Skip to content

Commit

Permalink
Add peer visibility to MultiplayerSynchronizer.
Browse files Browse the repository at this point in the history
MultiplayerSynchronizers can now be configured to limit their visibility
to a subset of the connected peers, if the synchronized node was spawned
by a MultiplayerSpawner (either automatically or via custom spawn) the
given node will also be despawned remotely.

The replication system doesn't have the logic to handle subspawn
directly, but it is possible to handle them appropriately by manually
updating the visibility of the parent before changing the one of the
nested spawns via the "update_visibility" function.

The visibility of each MultiplayerSynchronizer can be controlled by
adding or remove filters via "[add|remove]_visibility_filter(callable)".

To further optimize the network code, visibility filters can be configured
to be automatically updated during idle or physics frame, or set to always
require manual update (via the "update_visibility" function).
  • Loading branch information
Faless committed Jul 15, 2022
1 parent 9904a9d commit 326f40b
Show file tree
Hide file tree
Showing 15 changed files with 482 additions and 128 deletions.
8 changes: 6 additions & 2 deletions core/multiplayer/multiplayer_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
6 changes: 4 additions & 2 deletions core/multiplayer/multiplayer_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@ class MultiplayerCacheInterface : public RefCounted {
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 &p_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; }

Expand Down Expand Up @@ -160,7 +161,8 @@ class MultiplayerAPI : public RefCounted {
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 &p_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);

Expand Down
2 changes: 0 additions & 2 deletions doc/classes/MultiplayerSpawner.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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;)">
Expand Down
52 changes: 52 additions & 0 deletions doc/classes/MultiplayerSynchronizer.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
17 changes: 2 additions & 15 deletions scene/multiplayer/multiplayer_spawner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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")));
Expand All @@ -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 {
Expand Down Expand Up @@ -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;
}
Expand Down
3 changes: 0 additions & 3 deletions scene/multiplayer/multiplayer_spawner.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ class MultiplayerSpawner : public Node {

ObjectID spawn_node;
HashMap<ObjectID, SpawnInfo> tracked_nodes;
bool auto_spawn = false;
uint32_t spawn_limit = 0;

void _update_spawn_node();
Expand Down Expand Up @@ -102,8 +101,6 @@ class MultiplayerSpawner : public Node {
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;
Expand Down
142 changes: 141 additions & 1 deletion scene/multiplayer/multiplayer_synchronizer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,54 @@ 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);
}
}

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

Expand Down Expand Up @@ -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);
Expand All @@ -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) {
Expand All @@ -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;
}
}

Expand All @@ -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;
Expand All @@ -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);
}
Loading

0 comments on commit 326f40b

Please sign in to comment.