Merge branch 'godotengine:master' into master_texture_array_normal_added
This commit is contained in:
commit
24066b3c7a
38 changed files with 691 additions and 195 deletions
|
@ -45,6 +45,8 @@ Error HTTPClientTCP::connect_to_host(const String &p_host, int p_port, bool p_ss
|
||||||
conn_port = p_port;
|
conn_port = p_port;
|
||||||
conn_host = p_host;
|
conn_host = p_host;
|
||||||
|
|
||||||
|
ip_candidates.clear();
|
||||||
|
|
||||||
ssl = p_ssl;
|
ssl = p_ssl;
|
||||||
ssl_verify_host = p_verify_host;
|
ssl_verify_host = p_verify_host;
|
||||||
|
|
||||||
|
@ -234,6 +236,7 @@ void HTTPClientTCP::close() {
|
||||||
resolving = IP::RESOLVER_INVALID_ID;
|
resolving = IP::RESOLVER_INVALID_ID;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ip_candidates.clear();
|
||||||
response_headers.clear();
|
response_headers.clear();
|
||||||
response_str.clear();
|
response_str.clear();
|
||||||
body_size = -1;
|
body_size = -1;
|
||||||
|
@ -256,10 +259,17 @@ Error HTTPClientTCP::poll() {
|
||||||
return OK; // Still resolving
|
return OK; // Still resolving
|
||||||
|
|
||||||
case IP::RESOLVER_STATUS_DONE: {
|
case IP::RESOLVER_STATUS_DONE: {
|
||||||
IPAddress host = IP::get_singleton()->get_resolve_item_address(resolving);
|
ip_candidates = IP::get_singleton()->get_resolve_item_addresses(resolving);
|
||||||
Error err = tcp_connection->connect_to_host(host, conn_port);
|
|
||||||
IP::get_singleton()->erase_resolve_item(resolving);
|
IP::get_singleton()->erase_resolve_item(resolving);
|
||||||
resolving = IP::RESOLVER_INVALID_ID;
|
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) {
|
if (err) {
|
||||||
status = STATUS_CANT_CONNECT;
|
status = STATUS_CANT_CONNECT;
|
||||||
return err;
|
return err;
|
||||||
|
@ -313,6 +323,7 @@ Error HTTPClientTCP::poll() {
|
||||||
if (ssl->get_status() == StreamPeerSSL::STATUS_CONNECTED) {
|
if (ssl->get_status() == StreamPeerSSL::STATUS_CONNECTED) {
|
||||||
// Handshake has been successful
|
// Handshake has been successful
|
||||||
handshaking = false;
|
handshaking = false;
|
||||||
|
ip_candidates.clear();
|
||||||
status = STATUS_CONNECTED;
|
status = STATUS_CONNECTED;
|
||||||
return OK;
|
return OK;
|
||||||
} else if (ssl->get_status() != StreamPeerSSL::STATUS_HANDSHAKING) {
|
} 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
|
// ... we will need to poll more for handshake to finish
|
||||||
} else {
|
} else {
|
||||||
|
ip_candidates.clear();
|
||||||
status = STATUS_CONNECTED;
|
status = STATUS_CONNECTED;
|
||||||
}
|
}
|
||||||
return OK;
|
return OK;
|
||||||
} break;
|
} break;
|
||||||
case StreamPeerTCP::STATUS_ERROR:
|
case StreamPeerTCP::STATUS_ERROR:
|
||||||
case StreamPeerTCP::STATUS_NONE: {
|
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();
|
close();
|
||||||
status = STATUS_CANT_CONNECT;
|
status = STATUS_CANT_CONNECT;
|
||||||
return ERR_CANT_CONNECT;
|
return err;
|
||||||
} break;
|
} break;
|
||||||
}
|
}
|
||||||
} break;
|
} break;
|
||||||
|
|
|
@ -37,6 +37,7 @@ class HTTPClientTCP : public HTTPClient {
|
||||||
private:
|
private:
|
||||||
Status status = STATUS_DISCONNECTED;
|
Status status = STATUS_DISCONNECTED;
|
||||||
IP::ResolverID resolving = IP::RESOLVER_INVALID_ID;
|
IP::ResolverID resolving = IP::RESOLVER_INVALID_ID;
|
||||||
|
Array ip_candidates;
|
||||||
int conn_port = -1;
|
int conn_port = -1;
|
||||||
String conn_host;
|
String conn_host;
|
||||||
bool ssl = false;
|
bool ssl = false;
|
||||||
|
|
|
@ -96,14 +96,11 @@ _FORCE_INLINE_ bool _can_call_mode(Node *p_node, MultiplayerAPI::RPCMode mode, i
|
||||||
case MultiplayerAPI::RPC_MODE_DISABLED: {
|
case MultiplayerAPI::RPC_MODE_DISABLED: {
|
||||||
return false;
|
return false;
|
||||||
} break;
|
} break;
|
||||||
case MultiplayerAPI::RPC_MODE_REMOTE: {
|
case MultiplayerAPI::RPC_MODE_ANY: {
|
||||||
return true;
|
return true;
|
||||||
} break;
|
} break;
|
||||||
case MultiplayerAPI::RPC_MODE_MASTER: {
|
case MultiplayerAPI::RPC_MODE_AUTHORITY: {
|
||||||
return p_node->is_network_master();
|
return !p_node->is_network_authority() && p_remote_id == p_node->get_network_authority();
|
||||||
} break;
|
|
||||||
case MultiplayerAPI::RPC_MODE_PUPPET: {
|
|
||||||
return !p_node->is_network_master() && p_remote_id == p_node->get_network_master();
|
|
||||||
} break;
|
} 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.
|
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() {
|
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: {
|
case NETWORK_COMMAND_DESPAWN: {
|
||||||
replicator->process_spawn_despawn(p_from, p_packet, p_packet_len, false);
|
replicator->process_spawn_despawn(p_from, p_packet, p_packet_len, false);
|
||||||
} break;
|
} 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());
|
ERR_FAIL_COND(config.name == StringName());
|
||||||
|
|
||||||
bool can_call = _can_call_mode(p_node, config.rpc_mode, p_from);
|
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;
|
int argc = 0;
|
||||||
bool byte_only = false;
|
bool byte_only = false;
|
||||||
|
@ -1132,9 +1135,8 @@ void MultiplayerAPI::_bind_methods() {
|
||||||
ADD_SIGNAL(MethodInfo("server_disconnected"));
|
ADD_SIGNAL(MethodInfo("server_disconnected"));
|
||||||
|
|
||||||
BIND_ENUM_CONSTANT(RPC_MODE_DISABLED);
|
BIND_ENUM_CONSTANT(RPC_MODE_DISABLED);
|
||||||
BIND_ENUM_CONSTANT(RPC_MODE_REMOTE);
|
BIND_ENUM_CONSTANT(RPC_MODE_ANY);
|
||||||
BIND_ENUM_CONSTANT(RPC_MODE_MASTER);
|
BIND_ENUM_CONSTANT(RPC_MODE_AUTHORITY);
|
||||||
BIND_ENUM_CONSTANT(RPC_MODE_PUPPET);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MultiplayerAPI::MultiplayerAPI() {
|
MultiplayerAPI::MultiplayerAPI() {
|
||||||
|
|
|
@ -43,9 +43,8 @@ class MultiplayerAPI : public RefCounted {
|
||||||
public:
|
public:
|
||||||
enum RPCMode {
|
enum RPCMode {
|
||||||
RPC_MODE_DISABLED, // No rpc for this method, calls to this will be blocked (default)
|
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_ANY, // Any peer can call this rpc()
|
||||||
RPC_MODE_MASTER, // Using rpc() on it will call method on wherever the master is, be it local or remote
|
RPC_MODE_AUTHORITY, // Only the node's network authority (server by default) can call this rpc()
|
||||||
RPC_MODE_PUPPET, // Using rpc() on it will call method for all puppets
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct RPCConfig {
|
struct RPCConfig {
|
||||||
|
@ -74,6 +73,7 @@ public:
|
||||||
NETWORK_COMMAND_RAW,
|
NETWORK_COMMAND_RAW,
|
||||||
NETWORK_COMMAND_SPAWN,
|
NETWORK_COMMAND_SPAWN,
|
||||||
NETWORK_COMMAND_DESPAWN,
|
NETWORK_COMMAND_DESPAWN,
|
||||||
|
NETWORK_COMMAND_SYNC, // This is the max we can have. We should optmize simplify/confirm, possibly spawn/despawn.
|
||||||
};
|
};
|
||||||
|
|
||||||
enum NetworkNodeIdCompression {
|
enum NetworkNodeIdCompression {
|
||||||
|
|
|
@ -38,6 +38,140 @@
|
||||||
if (packet_cache.size() < m_amount) \
|
if (packet_cache.size() < m_amount) \
|
||||||
packet_cache.resize(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) {
|
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(p_spawn && !p_obj, ERR_INVALID_PARAMETER);
|
||||||
ERR_FAIL_COND_V(!replications.has(p_scene_id), 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();
|
Node *node = scene->instantiate();
|
||||||
ERR_FAIL_COND(!node);
|
ERR_FAIL_COND(!node);
|
||||||
replicated_nodes[node->get_instance_id()] = p_scene_id;
|
replicated_nodes[node->get_instance_id()] = p_scene_id;
|
||||||
|
_track(p_scene_id, node);
|
||||||
int size;
|
int size;
|
||||||
_decode_state(cfg.properties, node, &p_packet[ofs], p_packet_len - ofs, size, is_raw);
|
_decode_state(cfg.properties, node, &p_packet[ofs], p_packet_len - ofs, size, is_raw);
|
||||||
parent->_add_child_nocheck(node, name);
|
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);
|
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));
|
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);
|
emit_signal(SNAME("despawned"), p_scene_id, node);
|
||||||
|
_untrack(p_scene_id, node);
|
||||||
replicated_nodes.erase(node->get_instance_id());
|
replicated_nodes.erase(node->get_instance_id());
|
||||||
node->queue_delete();
|
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) {
|
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");
|
ERR_FAIL_COND_V_MSG(!p_obj, ERR_INVALID_PARAMETER, "Cannot encode null object");
|
||||||
for (const StringName &prop : p_properties) {
|
for (const StringName &prop : p_properties) {
|
||||||
|
@ -306,6 +473,21 @@ Error MultiplayerReplicator::spawn_config(const ResourceUID::ID &p_id, Replicati
|
||||||
return OK;
|
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) {
|
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 data_size = 0;
|
||||||
int is_raw = false;
|
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) {
|
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));
|
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 SceneConfig &cfg = replications[p_scene_id];
|
||||||
if (cfg.on_spawn_despawn_send.is_valid()) {
|
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) {
|
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));
|
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 SceneConfig &cfg = replications[p_scene_id];
|
||||||
if (cfg.on_spawn_despawn_send.is_valid()) {
|
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);
|
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;
|
PackedByteArray state;
|
||||||
ERR_FAIL_COND_V_MSG(!replications.has(p_scene_id), state, vformat("Spawnable not found: %d", p_scene_id));
|
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];
|
const SceneConfig &cfg = replications[p_scene_id];
|
||||||
int len = 0;
|
int len = 0;
|
||||||
List<Variant> state_vars;
|
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_FAIL_COND_V_MSG(err != OK, state, "Unable to retrieve object state.");
|
||||||
err = _encode_state(state_vars, nullptr, len);
|
err = _encode_state(state_vars, nullptr, len);
|
||||||
ERR_FAIL_COND_V_MSG(err != OK, state, "Unable to encode object state.");
|
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;
|
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));
|
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 SceneConfig &cfg = replications[p_scene_id];
|
||||||
|
const List<StringName> props = p_initial ? cfg.properties : cfg.sync_properties;
|
||||||
int size;
|
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) {
|
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 (p_enter) {
|
||||||
if (cfg.mode == REPLICATION_MODE_SERVER && multiplayer->is_network_server()) {
|
if (cfg.mode == REPLICATION_MODE_SERVER && multiplayer->is_network_server()) {
|
||||||
replicated_nodes[p_node->get_instance_id()] = id;
|
replicated_nodes[p_node->get_instance_id()] = id;
|
||||||
|
_track(id, p_node);
|
||||||
spawn(id, p_node, 0);
|
spawn(id, p_node, 0);
|
||||||
}
|
}
|
||||||
emit_signal(SNAME("replicated_instance_added"), id, p_node);
|
emit_signal(SNAME("replicated_instance_added"), id, p_node);
|
||||||
} else {
|
} else {
|
||||||
if (cfg.mode == REPLICATION_MODE_SERVER && multiplayer->is_network_server() && replicated_nodes.has(p_node->get_instance_id())) {
|
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());
|
replicated_nodes.erase(p_node->get_instance_id());
|
||||||
|
_untrack(id, p_node);
|
||||||
despawn(id, p_node, 0);
|
despawn(id, p_node, 0);
|
||||||
}
|
}
|
||||||
emit_signal(SNAME("replicated_instance_removed"), id, p_node);
|
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() {
|
void MultiplayerReplicator::clear() {
|
||||||
|
tracked_objects.clear();
|
||||||
replicated_nodes.clear();
|
replicated_nodes.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MultiplayerReplicator::_bind_methods() {
|
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("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("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("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_despawn", "peer_id", "scene_id", "data", "path"), &MultiplayerReplicator::send_despawn, DEFVAL(Variant()), DEFVAL(NodePath()));
|
||||||
ClassDB::bind_method(D_METHOD("send_spawn", "peer_id", "scene_id", "data", "path"), &MultiplayerReplicator::send_spawn, DEFVAL(Variant()), DEFVAL(NodePath()));
|
ClassDB::bind_method(D_METHOD("send_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("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("decode_state", "scene_id", "object", "data"), &MultiplayerReplicator::decode_state);
|
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("despawned", PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node")));
|
||||||
ADD_SIGNAL(MethodInfo("spawned", PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node")));
|
ADD_SIGNAL(MethodInfo("spawned", PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node")));
|
||||||
|
|
|
@ -32,6 +32,8 @@
|
||||||
#define MULTIPLAYER_REPLICATOR_H
|
#define MULTIPLAYER_REPLICATOR_H
|
||||||
|
|
||||||
#include "core/io/multiplayer_api.h"
|
#include "core/io/multiplayer_api.h"
|
||||||
|
|
||||||
|
#include "core/templates/hash_map.h"
|
||||||
#include "core/variant/typed_array.h"
|
#include "core/variant/typed_array.h"
|
||||||
|
|
||||||
class MultiplayerReplicator : public Object {
|
class MultiplayerReplicator : public Object {
|
||||||
|
@ -40,6 +42,7 @@ class MultiplayerReplicator : public Object {
|
||||||
public:
|
public:
|
||||||
enum {
|
enum {
|
||||||
SPAWN_CMD_OFFSET = 9,
|
SPAWN_CMD_OFFSET = 9,
|
||||||
|
SYNC_CMD_OFFSET = 9,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum ReplicationMode {
|
enum ReplicationMode {
|
||||||
|
@ -50,9 +53,15 @@ public:
|
||||||
|
|
||||||
struct SceneConfig {
|
struct SceneConfig {
|
||||||
ReplicationMode mode;
|
ReplicationMode mode;
|
||||||
|
uint64_t sync_interval = 0;
|
||||||
|
uint64_t sync_last = 0;
|
||||||
|
uint8_t sync_recv = 0;
|
||||||
List<StringName> properties;
|
List<StringName> properties;
|
||||||
|
List<StringName> sync_properties;
|
||||||
Callable on_spawn_despawn_send;
|
Callable on_spawn_despawn_send;
|
||||||
Callable on_spawn_despawn_receive;
|
Callable on_spawn_despawn_receive;
|
||||||
|
Callable on_sync_send;
|
||||||
|
Callable on_sync_receive;
|
||||||
};
|
};
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
@ -63,31 +72,52 @@ private:
|
||||||
Vector<uint8_t> packet_cache;
|
Vector<uint8_t> packet_cache;
|
||||||
Map<ResourceUID::ID, SceneConfig> replications;
|
Map<ResourceUID::ID, SceneConfig> replications;
|
||||||
Map<ObjectID, ResourceUID::ID> replicated_nodes;
|
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 _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 _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 _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);
|
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);
|
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);
|
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:
|
public:
|
||||||
void clear();
|
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_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 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 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_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());
|
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
|
// Used by MultiplayerAPI
|
||||||
void spawn_all(int p_peer);
|
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_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 scene_enter_exit_notify(const String &p_scene, Node *p_node, bool p_enter);
|
||||||
|
void poll();
|
||||||
|
|
||||||
MultiplayerReplicator(MultiplayerAPI *p_multiplayer) {
|
MultiplayerReplicator(MultiplayerAPI *p_multiplayer) {
|
||||||
multiplayer = p_multiplayer;
|
multiplayer = p_multiplayer;
|
||||||
|
|
|
@ -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.
|
[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>
|
||||||
<member name="network_peer" type="MultiplayerPeer" setter="set_network_peer" getter="get_network_peer">
|
<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>
|
||||||
<member name="refuse_new_network_connections" type="bool" setter="set_refuse_new_network_connections" getter="is_refusing_new_network_connections" default="false">
|
<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.
|
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">
|
<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.
|
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>
|
||||||
<constant name="RPC_MODE_REMOTE" value="1" enum="RPCMode">
|
<constant name="RPC_MODE_ANY" 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.
|
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>
|
||||||
<constant name="RPC_MODE_MASTER" value="2" enum="RPCMode">
|
<constant name="RPC_MODE_AUTHORITY" 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].
|
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>
|
|
||||||
<constant name="RPC_MODE_PUPPET" value="3" enum="RPCMode">
|
|
||||||
Used with [method Node.rpc_config] to set a method to be called or a property to be changed only on puppets for this node. Analogous to the [code]puppet[/code] keyword. Only accepts calls or property changes from the node's network master, see [method Node.set_network_master].
|
|
||||||
</constant>
|
</constant>
|
||||||
</constants>
|
</constants>
|
||||||
</class>
|
</class>
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
<argument index="0" name="scene_id" type="int" />
|
<argument index="0" name="scene_id" type="int" />
|
||||||
<argument index="1" name="object" type="Object" />
|
<argument index="1" name="object" type="Object" />
|
||||||
<argument index="2" name="data" type="PackedByteArray" />
|
<argument index="2" name="data" type="PackedByteArray" />
|
||||||
|
<argument index="3" name="initial" type="bool" default="true" />
|
||||||
<description>
|
<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].
|
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].
|
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="1" name="object" type="Object" />
|
||||||
<argument index="2" name="peer_id" type="int" default="0" />
|
<argument index="2" name="peer_id" type="int" default="0" />
|
||||||
<description>
|
<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>
|
</description>
|
||||||
</method>
|
</method>
|
||||||
<method name="encode_state">
|
<method name="encode_state">
|
||||||
<return type="PackedByteArray" />
|
<return type="PackedByteArray" />
|
||||||
<argument index="0" name="scene_id" type="int" />
|
<argument index="0" name="scene_id" type="int" />
|
||||||
<argument index="1" name="object" type="Object" />
|
<argument index="1" name="object" type="Object" />
|
||||||
|
<argument index="2" name="initial" type="bool" default="true" />
|
||||||
<description>
|
<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].
|
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].
|
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.
|
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>
|
</description>
|
||||||
</method>
|
</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">
|
<method name="spawn">
|
||||||
<return type="int" enum="Error" />
|
<return type="int" enum="Error" />
|
||||||
<argument index="0" name="scene_id" type="int" />
|
<argument index="0" name="scene_id" type="int" />
|
||||||
<argument index="1" name="object" type="Object" />
|
<argument index="1" name="object" type="Object" />
|
||||||
<argument index="2" name="peer_id" type="int" default="0" />
|
<argument index="2" name="peer_id" type="int" default="0" />
|
||||||
<description>
|
<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>
|
</description>
|
||||||
</method>
|
</method>
|
||||||
<method name="spawn_config">
|
<method name="spawn_config">
|
||||||
|
@ -70,10 +85,47 @@
|
||||||
<argument index="3" name="custom_send" type="Callable" />
|
<argument index="3" name="custom_send" type="Callable" />
|
||||||
<argument index="4" name="custom_receive" type="Callable" />
|
<argument index="4" name="custom_receive" type="Callable" />
|
||||||
<description>
|
<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.
|
Tip: You can use a custom property in the scene main script to return a customly optimized state representation.
|
||||||
</description>
|
</description>
|
||||||
</method>
|
</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>
|
</methods>
|
||||||
<signals>
|
<signals>
|
||||||
<signal name="despawn_requested">
|
<signal name="despawn_requested">
|
||||||
|
|
|
@ -109,9 +109,11 @@
|
||||||
<return type="void" />
|
<return type="void" />
|
||||||
<argument index="0" name="node" type="Node" />
|
<argument index="0" name="node" type="Node" />
|
||||||
<argument index="1" name="legible_unique_name" type="bool" default="false" />
|
<argument index="1" name="legible_unique_name" type="bool" default="false" />
|
||||||
|
<argument index="2" name="internal" type="int" enum="Node.InternalMode" default="0" />
|
||||||
<description>
|
<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.
|
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]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:
|
[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]
|
[codeblocks]
|
||||||
[gdscript]
|
[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.
|
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.
|
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.
|
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>
|
</description>
|
||||||
</method>
|
</method>
|
||||||
<method name="add_to_group">
|
<method name="add_to_group">
|
||||||
|
@ -200,22 +203,28 @@
|
||||||
<method name="get_child" qualifiers="const">
|
<method name="get_child" qualifiers="const">
|
||||||
<return type="Node" />
|
<return type="Node" />
|
||||||
<argument index="0" name="idx" type="int" />
|
<argument index="0" name="idx" type="int" />
|
||||||
|
<argument index="1" name="include_internal" type="bool" default="false" />
|
||||||
<description>
|
<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.
|
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.
|
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].
|
To access a child node via its name, use [method get_node].
|
||||||
</description>
|
</description>
|
||||||
</method>
|
</method>
|
||||||
<method name="get_child_count" qualifiers="const">
|
<method name="get_child_count" qualifiers="const">
|
||||||
<return type="int" />
|
<return type="int" />
|
||||||
|
<argument index="0" name="include_internal" type="bool" default="false" />
|
||||||
<description>
|
<description>
|
||||||
Returns the number of child nodes.
|
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>
|
</description>
|
||||||
</method>
|
</method>
|
||||||
<method name="get_children" qualifiers="const">
|
<method name="get_children" qualifiers="const">
|
||||||
<return type="Node[]" />
|
<return type="Node[]" />
|
||||||
|
<argument index="0" name="include_internal" type="bool" default="false" />
|
||||||
<description>
|
<description>
|
||||||
Returns an array of references to node's children.
|
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>
|
</description>
|
||||||
</method>
|
</method>
|
||||||
<method name="get_editor_description" qualifiers="const">
|
<method name="get_editor_description" qualifiers="const">
|
||||||
|
@ -231,14 +240,16 @@
|
||||||
</method>
|
</method>
|
||||||
<method name="get_index" qualifiers="const">
|
<method name="get_index" qualifiers="const">
|
||||||
<return type="int" />
|
<return type="int" />
|
||||||
|
<argument index="0" name="include_internal" type="bool" default="false" />
|
||||||
<description>
|
<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].
|
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>
|
</description>
|
||||||
</method>
|
</method>
|
||||||
<method name="get_network_master" qualifiers="const">
|
<method name="get_network_authority" qualifiers="const">
|
||||||
<return type="int" />
|
<return type="int" />
|
||||||
<description>
|
<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>
|
</description>
|
||||||
</method>
|
</method>
|
||||||
<method name="get_node" qualifiers="const">
|
<method name="get_node" qualifiers="const">
|
||||||
|
@ -406,10 +417,10 @@
|
||||||
Returns [code]true[/code] if this node is currently inside a [SceneTree].
|
Returns [code]true[/code] if this node is currently inside a [SceneTree].
|
||||||
</description>
|
</description>
|
||||||
</method>
|
</method>
|
||||||
<method name="is_network_master" qualifiers="const">
|
<method name="is_network_authority" qualifiers="const">
|
||||||
<return type="bool" />
|
<return type="bool" />
|
||||||
<description>
|
<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>
|
</description>
|
||||||
</method>
|
</method>
|
||||||
<method name="is_physics_processing" qualifiers="const">
|
<method name="is_physics_processing" qualifiers="const">
|
||||||
|
@ -460,6 +471,7 @@
|
||||||
<argument index="1" name="to_position" type="int" />
|
<argument index="1" name="to_position" type="int" />
|
||||||
<description>
|
<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.
|
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>
|
</description>
|
||||||
</method>
|
</method>
|
||||||
<method name="print_stray_nodes">
|
<method name="print_stray_nodes">
|
||||||
|
@ -576,7 +588,7 @@
|
||||||
<argument index="2" name="transfer_mode" type="int" enum="MultiplayerPeer.TransferMode" default="2" />
|
<argument index="2" name="transfer_mode" type="int" enum="MultiplayerPeer.TransferMode" default="2" />
|
||||||
<argument index="3" name="channel" type="int" default="0" />
|
<argument index="3" name="channel" type="int" default="0" />
|
||||||
<description>
|
<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>
|
</description>
|
||||||
</method>
|
</method>
|
||||||
<method name="rpc_id" qualifiers="vararg">
|
<method name="rpc_id" qualifiers="vararg">
|
||||||
|
@ -608,12 +620,12 @@
|
||||||
<description>
|
<description>
|
||||||
</description>
|
</description>
|
||||||
</method>
|
</method>
|
||||||
<method name="set_network_master">
|
<method name="set_network_authority">
|
||||||
<return type="void" />
|
<return type="void" />
|
||||||
<argument index="0" name="id" type="int" />
|
<argument index="0" name="id" type="int" />
|
||||||
<argument index="1" name="recursive" type="bool" default="true" />
|
<argument index="1" name="recursive" type="bool" default="true" />
|
||||||
<description>
|
<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>
|
</description>
|
||||||
</method>
|
</method>
|
||||||
<method name="set_physics_process">
|
<method name="set_physics_process">
|
||||||
|
@ -888,5 +900,14 @@
|
||||||
Duplicate using instancing.
|
Duplicate using instancing.
|
||||||
An instance stays linked to the original so when the original changes, the instance changes too.
|
An instance stays linked to the original so when the original changes, the instance changes too.
|
||||||
</constant>
|
</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>
|
</constants>
|
||||||
</class>
|
</class>
|
||||||
|
|
|
@ -39,12 +39,8 @@ extern "C" {
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
GODOT_METHOD_RPC_MODE_DISABLED,
|
GODOT_METHOD_RPC_MODE_DISABLED,
|
||||||
GODOT_METHOD_RPC_MODE_REMOTE,
|
GODOT_METHOD_RPC_MODE_ANY,
|
||||||
GODOT_METHOD_RPC_MODE_MASTER,
|
GODOT_METHOD_RPC_MODE_AUTHORITY,
|
||||||
GODOT_METHOD_RPC_MODE_PUPPET,
|
|
||||||
GODOT_METHOD_RPC_MODE_REMOTESYNC,
|
|
||||||
GODOT_METHOD_RPC_MODE_MASTERSYNC,
|
|
||||||
GODOT_METHOD_RPC_MODE_PUPPETSYNC,
|
|
||||||
} godot_nativescript_method_rpc_mode;
|
} godot_nativescript_method_rpc_mode;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
|
|
|
@ -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_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>);
|
register_annotation(MethodInfo("@export_flags_3d_navigation"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_NAVIGATION, Variant::INT>);
|
||||||
// Networking.
|
// 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.
|
// TODO: Warning annotations.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3399,43 +3399,35 @@ bool GDScriptParser::network_annotations(const AnnotationNode *p_annotation, Nod
|
||||||
|
|
||||||
MultiplayerAPI::RPCConfig rpc_config;
|
MultiplayerAPI::RPCConfig rpc_config;
|
||||||
rpc_config.rpc_mode = t_mode;
|
rpc_config.rpc_mode = t_mode;
|
||||||
for (int i = 0; i < p_annotation->resolved_arguments.size(); i++) {
|
if (p_annotation->resolved_arguments.size()) {
|
||||||
if (i == 0) {
|
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();
|
String mode = p_annotation->resolved_arguments[i].operator String();
|
||||||
if (mode == "any") {
|
if (mode == "any") {
|
||||||
rpc_config.rpc_mode = MultiplayerAPI::RPC_MODE_REMOTE;
|
rpc_config.rpc_mode = MultiplayerAPI::RPC_MODE_ANY;
|
||||||
} else if (mode == "master") {
|
} else if (mode == "auth") {
|
||||||
rpc_config.rpc_mode = MultiplayerAPI::RPC_MODE_MASTER;
|
rpc_config.rpc_mode = MultiplayerAPI::RPC_MODE_AUTHORITY;
|
||||||
} else if (mode == "puppet") {
|
} else if (mode == "sync") {
|
||||||
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.sync = true;
|
rpc_config.sync = true;
|
||||||
} else if (sync == "nosync") {
|
} else if (mode == "nosync") {
|
||||||
rpc_config.sync = false;
|
rpc_config.sync = false;
|
||||||
} else {
|
} else if (mode == "reliable") {
|
||||||
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") {
|
|
||||||
rpc_config.transfer_mode = MultiplayerPeer::TRANSFER_MODE_RELIABLE;
|
rpc_config.transfer_mode = MultiplayerPeer::TRANSFER_MODE_RELIABLE;
|
||||||
} else if (mode == "unreliable") {
|
} else if (mode == "unreliable") {
|
||||||
rpc_config.transfer_mode = MultiplayerPeer::TRANSFER_MODE_UNRELIABLE;
|
rpc_config.transfer_mode = MultiplayerPeer::TRANSFER_MODE_UNRELIABLE;
|
||||||
} else if (mode == "ordered") {
|
} else if (mode == "ordered") {
|
||||||
rpc_config.transfer_mode = MultiplayerPeer::TRANSFER_MODE_UNRELIABLE_ORDERED;
|
rpc_config.transfer_mode = MultiplayerPeer::TRANSFER_MODE_UNRELIABLE_ORDERED;
|
||||||
} else {
|
} else {
|
||||||
push_error(R"(Invalid RPC transfer mode. Must be one of: 'reliable', 'unreliable', 'ordered')", p_annotation);
|
push_error(R"(Invalid RPC argument. Must be one of: 'sync'/'nosync' (local calls), 'any'/'auth' (permission), 'reliable'/'unreliable'/'ordered' (transfer mode).)", p_annotation);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
} else if (i == 3) {
|
|
||||||
rpc_config.channel = p_annotation->resolved_arguments[i].operator int();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
switch (p_node->type) {
|
switch (p_node->type) {
|
||||||
|
|
|
@ -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 {
|
MultiplayerAPI::RPCMode CSharpScript::_member_get_rpc_mode(IMonoClassMember *p_member) const {
|
||||||
if (p_member->has_attribute(CACHED_CLASS(RemoteAttribute))) {
|
if (p_member->has_attribute(CACHED_CLASS(RemoteAttribute))) {
|
||||||
return MultiplayerAPI::RPC_MODE_REMOTE;
|
return MultiplayerAPI::RPC_MODE_ANY;
|
||||||
}
|
|
||||||
if (p_member->has_attribute(CACHED_CLASS(MasterAttribute))) {
|
|
||||||
return MultiplayerAPI::RPC_MODE_MASTER;
|
|
||||||
}
|
}
|
||||||
if (p_member->has_attribute(CACHED_CLASS(PuppetAttribute))) {
|
if (p_member->has_attribute(CACHED_CLASS(PuppetAttribute))) {
|
||||||
return MultiplayerAPI::RPC_MODE_PUPPET;
|
return MultiplayerAPI::RPC_MODE_AUTHORITY;
|
||||||
}
|
}
|
||||||
|
|
||||||
return MultiplayerAPI::RPC_MODE_DISABLED;
|
return MultiplayerAPI::RPC_MODE_DISABLED;
|
||||||
|
|
|
@ -5,9 +5,6 @@ namespace Godot
|
||||||
[AttributeUsage(AttributeTargets.Method)]
|
[AttributeUsage(AttributeTargets.Method)]
|
||||||
public class RemoteAttribute : Attribute {}
|
public class RemoteAttribute : Attribute {}
|
||||||
|
|
||||||
[AttributeUsage(AttributeTargets.Method)]
|
|
||||||
public class MasterAttribute : Attribute {}
|
|
||||||
|
|
||||||
[AttributeUsage(AttributeTargets.Method)]
|
[AttributeUsage(AttributeTargets.Method)]
|
||||||
public class PuppetAttribute : Attribute {}
|
public class PuppetAttribute : Attribute {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -141,7 +141,6 @@ void CachedData::clear_godot_api_cache() {
|
||||||
class_SignalAttribute = nullptr;
|
class_SignalAttribute = nullptr;
|
||||||
class_ToolAttribute = nullptr;
|
class_ToolAttribute = nullptr;
|
||||||
class_RemoteAttribute = nullptr;
|
class_RemoteAttribute = nullptr;
|
||||||
class_MasterAttribute = nullptr;
|
|
||||||
class_PuppetAttribute = nullptr;
|
class_PuppetAttribute = nullptr;
|
||||||
class_GodotMethodAttribute = nullptr;
|
class_GodotMethodAttribute = nullptr;
|
||||||
field_GodotMethodAttribute_methodName = 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(SignalAttribute, GODOT_API_CLASS(SignalAttribute));
|
||||||
CACHE_CLASS_AND_CHECK(ToolAttribute, GODOT_API_CLASS(ToolAttribute));
|
CACHE_CLASS_AND_CHECK(ToolAttribute, GODOT_API_CLASS(ToolAttribute));
|
||||||
CACHE_CLASS_AND_CHECK(RemoteAttribute, GODOT_API_CLASS(RemoteAttribute));
|
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(PuppetAttribute, GODOT_API_CLASS(PuppetAttribute));
|
||||||
CACHE_CLASS_AND_CHECK(GodotMethodAttribute, GODOT_API_CLASS(GodotMethodAttribute));
|
CACHE_CLASS_AND_CHECK(GodotMethodAttribute, GODOT_API_CLASS(GodotMethodAttribute));
|
||||||
CACHE_FIELD_AND_CHECK(GodotMethodAttribute, methodName, CACHED_CLASS(GodotMethodAttribute)->get_field("methodName"));
|
CACHE_FIELD_AND_CHECK(GodotMethodAttribute, methodName, CACHED_CLASS(GodotMethodAttribute)->get_field("methodName"));
|
||||||
|
|
|
@ -112,7 +112,6 @@ struct CachedData {
|
||||||
GDMonoClass *class_SignalAttribute;
|
GDMonoClass *class_SignalAttribute;
|
||||||
GDMonoClass *class_ToolAttribute;
|
GDMonoClass *class_ToolAttribute;
|
||||||
GDMonoClass *class_RemoteAttribute;
|
GDMonoClass *class_RemoteAttribute;
|
||||||
GDMonoClass *class_MasterAttribute;
|
|
||||||
GDMonoClass *class_PuppetAttribute;
|
GDMonoClass *class_PuppetAttribute;
|
||||||
GDMonoClass *class_GodotMethodAttribute;
|
GDMonoClass *class_GodotMethodAttribute;
|
||||||
GDMonoField *field_GodotMethodAttribute_methodName;
|
GDMonoField *field_GodotMethodAttribute_methodName;
|
||||||
|
|
|
@ -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::INT, "stack/size", PROPERTY_HINT_RANGE, "1,100000"));
|
||||||
}
|
}
|
||||||
p_list->push_back(PropertyInfo(Variant::BOOL, "stack/stackless"));
|
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 {
|
int VisualScriptFunction::get_output_sequence_port_count() const {
|
||||||
|
|
|
@ -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);
|
ERR_FAIL_COND_V(p_path.is_empty(), ERR_INVALID_PARAMETER);
|
||||||
|
|
||||||
_peer = Ref<WSLPeer>(memnew(WSLPeer));
|
_peer = Ref<WSLPeer>(memnew(WSLPeer));
|
||||||
IPAddress addr;
|
|
||||||
|
|
||||||
if (!p_host.is_valid_ip_address()) {
|
if (p_host.is_valid_ip_address()) {
|
||||||
addr = IP::get_singleton()->resolve_hostname(p_host);
|
ip_candidates.clear();
|
||||||
|
ip_candidates.push_back(IPAddress(p_host));
|
||||||
} else {
|
} 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 = "";
|
String port = "";
|
||||||
if ((p_port != 80 && !p_ssl) || (p_port != 443 && p_ssl)) {
|
if ((p_port != 80 && !p_ssl) || (p_port != 443 && p_ssl)) {
|
||||||
port = ":" + itos(p_port);
|
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) {
|
if (err != OK) {
|
||||||
_tcp->disconnect_from_host();
|
_tcp->disconnect_from_host();
|
||||||
_on_error();
|
_on_error();
|
||||||
|
@ -185,6 +191,7 @@ Error WSLClient::connect_to_host(String p_host, String p_path, uint16_t p_port,
|
||||||
_connection = _tcp;
|
_connection = _tcp;
|
||||||
_use_ssl = p_ssl;
|
_use_ssl = p_ssl;
|
||||||
_host = p_host;
|
_host = p_host;
|
||||||
|
_port = p_port;
|
||||||
// Strip edges from protocols.
|
// Strip edges from protocols.
|
||||||
_protocols.resize(p_protocols.size());
|
_protocols.resize(p_protocols.size());
|
||||||
String *pw = _protocols.ptrw();
|
String *pw = _protocols.ptrw();
|
||||||
|
@ -244,6 +251,7 @@ void WSLClient::poll() {
|
||||||
_on_error();
|
_on_error();
|
||||||
break;
|
break;
|
||||||
case StreamPeerTCP::STATUS_CONNECTED: {
|
case StreamPeerTCP::STATUS_CONNECTED: {
|
||||||
|
ip_candidates.clear();
|
||||||
Ref<StreamPeerSSL> ssl;
|
Ref<StreamPeerSSL> ssl;
|
||||||
if (_use_ssl) {
|
if (_use_ssl) {
|
||||||
if (_connection == _tcp) {
|
if (_connection == _tcp) {
|
||||||
|
@ -274,6 +282,12 @@ void WSLClient::poll() {
|
||||||
_do_handshake();
|
_do_handshake();
|
||||||
} break;
|
} break;
|
||||||
case StreamPeerTCP::STATUS_ERROR:
|
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();
|
disconnect_from_host();
|
||||||
_on_error();
|
_on_error();
|
||||||
break;
|
break;
|
||||||
|
@ -315,6 +329,8 @@ void WSLClient::disconnect_from_host(int p_code, String p_reason) {
|
||||||
|
|
||||||
memset(_resp_buf, 0, sizeof(_resp_buf));
|
memset(_resp_buf, 0, sizeof(_resp_buf));
|
||||||
_resp_pos = 0;
|
_resp_pos = 0;
|
||||||
|
|
||||||
|
ip_candidates.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
IPAddress WSLClient::get_connected_host() const {
|
IPAddress WSLClient::get_connected_host() const {
|
||||||
|
|
|
@ -63,6 +63,8 @@ private:
|
||||||
|
|
||||||
String _key;
|
String _key;
|
||||||
String _host;
|
String _host;
|
||||||
|
int _port;
|
||||||
|
Array ip_candidates;
|
||||||
Vector<String> _protocols;
|
Vector<String> _protocols;
|
||||||
bool _use_ssl = false;
|
bool _use_ssl = false;
|
||||||
|
|
||||||
|
|
|
@ -351,11 +351,11 @@ MarginContainer *VBoxContainer::add_margin_child(const String &p_label, Control
|
||||||
Label *l = memnew(Label);
|
Label *l = memnew(Label);
|
||||||
l->set_theme_type_variation("HeaderSmall");
|
l->set_theme_type_variation("HeaderSmall");
|
||||||
l->set_text(p_label);
|
l->set_text(p_label);
|
||||||
add_child(l);
|
add_child(l, false, INTERNAL_MODE_FRONT);
|
||||||
MarginContainer *mc = memnew(MarginContainer);
|
MarginContainer *mc = memnew(MarginContainer);
|
||||||
mc->add_theme_constant_override("margin_left", 0);
|
mc->add_theme_constant_override("margin_left", 0);
|
||||||
mc->add_child(p_control);
|
mc->add_child(p_control);
|
||||||
add_child(mc);
|
add_child(mc, false, INTERNAL_MODE_FRONT);
|
||||||
if (p_expand) {
|
if (p_expand) {
|
||||||
mc->set_v_size_flags(SIZE_EXPAND_FILL);
|
mc->set_v_size_flags(SIZE_EXPAND_FILL);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1139,7 +1139,7 @@ void ColorPicker::_bind_methods() {
|
||||||
ColorPicker::ColorPicker() :
|
ColorPicker::ColorPicker() :
|
||||||
BoxContainer(true) {
|
BoxContainer(true) {
|
||||||
HBoxContainer *hb_edit = memnew(HBoxContainer);
|
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->set_v_size_flags(SIZE_EXPAND_FILL);
|
||||||
|
|
||||||
hb_edit->add_child(uv_edit);
|
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));
|
uv_edit->connect("draw", callable_mp(this, &ColorPicker::_hsv_draw), make_binds(0, uv_edit));
|
||||||
|
|
||||||
HBoxContainer *hb_smpl = memnew(HBoxContainer);
|
HBoxContainer *hb_smpl = memnew(HBoxContainer);
|
||||||
add_child(hb_smpl);
|
add_child(hb_smpl, false, INTERNAL_MODE_FRONT);
|
||||||
|
|
||||||
hb_smpl->add_child(sample);
|
hb_smpl->add_child(sample);
|
||||||
sample->set_h_size_flags(SIZE_EXPAND_FILL);
|
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));
|
btn_pick->connect("pressed", callable_mp(this, &ColorPicker::_screen_pick_pressed));
|
||||||
|
|
||||||
VBoxContainer *vbl = memnew(VBoxContainer);
|
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);
|
VBoxContainer *vbr = memnew(VBoxContainer);
|
||||||
add_child(vbr);
|
add_child(vbr, false, INTERNAL_MODE_FRONT);
|
||||||
vbr->set_h_size_flags(SIZE_EXPAND_FILL);
|
vbr->set_h_size_flags(SIZE_EXPAND_FILL);
|
||||||
|
|
||||||
for (int i = 0; i < 4; i++) {
|
for (int i = 0; i < 4; i++) {
|
||||||
|
@ -1273,11 +1273,11 @@ ColorPicker::ColorPicker() :
|
||||||
|
|
||||||
set_pick_color(Color(1, 1, 1));
|
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_h_size_flags(SIZE_EXPAND_FILL);
|
||||||
preset_container->set_columns(preset_column_count);
|
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_icon_align(Button::ALIGN_CENTER);
|
||||||
btn_add_preset->set_tooltip(RTR("Add current color as a preset."));
|
btn_add_preset->set_tooltip(RTR("Add current color as a preset."));
|
||||||
|
@ -1405,7 +1405,7 @@ void ColorPickerButton::_update_picker() {
|
||||||
picker = memnew(ColorPicker);
|
picker = memnew(ColorPicker);
|
||||||
picker->set_anchors_and_offsets_preset(PRESET_WIDE);
|
picker->set_anchors_and_offsets_preset(PRESET_WIDE);
|
||||||
popup->add_child(picker);
|
popup->add_child(picker);
|
||||||
add_child(popup);
|
add_child(popup, false, INTERNAL_MODE_FRONT);
|
||||||
picker->connect("color_changed", callable_mp(this, &ColorPickerButton::_color_changed));
|
picker->connect("color_changed", callable_mp(this, &ColorPickerButton::_color_changed));
|
||||||
popup->connect("about_to_popup", callable_mp(this, &ColorPickerButton::_about_to_popup));
|
popup->connect("about_to_popup", callable_mp(this, &ColorPickerButton::_about_to_popup));
|
||||||
popup->connect("popup_hide", callable_mp(this, &ColorPickerButton::_modal_closed));
|
popup->connect("popup_hide", callable_mp(this, &ColorPickerButton::_modal_closed));
|
||||||
|
|
|
@ -319,7 +319,7 @@ AcceptDialog::AcceptDialog() {
|
||||||
set_clamp_to_embedder(true);
|
set_clamp_to_embedder(true);
|
||||||
|
|
||||||
bg = memnew(Panel);
|
bg = memnew(Panel);
|
||||||
add_child(bg);
|
add_child(bg, false, INTERNAL_MODE_FRONT);
|
||||||
|
|
||||||
hbc = memnew(HBoxContainer);
|
hbc = memnew(HBoxContainer);
|
||||||
|
|
||||||
|
@ -331,9 +331,9 @@ AcceptDialog::AcceptDialog() {
|
||||||
label->set_anchor(SIDE_BOTTOM, Control::ANCHOR_END);
|
label->set_anchor(SIDE_BOTTOM, Control::ANCHOR_END);
|
||||||
label->set_begin(Point2(margin, margin));
|
label->set_begin(Point2(margin, margin));
|
||||||
label->set_end(Point2(-margin, -button_margin - 10));
|
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();
|
hbc->add_spacer();
|
||||||
ok = memnew(Button);
|
ok = memnew(Button);
|
||||||
|
|
|
@ -924,7 +924,7 @@ FileDialog::FileDialog() {
|
||||||
show_hidden_files = default_show_hidden_files;
|
show_hidden_files = default_show_hidden_files;
|
||||||
|
|
||||||
vbox = memnew(VBoxContainer);
|
vbox = memnew(VBoxContainer);
|
||||||
add_child(vbox);
|
add_child(vbox, false, INTERNAL_MODE_FRONT);
|
||||||
vbox->connect("theme_changed", callable_mp(this, &FileDialog::_theme_changed));
|
vbox->connect("theme_changed", callable_mp(this, &FileDialog::_theme_changed));
|
||||||
|
|
||||||
mode = FILE_MODE_SAVE_FILE;
|
mode = FILE_MODE_SAVE_FILE;
|
||||||
|
@ -1023,8 +1023,7 @@ FileDialog::FileDialog() {
|
||||||
filter->connect("item_selected", callable_mp(this, &FileDialog::_filter_selected));
|
filter->connect("item_selected", callable_mp(this, &FileDialog::_filter_selected));
|
||||||
|
|
||||||
confirm_save = memnew(ConfirmationDialog);
|
confirm_save = memnew(ConfirmationDialog);
|
||||||
// confirm_save->set_as_top_level(true);
|
add_child(confirm_save, false, INTERNAL_MODE_FRONT);
|
||||||
add_child(confirm_save);
|
|
||||||
|
|
||||||
confirm_save->connect("confirmed", callable_mp(this, &FileDialog::_save_confirm_pressed));
|
confirm_save->connect("confirmed", callable_mp(this, &FileDialog::_save_confirm_pressed));
|
||||||
|
|
||||||
|
@ -1036,16 +1035,16 @@ FileDialog::FileDialog() {
|
||||||
makedirname = memnew(LineEdit);
|
makedirname = memnew(LineEdit);
|
||||||
makedirname->set_structured_text_bidi_override(Control::STRUCTURED_TEXT_FILE);
|
makedirname->set_structured_text_bidi_override(Control::STRUCTURED_TEXT_FILE);
|
||||||
makevb->add_margin_child(TTRC("Name:"), makedirname);
|
makevb->add_margin_child(TTRC("Name:"), makedirname);
|
||||||
add_child(makedialog);
|
add_child(makedialog, false, INTERNAL_MODE_FRONT);
|
||||||
makedialog->register_text_enter(makedirname);
|
makedialog->register_text_enter(makedirname);
|
||||||
makedialog->connect("confirmed", callable_mp(this, &FileDialog::_make_dir_confirm));
|
makedialog->connect("confirmed", callable_mp(this, &FileDialog::_make_dir_confirm));
|
||||||
mkdirerr = memnew(AcceptDialog);
|
mkdirerr = memnew(AcceptDialog);
|
||||||
mkdirerr->set_text(TTRC("Could not create folder."));
|
mkdirerr->set_text(TTRC("Could not create folder."));
|
||||||
add_child(mkdirerr);
|
add_child(mkdirerr, false, INTERNAL_MODE_FRONT);
|
||||||
|
|
||||||
exterr = memnew(AcceptDialog);
|
exterr = memnew(AcceptDialog);
|
||||||
exterr->set_text(TTRC("Must use a valid extension."));
|
exterr->set_text(TTRC("Must use a valid extension."));
|
||||||
add_child(exterr);
|
add_child(exterr, false, INTERNAL_MODE_FRONT);
|
||||||
|
|
||||||
update_filters();
|
update_filters();
|
||||||
update_dir();
|
update_dir();
|
||||||
|
|
|
@ -48,7 +48,7 @@ GradientEdit::GradientEdit() {
|
||||||
picker = memnew(ColorPicker);
|
picker = memnew(ColorPicker);
|
||||||
popup->add_child(picker);
|
popup->add_child(picker);
|
||||||
|
|
||||||
add_child(popup);
|
add_child(popup, false, INTERNAL_MODE_FRONT);
|
||||||
}
|
}
|
||||||
|
|
||||||
int GradientEdit::_get_point_from_pos(int x) {
|
int GradientEdit::_get_point_from_pos(int x) {
|
||||||
|
|
|
@ -366,7 +366,6 @@ void GraphEdit::_graph_node_raised(Node *p_gn) {
|
||||||
}
|
}
|
||||||
|
|
||||||
move_child(connections_layer, first_not_comment);
|
move_child(connections_layer, first_not_comment);
|
||||||
top_layer->raise();
|
|
||||||
emit_signal(SNAME("node_selected"), p_gn);
|
emit_signal(SNAME("node_selected"), p_gn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2246,14 +2245,14 @@ GraphEdit::GraphEdit() {
|
||||||
zoom_max = (1 * Math::pow(zoom_step, 4));
|
zoom_max = (1 * Math::pow(zoom_step, 4));
|
||||||
|
|
||||||
top_layer = memnew(GraphEditFilter(this));
|
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_mouse_filter(MOUSE_FILTER_PASS);
|
||||||
top_layer->set_anchors_and_offsets_preset(Control::PRESET_WIDE);
|
top_layer->set_anchors_and_offsets_preset(Control::PRESET_WIDE);
|
||||||
top_layer->connect("draw", callable_mp(this, &GraphEdit::_top_layer_draw));
|
top_layer->connect("draw", callable_mp(this, &GraphEdit::_top_layer_draw));
|
||||||
top_layer->connect("gui_input", callable_mp(this, &GraphEdit::_top_layer_input));
|
top_layer->connect("gui_input", callable_mp(this, &GraphEdit::_top_layer_input));
|
||||||
|
|
||||||
connections_layer = memnew(Control);
|
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->connect("draw", callable_mp(this, &GraphEdit::_connections_layer_draw));
|
||||||
connections_layer->set_name("CLAYER");
|
connections_layer->set_name("CLAYER");
|
||||||
connections_layer->set_disable_visibility_clip(true); // so it can draw freely and be offset
|
connections_layer->set_disable_visibility_clip(true); // so it can draw freely and be offset
|
||||||
|
|
|
@ -1656,7 +1656,7 @@ void ItemList::_bind_methods() {
|
||||||
|
|
||||||
ItemList::ItemList() {
|
ItemList::ItemList() {
|
||||||
scroll_bar = memnew(VScrollBar);
|
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));
|
scroll_bar->connect("value_changed", callable_mp(this, &ItemList::_scroll_changed));
|
||||||
|
|
||||||
|
|
|
@ -2220,7 +2220,7 @@ void LineEdit::_bind_methods() {
|
||||||
void LineEdit::_ensure_menu() {
|
void LineEdit::_ensure_menu() {
|
||||||
if (!menu) {
|
if (!menu) {
|
||||||
menu = memnew(PopupMenu);
|
menu = memnew(PopupMenu);
|
||||||
add_child(menu);
|
add_child(menu, false, INTERNAL_MODE_FRONT);
|
||||||
|
|
||||||
menu_dir = memnew(PopupMenu);
|
menu_dir = memnew(PopupMenu);
|
||||||
menu_dir->set_name("DirMenu");
|
menu_dir->set_name("DirMenu");
|
||||||
|
@ -2305,7 +2305,7 @@ LineEdit::LineEdit() {
|
||||||
set_mouse_filter(MOUSE_FILTER_STOP);
|
set_mouse_filter(MOUSE_FILTER_STOP);
|
||||||
|
|
||||||
caret_blink_timer = memnew(Timer);
|
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->set_wait_time(0.65);
|
||||||
caret_blink_timer->connect("timeout", callable_mp(this, &LineEdit::_toggle_draw_caret));
|
caret_blink_timer->connect("timeout", callable_mp(this, &LineEdit::_toggle_draw_caret));
|
||||||
set_caret_blink_enabled(false);
|
set_caret_blink_enabled(false);
|
||||||
|
|
|
@ -172,7 +172,7 @@ MenuButton::MenuButton() {
|
||||||
|
|
||||||
popup = memnew(PopupMenu);
|
popup = memnew(PopupMenu);
|
||||||
popup->hide();
|
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("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));
|
popup->connect("popup_hide", callable_mp(this, &MenuButton::_popup_visibility_changed), varray(false));
|
||||||
}
|
}
|
||||||
|
|
|
@ -351,7 +351,7 @@ OptionButton::OptionButton() {
|
||||||
|
|
||||||
popup = memnew(PopupMenu);
|
popup = memnew(PopupMenu);
|
||||||
popup->hide();
|
popup->hide();
|
||||||
add_child(popup);
|
add_child(popup, false, INTERNAL_MODE_FRONT);
|
||||||
popup->connect("index_pressed", callable_mp(this, &OptionButton::_selected));
|
popup->connect("index_pressed", callable_mp(this, &OptionButton::_selected));
|
||||||
popup->connect("id_focused", callable_mp(this, &OptionButton::_focused));
|
popup->connect("id_focused", callable_mp(this, &OptionButton::_focused));
|
||||||
popup->connect("popup_hide", callable_mp((BaseButton *)this, &BaseButton::set_pressed), varray(false));
|
popup->connect("popup_hide", callable_mp((BaseButton *)this, &BaseButton::set_pressed), varray(false));
|
||||||
|
|
|
@ -255,5 +255,5 @@ void PopupPanel::_notification(int p_what) {
|
||||||
|
|
||||||
PopupPanel::PopupPanel() {
|
PopupPanel::PopupPanel() {
|
||||||
panel = memnew(Panel);
|
panel = memnew(Panel);
|
||||||
add_child(panel);
|
add_child(panel, false, INTERNAL_MODE_FRONT);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1690,7 +1690,7 @@ PopupMenu::PopupMenu() {
|
||||||
// Margin Container
|
// Margin Container
|
||||||
margin_container = memnew(MarginContainer);
|
margin_container = memnew(MarginContainer);
|
||||||
margin_container->set_anchors_and_offsets_preset(Control::PRESET_WIDE);
|
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));
|
margin_container->connect("draw", callable_mp(this, &PopupMenu::_draw_background));
|
||||||
|
|
||||||
// Scroll Container
|
// Scroll Container
|
||||||
|
@ -1704,7 +1704,7 @@ PopupMenu::PopupMenu() {
|
||||||
control->set_anchors_and_offsets_preset(Control::PRESET_WIDE);
|
control->set_anchors_and_offsets_preset(Control::PRESET_WIDE);
|
||||||
control->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
control->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
||||||
control->set_v_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));
|
control->connect("draw", callable_mp(this, &PopupMenu::_draw_items));
|
||||||
|
|
||||||
connect("window_input", callable_mp(this, &PopupMenu::gui_input));
|
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_wait_time(0.3);
|
||||||
submenu_timer->set_one_shot(true);
|
submenu_timer->set_one_shot(true);
|
||||||
submenu_timer->connect("timeout", callable_mp(this, &PopupMenu::_submenu_timeout));
|
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 = memnew(Timer);
|
||||||
minimum_lifetime_timer->set_wait_time(0.3);
|
minimum_lifetime_timer->set_wait_time(0.3);
|
||||||
minimum_lifetime_timer->set_one_shot(true);
|
minimum_lifetime_timer->set_one_shot(true);
|
||||||
minimum_lifetime_timer->connect("timeout", callable_mp(this, &PopupMenu::_minimum_lifetime_timeout));
|
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() {
|
PopupMenu::~PopupMenu() {
|
||||||
|
|
|
@ -4361,7 +4361,7 @@ RichTextLabel::RichTextLabel() {
|
||||||
current_frame = main;
|
current_frame = main;
|
||||||
|
|
||||||
vscroll = memnew(VScrollBar);
|
vscroll = memnew(VScrollBar);
|
||||||
add_child(vscroll);
|
add_child(vscroll, false, INTERNAL_MODE_FRONT);
|
||||||
vscroll->set_drag_node(String(".."));
|
vscroll->set_drag_node(String(".."));
|
||||||
vscroll->set_step(1);
|
vscroll->set_step(1);
|
||||||
vscroll->set_anchor_and_offset(SIDE_TOP, ANCHOR_BEGIN, 0);
|
vscroll->set_anchor_and_offset(SIDE_TOP, ANCHOR_BEGIN, 0);
|
||||||
|
|
|
@ -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_TOP, ANCHOR_BEGIN, 0);
|
||||||
v_scroll->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, 0);
|
v_scroll->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, 0);
|
||||||
|
|
||||||
h_scroll->raise();
|
|
||||||
v_scroll->raise();
|
|
||||||
|
|
||||||
_updating_scrollbars = false;
|
_updating_scrollbars = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -618,12 +615,12 @@ void ScrollContainer::_bind_methods() {
|
||||||
ScrollContainer::ScrollContainer() {
|
ScrollContainer::ScrollContainer() {
|
||||||
h_scroll = memnew(HScrollBar);
|
h_scroll = memnew(HScrollBar);
|
||||||
h_scroll->set_name("_h_scroll");
|
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));
|
h_scroll->connect("value_changed", callable_mp(this, &ScrollContainer::_scroll_moved));
|
||||||
|
|
||||||
v_scroll = memnew(VScrollBar);
|
v_scroll = memnew(VScrollBar);
|
||||||
v_scroll->set_name("_v_scroll");
|
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));
|
v_scroll->connect("value_changed", callable_mp(this, &ScrollContainer::_scroll_moved));
|
||||||
|
|
||||||
deadzone = GLOBAL_GET("gui/common/default_scroll_deadzone");
|
deadzone = GLOBAL_GET("gui/common/default_scroll_deadzone");
|
||||||
|
|
|
@ -278,7 +278,7 @@ void SpinBox::_bind_methods() {
|
||||||
|
|
||||||
SpinBox::SpinBox() {
|
SpinBox::SpinBox() {
|
||||||
line_edit = memnew(LineEdit);
|
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_anchors_and_offsets_preset(Control::PRESET_WIDE);
|
||||||
line_edit->set_mouse_filter(MOUSE_FILTER_PASS);
|
line_edit->set_mouse_filter(MOUSE_FILTER_PASS);
|
||||||
|
@ -291,5 +291,5 @@ SpinBox::SpinBox() {
|
||||||
|
|
||||||
range_click_timer = memnew(Timer);
|
range_click_timer = memnew(Timer);
|
||||||
range_click_timer->connect("timeout", callable_mp(this, &SpinBox::_range_click_timeout));
|
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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()) {
|
if (from_tabc && from_tabc->get_tabs_rearrange_group() == get_tabs_rearrange_group()) {
|
||||||
Control *moving_tabc = from_tabc->get_tab_control(tab_from_id);
|
Control *moving_tabc = from_tabc->get_tab_control(tab_from_id);
|
||||||
from_tabc->remove_child(moving_tabc);
|
from_tabc->remove_child(moving_tabc);
|
||||||
add_child(moving_tabc);
|
add_child(moving_tabc, false, INTERNAL_MODE_FRONT);
|
||||||
if (hover_now < 0) {
|
if (hover_now < 0) {
|
||||||
hover_now = get_tab_count() - 1;
|
hover_now = get_tab_count() - 1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5004,7 +5004,7 @@ void TextEdit::_paste_internal() {
|
||||||
void TextEdit::_generate_context_menu() {
|
void TextEdit::_generate_context_menu() {
|
||||||
if (!menu) {
|
if (!menu) {
|
||||||
menu = memnew(PopupMenu);
|
menu = memnew(PopupMenu);
|
||||||
add_child(menu);
|
add_child(menu, false, INTERNAL_MODE_FRONT);
|
||||||
|
|
||||||
menu_dir = memnew(PopupMenu);
|
menu_dir = memnew(PopupMenu);
|
||||||
menu_dir->set_name("DirMenu");
|
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("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("Left-to-right"), MENU_DIR_LTR);
|
||||||
menu_dir->add_radio_check_item(RTR("Right-to-left"), MENU_DIR_RTL);
|
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 = memnew(PopupMenu);
|
||||||
menu_ctl->set_name("CTLMenu");
|
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("Zero width non-joiner (ZWNJ)"), MENU_INSERT_ZWNJ);
|
||||||
menu_ctl->add_item(RTR("Word joiner (WJ)"), MENU_INSERT_WJ);
|
menu_ctl->add_item(RTR("Word joiner (WJ)"), MENU_INSERT_WJ);
|
||||||
menu_ctl->add_item(RTR("Soft hyphen (SHY)"), MENU_INSERT_SHY);
|
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->connect("id_pressed", callable_mp(this, &TextEdit::menu_option));
|
||||||
menu_dir->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);
|
h_scroll = memnew(HScrollBar);
|
||||||
v_scroll = memnew(VScrollBar);
|
v_scroll = memnew(VScrollBar);
|
||||||
|
|
||||||
add_child(h_scroll);
|
add_child(h_scroll, false, INTERNAL_MODE_FRONT);
|
||||||
add_child(v_scroll);
|
add_child(v_scroll, false, INTERNAL_MODE_FRONT);
|
||||||
|
|
||||||
h_scroll->connect("value_changed", callable_mp(this, &TextEdit::_scroll_moved));
|
h_scroll->connect("value_changed", callable_mp(this, &TextEdit::_scroll_moved));
|
||||||
v_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. */
|
||||||
caret_blink_timer = memnew(Timer);
|
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->set_wait_time(0.65);
|
||||||
caret_blink_timer->connect("timeout", callable_mp(this, &TextEdit::_toggle_draw_caret));
|
caret_blink_timer->connect("timeout", callable_mp(this, &TextEdit::_toggle_draw_caret));
|
||||||
set_caret_blink_enabled(false);
|
set_caret_blink_enabled(false);
|
||||||
|
|
||||||
/* Selection. */
|
/* Selection. */
|
||||||
click_select_held = memnew(Timer);
|
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->set_wait_time(0.05);
|
||||||
click_select_held->connect("timeout", callable_mp(this, &TextEdit::_click_selection_held));
|
click_select_held->connect("timeout", callable_mp(this, &TextEdit::_click_selection_held));
|
||||||
|
|
||||||
idle_detect = memnew(Timer);
|
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_one_shot(true);
|
||||||
idle_detect->set_wait_time(GLOBAL_GET("gui/timers/text_edit_idle_detect_sec"));
|
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));
|
idle_detect->connect("timeout", callable_mp(this, &TextEdit::_push_current_op));
|
||||||
|
|
|
@ -4795,12 +4795,11 @@ Tree::Tree() {
|
||||||
|
|
||||||
popup_menu = memnew(PopupMenu);
|
popup_menu = memnew(PopupMenu);
|
||||||
popup_menu->hide();
|
popup_menu->hide();
|
||||||
add_child(popup_menu);
|
add_child(popup_menu, false, INTERNAL_MODE_FRONT);
|
||||||
// popup_menu->set_as_top_level(true);
|
|
||||||
|
|
||||||
popup_editor = memnew(Popup);
|
popup_editor = memnew(Popup);
|
||||||
popup_editor->set_wrap_controls(true);
|
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_vb = memnew(VBoxContainer);
|
||||||
popup_editor->add_child(popup_editor_vb);
|
popup_editor->add_child(popup_editor_vb);
|
||||||
popup_editor_vb->add_theme_constant_override("separation", 0);
|
popup_editor_vb->add_theme_constant_override("separation", 0);
|
||||||
|
@ -4818,12 +4817,12 @@ Tree::Tree() {
|
||||||
h_scroll = memnew(HScrollBar);
|
h_scroll = memnew(HScrollBar);
|
||||||
v_scroll = memnew(VScrollBar);
|
v_scroll = memnew(VScrollBar);
|
||||||
|
|
||||||
add_child(h_scroll);
|
add_child(h_scroll, false, INTERNAL_MODE_FRONT);
|
||||||
add_child(v_scroll);
|
add_child(v_scroll, false, INTERNAL_MODE_FRONT);
|
||||||
|
|
||||||
range_click_timer = memnew(Timer);
|
range_click_timer = memnew(Timer);
|
||||||
range_click_timer->connect("timeout", callable_mp(this, &Tree::_range_click_timeout));
|
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));
|
h_scroll->connect("value_changed", callable_mp(this, &Tree::_scroll_moved));
|
||||||
v_scroll->connect("value_changed", callable_mp(this, &Tree::_scroll_moved));
|
v_scroll->connect("value_changed", callable_mp(this, &Tree::_scroll_moved));
|
||||||
|
|
|
@ -48,6 +48,7 @@
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
VARIANT_ENUM_CAST(Node::ProcessMode);
|
VARIANT_ENUM_CAST(Node::ProcessMode);
|
||||||
|
VARIANT_ENUM_CAST(Node::InternalMode);
|
||||||
|
|
||||||
int Node::orphan_node_count = 0;
|
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) {
|
void Node::move_child(Node *p_child, int p_pos) {
|
||||||
ERR_FAIL_NULL(p_child);
|
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.");
|
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).");
|
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
|
// Specifying one place beyond the end
|
||||||
// means the same as moving to the last position
|
// means the same as moving to the last position
|
||||||
if (p_pos == data.children.size()) {
|
if (!p_ignore_end) { // p_ignore_end is a little hack to make back internal children work properly.
|
||||||
p_pos--;
|
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) {
|
if (p_child->data.pos == p_pos) {
|
||||||
|
@ -339,7 +366,14 @@ void Node::raise() {
|
||||||
return;
|
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) {
|
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) {
|
void Node::set_network_authority(int p_peer_id, bool p_recursive) {
|
||||||
data.network_master = p_peer_id;
|
data.network_authority = p_peer_id;
|
||||||
|
|
||||||
if (p_recursive) {
|
if (p_recursive) {
|
||||||
for (int i = 0; i < data.children.size(); i++) {
|
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 {
|
int Node::get_network_authority() const {
|
||||||
return data.network_master;
|
return data.network_authority;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Node::is_network_master() const {
|
bool Node::is_network_authority() const {
|
||||||
ERR_FAIL_COND_V(!is_inside_tree(), false);
|
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 ********/
|
/***** 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();
|
p_child->data.pos = data.children.size();
|
||||||
data.children.push_back(p_child);
|
data.children.push_back(p_child);
|
||||||
p_child->data.parent = this;
|
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);
|
p_child->notification(NOTIFICATION_PARENTED);
|
||||||
|
|
||||||
if (data.tree) {
|
if (data.tree) {
|
||||||
|
@ -1070,7 +1108,7 @@ void Node::_add_child_nocheck(Node *p_child, const StringName &p_name) {
|
||||||
add_child_notify(p_child);
|
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_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 == 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
|
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
|
#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.");
|
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);
|
_validate_child_name(p_child, p_legible_unique_name);
|
||||||
|
|
||||||
_add_child_nocheck(p_child, p_child->data.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) {
|
void Node::add_sibling(Node *p_sibling, bool p_legible_unique_name) {
|
||||||
ERR_FAIL_NULL(p_sibling);
|
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(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.");
|
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);
|
InternalMode internal = INTERNAL_MODE_DISABLED;
|
||||||
get_parent()->move_child(p_sibling, this->get_index() + 1);
|
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() {
|
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_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 );
|
//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);
|
p_child->_set_tree(nullptr);
|
||||||
//}
|
//}
|
||||||
|
@ -1175,17 +1234,29 @@ void Node::remove_child(Node *p_child) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int Node::get_child_count() const {
|
int Node::get_child_count(bool p_include_internal) const {
|
||||||
return data.children.size();
|
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 {
|
Node *Node::get_child(int p_index, bool p_include_internal) const {
|
||||||
if (p_index < 0) {
|
if (p_include_internal) {
|
||||||
p_index += data.children.size();
|
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 {
|
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--;
|
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;
|
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;
|
TypedArray<Node> arr;
|
||||||
int cc = get_child_count();
|
int cc = get_child_count(p_include_internal);
|
||||||
arr.resize(cc);
|
arr.resize(cc);
|
||||||
for (int i = 0; i < cc; i++) {
|
for (int i = 0; i < cc; i++) {
|
||||||
arr[i] = get_child(i);
|
arr[i] = get_child(i, p_include_internal);
|
||||||
}
|
}
|
||||||
|
|
||||||
return arr;
|
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("set_name", "name"), &Node::set_name);
|
||||||
ClassDB::bind_method(D_METHOD("get_name"), &Node::get_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("remove_child", "node"), &Node::remove_child);
|
||||||
ClassDB::bind_method(D_METHOD("get_child_count"), &Node::get_child_count);
|
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"), &Node::_get_children);
|
ClassDB::bind_method(D_METHOD("get_children", "include_internal"), &Node::_get_children, DEFVAL(false));
|
||||||
ClassDB::bind_method(D_METHOD("get_child", "idx"), &Node::get_child);
|
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("has_node", "path"), &Node::has_node);
|
||||||
ClassDB::bind_method(D_METHOD("get_node", "path"), &Node::get_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);
|
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("set_owner", "owner"), &Node::set_owner);
|
||||||
ClassDB::bind_method(D_METHOD("get_owner"), &Node::get_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("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"), &Node::print_tree);
|
||||||
ClassDB::bind_method(D_METHOD("print_tree_pretty"), &Node::print_tree_pretty);
|
ClassDB::bind_method(D_METHOD("print_tree_pretty"), &Node::print_tree_pretty);
|
||||||
ClassDB::bind_method(D_METHOD("set_filename", "filename"), &Node::set_filename);
|
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("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("set_network_authority", "id", "recursive"), &Node::set_network_authority, DEFVAL(true));
|
||||||
ClassDB::bind_method(D_METHOD("get_network_master"), &Node::get_network_master);
|
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_multiplayer"), &Node::get_multiplayer);
|
||||||
ClassDB::bind_method(D_METHOD("get_custom_multiplayer"), &Node::get_custom_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_SCRIPTS);
|
||||||
BIND_ENUM_CONSTANT(DUPLICATE_USE_INSTANCING);
|
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("ready"));
|
||||||
ADD_SIGNAL(MethodInfo("renamed"));
|
ADD_SIGNAL(MethodInfo("renamed"));
|
||||||
ADD_SIGNAL(MethodInfo("tree_entered"));
|
ADD_SIGNAL(MethodInfo("tree_entered"));
|
||||||
|
|
|
@ -73,6 +73,12 @@ public:
|
||||||
NAME_CASING_SNAKE_CASE
|
NAME_CASING_SNAKE_CASE
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum InternalMode {
|
||||||
|
INTERNAL_MODE_DISABLED,
|
||||||
|
INTERNAL_MODE_FRONT,
|
||||||
|
INTERNAL_MODE_BACK,
|
||||||
|
};
|
||||||
|
|
||||||
struct Comparator {
|
struct Comparator {
|
||||||
bool operator()(const Node *p_a, const Node *p_b) const { return p_b->is_greater_than(p_a); }
|
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 *parent = nullptr;
|
||||||
Node *owner = nullptr;
|
Node *owner = nullptr;
|
||||||
Vector<Node *> children;
|
Vector<Node *> children;
|
||||||
|
int internal_children_front = 0;
|
||||||
|
int internal_children_back = 0;
|
||||||
int pos = -1;
|
int pos = -1;
|
||||||
int depth = -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.
|
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;
|
ProcessMode process_mode = PROCESS_MODE_INHERIT;
|
||||||
Node *process_owner = nullptr;
|
Node *process_owner = nullptr;
|
||||||
|
|
||||||
int network_master = 1; // Server by default.
|
int network_authority = 1; // Server by default.
|
||||||
Vector<MultiplayerAPI::RPCConfig> rpc_methods;
|
Vector<MultiplayerAPI::RPCConfig> rpc_methods;
|
||||||
|
|
||||||
// Variables used to properly sort the node when processing, ignored otherwise.
|
// 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;
|
void _duplicate_signals(const Node *p_original, Node *p_copy) const;
|
||||||
Node *_duplicate(int p_flags, Map<const Node *, Node *> *r_duplimap = nullptr) 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;
|
Array _get_groups() const;
|
||||||
|
|
||||||
Variant _rpc_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error);
|
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);
|
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;
|
friend class SceneTree;
|
||||||
|
|
||||||
void _set_tree(SceneTree *p_tree);
|
void _set_tree(SceneTree *p_tree);
|
||||||
|
@ -284,12 +295,12 @@ public:
|
||||||
StringName get_name() const;
|
StringName get_name() const;
|
||||||
void set_name(const String &p_name);
|
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 add_sibling(Node *p_sibling, bool p_legible_unique_name = false);
|
||||||
void remove_child(Node *p_child);
|
void remove_child(Node *p_child);
|
||||||
|
|
||||||
int get_child_count() const;
|
int get_child_count(bool p_include_internal = true) const;
|
||||||
Node *get_child(int p_index) const;
|
Node *get_child(int p_index, bool p_include_internal = true) const;
|
||||||
bool has_node(const NodePath &p_path) const;
|
bool has_node(const NodePath &p_path) const;
|
||||||
Node *get_node(const NodePath &p_path) const;
|
Node *get_node(const NodePath &p_path) const;
|
||||||
Node *get_node_or_null(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;
|
int get_persistent_group_count() const;
|
||||||
|
|
||||||
void move_child(Node *p_child, int p_pos);
|
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 raise();
|
||||||
|
|
||||||
void set_owner(Node *p_owner);
|
void set_owner(Node *p_owner);
|
||||||
|
@ -334,7 +346,7 @@ public:
|
||||||
void get_owned_by(Node *p_by, List<Node *> *p_owned);
|
void get_owned_by(Node *p_by, List<Node *> *p_owned);
|
||||||
|
|
||||||
void remove_and_skip();
|
void remove_and_skip();
|
||||||
int get_index() const;
|
int get_index(bool p_include_internal = true) const;
|
||||||
|
|
||||||
Ref<Tween> create_tween();
|
Ref<Tween> create_tween();
|
||||||
|
|
||||||
|
@ -450,9 +462,9 @@ public:
|
||||||
bool is_displayed_folded() const;
|
bool is_displayed_folded() const;
|
||||||
/* NETWORK */
|
/* NETWORK */
|
||||||
|
|
||||||
void set_network_master(int p_peer_id, bool p_recursive = true);
|
void set_network_authority(int p_peer_id, bool p_recursive = true);
|
||||||
int get_network_master() const;
|
int get_network_authority() const;
|
||||||
bool is_network_master() 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
|
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;
|
Vector<MultiplayerAPI::RPCConfig> get_node_rpc_methods() const;
|
||||||
|
|
Loading…
Add table
Reference in a new issue