summaryrefslogtreecommitdiff
path: root/core/io
diff options
context:
space:
mode:
Diffstat (limited to 'core/io')
-rw-r--r--core/io/config_file.cpp4
-rw-r--r--core/io/dir_access.cpp4
-rw-r--r--core/io/http_client_tcp.cpp26
-rw-r--r--core/io/http_client_tcp.h1
-rw-r--r--core/io/multiplayer_api.cpp108
-rw-r--r--core/io/multiplayer_api.h67
-rw-r--r--core/io/multiplayer_replicator.cpp786
-rw-r--r--core/io/multiplayer_replicator.h129
-rw-r--r--core/io/resource.cpp9
-rw-r--r--core/io/resource.h2
-rw-r--r--core/io/resource_format_binary.cpp4
-rw-r--r--core/io/resource_loader.cpp90
-rw-r--r--core/io/resource_loader.h12
-rw-r--r--core/io/resource_saver.cpp35
-rw-r--r--core/io/resource_saver.h6
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;