summaryrefslogtreecommitdiff
path: root/scene/multiplayer
diff options
context:
space:
mode:
Diffstat (limited to 'scene/multiplayer')
-rw-r--r--scene/multiplayer/multiplayer_spawner.cpp147
-rw-r--r--scene/multiplayer/multiplayer_spawner.h34
-rw-r--r--scene/multiplayer/multiplayer_synchronizer.cpp147
-rw-r--r--scene/multiplayer/multiplayer_synchronizer.h27
-rw-r--r--scene/multiplayer/scene_cache_interface.cpp61
-rw-r--r--scene/multiplayer/scene_cache_interface.h9
-rw-r--r--scene/multiplayer/scene_replication_interface.cpp218
-rw-r--r--scene/multiplayer/scene_replication_interface.h7
-rw-r--r--scene/multiplayer/scene_replication_state.cpp91
-rw-r--r--scene/multiplayer/scene_replication_state.h31
-rw-r--r--scene/multiplayer/scene_rpc_interface.cpp6
11 files changed, 588 insertions, 190 deletions
diff --git a/scene/multiplayer/multiplayer_spawner.cpp b/scene/multiplayer/multiplayer_spawner.cpp
index 25ab27f3e7..8363d05e54 100644
--- a/scene/multiplayer/multiplayer_spawner.cpp
+++ b/scene/multiplayer/multiplayer_spawner.cpp
@@ -35,12 +35,106 @@
#include "scene/main/window.h"
#include "scene/scene_string_names.h"
+#ifdef TOOLS_ENABLED
+/* This is editor only */
+bool MultiplayerSpawner::_set(const StringName &p_name, const Variant &p_value) {
+ if (p_name == "_spawnable_scene_count") {
+ spawnable_scenes.resize(p_value);
+ notify_property_list_changed();
+ return true;
+ } else {
+ String ns = p_name;
+ if (ns.begins_with("scenes/")) {
+ uint32_t index = ns.get_slicec('/', 1).to_int();
+ ERR_FAIL_UNSIGNED_INDEX_V(index, spawnable_scenes.size(), false);
+ spawnable_scenes[index].path = p_value;
+ return true;
+ }
+ }
+ return false;
+}
+
+bool MultiplayerSpawner::_get(const StringName &p_name, Variant &r_ret) const {
+ if (p_name == "_spawnable_scene_count") {
+ r_ret = spawnable_scenes.size();
+ return true;
+ } else {
+ String ns = p_name;
+ if (ns.begins_with("scenes/")) {
+ uint32_t index = ns.get_slicec('/', 1).to_int();
+ ERR_FAIL_UNSIGNED_INDEX_V(index, spawnable_scenes.size(), false);
+ r_ret = spawnable_scenes[index].path;
+ return true;
+ }
+ }
+ return false;
+}
+
+void MultiplayerSpawner::_get_property_list(List<PropertyInfo> *p_list) const {
+ p_list->push_back(PropertyInfo(Variant::INT, "_spawnable_scene_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_ARRAY, "Auto Spawn List,scenes/"));
+ List<String> exts;
+ ResourceLoader::get_recognized_extensions_for_type("PackedScene", &exts);
+ String ext_hint;
+ for (const String &E : exts) {
+ if (!ext_hint.is_empty()) {
+ ext_hint += ",";
+ }
+ ext_hint += "*." + E;
+ }
+ for (uint32_t i = 0; i < spawnable_scenes.size(); i++) {
+ p_list->push_back(PropertyInfo(Variant::STRING, "scenes/" + itos(i), PROPERTY_HINT_FILE, ext_hint, PROPERTY_USAGE_EDITOR));
+ }
+}
+#endif
+void MultiplayerSpawner::add_spawnable_scene(const String &p_path) {
+ SpawnableScene sc;
+ sc.path = p_path;
+ if (Engine::get_singleton()->is_editor_hint()) {
+ ERR_FAIL_COND(!FileAccess::exists(p_path));
+ } else {
+ sc.cache = ResourceLoader::load(p_path);
+ ERR_FAIL_COND_MSG(sc.cache.is_null(), "Invalid spawnable scene: " + p_path);
+ }
+ spawnable_scenes.push_back(sc);
+}
+int MultiplayerSpawner::get_spawnable_scene_count() const {
+ return spawnable_scenes.size();
+}
+String MultiplayerSpawner::get_spawnable_scene(int p_idx) const {
+ return spawnable_scenes[p_idx].path;
+}
+void MultiplayerSpawner::clear_spawnable_scenes() {
+ spawnable_scenes.clear();
+}
+
+Vector<String> MultiplayerSpawner::_get_spawnable_scenes() const {
+ Vector<String> ss;
+ ss.resize(spawnable_scenes.size());
+ for (int i = 0; i < ss.size(); i++) {
+ ss.write[i] = spawnable_scenes[i].path;
+ }
+ return ss;
+}
+
+void MultiplayerSpawner::_set_spawnable_scenes(const Vector<String> &p_scenes) {
+ clear_spawnable_scenes();
+ for (int i = 0; i < p_scenes.size(); i++) {
+ add_spawnable_scene(p_scenes[i]);
+ }
+}
+
void MultiplayerSpawner::_bind_methods() {
- ClassDB::bind_method(D_METHOD("spawn", "data"), &MultiplayerSpawner::spawn, DEFVAL(Variant()));
+ ClassDB::bind_method(D_METHOD("add_spawnable_scene", "path"), &MultiplayerSpawner::add_spawnable_scene);
+ ClassDB::bind_method(D_METHOD("get_spawnable_scene_count"), &MultiplayerSpawner::get_spawnable_scene_count);
+ ClassDB::bind_method(D_METHOD("get_spawnable_scene", "path"), &MultiplayerSpawner::get_spawnable_scene);
+ ClassDB::bind_method(D_METHOD("clear_spawnable_scenes"), &MultiplayerSpawner::clear_spawnable_scenes);
+
+ ClassDB::bind_method(D_METHOD("_get_spawnable_scenes"), &MultiplayerSpawner::_get_spawnable_scenes);
+ ClassDB::bind_method(D_METHOD("_set_spawnable_scenes", "scenes"), &MultiplayerSpawner::_set_spawnable_scenes);
- 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");
+ ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "_spawnable_scenes", PROPERTY_HINT_NONE, "", (PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL)), "_set_spawnable_scenes", "_get_spawnable_scenes");
+
+ ClassDB::bind_method(D_METHOD("spawn", "data"), &MultiplayerSpawner::spawn, DEFVAL(Variant()));
ClassDB::bind_method(D_METHOD("get_spawn_path"), &MultiplayerSpawner::get_spawn_path);
ClassDB::bind_method(D_METHOD("set_spawn_path", "path"), &MultiplayerSpawner::set_spawn_path);
@@ -50,10 +144,6 @@ void MultiplayerSpawner::_bind_methods() {
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")));
@@ -75,7 +165,7 @@ void MultiplayerSpawner::_update_spawn_node() {
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) {
+ if (get_spawnable_scene_count() && !GDVIRTUAL_IS_OVERRIDDEN(_spawn_custom)) {
node->connect("child_entered_tree", callable_mp(this, &MultiplayerSpawner::_node_added));
}
} else {
@@ -91,9 +181,9 @@ void MultiplayerSpawner::_notification(int p_what) {
case 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));
+
+ for (const KeyValue<ObjectID, SpawnInfo> &E : tracked_nodes) {
+ Node *node = Object::cast_to<Node>(ObjectDB::get_instance(E.key));
ERR_CONTINUE(!node);
node->disconnect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &MultiplayerSpawner::_node_exit));
// This is unlikely, but might still crash the engine.
@@ -118,7 +208,7 @@ void MultiplayerSpawner::_node_added(Node *p_node) {
if (!parent || p_node->get_parent() != parent) {
return;
}
- int id = get_scene_id(p_node->get_scene_file_path());
+ int id = find_spawnable_scene_index_from_path(p_node->get_scene_file_path());
if (id == INVALID_ID) {
return;
}
@@ -127,23 +217,6 @@ void MultiplayerSpawner::_node_added(Node *p_node) {
_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;
}
@@ -175,18 +248,16 @@ void MultiplayerSpawner::_node_exit(ObjectID p_id) {
}
}
-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) {
+int MultiplayerSpawner::find_spawnable_scene_index_from_path(const String &p_scene) const {
+ for (uint32_t i = 0; i < spawnable_scenes.size(); i++) {
+ if (spawnable_scenes[i].path == p_scene) {
return i;
}
}
return INVALID_ID;
}
-int MultiplayerSpawner::get_spawn_id(const ObjectID &p_id) const {
+int MultiplayerSpawner::find_spawnable_scene_index_from_object(const ObjectID &p_id) const {
const SpawnInfo *info = tracked_nodes.getptr(p_id);
return info ? info->id : INVALID_ID;
}
@@ -198,8 +269,8 @@ const Variant MultiplayerSpawner::get_spawn_argument(const ObjectID &p_id) const
Node *MultiplayerSpawner::instantiate_scene(int p_id) {
ERR_FAIL_COND_V_MSG(spawn_limit && spawn_limit <= tracked_nodes.size(), nullptr, "Spawn limit reached!");
- ERR_FAIL_INDEX_V(p_id, spawnable_scenes.size(), nullptr);
- Ref<PackedScene> scene = spawnable_scenes[p_id];
+ ERR_FAIL_UNSIGNED_INDEX_V((uint32_t)p_id, spawnable_scenes.size(), nullptr);
+ Ref<PackedScene> scene = spawnable_scenes[p_id].cache;
ERR_FAIL_COND_V(scene.is_null(), nullptr);
return scene->instantiate();
}
diff --git a/scene/multiplayer/multiplayer_spawner.h b/scene/multiplayer/multiplayer_spawner.h
index 63948e39a5..2c0eb9a2f0 100644
--- a/scene/multiplayer/multiplayer_spawner.h
+++ b/scene/multiplayer/multiplayer_spawner.h
@@ -33,6 +33,7 @@
#include "scene/main/node.h"
+#include "core/templates/local_vector.h"
#include "core/variant/typed_array.h"
#include "scene/resources/packed_scene.h"
#include "scene/resources/scene_replication_config.h"
@@ -46,8 +47,14 @@ public:
};
private:
- TypedArray<PackedScene> spawnable_scenes;
- Set<ResourceUID::ID> spawnable_ids;
+ struct SpawnableScene {
+ String path;
+ Ref<PackedScene> cache;
+ };
+
+ LocalVector<SpawnableScene> spawnable_scenes;
+
+ HashSet<ResourceUID::ID> spawnable_ids;
NodePath spawn_path;
struct SpawnInfo {
@@ -62,7 +69,6 @@ private:
ObjectID spawn_node;
HashMap<ObjectID, SpawnInfo> tracked_nodes;
- bool auto_spawn = false;
uint32_t spawn_limit = 0;
void _update_spawn_node();
@@ -71,24 +77,34 @@ private:
void _node_exit(ObjectID p_id);
void _node_ready(ObjectID p_id);
+ Vector<String> _get_spawnable_scenes() const;
+ void _set_spawnable_scenes(const Vector<String> &p_scenes);
+
protected:
static void _bind_methods();
void _notification(int p_what);
+#ifdef TOOLS_ENABLED
+ bool _set(const StringName &p_name, const Variant &p_value);
+ bool _get(const StringName &p_name, Variant &r_ret) const;
+ void _get_property_list(List<PropertyInfo> *p_list) const;
+#endif
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);
+
+ void add_spawnable_scene(const String &p_path);
+ int get_spawnable_scene_count() const;
+ String get_spawnable_scene(int p_idx) const;
+ void clear_spawnable_scenes();
+
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;
+ int find_spawnable_scene_index_from_object(const ObjectID &p_id) const;
+ int find_spawnable_scene_index_from_path(const String &p_path) const;
Node *spawn(const Variant &p_data = Variant());
Node *instantiate_custom(const Variant &p_data);
Node *instantiate_scene(int p_idx);
diff --git a/scene/multiplayer/multiplayer_synchronizer.cpp b/scene/multiplayer/multiplayer_synchronizer.cpp
index 33e845a7a3..e1b7433968 100644
--- a/scene/multiplayer/multiplayer_synchronizer.cpp
+++ b/scene/multiplayer/multiplayer_synchronizer.cpp
@@ -43,6 +43,11 @@ Object *MultiplayerSynchronizer::_get_prop_target(Object *p_obj, const NodePath
}
void MultiplayerSynchronizer::_stop() {
+#ifdef TOOLS_ENABLED
+ if (Engine::get_singleton()->is_editor_hint()) {
+ return;
+ }
+#endif
Node *node = is_inside_tree() ? get_node_or_null(root_path) : nullptr;
if (node) {
get_multiplayer()->replication_stop(node, this);
@@ -50,9 +55,42 @@ void MultiplayerSynchronizer::_stop() {
}
void MultiplayerSynchronizer::_start() {
+#ifdef TOOLS_ENABLED
+ if (Engine::get_singleton()->is_editor_hint()) {
+ return;
+ }
+#endif
Node *node = is_inside_tree() ? get_node_or_null(root_path) : nullptr;
if (node) {
get_multiplayer()->replication_start(node, this);
+ _update_process();
+ }
+}
+
+void MultiplayerSynchronizer::_update_process() {
+#ifdef TOOLS_ENABLED
+ if (Engine::get_singleton()->is_editor_hint()) {
+ return;
+ }
+#endif
+ Node *node = is_inside_tree() ? get_node_or_null(root_path) : nullptr;
+ if (!node) {
+ return;
+ }
+ set_process_internal(false);
+ set_physics_process_internal(false);
+ if (!visibility_filters.size()) {
+ return;
+ }
+ switch (visibility_update_mode) {
+ case VISIBILITY_PROCESS_IDLE:
+ set_process_internal(true);
+ break;
+ case VISIBILITY_PROCESS_PHYSICS:
+ set_physics_process_internal(true);
+ break;
+ case VISIBILITY_PROCESS_NONE:
+ break;
}
}
@@ -85,18 +123,99 @@ Error MultiplayerSynchronizer::set_state(const List<NodePath> &p_properties, Obj
return OK;
}
+bool MultiplayerSynchronizer::is_visibility_public() const {
+ return peer_visibility.has(0);
+}
+
+void MultiplayerSynchronizer::set_visibility_public(bool p_visible) {
+ set_visibility_for(0, p_visible);
+}
+
+bool MultiplayerSynchronizer::is_visible_to(int p_peer) {
+ if (visibility_filters.size()) {
+ Variant arg = p_peer;
+ const Variant *argv[1] = { &arg };
+ for (Callable filter : visibility_filters) {
+ Variant ret;
+ Callable::CallError err;
+ filter.call(argv, 1, ret, err);
+ ERR_FAIL_COND_V(err.error != Callable::CallError::CALL_OK || ret.get_type() != Variant::BOOL, false);
+ if (!ret.operator bool()) {
+ return false;
+ }
+ }
+ }
+ return peer_visibility.has(0) || peer_visibility.has(p_peer);
+}
+
+void MultiplayerSynchronizer::add_visibility_filter(Callable p_callback) {
+ visibility_filters.insert(p_callback);
+ _update_process();
+}
+
+void MultiplayerSynchronizer::remove_visibility_filter(Callable p_callback) {
+ visibility_filters.erase(p_callback);
+ _update_process();
+}
+
+void MultiplayerSynchronizer::set_visibility_for(int p_peer, bool p_visible) {
+ if (peer_visibility.has(p_peer) == p_visible) {
+ return;
+ }
+ if (p_visible) {
+ peer_visibility.insert(p_peer);
+ } else {
+ peer_visibility.erase(p_peer);
+ }
+ update_visibility(p_peer);
+}
+
+bool MultiplayerSynchronizer::get_visibility_for(int p_peer) const {
+ return peer_visibility.has(p_peer);
+}
+
+void MultiplayerSynchronizer::set_visibility_update_mode(VisibilityUpdateMode p_mode) {
+ visibility_update_mode = p_mode;
+ _update_process();
+}
+
+MultiplayerSynchronizer::VisibilityUpdateMode MultiplayerSynchronizer::get_visibility_update_mode() const {
+ return visibility_update_mode;
+}
+
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");
+
+ ClassDB::bind_method(D_METHOD("set_visibility_update_mode", "mode"), &MultiplayerSynchronizer::set_visibility_update_mode);
+ ClassDB::bind_method(D_METHOD("get_visibility_update_mode"), &MultiplayerSynchronizer::get_visibility_update_mode);
+ ClassDB::bind_method(D_METHOD("update_visibility", "for_peer"), &MultiplayerSynchronizer::update_visibility, DEFVAL(0));
+
+ ClassDB::bind_method(D_METHOD("set_visibility_public", "visible"), &MultiplayerSynchronizer::set_visibility_public);
+ ClassDB::bind_method(D_METHOD("is_visibility_public"), &MultiplayerSynchronizer::is_visibility_public);
+
+ ClassDB::bind_method(D_METHOD("add_visibility_filter", "filter"), &MultiplayerSynchronizer::add_visibility_filter);
+ ClassDB::bind_method(D_METHOD("remove_visibility_filter", "filter"), &MultiplayerSynchronizer::remove_visibility_filter);
+ ClassDB::bind_method(D_METHOD("set_visibility_for", "peer", "visible"), &MultiplayerSynchronizer::set_visibility_for);
+ ClassDB::bind_method(D_METHOD("get_visibility_for", "peer"), &MultiplayerSynchronizer::get_visibility_for);
+
+ ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "root_path"), "set_root_path", "get_root_path");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "replication_interval", PROPERTY_HINT_RANGE, "0,5,0.001,suffix:s"), "set_replication_interval", "get_replication_interval");
+ ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "replication_config", PROPERTY_HINT_RESOURCE_TYPE, "SceneReplicationConfig", PROPERTY_USAGE_NO_EDITOR), "set_replication_config", "get_replication_config");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "visibility_update_mode", PROPERTY_HINT_ENUM, "Idle,Physics,None"), "set_visibility_update_mode", "get_visibility_update_mode");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "public_visibility"), "set_visibility_public", "is_visibility_public");
+
+ BIND_ENUM_CONSTANT(VISIBILITY_PROCESS_IDLE);
+ BIND_ENUM_CONSTANT(VISIBILITY_PROCESS_PHYSICS);
+ BIND_ENUM_CONSTANT(VISIBILITY_PROCESS_NONE);
+
+ ADD_SIGNAL(MethodInfo("visibility_changed", PropertyInfo(Variant::INT, "for_peer")));
}
void MultiplayerSynchronizer::_notification(int p_what) {
@@ -117,6 +236,11 @@ void MultiplayerSynchronizer::_notification(int p_what) {
case NOTIFICATION_EXIT_TREE: {
_stop();
} break;
+
+ case NOTIFICATION_INTERNAL_PROCESS:
+ case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
+ update_visibility(0);
+ } break;
}
}
@@ -141,6 +265,18 @@ Ref<SceneReplicationConfig> MultiplayerSynchronizer::get_replication_config() {
return replication_config;
}
+void MultiplayerSynchronizer::update_visibility(int p_for_peer) {
+#ifdef TOOLS_ENABLED
+ if (Engine::get_singleton()->is_editor_hint()) {
+ return;
+ }
+#endif
+ Node *node = is_inside_tree() ? get_node_or_null(root_path) : nullptr;
+ if (node && get_multiplayer()->has_multiplayer_peer() && is_multiplayer_authority()) {
+ emit_signal(SNAME("visibility_changed"), p_for_peer);
+ }
+}
+
void MultiplayerSynchronizer::set_root_path(const NodePath &p_path) {
_stop();
root_path = p_path;
@@ -161,3 +297,8 @@ void MultiplayerSynchronizer::set_multiplayer_authority(int p_peer_id, bool p_re
Node::set_multiplayer_authority(p_peer_id, p_recursive);
get_multiplayer()->replication_start(node, this);
}
+
+MultiplayerSynchronizer::MultiplayerSynchronizer() {
+ // Publicly visible by default.
+ peer_visibility.insert(0);
+}
diff --git a/scene/multiplayer/multiplayer_synchronizer.h b/scene/multiplayer/multiplayer_synchronizer.h
index e856745379..59f02b84c1 100644
--- a/scene/multiplayer/multiplayer_synchronizer.h
+++ b/scene/multiplayer/multiplayer_synchronizer.h
@@ -38,14 +38,25 @@
class MultiplayerSynchronizer : public Node {
GDCLASS(MultiplayerSynchronizer, Node);
+public:
+ enum VisibilityUpdateMode {
+ VISIBILITY_PROCESS_IDLE,
+ VISIBILITY_PROCESS_PHYSICS,
+ VISIBILITY_PROCESS_NONE,
+ };
+
private:
Ref<SceneReplicationConfig> replication_config;
- NodePath root_path;
+ NodePath root_path = NodePath(".."); // Start with parent, like with AnimationPlayer.
uint64_t interval_msec = 0;
+ VisibilityUpdateMode visibility_update_mode = VISIBILITY_PROCESS_IDLE;
+ HashSet<Callable> visibility_filters;
+ HashSet<int> peer_visibility;
static Object *_get_prop_target(Object *p_obj, const NodePath &p_prop);
void _start();
void _stop();
+ void _update_process();
protected:
static void _bind_methods();
@@ -66,7 +77,19 @@ public:
NodePath get_root_path() const;
virtual void set_multiplayer_authority(int p_peer_id, bool p_recursive = true) override;
- MultiplayerSynchronizer() {}
+ bool is_visibility_public() const;
+ void set_visibility_public(bool p_public);
+ bool is_visible_to(int p_peer);
+ void set_visibility_for(int p_peer, bool p_visible);
+ bool get_visibility_for(int p_peer) const;
+ void update_visibility(int p_for_peer);
+ void set_visibility_update_mode(VisibilityUpdateMode p_mode);
+ void add_visibility_filter(Callable p_callback);
+ void remove_visibility_filter(Callable p_callback);
+ VisibilityUpdateMode get_visibility_update_mode() const;
+
+ MultiplayerSynchronizer();
};
+VARIANT_ENUM_CAST(MultiplayerSynchronizer::VisibilityUpdateMode);
#endif // MULTIPLAYER_SYNCHRONIZER_H
diff --git a/scene/multiplayer/scene_cache_interface.cpp b/scene/multiplayer/scene_cache_interface.cpp
index f05dc5a2da..79a7dc2d5a 100644
--- a/scene/multiplayer/scene_cache_interface.cpp
+++ b/scene/multiplayer/scene_cache_interface.cpp
@@ -50,10 +50,8 @@ void SceneCacheInterface::on_peer_change(int p_id, bool p_connected) {
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);
+ for (const KeyValue<NodePath, PathSentCache> &E : path_send_cache) {
+ PathSentCache *psc = path_send_cache.getptr(E.key);
psc->confirmed_peers.erase(p_id);
}
}
@@ -134,9 +132,9 @@ void SceneCacheInterface::process_confirm_path(int p_from, const uint8_t *p_pack
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);
+ HashMap<int, bool>::Iterator 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;
+ E->value = true;
}
Error SceneCacheInterface::_send_confirm_path(Node *p_node, NodePath p_path, PathSentCache *psc, const List<int> &p_peers) {
@@ -184,58 +182,69 @@ Error SceneCacheInterface::_send_confirm_path(Node *p_node, NodePath p_path, Pat
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);
+ HashMap<int, bool>::ConstIterator F = psc->confirmed_peers.find(p_peer);
ERR_FAIL_COND_V(!F, false); // Should never happen.
- return F->get();
+ return F->value;
}
-bool SceneCacheInterface::send_object_cache(Object *p_obj, NodePath p_path, int p_peer_id, int &r_id) {
+int SceneCacheInterface::make_object_cache(Object *p_obj) {
Node *node = Object::cast_to<Node>(p_obj);
- ERR_FAIL_COND_V(!node, false);
+ ERR_FAIL_COND_V(!node, -1);
+ NodePath for_path = multiplayer->get_root_path().rel_path_to(node->get_path());
// See if the path is cached.
- PathSentCache *psc = path_send_cache.getptr(p_path);
+ PathSentCache *psc = path_send_cache.getptr(for_path);
if (!psc) {
// Path is not cached, create.
- path_send_cache[p_path] = PathSentCache();
- psc = path_send_cache.getptr(p_path);
+ path_send_cache[for_path] = PathSentCache();
+ psc = path_send_cache.getptr(for_path);
psc->id = last_send_cache_id++;
}
- r_id = psc->id;
+ return psc->id;
+}
+
+bool SceneCacheInterface::send_object_cache(Object *p_obj, int p_peer_id, int &r_id) {
+ Node *node = Object::cast_to<Node>(p_obj);
+ ERR_FAIL_COND_V(!node, false);
+
+ r_id = make_object_cache(p_obj);
+ ERR_FAIL_COND_V(r_id < 0, false);
+ NodePath for_path = multiplayer->get_root_path().rel_path_to(node->get_path());
+ PathSentCache *psc = path_send_cache.getptr(for_path);
bool has_all_peers = true;
List<int> peers_to_add; // If one is missing, take note to add it.
if (p_peer_id > 0) {
// Fast single peer check.
- Map<int, bool>::Element *F = psc->confirmed_peers.find(p_peer_id);
+ HashMap<int, bool>::Iterator F = psc->confirmed_peers.find(p_peer_id);
if (!F) {
peers_to_add.push_back(p_peer_id); // Need to also be notified.
has_all_peers = false;
- } else if (!F->get()) {
+ } else if (!F->value) {
has_all_peers = false;
}
} else {
// Long and painful.
- for (const Set<int>::Element *E = multiplayer->get_connected_peers().front(); E; E = E->next()) {
- if (p_peer_id < 0 && E->get() == -p_peer_id) {
+ for (const int &E : multiplayer->get_connected_peers()) {
+ if (p_peer_id < 0 && E == -p_peer_id) {
continue; // Continue, excluded.
}
- if (p_peer_id > 0 && E->get() != p_peer_id) {
+ if (p_peer_id > 0 && E != p_peer_id) {
continue; // Continue, not for this peer.
}
- Map<int, bool>::Element *F = psc->confirmed_peers.find(E->get());
+ HashMap<int, bool>::Iterator F = psc->confirmed_peers.find(E);
if (!F) {
- peers_to_add.push_back(E->get()); // Need to also be notified.
+ peers_to_add.push_back(E); // Need to also be notified.
has_all_peers = false;
- } else if (!F->get()) {
+ } else if (!F->value) {
has_all_peers = false;
}
}
}
if (peers_to_add.size()) {
- _send_confirm_path(node, p_path, psc, peers_to_add);
+ _send_confirm_path(node, for_path, psc, peers_to_add);
}
return has_all_peers;
@@ -244,13 +253,13 @@ bool SceneCacheInterface::send_object_cache(Object *p_obj, NodePath p_path, int
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);
+ HashMap<int, PathGetCache>::Iterator 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);
+ HashMap<int, PathGetCache::NodeInfo>::Iterator F = E->value.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();
+ PathGetCache::NodeInfo *ni = &F->value;
Node *node = root_node->get_node(ni->path);
if (!node) {
ERR_PRINT("Failed to get cached path: " + String(ni->path) + ".");
diff --git a/scene/multiplayer/scene_cache_interface.h b/scene/multiplayer/scene_cache_interface.h
index d53cf387fb..6bfd683cf4 100644
--- a/scene/multiplayer/scene_cache_interface.h
+++ b/scene/multiplayer/scene_cache_interface.h
@@ -41,7 +41,7 @@ private:
//path sent caches
struct PathSentCache {
- Map<int, bool> confirmed_peers;
+ HashMap<int, bool> confirmed_peers;
int id;
};
@@ -52,11 +52,11 @@ private:
ObjectID instance;
};
- Map<int, NodeInfo> nodes;
+ HashMap<int, NodeInfo> nodes;
};
HashMap<NodePath, PathSentCache> path_send_cache;
- Map<int, PathGetCache> path_get_cache;
+ HashMap<int, PathGetCache> path_get_cache;
int last_send_cache_id = 1;
protected:
@@ -72,7 +72,8 @@ public:
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 bool send_object_cache(Object *p_obj, int p_target, int &p_id) override;
+ virtual int make_object_cache(Object *p_obj) 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;
diff --git a/scene/multiplayer/scene_replication_interface.cpp b/scene/multiplayer/scene_replication_interface.cpp
index 0764f136e4..c616c5bb85 100644
--- a/scene/multiplayer/scene_replication_interface.cpp
+++ b/scene/multiplayer/scene_replication_interface.cpp
@@ -49,9 +49,8 @@ void SceneReplicationInterface::make_default() {
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));
+ for (const KeyValue<uint32_t, ObjectID> &E : remotes) {
+ Node *node = rep_state->get_node(E.value);
ERR_CONTINUE(!node);
node->queue_delete();
}
@@ -61,14 +60,13 @@ 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);
+ _update_spawn_visibility(p_id, oid);
}
- for (const ObjectID &oid : rep_state->get_path_only_nodes()) {
- Node *node = rep_state->get_node(oid);
+ for (const ObjectID &oid : rep_state->get_synced_nodes()) {
MultiplayerSynchronizer *sync = rep_state->get_synchronizer(oid);
- ERR_CONTINUE(!node || !sync);
+ ERR_CONTINUE(!sync); // ERR_BUG
if (sync->is_multiplayer_authority()) {
- rep_state->peer_add_node(p_id, oid);
+ _update_sync_visibility(p_id, oid);
}
}
} else {
@@ -98,7 +96,13 @@ Error SceneReplicationInterface::on_spawn(Object *p_obj, Variant p_config) {
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);
+ const ObjectID oid = node->get_instance_id();
+ if (multiplayer->has_multiplayer_peer() && spawner->is_multiplayer_authority()) {
+ rep_state->ensure_net_id(oid);
+ _update_spawn_visibility(0, oid);
+ }
+ ERR_FAIL_COND_V(err != OK, err);
+ return OK;
}
Error SceneReplicationInterface::on_despawn(Object *p_obj, Variant p_config) {
@@ -106,9 +110,19 @@ Error SceneReplicationInterface::on_despawn(Object *p_obj, Variant p_config) {
ERR_FAIL_COND_V(!node || p_config.get_type() != Variant::OBJECT, ERR_INVALID_PARAMETER);
MultiplayerSpawner *spawner = Object::cast_to<MultiplayerSpawner>(p_config.get_validated_object());
ERR_FAIL_COND_V(!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);
+ // Forcibly despawn to all peers that knowns me.
+ int len = 0;
+ Error err = _make_despawn_packet(node, len);
+ ERR_FAIL_COND_V(err != OK, ERR_BUG);
+ const ObjectID oid = p_obj->get_instance_id();
+ for (int pid : rep_state->get_peers()) {
+ if (!rep_state->is_peer_spawn(pid, oid)) {
+ continue;
+ }
+ _send_raw(packet_cache.ptr(), len, pid, true);
+ }
+ // Also remove spawner tracking from the replication state.
+ return rep_state->config_del_spawn(node, spawner);
}
Error SceneReplicationInterface::on_replication_start(Object *p_obj, Variant p_config) {
@@ -116,7 +130,15 @@ Error SceneReplicationInterface::on_replication_start(Object *p_obj, Variant p_c
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);
+
+ // Add to synchronizer list and setup visibility.
rep_state->config_add_sync(node, sync);
+ const ObjectID oid = node->get_instance_id();
+ sync->connect("visibility_changed", callable_mp(this, &SceneReplicationInterface::_visibility_changed), varray(oid));
+ if (multiplayer->has_multiplayer_peer() && sync->is_multiplayer_authority()) {
+ _update_sync_visibility(0, oid);
+ }
+
// Try to apply initial state if spawning (hack to apply if before ready).
if (pending_spawn == p_obj->get_instance_id()) {
pending_spawn = ObjectID(); // Make sure this only happens once.
@@ -128,9 +150,6 @@ Error SceneReplicationInterface::on_replication_start(Object *p_obj, Variant p_c
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;
}
@@ -139,10 +158,103 @@ Error SceneReplicationInterface::on_replication_stop(Object *p_obj, Variant p_co
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);
+ ERR_FAIL_COND_V(!sync, ERR_INVALID_PARAMETER);
+ sync->disconnect("visibility_changed", callable_mp(this, &SceneReplicationInterface::_visibility_changed));
return rep_state->config_del_sync(node, sync);
}
+void SceneReplicationInterface::_visibility_changed(int p_peer, ObjectID p_oid) {
+ if (rep_state->is_spawned_node(p_oid)) {
+ _update_spawn_visibility(p_peer, p_oid);
+ }
+ if (rep_state->is_synced_node(p_oid)) {
+ _update_sync_visibility(p_peer, p_oid);
+ }
+}
+
+Error SceneReplicationInterface::_update_sync_visibility(int p_peer, const ObjectID &p_oid) {
+ MultiplayerSynchronizer *sync = rep_state->get_synchronizer(p_oid);
+ ERR_FAIL_COND_V(!sync || !sync->is_multiplayer_authority(), ERR_BUG);
+ bool is_visible = sync->is_visible_to(p_peer);
+ if (p_peer == 0) {
+ for (int pid : rep_state->get_peers()) {
+ // Might be visible to this specific peer.
+ is_visible = is_visible || sync->is_visible_to(pid);
+ if (rep_state->is_peer_sync(pid, p_oid) == is_visible) {
+ continue;
+ }
+ if (is_visible) {
+ rep_state->peer_add_sync(pid, p_oid);
+ } else {
+ rep_state->peer_del_sync(pid, p_oid);
+ }
+ }
+ return OK;
+ } else {
+ if (is_visible == rep_state->is_peer_sync(p_peer, p_oid)) {
+ return OK;
+ }
+ if (is_visible) {
+ return rep_state->peer_add_sync(p_peer, p_oid);
+ } else {
+ return rep_state->peer_del_sync(p_peer, p_oid);
+ }
+ }
+}
+
+Error SceneReplicationInterface::_update_spawn_visibility(int p_peer, const ObjectID &p_oid) {
+ MultiplayerSpawner *spawner = rep_state->get_spawner(p_oid);
+ MultiplayerSynchronizer *sync = rep_state->get_synchronizer(p_oid);
+ Node *node = Object::cast_to<Node>(ObjectDB::get_instance(p_oid));
+ ERR_FAIL_COND_V(!node || !spawner || !spawner->is_multiplayer_authority(), ERR_BUG);
+ bool is_visible = !sync || sync->is_visible_to(p_peer);
+ // Spawn (and despawn) when needed.
+ HashSet<int> to_spawn;
+ HashSet<int> to_despawn;
+ if (p_peer) {
+ if (is_visible == rep_state->is_peer_spawn(p_peer, p_oid)) {
+ return OK;
+ }
+ if (is_visible) {
+ to_spawn.insert(p_peer);
+ } else {
+ to_despawn.insert(p_peer);
+ }
+ } else {
+ // Check visibility for each peers.
+ for (int pid : rep_state->get_peers()) {
+ bool peer_visible = is_visible || sync->is_visible_to(pid);
+ if (peer_visible == rep_state->is_peer_spawn(pid, p_oid)) {
+ continue;
+ }
+ if (peer_visible) {
+ to_spawn.insert(pid);
+ } else {
+ to_despawn.insert(pid);
+ }
+ }
+ }
+ if (to_spawn.size()) {
+ int len = 0;
+ _make_spawn_packet(node, len);
+ for (int pid : to_spawn) {
+ int path_id;
+ multiplayer->send_object_cache(spawner, pid, path_id);
+ _send_raw(packet_cache.ptr(), len, pid, true);
+ rep_state->peer_add_spawn(pid, p_oid);
+ }
+ }
+ if (to_despawn.size()) {
+ int len = 0;
+ _make_despawn_packet(node, len);
+ for (int pid : to_despawn) {
+ rep_state->peer_del_spawn(pid, p_oid);
+ _send_raw(packet_cache.ptr(), len, pid, true);
+ }
+ }
+ return OK;
+}
+
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);
@@ -159,18 +271,20 @@ Error SceneReplicationInterface::_send_raw(const uint8_t *p_buffer, int p_size,
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);
+Error SceneReplicationInterface::_make_spawn_packet(Node *p_node, int &r_len) {
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);
+ MultiplayerSpawner *spawner = rep_state->get_spawner(oid);
+ ERR_FAIL_COND_V(!spawner || !p_node, ERR_BUG);
+
+ uint32_t nid = rep_state->get_net_id(oid);
+ ERR_FAIL_COND_V(!nid, ERR_UNCONFIGURED);
// Prepare custom arg and scene_id
- uint8_t scene_id = p_spawner->get_spawn_id(oid);
+ uint8_t scene_id = spawner->find_spawnable_scene_index_from_object(oid);
bool is_custom = scene_id == MultiplayerSpawner::INVALID_ID;
- Variant spawn_arg = p_spawner->get_spawn_argument(oid);
+ Variant spawn_arg = 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);
@@ -182,7 +296,8 @@ Error SceneReplicationInterface::_send_spawn(Node *p_node, MultiplayerSpawner *p
Vector<Variant> state_vars;
Vector<const Variant *> state_varp;
MultiplayerSynchronizer *synchronizer = rep_state->get_synchronizer(oid);
- if (synchronizer && synchronizer->get_replication_config().is_valid()) {
+ if (synchronizer) {
+ ERR_FAIL_COND_V(synchronizer->get_replication_config().is_null(), ERR_BUG);
const List<NodePath> props = synchronizer->get_replication_config()->get_spawn_properties();
Error err = MultiplayerSynchronizer::get_state(props, p_node, state_vars, state_varp);
ERR_FAIL_COND_V_MSG(err != OK, err, "Unable to retrieve spawn state.");
@@ -190,13 +305,8 @@ Error SceneReplicationInterface::_send_spawn(Node *p_node, MultiplayerSpawner *p
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.
+ // Encode scene ID, path ID, net ID, node name.
+ int path_id = multiplayer->make_object_cache(spawner);
CharString cname = p_node->get_name().operator String().utf8();
int nlen = encode_cstring(cname.get_data(), nullptr);
MAKE_ROOM(1 + 1 + 4 + 4 + 4 + nlen + (is_custom ? 4 + spawn_arg_size : 0) + state_size);
@@ -221,12 +331,11 @@ Error SceneReplicationInterface::_send_spawn(Node *p_node, MultiplayerSpawner *p
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);
+ r_len = ofs;
+ return OK;
}
-Error SceneReplicationInterface::_send_despawn(Node *p_node, int p_peer) {
+Error SceneReplicationInterface::_make_despawn_packet(Node *p_node, int &r_len) {
const ObjectID oid = p_node->get_instance_id();
MAKE_ROOM(5);
uint8_t *ptr = packet_cache.ptrw();
@@ -234,9 +343,8 @@ Error SceneReplicationInterface::_send_despawn(Node *p_node, int p_peer) {
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);
+ r_len = ofs;
+ return OK;
}
Error SceneReplicationInterface::on_spawn_receive(int p_from, const uint8_t *p_buffer, int p_buffer_len) {
@@ -309,13 +417,16 @@ Error SceneReplicationInterface::on_despawn_receive(int p_from, const uint8_t *p
Error err = rep_state->peer_del_remote(p_from, net_id, &node);
ERR_FAIL_COND_V(err != OK, err);
ERR_FAIL_COND_V(!node, ERR_BUG);
+ if (node->get_parent() != nullptr) {
+ node->get_parent()->remove_child(node);
+ }
node->queue_delete();
return OK;
}
void SceneReplicationInterface::_send_sync(int p_peer, uint64_t p_msec) {
- const Set<ObjectID> &known = rep_state->get_known_nodes(p_peer);
- if (known.is_empty()) {
+ const HashSet<ObjectID> &to_sync = rep_state->get_peer_sync_nodes(p_peer);
+ if (to_sync.is_empty()) {
return;
}
MAKE_ROOM(sync_mtu);
@@ -325,14 +436,29 @@ void SceneReplicationInterface::_send_sync(int p_peer, uint64_t p_msec) {
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) {
+ for (const ObjectID &oid : to_sync) {
if (!rep_state->update_sync_time(oid, p_msec)) {
continue; // nothing to sync.
}
MultiplayerSynchronizer *sync = rep_state->get_synchronizer(oid);
- ERR_CONTINUE(!sync);
+ ERR_CONTINUE(!sync || !sync->get_replication_config().is_valid());
Node *node = rep_state->get_node(oid);
ERR_CONTINUE(!node);
+ uint32_t net_id = rep_state->get_net_id(oid);
+ if (net_id == 0 || (net_id & 0x80000000)) {
+ int path_id = 0;
+ bool verified = multiplayer->send_object_cache(sync, p_peer, path_id);
+ ERR_CONTINUE_MSG(path_id < 0, "This should never happen!");
+ if (net_id == 0) {
+ // First time path based ID.
+ net_id = path_id | 0x80000000;
+ rep_state->set_net_id(oid, net_id | 0x80000000);
+ }
+ if (!verified) {
+ // The path based sync is not yet confirmed, skipping.
+ continue;
+ }
+ }
int size;
Vector<Variant> vars;
Vector<const Variant *> varp;
@@ -349,16 +475,6 @@ void SceneReplicationInterface::_send_sync(int p_peer, uint64_t p_msec) {
ofs = 3;
}
if (size) {
- uint32_t net_id = rep_state->get_net_id(oid);
- if (net_id == 0 || (net_id & 0x80000000)) {
- // First time path based ID.
- NodePath rel_path = multiplayer->get_root_path().rel_path_to(sync->get_path());
- int path_id = 0;
- multiplayer->send_object_cache(sync, rel_path, p_peer, path_id);
- ERR_CONTINUE_MSG(net_id && net_id != (uint32_t(path_id) | 0x80000000), "This should never happen!");
- net_id = path_id;
- rep_state->set_net_id(oid, net_id | 0x80000000);
- }
ofs += encode_uint32(rep_state->get_net_id(oid), &ptr[ofs]);
ofs += encode_uint32(size, &ptr[ofs]);
MultiplayerAPI::encode_and_compress_variants(varp.ptrw(), varp.size(), &ptr[ofs], size);
diff --git a/scene/multiplayer/scene_replication_interface.h b/scene/multiplayer/scene_replication_interface.h
index 60ac95c93c..ad3a3be979 100644
--- a/scene/multiplayer/scene_replication_interface.h
+++ b/scene/multiplayer/scene_replication_interface.h
@@ -40,10 +40,13 @@ class SceneReplicationInterface : public 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 _make_spawn_packet(Node *p_node, int &r_len);
+ Error _make_despawn_packet(Node *p_node, int &r_len);
Error _send_raw(const uint8_t *p_buffer, int p_size, int p_peer, bool p_reliable);
+ void _visibility_changed(int p_peer, ObjectID p_oid);
+ Error _update_sync_visibility(int p_peer, const ObjectID &p_oid);
+ Error _update_spawn_visibility(int p_peer, const ObjectID &p_oid);
void _free_remotes(int p_peer);
Ref<SceneReplicationState> rep_state;
diff --git a/scene/multiplayer/scene_replication_state.cpp b/scene/multiplayer/scene_replication_state.cpp
index b8dadeff24..f6a51ff9c7 100644
--- a/scene/multiplayer/scene_replication_state.cpp
+++ b/scene/multiplayer/scene_replication_state.cpp
@@ -55,9 +55,9 @@ void SceneReplicationState::_untrack(const ObjectID &p_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);
+ for (KeyValue<int, PeerInfo> &E : peers_info) {
+ E.value.sync_nodes.erase(p_id);
+ E.value.spawn_nodes.erase(p_id);
}
}
}
@@ -94,11 +94,6 @@ bool SceneReplicationState::update_sync_time(const ObjectID &p_id, uint64_t p_ms
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);
@@ -134,9 +129,8 @@ 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];
+ for (KeyValue<ObjectID, TrackedNode> &E : tracked_nodes) {
+ TrackedNode &tobj = E.value;
tobj.net_id = 0;
tobj.remote_peer = 0;
tobj.last_sync = 0;
@@ -149,8 +143,6 @@ Error SceneReplicationState::config_add_spawn(Node *p_node, MultiplayerSpawner *
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;
}
@@ -161,6 +153,9 @@ Error SceneReplicationState::config_del_spawn(Node *p_node, MultiplayerSpawner *
ERR_FAIL_COND_V(tobj.spawner != p_spawner->get_instance_id(), ERR_INVALID_PARAMETER);
tobj.spawner = ObjectID();
spawned_nodes.erase(oid);
+ for (KeyValue<int, PeerInfo> &E : peers_info) {
+ E.value.spawn_nodes.erase(oid);
+ }
return OK;
}
@@ -169,10 +164,7 @@ Error SceneReplicationState::config_add_sync(Node *p_node, MultiplayerSynchroniz
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);
- }
+ synced_nodes.insert(oid);
return OK;
}
@@ -182,40 +174,57 @@ Error SceneReplicationState::config_del_sync(Node *p_node, MultiplayerSynchroniz
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);
+ synced_nodes.erase(oid);
+ for (KeyValue<int, PeerInfo> &E : peers_info) {
+ E.value.sync_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);
- }
- }
+Error SceneReplicationState::peer_add_sync(int p_peer, const ObjectID &p_id) {
+ ERR_FAIL_COND_V(!peers_info.has(p_peer), ERR_INVALID_PARAMETER);
+ peers_info[p_peer].sync_nodes.insert(p_id);
return OK;
}
-Error SceneReplicationState::peer_del_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);
- }
- }
+Error SceneReplicationState::peer_del_sync(int p_peer, const ObjectID &p_id) {
+ ERR_FAIL_COND_V(!peers_info.has(p_peer), ERR_INVALID_PARAMETER);
+ peers_info[p_peer].sync_nodes.erase(p_id);
+ return OK;
+}
+
+const HashSet<ObjectID> SceneReplicationState::get_peer_sync_nodes(int p_peer) {
+ ERR_FAIL_COND_V(!peers_info.has(p_peer), HashSet<ObjectID>());
+ return peers_info[p_peer].sync_nodes;
+}
+
+bool SceneReplicationState::is_peer_sync(int p_peer, const ObjectID &p_id) const {
+ ERR_FAIL_COND_V(!peers_info.has(p_peer), false);
+ return peers_info[p_peer].sync_nodes.has(p_id);
+}
+
+Error SceneReplicationState::peer_add_spawn(int p_peer, const ObjectID &p_id) {
+ ERR_FAIL_COND_V(!peers_info.has(p_peer), ERR_INVALID_PARAMETER);
+ peers_info[p_peer].spawn_nodes.insert(p_id);
+ return OK;
+}
+
+Error SceneReplicationState::peer_del_spawn(int p_peer, const ObjectID &p_id) {
+ ERR_FAIL_COND_V(!peers_info.has(p_peer), ERR_INVALID_PARAMETER);
+ peers_info[p_peer].spawn_nodes.erase(p_id);
return OK;
}
+const HashSet<ObjectID> SceneReplicationState::get_peer_spawn_nodes(int p_peer) {
+ ERR_FAIL_COND_V(!peers_info.has(p_peer), HashSet<ObjectID>());
+ return peers_info[p_peer].spawn_nodes;
+}
+
+bool SceneReplicationState::is_peer_spawn(int p_peer, const ObjectID &p_id) const {
+ ERR_FAIL_COND_V(!peers_info.has(p_peer), false);
+ return peers_info[p_peer].spawn_nodes.has(p_id);
+}
+
Node *SceneReplicationState::peer_get_remote(int p_peer, uint32_t p_net_id) {
PeerInfo *info = peers_info.getptr(p_peer);
return info && info->recv_nodes.has(p_net_id) ? Object::cast_to<Node>(ObjectDB::get_instance(info->recv_nodes[p_net_id])) : nullptr;
diff --git a/scene/multiplayer/scene_replication_state.h b/scene/multiplayer/scene_replication_state.h
index 18e4d9fa39..7973b5c904 100644
--- a/scene/multiplayer/scene_replication_state.h
+++ b/scene/multiplayer/scene_replication_state.h
@@ -62,27 +62,30 @@ private:
};
struct PeerInfo {
- Set<ObjectID> known_nodes;
+ HashSet<ObjectID> sync_nodes;
+ HashSet<ObjectID> spawn_nodes;
HashMap<uint32_t, ObjectID> recv_nodes;
uint16_t last_sent_sync = 0;
uint16_t last_recv_sync = 0;
};
- Set<int> known_peers;
+ HashSet<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;
+ HashSet<ObjectID> spawned_nodes;
+ HashSet<ObjectID> synced_nodes;
TrackedNode &_track(const ObjectID &p_id);
void _untrack(const ObjectID &p_id);
bool is_tracked(const ObjectID &p_id) const { return tracked_nodes.has(p_id); }
public:
- const 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; }
+ const HashSet<int> get_peers() const { return known_peers; }
+ const HashSet<ObjectID> &get_spawned_nodes() const { return spawned_nodes; }
+ bool is_spawned_node(const ObjectID &p_id) const { return spawned_nodes.has(p_id); }
+ const HashSet<ObjectID> &get_synced_nodes() const { return synced_nodes; }
+ bool is_synced_node(const ObjectID &p_id) const { return synced_nodes.has(p_id); }
MultiplayerSynchronizer *get_synchronizer(const ObjectID &p_id) { return tracked_nodes.has(p_id) ? tracked_nodes[p_id].get_synchronizer() : nullptr; }
MultiplayerSpawner *get_spawner(const ObjectID &p_id) { return tracked_nodes.has(p_id) ? tracked_nodes[p_id].get_spawner() : nullptr; }
@@ -90,7 +93,6 @@ public:
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);
@@ -104,8 +106,17 @@ public:
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);
+ Error peer_add_sync(int p_peer, const ObjectID &p_id);
+ Error peer_del_sync(int p_peer, const ObjectID &p_id);
+
+ const HashSet<ObjectID> get_peer_sync_nodes(int p_peer);
+ bool is_peer_sync(int p_peer, const ObjectID &p_id) const;
+
+ Error peer_add_spawn(int p_peer, const ObjectID &p_id);
+ Error peer_del_spawn(int p_peer, const ObjectID &p_id);
+
+ const HashSet<ObjectID> get_peer_spawn_nodes(int p_peer);
+ bool is_peer_spawn(int p_peer, const ObjectID &p_id) const;
const HashMap<uint32_t, ObjectID> peer_get_remotes(int p_peer) const;
Node *peer_get_remote(int p_peer, uint32_t p_net_id);
diff --git a/scene/multiplayer/scene_rpc_interface.cpp b/scene/multiplayer/scene_rpc_interface.cpp
index 84700a82f3..144a10c665 100644
--- a/scene/multiplayer/scene_rpc_interface.cpp
+++ b/scene/multiplayer/scene_rpc_interface.cpp
@@ -302,12 +302,9 @@ void SceneRPCInterface::_send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, con
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);
+ const bool has_all_peers = multiplayer->send_object_cache(p_from, p_to, psc_id);
// Create base packet, lots of hardcode because it must be tight.
@@ -414,6 +411,7 @@ void SceneRPCInterface::_send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, con
// Not all verified path, so send one by one.
// Append path at the end, since we will need it for some packets.
+ NodePath from_path = multiplayer->get_root_path().rel_path_to(p_from->get_path());
CharString pname = String(from_path).utf8();
int path_len = encode_cstring(pname.get_data(), nullptr);
MAKE_ROOM(ofs + path_len);