[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.
This commit is contained in:
Fabio Alessandrelli 2022-10-08 20:50:19 +02:00
parent 03e5de37ae
commit 7536d15fe3
17 changed files with 465 additions and 446 deletions

View file

@ -25,10 +25,22 @@
Returns the current state of the connection. See [enum ConnectionStatus].
</description>
</method>
<method name="get_packet_channel" qualifiers="const">
<return type="int" />
<description>
Returns the channel over which the next available packet was received. See [method PacketPeer.get_available_packet_count].
</description>
</method>
<method name="get_packet_mode" qualifiers="const">
<return type="int" enum="MultiplayerPeer.TransferMode" />
<description>
Returns the [enum MultiplayerPeer.TransferMode] the remote peer used to send the next available packet. See [method PacketPeer.get_available_packet_count].
</description>
</method>
<method name="get_packet_peer" qualifiers="const">
<return type="int" />
<description>
Returns the ID of the [MultiplayerPeer] who sent the most recent packet.
Returns the ID of the [MultiplayerPeer] who sent the next available packet. See [method PacketPeer.get_available_packet_count].
</description>
</method>
<method name="get_unique_id" qualifiers="const">
@ -37,6 +49,12 @@
Returns the ID of this [MultiplayerPeer].
</description>
</method>
<method name="is_server_relay_supported" qualifiers="const">
<return type="bool" />
<description>
Returns true if the server can act as a relay in the current configuration (i.e. if the higher level [MultiplayerAPI] should notify connected clients of other peers, and implement a relay protocol to allow communication between them).
</description>
</method>
<method name="poll">
<return type="void" />
<description>

View file

@ -77,8 +77,5 @@
<member name="host" type="ENetConnection" setter="" getter="get_host">
The underlying [ENetConnection] created after [method create_client] and [method create_server].
</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.
</member>
</members>
</class>

View file

@ -44,6 +44,22 @@ int ENetMultiplayerPeer::get_packet_peer() const {
return incoming_packets.front()->get().from;
}
MultiplayerPeer::TransferMode ENetMultiplayerPeer::get_packet_mode() const {
ERR_FAIL_COND_V_MSG(!_is_active(), TRANSFER_MODE_RELIABLE, "The multiplayer instance isn't currently active.");
ERR_FAIL_COND_V(incoming_packets.size() == 0, TRANSFER_MODE_RELIABLE);
return incoming_packets.front()->get().transfer_mode;
}
int ENetMultiplayerPeer::get_packet_channel() const {
ERR_FAIL_COND_V_MSG(!_is_active(), 1, "The multiplayer instance isn't currently active.");
ERR_FAIL_COND_V(incoming_packets.size() == 0, 1);
int ch = incoming_packets.front()->get().channel;
if (ch >= SYSCH_MAX) { // First 2 channels are reserved.
return ch - SYSCH_MAX + 1;
}
return 0;
}
Error ENetMultiplayerPeer::create_server(int p_port, int p_max_clients, int p_max_channels, int p_in_bandwidth, int p_out_bandwidth) {
ERR_FAIL_COND_V_MSG(_is_active(), ERR_ALREADY_IN_USE, "The multiplayer instance is already active.");
set_refuse_new_connections(false);
@ -114,6 +130,22 @@ Error ENetMultiplayerPeer::add_mesh_peer(int p_id, Ref<ENetConnection> p_host) {
return OK;
}
void ENetMultiplayerPeer::_store_packet(int32_t p_source, ENetConnection::Event &p_event) {
Packet packet;
packet.packet = p_event.packet;
packet.channel = p_event.channel_id;
packet.from = p_source;
if (p_event.packet->flags & ENET_PACKET_FLAG_RELIABLE) {
packet.transfer_mode = TRANSFER_MODE_RELIABLE;
} else if (p_event.packet->flags & ENET_PACKET_FLAG_UNSEQUENCED) {
packet.transfer_mode = TRANSFER_MODE_UNRELIABLE;
} else {
packet.transfer_mode = TRANSFER_MODE_UNRELIABLE_ORDERED;
}
packet.packet->referenceCount++;
incoming_packets.push_back(packet);
}
bool ENetMultiplayerPeer::_parse_server_event(ENetConnection::EventType p_type, ENetConnection::Event &p_event) {
switch (p_type) {
case ENetConnection::EVENT_CONNECT: {
@ -131,9 +163,6 @@ bool ENetMultiplayerPeer::_parse_server_event(ENetConnection::EventType p_type,
peers[id] = p_event.peer;
emit_signal(SNAME("peer_connected"), id);
if (server_relay) {
_notify_peers(id, true);
}
return false;
}
case ENetConnection::EVENT_DISCONNECT: {
@ -145,50 +174,11 @@ bool ENetMultiplayerPeer::_parse_server_event(ENetConnection::EventType p_type,
emit_signal(SNAME("peer_disconnected"), id);
peers.erase(id);
if (server_relay) {
_notify_peers(id, false);
}
return false;
}
case ENetConnection::EVENT_RECEIVE: {
if (p_event.channel_id == SYSCH_CONFIG) {
_destroy_unused(p_event.packet);
ERR_FAIL_V_MSG(false, "Only server can send config messages");
} else {
if (p_event.packet->dataLength < 8) {
_destroy_unused(p_event.packet);
ERR_FAIL_V_MSG(false, "Invalid packet size");
}
uint32_t source = decode_uint32(&p_event.packet->data[0]);
int target = decode_uint32(&p_event.packet->data[4]);
uint32_t id = p_event.peer->get_meta(SNAME("_net_id"));
// Someone is cheating and trying to fake the source!
if (source != id) {
_destroy_unused(p_event.packet);
ERR_FAIL_V_MSG(false, "Someone is cheating and trying to fake the source!");
}
Packet packet;
packet.packet = p_event.packet;
packet.channel = p_event.channel_id;
packet.from = id;
// Even if relaying is disabled, these targets are valid as incoming packets.
if (target == 1 || target == 0 || target < -1) {
packet.packet->referenceCount++;
incoming_packets.push_back(packet);
}
if (server_relay && target != 1) {
packet.packet->referenceCount++;
_relay(source, target, p_event.channel_id, p_event.packet);
packet.packet->referenceCount--;
_destroy_unused(p_event.packet);
}
// Destroy packet later
}
int32_t source = p_event.peer->get_meta(SNAME("_net_id"));
_store_packet(source, p_event);
return false;
}
default:
@ -215,44 +205,7 @@ bool ENetMultiplayerPeer::_parse_client_event(ENetConnection::EventType p_type,
return true;
}
case ENetConnection::EVENT_RECEIVE: {
if (p_event.channel_id == SYSCH_CONFIG) {
// Config message
if (p_event.packet->dataLength != 8) {
_destroy_unused(p_event.packet);
ERR_FAIL_V(false);
}
int msg = decode_uint32(&p_event.packet->data[0]);
int id = decode_uint32(&p_event.packet->data[4]);
switch (msg) {
case SYSMSG_ADD_PEER: {
peers[id] = Ref<ENetPacketPeer>();
emit_signal(SNAME("peer_connected"), id);
} break;
case SYSMSG_REMOVE_PEER: {
peers.erase(id);
emit_signal(SNAME("peer_disconnected"), id);
} break;
}
_destroy_unused(p_event.packet);
} else {
if (p_event.packet->dataLength < 8) {
_destroy_unused(p_event.packet);
ERR_FAIL_V_MSG(false, "Invalid packet size");
}
uint32_t source = decode_uint32(&p_event.packet->data[0]);
Packet packet;
packet.packet = p_event.packet;
packet.from = source;
packet.channel = p_event.channel_id;
packet.packet->referenceCount++;
incoming_packets.push_back(packet);
// Destroy packet later
}
_store_packet(1, p_event);
return false;
}
default:
@ -273,18 +226,7 @@ bool ENetMultiplayerPeer::_parse_mesh_event(ENetConnection::EventType p_type, EN
hosts.erase(p_peer_id);
return true;
case ENetConnection::EVENT_RECEIVE: {
if (p_event.packet->dataLength < 8) {
_destroy_unused(p_event.packet);
ERR_FAIL_V_MSG(false, "Invalid packet size");
}
Packet packet;
packet.packet = p_event.packet;
packet.from = p_peer_id;
packet.channel = p_event.channel_id;
packet.packet->referenceCount++;
incoming_packets.push_back(packet);
_store_packet(p_peer_id, p_event);
return false;
} break;
default:
@ -376,6 +318,10 @@ bool ENetMultiplayerPeer::is_server() const {
return active_mode == MODE_SERVER;
}
bool ENetMultiplayerPeer::is_server_relay_supported() const {
return active_mode == MODE_SERVER || active_mode == MODE_CLIENT;
}
void ENetMultiplayerPeer::close_connection(uint32_t wait_usec) {
if (!_is_active()) {
return;
@ -422,8 +368,8 @@ Error ENetMultiplayerPeer::get_packet(const uint8_t **r_buffer, int &r_buffer_si
current_packet = incoming_packets.front()->get();
incoming_packets.pop_front();
*r_buffer = (const uint8_t *)(&current_packet.packet->data[8]);
r_buffer_size = current_packet.packet->dataLength - 8;
*r_buffer = (const uint8_t *)(current_packet.packet->data);
r_buffer_size = current_packet.packet->dataLength;
return OK;
}
@ -457,15 +403,13 @@ Error ENetMultiplayerPeer::put_packet(const uint8_t *p_buffer, int p_buffer_size
}
#ifdef DEBUG_ENABLED
if ((packet_flags & ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT) && p_buffer_size + 8 > ENET_HOST_DEFAULT_MTU) {
WARN_PRINT_ONCE(vformat("Sending %d bytes unrealiably which is above the MTU (%d), this will result in higher packet loss", p_buffer_size + 8, ENET_HOST_DEFAULT_MTU));
if ((packet_flags & ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT) && p_buffer_size > ENET_HOST_DEFAULT_MTU) {
WARN_PRINT_ONCE(vformat("Sending %d bytes unrealiably which is above the MTU (%d), this will result in higher packet loss", p_buffer_size, ENET_HOST_DEFAULT_MTU));
}
#endif
ENetPacket *packet = enet_packet_create(nullptr, p_buffer_size + 8, packet_flags);
encode_uint32(unique_id, &packet->data[0]); // Source ID
encode_uint32(target_peer, &packet->data[4]); // Dest ID
memcpy(&packet->data[8], p_buffer, p_buffer_size);
ENetPacket *packet = enet_packet_create(nullptr, p_buffer_size, packet_flags);
memcpy(&packet->data[0], p_buffer, p_buffer_size);
if (is_server()) {
if (target_peer == 0) {
@ -548,16 +492,6 @@ void ENetMultiplayerPeer::set_refuse_new_connections(bool p_enabled) {
MultiplayerPeer::set_refuse_new_connections(p_enabled);
}
void ENetMultiplayerPeer::set_server_relay_enabled(bool p_enabled) {
ERR_FAIL_COND_MSG(_is_active(), "Server relaying can't be toggled while the multiplayer instance is active.");
server_relay = p_enabled;
}
bool ENetMultiplayerPeer::is_server_relay_enabled() const {
return server_relay;
}
Ref<ENetConnection> ENetMultiplayerPeer::get_host() const {
ERR_FAIL_COND_V(!_is_active(), nullptr);
ERR_FAIL_COND_V(active_mode == MODE_MESH, nullptr);
@ -577,70 +511,6 @@ void ENetMultiplayerPeer::_destroy_unused(ENetPacket *p_packet) {
}
}
void ENetMultiplayerPeer::_relay(int p_from, int p_to, enet_uint8 p_channel, ENetPacket *p_packet) {
if (p_to == 0) {
// Re-send to everyone but sender :|
for (KeyValue<int, Ref<ENetPacketPeer>> &E : peers) {
if (E.key == p_from) {
continue;
}
E.value->send(p_channel, p_packet);
}
} else if (p_to < 0) {
// Re-send to everyone but excluded and sender.
for (KeyValue<int, Ref<ENetPacketPeer>> &E : peers) {
if (E.key == p_from || E.key == -p_to) { // Do not resend to self, also do not send to excluded
continue;
}
E.value->send(p_channel, p_packet);
}
} else {
// To someone else, specifically
ERR_FAIL_COND(!peers.has(p_to));
ENetPacket *packet = enet_packet_create(p_packet->data, p_packet->dataLength, p_packet->flags);
peers[p_to]->send(p_channel, packet);
}
}
void ENetMultiplayerPeer::_notify_peers(int p_id, bool p_connected) {
if (p_connected) {
ERR_FAIL_COND(!peers.has(p_id));
// Someone connected, notify all the peers available.
Ref<ENetPacketPeer> peer = peers[p_id];
ENetPacket *packet = enet_packet_create(nullptr, 8, ENET_PACKET_FLAG_RELIABLE);
encode_uint32(SYSMSG_ADD_PEER, &packet->data[0]);
encode_uint32(p_id, &packet->data[4]);
for (KeyValue<int, Ref<ENetPacketPeer>> &E : peers) {
if (E.key == p_id) {
continue;
}
// Send new peer to existing peer.
E.value->send(SYSCH_CONFIG, packet);
// Send existing peer to new peer.
// This packet will be automatically destroyed by ENet after send.
ENetPacket *packet2 = enet_packet_create(nullptr, 8, ENET_PACKET_FLAG_RELIABLE);
encode_uint32(SYSMSG_ADD_PEER, &packet2->data[0]);
encode_uint32(E.key, &packet2->data[4]);
peer->send(SYSCH_CONFIG, packet2);
}
_destroy_unused(packet);
} else {
// Server just received a client disconnect and is in relay mode, notify everyone else.
ENetPacket *packet = enet_packet_create(nullptr, 8, ENET_PACKET_FLAG_RELIABLE);
encode_uint32(SYSMSG_REMOVE_PEER, &packet->data[0]);
encode_uint32(p_id, &packet->data[4]);
for (KeyValue<int, Ref<ENetPacketPeer>> &E : peers) {
if (E.key == p_id) {
continue;
}
E.value->send(SYSCH_CONFIG, packet);
}
_destroy_unused(packet);
}
}
void ENetMultiplayerPeer::_bind_methods() {
ClassDB::bind_method(D_METHOD("create_server", "port", "max_clients", "max_channels", "in_bandwidth", "out_bandwidth"), &ENetMultiplayerPeer::create_server, DEFVAL(32), DEFVAL(0), DEFVAL(0), DEFVAL(0));
ClassDB::bind_method(D_METHOD("create_client", "address", "port", "channel_count", "in_bandwidth", "out_bandwidth", "local_port"), &ENetMultiplayerPeer::create_client, DEFVAL(0), DEFVAL(0), DEFVAL(0), DEFVAL(0));
@ -649,12 +519,9 @@ void ENetMultiplayerPeer::_bind_methods() {
ClassDB::bind_method(D_METHOD("close_connection", "wait_usec"), &ENetMultiplayerPeer::close_connection, DEFVAL(100));
ClassDB::bind_method(D_METHOD("set_bind_ip", "ip"), &ENetMultiplayerPeer::set_bind_ip);
ClassDB::bind_method(D_METHOD("set_server_relay_enabled", "enabled"), &ENetMultiplayerPeer::set_server_relay_enabled);
ClassDB::bind_method(D_METHOD("is_server_relay_enabled"), &ENetMultiplayerPeer::is_server_relay_enabled);
ClassDB::bind_method(D_METHOD("get_host"), &ENetMultiplayerPeer::get_host);
ClassDB::bind_method(D_METHOD("get_peer", "id"), &ENetMultiplayerPeer::get_peer);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "server_relay"), "set_server_relay_enabled", "is_server_relay_enabled");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "host", PROPERTY_HINT_RESOURCE_TYPE, "ENetConnection", PROPERTY_USAGE_NONE), "", "get_host");
}

View file

@ -47,10 +47,9 @@ private:
};
enum {
SYSCH_CONFIG = 0,
SYSCH_RELIABLE = 1,
SYSCH_UNRELIABLE = 2,
SYSCH_MAX = 3
SYSCH_RELIABLE = 0,
SYSCH_UNRELIABLE = 1,
SYSCH_MAX = 2
};
enum Mode {
@ -66,8 +65,6 @@ private:
int target_peer = 0;
bool server_relay = true;
ConnectionStatus connection_status = CONNECTION_DISCONNECTED;
HashMap<int, Ref<ENetConnection>> hosts;
@ -77,18 +74,18 @@ private:
ENetPacket *packet = nullptr;
int from = 0;
int channel = 0;
TransferMode transfer_mode = TRANSFER_MODE_RELIABLE;
};
List<Packet> incoming_packets;
Packet current_packet;
void _store_packet(int32_t p_source, ENetConnection::Event &p_event);
void _pop_current_packet();
bool _parse_server_event(ENetConnection::EventType p_event_type, ENetConnection::Event &p_event);
bool _parse_client_event(ENetConnection::EventType p_event_type, ENetConnection::Event &p_event);
bool _parse_mesh_event(ENetConnection::EventType p_event_type, ENetConnection::Event &p_event, int p_peer_id);
void _relay(int p_from, int p_to, enet_uint8 p_channel, ENetPacket *p_packet);
void _notify_peers(int p_id, bool p_connected);
void _destroy_unused(ENetPacket *p_packet);
_FORCE_INLINE_ bool _is_active() const { return active_mode != MODE_NONE; }
@ -99,10 +96,15 @@ protected:
public:
virtual void set_target_peer(int p_peer) override;
virtual int get_packet_peer() const override;
virtual TransferMode get_packet_mode() const override;
virtual int get_packet_channel() const override;
virtual void poll() override;
virtual bool is_server() const override;
virtual bool is_server_relay_supported() const override;
// Overridden so we can instrument the DTLSServer when needed.
virtual void set_refuse_new_connections(bool p_enabled) override;
@ -125,8 +127,6 @@ public:
void disconnect_peer(int p_peer, bool now = false);
void set_bind_ip(const IPAddress &p_ip);
void set_server_relay_enabled(bool p_enabled);
bool is_server_relay_enabled() const;
Ref<ENetConnection> get_host() const;
Ref<ENetPacketPeer> get_peer(int p_id) const;

View file

@ -42,6 +42,10 @@
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_packet">

View file

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

View file

@ -67,12 +67,20 @@ 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 (len && (packet[0] & CMD_MASK) == NETWORK_COMMAND_SYS) {
// Sys messages are processed separately since they might call _process_packet themselves.
_process_sys(sender, packet, len, mode, channel);
} else {
remote_sender_id = sender;
_process_packet(sender, packet, len);
remote_sender_id = 0;
}
if (!multiplayer_peer.is_valid()) {
return OK; // It's also possible that a packet or RPC caused a disconnection, so also check here.
@ -86,6 +94,7 @@ void SceneMultiplayer::clear() {
connected_peers.clear();
packet_cache.clear();
cache->clear();
relay_buffer->clear();
}
void SceneMultiplayer::set_root_path(const NodePath &p_path) {
@ -166,10 +175,123 @@ 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) {
for (const int &pid : connected_peers) {
if (pid == -p_to) {
continue;
}
multiplayer_peer->set_target_peer(pid);
multiplayer_peer->put_packet(p_packet, p_packet_len);
}
return OK;
} else {
multiplayer_peer->set_target_peer(p_to);
return multiplayer_peer->put_packet(p_packet, p_packet_len);
}
}
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);
_add_peer(peer);
} 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 (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);
@ -177,6 +299,23 @@ void SceneMultiplayer::_add_peer(int p_id) {
}
void SceneMultiplayer::_del_peer(int p_id) {
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);
@ -209,11 +348,9 @@ 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 multiplayer_peer->put_packet(packet_cache.ptr(), p_data.size() + 1);
return send_command(p_to, packet_cache.ptr(), p_data.size() + 1);
}
void SceneMultiplayer::_process_raw(int p_from, const uint8_t *p_packet, int p_packet_len) {
@ -303,6 +440,15 @@ 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);
@ -311,17 +457,22 @@ void SceneMultiplayer::_bind_methods() {
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::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_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)));

View file

@ -49,6 +49,17 @@ public:
NETWORK_COMMAND_SPAWN,
NETWORK_COMMAND_DESPAWN,
NETWORK_COMMAND_SYNC,
NETWORK_COMMAND_SYS,
};
enum SysCommands {
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.
@ -74,6 +85,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,6 +97,7 @@ 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 _del_peer(int p_id);
@ -111,6 +125,7 @@ public:
void set_root_path(const NodePath &p_path);
NodePath get_root_path() const;
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 +138,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

View file

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

View file

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

View file

@ -6,7 +6,7 @@
<description>
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.
</description>
<tutorials>
@ -28,6 +28,31 @@
Close all the add peer connections and channels, freeing all resources.
</description>
</method>
<method name="create_client">
<return type="int" enum="Error" />
<param index="0" name="peer_id" type="int" />
<param index="1" name="channels_config" type="Array" default="[]" />
<description>
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).
</description>
</method>
<method name="create_mesh">
<return type="int" enum="Error" />
<param index="0" name="peer_id" type="int" />
<param index="1" name="channels_config" type="Array" default="[]" />
<description>
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).
</description>
</method>
<method name="create_server">
<return type="int" enum="Error" />
<param index="0" name="channels_config" type="Array" default="[]" />
<description>
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).
</description>
</method>
<method name="get_peer">
<return type="Dictionary" />
<param index="0" name="peer_id" type="int" />
@ -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).
</description>
</method>
<method name="initialize">
<return type="int" enum="Error" />
<param index="0" name="peer_id" type="int" />
<param index="1" name="server_compatibility" type="bool" default="false" />
<param index="2" name="channels_config" type="Array" default="[]" />
<description>
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).
</description>
</method>
<method name="remove_peer">
<return type="void" />
<param index="0" name="peer_id" type="int" />

View file

@ -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<int, Ref<ConnectedPeer>> &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<WebRTCDataChannel> &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<WebRTCDataChannel> &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<WebRTCPeerConnection> 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;
}

View file

@ -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<WebRTCPeerConnection> 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<int, Ref<ConnectedPeer>> peer_map;
List<TransferMode> channels_modes;
List<Dictionary> channels_config;
void _peer_to_dict(Ref<ConnectedPeer> p_connected_peer, Dictionary &r_dict);
void _find_next_peer();
Ref<ConnectedPeer> _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<WebRTCPeerConnection> 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;

View file

@ -142,12 +142,21 @@ Error WebSocketMultiplayerPeer::get_packet(const uint8_t **r_buffer, int &r_buff
Error WebSocketMultiplayerPeer::put_packet(const uint8_t *p_buffer, int p_buffer_size) {
ERR_FAIL_COND_V(get_connection_status() != CONNECTION_CONNECTED, ERR_UNCONFIGURED);
Vector<uint8_t> buffer = _make_pkt(SYS_NONE, get_unique_id(), target_peer, p_buffer, p_buffer_size);
if (is_server()) {
return _server_relay(1, target_peer, &(buffer.ptr()[0]), buffer.size());
if (target_peer > 0) {
ERR_FAIL_COND_V_MSG(!peers_map.has(target_peer), ERR_INVALID_PARAMETER, "Peer not found: " + itos(target_peer));
get_peer(target_peer)->put_packet(p_buffer, p_buffer_size);
} else {
for (KeyValue<int, Ref<WebSocketPeer>> &E : peers_map) {
if (target_peer && -target_peer == E.key) {
continue; // Excluded.
}
E.value->put_packet(p_buffer, p_buffer_size);
}
}
return OK;
} else {
return get_peer(1)->put_packet(&(buffer.ptr()[0]), buffer.size());
return get_peer(1)->put_packet(p_buffer, p_buffer_size);
}
}
@ -219,8 +228,41 @@ void WebSocketMultiplayerPeer::_poll_client() {
peer->poll(); // Update state and fetch packets.
WebSocketPeer::State ready_state = peer->get_ready_state();
if (ready_state == WebSocketPeer::STATE_OPEN) {
while (peer->get_available_packet_count()) {
_process_multiplayer(peer, 1);
if (connection_status == CONNECTION_CONNECTING) {
if (peer->get_available_packet_count() > 0) {
const uint8_t *in_buffer;
int size = 0;
Error err = peer->get_packet(&in_buffer, size);
if (err != OK || size != 4) {
peer->close(); // Will cause connection error on next poll.
ERR_FAIL_MSG("Invalid ID received from server");
}
unique_id = *((int32_t *)in_buffer);
if (unique_id < 2) {
peer->close(); // Will cause connection error on next poll.
ERR_FAIL_MSG("Invalid ID received from server");
}
connection_status = CONNECTION_CONNECTED;
emit_signal("peer_connected", 1);
emit_signal("connection_succeeded");
} else {
return; // Still waiting for an ID.
}
}
int pkts = peer->get_available_packet_count();
while (pkts > 0 && peer->get_ready_state() == WebSocketPeer::STATE_OPEN) {
const uint8_t *in_buffer;
int size = 0;
Error err = peer->get_packet(&in_buffer, size);
ERR_FAIL_COND(err != OK);
ERR_FAIL_COND(size <= 0);
Packet packet;
packet.data = (uint8_t *)memalloc(size);
memcpy(packet.data, in_buffer, size);
packet.size = size;
packet.source = 1;
incoming_packets.push_back(packet);
pkts--;
}
} else if (peer->get_ready_state() == WebSocketPeer::STATE_CLOSED) {
if (connection_status == CONNECTION_CONNECTED) {
@ -278,9 +320,14 @@ void WebSocketMultiplayerPeer::_poll_server() {
// The user does not want new connections, dropping it.
continue;
}
peers_map[id] = peer.ws;
_send_ack(peer.ws, id);
emit_signal("peer_connected", id);
int32_t peer_id = id;
Error err = peer.ws->put_packet((const uint8_t *)&peer_id, sizeof(peer_id));
if (err == OK) {
peers_map[id] = peer.ws;
emit_signal("peer_connected", id);
} else {
ERR_PRINT("Failed to send ID to newly connected peer.");
}
continue;
} else if (state == WebSocketPeer::STATE_CONNECTING) {
continue; // Still connecting.
@ -338,8 +385,19 @@ void WebSocketMultiplayerPeer::_poll_server() {
}
// Fetch packets
int pkts = ws->get_available_packet_count();
while (pkts && ws->get_ready_state() == WebSocketPeer::STATE_OPEN) {
_process_multiplayer(ws, id);
while (pkts > 0 && ws->get_ready_state() == WebSocketPeer::STATE_OPEN) {
const uint8_t *in_buffer;
int size = 0;
Error err = ws->get_packet(&in_buffer, size);
if (err != OK || size <= 0) {
break;
}
Packet packet;
packet.data = (uint8_t *)memalloc(size);
memcpy(packet.data, in_buffer, size);
packet.size = size;
packet.source = E.key;
incoming_packets.push_back(packet);
pkts--;
}
}
@ -371,180 +429,6 @@ Ref<WebSocketPeer> WebSocketMultiplayerPeer::get_peer(int p_id) const {
return peers_map[p_id];
}
void WebSocketMultiplayerPeer::_send_sys(Ref<WebSocketPeer> p_peer, uint8_t p_type, int32_t p_peer_id) {
ERR_FAIL_COND(!p_peer.is_valid());
ERR_FAIL_COND(p_peer->get_ready_state() != WebSocketPeer::STATE_OPEN);
Vector<uint8_t> message = _make_pkt(p_type, 1, 0, (uint8_t *)&p_peer_id, 4);
p_peer->put_packet(&(message.ptr()[0]), message.size());
}
Vector<uint8_t> WebSocketMultiplayerPeer::_make_pkt(uint8_t p_type, int32_t p_from, int32_t p_to, const uint8_t *p_data, uint32_t p_data_size) {
Vector<uint8_t> out;
out.resize(PROTO_SIZE + p_data_size);
uint8_t *w = out.ptrw();
memcpy(&w[0], &p_type, 1);
memcpy(&w[1], &p_from, 4);
memcpy(&w[5], &p_to, 4);
memcpy(&w[PROTO_SIZE], p_data, p_data_size);
return out;
}
void WebSocketMultiplayerPeer::_send_ack(Ref<WebSocketPeer> p_peer, int32_t p_peer_id) {
ERR_FAIL_COND(p_peer.is_null());
// First of all, confirm the ID!
_send_sys(p_peer, SYS_ID, p_peer_id);
// Then send the server peer (which will trigger connection_succeded in client)
_send_sys(p_peer, SYS_ADD, 1);
for (const KeyValue<int, Ref<WebSocketPeer>> &E : peers_map) {
ERR_CONTINUE(E.value.is_null());
int32_t id = E.key;
if (p_peer_id == id) {
continue; // Skip the newly added peer (already confirmed)
}
// Send new peer to others
_send_sys(E.value, SYS_ADD, p_peer_id);
// Send others to new peer
_send_sys(E.value, SYS_ADD, id);
}
}
void WebSocketMultiplayerPeer::_send_del(int32_t p_peer_id) {
for (const KeyValue<int, Ref<WebSocketPeer>> &E : peers_map) {
int32_t id = E.key;
if (p_peer_id != id) {
_send_sys(E.value, SYS_DEL, p_peer_id);
}
}
}
void WebSocketMultiplayerPeer::_store_pkt(int32_t p_source, int32_t p_dest, const uint8_t *p_data, uint32_t p_data_size) {
Packet packet;
packet.data = (uint8_t *)memalloc(p_data_size);
packet.size = p_data_size;
packet.source = p_source;
packet.destination = p_dest;
memcpy(packet.data, &p_data[PROTO_SIZE], p_data_size);
incoming_packets.push_back(packet);
}
Error WebSocketMultiplayerPeer::_server_relay(int32_t p_from, int32_t p_to, const uint8_t *p_buffer, uint32_t p_buffer_size) {
if (p_to == 1) {
return OK; // Will not send to self
} else if (p_to == 0) {
for (KeyValue<int, Ref<WebSocketPeer>> &E : peers_map) {
if (E.key != p_from) {
E.value->put_packet(p_buffer, p_buffer_size);
}
}
return OK; // Sent to all but sender
} else if (p_to < 0) {
for (KeyValue<int, Ref<WebSocketPeer>> &E : peers_map) {
if (E.key != p_from && E.key != -p_to) {
E.value->put_packet(p_buffer, p_buffer_size);
}
}
return OK; // Sent to all but sender and excluded
} else {
ERR_FAIL_COND_V(p_to == p_from, FAILED);
Ref<WebSocketPeer> peer_to = get_peer(p_to);
ERR_FAIL_COND_V(peer_to.is_null(), FAILED);
return peer_to->put_packet(p_buffer, p_buffer_size); // Sending to specific peer
}
}
void WebSocketMultiplayerPeer::_process_multiplayer(Ref<WebSocketPeer> p_peer, uint32_t p_peer_id) {
ERR_FAIL_COND(!p_peer.is_valid());
const uint8_t *in_buffer;
int size = 0;
int data_size = 0;
Error err = p_peer->get_packet(&in_buffer, size);
ERR_FAIL_COND(err != OK);
ERR_FAIL_COND(size < PROTO_SIZE);
data_size = size - PROTO_SIZE;
uint8_t type = 0;
uint32_t from = 0;
int32_t to = 0;
memcpy(&type, in_buffer, 1);
memcpy(&from, &in_buffer[1], 4);
memcpy(&to, &in_buffer[5], 4);
if (is_server()) { // Server can resend
ERR_FAIL_COND(type != SYS_NONE); // Only server sends sys messages
ERR_FAIL_COND(from != p_peer_id); // Someone is cheating
if (to == 1) {
// This is for the server
_store_pkt(from, to, in_buffer, data_size);
} else if (to == 0) {
// Broadcast, for us too
_store_pkt(from, to, in_buffer, data_size);
} else if (to < -1) {
// All but one, for us if not excluded
_store_pkt(from, to, in_buffer, data_size);
}
// Relay if needed (i.e. "to" includes a peer that is not the server)
_server_relay(from, to, in_buffer, size);
} else {
if (type == SYS_NONE) {
// Payload message
_store_pkt(from, to, in_buffer, data_size);
return;
}
// System message
ERR_FAIL_COND(data_size < 4);
int id = 0;
memcpy(&id, &in_buffer[PROTO_SIZE], 4);
switch (type) {
case SYS_ADD: // Add peer
if (id != 1) {
peers_map[id] = Ref<WebSocketPeer>();
} else {
pending_peers.clear();
connection_status = CONNECTION_CONNECTED;
}
emit_signal(SNAME("peer_connected"), id);
if (id == 1) { // We just connected to the server
emit_signal(SNAME("connection_succeeded"));
}
break;
case SYS_DEL: // Remove peer
emit_signal(SNAME("peer_disconnected"), id);
peers_map.erase(id);
break;
case SYS_ID: // Hello, server assigned ID
unique_id = id;
break;
default:
ERR_FAIL_MSG("Invalid multiplayer message.");
break;
}
}
}
void WebSocketMultiplayerPeer::set_supported_protocols(const Vector<String> &p_protocols) {
peer_config->set_supported_protocols(p_protocols);
}

View file

@ -42,9 +42,6 @@ class WebSocketMultiplayerPeer : public MultiplayerPeer {
GDCLASS(WebSocketMultiplayerPeer, MultiplayerPeer);
private:
Vector<uint8_t> _make_pkt(uint8_t p_type, int32_t p_from, int32_t p_to, const uint8_t *p_data, uint32_t p_data_size);
void _store_pkt(int32_t p_source, int32_t p_dest, const uint8_t *p_data, uint32_t p_data_size);
Error _server_relay(int32_t p_from, int32_t p_to, const uint8_t *p_buffer, uint32_t p_buffer_size);
Ref<WebSocketPeer> _create_peer();
protected:
@ -59,7 +56,6 @@ protected:
struct Packet {
int source = 0;
int destination = 0;
uint8_t *data = nullptr;
uint32_t size = 0;
};
@ -90,20 +86,18 @@ protected:
static void _bind_methods();
void _send_ack(Ref<WebSocketPeer> p_peer, int32_t p_peer_id);
void _send_sys(Ref<WebSocketPeer> p_peer, uint8_t p_type, int32_t p_peer_id);
void _send_del(int32_t p_peer_id);
void _process_multiplayer(Ref<WebSocketPeer> p_peer, uint32_t p_peer_id);
void _poll_client();
void _poll_server();
void _clear();
public:
/* MultiplayerPeer */
void set_target_peer(int p_target_peer) override;
int get_packet_peer() const override;
int get_unique_id() const override;
virtual void set_target_peer(int p_target_peer) override;
virtual int get_packet_peer() const override;
virtual int get_packet_channel() const override { return 0; }
virtual TransferMode get_packet_mode() const override { return TRANSFER_MODE_RELIABLE; }
virtual int get_unique_id() const override;
virtual bool is_server_relay_supported() const override { return true; }
virtual int get_max_packet_size() const override;
virtual bool is_server() const override;

View file

@ -78,6 +78,10 @@ bool MultiplayerPeer::is_refusing_new_connections() const {
return refuse_connections;
}
bool MultiplayerPeer::is_server_relay_supported() const {
return false;
}
void MultiplayerPeer::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_transfer_channel", "channel"), &MultiplayerPeer::set_transfer_channel);
ClassDB::bind_method(D_METHOD("get_transfer_channel"), &MultiplayerPeer::get_transfer_channel);
@ -86,6 +90,8 @@ void MultiplayerPeer::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_target_peer", "id"), &MultiplayerPeer::set_target_peer);
ClassDB::bind_method(D_METHOD("get_packet_peer"), &MultiplayerPeer::get_packet_peer);
ClassDB::bind_method(D_METHOD("get_packet_channel"), &MultiplayerPeer::get_packet_channel);
ClassDB::bind_method(D_METHOD("get_packet_mode"), &MultiplayerPeer::get_packet_mode);
ClassDB::bind_method(D_METHOD("poll"), &MultiplayerPeer::poll);
@ -96,6 +102,8 @@ void MultiplayerPeer::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_refuse_new_connections", "enable"), &MultiplayerPeer::set_refuse_new_connections);
ClassDB::bind_method(D_METHOD("is_refusing_new_connections"), &MultiplayerPeer::is_refusing_new_connections);
ClassDB::bind_method(D_METHOD("is_server_relay_supported"), &MultiplayerPeer::is_server_relay_supported);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "refuse_new_connections"), "set_refuse_new_connections", "is_refusing_new_connections");
ADD_PROPERTY(PropertyInfo(Variant::INT, "transfer_mode", PROPERTY_HINT_ENUM, "Unreliable,Unreliable Ordered,Reliable"), "set_transfer_mode", "get_transfer_mode");
ADD_PROPERTY(PropertyInfo(Variant::INT, "transfer_channel", PROPERTY_HINT_RANGE, "0,255,1"), "set_transfer_channel", "get_transfer_channel");
@ -177,6 +185,14 @@ bool MultiplayerPeerExtension::is_refusing_new_connections() const {
return MultiplayerPeer::is_refusing_new_connections();
}
bool MultiplayerPeerExtension::is_server_relay_supported() const {
bool can_relay;
if (GDVIRTUAL_CALL(_is_server_relay_supported, can_relay)) {
return can_relay;
}
return MultiplayerPeer::is_server_relay_supported();
}
void MultiplayerPeerExtension::_bind_methods() {
GDVIRTUAL_BIND(_get_packet, "r_buffer", "r_buffer_size");
GDVIRTUAL_BIND(_put_packet, "p_buffer", "p_buffer_size");

View file

@ -74,10 +74,13 @@ public:
virtual TransferMode get_transfer_mode() const;
virtual void set_refuse_new_connections(bool p_enable);
virtual bool is_refusing_new_connections() const;
virtual bool is_server_relay_supported() const;
virtual void set_target_peer(int p_peer_id) = 0;
virtual int get_packet_peer() const = 0;
virtual TransferMode get_packet_mode() const = 0;
virtual int get_packet_channel() const = 0;
virtual bool is_server() const = 0;
@ -123,12 +126,17 @@ public:
virtual bool is_refusing_new_connections() const override;
GDVIRTUAL0RC(bool, _is_refusing_new_connections); // Optional.
virtual bool is_server_relay_supported() const override;
GDVIRTUAL0RC(bool, _is_server_relay_supported); // Optional.
EXBIND1(set_transfer_channel, int);
EXBIND0RC(int, get_transfer_channel);
EXBIND1(set_transfer_mode, TransferMode);
EXBIND0RC(TransferMode, get_transfer_mode);
EXBIND1(set_target_peer, int);
EXBIND0RC(int, get_packet_peer);
EXBIND0RC(TransferMode, get_packet_mode);
EXBIND0RC(int, get_packet_channel);
EXBIND0RC(bool, is_server);
EXBIND0(poll);
EXBIND0RC(int, get_unique_id);