diff options
Diffstat (limited to 'core/io')
-rw-r--r-- | core/io/file_access.cpp | 37 | ||||
-rw-r--r-- | core/io/ip.cpp | 63 | ||||
-rw-r--r-- | core/io/marshalls.cpp | 7 | ||||
-rw-r--r-- | core/io/multiplayer_api.cpp | 194 | ||||
-rw-r--r-- | core/io/multiplayer_api.h | 21 | ||||
-rw-r--r-- | core/io/multiplayer_peer.cpp | 3 | ||||
-rw-r--r-- | core/io/multiplayer_peer.h | 2 | ||||
-rw-r--r-- | core/io/resource.cpp | 2 | ||||
-rw-r--r-- | core/io/resource_format_binary.cpp | 11 | ||||
-rw-r--r-- | core/io/resource_format_binary.h | 2 |
10 files changed, 293 insertions, 49 deletions
diff --git a/core/io/file_access.cpp b/core/io/file_access.cpp index d21c0bd9a2..e6e79dff8a 100644 --- a/core/io/file_access.cpp +++ b/core/io/file_access.cpp @@ -316,52 +316,53 @@ String FileAccess::get_line() const { } Vector<String> FileAccess::get_csv_line(const String &p_delim) const { - ERR_FAIL_COND_V(p_delim.length() != 1, Vector<String>()); + ERR_FAIL_COND_V_MSG(p_delim.length() != 1, Vector<String>(), "Only single character delimiters are supported to parse CSV lines."); + ERR_FAIL_COND_V_MSG(p_delim[0] == '"', Vector<String>(), "The double quotation mark character (\") is not supported as a delimiter for CSV lines."); - String l; + String line; + + // CSV can support entries with line breaks as long as they are enclosed + // in double quotes. So our "line" might be more than a single line in the + // text file. int qc = 0; do { if (eof_reached()) { break; } - - l += get_line() + "\n"; + line += get_line() + "\n"; qc = 0; - for (int i = 0; i < l.length(); i++) { - if (l[i] == '"') { + for (int i = 0; i < line.length(); i++) { + if (line[i] == '"') { qc++; } } - } while (qc % 2); - l = l.substr(0, l.length() - 1); + // Remove the extraneous newline we've added above. + line = line.substr(0, line.length() - 1); Vector<String> strings; bool in_quote = false; String current; - for (int i = 0; i < l.length(); i++) { - char32_t c = l[i]; - char32_t s[2] = { 0, 0 }; - + for (int i = 0; i < line.length(); i++) { + char32_t c = line[i]; + // A delimiter ends the current entry, unless it's in a quoted string. if (!in_quote && c == p_delim[0]) { strings.push_back(current); current = String(); } else if (c == '"') { - if (l[i + 1] == '"' && in_quote) { - s[0] = '"'; - current += s; + // Doubled quotes are escapes for intentional quotes in the string. + if (line[i + 1] == '"' && in_quote) { + current += '"'; i++; } else { in_quote = !in_quote; } } else { - s[0] = c; - current += s; + current += c; } } - strings.push_back(current); return strings; diff --git a/core/io/ip.cpp b/core/io/ip.cpp index cd1b6d1994..e3102508a3 100644 --- a/core/io/ip.cpp +++ b/core/io/ip.cpp @@ -83,8 +83,23 @@ struct _IP_ResolverPrivate { continue; } - IP::get_singleton()->_resolve_hostname(queue[i].response, queue[i].hostname, queue[i].type); - queue[i].status.set(queue[i].response.is_empty() ? IP::RESOLVER_STATUS_ERROR : IP::RESOLVER_STATUS_DONE); + mutex.lock(); + List<IPAddress> response; + String hostname = queue[i].hostname; + IP::Type type = queue[i].type; + mutex.unlock(); + + // We should not lock while resolving the hostname, + // only when modifying the queue. + IP::get_singleton()->_resolve_hostname(response, hostname, type); + + MutexLock lock(mutex); + // Could have been completed by another function, or deleted. + if (queue[i].status.get() != IP::RESOLVER_STATUS_WAITING) { + continue; + } + queue[i].response = response; + queue[i].status.set(response.is_empty() ? IP::RESOLVER_STATUS_ERROR : IP::RESOLVER_STATUS_DONE); } } @@ -93,8 +108,6 @@ struct _IP_ResolverPrivate { while (!ipr->thread_abort) { ipr->sem.wait(); - - MutexLock lock(ipr->mutex); ipr->resolve_queues(); } } @@ -107,17 +120,23 @@ struct _IP_ResolverPrivate { }; IPAddress IP::resolve_hostname(const String &p_hostname, IP::Type p_type) { - MutexLock lock(resolver->mutex); - List<IPAddress> res; - String key = _IP_ResolverPrivate::get_cache_key(p_hostname, p_type); + + resolver->mutex.lock(); if (resolver->cache.has(key)) { res = resolver->cache[key]; } else { + // This should be run unlocked so the resolver thread can keep + // resolving other requests. + resolver->mutex.unlock(); _resolve_hostname(res, p_hostname, p_type); + resolver->mutex.lock(); + // We might be overriding another result, but we don't care (they are the + // same hostname). resolver->cache[key] = res; } + resolver->mutex.unlock(); for (int i = 0; i < res.size(); ++i) { if (res[i].is_valid()) { @@ -128,14 +147,23 @@ IPAddress IP::resolve_hostname(const String &p_hostname, IP::Type p_type) { } Array IP::resolve_hostname_addresses(const String &p_hostname, Type p_type) { - MutexLock lock(resolver->mutex); - + List<IPAddress> res; String key = _IP_ResolverPrivate::get_cache_key(p_hostname, p_type); - if (!resolver->cache.has(key)) { - _resolve_hostname(resolver->cache[key], p_hostname, p_type); - } - List<IPAddress> res = resolver->cache[key]; + resolver->mutex.lock(); + if (resolver->cache.has(key)) { + res = resolver->cache[key]; + } else { + // This should be run unlocked so the resolver thread can keep resolving + // other requests. + resolver->mutex.unlock(); + _resolve_hostname(res, p_hostname, p_type); + resolver->mutex.lock(); + // We might be overriding another result, but we don't care (they are the + // same hostname). + resolver->cache[key] = res; + } + resolver->mutex.unlock(); Array result; for (int i = 0; i < res.size(); ++i) { @@ -178,13 +206,12 @@ IP::ResolverID IP::resolve_hostname_queue_item(const String &p_hostname, IP::Typ IP::ResolverStatus IP::get_resolve_item_status(ResolverID p_id) const { ERR_FAIL_INDEX_V(p_id, IP::RESOLVER_MAX_QUERIES, IP::RESOLVER_STATUS_NONE); - MutexLock lock(resolver->mutex); - - if (resolver->queue[p_id].status.get() == IP::RESOLVER_STATUS_NONE) { + IP::ResolverStatus res = resolver->queue[p_id].status.get(); + if (res == IP::RESOLVER_STATUS_NONE) { ERR_PRINT("Condition status == IP::RESOLVER_STATUS_NONE"); return IP::RESOLVER_STATUS_NONE; } - return resolver->queue[p_id].status.get(); + return res; } IPAddress IP::get_resolve_item_address(ResolverID p_id) const { @@ -230,8 +257,6 @@ Array IP::get_resolve_item_addresses(ResolverID p_id) const { void IP::erase_resolve_item(ResolverID p_id) { ERR_FAIL_INDEX(p_id, IP::RESOLVER_MAX_QUERIES); - MutexLock lock(resolver->mutex); - resolver->queue[p_id].status.set(IP::RESOLVER_STATUS_NONE); } diff --git a/core/io/marshalls.cpp b/core/io/marshalls.cpp index 4f85eced93..e7d5b78d14 100644 --- a/core/io/marshalls.cpp +++ b/core/io/marshalls.cpp @@ -746,7 +746,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int } break; case Variant::PACKED_INT64_ARRAY: { ERR_FAIL_COND_V(len < 4, ERR_INVALID_DATA); - int64_t count = decode_uint64(buf); + int32_t count = decode_uint32(buf); buf += 4; len -= 4; ERR_FAIL_MUL_OF(count, 8, ERR_INVALID_DATA); @@ -795,7 +795,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int } break; case Variant::PACKED_FLOAT64_ARRAY: { ERR_FAIL_COND_V(len < 4, ERR_INVALID_DATA); - int64_t count = decode_uint64(buf); + int32_t count = decode_uint32(buf); buf += 4; len -= 4; ERR_FAIL_MUL_OF(count, 8, ERR_INVALID_DATA); @@ -804,7 +804,6 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int Vector<double> data; if (count) { - //const double*rbuf=(const double*)buf; data.resize(count); double *w = data.ptrw(); for (int64_t i = 0; i < count; i++) { @@ -1519,7 +1518,7 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo int datasize = sizeof(int64_t); if (buf) { - encode_uint64(datalen, buf); + encode_uint32(datalen, buf); buf += 4; const int64_t *r = data.ptr(); for (int64_t i = 0; i < datalen; i++) { diff --git a/core/io/multiplayer_api.cpp b/core/io/multiplayer_api.cpp index d4f09b2135..f792dc6cb4 100644 --- a/core/io/multiplayer_api.cpp +++ b/core/io/multiplayer_api.cpp @@ -33,6 +33,7 @@ #include "core/debugger/engine_debugger.h" #include "core/io/marshalls.h" #include "scene/main/node.h" +#include "scene/resources/packed_scene.h" #include <stdint.h> @@ -146,6 +147,7 @@ void MultiplayerAPI::poll() { } void MultiplayerAPI::clear() { + replicated_nodes.clear(); connected_peers.clear(); path_get_cache.clear(); path_send_cache.clear(); @@ -322,6 +324,12 @@ void MultiplayerAPI::_process_packet(int p_from, const uint8_t *p_packet, int p_ case NETWORK_COMMAND_RAW: { _process_raw(p_from, p_packet, p_packet_len); } break; + case NETWORK_COMMAND_SPAWN: { + _process_spawn_despawn(p_from, p_packet, p_packet_len, true); + } break; + case NETWORK_COMMAND_DESPAWN: { + _process_spawn_despawn(p_from, p_packet, p_packet_len, false); + } break; } } @@ -478,6 +486,7 @@ void MultiplayerAPI::_process_simplify_path(int p_from, const uint8_t *p_packet, packet.write[1] = valid_rpc_checksum; encode_cstring(pname.get_data(), &packet.write[2]); + network_peer->set_transfer_channel(0); network_peer->set_transfer_mode(MultiplayerPeer::TRANSFER_MODE_RELIABLE); network_peer->set_target_peer(p_from); network_peer->put_packet(packet.ptr(), packet.size()); @@ -505,6 +514,64 @@ void MultiplayerAPI::_process_confirm_path(int p_from, const uint8_t *p_packet, E->get() = true; } +void MultiplayerAPI::_process_spawn_despawn(int p_from, const uint8_t *p_packet, int p_packet_len, bool p_spawn) { + ERR_FAIL_COND_MSG(p_packet_len < 18, "Invalid spawn packet received"); + int ofs = 1; + ResourceUID::ID id = decode_uint64(&p_packet[ofs]); + ofs += 8; + ERR_FAIL_COND_MSG(!spawnables.has(id), "Invalid spawn ID received " + itos(id)); + + uint32_t node_target = decode_uint32(&p_packet[ofs]); + Node *parent = _process_get_node(p_from, nullptr, node_target, 0); + ofs += 4; + ERR_FAIL_COND_MSG(parent == nullptr, "Invalid packet received. Requested node was not found."); + + uint32_t name_len = decode_uint32(&p_packet[ofs]); + ofs += 4; + ERR_FAIL_COND_MSG(name_len > uint32_t(p_packet_len - ofs), vformat("Invalid spawn packet size: %d, wants: %d", p_packet_len, ofs + name_len)); + ERR_FAIL_COND_MSG(name_len < 1, "Zero spawn name size."); + + const String name = String::utf8((const char *)&p_packet[ofs], name_len); + // We need to make sure no trickery happens here (e.g. despawning a subpath), but we want to allow autogenerated ("@") node names. + ERR_FAIL_COND_MSG(name.validate_node_name() != name.replace("@", ""), vformat("Invalid node name received: '%s'", name)); + ofs += name_len; + PackedByteArray data; + if (p_packet_len > ofs) { + data.resize(p_packet_len - ofs); + memcpy(data.ptrw(), &p_packet[ofs], data.size()); + } + + SpawnMode mode = spawnables[id]; + if (mode == SPAWN_MODE_SERVER && p_from == 1) { + String scene_path = ResourceUID::get_singleton()->get_id_path(id); + if (p_spawn) { + ERR_FAIL_COND_MSG(parent->has_node(name), vformat("Unable to spawn node. Node already exists: %s/%s", parent->get_path(), name)); + RES res = ResourceLoader::load(scene_path); + ERR_FAIL_COND_MSG(!res.is_valid(), "Unable to load scene to spawn at path: " + scene_path); + PackedScene *scene = Object::cast_to<PackedScene>(res.ptr()); + ERR_FAIL_COND(!scene); + Node *node = scene->instantiate(); + ERR_FAIL_COND(!node); + replicated_nodes[node->get_instance_id()] = id; + parent->_add_child_nocheck(node, name); + emit_signal(SNAME("network_spawn"), p_from, id, node, data); + } else { + ERR_FAIL_COND_MSG(!parent->has_node(name), vformat("Path not found: %s/%s", parent->get_path(), name)); + Node *node = parent->get_node(name); + ERR_FAIL_COND_MSG(!replicated_nodes.has(node->get_instance_id()), vformat("Trying to despawn a Node that was not replicated: %s/%s", parent->get_path(), name)); + emit_signal(SNAME("network_despawn"), p_from, id, node, data); + replicated_nodes.erase(node->get_instance_id()); + node->queue_delete(); + } + } else { + if (p_spawn) { + emit_signal(SNAME("network_spawn_request"), p_from, id, parent, name, data); + } else { + emit_signal(SNAME("network_despawn_request"), p_from, id, parent, name, data); + } + } +} + bool MultiplayerAPI::_send_confirm_path(Node *p_node, NodePath p_path, PathSentCache *psc, int p_target) { bool has_all_peers = true; List<int> peers_to_add; // If one is missing, take note to add it. @@ -557,6 +624,7 @@ bool MultiplayerAPI::_send_confirm_path(Node *p_node, NodePath p_path, PathSentC for (int &E : peers_to_add) { network_peer->set_target_peer(E); // To all of you. + network_peer->set_transfer_channel(0); network_peer->set_transfer_mode(MultiplayerPeer::TRANSFER_MODE_RELIABLE); network_peer->put_packet(packet.ptr(), packet.size()); @@ -858,6 +926,7 @@ void MultiplayerAPI::_send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, const #endif // Take chance and set transfer mode, since all send methods will use it. + network_peer->set_transfer_channel(p_config.channel); network_peer->set_transfer_mode(p_config.transfer_mode); if (has_all_peers) { @@ -906,6 +975,16 @@ void MultiplayerAPI::_send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, const void MultiplayerAPI::_add_peer(int p_id) { connected_peers.insert(p_id); path_get_cache.insert(p_id, PathGetCache()); + if (is_network_server()) { + for (const KeyValue<ObjectID, ResourceUID::ID> &E : replicated_nodes) { + // Only server mode adds to replicated_nodes, no need to check it. + Object *obj = ObjectDB::get_instance(E.key); + ERR_CONTINUE(!obj); + Node *node = Object::cast_to<Node>(obj); + ERR_CONTINUE(!node); + _send_spawn_despawn(p_id, E.value, node->get_path(), nullptr, 0, true); + } + } emit_signal(SNAME("network_peer_connected"), p_id); } @@ -921,6 +1000,10 @@ void MultiplayerAPI::_del_peer(int p_id) { PathSentCache *psc = path_send_cache.getptr(E); psc->confirmed_peers.erase(p_id); } + if (p_id == 1) { + // Erase server replicated nodes, but do not queue them for deletion. + replicated_nodes.clear(); + } emit_signal(SNAME("network_peer_disconnected"), p_id); } @@ -996,7 +1079,7 @@ void MultiplayerAPI::rpcp(Node *p_node, int p_peer_id, bool p_unreliable, const ERR_FAIL_COND_MSG(p_peer_id == node_id && !config.sync, "RPC '" + p_method + "' on yourself is not allowed by selected mode."); } -Error MultiplayerAPI::send_bytes(Vector<uint8_t> p_data, int p_to, MultiplayerPeer::TransferMode p_mode) { +Error MultiplayerAPI::send_bytes(Vector<uint8_t> p_data, int p_to, MultiplayerPeer::TransferMode p_mode, int p_channel) { ERR_FAIL_COND_V_MSG(p_data.size() < 1, ERR_INVALID_DATA, "Trying to send an empty raw packet."); ERR_FAIL_COND_V_MSG(!network_peer.is_valid(), ERR_UNCONFIGURED, "Trying to send a raw packet while no network peer is active."); ERR_FAIL_COND_V_MSG(network_peer->get_connection_status() != MultiplayerPeer::CONNECTION_CONNECTED, ERR_UNCONFIGURED, "Trying to send a raw packet via a network peer which is not connected."); @@ -1007,6 +1090,7 @@ Error MultiplayerAPI::send_bytes(Vector<uint8_t> p_data, int p_to, MultiplayerPe memcpy(&packet_cache.write[1], &r[0], p_data.size()); network_peer->set_target_peer(p_to); + network_peer->set_transfer_channel(p_channel); network_peer->set_transfer_mode(p_mode); return network_peer->put_packet(packet_cache.ptr(), p_data.size() + 1); @@ -1063,10 +1147,103 @@ bool MultiplayerAPI::is_object_decoding_allowed() const { return allow_object_decoding; } +Error MultiplayerAPI::spawnable_config(const ResourceUID::ID &p_id, SpawnMode p_mode) { + ERR_FAIL_COND_V(p_mode < SPAWN_MODE_NONE || p_mode > SPAWN_MODE_CUSTOM, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(!ResourceUID::get_singleton()->has_id(p_id), ERR_INVALID_PARAMETER); +#ifdef TOOLS_ENABLED + String path = ResourceUID::get_singleton()->get_id_path(p_id); + RES res = ResourceLoader::load(path); + ERR_FAIL_COND_V(!res->is_class("PackedScene"), ERR_INVALID_PARAMETER); +#endif + if (p_mode == SPAWN_MODE_NONE) { + if (spawnables.has(p_id)) { + spawnables.erase(p_id); + } + } else { + spawnables[p_id] = p_mode; + } + return OK; +} + +Error MultiplayerAPI::send_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const NodePath &p_path, const PackedByteArray &p_data) { + return _send_spawn_despawn(p_peer_id, p_scene_id, p_path, p_data.ptr(), p_data.size(), false); +} + +Error MultiplayerAPI::send_spawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const NodePath &p_path, const PackedByteArray &p_data) { + return _send_spawn_despawn(p_peer_id, p_scene_id, p_path, p_data.ptr(), p_data.size(), true); +} + +Error MultiplayerAPI::_send_spawn_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const NodePath &p_path, const uint8_t *p_data, int p_data_len, bool p_spawn) { + ERR_FAIL_COND_V(!root_node, ERR_UNCONFIGURED); + ERR_FAIL_COND_V_MSG(!spawnables.has(p_scene_id), ERR_INVALID_PARAMETER, vformat("Spawnable not found: %d", p_scene_id)); + + NodePath rel_path = (root_node->get_path()).rel_path_to(p_path); + const Vector<StringName> names = rel_path.get_names(); + ERR_FAIL_COND_V(names.size() < 2, ERR_INVALID_PARAMETER); + + NodePath parent = NodePath(names.subarray(0, names.size() - 2), false); + ERR_FAIL_COND_V_MSG(!root_node->has_node(parent), ERR_INVALID_PARAMETER, "Path not found: " + parent); + + // See if the path is cached. + PathSentCache *psc = path_send_cache.getptr(parent); + if (!psc) { + // Path is not cached, create. + path_send_cache[parent] = PathSentCache(); + psc = path_send_cache.getptr(parent); + psc->id = last_send_cache_id++; + } + _send_confirm_path(root_node->get_node(parent), parent, psc, p_peer_id); + + const CharString cname = String(names[names.size() - 1]).utf8(); + int nlen = encode_cstring(cname.get_data(), nullptr); + MAKE_ROOM(1 + 8 + 4 + 4 + nlen + p_data_len); + uint8_t *ptr = packet_cache.ptrw(); + ptr[0] = p_spawn ? NETWORK_COMMAND_SPAWN : NETWORK_COMMAND_DESPAWN; + int ofs = 1; + ofs += encode_uint64(p_scene_id, &ptr[ofs]); + ofs += encode_uint32(psc->id, &ptr[ofs]); + ofs += encode_uint32(nlen, &ptr[ofs]); + ofs += encode_cstring(cname.get_data(), &ptr[ofs]); + memcpy(&ptr[ofs], p_data, p_data_len); + network_peer->set_target_peer(p_peer_id); + network_peer->set_transfer_channel(0); + network_peer->set_transfer_mode(MultiplayerPeer::TRANSFER_MODE_RELIABLE); + return network_peer->put_packet(ptr, ofs + p_data_len); +} + +void MultiplayerAPI::scene_enter_exit_notify(const String &p_scene, const Node *p_node, bool p_enter) { + if (!has_network_peer()) { + return; + } + ERR_FAIL_COND(!p_node || !p_node->get_parent() || !root_node); + NodePath path = (root_node->get_path()).rel_path_to(p_node->get_parent()->get_path()); + if (path.is_empty()) { + return; + } + ResourceUID::ID id = ResourceLoader::get_resource_uid(p_scene); + if (!spawnables.has(id)) { + return; + } + SpawnMode mode = spawnables[id]; + if (p_enter) { + if (mode == SPAWN_MODE_SERVER && is_network_server()) { + replicated_nodes[p_node->get_instance_id()] = id; + _send_spawn_despawn(0, id, p_node->get_path(), nullptr, 0, true); + } + emit_signal(SNAME("network_spawnable_added"), id, p_node); + } else { + if (mode == SPAWN_MODE_SERVER && is_network_server() && replicated_nodes.has(p_node->get_instance_id())) { + replicated_nodes.erase(p_node->get_instance_id()); + _send_spawn_despawn(0, id, p_node->get_path(), nullptr, 0, false); + } + emit_signal(SNAME("network_spawnable_removed"), id, p_node); + } +} + void MultiplayerAPI::_bind_methods() { ClassDB::bind_method(D_METHOD("set_root_node", "node"), &MultiplayerAPI::set_root_node); ClassDB::bind_method(D_METHOD("get_root_node"), &MultiplayerAPI::get_root_node); - ClassDB::bind_method(D_METHOD("send_bytes", "bytes", "id", "mode"), &MultiplayerAPI::send_bytes, DEFVAL(MultiplayerPeer::TARGET_PEER_BROADCAST), DEFVAL(MultiplayerPeer::TRANSFER_MODE_RELIABLE)); + ClassDB::bind_method(D_METHOD("send_bytes", "bytes", "id", "mode", "channel"), &MultiplayerAPI::send_bytes, DEFVAL(MultiplayerPeer::TARGET_PEER_BROADCAST), DEFVAL(MultiplayerPeer::TRANSFER_MODE_RELIABLE), DEFVAL(0)); ClassDB::bind_method(D_METHOD("has_network_peer"), &MultiplayerAPI::has_network_peer); ClassDB::bind_method(D_METHOD("get_network_peer"), &MultiplayerAPI::get_network_peer); ClassDB::bind_method(D_METHOD("get_network_unique_id"), &MultiplayerAPI::get_network_unique_id); @@ -1081,6 +1258,9 @@ void MultiplayerAPI::_bind_methods() { ClassDB::bind_method(D_METHOD("is_refusing_new_network_connections"), &MultiplayerAPI::is_refusing_new_network_connections); ClassDB::bind_method(D_METHOD("set_allow_object_decoding", "enable"), &MultiplayerAPI::set_allow_object_decoding); ClassDB::bind_method(D_METHOD("is_object_decoding_allowed"), &MultiplayerAPI::is_object_decoding_allowed); + ClassDB::bind_method(D_METHOD("spawnable_config", "scene_id", "spawn_mode"), &MultiplayerAPI::spawnable_config); + ClassDB::bind_method(D_METHOD("send_despawn", "peer_id", "scene_id", "path", "data"), &MultiplayerAPI::send_despawn, DEFVAL(PackedByteArray())); + ClassDB::bind_method(D_METHOD("send_spawn", "peer_id", "scene_id", "path", "data"), &MultiplayerAPI::send_spawn, DEFVAL(PackedByteArray())); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_object_decoding"), "set_allow_object_decoding", "is_object_decoding_allowed"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "refuse_new_network_connections"), "set_refuse_new_network_connections", "is_refusing_new_network_connections"); @@ -1094,11 +1274,21 @@ void MultiplayerAPI::_bind_methods() { ADD_SIGNAL(MethodInfo("connected_to_server")); ADD_SIGNAL(MethodInfo("connection_failed")); ADD_SIGNAL(MethodInfo("server_disconnected")); + ADD_SIGNAL(MethodInfo("network_despawn", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node"), PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data"))); + ADD_SIGNAL(MethodInfo("network_spawn", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node"), PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data"))); + ADD_SIGNAL(MethodInfo("network_despawn_request", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "parent", PROPERTY_HINT_RESOURCE_TYPE, "Node"), PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data"))); + ADD_SIGNAL(MethodInfo("network_spawn_request", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "parent", PROPERTY_HINT_RESOURCE_TYPE, "Node"), PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data"))); + ADD_SIGNAL(MethodInfo("network_spawnable_added", PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node"))); + ADD_SIGNAL(MethodInfo("network_spawnable_removed", PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node"))); BIND_ENUM_CONSTANT(RPC_MODE_DISABLED); BIND_ENUM_CONSTANT(RPC_MODE_REMOTE); BIND_ENUM_CONSTANT(RPC_MODE_MASTER); BIND_ENUM_CONSTANT(RPC_MODE_PUPPET); + + BIND_ENUM_CONSTANT(SPAWN_MODE_NONE); + BIND_ENUM_CONSTANT(SPAWN_MODE_SERVER); + BIND_ENUM_CONSTANT(SPAWN_MODE_CUSTOM); } MultiplayerAPI::MultiplayerAPI() { diff --git a/core/io/multiplayer_api.h b/core/io/multiplayer_api.h index cc994a9852..9552a0bf35 100644 --- a/core/io/multiplayer_api.h +++ b/core/io/multiplayer_api.h @@ -32,6 +32,7 @@ #define MULTIPLAYER_API_H #include "core/io/multiplayer_peer.h" +#include "core/io/resource_uid.h" #include "core/object/ref_counted.h" class MultiplayerAPI : public RefCounted { @@ -45,6 +46,12 @@ public: RPC_MODE_PUPPET, // Using rpc() on it will call method for all puppets }; + enum SpawnMode { + SPAWN_MODE_NONE, + SPAWN_MODE_SERVER, + SPAWN_MODE_CUSTOM, + }; + struct RPCConfig { StringName name; RPCMode rpc_mode = RPC_MODE_DISABLED; @@ -82,10 +89,12 @@ private: }; Ref<MultiplayerPeer> network_peer; + Map<ResourceUID::ID, SpawnMode> spawnables; int rpc_sender_id = 0; Set<int> connected_peers; HashMap<NodePath, PathSentCache> path_send_cache; Map<int, PathGetCache> path_get_cache; + Map<ObjectID, ResourceUID::ID> replicated_nodes; int last_send_cache_id; Vector<uint8_t> packet_cache; Node *root_node = nullptr; @@ -97,6 +106,7 @@ protected: void _process_packet(int p_from, const uint8_t *p_packet, int p_packet_len); void _process_simplify_path(int p_from, const uint8_t *p_packet, int p_packet_len); void _process_confirm_path(int p_from, const uint8_t *p_packet, int p_packet_len); + void _process_spawn_despawn(int p_from, const uint8_t *p_packet, int p_packet_len, bool p_spawn); Node *_process_get_node(int p_from, const uint8_t *p_packet, uint32_t p_node_target, int p_packet_len); void _process_rpc(Node *p_node, const uint16_t p_rpc_method_id, int p_from, const uint8_t *p_packet, int p_packet_len, int p_offset); void _process_raw(int p_from, const uint8_t *p_packet, int p_packet_len); @@ -113,6 +123,8 @@ public: NETWORK_COMMAND_SIMPLIFY_PATH, NETWORK_COMMAND_CONFIRM_PATH, NETWORK_COMMAND_RAW, + NETWORK_COMMAND_SPAWN, + NETWORK_COMMAND_DESPAWN, }; enum NetworkNodeIdCompression { @@ -132,7 +144,7 @@ public: Node *get_root_node(); void set_network_peer(const Ref<MultiplayerPeer> &p_peer); Ref<MultiplayerPeer> get_network_peer() const; - Error send_bytes(Vector<uint8_t> p_data, int p_to = MultiplayerPeer::TARGET_PEER_BROADCAST, MultiplayerPeer::TransferMode p_mode = MultiplayerPeer::TRANSFER_MODE_RELIABLE); + Error send_bytes(Vector<uint8_t> p_data, int p_to = MultiplayerPeer::TARGET_PEER_BROADCAST, MultiplayerPeer::TransferMode p_mode = MultiplayerPeer::TRANSFER_MODE_RELIABLE, int p_channel = 0); // Called by Node.rpc void rpcp(Node *p_node, int p_peer_id, bool p_unreliable, const StringName &p_method, const Variant **p_arg, int p_argcount); @@ -154,10 +166,17 @@ public: void set_allow_object_decoding(bool p_enable); bool is_object_decoding_allowed() const; + Error spawnable_config(const ResourceUID::ID &p_id, SpawnMode p_mode); + Error send_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const NodePath &p_path, const PackedByteArray &p_data = PackedByteArray()); + Error send_spawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const NodePath &p_path, const PackedByteArray &p_data = PackedByteArray()); + Error _send_spawn_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const NodePath &p_path, const uint8_t *p_data, int p_data_len, bool p_spawn); + void scene_enter_exit_notify(const String &p_scene, const Node *p_node, bool p_enter); + MultiplayerAPI(); ~MultiplayerAPI(); }; VARIANT_ENUM_CAST(MultiplayerAPI::RPCMode); +VARIANT_ENUM_CAST(MultiplayerAPI::SpawnMode); #endif // MULTIPLAYER_API_H diff --git a/core/io/multiplayer_peer.cpp b/core/io/multiplayer_peer.cpp index 8b3b1eef8e..83cf24d7e3 100644 --- a/core/io/multiplayer_peer.cpp +++ b/core/io/multiplayer_peer.cpp @@ -54,6 +54,8 @@ uint32_t MultiplayerPeer::generate_unique_id() const { } void MultiplayerPeer::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_transfer_channel", "channel"), &MultiplayerPeer::set_transfer_channel); + ClassDB::bind_method(D_METHOD("get_transfer_channel"), &MultiplayerPeer::get_transfer_channel); ClassDB::bind_method(D_METHOD("set_transfer_mode", "mode"), &MultiplayerPeer::set_transfer_mode); ClassDB::bind_method(D_METHOD("get_transfer_mode"), &MultiplayerPeer::get_transfer_mode); ClassDB::bind_method(D_METHOD("set_target_peer", "id"), &MultiplayerPeer::set_target_peer); @@ -71,6 +73,7 @@ void MultiplayerPeer::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "refuse_new_connections"), "set_refuse_new_connections", "is_refusing_new_connections"); ADD_PROPERTY(PropertyInfo(Variant::INT, "transfer_mode", PROPERTY_HINT_ENUM, "Unreliable,Unreliable Ordered,Reliable"), "set_transfer_mode", "get_transfer_mode"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "transfer_channel", PROPERTY_HINT_RANGE, "0,255,1"), "set_transfer_channel", "get_transfer_channel"); BIND_ENUM_CONSTANT(TRANSFER_MODE_UNRELIABLE); BIND_ENUM_CONSTANT(TRANSFER_MODE_UNRELIABLE_ORDERED); diff --git a/core/io/multiplayer_peer.h b/core/io/multiplayer_peer.h index 91a3ad7954..7ca4e7930b 100644 --- a/core/io/multiplayer_peer.h +++ b/core/io/multiplayer_peer.h @@ -56,6 +56,8 @@ public: CONNECTION_CONNECTED, }; + virtual void set_transfer_channel(int p_channel) = 0; + virtual int get_transfer_channel() const = 0; virtual void set_transfer_mode(TransferMode p_mode) = 0; virtual TransferMode get_transfer_mode() const = 0; virtual void set_target_peer(int p_peer_id) = 0; diff --git a/core/io/resource.cpp b/core/io/resource.cpp index 727611a573..0262655927 100644 --- a/core/io/resource.cpp +++ b/core/io/resource.cpp @@ -552,5 +552,7 @@ void ResourceCache::dump(const char *p_file, bool p_short) { } lock.read_unlock(); +#else + WARN_PRINT("ResourceCache::dump only with in debug builds."); #endif } diff --git a/core/io/resource_format_binary.cpp b/core/io/resource_format_binary.cpp index a3ebc32cc5..00d4d093da 100644 --- a/core/io/resource_format_binary.cpp +++ b/core/io/resource_format_binary.cpp @@ -903,10 +903,11 @@ void ResourceLoaderBinary::open(FileAccess *p_f, bool p_no_resources, bool p_kee if (using_uids) { uid = f->get_64(); } else { + f->get_64(); // skip over uid field uid = ResourceUID::INVALID_ID; } - for (int i = 0; i < 5; i++) { + for (int i = 0; i < ResourceFormatSaverBinaryInstance::RESERVED_FIELDS; i++) { f->get_32(); //skip a few reserved fields } @@ -1209,8 +1210,8 @@ Error ResourceFormatLoaderBinary::rename_dependencies(const String &p_path, cons fw->store_32(flags); fw->store_64(uid_data); - for (int i = 0; i < 5; i++) { - f->store_32(0); // reserved + for (int i = 0; i < ResourceFormatSaverBinaryInstance::RESERVED_FIELDS; i++) { + fw->store_32(0); // reserved f->get_32(); } @@ -1264,7 +1265,7 @@ Error ResourceFormatLoaderBinary::rename_dependencies(const String &p_path, cons if (using_uids) { ResourceUID::ID uid = ResourceSaver::get_resource_id_for_path(full_path); - f->store_64(uid); + fw->store_64(uid); } } @@ -1902,7 +1903,7 @@ Error ResourceFormatSaverBinaryInstance::save(const String &p_path, const RES &p f->store_32(FORMAT_FLAG_NAMED_SCENE_IDS | FORMAT_FLAG_UIDS); ResourceUID::ID uid = ResourceSaver::get_resource_id_for_path(p_path, true); f->store_64(uid); - for (int i = 0; i < 5; i++) { + for (int i = 0; i < ResourceFormatSaverBinaryInstance::RESERVED_FIELDS; i++) { f->store_32(0); // reserved } diff --git a/core/io/resource_format_binary.h b/core/io/resource_format_binary.h index ac964d2053..a6e6d1848e 100644 --- a/core/io/resource_format_binary.h +++ b/core/io/resource_format_binary.h @@ -164,6 +164,8 @@ public: enum { FORMAT_FLAG_NAMED_SCENE_IDS = 1, FORMAT_FLAG_UIDS = 2, + // Amount of reserved 32-bit fields in resource header + RESERVED_FIELDS = 11 }; Error save(const String &p_path, const RES &p_resource, uint32_t p_flags = 0); static void write_variant(FileAccess *f, const Variant &p_property, Map<RES, int> &resource_map, Map<RES, int> &external_resources, Map<StringName, int> &string_map, const PropertyInfo &p_hint = PropertyInfo()); |