diff options
-rw-r--r-- | doc/classes/OfflineMultiplayerPeer.xml | 12 | ||||
-rw-r--r-- | modules/multiplayer/register_types.cpp | 1 | ||||
-rw-r--r-- | modules/multiplayer/scene_multiplayer.cpp | 1 | ||||
-rw-r--r-- | modules/multiplayer/scene_multiplayer.h | 26 | ||||
-rw-r--r-- | modules/multiplayer/scene_replication_interface.cpp | 43 | ||||
-rw-r--r-- | modules/multiplayer/scene_replication_interface.h | 2 | ||||
-rw-r--r-- | modules/multiplayer/scene_rpc_interface.cpp | 54 |
7 files changed, 118 insertions, 21 deletions
diff --git a/doc/classes/OfflineMultiplayerPeer.xml b/doc/classes/OfflineMultiplayerPeer.xml new file mode 100644 index 0000000000..5e15992d54 --- /dev/null +++ b/doc/classes/OfflineMultiplayerPeer.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="OfflineMultiplayerPeer" inherits="MultiplayerPeer" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> + <brief_description> + A [MultiplayerPeer] which is always connected and acts as a server. + </brief_description> + <description> + This is the default [member MultiplayerAPI.multiplayer_peer] for the [member Node.multiplayer]. It mimics the behavior of a server with no peers connected. + This means that the [SceneTree] will act as the multiplayer authority by default. Calls to [method MultiplayerAPI.is_server] will return [code]true[/code], and calls to [method MultiplayerAPI.get_unique_id] will return [constant MultiplayerPeer.TARGET_PEER_SERVER]. + </description> + <tutorials> + </tutorials> +</class> diff --git a/modules/multiplayer/register_types.cpp b/modules/multiplayer/register_types.cpp index 2bf1041029..ca85a46f31 100644 --- a/modules/multiplayer/register_types.cpp +++ b/modules/multiplayer/register_types.cpp @@ -47,6 +47,7 @@ void initialize_multiplayer_module(ModuleInitializationLevel p_level) { GDREGISTER_CLASS(SceneReplicationConfig); GDREGISTER_CLASS(MultiplayerSpawner); GDREGISTER_CLASS(MultiplayerSynchronizer); + GDREGISTER_CLASS(OfflineMultiplayerPeer); GDREGISTER_CLASS(SceneMultiplayer); MultiplayerAPI::set_default_interface("SceneMultiplayer"); MultiplayerDebugger::initialize(); diff --git a/modules/multiplayer/scene_multiplayer.cpp b/modules/multiplayer/scene_multiplayer.cpp index e0a21fca9a..5042a0502d 100644 --- a/modules/multiplayer/scene_multiplayer.cpp +++ b/modules/multiplayer/scene_multiplayer.cpp @@ -661,6 +661,7 @@ SceneMultiplayer::SceneMultiplayer() { replicator = Ref<SceneReplicationInterface>(memnew(SceneReplicationInterface(this))); rpc = Ref<SceneRPCInterface>(memnew(SceneRPCInterface(this))); cache = Ref<SceneCacheInterface>(memnew(SceneCacheInterface(this))); + set_multiplayer_peer(Ref<OfflineMultiplayerPeer>(memnew(OfflineMultiplayerPeer))); } SceneMultiplayer::~SceneMultiplayer() { diff --git a/modules/multiplayer/scene_multiplayer.h b/modules/multiplayer/scene_multiplayer.h index da8c762134..1a8de11f3f 100644 --- a/modules/multiplayer/scene_multiplayer.h +++ b/modules/multiplayer/scene_multiplayer.h @@ -37,6 +37,31 @@ #include "scene_replication_interface.h" #include "scene_rpc_interface.h" +class OfflineMultiplayerPeer : public MultiplayerPeer { + GDCLASS(OfflineMultiplayerPeer, MultiplayerPeer); + +public: + virtual int get_available_packet_count() const override { return 0; } + virtual Error get_packet(const uint8_t **r_buffer, int &r_buffer_size) override { + *r_buffer = nullptr; + r_buffer_size = 0; + return OK; + } + virtual Error put_packet(const uint8_t *p_buffer, int p_buffer_size) override { return OK; } + virtual int get_max_packet_size() const override { return 0; } + + virtual void set_target_peer(int p_peer_id) override {} + virtual int get_packet_peer() const override { return 0; } + virtual TransferMode get_packet_mode() const override { return TRANSFER_MODE_RELIABLE; }; + virtual int get_packet_channel() const override { return 0; } + virtual void disconnect_peer(int p_peer, bool p_force = false) override {} + virtual bool is_server() const override { return true; } + virtual void poll() override {} + virtual void close() override {} + virtual int get_unique_id() const override { return TARGET_PEER_SERVER; } + virtual ConnectionStatus get_connection_status() const override { return CONNECTION_CONNECTED; }; +}; + class SceneMultiplayer : public MultiplayerAPI { GDCLASS(SceneMultiplayer, MultiplayerAPI); @@ -171,6 +196,7 @@ public: bool is_server_relay_enabled() const; Ref<SceneCacheInterface> get_path_cache() { return cache; } + Ref<SceneReplicationInterface> get_replicator() { return replicator; } SceneMultiplayer(); ~SceneMultiplayer(); diff --git a/modules/multiplayer/scene_replication_interface.cpp b/modules/multiplayer/scene_replication_interface.cpp index 9302ea4f20..7d9437936a 100644 --- a/modules/multiplayer/scene_replication_interface.cpp +++ b/modules/multiplayer/scene_replication_interface.cpp @@ -257,15 +257,54 @@ void SceneReplicationInterface::_visibility_changed(int p_peer, ObjectID p_sid) Node *node = sync->get_root_node(); ERR_FAIL_COND(!node); // Bug. const ObjectID oid = node->get_instance_id(); - if (spawned_nodes.has(oid)) { + if (spawned_nodes.has(oid) && p_peer != multiplayer->get_unique_id()) { _update_spawn_visibility(p_peer, oid); } _update_sync_visibility(p_peer, sync); } +bool SceneReplicationInterface::is_rpc_visible(const ObjectID &p_oid, int p_peer) const { + if (!tracked_nodes.has(p_oid)) { + return true; // Untracked nodes are always visible to RPCs. + } + ERR_FAIL_COND_V(p_peer < 0, false); + const TrackedNode &tnode = tracked_nodes[p_oid]; + if (tnode.synchronizers.is_empty()) { + return true; // No synchronizers means no visibility restrictions. + } + if (tnode.remote_peer && uint32_t(p_peer) == tnode.remote_peer) { + return true; // RPCs on spawned nodes are always visible to spawner. + } else if (spawned_nodes.has(p_oid)) { + // It's a spwaned node we control, this can be fast + if (p_peer) { + return peers_info.has(p_peer) && peers_info[p_peer].spawn_nodes.has(p_oid); + } else { + for (const KeyValue<int, PeerInfo> &E : peers_info) { + if (!E.value.spawn_nodes.has(p_oid)) { + return false; // Not public. + } + } + return true; // All peers have this node. + } + } else { + // Cycle object synchronizers to check visibility. + for (const ObjectID &sid : tnode.synchronizers) { + MultiplayerSynchronizer *sync = get_id_as<MultiplayerSynchronizer>(sid); + ERR_CONTINUE(!sync); + // RPC visibility is composed using OR when multiple synchronizers are present. + // Note that we don't really care about authority here which may lead to unexpected + // results when using multiple synchronizers to control the same node. + if (sync->is_visible_to(p_peer)) { + return true; + } + } + return false; // Not visible. + } +} + Error SceneReplicationInterface::_update_sync_visibility(int p_peer, MultiplayerSynchronizer *p_sync) { ERR_FAIL_COND_V(!p_sync, ERR_BUG); - if (!multiplayer->has_multiplayer_peer() || !p_sync->is_multiplayer_authority()) { + if (!multiplayer->has_multiplayer_peer() || !p_sync->is_multiplayer_authority() || p_peer == multiplayer->get_unique_id()) { return OK; } diff --git a/modules/multiplayer/scene_replication_interface.h b/modules/multiplayer/scene_replication_interface.h index ad68425d77..30d58f7129 100644 --- a/modules/multiplayer/scene_replication_interface.h +++ b/modules/multiplayer/scene_replication_interface.h @@ -125,6 +125,8 @@ public: Error on_despawn_receive(int p_from, const uint8_t *p_buffer, int p_buffer_len); Error on_sync_receive(int p_from, const uint8_t *p_buffer, int p_buffer_len); + bool is_rpc_visible(const ObjectID &p_oid, int p_peer) const; + SceneReplicationInterface(SceneMultiplayer *p_multiplayer) { multiplayer = p_multiplayer; } diff --git a/modules/multiplayer/scene_rpc_interface.cpp b/modules/multiplayer/scene_rpc_interface.cpp index bfe54c00ff..dbf2b3751e 100644 --- a/modules/multiplayer/scene_rpc_interface.cpp +++ b/modules/multiplayer/scene_rpc_interface.cpp @@ -295,7 +295,7 @@ void SceneRPCInterface::_process_rpc(Node *p_node, const uint16_t p_rpc_method_i } } -void SceneRPCInterface::_send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, const RPCConfig &p_config, const StringName &p_name, const Variant **p_arg, int p_argcount) { +void SceneRPCInterface::_send_rpc(Node *p_node, int p_to, uint16_t p_rpc_id, const RPCConfig &p_config, const StringName &p_name, const Variant **p_arg, int p_argcount) { Ref<MultiplayerPeer> peer = multiplayer->get_multiplayer_peer(); ERR_FAIL_COND_MSG(peer.is_null(), "Attempt to call RPC without active multiplayer peer."); @@ -311,12 +311,35 @@ 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) + "."); } - // See if all peers have cached path (if so, call can be fast). - int psc_id; - const bool has_all_peers = multiplayer->get_path_cache()->send_object_cache(p_from, p_to, psc_id); + // See if all peers have cached path (if so, call can be fast) while building the RPC target list. + HashSet<int> targets; + Ref<SceneCacheInterface> cache = multiplayer->get_path_cache(); + int psc_id = -1; + bool has_all_peers = true; + const ObjectID oid = p_node->get_instance_id(); + if (p_to > 0) { + ERR_FAIL_COND_MSG(!multiplayer->get_replicator()->is_rpc_visible(oid, p_to), "Attempt to call an RPC to a peer that cannot see this node. Peer ID: " + itos(p_to)); + targets.insert(p_to); + has_all_peers = cache->send_object_cache(p_node, p_to, psc_id); + } else { + bool restricted = !multiplayer->get_replicator()->is_rpc_visible(oid, 0); + for (const int &P : multiplayer->get_connected_peers()) { + if (p_to < 0 && P == -p_to) { + continue; // Excluded peer. + } + if (restricted && !multiplayer->get_replicator()->is_rpc_visible(oid, P)) { + continue; // Not visible to this peer. + } + targets.insert(P); + bool has_peer = cache->send_object_cache(p_node, P, psc_id); + has_all_peers = has_all_peers && has_peer; + } + } + if (targets.is_empty()) { + return; // No one in sight. + } // Create base packet, lots of hardcode because it must be tight. - int ofs = 0; #define MAKE_ROOM(m_amount) \ @@ -399,7 +422,7 @@ void SceneRPCInterface::_send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, con ERR_FAIL_COND(name_id_compression > 1); #ifdef DEBUG_ENABLED - _profile_node_data("rpc_out", p_from->get_instance_id(), ofs); + _profile_node_data("rpc_out", p_node->get_instance_id(), ofs); #endif // We can now set the meta @@ -410,8 +433,9 @@ void SceneRPCInterface::_send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, con peer->set_transfer_mode(p_config.transfer_mode); if (has_all_peers) { - // They all have verified paths, so send fast. - multiplayer->send_command(p_to, packet_cache.ptr(), ofs); + for (const int P : targets) { + multiplayer->send_command(P, packet_cache.ptr(), ofs); + } } else { // Unreachable because the node ID is never compressed if the peers doesn't know it. CRASH_COND(node_id_compression != NETWORK_NODE_ID_COMPRESSION_32); @@ -419,23 +443,15 @@ 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()); + NodePath from_path = multiplayer->get_root_path().rel_path_to(p_node->get_path()); CharString pname = String(from_path).utf8(); int path_len = encode_cstring(pname.get_data(), nullptr); MAKE_ROOM(ofs + path_len); encode_cstring(pname.get_data(), &(packet_cache.write[ofs])); - for (const int &P : multiplayer->get_connected_peers()) { - if (p_to < 0 && P == -p_to) { - continue; // Continue, excluded. - } - - if (p_to > 0 && P != p_to) { - continue; // Continue, not for this peer. - } - + // Not all verified path, so check which needs the longer packet. + for (const int P : targets) { bool confirmed = multiplayer->get_path_cache()->is_cache_confirmed(from_path, P); - if (confirmed) { // This one confirmed path, so use id. encode_uint32(psc_id, &(packet_cache.write[1])); |