diff options
Diffstat (limited to 'modules/multiplayer')
26 files changed, 1736 insertions, 738 deletions
diff --git a/modules/multiplayer/SCsub b/modules/multiplayer/SCsub index ff33655537..e89038c3e0 100644 --- a/modules/multiplayer/SCsub +++ b/modules/multiplayer/SCsub @@ -8,7 +8,7 @@ env_mp = env_modules.Clone() module_obj = [] env_mp.add_source_files(module_obj, "*.cpp") -if env["tools"]: +if env.editor_build: env_mp.add_source_files(module_obj, "editor/*.cpp") env.modules_sources += module_obj diff --git a/modules/multiplayer/doc_classes/MultiplayerSpawner.xml b/modules/multiplayer/doc_classes/MultiplayerSpawner.xml index c0265c9161..a3ca2d6486 100644 --- a/modules/multiplayer/doc_classes/MultiplayerSpawner.xml +++ b/modules/multiplayer/doc_classes/MultiplayerSpawner.xml @@ -6,7 +6,6 @@ <description> Spawnable scenes can be configured in the editor or through code (see [method add_spawnable_scene]). Also supports custom node spawns through [method spawn], calling [method _spawn_custom] on all peers. - Internally, [MultiplayerSpawner] uses [method MultiplayerAPI.object_configuration_add] to notify spawns passing the spawned node as the [code]object[/code] and itself as the [code]configuration[/code], and [method MultiplayerAPI.object_configuration_remove] to notify despawns in a similar way. </description> <tutorials> @@ -17,8 +16,7 @@ <param index="0" name="data" type="Variant" /> <description> Method called on all peers when a custom spawn was requested by the authority using [method spawn]. Should return a [Node] that is not in the scene tree. - - [b]Note:[/b] Spawned nodes should [b]not[/b] be added to the scene with `add_child`. This is done automatically. + [b]Note:[/b] Spawned nodes should [b]not[/b] be added to the scene with [method Node.add_child]. This is done automatically. </description> </method> <method name="add_spawnable_scene"> @@ -52,7 +50,6 @@ <param index="0" name="data" type="Variant" default="null" /> <description> Requests a custom spawn, with [code]data[/code] passed to [method _spawn_custom] on all peers. Returns the locally spawned node instance already inside the scene tree, and added as a child of the node pointed by [member spawn_path]. - [b]Note:[/b] Spawnable scenes are spawned automatically. [method spawn] is only needed for custom spawns. </description> </method> @@ -60,7 +57,6 @@ <members> <member name="spawn_limit" type="int" setter="set_spawn_limit" getter="get_spawn_limit" default="0"> Maximum nodes that is allowed to be spawned by this spawner. Includes both spawnable scenes and custom spawns. - When set to [code]0[/code] (the default), there is no limit. </member> <member name="spawn_path" type="NodePath" setter="set_spawn_path" getter="get_spawn_path" default="NodePath("")"> diff --git a/modules/multiplayer/doc_classes/MultiplayerSynchronizer.xml b/modules/multiplayer/doc_classes/MultiplayerSynchronizer.xml index 9a4d755d64..7ed6255a62 100644 --- a/modules/multiplayer/doc_classes/MultiplayerSynchronizer.xml +++ b/modules/multiplayer/doc_classes/MultiplayerSynchronizer.xml @@ -5,10 +5,8 @@ </brief_description> <description> By default, [MultiplayerSynchronizer] synchronizes configured properties to all peers. - Visiblity can be handled directly with [method set_visibility_for] or as-needed with [method add_visibility_filter] and [method update_visibility]. - + Visibility can be handled directly with [method set_visibility_for] or as-needed with [method add_visibility_filter] and [method update_visibility]. [MultiplayerSpawner]s will handle nodes according to visibility of synchronizers as long as the node at [member root_path] was spawned by one. - Internally, [MultiplayerSynchronizer] uses [method MultiplayerAPI.object_configuration_add] to notify synchronization start passing the [Node] at [member root_path] as the [code]object[/code] and itself as the [code]configuration[/code], and uses [method MultiplayerAPI.object_configuration_remove] to notify synchronization end in a similar way. </description> <tutorials> @@ -19,7 +17,6 @@ <param index="0" name="filter" type="Callable" /> <description> Adds a peer visibility filter for this synchronizer. - [code]filter[/code] should take a peer id [int] and return a [bool]. </description> </method> @@ -34,7 +31,7 @@ <return type="void" /> <param index="0" name="filter" type="Callable" /> <description> - Removes a peer visiblity filter from this synchronizer. + Removes a peer visibility filter from this synchronizer. </description> </method> <method name="set_visibility_for"> diff --git a/modules/multiplayer/doc_classes/SceneMultiplayer.xml b/modules/multiplayer/doc_classes/SceneMultiplayer.xml index 62bb396d15..e4e2b4f631 100644 --- a/modules/multiplayer/doc_classes/SceneMultiplayer.xml +++ b/modules/multiplayer/doc_classes/SceneMultiplayer.xml @@ -19,6 +19,35 @@ Clears the current SceneMultiplayer network state (you shouldn't call this unless you know what you are doing). </description> </method> + <method name="complete_auth"> + <return type="int" enum="Error" /> + <param index="0" name="id" type="int" /> + <description> + Mark the authentication step as completed for the remote peer identified by [param id]. The [signal MultiplayerAPI.peer_connected] signal will be emitted for this peer once the remote side also completes the authentication. No further authentication messages are expected to be received from this peer. + If a peer disconnects before completing authentication, either due to a network issue, the [member auth_timeout] expiring, or manually calling [method disconnect_peer], the [signal peer_authentication_failed] signal will be emitted instead of [signal MultiplayerAPI.peer_disconnected]. + </description> + </method> + <method name="disconnect_peer"> + <return type="void" /> + <param index="0" name="id" type="int" /> + <description> + Disconnects the peer identified by [param id], removing it from the list of connected peers, and closing the underlying connection with it. + </description> + </method> + <method name="get_authenticating_peers"> + <return type="PackedInt32Array" /> + <description> + Returns the IDs of the peers currently trying to authenticate with this [MultiplayerAPI]. + </description> + </method> + <method name="send_auth"> + <return type="int" enum="Error" /> + <param index="0" name="id" type="int" /> + <param index="1" name="data" type="PackedByteArray" /> + <description> + Sends the specified [param data] to the remote peer identified by [param id] as part of an authentication message. This can be used to authenticate peers, and control when [signal MultiplayerAPI.peer_connected] is emitted (and the remote peer accepted as one of the connected peers). + </description> + </method> <method name="send_bytes"> <return type="int" enum="Error" /> <param index="0" name="bytes" type="PackedByteArray" /> @@ -35,6 +64,12 @@ If [code]true[/code], the MultiplayerAPI will allow encoding and decoding of object during RPCs. [b]Warning:[/b] Deserialized objects can contain code which gets executed. Do not use this option if the serialized object comes from untrusted sources to avoid potential security threat such as remote code execution. </member> + <member name="auth_callback" type="Callable" setter="set_auth_callback" getter="get_auth_callback"> + The callback to execute when when receiving authentication data sent via [method send_auth]. If the [Callable] is empty (default), peers will be automatically accepted as soon as they connect. + </member> + <member name="auth_timeout" type="float" setter="set_auth_timeout" getter="get_auth_timeout" default="3.0"> + If set to a value greater than [code]0.0[/code], the maximum amount of time peers can stay in the authenticating state, after which the authentication will automatically fail. See the [signal peer_authenticating] and [signal peer_authentication_failed] signals. + </member> <member name="refuse_new_connections" type="bool" setter="set_refuse_new_connections" getter="is_refusing_new_connections" default="false"> If [code]true[/code], the MultiplayerAPI's [member MultiplayerAPI.multiplayer_peer] refuses new incoming connections. </member> @@ -42,8 +77,24 @@ The root path to use for RPCs and replication. Instead of an absolute path, a relative path will be used to find the node upon which the RPC should be executed. This effectively allows to have different branches of the scene tree to be managed by different MultiplayerAPI, allowing for example to run both client and server in the same scene. </member> + <member name="server_relay" type="bool" setter="set_server_relay_enabled" getter="is_server_relay_enabled" default="true"> + Enable or disable the server feature that notifies clients of other peers' connection/disconnection, and relays messages between them. When this option is [code]false[/code], clients won't be automatically notified of other peers and won't be able to send them packets through the server. + [b]Note:[/b] Support for this feature may depend on the current [MultiplayerPeer] configuration. See [method MultiplayerPeer.is_server_relay_supported]. + </member> </members> <signals> + <signal name="peer_authenticating"> + <param index="0" name="id" type="int" /> + <description> + Emitted when this MultiplayerAPI's [member MultiplayerAPI.multiplayer_peer] connects to a new peer and a valid [member auth_callback] is set. In this case, the [signal MultiplayerAPI.peer_connected] will not be emitted until [method complete_auth] is called with given peer [param id]. While in this state, the peer will not be included in the list returned by [method MultiplayerAPI.get_peers] (but in the one returned by [method get_authenticating_peers]), and only authentication data will be sent or received. See [method send_auth] for sending authentication data. + </description> + </signal> + <signal name="peer_authentication_failed"> + <param index="0" name="id" type="int" /> + <description> + Emitted when this MultiplayerAPI's [member MultiplayerAPI.multiplayer_peer] disconnects from a peer for which authentication had not yet completed. See [signal peer_authenticating]. + </description> + </signal> <signal name="peer_packet"> <param index="0" name="id" type="int" /> <param index="1" name="packet" type="PackedByteArray" /> diff --git a/modules/multiplayer/editor/editor_network_profiler.cpp b/modules/multiplayer/editor/editor_network_profiler.cpp new file mode 100644 index 0000000000..a7e5b80b66 --- /dev/null +++ b/modules/multiplayer/editor/editor_network_profiler.cpp @@ -0,0 +1,197 @@ +/*************************************************************************/ +/* editor_network_profiler.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "editor_network_profiler.h" + +#include "core/os/os.h" +#include "editor/editor_scale.h" +#include "editor/editor_settings.h" + +void EditorNetworkProfiler::_bind_methods() { + ADD_SIGNAL(MethodInfo("enable_profiling", PropertyInfo(Variant::BOOL, "enable"))); +} + +void EditorNetworkProfiler::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + activate->set_icon(get_theme_icon(SNAME("Play"), SNAME("EditorIcons"))); + clear_button->set_icon(get_theme_icon(SNAME("Clear"), SNAME("EditorIcons"))); + incoming_bandwidth_text->set_right_icon(get_theme_icon(SNAME("ArrowDown"), SNAME("EditorIcons"))); + outgoing_bandwidth_text->set_right_icon(get_theme_icon(SNAME("ArrowUp"), SNAME("EditorIcons"))); + + // This needs to be done here to set the faded color when the profiler is first opened + incoming_bandwidth_text->add_theme_color_override("font_uneditable_color", get_theme_color(SNAME("font_color"), SNAME("Editor")) * Color(1, 1, 1, 0.5)); + outgoing_bandwidth_text->add_theme_color_override("font_uneditable_color", get_theme_color(SNAME("font_color"), SNAME("Editor")) * Color(1, 1, 1, 0.5)); + } break; + } +} + +void EditorNetworkProfiler::_update_frame() { + counters_display->clear(); + + TreeItem *root = counters_display->create_item(); + + for (const KeyValue<ObjectID, RPCNodeInfo> &E : nodes_data) { + TreeItem *node = counters_display->create_item(root); + + for (int j = 0; j < counters_display->get_columns(); ++j) { + node->set_text_alignment(j, j > 0 ? HORIZONTAL_ALIGNMENT_RIGHT : HORIZONTAL_ALIGNMENT_LEFT); + } + + node->set_text(0, E.value.node_path); + node->set_text(1, E.value.incoming_rpc == 0 ? "-" : itos(E.value.incoming_rpc)); + node->set_text(2, E.value.outgoing_rpc == 0 ? "-" : itos(E.value.outgoing_rpc)); + } +} + +void EditorNetworkProfiler::_activate_pressed() { + if (activate->is_pressed()) { + activate->set_icon(get_theme_icon(SNAME("Stop"), SNAME("EditorIcons"))); + activate->set_text(TTR("Stop")); + } else { + activate->set_icon(get_theme_icon(SNAME("Play"), SNAME("EditorIcons"))); + activate->set_text(TTR("Start")); + } + emit_signal(SNAME("enable_profiling"), activate->is_pressed()); +} + +void EditorNetworkProfiler::_clear_pressed() { + nodes_data.clear(); + set_bandwidth(0, 0); + if (frame_delay->is_stopped()) { + frame_delay->set_wait_time(0.1); + frame_delay->start(); + } +} + +void EditorNetworkProfiler::add_node_frame_data(const RPCNodeInfo p_frame) { + if (!nodes_data.has(p_frame.node)) { + nodes_data.insert(p_frame.node, p_frame); + } else { + nodes_data[p_frame.node].incoming_rpc += p_frame.incoming_rpc; + nodes_data[p_frame.node].outgoing_rpc += p_frame.outgoing_rpc; + } + + if (frame_delay->is_stopped()) { + frame_delay->set_wait_time(0.1); + frame_delay->start(); + } +} + +void EditorNetworkProfiler::set_bandwidth(int p_incoming, int p_outgoing) { + incoming_bandwidth_text->set_text(vformat(TTR("%s/s"), String::humanize_size(p_incoming))); + outgoing_bandwidth_text->set_text(vformat(TTR("%s/s"), String::humanize_size(p_outgoing))); + + // Make labels more prominent when the bandwidth is greater than 0 to attract user attention + incoming_bandwidth_text->add_theme_color_override( + "font_uneditable_color", + get_theme_color(SNAME("font_color"), SNAME("Editor")) * Color(1, 1, 1, p_incoming > 0 ? 1 : 0.5)); + outgoing_bandwidth_text->add_theme_color_override( + "font_uneditable_color", + get_theme_color(SNAME("font_color"), SNAME("Editor")) * Color(1, 1, 1, p_outgoing > 0 ? 1 : 0.5)); +} + +bool EditorNetworkProfiler::is_profiling() { + return activate->is_pressed(); +} + +EditorNetworkProfiler::EditorNetworkProfiler() { + HBoxContainer *hb = memnew(HBoxContainer); + hb->add_theme_constant_override("separation", 8 * EDSCALE); + add_child(hb); + + activate = memnew(Button); + activate->set_toggle_mode(true); + activate->set_text(TTR("Start")); + activate->connect("pressed", callable_mp(this, &EditorNetworkProfiler::_activate_pressed)); + hb->add_child(activate); + + clear_button = memnew(Button); + clear_button->set_text(TTR("Clear")); + clear_button->connect("pressed", callable_mp(this, &EditorNetworkProfiler::_clear_pressed)); + hb->add_child(clear_button); + + hb->add_spacer(); + + Label *lb = memnew(Label); + lb->set_text(TTR("Down")); + hb->add_child(lb); + + incoming_bandwidth_text = memnew(LineEdit); + incoming_bandwidth_text->set_editable(false); + incoming_bandwidth_text->set_custom_minimum_size(Size2(120, 0) * EDSCALE); + incoming_bandwidth_text->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT); + hb->add_child(incoming_bandwidth_text); + + Control *down_up_spacer = memnew(Control); + down_up_spacer->set_custom_minimum_size(Size2(30, 0) * EDSCALE); + hb->add_child(down_up_spacer); + + lb = memnew(Label); + lb->set_text(TTR("Up")); + hb->add_child(lb); + + outgoing_bandwidth_text = memnew(LineEdit); + outgoing_bandwidth_text->set_editable(false); + outgoing_bandwidth_text->set_custom_minimum_size(Size2(120, 0) * EDSCALE); + outgoing_bandwidth_text->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT); + hb->add_child(outgoing_bandwidth_text); + + // Set initial texts in the incoming/outgoing bandwidth labels + set_bandwidth(0, 0); + + counters_display = memnew(Tree); + counters_display->set_custom_minimum_size(Size2(300, 0) * EDSCALE); + counters_display->set_v_size_flags(SIZE_EXPAND_FILL); + counters_display->set_hide_folding(true); + counters_display->set_hide_root(true); + counters_display->set_columns(3); + counters_display->set_column_titles_visible(true); + counters_display->set_column_title(0, TTR("Node")); + counters_display->set_column_expand(0, true); + counters_display->set_column_clip_content(0, true); + counters_display->set_column_custom_minimum_width(0, 60 * EDSCALE); + counters_display->set_column_title(1, TTR("Incoming RPC")); + counters_display->set_column_expand(1, false); + counters_display->set_column_clip_content(1, true); + counters_display->set_column_custom_minimum_width(1, 120 * EDSCALE); + counters_display->set_column_title(2, TTR("Outgoing RPC")); + counters_display->set_column_expand(2, false); + counters_display->set_column_clip_content(2, true); + counters_display->set_column_custom_minimum_width(2, 120 * EDSCALE); + add_child(counters_display); + + frame_delay = memnew(Timer); + frame_delay->set_wait_time(0.1); + frame_delay->set_one_shot(true); + add_child(frame_delay); + frame_delay->connect("timeout", callable_mp(this, &EditorNetworkProfiler::_update_frame)); +} diff --git a/modules/multiplayer/editor/editor_network_profiler.h b/modules/multiplayer/editor/editor_network_profiler.h new file mode 100644 index 0000000000..98d12e3c0a --- /dev/null +++ b/modules/multiplayer/editor/editor_network_profiler.h @@ -0,0 +1,76 @@ +/*************************************************************************/ +/* editor_network_profiler.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef EDITOR_NETWORK_PROFILER_H +#define EDITOR_NETWORK_PROFILER_H + +#include "scene/debugger/scene_debugger.h" +#include "scene/gui/box_container.h" +#include "scene/gui/button.h" +#include "scene/gui/label.h" +#include "scene/gui/split_container.h" +#include "scene/gui/tree.h" + +#include "../multiplayer_debugger.h" + +class EditorNetworkProfiler : public VBoxContainer { + GDCLASS(EditorNetworkProfiler, VBoxContainer) + +private: + using RPCNodeInfo = MultiplayerDebugger::RPCNodeInfo; + + Button *activate = nullptr; + Button *clear_button = nullptr; + Tree *counters_display = nullptr; + LineEdit *incoming_bandwidth_text = nullptr; + LineEdit *outgoing_bandwidth_text = nullptr; + + Timer *frame_delay = nullptr; + + HashMap<ObjectID, RPCNodeInfo> nodes_data; + + void _update_frame(); + + void _activate_pressed(); + void _clear_pressed(); + +protected: + void _notification(int p_what); + static void _bind_methods(); + +public: + void add_node_frame_data(const RPCNodeInfo p_frame); + void set_bandwidth(int p_incoming, int p_outgoing); + bool is_profiling(); + + EditorNetworkProfiler(); +}; + +#endif // EDITOR_NETWORK_PROFILER_H diff --git a/modules/multiplayer/editor/multiplayer_editor_plugin.cpp b/modules/multiplayer/editor/multiplayer_editor_plugin.cpp new file mode 100644 index 0000000000..00b1537827 --- /dev/null +++ b/modules/multiplayer/editor/multiplayer_editor_plugin.cpp @@ -0,0 +1,140 @@ +/*************************************************************************/ +/* multiplayer_editor_plugin.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "multiplayer_editor_plugin.h" + +#include "../multiplayer_synchronizer.h" +#include "editor_network_profiler.h" +#include "replication_editor.h" + +#include "editor/editor_node.h" + +bool MultiplayerEditorDebugger::has_capture(const String &p_capture) const { + return p_capture == "multiplayer"; +} + +bool MultiplayerEditorDebugger::capture(const String &p_message, const Array &p_data, int p_session) { + ERR_FAIL_COND_V(!profilers.has(p_session), false); + EditorNetworkProfiler *profiler = profilers[p_session]; + if (p_message == "multiplayer:rpc") { + MultiplayerDebugger::RPCFrame frame; + frame.deserialize(p_data); + for (int i = 0; i < frame.infos.size(); i++) { + profiler->add_node_frame_data(frame.infos[i]); + } + return true; + + } else if (p_message == "multiplayer:bandwidth") { + ERR_FAIL_COND_V(p_data.size() < 2, false); + profiler->set_bandwidth(p_data[0], p_data[1]); + return true; + } + return false; +} + +void MultiplayerEditorDebugger::_profiler_activate(bool p_enable, int p_session_id) { + Ref<EditorDebuggerSession> session = get_session(p_session_id); + ERR_FAIL_COND(session.is_null()); + session->toggle_profiler("multiplayer", p_enable); + session->toggle_profiler("rpc", p_enable); +} + +void MultiplayerEditorDebugger::setup_session(int p_session_id) { + Ref<EditorDebuggerSession> session = get_session(p_session_id); + ERR_FAIL_COND(session.is_null()); + EditorNetworkProfiler *profiler = memnew(EditorNetworkProfiler); + profiler->connect("enable_profiling", callable_mp(this, &MultiplayerEditorDebugger::_profiler_activate).bind(p_session_id)); + profiler->set_name(TTR("Network Profiler")); + session->add_session_tab(profiler); + profilers[p_session_id] = profiler; +} + +MultiplayerEditorPlugin::MultiplayerEditorPlugin() { + repl_editor = memnew(ReplicationEditor); + button = EditorNode::get_singleton()->add_bottom_panel_item(TTR("Replication"), repl_editor); + button->hide(); + repl_editor->get_pin()->connect("pressed", callable_mp(this, &MultiplayerEditorPlugin::_pinned)); + debugger.instantiate(); +} + +MultiplayerEditorPlugin::~MultiplayerEditorPlugin() { +} + +void MultiplayerEditorPlugin::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + get_tree()->connect("node_removed", callable_mp(this, &MultiplayerEditorPlugin::_node_removed)); + add_debugger_plugin(debugger); + } break; + case NOTIFICATION_EXIT_TREE: { + remove_debugger_plugin(debugger); + } + } +} + +void MultiplayerEditorPlugin::_node_removed(Node *p_node) { + if (p_node && p_node == repl_editor->get_current()) { + repl_editor->edit(nullptr); + if (repl_editor->is_visible_in_tree()) { + EditorNode::get_singleton()->hide_bottom_panel(); + } + button->hide(); + repl_editor->get_pin()->set_pressed(false); + } +} + +void MultiplayerEditorPlugin::_pinned() { + if (!repl_editor->get_pin()->is_pressed()) { + if (repl_editor->is_visible_in_tree()) { + EditorNode::get_singleton()->hide_bottom_panel(); + } + button->hide(); + } +} + +void MultiplayerEditorPlugin::edit(Object *p_object) { + repl_editor->edit(Object::cast_to<MultiplayerSynchronizer>(p_object)); +} + +bool MultiplayerEditorPlugin::handles(Object *p_object) const { + return p_object->is_class("MultiplayerSynchronizer"); +} + +void MultiplayerEditorPlugin::make_visible(bool p_visible) { + if (p_visible) { + button->show(); + EditorNode::get_singleton()->make_bottom_panel_item_visible(repl_editor); + } else if (!repl_editor->get_pin()->is_pressed()) { + if (repl_editor->is_visible_in_tree()) { + EditorNode::get_singleton()->hide_bottom_panel(); + } + button->hide(); + } +} diff --git a/modules/multiplayer/editor/multiplayer_editor_plugin.h b/modules/multiplayer/editor/multiplayer_editor_plugin.h new file mode 100644 index 0000000000..6d1514cdb1 --- /dev/null +++ b/modules/multiplayer/editor/multiplayer_editor_plugin.h @@ -0,0 +1,81 @@ +/*************************************************************************/ +/* multiplayer_editor_plugin.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef MULTIPLAYER_EDITOR_PLUGIN_H +#define MULTIPLAYER_EDITOR_PLUGIN_H + +#include "editor/editor_plugin.h" + +#include "editor/plugins/editor_debugger_plugin.h" + +class EditorNetworkProfiler; +class MultiplayerEditorDebugger : public EditorDebuggerPlugin { + GDCLASS(MultiplayerEditorDebugger, EditorDebuggerPlugin); + +private: + HashMap<int, EditorNetworkProfiler *> profilers; + + void _profiler_activate(bool p_enable, int p_session_id); + +public: + virtual bool has_capture(const String &p_capture) const override; + virtual bool capture(const String &p_message, const Array &p_data, int p_index) override; + virtual void setup_session(int p_session_id) override; + + MultiplayerEditorDebugger() {} +}; + +class ReplicationEditor; + +class MultiplayerEditorPlugin : public EditorPlugin { + GDCLASS(MultiplayerEditorPlugin, EditorPlugin); + +private: + Button *button = nullptr; + ReplicationEditor *repl_editor = nullptr; + Ref<MultiplayerEditorDebugger> debugger; + + void _node_removed(Node *p_node); + + void _pinned(); + +protected: + void _notification(int p_what); + +public: + virtual void edit(Object *p_object) override; + virtual bool handles(Object *p_object) const override; + virtual void make_visible(bool p_visible) override; + + MultiplayerEditorPlugin(); + ~MultiplayerEditorPlugin(); +}; + +#endif // MULTIPLAYER_EDITOR_PLUGIN_H diff --git a/modules/multiplayer/editor/replication_editor_plugin.cpp b/modules/multiplayer/editor/replication_editor.cpp index 50f1434ad8..ccde9fed22 100644 --- a/modules/multiplayer/editor/replication_editor_plugin.cpp +++ b/modules/multiplayer/editor/replication_editor.cpp @@ -1,5 +1,5 @@ /*************************************************************************/ -/* replication_editor_plugin.cpp */ +/* replication_editor.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,14 +28,17 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "replication_editor_plugin.h" +#include "replication_editor.h" + +#include "../multiplayer_synchronizer.h" #include "editor/editor_node.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" +#include "editor/editor_undo_redo_manager.h" #include "editor/inspector_dock.h" +#include "editor/property_selector.h" #include "editor/scene_tree_editor.h" -#include "modules/multiplayer/multiplayer_synchronizer.h" #include "scene/gui/dialogs.h" #include "scene/gui/separator.h" #include "scene/gui/tree.h" @@ -139,7 +142,7 @@ void ReplicationEditor::_add_sync_property(String p_path) { return; } - UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo(); + Ref<EditorUndoRedoManager> &undo_redo = EditorNode::get_singleton()->get_undo_redo(); undo_redo->create_action(TTR("Add property to synchronizer")); if (config.is_null()) { @@ -199,7 +202,7 @@ ReplicationEditor::ReplicationEditor() { add_pick_button = memnew(Button); add_pick_button->connect("pressed", callable_mp(this, &ReplicationEditor::_pick_new_property)); - add_pick_button->set_text(TTR("Add property to sync..")); + add_pick_button->set_text(TTR("Add property to sync...")); hb->add_child(add_pick_button); VSeparator *vs = memnew(VSeparator); vs->set_custom_minimum_size(Size2(30 * EDSCALE, 0)); @@ -354,7 +357,7 @@ void ReplicationEditor::_tree_item_edited() { int column = tree->get_edited_column(); ERR_FAIL_COND(column < 1 || column > 2); const NodePath prop = ti->get_metadata(0); - UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo(); + Ref<EditorUndoRedoManager> &undo_redo = EditorNode::get_undo_redo(); bool value = ti->is_checked(column); String method; if (column == 1) { @@ -394,7 +397,7 @@ void ReplicationEditor::_dialog_closed(bool p_confirmed) { int idx = config->property_get_index(prop); bool spawn = config->property_get_spawn(prop); bool sync = config->property_get_sync(prop); - UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo(); + Ref<EditorUndoRedoManager> &undo_redo = EditorNode::get_undo_redo(); undo_redo->create_action(TTR("Remove Property")); undo_redo->add_do_method(config.ptr(), "remove_property", prop); undo_redo->add_undo_method(config.ptr(), "add_property", prop, idx); @@ -491,62 +494,3 @@ void ReplicationEditor::_add_property(const NodePath &p_property, bool p_spawn, item->set_checked(2, p_sync); item->set_editable(2, true); } - -/// ReplicationEditorPlugin -ReplicationEditorPlugin::ReplicationEditorPlugin() { - repl_editor = memnew(ReplicationEditor); - button = EditorNode::get_singleton()->add_bottom_panel_item(TTR("Replication"), repl_editor); - button->hide(); - repl_editor->get_pin()->connect("pressed", callable_mp(this, &ReplicationEditorPlugin::_pinned)); -} - -ReplicationEditorPlugin::~ReplicationEditorPlugin() { -} - -void ReplicationEditorPlugin::_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_ENTER_TREE: { - get_tree()->connect("node_removed", callable_mp(this, &ReplicationEditorPlugin::_node_removed)); - } break; - } -} - -void ReplicationEditorPlugin::_node_removed(Node *p_node) { - if (p_node && p_node == repl_editor->get_current()) { - repl_editor->edit(nullptr); - if (repl_editor->is_visible_in_tree()) { - EditorNode::get_singleton()->hide_bottom_panel(); - } - button->hide(); - repl_editor->get_pin()->set_pressed(false); - } -} - -void ReplicationEditorPlugin::_pinned() { - if (!repl_editor->get_pin()->is_pressed()) { - if (repl_editor->is_visible_in_tree()) { - EditorNode::get_singleton()->hide_bottom_panel(); - } - button->hide(); - } -} - -void ReplicationEditorPlugin::edit(Object *p_object) { - repl_editor->edit(Object::cast_to<MultiplayerSynchronizer>(p_object)); -} - -bool ReplicationEditorPlugin::handles(Object *p_object) const { - return p_object->is_class("MultiplayerSynchronizer"); -} - -void ReplicationEditorPlugin::make_visible(bool p_visible) { - if (p_visible) { - button->show(); - EditorNode::get_singleton()->make_bottom_panel_item_visible(repl_editor); - } else if (!repl_editor->get_pin()->is_pressed()) { - if (repl_editor->is_visible_in_tree()) { - EditorNode::get_singleton()->hide_bottom_panel(); - } - button->hide(); - } -} diff --git a/modules/multiplayer/editor/replication_editor_plugin.h b/modules/multiplayer/editor/replication_editor.h index e60e49cc25..8a48e8dbe7 100644 --- a/modules/multiplayer/editor/replication_editor_plugin.h +++ b/modules/multiplayer/editor/replication_editor.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* replication_editor_plugin.h */ +/* replication_editor.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,21 +28,21 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef REPLICATION_EDITOR_PLUGIN_H -#define REPLICATION_EDITOR_PLUGIN_H +#ifndef REPLICATION_EDITOR_H +#define REPLICATION_EDITOR_H #include "editor/editor_plugin.h" - -#include "editor/editor_spin_slider.h" -#include "editor/property_selector.h" - -#include "../scene_replication_config.h" +#include "modules/multiplayer/scene_replication_config.h" +#include "scene/gui/box_container.h" class ConfirmationDialog; class MultiplayerSynchronizer; -class SceneTreeDialog; +class AcceptDialog; +class LineEdit; class Tree; class TreeItem; +class PropertySelector; +class SceneTreeDialog; class ReplicationEditor : public VBoxContainer { GDCLASS(ReplicationEditor, VBoxContainer); @@ -105,27 +105,4 @@ public: ~ReplicationEditor() {} }; -class ReplicationEditorPlugin : public EditorPlugin { - GDCLASS(ReplicationEditorPlugin, EditorPlugin); - -private: - Button *button = nullptr; - ReplicationEditor *repl_editor = nullptr; - - void _node_removed(Node *p_node); - - void _pinned(); - -protected: - void _notification(int p_what); - -public: - virtual void edit(Object *p_object) override; - virtual bool handles(Object *p_object) const override; - virtual void make_visible(bool p_visible) override; - - ReplicationEditorPlugin(); - ~ReplicationEditorPlugin(); -}; - -#endif // REPLICATION_EDITOR_PLUGIN_H +#endif // REPLICATION_EDITOR_H diff --git a/modules/multiplayer/multiplayer_debugger.cpp b/modules/multiplayer/multiplayer_debugger.cpp new file mode 100644 index 0000000000..3d22af04dc --- /dev/null +++ b/modules/multiplayer/multiplayer_debugger.cpp @@ -0,0 +1,194 @@ +/*************************************************************************/ +/* multiplayer_debugger.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "multiplayer_debugger.h" + +#include "core/debugger/engine_debugger.h" +#include "scene/main/node.h" + +List<Ref<EngineProfiler>> multiplayer_profilers; + +void MultiplayerDebugger::initialize() { + Ref<BandwidthProfiler> bandwidth; + bandwidth.instantiate(); + bandwidth->bind("multiplayer"); + multiplayer_profilers.push_back(bandwidth); + + Ref<RPCProfiler> rpc_profiler; + rpc_profiler.instantiate(); + rpc_profiler->bind("rpc"); + multiplayer_profilers.push_back(rpc_profiler); +} + +void MultiplayerDebugger::deinitialize() { + multiplayer_profilers.clear(); +} + +// BandwidthProfiler + +int MultiplayerDebugger::BandwidthProfiler::bandwidth_usage(const Vector<BandwidthFrame> &p_buffer, int p_pointer) { + ERR_FAIL_COND_V(p_buffer.size() == 0, 0); + int total_bandwidth = 0; + + uint64_t timestamp = OS::get_singleton()->get_ticks_msec(); + uint64_t final_timestamp = timestamp - 1000; + + int i = (p_pointer + p_buffer.size() - 1) % p_buffer.size(); + + while (i != p_pointer && p_buffer[i].packet_size > 0) { + if (p_buffer[i].timestamp < final_timestamp) { + return total_bandwidth; + } + total_bandwidth += p_buffer[i].packet_size; + i = (i + p_buffer.size() - 1) % p_buffer.size(); + } + + ERR_FAIL_COND_V_MSG(i == p_pointer, total_bandwidth, "Reached the end of the bandwidth profiler buffer, values might be inaccurate."); + return total_bandwidth; +} + +void MultiplayerDebugger::BandwidthProfiler::toggle(bool p_enable, const Array &p_opts) { + if (!p_enable) { + bandwidth_in.clear(); + bandwidth_out.clear(); + } else { + bandwidth_in_ptr = 0; + bandwidth_in.resize(16384); // ~128kB + for (int i = 0; i < bandwidth_in.size(); ++i) { + bandwidth_in.write[i].packet_size = -1; + } + bandwidth_out_ptr = 0; + bandwidth_out.resize(16384); // ~128kB + for (int i = 0; i < bandwidth_out.size(); ++i) { + bandwidth_out.write[i].packet_size = -1; + } + } +} + +void MultiplayerDebugger::BandwidthProfiler::add(const Array &p_data) { + ERR_FAIL_COND(p_data.size() < 3); + const String inout = p_data[0]; + int time = p_data[1]; + int size = p_data[2]; + if (inout == "in") { + bandwidth_in.write[bandwidth_in_ptr].timestamp = time; + bandwidth_in.write[bandwidth_in_ptr].packet_size = size; + bandwidth_in_ptr = (bandwidth_in_ptr + 1) % bandwidth_in.size(); + } else if (inout == "out") { + bandwidth_out.write[bandwidth_out_ptr].timestamp = time; + bandwidth_out.write[bandwidth_out_ptr].packet_size = size; + bandwidth_out_ptr = (bandwidth_out_ptr + 1) % bandwidth_out.size(); + } +} + +void MultiplayerDebugger::BandwidthProfiler::tick(double p_frame_time, double p_process_time, double p_physics_time, double p_physics_frame_time) { + uint64_t pt = OS::get_singleton()->get_ticks_msec(); + if (pt - last_bandwidth_time > 200) { + last_bandwidth_time = pt; + int incoming_bandwidth = bandwidth_usage(bandwidth_in, bandwidth_in_ptr); + int outgoing_bandwidth = bandwidth_usage(bandwidth_out, bandwidth_out_ptr); + + Array arr; + arr.push_back(incoming_bandwidth); + arr.push_back(outgoing_bandwidth); + EngineDebugger::get_singleton()->send_message("multiplayer:bandwidth", arr); + } +} + +// RPCProfiler + +Array MultiplayerDebugger::RPCFrame::serialize() { + Array arr; + arr.push_back(infos.size() * 4); + for (int i = 0; i < infos.size(); ++i) { + arr.push_back(uint64_t(infos[i].node)); + arr.push_back(infos[i].node_path); + arr.push_back(infos[i].incoming_rpc); + arr.push_back(infos[i].outgoing_rpc); + } + return arr; +} + +bool MultiplayerDebugger::RPCFrame::deserialize(const Array &p_arr) { + ERR_FAIL_COND_V(p_arr.size() < 1, false); + uint32_t size = p_arr[0]; + ERR_FAIL_COND_V(size % 4, false); + ERR_FAIL_COND_V((uint32_t)p_arr.size() != size + 1, false); + infos.resize(size / 4); + int idx = 1; + for (uint32_t i = 0; i < size / 4; ++i) { + infos.write[i].node = uint64_t(p_arr[idx]); + infos.write[i].node_path = p_arr[idx + 1]; + infos.write[i].incoming_rpc = p_arr[idx + 2]; + infos.write[i].outgoing_rpc = p_arr[idx + 3]; + } + return true; +} + +void MultiplayerDebugger::RPCProfiler::init_node(const ObjectID p_node) { + if (rpc_node_data.has(p_node)) { + return; + } + rpc_node_data.insert(p_node, RPCNodeInfo()); + rpc_node_data[p_node].node = p_node; + rpc_node_data[p_node].node_path = Object::cast_to<Node>(ObjectDB::get_instance(p_node))->get_path(); + rpc_node_data[p_node].incoming_rpc = 0; + rpc_node_data[p_node].outgoing_rpc = 0; +} + +void MultiplayerDebugger::RPCProfiler::toggle(bool p_enable, const Array &p_opts) { + rpc_node_data.clear(); +} + +void MultiplayerDebugger::RPCProfiler::add(const Array &p_data) { + ERR_FAIL_COND(p_data.size() < 2); + const ObjectID id = p_data[0]; + const String what = p_data[1]; + init_node(id); + RPCNodeInfo &info = rpc_node_data[id]; + if (what == "rpc_in") { + info.incoming_rpc++; + } else if (what == "rpc_out") { + info.outgoing_rpc++; + } +} + +void MultiplayerDebugger::RPCProfiler::tick(double p_frame_time, double p_process_time, double p_physics_time, double p_physics_frame_time) { + uint64_t pt = OS::get_singleton()->get_ticks_msec(); + if (pt - last_profile_time > 100) { + last_profile_time = pt; + RPCFrame frame; + for (const KeyValue<ObjectID, RPCNodeInfo> &E : rpc_node_data) { + frame.infos.push_back(E.value); + } + rpc_node_data.clear(); + EngineDebugger::get_singleton()->send_message("multiplayer:rpc", frame.serialize()); + } +} diff --git a/modules/multiplayer/multiplayer_debugger.h b/modules/multiplayer/multiplayer_debugger.h new file mode 100644 index 0000000000..4efd1da016 --- /dev/null +++ b/modules/multiplayer/multiplayer_debugger.h @@ -0,0 +1,95 @@ +/*************************************************************************/ +/* multiplayer_debugger.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef MULTIPLAYER_DEBUGGER_H +#define MULTIPLAYER_DEBUGGER_H + +#include "core/debugger/engine_profiler.h" + +#include "core/os/os.h" + +class MultiplayerDebugger { +public: + struct RPCNodeInfo { + ObjectID node; + String node_path; + int incoming_rpc = 0; + int outgoing_rpc = 0; + }; + + struct RPCFrame { + Vector<RPCNodeInfo> infos; + + Array serialize(); + bool deserialize(const Array &p_arr); + }; + +private: + class BandwidthProfiler : public EngineProfiler { + protected: + struct BandwidthFrame { + uint32_t timestamp; + int packet_size; + }; + + int bandwidth_in_ptr = 0; + Vector<BandwidthFrame> bandwidth_in; + int bandwidth_out_ptr = 0; + Vector<BandwidthFrame> bandwidth_out; + uint64_t last_bandwidth_time = 0; + + int bandwidth_usage(const Vector<BandwidthFrame> &p_buffer, int p_pointer); + + public: + void toggle(bool p_enable, const Array &p_opts); + void add(const Array &p_data); + void tick(double p_frame_time, double p_process_time, double p_physics_time, double p_physics_frame_time); + }; + + class RPCProfiler : public EngineProfiler { + public: + private: + HashMap<ObjectID, RPCNodeInfo> rpc_node_data; + uint64_t last_profile_time = 0; + + void init_node(const ObjectID p_node); + + public: + void toggle(bool p_enable, const Array &p_opts); + void add(const Array &p_data); + void tick(double p_frame_time, double p_process_time, double p_physics_time, double p_physics_frame_time); + }; + +public: + static void initialize(); + static void deinitialize(); +}; + +#endif // MULTIPLAYER_DEBUGGER_H diff --git a/modules/multiplayer/multiplayer_spawner.cpp b/modules/multiplayer/multiplayer_spawner.cpp index e8f3aecc69..620e4f2f6c 100644 --- a/modules/multiplayer/multiplayer_spawner.cpp +++ b/modules/multiplayer/multiplayer_spawner.cpp @@ -86,23 +86,41 @@ void MultiplayerSpawner::_get_property_list(List<PropertyInfo> *p_list) const { } } #endif + +PackedStringArray MultiplayerSpawner::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); + + if (spawn_path.is_empty() || !has_node(spawn_path)) { + warnings.push_back(RTR("A valid NodePath must be set in the \"Spawn Path\" property in order for MultiplayerSpawner to be able to spawn Nodes.")); + } + bool has_scenes = get_spawnable_scene_count() > 0; + // Can't check if method is overridden in placeholder scripts. + bool has_placeholder_script = get_script_instance() && get_script_instance()->is_placeholder(); + if (!has_scenes && !GDVIRTUAL_IS_OVERRIDDEN(_spawn_custom) && !has_placeholder_script) { + warnings.push_back(RTR("A list of PackedScenes must be set in the \"Auto Spawn List\" property in order for MultiplayerSpawner to automatically spawn them remotely when added as child of \"spawn_path\".")); + warnings.push_back(RTR("Alternatively, a Script implementing the function \"_spawn_custom\" must be set for this MultiplayerSpawner, and \"spawn\" must be called explicitly in code.")); + } + return warnings; +} + void MultiplayerSpawner::add_spawnable_scene(const String &p_path) { SpawnableScene sc; sc.path = p_path; if (Engine::get_singleton()->is_editor_hint()) { ERR_FAIL_COND(!FileAccess::exists(p_path)); - } else { - sc.cache = ResourceLoader::load(p_path); - ERR_FAIL_COND_MSG(sc.cache.is_null(), "Invalid spawnable scene: " + p_path); } spawnable_scenes.push_back(sc); } + int MultiplayerSpawner::get_spawnable_scene_count() const { return spawnable_scenes.size(); } + String MultiplayerSpawner::get_spawnable_scene(int p_idx) const { + ERR_FAIL_INDEX_V(p_idx, (int)spawnable_scenes.size(), ""); return spawnable_scenes[p_idx].path; } + void MultiplayerSpawner::clear_spawnable_scenes() { spawnable_scenes.clear(); } @@ -230,8 +248,8 @@ void MultiplayerSpawner::_track(Node *p_node, const Variant &p_argument, int p_s ObjectID oid = p_node->get_instance_id(); if (!tracked_nodes.has(oid)) { tracked_nodes[oid] = SpawnInfo(p_argument.duplicate(true), p_scene_id); - p_node->connect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &MultiplayerSpawner::_node_exit).bind(p_node->get_instance_id()), CONNECT_ONESHOT); - p_node->connect(SceneStringNames::get_singleton()->ready, callable_mp(this, &MultiplayerSpawner::_node_ready).bind(p_node->get_instance_id()), CONNECT_ONESHOT); + p_node->connect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &MultiplayerSpawner::_node_exit).bind(p_node->get_instance_id()), CONNECT_ONE_SHOT); + p_node->connect(SceneStringNames::get_singleton()->ready, callable_mp(this, &MultiplayerSpawner::_node_ready).bind(p_node->get_instance_id()), CONNECT_ONE_SHOT); } } @@ -270,9 +288,12 @@ const Variant MultiplayerSpawner::get_spawn_argument(const ObjectID &p_id) const Node *MultiplayerSpawner::instantiate_scene(int p_id) { ERR_FAIL_COND_V_MSG(spawn_limit && spawn_limit <= tracked_nodes.size(), nullptr, "Spawn limit reached!"); ERR_FAIL_UNSIGNED_INDEX_V((uint32_t)p_id, spawnable_scenes.size(), nullptr); - Ref<PackedScene> scene = spawnable_scenes[p_id].cache; - ERR_FAIL_COND_V(scene.is_null(), nullptr); - return scene->instantiate(); + SpawnableScene &sc = spawnable_scenes[p_id]; + if (sc.cache.is_null()) { + sc.cache = ResourceLoader::load(sc.path); + } + ERR_FAIL_COND_V_MSG(sc.cache.is_null(), nullptr, "Invalid spawnable scene: " + sc.path); + return sc.cache->instantiate(); } Node *MultiplayerSpawner::instantiate_custom(const Variant &p_data) { diff --git a/modules/multiplayer/multiplayer_spawner.h b/modules/multiplayer/multiplayer_spawner.h index fc3befc2d4..587c99efd1 100644 --- a/modules/multiplayer/multiplayer_spawner.h +++ b/modules/multiplayer/multiplayer_spawner.h @@ -91,6 +91,8 @@ protected: void _get_property_list(List<PropertyInfo> *p_list) const; #endif public: + PackedStringArray get_configuration_warnings() const override; + Node *get_spawn_node() const { return spawn_node.is_valid() ? Object::cast_to<Node>(ObjectDB::get_instance(spawn_node)) : nullptr; } diff --git a/modules/multiplayer/multiplayer_synchronizer.cpp b/modules/multiplayer/multiplayer_synchronizer.cpp index eee1495c14..95857392c7 100644 --- a/modules/multiplayer/multiplayer_synchronizer.cpp +++ b/modules/multiplayer/multiplayer_synchronizer.cpp @@ -48,6 +48,8 @@ void MultiplayerSynchronizer::_stop() { return; } #endif + root_node_cache = ObjectID(); + reset(); Node *node = is_inside_tree() ? get_node_or_null(root_path) : nullptr; if (node) { get_multiplayer()->object_configuration_remove(node, this); @@ -60,8 +62,11 @@ void MultiplayerSynchronizer::_start() { return; } #endif + root_node_cache = ObjectID(); + reset(); Node *node = is_inside_tree() ? get_node_or_null(root_path) : nullptr; if (node) { + root_node_cache = node->get_instance_id(); get_multiplayer()->object_configuration_add(node, this); _update_process(); } @@ -94,6 +99,54 @@ void MultiplayerSynchronizer::_update_process() { } } +Node *MultiplayerSynchronizer::get_root_node() { + return root_node_cache.is_valid() ? Object::cast_to<Node>(ObjectDB::get_instance(root_node_cache)) : nullptr; +} + +void MultiplayerSynchronizer::reset() { + net_id = 0; + last_sync_msec = 0; + last_inbound_sync = 0; +} + +uint32_t MultiplayerSynchronizer::get_net_id() const { + return net_id; +} + +void MultiplayerSynchronizer::set_net_id(uint32_t p_net_id) { + net_id = p_net_id; +} + +bool MultiplayerSynchronizer::update_outbound_sync_time(uint64_t p_msec) { + if (last_sync_msec == p_msec) { + // last_sync_msec has been updated on this frame. + return true; + } + if (p_msec >= last_sync_msec + interval_msec) { + last_sync_msec = p_msec; + return true; + } + return false; +} + +bool MultiplayerSynchronizer::update_inbound_sync_time(uint16_t p_network_time) { + if (p_network_time <= last_inbound_sync && last_inbound_sync - p_network_time < 32767) { + return false; + } + last_inbound_sync = p_network_time; + return true; +} + +PackedStringArray MultiplayerSynchronizer::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); + + if (root_path.is_empty() || !has_node(root_path)) { + warnings.push_back(RTR("A valid NodePath must be set in the \"Root Path\" property in order for MultiplayerSynchronizer to be able to synchronize properties.")); + } + + return warnings; +} + Error MultiplayerSynchronizer::get_state(const List<NodePath> &p_properties, Object *p_obj, Vector<Variant> &r_variant, Vector<const Variant *> &r_variant_ptrs) { ERR_FAIL_COND_V(!p_obj, ERR_INVALID_PARAMETER); r_variant.resize(p_properties.size()); @@ -253,10 +306,6 @@ double MultiplayerSynchronizer::get_replication_interval() const { return double(interval_msec) / 1000.0; } -uint64_t MultiplayerSynchronizer::get_replication_interval_msec() const { - return interval_msec; -} - void MultiplayerSynchronizer::set_replication_config(Ref<SceneReplicationConfig> p_config) { replication_config = p_config; } @@ -289,10 +338,11 @@ NodePath MultiplayerSynchronizer::get_root_path() const { void MultiplayerSynchronizer::set_multiplayer_authority(int p_peer_id, bool p_recursive) { Node *node = is_inside_tree() ? get_node_or_null(root_path) : nullptr; - if (!node) { + if (!node || get_multiplayer_authority() == p_peer_id) { Node::set_multiplayer_authority(p_peer_id, p_recursive); return; } + get_multiplayer()->object_configuration_remove(node, this); Node::set_multiplayer_authority(p_peer_id, p_recursive); get_multiplayer()->object_configuration_add(node, this); diff --git a/modules/multiplayer/multiplayer_synchronizer.h b/modules/multiplayer/multiplayer_synchronizer.h index e84d41db86..9a7ce717cd 100644 --- a/modules/multiplayer/multiplayer_synchronizer.h +++ b/modules/multiplayer/multiplayer_synchronizer.h @@ -53,6 +53,11 @@ private: HashSet<Callable> visibility_filters; HashSet<int> peer_visibility; + ObjectID root_node_cache; + uint64_t last_sync_msec = 0; + uint16_t last_inbound_sync = 0; + uint32_t net_id = 0; + static Object *_get_prop_target(Object *p_obj, const NodePath &p_prop); void _start(); void _stop(); @@ -66,9 +71,19 @@ public: static Error get_state(const List<NodePath> &p_properties, Object *p_obj, Vector<Variant> &r_variant, Vector<const Variant *> &r_variant_ptrs); static Error set_state(const List<NodePath> &p_properties, Object *p_obj, const Vector<Variant> &p_state); + void reset(); + Node *get_root_node(); + + uint32_t get_net_id() const; + void set_net_id(uint32_t p_net_id); + + bool update_outbound_sync_time(uint64_t p_msec); + bool update_inbound_sync_time(uint16_t p_network_time); + + PackedStringArray get_configuration_warnings() const override; + void set_replication_interval(double p_interval); double get_replication_interval() const; - uint64_t get_replication_interval_msec() const; void set_replication_config(Ref<SceneReplicationConfig> p_config); Ref<SceneReplicationConfig> get_replication_config(); diff --git a/modules/multiplayer/register_types.cpp b/modules/multiplayer/register_types.cpp index a2c524da80..2bf1041029 100644 --- a/modules/multiplayer/register_types.cpp +++ b/modules/multiplayer/register_types.cpp @@ -36,9 +36,10 @@ #include "scene_replication_interface.h" #include "scene_rpc_interface.h" +#include "multiplayer_debugger.h" + #ifdef TOOLS_ENABLED -#include "editor/editor_plugin.h" -#include "editor/replication_editor_plugin.h" +#include "editor/multiplayer_editor_plugin.h" #endif void initialize_multiplayer_module(ModuleInitializationLevel p_level) { @@ -48,13 +49,15 @@ void initialize_multiplayer_module(ModuleInitializationLevel p_level) { GDREGISTER_CLASS(MultiplayerSynchronizer); GDREGISTER_CLASS(SceneMultiplayer); MultiplayerAPI::set_default_interface("SceneMultiplayer"); + MultiplayerDebugger::initialize(); } #ifdef TOOLS_ENABLED if (p_level == MODULE_INITIALIZATION_LEVEL_EDITOR) { - EditorPlugins::add_by_type<ReplicationEditorPlugin>(); + EditorPlugins::add_by_type<MultiplayerEditorPlugin>(); } #endif } void uninitialize_multiplayer_module(ModuleInitializationLevel p_level) { + MultiplayerDebugger::deinitialize(); } diff --git a/modules/multiplayer/scene_cache_interface.cpp b/modules/multiplayer/scene_cache_interface.cpp index b3b642f815..7df9b95b30 100644 --- a/modules/multiplayer/scene_cache_interface.cpp +++ b/modules/multiplayer/scene_cache_interface.cpp @@ -105,8 +105,7 @@ void SceneCacheInterface::process_simplify_path(int p_from, const uint8_t *p_pac multiplayer_peer->set_transfer_channel(0); multiplayer_peer->set_transfer_mode(MultiplayerPeer::TRANSFER_MODE_RELIABLE); - multiplayer_peer->set_target_peer(p_from); - multiplayer_peer->put_packet(packet.ptr(), packet.size()); + multiplayer->send_command(p_from, packet.ptr(), packet.size()); } void SceneCacheInterface::process_confirm_path(int p_from, const uint8_t *p_packet, int p_packet_len) { @@ -162,10 +161,9 @@ Error SceneCacheInterface::_send_confirm_path(Node *p_node, NodePath p_path, Pat Error err = OK; for (int peer_id : p_peers) { - multiplayer_peer->set_target_peer(peer_id); multiplayer_peer->set_transfer_channel(0); multiplayer_peer->set_transfer_mode(MultiplayerPeer::TRANSFER_MODE_RELIABLE); - err = multiplayer_peer->put_packet(packet.ptr(), packet.size()); + err = multiplayer->send_command(peer_id, packet.ptr(), packet.size()); ERR_FAIL_COND_V(err != OK, err); // Insert into confirmed, but as false since it was not confirmed. psc->confirmed_peers.insert(peer_id, false); diff --git a/modules/multiplayer/scene_multiplayer.cpp b/modules/multiplayer/scene_multiplayer.cpp index 3fc1eef366..db7c5037cd 100644 --- a/modules/multiplayer/scene_multiplayer.cpp +++ b/modules/multiplayer/scene_multiplayer.cpp @@ -51,14 +51,32 @@ void SceneMultiplayer::profile_bandwidth(const String &p_inout, int p_size) { } #endif +void SceneMultiplayer::_update_status() { + MultiplayerPeer::ConnectionStatus status = multiplayer_peer.is_valid() ? multiplayer_peer->get_connection_status() : MultiplayerPeer::CONNECTION_DISCONNECTED; + if (last_connection_status != status) { + if (status == MultiplayerPeer::CONNECTION_DISCONNECTED) { + if (last_connection_status == MultiplayerPeer::CONNECTION_CONNECTING) { + emit_signal(SNAME("connection_failed")); + } else { + emit_signal(SNAME("server_disconnected")); + } + clear(); + } + last_connection_status = status; + } +} + Error SceneMultiplayer::poll() { - if (!multiplayer_peer.is_valid() || multiplayer_peer->get_connection_status() == MultiplayerPeer::CONNECTION_DISCONNECTED) { - return ERR_UNCONFIGURED; + _update_status(); + if (last_connection_status == MultiplayerPeer::CONNECTION_DISCONNECTED) { + return OK; } multiplayer_peer->poll(); - if (!multiplayer_peer.is_valid()) { // It's possible that polling might have resulted in a disconnection, so check here. + _update_status(); + if (last_connection_status != MultiplayerPeer::CONNECTION_CONNECTED) { + // We might be still connecting, or polling might have resulted in a disconnection. return OK; } @@ -67,25 +85,99 @@ Error SceneMultiplayer::poll() { const uint8_t *packet; int len; + int channel = multiplayer_peer->get_packet_channel(); + MultiplayerPeer::TransferMode mode = multiplayer_peer->get_packet_mode(); + Error err = multiplayer_peer->get_packet(&packet, len); ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Error getting packet! %d", err)); - remote_sender_id = sender; - _process_packet(sender, packet, len); - remote_sender_id = 0; + if (pending_peers.has(sender)) { + if (pending_peers[sender].local) { + // If the auth is over, admit the peer at the first packet. + pending_peers.erase(sender); + _admit_peer(sender); + } else { + ERR_CONTINUE(len < 2 || (packet[0] & CMD_MASK) != NETWORK_COMMAND_SYS || packet[1] != SYS_COMMAND_AUTH); + // Auth message. + PackedByteArray pba; + pba.resize(len - 2); + if (pba.size()) { + memcpy(pba.ptrw(), &packet[2], len - 2); + // User callback + const Variant sv = sender; + const Variant pbav = pba; + const Variant *argv[2] = { &sv, &pbav }; + Variant ret; + Callable::CallError ce; + auth_callback.callp(argv, 2, ret, ce); + ERR_CONTINUE_MSG(ce.error != Callable::CallError::CALL_OK, "Failed to call authentication callback"); + } else { + // Remote complete notification. + pending_peers[sender].remote = true; + if (pending_peers[sender].local) { + pending_peers.erase(sender); + _admit_peer(sender); + } + } + continue; // Auth in progress. + } + } - if (!multiplayer_peer.is_valid()) { - return OK; // It's also possible that a packet or RPC caused a disconnection, so also check here. + ERR_CONTINUE(!connected_peers.has(sender)); + + if (len && (packet[0] & CMD_MASK) == NETWORK_COMMAND_SYS) { + // Sys messages are processed separately since they might call _process_packet themselves. + if (len > 1 && packet[1] == SYS_COMMAND_AUTH) { + ERR_CONTINUE(len != 2); + // If we are here, we already admitted the peer locally, and this is just a confirmation packet. + continue; + } + + _process_sys(sender, packet, len, mode, channel); + } else { + remote_sender_id = sender; + _process_packet(sender, packet, len); + remote_sender_id = 0; + } + + _update_status(); + if (last_connection_status != MultiplayerPeer::CONNECTION_CONNECTED) { // It's possible that processing a packet might have resulted in a disconnection, so check here. + return OK; + } + } + if (pending_peers.size() && auth_timeout) { + HashSet<int> to_drop; + uint64_t time = OS::get_singleton()->get_ticks_msec(); + for (const KeyValue<int, PendingPeer> &pending : pending_peers) { + if (pending.value.time + auth_timeout <= time) { + multiplayer_peer->disconnect_peer(pending.key); + to_drop.insert(pending.key); + } + } + for (const int &P : to_drop) { + // Each signal might trigger a disconnection. + pending_peers.erase(P); + emit_signal(SNAME("peer_authentication_failed"), P); } } + + _update_status(); + if (last_connection_status != MultiplayerPeer::CONNECTION_CONNECTED) { // Signals might have triggered disconnection. + return OK; + } + replicator->on_network_process(); return OK; } void SceneMultiplayer::clear() { + last_connection_status = MultiplayerPeer::CONNECTION_DISCONNECTED; + pending_peers.clear(); connected_peers.clear(); packet_cache.clear(); + replicator->on_reset(); cache->clear(); + relay_buffer->clear(); } void SceneMultiplayer::set_root_path(const NodePath &p_path) { @@ -108,9 +200,6 @@ void SceneMultiplayer::set_multiplayer_peer(const Ref<MultiplayerPeer> &p_peer) if (multiplayer_peer.is_valid()) { multiplayer_peer->disconnect("peer_connected", callable_mp(this, &SceneMultiplayer::_add_peer)); multiplayer_peer->disconnect("peer_disconnected", callable_mp(this, &SceneMultiplayer::_del_peer)); - multiplayer_peer->disconnect("connection_succeeded", callable_mp(this, &SceneMultiplayer::_connected_to_server)); - multiplayer_peer->disconnect("connection_failed", callable_mp(this, &SceneMultiplayer::_connection_failed)); - multiplayer_peer->disconnect("server_disconnected", callable_mp(this, &SceneMultiplayer::_server_disconnected)); clear(); } @@ -119,11 +208,8 @@ void SceneMultiplayer::set_multiplayer_peer(const Ref<MultiplayerPeer> &p_peer) if (multiplayer_peer.is_valid()) { multiplayer_peer->connect("peer_connected", callable_mp(this, &SceneMultiplayer::_add_peer)); multiplayer_peer->connect("peer_disconnected", callable_mp(this, &SceneMultiplayer::_del_peer)); - multiplayer_peer->connect("connection_succeeded", callable_mp(this, &SceneMultiplayer::_connected_to_server)); - multiplayer_peer->connect("connection_failed", callable_mp(this, &SceneMultiplayer::_connection_failed)); - multiplayer_peer->connect("server_disconnected", callable_mp(this, &SceneMultiplayer::_server_disconnected)); } - replicator->on_reset(); + _update_status(); } Ref<MultiplayerPeer> SceneMultiplayer::get_multiplayer_peer() { @@ -166,34 +252,184 @@ void SceneMultiplayer::_process_packet(int p_from, const uint8_t *p_packet, int case NETWORK_COMMAND_SYNC: { replicator->on_sync_receive(p_from, p_packet, p_packet_len); } break; + default: { + ERR_FAIL_MSG("Invalid network command from " + itos(p_from)); + } break; + } +} + +Error SceneMultiplayer::send_command(int p_to, const uint8_t *p_packet, int p_packet_len) { + if (server_relay && get_unique_id() != 1 && p_to != 1 && multiplayer_peer->is_server_relay_supported()) { + // Send relay packet. + relay_buffer->seek(0); + relay_buffer->put_u8(NETWORK_COMMAND_SYS); + relay_buffer->put_u8(SYS_COMMAND_RELAY); + relay_buffer->put_32(p_to); // Set the destination. + relay_buffer->put_data(p_packet, p_packet_len); + multiplayer_peer->set_target_peer(1); + const Vector<uint8_t> data = relay_buffer->get_data_array(); + return multiplayer_peer->put_packet(data.ptr(), relay_buffer->get_position()); + } + if (p_to > 0) { + ERR_FAIL_COND_V(!connected_peers.has(p_to), ERR_BUG); + multiplayer_peer->set_target_peer(p_to); + return multiplayer_peer->put_packet(p_packet, p_packet_len); + } else { + for (const int &pid : connected_peers) { + if (p_to && pid == -p_to) { + continue; + } + multiplayer_peer->set_target_peer(pid); + multiplayer_peer->put_packet(p_packet, p_packet_len); + } + return OK; + } +} + +void SceneMultiplayer::_process_sys(int p_from, const uint8_t *p_packet, int p_packet_len, MultiplayerPeer::TransferMode p_mode, int p_channel) { + ERR_FAIL_COND_MSG(p_packet_len < SYS_CMD_SIZE, "Invalid packet received. Size too small."); + uint8_t sys_cmd_type = p_packet[1]; + int32_t peer = int32_t(decode_uint32(&p_packet[2])); + switch (sys_cmd_type) { + case SYS_COMMAND_ADD_PEER: { + ERR_FAIL_COND(!server_relay || !multiplayer_peer->is_server_relay_supported() || get_unique_id() == 1 || p_from != 1); + _admit_peer(peer); // Relayed peers are automatically accepted. + } break; + case SYS_COMMAND_DEL_PEER: { + ERR_FAIL_COND(!server_relay || !multiplayer_peer->is_server_relay_supported() || get_unique_id() == 1 || p_from != 1); + _del_peer(peer); + } break; + case SYS_COMMAND_RELAY: { + ERR_FAIL_COND(!server_relay || !multiplayer_peer->is_server_relay_supported()); + ERR_FAIL_COND(p_packet_len < SYS_CMD_SIZE + 1); + const uint8_t *packet = p_packet + SYS_CMD_SIZE; + int len = p_packet_len - SYS_CMD_SIZE; + bool should_process = false; + if (get_unique_id() == 1) { // I am the server. + // Direct messages to server should not go through relay. + ERR_FAIL_COND(peer > 0 && !connected_peers.has(peer)); + // Send relay packet. + relay_buffer->seek(0); + relay_buffer->put_u8(NETWORK_COMMAND_SYS); + relay_buffer->put_u8(SYS_COMMAND_RELAY); + relay_buffer->put_32(p_from); // Set the source. + relay_buffer->put_data(packet, len); + const Vector<uint8_t> data = relay_buffer->get_data_array(); + multiplayer_peer->set_transfer_mode(p_mode); + multiplayer_peer->set_transfer_channel(p_channel); + if (peer > 0) { + multiplayer_peer->set_target_peer(peer); + multiplayer_peer->put_packet(data.ptr(), relay_buffer->get_position()); + } else { + for (const int &P : connected_peers) { + // Not to sender, nor excluded. + if (P == p_from || (peer < 0 && P != -peer)) { + continue; + } + multiplayer_peer->set_target_peer(P); + multiplayer_peer->put_packet(data.ptr(), relay_buffer->get_position()); + } + } + if (peer == 0 || peer == -1) { + should_process = true; + peer = p_from; // Process as the source. + } + } else { + ERR_FAIL_COND(p_from != 1); // Bug. + should_process = true; + } + if (should_process) { + remote_sender_id = peer; + _process_packet(peer, packet, len); + remote_sender_id = 0; + } + } break; + default: { + ERR_FAIL(); + } } } void SceneMultiplayer::_add_peer(int p_id) { + if (auth_callback.is_valid()) { + pending_peers[p_id] = PendingPeer(); + pending_peers[p_id].time = OS::get_singleton()->get_ticks_msec(); + emit_signal(SNAME("peer_authenticating"), p_id); + return; + } else { + _admit_peer(p_id); + } +} + +void SceneMultiplayer::_admit_peer(int p_id) { + if (server_relay && get_unique_id() == 1 && multiplayer_peer->is_server_relay_supported()) { + // Notify others of connection, and send connected peers to newly connected one. + uint8_t buf[SYS_CMD_SIZE]; + buf[0] = NETWORK_COMMAND_SYS; + buf[1] = SYS_COMMAND_ADD_PEER; + multiplayer_peer->set_transfer_channel(0); + multiplayer_peer->set_transfer_mode(MultiplayerPeer::TRANSFER_MODE_RELIABLE); + for (const int &P : connected_peers) { + // Send new peer to already connected. + encode_uint32(p_id, &buf[2]); + multiplayer_peer->set_target_peer(P); + multiplayer_peer->put_packet(buf, sizeof(buf)); + // Send already connected to new peer. + encode_uint32(P, &buf[2]); + multiplayer_peer->set_target_peer(p_id); + multiplayer_peer->put_packet(buf, sizeof(buf)); + } + } + connected_peers.insert(p_id); cache->on_peer_change(p_id, true); replicator->on_peer_change(p_id, true); + if (p_id == 1) { + emit_signal(SNAME("connected_to_server")); + } emit_signal(SNAME("peer_connected"), p_id); } void SceneMultiplayer::_del_peer(int p_id) { + if (pending_peers.has(p_id)) { + pending_peers.erase(p_id); + emit_signal(SNAME("peer_authentication_failed"), p_id); + return; + } else if (!connected_peers.has(p_id)) { + return; + } + + if (server_relay && get_unique_id() == 1 && multiplayer_peer->is_server_relay_supported()) { + // Notify others of disconnection. + uint8_t buf[SYS_CMD_SIZE]; + buf[0] = NETWORK_COMMAND_SYS; + buf[1] = SYS_COMMAND_DEL_PEER; + multiplayer_peer->set_transfer_channel(0); + multiplayer_peer->set_transfer_mode(MultiplayerPeer::TRANSFER_MODE_RELIABLE); + encode_uint32(p_id, &buf[2]); + for (const int &P : connected_peers) { + if (P == p_id) { + continue; + } + multiplayer_peer->set_target_peer(P); + multiplayer_peer->put_packet(buf, sizeof(buf)); + } + } + replicator->on_peer_change(p_id, false); cache->on_peer_change(p_id, false); connected_peers.erase(p_id); emit_signal(SNAME("peer_disconnected"), p_id); } -void SceneMultiplayer::_connected_to_server() { - emit_signal(SNAME("connected_to_server")); -} - -void SceneMultiplayer::_connection_failed() { - emit_signal(SNAME("connection_failed")); -} - -void SceneMultiplayer::_server_disconnected() { - replicator->on_reset(); - emit_signal(SNAME("server_disconnected")); +void SceneMultiplayer::disconnect_peer(int p_id) { + ERR_FAIL_COND(multiplayer_peer.is_null() || multiplayer_peer->get_connection_status() != MultiplayerPeer::CONNECTION_CONNECTED); + if (pending_peers.has(p_id)) { + pending_peers.erase(p_id); + } else if (connected_peers.has(p_id)) { + connected_peers.has(p_id); + } + multiplayer_peer->disconnect_peer(p_id); } Error SceneMultiplayer::send_bytes(Vector<uint8_t> p_data, int p_to, MultiplayerPeer::TransferMode p_mode, int p_channel) { @@ -209,11 +445,64 @@ Error SceneMultiplayer::send_bytes(Vector<uint8_t> p_data, int p_to, Multiplayer packet_cache.write[0] = NETWORK_COMMAND_RAW; memcpy(&packet_cache.write[1], &r[0], p_data.size()); - multiplayer_peer->set_target_peer(p_to); multiplayer_peer->set_transfer_channel(p_channel); multiplayer_peer->set_transfer_mode(p_mode); + return send_command(p_to, packet_cache.ptr(), p_data.size() + 1); +} + +Error SceneMultiplayer::send_auth(int p_to, Vector<uint8_t> p_data) { + ERR_FAIL_COND_V(multiplayer_peer.is_null() || multiplayer_peer->get_connection_status() != MultiplayerPeer::CONNECTION_CONNECTED, ERR_UNCONFIGURED); + ERR_FAIL_COND_V(!pending_peers.has(p_to), ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_data.size() < 1, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V_MSG(pending_peers[p_to].local, ERR_FILE_CANT_WRITE, "The authentication session was previously marked as completed, no more authentication data can be sent."); + ERR_FAIL_COND_V_MSG(pending_peers[p_to].remote, ERR_FILE_CANT_WRITE, "The remote peer notified that the authentication session was completed, no more authentication data can be sent."); + + if (packet_cache.size() < p_data.size() + 2) { + packet_cache.resize(p_data.size() + 2); + } + + packet_cache.write[0] = NETWORK_COMMAND_SYS; + packet_cache.write[1] = SYS_COMMAND_AUTH; + memcpy(&packet_cache.write[2], p_data.ptr(), p_data.size()); + + multiplayer_peer->set_target_peer(p_to); + multiplayer_peer->set_transfer_channel(0); + multiplayer_peer->set_transfer_mode(MultiplayerPeer::TRANSFER_MODE_RELIABLE); + return multiplayer_peer->put_packet(packet_cache.ptr(), p_data.size() + 2); +} + +Error SceneMultiplayer::complete_auth(int p_peer) { + ERR_FAIL_COND_V(multiplayer_peer.is_null() || multiplayer_peer->get_connection_status() != MultiplayerPeer::CONNECTION_CONNECTED, ERR_UNCONFIGURED); + ERR_FAIL_COND_V(!pending_peers.has(p_peer), ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V_MSG(pending_peers[p_peer].local, ERR_FILE_CANT_WRITE, "The authentication session was already marked as completed."); + pending_peers[p_peer].local = true; + // Notify the remote peer that the authentication has completed. + uint8_t buf[2] = { NETWORK_COMMAND_SYS, SYS_COMMAND_AUTH }; + Error err = multiplayer_peer->put_packet(buf, 2); + // The remote peer already reported the authentication as completed, so admit the peer. + // May generate new packets, so it must happen after sending confirmation. + if (pending_peers[p_peer].remote) { + pending_peers.erase(p_peer); + _admit_peer(p_peer); + } + return err; +} + +void SceneMultiplayer::set_auth_callback(Callable p_callback) { + auth_callback = p_callback; +} + +Callable SceneMultiplayer::get_auth_callback() const { + return auth_callback; +} + +void SceneMultiplayer::set_auth_timeout(double p_timeout) { + ERR_FAIL_COND_MSG(p_timeout < 0, "Timeout must be greater or equal to 0 (where 0 means no timeout)"); + auth_timeout = uint64_t(p_timeout * 1000); +} - return multiplayer_peer->put_packet(packet_cache.ptr(), p_data.size() + 1); +double SceneMultiplayer::get_auth_timeout() const { + return double(auth_timeout) / 1000.0; } void SceneMultiplayer::_process_raw(int p_from, const uint8_t *p_packet, int p_packet_len) { @@ -255,6 +544,16 @@ Vector<int> SceneMultiplayer::get_peer_ids() { return ret; } +Vector<int> SceneMultiplayer::get_authenticating_peer_ids() { + Vector<int> out; + out.resize(pending_peers.size()); + int idx = 0; + for (const KeyValue<int, PendingPeer> &E : pending_peers) { + out.write[idx++] = E.key; + } + return out; +} + void SceneMultiplayer::set_allow_object_decoding(bool p_enable) { allow_object_decoding = p_enable; } @@ -303,25 +602,55 @@ Error SceneMultiplayer::object_configuration_remove(Object *p_obj, Variant p_con return ERR_INVALID_PARAMETER; } +void SceneMultiplayer::set_server_relay_enabled(bool p_enabled) { + ERR_FAIL_COND_MSG(multiplayer_peer.is_valid() && multiplayer_peer->get_connection_status() != MultiplayerPeer::CONNECTION_DISCONNECTED, "Cannot change the server relay option while the multiplayer peer is active."); + server_relay = p_enabled; +} + +bool SceneMultiplayer::is_server_relay_enabled() const { + return server_relay; +} + void SceneMultiplayer::_bind_methods() { ClassDB::bind_method(D_METHOD("set_root_path", "path"), &SceneMultiplayer::set_root_path); ClassDB::bind_method(D_METHOD("get_root_path"), &SceneMultiplayer::get_root_path); ClassDB::bind_method(D_METHOD("clear"), &SceneMultiplayer::clear); + + ClassDB::bind_method(D_METHOD("disconnect_peer", "id"), &SceneMultiplayer::disconnect_peer); + + ClassDB::bind_method(D_METHOD("get_authenticating_peers"), &SceneMultiplayer::get_authenticating_peer_ids); + ClassDB::bind_method(D_METHOD("send_auth", "id", "data"), &SceneMultiplayer::send_auth); + ClassDB::bind_method(D_METHOD("complete_auth", "id"), &SceneMultiplayer::complete_auth); + + ClassDB::bind_method(D_METHOD("set_auth_callback", "callback"), &SceneMultiplayer::set_auth_callback); + ClassDB::bind_method(D_METHOD("get_auth_callback"), &SceneMultiplayer::get_auth_callback); + ClassDB::bind_method(D_METHOD("set_auth_timeout", "timeout"), &SceneMultiplayer::set_auth_timeout); + ClassDB::bind_method(D_METHOD("get_auth_timeout"), &SceneMultiplayer::get_auth_timeout); + ClassDB::bind_method(D_METHOD("set_refuse_new_connections", "refuse"), &SceneMultiplayer::set_refuse_new_connections); ClassDB::bind_method(D_METHOD("is_refusing_new_connections"), &SceneMultiplayer::is_refusing_new_connections); ClassDB::bind_method(D_METHOD("set_allow_object_decoding", "enable"), &SceneMultiplayer::set_allow_object_decoding); ClassDB::bind_method(D_METHOD("is_object_decoding_allowed"), &SceneMultiplayer::is_object_decoding_allowed); + ClassDB::bind_method(D_METHOD("set_server_relay_enabled", "enabled"), &SceneMultiplayer::set_server_relay_enabled); + ClassDB::bind_method(D_METHOD("is_server_relay_enabled"), &SceneMultiplayer::is_server_relay_enabled); ClassDB::bind_method(D_METHOD("send_bytes", "bytes", "id", "mode", "channel"), &SceneMultiplayer::send_bytes, DEFVAL(MultiplayerPeer::TARGET_PEER_BROADCAST), DEFVAL(MultiplayerPeer::TRANSFER_MODE_RELIABLE), DEFVAL(0)); ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "root_path"), "set_root_path", "get_root_path"); + ADD_PROPERTY(PropertyInfo(Variant::CALLABLE, "auth_callback"), "set_auth_callback", "get_auth_callback"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "auth_timeout", PROPERTY_HINT_RANGE, "0,30,0.1,or_greater,suffix:s"), "set_auth_timeout", "get_auth_timeout"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_object_decoding"), "set_allow_object_decoding", "is_object_decoding_allowed"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "refuse_new_connections"), "set_refuse_new_connections", "is_refusing_new_connections"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "server_relay"), "set_server_relay_enabled", "is_server_relay_enabled"); + ADD_PROPERTY_DEFAULT("refuse_new_connections", false); + ADD_SIGNAL(MethodInfo("peer_authenticating", PropertyInfo(Variant::INT, "id"))); + ADD_SIGNAL(MethodInfo("peer_authentication_failed", PropertyInfo(Variant::INT, "id"))); ADD_SIGNAL(MethodInfo("peer_packet", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::PACKED_BYTE_ARRAY, "packet"))); } SceneMultiplayer::SceneMultiplayer() { + relay_buffer.instantiate(); replicator = Ref<SceneReplicationInterface>(memnew(SceneReplicationInterface(this))); rpc = Ref<SceneRPCInterface>(memnew(SceneRPCInterface(this))); cache = Ref<SceneCacheInterface>(memnew(SceneCacheInterface(this))); diff --git a/modules/multiplayer/scene_multiplayer.h b/modules/multiplayer/scene_multiplayer.h index a99cca7b21..b0ecc48f8c 100644 --- a/modules/multiplayer/scene_multiplayer.h +++ b/modules/multiplayer/scene_multiplayer.h @@ -49,6 +49,18 @@ public: NETWORK_COMMAND_SPAWN, NETWORK_COMMAND_DESPAWN, NETWORK_COMMAND_SYNC, + NETWORK_COMMAND_SYS, + }; + + enum SysCommands { + SYS_COMMAND_AUTH, + SYS_COMMAND_ADD_PEER, + SYS_COMMAND_DEL_PEER, + SYS_COMMAND_RELAY, + }; + + enum { + SYS_CMD_SIZE = 6, // Command + sys command + peer_id (+ optional payload). }; // For each command, the 4 MSB can contain custom flags, as defined by subsystems. @@ -65,7 +77,17 @@ public: }; private: + struct PendingPeer { + bool local = false; + bool remote = false; + uint64_t time = 0; + }; + Ref<MultiplayerPeer> multiplayer_peer; + MultiplayerPeer::ConnectionStatus last_connection_status = MultiplayerPeer::CONNECTION_DISCONNECTED; + HashMap<int, PendingPeer> pending_peers; // true if locally finalized. + Callable auth_callback; + uint64_t auth_timeout = 3000; HashSet<int> connected_peers; int remote_sender_id = 0; int remote_sender_override = 0; @@ -74,6 +96,8 @@ private: NodePath root_path; bool allow_object_decoding = false; + bool server_relay = true; + Ref<StreamPeerBuffer> relay_buffer; Ref<SceneCacheInterface> cache; Ref<SceneReplicationInterface> replicator; @@ -84,12 +108,12 @@ protected: void _process_packet(int p_from, const uint8_t *p_packet, int p_packet_len); void _process_raw(int p_from, const uint8_t *p_packet, int p_packet_len); + void _process_sys(int p_from, const uint8_t *p_packet, int p_packet_len, MultiplayerPeer::TransferMode p_mode, int p_channel); void _add_peer(int p_id); + void _admit_peer(int p_id); void _del_peer(int p_id); - void _connected_to_server(); - void _connection_failed(); - void _server_disconnected(); + void _update_status(); public: virtual void set_multiplayer_peer(const Ref<MultiplayerPeer> &p_peer) override; @@ -111,6 +135,17 @@ public: void set_root_path(const NodePath &p_path); NodePath get_root_path() const; + void disconnect_peer(int p_id); + + Error send_auth(int p_to, Vector<uint8_t> p_bytes); + Error complete_auth(int p_peer); + void set_auth_callback(Callable p_callback); + Callable get_auth_callback() const; + void set_auth_timeout(double p_timeout); + double get_auth_timeout() const; + Vector<int> get_authenticating_peer_ids(); + + Error send_command(int p_to, const uint8_t *p_packet, int p_packet_len); // Used internally to relay packets when needed. Error send_bytes(Vector<uint8_t> p_data, int p_to = MultiplayerPeer::TARGET_PEER_BROADCAST, MultiplayerPeer::TransferMode p_mode = MultiplayerPeer::TRANSFER_MODE_RELIABLE, int p_channel = 0); String get_rpc_md5(const Object *p_obj); @@ -123,6 +158,9 @@ public: void set_allow_object_decoding(bool p_enable); bool is_object_decoding_allowed() const; + void set_server_relay_enabled(bool p_enabled); + bool is_server_relay_enabled() const; + Ref<SceneCacheInterface> get_path_cache() { return cache; } #ifdef DEBUG_ENABLED diff --git a/modules/multiplayer/scene_replication_config.cpp b/modules/multiplayer/scene_replication_config.cpp index ae06516b7b..61fe51b6c9 100644 --- a/modules/multiplayer/scene_replication_config.cpp +++ b/modules/multiplayer/scene_replication_config.cpp @@ -34,11 +34,11 @@ #include "scene/main/node.h" bool SceneReplicationConfig::_set(const StringName &p_name, const Variant &p_value) { - String name = p_name; + String prop_name = p_name; - if (name.begins_with("properties/")) { - int idx = name.get_slicec('/', 1).to_int(); - String what = name.get_slicec('/', 2); + if (prop_name.begins_with("properties/")) { + int idx = prop_name.get_slicec('/', 1).to_int(); + String what = prop_name.get_slicec('/', 2); if (properties.size() == idx && what == "path") { ERR_FAIL_COND_V(p_value.get_type() != Variant::NODE_PATH, false); @@ -72,11 +72,11 @@ bool SceneReplicationConfig::_set(const StringName &p_name, const Variant &p_val } bool SceneReplicationConfig::_get(const StringName &p_name, Variant &r_ret) const { - String name = p_name; + String prop_name = p_name; - if (name.begins_with("properties/")) { - int idx = name.get_slicec('/', 1).to_int(); - String what = name.get_slicec('/', 2); + if (prop_name.begins_with("properties/")) { + int idx = prop_name.get_slicec('/', 1).to_int(); + String what = prop_name.get_slicec('/', 2); ERR_FAIL_INDEX_V(idx, properties.size(), false); const ReplicationProperty &prop = properties[idx]; if (what == "path") { diff --git a/modules/multiplayer/scene_replication_interface.cpp b/modules/multiplayer/scene_replication_interface.cpp index 6e3dbfab47..f1bab7327a 100644 --- a/modules/multiplayer/scene_replication_interface.cpp +++ b/modules/multiplayer/scene_replication_interface.cpp @@ -30,56 +30,96 @@ #include "scene_replication_interface.h" +#include "scene_multiplayer.h" + #include "core/io/marshalls.h" #include "scene/main/node.h" - -#include "multiplayer_spawner.h" -#include "multiplayer_synchronizer.h" -#include "scene_multiplayer.h" +#include "scene/scene_string_names.h" #define MAKE_ROOM(m_amount) \ if (packet_cache.size() < m_amount) \ packet_cache.resize(m_amount); -void SceneReplicationInterface::_free_remotes(int p_id) { - const HashMap<uint32_t, ObjectID> remotes = rep_state->peer_get_remotes(p_id); - for (const KeyValue<uint32_t, ObjectID> &E : remotes) { - Node *node = rep_state->get_node(E.value); +SceneReplicationInterface::TrackedNode &SceneReplicationInterface::_track(const ObjectID &p_id) { + if (!tracked_nodes.has(p_id)) { + tracked_nodes[p_id] = TrackedNode(p_id); + Node *node = get_id_as<Node>(p_id); + node->connect(SceneStringNames::get_singleton()->tree_exited, callable_mp(this, &SceneReplicationInterface::_untrack).bind(p_id), Node::CONNECT_ONE_SHOT); + } + return tracked_nodes[p_id]; +} + +void SceneReplicationInterface::_untrack(const ObjectID &p_id) { + if (!tracked_nodes.has(p_id)) { + return; + } + uint32_t net_id = tracked_nodes[p_id].net_id; + uint32_t peer = tracked_nodes[p_id].remote_peer; + tracked_nodes.erase(p_id); + // If it was spawned by a remote, remove it from the received nodes. + if (peer && peers_info.has(peer)) { + peers_info[peer].recv_nodes.erase(net_id); + } + // If we spawned or synced it, we need to remove it from any peer it was sent to. + if (net_id || peer == 0) { + for (KeyValue<int, PeerInfo> &E : peers_info) { + E.value.spawn_nodes.erase(p_id); + } + } +} + +void SceneReplicationInterface::_free_remotes(const PeerInfo &p_info) { + for (const KeyValue<uint32_t, ObjectID> &E : p_info.recv_nodes) { + Node *node = tracked_nodes.has(E.value) ? get_id_as<Node>(E.value) : nullptr; ERR_CONTINUE(!node); - node->queue_delete(); + node->queue_free(); } } void SceneReplicationInterface::on_peer_change(int p_id, bool p_connected) { if (p_connected) { - rep_state->on_peer_change(p_id, p_connected); - for (const ObjectID &oid : rep_state->get_spawned_nodes()) { + peers_info[p_id] = PeerInfo(); + for (const ObjectID &oid : spawned_nodes) { _update_spawn_visibility(p_id, oid); } - for (const ObjectID &oid : rep_state->get_synced_nodes()) { - MultiplayerSynchronizer *sync = rep_state->get_synchronizer(oid); - ERR_CONTINUE(!sync); // ERR_BUG - if (sync->is_multiplayer_authority()) { - _update_sync_visibility(p_id, oid); - } + for (const ObjectID &oid : sync_nodes) { + _update_sync_visibility(p_id, get_id_as<MultiplayerSynchronizer>(oid)); } } else { - _free_remotes(p_id); - rep_state->on_peer_change(p_id, p_connected); + ERR_FAIL_COND(!peers_info.has(p_id)); + _free_remotes(peers_info[p_id]); + peers_info.erase(p_id); } } void SceneReplicationInterface::on_reset() { - for (int pid : rep_state->get_peers()) { - _free_remotes(pid); + for (const KeyValue<int, PeerInfo> &E : peers_info) { + _free_remotes(E.value); } - rep_state->reset(); + peers_info.clear(); + // Tracked nodes are cleared on deletion, here we only reset the ids so they can be later re-assigned. + for (KeyValue<ObjectID, TrackedNode> &E : tracked_nodes) { + TrackedNode &tobj = E.value; + tobj.net_id = 0; + tobj.remote_peer = 0; + } + for (const ObjectID &oid : sync_nodes) { + MultiplayerSynchronizer *sync = get_id_as<MultiplayerSynchronizer>(oid); + ERR_CONTINUE(!sync); + sync->reset(); + } + last_net_id = 0; } void SceneReplicationInterface::on_network_process() { uint64_t msec = OS::get_singleton()->get_ticks_msec(); - for (int peer : rep_state->get_peers()) { - _send_sync(peer, msec); + for (KeyValue<int, PeerInfo> &E : peers_info) { + const HashSet<ObjectID> to_sync = E.value.sync_nodes; + if (to_sync.is_empty()) { + continue; // Nothing to sync + } + uint16_t sync_net_time = ++E.value.last_sent_sync; + _send_sync(E.key, to_sync, sync_net_time, msec); } } @@ -88,14 +128,19 @@ Error SceneReplicationInterface::on_spawn(Object *p_obj, Variant p_config) { ERR_FAIL_COND_V(!node || p_config.get_type() != Variant::OBJECT, ERR_INVALID_PARAMETER); MultiplayerSpawner *spawner = Object::cast_to<MultiplayerSpawner>(p_config.get_validated_object()); ERR_FAIL_COND_V(!spawner, ERR_INVALID_PARAMETER); - Error err = rep_state->config_add_spawn(node, spawner); - ERR_FAIL_COND_V(err != OK, err); + // Track node. const ObjectID oid = node->get_instance_id(); + TrackedNode &tobj = _track(oid); + ERR_FAIL_COND_V(tobj.spawner != ObjectID(), ERR_ALREADY_IN_USE); + tobj.spawner = spawner->get_instance_id(); + spawned_nodes.insert(oid); + if (multiplayer->has_multiplayer_peer() && spawner->is_multiplayer_authority()) { - rep_state->ensure_net_id(oid); + if (tobj.net_id == 0) { + tobj.net_id = ++last_net_id; + } _update_spawn_visibility(0, oid); } - ERR_FAIL_COND_V(err != OK, err); return OK; } @@ -109,14 +154,22 @@ Error SceneReplicationInterface::on_despawn(Object *p_obj, Variant p_config) { Error err = _make_despawn_packet(node, len); ERR_FAIL_COND_V(err != OK, ERR_BUG); const ObjectID oid = p_obj->get_instance_id(); - for (int pid : rep_state->get_peers()) { - if (!rep_state->is_peer_spawn(pid, oid)) { + for (const KeyValue<int, PeerInfo> &E : peers_info) { + if (!E.value.spawn_nodes.has(oid)) { continue; } - _send_raw(packet_cache.ptr(), len, pid, true); + _send_raw(packet_cache.ptr(), len, E.key, true); } // Also remove spawner tracking from the replication state. - return rep_state->config_del_spawn(node, spawner); + ERR_FAIL_COND_V(!tracked_nodes.has(oid), ERR_INVALID_PARAMETER); + TrackedNode &tobj = _track(oid); + ERR_FAIL_COND_V(tobj.spawner != spawner->get_instance_id(), ERR_INVALID_PARAMETER); + tobj.spawner = ObjectID(); + spawned_nodes.erase(oid); + for (KeyValue<int, PeerInfo> &E : peers_info) { + E.value.spawn_nodes.erase(oid); + } + return OK; } Error SceneReplicationInterface::on_replication_start(Object *p_obj, Variant p_config) { @@ -125,25 +178,40 @@ Error SceneReplicationInterface::on_replication_start(Object *p_obj, Variant p_c MultiplayerSynchronizer *sync = Object::cast_to<MultiplayerSynchronizer>(p_config.get_validated_object()); ERR_FAIL_COND_V(!sync, ERR_INVALID_PARAMETER); - // Add to synchronizer list and setup visibility. - rep_state->config_add_sync(node, sync); - const ObjectID oid = node->get_instance_id(); - sync->connect("visibility_changed", callable_mp(this, &SceneReplicationInterface::_visibility_changed).bind(oid)); - if (multiplayer->has_multiplayer_peer() && sync->is_multiplayer_authority()) { - _update_sync_visibility(0, oid); - } - - // Try to apply initial state if spawning (hack to apply if before ready). - if (pending_spawn == p_obj->get_instance_id()) { - pending_spawn = ObjectID(); // Make sure this only happens once. - const List<NodePath> props = sync->get_replication_config()->get_spawn_properties(); - Vector<Variant> vars; - vars.resize(props.size()); - int consumed; - Error err = MultiplayerAPI::decode_and_decompress_variants(vars, pending_buffer, pending_buffer_size, consumed); - ERR_FAIL_COND_V(err, err); - err = MultiplayerSynchronizer::set_state(props, node, vars); - ERR_FAIL_COND_V(err, err); + // Add to synchronizer list. + TrackedNode &tobj = _track(p_obj->get_instance_id()); + const ObjectID sid = sync->get_instance_id(); + tobj.synchronizers.insert(sid); + sync_nodes.insert(sid); + + // Update visibility. + sync->connect("visibility_changed", callable_mp(this, &SceneReplicationInterface::_visibility_changed).bind(sync->get_instance_id())); + _update_sync_visibility(0, sync); + + if (pending_spawn == p_obj->get_instance_id() && sync->get_multiplayer_authority() == pending_spawn_remote) { + // Try to apply synchronizer Net ID + ERR_FAIL_COND_V_MSG(pending_sync_net_ids.is_empty(), ERR_INVALID_DATA, vformat("The MultiplayerSynchronizer at path \"%s\" is unable to process the pending spawn since it has no network ID. This might happen when changing the multiplayer authority during the \"_ready\" callback. Make sure to only change the authority of multiplayer synchronizers during \"_enter_tree\" or the \"_spawn_custom\" callback of their multiplayer spawner.", sync->get_path())); + ERR_FAIL_COND_V(!peers_info.has(pending_spawn_remote), ERR_INVALID_DATA); + uint32_t net_id = pending_sync_net_ids[0]; + pending_sync_net_ids.pop_front(); + peers_info[pending_spawn_remote].recv_sync_ids[net_id] = sync->get_instance_id(); + + // Try to apply spawn state (before ready). + if (pending_buffer_size > 0) { + ERR_FAIL_COND_V(!node || sync->get_replication_config().is_null(), ERR_UNCONFIGURED); + int consumed = 0; + const List<NodePath> props = sync->get_replication_config()->get_spawn_properties(); + Vector<Variant> vars; + vars.resize(props.size()); + Error err = MultiplayerAPI::decode_and_decompress_variants(vars, pending_buffer, pending_buffer_size, consumed); + ERR_FAIL_COND_V(err, err); + if (consumed > 0) { + pending_buffer += consumed; + pending_buffer_size -= consumed; + err = MultiplayerSynchronizer::set_state(props, node, vars); + ERR_FAIL_COND_V(err, err); + } + } } return OK; } @@ -154,59 +222,98 @@ Error SceneReplicationInterface::on_replication_stop(Object *p_obj, Variant p_co MultiplayerSynchronizer *sync = Object::cast_to<MultiplayerSynchronizer>(p_config.get_validated_object()); ERR_FAIL_COND_V(!sync, ERR_INVALID_PARAMETER); sync->disconnect("visibility_changed", callable_mp(this, &SceneReplicationInterface::_visibility_changed)); - return rep_state->config_del_sync(node, sync); + // Untrack synchronizer. + const ObjectID oid = node->get_instance_id(); + const ObjectID sid = sync->get_instance_id(); + ERR_FAIL_COND_V(!tracked_nodes.has(oid), ERR_INVALID_PARAMETER); + TrackedNode &tobj = _track(oid); + tobj.synchronizers.erase(sid); + sync_nodes.erase(sid); + for (KeyValue<int, PeerInfo> &E : peers_info) { + E.value.sync_nodes.erase(sid); + if (sync->get_net_id()) { + E.value.recv_sync_ids.erase(sync->get_net_id()); + } + } + return OK; } -void SceneReplicationInterface::_visibility_changed(int p_peer, ObjectID p_oid) { - if (rep_state->is_spawned_node(p_oid)) { - _update_spawn_visibility(p_peer, p_oid); - } - if (rep_state->is_synced_node(p_oid)) { - _update_sync_visibility(p_peer, p_oid); +void SceneReplicationInterface::_visibility_changed(int p_peer, ObjectID p_sid) { + MultiplayerSynchronizer *sync = get_id_as<MultiplayerSynchronizer>(p_sid); + ERR_FAIL_COND(!sync); // Bug. + Node *node = sync->get_root_node(); + ERR_FAIL_COND(!node); // Bug. + const ObjectID oid = node->get_instance_id(); + if (spawned_nodes.has(oid)) { + _update_spawn_visibility(p_peer, oid); } + _update_sync_visibility(p_peer, sync); } -Error SceneReplicationInterface::_update_sync_visibility(int p_peer, const ObjectID &p_oid) { - MultiplayerSynchronizer *sync = rep_state->get_synchronizer(p_oid); - ERR_FAIL_COND_V(!sync || !sync->is_multiplayer_authority(), ERR_BUG); - bool is_visible = sync->is_visible_to(p_peer); +Error SceneReplicationInterface::_update_sync_visibility(int p_peer, MultiplayerSynchronizer *p_sync) { + ERR_FAIL_COND_V(!p_sync, ERR_BUG); + if (!multiplayer->has_multiplayer_peer() || !p_sync->is_multiplayer_authority()) { + return OK; + } + + const ObjectID &sid = p_sync->get_instance_id(); + bool is_visible = p_sync->is_visible_to(p_peer); if (p_peer == 0) { - for (int pid : rep_state->get_peers()) { + for (KeyValue<int, PeerInfo> &E : peers_info) { // Might be visible to this specific peer. - is_visible = is_visible || sync->is_visible_to(pid); - if (rep_state->is_peer_sync(pid, p_oid) == is_visible) { + bool is_visible_to_peer = is_visible || p_sync->is_visible_to(E.key); + if (is_visible_to_peer == E.value.sync_nodes.has(sid)) { continue; } - if (is_visible) { - rep_state->peer_add_sync(pid, p_oid); + if (is_visible_to_peer) { + E.value.sync_nodes.insert(sid); } else { - rep_state->peer_del_sync(pid, p_oid); + E.value.sync_nodes.erase(sid); } } return OK; } else { - if (is_visible == rep_state->is_peer_sync(p_peer, p_oid)) { + ERR_FAIL_COND_V(!peers_info.has(p_peer), ERR_INVALID_PARAMETER); + if (is_visible == peers_info[p_peer].sync_nodes.has(sid)) { return OK; } if (is_visible) { - return rep_state->peer_add_sync(p_peer, p_oid); + peers_info[p_peer].sync_nodes.insert(sid); } else { - return rep_state->peer_del_sync(p_peer, p_oid); + peers_info[p_peer].sync_nodes.erase(sid); } + return OK; } } Error SceneReplicationInterface::_update_spawn_visibility(int p_peer, const ObjectID &p_oid) { - MultiplayerSpawner *spawner = rep_state->get_spawner(p_oid); - MultiplayerSynchronizer *sync = rep_state->get_synchronizer(p_oid); - Node *node = Object::cast_to<Node>(ObjectDB::get_instance(p_oid)); + const TrackedNode *tnode = tracked_nodes.getptr(p_oid); + ERR_FAIL_COND_V(!tnode, ERR_BUG); + MultiplayerSpawner *spawner = get_id_as<MultiplayerSpawner>(tnode->spawner); + Node *node = get_id_as<Node>(p_oid); ERR_FAIL_COND_V(!node || !spawner || !spawner->is_multiplayer_authority(), ERR_BUG); - bool is_visible = !sync || sync->is_visible_to(p_peer); + ERR_FAIL_COND_V(!tracked_nodes.has(p_oid), ERR_BUG); + const HashSet<ObjectID> synchronizers = tracked_nodes[p_oid].synchronizers; + bool is_visible = true; + for (const ObjectID &sid : synchronizers) { + MultiplayerSynchronizer *sync = get_id_as<MultiplayerSynchronizer>(sid); + ERR_CONTINUE(!sync); + if (!sync->is_multiplayer_authority()) { + continue; + } + // Spawn visibility is composed using OR when multiple synchronizers are present. + if (sync->is_visible_to(p_peer)) { + is_visible = true; + break; + } + is_visible = false; + } // Spawn (and despawn) when needed. HashSet<int> to_spawn; HashSet<int> to_despawn; if (p_peer) { - if (is_visible == rep_state->is_peer_spawn(p_peer, p_oid)) { + ERR_FAIL_COND_V(!peers_info.has(p_peer), ERR_INVALID_PARAMETER); + if (is_visible == peers_info[p_peer].spawn_nodes.has(p_oid)) { return OK; } if (is_visible) { @@ -216,33 +323,37 @@ Error SceneReplicationInterface::_update_spawn_visibility(int p_peer, const Obje } } else { // Check visibility for each peers. - for (int pid : rep_state->get_peers()) { - bool peer_visible = is_visible || sync->is_visible_to(pid); - if (peer_visible == rep_state->is_peer_spawn(pid, p_oid)) { - continue; - } - if (peer_visible) { - to_spawn.insert(pid); + for (const KeyValue<int, PeerInfo> &E : peers_info) { + if (is_visible) { + // This is fast, since the the object is visible to everyone, we don't need to check each peer. + if (E.value.spawn_nodes.has(p_oid)) { + // Already spawned. + continue; + } + to_spawn.insert(E.key); } else { - to_despawn.insert(pid); + // Need to check visibility for each peer. + _update_spawn_visibility(E.key, p_oid); } } } if (to_spawn.size()) { int len = 0; - _make_spawn_packet(node, len); + _make_spawn_packet(node, spawner, len); for (int pid : to_spawn) { + ERR_CONTINUE(!peers_info.has(pid)); int path_id; multiplayer->get_path_cache()->send_object_cache(spawner, pid, path_id); _send_raw(packet_cache.ptr(), len, pid, true); - rep_state->peer_add_spawn(pid, p_oid); + peers_info[pid].spawn_nodes.insert(p_oid); } } if (to_despawn.size()) { int len = 0; _make_despawn_packet(node, len); for (int pid : to_despawn) { - rep_state->peer_del_spawn(pid, p_oid); + ERR_CONTINUE(!peers_info.has(pid)); + peers_info[pid].spawn_nodes.erase(p_oid); _send_raw(packet_cache.ptr(), len, pid, true); } } @@ -259,26 +370,25 @@ Error SceneReplicationInterface::_send_raw(const uint8_t *p_buffer, int p_size, #endif Ref<MultiplayerPeer> peer = multiplayer->get_multiplayer_peer(); - peer->set_target_peer(p_peer); peer->set_transfer_channel(0); peer->set_transfer_mode(p_reliable ? MultiplayerPeer::TRANSFER_MODE_RELIABLE : MultiplayerPeer::TRANSFER_MODE_UNRELIABLE); - return peer->put_packet(p_buffer, p_size); + return multiplayer->send_command(p_peer, p_buffer, p_size); } -Error SceneReplicationInterface::_make_spawn_packet(Node *p_node, int &r_len) { - ERR_FAIL_COND_V(!multiplayer, ERR_BUG); +Error SceneReplicationInterface::_make_spawn_packet(Node *p_node, MultiplayerSpawner *p_spawner, int &r_len) { + ERR_FAIL_COND_V(!multiplayer || !p_node || !p_spawner, ERR_BUG); const ObjectID oid = p_node->get_instance_id(); - MultiplayerSpawner *spawner = rep_state->get_spawner(oid); - ERR_FAIL_COND_V(!spawner || !p_node, ERR_BUG); + const TrackedNode *tnode = tracked_nodes.getptr(oid); + ERR_FAIL_COND_V(!tnode, ERR_INVALID_PARAMETER); - uint32_t nid = rep_state->get_net_id(oid); + uint32_t nid = tnode->net_id; ERR_FAIL_COND_V(!nid, ERR_UNCONFIGURED); // Prepare custom arg and scene_id - uint8_t scene_id = spawner->find_spawnable_scene_index_from_object(oid); + uint8_t scene_id = p_spawner->find_spawnable_scene_index_from_object(oid); bool is_custom = scene_id == MultiplayerSpawner::INVALID_ID; - Variant spawn_arg = spawner->get_spawn_argument(oid); + Variant spawn_arg = p_spawner->get_spawn_argument(oid); int spawn_arg_size = 0; if (is_custom) { Error err = MultiplayerAPI::encode_and_compress_variant(spawn_arg, nullptr, spawn_arg_size, false); @@ -286,31 +396,51 @@ Error SceneReplicationInterface::_make_spawn_packet(Node *p_node, int &r_len) { } // Prepare spawn state. + List<NodePath> state_props; + List<uint32_t> sync_ids; + const HashSet<ObjectID> synchronizers = tnode->synchronizers; + for (const ObjectID &sid : synchronizers) { + MultiplayerSynchronizer *sync = get_id_as<MultiplayerSynchronizer>(sid); + if (!sync->is_multiplayer_authority()) { + continue; + } + ERR_CONTINUE(!sync); + ERR_FAIL_COND_V(sync->get_replication_config().is_null(), ERR_BUG); + for (const NodePath &prop : sync->get_replication_config()->get_spawn_properties()) { + state_props.push_back(prop); + } + // Ensure the synchronizer has an ID. + if (sync->get_net_id() == 0) { + sync->set_net_id(++last_net_id); + } + sync_ids.push_back(sync->get_net_id()); + } int state_size = 0; Vector<Variant> state_vars; Vector<const Variant *> state_varp; - MultiplayerSynchronizer *synchronizer = rep_state->get_synchronizer(oid); - if (synchronizer) { - ERR_FAIL_COND_V(synchronizer->get_replication_config().is_null(), ERR_BUG); - const List<NodePath> props = synchronizer->get_replication_config()->get_spawn_properties(); - Error err = MultiplayerSynchronizer::get_state(props, p_node, state_vars, state_varp); + if (state_props.size()) { + Error err = MultiplayerSynchronizer::get_state(state_props, p_node, state_vars, state_varp); ERR_FAIL_COND_V_MSG(err != OK, err, "Unable to retrieve spawn state."); err = MultiplayerAPI::encode_and_compress_variants(state_varp.ptrw(), state_varp.size(), nullptr, state_size); ERR_FAIL_COND_V_MSG(err != OK, err, "Unable to encode spawn state."); } // Encode scene ID, path ID, net ID, node name. - int path_id = multiplayer->get_path_cache()->make_object_cache(spawner); + int path_id = multiplayer->get_path_cache()->make_object_cache(p_spawner); CharString cname = p_node->get_name().operator String().utf8(); int nlen = encode_cstring(cname.get_data(), nullptr); - MAKE_ROOM(1 + 1 + 4 + 4 + 4 + nlen + (is_custom ? 4 + spawn_arg_size : 0) + state_size); + MAKE_ROOM(1 + 1 + 4 + 4 + 4 + 4 * sync_ids.size() + 4 + nlen + (is_custom ? 4 + spawn_arg_size : 0) + state_size); uint8_t *ptr = packet_cache.ptrw(); ptr[0] = (uint8_t)SceneMultiplayer::NETWORK_COMMAND_SPAWN; ptr[1] = scene_id; int ofs = 2; ofs += encode_uint32(path_id, &ptr[ofs]); ofs += encode_uint32(nid, &ptr[ofs]); + ofs += encode_uint32(sync_ids.size(), &ptr[ofs]); ofs += encode_uint32(nlen, &ptr[ofs]); + for (uint32_t snid : sync_ids) { + ofs += encode_uint32(snid, &ptr[ofs]); + } ofs += encode_cstring(cname.get_data(), &ptr[ofs]); // Write args if (is_custom) { @@ -331,18 +461,20 @@ Error SceneReplicationInterface::_make_spawn_packet(Node *p_node, int &r_len) { Error SceneReplicationInterface::_make_despawn_packet(Node *p_node, int &r_len) { const ObjectID oid = p_node->get_instance_id(); + const TrackedNode *tnode = tracked_nodes.getptr(oid); + ERR_FAIL_COND_V(!tnode, ERR_INVALID_PARAMETER); MAKE_ROOM(5); uint8_t *ptr = packet_cache.ptrw(); ptr[0] = (uint8_t)SceneMultiplayer::NETWORK_COMMAND_DESPAWN; int ofs = 1; - uint32_t nid = rep_state->get_net_id(oid); + uint32_t nid = tnode->net_id; ofs += encode_uint32(nid, &ptr[ofs]); r_len = ofs; return OK; } Error SceneReplicationInterface::on_spawn_receive(int p_from, const uint8_t *p_buffer, int p_buffer_len) { - ERR_FAIL_COND_V_MSG(p_buffer_len < 14, ERR_INVALID_DATA, "Invalid spawn packet received"); + ERR_FAIL_COND_V_MSG(p_buffer_len < 18, ERR_INVALID_DATA, "Invalid spawn packet received"); int ofs = 1; // The spawn/despawn command. uint8_t scene_id = p_buffer[ofs]; ofs += 1; @@ -354,9 +486,16 @@ Error SceneReplicationInterface::on_spawn_receive(int p_from, const uint8_t *p_b uint32_t net_id = decode_uint32(&p_buffer[ofs]); ofs += 4; + uint32_t sync_len = decode_uint32(&p_buffer[ofs]); + ofs += 4; uint32_t name_len = decode_uint32(&p_buffer[ofs]); ofs += 4; - ERR_FAIL_COND_V_MSG(name_len > uint32_t(p_buffer_len - ofs), ERR_INVALID_DATA, vformat("Invalid spawn packet size: %d, wants: %d", p_buffer_len, ofs + name_len)); + ERR_FAIL_COND_V_MSG(name_len + (sync_len * 4) > uint32_t(p_buffer_len - ofs), ERR_INVALID_DATA, vformat("Invalid spawn packet size: %d, wants: %d", p_buffer_len, ofs + name_len + (sync_len * 4))); + List<uint32_t> sync_ids; + for (uint32_t i = 0; i < sync_len; i++) { + sync_ids.push_back(decode_uint32(&p_buffer[ofs])); + ofs += 4; + } ERR_FAIL_COND_V_MSG(name_len < 1, ERR_INVALID_DATA, "Zero spawn name size."); // We need to make sure no trickery happens here, but we want to allow autogenerated ("@") node names. @@ -387,20 +526,35 @@ Error SceneReplicationInterface::on_spawn_receive(int p_from, const uint8_t *p_b } ERR_FAIL_COND_V(!node, ERR_UNAUTHORIZED); node->set_name(name); - rep_state->peer_add_remote(p_from, net_id, node, spawner); + + // Add and track remote + ERR_FAIL_COND_V(!peers_info.has(p_from), ERR_UNAVAILABLE); + ERR_FAIL_COND_V(peers_info[p_from].recv_nodes.has(net_id), ERR_ALREADY_IN_USE); + ObjectID oid = node->get_instance_id(); + TrackedNode &tobj = _track(oid); + tobj.spawner = spawner->get_instance_id(); + tobj.net_id = net_id; + tobj.remote_peer = p_from; + peers_info[p_from].recv_nodes[net_id] = oid; + // The initial state will be applied during the sync config (i.e. before _ready). - int state_len = p_buffer_len - ofs; - if (state_len) { - pending_spawn = node->get_instance_id(); - pending_buffer = &p_buffer[ofs]; - pending_buffer_size = state_len; - } + pending_spawn = node->get_instance_id(); + pending_spawn_remote = p_from; + pending_buffer_size = p_buffer_len - ofs; + pending_buffer = pending_buffer_size > 0 ? &p_buffer[ofs] : nullptr; + pending_sync_net_ids = sync_ids; + parent->add_child(node); spawner->emit_signal(SNAME("spawned"), node); pending_spawn = ObjectID(); + pending_spawn_remote = 0; pending_buffer = nullptr; pending_buffer_size = 0; + if (pending_sync_net_ids.size()) { + pending_sync_net_ids.clear(); + ERR_FAIL_V(ERR_INVALID_DATA); // Should have been consumed. + } return OK; } @@ -409,45 +563,48 @@ Error SceneReplicationInterface::on_despawn_receive(int p_from, const uint8_t *p int ofs = 1; // The spawn/despawn command. uint32_t net_id = decode_uint32(&p_buffer[ofs]); ofs += 4; - Node *node = nullptr; - Error err = rep_state->peer_del_remote(p_from, net_id, &node); - ERR_FAIL_COND_V(err != OK, err); + + // Untrack remote + ERR_FAIL_COND_V(!peers_info.has(p_from), ERR_UNAUTHORIZED); + PeerInfo &pinfo = peers_info[p_from]; + ERR_FAIL_COND_V(!pinfo.recv_nodes.has(net_id), ERR_UNAUTHORIZED); + Node *node = get_id_as<Node>(pinfo.recv_nodes[net_id]); ERR_FAIL_COND_V(!node, ERR_BUG); + pinfo.recv_nodes.erase(net_id); - MultiplayerSpawner *spawner = rep_state->get_spawner(node->get_instance_id()); + const ObjectID oid = node->get_instance_id(); + ERR_FAIL_COND_V(!tracked_nodes.has(oid), ERR_BUG); + MultiplayerSpawner *spawner = get_id_as<MultiplayerSpawner>(tracked_nodes[oid].spawner); ERR_FAIL_COND_V(!spawner, ERR_DOES_NOT_EXIST); ERR_FAIL_COND_V(p_from != spawner->get_multiplayer_authority(), ERR_UNAUTHORIZED); if (node->get_parent() != nullptr) { node->get_parent()->remove_child(node); } - node->queue_delete(); + node->queue_free(); spawner->emit_signal(SNAME("despawned"), node); return OK; } -void SceneReplicationInterface::_send_sync(int p_peer, uint64_t p_msec) { - const HashSet<ObjectID> &to_sync = rep_state->get_peer_sync_nodes(p_peer); - if (to_sync.is_empty()) { - return; - } +void SceneReplicationInterface::_send_sync(int p_peer, const HashSet<ObjectID> p_synchronizers, uint16_t p_sync_net_time, uint64_t p_msec) { MAKE_ROOM(sync_mtu); uint8_t *ptr = packet_cache.ptrw(); ptr[0] = SceneMultiplayer::NETWORK_COMMAND_SYNC; int ofs = 1; - ofs += encode_uint16(rep_state->peer_sync_next(p_peer), &ptr[1]); + ofs += encode_uint16(p_sync_net_time, &ptr[1]); // Can only send updates for already notified nodes. // This is a lazy implementation, we could optimize much more here with by grouping by replication config. - for (const ObjectID &oid : to_sync) { - if (!rep_state->update_sync_time(oid, p_msec)) { + for (const ObjectID &oid : p_synchronizers) { + MultiplayerSynchronizer *sync = get_id_as<MultiplayerSynchronizer>(oid); + ERR_CONTINUE(!sync || !sync->get_replication_config().is_valid() || !sync->is_multiplayer_authority()); + if (!sync->update_outbound_sync_time(p_msec)) { continue; // nothing to sync. } - MultiplayerSynchronizer *sync = rep_state->get_synchronizer(oid); - ERR_CONTINUE(!sync || !sync->get_replication_config().is_valid()); - Node *node = rep_state->get_node(oid); + + Node *node = sync->get_root_node(); ERR_CONTINUE(!node); - uint32_t net_id = rep_state->get_net_id(oid); + uint32_t net_id = sync->get_net_id(); if (net_id == 0 || (net_id & 0x80000000)) { int path_id = 0; bool verified = multiplayer->get_path_cache()->send_object_cache(sync, p_peer, path_id); @@ -455,7 +612,7 @@ void SceneReplicationInterface::_send_sync(int p_peer, uint64_t p_msec) { if (net_id == 0) { // First time path based ID. net_id = path_id | 0x80000000; - rep_state->set_net_id(oid, net_id | 0x80000000); + sync->set_net_id(net_id | 0x80000000); } if (!verified) { // The path based sync is not yet confirmed, skipping. @@ -478,7 +635,7 @@ void SceneReplicationInterface::_send_sync(int p_peer, uint64_t p_msec) { ofs = 3; } if (size) { - ofs += encode_uint32(rep_state->get_net_id(oid), &ptr[ofs]); + ofs += encode_uint32(sync->get_net_id(), &ptr[ofs]); ofs += encode_uint32(size, &ptr[ofs]); MultiplayerAPI::encode_and_compress_variants(varp.ptrw(), varp.size(), &ptr[ofs], size); ofs += size; @@ -494,33 +651,32 @@ Error SceneReplicationInterface::on_sync_receive(int p_from, const uint8_t *p_bu ERR_FAIL_COND_V_MSG(p_buffer_len < 11, ERR_INVALID_DATA, "Invalid sync packet received"); uint16_t time = decode_uint16(&p_buffer[1]); int ofs = 3; - rep_state->peer_sync_recv(p_from, time); while (ofs + 8 < p_buffer_len) { uint32_t net_id = decode_uint32(&p_buffer[ofs]); ofs += 4; uint32_t size = decode_uint32(&p_buffer[ofs]); ofs += 4; - Node *node = nullptr; + MultiplayerSynchronizer *sync = nullptr; if (net_id & 0x80000000) { - MultiplayerSynchronizer *sync = Object::cast_to<MultiplayerSynchronizer>(multiplayer->get_path_cache()->get_cached_object(p_from, net_id & 0x7FFFFFFF)); - ERR_FAIL_COND_V(!sync || sync->get_multiplayer_authority() != p_from, ERR_UNAUTHORIZED); - node = sync->get_node(sync->get_root_path()); - } else { - node = rep_state->peer_get_remote(p_from, net_id); + sync = Object::cast_to<MultiplayerSynchronizer>(multiplayer->get_path_cache()->get_cached_object(p_from, net_id & 0x7FFFFFFF)); + } else if (peers_info[p_from].recv_sync_ids.has(net_id)) { + const ObjectID &sid = peers_info[p_from].recv_sync_ids[net_id]; + sync = get_id_as<MultiplayerSynchronizer>(sid); } - if (!node) { + if (!sync) { // Not received yet. ofs += size; continue; } - const ObjectID oid = node->get_instance_id(); - if (!rep_state->update_last_node_sync(oid, time)) { + Node *node = sync->get_root_node(); + if (sync->get_multiplayer_authority() != p_from || !node) { + ERR_CONTINUE(true); + } + if (!sync->update_inbound_sync_time(time)) { // State is too old. ofs += size; continue; } - MultiplayerSynchronizer *sync = rep_state->get_synchronizer(oid); - ERR_FAIL_COND_V(!sync, ERR_BUG); ERR_FAIL_COND_V(size > uint32_t(p_buffer_len - ofs), ERR_BUG); const List<NodePath> props = sync->get_replication_config()->get_sync_properties(); Vector<Variant> vars; diff --git a/modules/multiplayer/scene_replication_interface.h b/modules/multiplayer/scene_replication_interface.h index 8981647429..c8bd96eb87 100644 --- a/modules/multiplayer/scene_replication_interface.h +++ b/modules/multiplayer/scene_replication_interface.h @@ -31,9 +31,10 @@ #ifndef SCENE_REPLICATION_INTERFACE_H #define SCENE_REPLICATION_INTERFACE_H -#include "scene/main/multiplayer_api.h" +#include "core/object/ref_counted.h" -#include "scene_replication_state.h" +#include "multiplayer_spawner.h" +#include "multiplayer_synchronizer.h" class SceneMultiplayer; @@ -41,25 +42,68 @@ class SceneReplicationInterface : public RefCounted { GDCLASS(SceneReplicationInterface, RefCounted); private: - void _send_sync(int p_peer, uint64_t p_msec); - Error _make_spawn_packet(Node *p_node, int &r_len); - Error _make_despawn_packet(Node *p_node, int &r_len); - Error _send_raw(const uint8_t *p_buffer, int p_size, int p_peer, bool p_reliable); + struct TrackedNode { + ObjectID id; + uint32_t net_id = 0; + uint32_t remote_peer = 0; + ObjectID spawner; + HashSet<ObjectID> synchronizers; - void _visibility_changed(int p_peer, ObjectID p_oid); - Error _update_sync_visibility(int p_peer, const ObjectID &p_oid); - Error _update_spawn_visibility(int p_peer, const ObjectID &p_oid); - void _free_remotes(int p_peer); + bool operator==(const ObjectID &p_other) { return id == p_other; } - Ref<SceneReplicationState> rep_state; - SceneMultiplayer *multiplayer = nullptr; - PackedByteArray packet_cache; - int sync_mtu = 1350; // Highly dependent on underlying protocol. + _FORCE_INLINE_ MultiplayerSpawner *get_spawner() const { return spawner.is_valid() ? Object::cast_to<MultiplayerSpawner>(ObjectDB::get_instance(spawner)) : nullptr; } + TrackedNode() {} + TrackedNode(const ObjectID &p_id) { id = p_id; } + TrackedNode(const ObjectID &p_id, uint32_t p_net_id) { + id = p_id; + net_id = p_net_id; + } + }; + + struct PeerInfo { + HashSet<ObjectID> sync_nodes; + HashSet<ObjectID> spawn_nodes; + HashMap<uint32_t, ObjectID> recv_sync_ids; + HashMap<uint32_t, ObjectID> recv_nodes; + uint16_t last_sent_sync = 0; + }; + + // Replication state. + HashMap<int, PeerInfo> peers_info; + uint32_t last_net_id = 0; + HashMap<ObjectID, TrackedNode> tracked_nodes; + HashSet<ObjectID> spawned_nodes; + HashSet<ObjectID> sync_nodes; - // An hack to apply the initial state before ready. + // Pending spawn information. ObjectID pending_spawn; + int pending_spawn_remote = 0; const uint8_t *pending_buffer = nullptr; int pending_buffer_size = 0; + List<uint32_t> pending_sync_net_ids; + + // Replicator config. + SceneMultiplayer *multiplayer = nullptr; + PackedByteArray packet_cache; + int sync_mtu = 1350; // Highly dependent on underlying protocol. + + TrackedNode &_track(const ObjectID &p_id); + void _untrack(const ObjectID &p_id); + + void _send_sync(int p_peer, const HashSet<ObjectID> p_synchronizers, uint16_t p_sync_net_time, uint64_t p_msec); + Error _make_spawn_packet(Node *p_node, MultiplayerSpawner *p_spawner, int &r_len); + Error _make_despawn_packet(Node *p_node, int &r_len); + Error _send_raw(const uint8_t *p_buffer, int p_size, int p_peer, bool p_reliable); + + void _visibility_changed(int p_peer, ObjectID p_oid); + Error _update_sync_visibility(int p_peer, MultiplayerSynchronizer *p_sync); + Error _update_spawn_visibility(int p_peer, const ObjectID &p_oid); + void _free_remotes(const PeerInfo &p_info); + + template <class T> + static T *get_id_as(const ObjectID &p_id) { + return p_id.is_valid() ? Object::cast_to<T>(ObjectDB::get_instance(p_id)) : nullptr; + } public: static void make_default(); @@ -78,7 +122,6 @@ public: Error on_sync_receive(int p_from, const uint8_t *p_buffer, int p_buffer_len); SceneReplicationInterface(SceneMultiplayer *p_multiplayer) { - rep_state.instantiate(); multiplayer = p_multiplayer; } }; diff --git a/modules/multiplayer/scene_replication_state.cpp b/modules/multiplayer/scene_replication_state.cpp deleted file mode 100644 index 25442bb7fa..0000000000 --- a/modules/multiplayer/scene_replication_state.cpp +++ /dev/null @@ -1,267 +0,0 @@ -/*************************************************************************/ -/* scene_replication_state.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#include "scene_replication_state.h" - -#include "scene/scene_string_names.h" - -#include "multiplayer_spawner.h" -#include "multiplayer_synchronizer.h" - -SceneReplicationState::TrackedNode &SceneReplicationState::_track(const ObjectID &p_id) { - if (!tracked_nodes.has(p_id)) { - tracked_nodes[p_id] = TrackedNode(p_id); - Node *node = Object::cast_to<Node>(ObjectDB::get_instance(p_id)); - node->connect(SceneStringNames::get_singleton()->tree_exited, callable_mp(this, &SceneReplicationState::_untrack).bind(p_id), Node::CONNECT_ONESHOT); - } - return tracked_nodes[p_id]; -} - -void SceneReplicationState::_untrack(const ObjectID &p_id) { - if (tracked_nodes.has(p_id)) { - uint32_t net_id = tracked_nodes[p_id].net_id; - uint32_t peer = tracked_nodes[p_id].remote_peer; - tracked_nodes.erase(p_id); - // If it was spawned by a remote, remove it from the received nodes. - if (peer && peers_info.has(peer)) { - peers_info[peer].recv_nodes.erase(net_id); - } - // If we spawned or synced it, we need to remove it from any peer it was sent to. - if (net_id || peer == 0) { - for (KeyValue<int, PeerInfo> &E : peers_info) { - E.value.sync_nodes.erase(p_id); - E.value.spawn_nodes.erase(p_id); - } - } - } -} - -const HashMap<uint32_t, ObjectID> SceneReplicationState::peer_get_remotes(int p_peer) const { - return peers_info.has(p_peer) ? peers_info[p_peer].recv_nodes : HashMap<uint32_t, ObjectID>(); -} - -bool SceneReplicationState::update_last_node_sync(const ObjectID &p_id, uint16_t p_time) { - TrackedNode *tnode = tracked_nodes.getptr(p_id); - ERR_FAIL_COND_V(!tnode, false); - if (p_time <= tnode->last_sync && tnode->last_sync - p_time < 32767) { - return false; - } - tnode->last_sync = p_time; - return true; -} - -bool SceneReplicationState::update_sync_time(const ObjectID &p_id, uint64_t p_msec) { - TrackedNode *tnode = tracked_nodes.getptr(p_id); - ERR_FAIL_COND_V(!tnode, false); - MultiplayerSynchronizer *sync = get_synchronizer(p_id); - if (!sync) { - return false; - } - if (tnode->last_sync_msec == p_msec) { - return true; - } - if (p_msec >= tnode->last_sync_msec + sync->get_replication_interval_msec()) { - tnode->last_sync_msec = p_msec; - return true; - } - return false; -} - -uint32_t SceneReplicationState::get_net_id(const ObjectID &p_id) const { - const TrackedNode *tnode = tracked_nodes.getptr(p_id); - ERR_FAIL_COND_V(!tnode, 0); - return tnode->net_id; -} - -void SceneReplicationState::set_net_id(const ObjectID &p_id, uint32_t p_net_id) { - TrackedNode *tnode = tracked_nodes.getptr(p_id); - ERR_FAIL_COND(!tnode); - tnode->net_id = p_net_id; -} - -uint32_t SceneReplicationState::ensure_net_id(const ObjectID &p_id) { - TrackedNode *tnode = tracked_nodes.getptr(p_id); - ERR_FAIL_COND_V(!tnode, 0); - if (tnode->net_id == 0) { - tnode->net_id = ++last_net_id; - } - return tnode->net_id; -} - -void SceneReplicationState::on_peer_change(int p_peer, bool p_connected) { - if (p_connected) { - peers_info[p_peer] = PeerInfo(); - known_peers.insert(p_peer); - } else { - peers_info.erase(p_peer); - known_peers.erase(p_peer); - } -} - -void SceneReplicationState::reset() { - peers_info.clear(); - known_peers.clear(); - // Tracked nodes are cleared on deletion, here we only reset the ids so they can be later re-assigned. - for (KeyValue<ObjectID, TrackedNode> &E : tracked_nodes) { - TrackedNode &tobj = E.value; - tobj.net_id = 0; - tobj.remote_peer = 0; - tobj.last_sync = 0; - } -} - -Error SceneReplicationState::config_add_spawn(Node *p_node, MultiplayerSpawner *p_spawner) { - const ObjectID oid = p_node->get_instance_id(); - TrackedNode &tobj = _track(oid); - ERR_FAIL_COND_V(tobj.spawner != ObjectID(), ERR_ALREADY_IN_USE); - tobj.spawner = p_spawner->get_instance_id(); - spawned_nodes.insert(oid); - return OK; -} - -Error SceneReplicationState::config_del_spawn(Node *p_node, MultiplayerSpawner *p_spawner) { - const ObjectID oid = p_node->get_instance_id(); - ERR_FAIL_COND_V(!is_tracked(oid), ERR_INVALID_PARAMETER); - TrackedNode &tobj = _track(oid); - ERR_FAIL_COND_V(tobj.spawner != p_spawner->get_instance_id(), ERR_INVALID_PARAMETER); - tobj.spawner = ObjectID(); - spawned_nodes.erase(oid); - for (KeyValue<int, PeerInfo> &E : peers_info) { - E.value.spawn_nodes.erase(oid); - } - return OK; -} - -Error SceneReplicationState::config_add_sync(Node *p_node, MultiplayerSynchronizer *p_sync) { - const ObjectID oid = p_node->get_instance_id(); - TrackedNode &tobj = _track(oid); - ERR_FAIL_COND_V(tobj.synchronizer != ObjectID(), ERR_ALREADY_IN_USE); - tobj.synchronizer = p_sync->get_instance_id(); - synced_nodes.insert(oid); - return OK; -} - -Error SceneReplicationState::config_del_sync(Node *p_node, MultiplayerSynchronizer *p_sync) { - const ObjectID oid = p_node->get_instance_id(); - ERR_FAIL_COND_V(!is_tracked(oid), ERR_INVALID_PARAMETER); - TrackedNode &tobj = _track(oid); - ERR_FAIL_COND_V(tobj.synchronizer != p_sync->get_instance_id(), ERR_INVALID_PARAMETER); - tobj.synchronizer = ObjectID(); - synced_nodes.erase(oid); - for (KeyValue<int, PeerInfo> &E : peers_info) { - E.value.sync_nodes.erase(oid); - } - return OK; -} - -Error SceneReplicationState::peer_add_sync(int p_peer, const ObjectID &p_id) { - ERR_FAIL_COND_V(!peers_info.has(p_peer), ERR_INVALID_PARAMETER); - peers_info[p_peer].sync_nodes.insert(p_id); - return OK; -} - -Error SceneReplicationState::peer_del_sync(int p_peer, const ObjectID &p_id) { - ERR_FAIL_COND_V(!peers_info.has(p_peer), ERR_INVALID_PARAMETER); - peers_info[p_peer].sync_nodes.erase(p_id); - return OK; -} - -const HashSet<ObjectID> SceneReplicationState::get_peer_sync_nodes(int p_peer) { - ERR_FAIL_COND_V(!peers_info.has(p_peer), HashSet<ObjectID>()); - return peers_info[p_peer].sync_nodes; -} - -bool SceneReplicationState::is_peer_sync(int p_peer, const ObjectID &p_id) const { - ERR_FAIL_COND_V(!peers_info.has(p_peer), false); - return peers_info[p_peer].sync_nodes.has(p_id); -} - -Error SceneReplicationState::peer_add_spawn(int p_peer, const ObjectID &p_id) { - ERR_FAIL_COND_V(!peers_info.has(p_peer), ERR_INVALID_PARAMETER); - peers_info[p_peer].spawn_nodes.insert(p_id); - return OK; -} - -Error SceneReplicationState::peer_del_spawn(int p_peer, const ObjectID &p_id) { - ERR_FAIL_COND_V(!peers_info.has(p_peer), ERR_INVALID_PARAMETER); - peers_info[p_peer].spawn_nodes.erase(p_id); - return OK; -} - -const HashSet<ObjectID> SceneReplicationState::get_peer_spawn_nodes(int p_peer) { - ERR_FAIL_COND_V(!peers_info.has(p_peer), HashSet<ObjectID>()); - return peers_info[p_peer].spawn_nodes; -} - -bool SceneReplicationState::is_peer_spawn(int p_peer, const ObjectID &p_id) const { - ERR_FAIL_COND_V(!peers_info.has(p_peer), false); - return peers_info[p_peer].spawn_nodes.has(p_id); -} - -Node *SceneReplicationState::peer_get_remote(int p_peer, uint32_t p_net_id) { - PeerInfo *info = peers_info.getptr(p_peer); - return info && info->recv_nodes.has(p_net_id) ? Object::cast_to<Node>(ObjectDB::get_instance(info->recv_nodes[p_net_id])) : nullptr; -} - -Error SceneReplicationState::peer_add_remote(int p_peer, uint32_t p_net_id, Node *p_node, MultiplayerSpawner *p_spawner) { - ERR_FAIL_COND_V(!p_node || !p_spawner, ERR_INVALID_PARAMETER); - ERR_FAIL_COND_V(!peers_info.has(p_peer), ERR_UNAVAILABLE); - PeerInfo &pinfo = peers_info[p_peer]; - ObjectID oid = p_node->get_instance_id(); - TrackedNode &tobj = _track(oid); - tobj.spawner = p_spawner->get_instance_id(); - tobj.net_id = p_net_id; - tobj.remote_peer = p_peer; - tobj.last_sync = pinfo.last_recv_sync; - // Also track as a remote. - ERR_FAIL_COND_V(pinfo.recv_nodes.has(p_net_id), ERR_ALREADY_IN_USE); - pinfo.recv_nodes[p_net_id] = oid; - return OK; -} - -Error SceneReplicationState::peer_del_remote(int p_peer, uint32_t p_net_id, Node **r_node) { - ERR_FAIL_COND_V(!peers_info.has(p_peer), ERR_UNAUTHORIZED); - PeerInfo &info = peers_info[p_peer]; - ERR_FAIL_COND_V(!info.recv_nodes.has(p_net_id), ERR_UNAUTHORIZED); - *r_node = Object::cast_to<Node>(ObjectDB::get_instance(info.recv_nodes[p_net_id])); - info.recv_nodes.erase(p_net_id); - return OK; -} - -uint16_t SceneReplicationState::peer_sync_next(int p_peer) { - ERR_FAIL_COND_V(!peers_info.has(p_peer), 0); - PeerInfo &info = peers_info[p_peer]; - return ++info.last_sent_sync; -} - -void SceneReplicationState::peer_sync_recv(int p_peer, uint16_t p_time) { - ERR_FAIL_COND(!peers_info.has(p_peer)); - peers_info[p_peer].last_recv_sync = p_time; -} diff --git a/modules/multiplayer/scene_replication_state.h b/modules/multiplayer/scene_replication_state.h deleted file mode 100644 index bdff6ae3b7..0000000000 --- a/modules/multiplayer/scene_replication_state.h +++ /dev/null @@ -1,135 +0,0 @@ -/*************************************************************************/ -/* scene_replication_state.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#ifndef SCENE_REPLICATION_STATE_H -#define SCENE_REPLICATION_STATE_H - -#include "core/object/ref_counted.h" - -#include "multiplayer_spawner.h" -#include "multiplayer_synchronizer.h" - -class MultiplayerSpawner; -class MultiplayerSynchronizer; -class Node; - -class SceneReplicationState : public RefCounted { -private: - struct TrackedNode { - ObjectID id; - uint32_t net_id = 0; - uint32_t remote_peer = 0; - ObjectID spawner; - ObjectID synchronizer; - uint16_t last_sync = 0; - uint64_t last_sync_msec = 0; - - bool operator==(const ObjectID &p_other) { return id == p_other; } - - Node *get_node() const { return id.is_valid() ? Object::cast_to<Node>(ObjectDB::get_instance(id)) : nullptr; } - MultiplayerSpawner *get_spawner() const { return spawner.is_valid() ? Object::cast_to<MultiplayerSpawner>(ObjectDB::get_instance(spawner)) : nullptr; } - MultiplayerSynchronizer *get_synchronizer() const { return synchronizer.is_valid() ? Object::cast_to<MultiplayerSynchronizer>(ObjectDB::get_instance(synchronizer)) : nullptr; } - TrackedNode() {} - TrackedNode(const ObjectID &p_id) { id = p_id; } - TrackedNode(const ObjectID &p_id, uint32_t p_net_id) { - id = p_id; - net_id = p_net_id; - } - }; - - struct PeerInfo { - HashSet<ObjectID> sync_nodes; - HashSet<ObjectID> spawn_nodes; - HashMap<uint32_t, ObjectID> recv_nodes; - uint16_t last_sent_sync = 0; - uint16_t last_recv_sync = 0; - }; - - HashSet<int> known_peers; - uint32_t last_net_id = 0; - HashMap<ObjectID, TrackedNode> tracked_nodes; - HashMap<int, PeerInfo> peers_info; - HashSet<ObjectID> spawned_nodes; - HashSet<ObjectID> synced_nodes; - - TrackedNode &_track(const ObjectID &p_id); - void _untrack(const ObjectID &p_id); - bool is_tracked(const ObjectID &p_id) const { return tracked_nodes.has(p_id); } - -public: - const HashSet<int> get_peers() const { return known_peers; } - const HashSet<ObjectID> &get_spawned_nodes() const { return spawned_nodes; } - bool is_spawned_node(const ObjectID &p_id) const { return spawned_nodes.has(p_id); } - const HashSet<ObjectID> &get_synced_nodes() const { return synced_nodes; } - bool is_synced_node(const ObjectID &p_id) const { return synced_nodes.has(p_id); } - - MultiplayerSynchronizer *get_synchronizer(const ObjectID &p_id) { return tracked_nodes.has(p_id) ? tracked_nodes[p_id].get_synchronizer() : nullptr; } - MultiplayerSpawner *get_spawner(const ObjectID &p_id) { return tracked_nodes.has(p_id) ? tracked_nodes[p_id].get_spawner() : nullptr; } - Node *get_node(const ObjectID &p_id) { return tracked_nodes.has(p_id) ? tracked_nodes[p_id].get_node() : nullptr; } - bool update_last_node_sync(const ObjectID &p_id, uint16_t p_time); - bool update_sync_time(const ObjectID &p_id, uint64_t p_msec); - - uint32_t get_net_id(const ObjectID &p_id) const; - void set_net_id(const ObjectID &p_id, uint32_t p_net_id); - uint32_t ensure_net_id(const ObjectID &p_id); - - void reset(); - void on_peer_change(int p_peer, bool p_connected); - - Error config_add_spawn(Node *p_node, MultiplayerSpawner *p_spawner); - Error config_del_spawn(Node *p_node, MultiplayerSpawner *p_spawner); - - Error config_add_sync(Node *p_node, MultiplayerSynchronizer *p_sync); - Error config_del_sync(Node *p_node, MultiplayerSynchronizer *p_sync); - - Error peer_add_sync(int p_peer, const ObjectID &p_id); - Error peer_del_sync(int p_peer, const ObjectID &p_id); - - const HashSet<ObjectID> get_peer_sync_nodes(int p_peer); - bool is_peer_sync(int p_peer, const ObjectID &p_id) const; - - Error peer_add_spawn(int p_peer, const ObjectID &p_id); - Error peer_del_spawn(int p_peer, const ObjectID &p_id); - - const HashSet<ObjectID> get_peer_spawn_nodes(int p_peer); - bool is_peer_spawn(int p_peer, const ObjectID &p_id) const; - - const HashMap<uint32_t, ObjectID> peer_get_remotes(int p_peer) const; - Node *peer_get_remote(int p_peer, uint32_t p_net_id); - Error peer_add_remote(int p_peer, uint32_t p_net_id, Node *p_node, MultiplayerSpawner *p_spawner); - Error peer_del_remote(int p_peer, uint32_t p_net_id, Node **r_node); - - uint16_t peer_sync_next(int p_peer); - void peer_sync_recv(int p_peer, uint16_t p_time); - - SceneReplicationState() {} -}; - -#endif // SCENE_REPLICATION_STATE_H diff --git a/modules/multiplayer/scene_rpc_interface.cpp b/modules/multiplayer/scene_rpc_interface.cpp index 65090b9316..acc113c901 100644 --- a/modules/multiplayer/scene_rpc_interface.cpp +++ b/modules/multiplayer/scene_rpc_interface.cpp @@ -412,8 +412,7 @@ void SceneRPCInterface::_send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, con if (has_all_peers) { // They all have verified paths, so send fast. - peer->set_target_peer(p_to); // To all of you. - peer->put_packet(packet_cache.ptr(), ofs); // A message with love. + multiplayer->send_command(p_to, packet_cache.ptr(), ofs); } else { // Unreachable because the node ID is never compressed if the peers doesn't know it. CRASH_COND(node_id_compression != NETWORK_NODE_ID_COMPRESSION_32); @@ -438,16 +437,14 @@ void SceneRPCInterface::_send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, con bool confirmed = multiplayer->get_path_cache()->is_cache_confirmed(from_path, P); - peer->set_target_peer(P); // To this one specifically. - if (confirmed) { // This one confirmed path, so use id. encode_uint32(psc_id, &(packet_cache.write[1])); - peer->put_packet(packet_cache.ptr(), ofs); + multiplayer->send_command(P, packet_cache.ptr(), ofs); } else { // This one did not confirm path yet, so use entire path (sorry!). encode_uint32(0x80000000 | ofs, &(packet_cache.write[1])); // Offset to path and flag. - peer->put_packet(packet_cache.ptr(), ofs + path_len); + multiplayer->send_command(P, packet_cache.ptr(), ofs + path_len); } } } |