diff --git a/core/SCsub b/core/SCsub index d9167b8f83..14dfa3487f 100644 --- a/core/SCsub +++ b/core/SCsub @@ -183,6 +183,7 @@ 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 4f3f1fd16e..ef3d9c346b 100644 --- a/core/core_constants.cpp +++ b/core/core_constants.cpp @@ -31,6 +31,7 @@ #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" @@ -609,6 +610,15 @@ 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", Multiplayer::RPC_MODE_ANY); + BIND_CORE_ENUM_CONSTANT_CUSTOM("RPC_MODE_AUTH", Multiplayer::RPC_MODE_AUTHORITY); + + BIND_CORE_ENUM_CONSTANT_CUSTOM("TRANSFER_MODE_UNRELIABLE", Multiplayer::TRANSFER_MODE_UNRELIABLE); + BIND_CORE_ENUM_CONSTANT_CUSTOM("TRANSFER_MODE_ORDERED", Multiplayer::TRANSFER_MODE_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 new file mode 100644 index 0000000000..19a6549225 --- /dev/null +++ b/core/multiplayer/SCsub @@ -0,0 +1,5 @@ +#!/usr/bin/env python + +Import("env") + +env.add_source_files(env.core_sources, "*.cpp") diff --git a/core/multiplayer/multiplayer.h b/core/multiplayer/multiplayer.h new file mode 100644 index 0000000000..8ddad61dd0 --- /dev/null +++ b/core/multiplayer/multiplayer.h @@ -0,0 +1,80 @@ +/*************************************************************************/ +/* multiplayer.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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_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_ORDERED, + TRANSFER_MODE_RELIABLE +}; + +enum RPCMode { + RPC_MODE_DISABLED, // No rpc for this method, calls to this will be blocked (default) + RPC_MODE_ANY, // Any peer can call this rpc() + RPC_MODE_AUTHORITY, // / Only the node's network authority (server by default) can call this rpc() +}; + +struct RPCConfig { + StringName name; + RPCMode rpc_mode = RPC_MODE_DISABLED; + bool sync = false; + TransferMode transfer_mode = TRANSFER_MODE_RELIABLE; + int channel = 0; + + bool operator==(RPCConfig const &p_other) const { + return name == p_other.name; + } +}; + +struct SortRPCConfig { + StringName::AlphCompare compare; + bool operator()(const RPCConfig &p_a, const RPCConfig &p_b) const { + return compare(p_a.name, p_b.name); + } +}; + +}; // namespace Multiplayer + +// This is needed for proper docs generation (i.e. not "Multiplayer."-prefixed). +typedef Multiplayer::RPCMode RPCMode; +typedef Multiplayer::TransferMode TransferMode; + +VARIANT_ENUM_CAST(RPCMode); +VARIANT_ENUM_CAST(TransferMode); + +#endif // MULTIPLAYER_H diff --git a/core/io/multiplayer_api.cpp b/core/multiplayer/multiplayer_api.cpp similarity index 54% rename from core/io/multiplayer_api.cpp rename to core/multiplayer/multiplayer_api.cpp index c145225751..1130fcf9cb 100644 --- a/core/io/multiplayer_api.cpp +++ b/core/multiplayer/multiplayer_api.cpp @@ -32,7 +32,8 @@ #include "core/debugger/engine_debugger.h" #include "core/io/marshalls.h" -#include "core/io/multiplayer_replicator.h" +#include "core/multiplayer/multiplayer_replicator.h" +#include "core/multiplayer/rpc_manager.h" #include "scene/main/node.h" #include @@ -41,71 +42,18 @@ #include "core/os/os.h" #endif -String _get_rpc_md5(const Node *p_node) { - String rpc_list; - const Vector node_config = p_node->get_node_rpc_methods(); - for (int i = 0; i < node_config.size(); i++) { - rpc_list += String(node_config[i].name); +#ifdef DEBUG_ENABLED +void MultiplayerAPI::profile_bandwidth(const String &p_inout, int p_size) { + if (EngineDebugger::is_profiling("multiplayer")) { + Array values; + values.push_back("bandwidth"); + 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); } - if (p_node->get_script_instance()) { - const Vector script_config = p_node->get_script_instance()->get_rpc_methods(); - for (int i = 0; i < script_config.size(); i++) { - rpc_list += String(script_config[i].name); - } - } - return rpc_list.md5_text(); -} - -const MultiplayerAPI::RPCConfig _get_rpc_config(const Node *p_node, const StringName &p_method, uint16_t &r_id) { - const Vector 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]; - } - } - if (p_node->get_script_instance()) { - const Vector 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]; - } - } - } - return MultiplayerAPI::RPCConfig(); -} - -const MultiplayerAPI::RPCConfig _get_rpc_config_by_id(Node *p_node, uint16_t p_id) { - Vector config; - uint16_t id = p_id; - if (id & (1 << 15)) { - id = id & ~(1 << 15); - config = p_node->get_node_rpc_methods(); - } else if (p_node->get_script_instance()) { - config = p_node->get_script_instance()->get_rpc_methods(); - } - if (id < config.size()) { - return config[id]; - } - return MultiplayerAPI::RPCConfig(); -} - -_FORCE_INLINE_ bool _can_call_mode(Node *p_node, MultiplayerAPI::RPCMode mode, int p_remote_id) { - switch (mode) { - case MultiplayerAPI::RPC_MODE_DISABLED: { - return false; - } break; - case MultiplayerAPI::RPC_MODE_ANY: { - return true; - } break; - case MultiplayerAPI::RPC_MODE_AUTHORITY: { - return !p_node->is_network_authority() && p_remote_id == p_node->get_network_authority(); - } break; - } - - return false; } +#endif void MultiplayerAPI::poll() { if (!network_peer.is_valid() || network_peer->get_connection_status() == MultiplayerPeer::CONNECTION_DISCONNECTED) { @@ -129,9 +77,9 @@ void MultiplayerAPI::poll() { break; // Something is wrong! } - rpc_sender_id = sender; + remote_sender_id = sender; _process_packet(sender, packet, len); - rpc_sender_id = 0; + remote_sender_id = 0; if (!network_peer.is_valid()) { break; // It's also possible that a packet or RPC caused a disconnection, so also check here. @@ -191,49 +139,16 @@ Ref MultiplayerAPI::get_network_peer() const { return network_peer; } -#ifdef DEBUG_ENABLED -void _profile_node_data(const String &p_what, ObjectID p_id) { - if (EngineDebugger::is_profiling("multiplayer")) { - Array values; - values.push_back("node"); - values.push_back(p_id); - values.push_back(p_what); - EngineDebugger::profiler_add_frame_data("multiplayer", values); - } -} - -void _profile_bandwidth_data(const String &p_inout, int p_size) { - if (EngineDebugger::is_profiling("multiplayer")) { - Array values; - values.push_back("bandwidth"); - 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 - -// Returns the packet size stripping the node path added when the node is not yet cached. -int get_packet_len(uint32_t p_node_target, int p_packet_len) { - if (p_node_target & 0x80000000) { - int ofs = p_node_target & 0x7FFFFFFF; - return p_packet_len - (p_packet_len - ofs); - } else { - return p_packet_len; - } -} - void MultiplayerAPI::_process_packet(int p_from, const uint8_t *p_packet, int p_packet_len) { ERR_FAIL_COND_MSG(root_node == nullptr, "Multiplayer root node was not initialized. If you are using custom multiplayer, remember to set the root node via MultiplayerAPI.set_root_node before using it."); ERR_FAIL_COND_MSG(p_packet_len < 1, "Invalid packet received. Size too small."); #ifdef DEBUG_ENABLED - _profile_bandwidth_data("in", p_packet_len); + profile_bandwidth("in", p_packet_len); #endif // Extract the `packet_type` from the LSB three bits: - uint8_t packet_type = p_packet[0] & 7; + uint8_t packet_type = p_packet[0] & CMD_MASK; switch (packet_type) { case NETWORK_COMMAND_SIMPLIFY_PATH: { @@ -245,76 +160,7 @@ void MultiplayerAPI::_process_packet(int p_from, const uint8_t *p_packet, int p_ } break; case NETWORK_COMMAND_REMOTE_CALL: { - // Extract packet meta - int packet_min_size = 1; - int name_id_offset = 1; - ERR_FAIL_COND_MSG(p_packet_len < packet_min_size, "Invalid packet received. Size too small."); - // Compute the meta size, which depends on the compression level. - int node_id_compression = (p_packet[0] & 24) >> NODE_ID_COMPRESSION_SHIFT; - int name_id_compression = (p_packet[0] & 32) >> NAME_ID_COMPRESSION_SHIFT; - - switch (node_id_compression) { - case NETWORK_NODE_ID_COMPRESSION_8: - packet_min_size += 1; - name_id_offset += 1; - break; - case NETWORK_NODE_ID_COMPRESSION_16: - packet_min_size += 2; - name_id_offset += 2; - break; - case NETWORK_NODE_ID_COMPRESSION_32: - packet_min_size += 4; - name_id_offset += 4; - break; - default: - ERR_FAIL_MSG("Was not possible to extract the node id compression mode."); - } - switch (name_id_compression) { - case NETWORK_NAME_ID_COMPRESSION_8: - packet_min_size += 1; - break; - case NETWORK_NAME_ID_COMPRESSION_16: - packet_min_size += 2; - break; - default: - ERR_FAIL_MSG("Was not possible to extract the name id compression mode."); - } - ERR_FAIL_COND_MSG(p_packet_len < packet_min_size, "Invalid packet received. Size too small."); - - uint32_t node_target = 0; - switch (node_id_compression) { - case NETWORK_NODE_ID_COMPRESSION_8: - node_target = p_packet[1]; - break; - case NETWORK_NODE_ID_COMPRESSION_16: - node_target = decode_uint16(p_packet + 1); - break; - case NETWORK_NODE_ID_COMPRESSION_32: - node_target = decode_uint32(p_packet + 1); - break; - default: - // Unreachable, checked before. - CRASH_NOW(); - } - - Node *node = _process_get_node(p_from, p_packet, node_target, p_packet_len); - ERR_FAIL_COND_MSG(node == nullptr, "Invalid packet received. Requested node was not found."); - - uint16_t name_id = 0; - switch (name_id_compression) { - case NETWORK_NAME_ID_COMPRESSION_8: - name_id = p_packet[name_id_offset]; - break; - case NETWORK_NAME_ID_COMPRESSION_16: - name_id = decode_uint16(p_packet + name_id_offset); - break; - default: - // Unreachable, checked before. - CRASH_NOW(); - } - - const int packet_len = get_packet_len(node_target, p_packet_len); - _process_rpc(node, name_id, p_from, p_packet, packet_len, packet_min_size); + rpc_manager->process_rpc(p_from, p_packet, p_packet_len); } break; case NETWORK_COMMAND_RAW: { @@ -332,101 +178,6 @@ void MultiplayerAPI::_process_packet(int p_from, const uint8_t *p_packet, int p_ } } -Node *MultiplayerAPI::_process_get_node(int p_from, const uint8_t *p_packet, uint32_t p_node_target, int p_packet_len) { - Node *node = nullptr; - - if (p_node_target & 0x80000000) { - // Use full path (not cached yet). - int ofs = p_node_target & 0x7FFFFFFF; - - ERR_FAIL_COND_V_MSG(ofs >= p_packet_len, nullptr, "Invalid packet received. Size smaller than declared."); - - String paths; - paths.parse_utf8((const char *)&p_packet[ofs], p_packet_len - ofs); - - NodePath np = paths; - - node = root_node->get_node(np); - - if (!node) { - ERR_PRINT("Failed to get path from RPC: " + String(np) + "."); - } - return node; - } else { - // Use cached path. - return get_cached_node(p_from, p_node_target); - } -} - -void MultiplayerAPI::_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) { - 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 RPCConfig config = _get_rpc_config_by_id(p_node, p_rpc_method_id); - ERR_FAIL_COND(config.name == StringName()); - - 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_network_authority()) + "."); - - int argc = 0; - bool byte_only = false; - - const bool byte_only_or_no_args = ((p_packet[0] & 64) >> BYTE_ONLY_OR_NO_ARGS_SHIFT) == 1; - if (byte_only_or_no_args) { - if (p_offset < p_packet_len) { - // This packet contains only bytes. - argc = 1; - byte_only = true; - } else { - // This rpc calls a method without parameters. - } - } else { - // Normal variant, takes the argument count from the packet. - ERR_FAIL_COND_MSG(p_offset >= p_packet_len, "Invalid packet received. Size too small."); - argc = p_packet[p_offset]; - p_offset += 1; - } - - Vector args; - Vector argp; - args.resize(argc); - argp.resize(argc); - -#ifdef DEBUG_ENABLED - _profile_node_data("in_rpc", p_node->get_instance_id()); -#endif - - if (byte_only) { - Vector pure_data; - const int len = p_packet_len - p_offset; - pure_data.resize(len); - memcpy(pure_data.ptrw(), &p_packet[p_offset], len); - args.write[0] = pure_data; - argp.write[0] = &args[0]; - p_offset += len; - } else { - for (int i = 0; i < argc; i++) { - ERR_FAIL_COND_MSG(p_offset >= p_packet_len, "Invalid packet received. Size too small."); - - int vlen; - Error err = decode_and_decompress_variant(args.write[i], &p_packet[p_offset], p_packet_len - p_offset, &vlen); - ERR_FAIL_COND_MSG(err != OK, "Invalid packet received. Unable to decode RPC argument."); - - argp.write[i] = &args[i]; - p_offset += vlen; - } - } - - Callable::CallError ce; - - p_node->call(config.name, (const Variant **)argp.ptr(), argc, ce); - if (ce.error != Callable::CallError::CALL_OK) { - String error = Variant::get_call_error_text(p_node, config.name, (const Variant **)argp.ptr(), argc, ce); - error = "RPC - " + error; - ERR_PRINT(error); - } -} - void MultiplayerAPI::_process_simplify_path(int p_from, const uint8_t *p_packet, int p_packet_len) { ERR_FAIL_COND_MSG(p_packet_len < 38, "Invalid packet received. Size too small."); int ofs = 1; @@ -449,7 +200,7 @@ void MultiplayerAPI::_process_simplify_path(int p_from, const uint8_t *p_packet, Node *node = root_node->get_node(path); ERR_FAIL_COND(node == nullptr); - const bool valid_rpc_checksum = _get_rpc_md5(node) == methods_md5; + const bool valid_rpc_checksum = rpc_manager->get_rpc_md5(node) == methods_md5; if (valid_rpc_checksum == false) { ERR_PRINT("The rpc node checksum failed. Make sure to have the same methods on both nodes. Node path: " + path); } @@ -471,7 +222,7 @@ void MultiplayerAPI::_process_simplify_path(int p_from, const uint8_t *p_packet, encode_cstring(pname.get_data(), &packet.write[2]); network_peer->set_transfer_channel(0); - network_peer->set_transfer_mode(MultiplayerPeer::TRANSFER_MODE_RELIABLE); + network_peer->set_transfer_mode(Multiplayer::TRANSFER_MODE_RELIABLE); network_peer->set_target_peer(p_from); network_peer->put_packet(packet.ptr(), packet.size()); } @@ -532,7 +283,7 @@ bool MultiplayerAPI::_send_confirm_path(Node *p_node, NodePath p_path, PathSentC const int path_len = encode_cstring(path.get_data(), nullptr); // Extract MD5 from rpc methods list. - const String methods_md5 = _get_rpc_md5(p_node); + const String methods_md5 = rpc_manager->get_rpc_md5(p_node); const int methods_md5_len = 33; // 32 + 1 for the `0` that is added by the encoder. Vector packet; @@ -551,7 +302,7 @@ bool MultiplayerAPI::_send_confirm_path(Node *p_node, NodePath p_path, PathSentC for (int &E : peers_to_add) { network_peer->set_target_peer(E); // To all of you. network_peer->set_transfer_channel(0); - network_peer->set_transfer_mode(MultiplayerPeer::TRANSFER_MODE_RELIABLE); + network_peer->set_transfer_mode(Multiplayer::TRANSFER_MODE_RELIABLE); network_peer->put_packet(packet.ptr(), packet.size()); psc->confirmed_peers.insert(E, false); // Insert into confirmed, but as false since it was not confirmed. @@ -716,188 +467,6 @@ Error MultiplayerAPI::decode_and_decompress_variant(Variant &r_variant, const ui return OK; } -void MultiplayerAPI::_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) { - ERR_FAIL_COND_MSG(network_peer.is_null(), "Attempt to remote call/set when networking is not active in SceneTree."); - - ERR_FAIL_COND_MSG(network_peer->get_connection_status() == MultiplayerPeer::CONNECTION_CONNECTING, "Attempt to remote call/set when networking is not connected yet in SceneTree."); - - ERR_FAIL_COND_MSG(network_peer->get_connection_status() == MultiplayerPeer::CONNECTION_DISCONNECTED, "Attempt to remote call/set when networking is disconnected."); - - ERR_FAIL_COND_MSG(p_argcount > 255, "Too many arguments >255."); - - if (p_to != 0 && !connected_peers.has(ABS(p_to))) { - ERR_FAIL_COND_MSG(p_to == network_peer->get_unique_id(), "Attempt to remote call/set yourself! unique ID: " + itos(network_peer->get_unique_id()) + "."); - - ERR_FAIL_MSG("Attempt to remote call unexisting ID: " + itos(p_to) + "."); - } - - NodePath from_path = (root_node->get_path()).rel_path_to(p_from->get_path()); - ERR_FAIL_COND_MSG(from_path.is_empty(), "Unable to send RPC. Relative path is empty. THIS IS LIKELY A BUG IN THE ENGINE!"); - - // See if the path is cached. - PathSentCache *psc = path_send_cache.getptr(from_path); - if (!psc) { - // Path is not cached, create. - path_send_cache[from_path] = PathSentCache(); - psc = path_send_cache.getptr(from_path); - psc->id = last_send_cache_id++; - } - - // See if all peers have cached path (if so, call can be fast). - const bool has_all_peers = _send_confirm_path(p_from, from_path, psc, p_to); - - // Create base packet, lots of hardcode because it must be tight. - - int ofs = 0; - -#define MAKE_ROOM(m_amount) \ - if (packet_cache.size() < m_amount) \ - packet_cache.resize(m_amount); - - // Encode meta. - // The meta is composed by a single byte that contains (starting from the least significant bit): - // - `NetworkCommands` in the first three bits. - // - `NetworkNodeIdCompression` in the next 2 bits. - // - `NetworkNameIdCompression` in the next 1 bit. - // - `byte_only_or_no_args` in the next 1 bit. - // - So we still have the last bit free! - uint8_t command_type = 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; - - MAKE_ROOM(1); - // The meta is composed along the way, so just set 0 for now. - packet_cache.write[0] = 0; - ofs += 1; - - // Encode Node ID. - if (has_all_peers) { - // Compress the node ID only if all the target peers already know it. - if (psc->id >= 0 && psc->id <= 255) { - // We can encode the id in 1 byte - node_id_compression = NETWORK_NODE_ID_COMPRESSION_8; - MAKE_ROOM(ofs + 1); - packet_cache.write[ofs] = static_cast(psc->id); - ofs += 1; - } else if (psc->id >= 0 && psc->id <= 65535) { - // We can encode the id in 2 bytes - node_id_compression = NETWORK_NODE_ID_COMPRESSION_16; - MAKE_ROOM(ofs + 2); - encode_uint16(static_cast(psc->id), &(packet_cache.write[ofs])); - ofs += 2; - } else { - // Too big, let's use 4 bytes. - node_id_compression = NETWORK_NODE_ID_COMPRESSION_32; - MAKE_ROOM(ofs + 4); - encode_uint32(psc->id, &(packet_cache.write[ofs])); - ofs += 4; - } - } else { - // The targets don't know the node yet, so we need to use 32 bits int. - node_id_compression = NETWORK_NODE_ID_COMPRESSION_32; - MAKE_ROOM(ofs + 4); - encode_uint32(psc->id, &(packet_cache.write[ofs])); - ofs += 4; - } - - // Encode method ID - if (p_rpc_id <= UINT8_MAX) { - // The ID fits in 1 byte - name_id_compression = NETWORK_NAME_ID_COMPRESSION_8; - MAKE_ROOM(ofs + 1); - packet_cache.write[ofs] = static_cast(p_rpc_id); - ofs += 1; - } else { - // The ID is larger, let's use 2 bytes - name_id_compression = NETWORK_NAME_ID_COMPRESSION_16; - MAKE_ROOM(ofs + 2); - encode_uint16(p_rpc_id, &(packet_cache.write[ofs])); - ofs += 2; - } - - if (p_argcount == 0) { - byte_only_or_no_args = true; - } else if (p_argcount == 1 && p_arg[0]->get_type() == Variant::PACKED_BYTE_ARRAY) { - byte_only_or_no_args = true; - // Special optimization when only the byte vector is sent. - const Vector data = *p_arg[0]; - MAKE_ROOM(ofs + data.size()); - memcpy(&(packet_cache.write[ofs]), data.ptr(), sizeof(uint8_t) * data.size()); - ofs += data.size(); - } else { - // Arguments - MAKE_ROOM(ofs + 1); - packet_cache.write[ofs] = p_argcount; - ofs += 1; - for (int i = 0; i < p_argcount; i++) { - int len(0); - Error err = encode_and_compress_variant(*p_arg[i], nullptr, len); - ERR_FAIL_COND_MSG(err != OK, "Unable to encode RPC argument. THIS IS LIKELY A BUG IN THE ENGINE!"); - MAKE_ROOM(ofs + len); - encode_and_compress_variant(*p_arg[i], &(packet_cache.write[ofs]), len); - ofs += len; - } - } - - ERR_FAIL_COND(command_type > 7); - ERR_FAIL_COND(node_id_compression > 3); - ERR_FAIL_COND(name_id_compression > 1); - - // We can now set the meta - packet_cache.write[0] = command_type + (node_id_compression << NODE_ID_COMPRESSION_SHIFT) + (name_id_compression << NAME_ID_COMPRESSION_SHIFT) + ((byte_only_or_no_args ? 1 : 0) << BYTE_ONLY_OR_NO_ARGS_SHIFT); - -#ifdef DEBUG_ENABLED - _profile_bandwidth_data("out", ofs); -#endif - - // Take chance and set transfer mode, since all send methods will use it. - network_peer->set_transfer_channel(p_config.channel); - network_peer->set_transfer_mode(p_config.transfer_mode); - - if (has_all_peers) { - // They all have verified paths, so send fast. - network_peer->set_target_peer(p_to); // To all of you. - network_peer->put_packet(packet_cache.ptr(), ofs); // A message with love. - } else { - // Unreachable because the node ID is never compressed if the peers doesn't know it. - CRASH_COND(node_id_compression != NETWORK_NODE_ID_COMPRESSION_32); - - // Not all verified path, so send one by one. - - // Append path at the end, since we will need it for some packets. - CharString pname = String(from_path).utf8(); - int path_len = encode_cstring(pname.get_data(), nullptr); - MAKE_ROOM(ofs + path_len); - encode_cstring(pname.get_data(), &(packet_cache.write[ofs])); - - for (Set::Element *E = connected_peers.front(); E; E = E->next()) { - if (p_to < 0 && E->get() == -p_to) { - continue; // Continue, excluded. - } - - if (p_to > 0 && E->get() != p_to) { - continue; // Continue, not for this peer. - } - - Map::Element *F = psc->confirmed_peers.find(E->get()); - ERR_CONTINUE(!F); // Should never happen. - - network_peer->set_target_peer(E->get()); // To this one specifically. - - if (F->get()) { - // This one confirmed path, so use id. - encode_uint32(psc->id, &(packet_cache.write[1])); - network_peer->put_packet(packet_cache.ptr(), ofs); - } else { - // This one did not confirm path yet, so use entire path (sorry!). - encode_uint32(0x80000000 | ofs, &(packet_cache.write[1])); // Offset to path and flag. - network_peer->put_packet(packet_cache.ptr(), ofs + path_len); - } - } - } -} - void MultiplayerAPI::_add_peer(int p_id) { connected_peers.insert(p_id); path_get_cache.insert(p_id, PathGetCache()); @@ -934,72 +503,15 @@ void MultiplayerAPI::_server_disconnected() { emit_signal(SNAME("server_disconnected")); } -void MultiplayerAPI::rpcp(Node *p_node, int p_peer_id, bool p_unreliable, const StringName &p_method, const Variant **p_arg, int p_argcount) { - ERR_FAIL_COND_MSG(!network_peer.is_valid(), "Trying to call an RPC while no network peer is active."); - ERR_FAIL_COND_MSG(!p_node->is_inside_tree(), "Trying to call an RPC on a node which is not inside SceneTree."); - ERR_FAIL_COND_MSG(network_peer->get_connection_status() != MultiplayerPeer::CONNECTION_CONNECTED, "Trying to call an RPC via a network peer which is not connected."); - - int node_id = network_peer->get_unique_id(); - bool call_local_native = false; - bool call_local_script = false; - uint16_t rpc_id = UINT16_MAX; - const RPCConfig config = _get_rpc_config(p_node, p_method, rpc_id); - ERR_FAIL_COND_MSG(config.name == StringName(), - vformat("Unable to get the RPC configuration for the function \"%s\" at path: \"%s\". This happens when the method is not marked for RPCs.", p_method, p_node->get_path())); - if (p_peer_id == 0 || p_peer_id == node_id || (p_peer_id < 0 && p_peer_id != -node_id)) { - if (rpc_id & (1 << 15)) { - call_local_native = config.sync; - } else { - call_local_script = config.sync; - } - } - - if (p_peer_id != node_id) { -#ifdef DEBUG_ENABLED - _profile_node_data("out_rpc", p_node->get_instance_id()); -#endif - - _send_rpc(p_node, p_peer_id, rpc_id, config, p_method, p_arg, p_argcount); - } - - if (call_local_native) { - int temp_id = rpc_sender_id; - rpc_sender_id = get_network_unique_id(); - Callable::CallError ce; - p_node->call(p_method, p_arg, p_argcount, ce); - rpc_sender_id = temp_id; - if (ce.error != Callable::CallError::CALL_OK) { - String error = Variant::get_call_error_text(p_node, p_method, p_arg, p_argcount, ce); - error = "rpc() aborted in local call: - " + error + "."; - ERR_PRINT(error); - return; - } - } - - if (call_local_script) { - int temp_id = rpc_sender_id; - rpc_sender_id = get_network_unique_id(); - Callable::CallError ce; - ce.error = Callable::CallError::CALL_OK; - p_node->get_script_instance()->call(p_method, p_arg, p_argcount, ce); - rpc_sender_id = temp_id; - if (ce.error != Callable::CallError::CALL_OK) { - String error = Variant::get_call_error_text(p_node, p_method, p_arg, p_argcount, ce); - error = "rpc() aborted in script local call: - " + error + "."; - ERR_PRINT(error); - return; - } - } - - ERR_FAIL_COND_MSG(p_peer_id == node_id && !config.sync, "RPC '" + p_method + "' on yourself is not allowed by selected mode."); -} - -Error MultiplayerAPI::send_bytes(Vector p_data, int p_to, MultiplayerPeer::TransferMode p_mode, int p_channel) { +Error MultiplayerAPI::send_bytes(Vector 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(!network_peer.is_valid(), ERR_UNCONFIGURED, "Trying to send a raw packet while no network peer is active."); ERR_FAIL_COND_V_MSG(network_peer->get_connection_status() != MultiplayerPeer::CONNECTION_CONNECTED, ERR_UNCONFIGURED, "Trying to send a raw packet via a network peer which is not connected."); - MAKE_ROOM(p_data.size() + 1); + 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()); @@ -1024,6 +536,14 @@ void MultiplayerAPI::_process_raw(int p_from, const uint8_t *p_packet, int p_pac emit_signal(SNAME("network_peer_packet"), p_from, out); } +bool MultiplayerAPI::is_cache_confirmed(NodePath p_path, int p_peer) { + const PathSentCache *psc = path_send_cache.getptr(p_path); + ERR_FAIL_COND_V(!psc, false); + const Map::Element *F = psc->confirmed_peers.find(p_peer); + ERR_FAIL_COND_V(!F, false); // Should never happen. + return F->get(); +} + bool MultiplayerAPI::send_confirm_path(Node *p_node, NodePath p_path, int p_peer_id, int &r_id) { // See if the path is cached. PathSentCache *psc = path_send_cache.getptr(p_path); @@ -1092,23 +612,23 @@ bool MultiplayerAPI::is_object_decoding_allowed() const { return allow_object_decoding; } -MultiplayerReplicator *MultiplayerAPI::get_replicator() const { - return replicator; -} - void MultiplayerAPI::scene_enter_exit_notify(const String &p_scene, Node *p_node, bool p_enter) { replicator->scene_enter_exit_notify(p_scene, p_node, p_enter); } +void MultiplayerAPI::rpcp(Node *p_node, int p_peer_id, const StringName &p_method, const Variant **p_arg, int p_argcount) { + rpc_manager->rpcp(p_node, p_peer_id, p_method, p_arg, p_argcount); +} + void MultiplayerAPI::_bind_methods() { ClassDB::bind_method(D_METHOD("set_root_node", "node"), &MultiplayerAPI::set_root_node); ClassDB::bind_method(D_METHOD("get_root_node"), &MultiplayerAPI::get_root_node); - ClassDB::bind_method(D_METHOD("send_bytes", "bytes", "id", "mode", "channel"), &MultiplayerAPI::send_bytes, DEFVAL(MultiplayerPeer::TARGET_PEER_BROADCAST), DEFVAL(MultiplayerPeer::TRANSFER_MODE_RELIABLE), DEFVAL(0)); + 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_network_peer"), &MultiplayerAPI::has_network_peer); ClassDB::bind_method(D_METHOD("get_network_peer"), &MultiplayerAPI::get_network_peer); ClassDB::bind_method(D_METHOD("get_network_unique_id"), &MultiplayerAPI::get_network_unique_id); ClassDB::bind_method(D_METHOD("is_network_server"), &MultiplayerAPI::is_network_server); - ClassDB::bind_method(D_METHOD("get_rpc_sender_id"), &MultiplayerAPI::get_rpc_sender_id); + ClassDB::bind_method(D_METHOD("get_remote_sender_id"), &MultiplayerAPI::get_remote_sender_id); ClassDB::bind_method(D_METHOD("set_network_peer", "peer"), &MultiplayerAPI::set_network_peer); ClassDB::bind_method(D_METHOD("poll"), &MultiplayerAPI::poll); ClassDB::bind_method(D_METHOD("clear"), &MultiplayerAPI::clear); @@ -1133,18 +653,16 @@ void MultiplayerAPI::_bind_methods() { 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); - BIND_ENUM_CONSTANT(RPC_MODE_AUTHORITY); } MultiplayerAPI::MultiplayerAPI() { replicator = memnew(MultiplayerReplicator(this)); + rpc_manager = memnew(RPCManager(this)); clear(); } MultiplayerAPI::~MultiplayerAPI() { clear(); memdelete(replicator); + memdelete(rpc_manager); } diff --git a/core/io/multiplayer_api.h b/core/multiplayer/multiplayer_api.h similarity index 70% rename from core/io/multiplayer_api.h rename to core/multiplayer/multiplayer_api.h index 3c96a3eed1..acabda62e2 100644 --- a/core/io/multiplayer_api.h +++ b/core/multiplayer/multiplayer_api.h @@ -31,41 +31,17 @@ #ifndef MULTIPLAYER_API_H #define MULTIPLAYER_API_H -#include "core/io/multiplayer_peer.h" -#include "core/io/resource_uid.h" +#include "core/multiplayer/multiplayer.h" +#include "core/multiplayer/multiplayer_peer.h" #include "core/object/ref_counted.h" class MultiplayerReplicator; +class RPCManager; class MultiplayerAPI : public RefCounted { GDCLASS(MultiplayerAPI, RefCounted); public: - enum RPCMode { - RPC_MODE_DISABLED, // No rpc for this method, calls to this will be blocked (default) - RPC_MODE_ANY, // Any peer can call this rpc() - RPC_MODE_AUTHORITY, // Only the node's network authority (server by default) can call this rpc() - }; - - struct RPCConfig { - StringName name; - RPCMode rpc_mode = RPC_MODE_DISABLED; - bool sync = 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 SortRPCConfig { - StringName::AlphCompare compare; - bool operator()(const RPCConfig &p_a, const RPCConfig &p_b) const { - return compare(p_a.name, p_b.name); - } - }; - enum NetworkCommands { NETWORK_COMMAND_REMOTE_CALL = 0, NETWORK_COMMAND_SIMPLIFY_PATH, @@ -73,24 +49,20 @@ public: NETWORK_COMMAND_RAW, NETWORK_COMMAND_SPAWN, NETWORK_COMMAND_DESPAWN, - NETWORK_COMMAND_SYNC, // This is the max we can have. We should optmize simplify/confirm, possibly spawn/despawn. - }; - - enum NetworkNodeIdCompression { - NETWORK_NODE_ID_COMPRESSION_8 = 0, - NETWORK_NODE_ID_COMPRESSION_16, - NETWORK_NODE_ID_COMPRESSION_32, - }; - - enum NetworkNameIdCompression { - NETWORK_NAME_ID_COMPRESSION_8 = 0, - NETWORK_NAME_ID_COMPRESSION_16, + NETWORK_COMMAND_SYNC, }; + // For each command, the 4 MSB can contain custom flags, as defined by subsystems. enum { - NODE_ID_COMPRESSION_SHIFT = 3, - NAME_ID_COMPRESSION_SHIFT = 5, - BYTE_ONLY_OR_NO_ARGS_SHIFT = 6, + 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: @@ -111,29 +83,30 @@ private: }; Ref network_peer; - int rpc_sender_id = 0; Set connected_peers; + int remote_sender_id = 0; + int remote_sender_override = 0; + HashMap path_send_cache; Map path_get_cache; int last_send_cache_id; Vector packet_cache; + Node *root_node = nullptr; bool allow_object_decoding = false; + MultiplayerReplicator *replicator = nullptr; + RPCManager *rpc_manager = nullptr; protected: static void _bind_methods(); + bool _send_confirm_path(Node *p_node, NodePath p_path, PathSentCache *psc, int p_target); void _process_packet(int p_from, const uint8_t *p_packet, int p_packet_len); 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); - Node *_process_get_node(int p_from, const uint8_t *p_packet, uint32_t p_node_target, int p_packet_len); - 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 _process_raw(int p_from, const uint8_t *p_packet, int p_packet_len); - 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); - bool _send_confirm_path(Node *p_node, NodePath p_path, PathSentCache *psc, int p_target); - public: void poll(); void clear(); @@ -141,18 +114,20 @@ public: Node *get_root_node(); void set_network_peer(const Ref &p_peer); Ref get_network_peer() const; - Error send_bytes(Vector p_data, int p_to = MultiplayerPeer::TARGET_PEER_BROADCAST, MultiplayerPeer::TransferMode p_mode = MultiplayerPeer::TRANSFER_MODE_RELIABLE, int p_channel = 0); + + Error send_bytes(Vector p_data, int p_to = MultiplayerPeer::TARGET_PEER_BROADCAST, Multiplayer::TransferMode p_mode = Multiplayer::TRANSFER_MODE_RELIABLE, int p_channel = 0); Error encode_and_compress_variant(const Variant &p_variant, uint8_t *p_buffer, int &r_len); Error decode_and_decompress_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int *r_len); // Called by Node.rpc - void rpcp(Node *p_node, int p_peer_id, bool p_unreliable, const StringName &p_method, const Variant **p_arg, int p_argcount); + void rpcp(Node *p_node, int p_peer_id, const StringName &p_method, const Variant **p_arg, int p_argcount); // Called by Node._notification void scene_enter_exit_notify(const String &p_scene, Node *p_node, bool p_enter); // Called by replicator bool send_confirm_path(Node *p_node, NodePath p_path, int p_target, int &p_id); Node *get_cached_node(int p_from, uint32_t p_node_id); + bool is_cache_confirmed(NodePath p_path, int p_peer); void _add_peer(int p_id); void _del_peer(int p_id); @@ -162,7 +137,9 @@ public: bool has_network_peer() const { return network_peer.is_valid(); } Vector get_network_connected_peers() const; - int get_rpc_sender_id() const { return rpc_sender_id; } + const Set 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_network_unique_id() const; bool is_network_server() const; void set_refuse_new_network_connections(bool p_refuse); @@ -171,12 +148,15 @@ public: void set_allow_object_decoding(bool p_enable); bool is_object_decoding_allowed() const; - MultiplayerReplicator *get_replicator() const; + MultiplayerReplicator *get_replicator() const { return replicator; } + RPCManager *get_rpc_manager() const { return rpc_manager; } + +#ifdef DEBUG_ENABLED + void profile_bandwidth(const String &p_inout, int p_size); +#endif MultiplayerAPI(); ~MultiplayerAPI(); }; -VARIANT_ENUM_CAST(MultiplayerAPI::RPCMode); - #endif // MULTIPLAYER_API_H diff --git a/core/io/multiplayer_peer.cpp b/core/multiplayer/multiplayer_peer.cpp similarity index 97% rename from core/io/multiplayer_peer.cpp rename to core/multiplayer/multiplayer_peer.cpp index 83cf24d7e3..40847102d8 100644 --- a/core/io/multiplayer_peer.cpp +++ b/core/multiplayer/multiplayer_peer.cpp @@ -75,10 +75,6 @@ void MultiplayerPeer::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "transfer_mode", PROPERTY_HINT_ENUM, "Unreliable,Unreliable Ordered,Reliable"), "set_transfer_mode", "get_transfer_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "transfer_channel", PROPERTY_HINT_RANGE, "0,255,1"), "set_transfer_channel", "get_transfer_channel"); - BIND_ENUM_CONSTANT(TRANSFER_MODE_UNRELIABLE); - BIND_ENUM_CONSTANT(TRANSFER_MODE_UNRELIABLE_ORDERED); - BIND_ENUM_CONSTANT(TRANSFER_MODE_RELIABLE); - BIND_ENUM_CONSTANT(CONNECTION_DISCONNECTED); BIND_ENUM_CONSTANT(CONNECTION_CONNECTING); BIND_ENUM_CONSTANT(CONNECTION_CONNECTED); diff --git a/core/io/multiplayer_peer.h b/core/multiplayer/multiplayer_peer.h similarity index 92% rename from core/io/multiplayer_peer.h rename to core/multiplayer/multiplayer_peer.h index 7ca4e7930b..ba00c3b41b 100644 --- a/core/io/multiplayer_peer.h +++ b/core/multiplayer/multiplayer_peer.h @@ -32,6 +32,7 @@ #define NETWORKED_MULTIPLAYER_PEER_H #include "core/io/packet_peer.h" +#include "core/multiplayer/multiplayer.h" class MultiplayerPeer : public PacketPeer { GDCLASS(MultiplayerPeer, PacketPeer); @@ -44,11 +45,6 @@ public: TARGET_PEER_BROADCAST = 0, TARGET_PEER_SERVER = 1 }; - enum TransferMode { - TRANSFER_MODE_UNRELIABLE, - TRANSFER_MODE_UNRELIABLE_ORDERED, - TRANSFER_MODE_RELIABLE, - }; enum ConnectionStatus { CONNECTION_DISCONNECTED, @@ -58,8 +54,8 @@ public: virtual void set_transfer_channel(int p_channel) = 0; virtual int get_transfer_channel() const = 0; - virtual void set_transfer_mode(TransferMode p_mode) = 0; - virtual TransferMode get_transfer_mode() const = 0; + virtual void set_transfer_mode(Multiplayer::TransferMode p_mode) = 0; + virtual Multiplayer::TransferMode get_transfer_mode() const = 0; virtual void set_target_peer(int p_peer_id) = 0; virtual int get_packet_peer() const = 0; @@ -79,7 +75,6 @@ public: MultiplayerPeer() {} }; -VARIANT_ENUM_CAST(MultiplayerPeer::TransferMode) VARIANT_ENUM_CAST(MultiplayerPeer::ConnectionStatus) #endif // NETWORKED_MULTIPLAYER_PEER_H diff --git a/core/io/multiplayer_replicator.cpp b/core/multiplayer/multiplayer_replicator.cpp similarity index 96% rename from core/io/multiplayer_replicator.cpp rename to core/multiplayer/multiplayer_replicator.cpp index b9d0675af1..0340e11288 100644 --- a/core/io/multiplayer_replicator.cpp +++ b/core/multiplayer/multiplayer_replicator.cpp @@ -28,7 +28,7 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "core/io/multiplayer_replicator.h" +#include "core/multiplayer/multiplayer_replicator.h" #include "core/io/marshalls.h" #include "scene/main/node.h" @@ -88,7 +88,7 @@ Error MultiplayerReplicator::_sync_all_default(const ResourceUID::ID &p_scene_id } int ofs = 0; uint8_t *ptr = packet_cache.ptrw(); - ptr[0] = MultiplayerAPI::NETWORK_COMMAND_SYNC + ((same_size ? 1 : 0) << MultiplayerAPI::BYTE_ONLY_OR_NO_ARGS_SHIFT); + ptr[0] = MultiplayerAPI::NETWORK_COMMAND_SYNC | (same_size ? BYTE_OR_ZERO_FLAG : 0); ofs = 1; ofs += encode_uint64(p_scene_id, &ptr[ofs]); ptr[ofs] = cfg.sync_recv++; @@ -116,7 +116,7 @@ Error MultiplayerReplicator::_sync_all_default(const ResourceUID::ID &p_scene_id Ref network_peer = multiplayer->get_network_peer(); network_peer->set_target_peer(p_peer); network_peer->set_transfer_channel(0); - network_peer->set_transfer_mode(MultiplayerPeer::TRANSFER_MODE_UNRELIABLE); + network_peer->set_transfer_mode(Multiplayer::TRANSFER_MODE_UNRELIABLE); return network_peer->put_packet(ptr, ofs); } @@ -125,7 +125,7 @@ void MultiplayerReplicator::_process_default_sync(const ResourceUID::ID &p_id, c ERR_FAIL_COND_MSG(!replications.has(p_id), "Invalid spawn ID received " + itos(p_id)); SceneConfig &cfg = replications[p_id]; ERR_FAIL_COND_MSG(cfg.mode != REPLICATION_MODE_SERVER || multiplayer->is_network_server(), "The defualt implementation only allows sync packets from the server"); - const bool same_size = ((p_packet[0] & 64) >> MultiplayerAPI::BYTE_ONLY_OR_NO_ARGS_SHIFT) == 1; + const bool same_size = p_packet[0] & BYTE_OR_ZERO_FLAG; int ofs = SYNC_CMD_OFFSET; int time = p_packet[ofs]; // Skip old update. @@ -218,7 +218,7 @@ Error MultiplayerReplicator::_send_default_spawn_despawn(int p_peer_id, const Re int nlen = encode_cstring(cname.get_data(), nullptr); MAKE_ROOM(SPAWN_CMD_OFFSET + 4 + 4 + nlen + state_len); uint8_t *ptr = packet_cache.ptrw(); - ptr[0] = (p_spawn ? MultiplayerAPI::NETWORK_COMMAND_SPAWN : MultiplayerAPI::NETWORK_COMMAND_DESPAWN) + ((is_raw ? 1 : 0) << MultiplayerAPI::BYTE_ONLY_OR_NO_ARGS_SHIFT); + ptr[0] = (p_spawn ? MultiplayerAPI::NETWORK_COMMAND_SPAWN : MultiplayerAPI::NETWORK_COMMAND_DESPAWN) | (is_raw ? BYTE_OR_ZERO_FLAG : 0); ofs = 1; ofs += encode_uint64(p_scene_id, &ptr[ofs]); ofs += encode_uint32(path_id, &ptr[ofs]); @@ -236,7 +236,7 @@ Error MultiplayerReplicator::_send_default_spawn_despawn(int p_peer_id, const Re Ref network_peer = multiplayer->get_network_peer(); network_peer->set_target_peer(p_peer_id); network_peer->set_transfer_channel(0); - network_peer->set_transfer_mode(MultiplayerPeer::TRANSFER_MODE_RELIABLE); + network_peer->set_transfer_mode(Multiplayer::TRANSFER_MODE_RELIABLE); return network_peer->put_packet(ptr, ofs + state_len); } @@ -262,7 +262,7 @@ void MultiplayerReplicator::_process_default_spawn_despawn(int p_from, const Res if (cfg.mode == REPLICATION_MODE_SERVER && p_from == 1) { String scene_path = ResourceUID::get_singleton()->get_id_path(p_scene_id); if (p_spawn) { - const bool is_raw = ((p_packet[0] & 64) >> MultiplayerAPI::BYTE_ONLY_OR_NO_ARGS_SHIFT) == 1; + const bool is_raw = ((p_packet[0] & BYTE_OR_ZERO_FLAG) >> BYTE_OR_ZERO_SHIFT) == 1; ERR_FAIL_COND_MSG(parent->has_node(name), vformat("Unable to spawn node. Node already exists: %s/%s", parent->get_path(), name)); RES res = ResourceLoader::load(scene_path); @@ -308,7 +308,7 @@ void MultiplayerReplicator::process_spawn_despawn(int p_from, const uint8_t *p_p const SceneConfig &cfg = replications[id]; if (cfg.on_spawn_despawn_receive.is_valid()) { int ofs = SPAWN_CMD_OFFSET; - bool is_raw = ((p_packet[0] & 64) >> MultiplayerAPI::BYTE_ONLY_OR_NO_ARGS_SHIFT) == 1; + bool is_raw = ((p_packet[0] & BYTE_OR_ZERO_FLAG) >> BYTE_OR_ZERO_SHIFT) == 1; Variant data; int left = p_packet_len - ofs; if (is_raw && left) { @@ -466,7 +466,7 @@ Error MultiplayerReplicator::spawn_config(const ResourceUID::ID &p_id, Replicati SceneConfig cfg; cfg.mode = p_mode; for (int i = 0; i < p_props.size(); i++) { - cfg.properties.push_back(StringName(p_props[i])); + cfg.properties.push_back(p_props[i]); } cfg.on_spawn_despawn_send = p_on_send; cfg.on_spawn_despawn_receive = p_on_recv; @@ -505,7 +505,7 @@ Error MultiplayerReplicator::_send_spawn_despawn(int p_peer_id, const ResourceUI } MAKE_ROOM(SPAWN_CMD_OFFSET + data_size); uint8_t *ptr = packet_cache.ptrw(); - ptr[0] = (p_spawn ? MultiplayerAPI::NETWORK_COMMAND_SPAWN : MultiplayerAPI::NETWORK_COMMAND_DESPAWN) + ((is_raw ? 1 : 0) << MultiplayerAPI::BYTE_ONLY_OR_NO_ARGS_SHIFT); + ptr[0] = (p_spawn ? MultiplayerAPI::NETWORK_COMMAND_SPAWN : MultiplayerAPI::NETWORK_COMMAND_DESPAWN) + ((is_raw ? 1 : 0) << BYTE_OR_ZERO_SHIFT); encode_uint64(p_scene_id, &ptr[1]); if (p_data.get_type() == Variant::PACKED_BYTE_ARRAY) { const PackedByteArray pba = p_data; @@ -516,7 +516,7 @@ Error MultiplayerReplicator::_send_spawn_despawn(int p_peer_id, const ResourceUI Ref network_peer = multiplayer->get_network_peer(); network_peer->set_target_peer(p_peer_id); network_peer->set_transfer_channel(0); - network_peer->set_transfer_mode(MultiplayerPeer::TRANSFER_MODE_RELIABLE); + network_peer->set_transfer_mode(Multiplayer::TRANSFER_MODE_RELIABLE); return network_peer->put_packet(ptr, SPAWN_CMD_OFFSET + data_size); } @@ -740,7 +740,7 @@ Error MultiplayerReplicator::sync_all(const ResourceUID::ID &p_scene_id, int p_p return OK; } -Error MultiplayerReplicator::send_sync(int p_peer_id, const ResourceUID::ID &p_scene_id, PackedByteArray p_data, MultiplayerPeer::TransferMode p_transfer_mode, int p_channel) { +Error MultiplayerReplicator::send_sync(int p_peer_id, const ResourceUID::ID &p_scene_id, PackedByteArray p_data, Multiplayer::TransferMode p_transfer_mode, int p_channel) { ERR_FAIL_COND_V(!multiplayer->has_network_peer(), ERR_UNCONFIGURED); ERR_FAIL_COND_V(!replications.has(p_scene_id), ERR_INVALID_PARAMETER); const SceneConfig &cfg = replications[p_scene_id]; @@ -768,7 +768,7 @@ void MultiplayerReplicator::_bind_methods() { ClassDB::bind_method(D_METHOD("spawn", "scene_id", "object", "peer_id"), &MultiplayerReplicator::spawn, DEFVAL(0)); ClassDB::bind_method(D_METHOD("send_despawn", "peer_id", "scene_id", "data", "path"), &MultiplayerReplicator::send_despawn, DEFVAL(Variant()), DEFVAL(NodePath())); ClassDB::bind_method(D_METHOD("send_spawn", "peer_id", "scene_id", "data", "path"), &MultiplayerReplicator::send_spawn, DEFVAL(Variant()), DEFVAL(NodePath())); - ClassDB::bind_method(D_METHOD("send_sync", "peer_id", "scene_id", "data", "transfer_mode", "channel"), &MultiplayerReplicator::send_sync, DEFVAL(MultiplayerPeer::TRANSFER_MODE_RELIABLE), DEFVAL(0)); + ClassDB::bind_method(D_METHOD("send_sync", "peer_id", "scene_id", "data", "transfer_mode", "channel"), &MultiplayerReplicator::send_sync, DEFVAL(Multiplayer::TRANSFER_MODE_RELIABLE), DEFVAL(0)); ClassDB::bind_method(D_METHOD("sync_all", "scene_id", "peer_id"), &MultiplayerReplicator::sync_all, DEFVAL(0)); ClassDB::bind_method(D_METHOD("track", "scene_id", "object"), &MultiplayerReplicator::track); ClassDB::bind_method(D_METHOD("untrack", "scene_id", "object"), &MultiplayerReplicator::untrack); diff --git a/core/io/multiplayer_replicator.h b/core/multiplayer/multiplayer_replicator.h similarity index 95% rename from core/io/multiplayer_replicator.h rename to core/multiplayer/multiplayer_replicator.h index 2630ad7a8a..7fa774fdf4 100644 --- a/core/io/multiplayer_replicator.h +++ b/core/multiplayer/multiplayer_replicator.h @@ -31,8 +31,9 @@ #ifndef MULTIPLAYER_REPLICATOR_H #define MULTIPLAYER_REPLICATOR_H -#include "core/io/multiplayer_api.h" +#include "core/multiplayer/multiplayer_api.h" +#include "core/io/resource_uid.h" #include "core/templates/hash_map.h" #include "core/variant/typed_array.h" @@ -68,6 +69,14 @@ protected: static void _bind_methods(); private: + enum { + BYTE_OR_ZERO_SHIFT = MultiplayerAPI::CMD_FLAG_0_SHIFT, + }; + + enum { + BYTE_OR_ZERO_FLAG = 1 << BYTE_OR_ZERO_SHIFT, + }; + MultiplayerAPI *multiplayer = nullptr; Vector packet_cache; Map replications; @@ -108,7 +117,7 @@ public: // Sync Error sync_config(const ResourceUID::ID &p_id, uint64_t p_interval, const TypedArray &p_props = TypedArray(), const Callable &p_on_send = Callable(), const Callable &p_on_recv = Callable()); Error sync_all(const ResourceUID::ID &p_scene_id, int p_peer); - Error send_sync(int p_peer_id, const ResourceUID::ID &p_scene_id, PackedByteArray p_data, MultiplayerPeer::TransferMode p_mode, int p_channel); + Error send_sync(int p_peer_id, const ResourceUID::ID &p_scene_id, PackedByteArray p_data, Multiplayer::TransferMode p_mode, int p_channel); void track(const ResourceUID::ID &p_scene_id, Object *p_object); void untrack(const ResourceUID::ID &p_scene_id, Object *p_object); diff --git a/core/multiplayer/rpc_manager.cpp b/core/multiplayer/rpc_manager.cpp new file mode 100644 index 0000000000..135dc4a187 --- /dev/null +++ b/core/multiplayer/rpc_manager.cpp @@ -0,0 +1,525 @@ +/*************************************************************************/ +/* rpc_manager.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 "core/multiplayer/rpc_manager.h" + +#include "core/debugger/engine_debugger.h" +#include "core/io/marshalls.h" +#include "core/multiplayer/multiplayer_api.h" +#include "scene/main/node.h" + +#ifdef DEBUG_ENABLED +_FORCE_INLINE_ void RPCManager::_profile_node_data(const String &p_what, ObjectID p_id) { + if (EngineDebugger::is_profiling("multiplayer")) { + Array values; + values.push_back("node"); + values.push_back(p_id); + values.push_back(p_what); + EngineDebugger::profiler_add_frame_data("multiplayer", values); + } +} +#else +_FORCE_INLINE_ void RPCManager::_profile_node_data(const String &p_what, ObjectID p_id) {} +#endif + +// Returns the packet size stripping the node path added when the node is not yet cached. +int get_packet_len(uint32_t p_node_target, int p_packet_len) { + if (p_node_target & 0x80000000) { + int ofs = p_node_target & 0x7FFFFFFF; + return p_packet_len - (p_packet_len - ofs); + } else { + return p_packet_len; + } +} + +const Multiplayer::RPCConfig _get_rpc_config(const Node *p_node, const StringName &p_method, uint16_t &r_id) { + const Vector 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]; + } + } + if (p_node->get_script_instance()) { + const Vector 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]; + } + } + } + return Multiplayer::RPCConfig(); +} + +const Multiplayer::RPCConfig _get_rpc_config_by_id(Node *p_node, uint16_t p_id) { + Vector config; + uint16_t id = p_id; + if (id & (1 << 15)) { + id = id & ~(1 << 15); + config = p_node->get_node_rpc_methods(); + } else if (p_node->get_script_instance()) { + config = p_node->get_script_instance()->get_rpc_methods(); + } + if (id < config.size()) { + return config[id]; + } + return Multiplayer::RPCConfig(); +} + +_FORCE_INLINE_ bool _can_call_mode(Node *p_node, Multiplayer::RPCMode mode, int p_remote_id) { + switch (mode) { + case Multiplayer::RPC_MODE_DISABLED: { + return false; + } break; + case Multiplayer::RPC_MODE_ANY: { + return true; + } break; + case Multiplayer::RPC_MODE_AUTHORITY: { + return !p_node->is_network_authority() && p_remote_id == p_node->get_network_authority(); + } break; + } + + return false; +} + +String RPCManager::get_rpc_md5(const Node *p_node) { + String rpc_list; + const Vector node_config = p_node->get_node_rpc_methods(); + for (int i = 0; i < node_config.size(); i++) { + rpc_list += String(node_config[i].name); + } + if (p_node->get_script_instance()) { + const Vector script_config = p_node->get_script_instance()->get_rpc_methods(); + for (int i = 0; i < script_config.size(); i++) { + rpc_list += String(script_config[i].name); + } + } + return rpc_list.md5_text(); +} + +Node *RPCManager::_process_get_node(int p_from, const uint8_t *p_packet, uint32_t p_node_target, int p_packet_len) { + Node *node = nullptr; + + if (p_node_target & 0x80000000) { + // Use full path (not cached yet). + int ofs = p_node_target & 0x7FFFFFFF; + + ERR_FAIL_COND_V_MSG(ofs >= p_packet_len, nullptr, "Invalid packet received. Size smaller than declared."); + + String paths; + paths.parse_utf8((const char *)&p_packet[ofs], p_packet_len - ofs); + + NodePath np = paths; + + node = multiplayer->get_root_node()->get_node(np); + + if (!node) { + ERR_PRINT("Failed to get path from RPC: " + String(np) + "."); + } + return node; + } else { + // Use cached path. + return multiplayer->get_cached_node(p_from, p_node_target); + } +} + +void RPCManager::process_rpc(int p_from, const uint8_t *p_packet, int p_packet_len) { + // Extract packet meta + int packet_min_size = 1; + int name_id_offset = 1; + ERR_FAIL_COND_MSG(p_packet_len < packet_min_size, "Invalid packet received. Size too small."); + // Compute the meta size, which depends on the compression level. + int node_id_compression = (p_packet[0] & NODE_ID_COMPRESSION_FLAG) >> NODE_ID_COMPRESSION_SHIFT; + int name_id_compression = (p_packet[0] & NAME_ID_COMPRESSION_FLAG) >> NAME_ID_COMPRESSION_SHIFT; + + switch (node_id_compression) { + case NETWORK_NODE_ID_COMPRESSION_8: + packet_min_size += 1; + name_id_offset += 1; + break; + case NETWORK_NODE_ID_COMPRESSION_16: + packet_min_size += 2; + name_id_offset += 2; + break; + case NETWORK_NODE_ID_COMPRESSION_32: + packet_min_size += 4; + name_id_offset += 4; + break; + default: + ERR_FAIL_MSG("Was not possible to extract the node id compression mode."); + } + switch (name_id_compression) { + case NETWORK_NAME_ID_COMPRESSION_8: + packet_min_size += 1; + break; + case NETWORK_NAME_ID_COMPRESSION_16: + packet_min_size += 2; + break; + default: + ERR_FAIL_MSG("Was not possible to extract the name id compression mode."); + } + ERR_FAIL_COND_MSG(p_packet_len < packet_min_size, "Invalid packet received. Size too small."); + + uint32_t node_target = 0; + switch (node_id_compression) { + case NETWORK_NODE_ID_COMPRESSION_8: + node_target = p_packet[1]; + break; + case NETWORK_NODE_ID_COMPRESSION_16: + node_target = decode_uint16(p_packet + 1); + break; + case NETWORK_NODE_ID_COMPRESSION_32: + node_target = decode_uint32(p_packet + 1); + break; + default: + // Unreachable, checked before. + CRASH_NOW(); + } + + Node *node = _process_get_node(p_from, p_packet, node_target, p_packet_len); + ERR_FAIL_COND_MSG(node == nullptr, "Invalid packet received. Requested node was not found."); + + uint16_t name_id = 0; + switch (name_id_compression) { + case NETWORK_NAME_ID_COMPRESSION_8: + name_id = p_packet[name_id_offset]; + break; + case NETWORK_NAME_ID_COMPRESSION_16: + name_id = decode_uint16(p_packet + name_id_offset); + break; + default: + // Unreachable, checked before. + CRASH_NOW(); + } + + const int packet_len = get_packet_len(node_target, p_packet_len); + _process_rpc(node, name_id, p_from, p_packet, packet_len, packet_min_size); +} + +void RPCManager::_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) { + 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()); + + 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_network_authority()) + "."); + + int argc = 0; + bool byte_only = false; + + const bool byte_only_or_no_args = p_packet[0] & BYTE_ONLY_OR_NO_ARGS_FLAG; + if (byte_only_or_no_args) { + if (p_offset < p_packet_len) { + // This packet contains only bytes. + argc = 1; + byte_only = true; + } else { + // This rpc calls a method without parameters. + } + } else { + // Normal variant, takes the argument count from the packet. + ERR_FAIL_COND_MSG(p_offset >= p_packet_len, "Invalid packet received. Size too small."); + argc = p_packet[p_offset]; + p_offset += 1; + } + + Vector args; + Vector argp; + args.resize(argc); + argp.resize(argc); + +#ifdef DEBUG_ENABLED + _profile_node_data("in_rpc", p_node->get_instance_id()); +#endif + + if (byte_only) { + Vector pure_data; + const int len = p_packet_len - p_offset; + pure_data.resize(len); + memcpy(pure_data.ptrw(), &p_packet[p_offset], len); + args.write[0] = pure_data; + argp.write[0] = &args[0]; + p_offset += len; + } else { + for (int i = 0; i < argc; i++) { + ERR_FAIL_COND_MSG(p_offset >= p_packet_len, "Invalid packet received. Size too small."); + + int vlen; + Error err = multiplayer->decode_and_decompress_variant(args.write[i], &p_packet[p_offset], p_packet_len - p_offset, &vlen); + ERR_FAIL_COND_MSG(err != OK, "Invalid packet received. Unable to decode RPC argument."); + + argp.write[i] = &args[i]; + p_offset += vlen; + } + } + + Callable::CallError ce; + + p_node->call(config.name, (const Variant **)argp.ptr(), argc, ce); + if (ce.error != Callable::CallError::CALL_OK) { + String error = Variant::get_call_error_text(p_node, config.name, (const Variant **)argp.ptr(), argc, ce); + error = "RPC - " + error; + ERR_PRINT(error); + } +} + +void RPCManager::_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) { + Ref network_peer = multiplayer->get_network_peer(); + ERR_FAIL_COND_MSG(network_peer.is_null(), "Attempt to remote call/set when networking is not active in SceneTree."); + + ERR_FAIL_COND_MSG(network_peer->get_connection_status() == MultiplayerPeer::CONNECTION_CONNECTING, "Attempt to remote call/set when networking is not connected yet in SceneTree."); + + ERR_FAIL_COND_MSG(network_peer->get_connection_status() == MultiplayerPeer::CONNECTION_DISCONNECTED, "Attempt to remote call/set when networking is disconnected."); + + ERR_FAIL_COND_MSG(p_argcount > 255, "Too many arguments >255."); + + if (p_to != 0 && !multiplayer->get_network_connected_peers().has(ABS(p_to))) { + ERR_FAIL_COND_MSG(p_to == network_peer->get_unique_id(), "Attempt to remote call/set yourself! unique ID: " + itos(network_peer->get_unique_id()) + "."); + + ERR_FAIL_MSG("Attempt to remote call unexisting ID: " + itos(p_to) + "."); + } + + NodePath from_path = (multiplayer->get_root_node()->get_path()).rel_path_to(p_from->get_path()); + ERR_FAIL_COND_MSG(from_path.is_empty(), "Unable to send RPC. Relative path is empty. THIS IS LIKELY A BUG IN THE ENGINE!"); + + // See if all peers have cached path (if so, call can be fast). + int psc_id; + const bool has_all_peers = multiplayer->send_confirm_path(p_from, from_path, p_to, psc_id); + + // Create base packet, lots of hardcode because it must be tight. + + int ofs = 0; + +#define MAKE_ROOM(m_amount) \ + if (packet_cache.size() < m_amount) \ + packet_cache.resize(m_amount); + + // Encode meta. + uint8_t command_type = MultiplayerAPI::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; + + MAKE_ROOM(1); + // The meta is composed along the way, so just set 0 for now. + packet_cache.write[0] = 0; + ofs += 1; + + // Encode Node ID. + if (has_all_peers) { + // Compress the node ID only if all the target peers already know it. + if (psc_id >= 0 && psc_id <= 255) { + // We can encode the id in 1 byte + node_id_compression = NETWORK_NODE_ID_COMPRESSION_8; + MAKE_ROOM(ofs + 1); + packet_cache.write[ofs] = static_cast(psc_id); + ofs += 1; + } else if (psc_id >= 0 && psc_id <= 65535) { + // We can encode the id in 2 bytes + node_id_compression = NETWORK_NODE_ID_COMPRESSION_16; + MAKE_ROOM(ofs + 2); + encode_uint16(static_cast(psc_id), &(packet_cache.write[ofs])); + ofs += 2; + } else { + // Too big, let's use 4 bytes. + node_id_compression = NETWORK_NODE_ID_COMPRESSION_32; + MAKE_ROOM(ofs + 4); + encode_uint32(psc_id, &(packet_cache.write[ofs])); + ofs += 4; + } + } else { + // The targets don't know the node yet, so we need to use 32 bits int. + node_id_compression = NETWORK_NODE_ID_COMPRESSION_32; + MAKE_ROOM(ofs + 4); + encode_uint32(psc_id, &(packet_cache.write[ofs])); + ofs += 4; + } + + // Encode method ID + if (p_rpc_id <= UINT8_MAX) { + // The ID fits in 1 byte + name_id_compression = NETWORK_NAME_ID_COMPRESSION_8; + MAKE_ROOM(ofs + 1); + packet_cache.write[ofs] = static_cast(p_rpc_id); + ofs += 1; + } else { + // The ID is larger, let's use 2 bytes + name_id_compression = NETWORK_NAME_ID_COMPRESSION_16; + MAKE_ROOM(ofs + 2); + encode_uint16(p_rpc_id, &(packet_cache.write[ofs])); + ofs += 2; + } + + if (p_argcount == 0) { + byte_only_or_no_args = true; + } else if (p_argcount == 1 && p_arg[0]->get_type() == Variant::PACKED_BYTE_ARRAY) { + byte_only_or_no_args = true; + // Special optimization when only the byte vector is sent. + const Vector data = *p_arg[0]; + MAKE_ROOM(ofs + data.size()); + memcpy(&(packet_cache.write[ofs]), data.ptr(), sizeof(uint8_t) * data.size()); + ofs += data.size(); + } else { + // Arguments + MAKE_ROOM(ofs + 1); + packet_cache.write[ofs] = p_argcount; + ofs += 1; + for (int i = 0; i < p_argcount; i++) { + int len(0); + Error err = multiplayer->encode_and_compress_variant(*p_arg[i], nullptr, len); + ERR_FAIL_COND_MSG(err != OK, "Unable to encode RPC argument. THIS IS LIKELY A BUG IN THE ENGINE!"); + MAKE_ROOM(ofs + len); + multiplayer->encode_and_compress_variant(*p_arg[i], &(packet_cache.write[ofs]), len); + ofs += len; + } + } + + ERR_FAIL_COND(command_type > 7); + ERR_FAIL_COND(node_id_compression > 3); + ERR_FAIL_COND(name_id_compression > 1); + + // We can now set the meta + packet_cache.write[0] = command_type + (node_id_compression << NODE_ID_COMPRESSION_SHIFT) + (name_id_compression << NAME_ID_COMPRESSION_SHIFT) + (byte_only_or_no_args ? BYTE_ONLY_OR_NO_ARGS_FLAG : 0); + +#ifdef DEBUG_ENABLED + multiplayer->profile_bandwidth("out", ofs); +#endif + + // Take chance and set transfer mode, since all send methods will use it. + network_peer->set_transfer_channel(p_config.channel); + network_peer->set_transfer_mode(p_config.transfer_mode); + + if (has_all_peers) { + // They all have verified paths, so send fast. + network_peer->set_target_peer(p_to); // To all of you. + network_peer->put_packet(packet_cache.ptr(), ofs); // A message with love. + } else { + // Unreachable because the node ID is never compressed if the peers doesn't know it. + CRASH_COND(node_id_compression != NETWORK_NODE_ID_COMPRESSION_32); + + // Not all verified path, so send one by one. + + // Append path at the end, since we will need it for some packets. + CharString pname = String(from_path).utf8(); + int path_len = encode_cstring(pname.get_data(), nullptr); + MAKE_ROOM(ofs + path_len); + encode_cstring(pname.get_data(), &(packet_cache.write[ofs])); + + for (const int &P : multiplayer->get_connected_peers()) { + if (p_to < 0 && P == -p_to) { + continue; // Continue, excluded. + } + + if (p_to > 0 && P != p_to) { + continue; // Continue, not for this peer. + } + + bool confirmed = multiplayer->is_cache_confirmed(from_path, P); + + network_peer->set_target_peer(P); // To this one specifically. + + if (confirmed) { + // This one confirmed path, so use id. + encode_uint32(psc_id, &(packet_cache.write[1])); + network_peer->put_packet(packet_cache.ptr(), ofs); + } else { + // This one did not confirm path yet, so use entire path (sorry!). + encode_uint32(0x80000000 | ofs, &(packet_cache.write[1])); // Offset to path and flag. + network_peer->put_packet(packet_cache.ptr(), ofs + path_len); + } + } + } +} + +void RPCManager::rpcp(Node *p_node, int p_peer_id, const StringName &p_method, const Variant **p_arg, int p_argcount) { + Ref network_peer = multiplayer->get_network_peer(); + ERR_FAIL_COND_MSG(!network_peer.is_valid(), "Trying to call an RPC while no network peer is active."); + ERR_FAIL_COND_MSG(!p_node->is_inside_tree(), "Trying to call an RPC on a node which is not inside SceneTree."); + ERR_FAIL_COND_MSG(network_peer->get_connection_status() != MultiplayerPeer::CONNECTION_CONNECTED, "Trying to call an RPC via a network peer which is not connected."); + + int node_id = network_peer->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(p_node, p_method, rpc_id); + ERR_FAIL_COND_MSG(config.name == StringName(), + vformat("Unable to get the RPC configuration for the function \"%s\" at path: \"%s\". This happens when the method is not marked for RPCs.", p_method, p_node->get_path())); + if (p_peer_id == 0 || p_peer_id == node_id || (p_peer_id < 0 && p_peer_id != -node_id)) { + if (rpc_id & (1 << 15)) { + call_local_native = config.sync; + } else { + call_local_script = config.sync; + } + } + + if (p_peer_id != node_id) { +#ifdef DEBUG_ENABLED + _profile_node_data("out_rpc", p_node->get_instance_id()); +#endif + + _send_rpc(p_node, p_peer_id, rpc_id, config, p_method, p_arg, p_argcount); + } + + if (call_local_native) { + Callable::CallError ce; + + multiplayer->set_remote_sender_override(network_peer->get_unique_id()); + p_node->call(p_method, p_arg, p_argcount, ce); + multiplayer->set_remote_sender_override(0); + + if (ce.error != Callable::CallError::CALL_OK) { + String error = Variant::get_call_error_text(p_node, p_method, p_arg, p_argcount, ce); + error = "rpc() aborted in local call: - " + error + "."; + ERR_PRINT(error); + return; + } + } + + if (call_local_script) { + Callable::CallError ce; + ce.error = Callable::CallError::CALL_OK; + + multiplayer->set_remote_sender_override(network_peer->get_unique_id()); + p_node->get_script_instance()->call(p_method, p_arg, p_argcount, ce); + multiplayer->set_remote_sender_override(0); + + if (ce.error != Callable::CallError::CALL_OK) { + String error = Variant::get_call_error_text(p_node, p_method, p_arg, p_argcount, ce); + error = "rpc() aborted in script local call: - " + error + "."; + ERR_PRINT(error); + return; + } + } + + ERR_FAIL_COND_MSG(p_peer_id == node_id && !config.sync, "RPC '" + p_method + "' on yourself is not allowed by selected mode."); +} diff --git a/core/multiplayer/rpc_manager.h b/core/multiplayer/rpc_manager.h new file mode 100644 index 0000000000..7b99dee226 --- /dev/null +++ b/core/multiplayer/rpc_manager.h @@ -0,0 +1,89 @@ +/*************************************************************************/ +/* rpc_manager.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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_RPC_H +#define MULTIPLAYER_RPC_H + +#include "core/multiplayer/multiplayer.h" +#include "core/multiplayer/multiplayer_api.h" +#include "core/object/ref_counted.h" + +class RPCManager : public RefCounted { + GDCLASS(RPCManager, RefCounted); + +private: + enum NetworkNodeIdCompression { + NETWORK_NODE_ID_COMPRESSION_8 = 0, + NETWORK_NODE_ID_COMPRESSION_16, + NETWORK_NODE_ID_COMPRESSION_32, + }; + + enum NetworkNameIdCompression { + NETWORK_NAME_ID_COMPRESSION_8 = 0, + 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; + Vector packet_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); + Node *_process_get_node(int p_from, const uint8_t *p_packet, uint32_t p_node_target, int p_packet_len); + +public: + // Called by Node.rpc + void rpcp(Node *p_node, 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 Node *p_node); + RPCManager(MultiplayerAPI *p_multiplayer) { multiplayer = p_multiplayer; } +}; + +#endif // MULTIPLAYER_RPC_H diff --git a/core/object/script_language.h b/core/object/script_language.h index 385bf79c1a..8d76cbf479 100644 --- a/core/object/script_language.h +++ b/core/object/script_language.h @@ -32,8 +32,8 @@ #define SCRIPT_LANGUAGE_H #include "core/doc_data.h" -#include "core/io/multiplayer_api.h" #include "core/io/resource.h" +#include "core/multiplayer/multiplayer.h" #include "core/templates/map.h" #include "core/templates/pair.h" @@ -159,7 +159,7 @@ public: virtual bool is_placeholder_fallback_enabled() const { return false; } - virtual const Vector get_rpc_methods() const = 0; + virtual const Vector get_rpc_methods() const = 0; Script() {} }; @@ -200,7 +200,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 get_rpc_methods() const = 0; + virtual const Vector get_rpc_methods() const = 0; virtual ScriptLanguage *get_language() = 0; virtual ~ScriptInstance(); @@ -419,7 +419,7 @@ public: virtual void property_set_fallback(const StringName &p_name, const Variant &p_value, bool *r_valid = nullptr); virtual Variant property_get_fallback(const StringName &p_name, bool *r_valid = nullptr); - virtual const Vector get_rpc_methods() const { return Vector(); } + virtual const Vector get_rpc_methods() const { return Vector(); } PlaceHolderScriptInstance(ScriptLanguage *p_language, Ref