mirror of
https://github.com/godotengine/godot.git
synced 2025-01-22 09:51:37 +00:00
Merge pull request #55950 from Faless/mp/4.x_replication_nodes
This commit is contained in:
commit
2885befbe6
@ -32,7 +32,6 @@
|
||||
|
||||
#include "core/debugger/engine_debugger.h"
|
||||
#include "core/io/marshalls.h"
|
||||
#include "core/multiplayer/multiplayer_replicator.h"
|
||||
#include "core/multiplayer/rpc_manager.h"
|
||||
#include "scene/main/node.h"
|
||||
|
||||
@ -42,6 +41,8 @@
|
||||
#include "core/os/os.h"
|
||||
#endif
|
||||
|
||||
MultiplayerReplicationInterface *(*MultiplayerAPI::create_default_replication_interface)(MultiplayerAPI *p_multiplayer) = nullptr;
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
void MultiplayerAPI::profile_bandwidth(const String &p_inout, int p_size) {
|
||||
if (EngineDebugger::is_profiling("multiplayer")) {
|
||||
@ -74,7 +75,7 @@ void MultiplayerAPI::poll() {
|
||||
Error err = multiplayer_peer->get_packet(&packet, len);
|
||||
if (err != OK) {
|
||||
ERR_PRINT("Error getting packet!");
|
||||
break; // Something is wrong!
|
||||
return; // Something is wrong!
|
||||
}
|
||||
|
||||
remote_sender_id = sender;
|
||||
@ -82,16 +83,13 @@ void MultiplayerAPI::poll() {
|
||||
remote_sender_id = 0;
|
||||
|
||||
if (!multiplayer_peer.is_valid()) {
|
||||
break; // It's also possible that a packet or RPC caused a disconnection, so also check here.
|
||||
return; // It's also possible that a packet or RPC caused a disconnection, so also check here.
|
||||
}
|
||||
}
|
||||
if (multiplayer_peer.is_valid() && multiplayer_peer->get_connection_status() == MultiplayerPeer::CONNECTION_CONNECTED) {
|
||||
replicator->poll();
|
||||
}
|
||||
replicator->on_network_process();
|
||||
}
|
||||
|
||||
void MultiplayerAPI::clear() {
|
||||
replicator->clear();
|
||||
connected_peers.clear();
|
||||
path_get_cache.clear();
|
||||
path_send_cache.clear();
|
||||
@ -133,6 +131,7 @@ void MultiplayerAPI::set_multiplayer_peer(const Ref<MultiplayerPeer> &p_peer) {
|
||||
multiplayer_peer->connect("connection_failed", callable_mp(this, &MultiplayerAPI::_connection_failed));
|
||||
multiplayer_peer->connect("server_disconnected", callable_mp(this, &MultiplayerAPI::_server_disconnected));
|
||||
}
|
||||
replicator->on_reset();
|
||||
}
|
||||
|
||||
Ref<MultiplayerPeer> MultiplayerAPI::get_multiplayer_peer() const {
|
||||
@ -167,13 +166,13 @@ 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: {
|
||||
replicator->process_spawn_despawn(p_from, p_packet, p_packet_len, true);
|
||||
replicator->on_spawn_receive(p_from, p_packet, p_packet_len);
|
||||
} break;
|
||||
case NETWORK_COMMAND_DESPAWN: {
|
||||
replicator->process_spawn_despawn(p_from, p_packet, p_packet_len, false);
|
||||
replicator->on_despawn_receive(p_from, p_packet, p_packet_len);
|
||||
} break;
|
||||
case NETWORK_COMMAND_SYNC: {
|
||||
replicator->process_sync(p_from, p_packet, p_packet_len);
|
||||
replicator->on_sync_receive(p_from, p_packet, p_packet_len);
|
||||
} break;
|
||||
}
|
||||
}
|
||||
@ -324,7 +323,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, bool p_allow_object_decoding) {
|
||||
// Unreachable because `VARIANT_MAX` == 27 and `ENCODE_VARIANT_MASK` == 31
|
||||
CRASH_COND(p_variant.get_type() > VARIANT_META_TYPE_MASK);
|
||||
|
||||
@ -385,7 +384,7 @@ Error MultiplayerAPI::encode_and_compress_variant(const Variant &p_variant, uint
|
||||
} break;
|
||||
default:
|
||||
// Any other case is not yet compressed.
|
||||
Error err = encode_variant(p_variant, r_buffer, r_len, allow_object_decoding);
|
||||
Error err = encode_variant(p_variant, r_buffer, r_len, p_allow_object_decoding);
|
||||
if (err != OK) {
|
||||
return err;
|
||||
}
|
||||
@ -399,7 +398,7 @@ Error MultiplayerAPI::encode_and_compress_variant(const Variant &p_variant, uint
|
||||
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, bool p_allow_object_decoding) {
|
||||
const uint8_t *buf = p_buffer;
|
||||
int len = p_len;
|
||||
|
||||
@ -458,7 +457,7 @@ Error MultiplayerAPI::decode_and_decompress_variant(Variant &r_variant, const ui
|
||||
}
|
||||
} break;
|
||||
default:
|
||||
Error err = decode_variant(r_variant, p_buffer, p_len, r_len, allow_object_decoding);
|
||||
Error err = decode_variant(r_variant, p_buffer, p_len, r_len, p_allow_object_decoding);
|
||||
if (err != OK) {
|
||||
return err;
|
||||
}
|
||||
@ -467,17 +466,84 @@ Error MultiplayerAPI::decode_and_decompress_variant(Variant &r_variant, const ui
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error MultiplayerAPI::encode_and_compress_variants(const Variant **p_variants, int p_count, uint8_t *p_buffer, int &r_len, bool *r_raw, bool p_allow_object_decoding) {
|
||||
r_len = 0;
|
||||
int size = 0;
|
||||
|
||||
if (p_count == 0) {
|
||||
if (r_raw) {
|
||||
*r_raw = true;
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
// Try raw encoding optimization.
|
||||
if (r_raw && p_count == 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 {
|
||||
encode_and_compress_variant(v, p_buffer, size, p_allow_object_decoding);
|
||||
r_len += size;
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
// Regular encoding.
|
||||
for (int i = 0; i < p_count; i++) {
|
||||
const Variant &v = *(p_variants[i]);
|
||||
encode_and_compress_variant(v, p_buffer ? p_buffer + r_len : nullptr, size, p_allow_object_decoding);
|
||||
r_len += size;
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error MultiplayerAPI::decode_and_decompress_variants(Vector<Variant> &r_variants, const uint8_t *p_buffer, int p_len, int &r_len, bool p_raw, bool p_allow_object_decoding) {
|
||||
r_len = 0;
|
||||
int argc = r_variants.size();
|
||||
if (argc == 0 && p_raw) {
|
||||
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);
|
||||
r_variants.write[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 = MultiplayerAPI::decode_and_decompress_variant(r_variants.write[i], &p_buffer[r_len], p_len - r_len, &vlen, p_allow_object_decoding);
|
||||
ERR_FAIL_COND_V_MSG(err != OK, err, "Invalid packet received. Unable to decode state variable.");
|
||||
r_len += vlen;
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
void MultiplayerAPI::_add_peer(int p_id) {
|
||||
connected_peers.insert(p_id);
|
||||
path_get_cache.insert(p_id, PathGetCache());
|
||||
if (is_server()) {
|
||||
replicator->spawn_all(p_id);
|
||||
}
|
||||
replicator->on_peer_change(p_id, true);
|
||||
emit_signal(SNAME("peer_connected"), p_id);
|
||||
}
|
||||
|
||||
void MultiplayerAPI::_del_peer(int p_id) {
|
||||
connected_peers.erase(p_id);
|
||||
replicator->on_peer_change(p_id, false);
|
||||
// Cleanup get cache.
|
||||
path_get_cache.erase(p_id);
|
||||
// Cleanup sent cache.
|
||||
@ -488,6 +554,7 @@ void MultiplayerAPI::_del_peer(int p_id) {
|
||||
PathSentCache *psc = path_send_cache.getptr(E);
|
||||
psc->confirmed_peers.erase(p_id);
|
||||
}
|
||||
connected_peers.erase(p_id);
|
||||
emit_signal(SNAME("peer_disconnected"), p_id);
|
||||
}
|
||||
|
||||
@ -500,6 +567,7 @@ void MultiplayerAPI::_connection_failed() {
|
||||
}
|
||||
|
||||
void MultiplayerAPI::_server_disconnected() {
|
||||
replicator->on_reset();
|
||||
emit_signal(SNAME("server_disconnected"));
|
||||
}
|
||||
|
||||
@ -612,14 +680,26 @@ bool MultiplayerAPI::is_object_decoding_allowed() const {
|
||||
return allow_object_decoding;
|
||||
}
|
||||
|
||||
void MultiplayerAPI::scene_enter_exit_notify(const String &p_scene, Node *p_node, bool p_enter) {
|
||||
replicator->scene_enter_exit_notify(p_scene, p_node, p_enter);
|
||||
}
|
||||
|
||||
void MultiplayerAPI::rpcp(Node *p_node, int p_peer_id, const StringName &p_method, const Variant **p_arg, int p_argcount) {
|
||||
rpc_manager->rpcp(p_node, p_peer_id, p_method, p_arg, p_argcount);
|
||||
}
|
||||
|
||||
Error MultiplayerAPI::spawn(Object *p_object, Variant p_config) {
|
||||
return replicator->on_spawn(p_object, p_config);
|
||||
}
|
||||
|
||||
Error MultiplayerAPI::despawn(Object *p_object, Variant p_config) {
|
||||
return replicator->on_despawn(p_object, p_config);
|
||||
}
|
||||
|
||||
Error MultiplayerAPI::replication_start(Object *p_object, Variant p_config) {
|
||||
return replicator->on_replication_start(p_object, p_config);
|
||||
}
|
||||
|
||||
Error MultiplayerAPI::replication_stop(Object *p_object, Variant p_config) {
|
||||
return replicator->on_replication_stop(p_object, p_config);
|
||||
}
|
||||
|
||||
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);
|
||||
@ -638,14 +718,12 @@ void MultiplayerAPI::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("is_refusing_new_connections"), &MultiplayerAPI::is_refusing_new_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_connections"), "set_refuse_new_connections", "is_refusing_new_connections");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "multiplayer_peer", PROPERTY_HINT_RESOURCE_TYPE, "MultiplayerPeer", PROPERTY_USAGE_NONE), "set_multiplayer_peer", "get_multiplayer_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_connections", false);
|
||||
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "replicator", PROPERTY_HINT_RESOURCE_TYPE, "MultiplayerReplicator", PROPERTY_USAGE_NONE), "", "get_replicator");
|
||||
|
||||
ADD_SIGNAL(MethodInfo("peer_connected", PropertyInfo(Variant::INT, "id")));
|
||||
ADD_SIGNAL(MethodInfo("peer_disconnected", PropertyInfo(Variant::INT, "id")));
|
||||
@ -656,13 +734,16 @@ void MultiplayerAPI::_bind_methods() {
|
||||
}
|
||||
|
||||
MultiplayerAPI::MultiplayerAPI() {
|
||||
replicator = memnew(MultiplayerReplicator(this));
|
||||
if (create_default_replication_interface) {
|
||||
replicator = Ref<MultiplayerReplicationInterface>(create_default_replication_interface(this));
|
||||
} else {
|
||||
replicator.instantiate();
|
||||
}
|
||||
rpc_manager = memnew(RPCManager(this));
|
||||
clear();
|
||||
}
|
||||
|
||||
MultiplayerAPI::~MultiplayerAPI() {
|
||||
clear();
|
||||
memdelete(replicator);
|
||||
memdelete(rpc_manager);
|
||||
}
|
||||
|
@ -35,7 +35,26 @@
|
||||
#include "core/multiplayer/multiplayer_peer.h"
|
||||
#include "core/object/ref_counted.h"
|
||||
|
||||
class MultiplayerReplicator;
|
||||
class MultiplayerAPI;
|
||||
|
||||
class MultiplayerReplicationInterface : public RefCounted {
|
||||
GDCLASS(MultiplayerReplicationInterface, RefCounted);
|
||||
|
||||
public:
|
||||
virtual void on_peer_change(int p_id, bool p_connected) {}
|
||||
virtual void on_reset() {}
|
||||
virtual Error on_spawn_receive(int p_from, const uint8_t *p_buffer, int p_buffer_len) { return ERR_UNAVAILABLE; }
|
||||
virtual Error on_despawn_receive(int p_from, const uint8_t *p_buffer, int p_buffer_len) { return ERR_UNAVAILABLE; }
|
||||
virtual Error on_sync_receive(int p_from, const uint8_t *p_buffer, int p_buffer_len) { return ERR_UNAVAILABLE; }
|
||||
virtual Error on_spawn(Object *p_obj, Variant p_config) { return ERR_UNAVAILABLE; }
|
||||
virtual Error on_despawn(Object *p_obj, Variant p_config) { return ERR_UNAVAILABLE; }
|
||||
virtual Error on_replication_start(Object *p_obj, Variant p_config) { return ERR_UNAVAILABLE; }
|
||||
virtual Error on_replication_stop(Object *p_obj, Variant p_config) { return ERR_UNAVAILABLE; }
|
||||
virtual void on_network_process() {}
|
||||
|
||||
MultiplayerReplicationInterface() {}
|
||||
};
|
||||
|
||||
class RPCManager;
|
||||
|
||||
class MultiplayerAPI : public RefCounted {
|
||||
@ -95,7 +114,7 @@ private:
|
||||
Node *root_node = nullptr;
|
||||
bool allow_object_decoding = false;
|
||||
|
||||
MultiplayerReplicator *replicator = nullptr;
|
||||
Ref<MultiplayerReplicationInterface> replicator;
|
||||
RPCManager *rpc_manager = nullptr;
|
||||
|
||||
protected:
|
||||
@ -108,6 +127,13 @@ protected:
|
||||
void _process_raw(int p_from, const uint8_t *p_packet, int p_packet_len);
|
||||
|
||||
public:
|
||||
static MultiplayerReplicationInterface *(*create_default_replication_interface)(MultiplayerAPI *p_multiplayer);
|
||||
|
||||
static Error encode_and_compress_variant(const Variant &p_variant, uint8_t *p_buffer, int &r_len, bool p_allow_object_decoding);
|
||||
static Error decode_and_decompress_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int *r_len, bool p_allow_object_decoding);
|
||||
static Error encode_and_compress_variants(const Variant **p_variants, int p_count, uint8_t *p_buffer, int &r_len, bool *r_raw = nullptr, bool p_allow_object_decoding = false);
|
||||
static Error decode_and_decompress_variants(Vector<Variant> &r_variants, const uint8_t *p_buffer, int p_len, int &r_len, bool p_raw = false, bool p_allow_object_decoding = false);
|
||||
|
||||
void poll();
|
||||
void clear();
|
||||
void set_root_node(Node *p_node);
|
||||
@ -117,13 +143,13 @@ public:
|
||||
|
||||
Error send_bytes(Vector<uint8_t> p_data, int p_to = MultiplayerPeer::TARGET_PEER_BROADCAST, Multiplayer::TransferMode p_mode = Multiplayer::TRANSFER_MODE_RELIABLE, int p_channel = 0);
|
||||
|
||||
Error encode_and_compress_variant(const Variant &p_variant, uint8_t *p_buffer, int &r_len);
|
||||
Error decode_and_decompress_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int *r_len);
|
||||
|
||||
// Called by Node.rpc
|
||||
void rpcp(Node *p_node, int p_peer_id, 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);
|
||||
// Replication API
|
||||
Error spawn(Object *p_object, Variant p_config);
|
||||
Error despawn(Object *p_object, Variant p_config);
|
||||
Error replication_start(Object *p_object, Variant p_config);
|
||||
Error replication_stop(Object *p_object, Variant p_config);
|
||||
// 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);
|
||||
@ -148,7 +174,6 @@ public:
|
||||
void set_allow_object_decoding(bool p_enable);
|
||||
bool is_object_decoding_allowed() const;
|
||||
|
||||
MultiplayerReplicator *get_replicator() const { return replicator; }
|
||||
RPCManager *get_rpc_manager() const { return rpc_manager; }
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
|
@ -1,791 +0,0 @@
|
||||
/*************************************************************************/
|
||||
/* multiplayer_replicator.cpp */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "core/multiplayer/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 ? BYTE_OR_ZERO_FLAG : 0);
|
||||
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> peer = multiplayer->get_multiplayer_peer();
|
||||
peer->set_target_peer(p_peer);
|
||||
peer->set_transfer_channel(0);
|
||||
peer->set_transfer_mode(Multiplayer::TRANSFER_MODE_UNRELIABLE);
|
||||
return 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_server(), "The default implementation only allows sync packets from the server");
|
||||
const bool same_size = p_packet[0] & BYTE_OR_ZERO_FLAG;
|
||||
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;
|
||||
const PackedByteArray pba = state_variants[0];
|
||||
state_len = pba.size();
|
||||
} 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.slice(0, names.size() - 1), 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 ? BYTE_OR_ZERO_FLAG : 0);
|
||||
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> peer = multiplayer->get_multiplayer_peer();
|
||||
peer->set_target_peer(p_peer_id);
|
||||
peer->set_transfer_channel(0);
|
||||
peer->set_transfer_mode(Multiplayer::TRANSFER_MODE_RELIABLE);
|
||||
return 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] & BYTE_OR_ZERO_FLAG) >> BYTE_OR_ZERO_SHIFT) == 1;
|
||||
|
||||
ERR_FAIL_COND_MSG(parent->has_node(name), vformat("Unable to spawn node. Node already exists: %s/%s", parent->get_path(), name));
|
||||
RES res = ResourceLoader::load(scene_path);
|
||||
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] & BYTE_OR_ZERO_FLAG) >> BYTE_OR_ZERO_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 - SYNC_CMD_OFFSET);
|
||||
if (pba.size()) {
|
||||
memcpy(pba.ptrw(), p_packet + SYNC_CMD_OFFSET, p_packet_len - SYNC_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(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) << BYTE_OR_ZERO_SHIFT);
|
||||
encode_uint64(p_scene_id, &ptr[1]);
|
||||
if (p_data.get_type() == Variant::PACKED_BYTE_ARRAY) {
|
||||
const PackedByteArray pba = p_data;
|
||||
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> peer = multiplayer->get_multiplayer_peer();
|
||||
peer->set_target_peer(p_peer_id);
|
||||
peer->set_transfer_channel(0);
|
||||
peer->set_transfer_mode(Multiplayer::TRANSFER_MODE_RELIABLE);
|
||||
return 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_multiplayer_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_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_multiplayer_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_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] = p_spawn;
|
||||
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_multiplayer_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_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_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_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, Multiplayer::TransferMode p_transfer_mode, int p_channel) {
|
||||
ERR_FAIL_COND_V(!multiplayer->has_multiplayer_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]);
|
||||
if (p_data.size()) {
|
||||
memcpy(&ptr[SYNC_CMD_OFFSET], p_data.ptr(), p_data.size());
|
||||
}
|
||||
Ref<MultiplayerPeer> peer = multiplayer->get_multiplayer_peer();
|
||||
peer->set_target_peer(p_peer_id);
|
||||
peer->set_transfer_channel(p_channel);
|
||||
peer->set_transfer_mode(p_transfer_mode);
|
||||
return 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(Multiplayer::TRANSFER_MODE_RELIABLE), DEFVAL(0));
|
||||
ClassDB::bind_method(D_METHOD("sync_all", "scene_id", "peer_id"), &MultiplayerReplicator::sync_all, DEFVAL(0));
|
||||
ClassDB::bind_method(D_METHOD("track", "scene_id", "object"), &MultiplayerReplicator::track);
|
||||
ClassDB::bind_method(D_METHOD("untrack", "scene_id", "object"), &MultiplayerReplicator::untrack);
|
||||
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);
|
||||
}
|
@ -1,138 +0,0 @@
|
||||
/*************************************************************************/
|
||||
/* multiplayer_replicator.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 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/multiplayer/multiplayer_api.h"
|
||||
|
||||
#include "core/io/resource_uid.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:
|
||||
enum {
|
||||
BYTE_OR_ZERO_SHIFT = MultiplayerAPI::CMD_FLAG_0_SHIFT,
|
||||
};
|
||||
|
||||
enum {
|
||||
BYTE_OR_ZERO_FLAG = 1 << BYTE_OR_ZERO_SHIFT,
|
||||
};
|
||||
|
||||
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, Multiplayer::TransferMode p_mode, int p_channel);
|
||||
void track(const ResourceUID::ID &p_scene_id, Object *p_object);
|
||||
void untrack(const ResourceUID::ID &p_scene_id, Object *p_object);
|
||||
|
||||
// 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
|
@ -235,16 +235,12 @@ void RPCManager::_process_rpc(Node *p_node, const uint16_t p_rpc_method_id, int
|
||||
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_multiplayer_authority()) + ".");
|
||||
|
||||
int argc = 0;
|
||||
bool byte_only = false;
|
||||
|
||||
const bool byte_only_or_no_args = p_packet[0] & BYTE_ONLY_OR_NO_ARGS_FLAG;
|
||||
if (byte_only_or_no_args) {
|
||||
if (p_offset < p_packet_len) {
|
||||
// This packet contains only bytes.
|
||||
argc = 1;
|
||||
byte_only = true;
|
||||
} else {
|
||||
// This rpc calls a method without parameters.
|
||||
}
|
||||
} else {
|
||||
// Normal variant, takes the argument count from the packet.
|
||||
@ -262,25 +258,10 @@ void RPCManager::_process_rpc(Node *p_node, const uint16_t p_rpc_method_id, int
|
||||
_profile_node_data("in_rpc", p_node->get_instance_id());
|
||||
#endif
|
||||
|
||||
if (byte_only) {
|
||||
Vector<uint8_t> pure_data;
|
||||
const int len = p_packet_len - p_offset;
|
||||
pure_data.resize(len);
|
||||
memcpy(pure_data.ptrw(), &p_packet[p_offset], len);
|
||||
args.write[0] = pure_data;
|
||||
argp.write[0] = &args[0];
|
||||
p_offset += len;
|
||||
} else {
|
||||
for (int i = 0; i < argc; i++) {
|
||||
ERR_FAIL_COND_MSG(p_offset >= p_packet_len, "Invalid packet received. Size too small.");
|
||||
|
||||
int vlen;
|
||||
Error err = multiplayer->decode_and_decompress_variant(args.write[i], &p_packet[p_offset], p_packet_len - p_offset, &vlen);
|
||||
ERR_FAIL_COND_MSG(err != OK, "Invalid packet received. Unable to decode RPC argument.");
|
||||
|
||||
argp.write[i] = &args[i];
|
||||
p_offset += vlen;
|
||||
}
|
||||
int out;
|
||||
MultiplayerAPI::decode_and_decompress_variants(args, &p_packet[p_offset], p_packet_len - p_offset, out, byte_only_or_no_args, multiplayer->is_object_decoding_allowed());
|
||||
for (int i = 0; i < argc; i++) {
|
||||
argp.write[i] = &args[i];
|
||||
}
|
||||
|
||||
Callable::CallError ce;
|
||||
@ -380,28 +361,19 @@ void RPCManager::_send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, const Mult
|
||||
ofs += 2;
|
||||
}
|
||||
|
||||
if (p_argcount == 0) {
|
||||
byte_only_or_no_args = true;
|
||||
} else if (p_argcount == 1 && p_arg[0]->get_type() == Variant::PACKED_BYTE_ARRAY) {
|
||||
byte_only_or_no_args = true;
|
||||
// Special optimization when only the byte vector is sent.
|
||||
const Vector<uint8_t> data = *p_arg[0];
|
||||
MAKE_ROOM(ofs + data.size());
|
||||
memcpy(&(packet_cache.write[ofs]), data.ptr(), sizeof(uint8_t) * data.size());
|
||||
ofs += data.size();
|
||||
int len;
|
||||
Error err = MultiplayerAPI::encode_and_compress_variants(p_arg, p_argcount, nullptr, len, &byte_only_or_no_args, multiplayer->is_object_decoding_allowed());
|
||||
ERR_FAIL_COND_MSG(err != OK, "Unable to encode RPC arguments. THIS IS LIKELY A BUG IN THE ENGINE!");
|
||||
if (byte_only_or_no_args) {
|
||||
MAKE_ROOM(ofs + len);
|
||||
} else {
|
||||
// Arguments
|
||||
MAKE_ROOM(ofs + 1);
|
||||
MAKE_ROOM(ofs + 1 + len);
|
||||
packet_cache.write[ofs] = p_argcount;
|
||||
ofs += 1;
|
||||
for (int i = 0; i < p_argcount; i++) {
|
||||
int len(0);
|
||||
Error err = multiplayer->encode_and_compress_variant(*p_arg[i], nullptr, len);
|
||||
ERR_FAIL_COND_MSG(err != OK, "Unable to encode RPC argument. THIS IS LIKELY A BUG IN THE ENGINE!");
|
||||
MAKE_ROOM(ofs + len);
|
||||
multiplayer->encode_and_compress_variant(*p_arg[i], &(packet_cache.write[ofs]), len);
|
||||
ofs += len;
|
||||
}
|
||||
}
|
||||
if (len) {
|
||||
MultiplayerAPI::encode_and_compress_variants(p_arg, p_argcount, &packet_cache.write[ofs], len, &byte_only_or_no_args, multiplayer->is_object_decoding_allowed());
|
||||
ofs += len;
|
||||
}
|
||||
|
||||
ERR_FAIL_COND(command_type > 7);
|
||||
|
@ -69,7 +69,6 @@
|
||||
#include "core/math/triangle_mesh.h"
|
||||
#include "core/multiplayer/multiplayer_api.h"
|
||||
#include "core/multiplayer/multiplayer_peer.h"
|
||||
#include "core/multiplayer/multiplayer_replicator.h"
|
||||
#include "core/object/class_db.h"
|
||||
#include "core/object/undo_redo.h"
|
||||
#include "core/os/main_loop.h"
|
||||
@ -200,7 +199,6 @@ void register_core_types() {
|
||||
|
||||
GDREGISTER_VIRTUAL_CLASS(MultiplayerPeer);
|
||||
GDREGISTER_CLASS(MultiplayerPeerExtension);
|
||||
GDREGISTER_VIRTUAL_CLASS(MultiplayerReplicator);
|
||||
GDREGISTER_CLASS(MultiplayerAPI);
|
||||
GDREGISTER_CLASS(MainLoop);
|
||||
GDREGISTER_CLASS(Translation);
|
||||
|
@ -79,8 +79,6 @@
|
||||
<member name="refuse_new_connections" type="bool" setter="set_refuse_new_connections" getter="is_refusing_new_connections" default="false">
|
||||
If [code]true[/code], the MultiplayerAPI's [member multiplayer_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.
|
||||
|
@ -1,191 +0,0 @@
|
||||
<?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" />
|
||||
<argument index="3" name="initial" type="bool" default="true" />
|
||||
<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>
|
||||
Request a despawn for the scene identified by [code]scene_id[/code] to the given [code]peer_id[/code]. This will either trigger the default behavior, or invoke the custom spawn/despawn callables specified in [method spawn_config]. See [method send_despawn] for the default behavior.
|
||||
</description>
|
||||
</method>
|
||||
<method name="encode_state">
|
||||
<return type="PackedByteArray" />
|
||||
<argument index="0" name="scene_id" type="int" />
|
||||
<argument index="1" name="object" type="Object" />
|
||||
<argument index="2" name="initial" type="bool" default="true" />
|
||||
<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_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_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="send_sync">
|
||||
<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="PackedByteArray" />
|
||||
<argument index="3" name="transfer_mode" type="int" enum="TransferMode" default="2" />
|
||||
<argument index="4" name="channel" type="int" default="0" />
|
||||
<description>
|
||||
Sends a sync request for the instances of the scene identified by [code]scene_id[/code] to the given [code]peer_id[/code] (see [method MultiplayerPeer.set_target_peer]). This function can only be called manually when overriding the send and receive sync functions (see [method sync_config]).
|
||||
</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>
|
||||
Request a spawn for the scene identified by [code]scene_id[/code] to the given [code]peer_id[/code]. This will either trigger the default behavior, or invoke the custom spawn/despawn callables specified in [method spawn_config]. See [method send_spawn] for the default behavior.
|
||||
</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 MultiplayerReplicator 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. You can optionally specify a [code]custom_send[/code] and a [code]custom_receive[/code] to override the default behavior and customize the spawn/despawn proecess.
|
||||
Tip: You can use a custom property in the scene main script to return a customly optimized state representation.
|
||||
</description>
|
||||
</method>
|
||||
<method name="sync_all">
|
||||
<return type="int" enum="Error" />
|
||||
<argument index="0" name="scene_id" type="int" />
|
||||
<argument index="1" name="peer_id" type="int" default="0" />
|
||||
<description>
|
||||
Manually request a sync for all the instances of the scene identified by [code]scene_id[/code]. This function will trigger the default sync behavior, or call your send custom send callable if specified in [method sync_config].
|
||||
[b]Note:[/b] The default implementation only allow syncing from server to clients.
|
||||
</description>
|
||||
</method>
|
||||
<method name="sync_config">
|
||||
<return type="int" enum="Error" />
|
||||
<argument index="0" name="scene_id" type="int" />
|
||||
<argument index="1" name="interval" type="int" />
|
||||
<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 MultiplayerReplicator to sync instances of the [PackedScene] identified by [code]scene_id[/code] (see [method ResourceLoader.get_resource_uid]) for the purpose of network replication at the desired [code]interval[/code] (in milliseconds). The specified [code]properties[/code] will be part of the state sync. You can optionally specify a [code]custom_send[/code] and a [code]custom_receive[/code] to override the default behavior and customize the synchronization proecess.
|
||||
Tip: You can use a custom property in the scene main script to return a customly optimized state representation (having a single property that returns a PackedByteArray is highly recommended when dealing with many instances).
|
||||
</description>
|
||||
</method>
|
||||
<method name="track">
|
||||
<return type="void" />
|
||||
<argument index="0" name="scene_id" type="int" />
|
||||
<argument index="1" name="object" type="Object" />
|
||||
<description>
|
||||
Track the given [code]object[/code] as an instance of the scene identified by [code]scene_id[/code]. This object will be passed to your custom sync callables (see [method sync_config]). Tracking and untracking is automatic in [constant REPLICATION_MODE_SERVER].
|
||||
</description>
|
||||
</method>
|
||||
<method name="untrack">
|
||||
<return type="void" />
|
||||
<argument index="0" name="scene_id" type="int" />
|
||||
<argument index="1" name="object" type="Object" />
|
||||
<description>
|
||||
Untrack the given [code]object[/code]. This object will no longer be passed to your custom sync callables (see [method sync_config]). Tracking and untracking is automatic in [constant REPLICATION_MODE_SERVER].
|
||||
</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>
|
47
doc/classes/MultiplayerSpawner.xml
Normal file
47
doc/classes/MultiplayerSpawner.xml
Normal file
@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<class name="MultiplayerSpawner" inherits="Node" version="4.0">
|
||||
<brief_description>
|
||||
</brief_description>
|
||||
<description>
|
||||
</description>
|
||||
<tutorials>
|
||||
</tutorials>
|
||||
<methods>
|
||||
<method name="_spawn_custom" qualifiers="virtual">
|
||||
<return type="Object" />
|
||||
<argument index="0" name="data" type="Variant" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="spawn">
|
||||
<return type="Node" />
|
||||
<argument index="0" name="data" type="Variant" default="null" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
</methods>
|
||||
<members>
|
||||
<member name="auto_spawn" type="bool" setter="set_auto_spawning" getter="is_auto_spawning" default="false">
|
||||
</member>
|
||||
<member name="replication" type="PackedScene[]" setter="set_spawnable_scenes" getter="get_spawnable_scenes" default="[]">
|
||||
</member>
|
||||
<member name="spawn_limit" type="int" setter="set_spawn_limit" getter="get_spawn_limit" default="0">
|
||||
</member>
|
||||
<member name="spawn_path" type="NodePath" setter="set_spawn_path" getter="get_spawn_path" default="NodePath("")">
|
||||
</member>
|
||||
</members>
|
||||
<signals>
|
||||
<signal name="despawned">
|
||||
<argument index="0" name="scene_id" type="int" />
|
||||
<argument index="1" name="node" type="Node" />
|
||||
<description>
|
||||
</description>
|
||||
</signal>
|
||||
<signal name="spawned">
|
||||
<argument index="0" name="scene_id" type="int" />
|
||||
<argument index="1" name="node" type="Node" />
|
||||
<description>
|
||||
</description>
|
||||
</signal>
|
||||
</signals>
|
||||
</class>
|
17
doc/classes/MultiplayerSynchronizer.xml
Normal file
17
doc/classes/MultiplayerSynchronizer.xml
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<class name="MultiplayerSynchronizer" inherits="Node" version="4.0">
|
||||
<brief_description>
|
||||
</brief_description>
|
||||
<description>
|
||||
</description>
|
||||
<tutorials>
|
||||
</tutorials>
|
||||
<members>
|
||||
<member name="replication_interval" type="float" setter="set_replication_interval" getter="get_replication_interval" default="0.0">
|
||||
</member>
|
||||
<member name="resource" type="SceneReplicationConfig" setter="set_replication_config" getter="get_replication_config">
|
||||
</member>
|
||||
<member name="root_path" type="NodePath" setter="set_root_path" getter="get_root_path" default="NodePath("")">
|
||||
</member>
|
||||
</members>
|
||||
</class>
|
61
doc/classes/SceneReplicationConfig.xml
Normal file
61
doc/classes/SceneReplicationConfig.xml
Normal file
@ -0,0 +1,61 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<class name="SceneReplicationConfig" inherits="Resource" version="4.0">
|
||||
<brief_description>
|
||||
</brief_description>
|
||||
<description>
|
||||
</description>
|
||||
<tutorials>
|
||||
</tutorials>
|
||||
<methods>
|
||||
<method name="add_property">
|
||||
<return type="void" />
|
||||
<argument index="0" name="path" type="NodePath" />
|
||||
<argument index="1" name="index" type="int" default="-1" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_properties" qualifiers="const">
|
||||
<return type="NodePath[]" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="property_get_index" qualifiers="const">
|
||||
<return type="int" />
|
||||
<argument index="0" name="path" type="NodePath" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="property_get_spawn">
|
||||
<return type="bool" />
|
||||
<argument index="0" name="path" type="NodePath" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="property_get_sync">
|
||||
<return type="bool" />
|
||||
<argument index="0" name="path" type="NodePath" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="property_set_spawn">
|
||||
<return type="void" />
|
||||
<argument index="0" name="path" type="NodePath" />
|
||||
<argument index="1" name="enabled" type="bool" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="property_set_sync">
|
||||
<return type="void" />
|
||||
<argument index="0" name="path" type="NodePath" />
|
||||
<argument index="1" name="enabled" type="bool" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="remove_property">
|
||||
<return type="void" />
|
||||
<argument index="0" name="path" type="NodePath" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
</methods>
|
||||
</class>
|
@ -162,6 +162,7 @@
|
||||
#include "editor/plugins/path_3d_editor_plugin.h"
|
||||
#include "editor/plugins/physical_bone_3d_editor_plugin.h"
|
||||
#include "editor/plugins/polygon_2d_editor_plugin.h"
|
||||
#include "editor/plugins/replication_editor_plugin.h"
|
||||
#include "editor/plugins/resource_preloader_editor_plugin.h"
|
||||
#include "editor/plugins/root_motion_editor_plugin.h"
|
||||
#include "editor/plugins/script_editor_plugin.h"
|
||||
@ -7021,6 +7022,7 @@ EditorNode::EditorNode() {
|
||||
add_editor_plugin(memnew(InputEventEditorPlugin(this)));
|
||||
add_editor_plugin(memnew(SubViewportPreviewEditorPlugin(this)));
|
||||
add_editor_plugin(memnew(TextControlEditorPlugin(this)));
|
||||
add_editor_plugin(memnew(ReplicationEditorPlugin(this)));
|
||||
|
||||
for (int i = 0; i < EditorPlugins::get_plugin_count(); i++) {
|
||||
add_editor_plugin(EditorPlugins::create(i, this));
|
||||
|
390
editor/plugins/replication_editor_plugin.cpp
Normal file
390
editor/plugins/replication_editor_plugin.cpp
Normal file
@ -0,0 +1,390 @@
|
||||
/*************************************************************************/
|
||||
/* replication_editor_plugin.cpp */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 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 "replication_editor_plugin.h"
|
||||
|
||||
#include "editor/editor_scale.h"
|
||||
#include "editor/inspector_dock.h"
|
||||
#include "scene/gui/dialogs.h"
|
||||
#include "scene/gui/tree.h"
|
||||
#include "scene/multiplayer/multiplayer_synchronizer.h"
|
||||
|
||||
/// ReplicationEditor
|
||||
ReplicationEditor::ReplicationEditor(EditorNode *p_editor) {
|
||||
editor = p_editor;
|
||||
set_v_size_flags(SIZE_EXPAND_FILL);
|
||||
set_custom_minimum_size(Size2(0, 200) * EDSCALE);
|
||||
|
||||
delete_dialog = memnew(ConfirmationDialog);
|
||||
delete_dialog->connect("cancelled", callable_mp(this, &ReplicationEditor::_dialog_closed), varray(false));
|
||||
delete_dialog->connect("confirmed", callable_mp(this, &ReplicationEditor::_dialog_closed), varray(true));
|
||||
add_child(delete_dialog);
|
||||
|
||||
error_dialog = memnew(AcceptDialog);
|
||||
error_dialog->get_ok_button()->set_text(TTR("Close"));
|
||||
error_dialog->set_title(TTR("Error!"));
|
||||
add_child(error_dialog);
|
||||
|
||||
VBoxContainer *vb = memnew(VBoxContainer);
|
||||
vb->set_v_size_flags(SIZE_EXPAND_FILL);
|
||||
add_child(vb);
|
||||
|
||||
HBoxContainer *hb = memnew(HBoxContainer);
|
||||
vb->add_child(hb);
|
||||
np_line_edit = memnew(LineEdit);
|
||||
np_line_edit->set_placeholder(":property");
|
||||
np_line_edit->set_h_size_flags(SIZE_EXPAND_FILL);
|
||||
hb->add_child(np_line_edit);
|
||||
add_button = memnew(Button);
|
||||
add_button->connect("pressed", callable_mp(this, &ReplicationEditor::_add_pressed));
|
||||
add_button->set_text(TTR("Add"));
|
||||
hb->add_child(add_button);
|
||||
|
||||
tree = memnew(Tree);
|
||||
tree->set_hide_root(true);
|
||||
tree->set_columns(4);
|
||||
tree->set_column_titles_visible(true);
|
||||
tree->set_column_title(0, TTR("Properties"));
|
||||
tree->set_column_expand(0, true);
|
||||
tree->set_column_title(1, TTR("Spawn"));
|
||||
tree->set_column_expand(1, false);
|
||||
tree->set_column_custom_minimum_width(1, 100);
|
||||
tree->set_column_title(2, TTR("Sync"));
|
||||
tree->set_column_custom_minimum_width(2, 100);
|
||||
tree->set_column_expand(2, false);
|
||||
tree->set_column_expand(3, false);
|
||||
tree->create_item();
|
||||
tree->connect("button_pressed", callable_mp(this, &ReplicationEditor::_tree_button_pressed));
|
||||
tree->connect("item_edited", callable_mp(this, &ReplicationEditor::_tree_item_edited));
|
||||
tree->set_v_size_flags(SIZE_EXPAND_FILL);
|
||||
vb->add_child(tree);
|
||||
}
|
||||
|
||||
void ReplicationEditor::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("_update_config"), &ReplicationEditor::_update_config);
|
||||
ClassDB::bind_method(D_METHOD("_update_checked", "property", "column", "checked"), &ReplicationEditor::_update_checked);
|
||||
ADD_SIGNAL(MethodInfo("keying_changed"));
|
||||
}
|
||||
|
||||
void ReplicationEditor::_notification(int p_what) {
|
||||
if (p_what == NOTIFICATION_ENTER_TREE || p_what == EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED) {
|
||||
add_theme_style_override("panel", editor->get_gui_base()->get_theme_stylebox(SNAME("panel"), SNAME("Panel")));
|
||||
} else if (p_what == NOTIFICATION_VISIBILITY_CHANGED) {
|
||||
update_keying();
|
||||
}
|
||||
}
|
||||
|
||||
void ReplicationEditor::_add_pressed() {
|
||||
if (!current) {
|
||||
error_dialog->set_text(TTR("Please select a MultiplayerSynchronizer first."));
|
||||
error_dialog->popup_centered();
|
||||
return;
|
||||
}
|
||||
if (current->get_root_path().is_empty()) {
|
||||
error_dialog->set_text(TTR("The MultiplayerSynchronizer needs a root path."));
|
||||
error_dialog->popup_centered();
|
||||
return;
|
||||
}
|
||||
String np_text = np_line_edit->get_text();
|
||||
if (np_text.find(":") == -1) {
|
||||
np_text = ":" + np_text;
|
||||
}
|
||||
NodePath prop = NodePath(np_text);
|
||||
if (prop.is_empty()) {
|
||||
return;
|
||||
}
|
||||
UndoRedo *undo_redo = editor->get_undo_redo();
|
||||
undo_redo->create_action(TTR("Add property"));
|
||||
config = current->get_replication_config();
|
||||
if (config.is_null()) {
|
||||
config.instantiate();
|
||||
current->set_replication_config(config);
|
||||
undo_redo->add_do_method(current, "set_replication_config", config);
|
||||
undo_redo->add_undo_method(current, "set_replication_config", Ref<SceneReplicationConfig>());
|
||||
_update_config();
|
||||
}
|
||||
undo_redo->add_do_method(config.ptr(), "add_property", prop);
|
||||
undo_redo->add_undo_method(config.ptr(), "remove_property", prop);
|
||||
undo_redo->add_do_method(this, "_update_config");
|
||||
undo_redo->add_undo_method(this, "_update_config");
|
||||
undo_redo->commit_action();
|
||||
}
|
||||
|
||||
void ReplicationEditor::_tree_item_edited() {
|
||||
TreeItem *ti = tree->get_edited();
|
||||
if (!ti || config.is_null()) {
|
||||
return;
|
||||
}
|
||||
int column = tree->get_edited_column();
|
||||
ERR_FAIL_COND(column < 1 || column > 2);
|
||||
const NodePath prop = ti->get_metadata(0);
|
||||
UndoRedo *undo_redo = editor->get_undo_redo();
|
||||
bool value = ti->is_checked(column);
|
||||
String method;
|
||||
if (column == 1) {
|
||||
undo_redo->create_action(TTR("Set spawn property"));
|
||||
method = "property_set_spawn";
|
||||
} else {
|
||||
undo_redo->create_action(TTR("Set sync property"));
|
||||
method = "property_set_sync";
|
||||
}
|
||||
undo_redo->add_do_method(config.ptr(), method, prop, value);
|
||||
undo_redo->add_undo_method(config.ptr(), method, prop, !value);
|
||||
undo_redo->add_do_method(this, "_update_checked", prop, column, value);
|
||||
undo_redo->add_undo_method(this, "_update_checked", prop, column, !value);
|
||||
undo_redo->commit_action();
|
||||
}
|
||||
|
||||
void ReplicationEditor::_tree_button_pressed(Object *p_item, int p_column, int p_id) {
|
||||
TreeItem *ti = Object::cast_to<TreeItem>(p_item);
|
||||
if (!ti) {
|
||||
return;
|
||||
}
|
||||
deleting = ti->get_metadata(0);
|
||||
delete_dialog->set_text(TTR("Delete Property?") + "\n\"" + ti->get_text(0) + "\"");
|
||||
delete_dialog->popup_centered();
|
||||
}
|
||||
|
||||
void ReplicationEditor::_dialog_closed(bool p_confirmed) {
|
||||
if (deleting.is_empty() || config.is_null()) {
|
||||
return;
|
||||
}
|
||||
if (p_confirmed) {
|
||||
const NodePath prop = deleting;
|
||||
int idx = config->property_get_index(prop);
|
||||
bool spawn = config->property_get_spawn(prop);
|
||||
bool sync = config->property_get_sync(prop);
|
||||
UndoRedo *undo_redo = editor->get_undo_redo();
|
||||
undo_redo->create_action(TTR("Remove Property"));
|
||||
undo_redo->add_do_method(config.ptr(), "remove_property", prop);
|
||||
undo_redo->add_undo_method(config.ptr(), "add_property", prop, idx);
|
||||
undo_redo->add_undo_method(config.ptr(), "property_set_spawn", prop, spawn);
|
||||
undo_redo->add_undo_method(config.ptr(), "property_set_sync", prop, sync);
|
||||
undo_redo->add_do_method(this, "_update_config");
|
||||
undo_redo->add_undo_method(this, "_update_config");
|
||||
undo_redo->commit_action();
|
||||
}
|
||||
deleting = NodePath();
|
||||
}
|
||||
|
||||
void ReplicationEditor::_update_checked(const NodePath &p_prop, int p_column, bool p_checked) {
|
||||
if (!tree->get_root()) {
|
||||
return;
|
||||
}
|
||||
TreeItem *ti = tree->get_root()->get_first_child();
|
||||
while (ti) {
|
||||
if (ti->get_metadata(0).operator NodePath() == p_prop) {
|
||||
ti->set_checked(p_column, p_checked);
|
||||
return;
|
||||
}
|
||||
ti = ti->get_next();
|
||||
}
|
||||
}
|
||||
|
||||
void ReplicationEditor::update_keying() {
|
||||
/// TODO make keying usable.
|
||||
#if 0
|
||||
bool keying_enabled = false;
|
||||
EditorHistory *editor_history = EditorNode::get_singleton()->get_editor_history();
|
||||
if (is_visible_in_tree() && config.is_valid() && editor_history->get_path_size() > 0) {
|
||||
Object *obj = ObjectDB::get_instance(editor_history->get_path_object(0));
|
||||
keying_enabled = Object::cast_to<Node>(obj) != nullptr;
|
||||
}
|
||||
|
||||
if (keying_enabled == keying) {
|
||||
return;
|
||||
}
|
||||
|
||||
keying = keying_enabled;
|
||||
emit_signal(SNAME("keying_changed"));
|
||||
#endif
|
||||
}
|
||||
|
||||
void ReplicationEditor::_update_config() {
|
||||
deleting = NodePath();
|
||||
tree->clear();
|
||||
tree->create_item();
|
||||
if (!config.is_valid()) {
|
||||
update_keying();
|
||||
return;
|
||||
}
|
||||
TypedArray<NodePath> props = config->get_properties();
|
||||
for (int i = 0; i < props.size(); i++) {
|
||||
const NodePath path = props[i];
|
||||
_add_property(path, config->property_get_spawn(path), config->property_get_sync(path));
|
||||
}
|
||||
update_keying();
|
||||
}
|
||||
|
||||
void ReplicationEditor::edit(MultiplayerSynchronizer *p_sync) {
|
||||
if (current == p_sync) {
|
||||
return;
|
||||
}
|
||||
current = p_sync;
|
||||
if (current) {
|
||||
config = current->get_replication_config();
|
||||
} else {
|
||||
config.unref();
|
||||
}
|
||||
_update_config();
|
||||
}
|
||||
|
||||
Ref<Texture2D> ReplicationEditor::_get_class_icon(const Node *p_node) {
|
||||
if (!p_node || !has_theme_icon(p_node->get_class(), "EditorIcons")) {
|
||||
return get_theme_icon("ImportFail", "EditorIcons");
|
||||
}
|
||||
return get_theme_icon(p_node->get_class(), "EditorIcons");
|
||||
}
|
||||
|
||||
void ReplicationEditor::_add_property(const NodePath &p_property, bool p_spawn, bool p_sync) {
|
||||
String prop = String(p_property);
|
||||
TreeItem *item = tree->create_item();
|
||||
item->set_selectable(0, false);
|
||||
item->set_selectable(1, false);
|
||||
item->set_selectable(2, false);
|
||||
item->set_selectable(3, false);
|
||||
item->set_text(0, prop);
|
||||
item->set_metadata(0, prop);
|
||||
Node *root_node = current && !current->get_root_path().is_empty() ? current->get_node(current->get_root_path()) : nullptr;
|
||||
Ref<Texture2D> icon = _get_class_icon(root_node);
|
||||
if (root_node) {
|
||||
String path = prop.substr(0, prop.find(":"));
|
||||
String subpath = prop.substr(path.size());
|
||||
Node *node = root_node->get_node_or_null(path);
|
||||
if (!node) {
|
||||
node = root_node;
|
||||
}
|
||||
item->set_text(0, String(node->get_name()) + ":" + subpath);
|
||||
icon = _get_class_icon(node);
|
||||
}
|
||||
item->set_icon(0, icon);
|
||||
item->add_button(3, get_theme_icon("Remove", "EditorIcons"));
|
||||
item->set_text_alignment(1, HORIZONTAL_ALIGNMENT_CENTER);
|
||||
item->set_cell_mode(1, TreeItem::CELL_MODE_CHECK);
|
||||
item->set_checked(1, p_spawn);
|
||||
item->set_editable(1, true);
|
||||
item->set_text_alignment(2, HORIZONTAL_ALIGNMENT_CENTER);
|
||||
item->set_cell_mode(2, TreeItem::CELL_MODE_CHECK);
|
||||
item->set_checked(2, p_sync);
|
||||
item->set_editable(2, true);
|
||||
}
|
||||
|
||||
void ReplicationEditor::property_keyed(const String &p_property) {
|
||||
ERR_FAIL_COND(!current || config.is_null());
|
||||
Node *root = current->get_node(current->get_root_path());
|
||||
ERR_FAIL_COND(!root);
|
||||
EditorHistory *history = editor->get_editor_history();
|
||||
ERR_FAIL_COND(history->get_path_size() == 0);
|
||||
Node *node = Object::cast_to<Node>(ObjectDB::get_instance(history->get_path_object(0)));
|
||||
ERR_FAIL_COND(!node);
|
||||
if (node->is_class("MultiplayerSynchronizer")) {
|
||||
error_dialog->set_text(TTR("Properties of 'MultiplayerSynchronizer' cannot be configured for replication."));
|
||||
error_dialog->popup_centered();
|
||||
return;
|
||||
}
|
||||
if (history->get_path_size() > 1 || p_property.get_slice_count(":") > 1) {
|
||||
error_dialog->set_text(TTR("Subresources cannot yet be configured for replication."));
|
||||
error_dialog->popup_centered();
|
||||
return;
|
||||
}
|
||||
|
||||
String path = root->get_path_to(node);
|
||||
for (int i = 1; i < history->get_path_size(); i++) {
|
||||
String prop = history->get_path_property(i);
|
||||
ERR_FAIL_COND(prop == "");
|
||||
path += ":" + prop;
|
||||
}
|
||||
path += ":" + p_property;
|
||||
|
||||
NodePath prop = path;
|
||||
UndoRedo *undo_redo = editor->get_undo_redo();
|
||||
undo_redo->create_action(TTR("Add property"));
|
||||
undo_redo->add_do_method(config.ptr(), "add_property", prop);
|
||||
undo_redo->add_undo_method(config.ptr(), "remove_property", prop);
|
||||
undo_redo->add_do_method(this, "_update_config");
|
||||
undo_redo->add_undo_method(this, "_update_config");
|
||||
undo_redo->commit_action();
|
||||
}
|
||||
|
||||
/// ReplicationEditorPlugin
|
||||
ReplicationEditorPlugin::ReplicationEditorPlugin(EditorNode *p_node) {
|
||||
editor = p_node;
|
||||
repl_editor = memnew(ReplicationEditor(editor));
|
||||
editor->add_bottom_panel_item(TTR("Replication"), repl_editor);
|
||||
}
|
||||
|
||||
ReplicationEditorPlugin::~ReplicationEditorPlugin() {
|
||||
}
|
||||
|
||||
void ReplicationEditorPlugin::_keying_changed() {
|
||||
// TODO make lock usable.
|
||||
//InspectorDock::get_inspector_singleton()->set_keying(repl_editor->has_keying(), this);
|
||||
}
|
||||
|
||||
void ReplicationEditorPlugin::_property_keyed(const String &p_keyed, const Variant &p_value, bool p_advance) {
|
||||
if (!repl_editor->has_keying()) {
|
||||
return;
|
||||
}
|
||||
repl_editor->property_keyed(p_keyed);
|
||||
}
|
||||
|
||||
void ReplicationEditorPlugin::_notification(int p_what) {
|
||||
if (p_what == NOTIFICATION_ENTER_TREE) {
|
||||
//Node3DEditor::get_singleton()->connect("transform_key_request", callable_mp(this, &AnimationPlayerEditorPlugin::_transform_key_request));
|
||||
InspectorDock::get_inspector_singleton()->connect("property_keyed", callable_mp(this, &ReplicationEditorPlugin::_property_keyed));
|
||||
repl_editor->connect("keying_changed", callable_mp(this, &ReplicationEditorPlugin::_keying_changed));
|
||||
// TODO make lock usable.
|
||||
//InspectorDock::get_inspector_singleton()->connect("object_inspected", callable_mp(repl_editor, &ReplicationEditor::update_keying));
|
||||
get_tree()->connect("node_removed", callable_mp(this, &ReplicationEditorPlugin::_node_removed));
|
||||
}
|
||||
}
|
||||
|
||||
void ReplicationEditorPlugin::_node_removed(Node *p_node) {
|
||||
if (p_node && p_node == repl_editor->get_current()) {
|
||||
repl_editor->edit(nullptr);
|
||||
if (repl_editor->is_visible_in_tree()) {
|
||||
editor->hide_bottom_panel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ReplicationEditorPlugin::edit(Object *p_object) {
|
||||
repl_editor->edit(Object::cast_to<MultiplayerSynchronizer>(p_object));
|
||||
}
|
||||
|
||||
bool ReplicationEditorPlugin::handles(Object *p_object) const {
|
||||
return p_object->is_class("MultiplayerSynchronizer");
|
||||
}
|
||||
|
||||
void ReplicationEditorPlugin::make_visible(bool p_visible) {
|
||||
if (p_visible) {
|
||||
editor->make_bottom_panel_item_visible(repl_editor);
|
||||
}
|
||||
}
|
108
editor/plugins/replication_editor_plugin.h
Normal file
108
editor/plugins/replication_editor_plugin.h
Normal file
@ -0,0 +1,108 @@
|
||||
/*************************************************************************/
|
||||
/* replication_editor_plugin.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 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 REPLICATION_EDITOR_PLUGIN_H
|
||||
#define REPLICATION_EDITOR_PLUGIN_H
|
||||
|
||||
#include "editor/editor_node.h"
|
||||
#include "editor/editor_plugin.h"
|
||||
#include "scene/resources/scene_replication_config.h"
|
||||
|
||||
class ConfirmationDialog;
|
||||
class MultiplayerSynchronizer;
|
||||
class Tree;
|
||||
|
||||
class ReplicationEditor : public VBoxContainer {
|
||||
GDCLASS(ReplicationEditor, VBoxContainer);
|
||||
|
||||
private:
|
||||
EditorNode *editor;
|
||||
MultiplayerSynchronizer *current = nullptr;
|
||||
|
||||
AcceptDialog *error_dialog = nullptr;
|
||||
ConfirmationDialog *delete_dialog = nullptr;
|
||||
Button *add_button = nullptr;
|
||||
LineEdit *np_line_edit = nullptr;
|
||||
|
||||
Ref<SceneReplicationConfig> config;
|
||||
NodePath deleting;
|
||||
Tree *tree;
|
||||
bool keying = false;
|
||||
|
||||
Ref<Texture2D> _get_class_icon(const Node *p_node);
|
||||
|
||||
void _add_pressed();
|
||||
void _tree_item_edited();
|
||||
void _tree_button_pressed(Object *p_item, int p_column, int p_id);
|
||||
void _update_checked(const NodePath &p_prop, int p_column, bool p_checked);
|
||||
void _update_config();
|
||||
void _dialog_closed(bool p_confirmed);
|
||||
void _add_property(const NodePath &p_property, bool p_spawn = true, bool p_sync = true);
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
void update_keying();
|
||||
void edit(MultiplayerSynchronizer *p_object);
|
||||
bool has_keying() const { return keying; }
|
||||
MultiplayerSynchronizer *get_current() const { return current; }
|
||||
void property_keyed(const String &p_property);
|
||||
|
||||
ReplicationEditor(EditorNode *p_node);
|
||||
~ReplicationEditor() {}
|
||||
};
|
||||
|
||||
class ReplicationEditorPlugin : public EditorPlugin {
|
||||
GDCLASS(ReplicationEditorPlugin, EditorPlugin);
|
||||
|
||||
private:
|
||||
EditorNode *editor;
|
||||
ReplicationEditor *repl_editor;
|
||||
|
||||
void _node_removed(Node *p_node);
|
||||
void _keying_changed();
|
||||
void _property_keyed(const String &p_keyed, const Variant &p_value, bool p_advance);
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
virtual void edit(Object *p_object) override;
|
||||
virtual bool handles(Object *p_object) const override;
|
||||
virtual void make_visible(bool p_visible) override;
|
||||
|
||||
ReplicationEditorPlugin(EditorNode *p_node);
|
||||
~ReplicationEditorPlugin();
|
||||
};
|
||||
|
||||
#endif // REPLICATION_EDITOR_PLUGIN_H
|
@ -50,6 +50,7 @@
|
||||
#include "core/io/file_access.h"
|
||||
#include "core/io/file_access_memory.h"
|
||||
#include "core/io/json.h"
|
||||
#include "core/io/stream_peer.h"
|
||||
#include "core/math/disjoint_set.h"
|
||||
#include "core/math/vector2.h"
|
||||
#include "core/variant/dictionary.h"
|
||||
|
@ -9,6 +9,7 @@ env.add_source_files(env.scene_sources, "*.cpp")
|
||||
|
||||
# Chain load SCsubs
|
||||
SConscript("main/SCsub")
|
||||
SConscript("multiplayer/SCsub")
|
||||
SConscript("gui/SCsub")
|
||||
if not env["disable_3d"]:
|
||||
SConscript("3d/SCsub")
|
||||
|
@ -32,6 +32,7 @@
|
||||
|
||||
#include "core/core_string_names.h"
|
||||
#include "core/io/resource_loader.h"
|
||||
#include "core/multiplayer/multiplayer_api.h"
|
||||
#include "core/object/message_queue.h"
|
||||
#include "core/string/print_string.h"
|
||||
#include "instance_placeholder.h"
|
||||
@ -110,9 +111,6 @@ void Node::_notification(int p_notification) {
|
||||
memdelete(data.path_cache);
|
||||
data.path_cache = nullptr;
|
||||
}
|
||||
if (data.scene_file_path.length()) {
|
||||
get_multiplayer()->scene_enter_exit_notify(data.scene_file_path, this, false);
|
||||
}
|
||||
} break;
|
||||
case NOTIFICATION_PATH_RENAMED: {
|
||||
if (data.path_cache) {
|
||||
@ -141,12 +139,6 @@ void Node::_notification(int p_notification) {
|
||||
}
|
||||
|
||||
GDVIRTUAL_CALL(_ready);
|
||||
|
||||
if (data.scene_file_path.length()) {
|
||||
ERR_FAIL_COND(!is_inside_tree());
|
||||
get_multiplayer()->scene_enter_exit_notify(data.scene_file_path, this, true);
|
||||
}
|
||||
|
||||
} break;
|
||||
case NOTIFICATION_POSTINITIALIZE: {
|
||||
data.in_constructor = false;
|
||||
|
@ -212,7 +212,6 @@ protected:
|
||||
static String _get_name_num_separator();
|
||||
|
||||
friend class SceneState;
|
||||
friend class MultiplayerReplicator;
|
||||
|
||||
void _add_child_nocheck(Node *p_child, const StringName &p_name);
|
||||
void _set_owner_nocheck(Node *p_owner);
|
||||
@ -467,7 +466,7 @@ public:
|
||||
bool is_displayed_folded() const;
|
||||
/* NETWORK */
|
||||
|
||||
void set_multiplayer_authority(int p_peer_id, bool p_recursive = true);
|
||||
virtual void set_multiplayer_authority(int p_peer_id, bool p_recursive = true);
|
||||
int get_multiplayer_authority() const;
|
||||
bool is_multiplayer_authority() const;
|
||||
|
||||
|
@ -36,6 +36,7 @@
|
||||
#include "core/io/dir_access.h"
|
||||
#include "core/io/marshalls.h"
|
||||
#include "core/io/resource_loader.h"
|
||||
#include "core/multiplayer/multiplayer_api.h"
|
||||
#include "core/object/message_queue.h"
|
||||
#include "core/os/keyboard.h"
|
||||
#include "core/os/os.h"
|
||||
|
@ -31,7 +31,6 @@
|
||||
#ifndef SCENE_TREE_H
|
||||
#define SCENE_TREE_H
|
||||
|
||||
#include "core/multiplayer/multiplayer_api.h"
|
||||
#include "core/os/main_loop.h"
|
||||
#include "core/os/thread_safe.h"
|
||||
#include "core/templates/self_list.h"
|
||||
@ -46,6 +45,7 @@ class Node;
|
||||
class Window;
|
||||
class Material;
|
||||
class Mesh;
|
||||
class MultiplayerAPI;
|
||||
class SceneDebugger;
|
||||
class Tween;
|
||||
|
||||
|
5
scene/multiplayer/SCsub
Normal file
5
scene/multiplayer/SCsub
Normal file
@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
Import("env")
|
||||
|
||||
env.add_source_files(env.scene_sources, "*.cpp")
|
227
scene/multiplayer/multiplayer_spawner.cpp
Normal file
227
scene/multiplayer/multiplayer_spawner.cpp
Normal file
@ -0,0 +1,227 @@
|
||||
/*************************************************************************/
|
||||
/* multiplayer_spawner.cpp */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 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 "multiplayer_spawner.h"
|
||||
|
||||
#include "core/io/marshalls.h"
|
||||
#include "core/multiplayer/multiplayer_api.h"
|
||||
#include "scene/main/window.h"
|
||||
#include "scene/scene_string_names.h"
|
||||
|
||||
void MultiplayerSpawner::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("spawn", "data"), &MultiplayerSpawner::spawn, DEFVAL(Variant()));
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_spawnable_scenes"), &MultiplayerSpawner::get_spawnable_scenes);
|
||||
ClassDB::bind_method(D_METHOD("set_spawnable_scenes", "scenes"), &MultiplayerSpawner::set_spawnable_scenes);
|
||||
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "replication", PROPERTY_HINT_ARRAY_TYPE, vformat("%s/%s:%s", Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "PackedScene"), (PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE)), "set_spawnable_scenes", "get_spawnable_scenes");
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_spawn_path"), &MultiplayerSpawner::get_spawn_path);
|
||||
ClassDB::bind_method(D_METHOD("set_spawn_path", "path"), &MultiplayerSpawner::set_spawn_path);
|
||||
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "spawn_path", PROPERTY_HINT_NONE, ""), "set_spawn_path", "get_spawn_path");
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_spawn_limit"), &MultiplayerSpawner::get_spawn_limit);
|
||||
ClassDB::bind_method(D_METHOD("set_spawn_limit", "limit"), &MultiplayerSpawner::set_spawn_limit);
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "spawn_limit", PROPERTY_HINT_RANGE, "0,1024,1,or_greater"), "set_spawn_limit", "get_spawn_limit");
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_auto_spawning", "enabled"), &MultiplayerSpawner::set_auto_spawning);
|
||||
ClassDB::bind_method(D_METHOD("is_auto_spawning"), &MultiplayerSpawner::is_auto_spawning);
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_spawn"), "set_auto_spawning", "is_auto_spawning");
|
||||
|
||||
GDVIRTUAL_BIND(_spawn_custom, "data");
|
||||
|
||||
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")));
|
||||
}
|
||||
|
||||
void MultiplayerSpawner::_update_spawn_node() {
|
||||
#ifdef TOOLS_ENABLED
|
||||
if (Engine::get_singleton()->is_editor_hint()) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
if (spawn_node.is_valid()) {
|
||||
Node *node = Object::cast_to<Node>(ObjectDB::get_instance(spawn_node));
|
||||
if (node && node->is_connected("child_entered_tree", callable_mp(this, &MultiplayerSpawner::_node_added))) {
|
||||
node->disconnect("child_entered_tree", callable_mp(this, &MultiplayerSpawner::_node_added));
|
||||
}
|
||||
}
|
||||
Node *node = spawn_path.is_empty() && is_inside_tree() ? nullptr : get_node_or_null(spawn_path);
|
||||
if (node) {
|
||||
spawn_node = node->get_instance_id();
|
||||
if (auto_spawn) {
|
||||
node->connect("child_entered_tree", callable_mp(this, &MultiplayerSpawner::_node_added));
|
||||
}
|
||||
} else {
|
||||
spawn_node = ObjectID();
|
||||
}
|
||||
}
|
||||
|
||||
void MultiplayerSpawner::_notification(int p_what) {
|
||||
if (p_what == NOTIFICATION_POST_ENTER_TREE) {
|
||||
_update_spawn_node();
|
||||
} else if (p_what == NOTIFICATION_EXIT_TREE) {
|
||||
_update_spawn_node();
|
||||
const ObjectID *oid = nullptr;
|
||||
while ((oid = tracked_nodes.next(oid))) {
|
||||
Node *node = Object::cast_to<Node>(ObjectDB::get_instance(*oid));
|
||||
ERR_CONTINUE(!node);
|
||||
node->disconnect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &MultiplayerSpawner::_node_exit));
|
||||
// This is unlikely, but might still crash the engine.
|
||||
if (node->is_connected(SceneStringNames::get_singleton()->ready, callable_mp(this, &MultiplayerSpawner::_node_ready))) {
|
||||
node->disconnect(SceneStringNames::get_singleton()->ready, callable_mp(this, &MultiplayerSpawner::_node_ready));
|
||||
}
|
||||
get_multiplayer()->despawn(node, this);
|
||||
}
|
||||
tracked_nodes.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void MultiplayerSpawner::_node_added(Node *p_node) {
|
||||
if (!get_multiplayer()->has_multiplayer_peer() || !is_multiplayer_authority()) {
|
||||
return;
|
||||
}
|
||||
if (tracked_nodes.has(p_node->get_instance_id())) {
|
||||
return;
|
||||
}
|
||||
const Node *parent = get_spawn_node();
|
||||
if (!parent || p_node->get_parent() != parent) {
|
||||
return;
|
||||
}
|
||||
int id = get_scene_id(p_node->get_scene_file_path());
|
||||
if (id == INVALID_ID) {
|
||||
return;
|
||||
}
|
||||
const String name = p_node->get_name();
|
||||
ERR_FAIL_COND_MSG(name.validate_node_name() != name, vformat("Unable to auto-spawn node with reserved name: %s. Make sure to add your replicated scenes via 'add_child(node, true)' to produce valid names.", name));
|
||||
_track(p_node, Variant(), id);
|
||||
}
|
||||
|
||||
void MultiplayerSpawner::set_auto_spawning(bool p_enabled) {
|
||||
auto_spawn = p_enabled;
|
||||
_update_spawn_node();
|
||||
}
|
||||
|
||||
bool MultiplayerSpawner::is_auto_spawning() const {
|
||||
return auto_spawn;
|
||||
}
|
||||
|
||||
TypedArray<PackedScene> MultiplayerSpawner::get_spawnable_scenes() {
|
||||
return spawnable_scenes;
|
||||
}
|
||||
|
||||
void MultiplayerSpawner::set_spawnable_scenes(TypedArray<PackedScene> p_scenes) {
|
||||
spawnable_scenes = p_scenes;
|
||||
}
|
||||
|
||||
NodePath MultiplayerSpawner::get_spawn_path() const {
|
||||
return spawn_path;
|
||||
}
|
||||
|
||||
void MultiplayerSpawner::set_spawn_path(const NodePath &p_path) {
|
||||
spawn_path = p_path;
|
||||
_update_spawn_node();
|
||||
}
|
||||
|
||||
void MultiplayerSpawner::_track(Node *p_node, const Variant &p_argument, int p_scene_id) {
|
||||
ObjectID oid = p_node->get_instance_id();
|
||||
if (!tracked_nodes.has(oid)) {
|
||||
tracked_nodes[oid] = SpawnInfo(p_argument.duplicate(true), p_scene_id);
|
||||
p_node->connect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &MultiplayerSpawner::_node_exit), varray(p_node->get_instance_id()), CONNECT_ONESHOT);
|
||||
p_node->connect(SceneStringNames::get_singleton()->ready, callable_mp(this, &MultiplayerSpawner::_node_ready), varray(p_node->get_instance_id()), CONNECT_ONESHOT);
|
||||
}
|
||||
}
|
||||
|
||||
void MultiplayerSpawner::_node_ready(ObjectID p_id) {
|
||||
get_multiplayer()->spawn(ObjectDB::get_instance(p_id), this);
|
||||
}
|
||||
|
||||
void MultiplayerSpawner::_node_exit(ObjectID p_id) {
|
||||
Node *node = Object::cast_to<Node>(ObjectDB::get_instance(p_id));
|
||||
ERR_FAIL_COND(!node);
|
||||
if (tracked_nodes.has(p_id)) {
|
||||
tracked_nodes.erase(p_id);
|
||||
get_multiplayer()->despawn(node, this);
|
||||
}
|
||||
}
|
||||
|
||||
int MultiplayerSpawner::get_scene_id(const String &p_scene) const {
|
||||
for (int i = 0; i < spawnable_scenes.size(); i++) {
|
||||
Ref<PackedScene> ps = spawnable_scenes[i];
|
||||
ERR_CONTINUE(ps.is_null());
|
||||
if (ps->get_path() == p_scene) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return INVALID_ID;
|
||||
}
|
||||
|
||||
int MultiplayerSpawner::get_spawn_id(const ObjectID &p_id) const {
|
||||
const SpawnInfo *info = tracked_nodes.getptr(p_id);
|
||||
return info ? info->id : INVALID_ID;
|
||||
}
|
||||
|
||||
const Variant MultiplayerSpawner::get_spawn_argument(const ObjectID &p_id) const {
|
||||
const SpawnInfo *info = tracked_nodes.getptr(p_id);
|
||||
return info ? info->args : Variant();
|
||||
}
|
||||
|
||||
Node *MultiplayerSpawner::instantiate_scene(int p_id) {
|
||||
ERR_FAIL_COND_V_MSG(spawn_limit && spawn_limit <= tracked_nodes.size(), nullptr, "Spawn limit reached!");
|
||||
ERR_FAIL_INDEX_V(p_id, spawnable_scenes.size(), nullptr);
|
||||
Ref<PackedScene> scene = spawnable_scenes[p_id];
|
||||
ERR_FAIL_COND_V(scene.is_null(), nullptr);
|
||||
return scene->instantiate();
|
||||
}
|
||||
|
||||
Node *MultiplayerSpawner::instantiate_custom(const Variant &p_data) {
|
||||
ERR_FAIL_COND_V_MSG(spawn_limit && spawn_limit <= tracked_nodes.size(), nullptr, "Spawn limit reached!");
|
||||
Object *obj = nullptr;
|
||||
Node *node = nullptr;
|
||||
if (GDVIRTUAL_CALL(_spawn_custom, p_data, obj)) {
|
||||
node = Object::cast_to<Node>(obj);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
Node *MultiplayerSpawner::spawn(const Variant &p_data) {
|
||||
ERR_FAIL_COND_V(!is_inside_tree() || !get_multiplayer()->has_multiplayer_peer() || !is_multiplayer_authority(), nullptr);
|
||||
ERR_FAIL_COND_V_MSG(spawn_limit && spawn_limit <= tracked_nodes.size(), nullptr, "Spawn limit reached!");
|
||||
ERR_FAIL_COND_V_MSG(!GDVIRTUAL_IS_OVERRIDDEN(_spawn_custom), nullptr, "Custom spawn requires the '_spawn_custom' virtual method to be implemented via script.");
|
||||
|
||||
Node *parent = get_spawn_node();
|
||||
ERR_FAIL_COND_V_MSG(!parent, nullptr, "Cannot find spawn node.");
|
||||
|
||||
Node *node = instantiate_custom(p_data);
|
||||
ERR_FAIL_COND_V_MSG(!node, nullptr, "The '_spawn_custom' implementation must return a valid Node.");
|
||||
|
||||
_track(node, p_data);
|
||||
parent->add_child(node, true);
|
||||
return node;
|
||||
}
|
101
scene/multiplayer/multiplayer_spawner.h
Normal file
101
scene/multiplayer/multiplayer_spawner.h
Normal file
@ -0,0 +1,101 @@
|
||||
/*************************************************************************/
|
||||
/* multiplayer_spawner.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 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_SPAWNER_H
|
||||
#define MULTIPLAYER_SPAWNER_H
|
||||
|
||||
#include "scene/main/node.h"
|
||||
|
||||
#include "core/variant/typed_array.h"
|
||||
#include "scene/resources/packed_scene.h"
|
||||
#include "scene/resources/scene_replication_config.h"
|
||||
|
||||
class MultiplayerSpawner : public Node {
|
||||
GDCLASS(MultiplayerSpawner, Node);
|
||||
|
||||
public:
|
||||
enum {
|
||||
INVALID_ID = 0xFF,
|
||||
};
|
||||
|
||||
private:
|
||||
TypedArray<PackedScene> spawnable_scenes;
|
||||
Set<ResourceUID::ID> spawnable_ids;
|
||||
NodePath spawn_path;
|
||||
|
||||
struct SpawnInfo {
|
||||
Variant args;
|
||||
int id = INVALID_ID;
|
||||
SpawnInfo(Variant p_args, int p_id) {
|
||||
id = p_id;
|
||||
args = p_args;
|
||||
}
|
||||
SpawnInfo() {}
|
||||
};
|
||||
|
||||
ObjectID spawn_node;
|
||||
HashMap<ObjectID, SpawnInfo> tracked_nodes;
|
||||
bool auto_spawn = false;
|
||||
uint32_t spawn_limit = 0;
|
||||
|
||||
void _update_spawn_node();
|
||||
void _track(Node *p_node, const Variant &p_argument, int p_scene_id = INVALID_ID);
|
||||
void _node_added(Node *p_node);
|
||||
void _node_exit(ObjectID p_id);
|
||||
void _node_ready(ObjectID p_id);
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
Node *get_spawn_node() const { return spawn_node.is_valid() ? Object::cast_to<Node>(ObjectDB::get_instance(spawn_node)) : nullptr; }
|
||||
TypedArray<PackedScene> get_spawnable_scenes();
|
||||
void set_spawnable_scenes(TypedArray<PackedScene> p_scenes);
|
||||
NodePath get_spawn_path() const;
|
||||
void set_spawn_path(const NodePath &p_path);
|
||||
uint32_t get_spawn_limit() const { return spawn_limit; }
|
||||
void set_spawn_limit(uint32_t p_limit) { spawn_limit = p_limit; }
|
||||
bool is_auto_spawning() const;
|
||||
void set_auto_spawning(bool p_enabled);
|
||||
|
||||
const Variant get_spawn_argument(const ObjectID &p_id) const;
|
||||
int get_spawn_id(const ObjectID &p_id) const;
|
||||
int get_scene_id(const String &p_path) const;
|
||||
Node *spawn(const Variant &p_data = Variant());
|
||||
Node *instantiate_custom(const Variant &p_data);
|
||||
Node *instantiate_scene(int p_idx);
|
||||
|
||||
GDVIRTUAL1R(Object *, _spawn_custom, const Variant &);
|
||||
|
||||
MultiplayerSpawner() {}
|
||||
};
|
||||
|
||||
#endif // MULTIPLAYER_SPAWNER_H
|
158
scene/multiplayer/multiplayer_synchronizer.cpp
Normal file
158
scene/multiplayer/multiplayer_synchronizer.cpp
Normal file
@ -0,0 +1,158 @@
|
||||
/*************************************************************************/
|
||||
/* multiplayer_synchronizer.cpp */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 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 "multiplayer_synchronizer.h"
|
||||
|
||||
#include "core/config/engine.h"
|
||||
#include "core/multiplayer/multiplayer_api.h"
|
||||
|
||||
Object *MultiplayerSynchronizer::_get_prop_target(Object *p_obj, const NodePath &p_path) {
|
||||
if (p_path.get_name_count() == 0) {
|
||||
return p_obj;
|
||||
}
|
||||
Node *node = Object::cast_to<Node>(p_obj);
|
||||
ERR_FAIL_COND_V_MSG(!node || !node->has_node(p_path), nullptr, vformat("Node '%s' not found.", p_path));
|
||||
return node->get_node(p_path);
|
||||
}
|
||||
|
||||
void MultiplayerSynchronizer::_stop() {
|
||||
Node *node = is_inside_tree() ? get_node_or_null(root_path) : nullptr;
|
||||
if (node) {
|
||||
get_multiplayer()->replication_stop(node, this);
|
||||
}
|
||||
}
|
||||
|
||||
void MultiplayerSynchronizer::_start() {
|
||||
Node *node = is_inside_tree() ? get_node_or_null(root_path) : nullptr;
|
||||
if (node) {
|
||||
get_multiplayer()->replication_start(node, this);
|
||||
}
|
||||
}
|
||||
|
||||
Error MultiplayerSynchronizer::get_state(const List<NodePath> &p_properties, Object *p_obj, Vector<Variant> &r_variant, Vector<const Variant *> &r_variant_ptrs) {
|
||||
ERR_FAIL_COND_V(!p_obj, ERR_INVALID_PARAMETER);
|
||||
r_variant.resize(p_properties.size());
|
||||
r_variant_ptrs.resize(r_variant.size());
|
||||
int i = 0;
|
||||
for (const NodePath &prop : p_properties) {
|
||||
bool valid = false;
|
||||
const Object *obj = _get_prop_target(p_obj, prop);
|
||||
ERR_FAIL_COND_V(!obj, FAILED);
|
||||
r_variant.write[i] = obj->get(prop.get_concatenated_subnames(), &valid);
|
||||
r_variant_ptrs.write[i] = &r_variant[i];
|
||||
ERR_FAIL_COND_V_MSG(!valid, ERR_INVALID_DATA, vformat("Property '%s' not found.", prop));
|
||||
i++;
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error MultiplayerSynchronizer::set_state(const List<NodePath> &p_properties, Object *p_obj, const Vector<Variant> &p_state) {
|
||||
ERR_FAIL_COND_V(!p_obj, ERR_INVALID_PARAMETER);
|
||||
int i = 0;
|
||||
for (const NodePath &prop : p_properties) {
|
||||
Object *obj = _get_prop_target(p_obj, prop);
|
||||
ERR_FAIL_COND_V(!obj, FAILED);
|
||||
obj->set(prop.get_concatenated_subnames(), p_state[i]);
|
||||
i += 1;
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
void MultiplayerSynchronizer::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("set_root_path", "path"), &MultiplayerSynchronizer::set_root_path);
|
||||
ClassDB::bind_method(D_METHOD("get_root_path"), &MultiplayerSynchronizer::get_root_path);
|
||||
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "root_path"), "set_root_path", "get_root_path");
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_replication_interval", "milliseconds"), &MultiplayerSynchronizer::set_replication_interval);
|
||||
ClassDB::bind_method(D_METHOD("get_replication_interval"), &MultiplayerSynchronizer::get_replication_interval);
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "replication_interval", PROPERTY_HINT_RANGE, "0,5,0.001"), "set_replication_interval", "get_replication_interval");
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_replication_config", "config"), &MultiplayerSynchronizer::set_replication_config);
|
||||
ClassDB::bind_method(D_METHOD("get_replication_config"), &MultiplayerSynchronizer::get_replication_config);
|
||||
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "resource", PROPERTY_HINT_RESOURCE_TYPE, "SceneReplicationConfig"), "set_replication_config", "get_replication_config");
|
||||
}
|
||||
|
||||
void MultiplayerSynchronizer::_notification(int p_what) {
|
||||
#ifdef TOOLS_ENABLED
|
||||
if (Engine::get_singleton()->is_editor_hint()) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
if (root_path.is_empty()) {
|
||||
return;
|
||||
}
|
||||
if (p_what == NOTIFICATION_ENTER_TREE) {
|
||||
_start();
|
||||
} else if (p_what == NOTIFICATION_EXIT_TREE) {
|
||||
_stop();
|
||||
}
|
||||
}
|
||||
|
||||
void MultiplayerSynchronizer::set_replication_interval(double p_interval) {
|
||||
ERR_FAIL_COND_MSG(p_interval < 0, "Interval must be greater or equal to 0 (where 0 means default)");
|
||||
interval_msec = uint64_t(p_interval * 1000);
|
||||
}
|
||||
|
||||
double MultiplayerSynchronizer::get_replication_interval() const {
|
||||
return double(interval_msec) / 1000.0;
|
||||
}
|
||||
|
||||
uint64_t MultiplayerSynchronizer::get_replication_interval_msec() const {
|
||||
return interval_msec;
|
||||
}
|
||||
|
||||
void MultiplayerSynchronizer::set_replication_config(Ref<SceneReplicationConfig> p_config) {
|
||||
replication_config = p_config;
|
||||
}
|
||||
|
||||
Ref<SceneReplicationConfig> MultiplayerSynchronizer::get_replication_config() {
|
||||
return replication_config;
|
||||
}
|
||||
|
||||
void MultiplayerSynchronizer::set_root_path(const NodePath &p_path) {
|
||||
_stop();
|
||||
root_path = p_path;
|
||||
_start();
|
||||
}
|
||||
|
||||
NodePath MultiplayerSynchronizer::get_root_path() const {
|
||||
return root_path;
|
||||
}
|
||||
|
||||
void MultiplayerSynchronizer::set_multiplayer_authority(int p_peer_id, bool p_recursive) {
|
||||
Node *node = is_inside_tree() ? get_node_or_null(root_path) : nullptr;
|
||||
if (!node) {
|
||||
Node::set_multiplayer_authority(p_peer_id, p_recursive);
|
||||
return;
|
||||
}
|
||||
get_multiplayer()->replication_stop(node, this);
|
||||
Node::set_multiplayer_authority(p_peer_id, p_recursive);
|
||||
get_multiplayer()->replication_start(node, this);
|
||||
}
|
72
scene/multiplayer/multiplayer_synchronizer.h
Normal file
72
scene/multiplayer/multiplayer_synchronizer.h
Normal file
@ -0,0 +1,72 @@
|
||||
/*************************************************************************/
|
||||
/* multiplayer_synchronizer.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 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_SYNCHRONIZER_H
|
||||
#define MULTIPLAYER_SYNCHRONIZER_H
|
||||
|
||||
#include "scene/main/node.h"
|
||||
|
||||
#include "scene/resources/scene_replication_config.h"
|
||||
|
||||
class MultiplayerSynchronizer : public Node {
|
||||
GDCLASS(MultiplayerSynchronizer, Node);
|
||||
|
||||
private:
|
||||
Ref<SceneReplicationConfig> replication_config;
|
||||
NodePath root_path;
|
||||
uint64_t interval_msec = 0;
|
||||
|
||||
static Object *_get_prop_target(Object *p_obj, const NodePath &p_prop);
|
||||
void _start();
|
||||
void _stop();
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
static Error get_state(const List<NodePath> &p_properties, Object *p_obj, Vector<Variant> &r_variant, Vector<const Variant *> &r_variant_ptrs);
|
||||
static Error set_state(const List<NodePath> &p_properties, Object *p_obj, const Vector<Variant> &p_state);
|
||||
|
||||
void set_replication_interval(double p_interval);
|
||||
double get_replication_interval() const;
|
||||
uint64_t get_replication_interval_msec() const;
|
||||
|
||||
void set_replication_config(Ref<SceneReplicationConfig> p_config);
|
||||
Ref<SceneReplicationConfig> get_replication_config();
|
||||
|
||||
void set_root_path(const NodePath &p_path);
|
||||
NodePath get_root_path() const;
|
||||
virtual void set_multiplayer_authority(int p_peer_id, bool p_recursive = true) override;
|
||||
|
||||
MultiplayerSynchronizer() {}
|
||||
};
|
||||
|
||||
#endif // MULTIPLAYER_SYNCHRONIZER_H
|
415
scene/multiplayer/scene_replication_interface.cpp
Normal file
415
scene/multiplayer/scene_replication_interface.cpp
Normal file
@ -0,0 +1,415 @@
|
||||
/*************************************************************************/
|
||||
/* scene_replication_interface.cpp */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 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 "scene_replication_interface.h"
|
||||
|
||||
#include "core/io/marshalls.h"
|
||||
#include "scene/main/node.h"
|
||||
#include "scene/multiplayer/multiplayer_spawner.h"
|
||||
#include "scene/multiplayer/multiplayer_synchronizer.h"
|
||||
|
||||
#define MAKE_ROOM(m_amount) \
|
||||
if (packet_cache.size() < m_amount) \
|
||||
packet_cache.resize(m_amount);
|
||||
|
||||
MultiplayerReplicationInterface *SceneReplicationInterface::_create(MultiplayerAPI *p_multiplayer) {
|
||||
return memnew(SceneReplicationInterface(p_multiplayer));
|
||||
}
|
||||
|
||||
void SceneReplicationInterface::make_default() {
|
||||
MultiplayerAPI::create_default_replication_interface = _create;
|
||||
}
|
||||
|
||||
void SceneReplicationInterface::_free_remotes(int p_id) {
|
||||
const HashMap<uint32_t, ObjectID> remotes = rep_state->peer_get_remotes(p_id);
|
||||
const uint32_t *k = nullptr;
|
||||
while ((k = remotes.next(k))) {
|
||||
Node *node = rep_state->get_node(remotes.get(*k));
|
||||
ERR_CONTINUE(!node);
|
||||
node->queue_delete();
|
||||
}
|
||||
}
|
||||
|
||||
void SceneReplicationInterface::on_peer_change(int p_id, bool p_connected) {
|
||||
if (p_connected) {
|
||||
rep_state->on_peer_change(p_id, p_connected);
|
||||
for (const ObjectID &oid : rep_state->get_spawned_nodes()) {
|
||||
_send_spawn(rep_state->get_node(oid), rep_state->get_spawner(oid), p_id);
|
||||
}
|
||||
for (const ObjectID &oid : rep_state->get_path_only_nodes()) {
|
||||
Node *node = rep_state->get_node(oid);
|
||||
MultiplayerSynchronizer *sync = rep_state->get_synchronizer(oid);
|
||||
ERR_CONTINUE(!node || !sync);
|
||||
if (sync->is_multiplayer_authority()) {
|
||||
rep_state->peer_add_node(p_id, oid);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_free_remotes(p_id);
|
||||
rep_state->on_peer_change(p_id, p_connected);
|
||||
}
|
||||
}
|
||||
|
||||
void SceneReplicationInterface::on_reset() {
|
||||
for (int pid : rep_state->get_peers()) {
|
||||
_free_remotes(pid);
|
||||
}
|
||||
rep_state->reset();
|
||||
}
|
||||
|
||||
void SceneReplicationInterface::on_network_process() {
|
||||
uint64_t msec = OS::get_singleton()->get_ticks_msec();
|
||||
for (int peer : rep_state->get_peers()) {
|
||||
_send_sync(peer, msec);
|
||||
}
|
||||
}
|
||||
|
||||
Error SceneReplicationInterface::on_spawn(Object *p_obj, Variant p_config) {
|
||||
Node *node = Object::cast_to<Node>(p_obj);
|
||||
ERR_FAIL_COND_V(!node || p_config.get_type() != Variant::OBJECT, ERR_INVALID_PARAMETER);
|
||||
MultiplayerSpawner *spawner = Object::cast_to<MultiplayerSpawner>(p_config.get_validated_object());
|
||||
ERR_FAIL_COND_V(!spawner, ERR_INVALID_PARAMETER);
|
||||
Error err = rep_state->config_add_spawn(node, spawner);
|
||||
ERR_FAIL_COND_V(err != OK, err);
|
||||
return _send_spawn(node, spawner, 0);
|
||||
}
|
||||
|
||||
Error SceneReplicationInterface::on_despawn(Object *p_obj, Variant p_config) {
|
||||
Node *node = Object::cast_to<Node>(p_obj);
|
||||
ERR_FAIL_COND_V(!node || p_config.get_type() != Variant::OBJECT, ERR_INVALID_PARAMETER);
|
||||
MultiplayerSpawner *spawner = Object::cast_to<MultiplayerSpawner>(p_config.get_validated_object());
|
||||
ERR_FAIL_COND_V(!p_obj || !spawner, ERR_INVALID_PARAMETER);
|
||||
Error err = rep_state->config_del_spawn(node, spawner);
|
||||
ERR_FAIL_COND_V(err != OK, err);
|
||||
return _send_despawn(node, 0);
|
||||
}
|
||||
|
||||
Error SceneReplicationInterface::on_replication_start(Object *p_obj, Variant p_config) {
|
||||
Node *node = Object::cast_to<Node>(p_obj);
|
||||
ERR_FAIL_COND_V(!node || p_config.get_type() != Variant::OBJECT, ERR_INVALID_PARAMETER);
|
||||
MultiplayerSynchronizer *sync = Object::cast_to<MultiplayerSynchronizer>(p_config.get_validated_object());
|
||||
ERR_FAIL_COND_V(!sync, ERR_INVALID_PARAMETER);
|
||||
rep_state->config_add_sync(node, sync);
|
||||
// Try to apply initial state if spawning (hack to apply if before ready).
|
||||
if (pending_spawn == p_obj->get_instance_id()) {
|
||||
pending_spawn = ObjectID(); // Make sure this only happens once.
|
||||
const List<NodePath> props = sync->get_replication_config()->get_spawn_properties();
|
||||
Vector<Variant> vars;
|
||||
vars.resize(props.size());
|
||||
int consumed;
|
||||
Error err = MultiplayerAPI::decode_and_decompress_variants(vars, pending_buffer, pending_buffer_size, consumed);
|
||||
ERR_FAIL_COND_V(err, err);
|
||||
err = MultiplayerSynchronizer::set_state(props, node, vars);
|
||||
ERR_FAIL_COND_V(err, err);
|
||||
} else if (multiplayer->has_multiplayer_peer() && sync->is_multiplayer_authority()) {
|
||||
// Either it's a spawn or a static sync, in any case add it to the list of known nodes.
|
||||
rep_state->peer_add_node(0, p_obj->get_instance_id());
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error SceneReplicationInterface::on_replication_stop(Object *p_obj, Variant p_config) {
|
||||
Node *node = Object::cast_to<Node>(p_obj);
|
||||
ERR_FAIL_COND_V(!node || p_config.get_type() != Variant::OBJECT, ERR_INVALID_PARAMETER);
|
||||
MultiplayerSynchronizer *sync = Object::cast_to<MultiplayerSynchronizer>(p_config.get_validated_object());
|
||||
ERR_FAIL_COND_V(!p_obj || !sync, ERR_INVALID_PARAMETER);
|
||||
return rep_state->config_del_sync(node, sync);
|
||||
}
|
||||
|
||||
Error SceneReplicationInterface::_send_raw(const uint8_t *p_buffer, int p_size, int p_peer, bool p_reliable) {
|
||||
ERR_FAIL_COND_V(!p_buffer || p_size < 1, ERR_INVALID_PARAMETER);
|
||||
ERR_FAIL_COND_V(!multiplayer, ERR_UNCONFIGURED);
|
||||
ERR_FAIL_COND_V(!multiplayer->has_multiplayer_peer(), ERR_UNCONFIGURED);
|
||||
Ref<MultiplayerPeer> peer = multiplayer->get_multiplayer_peer();
|
||||
peer->set_target_peer(p_peer);
|
||||
peer->set_transfer_channel(0);
|
||||
peer->set_transfer_mode(p_reliable ? Multiplayer::TRANSFER_MODE_RELIABLE : Multiplayer::TRANSFER_MODE_UNRELIABLE);
|
||||
return peer->put_packet(p_buffer, p_size);
|
||||
}
|
||||
|
||||
Error SceneReplicationInterface::_send_spawn(Node *p_node, MultiplayerSpawner *p_spawner, int p_peer) {
|
||||
ERR_FAIL_COND_V(p_peer < 0, ERR_BUG);
|
||||
ERR_FAIL_COND_V(!multiplayer, ERR_BUG);
|
||||
ERR_FAIL_COND_V(!p_spawner || !p_node, ERR_BUG);
|
||||
|
||||
const ObjectID oid = p_node->get_instance_id();
|
||||
uint32_t nid = rep_state->ensure_net_id(oid);
|
||||
|
||||
// Prepare custom arg and scene_id
|
||||
uint8_t scene_id = p_spawner->get_spawn_id(oid);
|
||||
bool is_custom = scene_id == MultiplayerSpawner::INVALID_ID;
|
||||
Variant spawn_arg = p_spawner->get_spawn_argument(oid);
|
||||
int spawn_arg_size = 0;
|
||||
if (is_custom) {
|
||||
Error err = MultiplayerAPI::encode_and_compress_variant(spawn_arg, nullptr, spawn_arg_size, false);
|
||||
ERR_FAIL_COND_V(err, err);
|
||||
}
|
||||
|
||||
// Prepare spawn state.
|
||||
int state_size = 0;
|
||||
Vector<Variant> state_vars;
|
||||
Vector<const Variant *> state_varp;
|
||||
MultiplayerSynchronizer *synchronizer = rep_state->get_synchronizer(oid);
|
||||
if (synchronizer && synchronizer->get_replication_config().is_valid()) {
|
||||
const List<NodePath> props = synchronizer->get_replication_config()->get_spawn_properties();
|
||||
Error err = MultiplayerSynchronizer::get_state(props, p_node, state_vars, state_varp);
|
||||
ERR_FAIL_COND_V_MSG(err != OK, err, "Unable to retrieve spawn state.");
|
||||
err = MultiplayerAPI::encode_and_compress_variants(state_varp.ptrw(), state_varp.size(), nullptr, state_size);
|
||||
ERR_FAIL_COND_V_MSG(err != OK, err, "Unable to encode spawn state.");
|
||||
}
|
||||
|
||||
// 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_spawner->get_path());
|
||||
|
||||
int path_id = 0;
|
||||
multiplayer->send_confirm_path(p_spawner, rel_path, p_peer, path_id);
|
||||
|
||||
// Encode name and parent ID.
|
||||
CharString cname = p_node->get_name().operator String().utf8();
|
||||
int nlen = encode_cstring(cname.get_data(), nullptr);
|
||||
MAKE_ROOM(1 + 1 + 4 + 4 + 4 + nlen + (is_custom ? 4 + spawn_arg_size : 0) + state_size);
|
||||
uint8_t *ptr = packet_cache.ptrw();
|
||||
ptr[0] = (uint8_t)MultiplayerAPI::NETWORK_COMMAND_SPAWN;
|
||||
ptr[1] = scene_id;
|
||||
int ofs = 2;
|
||||
ofs += encode_uint32(path_id, &ptr[ofs]);
|
||||
ofs += encode_uint32(nid, &ptr[ofs]);
|
||||
ofs += encode_uint32(nlen, &ptr[ofs]);
|
||||
ofs += encode_cstring(cname.get_data(), &ptr[ofs]);
|
||||
// Write args
|
||||
if (is_custom) {
|
||||
ofs += encode_uint32(spawn_arg_size, &ptr[ofs]);
|
||||
Error err = MultiplayerAPI::encode_and_compress_variant(spawn_arg, &ptr[ofs], spawn_arg_size, false);
|
||||
ERR_FAIL_COND_V(err, err);
|
||||
ofs += spawn_arg_size;
|
||||
}
|
||||
// Write state.
|
||||
if (state_size) {
|
||||
Error err = MultiplayerAPI::encode_and_compress_variants(state_varp.ptrw(), state_varp.size(), &ptr[ofs], state_size);
|
||||
ERR_FAIL_COND_V(err, err);
|
||||
ofs += state_size;
|
||||
}
|
||||
Error err = _send_raw(ptr, ofs, p_peer, true);
|
||||
ERR_FAIL_COND_V(err, err);
|
||||
return rep_state->peer_add_node(p_peer, oid);
|
||||
}
|
||||
|
||||
Error SceneReplicationInterface::_send_despawn(Node *p_node, int p_peer) {
|
||||
const ObjectID oid = p_node->get_instance_id();
|
||||
MAKE_ROOM(5);
|
||||
uint8_t *ptr = packet_cache.ptrw();
|
||||
ptr[0] = (uint8_t)MultiplayerAPI::NETWORK_COMMAND_DESPAWN;
|
||||
int ofs = 1;
|
||||
uint32_t nid = rep_state->get_net_id(oid);
|
||||
ofs += encode_uint32(nid, &ptr[ofs]);
|
||||
Error err = _send_raw(ptr, ofs, p_peer, true);
|
||||
ERR_FAIL_COND_V(err, err);
|
||||
return rep_state->peer_del_node(p_peer, oid);
|
||||
}
|
||||
|
||||
Error SceneReplicationInterface::on_spawn_receive(int p_from, const uint8_t *p_buffer, int p_buffer_len) {
|
||||
ERR_FAIL_COND_V_MSG(p_buffer_len < 14, ERR_INVALID_DATA, "Invalid spawn packet received");
|
||||
int ofs = 1; // The spawn/despawn command.
|
||||
uint8_t scene_id = p_buffer[ofs];
|
||||
ofs += 1;
|
||||
uint32_t node_target = decode_uint32(&p_buffer[ofs]);
|
||||
ofs += 4;
|
||||
MultiplayerSpawner *spawner = Object::cast_to<MultiplayerSpawner>(multiplayer->get_cached_node(p_from, node_target));
|
||||
ERR_FAIL_COND_V(!spawner, ERR_DOES_NOT_EXIST);
|
||||
ERR_FAIL_COND_V(p_from != spawner->get_multiplayer_authority(), ERR_UNAUTHORIZED);
|
||||
|
||||
uint32_t net_id = decode_uint32(&p_buffer[ofs]);
|
||||
ofs += 4;
|
||||
uint32_t name_len = decode_uint32(&p_buffer[ofs]);
|
||||
ofs += 4;
|
||||
ERR_FAIL_COND_V_MSG(name_len > uint32_t(p_buffer_len - ofs), ERR_INVALID_DATA, vformat("Invalid spawn packet size: %d, wants: %d", p_buffer_len, ofs + name_len));
|
||||
ERR_FAIL_COND_V_MSG(name_len < 1, ERR_INVALID_DATA, "Zero spawn name size.");
|
||||
|
||||
// We need to make sure no trickery happens here, but we want to allow autogenerated ("@") node names.
|
||||
const String name = String::utf8((const char *)&p_buffer[ofs], name_len);
|
||||
ERR_FAIL_COND_V_MSG(name.validate_node_name() != name, ERR_INVALID_DATA, vformat("Invalid node name received: '%s'. Make sure to add nodes via 'add_child(node, true)' remotely.", name));
|
||||
ofs += name_len;
|
||||
|
||||
// Check that we can spawn.
|
||||
Node *parent = spawner->get_node_or_null(spawner->get_spawn_path());
|
||||
ERR_FAIL_COND_V(!parent, ERR_UNCONFIGURED);
|
||||
ERR_FAIL_COND_V(parent->has_node(name), ERR_INVALID_DATA);
|
||||
|
||||
Node *node = nullptr;
|
||||
if (scene_id == MultiplayerSpawner::INVALID_ID) {
|
||||
// Custom spawn.
|
||||
ERR_FAIL_COND_V(p_buffer_len - ofs < 4, ERR_INVALID_DATA);
|
||||
uint32_t arg_size = decode_uint32(&p_buffer[ofs]);
|
||||
ofs += 4;
|
||||
ERR_FAIL_COND_V(arg_size > uint32_t(p_buffer_len - ofs), ERR_INVALID_DATA);
|
||||
Variant v;
|
||||
Error err = MultiplayerAPI::decode_and_decompress_variant(v, &p_buffer[ofs], arg_size, nullptr, false);
|
||||
ERR_FAIL_COND_V(err != OK, err);
|
||||
ofs += arg_size;
|
||||
node = spawner->instantiate_custom(v);
|
||||
} else {
|
||||
// Scene based spawn.
|
||||
node = spawner->instantiate_scene(scene_id);
|
||||
}
|
||||
ERR_FAIL_COND_V(!node, ERR_UNAUTHORIZED);
|
||||
node->set_name(name);
|
||||
rep_state->peer_add_remote(p_from, net_id, node, spawner);
|
||||
// The initial state will be applied during the sync config (i.e. before _ready).
|
||||
int state_len = p_buffer_len - ofs;
|
||||
if (state_len) {
|
||||
pending_spawn = node->get_instance_id();
|
||||
pending_buffer = &p_buffer[ofs];
|
||||
pending_buffer_size = state_len;
|
||||
}
|
||||
parent->add_child(node);
|
||||
pending_spawn = ObjectID();
|
||||
pending_buffer = nullptr;
|
||||
pending_buffer_size = 0;
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error SceneReplicationInterface::on_despawn_receive(int p_from, const uint8_t *p_buffer, int p_buffer_len) {
|
||||
ERR_FAIL_COND_V_MSG(p_buffer_len < 5, ERR_INVALID_DATA, "Invalid spawn packet received");
|
||||
int ofs = 1; // The spawn/despawn command.
|
||||
uint32_t net_id = decode_uint32(&p_buffer[ofs]);
|
||||
ofs += 4;
|
||||
Node *node = nullptr;
|
||||
Error err = rep_state->peer_del_remote(p_from, net_id, &node);
|
||||
ERR_FAIL_COND_V(err != OK, err);
|
||||
ERR_FAIL_COND_V(!node, ERR_BUG);
|
||||
node->queue_delete();
|
||||
return OK;
|
||||
}
|
||||
|
||||
void SceneReplicationInterface::_send_sync(int p_peer, uint64_t p_msec) {
|
||||
const Set<ObjectID> &known = rep_state->get_known_nodes(p_peer);
|
||||
if (known.is_empty()) {
|
||||
return;
|
||||
}
|
||||
MAKE_ROOM(sync_mtu);
|
||||
uint8_t *ptr = packet_cache.ptrw();
|
||||
ptr[0] = MultiplayerAPI::NETWORK_COMMAND_SYNC;
|
||||
int ofs = 1;
|
||||
ofs += encode_uint16(rep_state->peer_sync_next(p_peer), &ptr[1]);
|
||||
// Can only send updates for already notified nodes.
|
||||
// This is a lazy implementation, we could optimize much more here with by grouping by replication config.
|
||||
for (const ObjectID &oid : known) {
|
||||
if (!rep_state->update_sync_time(oid, p_msec)) {
|
||||
continue; // nothing to sync.
|
||||
}
|
||||
MultiplayerSynchronizer *sync = rep_state->get_synchronizer(oid);
|
||||
ERR_CONTINUE(!sync);
|
||||
Node *node = rep_state->get_node(oid);
|
||||
ERR_CONTINUE(!node);
|
||||
int size;
|
||||
Vector<Variant> vars;
|
||||
Vector<const Variant *> varp;
|
||||
const List<NodePath> props = sync->get_replication_config()->get_sync_properties();
|
||||
Error err = MultiplayerSynchronizer::get_state(props, node, vars, varp);
|
||||
ERR_CONTINUE_MSG(err != OK, "Unable to retrieve sync state.");
|
||||
err = MultiplayerAPI::encode_and_compress_variants(varp.ptrw(), varp.size(), nullptr, size);
|
||||
ERR_CONTINUE_MSG(err != OK, "Unable to encode sync state.");
|
||||
// TODO Handle single state above MTU.
|
||||
ERR_CONTINUE_MSG(size > 3 + 4 + 4 + sync_mtu, vformat("Node states bigger then MTU will not be sent (%d > %d): %s", size, sync_mtu, node->get_path()));
|
||||
if (ofs + 4 + 4 + size > sync_mtu) {
|
||||
// Send what we got, and reset write.
|
||||
_send_raw(packet_cache.ptr(), ofs, p_peer, false);
|
||||
ofs = 3;
|
||||
}
|
||||
if (size) {
|
||||
uint32_t net_id = rep_state->get_net_id(oid);
|
||||
if (net_id == 0) {
|
||||
// First time path based ID.
|
||||
const Node *root_node = multiplayer->get_root_node();
|
||||
ERR_FAIL_COND(!root_node);
|
||||
NodePath rel_path = (root_node->get_path()).rel_path_to(sync->get_path());
|
||||
int path_id = 0;
|
||||
multiplayer->send_confirm_path(sync, rel_path, p_peer, path_id);
|
||||
net_id = path_id;
|
||||
rep_state->set_net_id(oid, net_id | 0x80000000);
|
||||
}
|
||||
ofs += encode_uint32(rep_state->get_net_id(oid), &ptr[ofs]);
|
||||
ofs += encode_uint32(size, &ptr[ofs]);
|
||||
MultiplayerAPI::encode_and_compress_variants(varp.ptrw(), varp.size(), &ptr[ofs], size);
|
||||
ofs += size;
|
||||
}
|
||||
}
|
||||
if (ofs > 3) {
|
||||
// Got some left over to send.
|
||||
_send_raw(packet_cache.ptr(), ofs, p_peer, false);
|
||||
}
|
||||
}
|
||||
|
||||
Error SceneReplicationInterface::on_sync_receive(int p_from, const uint8_t *p_buffer, int p_buffer_len) {
|
||||
ERR_FAIL_COND_V_MSG(p_buffer_len < 11, ERR_INVALID_DATA, "Invalid sync packet received");
|
||||
uint16_t time = decode_uint16(&p_buffer[1]);
|
||||
int ofs = 3;
|
||||
rep_state->peer_sync_recv(p_from, time);
|
||||
while (ofs + 8 < p_buffer_len) {
|
||||
uint32_t net_id = decode_uint32(&p_buffer[ofs]);
|
||||
ofs += 4;
|
||||
uint32_t size = decode_uint32(&p_buffer[ofs]);
|
||||
ofs += 4;
|
||||
Node *node = nullptr;
|
||||
if (net_id & 0x80000000) {
|
||||
MultiplayerSynchronizer *sync = Object::cast_to<MultiplayerSynchronizer>(multiplayer->get_cached_node(p_from, net_id & 0x7FFFFFFF));
|
||||
ERR_FAIL_COND_V(!sync || sync->get_multiplayer_authority() != p_from, ERR_UNAUTHORIZED);
|
||||
node = sync->get_node(sync->get_root_path());
|
||||
} else {
|
||||
node = rep_state->peer_get_remote(p_from, net_id);
|
||||
}
|
||||
if (!node) {
|
||||
// Not received yet.
|
||||
ofs += size;
|
||||
continue;
|
||||
}
|
||||
const ObjectID oid = node->get_instance_id();
|
||||
if (!rep_state->update_last_node_sync(oid, time)) {
|
||||
// State is too old.
|
||||
ofs += size;
|
||||
continue;
|
||||
}
|
||||
MultiplayerSynchronizer *sync = rep_state->get_synchronizer(oid);
|
||||
ERR_FAIL_COND_V(!sync, ERR_BUG);
|
||||
ERR_FAIL_COND_V(size > uint32_t(p_buffer_len - ofs), ERR_BUG);
|
||||
const List<NodePath> props = sync->get_replication_config()->get_sync_properties();
|
||||
Vector<Variant> vars;
|
||||
vars.resize(props.size());
|
||||
int consumed;
|
||||
Error err = MultiplayerAPI::decode_and_decompress_variants(vars, &p_buffer[ofs], size, consumed);
|
||||
ERR_FAIL_COND_V(err, err);
|
||||
err = MultiplayerSynchronizer::set_state(props, node, vars);
|
||||
ERR_FAIL_COND_V(err, err);
|
||||
ofs += size;
|
||||
}
|
||||
return OK;
|
||||
}
|
84
scene/multiplayer/scene_replication_interface.h
Normal file
84
scene/multiplayer/scene_replication_interface.h
Normal file
@ -0,0 +1,84 @@
|
||||
/*************************************************************************/
|
||||
/* scene_replication_interface.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 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 SCENE_TREE_REPLICATOR_INTERFACE_H
|
||||
#define SCENE_TREE_REPLICATOR_INTERFACE_H
|
||||
|
||||
#include "core/multiplayer/multiplayer_api.h"
|
||||
|
||||
#include "scene/multiplayer/scene_replication_state.h"
|
||||
|
||||
class SceneReplicationInterface : public MultiplayerReplicationInterface {
|
||||
GDCLASS(SceneReplicationInterface, MultiplayerReplicationInterface);
|
||||
|
||||
private:
|
||||
void _send_sync(int p_peer, uint64_t p_msec);
|
||||
Error _send_spawn(Node *p_node, MultiplayerSpawner *p_spawner, int p_peer);
|
||||
Error _send_despawn(Node *p_node, int p_peer);
|
||||
Error _send_raw(const uint8_t *p_buffer, int p_size, int p_peer, bool p_reliable);
|
||||
|
||||
void _free_remotes(int p_peer);
|
||||
|
||||
Ref<SceneReplicationState> rep_state;
|
||||
MultiplayerAPI *multiplayer;
|
||||
PackedByteArray packet_cache;
|
||||
int sync_mtu = 1350; // Highly dependent on underlying protocol.
|
||||
|
||||
// An hack to apply the initial state before ready.
|
||||
ObjectID pending_spawn;
|
||||
const uint8_t *pending_buffer = nullptr;
|
||||
int pending_buffer_size = 0;
|
||||
|
||||
protected:
|
||||
static MultiplayerReplicationInterface *_create(MultiplayerAPI *p_multiplayer);
|
||||
|
||||
public:
|
||||
static void make_default();
|
||||
|
||||
virtual void on_reset() override;
|
||||
virtual void on_peer_change(int p_id, bool p_connected) override;
|
||||
|
||||
virtual Error on_spawn(Object *p_obj, Variant p_config) override;
|
||||
virtual Error on_despawn(Object *p_obj, Variant p_config) override;
|
||||
virtual Error on_replication_start(Object *p_obj, Variant p_config) override;
|
||||
virtual Error on_replication_stop(Object *p_obj, Variant p_config) override;
|
||||
virtual void on_network_process() override;
|
||||
|
||||
virtual Error on_spawn_receive(int p_from, const uint8_t *p_buffer, int p_buffer_len) override;
|
||||
virtual Error on_despawn_receive(int p_from, const uint8_t *p_buffer, int p_buffer_len) override;
|
||||
virtual Error on_sync_receive(int p_from, const uint8_t *p_buffer, int p_buffer_len) override;
|
||||
|
||||
SceneReplicationInterface(MultiplayerAPI *p_multiplayer) {
|
||||
rep_state.instantiate();
|
||||
multiplayer = p_multiplayer;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // SCENE_TREE_REPLICATOR_INTERFACE_H
|
258
scene/multiplayer/scene_replication_state.cpp
Normal file
258
scene/multiplayer/scene_replication_state.cpp
Normal file
@ -0,0 +1,258 @@
|
||||
/*************************************************************************/
|
||||
/* scene_replication_state.cpp */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 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 "scene/multiplayer/scene_replication_state.h"
|
||||
|
||||
#include "core/multiplayer/multiplayer_api.h"
|
||||
#include "scene/multiplayer/multiplayer_spawner.h"
|
||||
#include "scene/multiplayer/multiplayer_synchronizer.h"
|
||||
#include "scene/scene_string_names.h"
|
||||
|
||||
SceneReplicationState::TrackedNode &SceneReplicationState::_track(const ObjectID &p_id) {
|
||||
if (!tracked_nodes.has(p_id)) {
|
||||
tracked_nodes[p_id] = TrackedNode(p_id);
|
||||
Node *node = Object::cast_to<Node>(ObjectDB::get_instance(p_id));
|
||||
node->connect(SceneStringNames::get_singleton()->tree_exited, callable_mp(this, &SceneReplicationState::_untrack), varray(p_id), Node::CONNECT_ONESHOT);
|
||||
}
|
||||
return tracked_nodes[p_id];
|
||||
}
|
||||
|
||||
void SceneReplicationState::_untrack(const ObjectID &p_id) {
|
||||
if (tracked_nodes.has(p_id)) {
|
||||
uint32_t net_id = tracked_nodes[p_id].net_id;
|
||||
uint32_t peer = tracked_nodes[p_id].remote_peer;
|
||||
tracked_nodes.erase(p_id);
|
||||
// If it was spawned by a remote, remove it from the received nodes.
|
||||
if (peer && peers_info.has(peer)) {
|
||||
peers_info[peer].recv_nodes.erase(net_id);
|
||||
}
|
||||
// If we spawned or synced it, we need to remove it from any peer it was sent to.
|
||||
if (net_id || peer == 0) {
|
||||
const int *k = nullptr;
|
||||
while ((k = peers_info.next(k))) {
|
||||
peers_info.get(*k).known_nodes.erase(p_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const HashMap<uint32_t, ObjectID> SceneReplicationState::peer_get_remotes(int p_peer) const {
|
||||
return peers_info.has(p_peer) ? peers_info[p_peer].recv_nodes : HashMap<uint32_t, ObjectID>();
|
||||
}
|
||||
|
||||
bool SceneReplicationState::update_last_node_sync(const ObjectID &p_id, uint16_t p_time) {
|
||||
TrackedNode *tnode = tracked_nodes.getptr(p_id);
|
||||
ERR_FAIL_COND_V(!tnode, false);
|
||||
if (p_time <= tnode->last_sync && tnode->last_sync - p_time < 32767) {
|
||||
return false;
|
||||
}
|
||||
tnode->last_sync = p_time;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SceneReplicationState::update_sync_time(const ObjectID &p_id, uint64_t p_msec) {
|
||||
TrackedNode *tnode = tracked_nodes.getptr(p_id);
|
||||
ERR_FAIL_COND_V(!tnode, false);
|
||||
MultiplayerSynchronizer *sync = get_synchronizer(p_id);
|
||||
if (!sync) {
|
||||
return false;
|
||||
}
|
||||
if (tnode->last_sync_msec == p_msec) {
|
||||
return true;
|
||||
}
|
||||
if (p_msec >= tnode->last_sync_msec + sync->get_replication_interval_msec()) {
|
||||
tnode->last_sync_msec = p_msec;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const Set<ObjectID> SceneReplicationState::get_known_nodes(int p_peer) {
|
||||
ERR_FAIL_COND_V(!peers_info.has(p_peer), Set<ObjectID>());
|
||||
return peers_info[p_peer].known_nodes;
|
||||
}
|
||||
|
||||
uint32_t SceneReplicationState::get_net_id(const ObjectID &p_id) const {
|
||||
const TrackedNode *tnode = tracked_nodes.getptr(p_id);
|
||||
ERR_FAIL_COND_V(!tnode, 0);
|
||||
return tnode->net_id;
|
||||
}
|
||||
|
||||
void SceneReplicationState::set_net_id(const ObjectID &p_id, uint32_t p_net_id) {
|
||||
TrackedNode *tnode = tracked_nodes.getptr(p_id);
|
||||
ERR_FAIL_COND(!tnode);
|
||||
tnode->net_id = p_net_id;
|
||||
}
|
||||
|
||||
uint32_t SceneReplicationState::ensure_net_id(const ObjectID &p_id) {
|
||||
TrackedNode *tnode = tracked_nodes.getptr(p_id);
|
||||
ERR_FAIL_COND_V(!tnode, 0);
|
||||
if (tnode->net_id == 0) {
|
||||
tnode->net_id = ++last_net_id;
|
||||
}
|
||||
return tnode->net_id;
|
||||
}
|
||||
|
||||
void SceneReplicationState::on_peer_change(int p_peer, bool p_connected) {
|
||||
if (p_connected) {
|
||||
peers_info[p_peer] = PeerInfo();
|
||||
known_peers.insert(p_peer);
|
||||
} else {
|
||||
peers_info.erase(p_peer);
|
||||
known_peers.erase(p_peer);
|
||||
}
|
||||
}
|
||||
|
||||
void SceneReplicationState::reset() {
|
||||
peers_info.clear();
|
||||
known_peers.clear();
|
||||
// Tracked nodes are cleared on deletion, here we only reset the ids so they can be later re-assigned.
|
||||
const ObjectID *oid = nullptr;
|
||||
while ((oid = tracked_nodes.next(oid))) {
|
||||
TrackedNode &tobj = tracked_nodes[*oid];
|
||||
tobj.net_id = 0;
|
||||
tobj.remote_peer = 0;
|
||||
tobj.last_sync = 0;
|
||||
}
|
||||
}
|
||||
|
||||
Error SceneReplicationState::config_add_spawn(Node *p_node, MultiplayerSpawner *p_spawner) {
|
||||
const ObjectID oid = p_node->get_instance_id();
|
||||
TrackedNode &tobj = _track(oid);
|
||||
ERR_FAIL_COND_V(tobj.spawner != ObjectID(), ERR_ALREADY_IN_USE);
|
||||
tobj.spawner = p_spawner->get_instance_id();
|
||||
spawned_nodes.insert(oid);
|
||||
// The spawner may be notified after the synchronizer.
|
||||
path_only_nodes.erase(oid);
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error SceneReplicationState::config_del_spawn(Node *p_node, MultiplayerSpawner *p_spawner) {
|
||||
const ObjectID oid = p_node->get_instance_id();
|
||||
ERR_FAIL_COND_V(!is_tracked(oid), ERR_INVALID_PARAMETER);
|
||||
TrackedNode &tobj = _track(oid);
|
||||
ERR_FAIL_COND_V(tobj.spawner != p_spawner->get_instance_id(), ERR_INVALID_PARAMETER);
|
||||
tobj.spawner = ObjectID();
|
||||
spawned_nodes.erase(oid);
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error SceneReplicationState::config_add_sync(Node *p_node, MultiplayerSynchronizer *p_sync) {
|
||||
const ObjectID oid = p_node->get_instance_id();
|
||||
TrackedNode &tobj = _track(oid);
|
||||
ERR_FAIL_COND_V(tobj.synchronizer != ObjectID(), ERR_ALREADY_IN_USE);
|
||||
tobj.synchronizer = p_sync->get_instance_id();
|
||||
// If it doesn't have a spawner, we might need to assign ID for this node using it's path.
|
||||
if (tobj.spawner.is_null()) {
|
||||
path_only_nodes.insert(oid);
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error SceneReplicationState::config_del_sync(Node *p_node, MultiplayerSynchronizer *p_sync) {
|
||||
const ObjectID oid = p_node->get_instance_id();
|
||||
ERR_FAIL_COND_V(!is_tracked(oid), ERR_INVALID_PARAMETER);
|
||||
TrackedNode &tobj = _track(oid);
|
||||
ERR_FAIL_COND_V(tobj.synchronizer != p_sync->get_instance_id(), ERR_INVALID_PARAMETER);
|
||||
tobj.synchronizer = ObjectID();
|
||||
if (path_only_nodes.has(oid)) {
|
||||
p_node->disconnect(SceneStringNames::get_singleton()->tree_exited, callable_mp(this, &SceneReplicationState::_untrack));
|
||||
_untrack(oid);
|
||||
path_only_nodes.erase(oid);
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error SceneReplicationState::peer_add_node(int p_peer, const ObjectID &p_id) {
|
||||
if (p_peer) {
|
||||
ERR_FAIL_COND_V(!peers_info.has(p_peer), ERR_INVALID_PARAMETER);
|
||||
peers_info[p_peer].known_nodes.insert(p_id);
|
||||
} else {
|
||||
const int *pid = nullptr;
|
||||
while ((pid = peers_info.next(pid))) {
|
||||
peers_info.get(*pid).known_nodes.insert(p_id);
|
||||
}
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error SceneReplicationState::peer_del_node(int p_peer, const ObjectID &p_id) {
|
||||
if (p_peer) {
|
||||
ERR_FAIL_COND_V(!peers_info.has(p_peer), ERR_INVALID_PARAMETER);
|
||||
peers_info[p_peer].known_nodes.erase(p_id);
|
||||
} else {
|
||||
const int *pid = nullptr;
|
||||
while ((pid = peers_info.next(pid))) {
|
||||
peers_info.get(*pid).known_nodes.erase(p_id);
|
||||
}
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
Node *SceneReplicationState::peer_get_remote(int p_peer, uint32_t p_net_id) {
|
||||
PeerInfo *info = peers_info.getptr(p_peer);
|
||||
return info && info->recv_nodes.has(p_net_id) ? Object::cast_to<Node>(ObjectDB::get_instance(info->recv_nodes[p_net_id])) : nullptr;
|
||||
}
|
||||
|
||||
Error SceneReplicationState::peer_add_remote(int p_peer, uint32_t p_net_id, Node *p_node, MultiplayerSpawner *p_spawner) {
|
||||
ERR_FAIL_COND_V(!p_node || !p_spawner, ERR_INVALID_PARAMETER);
|
||||
ERR_FAIL_COND_V(!peers_info.has(p_peer), ERR_UNAVAILABLE);
|
||||
PeerInfo &pinfo = peers_info[p_peer];
|
||||
ObjectID oid = p_node->get_instance_id();
|
||||
TrackedNode &tobj = _track(oid);
|
||||
tobj.spawner = p_spawner->get_instance_id();
|
||||
tobj.net_id = p_net_id;
|
||||
tobj.remote_peer = p_peer;
|
||||
tobj.last_sync = pinfo.last_recv_sync;
|
||||
// Also track as a remote.
|
||||
ERR_FAIL_COND_V(pinfo.recv_nodes.has(p_net_id), ERR_ALREADY_IN_USE);
|
||||
pinfo.recv_nodes[p_net_id] = oid;
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error SceneReplicationState::peer_del_remote(int p_peer, uint32_t p_net_id, Node **r_node) {
|
||||
ERR_FAIL_COND_V(!peers_info.has(p_peer), ERR_UNAUTHORIZED);
|
||||
PeerInfo &info = peers_info[p_peer];
|
||||
ERR_FAIL_COND_V(!info.recv_nodes.has(p_net_id), ERR_UNAUTHORIZED);
|
||||
*r_node = Object::cast_to<Node>(ObjectDB::get_instance(info.recv_nodes[p_net_id]));
|
||||
info.recv_nodes.erase(p_net_id);
|
||||
return OK;
|
||||
}
|
||||
|
||||
uint16_t SceneReplicationState::peer_sync_next(int p_peer) {
|
||||
ERR_FAIL_COND_V(!peers_info.has(p_peer), 0);
|
||||
PeerInfo &info = peers_info[p_peer];
|
||||
return ++info.last_sent_sync;
|
||||
}
|
||||
|
||||
void SceneReplicationState::peer_sync_recv(int p_peer, uint16_t p_time) {
|
||||
ERR_FAIL_COND(!peers_info.has(p_peer));
|
||||
peers_info[p_peer].last_recv_sync = p_time;
|
||||
}
|
121
scene/multiplayer/scene_replication_state.h
Normal file
121
scene/multiplayer/scene_replication_state.h
Normal file
@ -0,0 +1,121 @@
|
||||
/*************************************************************************/
|
||||
/* scene_replication_state.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 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 SCENE_REPLICATON_STATE_H
|
||||
#define SCENE_REPLICATON_STATE_H
|
||||
|
||||
#include "core/object/ref_counted.h"
|
||||
|
||||
class MultiplayerSpawner;
|
||||
class MultiplayerSynchronizer;
|
||||
class Node;
|
||||
|
||||
class SceneReplicationState : public RefCounted {
|
||||
private:
|
||||
struct TrackedNode {
|
||||
ObjectID id;
|
||||
uint32_t net_id = 0;
|
||||
uint32_t remote_peer = 0;
|
||||
ObjectID spawner;
|
||||
ObjectID synchronizer;
|
||||
uint16_t last_sync = 0;
|
||||
uint64_t last_sync_msec = 0;
|
||||
|
||||
bool operator==(const ObjectID &p_other) { return id == p_other; }
|
||||
|
||||
Node *get_node() const { return id.is_valid() ? Object::cast_to<Node>(ObjectDB::get_instance(id)) : nullptr; }
|
||||
MultiplayerSpawner *get_spawner() const { return spawner.is_valid() ? Object::cast_to<MultiplayerSpawner>(ObjectDB::get_instance(spawner)) : nullptr; }
|
||||
MultiplayerSynchronizer *get_synchronizer() const { return synchronizer.is_valid() ? Object::cast_to<MultiplayerSynchronizer>(ObjectDB::get_instance(synchronizer)) : nullptr; }
|
||||
TrackedNode() {}
|
||||
TrackedNode(const ObjectID &p_id) { id = p_id; }
|
||||
TrackedNode(const ObjectID &p_id, uint32_t p_net_id) {
|
||||
id = p_id;
|
||||
net_id = p_net_id;
|
||||
}
|
||||
};
|
||||
|
||||
struct PeerInfo {
|
||||
Set<ObjectID> known_nodes;
|
||||
HashMap<uint32_t, ObjectID> recv_nodes;
|
||||
uint16_t last_sent_sync = 0;
|
||||
uint16_t last_recv_sync = 0;
|
||||
};
|
||||
|
||||
Set<int> known_peers;
|
||||
uint32_t last_net_id = 0;
|
||||
HashMap<ObjectID, TrackedNode> tracked_nodes;
|
||||
HashMap<int, PeerInfo> peers_info;
|
||||
Set<ObjectID> spawned_nodes;
|
||||
Set<ObjectID> path_only_nodes;
|
||||
|
||||
TrackedNode &_track(const ObjectID &p_id);
|
||||
void _untrack(const ObjectID &p_id);
|
||||
bool is_tracked(const ObjectID &p_id) const { return tracked_nodes.has(p_id); }
|
||||
|
||||
public:
|
||||
const Set<int> get_peers() const { return known_peers; }
|
||||
const Set<ObjectID> get_spawned_nodes() const { return spawned_nodes; }
|
||||
const Set<ObjectID> get_path_only_nodes() const { return path_only_nodes; }
|
||||
|
||||
MultiplayerSynchronizer *get_synchronizer(const ObjectID &p_id) { return tracked_nodes.has(p_id) ? tracked_nodes[p_id].get_synchronizer() : nullptr; }
|
||||
MultiplayerSpawner *get_spawner(const ObjectID &p_id) { return tracked_nodes.has(p_id) ? tracked_nodes[p_id].get_spawner() : nullptr; }
|
||||
Node *get_node(const ObjectID &p_id) { return tracked_nodes.has(p_id) ? tracked_nodes[p_id].get_node() : nullptr; }
|
||||
bool update_last_node_sync(const ObjectID &p_id, uint16_t p_time);
|
||||
bool update_sync_time(const ObjectID &p_id, uint64_t p_msec);
|
||||
|
||||
const Set<ObjectID> get_known_nodes(int p_peer);
|
||||
uint32_t get_net_id(const ObjectID &p_id) const;
|
||||
void set_net_id(const ObjectID &p_id, uint32_t p_net_id);
|
||||
uint32_t ensure_net_id(const ObjectID &p_id);
|
||||
|
||||
void reset();
|
||||
void on_peer_change(int p_peer, bool p_connected);
|
||||
|
||||
Error config_add_spawn(Node *p_node, MultiplayerSpawner *p_spawner);
|
||||
Error config_del_spawn(Node *p_node, MultiplayerSpawner *p_spawner);
|
||||
|
||||
Error config_add_sync(Node *p_node, MultiplayerSynchronizer *p_sync);
|
||||
Error config_del_sync(Node *p_node, MultiplayerSynchronizer *p_sync);
|
||||
|
||||
Error peer_add_node(int p_peer, const ObjectID &p_id);
|
||||
Error peer_del_node(int p_peer, const ObjectID &p_id);
|
||||
|
||||
const HashMap<uint32_t, ObjectID> peer_get_remotes(int p_peer) const;
|
||||
Node *peer_get_remote(int p_peer, uint32_t p_net_id);
|
||||
Error peer_add_remote(int p_peer, uint32_t p_net_id, Node *p_node, MultiplayerSpawner *p_spawner);
|
||||
Error peer_del_remote(int p_peer, uint32_t p_net_id, Node **r_node);
|
||||
|
||||
uint16_t peer_sync_next(int p_peer);
|
||||
void peer_sync_recv(int p_peer, uint16_t p_time);
|
||||
|
||||
SceneReplicationState() {}
|
||||
};
|
||||
|
||||
#endif // SCENE_REPLICATON_STATE_H
|
@ -134,6 +134,9 @@
|
||||
#include "scene/main/timer.h"
|
||||
#include "scene/main/viewport.h"
|
||||
#include "scene/main/window.h"
|
||||
#include "scene/multiplayer/multiplayer_spawner.h"
|
||||
#include "scene/multiplayer/multiplayer_synchronizer.h"
|
||||
#include "scene/multiplayer/scene_replication_interface.h"
|
||||
#include "scene/resources/audio_stream_sample.h"
|
||||
#include "scene/resources/bit_map.h"
|
||||
#include "scene/resources/box_shape_3d.h"
|
||||
@ -301,6 +304,8 @@ void register_scene_types() {
|
||||
GDREGISTER_CLASS(SubViewport);
|
||||
GDREGISTER_CLASS(ViewportTexture);
|
||||
GDREGISTER_CLASS(HTTPRequest);
|
||||
GDREGISTER_CLASS(MultiplayerSpawner);
|
||||
GDREGISTER_CLASS(MultiplayerSynchronizer);
|
||||
GDREGISTER_CLASS(Timer);
|
||||
GDREGISTER_CLASS(CanvasLayer);
|
||||
GDREGISTER_CLASS(CanvasModulate);
|
||||
@ -822,6 +827,8 @@ void register_scene_types() {
|
||||
GDREGISTER_CLASS(Font);
|
||||
GDREGISTER_CLASS(Curve);
|
||||
|
||||
GDREGISTER_CLASS(SceneReplicationConfig);
|
||||
|
||||
GDREGISTER_CLASS(TextLine);
|
||||
GDREGISTER_CLASS(TextParagraph);
|
||||
|
||||
@ -1050,6 +1057,7 @@ void register_scene_types() {
|
||||
}
|
||||
|
||||
SceneDebugger::initialize();
|
||||
SceneReplicationInterface::make_default();
|
||||
|
||||
NativeExtensionManager::get_singleton()->initialize_extensions(NativeExtension::INITIALIZATION_LEVEL_SCENE);
|
||||
}
|
||||
|
187
scene/resources/scene_replication_config.cpp
Normal file
187
scene/resources/scene_replication_config.cpp
Normal file
@ -0,0 +1,187 @@
|
||||
/*************************************************************************/
|
||||
/* scene_replication_config.cpp */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 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 "scene_replication_config.h"
|
||||
|
||||
#include "core/multiplayer/multiplayer_api.h"
|
||||
#include "scene/main/node.h"
|
||||
|
||||
bool SceneReplicationConfig::_set(const StringName &p_name, const Variant &p_value) {
|
||||
String name = p_name;
|
||||
|
||||
if (name.begins_with("properties/")) {
|
||||
int idx = name.get_slicec('/', 1).to_int();
|
||||
String what = name.get_slicec('/', 2);
|
||||
|
||||
if (properties.size() == idx && what == "path") {
|
||||
ERR_FAIL_COND_V(p_value.get_type() != Variant::NODE_PATH, false);
|
||||
NodePath path = p_value;
|
||||
ERR_FAIL_COND_V(path.is_empty() || path.get_subname_count() == 0, false);
|
||||
add_property(path);
|
||||
return true;
|
||||
}
|
||||
ERR_FAIL_COND_V(p_value.get_type() != Variant::BOOL, false);
|
||||
ERR_FAIL_INDEX_V(idx, properties.size(), false);
|
||||
ReplicationProperty &prop = properties[idx];
|
||||
if (what == "sync") {
|
||||
prop.sync = p_value;
|
||||
sync_props.push_back(prop.name);
|
||||
return true;
|
||||
} else if (what == "spawn") {
|
||||
prop.spawn = p_value;
|
||||
spawn_props.push_back(prop.name);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SceneReplicationConfig::_get(const StringName &p_name, Variant &r_ret) const {
|
||||
String name = p_name;
|
||||
|
||||
if (name.begins_with("properties/")) {
|
||||
int idx = name.get_slicec('/', 1).to_int();
|
||||
String what = name.get_slicec('/', 2);
|
||||
ERR_FAIL_INDEX_V(idx, properties.size(), false);
|
||||
const ReplicationProperty &prop = properties[idx];
|
||||
if (what == "path") {
|
||||
r_ret = prop.name;
|
||||
return true;
|
||||
} else if (what == "sync") {
|
||||
r_ret = prop.sync;
|
||||
return true;
|
||||
} else if (what == "spawn") {
|
||||
r_ret = prop.spawn;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void SceneReplicationConfig::_get_property_list(List<PropertyInfo> *p_list) const {
|
||||
for (int i = 0; i < properties.size(); i++) {
|
||||
p_list->push_back(PropertyInfo(Variant::STRING, "properties/" + itos(i) + "/path", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL));
|
||||
p_list->push_back(PropertyInfo(Variant::STRING, "properties/" + itos(i) + "/spawn", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL));
|
||||
p_list->push_back(PropertyInfo(Variant::STRING, "properties/" + itos(i) + "/sync", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL));
|
||||
}
|
||||
}
|
||||
|
||||
TypedArray<NodePath> SceneReplicationConfig::get_properties() const {
|
||||
TypedArray<NodePath> paths;
|
||||
for (const ReplicationProperty &prop : properties) {
|
||||
paths.push_back(prop.name);
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
|
||||
void SceneReplicationConfig::add_property(const NodePath &p_path, int p_index) {
|
||||
ERR_FAIL_COND(properties.find(p_path));
|
||||
|
||||
if (p_index < 0 || p_index == properties.size()) {
|
||||
properties.push_back(ReplicationProperty(p_path));
|
||||
return;
|
||||
}
|
||||
|
||||
ERR_FAIL_INDEX(p_index, properties.size());
|
||||
|
||||
List<ReplicationProperty>::Element *I = properties.front();
|
||||
int c = 0;
|
||||
while (c < p_index) {
|
||||
I = I->next();
|
||||
c++;
|
||||
}
|
||||
properties.insert_before(I, ReplicationProperty(p_path));
|
||||
}
|
||||
|
||||
void SceneReplicationConfig::remove_property(const NodePath &p_path) {
|
||||
properties.erase(p_path);
|
||||
}
|
||||
|
||||
int SceneReplicationConfig::property_get_index(const NodePath &p_path) const {
|
||||
for (int i = 0; i < properties.size(); i++) {
|
||||
if (properties[i].name == p_path) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
ERR_FAIL_V(-1);
|
||||
}
|
||||
|
||||
bool SceneReplicationConfig::property_get_spawn(const NodePath &p_path) {
|
||||
List<ReplicationProperty>::Element *E = properties.find(p_path);
|
||||
ERR_FAIL_COND_V(!E, false);
|
||||
return E->get().spawn;
|
||||
}
|
||||
|
||||
void SceneReplicationConfig::property_set_spawn(const NodePath &p_path, bool p_enabled) {
|
||||
List<ReplicationProperty>::Element *E = properties.find(p_path);
|
||||
ERR_FAIL_COND(!E);
|
||||
if (E->get().spawn == p_enabled) {
|
||||
return;
|
||||
}
|
||||
E->get().spawn = p_enabled;
|
||||
spawn_props.clear();
|
||||
for (const ReplicationProperty &prop : properties) {
|
||||
if (prop.spawn) {
|
||||
spawn_props.push_back(p_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool SceneReplicationConfig::property_get_sync(const NodePath &p_path) {
|
||||
List<ReplicationProperty>::Element *E = properties.find(p_path);
|
||||
ERR_FAIL_COND_V(!E, false);
|
||||
return E->get().sync;
|
||||
}
|
||||
|
||||
void SceneReplicationConfig::property_set_sync(const NodePath &p_path, bool p_enabled) {
|
||||
List<ReplicationProperty>::Element *E = properties.find(p_path);
|
||||
ERR_FAIL_COND(!E);
|
||||
if (E->get().sync == p_enabled) {
|
||||
return;
|
||||
}
|
||||
E->get().sync = p_enabled;
|
||||
sync_props.clear();
|
||||
for (const ReplicationProperty &prop : properties) {
|
||||
if (prop.sync) {
|
||||
sync_props.push_back(p_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SceneReplicationConfig::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("get_properties"), &SceneReplicationConfig::get_properties);
|
||||
ClassDB::bind_method(D_METHOD("add_property", "path", "index"), &SceneReplicationConfig::add_property, DEFVAL(-1));
|
||||
ClassDB::bind_method(D_METHOD("remove_property", "path"), &SceneReplicationConfig::remove_property);
|
||||
ClassDB::bind_method(D_METHOD("property_get_index", "path"), &SceneReplicationConfig::property_get_index);
|
||||
ClassDB::bind_method(D_METHOD("property_get_spawn", "path"), &SceneReplicationConfig::property_get_spawn);
|
||||
ClassDB::bind_method(D_METHOD("property_set_spawn", "path", "enabled"), &SceneReplicationConfig::property_set_spawn);
|
||||
ClassDB::bind_method(D_METHOD("property_get_sync", "path"), &SceneReplicationConfig::property_get_sync);
|
||||
ClassDB::bind_method(D_METHOD("property_set_sync", "path", "enabled"), &SceneReplicationConfig::property_set_sync);
|
||||
}
|
90
scene/resources/scene_replication_config.h
Normal file
90
scene/resources/scene_replication_config.h
Normal file
@ -0,0 +1,90 @@
|
||||
/*************************************************************************/
|
||||
/* scene_replication_config.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 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 SCENE_REPLICATION_CONFIG_H
|
||||
#define SCENE_REPLICATION_CONFIG_H
|
||||
|
||||
#include "core/io/resource.h"
|
||||
|
||||
#include "core/variant/typed_array.h"
|
||||
|
||||
class SceneReplicationConfig : public Resource {
|
||||
GDCLASS(SceneReplicationConfig, Resource);
|
||||
OBJ_SAVE_TYPE(SceneReplicationConfig);
|
||||
RES_BASE_EXTENSION("repl");
|
||||
|
||||
private:
|
||||
struct ReplicationProperty {
|
||||
NodePath name;
|
||||
bool spawn = true;
|
||||
bool sync = true;
|
||||
|
||||
bool operator==(const ReplicationProperty &p_to) {
|
||||
return name == p_to.name;
|
||||
}
|
||||
|
||||
ReplicationProperty() {}
|
||||
|
||||
ReplicationProperty(const NodePath &p_name) {
|
||||
name = p_name;
|
||||
}
|
||||
};
|
||||
|
||||
List<ReplicationProperty> properties;
|
||||
List<NodePath> spawn_props;
|
||||
List<NodePath> sync_props;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
bool _set(const StringName &p_name, const Variant &p_value);
|
||||
bool _get(const StringName &p_name, Variant &r_ret) const;
|
||||
void _get_property_list(List<PropertyInfo> *p_list) const;
|
||||
|
||||
public:
|
||||
TypedArray<NodePath> get_properties() const;
|
||||
|
||||
void add_property(const NodePath &p_path, int p_index = -1);
|
||||
void remove_property(const NodePath &p_path);
|
||||
|
||||
int property_get_index(const NodePath &p_path) const;
|
||||
bool property_get_spawn(const NodePath &p_path);
|
||||
void property_set_spawn(const NodePath &p_path, bool p_enabled);
|
||||
|
||||
bool property_get_sync(const NodePath &p_path);
|
||||
void property_set_sync(const NodePath &p_path, bool p_enabled);
|
||||
|
||||
const List<NodePath> &get_spawn_properties() { return spawn_props; }
|
||||
const List<NodePath> &get_sync_properties() { return sync_props; }
|
||||
|
||||
SceneReplicationConfig() {}
|
||||
};
|
||||
|
||||
#endif // SCENE_REPLICATION_CONFIG_H
|
Loading…
Reference in New Issue
Block a user