Merge pull request #62961 from Faless/mp/4.x_interest

Add peer visibility to MultiplayerSynchronizer.
This commit is contained in:
Fabio Alessandrelli 2022-07-20 21:18:58 +02:00 committed by GitHub
commit 1cf7ebda50
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 482 additions and 128 deletions

View file

@ -463,8 +463,12 @@ bool MultiplayerAPI::is_cache_confirmed(NodePath p_path, int p_peer) {
return cache->is_cache_confirmed(p_path, p_peer);
}
bool MultiplayerAPI::send_object_cache(Object *p_obj, NodePath p_path, int p_peer_id, int &r_id) {
return cache->send_object_cache(p_obj, p_path, p_peer_id, r_id);
bool MultiplayerAPI::send_object_cache(Object *p_obj, int p_peer_id, int &r_id) {
return cache->send_object_cache(p_obj, p_peer_id, r_id);
}
int MultiplayerAPI::make_object_cache(Object *p_obj) {
return cache->make_object_cache(p_obj);
}
Object *MultiplayerAPI::get_cached_object(int p_from, uint32_t p_cache_id) {

View file

@ -77,7 +77,8 @@ public:
virtual void process_confirm_path(int p_from, const uint8_t *p_packet, int p_packet_len) {}
// 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) { return false; }
virtual bool send_object_cache(Object *p_obj, int p_target, int &r_id) { return false; }
virtual int make_object_cache(Object *p_obj) { return false; }
virtual Object *get_cached_object(int p_from, uint32_t p_cache_id) { return nullptr; }
virtual bool is_cache_confirmed(NodePath p_path, int p_peer) { return false; }
@ -160,7 +161,8 @@ public:
Error replication_start(Object *p_object, Variant p_config);
Error replication_stop(Object *p_object, Variant p_config);
// Cache API
bool send_object_cache(Object *p_obj, NodePath p_path, int p_target, int &p_id);
bool send_object_cache(Object *p_obj, int p_target, int &r_id);
int make_object_cache(Object *p_obj);
Object *get_cached_object(int p_from, uint32_t p_cache_id);
bool is_cache_confirmed(NodePath p_path, int p_peer);

View file

@ -43,8 +43,6 @@
</method>
</methods>
<members>
<member name="auto_spawn" type="bool" setter="set_auto_spawning" getter="is_auto_spawning" default="false">
</member>
<member name="spawn_limit" type="int" setter="set_spawn_limit" getter="get_spawn_limit" default="0">
</member>
<member name="spawn_path" type="NodePath" setter="set_spawn_path" getter="get_spawn_path" default="NodePath(&quot;&quot;)">

View file

@ -6,12 +6,64 @@
</description>
<tutorials>
</tutorials>
<methods>
<method name="add_visibility_filter">
<return type="void" />
<argument index="0" name="filter" type="Callable" />
<description>
</description>
</method>
<method name="get_visibility_for" qualifiers="const">
<return type="bool" />
<argument index="0" name="peer" type="int" />
<description>
</description>
</method>
<method name="remove_visibility_filter">
<return type="void" />
<argument index="0" name="filter" type="Callable" />
<description>
</description>
</method>
<method name="set_visibility_for">
<return type="void" />
<argument index="0" name="peer" type="int" />
<argument index="1" name="visible" type="bool" />
<description>
</description>
</method>
<method name="update_visibility">
<return type="void" />
<argument index="0" name="for_peer" type="int" default="0" />
<description>
</description>
</method>
</methods>
<members>
<member name="public_visibility" type="bool" setter="set_visibility_public" getter="is_visibility_public" default="true">
</member>
<member name="replication_config" type="SceneReplicationConfig" setter="set_replication_config" getter="get_replication_config">
</member>
<member name="replication_interval" type="float" setter="set_replication_interval" getter="get_replication_interval" default="0.0">
</member>
<member name="root_path" type="NodePath" setter="set_root_path" getter="get_root_path" default="NodePath(&quot;..&quot;)">
</member>
<member name="visibility_update_mode" type="int" setter="set_visibility_update_mode" getter="get_visibility_update_mode" enum="MultiplayerSynchronizer.VisibilityUpdateMode" default="0">
</member>
</members>
<signals>
<signal name="visibility_changed">
<argument index="0" name="for_peer" type="int" />
<description>
</description>
</signal>
</signals>
<constants>
<constant name="VISIBILITY_PROCESS_IDLE" value="0" enum="VisibilityUpdateMode">
</constant>
<constant name="VISIBILITY_PROCESS_PHYSICS" value="1" enum="VisibilityUpdateMode">
</constant>
<constant name="VISIBILITY_PROCESS_NONE" value="2" enum="VisibilityUpdateMode">
</constant>
</constants>
</class>

View file

@ -71,7 +71,7 @@ bool MultiplayerSpawner::_get(const StringName &p_name, Variant &r_ret) const {
}
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, "Scenes,scenes/"));
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;
@ -144,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")));
@ -169,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 {
@ -221,15 +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;
}
NodePath MultiplayerSpawner::get_spawn_path() const {
return spawn_path;
}

View file

@ -69,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();
@ -102,8 +101,6 @@ public:
void set_spawn_path(const NodePath &p_path);
uint32_t get_spawn_limit() const { return spawn_limit; }
void set_spawn_limit(uint32_t p_limit) { spawn_limit = p_limit; }
bool is_auto_spawning() const;
void set_auto_spawning(bool p_enabled);
const Variant get_spawn_argument(const ObjectID &p_id) const;
int find_spawnable_scene_index_from_object(const ObjectID &p_id) const;

View file

@ -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,6 +123,66 @@ 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);
@ -95,9 +193,29 @@ void MultiplayerSynchronizer::_bind_methods() {
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);
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"), "set_replication_config", "get_replication_config");
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) {
@ -118,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;
}
}
@ -142,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;
@ -162,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);
}

View file

@ -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(".."); // 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

View file

@ -187,18 +187,29 @@ bool SceneCacheInterface::is_cache_confirmed(NodePath p_path, int p_peer) {
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.
@ -233,7 +244,7 @@ bool SceneCacheInterface::send_object_cache(Object *p_obj, NodePath p_path, int
}
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;

View file

@ -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;

View file

@ -60,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 {
@ -97,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) {
@ -105,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) {
@ -115,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.
@ -127,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;
}
@ -138,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);
@ -158,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->find_spawnable_scene_index_from_object(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);
@ -181,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.");
@ -189,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);
@ -220,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();
@ -233,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) {
@ -316,8 +425,8 @@ Error SceneReplicationInterface::on_despawn_receive(int p_from, const uint8_t *p
}
void SceneReplicationInterface::_send_sync(int p_peer, uint64_t p_msec) {
const HashSet<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);
@ -327,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;
@ -351,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);

View file

@ -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;

View file

@ -56,7 +56,8 @@ 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) {
for (KeyValue<int, PeerInfo> &E : peers_info) {
E.value.known_nodes.erase(p_id);
E.value.sync_nodes.erase(p_id);
E.value.spawn_nodes.erase(p_id);
}
}
}
@ -93,11 +94,6 @@ bool SceneReplicationState::update_sync_time(const ObjectID &p_id, uint64_t p_ms
return false;
}
const HashSet<ObjectID> SceneReplicationState::get_known_nodes(int p_peer) {
ERR_FAIL_COND_V(!peers_info.has(p_peer), HashSet<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);
@ -147,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;
}
@ -159,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;
}
@ -167,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;
}
@ -180,38 +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) {
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].known_nodes.insert(p_id);
} else {
for (KeyValue<int, PeerInfo> &E : peers_info) {
E.value.known_nodes.insert(p_id);
}
}
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) {
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].known_nodes.erase(p_id);
} else {
for (KeyValue<int, PeerInfo> &E : peers_info) {
E.value.known_nodes.erase(p_id);
}
}
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;

View file

@ -62,7 +62,8 @@ private:
};
struct PeerInfo {
HashSet<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;
@ -73,7 +74,7 @@ private:
HashMap<ObjectID, TrackedNode> tracked_nodes;
HashMap<int, PeerInfo> peers_info;
HashSet<ObjectID> spawned_nodes;
HashSet<ObjectID> path_only_nodes;
HashSet<ObjectID> synced_nodes;
TrackedNode &_track(const ObjectID &p_id);
void _untrack(const ObjectID &p_id);
@ -82,7 +83,9 @@ private:
public:
const HashSet<int> get_peers() const { return known_peers; }
const HashSet<ObjectID> &get_spawned_nodes() const { return spawned_nodes; }
const HashSet<ObjectID> &get_path_only_nodes() const { return path_only_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 HashSet<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);

View file

@ -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);