From 7536d15fe3c5991b79aa54f0db3cf110e882e87a Mon Sep 17 00:00:00 2001 From: Fabio Alessandrelli Date: Sat, 8 Oct 2022 20:50:19 +0200 Subject: [MP] Let MultiplayerAPI handle packet relaying and peer signaling. MultiplayerPeer changes: - Adds is_server_relay_supported virtual method Informs the upper MultiplayerAPI layer if it can signal peers connected to the server to other clients, and perform packet relaying among them. - Adds get_packet_channel and get_packet_mode virtual methods Allows the MultiplayerAPI to retrieve the channel and transfer modes to use when relaying the last received packet. SceneMultiplayerPeer changes: - Implement peer signaling and packet relaying when the MultiplayerPeer advertise they are supported. ENet, WebRTC, WebSocket changes: - Removed custom code for relaying from WebSocket and ENet, and let it be handled by the upper layer. - Update WebRTC to split create_client, create_server, and create_mesh, with the latter behaving like the old initialize with "server_compatibility = false", and the first two supporting the upper layer relaying protocol. --- .../webrtc/doc_classes/WebRTCMultiplayerPeer.xml | 39 ++++++---- modules/webrtc/webrtc_multiplayer_peer.cpp | 85 ++++++++++++++++------ modules/webrtc/webrtc_multiplayer_peer.h | 20 ++++- 3 files changed, 106 insertions(+), 38 deletions(-) (limited to 'modules/webrtc') diff --git a/modules/webrtc/doc_classes/WebRTCMultiplayerPeer.xml b/modules/webrtc/doc_classes/WebRTCMultiplayerPeer.xml index 927888fe21..0b42c6ed35 100644 --- a/modules/webrtc/doc_classes/WebRTCMultiplayerPeer.xml +++ b/modules/webrtc/doc_classes/WebRTCMultiplayerPeer.xml @@ -6,7 +6,7 @@ This class constructs a full mesh of [WebRTCPeerConnection] (one connection for each peer) that can be used as a [member MultiplayerAPI.multiplayer_peer]. You can add each [WebRTCPeerConnection] via [method add_peer] or remove them via [method remove_peer]. Peers must be added in [constant WebRTCPeerConnection.STATE_NEW] state to allow it to create the appropriate channels. This class will not create offers nor set descriptions, it will only poll them, and notify connections and disconnections. - [signal MultiplayerPeer.connection_succeeded] and [signal MultiplayerPeer.server_disconnected] will not be emitted unless [code]server_compatibility[/code] is [code]true[/code] in [method initialize]. Beside that data transfer works like in a [MultiplayerPeer]. + [signal MultiplayerPeer.connection_succeeded] and [signal MultiplayerPeer.server_disconnected] will not be emitted unless the peer is created using [method create_client]. Beside that data transfer works like in a [MultiplayerPeer]. [b]Note:[/b] When exporting to Android, make sure to enable the [code]INTERNET[/code] permission in the Android export preset before exporting the project or using one-click deploy. Otherwise, network communication of any kind will be blocked by Android. @@ -28,6 +28,31 @@ Close all the add peer connections and channels, freeing all resources. + + + + + + Initialize the multiplayer peer as a client with the given [code]peer_id[/code] (must be between 2 and 2147483647). In this mode, you should only call [method add_peer] once and with [code]peer_id[/code] of [code]1[/code]. This mode enables [method MultiplayerPeer.is_server_relay_supported], allowing the upper [MultiplayerAPI] layer to perform peer exchange and packet relaying. + You can optionally specify a [code]channels_config[/code] array of [enum MultiplayerPeer.TransferMode] which will be used to create extra channels (WebRTC only supports one transfer mode per channel). + + + + + + + + Initialize the multiplayer peer as a mesh (i.e. all peers connect to each other) with the given [code]peer_id[/code] (must be between 1 and 2147483647). + + + + + + + Initialize the multiplayer peer as a server (with unique ID of [code]1[/code]). This mode enables [method MultiplayerPeer.is_server_relay_supported], allowing the upper [MultiplayerAPI] layer to perform peer exchange and packet relaying. + You can optionally specify a [code]channels_config[/code] array of [enum MultiplayerPeer.TransferMode] which will be used to create extra channels (WebRTC only supports one transfer mode per channel). + + @@ -48,18 +73,6 @@ Returns [code]true[/code] if the given [code]peer_id[/code] is in the peers map (it might not be connected though). - - - - - - - Initialize the multiplayer peer with the given [code]peer_id[/code] (must be between 1 and 2147483647). - If [code]server_compatibilty[/code] is [code]false[/code] (default), the multiplayer peer will be immediately in state [constant MultiplayerPeer.CONNECTION_CONNECTED] and [signal MultiplayerPeer.connection_succeeded] will not be emitted. - If [code]server_compatibilty[/code] is [code]true[/code] the peer will suppress all [signal MultiplayerPeer.peer_connected] signals until a peer with id [constant MultiplayerPeer.TARGET_PEER_SERVER] connects and then emit [signal MultiplayerPeer.connection_succeeded]. After that the signal [signal MultiplayerPeer.peer_connected] will be emitted for every already connected peer, and any new peer that might connect. If the server peer disconnects after that, signal [signal MultiplayerPeer.server_disconnected] will be emitted and state will become [constant MultiplayerPeer.CONNECTION_CONNECTED]. - You can optionally specify a [code]channels_config[/code] array of [enum MultiplayerPeer.TransferMode] which will be used to create extra channels (WebRTC only supports one transfer mode per channel). - - diff --git a/modules/webrtc/webrtc_multiplayer_peer.cpp b/modules/webrtc/webrtc_multiplayer_peer.cpp index e03b6b2473..5ea81d5a1b 100644 --- a/modules/webrtc/webrtc_multiplayer_peer.cpp +++ b/modules/webrtc/webrtc_multiplayer_peer.cpp @@ -34,7 +34,9 @@ #include "core/os/os.h" void WebRTCMultiplayerPeer::_bind_methods() { - ClassDB::bind_method(D_METHOD("initialize", "peer_id", "server_compatibility", "channels_config"), &WebRTCMultiplayerPeer::initialize, DEFVAL(false), DEFVAL(Array())); + ClassDB::bind_method(D_METHOD("create_server", "channels_config"), &WebRTCMultiplayerPeer::create_server, DEFVAL(Array())); + ClassDB::bind_method(D_METHOD("create_client", "peer_id", "channels_config"), &WebRTCMultiplayerPeer::create_client, DEFVAL(Array())); + ClassDB::bind_method(D_METHOD("create_mesh", "peer_id", "channels_config"), &WebRTCMultiplayerPeer::create_mesh, DEFVAL(Array())); ClassDB::bind_method(D_METHOD("add_peer", "peer", "peer_id", "unreliable_lifetime"), &WebRTCMultiplayerPeer::add_peer, DEFVAL(1)); ClassDB::bind_method(D_METHOD("remove_peer", "peer_id"), &WebRTCMultiplayerPeer::remove_peer); ClassDB::bind_method(D_METHOD("has_peer", "peer_id"), &WebRTCMultiplayerPeer::has_peer); @@ -52,6 +54,15 @@ int WebRTCMultiplayerPeer::get_packet_peer() const { return next_packet_peer; } +int WebRTCMultiplayerPeer::get_packet_channel() const { + return next_packet_channel < CH_RESERVED_MAX ? 0 : next_packet_channel - CH_RESERVED_MAX + 1; +} + +MultiplayerPeer::TransferMode WebRTCMultiplayerPeer::get_packet_mode() const { + ERR_FAIL_INDEX_V(next_packet_channel, channels_modes.size(), TRANSFER_MODE_RELIABLE); + return channels_modes[next_packet_channel]; +} + bool WebRTCMultiplayerPeer::is_server() const { return unique_id == TARGET_PEER_SERVER; } @@ -113,24 +124,14 @@ void WebRTCMultiplayerPeer::poll() { // Signal newly connected peers for (int &E : add) { // Already connected to server: simply notify new peer. - // NOTE: Mesh is always connected. - if (connection_status == CONNECTION_CONNECTED) { - emit_signal(SNAME("peer_connected"), E); - } - - // Server emulation mode suppresses peer_conencted until server connects. - if (server_compat && E == TARGET_PEER_SERVER) { + if (network_mode == MODE_CLIENT) { + ERR_CONTINUE(E != TARGET_PEER_SERVER); // Bug. // Server connected. connection_status = CONNECTION_CONNECTED; emit_signal(SNAME("peer_connected"), TARGET_PEER_SERVER); emit_signal(SNAME("connection_succeeded")); - // Notify of all previously connected peers - for (const KeyValue> &F : peer_map) { - if (F.key != 1 && F.value->connected) { - emit_signal(SNAME("peer_connected"), F.key); - } - } - break; // Because we already notified of all newly added peers. + } else { + emit_signal(SNAME("peer_connected"), E); } } // Fetch next packet @@ -150,11 +151,14 @@ void WebRTCMultiplayerPeer::_find_next_peer() { ++E; continue; } + int idx = 0; for (const Ref &F : E->value->channels) { if (F->get_available_packet_count()) { + next_packet_channel = idx; next_packet_peer = E->key; return; } + idx++; } ++E; } @@ -165,11 +169,14 @@ void WebRTCMultiplayerPeer::_find_next_peer() { ++E; continue; } + int idx = 0; for (const Ref &F : E->value->channels) { if (F->get_available_packet_count()) { + next_packet_channel = idx; next_packet_peer = E->key; return; } + idx++; } if (E->key == (int)next_packet_peer) { break; @@ -177,6 +184,7 @@ void WebRTCMultiplayerPeer::_find_next_peer() { ++E; } // No packet found + next_packet_channel = 0; next_packet_peer = 0; } @@ -184,11 +192,28 @@ MultiplayerPeer::ConnectionStatus WebRTCMultiplayerPeer::get_connection_status() return connection_status; } -Error WebRTCMultiplayerPeer::initialize(int p_self_id, bool p_server_compat, Array p_channels_config) { +Error WebRTCMultiplayerPeer::create_server(Array p_channels_config) { + return _initialize(1, MODE_SERVER, p_channels_config); +} + +Error WebRTCMultiplayerPeer::create_client(int p_self_id, Array p_channels_config) { + ERR_FAIL_COND_V_MSG(p_self_id == 1, ERR_INVALID_PARAMETER, "Clients cannot have ID 1."); + return _initialize(p_self_id, MODE_CLIENT, p_channels_config); +} + +Error WebRTCMultiplayerPeer::create_mesh(int p_self_id, Array p_channels_config) { + return _initialize(p_self_id, MODE_MESH, p_channels_config); +} + +Error WebRTCMultiplayerPeer::_initialize(int p_self_id, NetworkMode p_mode, Array p_channels_config) { ERR_FAIL_COND_V(p_self_id < 1 || p_self_id > ~(1 << 31), ERR_INVALID_PARAMETER); channels_config.clear(); + channels_modes.clear(); + channels_modes.push_back(TRANSFER_MODE_RELIABLE); + channels_modes.push_back(TRANSFER_MODE_UNRELIABLE_ORDERED); + channels_modes.push_back(TRANSFER_MODE_UNRELIABLE); for (int i = 0; i < p_channels_config.size(); i++) { - ERR_FAIL_COND_V_MSG(p_channels_config[i].get_type() != Variant::INT, ERR_INVALID_PARAMETER, "The 'channels_config' array must contain only enum values from 'MultiplayerPeer.Multiplayer::TransferMode'"); + ERR_FAIL_COND_V_MSG(p_channels_config[i].get_type() != Variant::INT, ERR_INVALID_PARAMETER, "The 'channels_config' array must contain only enum values from 'MultiplayerPeer.TransferMode'"); int mode = p_channels_config[i].operator int(); // Initialize data channel configurations. Dictionary cfg; @@ -207,16 +232,17 @@ Error WebRTCMultiplayerPeer::initialize(int p_self_id, bool p_server_compat, Arr case TRANSFER_MODE_RELIABLE: break; default: - ERR_FAIL_V_MSG(ERR_INVALID_PARAMETER, vformat("The 'channels_config' array must contain only enum values from 'MultiplayerPeer.Multiplayer::TransferMode'. Got: %d", mode)); + ERR_FAIL_V_MSG(ERR_INVALID_PARAMETER, vformat("The 'channels_config' array must contain only enum values from 'MultiplayerPeer.TransferMode'. Got: %d", mode)); } channels_config.push_back(cfg); + channels_modes.push_back((TransferMode)mode); } unique_id = p_self_id; - server_compat = p_server_compat; + network_mode = p_mode; // Mesh and server are always connected - if (!server_compat || p_self_id == 1) { + if (p_mode != MODE_CLIENT) { connection_status = CONNECTION_CONNECTED; } else { connection_status = CONNECTION_CONNECTING; @@ -224,6 +250,10 @@ Error WebRTCMultiplayerPeer::initialize(int p_self_id, bool p_server_compat, Arr return OK; } +bool WebRTCMultiplayerPeer::is_server_relay_supported() const { + return network_mode == MODE_SERVER || network_mode == MODE_CLIENT; +} + int WebRTCMultiplayerPeer::get_unique_id() const { ERR_FAIL_COND_V(connection_status == CONNECTION_DISCONNECTED, 1); return unique_id; @@ -261,7 +291,10 @@ Dictionary WebRTCMultiplayerPeer::get_peers() { } Error WebRTCMultiplayerPeer::add_peer(Ref p_peer, int p_peer_id, int p_unreliable_lifetime) { - ERR_FAIL_COND_V(p_peer_id < 0 || p_peer_id > ~(1 << 31), ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(network_mode == MODE_NONE, ERR_UNCONFIGURED); + ERR_FAIL_COND_V(network_mode == MODE_CLIENT && p_peer_id != 1, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(network_mode == MODE_SERVER && p_peer_id == 1, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_peer_id < 1 || p_peer_id > ~(1 << 31), ERR_INVALID_PARAMETER); ERR_FAIL_COND_V(p_unreliable_lifetime < 0, ERR_INVALID_PARAMETER); ERR_FAIL_COND_V(is_refusing_new_connections(), ERR_UNAUTHORIZED); // Peer must be valid, and in new state (to create data channels) @@ -308,8 +341,12 @@ void WebRTCMultiplayerPeer::remove_peer(int p_peer_id) { if (peer->connected) { peer->connected = false; emit_signal(SNAME("peer_disconnected"), p_peer_id); - if (server_compat && p_peer_id == TARGET_PEER_SERVER) { - emit_signal(SNAME("server_disconnected")); + if (network_mode == MODE_CLIENT && p_peer_id == TARGET_PEER_SERVER) { + if (connection_status == CONNECTION_CONNECTING) { + emit_signal(SNAME("connection_failed")); + } else { + emit_signal(SNAME("server_disconnected")); + } connection_status = CONNECTION_DISCONNECTED; } } @@ -403,7 +440,9 @@ void WebRTCMultiplayerPeer::close() { channels_config.clear(); unique_id = 0; next_packet_peer = 0; + next_packet_channel = 0; target_peer = 0; + network_mode = MODE_NONE; connection_status = CONNECTION_DISCONNECTED; } diff --git a/modules/webrtc/webrtc_multiplayer_peer.h b/modules/webrtc/webrtc_multiplayer_peer.h index ea7c60036b..3f608200fd 100644 --- a/modules/webrtc/webrtc_multiplayer_peer.h +++ b/modules/webrtc/webrtc_multiplayer_peer.h @@ -48,6 +48,13 @@ private: CH_RESERVED_MAX = 3 }; + enum NetworkMode { + MODE_NONE, + MODE_SERVER, + MODE_CLIENT, + MODE_MESH, + }; + class ConnectedPeer : public RefCounted { public: Ref connection; @@ -67,19 +74,25 @@ private: int client_count = 0; ConnectionStatus connection_status = CONNECTION_DISCONNECTED; int next_packet_peer = 0; - bool server_compat = false; + int next_packet_channel = 0; + NetworkMode network_mode = MODE_NONE; HashMap> peer_map; + List channels_modes; List channels_config; void _peer_to_dict(Ref p_connected_peer, Dictionary &r_dict); void _find_next_peer(); + Ref _get_next_peer(); + Error _initialize(int p_self_id, NetworkMode p_mode, Array p_channels_config = Array()); public: WebRTCMultiplayerPeer() {} ~WebRTCMultiplayerPeer(); - Error initialize(int p_self_id, bool p_server_compat = false, Array p_channels_config = Array()); + Error create_server(Array p_channels_config = Array()); + Error create_client(int p_self_id, Array p_channels_config = Array()); + Error create_mesh(int p_self_id, Array p_channels_config = Array()); Error add_peer(Ref p_peer, int p_peer_id, int p_unreliable_lifetime = 1); void remove_peer(int p_peer_id); bool has_peer(int p_peer_id); @@ -98,8 +111,11 @@ public: int get_unique_id() const override; int get_packet_peer() const override; + int get_packet_channel() const override; + TransferMode get_packet_mode() const override; bool is_server() const override; + bool is_server_relay_supported() const override; void poll() override; -- cgit v1.2.3