diff options
135 files changed, 3873 insertions, 1102 deletions
diff --git a/core/SCsub b/core/SCsub index 1bd4eae16c..c8267ae960 100644 --- a/core/SCsub +++ b/core/SCsub @@ -140,7 +140,7 @@ if env["builtin_zstd"]: "decompress/zstd_decompress_block.c", "decompress/zstd_decompress.c", ] - if env["platform"] in ["android", "ios", "linuxbsd", "macos"]: + if env["platform"] in ["android", "ios", "linuxbsd", "macos"] and env["arch"] == "x86_64": # Match platforms with ZSTD_ASM_SUPPORTED in common/portability_macros.h thirdparty_zstd_sources.append("decompress/huf_decompress_amd64.S") thirdparty_zstd_sources = [thirdparty_zstd_dir + file for file in thirdparty_zstd_sources] diff --git a/core/debugger/remote_debugger_peer.cpp b/core/debugger/remote_debugger_peer.cpp index 21a9014626..9dca47a0b4 100644 --- a/core/debugger/remote_debugger_peer.cpp +++ b/core/debugger/remote_debugger_peer.cpp @@ -144,9 +144,8 @@ void RemoteDebuggerPeerTCP::_read_in() { Error err = decode_variant(var, buf, in_pos, &read); ERR_CONTINUE(read != in_pos || err != OK); ERR_CONTINUE_MSG(var.get_type() != Variant::ARRAY, "Malformed packet received, not an Array."); - mutex.lock(); + MutexLock lock(mutex); in_queue.push_back(var); - mutex.unlock(); } } } diff --git a/core/extension/gdextension.cpp b/core/extension/gdextension.cpp index c9e609cddc..e764b9c112 100644 --- a/core/extension/gdextension.cpp +++ b/core/extension/gdextension.cpp @@ -678,12 +678,10 @@ Error GDExtension::open_library(const String &p_path, const Ref<GDExtensionLoade ERR_FAIL_NULL_V_MSG(p_loader, FAILED, "Can't open GDExtension without a loader."); loader = p_loader; - String abs_path = ProjectSettings::get_singleton()->globalize_path(p_path); + Error err = loader->open_library(p_path); - Error err = loader->open_library(abs_path); - - ERR_FAIL_COND_V_MSG(err == ERR_FILE_NOT_FOUND, err, "GDExtension dynamic library not found: " + abs_path); - ERR_FAIL_COND_V_MSG(err != OK, err, "Can't open GDExtension dynamic library: " + abs_path); + ERR_FAIL_COND_V_MSG(err == ERR_FILE_NOT_FOUND, err, "GDExtension dynamic library not found: " + p_path); + ERR_FAIL_COND_V_MSG(err != OK, err, "Can't open GDExtension dynamic library: " + p_path); err = loader->initialize(&gdextension_get_proc_address, this, &initialization); diff --git a/core/extension/gdextension_manager.cpp b/core/extension/gdextension_manager.cpp index eeae6b1996..01efe0d96e 100644 --- a/core/extension/gdextension_manager.cpp +++ b/core/extension/gdextension_manager.cpp @@ -32,14 +32,18 @@ #include "core/extension/gdextension_compat_hashes.h" #include "core/extension/gdextension_library_loader.h" +#include "core/io/dir_access.h" #include "core/io/file_access.h" #include "core/object/script_language.h" -GDExtensionManager::LoadStatus GDExtensionManager::_load_extension_internal(const Ref<GDExtension> &p_extension) { +GDExtensionManager::LoadStatus GDExtensionManager::_load_extension_internal(const Ref<GDExtension> &p_extension, bool p_first_load) { if (level >= 0) { // Already initialized up to some level. - int32_t minimum_level = p_extension->get_minimum_library_initialization_level(); - if (minimum_level < MIN(level, GDExtension::INITIALIZATION_LEVEL_SCENE)) { - return LOAD_STATUS_NEEDS_RESTART; + int32_t minimum_level = 0; + if (!p_first_load) { + minimum_level = p_extension->get_minimum_library_initialization_level(); + if (minimum_level < MIN(level, GDExtension::INITIALIZATION_LEVEL_SCENE)) { + return LOAD_STATUS_NEEDS_RESTART; + } } // Initialize up to current level. for (int32_t i = minimum_level; i <= level; i++) { @@ -51,10 +55,20 @@ GDExtensionManager::LoadStatus GDExtensionManager::_load_extension_internal(cons gdextension_class_icon_paths[kv.key] = kv.value; } +#ifdef TOOLS_ENABLED + // Signals that a new extension is loaded so GDScript can register new class names. + emit_signal("extension_loaded", p_extension); +#endif + return LOAD_STATUS_OK; } GDExtensionManager::LoadStatus GDExtensionManager::_unload_extension_internal(const Ref<GDExtension> &p_extension) { +#ifdef TOOLS_ENABLED + // Signals that a new extension is unloading so GDScript can unregister class names. + emit_signal("extension_unloading", p_extension); +#endif + if (level >= 0) { // Already initialized up to some level. // Deinitialize down from current level. for (int32_t i = level; i >= GDExtension::INITIALIZATION_LEVEL_CORE; i--) { @@ -89,7 +103,7 @@ GDExtensionManager::LoadStatus GDExtensionManager::load_extension_with_loader(co return LOAD_STATUS_FAILED; } - LoadStatus status = _load_extension_internal(extension); + LoadStatus status = _load_extension_internal(extension, true); if (status != LOAD_STATUS_OK) { return status; } @@ -135,7 +149,7 @@ GDExtensionManager::LoadStatus GDExtensionManager::reload_extension(const String return LOAD_STATUS_FAILED; } - status = _load_extension_internal(extension); + status = _load_extension_internal(extension, false); if (status != LOAD_STATUS_OK) { return status; } @@ -274,6 +288,71 @@ void GDExtensionManager::reload_extensions() { #endif } +bool GDExtensionManager::ensure_extensions_loaded(const HashSet<String> &p_extensions) { + Vector<String> extensions_added; + Vector<String> extensions_removed; + + for (const String &E : p_extensions) { + if (!is_extension_loaded(E)) { + extensions_added.push_back(E); + } + } + + Vector<String> loaded_extensions = get_loaded_extensions(); + for (const String &loaded_extension : loaded_extensions) { + if (!p_extensions.has(loaded_extension)) { + // The extension may not have a .gdextension file. + if (!FileAccess::exists(loaded_extension)) { + extensions_removed.push_back(loaded_extension); + } + } + } + + String extension_list_config_file = GDExtension::get_extension_list_config_file(); + if (p_extensions.size()) { + if (extensions_added.size() || extensions_removed.size()) { + // Extensions were added or removed. + Ref<FileAccess> f = FileAccess::open(extension_list_config_file, FileAccess::WRITE); + for (const String &E : p_extensions) { + f->store_line(E); + } + } + } else { + if (loaded_extensions.size() || FileAccess::exists(extension_list_config_file)) { + // Extensions were removed. + Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES); + da->remove(extension_list_config_file); + } + } + + bool needs_restart = false; + for (const String &extension : extensions_added) { + GDExtensionManager::LoadStatus st = GDExtensionManager::get_singleton()->load_extension(extension); + if (st == GDExtensionManager::LOAD_STATUS_NEEDS_RESTART) { + needs_restart = true; + } + } + + for (const String &extension : extensions_removed) { + GDExtensionManager::LoadStatus st = GDExtensionManager::get_singleton()->unload_extension(extension); + if (st == GDExtensionManager::LOAD_STATUS_NEEDS_RESTART) { + needs_restart = true; + } + } + +#ifdef TOOLS_ENABLED + if (extensions_added.size() || extensions_removed.size()) { + // Emitting extensions_reloaded so EditorNode can reload Inspector and regenerate documentation. + emit_signal("extensions_reloaded"); + + // Reload all scripts to clear out old references. + callable_mp_static(&GDExtensionManager::_reload_all_scripts).call_deferred(); + } +#endif + + return needs_restart; +} + GDExtensionManager *GDExtensionManager::get_singleton() { return singleton; } @@ -294,6 +373,8 @@ void GDExtensionManager::_bind_methods() { BIND_ENUM_CONSTANT(LOAD_STATUS_NEEDS_RESTART); ADD_SIGNAL(MethodInfo("extensions_reloaded")); + ADD_SIGNAL(MethodInfo("extension_loaded", PropertyInfo(Variant::OBJECT, "extension", PROPERTY_HINT_RESOURCE_TYPE, "GDExtension"))); + ADD_SIGNAL(MethodInfo("extension_unloading", PropertyInfo(Variant::OBJECT, "extension", PROPERTY_HINT_RESOURCE_TYPE, "GDExtension"))); } GDExtensionManager *GDExtensionManager::singleton = nullptr; diff --git a/core/extension/gdextension_manager.h b/core/extension/gdextension_manager.h index b488189604..39a600474c 100644 --- a/core/extension/gdextension_manager.h +++ b/core/extension/gdextension_manager.h @@ -54,7 +54,7 @@ public: }; private: - LoadStatus _load_extension_internal(const Ref<GDExtension> &p_extension); + LoadStatus _load_extension_internal(const Ref<GDExtension> &p_extension, bool p_first_load); LoadStatus _unload_extension_internal(const Ref<GDExtension> &p_extension); #ifdef TOOLS_ENABLED @@ -85,6 +85,7 @@ public: void load_extensions(); void reload_extensions(); + bool ensure_extensions_loaded(const HashSet<String> &p_extensions); GDExtensionManager(); ~GDExtensionManager(); diff --git a/core/io/json.cpp b/core/io/json.cpp index 61051727c1..664ff7857b 100644 --- a/core/io/json.cpp +++ b/core/io/json.cpp @@ -588,10 +588,756 @@ void JSON::_bind_methods() { ClassDB::bind_method(D_METHOD("get_error_line"), &JSON::get_error_line); ClassDB::bind_method(D_METHOD("get_error_message"), &JSON::get_error_message); + ClassDB::bind_static_method("JSON", D_METHOD("to_native", "json", "allow_classes", "allow_scripts"), &JSON::to_native, DEFVAL(false), DEFVAL(false)); + ClassDB::bind_static_method("JSON", D_METHOD("from_native", "variant", "allow_classes", "allow_scripts"), &JSON::from_native, DEFVAL(false), DEFVAL(false)); + ADD_PROPERTY(PropertyInfo(Variant::NIL, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT), "set_data", "get_data"); // Ensures that it can be serialized as binary. } -//// +#define GDTYPE "__gdtype" +#define VALUES "values" +#define PASS_ARG p_allow_classes, p_allow_scripts + +Variant JSON::from_native(const Variant &p_variant, bool p_allow_classes, bool p_allow_scripts) { + switch (p_variant.get_type()) { + case Variant::NIL: { + Dictionary nil; + nil[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return nil; + } break; + case Variant::BOOL: { + return p_variant; + } break; + case Variant::INT: { + return p_variant; + } break; + case Variant::FLOAT: { + return p_variant; + } break; + case Variant::STRING: { + return p_variant; + } break; + case Variant::VECTOR2: { + Dictionary d; + Vector2 v = p_variant; + Array values; + values.push_back(v.x); + values.push_back(v.y); + d[VALUES] = values; + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::VECTOR2I: { + Dictionary d; + Vector2i v = p_variant; + Array values; + values.push_back(v.x); + values.push_back(v.y); + d[VALUES] = values; + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::RECT2: { + Dictionary d; + Rect2 r = p_variant; + d["position"] = from_native(r.position); + d["size"] = from_native(r.size); + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::RECT2I: { + Dictionary d; + Rect2i r = p_variant; + d["position"] = from_native(r.position); + d["size"] = from_native(r.size); + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::VECTOR3: { + Dictionary d; + Vector3 v = p_variant; + Array values; + values.push_back(v.x); + values.push_back(v.y); + values.push_back(v.z); + d[VALUES] = values; + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::VECTOR3I: { + Dictionary d; + Vector3i v = p_variant; + Array values; + values.push_back(v.x); + values.push_back(v.y); + values.push_back(v.z); + d[VALUES] = values; + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::TRANSFORM2D: { + Dictionary d; + Transform2D t = p_variant; + d["x"] = from_native(t[0]); + d["y"] = from_native(t[1]); + d["origin"] = from_native(t[2]); + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::VECTOR4: { + Dictionary d; + Vector4 v = p_variant; + Array values; + values.push_back(v.x); + values.push_back(v.y); + values.push_back(v.z); + values.push_back(v.w); + d[VALUES] = values; + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::VECTOR4I: { + Dictionary d; + Vector4i v = p_variant; + Array values; + values.push_back(v.x); + values.push_back(v.y); + values.push_back(v.z); + values.push_back(v.w); + d[VALUES] = values; + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::PLANE: { + Dictionary d; + Plane p = p_variant; + d["normal"] = from_native(p.normal); + d["d"] = p.d; + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::QUATERNION: { + Dictionary d; + Quaternion q = p_variant; + Array values; + values.push_back(q.x); + values.push_back(q.y); + values.push_back(q.z); + values.push_back(q.w); + d[VALUES] = values; + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::AABB: { + Dictionary d; + AABB aabb = p_variant; + d["position"] = from_native(aabb.position); + d["size"] = from_native(aabb.size); + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::BASIS: { + Dictionary d; + Basis t = p_variant; + d["x"] = from_native(t.get_column(0)); + d["y"] = from_native(t.get_column(1)); + d["z"] = from_native(t.get_column(2)); + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::TRANSFORM3D: { + Dictionary d; + Transform3D t = p_variant; + d["basis"] = from_native(t.basis); + d["origin"] = from_native(t.origin); + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::PROJECTION: { + Dictionary d; + Projection t = p_variant; + d["x"] = from_native(t[0]); + d["y"] = from_native(t[1]); + d["z"] = from_native(t[2]); + d["w"] = from_native(t[3]); + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::COLOR: { + Dictionary d; + Color c = p_variant; + Array values; + values.push_back(c.r); + values.push_back(c.g); + values.push_back(c.b); + values.push_back(c.a); + d[VALUES] = values; + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::STRING_NAME: { + Dictionary d; + d["name"] = String(p_variant); + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::NODE_PATH: { + Dictionary d; + d["path"] = String(p_variant); + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::RID: { + Dictionary d; + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::OBJECT: { + Object *obj = p_variant.get_validated_object(); + + if (p_allow_classes && obj) { + Dictionary d; + List<PropertyInfo> property_list; + obj->get_property_list(&property_list); + + d["type"] = obj->get_class(); + Dictionary p; + for (const PropertyInfo &P : property_list) { + if (P.usage & PROPERTY_USAGE_STORAGE) { + if (P.name == "script" && !p_allow_scripts) { + continue; + } + p[P.name] = from_native(obj->get(P.name), PASS_ARG); + } + } + d["properties"] = p; + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } else { + Dictionary nil; + nil[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return nil; + } + } break; + case Variant::CALLABLE: + case Variant::SIGNAL: { + Dictionary nil; + nil[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return nil; + } break; + case Variant::DICTIONARY: { + Dictionary d = p_variant; + List<Variant> keys; + d.get_key_list(&keys); + bool all_strings = true; + for (const Variant &K : keys) { + if (K.get_type() != Variant::STRING) { + all_strings = false; + break; + } + } + + if (all_strings) { + Dictionary ret_dict; + for (const Variant &K : keys) { + ret_dict[K] = from_native(d[K], PASS_ARG); + } + return ret_dict; + } else { + Dictionary ret; + Array pairs; + for (const Variant &K : keys) { + Dictionary pair; + pair["key"] = from_native(K, PASS_ARG); + pair["value"] = from_native(d[K], PASS_ARG); + pairs.push_back(pair); + } + ret["pairs"] = pairs; + ret[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return ret; + } + } break; + case Variant::ARRAY: { + Array arr = p_variant; + Array ret; + for (int i = 0; i < arr.size(); i++) { + ret.push_back(from_native(arr[i], PASS_ARG)); + } + return ret; + } break; + case Variant::PACKED_BYTE_ARRAY: { + Dictionary d; + PackedByteArray arr = p_variant; + Array values; + for (int i = 0; i < arr.size(); i++) { + values.push_back(arr[i]); + } + d[VALUES] = values; + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::PACKED_INT32_ARRAY: { + Dictionary d; + PackedInt32Array arr = p_variant; + Array values; + for (int i = 0; i < arr.size(); i++) { + values.push_back(arr[i]); + } + d[VALUES] = values; + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + + } break; + case Variant::PACKED_INT64_ARRAY: { + Dictionary d; + PackedInt64Array arr = p_variant; + Array values; + for (int i = 0; i < arr.size(); i++) { + values.push_back(arr[i]); + } + d[VALUES] = values; + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::PACKED_FLOAT32_ARRAY: { + Dictionary d; + PackedFloat32Array arr = p_variant; + Array values; + for (int i = 0; i < arr.size(); i++) { + values.push_back(arr[i]); + } + d[VALUES] = values; + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::PACKED_FLOAT64_ARRAY: { + Dictionary d; + PackedFloat64Array arr = p_variant; + Array values; + for (int i = 0; i < arr.size(); i++) { + values.push_back(arr[i]); + } + d[VALUES] = values; + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::PACKED_STRING_ARRAY: { + Dictionary d; + PackedStringArray arr = p_variant; + Array values; + for (int i = 0; i < arr.size(); i++) { + values.push_back(arr[i]); + } + d[VALUES] = values; + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::PACKED_VECTOR2_ARRAY: { + Dictionary d; + PackedVector2Array arr = p_variant; + Array values; + for (int i = 0; i < arr.size(); i++) { + Vector2 v = arr[i]; + values.push_back(v.x); + values.push_back(v.y); + } + d[VALUES] = values; + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::PACKED_VECTOR3_ARRAY: { + Dictionary d; + PackedVector3Array arr = p_variant; + Array values; + for (int i = 0; i < arr.size(); i++) { + Vector3 v = arr[i]; + values.push_back(v.x); + values.push_back(v.y); + values.push_back(v.z); + } + d[VALUES] = values; + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::PACKED_COLOR_ARRAY: { + Dictionary d; + PackedColorArray arr = p_variant; + Array values; + for (int i = 0; i < arr.size(); i++) { + Color v = arr[i]; + values.push_back(v.r); + values.push_back(v.g); + values.push_back(v.b); + values.push_back(v.a); + } + d[VALUES] = values; + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::PACKED_VECTOR4_ARRAY: { + Dictionary d; + PackedVector4Array arr = p_variant; + Array values; + for (int i = 0; i < arr.size(); i++) { + Vector4 v = arr[i]; + values.push_back(v.x); + values.push_back(v.y); + values.push_back(v.z); + values.push_back(v.w); + } + d[VALUES] = values; + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + default: { + ERR_PRINT(vformat("Unhandled conversion from native Variant type '%s' to JSON.", Variant::get_type_name(p_variant.get_type()))); + } break; + } + + Dictionary nil; + nil[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return nil; +} + +Variant JSON::to_native(const Variant &p_json, bool p_allow_classes, bool p_allow_scripts) { + switch (p_json.get_type()) { + case Variant::BOOL: { + return p_json; + } break; + case Variant::INT: { + return p_json; + } break; + case Variant::FLOAT: { + return p_json; + } break; + case Variant::STRING: { + return p_json; + } break; + case Variant::STRING_NAME: { + return p_json; + } break; + case Variant::CALLABLE: { + return p_json; + } break; + case Variant::DICTIONARY: { + Dictionary d = p_json; + if (d.has(GDTYPE)) { + // Specific Godot Variant types serialized to JSON. + String type = d[GDTYPE]; + if (type == Variant::get_type_name(Variant::VECTOR2)) { + ERR_FAIL_COND_V(!d.has(VALUES), Variant()); + Array values = d[VALUES]; + ERR_FAIL_COND_V(values.size() != 2, Variant()); + Vector2 v; + v.x = values[0]; + v.y = values[1]; + return v; + } else if (type == Variant::get_type_name(Variant::VECTOR2I)) { + ERR_FAIL_COND_V(!d.has(VALUES), Variant()); + Array values = d[VALUES]; + ERR_FAIL_COND_V(values.size() != 2, Variant()); + Vector2i v; + v.x = values[0]; + v.y = values[1]; + return v; + } else if (type == Variant::get_type_name(Variant::RECT2)) { + ERR_FAIL_COND_V(!d.has("position"), Variant()); + ERR_FAIL_COND_V(!d.has("size"), Variant()); + Rect2 r; + r.position = to_native(d["position"]); + r.size = to_native(d["size"]); + return r; + } else if (type == Variant::get_type_name(Variant::RECT2I)) { + ERR_FAIL_COND_V(!d.has("position"), Variant()); + ERR_FAIL_COND_V(!d.has("size"), Variant()); + Rect2i r; + r.position = to_native(d["position"]); + r.size = to_native(d["size"]); + return r; + } else if (type == Variant::get_type_name(Variant::VECTOR3)) { + ERR_FAIL_COND_V(!d.has(VALUES), Variant()); + Array values = d[VALUES]; + ERR_FAIL_COND_V(values.size() != 3, Variant()); + Vector3 v; + v.x = values[0]; + v.y = values[1]; + v.z = values[2]; + return v; + } else if (type == Variant::get_type_name(Variant::VECTOR3I)) { + ERR_FAIL_COND_V(!d.has(VALUES), Variant()); + Array values = d[VALUES]; + ERR_FAIL_COND_V(values.size() != 3, Variant()); + Vector3i v; + v.x = values[0]; + v.y = values[1]; + v.z = values[2]; + return v; + } else if (type == Variant::get_type_name(Variant::TRANSFORM2D)) { + ERR_FAIL_COND_V(!d.has("x"), Variant()); + ERR_FAIL_COND_V(!d.has("y"), Variant()); + ERR_FAIL_COND_V(!d.has("origin"), Variant()); + Transform2D t; + t[0] = to_native(d["x"]); + t[1] = to_native(d["y"]); + t[2] = to_native(d["origin"]); + return t; + } else if (type == Variant::get_type_name(Variant::VECTOR4)) { + ERR_FAIL_COND_V(!d.has(VALUES), Variant()); + Array values = d[VALUES]; + ERR_FAIL_COND_V(values.size() != 4, Variant()); + Vector4 v; + v.x = values[0]; + v.y = values[1]; + v.z = values[2]; + v.w = values[3]; + return v; + } else if (type == Variant::get_type_name(Variant::VECTOR4I)) { + ERR_FAIL_COND_V(!d.has(VALUES), Variant()); + Array values = d[VALUES]; + ERR_FAIL_COND_V(values.size() != 4, Variant()); + Vector4i v; + v.x = values[0]; + v.y = values[1]; + v.z = values[2]; + v.w = values[3]; + return v; + } else if (type == Variant::get_type_name(Variant::PLANE)) { + ERR_FAIL_COND_V(!d.has("normal"), Variant()); + ERR_FAIL_COND_V(!d.has("d"), Variant()); + Plane p; + p.normal = to_native(d["normal"]); + p.d = d["d"]; + return p; + } else if (type == Variant::get_type_name(Variant::QUATERNION)) { + ERR_FAIL_COND_V(!d.has(VALUES), Variant()); + Array values = d[VALUES]; + ERR_FAIL_COND_V(values.size() != 4, Variant()); + Quaternion v; + v.x = values[0]; + v.y = values[1]; + v.z = values[2]; + v.w = values[3]; + return v; + } else if (type == Variant::get_type_name(Variant::AABB)) { + ERR_FAIL_COND_V(!d.has("position"), Variant()); + ERR_FAIL_COND_V(!d.has("size"), Variant()); + AABB r; + r.position = to_native(d["position"]); + r.size = to_native(d["size"]); + return r; + } else if (type == Variant::get_type_name(Variant::BASIS)) { + ERR_FAIL_COND_V(!d.has("x"), Variant()); + ERR_FAIL_COND_V(!d.has("y"), Variant()); + ERR_FAIL_COND_V(!d.has("z"), Variant()); + Basis b; + b.set_column(0, to_native(d["x"])); + b.set_column(1, to_native(d["y"])); + b.set_column(2, to_native(d["z"])); + return b; + } else if (type == Variant::get_type_name(Variant::TRANSFORM3D)) { + ERR_FAIL_COND_V(!d.has("basis"), Variant()); + ERR_FAIL_COND_V(!d.has("origin"), Variant()); + Transform3D t; + t.basis = to_native(d["basis"]); + t.origin = to_native(d["origin"]); + return t; + } else if (type == Variant::get_type_name(Variant::PROJECTION)) { + ERR_FAIL_COND_V(!d.has("x"), Variant()); + ERR_FAIL_COND_V(!d.has("y"), Variant()); + ERR_FAIL_COND_V(!d.has("z"), Variant()); + ERR_FAIL_COND_V(!d.has("w"), Variant()); + Projection p; + p[0] = to_native(d["x"]); + p[1] = to_native(d["y"]); + p[2] = to_native(d["z"]); + p[3] = to_native(d["w"]); + return p; + } else if (type == Variant::get_type_name(Variant::COLOR)) { + ERR_FAIL_COND_V(!d.has(VALUES), Variant()); + Array values = d[VALUES]; + ERR_FAIL_COND_V(values.size() != 4, Variant()); + Color c; + c.r = values[0]; + c.g = values[1]; + c.b = values[2]; + c.a = values[3]; + return c; + } else if (type == Variant::get_type_name(Variant::NODE_PATH)) { + ERR_FAIL_COND_V(!d.has("path"), Variant()); + NodePath np = d["path"]; + return np; + } else if (type == Variant::get_type_name(Variant::STRING_NAME)) { + ERR_FAIL_COND_V(!d.has("name"), Variant()); + StringName s = d["name"]; + return s; + } else if (type == Variant::get_type_name(Variant::OBJECT)) { + ERR_FAIL_COND_V(!d.has("type"), Variant()); + ERR_FAIL_COND_V(!d.has("properties"), Variant()); + + ERR_FAIL_COND_V(!p_allow_classes, Variant()); + + String obj_type = d["type"]; + bool is_script = obj_type == "Script" || ClassDB::is_parent_class(obj_type, "Script"); + ERR_FAIL_COND_V(!p_allow_scripts && is_script, Variant()); + Object *obj = ClassDB::instantiate(obj_type); + ERR_FAIL_NULL_V(obj, Variant()); + + Dictionary p = d["properties"]; + + List<Variant> keys; + p.get_key_list(&keys); + + for (const Variant &K : keys) { + String property = K; + Variant value = to_native(p[K], PASS_ARG); + obj->set(property, value); + } + + Variant v(obj); + + return v; + } else if (type == Variant::get_type_name(Variant::DICTIONARY)) { + ERR_FAIL_COND_V(!d.has("pairs"), Variant()); + Array pairs = d["pairs"]; + Dictionary r; + for (int i = 0; i < pairs.size(); i++) { + Dictionary p = pairs[i]; + ERR_CONTINUE(!p.has("key")); + ERR_CONTINUE(!p.has("value")); + r[to_native(p["key"], PASS_ARG)] = to_native(p["value"]); + } + return r; + } else if (type == Variant::get_type_name(Variant::ARRAY)) { + ERR_PRINT(vformat("Unexpected Array with '%s' key. Arrays are supported natively.", GDTYPE)); + } else if (type == Variant::get_type_name(Variant::PACKED_BYTE_ARRAY)) { + ERR_FAIL_COND_V(!d.has(VALUES), Variant()); + Array values = d[VALUES]; + PackedByteArray pbarr; + pbarr.resize(values.size()); + for (int i = 0; i < pbarr.size(); i++) { + pbarr.write[i] = values[i]; + } + return pbarr; + } else if (type == Variant::get_type_name(Variant::PACKED_INT32_ARRAY)) { + ERR_FAIL_COND_V(!d.has(VALUES), Variant()); + Array values = d[VALUES]; + PackedInt32Array arr; + arr.resize(values.size()); + for (int i = 0; i < arr.size(); i++) { + arr.write[i] = values[i]; + } + return arr; + } else if (type == Variant::get_type_name(Variant::PACKED_INT64_ARRAY)) { + ERR_FAIL_COND_V(!d.has(VALUES), Variant()); + Array values = d[VALUES]; + PackedInt64Array arr; + arr.resize(values.size()); + for (int i = 0; i < arr.size(); i++) { + arr.write[i] = values[i]; + } + return arr; + } else if (type == Variant::get_type_name(Variant::PACKED_FLOAT32_ARRAY)) { + ERR_FAIL_COND_V(!d.has(VALUES), Variant()); + Array values = d[VALUES]; + PackedFloat32Array arr; + arr.resize(values.size()); + for (int i = 0; i < arr.size(); i++) { + arr.write[i] = values[i]; + } + return arr; + } else if (type == Variant::get_type_name(Variant::PACKED_FLOAT64_ARRAY)) { + ERR_FAIL_COND_V(!d.has(VALUES), Variant()); + Array values = d[VALUES]; + PackedFloat64Array arr; + arr.resize(values.size()); + for (int i = 0; i < arr.size(); i++) { + arr.write[i] = values[i]; + } + return arr; + } else if (type == Variant::get_type_name(Variant::PACKED_STRING_ARRAY)) { + ERR_FAIL_COND_V(!d.has(VALUES), Variant()); + Array values = d[VALUES]; + PackedStringArray arr; + arr.resize(values.size()); + for (int i = 0; i < arr.size(); i++) { + arr.write[i] = values[i]; + } + return arr; + } else if (type == Variant::get_type_name(Variant::PACKED_VECTOR2_ARRAY)) { + ERR_FAIL_COND_V(!d.has(VALUES), Variant()); + Array values = d[VALUES]; + ERR_FAIL_COND_V(values.size() % 2 != 0, Variant()); + PackedVector2Array arr; + arr.resize(values.size() / 2); + for (int i = 0; i < arr.size(); i++) { + arr.write[i] = Vector2(values[i * 2 + 0], values[i * 2 + 1]); + } + return arr; + } else if (type == Variant::get_type_name(Variant::PACKED_VECTOR3_ARRAY)) { + ERR_FAIL_COND_V(!d.has(VALUES), Variant()); + Array values = d[VALUES]; + ERR_FAIL_COND_V(values.size() % 3 != 0, Variant()); + PackedVector3Array arr; + arr.resize(values.size() / 3); + for (int i = 0; i < arr.size(); i++) { + arr.write[i] = Vector3(values[i * 3 + 0], values[i * 3 + 1], values[i * 3 + 2]); + } + return arr; + } else if (type == Variant::get_type_name(Variant::PACKED_COLOR_ARRAY)) { + ERR_FAIL_COND_V(!d.has(VALUES), Variant()); + Array values = d[VALUES]; + ERR_FAIL_COND_V(values.size() % 4 != 0, Variant()); + PackedColorArray arr; + arr.resize(values.size() / 4); + for (int i = 0; i < arr.size(); i++) { + arr.write[i] = Color(values[i * 4 + 0], values[i * 4 + 1], values[i * 4 + 2], values[i * 4 + 3]); + } + return arr; + } else if (type == Variant::get_type_name(Variant::PACKED_VECTOR4_ARRAY)) { + ERR_FAIL_COND_V(!d.has(VALUES), Variant()); + Array values = d[VALUES]; + ERR_FAIL_COND_V(values.size() % 4 != 0, Variant()); + PackedVector4Array arr; + arr.resize(values.size() / 4); + for (int i = 0; i < arr.size(); i++) { + arr.write[i] = Vector4(values[i * 4 + 0], values[i * 4 + 1], values[i * 4 + 2], values[i * 4 + 3]); + } + return arr; + } else { + return Variant(); + } + } else { + // Regular dictionary with string keys. + List<Variant> keys; + d.get_key_list(&keys); + Dictionary r; + for (const Variant &K : keys) { + r[K] = to_native(d[K], PASS_ARG); + } + return r; + } + } break; + case Variant::ARRAY: { + Array arr = p_json; + Array ret; + ret.resize(arr.size()); + for (int i = 0; i < arr.size(); i++) { + ret[i] = to_native(arr[i], PASS_ARG); + } + return ret; + } break; + default: { + ERR_PRINT(vformat("Unhandled conversion from JSON type '%s' to native Variant type.", Variant::get_type_name(p_json.get_type()))); + return Variant(); + } + } + + return Variant(); +} + +#undef GDTYPE +#undef VALUES +#undef PASS_ARG //////////// diff --git a/core/io/json.h b/core/io/json.h index 801fa29b4b..67b5e09afa 100644 --- a/core/io/json.h +++ b/core/io/json.h @@ -94,6 +94,9 @@ public: void set_data(const Variant &p_data); inline int get_error_line() const { return err_line; } inline String get_error_message() const { return err_str; } + + static Variant from_native(const Variant &p_variant, bool p_allow_classes = false, bool p_allow_scripts = false); + static Variant to_native(const Variant &p_json, bool p_allow_classes = false, bool p_allow_scripts = false); }; class ResourceFormatLoaderJSON : public ResourceFormatLoader { diff --git a/core/io/resource.cpp b/core/io/resource.cpp index 598c99c188..ff12dc5851 100644 --- a/core/io/resource.cpp +++ b/core/io/resource.cpp @@ -60,32 +60,32 @@ void Resource::set_path(const String &p_path, bool p_take_over) { p_take_over = false; // Can't take over an empty path } - ResourceCache::lock.lock(); + { + MutexLock lock(ResourceCache::lock); - if (!path_cache.is_empty()) { - ResourceCache::resources.erase(path_cache); - } + if (!path_cache.is_empty()) { + ResourceCache::resources.erase(path_cache); + } - path_cache = ""; + path_cache = ""; - Ref<Resource> existing = ResourceCache::get_ref(p_path); + Ref<Resource> existing = ResourceCache::get_ref(p_path); - if (existing.is_valid()) { - if (p_take_over) { - existing->path_cache = String(); - ResourceCache::resources.erase(p_path); - } else { - ResourceCache::lock.unlock(); - ERR_FAIL_MSG("Another resource is loaded from path '" + p_path + "' (possible cyclic resource inclusion)."); + if (existing.is_valid()) { + if (p_take_over) { + existing->path_cache = String(); + ResourceCache::resources.erase(p_path); + } else { + ERR_FAIL_MSG("Another resource is loaded from path '" + p_path + "' (possible cyclic resource inclusion)."); + } } - } - path_cache = p_path; + path_cache = p_path; - if (!path_cache.is_empty()) { - ResourceCache::resources[path_cache] = this; + if (!path_cache.is_empty()) { + ResourceCache::resources[path_cache] = this; + } } - ResourceCache::lock.unlock(); _resource_path_changed(); } @@ -486,15 +486,13 @@ void Resource::set_as_translation_remapped(bool p_remapped) { return; } - ResourceCache::lock.lock(); + MutexLock lock(ResourceCache::lock); if (p_remapped) { ResourceLoader::remapped_list.add(&remapped_list); } else { ResourceLoader::remapped_list.remove(&remapped_list); } - - ResourceCache::lock.unlock(); } #ifdef TOOLS_ENABLED @@ -564,14 +562,13 @@ Resource::~Resource() { return; } - ResourceCache::lock.lock(); + MutexLock lock(ResourceCache::lock); // Only unregister from the cache if this is the actual resource listed there. // (Other resources can have the same value in `path_cache` if loaded with `CACHE_IGNORE`.) HashMap<String, Resource *>::Iterator E = ResourceCache::resources.find(path_cache); if (likely(E && E->value == this)) { ResourceCache::resources.remove(E); } - ResourceCache::lock.unlock(); } HashMap<String, Resource *> ResourceCache::resources; @@ -600,18 +597,20 @@ void ResourceCache::clear() { } bool ResourceCache::has(const String &p_path) { - lock.lock(); + Resource **res = nullptr; - Resource **res = resources.getptr(p_path); + { + MutexLock mutex_lock(lock); - if (res && (*res)->get_reference_count() == 0) { - // This resource is in the process of being deleted, ignore its existence. - (*res)->path_cache = String(); - resources.erase(p_path); - res = nullptr; - } + res = resources.getptr(p_path); - lock.unlock(); + if (res && (*res)->get_reference_count() == 0) { + // This resource is in the process of being deleted, ignore its existence. + (*res)->path_cache = String(); + resources.erase(p_path); + res = nullptr; + } + } if (!res) { return false; @@ -622,28 +621,27 @@ bool ResourceCache::has(const String &p_path) { Ref<Resource> ResourceCache::get_ref(const String &p_path) { Ref<Resource> ref; - lock.lock(); - - Resource **res = resources.getptr(p_path); + { + MutexLock mutex_lock(lock); + Resource **res = resources.getptr(p_path); - if (res) { - ref = Ref<Resource>(*res); - } + if (res) { + ref = Ref<Resource>(*res); + } - if (res && !ref.is_valid()) { - // This resource is in the process of being deleted, ignore its existence - (*res)->path_cache = String(); - resources.erase(p_path); - res = nullptr; + if (res && !ref.is_valid()) { + // This resource is in the process of being deleted, ignore its existence + (*res)->path_cache = String(); + resources.erase(p_path); + res = nullptr; + } } - lock.unlock(); - return ref; } void ResourceCache::get_cached_resources(List<Ref<Resource>> *p_resources) { - lock.lock(); + MutexLock mutex_lock(lock); LocalVector<String> to_remove; @@ -663,14 +661,9 @@ void ResourceCache::get_cached_resources(List<Ref<Resource>> *p_resources) { for (const String &E : to_remove) { resources.erase(E); } - - lock.unlock(); } int ResourceCache::get_cached_resource_count() { - lock.lock(); - int rc = resources.size(); - lock.unlock(); - - return rc; + MutexLock mutex_lock(lock); + return resources.size(); } diff --git a/core/io/resource_loader.cpp b/core/io/resource_loader.cpp index 3e809ae762..7cf101b0de 100644 --- a/core/io/resource_loader.cpp +++ b/core/io/resource_loader.cpp @@ -227,28 +227,27 @@ void ResourceFormatLoader::_bind_methods() { // This should be robust enough to be called redundantly without issues. void ResourceLoader::LoadToken::clear() { - thread_load_mutex.lock(); - WorkerThreadPool::TaskID task_to_await = 0; - // User-facing tokens shouldn't be deleted until completely claimed. - DEV_ASSERT(user_rc == 0 && user_path.is_empty()); - - if (!local_path.is_empty()) { // Empty is used for the special case where the load task is not registered. - DEV_ASSERT(thread_load_tasks.has(local_path)); - ThreadLoadTask &load_task = thread_load_tasks[local_path]; - if (load_task.task_id && !load_task.awaited) { - task_to_await = load_task.task_id; + { + MutexLock thread_load_lock(thread_load_mutex); + // User-facing tokens shouldn't be deleted until completely claimed. + DEV_ASSERT(user_rc == 0 && user_path.is_empty()); + + if (!local_path.is_empty()) { // Empty is used for the special case where the load task is not registered. + DEV_ASSERT(thread_load_tasks.has(local_path)); + ThreadLoadTask &load_task = thread_load_tasks[local_path]; + if (load_task.task_id && !load_task.awaited) { + task_to_await = load_task.task_id; + } + // Removing a task which is still in progress would be catastrophic. + // Tokens must be alive until the task thread function is done. + DEV_ASSERT(load_task.status == THREAD_LOAD_FAILED || load_task.status == THREAD_LOAD_LOADED); + thread_load_tasks.erase(local_path); + local_path.clear(); } - // Removing a task which is still in progress would be catastrophic. - // Tokens must be alive until the task thread function is done. - DEV_ASSERT(load_task.status == THREAD_LOAD_FAILED || load_task.status == THREAD_LOAD_LOADED); - thread_load_tasks.erase(local_path); - local_path.clear(); } - thread_load_mutex.unlock(); - // If task is unused, await it here, locally, now the token data is consistent. if (task_to_await) { PREPARE_FOR_WTP_WAIT @@ -265,7 +264,7 @@ Ref<Resource> ResourceLoader::_load(const String &p_path, const String &p_origin const String &original_path = p_original_path.is_empty() ? p_path : p_original_path; load_nesting++; if (load_paths_stack.size()) { - thread_load_mutex.lock(); + MutexLock thread_load_lock(thread_load_mutex); const String &parent_task_path = load_paths_stack.get(load_paths_stack.size() - 1); HashMap<String, ThreadLoadTask>::Iterator E = thread_load_tasks.find(parent_task_path); // Avoid double-tracking, for progress reporting, resources that boil down to a remapped path containing the real payload (e.g., imported resources). @@ -273,7 +272,6 @@ Ref<Resource> ResourceLoader::_load(const String &p_path, const String &p_origin if (E && !is_remapped_load) { E->value.sub_tasks.insert(p_original_path); } - thread_load_mutex.unlock(); } load_paths_stack.push_back(original_path); @@ -318,13 +316,13 @@ Ref<Resource> ResourceLoader::_load(const String &p_path, const String &p_origin void ResourceLoader::_run_load_task(void *p_userdata) { ThreadLoadTask &load_task = *(ThreadLoadTask *)p_userdata; - thread_load_mutex.lock(); - if (cleaning_tasks) { - load_task.status = THREAD_LOAD_FAILED; - thread_load_mutex.unlock(); - return; + { + MutexLock thread_load_lock(thread_load_mutex); + if (cleaning_tasks) { + load_task.status = THREAD_LOAD_FAILED; + return; + } } - thread_load_mutex.unlock(); // Thread-safe either if it's the current thread or a brand new one. CallQueue *own_mq_override = nullptr; @@ -1170,17 +1168,17 @@ String ResourceLoader::path_remap(const String &p_path) { } void ResourceLoader::reload_translation_remaps() { - ResourceCache::lock.lock(); - List<Resource *> to_reload; - SelfList<Resource> *E = remapped_list.first(); - while (E) { - to_reload.push_back(E->self()); - E = E->next(); - } + { + MutexLock lock(ResourceCache::lock); + SelfList<Resource> *E = remapped_list.first(); - ResourceCache::lock.unlock(); + while (E) { + to_reload.push_back(E->self()); + E = E->next(); + } + } //now just make sure to not delete any of these resources while changing locale.. while (to_reload.front()) { diff --git a/core/object/class_db.cpp b/core/object/class_db.cpp index 5c793a676f..c929b29ee9 100644 --- a/core/object/class_db.cpp +++ b/core/object/class_db.cpp @@ -271,6 +271,22 @@ void ClassDB::get_extensions_class_list(List<StringName> *p_classes) { p_classes->sort_custom<StringName::AlphCompare>(); } + +void ClassDB::get_extension_class_list(const Ref<GDExtension> &p_extension, List<StringName> *p_classes) { + OBJTYPE_RLOCK; + + for (const KeyValue<StringName, ClassInfo> &E : classes) { + if (E.value.api != API_EXTENSION && E.value.api != API_EDITOR_EXTENSION) { + continue; + } + if (!E.value.gdextension || E.value.gdextension->library != p_extension.ptr()) { + continue; + } + p_classes->push_back(E.key); + } + + p_classes->sort_custom<StringName::AlphCompare>(); +} #endif void ClassDB::get_inheriters_from_class(const StringName &p_class, List<StringName> *p_classes) { diff --git a/core/object/class_db.h b/core/object/class_db.h index d6a95b58e2..620092a6c4 100644 --- a/core/object/class_db.h +++ b/core/object/class_db.h @@ -285,6 +285,7 @@ public: static void get_class_list(List<StringName> *p_classes); #ifdef TOOLS_ENABLED static void get_extensions_class_list(List<StringName> *p_classes); + static void get_extension_class_list(const Ref<GDExtension> &p_extension, List<StringName> *p_classes); static ObjectGDExtension *get_placeholder_extension(const StringName &p_class); #endif static void get_inheriters_from_class(const StringName &p_class, List<StringName> *p_classes); diff --git a/core/object/object.cpp b/core/object/object.cpp index 103421bd17..da2a6e7587 100644 --- a/core/object/object.cpp +++ b/core/object/object.cpp @@ -1023,6 +1023,14 @@ void Object::remove_meta(const StringName &p_name) { set_meta(p_name, Variant()); } +void Object::merge_meta_from(const Object *p_src) { + List<StringName> meta_keys; + p_src->get_meta_list(&meta_keys); + for (const StringName &key : meta_keys) { + set_meta(key, p_src->get_meta(key)); + } +} + TypedArray<Dictionary> Object::_get_property_list_bind() const { List<PropertyInfo> lpi; get_property_list(&lpi); @@ -1904,7 +1912,7 @@ void Object::set_instance_binding(void *p_token, void *p_binding, const GDExtens void *Object::get_instance_binding(void *p_token, const GDExtensionInstanceBindingCallbacks *p_callbacks) { void *binding = nullptr; - _instance_binding_mutex.lock(); + MutexLock instance_binding_lock(_instance_binding_mutex); for (uint32_t i = 0; i < _instance_binding_count; i++) { if (_instance_bindings[i].token == p_token) { binding = _instance_bindings[i].binding; @@ -1935,14 +1943,12 @@ void *Object::get_instance_binding(void *p_token, const GDExtensionInstanceBindi _instance_binding_count++; } - _instance_binding_mutex.unlock(); - return binding; } bool Object::has_instance_binding(void *p_token) { bool found = false; - _instance_binding_mutex.lock(); + MutexLock instance_binding_lock(_instance_binding_mutex); for (uint32_t i = 0; i < _instance_binding_count; i++) { if (_instance_bindings[i].token == p_token) { found = true; @@ -1950,14 +1956,12 @@ bool Object::has_instance_binding(void *p_token) { } } - _instance_binding_mutex.unlock(); - return found; } void Object::free_instance_binding(void *p_token) { bool found = false; - _instance_binding_mutex.lock(); + MutexLock instance_binding_lock(_instance_binding_mutex); for (uint32_t i = 0; i < _instance_binding_count; i++) { if (!found && _instance_bindings[i].token == p_token) { if (_instance_bindings[i].free_callback) { @@ -1976,7 +1980,6 @@ void Object::free_instance_binding(void *p_token) { if (found) { _instance_binding_count--; } - _instance_binding_mutex.unlock(); } #ifdef TOOLS_ENABLED diff --git a/core/object/object.h b/core/object/object.h index ba6b309542..4752e72d67 100644 --- a/core/object/object.h +++ b/core/object/object.h @@ -667,7 +667,7 @@ protected: _FORCE_INLINE_ bool _instance_binding_reference(bool p_reference) { bool can_die = true; if (_instance_bindings) { - _instance_binding_mutex.lock(); + MutexLock instance_binding_lock(_instance_binding_mutex); for (uint32_t i = 0; i < _instance_binding_count; i++) { if (_instance_bindings[i].reference_callback) { if (!_instance_bindings[i].reference_callback(_instance_bindings[i].token, _instance_bindings[i].binding, p_reference)) { @@ -675,7 +675,6 @@ protected: } } } - _instance_binding_mutex.unlock(); } return can_die; } @@ -895,6 +894,7 @@ public: MTVIRTUAL void remove_meta(const StringName &p_name); MTVIRTUAL Variant get_meta(const StringName &p_name, const Variant &p_default = Variant()) const; MTVIRTUAL void get_meta_list(List<StringName> *p_list) const; + MTVIRTUAL void merge_meta_from(const Object *p_src); #ifdef TOOLS_ENABLED void set_edited(bool p_edited); diff --git a/core/object/worker_thread_pool.cpp b/core/object/worker_thread_pool.cpp index 25ad3bf964..36a3f9d99b 100644 --- a/core/object/worker_thread_pool.cpp +++ b/core/object/worker_thread_pool.cpp @@ -127,9 +127,8 @@ void WorkerThreadPool::_process_task(Task *p_task) { if (finished_users == max_users) { // Get rid of the group, because nobody else is using it. - task_mutex.lock(); + MutexLock task_lock(task_mutex); group_allocator.free(p_task->group); - task_mutex.unlock(); } // For groups, tasks get rid of themselves. @@ -349,17 +348,13 @@ WorkerThreadPool::TaskID WorkerThreadPool::add_task(const Callable &p_action, bo } bool WorkerThreadPool::is_task_completed(TaskID p_task_id) const { - task_mutex.lock(); + MutexLock task_lock(task_mutex); const Task *const *taskp = tasks.getptr(p_task_id); if (!taskp) { - task_mutex.unlock(); ERR_FAIL_V_MSG(false, "Invalid Task ID"); // Invalid task } - bool completed = (*taskp)->completed; - task_mutex.unlock(); - - return completed; + return (*taskp)->completed; } Error WorkerThreadPool::wait_for_task_completion(TaskID p_task_id) { @@ -522,10 +517,9 @@ void WorkerThreadPool::yield() { } void WorkerThreadPool::notify_yield_over(TaskID p_task_id) { - task_mutex.lock(); + MutexLock task_lock(task_mutex); Task **taskp = tasks.getptr(p_task_id); if (!taskp) { - task_mutex.unlock(); ERR_FAIL_MSG("Invalid Task ID."); } Task *task = *taskp; @@ -534,7 +528,6 @@ void WorkerThreadPool::notify_yield_over(TaskID p_task_id) { // This avoids a race condition where a task is created and yield-over called before it's processed. task->pending_notify_yield_over = true; } - task_mutex.unlock(); return; } @@ -542,8 +535,6 @@ void WorkerThreadPool::notify_yield_over(TaskID p_task_id) { td.yield_is_over = true; td.signaled = true; td.cond_var.notify_one(); - - task_mutex.unlock(); } WorkerThreadPool::GroupID WorkerThreadPool::_add_group_task(const Callable &p_callable, void (*p_func)(void *, uint32_t), void *p_userdata, BaseTemplateUserdata *p_template_userdata, int p_elements, int p_tasks, bool p_high_priority, const String &p_description) { @@ -601,26 +592,20 @@ WorkerThreadPool::GroupID WorkerThreadPool::add_group_task(const Callable &p_act } uint32_t WorkerThreadPool::get_group_processed_element_count(GroupID p_group) const { - task_mutex.lock(); + MutexLock task_lock(task_mutex); const Group *const *groupp = groups.getptr(p_group); if (!groupp) { - task_mutex.unlock(); ERR_FAIL_V_MSG(0, "Invalid Group ID"); } - uint32_t elements = (*groupp)->completed_index.get(); - task_mutex.unlock(); - return elements; + return (*groupp)->completed_index.get(); } bool WorkerThreadPool::is_group_task_completed(GroupID p_group) const { - task_mutex.lock(); + MutexLock task_lock(task_mutex); const Group *const *groupp = groups.getptr(p_group); if (!groupp) { - task_mutex.unlock(); ERR_FAIL_V_MSG(false, "Invalid Group ID"); } - bool completed = (*groupp)->completed.is_set(); - task_mutex.unlock(); - return completed; + return (*groupp)->completed.is_set(); } void WorkerThreadPool::wait_for_group_task_completion(GroupID p_group) { @@ -644,15 +629,13 @@ void WorkerThreadPool::wait_for_group_task_completion(GroupID p_group) { if (finished_users == max_users) { // All tasks using this group are gone (finished before the group), so clear the group too. - task_mutex.lock(); + MutexLock task_lock(task_mutex); group_allocator.free(group); - task_mutex.unlock(); } } - task_mutex.lock(); // This mutex is needed when Physics 2D and/or 3D is selected to run on a separate thread. + MutexLock task_lock(task_mutex); // This mutex is needed when Physics 2D and/or 3D is selected to run on a separate thread. groups.erase(p_group); - task_mutex.unlock(); #endif } diff --git a/core/string/string_name.cpp b/core/string/string_name.cpp index 5d59d65f92..28077fc8c5 100644 --- a/core/string/string_name.cpp +++ b/core/string/string_name.cpp @@ -191,11 +191,10 @@ StringName::StringName(const StringName &p_name) { } void StringName::assign_static_unique_class_name(StringName *ptr, const char *p_name) { - mutex.lock(); + MutexLock lock(mutex); if (*ptr == StringName()) { *ptr = StringName(p_name, true); } - mutex.unlock(); } StringName::StringName(const char *p_name, bool p_static) { diff --git a/doc/classes/EditorExportPlatform.xml b/doc/classes/EditorExportPlatform.xml index 0e5de79b25..d4084e84a0 100644 --- a/doc/classes/EditorExportPlatform.xml +++ b/doc/classes/EditorExportPlatform.xml @@ -11,11 +11,217 @@ <link title="Console support in Godot">$DOCS_URL/tutorials/platform/consoles.html</link> </tutorials> <methods> + <method name="add_message"> + <return type="void" /> + <param index="0" name="type" type="int" enum="EditorExportPlatform.ExportMessageType" /> + <param index="1" name="category" type="String" /> + <param index="2" name="message" type="String" /> + <description> + Adds a message to the export log that will be displayed when exporting ends. + </description> + </method> + <method name="clear_messages"> + <return type="void" /> + <description> + Clears the export log. + </description> + </method> + <method name="create_preset"> + <return type="EditorExportPreset" /> + <description> + Create a new preset for this platform. + </description> + </method> + <method name="export_pack"> + <return type="int" enum="Error" /> + <param index="0" name="preset" type="EditorExportPreset" /> + <param index="1" name="debug" type="bool" /> + <param index="2" name="path" type="String" /> + <param index="3" name="flags" type="int" enum="EditorExportPlatform.DebugFlags" is_bitfield="true" default="0" /> + <description> + Creates a PCK archive at [param path] for the specified [param preset]. + </description> + </method> + <method name="export_project"> + <return type="int" enum="Error" /> + <param index="0" name="preset" type="EditorExportPreset" /> + <param index="1" name="debug" type="bool" /> + <param index="2" name="path" type="String" /> + <param index="3" name="flags" type="int" enum="EditorExportPlatform.DebugFlags" is_bitfield="true" default="0" /> + <description> + Creates a full project at [param path] for the specified [param preset]. + </description> + </method> + <method name="export_project_files"> + <return type="int" enum="Error" /> + <param index="0" name="preset" type="EditorExportPreset" /> + <param index="1" name="debug" type="bool" /> + <param index="2" name="save_cb" type="Callable" /> + <param index="3" name="shared_cb" type="Callable" default="Callable()" /> + <description> + Exports project files for the specified preset. This method can be used to implement custom export format, other than PCK and ZIP. One of the callbacks is called for each exported file. + [param save_cb] is called for all exported files and have the following arguments: [code]file_path: String[/code], [code]file_data: PackedByteArray[/code], [code]file_index: int[/code], [code]file_count: int[/code], [code]encryption_include_filters: PackedStringArray[/code], [code]encryption_exclude_filters: PackedStringArray[/code], [code]encryption_key: PackedByteArray[/code]. + [param shared_cb] is called for exported native shared/static libraries and have the following arguments: [code]file_path: String[/code], [code]tags: PackedStringArray[/code], [code]target_folder: String[/code]. + [b]Note:[/b] [code]file_index[/code] and [code]file_count[/code] are intended for progress tracking only and aren't necesserely unique and precise. + </description> + </method> + <method name="export_zip"> + <return type="int" enum="Error" /> + <param index="0" name="preset" type="EditorExportPreset" /> + <param index="1" name="debug" type="bool" /> + <param index="2" name="path" type="String" /> + <param index="3" name="flags" type="int" enum="EditorExportPlatform.DebugFlags" is_bitfield="true" default="0" /> + <description> + Create a ZIP archive at [param path] for the specified [param preset]. + </description> + </method> + <method name="find_export_template" qualifiers="const"> + <return type="Dictionary" /> + <param index="0" name="template_file_name" type="String" /> + <description> + Locates export template for the platform, and returns [Dictionary] with the following keys: [code]path: String[/code] and [code]error: String[/code]. This method is provided for convenience and custom export platforms aren't required to use it or keep export templates stored in the same way official templates are. + </description> + </method> + <method name="gen_export_flags"> + <return type="PackedStringArray" /> + <param index="0" name="flags" type="int" enum="EditorExportPlatform.DebugFlags" is_bitfield="true" /> + <description> + Generates array of command line arguments for the default export templates for the debug flags and editor settings. + </description> + </method> + <method name="get_current_presets" qualifiers="const"> + <return type="Array" /> + <description> + Returns array of [EditorExportPreset]s for this platform. + </description> + </method> + <method name="get_forced_export_files" qualifiers="static"> + <return type="PackedStringArray" /> + <description> + Returns array of core file names that always should be exported regardless of preset config. + </description> + </method> + <method name="get_message_category" qualifiers="const"> + <return type="String" /> + <param index="0" name="index" type="int" /> + <description> + Returns message category, for the message with [param index]. + </description> + </method> + <method name="get_message_count" qualifiers="const"> + <return type="int" /> + <description> + Returns number of messages in the export log. + </description> + </method> + <method name="get_message_text" qualifiers="const"> + <return type="String" /> + <param index="0" name="index" type="int" /> + <description> + Returns message text, for the message with [param index]. + </description> + </method> + <method name="get_message_type" qualifiers="const"> + <return type="int" enum="EditorExportPlatform.ExportMessageType" /> + <param index="0" name="index" type="int" /> + <description> + Returns message type, for the message with [param index]. + </description> + </method> <method name="get_os_name" qualifiers="const"> <return type="String" /> <description> Returns the name of the export operating system handled by this [EditorExportPlatform] class, as a friendly string. Possible return values are [code]Windows[/code], [code]Linux[/code], [code]macOS[/code], [code]Android[/code], [code]iOS[/code], and [code]Web[/code]. </description> </method> + <method name="get_worst_message_type" qualifiers="const"> + <return type="int" enum="EditorExportPlatform.ExportMessageType" /> + <description> + Returns most severe message type currently present in the export log. + </description> + </method> + <method name="save_pack"> + <return type="Dictionary" /> + <param index="0" name="preset" type="EditorExportPreset" /> + <param index="1" name="debug" type="bool" /> + <param index="2" name="path" type="String" /> + <param index="3" name="embed" type="bool" default="false" /> + <description> + Saves PCK archive and returns [Dictionary] with the following keys: [code]result: Error[/code], [code]so_files: Array[/code] (array of the shared/static objects which contains dictionaries with the following keys: [code]path: String[/code], [code]tags: PackedStringArray[/code], and [code]target_folder: String[/code]). + If [param embed] is [code]true[/code], PCK content is appended to the end of [param path] file and return [Dictionary] additionally include following keys: [code]embedded_start: int[/code] (embedded PCK offset) and [code]embedded_size: int[/code] (embedded PCK size). + </description> + </method> + <method name="save_zip"> + <return type="Dictionary" /> + <param index="0" name="preset" type="EditorExportPreset" /> + <param index="1" name="debug" type="bool" /> + <param index="2" name="path" type="String" /> + <description> + Saves ZIP archive and returns [Dictionary] with the following keys: [code]result: Error[/code], [code]so_files: Array[/code] (array of the shared/static objects which contains dictionaries with the following keys: [code]path: String[/code], [code]tags: PackedStringArray[/code], and [code]target_folder: String[/code]). + </description> + </method> + <method name="ssh_push_to_remote" qualifiers="const"> + <return type="int" enum="Error" /> + <param index="0" name="host" type="String" /> + <param index="1" name="port" type="String" /> + <param index="2" name="scp_args" type="PackedStringArray" /> + <param index="3" name="src_file" type="String" /> + <param index="4" name="dst_file" type="String" /> + <description> + Uploads specified file over SCP protocol to the remote host. + </description> + </method> + <method name="ssh_run_on_remote" qualifiers="const"> + <return type="int" enum="Error" /> + <param index="0" name="host" type="String" /> + <param index="1" name="port" type="String" /> + <param index="2" name="ssh_arg" type="PackedStringArray" /> + <param index="3" name="cmd_args" type="String" /> + <param index="4" name="output" type="Array" default="[]" /> + <param index="5" name="port_fwd" type="int" default="-1" /> + <description> + Executes specified command on the remote host via SSH protocol and returns command output in the [param output]. + </description> + </method> + <method name="ssh_run_on_remote_no_wait" qualifiers="const"> + <return type="int" /> + <param index="0" name="host" type="String" /> + <param index="1" name="port" type="String" /> + <param index="2" name="ssh_args" type="PackedStringArray" /> + <param index="3" name="cmd_args" type="String" /> + <param index="4" name="port_fwd" type="int" default="-1" /> + <description> + Executes specified command on the remote host via SSH protocol and returns process ID (on the remote host) without waiting for command to finish. + </description> + </method> </methods> + <constants> + <constant name="EXPORT_MESSAGE_NONE" value="0" enum="ExportMessageType"> + Invalid message type used as the default value when no type is specified. + </constant> + <constant name="EXPORT_MESSAGE_INFO" value="1" enum="ExportMessageType"> + Message type for informational messages that have no effect on the export. + </constant> + <constant name="EXPORT_MESSAGE_WARNING" value="2" enum="ExportMessageType"> + Message type for warning messages that should be addressed but still allow to complete the export. + </constant> + <constant name="EXPORT_MESSAGE_ERROR" value="3" enum="ExportMessageType"> + Message type for error messages that must be addressed and fail the export. + </constant> + <constant name="DEBUG_FLAG_DUMB_CLIENT" value="1" enum="DebugFlags" is_bitfield="true"> + Flag is set if remotely debugged project is expected to use remote file system. If set, [method gen_export_flags] will add [code]--remove-fs[/code] and [code]--remote-fs-password[/code] (if password is set in the editor settings) command line arguments to the list. + </constant> + <constant name="DEBUG_FLAG_REMOTE_DEBUG" value="2" enum="DebugFlags" is_bitfield="true"> + Flag is set if remote debug is enabled. If set, [method gen_export_flags] will add [code]--remote-debug[/code] and [code]--breakpoints[/code] (if breakpoints are selected in the script editor or added by the plugin) command line arguments to the list. + </constant> + <constant name="DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST" value="4" enum="DebugFlags" is_bitfield="true"> + Flag is set if remotely debugged project is running on the localhost. If set, [method gen_export_flags] will use [code]localhost[/code] instead of [member EditorSettings.network/debug/remote_host] as remote debugger host. + </constant> + <constant name="DEBUG_FLAG_VIEW_COLLISIONS" value="8" enum="DebugFlags" is_bitfield="true"> + Flag is set if "Visible Collision Shapes" remote debug option is enabled. If set, [method gen_export_flags] will add [code]--debug-collisions[/code] command line arguments to the list. + </constant> + <constant name="DEBUG_FLAG_VIEW_NAVIGATION" value="16" enum="DebugFlags" is_bitfield="true"> + Flag is set if Visible Navigation" remote debug option is enabled. If set, [method gen_export_flags] will add [code]--debug-navigation[/code] command line arguments to the list. + </constant> + </constants> </class> diff --git a/doc/classes/EditorExportPlatformExtension.xml b/doc/classes/EditorExportPlatformExtension.xml new file mode 100644 index 0000000000..ef589e2f58 --- /dev/null +++ b/doc/classes/EditorExportPlatformExtension.xml @@ -0,0 +1,282 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="EditorExportPlatformExtension" inherits="EditorExportPlatform" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> + <brief_description> + Base class for custom [EditorExportPlatform] implementations (plugins). + </brief_description> + <description> + External [EditorExportPlatform] implementations should inherit from this class. + To use [EditorExportPlatform], register it using the [method EditorPlugin.add_export_platform] method first. + </description> + <tutorials> + </tutorials> + <methods> + <method name="_can_export" qualifiers="virtual const"> + <return type="bool" /> + <param index="0" name="preset" type="EditorExportPreset" /> + <param index="1" name="debug" type="bool" /> + <description> + [b]Optional.[/b] + Returns [code]true[/code], if specified [param preset] is valid and can be exported. Use [method set_config_error] and [method set_config_missing_templates] to set error details. + Usual implementation can call [method _has_valid_export_configuration] and [method _has_valid_project_configuration] to determine if export is possible. + </description> + </method> + <method name="_cleanup" qualifiers="virtual"> + <return type="void" /> + <description> + [b]Optional.[/b] + Called by the editor before platform is unregistered. + </description> + </method> + <method name="_export_pack" qualifiers="virtual"> + <return type="int" enum="Error" /> + <param index="0" name="preset" type="EditorExportPreset" /> + <param index="1" name="debug" type="bool" /> + <param index="2" name="path" type="String" /> + <param index="3" name="flags" type="int" enum="EditorExportPlatform.DebugFlags" is_bitfield="true" /> + <description> + [b]Optional.[/b] + Creates a PCK archive at [param path] for the specified [param preset]. + This method is called when "Export PCK/ZIP" button is pressed in the export dialog, and PCK is selected as a file type. + </description> + </method> + <method name="_export_project" qualifiers="virtual"> + <return type="int" enum="Error" /> + <param index="0" name="preset" type="EditorExportPreset" /> + <param index="1" name="debug" type="bool" /> + <param index="2" name="path" type="String" /> + <param index="3" name="flags" type="int" enum="EditorExportPlatform.DebugFlags" is_bitfield="true" /> + <description> + [b]Required.[/b] + Creates a full project at [param path] for the specified [param preset]. + This method is called when "Export" button is pressed in the export dialog. + This method implementation can call [method EditorExportPlatform.save_pack] or [method EditorExportPlatform.save_zip] to use default PCK/ZIP export process, or calls [method EditorExportPlatform.export_project_files] and implement custom callback for processing each exported file. + </description> + </method> + <method name="_export_zip" qualifiers="virtual"> + <return type="int" enum="Error" /> + <param index="0" name="preset" type="EditorExportPreset" /> + <param index="1" name="debug" type="bool" /> + <param index="2" name="path" type="String" /> + <param index="3" name="flags" type="int" enum="EditorExportPlatform.DebugFlags" is_bitfield="true" /> + <description> + [b]Optional.[/b] + Create a ZIP archive at [param path] for the specified [param preset]. + This method is called when "Export PCK/ZIP" button is pressed in the export dialog, and ZIP is selected as a file type. + </description> + </method> + <method name="_get_binary_extensions" qualifiers="virtual const"> + <return type="PackedStringArray" /> + <param index="0" name="preset" type="EditorExportPreset" /> + <description> + [b]Required.[/b] + Returns array of supported binary extensions for the full project export. + </description> + </method> + <method name="_get_debug_protocol" qualifiers="virtual const"> + <return type="String" /> + <description> + [b]Optional.[/b] + Returns protocol used for remote debugging. Default implementation return [code]tcp://[/code]. + </description> + </method> + <method name="_get_device_architecture" qualifiers="virtual const"> + <return type="String" /> + <param index="0" name="device" type="int" /> + <description> + [b]Optional.[/b] + Returns device architecture for one-click deploy. + </description> + </method> + <method name="_get_export_option_visibility" qualifiers="virtual const"> + <return type="bool" /> + <param index="0" name="preset" type="EditorExportPreset" /> + <param index="1" name="option" type="String" /> + <description> + [b]Optional.[/b] + Validates [param option] and returns visibility for the specified [param preset]. Default implementation return [code]true[/code] for all options. + </description> + </method> + <method name="_get_export_option_warning" qualifiers="virtual const"> + <return type="String" /> + <param index="0" name="preset" type="EditorExportPreset" /> + <param index="1" name="option" type="StringName" /> + <description> + [b]Optional.[/b] + Validates [param option] and returns warning message for the specified [param preset]. Default implementation return empty string for all options. + </description> + </method> + <method name="_get_export_options" qualifiers="virtual const"> + <return type="Dictionary[]" /> + <description> + [b]Optional.[/b] + Returns a property list, as an [Array] of dictionaries. Each [Dictionary] must at least contain the [code]name: StringName[/code] and [code]type: Variant.Type[/code] entries. + Additionally, the following keys are supported: + - [code]hint: PropertyHint[/code] + - [code]hint_string: String[/code] + - [code]usage: PropertyUsageFlags[/code] + - [code]class_name: StringName[/code] + - [code]default_value: Variant[/code], default value of the property. + - [code]update_visibility: bool[/code], if set to [code]true[/code], [method _get_export_option_visibility] is called for each property when this property is changed. + - [code]required: bool[/code], if set to [code]true[/code], this property warnings are critical, and should be resolved to make export possible. This value is a hint for the [method _has_valid_export_configuration] implementation, and not used by the engine directly. + See also [method Object._get_property_list]. + </description> + </method> + <method name="_get_logo" qualifiers="virtual const"> + <return type="Texture2D" /> + <description> + [b]Required.[/b] + Returns platform logo displayed in the export dialog, logo should be 32x32 adjusted to the current editor scale, see [method EditorInterface.get_editor_scale]. + </description> + </method> + <method name="_get_name" qualifiers="virtual const"> + <return type="String" /> + <description> + [b]Required.[/b] + Returns export platform name. + </description> + </method> + <method name="_get_option_icon" qualifiers="virtual const"> + <return type="ImageTexture" /> + <param index="0" name="device" type="int" /> + <description> + [b]Optional.[/b] + Returns one-click deploy menu item icon for the specified [param device], icon should be 16x16 adjusted to the current editor scale, see [method EditorInterface.get_editor_scale]. + </description> + </method> + <method name="_get_option_label" qualifiers="virtual const"> + <return type="String" /> + <param index="0" name="device" type="int" /> + <description> + [b]Optional.[/b] + Returns one-click deploy menu item label for the specified [param device]. + </description> + </method> + <method name="_get_option_tooltip" qualifiers="virtual const"> + <return type="String" /> + <param index="0" name="device" type="int" /> + <description> + [b]Optional.[/b] + Returns one-click deploy menu item tooltip for the specified [param device]. + </description> + </method> + <method name="_get_options_count" qualifiers="virtual const"> + <return type="int" /> + <description> + [b]Optional.[/b] + Returns number one-click deploy devices (or other one-click option displayed in the menu). + </description> + </method> + <method name="_get_options_tooltip" qualifiers="virtual const"> + <return type="String" /> + <description> + [b]Optional.[/b] + Returns tooltip of the one-click deploy menu button. + </description> + </method> + <method name="_get_os_name" qualifiers="virtual const"> + <return type="String" /> + <description> + [b]Required.[/b] + Returns target OS name. + </description> + </method> + <method name="_get_platform_features" qualifiers="virtual const"> + <return type="PackedStringArray" /> + <description> + [b]Required.[/b] + Returns array of platform specific features. + </description> + </method> + <method name="_get_preset_features" qualifiers="virtual const"> + <return type="PackedStringArray" /> + <param index="0" name="preset" type="EditorExportPreset" /> + <description> + [b]Required.[/b] + Returns array of platform specific features for the specified [param preset]. + </description> + </method> + <method name="_get_run_icon" qualifiers="virtual const"> + <return type="Texture2D" /> + <description> + [b]Optional.[/b] + Returns icon of the one-click deploy menu button, icon should be 16x16 adjusted to the current editor scale, see [method EditorInterface.get_editor_scale]. + </description> + </method> + <method name="_has_valid_export_configuration" qualifiers="virtual const"> + <return type="bool" /> + <param index="0" name="preset" type="EditorExportPreset" /> + <param index="1" name="debug" type="bool" /> + <description> + [b]Required.[/b] + Returns [code]true[/code] if export configuration is valid. + </description> + </method> + <method name="_has_valid_project_configuration" qualifiers="virtual const"> + <return type="bool" /> + <param index="0" name="preset" type="EditorExportPreset" /> + <description> + [b]Required.[/b] + Returns [code]true[/code] if project configuration is valid. + </description> + </method> + <method name="_is_executable" qualifiers="virtual const"> + <return type="bool" /> + <param index="0" name="path" type="String" /> + <description> + [b]Optional.[/b] + Returns [code]true[/code] if specified file is a valid executable (native executable or script) for the target platform. + </description> + </method> + <method name="_poll_export" qualifiers="virtual"> + <return type="bool" /> + <description> + [b]Optional.[/b] + Returns [code]true[/code] if one-click deploy options are changed and editor interface should be updated. + </description> + </method> + <method name="_run" qualifiers="virtual"> + <return type="int" enum="Error" /> + <param index="0" name="preset" type="EditorExportPreset" /> + <param index="1" name="device" type="int" /> + <param index="2" name="debug_flags" type="int" enum="EditorExportPlatform.DebugFlags" is_bitfield="true" /> + <description> + [b]Optional.[/b] + This method is called when [param device] one-click deploy menu option is selected. + Implementation should export project to a temporary location, upload and run it on the specific [param device], or perform another action associated with the menu item. + </description> + </method> + <method name="_should_update_export_options" qualifiers="virtual"> + <return type="bool" /> + <description> + [b]Optional.[/b] + Returns [code]true[/code] if export options list is changed and presets should be updated. + </description> + </method> + <method name="get_config_error" qualifiers="const"> + <return type="String" /> + <description> + Returns current configuration error message text. This method should be called only from the [method _can_export], [method _has_valid_export_configuration], or [method _has_valid_project_configuration] implementations. + </description> + </method> + <method name="get_config_missing_templates" qualifiers="const"> + <return type="bool" /> + <description> + Returns [code]true[/code] is export templates are missing from the current configuration. This method should be called only from the [method _can_export], [method _has_valid_export_configuration], or [method _has_valid_project_configuration] implementations. + </description> + </method> + <method name="set_config_error" qualifiers="const"> + <return type="void" /> + <param index="0" name="error_text" type="String" /> + <description> + Sets current configuration error message text. This method should be called only from the [method _can_export], [method _has_valid_export_configuration], or [method _has_valid_project_configuration] implementations. + </description> + </method> + <method name="set_config_missing_templates" qualifiers="const"> + <return type="void" /> + <param index="0" name="missing_templates" type="bool" /> + <description> + Set to [code]true[/code] is export templates are missing from the current configuration. This method should be called only from the [method _can_export], [method _has_valid_export_configuration], or [method _has_valid_project_configuration] implementations. + </description> + </method> + </methods> +</class> diff --git a/doc/classes/EditorExportPlugin.xml b/doc/classes/EditorExportPlugin.xml index 9ef911a68d..42e1968eb0 100644 --- a/doc/classes/EditorExportPlugin.xml +++ b/doc/classes/EditorExportPlugin.xml @@ -305,6 +305,18 @@ In case of a directory code-sign will error if you place non code object in directory. </description> </method> + <method name="get_export_platform" qualifiers="const"> + <return type="EditorExportPlatform" /> + <description> + Returns currently used export platform. + </description> + </method> + <method name="get_export_preset" qualifiers="const"> + <return type="EditorExportPreset" /> + <description> + Returns currently used export preset. + </description> + </method> <method name="get_option" qualifiers="const"> <return type="Variant" /> <param index="0" name="name" type="StringName" /> diff --git a/doc/classes/EditorExportPreset.xml b/doc/classes/EditorExportPreset.xml new file mode 100644 index 0000000000..bba79364e4 --- /dev/null +++ b/doc/classes/EditorExportPreset.xml @@ -0,0 +1,186 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="EditorExportPreset" inherits="RefCounted" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> + <brief_description> + Export preset configuration. + </brief_description> + <description> + Export preset configuration. Instances of [EditorExportPreset] by editor UI and intended to be used a read-only configuration passed to the [EditorExportPlatform] methods when exporting the project. + </description> + <tutorials> + </tutorials> + <methods> + <method name="are_advanced_options_enabled" qualifiers="const"> + <return type="bool" /> + <description> + Returns [code]true[/code], is "Advanced" toggle is enabled in the export dialog. + </description> + </method> + <method name="get_custom_features" qualifiers="const"> + <return type="String" /> + <description> + Returns string with a comma separated list of custom features. + </description> + </method> + <method name="get_customized_files" qualifiers="const"> + <return type="Dictionary" /> + <description> + Returns [Dictionary] of files selected in the "Resources" tab of the export dialog. Dictionary keys are file names and values are export mode - [code]"strip[/code], [code]"keep"[/code], or [code]"remove"[/code]. See also [method get_file_export_mode]. + </description> + </method> + <method name="get_customized_files_count" qualifiers="const"> + <return type="int" /> + <description> + Returns number of files selected in the "Resources" tab of the export dialog. + </description> + </method> + <method name="get_encrypt_directory" qualifiers="const"> + <return type="bool" /> + <description> + Returns [code]true[/code], PCK directory encryption is enabled in the export dialog. + </description> + </method> + <method name="get_encrypt_pck" qualifiers="const"> + <return type="bool" /> + <description> + Returns [code]true[/code], PCK encryption is enabled in the export dialog. + </description> + </method> + <method name="get_encryption_ex_filter" qualifiers="const"> + <return type="String" /> + <description> + Returns file filters to exclude during PCK encryption. + </description> + </method> + <method name="get_encryption_in_filter" qualifiers="const"> + <return type="String" /> + <description> + Returns file filters to include during PCK encryption. + </description> + </method> + <method name="get_encryption_key" qualifiers="const"> + <return type="String" /> + <description> + Returns PCK encryption key. + </description> + </method> + <method name="get_exclude_filter" qualifiers="const"> + <return type="String" /> + <description> + Returns file filters to exclude during export. + </description> + </method> + <method name="get_export_filter" qualifiers="const"> + <return type="int" enum="EditorExportPreset.ExportFilter" /> + <description> + Returns export file filter mode selected in the "Resources" tab of the export dialog. + </description> + </method> + <method name="get_export_path" qualifiers="const"> + <return type="String" /> + <description> + Returns export target path. + </description> + </method> + <method name="get_file_export_mode" qualifiers="const"> + <return type="int" enum="EditorExportPreset.FileExportMode" /> + <param index="0" name="path" type="String" /> + <param index="1" name="default" type="int" enum="EditorExportPreset.FileExportMode" default="0" /> + <description> + Returns file export mode for the specified file. + </description> + </method> + <method name="get_files_to_export" qualifiers="const"> + <return type="PackedStringArray" /> + <description> + Returns array of files to export. + </description> + </method> + <method name="get_include_filter" qualifiers="const"> + <return type="String" /> + <description> + Returns file filters to include during export. + </description> + </method> + <method name="get_or_env" qualifiers="const"> + <return type="Variant" /> + <param index="0" name="name" type="StringName" /> + <param index="1" name="env_var" type="String" /> + <description> + Returns export option value or value of environment variable if it is set. + </description> + </method> + <method name="get_preset_name" qualifiers="const"> + <return type="String" /> + <description> + Returns export preset name. + </description> + </method> + <method name="get_script_export_mode" qualifiers="const"> + <return type="int" /> + <description> + Returns script export mode. + </description> + </method> + <method name="get_version" qualifiers="const"> + <return type="String" /> + <param index="0" name="name" type="StringName" /> + <param index="1" name="windows_version" type="bool" /> + <description> + Returns the preset's version number, or fall back to the [member ProjectSettings.application/config/version] project setting if set to an empty string. + If [param windows_version] is [code]true[/code], formats the returned version number to be compatible with Windows executable metadata. + </description> + </method> + <method name="has" qualifiers="const"> + <return type="bool" /> + <param index="0" name="property" type="StringName" /> + <description> + Returns [code]true[/code] if preset has specified property. + </description> + </method> + <method name="has_export_file"> + <return type="bool" /> + <param index="0" name="path" type="String" /> + <description> + Returns [code]true[/code] if specified file is exported. + </description> + </method> + <method name="is_dedicated_server" qualifiers="const"> + <return type="bool" /> + <description> + Returns [code]true[/code] if dedicated server export mode is selected in the export dialog. + </description> + </method> + <method name="is_runnable" qualifiers="const"> + <return type="bool" /> + <description> + Returns [code]true[/code] if "Runnable" toggle is enabled in the export dialog. + </description> + </method> + </methods> + <constants> + <constant name="EXPORT_ALL_RESOURCES" value="0" enum="ExportFilter"> + </constant> + <constant name="EXPORT_SELECTED_SCENES" value="1" enum="ExportFilter"> + </constant> + <constant name="EXPORT_SELECTED_RESOURCES" value="2" enum="ExportFilter"> + </constant> + <constant name="EXCLUDE_SELECTED_RESOURCES" value="3" enum="ExportFilter"> + </constant> + <constant name="EXPORT_CUSTOMIZED" value="4" enum="ExportFilter"> + </constant> + <constant name="MODE_FILE_NOT_CUSTOMIZED" value="0" enum="FileExportMode"> + </constant> + <constant name="MODE_FILE_STRIP" value="1" enum="FileExportMode"> + </constant> + <constant name="MODE_FILE_KEEP" value="2" enum="FileExportMode"> + </constant> + <constant name="MODE_FILE_REMOVE" value="3" enum="FileExportMode"> + </constant> + <constant name="MODE_SCRIPT_TEXT" value="0" enum="ScriptExportMode"> + </constant> + <constant name="MODE_SCRIPT_BINARY_TOKENS" value="1" enum="ScriptExportMode"> + </constant> + <constant name="MODE_SCRIPT_BINARY_TOKENS_COMPRESSED" value="2" enum="ScriptExportMode"> + </constant> + </constants> +</class> diff --git a/doc/classes/EditorInterface.xml b/doc/classes/EditorInterface.xml index 178be439c3..6809d4ac93 100644 --- a/doc/classes/EditorInterface.xml +++ b/doc/classes/EditorInterface.xml @@ -117,6 +117,12 @@ [b]Note:[/b] When creating custom editor UI, prefer accessing theme items directly from your GUI nodes using the [code]get_theme_*[/code] methods. </description> </method> + <method name="get_editor_undo_redo" qualifiers="const"> + <return type="EditorUndoRedoManager" /> + <description> + Returns the editor's [EditorUndoRedoManager]. + </description> + </method> <method name="get_editor_viewport_2d" qualifiers="const"> <return type="SubViewport" /> <description> diff --git a/doc/classes/EditorPlugin.xml b/doc/classes/EditorPlugin.xml index ed3233b1ae..37f8b2213b 100644 --- a/doc/classes/EditorPlugin.xml +++ b/doc/classes/EditorPlugin.xml @@ -455,6 +455,13 @@ Adds a [Script] as debugger plugin to the Debugger. The script must extend [EditorDebuggerPlugin]. </description> </method> + <method name="add_export_platform"> + <return type="void" /> + <param index="0" name="platform" type="EditorExportPlatform" /> + <description> + Registers a new [EditorExportPlatform]. Export platforms provides functionality of exporting to the specific platform. + </description> + </method> <method name="add_export_plugin"> <return type="void" /> <param index="0" name="plugin" type="EditorExportPlugin" /> @@ -653,6 +660,13 @@ Removes the debugger plugin with given script from the Debugger. </description> </method> + <method name="remove_export_platform"> + <return type="void" /> + <param index="0" name="platform" type="EditorExportPlatform" /> + <description> + Removes an export platform registered by [method add_export_platform]. + </description> + </method> <method name="remove_export_plugin"> <return type="void" /> <param index="0" name="plugin" type="EditorExportPlugin" /> diff --git a/doc/classes/EditorUndoRedoManager.xml b/doc/classes/EditorUndoRedoManager.xml index 5ac0d790a2..0f8c69a1bb 100644 --- a/doc/classes/EditorUndoRedoManager.xml +++ b/doc/classes/EditorUndoRedoManager.xml @@ -68,6 +68,21 @@ Register a reference for "undo" that will be erased if the "undo" history is lost. This is useful mostly for nodes removed with the "do" call (not the "undo" call!). </description> </method> + <method name="clear_history"> + <return type="void" /> + <param index="0" name="id" type="int" default="-99" /> + <param index="1" name="increase_version" type="bool" default="true" /> + <description> + Clears the given undo history. You can clear history for a specific scene, global history, or for all scenes at once if [param id] is [constant INVALID_HISTORY]. + If [param increase_version] is [code]true[/code], the undo history version will be increased, marking it as unsaved. Useful for operations that modify the scene, but don't support undo. + [codeblock] + var scene_root = EditorInterface.get_edited_scene_root() + var undo_redo = EditorInterface.get_editor_undo_redo() + undo_redo.clear_history(undo_redo.get_object_history_id(scene_root)) + [/codeblock] + [b]Note:[/b] If you want to mark an edited scene as unsaved without clearing its history, use [method EditorInterface.mark_scene_as_unsaved] instead. + </description> + </method> <method name="commit_action"> <return type="void" /> <param index="0" name="execute" type="bool" default="true" /> diff --git a/doc/classes/GDExtensionManager.xml b/doc/classes/GDExtensionManager.xml index 211bc023c0..97d2d08752 100644 --- a/doc/classes/GDExtensionManager.xml +++ b/doc/classes/GDExtensionManager.xml @@ -56,6 +56,20 @@ </method> </methods> <signals> + <signal name="extension_loaded"> + <param index="0" name="extension" type="GDExtension" /> + <description> + Emitted after the editor has finished loading a new extension. + [b]Note:[/b] This signal is only emitted in editor builds. + </description> + </signal> + <signal name="extension_unloading"> + <param index="0" name="extension" type="GDExtension" /> + <description> + Emitted before the editor starts unloading an extension. + [b]Note:[/b] This signal is only emitted in editor builds. + </description> + </signal> <signal name="extensions_reloaded"> <description> Emitted after the editor has finished reloading one or more extensions. diff --git a/doc/classes/JSON.xml b/doc/classes/JSON.xml index 8a19aa39bf..fe5fdfa89a 100644 --- a/doc/classes/JSON.xml +++ b/doc/classes/JSON.xml @@ -37,6 +37,16 @@ <tutorials> </tutorials> <methods> + <method name="from_native" qualifiers="static"> + <return type="Variant" /> + <param index="0" name="variant" type="Variant" /> + <param index="1" name="allow_classes" type="bool" default="false" /> + <param index="2" name="allow_scripts" type="bool" default="false" /> + <description> + Converts a native engine type to a JSON-compliant dictionary. + By default, classes and scripts are ignored for security reasons, unless [param allow_classes] or [param allow_scripts] are specified. + </description> + </method> <method name="get_error_line" qualifiers="const"> <return type="int" /> <description> @@ -123,6 +133,16 @@ [/codeblock] </description> </method> + <method name="to_native" qualifiers="static"> + <return type="Variant" /> + <param index="0" name="json" type="Variant" /> + <param index="1" name="allow_classes" type="bool" default="false" /> + <param index="2" name="allow_scripts" type="bool" default="false" /> + <description> + Converts a JSON-compliant dictionary that was created with [method from_native] back to native engine types. + By default, classes and scripts are ignored for security reasons, unless [param allow_classes] or [param allow_scripts] are specified. + </description> + </method> </methods> <members> <member name="data" type="Variant" setter="set_data" getter="get_data" default="null"> diff --git a/doc/classes/PropertyTweener.xml b/doc/classes/PropertyTweener.xml index b7aa6947d9..76cf4cbfeb 100644 --- a/doc/classes/PropertyTweener.xml +++ b/doc/classes/PropertyTweener.xml @@ -5,6 +5,7 @@ </brief_description> <description> [PropertyTweener] is used to interpolate a property in an object. See [method Tween.tween_property] for more usage information. + The tweener will finish automatically if the target object is freed. [b]Note:[/b] [method Tween.tween_property] is the only correct way to create [PropertyTweener]. Any [PropertyTweener] created manually will not function correctly. </description> <tutorials> diff --git a/doc/classes/ScriptEditor.xml b/doc/classes/ScriptEditor.xml index 50adecccf5..5cf077c266 100644 --- a/doc/classes/ScriptEditor.xml +++ b/doc/classes/ScriptEditor.xml @@ -10,6 +10,12 @@ <tutorials> </tutorials> <methods> + <method name="get_breakpoints"> + <return type="PackedStringArray" /> + <description> + Returns array of breakpoints. + </description> + </method> <method name="get_current_editor" qualifiers="const"> <return type="ScriptEditorBase" /> <description> diff --git a/doc/classes/Tweener.xml b/doc/classes/Tweener.xml index 65148e875d..88f5f9978c 100644 --- a/doc/classes/Tweener.xml +++ b/doc/classes/Tweener.xml @@ -11,7 +11,7 @@ <signals> <signal name="finished"> <description> - Emitted when the [Tweener] has just finished its job. + Emitted when the [Tweener] has just finished its job or became invalid (e.g. due to a freed object). </description> </signal> </signals> diff --git a/drivers/windows/dir_access_windows.cpp b/drivers/windows/dir_access_windows.cpp index b8677fe404..f7632842ed 100644 --- a/drivers/windows/dir_access_windows.cpp +++ b/drivers/windows/dir_access_windows.cpp @@ -71,7 +71,9 @@ struct DirAccessWindowsPrivate { String DirAccessWindows::fix_path(const String &p_path) const { String r_path = DirAccess::fix_path(p_path.trim_prefix(R"(\\?\)").replace("\\", "/")); - + if (r_path.ends_with(":")) { + r_path += "/"; + } if (r_path.is_relative_path()) { r_path = current_dir.trim_prefix(R"(\\?\)").replace("\\", "/").path_join(r_path); } else if (r_path == ".") { diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp index f4403780c2..7d09415140 100644 --- a/editor/animation_track_editor.cpp +++ b/editor/animation_track_editor.cpp @@ -41,6 +41,7 @@ #include "editor/gui/editor_spin_slider.h" #include "editor/gui/scene_tree_editor.h" #include "editor/inspector_dock.h" +#include "editor/multi_node_edit.h" #include "editor/plugins/animation_player_editor_plugin.h" #include "editor/themes/editor_scale.h" #include "scene/3d/mesh_instance_3d.h" @@ -3661,7 +3662,7 @@ void AnimationTrackEditor::update_keying() { EditorSelectionHistory *editor_history = EditorNode::get_singleton()->get_editor_selection_history(); if (is_visible_in_tree() && animation.is_valid() && editor_history->get_path_size() > 0) { Object *obj = ObjectDB::get_instance(editor_history->get_path_object(0)); - keying_enabled = Object::cast_to<Node>(obj) != nullptr; + keying_enabled = Object::cast_to<Node>(obj) != nullptr || Object::cast_to<MultiNodeEdit>(obj) != nullptr; } if (keying_enabled == keying) { @@ -4078,19 +4079,20 @@ void AnimationTrackEditor::_insert_animation_key(NodePath p_path, const Variant _query_insert(id); } -void AnimationTrackEditor::insert_node_value_key(Node *p_node, const String &p_property, const Variant &p_value, bool p_only_if_exists) { +void AnimationTrackEditor::insert_node_value_key(Node *p_node, const String &p_property, bool p_only_if_exists, bool p_advance) { ERR_FAIL_NULL(root); // Let's build a node path. - Node *node = p_node; - String path = root->get_path_to(node, true); + String path = root->get_path_to(p_node, true); + + Variant value = p_node->get(p_property); - if (Object::cast_to<AnimationPlayer>(node) && p_property == "current_animation") { - if (node == AnimationPlayerEditor::get_singleton()->get_player()) { + if (Object::cast_to<AnimationPlayer>(p_node) && p_property == "current_animation") { + if (p_node == AnimationPlayerEditor::get_singleton()->get_player()) { EditorNode::get_singleton()->show_warning(TTR("AnimationPlayer can't animate itself, only other players.")); return; } - _insert_animation_key(path, p_value); + _insert_animation_key(path, value); return; } @@ -4118,26 +4120,26 @@ void AnimationTrackEditor::insert_node_value_key(Node *p_node, const String &p_p InsertData id; id.path = np; id.track_idx = i; - id.value = p_value; + id.value = value; id.type = Animation::TYPE_VALUE; // TRANSLATORS: This describes the target of new animation track, will be inserted into another string. id.query = vformat(TTR("property '%s'"), p_property); - id.advance = false; + id.advance = p_advance; // Dialog insert. _query_insert(id); inserted = true; } else if (animation->track_get_type(i) == Animation::TYPE_BEZIER) { - Variant value; + Variant actual_value; String track_path = animation->track_get_path(i); if (track_path == np) { - value = p_value; // All good. + actual_value = value; // All good. } else { int sep = track_path.rfind(":"); if (sep != -1) { String base_path = track_path.substr(0, sep); if (base_path == np) { String value_name = track_path.substr(sep + 1); - value = p_value.get(value_name); + actual_value = value.get(value_name); } else { continue; } @@ -4149,10 +4151,10 @@ void AnimationTrackEditor::insert_node_value_key(Node *p_node, const String &p_p InsertData id; id.path = animation->track_get_path(i); id.track_idx = i; - id.value = value; + id.value = actual_value; id.type = Animation::TYPE_BEZIER; id.query = vformat(TTR("property '%s'"), p_property); - id.advance = false; + id.advance = p_advance; // Dialog insert. _query_insert(id); inserted = true; @@ -4165,105 +4167,41 @@ void AnimationTrackEditor::insert_node_value_key(Node *p_node, const String &p_p InsertData id; id.path = np; id.track_idx = -1; - id.value = p_value; + id.value = value; id.type = Animation::TYPE_VALUE; id.query = vformat(TTR("property '%s'"), p_property); - id.advance = false; + id.advance = p_advance; // Dialog insert. _query_insert(id); } -void AnimationTrackEditor::insert_value_key(const String &p_property, const Variant &p_value, bool p_advance) { +void AnimationTrackEditor::insert_value_key(const String &p_property, bool p_advance) { EditorSelectionHistory *history = EditorNode::get_singleton()->get_editor_selection_history(); ERR_FAIL_NULL(root); ERR_FAIL_COND(history->get_path_size() == 0); Object *obj = ObjectDB::get_instance(history->get_path_object(0)); - ERR_FAIL_NULL(Object::cast_to<Node>(obj)); - - // Let's build a node path. - Node *node = Object::cast_to<Node>(obj); - String path = root->get_path_to(node, true); - - if (Object::cast_to<AnimationPlayer>(node) && p_property == "current_animation") { - if (node == AnimationPlayerEditor::get_singleton()->get_player()) { - EditorNode::get_singleton()->show_warning(TTR("AnimationPlayer can't animate itself, only other players.")); - return; - } - _insert_animation_key(path, p_value); - return; - } - - for (int i = 1; i < history->get_path_size(); i++) { - String prop = history->get_path_property(i); - ERR_FAIL_COND(prop.is_empty()); - path += ":" + prop; - } - - path += ":" + p_property; - NodePath np = path; + Ref<MultiNodeEdit> multi_node_edit(obj); + if (multi_node_edit.is_valid()) { + Node *edited_scene = EditorNode::get_singleton()->get_edited_scene(); + ERR_FAIL_NULL(edited_scene); - // Locate track. - - bool inserted = false; + make_insert_queue(); - make_insert_queue(); - for (int i = 0; i < animation->get_track_count(); i++) { - if (animation->track_get_type(i) == Animation::TYPE_VALUE) { - if (animation->track_get_path(i) != np) { - continue; - } - - InsertData id; - id.path = np; - id.track_idx = i; - id.value = p_value; - id.type = Animation::TYPE_VALUE; - id.query = vformat(TTR("property '%s'"), p_property); - id.advance = p_advance; - // Dialog insert. - _query_insert(id); - inserted = true; - } else if (animation->track_get_type(i) == Animation::TYPE_BEZIER) { - Variant value; - if (animation->track_get_path(i) == np) { - value = p_value; // All good. - } else { - String tpath = animation->track_get_path(i); - int index = tpath.rfind(":"); - if (NodePath(tpath.substr(0, index + 1)) == np) { - String subindex = tpath.substr(index + 1, tpath.length() - index); - value = p_value.get(subindex); - } else { - continue; - } - } - - InsertData id; - id.path = animation->track_get_path(i); - id.track_idx = i; - id.value = value; - id.type = Animation::TYPE_BEZIER; - id.query = vformat(TTR("property '%s'"), p_property); - id.advance = p_advance; - // Dialog insert. - _query_insert(id); - inserted = true; + for (int i = 0; i < multi_node_edit->get_node_count(); ++i) { + Node *node = edited_scene->get_node(multi_node_edit->get_node(i)); + insert_node_value_key(node, p_property, false, p_advance); } - } - commit_insert_queue(); - if (!inserted) { - InsertData id; - id.path = np; - id.track_idx = -1; - id.value = p_value; - id.type = Animation::TYPE_VALUE; - id.query = vformat(TTR("property '%s'"), p_property); - id.advance = p_advance; - // Dialog insert. - _query_insert(id); + commit_insert_queue(); + } else { + Node *node = Object::cast_to<Node>(obj); + ERR_FAIL_NULL(node); + + make_insert_queue(); + insert_node_value_key(node, p_property, false, p_advance); + commit_insert_queue(); } } @@ -6887,8 +6825,8 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) { _redraw_tracks(); _update_key_edit(); EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); - undo_redo->clear_history(true, undo_redo->get_history_id_for_object(animation.ptr())); - undo_redo->clear_history(true, undo_redo->get_history_id_for_object(this)); + undo_redo->clear_history(undo_redo->get_history_id_for_object(animation.ptr())); + undo_redo->clear_history(undo_redo->get_history_id_for_object(this)); } break; case EDIT_CLEAN_UP_ANIMATION: { @@ -7030,8 +6968,8 @@ void AnimationTrackEditor::_cleanup_animation(Ref<Animation> p_animation) { } EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); - undo_redo->clear_history(true, undo_redo->get_history_id_for_object(animation.ptr())); - undo_redo->clear_history(true, undo_redo->get_history_id_for_object(this)); + undo_redo->clear_history(undo_redo->get_history_id_for_object(animation.ptr())); + undo_redo->clear_history(undo_redo->get_history_id_for_object(this)); _update_tracks(); } diff --git a/editor/animation_track_editor.h b/editor/animation_track_editor.h index 59ee6535ac..a517ba8b43 100644 --- a/editor/animation_track_editor.h +++ b/editor/animation_track_editor.h @@ -713,8 +713,8 @@ public: void cleanup(); void set_anim_pos(float p_pos); - void insert_node_value_key(Node *p_node, const String &p_property, const Variant &p_value, bool p_only_if_exists = false); - void insert_value_key(const String &p_property, const Variant &p_value, bool p_advance); + void insert_node_value_key(Node *p_node, const String &p_property, bool p_only_if_exists = false, bool p_advance = false); + void insert_value_key(const String &p_property, bool p_advance); void insert_transform_key(Node3D *p_node, const String &p_sub, const Animation::TrackType p_type, const Variant &p_value); bool has_track(Node3D *p_node, const String &p_sub, const Animation::TrackType p_type); void make_insert_queue(); diff --git a/editor/debugger/editor_debugger_node.cpp b/editor/debugger/editor_debugger_node.cpp index f01301384a..b4265f9fc0 100644 --- a/editor/debugger/editor_debugger_node.cpp +++ b/editor/debugger/editor_debugger_node.cpp @@ -303,7 +303,7 @@ void EditorDebuggerNode::stop(bool p_force) { }); _break_state_changed(); breakpoints.clear(); - EditorUndoRedoManager::get_singleton()->clear_history(false, EditorUndoRedoManager::REMOTE_HISTORY); + EditorUndoRedoManager::get_singleton()->clear_history(EditorUndoRedoManager::REMOTE_HISTORY, false); set_process(false); } diff --git a/editor/editor_audio_buses.cpp b/editor/editor_audio_buses.cpp index 3b337997e0..c076c99cd3 100644 --- a/editor/editor_audio_buses.cpp +++ b/editor/editor_audio_buses.cpp @@ -1262,7 +1262,7 @@ void EditorAudioBuses::_load_default_layout() { file->set_text(String(TTR("Layout:")) + " " + layout_path.get_file()); AudioServer::get_singleton()->set_bus_layout(state); _rebuild_buses(); - EditorUndoRedoManager::get_singleton()->clear_history(true, EditorUndoRedoManager::GLOBAL_HISTORY); + EditorUndoRedoManager::get_singleton()->clear_history(EditorUndoRedoManager::GLOBAL_HISTORY); callable_mp(this, &EditorAudioBuses::_select_layout).call_deferred(); } @@ -1278,7 +1278,7 @@ void EditorAudioBuses::_file_dialog_callback(const String &p_string) { file->set_text(String(TTR("Layout:")) + " " + p_string.get_file()); AudioServer::get_singleton()->set_bus_layout(state); _rebuild_buses(); - EditorUndoRedoManager::get_singleton()->clear_history(true, EditorUndoRedoManager::GLOBAL_HISTORY); + EditorUndoRedoManager::get_singleton()->clear_history(EditorUndoRedoManager::GLOBAL_HISTORY); callable_mp(this, &EditorAudioBuses::_select_layout).call_deferred(); } else if (file_dialog->get_file_mode() == EditorFileDialog::FILE_MODE_SAVE_FILE) { @@ -1298,7 +1298,7 @@ void EditorAudioBuses::_file_dialog_callback(const String &p_string) { edited_path = p_string; file->set_text(String(TTR("Layout:")) + " " + p_string.get_file()); _rebuild_buses(); - EditorUndoRedoManager::get_singleton()->clear_history(true, EditorUndoRedoManager::GLOBAL_HISTORY); + EditorUndoRedoManager::get_singleton()->clear_history(EditorUndoRedoManager::GLOBAL_HISTORY); callable_mp(this, &EditorAudioBuses::_select_layout).call_deferred(); } } @@ -1397,7 +1397,7 @@ void EditorAudioBuses::open_layout(const String &p_path) { file->set_text(p_path.get_file()); AudioServer::get_singleton()->set_bus_layout(state); _rebuild_buses(); - EditorUndoRedoManager::get_singleton()->clear_history(true, EditorUndoRedoManager::GLOBAL_HISTORY); + EditorUndoRedoManager::get_singleton()->clear_history(EditorUndoRedoManager::GLOBAL_HISTORY); callable_mp(this, &EditorAudioBuses::_select_layout).call_deferred(); } diff --git a/editor/editor_file_system.cpp b/editor/editor_file_system.cpp index 474a45cf2b..bae9062ff1 100644 --- a/editor/editor_file_system.cpp +++ b/editor/editor_file_system.cpp @@ -243,18 +243,27 @@ void EditorFileSystem::_first_scan_filesystem() { first_scan_root_dir = memnew(ScannedDirectory); first_scan_root_dir->full_path = "res://"; HashSet<String> existing_class_names; + HashSet<String> extensions; ep.step(TTR("Scanning file structure..."), 0, true); nb_files_total = _scan_new_dir(first_scan_root_dir, d); // This loads the global class names from the scripts and ensures that even if the // global_script_class_cache.cfg was missing or invalid, the global class names are valid in ScriptServer. + // At the same time, to prevent looping multiple times in all files, it looks for extensions. ep.step(TTR("Loading global class names..."), 1, true); - _first_scan_process_scripts(first_scan_root_dir, existing_class_names); + _first_scan_process_scripts(first_scan_root_dir, existing_class_names, extensions); // Removing invalid global class to prevent having invalid paths in ScriptServer. _remove_invalid_global_class_names(existing_class_names); + // Processing extensions to add new extensions or remove invalid ones. + // Important to do it in the first scan so custom types, new class names, custom importers, etc... + // from extensions are ready to go before plugins, autoloads and resources validation/importation. + // At this point, a restart of the editor should not be needed so we don't use the return value. + ep.step(TTR("Verifying GDExtensions..."), 2, true); + GDExtensionManager::get_singleton()->ensure_extensions_loaded(extensions); + // Now that all the global class names should be loaded, create autoloads and plugins. // This is done after loading the global class names because autoloads and plugins can use // global class names. @@ -267,9 +276,9 @@ void EditorFileSystem::_first_scan_filesystem() { ep.step(TTR("Starting file scan..."), 5, true); } -void EditorFileSystem::_first_scan_process_scripts(const ScannedDirectory *p_scan_dir, HashSet<String> &p_existing_class_names) { +void EditorFileSystem::_first_scan_process_scripts(const ScannedDirectory *p_scan_dir, HashSet<String> &p_existing_class_names, HashSet<String> &p_extensions) { for (ScannedDirectory *scan_sub_dir : p_scan_dir->subdirs) { - _first_scan_process_scripts(scan_sub_dir, p_existing_class_names); + _first_scan_process_scripts(scan_sub_dir, p_existing_class_names, p_extensions); } for (const String &scan_file : p_scan_dir->files) { @@ -285,6 +294,8 @@ void EditorFileSystem::_first_scan_process_scripts(const ScannedDirectory *p_sca if (!script_class_name.is_empty()) { p_existing_class_names.insert(script_class_name); } + } else if (type == SNAME("GDExtension")) { + p_extensions.insert(path); } } } @@ -1855,25 +1866,26 @@ void EditorFileSystem::_update_script_classes() { return; } - update_script_mutex.lock(); + { + MutexLock update_script_lock(update_script_mutex); - EditorProgress *ep = nullptr; - if (update_script_paths.size() > 1) { - ep = memnew(EditorProgress("update_scripts_classes", TTR("Registering global classes..."), update_script_paths.size())); - } + EditorProgress *ep = nullptr; + if (update_script_paths.size() > 1) { + ep = memnew(EditorProgress("update_scripts_classes", TTR("Registering global classes..."), update_script_paths.size())); + } - int step_count = 0; - for (const KeyValue<String, ScriptInfo> &E : update_script_paths) { - _register_global_class_script(E.key, E.key, E.value.type, E.value.script_class_name, E.value.script_class_extends, E.value.script_class_icon_path); - if (ep) { - ep->step(E.value.script_class_name, step_count++, false); + int step_count = 0; + for (const KeyValue<String, ScriptInfo> &E : update_script_paths) { + _register_global_class_script(E.key, E.key, E.value.type, E.value.script_class_name, E.value.script_class_extends, E.value.script_class_icon_path); + if (ep) { + ep->step(E.value.script_class_name, step_count++, false); + } } - } - memdelete_notnull(ep); + memdelete_notnull(ep); - update_script_paths.clear(); - update_script_mutex.unlock(); + update_script_paths.clear(); + } ScriptServer::save_global_classes(); EditorNode::get_editor_data().script_class_save_icon_paths(); @@ -1894,7 +1906,7 @@ void EditorFileSystem::_update_script_documentation() { return; } - update_script_mutex.lock(); + MutexLock update_script_lock(update_script_mutex); EditorProgress *ep = nullptr; if (update_script_paths_documentation.size() > 1) { @@ -1933,7 +1945,6 @@ void EditorFileSystem::_update_script_documentation() { memdelete_notnull(ep); update_script_paths_documentation.clear(); - update_script_mutex.unlock(); } void EditorFileSystem::_process_update_pending() { @@ -1945,7 +1956,7 @@ void EditorFileSystem::_process_update_pending() { } void EditorFileSystem::_queue_update_script_class(const String &p_path, const String &p_type, const String &p_script_class_name, const String &p_script_class_extends, const String &p_script_class_icon_path) { - update_script_mutex.lock(); + MutexLock update_script_lock(update_script_mutex); ScriptInfo si; si.type = p_type; @@ -1955,8 +1966,6 @@ void EditorFileSystem::_queue_update_script_class(const String &p_path, const St update_script_paths.insert(p_path, si); update_script_paths_documentation.insert(p_path); - - update_script_mutex.unlock(); } void EditorFileSystem::_update_scene_groups() { @@ -1970,31 +1979,32 @@ void EditorFileSystem::_update_scene_groups() { } int step_count = 0; - update_scene_mutex.lock(); - for (const String &path : update_scene_paths) { - ProjectSettings::get_singleton()->remove_scene_groups_cache(path); + { + MutexLock update_scene_lock(update_scene_mutex); + for (const String &path : update_scene_paths) { + ProjectSettings::get_singleton()->remove_scene_groups_cache(path); - int index = -1; - EditorFileSystemDirectory *efd = find_file(path, &index); + int index = -1; + EditorFileSystemDirectory *efd = find_file(path, &index); - if (!efd || index < 0) { - // The file was removed. - continue; - } + if (!efd || index < 0) { + // The file was removed. + continue; + } - const HashSet<StringName> scene_groups = PackedScene::get_scene_groups(path); - if (!scene_groups.is_empty()) { - ProjectSettings::get_singleton()->add_scene_groups_cache(path, scene_groups); - } + const HashSet<StringName> scene_groups = PackedScene::get_scene_groups(path); + if (!scene_groups.is_empty()) { + ProjectSettings::get_singleton()->add_scene_groups_cache(path, scene_groups); + } - if (ep) { - ep->step(efd->files[index]->file, step_count++, false); + if (ep) { + ep->step(efd->files[index]->file, step_count++, false); + } } - } - memdelete_notnull(ep); - update_scene_paths.clear(); - update_scene_mutex.unlock(); + memdelete_notnull(ep); + update_scene_paths.clear(); + } ProjectSettings::get_singleton()->save_scene_groups_cache(); } @@ -2009,9 +2019,8 @@ void EditorFileSystem::_update_pending_scene_groups() { } void EditorFileSystem::_queue_update_scene_groups(const String &p_path) { - update_scene_mutex.lock(); + MutexLock update_scene_lock(update_scene_mutex); update_scene_paths.insert(p_path); - update_scene_mutex.unlock(); } void EditorFileSystem::_get_all_scenes(EditorFileSystemDirectory *p_dir, HashSet<String> &r_list) { @@ -3016,57 +3025,7 @@ bool EditorFileSystem::_scan_extensions() { _scan_extensions_dir(d, extensions); - //verify against loaded extensions - - Vector<String> extensions_added; - Vector<String> extensions_removed; - - for (const String &E : extensions) { - if (!GDExtensionManager::get_singleton()->is_extension_loaded(E)) { - extensions_added.push_back(E); - } - } - - Vector<String> loaded_extensions = GDExtensionManager::get_singleton()->get_loaded_extensions(); - for (int i = 0; i < loaded_extensions.size(); i++) { - if (!extensions.has(loaded_extensions[i])) { - // The extension may not have a .gdextension file. - if (!FileAccess::exists(loaded_extensions[i])) { - extensions_removed.push_back(loaded_extensions[i]); - } - } - } - - String extension_list_config_file = GDExtension::get_extension_list_config_file(); - if (extensions.size()) { - if (extensions_added.size() || extensions_removed.size()) { //extensions were added or removed - Ref<FileAccess> f = FileAccess::open(extension_list_config_file, FileAccess::WRITE); - for (const String &E : extensions) { - f->store_line(E); - } - } - } else { - if (loaded_extensions.size() || FileAccess::exists(extension_list_config_file)) { //extensions were removed - Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES); - da->remove(extension_list_config_file); - } - } - - bool needs_restart = false; - for (int i = 0; i < extensions_added.size(); i++) { - GDExtensionManager::LoadStatus st = GDExtensionManager::get_singleton()->load_extension(extensions_added[i]); - if (st == GDExtensionManager::LOAD_STATUS_NEEDS_RESTART) { - needs_restart = true; - } - } - for (int i = 0; i < extensions_removed.size(); i++) { - GDExtensionManager::LoadStatus st = GDExtensionManager::get_singleton()->unload_extension(extensions_removed[i]); - if (st == GDExtensionManager::LOAD_STATUS_NEEDS_RESTART) { - needs_restart = true; - } - } - - return needs_restart; + return GDExtensionManager::get_singleton()->ensure_extensions_loaded(extensions); } void EditorFileSystem::_bind_methods() { diff --git a/editor/editor_file_system.h b/editor/editor_file_system.h index 1bc24416eb..ca4a64bfac 100644 --- a/editor/editor_file_system.h +++ b/editor/editor_file_system.h @@ -187,7 +187,7 @@ class EditorFileSystem : public Node { void _scan_filesystem(); void _first_scan_filesystem(); - void _first_scan_process_scripts(const ScannedDirectory *p_scan_dir, HashSet<String> &p_existing_class_names); + void _first_scan_process_scripts(const ScannedDirectory *p_scan_dir, HashSet<String> &p_existing_class_names, HashSet<String> &p_extensions); HashSet<String> late_update_files; diff --git a/editor/editor_interface.cpp b/editor/editor_interface.cpp index 46113ab2cb..e699b486ee 100644 --- a/editor/editor_interface.cpp +++ b/editor/editor_interface.cpp @@ -86,6 +86,10 @@ Ref<EditorSettings> EditorInterface::get_editor_settings() const { return EditorSettings::get_singleton(); } +EditorUndoRedoManager *EditorInterface::get_editor_undo_redo() const { + return EditorUndoRedoManager::get_singleton(); +} + TypedArray<Texture2D> EditorInterface::_make_mesh_previews(const TypedArray<Mesh> &p_meshes, int p_preview_size) { Vector<Ref<Mesh>> meshes; @@ -525,6 +529,7 @@ void EditorInterface::_bind_methods() { ClassDB::bind_method(D_METHOD("get_resource_previewer"), &EditorInterface::get_resource_previewer); ClassDB::bind_method(D_METHOD("get_selection"), &EditorInterface::get_selection); ClassDB::bind_method(D_METHOD("get_editor_settings"), &EditorInterface::get_editor_settings); + ClassDB::bind_method(D_METHOD("get_editor_undo_redo"), &EditorInterface::get_editor_undo_redo); ClassDB::bind_method(D_METHOD("make_mesh_previews", "meshes", "preview_size"), &EditorInterface::_make_mesh_previews); diff --git a/editor/editor_interface.h b/editor/editor_interface.h index 3ef4325780..2538f97c77 100644 --- a/editor/editor_interface.h +++ b/editor/editor_interface.h @@ -45,6 +45,7 @@ class EditorPlugin; class EditorResourcePreview; class EditorSelection; class EditorSettings; +class EditorUndoRedoManager; class FileSystemDock; class Mesh; class Node; @@ -93,6 +94,7 @@ public: EditorResourcePreview *get_resource_previewer() const; EditorSelection *get_selection() const; Ref<EditorSettings> get_editor_settings() const; + EditorUndoRedoManager *get_editor_undo_redo() const; Vector<Ref<Texture2D>> make_mesh_previews(const Vector<Ref<Mesh>> &p_meshes, Vector<Transform3D> *p_transforms, int p_preview_size); diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index c6144a34cb..d3fa2ed716 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -481,6 +481,9 @@ void EditorNode::_gdextensions_reloaded() { // In case the developer is inspecting an object that will be changed by the reload. InspectorDock::get_inspector_singleton()->update_tree(); + // Reload script editor to revalidate GDScript if classes are added or removed. + ScriptEditor::get_singleton()->reload_scripts(true); + // Regenerate documentation. EditorHelp::generate_doc(); } @@ -2961,7 +2964,7 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { ERR_PRINT("Failed to load scene"); } editor_data.move_edited_scene_to_index(cur_idx); - EditorUndoRedoManager::get_singleton()->clear_history(false, editor_data.get_current_edited_scene_history_id()); + EditorUndoRedoManager::get_singleton()->clear_history(editor_data.get_current_edited_scene_history_id(), false); scene_tabs->set_current_tab(cur_idx); } break; @@ -3963,7 +3966,7 @@ void EditorNode::_set_current_scene_nocheck(int p_idx) { editor_folding.load_scene_folding(editor_data.get_edited_scene_root(p_idx), editor_data.get_scene_path(p_idx)); } - EditorUndoRedoManager::get_singleton()->clear_history(false, editor_data.get_scene_history_id(p_idx)); + EditorUndoRedoManager::get_singleton()->clear_history(editor_data.get_scene_history_id(p_idx), false); } Dictionary state = editor_data.restore_edited_scene_state(editor_selection, &editor_history); @@ -4073,7 +4076,7 @@ Error EditorNode::load_scene(const String &p_scene, bool p_ignore_broken_deps, b _set_current_scene(idx); } } else { - EditorUndoRedoManager::get_singleton()->clear_history(false, editor_data.get_current_edited_scene_history_id()); + EditorUndoRedoManager::get_singleton()->clear_history(editor_data.get_current_edited_scene_history_id(), false); } dependency_errors.clear(); @@ -4949,7 +4952,9 @@ bool EditorNode::is_object_of_custom_type(const Object *p_object, const StringNa } void EditorNode::progress_add_task(const String &p_task, const String &p_label, int p_steps, bool p_can_cancel) { - if (singleton->cmdline_export_mode) { + if (!singleton) { + return; + } else if (singleton->cmdline_export_mode) { print_line(p_task + ": begin: " + p_label + " steps: " + itos(p_steps)); } else if (singleton->progress_dialog) { singleton->progress_dialog->add_task(p_task, p_label, p_steps, p_can_cancel); @@ -4957,7 +4962,9 @@ void EditorNode::progress_add_task(const String &p_task, const String &p_label, } bool EditorNode::progress_task_step(const String &p_task, const String &p_state, int p_step, bool p_force_refresh) { - if (singleton->cmdline_export_mode) { + if (!singleton) { + return false; + } else if (singleton->cmdline_export_mode) { print_line("\t" + p_task + ": step " + itos(p_step) + ": " + p_state); return false; } else if (singleton->progress_dialog) { @@ -4968,7 +4975,9 @@ bool EditorNode::progress_task_step(const String &p_task, const String &p_state, } void EditorNode::progress_end_task(const String &p_task) { - if (singleton->cmdline_export_mode) { + if (!singleton) { + return; + } else if (singleton->cmdline_export_mode) { print_line(p_task + ": end"); } else if (singleton->progress_dialog) { singleton->progress_dialog->end_task(p_task); @@ -5928,7 +5937,7 @@ void EditorNode::reload_scene(const String &p_path) { bool is_unsaved = EditorUndoRedoManager::get_singleton()->is_history_unsaved(current_history_id); // Scene is not open, so at it might be instantiated. We'll refresh the whole scene later. - EditorUndoRedoManager::get_singleton()->clear_history(false, current_history_id); + EditorUndoRedoManager::get_singleton()->clear_history(current_history_id, false); if (is_unsaved) { EditorUndoRedoManager::get_singleton()->set_history_as_unsaved(current_history_id); } @@ -5947,7 +5956,7 @@ void EditorNode::reload_scene(const String &p_path) { // Adjust index so tab is back a the previous position. editor_data.move_edited_scene_to_index(scene_idx); - EditorUndoRedoManager::get_singleton()->clear_history(false, editor_data.get_scene_history_id(scene_idx)); + EditorUndoRedoManager::get_singleton()->clear_history(editor_data.get_scene_history_id(scene_idx), false); // Recover the tab. scene_tabs->set_current_tab(current_tab); @@ -6092,7 +6101,7 @@ void EditorNode::reload_instances_with_path_in_edited_scenes() { bool is_unsaved = EditorUndoRedoManager::get_singleton()->is_history_unsaved(current_history_id); // Clear the history for this affected tab. - EditorUndoRedoManager::get_singleton()->clear_history(false, current_history_id); + EditorUndoRedoManager::get_singleton()->clear_history(current_history_id, false); // Update the version editor_data.is_scene_changed(current_scene_idx); @@ -7358,11 +7367,9 @@ EditorNode::EditorNode() { settings_menu->set_item_tooltip(-1, TTR("Screenshots are stored in the user data folder (\"user://\").")); -#ifndef ANDROID_ENABLED ED_SHORTCUT_AND_COMMAND("editor/fullscreen_mode", TTR("Toggle Fullscreen"), KeyModifierMask::SHIFT | Key::F11); ED_SHORTCUT_OVERRIDE("editor/fullscreen_mode", "macos", KeyModifierMask::META | KeyModifierMask::CTRL | Key::F); settings_menu->add_shortcut(ED_GET_SHORTCUT("editor/fullscreen_mode"), SETTINGS_TOGGLE_FULLSCREEN); -#endif settings_menu->add_separator(); #ifndef ANDROID_ENABLED diff --git a/editor/editor_run_native.cpp b/editor/editor_run_native.cpp index 5d378820ae..e0e1ef6d19 100644 --- a/editor/editor_run_native.cpp +++ b/editor/editor_run_native.cpp @@ -141,7 +141,7 @@ Error EditorRunNative::start_run_native(int p_id) { emit_signal(SNAME("native_run"), preset); - int flags = 0; + BitField<EditorExportPlatform::DebugFlags> flags = 0; bool deploy_debug_remote = is_deploy_debug_remote_enabled(); bool deploy_dumb = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_file_server", false); @@ -149,16 +149,16 @@ Error EditorRunNative::start_run_native(int p_id) { bool debug_navigation = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_debug_navigation", false); if (deploy_debug_remote) { - flags |= EditorExportPlatform::DEBUG_FLAG_REMOTE_DEBUG; + flags.set_flag(EditorExportPlatform::DEBUG_FLAG_REMOTE_DEBUG); } if (deploy_dumb) { - flags |= EditorExportPlatform::DEBUG_FLAG_DUMB_CLIENT; + flags.set_flag(EditorExportPlatform::DEBUG_FLAG_DUMB_CLIENT); } if (debug_collisions) { - flags |= EditorExportPlatform::DEBUG_FLAG_VIEW_COLLISIONS; + flags.set_flag(EditorExportPlatform::DEBUG_FLAG_VIEW_COLLISIONS); } if (debug_navigation) { - flags |= EditorExportPlatform::DEBUG_FLAG_VIEW_NAVIGATION; + flags.set_flag(EditorExportPlatform::DEBUG_FLAG_VIEW_NAVIGATION); } eep->clear_messages(); diff --git a/editor/editor_undo_redo_manager.cpp b/editor/editor_undo_redo_manager.cpp index 55bc198dfb..c0bf216634 100644 --- a/editor/editor_undo_redo_manager.cpp +++ b/editor/editor_undo_redo_manager.cpp @@ -390,7 +390,7 @@ bool EditorUndoRedoManager::has_history(int p_idx) const { return history_map.has(p_idx); } -void EditorUndoRedoManager::clear_history(bool p_increase_version, int p_idx) { +void EditorUndoRedoManager::clear_history(int p_idx, bool p_increase_version) { if (p_idx != INVALID_HISTORY) { History &history = get_or_create_history(p_idx); history.undo_redo->clear_history(p_increase_version); @@ -507,6 +507,7 @@ void EditorUndoRedoManager::_bind_methods() { ClassDB::bind_method(D_METHOD("get_object_history_id", "object"), &EditorUndoRedoManager::get_history_id_for_object); ClassDB::bind_method(D_METHOD("get_history_undo_redo", "id"), &EditorUndoRedoManager::get_history_undo_redo); + ClassDB::bind_method(D_METHOD("clear_history", "id", "increase_version"), &EditorUndoRedoManager::clear_history, DEFVAL(INVALID_HISTORY), DEFVAL(true)); ADD_SIGNAL(MethodInfo("history_changed")); ADD_SIGNAL(MethodInfo("version_changed")); diff --git a/editor/editor_undo_redo_manager.h b/editor/editor_undo_redo_manager.h index 219d5e0702..54475c3c6c 100644 --- a/editor/editor_undo_redo_manager.h +++ b/editor/editor_undo_redo_manager.h @@ -125,7 +125,7 @@ public: bool undo_history(int p_id); bool redo(); bool redo_history(int p_id); - void clear_history(bool p_increase_version = true, int p_idx = INVALID_HISTORY); + void clear_history(int p_idx = INVALID_HISTORY, bool p_increase_version = true); void set_history_as_saved(int p_idx); void set_history_as_unsaved(int p_idx); diff --git a/editor/export/editor_export.cpp b/editor/export/editor_export.cpp index 72ab186036..975a601ae1 100644 --- a/editor/export/editor_export.cpp +++ b/editor/export/editor_export.cpp @@ -124,7 +124,17 @@ void EditorExport::_bind_methods() { void EditorExport::add_export_platform(const Ref<EditorExportPlatform> &p_platform) { export_platforms.push_back(p_platform); + should_update_presets = true; + should_reload_presets = true; +} + +void EditorExport::remove_export_platform(const Ref<EditorExportPlatform> &p_platform) { + export_platforms.erase(p_platform); + p_platform->cleanup(); + + should_update_presets = true; + should_reload_presets = true; } int EditorExport::get_export_platform_count() { @@ -244,7 +254,7 @@ void EditorExport::load_config() { if (!preset.is_valid()) { index++; - ERR_CONTINUE(!preset.is_valid()); + continue; // Unknown platform, skip without error (platform might be loaded later). } preset->set_name(config->get_value(section, "name")); @@ -343,6 +353,12 @@ void EditorExport::load_config() { void EditorExport::update_export_presets() { HashMap<StringName, List<EditorExportPlatform::ExportOption>> platform_options; + if (should_reload_presets) { + should_reload_presets = false; + export_presets.clear(); + load_config(); + } + for (int i = 0; i < export_platforms.size(); i++) { Ref<EditorExportPlatform> platform = export_platforms[i]; diff --git a/editor/export/editor_export.h b/editor/export/editor_export.h index f8cb90dc39..ebb2038f53 100644 --- a/editor/export/editor_export.h +++ b/editor/export/editor_export.h @@ -47,6 +47,7 @@ class EditorExport : public Node { Timer *save_timer = nullptr; bool block_save = false; bool should_update_presets = false; + bool should_reload_presets = false; static EditorExport *singleton; @@ -66,6 +67,7 @@ public: void add_export_platform(const Ref<EditorExportPlatform> &p_platform); int get_export_platform_count(); Ref<EditorExportPlatform> get_export_platform(int p_idx); + void remove_export_platform(const Ref<EditorExportPlatform> &p_platform); void add_export_preset(const Ref<EditorExportPreset> &p_preset, int p_at_pos = -1); int get_export_preset_count() const; diff --git a/editor/export/editor_export_platform.cpp b/editor/export/editor_export_platform.cpp index 8b31eda3bc..7ad589a58d 100644 --- a/editor/export/editor_export_platform.cpp +++ b/editor/export/editor_export_platform.cpp @@ -71,7 +71,7 @@ bool EditorExportPlatform::fill_log_messages(RichTextLabel *p_log, Error p_err) p_log->add_text(" "); p_log->add_text(get_name()); p_log->add_text(" - "); - if (p_err == OK) { + if (p_err == OK && get_worst_message_type() < EditorExportPlatform::EXPORT_MESSAGE_ERROR) { if (get_worst_message_type() >= EditorExportPlatform::EXPORT_MESSAGE_WARNING) { p_log->add_image(p_log->get_editor_theme_icon(SNAME("StatusWarning")), 16 * EDSCALE, 16 * EDSCALE, Color(1.0, 1.0, 1.0), INLINE_ALIGNMENT_CENTER); p_log->add_text(" "); @@ -167,58 +167,6 @@ bool EditorExportPlatform::fill_log_messages(RichTextLabel *p_log, Error p_err) return has_messages; } -void EditorExportPlatform::gen_debug_flags(Vector<String> &r_flags, int p_flags) { - String host = EDITOR_GET("network/debug/remote_host"); - int remote_port = (int)EDITOR_GET("network/debug/remote_port"); - - if (EditorSettings::get_singleton()->has_setting("export/android/use_wifi_for_remote_debug") && EDITOR_GET("export/android/use_wifi_for_remote_debug")) { - host = EDITOR_GET("export/android/wifi_remote_debug_host"); - } else if (p_flags & DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST) { - host = "localhost"; - } - - if (p_flags & DEBUG_FLAG_DUMB_CLIENT) { - int port = EDITOR_GET("filesystem/file_server/port"); - String passwd = EDITOR_GET("filesystem/file_server/password"); - r_flags.push_back("--remote-fs"); - r_flags.push_back(host + ":" + itos(port)); - if (!passwd.is_empty()) { - r_flags.push_back("--remote-fs-password"); - r_flags.push_back(passwd); - } - } - - if (p_flags & DEBUG_FLAG_REMOTE_DEBUG) { - r_flags.push_back("--remote-debug"); - - r_flags.push_back(get_debug_protocol() + host + ":" + String::num(remote_port)); - - List<String> breakpoints; - ScriptEditor::get_singleton()->get_breakpoints(&breakpoints); - - if (breakpoints.size()) { - r_flags.push_back("--breakpoints"); - String bpoints; - for (const List<String>::Element *E = breakpoints.front(); E; E = E->next()) { - bpoints += E->get().replace(" ", "%20"); - if (E->next()) { - bpoints += ","; - } - } - - r_flags.push_back(bpoints); - } - } - - if (p_flags & DEBUG_FLAG_VIEW_COLLISIONS) { - r_flags.push_back("--debug-collisions"); - } - - if (p_flags & DEBUG_FLAG_VIEW_NAVIGATION) { - r_flags.push_back("--debug-navigation"); - } -} - Error EditorExportPlatform::_save_pack_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key) { ERR_FAIL_COND_V_MSG(p_total < 1, ERR_PARAMETER_RANGE_ERROR, "Must select at least one file to export."); @@ -530,7 +478,7 @@ HashSet<String> EditorExportPlatform::get_features(const Ref<EditorExportPreset> return result; } -EditorExportPlatform::ExportNotifier::ExportNotifier(EditorExportPlatform &p_platform, const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) { +EditorExportPlatform::ExportNotifier::ExportNotifier(EditorExportPlatform &p_platform, const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) { HashSet<String> features = p_platform.get_features(p_preset, p_debug); Vector<Ref<EditorExportPlugin>> export_plugins = EditorExport::get_singleton()->get_export_plugins(); //initial export plugin callback @@ -919,6 +867,55 @@ Vector<String> EditorExportPlatform::get_forced_export_files() { return files; } +Error EditorExportPlatform::_script_save_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key) { + Callable cb = ((ScriptCallbackData *)p_userdata)->file_cb; + ERR_FAIL_COND_V(!cb.is_valid(), FAILED); + + Variant path = p_path; + Variant data = p_data; + Variant file = p_file; + Variant total = p_total; + Variant enc_in = p_enc_in_filters; + Variant enc_ex = p_enc_ex_filters; + Variant enc_key = p_key; + + Variant ret; + Callable::CallError ce; + const Variant *args[7] = { &path, &data, &file, &total, &enc_in, &enc_ex, &enc_key }; + + cb.callp(args, 7, ret, ce); + ERR_FAIL_COND_V_MSG(ce.error != Callable::CallError::CALL_OK, FAILED, vformat("Failed to execute file save callback: %s.", Variant::get_callable_error_text(cb, args, 7, ce))); + + return (Error)ret.operator int(); +} + +Error EditorExportPlatform::_script_add_shared_object(void *p_userdata, const SharedObject &p_so) { + Callable cb = ((ScriptCallbackData *)p_userdata)->so_cb; + if (!cb.is_valid()) { + return OK; // Optional. + } + + Variant path = p_so.path; + Variant tags = p_so.tags; + Variant target = p_so.target; + + Variant ret; + Callable::CallError ce; + const Variant *args[3] = { &path, &tags, &target }; + + cb.callp(args, 3, ret, ce); + ERR_FAIL_COND_V_MSG(ce.error != Callable::CallError::CALL_OK, FAILED, vformat("Failed to execute shared object save callback: %s.", Variant::get_callable_error_text(cb, args, 3, ce))); + + return (Error)ret.operator int(); +} + +Error EditorExportPlatform::_export_project_files(const Ref<EditorExportPreset> &p_preset, bool p_debug, const Callable &p_save_func, const Callable &p_so_func) { + ScriptCallbackData data; + data.file_cb = p_save_func; + data.so_cb = p_so_func; + return export_project_files(p_preset, p_debug, _script_save_file, &data, _script_add_shared_object); +} + Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> &p_preset, bool p_debug, EditorExportSaveFunction p_func, void *p_udata, EditorExportSaveSharedObject p_so_func) { //figure out paths of files that will be exported HashSet<String> paths; @@ -1425,7 +1422,7 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> & return p_func(p_udata, "res://" + config_file, data, idx, total, enc_in_filters, enc_ex_filters, key); } -Error EditorExportPlatform::_add_shared_object(void *p_userdata, const SharedObject &p_so) { +Error EditorExportPlatform::_pack_add_shared_object(void *p_userdata, const SharedObject &p_so) { PackData *pack_data = (PackData *)p_userdata; if (pack_data->so_files) { pack_data->so_files->push_back(p_so); @@ -1434,6 +1431,15 @@ Error EditorExportPlatform::_add_shared_object(void *p_userdata, const SharedObj return OK; } +Error EditorExportPlatform::_zip_add_shared_object(void *p_userdata, const SharedObject &p_so) { + ZipData *zip_data = (ZipData *)p_userdata; + if (zip_data->so_files) { + zip_data->so_files->push_back(p_so); + } + + return OK; +} + void EditorExportPlatform::zip_folder_recursive(zipFile &p_zip, const String &p_root_path, const String &p_folder, const String &p_pkg_name) { String dir = p_folder.is_empty() ? p_root_path : p_root_path.path_join(p_folder); @@ -1551,6 +1557,54 @@ void EditorExportPlatform::zip_folder_recursive(zipFile &p_zip, const String &p_ da->list_dir_end(); } +Dictionary EditorExportPlatform::_save_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, bool p_embed) { + Vector<SharedObject> so_files; + int64_t embedded_start = 0; + int64_t embedded_size = 0; + Error err_code = save_pack(p_preset, p_debug, p_path, &so_files, p_embed, &embedded_start, &embedded_size); + + Dictionary ret; + ret["result"] = err_code; + if (err_code == OK) { + Array arr; + for (const SharedObject &E : so_files) { + Dictionary so; + so["path"] = E.path; + so["tags"] = E.tags; + so["target_folder"] = E.target; + arr.push_back(so); + } + ret["so_files"] = arr; + if (p_embed) { + ret["embedded_start"] = embedded_start; + ret["embedded_size"] = embedded_size; + } + } + + return ret; +} + +Dictionary EditorExportPlatform::_save_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path) { + Vector<SharedObject> so_files; + Error err_code = save_zip(p_preset, p_debug, p_path, &so_files); + + Dictionary ret; + ret["result"] = err_code; + if (err_code == OK) { + Array arr; + for (const SharedObject &E : so_files) { + Dictionary so; + so["path"] = E.path; + so["tags"] = E.tags; + so["target_folder"] = E.target; + arr.push_back(so); + } + ret["so_files"] = arr; + } + + return ret; +} + Error EditorExportPlatform::save_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, Vector<SharedObject> *p_so_files, bool p_embed, int64_t *r_embedded_start, int64_t *r_embedded_size) { EditorProgress ep("savepack", TTR("Packing"), 102, true); @@ -1570,7 +1624,7 @@ Error EditorExportPlatform::save_pack(const Ref<EditorExportPreset> &p_preset, b pd.f = ftmp; pd.so_files = p_so_files; - Error err = export_project_files(p_preset, p_debug, _save_pack_file, &pd, _add_shared_object); + Error err = export_project_files(p_preset, p_debug, _save_pack_file, &pd, _pack_add_shared_object); // Close temp file. pd.f.unref(); @@ -1777,7 +1831,7 @@ Error EditorExportPlatform::save_pack(const Ref<EditorExportPreset> &p_preset, b return OK; } -Error EditorExportPlatform::save_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path) { +Error EditorExportPlatform::save_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, Vector<SharedObject> *p_so_files) { EditorProgress ep("savezip", TTR("Packing"), 102, true); Ref<FileAccess> io_fa; @@ -1787,8 +1841,9 @@ Error EditorExportPlatform::save_zip(const Ref<EditorExportPreset> &p_preset, bo ZipData zd; zd.ep = &ep; zd.zip = zip; + zd.so_files = p_so_files; - Error err = export_project_files(p_preset, p_debug, _save_zip_file, &zd); + Error err = export_project_files(p_preset, p_debug, _save_zip_file, &zd, _zip_add_shared_object); if (err != OK && err != ERR_SKIP) { add_message(EXPORT_MESSAGE_ERROR, TTR("Save ZIP"), TTR("Failed to export project files.")); } @@ -1798,45 +1853,48 @@ Error EditorExportPlatform::save_zip(const Ref<EditorExportPreset> &p_preset, bo return OK; } -Error EditorExportPlatform::export_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) { +Error EditorExportPlatform::export_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) { ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags); return save_pack(p_preset, p_debug, p_path); } -Error EditorExportPlatform::export_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) { +Error EditorExportPlatform::export_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) { ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags); return save_zip(p_preset, p_debug, p_path); } -void EditorExportPlatform::gen_export_flags(Vector<String> &r_flags, int p_flags) { +Vector<String> EditorExportPlatform::gen_export_flags(BitField<EditorExportPlatform::DebugFlags> p_flags) { + Vector<String> ret; String host = EDITOR_GET("network/debug/remote_host"); int remote_port = (int)EDITOR_GET("network/debug/remote_port"); - if (p_flags & DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST) { + if (get_name() == "Android" && EditorSettings::get_singleton()->has_setting("export/android/use_wifi_for_remote_debug") && EDITOR_GET("export/android/use_wifi_for_remote_debug")) { + host = EDITOR_GET("export/android/wifi_remote_debug_host"); + } else if (p_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST)) { host = "localhost"; } - if (p_flags & DEBUG_FLAG_DUMB_CLIENT) { + if (p_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT)) { int port = EDITOR_GET("filesystem/file_server/port"); String passwd = EDITOR_GET("filesystem/file_server/password"); - r_flags.push_back("--remote-fs"); - r_flags.push_back(host + ":" + itos(port)); + ret.push_back("--remote-fs"); + ret.push_back(host + ":" + itos(port)); if (!passwd.is_empty()) { - r_flags.push_back("--remote-fs-password"); - r_flags.push_back(passwd); + ret.push_back("--remote-fs-password"); + ret.push_back(passwd); } } - if (p_flags & DEBUG_FLAG_REMOTE_DEBUG) { - r_flags.push_back("--remote-debug"); + if (p_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG)) { + ret.push_back("--remote-debug"); - r_flags.push_back(get_debug_protocol() + host + ":" + String::num(remote_port)); + ret.push_back(get_debug_protocol() + host + ":" + String::num(remote_port)); List<String> breakpoints; ScriptEditor::get_singleton()->get_breakpoints(&breakpoints); if (breakpoints.size()) { - r_flags.push_back("--breakpoints"); + ret.push_back("--breakpoints"); String bpoints; for (List<String>::Element *E = breakpoints.front(); E; E = E->next()) { bpoints += E->get().replace(" ", "%20"); @@ -1845,17 +1903,18 @@ void EditorExportPlatform::gen_export_flags(Vector<String> &r_flags, int p_flags } } - r_flags.push_back(bpoints); + ret.push_back(bpoints); } } - if (p_flags & DEBUG_FLAG_VIEW_COLLISIONS) { - r_flags.push_back("--debug-collisions"); + if (p_flags.has_flag(DEBUG_FLAG_VIEW_COLLISIONS)) { + ret.push_back("--debug-collisions"); } - if (p_flags & DEBUG_FLAG_VIEW_NAVIGATION) { - r_flags.push_back("--debug-navigation"); + if (p_flags.has_flag(DEBUG_FLAG_VIEW_NAVIGATION)) { + ret.push_back("--debug-navigation"); } + return ret; } bool EditorExportPlatform::can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug) const { @@ -2035,8 +2094,61 @@ Error EditorExportPlatform::ssh_push_to_remote(const String &p_host, const Strin return OK; } +Array EditorExportPlatform::get_current_presets() const { + Array ret; + for (int i = 0; i < EditorExport::get_singleton()->get_export_preset_count(); i++) { + Ref<EditorExportPreset> ep = EditorExport::get_singleton()->get_export_preset(i); + if (ep->get_platform() == this) { + ret.push_back(ep); + } + } + return ret; +} + void EditorExportPlatform::_bind_methods() { ClassDB::bind_method(D_METHOD("get_os_name"), &EditorExportPlatform::get_os_name); + + ClassDB::bind_method(D_METHOD("create_preset"), &EditorExportPlatform::create_preset); + + ClassDB::bind_method(D_METHOD("find_export_template", "template_file_name"), &EditorExportPlatform::_find_export_template); + ClassDB::bind_method(D_METHOD("get_current_presets"), &EditorExportPlatform::get_current_presets); + + ClassDB::bind_method(D_METHOD("save_pack", "preset", "debug", "path", "embed"), &EditorExportPlatform::_save_pack, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("save_zip", "preset", "debug", "path"), &EditorExportPlatform::_save_zip); + + ClassDB::bind_method(D_METHOD("gen_export_flags", "flags"), &EditorExportPlatform::gen_export_flags); + + ClassDB::bind_method(D_METHOD("export_project_files", "preset", "debug", "save_cb", "shared_cb"), &EditorExportPlatform::_export_project_files, DEFVAL(Callable())); + + ClassDB::bind_method(D_METHOD("export_project", "preset", "debug", "path", "flags"), &EditorExportPlatform::export_project, DEFVAL(0)); + ClassDB::bind_method(D_METHOD("export_pack", "preset", "debug", "path", "flags"), &EditorExportPlatform::export_pack, DEFVAL(0)); + ClassDB::bind_method(D_METHOD("export_zip", "preset", "debug", "path", "flags"), &EditorExportPlatform::export_zip, DEFVAL(0)); + + ClassDB::bind_method(D_METHOD("clear_messages"), &EditorExportPlatform::clear_messages); + ClassDB::bind_method(D_METHOD("add_message", "type", "category", "message"), &EditorExportPlatform::add_message); + ClassDB::bind_method(D_METHOD("get_message_count"), &EditorExportPlatform::get_message_count); + + ClassDB::bind_method(D_METHOD("get_message_type", "index"), &EditorExportPlatform::_get_message_type); + ClassDB::bind_method(D_METHOD("get_message_category", "index"), &EditorExportPlatform::_get_message_category); + ClassDB::bind_method(D_METHOD("get_message_text", "index"), &EditorExportPlatform::_get_message_text); + ClassDB::bind_method(D_METHOD("get_worst_message_type"), &EditorExportPlatform::get_worst_message_type); + + ClassDB::bind_method(D_METHOD("ssh_run_on_remote", "host", "port", "ssh_arg", "cmd_args", "output", "port_fwd"), &EditorExportPlatform::_ssh_run_on_remote, DEFVAL(Array()), DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("ssh_run_on_remote_no_wait", "host", "port", "ssh_args", "cmd_args", "port_fwd"), &EditorExportPlatform::_ssh_run_on_remote_no_wait, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("ssh_push_to_remote", "host", "port", "scp_args", "src_file", "dst_file"), &EditorExportPlatform::ssh_push_to_remote); + + ClassDB::bind_static_method("EditorExportPlatform", D_METHOD("get_forced_export_files"), &EditorExportPlatform::get_forced_export_files); + + BIND_ENUM_CONSTANT(EXPORT_MESSAGE_NONE); + BIND_ENUM_CONSTANT(EXPORT_MESSAGE_INFO); + BIND_ENUM_CONSTANT(EXPORT_MESSAGE_WARNING); + BIND_ENUM_CONSTANT(EXPORT_MESSAGE_ERROR); + + BIND_BITFIELD_FLAG(DEBUG_FLAG_DUMB_CLIENT); + BIND_BITFIELD_FLAG(DEBUG_FLAG_REMOTE_DEBUG); + BIND_BITFIELD_FLAG(DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST); + BIND_BITFIELD_FLAG(DEBUG_FLAG_VIEW_COLLISIONS); + BIND_BITFIELD_FLAG(DEBUG_FLAG_VIEW_NAVIGATION); } EditorExportPlatform::EditorExportPlatform() { diff --git a/editor/export/editor_export_platform.h b/editor/export/editor_export_platform.h index 3fd75ff67f..a800bb95e6 100644 --- a/editor/export/editor_export_platform.h +++ b/editor/export/editor_export_platform.h @@ -56,6 +56,14 @@ public: typedef Error (*EditorExportSaveFunction)(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key); typedef Error (*EditorExportSaveSharedObject)(void *p_userdata, const SharedObject &p_so); + enum DebugFlags { + DEBUG_FLAG_DUMB_CLIENT = 1, + DEBUG_FLAG_REMOTE_DEBUG = 2, + DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST = 4, + DEBUG_FLAG_VIEW_COLLISIONS = 8, + DEBUG_FLAG_VIEW_NAVIGATION = 16, + }; + enum ExportMessageType { EXPORT_MESSAGE_NONE, EXPORT_MESSAGE_INFO, @@ -92,6 +100,7 @@ private: struct ZipData { void *zip = nullptr; EditorProgress *ep = nullptr; + Vector<SharedObject> *so_files = nullptr; }; Vector<ExportMessage> messages; @@ -101,13 +110,22 @@ private: void _export_find_dependencies(const String &p_path, HashSet<String> &p_paths); static Error _save_pack_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key); + static Error _pack_add_shared_object(void *p_userdata, const SharedObject &p_so); + static Error _save_zip_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key); + static Error _zip_add_shared_object(void *p_userdata, const SharedObject &p_so); + + struct ScriptCallbackData { + Callable file_cb; + Callable so_cb; + }; + + static Error _script_save_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key); + static Error _script_add_shared_object(void *p_userdata, const SharedObject &p_so); void _edit_files_with_filter(Ref<DirAccess> &da, const Vector<String> &p_filters, HashSet<String> &r_list, bool exclude); void _edit_filter_list(HashSet<String> &r_list, const String &p_filter, bool exclude); - static Error _add_shared_object(void *p_userdata, const SharedObject &p_so); - struct FileExportCache { uint64_t source_modified_time = 0; String source_md5; @@ -126,19 +144,46 @@ private: protected: struct ExportNotifier { - ExportNotifier(EditorExportPlatform &p_platform, const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags); + ExportNotifier(EditorExportPlatform &p_platform, const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags); ~ExportNotifier(); }; HashSet<String> get_features(const Ref<EditorExportPreset> &p_preset, bool p_debug) const; - bool exists_export_template(const String &template_file_name, String *err) const; - String find_export_template(const String &template_file_name, String *err = nullptr) const; - void gen_export_flags(Vector<String> &r_flags, int p_flags); - void gen_debug_flags(Vector<String> &r_flags, int p_flags); + Dictionary _find_export_template(const String &p_template_file_name) const { + Dictionary ret; + String err; + + String path = find_export_template(p_template_file_name, &err); + ret["result"] = (err.is_empty() && !path.is_empty()) ? OK : FAILED; + ret["path"] = path; + ret["error_string"] = err; + + return ret; + } + + bool exists_export_template(const String &p_template_file_name, String *r_err) const; + String find_export_template(const String &p_template_file_name, String *r_err = nullptr) const; + Vector<String> gen_export_flags(BitField<EditorExportPlatform::DebugFlags> p_flags); virtual void zip_folder_recursive(zipFile &p_zip, const String &p_root_path, const String &p_folder, const String &p_pkg_name); + Error _ssh_run_on_remote(const String &p_host, const String &p_port, const Vector<String> &p_ssh_args, const String &p_cmd_args, Array r_output = Array(), int p_port_fwd = -1) const { + String pipe; + Error err = ssh_run_on_remote(p_host, p_port, p_ssh_args, p_cmd_args, &pipe, p_port_fwd); + r_output.push_back(pipe); + return err; + } + OS::ProcessID _ssh_run_on_remote_no_wait(const String &p_host, const String &p_port, const Vector<String> &p_ssh_args, const String &p_cmd_args, int p_port_fwd = -1) const { + OS::ProcessID pid = 0; + Error err = ssh_run_on_remote_no_wait(p_host, p_port, p_ssh_args, p_cmd_args, &pid, p_port_fwd); + if (err != OK) { + return -1; + } else { + return pid; + } + } + Error ssh_run_on_remote(const String &p_host, const String &p_port, const Vector<String> &p_ssh_args, const String &p_cmd_args, String *r_out = nullptr, int p_port_fwd = -1) const; Error ssh_run_on_remote_no_wait(const String &p_host, const String &p_port, const Vector<String> &p_ssh_args, const String &p_cmd_args, OS::ProcessID *r_pid = nullptr, int p_port_fwd = -1) const; Error ssh_push_to_remote(const String &p_host, const String &p_port, const Vector<String> &p_scp_args, const String &p_src_file, const String &p_dst_file) const; @@ -195,6 +240,21 @@ public: return messages[p_index]; } + virtual ExportMessageType _get_message_type(int p_index) const { + ERR_FAIL_INDEX_V(p_index, messages.size(), EXPORT_MESSAGE_NONE); + return messages[p_index].msg_type; + } + + virtual String _get_message_category(int p_index) const { + ERR_FAIL_INDEX_V(p_index, messages.size(), String()); + return messages[p_index].category; + } + + virtual String _get_message_text(int p_index) const { + ERR_FAIL_INDEX_V(p_index, messages.size(), String()); + return messages[p_index].text; + } + virtual ExportMessageType get_worst_message_type() const { ExportMessageType worst_type = EXPORT_MESSAGE_NONE; for (int i = 0; i < messages.size(); i++) { @@ -216,10 +276,16 @@ public: virtual String get_name() const = 0; virtual Ref<Texture2D> get_logo() const = 0; + Array get_current_presets() const; + + Error _export_project_files(const Ref<EditorExportPreset> &p_preset, bool p_debug, const Callable &p_save_func, const Callable &p_so_func); Error export_project_files(const Ref<EditorExportPreset> &p_preset, bool p_debug, EditorExportSaveFunction p_func, void *p_udata, EditorExportSaveSharedObject p_so_func = nullptr); + Dictionary _save_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, bool p_embed = false); + Dictionary _save_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path); + Error save_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, Vector<SharedObject> *p_so_files = nullptr, bool p_embed = false, int64_t *r_embedded_start = nullptr, int64_t *r_embedded_size = nullptr); - Error save_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path); + Error save_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, Vector<SharedObject> *p_so_files = nullptr); virtual bool poll_export() { return false; } virtual int get_options_count() const { return 0; } @@ -229,31 +295,26 @@ public: virtual String get_option_tooltip(int p_device) const { return ""; } virtual String get_device_architecture(int p_device) const { return ""; } - enum DebugFlags { - DEBUG_FLAG_DUMB_CLIENT = 1, - DEBUG_FLAG_REMOTE_DEBUG = 2, - DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST = 4, - DEBUG_FLAG_VIEW_COLLISIONS = 8, - DEBUG_FLAG_VIEW_NAVIGATION = 16, - }; - virtual void cleanup() {} - virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) { return OK; } + virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) { return OK; } virtual Ref<Texture2D> get_run_icon() const { return get_logo(); } - bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const; + virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const; virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const = 0; virtual bool has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const = 0; virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const = 0; - virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) = 0; - virtual Error export_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0); - virtual Error export_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0); + virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) = 0; + virtual Error export_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0); + virtual Error export_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0); virtual void get_platform_features(List<String> *r_features) const = 0; - virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, HashSet<String> &p_features) = 0; + virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, HashSet<String> &p_features){}; virtual String get_debug_protocol() const { return "tcp://"; } EditorExportPlatform(); }; +VARIANT_ENUM_CAST(EditorExportPlatform::ExportMessageType) +VARIANT_BITFIELD_CAST(EditorExportPlatform::DebugFlags); + #endif // EDITOR_EXPORT_PLATFORM_H diff --git a/editor/export/editor_export_platform_extension.cpp b/editor/export/editor_export_platform_extension.cpp new file mode 100644 index 0000000000..808a2076e2 --- /dev/null +++ b/editor/export/editor_export_platform_extension.cpp @@ -0,0 +1,317 @@ +/**************************************************************************/ +/* editor_export_platform_extension.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "editor_export_platform_extension.h" + +void EditorExportPlatformExtension::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_config_error", "error_text"), &EditorExportPlatformExtension::set_config_error); + ClassDB::bind_method(D_METHOD("get_config_error"), &EditorExportPlatformExtension::get_config_error); + + ClassDB::bind_method(D_METHOD("set_config_missing_templates", "missing_templates"), &EditorExportPlatformExtension::set_config_missing_templates); + ClassDB::bind_method(D_METHOD("get_config_missing_templates"), &EditorExportPlatformExtension::get_config_missing_templates); + + GDVIRTUAL_BIND(_get_preset_features, "preset"); + GDVIRTUAL_BIND(_is_executable, "path"); + GDVIRTUAL_BIND(_get_export_options); + GDVIRTUAL_BIND(_should_update_export_options); + GDVIRTUAL_BIND(_get_export_option_visibility, "preset", "option"); + GDVIRTUAL_BIND(_get_export_option_warning, "preset", "option"); + + GDVIRTUAL_BIND(_get_os_name); + GDVIRTUAL_BIND(_get_name); + GDVIRTUAL_BIND(_get_logo); + + GDVIRTUAL_BIND(_poll_export); + GDVIRTUAL_BIND(_get_options_count); + GDVIRTUAL_BIND(_get_options_tooltip); + + GDVIRTUAL_BIND(_get_option_icon, "device"); + GDVIRTUAL_BIND(_get_option_label, "device"); + GDVIRTUAL_BIND(_get_option_tooltip, "device"); + GDVIRTUAL_BIND(_get_device_architecture, "device"); + + GDVIRTUAL_BIND(_cleanup); + + GDVIRTUAL_BIND(_run, "preset", "device", "debug_flags"); + GDVIRTUAL_BIND(_get_run_icon); + + GDVIRTUAL_BIND(_can_export, "preset", "debug"); + GDVIRTUAL_BIND(_has_valid_export_configuration, "preset", "debug"); + GDVIRTUAL_BIND(_has_valid_project_configuration, "preset"); + + GDVIRTUAL_BIND(_get_binary_extensions, "preset"); + + GDVIRTUAL_BIND(_export_project, "preset", "debug", "path", "flags"); + GDVIRTUAL_BIND(_export_pack, "preset", "debug", "path", "flags"); + GDVIRTUAL_BIND(_export_zip, "preset", "debug", "path", "flags"); + + GDVIRTUAL_BIND(_get_platform_features); + + GDVIRTUAL_BIND(_get_debug_protocol); +} + +void EditorExportPlatformExtension::get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) const { + Vector<String> ret; + if (GDVIRTUAL_REQUIRED_CALL(_get_preset_features, p_preset, ret) && r_features) { + for (const String &E : ret) { + r_features->push_back(E); + } + } +} + +bool EditorExportPlatformExtension::is_executable(const String &p_path) const { + bool ret = false; + GDVIRTUAL_CALL(_is_executable, p_path, ret); + return ret; +} + +void EditorExportPlatformExtension::get_export_options(List<ExportOption> *r_options) const { + TypedArray<Dictionary> ret; + if (GDVIRTUAL_CALL(_get_export_options, ret) && r_options) { + for (const Variant &var : ret) { + const Dictionary &d = var; + ERR_CONTINUE(!d.has("name")); + ERR_CONTINUE(!d.has("type")); + + PropertyInfo pinfo = PropertyInfo::from_dict(d); + ERR_CONTINUE(pinfo.name.is_empty() && (pinfo.usage & PROPERTY_USAGE_STORAGE)); + ERR_CONTINUE(pinfo.type < 0 || pinfo.type >= Variant::VARIANT_MAX); + + Variant default_value; + if (d.has("default_value")) { + default_value = d["default_value"]; + } + bool update_visibility = false; + if (d.has("update_visibility")) { + update_visibility = d["update_visibility"]; + } + bool required = false; + if (d.has("required")) { + required = d["required"]; + } + + r_options->push_back(ExportOption(pinfo, default_value, update_visibility, required)); + } + } +} + +bool EditorExportPlatformExtension::should_update_export_options() { + bool ret = false; + GDVIRTUAL_CALL(_should_update_export_options, ret); + return ret; +} + +bool EditorExportPlatformExtension::get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option) const { + bool ret = true; + GDVIRTUAL_CALL(_get_export_option_visibility, Ref<EditorExportPreset>(p_preset), p_option, ret); + return ret; +} + +String EditorExportPlatformExtension::get_export_option_warning(const EditorExportPreset *p_preset, const StringName &p_name) const { + String ret; + GDVIRTUAL_CALL(_get_export_option_warning, Ref<EditorExportPreset>(p_preset), p_name, ret); + return ret; +} + +String EditorExportPlatformExtension::get_os_name() const { + String ret; + GDVIRTUAL_REQUIRED_CALL(_get_os_name, ret); + return ret; +} + +String EditorExportPlatformExtension::get_name() const { + String ret; + GDVIRTUAL_REQUIRED_CALL(_get_name, ret); + return ret; +} + +Ref<Texture2D> EditorExportPlatformExtension::get_logo() const { + Ref<Texture2D> ret; + GDVIRTUAL_REQUIRED_CALL(_get_logo, ret); + return ret; +} + +bool EditorExportPlatformExtension::poll_export() { + bool ret = false; + GDVIRTUAL_CALL(_poll_export, ret); + return ret; +} + +int EditorExportPlatformExtension::get_options_count() const { + int ret = 0; + GDVIRTUAL_CALL(_get_options_count, ret); + return ret; +} + +String EditorExportPlatformExtension::get_options_tooltip() const { + String ret; + GDVIRTUAL_CALL(_get_options_tooltip, ret); + return ret; +} + +Ref<ImageTexture> EditorExportPlatformExtension::get_option_icon(int p_index) const { + Ref<ImageTexture> ret; + if (GDVIRTUAL_CALL(_get_option_icon, p_index, ret)) { + return ret; + } + return EditorExportPlatform::get_option_icon(p_index); +} + +String EditorExportPlatformExtension::get_option_label(int p_device) const { + String ret; + GDVIRTUAL_CALL(_get_option_label, p_device, ret); + return ret; +} + +String EditorExportPlatformExtension::get_option_tooltip(int p_device) const { + String ret; + GDVIRTUAL_CALL(_get_option_tooltip, p_device, ret); + return ret; +} + +String EditorExportPlatformExtension::get_device_architecture(int p_device) const { + String ret; + GDVIRTUAL_CALL(_get_device_architecture, p_device, ret); + return ret; +} + +void EditorExportPlatformExtension::cleanup() { + GDVIRTUAL_CALL(_cleanup); +} + +Error EditorExportPlatformExtension::run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) { + Error ret = OK; + GDVIRTUAL_CALL(_run, p_preset, p_device, p_debug_flags, ret); + return ret; +} + +Ref<Texture2D> EditorExportPlatformExtension::get_run_icon() const { + Ref<Texture2D> ret; + if (GDVIRTUAL_CALL(_get_run_icon, ret)) { + return ret; + } + return EditorExportPlatform::get_run_icon(); +} + +bool EditorExportPlatformExtension::can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug) const { + bool ret = false; + config_error = r_error; + config_missing_templates = r_missing_templates; + if (GDVIRTUAL_CALL(_can_export, p_preset, p_debug, ret)) { + r_error = config_error; + r_missing_templates = config_missing_templates; + return ret; + } + return EditorExportPlatform::can_export(p_preset, r_error, r_missing_templates, p_debug); +} + +bool EditorExportPlatformExtension::has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug) const { + bool ret = false; + config_error = r_error; + config_missing_templates = r_missing_templates; + if (GDVIRTUAL_REQUIRED_CALL(_has_valid_export_configuration, p_preset, p_debug, ret)) { + r_error = config_error; + r_missing_templates = config_missing_templates; + } + return ret; +} + +bool EditorExportPlatformExtension::has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const { + bool ret = false; + config_error = r_error; + if (GDVIRTUAL_REQUIRED_CALL(_has_valid_project_configuration, p_preset, ret)) { + r_error = config_error; + } + return ret; +} + +List<String> EditorExportPlatformExtension::get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const { + List<String> ret_list; + Vector<String> ret; + if (GDVIRTUAL_REQUIRED_CALL(_get_binary_extensions, p_preset, ret)) { + for (const String &E : ret) { + ret_list.push_back(E); + } + } + return ret_list; +} + +Error EditorExportPlatformExtension::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) { + ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags); + + Error ret = FAILED; + GDVIRTUAL_REQUIRED_CALL(_export_project, p_preset, p_debug, p_path, p_flags, ret); + return ret; +} + +Error EditorExportPlatformExtension::export_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) { + ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags); + + Error ret = FAILED; + if (GDVIRTUAL_CALL(_export_pack, p_preset, p_debug, p_path, p_flags, ret)) { + return ret; + } + return save_pack(p_preset, p_debug, p_path); +} + +Error EditorExportPlatformExtension::export_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) { + ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags); + + Error ret = FAILED; + if (GDVIRTUAL_CALL(_export_zip, p_preset, p_debug, p_path, p_flags, ret)) { + return ret; + } + return save_zip(p_preset, p_debug, p_path); +} + +void EditorExportPlatformExtension::get_platform_features(List<String> *r_features) const { + Vector<String> ret; + if (GDVIRTUAL_REQUIRED_CALL(_get_platform_features, ret) && r_features) { + for (const String &E : ret) { + r_features->push_back(E); + } + } +} + +String EditorExportPlatformExtension::get_debug_protocol() const { + String ret; + if (GDVIRTUAL_CALL(_get_debug_protocol, ret)) { + return ret; + } + return EditorExportPlatform::get_debug_protocol(); +} + +EditorExportPlatformExtension::EditorExportPlatformExtension() { + //NOP +} + +EditorExportPlatformExtension::~EditorExportPlatformExtension() { + //NOP +} diff --git a/editor/export/editor_export_platform_extension.h b/editor/export/editor_export_platform_extension.h new file mode 100644 index 0000000000..6391e65ac1 --- /dev/null +++ b/editor/export/editor_export_platform_extension.h @@ -0,0 +1,149 @@ +/**************************************************************************/ +/* editor_export_platform_extension.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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 EDITOR_EXPORT_PLATFORM_EXTENSION_H +#define EDITOR_EXPORT_PLATFORM_EXTENSION_H + +#include "editor_export_platform.h" +#include "editor_export_preset.h" + +class EditorExportPlatformExtension : public EditorExportPlatform { + GDCLASS(EditorExportPlatformExtension, EditorExportPlatform); + + mutable String config_error; + mutable bool config_missing_templates = false; + +protected: + static void _bind_methods(); + +public: + virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) const override; + GDVIRTUAL1RC(Vector<String>, _get_preset_features, Ref<EditorExportPreset>); + + virtual bool is_executable(const String &p_path) const override; + GDVIRTUAL1RC(bool, _is_executable, const String &); + + virtual void get_export_options(List<ExportOption> *r_options) const override; + GDVIRTUAL0RC(TypedArray<Dictionary>, _get_export_options); + + virtual bool should_update_export_options() override; + GDVIRTUAL0R(bool, _should_update_export_options); + + virtual bool get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option) const override; + GDVIRTUAL2RC(bool, _get_export_option_visibility, Ref<EditorExportPreset>, const String &); + + virtual String get_export_option_warning(const EditorExportPreset *p_preset, const StringName &p_name) const override; + GDVIRTUAL2RC(String, _get_export_option_warning, Ref<EditorExportPreset>, const StringName &); + + virtual String get_os_name() const override; + GDVIRTUAL0RC(String, _get_os_name); + + virtual String get_name() const override; + GDVIRTUAL0RC(String, _get_name); + + virtual Ref<Texture2D> get_logo() const override; + GDVIRTUAL0RC(Ref<Texture2D>, _get_logo); + + virtual bool poll_export() override; + GDVIRTUAL0R(bool, _poll_export); + + virtual int get_options_count() const override; + GDVIRTUAL0RC(int, _get_options_count); + + virtual String get_options_tooltip() const override; + GDVIRTUAL0RC(String, _get_options_tooltip); + + virtual Ref<ImageTexture> get_option_icon(int p_index) const override; + GDVIRTUAL1RC(Ref<ImageTexture>, _get_option_icon, int); + + virtual String get_option_label(int p_device) const override; + GDVIRTUAL1RC(String, _get_option_label, int); + + virtual String get_option_tooltip(int p_device) const override; + GDVIRTUAL1RC(String, _get_option_tooltip, int); + + virtual String get_device_architecture(int p_device) const override; + GDVIRTUAL1RC(String, _get_device_architecture, int); + + virtual void cleanup() override; + GDVIRTUAL0(_cleanup); + + virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) override; + GDVIRTUAL3R(Error, _run, Ref<EditorExportPreset>, int, BitField<EditorExportPlatform::DebugFlags>); + + virtual Ref<Texture2D> get_run_icon() const override; + GDVIRTUAL0RC(Ref<Texture2D>, _get_run_icon); + + void set_config_error(const String &p_error) const { + config_error = p_error; + } + String get_config_error() const { + return config_error; + } + + void set_config_missing_templates(bool p_missing_templates) const { + config_missing_templates = p_missing_templates; + } + bool get_config_missing_templates() const { + return config_missing_templates; + } + + virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const override; + GDVIRTUAL2RC(bool, _can_export, Ref<EditorExportPreset>, bool); + + virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const override; + GDVIRTUAL2RC(bool, _has_valid_export_configuration, Ref<EditorExportPreset>, bool); + + virtual bool has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const override; + GDVIRTUAL1RC(bool, _has_valid_project_configuration, Ref<EditorExportPreset>); + + virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override; + GDVIRTUAL1RC(Vector<String>, _get_binary_extensions, Ref<EditorExportPreset>); + + virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override; + GDVIRTUAL4R(Error, _export_project, Ref<EditorExportPreset>, bool, const String &, BitField<EditorExportPlatform::DebugFlags>); + + virtual Error export_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override; + GDVIRTUAL4R(Error, _export_pack, Ref<EditorExportPreset>, bool, const String &, BitField<EditorExportPlatform::DebugFlags>); + + virtual Error export_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override; + GDVIRTUAL4R(Error, _export_zip, Ref<EditorExportPreset>, bool, const String &, BitField<EditorExportPlatform::DebugFlags>); + + virtual void get_platform_features(List<String> *r_features) const override; + GDVIRTUAL0RC(Vector<String>, _get_platform_features); + + virtual String get_debug_protocol() const override; + GDVIRTUAL0RC(String, _get_debug_protocol); + + EditorExportPlatformExtension(); + ~EditorExportPlatformExtension(); +}; + +#endif // EDITOR_EXPORT_PLATFORM_EXTENSION_H diff --git a/editor/export/editor_export_platform_pc.cpp b/editor/export/editor_export_platform_pc.cpp index cdaf18b346..24d89b7f34 100644 --- a/editor/export/editor_export_platform_pc.cpp +++ b/editor/export/editor_export_platform_pc.cpp @@ -115,7 +115,7 @@ bool EditorExportPlatformPC::has_valid_project_configuration(const Ref<EditorExp return true; } -Error EditorExportPlatformPC::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) { +Error EditorExportPlatformPC::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) { ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags); Error err = prepare_template(p_preset, p_debug, p_path, p_flags); @@ -129,7 +129,7 @@ Error EditorExportPlatformPC::export_project(const Ref<EditorExportPreset> &p_pr return err; } -Error EditorExportPlatformPC::prepare_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) { +Error EditorExportPlatformPC::prepare_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) { if (!DirAccess::exists(p_path.get_base_dir())) { add_message(EXPORT_MESSAGE_ERROR, TTR("Prepare Template"), TTR("The given export path doesn't exist.")); return ERR_FILE_BAD_PATH; @@ -182,7 +182,7 @@ Error EditorExportPlatformPC::prepare_template(const Ref<EditorExportPreset> &p_ return err; } -Error EditorExportPlatformPC::export_project_data(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) { +Error EditorExportPlatformPC::export_project_data(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) { String pck_path; if (p_preset->get("binary_format/embed_pck")) { pck_path = p_path; diff --git a/editor/export/editor_export_platform_pc.h b/editor/export/editor_export_platform_pc.h index 53574c2333..668ddaf47e 100644 --- a/editor/export/editor_export_platform_pc.h +++ b/editor/export/editor_export_platform_pc.h @@ -54,13 +54,13 @@ public: virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const override; virtual bool has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const override; - virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override; + virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override; virtual Error sign_shared_object(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path); virtual String get_template_file_name(const String &p_target, const String &p_arch) const = 0; - virtual Error prepare_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags); - virtual Error modify_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) { return OK; }; - virtual Error export_project_data(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags); + virtual Error prepare_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags); + virtual Error modify_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) { return OK; } + virtual Error export_project_data(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags); void set_name(const String &p_name); void set_os_name(const String &p_name); diff --git a/editor/export/editor_export_plugin.cpp b/editor/export/editor_export_plugin.cpp index 28d0750d5a..f56dd61e3b 100644 --- a/editor/export/editor_export_plugin.cpp +++ b/editor/export/editor_export_plugin.cpp @@ -48,6 +48,14 @@ Ref<EditorExportPreset> EditorExportPlugin::get_export_preset() const { return export_preset; } +Ref<EditorExportPlatform> EditorExportPlugin::get_export_platform() const { + if (export_preset.is_valid()) { + return export_preset->get_platform(); + } else { + return Ref<EditorExportPlatform>(); + } +} + void EditorExportPlugin::add_file(const String &p_path, const Vector<uint8_t> &p_file, bool p_remap) { ExtraFile ef; ef.data = p_file; @@ -321,6 +329,9 @@ void EditorExportPlugin::_bind_methods() { ClassDB::bind_method(D_METHOD("skip"), &EditorExportPlugin::skip); ClassDB::bind_method(D_METHOD("get_option", "name"), &EditorExportPlugin::get_option); + ClassDB::bind_method(D_METHOD("get_export_preset"), &EditorExportPlugin::get_export_preset); + ClassDB::bind_method(D_METHOD("get_export_platform"), &EditorExportPlugin::get_export_platform); + GDVIRTUAL_BIND(_export_file, "path", "type", "features"); GDVIRTUAL_BIND(_export_begin, "features", "is_debug", "path", "flags"); GDVIRTUAL_BIND(_export_end); diff --git a/editor/export/editor_export_plugin.h b/editor/export/editor_export_plugin.h index 56eea85010..7a355614c7 100644 --- a/editor/export/editor_export_plugin.h +++ b/editor/export/editor_export_plugin.h @@ -91,6 +91,7 @@ class EditorExportPlugin : public RefCounted { protected: void set_export_preset(const Ref<EditorExportPreset> &p_preset); Ref<EditorExportPreset> get_export_preset() const; + Ref<EditorExportPlatform> get_export_platform() const; void add_file(const String &p_path, const Vector<uint8_t> &p_file, bool p_remap); void add_shared_object(const String &p_path, const Vector<String> &tags, const String &p_target = String()); diff --git a/editor/export/editor_export_preset.cpp b/editor/export/editor_export_preset.cpp index e2e3e9d154..9f805666d0 100644 --- a/editor/export/editor_export_preset.cpp +++ b/editor/export/editor_export_preset.cpp @@ -62,6 +62,48 @@ bool EditorExportPreset::_get(const StringName &p_name, Variant &r_ret) const { void EditorExportPreset::_bind_methods() { ClassDB::bind_method(D_METHOD("_get_property_warning", "name"), &EditorExportPreset::_get_property_warning); + + ClassDB::bind_method(D_METHOD("has", "property"), &EditorExportPreset::has); + + ClassDB::bind_method(D_METHOD("get_files_to_export"), &EditorExportPreset::get_files_to_export); + ClassDB::bind_method(D_METHOD("get_customized_files"), &EditorExportPreset::get_customized_files); + ClassDB::bind_method(D_METHOD("get_customized_files_count"), &EditorExportPreset::get_customized_files_count); + ClassDB::bind_method(D_METHOD("has_export_file", "path"), &EditorExportPreset::has_export_file); + ClassDB::bind_method(D_METHOD("get_file_export_mode", "path", "default"), &EditorExportPreset::get_file_export_mode, DEFVAL(MODE_FILE_NOT_CUSTOMIZED)); + + ClassDB::bind_method(D_METHOD("get_preset_name"), &EditorExportPreset::get_name); + ClassDB::bind_method(D_METHOD("is_runnable"), &EditorExportPreset::is_runnable); + ClassDB::bind_method(D_METHOD("are_advanced_options_enabled"), &EditorExportPreset::are_advanced_options_enabled); + ClassDB::bind_method(D_METHOD("is_dedicated_server"), &EditorExportPreset::is_dedicated_server); + ClassDB::bind_method(D_METHOD("get_export_filter"), &EditorExportPreset::get_export_filter); + ClassDB::bind_method(D_METHOD("get_include_filter"), &EditorExportPreset::get_include_filter); + ClassDB::bind_method(D_METHOD("get_exclude_filter"), &EditorExportPreset::get_exclude_filter); + ClassDB::bind_method(D_METHOD("get_custom_features"), &EditorExportPreset::get_custom_features); + ClassDB::bind_method(D_METHOD("get_export_path"), &EditorExportPreset::get_export_path); + ClassDB::bind_method(D_METHOD("get_encryption_in_filter"), &EditorExportPreset::get_enc_in_filter); + ClassDB::bind_method(D_METHOD("get_encryption_ex_filter"), &EditorExportPreset::get_enc_ex_filter); + ClassDB::bind_method(D_METHOD("get_encrypt_pck"), &EditorExportPreset::get_enc_pck); + ClassDB::bind_method(D_METHOD("get_encrypt_directory"), &EditorExportPreset::get_enc_directory); + ClassDB::bind_method(D_METHOD("get_encryption_key"), &EditorExportPreset::get_script_encryption_key); + ClassDB::bind_method(D_METHOD("get_script_export_mode"), &EditorExportPreset::get_script_export_mode); + + ClassDB::bind_method(D_METHOD("get_or_env", "name", "env_var"), &EditorExportPreset::_get_or_env); + ClassDB::bind_method(D_METHOD("get_version", "name", "windows_version"), &EditorExportPreset::get_version); + + BIND_ENUM_CONSTANT(EXPORT_ALL_RESOURCES); + BIND_ENUM_CONSTANT(EXPORT_SELECTED_SCENES); + BIND_ENUM_CONSTANT(EXPORT_SELECTED_RESOURCES); + BIND_ENUM_CONSTANT(EXCLUDE_SELECTED_RESOURCES); + BIND_ENUM_CONSTANT(EXPORT_CUSTOMIZED); + + BIND_ENUM_CONSTANT(MODE_FILE_NOT_CUSTOMIZED); + BIND_ENUM_CONSTANT(MODE_FILE_STRIP); + BIND_ENUM_CONSTANT(MODE_FILE_KEEP); + BIND_ENUM_CONSTANT(MODE_FILE_REMOVE); + + BIND_ENUM_CONSTANT(MODE_SCRIPT_TEXT); + BIND_ENUM_CONSTANT(MODE_SCRIPT_BINARY_TOKENS); + BIND_ENUM_CONSTANT(MODE_SCRIPT_BINARY_TOKENS_COMPRESSED); } String EditorExportPreset::_get_property_warning(const StringName &p_name) const { diff --git a/editor/export/editor_export_preset.h b/editor/export/editor_export_preset.h index c6a8808af1..f220477461 100644 --- a/editor/export/editor_export_preset.h +++ b/editor/export/editor_export_preset.h @@ -168,6 +168,9 @@ public: void set_script_export_mode(int p_mode); int get_script_export_mode() const; + Variant _get_or_env(const StringName &p_name, const String &p_env_var) const { + return get_or_env(p_name, p_env_var); + } Variant get_or_env(const StringName &p_name, const String &p_env_var, bool *r_valid = nullptr) const; // Return the preset's version number, or fall back to the @@ -183,4 +186,8 @@ public: EditorExportPreset(); }; +VARIANT_ENUM_CAST(EditorExportPreset::ExportFilter); +VARIANT_ENUM_CAST(EditorExportPreset::FileExportMode); +VARIANT_ENUM_CAST(EditorExportPreset::ScriptExportMode); + #endif // EDITOR_EXPORT_PRESET_H diff --git a/editor/import/3d/resource_importer_scene.cpp b/editor/import/3d/resource_importer_scene.cpp index 853e3e813b..fa07511dd0 100644 --- a/editor/import/3d/resource_importer_scene.cpp +++ b/editor/import/3d/resource_importer_scene.cpp @@ -649,6 +649,9 @@ Node *ResourceImporterScene::_pre_fix_node(Node *p_node, Node *p_root, HashMap<R String name = p_node->get_name(); NodePath original_path = p_root->get_path_to(p_node); // Used to detect renames due to import hints. + Ref<Resource> original_meta = memnew(Resource); // Create temp resource to hold original meta + original_meta->merge_meta_from(p_node); + bool isroot = p_node == p_root; if (!isroot && _teststr(name, "noimp")) { @@ -1022,6 +1025,8 @@ Node *ResourceImporterScene::_pre_fix_node(Node *p_node, Node *p_root, HashMap<R print_verbose(vformat("Fix: Renamed %s to %s", original_path, new_path)); r_node_renames.push_back({ original_path, p_node }); } + // If we created new node instead, merge meta values from the original node. + p_node->merge_meta_from(*original_meta); } return p_node; @@ -2452,6 +2457,8 @@ Node *ResourceImporterScene::_generate_meshes(Node *p_node, const Dictionary &p_ mesh_node->set_transform(src_mesh_node->get_transform()); mesh_node->set_skin(src_mesh_node->get_skin()); mesh_node->set_skeleton_path(src_mesh_node->get_skeleton_path()); + mesh_node->merge_meta_from(src_mesh_node); + if (src_mesh_node->get_mesh().is_valid()) { Ref<ArrayMesh> mesh; if (!src_mesh_node->get_mesh()->has_mesh()) { @@ -2599,6 +2606,7 @@ Node *ResourceImporterScene::_generate_meshes(Node *p_node, const Dictionary &p_ for (int i = 0; i < mesh->get_surface_count(); i++) { mesh_node->set_surface_override_material(i, src_mesh_node->get_surface_material(i)); } + mesh->merge_meta_from(*src_mesh_node->get_mesh()); } } @@ -3096,6 +3104,19 @@ Error ResourceImporterScene::import(const String &p_source_file, const String &p } } + // Apply RESET animation before serializing. + if (_scene_import_type == "PackedScene") { + int scene_child_count = scene->get_child_count(); + for (int i = 0; i < scene_child_count; i++) { + AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(scene->get_child(i)); + if (ap) { + if (ap->can_apply_reset()) { + ap->apply_reset(); + } + } + } + } + if (post_import_script.is_valid()) { post_import_script->init(p_source_file); scene = post_import_script->post_import(scene); @@ -3114,7 +3135,7 @@ Error ResourceImporterScene::import(const String &p_source_file, const String &p progress.step(TTR("Saving..."), 104); int flags = 0; - if (EDITOR_GET("filesystem/on_save/compress_binary_resources")) { + if (EditorSettings::get_singleton() && EDITOR_GET("filesystem/on_save/compress_binary_resources")) { flags |= ResourceSaver::FLAG_COMPRESS; } diff --git a/editor/inspector_dock.cpp b/editor/inspector_dock.cpp index aa075c80c3..dc07403213 100644 --- a/editor/inspector_dock.cpp +++ b/editor/inspector_dock.cpp @@ -190,7 +190,7 @@ void InspectorDock::_menu_option_confirm(int p_option, bool p_confirmed) { } int history_id = EditorUndoRedoManager::get_singleton()->get_history_id_for_object(current); - EditorUndoRedoManager::get_singleton()->clear_history(true, history_id); + EditorUndoRedoManager::get_singleton()->clear_history(history_id); EditorNode::get_singleton()->edit_item(current, inspector); } diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp index 660e4647a1..bed9983461 100644 --- a/editor/plugins/animation_player_editor_plugin.cpp +++ b/editor/plugins/animation_player_editor_plugin.cpp @@ -2165,7 +2165,7 @@ void AnimationPlayerEditorPlugin::_property_keyed(const String &p_keyed, const V return; } te->_clear_selection(); - te->insert_value_key(p_keyed, p_value, p_advance); + te->insert_value_key(p_keyed, p_advance); } void AnimationPlayerEditorPlugin::_transform_key_request(Object *sp, const String &p_sub, const Transform3D &p_key) { diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index ec42cb31be..6e41e98360 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -4331,13 +4331,13 @@ void CanvasItemEditor::_insert_animation_keys(bool p_location, bool p_rotation, Node2D *n2d = Object::cast_to<Node2D>(ci); if (key_pos && p_location) { - te->insert_node_value_key(n2d, "position", n2d->get_position(), p_on_existing); + te->insert_node_value_key(n2d, "position", p_on_existing); } if (key_rot && p_rotation) { - te->insert_node_value_key(n2d, "rotation", n2d->get_rotation(), p_on_existing); + te->insert_node_value_key(n2d, "rotation", p_on_existing); } if (key_scale && p_scale) { - te->insert_node_value_key(n2d, "scale", n2d->get_scale(), p_on_existing); + te->insert_node_value_key(n2d, "scale", p_on_existing); } if (n2d->has_meta("_edit_bone_") && n2d->get_parent_item()) { @@ -4363,13 +4363,13 @@ void CanvasItemEditor::_insert_animation_keys(bool p_location, bool p_rotation, if (has_chain && ik_chain.size()) { for (Node2D *&F : ik_chain) { if (key_pos) { - te->insert_node_value_key(F, "position", F->get_position(), p_on_existing); + te->insert_node_value_key(F, "position", p_on_existing); } if (key_rot) { - te->insert_node_value_key(F, "rotation", F->get_rotation(), p_on_existing); + te->insert_node_value_key(F, "rotation", p_on_existing); } if (key_scale) { - te->insert_node_value_key(F, "scale", F->get_scale(), p_on_existing); + te->insert_node_value_key(F, "scale", p_on_existing); } } } @@ -4379,13 +4379,13 @@ void CanvasItemEditor::_insert_animation_keys(bool p_location, bool p_rotation, Control *ctrl = Object::cast_to<Control>(ci); if (key_pos) { - te->insert_node_value_key(ctrl, "position", ctrl->get_position(), p_on_existing); + te->insert_node_value_key(ctrl, "position", p_on_existing); } if (key_rot) { - te->insert_node_value_key(ctrl, "rotation", ctrl->get_rotation(), p_on_existing); + te->insert_node_value_key(ctrl, "rotation", p_on_existing); } if (key_scale) { - te->insert_node_value_key(ctrl, "size", ctrl->get_size(), p_on_existing); + te->insert_node_value_key(ctrl, "size", p_on_existing); } } } diff --git a/editor/plugins/editor_plugin.cpp b/editor/plugins/editor_plugin.cpp index d9f60e155d..8ce667568f 100644 --- a/editor/plugins/editor_plugin.cpp +++ b/editor/plugins/editor_plugin.cpp @@ -40,6 +40,7 @@ #include "editor/editor_translation_parser.h" #include "editor/editor_undo_redo_manager.h" #include "editor/export/editor_export.h" +#include "editor/export/editor_export_platform.h" #include "editor/gui/editor_bottom_panel.h" #include "editor/gui/editor_title_bar.h" #include "editor/import/3d/resource_importer_scene.h" @@ -441,6 +442,16 @@ void EditorPlugin::remove_export_plugin(const Ref<EditorExportPlugin> &p_exporte EditorExport::get_singleton()->remove_export_plugin(p_exporter); } +void EditorPlugin::add_export_platform(const Ref<EditorExportPlatform> &p_platform) { + ERR_FAIL_COND(p_platform.is_null()); + EditorExport::get_singleton()->add_export_platform(p_platform); +} + +void EditorPlugin::remove_export_platform(const Ref<EditorExportPlatform> &p_platform) { + ERR_FAIL_COND(p_platform.is_null()); + EditorExport::get_singleton()->remove_export_platform(p_platform); +} + void EditorPlugin::add_node_3d_gizmo_plugin(const Ref<EditorNode3DGizmoPlugin> &p_gizmo_plugin) { ERR_FAIL_COND(!p_gizmo_plugin.is_valid()); Node3DEditor::get_singleton()->add_gizmo_plugin(p_gizmo_plugin); @@ -608,6 +619,8 @@ void EditorPlugin::_bind_methods() { ClassDB::bind_method(D_METHOD("remove_scene_post_import_plugin", "scene_import_plugin"), &EditorPlugin::remove_scene_post_import_plugin); ClassDB::bind_method(D_METHOD("add_export_plugin", "plugin"), &EditorPlugin::add_export_plugin); ClassDB::bind_method(D_METHOD("remove_export_plugin", "plugin"), &EditorPlugin::remove_export_plugin); + ClassDB::bind_method(D_METHOD("add_export_platform", "platform"), &EditorPlugin::add_export_platform); + ClassDB::bind_method(D_METHOD("remove_export_platform", "platform"), &EditorPlugin::remove_export_platform); ClassDB::bind_method(D_METHOD("add_node_3d_gizmo_plugin", "plugin"), &EditorPlugin::add_node_3d_gizmo_plugin); ClassDB::bind_method(D_METHOD("remove_node_3d_gizmo_plugin", "plugin"), &EditorPlugin::remove_node_3d_gizmo_plugin); ClassDB::bind_method(D_METHOD("add_inspector_plugin", "plugin"), &EditorPlugin::add_inspector_plugin); diff --git a/editor/plugins/editor_plugin.h b/editor/plugins/editor_plugin.h index f6c4b35407..2e0771f906 100644 --- a/editor/plugins/editor_plugin.h +++ b/editor/plugins/editor_plugin.h @@ -41,6 +41,7 @@ class PopupMenu; class EditorDebuggerPlugin; class EditorExport; class EditorExportPlugin; +class EditorExportPlatform; class EditorImportPlugin; class EditorInspectorPlugin; class EditorInterface; @@ -224,6 +225,9 @@ public: void add_export_plugin(const Ref<EditorExportPlugin> &p_exporter); void remove_export_plugin(const Ref<EditorExportPlugin> &p_exporter); + void add_export_platform(const Ref<EditorExportPlatform> &p_platform); + void remove_export_platform(const Ref<EditorExportPlatform> &p_platform); + void add_node_3d_gizmo_plugin(const Ref<EditorNode3DGizmoPlugin> &p_gizmo_plugin); void remove_node_3d_gizmo_plugin(const Ref<EditorNode3DGizmoPlugin> &p_gizmo_plugin); diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp index a33fc47955..d7de5a7223 100644 --- a/editor/plugins/script_editor_plugin.cpp +++ b/editor/plugins/script_editor_plugin.cpp @@ -578,8 +578,8 @@ void ScriptEditor::_clear_breakpoints() { script_editor_cache->get_sections(&cached_editors); for (const String &E : cached_editors) { Array breakpoints = _get_cached_breakpoints_for_script(E); - for (int i = 0; i < breakpoints.size(); i++) { - EditorDebuggerNode::get_singleton()->set_breakpoint(E, (int)breakpoints[i] + 1, false); + for (int breakpoint : breakpoints) { + EditorDebuggerNode::get_singleton()->set_breakpoint(E, (int)breakpoint + 1, false); } if (breakpoints.size() > 0) { @@ -1820,6 +1820,48 @@ void ScriptEditor::notify_script_changed(const Ref<Script> &p_script) { emit_signal(SNAME("editor_script_changed"), p_script); } +Vector<String> ScriptEditor::_get_breakpoints() { + Vector<String> ret; + HashSet<String> loaded_scripts; + for (int i = 0; i < tab_container->get_tab_count(); i++) { + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i)); + if (!se) { + continue; + } + + Ref<Script> scr = se->get_edited_resource(); + if (scr.is_null()) { + continue; + } + + String base = scr->get_path(); + loaded_scripts.insert(base); + if (base.is_empty() || base.begins_with("local://")) { + continue; + } + + PackedInt32Array bpoints = se->get_breakpoints(); + for (int32_t bpoint : bpoints) { + ret.push_back(base + ":" + itos((int)bpoint + 1)); + } + } + + // Load breakpoints that are in closed scripts. + List<String> cached_editors; + script_editor_cache->get_sections(&cached_editors); + for (const String &E : cached_editors) { + if (loaded_scripts.has(E)) { + continue; + } + + Array breakpoints = _get_cached_breakpoints_for_script(E); + for (int breakpoint : breakpoints) { + ret.push_back(E + ":" + itos((int)breakpoint + 1)); + } + } + return ret; +} + void ScriptEditor::get_breakpoints(List<String> *p_breakpoints) { HashSet<String> loaded_scripts; for (int i = 0; i < tab_container->get_tab_count(); i++) { @@ -1829,19 +1871,19 @@ void ScriptEditor::get_breakpoints(List<String> *p_breakpoints) { } Ref<Script> scr = se->get_edited_resource(); - if (scr == nullptr) { + if (scr.is_null()) { continue; } String base = scr->get_path(); loaded_scripts.insert(base); - if (base.begins_with("local://") || base.is_empty()) { + if (base.is_empty() || base.begins_with("local://")) { continue; } PackedInt32Array bpoints = se->get_breakpoints(); - for (int j = 0; j < bpoints.size(); j++) { - p_breakpoints->push_back(base + ":" + itos((int)bpoints[j] + 1)); + for (int32_t bpoint : bpoints) { + p_breakpoints->push_back(base + ":" + itos((int)bpoint + 1)); } } @@ -1854,8 +1896,8 @@ void ScriptEditor::get_breakpoints(List<String> *p_breakpoints) { } Array breakpoints = _get_cached_breakpoints_for_script(E); - for (int i = 0; i < breakpoints.size(); i++) { - p_breakpoints->push_back(E + ":" + itos((int)breakpoints[i] + 1)); + for (int breakpoint : breakpoints) { + p_breakpoints->push_back(E + ":" + itos((int)breakpoint + 1)); } } } @@ -2944,8 +2986,8 @@ void ScriptEditor::_files_moved(const String &p_old_file, const String &p_new_fi // If Script, update breakpoints with debugger. Array breakpoints = _get_cached_breakpoints_for_script(p_new_file); - for (int i = 0; i < breakpoints.size(); i++) { - int line = (int)breakpoints[i] + 1; + for (int breakpoint : breakpoints) { + int line = (int)breakpoint + 1; EditorDebuggerNode::get_singleton()->set_breakpoint(p_old_file, line, false); if (!p_new_file.begins_with("local://") && ResourceLoader::exists(p_new_file, "Script")) { EditorDebuggerNode::get_singleton()->set_breakpoint(p_new_file, line, true); @@ -2969,8 +3011,8 @@ void ScriptEditor::_file_removed(const String &p_removed_file) { // Check closed. if (script_editor_cache->has_section(p_removed_file)) { Array breakpoints = _get_cached_breakpoints_for_script(p_removed_file); - for (int i = 0; i < breakpoints.size(); i++) { - EditorDebuggerNode::get_singleton()->set_breakpoint(p_removed_file, (int)breakpoints[i] + 1, false); + for (int breakpoint : breakpoints) { + EditorDebuggerNode::get_singleton()->set_breakpoint(p_removed_file, (int)breakpoint + 1, false); } script_editor_cache->erase_section(p_removed_file); } @@ -3425,8 +3467,8 @@ void ScriptEditor::set_window_layout(Ref<ConfigFile> p_layout) { } Array breakpoints = _get_cached_breakpoints_for_script(E); - for (int i = 0; i < breakpoints.size(); i++) { - EditorDebuggerNode::get_singleton()->set_breakpoint(E, (int)breakpoints[i] + 1, true); + for (int breakpoint : breakpoints) { + EditorDebuggerNode::get_singleton()->set_breakpoint(E, (int)breakpoint + 1, true); } } @@ -3972,6 +4014,7 @@ void ScriptEditor::_bind_methods() { ClassDB::bind_method("_help_tab_goto", &ScriptEditor::_help_tab_goto); ClassDB::bind_method("get_current_editor", &ScriptEditor::_get_current_editor); ClassDB::bind_method("get_open_script_editors", &ScriptEditor::_get_open_script_editors); + ClassDB::bind_method("get_breakpoints", &ScriptEditor::_get_breakpoints); ClassDB::bind_method(D_METHOD("register_syntax_highlighter", "syntax_highlighter"), &ScriptEditor::register_syntax_highlighter); ClassDB::bind_method(D_METHOD("unregister_syntax_highlighter", "syntax_highlighter"), &ScriptEditor::unregister_syntax_highlighter); diff --git a/editor/plugins/script_editor_plugin.h b/editor/plugins/script_editor_plugin.h index 66885790a7..e0fac5d0c6 100644 --- a/editor/plugins/script_editor_plugin.h +++ b/editor/plugins/script_editor_plugin.h @@ -539,6 +539,7 @@ public: _FORCE_INLINE_ bool edit(const Ref<Resource> &p_resource, bool p_grab_focus = true) { return edit(p_resource, -1, 0, p_grab_focus); } bool edit(const Ref<Resource> &p_resource, int p_line, int p_col, bool p_grab_focus = true); + Vector<String> _get_breakpoints(); void get_breakpoints(List<String> *p_breakpoints); PackedStringArray get_unsaved_scripts() const; diff --git a/editor/plugins/theme_editor_plugin.cpp b/editor/plugins/theme_editor_plugin.cpp index 99635a2531..ea1756b65a 100644 --- a/editor/plugins/theme_editor_plugin.cpp +++ b/editor/plugins/theme_editor_plugin.cpp @@ -2174,8 +2174,26 @@ void ThemeTypeDialog::_add_type_filter_cbk(const String &p_value) { _update_add_type_options(p_value); } +void ThemeTypeDialog::_type_filter_input(const Ref<InputEvent> &p_ie) { + Ref<InputEventKey> k = p_ie; + if (k.is_valid() && k->is_pressed()) { + switch (k->get_keycode()) { + case Key::UP: + case Key::DOWN: + case Key::PAGEUP: + case Key::PAGEDOWN: { + add_type_options->gui_input(k); + add_type_filter->accept_event(); + } break; + default: + break; + } + } +} + void ThemeTypeDialog::_add_type_options_cbk(int p_index) { add_type_filter->set_text(add_type_options->get_item_text(p_index)); + add_type_filter->set_caret_column(add_type_filter->get_text().length()); } void ThemeTypeDialog::_add_type_dialog_entered(const String &p_value) { @@ -2245,6 +2263,7 @@ ThemeTypeDialog::ThemeTypeDialog() { add_type_vb->add_child(add_type_filter); add_type_filter->connect(SceneStringName(text_changed), callable_mp(this, &ThemeTypeDialog::_add_type_filter_cbk)); add_type_filter->connect("text_submitted", callable_mp(this, &ThemeTypeDialog::_add_type_dialog_entered)); + add_type_filter->connect(SceneStringName(gui_input), callable_mp(this, &ThemeTypeDialog::_type_filter_input)); Label *add_type_options_label = memnew(Label); add_type_options_label->set_text(TTR("Available Node-based types:")); diff --git a/editor/plugins/theme_editor_plugin.h b/editor/plugins/theme_editor_plugin.h index ba8e3a30b7..ae92365c32 100644 --- a/editor/plugins/theme_editor_plugin.h +++ b/editor/plugins/theme_editor_plugin.h @@ -303,6 +303,7 @@ class ThemeTypeDialog : public ConfirmationDialog { void _update_add_type_options(const String &p_filter = ""); void _add_type_filter_cbk(const String &p_value); + void _type_filter_input(const Ref<InputEvent> &p_ie); void _add_type_options_cbk(int p_index); void _add_type_dialog_entered(const String &p_value); void _add_type_dialog_activated(int p_index); diff --git a/editor/plugins/tiles/tile_map_layer_editor.cpp b/editor/plugins/tiles/tile_map_layer_editor.cpp index 7e7d7e3291..7e6746dd3c 100644 --- a/editor/plugins/tiles/tile_map_layer_editor.cpp +++ b/editor/plugins/tiles/tile_map_layer_editor.cpp @@ -86,8 +86,7 @@ void TileMapLayerEditorTilesPlugin::_update_toolbar() { transform_toolbar->show(); tools_settings_vsep_2->show(); random_tile_toggle->show(); - scatter_label->show(); - scatter_spinbox->show(); + scatter_controls_container->set_visible(random_tile_toggle->is_pressed()); } else { tools_settings_vsep->show(); picker_button->show(); @@ -96,8 +95,7 @@ void TileMapLayerEditorTilesPlugin::_update_toolbar() { tools_settings_vsep_2->show(); bucket_contiguous_checkbox->show(); random_tile_toggle->show(); - scatter_label->show(); - scatter_spinbox->show(); + scatter_controls_container->set_visible(random_tile_toggle->is_pressed()); } } diff --git a/editor/register_editor_types.cpp b/editor/register_editor_types.cpp index 610ad3efdf..8e135e7eae 100644 --- a/editor/register_editor_types.cpp +++ b/editor/register_editor_types.cpp @@ -46,6 +46,7 @@ #include "editor/editor_translation_parser.h" #include "editor/editor_undo_redo_manager.h" #include "editor/export/editor_export_platform.h" +#include "editor/export/editor_export_platform_extension.h" #include "editor/export/editor_export_platform_pc.h" #include "editor/export/editor_export_plugin.h" #include "editor/filesystem_dock.h" @@ -161,6 +162,8 @@ void register_editor_types() { GDREGISTER_CLASS(EditorExportPlugin); GDREGISTER_ABSTRACT_CLASS(EditorExportPlatform); GDREGISTER_ABSTRACT_CLASS(EditorExportPlatformPC); + GDREGISTER_CLASS(EditorExportPlatformExtension); + GDREGISTER_ABSTRACT_CLASS(EditorExportPreset); register_exporter_types(); diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index 02c8a03ac6..3110ecb926 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -2650,6 +2650,13 @@ void SceneTreeDock::_delete_confirm(bool p_cut) { } } + if (!entire_scene) { + for (const Node *E : remove_list) { + // `move_child` + `get_index` doesn't really work for internal nodes. + ERR_FAIL_COND_MSG(E->get_internal_mode() != INTERNAL_MODE_DISABLED, "Trying to remove internal node, this is not supported."); + } + } + EditorNode::get_singleton()->hide_unused_editors(this); EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); @@ -2662,10 +2669,6 @@ void SceneTreeDock::_delete_confirm(bool p_cut) { undo_redo->add_undo_method(scene_tree, "update_tree"); undo_redo->add_undo_reference(edited_scene); } else { - for (const Node *E : remove_list) { - // `move_child` + `get_index` doesn't really work for internal nodes. - ERR_FAIL_COND_MSG(E->get_internal_mode() != INTERNAL_MODE_DISABLED, "Trying to remove internal node, this is not supported."); - } if (delete_tracks_checkbox->is_pressed() || p_cut) { remove_list.sort_custom<Node::Comparator>(); // Sort nodes to keep positions. HashMap<Node *, NodePath> path_renames; diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index c72755642b..36c5ad0937 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -57,6 +57,7 @@ #include "scene/scene_string_names.h" #ifdef TOOLS_ENABLED +#include "core/extension/gdextension_manager.h" #include "editor/editor_paths.h" #endif @@ -2177,9 +2178,26 @@ void GDScriptLanguage::_add_global(const StringName &p_name, const Variant &p_va global_array.write[globals[p_name]] = p_value; return; } - globals[p_name] = global_array.size(); - global_array.push_back(p_value); - _global_array = global_array.ptrw(); + + if (global_array_empty_indexes.size()) { + int index = global_array_empty_indexes[global_array_empty_indexes.size() - 1]; + globals[p_name] = index; + global_array.write[index] = p_value; + global_array_empty_indexes.resize(global_array_empty_indexes.size() - 1); + } else { + globals[p_name] = global_array.size(); + global_array.push_back(p_value); + _global_array = global_array.ptrw(); + } +} + +void GDScriptLanguage::_remove_global(const StringName &p_name) { + if (!globals.has(p_name)) { + return; + } + global_array_empty_indexes.push_back(globals[p_name]); + global_array.write[globals[p_name]] = Variant::NIL; + globals.erase(p_name); } void GDScriptLanguage::add_global_constant(const StringName &p_variable, const Variant &p_value) { @@ -2237,11 +2255,40 @@ void GDScriptLanguage::init() { _add_global(E.name, E.ptr); } +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + GDExtensionManager::get_singleton()->connect("extension_loaded", callable_mp(this, &GDScriptLanguage::_extension_loaded)); + GDExtensionManager::get_singleton()->connect("extension_unloading", callable_mp(this, &GDScriptLanguage::_extension_unloading)); + } +#endif + #ifdef TESTS_ENABLED GDScriptTests::GDScriptTestRunner::handle_cmdline(); #endif } +#ifdef TOOLS_ENABLED +void GDScriptLanguage::_extension_loaded(const Ref<GDExtension> &p_extension) { + List<StringName> class_list; + ClassDB::get_extension_class_list(p_extension, &class_list); + for (const StringName &n : class_list) { + if (globals.has(n)) { + continue; + } + Ref<GDScriptNativeClass> nc = memnew(GDScriptNativeClass(n)); + _add_global(n, nc); + } +} + +void GDScriptLanguage::_extension_unloading(const Ref<GDExtension> &p_extension) { + List<StringName> class_list; + ClassDB::get_extension_class_list(p_extension, &class_list); + for (const StringName &n : class_list) { + _remove_global(n); + } +} +#endif + String GDScriptLanguage::get_type() const { return "GDScript"; } diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h index 6527a0ea4d..4d21651365 100644 --- a/modules/gdscript/gdscript.h +++ b/modules/gdscript/gdscript.h @@ -417,6 +417,7 @@ class GDScriptLanguage : public ScriptLanguage { Vector<Variant> global_array; HashMap<StringName, int> globals; HashMap<StringName, Variant> named_globals; + Vector<int> global_array_empty_indexes; struct CallLevel { Variant *stack = nullptr; @@ -448,6 +449,7 @@ class GDScriptLanguage : public ScriptLanguage { int _debug_max_call_stack = 0; void _add_global(const StringName &p_name, const Variant &p_value); + void _remove_global(const StringName &p_name); friend class GDScriptInstance; @@ -467,6 +469,11 @@ class GDScriptLanguage : public ScriptLanguage { HashMap<String, ObjectID> orphan_subclasses; +#ifdef TOOLS_ENABLED + void _extension_loaded(const Ref<GDExtension> &p_extension); + void _extension_unloading(const Ref<GDExtension> &p_extension); +#endif + public: int calls; diff --git a/modules/gdscript/tests/scripts/analyzer/features/assymetric_assignment_good.gd b/modules/gdscript/tests/scripts/analyzer/features/assymetric_assignment_good.gd index efd8ad6edb..60bcde4b8c 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/assymetric_assignment_good.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/assymetric_assignment_good.gd @@ -3,14 +3,13 @@ const const_color: Color = 'red' func func_color(arg_color: Color = 'blue') -> bool: return arg_color == Color.BLUE -@warning_ignore("assert_always_true") func test(): - assert(const_color == Color.RED) + Utils.check(const_color == Color.RED) - assert(func_color() == true) - assert(func_color('blue') == true) + Utils.check(func_color() == true) + Utils.check(func_color('blue') == true) var var_color: Color = 'green' - assert(var_color == Color.GREEN) + Utils.check(var_color == Color.GREEN) print('ok') diff --git a/modules/gdscript/tests/scripts/analyzer/features/const_conversions.gd b/modules/gdscript/tests/scripts/analyzer/features/const_conversions.gd index bed9dd0e96..5318d11f33 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/const_conversions.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/const_conversions.gd @@ -5,20 +5,19 @@ const const_float_cast: float = 76 as float const const_packed_empty: PackedFloat64Array = [] const const_packed_ints: PackedFloat64Array = [52] -@warning_ignore("assert_always_true") func test(): - assert(typeof(const_float_int) == TYPE_FLOAT) - assert(str(const_float_int) == '19') - assert(typeof(const_float_plus) == TYPE_FLOAT) - assert(str(const_float_plus) == '34') - assert(typeof(const_float_cast) == TYPE_FLOAT) - assert(str(const_float_cast) == '76') + Utils.check(typeof(const_float_int) == TYPE_FLOAT) + Utils.check(str(const_float_int) == '19') + Utils.check(typeof(const_float_plus) == TYPE_FLOAT) + Utils.check(str(const_float_plus) == '34') + Utils.check(typeof(const_float_cast) == TYPE_FLOAT) + Utils.check(str(const_float_cast) == '76') - assert(typeof(const_packed_empty) == TYPE_PACKED_FLOAT64_ARRAY) - assert(str(const_packed_empty) == '[]') - assert(typeof(const_packed_ints) == TYPE_PACKED_FLOAT64_ARRAY) - assert(str(const_packed_ints) == '[52]') - assert(typeof(const_packed_ints[0]) == TYPE_FLOAT) - assert(str(const_packed_ints[0]) == '52') + Utils.check(typeof(const_packed_empty) == TYPE_PACKED_FLOAT64_ARRAY) + Utils.check(str(const_packed_empty) == '[]') + Utils.check(typeof(const_packed_ints) == TYPE_PACKED_FLOAT64_ARRAY) + Utils.check(str(const_packed_ints) == '[52]') + Utils.check(typeof(const_packed_ints[0]) == TYPE_FLOAT) + Utils.check(str(const_packed_ints[0]) == '52') print('ok') diff --git a/modules/gdscript/tests/scripts/analyzer/features/enums_in_range_call.gd b/modules/gdscript/tests/scripts/analyzer/features/enums_in_range_call.gd index d2d9d04508..a569488d3c 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/enums_in_range_call.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/enums_in_range_call.gd @@ -5,5 +5,5 @@ func test(): for value in range(E.E0, E.E3): var inferable := value total += inferable - assert(total == 0 + 1 + 2) + Utils.check(total == 0 + 1 + 2) print('ok') diff --git a/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.gd b/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.gd index 39f490c4b3..ec89226328 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.gd @@ -2,8 +2,6 @@ class_name TestExportEnumAsDictionary enum MyEnum {A, B, C} -const Utils = preload("../../utils.notest.gd") - @export var test_1 = MyEnum @export var test_2 = MyEnum.A @export var test_3 := MyEnum diff --git a/modules/gdscript/tests/scripts/analyzer/features/for_range_usage.gd b/modules/gdscript/tests/scripts/analyzer/features/for_range_usage.gd index 4a7f10f1ee..9ce0782d5c 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/for_range_usage.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/for_range_usage.gd @@ -3,5 +3,5 @@ func test(): var result := '' for i in range(array.size(), 0, -1): result += str(array[i - 1]) - assert(result == '963') + Utils.check(result == '963') print('ok') diff --git a/modules/gdscript/tests/scripts/analyzer/features/function_match_parent_signature_with_extra_parameters.gd b/modules/gdscript/tests/scripts/analyzer/features/function_match_parent_signature_with_extra_parameters.gd index d678f3acfc..e0cbdacb38 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/function_match_parent_signature_with_extra_parameters.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/function_match_parent_signature_with_extra_parameters.gd @@ -2,11 +2,11 @@ func test(): var instance := Parent.new() var result := instance.my_function(1) print(result) - assert(result == 1) + Utils.check(result == 1) instance = Child.new() result = instance.my_function(2) print(result) - assert(result == 0) + Utils.check(result == 0) class Parent: func my_function(par1: int) -> int: diff --git a/modules/gdscript/tests/scripts/analyzer/features/return_conversions.gd b/modules/gdscript/tests/scripts/analyzer/features/return_conversions.gd index 0b1576e66e..cbe8e9da34 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/return_conversions.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/return_conversions.gd @@ -8,27 +8,27 @@ func convert_var_array_to_packed() -> PackedStringArray: var array := ['79']; re func test(): var converted_literal_int := convert_literal_int_to_float() - assert(typeof(converted_literal_int) == TYPE_FLOAT) - assert(converted_literal_int == 76.0) + Utils.check(typeof(converted_literal_int) == TYPE_FLOAT) + Utils.check(converted_literal_int == 76.0) var converted_arg_int := convert_arg_int_to_float(36) - assert(typeof(converted_arg_int) == TYPE_FLOAT) - assert(converted_arg_int == 36.0) + Utils.check(typeof(converted_arg_int) == TYPE_FLOAT) + Utils.check(converted_arg_int == 36.0) var converted_var_int := convert_var_int_to_float() - assert(typeof(converted_var_int) == TYPE_FLOAT) - assert(converted_var_int == 59.0) + Utils.check(typeof(converted_var_int) == TYPE_FLOAT) + Utils.check(converted_var_int == 59.0) var converted_literal_array := convert_literal_array_to_packed() - assert(typeof(converted_literal_array) == TYPE_PACKED_STRING_ARRAY) - assert(str(converted_literal_array) == '["46"]') + Utils.check(typeof(converted_literal_array) == TYPE_PACKED_STRING_ARRAY) + Utils.check(str(converted_literal_array) == '["46"]') var converted_arg_array := convert_arg_array_to_packed(['91']) - assert(typeof(converted_arg_array) == TYPE_PACKED_STRING_ARRAY) - assert(str(converted_arg_array) == '["91"]') + Utils.check(typeof(converted_arg_array) == TYPE_PACKED_STRING_ARRAY) + Utils.check(str(converted_arg_array) == '["91"]') var converted_var_array := convert_var_array_to_packed() - assert(typeof(converted_var_array) == TYPE_PACKED_STRING_ARRAY) - assert(str(converted_var_array) == '["79"]') + Utils.check(typeof(converted_var_array) == TYPE_PACKED_STRING_ARRAY) + Utils.check(str(converted_var_array) == '["79"]') print('ok') diff --git a/modules/gdscript/tests/scripts/analyzer/features/ternary_hard_infer.gd b/modules/gdscript/tests/scripts/analyzer/features/ternary_hard_infer.gd index 44ca5f4dd0..d49acaacd3 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/ternary_hard_infer.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/ternary_hard_infer.gd @@ -2,7 +2,7 @@ func test(): var left_hard_int := 1 var right_hard_int := 2 var result_hard_int := left_hard_int if true else right_hard_int - assert(result_hard_int == 1) + Utils.check(result_hard_int == 1) @warning_ignore("inference_on_variant") var left_hard_variant := 1 as Variant @@ -10,6 +10,6 @@ func test(): var right_hard_variant := 2.0 as Variant @warning_ignore("inference_on_variant") var result_hard_variant := left_hard_variant if true else right_hard_variant - assert(result_hard_variant == 1) + Utils.check(result_hard_variant == 1) print('ok') diff --git a/modules/gdscript/tests/scripts/analyzer/features/type_test_usage.gd b/modules/gdscript/tests/scripts/analyzer/features/type_test_usage.gd index 12dc0b93df..ee30f01dfb 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/type_test_usage.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/type_test_usage.gd @@ -4,124 +4,123 @@ class A extends RefCounted: class B extends A: pass -@warning_ignore("assert_always_true") func test(): var builtin: Variant = 3 - assert((builtin is Variant) == true) - assert((builtin is int) == true) - assert(is_instance_of(builtin, TYPE_INT) == true) - assert((builtin is float) == false) - assert(is_instance_of(builtin, TYPE_FLOAT) == false) + Utils.check((builtin is Variant) == true) + Utils.check((builtin is int) == true) + Utils.check(is_instance_of(builtin, TYPE_INT) == true) + Utils.check((builtin is float) == false) + Utils.check(is_instance_of(builtin, TYPE_FLOAT) == false) const const_builtin: Variant = 3 - assert((const_builtin is Variant) == true) - assert((const_builtin is int) == true) - assert(is_instance_of(const_builtin, TYPE_INT) == true) - assert((const_builtin is float) == false) - assert(is_instance_of(const_builtin, TYPE_FLOAT) == false) + Utils.check((const_builtin is Variant) == true) + Utils.check((const_builtin is int) == true) + Utils.check(is_instance_of(const_builtin, TYPE_INT) == true) + Utils.check((const_builtin is float) == false) + Utils.check(is_instance_of(const_builtin, TYPE_FLOAT) == false) var int_array: Variant = [] as Array[int] - assert((int_array is Variant) == true) - assert((int_array is Array) == true) - assert(is_instance_of(int_array, TYPE_ARRAY) == true) - assert((int_array is Array[int]) == true) - assert((int_array is Array[float]) == false) - assert((int_array is int) == false) - assert(is_instance_of(int_array, TYPE_INT) == false) + Utils.check((int_array is Variant) == true) + Utils.check((int_array is Array) == true) + Utils.check(is_instance_of(int_array, TYPE_ARRAY) == true) + Utils.check((int_array is Array[int]) == true) + Utils.check((int_array is Array[float]) == false) + Utils.check((int_array is int) == false) + Utils.check(is_instance_of(int_array, TYPE_INT) == false) var const_int_array: Variant = [] as Array[int] - assert((const_int_array is Variant) == true) - assert((const_int_array is Array) == true) - assert(is_instance_of(const_int_array, TYPE_ARRAY) == true) - assert((const_int_array is Array[int]) == true) - assert((const_int_array is Array[float]) == false) - assert((const_int_array is int) == false) - assert(is_instance_of(const_int_array, TYPE_INT) == false) + Utils.check((const_int_array is Variant) == true) + Utils.check((const_int_array is Array) == true) + Utils.check(is_instance_of(const_int_array, TYPE_ARRAY) == true) + Utils.check((const_int_array is Array[int]) == true) + Utils.check((const_int_array is Array[float]) == false) + Utils.check((const_int_array is int) == false) + Utils.check(is_instance_of(const_int_array, TYPE_INT) == false) var b_array: Variant = [] as Array[B] - assert((b_array is Variant) == true) - assert((b_array is Array) == true) - assert(is_instance_of(b_array, TYPE_ARRAY) == true) - assert((b_array is Array[B]) == true) - assert((b_array is Array[A]) == false) - assert((b_array is Array[int]) == false) - assert((b_array is int) == false) - assert(is_instance_of(b_array, TYPE_INT) == false) + Utils.check((b_array is Variant) == true) + Utils.check((b_array is Array) == true) + Utils.check(is_instance_of(b_array, TYPE_ARRAY) == true) + Utils.check((b_array is Array[B]) == true) + Utils.check((b_array is Array[A]) == false) + Utils.check((b_array is Array[int]) == false) + Utils.check((b_array is int) == false) + Utils.check(is_instance_of(b_array, TYPE_INT) == false) var const_b_array: Variant = [] as Array[B] - assert((const_b_array is Variant) == true) - assert((const_b_array is Array) == true) - assert(is_instance_of(const_b_array, TYPE_ARRAY) == true) - assert((const_b_array is Array[B]) == true) - assert((const_b_array is Array[A]) == false) - assert((const_b_array is Array[int]) == false) - assert((const_b_array is int) == false) - assert(is_instance_of(const_b_array, TYPE_INT) == false) + Utils.check((const_b_array is Variant) == true) + Utils.check((const_b_array is Array) == true) + Utils.check(is_instance_of(const_b_array, TYPE_ARRAY) == true) + Utils.check((const_b_array is Array[B]) == true) + Utils.check((const_b_array is Array[A]) == false) + Utils.check((const_b_array is Array[int]) == false) + Utils.check((const_b_array is int) == false) + Utils.check(is_instance_of(const_b_array, TYPE_INT) == false) var native: Variant = RefCounted.new() - assert((native is Variant) == true) - assert((native is Object) == true) - assert(is_instance_of(native, TYPE_OBJECT) == true) - assert(is_instance_of(native, Object) == true) - assert((native is RefCounted) == true) - assert(is_instance_of(native, RefCounted) == true) - assert((native is Node) == false) - assert(is_instance_of(native, Node) == false) - assert((native is int) == false) - assert(is_instance_of(native, TYPE_INT) == false) + Utils.check((native is Variant) == true) + Utils.check((native is Object) == true) + Utils.check(is_instance_of(native, TYPE_OBJECT) == true) + Utils.check(is_instance_of(native, Object) == true) + Utils.check((native is RefCounted) == true) + Utils.check(is_instance_of(native, RefCounted) == true) + Utils.check((native is Node) == false) + Utils.check(is_instance_of(native, Node) == false) + Utils.check((native is int) == false) + Utils.check(is_instance_of(native, TYPE_INT) == false) var a_script: Variant = A.new() - assert((a_script is Variant) == true) - assert((a_script is Object) == true) - assert(is_instance_of(a_script, TYPE_OBJECT) == true) - assert(is_instance_of(a_script, Object) == true) - assert((a_script is RefCounted) == true) - assert(is_instance_of(a_script, RefCounted) == true) - assert((a_script is A) == true) - assert(is_instance_of(a_script, A) == true) - assert((a_script is B) == false) - assert(is_instance_of(a_script, B) == false) - assert((a_script is Node) == false) - assert(is_instance_of(a_script, Node) == false) - assert((a_script is int) == false) - assert(is_instance_of(a_script, TYPE_INT) == false) + Utils.check((a_script is Variant) == true) + Utils.check((a_script is Object) == true) + Utils.check(is_instance_of(a_script, TYPE_OBJECT) == true) + Utils.check(is_instance_of(a_script, Object) == true) + Utils.check((a_script is RefCounted) == true) + Utils.check(is_instance_of(a_script, RefCounted) == true) + Utils.check((a_script is A) == true) + Utils.check(is_instance_of(a_script, A) == true) + Utils.check((a_script is B) == false) + Utils.check(is_instance_of(a_script, B) == false) + Utils.check((a_script is Node) == false) + Utils.check(is_instance_of(a_script, Node) == false) + Utils.check((a_script is int) == false) + Utils.check(is_instance_of(a_script, TYPE_INT) == false) var b_script: Variant = B.new() - assert((b_script is Variant) == true) - assert((b_script is Object) == true) - assert(is_instance_of(b_script, TYPE_OBJECT) == true) - assert(is_instance_of(b_script, Object) == true) - assert((b_script is RefCounted) == true) - assert(is_instance_of(b_script, RefCounted) == true) - assert((b_script is A) == true) - assert(is_instance_of(b_script, A) == true) - assert((b_script is B) == true) - assert(is_instance_of(b_script, B) == true) - assert((b_script is Node) == false) - assert(is_instance_of(b_script, Node) == false) - assert((b_script is int) == false) - assert(is_instance_of(b_script, TYPE_INT) == false) + Utils.check((b_script is Variant) == true) + Utils.check((b_script is Object) == true) + Utils.check(is_instance_of(b_script, TYPE_OBJECT) == true) + Utils.check(is_instance_of(b_script, Object) == true) + Utils.check((b_script is RefCounted) == true) + Utils.check(is_instance_of(b_script, RefCounted) == true) + Utils.check((b_script is A) == true) + Utils.check(is_instance_of(b_script, A) == true) + Utils.check((b_script is B) == true) + Utils.check(is_instance_of(b_script, B) == true) + Utils.check((b_script is Node) == false) + Utils.check(is_instance_of(b_script, Node) == false) + Utils.check((b_script is int) == false) + Utils.check(is_instance_of(b_script, TYPE_INT) == false) var var_null: Variant = null - assert((var_null is Variant) == true) - assert((var_null is int) == false) - assert(is_instance_of(var_null, TYPE_INT) == false) - assert((var_null is Object) == false) - assert(is_instance_of(var_null, TYPE_OBJECT) == false) - assert((var_null is RefCounted) == false) - assert(is_instance_of(var_null, RefCounted) == false) - assert((var_null is A) == false) - assert(is_instance_of(var_null, A) == false) + Utils.check((var_null is Variant) == true) + Utils.check((var_null is int) == false) + Utils.check(is_instance_of(var_null, TYPE_INT) == false) + Utils.check((var_null is Object) == false) + Utils.check(is_instance_of(var_null, TYPE_OBJECT) == false) + Utils.check((var_null is RefCounted) == false) + Utils.check(is_instance_of(var_null, RefCounted) == false) + Utils.check((var_null is A) == false) + Utils.check(is_instance_of(var_null, A) == false) const const_null: Variant = null - assert((const_null is Variant) == true) - assert((const_null is int) == false) - assert(is_instance_of(const_null, TYPE_INT) == false) - assert((const_null is Object) == false) - assert(is_instance_of(const_null, TYPE_OBJECT) == false) - assert((const_null is RefCounted) == false) - assert(is_instance_of(const_null, RefCounted) == false) - assert((const_null is A) == false) - assert(is_instance_of(const_null, A) == false) + Utils.check((const_null is Variant) == true) + Utils.check((const_null is int) == false) + Utils.check(is_instance_of(const_null, TYPE_INT) == false) + Utils.check((const_null is Object) == false) + Utils.check(is_instance_of(const_null, TYPE_OBJECT) == false) + Utils.check((const_null is RefCounted) == false) + Utils.check(is_instance_of(const_null, RefCounted) == false) + Utils.check((const_null is A) == false) + Utils.check(is_instance_of(const_null, A) == false) print('ok') diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd b/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd index b000c82717..fe0274c27b 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd @@ -10,206 +10,205 @@ class Members: var two: Array[int] = one func check_passing() -> bool: - assert(str(one) == '[104]') - assert(str(two) == '[104]') + Utils.check(str(one) == '[104]') + Utils.check(str(two) == '[104]') two.push_back(582) - assert(str(one) == '[104, 582]') - assert(str(two) == '[104, 582]') + Utils.check(str(one) == '[104, 582]') + Utils.check(str(two) == '[104, 582]') two = [486] - assert(str(one) == '[104, 582]') - assert(str(two) == '[486]') + Utils.check(str(one) == '[104, 582]') + Utils.check(str(two) == '[486]') return true @warning_ignore("unsafe_method_access") -@warning_ignore("assert_always_true") @warning_ignore("return_value_discarded") func test(): var untyped_basic = [459] - assert(str(untyped_basic) == '[459]') - assert(untyped_basic.get_typed_builtin() == TYPE_NIL) + Utils.check(str(untyped_basic) == '[459]') + Utils.check(untyped_basic.get_typed_builtin() == TYPE_NIL) var inferred_basic := [366] - assert(str(inferred_basic) == '[366]') - assert(inferred_basic.get_typed_builtin() == TYPE_NIL) + Utils.check(str(inferred_basic) == '[366]') + Utils.check(inferred_basic.get_typed_builtin() == TYPE_NIL) var typed_basic: Array = [521] - assert(str(typed_basic) == '[521]') - assert(typed_basic.get_typed_builtin() == TYPE_NIL) + Utils.check(str(typed_basic) == '[521]') + Utils.check(typed_basic.get_typed_builtin() == TYPE_NIL) var empty_floats: Array[float] = [] - assert(str(empty_floats) == '[]') - assert(empty_floats.get_typed_builtin() == TYPE_FLOAT) + Utils.check(str(empty_floats) == '[]') + Utils.check(empty_floats.get_typed_builtin() == TYPE_FLOAT) untyped_basic = empty_floats - assert(untyped_basic.get_typed_builtin() == TYPE_FLOAT) + Utils.check(untyped_basic.get_typed_builtin() == TYPE_FLOAT) inferred_basic = empty_floats - assert(inferred_basic.get_typed_builtin() == TYPE_FLOAT) + Utils.check(inferred_basic.get_typed_builtin() == TYPE_FLOAT) typed_basic = empty_floats - assert(typed_basic.get_typed_builtin() == TYPE_FLOAT) + Utils.check(typed_basic.get_typed_builtin() == TYPE_FLOAT) empty_floats.push_back(705.0) untyped_basic.push_back(430.0) inferred_basic.push_back(263.0) typed_basic.push_back(518.0) - assert(str(empty_floats) == '[705, 430, 263, 518]') - assert(str(untyped_basic) == '[705, 430, 263, 518]') - assert(str(inferred_basic) == '[705, 430, 263, 518]') - assert(str(typed_basic) == '[705, 430, 263, 518]') + Utils.check(str(empty_floats) == '[705, 430, 263, 518]') + Utils.check(str(untyped_basic) == '[705, 430, 263, 518]') + Utils.check(str(inferred_basic) == '[705, 430, 263, 518]') + Utils.check(str(typed_basic) == '[705, 430, 263, 518]') const constant_float := 950.0 const constant_int := 170 var typed_float := 954.0 var filled_floats: Array[float] = [constant_float, constant_int, typed_float, empty_floats[1] + empty_floats[2]] - assert(str(filled_floats) == '[950, 170, 954, 693]') - assert(filled_floats.get_typed_builtin() == TYPE_FLOAT) + Utils.check(str(filled_floats) == '[950, 170, 954, 693]') + Utils.check(filled_floats.get_typed_builtin() == TYPE_FLOAT) var casted_floats := [empty_floats[2] * 2] as Array[float] - assert(str(casted_floats) == '[526]') - assert(casted_floats.get_typed_builtin() == TYPE_FLOAT) + Utils.check(str(casted_floats) == '[526]') + Utils.check(casted_floats.get_typed_builtin() == TYPE_FLOAT) var returned_floats = (func () -> Array[float]: return [554]).call() - assert(str(returned_floats) == '[554]') - assert(returned_floats.get_typed_builtin() == TYPE_FLOAT) + Utils.check(str(returned_floats) == '[554]') + Utils.check(returned_floats.get_typed_builtin() == TYPE_FLOAT) var passed_floats = floats_identity([663.0 if randf() > 0.5 else 663.0]) - assert(str(passed_floats) == '[663]') - assert(passed_floats.get_typed_builtin() == TYPE_FLOAT) + Utils.check(str(passed_floats) == '[663]') + Utils.check(passed_floats.get_typed_builtin() == TYPE_FLOAT) var default_floats = (func (floats: Array[float] = [364.0]): return floats).call() - assert(str(default_floats) == '[364]') - assert(default_floats.get_typed_builtin() == TYPE_FLOAT) + Utils.check(str(default_floats) == '[364]') + Utils.check(default_floats.get_typed_builtin() == TYPE_FLOAT) var typed_int := 556 var converted_floats: Array[float] = [typed_int] converted_floats.push_back(498) - assert(str(converted_floats) == '[556, 498]') - assert(converted_floats.get_typed_builtin() == TYPE_FLOAT) + Utils.check(str(converted_floats) == '[556, 498]') + Utils.check(converted_floats.get_typed_builtin() == TYPE_FLOAT) const constant_basic = [228] - assert(str(constant_basic) == '[228]') - assert(constant_basic.get_typed_builtin() == TYPE_NIL) + Utils.check(str(constant_basic) == '[228]') + Utils.check(constant_basic.get_typed_builtin() == TYPE_NIL) const constant_floats: Array[float] = [constant_float - constant_basic[0] - constant_int] - assert(str(constant_floats) == '[552]') - assert(constant_floats.get_typed_builtin() == TYPE_FLOAT) + Utils.check(str(constant_floats) == '[552]') + Utils.check(constant_floats.get_typed_builtin() == TYPE_FLOAT) var source_floats: Array[float] = [999.74] untyped_basic = source_floats var destination_floats: Array[float] = untyped_basic destination_floats[0] -= 0.74 - assert(str(source_floats) == '[999]') - assert(str(untyped_basic) == '[999]') - assert(str(destination_floats) == '[999]') - assert(destination_floats.get_typed_builtin() == TYPE_FLOAT) + Utils.check(str(source_floats) == '[999]') + Utils.check(str(untyped_basic) == '[999]') + Utils.check(str(destination_floats) == '[999]') + Utils.check(destination_floats.get_typed_builtin() == TYPE_FLOAT) var duplicated_floats := empty_floats.duplicate().slice(2, 3) duplicated_floats[0] *= 3 - assert(str(duplicated_floats) == '[789]') - assert(duplicated_floats.get_typed_builtin() == TYPE_FLOAT) + Utils.check(str(duplicated_floats) == '[789]') + Utils.check(duplicated_floats.get_typed_builtin() == TYPE_FLOAT) var b_objects: Array[B] = [B.new(), B.new() as A, null] - assert(b_objects.size() == 3) - assert(b_objects.get_typed_builtin() == TYPE_OBJECT) - assert(b_objects.get_typed_script() == B) + Utils.check(b_objects.size() == 3) + Utils.check(b_objects.get_typed_builtin() == TYPE_OBJECT) + Utils.check(b_objects.get_typed_script() == B) var a_objects: Array[A] = [A.new(), B.new(), null, b_objects[0]] - assert(a_objects.size() == 4) - assert(a_objects.get_typed_builtin() == TYPE_OBJECT) - assert(a_objects.get_typed_script() == A) + Utils.check(a_objects.size() == 4) + Utils.check(a_objects.get_typed_builtin() == TYPE_OBJECT) + Utils.check(a_objects.get_typed_script() == A) var a_passed = (func check_a_passing(p_objects: Array[A]): return p_objects.size()).call(a_objects) - assert(a_passed == 4) + Utils.check(a_passed == 4) var b_passed = (func check_b_passing(basic: Array): return basic[0] != null).call(b_objects) - assert(b_passed == true) + Utils.check(b_passed == true) var empty_strings: Array[String] = [] var empty_bools: Array[bool] = [] var empty_basic_one := [] var empty_basic_two := [] - assert(empty_strings == empty_bools) - assert(empty_basic_one == empty_basic_two) - assert(empty_strings.hash() == empty_bools.hash()) - assert(empty_basic_one.hash() == empty_basic_two.hash()) + Utils.check(empty_strings == empty_bools) + Utils.check(empty_basic_one == empty_basic_two) + Utils.check(empty_strings.hash() == empty_bools.hash()) + Utils.check(empty_basic_one.hash() == empty_basic_two.hash()) var assign_source: Array[int] = [527] var assign_target: Array[int] = [] assign_target.assign(assign_source) - assert(str(assign_source) == '[527]') - assert(str(assign_target) == '[527]') + Utils.check(str(assign_source) == '[527]') + Utils.check(str(assign_target) == '[527]') assign_source.push_back(657) - assert(str(assign_source) == '[527, 657]') - assert(str(assign_target) == '[527]') + Utils.check(str(assign_source) == '[527, 657]') + Utils.check(str(assign_target) == '[527]') var defaults_passed = (func check_defaults_passing(one: Array[int] = [], two := one): one.push_back(887) two.push_back(198) - assert(str(one) == '[887, 198]') - assert(str(two) == '[887, 198]') + Utils.check(str(one) == '[887, 198]') + Utils.check(str(two) == '[887, 198]') two = [130] - assert(str(one) == '[887, 198]') - assert(str(two) == '[130]') + Utils.check(str(one) == '[887, 198]') + Utils.check(str(two) == '[130]') return true ).call() - assert(defaults_passed == true) + Utils.check(defaults_passed == true) var members := Members.new() var members_passed := members.check_passing() - assert(members_passed == true) + Utils.check(members_passed == true) var resized_basic: Array = [] resized_basic.resize(1) - assert(typeof(resized_basic[0]) == TYPE_NIL) - assert(resized_basic[0] == null) + Utils.check(typeof(resized_basic[0]) == TYPE_NIL) + Utils.check(resized_basic[0] == null) var resized_ints: Array[int] = [] resized_ints.resize(1) - assert(typeof(resized_ints[0]) == TYPE_INT) - assert(resized_ints[0] == 0) + Utils.check(typeof(resized_ints[0]) == TYPE_INT) + Utils.check(resized_ints[0] == 0) var resized_arrays: Array[Array] = [] resized_arrays.resize(1) - assert(typeof(resized_arrays[0]) == TYPE_ARRAY) + Utils.check(typeof(resized_arrays[0]) == TYPE_ARRAY) resized_arrays[0].resize(1) resized_arrays[0][0] = 523 - assert(str(resized_arrays) == '[[523]]') + Utils.check(str(resized_arrays) == '[[523]]') var resized_objects: Array[Object] = [] resized_objects.resize(1) - assert(typeof(resized_objects[0]) == TYPE_NIL) - assert(resized_objects[0] == null) + Utils.check(typeof(resized_objects[0]) == TYPE_NIL) + Utils.check(resized_objects[0] == null) var typed_enums: Array[E] = [] typed_enums.resize(1) - assert(str(typed_enums) == '[0]') + Utils.check(str(typed_enums) == '[0]') typed_enums[0] = E.E0 - assert(str(typed_enums) == '[391]') - assert(typed_enums.get_typed_builtin() == TYPE_INT) + Utils.check(str(typed_enums) == '[391]') + Utils.check(typed_enums.get_typed_builtin() == TYPE_INT) const const_enums: Array[E] = [] - assert(const_enums.get_typed_builtin() == TYPE_INT) - assert(const_enums.get_typed_class_name() == &'') + Utils.check(const_enums.get_typed_builtin() == TYPE_INT) + Utils.check(const_enums.get_typed_class_name() == &'') var a := A.new() var typed_natives: Array[RefCounted] = [a] var typed_scripts = Array(typed_natives, TYPE_OBJECT, "RefCounted", A) - assert(typed_scripts[0] == a) + Utils.check(typed_scripts[0] == a) print('ok') diff --git a/modules/gdscript/tests/scripts/parser/features/annotations.gd b/modules/gdscript/tests/scripts/parser/features/annotations.gd index 7a7d6d953e..1e5d3fdcad 100644 --- a/modules/gdscript/tests/scripts/parser/features/annotations.gd +++ b/modules/gdscript/tests/scripts/parser/features/annotations.gd @@ -1,7 +1,5 @@ extends Node -const Utils = preload("../../utils.notest.gd") - @export_enum("A", "B", "C") var test_1 @export_enum("A", "B", "C",) var test_2 diff --git a/modules/gdscript/tests/scripts/parser/features/class.gd b/modules/gdscript/tests/scripts/parser/features/class.gd index af24b32322..482d04a63b 100644 --- a/modules/gdscript/tests/scripts/parser/features/class.gd +++ b/modules/gdscript/tests/scripts/parser/features/class.gd @@ -18,8 +18,8 @@ func test(): test_instance.number = 42 var test_sub = TestSub.new() - assert(test_sub.number == 25) # From Test. - assert(test_sub.other_string == "bye") # From TestSub. + Utils.check(test_sub.number == 25) # From Test. + Utils.check(test_sub.other_string == "bye") # From TestSub. var _test_constructor = TestConstructor.new() _test_constructor = TestConstructor.new(500) diff --git a/modules/gdscript/tests/scripts/parser/features/export_arrays.gd b/modules/gdscript/tests/scripts/parser/features/export_arrays.gd index 0d97135a7b..cfda255905 100644 --- a/modules/gdscript/tests/scripts/parser/features/export_arrays.gd +++ b/modules/gdscript/tests/scripts/parser/features/export_arrays.gd @@ -1,5 +1,3 @@ -const Utils = preload("../../utils.notest.gd") - @export_dir var test_dir: Array[String] @export_dir var test_dir_packed: PackedStringArray @export_file var test_file: Array[String] diff --git a/modules/gdscript/tests/scripts/parser/features/export_enum.gd b/modules/gdscript/tests/scripts/parser/features/export_enum.gd index 7f0737f4db..d50f0b2528 100644 --- a/modules/gdscript/tests/scripts/parser/features/export_enum.gd +++ b/modules/gdscript/tests/scripts/parser/features/export_enum.gd @@ -1,5 +1,3 @@ -const Utils = preload("../../utils.notest.gd") - @export_enum("Red", "Green", "Blue") var test_untyped @export_enum("Red:10", "Green:20", "Blue:30") var test_with_values diff --git a/modules/gdscript/tests/scripts/parser/features/export_variable.gd b/modules/gdscript/tests/scripts/parser/features/export_variable.gd index 8bcb2bcb9a..1e134d0e0e 100644 --- a/modules/gdscript/tests/scripts/parser/features/export_variable.gd +++ b/modules/gdscript/tests/scripts/parser/features/export_variable.gd @@ -1,7 +1,6 @@ class_name ExportVariableTest extends Node -const Utils = preload("../../utils.notest.gd") const PreloadedGlobalClass = preload("./export_variable_global.notest.gd") const PreloadedUnnamedClass = preload("./export_variable_unnamed.notest.gd") diff --git a/modules/gdscript/tests/scripts/parser/features/good_continue_in_lambda.gd b/modules/gdscript/tests/scripts/parser/features/good_continue_in_lambda.gd index 2fa45c1d7d..0ec118b6b7 100644 --- a/modules/gdscript/tests/scripts/parser/features/good_continue_in_lambda.gd +++ b/modules/gdscript/tests/scripts/parser/features/good_continue_in_lambda.gd @@ -9,5 +9,5 @@ func test(): j_string += str(j) return j_string i_string += lambda.call() - assert(i_string == '0202') + Utils.check(i_string == '0202') print('ok') diff --git a/modules/gdscript/tests/scripts/parser/features/truthiness.gd b/modules/gdscript/tests/scripts/parser/features/truthiness.gd index 9c67a152f5..736cda7f74 100644 --- a/modules/gdscript/tests/scripts/parser/features/truthiness.gd +++ b/modules/gdscript/tests/scripts/parser/features/truthiness.gd @@ -1,30 +1,32 @@ func test(): - # The assertions below should all evaluate to `true` for this test to pass. - assert(true) - assert(not false) - assert(500) - assert(not 0) - assert(500.5) - assert(not 0.0) - assert("non-empty string") - assert(["non-empty array"]) - assert({"non-empty": "dictionary"}) - assert(Vector2(1, 0)) - assert(Vector2i(-1, -1)) - assert(Vector3(0, 0, 0.0001)) - assert(Vector3i(0, 0, 10000)) + # The checks below should all evaluate to `true` for this test to pass. + Utils.check(true) + Utils.check(not false) + Utils.check(500) + Utils.check(not 0) + Utils.check(500.5) + Utils.check(not 0.0) + Utils.check("non-empty string") + Utils.check(["non-empty array"]) + Utils.check({"non-empty": "dictionary"}) + Utils.check(Vector2(1, 0)) + Utils.check(Vector2i(-1, -1)) + Utils.check(Vector3(0, 0, 0.0001)) + Utils.check(Vector3i(0, 0, 10000)) # Zero position is `true` only if the Rect2's size is non-zero. - assert(Rect2(0, 0, 0, 1)) + Utils.check(Rect2(0, 0, 0, 1)) # Zero size is `true` only if the position is non-zero. - assert(Rect2(1, 1, 0, 0)) + Utils.check(Rect2(1, 1, 0, 0)) # Zero position is `true` only if the Rect2's size is non-zero. - assert(Rect2i(0, 0, 0, 1)) + Utils.check(Rect2i(0, 0, 0, 1)) # Zero size is `true` only if the position is non-zero. - assert(Rect2i(1, 1, 0, 0)) + Utils.check(Rect2i(1, 1, 0, 0)) # A fully black color is only truthy if its alpha component is not equal to `1`. - assert(Color(0, 0, 0, 0.5)) + Utils.check(Color(0, 0, 0, 0.5)) + + print("ok") diff --git a/modules/gdscript/tests/scripts/parser/features/truthiness.out b/modules/gdscript/tests/scripts/parser/features/truthiness.out index 705524857b..1b47ed10dc 100644 --- a/modules/gdscript/tests/scripts/parser/features/truthiness.out +++ b/modules/gdscript/tests/scripts/parser/features/truthiness.out @@ -1,65 +1,2 @@ GDTEST_OK ->> WARNING ->> Line: 3 ->> ASSERT_ALWAYS_TRUE ->> Assert statement is redundant because the expression is always true. ->> WARNING ->> Line: 4 ->> ASSERT_ALWAYS_TRUE ->> Assert statement is redundant because the expression is always true. ->> WARNING ->> Line: 5 ->> ASSERT_ALWAYS_TRUE ->> Assert statement is redundant because the expression is always true. ->> WARNING ->> Line: 6 ->> ASSERT_ALWAYS_TRUE ->> Assert statement is redundant because the expression is always true. ->> WARNING ->> Line: 7 ->> ASSERT_ALWAYS_TRUE ->> Assert statement is redundant because the expression is always true. ->> WARNING ->> Line: 8 ->> ASSERT_ALWAYS_TRUE ->> Assert statement is redundant because the expression is always true. ->> WARNING ->> Line: 9 ->> ASSERT_ALWAYS_TRUE ->> Assert statement is redundant because the expression is always true. ->> WARNING ->> Line: 12 ->> ASSERT_ALWAYS_TRUE ->> Assert statement is redundant because the expression is always true. ->> WARNING ->> Line: 13 ->> ASSERT_ALWAYS_TRUE ->> Assert statement is redundant because the expression is always true. ->> WARNING ->> Line: 14 ->> ASSERT_ALWAYS_TRUE ->> Assert statement is redundant because the expression is always true. ->> WARNING ->> Line: 15 ->> ASSERT_ALWAYS_TRUE ->> Assert statement is redundant because the expression is always true. ->> WARNING ->> Line: 18 ->> ASSERT_ALWAYS_TRUE ->> Assert statement is redundant because the expression is always true. ->> WARNING ->> Line: 21 ->> ASSERT_ALWAYS_TRUE ->> Assert statement is redundant because the expression is always true. ->> WARNING ->> Line: 24 ->> ASSERT_ALWAYS_TRUE ->> Assert statement is redundant because the expression is always true. ->> WARNING ->> Line: 27 ->> ASSERT_ALWAYS_TRUE ->> Assert statement is redundant because the expression is always true. ->> WARNING ->> Line: 30 ->> ASSERT_ALWAYS_TRUE ->> Assert statement is redundant because the expression is always true. +ok diff --git a/modules/gdscript/tests/scripts/runtime/features/array_string_stringname_equivalent.gd b/modules/gdscript/tests/scripts/runtime/features/array_string_stringname_equivalent.gd index bd38259cec..6eec37d64d 100644 --- a/modules/gdscript/tests/scripts/runtime/features/array_string_stringname_equivalent.gd +++ b/modules/gdscript/tests/scripts/runtime/features/array_string_stringname_equivalent.gd @@ -25,7 +25,7 @@ func test(): print("String in Array[StringName]: ", "abc" in stringname_array) var packed_string_array: PackedStringArray = [] - assert(!packed_string_array.push_back("abc")) + Utils.check(!packed_string_array.push_back("abc")) print("StringName in PackedStringArray: ", &"abc" in packed_string_array) string_array.push_back("abc") diff --git a/modules/gdscript/tests/scripts/runtime/features/constants_are_read_only.gd b/modules/gdscript/tests/scripts/runtime/features/constants_are_read_only.gd index d1746979be..6aa863c05f 100644 --- a/modules/gdscript/tests/scripts/runtime/features/constants_are_read_only.gd +++ b/modules/gdscript/tests/scripts/runtime/features/constants_are_read_only.gd @@ -1,10 +1,9 @@ const array: Array = [0] const dictionary := {1: 2} -@warning_ignore("assert_always_true") func test(): - assert(array.is_read_only() == true) - assert(str(array) == '[0]') - assert(dictionary.is_read_only() == true) - assert(str(dictionary) == '{ 1: 2 }') + Utils.check(array.is_read_only() == true) + Utils.check(str(array) == '[0]') + Utils.check(dictionary.is_read_only() == true) + Utils.check(str(dictionary) == '{ 1: 2 }') print('ok') diff --git a/modules/gdscript/tests/scripts/runtime/features/conversions_from_native_members.gd b/modules/gdscript/tests/scripts/runtime/features/conversions_from_native_members.gd index a778fb1a94..0f2526667d 100644 --- a/modules/gdscript/tests/scripts/runtime/features/conversions_from_native_members.gd +++ b/modules/gdscript/tests/scripts/runtime/features/conversions_from_native_members.gd @@ -2,8 +2,8 @@ class Foo extends Node: func _init(): name = 'f' var string: String = name - assert(typeof(string) == TYPE_STRING) - assert(string == 'f') + Utils.check(typeof(string) == TYPE_STRING) + Utils.check(string == 'f') print('ok') func test(): diff --git a/modules/gdscript/tests/scripts/runtime/features/default_set_beforehand.gd b/modules/gdscript/tests/scripts/runtime/features/default_set_beforehand.gd index 0851d939dc..9e67e75140 100644 --- a/modules/gdscript/tests/scripts/runtime/features/default_set_beforehand.gd +++ b/modules/gdscript/tests/scripts/runtime/features/default_set_beforehand.gd @@ -6,15 +6,15 @@ extends Node @onready var later_untyped = [1] func test(): - assert(typeof(later_inferred) == TYPE_ARRAY) - assert(later_inferred.size() == 0) + Utils.check(typeof(later_inferred) == TYPE_ARRAY) + Utils.check(later_inferred.size() == 0) - assert(typeof(later_static) == TYPE_ARRAY) - assert(later_static.size() == 0) + Utils.check(typeof(later_static) == TYPE_ARRAY) + Utils.check(later_static.size() == 0) - assert(typeof(later_static_with_init) == TYPE_ARRAY) - assert(later_static_with_init.size() == 0) + Utils.check(typeof(later_static_with_init) == TYPE_ARRAY) + Utils.check(later_static_with_init.size() == 0) - assert(typeof(later_untyped) == TYPE_NIL) + Utils.check(typeof(later_untyped) == TYPE_NIL) print("ok") diff --git a/modules/gdscript/tests/scripts/runtime/features/export_group_no_name_conflict_with_properties.gd b/modules/gdscript/tests/scripts/runtime/features/export_group_no_name_conflict_with_properties.gd index 0133d7fcfc..90df98e05b 100644 --- a/modules/gdscript/tests/scripts/runtime/features/export_group_no_name_conflict_with_properties.gd +++ b/modules/gdscript/tests/scripts/runtime/features/export_group_no_name_conflict_with_properties.gd @@ -1,5 +1,3 @@ -const Utils = preload("../../utils.notest.gd") - # GH-73843 @export_group("Resource") diff --git a/modules/gdscript/tests/scripts/runtime/features/match_with_pattern_guards.gd b/modules/gdscript/tests/scripts/runtime/features/match_with_pattern_guards.gd index 4cb51f8512..48a9349bf8 100644 --- a/modules/gdscript/tests/scripts/runtime/features/match_with_pattern_guards.gd +++ b/modules/gdscript/tests/scripts/runtime/features/match_with_pattern_guards.gd @@ -62,7 +62,7 @@ func test(): 0 when side_effect(): print("will run the side effect call, but not this") _: - assert(global == 1) + Utils.check(global == 1) print("side effect only ran once") func side_effect(): diff --git a/modules/gdscript/tests/scripts/runtime/features/member_info.gd b/modules/gdscript/tests/scripts/runtime/features/member_info.gd index 42b29eee43..91d5a501c8 100644 --- a/modules/gdscript/tests/scripts/runtime/features/member_info.gd +++ b/modules/gdscript/tests/scripts/runtime/features/member_info.gd @@ -5,8 +5,6 @@ class MyClass: enum MyEnum {} -const Utils = preload("../../utils.notest.gd") - static var test_static_var_untyped static var test_static_var_weak_null = null static var test_static_var_weak_int = 1 diff --git a/modules/gdscript/tests/scripts/runtime/features/member_info_inheritance.gd b/modules/gdscript/tests/scripts/runtime/features/member_info_inheritance.gd index ee5c1e1267..4ddbeaec0b 100644 --- a/modules/gdscript/tests/scripts/runtime/features/member_info_inheritance.gd +++ b/modules/gdscript/tests/scripts/runtime/features/member_info_inheritance.gd @@ -1,7 +1,5 @@ # GH-82169 -const Utils = preload("../../utils.notest.gd") - class A: static var test_static_var_a1 static var test_static_var_a2 diff --git a/modules/gdscript/tests/scripts/runtime/features/metatypes.gd b/modules/gdscript/tests/scripts/runtime/features/metatypes.gd index fd23ea0db5..d6847768e6 100644 --- a/modules/gdscript/tests/scripts/runtime/features/metatypes.gd +++ b/modules/gdscript/tests/scripts/runtime/features/metatypes.gd @@ -3,7 +3,6 @@ class MyClass: enum MyEnum {A, B, C} -const Utils = preload("../../utils.notest.gd") const Other = preload("./metatypes.notest.gd") var test_native := JSON diff --git a/modules/gdscript/tests/scripts/runtime/features/set_does_not_leak.gd b/modules/gdscript/tests/scripts/runtime/features/set_does_not_leak.gd index e1aba83507..dee36d3ae0 100644 --- a/modules/gdscript/tests/scripts/runtime/features/set_does_not_leak.gd +++ b/modules/gdscript/tests/scripts/runtime/features/set_does_not_leak.gd @@ -6,6 +6,6 @@ class MyObj: func test(): var obj_1 = MyObj.new() var obj_2 = MyObj.new() - assert(obj_2.get_reference_count() == 1) + Utils.check(obj_2.get_reference_count() == 1) obj_1.set(&"obj", obj_2) - assert(obj_2.get_reference_count() == 1) + Utils.check(obj_2.get_reference_count() == 1) diff --git a/modules/gdscript/tests/scripts/runtime/features/single_underscore_node_name.gd b/modules/gdscript/tests/scripts/runtime/features/single_underscore_node_name.gd index b07c40b6da..11a670a7fb 100644 --- a/modules/gdscript/tests/scripts/runtime/features/single_underscore_node_name.gd +++ b/modules/gdscript/tests/scripts/runtime/features/single_underscore_node_name.gd @@ -12,4 +12,4 @@ func test() -> void: node1.add_child(node2) add_child(node3) - assert(get_node("_/Child") == $_/Child) + Utils.check(get_node("_/Child") == $_/Child) diff --git a/modules/gdscript/tests/scripts/runtime/features/standalone_calls_do_not_write_to_nil.gd b/modules/gdscript/tests/scripts/runtime/features/standalone_calls_do_not_write_to_nil.gd index 691b611574..d72662736e 100644 --- a/modules/gdscript/tests/scripts/runtime/features/standalone_calls_do_not_write_to_nil.gd +++ b/modules/gdscript/tests/scripts/runtime/features/standalone_calls_do_not_write_to_nil.gd @@ -14,33 +14,33 @@ func test(): func test_construct(v, f): @warning_ignore("unsafe_call_argument") Vector2(v, v) # Built-in type construct. - assert(not f) # Test unary operator reading from `nil`. + Utils.check(not f) # Test unary operator reading from `nil`. func test_utility(v, f): abs(v) # Utility function. - assert(not f) # Test unary operator reading from `nil`. + Utils.check(not f) # Test unary operator reading from `nil`. func test_builtin_call(v, f): @warning_ignore("unsafe_method_access") v.angle() # Built-in method call. - assert(not f) # Test unary operator reading from `nil`. + Utils.check(not f) # Test unary operator reading from `nil`. func test_builtin_call_validated(v: Vector2, f): @warning_ignore("return_value_discarded") v.abs() # Built-in method call validated. - assert(not f) # Test unary operator reading from `nil`. + Utils.check(not f) # Test unary operator reading from `nil`. func test_object_call(v, f): @warning_ignore("unsafe_method_access") v.get_reference_count() # Native type method call. - assert(not f) # Test unary operator reading from `nil`. + Utils.check(not f) # Test unary operator reading from `nil`. func test_object_call_method_bind(v: Resource, f): @warning_ignore("return_value_discarded") v.duplicate() # Native type method call with MethodBind. - assert(not f) # Test unary operator reading from `nil`. + Utils.check(not f) # Test unary operator reading from `nil`. func test_object_call_method_bind_validated(v: RefCounted, f): @warning_ignore("return_value_discarded") v.get_reference_count() # Native type method call with validated MethodBind. - assert(not f) # Test unary operator reading from `nil`. + Utils.check(not f) # Test unary operator reading from `nil`. diff --git a/modules/gdscript/tests/scripts/runtime/features/typed_array_init_with_untyped_in_literal.gd b/modules/gdscript/tests/scripts/runtime/features/typed_array_init_with_untyped_in_literal.gd index ec444b4ffa..859bfd7987 100644 --- a/modules/gdscript/tests/scripts/runtime/features/typed_array_init_with_untyped_in_literal.gd +++ b/modules/gdscript/tests/scripts/runtime/features/typed_array_init_with_untyped_in_literal.gd @@ -1,6 +1,6 @@ func test(): var untyped: Variant = 32 var typed: Array[int] = [untyped] - assert(typed.get_typed_builtin() == TYPE_INT) - assert(str(typed) == '[32]') + Utils.check(typed.get_typed_builtin() == TYPE_INT) + Utils.check(str(typed) == '[32]') print('ok') diff --git a/modules/gdscript/tests/scripts/utils.notest.gd b/modules/gdscript/tests/scripts/utils.notest.gd index 7fdd6556ec..5d615d8557 100644 --- a/modules/gdscript/tests/scripts/utils.notest.gd +++ b/modules/gdscript/tests/scripts/utils.notest.gd @@ -1,3 +1,13 @@ +class_name Utils + + +# `assert()` is not evaluated in non-debug builds. Do not use `assert()` +# for anything other than testing the `assert()` itself. +static func check(condition: Variant) -> void: + if not condition: + printerr("Check failed.") + + static func get_type(property: Dictionary, is_return: bool = false) -> String: match property.type: TYPE_NIL: @@ -46,7 +56,7 @@ static func get_human_readable_hint_string(property: Dictionary) -> String: while true: if not hint_string.contains(":"): - push_error("Invalid PROPERTY_HINT_TYPE_STRING format.") + printerr("Invalid PROPERTY_HINT_TYPE_STRING format.") var elem_type_hint: String = hint_string.get_slice(":", 0) hint_string = hint_string.substr(elem_type_hint.length() + 1) @@ -58,7 +68,7 @@ static func get_human_readable_hint_string(property: Dictionary) -> String: type_hint_prefixes += "<%s>:" % type_string(elem_type) else: if elem_type_hint.count("/") != 1: - push_error("Invalid PROPERTY_HINT_TYPE_STRING format.") + printerr("Invalid PROPERTY_HINT_TYPE_STRING format.") elem_type = elem_type_hint.get_slice("/", 0).to_int() elem_hint = elem_type_hint.get_slice("/", 1).to_int() type_hint_prefixes += "<%s>/<%s>:" % [ @@ -188,7 +198,7 @@ static func get_property_hint_name(hint: PropertyHint) -> String: return "PROPERTY_HINT_HIDE_QUATERNION_EDIT" PROPERTY_HINT_PASSWORD: return "PROPERTY_HINT_PASSWORD" - push_error("Argument `hint` is invalid. Use `PROPERTY_HINT_*` constants.") + printerr("Argument `hint` is invalid. Use `PROPERTY_HINT_*` constants.") return "<invalid hint>" @@ -240,7 +250,7 @@ static func get_property_usage_string(usage: int) -> String: usage &= ~flag[0] if usage != PROPERTY_USAGE_NONE: - push_error("Argument `usage` is invalid. Use `PROPERTY_USAGE_*` constants.") + printerr("Argument `usage` is invalid. Use `PROPERTY_USAGE_*` constants.") return "<invalid usage flags>" return result.left(-1) diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp index 77bba940cb..69973a34dd 100644 --- a/modules/gltf/gltf_document.cpp +++ b/modules/gltf/gltf_document.cpp @@ -69,6 +69,24 @@ #include <stdlib.h> #include <cstdint> +static void _attach_extras_to_meta(const Dictionary &p_extras, Ref<Resource> p_node) { + if (!p_extras.is_empty()) { + p_node->set_meta("extras", p_extras); + } +} + +static void _attach_meta_to_extras(Ref<Resource> p_node, Dictionary &p_json) { + if (p_node->has_meta("extras")) { + Dictionary node_extras = p_node->get_meta("extras"); + if (p_json.has("extras")) { + Dictionary extras = p_json["extras"]; + extras.merge(node_extras); + } else { + p_json["extras"] = node_extras; + } + } +} + static Ref<ImporterMesh> _mesh_to_importer_mesh(Ref<Mesh> p_mesh) { Ref<ImporterMesh> importer_mesh; importer_mesh.instantiate(); @@ -101,6 +119,7 @@ static Ref<ImporterMesh> _mesh_to_importer_mesh(Ref<Mesh> p_mesh) { array, p_mesh->surface_get_blend_shape_arrays(surface_i), p_mesh->surface_get_lods(surface_i), mat, mat_name, p_mesh->surface_get_format(surface_i)); } + importer_mesh->merge_meta_from(*p_mesh); return importer_mesh; } @@ -458,7 +477,7 @@ Error GLTFDocument::_serialize_nodes(Ref<GLTFState> p_state) { if (extensions.is_empty()) { node.erase("extensions"); } - + _attach_meta_to_extras(gltf_node, node); nodes.push_back(node); } if (!nodes.is_empty()) { @@ -624,6 +643,10 @@ Error GLTFDocument::_parse_nodes(Ref<GLTFState> p_state) { } } + if (n.has("extras")) { + _attach_extras_to_meta(n["extras"], node); + } + if (n.has("children")) { const Array &children = n["children"]; for (int j = 0; j < children.size(); j++) { @@ -2727,6 +2750,8 @@ Error GLTFDocument::_serialize_meshes(Ref<GLTFState> p_state) { Dictionary e; e["targetNames"] = target_names; + gltf_mesh["extras"] = e; + _attach_meta_to_extras(import_mesh, gltf_mesh); weights.resize(target_names.size()); for (int name_i = 0; name_i < target_names.size(); name_i++) { @@ -2742,8 +2767,6 @@ Error GLTFDocument::_serialize_meshes(Ref<GLTFState> p_state) { ERR_FAIL_COND_V(target_names.size() != weights.size(), FAILED); - gltf_mesh["extras"] = e; - gltf_mesh["primitives"] = primitives; meshes.push_back(gltf_mesh); @@ -2776,6 +2799,7 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) { Array primitives = d["primitives"]; const Dictionary &extras = d.has("extras") ? (Dictionary)d["extras"] : Dictionary(); + _attach_extras_to_meta(extras, mesh); Ref<ImporterMesh> import_mesh; import_mesh.instantiate(); String mesh_name = "mesh"; @@ -4170,6 +4194,7 @@ Error GLTFDocument::_serialize_materials(Ref<GLTFState> p_state) { } d["extensions"] = extensions; + _attach_meta_to_extras(material, d); materials.push_back(d); } if (!materials.size()) { @@ -4372,6 +4397,10 @@ Error GLTFDocument::_parse_materials(Ref<GLTFState> p_state) { } } } + + if (material_dict.has("extras")) { + _attach_extras_to_meta(material_dict["extras"], material); + } p_state->materials.push_back(material); } @@ -5161,6 +5190,7 @@ ImporterMeshInstance3D *GLTFDocument::_generate_mesh_instance(Ref<GLTFState> p_s return mi; } mi->set_mesh(import_mesh); + import_mesh->merge_meta_from(*mesh); return mi; } @@ -5285,6 +5315,7 @@ void GLTFDocument::_convert_scene_node(Ref<GLTFState> p_state, Node *p_current, gltf_root = current_node_i; p_state->root_nodes.push_back(gltf_root); } + gltf_node->merge_meta_from(p_current); _create_gltf_node(p_state, p_current, current_node_i, p_gltf_parent, gltf_root, gltf_node); for (int node_i = 0; node_i < p_current->get_child_count(); node_i++) { _convert_scene_node(p_state, p_current->get_child(node_i), current_node_i, gltf_root); @@ -5676,6 +5707,8 @@ void GLTFDocument::_generate_scene_node(Ref<GLTFState> p_state, const GLTFNodeIn current_node->set_transform(gltf_node->transform); } + current_node->merge_meta_from(*gltf_node); + p_state->scene_nodes.insert(p_node_index, current_node); for (int i = 0; i < gltf_node->children.size(); ++i) { _generate_scene_node(p_state, gltf_node->children[i], current_node, p_scene_root); diff --git a/modules/gltf/tests/test_gltf_extras.h b/modules/gltf/tests/test_gltf_extras.h new file mode 100644 index 0000000000..96aadf3023 --- /dev/null +++ b/modules/gltf/tests/test_gltf_extras.h @@ -0,0 +1,165 @@ +/**************************************************************************/ +/* test_gltf_extras.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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 TEST_GLTF_EXTRAS_H +#define TEST_GLTF_EXTRAS_H + +#include "tests/test_macros.h" + +#ifdef TOOLS_ENABLED + +#include "core/os/os.h" +#include "editor/import/3d/resource_importer_scene.h" +#include "modules/gltf/editor/editor_scene_importer_gltf.h" +#include "modules/gltf/gltf_document.h" +#include "modules/gltf/gltf_state.h" +#include "scene/3d/mesh_instance_3d.h" +#include "scene/main/window.h" +#include "scene/resources/3d/primitive_meshes.h" +#include "scene/resources/material.h" +#include "scene/resources/packed_scene.h" + +namespace TestGltfExtras { + +static Node *_gltf_export_then_import(Node *p_root, String &p_tempfilebase) { + Ref<GLTFDocument> doc; + doc.instantiate(); + Ref<GLTFState> state; + state.instantiate(); + Error err = doc->append_from_scene(p_root, state, EditorSceneFormatImporter::IMPORT_USE_NAMED_SKIN_BINDS); + CHECK_MESSAGE(err == OK, "GLTF state generation failed."); + err = doc->write_to_filesystem(state, p_tempfilebase + ".gltf"); + CHECK_MESSAGE(err == OK, "Writing GLTF to cache dir failed."); + + // Setting up importers. + Ref<ResourceImporterScene> import_scene = memnew(ResourceImporterScene("PackedScene", true)); + ResourceFormatImporter::get_singleton()->add_importer(import_scene); + Ref<EditorSceneFormatImporterGLTF> import_gltf; + import_gltf.instantiate(); + ResourceImporterScene::add_scene_importer(import_gltf); + + // GTLF importer behaves differently outside of editor, it's too late to modify Engine::get_editor_hint + // as the registration of runtime extensions already happened, so remove them. See modules/gltf/register_types.cpp + GLTFDocument::unregister_all_gltf_document_extensions(); + + HashMap<StringName, Variant> options(20); + options["nodes/root_type"] = ""; + options["nodes/root_name"] = ""; + options["nodes/apply_root_scale"] = true; + options["nodes/root_scale"] = 1.0; + options["meshes/ensure_tangents"] = true; + options["meshes/generate_lods"] = false; + options["meshes/create_shadow_meshes"] = true; + options["meshes/light_baking"] = 1; + options["meshes/lightmap_texel_size"] = 0.2; + options["meshes/force_disable_compression"] = false; + options["skins/use_named_skins"] = true; + options["animation/import"] = true; + options["animation/fps"] = 30; + options["animation/trimming"] = false; + options["animation/remove_immutable_tracks"] = true; + options["import_script/path"] = ""; + options["_subresources"] = Dictionary(); + options["gltf/naming_version"] = 1; + + // Process gltf file, note that this generates `.scn` resource from the 2nd argument. + err = import_scene->import(p_tempfilebase + ".gltf", p_tempfilebase, options, nullptr, nullptr, nullptr); + CHECK_MESSAGE(err == OK, "GLTF import failed."); + ResourceImporterScene::remove_scene_importer(import_gltf); + + Ref<PackedScene> packed_scene = ResourceLoader::load(p_tempfilebase + ".scn", "", ResourceFormatLoader::CACHE_MODE_REPLACE, &err); + CHECK_MESSAGE(err == OK, "Loading scene failed."); + Node *p_scene = packed_scene->instantiate(); + return p_scene; +} + +TEST_CASE("[SceneTree][Node] GLTF test mesh and material meta export and import") { + // Setup scene. + Ref<StandardMaterial3D> original_material = memnew(StandardMaterial3D); + original_material->set_albedo(Color(1.0, .0, .0)); + original_material->set_name("material"); + Dictionary material_dict; + material_dict["node_type"] = "material"; + original_material->set_meta("extras", material_dict); + + Ref<PlaneMesh> original_meshdata = memnew(PlaneMesh); + original_meshdata->set_name("planemesh"); + Dictionary meshdata_dict; + meshdata_dict["node_type"] = "planemesh"; + original_meshdata->set_meta("extras", meshdata_dict); + original_meshdata->surface_set_material(0, original_material); + + MeshInstance3D *original_mesh_instance = memnew(MeshInstance3D); + original_mesh_instance->set_mesh(original_meshdata); + original_mesh_instance->set_name("mesh_instance_3d"); + Dictionary mesh_instance_dict; + mesh_instance_dict["node_type"] = "mesh_instance_3d"; + original_mesh_instance->set_meta("extras", mesh_instance_dict); + + Node3D *original = memnew(Node3D); + SceneTree::get_singleton()->get_root()->add_child(original); + original->add_child(original_mesh_instance); + original->set_name("node3d"); + Dictionary node_dict; + node_dict["node_type"] = "node3d"; + original->set_meta("extras", node_dict); + original->set_meta("meta_not_nested_under_extras", "should not propagate"); + + // Convert to GLFT and back. + String tempfile = OS::get_singleton()->get_cache_path().path_join("gltf_extras"); + Node *loaded = _gltf_export_then_import(original, tempfile); + + // Compare the results. + CHECK(loaded->get_name() == "node3d"); + CHECK(Dictionary(loaded->get_meta("extras")).size() == 1); + CHECK(Dictionary(loaded->get_meta("extras"))["node_type"] == "node3d"); + CHECK_FALSE(loaded->has_meta("meta_not_nested_under_extras")); + CHECK_FALSE(Dictionary(loaded->get_meta("extras")).has("meta_not_nested_under_extras")); + + MeshInstance3D *mesh_instance_3d = Object::cast_to<MeshInstance3D>(loaded->find_child("mesh_instance_3d", false, true)); + CHECK(mesh_instance_3d->get_name() == "mesh_instance_3d"); + CHECK(Dictionary(mesh_instance_3d->get_meta("extras"))["node_type"] == "mesh_instance_3d"); + + Ref<Mesh> mesh = mesh_instance_3d->get_mesh(); + CHECK(Dictionary(mesh->get_meta("extras"))["node_type"] == "planemesh"); + + Ref<Material> material = mesh->surface_get_material(0); + CHECK(material->get_name() == "material"); + CHECK(Dictionary(material->get_meta("extras"))["node_type"] == "material"); + + memdelete(original_mesh_instance); + memdelete(original); + memdelete(loaded); +} +} // namespace TestGltfExtras + +#endif // TOOLS_ENABLED + +#endif // TEST_GLTF_EXTRAS_H diff --git a/modules/minimp3/audio_stream_mp3.cpp b/modules/minimp3/audio_stream_mp3.cpp index 5720f844bb..394213963a 100644 --- a/modules/minimp3/audio_stream_mp3.cpp +++ b/modules/minimp3/audio_stream_mp3.cpp @@ -57,7 +57,7 @@ int AudioStreamPlaybackMP3::_mix_internal(AudioFrame *p_buffer, int p_frames) { mp3dec_frame_info_t frame_info; mp3d_sample_t *buf_frame = nullptr; - int samples_mixed = mp3dec_ex_read_frame(mp3d, &buf_frame, &frame_info, mp3_stream->channels); + int samples_mixed = mp3dec_ex_read_frame(&mp3d, &buf_frame, &frame_info, mp3_stream->channels); if (samples_mixed) { p_buffer[p_frames - todo] = AudioFrame(buf_frame[0], buf_frame[samples_mixed - 1]); @@ -70,7 +70,7 @@ int AudioStreamPlaybackMP3::_mix_internal(AudioFrame *p_buffer, int p_frames) { if (beat_loop && (int)frames_mixed >= beat_length_frames) { for (int i = 0; i < FADE_SIZE; i++) { - samples_mixed = mp3dec_ex_read_frame(mp3d, &buf_frame, &frame_info, mp3_stream->channels); + samples_mixed = mp3dec_ex_read_frame(&mp3d, &buf_frame, &frame_info, mp3_stream->channels); loop_fade[i] = AudioFrame(buf_frame[0], buf_frame[samples_mixed - 1]); if (!samples_mixed) { break; @@ -138,7 +138,7 @@ void AudioStreamPlaybackMP3::seek(double p_time) { } frames_mixed = uint32_t(mp3_stream->sample_rate * p_time); - mp3dec_ex_seek(mp3d, (uint64_t)frames_mixed * mp3_stream->channels); + mp3dec_ex_seek(&mp3d, (uint64_t)frames_mixed * mp3_stream->channels); } void AudioStreamPlaybackMP3::tag_used_streams() { @@ -181,10 +181,7 @@ Variant AudioStreamPlaybackMP3::get_parameter(const StringName &p_name) const { } AudioStreamPlaybackMP3::~AudioStreamPlaybackMP3() { - if (mp3d) { - mp3dec_ex_close(mp3d); - memfree(mp3d); - } + mp3dec_ex_close(&mp3d); } Ref<AudioStreamPlayback> AudioStreamMP3::instantiate_playback() { @@ -197,9 +194,8 @@ Ref<AudioStreamPlayback> AudioStreamMP3::instantiate_playback() { mp3s.instantiate(); mp3s->mp3_stream = Ref<AudioStreamMP3>(this); - mp3s->mp3d = (mp3dec_ex_t *)memalloc(sizeof(mp3dec_ex_t)); - int errorcode = mp3dec_ex_open_buf(mp3s->mp3d, data.ptr(), data_len, MP3D_SEEK_TO_SAMPLE); + int errorcode = mp3dec_ex_open_buf(&mp3s->mp3d, data.ptr(), data_len, MP3D_SEEK_TO_SAMPLE); mp3s->frames_mixed = 0; mp3s->active = false; @@ -224,15 +220,19 @@ void AudioStreamMP3::set_data(const Vector<uint8_t> &p_data) { int src_data_len = p_data.size(); const uint8_t *src_datar = p_data.ptr(); - mp3dec_ex_t mp3d; - int err = mp3dec_ex_open_buf(&mp3d, src_datar, src_data_len, MP3D_SEEK_TO_SAMPLE); - ERR_FAIL_COND_MSG(err || mp3d.info.hz == 0, "Failed to decode mp3 file. Make sure it is a valid mp3 audio file."); + mp3dec_ex_t *mp3d = memnew(mp3dec_ex_t); + int err = mp3dec_ex_open_buf(mp3d, src_datar, src_data_len, MP3D_SEEK_TO_SAMPLE); + if (err || mp3d->info.hz == 0) { + memdelete(mp3d); + ERR_FAIL_MSG("Failed to decode mp3 file. Make sure it is a valid mp3 audio file."); + } - channels = mp3d.info.channels; - sample_rate = mp3d.info.hz; - length = float(mp3d.samples) / (sample_rate * float(channels)); + channels = mp3d->info.channels; + sample_rate = mp3d->info.hz; + length = float(mp3d->samples) / (sample_rate * float(channels)); - mp3dec_ex_close(&mp3d); + mp3dec_ex_close(mp3d); + memdelete(mp3d); clear_data(); diff --git a/modules/minimp3/audio_stream_mp3.h b/modules/minimp3/audio_stream_mp3.h index 81e8f8633c..39d389b8cd 100644 --- a/modules/minimp3/audio_stream_mp3.h +++ b/modules/minimp3/audio_stream_mp3.h @@ -49,7 +49,7 @@ class AudioStreamPlaybackMP3 : public AudioStreamPlaybackResampled { bool looping_override = false; bool looping = false; - mp3dec_ex_t *mp3d = nullptr; + mp3dec_ex_t mp3d = {}; uint32_t frames_mixed = 0; bool active = false; int loops = 0; diff --git a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs index b699765b8e..032d067ae4 100644 --- a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs +++ b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs @@ -86,7 +86,7 @@ namespace GodotTools.BuildLogger WriteLine(line); - string errorLine = $@"error,{e.File.CsvEscape()},{e.LineNumber},{e.ColumnNumber}," + + string errorLine = $@"error,{e.File?.CsvEscape() ?? string.Empty},{e.LineNumber},{e.ColumnNumber}," + $"{e.Code?.CsvEscape() ?? string.Empty},{e.Message.CsvEscape()}," + $"{e.ProjectFile?.CsvEscape() ?? string.Empty}"; _issuesStreamWriter.WriteLine(errorLine); @@ -101,7 +101,7 @@ namespace GodotTools.BuildLogger WriteLine(line); - string warningLine = $@"warning,{e.File.CsvEscape()},{e.LineNumber},{e.ColumnNumber}," + + string warningLine = $@"warning,{e.File?.CsvEscape() ?? string.Empty},{e.LineNumber},{e.ColumnNumber}," + $"{e.Code?.CsvEscape() ?? string.Empty},{e.Message.CsvEscape()}," + $"{e.ProjectFile?.CsvEscape() ?? string.Empty}"; _issuesStreamWriter.WriteLine(warningLine); diff --git a/modules/navigation/2d/nav_mesh_generator_2d.cpp b/modules/navigation/2d/nav_mesh_generator_2d.cpp index 33b92f6266..78983187c7 100644 --- a/modules/navigation/2d/nav_mesh_generator_2d.cpp +++ b/modules/navigation/2d/nav_mesh_generator_2d.cpp @@ -87,57 +87,55 @@ void NavMeshGenerator2D::sync() { return; } - baking_navmesh_mutex.lock(); - generator_task_mutex.lock(); + MutexLock baking_navmesh_lock(baking_navmesh_mutex); + { + MutexLock generator_task_lock(generator_task_mutex); - LocalVector<WorkerThreadPool::TaskID> finished_task_ids; + LocalVector<WorkerThreadPool::TaskID> finished_task_ids; - for (KeyValue<WorkerThreadPool::TaskID, NavMeshGeneratorTask2D *> &E : generator_tasks) { - if (WorkerThreadPool::get_singleton()->is_task_completed(E.key)) { - WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key); - finished_task_ids.push_back(E.key); + for (KeyValue<WorkerThreadPool::TaskID, NavMeshGeneratorTask2D *> &E : generator_tasks) { + if (WorkerThreadPool::get_singleton()->is_task_completed(E.key)) { + WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key); + finished_task_ids.push_back(E.key); - NavMeshGeneratorTask2D *generator_task = E.value; - DEV_ASSERT(generator_task->status == NavMeshGeneratorTask2D::TaskStatus::BAKING_FINISHED); + NavMeshGeneratorTask2D *generator_task = E.value; + DEV_ASSERT(generator_task->status == NavMeshGeneratorTask2D::TaskStatus::BAKING_FINISHED); - baking_navmeshes.erase(generator_task->navigation_mesh); - if (generator_task->callback.is_valid()) { - generator_emit_callback(generator_task->callback); + baking_navmeshes.erase(generator_task->navigation_mesh); + if (generator_task->callback.is_valid()) { + generator_emit_callback(generator_task->callback); + } + memdelete(generator_task); } - memdelete(generator_task); } - } - for (WorkerThreadPool::TaskID finished_task_id : finished_task_ids) { - generator_tasks.erase(finished_task_id); + for (WorkerThreadPool::TaskID finished_task_id : finished_task_ids) { + generator_tasks.erase(finished_task_id); + } } - - generator_task_mutex.unlock(); - baking_navmesh_mutex.unlock(); } void NavMeshGenerator2D::cleanup() { - baking_navmesh_mutex.lock(); - generator_task_mutex.lock(); + MutexLock baking_navmesh_lock(baking_navmesh_mutex); + { + MutexLock generator_task_lock(generator_task_mutex); - baking_navmeshes.clear(); + baking_navmeshes.clear(); - for (KeyValue<WorkerThreadPool::TaskID, NavMeshGeneratorTask2D *> &E : generator_tasks) { - WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key); - NavMeshGeneratorTask2D *generator_task = E.value; - memdelete(generator_task); - } - generator_tasks.clear(); + for (KeyValue<WorkerThreadPool::TaskID, NavMeshGeneratorTask2D *> &E : generator_tasks) { + WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key); + NavMeshGeneratorTask2D *generator_task = E.value; + memdelete(generator_task); + } + generator_tasks.clear(); - generator_rid_rwlock.write_lock(); - for (NavMeshGeometryParser2D *parser : generator_parsers) { - generator_parser_owner.free(parser->self); + generator_rid_rwlock.write_lock(); + for (NavMeshGeometryParser2D *parser : generator_parsers) { + generator_parser_owner.free(parser->self); + } + generator_parsers.clear(); + generator_rid_rwlock.write_unlock(); } - generator_parsers.clear(); - generator_rid_rwlock.write_unlock(); - - generator_task_mutex.unlock(); - baking_navmesh_mutex.unlock(); } void NavMeshGenerator2D::finish() { @@ -212,7 +210,7 @@ void NavMeshGenerator2D::bake_from_source_geometry_data_async(Ref<NavigationPoly baking_navmeshes.insert(p_navigation_mesh); baking_navmesh_mutex.unlock(); - generator_task_mutex.lock(); + MutexLock generator_task_lock(generator_task_mutex); NavMeshGeneratorTask2D *generator_task = memnew(NavMeshGeneratorTask2D); generator_task->navigation_mesh = p_navigation_mesh; generator_task->source_geometry_data = p_source_geometry_data; @@ -220,14 +218,11 @@ void NavMeshGenerator2D::bake_from_source_geometry_data_async(Ref<NavigationPoly generator_task->status = NavMeshGeneratorTask2D::TaskStatus::BAKING_STARTED; generator_task->thread_task_id = WorkerThreadPool::get_singleton()->add_native_task(&NavMeshGenerator2D::generator_thread_bake, generator_task, NavMeshGenerator2D::baking_use_high_priority_threads, "NavMeshGeneratorBake2D"); generator_tasks.insert(generator_task->thread_task_id, generator_task); - generator_task_mutex.unlock(); } bool NavMeshGenerator2D::is_baking(Ref<NavigationPolygon> p_navigation_polygon) { - baking_navmesh_mutex.lock(); - bool baking = baking_navmeshes.has(p_navigation_polygon); - baking_navmesh_mutex.unlock(); - return baking; + MutexLock baking_navmesh_lock(baking_navmesh_mutex); + return baking_navmeshes.has(p_navigation_polygon); } void NavMeshGenerator2D::generator_thread_bake(void *p_arg) { diff --git a/modules/navigation/3d/nav_mesh_generator_3d.cpp b/modules/navigation/3d/nav_mesh_generator_3d.cpp index d17724baa0..e92a9d304b 100644 --- a/modules/navigation/3d/nav_mesh_generator_3d.cpp +++ b/modules/navigation/3d/nav_mesh_generator_3d.cpp @@ -100,57 +100,55 @@ void NavMeshGenerator3D::sync() { return; } - baking_navmesh_mutex.lock(); - generator_task_mutex.lock(); + MutexLock baking_navmesh_lock(baking_navmesh_mutex); + { + MutexLock generator_task_lock(generator_task_mutex); - LocalVector<WorkerThreadPool::TaskID> finished_task_ids; + LocalVector<WorkerThreadPool::TaskID> finished_task_ids; - for (KeyValue<WorkerThreadPool::TaskID, NavMeshGeneratorTask3D *> &E : generator_tasks) { - if (WorkerThreadPool::get_singleton()->is_task_completed(E.key)) { - WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key); - finished_task_ids.push_back(E.key); + for (KeyValue<WorkerThreadPool::TaskID, NavMeshGeneratorTask3D *> &E : generator_tasks) { + if (WorkerThreadPool::get_singleton()->is_task_completed(E.key)) { + WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key); + finished_task_ids.push_back(E.key); - NavMeshGeneratorTask3D *generator_task = E.value; - DEV_ASSERT(generator_task->status == NavMeshGeneratorTask3D::TaskStatus::BAKING_FINISHED); + NavMeshGeneratorTask3D *generator_task = E.value; + DEV_ASSERT(generator_task->status == NavMeshGeneratorTask3D::TaskStatus::BAKING_FINISHED); - baking_navmeshes.erase(generator_task->navigation_mesh); - if (generator_task->callback.is_valid()) { - generator_emit_callback(generator_task->callback); + baking_navmeshes.erase(generator_task->navigation_mesh); + if (generator_task->callback.is_valid()) { + generator_emit_callback(generator_task->callback); + } + memdelete(generator_task); } - memdelete(generator_task); } - } - for (WorkerThreadPool::TaskID finished_task_id : finished_task_ids) { - generator_tasks.erase(finished_task_id); + for (WorkerThreadPool::TaskID finished_task_id : finished_task_ids) { + generator_tasks.erase(finished_task_id); + } } - - generator_task_mutex.unlock(); - baking_navmesh_mutex.unlock(); } void NavMeshGenerator3D::cleanup() { - baking_navmesh_mutex.lock(); - generator_task_mutex.lock(); + MutexLock baking_navmesh_lock(baking_navmesh_mutex); + { + MutexLock generator_task_lock(generator_task_mutex); - baking_navmeshes.clear(); + baking_navmeshes.clear(); - for (KeyValue<WorkerThreadPool::TaskID, NavMeshGeneratorTask3D *> &E : generator_tasks) { - WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key); - NavMeshGeneratorTask3D *generator_task = E.value; - memdelete(generator_task); - } - generator_tasks.clear(); + for (KeyValue<WorkerThreadPool::TaskID, NavMeshGeneratorTask3D *> &E : generator_tasks) { + WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key); + NavMeshGeneratorTask3D *generator_task = E.value; + memdelete(generator_task); + } + generator_tasks.clear(); - generator_rid_rwlock.write_lock(); - for (NavMeshGeometryParser3D *parser : generator_parsers) { - generator_parser_owner.free(parser->self); + generator_rid_rwlock.write_lock(); + for (NavMeshGeometryParser3D *parser : generator_parsers) { + generator_parser_owner.free(parser->self); + } + generator_parsers.clear(); + generator_rid_rwlock.write_unlock(); } - generator_parsers.clear(); - generator_rid_rwlock.write_unlock(); - - generator_task_mutex.unlock(); - baking_navmesh_mutex.unlock(); } void NavMeshGenerator3D::finish() { @@ -226,7 +224,7 @@ void NavMeshGenerator3D::bake_from_source_geometry_data_async(Ref<NavigationMesh baking_navmeshes.insert(p_navigation_mesh); baking_navmesh_mutex.unlock(); - generator_task_mutex.lock(); + MutexLock generator_task_lock(generator_task_mutex); NavMeshGeneratorTask3D *generator_task = memnew(NavMeshGeneratorTask3D); generator_task->navigation_mesh = p_navigation_mesh; generator_task->source_geometry_data = p_source_geometry_data; @@ -234,14 +232,11 @@ void NavMeshGenerator3D::bake_from_source_geometry_data_async(Ref<NavigationMesh generator_task->status = NavMeshGeneratorTask3D::TaskStatus::BAKING_STARTED; generator_task->thread_task_id = WorkerThreadPool::get_singleton()->add_native_task(&NavMeshGenerator3D::generator_thread_bake, generator_task, NavMeshGenerator3D::baking_use_high_priority_threads, SNAME("NavMeshGeneratorBake3D")); generator_tasks.insert(generator_task->thread_task_id, generator_task); - generator_task_mutex.unlock(); } bool NavMeshGenerator3D::is_baking(Ref<NavigationMesh> p_navigation_mesh) { - baking_navmesh_mutex.lock(); - bool baking = baking_navmeshes.has(p_navigation_mesh); - baking_navmesh_mutex.unlock(); - return baking; + MutexLock baking_navmesh_lock(baking_navmesh_mutex); + return baking_navmeshes.has(p_navigation_mesh); } void NavMeshGenerator3D::generator_thread_bake(void *p_arg) { diff --git a/platform/android/display_server_android.cpp b/platform/android/display_server_android.cpp index 8dc0e869d0..5bb520bd73 100644 --- a/platform/android/display_server_android.cpp +++ b/platform/android/display_server_android.cpp @@ -455,11 +455,15 @@ Size2i DisplayServerAndroid::window_get_size_with_decorations(DisplayServer::Win } void DisplayServerAndroid::window_set_mode(DisplayServer::WindowMode p_mode, DisplayServer::WindowID p_window) { - // Not supported on Android. + OS_Android::get_singleton()->get_godot_java()->enable_immersive_mode(p_mode == WINDOW_MODE_FULLSCREEN || p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN); } DisplayServer::WindowMode DisplayServerAndroid::window_get_mode(DisplayServer::WindowID p_window) const { - return WINDOW_MODE_FULLSCREEN; + if (OS_Android::get_singleton()->get_godot_java()->is_in_immersive_mode()) { + return WINDOW_MODE_FULLSCREEN; + } else { + return WINDOW_MODE_MAXIMIZED; + } } bool DisplayServerAndroid::window_is_maximize_allowed(DisplayServer::WindowID p_window) const { diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp index 0fdaca4839..f8ac591a78 100644 --- a/platform/android/export/export_plugin.cpp +++ b/platform/android/export/export_plugin.cpp @@ -2002,7 +2002,7 @@ String EditorExportPlatformAndroid::get_device_architecture(int p_index) const { return devices[p_index].architecture; } -Error EditorExportPlatformAndroid::run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) { +Error EditorExportPlatformAndroid::run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) { ERR_FAIL_INDEX_V(p_device, devices.size(), ERR_INVALID_PARAMETER); String can_export_error; @@ -2024,11 +2024,11 @@ Error EditorExportPlatformAndroid::run(const Ref<EditorExportPreset> &p_preset, } const bool use_wifi_for_remote_debug = EDITOR_GET("export/android/use_wifi_for_remote_debug"); - const bool use_remote = (p_debug_flags & DEBUG_FLAG_REMOTE_DEBUG) || (p_debug_flags & DEBUG_FLAG_DUMB_CLIENT); + const bool use_remote = p_debug_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG) || p_debug_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT); const bool use_reverse = devices[p_device].api_level >= 21 && !use_wifi_for_remote_debug; if (use_reverse) { - p_debug_flags |= DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST; + p_debug_flags.set_flag(DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST); } String tmp_export_path = EditorPaths::get_singleton()->get_cache_dir().path_join("tmpexport." + uitos(OS::get_singleton()->get_unix_time()) + ".apk"); @@ -2107,7 +2107,7 @@ Error EditorExportPlatformAndroid::run(const Ref<EditorExportPreset> &p_preset, OS::get_singleton()->execute(adb, args, &output, &rv, true); print_verbose(output); - if (p_debug_flags & DEBUG_FLAG_REMOTE_DEBUG) { + if (p_debug_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG)) { int dbg_port = EDITOR_GET("network/debug/remote_port"); args.clear(); args.push_back("-s"); @@ -2122,7 +2122,7 @@ Error EditorExportPlatformAndroid::run(const Ref<EditorExportPreset> &p_preset, print_line("Reverse result: " + itos(rv)); } - if (p_debug_flags & DEBUG_FLAG_DUMB_CLIENT) { + if (p_debug_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT)) { int fs_port = EDITOR_GET("filesystem/file_server/port"); args.clear(); @@ -2667,7 +2667,7 @@ Error EditorExportPlatformAndroid::save_apk_expansion_file(const Ref<EditorExpor return err; } -void EditorExportPlatformAndroid::get_command_line_flags(const Ref<EditorExportPreset> &p_preset, const String &p_path, int p_flags, Vector<uint8_t> &r_command_line_flags) { +void EditorExportPlatformAndroid::get_command_line_flags(const Ref<EditorExportPreset> &p_preset, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags, Vector<uint8_t> &r_command_line_flags) { String cmdline = p_preset->get("command_line/extra_args"); Vector<String> command_line_strings = cmdline.strip_edges().split(" "); for (int i = 0; i < command_line_strings.size(); i++) { @@ -2677,7 +2677,7 @@ void EditorExportPlatformAndroid::get_command_line_flags(const Ref<EditorExportP } } - gen_export_flags(command_line_strings, p_flags); + command_line_strings.append_array(gen_export_flags(p_flags)); bool apk_expansion = p_preset->get("apk_expansion/enable"); if (apk_expansion) { @@ -2700,7 +2700,7 @@ void EditorExportPlatformAndroid::get_command_line_flags(const Ref<EditorExportP bool immersive = p_preset->get("screen/immersive_mode"); if (immersive) { - command_line_strings.push_back("--use_immersive"); + command_line_strings.push_back("--fullscreen"); } bool debug_opengl = p_preset->get("graphics/opengl_debug"); @@ -3000,13 +3000,13 @@ bool EditorExportPlatformAndroid::_is_clean_build_required(const Ref<EditorExpor return have_plugins_changed || has_build_dir_changed || first_build; } -Error EditorExportPlatformAndroid::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) { +Error EditorExportPlatformAndroid::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) { int export_format = int(p_preset->get("gradle_build/export_format")); bool should_sign = p_preset->get("package/signed"); return export_project_helper(p_preset, p_debug, p_path, export_format, should_sign, p_flags); } -Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int export_format, bool should_sign, int p_flags) { +Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int export_format, bool should_sign, BitField<EditorExportPlatform::DebugFlags> p_flags) { ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags); const String base_dir = p_path.get_base_dir(); @@ -3022,7 +3022,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP bool use_gradle_build = bool(p_preset->get("gradle_build/use_gradle_build")); String gradle_build_directory = use_gradle_build ? ExportTemplateManager::get_android_build_directory(p_preset) : ""; - bool p_give_internet = p_flags & (DEBUG_FLAG_DUMB_CLIENT | DEBUG_FLAG_REMOTE_DEBUG); + bool p_give_internet = p_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT) || p_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG); bool apk_expansion = p_preset->get("apk_expansion/enable"); Vector<ABI> enabled_abis = get_enabled_abis(p_preset); @@ -3127,7 +3127,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP user_data.assets_directory = assets_directory; user_data.libs_directory = gradle_build_directory.path_join("libs"); user_data.debug = p_debug; - if (p_flags & DEBUG_FLAG_DUMB_CLIENT) { + if (p_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT)) { err = export_project_files(p_preset, p_debug, ignore_apk_file, &user_data, copy_gradle_so); } else { err = export_project_files(p_preset, p_debug, rename_and_store_file_in_gradle_project, &user_data, copy_gradle_so); @@ -3500,7 +3500,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP } err = OK; - if (p_flags & DEBUG_FLAG_DUMB_CLIENT) { + if (p_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT)) { APKExportData ed; ed.ep = &ep; ed.apk = unaligned_apk; diff --git a/platform/android/export/export_plugin.h b/platform/android/export/export_plugin.h index 97bbd0c7bc..708288fbf4 100644 --- a/platform/android/export/export_plugin.h +++ b/platform/android/export/export_plugin.h @@ -214,7 +214,7 @@ public: virtual String get_device_architecture(int p_index) const override; - virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) override; + virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) override; virtual Ref<Texture2D> get_run_icon() const override; @@ -242,7 +242,7 @@ public: Error save_apk_expansion_file(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path); - void get_command_line_flags(const Ref<EditorExportPreset> &p_preset, const String &p_path, int p_flags, Vector<uint8_t> &r_command_line_flags); + void get_command_line_flags(const Ref<EditorExportPreset> &p_preset, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags, Vector<uint8_t> &r_command_line_flags); Error sign_apk(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &export_path, EditorProgress &ep); @@ -253,9 +253,9 @@ public: static String join_list(const List<String> &p_parts, const String &p_separator); static String join_abis(const Vector<ABI> &p_parts, const String &p_separator, bool p_use_arch); - virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override; + virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override; - Error export_project_helper(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int export_format, bool should_sign, int p_flags); + Error export_project_helper(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int export_format, bool should_sign, BitField<EditorExportPlatform::DebugFlags> p_flags); virtual void get_platform_features(List<String> *r_features) const override; diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/EditorMessageDispatcher.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/EditorMessageDispatcher.kt index ba1185d647..b16e62149a 100644 --- a/platform/android/java/editor/src/main/java/org/godotengine/editor/EditorMessageDispatcher.kt +++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/EditorMessageDispatcher.kt @@ -176,7 +176,7 @@ internal class EditorMessageDispatcher(private val editor: GodotEditor) { registerMessenger(senderId, senderMessenger) // Register ourselves to the sender so that it can communicate with us. - registerSelfTo(pm, senderMessenger, editor.getEditorId()) + registerSelfTo(pm, senderMessenger, editor.getEditorWindowInfo().windowId) } /** @@ -185,7 +185,7 @@ internal class EditorMessageDispatcher(private val editor: GodotEditor) { */ fun getMessageDispatcherPayload(): Bundle { return Bundle().apply { - putInt(KEY_EDITOR_ID, editor.getEditorId()) + putInt(KEY_EDITOR_ID, editor.getEditorWindowInfo().windowId) putParcelable(KEY_EDITOR_MESSENGER, Messenger(dispatcherHandler)) } } diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt index 5d6da06f97..1995a38c2a 100644 --- a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt +++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt @@ -40,6 +40,7 @@ import android.content.pm.PackageManager import android.os.* import android.util.Log import android.view.View +import android.view.WindowManager import android.widget.Toast import androidx.annotation.CallSuper import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen @@ -78,6 +79,8 @@ open class GodotEditor : GodotActivity() { protected val EXTRA_LAUNCH_IN_PIP = "launch_in_pip_requested" // Command line arguments + private const val FULLSCREEN_ARG = "--fullscreen" + private const val FULLSCREEN_ARG_SHORT = "-f" private const val EDITOR_ARG = "--editor" private const val EDITOR_ARG_SHORT = "-e" private const val EDITOR_PROJECT_MANAGER_ARG = "--project-manager" @@ -116,11 +119,16 @@ open class GodotEditor : GodotActivity() { override fun getGodotAppLayout() = R.layout.godot_editor_layout - internal open fun getEditorId() = EDITOR_MAIN_INFO.windowId + internal open fun getEditorWindowInfo() = EDITOR_MAIN_INFO override fun onCreate(savedInstanceState: Bundle?) { installSplashScreen() + // Prevent the editor window from showing in the display cutout + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && getEditorWindowInfo() == EDITOR_MAIN_INFO) { + window.attributes.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER + } + // We exclude certain permissions from the set we request at startup, as they'll be // requested on demand based on use-cases. PermissionsUtil.requestManifestPermissions(this, setOf(Manifest.permission.RECORD_AUDIO)) @@ -213,10 +221,24 @@ open class GodotEditor : GodotActivity() { } protected fun getNewGodotInstanceIntent(editorWindowInfo: EditorWindowInfo, args: Array<String>): Intent { + val updatedArgs = if (editorWindowInfo == EDITOR_MAIN_INFO && + godot?.isInImmersiveMode() == true && + !args.contains(FULLSCREEN_ARG) && + !args.contains(FULLSCREEN_ARG_SHORT) + ) { + // If we're launching an editor window (project manager or editor) and we're in + // fullscreen mode, we want to remain in fullscreen mode. + // This doesn't apply to the play / game window since for that window fullscreen is + // controlled by the game logic. + args + FULLSCREEN_ARG + } else { + args + } + val newInstance = Intent() .setComponent(ComponentName(this, editorWindowInfo.windowClassName)) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - .putExtra(EXTRA_COMMAND_LINE_PARAMS, args) + .putExtra(EXTRA_COMMAND_LINE_PARAMS, updatedArgs) val launchPolicy = resolveLaunchPolicyIfNeeded(editorWindowInfo.launchPolicy) val isPiPAvailable = if (editorWindowInfo.supportsPiPMode && hasPiPSystemFeature()) { @@ -235,7 +257,7 @@ open class GodotEditor : GodotActivity() { } } else if (launchPolicy == LaunchPolicy.SAME) { if (isPiPAvailable && - (args.contains(BREAKPOINTS_ARG) || args.contains(BREAKPOINTS_ARG_SHORT))) { + (updatedArgs.contains(BREAKPOINTS_ARG) || updatedArgs.contains(BREAKPOINTS_ARG_SHORT))) { Log.v(TAG, "Launching in PiP mode because of breakpoints") newInstance.putExtra(EXTRA_LAUNCH_IN_PIP, true) } diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt index 33fcbf9030..6b4bf255f2 100644 --- a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt +++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt @@ -128,7 +128,7 @@ class GodotGame : GodotEditor() { override fun getGodotAppLayout() = R.layout.godot_game_layout - override fun getEditorId() = RUN_GAME_INFO.windowId + override fun getEditorWindowInfo() = RUN_GAME_INFO override fun overrideOrientationRequest() = false diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt index 49e8ffb008..38bd336e2d 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt @@ -42,13 +42,16 @@ import android.hardware.Sensor import android.hardware.SensorManager import android.os.* import android.util.Log +import android.util.TypedValue import android.view.* import android.widget.FrameLayout import androidx.annotation.Keep import androidx.annotation.StringRes import androidx.core.view.ViewCompat +import androidx.core.view.WindowCompat import androidx.core.view.WindowInsetsAnimationCompat import androidx.core.view.WindowInsetsCompat +import androidx.core.view.WindowInsetsControllerCompat import com.google.android.vending.expansion.downloader.* import org.godotengine.godot.error.Error import org.godotengine.godot.input.GodotEditText @@ -105,36 +108,26 @@ class Godot(private val context: Context) { GodotPluginRegistry.getPluginRegistry() } - private val accelerometer_enabled = AtomicBoolean(false) + private val accelerometerEnabled = AtomicBoolean(false) private val mAccelerometer: Sensor? by lazy { mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) } - private val gravity_enabled = AtomicBoolean(false) + private val gravityEnabled = AtomicBoolean(false) private val mGravity: Sensor? by lazy { mSensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY) } - private val magnetometer_enabled = AtomicBoolean(false) + private val magnetometerEnabled = AtomicBoolean(false) private val mMagnetometer: Sensor? by lazy { mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD) } - private val gyroscope_enabled = AtomicBoolean(false) + private val gyroscopeEnabled = AtomicBoolean(false) private val mGyroscope: Sensor? by lazy { mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE) } - private val uiChangeListener = View.OnSystemUiVisibilityChangeListener { visibility: Int -> - if (visibility and View.SYSTEM_UI_FLAG_FULLSCREEN == 0) { - val decorView = requireActivity().window.decorView - decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or - View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or - View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or - View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or - View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY - }} - val tts = GodotTTS(context) val directoryAccessHandler = DirectoryAccessHandler(context) val fileAccessHandler = FileAccessHandler(context) @@ -185,7 +178,7 @@ class Godot(private val context: Context) { private var xrMode = XRMode.REGULAR private var expansionPackPath: String = "" private var useApkExpansion = false - private var useImmersive = false + private val useImmersive = AtomicBoolean(false) private var useDebugOpengl = false private var darkMode = false @@ -254,15 +247,9 @@ class Godot(private val context: Context) { xrMode = XRMode.OPENXR } else if (commandLine[i] == "--debug_opengl") { useDebugOpengl = true - } else if (commandLine[i] == "--use_immersive") { - useImmersive = true - window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or - View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or - View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or - View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or // hide nav bar - View.SYSTEM_UI_FLAG_FULLSCREEN or // hide status bar - View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY - registerUiChangeListener() + } else if (commandLine[i] == "--fullscreen") { + useImmersive.set(true) + newArgs.add(commandLine[i]) } else if (commandLine[i] == "--use_apk_expansion") { useApkExpansion = true } else if (hasExtra && commandLine[i] == "--apk_expansion_md5") { @@ -336,6 +323,54 @@ class Godot(private val context: Context) { } /** + * Toggle immersive mode. + * Must be called from the UI thread. + */ + private fun enableImmersiveMode(enabled: Boolean, override: Boolean = false) { + val activity = getActivity() ?: return + val window = activity.window ?: return + + if (!useImmersive.compareAndSet(!enabled, enabled) && !override) { + return + } + + WindowCompat.setDecorFitsSystemWindows(window, !enabled) + val controller = WindowInsetsControllerCompat(window, window.decorView) + if (enabled) { + controller.hide(WindowInsetsCompat.Type.systemBars()) + controller.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE + } else { + val fullScreenThemeValue = TypedValue() + val hasStatusBar = if (activity.theme.resolveAttribute(android.R.attr.windowFullscreen, fullScreenThemeValue, true) && fullScreenThemeValue.type == TypedValue.TYPE_INT_BOOLEAN) { + fullScreenThemeValue.data == 0 + } else { + // Fallback to checking the editor build + !isEditorBuild() + } + + val types = if (hasStatusBar) { + WindowInsetsCompat.Type.navigationBars() or WindowInsetsCompat.Type.statusBars() + } else { + WindowInsetsCompat.Type.navigationBars() + } + controller.show(types) + } + } + + /** + * Invoked from the render thread to toggle the immersive mode. + */ + @Keep + private fun nativeEnableImmersiveMode(enabled: Boolean) { + runOnUiThread { + enableImmersiveMode(enabled) + } + } + + @Keep + fun isInImmersiveMode() = useImmersive.get() + + /** * Initializes the native layer of the Godot engine. * * This must be preceded by [onCreate] and followed by [onInitRenderView] to complete @@ -552,15 +587,7 @@ class Godot(private val context: Context) { renderView?.onActivityResumed() registerSensorsIfNeeded() - if (useImmersive) { - val window = requireActivity().window - window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or - View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or - View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or - View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or // hide nav bar - View.SYSTEM_UI_FLAG_FULLSCREEN or // hide status bar - View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY - } + enableImmersiveMode(useImmersive.get(), true) for (plugin in pluginRegistry.allPlugins) { plugin.onMainResume() } @@ -571,16 +598,16 @@ class Godot(private val context: Context) { return } - if (accelerometer_enabled.get() && mAccelerometer != null) { + if (accelerometerEnabled.get() && mAccelerometer != null) { mSensorManager.registerListener(godotInputHandler, mAccelerometer, SensorManager.SENSOR_DELAY_GAME) } - if (gravity_enabled.get() && mGravity != null) { + if (gravityEnabled.get() && mGravity != null) { mSensorManager.registerListener(godotInputHandler, mGravity, SensorManager.SENSOR_DELAY_GAME) } - if (magnetometer_enabled.get() && mMagnetometer != null) { + if (magnetometerEnabled.get() && mMagnetometer != null) { mSensorManager.registerListener(godotInputHandler, mMagnetometer, SensorManager.SENSOR_DELAY_GAME) } - if (gyroscope_enabled.get() && mGyroscope != null) { + if (gyroscopeEnabled.get() && mGyroscope != null) { mSensorManager.registerListener(godotInputHandler, mGyroscope, SensorManager.SENSOR_DELAY_GAME) } } @@ -696,10 +723,10 @@ class Godot(private val context: Context) { Log.v(TAG, "OnGodotMainLoopStarted") godotMainLoopStarted.set(true) - accelerometer_enabled.set(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/sensors/enable_accelerometer"))) - gravity_enabled.set(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/sensors/enable_gravity"))) - gyroscope_enabled.set(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/sensors/enable_gyroscope"))) - magnetometer_enabled.set(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/sensors/enable_magnetometer"))) + accelerometerEnabled.set(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/sensors/enable_accelerometer"))) + gravityEnabled.set(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/sensors/enable_gravity"))) + gyroscopeEnabled.set(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/sensors/enable_gyroscope"))) + magnetometerEnabled.set(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/sensors/enable_magnetometer"))) runOnUiThread { registerSensorsIfNeeded() @@ -724,11 +751,6 @@ class Godot(private val context: Context) { primaryHost?.onGodotRestartRequested(this) } - private fun registerUiChangeListener() { - val decorView = requireActivity().window.decorView - decorView.setOnSystemUiVisibilityChangeListener(uiChangeListener) - } - fun alert( @StringRes messageResId: Int, @StringRes titleResId: Int, diff --git a/platform/android/java_godot_wrapper.cpp b/platform/android/java_godot_wrapper.cpp index f1759af54a..d3b30e4589 100644 --- a/platform/android/java_godot_wrapper.cpp +++ b/platform/android/java_godot_wrapper.cpp @@ -86,6 +86,8 @@ GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_ _has_feature = p_env->GetMethodID(godot_class, "hasFeature", "(Ljava/lang/String;)Z"); _sign_apk = p_env->GetMethodID(godot_class, "nativeSignApk", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I"); _verify_apk = p_env->GetMethodID(godot_class, "nativeVerifyApk", "(Ljava/lang/String;)I"); + _enable_immersive_mode = p_env->GetMethodID(godot_class, "nativeEnableImmersiveMode", "(Z)V"); + _is_in_immersive_mode = p_env->GetMethodID(godot_class, "isInImmersiveMode", "()Z"); } GodotJavaWrapper::~GodotJavaWrapper() { @@ -465,3 +467,21 @@ Error GodotJavaWrapper::verify_apk(const String &p_apk_path) { return ERR_UNCONFIGURED; } } + +void GodotJavaWrapper::enable_immersive_mode(bool p_enabled) { + if (_enable_immersive_mode) { + JNIEnv *env = get_jni_env(); + ERR_FAIL_NULL(env); + env->CallVoidMethod(godot_instance, _enable_immersive_mode, p_enabled); + } +} + +bool GodotJavaWrapper::is_in_immersive_mode() { + if (_is_in_immersive_mode) { + JNIEnv *env = get_jni_env(); + ERR_FAIL_NULL_V(env, false); + return env->CallBooleanMethod(godot_instance, _is_in_immersive_mode); + } else { + return false; + } +} diff --git a/platform/android/java_godot_wrapper.h b/platform/android/java_godot_wrapper.h index 6b66565981..51d7f98541 100644 --- a/platform/android/java_godot_wrapper.h +++ b/platform/android/java_godot_wrapper.h @@ -77,6 +77,8 @@ private: jmethodID _has_feature = nullptr; jmethodID _sign_apk = nullptr; jmethodID _verify_apk = nullptr; + jmethodID _enable_immersive_mode = nullptr; + jmethodID _is_in_immersive_mode = nullptr; public: GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_godot_instance); @@ -122,6 +124,9 @@ public: // Sign and verify apks Error sign_apk(const String &p_input_path, const String &p_output_path, const String &p_keystore_path, const String &p_keystore_user, const String &p_keystore_password); Error verify_apk(const String &p_apk_path); + + void enable_immersive_mode(bool p_enabled); + bool is_in_immersive_mode(); }; #endif // JAVA_GODOT_WRAPPER_H diff --git a/platform/ios/export/export_plugin.cpp b/platform/ios/export/export_plugin.cpp index e4b5392c4e..b99e825540 100644 --- a/platform/ios/export/export_plugin.cpp +++ b/platform/ios/export/export_plugin.cpp @@ -2017,11 +2017,11 @@ Error EditorExportPlatformIOS::_export_ios_plugins(const Ref<EditorExportPreset> return OK; } -Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) { +Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) { return _export_project_helper(p_preset, p_debug, p_path, p_flags, false, false); } -Error EditorExportPlatformIOS::_export_project_helper(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags, bool p_simulator, bool p_oneclick) { +Error EditorExportPlatformIOS::_export_project_helper(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags, bool p_simulator, bool p_oneclick) { ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags); const String dest_dir = p_path.get_base_dir() + "/"; @@ -2983,7 +2983,7 @@ void EditorExportPlatformIOS::_update_preset_status() { } #endif -Error EditorExportPlatformIOS::run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) { +Error EditorExportPlatformIOS::run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) { #ifdef MACOS_ENABLED ERR_FAIL_INDEX_V(p_device, devices.size(), ERR_INVALID_PARAMETER); @@ -3029,11 +3029,11 @@ Error EditorExportPlatformIOS::run(const Ref<EditorExportPreset> &p_preset, int String host = EDITOR_GET("network/debug/remote_host"); int remote_port = (int)EDITOR_GET("network/debug/remote_port"); - if (p_debug_flags & DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST) { + if (p_debug_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST)) { host = "localhost"; } - if (p_debug_flags & DEBUG_FLAG_DUMB_CLIENT) { + if (p_debug_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT)) { int port = EDITOR_GET("filesystem/file_server/port"); String passwd = EDITOR_GET("filesystem/file_server/password"); cmd_args_list.push_back("--remote-fs"); @@ -3044,7 +3044,7 @@ Error EditorExportPlatformIOS::run(const Ref<EditorExportPreset> &p_preset, int } } - if (p_debug_flags & DEBUG_FLAG_REMOTE_DEBUG) { + if (p_debug_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG)) { cmd_args_list.push_back("--remote-debug"); cmd_args_list.push_back(get_debug_protocol() + host + ":" + String::num(remote_port)); @@ -3066,11 +3066,11 @@ Error EditorExportPlatformIOS::run(const Ref<EditorExportPreset> &p_preset, int } } - if (p_debug_flags & DEBUG_FLAG_VIEW_COLLISIONS) { + if (p_debug_flags.has_flag(DEBUG_FLAG_VIEW_COLLISIONS)) { cmd_args_list.push_back("--debug-collisions"); } - if (p_debug_flags & DEBUG_FLAG_VIEW_NAVIGATION) { + if (p_debug_flags.has_flag(DEBUG_FLAG_VIEW_NAVIGATION)) { cmd_args_list.push_back("--debug-navigation"); } diff --git a/platform/ios/export/export_plugin.h b/platform/ios/export/export_plugin.h index 1964906c27..db7c0553dd 100644 --- a/platform/ios/export/export_plugin.h +++ b/platform/ios/export/export_plugin.h @@ -146,7 +146,7 @@ class EditorExportPlatformIOS : public EditorExportPlatform { Error _export_additional_assets(const Ref<EditorExportPreset> &p_preset, const String &p_out_dir, const Vector<SharedObject> &p_libraries, Vector<IOSExportAsset> &r_exported_assets); Error _export_ios_plugins(const Ref<EditorExportPreset> &p_preset, IOSConfigData &p_config_data, const String &dest_dir, Vector<IOSExportAsset> &r_exported_assets, bool p_debug); - Error _export_project_helper(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags, bool p_simulator, bool p_oneclick); + Error _export_project_helper(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags, bool p_simulator, bool p_oneclick); bool is_package_name_valid(const String &p_package, String *r_error = nullptr) const; @@ -169,7 +169,7 @@ public: virtual Ref<ImageTexture> get_option_icon(int p_index) const override; virtual String get_option_label(int p_index) const override; virtual String get_option_tooltip(int p_index) const override; - virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) override; + virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) override; virtual bool poll_export() override { bool dc = devices_changed.is_set(); @@ -202,7 +202,7 @@ public: return list; } - virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override; + virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override; virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const override; virtual bool has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const override; diff --git a/platform/linuxbsd/export/export_plugin.cpp b/platform/linuxbsd/export/export_plugin.cpp index 0032b898d2..69ba742f72 100644 --- a/platform/linuxbsd/export/export_plugin.cpp +++ b/platform/linuxbsd/export/export_plugin.cpp @@ -60,7 +60,7 @@ Error EditorExportPlatformLinuxBSD::_export_debug_script(const Ref<EditorExportP return OK; } -Error EditorExportPlatformLinuxBSD::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) { +Error EditorExportPlatformLinuxBSD::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) { String custom_debug = p_preset->get("custom_template/debug"); String custom_release = p_preset->get("custom_template/release"); String arch = p_preset->get("binary_format/architecture"); @@ -458,7 +458,7 @@ void EditorExportPlatformLinuxBSD::cleanup() { cleanup_commands.clear(); } -Error EditorExportPlatformLinuxBSD::run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) { +Error EditorExportPlatformLinuxBSD::run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) { cleanup(); if (p_device) { // Stop command, cleanup only. return OK; @@ -512,8 +512,7 @@ Error EditorExportPlatformLinuxBSD::run(const Ref<EditorExportPreset> &p_preset, String cmd_args; { - Vector<String> cmd_args_list; - gen_debug_flags(cmd_args_list, p_debug_flags); + Vector<String> cmd_args_list = gen_export_flags(p_debug_flags); for (int i = 0; i < cmd_args_list.size(); i++) { if (i != 0) { cmd_args += " "; @@ -522,7 +521,7 @@ Error EditorExportPlatformLinuxBSD::run(const Ref<EditorExportPreset> &p_preset, } } - const bool use_remote = (p_debug_flags & DEBUG_FLAG_REMOTE_DEBUG) || (p_debug_flags & DEBUG_FLAG_DUMB_CLIENT); + const bool use_remote = p_debug_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG) || p_debug_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT); int dbg_port = EditorSettings::get_singleton()->get("network/debug/remote_port"); print_line("Creating temporary directory..."); diff --git a/platform/linuxbsd/export/export_plugin.h b/platform/linuxbsd/export/export_plugin.h index bbc55b82ce..1d9ef01d1a 100644 --- a/platform/linuxbsd/export/export_plugin.h +++ b/platform/linuxbsd/export/export_plugin.h @@ -76,7 +76,7 @@ public: virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override; virtual bool get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option) const override; virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const override; - virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override; + virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override; virtual String get_template_file_name(const String &p_target, const String &p_arch) const override; virtual Error fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size) override; virtual bool is_executable(const String &p_path) const override; @@ -87,7 +87,7 @@ public: virtual int get_options_count() const override; virtual String get_option_label(int p_index) const override; virtual String get_option_tooltip(int p_index) const override; - virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) override; + virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) override; virtual void cleanup() override; EditorExportPlatformLinuxBSD(); diff --git a/platform/linuxbsd/freedesktop_portal_desktop.cpp b/platform/linuxbsd/freedesktop_portal_desktop.cpp index 2b98fda0d5..94a748e414 100644 --- a/platform/linuxbsd/freedesktop_portal_desktop.cpp +++ b/platform/linuxbsd/freedesktop_portal_desktop.cpp @@ -210,7 +210,15 @@ void FreeDesktopPortalDesktop::append_dbus_dict_filters(DBusMessageIter *p_iter, append_dbus_string(&struct_iter, p_filter_names[i]); dbus_message_iter_open_container(&struct_iter, DBUS_TYPE_ARRAY, "(us)", &array_iter); - const String &flt = p_filter_exts[i]; + const String &flt_orig = p_filter_exts[i]; + String flt; + for (int j = 0; j < flt_orig.length(); j++) { + if (is_unicode_letter(flt_orig[j])) { + flt += vformat("[%c%c]", String::char_lowercase(flt_orig[j]), String::char_uppercase(flt_orig[j])); + } else { + flt += flt_orig[j]; + } + } int filter_slice_count = flt.get_slice_count(","); for (int j = 0; j < filter_slice_count; j++) { dbus_message_iter_open_container(&array_iter, DBUS_TYPE_STRUCT, nullptr, &array_struct_iter); diff --git a/platform/macos/export/export_plugin.cpp b/platform/macos/export/export_plugin.cpp index 290b0082fc..8372600ae9 100644 --- a/platform/macos/export/export_plugin.cpp +++ b/platform/macos/export/export_plugin.cpp @@ -1505,7 +1505,7 @@ Error EditorExportPlatformMacOS::_export_debug_script(const Ref<EditorExportPres return OK; } -Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) { +Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) { ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags); const String base_dir = p_path.get_base_dir(); @@ -2511,7 +2511,7 @@ void EditorExportPlatformMacOS::cleanup() { cleanup_commands.clear(); } -Error EditorExportPlatformMacOS::run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) { +Error EditorExportPlatformMacOS::run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) { cleanup(); if (p_device) { // Stop command, cleanup only. return OK; @@ -2573,8 +2573,7 @@ Error EditorExportPlatformMacOS::run(const Ref<EditorExportPreset> &p_preset, in String cmd_args; { - Vector<String> cmd_args_list; - gen_debug_flags(cmd_args_list, p_debug_flags); + Vector<String> cmd_args_list = gen_export_flags(p_debug_flags); for (int i = 0; i < cmd_args_list.size(); i++) { if (i != 0) { cmd_args += " "; @@ -2583,7 +2582,7 @@ Error EditorExportPlatformMacOS::run(const Ref<EditorExportPreset> &p_preset, in } } - const bool use_remote = (p_debug_flags & DEBUG_FLAG_REMOTE_DEBUG) || (p_debug_flags & DEBUG_FLAG_DUMB_CLIENT); + const bool use_remote = p_debug_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG) || p_debug_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT); int dbg_port = EditorSettings::get_singleton()->get("network/debug/remote_port"); print_line("Creating temporary directory..."); diff --git a/platform/macos/export/export_plugin.h b/platform/macos/export/export_plugin.h index 062a2e5f95..5457c687d3 100644 --- a/platform/macos/export/export_plugin.h +++ b/platform/macos/export/export_plugin.h @@ -147,7 +147,7 @@ public: virtual bool is_executable(const String &p_path) const override; virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override; - virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override; + virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override; virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const override; virtual bool has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const override; @@ -167,7 +167,7 @@ public: virtual int get_options_count() const override; virtual String get_option_label(int p_index) const override; virtual String get_option_tooltip(int p_index) const override; - virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) override; + virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) override; virtual void cleanup() override; EditorExportPlatformMacOS(); diff --git a/platform/web/export/export_plugin.cpp b/platform/web/export/export_plugin.cpp index d8c1b6033d..5faab74d7b 100644 --- a/platform/web/export/export_plugin.cpp +++ b/platform/web/export/export_plugin.cpp @@ -130,15 +130,14 @@ void EditorExportPlatformWeb::_replace_strings(const HashMap<String, String> &p_ } } -void EditorExportPlatformWeb::_fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, int p_flags, const Vector<SharedObject> p_shared_objects, const Dictionary &p_file_sizes) { +void EditorExportPlatformWeb::_fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, BitField<EditorExportPlatform::DebugFlags> p_flags, const Vector<SharedObject> p_shared_objects, const Dictionary &p_file_sizes) { // Engine.js config Dictionary config; Array libs; for (int i = 0; i < p_shared_objects.size(); i++) { libs.push_back(p_shared_objects[i].path.get_file()); } - Vector<String> flags; - gen_export_flags(flags, p_flags & (~DEBUG_FLAG_DUMB_CLIENT)); + Vector<String> flags = gen_export_flags(p_flags & (~DEBUG_FLAG_DUMB_CLIENT)); Array args; for (int i = 0; i < flags.size(); i++) { args.push_back(flags[i]); @@ -450,7 +449,7 @@ List<String> EditorExportPlatformWeb::get_binary_extensions(const Ref<EditorExpo return list; } -Error EditorExportPlatformWeb::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) { +Error EditorExportPlatformWeb::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) { ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags); const String custom_debug = p_preset->get("custom_template/debug"); @@ -744,7 +743,7 @@ String EditorExportPlatformWeb::get_option_tooltip(int p_index) const { return ""; } -Error EditorExportPlatformWeb::run(const Ref<EditorExportPreset> &p_preset, int p_option, int p_debug_flags) { +Error EditorExportPlatformWeb::run(const Ref<EditorExportPreset> &p_preset, int p_option, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) { const uint16_t bind_port = EDITOR_GET("export/web/http_port"); // Resolve host if needed. const String bind_host = EDITOR_GET("export/web/http_host"); diff --git a/platform/web/export/export_plugin.h b/platform/web/export/export_plugin.h index 2f67d8107f..3c743e2e74 100644 --- a/platform/web/export/export_plugin.h +++ b/platform/web/export/export_plugin.h @@ -98,7 +98,7 @@ class EditorExportPlatformWeb : public EditorExportPlatform { Error _extract_template(const String &p_template, const String &p_dir, const String &p_name, bool pwa); void _replace_strings(const HashMap<String, String> &p_replaces, Vector<uint8_t> &r_template); - void _fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, int p_flags, const Vector<SharedObject> p_shared_objects, const Dictionary &p_file_sizes); + void _fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, BitField<EditorExportPlatform::DebugFlags> p_flags, const Vector<SharedObject> p_shared_objects, const Dictionary &p_file_sizes); Error _add_manifest_icon(const String &p_path, const String &p_icon, int p_size, Array &r_arr); Error _build_pwa(const Ref<EditorExportPreset> &p_preset, const String p_path, const Vector<SharedObject> &p_shared_objects); Error _write_or_error(const uint8_t *p_content, int p_len, String p_path); @@ -120,14 +120,14 @@ public: virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const override; virtual bool has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const override; virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override; - virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override; + virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override; virtual bool poll_export() override; virtual int get_options_count() const override; virtual String get_option_label(int p_index) const override; virtual String get_option_tooltip(int p_index) const override; virtual Ref<ImageTexture> get_option_icon(int p_index) const override; - virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_option, int p_debug_flags) override; + virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_option, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) override; virtual Ref<Texture2D> get_run_icon() const override; virtual void get_platform_features(List<String> *r_features) const override { diff --git a/platform/windows/export/export_plugin.cpp b/platform/windows/export/export_plugin.cpp index b465bd4ecd..8d3f4bb269 100644 --- a/platform/windows/export/export_plugin.cpp +++ b/platform/windows/export/export_plugin.cpp @@ -167,7 +167,7 @@ Error EditorExportPlatformWindows::sign_shared_object(const Ref<EditorExportPres } } -Error EditorExportPlatformWindows::modify_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) { +Error EditorExportPlatformWindows::modify_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) { if (p_preset->get("application/modify_resources")) { _rcedit_add_data(p_preset, p_path, false); String wrapper_path = p_path.get_basename() + ".console.exe"; @@ -178,7 +178,7 @@ Error EditorExportPlatformWindows::modify_template(const Ref<EditorExportPreset> return OK; } -Error EditorExportPlatformWindows::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) { +Error EditorExportPlatformWindows::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) { String custom_debug = p_preset->get("custom_template/debug"); String custom_release = p_preset->get("custom_template/release"); String arch = p_preset->get("binary_format/architecture"); @@ -996,7 +996,7 @@ void EditorExportPlatformWindows::cleanup() { cleanup_commands.clear(); } -Error EditorExportPlatformWindows::run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) { +Error EditorExportPlatformWindows::run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) { cleanup(); if (p_device) { // Stop command, cleanup only. return OK; @@ -1050,8 +1050,7 @@ Error EditorExportPlatformWindows::run(const Ref<EditorExportPreset> &p_preset, String cmd_args; { - Vector<String> cmd_args_list; - gen_debug_flags(cmd_args_list, p_debug_flags); + Vector<String> cmd_args_list = gen_export_flags(p_debug_flags); for (int i = 0; i < cmd_args_list.size(); i++) { if (i != 0) { cmd_args += " "; @@ -1060,7 +1059,7 @@ Error EditorExportPlatformWindows::run(const Ref<EditorExportPreset> &p_preset, } } - const bool use_remote = (p_debug_flags & DEBUG_FLAG_REMOTE_DEBUG) || (p_debug_flags & DEBUG_FLAG_DUMB_CLIENT); + const bool use_remote = p_debug_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG) || p_debug_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT); int dbg_port = EditorSettings::get_singleton()->get("network/debug/remote_port"); print_line("Creating temporary directory..."); diff --git a/platform/windows/export/export_plugin.h b/platform/windows/export/export_plugin.h index 6ccb4a15a7..e86aac83d4 100644 --- a/platform/windows/export/export_plugin.h +++ b/platform/windows/export/export_plugin.h @@ -76,8 +76,8 @@ class EditorExportPlatformWindows : public EditorExportPlatformPC { String _get_exe_arch(const String &p_path) const; public: - virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override; - virtual Error modify_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) override; + virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override; + virtual Error modify_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) override; virtual Error sign_shared_object(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path) override; virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override; virtual void get_export_options(List<ExportOption> *r_options) const override; @@ -95,7 +95,7 @@ public: virtual int get_options_count() const override; virtual String get_option_label(int p_index) const override; virtual String get_option_tooltip(int p_index) const override; - virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) override; + virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) override; virtual void cleanup() override; EditorExportPlatformWindows(); diff --git a/scene/animation/tween.cpp b/scene/animation/tween.cpp index f8bbd704f4..e1fd8abede 100644 --- a/scene/animation/tween.cpp +++ b/scene/animation/tween.cpp @@ -579,6 +579,7 @@ bool PropertyTweener::step(double &r_delta) { Object *target_instance = ObjectDB::get_instance(target); if (!target_instance) { + _finish(); return false; } elapsed_time += r_delta; @@ -706,6 +707,7 @@ bool CallbackTweener::step(double &r_delta) { } if (!callback.is_valid()) { + _finish(); return false; } @@ -770,6 +772,7 @@ bool MethodTweener::step(double &r_delta) { } if (!callback.is_valid()) { + _finish(); return false; } diff --git a/scene/resources/3d/fog_material.cpp b/scene/resources/3d/fog_material.cpp index 5e4f1970ee..92246b50db 100644 --- a/scene/resources/3d/fog_material.cpp +++ b/scene/resources/3d/fog_material.cpp @@ -138,7 +138,7 @@ void FogMaterial::cleanup_shader() { } void FogMaterial::_update_shader() { - shader_mutex.lock(); + MutexLock shader_lock(shader_mutex); if (shader.is_null()) { shader = RS::get_singleton()->shader_create(); @@ -165,7 +165,6 @@ void fog() { } )"); } - shader_mutex.unlock(); } FogMaterial::FogMaterial() { diff --git a/scene/resources/3d/sky_material.cpp b/scene/resources/3d/sky_material.cpp index 640261d615..c470db5d7f 100644 --- a/scene/resources/3d/sky_material.cpp +++ b/scene/resources/3d/sky_material.cpp @@ -269,7 +269,7 @@ void ProceduralSkyMaterial::cleanup_shader() { } void ProceduralSkyMaterial::_update_shader() { - shader_mutex.lock(); + MutexLock shader_lock(shader_mutex); if (shader_cache[0].is_null()) { for (int i = 0; i < 2; i++) { shader_cache[i] = RS::get_singleton()->shader_create(); @@ -354,7 +354,6 @@ void sky() { i ? "render_mode use_debanding;" : "")); } } - shader_mutex.unlock(); } ProceduralSkyMaterial::ProceduralSkyMaterial() { @@ -463,7 +462,7 @@ void PanoramaSkyMaterial::cleanup_shader() { } void PanoramaSkyMaterial::_update_shader() { - shader_mutex.lock(); + MutexLock shader_lock(shader_mutex); if (shader_cache[0].is_null()) { for (int i = 0; i < 2; i++) { shader_cache[i] = RS::get_singleton()->shader_create(); @@ -484,8 +483,6 @@ void sky() { i ? "filter_linear" : "filter_nearest")); } } - - shader_mutex.unlock(); } PanoramaSkyMaterial::PanoramaSkyMaterial() { @@ -692,7 +689,7 @@ void PhysicalSkyMaterial::cleanup_shader() { } void PhysicalSkyMaterial::_update_shader() { - shader_mutex.lock(); + MutexLock shader_lock(shader_mutex); if (shader_cache[0].is_null()) { for (int i = 0; i < 2; i++) { shader_cache[i] = RS::get_singleton()->shader_create(); @@ -785,8 +782,6 @@ void sky() { i ? "render_mode use_debanding;" : "")); } } - - shader_mutex.unlock(); } PhysicalSkyMaterial::PhysicalSkyMaterial() { diff --git a/scene/resources/audio_stream_wav.cpp b/scene/resources/audio_stream_wav.cpp index de6a069567..b293a91123 100644 --- a/scene/resources/audio_stream_wav.cpp +++ b/scene/resources/audio_stream_wav.cpp @@ -179,17 +179,17 @@ void AudioStreamPlaybackWAV::do_resample(const Depth *p_src, AudioFrame *p_dst, if (pos != p_qoa->cache_pos) { // Prevents triple decoding on lower mix rates. for (int i = 0; i < 2; i++) { // Sign operations prevent triple decoding on backward loops, maxing prevents pop. - uint32_t interp_pos = MIN(pos + (i * sign) + (sign < 0), p_qoa->desc->samples - 1); + uint32_t interp_pos = MIN(pos + (i * sign) + (sign < 0), p_qoa->desc.samples - 1); uint32_t new_data_ofs = 8 + interp_pos / QOA_FRAME_LEN * p_qoa->frame_len; if (p_qoa->data_ofs != new_data_ofs) { p_qoa->data_ofs = new_data_ofs; const uint8_t *src_ptr = (const uint8_t *)base->data; src_ptr += p_qoa->data_ofs + AudioStreamWAV::DATA_PAD; - qoa_decode_frame(src_ptr, p_qoa->frame_len, p_qoa->desc, p_qoa->dec, &p_qoa->dec_len); + qoa_decode_frame(src_ptr, p_qoa->frame_len, &p_qoa->desc, p_qoa->dec, &p_qoa->dec_len); } - uint32_t dec_idx = (interp_pos % QOA_FRAME_LEN) * p_qoa->desc->channels; + uint32_t dec_idx = (interp_pos % QOA_FRAME_LEN) * p_qoa->desc.channels; if ((sign > 0 && i == 0) || (sign < 0 && i == 1)) { final = p_qoa->dec[dec_idx]; @@ -286,7 +286,7 @@ int AudioStreamPlaybackWAV::mix(AudioFrame *p_buffer, float p_rate_scale, int p_ len *= 2; break; case AudioStreamWAV::FORMAT_QOA: - len = qoa.desc->samples * qoa.desc->channels; + len = qoa.desc.samples * qoa.desc.channels; break; } @@ -484,10 +484,6 @@ void AudioStreamPlaybackWAV::set_sample_playback(const Ref<AudioSamplePlayback> AudioStreamPlaybackWAV::AudioStreamPlaybackWAV() {} AudioStreamPlaybackWAV::~AudioStreamPlaybackWAV() { - if (qoa.desc) { - memfree(qoa.desc); - } - if (qoa.dec) { memfree(qoa.dec); } @@ -557,7 +553,7 @@ double AudioStreamWAV::get_length() const { len *= 2; break; case AudioStreamWAV::FORMAT_QOA: - qoa_desc desc = { 0, 0, 0, { { { 0 }, { 0 } } } }; + qoa_desc desc = {}; qoa_decode_header((uint8_t *)data + DATA_PAD, data_bytes, &desc); len = desc.samples * desc.channels; break; @@ -697,12 +693,11 @@ Ref<AudioStreamPlayback> AudioStreamWAV::instantiate_playback() { sample->base = Ref<AudioStreamWAV>(this); if (format == AudioStreamWAV::FORMAT_QOA) { - sample->qoa.desc = (qoa_desc *)memalloc(sizeof(qoa_desc)); - uint32_t ffp = qoa_decode_header((uint8_t *)data + DATA_PAD, data_bytes, sample->qoa.desc); + uint32_t ffp = qoa_decode_header((uint8_t *)data + DATA_PAD, data_bytes, &sample->qoa.desc); ERR_FAIL_COND_V(ffp != 8, Ref<AudioStreamPlaybackWAV>()); - sample->qoa.frame_len = qoa_max_frame_size(sample->qoa.desc); - int samples_len = (sample->qoa.desc->samples > QOA_FRAME_LEN ? QOA_FRAME_LEN : sample->qoa.desc->samples); - int alloc_len = sample->qoa.desc->channels * samples_len * sizeof(int16_t); + sample->qoa.frame_len = qoa_max_frame_size(&sample->qoa.desc); + int samples_len = (sample->qoa.desc.samples > QOA_FRAME_LEN ? QOA_FRAME_LEN : sample->qoa.desc.samples); + int alloc_len = sample->qoa.desc.channels * samples_len * sizeof(int16_t); sample->qoa.dec = (int16_t *)memalloc(alloc_len); } diff --git a/scene/resources/audio_stream_wav.h b/scene/resources/audio_stream_wav.h index 806db675b6..47aa10e790 100644 --- a/scene/resources/audio_stream_wav.h +++ b/scene/resources/audio_stream_wav.h @@ -59,7 +59,7 @@ class AudioStreamPlaybackWAV : public AudioStreamPlayback { } ima_adpcm[2]; struct QOA_State { - qoa_desc *desc = nullptr; + qoa_desc desc = {}; uint32_t data_ofs = 0; uint32_t frame_len = 0; int16_t *dec = nullptr; diff --git a/tests/core/io/test_json_native.h b/tests/core/io/test_json_native.h new file mode 100644 index 0000000000..819078ac57 --- /dev/null +++ b/tests/core/io/test_json_native.h @@ -0,0 +1,160 @@ +/**************************************************************************/ +/* test_json_native.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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 TEST_JSON_NATIVE_H +#define TEST_JSON_NATIVE_H + +#include "core/io/json.h" + +namespace TestJSONNative { + +bool compare_variants(Variant variant_1, Variant variant_2, int depth = 0) { + if (depth > 100) { + return false; + } + if (variant_1.get_type() == Variant::RID && variant_2.get_type() == Variant::RID) { + return true; + } + if (variant_1.get_type() == Variant::CALLABLE || variant_2.get_type() == Variant::CALLABLE) { + return true; + } + + List<PropertyInfo> variant_1_properties; + variant_1.get_property_list(&variant_1_properties); + List<PropertyInfo> variant_2_properties; + variant_2.get_property_list(&variant_2_properties); + + if (variant_1_properties.size() != variant_2_properties.size()) { + return false; + } + + for (List<PropertyInfo>::Element *E = variant_1_properties.front(); E; E = E->next()) { + String name = E->get().name; + Variant variant_1_value = variant_1.get(name); + Variant variant_2_value = variant_2.get(name); + + if (!compare_variants(variant_1_value, variant_2_value, depth + 1)) { + return false; + } + } + + return true; +} + +TEST_CASE("[JSON][Native][SceneTree] Conversion between native and JSON formats") { + for (int variant_i = 0; variant_i < Variant::VARIANT_MAX; variant_i++) { + Variant::Type type = static_cast<Variant::Type>(variant_i); + Variant native_data; + Callable::CallError error; + + if (type == Variant::Type::INT || type == Variant::Type::FLOAT) { + Variant value = int64_t(INT64_MAX); + const Variant *args[] = { &value }; + Variant::construct(type, native_data, args, 1, error); + } else if (type == Variant::Type::OBJECT) { + Ref<JSON> json = memnew(JSON); + native_data = json; + } else if (type == Variant::Type::DICTIONARY) { + Dictionary dictionary; + dictionary["key"] = "value"; + native_data = dictionary; + } else if (type == Variant::Type::ARRAY) { + Array array; + array.push_back("element1"); + array.push_back("element2"); + native_data = array; + } else if (type == Variant::Type::PACKED_BYTE_ARRAY) { + PackedByteArray packed_array; + packed_array.push_back(1); + packed_array.push_back(2); + native_data = packed_array; + } else if (type == Variant::Type::PACKED_INT32_ARRAY) { + PackedInt32Array packed_array; + packed_array.push_back(INT32_MIN); + packed_array.push_back(INT32_MAX); + native_data = packed_array; + } else if (type == Variant::Type::PACKED_INT64_ARRAY) { + PackedInt64Array packed_array; + packed_array.push_back(INT64_MIN); + packed_array.push_back(INT64_MAX); + native_data = packed_array; + } else if (type == Variant::Type::PACKED_FLOAT32_ARRAY) { + PackedFloat32Array packed_array; + packed_array.push_back(FLT_MIN); + packed_array.push_back(FLT_MAX); + native_data = packed_array; + } else if (type == Variant::Type::PACKED_FLOAT64_ARRAY) { + PackedFloat64Array packed_array; + packed_array.push_back(DBL_MIN); + packed_array.push_back(DBL_MAX); + native_data = packed_array; + } else if (type == Variant::Type::PACKED_STRING_ARRAY) { + PackedStringArray packed_array; + packed_array.push_back("string1"); + packed_array.push_back("string2"); + native_data = packed_array; + } else if (type == Variant::Type::PACKED_VECTOR2_ARRAY) { + PackedVector2Array packed_array; + Vector2 vector(1.0, 2.0); + packed_array.push_back(vector); + native_data = packed_array; + } else if (type == Variant::Type::PACKED_VECTOR3_ARRAY) { + PackedVector3Array packed_array; + Vector3 vector(1.0, 2.0, 3.0); + packed_array.push_back(vector); + native_data = packed_array; + } else if (type == Variant::Type::PACKED_COLOR_ARRAY) { + PackedColorArray packed_array; + Color color(1.0, 1.0, 1.0); + packed_array.push_back(color); + native_data = packed_array; + } else if (type == Variant::Type::PACKED_VECTOR4_ARRAY) { + PackedVector4Array packed_array; + Vector4 vector(1.0, 2.0, 3.0, 4.0); + packed_array.push_back(vector); + native_data = packed_array; + } else { + Variant::construct(type, native_data, nullptr, 0, error); + } + Variant json_converted_from_native = JSON::from_native(native_data, true, true); + Variant variant_native_converted = JSON::to_native(json_converted_from_native, true, true); + CHECK_MESSAGE(compare_variants(native_data, variant_native_converted), + vformat("Conversion from native to JSON type %s and back successful. \nNative: %s \nNative Converted: %s \nError: %s\nConversion from native to JSON type %s successful: %s", + Variant::get_type_name(type), + native_data, + variant_native_converted, + itos(error.error), + Variant::get_type_name(type), + json_converted_from_native)); + } +} +} // namespace TestJSONNative + +#endif // TEST_JSON_NATIVE_H diff --git a/tests/core/object/test_object.h b/tests/core/object/test_object.h index 57bc65328a..f1bb62cb70 100644 --- a/tests/core/object/test_object.h +++ b/tests/core/object/test_object.h @@ -174,6 +174,31 @@ TEST_CASE("[Object] Metadata") { CHECK_MESSAGE( meta_list2.size() == 0, "The metadata list should contain 0 items after removing all metadata items."); + + Object other; + object.set_meta("conflicting_meta", "string"); + object.set_meta("not_conflicting_meta", 123); + other.set_meta("conflicting_meta", Color(0, 1, 0)); + other.set_meta("other_meta", "other"); + object.merge_meta_from(&other); + + CHECK_MESSAGE( + Color(object.get_meta("conflicting_meta")).is_equal_approx(Color(0, 1, 0)), + "String meta should be overwritten with Color after merging."); + + CHECK_MESSAGE( + int(object.get_meta("not_conflicting_meta")) == 123, + "Not conflicting meta on destination should be kept intact."); + + CHECK_MESSAGE( + object.get_meta("other_meta", String()) == "other", + "Not conflicting meta name on source should merged."); + + List<StringName> meta_list3; + object.get_meta_list(&meta_list3); + CHECK_MESSAGE( + meta_list3.size() == 3, + "The metadata list should contain 3 items after merging meta from two objects."); } TEST_CASE("[Object] Construction") { diff --git a/tests/core/string/test_string.h b/tests/core/string/test_string.h index b47e5b1eb9..a9f615af84 100644 --- a/tests/core/string/test_string.h +++ b/tests/core/string/test_string.h @@ -433,6 +433,19 @@ TEST_CASE("[String] Insertion") { String s = "Who is Frederic?"; s = s.insert(s.find("?"), " Chopin"); CHECK(s == "Who is Frederic Chopin?"); + + s = "foobar"; + CHECK(s.insert(0, "X") == "Xfoobar"); + CHECK(s.insert(-100, "X") == "foobar"); + CHECK(s.insert(6, "X") == "foobarX"); + CHECK(s.insert(100, "X") == "foobarX"); + CHECK(s.insert(2, "") == "foobar"); + + s = ""; + CHECK(s.insert(0, "abc") == "abc"); + CHECK(s.insert(100, "abc") == "abc"); + CHECK(s.insert(-100, "abc") == ""); + CHECK(s.insert(0, "") == ""); } TEST_CASE("[String] Erasing") { @@ -1811,13 +1824,25 @@ TEST_CASE("[String] SHA1/SHA256/MD5") { } TEST_CASE("[String] Join") { - String s = ", "; + String comma = ", "; + String empty = ""; Vector<String> parts; + + CHECK(comma.join(parts) == ""); + CHECK(empty.join(parts) == ""); + parts.push_back("One"); + CHECK(comma.join(parts) == "One"); + CHECK(empty.join(parts) == "One"); + parts.push_back("B"); parts.push_back("C"); - String t = s.join(parts); - CHECK(t == "One, B, C"); + CHECK(comma.join(parts) == "One, B, C"); + CHECK(empty.join(parts) == "OneBC"); + + parts.push_back(""); + CHECK(comma.join(parts) == "One, B, C, "); + CHECK(empty.join(parts) == "OneBC"); } TEST_CASE("[String] Is_*") { diff --git a/tests/test_main.cpp b/tests/test_main.cpp index 46714a2627..7e1c431a3c 100644 --- a/tests/test_main.cpp +++ b/tests/test_main.cpp @@ -48,6 +48,7 @@ #include "tests/core/io/test_image.h" #include "tests/core/io/test_ip.h" #include "tests/core/io/test_json.h" +#include "tests/core/io/test_json_native.h" #include "tests/core/io/test_marshalls.h" #include "tests/core/io/test_pck_packer.h" #include "tests/core/io/test_resource.h" |