[Net] Modularize multiplayer, expose MultiplayerAPI to extensions.

- RPC configurations are now dictionaries.
- Script.get_rpc_methods renamed to Script.get_rpc_config.
- Node.rpc[_id] and Callable.rpc now return an Error.
- Refactor MultiplayerAPI to allow extension.
- New MultiplayerAPI.rpc method with Array argument (for scripts).
- Move the default MultiplayerAPI implementation to a module.
This commit is contained in:
Fabio Alessandrelli 2022-07-12 23:12:42 +02:00
parent c3dc887c41
commit ca7d572908
80 changed files with 1819 additions and 1388 deletions

View File

@ -188,7 +188,6 @@ SConscript("os/SCsub")
SConscript("math/SCsub")
SConscript("crypto/SCsub")
SConscript("io/SCsub")
SConscript("multiplayer/SCsub")
SConscript("debugger/SCsub")
SConscript("input/SCsub")
SConscript("variant/SCsub")

View File

@ -31,7 +31,6 @@
#include "core_constants.h"
#include "core/input/input_event.h"
#include "core/multiplayer/multiplayer.h"
#include "core/object/class_db.h"
#include "core/os/keyboard.h"
#include "core/variant/variant.h"
@ -660,15 +659,6 @@ void register_global_constants() {
BIND_CORE_ENUM_CONSTANT(METHOD_FLAG_OBJECT_CORE);
BIND_CORE_ENUM_CONSTANT(METHOD_FLAGS_DEFAULT);
// rpc
BIND_CORE_ENUM_CONSTANT_CUSTOM("RPC_MODE_DISABLED", Multiplayer::RPC_MODE_DISABLED);
BIND_CORE_ENUM_CONSTANT_CUSTOM("RPC_MODE_ANY_PEER", Multiplayer::RPC_MODE_ANY_PEER);
BIND_CORE_ENUM_CONSTANT_CUSTOM("RPC_MODE_AUTHORITY", Multiplayer::RPC_MODE_AUTHORITY);
BIND_CORE_ENUM_CONSTANT_CUSTOM("TRANSFER_MODE_UNRELIABLE", Multiplayer::TRANSFER_MODE_UNRELIABLE);
BIND_CORE_ENUM_CONSTANT_CUSTOM("TRANSFER_MODE_UNRELIABLE_ORDERED", Multiplayer::TRANSFER_MODE_UNRELIABLE_ORDERED);
BIND_CORE_ENUM_CONSTANT_CUSTOM("TRANSFER_MODE_RELIABLE", Multiplayer::TRANSFER_MODE_RELIABLE);
BIND_CORE_ENUM_CONSTANT_CUSTOM("TYPE_NIL", Variant::NIL);
BIND_CORE_ENUM_CONSTANT_CUSTOM("TYPE_BOOL", Variant::BOOL);
BIND_CORE_ENUM_CONSTANT_CUSTOM("TYPE_INT", Variant::INT);

View File

@ -1,5 +0,0 @@
#!/usr/bin/env python
Import("env")
env.add_source_files(env.core_sources, "*.cpp")

View File

@ -1,593 +0,0 @@
/*************************************************************************/
/* multiplayer_api.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#include "multiplayer_api.h"
#include "core/debugger/engine_debugger.h"
#include "core/io/marshalls.h"
#include <stdint.h>
#ifdef DEBUG_ENABLED
#include "core/os/os.h"
#endif
MultiplayerReplicationInterface *(*MultiplayerAPI::create_default_replication_interface)(MultiplayerAPI *p_multiplayer) = nullptr;
MultiplayerRPCInterface *(*MultiplayerAPI::create_default_rpc_interface)(MultiplayerAPI *p_multiplayer) = nullptr;
MultiplayerCacheInterface *(*MultiplayerAPI::create_default_cache_interface)(MultiplayerAPI *p_multiplayer) = nullptr;
#ifdef DEBUG_ENABLED
void MultiplayerAPI::profile_bandwidth(const String &p_inout, int p_size) {
if (EngineDebugger::is_profiling("multiplayer")) {
Array values;
values.push_back(p_inout);
values.push_back(OS::get_singleton()->get_ticks_msec());
values.push_back(p_size);
EngineDebugger::profiler_add_frame_data("multiplayer", values);
}
}
#endif
void MultiplayerAPI::poll() {
if (!multiplayer_peer.is_valid() || multiplayer_peer->get_connection_status() == MultiplayerPeer::CONNECTION_DISCONNECTED) {
return;
}
multiplayer_peer->poll();
if (!multiplayer_peer.is_valid()) { // It's possible that polling might have resulted in a disconnection, so check here.
return;
}
while (multiplayer_peer->get_available_packet_count()) {
int sender = multiplayer_peer->get_packet_peer();
const uint8_t *packet;
int len;
Error err = multiplayer_peer->get_packet(&packet, len);
if (err != OK) {
ERR_PRINT("Error getting packet!");
return; // Something is wrong!
}
remote_sender_id = sender;
_process_packet(sender, packet, len);
remote_sender_id = 0;
if (!multiplayer_peer.is_valid()) {
return; // It's also possible that a packet or RPC caused a disconnection, so also check here.
}
}
replicator->on_network_process();
}
void MultiplayerAPI::clear() {
connected_peers.clear();
packet_cache.clear();
cache->clear();
}
void MultiplayerAPI::set_root_path(const NodePath &p_path) {
ERR_FAIL_COND_MSG(!p_path.is_absolute() && !p_path.is_empty(), "MultiplayerAPI root path must be absolute.");
root_path = p_path;
}
NodePath MultiplayerAPI::get_root_path() const {
return root_path;
}
void MultiplayerAPI::set_multiplayer_peer(const Ref<MultiplayerPeer> &p_peer) {
if (p_peer == multiplayer_peer) {
return; // Nothing to do
}
ERR_FAIL_COND_MSG(p_peer.is_valid() && p_peer->get_connection_status() == MultiplayerPeer::CONNECTION_DISCONNECTED,
"Supplied MultiplayerPeer must be connecting or connected.");
if (multiplayer_peer.is_valid()) {
multiplayer_peer->disconnect("peer_connected", callable_mp(this, &MultiplayerAPI::_add_peer));
multiplayer_peer->disconnect("peer_disconnected", callable_mp(this, &MultiplayerAPI::_del_peer));
multiplayer_peer->disconnect("connection_succeeded", callable_mp(this, &MultiplayerAPI::_connected_to_server));
multiplayer_peer->disconnect("connection_failed", callable_mp(this, &MultiplayerAPI::_connection_failed));
multiplayer_peer->disconnect("server_disconnected", callable_mp(this, &MultiplayerAPI::_server_disconnected));
clear();
}
multiplayer_peer = p_peer;
if (multiplayer_peer.is_valid()) {
multiplayer_peer->connect("peer_connected", callable_mp(this, &MultiplayerAPI::_add_peer));
multiplayer_peer->connect("peer_disconnected", callable_mp(this, &MultiplayerAPI::_del_peer));
multiplayer_peer->connect("connection_succeeded", callable_mp(this, &MultiplayerAPI::_connected_to_server));
multiplayer_peer->connect("connection_failed", callable_mp(this, &MultiplayerAPI::_connection_failed));
multiplayer_peer->connect("server_disconnected", callable_mp(this, &MultiplayerAPI::_server_disconnected));
}
replicator->on_reset();
}
Ref<MultiplayerPeer> MultiplayerAPI::get_multiplayer_peer() const {
return multiplayer_peer;
}
void MultiplayerAPI::_process_packet(int p_from, const uint8_t *p_packet, int p_packet_len) {
ERR_FAIL_COND_MSG(root_path.is_empty(), "Multiplayer root was not initialized. If you are using custom multiplayer, remember to set the root path via MultiplayerAPI.set_root_path before using it.");
ERR_FAIL_COND_MSG(p_packet_len < 1, "Invalid packet received. Size too small.");
#ifdef DEBUG_ENABLED
profile_bandwidth("in", p_packet_len);
#endif
// Extract the `packet_type` from the LSB three bits:
uint8_t packet_type = p_packet[0] & CMD_MASK;
switch (packet_type) {
case NETWORK_COMMAND_SIMPLIFY_PATH: {
cache->process_simplify_path(p_from, p_packet, p_packet_len);
} break;
case NETWORK_COMMAND_CONFIRM_PATH: {
cache->process_confirm_path(p_from, p_packet, p_packet_len);
} break;
case NETWORK_COMMAND_REMOTE_CALL: {
rpc->process_rpc(p_from, p_packet, p_packet_len);
} break;
case NETWORK_COMMAND_RAW: {
_process_raw(p_from, p_packet, p_packet_len);
} break;
case NETWORK_COMMAND_SPAWN: {
replicator->on_spawn_receive(p_from, p_packet, p_packet_len);
} break;
case NETWORK_COMMAND_DESPAWN: {
replicator->on_despawn_receive(p_from, p_packet, p_packet_len);
} break;
case NETWORK_COMMAND_SYNC: {
replicator->on_sync_receive(p_from, p_packet, p_packet_len);
} break;
}
}
// The variant is compressed and encoded; The first byte contains all the meta
// information and the format is:
// - The first LSB 5 bits are used for the variant type.
// - The next two bits are used to store the encoding mode.
// - The most significant is used to store the boolean value.
#define VARIANT_META_TYPE_MASK 0x1F
#define VARIANT_META_EMODE_MASK 0x60
#define VARIANT_META_BOOL_MASK 0x80
#define ENCODE_8 0 << 5
#define ENCODE_16 1 << 5
#define ENCODE_32 2 << 5
#define ENCODE_64 3 << 5
Error MultiplayerAPI::encode_and_compress_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bool p_allow_object_decoding) {
// Unreachable because `VARIANT_MAX` == 27 and `ENCODE_VARIANT_MASK` == 31
CRASH_COND(p_variant.get_type() > VARIANT_META_TYPE_MASK);
uint8_t *buf = r_buffer;
r_len = 0;
uint8_t encode_mode = 0;
switch (p_variant.get_type()) {
case Variant::BOOL: {
if (buf) {
// We still have 1 free bit in the meta, so let's use it.
buf[0] = (p_variant.operator bool()) ? (1 << 7) : 0;
buf[0] |= encode_mode | p_variant.get_type();
}
r_len += 1;
} break;
case Variant::INT: {
if (buf) {
// Reserve the first byte for the meta.
buf += 1;
}
r_len += 1;
int64_t val = p_variant;
if (val <= (int64_t)INT8_MAX && val >= (int64_t)INT8_MIN) {
// Use 8 bit
encode_mode = ENCODE_8;
if (buf) {
buf[0] = val;
}
r_len += 1;
} else if (val <= (int64_t)INT16_MAX && val >= (int64_t)INT16_MIN) {
// Use 16 bit
encode_mode = ENCODE_16;
if (buf) {
encode_uint16(val, buf);
}
r_len += 2;
} else if (val <= (int64_t)INT32_MAX && val >= (int64_t)INT32_MIN) {
// Use 32 bit
encode_mode = ENCODE_32;
if (buf) {
encode_uint32(val, buf);
}
r_len += 4;
} else {
// Use 64 bit
encode_mode = ENCODE_64;
if (buf) {
encode_uint64(val, buf);
}
r_len += 8;
}
// Store the meta
if (buf) {
buf -= 1;
buf[0] = encode_mode | p_variant.get_type();
}
} break;
default:
// Any other case is not yet compressed.
Error err = encode_variant(p_variant, r_buffer, r_len, p_allow_object_decoding);
if (err != OK) {
return err;
}
if (r_buffer) {
// The first byte is not used by the marshalling, so store the type
// so we know how to decompress and decode this variant.
r_buffer[0] = p_variant.get_type();
}
}
return OK;
}
Error MultiplayerAPI::decode_and_decompress_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int *r_len, bool p_allow_object_decoding) {
const uint8_t *buf = p_buffer;
int len = p_len;
ERR_FAIL_COND_V(len < 1, ERR_INVALID_DATA);
uint8_t type = buf[0] & VARIANT_META_TYPE_MASK;
uint8_t encode_mode = buf[0] & VARIANT_META_EMODE_MASK;
ERR_FAIL_COND_V(type >= Variant::VARIANT_MAX, ERR_INVALID_DATA);
switch (type) {
case Variant::BOOL: {
bool val = (buf[0] & VARIANT_META_BOOL_MASK) > 0;
r_variant = val;
if (r_len) {
*r_len = 1;
}
} break;
case Variant::INT: {
buf += 1;
len -= 1;
if (r_len) {
*r_len = 1;
}
if (encode_mode == ENCODE_8) {
// 8 bits.
ERR_FAIL_COND_V(len < 1, ERR_INVALID_DATA);
int8_t val = buf[0];
r_variant = val;
if (r_len) {
(*r_len) += 1;
}
} else if (encode_mode == ENCODE_16) {
// 16 bits.
ERR_FAIL_COND_V(len < 2, ERR_INVALID_DATA);
int16_t val = decode_uint16(buf);
r_variant = val;
if (r_len) {
(*r_len) += 2;
}
} else if (encode_mode == ENCODE_32) {
// 32 bits.
ERR_FAIL_COND_V(len < 4, ERR_INVALID_DATA);
int32_t val = decode_uint32(buf);
r_variant = val;
if (r_len) {
(*r_len) += 4;
}
} else {
// 64 bits.
ERR_FAIL_COND_V(len < 8, ERR_INVALID_DATA);
int64_t val = decode_uint64(buf);
r_variant = val;
if (r_len) {
(*r_len) += 8;
}
}
} break;
default:
Error err = decode_variant(r_variant, p_buffer, p_len, r_len, p_allow_object_decoding);
if (err != OK) {
return err;
}
}
return OK;
}
Error MultiplayerAPI::encode_and_compress_variants(const Variant **p_variants, int p_count, uint8_t *p_buffer, int &r_len, bool *r_raw, bool p_allow_object_decoding) {
r_len = 0;
int size = 0;
if (p_count == 0) {
if (r_raw) {
*r_raw = true;
}
return OK;
}
// Try raw encoding optimization.
if (r_raw && p_count == 1) {
*r_raw = false;
const Variant &v = *(p_variants[0]);
if (v.get_type() == Variant::PACKED_BYTE_ARRAY) {
*r_raw = true;
const PackedByteArray pba = v;
if (p_buffer) {
memcpy(p_buffer, pba.ptr(), pba.size());
}
r_len += pba.size();
} else {
encode_and_compress_variant(v, p_buffer, size, p_allow_object_decoding);
r_len += size;
}
return OK;
}
// Regular encoding.
for (int i = 0; i < p_count; i++) {
const Variant &v = *(p_variants[i]);
encode_and_compress_variant(v, p_buffer ? p_buffer + r_len : nullptr, size, p_allow_object_decoding);
r_len += size;
}
return OK;
}
Error MultiplayerAPI::decode_and_decompress_variants(Vector<Variant> &r_variants, const uint8_t *p_buffer, int p_len, int &r_len, bool p_raw, bool p_allow_object_decoding) {
r_len = 0;
int argc = r_variants.size();
if (argc == 0 && p_raw) {
return OK;
}
ERR_FAIL_COND_V(p_raw && argc != 1, ERR_INVALID_DATA);
if (p_raw) {
r_len = p_len;
PackedByteArray pba;
pba.resize(p_len);
memcpy(pba.ptrw(), p_buffer, p_len);
r_variants.write[0] = pba;
return OK;
}
Vector<Variant> args;
Vector<const Variant *> argp;
args.resize(argc);
for (int i = 0; i < argc; i++) {
ERR_FAIL_COND_V_MSG(r_len >= p_len, ERR_INVALID_DATA, "Invalid packet received. Size too small.");
int vlen;
Error err = MultiplayerAPI::decode_and_decompress_variant(r_variants.write[i], &p_buffer[r_len], p_len - r_len, &vlen, p_allow_object_decoding);
ERR_FAIL_COND_V_MSG(err != OK, err, "Invalid packet received. Unable to decode state variable.");
r_len += vlen;
}
return OK;
}
void MultiplayerAPI::_add_peer(int p_id) {
connected_peers.insert(p_id);
cache->on_peer_change(p_id, true);
replicator->on_peer_change(p_id, true);
emit_signal(SNAME("peer_connected"), p_id);
}
void MultiplayerAPI::_del_peer(int p_id) {
replicator->on_peer_change(p_id, false);
cache->on_peer_change(p_id, false);
connected_peers.erase(p_id);
emit_signal(SNAME("peer_disconnected"), p_id);
}
void MultiplayerAPI::_connected_to_server() {
emit_signal(SNAME("connected_to_server"));
}
void MultiplayerAPI::_connection_failed() {
emit_signal(SNAME("connection_failed"));
}
void MultiplayerAPI::_server_disconnected() {
replicator->on_reset();
emit_signal(SNAME("server_disconnected"));
}
Error MultiplayerAPI::send_bytes(Vector<uint8_t> p_data, int p_to, Multiplayer::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(!multiplayer_peer.is_valid(), ERR_UNCONFIGURED, "Trying to send a raw packet while no multiplayer peer is active.");
ERR_FAIL_COND_V_MSG(multiplayer_peer->get_connection_status() != MultiplayerPeer::CONNECTION_CONNECTED, ERR_UNCONFIGURED, "Trying to send a raw packet via a multiplayer peer which is not connected.");
if (packet_cache.size() < p_data.size() + 1) {
packet_cache.resize(p_data.size() + 1);
}
const uint8_t *r = p_data.ptr();
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);
}
void MultiplayerAPI::_process_raw(int p_from, const uint8_t *p_packet, int p_packet_len) {
ERR_FAIL_COND_MSG(p_packet_len < 2, "Invalid packet received. Size too small.");
Vector<uint8_t> out;
int len = p_packet_len - 1;
out.resize(len);
{
uint8_t *w = out.ptrw();
memcpy(&w[0], &p_packet[1], len);
}
emit_signal(SNAME("peer_packet"), p_from, out);
}
bool MultiplayerAPI::is_cache_confirmed(NodePath p_path, int p_peer) {
return cache->is_cache_confirmed(p_path, p_peer);
}
bool MultiplayerAPI::send_object_cache(Object *p_obj, int p_peer_id, int &r_id) {
return cache->send_object_cache(p_obj, p_peer_id, r_id);
}
int MultiplayerAPI::make_object_cache(Object *p_obj) {
return cache->make_object_cache(p_obj);
}
Object *MultiplayerAPI::get_cached_object(int p_from, uint32_t p_cache_id) {
return cache->get_cached_object(p_from, p_cache_id);
}
int MultiplayerAPI::get_unique_id() const {
ERR_FAIL_COND_V_MSG(!multiplayer_peer.is_valid(), 0, "No multiplayer peer is assigned. Unable to get unique ID.");
return multiplayer_peer->get_unique_id();
}
bool MultiplayerAPI::is_server() const {
return multiplayer_peer.is_valid() && multiplayer_peer->is_server();
}
void MultiplayerAPI::set_refuse_new_connections(bool p_refuse) {
ERR_FAIL_COND_MSG(!multiplayer_peer.is_valid(), "No multiplayer peer is assigned. Unable to set 'refuse_new_connections'.");
multiplayer_peer->set_refuse_new_connections(p_refuse);
}
bool MultiplayerAPI::is_refusing_new_connections() const {
ERR_FAIL_COND_V_MSG(!multiplayer_peer.is_valid(), false, "No multiplayer peer is assigned. Unable to get 'refuse_new_connections'.");
return multiplayer_peer->is_refusing_new_connections();
}
Vector<int> MultiplayerAPI::get_peer_ids() const {
ERR_FAIL_COND_V_MSG(!multiplayer_peer.is_valid(), Vector<int>(), "No multiplayer peer is assigned. Assume no peers are connected.");
Vector<int> ret;
for (const int &E : connected_peers) {
ret.push_back(E);
}
return ret;
}
void MultiplayerAPI::set_allow_object_decoding(bool p_enable) {
allow_object_decoding = p_enable;
}
bool MultiplayerAPI::is_object_decoding_allowed() const {
return allow_object_decoding;
}
String MultiplayerAPI::get_rpc_md5(const Object *p_obj) const {
return rpc->get_rpc_md5(p_obj);
}
void MultiplayerAPI::rpcp(Object *p_obj, int p_peer_id, const StringName &p_method, const Variant **p_arg, int p_argcount) {
rpc->rpcp(p_obj, p_peer_id, p_method, p_arg, p_argcount);
}
Error MultiplayerAPI::spawn(Object *p_object, Variant p_config) {
return replicator->on_spawn(p_object, p_config);
}
Error MultiplayerAPI::despawn(Object *p_object, Variant p_config) {
return replicator->on_despawn(p_object, p_config);
}
Error MultiplayerAPI::replication_start(Object *p_object, Variant p_config) {
return replicator->on_replication_start(p_object, p_config);
}
Error MultiplayerAPI::replication_stop(Object *p_object, Variant p_config) {
return replicator->on_replication_stop(p_object, p_config);
}
void MultiplayerAPI::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_root_path", "path"), &MultiplayerAPI::set_root_path);
ClassDB::bind_method(D_METHOD("get_root_path"), &MultiplayerAPI::get_root_path);
ClassDB::bind_method(D_METHOD("send_bytes", "bytes", "id", "mode", "channel"), &MultiplayerAPI::send_bytes, DEFVAL(MultiplayerPeer::TARGET_PEER_BROADCAST), DEFVAL(Multiplayer::TRANSFER_MODE_RELIABLE), DEFVAL(0));
ClassDB::bind_method(D_METHOD("has_multiplayer_peer"), &MultiplayerAPI::has_multiplayer_peer);
ClassDB::bind_method(D_METHOD("get_multiplayer_peer"), &MultiplayerAPI::get_multiplayer_peer);
ClassDB::bind_method(D_METHOD("set_multiplayer_peer", "peer"), &MultiplayerAPI::set_multiplayer_peer);
ClassDB::bind_method(D_METHOD("get_unique_id"), &MultiplayerAPI::get_unique_id);
ClassDB::bind_method(D_METHOD("is_server"), &MultiplayerAPI::is_server);
ClassDB::bind_method(D_METHOD("get_remote_sender_id"), &MultiplayerAPI::get_remote_sender_id);
ClassDB::bind_method(D_METHOD("poll"), &MultiplayerAPI::poll);
ClassDB::bind_method(D_METHOD("clear"), &MultiplayerAPI::clear);
ClassDB::bind_method(D_METHOD("get_peers"), &MultiplayerAPI::get_peer_ids);
ClassDB::bind_method(D_METHOD("set_refuse_new_connections", "refuse"), &MultiplayerAPI::set_refuse_new_connections);
ClassDB::bind_method(D_METHOD("is_refusing_new_connections"), &MultiplayerAPI::is_refusing_new_connections);
ClassDB::bind_method(D_METHOD("set_allow_object_decoding", "enable"), &MultiplayerAPI::set_allow_object_decoding);
ClassDB::bind_method(D_METHOD("is_object_decoding_allowed"), &MultiplayerAPI::is_object_decoding_allowed);
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::OBJECT, "multiplayer_peer", PROPERTY_HINT_RESOURCE_TYPE, "MultiplayerPeer", PROPERTY_USAGE_NONE), "set_multiplayer_peer", "get_multiplayer_peer");
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "root_path"), "set_root_path", "get_root_path");
ADD_PROPERTY_DEFAULT("refuse_new_connections", false);
ADD_SIGNAL(MethodInfo("peer_connected", PropertyInfo(Variant::INT, "id")));
ADD_SIGNAL(MethodInfo("peer_disconnected", PropertyInfo(Variant::INT, "id")));
ADD_SIGNAL(MethodInfo("peer_packet", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::PACKED_BYTE_ARRAY, "packet")));
ADD_SIGNAL(MethodInfo("connected_to_server"));
ADD_SIGNAL(MethodInfo("connection_failed"));
ADD_SIGNAL(MethodInfo("server_disconnected"));
}
MultiplayerAPI::MultiplayerAPI() {
if (create_default_replication_interface) {
replicator = Ref<MultiplayerReplicationInterface>(create_default_replication_interface(this));
} else {
replicator.instantiate();
}
if (create_default_rpc_interface) {
rpc = Ref<MultiplayerRPCInterface>(create_default_rpc_interface(this));
} else {
rpc.instantiate();
}
if (create_default_cache_interface) {
cache = Ref<MultiplayerCacheInterface>(create_default_cache_interface(this));
} else {
cache.instantiate();
}
}
MultiplayerAPI::~MultiplayerAPI() {
clear();
}

View File

@ -1,196 +0,0 @@
/*************************************************************************/
/* multiplayer_api.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#ifndef MULTIPLAYER_API_H
#define MULTIPLAYER_API_H
#include "core/multiplayer/multiplayer.h"
#include "core/multiplayer/multiplayer_peer.h"
#include "core/object/ref_counted.h"
class MultiplayerAPI;
class MultiplayerReplicationInterface : public RefCounted {
GDCLASS(MultiplayerReplicationInterface, RefCounted);
public:
virtual void on_peer_change(int p_id, bool p_connected) {}
virtual void on_reset() {}
virtual Error on_spawn_receive(int p_from, const uint8_t *p_buffer, int p_buffer_len) { return ERR_UNAVAILABLE; }
virtual Error on_despawn_receive(int p_from, const uint8_t *p_buffer, int p_buffer_len) { return ERR_UNAVAILABLE; }
virtual Error on_sync_receive(int p_from, const uint8_t *p_buffer, int p_buffer_len) { return ERR_UNAVAILABLE; }
virtual Error on_spawn(Object *p_obj, Variant p_config) { return ERR_UNAVAILABLE; }
virtual Error on_despawn(Object *p_obj, Variant p_config) { return ERR_UNAVAILABLE; }
virtual Error on_replication_start(Object *p_obj, Variant p_config) { return ERR_UNAVAILABLE; }
virtual Error on_replication_stop(Object *p_obj, Variant p_config) { return ERR_UNAVAILABLE; }
virtual void on_network_process() {}
MultiplayerReplicationInterface() {}
};
class MultiplayerRPCInterface : public RefCounted {
GDCLASS(MultiplayerRPCInterface, RefCounted);
public:
// Called by Node.rpc
virtual void rpcp(Object *p_obj, int p_peer_id, const StringName &p_method, const Variant **p_arg, int p_argcount) {}
virtual void process_rpc(int p_from, const uint8_t *p_packet, int p_packet_len) {}
virtual String get_rpc_md5(const Object *p_obj) const { return String(); }
MultiplayerRPCInterface() {}
};
class MultiplayerCacheInterface : public RefCounted {
GDCLASS(MultiplayerCacheInterface, RefCounted);
public:
virtual void clear() {}
virtual void on_peer_change(int p_id, bool p_connected) {}
virtual void process_simplify_path(int p_from, const uint8_t *p_packet, int p_packet_len) {}
virtual void process_confirm_path(int p_from, const uint8_t *p_packet, int p_packet_len) {}
// Returns true if all peers have cached path.
virtual bool send_object_cache(Object *p_obj, int p_target, int &r_id) { return false; }
virtual int make_object_cache(Object *p_obj) { return false; }
virtual Object *get_cached_object(int p_from, uint32_t p_cache_id) { return nullptr; }
virtual bool is_cache_confirmed(NodePath p_path, int p_peer) { return false; }
MultiplayerCacheInterface() {}
};
class MultiplayerAPI : public RefCounted {
GDCLASS(MultiplayerAPI, RefCounted);
public:
enum NetworkCommands {
NETWORK_COMMAND_REMOTE_CALL = 0,
NETWORK_COMMAND_SIMPLIFY_PATH,
NETWORK_COMMAND_CONFIRM_PATH,
NETWORK_COMMAND_RAW,
NETWORK_COMMAND_SPAWN,
NETWORK_COMMAND_DESPAWN,
NETWORK_COMMAND_SYNC,
};
// For each command, the 4 MSB can contain custom flags, as defined by subsystems.
enum {
CMD_FLAG_0_SHIFT = 4,
CMD_FLAG_1_SHIFT = 5,
CMD_FLAG_2_SHIFT = 6,
CMD_FLAG_3_SHIFT = 7,
};
// This is the mask that will be used to extract the command.
enum {
CMD_MASK = 7, // 0x7 -> 0b00001111
};
private:
Ref<MultiplayerPeer> multiplayer_peer;
HashSet<int> connected_peers;
int remote_sender_id = 0;
int remote_sender_override = 0;
Vector<uint8_t> packet_cache;
NodePath root_path;
bool allow_object_decoding = false;
Ref<MultiplayerCacheInterface> cache;
Ref<MultiplayerReplicationInterface> replicator;
Ref<MultiplayerRPCInterface> rpc;
protected:
static void _bind_methods();
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);
public:
static MultiplayerReplicationInterface *(*create_default_replication_interface)(MultiplayerAPI *p_multiplayer);
static MultiplayerRPCInterface *(*create_default_rpc_interface)(MultiplayerAPI *p_multiplayer);
static MultiplayerCacheInterface *(*create_default_cache_interface)(MultiplayerAPI *p_multiplayer);
static Error encode_and_compress_variant(const Variant &p_variant, uint8_t *p_buffer, int &r_len, bool p_allow_object_decoding);
static Error decode_and_decompress_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int *r_len, bool p_allow_object_decoding);
static Error encode_and_compress_variants(const Variant **p_variants, int p_count, uint8_t *p_buffer, int &r_len, bool *r_raw = nullptr, bool p_allow_object_decoding = false);
static Error decode_and_decompress_variants(Vector<Variant> &r_variants, const uint8_t *p_buffer, int p_len, int &r_len, bool p_raw = false, bool p_allow_object_decoding = false);
void poll();
void clear();
void set_root_path(const NodePath &p_path);
NodePath get_root_path() const;
void set_multiplayer_peer(const Ref<MultiplayerPeer> &p_peer);
Ref<MultiplayerPeer> get_multiplayer_peer() const;
Error send_bytes(Vector<uint8_t> p_data, int p_to = MultiplayerPeer::TARGET_PEER_BROADCAST, Multiplayer::TransferMode p_mode = Multiplayer::TRANSFER_MODE_RELIABLE, int p_channel = 0);
// RPC API
void rpcp(Object *p_obj, int p_peer_id, const StringName &p_method, const Variant **p_arg, int p_argcount);
String get_rpc_md5(const Object *p_obj) const;
// Replication API
Error spawn(Object *p_object, Variant p_config);
Error despawn(Object *p_object, Variant p_config);
Error replication_start(Object *p_object, Variant p_config);
Error replication_stop(Object *p_object, Variant p_config);
// Cache API
bool send_object_cache(Object *p_obj, int p_target, int &r_id);
int make_object_cache(Object *p_obj);
Object *get_cached_object(int p_from, uint32_t p_cache_id);
bool is_cache_confirmed(NodePath p_path, int p_peer);
void _add_peer(int p_id);
void _del_peer(int p_id);
void _connected_to_server();
void _connection_failed();
void _server_disconnected();
bool has_multiplayer_peer() const { return multiplayer_peer.is_valid(); }
Vector<int> get_peer_ids() const;
const HashSet<int> get_connected_peers() const { return connected_peers; }
int get_remote_sender_id() const { return remote_sender_override ? remote_sender_override : remote_sender_id; }
void set_remote_sender_override(int p_id) { remote_sender_override = p_id; }
int get_unique_id() const;
bool is_server() const;
void set_refuse_new_connections(bool p_refuse);
bool is_refusing_new_connections() const;
void set_allow_object_decoding(bool p_enable);
bool is_object_decoding_allowed() const;
#ifdef DEBUG_ENABLED
void profile_bandwidth(const String &p_inout, int p_size);
#endif
MultiplayerAPI();
~MultiplayerAPI();
};
#endif // MULTIPLAYER_API_H

View File

@ -33,7 +33,6 @@
#include "core/doc_data.h"
#include "core/io/resource.h"
#include "core/multiplayer/multiplayer.h"
#include "core/templates/pair.h"
#include "core/templates/rb_map.h"
@ -159,7 +158,7 @@ public:
virtual bool is_placeholder_fallback_enabled() const { return false; }
virtual const Vector<Multiplayer::RPCConfig> get_rpc_methods() const = 0;
virtual const Variant get_rpc_config() const = 0;
Script() {}
};
@ -213,7 +212,7 @@ public:
virtual void property_set_fallback(const StringName &p_name, const Variant &p_value, bool *r_valid);
virtual Variant property_get_fallback(const StringName &p_name, bool *r_valid);
virtual const Vector<Multiplayer::RPCConfig> get_rpc_methods() const { return get_script()->get_rpc_methods(); }
virtual const Variant get_rpc_config() const { return get_script()->get_rpc_config(); }
virtual ScriptLanguage *get_language() = 0;
virtual ~ScriptInstance();
@ -469,7 +468,7 @@ public:
virtual void property_set_fallback(const StringName &p_name, const Variant &p_value, bool *r_valid = nullptr) override;
virtual Variant property_get_fallback(const StringName &p_name, bool *r_valid = nullptr) override;
virtual const Vector<Multiplayer::RPCConfig> get_rpc_methods() const override { return Vector<Multiplayer::RPCConfig>(); }
virtual const Variant get_rpc_config() const override { return Variant(); }
PlaceHolderScriptInstance(ScriptLanguage *p_language, Ref<Script> p_script, Object *p_owner);
~PlaceHolderScriptInstance();

View File

@ -74,7 +74,7 @@ void ScriptExtension::_bind_methods() {
GDVIRTUAL_BIND(_get_members);
GDVIRTUAL_BIND(_is_placeholder_fallback_enabled);
GDVIRTUAL_BIND(_get_rpc_methods);
GDVIRTUAL_BIND(_get_rpc_config);
}
void ScriptLanguageExtension::_bind_methods() {

View File

@ -173,28 +173,12 @@ public:
EXBIND0RC(bool, is_placeholder_fallback_enabled)
GDVIRTUAL0RC(TypedArray<Dictionary>, _get_rpc_methods)
GDVIRTUAL0RC(Variant, _get_rpc_config)
virtual const Vector<Multiplayer::RPCConfig> get_rpc_methods() const override {
TypedArray<Dictionary> ret;
GDVIRTUAL_REQUIRED_CALL(_get_rpc_methods, ret);
Vector<Multiplayer::RPCConfig> rpcret;
for (int i = 0; i < ret.size(); i++) {
Dictionary d = ret[i];
Multiplayer::RPCConfig rpc;
ERR_CONTINUE(!d.has("name"));
rpc.name = d["name"];
ERR_CONTINUE(!d.has("rpc_mode"));
rpc.rpc_mode = Multiplayer::RPCMode(int(d["rpc_mode"]));
ERR_CONTINUE(!d.has("call_local"));
rpc.call_local = d["call_local"];
ERR_CONTINUE(!d.has("transfer_mode"));
rpc.transfer_mode = Multiplayer::TransferMode(int(d["transfer_mode"]));
ERR_CONTINUE(!d.has("channel"));
rpc.channel = d["channel"];
rpcret.push_back(rpc);
}
return rpcret;
virtual const Variant get_rpc_config() const override {
Variant ret;
GDVIRTUAL_REQUIRED_CALL(_get_rpc_config, ret);
return ret;
}
ScriptExtension() {}

View File

@ -69,8 +69,6 @@
#include "core/math/geometry_3d.h"
#include "core/math/random_number_generator.h"
#include "core/math/triangle_mesh.h"
#include "core/multiplayer/multiplayer_api.h"
#include "core/multiplayer/multiplayer_peer.h"
#include "core/object/class_db.h"
#include "core/object/script_language_extension.h"
#include "core/object/undo_redo.h"
@ -212,9 +210,6 @@ void register_core_types() {
resource_format_loader_crypto.instantiate();
ResourceLoader::add_resource_format_loader(resource_format_loader_crypto);
GDREGISTER_ABSTRACT_CLASS(MultiplayerPeer);
GDREGISTER_CLASS(MultiplayerPeerExtension);
GDREGISTER_CLASS(MultiplayerAPI);
GDREGISTER_CLASS(MainLoop);
GDREGISTER_CLASS(Translation);
GDREGISTER_CLASS(OptimizedTranslation);

View File

@ -63,17 +63,19 @@ void Callable::call(const Variant **p_arguments, int p_argcount, Variant &r_retu
}
}
void Callable::rpc(int p_id, const Variant **p_arguments, int p_argcount, CallError &r_call_error) const {
Error Callable::rpc(int p_id, const Variant **p_arguments, int p_argcount, CallError &r_call_error) const {
if (is_null()) {
r_call_error.error = CallError::CALL_ERROR_INSTANCE_IS_NULL;
r_call_error.argument = 0;
r_call_error.expected = 0;
return ERR_UNCONFIGURED;
} else if (!is_custom()) {
r_call_error.error = CallError::CALL_ERROR_INVALID_METHOD;
r_call_error.argument = 0;
r_call_error.expected = 0;
return ERR_UNCONFIGURED;
} else {
custom->rpc(p_id, p_arguments, p_argcount, r_call_error);
return custom->rpc(p_id, p_arguments, p_argcount, r_call_error);
}
}
@ -316,10 +318,11 @@ StringName CallableCustom::get_method() const {
ERR_FAIL_V_MSG(StringName(), vformat("Can't get method on CallableCustom \"%s\".", get_as_text()));
}
void CallableCustom::rpc(int p_peer_id, const Variant **p_arguments, int p_argcount, Callable::CallError &r_call_error) const {
Error CallableCustom::rpc(int p_peer_id, const Variant **p_arguments, int p_argcount, Callable::CallError &r_call_error) const {
r_call_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD;
r_call_error.argument = 0;
r_call_error.expected = 0;
return ERR_UNCONFIGURED;
}
const Callable *CallableCustom::get_base_comparator() const {

View File

@ -71,7 +71,7 @@ public:
void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, CallError &r_call_error) const;
void call_deferred(const Variant **p_arguments, int p_argcount) const;
void rpc(int p_id, const Variant **p_arguments, int p_argcount, CallError &r_call_error) const;
Error rpc(int p_id, const Variant **p_arguments, int p_argcount, CallError &r_call_error) const;
_FORCE_INLINE_ bool is_null() const {
return method == StringName() && object == 0;
@ -129,7 +129,7 @@ public:
virtual StringName get_method() const;
virtual ObjectID get_object() const = 0; //must always be able to provide an object
virtual void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const = 0;
virtual void rpc(int p_peer_id, const Variant **p_arguments, int p_argcount, Callable::CallError &r_call_error) const;
virtual Error rpc(int p_peer_id, const Variant **p_arguments, int p_argcount, Callable::CallError &r_call_error) const;
virtual const Callable *get_base_comparator() const;
CallableCustom();

View File

@ -2728,24 +2728,6 @@
<constant name="METHOD_FLAGS_DEFAULT" value="1" enum="MethodFlags">
Default method flags.
</constant>
<constant name="RPC_MODE_DISABLED" value="0" enum="RPCMode">
Used with [method Node.rpc_config] to disable a method or property for all RPC calls, making it unavailable. Default for all methods.
</constant>
<constant name="RPC_MODE_ANY_PEER" value="1" enum="RPCMode">
Used with [method Node.rpc_config] to set a method to be callable remotely by any peer. Analogous to the [code]@rpc(any)[/code] annotation. Calls are accepted from all remote peers, no matter if they are node's authority or not.
</constant>
<constant name="RPC_MODE_AUTHORITY" value="2" enum="RPCMode">
Used with [method Node.rpc_config] to set a method to be callable remotely only by the current multiplayer authority (which is the server by default). Analogous to the [code]@rpc(authority)[/code] annotation. See [method Node.set_multiplayer_authority].
</constant>
<constant name="TRANSFER_MODE_UNRELIABLE" value="0" enum="TransferMode">
Packets are not acknowledged, no resend attempts are made for lost packets. Packets may arrive in any order. Potentially faster than [constant TRANSFER_MODE_UNRELIABLE_ORDERED]. Use for non-critical data, and always consider whether the order matters.
</constant>
<constant name="TRANSFER_MODE_UNRELIABLE_ORDERED" value="1" enum="TransferMode">
Packets are not acknowledged, no resend attempts are made for lost packets. Packets are received in the order they were sent in. Potentially faster than [constant TRANSFER_MODE_RELIABLE]. Use for non-critical data or data that would be outdated if received late due to resend attempt(s) anyway, for example movement and positional data.
</constant>
<constant name="TRANSFER_MODE_RELIABLE" value="2" enum="TransferMode">
Packets must be received and resend attempts should be made until the packets are acknowledged. Packets must be received in the order they were sent in. Most reliable transfer mode, but potentially the slowest due to the overhead. Use for critical data that must be transmitted and arrive in order, for example an ability being triggered or a chat message. Consider carefully if the information really is critical, and use sparingly.
</constant>
<constant name="TYPE_NIL" value="0" enum="Variant.Type">
Variable is [code]null[/code].
</constant>

View File

@ -1,88 +1,108 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="MultiplayerAPI" inherits="RefCounted" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<brief_description>
High-level multiplayer API.
High-level multiplayer API interface.
</brief_description>
<description>
This class implements the high-level multiplayer API. See also [MultiplayerPeer].
By default, [SceneTree] has a reference to this class that is used to provide multiplayer capabilities (i.e. RPCs) across the whole scene.
Base class for high-level multiplayer API implementations. See also [MultiplayerPeer].
By default, [SceneTree] has a reference to an implementation of this class and uses it to provide multiplayer capabilities (i.e. RPCs) across the whole scene.
It is possible to override the MultiplayerAPI instance used by specific tree branches by calling the [method SceneTree.set_multiplayer] method, effectively allowing to run both client and server in the same scene.
[b]Note:[/b] The high-level multiplayer API protocol is an implementation detail and isn't meant to be used by non-Godot servers. It may change without notice.
[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.
It is also possible to extend or replace the default implementation via scripting or native extensions. See [MultiplayerAPIExtension] for details about extensions, [SceneMultiplayer] for the details about the default implementation.
</description>
<tutorials>
</tutorials>
<methods>
<method name="clear">
<return type="void" />
<method name="create_default_interface" qualifiers="static">
<return type="MultiplayerAPI" />
<description>
Clears the current MultiplayerAPI network state (you shouldn't call this unless you know what you are doing).
Returns a new instance of the default MultiplayerAPI.
</description>
</method>
<method name="get_peers" qualifiers="const">
<method name="get_default_interface" qualifiers="static">
<return type="StringName" />
<description>
Returns the default MultiplayerAPI implementation class name. This is usually [code]"SceneMultiplayer"[/code] when [SceneMultiplayer] is available. See [method set_default_interface].
</description>
</method>
<method name="get_peers">
<return type="PackedInt32Array" />
<description>
Returns the peer IDs of all connected peers of this MultiplayerAPI's [member multiplayer_peer].
</description>
</method>
<method name="get_remote_sender_id" qualifiers="const">
<method name="get_remote_sender_id">
<return type="int" />
<description>
Returns the sender's peer ID for the RPC currently being executed.
[b]Note:[/b] If not inside an RPC this method will return 0.
</description>
</method>
<method name="get_unique_id" qualifiers="const">
<method name="get_unique_id">
<return type="int" />
<description>
Returns the unique peer ID of this MultiplayerAPI's [member multiplayer_peer].
</description>
</method>
<method name="has_multiplayer_peer" qualifiers="const">
<method name="has_multiplayer_peer">
<return type="bool" />
<description>
Returns [code]true[/code] if there is a [member multiplayer_peer] set.
</description>
</method>
<method name="is_server" qualifiers="const">
<method name="is_server">
<return type="bool" />
<description>
Returns [code]true[/code] if this MultiplayerAPI's [member multiplayer_peer] is valid and in server mode (listening for connections).
</description>
</method>
<method name="object_configuration_add">
<return type="int" enum="Error" />
<argument index="0" name="object" type="Object" />
<argument index="1" name="configuration" type="Variant" />
<description>
Notifies the MultiplayerAPI of a new [code]configuration[/code] for the given [code]object[/code]. This method is used internally by [SceneTree] to configure the root path for this MultiplayerAPI (passing [code]null[/code] and a valid [NodePath] as [code]configuration[/code]). This method can be further used by MultiplayerAPI implementations to provide additional features, refer to specific implementation (e.g. [SceneMultiplayer]) for details on how they use it.
[b]Note:[/b] This method is mostly relevant when extending or overriding the MultiplayerAPI behavior via [MultiplayerAPIExtension].
</description>
</method>
<method name="object_configuration_remove">
<return type="int" enum="Error" />
<argument index="0" name="object" type="Object" />
<argument index="1" name="configuration" type="Variant" />
<description>
Notifies the MultiplayerAPI to remove a [code]configuration[/code] for the given [code]object[/code]. This method is used internally by [SceneTree] to configure the root path for this MultiplayerAPI (passing [code]null[/code] and an empty [NodePath] as [code]configuration[/code]). This method can be further used by MultiplayerAPI implementations to provide additional features, refer to specific implementation (e.g. [SceneMultiplayer]) for details on how they use it.
[b]Note:[/b] This method is mostly relevant when extending or overriding the MultiplayerAPI behavior via [MultiplayerAPIExtension].
</description>
</method>
<method name="poll">
<return type="void" />
<return type="int" enum="Error" />
<description>
Method used for polling the MultiplayerAPI. You only need to worry about this if you set [member SceneTree.multiplayer_poll] to [code]false[/code]. By default, [SceneTree] will poll its MultiplayerAPI(s) for you.
[b]Note:[/b] This method results in RPCs being called, so they will be executed in the same context of this function (e.g. [code]_process[/code], [code]physics[/code], [Thread]).
</description>
</method>
<method name="send_bytes">
<method name="rpc">
<return type="int" enum="Error" />
<argument index="0" name="bytes" type="PackedByteArray" />
<argument index="1" name="id" type="int" default="0" />
<argument index="2" name="mode" type="int" enum="TransferMode" default="2" />
<argument index="3" name="channel" type="int" default="0" />
<argument index="0" name="peer" type="int" />
<argument index="1" name="object" type="Object" />
<argument index="2" name="method" type="StringName" />
<argument index="3" name="arguments" type="Array" default="[]" />
<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.
Sends an RPC to the target [code]peer[/code]. The given [code]method[/code] will be called on the remote [code]object[/code] with the provided [code]arguments[/code]. The RPC may also be called locally depending on the implementation and RPC configuration. See [method Node.rpc] and [method Node.rpc_config].
[b]Note:[/b] Prefer using [method Node.rpc], [method Node.rpc_id], or [code]my_method.rpc(peer, arg1, arg2, ...)[/code] (in GDScript), since they are faster. This method is mostly useful in conjunction with [MultiplayerAPIExtension] when augmenting or replacing the multiplayer capabilities.
</description>
</method>
<method name="set_default_interface" qualifiers="static">
<return type="void" />
<argument index="0" name="interface_name" type="StringName" />
<description>
Sets the default MultiplayerAPI implementation class. This method can be used by modules and extensions to configure which implementation will be used by [SceneTree] when the engine starts.
</description>
</method>
</methods>
<members>
<member name="allow_object_decoding" type="bool" setter="set_allow_object_decoding" getter="is_object_decoding_allowed" default="false">
If [code]true[/code], the MultiplayerAPI will allow encoding and decoding of object during RPCs.
[b]Warning:[/b] Deserialized objects can contain code which gets executed. Do not use this option if the serialized object comes from untrusted sources to avoid potential security threats such as remote code execution.
</member>
<member name="multiplayer_peer" type="MultiplayerPeer" setter="set_multiplayer_peer" getter="get_multiplayer_peer">
The peer object to handle the RPC system (effectively enabling networking when set). Depending on the peer itself, the MultiplayerAPI will become a network server (check with [method is_server]) and will set root node's network mode to authority, or it will become a regular client peer. All child nodes are set to inherit the network mode by default. Handling of networking-related events (connection, disconnection, new clients) is done by connecting to MultiplayerAPI's signals.
</member>
<member name="refuse_new_connections" type="bool" setter="set_refuse_new_connections" getter="is_refusing_new_connections" default="false">
If [code]true[/code], the MultiplayerAPI's [member multiplayer_peer] refuses new incoming connections.
</member>
<member name="root_path" type="NodePath" setter="set_root_path" getter="get_root_path" default="NodePath(&quot;&quot;)">
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>
</members>
<signals>
<signal name="connected_to_server">
@ -107,17 +127,21 @@
Emitted when this MultiplayerAPI's [member multiplayer_peer] disconnects from a peer. Clients get notified when other clients disconnect from the same server.
</description>
</signal>
<signal name="peer_packet">
<argument index="0" name="id" type="int" />
<argument index="1" name="packet" type="PackedByteArray" />
<description>
Emitted when this MultiplayerAPI's [member multiplayer_peer] receives a [code]packet[/code] with custom data (see [method send_bytes]). ID is the peer ID of the peer that sent the packet.
</description>
</signal>
<signal name="server_disconnected">
<description>
Emitted when this MultiplayerAPI's [member multiplayer_peer] disconnects from server. Only emitted on clients.
</description>
</signal>
</signals>
<constants>
<constant name="RPC_MODE_DISABLED" value="0" enum="RPCMode">
Used with [method Node.rpc_config] to disable a method or property for all RPC calls, making it unavailable. Default for all methods.
</constant>
<constant name="RPC_MODE_ANY_PEER" value="1" enum="RPCMode">
Used with [method Node.rpc_config] to set a method to be callable remotely by any peer. Analogous to the [code]@rpc(any)[/code] annotation. Calls are accepted from all remote peers, no matter if they are node's authority or not.
</constant>
<constant name="RPC_MODE_AUTHORITY" value="2" enum="RPCMode">
Used with [method Node.rpc_config] to set a method to be callable remotely only by the current multiplayer authority (which is the server by default). Analogous to the [code]@rpc(authority)[/code] annotation. See [method Node.set_multiplayer_authority].
</constant>
</constants>
</class>

View File

@ -0,0 +1,141 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="MultiplayerAPIExtension" inherits="MultiplayerAPI" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<brief_description>
Base class used for extending the [MultiplayerAPI].
</brief_description>
<description>
This class can be used to augment or replace the default [MultiplayerAPI] implementation via script or extensions.
The following example augment the default implemenation ([SceneMultiplayer]) by logging every RPC being made, and every object being configured for replication.
[codeblocks]
[gdscript]
extends MultiplayerAPIExtension
class_name LogMultiplayer
# We want to augment the default SceneMultiplayer.
var base_multiplayer = SceneMultiplayer.new()
func _init():
# Just passthourgh base signals (copied to var to avoid cyclic reference)
var cts = connected_to_server
var cf = connection_failed
var pc = peer_connected
var pd = peer_disconnected
base_multiplayer.connected_to_server.connect(func(): cts.emit())
base_multiplayer.connection_failed.connect(func(): cf.emit())
base_multiplayer.peer_connected.connect(func(id): pc.emit(id))
base_multiplayer.peer_disconnected.connect(func(id): pd.emit(id))
# Log RPC being made and forward it to the default multiplayer.
func _rpc(peer: int, object: Object, method: StringName, args: Array) -&gt; int: # Error
print("Got RPC for %d: %s::%s(%s)" % [peer, object, method, args])
return base_multiplayer.rpc(peer, object, method, args)
# Log configuration add. E.g. root path (nullptr, NodePath), replication (Node, Spawner|Synchronizer), custom.
func _object_configuration_add(object, config: Variant) -&gt; int: # Error
if config is MultiplayerSynchronizer:
print("Adding synchronization configuration for %s. Synchronizer: %s" % [object, config])
elif config is MultiplayerSpawner:
print("Adding node %s to the spawn list. Spawner: %s" % [object, config])
return base_multiplayer.object_configuration_add(object, config)
# Log configuration remove. E.g. root path (nullptr, NodePath), replication (Node, Spawner|Synchronizer), custom.
func _object_configuration_remove(object, config: Variant) -&gt; int: # Error
if config is MultiplayerSynchronizer:
print("Removing synchronization configuration for %s. Synchronizer: %s" % [object, config])
elif config is MultiplayerSpawner:
print("Removing node %s from the spawn list. Spawner: %s" % [object, config])
return base_multiplayer.object_configuration_remove(object, config)
# These can be optional, but in our case we want to augment SceneMultiplayer, so forward everything.
func _set_multiplayer_peer(p_peer: MultiplayerPeer):
base_multiplayer.multiplayer_peer = p_peer
func _get_multiplayer_peer() -&gt; MultiplayerPeer:
return base_multiplayer.multiplayer_peer
func _get_unique_id() -&gt; int:
return base_multiplayer.get_unique_id()
func _get_peer_ids() -&gt; PackedInt32Array:
return base_multiplayer.get_peers()
[/gdscript]
[/codeblocks]
Then in your main scene or in an autoload call [method SceneTree.set_multiplayer] to start using your custom [MultiplayerAPI]:
[codeblocks]
[gdscript]
# autoload.gd
func _enter_tree():
# Sets our custom multiplayer as the main one in SceneTree.
get_tree().set_multiplayer(LogMultiplayer.new())
[/gdscript]
[/codeblocks]
Native extensions can alternatively use the [method MultiplayerAPI.set_default_interface] method during initialization to configure themselves as the default implementation.
</description>
<tutorials>
</tutorials>
<methods>
<method name="_get_multiplayer_peer" qualifiers="virtual">
<return type="MultiplayerPeer" />
<description>
Called when the [member MultiplayerAPI.multiplayer_peer] is retrieved.
</description>
</method>
<method name="_get_peer_ids" qualifiers="virtual const">
<return type="PackedInt32Array" />
<description>
Callback for [method MultiplayerAPI.get_peers].
</description>
</method>
<method name="_get_remote_sender_id" qualifiers="virtual const">
<return type="int" />
<description>
Callback for [method MultiplayerAPI.get_remote_sender_id].
</description>
</method>
<method name="_get_unique_id" qualifiers="virtual const">
<return type="int" />
<description>
Callback for [method MultiplayerAPI.get_unique_id].
</description>
</method>
<method name="_object_configuration_add" qualifiers="virtual">
<return type="int" />
<argument index="0" name="object" type="Object" />
<argument index="1" name="configuration" type="Variant" />
<description>
Callback for [method MultiplayerAPI.object_configuration_add].
</description>
</method>
<method name="_object_configuration_remove" qualifiers="virtual">
<return type="int" />
<argument index="0" name="object" type="Object" />
<argument index="1" name="configuration" type="Variant" />
<description>
Callback for [method MultiplayerAPI.object_configuration_remove].
</description>
</method>
<method name="_poll" qualifiers="virtual">
<return type="int" />
<description>
Callback for [method MultiplayerAPI.poll].
</description>
</method>
<method name="_rpc" qualifiers="virtual">
<return type="int" />
<argument index="0" name="peer" type="int" />
<argument index="1" name="object" type="Object" />
<argument index="2" name="method" type="StringName" />
<argument index="3" name="args" type="Array" />
<description>
Callback for [method MultiplayerAPI.rpc].
</description>
</method>
<method name="_set_multiplayer_peer" qualifiers="virtual">
<return type="void" />
<argument index="0" name="multiplayer_peer" type="MultiplayerPeer" />
<description>
Called when the [member MultiplayerAPI.multiplayer_peer] is set.
</description>
</method>
</methods>
</class>

View File

@ -60,7 +60,7 @@
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="TransferMode" default="2">
<member name="transfer_mode" type="int" setter="set_transfer_mode" getter="get_transfer_mode" enum="MultiplayerPeer.TransferMode" default="2">
The manner in which to send packets to the [code]target_peer[/code]. See [enum TransferMode].
</member>
</members>
@ -109,5 +109,14 @@
<constant name="TARGET_PEER_SERVER" value="1">
Packets are sent to the server alone.
</constant>
<constant name="TRANSFER_MODE_UNRELIABLE" value="0" enum="TransferMode">
Packets are not acknowledged, no resend attempts are made for lost packets. Packets may arrive in any order. Potentially faster than [constant TRANSFER_MODE_UNRELIABLE_ORDERED]. Use for non-critical data, and always consider whether the order matters.
</constant>
<constant name="TRANSFER_MODE_UNRELIABLE_ORDERED" value="1" enum="TransferMode">
Packets are not acknowledged, no resend attempts are made for lost packets. Packets are received in the order they were sent in. Potentially faster than [constant TRANSFER_MODE_RELIABLE]. Use for non-critical data or data that would be outdated if received late due to resend attempt(s) anyway, for example movement and positional data.
</constant>
<constant name="TRANSFER_MODE_RELIABLE" value="2" enum="TransferMode">
Packets must be received and resend attempts should be made until the packets are acknowledged. Packets must be received in the order they were sent in. Most reliable transfer mode, but potentially the slowest due to the overhead. Use for critical data that must be transmitted and arrive in order, for example an ability being triggered or a chat message. Consider carefully if the information really is critical, and use sparingly.
</constant>
</constants>
</class>

View File

@ -622,7 +622,7 @@
</description>
</method>
<method name="rpc" qualifiers="vararg">
<return type="void" />
<return type="int" enum="Error" />
<argument index="0" name="method" type="StringName" />
<description>
Sends a remote procedure call request for the given [code]method[/code] to peers on the network (and locally), optionally sending all additional arguments as arguments to the method called by the RPC. The call request will only be received by nodes with the same [NodePath], including the exact same node name. Behaviour depends on the RPC configuration for the given method, see [method rpc_config]. Methods are not exposed to RPCs by default. Returns [code]null[/code].
@ -630,18 +630,24 @@
</description>
</method>
<method name="rpc_config">
<return type="int" />
<return type="void" />
<argument index="0" name="method" type="StringName" />
<argument index="1" name="rpc_mode" type="int" enum="RPCMode" />
<argument index="2" name="call_local" type="bool" default="false" />
<argument index="3" name="transfer_mode" type="int" enum="TransferMode" default="2" />
<argument index="4" name="channel" type="int" default="0" />
<argument index="1" name="config" type="Variant" />
<description>
Changes the RPC mode for the given [code]method[/code] to the given [code]rpc_mode[/code], optionally specifying the [code]transfer_mode[/code] and [code]channel[/code] (on supported peers). See [enum RPCMode] and [enum TransferMode]. An alternative is annotating methods and properties with the corresponding annotation ([code]@rpc(any)[/code], [code]@rpc(authority)[/code]). By default, methods are not exposed to networking (and RPCs).
Changes the RPC mode for the given [code]method[/code] with the given [code]config[/code] which should be [code]null[/code] (to disable) or a [Dictionary] in the form:
[codeblock]
{
rpc_mode = MultiplayerAPI.RPCMode,
transfer_mode = MultiplayerPeer.TranferMode,
call_local = false,
channel = 0,
}
[/codeblock]
See [enum MultiplayerAPI.RPCMode] and [enum MultiplayerPeer.TransferMode]. An alternative is annotating methods and properties with the corresponding annotation ([code]@rpc(any)[/code], [code]@rpc(authority)[/code]). By default, methods are not exposed to networking (and RPCs).
</description>
</method>
<method name="rpc_id" qualifiers="vararg">
<return type="void" />
<return type="int" enum="Error" />
<argument index="0" name="peer_id" type="int" />
<argument index="1" name="method" type="StringName" />
<description>

View File

@ -65,8 +65,8 @@
<description>
</description>
</method>
<method name="_get_rpc_methods" qualifiers="virtual const">
<return type="Dictionary[]" />
<method name="_get_rpc_config" qualifiers="virtual const">
<return type="Variant" />
<description>
</description>
</method>

View File

@ -174,7 +174,6 @@
#include "editor/plugins/polygon_2d_editor_plugin.h"
#include "editor/plugins/polygon_3d_editor_plugin.h"
#include "editor/plugins/ray_cast_2d_editor_plugin.h"
#include "editor/plugins/replication_editor_plugin.h"
#include "editor/plugins/resource_preloader_editor_plugin.h"
#include "editor/plugins/root_motion_editor_plugin.h"
#include "editor/plugins/script_editor_plugin.h"
@ -7131,7 +7130,6 @@ EditorNode::EditorNode() {
add_editor_plugin(memnew(Path3DEditorPlugin));
add_editor_plugin(memnew(PhysicalBone3DEditorPlugin));
add_editor_plugin(memnew(Polygon3DEditorPlugin));
add_editor_plugin(memnew(ReplicationEditorPlugin));
add_editor_plugin(memnew(ResourcePreloaderEditorPlugin));
add_editor_plugin(memnew(ShaderEditorPlugin));
add_editor_plugin(memnew(ShaderFileEditorPlugin));

View File

@ -441,15 +441,15 @@ Error ENetMultiplayerPeer::put_packet(const uint8_t *p_buffer, int p_buffer_size
channel = SYSCH_MAX + transfer_channel - 1;
} else {
switch (get_transfer_mode()) {
case Multiplayer::TRANSFER_MODE_UNRELIABLE: {
case TRANSFER_MODE_UNRELIABLE: {
packet_flags = ENET_PACKET_FLAG_UNSEQUENCED | ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT;
channel = SYSCH_UNRELIABLE;
} break;
case Multiplayer::TRANSFER_MODE_UNRELIABLE_ORDERED: {
case TRANSFER_MODE_UNRELIABLE_ORDERED: {
packet_flags = ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT;
channel = SYSCH_UNRELIABLE;
} break;
case Multiplayer::TRANSFER_MODE_RELIABLE: {
case TRANSFER_MODE_RELIABLE: {
packet_flags = ENET_PACKET_FLAG_RELIABLE;
channel = SYSCH_RELIABLE;
} break;

View File

@ -32,7 +32,7 @@
#define ENET_MULTIPLAYER_PEER_H
#include "core/crypto/crypto.h"
#include "core/multiplayer/multiplayer_peer.h"
#include "scene/main/multiplayer_peer.h"
#include "enet_connection.h"
#include <enet/enet.h>

View File

@ -954,8 +954,8 @@ void GDScript::get_members(HashSet<StringName> *p_members) {
}
}
const Vector<Multiplayer::RPCConfig> GDScript::get_rpc_methods() const {
return rpc_functions;
const Variant GDScript::get_rpc_config() const {
return rpc_config;
}
Variant GDScript::callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
@ -1212,9 +1212,9 @@ void GDScript::_save_orphaned_subclasses() {
void GDScript::_init_rpc_methods_properties() {
// Copy the base rpc methods so we don't mask their IDs.
rpc_functions.clear();
rpc_config.clear();
if (base.is_valid()) {
rpc_functions = base->rpc_functions;
rpc_config = base->rpc_config.duplicate();
}
GDScript *cscript = this;
@ -1222,12 +1222,9 @@ void GDScript::_init_rpc_methods_properties() {
while (cscript) {
// RPC Methods
for (KeyValue<StringName, GDScriptFunction *> &E : cscript->member_functions) {
Multiplayer::RPCConfig config = E.value->get_rpc_config();
if (config.rpc_mode != Multiplayer::RPC_MODE_DISABLED) {
config.name = E.value->get_name();
if (rpc_functions.find(config) == -1) {
rpc_functions.push_back(config);
}
Variant config = E.value->get_rpc_config();
if (config.get_type() != Variant::NIL) {
rpc_config[E.value->get_name()] = config;
}
}
@ -1241,9 +1238,6 @@ void GDScript::_init_rpc_methods_properties() {
cscript = nullptr;
}
}
// Sort so we are 100% that they are always the same.
rpc_functions.sort_custom<Multiplayer::SortRPCConfig>();
}
GDScript::~GDScript() {
@ -1408,9 +1402,7 @@ bool GDScriptInstance::get(const StringName &p_name, Variant &r_ret) const {
while (sl) {
HashMap<StringName, GDScriptFunction *>::ConstIterator E = sl->member_functions.find(p_name);
if (E) {
Multiplayer::RPCConfig config;
config.name = p_name;
if (sptr->rpc_functions.find(config) != -1) {
if (sptr->rpc_config.has(p_name)) {
r_ret = Callable(memnew(GDScriptRPCCallable(this->owner, E->key)));
} else {
r_ret = Callable(this->owner, E->key);
@ -1629,8 +1621,8 @@ ScriptLanguage *GDScriptInstance::get_language() {
return GDScriptLanguage::get_singleton();
}
const Vector<Multiplayer::RPCConfig> GDScriptInstance::get_rpc_methods() const {
return script->get_rpc_methods();
const Variant GDScriptInstance::get_rpc_config() const {
return script->get_rpc_config();
}
void GDScriptInstance::reload_members() {

View File

@ -87,7 +87,7 @@ class GDScript : public Script {
HashMap<StringName, MemberInfo> member_indices; //members are just indices to the instantiated script.
HashMap<StringName, Ref<GDScript>> subclasses;
HashMap<StringName, Vector<StringName>> _signals;
Vector<Multiplayer::RPCConfig> rpc_functions;
Dictionary rpc_config;
#ifdef TOOLS_ENABLED
@ -250,7 +250,7 @@ public:
virtual void get_constants(HashMap<StringName, Variant> *p_constants) override;
virtual void get_members(HashSet<StringName> *p_members) override;
virtual const Vector<Multiplayer::RPCConfig> get_rpc_methods() const override;
virtual const Variant get_rpc_config() const override;
#ifdef TOOLS_ENABLED
virtual bool is_placeholder_fallback_enabled() const override { return placeholder_fallback_enabled; }
@ -304,7 +304,7 @@ public:
void reload_members();
virtual const Vector<Multiplayer::RPCConfig> get_rpc_methods() const;
virtual const Variant get_rpc_config() const;
GDScriptInstance();
~GDScriptInstance();

View File

@ -158,7 +158,7 @@ void GDScriptByteCodeGenerator::end_parameters() {
function->default_arguments.reverse();
}
void GDScriptByteCodeGenerator::write_start(GDScript *p_script, const StringName &p_function_name, bool p_static, Multiplayer::RPCConfig p_rpc_config, const GDScriptDataType &p_return_type) {
void GDScriptByteCodeGenerator::write_start(GDScript *p_script, const StringName &p_function_name, bool p_static, Variant p_rpc_config, const GDScriptDataType &p_return_type) {
function = memnew(GDScriptFunction);
debug_stack = EngineDebugger::is_active();

View File

@ -419,7 +419,7 @@ public:
virtual void start_block() override;
virtual void end_block() override;
virtual void write_start(GDScript *p_script, const StringName &p_function_name, bool p_static, Multiplayer::RPCConfig p_rpc_config, const GDScriptDataType &p_return_type) override;
virtual void write_start(GDScript *p_script, const StringName &p_function_name, bool p_static, Variant p_rpc_config, const GDScriptDataType &p_return_type) override;
virtual GDScriptFunction *write_end() override;
#ifdef DEBUG_ENABLED

View File

@ -31,7 +31,6 @@
#ifndef GDSCRIPT_CODEGEN_H
#define GDSCRIPT_CODEGEN_H
#include "core/multiplayer/multiplayer.h"
#include "core/string/string_name.h"
#include "core/variant/variant.h"
#include "gdscript_function.h"
@ -80,7 +79,7 @@ public:
virtual void start_block() = 0;
virtual void end_block() = 0;
virtual void write_start(GDScript *p_script, const StringName &p_function_name, bool p_static, Multiplayer::RPCConfig p_rpc_config, const GDScriptDataType &p_return_type) = 0;
virtual void write_start(GDScript *p_script, const StringName &p_function_name, bool p_static, Variant p_rpc_config, const GDScriptDataType &p_return_type) = 0;
virtual GDScriptFunction *write_end() = 0;
#ifdef DEBUG_ENABLED

View File

@ -1975,7 +1975,7 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_
StringName func_name;
bool is_static = false;
Multiplayer::RPCConfig rpc_config;
Variant rpc_config;
GDScriptDataType return_type;
return_type.has_type = true;
return_type.kind = GDScriptDataType::BUILTIN;

View File

@ -477,7 +477,7 @@ private:
int _initial_line = 0;
bool _static = false;
Multiplayer::RPCConfig rpc_config;
Variant rpc_config;
GDScript *_script = nullptr;
@ -599,7 +599,7 @@ public:
void disassemble(const Vector<String> &p_code_lines) const;
#endif
_FORCE_INLINE_ Multiplayer::RPCConfig get_rpc_config() const { return rpc_config; }
_FORCE_INLINE_ const Variant get_rpc_config() const { return rpc_config; }
GDScriptFunction();
~GDScriptFunction();
};

View File

@ -35,6 +35,7 @@
#include "core/io/resource_loader.h"
#include "core/math/math_defs.h"
#include "gdscript.h"
#include "scene/main/multiplayer_api.h"
#ifdef DEBUG_ENABLED
#include "core/os/os.h"
@ -145,7 +146,7 @@ GDScriptParser::GDScriptParser() {
// Warning annotations.
register_annotation(MethodInfo("@warning_ignore", PropertyInfo(Variant::STRING, "warning")), AnnotationInfo::CLASS | AnnotationInfo::VARIABLE | AnnotationInfo::SIGNAL | AnnotationInfo::CONSTANT | AnnotationInfo::FUNCTION | AnnotationInfo::STATEMENT, &GDScriptParser::warning_annotations, varray(), true);
// Networking.
register_annotation(MethodInfo("@rpc", PropertyInfo(Variant::STRING, "mode"), PropertyInfo(Variant::STRING, "sync"), PropertyInfo(Variant::STRING, "transfer_mode"), PropertyInfo(Variant::INT, "transfer_channel")), AnnotationInfo::FUNCTION, &GDScriptParser::network_annotations<Multiplayer::RPC_MODE_AUTHORITY>, varray("", "", "", 0), true);
register_annotation(MethodInfo("@rpc", PropertyInfo(Variant::STRING, "mode"), PropertyInfo(Variant::STRING, "sync"), PropertyInfo(Variant::STRING, "transfer_mode"), PropertyInfo(Variant::INT, "transfer_channel")), AnnotationInfo::FUNCTION, &GDScriptParser::rpc_annotation, varray("", "", "", 0), true);
}
GDScriptParser::~GDScriptParser() {
@ -3867,16 +3868,21 @@ bool GDScriptParser::warning_annotations(const AnnotationNode *p_annotation, Nod
#endif // DEBUG_ENABLED
}
template <Multiplayer::RPCMode t_mode>
bool GDScriptParser::network_annotations(const AnnotationNode *p_annotation, Node *p_node) {
ERR_FAIL_COND_V_MSG(p_node->type != Node::VARIABLE && p_node->type != Node::FUNCTION, false, vformat(R"("%s" annotation can only be applied to variables and functions.)", p_annotation->name));
bool GDScriptParser::rpc_annotation(const AnnotationNode *p_annotation, Node *p_node) {
ERR_FAIL_COND_V_MSG(p_node->type != Node::FUNCTION, false, vformat(R"("%s" annotation can only be applied to functions.)", p_annotation->name));
Multiplayer::RPCConfig rpc_config;
rpc_config.rpc_mode = t_mode;
FunctionNode *function = static_cast<FunctionNode *>(p_node);
if (function->rpc_config.get_type() != Variant::NIL) {
push_error(R"(RPC annotations can only be used once per function.)", p_annotation);
return false;
}
Dictionary rpc_config;
rpc_config["rpc_mode"] = MultiplayerAPI::RPC_MODE_AUTHORITY;
if (p_annotation->resolved_arguments.size()) {
int last = p_annotation->resolved_arguments.size() - 1;
if (p_annotation->resolved_arguments[last].get_type() == Variant::INT) {
rpc_config.channel = p_annotation->resolved_arguments[last].operator int();
rpc_config["channel"] = p_annotation->resolved_arguments[last].operator int();
last -= 1;
}
if (last > 3) {
@ -3886,37 +3892,25 @@ bool GDScriptParser::network_annotations(const AnnotationNode *p_annotation, Nod
for (int i = last; i >= 0; i--) {
String mode = p_annotation->resolved_arguments[i].operator String();
if (mode == "any_peer") {
rpc_config.rpc_mode = Multiplayer::RPC_MODE_ANY_PEER;
rpc_config["rpc_mode"] = MultiplayerAPI::RPC_MODE_ANY_PEER;
} else if (mode == "authority") {
rpc_config.rpc_mode = Multiplayer::RPC_MODE_AUTHORITY;
rpc_config["rpc_mode"] = MultiplayerAPI::RPC_MODE_AUTHORITY;
} else if (mode == "call_local") {
rpc_config.call_local = true;
rpc_config["call_local"] = true;
} else if (mode == "call_remote") {
rpc_config.call_local = false;
rpc_config["call_local"] = false;
} else if (mode == "reliable") {
rpc_config.transfer_mode = Multiplayer::TRANSFER_MODE_RELIABLE;
rpc_config["transfer_mode"] = MultiplayerPeer::TRANSFER_MODE_RELIABLE;
} else if (mode == "unreliable") {
rpc_config.transfer_mode = Multiplayer::TRANSFER_MODE_UNRELIABLE;
rpc_config["transfer_mode"] = MultiplayerPeer::TRANSFER_MODE_UNRELIABLE;
} else if (mode == "unreliable_ordered") {
rpc_config.transfer_mode = Multiplayer::TRANSFER_MODE_UNRELIABLE_ORDERED;
rpc_config["transfer_mode"] = MultiplayerPeer::TRANSFER_MODE_UNRELIABLE_ORDERED;
} else {
push_error(R"(Invalid RPC argument. Must be one of: 'call_local'/'call_remote' (local calls), 'any_peer'/'authority' (permission), 'reliable'/'unreliable'/'unreliable_ordered' (transfer mode).)", p_annotation);
}
}
}
switch (p_node->type) {
case Node::FUNCTION: {
FunctionNode *function = static_cast<FunctionNode *>(p_node);
if (function->rpc_config.rpc_mode != Multiplayer::RPC_MODE_DISABLED) {
push_error(R"(RPC annotations can only be used once per function.)", p_annotation);
return false;
}
function->rpc_config = rpc_config;
break;
}
default:
return false; // Unreachable.
}
function->rpc_config = rpc_config;
return true;
}

View File

@ -32,7 +32,6 @@
#define GDSCRIPT_PARSER_H
#include "core/io/resource.h"
#include "core/multiplayer/multiplayer.h"
#include "core/object/ref_counted.h"
#include "core/object/script_language.h"
#include "core/string/string_name.h"
@ -750,7 +749,7 @@ public:
SuiteNode *body = nullptr;
bool is_static = false;
bool is_coroutine = false;
Multiplayer::RPCConfig rpc_config;
Variant rpc_config;
MethodInfo info;
LambdaNode *source_lambda = nullptr;
#ifdef TOOLS_ENABLED
@ -1371,8 +1370,7 @@ private:
template <PropertyUsageFlags t_usage>
bool export_group_annotations(const AnnotationNode *p_annotation, Node *p_target);
bool warning_annotations(const AnnotationNode *p_annotation, Node *p_target);
template <Multiplayer::RPCMode t_mode>
bool network_annotations(const AnnotationNode *p_annotation, Node *p_target);
bool rpc_annotation(const AnnotationNode *p_annotation, Node *p_target);
// Statements.
Node *parse_statement();
VariableNode *parse_variable();

View File

@ -76,11 +76,11 @@ GDScriptRPCCallable::GDScriptRPCCallable(Object *p_object, const StringName &p_m
ERR_FAIL_COND_MSG(!node, "RPC can only be defined on class that extends Node.");
}
void GDScriptRPCCallable::rpc(int p_peer_id, const Variant **p_arguments, int p_argcount, Callable::CallError &r_call_error) const {
Error GDScriptRPCCallable::rpc(int p_peer_id, const Variant **p_arguments, int p_argcount, Callable::CallError &r_call_error) const {
if (unlikely(!node)) {
r_call_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL;
return;
return ERR_UNCONFIGURED;
}
r_call_error.error = Callable::CallError::CALL_OK;
node->rpcp(p_peer_id, method, p_arguments, p_argcount);
return node->rpcp(p_peer_id, method, p_arguments, p_argcount);
}

View File

@ -52,7 +52,7 @@ public:
CompareLessFunc get_compare_less_func() const override;
ObjectID get_object() const override;
void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override;
void rpc(int p_peer_id, const Variant **p_arguments, int p_argcount, Callable::CallError &r_call_error) const override;
Error rpc(int p_peer_id, const Variant **p_arguments, int p_argcount, Callable::CallError &r_call_error) const override;
GDScriptRPCCallable(Object *p_object, const StringName &p_method);
virtual ~GDScriptRPCCallable() = default;

View File

@ -690,9 +690,7 @@ Dictionary ExtendGDScriptParser::dump_function_api(const GDScriptParser::Functio
ERR_FAIL_NULL_V(p_func, func);
func["name"] = p_func->identifier->name;
func["return_type"] = p_func->get_datatype().to_string();
func["rpc_mode"] = p_func->rpc_config.rpc_mode;
func["rpc_transfer_mode"] = p_func->rpc_config.transfer_mode;
func["rpc_transfer_channel"] = p_func->rpc_config.channel;
func["rpc_config"] = p_func->rpc_config;
Array parameters;
for (int i = 0; i < p_func->parameters.size(); i++) {
Dictionary arg;

View File

@ -2141,8 +2141,8 @@ bool CSharpInstance::refcount_decremented() {
return ref_dying;
}
const Vector<Multiplayer::RPCConfig> CSharpInstance::get_rpc_methods() const {
return script->get_rpc_methods();
const Variant CSharpInstance::get_rpc_config() const {
return script->get_rpc_config();
}
void CSharpInstance::notification(int p_notification) {
@ -3060,7 +3060,7 @@ void CSharpScript::update_script_class_info(Ref<CSharpScript> p_script) {
p_script->script_class->fetch_methods_with_godot_api_checks(p_script->native);
p_script->rpc_functions.clear();
p_script->rpc_config.clear();
GDMonoClass *top = p_script->script_class;
while (top && top != p_script->native) {
@ -3072,12 +3072,9 @@ void CSharpScript::update_script_class_info(Ref<CSharpScript> p_script) {
Vector<GDMonoMethod *> methods = top->get_all_methods();
for (int i = 0; i < methods.size(); i++) {
if (!methods[i]->is_static()) {
Multiplayer::RPCConfig rpc_config = p_script->_member_get_rpc_config(methods[i]);
if (rpc_config.rpc_mode != Multiplayer::RPC_MODE_DISABLED) {
// RPC annotations can only be used once per method
if (p_script->rpc_functions.find(rpc_config) == -1) {
p_script->rpc_functions.push_back(rpc_config);
}
const Variant rpc_config = p_script->_member_get_rpc_config(methods[i]);
if (rpc_config.get_type() != Variant::NIL) {
p_script->rpc_config[methods[i]->get_name()] = rpc_config;
}
}
}
@ -3086,9 +3083,6 @@ void CSharpScript::update_script_class_info(Ref<CSharpScript> p_script) {
top = top->get_parent_class();
}
// Sort so we are 100% that they are always the same.
p_script->rpc_functions.sort_custom<Multiplayer::SortRPCConfig>();
p_script->load_script_signals(p_script->script_class, p_script->native);
}
@ -3511,23 +3505,24 @@ int CSharpScript::get_member_line(const StringName &p_member) const {
return -1;
}
Multiplayer::RPCConfig CSharpScript::_member_get_rpc_config(IMonoClassMember *p_member) const {
Multiplayer::RPCConfig rpc_config;
Variant CSharpScript::_member_get_rpc_config(IMonoClassMember *p_member) const {
Variant out;
MonoObject *rpc_attribute = p_member->get_attribute(CACHED_CLASS(RPCAttribute));
if (rpc_attribute != nullptr) {
rpc_config.name = p_member->get_name();
rpc_config.rpc_mode = (Multiplayer::RPCMode)CACHED_PROPERTY(RPCAttribute, Mode)->get_int_value(rpc_attribute);
rpc_config.call_local = CACHED_PROPERTY(RPCAttribute, CallLocal)->get_bool_value(rpc_attribute);
rpc_config.transfer_mode = (Multiplayer::TransferMode)CACHED_PROPERTY(RPCAttribute, TransferMode)->get_int_value(rpc_attribute);
rpc_config.channel = CACHED_PROPERTY(RPCAttribute, TransferChannel)->get_int_value(rpc_attribute);
Dictionary rpc_config;
rpc_config["rpc_mode"] = CACHED_PROPERTY(RPCAttribute, Mode)->get_int_value(rpc_attribute);
rpc_config["call_local"] = CACHED_PROPERTY(RPCAttribute, CallLocal)->get_bool_value(rpc_attribute);
rpc_config["transfer_mode"] = CACHED_PROPERTY(RPCAttribute, TransferMode)->get_int_value(rpc_attribute);
rpc_config["channel"] = CACHED_PROPERTY(RPCAttribute, TransferChannel)->get_int_value(rpc_attribute);
out = rpc_config;
}
return rpc_config;
return out;
}
const Vector<Multiplayer::RPCConfig> CSharpScript::get_rpc_methods() const {
return rpc_functions;
const Variant CSharpScript::get_rpc_config() const {
return rpc_config;
}
Error CSharpScript::load_source_code(const String &p_path) {

View File

@ -136,7 +136,7 @@ private:
HashMap<StringName, EventSignal> event_signals;
bool signals_invalidated = true;
Vector<Multiplayer::RPCConfig> rpc_functions;
Dictionary rpc_config;
#ifdef TOOLS_ENABLED
List<PropertyInfo> exported_members_cache; // members_cache
@ -179,7 +179,7 @@ private:
static void update_script_class_info(Ref<CSharpScript> p_script);
static void initialize_for_managed_type(Ref<CSharpScript> p_script, GDMonoClass *p_class, GDMonoClass *p_native);
Multiplayer::RPCConfig _member_get_rpc_config(IMonoClassMember *p_member) const;
Variant _member_get_rpc_config(IMonoClassMember *p_member) const;
protected:
static void _bind_methods();
@ -234,7 +234,7 @@ public:
int get_member_line(const StringName &p_member) const override;
const Vector<Multiplayer::RPCConfig> get_rpc_methods() const override;
const Variant get_rpc_config() const override;
#ifdef TOOLS_ENABLED
bool is_placeholder_fallback_enabled() const override { return placeholder_fallback_enabled; }
@ -311,7 +311,7 @@ public:
void refcount_incremented() override;
bool refcount_decremented() override;
const Vector<Multiplayer::RPCConfig> get_rpc_methods() const override;
const Variant get_rpc_config() const override;
void notification(int p_notification) override;
void _call_notification(int p_notification);

View File

@ -5,8 +5,8 @@ namespace Godot
/// <summary>
/// Attribute that changes the RPC mode for the annotated <c>method</c> to the given <see cref="Mode"/>,
/// optionally specifying the <see cref="TransferMode"/> and <see cref="TransferChannel"/> (on supported peers).
/// See <see cref="RPCMode"/> and <see cref="TransferMode"/>. By default, methods are not exposed to networking
/// (and RPCs).
/// See <see cref="MultiplayerAPI.RPCMode"/> and <see cref="MultiplayerPeer.TransferModeEnum"/>.
/// By default, methods are not exposed to networking (and RPCs).
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class RPCAttribute : Attribute
@ -14,7 +14,7 @@ namespace Godot
/// <summary>
/// RPC mode for the annotated method.
/// </summary>
public RPCMode Mode { get; } = RPCMode.Disabled;
public MultiplayerAPI.RPCMode Mode { get; } = MultiplayerAPI.RPCMode.Disabled;
/// <summary>
/// If the method will also be called locally; otherwise, it is only called remotely.
@ -24,7 +24,7 @@ namespace Godot
/// <summary>
/// Transfer mode for the annotated method.
/// </summary>
public TransferMode TransferMode { get; set; } = TransferMode.Reliable;
public MultiplayerPeer.TransferModeEnum TransferMode { get; set; } = MultiplayerPeer.TransferModeEnum.Reliable;
/// <summary>
/// Transfer channel for the annotated mode.
@ -35,7 +35,7 @@ namespace Godot
/// Constructs a <see cref="RPCAttribute"/> instance.
/// </summary>
/// <param name="mode">The RPC mode to use.</param>
public RPCAttribute(RPCMode mode = RPCMode.Authority)
public RPCAttribute(MultiplayerAPI.RPCMode mode = MultiplayerAPI.RPCMode.Authority)
{
Mode = mode;
}

14
modules/multiplayer/SCsub Normal file
View File

@ -0,0 +1,14 @@
#!/usr/bin/env python
Import("env")
Import("env_modules")
env_mp = env_modules.Clone()
module_obj = []
env_mp.add_source_files(module_obj, "*.cpp")
if env["tools"]:
env_mp.add_source_files(module_obj, "editor/*.cpp")
env.modules_sources += module_obj

View File

@ -0,0 +1,19 @@
def can_build(env, platform):
return True
def configure(env):
pass
def get_doc_classes():
return [
"SceneReplicationConfig",
"SceneMultiplayer",
"MultiplayerSpawner",
"MultiplayerSynchronizer",
]
def get_doc_path():
return "doc_classes"

View File

@ -1,8 +1,9 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="MultiplayerSpawner" inherits="Node" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<class name="MultiplayerSpawner" inherits="Node" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
</brief_description>
<description>
This node uses [method MultiplayerAPI.object_configuration_add] to notify spawns passing the spawned node as the [code]object[/code] and itself as the [code]configuration[/code], and [method MultiplayerAPI.object_configuration_remove] to notify despawns in a similar way.
</description>
<tutorials>
</tutorials>

View File

@ -1,8 +1,9 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="MultiplayerSynchronizer" inherits="Node" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<class name="MultiplayerSynchronizer" inherits="Node" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
</brief_description>
<description>
The [MultiplayerSynchronizer] uses [method MultiplayerAPI.object_configuration_add] to notify synchronization start passing the [Node] at [member root_path] as the [code]object[/code] and itself as the [code]configuration[/code], and uses [method MultiplayerAPI.object_configuration_remove] to notify synchronization end in a similar way.
</description>
<tutorials>
</tutorials>

View File

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="SceneMultiplayer" inherits="MultiplayerAPI" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
High-level multiplayer API implementation.
</brief_description>
<description>
This class is the default implementation of [MultiplayerAPI], used to provide multiplayer functionalities in Godot Engine.
This implementation supports RPCs via [method Node.rpc] and [method Node.rpc_id] and requires [method MultiplayerAPI.rpc] to be passed a [Node] (it will fail for other object types).
This implementation additionally provide [SceneTree] replication via the [MultiplayerSpawner] and [MultiplayerSynchronizer] nodes, and the [SceneReplicationConfig] resource.
[b]Note:[/b] The high-level multiplayer API protocol is an implementation detail and isn't meant to be used by non-Godot servers. It may change without notice.
[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>
</tutorials>
<methods>
<method name="clear">
<return type="void" />
<description>
Clears the current SceneMultiplayer network state (you shouldn't call this unless you know what you are doing).
</description>
</method>
<method name="send_bytes">
<return type="int" enum="Error" />
<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>
</method>
</methods>
<members>
<member name="allow_object_decoding" type="bool" setter="set_allow_object_decoding" getter="is_object_decoding_allowed" default="false">
If [code]true[/code], the MultiplayerAPI will allow encoding and decoding of object during RPCs.
[b]Warning:[/b] Deserialized objects can contain code which gets executed. Do not use this option if the serialized object comes from untrusted sources to avoid potential security threat such as remote code execution.
</member>
<member name="refuse_new_connections" type="bool" setter="set_refuse_new_connections" getter="is_refusing_new_connections" default="false">
If [code]true[/code], the MultiplayerAPI's [member MultiplayerAPI.multiplayer_peer] refuses new incoming connections.
</member>
<member name="root_path" type="NodePath" setter="set_root_path" getter="get_root_path" default="NodePath(&quot;&quot;)">
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>
</members>
<signals>
<signal name="peer_packet">
<argument index="0" name="id" type="int" />
<argument index="1" name="packet" type="PackedByteArray" />
<description>
Emitted when this MultiplayerAPI's [member MultiplayerAPI.multiplayer_peer] receives a [code]packet[/code] with custom data (see [method send_bytes]). ID is the peer ID of the peer that sent the packet.
</description>
</signal>
</signals>
</class>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="SceneReplicationConfig" inherits="Resource" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<class name="SceneReplicationConfig" inherits="Resource" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
</brief_description>
<description>

View File

@ -33,9 +33,9 @@
#include "editor/editor_node.h"
#include "editor/editor_scale.h"
#include "editor/inspector_dock.h"
#include "modules/multiplayer/multiplayer_synchronizer.h"
#include "scene/gui/dialogs.h"
#include "scene/gui/tree.h"
#include "scene/multiplayer/multiplayer_synchronizer.h"
void ReplicationEditor::_pick_node_filter_text_changed(const String &p_newtext) {
TreeItem *root_item = pick_node->get_scene_tree()->get_scene_tree()->get_root();

View File

@ -32,12 +32,13 @@
#define REPLICATION_EDITOR_PLUGIN_H
#include "editor/editor_plugin.h"
#include "scene/resources/scene_replication_config.h"
#include "editor/editor_spin_slider.h"
#include "editor/property_editor.h"
#include "editor/property_selector.h"
#include "../scene_replication_config.h"
class ConfirmationDialog;
class MultiplayerSynchronizer;
class Tree;
@ -131,5 +132,17 @@ public:
ReplicationEditorPlugin();
~ReplicationEditorPlugin();
};
#else
class ReplicationEditorPlugin : public EditorPlugin {
GDCLASS(ReplicationEditorPlugin, EditorPlugin);
public:
virtual void edit(Object *p_object) override {}
virtual bool handles(Object *p_object) const override { return false; }
virtual void make_visible(bool p_visible) override {}
ReplicationEditorPlugin() {}
~ReplicationEditorPlugin() {}
};
#endif // REPLICATION_EDITOR_PLUGIN_H

View File

@ -31,7 +31,7 @@
#include "multiplayer_spawner.h"
#include "core/io/marshalls.h"
#include "core/multiplayer/multiplayer_api.h"
#include "scene/main/multiplayer_api.h"
#include "scene/main/window.h"
#include "scene/scene_string_names.h"
@ -190,7 +190,7 @@ void MultiplayerSpawner::_notification(int p_what) {
if (node->is_connected(SceneStringNames::get_singleton()->ready, callable_mp(this, &MultiplayerSpawner::_node_ready))) {
node->disconnect(SceneStringNames::get_singleton()->ready, callable_mp(this, &MultiplayerSpawner::_node_ready));
}
get_multiplayer()->despawn(node, this);
get_multiplayer()->object_configuration_remove(node, this);
}
tracked_nodes.clear();
} break;
@ -236,7 +236,7 @@ void MultiplayerSpawner::_track(Node *p_node, const Variant &p_argument, int p_s
}
void MultiplayerSpawner::_node_ready(ObjectID p_id) {
get_multiplayer()->spawn(ObjectDB::get_instance(p_id), this);
get_multiplayer()->object_configuration_add(ObjectDB::get_instance(p_id), this);
}
void MultiplayerSpawner::_node_exit(ObjectID p_id) {
@ -244,7 +244,7 @@ void MultiplayerSpawner::_node_exit(ObjectID p_id) {
ERR_FAIL_COND(!node);
if (tracked_nodes.has(p_id)) {
tracked_nodes.erase(p_id);
get_multiplayer()->despawn(node, this);
get_multiplayer()->object_configuration_remove(node, this);
}
}

View File

@ -36,7 +36,8 @@
#include "core/templates/local_vector.h"
#include "core/variant/typed_array.h"
#include "scene/resources/packed_scene.h"
#include "scene/resources/scene_replication_config.h"
#include "scene_replication_config.h"
class MultiplayerSpawner : public Node {
GDCLASS(MultiplayerSpawner, Node);

View File

@ -31,7 +31,7 @@
#include "multiplayer_synchronizer.h"
#include "core/config/engine.h"
#include "core/multiplayer/multiplayer_api.h"
#include "scene/main/multiplayer_api.h"
Object *MultiplayerSynchronizer::_get_prop_target(Object *p_obj, const NodePath &p_path) {
if (p_path.get_name_count() == 0) {
@ -50,7 +50,7 @@ void MultiplayerSynchronizer::_stop() {
#endif
Node *node = is_inside_tree() ? get_node_or_null(root_path) : nullptr;
if (node) {
get_multiplayer()->replication_stop(node, this);
get_multiplayer()->object_configuration_remove(node, this);
}
}
@ -62,7 +62,7 @@ void MultiplayerSynchronizer::_start() {
#endif
Node *node = is_inside_tree() ? get_node_or_null(root_path) : nullptr;
if (node) {
get_multiplayer()->replication_start(node, this);
get_multiplayer()->object_configuration_add(node, this);
_update_process();
}
}
@ -293,9 +293,9 @@ void MultiplayerSynchronizer::set_multiplayer_authority(int p_peer_id, bool p_re
Node::set_multiplayer_authority(p_peer_id, p_recursive);
return;
}
get_multiplayer()->replication_stop(node, this);
get_multiplayer()->object_configuration_remove(node, this);
Node::set_multiplayer_authority(p_peer_id, p_recursive);
get_multiplayer()->replication_start(node, this);
get_multiplayer()->object_configuration_add(node, this);
}
MultiplayerSynchronizer::MultiplayerSynchronizer() {

View File

@ -33,7 +33,7 @@
#include "scene/main/node.h"
#include "scene/resources/scene_replication_config.h"
#include "scene_replication_config.h"
class MultiplayerSynchronizer : public Node {
GDCLASS(MultiplayerSynchronizer, Node);

View File

@ -1,5 +1,5 @@
/*************************************************************************/
/* multiplayer.h */
/* register_types.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@ -28,53 +28,33 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#ifndef MULTIPLAYER_H
#define MULTIPLAYER_H
#include "register_types.h"
#include "core/variant/binder_common.h"
#include "multiplayer_spawner.h"
#include "multiplayer_synchronizer.h"
#include "scene_multiplayer.h"
#include "scene_replication_interface.h"
#include "scene_rpc_interface.h"
#include "core/string/string_name.h"
#ifdef TOOLS_ENABLED
#include "editor/editor_plugin.h"
#include "editor/replication_editor_plugin.h"
#endif
namespace Multiplayer {
enum TransferMode {
TRANSFER_MODE_UNRELIABLE,
TRANSFER_MODE_UNRELIABLE_ORDERED,
TRANSFER_MODE_RELIABLE
};
enum RPCMode {
RPC_MODE_DISABLED, // No rpc for this method, calls to this will be blocked (default)
RPC_MODE_ANY_PEER, // Any peer can call this RPC
RPC_MODE_AUTHORITY, // Only the node's multiplayer authority (server by default) can call this RPC
};
struct RPCConfig {
StringName name;
RPCMode rpc_mode = RPC_MODE_DISABLED;
bool call_local = false;
TransferMode transfer_mode = TRANSFER_MODE_RELIABLE;
int channel = 0;
bool operator==(RPCConfig const &p_other) const {
return name == p_other.name;
void initialize_multiplayer_module(ModuleInitializationLevel p_level) {
if (p_level == MODULE_INITIALIZATION_LEVEL_SCENE) {
GDREGISTER_CLASS(SceneReplicationConfig);
GDREGISTER_CLASS(MultiplayerSpawner);
GDREGISTER_CLASS(MultiplayerSynchronizer);
GDREGISTER_CLASS(SceneMultiplayer);
MultiplayerAPI::set_default_interface("SceneMultiplayer");
}
};
struct SortRPCConfig {
StringName::AlphCompare compare;
bool operator()(const RPCConfig &p_a, const RPCConfig &p_b) const {
return compare(p_a.name, p_b.name);
#ifdef TOOLS_ENABLED
if (p_level == MODULE_INITIALIZATION_LEVEL_EDITOR) {
EditorPlugins::add_by_type<ReplicationEditorPlugin>();
}
};
#endif
}
}; // namespace Multiplayer
// This is needed for proper docs generation (i.e. not "Multiplayer."-prefixed).
typedef Multiplayer::RPCMode RPCMode;
typedef Multiplayer::TransferMode TransferMode;
VARIANT_ENUM_CAST(RPCMode);
VARIANT_ENUM_CAST(TransferMode);
#endif // MULTIPLAYER_H
void uninitialize_multiplayer_module(ModuleInitializationLevel p_level) {
}

View File

@ -0,0 +1,39 @@
/*************************************************************************/
/* register_types.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#ifndef MULTIPLAYER_REGISTER_TYPES_H
#define MULTIPLAYER_REGISTER_TYPES_H
#include "modules/register_module_types.h"
void initialize_multiplayer_module(ModuleInitializationLevel p_level);
void uninitialize_multiplayer_module(ModuleInitializationLevel p_level);
#endif // MULTIPLAYER_REGISTER_TYPES_H

View File

@ -34,13 +34,7 @@
#include "scene/main/node.h"
#include "scene/main/window.h"
MultiplayerCacheInterface *SceneCacheInterface::_create(MultiplayerAPI *p_multiplayer) {
return memnew(SceneCacheInterface(p_multiplayer));
}
void SceneCacheInterface::make_default() {
MultiplayerAPI::create_default_cache_interface = _create;
}
#include "scene_multiplayer.h"
void SceneCacheInterface::on_peer_change(int p_id, bool p_connected) {
if (p_connected) {
@ -98,7 +92,7 @@ void SceneCacheInterface::process_simplify_path(int p_from, const uint8_t *p_pac
Vector<uint8_t> packet;
packet.resize(1 + 1 + len);
packet.write[0] = MultiplayerAPI::NETWORK_COMMAND_CONFIRM_PATH;
packet.write[0] = SceneMultiplayer::NETWORK_COMMAND_CONFIRM_PATH;
packet.write[1] = valid_rpc_checksum;
encode_cstring(pname.get_data(), &packet.write[2]);
@ -110,7 +104,7 @@ void SceneCacheInterface::process_simplify_path(int p_from, const uint8_t *p_pac
#endif
multiplayer_peer->set_transfer_channel(0);
multiplayer_peer->set_transfer_mode(Multiplayer::TRANSFER_MODE_RELIABLE);
multiplayer_peer->set_transfer_mode(MultiplayerPeer::TRANSFER_MODE_RELIABLE);
multiplayer_peer->set_target_peer(p_from);
multiplayer_peer->put_packet(packet.ptr(), packet.size());
}
@ -150,7 +144,7 @@ Error SceneCacheInterface::_send_confirm_path(Node *p_node, NodePath p_path, Pat
packet.resize(1 + 4 + path_len + methods_md5_len);
int ofs = 0;
packet.write[ofs] = MultiplayerAPI::NETWORK_COMMAND_SIMPLIFY_PATH;
packet.write[ofs] = SceneMultiplayer::NETWORK_COMMAND_SIMPLIFY_PATH;
ofs += 1;
ofs += encode_cstring(methods_md5.utf8().get_data(), &packet.write[ofs]);
@ -170,7 +164,7 @@ Error SceneCacheInterface::_send_confirm_path(Node *p_node, NodePath p_path, Pat
for (int peer_id : p_peers) {
multiplayer_peer->set_target_peer(peer_id);
multiplayer_peer->set_transfer_channel(0);
multiplayer_peer->set_transfer_mode(Multiplayer::TRANSFER_MODE_RELIABLE);
multiplayer_peer->set_transfer_mode(MultiplayerPeer::TRANSFER_MODE_RELIABLE);
err = multiplayer_peer->put_packet(packet.ptr(), packet.size());
ERR_FAIL_COND_V(err != OK, err);
// Insert into confirmed, but as false since it was not confirmed.

View File

@ -31,13 +31,15 @@
#ifndef SCENE_CACHE_INTERFACE_H
#define SCENE_CACHE_INTERFACE_H
#include "core/multiplayer/multiplayer_api.h"
#include "scene/main/multiplayer_api.h"
class SceneCacheInterface : public MultiplayerCacheInterface {
GDCLASS(SceneCacheInterface, MultiplayerCacheInterface);
class SceneMultiplayer;
class SceneCacheInterface : public RefCounted {
GDCLASS(SceneCacheInterface, RefCounted);
private:
MultiplayerAPI *multiplayer = nullptr;
SceneMultiplayer *multiplayer = nullptr;
//path sent caches
struct PathSentCache {
@ -61,23 +63,20 @@ private:
protected:
Error _send_confirm_path(Node *p_node, NodePath p_path, PathSentCache *psc, const List<int> &p_peers);
static MultiplayerCacheInterface *_create(MultiplayerAPI *p_multiplayer);
public:
static void make_default();
virtual void clear() override;
virtual void on_peer_change(int p_id, bool p_connected) override;
virtual void process_simplify_path(int p_from, const uint8_t *p_packet, int p_packet_len) override;
virtual void process_confirm_path(int p_from, const uint8_t *p_packet, int p_packet_len) override;
void clear();
void on_peer_change(int p_id, bool p_connected);
void process_simplify_path(int p_from, const uint8_t *p_packet, int p_packet_len);
void process_confirm_path(int p_from, const uint8_t *p_packet, int p_packet_len);
// Returns true if all peers have cached path.
virtual bool send_object_cache(Object *p_obj, int p_target, int &p_id) override;
virtual int make_object_cache(Object *p_obj) override;
virtual Object *get_cached_object(int p_from, uint32_t p_cache_id) override;
virtual bool is_cache_confirmed(NodePath p_path, int p_peer) override;
bool send_object_cache(Object *p_obj, int p_target, int &p_id);
int make_object_cache(Object *p_obj);
Object *get_cached_object(int p_from, uint32_t p_cache_id);
bool is_cache_confirmed(NodePath p_path, int p_peer);
SceneCacheInterface(MultiplayerAPI *p_multiplayer) { multiplayer = p_multiplayer; }
SceneCacheInterface(SceneMultiplayer *p_multiplayer) { multiplayer = p_multiplayer; }
};
#endif // SCENE_CACHE_INTERFACE_H

View File

@ -0,0 +1,332 @@
/*************************************************************************/
/* scene_multiplayer.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#include "scene_multiplayer.h"
#include "core/debugger/engine_debugger.h"
#include "core/io/marshalls.h"
#include <stdint.h>
#ifdef DEBUG_ENABLED
#include "core/os/os.h"
#endif
#ifdef DEBUG_ENABLED
void SceneMultiplayer::profile_bandwidth(const String &p_inout, int p_size) {
if (EngineDebugger::is_profiling("multiplayer")) {
Array values;
values.push_back(p_inout);
values.push_back(OS::get_singleton()->get_ticks_msec());
values.push_back(p_size);
EngineDebugger::profiler_add_frame_data("multiplayer", values);
}
}
#endif
Error SceneMultiplayer::poll() {
if (!multiplayer_peer.is_valid() || multiplayer_peer->get_connection_status() == MultiplayerPeer::CONNECTION_DISCONNECTED) {
return ERR_UNCONFIGURED;
}
multiplayer_peer->poll();
if (!multiplayer_peer.is_valid()) { // It's possible that polling might have resulted in a disconnection, so check here.
return OK;
}
while (multiplayer_peer->get_available_packet_count()) {
int sender = multiplayer_peer->get_packet_peer();
const uint8_t *packet;
int len;
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 (!multiplayer_peer.is_valid()) {
return OK; // It's also possible that a packet or RPC caused a disconnection, so also check here.
}
}
replicator->on_network_process();
return OK;
}
void SceneMultiplayer::clear() {
connected_peers.clear();
packet_cache.clear();
cache->clear();
}
void SceneMultiplayer::set_root_path(const NodePath &p_path) {
ERR_FAIL_COND_MSG(!p_path.is_absolute() && !p_path.is_empty(), "SceneMultiplayer root path must be absolute.");
root_path = p_path;
}
NodePath SceneMultiplayer::get_root_path() const {
return root_path;
}
void SceneMultiplayer::set_multiplayer_peer(const Ref<MultiplayerPeer> &p_peer) {
if (p_peer == multiplayer_peer) {
return; // Nothing to do
}
ERR_FAIL_COND_MSG(p_peer.is_valid() && p_peer->get_connection_status() == MultiplayerPeer::CONNECTION_DISCONNECTED,
"Supplied MultiplayerPeer must be connecting or connected.");
if (multiplayer_peer.is_valid()) {
multiplayer_peer->disconnect("peer_connected", callable_mp(this, &SceneMultiplayer::_add_peer));
multiplayer_peer->disconnect("peer_disconnected", callable_mp(this, &SceneMultiplayer::_del_peer));
multiplayer_peer->disconnect("connection_succeeded", callable_mp(this, &SceneMultiplayer::_connected_to_server));
multiplayer_peer->disconnect("connection_failed", callable_mp(this, &SceneMultiplayer::_connection_failed));
multiplayer_peer->disconnect("server_disconnected", callable_mp(this, &SceneMultiplayer::_server_disconnected));
clear();
}
multiplayer_peer = p_peer;
if (multiplayer_peer.is_valid()) {
multiplayer_peer->connect("peer_connected", callable_mp(this, &SceneMultiplayer::_add_peer));
multiplayer_peer->connect("peer_disconnected", callable_mp(this, &SceneMultiplayer::_del_peer));
multiplayer_peer->connect("connection_succeeded", callable_mp(this, &SceneMultiplayer::_connected_to_server));
multiplayer_peer->connect("connection_failed", callable_mp(this, &SceneMultiplayer::_connection_failed));
multiplayer_peer->connect("server_disconnected", callable_mp(this, &SceneMultiplayer::_server_disconnected));
}
replicator->on_reset();
}
Ref<MultiplayerPeer> SceneMultiplayer::get_multiplayer_peer() {
return multiplayer_peer;
}
void SceneMultiplayer::_process_packet(int p_from, const uint8_t *p_packet, int p_packet_len) {
ERR_FAIL_COND_MSG(root_path.is_empty(), "Multiplayer root was not initialized. If you are using custom multiplayer, remember to set the root path via SceneMultiplayer.set_root_path before using it.");
ERR_FAIL_COND_MSG(p_packet_len < 1, "Invalid packet received. Size too small.");
#ifdef DEBUG_ENABLED
profile_bandwidth("in", p_packet_len);
#endif
// Extract the `packet_type` from the LSB three bits:
uint8_t packet_type = p_packet[0] & CMD_MASK;
switch (packet_type) {
case NETWORK_COMMAND_SIMPLIFY_PATH: {
cache->process_simplify_path(p_from, p_packet, p_packet_len);
} break;
case NETWORK_COMMAND_CONFIRM_PATH: {
cache->process_confirm_path(p_from, p_packet, p_packet_len);
} break;
case NETWORK_COMMAND_REMOTE_CALL: {
rpc->process_rpc(p_from, p_packet, p_packet_len);
} break;
case NETWORK_COMMAND_RAW: {
_process_raw(p_from, p_packet, p_packet_len);
} break;
case NETWORK_COMMAND_SPAWN: {
replicator->on_spawn_receive(p_from, p_packet, p_packet_len);
} break;
case NETWORK_COMMAND_DESPAWN: {
replicator->on_despawn_receive(p_from, p_packet, p_packet_len);
} break;
case NETWORK_COMMAND_SYNC: {
replicator->on_sync_receive(p_from, p_packet, p_packet_len);
} break;
}
}
void SceneMultiplayer::_add_peer(int p_id) {
connected_peers.insert(p_id);
cache->on_peer_change(p_id, true);
replicator->on_peer_change(p_id, true);
emit_signal(SNAME("peer_connected"), p_id);
}
void SceneMultiplayer::_del_peer(int p_id) {
replicator->on_peer_change(p_id, false);
cache->on_peer_change(p_id, false);
connected_peers.erase(p_id);
emit_signal(SNAME("peer_disconnected"), p_id);
}
void SceneMultiplayer::_connected_to_server() {
emit_signal(SNAME("connected_to_server"));
}
void SceneMultiplayer::_connection_failed() {
emit_signal(SNAME("connection_failed"));
}
void SceneMultiplayer::_server_disconnected() {
replicator->on_reset();
emit_signal(SNAME("server_disconnected"));
}
Error SceneMultiplayer::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(!multiplayer_peer.is_valid(), ERR_UNCONFIGURED, "Trying to send a raw packet while no multiplayer peer is active.");
ERR_FAIL_COND_V_MSG(multiplayer_peer->get_connection_status() != MultiplayerPeer::CONNECTION_CONNECTED, ERR_UNCONFIGURED, "Trying to send a raw packet via a multiplayer peer which is not connected.");
if (packet_cache.size() < p_data.size() + 1) {
packet_cache.resize(p_data.size() + 1);
}
const uint8_t *r = p_data.ptr();
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);
}
void SceneMultiplayer::_process_raw(int p_from, const uint8_t *p_packet, int p_packet_len) {
ERR_FAIL_COND_MSG(p_packet_len < 2, "Invalid packet received. Size too small.");
Vector<uint8_t> out;
int len = p_packet_len - 1;
out.resize(len);
{
uint8_t *w = out.ptrw();
memcpy(&w[0], &p_packet[1], len);
}
emit_signal(SNAME("peer_packet"), p_from, out);
}
int SceneMultiplayer::get_unique_id() {
ERR_FAIL_COND_V_MSG(!multiplayer_peer.is_valid(), 0, "No multiplayer peer is assigned. Unable to get unique ID.");
return multiplayer_peer->get_unique_id();
}
void SceneMultiplayer::set_refuse_new_connections(bool p_refuse) {
ERR_FAIL_COND_MSG(!multiplayer_peer.is_valid(), "No multiplayer peer is assigned. Unable to set 'refuse_new_connections'.");
multiplayer_peer->set_refuse_new_connections(p_refuse);
}
bool SceneMultiplayer::is_refusing_new_connections() const {
ERR_FAIL_COND_V_MSG(!multiplayer_peer.is_valid(), false, "No multiplayer peer is assigned. Unable to get 'refuse_new_connections'.");
return multiplayer_peer->is_refusing_new_connections();
}
Vector<int> SceneMultiplayer::get_peer_ids() {
ERR_FAIL_COND_V_MSG(!multiplayer_peer.is_valid(), Vector<int>(), "No multiplayer peer is assigned. Assume no peers are connected.");
Vector<int> ret;
for (const int &E : connected_peers) {
ret.push_back(E);
}
return ret;
}
void SceneMultiplayer::set_allow_object_decoding(bool p_enable) {
allow_object_decoding = p_enable;
}
bool SceneMultiplayer::is_object_decoding_allowed() const {
return allow_object_decoding;
}
String SceneMultiplayer::get_rpc_md5(const Object *p_obj) {
return rpc->get_rpc_md5(p_obj);
}
Error SceneMultiplayer::rpcp(Object *p_obj, int p_peer_id, const StringName &p_method, const Variant **p_arg, int p_argcount) {
return rpc->rpcp(p_obj, p_peer_id, p_method, p_arg, p_argcount);
}
Error SceneMultiplayer::object_configuration_add(Object *p_obj, Variant p_config) {
if (p_obj == nullptr && p_config.get_type() == Variant::NODE_PATH) {
set_root_path(p_config);
return OK;
}
MultiplayerSpawner *spawner = Object::cast_to<MultiplayerSpawner>(p_config.get_validated_object());
MultiplayerSynchronizer *sync = Object::cast_to<MultiplayerSynchronizer>(p_config.get_validated_object());
if (spawner) {
return replicator->on_spawn(p_obj, p_config);
} else if (sync) {
return replicator->on_replication_start(p_obj, p_config);
}
return ERR_INVALID_PARAMETER;
}
Error SceneMultiplayer::object_configuration_remove(Object *p_obj, Variant p_config) {
if (p_obj == nullptr && p_config.get_type() == Variant::NODE_PATH) {
ERR_FAIL_COND_V(root_path != p_config.operator NodePath(), ERR_INVALID_PARAMETER);
set_root_path(NodePath());
return OK;
}
MultiplayerSpawner *spawner = Object::cast_to<MultiplayerSpawner>(p_config.get_validated_object());
MultiplayerSynchronizer *sync = Object::cast_to<MultiplayerSynchronizer>(p_config.get_validated_object());
if (spawner) {
return replicator->on_despawn(p_obj, p_config);
}
if (sync) {
return replicator->on_replication_stop(p_obj, p_config);
}
return ERR_INVALID_PARAMETER;
}
void SceneMultiplayer::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_root_path", "path"), &SceneMultiplayer::set_root_path);
ClassDB::bind_method(D_METHOD("get_root_path"), &SceneMultiplayer::get_root_path);
ClassDB::bind_method(D_METHOD("clear"), &SceneMultiplayer::clear);
ClassDB::bind_method(D_METHOD("set_refuse_new_connections", "refuse"), &SceneMultiplayer::set_refuse_new_connections);
ClassDB::bind_method(D_METHOD("is_refusing_new_connections"), &SceneMultiplayer::is_refusing_new_connections);
ClassDB::bind_method(D_METHOD("set_allow_object_decoding", "enable"), &SceneMultiplayer::set_allow_object_decoding);
ClassDB::bind_method(D_METHOD("is_object_decoding_allowed"), &SceneMultiplayer::is_object_decoding_allowed);
ClassDB::bind_method(D_METHOD("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_DEFAULT("refuse_new_connections", false);
ADD_SIGNAL(MethodInfo("peer_packet", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::PACKED_BYTE_ARRAY, "packet")));
}
SceneMultiplayer::SceneMultiplayer() {
replicator = Ref<SceneReplicationInterface>(memnew(SceneReplicationInterface(this)));
rpc = Ref<SceneRPCInterface>(memnew(SceneRPCInterface(this)));
cache = Ref<SceneCacheInterface>(memnew(SceneCacheInterface(this)));
}
SceneMultiplayer::~SceneMultiplayer() {
clear();
}

View File

@ -0,0 +1,136 @@
/*************************************************************************/
/* scene_multiplayer.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#ifndef SCENE_MULTIPLAYER_H
#define SCENE_MULTIPLAYER_H
#include "scene/main/multiplayer_api.h"
#include "scene_cache_interface.h"
#include "scene_replication_interface.h"
#include "scene_rpc_interface.h"
class SceneMultiplayer : public MultiplayerAPI {
GDCLASS(SceneMultiplayer, MultiplayerAPI);
public:
enum NetworkCommands {
NETWORK_COMMAND_REMOTE_CALL = 0,
NETWORK_COMMAND_SIMPLIFY_PATH,
NETWORK_COMMAND_CONFIRM_PATH,
NETWORK_COMMAND_RAW,
NETWORK_COMMAND_SPAWN,
NETWORK_COMMAND_DESPAWN,
NETWORK_COMMAND_SYNC,
};
// For each command, the 4 MSB can contain custom flags, as defined by subsystems.
enum {
CMD_FLAG_0_SHIFT = 4,
CMD_FLAG_1_SHIFT = 5,
CMD_FLAG_2_SHIFT = 6,
CMD_FLAG_3_SHIFT = 7,
};
// This is the mask that will be used to extract the command.
enum {
CMD_MASK = 7, // 0x7 -> 0b00001111
};
private:
Ref<MultiplayerPeer> multiplayer_peer;
HashSet<int> connected_peers;
int remote_sender_id = 0;
int remote_sender_override = 0;
Vector<uint8_t> packet_cache;
NodePath root_path;
bool allow_object_decoding = false;
Ref<SceneCacheInterface> cache;
Ref<SceneReplicationInterface> replicator;
Ref<SceneRPCInterface> rpc;
protected:
static void _bind_methods();
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 _add_peer(int p_id);
void _del_peer(int p_id);
void _connected_to_server();
void _connection_failed();
void _server_disconnected();
public:
virtual void set_multiplayer_peer(const Ref<MultiplayerPeer> &p_peer) override;
virtual Ref<MultiplayerPeer> get_multiplayer_peer() override;
virtual Error poll() override;
virtual int get_unique_id() override;
virtual Vector<int> get_peer_ids() override;
virtual int get_remote_sender_id() override { return remote_sender_override ? remote_sender_override : remote_sender_id; }
virtual Error rpcp(Object *p_obj, int p_peer_id, const StringName &p_method, const Variant **p_arg, int p_argcount) override;
virtual Error object_configuration_add(Object *p_obj, Variant p_config) override;
virtual Error object_configuration_remove(Object *p_obj, Variant p_config) override;
void clear();
// Usually from object_configuration_add/remove
void set_root_path(const NodePath &p_path);
NodePath get_root_path() const;
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);
const HashSet<int> get_connected_peers() const { return connected_peers; }
void set_remote_sender_override(int p_id) { remote_sender_override = p_id; }
void set_refuse_new_connections(bool p_refuse);
bool is_refusing_new_connections() const;
void set_allow_object_decoding(bool p_enable);
bool is_object_decoding_allowed() const;
Ref<SceneCacheInterface> get_path_cache() { return cache; }
#ifdef DEBUG_ENABLED
void profile_bandwidth(const String &p_inout, int p_size);
#endif
SceneMultiplayer();
~SceneMultiplayer();
};
#endif // SCENE_MULTIPLAYER_H

View File

@ -30,7 +30,7 @@
#include "scene_replication_config.h"
#include "core/multiplayer/multiplayer_api.h"
#include "scene/main/multiplayer_api.h"
#include "scene/main/node.h"
bool SceneReplicationConfig::_set(const StringName &p_name, const Variant &p_value) {

View File

@ -32,21 +32,15 @@
#include "core/io/marshalls.h"
#include "scene/main/node.h"
#include "scene/multiplayer/multiplayer_spawner.h"
#include "scene/multiplayer/multiplayer_synchronizer.h"
#include "multiplayer_spawner.h"
#include "multiplayer_synchronizer.h"
#include "scene_multiplayer.h"
#define MAKE_ROOM(m_amount) \
if (packet_cache.size() < m_amount) \
packet_cache.resize(m_amount);
MultiplayerReplicationInterface *SceneReplicationInterface::_create(MultiplayerAPI *p_multiplayer) {
return memnew(SceneReplicationInterface(p_multiplayer));
}
void SceneReplicationInterface::make_default() {
MultiplayerAPI::create_default_replication_interface = _create;
}
void SceneReplicationInterface::_free_remotes(int p_id) {
const HashMap<uint32_t, ObjectID> remotes = rep_state->peer_get_remotes(p_id);
for (const KeyValue<uint32_t, ObjectID> &E : remotes) {
@ -239,7 +233,7 @@ Error SceneReplicationInterface::_update_spawn_visibility(int p_peer, const Obje
_make_spawn_packet(node, len);
for (int pid : to_spawn) {
int path_id;
multiplayer->send_object_cache(spawner, pid, path_id);
multiplayer->get_path_cache()->send_object_cache(spawner, pid, path_id);
_send_raw(packet_cache.ptr(), len, pid, true);
rep_state->peer_add_spawn(pid, p_oid);
}
@ -267,7 +261,7 @@ Error SceneReplicationInterface::_send_raw(const uint8_t *p_buffer, int p_size,
Ref<MultiplayerPeer> peer = multiplayer->get_multiplayer_peer();
peer->set_target_peer(p_peer);
peer->set_transfer_channel(0);
peer->set_transfer_mode(p_reliable ? Multiplayer::TRANSFER_MODE_RELIABLE : Multiplayer::TRANSFER_MODE_UNRELIABLE);
peer->set_transfer_mode(p_reliable ? MultiplayerPeer::TRANSFER_MODE_RELIABLE : MultiplayerPeer::TRANSFER_MODE_UNRELIABLE);
return peer->put_packet(p_buffer, p_size);
}
@ -306,12 +300,12 @@ Error SceneReplicationInterface::_make_spawn_packet(Node *p_node, int &r_len) {
}
// Encode scene ID, path ID, net ID, node name.
int path_id = multiplayer->make_object_cache(spawner);
int path_id = multiplayer->get_path_cache()->make_object_cache(spawner);
CharString cname = p_node->get_name().operator String().utf8();
int nlen = encode_cstring(cname.get_data(), nullptr);
MAKE_ROOM(1 + 1 + 4 + 4 + 4 + nlen + (is_custom ? 4 + spawn_arg_size : 0) + state_size);
uint8_t *ptr = packet_cache.ptrw();
ptr[0] = (uint8_t)MultiplayerAPI::NETWORK_COMMAND_SPAWN;
ptr[0] = (uint8_t)SceneMultiplayer::NETWORK_COMMAND_SPAWN;
ptr[1] = scene_id;
int ofs = 2;
ofs += encode_uint32(path_id, &ptr[ofs]);
@ -339,7 +333,7 @@ Error SceneReplicationInterface::_make_despawn_packet(Node *p_node, int &r_len)
const ObjectID oid = p_node->get_instance_id();
MAKE_ROOM(5);
uint8_t *ptr = packet_cache.ptrw();
ptr[0] = (uint8_t)MultiplayerAPI::NETWORK_COMMAND_DESPAWN;
ptr[0] = (uint8_t)SceneMultiplayer::NETWORK_COMMAND_DESPAWN;
int ofs = 1;
uint32_t nid = rep_state->get_net_id(oid);
ofs += encode_uint32(nid, &ptr[ofs]);
@ -354,7 +348,7 @@ Error SceneReplicationInterface::on_spawn_receive(int p_from, const uint8_t *p_b
ofs += 1;
uint32_t node_target = decode_uint32(&p_buffer[ofs]);
ofs += 4;
MultiplayerSpawner *spawner = Object::cast_to<MultiplayerSpawner>(multiplayer->get_cached_object(p_from, node_target));
MultiplayerSpawner *spawner = Object::cast_to<MultiplayerSpawner>(multiplayer->get_path_cache()->get_cached_object(p_from, node_target));
ERR_FAIL_COND_V(!spawner, ERR_DOES_NOT_EXIST);
ERR_FAIL_COND_V(p_from != spawner->get_multiplayer_authority(), ERR_UNAUTHORIZED);
@ -431,7 +425,7 @@ void SceneReplicationInterface::_send_sync(int p_peer, uint64_t p_msec) {
}
MAKE_ROOM(sync_mtu);
uint8_t *ptr = packet_cache.ptrw();
ptr[0] = MultiplayerAPI::NETWORK_COMMAND_SYNC;
ptr[0] = SceneMultiplayer::NETWORK_COMMAND_SYNC;
int ofs = 1;
ofs += encode_uint16(rep_state->peer_sync_next(p_peer), &ptr[1]);
// Can only send updates for already notified nodes.
@ -447,7 +441,7 @@ void SceneReplicationInterface::_send_sync(int p_peer, uint64_t p_msec) {
uint32_t net_id = rep_state->get_net_id(oid);
if (net_id == 0 || (net_id & 0x80000000)) {
int path_id = 0;
bool verified = multiplayer->send_object_cache(sync, p_peer, path_id);
bool verified = multiplayer->get_path_cache()->send_object_cache(sync, p_peer, path_id);
ERR_CONTINUE_MSG(path_id < 0, "This should never happen!");
if (net_id == 0) {
// First time path based ID.
@ -499,7 +493,7 @@ Error SceneReplicationInterface::on_sync_receive(int p_from, const uint8_t *p_bu
ofs += 4;
Node *node = nullptr;
if (net_id & 0x80000000) {
MultiplayerSynchronizer *sync = Object::cast_to<MultiplayerSynchronizer>(multiplayer->get_cached_object(p_from, net_id & 0x7FFFFFFF));
MultiplayerSynchronizer *sync = Object::cast_to<MultiplayerSynchronizer>(multiplayer->get_path_cache()->get_cached_object(p_from, net_id & 0x7FFFFFFF));
ERR_FAIL_COND_V(!sync || sync->get_multiplayer_authority() != p_from, ERR_UNAUTHORIZED);
node = sync->get_node(sync->get_root_path());
} else {

View File

@ -31,12 +31,14 @@
#ifndef SCENE_REPLICATION_INTERFACE_H
#define SCENE_REPLICATION_INTERFACE_H
#include "core/multiplayer/multiplayer_api.h"
#include "scene/main/multiplayer_api.h"
#include "scene/multiplayer/scene_replication_state.h"
#include "scene_replication_state.h"
class SceneReplicationInterface : public MultiplayerReplicationInterface {
GDCLASS(SceneReplicationInterface, MultiplayerReplicationInterface);
class SceneMultiplayer;
class SceneReplicationInterface : public RefCounted {
GDCLASS(SceneReplicationInterface, RefCounted);
private:
void _send_sync(int p_peer, uint64_t p_msec);
@ -50,7 +52,7 @@ private:
void _free_remotes(int p_peer);
Ref<SceneReplicationState> rep_state;
MultiplayerAPI *multiplayer = nullptr;
SceneMultiplayer *multiplayer = nullptr;
PackedByteArray packet_cache;
int sync_mtu = 1350; // Highly dependent on underlying protocol.
@ -59,26 +61,23 @@ private:
const uint8_t *pending_buffer = nullptr;
int pending_buffer_size = 0;
protected:
static MultiplayerReplicationInterface *_create(MultiplayerAPI *p_multiplayer);
public:
static void make_default();
virtual void on_reset() override;
virtual void on_peer_change(int p_id, bool p_connected) override;
void on_reset();
void on_peer_change(int p_id, bool p_connected);
virtual Error on_spawn(Object *p_obj, Variant p_config) override;
virtual Error on_despawn(Object *p_obj, Variant p_config) override;
virtual Error on_replication_start(Object *p_obj, Variant p_config) override;
virtual Error on_replication_stop(Object *p_obj, Variant p_config) override;
virtual void on_network_process() override;
Error on_spawn(Object *p_obj, Variant p_config);
Error on_despawn(Object *p_obj, Variant p_config);
Error on_replication_start(Object *p_obj, Variant p_config);
Error on_replication_stop(Object *p_obj, Variant p_config);
void on_network_process();
virtual Error on_spawn_receive(int p_from, const uint8_t *p_buffer, int p_buffer_len) override;
virtual Error on_despawn_receive(int p_from, const uint8_t *p_buffer, int p_buffer_len) override;
virtual Error on_sync_receive(int p_from, const uint8_t *p_buffer, int p_buffer_len) override;
Error on_spawn_receive(int p_from, const uint8_t *p_buffer, int p_buffer_len);
Error on_despawn_receive(int p_from, const uint8_t *p_buffer, int p_buffer_len);
Error on_sync_receive(int p_from, const uint8_t *p_buffer, int p_buffer_len);
SceneReplicationInterface(MultiplayerAPI *p_multiplayer) {
SceneReplicationInterface(SceneMultiplayer *p_multiplayer) {
rep_state.instantiate();
multiplayer = p_multiplayer;
}

View File

@ -28,13 +28,13 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#include "scene/multiplayer/scene_replication_state.h"
#include "scene_replication_state.h"
#include "core/multiplayer/multiplayer_api.h"
#include "scene/multiplayer/multiplayer_spawner.h"
#include "scene/multiplayer/multiplayer_synchronizer.h"
#include "scene/scene_string_names.h"
#include "multiplayer_spawner.h"
#include "multiplayer_synchronizer.h"
SceneReplicationState::TrackedNode &SceneReplicationState::_track(const ObjectID &p_id) {
if (!tracked_nodes.has(p_id)) {
tracked_nodes[p_id] = TrackedNode(p_id);

View File

@ -33,6 +33,9 @@
#include "core/object/ref_counted.h"
#include "multiplayer_spawner.h"
#include "multiplayer_synchronizer.h"
class MultiplayerSpawner;
class MultiplayerSynchronizer;
class Node;

View File

@ -28,21 +28,28 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#include "scene/multiplayer/scene_rpc_interface.h"
#include "scene_rpc_interface.h"
#include "core/debugger/engine_debugger.h"
#include "core/io/marshalls.h"
#include "core/multiplayer/multiplayer_api.h"
#include "scene/main/multiplayer_api.h"
#include "scene/main/node.h"
#include "scene/main/window.h"
MultiplayerRPCInterface *SceneRPCInterface::_create(MultiplayerAPI *p_multiplayer) {
return memnew(SceneRPCInterface(p_multiplayer));
}
#include "scene_multiplayer.h"
void SceneRPCInterface::make_default() {
MultiplayerAPI::create_default_rpc_interface = _create;
}
// The RPC meta is composed by a single byte that contains (starting from the least significant bit):
// - `NetworkCommands` in the first four bits.
// - `NetworkNodeIdCompression` in the next 2 bits.
// - `NetworkNameIdCompression` in the next 1 bit.
// - `byte_only_or_no_args` in the next 1 bit.
#define NODE_ID_COMPRESSION_SHIFT SceneMultiplayer::CMD_FLAG_0_SHIFT
#define NAME_ID_COMPRESSION_SHIFT SceneMultiplayer::CMD_FLAG_2_SHIFT
#define BYTE_ONLY_OR_NO_ARGS_SHIFT SceneMultiplayer::CMD_FLAG_3_SHIFT
#define NODE_ID_COMPRESSION_FLAG ((1 << NODE_ID_COMPRESSION_SHIFT) | (1 << (NODE_ID_COMPRESSION_SHIFT + 1)))
#define NAME_ID_COMPRESSION_FLAG (1 << NAME_ID_COMPRESSION_SHIFT)
#define BYTE_ONLY_OR_NO_ARGS_FLAG (1 << BYTE_ONLY_OR_NO_ARGS_SHIFT)
#ifdef DEBUG_ENABLED
_FORCE_INLINE_ void SceneRPCInterface::_profile_node_data(const String &p_what, ObjectID p_id) {
@ -67,50 +74,58 @@ int get_packet_len(uint32_t p_node_target, int p_packet_len) {
}
}
const Multiplayer::RPCConfig _get_rpc_config(const Node *p_node, const StringName &p_method, uint16_t &r_id) {
const Vector<Multiplayer::RPCConfig> node_config = p_node->get_node_rpc_methods();
for (int i = 0; i < node_config.size(); i++) {
if (node_config[i].name == p_method) {
r_id = ((uint16_t)i) | (1 << 15);
return node_config[i];
}
void SceneRPCInterface::_parse_rpc_config(const Variant &p_config, bool p_for_node, RPCConfigCache &r_cache) {
if (p_config.get_type() == Variant::NIL) {
return;
}
ERR_FAIL_COND(p_config.get_type() != Variant::DICTIONARY);
const Dictionary config = p_config;
Array names = config.keys();
names.sort(); // Ensure ID order
for (int i = 0; i < names.size(); i++) {
ERR_CONTINUE(names[i].get_type() != Variant::STRING);
String name = names[i].operator String();
ERR_CONTINUE(config[name].get_type() != Variant::DICTIONARY);
ERR_CONTINUE(!config[name].operator Dictionary().has("rpc_mode"));
Dictionary dict = config[name];
RPCConfig cfg;
cfg.name = name;
cfg.rpc_mode = ((MultiplayerAPI::RPCMode)dict.get("rpc_mode", MultiplayerAPI::RPC_MODE_AUTHORITY).operator int());
cfg.transfer_mode = ((MultiplayerPeer::TransferMode)dict.get("transfer_mode", MultiplayerPeer::TRANSFER_MODE_RELIABLE).operator int());
cfg.call_local = dict.get("call_local", false).operator bool();
cfg.channel = dict.get("channel", 0).operator int();
uint16_t id = ((uint16_t)i);
if (p_for_node) {
id |= (1 << 15);
}
r_cache.configs[id] = cfg;
r_cache.ids[name] = id;
}
}
const SceneRPCInterface::RPCConfigCache &SceneRPCInterface::_get_node_config(const Node *p_node) {
const ObjectID oid = p_node->get_instance_id();
if (rpc_cache.has(oid)) {
return rpc_cache[oid];
}
RPCConfigCache cache;
_parse_rpc_config(p_node->get_node_rpc_config(), true, cache);
if (p_node->get_script_instance()) {
const Vector<Multiplayer::RPCConfig> script_config = p_node->get_script_instance()->get_rpc_methods();
for (int i = 0; i < script_config.size(); i++) {
if (script_config[i].name == p_method) {
r_id = (uint16_t)i;
return script_config[i];
}
}
_parse_rpc_config(p_node->get_script_instance()->get_rpc_config(), false, cache);
}
return Multiplayer::RPCConfig();
rpc_cache[oid] = cache;
return rpc_cache[oid];
}
const Multiplayer::RPCConfig _get_rpc_config_by_id(Node *p_node, uint16_t p_id) {
Vector<Multiplayer::RPCConfig> config;
uint16_t id = p_id;
if (id & (1 << 15)) {
id = id & ~(1 << 15);
config = p_node->get_node_rpc_methods();
} else if (p_node->get_script_instance()) {
config = p_node->get_script_instance()->get_rpc_methods();
}
if (id < config.size()) {
return config[id];
}
return Multiplayer::RPCConfig();
}
_FORCE_INLINE_ bool _can_call_mode(Node *p_node, Multiplayer::RPCMode mode, int p_remote_id) {
_FORCE_INLINE_ bool _can_call_mode(Node *p_node, MultiplayerAPI::RPCMode mode, int p_remote_id) {
switch (mode) {
case Multiplayer::RPC_MODE_DISABLED: {
case MultiplayerAPI::RPC_MODE_DISABLED: {
return false;
} break;
case Multiplayer::RPC_MODE_ANY_PEER: {
case MultiplayerAPI::RPC_MODE_ANY_PEER: {
return true;
} break;
case Multiplayer::RPC_MODE_AUTHORITY: {
case MultiplayerAPI::RPC_MODE_AUTHORITY: {
return !p_node->is_multiplayer_authority() && p_remote_id == p_node->get_multiplayer_authority();
} break;
}
@ -118,19 +133,13 @@ _FORCE_INLINE_ bool _can_call_mode(Node *p_node, Multiplayer::RPCMode mode, int
return false;
}
String SceneRPCInterface::get_rpc_md5(const Object *p_obj) const {
String SceneRPCInterface::get_rpc_md5(const Object *p_obj) {
const Node *node = Object::cast_to<Node>(p_obj);
ERR_FAIL_COND_V(!node, "");
const RPCConfigCache cache = _get_node_config(node);
String rpc_list;
const Vector<Multiplayer::RPCConfig> node_config = node->get_node_rpc_methods();
for (int i = 0; i < node_config.size(); i++) {
rpc_list += String(node_config[i].name);
}
if (node->get_script_instance()) {
const Vector<Multiplayer::RPCConfig> script_config = node->get_script_instance()->get_rpc_methods();
for (int i = 0; i < script_config.size(); i++) {
rpc_list += String(script_config[i].name);
}
for (const KeyValue<uint16_t, RPCConfig> &config : cache.configs) {
rpc_list += String(config.value.name);
}
return rpc_list.md5_text();
}
@ -159,7 +168,7 @@ Node *SceneRPCInterface::_process_get_node(int p_from, const uint8_t *p_packet,
return node;
} else {
// Use cached path.
return Object::cast_to<Node>(multiplayer->get_cached_object(p_from, p_node_target));
return Object::cast_to<Node>(multiplayer->get_path_cache()->get_cached_object(p_from, p_node_target));
}
}
@ -240,8 +249,9 @@ void SceneRPCInterface::_process_rpc(Node *p_node, const uint16_t p_rpc_method_i
ERR_FAIL_COND_MSG(p_offset > p_packet_len, "Invalid packet received. Size too small.");
// Check that remote can call the RPC on this node.
const Multiplayer::RPCConfig config = _get_rpc_config_by_id(p_node, p_rpc_method_id);
ERR_FAIL_COND(config.name == StringName());
const RPCConfigCache &cache_config = _get_node_config(p_node);
ERR_FAIL_COND(!cache_config.configs.has(p_rpc_method_id));
const RPCConfig &config = cache_config.configs[p_rpc_method_id];
bool can_call = _can_call_mode(p_node, config.rpc_mode, p_from);
ERR_FAIL_COND_MSG(!can_call, "RPC '" + String(config.name) + "' is not allowed on node " + p_node->get_path() + " from: " + itos(p_from) + ". Mode is " + itos((int)config.rpc_mode) + ", authority is " + itos(p_node->get_multiplayer_authority()) + ".");
@ -286,7 +296,7 @@ void SceneRPCInterface::_process_rpc(Node *p_node, const uint16_t p_rpc_method_i
}
}
void SceneRPCInterface::_send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, const Multiplayer::RPCConfig &p_config, const StringName &p_name, const Variant **p_arg, int p_argcount) {
void SceneRPCInterface::_send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, const RPCConfig &p_config, const StringName &p_name, const Variant **p_arg, int p_argcount) {
Ref<MultiplayerPeer> peer = multiplayer->get_multiplayer_peer();
ERR_FAIL_COND_MSG(peer.is_null(), "Attempt to call RPC without active multiplayer peer.");
@ -297,14 +307,14 @@ void SceneRPCInterface::_send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, con
ERR_FAIL_COND_MSG(p_argcount > 255, "Too many arguments (>255).");
if (p_to != 0 && !multiplayer->get_connected_peers().has(ABS(p_to))) {
ERR_FAIL_COND_MSG(p_to == peer->get_unique_id(), "Attempt to call RPC on yourself! Peer unique ID: " + itos(peer->get_unique_id()) + ".");
ERR_FAIL_COND_MSG(p_to == multiplayer->get_unique_id(), "Attempt to call RPC on yourself! Peer unique ID: " + itos(multiplayer->get_unique_id()) + ".");
ERR_FAIL_MSG("Attempt to call RPC with unknown peer ID: " + itos(p_to) + ".");
}
// See if all peers have cached path (if so, call can be fast).
int psc_id;
const bool has_all_peers = multiplayer->send_object_cache(p_from, p_to, psc_id);
const bool has_all_peers = multiplayer->get_path_cache()->send_object_cache(p_from, p_to, psc_id);
// Create base packet, lots of hardcode because it must be tight.
@ -315,7 +325,7 @@ void SceneRPCInterface::_send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, con
packet_cache.resize(m_amount);
// Encode meta.
uint8_t command_type = MultiplayerAPI::NETWORK_COMMAND_REMOTE_CALL;
uint8_t command_type = SceneMultiplayer::NETWORK_COMMAND_REMOTE_CALL;
uint8_t node_id_compression = UINT8_MAX;
uint8_t name_id_compression = UINT8_MAX;
bool byte_only_or_no_args = false;
@ -426,7 +436,7 @@ void SceneRPCInterface::_send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, con
continue; // Continue, not for this peer.
}
bool confirmed = multiplayer->is_cache_confirmed(from_path, P);
bool confirmed = multiplayer->get_path_cache()->is_cache_confirmed(from_path, P);
peer->set_target_peer(P); // To this one specifically.
@ -443,22 +453,25 @@ void SceneRPCInterface::_send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, con
}
}
void SceneRPCInterface::rpcp(Object *p_obj, int p_peer_id, const StringName &p_method, const Variant **p_arg, int p_argcount) {
Error SceneRPCInterface::rpcp(Object *p_obj, int p_peer_id, const StringName &p_method, const Variant **p_arg, int p_argcount) {
Ref<MultiplayerPeer> peer = multiplayer->get_multiplayer_peer();
ERR_FAIL_COND_MSG(!peer.is_valid(), "Trying to call an RPC while no multiplayer peer is active.");
ERR_FAIL_COND_V_MSG(!peer.is_valid(), ERR_UNCONFIGURED, "Trying to call an RPC while no multiplayer peer is active.");
Node *node = Object::cast_to<Node>(p_obj);
ERR_FAIL_COND(!node);
ERR_FAIL_COND_MSG(!node->is_inside_tree(), "Trying to call an RPC on a node which is not inside SceneTree.");
ERR_FAIL_COND_MSG(peer->get_connection_status() != MultiplayerPeer::CONNECTION_CONNECTED, "Trying to call an RPC via a multiplayer peer which is not connected.");
ERR_FAIL_COND_V_MSG(!node || !node->is_inside_tree(), ERR_INVALID_PARAMETER, "The object must be a valid Node inside the SceneTree");
ERR_FAIL_COND_V_MSG(peer->get_connection_status() != MultiplayerPeer::CONNECTION_CONNECTED, ERR_CONNECTION_ERROR, "Trying to call an RPC via a multiplayer peer which is not connected.");
int node_id = peer->get_unique_id();
int caller_id = multiplayer->get_unique_id();
bool call_local_native = false;
bool call_local_script = false;
uint16_t rpc_id = UINT16_MAX;
const Multiplayer::RPCConfig config = _get_rpc_config(node, p_method, rpc_id);
ERR_FAIL_COND_MSG(config.name == StringName(),
const RPCConfigCache &config_cache = _get_node_config(node);
uint16_t rpc_id = config_cache.ids.has(p_method) ? config_cache.ids[p_method] : UINT16_MAX;
ERR_FAIL_COND_V_MSG(rpc_id == UINT16_MAX, ERR_INVALID_PARAMETER,
vformat("Unable to get the RPC configuration for the function \"%s\" at path: \"%s\". This happens when the method is missing or not marked for RPCs in the local script.", p_method, node->get_path()));
if (p_peer_id == 0 || p_peer_id == node_id || (p_peer_id < 0 && p_peer_id != -node_id)) {
const RPCConfig &config = config_cache.configs[rpc_id];
ERR_FAIL_COND_V_MSG(p_peer_id == caller_id && !config.call_local, ERR_INVALID_PARAMETER, "RPC '" + p_method + "' on yourself is not allowed by selected mode.");
if (p_peer_id == 0 || p_peer_id == caller_id || (p_peer_id < 0 && p_peer_id != -caller_id)) {
if (rpc_id & (1 << 15)) {
call_local_native = config.call_local;
} else {
@ -466,7 +479,7 @@ void SceneRPCInterface::rpcp(Object *p_obj, int p_peer_id, const StringName &p_m
}
}
if (p_peer_id != node_id) {
if (p_peer_id != caller_id) {
#ifdef DEBUG_ENABLED
_profile_node_data("rpc_out", node->get_instance_id());
#endif
@ -477,7 +490,7 @@ void SceneRPCInterface::rpcp(Object *p_obj, int p_peer_id, const StringName &p_m
if (call_local_native) {
Callable::CallError ce;
multiplayer->set_remote_sender_override(peer->get_unique_id());
multiplayer->set_remote_sender_override(multiplayer->get_unique_id());
node->callp(p_method, p_arg, p_argcount, ce);
multiplayer->set_remote_sender_override(0);
@ -485,7 +498,7 @@ void SceneRPCInterface::rpcp(Object *p_obj, int p_peer_id, const StringName &p_m
String error = Variant::get_call_error_text(node, p_method, p_arg, p_argcount, ce);
error = "rpc() aborted in local call: - " + error + ".";
ERR_PRINT(error);
return;
return FAILED;
}
}
@ -493,7 +506,7 @@ void SceneRPCInterface::rpcp(Object *p_obj, int p_peer_id, const StringName &p_m
Callable::CallError ce;
ce.error = Callable::CallError::CALL_OK;
multiplayer->set_remote_sender_override(peer->get_unique_id());
multiplayer->set_remote_sender_override(multiplayer->get_unique_id());
node->get_script_instance()->callp(p_method, p_arg, p_argcount, ce);
multiplayer->set_remote_sender_override(0);
@ -501,9 +514,8 @@ void SceneRPCInterface::rpcp(Object *p_obj, int p_peer_id, const StringName &p_m
String error = Variant::get_call_error_text(node, p_method, p_arg, p_argcount, ce);
error = "rpc() aborted in script local call: - " + error + ".";
ERR_PRINT(error);
return;
return FAILED;
}
}
ERR_FAIL_COND_MSG(p_peer_id == node_id && !config.call_local, "RPC '" + p_method + "' on yourself is not allowed by selected mode.");
return OK;
}

View File

@ -31,13 +31,40 @@
#ifndef SCENE_RPC_INTERFACE_H
#define SCENE_RPC_INTERFACE_H
#include "core/multiplayer/multiplayer.h"
#include "core/multiplayer/multiplayer_api.h"
#include "core/object/ref_counted.h"
#include "scene/main/multiplayer_api.h"
class SceneRPCInterface : public MultiplayerRPCInterface {
GDCLASS(SceneRPCInterface, MultiplayerRPCInterface);
class SceneMultiplayer;
class Node;
class SceneRPCInterface : public RefCounted {
GDCLASS(SceneRPCInterface, RefCounted);
private:
struct RPCConfig {
StringName name;
MultiplayerAPI::RPCMode rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED;
bool call_local = false;
MultiplayerPeer::TransferMode transfer_mode = MultiplayerPeer::TRANSFER_MODE_RELIABLE;
int channel = 0;
bool operator==(RPCConfig const &p_other) const {
return name == p_other.name;
}
};
struct RPCConfigCache {
HashMap<uint16_t, RPCConfig> configs;
HashMap<StringName, uint16_t> ids;
};
struct SortRPCConfig {
StringName::AlphCompare compare;
bool operator()(const RPCConfig &p_a, const RPCConfig &p_b) const {
return compare(p_a.name, p_b.name);
}
};
enum NetworkNodeIdCompression {
NETWORK_NODE_ID_COMPRESSION_8 = 0,
NETWORK_NODE_ID_COMPRESSION_16,
@ -49,43 +76,27 @@ private:
NETWORK_NAME_ID_COMPRESSION_16,
};
// The RPC meta is composed by a single byte that contains (starting from the least significant bit):
// - `NetworkCommands` in the first four bits.
// - `NetworkNodeIdCompression` in the next 2 bits.
// - `NetworkNameIdCompression` in the next 1 bit.
// - `byte_only_or_no_args` in the next 1 bit.
enum {
NODE_ID_COMPRESSION_SHIFT = MultiplayerAPI::CMD_FLAG_0_SHIFT, // 2 bits for this.
NAME_ID_COMPRESSION_SHIFT = MultiplayerAPI::CMD_FLAG_2_SHIFT,
BYTE_ONLY_OR_NO_ARGS_SHIFT = MultiplayerAPI::CMD_FLAG_3_SHIFT,
};
enum {
NODE_ID_COMPRESSION_FLAG = (1 << NODE_ID_COMPRESSION_SHIFT) | (1 << (NODE_ID_COMPRESSION_SHIFT + 1)), // 2 bits for this.
NAME_ID_COMPRESSION_FLAG = (1 << NAME_ID_COMPRESSION_SHIFT),
BYTE_ONLY_OR_NO_ARGS_FLAG = (1 << BYTE_ONLY_OR_NO_ARGS_SHIFT),
};
MultiplayerAPI *multiplayer = nullptr;
SceneMultiplayer *multiplayer = nullptr;
Vector<uint8_t> packet_cache;
protected:
static MultiplayerRPCInterface *_create(MultiplayerAPI *p_multiplayer);
HashMap<ObjectID, RPCConfigCache> rpc_cache;
protected:
_FORCE_INLINE_ void _profile_node_data(const String &p_what, ObjectID p_id);
void _process_rpc(Node *p_node, const uint16_t p_rpc_method_id, int p_from, const uint8_t *p_packet, int p_packet_len, int p_offset);
void _send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, const Multiplayer::RPCConfig &p_config, const StringName &p_name, const Variant **p_arg, int p_argcount);
void _send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, const RPCConfig &p_config, const StringName &p_name, const Variant **p_arg, int p_argcount);
Node *_process_get_node(int p_from, const uint8_t *p_packet, uint32_t p_node_target, int p_packet_len);
void _parse_rpc_config(const Variant &p_config, bool p_for_node, RPCConfigCache &r_cache);
const RPCConfigCache &_get_node_config(const Node *p_node);
public:
static void make_default();
Error rpcp(Object *p_obj, int p_peer_id, const StringName &p_method, const Variant **p_arg, int p_argcount);
void process_rpc(int p_from, const uint8_t *p_packet, int p_packet_len);
String get_rpc_md5(const Object *p_obj);
virtual void rpcp(Object *p_obj, int p_peer_id, const StringName &p_method, const Variant **p_arg, int p_argcount) override;
virtual void process_rpc(int p_from, const uint8_t *p_packet, int p_packet_len) override;
virtual String get_rpc_md5(const Object *p_obj) const override;
SceneRPCInterface(MultiplayerAPI *p_multiplayer) { multiplayer = p_multiplayer; }
SceneRPCInterface(SceneMultiplayer *p_multiplayer) { multiplayer = p_multiplayer; }
};
#endif // SCENE_RPC_INTERFACE_H

View File

@ -948,7 +948,7 @@ bool VisualScript::are_subnodes_edited() const {
}
#endif
const Vector<Multiplayer::RPCConfig> VisualScript::get_rpc_methods() const {
const Variant VisualScript::get_rpc_config() const {
return rpc_functions;
}
@ -1012,22 +1012,16 @@ void VisualScript::_set_data(const Dictionary &p_data) {
for (const KeyValue<StringName, Function> &E : functions) {
if (E.value.func_id >= 0 && nodes.has(E.value.func_id)) {
Ref<VisualScriptFunction> vsf = nodes[E.value.func_id].node;
if (vsf.is_valid()) {
if (vsf->get_rpc_mode() != Multiplayer::RPC_MODE_DISABLED) {
Multiplayer::RPCConfig nd;
nd.name = E.key;
nd.rpc_mode = vsf->get_rpc_mode();
nd.transfer_mode = Multiplayer::TRANSFER_MODE_RELIABLE; // TODO
if (rpc_functions.find(nd) == -1) {
rpc_functions.push_back(nd);
}
}
if (!vsf.is_valid() || vsf->get_rpc_mode() == MultiplayerAPI::RPC_MODE_DISABLED) {
continue;
}
Dictionary nd;
nd["rpc_mode"] = vsf->get_rpc_mode();
nd["transfer_mode"] = MultiplayerPeer::TRANSFER_MODE_RELIABLE; // TODO
nd["call_local"] = false; // TODO
rpc_functions[E.key] = nd;
}
}
// Sort so we are 100% that they are always the same.
rpc_functions.sort_custom<Multiplayer::SortRPCConfig>();
}
Dictionary VisualScript::_get_data() const {
@ -1811,8 +1805,8 @@ Ref<Script> VisualScriptInstance::get_script() const {
return script;
}
const Vector<Multiplayer::RPCConfig> VisualScriptInstance::get_rpc_methods() const {
return script->get_rpc_methods();
const Variant VisualScriptInstance::get_rpc_config() const {
return script->get_rpc_config();
}
void VisualScriptInstance::create(const Ref<VisualScript> &p_script, Object *p_owner) {

View File

@ -235,7 +235,7 @@ private:
HashMap<StringName, Function> functions;
HashMap<StringName, Variable> variables;
HashMap<StringName, Vector<Argument>> custom_signals;
Vector<Multiplayer::RPCConfig> rpc_functions;
Dictionary rpc_functions;
HashMap<Object *, VisualScriptInstance *> instances;
@ -363,7 +363,7 @@ public:
virtual int get_member_line(const StringName &p_member) const override;
virtual const Vector<Multiplayer::RPCConfig> get_rpc_methods() const override;
virtual const Variant get_rpc_config() const override;
#ifdef TOOLS_ENABLED
virtual bool are_subnodes_edited() const;
@ -444,7 +444,7 @@ public:
virtual ScriptLanguage *get_language();
virtual const Vector<Multiplayer::RPCConfig> get_rpc_methods() const;
virtual const Variant get_rpc_config() const;
VisualScriptInstance();
~VisualScriptInstance();

View File

@ -90,7 +90,7 @@ bool VisualScriptFunction::_set(const StringName &p_name, const Variant &p_value
}
if (p_name == "rpc/mode") {
rpc_mode = Multiplayer::RPCMode(int(p_value));
rpc_mode = MultiplayerAPI::RPCMode(int(p_value));
return true;
}
@ -261,11 +261,11 @@ int VisualScriptFunction::get_argument_count() const {
return arguments.size();
}
void VisualScriptFunction::set_rpc_mode(Multiplayer::RPCMode p_mode) {
void VisualScriptFunction::set_rpc_mode(MultiplayerAPI::RPCMode p_mode) {
rpc_mode = p_mode;
}
Multiplayer::RPCMode VisualScriptFunction::get_rpc_mode() const {
MultiplayerAPI::RPCMode VisualScriptFunction::get_rpc_mode() const {
return rpc_mode;
}
@ -311,14 +311,14 @@ void VisualScriptFunction::reset_state() {
stack_size = 256;
stack_less = false;
sequenced = true;
rpc_mode = Multiplayer::RPC_MODE_DISABLED;
rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED;
}
VisualScriptFunction::VisualScriptFunction() {
stack_size = 256;
stack_less = false;
sequenced = true;
rpc_mode = Multiplayer::RPC_MODE_DISABLED;
rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED;
}
void VisualScriptFunction::set_stack_less(bool p_enable) {

View File

@ -33,6 +33,7 @@
#include "core/object/gdvirtual.gen.inc"
#include "core/object/script_language.h"
#include "scene/main/multiplayer_api.h"
#include "visual_script.h"
class VisualScriptFunction : public VisualScriptNode {
@ -49,7 +50,7 @@ class VisualScriptFunction : public VisualScriptNode {
bool stack_less;
int stack_size;
Multiplayer::RPCMode rpc_mode;
MultiplayerAPI::RPCMode rpc_mode;
bool sequenced;
protected:
@ -90,8 +91,8 @@ public:
void set_stack_size(int p_size);
int get_stack_size() const;
void set_rpc_mode(Multiplayer::RPCMode p_mode);
Multiplayer::RPCMode get_rpc_mode() const;
void set_rpc_mode(MultiplayerAPI::RPCMode p_mode);
MultiplayerAPI::RPCMode get_rpc_mode() const;
virtual VisualScriptNodeInstance *instantiate(VisualScriptInstance *p_instance) override;

View File

@ -57,7 +57,7 @@
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 TransferMode] which will be used to create extra channels (WebRTC only supports one transfer mode per channel).
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

@ -197,14 +197,14 @@ Error WebRTCMultiplayerPeer::initialize(int p_self_id, bool p_server_compat, Arr
cfg["ordered"] = true;
switch (mode) {
case Multiplayer::TRANSFER_MODE_UNRELIABLE_ORDERED:
case TRANSFER_MODE_UNRELIABLE_ORDERED:
cfg["maxPacketLifetime"] = 1;
break;
case Multiplayer::TRANSFER_MODE_UNRELIABLE:
case TRANSFER_MODE_UNRELIABLE:
cfg["maxPacketLifetime"] = 1;
cfg["ordered"] = false;
break;
case Multiplayer::TRANSFER_MODE_RELIABLE:
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));
@ -339,13 +339,13 @@ Error WebRTCMultiplayerPeer::put_packet(const uint8_t *p_buffer, int p_buffer_si
int ch = get_transfer_channel();
if (ch == 0) {
switch (get_transfer_mode()) {
case Multiplayer::TRANSFER_MODE_RELIABLE:
case TRANSFER_MODE_RELIABLE:
ch = CH_RELIABLE;
break;
case Multiplayer::TRANSFER_MODE_UNRELIABLE_ORDERED:
case TRANSFER_MODE_UNRELIABLE_ORDERED:
ch = CH_ORDERED;
break;
case Multiplayer::TRANSFER_MODE_UNRELIABLE:
case TRANSFER_MODE_UNRELIABLE:
ch = CH_UNRELIABLE;
break;
}

View File

@ -31,7 +31,7 @@
#ifndef WEBRTC_MULTIPLAYER_PEER_H
#define WEBRTC_MULTIPLAYER_PEER_H
#include "core/multiplayer/multiplayer_peer.h"
#include "scene/main/multiplayer_peer.h"
#include "webrtc_peer_connection.h"
class WebRTCMultiplayerPeer : public MultiplayerPeer {

View File

@ -32,8 +32,8 @@
#define WEBSOCKET_MULTIPLAYER_PEER_H
#include "core/error/error_list.h"
#include "core/multiplayer/multiplayer_peer.h"
#include "core/templates/list.h"
#include "scene/main/multiplayer_peer.h"
#include "websocket_peer.h"
class WebSocketMultiplayerPeer : public MultiplayerPeer {

View File

@ -9,7 +9,6 @@ env.add_source_files(env.scene_sources, "*.cpp")
# Chain load SCsubs
SConscript("main/SCsub")
SConscript("multiplayer/SCsub")
SConscript("gui/SCsub")
if not env["disable_3d"]:
SConscript("3d/SCsub")

View File

@ -0,0 +1,416 @@
/*************************************************************************/
/* multiplayer_api.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#include "multiplayer_api.h"
#include "core/debugger/engine_debugger.h"
#include "core/io/marshalls.h"
#include <stdint.h>
#ifdef DEBUG_ENABLED
#include "core/os/os.h"
#endif
StringName MultiplayerAPI::default_interface = StringName();
void MultiplayerAPI::set_default_interface(const StringName &p_interface) {
ERR_FAIL_COND_MSG(!ClassDB::is_parent_class(p_interface, MultiplayerAPI::get_class_static()), vformat("Can't make %s the default multiplayer interface since it does not extend MultiplayerAPI.", p_interface));
default_interface = p_interface;
}
StringName MultiplayerAPI::get_default_interface() {
return default_interface;
}
Ref<MultiplayerAPI> MultiplayerAPI::create_default_interface() {
if (default_interface != StringName()) {
return Ref<MultiplayerAPI>(Object::cast_to<MultiplayerAPI>(ClassDB::instantiate(default_interface)));
}
return Ref<MultiplayerAPI>(memnew(MultiplayerAPIExtension));
}
// The variant is compressed and encoded; The first byte contains all the meta
// information and the format is:
// - The first LSB 5 bits are used for the variant type.
// - The next two bits are used to store the encoding mode.
// - The most significant is used to store the boolean value.
#define VARIANT_META_TYPE_MASK 0x1F
#define VARIANT_META_EMODE_MASK 0x60
#define VARIANT_META_BOOL_MASK 0x80
#define ENCODE_8 0 << 5
#define ENCODE_16 1 << 5
#define ENCODE_32 2 << 5
#define ENCODE_64 3 << 5
Error MultiplayerAPI::encode_and_compress_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bool p_allow_object_decoding) {
// Unreachable because `VARIANT_MAX` == 27 and `ENCODE_VARIANT_MASK` == 31
CRASH_COND(p_variant.get_type() > VARIANT_META_TYPE_MASK);
uint8_t *buf = r_buffer;
r_len = 0;
uint8_t encode_mode = 0;
switch (p_variant.get_type()) {
case Variant::BOOL: {
if (buf) {
// We still have 1 free bit in the meta, so let's use it.
buf[0] = (p_variant.operator bool()) ? (1 << 7) : 0;
buf[0] |= encode_mode | p_variant.get_type();
}
r_len += 1;
} break;
case Variant::INT: {
if (buf) {
// Reserve the first byte for the meta.
buf += 1;
}
r_len += 1;
int64_t val = p_variant;
if (val <= (int64_t)INT8_MAX && val >= (int64_t)INT8_MIN) {
// Use 8 bit
encode_mode = ENCODE_8;
if (buf) {
buf[0] = val;
}
r_len += 1;
} else if (val <= (int64_t)INT16_MAX && val >= (int64_t)INT16_MIN) {
// Use 16 bit
encode_mode = ENCODE_16;
if (buf) {
encode_uint16(val, buf);
}
r_len += 2;
} else if (val <= (int64_t)INT32_MAX && val >= (int64_t)INT32_MIN) {
// Use 32 bit
encode_mode = ENCODE_32;
if (buf) {
encode_uint32(val, buf);
}
r_len += 4;
} else {
// Use 64 bit
encode_mode = ENCODE_64;
if (buf) {
encode_uint64(val, buf);
}
r_len += 8;
}
// Store the meta
if (buf) {
buf -= 1;
buf[0] = encode_mode | p_variant.get_type();
}
} break;
default:
// Any other case is not yet compressed.
Error err = encode_variant(p_variant, r_buffer, r_len, p_allow_object_decoding);
if (err != OK) {
return err;
}
if (r_buffer) {
// The first byte is not used by the marshalling, so store the type
// so we know how to decompress and decode this variant.
r_buffer[0] = p_variant.get_type();
}
}
return OK;
}
Error MultiplayerAPI::decode_and_decompress_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int *r_len, bool p_allow_object_decoding) {
const uint8_t *buf = p_buffer;
int len = p_len;
ERR_FAIL_COND_V(len < 1, ERR_INVALID_DATA);
uint8_t type = buf[0] & VARIANT_META_TYPE_MASK;
uint8_t encode_mode = buf[0] & VARIANT_META_EMODE_MASK;
ERR_FAIL_COND_V(type >= Variant::VARIANT_MAX, ERR_INVALID_DATA);
switch (type) {
case Variant::BOOL: {
bool val = (buf[0] & VARIANT_META_BOOL_MASK) > 0;
r_variant = val;
if (r_len) {
*r_len = 1;
}
} break;
case Variant::INT: {
buf += 1;
len -= 1;
if (r_len) {
*r_len = 1;
}
if (encode_mode == ENCODE_8) {
// 8 bits.
ERR_FAIL_COND_V(len < 1, ERR_INVALID_DATA);
int8_t val = buf[0];
r_variant = val;
if (r_len) {
(*r_len) += 1;
}
} else if (encode_mode == ENCODE_16) {
// 16 bits.
ERR_FAIL_COND_V(len < 2, ERR_INVALID_DATA);
int16_t val = decode_uint16(buf);
r_variant = val;
if (r_len) {
(*r_len) += 2;
}
} else if (encode_mode == ENCODE_32) {
// 32 bits.
ERR_FAIL_COND_V(len < 4, ERR_INVALID_DATA);
int32_t val = decode_uint32(buf);
r_variant = val;
if (r_len) {
(*r_len) += 4;
}
} else {
// 64 bits.
ERR_FAIL_COND_V(len < 8, ERR_INVALID_DATA);
int64_t val = decode_uint64(buf);
r_variant = val;
if (r_len) {
(*r_len) += 8;
}
}
} break;
default:
Error err = decode_variant(r_variant, p_buffer, p_len, r_len, p_allow_object_decoding);
if (err != OK) {
return err;
}
}
return OK;
}
Error MultiplayerAPI::encode_and_compress_variants(const Variant **p_variants, int p_count, uint8_t *p_buffer, int &r_len, bool *r_raw, bool p_allow_object_decoding) {
r_len = 0;
int size = 0;
if (p_count == 0) {
if (r_raw) {
*r_raw = true;
}
return OK;
}
// Try raw encoding optimization.
if (r_raw && p_count == 1) {
*r_raw = false;
const Variant &v = *(p_variants[0]);
if (v.get_type() == Variant::PACKED_BYTE_ARRAY) {
*r_raw = true;
const PackedByteArray pba = v;
if (p_buffer) {
memcpy(p_buffer, pba.ptr(), pba.size());
}
r_len += pba.size();
} else {
encode_and_compress_variant(v, p_buffer, size, p_allow_object_decoding);
r_len += size;
}
return OK;
}
// Regular encoding.
for (int i = 0; i < p_count; i++) {
const Variant &v = *(p_variants[i]);
encode_and_compress_variant(v, p_buffer ? p_buffer + r_len : nullptr, size, p_allow_object_decoding);
r_len += size;
}
return OK;
}
Error MultiplayerAPI::decode_and_decompress_variants(Vector<Variant> &r_variants, const uint8_t *p_buffer, int p_len, int &r_len, bool p_raw, bool p_allow_object_decoding) {
r_len = 0;
int argc = r_variants.size();
if (argc == 0 && p_raw) {
return OK;
}
ERR_FAIL_COND_V(p_raw && argc != 1, ERR_INVALID_DATA);
if (p_raw) {
r_len = p_len;
PackedByteArray pba;
pba.resize(p_len);
memcpy(pba.ptrw(), p_buffer, p_len);
r_variants.write[0] = pba;
return OK;
}
Vector<Variant> args;
Vector<const Variant *> argp;
args.resize(argc);
for (int i = 0; i < argc; i++) {
ERR_FAIL_COND_V_MSG(r_len >= p_len, ERR_INVALID_DATA, "Invalid packet received. Size too small.");
int vlen;
Error err = MultiplayerAPI::decode_and_decompress_variant(r_variants.write[i], &p_buffer[r_len], p_len - r_len, &vlen, p_allow_object_decoding);
ERR_FAIL_COND_V_MSG(err != OK, err, "Invalid packet received. Unable to decode state variable.");
r_len += vlen;
}
return OK;
}
Error MultiplayerAPI::_rpc_bind(int p_peer, Object *p_object, const StringName &p_method, Array p_args) {
Vector<Variant> args;
Vector<const Variant *> argsp;
args.resize(p_args.size());
argsp.resize(p_args.size());
Variant *ptr = args.ptrw();
const Variant **pptr = argsp.ptrw();
for (int i = 0; i < p_args.size(); i++) {
ptr[i] = p_args[i];
pptr[i] = &ptr[i];
}
return rpcp(p_object, p_peer, p_method, argsp.size() ? argsp.ptrw() : nullptr, argsp.size());
}
void MultiplayerAPI::_bind_methods() {
ClassDB::bind_method(D_METHOD("has_multiplayer_peer"), &MultiplayerAPI::has_multiplayer_peer);
ClassDB::bind_method(D_METHOD("get_multiplayer_peer"), &MultiplayerAPI::get_multiplayer_peer);
ClassDB::bind_method(D_METHOD("set_multiplayer_peer", "peer"), &MultiplayerAPI::set_multiplayer_peer);
ClassDB::bind_method(D_METHOD("get_unique_id"), &MultiplayerAPI::get_unique_id);
ClassDB::bind_method(D_METHOD("is_server"), &MultiplayerAPI::is_server);
ClassDB::bind_method(D_METHOD("get_remote_sender_id"), &MultiplayerAPI::get_remote_sender_id);
ClassDB::bind_method(D_METHOD("poll"), &MultiplayerAPI::poll);
ClassDB::bind_method(D_METHOD("rpc", "peer", "object", "method", "arguments"), &MultiplayerAPI::_rpc_bind, DEFVAL(Array()));
ClassDB::bind_method(D_METHOD("object_configuration_add", "object", "configuration"), &MultiplayerAPI::object_configuration_add);
ClassDB::bind_method(D_METHOD("object_configuration_remove", "object", "configuration"), &MultiplayerAPI::object_configuration_remove);
ClassDB::bind_method(D_METHOD("get_peers"), &MultiplayerAPI::get_peer_ids);
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "multiplayer_peer", PROPERTY_HINT_RESOURCE_TYPE, "MultiplayerPeer", PROPERTY_USAGE_NONE), "set_multiplayer_peer", "get_multiplayer_peer");
ClassDB::bind_static_method("MultiplayerAPI", D_METHOD("set_default_interface", "interface_name"), &MultiplayerAPI::set_default_interface);
ClassDB::bind_static_method("MultiplayerAPI", D_METHOD("get_default_interface"), &MultiplayerAPI::get_default_interface);
ClassDB::bind_static_method("MultiplayerAPI", D_METHOD("create_default_interface"), &MultiplayerAPI::create_default_interface);
ADD_SIGNAL(MethodInfo("peer_connected", PropertyInfo(Variant::INT, "id")));
ADD_SIGNAL(MethodInfo("peer_disconnected", PropertyInfo(Variant::INT, "id")));
ADD_SIGNAL(MethodInfo("connected_to_server"));
ADD_SIGNAL(MethodInfo("connection_failed"));
ADD_SIGNAL(MethodInfo("server_disconnected"));
BIND_ENUM_CONSTANT(RPC_MODE_DISABLED);
BIND_ENUM_CONSTANT(RPC_MODE_ANY_PEER);
BIND_ENUM_CONSTANT(RPC_MODE_AUTHORITY);
}
/// MultiplayerAPIExtension
Error MultiplayerAPIExtension::poll() {
int err;
if (GDVIRTUAL_CALL(_poll, err)) {
return (Error)err;
}
return OK;
}
void MultiplayerAPIExtension::set_multiplayer_peer(const Ref<MultiplayerPeer> &p_peer) {
GDVIRTUAL_CALL(_set_multiplayer_peer, p_peer);
}
Ref<MultiplayerPeer> MultiplayerAPIExtension::get_multiplayer_peer() {
Ref<MultiplayerPeer> peer;
if (GDVIRTUAL_CALL(_get_multiplayer_peer, peer)) {
return peer;
}
return nullptr;
}
int MultiplayerAPIExtension::get_unique_id() {
int id;
if (GDVIRTUAL_CALL(_get_unique_id, id)) {
return id;
}
return 1;
}
Vector<int> MultiplayerAPIExtension::get_peer_ids() {
Vector<int> ids;
if (GDVIRTUAL_CALL(_get_peer_ids, ids)) {
return ids;
}
return Vector<int>();
}
Error MultiplayerAPIExtension::rpcp(Object *p_obj, int p_peer_id, const StringName &p_method, const Variant **p_arg, int p_argcount) {
if (!GDVIRTUAL_IS_OVERRIDDEN(_rpc)) {
return ERR_UNAVAILABLE;
}
Array args;
for (int i = 0; i < p_argcount; i++) {
args.push_back(*p_arg[i]);
}
int ret;
if (GDVIRTUAL_CALL(_rpc, p_peer_id, p_obj, p_method, args, ret)) {
return (Error)ret;
}
return FAILED;
}
int MultiplayerAPIExtension::get_remote_sender_id() {
int id;
if (GDVIRTUAL_CALL(_get_remote_sender_id, id)) {
return id;
}
return 0;
}
Error MultiplayerAPIExtension::object_configuration_add(Object *p_object, Variant p_config) {
int err;
if (GDVIRTUAL_CALL(_object_configuration_add, p_object, p_config, err)) {
return (Error)err;
}
return ERR_UNAVAILABLE;
}
Error MultiplayerAPIExtension::object_configuration_remove(Object *p_object, Variant p_config) {
int err;
if (GDVIRTUAL_CALL(_object_configuration_remove, p_object, p_config, err)) {
return (Error)err;
}
return ERR_UNAVAILABLE;
}
void MultiplayerAPIExtension::_bind_methods() {
GDVIRTUAL_BIND(_poll);
GDVIRTUAL_BIND(_set_multiplayer_peer, "multiplayer_peer");
GDVIRTUAL_BIND(_get_multiplayer_peer);
GDVIRTUAL_BIND(_get_unique_id);
GDVIRTUAL_BIND(_get_peer_ids);
GDVIRTUAL_BIND(_rpc, "peer", "object", "method", "args");
GDVIRTUAL_BIND(_get_remote_sender_id);
GDVIRTUAL_BIND(_object_configuration_add, "object", "configuration");
GDVIRTUAL_BIND(_object_configuration_remove, "object", "configuration");
}

View File

@ -0,0 +1,115 @@
/*************************************************************************/
/* multiplayer_api.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#ifndef MULTIPLAYER_API_H
#define MULTIPLAYER_API_H
#include "core/object/ref_counted.h"
#include "scene/main/multiplayer_peer.h"
class MultiplayerAPI : public RefCounted {
GDCLASS(MultiplayerAPI, RefCounted);
private:
static StringName default_interface;
protected:
static void _bind_methods();
Error _rpc_bind(int p_peer, Object *p_obj, const StringName &p_method, Array args = Array());
public:
enum RPCMode {
RPC_MODE_DISABLED, // No rpc for this method, calls to this will be blocked (default)
RPC_MODE_ANY_PEER, // Any peer can call this RPC
RPC_MODE_AUTHORITY, // Only the node's multiplayer authority (server by default) can call this RPC
};
static Ref<MultiplayerAPI> create_default_interface();
static void set_default_interface(const StringName &p_interface);
static StringName get_default_interface();
static Error encode_and_compress_variant(const Variant &p_variant, uint8_t *p_buffer, int &r_len, bool p_allow_object_decoding);
static Error decode_and_decompress_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int *r_len, bool p_allow_object_decoding);
static Error encode_and_compress_variants(const Variant **p_variants, int p_count, uint8_t *p_buffer, int &r_len, bool *r_raw = nullptr, bool p_allow_object_decoding = false);
static Error decode_and_decompress_variants(Vector<Variant> &r_variants, const uint8_t *p_buffer, int p_len, int &r_len, bool p_raw = false, bool p_allow_object_decoding = false);
virtual Error poll() = 0;
virtual void set_multiplayer_peer(const Ref<MultiplayerPeer> &p_peer) = 0;
virtual Ref<MultiplayerPeer> get_multiplayer_peer() = 0;
virtual int get_unique_id() = 0;
virtual Vector<int> get_peer_ids() = 0;
virtual Error rpcp(Object *p_obj, int p_peer_id, const StringName &p_method, const Variant **p_arg, int p_argcount) = 0;
virtual int get_remote_sender_id() = 0;
virtual Error object_configuration_add(Object *p_object, Variant p_config) = 0;
virtual Error object_configuration_remove(Object *p_object, Variant p_config) = 0;
bool has_multiplayer_peer() { return get_multiplayer_peer().is_valid(); }
bool is_server() { return get_unique_id() == MultiplayerPeer::TARGET_PEER_SERVER; }
MultiplayerAPI() {}
virtual ~MultiplayerAPI() {}
};
VARIANT_ENUM_CAST(MultiplayerAPI::RPCMode);
class MultiplayerAPIExtension : public MultiplayerAPI {
GDCLASS(MultiplayerAPIExtension, MultiplayerAPI);
protected:
static void _bind_methods();
public:
virtual Error poll() override;
virtual void set_multiplayer_peer(const Ref<MultiplayerPeer> &p_peer) override;
virtual Ref<MultiplayerPeer> get_multiplayer_peer() override;
virtual int get_unique_id() override;
virtual Vector<int> get_peer_ids() override;
virtual Error rpcp(Object *p_obj, int p_peer_id, const StringName &p_method, const Variant **p_arg, int p_argcount) override;
virtual int get_remote_sender_id() override;
virtual Error object_configuration_add(Object *p_object, Variant p_config) override;
virtual Error object_configuration_remove(Object *p_object, Variant p_config) override;
// Extensions
GDVIRTUAL0R(int, _poll);
GDVIRTUAL1(_set_multiplayer_peer, Ref<MultiplayerPeer>);
GDVIRTUAL0R(Ref<MultiplayerPeer>, _get_multiplayer_peer);
GDVIRTUAL0RC(int, _get_unique_id);
GDVIRTUAL0RC(PackedInt32Array, _get_peer_ids);
GDVIRTUAL4R(int, _rpc, int, Object *, StringName, Array);
GDVIRTUAL0RC(int, _get_remote_sender_id);
GDVIRTUAL2R(int, _object_configuration_add, Object *, Variant);
GDVIRTUAL2R(int, _object_configuration_remove, Object *, Variant);
};
#endif // MULTIPLAYER_API_H

View File

@ -62,11 +62,11 @@ int MultiplayerPeer::get_transfer_channel() const {
return transfer_channel;
}
void MultiplayerPeer::set_transfer_mode(Multiplayer::TransferMode p_mode) {
void MultiplayerPeer::set_transfer_mode(TransferMode p_mode) {
transfer_mode = p_mode;
}
Multiplayer::TransferMode MultiplayerPeer::get_transfer_mode() const {
MultiplayerPeer::TransferMode MultiplayerPeer::get_transfer_mode() const {
return transfer_mode;
}
@ -107,6 +107,10 @@ void MultiplayerPeer::_bind_methods() {
BIND_CONSTANT(TARGET_PEER_BROADCAST);
BIND_CONSTANT(TARGET_PEER_SERVER);
BIND_ENUM_CONSTANT(TRANSFER_MODE_UNRELIABLE);
BIND_ENUM_CONSTANT(TRANSFER_MODE_UNRELIABLE_ORDERED);
BIND_ENUM_CONSTANT(TRANSFER_MODE_RELIABLE);
ADD_SIGNAL(MethodInfo("peer_connected", PropertyInfo(Variant::INT, "id")));
ADD_SIGNAL(MethodInfo("peer_disconnected", PropertyInfo(Variant::INT, "id")));
ADD_SIGNAL(MethodInfo("server_disconnected"));
@ -191,17 +195,17 @@ int MultiplayerPeerExtension::get_transfer_channel() const {
return MultiplayerPeer::get_transfer_channel();
}
void MultiplayerPeerExtension::set_transfer_mode(Multiplayer::TransferMode p_mode) {
void MultiplayerPeerExtension::set_transfer_mode(TransferMode p_mode) {
if (GDVIRTUAL_CALL(_set_transfer_mode, p_mode)) {
return;
}
MultiplayerPeer::set_transfer_mode(p_mode);
}
Multiplayer::TransferMode MultiplayerPeerExtension::get_transfer_mode() const {
MultiplayerPeer::TransferMode MultiplayerPeerExtension::get_transfer_mode() const {
int mode;
if (GDVIRTUAL_CALL(_get_transfer_mode, mode)) {
return (Multiplayer::TransferMode)mode;
return (MultiplayerPeer::TransferMode)mode;
}
return MultiplayerPeer::get_transfer_mode();
}

View File

@ -32,7 +32,6 @@
#define MULTIPLAYER_PEER_H
#include "core/io/packet_peer.h"
#include "core/multiplayer/multiplayer.h"
#include "core/object/gdvirtual.gen.inc"
#include "core/object/script_language.h"
@ -41,12 +40,19 @@
class MultiplayerPeer : public PacketPeer {
GDCLASS(MultiplayerPeer, PacketPeer);
public:
enum TransferMode {
TRANSFER_MODE_UNRELIABLE,
TRANSFER_MODE_UNRELIABLE_ORDERED,
TRANSFER_MODE_RELIABLE
};
protected:
static void _bind_methods();
private:
int transfer_channel = 0;
Multiplayer::TransferMode transfer_mode = Multiplayer::TRANSFER_MODE_RELIABLE;
TransferMode transfer_mode = TRANSFER_MODE_RELIABLE;
bool refuse_connections = false;
public:
@ -63,8 +69,8 @@ public:
virtual void set_transfer_channel(int p_channel);
virtual int get_transfer_channel() const;
virtual void set_transfer_mode(Multiplayer::TransferMode p_mode);
virtual Multiplayer::TransferMode get_transfer_mode() const;
virtual void set_transfer_mode(TransferMode p_mode);
virtual TransferMode get_transfer_mode() const;
virtual void set_refuse_new_connections(bool p_enable);
virtual bool is_refusing_new_connections() const;
@ -86,6 +92,7 @@ public:
};
VARIANT_ENUM_CAST(MultiplayerPeer::ConnectionStatus);
VARIANT_ENUM_CAST(MultiplayerPeer::TransferMode);
class MultiplayerPeerExtension : public MultiplayerPeer {
GDCLASS(MultiplayerPeerExtension, MultiplayerPeer);
@ -105,8 +112,8 @@ public:
/* MultiplayerPeer */
virtual void set_transfer_channel(int p_channel) override;
virtual int get_transfer_channel() const override;
virtual void set_transfer_mode(Multiplayer::TransferMode p_mode) override;
virtual Multiplayer::TransferMode get_transfer_mode() 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;
virtual int get_packet_peer() const override;

View File

@ -33,12 +33,12 @@
#include "core/config/project_settings.h"
#include "core/core_string_names.h"
#include "core/io/resource_loader.h"
#include "core/multiplayer/multiplayer_api.h"
#include "core/object/message_queue.h"
#include "core/string/print_string.h"
#include "instance_placeholder.h"
#include "scene/animation/tween.h"
#include "scene/debugger/scene_debugger.h"
#include "scene/main/multiplayer_api.h"
#include "scene/resources/packed_scene.h"
#include "scene/scene_string_names.h"
#include "viewport.h"
@ -582,35 +582,30 @@ bool Node::is_multiplayer_authority() const {
/***** RPC CONFIG ********/
uint16_t Node::rpc_config(const StringName &p_method, Multiplayer::RPCMode p_rpc_mode, bool p_call_local, Multiplayer::TransferMode p_transfer_mode, int p_channel) {
for (int i = 0; i < data.rpc_methods.size(); i++) {
if (data.rpc_methods[i].name == p_method) {
Multiplayer::RPCConfig &nd = data.rpc_methods.write[i];
nd.rpc_mode = p_rpc_mode;
nd.transfer_mode = p_transfer_mode;
nd.call_local = p_call_local;
nd.channel = p_channel;
return i | (1 << 15);
}
void Node::rpc_config(const StringName &p_method, const Variant &p_config) {
if (data.rpc_config.get_type() != Variant::DICTIONARY) {
data.rpc_config = Dictionary();
}
// New method
Multiplayer::RPCConfig nd;
nd.name = p_method;
nd.rpc_mode = p_rpc_mode;
nd.transfer_mode = p_transfer_mode;
nd.channel = p_channel;
nd.call_local = p_call_local;
data.rpc_methods.push_back(nd);
return ((uint16_t)data.rpc_methods.size() - 1) | (1 << 15);
Dictionary node_config = data.rpc_config;
if (p_config.get_type() == Variant::NIL) {
node_config.erase(p_method);
} else {
ERR_FAIL_COND(p_config.get_type() != Variant::DICTIONARY);
node_config[p_method] = p_config;
}
}
const Variant Node::get_node_rpc_config() const {
return data.rpc_config;
}
/***** RPC FUNCTIONS ********/
void Node::_rpc_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
Error Node::_rpc_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
if (p_argcount < 1) {
r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
r_error.argument = 1;
return;
return ERR_INVALID_PARAMETER;
}
Variant::Type type = p_args[0]->get_type();
@ -618,28 +613,28 @@ void Node::_rpc_bind(const Variant **p_args, int p_argcount, Callable::CallError
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 0;
r_error.expected = Variant::STRING_NAME;
return;
return ERR_INVALID_PARAMETER;
}
StringName method = (*p_args[0]).operator StringName();
rpcp(0, method, &p_args[1], p_argcount - 1);
Error err = rpcp(0, method, &p_args[1], p_argcount - 1);
r_error.error = Callable::CallError::CALL_OK;
return err;
}
void Node::_rpc_id_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
Error Node::_rpc_id_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
if (p_argcount < 2) {
r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
r_error.argument = 2;
return;
return ERR_INVALID_PARAMETER;
}
if (p_args[0]->get_type() != Variant::INT) {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 0;
r_error.expected = Variant::INT;
return;
return ERR_INVALID_PARAMETER;
}
Variant::Type type = p_args[1]->get_type();
@ -647,20 +642,35 @@ void Node::_rpc_id_bind(const Variant **p_args, int p_argcount, Callable::CallEr
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 1;
r_error.expected = Variant::STRING_NAME;
return;
return ERR_INVALID_PARAMETER;
}
int peer_id = *p_args[0];
StringName method = (*p_args[1]).operator StringName();
rpcp(peer_id, method, &p_args[2], p_argcount - 2);
Error err = rpcp(peer_id, method, &p_args[2], p_argcount - 2);
r_error.error = Callable::CallError::CALL_OK;
return err;
}
void Node::rpcp(int p_peer_id, const StringName &p_method, const Variant **p_arg, int p_argcount) {
ERR_FAIL_COND(!is_inside_tree());
get_multiplayer()->rpcp(this, p_peer_id, p_method, p_arg, p_argcount);
template <typename... VarArgs>
Error Node::rpc(const StringName &p_method, VarArgs... p_args) {
return rpc_id(0, p_method, p_args...);
}
template <typename... VarArgs>
Error Node::rpc_id(int p_peer_id, const StringName &p_method, VarArgs... p_args) {
Variant args[sizeof...(p_args) + 1] = { p_args..., Variant() }; // +1 makes sure zero sized arrays are also supported.
const Variant *argptrs[sizeof...(p_args) + 1];
for (uint32_t i = 0; i < sizeof...(p_args); i++) {
argptrs[i] = &args[i];
}
return rpcp(p_peer_id, p_method, sizeof...(p_args) == 0 ? nullptr : (const Variant **)argptrs, sizeof...(p_args));
}
Error Node::rpcp(int p_peer_id, const StringName &p_method, const Variant **p_arg, int p_argcount) {
ERR_FAIL_COND_V(!is_inside_tree(), ERR_UNCONFIGURED);
return get_multiplayer()->rpcp(this, p_peer_id, p_method, p_arg, p_argcount);
}
Ref<MultiplayerAPI> Node::get_multiplayer() const {
@ -670,10 +680,6 @@ Ref<MultiplayerAPI> Node::get_multiplayer() const {
return get_tree()->get_multiplayer(get_path());
}
Vector<Multiplayer::RPCConfig> Node::get_node_rpc_methods() const {
return data.rpc_methods;
}
//////////// end of rpc
bool Node::can_process_notification(int p_what) const {
@ -2888,7 +2894,7 @@ void Node::_bind_methods() {
ClassDB::bind_method(D_METHOD("is_multiplayer_authority"), &Node::is_multiplayer_authority);
ClassDB::bind_method(D_METHOD("get_multiplayer"), &Node::get_multiplayer);
ClassDB::bind_method(D_METHOD("rpc_config", "method", "rpc_mode", "call_local", "transfer_mode", "channel"), &Node::rpc_config, DEFVAL(false), DEFVAL(Multiplayer::TRANSFER_MODE_RELIABLE), DEFVAL(0));
ClassDB::bind_method(D_METHOD("rpc_config", "method", "config"), &Node::rpc_config);
ClassDB::bind_method(D_METHOD("set_editor_description", "editor_description"), &Node::set_editor_description);
ClassDB::bind_method(D_METHOD("get_editor_description"), &Node::get_editor_description);

View File

@ -127,7 +127,7 @@ private:
Node *process_owner = nullptr;
int multiplayer_authority = 1; // Server by default.
Vector<Multiplayer::RPCConfig> rpc_methods;
Variant rpc_config;
// Variables used to properly sort the node when processing, ignored otherwise.
// TODO: Should move all the stuff below to bits.
@ -183,8 +183,8 @@ private:
TypedArray<Node> _get_children(bool p_include_internal = true) const;
Array _get_groups() const;
void _rpc_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error);
void _rpc_id_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error);
Error _rpc_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error);
Error _rpc_id_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error);
_FORCE_INLINE_ bool _is_internal_front() const { return data.parent && data.pos < data.parent->data.internal_children_front; }
_FORCE_INLINE_ bool _is_internal_back() const { return data.parent && data.pos >= data.parent->data.children.size() - data.parent->data.internal_children_back; }
@ -491,30 +491,16 @@ public:
int get_multiplayer_authority() const;
bool is_multiplayer_authority() const;
uint16_t rpc_config(const StringName &p_method, Multiplayer::RPCMode p_rpc_mode, bool p_call_local = false, Multiplayer::TransferMode p_transfer_mode = Multiplayer::TRANSFER_MODE_RELIABLE, int p_channel = 0); // config a local method for RPC
Vector<Multiplayer::RPCConfig> get_node_rpc_methods() const;
void rpc_config(const StringName &p_method, const Variant &p_config); // config a local method for RPC
const Variant get_node_rpc_config() const;
template <typename... VarArgs>
void rpc(const StringName &p_method, VarArgs... p_args) {
Variant args[sizeof...(p_args) + 1] = { p_args..., Variant() }; // +1 makes sure zero sized arrays are also supported.
const Variant *argptrs[sizeof...(p_args) + 1];
for (uint32_t i = 0; i < sizeof...(p_args); i++) {
argptrs[i] = &args[i];
}
rpcp(0, p_method, sizeof...(p_args) == 0 ? nullptr : (const Variant **)argptrs, sizeof...(p_args));
}
Error rpc(const StringName &p_method, VarArgs... p_args);
template <typename... VarArgs>
void rpc_id(int p_peer_id, const StringName &p_method, VarArgs... p_args) {
Variant args[sizeof...(p_args) + 1] = { p_args..., Variant() }; // +1 makes sure zero sized arrays are also supported.
const Variant *argptrs[sizeof...(p_args) + 1];
for (uint32_t i = 0; i < sizeof...(p_args); i++) {
argptrs[i] = &args[i];
}
rpcp(p_peer_id, p_method, sizeof...(p_args) == 0 ? nullptr : (const Variant **)argptrs, sizeof...(p_args));
}
Error rpc_id(int p_peer_id, const StringName &p_method, VarArgs... p_args);
void rpcp(int p_peer_id, const StringName &p_method, const Variant **p_arg, int p_argcount);
Error rpcp(int p_peer_id, const StringName &p_method, const Variant **p_arg, int p_argcount);
Ref<MultiplayerAPI> get_multiplayer() const;

View File

@ -37,7 +37,6 @@
#include "core/io/image_loader.h"
#include "core/io/marshalls.h"
#include "core/io/resource_loader.h"
#include "core/multiplayer/multiplayer_api.h"
#include "core/object/message_queue.h"
#include "core/os/keyboard.h"
#include "core/os/os.h"
@ -45,6 +44,7 @@
#include "node.h"
#include "scene/animation/tween.h"
#include "scene/debugger/scene_debugger.h"
#include "scene/main/multiplayer_api.h"
#include "scene/main/viewport.h"
#include "scene/resources/font.h"
#include "scene/resources/material.h"
@ -1213,19 +1213,17 @@ void SceneTree::set_multiplayer(Ref<MultiplayerAPI> p_multiplayer, const NodePat
if (p_root_path.is_empty()) {
ERR_FAIL_COND(!p_multiplayer.is_valid());
if (multiplayer.is_valid()) {
multiplayer->set_root_path(NodePath());
multiplayer->object_configuration_remove(nullptr, NodePath("/" + root->get_name()));
}
multiplayer = p_multiplayer;
multiplayer->set_root_path("/" + root->get_name());
multiplayer->object_configuration_add(nullptr, NodePath("/" + root->get_name()));
} else {
if (custom_multiplayers.has(p_root_path)) {
custom_multiplayers[p_root_path]->object_configuration_remove(nullptr, p_root_path);
}
if (p_multiplayer.is_valid()) {
custom_multiplayers[p_root_path] = p_multiplayer;
p_multiplayer->set_root_path(p_root_path);
} else {
if (custom_multiplayers.has(p_root_path)) {
custom_multiplayers[p_root_path]->set_root_path(NodePath());
custom_multiplayers.erase(p_root_path);
}
p_multiplayer->object_configuration_add(nullptr, p_root_path);
}
}
}
@ -1415,7 +1413,7 @@ SceneTree::SceneTree() {
#endif // _3D_DISABLED
// Initialize network state.
set_multiplayer(Ref<MultiplayerAPI>(memnew(MultiplayerAPI)));
set_multiplayer(MultiplayerAPI::create_default_interface());
root->set_as_audio_listener_2d(true);
current_scene = nullptr;

View File

@ -1,5 +0,0 @@
#!/usr/bin/env python
Import("env")
env.add_source_files(env.scene_sources, "*.cpp")

View File

@ -130,16 +130,12 @@
#include "scene/main/http_request.h"
#include "scene/main/instance_placeholder.h"
#include "scene/main/missing_node.h"
#include "scene/main/multiplayer_api.h"
#include "scene/main/resource_preloader.h"
#include "scene/main/scene_tree.h"
#include "scene/main/timer.h"
#include "scene/main/viewport.h"
#include "scene/main/window.h"
#include "scene/multiplayer/multiplayer_spawner.h"
#include "scene/multiplayer/multiplayer_synchronizer.h"
#include "scene/multiplayer/scene_cache_interface.h"
#include "scene/multiplayer/scene_replication_interface.h"
#include "scene/multiplayer/scene_rpc_interface.h"
#include "scene/resources/animation_library.h"
#include "scene/resources/audio_stream_sample.h"
#include "scene/resources/bit_map.h"
@ -322,9 +318,13 @@ void register_scene_types() {
GDREGISTER_ABSTRACT_CLASS(Viewport);
GDREGISTER_CLASS(SubViewport);
GDREGISTER_CLASS(ViewportTexture);
GDREGISTER_ABSTRACT_CLASS(MultiplayerPeer);
GDREGISTER_CLASS(MultiplayerPeerExtension);
GDREGISTER_ABSTRACT_CLASS(MultiplayerAPI);
GDREGISTER_CLASS(MultiplayerAPIExtension);
GDREGISTER_CLASS(HTTPRequest);
GDREGISTER_CLASS(MultiplayerSpawner);
GDREGISTER_CLASS(MultiplayerSynchronizer);
GDREGISTER_CLASS(Timer);
GDREGISTER_CLASS(CanvasLayer);
GDREGISTER_CLASS(CanvasModulate);
@ -875,8 +875,6 @@ void register_scene_types() {
GDREGISTER_CLASS(LabelSettings);
GDREGISTER_CLASS(SceneReplicationConfig);
GDREGISTER_CLASS(TextLine);
GDREGISTER_CLASS(TextParagraph);
@ -1117,9 +1115,6 @@ void register_scene_types() {
}
SceneDebugger::initialize();
SceneReplicationInterface::make_default();
SceneRPCInterface::make_default();
SceneCacheInterface::make_default();
}
void initialize_theme() {

View File

@ -95,8 +95,8 @@ public:
Ref<Script> get_script() const override {
return Ref<Script>();
}
const Vector<Multiplayer::RPCConfig> get_rpc_methods() const override {
return Vector<Multiplayer::RPCConfig>();
const Variant get_rpc_config() const override {
return Variant();
}
ScriptLanguage *get_language() override {
return nullptr;