diff options
Diffstat (limited to 'scene/multiplayer')
-rw-r--r-- | scene/multiplayer/SCsub | 5 | ||||
-rw-r--r-- | scene/multiplayer/multiplayer_spawner.cpp | 227 | ||||
-rw-r--r-- | scene/multiplayer/multiplayer_spawner.h | 101 | ||||
-rw-r--r-- | scene/multiplayer/multiplayer_synchronizer.cpp | 158 | ||||
-rw-r--r-- | scene/multiplayer/multiplayer_synchronizer.h | 72 | ||||
-rw-r--r-- | scene/multiplayer/scene_cache_interface.cpp | 249 | ||||
-rw-r--r-- | scene/multiplayer/scene_cache_interface.h | 82 | ||||
-rw-r--r-- | scene/multiplayer/scene_replication_interface.cpp | 411 | ||||
-rw-r--r-- | scene/multiplayer/scene_replication_interface.h | 84 | ||||
-rw-r--r-- | scene/multiplayer/scene_replication_state.cpp | 258 | ||||
-rw-r--r-- | scene/multiplayer/scene_replication_state.h | 121 | ||||
-rw-r--r-- | scene/multiplayer/scene_rpc_interface.cpp | 512 | ||||
-rw-r--r-- | scene/multiplayer/scene_rpc_interface.h | 91 |
13 files changed, 2371 insertions, 0 deletions
diff --git a/scene/multiplayer/SCsub b/scene/multiplayer/SCsub new file mode 100644 index 0000000000..fc61250247 --- /dev/null +++ b/scene/multiplayer/SCsub @@ -0,0 +1,5 @@ +#!/usr/bin/env python + +Import("env") + +env.add_source_files(env.scene_sources, "*.cpp") diff --git a/scene/multiplayer/multiplayer_spawner.cpp b/scene/multiplayer/multiplayer_spawner.cpp new file mode 100644 index 0000000000..4f2a9d9e83 --- /dev/null +++ b/scene/multiplayer/multiplayer_spawner.cpp @@ -0,0 +1,227 @@ +/*************************************************************************/ +/* 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" + +void MultiplayerSpawner::_bind_methods() { + ClassDB::bind_method(D_METHOD("spawn", "data"), &MultiplayerSpawner::spawn, DEFVAL(Variant())); + + 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, "replication", PROPERTY_HINT_ARRAY_TYPE, vformat("%s/%s:%s", Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "PackedScene"), (PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE)), "set_spawnable_scenes", "get_spawnable_scenes"); + + 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<Node>(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) { + if (p_what == NOTIFICATION_POST_ENTER_TREE) { + _update_spawn_node(); + } else if (p_what == NOTIFICATION_EXIT_TREE) { + _update_spawn_node(); + const ObjectID *oid = nullptr; + while ((oid = tracked_nodes.next(oid))) { + Node *node = Object::cast_to<Node>(ObjectDB::get_instance(*oid)); + 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(); + } +} + +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 = get_scene_id(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; +} + +TypedArray<PackedScene> MultiplayerSpawner::get_spawnable_scenes() { + return spawnable_scenes; +} + +void MultiplayerSpawner::set_spawnable_scenes(TypedArray<PackedScene> p_scenes) { + spawnable_scenes = p_scenes; +} + +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<Node>(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::get_scene_id(const String &p_scene) const { + for (int i = 0; i < spawnable_scenes.size(); i++) { + Ref<PackedScene> ps = spawnable_scenes[i]; + ERR_CONTINUE(ps.is_null()); + if (ps->get_path() == p_scene) { + return i; + } + } + return INVALID_ID; +} + +int MultiplayerSpawner::get_spawn_id(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_INDEX_V(p_id, spawnable_scenes.size(), nullptr); + Ref<PackedScene> scene = spawnable_scenes[p_id]; + 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<Node>(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_spawner.h b/scene/multiplayer/multiplayer_spawner.h new file mode 100644 index 0000000000..63948e39a5 --- /dev/null +++ b/scene/multiplayer/multiplayer_spawner.h @@ -0,0 +1,101 @@ +/*************************************************************************/ +/* multiplayer_spawner.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef MULTIPLAYER_SPAWNER_H +#define MULTIPLAYER_SPAWNER_H + +#include "scene/main/node.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, + }; + +private: + TypedArray<PackedScene> spawnable_scenes; + Set<ResourceUID::ID> spawnable_ids; + NodePath spawn_path; + + struct SpawnInfo { + Variant args; + int id = INVALID_ID; + SpawnInfo(Variant p_args, int p_id) { + id = p_id; + args = p_args; + } + SpawnInfo() {} + }; + + ObjectID spawn_node; + HashMap<ObjectID, SpawnInfo> tracked_nodes; + bool auto_spawn = false; + uint32_t spawn_limit = 0; + + 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); + +protected: + static void _bind_methods(); + void _notification(int p_what); + +public: + Node *get_spawn_node() const { return spawn_node.is_valid() ? Object::cast_to<Node>(ObjectDB::get_instance(spawn_node)) : nullptr; } + TypedArray<PackedScene> get_spawnable_scenes(); + void set_spawnable_scenes(TypedArray<PackedScene> p_scenes); + 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 get_spawn_id(const ObjectID &p_id) const; + int get_scene_id(const String &p_path) const; + Node *spawn(const Variant &p_data = Variant()); + Node *instantiate_custom(const Variant &p_data); + Node *instantiate_scene(int p_idx); + + GDVIRTUAL1R(Object *, _spawn_custom, const Variant &); + + MultiplayerSpawner() {} +}; + +#endif // MULTIPLAYER_SPAWNER_H diff --git a/scene/multiplayer/multiplayer_synchronizer.cpp b/scene/multiplayer/multiplayer_synchronizer.cpp new file mode 100644 index 0000000000..fbe1b99cc9 --- /dev/null +++ b/scene/multiplayer/multiplayer_synchronizer.cpp @@ -0,0 +1,158 @@ +/*************************************************************************/ +/* 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<Node>(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<NodePath> &p_properties, Object *p_obj, Vector<Variant> &r_variant, Vector<const Variant *> &r_variant_ptrs) { + ERR_FAIL_COND_V(!p_obj, ERR_INVALID_PARAMETER); + r_variant.resize(p_properties.size()); + 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<NodePath> &p_properties, Object *p_obj, const Vector<Variant> &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); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "root_path"), "set_root_path", "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); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "replication_interval", PROPERTY_HINT_RANGE, "0,5,0.001"), "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, "resource", 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; + } + if (p_what == NOTIFICATION_ENTER_TREE) { + _start(); + } else if (p_what == NOTIFICATION_EXIT_TREE) { + _stop(); + } +} + +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<SceneReplicationConfig> p_config) { + replication_config = p_config; +} + +Ref<SceneReplicationConfig> 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/multiplayer_synchronizer.h b/scene/multiplayer/multiplayer_synchronizer.h new file mode 100644 index 0000000000..e856745379 --- /dev/null +++ b/scene/multiplayer/multiplayer_synchronizer.h @@ -0,0 +1,72 @@ +/*************************************************************************/ +/* multiplayer_synchronizer.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef MULTIPLAYER_SYNCHRONIZER_H +#define MULTIPLAYER_SYNCHRONIZER_H + +#include "scene/main/node.h" + +#include "scene/resources/scene_replication_config.h" + +class MultiplayerSynchronizer : public Node { + GDCLASS(MultiplayerSynchronizer, Node); + +private: + Ref<SceneReplicationConfig> replication_config; + NodePath root_path; + uint64_t interval_msec = 0; + + static Object *_get_prop_target(Object *p_obj, const NodePath &p_prop); + void _start(); + void _stop(); + +protected: + static void _bind_methods(); + void _notification(int p_what); + +public: + static Error get_state(const List<NodePath> &p_properties, Object *p_obj, Vector<Variant> &r_variant, Vector<const Variant *> &r_variant_ptrs); + static Error set_state(const List<NodePath> &p_properties, Object *p_obj, const Vector<Variant> &p_state); + + void set_replication_interval(double p_interval); + double get_replication_interval() const; + uint64_t get_replication_interval_msec() const; + + void set_replication_config(Ref<SceneReplicationConfig> p_config); + Ref<SceneReplicationConfig> get_replication_config(); + + 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() {} +}; + +#endif // MULTIPLAYER_SYNCHRONIZER_H diff --git a/scene/multiplayer/scene_cache_interface.cpp b/scene/multiplayer/scene_cache_interface.cpp new file mode 100644 index 0000000000..de4a94470a --- /dev/null +++ b/scene/multiplayer/scene_cache_interface.cpp @@ -0,0 +1,249 @@ +/*************************************************************************/ +/* scene_cache_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_cache_interface.h" + +#include "core/io/marshalls.h" +#include "scene/main/node.h" +#include "scene/main/window.h" + +MultiplayerCacheInterface *SceneCacheInterface::_create(MultiplayerAPI *p_multiplayer) { + return memnew(SceneCacheInterface(p_multiplayer)); +} + +void SceneCacheInterface::make_default() { + MultiplayerAPI::create_default_cache_interface = _create; +} + +void SceneCacheInterface::on_peer_change(int p_id, bool p_connected) { + if (p_connected) { + path_get_cache.insert(p_id, PathGetCache()); + } else { + // Cleanup get cache. + path_get_cache.erase(p_id); + // Cleanup sent cache. + // Some refactoring is needed to make this faster and do paths GC. + List<NodePath> keys; + path_send_cache.get_key_list(&keys); + for (const NodePath &E : keys) { + PathSentCache *psc = path_send_cache.getptr(E); + psc->confirmed_peers.erase(p_id); + } + } +} + +void SceneCacheInterface::process_simplify_path(int p_from, const uint8_t *p_packet, int p_packet_len) { + Node *root_node = SceneTree::get_singleton()->get_root()->get_node(multiplayer->get_root_path()); + ERR_FAIL_COND(!root_node); + ERR_FAIL_COND_MSG(p_packet_len < 38, "Invalid packet received. Size too small."); + int ofs = 1; + + String methods_md5; + methods_md5.parse_utf8((const char *)(p_packet + ofs), 32); + ofs += 33; + + int id = decode_uint32(&p_packet[ofs]); + ofs += 4; + + String paths; + paths.parse_utf8((const char *)(p_packet + ofs), p_packet_len - ofs); + + NodePath path = paths; + + if (!path_get_cache.has(p_from)) { + path_get_cache[p_from] = PathGetCache(); + } + + Node *node = root_node->get_node(path); + ERR_FAIL_COND(node == nullptr); + const bool valid_rpc_checksum = multiplayer->get_rpc_md5(node) == methods_md5; + if (valid_rpc_checksum == false) { + ERR_PRINT("The rpc node checksum failed. Make sure to have the same methods on both nodes. Node path: " + path); + } + + PathGetCache::NodeInfo ni; + ni.path = path; + + path_get_cache[p_from].nodes[id] = ni; + + // Encode path to send ack. + CharString pname = String(path).utf8(); + int len = encode_cstring(pname.get_data(), nullptr); + + Vector<uint8_t> packet; + + packet.resize(1 + 1 + len); + packet.write[0] = MultiplayerAPI::NETWORK_COMMAND_CONFIRM_PATH; + packet.write[1] = valid_rpc_checksum; + encode_cstring(pname.get_data(), &packet.write[2]); + + Ref<MultiplayerPeer> multiplayer_peer = multiplayer->get_multiplayer_peer(); + ERR_FAIL_COND(multiplayer_peer.is_null()); + + multiplayer_peer->set_transfer_channel(0); + multiplayer_peer->set_transfer_mode(Multiplayer::TRANSFER_MODE_RELIABLE); + multiplayer_peer->set_target_peer(p_from); + multiplayer_peer->put_packet(packet.ptr(), packet.size()); +} + +void SceneCacheInterface::process_confirm_path(int p_from, const uint8_t *p_packet, int p_packet_len) { + ERR_FAIL_COND_MSG(p_packet_len < 3, "Invalid packet received. Size too small."); + + const bool valid_rpc_checksum = p_packet[1]; + + String paths; + paths.parse_utf8((const char *)&p_packet[2], p_packet_len - 2); + + NodePath path = paths; + + if (valid_rpc_checksum == false) { + ERR_PRINT("The rpc node checksum failed. Make sure to have the same methods on both nodes. Node path: " + path); + } + + PathSentCache *psc = path_send_cache.getptr(path); + ERR_FAIL_COND_MSG(!psc, "Invalid packet received. Tries to confirm a path which was not found in cache."); + + Map<int, bool>::Element *E = psc->confirmed_peers.find(p_from); + ERR_FAIL_COND_MSG(!E, "Invalid packet received. Source peer was not found in cache for the given path."); + E->get() = true; +} + +bool SceneCacheInterface::_send_confirm_path(Node *p_node, NodePath p_path, PathSentCache *psc, int p_target) { + bool has_all_peers = true; + List<int> peers_to_add; // If one is missing, take note to add it. + + for (const Set<int>::Element *E = multiplayer->get_connected_peers().front(); E; E = E->next()) { + if (p_target < 0 && E->get() == -p_target) { + continue; // Continue, excluded. + } + + if (p_target > 0 && E->get() != p_target) { + continue; // Continue, not for this peer. + } + + Map<int, bool>::Element *F = psc->confirmed_peers.find(E->get()); + + if (!F || !F->get()) { + // Path was not cached, or was cached but is unconfirmed. + if (!F) { + // Not cached at all, take note. + peers_to_add.push_back(E->get()); + } + + has_all_peers = false; + } + } + + if (peers_to_add.size() > 0) { + // Those that need to be added, send a message for this. + + // Encode function name. + const CharString path = String(p_path).utf8(); + const int path_len = encode_cstring(path.get_data(), nullptr); + + // Extract MD5 from rpc methods list. + const String methods_md5 = multiplayer->get_rpc_md5(p_node); + const int methods_md5_len = 33; // 32 + 1 for the `0` that is added by the encoder. + + Vector<uint8_t> packet; + packet.resize(1 + 4 + path_len + methods_md5_len); + int ofs = 0; + + packet.write[ofs] = MultiplayerAPI::NETWORK_COMMAND_SIMPLIFY_PATH; + ofs += 1; + + ofs += encode_cstring(methods_md5.utf8().get_data(), &packet.write[ofs]); + + ofs += encode_uint32(psc->id, &packet.write[ofs]); + + ofs += encode_cstring(path.get_data(), &packet.write[ofs]); + + Ref<MultiplayerPeer> multiplayer_peer = multiplayer->get_multiplayer_peer(); + ERR_FAIL_COND_V(multiplayer_peer.is_null(), false); + + for (int &E : peers_to_add) { + multiplayer_peer->set_target_peer(E); // To all of you. + multiplayer_peer->set_transfer_channel(0); + multiplayer_peer->set_transfer_mode(Multiplayer::TRANSFER_MODE_RELIABLE); + multiplayer_peer->put_packet(packet.ptr(), packet.size()); + + psc->confirmed_peers.insert(E, false); // Insert into confirmed, but as false since it was not confirmed. + } + } + + return has_all_peers; +} + +bool SceneCacheInterface::is_cache_confirmed(NodePath p_path, int p_peer) { + const PathSentCache *psc = path_send_cache.getptr(p_path); + ERR_FAIL_COND_V(!psc, false); + const Map<int, bool>::Element *F = psc->confirmed_peers.find(p_peer); + ERR_FAIL_COND_V(!F, false); // Should never happen. + return F->get(); +} + +bool SceneCacheInterface::send_object_cache(Object *p_obj, NodePath p_path, int p_peer_id, int &r_id) { + Node *node = Object::cast_to<Node>(p_obj); + ERR_FAIL_COND_V(!node, false); + // See if the path is cached. + PathSentCache *psc = path_send_cache.getptr(p_path); + if (!psc) { + // Path is not cached, create. + path_send_cache[p_path] = PathSentCache(); + psc = path_send_cache.getptr(p_path); + psc->id = last_send_cache_id++; + } + r_id = psc->id; + + return _send_confirm_path(node, p_path, psc, p_peer_id); +} + +Object *SceneCacheInterface::get_cached_object(int p_from, uint32_t p_cache_id) { + Node *root_node = SceneTree::get_singleton()->get_root()->get_node(multiplayer->get_root_path()); + ERR_FAIL_COND_V(!root_node, nullptr); + Map<int, PathGetCache>::Element *E = path_get_cache.find(p_from); + ERR_FAIL_COND_V_MSG(!E, nullptr, vformat("No cache found for peer %d.", p_from)); + + Map<int, PathGetCache::NodeInfo>::Element *F = E->get().nodes.find(p_cache_id); + ERR_FAIL_COND_V_MSG(!F, nullptr, vformat("ID %d not found in cache of peer %d.", p_cache_id, p_from)); + + PathGetCache::NodeInfo *ni = &F->get(); + Node *node = root_node->get_node(ni->path); + if (!node) { + ERR_PRINT("Failed to get cached path: " + String(ni->path) + "."); + } + return node; +} + +void SceneCacheInterface::clear() { + path_get_cache.clear(); + path_send_cache.clear(); + last_send_cache_id = 1; +} diff --git a/scene/multiplayer/scene_cache_interface.h b/scene/multiplayer/scene_cache_interface.h new file mode 100644 index 0000000000..91a53cb948 --- /dev/null +++ b/scene/multiplayer/scene_cache_interface.h @@ -0,0 +1,82 @@ +/*************************************************************************/ +/* scene_cache_interface.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_CACHE_INTERFACE_H +#define SCENE_CACHE_INTERFACE_H + +#include "core/multiplayer/multiplayer_api.h" + +class SceneCacheInterface : public MultiplayerCacheInterface { + GDCLASS(SceneCacheInterface, MultiplayerCacheInterface); + +private: + MultiplayerAPI *multiplayer; + + //path sent caches + struct PathSentCache { + Map<int, bool> confirmed_peers; + int id; + }; + + //path get caches + struct PathGetCache { + struct NodeInfo { + NodePath path; + ObjectID instance; + }; + + Map<int, NodeInfo> nodes; + }; + + HashMap<NodePath, PathSentCache> path_send_cache; + Map<int, PathGetCache> path_get_cache; + int last_send_cache_id = 1; + +protected: + bool _send_confirm_path(Node *p_node, NodePath p_path, PathSentCache *psc, int p_target); + static MultiplayerCacheInterface *_create(MultiplayerAPI *p_multiplayer); + +public: + static void make_default(); + + virtual void clear() override; + virtual void on_peer_change(int p_id, bool p_connected) override; + virtual void process_simplify_path(int p_from, const uint8_t *p_packet, int p_packet_len) override; + virtual void process_confirm_path(int p_from, const uint8_t *p_packet, int p_packet_len) override; + + // Returns true if all peers have cached path. + virtual bool send_object_cache(Object *p_obj, NodePath p_path, int p_target, int &p_id) override; + virtual Object *get_cached_object(int p_from, uint32_t p_cache_id) override; + virtual bool is_cache_confirmed(NodePath p_path, int p_peer) override; + + SceneCacheInterface(MultiplayerAPI *p_multiplayer) { multiplayer = p_multiplayer; } +}; + +#endif // SCENE_CACHE_INTERFACE_H diff --git a/scene/multiplayer/scene_replication_interface.cpp b/scene/multiplayer/scene_replication_interface.cpp new file mode 100644 index 0000000000..2088a43ba7 --- /dev/null +++ b/scene/multiplayer/scene_replication_interface.cpp @@ -0,0 +1,411 @@ +/*************************************************************************/ +/* 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<uint32_t, ObjectID> remotes = rep_state->peer_get_remotes(p_id); + const uint32_t *k = nullptr; + while ((k = remotes.next(k))) { + Node *node = rep_state->get_node(remotes.get(*k)); + 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<Node>(p_obj); + ERR_FAIL_COND_V(!node || p_config.get_type() != Variant::OBJECT, ERR_INVALID_PARAMETER); + MultiplayerSpawner *spawner = Object::cast_to<MultiplayerSpawner>(p_config.get_validated_object()); + ERR_FAIL_COND_V(!spawner, ERR_INVALID_PARAMETER); + Error err = rep_state->config_add_spawn(node, spawner); + ERR_FAIL_COND_V(err != OK, err); + return _send_spawn(node, spawner, 0); +} + +Error SceneReplicationInterface::on_despawn(Object *p_obj, Variant p_config) { + Node *node = Object::cast_to<Node>(p_obj); + ERR_FAIL_COND_V(!node || p_config.get_type() != Variant::OBJECT, ERR_INVALID_PARAMETER); + MultiplayerSpawner *spawner = Object::cast_to<MultiplayerSpawner>(p_config.get_validated_object()); + ERR_FAIL_COND_V(!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<Node>(p_obj); + ERR_FAIL_COND_V(!node || p_config.get_type() != Variant::OBJECT, ERR_INVALID_PARAMETER); + MultiplayerSynchronizer *sync = Object::cast_to<MultiplayerSynchronizer>(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<NodePath> props = sync->get_replication_config()->get_spawn_properties(); + Vector<Variant> vars; + vars.resize(props.size()); + int consumed; + Error err = MultiplayerAPI::decode_and_decompress_variants(vars, pending_buffer, pending_buffer_size, consumed); + ERR_FAIL_COND_V(err, err); + err = MultiplayerSynchronizer::set_state(props, node, vars); + ERR_FAIL_COND_V(err, err); + } 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<Node>(p_obj); + ERR_FAIL_COND_V(!node || p_config.get_type() != Variant::OBJECT, ERR_INVALID_PARAMETER); + MultiplayerSynchronizer *sync = Object::cast_to<MultiplayerSynchronizer>(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); + Ref<MultiplayerPeer> 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->get_spawn_id(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<Variant> state_vars; + Vector<const Variant *> state_varp; + MultiplayerSynchronizer *synchronizer = rep_state->get_synchronizer(oid); + if (synchronizer && synchronizer->get_replication_config().is_valid()) { + const List<NodePath> 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<MultiplayerSpawner>(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); + node->queue_delete(); + return OK; +} + +void SceneReplicationInterface::_send_sync(int p_peer, uint64_t p_msec) { + const Set<ObjectID> &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<Variant> vars; + Vector<const Variant *> varp; + const List<NodePath> 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) { + // 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); + 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<MultiplayerSynchronizer>(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<NodePath> props = sync->get_replication_config()->get_sync_properties(); + Vector<Variant> 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_interface.h b/scene/multiplayer/scene_replication_interface.h new file mode 100644 index 0000000000..855878d029 --- /dev/null +++ b/scene/multiplayer/scene_replication_interface.h @@ -0,0 +1,84 @@ +/*************************************************************************/ +/* scene_replication_interface.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_TREE_REPLICATOR_INTERFACE_H +#define SCENE_TREE_REPLICATOR_INTERFACE_H + +#include "core/multiplayer/multiplayer_api.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<SceneReplicationState> rep_state; + MultiplayerAPI *multiplayer; + 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; + + 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; + + 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; + + SceneReplicationInterface(MultiplayerAPI *p_multiplayer) { + rep_state.instantiate(); + multiplayer = p_multiplayer; + } +}; + +#endif // SCENE_TREE_REPLICATOR_INTERFACE_H diff --git a/scene/multiplayer/scene_replication_state.cpp b/scene/multiplayer/scene_replication_state.cpp new file mode 100644 index 0000000000..b8dadeff24 --- /dev/null +++ b/scene/multiplayer/scene_replication_state.cpp @@ -0,0 +1,258 @@ +/*************************************************************************/ +/* 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<Node>(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) { + const int *k = nullptr; + while ((k = peers_info.next(k))) { + peers_info.get(*k).known_nodes.erase(p_id); + } + } + } +} + +const HashMap<uint32_t, ObjectID> SceneReplicationState::peer_get_remotes(int p_peer) const { + return peers_info.has(p_peer) ? peers_info[p_peer].recv_nodes : HashMap<uint32_t, ObjectID>(); +} + +bool SceneReplicationState::update_last_node_sync(const ObjectID &p_id, uint16_t p_time) { + TrackedNode *tnode = tracked_nodes.getptr(p_id); + ERR_FAIL_COND_V(!tnode, false); + if (p_time <= tnode->last_sync && tnode->last_sync - p_time < 32767) { + return false; + } + tnode->last_sync = p_time; + return true; +} + +bool SceneReplicationState::update_sync_time(const ObjectID &p_id, uint64_t p_msec) { + TrackedNode *tnode = tracked_nodes.getptr(p_id); + ERR_FAIL_COND_V(!tnode, false); + MultiplayerSynchronizer *sync = get_synchronizer(p_id); + if (!sync) { + return false; + } + if (tnode->last_sync_msec == p_msec) { + return true; + } + if (p_msec >= tnode->last_sync_msec + sync->get_replication_interval_msec()) { + tnode->last_sync_msec = p_msec; + return true; + } + return false; +} + +const Set<ObjectID> SceneReplicationState::get_known_nodes(int p_peer) { + ERR_FAIL_COND_V(!peers_info.has(p_peer), Set<ObjectID>()); + 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. + const ObjectID *oid = nullptr; + while ((oid = tracked_nodes.next(oid))) { + TrackedNode &tobj = tracked_nodes[*oid]; + 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 { + const int *pid = nullptr; + while ((pid = peers_info.next(pid))) { + peers_info.get(*pid).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 { + const int *pid = nullptr; + while ((pid = peers_info.next(pid))) { + peers_info.get(*pid).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<Node>(ObjectDB::get_instance(info->recv_nodes[p_net_id])) : nullptr; +} + +Error SceneReplicationState::peer_add_remote(int p_peer, uint32_t p_net_id, Node *p_node, MultiplayerSpawner *p_spawner) { + ERR_FAIL_COND_V(!p_node || !p_spawner, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(!peers_info.has(p_peer), ERR_UNAVAILABLE); + PeerInfo &pinfo = peers_info[p_peer]; + ObjectID oid = p_node->get_instance_id(); + TrackedNode &tobj = _track(oid); + tobj.spawner = p_spawner->get_instance_id(); + tobj.net_id = p_net_id; + tobj.remote_peer = p_peer; + tobj.last_sync = pinfo.last_recv_sync; + // Also track as a remote. + ERR_FAIL_COND_V(pinfo.recv_nodes.has(p_net_id), ERR_ALREADY_IN_USE); + pinfo.recv_nodes[p_net_id] = oid; + return OK; +} + +Error SceneReplicationState::peer_del_remote(int p_peer, uint32_t p_net_id, Node **r_node) { + ERR_FAIL_COND_V(!peers_info.has(p_peer), ERR_UNAUTHORIZED); + PeerInfo &info = peers_info[p_peer]; + ERR_FAIL_COND_V(!info.recv_nodes.has(p_net_id), ERR_UNAUTHORIZED); + *r_node = Object::cast_to<Node>(ObjectDB::get_instance(info.recv_nodes[p_net_id])); + info.recv_nodes.erase(p_net_id); + return OK; +} + +uint16_t SceneReplicationState::peer_sync_next(int p_peer) { + ERR_FAIL_COND_V(!peers_info.has(p_peer), 0); + PeerInfo &info = peers_info[p_peer]; + return ++info.last_sent_sync; +} + +void SceneReplicationState::peer_sync_recv(int p_peer, uint16_t p_time) { + ERR_FAIL_COND(!peers_info.has(p_peer)); + peers_info[p_peer].last_recv_sync = p_time; +} diff --git a/scene/multiplayer/scene_replication_state.h b/scene/multiplayer/scene_replication_state.h new file mode 100644 index 0000000000..18e4d9fa39 --- /dev/null +++ b/scene/multiplayer/scene_replication_state.h @@ -0,0 +1,121 @@ +/*************************************************************************/ +/* 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<Node>(ObjectDB::get_instance(id)) : nullptr; } + MultiplayerSpawner *get_spawner() const { return spawner.is_valid() ? Object::cast_to<MultiplayerSpawner>(ObjectDB::get_instance(spawner)) : nullptr; } + MultiplayerSynchronizer *get_synchronizer() const { return synchronizer.is_valid() ? Object::cast_to<MultiplayerSynchronizer>(ObjectDB::get_instance(synchronizer)) : nullptr; } + TrackedNode() {} + TrackedNode(const ObjectID &p_id) { id = p_id; } + TrackedNode(const ObjectID &p_id, uint32_t p_net_id) { + id = p_id; + net_id = p_net_id; + } + }; + + struct PeerInfo { + Set<ObjectID> known_nodes; + HashMap<uint32_t, ObjectID> recv_nodes; + uint16_t last_sent_sync = 0; + uint16_t last_recv_sync = 0; + }; + + Set<int> known_peers; + uint32_t last_net_id = 0; + HashMap<ObjectID, TrackedNode> tracked_nodes; + HashMap<int, PeerInfo> peers_info; + Set<ObjectID> spawned_nodes; + Set<ObjectID> 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 Set<int> get_peers() const { return known_peers; } + const Set<ObjectID> get_spawned_nodes() const { return spawned_nodes; } + const Set<ObjectID> 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 Set<ObjectID> 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<uint32_t, ObjectID> peer_get_remotes(int p_peer) const; + Node *peer_get_remote(int p_peer, uint32_t p_net_id); + Error peer_add_remote(int p_peer, uint32_t p_net_id, Node *p_node, MultiplayerSpawner *p_spawner); + Error peer_del_remote(int p_peer, uint32_t p_net_id, Node **r_node); + + uint16_t peer_sync_next(int p_peer); + void peer_sync_recv(int p_peer, uint16_t p_time); + + SceneReplicationState() {} +}; + +#endif // SCENE_REPLICATON_STATE_H diff --git a/scene/multiplayer/scene_rpc_interface.cpp b/scene/multiplayer/scene_rpc_interface.cpp new file mode 100644 index 0000000000..7d7f57b9a1 --- /dev/null +++ b/scene/multiplayer/scene_rpc_interface.cpp @@ -0,0 +1,512 @@ +/*************************************************************************/ +/* scene_rpc_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/multiplayer/scene_rpc_interface.h" + +#include "core/debugger/engine_debugger.h" +#include "core/io/marshalls.h" +#include "core/multiplayer/multiplayer_api.h" +#include "scene/main/node.h" +#include "scene/main/window.h" + +MultiplayerRPCInterface *SceneRPCInterface::_create(MultiplayerAPI *p_multiplayer) { + return memnew(SceneRPCInterface(p_multiplayer)); +} + +void SceneRPCInterface::make_default() { + MultiplayerAPI::create_default_rpc_interface = _create; +} + +#ifdef DEBUG_ENABLED +_FORCE_INLINE_ void SceneRPCInterface::_profile_node_data(const String &p_what, ObjectID p_id) { + if (EngineDebugger::is_profiling("multiplayer")) { + Array values; + values.push_back("node"); + values.push_back(p_id); + values.push_back(p_what); + EngineDebugger::profiler_add_frame_data("multiplayer", values); + } +} +#else +_FORCE_INLINE_ void SceneRPCInterface::_profile_node_data(const String &p_what, ObjectID p_id) {} +#endif + +// Returns the packet size stripping the node path added when the node is not yet cached. +int get_packet_len(uint32_t p_node_target, int p_packet_len) { + if (p_node_target & 0x80000000) { + int ofs = p_node_target & 0x7FFFFFFF; + return p_packet_len - (p_packet_len - ofs); + } else { + return p_packet_len; + } +} + +const Multiplayer::RPCConfig _get_rpc_config(const Node *p_node, const StringName &p_method, uint16_t &r_id) { + const Vector<Multiplayer::RPCConfig> node_config = p_node->get_node_rpc_methods(); + for (int i = 0; i < node_config.size(); i++) { + if (node_config[i].name == p_method) { + r_id = ((uint16_t)i) | (1 << 15); + return node_config[i]; + } + } + if (p_node->get_script_instance()) { + const Vector<Multiplayer::RPCConfig> script_config = p_node->get_script_instance()->get_rpc_methods(); + for (int i = 0; i < script_config.size(); i++) { + if (script_config[i].name == p_method) { + r_id = (uint16_t)i; + return script_config[i]; + } + } + } + return Multiplayer::RPCConfig(); +} + +const Multiplayer::RPCConfig _get_rpc_config_by_id(Node *p_node, uint16_t p_id) { + Vector<Multiplayer::RPCConfig> config; + uint16_t id = p_id; + if (id & (1 << 15)) { + id = id & ~(1 << 15); + config = p_node->get_node_rpc_methods(); + } else if (p_node->get_script_instance()) { + config = p_node->get_script_instance()->get_rpc_methods(); + } + if (id < config.size()) { + return config[id]; + } + return Multiplayer::RPCConfig(); +} + +_FORCE_INLINE_ bool _can_call_mode(Node *p_node, Multiplayer::RPCMode mode, int p_remote_id) { + switch (mode) { + case Multiplayer::RPC_MODE_DISABLED: { + return false; + } break; + case Multiplayer::RPC_MODE_ANY_PEER: { + return true; + } break; + case Multiplayer::RPC_MODE_AUTHORITY: { + return !p_node->is_multiplayer_authority() && p_remote_id == p_node->get_multiplayer_authority(); + } break; + } + + return false; +} + +String SceneRPCInterface::get_rpc_md5(const Object *p_obj) const { + const Node *node = Object::cast_to<Node>(p_obj); + ERR_FAIL_COND_V(!node, ""); + String rpc_list; + const Vector<Multiplayer::RPCConfig> node_config = node->get_node_rpc_methods(); + for (int i = 0; i < node_config.size(); i++) { + rpc_list += String(node_config[i].name); + } + if (node->get_script_instance()) { + const Vector<Multiplayer::RPCConfig> script_config = node->get_script_instance()->get_rpc_methods(); + for (int i = 0; i < script_config.size(); i++) { + rpc_list += String(script_config[i].name); + } + } + return rpc_list.md5_text(); +} + +Node *SceneRPCInterface::_process_get_node(int p_from, const uint8_t *p_packet, uint32_t p_node_target, int p_packet_len) { + Node *root_node = SceneTree::get_singleton()->get_root()->get_node(multiplayer->get_root_path()); + ERR_FAIL_COND_V(!root_node, nullptr); + Node *node = nullptr; + + if (p_node_target & 0x80000000) { + // Use full path (not cached yet). + int ofs = p_node_target & 0x7FFFFFFF; + + ERR_FAIL_COND_V_MSG(ofs >= p_packet_len, nullptr, "Invalid packet received. Size smaller than declared."); + + String paths; + paths.parse_utf8((const char *)&p_packet[ofs], p_packet_len - ofs); + + NodePath np = paths; + + node = root_node->get_node(np); + + if (!node) { + ERR_PRINT("Failed to get path from RPC: " + String(np) + "."); + } + return node; + } else { + // Use cached path. + return Object::cast_to<Node>(multiplayer->get_cached_object(p_from, p_node_target)); + } +} + +void SceneRPCInterface::process_rpc(int p_from, const uint8_t *p_packet, int p_packet_len) { + // Extract packet meta + int packet_min_size = 1; + int name_id_offset = 1; + ERR_FAIL_COND_MSG(p_packet_len < packet_min_size, "Invalid packet received. Size too small."); + // Compute the meta size, which depends on the compression level. + int node_id_compression = (p_packet[0] & NODE_ID_COMPRESSION_FLAG) >> NODE_ID_COMPRESSION_SHIFT; + int name_id_compression = (p_packet[0] & NAME_ID_COMPRESSION_FLAG) >> NAME_ID_COMPRESSION_SHIFT; + + switch (node_id_compression) { + case NETWORK_NODE_ID_COMPRESSION_8: + packet_min_size += 1; + name_id_offset += 1; + break; + case NETWORK_NODE_ID_COMPRESSION_16: + packet_min_size += 2; + name_id_offset += 2; + break; + case NETWORK_NODE_ID_COMPRESSION_32: + packet_min_size += 4; + name_id_offset += 4; + break; + default: + ERR_FAIL_MSG("Was not possible to extract the node id compression mode."); + } + switch (name_id_compression) { + case NETWORK_NAME_ID_COMPRESSION_8: + packet_min_size += 1; + break; + case NETWORK_NAME_ID_COMPRESSION_16: + packet_min_size += 2; + break; + default: + ERR_FAIL_MSG("Was not possible to extract the name id compression mode."); + } + ERR_FAIL_COND_MSG(p_packet_len < packet_min_size, "Invalid packet received. Size too small."); + + uint32_t node_target = 0; + switch (node_id_compression) { + case NETWORK_NODE_ID_COMPRESSION_8: + node_target = p_packet[1]; + break; + case NETWORK_NODE_ID_COMPRESSION_16: + node_target = decode_uint16(p_packet + 1); + break; + case NETWORK_NODE_ID_COMPRESSION_32: + node_target = decode_uint32(p_packet + 1); + break; + default: + // Unreachable, checked before. + CRASH_NOW(); + } + + Node *node = _process_get_node(p_from, p_packet, node_target, p_packet_len); + ERR_FAIL_COND_MSG(node == nullptr, "Invalid packet received. Requested node was not found."); + + uint16_t name_id = 0; + switch (name_id_compression) { + case NETWORK_NAME_ID_COMPRESSION_8: + name_id = p_packet[name_id_offset]; + break; + case NETWORK_NAME_ID_COMPRESSION_16: + name_id = decode_uint16(p_packet + name_id_offset); + break; + default: + // Unreachable, checked before. + CRASH_NOW(); + } + + const int packet_len = get_packet_len(node_target, p_packet_len); + _process_rpc(node, name_id, p_from, p_packet, packet_len, packet_min_size); +} + +void SceneRPCInterface::_process_rpc(Node *p_node, const uint16_t p_rpc_method_id, int p_from, const uint8_t *p_packet, int p_packet_len, int p_offset) { + ERR_FAIL_COND_MSG(p_offset > p_packet_len, "Invalid packet received. Size too small."); + + // Check that remote can call the RPC on this node. + const Multiplayer::RPCConfig config = _get_rpc_config_by_id(p_node, p_rpc_method_id); + ERR_FAIL_COND(config.name == StringName()); + + bool can_call = _can_call_mode(p_node, config.rpc_mode, p_from); + ERR_FAIL_COND_MSG(!can_call, "RPC '" + String(config.name) + "' is not allowed on node " + p_node->get_path() + " from: " + itos(p_from) + ". Mode is " + itos((int)config.rpc_mode) + ", authority is " + itos(p_node->get_multiplayer_authority()) + "."); + + int argc = 0; + + const bool byte_only_or_no_args = p_packet[0] & BYTE_ONLY_OR_NO_ARGS_FLAG; + if (byte_only_or_no_args) { + if (p_offset < p_packet_len) { + // This packet contains only bytes. + argc = 1; + } + } else { + // Normal variant, takes the argument count from the packet. + ERR_FAIL_COND_MSG(p_offset >= p_packet_len, "Invalid packet received. Size too small."); + argc = p_packet[p_offset]; + p_offset += 1; + } + + Vector<Variant> args; + Vector<const Variant *> argp; + args.resize(argc); + argp.resize(argc); + +#ifdef DEBUG_ENABLED + _profile_node_data("in_rpc", p_node->get_instance_id()); +#endif + + int out; + MultiplayerAPI::decode_and_decompress_variants(args, &p_packet[p_offset], p_packet_len - p_offset, out, byte_only_or_no_args, multiplayer->is_object_decoding_allowed()); + for (int i = 0; i < argc; i++) { + argp.write[i] = &args[i]; + } + + Callable::CallError ce; + + p_node->call(config.name, (const Variant **)argp.ptr(), argc, ce); + if (ce.error != Callable::CallError::CALL_OK) { + String error = Variant::get_call_error_text(p_node, config.name, (const Variant **)argp.ptr(), argc, ce); + error = "RPC - " + error; + ERR_PRINT(error); + } +} + +void SceneRPCInterface::_send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, const Multiplayer::RPCConfig &p_config, const StringName &p_name, const Variant **p_arg, int p_argcount) { + Ref<MultiplayerPeer> peer = multiplayer->get_multiplayer_peer(); + ERR_FAIL_COND_MSG(peer.is_null(), "Attempt to call RPC without active multiplayer peer."); + + ERR_FAIL_COND_MSG(peer->get_connection_status() == MultiplayerPeer::CONNECTION_CONNECTING, "Attempt to call RPC while multiplayer peer is not connected yet."); + + ERR_FAIL_COND_MSG(peer->get_connection_status() == MultiplayerPeer::CONNECTION_DISCONNECTED, "Attempt to call RPC while multiplayer peer is disconnected."); + + ERR_FAIL_COND_MSG(p_argcount > 255, "Too many arguments (>255)."); + + if (p_to != 0 && !multiplayer->get_connected_peers().has(ABS(p_to))) { + ERR_FAIL_COND_MSG(p_to == peer->get_unique_id(), "Attempt to call RPC on yourself! Peer unique ID: " + itos(peer->get_unique_id()) + "."); + + ERR_FAIL_MSG("Attempt to call RPC with unknown peer ID: " + itos(p_to) + "."); + } + + NodePath from_path = multiplayer->get_root_path().rel_path_to(p_from->get_path()); + ERR_FAIL_COND_MSG(from_path.is_empty(), "Unable to send RPC. Relative path is empty. THIS IS LIKELY A BUG IN THE ENGINE!"); + + // See if all peers have cached path (if so, call can be fast). + int psc_id; + const bool has_all_peers = multiplayer->send_object_cache(p_from, from_path, p_to, psc_id); + + // Create base packet, lots of hardcode because it must be tight. + + int ofs = 0; + +#define MAKE_ROOM(m_amount) \ + if (packet_cache.size() < m_amount) \ + packet_cache.resize(m_amount); + + // Encode meta. + uint8_t command_type = MultiplayerAPI::NETWORK_COMMAND_REMOTE_CALL; + uint8_t node_id_compression = UINT8_MAX; + uint8_t name_id_compression = UINT8_MAX; + bool byte_only_or_no_args = false; + + MAKE_ROOM(1); + // The meta is composed along the way, so just set 0 for now. + packet_cache.write[0] = 0; + ofs += 1; + + // Encode Node ID. + if (has_all_peers) { + // Compress the node ID only if all the target peers already know it. + if (psc_id >= 0 && psc_id <= 255) { + // We can encode the id in 1 byte + node_id_compression = NETWORK_NODE_ID_COMPRESSION_8; + MAKE_ROOM(ofs + 1); + packet_cache.write[ofs] = static_cast<uint8_t>(psc_id); + ofs += 1; + } else if (psc_id >= 0 && psc_id <= 65535) { + // We can encode the id in 2 bytes + node_id_compression = NETWORK_NODE_ID_COMPRESSION_16; + MAKE_ROOM(ofs + 2); + encode_uint16(static_cast<uint16_t>(psc_id), &(packet_cache.write[ofs])); + ofs += 2; + } else { + // Too big, let's use 4 bytes. + node_id_compression = NETWORK_NODE_ID_COMPRESSION_32; + MAKE_ROOM(ofs + 4); + encode_uint32(psc_id, &(packet_cache.write[ofs])); + ofs += 4; + } + } else { + // The targets don't know the node yet, so we need to use 32 bits int. + node_id_compression = NETWORK_NODE_ID_COMPRESSION_32; + MAKE_ROOM(ofs + 4); + encode_uint32(psc_id, &(packet_cache.write[ofs])); + ofs += 4; + } + + // Encode method ID + if (p_rpc_id <= UINT8_MAX) { + // The ID fits in 1 byte + name_id_compression = NETWORK_NAME_ID_COMPRESSION_8; + MAKE_ROOM(ofs + 1); + packet_cache.write[ofs] = static_cast<uint8_t>(p_rpc_id); + ofs += 1; + } else { + // The ID is larger, let's use 2 bytes + name_id_compression = NETWORK_NAME_ID_COMPRESSION_16; + MAKE_ROOM(ofs + 2); + encode_uint16(p_rpc_id, &(packet_cache.write[ofs])); + ofs += 2; + } + + int len; + Error err = MultiplayerAPI::encode_and_compress_variants(p_arg, p_argcount, nullptr, len, &byte_only_or_no_args, multiplayer->is_object_decoding_allowed()); + ERR_FAIL_COND_MSG(err != OK, "Unable to encode RPC arguments. THIS IS LIKELY A BUG IN THE ENGINE!"); + if (byte_only_or_no_args) { + MAKE_ROOM(ofs + len); + } else { + MAKE_ROOM(ofs + 1 + len); + packet_cache.write[ofs] = p_argcount; + ofs += 1; + } + if (len) { + MultiplayerAPI::encode_and_compress_variants(p_arg, p_argcount, &packet_cache.write[ofs], len, &byte_only_or_no_args, multiplayer->is_object_decoding_allowed()); + ofs += len; + } + + ERR_FAIL_COND(command_type > 7); + ERR_FAIL_COND(node_id_compression > 3); + ERR_FAIL_COND(name_id_compression > 1); + + // We can now set the meta + packet_cache.write[0] = command_type + (node_id_compression << NODE_ID_COMPRESSION_SHIFT) + (name_id_compression << NAME_ID_COMPRESSION_SHIFT) + (byte_only_or_no_args ? BYTE_ONLY_OR_NO_ARGS_FLAG : 0); + +#ifdef DEBUG_ENABLED + multiplayer->profile_bandwidth("out", ofs); +#endif + + // Take chance and set transfer mode, since all send methods will use it. + peer->set_transfer_channel(p_config.channel); + peer->set_transfer_mode(p_config.transfer_mode); + + if (has_all_peers) { + // They all have verified paths, so send fast. + peer->set_target_peer(p_to); // To all of you. + peer->put_packet(packet_cache.ptr(), ofs); // A message with love. + } else { + // Unreachable because the node ID is never compressed if the peers doesn't know it. + CRASH_COND(node_id_compression != NETWORK_NODE_ID_COMPRESSION_32); + + // Not all verified path, so send one by one. + + // Append path at the end, since we will need it for some packets. + CharString pname = String(from_path).utf8(); + int path_len = encode_cstring(pname.get_data(), nullptr); + MAKE_ROOM(ofs + path_len); + encode_cstring(pname.get_data(), &(packet_cache.write[ofs])); + + for (const int &P : multiplayer->get_connected_peers()) { + if (p_to < 0 && P == -p_to) { + continue; // Continue, excluded. + } + + if (p_to > 0 && P != p_to) { + continue; // Continue, not for this peer. + } + + bool confirmed = multiplayer->is_cache_confirmed(from_path, P); + + peer->set_target_peer(P); // To this one specifically. + + if (confirmed) { + // This one confirmed path, so use id. + encode_uint32(psc_id, &(packet_cache.write[1])); + peer->put_packet(packet_cache.ptr(), ofs); + } else { + // This one did not confirm path yet, so use entire path (sorry!). + encode_uint32(0x80000000 | ofs, &(packet_cache.write[1])); // Offset to path and flag. + peer->put_packet(packet_cache.ptr(), ofs + path_len); + } + } + } +} + +void SceneRPCInterface::rpcp(Object *p_obj, int p_peer_id, const StringName &p_method, const Variant **p_arg, int p_argcount) { + Ref<MultiplayerPeer> peer = multiplayer->get_multiplayer_peer(); + ERR_FAIL_COND_MSG(!peer.is_valid(), "Trying to call an RPC while no multiplayer peer is active."); + Node *node = Object::cast_to<Node>(p_obj); + ERR_FAIL_COND(!node); + ERR_FAIL_COND_MSG(!node->is_inside_tree(), "Trying to call an RPC on a node which is not inside SceneTree."); + ERR_FAIL_COND_MSG(peer->get_connection_status() != MultiplayerPeer::CONNECTION_CONNECTED, "Trying to call an RPC via a multiplayer peer which is not connected."); + + int node_id = peer->get_unique_id(); + bool call_local_native = false; + bool call_local_script = false; + uint16_t rpc_id = UINT16_MAX; + const Multiplayer::RPCConfig config = _get_rpc_config(node, p_method, rpc_id); + ERR_FAIL_COND_MSG(config.name == StringName(), + vformat("Unable to get the RPC configuration for the function \"%s\" at path: \"%s\". This happens when the method is not marked for RPCs.", p_method, node->get_path())); + if (p_peer_id == 0 || p_peer_id == node_id || (p_peer_id < 0 && p_peer_id != -node_id)) { + if (rpc_id & (1 << 15)) { + call_local_native = config.call_local; + } else { + call_local_script = config.call_local; + } + } + + if (p_peer_id != node_id) { +#ifdef DEBUG_ENABLED + _profile_node_data("out_rpc", node->get_instance_id()); +#endif + + _send_rpc(node, p_peer_id, rpc_id, config, p_method, p_arg, p_argcount); + } + + if (call_local_native) { + Callable::CallError ce; + + multiplayer->set_remote_sender_override(peer->get_unique_id()); + node->call(p_method, p_arg, p_argcount, ce); + multiplayer->set_remote_sender_override(0); + + if (ce.error != Callable::CallError::CALL_OK) { + String error = Variant::get_call_error_text(node, p_method, p_arg, p_argcount, ce); + error = "rpc() aborted in local call: - " + error + "."; + ERR_PRINT(error); + return; + } + } + + if (call_local_script) { + Callable::CallError ce; + ce.error = Callable::CallError::CALL_OK; + + multiplayer->set_remote_sender_override(peer->get_unique_id()); + node->get_script_instance()->call(p_method, p_arg, p_argcount, ce); + multiplayer->set_remote_sender_override(0); + + if (ce.error != Callable::CallError::CALL_OK) { + String error = Variant::get_call_error_text(node, p_method, p_arg, p_argcount, ce); + error = "rpc() aborted in script local call: - " + error + "."; + ERR_PRINT(error); + return; + } + } + + ERR_FAIL_COND_MSG(p_peer_id == node_id && !config.call_local, "RPC '" + p_method + "' on yourself is not allowed by selected mode."); +} diff --git a/scene/multiplayer/scene_rpc_interface.h b/scene/multiplayer/scene_rpc_interface.h new file mode 100644 index 0000000000..86e1d0d280 --- /dev/null +++ b/scene/multiplayer/scene_rpc_interface.h @@ -0,0 +1,91 @@ +/*************************************************************************/ +/* scene_rpc_interface.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_RPC_INTERFACE_H +#define SCENE_RPC_INTERFACE_H + +#include "core/multiplayer/multiplayer.h" +#include "core/multiplayer/multiplayer_api.h" + +class SceneRPCInterface : public MultiplayerRPCInterface { + GDCLASS(SceneRPCInterface, MultiplayerRPCInterface); + +private: + enum NetworkNodeIdCompression { + NETWORK_NODE_ID_COMPRESSION_8 = 0, + NETWORK_NODE_ID_COMPRESSION_16, + NETWORK_NODE_ID_COMPRESSION_32, + }; + + enum NetworkNameIdCompression { + NETWORK_NAME_ID_COMPRESSION_8 = 0, + NETWORK_NAME_ID_COMPRESSION_16, + }; + + // The RPC meta is composed by a single byte that contains (starting from the least significant bit): + // - `NetworkCommands` in the first four bits. + // - `NetworkNodeIdCompression` in the next 2 bits. + // - `NetworkNameIdCompression` in the next 1 bit. + // - `byte_only_or_no_args` in the next 1 bit. + enum { + NODE_ID_COMPRESSION_SHIFT = MultiplayerAPI::CMD_FLAG_0_SHIFT, // 2 bits for this. + NAME_ID_COMPRESSION_SHIFT = MultiplayerAPI::CMD_FLAG_2_SHIFT, + BYTE_ONLY_OR_NO_ARGS_SHIFT = MultiplayerAPI::CMD_FLAG_3_SHIFT, + }; + + enum { + NODE_ID_COMPRESSION_FLAG = (1 << NODE_ID_COMPRESSION_SHIFT) | (1 << (NODE_ID_COMPRESSION_SHIFT + 1)), // 2 bits for this. + NAME_ID_COMPRESSION_FLAG = (1 << NAME_ID_COMPRESSION_SHIFT), + BYTE_ONLY_OR_NO_ARGS_FLAG = (1 << BYTE_ONLY_OR_NO_ARGS_SHIFT), + }; + + MultiplayerAPI *multiplayer = nullptr; + Vector<uint8_t> packet_cache; + +protected: + static MultiplayerRPCInterface *_create(MultiplayerAPI *p_multiplayer); + + _FORCE_INLINE_ void _profile_node_data(const String &p_what, ObjectID p_id); + void _process_rpc(Node *p_node, const uint16_t p_rpc_method_id, int p_from, const uint8_t *p_packet, int p_packet_len, int p_offset); + + void _send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, const Multiplayer::RPCConfig &p_config, const StringName &p_name, const Variant **p_arg, int p_argcount); + Node *_process_get_node(int p_from, const uint8_t *p_packet, uint32_t p_node_target, int p_packet_len); + +public: + static void make_default(); + + virtual void rpcp(Object *p_obj, int p_peer_id, const StringName &p_method, const Variant **p_arg, int p_argcount) override; + virtual void process_rpc(int p_from, const uint8_t *p_packet, int p_packet_len) override; + virtual String get_rpc_md5(const Object *p_obj) const override; + + SceneRPCInterface(MultiplayerAPI *p_multiplayer) { multiplayer = p_multiplayer; } +}; + +#endif // SCENE_RPC_INTERFACE_H |