summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/io/http_client_tcp.cpp26
-rw-r--r--core/io/http_client_tcp.h1
-rw-r--r--core/io/multiplayer_api.cpp22
-rw-r--r--core/io/multiplayer_api.h6
-rw-r--r--core/io/multiplayer_replicator.cpp301
-rw-r--r--core/io/multiplayer_replicator.h38
-rw-r--r--doc/classes/MultiplayerAPI.xml13
-rw-r--r--doc/classes/MultiplayerReplicator.xml54
-rw-r--r--doc/classes/Node.xml35
-rw-r--r--modules/gdnative/include/nativescript/godot_nativescript.h8
-rw-r--r--modules/gdscript/gdscript_parser.cpp46
-rw-r--r--modules/mono/csharp_script.cpp7
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/RPCAttributes.cs3
-rw-r--r--modules/mono/mono_gd/gd_mono_cache.cpp2
-rw-r--r--modules/mono/mono_gd/gd_mono_cache.h1
-rw-r--r--modules/visual_script/visual_script_nodes.cpp2
-rw-r--r--modules/websocket/wsl_client.cpp28
-rw-r--r--modules/websocket/wsl_client.h2
-rw-r--r--scene/gui/box_container.cpp4
-rw-r--r--scene/gui/color_picker.cpp16
-rw-r--r--scene/gui/dialogs.cpp6
-rw-r--r--scene/gui/file_dialog.cpp11
-rw-r--r--scene/gui/gradient_edit.cpp2
-rw-r--r--scene/gui/graph_edit.cpp5
-rw-r--r--scene/gui/item_list.cpp2
-rw-r--r--scene/gui/line_edit.cpp4
-rw-r--r--scene/gui/menu_button.cpp2
-rw-r--r--scene/gui/option_button.cpp2
-rw-r--r--scene/gui/popup.cpp2
-rw-r--r--scene/gui/popup_menu.cpp8
-rw-r--r--scene/gui/rich_text_label.cpp2
-rw-r--r--scene/gui/scroll_container.cpp7
-rw-r--r--scene/gui/spin_box.cpp4
-rw-r--r--scene/gui/tab_container.cpp2
-rw-r--r--scene/gui/text_edit.cpp16
-rw-r--r--scene/gui/tree.cpp11
-rw-r--r--scene/main/node.cpp155
-rw-r--r--scene/main/node.h30
38 files changed, 691 insertions, 195 deletions
diff --git a/core/io/http_client_tcp.cpp b/core/io/http_client_tcp.cpp
index f291086808..b3d35b3603 100644
--- a/core/io/http_client_tcp.cpp
+++ b/core/io/http_client_tcp.cpp
@@ -45,6 +45,8 @@ Error HTTPClientTCP::connect_to_host(const String &p_host, int p_port, bool p_ss
conn_port = p_port;
conn_host = p_host;
+ ip_candidates.clear();
+
ssl = p_ssl;
ssl_verify_host = p_verify_host;
@@ -234,6 +236,7 @@ void HTTPClientTCP::close() {
resolving = IP::RESOLVER_INVALID_ID;
}
+ ip_candidates.clear();
response_headers.clear();
response_str.clear();
body_size = -1;
@@ -256,10 +259,17 @@ Error HTTPClientTCP::poll() {
return OK; // Still resolving
case IP::RESOLVER_STATUS_DONE: {
- IPAddress host = IP::get_singleton()->get_resolve_item_address(resolving);
- Error err = tcp_connection->connect_to_host(host, conn_port);
+ ip_candidates = IP::get_singleton()->get_resolve_item_addresses(resolving);
IP::get_singleton()->erase_resolve_item(resolving);
resolving = IP::RESOLVER_INVALID_ID;
+
+ Error err = ERR_BUG; // Should be at least one entry.
+ while (ip_candidates.size() > 0) {
+ err = tcp_connection->connect_to_host(ip_candidates.front(), conn_port);
+ if (err == OK) {
+ break;
+ }
+ }
if (err) {
status = STATUS_CANT_CONNECT;
return err;
@@ -313,6 +323,7 @@ Error HTTPClientTCP::poll() {
if (ssl->get_status() == StreamPeerSSL::STATUS_CONNECTED) {
// Handshake has been successful
handshaking = false;
+ ip_candidates.clear();
status = STATUS_CONNECTED;
return OK;
} else if (ssl->get_status() != StreamPeerSSL::STATUS_HANDSHAKING) {
@@ -323,15 +334,24 @@ Error HTTPClientTCP::poll() {
}
// ... we will need to poll more for handshake to finish
} else {
+ ip_candidates.clear();
status = STATUS_CONNECTED;
}
return OK;
} break;
case StreamPeerTCP::STATUS_ERROR:
case StreamPeerTCP::STATUS_NONE: {
+ Error err = ERR_CANT_CONNECT;
+ while (ip_candidates.size() > 0) {
+ tcp_connection->disconnect_from_host();
+ err = tcp_connection->connect_to_host(ip_candidates.pop_front(), conn_port);
+ if (err == OK) {
+ return OK;
+ }
+ }
close();
status = STATUS_CANT_CONNECT;
- return ERR_CANT_CONNECT;
+ return err;
} break;
}
} break;
diff --git a/core/io/http_client_tcp.h b/core/io/http_client_tcp.h
index e178399fbe..170afb551c 100644
--- a/core/io/http_client_tcp.h
+++ b/core/io/http_client_tcp.h
@@ -37,6 +37,7 @@ class HTTPClientTCP : public HTTPClient {
private:
Status status = STATUS_DISCONNECTED;
IP::ResolverID resolving = IP::RESOLVER_INVALID_ID;
+ Array ip_candidates;
int conn_port = -1;
String conn_host;
bool ssl = false;
diff --git a/core/io/multiplayer_api.cpp b/core/io/multiplayer_api.cpp
index 0ce9a70921..c145225751 100644
--- a/core/io/multiplayer_api.cpp
+++ b/core/io/multiplayer_api.cpp
@@ -96,14 +96,11 @@ _FORCE_INLINE_ bool _can_call_mode(Node *p_node, MultiplayerAPI::RPCMode mode, i
case MultiplayerAPI::RPC_MODE_DISABLED: {
return false;
} break;
- case MultiplayerAPI::RPC_MODE_REMOTE: {
+ case MultiplayerAPI::RPC_MODE_ANY: {
return true;
} break;
- case MultiplayerAPI::RPC_MODE_MASTER: {
- return p_node->is_network_master();
- } break;
- case MultiplayerAPI::RPC_MODE_PUPPET: {
- return !p_node->is_network_master() && p_remote_id == p_node->get_network_master();
+ case MultiplayerAPI::RPC_MODE_AUTHORITY: {
+ return !p_node->is_network_authority() && p_remote_id == p_node->get_network_authority();
} break;
}
@@ -140,6 +137,9 @@ void MultiplayerAPI::poll() {
break; // It's also possible that a packet or RPC caused a disconnection, so also check here.
}
}
+ if (network_peer.is_valid() && network_peer->get_connection_status() == MultiplayerPeer::CONNECTION_CONNECTED) {
+ replicator->poll();
+ }
}
void MultiplayerAPI::clear() {
@@ -326,6 +326,9 @@ void MultiplayerAPI::_process_packet(int p_from, const uint8_t *p_packet, int p_
case NETWORK_COMMAND_DESPAWN: {
replicator->process_spawn_despawn(p_from, p_packet, p_packet_len, false);
} break;
+ case NETWORK_COMMAND_SYNC: {
+ replicator->process_sync(p_from, p_packet, p_packet_len);
+ } break;
}
}
@@ -363,7 +366,7 @@ void MultiplayerAPI::_process_rpc(Node *p_node, const uint16_t p_rpc_method_id,
ERR_FAIL_COND(config.name == StringName());
bool can_call = _can_call_mode(p_node, config.rpc_mode, p_from);
- ERR_FAIL_COND_MSG(!can_call, "RPC '" + String(config.name) + "' is not allowed on node " + p_node->get_path() + " from: " + itos(p_from) + ". Mode is " + itos((int)config.rpc_mode) + ", master is " + itos(p_node->get_network_master()) + ".");
+ ERR_FAIL_COND_MSG(!can_call, "RPC '" + String(config.name) + "' is not allowed on node " + p_node->get_path() + " from: " + itos(p_from) + ". Mode is " + itos((int)config.rpc_mode) + ", authority is " + itos(p_node->get_network_authority()) + ".");
int argc = 0;
bool byte_only = false;
@@ -1132,9 +1135,8 @@ void MultiplayerAPI::_bind_methods() {
ADD_SIGNAL(MethodInfo("server_disconnected"));
BIND_ENUM_CONSTANT(RPC_MODE_DISABLED);
- BIND_ENUM_CONSTANT(RPC_MODE_REMOTE);
- BIND_ENUM_CONSTANT(RPC_MODE_MASTER);
- BIND_ENUM_CONSTANT(RPC_MODE_PUPPET);
+ BIND_ENUM_CONSTANT(RPC_MODE_ANY);
+ BIND_ENUM_CONSTANT(RPC_MODE_AUTHORITY);
}
MultiplayerAPI::MultiplayerAPI() {
diff --git a/core/io/multiplayer_api.h b/core/io/multiplayer_api.h
index 5853541efa..3c96a3eed1 100644
--- a/core/io/multiplayer_api.h
+++ b/core/io/multiplayer_api.h
@@ -43,9 +43,8 @@ class MultiplayerAPI : public RefCounted {
public:
enum RPCMode {
RPC_MODE_DISABLED, // No rpc for this method, calls to this will be blocked (default)
- RPC_MODE_REMOTE, // Using rpc() on it will call method in all remote peers
- RPC_MODE_MASTER, // Using rpc() on it will call method on wherever the master is, be it local or remote
- RPC_MODE_PUPPET, // Using rpc() on it will call method for all puppets
+ RPC_MODE_ANY, // Any peer can call this rpc()
+ RPC_MODE_AUTHORITY, // Only the node's network authority (server by default) can call this rpc()
};
struct RPCConfig {
@@ -74,6 +73,7 @@ public:
NETWORK_COMMAND_RAW,
NETWORK_COMMAND_SPAWN,
NETWORK_COMMAND_DESPAWN,
+ NETWORK_COMMAND_SYNC, // This is the max we can have. We should optmize simplify/confirm, possibly spawn/despawn.
};
enum NetworkNodeIdCompression {
diff --git a/core/io/multiplayer_replicator.cpp b/core/io/multiplayer_replicator.cpp
index ba0fe32b58..1642aab136 100644
--- a/core/io/multiplayer_replicator.cpp
+++ b/core/io/multiplayer_replicator.cpp
@@ -38,6 +38,140 @@
if (packet_cache.size() < m_amount) \
packet_cache.resize(m_amount);
+Error MultiplayerReplicator::_sync_all_default(const ResourceUID::ID &p_scene_id, int p_peer) {
+ ERR_FAIL_COND_V(!replications.has(p_scene_id), ERR_INVALID_PARAMETER);
+ SceneConfig &cfg = replications[p_scene_id];
+ int full_size = 0;
+ bool same_size = true;
+ int last_size = 0;
+ bool all_raw = true;
+ struct EncodeInfo {
+ int size = 0;
+ bool raw = false;
+ List<Variant> state;
+ };
+ Map<ObjectID, struct EncodeInfo> state;
+ if (tracked_objects.has(p_scene_id)) {
+ for (const ObjectID &obj_id : tracked_objects[p_scene_id]) {
+ Object *obj = ObjectDB::get_instance(obj_id);
+ if (obj) {
+ struct EncodeInfo info;
+ Error err = _get_state(cfg.sync_properties, obj, info.state);
+ ERR_CONTINUE(err);
+ err = _encode_state(info.state, nullptr, info.size, &info.raw);
+ ERR_CONTINUE(err);
+ state[obj_id] = info;
+ full_size += info.size;
+ if (last_size && info.size != last_size) {
+ same_size = false;
+ }
+ all_raw = all_raw && info.raw;
+ last_size = info.size;
+ }
+ }
+ }
+ // Default implementation do not send empty updates.
+ if (!full_size) {
+ return OK;
+ }
+#ifdef DEBUG_ENABLED
+ if (full_size > 4096 && cfg.sync_interval) {
+ WARN_PRINT_ONCE(vformat("The timed state update for scene %d is big (%d bytes) consider optimizing it", p_scene_id));
+ }
+#endif
+ if (same_size) {
+ // This is fast and small. Should we allow more than 256 objects per type?
+ // This costs us 1 byte.
+ MAKE_ROOM(SYNC_CMD_OFFSET + 1 + 2 + 2 + full_size);
+ } else {
+ MAKE_ROOM(SYNC_CMD_OFFSET + 1 + 2 + state.size() * 2 + full_size);
+ }
+ int ofs = 0;
+ uint8_t *ptr = packet_cache.ptrw();
+ ptr[0] = MultiplayerAPI::NETWORK_COMMAND_SYNC + ((same_size ? 1 : 0) << MultiplayerAPI::BYTE_ONLY_OR_NO_ARGS_SHIFT);
+ ofs = 1;
+ ofs += encode_uint64(p_scene_id, &ptr[ofs]);
+ ptr[ofs] = cfg.sync_recv++;
+ ofs += 1;
+ ofs += encode_uint16(state.size(), &ptr[ofs]);
+ if (same_size) {
+ ofs += encode_uint16(last_size + (all_raw ? 1 << 15 : 0), &ptr[ofs]);
+ }
+ for (const ObjectID &obj_id : tracked_objects[p_scene_id]) {
+ if (!state.has(obj_id)) {
+ continue;
+ }
+ struct EncodeInfo &info = state[obj_id];
+ Object *obj = ObjectDB::get_instance(obj_id);
+ ERR_CONTINUE(!obj);
+ int size = 0;
+ if (!same_size) {
+ // We need to encode the size of every object.
+ ofs += encode_uint16(info.size + (info.raw ? 1 << 15 : 0), &ptr[ofs]);
+ }
+ Error err = _encode_state(info.state, &ptr[ofs], size, &info.raw);
+ ERR_CONTINUE(err);
+ ofs += size;
+ }
+ Ref<MultiplayerPeer> network_peer = multiplayer->get_network_peer();
+ network_peer->set_target_peer(p_peer);
+ network_peer->set_transfer_channel(0);
+ network_peer->set_transfer_mode(MultiplayerPeer::TRANSFER_MODE_UNRELIABLE);
+ return network_peer->put_packet(ptr, ofs);
+}
+
+void MultiplayerReplicator::_process_default_sync(const ResourceUID::ID &p_id, const uint8_t *p_packet, int p_packet_len) {
+ ERR_FAIL_COND_MSG(p_packet_len < SYNC_CMD_OFFSET + 5, "Invalid spawn packet received");
+ ERR_FAIL_COND_MSG(!replications.has(p_id), "Invalid spawn ID received " + itos(p_id));
+ SceneConfig &cfg = replications[p_id];
+ ERR_FAIL_COND_MSG(cfg.mode != REPLICATION_MODE_SERVER || multiplayer->is_network_server(), "The defualt implementation only allows sync packets from the server");
+ const bool same_size = ((p_packet[0] & 64) >> MultiplayerAPI::BYTE_ONLY_OR_NO_ARGS_SHIFT) == 1;
+ int ofs = SYNC_CMD_OFFSET;
+ int time = p_packet[ofs];
+ // Skip old update.
+ if (time < cfg.sync_recv && cfg.sync_recv - time < 127) {
+ return;
+ }
+ cfg.sync_recv = time;
+ ofs += 1;
+ int count = decode_uint16(&p_packet[ofs]);
+ ofs += 2;
+#ifdef DEBUG_ENABLED
+ ERR_FAIL_COND(!tracked_objects.has(p_id) || tracked_objects[p_id].size() != count);
+#else
+ if (!tracked_objects.has(p_id) || tracked_objects[p_id].size() != count) {
+ return;
+ }
+#endif
+ int data_size = 0;
+ bool raw = false;
+ if (same_size) {
+ // This is fast and optimized.
+ data_size = decode_uint16(&p_packet[ofs]);
+ raw = (data_size & (1 << 15)) != 0;
+ data_size = data_size & ~(1 << 15);
+ ofs += 2;
+ ERR_FAIL_COND(p_packet_len - ofs < data_size * count);
+ }
+ for (const ObjectID &obj_id : tracked_objects[p_id]) {
+ Object *obj = ObjectDB::get_instance(obj_id);
+ ERR_CONTINUE(!obj);
+ if (!same_size) {
+ // This is slow and wasteful.
+ data_size = decode_uint16(&p_packet[ofs]);
+ raw = (data_size & (1 << 15)) != 0;
+ data_size = data_size & ~(1 << 15);
+ ofs += 2;
+ ERR_FAIL_COND(p_packet_len - ofs < data_size);
+ }
+ int size = 0;
+ Error err = _decode_state(cfg.sync_properties, obj, &p_packet[ofs], data_size, size, raw);
+ ofs += data_size;
+ ERR_CONTINUE(err);
+ ERR_CONTINUE(size != data_size);
+ }
+}
+
Error MultiplayerReplicator::_send_default_spawn_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, Object *p_obj, const NodePath &p_path, bool p_spawn) {
ERR_FAIL_COND_V(p_spawn && !p_obj, ERR_INVALID_PARAMETER);
ERR_FAIL_COND_V(!replications.has(p_scene_id), ERR_INVALID_PARAMETER);
@@ -136,6 +270,7 @@ void MultiplayerReplicator::_process_default_spawn_despawn(int p_from, const Res
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);
@@ -145,6 +280,7 @@ void MultiplayerReplicator::_process_default_spawn_despawn(int p_from, const Res
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();
}
@@ -197,6 +333,37 @@ void MultiplayerReplicator::process_spawn_despawn(int p_from, const uint8_t *p_p
}
}
+void MultiplayerReplicator::process_sync(int p_from, const uint8_t *p_packet, int p_packet_len) {
+ ERR_FAIL_COND_MSG(p_packet_len < SPAWN_CMD_OFFSET, "Invalid spawn packet received");
+ ResourceUID::ID id = decode_uint64(&p_packet[1]);
+ ERR_FAIL_COND_MSG(!replications.has(id), "Invalid spawn ID received " + itos(id));
+ const SceneConfig &cfg = replications[id];
+ if (cfg.on_sync_receive.is_valid()) {
+ Array objs;
+ if (tracked_objects.has(id)) {
+ objs.resize(tracked_objects[id].size());
+ int idx = 0;
+ for (const ObjectID &obj_id : tracked_objects[id]) {
+ objs[idx++] = ObjectDB::get_instance(obj_id);
+ }
+ }
+ PackedByteArray pba;
+ pba.resize(p_packet_len - SPAWN_CMD_OFFSET);
+ if (pba.size()) {
+ memcpy(pba.ptrw(), p_packet, p_packet_len - SPAWN_CMD_OFFSET);
+ }
+ Variant args[4] = { p_from, id, objs, pba };
+ Variant *argp[4] = { args, &args[1], &args[2], &args[3] };
+ Callable::CallError ce;
+ Variant ret;
+ cfg.on_sync_receive.call((const Variant **)argp, 4, ret, ce);
+ ERR_FAIL_COND_MSG(ce.error != Callable::CallError::CALL_OK, "Custom sync function failed");
+ } else {
+ ERR_FAIL_COND_MSG(p_from != 1, "Default sync implementation only allow syncing from server to client");
+ _process_default_sync(id, p_packet, p_packet_len);
+ }
+}
+
Error MultiplayerReplicator::_get_state(const List<StringName> &p_properties, const Object *p_obj, List<Variant> &r_variant) {
ERR_FAIL_COND_V_MSG(!p_obj, ERR_INVALID_PARAMETER, "Cannot encode null object");
for (const StringName &prop : p_properties) {
@@ -306,6 +473,21 @@ Error MultiplayerReplicator::spawn_config(const ResourceUID::ID &p_id, Replicati
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;
@@ -337,6 +519,7 @@ Error MultiplayerReplicator::_send_spawn_despawn(int p_peer_id, const ResourceUI
}
Error MultiplayerReplicator::send_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const Variant &p_data, const NodePath &p_path) {
+ ERR_FAIL_COND_V(!multiplayer->has_network_peer(), ERR_UNCONFIGURED);
ERR_FAIL_COND_V_MSG(!replications.has(p_scene_id), ERR_INVALID_PARAMETER, vformat("Spawnable not found: %d", p_scene_id));
const SceneConfig &cfg = replications[p_scene_id];
if (cfg.on_spawn_despawn_send.is_valid()) {
@@ -357,6 +540,7 @@ Error MultiplayerReplicator::send_despawn(int p_peer_id, const ResourceUID::ID &
}
Error MultiplayerReplicator::send_spawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const Variant &p_data, const NodePath &p_path) {
+ ERR_FAIL_COND_V(!multiplayer->has_network_peer(), ERR_UNCONFIGURED);
ERR_FAIL_COND_V_MSG(!replications.has(p_scene_id), ERR_INVALID_PARAMETER, vformat("Spawnable not found: %d", p_scene_id));
const SceneConfig &cfg = replications[p_scene_id];
if (cfg.on_spawn_despawn_send.is_valid()) {
@@ -408,13 +592,14 @@ Error MultiplayerReplicator::despawn(ResourceUID::ID p_scene_id, Object *p_obj,
return _spawn_despawn(p_scene_id, p_obj, p_peer, false);
}
-PackedByteArray MultiplayerReplicator::encode_state(const ResourceUID::ID &p_scene_id, const Object *p_obj) {
+PackedByteArray 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;
- Error err = _get_state(cfg.properties, p_obj, 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.");
@@ -423,11 +608,12 @@ PackedByteArray MultiplayerReplicator::encode_state(const ResourceUID::ID &p_sce
return state;
}
-Error MultiplayerReplicator::decode_state(const ResourceUID::ID &p_scene_id, Object *p_obj, const PackedByteArray p_data) {
+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(cfg.properties, p_obj, p_data.ptr(), p_data.size(), 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) {
@@ -448,12 +634,14 @@ void MultiplayerReplicator::scene_enter_exit_notify(const String &p_scene, Node
if (p_enter) {
if (cfg.mode == REPLICATION_MODE_SERVER && multiplayer->is_network_server()) {
replicated_nodes[p_node->get_instance_id()] = id;
+ _track(id, p_node);
spawn(id, p_node, 0);
}
emit_signal(SNAME("replicated_instance_added"), id, p_node);
} else {
if (cfg.mode == REPLICATION_MODE_SERVER && multiplayer->is_network_server() && replicated_nodes.has(p_node->get_instance_id())) {
replicated_nodes.erase(p_node->get_instance_id());
+ _untrack(id, p_node);
despawn(id, p_node, 0);
}
emit_signal(SNAME("replicated_instance_removed"), id, p_node);
@@ -471,18 +659,119 @@ void MultiplayerReplicator::spawn_all(int p_peer) {
}
}
+void MultiplayerReplicator::poll() {
+ for (KeyValue<ResourceUID::ID, SceneConfig> &E : replications) {
+ if (!E.value.sync_interval) {
+ continue;
+ }
+ if (E.value.mode == REPLICATION_MODE_SERVER && !multiplayer->is_network_server()) {
+ continue;
+ }
+ uint64_t time = OS::get_singleton()->get_ticks_usec();
+ if (E.value.sync_last + E.value.sync_interval <= time) {
+ sync_all(E.key, 0);
+ E.value.sync_last = time;
+ }
+ // Handle wrapping.
+ if (E.value.sync_last > time) {
+ E.value.sync_last = time;
+ }
+ }
+}
+
+void MultiplayerReplicator::track(const ResourceUID::ID &p_scene_id, Object *p_obj) {
+ ERR_FAIL_COND(!replications.has(p_scene_id));
+ const SceneConfig &cfg = replications[p_scene_id];
+ ERR_FAIL_COND_MSG(cfg.mode == REPLICATION_MODE_SERVER, "Manual object tracking is not allowed in server mode.");
+ _track(p_scene_id, p_obj);
+}
+
+void MultiplayerReplicator::_track(const ResourceUID::ID &p_scene_id, Object *p_obj) {
+ ERR_FAIL_COND(!p_obj);
+ ERR_FAIL_COND(!replications.has(p_scene_id));
+ if (!tracked_objects.has(p_scene_id)) {
+ tracked_objects[p_scene_id] = List<ObjectID>();
+ }
+ tracked_objects[p_scene_id].push_back(p_obj->get_instance_id());
+}
+
+void MultiplayerReplicator::untrack(const ResourceUID::ID &p_scene_id, Object *p_obj) {
+ ERR_FAIL_COND(!replications.has(p_scene_id));
+ const SceneConfig &cfg = replications[p_scene_id];
+ ERR_FAIL_COND_MSG(cfg.mode == REPLICATION_MODE_SERVER, "Manual object tracking is not allowed in server mode.");
+ _untrack(p_scene_id, p_obj);
+}
+
+void MultiplayerReplicator::_untrack(const ResourceUID::ID &p_scene_id, Object *p_obj) {
+ ERR_FAIL_COND(!p_obj);
+ ERR_FAIL_COND(!replications.has(p_scene_id));
+ if (tracked_objects.has(p_scene_id)) {
+ tracked_objects[p_scene_id].erase(p_obj->get_instance_id());
+ }
+}
+
+Error MultiplayerReplicator::sync_all(const ResourceUID::ID &p_scene_id, int p_peer) {
+ ERR_FAIL_COND_V(!replications.has(p_scene_id), ERR_INVALID_PARAMETER);
+ if (!tracked_objects.has(p_scene_id)) {
+ return OK;
+ }
+ const SceneConfig &cfg = replications[p_scene_id];
+ if (cfg.on_sync_send.is_valid()) {
+ Array objs;
+ if (tracked_objects.has(p_scene_id)) {
+ objs.resize(tracked_objects[p_scene_id].size());
+ int idx = 0;
+ for (const ObjectID &obj_id : tracked_objects[p_scene_id]) {
+ objs[idx++] = ObjectDB::get_instance(obj_id);
+ }
+ }
+ Variant args[3] = { p_scene_id, objs, p_peer };
+ Variant *argp[3] = { args, &args[1], &args[2] };
+ Callable::CallError ce;
+ Variant ret;
+ cfg.on_sync_send.call((const Variant **)argp, 3, ret, ce);
+ ERR_FAIL_COND_V_MSG(ce.error != Callable::CallError::CALL_OK, FAILED, "Custom sync function failed");
+ return OK;
+ } else if (cfg.sync_properties.size()) {
+ return _sync_all_default(p_scene_id, p_peer);
+ }
+ return OK;
+}
+
+Error MultiplayerReplicator::send_sync(int p_peer_id, const ResourceUID::ID &p_scene_id, PackedByteArray p_data, MultiplayerPeer::TransferMode p_transfer_mode, int p_channel) {
+ ERR_FAIL_COND_V(!multiplayer->has_network_peer(), ERR_UNCONFIGURED);
+ ERR_FAIL_COND_V(!replications.has(p_scene_id), ERR_INVALID_PARAMETER);
+ const SceneConfig &cfg = replications[p_scene_id];
+ ERR_FAIL_COND_V_MSG(!cfg.on_sync_send.is_valid(), ERR_UNCONFIGURED, "Sending raw sync messages is only available with custom functions");
+ MAKE_ROOM(SYNC_CMD_OFFSET + p_data.size());
+ uint8_t *ptr = packet_cache.ptrw();
+ ptr[0] = MultiplayerAPI::NETWORK_COMMAND_SYNC;
+ encode_uint64(p_scene_id, &ptr[1]);
+ Ref<MultiplayerPeer> network_peer = multiplayer->get_network_peer();
+ network_peer->set_target_peer(p_peer_id);
+ network_peer->set_transfer_channel(p_channel);
+ network_peer->set_transfer_mode(p_transfer_mode);
+ return network_peer->put_packet(ptr, SYNC_CMD_OFFSET + p_data.size());
+}
+
void MultiplayerReplicator::clear() {
+ tracked_objects.clear();
replicated_nodes.clear();
}
void MultiplayerReplicator::_bind_methods() {
ClassDB::bind_method(D_METHOD("spawn_config", "scene_id", "spawn_mode", "properties", "custom_send", "custom_receive"), &MultiplayerReplicator::spawn_config, DEFVAL(TypedArray<StringName>()), DEFVAL(Callable()), DEFVAL(Callable()));
+ ClassDB::bind_method(D_METHOD("sync_config", "scene_id", "interval", "properties", "custom_send", "custom_receive"), &MultiplayerReplicator::sync_config, DEFVAL(TypedArray<StringName>()), DEFVAL(Callable()), DEFVAL(Callable()));
ClassDB::bind_method(D_METHOD("despawn", "scene_id", "object", "peer_id"), &MultiplayerReplicator::despawn, DEFVAL(0));
ClassDB::bind_method(D_METHOD("spawn", "scene_id", "object", "peer_id"), &MultiplayerReplicator::spawn, DEFVAL(0));
ClassDB::bind_method(D_METHOD("send_despawn", "peer_id", "scene_id", "data", "path"), &MultiplayerReplicator::send_despawn, DEFVAL(Variant()), DEFVAL(NodePath()));
ClassDB::bind_method(D_METHOD("send_spawn", "peer_id", "scene_id", "data", "path"), &MultiplayerReplicator::send_spawn, DEFVAL(Variant()), DEFVAL(NodePath()));
- ClassDB::bind_method(D_METHOD("encode_state", "scene_id", "object"), &MultiplayerReplicator::encode_state);
- ClassDB::bind_method(D_METHOD("decode_state", "scene_id", "object", "data"), &MultiplayerReplicator::decode_state);
+ ClassDB::bind_method(D_METHOD("send_sync", "peer_id", "scene_id", "data", "transfer_mode", "channel"), &MultiplayerReplicator::send_sync, DEFVAL(MultiplayerPeer::TRANSFER_MODE_RELIABLE), DEFVAL(0));
+ ClassDB::bind_method(D_METHOD("sync_all", "scene_id", "peer_id"), &MultiplayerReplicator::sync_all, DEFVAL(0));
+ ClassDB::bind_method(D_METHOD("track", "scene_id", "object"), &MultiplayerReplicator::track);
+ ClassDB::bind_method(D_METHOD("untrack", "scene_id", "object"), &MultiplayerReplicator::untrack);
+ ClassDB::bind_method(D_METHOD("encode_state", "scene_id", "object", "initial"), &MultiplayerReplicator::encode_state, DEFVAL(true));
+ ClassDB::bind_method(D_METHOD("decode_state", "scene_id", "object", "data", "initial"), &MultiplayerReplicator::decode_state, DEFVAL(true));
ADD_SIGNAL(MethodInfo("despawned", PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node")));
ADD_SIGNAL(MethodInfo("spawned", PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node")));
diff --git a/core/io/multiplayer_replicator.h b/core/io/multiplayer_replicator.h
index e19dd80602..2630ad7a8a 100644
--- a/core/io/multiplayer_replicator.h
+++ b/core/io/multiplayer_replicator.h
@@ -32,6 +32,8 @@
#define MULTIPLAYER_REPLICATOR_H
#include "core/io/multiplayer_api.h"
+
+#include "core/templates/hash_map.h"
#include "core/variant/typed_array.h"
class MultiplayerReplicator : public Object {
@@ -40,6 +42,7 @@ class MultiplayerReplicator : public Object {
public:
enum {
SPAWN_CMD_OFFSET = 9,
+ SYNC_CMD_OFFSET = 9,
};
enum ReplicationMode {
@@ -50,9 +53,15 @@ public:
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:
@@ -63,31 +72,52 @@ private:
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);
- Error _get_state(const List<StringName> &p_properties, const Object *p_obj, List<Variant> &r_variant);
+
+ // 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());
- PackedByteArray encode_state(const ResourceUID::ID &p_scene_id, const Object *p_node);
- Error decode_state(const ResourceUID::ID &p_scene_id, Object *p_node, PackedByteArray p_data);
+
+ // Sync
+ Error sync_config(const ResourceUID::ID &p_id, uint64_t p_interval, const TypedArray<StringName> &p_props = TypedArray<StringName>(), const Callable &p_on_send = Callable(), const Callable &p_on_recv = Callable());
+ Error sync_all(const ResourceUID::ID &p_scene_id, int p_peer);
+ Error send_sync(int p_peer_id, const ResourceUID::ID &p_scene_id, PackedByteArray p_data, MultiplayerPeer::TransferMode p_mode, int p_channel);
+ void track(const ResourceUID::ID &p_scene_id, Object *p_object);
+ void untrack(const ResourceUID::ID &p_scene_id, Object *p_object);
// Used by MultiplayerAPI
void spawn_all(int p_peer);
void process_spawn_despawn(int p_from, const uint8_t *p_packet, int p_packet_len, bool p_spawn);
+ void process_sync(int p_from, const uint8_t *p_packet, int p_packet_len);
void scene_enter_exit_notify(const String &p_scene, Node *p_node, bool p_enter);
+ void poll();
MultiplayerReplicator(MultiplayerAPI *p_multiplayer) {
multiplayer = p_multiplayer;
diff --git a/doc/classes/MultiplayerAPI.xml b/doc/classes/MultiplayerAPI.xml
index 610b00efe9..70046fc3e9 100644
--- a/doc/classes/MultiplayerAPI.xml
+++ b/doc/classes/MultiplayerAPI.xml
@@ -73,7 +73,7 @@
[b]Warning:[/b] Deserialized objects can contain code which gets executed. Do not use this option if the serialized object comes from untrusted sources to avoid potential security threats such as remote code execution.
</member>
<member name="network_peer" type="MultiplayerPeer" setter="set_network_peer" getter="get_network_peer">
- The peer object to handle the RPC system (effectively enabling networking when set). Depending on the peer itself, the MultiplayerAPI will become a network server (check with [method is_network_server]) and will set root node's network mode to master, or it will become a regular peer with root node set to puppet. All child nodes are set to inherit the network mode by default. Handling of networking-related events (connection, disconnection, new clients) is done by connecting to MultiplayerAPI's signals.
+ The peer object to handle the RPC system (effectively enabling networking when set). Depending on the peer itself, the MultiplayerAPI will become a network server (check with [method is_network_server]) and will set root node's network mode to authority, or it will become a regular client peer. All child nodes are set to inherit the network mode by default. Handling of networking-related events (connection, disconnection, new clients) is done by connecting to MultiplayerAPI's signals.
</member>
<member name="refuse_new_network_connections" type="bool" setter="set_refuse_new_network_connections" getter="is_refusing_new_network_connections" default="false">
If [code]true[/code], the MultiplayerAPI's [member network_peer] refuses new incoming connections.
@@ -125,14 +125,11 @@
<constant name="RPC_MODE_DISABLED" value="0" enum="RPCMode">
Used with [method Node.rpc_config] to disable a method or property for all RPC calls, making it unavailable. Default for all methods.
</constant>
- <constant name="RPC_MODE_REMOTE" value="1" enum="RPCMode">
- Used with [method Node.rpc_config] to set a method to be called or a property to be changed only on the remote end, not locally. Analogous to the [code]remote[/code] keyword. Calls and property changes are accepted from all remote peers, no matter if they are node's master or puppets.
+ <constant name="RPC_MODE_ANY" value="1" enum="RPCMode">
+ Used with [method Node.rpc_config] to set a method to be callable remotely by any peer. Analogous to the [code]@rpc(any)[/code] annotation. Calls are accepted from all remote peers, no matter if they are node's authority or not.
</constant>
- <constant name="RPC_MODE_MASTER" value="2" enum="RPCMode">
- Used with [method Node.rpc_config] to set a method to be called or a property to be changed only on the network master for this node. Analogous to the [code]master[/code] keyword. Only accepts calls or property changes from the node's network puppets, see [method Node.set_network_master].
- </constant>
- <constant name="RPC_MODE_PUPPET" value="3" enum="RPCMode">
- Used with [method Node.rpc_config] to set a method to be called or a property to be changed only on puppets for this node. Analogous to the [code]puppet[/code] keyword. Only accepts calls or property changes from the node's network master, see [method Node.set_network_master].
+ <constant name="RPC_MODE_AUTHORITY" value="2" enum="RPCMode">
+ Used with [method Node.rpc_config] to set a method to be callable remotely only by the current network authority (which is the server by default). Analogous to the [code]@rpc(auth)[/code] annotation. See [method Node.set_network_authority].
</constant>
</constants>
</class>
diff --git a/doc/classes/MultiplayerReplicator.xml b/doc/classes/MultiplayerReplicator.xml
index 15029e181f..0778a7335f 100644
--- a/doc/classes/MultiplayerReplicator.xml
+++ b/doc/classes/MultiplayerReplicator.xml
@@ -12,6 +12,7 @@
<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].
@@ -23,12 +24,14 @@
<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 behaviour, 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].
@@ -54,12 +57,24 @@
Sends a spawn request for the scene identified by [code]scene_id[/code] to the given [code]peer_id[/code] (see [method MultiplayerPeer.set_target_peer]). If the scene is configured as [constant REPLICATION_MODE_SERVER] (see [method spawn_config]) and the request is sent by the server (see [method MultiplayerAPI.is_network_server]), the receiving peer(s) will automatically instantiate that scene, add it to the [SceneTree] at the given [code]path[/code] and emit the signal [signal spawned]. In all other cases no instantiation happens, and the signal [signal spawn_requested] is emitted instead.
</description>
</method>
+ <method name="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="MultiplayerPeer.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 behaviour, 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">
@@ -70,10 +85,47 @@
<argument index="3" name="custom_send" type="Callable" />
<argument index="4" name="custom_receive" type="Callable" />
<description>
- Configures the MultiplayerAPI to track instances of the [PackedScene] identified by [code]scene_id[/code] (see [method ResourceLoader.get_resource_uid]) for the purpose of network replication. When [code]mode[/code] is [constant REPLICATION_MODE_SERVER], the specified [code]properties[/code] will also be replicated to clients during the initial spawn.
+ 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 behaviour 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 behaviour, or call your send custom send callable if specified in [method sync_config].
+ Note: 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 behaviour and customize the syncronization 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 higly 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">
diff --git a/doc/classes/Node.xml b/doc/classes/Node.xml
index f5bc705ecb..7d79c50466 100644
--- a/doc/classes/Node.xml
+++ b/doc/classes/Node.xml
@@ -109,9 +109,11 @@
<return type="void" />
<argument index="0" name="node" type="Node" />
<argument index="1" name="legible_unique_name" type="bool" default="false" />
+ <argument index="2" name="internal" type="int" enum="Node.InternalMode" default="0" />
<description>
Adds a child node. Nodes can have any number of children, but every child must have a unique name. Child nodes are automatically deleted when the parent node is deleted, so an entire scene can be removed by deleting its topmost node.
If [code]legible_unique_name[/code] is [code]true[/code], the child node will have a human-readable name based on the name of the node being instantiated instead of its type.
+ If [code]internal[/code] is different than [constant INTERNAL_MODE_DISABLED], the child will be added as internal node. Such nodes are ignored by methods like [method get_children], unless their parameter [code]include_internal[/code] is [code]true[/code].The intended usage is to hide the internal nodes from the user, so the user won't accidentally delete or modify them. Used by some GUI nodes, e.g. [ColorPicker]. See [enum InternalMode] for available modes.
[b]Note:[/b] If the child node already has a parent, the function will fail. Use [method remove_child] first to remove the node from its current parent. For example:
[codeblocks]
[gdscript]
@@ -141,6 +143,7 @@
Adds a [code]sibling[/code] node to current's node parent, at the same level as that node, right below it.
If [code]legible_unique_name[/code] is [code]true[/code], the child node will have a human-readable name based on the name of the node being instantiated instead of its type.
Use [method add_child] instead of this method if you don't need the child node to be added below a specific node in the list of children.
+ [b]Note:[/b] If this node is internal, the new sibling will be internal too (see [code]internal[/code] parameter in [method add_child]).
</description>
</method>
<method name="add_to_group">
@@ -200,22 +203,28 @@
<method name="get_child" qualifiers="const">
<return type="Node" />
<argument index="0" name="idx" type="int" />
+ <argument index="1" name="include_internal" type="bool" default="false" />
<description>
Returns a child node by its index (see [method get_child_count]). This method is often used for iterating all children of a node.
Negative indices access the children from the last one.
+ If [code]include_internal[/code] is [code]true[/code], internal children are skipped (see [code]internal[/code] parameter in [method add_child]).
To access a child node via its name, use [method get_node].
</description>
</method>
<method name="get_child_count" qualifiers="const">
<return type="int" />
+ <argument index="0" name="include_internal" type="bool" default="false" />
<description>
Returns the number of child nodes.
+ If [code]include_internal[/code] is [code]false[/code], internal children aren't counted (see [code]internal[/code] parameter in [method add_child]).
</description>
</method>
<method name="get_children" qualifiers="const">
<return type="Node[]" />
+ <argument index="0" name="include_internal" type="bool" default="false" />
<description>
Returns an array of references to node's children.
+ If [code]include_internal[/code] is [code]false[/code], the returned array won't include internal children (see [code]internal[/code] parameter in [method add_child]).
</description>
</method>
<method name="get_editor_description" qualifiers="const">
@@ -231,14 +240,16 @@
</method>
<method name="get_index" qualifiers="const">
<return type="int" />
+ <argument index="0" name="include_internal" type="bool" default="false" />
<description>
Returns the node's order in the scene tree branch. For example, if called on the first child node the position is [code]0[/code].
+ If [code]include_internal[/code] is [code]false[/code], the index won't take internal children into account, i.e. first non-internal child will have index of 0 (see [code]internal[/code] parameter in [method add_child]).
</description>
</method>
- <method name="get_network_master" qualifiers="const">
+ <method name="get_network_authority" qualifiers="const">
<return type="int" />
<description>
- Returns the peer ID of the network master for this node. See [method set_network_master].
+ Returns the peer ID of the network authority for this node. See [method set_network_authority].
</description>
</method>
<method name="get_node" qualifiers="const">
@@ -406,10 +417,10 @@
Returns [code]true[/code] if this node is currently inside a [SceneTree].
</description>
</method>
- <method name="is_network_master" qualifiers="const">
+ <method name="is_network_authority" qualifiers="const">
<return type="bool" />
<description>
- Returns [code]true[/code] if the local system is the master of this node.
+ Returns [code]true[/code] if the local system is the authority of this node.
</description>
</method>
<method name="is_physics_processing" qualifiers="const">
@@ -460,6 +471,7 @@
<argument index="1" name="to_position" type="int" />
<description>
Moves a child node to a different position (order) among the other children. Since calls, signals, etc are performed by tree order, changing the order of children nodes may be useful.
+ [b]Note:[/b] Internal children can only be moved within their expected "internal range" (see [code]internal[/code] parameter in [method add_child]).
</description>
</method>
<method name="print_stray_nodes">
@@ -576,7 +588,7 @@
<argument index="2" name="transfer_mode" type="int" enum="MultiplayerPeer.TransferMode" default="2" />
<argument index="3" name="channel" type="int" default="0" />
<description>
- Changes the RPC mode for the given [code]method[/code] to the given [code]rpc_mode[/code], optionally specifying the [code]transfer_mode[/code] and [code]channel[/code] (on supported peers). See [enum MultiplayerAPI.RPCMode] and [enum MultiplayerPeer.TransferMode]. An alternative is annotating methods and properties with the corresponding keywords ([code]remote[/code], [code]master[/code], [code]puppet[/code], [code]remotesync[/code], [code]mastersync[/code], [code]puppetsync[/code]). By default, methods are not exposed to networking (and RPCs).
+ Changes the RPC mode for the given [code]method[/code] to the given [code]rpc_mode[/code], optionally specifying the [code]transfer_mode[/code] and [code]channel[/code] (on supported peers). See [enum MultiplayerAPI.RPCMode] and [enum MultiplayerPeer.TransferMode]. An alternative is annotating methods and properties with the corresponding annotation ([code]@rpc(any)[/code], [code]@rpc(auth)[/code]). By default, methods are not exposed to networking (and RPCs).
</description>
</method>
<method name="rpc_id" qualifiers="vararg">
@@ -608,12 +620,12 @@
<description>
</description>
</method>
- <method name="set_network_master">
+ <method name="set_network_authority">
<return type="void" />
<argument index="0" name="id" type="int" />
<argument index="1" name="recursive" type="bool" default="true" />
<description>
- Sets the node's network master to the peer with the given peer ID. The network master is the peer that has authority over the node on the network. Useful in conjunction with the [code]master[/code] and [code]puppet[/code] keywords. Inherited from the parent node by default, which ultimately defaults to peer ID 1 (the server). If [code]recursive[/code], the given peer is recursively set as the master for all children of this node.
+ Sets the node's network authority to the peer with the given peer ID. The network authority is the peer that has authority over the node on the network. Useful in conjunction with [method rpc_config] and the [MultiplayerAPI]. Inherited from the parent node by default, which ultimately defaults to peer ID 1 (the server). If [code]recursive[/code], the given peer is recursively set as the authority for all children of this node.
</description>
</method>
<method name="set_physics_process">
@@ -888,5 +900,14 @@
Duplicate using instancing.
An instance stays linked to the original so when the original changes, the instance changes too.
</constant>
+ <constant name="INTERNAL_MODE_DISABLED" value="0" enum="InternalMode">
+ Node will not be internal.
+ </constant>
+ <constant name="INTERNAL_MODE_FRONT" value="1" enum="InternalMode">
+ Node will be placed at the front of parent's node list, before any non-internal sibling.
+ </constant>
+ <constant name="INTERNAL_MODE_BACK" value="2" enum="InternalMode">
+ Node will be placed at the back of parent's node list, after any non-internal sibling.
+ </constant>
</constants>
</class>
diff --git a/modules/gdnative/include/nativescript/godot_nativescript.h b/modules/gdnative/include/nativescript/godot_nativescript.h
index c97f5f0389..09eac2492f 100644
--- a/modules/gdnative/include/nativescript/godot_nativescript.h
+++ b/modules/gdnative/include/nativescript/godot_nativescript.h
@@ -39,12 +39,8 @@ extern "C" {
typedef enum {
GODOT_METHOD_RPC_MODE_DISABLED,
- GODOT_METHOD_RPC_MODE_REMOTE,
- GODOT_METHOD_RPC_MODE_MASTER,
- GODOT_METHOD_RPC_MODE_PUPPET,
- GODOT_METHOD_RPC_MODE_REMOTESYNC,
- GODOT_METHOD_RPC_MODE_MASTERSYNC,
- GODOT_METHOD_RPC_MODE_PUPPETSYNC,
+ GODOT_METHOD_RPC_MODE_ANY,
+ GODOT_METHOD_RPC_MODE_AUTHORITY,
} godot_nativescript_method_rpc_mode;
typedef enum {
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index d202bd4420..f4c721e00a 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -133,7 +133,7 @@ GDScriptParser::GDScriptParser() {
register_annotation(MethodInfo("@export_flags_3d_physics"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_PHYSICS, Variant::INT>);
register_annotation(MethodInfo("@export_flags_3d_navigation"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_NAVIGATION, Variant::INT>);
// Networking.
- register_annotation(MethodInfo("@rpc", { Variant::STRING, "mode" }, { Variant::STRING, "sync" }, { Variant::STRING, "transfer_mode" }, { Variant::INT, "transfer_channel" }), AnnotationInfo::FUNCTION, &GDScriptParser::network_annotations<MultiplayerAPI::RPC_MODE_PUPPET>, 4, true);
+ register_annotation(MethodInfo("@rpc", { Variant::STRING, "mode" }, { Variant::STRING, "sync" }, { Variant::STRING, "transfer_mode" }, { Variant::INT, "transfer_channel" }), AnnotationInfo::FUNCTION, &GDScriptParser::network_annotations<MultiplayerAPI::RPC_MODE_AUTHORITY>, 4, true);
// TODO: Warning annotations.
}
@@ -3399,43 +3399,35 @@ bool GDScriptParser::network_annotations(const AnnotationNode *p_annotation, Nod
MultiplayerAPI::RPCConfig rpc_config;
rpc_config.rpc_mode = t_mode;
- for (int i = 0; i < p_annotation->resolved_arguments.size(); i++) {
- if (i == 0) {
+ if (p_annotation->resolved_arguments.size()) {
+ int last = p_annotation->resolved_arguments.size() - 1;
+ if (p_annotation->resolved_arguments[last].get_type() == Variant::INT) {
+ rpc_config.channel = p_annotation->resolved_arguments[last].operator int();
+ last -= 1;
+ }
+ if (last > 3) {
+ push_error(R"(Invalid RPC arguments. At most 4 arguments are allowed, where only the last argument can be an integer to specify the channel.')", p_annotation);
+ return false;
+ }
+ for (int i = last; i >= 0; i--) {
String mode = p_annotation->resolved_arguments[i].operator String();
if (mode == "any") {
- rpc_config.rpc_mode = MultiplayerAPI::RPC_MODE_REMOTE;
- } else if (mode == "master") {
- rpc_config.rpc_mode = MultiplayerAPI::RPC_MODE_MASTER;
- } else if (mode == "puppet") {
- rpc_config.rpc_mode = MultiplayerAPI::RPC_MODE_PUPPET;
- } else {
- push_error(R"(Invalid RPC mode. Must be one of: 'any', 'master', or 'puppet')", p_annotation);
- return false;
- }
- } else if (i == 1) {
- String sync = p_annotation->resolved_arguments[i].operator String();
- if (sync == "sync") {
+ rpc_config.rpc_mode = MultiplayerAPI::RPC_MODE_ANY;
+ } else if (mode == "auth") {
+ rpc_config.rpc_mode = MultiplayerAPI::RPC_MODE_AUTHORITY;
+ } else if (mode == "sync") {
rpc_config.sync = true;
- } else if (sync == "nosync") {
+ } else if (mode == "nosync") {
rpc_config.sync = false;
- } else {
- push_error(R"(Invalid RPC sync mode. Must be one of: 'sync' or 'nosync')", p_annotation);
- return false;
- }
- } else if (i == 2) {
- String mode = p_annotation->resolved_arguments[i].operator String();
- if (mode == "reliable") {
+ } else if (mode == "reliable") {
rpc_config.transfer_mode = MultiplayerPeer::TRANSFER_MODE_RELIABLE;
} else if (mode == "unreliable") {
rpc_config.transfer_mode = MultiplayerPeer::TRANSFER_MODE_UNRELIABLE;
} else if (mode == "ordered") {
rpc_config.transfer_mode = MultiplayerPeer::TRANSFER_MODE_UNRELIABLE_ORDERED;
} else {
- push_error(R"(Invalid RPC transfer mode. Must be one of: 'reliable', 'unreliable', 'ordered')", p_annotation);
- return false;
+ push_error(R"(Invalid RPC argument. Must be one of: 'sync'/'nosync' (local calls), 'any'/'auth' (permission), 'reliable'/'unreliable'/'ordered' (transfer mode).)", p_annotation);
}
- } else if (i == 3) {
- rpc_config.channel = p_annotation->resolved_arguments[i].operator int();
}
}
switch (p_node->type) {
diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp
index 1f7f1390ea..978093b7d5 100644
--- a/modules/mono/csharp_script.cpp
+++ b/modules/mono/csharp_script.cpp
@@ -3466,13 +3466,10 @@ int CSharpScript::get_member_line(const StringName &p_member) const {
MultiplayerAPI::RPCMode CSharpScript::_member_get_rpc_mode(IMonoClassMember *p_member) const {
if (p_member->has_attribute(CACHED_CLASS(RemoteAttribute))) {
- return MultiplayerAPI::RPC_MODE_REMOTE;
- }
- if (p_member->has_attribute(CACHED_CLASS(MasterAttribute))) {
- return MultiplayerAPI::RPC_MODE_MASTER;
+ return MultiplayerAPI::RPC_MODE_ANY;
}
if (p_member->has_attribute(CACHED_CLASS(PuppetAttribute))) {
- return MultiplayerAPI::RPC_MODE_PUPPET;
+ return MultiplayerAPI::RPC_MODE_AUTHORITY;
}
return MultiplayerAPI::RPC_MODE_DISABLED;
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/RPCAttributes.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/RPCAttributes.cs
index 6cec8773b2..8aaa9e849d 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/RPCAttributes.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/RPCAttributes.cs
@@ -6,8 +6,5 @@ namespace Godot
public class RemoteAttribute : Attribute {}
[AttributeUsage(AttributeTargets.Method)]
- public class MasterAttribute : Attribute {}
-
- [AttributeUsage(AttributeTargets.Method)]
public class PuppetAttribute : Attribute {}
}
diff --git a/modules/mono/mono_gd/gd_mono_cache.cpp b/modules/mono/mono_gd/gd_mono_cache.cpp
index 0b36796d98..8b215a66c2 100644
--- a/modules/mono/mono_gd/gd_mono_cache.cpp
+++ b/modules/mono/mono_gd/gd_mono_cache.cpp
@@ -141,7 +141,6 @@ void CachedData::clear_godot_api_cache() {
class_SignalAttribute = nullptr;
class_ToolAttribute = nullptr;
class_RemoteAttribute = nullptr;
- class_MasterAttribute = nullptr;
class_PuppetAttribute = nullptr;
class_GodotMethodAttribute = nullptr;
field_GodotMethodAttribute_methodName = nullptr;
@@ -267,7 +266,6 @@ void update_godot_api_cache() {
CACHE_CLASS_AND_CHECK(SignalAttribute, GODOT_API_CLASS(SignalAttribute));
CACHE_CLASS_AND_CHECK(ToolAttribute, GODOT_API_CLASS(ToolAttribute));
CACHE_CLASS_AND_CHECK(RemoteAttribute, GODOT_API_CLASS(RemoteAttribute));
- CACHE_CLASS_AND_CHECK(MasterAttribute, GODOT_API_CLASS(MasterAttribute));
CACHE_CLASS_AND_CHECK(PuppetAttribute, GODOT_API_CLASS(PuppetAttribute));
CACHE_CLASS_AND_CHECK(GodotMethodAttribute, GODOT_API_CLASS(GodotMethodAttribute));
CACHE_FIELD_AND_CHECK(GodotMethodAttribute, methodName, CACHED_CLASS(GodotMethodAttribute)->get_field("methodName"));
diff --git a/modules/mono/mono_gd/gd_mono_cache.h b/modules/mono/mono_gd/gd_mono_cache.h
index 6d49da0af3..fd28bbda14 100644
--- a/modules/mono/mono_gd/gd_mono_cache.h
+++ b/modules/mono/mono_gd/gd_mono_cache.h
@@ -112,7 +112,6 @@ struct CachedData {
GDMonoClass *class_SignalAttribute;
GDMonoClass *class_ToolAttribute;
GDMonoClass *class_RemoteAttribute;
- GDMonoClass *class_MasterAttribute;
GDMonoClass *class_PuppetAttribute;
GDMonoClass *class_GodotMethodAttribute;
GDMonoField *field_GodotMethodAttribute_methodName;
diff --git a/modules/visual_script/visual_script_nodes.cpp b/modules/visual_script/visual_script_nodes.cpp
index 480136736a..44fc91d8f5 100644
--- a/modules/visual_script/visual_script_nodes.cpp
+++ b/modules/visual_script/visual_script_nodes.cpp
@@ -163,7 +163,7 @@ void VisualScriptFunction::_get_property_list(List<PropertyInfo> *p_list) const
p_list->push_back(PropertyInfo(Variant::INT, "stack/size", PROPERTY_HINT_RANGE, "1,100000"));
}
p_list->push_back(PropertyInfo(Variant::BOOL, "stack/stackless"));
- p_list->push_back(PropertyInfo(Variant::INT, "rpc/mode", PROPERTY_HINT_ENUM, "Disabled,Remote,Master,Puppet,Remote Sync,Master Sync,Puppet Sync"));
+ p_list->push_back(PropertyInfo(Variant::INT, "rpc/mode", PROPERTY_HINT_ENUM, "Disabled,Any,Authority"));
}
int VisualScriptFunction::get_output_sequence_port_count() const {
diff --git a/modules/websocket/wsl_client.cpp b/modules/websocket/wsl_client.cpp
index 49997b42d3..26c0176ea4 100644
--- a/modules/websocket/wsl_client.cpp
+++ b/modules/websocket/wsl_client.cpp
@@ -161,22 +161,28 @@ Error WSLClient::connect_to_host(String p_host, String p_path, uint16_t p_port,
ERR_FAIL_COND_V(p_path.is_empty(), ERR_INVALID_PARAMETER);
_peer = Ref<WSLPeer>(memnew(WSLPeer));
- IPAddress addr;
- if (!p_host.is_valid_ip_address()) {
- addr = IP::get_singleton()->resolve_hostname(p_host);
+ if (p_host.is_valid_ip_address()) {
+ ip_candidates.clear();
+ ip_candidates.push_back(IPAddress(p_host));
} else {
- addr = p_host;
+ ip_candidates = IP::get_singleton()->resolve_hostname_addresses(p_host);
}
- ERR_FAIL_COND_V(!addr.is_valid(), ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(ip_candidates.is_empty(), ERR_INVALID_PARAMETER);
String port = "";
if ((p_port != 80 && !p_ssl) || (p_port != 443 && p_ssl)) {
port = ":" + itos(p_port);
}
- Error err = _tcp->connect_to_host(addr, p_port);
+ Error err = ERR_BUG; // Should be at least one entry.
+ while (ip_candidates.size() > 0) {
+ err = _tcp->connect_to_host(ip_candidates.pop_front(), p_port);
+ if (err == OK) {
+ break;
+ }
+ }
if (err != OK) {
_tcp->disconnect_from_host();
_on_error();
@@ -185,6 +191,7 @@ Error WSLClient::connect_to_host(String p_host, String p_path, uint16_t p_port,
_connection = _tcp;
_use_ssl = p_ssl;
_host = p_host;
+ _port = p_port;
// Strip edges from protocols.
_protocols.resize(p_protocols.size());
String *pw = _protocols.ptrw();
@@ -244,6 +251,7 @@ void WSLClient::poll() {
_on_error();
break;
case StreamPeerTCP::STATUS_CONNECTED: {
+ ip_candidates.clear();
Ref<StreamPeerSSL> ssl;
if (_use_ssl) {
if (_connection == _tcp) {
@@ -274,6 +282,12 @@ void WSLClient::poll() {
_do_handshake();
} break;
case StreamPeerTCP::STATUS_ERROR:
+ while (ip_candidates.size() > 0) {
+ _tcp->disconnect_from_host();
+ if (_tcp->connect_to_host(ip_candidates.pop_front(), _port) == OK) {
+ return;
+ }
+ }
disconnect_from_host();
_on_error();
break;
@@ -315,6 +329,8 @@ void WSLClient::disconnect_from_host(int p_code, String p_reason) {
memset(_resp_buf, 0, sizeof(_resp_buf));
_resp_pos = 0;
+
+ ip_candidates.clear();
}
IPAddress WSLClient::get_connected_host() const {
diff --git a/modules/websocket/wsl_client.h b/modules/websocket/wsl_client.h
index 849639ee8b..3972977910 100644
--- a/modules/websocket/wsl_client.h
+++ b/modules/websocket/wsl_client.h
@@ -63,6 +63,8 @@ private:
String _key;
String _host;
+ int _port;
+ Array ip_candidates;
Vector<String> _protocols;
bool _use_ssl = false;
diff --git a/scene/gui/box_container.cpp b/scene/gui/box_container.cpp
index a2f1d2b15a..cf2df4e1a2 100644
--- a/scene/gui/box_container.cpp
+++ b/scene/gui/box_container.cpp
@@ -351,11 +351,11 @@ MarginContainer *VBoxContainer::add_margin_child(const String &p_label, Control
Label *l = memnew(Label);
l->set_theme_type_variation("HeaderSmall");
l->set_text(p_label);
- add_child(l);
+ add_child(l, false, INTERNAL_MODE_FRONT);
MarginContainer *mc = memnew(MarginContainer);
mc->add_theme_constant_override("margin_left", 0);
mc->add_child(p_control);
- add_child(mc);
+ add_child(mc, false, INTERNAL_MODE_FRONT);
if (p_expand) {
mc->set_v_size_flags(SIZE_EXPAND_FILL);
}
diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp
index 0dddb2b09a..1afb0b8e9d 100644
--- a/scene/gui/color_picker.cpp
+++ b/scene/gui/color_picker.cpp
@@ -1139,7 +1139,7 @@ void ColorPicker::_bind_methods() {
ColorPicker::ColorPicker() :
BoxContainer(true) {
HBoxContainer *hb_edit = memnew(HBoxContainer);
- add_child(hb_edit);
+ add_child(hb_edit, false, INTERNAL_MODE_FRONT);
hb_edit->set_v_size_flags(SIZE_EXPAND_FILL);
hb_edit->add_child(uv_edit);
@@ -1151,7 +1151,7 @@ ColorPicker::ColorPicker() :
uv_edit->connect("draw", callable_mp(this, &ColorPicker::_hsv_draw), make_binds(0, uv_edit));
HBoxContainer *hb_smpl = memnew(HBoxContainer);
- add_child(hb_smpl);
+ add_child(hb_smpl, false, INTERNAL_MODE_FRONT);
hb_smpl->add_child(sample);
sample->set_h_size_flags(SIZE_EXPAND_FILL);
@@ -1165,12 +1165,12 @@ ColorPicker::ColorPicker() :
btn_pick->connect("pressed", callable_mp(this, &ColorPicker::_screen_pick_pressed));
VBoxContainer *vbl = memnew(VBoxContainer);
- add_child(vbl);
+ add_child(vbl, false, INTERNAL_MODE_FRONT);
- add_child(memnew(HSeparator));
+ add_child(memnew(HSeparator), false, INTERNAL_MODE_FRONT);
VBoxContainer *vbr = memnew(VBoxContainer);
- add_child(vbr);
+ add_child(vbr, false, INTERNAL_MODE_FRONT);
vbr->set_h_size_flags(SIZE_EXPAND_FILL);
for (int i = 0; i < 4; i++) {
@@ -1273,11 +1273,11 @@ ColorPicker::ColorPicker() :
set_pick_color(Color(1, 1, 1));
- add_child(preset_separator);
+ add_child(preset_separator, false, INTERNAL_MODE_FRONT);
preset_container->set_h_size_flags(SIZE_EXPAND_FILL);
preset_container->set_columns(preset_column_count);
- add_child(preset_container);
+ add_child(preset_container, false, INTERNAL_MODE_FRONT);
btn_add_preset->set_icon_align(Button::ALIGN_CENTER);
btn_add_preset->set_tooltip(RTR("Add current color as a preset."));
@@ -1405,7 +1405,7 @@ void ColorPickerButton::_update_picker() {
picker = memnew(ColorPicker);
picker->set_anchors_and_offsets_preset(PRESET_WIDE);
popup->add_child(picker);
- add_child(popup);
+ add_child(popup, false, INTERNAL_MODE_FRONT);
picker->connect("color_changed", callable_mp(this, &ColorPickerButton::_color_changed));
popup->connect("about_to_popup", callable_mp(this, &ColorPickerButton::_about_to_popup));
popup->connect("popup_hide", callable_mp(this, &ColorPickerButton::_modal_closed));
diff --git a/scene/gui/dialogs.cpp b/scene/gui/dialogs.cpp
index da858e8e83..5d98aaa698 100644
--- a/scene/gui/dialogs.cpp
+++ b/scene/gui/dialogs.cpp
@@ -319,7 +319,7 @@ AcceptDialog::AcceptDialog() {
set_clamp_to_embedder(true);
bg = memnew(Panel);
- add_child(bg);
+ add_child(bg, false, INTERNAL_MODE_FRONT);
hbc = memnew(HBoxContainer);
@@ -331,9 +331,9 @@ AcceptDialog::AcceptDialog() {
label->set_anchor(SIDE_BOTTOM, Control::ANCHOR_END);
label->set_begin(Point2(margin, margin));
label->set_end(Point2(-margin, -button_margin - 10));
- add_child(label);
+ add_child(label, false, INTERNAL_MODE_FRONT);
- add_child(hbc);
+ add_child(hbc, false, INTERNAL_MODE_FRONT);
hbc->add_spacer();
ok = memnew(Button);
diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp
index 2512b24623..973b72973d 100644
--- a/scene/gui/file_dialog.cpp
+++ b/scene/gui/file_dialog.cpp
@@ -924,7 +924,7 @@ FileDialog::FileDialog() {
show_hidden_files = default_show_hidden_files;
vbox = memnew(VBoxContainer);
- add_child(vbox);
+ add_child(vbox, false, INTERNAL_MODE_FRONT);
vbox->connect("theme_changed", callable_mp(this, &FileDialog::_theme_changed));
mode = FILE_MODE_SAVE_FILE;
@@ -1023,8 +1023,7 @@ FileDialog::FileDialog() {
filter->connect("item_selected", callable_mp(this, &FileDialog::_filter_selected));
confirm_save = memnew(ConfirmationDialog);
- // confirm_save->set_as_top_level(true);
- add_child(confirm_save);
+ add_child(confirm_save, false, INTERNAL_MODE_FRONT);
confirm_save->connect("confirmed", callable_mp(this, &FileDialog::_save_confirm_pressed));
@@ -1036,16 +1035,16 @@ FileDialog::FileDialog() {
makedirname = memnew(LineEdit);
makedirname->set_structured_text_bidi_override(Control::STRUCTURED_TEXT_FILE);
makevb->add_margin_child(TTRC("Name:"), makedirname);
- add_child(makedialog);
+ add_child(makedialog, false, INTERNAL_MODE_FRONT);
makedialog->register_text_enter(makedirname);
makedialog->connect("confirmed", callable_mp(this, &FileDialog::_make_dir_confirm));
mkdirerr = memnew(AcceptDialog);
mkdirerr->set_text(TTRC("Could not create folder."));
- add_child(mkdirerr);
+ add_child(mkdirerr, false, INTERNAL_MODE_FRONT);
exterr = memnew(AcceptDialog);
exterr->set_text(TTRC("Must use a valid extension."));
- add_child(exterr);
+ add_child(exterr, false, INTERNAL_MODE_FRONT);
update_filters();
update_dir();
diff --git a/scene/gui/gradient_edit.cpp b/scene/gui/gradient_edit.cpp
index bbab3008e2..56b8a936e1 100644
--- a/scene/gui/gradient_edit.cpp
+++ b/scene/gui/gradient_edit.cpp
@@ -48,7 +48,7 @@ GradientEdit::GradientEdit() {
picker = memnew(ColorPicker);
popup->add_child(picker);
- add_child(popup);
+ add_child(popup, false, INTERNAL_MODE_FRONT);
}
int GradientEdit::_get_point_from_pos(int x) {
diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp
index 0281bc7efb..cabae9feb2 100644
--- a/scene/gui/graph_edit.cpp
+++ b/scene/gui/graph_edit.cpp
@@ -366,7 +366,6 @@ void GraphEdit::_graph_node_raised(Node *p_gn) {
}
move_child(connections_layer, first_not_comment);
- top_layer->raise();
emit_signal(SNAME("node_selected"), p_gn);
}
@@ -2246,14 +2245,14 @@ GraphEdit::GraphEdit() {
zoom_max = (1 * Math::pow(zoom_step, 4));
top_layer = memnew(GraphEditFilter(this));
- add_child(top_layer);
+ add_child(top_layer, false, INTERNAL_MODE_BACK);
top_layer->set_mouse_filter(MOUSE_FILTER_PASS);
top_layer->set_anchors_and_offsets_preset(Control::PRESET_WIDE);
top_layer->connect("draw", callable_mp(this, &GraphEdit::_top_layer_draw));
top_layer->connect("gui_input", callable_mp(this, &GraphEdit::_top_layer_input));
connections_layer = memnew(Control);
- add_child(connections_layer);
+ add_child(connections_layer, false, INTERNAL_MODE_FRONT);
connections_layer->connect("draw", callable_mp(this, &GraphEdit::_connections_layer_draw));
connections_layer->set_name("CLAYER");
connections_layer->set_disable_visibility_clip(true); // so it can draw freely and be offset
diff --git a/scene/gui/item_list.cpp b/scene/gui/item_list.cpp
index 8297de9f30..d10ad90c1f 100644
--- a/scene/gui/item_list.cpp
+++ b/scene/gui/item_list.cpp
@@ -1656,7 +1656,7 @@ void ItemList::_bind_methods() {
ItemList::ItemList() {
scroll_bar = memnew(VScrollBar);
- add_child(scroll_bar);
+ add_child(scroll_bar, false, INTERNAL_MODE_FRONT);
scroll_bar->connect("value_changed", callable_mp(this, &ItemList::_scroll_changed));
diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp
index d524e78caa..3605842224 100644
--- a/scene/gui/line_edit.cpp
+++ b/scene/gui/line_edit.cpp
@@ -2220,7 +2220,7 @@ void LineEdit::_bind_methods() {
void LineEdit::_ensure_menu() {
if (!menu) {
menu = memnew(PopupMenu);
- add_child(menu);
+ add_child(menu, false, INTERNAL_MODE_FRONT);
menu_dir = memnew(PopupMenu);
menu_dir->set_name("DirMenu");
@@ -2305,7 +2305,7 @@ LineEdit::LineEdit() {
set_mouse_filter(MOUSE_FILTER_STOP);
caret_blink_timer = memnew(Timer);
- add_child(caret_blink_timer);
+ add_child(caret_blink_timer, false, INTERNAL_MODE_FRONT);
caret_blink_timer->set_wait_time(0.65);
caret_blink_timer->connect("timeout", callable_mp(this, &LineEdit::_toggle_draw_caret));
set_caret_blink_enabled(false);
diff --git a/scene/gui/menu_button.cpp b/scene/gui/menu_button.cpp
index e312aaed57..737ba84617 100644
--- a/scene/gui/menu_button.cpp
+++ b/scene/gui/menu_button.cpp
@@ -172,7 +172,7 @@ MenuButton::MenuButton() {
popup = memnew(PopupMenu);
popup->hide();
- add_child(popup);
+ add_child(popup, false, INTERNAL_MODE_FRONT);
popup->connect("about_to_popup", callable_mp(this, &MenuButton::_popup_visibility_changed), varray(true));
popup->connect("popup_hide", callable_mp(this, &MenuButton::_popup_visibility_changed), varray(false));
}
diff --git a/scene/gui/option_button.cpp b/scene/gui/option_button.cpp
index cd55f258b3..d16e96dbec 100644
--- a/scene/gui/option_button.cpp
+++ b/scene/gui/option_button.cpp
@@ -351,7 +351,7 @@ OptionButton::OptionButton() {
popup = memnew(PopupMenu);
popup->hide();
- add_child(popup);
+ add_child(popup, false, INTERNAL_MODE_FRONT);
popup->connect("index_pressed", callable_mp(this, &OptionButton::_selected));
popup->connect("id_focused", callable_mp(this, &OptionButton::_focused));
popup->connect("popup_hide", callable_mp((BaseButton *)this, &BaseButton::set_pressed), varray(false));
diff --git a/scene/gui/popup.cpp b/scene/gui/popup.cpp
index f7e7e1cd60..e9414598a2 100644
--- a/scene/gui/popup.cpp
+++ b/scene/gui/popup.cpp
@@ -255,5 +255,5 @@ void PopupPanel::_notification(int p_what) {
PopupPanel::PopupPanel() {
panel = memnew(Panel);
- add_child(panel);
+ add_child(panel, false, INTERNAL_MODE_FRONT);
}
diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp
index c37095a9f8..c0a559e624 100644
--- a/scene/gui/popup_menu.cpp
+++ b/scene/gui/popup_menu.cpp
@@ -1690,7 +1690,7 @@ PopupMenu::PopupMenu() {
// Margin Container
margin_container = memnew(MarginContainer);
margin_container->set_anchors_and_offsets_preset(Control::PRESET_WIDE);
- add_child(margin_container);
+ add_child(margin_container, false, INTERNAL_MODE_FRONT);
margin_container->connect("draw", callable_mp(this, &PopupMenu::_draw_background));
// Scroll Container
@@ -1704,7 +1704,7 @@ PopupMenu::PopupMenu() {
control->set_anchors_and_offsets_preset(Control::PRESET_WIDE);
control->set_h_size_flags(Control::SIZE_EXPAND_FILL);
control->set_v_size_flags(Control::SIZE_EXPAND_FILL);
- scroll_container->add_child(control);
+ scroll_container->add_child(control, false, INTERNAL_MODE_FRONT);
control->connect("draw", callable_mp(this, &PopupMenu::_draw_items));
connect("window_input", callable_mp(this, &PopupMenu::gui_input));
@@ -1713,13 +1713,13 @@ PopupMenu::PopupMenu() {
submenu_timer->set_wait_time(0.3);
submenu_timer->set_one_shot(true);
submenu_timer->connect("timeout", callable_mp(this, &PopupMenu::_submenu_timeout));
- add_child(submenu_timer);
+ add_child(submenu_timer, false, INTERNAL_MODE_FRONT);
minimum_lifetime_timer = memnew(Timer);
minimum_lifetime_timer->set_wait_time(0.3);
minimum_lifetime_timer->set_one_shot(true);
minimum_lifetime_timer->connect("timeout", callable_mp(this, &PopupMenu::_minimum_lifetime_timeout));
- add_child(minimum_lifetime_timer);
+ add_child(minimum_lifetime_timer, false, INTERNAL_MODE_FRONT);
}
PopupMenu::~PopupMenu() {
diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp
index b7519af4aa..fbc67d8a24 100644
--- a/scene/gui/rich_text_label.cpp
+++ b/scene/gui/rich_text_label.cpp
@@ -4361,7 +4361,7 @@ RichTextLabel::RichTextLabel() {
current_frame = main;
vscroll = memnew(VScrollBar);
- add_child(vscroll);
+ add_child(vscroll, false, INTERNAL_MODE_FRONT);
vscroll->set_drag_node(String(".."));
vscroll->set_step(1);
vscroll->set_anchor_and_offset(SIDE_TOP, ANCHOR_BEGIN, 0);
diff --git a/scene/gui/scroll_container.cpp b/scene/gui/scroll_container.cpp
index b5daa8b3f1..0c051f61e2 100644
--- a/scene/gui/scroll_container.cpp
+++ b/scene/gui/scroll_container.cpp
@@ -228,9 +228,6 @@ void ScrollContainer::_update_scrollbar_position() {
v_scroll->set_anchor_and_offset(SIDE_TOP, ANCHOR_BEGIN, 0);
v_scroll->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, 0);
- h_scroll->raise();
- v_scroll->raise();
-
_updating_scrollbars = false;
}
@@ -618,12 +615,12 @@ void ScrollContainer::_bind_methods() {
ScrollContainer::ScrollContainer() {
h_scroll = memnew(HScrollBar);
h_scroll->set_name("_h_scroll");
- add_child(h_scroll);
+ add_child(h_scroll, false, INTERNAL_MODE_BACK);
h_scroll->connect("value_changed", callable_mp(this, &ScrollContainer::_scroll_moved));
v_scroll = memnew(VScrollBar);
v_scroll->set_name("_v_scroll");
- add_child(v_scroll);
+ add_child(v_scroll, false, INTERNAL_MODE_BACK);
v_scroll->connect("value_changed", callable_mp(this, &ScrollContainer::_scroll_moved));
deadzone = GLOBAL_GET("gui/common/default_scroll_deadzone");
diff --git a/scene/gui/spin_box.cpp b/scene/gui/spin_box.cpp
index 65a3eb3adf..1074d0d8a0 100644
--- a/scene/gui/spin_box.cpp
+++ b/scene/gui/spin_box.cpp
@@ -278,7 +278,7 @@ void SpinBox::_bind_methods() {
SpinBox::SpinBox() {
line_edit = memnew(LineEdit);
- add_child(line_edit);
+ add_child(line_edit, false, INTERNAL_MODE_FRONT);
line_edit->set_anchors_and_offsets_preset(Control::PRESET_WIDE);
line_edit->set_mouse_filter(MOUSE_FILTER_PASS);
@@ -291,5 +291,5 @@ SpinBox::SpinBox() {
range_click_timer = memnew(Timer);
range_click_timer->connect("timeout", callable_mp(this, &SpinBox::_range_click_timeout));
- add_child(range_click_timer);
+ add_child(range_click_timer, false, INTERNAL_MODE_FRONT);
}
diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp
index 5884d2a854..481e211eb3 100644
--- a/scene/gui/tab_container.cpp
+++ b/scene/gui/tab_container.cpp
@@ -905,7 +905,7 @@ void TabContainer::drop_data(const Point2 &p_point, const Variant &p_data) {
if (from_tabc && from_tabc->get_tabs_rearrange_group() == get_tabs_rearrange_group()) {
Control *moving_tabc = from_tabc->get_tab_control(tab_from_id);
from_tabc->remove_child(moving_tabc);
- add_child(moving_tabc);
+ add_child(moving_tabc, false, INTERNAL_MODE_FRONT);
if (hover_now < 0) {
hover_now = get_tab_count() - 1;
}
diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp
index a731f80741..7e64c13b37 100644
--- a/scene/gui/text_edit.cpp
+++ b/scene/gui/text_edit.cpp
@@ -5004,7 +5004,7 @@ void TextEdit::_paste_internal() {
void TextEdit::_generate_context_menu() {
if (!menu) {
menu = memnew(PopupMenu);
- add_child(menu);
+ add_child(menu, false, INTERNAL_MODE_FRONT);
menu_dir = memnew(PopupMenu);
menu_dir->set_name("DirMenu");
@@ -5012,7 +5012,7 @@ void TextEdit::_generate_context_menu() {
menu_dir->add_radio_check_item(RTR("Auto-detect direction"), MENU_DIR_AUTO);
menu_dir->add_radio_check_item(RTR("Left-to-right"), MENU_DIR_LTR);
menu_dir->add_radio_check_item(RTR("Right-to-left"), MENU_DIR_RTL);
- menu->add_child(menu_dir);
+ menu->add_child(menu_dir, false, INTERNAL_MODE_FRONT);
menu_ctl = memnew(PopupMenu);
menu_ctl->set_name("CTLMenu");
@@ -5034,7 +5034,7 @@ void TextEdit::_generate_context_menu() {
menu_ctl->add_item(RTR("Zero width non-joiner (ZWNJ)"), MENU_INSERT_ZWNJ);
menu_ctl->add_item(RTR("Word joiner (WJ)"), MENU_INSERT_WJ);
menu_ctl->add_item(RTR("Soft hyphen (SHY)"), MENU_INSERT_SHY);
- menu->add_child(menu_ctl);
+ menu->add_child(menu_ctl, false, INTERNAL_MODE_FRONT);
menu->connect("id_pressed", callable_mp(this, &TextEdit::menu_option));
menu_dir->connect("id_pressed", callable_mp(this, &TextEdit::menu_option));
@@ -5948,8 +5948,8 @@ TextEdit::TextEdit() {
h_scroll = memnew(HScrollBar);
v_scroll = memnew(VScrollBar);
- add_child(h_scroll);
- add_child(v_scroll);
+ add_child(h_scroll, false, INTERNAL_MODE_FRONT);
+ add_child(v_scroll, false, INTERNAL_MODE_FRONT);
h_scroll->connect("value_changed", callable_mp(this, &TextEdit::_scroll_moved));
v_scroll->connect("value_changed", callable_mp(this, &TextEdit::_scroll_moved));
@@ -5958,19 +5958,19 @@ TextEdit::TextEdit() {
/* Caret. */
caret_blink_timer = memnew(Timer);
- add_child(caret_blink_timer);
+ add_child(caret_blink_timer, false, INTERNAL_MODE_FRONT);
caret_blink_timer->set_wait_time(0.65);
caret_blink_timer->connect("timeout", callable_mp(this, &TextEdit::_toggle_draw_caret));
set_caret_blink_enabled(false);
/* Selection. */
click_select_held = memnew(Timer);
- add_child(click_select_held);
+ add_child(click_select_held, false, INTERNAL_MODE_FRONT);
click_select_held->set_wait_time(0.05);
click_select_held->connect("timeout", callable_mp(this, &TextEdit::_click_selection_held));
idle_detect = memnew(Timer);
- add_child(idle_detect);
+ add_child(idle_detect, false, INTERNAL_MODE_FRONT);
idle_detect->set_one_shot(true);
idle_detect->set_wait_time(GLOBAL_GET("gui/timers/text_edit_idle_detect_sec"));
idle_detect->connect("timeout", callable_mp(this, &TextEdit::_push_current_op));
diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp
index 3e46e2749b..cb990892ed 100644
--- a/scene/gui/tree.cpp
+++ b/scene/gui/tree.cpp
@@ -4795,12 +4795,11 @@ Tree::Tree() {
popup_menu = memnew(PopupMenu);
popup_menu->hide();
- add_child(popup_menu);
- // popup_menu->set_as_top_level(true);
+ add_child(popup_menu, false, INTERNAL_MODE_FRONT);
popup_editor = memnew(Popup);
popup_editor->set_wrap_controls(true);
- add_child(popup_editor);
+ add_child(popup_editor, false, INTERNAL_MODE_FRONT);
popup_editor_vb = memnew(VBoxContainer);
popup_editor->add_child(popup_editor_vb);
popup_editor_vb->add_theme_constant_override("separation", 0);
@@ -4818,12 +4817,12 @@ Tree::Tree() {
h_scroll = memnew(HScrollBar);
v_scroll = memnew(VScrollBar);
- add_child(h_scroll);
- add_child(v_scroll);
+ add_child(h_scroll, false, INTERNAL_MODE_FRONT);
+ add_child(v_scroll, false, INTERNAL_MODE_FRONT);
range_click_timer = memnew(Timer);
range_click_timer->connect("timeout", callable_mp(this, &Tree::_range_click_timeout));
- add_child(range_click_timer);
+ add_child(range_click_timer, false, INTERNAL_MODE_FRONT);
h_scroll->connect("value_changed", callable_mp(this, &Tree::_scroll_moved));
v_scroll->connect("value_changed", callable_mp(this, &Tree::_scroll_moved));
diff --git a/scene/main/node.cpp b/scene/main/node.cpp
index cafa4fa2d2..f2a2648140 100644
--- a/scene/main/node.cpp
+++ b/scene/main/node.cpp
@@ -48,6 +48,7 @@
#include <stdint.h>
VARIANT_ENUM_CAST(Node::ProcessMode);
+VARIANT_ENUM_CAST(Node::InternalMode);
int Node::orphan_node_count = 0;
@@ -291,14 +292,40 @@ void Node::_propagate_exit_tree() {
void Node::move_child(Node *p_child, int p_pos) {
ERR_FAIL_NULL(p_child);
- ERR_FAIL_INDEX_MSG(p_pos, data.children.size() + 1, vformat("Invalid new child position: %d.", p_pos));
ERR_FAIL_COND_MSG(p_child->data.parent != this, "Child is not a child of this node.");
+
+ // We need to check whether node is internal and move it only in the relevant node range.
+ if (p_child->_is_internal_front()) {
+ ERR_FAIL_INDEX_MSG(p_pos, data.internal_children_front, vformat("Invalid new child position: %d. Child is internal.", p_pos));
+ _move_child(p_child, p_pos);
+ } else if (p_child->_is_internal_back()) {
+ ERR_FAIL_INDEX_MSG(p_pos, data.internal_children_back, vformat("Invalid new child position: %d. Child is internal.", p_pos));
+ _move_child(p_child, data.children.size() - data.internal_children_back + p_pos);
+ } else {
+ ERR_FAIL_INDEX_MSG(p_pos, data.children.size() + 1 - data.internal_children_front - data.internal_children_back, vformat("Invalid new child position: %d.", p_pos));
+ _move_child(p_child, p_pos + data.internal_children_front);
+ }
+}
+
+void Node::_move_child(Node *p_child, int p_pos, bool p_ignore_end) {
ERR_FAIL_COND_MSG(data.blocked > 0, "Parent node is busy setting up children, move_child() failed. Consider using call_deferred(\"move_child\") instead (or \"popup\" if this is from a popup).");
// Specifying one place beyond the end
// means the same as moving to the last position
- if (p_pos == data.children.size()) {
- p_pos--;
+ if (!p_ignore_end) { // p_ignore_end is a little hack to make back internal children work properly.
+ if (p_child->_is_internal_front()) {
+ if (p_pos == data.internal_children_front) {
+ p_pos--;
+ }
+ } else if (p_child->_is_internal_back()) {
+ if (p_pos == data.children.size()) {
+ p_pos--;
+ }
+ } else {
+ if (p_pos == data.children.size() - data.internal_children_back) {
+ p_pos--;
+ }
+ }
}
if (p_child->data.pos == p_pos) {
@@ -339,7 +366,14 @@ void Node::raise() {
return;
}
- data.parent->move_child(this, data.parent->data.children.size() - 1);
+ // Internal children move within a different index range.
+ if (_is_internal_front()) {
+ data.parent->move_child(this, data.parent->data.internal_children_front - 1);
+ } else if (_is_internal_back()) {
+ data.parent->move_child(this, data.parent->data.internal_children_back - 1);
+ } else {
+ data.parent->move_child(this, data.parent->get_child_count(false) - 1);
+ }
}
void Node::add_child_notify(Node *p_child) {
@@ -483,24 +517,24 @@ void Node::_propagate_process_owner(Node *p_owner, int p_pause_notification, int
}
}
-void Node::set_network_master(int p_peer_id, bool p_recursive) {
- data.network_master = p_peer_id;
+void Node::set_network_authority(int p_peer_id, bool p_recursive) {
+ data.network_authority = p_peer_id;
if (p_recursive) {
for (int i = 0; i < data.children.size(); i++) {
- data.children[i]->set_network_master(p_peer_id, true);
+ data.children[i]->set_network_authority(p_peer_id, true);
}
}
}
-int Node::get_network_master() const {
- return data.network_master;
+int Node::get_network_authority() const {
+ return data.network_authority;
}
-bool Node::is_network_master() const {
+bool Node::is_network_authority() const {
ERR_FAIL_COND_V(!is_inside_tree(), false);
- return get_multiplayer()->get_network_unique_id() == data.network_master;
+ return get_multiplayer()->get_network_unique_id() == data.network_authority;
}
/***** RPC CONFIG ********/
@@ -1058,6 +1092,10 @@ void Node::_add_child_nocheck(Node *p_child, const StringName &p_name) {
p_child->data.pos = data.children.size();
data.children.push_back(p_child);
p_child->data.parent = this;
+
+ if (data.internal_children_back > 0) {
+ _move_child(p_child, data.children.size() - data.internal_children_back - 1);
+ }
p_child->notification(NOTIFICATION_PARENTED);
if (data.tree) {
@@ -1070,7 +1108,7 @@ void Node::_add_child_nocheck(Node *p_child, const StringName &p_name) {
add_child_notify(p_child);
}
-void Node::add_child(Node *p_child, bool p_legible_unique_name) {
+void Node::add_child(Node *p_child, bool p_legible_unique_name, InternalMode p_internal) {
ERR_FAIL_NULL(p_child);
ERR_FAIL_COND_MSG(p_child == this, vformat("Can't add child '%s' to itself.", p_child->get_name())); // adding to itself!
ERR_FAIL_COND_MSG(p_child->data.parent, vformat("Can't add child '%s' to '%s', already has a parent '%s'.", p_child->get_name(), get_name(), p_child->data.parent->get_name())); //Fail if node has a parent
@@ -1079,19 +1117,35 @@ void Node::add_child(Node *p_child, bool p_legible_unique_name) {
#endif
ERR_FAIL_COND_MSG(data.blocked > 0, "Parent node is busy setting up children, add_node() failed. Consider using call_deferred(\"add_child\", child) instead.");
- /* Validate name */
_validate_child_name(p_child, p_legible_unique_name);
-
_add_child_nocheck(p_child, p_child->data.name);
+
+ if (p_internal == INTERNAL_MODE_FRONT) {
+ _move_child(p_child, data.internal_children_front);
+ data.internal_children_front++;
+ } else if (p_internal == INTERNAL_MODE_BACK) {
+ if (data.internal_children_back > 0) {
+ _move_child(p_child, data.children.size() - 1, true);
+ }
+ data.internal_children_back++;
+ }
}
void Node::add_sibling(Node *p_sibling, bool p_legible_unique_name) {
ERR_FAIL_NULL(p_sibling);
+ ERR_FAIL_NULL(data.parent);
ERR_FAIL_COND_MSG(p_sibling == this, vformat("Can't add sibling '%s' to itself.", p_sibling->get_name())); // adding to itself!
ERR_FAIL_COND_MSG(data.blocked > 0, "Parent node is busy setting up children, add_sibling() failed. Consider using call_deferred(\"add_sibling\", sibling) instead.");
- get_parent()->add_child(p_sibling, p_legible_unique_name);
- get_parent()->move_child(p_sibling, this->get_index() + 1);
+ InternalMode internal = INTERNAL_MODE_DISABLED;
+ if (_is_internal_front()) { // The sibling will have the same internal status.
+ internal = INTERNAL_MODE_FRONT;
+ } else if (_is_internal_back()) {
+ internal = INTERNAL_MODE_BACK;
+ }
+
+ data.parent->add_child(p_sibling, p_legible_unique_name, internal);
+ data.parent->_move_child(p_sibling, get_index() + 1);
}
void Node::_propagate_validate_owner() {
@@ -1145,7 +1199,12 @@ void Node::remove_child(Node *p_child) {
ERR_FAIL_COND_MSG(idx == -1, vformat("Cannot remove child node '%s' as it is not a child of this node.", p_child->get_name()));
//ERR_FAIL_COND( p_child->data.blocked > 0 );
- //if (data.scene) { does not matter
+ // If internal child, update the counter.
+ if (p_child->_is_internal_front()) {
+ data.internal_children_front--;
+ } else if (p_child->_is_internal_back()) {
+ data.internal_children_back--;
+ }
p_child->_set_tree(nullptr);
//}
@@ -1175,17 +1234,29 @@ void Node::remove_child(Node *p_child) {
}
}
-int Node::get_child_count() const {
- return data.children.size();
+int Node::get_child_count(bool p_include_internal) const {
+ if (p_include_internal) {
+ return data.children.size();
+ } else {
+ return data.children.size() - data.internal_children_front - data.internal_children_back;
+ }
}
-Node *Node::get_child(int p_index) const {
- if (p_index < 0) {
- p_index += data.children.size();
+Node *Node::get_child(int p_index, bool p_include_internal) const {
+ if (p_include_internal) {
+ if (p_index < 0) {
+ p_index += data.children.size();
+ }
+ ERR_FAIL_INDEX_V(p_index, data.children.size(), nullptr);
+ return data.children[p_index];
+ } else {
+ if (p_index < 0) {
+ p_index += data.children.size() - data.internal_children_front - data.internal_children_back;
+ }
+ ERR_FAIL_INDEX_V(p_index, data.children.size() - data.internal_children_front - data.internal_children_back, nullptr);
+ p_index += data.internal_children_front;
+ return data.children[p_index];
}
- ERR_FAIL_INDEX_V(p_index, data.children.size(), nullptr);
-
- return data.children[p_index];
}
Node *Node::_get_child_by_name(const StringName &p_name) const {
@@ -1717,7 +1788,13 @@ void Node::_propagate_replace_owner(Node *p_owner, Node *p_by_owner) {
data.blocked--;
}
-int Node::get_index() const {
+int Node::get_index(bool p_include_internal) const {
+ // p_include_internal = false doesn't make sense if the node is internal.
+ ERR_FAIL_COND_V_MSG(!p_include_internal && (_is_internal_front() || _is_internal_back()), -1, "Node is internal. Can't get index with 'include_internal' being false.");
+
+ if (data.parent && !p_include_internal) {
+ return data.pos - data.parent->data.internal_children_front;
+ }
return data.pos;
}
@@ -2429,12 +2506,12 @@ void Node::queue_delete() {
}
}
-TypedArray<Node> Node::_get_children() const {
+TypedArray<Node> Node::_get_children(bool p_include_internal) const {
TypedArray<Node> arr;
- int cc = get_child_count();
+ int cc = get_child_count(p_include_internal);
arr.resize(cc);
for (int i = 0; i < cc; i++) {
- arr[i] = get_child(i);
+ arr[i] = get_child(i, p_include_internal);
}
return arr;
@@ -2581,11 +2658,11 @@ void Node::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_name", "name"), &Node::set_name);
ClassDB::bind_method(D_METHOD("get_name"), &Node::get_name);
- ClassDB::bind_method(D_METHOD("add_child", "node", "legible_unique_name"), &Node::add_child, DEFVAL(false));
+ ClassDB::bind_method(D_METHOD("add_child", "node", "legible_unique_name", "internal"), &Node::add_child, DEFVAL(false), DEFVAL(0));
ClassDB::bind_method(D_METHOD("remove_child", "node"), &Node::remove_child);
- ClassDB::bind_method(D_METHOD("get_child_count"), &Node::get_child_count);
- ClassDB::bind_method(D_METHOD("get_children"), &Node::_get_children);
- ClassDB::bind_method(D_METHOD("get_child", "idx"), &Node::get_child);
+ ClassDB::bind_method(D_METHOD("get_child_count", "include_internal"), &Node::get_child_count, DEFVAL(false)); // Note that the default value bound for include_internal is false, while the method is declared with true. This is because internal nodes are irrelevant for GDSCript.
+ ClassDB::bind_method(D_METHOD("get_children", "include_internal"), &Node::_get_children, DEFVAL(false));
+ ClassDB::bind_method(D_METHOD("get_child", "idx", "include_internal"), &Node::get_child, DEFVAL(false));
ClassDB::bind_method(D_METHOD("has_node", "path"), &Node::has_node);
ClassDB::bind_method(D_METHOD("get_node", "path"), &Node::get_node);
ClassDB::bind_method(D_METHOD("get_node_or_null", "path"), &Node::get_node_or_null);
@@ -2609,7 +2686,7 @@ void Node::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_owner", "owner"), &Node::set_owner);
ClassDB::bind_method(D_METHOD("get_owner"), &Node::get_owner);
ClassDB::bind_method(D_METHOD("remove_and_skip"), &Node::remove_and_skip);
- ClassDB::bind_method(D_METHOD("get_index"), &Node::get_index);
+ ClassDB::bind_method(D_METHOD("get_index", "include_internal"), &Node::get_index, DEFVAL(false));
ClassDB::bind_method(D_METHOD("print_tree"), &Node::print_tree);
ClassDB::bind_method(D_METHOD("print_tree_pretty"), &Node::print_tree_pretty);
ClassDB::bind_method(D_METHOD("set_filename", "filename"), &Node::set_filename);
@@ -2661,10 +2738,10 @@ void Node::_bind_methods() {
ClassDB::bind_method(D_METHOD("request_ready"), &Node::request_ready);
- ClassDB::bind_method(D_METHOD("set_network_master", "id", "recursive"), &Node::set_network_master, DEFVAL(true));
- ClassDB::bind_method(D_METHOD("get_network_master"), &Node::get_network_master);
+ ClassDB::bind_method(D_METHOD("set_network_authority", "id", "recursive"), &Node::set_network_authority, DEFVAL(true));
+ ClassDB::bind_method(D_METHOD("get_network_authority"), &Node::get_network_authority);
- ClassDB::bind_method(D_METHOD("is_network_master"), &Node::is_network_master);
+ ClassDB::bind_method(D_METHOD("is_network_authority"), &Node::is_network_authority);
ClassDB::bind_method(D_METHOD("get_multiplayer"), &Node::get_multiplayer);
ClassDB::bind_method(D_METHOD("get_custom_multiplayer"), &Node::get_custom_multiplayer);
@@ -2747,6 +2824,10 @@ void Node::_bind_methods() {
BIND_ENUM_CONSTANT(DUPLICATE_SCRIPTS);
BIND_ENUM_CONSTANT(DUPLICATE_USE_INSTANCING);
+ BIND_ENUM_CONSTANT(INTERNAL_MODE_DISABLED);
+ BIND_ENUM_CONSTANT(INTERNAL_MODE_FRONT);
+ BIND_ENUM_CONSTANT(INTERNAL_MODE_BACK);
+
ADD_SIGNAL(MethodInfo("ready"));
ADD_SIGNAL(MethodInfo("renamed"));
ADD_SIGNAL(MethodInfo("tree_entered"));
diff --git a/scene/main/node.h b/scene/main/node.h
index a07accba2f..d0246ebe84 100644
--- a/scene/main/node.h
+++ b/scene/main/node.h
@@ -73,6 +73,12 @@ public:
NAME_CASING_SNAKE_CASE
};
+ enum InternalMode {
+ INTERNAL_MODE_DISABLED,
+ INTERNAL_MODE_FRONT,
+ INTERNAL_MODE_BACK,
+ };
+
struct Comparator {
bool operator()(const Node *p_a, const Node *p_b) const { return p_b->is_greater_than(p_a); }
};
@@ -97,6 +103,8 @@ private:
Node *parent = nullptr;
Node *owner = nullptr;
Vector<Node *> children;
+ int internal_children_front = 0;
+ int internal_children_back = 0;
int pos = -1;
int depth = -1;
int blocked = 0; // Safeguard that throws an error when attempting to modify the tree in a harmful way while being traversed.
@@ -119,7 +127,7 @@ private:
ProcessMode process_mode = PROCESS_MODE_INHERIT;
Node *process_owner = nullptr;
- int network_master = 1; // Server by default.
+ int network_authority = 1; // Server by default.
Vector<MultiplayerAPI::RPCConfig> rpc_methods;
// Variables used to properly sort the node when processing, ignored otherwise.
@@ -172,12 +180,15 @@ private:
void _duplicate_signals(const Node *p_original, Node *p_copy) const;
Node *_duplicate(int p_flags, Map<const Node *, Node *> *r_duplimap = nullptr) const;
- TypedArray<Node> _get_children() const;
+ TypedArray<Node> _get_children(bool p_include_internal = true) const;
Array _get_groups() const;
Variant _rpc_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error);
Variant _rpc_id_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error);
+ _FORCE_INLINE_ bool _is_internal_front() const { return data.parent && data.pos < data.parent->data.internal_children_front; }
+ _FORCE_INLINE_ bool _is_internal_back() const { return data.parent && data.pos >= data.parent->data.children.size() - data.parent->data.internal_children_back; }
+
friend class SceneTree;
void _set_tree(SceneTree *p_tree);
@@ -284,12 +295,12 @@ public:
StringName get_name() const;
void set_name(const String &p_name);
- void add_child(Node *p_child, bool p_legible_unique_name = false);
+ void add_child(Node *p_child, bool p_legible_unique_name = false, InternalMode p_internal = INTERNAL_MODE_DISABLED);
void add_sibling(Node *p_sibling, bool p_legible_unique_name = false);
void remove_child(Node *p_child);
- int get_child_count() const;
- Node *get_child(int p_index) const;
+ int get_child_count(bool p_include_internal = true) const;
+ Node *get_child(int p_index, bool p_include_internal = true) const;
bool has_node(const NodePath &p_path) const;
Node *get_node(const NodePath &p_path) const;
Node *get_node_or_null(const NodePath &p_path) const;
@@ -327,6 +338,7 @@ public:
int get_persistent_group_count() const;
void move_child(Node *p_child, int p_pos);
+ void _move_child(Node *p_child, int p_pos, bool p_ignore_end = false);
void raise();
void set_owner(Node *p_owner);
@@ -334,7 +346,7 @@ public:
void get_owned_by(Node *p_by, List<Node *> *p_owned);
void remove_and_skip();
- int get_index() const;
+ int get_index(bool p_include_internal = true) const;
Ref<Tween> create_tween();
@@ -450,9 +462,9 @@ public:
bool is_displayed_folded() const;
/* NETWORK */
- void set_network_master(int p_peer_id, bool p_recursive = true);
- int get_network_master() const;
- bool is_network_master() const;
+ void set_network_authority(int p_peer_id, bool p_recursive = true);
+ int get_network_authority() const;
+ bool is_network_authority() const;
uint16_t rpc_config(const StringName &p_method, MultiplayerAPI::RPCMode p_rpc_mode, MultiplayerPeer::TransferMode p_transfer_mode, int p_channel = 0); // config a local method for RPC
Vector<MultiplayerAPI::RPCConfig> get_node_rpc_methods() const;