diff options
Diffstat (limited to 'modules/multiplayer')
-rw-r--r-- | modules/multiplayer/doc_classes/MultiplayerSpawner.xml | 6 | ||||
-rw-r--r-- | modules/multiplayer/doc_classes/MultiplayerSynchronizer.xml | 3 | ||||
-rw-r--r-- | modules/multiplayer/doc_classes/SceneMultiplayer.xml | 51 | ||||
-rw-r--r-- | modules/multiplayer/editor/replication_editor_plugin.cpp | 4 | ||||
-rw-r--r-- | modules/multiplayer/multiplayer_synchronizer.cpp | 4 | ||||
-rw-r--r-- | modules/multiplayer/scene_cache_interface.cpp | 6 | ||||
-rw-r--r-- | modules/multiplayer/scene_multiplayer.cpp | 385 | ||||
-rw-r--r-- | modules/multiplayer/scene_multiplayer.h | 44 | ||||
-rw-r--r-- | modules/multiplayer/scene_replication_interface.cpp | 9 | ||||
-rw-r--r-- | modules/multiplayer/scene_replication_interface.h | 2 | ||||
-rw-r--r-- | modules/multiplayer/scene_rpc_interface.cpp | 9 |
11 files changed, 466 insertions, 57 deletions
diff --git a/modules/multiplayer/doc_classes/MultiplayerSpawner.xml b/modules/multiplayer/doc_classes/MultiplayerSpawner.xml index c0265c9161..a3ca2d6486 100644 --- a/modules/multiplayer/doc_classes/MultiplayerSpawner.xml +++ b/modules/multiplayer/doc_classes/MultiplayerSpawner.xml @@ -6,7 +6,6 @@ <description> Spawnable scenes can be configured in the editor or through code (see [method add_spawnable_scene]). Also supports custom node spawns through [method spawn], calling [method _spawn_custom] on all peers. - Internally, [MultiplayerSpawner] uses [method MultiplayerAPI.object_configuration_add] to notify spawns passing the spawned node as the [code]object[/code] and itself as the [code]configuration[/code], and [method MultiplayerAPI.object_configuration_remove] to notify despawns in a similar way. </description> <tutorials> @@ -17,8 +16,7 @@ <param index="0" name="data" type="Variant" /> <description> Method called on all peers when a custom spawn was requested by the authority using [method spawn]. Should return a [Node] that is not in the scene tree. - - [b]Note:[/b] Spawned nodes should [b]not[/b] be added to the scene with `add_child`. This is done automatically. + [b]Note:[/b] Spawned nodes should [b]not[/b] be added to the scene with [method Node.add_child]. This is done automatically. </description> </method> <method name="add_spawnable_scene"> @@ -52,7 +50,6 @@ <param index="0" name="data" type="Variant" default="null" /> <description> Requests a custom spawn, with [code]data[/code] passed to [method _spawn_custom] on all peers. Returns the locally spawned node instance already inside the scene tree, and added as a child of the node pointed by [member spawn_path]. - [b]Note:[/b] Spawnable scenes are spawned automatically. [method spawn] is only needed for custom spawns. </description> </method> @@ -60,7 +57,6 @@ <members> <member name="spawn_limit" type="int" setter="set_spawn_limit" getter="get_spawn_limit" default="0"> Maximum nodes that is allowed to be spawned by this spawner. Includes both spawnable scenes and custom spawns. - When set to [code]0[/code] (the default), there is no limit. </member> <member name="spawn_path" type="NodePath" setter="set_spawn_path" getter="get_spawn_path" default="NodePath("")"> diff --git a/modules/multiplayer/doc_classes/MultiplayerSynchronizer.xml b/modules/multiplayer/doc_classes/MultiplayerSynchronizer.xml index 42c190f504..7ed6255a62 100644 --- a/modules/multiplayer/doc_classes/MultiplayerSynchronizer.xml +++ b/modules/multiplayer/doc_classes/MultiplayerSynchronizer.xml @@ -6,9 +6,7 @@ <description> By default, [MultiplayerSynchronizer] synchronizes configured properties to all peers. Visibility can be handled directly with [method set_visibility_for] or as-needed with [method add_visibility_filter] and [method update_visibility]. - [MultiplayerSpawner]s will handle nodes according to visibility of synchronizers as long as the node at [member root_path] was spawned by one. - Internally, [MultiplayerSynchronizer] uses [method MultiplayerAPI.object_configuration_add] to notify synchronization start passing the [Node] at [member root_path] as the [code]object[/code] and itself as the [code]configuration[/code], and uses [method MultiplayerAPI.object_configuration_remove] to notify synchronization end in a similar way. </description> <tutorials> @@ -19,7 +17,6 @@ <param index="0" name="filter" type="Callable" /> <description> Adds a peer visibility filter for this synchronizer. - [code]filter[/code] should take a peer id [int] and return a [bool]. </description> </method> diff --git a/modules/multiplayer/doc_classes/SceneMultiplayer.xml b/modules/multiplayer/doc_classes/SceneMultiplayer.xml index 62bb396d15..e4e2b4f631 100644 --- a/modules/multiplayer/doc_classes/SceneMultiplayer.xml +++ b/modules/multiplayer/doc_classes/SceneMultiplayer.xml @@ -19,6 +19,35 @@ Clears the current SceneMultiplayer network state (you shouldn't call this unless you know what you are doing). </description> </method> + <method name="complete_auth"> + <return type="int" enum="Error" /> + <param index="0" name="id" type="int" /> + <description> + Mark the authentication step as completed for the remote peer identified by [param id]. The [signal MultiplayerAPI.peer_connected] signal will be emitted for this peer once the remote side also completes the authentication. No further authentication messages are expected to be received from this peer. + If a peer disconnects before completing authentication, either due to a network issue, the [member auth_timeout] expiring, or manually calling [method disconnect_peer], the [signal peer_authentication_failed] signal will be emitted instead of [signal MultiplayerAPI.peer_disconnected]. + </description> + </method> + <method name="disconnect_peer"> + <return type="void" /> + <param index="0" name="id" type="int" /> + <description> + Disconnects the peer identified by [param id], removing it from the list of connected peers, and closing the underlying connection with it. + </description> + </method> + <method name="get_authenticating_peers"> + <return type="PackedInt32Array" /> + <description> + Returns the IDs of the peers currently trying to authenticate with this [MultiplayerAPI]. + </description> + </method> + <method name="send_auth"> + <return type="int" enum="Error" /> + <param index="0" name="id" type="int" /> + <param index="1" name="data" type="PackedByteArray" /> + <description> + Sends the specified [param data] to the remote peer identified by [param id] as part of an authentication message. This can be used to authenticate peers, and control when [signal MultiplayerAPI.peer_connected] is emitted (and the remote peer accepted as one of the connected peers). + </description> + </method> <method name="send_bytes"> <return type="int" enum="Error" /> <param index="0" name="bytes" type="PackedByteArray" /> @@ -35,6 +64,12 @@ If [code]true[/code], the MultiplayerAPI will allow encoding and decoding of object during RPCs. [b]Warning:[/b] Deserialized objects can contain code which gets executed. Do not use this option if the serialized object comes from untrusted sources to avoid potential security threat such as remote code execution. </member> + <member name="auth_callback" type="Callable" setter="set_auth_callback" getter="get_auth_callback"> + The callback to execute when when receiving authentication data sent via [method send_auth]. If the [Callable] is empty (default), peers will be automatically accepted as soon as they connect. + </member> + <member name="auth_timeout" type="float" setter="set_auth_timeout" getter="get_auth_timeout" default="3.0"> + If set to a value greater than [code]0.0[/code], the maximum amount of time peers can stay in the authenticating state, after which the authentication will automatically fail. See the [signal peer_authenticating] and [signal peer_authentication_failed] signals. + </member> <member name="refuse_new_connections" type="bool" setter="set_refuse_new_connections" getter="is_refusing_new_connections" default="false"> If [code]true[/code], the MultiplayerAPI's [member MultiplayerAPI.multiplayer_peer] refuses new incoming connections. </member> @@ -42,8 +77,24 @@ The root path to use for RPCs and replication. Instead of an absolute path, a relative path will be used to find the node upon which the RPC should be executed. This effectively allows to have different branches of the scene tree to be managed by different MultiplayerAPI, allowing for example to run both client and server in the same scene. </member> + <member name="server_relay" type="bool" setter="set_server_relay_enabled" getter="is_server_relay_enabled" default="true"> + Enable or disable the server feature that notifies clients of other peers' connection/disconnection, and relays messages between them. When this option is [code]false[/code], clients won't be automatically notified of other peers and won't be able to send them packets through the server. + [b]Note:[/b] Support for this feature may depend on the current [MultiplayerPeer] configuration. See [method MultiplayerPeer.is_server_relay_supported]. + </member> </members> <signals> + <signal name="peer_authenticating"> + <param index="0" name="id" type="int" /> + <description> + Emitted when this MultiplayerAPI's [member MultiplayerAPI.multiplayer_peer] connects to a new peer and a valid [member auth_callback] is set. In this case, the [signal MultiplayerAPI.peer_connected] will not be emitted until [method complete_auth] is called with given peer [param id]. While in this state, the peer will not be included in the list returned by [method MultiplayerAPI.get_peers] (but in the one returned by [method get_authenticating_peers]), and only authentication data will be sent or received. See [method send_auth] for sending authentication data. + </description> + </signal> + <signal name="peer_authentication_failed"> + <param index="0" name="id" type="int" /> + <description> + Emitted when this MultiplayerAPI's [member MultiplayerAPI.multiplayer_peer] disconnects from a peer for which authentication had not yet completed. See [signal peer_authenticating]. + </description> + </signal> <signal name="peer_packet"> <param index="0" name="id" type="int" /> <param index="1" name="packet" type="PackedByteArray" /> diff --git a/modules/multiplayer/editor/replication_editor_plugin.cpp b/modules/multiplayer/editor/replication_editor_plugin.cpp index f045018f25..aee5f5b483 100644 --- a/modules/multiplayer/editor/replication_editor_plugin.cpp +++ b/modules/multiplayer/editor/replication_editor_plugin.cpp @@ -355,7 +355,7 @@ void ReplicationEditor::_tree_item_edited() { int column = tree->get_edited_column(); ERR_FAIL_COND(column < 1 || column > 2); const NodePath prop = ti->get_metadata(0); - Ref<EditorUndoRedoManager> undo_redo = EditorNode::get_undo_redo(); + Ref<EditorUndoRedoManager> &undo_redo = EditorNode::get_undo_redo(); bool value = ti->is_checked(column); String method; if (column == 1) { @@ -395,7 +395,7 @@ void ReplicationEditor::_dialog_closed(bool p_confirmed) { int idx = config->property_get_index(prop); bool spawn = config->property_get_spawn(prop); bool sync = config->property_get_sync(prop); - Ref<EditorUndoRedoManager> undo_redo = EditorNode::get_undo_redo(); + Ref<EditorUndoRedoManager> &undo_redo = EditorNode::get_undo_redo(); undo_redo->create_action(TTR("Remove Property")); undo_redo->add_do_method(config.ptr(), "remove_property", prop); undo_redo->add_undo_method(config.ptr(), "add_property", prop, idx); diff --git a/modules/multiplayer/multiplayer_synchronizer.cpp b/modules/multiplayer/multiplayer_synchronizer.cpp index 9755f426d5..95857392c7 100644 --- a/modules/multiplayer/multiplayer_synchronizer.cpp +++ b/modules/multiplayer/multiplayer_synchronizer.cpp @@ -118,6 +118,10 @@ void MultiplayerSynchronizer::set_net_id(uint32_t p_net_id) { } bool MultiplayerSynchronizer::update_outbound_sync_time(uint64_t p_msec) { + if (last_sync_msec == p_msec) { + // last_sync_msec has been updated on this frame. + return true; + } if (p_msec >= last_sync_msec + interval_msec) { last_sync_msec = p_msec; return true; diff --git a/modules/multiplayer/scene_cache_interface.cpp b/modules/multiplayer/scene_cache_interface.cpp index b3b642f815..7df9b95b30 100644 --- a/modules/multiplayer/scene_cache_interface.cpp +++ b/modules/multiplayer/scene_cache_interface.cpp @@ -105,8 +105,7 @@ void SceneCacheInterface::process_simplify_path(int p_from, const uint8_t *p_pac multiplayer_peer->set_transfer_channel(0); multiplayer_peer->set_transfer_mode(MultiplayerPeer::TRANSFER_MODE_RELIABLE); - multiplayer_peer->set_target_peer(p_from); - multiplayer_peer->put_packet(packet.ptr(), packet.size()); + multiplayer->send_command(p_from, packet.ptr(), packet.size()); } void SceneCacheInterface::process_confirm_path(int p_from, const uint8_t *p_packet, int p_packet_len) { @@ -162,10 +161,9 @@ Error SceneCacheInterface::_send_confirm_path(Node *p_node, NodePath p_path, Pat Error err = OK; for (int peer_id : p_peers) { - multiplayer_peer->set_target_peer(peer_id); multiplayer_peer->set_transfer_channel(0); multiplayer_peer->set_transfer_mode(MultiplayerPeer::TRANSFER_MODE_RELIABLE); - err = multiplayer_peer->put_packet(packet.ptr(), packet.size()); + err = multiplayer->send_command(peer_id, packet.ptr(), packet.size()); ERR_FAIL_COND_V(err != OK, err); // Insert into confirmed, but as false since it was not confirmed. psc->confirmed_peers.insert(peer_id, false); diff --git a/modules/multiplayer/scene_multiplayer.cpp b/modules/multiplayer/scene_multiplayer.cpp index 3fc1eef366..db7c5037cd 100644 --- a/modules/multiplayer/scene_multiplayer.cpp +++ b/modules/multiplayer/scene_multiplayer.cpp @@ -51,14 +51,32 @@ void SceneMultiplayer::profile_bandwidth(const String &p_inout, int p_size) { } #endif +void SceneMultiplayer::_update_status() { + MultiplayerPeer::ConnectionStatus status = multiplayer_peer.is_valid() ? multiplayer_peer->get_connection_status() : MultiplayerPeer::CONNECTION_DISCONNECTED; + if (last_connection_status != status) { + if (status == MultiplayerPeer::CONNECTION_DISCONNECTED) { + if (last_connection_status == MultiplayerPeer::CONNECTION_CONNECTING) { + emit_signal(SNAME("connection_failed")); + } else { + emit_signal(SNAME("server_disconnected")); + } + clear(); + } + last_connection_status = status; + } +} + Error SceneMultiplayer::poll() { - if (!multiplayer_peer.is_valid() || multiplayer_peer->get_connection_status() == MultiplayerPeer::CONNECTION_DISCONNECTED) { - return ERR_UNCONFIGURED; + _update_status(); + if (last_connection_status == MultiplayerPeer::CONNECTION_DISCONNECTED) { + return OK; } multiplayer_peer->poll(); - if (!multiplayer_peer.is_valid()) { // It's possible that polling might have resulted in a disconnection, so check here. + _update_status(); + if (last_connection_status != MultiplayerPeer::CONNECTION_CONNECTED) { + // We might be still connecting, or polling might have resulted in a disconnection. return OK; } @@ -67,25 +85,99 @@ Error SceneMultiplayer::poll() { const uint8_t *packet; int len; + int channel = multiplayer_peer->get_packet_channel(); + MultiplayerPeer::TransferMode mode = multiplayer_peer->get_packet_mode(); + Error err = multiplayer_peer->get_packet(&packet, len); ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Error getting packet! %d", err)); - remote_sender_id = sender; - _process_packet(sender, packet, len); - remote_sender_id = 0; + if (pending_peers.has(sender)) { + if (pending_peers[sender].local) { + // If the auth is over, admit the peer at the first packet. + pending_peers.erase(sender); + _admit_peer(sender); + } else { + ERR_CONTINUE(len < 2 || (packet[0] & CMD_MASK) != NETWORK_COMMAND_SYS || packet[1] != SYS_COMMAND_AUTH); + // Auth message. + PackedByteArray pba; + pba.resize(len - 2); + if (pba.size()) { + memcpy(pba.ptrw(), &packet[2], len - 2); + // User callback + const Variant sv = sender; + const Variant pbav = pba; + const Variant *argv[2] = { &sv, &pbav }; + Variant ret; + Callable::CallError ce; + auth_callback.callp(argv, 2, ret, ce); + ERR_CONTINUE_MSG(ce.error != Callable::CallError::CALL_OK, "Failed to call authentication callback"); + } else { + // Remote complete notification. + pending_peers[sender].remote = true; + if (pending_peers[sender].local) { + pending_peers.erase(sender); + _admit_peer(sender); + } + } + continue; // Auth in progress. + } + } - if (!multiplayer_peer.is_valid()) { - return OK; // It's also possible that a packet or RPC caused a disconnection, so also check here. + ERR_CONTINUE(!connected_peers.has(sender)); + + if (len && (packet[0] & CMD_MASK) == NETWORK_COMMAND_SYS) { + // Sys messages are processed separately since they might call _process_packet themselves. + if (len > 1 && packet[1] == SYS_COMMAND_AUTH) { + ERR_CONTINUE(len != 2); + // If we are here, we already admitted the peer locally, and this is just a confirmation packet. + continue; + } + + _process_sys(sender, packet, len, mode, channel); + } else { + remote_sender_id = sender; + _process_packet(sender, packet, len); + remote_sender_id = 0; + } + + _update_status(); + if (last_connection_status != MultiplayerPeer::CONNECTION_CONNECTED) { // It's possible that processing a packet might have resulted in a disconnection, so check here. + return OK; + } + } + if (pending_peers.size() && auth_timeout) { + HashSet<int> to_drop; + uint64_t time = OS::get_singleton()->get_ticks_msec(); + for (const KeyValue<int, PendingPeer> &pending : pending_peers) { + if (pending.value.time + auth_timeout <= time) { + multiplayer_peer->disconnect_peer(pending.key); + to_drop.insert(pending.key); + } + } + for (const int &P : to_drop) { + // Each signal might trigger a disconnection. + pending_peers.erase(P); + emit_signal(SNAME("peer_authentication_failed"), P); } } + + _update_status(); + if (last_connection_status != MultiplayerPeer::CONNECTION_CONNECTED) { // Signals might have triggered disconnection. + return OK; + } + replicator->on_network_process(); return OK; } void SceneMultiplayer::clear() { + last_connection_status = MultiplayerPeer::CONNECTION_DISCONNECTED; + pending_peers.clear(); connected_peers.clear(); packet_cache.clear(); + replicator->on_reset(); cache->clear(); + relay_buffer->clear(); } void SceneMultiplayer::set_root_path(const NodePath &p_path) { @@ -108,9 +200,6 @@ void SceneMultiplayer::set_multiplayer_peer(const Ref<MultiplayerPeer> &p_peer) if (multiplayer_peer.is_valid()) { multiplayer_peer->disconnect("peer_connected", callable_mp(this, &SceneMultiplayer::_add_peer)); multiplayer_peer->disconnect("peer_disconnected", callable_mp(this, &SceneMultiplayer::_del_peer)); - multiplayer_peer->disconnect("connection_succeeded", callable_mp(this, &SceneMultiplayer::_connected_to_server)); - multiplayer_peer->disconnect("connection_failed", callable_mp(this, &SceneMultiplayer::_connection_failed)); - multiplayer_peer->disconnect("server_disconnected", callable_mp(this, &SceneMultiplayer::_server_disconnected)); clear(); } @@ -119,11 +208,8 @@ void SceneMultiplayer::set_multiplayer_peer(const Ref<MultiplayerPeer> &p_peer) if (multiplayer_peer.is_valid()) { multiplayer_peer->connect("peer_connected", callable_mp(this, &SceneMultiplayer::_add_peer)); multiplayer_peer->connect("peer_disconnected", callable_mp(this, &SceneMultiplayer::_del_peer)); - multiplayer_peer->connect("connection_succeeded", callable_mp(this, &SceneMultiplayer::_connected_to_server)); - multiplayer_peer->connect("connection_failed", callable_mp(this, &SceneMultiplayer::_connection_failed)); - multiplayer_peer->connect("server_disconnected", callable_mp(this, &SceneMultiplayer::_server_disconnected)); } - replicator->on_reset(); + _update_status(); } Ref<MultiplayerPeer> SceneMultiplayer::get_multiplayer_peer() { @@ -166,34 +252,184 @@ void SceneMultiplayer::_process_packet(int p_from, const uint8_t *p_packet, int case NETWORK_COMMAND_SYNC: { replicator->on_sync_receive(p_from, p_packet, p_packet_len); } break; + default: { + ERR_FAIL_MSG("Invalid network command from " + itos(p_from)); + } break; + } +} + +Error SceneMultiplayer::send_command(int p_to, const uint8_t *p_packet, int p_packet_len) { + if (server_relay && get_unique_id() != 1 && p_to != 1 && multiplayer_peer->is_server_relay_supported()) { + // Send relay packet. + relay_buffer->seek(0); + relay_buffer->put_u8(NETWORK_COMMAND_SYS); + relay_buffer->put_u8(SYS_COMMAND_RELAY); + relay_buffer->put_32(p_to); // Set the destination. + relay_buffer->put_data(p_packet, p_packet_len); + multiplayer_peer->set_target_peer(1); + const Vector<uint8_t> data = relay_buffer->get_data_array(); + return multiplayer_peer->put_packet(data.ptr(), relay_buffer->get_position()); + } + if (p_to > 0) { + ERR_FAIL_COND_V(!connected_peers.has(p_to), ERR_BUG); + multiplayer_peer->set_target_peer(p_to); + return multiplayer_peer->put_packet(p_packet, p_packet_len); + } else { + for (const int &pid : connected_peers) { + if (p_to && pid == -p_to) { + continue; + } + multiplayer_peer->set_target_peer(pid); + multiplayer_peer->put_packet(p_packet, p_packet_len); + } + return OK; + } +} + +void SceneMultiplayer::_process_sys(int p_from, const uint8_t *p_packet, int p_packet_len, MultiplayerPeer::TransferMode p_mode, int p_channel) { + ERR_FAIL_COND_MSG(p_packet_len < SYS_CMD_SIZE, "Invalid packet received. Size too small."); + uint8_t sys_cmd_type = p_packet[1]; + int32_t peer = int32_t(decode_uint32(&p_packet[2])); + switch (sys_cmd_type) { + case SYS_COMMAND_ADD_PEER: { + ERR_FAIL_COND(!server_relay || !multiplayer_peer->is_server_relay_supported() || get_unique_id() == 1 || p_from != 1); + _admit_peer(peer); // Relayed peers are automatically accepted. + } break; + case SYS_COMMAND_DEL_PEER: { + ERR_FAIL_COND(!server_relay || !multiplayer_peer->is_server_relay_supported() || get_unique_id() == 1 || p_from != 1); + _del_peer(peer); + } break; + case SYS_COMMAND_RELAY: { + ERR_FAIL_COND(!server_relay || !multiplayer_peer->is_server_relay_supported()); + ERR_FAIL_COND(p_packet_len < SYS_CMD_SIZE + 1); + const uint8_t *packet = p_packet + SYS_CMD_SIZE; + int len = p_packet_len - SYS_CMD_SIZE; + bool should_process = false; + if (get_unique_id() == 1) { // I am the server. + // Direct messages to server should not go through relay. + ERR_FAIL_COND(peer > 0 && !connected_peers.has(peer)); + // Send relay packet. + relay_buffer->seek(0); + relay_buffer->put_u8(NETWORK_COMMAND_SYS); + relay_buffer->put_u8(SYS_COMMAND_RELAY); + relay_buffer->put_32(p_from); // Set the source. + relay_buffer->put_data(packet, len); + const Vector<uint8_t> data = relay_buffer->get_data_array(); + multiplayer_peer->set_transfer_mode(p_mode); + multiplayer_peer->set_transfer_channel(p_channel); + if (peer > 0) { + multiplayer_peer->set_target_peer(peer); + multiplayer_peer->put_packet(data.ptr(), relay_buffer->get_position()); + } else { + for (const int &P : connected_peers) { + // Not to sender, nor excluded. + if (P == p_from || (peer < 0 && P != -peer)) { + continue; + } + multiplayer_peer->set_target_peer(P); + multiplayer_peer->put_packet(data.ptr(), relay_buffer->get_position()); + } + } + if (peer == 0 || peer == -1) { + should_process = true; + peer = p_from; // Process as the source. + } + } else { + ERR_FAIL_COND(p_from != 1); // Bug. + should_process = true; + } + if (should_process) { + remote_sender_id = peer; + _process_packet(peer, packet, len); + remote_sender_id = 0; + } + } break; + default: { + ERR_FAIL(); + } } } void SceneMultiplayer::_add_peer(int p_id) { + if (auth_callback.is_valid()) { + pending_peers[p_id] = PendingPeer(); + pending_peers[p_id].time = OS::get_singleton()->get_ticks_msec(); + emit_signal(SNAME("peer_authenticating"), p_id); + return; + } else { + _admit_peer(p_id); + } +} + +void SceneMultiplayer::_admit_peer(int p_id) { + if (server_relay && get_unique_id() == 1 && multiplayer_peer->is_server_relay_supported()) { + // Notify others of connection, and send connected peers to newly connected one. + uint8_t buf[SYS_CMD_SIZE]; + buf[0] = NETWORK_COMMAND_SYS; + buf[1] = SYS_COMMAND_ADD_PEER; + multiplayer_peer->set_transfer_channel(0); + multiplayer_peer->set_transfer_mode(MultiplayerPeer::TRANSFER_MODE_RELIABLE); + for (const int &P : connected_peers) { + // Send new peer to already connected. + encode_uint32(p_id, &buf[2]); + multiplayer_peer->set_target_peer(P); + multiplayer_peer->put_packet(buf, sizeof(buf)); + // Send already connected to new peer. + encode_uint32(P, &buf[2]); + multiplayer_peer->set_target_peer(p_id); + multiplayer_peer->put_packet(buf, sizeof(buf)); + } + } + connected_peers.insert(p_id); cache->on_peer_change(p_id, true); replicator->on_peer_change(p_id, true); + if (p_id == 1) { + emit_signal(SNAME("connected_to_server")); + } emit_signal(SNAME("peer_connected"), p_id); } void SceneMultiplayer::_del_peer(int p_id) { + if (pending_peers.has(p_id)) { + pending_peers.erase(p_id); + emit_signal(SNAME("peer_authentication_failed"), p_id); + return; + } else if (!connected_peers.has(p_id)) { + return; + } + + if (server_relay && get_unique_id() == 1 && multiplayer_peer->is_server_relay_supported()) { + // Notify others of disconnection. + uint8_t buf[SYS_CMD_SIZE]; + buf[0] = NETWORK_COMMAND_SYS; + buf[1] = SYS_COMMAND_DEL_PEER; + multiplayer_peer->set_transfer_channel(0); + multiplayer_peer->set_transfer_mode(MultiplayerPeer::TRANSFER_MODE_RELIABLE); + encode_uint32(p_id, &buf[2]); + for (const int &P : connected_peers) { + if (P == p_id) { + continue; + } + multiplayer_peer->set_target_peer(P); + multiplayer_peer->put_packet(buf, sizeof(buf)); + } + } + replicator->on_peer_change(p_id, false); cache->on_peer_change(p_id, false); connected_peers.erase(p_id); emit_signal(SNAME("peer_disconnected"), p_id); } -void SceneMultiplayer::_connected_to_server() { - emit_signal(SNAME("connected_to_server")); -} - -void SceneMultiplayer::_connection_failed() { - emit_signal(SNAME("connection_failed")); -} - -void SceneMultiplayer::_server_disconnected() { - replicator->on_reset(); - emit_signal(SNAME("server_disconnected")); +void SceneMultiplayer::disconnect_peer(int p_id) { + ERR_FAIL_COND(multiplayer_peer.is_null() || multiplayer_peer->get_connection_status() != MultiplayerPeer::CONNECTION_CONNECTED); + if (pending_peers.has(p_id)) { + pending_peers.erase(p_id); + } else if (connected_peers.has(p_id)) { + connected_peers.has(p_id); + } + multiplayer_peer->disconnect_peer(p_id); } Error SceneMultiplayer::send_bytes(Vector<uint8_t> p_data, int p_to, MultiplayerPeer::TransferMode p_mode, int p_channel) { @@ -209,11 +445,64 @@ Error SceneMultiplayer::send_bytes(Vector<uint8_t> p_data, int p_to, Multiplayer packet_cache.write[0] = NETWORK_COMMAND_RAW; memcpy(&packet_cache.write[1], &r[0], p_data.size()); - multiplayer_peer->set_target_peer(p_to); multiplayer_peer->set_transfer_channel(p_channel); multiplayer_peer->set_transfer_mode(p_mode); + return send_command(p_to, packet_cache.ptr(), p_data.size() + 1); +} + +Error SceneMultiplayer::send_auth(int p_to, Vector<uint8_t> p_data) { + ERR_FAIL_COND_V(multiplayer_peer.is_null() || multiplayer_peer->get_connection_status() != MultiplayerPeer::CONNECTION_CONNECTED, ERR_UNCONFIGURED); + ERR_FAIL_COND_V(!pending_peers.has(p_to), ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_data.size() < 1, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V_MSG(pending_peers[p_to].local, ERR_FILE_CANT_WRITE, "The authentication session was previously marked as completed, no more authentication data can be sent."); + ERR_FAIL_COND_V_MSG(pending_peers[p_to].remote, ERR_FILE_CANT_WRITE, "The remote peer notified that the authentication session was completed, no more authentication data can be sent."); + + if (packet_cache.size() < p_data.size() + 2) { + packet_cache.resize(p_data.size() + 2); + } + + packet_cache.write[0] = NETWORK_COMMAND_SYS; + packet_cache.write[1] = SYS_COMMAND_AUTH; + memcpy(&packet_cache.write[2], p_data.ptr(), p_data.size()); + + multiplayer_peer->set_target_peer(p_to); + multiplayer_peer->set_transfer_channel(0); + multiplayer_peer->set_transfer_mode(MultiplayerPeer::TRANSFER_MODE_RELIABLE); + return multiplayer_peer->put_packet(packet_cache.ptr(), p_data.size() + 2); +} + +Error SceneMultiplayer::complete_auth(int p_peer) { + ERR_FAIL_COND_V(multiplayer_peer.is_null() || multiplayer_peer->get_connection_status() != MultiplayerPeer::CONNECTION_CONNECTED, ERR_UNCONFIGURED); + ERR_FAIL_COND_V(!pending_peers.has(p_peer), ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V_MSG(pending_peers[p_peer].local, ERR_FILE_CANT_WRITE, "The authentication session was already marked as completed."); + pending_peers[p_peer].local = true; + // Notify the remote peer that the authentication has completed. + uint8_t buf[2] = { NETWORK_COMMAND_SYS, SYS_COMMAND_AUTH }; + Error err = multiplayer_peer->put_packet(buf, 2); + // The remote peer already reported the authentication as completed, so admit the peer. + // May generate new packets, so it must happen after sending confirmation. + if (pending_peers[p_peer].remote) { + pending_peers.erase(p_peer); + _admit_peer(p_peer); + } + return err; +} + +void SceneMultiplayer::set_auth_callback(Callable p_callback) { + auth_callback = p_callback; +} + +Callable SceneMultiplayer::get_auth_callback() const { + return auth_callback; +} + +void SceneMultiplayer::set_auth_timeout(double p_timeout) { + ERR_FAIL_COND_MSG(p_timeout < 0, "Timeout must be greater or equal to 0 (where 0 means no timeout)"); + auth_timeout = uint64_t(p_timeout * 1000); +} - return multiplayer_peer->put_packet(packet_cache.ptr(), p_data.size() + 1); +double SceneMultiplayer::get_auth_timeout() const { + return double(auth_timeout) / 1000.0; } void SceneMultiplayer::_process_raw(int p_from, const uint8_t *p_packet, int p_packet_len) { @@ -255,6 +544,16 @@ Vector<int> SceneMultiplayer::get_peer_ids() { return ret; } +Vector<int> SceneMultiplayer::get_authenticating_peer_ids() { + Vector<int> out; + out.resize(pending_peers.size()); + int idx = 0; + for (const KeyValue<int, PendingPeer> &E : pending_peers) { + out.write[idx++] = E.key; + } + return out; +} + void SceneMultiplayer::set_allow_object_decoding(bool p_enable) { allow_object_decoding = p_enable; } @@ -303,25 +602,55 @@ Error SceneMultiplayer::object_configuration_remove(Object *p_obj, Variant p_con return ERR_INVALID_PARAMETER; } +void SceneMultiplayer::set_server_relay_enabled(bool p_enabled) { + ERR_FAIL_COND_MSG(multiplayer_peer.is_valid() && multiplayer_peer->get_connection_status() != MultiplayerPeer::CONNECTION_DISCONNECTED, "Cannot change the server relay option while the multiplayer peer is active."); + server_relay = p_enabled; +} + +bool SceneMultiplayer::is_server_relay_enabled() const { + return server_relay; +} + void SceneMultiplayer::_bind_methods() { ClassDB::bind_method(D_METHOD("set_root_path", "path"), &SceneMultiplayer::set_root_path); ClassDB::bind_method(D_METHOD("get_root_path"), &SceneMultiplayer::get_root_path); ClassDB::bind_method(D_METHOD("clear"), &SceneMultiplayer::clear); + + ClassDB::bind_method(D_METHOD("disconnect_peer", "id"), &SceneMultiplayer::disconnect_peer); + + ClassDB::bind_method(D_METHOD("get_authenticating_peers"), &SceneMultiplayer::get_authenticating_peer_ids); + ClassDB::bind_method(D_METHOD("send_auth", "id", "data"), &SceneMultiplayer::send_auth); + ClassDB::bind_method(D_METHOD("complete_auth", "id"), &SceneMultiplayer::complete_auth); + + ClassDB::bind_method(D_METHOD("set_auth_callback", "callback"), &SceneMultiplayer::set_auth_callback); + ClassDB::bind_method(D_METHOD("get_auth_callback"), &SceneMultiplayer::get_auth_callback); + ClassDB::bind_method(D_METHOD("set_auth_timeout", "timeout"), &SceneMultiplayer::set_auth_timeout); + ClassDB::bind_method(D_METHOD("get_auth_timeout"), &SceneMultiplayer::get_auth_timeout); + ClassDB::bind_method(D_METHOD("set_refuse_new_connections", "refuse"), &SceneMultiplayer::set_refuse_new_connections); ClassDB::bind_method(D_METHOD("is_refusing_new_connections"), &SceneMultiplayer::is_refusing_new_connections); ClassDB::bind_method(D_METHOD("set_allow_object_decoding", "enable"), &SceneMultiplayer::set_allow_object_decoding); ClassDB::bind_method(D_METHOD("is_object_decoding_allowed"), &SceneMultiplayer::is_object_decoding_allowed); + ClassDB::bind_method(D_METHOD("set_server_relay_enabled", "enabled"), &SceneMultiplayer::set_server_relay_enabled); + ClassDB::bind_method(D_METHOD("is_server_relay_enabled"), &SceneMultiplayer::is_server_relay_enabled); ClassDB::bind_method(D_METHOD("send_bytes", "bytes", "id", "mode", "channel"), &SceneMultiplayer::send_bytes, DEFVAL(MultiplayerPeer::TARGET_PEER_BROADCAST), DEFVAL(MultiplayerPeer::TRANSFER_MODE_RELIABLE), DEFVAL(0)); ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "root_path"), "set_root_path", "get_root_path"); + ADD_PROPERTY(PropertyInfo(Variant::CALLABLE, "auth_callback"), "set_auth_callback", "get_auth_callback"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "auth_timeout", PROPERTY_HINT_RANGE, "0,30,0.1,or_greater,suffix:s"), "set_auth_timeout", "get_auth_timeout"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_object_decoding"), "set_allow_object_decoding", "is_object_decoding_allowed"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "refuse_new_connections"), "set_refuse_new_connections", "is_refusing_new_connections"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "server_relay"), "set_server_relay_enabled", "is_server_relay_enabled"); + ADD_PROPERTY_DEFAULT("refuse_new_connections", false); + ADD_SIGNAL(MethodInfo("peer_authenticating", PropertyInfo(Variant::INT, "id"))); + ADD_SIGNAL(MethodInfo("peer_authentication_failed", PropertyInfo(Variant::INT, "id"))); ADD_SIGNAL(MethodInfo("peer_packet", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::PACKED_BYTE_ARRAY, "packet"))); } SceneMultiplayer::SceneMultiplayer() { + relay_buffer.instantiate(); replicator = Ref<SceneReplicationInterface>(memnew(SceneReplicationInterface(this))); rpc = Ref<SceneRPCInterface>(memnew(SceneRPCInterface(this))); cache = Ref<SceneCacheInterface>(memnew(SceneCacheInterface(this))); diff --git a/modules/multiplayer/scene_multiplayer.h b/modules/multiplayer/scene_multiplayer.h index a99cca7b21..b0ecc48f8c 100644 --- a/modules/multiplayer/scene_multiplayer.h +++ b/modules/multiplayer/scene_multiplayer.h @@ -49,6 +49,18 @@ public: NETWORK_COMMAND_SPAWN, NETWORK_COMMAND_DESPAWN, NETWORK_COMMAND_SYNC, + NETWORK_COMMAND_SYS, + }; + + enum SysCommands { + SYS_COMMAND_AUTH, + SYS_COMMAND_ADD_PEER, + SYS_COMMAND_DEL_PEER, + SYS_COMMAND_RELAY, + }; + + enum { + SYS_CMD_SIZE = 6, // Command + sys command + peer_id (+ optional payload). }; // For each command, the 4 MSB can contain custom flags, as defined by subsystems. @@ -65,7 +77,17 @@ public: }; private: + struct PendingPeer { + bool local = false; + bool remote = false; + uint64_t time = 0; + }; + Ref<MultiplayerPeer> multiplayer_peer; + MultiplayerPeer::ConnectionStatus last_connection_status = MultiplayerPeer::CONNECTION_DISCONNECTED; + HashMap<int, PendingPeer> pending_peers; // true if locally finalized. + Callable auth_callback; + uint64_t auth_timeout = 3000; HashSet<int> connected_peers; int remote_sender_id = 0; int remote_sender_override = 0; @@ -74,6 +96,8 @@ private: NodePath root_path; bool allow_object_decoding = false; + bool server_relay = true; + Ref<StreamPeerBuffer> relay_buffer; Ref<SceneCacheInterface> cache; Ref<SceneReplicationInterface> replicator; @@ -84,12 +108,12 @@ protected: void _process_packet(int p_from, const uint8_t *p_packet, int p_packet_len); void _process_raw(int p_from, const uint8_t *p_packet, int p_packet_len); + void _process_sys(int p_from, const uint8_t *p_packet, int p_packet_len, MultiplayerPeer::TransferMode p_mode, int p_channel); void _add_peer(int p_id); + void _admit_peer(int p_id); void _del_peer(int p_id); - void _connected_to_server(); - void _connection_failed(); - void _server_disconnected(); + void _update_status(); public: virtual void set_multiplayer_peer(const Ref<MultiplayerPeer> &p_peer) override; @@ -111,6 +135,17 @@ public: void set_root_path(const NodePath &p_path); NodePath get_root_path() const; + void disconnect_peer(int p_id); + + Error send_auth(int p_to, Vector<uint8_t> p_bytes); + Error complete_auth(int p_peer); + void set_auth_callback(Callable p_callback); + Callable get_auth_callback() const; + void set_auth_timeout(double p_timeout); + double get_auth_timeout() const; + Vector<int> get_authenticating_peer_ids(); + + Error send_command(int p_to, const uint8_t *p_packet, int p_packet_len); // Used internally to relay packets when needed. Error send_bytes(Vector<uint8_t> p_data, int p_to = MultiplayerPeer::TARGET_PEER_BROADCAST, MultiplayerPeer::TransferMode p_mode = MultiplayerPeer::TRANSFER_MODE_RELIABLE, int p_channel = 0); String get_rpc_md5(const Object *p_obj); @@ -123,6 +158,9 @@ public: void set_allow_object_decoding(bool p_enable); bool is_object_decoding_allowed() const; + void set_server_relay_enabled(bool p_enabled); + bool is_server_relay_enabled() const; + Ref<SceneCacheInterface> get_path_cache() { return cache; } #ifdef DEBUG_ENABLED diff --git a/modules/multiplayer/scene_replication_interface.cpp b/modules/multiplayer/scene_replication_interface.cpp index 8359580805..f1bab7327a 100644 --- a/modules/multiplayer/scene_replication_interface.cpp +++ b/modules/multiplayer/scene_replication_interface.cpp @@ -72,7 +72,7 @@ void SceneReplicationInterface::_free_remotes(const PeerInfo &p_info) { for (const KeyValue<uint32_t, ObjectID> &E : p_info.recv_nodes) { Node *node = tracked_nodes.has(E.value) ? get_id_as<Node>(E.value) : nullptr; ERR_CONTINUE(!node); - node->queue_delete(); + node->queue_free(); } } @@ -325,7 +325,7 @@ Error SceneReplicationInterface::_update_spawn_visibility(int p_peer, const Obje // Check visibility for each peers. for (const KeyValue<int, PeerInfo> &E : peers_info) { if (is_visible) { - // This is fast, since the the object is visibile to everyone, we don't need to check each peer. + // This is fast, since the the object is visible to everyone, we don't need to check each peer. if (E.value.spawn_nodes.has(p_oid)) { // Already spawned. continue; @@ -370,10 +370,9 @@ Error SceneReplicationInterface::_send_raw(const uint8_t *p_buffer, int p_size, #endif Ref<MultiplayerPeer> peer = multiplayer->get_multiplayer_peer(); - peer->set_target_peer(p_peer); peer->set_transfer_channel(0); peer->set_transfer_mode(p_reliable ? MultiplayerPeer::TRANSFER_MODE_RELIABLE : MultiplayerPeer::TRANSFER_MODE_UNRELIABLE); - return peer->put_packet(p_buffer, p_size); + return multiplayer->send_command(p_peer, p_buffer, p_size); } Error SceneReplicationInterface::_make_spawn_packet(Node *p_node, MultiplayerSpawner *p_spawner, int &r_len) { @@ -582,7 +581,7 @@ Error SceneReplicationInterface::on_despawn_receive(int p_from, const uint8_t *p if (node->get_parent() != nullptr) { node->get_parent()->remove_child(node); } - node->queue_delete(); + node->queue_free(); spawner->emit_signal(SNAME("despawned"), node); return OK; diff --git a/modules/multiplayer/scene_replication_interface.h b/modules/multiplayer/scene_replication_interface.h index ee454f604e..c8bd96eb87 100644 --- a/modules/multiplayer/scene_replication_interface.h +++ b/modules/multiplayer/scene_replication_interface.h @@ -75,7 +75,7 @@ private: HashSet<ObjectID> spawned_nodes; HashSet<ObjectID> sync_nodes; - // Pending spawn informations. + // Pending spawn information. ObjectID pending_spawn; int pending_spawn_remote = 0; const uint8_t *pending_buffer = nullptr; diff --git a/modules/multiplayer/scene_rpc_interface.cpp b/modules/multiplayer/scene_rpc_interface.cpp index 65090b9316..acc113c901 100644 --- a/modules/multiplayer/scene_rpc_interface.cpp +++ b/modules/multiplayer/scene_rpc_interface.cpp @@ -412,8 +412,7 @@ void SceneRPCInterface::_send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, con if (has_all_peers) { // They all have verified paths, so send fast. - peer->set_target_peer(p_to); // To all of you. - peer->put_packet(packet_cache.ptr(), ofs); // A message with love. + multiplayer->send_command(p_to, 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); @@ -438,16 +437,14 @@ void SceneRPCInterface::_send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, con bool confirmed = multiplayer->get_path_cache()->is_cache_confirmed(from_path, P); - peer->set_target_peer(P); // To this one specifically. - if (confirmed) { // This one confirmed path, so use id. encode_uint32(psc_id, &(packet_cache.write[1])); - peer->put_packet(packet_cache.ptr(), ofs); + multiplayer->send_command(P, packet_cache.ptr(), ofs); } else { // This one did not confirm path yet, so use entire path (sorry!). encode_uint32(0x80000000 | ofs, &(packet_cache.write[1])); // Offset to path and flag. - peer->put_packet(packet_cache.ptr(), ofs + path_len); + multiplayer->send_command(P, packet_cache.ptr(), ofs + path_len); } } } |