diff options
author | Fabio Alessandrelli <fabio.alessandrelli@gmail.com> | 2021-08-09 22:46:23 +0200 |
---|---|---|
committer | Fabio Alessandrelli <fabio.alessandrelli@gmail.com> | 2021-08-18 10:21:29 +0100 |
commit | d4dd859991205e6cecfa9a0553b89db47c983d0b (patch) | |
tree | e69020f0586cfecda02934dc95017373fddb2616 /core | |
parent | a03157928622b8c4048ede29cd6f1a40df893d1b (diff) |
[Net] MultiplayerReplicator with initial state.
Move the former "spawnables" functions to a dedicated
MultiplayerReplicator class.
Support custom overrides in replicator.
Spawn/despawn messages can now contain a state.
The state can be automatically encoded/decoded by passing the desired
object properties to `spawnable_config`.
You can use script properties to optimize the state representation.
2 Callables can be also specified to completely override the default
implementation for sending and receiving the spawn/despawn event.
(9 bytes overhead, and there's room for improvement here).
When using a custom implementation `spawn` and `despawn` can be called
with any Object, `send_spawn`/`send_despawn` can receive any Variant as
a state, and the path is not required.
Two new functions, `spawn` and `despawn`, convey the implementation
independent method for requesting a spawn/despawn of an Object, while
`send_spawn` and `send_despawn` represent the more low-level send event
for a Variant to be used by the custom implementations.
Diffstat (limited to 'core')
-rw-r--r-- | core/io/multiplayer_api.cpp | 252 | ||||
-rw-r--r-- | core/io/multiplayer_api.h | 76 | ||||
-rw-r--r-- | core/io/multiplayer_replicator.cpp | 497 | ||||
-rw-r--r-- | core/io/multiplayer_replicator.h | 99 | ||||
-rw-r--r-- | core/register_core_types.cpp | 2 |
5 files changed, 686 insertions, 240 deletions
diff --git a/core/io/multiplayer_api.cpp b/core/io/multiplayer_api.cpp index f792dc6cb4..0ce9a70921 100644 --- a/core/io/multiplayer_api.cpp +++ b/core/io/multiplayer_api.cpp @@ -32,15 +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 "scene/resources/packed_scene.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 @@ -147,7 +143,7 @@ void MultiplayerAPI::poll() { } void MultiplayerAPI::clear() { - replicated_nodes.clear(); + replicator->clear(); connected_peers.clear(); path_get_cache.clear(); path_send_cache.clear(); @@ -325,10 +321,10 @@ void MultiplayerAPI::_process_packet(int p_from, const uint8_t *p_packet, int p_ _process_raw(p_from, p_packet, p_packet_len); } break; case NETWORK_COMMAND_SPAWN: { - _process_spawn_despawn(p_from, p_packet, p_packet_len, true); + replicator->process_spawn_despawn(p_from, p_packet, p_packet_len, true); } break; case NETWORK_COMMAND_DESPAWN: { - _process_spawn_despawn(p_from, p_packet, p_packet_len, false); + replicator->process_spawn_despawn(p_from, p_packet, p_packet_len, false); } break; } } @@ -338,7 +334,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."); @@ -353,25 +348,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) { @@ -425,7 +406,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]; @@ -514,64 +495,6 @@ void MultiplayerAPI::_process_confirm_path(int p_from, const uint8_t *p_packet, E->get() = true; } -void MultiplayerAPI::_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 < 18, "Invalid spawn packet received"); - int ofs = 1; - ResourceUID::ID id = decode_uint64(&p_packet[ofs]); - ofs += 8; - ERR_FAIL_COND_MSG(!spawnables.has(id), "Invalid spawn ID received " + itos(id)); - - uint32_t node_target = decode_uint32(&p_packet[ofs]); - Node *parent = _process_get_node(p_from, nullptr, node_target, 0); - 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; - PackedByteArray data; - if (p_packet_len > ofs) { - data.resize(p_packet_len - ofs); - memcpy(data.ptrw(), &p_packet[ofs], data.size()); - } - - SpawnMode mode = spawnables[id]; - if (mode == SPAWN_MODE_SERVER && p_from == 1) { - String scene_path = ResourceUID::get_singleton()->get_id_path(id); - if (p_spawn) { - 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()] = id; - parent->_add_child_nocheck(node, name); - emit_signal(SNAME("network_spawn"), p_from, id, node, data); - } 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("network_despawn"), p_from, id, node, data); - replicated_nodes.erase(node->get_instance_id()); - node->queue_delete(); - } - } else { - if (p_spawn) { - emit_signal(SNAME("network_spawn_request"), p_from, id, parent, name, data); - } else { - emit_signal(SNAME("network_despawn_request"), p_from, id, parent, name, data); - } - } -} - bool MultiplayerAPI::_send_confirm_path(Node *p_node, NodePath p_path, PathSentCache *psc, int p_target) { bool has_all_peers = true; List<int> peers_to_add; // If one is missing, take note to add it. @@ -647,7 +570,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); @@ -722,7 +645,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; @@ -906,10 +829,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; } } @@ -976,14 +899,7 @@ void MultiplayerAPI::_add_peer(int p_id) { connected_peers.insert(p_id); path_get_cache.insert(p_id, PathGetCache()); if (is_network_server()) { - 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); - _send_spawn_despawn(p_id, E.value, node->get_path(), nullptr, 0, true); - } + replicator->spawn_all(p_id); } emit_signal(SNAME("network_peer_connected"), p_id); } @@ -1000,10 +916,6 @@ void MultiplayerAPI::_del_peer(int p_id) { PathSentCache *psc = path_send_cache.getptr(E); psc->confirmed_peers.erase(p_id); } - if (p_id == 1) { - // Erase server replicated nodes, but do not queue them for deletion. - replicated_nodes.clear(); - } emit_signal(SNAME("network_peer_disconnected"), p_id); } @@ -1109,6 +1021,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(); @@ -1147,97 +1089,12 @@ bool MultiplayerAPI::is_object_decoding_allowed() const { return allow_object_decoding; } -Error MultiplayerAPI::spawnable_config(const ResourceUID::ID &p_id, SpawnMode p_mode) { - ERR_FAIL_COND_V(p_mode < SPAWN_MODE_NONE || p_mode > SPAWN_MODE_CUSTOM, ERR_INVALID_PARAMETER); - ERR_FAIL_COND_V(!ResourceUID::get_singleton()->has_id(p_id), ERR_INVALID_PARAMETER); -#ifdef TOOLS_ENABLED - 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 == SPAWN_MODE_NONE) { - if (spawnables.has(p_id)) { - spawnables.erase(p_id); - } - } else { - spawnables[p_id] = p_mode; - } - return OK; -} - -Error MultiplayerAPI::send_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const NodePath &p_path, const PackedByteArray &p_data) { - return _send_spawn_despawn(p_peer_id, p_scene_id, p_path, p_data.ptr(), p_data.size(), false); +MultiplayerReplicator *MultiplayerAPI::get_replicator() const { + return replicator; } -Error MultiplayerAPI::send_spawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const NodePath &p_path, const PackedByteArray &p_data) { - return _send_spawn_despawn(p_peer_id, p_scene_id, p_path, p_data.ptr(), p_data.size(), true); -} - -Error MultiplayerAPI::_send_spawn_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const NodePath &p_path, const uint8_t *p_data, int p_data_len, bool p_spawn) { - ERR_FAIL_COND_V(!root_node, ERR_UNCONFIGURED); - ERR_FAIL_COND_V_MSG(!spawnables.has(p_scene_id), ERR_INVALID_PARAMETER, vformat("Spawnable not found: %d", p_scene_id)); - - 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); - - // See if the path is cached. - PathSentCache *psc = path_send_cache.getptr(parent); - if (!psc) { - // Path is not cached, create. - path_send_cache[parent] = PathSentCache(); - psc = path_send_cache.getptr(parent); - psc->id = last_send_cache_id++; - } - _send_confirm_path(root_node->get_node(parent), parent, psc, p_peer_id); - - const CharString cname = String(names[names.size() - 1]).utf8(); - int nlen = encode_cstring(cname.get_data(), nullptr); - MAKE_ROOM(1 + 8 + 4 + 4 + nlen + p_data_len); - uint8_t *ptr = packet_cache.ptrw(); - ptr[0] = p_spawn ? NETWORK_COMMAND_SPAWN : NETWORK_COMMAND_DESPAWN; - int ofs = 1; - ofs += encode_uint64(p_scene_id, &ptr[ofs]); - ofs += encode_uint32(psc->id, &ptr[ofs]); - ofs += encode_uint32(nlen, &ptr[ofs]); - ofs += encode_cstring(cname.get_data(), &ptr[ofs]); - memcpy(&ptr[ofs], p_data, p_data_len); - 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 + p_data_len); -} - -void MultiplayerAPI::scene_enter_exit_notify(const String &p_scene, const Node *p_node, bool p_enter) { - if (!has_network_peer()) { - return; - } - 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 (!spawnables.has(id)) { - return; - } - SpawnMode mode = spawnables[id]; - if (p_enter) { - if (mode == SPAWN_MODE_SERVER && is_network_server()) { - replicated_nodes[p_node->get_instance_id()] = id; - _send_spawn_despawn(0, id, p_node->get_path(), nullptr, 0, true); - } - emit_signal(SNAME("network_spawnable_added"), id, p_node); - } else { - if (mode == SPAWN_MODE_SERVER && is_network_server() && replicated_nodes.has(p_node->get_instance_id())) { - replicated_nodes.erase(p_node->get_instance_id()); - _send_spawn_despawn(0, id, p_node->get_path(), nullptr, 0, false); - } - emit_signal(SNAME("network_spawnable_removed"), id, p_node); - } +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() { @@ -1258,15 +1115,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("spawnable_config", "scene_id", "spawn_mode"), &MultiplayerAPI::spawnable_config); - ClassDB::bind_method(D_METHOD("send_despawn", "peer_id", "scene_id", "path", "data"), &MultiplayerAPI::send_despawn, DEFVAL(PackedByteArray())); - ClassDB::bind_method(D_METHOD("send_spawn", "peer_id", "scene_id", "path", "data"), &MultiplayerAPI::send_spawn, DEFVAL(PackedByteArray())); + 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"))); @@ -1274,27 +1130,19 @@ void MultiplayerAPI::_bind_methods() { ADD_SIGNAL(MethodInfo("connected_to_server")); ADD_SIGNAL(MethodInfo("connection_failed")); ADD_SIGNAL(MethodInfo("server_disconnected")); - ADD_SIGNAL(MethodInfo("network_despawn", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node"), PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data"))); - ADD_SIGNAL(MethodInfo("network_spawn", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node"), PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data"))); - ADD_SIGNAL(MethodInfo("network_despawn_request", 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("network_spawn_request", 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("network_spawnable_added", PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node"))); - ADD_SIGNAL(MethodInfo("network_spawnable_removed", PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node"))); 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(SPAWN_MODE_NONE); - BIND_ENUM_CONSTANT(SPAWN_MODE_SERVER); - BIND_ENUM_CONSTANT(SPAWN_MODE_CUSTOM); } 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 9552a0bf35..5853541efa 100644 --- a/core/io/multiplayer_api.h +++ b/core/io/multiplayer_api.h @@ -35,6 +35,8 @@ #include "core/io/resource_uid.h" #include "core/object/ref_counted.h" +class MultiplayerReplicator; + class MultiplayerAPI : public RefCounted { GDCLASS(MultiplayerAPI, RefCounted); @@ -46,12 +48,6 @@ public: RPC_MODE_PUPPET, // Using rpc() on it will call method for all puppets }; - enum SpawnMode { - SPAWN_MODE_NONE, - SPAWN_MODE_SERVER, - SPAWN_MODE_CUSTOM, - }; - struct RPCConfig { StringName name; RPCMode rpc_mode = RPC_MODE_DISABLED; @@ -71,6 +67,32 @@ 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, + }; + + 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 { @@ -89,16 +111,15 @@ private: }; Ref<MultiplayerPeer> network_peer; - Map<ResourceUID::ID, SpawnMode> spawnables; int rpc_sender_id = 0; Set<int> connected_peers; HashMap<NodePath, PathSentCache> path_send_cache; Map<int, PathGetCache> path_get_cache; - Map<ObjectID, ResourceUID::ID> replicated_nodes; int last_send_cache_id; Vector<uint8_t> packet_cache; Node *root_node = nullptr; bool allow_object_decoding = false; + MultiplayerReplicator *replicator = nullptr; protected: static void _bind_methods(); @@ -106,7 +127,6 @@ protected: 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); - void _process_spawn_despawn(int p_from, const uint8_t *p_packet, int p_packet_len, bool p_spawn); 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); @@ -114,30 +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, - NETWORK_COMMAND_SPAWN, - NETWORK_COMMAND_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, - }; - void poll(); void clear(); void set_root_node(Node *p_node); @@ -146,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); @@ -166,17 +171,12 @@ public: void set_allow_object_decoding(bool p_enable); bool is_object_decoding_allowed() const; - Error spawnable_config(const ResourceUID::ID &p_id, SpawnMode p_mode); - Error send_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const NodePath &p_path, const PackedByteArray &p_data = PackedByteArray()); - Error send_spawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const NodePath &p_path, const PackedByteArray &p_data = PackedByteArray()); - Error _send_spawn_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const NodePath &p_path, const uint8_t *p_data, int p_data_len, bool p_spawn); - void scene_enter_exit_notify(const String &p_scene, const Node *p_node, bool p_enter); + MultiplayerReplicator *get_replicator() const; MultiplayerAPI(); ~MultiplayerAPI(); }; VARIANT_ENUM_CAST(MultiplayerAPI::RPCMode); -VARIANT_ENUM_CAST(MultiplayerAPI::SpawnMode); #endif // MULTIPLAYER_API_H diff --git a/core/io/multiplayer_replicator.cpp b/core/io/multiplayer_replicator.cpp new file mode 100644 index 0000000000..ba0fe32b58 --- /dev/null +++ b/core/io/multiplayer_replicator.cpp @@ -0,0 +1,497 @@ +/*************************************************************************/ +/* 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::_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; + 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); + 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); + } +} + +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::_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_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_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) { + 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; + Error err = _get_state(cfg.properties, 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) { + 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]; + int size; + return _decode_state(cfg.properties, 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; + 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()); + 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::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("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("encode_state", "scene_id", "object"), &MultiplayerReplicator::encode_state); + ClassDB::bind_method(D_METHOD("decode_state", "scene_id", "object", "data"), &MultiplayerReplicator::decode_state); + + 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..e19dd80602 --- /dev/null +++ b/core/io/multiplayer_replicator.h @@ -0,0 +1,99 @@ +/*************************************************************************/ +/* 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/variant/typed_array.h" + +class MultiplayerReplicator : public Object { + GDCLASS(MultiplayerReplicator, Object); + +public: + enum { + SPAWN_CMD_OFFSET = 9, + }; + + enum ReplicationMode { + REPLICATION_MODE_NONE, + REPLICATION_MODE_SERVER, + REPLICATION_MODE_CUSTOM, + }; + + struct SceneConfig { + ReplicationMode mode; + List<StringName> properties; + Callable on_spawn_despawn_send; + Callable on_spawn_despawn_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; + + 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); + Error _get_state(const List<StringName> &p_properties, const Object *p_obj, List<Variant> &r_variant); + 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); + +public: + void clear(); + + 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()); + PackedByteArray encode_state(const ResourceUID::ID &p_scene_id, const Object *p_node); + Error decode_state(const ResourceUID::ID &p_scene_id, Object *p_node, PackedByteArray p_data); + + // 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 scene_enter_exit_notify(const String &p_scene, Node *p_node, bool p_enter); + + MultiplayerReplicator(MultiplayerAPI *p_multiplayer) { + multiplayer = p_multiplayer; + } +}; + +VARIANT_ENUM_CAST(MultiplayerReplicator::ReplicationMode); + +#endif // MULTIPLAYER_REPLICATOR_H diff --git a/core/register_core_types.cpp b/core/register_core_types.cpp index f801be3db3..4fabd1551e 100644 --- a/core/register_core_types.cpp +++ b/core/register_core_types.cpp @@ -49,6 +49,7 @@ #include "core/io/marshalls.h" #include "core/io/multiplayer_api.h" #include "core/io/multiplayer_peer.h" +#include "core/io/multiplayer_replicator.h" #include "core/io/packed_data_container.h" #include "core/io/packet_peer.h" #include "core/io/packet_peer_dtls.h" @@ -193,6 +194,7 @@ void register_core_types() { ResourceLoader::add_resource_format_loader(resource_format_loader_crypto); GDREGISTER_VIRTUAL_CLASS(MultiplayerPeer); + GDREGISTER_VIRTUAL_CLASS(MultiplayerReplicator); GDREGISTER_CLASS(MultiplayerAPI); GDREGISTER_CLASS(MainLoop); GDREGISTER_CLASS(Translation); |