mirror of
https://github.com/godotengine/godot.git
synced 2024-11-22 20:23:53 +00:00
Merge pull request #51534 from Faless/mp/4.x_replicator
[Net] MultiplayerReplicator with initial state.
This commit is contained in:
commit
2a9c4a59df
@ -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;
|
||||
MultiplayerReplicator *MultiplayerAPI::get_replicator() const {
|
||||
return replicator;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
@ -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,53 +67,6 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
//path sent caches
|
||||
struct PathSentCache {
|
||||
Map<int, bool> confirmed_peers;
|
||||
int id;
|
||||
};
|
||||
|
||||
//path get caches
|
||||
struct PathGetCache {
|
||||
struct NodeInfo {
|
||||
NodePath path;
|
||||
ObjectID instance;
|
||||
};
|
||||
|
||||
Map<int, NodeInfo> nodes;
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
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);
|
||||
|
||||
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,
|
||||
@ -138,6 +87,54 @@ public:
|
||||
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 {
|
||||
Map<int, bool> confirmed_peers;
|
||||
int id;
|
||||
};
|
||||
|
||||
//path get caches
|
||||
struct PathGetCache {
|
||||
struct NodeInfo {
|
||||
NodePath path;
|
||||
ObjectID instance;
|
||||
};
|
||||
|
||||
Map<int, NodeInfo> nodes;
|
||||
};
|
||||
|
||||
Ref<MultiplayerPeer> network_peer;
|
||||
int rpc_sender_id = 0;
|
||||
Set<int> connected_peers;
|
||||
HashMap<NodePath, PathSentCache> path_send_cache;
|
||||
Map<int, PathGetCache> path_get_cache;
|
||||
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();
|
||||
|
||||
void _process_packet(int p_from, const uint8_t *p_packet, int p_packet_len);
|
||||
void _process_simplify_path(int p_from, const uint8_t *p_packet, int p_packet_len);
|
||||
void _process_confirm_path(int p_from, const uint8_t *p_packet, int p_packet_len);
|
||||
Node *_process_get_node(int p_from, const uint8_t *p_packet, uint32_t p_node_target, int p_packet_len);
|
||||
void _process_rpc(Node *p_node, const uint16_t p_rpc_method_id, int p_from, const uint8_t *p_packet, int p_packet_len, int p_offset);
|
||||
void _process_raw(int p_from, const uint8_t *p_packet, int p_packet_len);
|
||||
|
||||
void _send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, const RPCConfig &p_config, const StringName &p_name, const Variant **p_arg, int p_argcount);
|
||||
bool _send_confirm_path(Node *p_node, NodePath p_path, PathSentCache *psc, int p_target);
|
||||
|
||||
public:
|
||||
void poll();
|
||||
void clear();
|
||||
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
|
||||
|
497
core/io/multiplayer_replicator.cpp
Normal file
497
core/io/multiplayer_replicator.cpp
Normal file
@ -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);
|
||||
}
|
99
core/io/multiplayer_replicator.h
Normal file
99
core/io/multiplayer_replicator.h
Normal file
@ -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
|
@ -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);
|
||||
|
@ -66,34 +66,6 @@
|
||||
Sends the given raw [code]bytes[/code] to a specific peer identified by [code]id[/code] (see [method MultiplayerPeer.set_target_peer]). Default ID is [code]0[/code], i.e. broadcast to all peers.
|
||||
</description>
|
||||
</method>
|
||||
<method name="send_despawn">
|
||||
<return type="int" enum="Error" />
|
||||
<argument index="0" name="peer_id" type="int" />
|
||||
<argument index="1" name="scene_id" type="int" />
|
||||
<argument index="2" name="path" type="NodePath" />
|
||||
<argument index="3" name="data" type="PackedByteArray" default="PackedByteArray()" />
|
||||
<description>
|
||||
Sends a despawn request for the scene identified by [code]scene_id[/code] to the given [code]peer_id[/code] (see [method MultiplayerPeer.set_target_peer]). If the scene is configured as [constant SPAWN_MODE_SERVER] (see [method spawnable_config]) and the request is sent by the server (see [method is_network_server]), the receiving peer(s) will automatically queue for deletion the node at [code]path[/code] and emit the signal [signal network_despawn]. In all other cases no deletion happens, and the signal [signal network_despawn_request] is emitted instead.
|
||||
</description>
|
||||
</method>
|
||||
<method name="send_spawn">
|
||||
<return type="int" enum="Error" />
|
||||
<argument index="0" name="peer_id" type="int" />
|
||||
<argument index="1" name="scene_id" type="int" />
|
||||
<argument index="2" name="path" type="NodePath" />
|
||||
<argument index="3" name="data" type="PackedByteArray" default="PackedByteArray()" />
|
||||
<description>
|
||||
Sends a spawn request for the scene identified by [code]scene_id[/code] to the given [code]peer_id[/code] (see [method MultiplayerPeer.set_target_peer]). If the scene is configured as [constant SPAWN_MODE_SERVER] (see [method spawnable_config]) and the request is sent by the server (see [method is_network_server]), the receiving peer(s) will automatically instantiate that scene, add it to the [SceneTree] at the given [code]path[/code] and emit the signal [signal network_spawn]. In all other cases no instantiation happens, and the signal [signal network_spawn_request] is emitted instead.
|
||||
</description>
|
||||
</method>
|
||||
<method name="spawnable_config">
|
||||
<return type="int" enum="Error" />
|
||||
<argument index="0" name="scene_id" type="int" />
|
||||
<argument index="1" name="spawn_mode" type="int" enum="MultiplayerAPI.SpawnMode" />
|
||||
<description>
|
||||
Configures the MultiplayerAPI to track instances of the [PackedScene] idenfied by [code]scene_id[/code] (see [method ResourceLoader.get_resource_uid]) for the purpose of network replication. See [enum SpawnMode] for the possible configurations.
|
||||
</description>
|
||||
</method>
|
||||
</methods>
|
||||
<members>
|
||||
<member name="allow_object_decoding" type="bool" setter="set_allow_object_decoding" getter="is_object_decoding_allowed" default="false">
|
||||
@ -106,6 +78,8 @@
|
||||
<member name="refuse_new_network_connections" type="bool" setter="set_refuse_new_network_connections" getter="is_refusing_new_network_connections" default="false">
|
||||
If [code]true[/code], the MultiplayerAPI's [member network_peer] refuses new incoming connections.
|
||||
</member>
|
||||
<member name="replicator" type="MultiplayerReplicator" setter="" getter="get_replicator">
|
||||
</member>
|
||||
<member name="root_node" type="Node" setter="set_root_node" getter="get_root_node">
|
||||
The root node to use for RPCs. Instead of an absolute path, a relative path will be used to find the node upon which the RPC should be executed.
|
||||
This effectively allows to have different branches of the scene tree to be managed by different MultiplayerAPI, allowing for example to run both client and server in the same scene.
|
||||
@ -122,25 +96,6 @@
|
||||
Emitted when this MultiplayerAPI's [member network_peer] fails to establish a connection to a server. Only emitted on clients.
|
||||
</description>
|
||||
</signal>
|
||||
<signal name="network_despawn">
|
||||
<argument index="0" name="id" type="int" />
|
||||
<argument index="1" name="scene_id" type="int" />
|
||||
<argument index="2" name="node" type="Node" />
|
||||
<argument index="3" name="data" type="PackedByteArray" />
|
||||
<description>
|
||||
Emitted on a client before deleting a local Node upon receiving a despawn request from the server.
|
||||
</description>
|
||||
</signal>
|
||||
<signal name="network_despawn_request">
|
||||
<argument index="0" name="id" type="int" />
|
||||
<argument index="1" name="scene_id" type="int" />
|
||||
<argument index="2" name="parent" type="Node" />
|
||||
<argument index="3" name="name" type="String" />
|
||||
<argument index="4" name="data" type="PackedByteArray" />
|
||||
<description>
|
||||
Emitted when a network despawn request has been received from a client, or for a [PackedScene] that has been configured as [constant SPAWN_MODE_CUSTOM].
|
||||
</description>
|
||||
</signal>
|
||||
<signal name="network_peer_connected">
|
||||
<argument index="0" name="id" type="int" />
|
||||
<description>
|
||||
@ -160,39 +115,6 @@
|
||||
Emitted when this MultiplayerAPI's [member network_peer] receive a [code]packet[/code] with custom data (see [method send_bytes]). ID is the peer ID of the peer that sent the packet.
|
||||
</description>
|
||||
</signal>
|
||||
<signal name="network_spawn">
|
||||
<argument index="0" name="id" type="int" />
|
||||
<argument index="1" name="scene_id" type="int" />
|
||||
<argument index="2" name="node" type="Node" />
|
||||
<argument index="3" name="data" type="PackedByteArray" />
|
||||
<description>
|
||||
Emitted on a client after a new Node is instantiated locally and added to the SceneTree upon receiving a spawn request from the server.
|
||||
</description>
|
||||
</signal>
|
||||
<signal name="network_spawn_request">
|
||||
<argument index="0" name="id" type="int" />
|
||||
<argument index="1" name="scene_id" type="int" />
|
||||
<argument index="2" name="parent" type="Node" />
|
||||
<argument index="3" name="name" type="String" />
|
||||
<argument index="4" name="data" type="PackedByteArray" />
|
||||
<description>
|
||||
Emitted when a network spawn request has been received from a client, or for a [PackedScene] that has been configured as [constant SPAWN_MODE_CUSTOM].
|
||||
</description>
|
||||
</signal>
|
||||
<signal name="network_spawnable_added">
|
||||
<argument index="0" name="scene_id" type="int" />
|
||||
<argument index="1" name="node" type="Node" />
|
||||
<description>
|
||||
Emitted when an instance of a [PackedScene] that has been configured for networking enters the [SceneTree]. See [method spawnable_config].
|
||||
</description>
|
||||
</signal>
|
||||
<signal name="network_spawnable_removed">
|
||||
<argument index="0" name="scene_id" type="int" />
|
||||
<argument index="1" name="node" type="Node" />
|
||||
<description>
|
||||
Emitted when an instance of a [PackedScene] that has been configured for networking leaves the [SceneTree]. See [method spawnable_config].
|
||||
</description>
|
||||
</signal>
|
||||
<signal name="server_disconnected">
|
||||
<description>
|
||||
Emitted when this MultiplayerAPI's [member network_peer] disconnects from server. Only emitted on clients.
|
||||
@ -212,14 +134,5 @@
|
||||
<constant name="RPC_MODE_PUPPET" value="3" enum="RPCMode">
|
||||
Used with [method Node.rpc_config] to set a method to be called or a property to be changed only on puppets for this node. Analogous to the [code]puppet[/code] keyword. Only accepts calls or property changes from the node's network master, see [method Node.set_network_master].
|
||||
</constant>
|
||||
<constant name="SPAWN_MODE_NONE" value="0" enum="SpawnMode">
|
||||
Used with [method spawnable_config] to identify a [PackedScene] that should not be replicated.
|
||||
</constant>
|
||||
<constant name="SPAWN_MODE_SERVER" value="1" enum="SpawnMode">
|
||||
Used with [method spawnable_config] to identify a [PackedScene] that should be automatically replicated from server to clients.
|
||||
</constant>
|
||||
<constant name="SPAWN_MODE_CUSTOM" value="2" enum="SpawnMode">
|
||||
Used with [method spawnable_config] to identify a [PackedScene] that can be manually replicated among peers.
|
||||
</constant>
|
||||
</constants>
|
||||
</class>
|
||||
|
139
doc/classes/MultiplayerReplicator.xml
Normal file
139
doc/classes/MultiplayerReplicator.xml
Normal file
@ -0,0 +1,139 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<class name="MultiplayerReplicator" inherits="Object" version="4.0">
|
||||
<brief_description>
|
||||
</brief_description>
|
||||
<description>
|
||||
</description>
|
||||
<tutorials>
|
||||
</tutorials>
|
||||
<methods>
|
||||
<method name="decode_state">
|
||||
<return type="int" enum="Error" />
|
||||
<argument index="0" name="scene_id" type="int" />
|
||||
<argument index="1" name="object" type="Object" />
|
||||
<argument index="2" name="data" type="PackedByteArray" />
|
||||
<description>
|
||||
Decode the given [code]data[/code] representing a spawnable state into [code]object[/code] using the configuration associated with the provided [code]scene_id[/code]. This function is called automatically when a client receives a server spawn for a scene with [constant REPLICATION_MODE_SERVER]. See [method spawn_config].
|
||||
Tip: You may find this function useful in servers when parsing spawn requests from clients, or when implementing your own logic with [constant REPLICATION_MODE_CUSTOM].
|
||||
</description>
|
||||
</method>
|
||||
<method name="despawn">
|
||||
<return type="int" enum="Error" />
|
||||
<argument index="0" name="scene_id" type="int" />
|
||||
<argument index="1" name="object" type="Object" />
|
||||
<argument index="2" name="peer_id" type="int" default="0" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="encode_state">
|
||||
<return type="PackedByteArray" />
|
||||
<argument index="0" name="scene_id" type="int" />
|
||||
<argument index="1" name="object" type="Object" />
|
||||
<description>
|
||||
Encode the given [code]object[/code] using the configuration associated with the provided [code]scene_id[/code]. This function is called automatically when the server spawns scenes with [constant REPLICATION_MODE_SERVER]. See [method spawn_config].
|
||||
Tip: You may find this function useful when requesting spawns from clients to server, or when implementing your own logic with [constant REPLICATION_MODE_CUSTOM].
|
||||
</description>
|
||||
</method>
|
||||
<method name="send_despawn">
|
||||
<return type="int" enum="Error" />
|
||||
<argument index="0" name="peer_id" type="int" />
|
||||
<argument index="1" name="scene_id" type="int" />
|
||||
<argument index="2" name="data" type="Variant" default="null" />
|
||||
<argument index="3" name="path" type="NodePath" default="NodePath("")" />
|
||||
<description>
|
||||
Sends a despawn request for the scene identified by [code]scene_id[/code] to the given [code]peer_id[/code] (see [method MultiplayerPeer.set_target_peer]). If the scene is configured as [constant REPLICATION_MODE_SERVER] (see [method spawn_config]) and the request is sent by the server (see [method MultiplayerAPI.is_network_server]), the receiving peer(s) will automatically queue for deletion the node at [code]path[/code] and emit the signal [signal despawned]. In all other cases no deletion happens, and the signal [signal despawn_requested] is emitted instead.
|
||||
</description>
|
||||
</method>
|
||||
<method name="send_spawn">
|
||||
<return type="int" enum="Error" />
|
||||
<argument index="0" name="peer_id" type="int" />
|
||||
<argument index="1" name="scene_id" type="int" />
|
||||
<argument index="2" name="data" type="Variant" default="null" />
|
||||
<argument index="3" name="path" type="NodePath" default="NodePath("")" />
|
||||
<description>
|
||||
Sends a spawn request for the scene identified by [code]scene_id[/code] to the given [code]peer_id[/code] (see [method MultiplayerPeer.set_target_peer]). If the scene is configured as [constant REPLICATION_MODE_SERVER] (see [method spawn_config]) and the request is sent by the server (see [method MultiplayerAPI.is_network_server]), the receiving peer(s) will automatically instantiate that scene, add it to the [SceneTree] at the given [code]path[/code] and emit the signal [signal spawned]. In all other cases no instantiation happens, and the signal [signal spawn_requested] is emitted instead.
|
||||
</description>
|
||||
</method>
|
||||
<method name="spawn">
|
||||
<return type="int" enum="Error" />
|
||||
<argument index="0" name="scene_id" type="int" />
|
||||
<argument index="1" name="object" type="Object" />
|
||||
<argument index="2" name="peer_id" type="int" default="0" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="spawn_config">
|
||||
<return type="int" enum="Error" />
|
||||
<argument index="0" name="scene_id" type="int" />
|
||||
<argument index="1" name="spawn_mode" type="int" enum="MultiplayerReplicator.ReplicationMode" />
|
||||
<argument index="2" name="properties" type="StringName[]" default="[]" />
|
||||
<argument index="3" name="custom_send" type="Callable" />
|
||||
<argument index="4" name="custom_receive" type="Callable" />
|
||||
<description>
|
||||
Configures the MultiplayerAPI to track instances of the [PackedScene] identified by [code]scene_id[/code] (see [method ResourceLoader.get_resource_uid]) for the purpose of network replication. When [code]mode[/code] is [constant REPLICATION_MODE_SERVER], the specified [code]properties[/code] will also be replicated to clients during the initial spawn.
|
||||
Tip: You can use a custom property in the scene main script to return a customly optimized state representation.
|
||||
</description>
|
||||
</method>
|
||||
</methods>
|
||||
<signals>
|
||||
<signal name="despawn_requested">
|
||||
<argument index="0" name="id" type="int" />
|
||||
<argument index="1" name="scene_id" type="int" />
|
||||
<argument index="2" name="parent" type="Node" />
|
||||
<argument index="3" name="name" type="String" />
|
||||
<argument index="4" name="data" type="PackedByteArray" />
|
||||
<description>
|
||||
Emitted when a network despawn request has been received from a client, or for a [PackedScene] that has been configured as [constant REPLICATION_MODE_CUSTOM].
|
||||
</description>
|
||||
</signal>
|
||||
<signal name="despawned">
|
||||
<argument index="0" name="scene_id" type="int" />
|
||||
<argument index="1" name="node" type="Node" />
|
||||
<description>
|
||||
Emitted on a client before deleting a local Node upon receiving a despawn request from the server.
|
||||
</description>
|
||||
</signal>
|
||||
<signal name="replicated_instance_added">
|
||||
<argument index="0" name="scene_id" type="int" />
|
||||
<argument index="1" name="node" type="Node" />
|
||||
<description>
|
||||
Emitted when an instance of a [PackedScene] that has been configured for networking enters the [SceneTree]. See [method spawn_config].
|
||||
</description>
|
||||
</signal>
|
||||
<signal name="replicated_instance_removed">
|
||||
<argument index="0" name="scene_id" type="int" />
|
||||
<argument index="1" name="node" type="Node" />
|
||||
<description>
|
||||
Emitted when an instance of a [PackedScene] that has been configured for networking leaves the [SceneTree]. See [method spawn_config].
|
||||
</description>
|
||||
</signal>
|
||||
<signal name="spawn_requested">
|
||||
<argument index="0" name="id" type="int" />
|
||||
<argument index="1" name="scene_id" type="int" />
|
||||
<argument index="2" name="parent" type="Node" />
|
||||
<argument index="3" name="name" type="String" />
|
||||
<argument index="4" name="data" type="PackedByteArray" />
|
||||
<description>
|
||||
Emitted when a network spawn request has been received from a client, or for a [PackedScene] that has been configured as [constant REPLICATION_MODE_CUSTOM].
|
||||
</description>
|
||||
</signal>
|
||||
<signal name="spawned">
|
||||
<argument index="0" name="scene_id" type="int" />
|
||||
<argument index="1" name="node" type="Node" />
|
||||
<description>
|
||||
Emitted on a client after a new Node is instantiated locally and added to the SceneTree upon receiving a spawn request from the server.
|
||||
</description>
|
||||
</signal>
|
||||
</signals>
|
||||
<constants>
|
||||
<constant name="REPLICATION_MODE_NONE" value="0" enum="ReplicationMode">
|
||||
Used with [method spawn_config] to identify a [PackedScene] that should not be replicated.
|
||||
</constant>
|
||||
<constant name="REPLICATION_MODE_SERVER" value="1" enum="ReplicationMode">
|
||||
Used with [method spawn_config] to identify a [PackedScene] that should be automatically replicated from server to clients.
|
||||
</constant>
|
||||
<constant name="REPLICATION_MODE_CUSTOM" value="2" enum="ReplicationMode">
|
||||
Used with [method spawn_config] to identify a [PackedScene] that can be manually replicated among peers.
|
||||
</constant>
|
||||
</constants>
|
||||
</class>
|
@ -202,7 +202,7 @@ protected:
|
||||
static String _get_name_num_separator();
|
||||
|
||||
friend class SceneState;
|
||||
friend class MultiplayerAPI;
|
||||
friend class MultiplayerReplicator;
|
||||
|
||||
void _add_child_nocheck(Node *p_child, const StringName &p_name);
|
||||
void _set_owner_nocheck(Node *p_owner);
|
||||
|
Loading…
Reference in New Issue
Block a user