diff --git a/core/multiplayer/multiplayer_api.cpp b/core/multiplayer/multiplayer_api.cpp index 9605647b3f18..c648bb96e560 100644 --- a/core/multiplayer/multiplayer_api.cpp +++ b/core/multiplayer/multiplayer_api.cpp @@ -39,7 +39,6 @@ #include "core/os/os.h" #endif -MultiplayerReplicationInterface *(*MultiplayerAPI::create_default_replication_interface)(MultiplayerAPI *p_multiplayer) = nullptr; MultiplayerRPCInterface *(*MultiplayerAPI::create_default_rpc_interface)(MultiplayerAPI *p_multiplayer) = nullptr; MultiplayerCacheInterface *(*MultiplayerAPI::create_default_cache_interface)(MultiplayerAPI *p_multiplayer) = nullptr; @@ -85,7 +84,6 @@ void MultiplayerAPI::poll() { return; // It's also possible that a packet or RPC caused a disconnection, so also check here. } } - replicator->on_network_process(); } void MultiplayerAPI::clear() { @@ -129,7 +127,6 @@ void MultiplayerAPI::set_multiplayer_peer(const Ref &p_peer) { multiplayer_peer->connect("connection_failed", callable_mp(this, &MultiplayerAPI::_connection_failed)); multiplayer_peer->connect("server_disconnected", callable_mp(this, &MultiplayerAPI::_server_disconnected)); } - replicator->on_reset(); } Ref MultiplayerAPI::get_multiplayer_peer() const { @@ -163,15 +160,6 @@ void MultiplayerAPI::_process_packet(int p_from, const uint8_t *p_packet, int p_ case NETWORK_COMMAND_RAW: { _process_raw(p_from, p_packet, p_packet_len); } break; - case NETWORK_COMMAND_SPAWN: { - replicator->on_spawn_receive(p_from, p_packet, p_packet_len); - } break; - case NETWORK_COMMAND_DESPAWN: { - replicator->on_despawn_receive(p_from, p_packet, p_packet_len); - } break; - case NETWORK_COMMAND_SYNC: { - replicator->on_sync_receive(p_from, p_packet, p_packet_len); - } break; } } @@ -402,12 +390,10 @@ Error MultiplayerAPI::decode_and_decompress_variants(Vector &r_variants void MultiplayerAPI::_add_peer(int p_id) { connected_peers.insert(p_id); cache->on_peer_change(p_id, true); - replicator->on_peer_change(p_id, true); emit_signal(SNAME("peer_connected"), p_id); } void MultiplayerAPI::_del_peer(int p_id) { - 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); @@ -422,7 +408,6 @@ void MultiplayerAPI::_connection_failed() { } void MultiplayerAPI::_server_disconnected() { - replicator->on_reset(); emit_signal(SNAME("server_disconnected")); } @@ -517,22 +502,6 @@ void MultiplayerAPI::rpcp(Object *p_obj, int p_peer_id, const StringName &p_meth rpc->rpcp(p_obj, p_peer_id, p_method, p_arg, p_argcount); } -Error MultiplayerAPI::spawn(Object *p_object, Variant p_config) { - return replicator->on_spawn(p_object, p_config); -} - -Error MultiplayerAPI::despawn(Object *p_object, Variant p_config) { - return replicator->on_despawn(p_object, p_config); -} - -Error MultiplayerAPI::replication_start(Object *p_object, Variant p_config) { - return replicator->on_replication_start(p_object, p_config); -} - -Error MultiplayerAPI::replication_stop(Object *p_object, Variant p_config) { - return replicator->on_replication_stop(p_object, p_config); -} - void MultiplayerAPI::_bind_methods() { ClassDB::bind_method(D_METHOD("set_root_path", "path"), &MultiplayerAPI::set_root_path); ClassDB::bind_method(D_METHOD("get_root_path"), &MultiplayerAPI::get_root_path); @@ -567,11 +536,6 @@ void MultiplayerAPI::_bind_methods() { } MultiplayerAPI::MultiplayerAPI() { - if (create_default_replication_interface) { - replicator = Ref(create_default_replication_interface(this)); - } else { - replicator.instantiate(); - } if (create_default_rpc_interface) { rpc = Ref(create_default_rpc_interface(this)); } else { diff --git a/core/multiplayer/multiplayer_api.h b/core/multiplayer/multiplayer_api.h index cc7743ccf8d3..b2628f769f7a 100644 --- a/core/multiplayer/multiplayer_api.h +++ b/core/multiplayer/multiplayer_api.h @@ -37,24 +37,6 @@ class MultiplayerAPI; -class MultiplayerReplicationInterface : public RefCounted { - GDCLASS(MultiplayerReplicationInterface, RefCounted); - -public: - virtual void on_peer_change(int p_id, bool p_connected) {} - virtual void on_reset() {} - virtual Error on_spawn_receive(int p_from, const uint8_t *p_buffer, int p_buffer_len) { return ERR_UNAVAILABLE; } - virtual Error on_despawn_receive(int p_from, const uint8_t *p_buffer, int p_buffer_len) { return ERR_UNAVAILABLE; } - virtual Error on_sync_receive(int p_from, const uint8_t *p_buffer, int p_buffer_len) { return ERR_UNAVAILABLE; } - virtual Error on_spawn(Object *p_obj, Variant p_config) { return ERR_UNAVAILABLE; } - virtual Error on_despawn(Object *p_obj, Variant p_config) { return ERR_UNAVAILABLE; } - virtual Error on_replication_start(Object *p_obj, Variant p_config) { return ERR_UNAVAILABLE; } - virtual Error on_replication_stop(Object *p_obj, Variant p_config) { return ERR_UNAVAILABLE; } - virtual void on_network_process() {} - - MultiplayerReplicationInterface() {} -}; - class MultiplayerRPCInterface : public RefCounted { GDCLASS(MultiplayerRPCInterface, RefCounted); @@ -93,9 +75,6 @@ class MultiplayerAPI : public RefCounted { NETWORK_COMMAND_SIMPLIFY_PATH, NETWORK_COMMAND_CONFIRM_PATH, NETWORK_COMMAND_RAW, - NETWORK_COMMAND_SPAWN, - NETWORK_COMMAND_DESPAWN, - NETWORK_COMMAND_SYNC, }; // For each command, the 4 MSB can contain custom flags, as defined by subsystems. @@ -123,7 +102,6 @@ class MultiplayerAPI : public RefCounted { bool allow_object_decoding = false; Ref cache; - Ref replicator; Ref rpc; protected: @@ -133,7 +111,6 @@ class MultiplayerAPI : public RefCounted { void _process_raw(int p_from, const uint8_t *p_packet, int p_packet_len); public: - static MultiplayerReplicationInterface *(*create_default_replication_interface)(MultiplayerAPI *p_multiplayer); static MultiplayerRPCInterface *(*create_default_rpc_interface)(MultiplayerAPI *p_multiplayer); static MultiplayerCacheInterface *(*create_default_cache_interface)(MultiplayerAPI *p_multiplayer); @@ -154,11 +131,6 @@ class MultiplayerAPI : public RefCounted { // RPC API void rpcp(Object *p_obj, int p_peer_id, const StringName &p_method, const Variant **p_arg, int p_argcount); String get_rpc_md5(const Object *p_obj) const; - // Replication API - Error spawn(Object *p_object, Variant p_config); - Error despawn(Object *p_object, Variant p_config); - Error replication_start(Object *p_object, Variant p_config); - Error replication_stop(Object *p_object, Variant p_config); // Cache API bool send_object_cache(Object *p_obj, NodePath p_path, int p_target, int &p_id); Object *get_cached_object(int p_from, uint32_t p_cache_id); diff --git a/doc/classes/MultiplayerSpawner.xml b/doc/classes/MultiplayerSpawner.xml deleted file mode 100644 index 4ca92728ff24..000000000000 --- a/doc/classes/MultiplayerSpawner.xml +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 64665833dfb5..73411d1cea61 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -172,7 +172,6 @@ #include "editor/plugins/polygon_2d_editor_plugin.h" #include "editor/plugins/polygon_3d_editor_plugin.h" #include "editor/plugins/ray_cast_2d_editor_plugin.h" -#include "editor/plugins/replication_editor_plugin.h" #include "editor/plugins/resource_preloader_editor_plugin.h" #include "editor/plugins/root_motion_editor_plugin.h" #include "editor/plugins/script_editor_plugin.h" @@ -7065,7 +7064,6 @@ EditorNode::EditorNode() { // More visually meaningful to have this later. raise_bottom_panel_item(AnimationPlayerEditor::get_singleton()); - add_editor_plugin(memnew(ReplicationEditorPlugin)); add_editor_plugin(VersionControlEditorPlugin::get_singleton()); add_editor_plugin(memnew(ShaderEditorPlugin)); diff --git a/modules/replication/SCsub b/modules/replication/SCsub new file mode 100644 index 000000000000..a4efe3fbbd1a --- /dev/null +++ b/modules/replication/SCsub @@ -0,0 +1,9 @@ +#!/usr/bin/env python + +Import("env") +Import("env_modules") + +env_replication = env_modules.Clone() + +env_replication.add_source_files(env.modules_sources, "editor/*.cpp") +env_replication.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/replication/config.py b/modules/replication/config.py new file mode 100644 index 000000000000..2abecedcd294 --- /dev/null +++ b/modules/replication/config.py @@ -0,0 +1,18 @@ +def can_build(env, platform): + return True + + +def configure(env): + pass + + +def get_doc_classes(): + return [ + "MultiplayerSpawner", + "MultiplayerSynchronizer", + "SceneReplicationConfig", + ] + + +def get_doc_path(): + return "doc_classes" diff --git a/modules/replication/doc_classes/MultiplayerSpawner.xml b/modules/replication/doc_classes/MultiplayerSpawner.xml new file mode 100644 index 000000000000..b9c18e1b60da --- /dev/null +++ b/modules/replication/doc_classes/MultiplayerSpawner.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + Virtual method called when spawning a custom node, either on the multiplayer authority or on a puppet. + + Do not add custom nodes to the scene, as this is done automatically. + + + + + + + Request, as the multiplayer authority, to spawn a node on all clients. Calls [method _spawn_custom] internally. + + + + + + Maxmium node count that can be spawned. Includes both custom spawns and spawned scenes. A value of 0 means no limit. + + + Parent node that listens for spawnable children. Custom spawned nodes are added here too. + + + Array of scenes that, when added as a child of [member spawn_path], are automatically spawned on multiplayer puppets. + + + + + + + + Signal emitted after the multiplayer authority despawns a scene instance. Not called for custom spawns. + + + + + + + Signal emitted after the multiplayer authority spawns a scene instance. Not called for custom spawns. + + + + diff --git a/doc/classes/MultiplayerSynchronizer.xml b/modules/replication/doc_classes/MultiplayerSynchronizer.xml similarity index 93% rename from doc/classes/MultiplayerSynchronizer.xml rename to modules/replication/doc_classes/MultiplayerSynchronizer.xml index ac067791e67b..f3e25409441d 100644 --- a/doc/classes/MultiplayerSynchronizer.xml +++ b/modules/replication/doc_classes/MultiplayerSynchronizer.xml @@ -1,5 +1,5 @@ - + diff --git a/doc/classes/SceneReplicationConfig.xml b/modules/replication/doc_classes/SceneReplicationConfig.xml similarity index 97% rename from doc/classes/SceneReplicationConfig.xml rename to modules/replication/doc_classes/SceneReplicationConfig.xml index 62c108a47707..1d6dec2f9238 100644 --- a/doc/classes/SceneReplicationConfig.xml +++ b/modules/replication/doc_classes/SceneReplicationConfig.xml @@ -1,5 +1,5 @@ - + diff --git a/editor/plugins/replication_editor_plugin.cpp b/modules/replication/editor/replication_editor_plugin.cpp similarity index 99% rename from editor/plugins/replication_editor_plugin.cpp rename to modules/replication/editor/replication_editor_plugin.cpp index 72fe3c5f20fd..da03f3e50c2f 100644 --- a/editor/plugins/replication_editor_plugin.cpp +++ b/modules/replication/editor/replication_editor_plugin.cpp @@ -30,12 +30,15 @@ #include "replication_editor_plugin.h" +#ifdef TOOLS_ENABLED + #include "editor/editor_node.h" #include "editor/editor_scale.h" #include "editor/inspector_dock.h" #include "scene/gui/dialogs.h" #include "scene/gui/tree.h" -#include "scene/multiplayer/multiplayer_synchronizer.h" + +#include "../multiplayer_synchronizer.h" void ReplicationEditor::_pick_node_filter_text_changed(const String &p_newtext) { TreeItem *root_item = pick_node->get_scene_tree()->get_scene_tree()->get_root(); @@ -643,3 +646,5 @@ void ReplicationEditorPlugin::make_visible(bool p_visible) { button->hide(); } } + +#endif // TOOLS_ENABLED diff --git a/editor/plugins/replication_editor_plugin.h b/modules/replication/editor/replication_editor_plugin.h similarity index 98% rename from editor/plugins/replication_editor_plugin.h rename to modules/replication/editor/replication_editor_plugin.h index df3d97f88482..be6a6a8e35a8 100644 --- a/editor/plugins/replication_editor_plugin.h +++ b/modules/replication/editor/replication_editor_plugin.h @@ -31,13 +31,15 @@ #ifndef REPLICATION_EDITOR_PLUGIN_H #define REPLICATION_EDITOR_PLUGIN_H -#include "editor/editor_plugin.h" -#include "scene/resources/scene_replication_config.h" +#ifdef TOOLS_ENABLED +#include "editor/editor_plugin.h" #include "editor/editor_spin_slider.h" #include "editor/property_editor.h" #include "editor/property_selector.h" +#include "../scene_replication_config.h" + class ConfirmationDialog; class MultiplayerSynchronizer; class Tree; @@ -132,4 +134,6 @@ class ReplicationEditorPlugin : public EditorPlugin { ~ReplicationEditorPlugin(); }; +#endif // TOOLS_ENABLED + #endif // REPLICATION_EDITOR_PLUGIN_H diff --git a/editor/icons/MultiplayerSpawner.svg b/modules/replication/icons/MultiplayerSpawner.svg similarity index 100% rename from editor/icons/MultiplayerSpawner.svg rename to modules/replication/icons/MultiplayerSpawner.svg diff --git a/editor/icons/MultiplayerSynchronizer.svg b/modules/replication/icons/MultiplayerSynchronizer.svg similarity index 100% rename from editor/icons/MultiplayerSynchronizer.svg rename to modules/replication/icons/MultiplayerSynchronizer.svg diff --git a/modules/replication/multiplayer_spawner.cpp b/modules/replication/multiplayer_spawner.cpp new file mode 100644 index 000000000000..f487ed7e1eb3 --- /dev/null +++ b/modules/replication/multiplayer_spawner.cpp @@ -0,0 +1,404 @@ +/*************************************************************************/ +/* multiplayer_spawner.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_spawner.h" + +#include "core/io/marshalls.h" +#include "core/multiplayer/multiplayer_api.h" +#include "scene/main/window.h" +#include "scene/scene_string_names.h" + +#include "multiplayer_synchronizer.h" + +void MultiplayerSpawner::_bind_methods() { + // Must be bound for call_deferred and rpc_config to work. + ClassDB::bind_method(D_METHOD("_spawn_tracked_node"), &MultiplayerSpawner::_spawn_tracked_node); + ClassDB::bind_method(D_METHOD("_despawn_tracked_node"), &MultiplayerSpawner::_despawn_tracked_node); + ClassDB::bind_method(D_METHOD("_rpc_spawn"), &MultiplayerSpawner::_rpc_spawn); + ClassDB::bind_method(D_METHOD("_rpc_despawn"), &MultiplayerSpawner::_rpc_despawn); + ClassDB::bind_method(D_METHOD("_rpc_request_spawns"), &MultiplayerSpawner::_rpc_request_spawns); + + ClassDB::bind_method(D_METHOD("get_spawn_path"), &MultiplayerSpawner::get_spawn_path); + ClassDB::bind_method(D_METHOD("set_spawn_path", "path"), &MultiplayerSpawner::set_spawn_path); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "spawn_path", PROPERTY_HINT_NONE, ""), "set_spawn_path", "get_spawn_path"); + + ClassDB::bind_method(D_METHOD("get_spawn_limit"), &MultiplayerSpawner::get_spawn_limit); + ClassDB::bind_method(D_METHOD("set_spawn_limit", "limit"), &MultiplayerSpawner::set_spawn_limit); + ADD_PROPERTY(PropertyInfo(Variant::INT, "spawn_limit", PROPERTY_HINT_RANGE, "0,1024,1,or_greater"), "set_spawn_limit", "get_spawn_limit"); + + ClassDB::bind_method(D_METHOD("get_spawnable_scenes"), &MultiplayerSpawner::get_spawnable_scenes); + ClassDB::bind_method(D_METHOD("set_spawnable_scenes", "scenes"), &MultiplayerSpawner::set_spawnable_scenes); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "spawnable_scenes", PROPERTY_HINT_ARRAY_TYPE, vformat("%s/%s:%s", Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "PackedScene"), PROPERTY_USAGE_STORAGE), "set_spawnable_scenes", "get_spawnable_scenes"); + + ClassDB::bind_method(D_METHOD("spawn_custom", "custom_data"), &MultiplayerSpawner::spawn_custom, DEFVAL(Variant())); + + GDVIRTUAL_BIND(_spawn_custom, "custom_data"); + + ADD_SIGNAL(MethodInfo("spawned", PropertyInfo(Variant::INT, "scene_index"), PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node"))); + ADD_SIGNAL(MethodInfo("despawned", PropertyInfo(Variant::INT, "scene_index"), PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node"))); +} + +void MultiplayerSpawner::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_POST_ENTER_TREE: { + _update_spawn_node(); + + get_multiplayer()->connect(SNAME("peer_connected"), callable_mp(this, &MultiplayerSpawner::_on_peer_connected)); + //get_multiplayer()->connect(SNAME("peer_disconnected"), callable_mp(this, &MultiplayerSpawner::_on_peer_disconnected)); + + // When this node is added on a client, request spawns from the server. + if (get_multiplayer()->has_multiplayer_peer() && + get_multiplayer()->get_multiplayer_peer()->get_connection_status() == MultiplayerPeer::CONNECTION_CONNECTED && + !is_multiplayer_authority()) { + rpc_id(get_multiplayer_authority(), SNAME("_rpc_request_spawns")); + } + } break; + case NOTIFICATION_EXIT_TREE: { + get_multiplayer()->disconnect(SNAME("peer_connected"), callable_mp(this, &MultiplayerSpawner::_on_peer_connected)); + //get_multiplayer()->disconnect(SNAME("peer_disconnected"), callable_mp(this, &MultiplayerSpawner::_on_peer_disconnected)); + + // Despawn all spawned nodes. Duplicate the list since _despawn_tracked_node will remove elements. + for (Node *node : spawned_nodes.duplicate()) { + const SpawnInfo *info = tracked_nodes.getptr(node); + if (info == nullptr) { + continue; + } + + if (node->is_connected(SceneStringNames::get_singleton()->tree_entered, callable_mp(this, &MultiplayerSpawner::_spawn_tracked_node))) { + node->disconnect(SceneStringNames::get_singleton()->tree_entered, callable_mp(this, &MultiplayerSpawner::_spawn_tracked_node)); + } + node->disconnect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &MultiplayerSpawner::_despawn_tracked_node)); + + _despawn_tracked_node(node); + } + } break; + } +} + +// Editor-only array properties. +#ifdef TOOLS_ENABLED + +bool MultiplayerSpawner::_set(const StringName &p_name, const Variant &p_value) { + if (p_name == "_spawnable_scene_count") { + spawnable_scenes.resize(p_value); + notify_property_list_changed(); + return true; + } + + String name = p_name; + if (name.begins_with("scenes/")) { + int index = name.get_slicec('/', 1).to_int(); + ERR_FAIL_INDEX_V(index, spawnable_scenes.size(), false); + spawnable_scenes.set(index, p_value); + return true; + } + + return false; +} + +bool MultiplayerSpawner::_get(const StringName &p_name, Variant &r_ret) const { + if (p_name == "_spawnable_scene_count") { + r_ret = spawnable_scenes.size(); + return true; + } + + String name = p_name; + if (name.begins_with("scenes/")) { + int index = name.get_slicec('/', 1).to_int(); + ERR_FAIL_INDEX_V(index, spawnable_scenes.size(), false); + r_ret = spawnable_scenes.get(index); + return true; + } + + return false; +} + +void MultiplayerSpawner::_get_property_list(List *p_list) const { + p_list->push_back(PropertyInfo(Variant::INT, "_spawnable_scene_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_ARRAY, "Scenes,scenes/")); + + for (int i = 0; i < spawnable_scenes.size(); i++) { + p_list->push_back(PropertyInfo(Variant::OBJECT, "scenes/" + itos(i), PROPERTY_HINT_RESOURCE_TYPE, "PackedScene", PROPERTY_USAGE_EDITOR)); + } +} + +#endif + +void MultiplayerSpawner::_update_spawn_node() { +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + return; + } +#endif + + if (spawn_node != nullptr) { + spawn_node->disconnect("child_entered_tree", callable_mp(this, &MultiplayerSpawner::_on_child_added)); + } + + spawn_node = is_inside_tree() ? get_node_or_null(spawn_path) : nullptr; + + if (spawn_node != nullptr) { + spawn_node->connect("child_entered_tree", callable_mp(this, &MultiplayerSpawner::_on_child_added)); + } +} + +void MultiplayerSpawner::_on_child_added(Node *p_node) { + if (!get_multiplayer()->has_multiplayer_peer() || + get_multiplayer()->get_multiplayer_peer()->get_connection_status() != MultiplayerPeer::CONNECTION_CONNECTED || + !is_multiplayer_authority()) { + return; + } + + for (int scene_index = 0; scene_index < spawnable_scenes.size(); ++scene_index) { + const Ref scene = spawnable_scenes[scene_index]; + if (scene.is_null()) { + continue; + } + + if (scene->get_path() == p_node->get_scene_file_path()) { + _track(p_node, scene_index, Variant()); + break; + } + } +} + +void MultiplayerSpawner::_create_spawn_payloads(Node *p_parent, Node *p_node, Array &r_payloads) const { + for (int i = 0; i < p_node->get_child_count(); ++i) { + Node *child = p_node->get_child(i); + + MultiplayerSynchronizer *sync = Object::cast_to(child); + // Only synchronize if synchronizer is associated with this spawner; prevents syncs of nested synchronizers. + if (sync != nullptr && sync->root_node == p_parent) { + Array payload; + sync->_create_payload(MultiplayerSynchronizer::READY, payload); + + // Always add, even if an empty payload. + r_payloads.push_back(payload); + } + + _create_spawn_payloads(p_parent, child, r_payloads); + } +} + +void MultiplayerSpawner::_apply_spawn_payloads(Node *p_parent, Node *p_node, const Array &p_payloads, int &p_index) const { + for (int i = 0; i < p_node->get_child_count(); ++i) { + Node *child = p_node->get_child(i); + + MultiplayerSynchronizer *sync = Object::cast_to(child); + if (sync != nullptr) { + // Do not re-request sync when synchronizer is ready. + sync->spawn_synced = true; + + const Array payload = p_payloads[p_index++]; + sync->connect(SceneStringNames::get_singleton()->ready, callable_mp(sync, &MultiplayerSynchronizer::_apply_payload), varray(payload), CONNECT_ONESHOT); + } + + _apply_spawn_payloads(p_parent, child, p_payloads, p_index); + } +} + +void MultiplayerSpawner::_track(Node *p_node, const uint32_t p_scene_index, const Variant &p_custom_data) { + ERR_FAIL_COND_MSG(tracked_nodes.has(p_node), "Node is already being tracked. If using a custom spawn, do not include the scene in the configuration."); + + tracked_nodes[p_node] = SpawnInfo(p_scene_index, p_custom_data); + + if (p_node->is_inside_tree()) { + call_deferred(SNAME("_spawn_tracked_node"), p_node); + } else { + // Connect tree_entered deferred. This is similar to ready, except has the expected execution order (depth-first). + p_node->connect(SceneStringNames::get_singleton()->tree_entered, callable_mp(this, &MultiplayerSpawner::_spawn_tracked_node), varray(p_node), CONNECT_DEFERRED | CONNECT_ONESHOT); + } + p_node->connect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &MultiplayerSpawner::_despawn_tracked_node), varray(p_node), CONNECT_ONESHOT); +} + +void MultiplayerSpawner::_spawn_tracked_node(Node *p_node) { + const SpawnInfo *info = tracked_nodes.getptr(p_node); + ERR_FAIL_NULL(info); + + spawned_nodes.push_back(p_node); + + Array payloads; + _create_spawn_payloads(p_node, p_node, payloads); + + rpc(SNAME("_rpc_spawn"), p_node->get_name(), info->scene_index, info->custom_data, payloads); + + if (info->scene_index != CUSTOM_SCENE_INDEX) { + emit_signal(SNAME("spawned"), info->scene_index, p_node); + } +} + +void MultiplayerSpawner::_despawn_tracked_node(Node *p_node) { + const SpawnInfo *info = tracked_nodes.getptr(p_node); + ERR_FAIL_NULL(info); + + bool erased = tracked_nodes.erase(p_node); + ERR_FAIL_COND(!erased); + + const int index = spawned_nodes.find(p_node); + ERR_FAIL_COND(index == -1); + + spawned_nodes.remove_at(index); + + rpc(SNAME("_rpc_despawn"), index); + + if (info->scene_index != CUSTOM_SCENE_INDEX) { + emit_signal(SNAME("despawned"), info->scene_index, p_node); + } +} + +void MultiplayerSpawner::_on_peer_connected(const int p_peer) { + if (p_peer == get_multiplayer_authority()) { + rpc_id(get_multiplayer_authority(), SNAME("_rpc_request_spawns")); + } +} + +// Not needed currently. +/*void MultiplayerSpawner::_on_peer_disconnected(const int p_peer) { +}*/ + +void MultiplayerSpawner::_rpc_spawn(const String &p_name, const uint32_t p_scene_index, const Variant &p_custom_data, const Array &p_payloads) { + ERR_FAIL_NULL(spawn_node); + + // If the node already exists, do not try to spawn it again. + // This does not throw an error because the server may send a duplicate spawn if we call the request spawns rpc. + // It is important to listen to the server's first spawn rpc, since the server may immediately send synchronizer requests. + if (spawn_node->has_node(p_name)) { + return; + } + + ERR_FAIL_COND_MSG(p_name.validate_node_name() != p_name, vformat("Invalid node name received: '%s'. Make sure to add nodes via 'add_child(node, true)' remotely.", p_name)); + + Node *node; + if (p_scene_index != CUSTOM_SCENE_INDEX) { + node = instantiate_scene(p_scene_index); + } else { + node = instantiate_custom(p_custom_data); + } + + int index = 0; + _apply_spawn_payloads(node, node, p_payloads, index); + ERR_FAIL_COND_MSG(index != p_payloads.size(), "Spawn synchronization failed. The server and client have different configurations!"); + + node->set_name(p_name); + spawn_node->add_child(node); + + spawned_nodes.push_back(node); +} + +void MultiplayerSpawner::_rpc_despawn(const int p_index) { + ERR_FAIL_NULL(spawn_node); + ERR_FAIL_INDEX(p_index, spawned_nodes.size()); + + Node *node = spawned_nodes[p_index]; + ERR_FAIL_NULL(node); + + spawned_nodes.remove_at(p_index); + + spawn_node->remove_child(node); + node->queue_delete(); +} + +void MultiplayerSpawner::_rpc_request_spawns() { + ERR_FAIL_NULL(spawn_node); + + const int peer = get_multiplayer()->get_remote_sender_id(); + + for (Node *node : spawned_nodes) { + const SpawnInfo *info = tracked_nodes.getptr(node); + if (info == nullptr) { + continue; + } + + Array payloads; + _create_spawn_payloads(node, node, payloads); + + rpc_id(peer, SNAME("_rpc_spawn"), node->get_name(), info->scene_index, info->custom_data, payloads); + } +} + +void MultiplayerSpawner::set_spawn_path(const NodePath &p_path) { + spawn_path = p_path; + _update_spawn_node(); +} + +NodePath MultiplayerSpawner::get_spawn_path() const { + return spawn_path; +} + +void MultiplayerSpawner::set_spawn_limit(uint32_t p_limit) { + spawn_limit = p_limit; +} + +uint32_t MultiplayerSpawner::get_spawn_limit() const { + return spawn_limit; +} + +void MultiplayerSpawner::set_spawnable_scenes(const Array &p_scenes) { + spawnable_scenes = p_scenes; +} + +Array MultiplayerSpawner::get_spawnable_scenes() const { + return spawnable_scenes; +} + +Node *MultiplayerSpawner::spawn_custom(const Variant &p_custom_data) { + ERR_FAIL_COND_V(!is_inside_tree() || !get_multiplayer()->has_multiplayer_peer() || !is_multiplayer_authority(), nullptr); + ERR_FAIL_COND_V_MSG(spawn_limit > 0 && spawn_limit <= tracked_nodes.size(), nullptr, "Spawn limit reached!"); + ERR_FAIL_NULL_V_MSG(spawn_node, nullptr, "Cannot find spawn node."); + + Node *node = instantiate_custom(p_custom_data); + ERR_FAIL_NULL_V(node, nullptr); + + _track(node, CUSTOM_SCENE_INDEX, p_custom_data); + spawn_node->add_child(node, true); + return node; +} + +Node *MultiplayerSpawner::instantiate_scene(const uint32_t p_scene_index) { + ERR_FAIL_COND_V_MSG(spawn_limit > 0 && spawn_limit <= tracked_nodes.size(), nullptr, "Spawn limit reached!"); + ERR_FAIL_UNSIGNED_INDEX_V(p_scene_index, (uint32_t)spawnable_scenes.size(), nullptr); + const Ref scene = spawnable_scenes[p_scene_index]; + ERR_FAIL_COND_V(scene.is_null(), nullptr); + return scene->instantiate(); +} + +Node *MultiplayerSpawner::instantiate_custom(const Variant &p_custom_data) { + ERR_FAIL_COND_V_MSG(spawn_limit > 0 && spawn_limit <= tracked_nodes.size(), nullptr, "Spawn limit reached!"); + ERR_FAIL_COND_V_MSG(!GDVIRTUAL_IS_OVERRIDDEN(_spawn_custom), nullptr, "Custom spawn requires the '_spawn_custom' virtual method to be implemented via script."); + Node *node = nullptr; + GDVIRTUAL_CALL(_spawn_custom, p_custom_data, node); + ERR_FAIL_NULL_V_MSG(node, nullptr, "The '_spawn_custom' implementation must return a valid Node."); + return node; +} + +MultiplayerSpawner::MultiplayerSpawner() { + rpc_config(SNAME("_rpc_spawn"), RPCMode::RPC_MODE_AUTHORITY); + rpc_config(SNAME("_rpc_despawn"), RPCMode::RPC_MODE_AUTHORITY); + rpc_config(SNAME("_rpc_request_spawns"), RPCMode::RPC_MODE_ANY_PEER); +} diff --git a/scene/multiplayer/multiplayer_spawner.h b/modules/replication/multiplayer_spawner.h similarity index 65% rename from scene/multiplayer/multiplayer_spawner.h rename to modules/replication/multiplayer_spawner.h index e8abe702a0b1..6202427fd185 100644 --- a/scene/multiplayer/multiplayer_spawner.h +++ b/modules/replication/multiplayer_spawner.h @@ -36,85 +36,83 @@ #include "core/templates/local_vector.h" #include "core/variant/typed_array.h" #include "scene/resources/packed_scene.h" -#include "scene/resources/scene_replication_config.h" class MultiplayerSpawner : public Node { GDCLASS(MultiplayerSpawner, Node); public: enum { - INVALID_ID = 0xFF, + CUSTOM_SCENE_INDEX = 0xFFFFFFFF, }; private: - struct SpawnableScene { - String path; - Ref cache; - }; - - LocalVector spawnable_scenes; + struct SpawnInfo { + uint32_t scene_index; + Variant custom_data; - HashSet spawnable_ids; - NodePath spawn_path; + SpawnInfo(uint32_t p_scene_index, Variant p_custom_data) : + scene_index(p_scene_index), custom_data(p_custom_data) {} - struct SpawnInfo { - Variant args; - int id = INVALID_ID; - SpawnInfo(Variant p_args, int p_id) { - id = p_id; - args = p_args; - } + // Empty constructor for HashMap internally. SpawnInfo() {} }; - ObjectID spawn_node; - HashMap tracked_nodes; - bool auto_spawn = false; + NodePath spawn_path = NodePath(".."); uint32_t spawn_limit = 0; + Array spawnable_scenes; - void _update_spawn_node(); - void _track(Node *p_node, const Variant &p_argument, int p_scene_id = INVALID_ID); - void _node_added(Node *p_node); - void _node_exit(ObjectID p_id); - void _node_ready(ObjectID p_id); - - Vector _get_spawnable_scenes() const; - void _set_spawnable_scenes(const Vector &p_scenes); + Node *spawn_node = nullptr; + HashMap tracked_nodes; + Vector spawned_nodes; protected: static void _bind_methods(); void _notification(int p_what); +// Editor-only array properties. #ifdef TOOLS_ENABLED bool _set(const StringName &p_name, const Variant &p_value); bool _get(const StringName &p_name, Variant &r_ret) const; void _get_property_list(List *p_list) const; #endif -public: - Node *get_spawn_node() const { return spawn_node.is_valid() ? Object::cast_to(ObjectDB::get_instance(spawn_node)) : nullptr; } - void add_spawnable_scene(const String &p_path); - int get_spawnable_scene_count() const; - String get_spawnable_scene(int p_idx) const; - void clear_spawnable_scenes(); + void _update_spawn_node(); + void _on_child_added(Node *p_node); + + void _create_spawn_payloads(Node *p_parent, Node *p_node, Array &r_payloads) const; + void _apply_spawn_payloads(Node *p_parent, Node *p_node, const Array &p_payloads, int &p_index) const; + void _track(Node *p_node, const uint32_t p_scene_index, const Variant &p_custom_data); + void _spawn_tracked_node(Node *p_node); + void _despawn_tracked_node(Node *p_node); + + void _send_spawns(const int p_peer); + + void _on_peer_connected(const int p_peer); + //void _on_peer_disconnected(const int p_peer); + + void _rpc_spawn(const String &p_name, const uint32_t p_scene_index, const Variant &p_custom_data, const Array &p_payloads); + void _rpc_despawn(const int p_index); + void _rpc_request_spawns(); + +public: NodePath get_spawn_path() const; void set_spawn_path(const NodePath &p_path); - uint32_t get_spawn_limit() const { return spawn_limit; } - void set_spawn_limit(uint32_t p_limit) { spawn_limit = p_limit; } - bool is_auto_spawning() const; - void set_auto_spawning(bool p_enabled); - - const Variant get_spawn_argument(const ObjectID &p_id) const; - int find_spawnable_scene_index_from_object(const ObjectID &p_id) const; - int find_spawnable_scene_index_from_path(const String &p_path) const; - Node *spawn(const Variant &p_data = Variant()); + + void set_spawn_limit(uint32_t p_limit); + uint32_t get_spawn_limit() const; + + Array get_spawnable_scenes() const; + void set_spawnable_scenes(const Array &p_scenes); + + Node *spawn_custom(const Variant &p_data = Variant()); + + Node *instantiate_scene(const uint32_t p_scene_index); Node *instantiate_custom(const Variant &p_data); - Node *instantiate_scene(int p_idx); - GDVIRTUAL1R(Object *, _spawn_custom, const Variant &); + GDVIRTUAL1R(Node *, _spawn_custom, const Variant &); - MultiplayerSpawner() {} + MultiplayerSpawner(); }; #endif // MULTIPLAYER_SPAWNER_H diff --git a/modules/replication/multiplayer_synchronizer.cpp b/modules/replication/multiplayer_synchronizer.cpp new file mode 100644 index 000000000000..bb76bf87ad5e --- /dev/null +++ b/modules/replication/multiplayer_synchronizer.cpp @@ -0,0 +1,279 @@ +/*************************************************************************/ +/* multiplayer_synchronizer.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_synchronizer.h" + +#include "core/config/engine.h" +#include "core/multiplayer/multiplayer_api.h" +#include "scene/scene_string_names.h" + +void MultiplayerSynchronizer::_bind_methods() { + // Must be bound for rpc_config to work. + ClassDB::bind_method(D_METHOD("_rpc_synchronize_reliable", "payload"), &MultiplayerSynchronizer::_rpc_synchronize_reliable); + ClassDB::bind_method(D_METHOD("_rpc_synchronize", "payload"), &MultiplayerSynchronizer::_rpc_synchronize); + ClassDB::bind_method(D_METHOD("_rpc_request_synchronize"), &MultiplayerSynchronizer::_rpc_request_synchronize); + + ClassDB::bind_method(D_METHOD("set_root_path", "path"), &MultiplayerSynchronizer::set_root_path); + ClassDB::bind_method(D_METHOD("get_root_path"), &MultiplayerSynchronizer::get_root_path); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "root_path"), "set_root_path", "get_root_path"); + + ClassDB::bind_method(D_METHOD("set_replication_interval", "interval"), &MultiplayerSynchronizer::set_replication_interval); + ClassDB::bind_method(D_METHOD("get_replication_interval"), &MultiplayerSynchronizer::get_replication_interval); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "replication_interval", PROPERTY_HINT_RANGE, "0,5,0.001,suffix:s"), "set_replication_interval", "get_replication_interval"); + + ClassDB::bind_method(D_METHOD("set_replication_config", "config"), &MultiplayerSynchronizer::set_replication_config); + ClassDB::bind_method(D_METHOD("get_replication_config"), &MultiplayerSynchronizer::get_replication_config); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "replication_config", PROPERTY_HINT_RESOURCE_TYPE, "SceneReplicationConfig"), "set_replication_config", "get_replication_config"); +} + +void MultiplayerSynchronizer::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_POST_ENTER_TREE: { + set_process_internal(true); + + _update_root_node(); + + // Deferred signal to ensure the spawn was handled first. + get_multiplayer()->connect(SNAME("peer_connected"), callable_mp(this, &MultiplayerSynchronizer::_on_peer_connected), varray(), CONNECT_DEFERRED); + //get_multiplayer()->connect(SNAME("peer_disconnected"), callable_mp(this, &MultiplayerSynchronizer::_on_peer_disconnected)); + + // When this node is added on a client, request synchronize from the server. + if (get_multiplayer()->has_multiplayer_peer() && + get_multiplayer()->get_multiplayer_peer()->get_connection_status() == MultiplayerPeer::CONNECTION_CONNECTED && + !is_multiplayer_authority()) { + rpc_id(get_multiplayer_authority(), SNAME("_rpc_request_synchronize")); + } + } break; + case NOTIFICATION_EXIT_TREE: { + get_multiplayer()->disconnect(SNAME("peer_connected"), callable_mp(this, &MultiplayerSynchronizer::_on_peer_connected)); + //get_multiplayer()->disconnect(SNAME("peer_disconnected"), callable_mp(this, &MultiplayerSynchronizer::_on_peer_disconnected)); + } break; + case NOTIFICATION_INTERNAL_PROCESS: { + _synchronize(0, SYNC); + } break; + } +} + +bool MultiplayerSynchronizer::_get_path_target(const NodePath &p_path, Node *&r_node, StringName &r_prop) { + if (p_path.get_name_count() != 0) { + r_node = root_node->get_node(String(p_path.get_concatenated_names())); + } else { + r_node = root_node; + } + ERR_FAIL_NULL_V(r_node, false); + + r_prop = p_path.get_concatenated_subnames(); + return true; +} + +void MultiplayerSynchronizer::_update_root_node() { +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + return; + } +#endif + + root_node = is_inside_tree() ? get_node_or_null(root_path) : nullptr; +} + +bool MultiplayerSynchronizer::_create_payload(const SynchronizeAction p_action, Array &r_payload) { + bool has_values = false; + r_payload.push_back(p_action); + + List properties; + switch (p_action) { + case READY: { + properties = replication_config->get_spawn_properties(); + } break; + case SYNC: { + properties = replication_config->get_sync_properties(); + } break; + } + + for (const NodePath &path : properties) { + Node *node; + StringName prop; + const bool found_node = _get_path_target(path, node, prop); + ERR_CONTINUE(!found_node); + + Variant value = node->get(prop); + + r_payload.push_back(value); + has_values = true; + } + + return has_values; +} + +void MultiplayerSynchronizer::_apply_payload(const Array &p_payload) { + int index = 0; + + const int action = p_payload[index++]; + + List properties; + switch (action) { + case READY: { + properties = replication_config->get_spawn_properties(); + } break; + case SYNC: { + properties = replication_config->get_sync_properties(); + } break; + default: { + ERR_FAIL_MSG("Invalid action type: " + itos(action)); + } break; + } + + for (const NodePath &path : properties) { + Node *node; + StringName prop; + const bool found_node = _get_path_target(path, node, prop); + ERR_CONTINUE(!found_node); + + Variant value = p_payload[index++]; + node->set(prop, value); + } + + ERR_FAIL_COND_MSG(index != p_payload.size(), "Synchronization failed. The server and client have different configurations!"); +} + +void MultiplayerSynchronizer::_on_peer_connected(const int p_peer) { + if (p_peer == get_multiplayer_authority()) { + rpc_id(get_multiplayer_authority(), SNAME("_rpc_request_synchronize")); + } +} + +// Not needed currently. +/*void MultiplayerSpawner::_on_peer_disconnected(const int p_peer) { +}*/ + +void MultiplayerSynchronizer::_rpc_synchronize_reliable(const Array &p_payload) { + ERR_FAIL_COND(p_payload.is_empty()); + + _apply_payload(p_payload); +} + +void MultiplayerSynchronizer::_rpc_synchronize(const Array &p_payload) { + ERR_FAIL_COND(p_payload.is_empty()); + + _apply_payload(p_payload); +} + +void MultiplayerSynchronizer::_rpc_request_synchronize() { + const int peer = get_multiplayer()->get_remote_sender_id(); + ERR_FAIL_COND(peer == 0); + + const bool erased = watching_peers.erase(peer); + if (erased) { +#ifdef DEBUG_ENABLED + WARN_PRINT("Client requested another ready synchronize."); +#endif + } + + watching_peers.push_back(peer); + _synchronize(peer, READY); +} + +void MultiplayerSynchronizer::_synchronize(const int p_peer, const SynchronizeAction p_action) { +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + return; + } +#endif + + // Was already synchronized directly by a spawner. + if (p_action == READY && spawn_synced) { + return; + } + + if (!get_multiplayer()->has_multiplayer_peer() || + get_multiplayer()->get_multiplayer_peer()->get_connection_status() != MultiplayerPeer::CONNECTION_CONNECTED || + !is_multiplayer_authority()) { + return; + } + + ERR_FAIL_NULL(replication_config); + + Array payload; + bool payload_has_values = _create_payload(p_action, payload); + + if (payload_has_values) { + switch (p_action) { + case READY: { + if (p_peer != 0) { + rpc_id(p_peer, SNAME("_rpc_synchronize_reliable"), payload); + } else { + for (const int peer : watching_peers) { + rpc_id(peer, SNAME("_rpc_synchronize_reliable"), payload); + } + } + } break; + case SYNC: { + if (p_peer != 0) { + rpc_id(p_peer, SNAME("_rpc_synchronize"), payload); + } else { + for (const int peer : watching_peers) { + rpc_id(peer, SNAME("_rpc_synchronize"), payload); + } + } + } break; + } + } +} + +void MultiplayerSynchronizer::set_root_path(const NodePath &p_path) { + root_path = p_path; +} + +NodePath MultiplayerSynchronizer::get_root_path() const { + return root_path; +} + +void MultiplayerSynchronizer::set_replication_interval(double p_interval) { + ERR_FAIL_COND_MSG(p_interval < 0, "Replication interval must be greater or equal to 0."); + replication_interval = p_interval; +} + +double MultiplayerSynchronizer::get_replication_interval() const { + return replication_interval; +} + +void MultiplayerSynchronizer::set_replication_config(Ref p_config) { + replication_config = p_config; +} + +Ref MultiplayerSynchronizer::get_replication_config() { + return replication_config; +} + +MultiplayerSynchronizer::MultiplayerSynchronizer() { + rpc_config(SNAME("_rpc_synchronize_reliable"), RPCMode::RPC_MODE_AUTHORITY); + rpc_config(SNAME("_rpc_synchronize"), RPCMode::RPC_MODE_AUTHORITY, false, Multiplayer::TRANSFER_MODE_UNRELIABLE_ORDERED); + rpc_config(SNAME("_rpc_request_synchronize"), RPCMode::RPC_MODE_ANY_PEER); +} diff --git a/scene/multiplayer/multiplayer_synchronizer.h b/modules/replication/multiplayer_synchronizer.h similarity index 74% rename from scene/multiplayer/multiplayer_synchronizer.h rename to modules/replication/multiplayer_synchronizer.h index f61ef459dae9..145b1ddf2c15 100644 --- a/scene/multiplayer/multiplayer_synchronizer.h +++ b/modules/replication/multiplayer_synchronizer.h @@ -31,42 +31,66 @@ #ifndef MULTIPLAYER_SYNCHRONIZER_H #define MULTIPLAYER_SYNCHRONIZER_H +#include "core/templates/local_vector.h" +#include "core/variant/typed_array.h" #include "scene/main/node.h" +#include "scene/resources/packed_scene.h" -#include "scene/resources/scene_replication_config.h" +#include "scene_replication_config.h" + +class MultiplayerSpawner; class MultiplayerSynchronizer : public Node { GDCLASS(MultiplayerSynchronizer, Node); + friend class MultiplayerSpawner; + private: + NodePath root_path = NodePath(".."); + real_t replication_interval = 0.0; Ref replication_config; - NodePath root_path = NodePath(".."); // Start with parent, like with AnimationPlayer. - uint64_t interval_msec = 0; - static Object *_get_prop_target(Object *p_obj, const NodePath &p_prop); - void _start(); - void _stop(); + Node *root_node = nullptr; + bool spawn_synced = false; + + List watching_peers; protected: + enum SynchronizeAction { + READY, + SYNC, + }; + static void _bind_methods(); void _notification(int p_what); + bool _get_path_target(const NodePath &p_path, Node *&r_node, StringName &r_prop); + + void _update_root_node(); + + bool _create_payload(const SynchronizeAction p_action, Array &r_payload); + void _apply_payload(const Array &p_payload); + + void _on_peer_connected(const int p_peer); + //void _on_peer_disconnected(const int p_peer); + + void _rpc_synchronize_reliable(const Array &p_payload); + void _rpc_synchronize(const Array &p_payload); + void _rpc_request_synchronize(); + + void _synchronize(const int p_peer, const SynchronizeAction p_action); + public: - static Error get_state(const List &p_properties, Object *p_obj, Vector &r_variant, Vector &r_variant_ptrs); - static Error set_state(const List &p_properties, Object *p_obj, const Vector &p_state); + NodePath get_root_path() const; + void set_root_path(const NodePath &p_path); void set_replication_interval(double p_interval); double get_replication_interval() const; - uint64_t get_replication_interval_msec() const; void set_replication_config(Ref p_config); Ref get_replication_config(); - void set_root_path(const NodePath &p_path); - NodePath get_root_path() const; - virtual void set_multiplayer_authority(int p_peer_id, bool p_recursive = true) override; - - MultiplayerSynchronizer() {} + MultiplayerSynchronizer(); }; #endif // MULTIPLAYER_SYNCHRONIZER_H diff --git a/scene/multiplayer/scene_replication_interface.h b/modules/replication/register_types.cpp similarity index 51% rename from scene/multiplayer/scene_replication_interface.h rename to modules/replication/register_types.cpp index 60ac95c93c03..325e56dcdc88 100644 --- a/scene/multiplayer/scene_replication_interface.h +++ b/modules/replication/register_types.cpp @@ -1,5 +1,5 @@ /*************************************************************************/ -/* scene_replication_interface.h */ +/* register_types.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,57 +28,30 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef SCENE_TREE_REPLICATOR_INTERFACE_H -#define SCENE_TREE_REPLICATOR_INTERFACE_H +#include "register_types.h" +#include "core/object/class_db.h" -#include "core/multiplayer/multiplayer_api.h" +#include "editor/replication_editor_plugin.h" +#include "multiplayer_spawner.h" +#include "multiplayer_synchronizer.h" +#include "scene_replication_config.h" -#include "scene/multiplayer/scene_replication_state.h" - -class SceneReplicationInterface : public MultiplayerReplicationInterface { - GDCLASS(SceneReplicationInterface, MultiplayerReplicationInterface); - -private: - void _send_sync(int p_peer, uint64_t p_msec); - Error _send_spawn(Node *p_node, MultiplayerSpawner *p_spawner, int p_peer); - Error _send_despawn(Node *p_node, int p_peer); - Error _send_raw(const uint8_t *p_buffer, int p_size, int p_peer, bool p_reliable); - - void _free_remotes(int p_peer); - - Ref rep_state; - MultiplayerAPI *multiplayer = nullptr; - PackedByteArray packet_cache; - int sync_mtu = 1350; // Highly dependent on underlying protocol. - - // An hack to apply the initial state before ready. - ObjectID pending_spawn; - const uint8_t *pending_buffer = nullptr; - int pending_buffer_size = 0; - -protected: - static MultiplayerReplicationInterface *_create(MultiplayerAPI *p_multiplayer); - -public: - static void make_default(); - - virtual void on_reset() override; - virtual void on_peer_change(int p_id, bool p_connected) override; +void initialize_replication_module(ModuleInitializationLevel p_level) { + if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { + return; + } - virtual Error on_spawn(Object *p_obj, Variant p_config) override; - virtual Error on_despawn(Object *p_obj, Variant p_config) override; - virtual Error on_replication_start(Object *p_obj, Variant p_config) override; - virtual Error on_replication_stop(Object *p_obj, Variant p_config) override; - virtual void on_network_process() override; + GDREGISTER_CLASS(MultiplayerSpawner); + GDREGISTER_CLASS(MultiplayerSynchronizer); + GDREGISTER_CLASS(SceneReplicationConfig); - virtual Error on_spawn_receive(int p_from, const uint8_t *p_buffer, int p_buffer_len) override; - virtual Error on_despawn_receive(int p_from, const uint8_t *p_buffer, int p_buffer_len) override; - virtual Error on_sync_receive(int p_from, const uint8_t *p_buffer, int p_buffer_len) override; +#ifdef TOOLS_ENABLED + EditorPlugins::add_by_type(); +#endif +} - SceneReplicationInterface(MultiplayerAPI *p_multiplayer) { - rep_state.instantiate(); - multiplayer = p_multiplayer; +void uninitialize_replication_module(ModuleInitializationLevel p_level) { + if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { + return; } -}; - -#endif // SCENE_TREE_REPLICATOR_INTERFACE_H +} diff --git a/modules/replication/register_types.h b/modules/replication/register_types.h new file mode 100644 index 000000000000..daee3797b62e --- /dev/null +++ b/modules/replication/register_types.h @@ -0,0 +1,39 @@ +/*************************************************************************/ +/* register_types.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 REPLICATION_REGISTER_TYPES_H +#define REPLICATION_REGISTER_TYPES_H + +#include "modules/register_module_types.h" + +void initialize_replication_module(ModuleInitializationLevel p_level); +void uninitialize_replication_module(ModuleInitializationLevel p_level); + +#endif // REPLICATION_REGISTER_TYPES_H diff --git a/scene/resources/scene_replication_config.cpp b/modules/replication/scene_replication_config.cpp similarity index 100% rename from scene/resources/scene_replication_config.cpp rename to modules/replication/scene_replication_config.cpp diff --git a/scene/resources/scene_replication_config.h b/modules/replication/scene_replication_config.h similarity index 100% rename from scene/resources/scene_replication_config.h rename to modules/replication/scene_replication_config.h diff --git a/scene/multiplayer/multiplayer_spawner.cpp b/scene/multiplayer/multiplayer_spawner.cpp deleted file mode 100644 index ddd01d0a43f1..000000000000 --- a/scene/multiplayer/multiplayer_spawner.cpp +++ /dev/null @@ -1,315 +0,0 @@ -/*************************************************************************/ -/* multiplayer_spawner.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_spawner.h" - -#include "core/io/marshalls.h" -#include "core/multiplayer/multiplayer_api.h" -#include "scene/main/window.h" -#include "scene/scene_string_names.h" - -#ifdef TOOLS_ENABLED -/* This is editor only */ -bool MultiplayerSpawner::_set(const StringName &p_name, const Variant &p_value) { - if (p_name == "_spawnable_scene_count") { - spawnable_scenes.resize(p_value); - notify_property_list_changed(); - return true; - } else { - String ns = p_name; - if (ns.begins_with("scenes/")) { - uint32_t index = ns.get_slicec('/', 1).to_int(); - ERR_FAIL_UNSIGNED_INDEX_V(index, spawnable_scenes.size(), false); - spawnable_scenes[index].path = p_value; - return true; - } - } - return false; -} - -bool MultiplayerSpawner::_get(const StringName &p_name, Variant &r_ret) const { - if (p_name == "_spawnable_scene_count") { - r_ret = spawnable_scenes.size(); - return true; - } else { - String ns = p_name; - if (ns.begins_with("scenes/")) { - uint32_t index = ns.get_slicec('/', 1).to_int(); - ERR_FAIL_UNSIGNED_INDEX_V(index, spawnable_scenes.size(), false); - r_ret = spawnable_scenes[index].path; - return true; - } - } - return false; -} - -void MultiplayerSpawner::_get_property_list(List *p_list) const { - p_list->push_back(PropertyInfo(Variant::INT, "_spawnable_scene_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_ARRAY, "Scenes,scenes/")); - List exts; - ResourceLoader::get_recognized_extensions_for_type("PackedScene", &exts); - String ext_hint; - for (const String &E : exts) { - if (!ext_hint.is_empty()) { - ext_hint += ","; - } - ext_hint += "*." + E; - } - for (uint32_t i = 0; i < spawnable_scenes.size(); i++) { - p_list->push_back(PropertyInfo(Variant::STRING, "scenes/" + itos(i), PROPERTY_HINT_FILE, ext_hint, PROPERTY_USAGE_EDITOR)); - } -} -#endif -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 { - return spawnable_scenes[p_idx].path; -} -void MultiplayerSpawner::clear_spawnable_scenes() { - spawnable_scenes.clear(); -} - -Vector MultiplayerSpawner::_get_spawnable_scenes() const { - Vector ss; - ss.resize(spawnable_scenes.size()); - for (int i = 0; i < ss.size(); i++) { - ss.write[i] = spawnable_scenes[i].path; - } - return ss; -} - -void MultiplayerSpawner::_set_spawnable_scenes(const Vector &p_scenes) { - clear_spawnable_scenes(); - for (int i = 0; i < p_scenes.size(); i++) { - add_spawnable_scene(p_scenes[i]); - } -} - -void MultiplayerSpawner::_bind_methods() { - ClassDB::bind_method(D_METHOD("add_spawnable_scene", "path"), &MultiplayerSpawner::add_spawnable_scene); - ClassDB::bind_method(D_METHOD("get_spawnable_scene_count"), &MultiplayerSpawner::get_spawnable_scene_count); - ClassDB::bind_method(D_METHOD("get_spawnable_scene", "path"), &MultiplayerSpawner::get_spawnable_scene); - ClassDB::bind_method(D_METHOD("clear_spawnable_scenes"), &MultiplayerSpawner::clear_spawnable_scenes); - - ClassDB::bind_method(D_METHOD("_get_spawnable_scenes"), &MultiplayerSpawner::_get_spawnable_scenes); - ClassDB::bind_method(D_METHOD("_set_spawnable_scenes", "scenes"), &MultiplayerSpawner::_set_spawnable_scenes); - - ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "_spawnable_scenes", PROPERTY_HINT_NONE, "", (PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL)), "_set_spawnable_scenes", "_get_spawnable_scenes"); - - ClassDB::bind_method(D_METHOD("spawn", "data"), &MultiplayerSpawner::spawn, DEFVAL(Variant())); - - ClassDB::bind_method(D_METHOD("get_spawn_path"), &MultiplayerSpawner::get_spawn_path); - ClassDB::bind_method(D_METHOD("set_spawn_path", "path"), &MultiplayerSpawner::set_spawn_path); - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "spawn_path", PROPERTY_HINT_NONE, ""), "set_spawn_path", "get_spawn_path"); - - ClassDB::bind_method(D_METHOD("get_spawn_limit"), &MultiplayerSpawner::get_spawn_limit); - ClassDB::bind_method(D_METHOD("set_spawn_limit", "limit"), &MultiplayerSpawner::set_spawn_limit); - ADD_PROPERTY(PropertyInfo(Variant::INT, "spawn_limit", PROPERTY_HINT_RANGE, "0,1024,1,or_greater"), "set_spawn_limit", "get_spawn_limit"); - - ClassDB::bind_method(D_METHOD("set_auto_spawning", "enabled"), &MultiplayerSpawner::set_auto_spawning); - ClassDB::bind_method(D_METHOD("is_auto_spawning"), &MultiplayerSpawner::is_auto_spawning); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_spawn"), "set_auto_spawning", "is_auto_spawning"); - - GDVIRTUAL_BIND(_spawn_custom, "data"); - - ADD_SIGNAL(MethodInfo("despawned", PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node"))); - ADD_SIGNAL(MethodInfo("spawned", PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node"))); -} - -void MultiplayerSpawner::_update_spawn_node() { -#ifdef TOOLS_ENABLED - if (Engine::get_singleton()->is_editor_hint()) { - return; - } -#endif - if (spawn_node.is_valid()) { - Node *node = Object::cast_to(ObjectDB::get_instance(spawn_node)); - if (node && node->is_connected("child_entered_tree", callable_mp(this, &MultiplayerSpawner::_node_added))) { - node->disconnect("child_entered_tree", callable_mp(this, &MultiplayerSpawner::_node_added)); - } - } - Node *node = spawn_path.is_empty() && is_inside_tree() ? nullptr : get_node_or_null(spawn_path); - if (node) { - spawn_node = node->get_instance_id(); - if (auto_spawn) { - node->connect("child_entered_tree", callable_mp(this, &MultiplayerSpawner::_node_added)); - } - } else { - spawn_node = ObjectID(); - } -} - -void MultiplayerSpawner::_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_POST_ENTER_TREE: { - _update_spawn_node(); - } break; - - case NOTIFICATION_EXIT_TREE: { - _update_spawn_node(); - - for (const KeyValue &E : tracked_nodes) { - Node *node = Object::cast_to(ObjectDB::get_instance(E.key)); - ERR_CONTINUE(!node); - node->disconnect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &MultiplayerSpawner::_node_exit)); - // This is unlikely, but might still crash the engine. - if (node->is_connected(SceneStringNames::get_singleton()->ready, callable_mp(this, &MultiplayerSpawner::_node_ready))) { - node->disconnect(SceneStringNames::get_singleton()->ready, callable_mp(this, &MultiplayerSpawner::_node_ready)); - } - get_multiplayer()->despawn(node, this); - } - tracked_nodes.clear(); - } break; - } -} - -void MultiplayerSpawner::_node_added(Node *p_node) { - if (!get_multiplayer()->has_multiplayer_peer() || !is_multiplayer_authority()) { - return; - } - if (tracked_nodes.has(p_node->get_instance_id())) { - return; - } - const Node *parent = get_spawn_node(); - if (!parent || p_node->get_parent() != parent) { - return; - } - int id = find_spawnable_scene_index_from_path(p_node->get_scene_file_path()); - if (id == INVALID_ID) { - return; - } - const String name = p_node->get_name(); - ERR_FAIL_COND_MSG(name.validate_node_name() != name, vformat("Unable to auto-spawn node with reserved name: %s. Make sure to add your replicated scenes via 'add_child(node, true)' to produce valid names.", name)); - _track(p_node, Variant(), id); -} - -void MultiplayerSpawner::set_auto_spawning(bool p_enabled) { - auto_spawn = p_enabled; - _update_spawn_node(); -} - -bool MultiplayerSpawner::is_auto_spawning() const { - return auto_spawn; -} - -NodePath MultiplayerSpawner::get_spawn_path() const { - return spawn_path; -} - -void MultiplayerSpawner::set_spawn_path(const NodePath &p_path) { - spawn_path = p_path; - _update_spawn_node(); -} - -void MultiplayerSpawner::_track(Node *p_node, const Variant &p_argument, int p_scene_id) { - 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), varray(p_node->get_instance_id()), CONNECT_ONESHOT); - p_node->connect(SceneStringNames::get_singleton()->ready, callable_mp(this, &MultiplayerSpawner::_node_ready), varray(p_node->get_instance_id()), CONNECT_ONESHOT); - } -} - -void MultiplayerSpawner::_node_ready(ObjectID p_id) { - get_multiplayer()->spawn(ObjectDB::get_instance(p_id), this); -} - -void MultiplayerSpawner::_node_exit(ObjectID p_id) { - Node *node = Object::cast_to(ObjectDB::get_instance(p_id)); - ERR_FAIL_COND(!node); - if (tracked_nodes.has(p_id)) { - tracked_nodes.erase(p_id); - get_multiplayer()->despawn(node, this); - } -} - -int MultiplayerSpawner::find_spawnable_scene_index_from_path(const String &p_scene) const { - for (uint32_t i = 0; i < spawnable_scenes.size(); i++) { - if (spawnable_scenes[i].path == p_scene) { - return i; - } - } - return INVALID_ID; -} - -int MultiplayerSpawner::find_spawnable_scene_index_from_object(const ObjectID &p_id) const { - const SpawnInfo *info = tracked_nodes.getptr(p_id); - return info ? info->id : INVALID_ID; -} - -const Variant MultiplayerSpawner::get_spawn_argument(const ObjectID &p_id) const { - const SpawnInfo *info = tracked_nodes.getptr(p_id); - return info ? info->args : Variant(); -} - -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 scene = spawnable_scenes[p_id].cache; - ERR_FAIL_COND_V(scene.is_null(), nullptr); - return scene->instantiate(); -} - -Node *MultiplayerSpawner::instantiate_custom(const Variant &p_data) { - ERR_FAIL_COND_V_MSG(spawn_limit && spawn_limit <= tracked_nodes.size(), nullptr, "Spawn limit reached!"); - Object *obj = nullptr; - Node *node = nullptr; - if (GDVIRTUAL_CALL(_spawn_custom, p_data, obj)) { - node = Object::cast_to(obj); - } - return node; -} - -Node *MultiplayerSpawner::spawn(const Variant &p_data) { - ERR_FAIL_COND_V(!is_inside_tree() || !get_multiplayer()->has_multiplayer_peer() || !is_multiplayer_authority(), nullptr); - ERR_FAIL_COND_V_MSG(spawn_limit && spawn_limit <= tracked_nodes.size(), nullptr, "Spawn limit reached!"); - ERR_FAIL_COND_V_MSG(!GDVIRTUAL_IS_OVERRIDDEN(_spawn_custom), nullptr, "Custom spawn requires the '_spawn_custom' virtual method to be implemented via script."); - - Node *parent = get_spawn_node(); - ERR_FAIL_COND_V_MSG(!parent, nullptr, "Cannot find spawn node."); - - Node *node = instantiate_custom(p_data); - ERR_FAIL_COND_V_MSG(!node, nullptr, "The '_spawn_custom' implementation must return a valid Node."); - - _track(node, p_data); - parent->add_child(node, true); - return node; -} diff --git a/scene/multiplayer/multiplayer_synchronizer.cpp b/scene/multiplayer/multiplayer_synchronizer.cpp deleted file mode 100644 index 68f6e54fa8e9..000000000000 --- a/scene/multiplayer/multiplayer_synchronizer.cpp +++ /dev/null @@ -1,164 +0,0 @@ -/*************************************************************************/ -/* multiplayer_synchronizer.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_synchronizer.h" - -#include "core/config/engine.h" -#include "core/multiplayer/multiplayer_api.h" - -Object *MultiplayerSynchronizer::_get_prop_target(Object *p_obj, const NodePath &p_path) { - if (p_path.get_name_count() == 0) { - return p_obj; - } - Node *node = Object::cast_to(p_obj); - ERR_FAIL_COND_V_MSG(!node || !node->has_node(p_path), nullptr, vformat("Node '%s' not found.", p_path)); - return node->get_node(p_path); -} - -void MultiplayerSynchronizer::_stop() { - Node *node = is_inside_tree() ? get_node_or_null(root_path) : nullptr; - if (node) { - get_multiplayer()->replication_stop(node, this); - } -} - -void MultiplayerSynchronizer::_start() { - Node *node = is_inside_tree() ? get_node_or_null(root_path) : nullptr; - if (node) { - get_multiplayer()->replication_start(node, this); - } -} - -Error MultiplayerSynchronizer::get_state(const List &p_properties, Object *p_obj, Vector &r_variant, Vector &r_variant_ptrs) { - ERR_FAIL_COND_V(!p_obj, ERR_INVALID_PARAMETER); - r_variant.resize(p_properties.size()); - r_variant_ptrs.resize(r_variant.size()); - int i = 0; - for (const NodePath &prop : p_properties) { - bool valid = false; - const Object *obj = _get_prop_target(p_obj, prop); - ERR_FAIL_COND_V(!obj, FAILED); - r_variant.write[i] = obj->get(prop.get_concatenated_subnames(), &valid); - r_variant_ptrs.write[i] = &r_variant[i]; - ERR_FAIL_COND_V_MSG(!valid, ERR_INVALID_DATA, vformat("Property '%s' not found.", prop)); - i++; - } - return OK; -} - -Error MultiplayerSynchronizer::set_state(const List &p_properties, Object *p_obj, const Vector &p_state) { - ERR_FAIL_COND_V(!p_obj, ERR_INVALID_PARAMETER); - int i = 0; - for (const NodePath &prop : p_properties) { - Object *obj = _get_prop_target(p_obj, prop); - ERR_FAIL_COND_V(!obj, FAILED); - obj->set(prop.get_concatenated_subnames(), p_state[i]); - i += 1; - } - return OK; -} - -void MultiplayerSynchronizer::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_root_path", "path"), &MultiplayerSynchronizer::set_root_path); - ClassDB::bind_method(D_METHOD("get_root_path"), &MultiplayerSynchronizer::get_root_path); - - ClassDB::bind_method(D_METHOD("set_replication_interval", "milliseconds"), &MultiplayerSynchronizer::set_replication_interval); - ClassDB::bind_method(D_METHOD("get_replication_interval"), &MultiplayerSynchronizer::get_replication_interval); - - ClassDB::bind_method(D_METHOD("set_replication_config", "config"), &MultiplayerSynchronizer::set_replication_config); - ClassDB::bind_method(D_METHOD("get_replication_config"), &MultiplayerSynchronizer::get_replication_config); - - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "root_path"), "set_root_path", "get_root_path"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "replication_interval", PROPERTY_HINT_RANGE, "0,5,0.001,suffix:s"), "set_replication_interval", "get_replication_interval"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "replication_config", PROPERTY_HINT_RESOURCE_TYPE, "SceneReplicationConfig"), "set_replication_config", "get_replication_config"); -} - -void MultiplayerSynchronizer::_notification(int p_what) { -#ifdef TOOLS_ENABLED - if (Engine::get_singleton()->is_editor_hint()) { - return; - } -#endif - if (root_path.is_empty()) { - return; - } - - switch (p_what) { - case NOTIFICATION_ENTER_TREE: { - _start(); - } break; - - case NOTIFICATION_EXIT_TREE: { - _stop(); - } break; - } -} - -void MultiplayerSynchronizer::set_replication_interval(double p_interval) { - ERR_FAIL_COND_MSG(p_interval < 0, "Interval must be greater or equal to 0 (where 0 means default)"); - interval_msec = uint64_t(p_interval * 1000); -} - -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 p_config) { - replication_config = p_config; -} - -Ref MultiplayerSynchronizer::get_replication_config() { - return replication_config; -} - -void MultiplayerSynchronizer::set_root_path(const NodePath &p_path) { - _stop(); - root_path = p_path; - _start(); -} - -NodePath MultiplayerSynchronizer::get_root_path() const { - return root_path; -} - -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) { - Node::set_multiplayer_authority(p_peer_id, p_recursive); - return; - } - get_multiplayer()->replication_stop(node, this); - Node::set_multiplayer_authority(p_peer_id, p_recursive); - get_multiplayer()->replication_start(node, this); -} diff --git a/scene/multiplayer/scene_replication_interface.cpp b/scene/multiplayer/scene_replication_interface.cpp deleted file mode 100644 index e4715ceb8851..000000000000 --- a/scene/multiplayer/scene_replication_interface.cpp +++ /dev/null @@ -1,419 +0,0 @@ -/*************************************************************************/ -/* scene_replication_interface.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_interface.h" - -#include "core/io/marshalls.h" -#include "scene/main/node.h" -#include "scene/multiplayer/multiplayer_spawner.h" -#include "scene/multiplayer/multiplayer_synchronizer.h" - -#define MAKE_ROOM(m_amount) \ - if (packet_cache.size() < m_amount) \ - packet_cache.resize(m_amount); - -MultiplayerReplicationInterface *SceneReplicationInterface::_create(MultiplayerAPI *p_multiplayer) { - return memnew(SceneReplicationInterface(p_multiplayer)); -} - -void SceneReplicationInterface::make_default() { - MultiplayerAPI::create_default_replication_interface = _create; -} - -void SceneReplicationInterface::_free_remotes(int p_id) { - const HashMap remotes = rep_state->peer_get_remotes(p_id); - for (const KeyValue &E : remotes) { - Node *node = rep_state->get_node(E.value); - ERR_CONTINUE(!node); - node->queue_delete(); - } -} - -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()) { - _send_spawn(rep_state->get_node(oid), rep_state->get_spawner(oid), p_id); - } - for (const ObjectID &oid : rep_state->get_path_only_nodes()) { - Node *node = rep_state->get_node(oid); - MultiplayerSynchronizer *sync = rep_state->get_synchronizer(oid); - ERR_CONTINUE(!node || !sync); - if (sync->is_multiplayer_authority()) { - rep_state->peer_add_node(p_id, oid); - } - } - } else { - _free_remotes(p_id); - rep_state->on_peer_change(p_id, p_connected); - } -} - -void SceneReplicationInterface::on_reset() { - for (int pid : rep_state->get_peers()) { - _free_remotes(pid); - } - rep_state->reset(); -} - -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); - } -} - -Error SceneReplicationInterface::on_spawn(Object *p_obj, Variant p_config) { - Node *node = Object::cast_to(p_obj); - ERR_FAIL_COND_V(!node || p_config.get_type() != Variant::OBJECT, ERR_INVALID_PARAMETER); - MultiplayerSpawner *spawner = Object::cast_to(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); - return _send_spawn(node, spawner, 0); -} - -Error SceneReplicationInterface::on_despawn(Object *p_obj, Variant p_config) { - Node *node = Object::cast_to(p_obj); - ERR_FAIL_COND_V(!node || p_config.get_type() != Variant::OBJECT, ERR_INVALID_PARAMETER); - MultiplayerSpawner *spawner = Object::cast_to(p_config.get_validated_object()); - ERR_FAIL_COND_V(!p_obj || !spawner, ERR_INVALID_PARAMETER); - Error err = rep_state->config_del_spawn(node, spawner); - ERR_FAIL_COND_V(err != OK, err); - return _send_despawn(node, 0); -} - -Error SceneReplicationInterface::on_replication_start(Object *p_obj, Variant p_config) { - Node *node = Object::cast_to(p_obj); - ERR_FAIL_COND_V(!node || p_config.get_type() != Variant::OBJECT, ERR_INVALID_PARAMETER); - MultiplayerSynchronizer *sync = Object::cast_to(p_config.get_validated_object()); - ERR_FAIL_COND_V(!sync, ERR_INVALID_PARAMETER); - rep_state->config_add_sync(node, sync); - // 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 props = sync->get_replication_config()->get_spawn_properties(); - Vector 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); - } else if (multiplayer->has_multiplayer_peer() && sync->is_multiplayer_authority()) { - // Either it's a spawn or a static sync, in any case add it to the list of known nodes. - rep_state->peer_add_node(0, p_obj->get_instance_id()); - } - return OK; -} - -Error SceneReplicationInterface::on_replication_stop(Object *p_obj, Variant p_config) { - Node *node = Object::cast_to(p_obj); - ERR_FAIL_COND_V(!node || p_config.get_type() != Variant::OBJECT, ERR_INVALID_PARAMETER); - MultiplayerSynchronizer *sync = Object::cast_to(p_config.get_validated_object()); - ERR_FAIL_COND_V(!p_obj || !sync, ERR_INVALID_PARAMETER); - return rep_state->config_del_sync(node, sync); -} - -Error SceneReplicationInterface::_send_raw(const uint8_t *p_buffer, int p_size, int p_peer, bool p_reliable) { - ERR_FAIL_COND_V(!p_buffer || p_size < 1, ERR_INVALID_PARAMETER); - ERR_FAIL_COND_V(!multiplayer, ERR_UNCONFIGURED); - ERR_FAIL_COND_V(!multiplayer->has_multiplayer_peer(), ERR_UNCONFIGURED); - -#ifdef DEBUG_ENABLED - multiplayer->profile_bandwidth("out", p_size); -#endif - - Ref peer = multiplayer->get_multiplayer_peer(); - peer->set_target_peer(p_peer); - peer->set_transfer_channel(0); - peer->set_transfer_mode(p_reliable ? Multiplayer::TRANSFER_MODE_RELIABLE : Multiplayer::TRANSFER_MODE_UNRELIABLE); - return peer->put_packet(p_buffer, p_size); -} - -Error SceneReplicationInterface::_send_spawn(Node *p_node, MultiplayerSpawner *p_spawner, int p_peer) { - ERR_FAIL_COND_V(p_peer < 0, ERR_BUG); - ERR_FAIL_COND_V(!multiplayer, ERR_BUG); - ERR_FAIL_COND_V(!p_spawner || !p_node, ERR_BUG); - - const ObjectID oid = p_node->get_instance_id(); - uint32_t nid = rep_state->ensure_net_id(oid); - - // Prepare custom arg and scene_id - uint8_t scene_id = p_spawner->find_spawnable_scene_index_from_object(oid); - bool is_custom = scene_id == MultiplayerSpawner::INVALID_ID; - 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); - ERR_FAIL_COND_V(err, err); - } - - // Prepare spawn state. - int state_size = 0; - Vector state_vars; - Vector state_varp; - MultiplayerSynchronizer *synchronizer = rep_state->get_synchronizer(oid); - if (synchronizer && synchronizer->get_replication_config().is_valid()) { - const List props = synchronizer->get_replication_config()->get_spawn_properties(); - Error err = MultiplayerSynchronizer::get_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."); - } - - // Prepare simplified path. - NodePath rel_path = multiplayer->get_root_path().rel_path_to(p_spawner->get_path()); - - int path_id = 0; - multiplayer->send_object_cache(p_spawner, rel_path, p_peer, path_id); - - // Encode name and parent ID. - 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); - uint8_t *ptr = packet_cache.ptrw(); - ptr[0] = (uint8_t)MultiplayerAPI::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(nlen, &ptr[ofs]); - ofs += encode_cstring(cname.get_data(), &ptr[ofs]); - // Write args - if (is_custom) { - ofs += encode_uint32(spawn_arg_size, &ptr[ofs]); - Error err = MultiplayerAPI::encode_and_compress_variant(spawn_arg, &ptr[ofs], spawn_arg_size, false); - ERR_FAIL_COND_V(err, err); - ofs += spawn_arg_size; - } - // Write state. - if (state_size) { - Error err = MultiplayerAPI::encode_and_compress_variants(state_varp.ptrw(), state_varp.size(), &ptr[ofs], state_size); - ERR_FAIL_COND_V(err, err); - ofs += state_size; - } - Error err = _send_raw(ptr, ofs, p_peer, true); - ERR_FAIL_COND_V(err, err); - return rep_state->peer_add_node(p_peer, oid); -} - -Error SceneReplicationInterface::_send_despawn(Node *p_node, int p_peer) { - const ObjectID oid = p_node->get_instance_id(); - MAKE_ROOM(5); - uint8_t *ptr = packet_cache.ptrw(); - ptr[0] = (uint8_t)MultiplayerAPI::NETWORK_COMMAND_DESPAWN; - int ofs = 1; - uint32_t nid = rep_state->get_net_id(oid); - ofs += encode_uint32(nid, &ptr[ofs]); - Error err = _send_raw(ptr, ofs, p_peer, true); - ERR_FAIL_COND_V(err, err); - return rep_state->peer_del_node(p_peer, oid); -} - -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"); - int ofs = 1; // The spawn/despawn command. - uint8_t scene_id = p_buffer[ofs]; - ofs += 1; - uint32_t node_target = decode_uint32(&p_buffer[ofs]); - ofs += 4; - MultiplayerSpawner *spawner = Object::cast_to(multiplayer->get_cached_object(p_from, node_target)); - ERR_FAIL_COND_V(!spawner, ERR_DOES_NOT_EXIST); - ERR_FAIL_COND_V(p_from != spawner->get_multiplayer_authority(), ERR_UNAUTHORIZED); - - uint32_t net_id = 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 < 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. - const String name = String::utf8((const char *)&p_buffer[ofs], name_len); - ERR_FAIL_COND_V_MSG(name.validate_node_name() != name, ERR_INVALID_DATA, vformat("Invalid node name received: '%s'. Make sure to add nodes via 'add_child(node, true)' remotely.", name)); - ofs += name_len; - - // Check that we can spawn. - Node *parent = spawner->get_node_or_null(spawner->get_spawn_path()); - ERR_FAIL_COND_V(!parent, ERR_UNCONFIGURED); - ERR_FAIL_COND_V(parent->has_node(name), ERR_INVALID_DATA); - - Node *node = nullptr; - if (scene_id == MultiplayerSpawner::INVALID_ID) { - // Custom spawn. - ERR_FAIL_COND_V(p_buffer_len - ofs < 4, ERR_INVALID_DATA); - uint32_t arg_size = decode_uint32(&p_buffer[ofs]); - ofs += 4; - ERR_FAIL_COND_V(arg_size > uint32_t(p_buffer_len - ofs), ERR_INVALID_DATA); - Variant v; - Error err = MultiplayerAPI::decode_and_decompress_variant(v, &p_buffer[ofs], arg_size, nullptr, false); - ERR_FAIL_COND_V(err != OK, err); - ofs += arg_size; - node = spawner->instantiate_custom(v); - } else { - // Scene based spawn. - node = spawner->instantiate_scene(scene_id); - } - ERR_FAIL_COND_V(!node, ERR_UNAUTHORIZED); - node->set_name(name); - rep_state->peer_add_remote(p_from, net_id, node, spawner); - // 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; - } - parent->add_child(node); - pending_spawn = ObjectID(); - pending_buffer = nullptr; - pending_buffer_size = 0; - return OK; -} - -Error SceneReplicationInterface::on_despawn_receive(int p_from, const uint8_t *p_buffer, int p_buffer_len) { - ERR_FAIL_COND_V_MSG(p_buffer_len < 5, ERR_INVALID_DATA, "Invalid spawn packet received"); - 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); - ERR_FAIL_COND_V(!node, ERR_BUG); - if (node->get_parent() != nullptr) { - node->get_parent()->remove_child(node); - } - node->queue_delete(); - return OK; -} - -void SceneReplicationInterface::_send_sync(int p_peer, uint64_t p_msec) { - const HashSet &known = rep_state->get_known_nodes(p_peer); - if (known.is_empty()) { - return; - } - MAKE_ROOM(sync_mtu); - uint8_t *ptr = packet_cache.ptrw(); - ptr[0] = MultiplayerAPI::NETWORK_COMMAND_SYNC; - int ofs = 1; - ofs += encode_uint16(rep_state->peer_sync_next(p_peer), &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 : known) { - if (!rep_state->update_sync_time(oid, p_msec)) { - continue; // nothing to sync. - } - MultiplayerSynchronizer *sync = rep_state->get_synchronizer(oid); - ERR_CONTINUE(!sync); - Node *node = rep_state->get_node(oid); - ERR_CONTINUE(!node); - int size; - Vector vars; - Vector varp; - const List props = sync->get_replication_config()->get_sync_properties(); - Error err = MultiplayerSynchronizer::get_state(props, node, vars, varp); - ERR_CONTINUE_MSG(err != OK, "Unable to retrieve sync state."); - err = MultiplayerAPI::encode_and_compress_variants(varp.ptrw(), varp.size(), nullptr, size); - ERR_CONTINUE_MSG(err != OK, "Unable to encode sync state."); - // TODO Handle single state above MTU. - ERR_CONTINUE_MSG(size > 3 + 4 + 4 + sync_mtu, vformat("Node states bigger then MTU will not be sent (%d > %d): %s", size, sync_mtu, node->get_path())); - if (ofs + 4 + 4 + size > sync_mtu) { - // Send what we got, and reset write. - _send_raw(packet_cache.ptr(), ofs, p_peer, false); - ofs = 3; - } - if (size) { - uint32_t net_id = rep_state->get_net_id(oid); - if (net_id == 0 || (net_id & 0x80000000)) { - // First time path based ID. - NodePath rel_path = multiplayer->get_root_path().rel_path_to(sync->get_path()); - int path_id = 0; - multiplayer->send_object_cache(sync, rel_path, p_peer, path_id); - ERR_CONTINUE_MSG(net_id && net_id != (uint32_t(path_id) | 0x80000000), "This should never happen!"); - net_id = path_id; - rep_state->set_net_id(oid, net_id | 0x80000000); - } - ofs += encode_uint32(rep_state->get_net_id(oid), &ptr[ofs]); - ofs += encode_uint32(size, &ptr[ofs]); - MultiplayerAPI::encode_and_compress_variants(varp.ptrw(), varp.size(), &ptr[ofs], size); - ofs += size; - } - } - if (ofs > 3) { - // Got some left over to send. - _send_raw(packet_cache.ptr(), ofs, p_peer, false); - } -} - -Error SceneReplicationInterface::on_sync_receive(int p_from, const uint8_t *p_buffer, int p_buffer_len) { - 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; - if (net_id & 0x80000000) { - MultiplayerSynchronizer *sync = Object::cast_to(multiplayer->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); - } - if (!node) { - // Not received yet. - ofs += size; - continue; - } - const ObjectID oid = node->get_instance_id(); - if (!rep_state->update_last_node_sync(oid, 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 props = sync->get_replication_config()->get_sync_properties(); - Vector vars; - vars.resize(props.size()); - int consumed; - Error err = MultiplayerAPI::decode_and_decompress_variants(vars, &p_buffer[ofs], size, consumed); - ERR_FAIL_COND_V(err, err); - err = MultiplayerSynchronizer::set_state(props, node, vars); - ERR_FAIL_COND_V(err, err); - ofs += size; - } - return OK; -} diff --git a/scene/multiplayer/scene_replication_state.cpp b/scene/multiplayer/scene_replication_state.cpp deleted file mode 100644 index 937b30cb3638..000000000000 --- a/scene/multiplayer/scene_replication_state.cpp +++ /dev/null @@ -1,254 +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/multiplayer/scene_replication_state.h" - -#include "core/multiplayer/multiplayer_api.h" -#include "scene/multiplayer/multiplayer_spawner.h" -#include "scene/multiplayer/multiplayer_synchronizer.h" -#include "scene/scene_string_names.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(ObjectDB::get_instance(p_id)); - node->connect(SceneStringNames::get_singleton()->tree_exited, callable_mp(this, &SceneReplicationState::_untrack), varray(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 &E : peers_info) { - E.value.known_nodes.erase(p_id); - } - } - } -} - -const HashMap SceneReplicationState::peer_get_remotes(int p_peer) const { - return peers_info.has(p_peer) ? peers_info[p_peer].recv_nodes : HashMap(); -} - -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; -} - -const HashSet SceneReplicationState::get_known_nodes(int p_peer) { - ERR_FAIL_COND_V(!peers_info.has(p_peer), HashSet()); - return peers_info[p_peer].known_nodes; -} - -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 &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); - // The spawner may be notified after the synchronizer. - path_only_nodes.erase(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); - 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(); - // If it doesn't have a spawner, we might need to assign ID for this node using it's path. - if (tobj.spawner.is_null()) { - path_only_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(); - if (path_only_nodes.has(oid)) { - p_node->disconnect(SceneStringNames::get_singleton()->tree_exited, callable_mp(this, &SceneReplicationState::_untrack)); - _untrack(oid); - path_only_nodes.erase(oid); - } - return OK; -} - -Error SceneReplicationState::peer_add_node(int p_peer, const ObjectID &p_id) { - if (p_peer) { - ERR_FAIL_COND_V(!peers_info.has(p_peer), ERR_INVALID_PARAMETER); - peers_info[p_peer].known_nodes.insert(p_id); - } else { - for (KeyValue &E : peers_info) { - E.value.known_nodes.insert(p_id); - } - } - return OK; -} - -Error SceneReplicationState::peer_del_node(int p_peer, const ObjectID &p_id) { - if (p_peer) { - ERR_FAIL_COND_V(!peers_info.has(p_peer), ERR_INVALID_PARAMETER); - peers_info[p_peer].known_nodes.erase(p_id); - } else { - for (KeyValue &E : peers_info) { - E.value.known_nodes.erase(p_id); - } - } - return OK; -} - -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(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(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/scene/multiplayer/scene_replication_state.h b/scene/multiplayer/scene_replication_state.h deleted file mode 100644 index 60a6c5d70c64..000000000000 --- a/scene/multiplayer/scene_replication_state.h +++ /dev/null @@ -1,121 +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_REPLICATON_STATE_H -#define SCENE_REPLICATON_STATE_H - -#include "core/object/ref_counted.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(ObjectDB::get_instance(id)) : nullptr; } - MultiplayerSpawner *get_spawner() const { return spawner.is_valid() ? Object::cast_to(ObjectDB::get_instance(spawner)) : nullptr; } - MultiplayerSynchronizer *get_synchronizer() const { return synchronizer.is_valid() ? Object::cast_to(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 known_nodes; - HashMap recv_nodes; - uint16_t last_sent_sync = 0; - uint16_t last_recv_sync = 0; - }; - - HashSet known_peers; - uint32_t last_net_id = 0; - HashMap tracked_nodes; - HashMap peers_info; - HashSet spawned_nodes; - HashSet path_only_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 get_peers() const { return known_peers; } - const HashSet &get_spawned_nodes() const { return spawned_nodes; } - const HashSet &get_path_only_nodes() const { return path_only_nodes; } - - 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); - - const HashSet get_known_nodes(int p_peer); - 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_node(int p_peer, const ObjectID &p_id); - Error peer_del_node(int p_peer, const ObjectID &p_id); - - const HashMap 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_REPLICATON_STATE_H diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index 5c5b60df6360..708380e82048 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -135,10 +135,7 @@ #include "scene/main/timer.h" #include "scene/main/viewport.h" #include "scene/main/window.h" -#include "scene/multiplayer/multiplayer_spawner.h" -#include "scene/multiplayer/multiplayer_synchronizer.h" #include "scene/multiplayer/scene_cache_interface.h" -#include "scene/multiplayer/scene_replication_interface.h" #include "scene/multiplayer/scene_rpc_interface.h" #include "scene/resources/animation_library.h" #include "scene/resources/audio_stream_sample.h" @@ -312,8 +309,6 @@ void register_scene_types() { GDREGISTER_CLASS(SubViewport); GDREGISTER_CLASS(ViewportTexture); GDREGISTER_CLASS(HTTPRequest); - GDREGISTER_CLASS(MultiplayerSpawner); - GDREGISTER_CLASS(MultiplayerSynchronizer); GDREGISTER_CLASS(Timer); GDREGISTER_CLASS(CanvasLayer); GDREGISTER_CLASS(CanvasModulate); @@ -860,8 +855,6 @@ void register_scene_types() { GDREGISTER_CLASS(Curve); - GDREGISTER_CLASS(SceneReplicationConfig); - GDREGISTER_CLASS(TextLine); GDREGISTER_CLASS(TextParagraph); @@ -1102,7 +1095,6 @@ void register_scene_types() { } SceneDebugger::initialize(); - SceneReplicationInterface::make_default(); SceneRPCInterface::make_default(); SceneCacheInterface::make_default(); }