diff options
Diffstat (limited to 'core')
37 files changed, 1073 insertions, 80 deletions
diff --git a/core/config/engine.cpp b/core/config/engine.cpp index 495670bc88..d8fbb50a75 100644 --- a/core/config/engine.cpp +++ b/core/config/engine.cpp @@ -199,17 +199,41 @@ bool Engine::is_printing_error_messages() const { } void Engine::add_singleton(const Singleton &p_singleton) { + ERR_FAIL_COND_MSG(singleton_ptrs.has(p_singleton.name), "Can't register singleton that already exists: " + String(p_singleton.name)); singletons.push_back(p_singleton); singleton_ptrs[p_singleton.name] = p_singleton.ptr; } -Object *Engine::get_singleton_object(const String &p_name) const { +Object *Engine::get_singleton_object(const StringName &p_name) const { const Map<StringName, Object *>::Element *E = singleton_ptrs.find(p_name); - ERR_FAIL_COND_V_MSG(!E, nullptr, "Failed to retrieve non-existent singleton '" + p_name + "'."); + ERR_FAIL_COND_V_MSG(!E, nullptr, "Failed to retrieve non-existent singleton '" + String(p_name) + "'."); return E->get(); } -bool Engine::has_singleton(const String &p_name) const { +bool Engine::is_singleton_user_created(const StringName &p_name) const { + ERR_FAIL_COND_V(!singleton_ptrs.has(p_name), false); + + for (const Singleton &E : singletons) { + if (E.name == p_name && E.user_created) { + return true; + } + } + + return false; +} +void Engine::remove_singleton(const StringName &p_name) { + ERR_FAIL_COND(!singleton_ptrs.has(p_name)); + + for (List<Singleton>::Element *E = singletons.front(); E; E = E->next()) { + if (E->get().name == p_name) { + singletons.erase(E); + singleton_ptrs.erase(p_name); + return; + } + } +} + +bool Engine::has_singleton(const StringName &p_name) const { return singleton_ptrs.has(p_name); } diff --git a/core/config/engine.h b/core/config/engine.h index e6b5df2d5a..ae33acede2 100644 --- a/core/config/engine.h +++ b/core/config/engine.h @@ -42,6 +42,7 @@ public: StringName name; Object *ptr; StringName class_name; //used for binding generation hinting + bool user_created = false; Singleton(const StringName &p_name = StringName(), Object *p_ptr = nullptr, const StringName &p_class_name = StringName()); }; @@ -109,8 +110,10 @@ public: void add_singleton(const Singleton &p_singleton); void get_singletons(List<Singleton> *p_singletons); - bool has_singleton(const String &p_name) const; - Object *get_singleton_object(const String &p_name) const; + bool has_singleton(const StringName &p_name) const; + Object *get_singleton_object(const StringName &p_name) const; + void remove_singleton(const StringName &p_name); + bool is_singleton_user_created(const StringName &p_name) const; #ifdef TOOLS_ENABLED _FORCE_INLINE_ void set_editor_hint(bool p_enabled) { editor_hint = p_enabled; } diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp index c5e6c6d685..03892d1d4f 100644 --- a/core/config/project_settings.cpp +++ b/core/config/project_settings.cpp @@ -1006,7 +1006,7 @@ bool ProjectSettings::has_custom_feature(const String &p_feature) const { return custom_features.has(p_feature); } -Map<StringName, ProjectSettings::AutoloadInfo> ProjectSettings::get_autoload_list() const { +OrderedHashMap<StringName, ProjectSettings::AutoloadInfo> ProjectSettings::get_autoload_list() const { return autoloads; } diff --git a/core/config/project_settings.h b/core/config/project_settings.h index ed8fb19fa0..7e93f26f0d 100644 --- a/core/config/project_settings.h +++ b/core/config/project_settings.h @@ -33,6 +33,7 @@ #include "core/object/class_db.h" #include "core/os/thread_safe.h" +#include "core/templates/ordered_hash_map.h" #include "core/templates/set.h" class ProjectSettings : public Object { @@ -91,7 +92,7 @@ protected: Set<String> custom_features; Map<StringName, StringName> feature_overrides; - Map<StringName, AutoloadInfo> autoloads; + OrderedHashMap<StringName, AutoloadInfo> autoloads; bool _set(const StringName &p_name, const Variant &p_value); bool _get(const StringName &p_name, Variant &r_ret) const; @@ -168,7 +169,7 @@ public: bool has_custom_feature(const String &p_feature) const; - Map<StringName, AutoloadInfo> get_autoload_list() const; + OrderedHashMap<StringName, AutoloadInfo> get_autoload_list() const; void add_autoload(const AutoloadInfo &p_autoload); void remove_autoload(const StringName &p_autoload); bool has_autoload(const StringName &p_autoload) const; diff --git a/core/core_bind.cpp b/core/core_bind.cpp index efb4b84716..fd5b3bb731 100644 --- a/core/core_bind.cpp +++ b/core/core_bind.cpp @@ -1504,7 +1504,7 @@ String Directory::get_current_dir() { Error Directory::make_dir(String p_dir) { ERR_FAIL_COND_V_MSG(!d, ERR_UNCONFIGURED, "Directory is not configured properly."); - if (!p_dir.is_rel_path()) { + if (!p_dir.is_relative_path()) { DirAccess *d = DirAccess::create_for_path(p_dir); Error err = d->make_dir(p_dir); memdelete(d); @@ -1515,7 +1515,7 @@ Error Directory::make_dir(String p_dir) { Error Directory::make_dir_recursive(String p_dir) { ERR_FAIL_COND_V_MSG(!d, ERR_UNCONFIGURED, "Directory is not configured properly."); - if (!p_dir.is_rel_path()) { + if (!p_dir.is_relative_path()) { DirAccess *d = DirAccess::create_for_path(p_dir); Error err = d->make_dir_recursive(p_dir); memdelete(d); @@ -1526,7 +1526,7 @@ Error Directory::make_dir_recursive(String p_dir) { bool Directory::file_exists(String p_file) { ERR_FAIL_COND_V_MSG(!d, false, "Directory is not configured properly."); - if (!p_file.is_rel_path()) { + if (!p_file.is_relative_path()) { return FileAccess::exists(p_file); } @@ -1535,7 +1535,7 @@ bool Directory::file_exists(String p_file) { bool Directory::dir_exists(String p_dir) { ERR_FAIL_COND_V_MSG(!d, false, "Directory is not configured properly."); - if (!p_dir.is_rel_path()) { + if (!p_dir.is_relative_path()) { DirAccess *d = DirAccess::create_for_path(p_dir); bool exists = d->dir_exists(p_dir); memdelete(d); @@ -1559,7 +1559,7 @@ Error Directory::rename(String p_from, String p_to) { ERR_FAIL_COND_V_MSG(!is_open(), ERR_UNCONFIGURED, "Directory must be opened before use."); ERR_FAIL_COND_V_MSG(p_from.is_empty() || p_from == "." || p_from == "..", ERR_INVALID_PARAMETER, "Invalid path to rename."); - if (!p_from.is_rel_path()) { + if (!p_from.is_relative_path()) { DirAccess *d = DirAccess::create_for_path(p_from); ERR_FAIL_COND_V_MSG(!d->file_exists(p_from) && !d->dir_exists(p_from), ERR_DOES_NOT_EXIST, "File or directory does not exist."); Error err = d->rename(p_from, p_to); @@ -1573,7 +1573,7 @@ Error Directory::rename(String p_from, String p_to) { Error Directory::remove(String p_name) { ERR_FAIL_COND_V_MSG(!is_open(), ERR_UNCONFIGURED, "Directory must be opened before use."); - if (!p_name.is_rel_path()) { + if (!p_name.is_relative_path()) { DirAccess *d = DirAccess::create_for_path(p_name); Error err = d->remove(p_name); memdelete(d); @@ -2165,14 +2165,41 @@ bool Engine::is_in_physics_frame() const { return ::Engine::get_singleton()->is_in_physics_frame(); } -bool Engine::has_singleton(const String &p_name) const { +bool Engine::has_singleton(const StringName &p_name) const { return ::Engine::get_singleton()->has_singleton(p_name); } -Object *Engine::get_singleton_object(const String &p_name) const { +Object *Engine::get_singleton_object(const StringName &p_name) const { return ::Engine::get_singleton()->get_singleton_object(p_name); } +void Engine::register_singleton(const StringName &p_name, Object *p_object) { + ERR_FAIL_COND_MSG(has_singleton(p_name), "Singleton already registered: " + String(p_name)); + ERR_FAIL_COND_MSG(p_name.operator String().is_valid_identifier(), "Singleton name is not a valid identifier: " + String(p_name)); + ::Engine::Singleton s; + s.class_name = p_name; + s.name = p_name; + s.ptr = p_object; + s.user_created = true; + ::Engine::get_singleton()->add_singleton(s); + ; +} +void Engine::unregister_singleton(const StringName &p_name) { + ERR_FAIL_COND_MSG(!has_singleton(p_name), "Attempt to remove unregisteres singleton: " + String(p_name)); + ERR_FAIL_COND_MSG(!::Engine::get_singleton()->is_singleton_user_created(p_name), "Attempt to remove non-user created singleton: " + String(p_name)); + ::Engine::get_singleton()->remove_singleton(p_name); +} + +Vector<String> Engine::get_singleton_list() const { + List<::Engine::Singleton> singletons; + ::Engine::get_singleton()->get_singletons(&singletons); + Vector<String> ret; + for (List<::Engine::Singleton>::Element *E = singletons.front(); E; E = E->next()) { + ret.push_back(E->get().name); + } + return ret; +} + void Engine::set_editor_hint(bool p_enabled) { ::Engine::get_singleton()->set_editor_hint(p_enabled); } @@ -2220,6 +2247,10 @@ void Engine::_bind_methods() { ClassDB::bind_method(D_METHOD("has_singleton", "name"), &Engine::has_singleton); ClassDB::bind_method(D_METHOD("get_singleton", "name"), &Engine::get_singleton_object); + ClassDB::bind_method(D_METHOD("register_singleton", "name", "instance"), &Engine::register_singleton); + ClassDB::bind_method(D_METHOD("unregister_singleton", "name"), &Engine::unregister_singleton); + ClassDB::bind_method(D_METHOD("get_singleton_list"), &Engine::get_singleton_list); + ClassDB::bind_method(D_METHOD("set_editor_hint", "enabled"), &Engine::set_editor_hint); ClassDB::bind_method(D_METHOD("is_editor_hint"), &Engine::is_editor_hint); diff --git a/core/core_bind.h b/core/core_bind.h index 1dbe49f418..a6fac63edd 100644 --- a/core/core_bind.h +++ b/core/core_bind.h @@ -639,8 +639,11 @@ public: bool is_in_physics_frame() const; - bool has_singleton(const String &p_name) const; - Object *get_singleton_object(const String &p_name) const; + bool has_singleton(const StringName &p_name) const; + Object *get_singleton_object(const StringName &p_name) const; + void register_singleton(const StringName &p_name, Object *p_object); + void unregister_singleton(const StringName &p_name); + Vector<String> get_singleton_list() const; void set_editor_hint(bool p_enabled); bool is_editor_hint() const; diff --git a/core/doc_data.h b/core/doc_data.h index a3011fe275..19dec71927 100644 --- a/core/doc_data.h +++ b/core/doc_data.h @@ -67,6 +67,7 @@ public: String qualifiers; String description; Vector<ArgumentDoc> arguments; + Vector<int> errors_returned; bool operator<(const MethodDoc &p_method) const { if (name == p_method.name) { // Must be a constructor since there is no overloading. diff --git a/core/extension/native_extension.cpp b/core/extension/native_extension.cpp index 0556c9c84d..a3cd7ca14c 100644 --- a/core/extension/native_extension.cpp +++ b/core/extension/native_extension.cpp @@ -35,6 +35,8 @@ #include "core/object/method_bind.h" #include "core/os/os.h" +const char *NativeExtension::EXTENSION_LIST_CONFIG_FILE = "res://.godot/extension_list.cfg"; + class NativeExtensionMethodBind : public MethodBind { GDNativeExtensionClassMethodCall call_func; GDNativeExtensionClassMethodPtrCall ptrcall_func; diff --git a/core/extension/native_extension.h b/core/extension/native_extension.h index a961b21cc9..b661381d64 100644 --- a/core/extension/native_extension.h +++ b/core/extension/native_extension.h @@ -60,6 +60,8 @@ protected: static void _bind_methods(); public: + static const char *EXTENSION_LIST_CONFIG_FILE; + Error open_library(const String &p_path, const String &p_entry_symbol); void close_library(); diff --git a/core/extension/native_extension_manager.cpp b/core/extension/native_extension_manager.cpp index 7be2593845..8b7a9df4f1 100644 --- a/core/extension/native_extension_manager.cpp +++ b/core/extension/native_extension_manager.cpp @@ -29,6 +29,7 @@ /*************************************************************************/ #include "native_extension_manager.h" +#include "core/io/file_access.h" NativeExtensionManager::LoadStatus NativeExtensionManager::load_extension(const String &p_path) { if (native_extension_map.has(p_path)) { @@ -76,6 +77,11 @@ NativeExtensionManager::LoadStatus NativeExtensionManager::unload_extension(cons native_extension_map.erase(p_path); return LOAD_STATUS_OK; } + +bool NativeExtensionManager::is_extension_loaded(const String &p_path) const { + return native_extension_map.has(p_path); +} + Vector<String> NativeExtensionManager::get_loaded_extensions() const { Vector<String> ret; for (const Map<String, Ref<NativeExtension>>::Element *E = native_extension_map.front(); E; E = E->next()) { @@ -105,6 +111,17 @@ void NativeExtensionManager::deinitialize_extensions(NativeExtension::Initializa level = int32_t(p_level) - 1; } +void NativeExtensionManager::load_extensions() { + FileAccessRef f = FileAccess::open(NativeExtension::EXTENSION_LIST_CONFIG_FILE, FileAccess::READ); + while (f && !f->eof_reached()) { + String s = f->get_line().strip_edges(); + if (s != String()) { + LoadStatus err = load_extension(s); + ERR_CONTINUE_MSG(err == LOAD_STATUS_FAILED, "Error loading extension: " + s); + } + } +} + NativeExtensionManager *NativeExtensionManager::get_singleton() { return singleton; } @@ -112,6 +129,8 @@ void NativeExtensionManager::_bind_methods() { ClassDB::bind_method(D_METHOD("load_extension", "path"), &NativeExtensionManager::load_extension); ClassDB::bind_method(D_METHOD("reload_extension", "path"), &NativeExtensionManager::reload_extension); ClassDB::bind_method(D_METHOD("unload_extension", "path"), &NativeExtensionManager::unload_extension); + ClassDB::bind_method(D_METHOD("is_extension_loaded", "path"), &NativeExtensionManager::is_extension_loaded); + ClassDB::bind_method(D_METHOD("get_loaded_extensions"), &NativeExtensionManager::get_loaded_extensions); ClassDB::bind_method(D_METHOD("get_extension", "path"), &NativeExtensionManager::get_extension); diff --git a/core/extension/native_extension_manager.h b/core/extension/native_extension_manager.h index 78465bd5cf..89ccd155fe 100644 --- a/core/extension/native_extension_manager.h +++ b/core/extension/native_extension_manager.h @@ -55,6 +55,7 @@ public: LoadStatus load_extension(const String &p_path); LoadStatus reload_extension(const String &p_path); LoadStatus unload_extension(const String &p_path); + bool is_extension_loaded(const String &p_path) const; Vector<String> get_loaded_extensions() const; Ref<NativeExtension> get_extension(const String &p_path); @@ -63,6 +64,8 @@ public: static NativeExtensionManager *get_singleton(); + void load_extensions(); + NativeExtensionManager(); }; diff --git a/core/io/config_file.cpp b/core/io/config_file.cpp index aeaf25f321..49fa73dab2 100644 --- a/core/io/config_file.cpp +++ b/core/io/config_file.cpp @@ -188,7 +188,7 @@ Error ConfigFile::_internal_save(FileAccess *file) { for (OrderedHashMap<String, Variant>::Element F = E.get().front(); F; F = F.next()) { String vstr; VariantWriter::write_to_string(F.get(), vstr); - file->store_string(F.key() + "=" + vstr + "\n"); + file->store_string(F.key().property_name_encode() + "=" + vstr + "\n"); } } @@ -315,6 +315,8 @@ void ConfigFile::_bind_methods() { ClassDB::bind_method(D_METHOD("parse", "data"), &ConfigFile::parse); ClassDB::bind_method(D_METHOD("save", "path"), &ConfigFile::save); + BIND_METHOD_ERR_RETURN_DOC("load", ERR_FILE_CANT_OPEN); + ClassDB::bind_method(D_METHOD("load_encrypted", "path", "key"), &ConfigFile::load_encrypted); ClassDB::bind_method(D_METHOD("load_encrypted_pass", "path", "password"), &ConfigFile::load_encrypted_pass); diff --git a/core/io/dir_access.cpp b/core/io/dir_access.cpp index 8234adea06..3bff0a3fd5 100644 --- a/core/io/dir_access.cpp +++ b/core/io/dir_access.cpp @@ -135,7 +135,7 @@ Error DirAccess::make_dir_recursive(String p_dir) { String full_dir; - if (p_dir.is_rel_path()) { + if (p_dir.is_relative_path()) { //append current full_dir = get_current_dir().plus_file(p_dir); @@ -345,7 +345,7 @@ Error DirAccess::_copy_dir(DirAccess *p_target_da, String p_to, int p_chmod_flag dirs.push_back(n); } else { const String &rel_path = n; - if (!n.is_rel_path()) { + if (!n.is_relative_path()) { list_dir_end(); return ERR_BUG; } diff --git a/core/io/http_client_tcp.cpp b/core/io/http_client_tcp.cpp index f291086808..b3d35b3603 100644 --- a/core/io/http_client_tcp.cpp +++ b/core/io/http_client_tcp.cpp @@ -45,6 +45,8 @@ Error HTTPClientTCP::connect_to_host(const String &p_host, int p_port, bool p_ss conn_port = p_port; conn_host = p_host; + ip_candidates.clear(); + ssl = p_ssl; ssl_verify_host = p_verify_host; @@ -234,6 +236,7 @@ void HTTPClientTCP::close() { resolving = IP::RESOLVER_INVALID_ID; } + ip_candidates.clear(); response_headers.clear(); response_str.clear(); body_size = -1; @@ -256,10 +259,17 @@ Error HTTPClientTCP::poll() { return OK; // Still resolving case IP::RESOLVER_STATUS_DONE: { - IPAddress host = IP::get_singleton()->get_resolve_item_address(resolving); - Error err = tcp_connection->connect_to_host(host, conn_port); + ip_candidates = IP::get_singleton()->get_resolve_item_addresses(resolving); IP::get_singleton()->erase_resolve_item(resolving); resolving = IP::RESOLVER_INVALID_ID; + + Error err = ERR_BUG; // Should be at least one entry. + while (ip_candidates.size() > 0) { + err = tcp_connection->connect_to_host(ip_candidates.front(), conn_port); + if (err == OK) { + break; + } + } if (err) { status = STATUS_CANT_CONNECT; return err; @@ -313,6 +323,7 @@ Error HTTPClientTCP::poll() { if (ssl->get_status() == StreamPeerSSL::STATUS_CONNECTED) { // Handshake has been successful handshaking = false; + ip_candidates.clear(); status = STATUS_CONNECTED; return OK; } else if (ssl->get_status() != StreamPeerSSL::STATUS_HANDSHAKING) { @@ -323,15 +334,24 @@ Error HTTPClientTCP::poll() { } // ... we will need to poll more for handshake to finish } else { + ip_candidates.clear(); status = STATUS_CONNECTED; } return OK; } break; case StreamPeerTCP::STATUS_ERROR: case StreamPeerTCP::STATUS_NONE: { + Error err = ERR_CANT_CONNECT; + while (ip_candidates.size() > 0) { + tcp_connection->disconnect_from_host(); + err = tcp_connection->connect_to_host(ip_candidates.pop_front(), conn_port); + if (err == OK) { + return OK; + } + } close(); status = STATUS_CANT_CONNECT; - return ERR_CANT_CONNECT; + return err; } break; } } break; diff --git a/core/io/http_client_tcp.h b/core/io/http_client_tcp.h index e178399fbe..170afb551c 100644 --- a/core/io/http_client_tcp.h +++ b/core/io/http_client_tcp.h @@ -37,6 +37,7 @@ class HTTPClientTCP : public HTTPClient { private: Status status = STATUS_DISCONNECTED; IP::ResolverID resolving = IP::RESOLVER_INVALID_ID; + Array ip_candidates; int conn_port = -1; String conn_host; bool ssl = false; diff --git a/core/io/multiplayer_api.cpp b/core/io/multiplayer_api.cpp index 0ce9a70921..c145225751 100644 --- a/core/io/multiplayer_api.cpp +++ b/core/io/multiplayer_api.cpp @@ -96,14 +96,11 @@ _FORCE_INLINE_ bool _can_call_mode(Node *p_node, MultiplayerAPI::RPCMode mode, i case MultiplayerAPI::RPC_MODE_DISABLED: { return false; } break; - case MultiplayerAPI::RPC_MODE_REMOTE: { + case MultiplayerAPI::RPC_MODE_ANY: { return true; } break; - case MultiplayerAPI::RPC_MODE_MASTER: { - return p_node->is_network_master(); - } break; - case MultiplayerAPI::RPC_MODE_PUPPET: { - return !p_node->is_network_master() && p_remote_id == p_node->get_network_master(); + case MultiplayerAPI::RPC_MODE_AUTHORITY: { + return !p_node->is_network_authority() && p_remote_id == p_node->get_network_authority(); } break; } @@ -140,6 +137,9 @@ void MultiplayerAPI::poll() { break; // It's also possible that a packet or RPC caused a disconnection, so also check here. } } + if (network_peer.is_valid() && network_peer->get_connection_status() == MultiplayerPeer::CONNECTION_CONNECTED) { + replicator->poll(); + } } void MultiplayerAPI::clear() { @@ -326,6 +326,9 @@ void MultiplayerAPI::_process_packet(int p_from, const uint8_t *p_packet, int p_ case NETWORK_COMMAND_DESPAWN: { replicator->process_spawn_despawn(p_from, p_packet, p_packet_len, false); } break; + case NETWORK_COMMAND_SYNC: { + replicator->process_sync(p_from, p_packet, p_packet_len); + } break; } } @@ -363,7 +366,7 @@ void MultiplayerAPI::_process_rpc(Node *p_node, const uint16_t p_rpc_method_id, ERR_FAIL_COND(config.name == StringName()); bool can_call = _can_call_mode(p_node, config.rpc_mode, p_from); - ERR_FAIL_COND_MSG(!can_call, "RPC '" + String(config.name) + "' is not allowed on node " + p_node->get_path() + " from: " + itos(p_from) + ". Mode is " + itos((int)config.rpc_mode) + ", master is " + itos(p_node->get_network_master()) + "."); + ERR_FAIL_COND_MSG(!can_call, "RPC '" + String(config.name) + "' is not allowed on node " + p_node->get_path() + " from: " + itos(p_from) + ". Mode is " + itos((int)config.rpc_mode) + ", authority is " + itos(p_node->get_network_authority()) + "."); int argc = 0; bool byte_only = false; @@ -1132,9 +1135,8 @@ void MultiplayerAPI::_bind_methods() { ADD_SIGNAL(MethodInfo("server_disconnected")); BIND_ENUM_CONSTANT(RPC_MODE_DISABLED); - BIND_ENUM_CONSTANT(RPC_MODE_REMOTE); - BIND_ENUM_CONSTANT(RPC_MODE_MASTER); - BIND_ENUM_CONSTANT(RPC_MODE_PUPPET); + BIND_ENUM_CONSTANT(RPC_MODE_ANY); + BIND_ENUM_CONSTANT(RPC_MODE_AUTHORITY); } MultiplayerAPI::MultiplayerAPI() { diff --git a/core/io/multiplayer_api.h b/core/io/multiplayer_api.h index 5853541efa..3c96a3eed1 100644 --- a/core/io/multiplayer_api.h +++ b/core/io/multiplayer_api.h @@ -43,9 +43,8 @@ class MultiplayerAPI : public RefCounted { public: enum RPCMode { RPC_MODE_DISABLED, // No rpc for this method, calls to this will be blocked (default) - RPC_MODE_REMOTE, // Using rpc() on it will call method in all remote peers - RPC_MODE_MASTER, // Using rpc() on it will call method on wherever the master is, be it local or remote - RPC_MODE_PUPPET, // Using rpc() on it will call method for all puppets + RPC_MODE_ANY, // Any peer can call this rpc() + RPC_MODE_AUTHORITY, // Only the node's network authority (server by default) can call this rpc() }; struct RPCConfig { @@ -74,6 +73,7 @@ public: NETWORK_COMMAND_RAW, NETWORK_COMMAND_SPAWN, NETWORK_COMMAND_DESPAWN, + NETWORK_COMMAND_SYNC, // This is the max we can have. We should optmize simplify/confirm, possibly spawn/despawn. }; enum NetworkNodeIdCompression { diff --git a/core/io/multiplayer_replicator.cpp b/core/io/multiplayer_replicator.cpp index ba0fe32b58..1642aab136 100644 --- a/core/io/multiplayer_replicator.cpp +++ b/core/io/multiplayer_replicator.cpp @@ -38,6 +38,140 @@ if (packet_cache.size() < m_amount) \ packet_cache.resize(m_amount); +Error MultiplayerReplicator::_sync_all_default(const ResourceUID::ID &p_scene_id, int p_peer) { + ERR_FAIL_COND_V(!replications.has(p_scene_id), ERR_INVALID_PARAMETER); + SceneConfig &cfg = replications[p_scene_id]; + int full_size = 0; + bool same_size = true; + int last_size = 0; + bool all_raw = true; + struct EncodeInfo { + int size = 0; + bool raw = false; + List<Variant> state; + }; + Map<ObjectID, struct EncodeInfo> state; + if (tracked_objects.has(p_scene_id)) { + for (const ObjectID &obj_id : tracked_objects[p_scene_id]) { + Object *obj = ObjectDB::get_instance(obj_id); + if (obj) { + struct EncodeInfo info; + Error err = _get_state(cfg.sync_properties, obj, info.state); + ERR_CONTINUE(err); + err = _encode_state(info.state, nullptr, info.size, &info.raw); + ERR_CONTINUE(err); + state[obj_id] = info; + full_size += info.size; + if (last_size && info.size != last_size) { + same_size = false; + } + all_raw = all_raw && info.raw; + last_size = info.size; + } + } + } + // Default implementation do not send empty updates. + if (!full_size) { + return OK; + } +#ifdef DEBUG_ENABLED + if (full_size > 4096 && cfg.sync_interval) { + WARN_PRINT_ONCE(vformat("The timed state update for scene %d is big (%d bytes) consider optimizing it", p_scene_id)); + } +#endif + if (same_size) { + // This is fast and small. Should we allow more than 256 objects per type? + // This costs us 1 byte. + MAKE_ROOM(SYNC_CMD_OFFSET + 1 + 2 + 2 + full_size); + } else { + MAKE_ROOM(SYNC_CMD_OFFSET + 1 + 2 + state.size() * 2 + full_size); + } + int ofs = 0; + uint8_t *ptr = packet_cache.ptrw(); + ptr[0] = MultiplayerAPI::NETWORK_COMMAND_SYNC + ((same_size ? 1 : 0) << MultiplayerAPI::BYTE_ONLY_OR_NO_ARGS_SHIFT); + ofs = 1; + ofs += encode_uint64(p_scene_id, &ptr[ofs]); + ptr[ofs] = cfg.sync_recv++; + ofs += 1; + ofs += encode_uint16(state.size(), &ptr[ofs]); + if (same_size) { + ofs += encode_uint16(last_size + (all_raw ? 1 << 15 : 0), &ptr[ofs]); + } + for (const ObjectID &obj_id : tracked_objects[p_scene_id]) { + if (!state.has(obj_id)) { + continue; + } + struct EncodeInfo &info = state[obj_id]; + Object *obj = ObjectDB::get_instance(obj_id); + ERR_CONTINUE(!obj); + int size = 0; + if (!same_size) { + // We need to encode the size of every object. + ofs += encode_uint16(info.size + (info.raw ? 1 << 15 : 0), &ptr[ofs]); + } + Error err = _encode_state(info.state, &ptr[ofs], size, &info.raw); + ERR_CONTINUE(err); + ofs += size; + } + Ref<MultiplayerPeer> network_peer = multiplayer->get_network_peer(); + network_peer->set_target_peer(p_peer); + network_peer->set_transfer_channel(0); + network_peer->set_transfer_mode(MultiplayerPeer::TRANSFER_MODE_UNRELIABLE); + return network_peer->put_packet(ptr, ofs); +} + +void MultiplayerReplicator::_process_default_sync(const ResourceUID::ID &p_id, const uint8_t *p_packet, int p_packet_len) { + ERR_FAIL_COND_MSG(p_packet_len < SYNC_CMD_OFFSET + 5, "Invalid spawn packet received"); + ERR_FAIL_COND_MSG(!replications.has(p_id), "Invalid spawn ID received " + itos(p_id)); + SceneConfig &cfg = replications[p_id]; + ERR_FAIL_COND_MSG(cfg.mode != REPLICATION_MODE_SERVER || multiplayer->is_network_server(), "The defualt implementation only allows sync packets from the server"); + const bool same_size = ((p_packet[0] & 64) >> MultiplayerAPI::BYTE_ONLY_OR_NO_ARGS_SHIFT) == 1; + int ofs = SYNC_CMD_OFFSET; + int time = p_packet[ofs]; + // Skip old update. + if (time < cfg.sync_recv && cfg.sync_recv - time < 127) { + return; + } + cfg.sync_recv = time; + ofs += 1; + int count = decode_uint16(&p_packet[ofs]); + ofs += 2; +#ifdef DEBUG_ENABLED + ERR_FAIL_COND(!tracked_objects.has(p_id) || tracked_objects[p_id].size() != count); +#else + if (!tracked_objects.has(p_id) || tracked_objects[p_id].size() != count) { + return; + } +#endif + int data_size = 0; + bool raw = false; + if (same_size) { + // This is fast and optimized. + data_size = decode_uint16(&p_packet[ofs]); + raw = (data_size & (1 << 15)) != 0; + data_size = data_size & ~(1 << 15); + ofs += 2; + ERR_FAIL_COND(p_packet_len - ofs < data_size * count); + } + for (const ObjectID &obj_id : tracked_objects[p_id]) { + Object *obj = ObjectDB::get_instance(obj_id); + ERR_CONTINUE(!obj); + if (!same_size) { + // This is slow and wasteful. + data_size = decode_uint16(&p_packet[ofs]); + raw = (data_size & (1 << 15)) != 0; + data_size = data_size & ~(1 << 15); + ofs += 2; + ERR_FAIL_COND(p_packet_len - ofs < data_size); + } + int size = 0; + Error err = _decode_state(cfg.sync_properties, obj, &p_packet[ofs], data_size, size, raw); + ofs += data_size; + ERR_CONTINUE(err); + ERR_CONTINUE(size != data_size); + } +} + Error MultiplayerReplicator::_send_default_spawn_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, Object *p_obj, const NodePath &p_path, bool p_spawn) { ERR_FAIL_COND_V(p_spawn && !p_obj, ERR_INVALID_PARAMETER); ERR_FAIL_COND_V(!replications.has(p_scene_id), ERR_INVALID_PARAMETER); @@ -136,6 +270,7 @@ void MultiplayerReplicator::_process_default_spawn_despawn(int p_from, const Res Node *node = scene->instantiate(); ERR_FAIL_COND(!node); replicated_nodes[node->get_instance_id()] = p_scene_id; + _track(p_scene_id, node); int size; _decode_state(cfg.properties, node, &p_packet[ofs], p_packet_len - ofs, size, is_raw); parent->_add_child_nocheck(node, name); @@ -145,6 +280,7 @@ void MultiplayerReplicator::_process_default_spawn_despawn(int p_from, const Res Node *node = parent->get_node(name); ERR_FAIL_COND_MSG(!replicated_nodes.has(node->get_instance_id()), vformat("Trying to despawn a Node that was not replicated: %s/%s", parent->get_path(), name)); emit_signal(SNAME("despawned"), p_scene_id, node); + _untrack(p_scene_id, node); replicated_nodes.erase(node->get_instance_id()); node->queue_delete(); } @@ -197,6 +333,37 @@ void MultiplayerReplicator::process_spawn_despawn(int p_from, const uint8_t *p_p } } +void MultiplayerReplicator::process_sync(int p_from, const uint8_t *p_packet, int p_packet_len) { + ERR_FAIL_COND_MSG(p_packet_len < SPAWN_CMD_OFFSET, "Invalid spawn packet received"); + ResourceUID::ID id = decode_uint64(&p_packet[1]); + ERR_FAIL_COND_MSG(!replications.has(id), "Invalid spawn ID received " + itos(id)); + const SceneConfig &cfg = replications[id]; + if (cfg.on_sync_receive.is_valid()) { + Array objs; + if (tracked_objects.has(id)) { + objs.resize(tracked_objects[id].size()); + int idx = 0; + for (const ObjectID &obj_id : tracked_objects[id]) { + objs[idx++] = ObjectDB::get_instance(obj_id); + } + } + PackedByteArray pba; + pba.resize(p_packet_len - SPAWN_CMD_OFFSET); + if (pba.size()) { + memcpy(pba.ptrw(), p_packet, p_packet_len - SPAWN_CMD_OFFSET); + } + Variant args[4] = { p_from, id, objs, pba }; + Variant *argp[4] = { args, &args[1], &args[2], &args[3] }; + Callable::CallError ce; + Variant ret; + cfg.on_sync_receive.call((const Variant **)argp, 4, ret, ce); + ERR_FAIL_COND_MSG(ce.error != Callable::CallError::CALL_OK, "Custom sync function failed"); + } else { + ERR_FAIL_COND_MSG(p_from != 1, "Default sync implementation only allow syncing from server to client"); + _process_default_sync(id, p_packet, p_packet_len); + } +} + Error MultiplayerReplicator::_get_state(const List<StringName> &p_properties, const Object *p_obj, List<Variant> &r_variant) { ERR_FAIL_COND_V_MSG(!p_obj, ERR_INVALID_PARAMETER, "Cannot encode null object"); for (const StringName &prop : p_properties) { @@ -306,6 +473,21 @@ Error MultiplayerReplicator::spawn_config(const ResourceUID::ID &p_id, Replicati return OK; } +Error MultiplayerReplicator::sync_config(const ResourceUID::ID &p_id, uint64_t p_interval, const TypedArray<StringName> &p_props, const Callable &p_on_send, const Callable &p_on_recv) { + ERR_FAIL_COND_V(!ResourceUID::get_singleton()->has_id(p_id), ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V_MSG(p_on_send.is_valid() != p_on_recv.is_valid(), ERR_INVALID_PARAMETER, "Send and receive custom callables must be both valid or both empty"); + ERR_FAIL_COND_V(!replications.has(p_id), ERR_UNCONFIGURED); + SceneConfig &cfg = replications[p_id]; + ERR_FAIL_COND_V_MSG(p_interval && cfg.mode != REPLICATION_MODE_SERVER && !p_on_send.is_valid(), ERR_INVALID_PARAMETER, "Timed updates in custom mode are only allowed if custom callbacks are also specified"); + for (int i = 0; i < p_props.size(); i++) { + cfg.sync_properties.push_back(p_props[i]); + } + cfg.on_sync_send = p_on_send; + cfg.on_sync_receive = p_on_recv; + cfg.sync_interval = p_interval * 1000; + return OK; +} + Error MultiplayerReplicator::_send_spawn_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const Variant &p_data, bool p_spawn) { int data_size = 0; int is_raw = false; @@ -337,6 +519,7 @@ Error MultiplayerReplicator::_send_spawn_despawn(int p_peer_id, const ResourceUI } Error MultiplayerReplicator::send_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const Variant &p_data, const NodePath &p_path) { + ERR_FAIL_COND_V(!multiplayer->has_network_peer(), ERR_UNCONFIGURED); ERR_FAIL_COND_V_MSG(!replications.has(p_scene_id), ERR_INVALID_PARAMETER, vformat("Spawnable not found: %d", p_scene_id)); const SceneConfig &cfg = replications[p_scene_id]; if (cfg.on_spawn_despawn_send.is_valid()) { @@ -357,6 +540,7 @@ Error MultiplayerReplicator::send_despawn(int p_peer_id, const ResourceUID::ID & } Error MultiplayerReplicator::send_spawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const Variant &p_data, const NodePath &p_path) { + ERR_FAIL_COND_V(!multiplayer->has_network_peer(), ERR_UNCONFIGURED); ERR_FAIL_COND_V_MSG(!replications.has(p_scene_id), ERR_INVALID_PARAMETER, vformat("Spawnable not found: %d", p_scene_id)); const SceneConfig &cfg = replications[p_scene_id]; if (cfg.on_spawn_despawn_send.is_valid()) { @@ -408,13 +592,14 @@ Error MultiplayerReplicator::despawn(ResourceUID::ID p_scene_id, Object *p_obj, return _spawn_despawn(p_scene_id, p_obj, p_peer, false); } -PackedByteArray MultiplayerReplicator::encode_state(const ResourceUID::ID &p_scene_id, const Object *p_obj) { +PackedByteArray MultiplayerReplicator::encode_state(const ResourceUID::ID &p_scene_id, const Object *p_obj, bool p_initial) { PackedByteArray state; ERR_FAIL_COND_V_MSG(!replications.has(p_scene_id), state, vformat("Spawnable not found: %d", p_scene_id)); const SceneConfig &cfg = replications[p_scene_id]; int len = 0; List<Variant> state_vars; - Error err = _get_state(cfg.properties, p_obj, state_vars); + const List<StringName> props = p_initial ? cfg.properties : cfg.sync_properties; + Error err = _get_state(props, p_obj, state_vars); ERR_FAIL_COND_V_MSG(err != OK, state, "Unable to retrieve object state."); err = _encode_state(state_vars, nullptr, len); ERR_FAIL_COND_V_MSG(err != OK, state, "Unable to encode object state."); @@ -423,11 +608,12 @@ PackedByteArray MultiplayerReplicator::encode_state(const ResourceUID::ID &p_sce return state; } -Error MultiplayerReplicator::decode_state(const ResourceUID::ID &p_scene_id, Object *p_obj, const PackedByteArray p_data) { +Error MultiplayerReplicator::decode_state(const ResourceUID::ID &p_scene_id, Object *p_obj, const PackedByteArray p_data, bool p_initial) { ERR_FAIL_COND_V_MSG(!replications.has(p_scene_id), ERR_INVALID_PARAMETER, vformat("Spawnable not found: %d", p_scene_id)); const SceneConfig &cfg = replications[p_scene_id]; + const List<StringName> props = p_initial ? cfg.properties : cfg.sync_properties; int size; - return _decode_state(cfg.properties, p_obj, p_data.ptr(), p_data.size(), size); + return _decode_state(props, p_obj, p_data.ptr(), p_data.size(), size); } void MultiplayerReplicator::scene_enter_exit_notify(const String &p_scene, Node *p_node, bool p_enter) { @@ -448,12 +634,14 @@ void MultiplayerReplicator::scene_enter_exit_notify(const String &p_scene, Node if (p_enter) { if (cfg.mode == REPLICATION_MODE_SERVER && multiplayer->is_network_server()) { replicated_nodes[p_node->get_instance_id()] = id; + _track(id, p_node); spawn(id, p_node, 0); } emit_signal(SNAME("replicated_instance_added"), id, p_node); } else { if (cfg.mode == REPLICATION_MODE_SERVER && multiplayer->is_network_server() && replicated_nodes.has(p_node->get_instance_id())) { replicated_nodes.erase(p_node->get_instance_id()); + _untrack(id, p_node); despawn(id, p_node, 0); } emit_signal(SNAME("replicated_instance_removed"), id, p_node); @@ -471,18 +659,119 @@ void MultiplayerReplicator::spawn_all(int p_peer) { } } +void MultiplayerReplicator::poll() { + for (KeyValue<ResourceUID::ID, SceneConfig> &E : replications) { + if (!E.value.sync_interval) { + continue; + } + if (E.value.mode == REPLICATION_MODE_SERVER && !multiplayer->is_network_server()) { + continue; + } + uint64_t time = OS::get_singleton()->get_ticks_usec(); + if (E.value.sync_last + E.value.sync_interval <= time) { + sync_all(E.key, 0); + E.value.sync_last = time; + } + // Handle wrapping. + if (E.value.sync_last > time) { + E.value.sync_last = time; + } + } +} + +void MultiplayerReplicator::track(const ResourceUID::ID &p_scene_id, Object *p_obj) { + ERR_FAIL_COND(!replications.has(p_scene_id)); + const SceneConfig &cfg = replications[p_scene_id]; + ERR_FAIL_COND_MSG(cfg.mode == REPLICATION_MODE_SERVER, "Manual object tracking is not allowed in server mode."); + _track(p_scene_id, p_obj); +} + +void MultiplayerReplicator::_track(const ResourceUID::ID &p_scene_id, Object *p_obj) { + ERR_FAIL_COND(!p_obj); + ERR_FAIL_COND(!replications.has(p_scene_id)); + if (!tracked_objects.has(p_scene_id)) { + tracked_objects[p_scene_id] = List<ObjectID>(); + } + tracked_objects[p_scene_id].push_back(p_obj->get_instance_id()); +} + +void MultiplayerReplicator::untrack(const ResourceUID::ID &p_scene_id, Object *p_obj) { + ERR_FAIL_COND(!replications.has(p_scene_id)); + const SceneConfig &cfg = replications[p_scene_id]; + ERR_FAIL_COND_MSG(cfg.mode == REPLICATION_MODE_SERVER, "Manual object tracking is not allowed in server mode."); + _untrack(p_scene_id, p_obj); +} + +void MultiplayerReplicator::_untrack(const ResourceUID::ID &p_scene_id, Object *p_obj) { + ERR_FAIL_COND(!p_obj); + ERR_FAIL_COND(!replications.has(p_scene_id)); + if (tracked_objects.has(p_scene_id)) { + tracked_objects[p_scene_id].erase(p_obj->get_instance_id()); + } +} + +Error MultiplayerReplicator::sync_all(const ResourceUID::ID &p_scene_id, int p_peer) { + ERR_FAIL_COND_V(!replications.has(p_scene_id), ERR_INVALID_PARAMETER); + if (!tracked_objects.has(p_scene_id)) { + return OK; + } + const SceneConfig &cfg = replications[p_scene_id]; + if (cfg.on_sync_send.is_valid()) { + Array objs; + if (tracked_objects.has(p_scene_id)) { + objs.resize(tracked_objects[p_scene_id].size()); + int idx = 0; + for (const ObjectID &obj_id : tracked_objects[p_scene_id]) { + objs[idx++] = ObjectDB::get_instance(obj_id); + } + } + Variant args[3] = { p_scene_id, objs, p_peer }; + Variant *argp[3] = { args, &args[1], &args[2] }; + Callable::CallError ce; + Variant ret; + cfg.on_sync_send.call((const Variant **)argp, 3, ret, ce); + ERR_FAIL_COND_V_MSG(ce.error != Callable::CallError::CALL_OK, FAILED, "Custom sync function failed"); + return OK; + } else if (cfg.sync_properties.size()) { + return _sync_all_default(p_scene_id, p_peer); + } + return OK; +} + +Error MultiplayerReplicator::send_sync(int p_peer_id, const ResourceUID::ID &p_scene_id, PackedByteArray p_data, MultiplayerPeer::TransferMode p_transfer_mode, int p_channel) { + ERR_FAIL_COND_V(!multiplayer->has_network_peer(), ERR_UNCONFIGURED); + ERR_FAIL_COND_V(!replications.has(p_scene_id), ERR_INVALID_PARAMETER); + const SceneConfig &cfg = replications[p_scene_id]; + ERR_FAIL_COND_V_MSG(!cfg.on_sync_send.is_valid(), ERR_UNCONFIGURED, "Sending raw sync messages is only available with custom functions"); + MAKE_ROOM(SYNC_CMD_OFFSET + p_data.size()); + uint8_t *ptr = packet_cache.ptrw(); + ptr[0] = MultiplayerAPI::NETWORK_COMMAND_SYNC; + encode_uint64(p_scene_id, &ptr[1]); + Ref<MultiplayerPeer> network_peer = multiplayer->get_network_peer(); + network_peer->set_target_peer(p_peer_id); + network_peer->set_transfer_channel(p_channel); + network_peer->set_transfer_mode(p_transfer_mode); + return network_peer->put_packet(ptr, SYNC_CMD_OFFSET + p_data.size()); +} + void MultiplayerReplicator::clear() { + tracked_objects.clear(); replicated_nodes.clear(); } void MultiplayerReplicator::_bind_methods() { ClassDB::bind_method(D_METHOD("spawn_config", "scene_id", "spawn_mode", "properties", "custom_send", "custom_receive"), &MultiplayerReplicator::spawn_config, DEFVAL(TypedArray<StringName>()), DEFVAL(Callable()), DEFVAL(Callable())); + ClassDB::bind_method(D_METHOD("sync_config", "scene_id", "interval", "properties", "custom_send", "custom_receive"), &MultiplayerReplicator::sync_config, DEFVAL(TypedArray<StringName>()), DEFVAL(Callable()), DEFVAL(Callable())); ClassDB::bind_method(D_METHOD("despawn", "scene_id", "object", "peer_id"), &MultiplayerReplicator::despawn, DEFVAL(0)); ClassDB::bind_method(D_METHOD("spawn", "scene_id", "object", "peer_id"), &MultiplayerReplicator::spawn, DEFVAL(0)); ClassDB::bind_method(D_METHOD("send_despawn", "peer_id", "scene_id", "data", "path"), &MultiplayerReplicator::send_despawn, DEFVAL(Variant()), DEFVAL(NodePath())); ClassDB::bind_method(D_METHOD("send_spawn", "peer_id", "scene_id", "data", "path"), &MultiplayerReplicator::send_spawn, DEFVAL(Variant()), DEFVAL(NodePath())); - ClassDB::bind_method(D_METHOD("encode_state", "scene_id", "object"), &MultiplayerReplicator::encode_state); - ClassDB::bind_method(D_METHOD("decode_state", "scene_id", "object", "data"), &MultiplayerReplicator::decode_state); + ClassDB::bind_method(D_METHOD("send_sync", "peer_id", "scene_id", "data", "transfer_mode", "channel"), &MultiplayerReplicator::send_sync, DEFVAL(MultiplayerPeer::TRANSFER_MODE_RELIABLE), DEFVAL(0)); + ClassDB::bind_method(D_METHOD("sync_all", "scene_id", "peer_id"), &MultiplayerReplicator::sync_all, DEFVAL(0)); + ClassDB::bind_method(D_METHOD("track", "scene_id", "object"), &MultiplayerReplicator::track); + ClassDB::bind_method(D_METHOD("untrack", "scene_id", "object"), &MultiplayerReplicator::untrack); + ClassDB::bind_method(D_METHOD("encode_state", "scene_id", "object", "initial"), &MultiplayerReplicator::encode_state, DEFVAL(true)); + ClassDB::bind_method(D_METHOD("decode_state", "scene_id", "object", "data", "initial"), &MultiplayerReplicator::decode_state, DEFVAL(true)); ADD_SIGNAL(MethodInfo("despawned", PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node"))); ADD_SIGNAL(MethodInfo("spawned", PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node"))); diff --git a/core/io/multiplayer_replicator.h b/core/io/multiplayer_replicator.h index e19dd80602..2630ad7a8a 100644 --- a/core/io/multiplayer_replicator.h +++ b/core/io/multiplayer_replicator.h @@ -32,6 +32,8 @@ #define MULTIPLAYER_REPLICATOR_H #include "core/io/multiplayer_api.h" + +#include "core/templates/hash_map.h" #include "core/variant/typed_array.h" class MultiplayerReplicator : public Object { @@ -40,6 +42,7 @@ class MultiplayerReplicator : public Object { public: enum { SPAWN_CMD_OFFSET = 9, + SYNC_CMD_OFFSET = 9, }; enum ReplicationMode { @@ -50,9 +53,15 @@ public: struct SceneConfig { ReplicationMode mode; + uint64_t sync_interval = 0; + uint64_t sync_last = 0; + uint8_t sync_recv = 0; List<StringName> properties; + List<StringName> sync_properties; Callable on_spawn_despawn_send; Callable on_spawn_despawn_receive; + Callable on_sync_send; + Callable on_sync_receive; }; protected: @@ -63,31 +72,52 @@ private: Vector<uint8_t> packet_cache; Map<ResourceUID::ID, SceneConfig> replications; Map<ObjectID, ResourceUID::ID> replicated_nodes; + HashMap<ResourceUID::ID, List<ObjectID>> tracked_objects; + // Encoding + Error _get_state(const List<StringName> &p_properties, const Object *p_obj, List<Variant> &r_variant); Error _encode_state(const List<Variant> &p_variants, uint8_t *p_buffer, int &r_len, bool *r_raw = nullptr); Error _decode_state(const List<StringName> &p_cfg, Object *p_obj, const uint8_t *p_buffer, int p_len, int &r_len, bool p_raw = false); - Error _get_state(const List<StringName> &p_properties, const Object *p_obj, List<Variant> &r_variant); + + // Spawn Error _spawn_despawn(ResourceUID::ID p_scene_id, Object *p_obj, int p_peer, bool p_spawn); Error _send_spawn_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const Variant &p_data, bool p_spawn); void _process_default_spawn_despawn(int p_from, const ResourceUID::ID &p_scene_id, const uint8_t *p_packet, int p_packet_len, bool p_spawn); Error _send_default_spawn_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, Object *p_obj, const NodePath &p_path, bool p_spawn); + // Sync + void _process_default_sync(const ResourceUID::ID &p_id, const uint8_t *p_packet, int p_packet_len); + Error _sync_all_default(const ResourceUID::ID &p_scene_id, int p_peer); + void _track(const ResourceUID::ID &p_scene_id, Object *p_object); + void _untrack(const ResourceUID::ID &p_scene_id, Object *p_object); + public: void clear(); + // Encoding + PackedByteArray encode_state(const ResourceUID::ID &p_scene_id, const Object *p_node, bool p_initial); + Error decode_state(const ResourceUID::ID &p_scene_id, Object *p_node, PackedByteArray p_data, bool p_initial); + + // Spawn Error spawn_config(const ResourceUID::ID &p_id, ReplicationMode p_mode, const TypedArray<StringName> &p_props = TypedArray<StringName>(), const Callable &p_on_send = Callable(), const Callable &p_on_recv = Callable()); Error spawn(ResourceUID::ID p_scene_id, Object *p_obj, int p_peer = 0); Error despawn(ResourceUID::ID p_scene_id, Object *p_obj, int p_peer = 0); - Error send_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const Variant &p_data = Variant(), const NodePath &p_path = NodePath()); Error send_spawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const Variant &p_data = Variant(), const NodePath &p_path = NodePath()); - PackedByteArray encode_state(const ResourceUID::ID &p_scene_id, const Object *p_node); - Error decode_state(const ResourceUID::ID &p_scene_id, Object *p_node, PackedByteArray p_data); + + // Sync + Error sync_config(const ResourceUID::ID &p_id, uint64_t p_interval, const TypedArray<StringName> &p_props = TypedArray<StringName>(), const Callable &p_on_send = Callable(), const Callable &p_on_recv = Callable()); + Error sync_all(const ResourceUID::ID &p_scene_id, int p_peer); + Error send_sync(int p_peer_id, const ResourceUID::ID &p_scene_id, PackedByteArray p_data, MultiplayerPeer::TransferMode p_mode, int p_channel); + void track(const ResourceUID::ID &p_scene_id, Object *p_object); + void untrack(const ResourceUID::ID &p_scene_id, Object *p_object); // Used by MultiplayerAPI void spawn_all(int p_peer); void process_spawn_despawn(int p_from, const uint8_t *p_packet, int p_packet_len, bool p_spawn); + void process_sync(int p_from, const uint8_t *p_packet, int p_packet_len); void scene_enter_exit_notify(const String &p_scene, Node *p_node, bool p_enter); + void poll(); MultiplayerReplicator(MultiplayerAPI *p_multiplayer) { multiplayer = p_multiplayer; diff --git a/core/io/resource_format_binary.cpp b/core/io/resource_format_binary.cpp index 00d4d093da..84fd6496a7 100644 --- a/core/io/resource_format_binary.cpp +++ b/core/io/resource_format_binary.cpp @@ -335,7 +335,7 @@ Error ResourceLoaderBinary::parse_variant(Variant &r_v) { String exttype = get_unicode_string(); String path = get_unicode_string(); - if (path.find("://") == -1 && path.is_rel_path()) { + if (path.find("://") == -1 && path.is_relative_path()) { // path is relative to file being loaded, so convert to a resource path path = ProjectSettings::get_singleton()->localize_path(res_path.get_base_dir().plus_file(path)); } @@ -626,7 +626,7 @@ Error ResourceLoaderBinary::load() { path = remaps[path]; } - if (path.find("://") == -1 && path.is_rel_path()) { + if (path.find("://") == -1 && path.is_relative_path()) { // path is relative to file being loaded, so convert to a resource path path = ProjectSettings::get_singleton()->localize_path(path.get_base_dir().plus_file(external_resources[i].path)); } diff --git a/core/io/resource_loader.cpp b/core/io/resource_loader.cpp index 64237f3b15..3026236f07 100644 --- a/core/io/resource_loader.cpp +++ b/core/io/resource_loader.cpp @@ -278,7 +278,7 @@ static String _validate_local_path(const String &p_path) { ResourceUID::ID uid = ResourceUID::get_singleton()->text_to_id(p_path); if (uid != ResourceUID::INVALID_ID) { return ResourceUID::get_singleton()->get_id_path(uid); - } else if (p_path.is_rel_path()) { + } else if (p_path.is_relative_path()) { return "res://" + p_path; } else { return ProjectSettings::get_singleton()->localize_path(p_path); diff --git a/core/math/geometry_2d.h b/core/math/geometry_2d.h index e1a5bfe6f2..8e5830f9b3 100644 --- a/core/math/geometry_2d.h +++ b/core/math/geometry_2d.h @@ -182,7 +182,15 @@ public: C = Vector2(C.x * Bn.x + C.y * Bn.y, C.y * Bn.x - C.x * Bn.y); D = Vector2(D.x * Bn.x + D.y * Bn.y, D.y * Bn.x - D.x * Bn.y); - if ((C.y < 0 && D.y < 0) || (C.y >= 0 && D.y >= 0)) { + // Fail if C x B and D x B have the same sign (segments don't intersect). + // (equivalent to condition (C.y < 0 && D.y < CMP_EPSILON) || (C.y > 0 && D.y > CMP_EPSILON)) + if (C.y * D.y > CMP_EPSILON) { + return false; + } + + // Fail if segments are parallel or colinear. + // (when A x B == zero, i.e (C - D) x B == zero, i.e C x B == D x B) + if (Math::is_equal_approx(C.y, D.y)) { return false; } @@ -193,7 +201,7 @@ public: return false; } - // (4) Apply the discovered position to line A-B in the original coordinate system. + // Apply the discovered position to line A-B in the original coordinate system. if (r_result) { *r_result = p_from_a + B * ABpos; } @@ -353,8 +361,14 @@ public: for (int i = 0; i < c; i++) { const Vector2 &v1 = p[i]; const Vector2 &v2 = p[(i + 1) % c]; - if (segment_intersects_segment(v1, v2, p_point, further_away, nullptr)) { + + Vector2 res; + if (segment_intersects_segment(v1, v2, p_point, further_away, &res)) { intersections++; + if (res.is_equal_approx(p_point)) { + // Point is in one of the polygon edges. + return true; + } } } diff --git a/core/object/class_db.cpp b/core/object/class_db.cpp index b29b2bd421..e268a8d292 100644 --- a/core/object/class_db.cpp +++ b/core/object/class_db.cpp @@ -892,6 +892,32 @@ void ClassDB::get_enum_constants(const StringName &p_class, const StringName &p_ } } +void ClassDB::set_method_error_return_values(const StringName &p_class, const StringName &p_method, const Vector<Error> &p_values) { + OBJTYPE_RLOCK; +#ifdef DEBUG_METHODS_ENABLED + ClassInfo *type = classes.getptr(p_class); + + ERR_FAIL_COND(!type); + + type->method_error_values[p_method] = p_values; +#endif +} + +Vector<Error> ClassDB::get_method_error_return_values(const StringName &p_class, const StringName &p_method) { +#ifdef DEBUG_METHODS_ENABLED + ClassInfo *type = classes.getptr(p_class); + + ERR_FAIL_COND_V(!type, Vector<Error>()); + + if (!type->method_error_values.has(p_method)) { + return Vector<Error>(); + } + return type->method_error_values[p_method]; +#else + return Vector<Error>(); +#endif +} + bool ClassDB::has_enum(const StringName &p_class, const StringName &p_name, bool p_no_inheritance) { OBJTYPE_RLOCK; diff --git a/core/object/class_db.h b/core/object/class_db.h index 45572517be..166aa35469 100644 --- a/core/object/class_db.h +++ b/core/object/class_db.h @@ -132,6 +132,7 @@ public: List<MethodInfo> virtual_methods; Map<StringName, MethodInfo> virtual_methods_map; StringName category; + Map<StringName, Vector<Error>> method_error_values; #endif HashMap<StringName, PropertySetGet> property_setget; @@ -385,6 +386,8 @@ public: static void get_enum_constants(const StringName &p_class, const StringName &p_enum, List<StringName> *p_constants, bool p_no_inheritance = false); static bool has_enum(const StringName &p_class, const StringName &p_name, bool p_no_inheritance = false); + static void set_method_error_return_values(const StringName &p_class, const StringName &p_method, const Vector<Error> &p_values); + static Vector<Error> get_method_error_return_values(const StringName &p_class, const StringName &p_method); static Variant class_get_default_property_value(const StringName &p_class, const StringName &p_property, bool *r_valid = nullptr); static StringName get_category(const StringName &p_node); @@ -415,6 +418,29 @@ public: #define BIND_ENUM_CONSTANT(m_constant) \ ::ClassDB::bind_integer_constant(get_class_static(), __constant_get_enum_name(m_constant, #m_constant), #m_constant, m_constant); +_FORCE_INLINE_ void errarray_add_str(Vector<Error> &arr) { +} + +_FORCE_INLINE_ void errarray_add_str(Vector<Error> &arr, const Error &p_err) { + arr.push_back(p_err); +} + +template <class... P> +_FORCE_INLINE_ void errarray_add_str(Vector<Error> &arr, const Error &p_err, P... p_args) { + arr.push_back(p_err); + errarray_add_str(arr, p_args...); +} + +template <class... P> +_FORCE_INLINE_ Vector<Error> errarray(P... p_args) { + Vector<Error> arr; + errarray_add_str(arr, p_args...); + return arr; +} + +#define BIND_METHOD_ERR_RETURN_DOC(m_method, ...) \ + ::ClassDB::set_method_error_return_values(get_class_static(), m_method, errarray(__VA_ARGS__)); + #else #define BIND_CONSTANT(m_constant) \ @@ -423,6 +449,8 @@ public: #define BIND_ENUM_CONSTANT(m_constant) \ ::ClassDB::bind_integer_constant(get_class_static(), StringName(), #m_constant, m_constant); +#define BIND_METHOD_ERR_RETURN_DOC(m_method, ...) + #endif #define GDREGISTER_CLASS(m_class) \ diff --git a/core/object/make_virtuals.py b/core/object/make_virtuals.py index 65a421cfb2..86c2891e5d 100644 --- a/core/object/make_virtuals.py +++ b/core/object/make_virtuals.py @@ -23,7 +23,7 @@ _FORCE_INLINE_ bool _gdvirtual_##m_name##_call($CALLARGS) $CONST { \\ \\ return false;\\ }\\ -_FORCE_INLINE_ bool _gdvirtual_##m_name##_overriden() const { \\ +_FORCE_INLINE_ bool _gdvirtual_##m_name##_overridden() const { \\ ScriptInstance *script_instance = ((Object*)(this))->get_script_instance();\\ if (script_instance) {\\ return script_instance->has_method(_gdvirtual_##m_name##_sn);\\ diff --git a/core/object/object.h b/core/object/object.h index a53143158b..102776a589 100644 --- a/core/object/object.h +++ b/core/object/object.h @@ -289,8 +289,8 @@ struct ObjectNativeExtension { #else #define GDVIRTUAL_BIND(m_name, ...) #endif -#define GDVIRTUAL_IS_OVERRIDEN(m_name) _gdvirtual_##m_name##_overriden() -#define GDVIRTUAL_IS_OVERRIDEN_PTR(m_obj, m_name) m_obj->_gdvirtual_##m_name##_overriden() +#define GDVIRTUAL_IS_OVERRIDDEN(m_name) _gdvirtual_##m_name##_overridden() +#define GDVIRTUAL_IS_OVERRIDDEN_PTR(m_obj, m_name) m_obj->_gdvirtual_##m_name##_overridden() /* the following is an incomprehensible blob of hacks and workarounds to compensate for many of the fallencies in C++. As a plus, this macro pretty much alone defines the object model. diff --git a/core/register_core_types.cpp b/core/register_core_types.cpp index 35f4532abb..6c7d9cbd89 100644 --- a/core/register_core_types.cpp +++ b/core/register_core_types.cpp @@ -308,13 +308,7 @@ void register_core_singletons() { void register_core_extensions() { // Hardcoded for now. NativeExtension::initialize_native_extensions(); - if (ProjectSettings::get_singleton()->has_setting("native_extensions/paths")) { - Vector<String> paths = ProjectSettings::get_singleton()->get("native_extensions/paths"); - for (int i = 0; i < paths.size(); i++) { - NativeExtensionManager::LoadStatus status = native_extension_manager->load_extension(paths[i]); - ERR_CONTINUE_MSG(status != NativeExtensionManager::LOAD_STATUS_OK, "Error loading extension: " + paths[i]); - } - } + native_extension_manager->load_extensions(); native_extension_manager->initialize_extensions(NativeExtension::INITIALIZATION_LEVEL_CORE); } diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp index a30d6b9102..a3b5356b1d 100644 --- a/core/string/ustring.cpp +++ b/core/string/ustring.cpp @@ -4324,7 +4324,7 @@ bool String::is_resource_file() const { return begins_with("res://") && find("::") == -1; } -bool String::is_rel_path() const { +bool String::is_relative_path() const { return !is_absolute_path(); } diff --git a/core/string/ustring.h b/core/string/ustring.h index ffb354d6e1..6a4b40da60 100644 --- a/core/string/ustring.h +++ b/core/string/ustring.h @@ -398,7 +398,7 @@ public: // path functions bool is_absolute_path() const; - bool is_rel_path() const; + bool is_relative_path() const; bool is_resource_file() const; String path_to(const String &p_path) const; String path_to_file(const String &p_path) const; diff --git a/core/templates/local_vector.h b/core/templates/local_vector.h index 668ec513d6..5704b8f230 100644 --- a/core/templates/local_vector.h +++ b/core/templates/local_vector.h @@ -170,7 +170,7 @@ public: push_back(p_val); } else { resize(count + 1); - for (U i = count; i > p_pos; i--) { + for (U i = count - 1; i > p_pos; i--) { data[i] = data[i - 1]; } data[p_pos] = p_val; diff --git a/core/templates/safe_list.h b/core/templates/safe_list.h new file mode 100644 index 0000000000..d8f010663b --- /dev/null +++ b/core/templates/safe_list.h @@ -0,0 +1,375 @@ +/*************************************************************************/ +/* safe_list.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef SAFE_LIST_H +#define SAFE_LIST_H + +#include "core/os/memory.h" +#include "core/typedefs.h" +#include <functional> + +#if !defined(NO_THREADS) + +#include <atomic> +#include <type_traits> + +// Design goals for these classes: +// - Accessing this list with an iterator will never result in a use-after free, +// even if the element being accessed has been logically removed from the list on +// another thread. +// - Logical deletion from the list will not result in deallocation at that time, +// instead the node will be deallocated at a later time when it is safe to do so. +// - No blocking synchronization primitives will be used. + +// This is used in very specific areas of the engine where it's critical that these guarantees are held. + +template <class T, class A = DefaultAllocator> +class SafeList { + struct SafeListNode { + std::atomic<SafeListNode *> next = nullptr; + + // If the node is logically deleted, this pointer will typically point + // to the previous list item in time that was also logically deleted. + std::atomic<SafeListNode *> graveyard_next = nullptr; + + std::function<void(T)> deletion_fn = [](T t) { return; }; + + T val; + }; + + static_assert(std::atomic<T>::is_always_lock_free); + + std::atomic<SafeListNode *> head = nullptr; + std::atomic<SafeListNode *> graveyard_head = nullptr; + + std::atomic_uint active_iterator_count = 0; + +public: + class Iterator { + friend class SafeList; + + SafeListNode *cursor; + SafeList *list; + + Iterator(SafeListNode *p_cursor, SafeList *p_list) : + cursor(p_cursor), list(p_list) { + list->active_iterator_count++; + } + + public: + Iterator(const Iterator &p_other) : + cursor(p_other.cursor), list(p_other.list) { + list->active_iterator_count++; + } + + ~Iterator() { + list->active_iterator_count--; + } + + public: + T &operator*() { + return cursor->val; + } + + Iterator &operator++() { + cursor = cursor->next; + return *this; + } + + // These two operators are mostly useful for comparisons to nullptr. + bool operator==(const void *p_other) const { + return cursor == p_other; + } + + bool operator!=(const void *p_other) const { + return cursor != p_other; + } + + // These two allow easy range-based for loops. + bool operator==(const Iterator &p_other) const { + return cursor == p_other.cursor; + } + + bool operator!=(const Iterator &p_other) const { + return cursor != p_other.cursor; + } + }; + +public: + // Calling this will cause an allocation. + void insert(T p_value) { + SafeListNode *new_node = memnew_allocator(SafeListNode, A); + new_node->val = p_value; + SafeListNode *expected_head = nullptr; + do { + expected_head = head.load(); + new_node->next.store(expected_head); + } while (!head.compare_exchange_strong(/* expected= */ expected_head, /* new= */ new_node)); + } + + Iterator find(T p_value) { + for (Iterator it = begin(); it != end(); ++it) { + if (*it == p_value) { + return it; + } + } + return end(); + } + + void erase(T p_value, std::function<void(T)> p_deletion_fn) { + Iterator tmp = find(p_value); + erase(tmp, p_deletion_fn); + } + + void erase(T p_value) { + Iterator tmp = find(p_value); + erase(tmp, [](T t) { return; }); + } + + void erase(Iterator &p_iterator, std::function<void(T)> p_deletion_fn) { + p_iterator.cursor->deletion_fn = p_deletion_fn; + erase(p_iterator); + } + + void erase(Iterator &p_iterator) { + if (find(p_iterator.cursor->val) == nullptr) { + // Not in the list, nothing to do. + return; + } + // First, remove the node from the list. + while (true) { + Iterator prev = begin(); + SafeListNode *expected_head = prev.cursor; + for (; prev != end(); ++prev) { + if (prev.cursor && prev.cursor->next == p_iterator.cursor) { + break; + } + } + if (prev != end()) { + // There exists a node before this. + prev.cursor->next.store(p_iterator.cursor->next.load()); + // Done. + break; + } else { + if (head.compare_exchange_strong(/* expected= */ expected_head, /* new= */ p_iterator.cursor->next.load())) { + // Successfully reassigned the head pointer before another thread changed it to something else. + break; + } + // Fall through upon failure, try again. + } + } + // Then queue it for deletion by putting it in the node graveyard. + // Don't touch `next` because an iterator might still be pointing at this node. + SafeListNode *expected_head = nullptr; + do { + expected_head = graveyard_head.load(); + p_iterator.cursor->graveyard_next.store(expected_head); + } while (!graveyard_head.compare_exchange_strong(/* expected= */ expected_head, /* new= */ p_iterator.cursor)); + } + + Iterator begin() { + return Iterator(head.load(), this); + } + + Iterator end() { + return Iterator(nullptr, this); + } + + // Calling this will cause zero to many deallocations. + void maybe_cleanup() { + SafeListNode *cursor = nullptr; + SafeListNode *new_graveyard_head = nullptr; + do { + // The access order here is theoretically important. + cursor = graveyard_head.load(); + if (active_iterator_count.load() != 0) { + // It's not safe to clean up with an active iterator, because that iterator + // could be pointing to an element that we want to delete. + return; + } + // Any iterator created after this point will never point to a deleted node. + // Swap it out with the current graveyard head. + } while (!graveyard_head.compare_exchange_strong(/* expected= */ cursor, /* new= */ new_graveyard_head)); + // Our graveyard list is now unreachable by any active iterators, + // detached from the main graveyard head and ready for deletion. + while (cursor) { + SafeListNode *tmp = cursor; + cursor = cursor->graveyard_next; + tmp->deletion_fn(tmp->val); + memdelete_allocator<SafeListNode, A>(tmp); + } + } +}; + +#else // NO_THREADS + +// Effectively the same structure without the atomics. It's probably possible to simplify it but the semantics shouldn't differ greatly. +template <class T, class A = DefaultAllocator> +class SafeList { + struct SafeListNode { + SafeListNode *next = nullptr; + + // If the node is logically deleted, this pointer will typically point to the previous list item in time that was also logically deleted. + SafeListNode *graveyard_next = nullptr; + + std::function<void(T)> deletion_fn = [](T t) { return; }; + + T val; + }; + + SafeListNode *head = nullptr; + SafeListNode *graveyard_head = nullptr; + + unsigned int active_iterator_count = 0; + +public: + class Iterator { + friend class SafeList; + + SafeListNode *cursor; + SafeList *list; + + public: + Iterator(SafeListNode *p_cursor, SafeList *p_list) : + cursor(p_cursor), list(p_list) { + list->active_iterator_count++; + } + + ~Iterator() { + list->active_iterator_count--; + } + + T &operator*() { + return cursor->val; + } + + Iterator &operator++() { + cursor = cursor->next; + return *this; + } + + // These two operators are mostly useful for comparisons to nullptr. + bool operator==(const void *p_other) const { + return cursor == p_other; + } + + bool operator!=(const void *p_other) const { + return cursor != p_other; + } + + // These two allow easy range-based for loops. + bool operator==(const Iterator &p_other) const { + return cursor == p_other.cursor; + } + + bool operator!=(const Iterator &p_other) const { + return cursor != p_other.cursor; + } + }; + +public: + // Calling this will cause an allocation. + void insert(T p_value) { + SafeListNode *new_node = memnew_allocator(SafeListNode, A); + new_node->val = p_value; + new_node->next = head; + head = new_node; + } + + Iterator find(T p_value) { + for (Iterator it = begin(); it != end(); ++it) { + if (*it == p_value) { + return it; + } + } + return end(); + } + + void erase(T p_value, std::function<void(T)> p_deletion_fn) { + erase(find(p_value), p_deletion_fn); + } + + void erase(T p_value) { + erase(find(p_value), [](T t) { return; }); + } + + void erase(Iterator p_iterator, std::function<void(T)> p_deletion_fn) { + p_iterator.cursor->deletion_fn = p_deletion_fn; + erase(p_iterator); + } + + void erase(Iterator p_iterator) { + Iterator prev = begin(); + for (; prev != end(); ++prev) { + if (prev.cursor && prev.cursor->next == p_iterator.cursor) { + break; + } + } + if (prev == end()) { + // Not in the list, nothing to do. + return; + } + // First, remove the node from the list. + prev.cursor->next = p_iterator.cursor->next; + + // Then queue it for deletion by putting it in the node graveyard. Don't touch `next` because an iterator might still be pointing at this node. + p_iterator.cursor->graveyard_next = graveyard_head; + graveyard_head = p_iterator.cursor; + } + + Iterator begin() { + return Iterator(head, this); + } + + Iterator end() { + return Iterator(nullptr, this); + } + + // Calling this will cause zero to many deallocations. + void maybe_cleanup() { + SafeListNode *cursor = graveyard_head; + if (active_iterator_count != 0) { + // It's not safe to clean up with an active iterator, because that iterator could be pointing to an element that we want to delete. + return; + } + graveyard_head = nullptr; + // Our graveyard list is now unreachable by any active iterators, detached from the main graveyard head and ready for deletion. + while (cursor) { + SafeListNode *tmp = cursor; + cursor = cursor->next; + tmp->deletion_fn(tmp->val); + memdelete_allocator<SafeListNode, A>(tmp); + } + } +}; + +#endif + +#endif // SAFE_LIST_H diff --git a/core/variant/array.cpp b/core/variant/array.cpp index 09cf785390..8373cbd4e8 100644 --- a/core/variant/array.cpp +++ b/core/variant/array.cpp @@ -203,9 +203,9 @@ Error Array::resize(int p_new_size) { return _p->array.resize(p_new_size); } -void Array::insert(int p_pos, const Variant &p_value) { - ERR_FAIL_COND(!_p->typed.validate(p_value, "insert")); - _p->array.insert(p_pos, p_value); +Error Array::insert(int p_pos, const Variant &p_value) { + ERR_FAIL_COND_V(!_p->typed.validate(p_value, "insert"), ERR_INVALID_PARAMETER); + return _p->array.insert(p_pos, p_value); } void Array::fill(const Variant &p_value) { @@ -535,8 +535,8 @@ void Array::push_front(const Variant &p_value) { Variant Array::pop_back() { if (!_p->array.is_empty()) { - int n = _p->array.size() - 1; - Variant ret = _p->array.get(n); + const int n = _p->array.size() - 1; + const Variant ret = _p->array.get(n); _p->array.resize(n); return ret; } @@ -545,13 +545,38 @@ Variant Array::pop_back() { Variant Array::pop_front() { if (!_p->array.is_empty()) { - Variant ret = _p->array.get(0); + const Variant ret = _p->array.get(0); _p->array.remove(0); return ret; } return Variant(); } +Variant Array::pop_at(int p_pos) { + if (_p->array.is_empty()) { + // Return `null` without printing an error to mimic `pop_back()` and `pop_front()` behavior. + return Variant(); + } + + if (p_pos < 0) { + // Relative offset from the end + p_pos = _p->array.size() + p_pos; + } + + ERR_FAIL_INDEX_V_MSG( + p_pos, + _p->array.size(), + Variant(), + vformat( + "The calculated index %s is out of bounds (the array has %s elements). Leaving the array untouched and returning `null`.", + p_pos, + _p->array.size())); + + const Variant ret = _p->array.get(p_pos); + _p->array.remove(p_pos); + return ret; +} + Variant Array::min() const { Variant minval; for (int i = 0; i < size(); i++) { diff --git a/core/variant/array.h b/core/variant/array.h index 540dcb1f4e..4a1b25c4a9 100644 --- a/core/variant/array.h +++ b/core/variant/array.h @@ -72,7 +72,7 @@ public: void append_array(const Array &p_array); Error resize(int p_new_size); - void insert(int p_pos, const Variant &p_value); + Error insert(int p_pos, const Variant &p_value); void remove(int p_pos); void fill(const Variant &p_value); @@ -97,6 +97,7 @@ public: void push_front(const Variant &p_value); Variant pop_back(); Variant pop_front(); + Variant pop_at(int p_pos); Array duplicate(bool p_deep = false) const; diff --git a/core/variant/variant.cpp b/core/variant/variant.cpp index d538b9faff..c5cada674f 100644 --- a/core/variant/variant.cpp +++ b/core/variant/variant.cpp @@ -1627,9 +1627,9 @@ Variant::operator String() const { String Variant::stringify(List<const void *> &stack) const { switch (type) { case NIL: - return "Null"; + return "null"; case BOOL: - return _data._bool ? "True" : "False"; + return _data._bool ? "true" : "false"; case INT: return itos(_data._int); case FLOAT: diff --git a/core/variant/variant_call.cpp b/core/variant/variant_call.cpp index a4817eb7d2..c9c7eca40d 100644 --- a/core/variant/variant_call.cpp +++ b/core/variant/variant_call.cpp @@ -1421,7 +1421,7 @@ static void _register_variant_builtin_methods() { //bind_method(String, humanize_size, sarray("size"), varray()); bind_method(String, is_absolute_path, sarray(), varray()); - bind_method(String, is_rel_path, sarray(), varray()); + bind_method(String, is_relative_path, sarray(), varray()); bind_method(String, simplify_path, sarray(), varray()); bind_method(String, get_base_dir, sarray(), varray()); bind_method(String, get_file, sarray(), varray()); @@ -1807,6 +1807,7 @@ static void _register_variant_builtin_methods() { bind_method(Array, has, sarray("value"), varray()); bind_method(Array, pop_back, sarray(), varray()); bind_method(Array, pop_front, sarray(), varray()); + bind_method(Array, pop_at, sarray("position"), varray()); bind_method(Array, sort, sarray(), varray()); bind_method(Array, sort_custom, sarray("func"), varray()); bind_method(Array, shuffle, sarray(), varray()); diff --git a/core/variant/variant_op.cpp b/core/variant/variant_op.cpp index 16c7428781..a245aff35b 100644 --- a/core/variant/variant_op.cpp +++ b/core/variant/variant_op.cpp @@ -171,7 +171,7 @@ void Variant::_register_variant_operators() { register_op<OperatorEvaluatorDiv<Vector2, Vector2, double>>(Variant::OP_DIVIDE, Variant::VECTOR2, Variant::FLOAT); register_op<OperatorEvaluatorDiv<Vector2, Vector2, int64_t>>(Variant::OP_DIVIDE, Variant::VECTOR2, Variant::INT); - register_op<OperatorEvaluatorDiv<Vector2i, Vector2i, Vector2i>>(Variant::OP_DIVIDE, Variant::VECTOR2I, Variant::VECTOR2I); + register_op<OperatorEvaluatorDivNZ<Vector2i, Vector2i, Vector2i>>(Variant::OP_DIVIDE, Variant::VECTOR2I, Variant::VECTOR2I); register_op<OperatorEvaluatorDivNZ<Vector2i, Vector2i, double>>(Variant::OP_DIVIDE, Variant::VECTOR2I, Variant::FLOAT); register_op<OperatorEvaluatorDivNZ<Vector2i, Vector2i, int64_t>>(Variant::OP_DIVIDE, Variant::VECTOR2I, Variant::INT); @@ -183,7 +183,7 @@ void Variant::_register_variant_operators() { register_op<OperatorEvaluatorDiv<Vector3, Vector3, double>>(Variant::OP_DIVIDE, Variant::VECTOR3, Variant::FLOAT); register_op<OperatorEvaluatorDiv<Vector3, Vector3, int64_t>>(Variant::OP_DIVIDE, Variant::VECTOR3, Variant::INT); - register_op<OperatorEvaluatorDiv<Vector3i, Vector3i, Vector3i>>(Variant::OP_DIVIDE, Variant::VECTOR3I, Variant::VECTOR3I); + register_op<OperatorEvaluatorDivNZ<Vector3i, Vector3i, Vector3i>>(Variant::OP_DIVIDE, Variant::VECTOR3I, Variant::VECTOR3I); register_op<OperatorEvaluatorDivNZ<Vector3i, Vector3i, double>>(Variant::OP_DIVIDE, Variant::VECTOR3I, Variant::FLOAT); register_op<OperatorEvaluatorDivNZ<Vector3i, Vector3i, int64_t>>(Variant::OP_DIVIDE, Variant::VECTOR3I, Variant::INT); @@ -195,10 +195,10 @@ void Variant::_register_variant_operators() { register_op<OperatorEvaluatorDiv<Color, Color, int64_t>>(Variant::OP_DIVIDE, Variant::COLOR, Variant::INT); register_op<OperatorEvaluatorModNZ<int64_t, int64_t, int64_t>>(Variant::OP_MODULE, Variant::INT, Variant::INT); - register_op<OperatorEvaluatorMod<Vector2i, Vector2i, Vector2i>>(Variant::OP_MODULE, Variant::VECTOR2I, Variant::VECTOR2I); + register_op<OperatorEvaluatorModNZ<Vector2i, Vector2i, Vector2i>>(Variant::OP_MODULE, Variant::VECTOR2I, Variant::VECTOR2I); register_op<OperatorEvaluatorModNZ<Vector2i, Vector2i, int64_t>>(Variant::OP_MODULE, Variant::VECTOR2I, Variant::INT); - register_op<OperatorEvaluatorMod<Vector3i, Vector3i, Vector3i>>(Variant::OP_MODULE, Variant::VECTOR3I, Variant::VECTOR3I); + register_op<OperatorEvaluatorModNZ<Vector3i, Vector3i, Vector3i>>(Variant::OP_MODULE, Variant::VECTOR3I, Variant::VECTOR3I); register_op<OperatorEvaluatorModNZ<Vector3i, Vector3i, int64_t>>(Variant::OP_MODULE, Variant::VECTOR3I, Variant::INT); register_op<OperatorEvaluatorStringModNil>(Variant::OP_MODULE, Variant::STRING, Variant::NIL); diff --git a/core/variant/variant_op.h b/core/variant/variant_op.h index cbdd60f404..3c9f849a4f 100644 --- a/core/variant/variant_op.h +++ b/core/variant/variant_op.h @@ -168,6 +168,54 @@ public: static Variant::Type get_return_type() { return GetTypeInfo<R>::VARIANT_TYPE; } }; +template <> +class OperatorEvaluatorDivNZ<Vector2i, Vector2i, Vector2i> { +public: + static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) { + const Vector2i &a = *VariantGetInternalPtr<Vector2i>::get_ptr(&p_left); + const Vector2i &b = *VariantGetInternalPtr<Vector2i>::get_ptr(&p_right); + if (unlikely(b.x == 0 || b.y == 0)) { + r_valid = false; + *r_ret = "Division by zero error"; + return; + } + *r_ret = a / b; + r_valid = true; + } + static void validated_evaluate(const Variant *left, const Variant *right, Variant *r_ret) { + VariantTypeChanger<Vector2i>::change(r_ret); + *VariantGetInternalPtr<Vector2i>::get_ptr(r_ret) = *VariantGetInternalPtr<Vector2i>::get_ptr(left) / *VariantGetInternalPtr<Vector2i>::get_ptr(right); + } + static void ptr_evaluate(const void *left, const void *right, void *r_ret) { + PtrToArg<Vector2i>::encode(PtrToArg<Vector2i>::convert(left) / PtrToArg<Vector2i>::convert(right), r_ret); + } + static Variant::Type get_return_type() { return GetTypeInfo<Vector2i>::VARIANT_TYPE; } +}; + +template <> +class OperatorEvaluatorDivNZ<Vector3i, Vector3i, Vector3i> { +public: + static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) { + const Vector3i &a = *VariantGetInternalPtr<Vector3i>::get_ptr(&p_left); + const Vector3i &b = *VariantGetInternalPtr<Vector3i>::get_ptr(&p_right); + if (unlikely(b.x == 0 || b.y == 0 || b.z == 0)) { + r_valid = false; + *r_ret = "Division by zero error"; + return; + } + *r_ret = a / b; + r_valid = true; + } + static void validated_evaluate(const Variant *left, const Variant *right, Variant *r_ret) { + VariantTypeChanger<Vector3i>::change(r_ret); + *VariantGetInternalPtr<Vector3i>::get_ptr(r_ret) = *VariantGetInternalPtr<Vector3i>::get_ptr(left) / *VariantGetInternalPtr<Vector3i>::get_ptr(right); + } + static void ptr_evaluate(const void *left, const void *right, void *r_ret) { + PtrToArg<Vector3i>::encode(PtrToArg<Vector3i>::convert(left) / PtrToArg<Vector3i>::convert(right), r_ret); + } + static Variant::Type get_return_type() { return GetTypeInfo<Vector3i>::VARIANT_TYPE; } +}; + template <class R, class A, class B> class OperatorEvaluatorMod { public: @@ -209,6 +257,54 @@ public: static Variant::Type get_return_type() { return GetTypeInfo<R>::VARIANT_TYPE; } }; +template <> +class OperatorEvaluatorModNZ<Vector2i, Vector2i, Vector2i> { +public: + static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) { + const Vector2i &a = *VariantGetInternalPtr<Vector2i>::get_ptr(&p_left); + const Vector2i &b = *VariantGetInternalPtr<Vector2i>::get_ptr(&p_right); + if (unlikely(b.x == 0 || b.y == 0)) { + r_valid = false; + *r_ret = "Module by zero error"; + return; + } + *r_ret = a % b; + r_valid = true; + } + static void validated_evaluate(const Variant *left, const Variant *right, Variant *r_ret) { + VariantTypeChanger<Vector2i>::change(r_ret); + *VariantGetInternalPtr<Vector2i>::get_ptr(r_ret) = *VariantGetInternalPtr<Vector2i>::get_ptr(left) % *VariantGetInternalPtr<Vector2i>::get_ptr(right); + } + static void ptr_evaluate(const void *left, const void *right, void *r_ret) { + PtrToArg<Vector2i>::encode(PtrToArg<Vector2i>::convert(left) / PtrToArg<Vector2i>::convert(right), r_ret); + } + static Variant::Type get_return_type() { return GetTypeInfo<Vector2i>::VARIANT_TYPE; } +}; + +template <> +class OperatorEvaluatorModNZ<Vector3i, Vector3i, Vector3i> { +public: + static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) { + const Vector3i &a = *VariantGetInternalPtr<Vector3i>::get_ptr(&p_left); + const Vector3i &b = *VariantGetInternalPtr<Vector3i>::get_ptr(&p_right); + if (unlikely(b.x == 0 || b.y == 0 || b.z == 0)) { + r_valid = false; + *r_ret = "Module by zero error"; + return; + } + *r_ret = a % b; + r_valid = true; + } + static void validated_evaluate(const Variant *left, const Variant *right, Variant *r_ret) { + VariantTypeChanger<Vector3i>::change(r_ret); + *VariantGetInternalPtr<Vector3i>::get_ptr(r_ret) = *VariantGetInternalPtr<Vector3i>::get_ptr(left) % *VariantGetInternalPtr<Vector3i>::get_ptr(right); + } + static void ptr_evaluate(const void *left, const void *right, void *r_ret) { + PtrToArg<Vector3i>::encode(PtrToArg<Vector3i>::convert(left) % PtrToArg<Vector3i>::convert(right), r_ret); + } + static Variant::Type get_return_type() { return GetTypeInfo<Vector3i>::VARIANT_TYPE; } +}; + template <class R, class A> class OperatorEvaluatorNeg { public: |