[Net] Implement RPC channels in MultiplayerAPI.

This commit is contained in:
Fabio Alessandrelli 2021-07-27 12:06:48 +02:00
parent c27ef1565f
commit 2cf39b97ae
16 changed files with 157 additions and 62 deletions

View File

@ -478,6 +478,7 @@ void MultiplayerAPI::_process_simplify_path(int p_from, const uint8_t *p_packet,
packet.write[1] = valid_rpc_checksum;
encode_cstring(pname.get_data(), &packet.write[2]);
network_peer->set_transfer_channel(0);
network_peer->set_transfer_mode(MultiplayerPeer::TRANSFER_MODE_RELIABLE);
network_peer->set_target_peer(p_from);
network_peer->put_packet(packet.ptr(), packet.size());
@ -557,6 +558,7 @@ bool MultiplayerAPI::_send_confirm_path(Node *p_node, NodePath p_path, PathSentC
for (int &E : peers_to_add) {
network_peer->set_target_peer(E); // To all of you.
network_peer->set_transfer_channel(0);
network_peer->set_transfer_mode(MultiplayerPeer::TRANSFER_MODE_RELIABLE);
network_peer->put_packet(packet.ptr(), packet.size());
@ -858,6 +860,7 @@ void MultiplayerAPI::_send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, const
#endif
// Take chance and set transfer mode, since all send methods will use it.
network_peer->set_transfer_channel(p_config.channel);
network_peer->set_transfer_mode(p_config.transfer_mode);
if (has_all_peers) {
@ -996,7 +999,7 @@ void MultiplayerAPI::rpcp(Node *p_node, int p_peer_id, bool p_unreliable, const
ERR_FAIL_COND_MSG(p_peer_id == node_id && !config.sync, "RPC '" + p_method + "' on yourself is not allowed by selected mode.");
}
Error MultiplayerAPI::send_bytes(Vector<uint8_t> p_data, int p_to, MultiplayerPeer::TransferMode p_mode) {
Error MultiplayerAPI::send_bytes(Vector<uint8_t> p_data, int p_to, MultiplayerPeer::TransferMode p_mode, int p_channel) {
ERR_FAIL_COND_V_MSG(p_data.size() < 1, ERR_INVALID_DATA, "Trying to send an empty raw packet.");
ERR_FAIL_COND_V_MSG(!network_peer.is_valid(), ERR_UNCONFIGURED, "Trying to send a raw packet while no network peer is active.");
ERR_FAIL_COND_V_MSG(network_peer->get_connection_status() != MultiplayerPeer::CONNECTION_CONNECTED, ERR_UNCONFIGURED, "Trying to send a raw packet via a network peer which is not connected.");
@ -1007,6 +1010,7 @@ Error MultiplayerAPI::send_bytes(Vector<uint8_t> p_data, int p_to, MultiplayerPe
memcpy(&packet_cache.write[1], &r[0], p_data.size());
network_peer->set_target_peer(p_to);
network_peer->set_transfer_channel(p_channel);
network_peer->set_transfer_mode(p_mode);
return network_peer->put_packet(packet_cache.ptr(), p_data.size() + 1);
@ -1066,7 +1070,7 @@ bool MultiplayerAPI::is_object_decoding_allowed() const {
void MultiplayerAPI::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_root_node", "node"), &MultiplayerAPI::set_root_node);
ClassDB::bind_method(D_METHOD("get_root_node"), &MultiplayerAPI::get_root_node);
ClassDB::bind_method(D_METHOD("send_bytes", "bytes", "id", "mode"), &MultiplayerAPI::send_bytes, DEFVAL(MultiplayerPeer::TARGET_PEER_BROADCAST), DEFVAL(MultiplayerPeer::TRANSFER_MODE_RELIABLE));
ClassDB::bind_method(D_METHOD("send_bytes", "bytes", "id", "mode", "channel"), &MultiplayerAPI::send_bytes, DEFVAL(MultiplayerPeer::TARGET_PEER_BROADCAST), DEFVAL(MultiplayerPeer::TRANSFER_MODE_RELIABLE), DEFVAL(0));
ClassDB::bind_method(D_METHOD("has_network_peer"), &MultiplayerAPI::has_network_peer);
ClassDB::bind_method(D_METHOD("get_network_peer"), &MultiplayerAPI::get_network_peer);
ClassDB::bind_method(D_METHOD("get_network_unique_id"), &MultiplayerAPI::get_network_unique_id);

View File

@ -132,7 +132,7 @@ public:
Node *get_root_node();
void set_network_peer(const Ref<MultiplayerPeer> &p_peer);
Ref<MultiplayerPeer> get_network_peer() const;
Error send_bytes(Vector<uint8_t> p_data, int p_to = MultiplayerPeer::TARGET_PEER_BROADCAST, MultiplayerPeer::TransferMode p_mode = MultiplayerPeer::TRANSFER_MODE_RELIABLE);
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);
// Called by Node.rpc
void rpcp(Node *p_node, int p_peer_id, bool p_unreliable, const StringName &p_method, const Variant **p_arg, int p_argcount);

View File

@ -54,6 +54,8 @@ uint32_t MultiplayerPeer::generate_unique_id() const {
}
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);
ClassDB::bind_method(D_METHOD("set_transfer_mode", "mode"), &MultiplayerPeer::set_transfer_mode);
ClassDB::bind_method(D_METHOD("get_transfer_mode"), &MultiplayerPeer::get_transfer_mode);
ClassDB::bind_method(D_METHOD("set_target_peer", "id"), &MultiplayerPeer::set_target_peer);
@ -71,6 +73,7 @@ void MultiplayerPeer::_bind_methods() {
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");
BIND_ENUM_CONSTANT(TRANSFER_MODE_UNRELIABLE);
BIND_ENUM_CONSTANT(TRANSFER_MODE_UNRELIABLE_ORDERED);

View File

@ -56,6 +56,8 @@ public:
CONNECTION_CONNECTED,
};
virtual void set_transfer_channel(int p_channel) = 0;
virtual int get_transfer_channel() const = 0;
virtual void set_transfer_mode(TransferMode p_mode) = 0;
virtual TransferMode get_transfer_mode() const = 0;
virtual void set_target_peer(int p_peer_id) = 0;

View File

@ -61,6 +61,7 @@
<argument index="0" name="bytes" type="PackedByteArray" />
<argument index="1" name="id" type="int" default="0" />
<argument index="2" name="mode" type="int" enum="MultiplayerPeer.TransferMode" default="2" />
<argument index="3" name="channel" type="int" default="0" />
<description>
Sends the given raw [code]bytes[/code] to a specific peer identified by [code]id[/code] (see [method MultiplayerPeer.set_target_peer]). Default ID is [code]0[/code], i.e. broadcast to all peers.
</description>

View File

@ -55,6 +55,10 @@
<member name="refuse_new_connections" type="bool" setter="set_refuse_new_connections" getter="is_refusing_new_connections" default="true">
If [code]true[/code], this [MultiplayerPeer] refuses new connections.
</member>
<member name="transfer_channel" type="int" setter="set_transfer_channel" getter="get_transfer_channel" default="0">
The channel to use to send packets. Many network APIs such as ENet and WebRTC allow the creation of multiple independent channels which behaves, in a way, like separate connections. This means that reliable data will only block delivery of other packets on that channel, and ordering will only be in respect to the channel the packet is being sent on. Using different channels to send [b]different and independent[/b] state updates is a common way to optimize network usage and decrease latency in fast-paced games.
[b]Note:[/b] The default channel ([code]0[/code]) actually works as 3 separate channels (one for each [enum TransferMode]) so that [constant TRANSFER_MODE_RELIABLE] and [constant TRANSFER_MODE_UNRELIABLE_ORDERED] does not interact with each other by default. Refer to the specific network API documentation (e.g. ENet or WebRTC) to learn how to set up channels correctly.
</member>
<member name="transfer_mode" type="int" setter="set_transfer_mode" getter="get_transfer_mode" enum="MultiplayerPeer.TransferMode" default="0">
The manner in which to send packets to the [code]target_peer[/code]. See [enum TransferMode].
</member>

View File

@ -33,6 +33,14 @@
#include "core/io/marshalls.h"
#include "core/os/os.h"
void ENetMultiplayerPeer::set_transfer_channel(int p_channel) {
transfer_channel = p_channel;
}
int ENetMultiplayerPeer::get_transfer_channel() const {
return transfer_channel;
}
void ENetMultiplayerPeer::set_transfer_mode(TransferMode p_mode) {
transfer_mode = p_mode;
}
@ -441,7 +449,9 @@ Error ENetMultiplayerPeer::put_packet(const uint8_t *p_buffer, int p_buffer_size
int packet_flags = 0;
int channel = SYSCH_RELIABLE;
if (transfer_channel > 0) {
channel = SYSCH_MAX + transfer_channel - 1;
} else {
switch (transfer_mode) {
case TRANSFER_MODE_UNRELIABLE: {
packet_flags = ENET_PACKET_FLAG_UNSEQUENCED;
@ -456,6 +466,7 @@ Error ENetMultiplayerPeer::put_packet(const uint8_t *p_buffer, int p_buffer_size
channel = SYSCH_RELIABLE;
} break;
}
}
ENetPacket *packet = enet_packet_create(nullptr, p_buffer_size + 8, packet_flags);
encode_uint32(unique_id, &packet->data[0]); // Source ID

View File

@ -47,10 +47,10 @@ private:
};
enum {
SYSCH_CONFIG,
SYSCH_RELIABLE,
SYSCH_UNRELIABLE,
SYSCH_MAX
SYSCH_CONFIG = 0,
SYSCH_RELIABLE = 1,
SYSCH_UNRELIABLE = 2,
SYSCH_MAX = 3
};
enum Mode {
@ -65,6 +65,7 @@ private:
uint32_t unique_id = 0;
int target_peer = 0;
int transfer_channel = 0;
TransferMode transfer_mode = TRANSFER_MODE_RELIABLE;
bool refuse_connections = false;
@ -100,6 +101,9 @@ protected:
static void _bind_methods();
public:
virtual void set_transfer_channel(int p_channel) override;
virtual int get_transfer_channel() const override;
virtual void set_transfer_mode(TransferMode p_mode) override;
virtual TransferMode get_transfer_mode() const override;
virtual void set_target_peer(int p_peer) override;

View File

@ -91,6 +91,8 @@ typedef struct {
godot_int (*get_max_packet_size)(const void *);
/* This is MultiplayerPeer */
void (*set_transfer_channel)(void *, godot_int);
godot_int (*get_transfer_channel)(void *);
void (*set_transfer_mode)(void *, godot_int);
godot_int (*get_transfer_mode)(const void *);
// 0 = broadcast, 1 = server, <0 = all but abs(value)

View File

@ -62,6 +62,16 @@ int MultiplayerPeerGDNative::get_available_packet_count() const {
}
/* MultiplayerPeer */
void MultiplayerPeerGDNative::set_transfer_channel(int p_channel) {
ERR_FAIL_COND(interface == nullptr);
return interface->set_transfer_channel(interface->data, p_channel);
}
int MultiplayerPeerGDNative::get_transfer_channel() const {
ERR_FAIL_COND_V(interface == nullptr, 0);
return interface->get_transfer_channel(interface->data);
}
void MultiplayerPeerGDNative::set_transfer_mode(TransferMode p_mode) {
ERR_FAIL_COND(interface == nullptr);
interface->set_transfer_mode(interface->data, (godot_int)p_mode);
@ -113,6 +123,7 @@ MultiplayerPeer::ConnectionStatus MultiplayerPeerGDNative::get_connection_status
}
void MultiplayerPeerGDNative::_bind_methods() {
ADD_PROPERTY_DEFAULT("transfer_channel", 0);
ADD_PROPERTY_DEFAULT("transfer_mode", TRANSFER_MODE_UNRELIABLE);
ADD_PROPERTY_DEFAULT("refuse_new_connections", true);
}

View File

@ -56,6 +56,8 @@ public:
virtual int get_available_packet_count() const override;
/* Specific to MultiplayerPeer */
virtual void set_transfer_channel(int p_channel) override;
virtual int get_transfer_channel() const override;
virtual void set_transfer_mode(TransferMode p_mode) override;
virtual TransferMode get_transfer_mode() const override;
virtual void set_target_peer(int p_peer_id) override;

View File

@ -51,10 +51,12 @@
<return type="int" enum="Error" />
<argument index="0" name="peer_id" type="int" />
<argument index="1" name="server_compatibility" type="bool" default="false" />
<argument 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">

View File

@ -34,7 +34,7 @@
#include "core/os/os.h"
void WebRTCMultiplayerPeer::_bind_methods() {
ClassDB::bind_method(D_METHOD("initialize", "peer_id", "server_compatibility"), &WebRTCMultiplayerPeer::initialize, DEFVAL(false));
ClassDB::bind_method(D_METHOD("initialize", "peer_id", "server_compatibility", "channels_config"), &WebRTCMultiplayerPeer::initialize, DEFVAL(false), 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);
@ -43,6 +43,14 @@ void WebRTCMultiplayerPeer::_bind_methods() {
ClassDB::bind_method(D_METHOD("close"), &WebRTCMultiplayerPeer::close);
}
void WebRTCMultiplayerPeer::set_transfer_channel(int p_channel) {
transfer_channel = p_channel;
}
int WebRTCMultiplayerPeer::get_transfer_channel() const {
return transfer_channel;
}
void WebRTCMultiplayerPeer::set_transfer_mode(TransferMode p_mode) {
transfer_mode = p_mode;
}
@ -192,8 +200,34 @@ MultiplayerPeer::ConnectionStatus WebRTCMultiplayerPeer::get_connection_status()
return connection_status;
}
Error WebRTCMultiplayerPeer::initialize(int p_self_id, bool p_server_compat) {
ERR_FAIL_COND_V(p_self_id < 0 || p_self_id > ~(1 << 31), ERR_INVALID_PARAMETER);
Error WebRTCMultiplayerPeer::initialize(int p_self_id, bool p_server_compat, Array p_channels_config) {
ERR_FAIL_COND_V(p_self_id < 1 || p_self_id > ~(1 << 31), ERR_INVALID_PARAMETER);
channels_config.clear();
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.TransferMode'");
int mode = p_channels_config[i].operator int();
// Initialize data channel configurations.
Dictionary cfg;
cfg["id"] = CH_RESERVED_MAX + i + 1;
cfg["negotiated"] = true;
cfg["ordered"] = true;
switch (mode) {
case TRANSFER_MODE_UNRELIABLE_ORDERED:
cfg["maxPacketLifetime"] = 1;
break;
case TRANSFER_MODE_UNRELIABLE:
cfg["maxPacketLifetime"] = 1;
cfg["ordered"] = false;
break;
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.TransferMode'. Got: %d", mode));
}
channels_config.push_back(cfg);
}
unique_id = p_self_id;
server_compat = p_server_compat;
@ -260,17 +294,23 @@ Error WebRTCMultiplayerPeer::add_peer(Ref<WebRTCPeerConnection> p_peer, int p_pe
cfg["id"] = 1;
peer->channels[CH_RELIABLE] = p_peer->create_data_channel("reliable", cfg);
ERR_FAIL_COND_V(!peer->channels[CH_RELIABLE].is_valid(), FAILED);
ERR_FAIL_COND_V(peer->channels[CH_RELIABLE].is_null(), FAILED);
cfg["id"] = 2;
cfg["maxPacketLifetime"] = p_unreliable_lifetime;
peer->channels[CH_ORDERED] = p_peer->create_data_channel("ordered", cfg);
ERR_FAIL_COND_V(!peer->channels[CH_ORDERED].is_valid(), FAILED);
ERR_FAIL_COND_V(peer->channels[CH_ORDERED].is_null(), FAILED);
cfg["id"] = 3;
cfg["ordered"] = false;
peer->channels[CH_UNRELIABLE] = p_peer->create_data_channel("unreliable", cfg);
ERR_FAIL_COND_V(!peer->channels[CH_UNRELIABLE].is_valid(), FAILED);
ERR_FAIL_COND_V(peer->channels[CH_UNRELIABLE].is_null(), FAILED);
for (const Dictionary &dict : channels_config) {
Ref<WebRTCDataChannel> ch = p_peer->create_data_channel(String::num_int64(dict["id"]), dict);
ERR_FAIL_COND_V(ch.is_null(), FAILED);
peer->channels.push_back(ch);
}
peer_map[p_peer_id] = peer; // add the new peer connection to the peer_map
@ -312,7 +352,8 @@ Error WebRTCMultiplayerPeer::get_packet(const uint8_t **r_buffer, int &r_buffer_
Error WebRTCMultiplayerPeer::put_packet(const uint8_t *p_buffer, int p_buffer_size) {
ERR_FAIL_COND_V(connection_status == CONNECTION_DISCONNECTED, ERR_UNCONFIGURED);
int ch = CH_RELIABLE;
int ch = transfer_channel;
if (ch == 0) {
switch (transfer_mode) {
case TRANSFER_MODE_RELIABLE:
ch = CH_RELIABLE;
@ -324,6 +365,9 @@ Error WebRTCMultiplayerPeer::put_packet(const uint8_t *p_buffer, int p_buffer_si
ch = CH_UNRELIABLE;
break;
}
} else {
ch += CH_RESERVED_MAX - 1;
}
Map<int, Ref<ConnectedPeer>>::Element *E = nullptr;
@ -331,8 +375,8 @@ Error WebRTCMultiplayerPeer::put_packet(const uint8_t *p_buffer, int p_buffer_si
E = peer_map.find(target_peer);
ERR_FAIL_COND_V_MSG(!E, ERR_INVALID_PARAMETER, "Invalid target peer: " + itos(target_peer) + ".");
ERR_FAIL_COND_V(E->value()->channels.size() <= ch, ERR_BUG);
ERR_FAIL_COND_V(!E->value()->channels[ch].is_valid(), ERR_BUG);
ERR_FAIL_COND_V_MSG(E->value()->channels.size() <= ch, ERR_INVALID_PARAMETER, vformat("Unable to send packet on channel %d, max channels: %d", ch, E->value()->channels.size()));
ERR_FAIL_COND_V(E->value()->channels[ch].is_null(), ERR_BUG);
return E->value()->channels[ch]->put_packet(p_buffer, p_buffer_size);
} else {
@ -344,7 +388,8 @@ Error WebRTCMultiplayerPeer::put_packet(const uint8_t *p_buffer, int p_buffer_si
continue;
}
ERR_CONTINUE(F->value()->channels.size() <= ch || !F->value()->channels[ch].is_valid());
ERR_CONTINUE_MSG(F->value()->channels.size() <= ch, vformat("Unable to send packet on channel %d, max channels: %d", ch, E->value()->channels.size()));
ERR_CONTINUE(F->value()->channels[ch].is_null());
F->value()->channels[ch]->put_packet(p_buffer, p_buffer_size);
}
}
@ -370,23 +415,13 @@ int WebRTCMultiplayerPeer::get_max_packet_size() const {
void WebRTCMultiplayerPeer::close() {
peer_map.clear();
channels_config.clear();
unique_id = 0;
next_packet_peer = 0;
target_peer = 0;
connection_status = CONNECTION_DISCONNECTED;
}
WebRTCMultiplayerPeer::WebRTCMultiplayerPeer() {
unique_id = 0;
next_packet_peer = 0;
target_peer = 0;
client_count = 0;
transfer_mode = TRANSFER_MODE_RELIABLE;
refuse_connections = false;
connection_status = CONNECTION_DISCONNECTED;
server_compat = false;
}
WebRTCMultiplayerPeer::~WebRTCMultiplayerPeer() {
close();
}

View File

@ -62,25 +62,27 @@ private:
}
};
uint32_t unique_id;
int target_peer;
int client_count;
bool refuse_connections;
ConnectionStatus connection_status;
TransferMode transfer_mode;
int next_packet_peer;
bool server_compat;
uint32_t unique_id = 0;
int target_peer = 0;
int client_count = 0;
bool refuse_connections = false;
ConnectionStatus connection_status = CONNECTION_DISCONNECTED;
int transfer_channel = 0;
TransferMode transfer_mode = TRANSFER_MODE_RELIABLE;
int next_packet_peer = 0;
bool server_compat = false;
Map<int, Ref<ConnectedPeer>> peer_map;
List<Dictionary> channels_config;
void _peer_to_dict(Ref<ConnectedPeer> p_connected_peer, Dictionary &r_dict);
void _find_next_peer();
public:
WebRTCMultiplayerPeer();
WebRTCMultiplayerPeer() {}
~WebRTCMultiplayerPeer();
Error initialize(int p_self_id, bool p_server_compat = false);
Error initialize(int p_self_id, bool p_server_compat = false, 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);
@ -95,6 +97,8 @@ public:
int get_max_packet_size() const override;
// MultiplayerPeer
void set_transfer_channel(int p_channel) override;
int get_transfer_channel() const override;
void set_transfer_mode(TransferMode p_mode) override;
TransferMode get_transfer_mode() const override;
void set_target_peer(int p_peer_id) override;

View File

@ -105,6 +105,14 @@ Error WebSocketMultiplayerPeer::put_packet(const uint8_t *p_buffer, int p_buffer
//
// MultiplayerPeer
//
void WebSocketMultiplayerPeer::set_transfer_channel(int p_channel) {
// Websocket does not have channels.
}
int WebSocketMultiplayerPeer::get_transfer_channel() const {
return 0;
}
void WebSocketMultiplayerPeer::set_transfer_mode(TransferMode p_mode) {
// Websocket uses TCP, reliable
}

View File

@ -78,6 +78,8 @@ protected:
public:
/* MultiplayerPeer */
void set_transfer_channel(int p_channel) override;
int get_transfer_channel() const override;
void set_transfer_mode(TransferMode p_mode) override;
TransferMode get_transfer_mode() const override;
void set_target_peer(int p_target_peer) override;