diff options
Diffstat (limited to 'core/io')
-rw-r--r-- | core/io/config_file.cpp | 4 | ||||
-rw-r--r-- | core/io/dir_access.cpp | 4 | ||||
-rw-r--r-- | core/io/http_client_tcp.cpp | 26 | ||||
-rw-r--r-- | core/io/http_client_tcp.h | 1 | ||||
-rw-r--r-- | core/io/multiplayer_api.cpp | 108 | ||||
-rw-r--r-- | core/io/multiplayer_api.h | 67 | ||||
-rw-r--r-- | core/io/multiplayer_replicator.cpp | 786 | ||||
-rw-r--r-- | core/io/multiplayer_replicator.h | 129 | ||||
-rw-r--r-- | core/io/resource.cpp | 9 | ||||
-rw-r--r-- | core/io/resource.h | 2 | ||||
-rw-r--r-- | core/io/resource_format_binary.cpp | 4 | ||||
-rw-r--r-- | core/io/resource_loader.cpp | 90 | ||||
-rw-r--r-- | core/io/resource_loader.h | 12 | ||||
-rw-r--r-- | core/io/resource_saver.cpp | 35 | ||||
-rw-r--r-- | core/io/resource_saver.h | 6 |
15 files changed, 1140 insertions, 143 deletions
diff --git a/core/io/config_file.cpp b/core/io/config_file.cpp index aeaf25f321..49fa73dab2 100644 --- a/core/io/config_file.cpp +++ b/core/io/config_file.cpp @@ -188,7 +188,7 @@ Error ConfigFile::_internal_save(FileAccess *file) { for (OrderedHashMap<String, Variant>::Element F = E.get().front(); F; F = F.next()) { String vstr; VariantWriter::write_to_string(F.get(), vstr); - file->store_string(F.key() + "=" + vstr + "\n"); + file->store_string(F.key().property_name_encode() + "=" + vstr + "\n"); } } @@ -315,6 +315,8 @@ void ConfigFile::_bind_methods() { ClassDB::bind_method(D_METHOD("parse", "data"), &ConfigFile::parse); ClassDB::bind_method(D_METHOD("save", "path"), &ConfigFile::save); + BIND_METHOD_ERR_RETURN_DOC("load", ERR_FILE_CANT_OPEN); + ClassDB::bind_method(D_METHOD("load_encrypted", "path", "key"), &ConfigFile::load_encrypted); ClassDB::bind_method(D_METHOD("load_encrypted_pass", "path", "password"), &ConfigFile::load_encrypted_pass); diff --git a/core/io/dir_access.cpp b/core/io/dir_access.cpp index 8234adea06..3bff0a3fd5 100644 --- a/core/io/dir_access.cpp +++ b/core/io/dir_access.cpp @@ -135,7 +135,7 @@ Error DirAccess::make_dir_recursive(String p_dir) { String full_dir; - if (p_dir.is_rel_path()) { + if (p_dir.is_relative_path()) { //append current full_dir = get_current_dir().plus_file(p_dir); @@ -345,7 +345,7 @@ Error DirAccess::_copy_dir(DirAccess *p_target_da, String p_to, int p_chmod_flag dirs.push_back(n); } else { const String &rel_path = n; - if (!n.is_rel_path()) { + if (!n.is_relative_path()) { list_dir_end(); return ERR_BUG; } diff --git a/core/io/http_client_tcp.cpp b/core/io/http_client_tcp.cpp index f291086808..b3d35b3603 100644 --- a/core/io/http_client_tcp.cpp +++ b/core/io/http_client_tcp.cpp @@ -45,6 +45,8 @@ Error HTTPClientTCP::connect_to_host(const String &p_host, int p_port, bool p_ss conn_port = p_port; conn_host = p_host; + ip_candidates.clear(); + ssl = p_ssl; ssl_verify_host = p_verify_host; @@ -234,6 +236,7 @@ void HTTPClientTCP::close() { resolving = IP::RESOLVER_INVALID_ID; } + ip_candidates.clear(); response_headers.clear(); response_str.clear(); body_size = -1; @@ -256,10 +259,17 @@ Error HTTPClientTCP::poll() { return OK; // Still resolving case IP::RESOLVER_STATUS_DONE: { - IPAddress host = IP::get_singleton()->get_resolve_item_address(resolving); - Error err = tcp_connection->connect_to_host(host, conn_port); + ip_candidates = IP::get_singleton()->get_resolve_item_addresses(resolving); IP::get_singleton()->erase_resolve_item(resolving); resolving = IP::RESOLVER_INVALID_ID; + + Error err = ERR_BUG; // Should be at least one entry. + while (ip_candidates.size() > 0) { + err = tcp_connection->connect_to_host(ip_candidates.front(), conn_port); + if (err == OK) { + break; + } + } if (err) { status = STATUS_CANT_CONNECT; return err; @@ -313,6 +323,7 @@ Error HTTPClientTCP::poll() { if (ssl->get_status() == StreamPeerSSL::STATUS_CONNECTED) { // Handshake has been successful handshaking = false; + ip_candidates.clear(); status = STATUS_CONNECTED; return OK; } else if (ssl->get_status() != StreamPeerSSL::STATUS_HANDSHAKING) { @@ -323,15 +334,24 @@ Error HTTPClientTCP::poll() { } // ... we will need to poll more for handshake to finish } else { + ip_candidates.clear(); status = STATUS_CONNECTED; } return OK; } break; case StreamPeerTCP::STATUS_ERROR: case StreamPeerTCP::STATUS_NONE: { + Error err = ERR_CANT_CONNECT; + while (ip_candidates.size() > 0) { + tcp_connection->disconnect_from_host(); + err = tcp_connection->connect_to_host(ip_candidates.pop_front(), conn_port); + if (err == OK) { + return OK; + } + } close(); status = STATUS_CANT_CONNECT; - return ERR_CANT_CONNECT; + return err; } break; } } break; diff --git a/core/io/http_client_tcp.h b/core/io/http_client_tcp.h index e178399fbe..170afb551c 100644 --- a/core/io/http_client_tcp.h +++ b/core/io/http_client_tcp.h @@ -37,6 +37,7 @@ class HTTPClientTCP : public HTTPClient { private: Status status = STATUS_DISCONNECTED; IP::ResolverID resolving = IP::RESOLVER_INVALID_ID; + Array ip_candidates; int conn_port = -1; String conn_host; bool ssl = false; diff --git a/core/io/multiplayer_api.cpp b/core/io/multiplayer_api.cpp index 1c3f231170..c145225751 100644 --- a/core/io/multiplayer_api.cpp +++ b/core/io/multiplayer_api.cpp @@ -32,14 +32,11 @@ #include "core/debugger/engine_debugger.h" #include "core/io/marshalls.h" +#include "core/io/multiplayer_replicator.h" #include "scene/main/node.h" #include <stdint.h> -#define NODE_ID_COMPRESSION_SHIFT 3 -#define NAME_ID_COMPRESSION_SHIFT 5 -#define BYTE_ONLY_OR_NO_ARGS_SHIFT 6 - #ifdef DEBUG_ENABLED #include "core/os/os.h" #endif @@ -99,14 +96,11 @@ _FORCE_INLINE_ bool _can_call_mode(Node *p_node, MultiplayerAPI::RPCMode mode, i case MultiplayerAPI::RPC_MODE_DISABLED: { return false; } break; - case MultiplayerAPI::RPC_MODE_REMOTE: { + case MultiplayerAPI::RPC_MODE_ANY: { return true; } break; - case MultiplayerAPI::RPC_MODE_MASTER: { - return p_node->is_network_master(); - } break; - case MultiplayerAPI::RPC_MODE_PUPPET: { - return !p_node->is_network_master() && p_remote_id == p_node->get_network_master(); + case MultiplayerAPI::RPC_MODE_AUTHORITY: { + return !p_node->is_network_authority() && p_remote_id == p_node->get_network_authority(); } break; } @@ -143,9 +137,13 @@ void MultiplayerAPI::poll() { break; // It's also possible that a packet or RPC caused a disconnection, so also check here. } } + if (network_peer.is_valid() && network_peer->get_connection_status() == MultiplayerPeer::CONNECTION_CONNECTED) { + replicator->poll(); + } } void MultiplayerAPI::clear() { + replicator->clear(); connected_peers.clear(); path_get_cache.clear(); path_send_cache.clear(); @@ -322,6 +320,15 @@ void MultiplayerAPI::_process_packet(int p_from, const uint8_t *p_packet, int p_ case NETWORK_COMMAND_RAW: { _process_raw(p_from, p_packet, p_packet_len); } break; + case NETWORK_COMMAND_SPAWN: { + replicator->process_spawn_despawn(p_from, p_packet, p_packet_len, true); + } break; + case NETWORK_COMMAND_DESPAWN: { + replicator->process_spawn_despawn(p_from, p_packet, p_packet_len, false); + } break; + case NETWORK_COMMAND_SYNC: { + replicator->process_sync(p_from, p_packet, p_packet_len); + } break; } } @@ -330,7 +337,6 @@ Node *MultiplayerAPI::_process_get_node(int p_from, const uint8_t *p_packet, uin 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."); @@ -345,25 +351,11 @@ Node *MultiplayerAPI::_process_get_node(int p_from, const uint8_t *p_packet, uin if (!node) { ERR_PRINT("Failed to get path from RPC: " + String(np) + "."); } + return node; } else { // Use cached path. - int id = p_node_target; - - Map<int, PathGetCache>::Element *E = path_get_cache.find(p_from); - ERR_FAIL_COND_V_MSG(!E, nullptr, "Invalid packet received. Requests invalid peer cache."); - - Map<int, PathGetCache::NodeInfo>::Element *F = E->get().nodes.find(id); - ERR_FAIL_COND_V_MSG(!F, nullptr, "Invalid packet received. Unabled to find requested cached node."); - - PathGetCache::NodeInfo *ni = &F->get(); - // Do proper caching later. - - node = root_node->get_node(ni->path); - if (!node) { - ERR_PRINT("Failed to get cached path from RPC: " + String(ni->path) + "."); - } + return get_cached_node(p_from, p_node_target); } - return node; } 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) { @@ -374,7 +366,7 @@ void MultiplayerAPI::_process_rpc(Node *p_node, const uint16_t 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) + ", master is " + itos(p_node->get_network_master()) + "."); + 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; @@ -417,7 +409,7 @@ void MultiplayerAPI::_process_rpc(Node *p_node, const uint16_t p_rpc_method_id, 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); + 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]; @@ -581,7 +573,7 @@ bool MultiplayerAPI::_send_confirm_path(Node *p_node, NodePath p_path, PathSentC #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) { +Error MultiplayerAPI::encode_and_compress_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len) { // Unreachable because `VARIANT_MAX` == 27 and `ENCODE_VARIANT_MASK` == 31 CRASH_COND(p_variant.get_type() > VARIANT_META_TYPE_MASK); @@ -656,7 +648,7 @@ Error MultiplayerAPI::_encode_and_compress_variant(const Variant &p_variant, uin return OK; } -Error MultiplayerAPI::_decode_and_decompress_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int *r_len) { +Error MultiplayerAPI::decode_and_decompress_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int *r_len) { const uint8_t *buf = p_buffer; int len = p_len; @@ -840,10 +832,10 @@ void MultiplayerAPI::_send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, const ofs += 1; for (int i = 0; i < p_argcount; i++) { int len(0); - Error err = _encode_and_compress_variant(*p_arg[i], nullptr, len); + 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); + encode_and_compress_variant(*p_arg[i], &(packet_cache.write[ofs]), len); ofs += len; } } @@ -909,6 +901,9 @@ void MultiplayerAPI::_send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, const void MultiplayerAPI::_add_peer(int p_id) { connected_peers.insert(p_id); path_get_cache.insert(p_id, PathGetCache()); + if (is_network_server()) { + replicator->spawn_all(p_id); + } emit_signal(SNAME("network_peer_connected"), p_id); } @@ -1029,6 +1024,36 @@ 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::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); + if (!psc) { + // Path is not cached, create. + path_send_cache[p_path] = PathSentCache(); + psc = path_send_cache.getptr(p_path); + psc->id = last_send_cache_id++; + } + r_id = psc->id; + + // See if all peers have cached path (if so, call can be fast). + return _send_confirm_path(p_node, p_path, psc, p_peer_id); +} + +Node *MultiplayerAPI::get_cached_node(int p_from, uint32_t p_node_id) { + Map<int, PathGetCache>::Element *E = path_get_cache.find(p_from); + ERR_FAIL_COND_V_MSG(!E, nullptr, vformat("No cache found for peer %d.", p_from)); + + Map<int, PathGetCache::NodeInfo>::Element *F = E->get().nodes.find(p_node_id); + ERR_FAIL_COND_V_MSG(!F, nullptr, vformat("ID %d not found in cache of peer %d.", p_node_id, p_from)); + + PathGetCache::NodeInfo *ni = &F->get(); + Node *node = root_node->get_node(ni->path); + if (!node) { + ERR_PRINT("Failed to get cached path: " + String(ni->path) + "."); + } + return node; +} + int MultiplayerAPI::get_network_unique_id() const { ERR_FAIL_COND_V_MSG(!network_peer.is_valid(), 0, "No network peer is assigned. Unable to get unique network ID."); return network_peer->get_unique_id(); @@ -1067,6 +1092,14 @@ 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::_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); @@ -1085,12 +1118,14 @@ void MultiplayerAPI::_bind_methods() { ClassDB::bind_method(D_METHOD("is_refusing_new_network_connections"), &MultiplayerAPI::is_refusing_new_network_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); + ClassDB::bind_method(D_METHOD("get_replicator"), &MultiplayerAPI::get_replicator); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_object_decoding"), "set_allow_object_decoding", "is_object_decoding_allowed"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "refuse_new_network_connections"), "set_refuse_new_network_connections", "is_refusing_new_network_connections"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "network_peer", PROPERTY_HINT_RESOURCE_TYPE, "MultiplayerPeer", PROPERTY_USAGE_NONE), "set_network_peer", "get_network_peer"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "root_node", PROPERTY_HINT_RESOURCE_TYPE, "Node", PROPERTY_USAGE_NONE), "set_root_node", "get_root_node"); ADD_PROPERTY_DEFAULT("refuse_new_network_connections", false); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "replicator", PROPERTY_HINT_RESOURCE_TYPE, "MultiplayerReplicator", PROPERTY_USAGE_NONE), "", "get_replicator"); ADD_SIGNAL(MethodInfo("network_peer_connected", PropertyInfo(Variant::INT, "id"))); ADD_SIGNAL(MethodInfo("network_peer_disconnected", PropertyInfo(Variant::INT, "id"))); @@ -1100,15 +1135,16 @@ void MultiplayerAPI::_bind_methods() { ADD_SIGNAL(MethodInfo("server_disconnected")); BIND_ENUM_CONSTANT(RPC_MODE_DISABLED); - BIND_ENUM_CONSTANT(RPC_MODE_REMOTE); - BIND_ENUM_CONSTANT(RPC_MODE_MASTER); - BIND_ENUM_CONSTANT(RPC_MODE_PUPPET); + BIND_ENUM_CONSTANT(RPC_MODE_ANY); + BIND_ENUM_CONSTANT(RPC_MODE_AUTHORITY); } MultiplayerAPI::MultiplayerAPI() { + replicator = memnew(MultiplayerReplicator(this)); clear(); } MultiplayerAPI::~MultiplayerAPI() { clear(); + memdelete(replicator); } diff --git a/core/io/multiplayer_api.h b/core/io/multiplayer_api.h index 011bc3dde9..3c96a3eed1 100644 --- a/core/io/multiplayer_api.h +++ b/core/io/multiplayer_api.h @@ -32,17 +32,19 @@ #define MULTIPLAYER_API_H #include "core/io/multiplayer_peer.h" +#include "core/io/resource_uid.h" #include "core/object/ref_counted.h" +class MultiplayerReplicator; + 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_REMOTE, // Using rpc() on it will call method in all remote peers - RPC_MODE_MASTER, // Using rpc() on it will call method on wherever the master is, be it local or remote - RPC_MODE_PUPPET, // Using rpc() on it will call method for all puppets + 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 { @@ -64,6 +66,33 @@ 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, // 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, + }; + + enum { + NODE_ID_COMPRESSION_SHIFT = 3, + NAME_ID_COMPRESSION_SHIFT = 5, + BYTE_ONLY_OR_NO_ARGS_SHIFT = 6, + }; + private: //path sent caches struct PathSentCache { @@ -90,6 +119,7 @@ private: Vector<uint8_t> packet_cache; Node *root_node = nullptr; bool allow_object_decoding = false; + MultiplayerReplicator *replicator = nullptr; protected: static void _bind_methods(); @@ -104,28 +134,7 @@ protected: 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); - 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); - public: - enum NetworkCommands { - NETWORK_COMMAND_REMOTE_CALL = 0, - NETWORK_COMMAND_SIMPLIFY_PATH, - NETWORK_COMMAND_CONFIRM_PATH, - NETWORK_COMMAND_RAW, - }; - - 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, - }; - void poll(); void clear(); void set_root_node(Node *p_node); @@ -134,8 +143,16 @@ public: Ref<MultiplayerPeer> get_network_peer() const; Error send_bytes(Vector<uint8_t> p_data, int p_to = MultiplayerPeer::TARGET_PEER_BROADCAST, MultiplayerPeer::TransferMode p_mode = MultiplayerPeer::TRANSFER_MODE_RELIABLE, 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); + // 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); void _add_peer(int p_id); void _del_peer(int p_id); @@ -154,6 +171,8 @@ public: void set_allow_object_decoding(bool p_enable); bool is_object_decoding_allowed() const; + MultiplayerReplicator *get_replicator() const; + MultiplayerAPI(); ~MultiplayerAPI(); }; diff --git a/core/io/multiplayer_replicator.cpp b/core/io/multiplayer_replicator.cpp new file mode 100644 index 0000000000..1642aab136 --- /dev/null +++ b/core/io/multiplayer_replicator.cpp @@ -0,0 +1,786 @@ +/*************************************************************************/ +/* multiplayer_replicator.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/io/multiplayer_replicator.h" + +#include "core/io/marshalls.h" +#include "scene/main/node.h" +#include "scene/resources/packed_scene.h" + +#define MAKE_ROOM(m_amount) \ + if (packet_cache.size() < m_amount) \ + packet_cache.resize(m_amount); + +Error MultiplayerReplicator::_sync_all_default(const ResourceUID::ID &p_scene_id, int p_peer) { + ERR_FAIL_COND_V(!replications.has(p_scene_id), ERR_INVALID_PARAMETER); + SceneConfig &cfg = replications[p_scene_id]; + int full_size = 0; + bool same_size = true; + int last_size = 0; + bool all_raw = true; + struct EncodeInfo { + int size = 0; + bool raw = false; + List<Variant> state; + }; + Map<ObjectID, struct EncodeInfo> state; + if (tracked_objects.has(p_scene_id)) { + for (const ObjectID &obj_id : tracked_objects[p_scene_id]) { + Object *obj = ObjectDB::get_instance(obj_id); + if (obj) { + struct EncodeInfo info; + Error err = _get_state(cfg.sync_properties, obj, info.state); + ERR_CONTINUE(err); + err = _encode_state(info.state, nullptr, info.size, &info.raw); + ERR_CONTINUE(err); + state[obj_id] = info; + full_size += info.size; + if (last_size && info.size != last_size) { + same_size = false; + } + all_raw = all_raw && info.raw; + last_size = info.size; + } + } + } + // Default implementation do not send empty updates. + if (!full_size) { + return OK; + } +#ifdef DEBUG_ENABLED + if (full_size > 4096 && cfg.sync_interval) { + WARN_PRINT_ONCE(vformat("The timed state update for scene %d is big (%d bytes) consider optimizing it", p_scene_id)); + } +#endif + if (same_size) { + // This is fast and small. Should we allow more than 256 objects per type? + // This costs us 1 byte. + MAKE_ROOM(SYNC_CMD_OFFSET + 1 + 2 + 2 + full_size); + } else { + MAKE_ROOM(SYNC_CMD_OFFSET + 1 + 2 + state.size() * 2 + full_size); + } + 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); + ofs = 1; + ofs += encode_uint64(p_scene_id, &ptr[ofs]); + ptr[ofs] = cfg.sync_recv++; + ofs += 1; + ofs += encode_uint16(state.size(), &ptr[ofs]); + if (same_size) { + ofs += encode_uint16(last_size + (all_raw ? 1 << 15 : 0), &ptr[ofs]); + } + for (const ObjectID &obj_id : tracked_objects[p_scene_id]) { + if (!state.has(obj_id)) { + continue; + } + struct EncodeInfo &info = state[obj_id]; + Object *obj = ObjectDB::get_instance(obj_id); + ERR_CONTINUE(!obj); + int size = 0; + if (!same_size) { + // We need to encode the size of every object. + ofs += encode_uint16(info.size + (info.raw ? 1 << 15 : 0), &ptr[ofs]); + } + Error err = _encode_state(info.state, &ptr[ofs], size, &info.raw); + ERR_CONTINUE(err); + ofs += size; + } + Ref<MultiplayerPeer> 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); + return network_peer->put_packet(ptr, ofs); +} + +void MultiplayerReplicator::_process_default_sync(const ResourceUID::ID &p_id, const uint8_t *p_packet, int p_packet_len) { + ERR_FAIL_COND_MSG(p_packet_len < SYNC_CMD_OFFSET + 5, "Invalid spawn packet received"); + 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; + int ofs = SYNC_CMD_OFFSET; + int time = p_packet[ofs]; + // Skip old update. + if (time < cfg.sync_recv && cfg.sync_recv - time < 127) { + return; + } + cfg.sync_recv = time; + ofs += 1; + int count = decode_uint16(&p_packet[ofs]); + ofs += 2; +#ifdef DEBUG_ENABLED + ERR_FAIL_COND(!tracked_objects.has(p_id) || tracked_objects[p_id].size() != count); +#else + if (!tracked_objects.has(p_id) || tracked_objects[p_id].size() != count) { + return; + } +#endif + int data_size = 0; + bool raw = false; + if (same_size) { + // This is fast and optimized. + data_size = decode_uint16(&p_packet[ofs]); + raw = (data_size & (1 << 15)) != 0; + data_size = data_size & ~(1 << 15); + ofs += 2; + ERR_FAIL_COND(p_packet_len - ofs < data_size * count); + } + for (const ObjectID &obj_id : tracked_objects[p_id]) { + Object *obj = ObjectDB::get_instance(obj_id); + ERR_CONTINUE(!obj); + if (!same_size) { + // This is slow and wasteful. + data_size = decode_uint16(&p_packet[ofs]); + raw = (data_size & (1 << 15)) != 0; + data_size = data_size & ~(1 << 15); + ofs += 2; + ERR_FAIL_COND(p_packet_len - ofs < data_size); + } + int size = 0; + Error err = _decode_state(cfg.sync_properties, obj, &p_packet[ofs], data_size, size, raw); + ofs += data_size; + ERR_CONTINUE(err); + ERR_CONTINUE(size != data_size); + } +} + +Error MultiplayerReplicator::_send_default_spawn_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, Object *p_obj, const NodePath &p_path, bool p_spawn) { + ERR_FAIL_COND_V(p_spawn && !p_obj, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(!replications.has(p_scene_id), ERR_INVALID_PARAMETER); + Error err; + // Prepare state + List<Variant> state_variants; + int state_len = 0; + const SceneConfig &cfg = replications[p_scene_id]; + if (p_spawn) { + if ((err = _get_state(cfg.properties, p_obj, state_variants)) != OK) { + return err; + } + } + + bool is_raw = false; + if (state_variants.size() == 1 && state_variants[0].get_type() == Variant::PACKED_BYTE_ARRAY) { + is_raw = true; + } else if (state_variants.size()) { + err = _encode_state(state_variants, nullptr, state_len); + ERR_FAIL_COND_V(err, err); + } else { + is_raw = true; + } + + int ofs = 0; + + // Prepare simplified path + const Node *root_node = multiplayer->get_root_node(); + ERR_FAIL_COND_V(!root_node, ERR_UNCONFIGURED); + NodePath rel_path = (root_node->get_path()).rel_path_to(p_path); + const Vector<StringName> names = rel_path.get_names(); + ERR_FAIL_COND_V(names.size() < 2, ERR_INVALID_PARAMETER); + + NodePath parent = NodePath(names.subarray(0, names.size() - 2), false); + ERR_FAIL_COND_V_MSG(!root_node->has_node(parent), ERR_INVALID_PARAMETER, "Path not found: " + parent); + + int path_id = 0; + multiplayer->send_confirm_path(root_node->get_node(parent), parent, p_peer_id, path_id); + + // Encode name and parent ID. + CharString cname = String(names[names.size() - 1]).utf8(); + 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); + ofs = 1; + ofs += encode_uint64(p_scene_id, &ptr[ofs]); + ofs += encode_uint32(path_id, &ptr[ofs]); + ofs += encode_uint32(nlen, &ptr[ofs]); + ofs += encode_cstring(cname.get_data(), &ptr[ofs]); + + // Encode state. + if (!is_raw) { + _encode_state(state_variants, &ptr[ofs], state_len); + } else if (state_len) { + PackedByteArray pba = state_variants[0]; + memcpy(&ptr[ofs], pba.ptr(), state_len); + } + + Ref<MultiplayerPeer> 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); + return network_peer->put_packet(ptr, ofs + state_len); +} + +void MultiplayerReplicator::_process_default_spawn_despawn(int p_from, const ResourceUID::ID &p_scene_id, const uint8_t *p_packet, int p_packet_len, bool p_spawn) { + ERR_FAIL_COND_MSG(p_packet_len < SPAWN_CMD_OFFSET + 9, "Invalid spawn packet received"); + int ofs = SPAWN_CMD_OFFSET; + uint32_t node_target = decode_uint32(&p_packet[ofs]); + Node *parent = multiplayer->get_cached_node(p_from, node_target); + ofs += 4; + ERR_FAIL_COND_MSG(parent == nullptr, "Invalid packet received. Requested node was not found."); + + uint32_t name_len = decode_uint32(&p_packet[ofs]); + ofs += 4; + ERR_FAIL_COND_MSG(name_len > uint32_t(p_packet_len - ofs), vformat("Invalid spawn packet size: %d, wants: %d", p_packet_len, ofs + name_len)); + ERR_FAIL_COND_MSG(name_len < 1, "Zero spawn name size."); + + const String name = String::utf8((const char *)&p_packet[ofs], name_len); + // We need to make sure no trickery happens here (e.g. despawning a subpath), but we want to allow autogenerated ("@") node names. + ERR_FAIL_COND_MSG(name.validate_node_name() != name.replace("@", ""), vformat("Invalid node name received: '%s'", name)); + ofs += name_len; + + const SceneConfig &cfg = replications[p_scene_id]; + 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; + + 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); + ERR_FAIL_COND_MSG(!res.is_valid(), "Unable to load scene to spawn at path: " + scene_path); + PackedScene *scene = Object::cast_to<PackedScene>(res.ptr()); + ERR_FAIL_COND(!scene); + Node *node = scene->instantiate(); + ERR_FAIL_COND(!node); + replicated_nodes[node->get_instance_id()] = p_scene_id; + _track(p_scene_id, node); + int size; + _decode_state(cfg.properties, node, &p_packet[ofs], p_packet_len - ofs, size, is_raw); + parent->_add_child_nocheck(node, name); + emit_signal(SNAME("spawned"), p_scene_id, node); + } else { + ERR_FAIL_COND_MSG(!parent->has_node(name), vformat("Path not found: %s/%s", parent->get_path(), name)); + Node *node = parent->get_node(name); + ERR_FAIL_COND_MSG(!replicated_nodes.has(node->get_instance_id()), vformat("Trying to despawn a Node that was not replicated: %s/%s", parent->get_path(), name)); + emit_signal(SNAME("despawned"), p_scene_id, node); + _untrack(p_scene_id, node); + replicated_nodes.erase(node->get_instance_id()); + node->queue_delete(); + } + } else { + PackedByteArray data; + if (p_packet_len > ofs) { + data.resize(p_packet_len - ofs); + memcpy(data.ptrw(), &p_packet[ofs], data.size()); + } + if (p_spawn) { + emit_signal(SNAME("spawn_requested"), p_from, p_scene_id, parent, name, data); + } else { + emit_signal(SNAME("despawn_requested"), p_from, p_scene_id, parent, name, data); + } + } +} + +void MultiplayerReplicator::process_spawn_despawn(int p_from, const uint8_t *p_packet, int p_packet_len, bool p_spawn) { + ERR_FAIL_COND_MSG(p_packet_len < SPAWN_CMD_OFFSET, "Invalid spawn packet received"); + ResourceUID::ID id = decode_uint64(&p_packet[1]); + ERR_FAIL_COND_MSG(!replications.has(id), "Invalid spawn ID received " + itos(id)); + + 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; + Variant data; + int left = p_packet_len - ofs; + if (is_raw && left) { + PackedByteArray pba; + pba.resize(left); + memcpy(pba.ptrw(), &p_packet[ofs], pba.size()); + data = pba; + } else if (left) { + ERR_FAIL_COND(decode_variant(data, &p_packet[ofs], left) != OK); + } + + Variant args[4]; + args[0] = p_from; + args[1] = id; + args[2] = data; + args[3] = p_spawn; + const Variant *argp[] = { &args[0], &args[1], &args[2], &args[3] }; + Callable::CallError ce; + Variant ret; + cfg.on_spawn_despawn_receive.call(argp, 4, ret, ce); + ERR_FAIL_COND_MSG(ce.error != Callable::CallError::CALL_OK, "Custom receive function failed"); + } else { + _process_default_spawn_despawn(p_from, id, p_packet, p_packet_len, p_spawn); + } +} + +void MultiplayerReplicator::process_sync(int p_from, const uint8_t *p_packet, int p_packet_len) { + ERR_FAIL_COND_MSG(p_packet_len < SPAWN_CMD_OFFSET, "Invalid spawn packet received"); + ResourceUID::ID id = decode_uint64(&p_packet[1]); + ERR_FAIL_COND_MSG(!replications.has(id), "Invalid spawn ID received " + itos(id)); + const SceneConfig &cfg = replications[id]; + if (cfg.on_sync_receive.is_valid()) { + Array objs; + if (tracked_objects.has(id)) { + objs.resize(tracked_objects[id].size()); + int idx = 0; + for (const ObjectID &obj_id : tracked_objects[id]) { + objs[idx++] = ObjectDB::get_instance(obj_id); + } + } + PackedByteArray pba; + pba.resize(p_packet_len - SPAWN_CMD_OFFSET); + if (pba.size()) { + memcpy(pba.ptrw(), p_packet, p_packet_len - SPAWN_CMD_OFFSET); + } + Variant args[4] = { p_from, id, objs, pba }; + Variant *argp[4] = { args, &args[1], &args[2], &args[3] }; + Callable::CallError ce; + Variant ret; + cfg.on_sync_receive.call((const Variant **)argp, 4, ret, ce); + ERR_FAIL_COND_MSG(ce.error != Callable::CallError::CALL_OK, "Custom sync function failed"); + } else { + ERR_FAIL_COND_MSG(p_from != 1, "Default sync implementation only allow syncing from server to client"); + _process_default_sync(id, p_packet, p_packet_len); + } +} + +Error MultiplayerReplicator::_get_state(const List<StringName> &p_properties, const Object *p_obj, List<Variant> &r_variant) { + ERR_FAIL_COND_V_MSG(!p_obj, ERR_INVALID_PARAMETER, "Cannot encode null object"); + for (const StringName &prop : p_properties) { + bool valid = false; + const Variant v = p_obj->get(prop, &valid); + ERR_FAIL_COND_V_MSG(!valid, ERR_INVALID_DATA, vformat("Property '%s' not found.", prop)); + r_variant.push_back(v); + } + return OK; +} + +Error MultiplayerReplicator::_encode_state(const List<Variant> &p_variants, uint8_t *p_buffer, int &r_len, bool *r_raw) { + r_len = 0; + int size = 0; + + // Try raw encoding optimization. + if (r_raw && p_variants.size() == 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 { + multiplayer->encode_and_compress_variant(v, p_buffer, size); + r_len += size; + } + return OK; + } + + // Regular encoding. + for (const Variant &v : p_variants) { + multiplayer->encode_and_compress_variant(v, p_buffer ? p_buffer + r_len : nullptr, size); + r_len += size; + } + return OK; +} + +Error MultiplayerReplicator::_decode_state(const List<StringName> &p_properties, Object *p_obj, const uint8_t *p_buffer, int p_len, int &r_len, bool p_raw) { + r_len = 0; + int argc = p_properties.size(); + if (argc == 0 && p_raw) { + ERR_FAIL_COND_V_MSG(p_len != 0, ERR_INVALID_DATA, "Buffer has trailing bytes."); + 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); + p_obj->set(p_properties[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 = multiplayer->decode_and_decompress_variant(args.write[i], &p_buffer[r_len], p_len - r_len, &vlen); + ERR_FAIL_COND_V_MSG(err != OK, err, "Invalid packet received. Unable to decode state variable."); + r_len += vlen; + } + ERR_FAIL_COND_V_MSG(p_len - r_len != 0, ERR_INVALID_DATA, "Buffer has trailing bytes."); + + int i = 0; + for (const StringName &prop : p_properties) { + p_obj->set(prop, args[i]); + i += 1; + } + return OK; +} + +Error MultiplayerReplicator::spawn_config(const ResourceUID::ID &p_id, ReplicationMode p_mode, const TypedArray<StringName> &p_props, const Callable &p_on_send, const Callable &p_on_recv) { + ERR_FAIL_COND_V(p_mode < REPLICATION_MODE_NONE || p_mode > REPLICATION_MODE_CUSTOM, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(!ResourceUID::get_singleton()->has_id(p_id), ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V_MSG(p_on_send.is_valid() != p_on_recv.is_valid(), ERR_INVALID_PARAMETER, "Send and receive custom callables must be both valid or both empty"); +#ifdef TOOLS_ENABLED + if (!p_on_send.is_valid()) { + // We allow non scene spawning with custom callables. + String path = ResourceUID::get_singleton()->get_id_path(p_id); + RES res = ResourceLoader::load(path); + ERR_FAIL_COND_V(!res->is_class("PackedScene"), ERR_INVALID_PARAMETER); + } +#endif + if (p_mode == REPLICATION_MODE_NONE) { + if (replications.has(p_id)) { + replications.erase(p_id); + } + } else { + SceneConfig cfg; + cfg.mode = p_mode; + for (int i = 0; i < p_props.size(); i++) { + cfg.properties.push_back(StringName(p_props[i])); + } + cfg.on_spawn_despawn_send = p_on_send; + cfg.on_spawn_despawn_receive = p_on_recv; + replications[p_id] = cfg; + } + return OK; +} + +Error MultiplayerReplicator::sync_config(const ResourceUID::ID &p_id, uint64_t p_interval, const TypedArray<StringName> &p_props, const Callable &p_on_send, const Callable &p_on_recv) { + ERR_FAIL_COND_V(!ResourceUID::get_singleton()->has_id(p_id), ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V_MSG(p_on_send.is_valid() != p_on_recv.is_valid(), ERR_INVALID_PARAMETER, "Send and receive custom callables must be both valid or both empty"); + ERR_FAIL_COND_V(!replications.has(p_id), ERR_UNCONFIGURED); + SceneConfig &cfg = replications[p_id]; + ERR_FAIL_COND_V_MSG(p_interval && cfg.mode != REPLICATION_MODE_SERVER && !p_on_send.is_valid(), ERR_INVALID_PARAMETER, "Timed updates in custom mode are only allowed if custom callbacks are also specified"); + for (int i = 0; i < p_props.size(); i++) { + cfg.sync_properties.push_back(p_props[i]); + } + cfg.on_sync_send = p_on_send; + cfg.on_sync_receive = p_on_recv; + cfg.sync_interval = p_interval * 1000; + return OK; +} + +Error MultiplayerReplicator::_send_spawn_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const Variant &p_data, bool p_spawn) { + int data_size = 0; + int is_raw = false; + if (p_data.get_type() == Variant::PACKED_BYTE_ARRAY) { + const PackedByteArray pba = p_data; + is_raw = true; + data_size = p_data.operator PackedByteArray().size(); + } else if (p_data.get_type() == Variant::NIL) { + is_raw = true; + } else { + Error err = encode_variant(p_data, nullptr, data_size); + ERR_FAIL_COND_V(err, err); + } + 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); + encode_uint64(p_scene_id, &ptr[1]); + if (p_data.get_type() == Variant::PACKED_BYTE_ARRAY) { + const PackedByteArray pba = p_data; + memcpy(&ptr[SPAWN_CMD_OFFSET], pba.ptr(), pba.size()); + } else if (data_size) { + encode_variant(p_data, &ptr[SPAWN_CMD_OFFSET], data_size); + } + Ref<MultiplayerPeer> 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); + return network_peer->put_packet(ptr, SPAWN_CMD_OFFSET + data_size); +} + +Error MultiplayerReplicator::send_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const Variant &p_data, const NodePath &p_path) { + ERR_FAIL_COND_V(!multiplayer->has_network_peer(), ERR_UNCONFIGURED); + ERR_FAIL_COND_V_MSG(!replications.has(p_scene_id), ERR_INVALID_PARAMETER, vformat("Spawnable not found: %d", p_scene_id)); + const SceneConfig &cfg = replications[p_scene_id]; + if (cfg.on_spawn_despawn_send.is_valid()) { + return _send_spawn_despawn(p_peer_id, p_scene_id, p_data, true); + } else { + ERR_FAIL_COND_V_MSG(cfg.mode == REPLICATION_MODE_SERVER && multiplayer->is_network_server(), ERR_UNAVAILABLE, "Manual despawn is restricted in default server mode implementation. Use custom mode if you desire control over server spawn requests."); + NodePath path = p_path; + Object *obj = p_data.get_type() == Variant::OBJECT ? p_data.get_validated_object() : nullptr; + if (path.is_empty() && obj) { + Node *node = Object::cast_to<Node>(obj); + if (node && node->is_inside_tree()) { + path = node->get_path(); + } + } + ERR_FAIL_COND_V_MSG(path.is_empty(), ERR_INVALID_PARAMETER, "Despawn default implementation requires a despawn path, or the data to be a node inside the SceneTree"); + return _send_default_spawn_despawn(p_peer_id, p_scene_id, obj, path, false); + } +} + +Error MultiplayerReplicator::send_spawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const Variant &p_data, const NodePath &p_path) { + ERR_FAIL_COND_V(!multiplayer->has_network_peer(), ERR_UNCONFIGURED); + ERR_FAIL_COND_V_MSG(!replications.has(p_scene_id), ERR_INVALID_PARAMETER, vformat("Spawnable not found: %d", p_scene_id)); + const SceneConfig &cfg = replications[p_scene_id]; + if (cfg.on_spawn_despawn_send.is_valid()) { + return _send_spawn_despawn(p_peer_id, p_scene_id, p_data, false); + } else { + ERR_FAIL_COND_V_MSG(cfg.mode == REPLICATION_MODE_SERVER && multiplayer->is_network_server(), ERR_UNAVAILABLE, "Manual spawn is restricted in default server mode implementation. Use custom mode if you desire control over server spawn requests."); + NodePath path = p_path; + Object *obj = p_data.get_type() == Variant::OBJECT ? p_data.get_validated_object() : nullptr; + ERR_FAIL_COND_V_MSG(!obj, ERR_INVALID_PARAMETER, "Spawn default implementation requires the data to be an object."); + if (path.is_empty()) { + Node *node = Object::cast_to<Node>(obj); + if (node && node->is_inside_tree()) { + path = node->get_path(); + } + } + ERR_FAIL_COND_V_MSG(path.is_empty(), ERR_INVALID_PARAMETER, "Spawn default implementation requires a spawn path, or the data to be a node inside the SceneTree"); + return _send_default_spawn_despawn(p_peer_id, p_scene_id, obj, path, true); + } +} + +Error MultiplayerReplicator::_spawn_despawn(ResourceUID::ID p_scene_id, Object *p_obj, int p_peer, bool p_spawn) { + ERR_FAIL_COND_V_MSG(!replications.has(p_scene_id), ERR_INVALID_PARAMETER, vformat("Spawnable not found: %d", p_scene_id)); + + const SceneConfig &cfg = replications[p_scene_id]; + if (cfg.on_spawn_despawn_send.is_valid()) { + Variant args[4]; + args[0] = p_peer; + args[1] = p_scene_id; + args[2] = p_obj; + args[3] = true; + const Variant *argp[] = { &args[0], &args[1], &args[2], &args[3] }; + Callable::CallError ce; + Variant ret; + cfg.on_spawn_despawn_send.call(argp, 4, ret, ce); + ERR_FAIL_COND_V_MSG(ce.error != Callable::CallError::CALL_OK, FAILED, "Custom send function failed"); + return OK; + } else { + Node *node = Object::cast_to<Node>(p_obj); + ERR_FAIL_COND_V_MSG(!p_obj, ERR_INVALID_PARAMETER, "Only nodes can be replicated by the default implementation"); + return _send_default_spawn_despawn(p_peer, p_scene_id, node, node->get_path(), p_spawn); + } +} + +Error MultiplayerReplicator::spawn(ResourceUID::ID p_scene_id, Object *p_obj, int p_peer) { + return _spawn_despawn(p_scene_id, p_obj, p_peer, true); +} + +Error MultiplayerReplicator::despawn(ResourceUID::ID p_scene_id, Object *p_obj, int p_peer) { + return _spawn_despawn(p_scene_id, p_obj, p_peer, false); +} + +PackedByteArray MultiplayerReplicator::encode_state(const ResourceUID::ID &p_scene_id, const Object *p_obj, bool p_initial) { + PackedByteArray state; + ERR_FAIL_COND_V_MSG(!replications.has(p_scene_id), state, vformat("Spawnable not found: %d", p_scene_id)); + const SceneConfig &cfg = replications[p_scene_id]; + int len = 0; + List<Variant> state_vars; + const List<StringName> props = p_initial ? cfg.properties : cfg.sync_properties; + Error err = _get_state(props, p_obj, state_vars); + ERR_FAIL_COND_V_MSG(err != OK, state, "Unable to retrieve object state."); + err = _encode_state(state_vars, nullptr, len); + ERR_FAIL_COND_V_MSG(err != OK, state, "Unable to encode object state."); + state.resize(len); + _encode_state(state_vars, state.ptrw(), len); + return state; +} + +Error MultiplayerReplicator::decode_state(const ResourceUID::ID &p_scene_id, Object *p_obj, const PackedByteArray p_data, bool p_initial) { + ERR_FAIL_COND_V_MSG(!replications.has(p_scene_id), ERR_INVALID_PARAMETER, vformat("Spawnable not found: %d", p_scene_id)); + const SceneConfig &cfg = replications[p_scene_id]; + const List<StringName> props = p_initial ? cfg.properties : cfg.sync_properties; + int size; + return _decode_state(props, p_obj, p_data.ptr(), p_data.size(), size); +} + +void MultiplayerReplicator::scene_enter_exit_notify(const String &p_scene, Node *p_node, bool p_enter) { + if (!multiplayer->has_network_peer()) { + return; + } + Node *root_node = multiplayer->get_root_node(); + ERR_FAIL_COND(!p_node || !p_node->get_parent() || !root_node); + NodePath path = (root_node->get_path()).rel_path_to(p_node->get_parent()->get_path()); + if (path.is_empty()) { + return; + } + ResourceUID::ID id = ResourceLoader::get_resource_uid(p_scene); + if (!replications.has(id)) { + return; + } + const SceneConfig &cfg = replications[id]; + if (p_enter) { + if (cfg.mode == REPLICATION_MODE_SERVER && multiplayer->is_network_server()) { + replicated_nodes[p_node->get_instance_id()] = id; + _track(id, p_node); + spawn(id, p_node, 0); + } + emit_signal(SNAME("replicated_instance_added"), id, p_node); + } else { + if (cfg.mode == REPLICATION_MODE_SERVER && multiplayer->is_network_server() && replicated_nodes.has(p_node->get_instance_id())) { + replicated_nodes.erase(p_node->get_instance_id()); + _untrack(id, p_node); + despawn(id, p_node, 0); + } + emit_signal(SNAME("replicated_instance_removed"), id, p_node); + } +} + +void MultiplayerReplicator::spawn_all(int p_peer) { + for (const KeyValue<ObjectID, ResourceUID::ID> &E : replicated_nodes) { + // Only server mode adds to replicated_nodes, no need to check it. + Object *obj = ObjectDB::get_instance(E.key); + ERR_CONTINUE(!obj); + Node *node = Object::cast_to<Node>(obj); + ERR_CONTINUE(!node); + spawn(E.value, node, p_peer); + } +} + +void MultiplayerReplicator::poll() { + for (KeyValue<ResourceUID::ID, SceneConfig> &E : replications) { + if (!E.value.sync_interval) { + continue; + } + if (E.value.mode == REPLICATION_MODE_SERVER && !multiplayer->is_network_server()) { + continue; + } + uint64_t time = OS::get_singleton()->get_ticks_usec(); + if (E.value.sync_last + E.value.sync_interval <= time) { + sync_all(E.key, 0); + E.value.sync_last = time; + } + // Handle wrapping. + if (E.value.sync_last > time) { + E.value.sync_last = time; + } + } +} + +void MultiplayerReplicator::track(const ResourceUID::ID &p_scene_id, Object *p_obj) { + ERR_FAIL_COND(!replications.has(p_scene_id)); + const SceneConfig &cfg = replications[p_scene_id]; + ERR_FAIL_COND_MSG(cfg.mode == REPLICATION_MODE_SERVER, "Manual object tracking is not allowed in server mode."); + _track(p_scene_id, p_obj); +} + +void MultiplayerReplicator::_track(const ResourceUID::ID &p_scene_id, Object *p_obj) { + ERR_FAIL_COND(!p_obj); + ERR_FAIL_COND(!replications.has(p_scene_id)); + if (!tracked_objects.has(p_scene_id)) { + tracked_objects[p_scene_id] = List<ObjectID>(); + } + tracked_objects[p_scene_id].push_back(p_obj->get_instance_id()); +} + +void MultiplayerReplicator::untrack(const ResourceUID::ID &p_scene_id, Object *p_obj) { + ERR_FAIL_COND(!replications.has(p_scene_id)); + const SceneConfig &cfg = replications[p_scene_id]; + ERR_FAIL_COND_MSG(cfg.mode == REPLICATION_MODE_SERVER, "Manual object tracking is not allowed in server mode."); + _untrack(p_scene_id, p_obj); +} + +void MultiplayerReplicator::_untrack(const ResourceUID::ID &p_scene_id, Object *p_obj) { + ERR_FAIL_COND(!p_obj); + ERR_FAIL_COND(!replications.has(p_scene_id)); + if (tracked_objects.has(p_scene_id)) { + tracked_objects[p_scene_id].erase(p_obj->get_instance_id()); + } +} + +Error MultiplayerReplicator::sync_all(const ResourceUID::ID &p_scene_id, int p_peer) { + ERR_FAIL_COND_V(!replications.has(p_scene_id), ERR_INVALID_PARAMETER); + if (!tracked_objects.has(p_scene_id)) { + return OK; + } + const SceneConfig &cfg = replications[p_scene_id]; + if (cfg.on_sync_send.is_valid()) { + Array objs; + if (tracked_objects.has(p_scene_id)) { + objs.resize(tracked_objects[p_scene_id].size()); + int idx = 0; + for (const ObjectID &obj_id : tracked_objects[p_scene_id]) { + objs[idx++] = ObjectDB::get_instance(obj_id); + } + } + Variant args[3] = { p_scene_id, objs, p_peer }; + Variant *argp[3] = { args, &args[1], &args[2] }; + Callable::CallError ce; + Variant ret; + cfg.on_sync_send.call((const Variant **)argp, 3, ret, ce); + ERR_FAIL_COND_V_MSG(ce.error != Callable::CallError::CALL_OK, FAILED, "Custom sync function failed"); + return OK; + } else if (cfg.sync_properties.size()) { + return _sync_all_default(p_scene_id, p_peer); + } + 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) { + 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]; + ERR_FAIL_COND_V_MSG(!cfg.on_sync_send.is_valid(), ERR_UNCONFIGURED, "Sending raw sync messages is only available with custom functions"); + MAKE_ROOM(SYNC_CMD_OFFSET + p_data.size()); + uint8_t *ptr = packet_cache.ptrw(); + ptr[0] = MultiplayerAPI::NETWORK_COMMAND_SYNC; + encode_uint64(p_scene_id, &ptr[1]); + Ref<MultiplayerPeer> network_peer = multiplayer->get_network_peer(); + network_peer->set_target_peer(p_peer_id); + network_peer->set_transfer_channel(p_channel); + network_peer->set_transfer_mode(p_transfer_mode); + return network_peer->put_packet(ptr, SYNC_CMD_OFFSET + p_data.size()); +} + +void MultiplayerReplicator::clear() { + tracked_objects.clear(); + replicated_nodes.clear(); +} + +void MultiplayerReplicator::_bind_methods() { + ClassDB::bind_method(D_METHOD("spawn_config", "scene_id", "spawn_mode", "properties", "custom_send", "custom_receive"), &MultiplayerReplicator::spawn_config, DEFVAL(TypedArray<StringName>()), DEFVAL(Callable()), DEFVAL(Callable())); + ClassDB::bind_method(D_METHOD("sync_config", "scene_id", "interval", "properties", "custom_send", "custom_receive"), &MultiplayerReplicator::sync_config, DEFVAL(TypedArray<StringName>()), DEFVAL(Callable()), DEFVAL(Callable())); + ClassDB::bind_method(D_METHOD("despawn", "scene_id", "object", "peer_id"), &MultiplayerReplicator::despawn, DEFVAL(0)); + 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("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); + ClassDB::bind_method(D_METHOD("encode_state", "scene_id", "object", "initial"), &MultiplayerReplicator::encode_state, DEFVAL(true)); + ClassDB::bind_method(D_METHOD("decode_state", "scene_id", "object", "data", "initial"), &MultiplayerReplicator::decode_state, DEFVAL(true)); + + ADD_SIGNAL(MethodInfo("despawned", PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node"))); + ADD_SIGNAL(MethodInfo("spawned", PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node"))); + ADD_SIGNAL(MethodInfo("despawn_requested", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "parent", PROPERTY_HINT_RESOURCE_TYPE, "Node"), PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data"))); + ADD_SIGNAL(MethodInfo("spawn_requested", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "parent", PROPERTY_HINT_RESOURCE_TYPE, "Node"), PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data"))); + ADD_SIGNAL(MethodInfo("replicated_instance_added", PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node"))); + ADD_SIGNAL(MethodInfo("replicated_instance_removed", PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node"))); + + BIND_ENUM_CONSTANT(REPLICATION_MODE_NONE); + BIND_ENUM_CONSTANT(REPLICATION_MODE_SERVER); + BIND_ENUM_CONSTANT(REPLICATION_MODE_CUSTOM); +} diff --git a/core/io/multiplayer_replicator.h b/core/io/multiplayer_replicator.h new file mode 100644 index 0000000000..2630ad7a8a --- /dev/null +++ b/core/io/multiplayer_replicator.h @@ -0,0 +1,129 @@ +/*************************************************************************/ +/* multiplayer_replicator.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_REPLICATOR_H +#define MULTIPLAYER_REPLICATOR_H + +#include "core/io/multiplayer_api.h" + +#include "core/templates/hash_map.h" +#include "core/variant/typed_array.h" + +class MultiplayerReplicator : public Object { + GDCLASS(MultiplayerReplicator, Object); + +public: + enum { + SPAWN_CMD_OFFSET = 9, + SYNC_CMD_OFFSET = 9, + }; + + enum ReplicationMode { + REPLICATION_MODE_NONE, + REPLICATION_MODE_SERVER, + REPLICATION_MODE_CUSTOM, + }; + + struct SceneConfig { + ReplicationMode mode; + uint64_t sync_interval = 0; + uint64_t sync_last = 0; + uint8_t sync_recv = 0; + List<StringName> properties; + List<StringName> sync_properties; + Callable on_spawn_despawn_send; + Callable on_spawn_despawn_receive; + Callable on_sync_send; + Callable on_sync_receive; + }; + +protected: + static void _bind_methods(); + +private: + MultiplayerAPI *multiplayer = nullptr; + Vector<uint8_t> packet_cache; + Map<ResourceUID::ID, SceneConfig> replications; + Map<ObjectID, ResourceUID::ID> replicated_nodes; + HashMap<ResourceUID::ID, List<ObjectID>> tracked_objects; + + // Encoding + Error _get_state(const List<StringName> &p_properties, const Object *p_obj, List<Variant> &r_variant); + Error _encode_state(const List<Variant> &p_variants, uint8_t *p_buffer, int &r_len, bool *r_raw = nullptr); + Error _decode_state(const List<StringName> &p_cfg, Object *p_obj, const uint8_t *p_buffer, int p_len, int &r_len, bool p_raw = false); + + // Spawn + Error _spawn_despawn(ResourceUID::ID p_scene_id, Object *p_obj, int p_peer, bool p_spawn); + Error _send_spawn_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const Variant &p_data, bool p_spawn); + void _process_default_spawn_despawn(int p_from, const ResourceUID::ID &p_scene_id, const uint8_t *p_packet, int p_packet_len, bool p_spawn); + Error _send_default_spawn_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, Object *p_obj, const NodePath &p_path, bool p_spawn); + + // Sync + void _process_default_sync(const ResourceUID::ID &p_id, const uint8_t *p_packet, int p_packet_len); + Error _sync_all_default(const ResourceUID::ID &p_scene_id, int p_peer); + void _track(const ResourceUID::ID &p_scene_id, Object *p_object); + void _untrack(const ResourceUID::ID &p_scene_id, Object *p_object); + +public: + void clear(); + + // Encoding + PackedByteArray encode_state(const ResourceUID::ID &p_scene_id, const Object *p_node, bool p_initial); + Error decode_state(const ResourceUID::ID &p_scene_id, Object *p_node, PackedByteArray p_data, bool p_initial); + + // Spawn + Error spawn_config(const ResourceUID::ID &p_id, ReplicationMode p_mode, const TypedArray<StringName> &p_props = TypedArray<StringName>(), const Callable &p_on_send = Callable(), const Callable &p_on_recv = Callable()); + Error spawn(ResourceUID::ID p_scene_id, Object *p_obj, int p_peer = 0); + Error despawn(ResourceUID::ID p_scene_id, Object *p_obj, int p_peer = 0); + Error send_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const Variant &p_data = Variant(), const NodePath &p_path = NodePath()); + Error send_spawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const Variant &p_data = Variant(), const NodePath &p_path = NodePath()); + + // Sync + Error sync_config(const ResourceUID::ID &p_id, uint64_t p_interval, const TypedArray<StringName> &p_props = TypedArray<StringName>(), 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); + void track(const ResourceUID::ID &p_scene_id, Object *p_object); + void untrack(const ResourceUID::ID &p_scene_id, Object *p_object); + + // Used by MultiplayerAPI + void spawn_all(int p_peer); + void process_spawn_despawn(int p_from, const uint8_t *p_packet, int p_packet_len, bool p_spawn); + void process_sync(int p_from, const uint8_t *p_packet, int p_packet_len); + void scene_enter_exit_notify(const String &p_scene, Node *p_node, bool p_enter); + void poll(); + + MultiplayerReplicator(MultiplayerAPI *p_multiplayer) { + multiplayer = p_multiplayer; + } +}; + +VARIANT_ENUM_CAST(MultiplayerReplicator::ReplicationMode); + +#endif // MULTIPLAYER_REPLICATOR_H diff --git a/core/io/resource.cpp b/core/io/resource.cpp index 0262655927..87b4d7195d 100644 --- a/core/io/resource.cpp +++ b/core/io/resource.cpp @@ -352,9 +352,8 @@ Node *Resource::get_local_scene() const { } void Resource::setup_local_to_scene() { - if (get_script_instance()) { - get_script_instance()->call("_setup_local_to_scene"); - } + // Can't use GDVIRTUAL in Resource, so this will have to be done with a signal + emit_signal(SNAME("setup_local_to_scene_requested")); } Node *(*Resource::_get_local_scene_func)() = nullptr; @@ -422,12 +421,12 @@ void Resource::_bind_methods() { ClassDB::bind_method(D_METHOD("duplicate", "subresources"), &Resource::duplicate, DEFVAL(false)); ADD_SIGNAL(MethodInfo("changed")); + ADD_SIGNAL(MethodInfo("setup_local_to_scene_requested")); + ADD_GROUP("Resource", "resource_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "resource_local_to_scene"), "set_local_to_scene", "is_local_to_scene"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "resource_path", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_path", "get_path"); ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "resource_name"), "set_name", "get_name"); - - BIND_VMETHOD(MethodInfo("_setup_local_to_scene")); } Resource::Resource() : diff --git a/core/io/resource.h b/core/io/resource.h index e864b371ad..9ccc247887 100644 --- a/core/io/resource.h +++ b/core/io/resource.h @@ -62,8 +62,6 @@ private: String path_cache; String scene_unique_id; - virtual bool _use_builtin_script() const { return true; } - #ifdef TOOLS_ENABLED uint64_t last_modified_time = 0; uint64_t import_last_modified_time = 0; diff --git a/core/io/resource_format_binary.cpp b/core/io/resource_format_binary.cpp index 00d4d093da..84fd6496a7 100644 --- a/core/io/resource_format_binary.cpp +++ b/core/io/resource_format_binary.cpp @@ -335,7 +335,7 @@ Error ResourceLoaderBinary::parse_variant(Variant &r_v) { String exttype = get_unicode_string(); String path = get_unicode_string(); - if (path.find("://") == -1 && path.is_rel_path()) { + if (path.find("://") == -1 && path.is_relative_path()) { // path is relative to file being loaded, so convert to a resource path path = ProjectSettings::get_singleton()->localize_path(res_path.get_base_dir().plus_file(path)); } @@ -626,7 +626,7 @@ Error ResourceLoaderBinary::load() { path = remaps[path]; } - if (path.find("://") == -1 && path.is_rel_path()) { + if (path.find("://") == -1 && path.is_relative_path()) { // path is relative to file being loaded, so convert to a resource path path = ProjectSettings::get_singleton()->localize_path(path.get_base_dir().plus_file(external_resources[i].path)); } diff --git a/core/io/resource_loader.cpp b/core/io/resource_loader.cpp index d02d827443..3026236f07 100644 --- a/core/io/resource_loader.cpp +++ b/core/io/resource_loader.cpp @@ -68,25 +68,28 @@ bool ResourceFormatLoader::recognize_path(const String &p_path, const String &p_ } bool ResourceFormatLoader::handles_type(const String &p_type) const { - if (get_script_instance() && get_script_instance()->has_method("_handles_type")) { - // I guess custom loaders for custom resources should use "Resource" - return get_script_instance()->call("_handles_type", p_type); + bool success; + if (GDVIRTUAL_CALL(_handles_type, p_type, success)) { + return success; } return false; } String ResourceFormatLoader::get_resource_type(const String &p_path) const { - if (get_script_instance() && get_script_instance()->has_method("_get_resource_type")) { - return get_script_instance()->call("_get_resource_type", p_path); + String ret; + + if (GDVIRTUAL_CALL(_get_resource_type, p_path, ret)) { + return ret; } return ""; } ResourceUID::ID ResourceFormatLoader::get_resource_uid(const String &p_path) const { - if (get_script_instance() && get_script_instance()->has_method("_get_resource_uid")) { - return get_script_instance()->call("_get_resource_uid", p_path); + int64_t uid; + if (GDVIRTUAL_CALL(_get_resource_uid, p_path, uid)) { + return uid; } return ResourceUID::INVALID_ID; @@ -105,27 +108,26 @@ void ResourceLoader::get_recognized_extensions_for_type(const String &p_type, Li } bool ResourceFormatLoader::exists(const String &p_path) const { + bool success; + if (GDVIRTUAL_CALL(_exists, p_path, success)) { + return success; + } return FileAccess::exists(p_path); //by default just check file } void ResourceFormatLoader::get_recognized_extensions(List<String> *p_extensions) const { - if (get_script_instance() && get_script_instance()->has_method("_get_recognized_extensions")) { - PackedStringArray exts = get_script_instance()->call("_get_recognized_extensions"); - - { - const String *r = exts.ptr(); - for (int i = 0; i < exts.size(); ++i) { - p_extensions->push_back(r[i]); - } + PackedStringArray exts; + if (GDVIRTUAL_CALL(_get_recognized_extensions, exts)) { + const String *r = exts.ptr(); + for (int i = 0; i < exts.size(); ++i) { + p_extensions->push_back(r[i]); } } } RES ResourceFormatLoader::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) { - // Check user-defined loader if there's any. Hard fail if it returns an error. - if (get_script_instance() && get_script_instance()->has_method("_load")) { - Variant res = get_script_instance()->call("_load", p_path, p_original_path, p_use_sub_threads, p_cache_mode); - + Variant res; + if (GDVIRTUAL_CALL(_load, p_path, p_original_path, p_use_sub_threads, p_cache_mode, res)) { if (res.get_type() == Variant::INT) { // Error code, abort. if (r_error) { *r_error = (Error)res.operator int64_t(); @@ -143,48 +145,42 @@ RES ResourceFormatLoader::load(const String &p_path, const String &p_original_pa } void ResourceFormatLoader::get_dependencies(const String &p_path, List<String> *p_dependencies, bool p_add_types) { - if (get_script_instance() && get_script_instance()->has_method("_get_dependencies")) { - PackedStringArray deps = get_script_instance()->call("_get_dependencies", p_path, p_add_types); - - { - const String *r = deps.ptr(); - for (int i = 0; i < deps.size(); ++i) { - p_dependencies->push_back(r[i]); - } + PackedStringArray deps; + if (GDVIRTUAL_CALL(_get_dependencies, p_path, p_add_types, deps)) { + const String *r = deps.ptr(); + for (int i = 0; i < deps.size(); ++i) { + p_dependencies->push_back(r[i]); } } } Error ResourceFormatLoader::rename_dependencies(const String &p_path, const Map<String, String> &p_map) { - if (get_script_instance() && get_script_instance()->has_method("_rename_dependencies")) { - Dictionary deps_dict; - for (Map<String, String>::Element *E = p_map.front(); E; E = E->next()) { - deps_dict[E->key()] = E->value(); - } + Dictionary deps_dict; + for (Map<String, String>::Element *E = p_map.front(); E; E = E->next()) { + deps_dict[E->key()] = E->value(); + } - int64_t res = get_script_instance()->call("_rename_dependencies", deps_dict); - return (Error)res; + int64_t err; + if (GDVIRTUAL_CALL(_rename_dependencies, p_path, deps_dict, err)) { + return (Error)err; } return OK; } void ResourceFormatLoader::_bind_methods() { - { - MethodInfo info = MethodInfo(Variant::NIL, "_load", PropertyInfo(Variant::STRING, "path"), PropertyInfo(Variant::STRING, "original_path"), PropertyInfo(Variant::BOOL, "use_sub_threads"), PropertyInfo(Variant::INT, "cache_mode")); - info.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; - BIND_VMETHOD(info); - } - - BIND_VMETHOD(MethodInfo(Variant::PACKED_STRING_ARRAY, "_get_recognized_extensions")); - BIND_VMETHOD(MethodInfo(Variant::BOOL, "_handles_type", PropertyInfo(Variant::STRING_NAME, "typename"))); - BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_resource_type", PropertyInfo(Variant::STRING, "path"))); - BIND_VMETHOD(MethodInfo("_get_dependencies", PropertyInfo(Variant::STRING, "path"), PropertyInfo(Variant::STRING, "add_types"))); - BIND_VMETHOD(MethodInfo(Variant::INT, "_rename_dependencies", PropertyInfo(Variant::STRING, "path"), PropertyInfo(Variant::STRING, "renames"))); - BIND_ENUM_CONSTANT(CACHE_MODE_IGNORE); BIND_ENUM_CONSTANT(CACHE_MODE_REUSE); BIND_ENUM_CONSTANT(CACHE_MODE_REPLACE); + + GDVIRTUAL_BIND(_get_recognized_extensions); + GDVIRTUAL_BIND(_handles_type, "type"); + GDVIRTUAL_BIND(_get_resource_type, "path"); + GDVIRTUAL_BIND(_get_resource_uid, "path"); + GDVIRTUAL_BIND(_get_dependencies, "path", "add_types"); + GDVIRTUAL_BIND(_rename_dependencies, "path", "renames"); + GDVIRTUAL_BIND(_exists, "path"); + GDVIRTUAL_BIND(_load, "path", "original_path", "use_sub_threads", "cache_mode"); } /////////////////////////////////// @@ -282,7 +278,7 @@ static String _validate_local_path(const String &p_path) { ResourceUID::ID uid = ResourceUID::get_singleton()->text_to_id(p_path); if (uid != ResourceUID::INVALID_ID) { return ResourceUID::get_singleton()->get_id_path(uid); - } else if (p_path.is_rel_path()) { + } else if (p_path.is_relative_path()) { return "res://" + p_path; } else { return ProjectSettings::get_singleton()->localize_path(p_path); diff --git a/core/io/resource_loader.h b/core/io/resource_loader.h index e525e80a9d..f1d9815635 100644 --- a/core/io/resource_loader.h +++ b/core/io/resource_loader.h @@ -32,6 +32,8 @@ #define RESOURCE_LOADER_H #include "core/io/resource.h" +#include "core/object/gdvirtual.gen.inc" +#include "core/object/script_language.h" #include "core/os/semaphore.h" #include "core/os/thread.h" @@ -48,6 +50,16 @@ public: protected: static void _bind_methods(); + GDVIRTUAL0RC(Vector<String>, _get_recognized_extensions) + GDVIRTUAL1RC(bool, _handles_type, StringName) + GDVIRTUAL1RC(String, _get_resource_type, String) + GDVIRTUAL1RC(ResourceUID::ID, _get_resource_uid, String) + GDVIRTUAL2RC(Vector<String>, _get_dependencies, String, bool) + GDVIRTUAL2RC(int64_t, _rename_dependencies, String, Dictionary) + GDVIRTUAL1RC(bool, _exists, String) + + GDVIRTUAL4RC(Variant, _load, String, String, bool, int) + public: virtual RES load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE); virtual bool exists(const String &p_path) const; diff --git a/core/io/resource_saver.cpp b/core/io/resource_saver.cpp index 564de5ee69..823b5f75b1 100644 --- a/core/io/resource_saver.cpp +++ b/core/io/resource_saver.cpp @@ -42,44 +42,37 @@ ResourceSavedCallback ResourceSaver::save_callback = nullptr; ResourceSaverGetResourceIDForPath ResourceSaver::save_get_id_for_path = nullptr; Error ResourceFormatSaver::save(const String &p_path, const RES &p_resource, uint32_t p_flags) { - if (get_script_instance() && get_script_instance()->has_method("_save")) { - return (Error)get_script_instance()->call("_save", p_path, p_resource, p_flags).operator int64_t(); + int64_t res; + if (GDVIRTUAL_CALL(_save, p_path, p_resource, p_flags, res)) { + return (Error)res; } return ERR_METHOD_NOT_FOUND; } bool ResourceFormatSaver::recognize(const RES &p_resource) const { - if (get_script_instance() && get_script_instance()->has_method("_recognize")) { - return get_script_instance()->call("_recognize", p_resource); + bool success; + if (GDVIRTUAL_CALL(_recognize, p_resource, success)) { + return success; } return false; } void ResourceFormatSaver::get_recognized_extensions(const RES &p_resource, List<String> *p_extensions) const { - if (get_script_instance() && get_script_instance()->has_method("_get_recognized_extensions")) { - PackedStringArray exts = get_script_instance()->call("_get_recognized_extensions", p_resource); - - { - const String *r = exts.ptr(); - for (int i = 0; i < exts.size(); ++i) { - p_extensions->push_back(r[i]); - } + PackedStringArray exts; + if (GDVIRTUAL_CALL(_get_recognized_extensions, p_resource, exts)) { + const String *r = exts.ptr(); + for (int i = 0; i < exts.size(); ++i) { + p_extensions->push_back(r[i]); } } } void ResourceFormatSaver::_bind_methods() { - { - PropertyInfo arg0 = PropertyInfo(Variant::STRING, "path"); - PropertyInfo arg1 = PropertyInfo(Variant::OBJECT, "resource", PROPERTY_HINT_RESOURCE_TYPE, "Resource"); - PropertyInfo arg2 = PropertyInfo(Variant::INT, "flags"); - BIND_VMETHOD(MethodInfo(Variant::INT, "_save", arg0, arg1, arg2)); - } - - BIND_VMETHOD(MethodInfo(Variant::PACKED_STRING_ARRAY, "_get_recognized_extensions", PropertyInfo(Variant::OBJECT, "resource", PROPERTY_HINT_RESOURCE_TYPE, "Resource"))); - BIND_VMETHOD(MethodInfo(Variant::BOOL, "_recognize", PropertyInfo(Variant::OBJECT, "resource", PROPERTY_HINT_RESOURCE_TYPE, "Resource"))); + GDVIRTUAL_BIND(_save, "path", "resource", "flags"); + GDVIRTUAL_BIND(_recognize, "resource"); + GDVIRTUAL_BIND(_get_recognized_extensions, "resource"); } Error ResourceSaver::save(const String &p_path, const RES &p_resource, uint32_t p_flags) { diff --git a/core/io/resource_saver.h b/core/io/resource_saver.h index 2fc8d32126..fcde835dab 100644 --- a/core/io/resource_saver.h +++ b/core/io/resource_saver.h @@ -32,6 +32,8 @@ #define RESOURCE_SAVER_H #include "core/io/resource.h" +#include "core/object/gdvirtual.gen.inc" +#include "core/object/script_language.h" class ResourceFormatSaver : public RefCounted { GDCLASS(ResourceFormatSaver, RefCounted); @@ -39,6 +41,10 @@ class ResourceFormatSaver : public RefCounted { protected: static void _bind_methods(); + GDVIRTUAL3R(int64_t, _save, String, RES, uint32_t) + GDVIRTUAL1RC(bool, _recognize, RES) + GDVIRTUAL1RC(Vector<String>, _get_recognized_extensions, RES) + public: virtual Error save(const String &p_path, const RES &p_resource, uint32_t p_flags = 0); virtual bool recognize(const RES &p_resource) const; |