diff options
80 files changed, 1818 insertions, 1387 deletions
diff --git a/core/SCsub b/core/SCsub index 97080b8710..d4462fa546 100644 --- a/core/SCsub +++ b/core/SCsub @@ -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") diff --git a/core/core_constants.cpp b/core/core_constants.cpp index 4a2d09d2a0..dc0ab72a86 100644 --- a/core/core_constants.cpp +++ b/core/core_constants.cpp @@ -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); diff --git a/core/multiplayer/SCsub b/core/multiplayer/SCsub deleted file mode 100644 index 19a6549225..0000000000 --- a/core/multiplayer/SCsub +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env python - -Import("env") - -env.add_source_files(env.core_sources, "*.cpp") diff --git a/core/multiplayer/multiplayer_api.cpp b/core/multiplayer/multiplayer_api.cpp deleted file mode 100644 index 6cce31e0d1..0000000000 --- a/core/multiplayer/multiplayer_api.cpp +++ /dev/null @@ -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(); -} diff --git a/core/multiplayer/multiplayer_api.h b/core/multiplayer/multiplayer_api.h deleted file mode 100644 index 35452acb1f..0000000000 --- a/core/multiplayer/multiplayer_api.h +++ /dev/null @@ -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 diff --git a/core/object/script_language.h b/core/object/script_language.h index 686ab5b8d3..c9f8a4f828 100644 --- a/core/object/script_language.h +++ b/core/object/script_language.h @@ -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(); diff --git a/core/object/script_language_extension.cpp b/core/object/script_language_extension.cpp index ab8dd6d1ee..9de784ea7f 100644 --- a/core/object/script_language_extension.cpp +++ b/core/object/script_language_extension.cpp @@ -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() { diff --git a/core/object/script_language_extension.h b/core/object/script_language_extension.h index 2c53139ec2..10eacfd9f7 100644 --- a/core/object/script_language_extension.h +++ b/core/object/script_language_extension.h @@ -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() {} diff --git a/core/register_core_types.cpp b/core/register_core_types.cpp index aab4b9a580..1ae5d1c83f 100644 --- a/core/register_core_types.cpp +++ b/core/register_core_types.cpp @@ -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); diff --git a/core/variant/callable.cpp b/core/variant/callable.cpp index f20ec4037a..8b9b5f41de 100644 --- a/core/variant/callable.cpp +++ b/core/variant/callable.cpp @@ -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 { diff --git a/core/variant/callable.h b/core/variant/callable.h index bbcf5427ba..954365d010 100644 --- a/core/variant/callable.h +++ b/core/variant/callable.h @@ -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(); diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml index 30a2228294..709863b70f 100644 --- a/doc/classes/@GlobalScope.xml +++ b/doc/classes/@GlobalScope.xml @@ -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> diff --git a/doc/classes/MultiplayerAPI.xml b/doc/classes/MultiplayerAPI.xml index 059d147979..06658bf004 100644 --- a/doc/classes/MultiplayerAPI.xml +++ b/doc/classes/MultiplayerAPI.xml @@ -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> + Returns a new instance of the default MultiplayerAPI. + </description> + </method> + <method name="get_default_interface" qualifiers="static"> + <return type="StringName" /> <description> - Clears the current MultiplayerAPI network state (you shouldn't call this unless you know what you are doing). + 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" qualifiers="const"> + <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 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> - 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. + 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("")"> - 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> diff --git a/doc/classes/MultiplayerAPIExtension.xml b/doc/classes/MultiplayerAPIExtension.xml new file mode 100644 index 0000000000..c369977d57 --- /dev/null +++ b/doc/classes/MultiplayerAPIExtension.xml @@ -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) -> 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) -> 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) -> 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() -> MultiplayerPeer: + return base_multiplayer.multiplayer_peer + + func _get_unique_id() -> int: + return base_multiplayer.get_unique_id() + + func _get_peer_ids() -> 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> diff --git a/doc/classes/MultiplayerPeer.xml b/doc/classes/MultiplayerPeer.xml index 4a3559b0f7..6dde40f018 100644 --- a/doc/classes/MultiplayerPeer.xml +++ b/doc/classes/MultiplayerPeer.xml @@ -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> diff --git a/doc/classes/Node.xml b/doc/classes/Node.xml index b7591ed4f4..8cc8498609 100644 --- a/doc/classes/Node.xml +++ b/doc/classes/Node.xml @@ -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> diff --git a/doc/classes/ScriptExtension.xml b/doc/classes/ScriptExtension.xml index 91fa6206d7..4e432ca9a8 100644 --- a/doc/classes/ScriptExtension.xml +++ b/doc/classes/ScriptExtension.xml @@ -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> diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 6513028f33..455675b01d 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -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)); diff --git a/modules/enet/enet_multiplayer_peer.cpp b/modules/enet/enet_multiplayer_peer.cpp index cd94cc9425..dfdd08c9f4 100644 --- a/modules/enet/enet_multiplayer_peer.cpp +++ b/modules/enet/enet_multiplayer_peer.cpp @@ -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; diff --git a/modules/enet/enet_multiplayer_peer.h b/modules/enet/enet_multiplayer_peer.h index 131aa04df1..3152068d46 100644 --- a/modules/enet/enet_multiplayer_peer.h +++ b/modules/enet/enet_multiplayer_peer.h @@ -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> diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index a34bf6ef82..0216a710d7 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -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() { diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h index e9a206f48b..47de3ad088 100644 --- a/modules/gdscript/gdscript.h +++ b/modules/gdscript/gdscript.h @@ -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(); diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp index a1a28e7675..2c9150abde 100644 --- a/modules/gdscript/gdscript_byte_codegen.cpp +++ b/modules/gdscript/gdscript_byte_codegen.cpp @@ -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(); diff --git a/modules/gdscript/gdscript_byte_codegen.h b/modules/gdscript/gdscript_byte_codegen.h index ffc3178c83..7dd51845df 100644 --- a/modules/gdscript/gdscript_byte_codegen.h +++ b/modules/gdscript/gdscript_byte_codegen.h @@ -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 diff --git a/modules/gdscript/gdscript_codegen.h b/modules/gdscript/gdscript_codegen.h index 7b5f133ec5..5972481c3a 100644 --- a/modules/gdscript/gdscript_codegen.h +++ b/modules/gdscript/gdscript_codegen.h @@ -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 diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index e36252ada5..00e8223b9a 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -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; diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h index 53c75648a0..e44038d6da 100644 --- a/modules/gdscript/gdscript_function.h +++ b/modules/gdscript/gdscript_function.h @@ -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(); }; diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index e8fa2981ba..6f5397e1a3 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -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; } diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index 9c97f98fbc..d4efab173b 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -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(); diff --git a/modules/gdscript/gdscript_rpc_callable.cpp b/modules/gdscript/gdscript_rpc_callable.cpp index 63ebd8acf5..4e12419357 100644 --- a/modules/gdscript/gdscript_rpc_callable.cpp +++ b/modules/gdscript/gdscript_rpc_callable.cpp @@ -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); } diff --git a/modules/gdscript/gdscript_rpc_callable.h b/modules/gdscript/gdscript_rpc_callable.h index c8a91d6259..83b9c7e2df 100644 --- a/modules/gdscript/gdscript_rpc_callable.h +++ b/modules/gdscript/gdscript_rpc_callable.h @@ -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; diff --git a/modules/gdscript/language_server/gdscript_extend_parser.cpp b/modules/gdscript/language_server/gdscript_extend_parser.cpp index 03e93821c7..46a9b33eb0 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.cpp +++ b/modules/gdscript/language_server/gdscript_extend_parser.cpp @@ -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; diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index 1e4d82ca30..06793d25e0 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -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) { diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h index b17473470f..bd46a06a92 100644 --- a/modules/mono/csharp_script.h +++ b/modules/mono/csharp_script.h @@ -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); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/RPCAttribute.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/RPCAttribute.cs index 0a1c8322d7..fb37838ffa 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/RPCAttribute.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/RPCAttribute.cs @@ -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; } diff --git a/modules/multiplayer/SCsub b/modules/multiplayer/SCsub new file mode 100644 index 0000000000..ff33655537 --- /dev/null +++ b/modules/multiplayer/SCsub @@ -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 diff --git a/modules/multiplayer/config.py b/modules/multiplayer/config.py new file mode 100644 index 0000000000..414bf0afcf --- /dev/null +++ b/modules/multiplayer/config.py @@ -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" diff --git a/doc/classes/MultiplayerSpawner.xml b/modules/multiplayer/doc_classes/MultiplayerSpawner.xml index 9de67068eb..44ab34f52c 100644 --- a/doc/classes/MultiplayerSpawner.xml +++ b/modules/multiplayer/doc_classes/MultiplayerSpawner.xml @@ -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> diff --git a/doc/classes/MultiplayerSynchronizer.xml b/modules/multiplayer/doc_classes/MultiplayerSynchronizer.xml index 3766491a6c..ebd1b50201 100644 --- a/doc/classes/MultiplayerSynchronizer.xml +++ b/modules/multiplayer/doc_classes/MultiplayerSynchronizer.xml @@ -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> diff --git a/modules/multiplayer/doc_classes/SceneMultiplayer.xml b/modules/multiplayer/doc_classes/SceneMultiplayer.xml new file mode 100644 index 0000000000..0c3ed2d784 --- /dev/null +++ b/modules/multiplayer/doc_classes/SceneMultiplayer.xml @@ -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("")"> + 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> diff --git a/doc/classes/SceneReplicationConfig.xml b/modules/multiplayer/doc_classes/SceneReplicationConfig.xml index 62c108a477..1d6dec2f92 100644 --- a/doc/classes/SceneReplicationConfig.xml +++ b/modules/multiplayer/doc_classes/SceneReplicationConfig.xml @@ -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> diff --git a/editor/plugins/replication_editor_plugin.cpp b/modules/multiplayer/editor/replication_editor_plugin.cpp index 3e06a6739f..5891327db4 100644 --- a/editor/plugins/replication_editor_plugin.cpp +++ b/modules/multiplayer/editor/replication_editor_plugin.cpp @@ -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(); diff --git a/editor/plugins/replication_editor_plugin.h b/modules/multiplayer/editor/replication_editor_plugin.h index df3d97f884..57fa4c82fa 100644 --- a/editor/plugins/replication_editor_plugin.h +++ b/modules/multiplayer/editor/replication_editor_plugin.h @@ -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 diff --git a/scene/multiplayer/multiplayer_spawner.cpp b/modules/multiplayer/multiplayer_spawner.cpp index 8363d05e54..ca2fd1c023 100644 --- a/scene/multiplayer/multiplayer_spawner.cpp +++ b/modules/multiplayer/multiplayer_spawner.cpp @@ -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); } } diff --git a/scene/multiplayer/multiplayer_spawner.h b/modules/multiplayer/multiplayer_spawner.h index 2c0eb9a2f0..80bb878a74 100644 --- a/scene/multiplayer/multiplayer_spawner.h +++ b/modules/multiplayer/multiplayer_spawner.h @@ -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); diff --git a/scene/multiplayer/multiplayer_synchronizer.cpp b/modules/multiplayer/multiplayer_synchronizer.cpp index e1b7433968..621d62c4c7 100644 --- a/scene/multiplayer/multiplayer_synchronizer.cpp +++ b/modules/multiplayer/multiplayer_synchronizer.cpp @@ -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() { diff --git a/scene/multiplayer/multiplayer_synchronizer.h b/modules/multiplayer/multiplayer_synchronizer.h index 77c23b336d..e84d41db86 100644 --- a/scene/multiplayer/multiplayer_synchronizer.h +++ b/modules/multiplayer/multiplayer_synchronizer.h @@ -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); diff --git a/core/multiplayer/multiplayer.h b/modules/multiplayer/register_types.cpp index f4c965b0f8..a2c524da80 100644 --- a/core/multiplayer/multiplayer.h +++ b/modules/multiplayer/register_types.cpp @@ -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 "core/variant/binder_common.h" - -#include "core/string/string_name.h" - -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; +#include "register_types.h" + +#include "multiplayer_spawner.h" +#include "multiplayer_synchronizer.h" +#include "scene_multiplayer.h" +#include "scene_replication_interface.h" +#include "scene_rpc_interface.h" + +#ifdef TOOLS_ENABLED +#include "editor/editor_plugin.h" +#include "editor/replication_editor_plugin.h" +#endif + +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>(); } -}; - -}; // 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 +} -#endif // MULTIPLAYER_H +void uninitialize_multiplayer_module(ModuleInitializationLevel p_level) { +} diff --git a/modules/multiplayer/register_types.h b/modules/multiplayer/register_types.h new file mode 100644 index 0000000000..aca6cf46ed --- /dev/null +++ b/modules/multiplayer/register_types.h @@ -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 diff --git a/scene/multiplayer/scene_cache_interface.cpp b/modules/multiplayer/scene_cache_interface.cpp index 79a7dc2d5a..4dc01321b9 100644 --- a/scene/multiplayer/scene_cache_interface.cpp +++ b/modules/multiplayer/scene_cache_interface.cpp @@ -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. diff --git a/scene/multiplayer/scene_cache_interface.h b/modules/multiplayer/scene_cache_interface.h index 6bfd683cf4..1e80792fe7 100644 --- a/scene/multiplayer/scene_cache_interface.h +++ b/modules/multiplayer/scene_cache_interface.h @@ -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 diff --git a/modules/multiplayer/scene_multiplayer.cpp b/modules/multiplayer/scene_multiplayer.cpp new file mode 100644 index 0000000000..3fc1eef366 --- /dev/null +++ b/modules/multiplayer/scene_multiplayer.cpp @@ -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(); +} diff --git a/modules/multiplayer/scene_multiplayer.h b/modules/multiplayer/scene_multiplayer.h new file mode 100644 index 0000000000..a99cca7b21 --- /dev/null +++ b/modules/multiplayer/scene_multiplayer.h @@ -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 diff --git a/scene/resources/scene_replication_config.cpp b/modules/multiplayer/scene_replication_config.cpp index 6789f9f7d5..ae06516b7b 100644 --- a/scene/resources/scene_replication_config.cpp +++ b/modules/multiplayer/scene_replication_config.cpp @@ -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) { diff --git a/scene/resources/scene_replication_config.h b/modules/multiplayer/scene_replication_config.h index ab3658d2a7..ab3658d2a7 100644 --- a/scene/resources/scene_replication_config.h +++ b/modules/multiplayer/scene_replication_config.h diff --git a/scene/multiplayer/scene_replication_interface.cpp b/modules/multiplayer/scene_replication_interface.cpp index c616c5bb85..2dc0766fc3 100644 --- a/scene/multiplayer/scene_replication_interface.cpp +++ b/modules/multiplayer/scene_replication_interface.cpp @@ -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 { diff --git a/scene/multiplayer/scene_replication_interface.h b/modules/multiplayer/scene_replication_interface.h index 9ddab2d383..8981647429 100644 --- a/scene/multiplayer/scene_replication_interface.h +++ b/modules/multiplayer/scene_replication_interface.h @@ -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; } diff --git a/scene/multiplayer/scene_replication_state.cpp b/modules/multiplayer/scene_replication_state.cpp index f6a51ff9c7..5ad27e9a97 100644 --- a/scene/multiplayer/scene_replication_state.cpp +++ b/modules/multiplayer/scene_replication_state.cpp @@ -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); diff --git a/scene/multiplayer/scene_replication_state.h b/modules/multiplayer/scene_replication_state.h index 01b70b11b8..bdff6ae3b7 100644 --- a/scene/multiplayer/scene_replication_state.h +++ b/modules/multiplayer/scene_replication_state.h @@ -33,6 +33,9 @@ #include "core/object/ref_counted.h" +#include "multiplayer_spawner.h" +#include "multiplayer_synchronizer.h" + class MultiplayerSpawner; class MultiplayerSynchronizer; class Node; diff --git a/scene/multiplayer/scene_rpc_interface.cpp b/modules/multiplayer/scene_rpc_interface.cpp index 144a10c665..65090b9316 100644 --- a/scene/multiplayer/scene_rpc_interface.cpp +++ b/modules/multiplayer/scene_rpc_interface.cpp @@ -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; } - 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]; - } + 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; } - return Multiplayer::RPCConfig(); } -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(); +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]; } - if (id < config.size()) { - return config[id]; + RPCConfigCache cache; + _parse_rpc_config(p_node->get_node_rpc_config(), true, cache); + if (p_node->get_script_instance()) { + _parse_rpc_config(p_node->get_script_instance()->get_rpc_config(), false, cache); } - return Multiplayer::RPCConfig(); + rpc_cache[oid] = cache; + return rpc_cache[oid]; } -_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; } diff --git a/scene/multiplayer/scene_rpc_interface.h b/modules/multiplayer/scene_rpc_interface.h index 86e1d0d280..aa9be525a2 100644 --- a/scene/multiplayer/scene_rpc_interface.h +++ b/modules/multiplayer/scene_rpc_interface.h @@ -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); -public: - static void make_default(); + void _parse_rpc_config(const Variant &p_config, bool p_for_node, RPCConfigCache &r_cache); + const RPCConfigCache &_get_node_config(const Node *p_node); - 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; +public: + 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); - SceneRPCInterface(MultiplayerAPI *p_multiplayer) { multiplayer = p_multiplayer; } + SceneRPCInterface(SceneMultiplayer *p_multiplayer) { multiplayer = p_multiplayer; } }; #endif // SCENE_RPC_INTERFACE_H diff --git a/modules/visual_script/visual_script.cpp b/modules/visual_script/visual_script.cpp index 742fa75bb7..1ef3723011 100644 --- a/modules/visual_script/visual_script.cpp +++ b/modules/visual_script/visual_script.cpp @@ -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) { diff --git a/modules/visual_script/visual_script.h b/modules/visual_script/visual_script.h index 716310f59b..14cb14e8d9 100644 --- a/modules/visual_script/visual_script.h +++ b/modules/visual_script/visual_script.h @@ -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(); diff --git a/modules/visual_script/visual_script_nodes.cpp b/modules/visual_script/visual_script_nodes.cpp index 41f5b28677..5907e6a489 100644 --- a/modules/visual_script/visual_script_nodes.cpp +++ b/modules/visual_script/visual_script_nodes.cpp @@ -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) { diff --git a/modules/visual_script/visual_script_nodes.h b/modules/visual_script/visual_script_nodes.h index 18573f8682..35e3c490cd 100644 --- a/modules/visual_script/visual_script_nodes.h +++ b/modules/visual_script/visual_script_nodes.h @@ -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; diff --git a/modules/webrtc/doc_classes/WebRTCMultiplayerPeer.xml b/modules/webrtc/doc_classes/WebRTCMultiplayerPeer.xml index 3996a002ed..df92097135 100644 --- a/modules/webrtc/doc_classes/WebRTCMultiplayerPeer.xml +++ b/modules/webrtc/doc_classes/WebRTCMultiplayerPeer.xml @@ -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"> diff --git a/modules/webrtc/webrtc_multiplayer_peer.cpp b/modules/webrtc/webrtc_multiplayer_peer.cpp index 6f68b84ad3..e03b6b2473 100644 --- a/modules/webrtc/webrtc_multiplayer_peer.cpp +++ b/modules/webrtc/webrtc_multiplayer_peer.cpp @@ -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; } diff --git a/modules/webrtc/webrtc_multiplayer_peer.h b/modules/webrtc/webrtc_multiplayer_peer.h index d4ed7ef7de..ea7c60036b 100644 --- a/modules/webrtc/webrtc_multiplayer_peer.h +++ b/modules/webrtc/webrtc_multiplayer_peer.h @@ -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 { diff --git a/modules/websocket/websocket_multiplayer_peer.h b/modules/websocket/websocket_multiplayer_peer.h index db529a669d..3259e78b3b 100644 --- a/modules/websocket/websocket_multiplayer_peer.h +++ b/modules/websocket/websocket_multiplayer_peer.h @@ -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 { diff --git a/scene/SCsub b/scene/SCsub index a7b23af598..92288211bb 100644 --- a/scene/SCsub +++ b/scene/SCsub @@ -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") diff --git a/scene/main/multiplayer_api.cpp b/scene/main/multiplayer_api.cpp new file mode 100644 index 0000000000..95574042a8 --- /dev/null +++ b/scene/main/multiplayer_api.cpp @@ -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"); +} diff --git a/scene/main/multiplayer_api.h b/scene/main/multiplayer_api.h new file mode 100644 index 0000000000..c1d90d651e --- /dev/null +++ b/scene/main/multiplayer_api.h @@ -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 diff --git a/core/multiplayer/multiplayer_peer.cpp b/scene/main/multiplayer_peer.cpp index c7de7a1313..aad5baccab 100644 --- a/core/multiplayer/multiplayer_peer.cpp +++ b/scene/main/multiplayer_peer.cpp @@ -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(); } diff --git a/core/multiplayer/multiplayer_peer.h b/scene/main/multiplayer_peer.h index 91546832ce..8a012d7520 100644 --- a/core/multiplayer/multiplayer_peer.h +++ b/scene/main/multiplayer_peer.h @@ -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; diff --git a/scene/main/node.cpp b/scene/main/node.cpp index b4701637a4..4bf1936a65 100644 --- a/scene/main/node.cpp +++ b/scene/main/node.cpp @@ -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(); + } + 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; } - // 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); +} + +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); diff --git a/scene/main/node.h b/scene/main/node.h index d978c03442..0645c68eb9 100644 --- a/scene/main/node.h +++ b/scene/main/node.h @@ -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; diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp index 66482f65dc..644fb3e9cc 100644 --- a/scene/main/scene_tree.cpp +++ b/scene/main/scene_tree.cpp @@ -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; diff --git a/scene/multiplayer/SCsub b/scene/multiplayer/SCsub deleted file mode 100644 index fc61250247..0000000000 --- a/scene/multiplayer/SCsub +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env python - -Import("env") - -env.add_source_files(env.scene_sources, "*.cpp") diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index 09a283ea53..89e2327fc8 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -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() { diff --git a/tests/core/object/test_object.h b/tests/core/object/test_object.h index 5b9d9cab53..88a3e4ccad 100644 --- a/tests/core/object/test_object.h +++ b/tests/core/object/test_object.h @@ -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; |