From aee2240d5f75738a1c040a2ea6958bc578fb418a Mon Sep 17 00:00:00 2001 From: Fabio Alessandrelli Date: Sat, 5 Feb 2022 01:43:47 +0100 Subject: [PATCH] [Net] Allow branch-specific MultiplayerAPIs. Removes custom_multiplayer from Node. MultiplayerAPI overrides are now set at SceneTree level, and apply to whole branches. Impact on performance when using only the default multiplayer or overriding it is minimal, the use of branches can likely be further optimized by caching nodes and relevant MultiplayerAPI IDs. --- doc/classes/MultiplayerAPI.xml | 4 +-- doc/classes/Node.xml | 5 +-- doc/classes/SceneTree.xml | 18 ++++++++-- scene/main/node.cpp | 16 +-------- scene/main/node.h | 2 -- scene/main/scene_tree.cpp | 63 +++++++++++++++++++++++++++------- scene/main/scene_tree.h | 5 +-- 7 files changed, 73 insertions(+), 40 deletions(-) diff --git a/doc/classes/MultiplayerAPI.xml b/doc/classes/MultiplayerAPI.xml index ac17886183e..059d1479794 100644 --- a/doc/classes/MultiplayerAPI.xml +++ b/doc/classes/MultiplayerAPI.xml @@ -6,7 +6,7 @@ This class implements the high-level multiplayer API. See also [MultiplayerPeer]. By default, [SceneTree] has a reference to this class that is used to provide multiplayer capabilities (i.e. RPCs) across the whole scene. - It is possible to override the MultiplayerAPI instance used by specific Nodes by setting the [member Node.custom_multiplayer] property, effectively allowing to run both client and server in the same scene. + It is possible to override the MultiplayerAPI instance used by specific tree branches by calling the [method SceneTree.set_multiplayer] method, effectively allowing to run both client and server in the same scene. [b]Note:[/b] The high-level multiplayer API protocol is an implementation detail and isn't meant to be used by non-Godot servers. It may change without notice. [b]Note:[/b] When exporting to Android, make sure to enable the [code]INTERNET[/code] permission in the Android export preset before exporting the project or using one-click deploy. Otherwise, network communication of any kind will be blocked by Android. @@ -53,7 +53,7 @@ - Method used for polling the MultiplayerAPI. You only need to worry about this if you are using [member Node.custom_multiplayer] override or you set [member SceneTree.multiplayer_poll] to [code]false[/code]. By default, [SceneTree] will poll its MultiplayerAPI for you. + Method used for polling the MultiplayerAPI. You only need to worry about this if you set [member SceneTree.multiplayer_poll] to [code]false[/code]. By default, [SceneTree] will poll its MultiplayerAPI(s) for you. [b]Note:[/b] This method results in RPCs being called, so they will be executed in the same context of this function (e.g. [code]_process[/code], [code]physics[/code], [Thread]). diff --git a/doc/classes/Node.xml b/doc/classes/Node.xml index 56c80078b35..c9795856d51 100644 --- a/doc/classes/Node.xml +++ b/doc/classes/Node.xml @@ -745,14 +745,11 @@ - - The override to the default [MultiplayerAPI]. Set to [code]null[/code] to use the default [SceneTree] one. - Add a custom description to a node. It will be displayed in a tooltip when hovered in editor's scene tree. - The [MultiplayerAPI] instance associated with this node. Either the [member custom_multiplayer], or the default SceneTree one (if inside tree). + The [MultiplayerAPI] instance associated with this node. See [method SceneTree.get_multiplayer]. The name of the node. This name is unique among the siblings (other child nodes from the same parent). When set to an existing name, the node will be automatically renamed. diff --git a/doc/classes/SceneTree.xml b/doc/classes/SceneTree.xml index 77023d2126f..ddff766b174 100644 --- a/doc/classes/SceneTree.xml +++ b/doc/classes/SceneTree.xml @@ -99,6 +99,13 @@ Returns the current frame number, i.e. the total frame count since the application started. + + + + + Return the [MultiplayerAPI] configured for the given path, or the default one if [code]for_path[/code] is empty. + + @@ -193,6 +200,14 @@ Sets the given [code]property[/code] to [code]value[/code] on all members of the given group, respecting the given [enum GroupCallFlags]. + + + + + + Sets a custom [MultiplayerAPI] with the given [code]root_path[/code] (controlling also the relative subpaths), or override the default one if [code]root_path[/code] is empty. + + @@ -215,9 +230,6 @@ The root of the edited scene. - - The default [MultiplayerAPI] instance for this [SceneTree]. - If [code]true[/code] (default value), enables automatic polling of the [MultiplayerAPI] for this SceneTree during [signal process_frame]. If [code]false[/code], you need to manually call [method MultiplayerAPI.poll] to process network packets and deliver RPCs. This allows running RPCs in a different loop (e.g. physics, thread, specific time step) and for manual [Mutex] protection when accessing the [MultiplayerAPI] from threads. diff --git a/scene/main/node.cpp b/scene/main/node.cpp index 25e266c38e2..f1c0260dd55 100644 --- a/scene/main/node.cpp +++ b/scene/main/node.cpp @@ -653,21 +653,10 @@ void Node::rpcp(int p_peer_id, const StringName &p_method, const Variant **p_arg } Ref Node::get_multiplayer() const { - if (multiplayer.is_valid()) { - return multiplayer; - } if (!is_inside_tree()) { return Ref(); } - return get_tree()->get_multiplayer(); -} - -Ref Node::get_custom_multiplayer() const { - return multiplayer; -} - -void Node::set_custom_multiplayer(Ref p_multiplayer) { - multiplayer = p_multiplayer; + return get_tree()->get_multiplayer(get_path()); } Vector Node::get_node_rpc_methods() const { @@ -2892,8 +2881,6 @@ void Node::_bind_methods() { ClassDB::bind_method(D_METHOD("is_multiplayer_authority"), &Node::is_multiplayer_authority); ClassDB::bind_method(D_METHOD("get_multiplayer"), &Node::get_multiplayer); - ClassDB::bind_method(D_METHOD("get_custom_multiplayer"), &Node::get_custom_multiplayer); - ClassDB::bind_method(D_METHOD("set_custom_multiplayer", "api"), &Node::set_custom_multiplayer); ClassDB::bind_method(D_METHOD("rpc_config", "method", "rpc_mode", "call_local", "transfer_mode", "channel"), &Node::rpc_config, DEFVAL(false), DEFVAL(Multiplayer::TRANSFER_MODE_RELIABLE), DEFVAL(0)); ClassDB::bind_method(D_METHOD("set_editor_description", "editor_description"), &Node::set_editor_description); @@ -2999,7 +2986,6 @@ void Node::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::STRING, "scene_file_path", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_scene_file_path", "get_scene_file_path"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "owner", PROPERTY_HINT_RESOURCE_TYPE, "Node", PROPERTY_USAGE_NONE), "set_owner", "get_owner"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "multiplayer", PROPERTY_HINT_RESOURCE_TYPE, "MultiplayerAPI", PROPERTY_USAGE_NONE), "", "get_multiplayer"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "custom_multiplayer", PROPERTY_HINT_RESOURCE_TYPE, "MultiplayerAPI", PROPERTY_USAGE_NONE), "set_custom_multiplayer", "get_custom_multiplayer"); ADD_GROUP("Process", "process_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "process_mode", PROPERTY_HINT_ENUM, "Inherit,Pausable,When Paused,Always,Disabled"), "set_process_mode", "get_process_mode"); diff --git a/scene/main/node.h b/scene/main/node.h index 923d03dcfed..fb84aabb62d 100644 --- a/scene/main/node.h +++ b/scene/main/node.h @@ -515,8 +515,6 @@ public: void rpcp(int p_peer_id, const StringName &p_method, const Variant **p_arg, int p_argcount); Ref get_multiplayer() const; - Ref get_custom_multiplayer() const; - void set_custom_multiplayer(Ref p_multiplayer); Node(); ~Node(); diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp index 151239c9e73..d42c2aadad7 100644 --- a/scene/main/scene_tree.cpp +++ b/scene/main/scene_tree.cpp @@ -438,6 +438,10 @@ bool SceneTree::process(double p_time) { if (multiplayer_poll) { multiplayer->poll(); + const NodePath *rpath = nullptr; + while ((rpath = custom_multiplayers.next(rpath))) { + custom_multiplayers[*rpath]->poll(); + } } emit_signal(SNAME("process_frame")); @@ -1133,8 +1137,51 @@ Array SceneTree::get_processed_tweens() { return ret; } -Ref SceneTree::get_multiplayer() const { - return multiplayer; +Ref SceneTree::get_multiplayer(const NodePath &p_for_path) const { + Ref out = multiplayer; + const NodePath *spath = nullptr; + while ((spath = custom_multiplayers.next(spath))) { + const Vector snames = (*spath).get_names(); + const Vector tnames = p_for_path.get_names(); + if (tnames.size() < snames.size()) { + continue; + } + const StringName *sptr = snames.ptr(); + const StringName *nptr = tnames.ptr(); + bool valid = true; + for (int i = 0; i < snames.size(); i++) { + if (sptr[i] != nptr[i]) { + valid = false; + break; + } + } + if (valid) { + out = custom_multiplayers[*spath]; + break; + } + } + return out; +} + +void SceneTree::set_multiplayer(Ref p_multiplayer, const NodePath &p_root_path) { + if (p_root_path.is_empty()) { + ERR_FAIL_COND(!p_multiplayer.is_valid()); + if (multiplayer.is_valid()) { + multiplayer->set_root_path(NodePath()); + } + multiplayer = p_multiplayer; + multiplayer->set_root_path("/" + root->get_name()); + } else { + if (p_multiplayer.is_valid()) { + custom_multiplayers[p_root_path] = p_multiplayer; + p_multiplayer->set_root_path(p_root_path); + } else { + if (custom_multiplayers.has(p_root_path)) { + custom_multiplayers[p_root_path]->set_root_path(NodePath()); + custom_multiplayers.erase(p_root_path); + } + } + } } void SceneTree::set_multiplayer_poll_enabled(bool p_enabled) { @@ -1145,13 +1192,6 @@ bool SceneTree::is_multiplayer_poll_enabled() const { return multiplayer_poll; } -void SceneTree::set_multiplayer(Ref p_multiplayer) { - ERR_FAIL_COND(!p_multiplayer.is_valid()); - - multiplayer = p_multiplayer; - multiplayer->set_root_path("/" + root->get_name()); -} - void SceneTree::_bind_methods() { ClassDB::bind_method(D_METHOD("get_root"), &SceneTree::get_root); ClassDB::bind_method(D_METHOD("has_group", "name"), &SceneTree::has_group); @@ -1214,8 +1254,8 @@ void SceneTree::_bind_methods() { ClassDB::bind_method(D_METHOD("_change_scene"), &SceneTree::_change_scene); - ClassDB::bind_method(D_METHOD("set_multiplayer", "multiplayer"), &SceneTree::set_multiplayer); - ClassDB::bind_method(D_METHOD("get_multiplayer"), &SceneTree::get_multiplayer); + ClassDB::bind_method(D_METHOD("set_multiplayer", "multiplayer", "root_path"), &SceneTree::set_multiplayer, DEFVAL(NodePath())); + ClassDB::bind_method(D_METHOD("get_multiplayer", "for_path"), &SceneTree::get_multiplayer, DEFVAL(NodePath())); ClassDB::bind_method(D_METHOD("set_multiplayer_poll_enabled", "enabled"), &SceneTree::set_multiplayer_poll_enabled); ClassDB::bind_method(D_METHOD("is_multiplayer_poll_enabled"), &SceneTree::is_multiplayer_poll_enabled); @@ -1225,7 +1265,6 @@ void SceneTree::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "edited_scene_root", PROPERTY_HINT_RESOURCE_TYPE, "Node", PROPERTY_USAGE_NONE), "set_edited_scene_root", "get_edited_scene_root"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "current_scene", PROPERTY_HINT_RESOURCE_TYPE, "Node", PROPERTY_USAGE_NONE), "set_current_scene", "get_current_scene"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "root", PROPERTY_HINT_RESOURCE_TYPE, "Node", PROPERTY_USAGE_NONE), "", "get_root"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "multiplayer", PROPERTY_HINT_RESOURCE_TYPE, "MultiplayerAPI", PROPERTY_USAGE_NONE), "set_multiplayer", "get_multiplayer"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "multiplayer_poll"), "set_multiplayer_poll_enabled", "is_multiplayer_poll_enabled"); ADD_SIGNAL(MethodInfo("tree_changed")); diff --git a/scene/main/scene_tree.h b/scene/main/scene_tree.h index 705ca6ebd30..9d7757e0a37 100644 --- a/scene/main/scene_tree.h +++ b/scene/main/scene_tree.h @@ -159,6 +159,7 @@ private: ///network/// Ref multiplayer; + HashMap> custom_multiplayers; bool multiplayer_poll = true; static SceneTree *singleton; @@ -351,10 +352,10 @@ public: //network API - Ref get_multiplayer() const; + Ref get_multiplayer(const NodePath &p_for_path = NodePath()) const; + void set_multiplayer(Ref p_multiplayer, const NodePath &p_root_path = NodePath()); void set_multiplayer_poll_enabled(bool p_enabled); bool is_multiplayer_poll_enabled() const; - void set_multiplayer(Ref p_multiplayer); static void add_idle_callback(IdleCallback p_callback);