diff options
106 files changed, 1009 insertions, 516 deletions
diff --git a/SConstruct b/SConstruct index 3fabc4706f..8e9a536bdc 100644 --- a/SConstruct +++ b/SConstruct @@ -200,7 +200,10 @@ opts.Add(EnumVariable("arch", "CPU architecture", "auto", ["auto"] + architectur opts.Add(BoolVariable("dev_build", "Developer build with dev-only debugging code (DEV_ENABLED)", False)) opts.Add( EnumVariable( - "optimize", "Optimization level", "speed_trace", ("none", "custom", "debug", "speed", "speed_trace", "size") + "optimize", + "Optimization level (by default inferred from 'target' and 'dev_build')", + "auto", + ("auto", "none", "custom", "debug", "speed", "speed_trace", "size"), ) ) opts.Add(BoolVariable("debug_symbols", "Build with debugging symbols", False)) @@ -466,14 +469,15 @@ env.editor_build = env["target"] == "editor" env.dev_build = env["dev_build"] env.debug_features = env["target"] in ["editor", "template_debug"] -if env.dev_build: - opt_level = "none" -elif env.debug_features: - opt_level = "speed_trace" -else: # Release - opt_level = "speed" +if env["optimize"] == "auto": + if env.dev_build: + opt_level = "none" + elif env.debug_features: + opt_level = "speed_trace" + else: # Release + opt_level = "speed" + env["optimize"] = ARGUMENTS.get("optimize", opt_level) -env["optimize"] = ARGUMENTS.get("optimize", opt_level) env["debug_symbols"] = methods.get_cmdline_bool("debug_symbols", env.dev_build) if env.editor_build: diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp index 768540a0fa..eac1a66be7 100644 --- a/core/config/project_settings.cpp +++ b/core/config/project_settings.cpp @@ -1515,6 +1515,7 @@ ProjectSettings::ProjectSettings() { GLOBAL_DEF_BASIC(PropertyInfo(Variant::STRING, "display/window/stretch/scale_mode", PROPERTY_HINT_ENUM, "fractional,integer"), "fractional"); GLOBAL_DEF(PropertyInfo(Variant::INT, "debug/settings/profiler/max_functions", PROPERTY_HINT_RANGE, "128,65535,1"), 16384); + GLOBAL_DEF_RST(PropertyInfo(Variant::INT, "debug/settings/profiler/max_timestamp_query_elements", PROPERTY_HINT_RANGE, "256,65535,1"), 256); GLOBAL_DEF(PropertyInfo(Variant::BOOL, "compression/formats/zstd/long_distance_matching"), Compression::zstd_long_distance_matching); GLOBAL_DEF(PropertyInfo(Variant::INT, "compression/formats/zstd/compression_level", PROPERTY_HINT_RANGE, "1,22,1"), Compression::zstd_level); diff --git a/core/input/input.cpp b/core/input/input.cpp index 56f616fac4..ec0303df06 100644 --- a/core/input/input.cpp +++ b/core/input/input.cpp @@ -758,12 +758,13 @@ void Input::_parse_input_event_impl(const Ref<InputEvent> &p_event, bool p_is_em bool was_pressed = action_state.cache.pressed; _update_action_cache(E.key, action_state); + // As input may come in part way through a physics tick, the earliest we can react to it is the next physics tick. if (action_state.cache.pressed && !was_pressed) { - action_state.pressed_physics_frame = Engine::get_singleton()->get_physics_frames(); + action_state.pressed_physics_frame = Engine::get_singleton()->get_physics_frames() + 1; action_state.pressed_process_frame = Engine::get_singleton()->get_process_frames(); } if (!action_state.cache.pressed && was_pressed) { - action_state.released_physics_frame = Engine::get_singleton()->get_physics_frames(); + action_state.released_physics_frame = Engine::get_singleton()->get_physics_frames() + 1; action_state.released_process_frame = Engine::get_singleton()->get_process_frames(); } } diff --git a/core/input/input_map.cpp b/core/input/input_map.cpp index 178d02b987..ddeee9d765 100644 --- a/core/input/input_map.cpp +++ b/core/input/input_map.cpp @@ -636,6 +636,7 @@ const HashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() { inputs = List<Ref<InputEvent>>(); inputs.push_back(InputEventKey::create_reference(Key::A | KeyModifierMask::CTRL)); inputs.push_back(InputEventKey::create_reference(Key::LEFT | KeyModifierMask::CMD_OR_CTRL)); + inputs.push_back(InputEventKey::create_reference(Key::HOME)); default_builtin_cache.insert("ui_text_caret_line_start.macos", inputs); inputs = List<Ref<InputEvent>>(); @@ -645,6 +646,7 @@ const HashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() { inputs = List<Ref<InputEvent>>(); inputs.push_back(InputEventKey::create_reference(Key::E | KeyModifierMask::CTRL)); inputs.push_back(InputEventKey::create_reference(Key::RIGHT | KeyModifierMask::CMD_OR_CTRL)); + inputs.push_back(InputEventKey::create_reference(Key::END)); default_builtin_cache.insert("ui_text_caret_line_end.macos", inputs); // Text Caret Movement Page Up/Down @@ -665,6 +667,7 @@ const HashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() { inputs = List<Ref<InputEvent>>(); inputs.push_back(InputEventKey::create_reference(Key::UP | KeyModifierMask::CMD_OR_CTRL)); + inputs.push_back(InputEventKey::create_reference(Key::HOME | KeyModifierMask::CMD_OR_CTRL)); default_builtin_cache.insert("ui_text_caret_document_start.macos", inputs); inputs = List<Ref<InputEvent>>(); @@ -673,6 +676,7 @@ const HashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() { inputs = List<Ref<InputEvent>>(); inputs.push_back(InputEventKey::create_reference(Key::DOWN | KeyModifierMask::CMD_OR_CTRL)); + inputs.push_back(InputEventKey::create_reference(Key::END | KeyModifierMask::CMD_OR_CTRL)); default_builtin_cache.insert("ui_text_caret_document_end.macos", inputs); // Text Caret Addition Below/Above diff --git a/core/io/logger.cpp b/core/io/logger.cpp index 1476b8ccac..a24277fe72 100644 --- a/core/io/logger.cpp +++ b/core/io/logger.cpp @@ -212,7 +212,7 @@ void RotatedFileLogger::logv(const char *p_format, va_list p_list, bool p_err) { // Strip ANSI escape codes (such as those inserted by `print_rich()`) // before writing to file, as text editors cannot display those // correctly. - file->store_string(strip_ansi_regex->sub(String(buf), "", true)); + file->store_string(strip_ansi_regex->sub(String::utf8(buf), "", true)); #else file->store_buffer((uint8_t *)buf, len); #endif // MODULE_REGEX_ENABLED diff --git a/core/io/resource_loader.cpp b/core/io/resource_loader.cpp index 58ad61b621..20dd192da1 100644 --- a/core/io/resource_loader.cpp +++ b/core/io/resource_loader.cpp @@ -304,9 +304,10 @@ void ResourceLoader::_thread_load_function(void *p_userdata) { thread_load_mutex.unlock(); // Thread-safe either if it's the current thread or a brand new one. - bool mq_override_present = false; + thread_local bool mq_override_present = false; CallQueue *own_mq_override = nullptr; if (load_nesting == 0) { + mq_override_present = false; load_paths_stack = memnew(Vector<String>); if (!load_task.dependent_path.is_empty()) { @@ -326,10 +327,6 @@ void ResourceLoader::_thread_load_function(void *p_userdata) { } // -- - if (!Thread::is_main_thread()) { - set_current_thread_safe_for_nodes(true); - } - Ref<Resource> res = _load(load_task.remapped_path, load_task.remapped_path != load_task.local_path ? load_task.local_path : String(), load_task.type_hint, load_task.cache_mode, &load_task.error, load_task.use_sub_threads, &load_task.progress); if (mq_override_present) { MessageQueue::get_singleton()->flush(); @@ -691,6 +688,7 @@ Ref<Resource> ResourceLoader::_load_complete_inner(LoadToken &p_load_token, Erro Error wtp_task_err = FAILED; if (loader_is_wtp) { // Loading thread is in the worker pool. + load_task.awaited = true; thread_load_mutex.unlock(); wtp_task_err = WorkerThreadPool::get_singleton()->wait_for_task_completion(load_task.task_id); } @@ -715,7 +713,6 @@ Ref<Resource> ResourceLoader::_load_complete_inner(LoadToken &p_load_token, Erro } else { DEV_ASSERT(wtp_task_err == OK); thread_load_mutex.lock(); - load_task.awaited = true; } } else { // Loading thread is main or user thread. diff --git a/core/math/a_star_grid_2d.cpp b/core/math/a_star_grid_2d.cpp index f272407869..984bb1c9c1 100644 --- a/core/math/a_star_grid_2d.cpp +++ b/core/math/a_star_grid_2d.cpp @@ -122,6 +122,10 @@ AStarGrid2D::CellShape AStarGrid2D::get_cell_shape() const { } void AStarGrid2D::update() { + if (!dirty) { + return; + } + points.clear(); const int32_t end_x = region.get_end().x; diff --git a/core/object/class_db.cpp b/core/object/class_db.cpp index fe4345aa0d..24cf0a29c5 100644 --- a/core/object/class_db.cpp +++ b/core/object/class_db.cpp @@ -76,6 +76,21 @@ class PlaceholderExtensionInstance { StringName class_name; HashMap<StringName, Variant> properties; + // Checks if a property is from a runtime class, and not a non-runtime base class. + bool is_runtime_property(const StringName &p_property_name) { + StringName current_class_name = class_name; + + while (ClassDB::is_class_runtime(current_class_name)) { + if (ClassDB::has_property(current_class_name, p_property_name, true)) { + return true; + } + + current_class_name = ClassDB::get_parent_class(current_class_name); + } + + return false; + } + public: PlaceholderExtensionInstance(const StringName &p_class_name) { class_name = p_class_name; @@ -83,27 +98,24 @@ public: ~PlaceholderExtensionInstance() {} - void set(const StringName &p_name, const Variant &p_value) { - bool is_default_valid = false; - Variant default_value = ClassDB::class_get_default_property_value(class_name, p_name, &is_default_valid); - - // If there's a default value, then we know it's a valid property. - if (is_default_valid) { + void set(const StringName &p_name, const Variant &p_value, bool &r_valid) { + r_valid = is_runtime_property(p_name); + if (r_valid) { properties[p_name] = p_value; } } - Variant get(const StringName &p_name) { + Variant get(const StringName &p_name, bool &r_valid) { const Variant *value = properties.getptr(p_name); Variant ret; if (value) { ret = *value; + r_valid = true; } else { - bool is_default_valid = false; - Variant default_value = ClassDB::class_get_default_property_value(class_name, p_name, &is_default_valid); - if (is_default_valid) { - ret = default_value; + r_valid = is_runtime_property(p_name); + if (r_valid) { + ret = ClassDB::class_get_default_property_value(class_name, p_name); } } @@ -115,10 +127,10 @@ public: const StringName &name = *(StringName *)p_name; const Variant &value = *(const Variant *)p_value; - self->set(name, value); + bool valid = false; + self->set(name, value, valid); - // We have to return true so Godot doesn't try to call the real setter function. - return true; + return valid; } static GDExtensionBool placeholder_instance_get(GDExtensionClassInstancePtr p_instance, GDExtensionConstStringNamePtr p_name, GDExtensionVariantPtr r_ret) { @@ -126,10 +138,10 @@ public: const StringName &name = *(StringName *)p_name; Variant *value = (Variant *)r_ret; - *value = self->get(name); + bool valid = false; + *value = self->get(name, valid); - // We have to return true so Godot doesn't try to call the real getter function. - return true; + return valid; } static const GDExtensionPropertyInfo *placeholder_instance_get_property_list(GDExtensionClassInstancePtr p_instance, uint32_t *r_count) { @@ -172,9 +184,9 @@ public: static GDExtensionObjectPtr placeholder_class_create_instance(void *p_class_userdata) { ClassDB::ClassInfo *ti = (ClassDB::ClassInfo *)p_class_userdata; - // Find the closest native parent. + // Find the closest native parent, that isn't a runtime class. ClassDB::ClassInfo *native_parent = ti->inherits_ptr; - while (native_parent->gdextension) { + while (native_parent->gdextension || native_parent->is_runtime) { native_parent = native_parent->inherits_ptr; } ERR_FAIL_NULL_V(native_parent->creation_func, nullptr); @@ -1952,6 +1964,14 @@ bool ClassDB::is_class_reloadable(const StringName &p_class) { return ti->reloadable; } +bool ClassDB::is_class_runtime(const StringName &p_class) { + OBJTYPE_RLOCK; + + ClassInfo *ti = classes.getptr(p_class); + ERR_FAIL_NULL_V_MSG(ti, false, "Cannot get class '" + String(p_class) + "'."); + return ti->is_runtime; +} + void ClassDB::add_resource_base_extension(const StringName &p_extension, const StringName &p_class) { if (resource_base_extensions.has(p_extension)) { return; @@ -2063,6 +2083,11 @@ void ClassDB::register_extension_class(ObjectGDExtension *p_extension) { ClassInfo *parent = classes.getptr(p_extension->parent_class_name); +#ifdef TOOLS_ENABLED + // @todo This is a limitation of the current implementation, but it should be possible to remove. + ERR_FAIL_COND_MSG(p_extension->is_runtime && parent->gdextension && !parent->is_runtime, "Extension runtime class " + String(p_extension->class_name) + " cannot descend from " + parent->name + " which isn't also a runtime class"); +#endif + ClassInfo c; c.api = p_extension->editor_class ? API_EDITOR_EXTENSION : API_EXTENSION; c.gdextension = p_extension; diff --git a/core/object/class_db.h b/core/object/class_db.h index 37a864c109..fb671bdc84 100644 --- a/core/object/class_db.h +++ b/core/object/class_db.h @@ -460,6 +460,7 @@ public: static bool is_class_exposed(const StringName &p_class); static bool is_class_reloadable(const StringName &p_class); + static bool is_class_runtime(const StringName &p_class); static void add_resource_base_extension(const StringName &p_extension, const StringName &p_class); static void get_resource_base_extensions(List<String> *p_extensions); diff --git a/core/variant/array.cpp b/core/variant/array.cpp index 3685515db5..54cd1eda2f 100644 --- a/core/variant/array.cpp +++ b/core/variant/array.cpp @@ -235,7 +235,7 @@ void Array::assign(const Array &p_array) { for (int i = 0; i < size; i++) { const Variant &element = source[i]; if (element.get_type() != Variant::NIL && (element.get_type() != Variant::OBJECT || !typed.validate_object(element, "assign"))) { - ERR_FAIL_MSG(vformat(R"(Unable to convert array index %i from "%s" to "%s".)", i, Variant::get_type_name(element.get_type()), Variant::get_type_name(typed.type))); + ERR_FAIL_MSG(vformat(R"(Unable to convert array index %d from "%s" to "%s".)", i, Variant::get_type_name(element.get_type()), Variant::get_type_name(typed.type))); } } _p->array = p_array._p->array; @@ -258,11 +258,11 @@ void Array::assign(const Array &p_array) { continue; } if (!Variant::can_convert_strict(value->get_type(), typed.type)) { - ERR_FAIL_MSG("Unable to convert array index " + itos(i) + " from '" + Variant::get_type_name(value->get_type()) + "' to '" + Variant::get_type_name(typed.type) + "'."); + ERR_FAIL_MSG(vformat(R"(Unable to convert array index %d from "%s" to "%s".)", i, Variant::get_type_name(value->get_type()), Variant::get_type_name(typed.type))); } Callable::CallError ce; Variant::construct(typed.type, data[i], &value, 1, ce); - ERR_FAIL_COND_MSG(ce.error, vformat(R"(Unable to convert array index %i from "%s" to "%s".)", i, Variant::get_type_name(value->get_type()), Variant::get_type_name(typed.type))); + ERR_FAIL_COND_MSG(ce.error, vformat(R"(Unable to convert array index %d from "%s" to "%s".)", i, Variant::get_type_name(value->get_type()), Variant::get_type_name(typed.type))); } } else if (Variant::can_convert_strict(source_typed.type, typed.type)) { // from primitives to different convertible primitives @@ -270,7 +270,7 @@ void Array::assign(const Array &p_array) { const Variant *value = source + i; Callable::CallError ce; Variant::construct(typed.type, data[i], &value, 1, ce); - ERR_FAIL_COND_MSG(ce.error, vformat(R"(Unable to convert array index %i from "%s" to "%s".)", i, Variant::get_type_name(value->get_type()), Variant::get_type_name(typed.type))); + ERR_FAIL_COND_MSG(ce.error, vformat(R"(Unable to convert array index %d from "%s" to "%s".)", i, Variant::get_type_name(value->get_type()), Variant::get_type_name(typed.type))); } } else { ERR_FAIL_MSG(vformat(R"(Cannot assign contents of "Array[%s]" to "Array[%s]".)", Variant::get_type_name(source_typed.type), Variant::get_type_name(typed.type))); diff --git a/core/variant/variant.h b/core/variant/variant.h index f352af24da..1cb3580c01 100644 --- a/core/variant/variant.h +++ b/core/variant/variant.h @@ -857,7 +857,7 @@ String vformat(const String &p_text, const VarArgs... p_args) { bool error = false; String fmt = p_text.sprintf(args_array, &error); - ERR_FAIL_COND_V_MSG(error, String(), fmt); + ERR_FAIL_COND_V_MSG(error, String(), String("Formatting error in string \"") + p_text + "\": " + fmt + "."); return fmt; } diff --git a/doc/classes/Animation.xml b/doc/classes/Animation.xml index 3b7a6e66fe..80fd52c684 100644 --- a/doc/classes/Animation.xml +++ b/doc/classes/Animation.xml @@ -560,7 +560,7 @@ <param index="0" name="track_idx" type="int" /> <param index="1" name="path" type="NodePath" /> <description> - Sets the path of a track. Paths must be valid scene-tree paths to a node and must be specified starting from the parent node of the node that will reproduce the animation. Tracks that control properties or bones must append their name after the path, separated by [code]":"[/code]. + Sets the path of a track. Paths must be valid scene-tree paths to a node and must be specified starting from the [member AnimationMixer.root_node] that will reproduce the animation. Tracks that control properties or bones must append their name after the path, separated by [code]":"[/code]. For example, [code]"character/skeleton:ankle"[/code] or [code]"character/mesh:transform/local"[/code]. </description> </method> diff --git a/doc/classes/AnimationMixer.xml b/doc/classes/AnimationMixer.xml index 8a493ce91f..58ef118e46 100644 --- a/doc/classes/AnimationMixer.xml +++ b/doc/classes/AnimationMixer.xml @@ -304,8 +304,8 @@ This makes it more convenient to preview and edit animations in the editor, as changes to the scene will not be saved as long as they are set in the reset animation. </member> <member name="root_motion_track" type="NodePath" setter="set_root_motion_track" getter="get_root_motion_track" default="NodePath("")"> - The path to the Animation track used for root motion. Paths must be valid scene-tree paths to a node, and must be specified starting from the parent node of the node that will reproduce the animation. To specify a track that controls properties or bones, append its name after the path, separated by [code]":"[/code]. For example, [code]"character/skeleton:ankle"[/code] or [code]"character/mesh:transform/local"[/code]. - If the track has type [constant Animation.TYPE_POSITION_3D], [constant Animation.TYPE_ROTATION_3D] or [constant Animation.TYPE_SCALE_3D] the transformation will be canceled visually, and the animation will appear to stay in place. See also [method get_root_motion_position], [method get_root_motion_rotation], [method get_root_motion_scale] and [RootMotionView]. + The path to the Animation track used for root motion. Paths must be valid scene-tree paths to a node, and must be specified starting from the parent node of the node that will reproduce the animation. The [member root_motion_track] uses the same format as [method Animation.track_set_path], but note that a bone must be specified. + If the track has type [constant Animation.TYPE_POSITION_3D], [constant Animation.TYPE_ROTATION_3D], or [constant Animation.TYPE_SCALE_3D] the transformation will be canceled visually, and the animation will appear to stay in place. See also [method get_root_motion_position], [method get_root_motion_rotation], [method get_root_motion_scale], and [RootMotionView]. </member> <member name="root_node" type="NodePath" setter="set_root_node" getter="get_root_node" default="NodePath("..")"> The node which node path references will travel from. diff --git a/doc/classes/Array.xml b/doc/classes/Array.xml index 326b71c588..3731b8dcf1 100644 --- a/doc/classes/Array.xml +++ b/doc/classes/Array.xml @@ -4,41 +4,31 @@ A built-in data structure that holds a sequence of elements. </brief_description> <description> - An array data structure that can contain a sequence of elements of any type. Elements are accessed by a numerical index starting at 0. Negative indices are used to count from the back (-1 is the last element, -2 is the second to last, etc.). + An array data structure that can contain a sequence of elements of any [Variant] type. Elements are accessed by a numerical index starting at 0. Negative indices are used to count from the back (-1 is the last element, -2 is the second to last, etc.). [b]Example:[/b] [codeblocks] [gdscript] - var array = ["One", 2, 3, "Four"] - print(array[0]) # One. - print(array[2]) # 3. - print(array[-1]) # Four. - array[2] = "Three" - print(array[-2]) # Three. - [/gdscript] - [csharp] - var array = new Godot.Collections.Array{"One", 2, 3, "Four"}; - GD.Print(array[0]); // One. - GD.Print(array[2]); // 3. - GD.Print(array[array.Count - 1]); // Four. - array[2] = "Three"; - GD.Print(array[array.Count - 2]); // Three. - [/csharp] - [/codeblocks] - Arrays can be concatenated using the [code]+[/code] operator: - [codeblocks] - [gdscript] - var array1 = ["One", 2] - var array2 = [3, "Four"] - print(array1 + array2) # ["One", 2, 3, "Four"] + var array = ["First", 2, 3, "Last"] + print(array[0]) # Prints "First" + print(array[2]) # Prints 3 + print(array[-1]) # Prints "Last" + + array[1] = "Second" + print(array[1]) # Prints "Second" + print(array[-3]) # Prints "Second" [/gdscript] [csharp] - // Array concatenation is not possible with C# arrays, but is with Godot.Collections.Array. - var array1 = new Godot.Collections.Array{"One", 2}; - var array2 = new Godot.Collections.Array{3, "Four"}; - GD.Print(array1 + array2); // Prints [One, 2, 3, Four] + var array = new Godot.Collections.Array{"First", 2, 3, "Last"}; + GD.Print(array[0]); // Prints "First" + GD.Print(array[2]); // Prints 3 + GD.Print(array[array.Count - 1]); // Prints "Last" + + array[2] = "Second"; + GD.Print(array[1]); // Prints "Second" + GD.Print(array[array.Count - 3]); // Prints "Second" [/csharp] [/codeblocks] - [b]Note:[/b] Arrays are always passed by reference. To get a copy of an array that can be modified independently of the original array, use [method duplicate]. + [b]Note:[/b] Arrays are always passed by [b]reference[/b]. To get a copy of an array that can be modified independently of the original array, use [method duplicate]. [b]Note:[/b] Erasing elements while iterating over arrays is [b]not[/b] supported and will result in unpredictable behavior. [b]Differences between packed arrays, typed arrays, and untyped arrays:[/b] Packed arrays are generally faster to iterate on and modify compared to a typed array of the same type (e.g. [PackedInt64Array] versus [code]Array[int][/code]). Also, packed arrays consume less memory. As a downside, packed arrays are less flexible as they don't offer as many convenience methods such as [method Array.map]. Typed arrays are in turn faster to iterate on and modify than untyped arrays. </description> @@ -58,29 +48,32 @@ <param index="2" name="class_name" type="StringName" /> <param index="3" name="script" type="Variant" /> <description> - Creates a typed array from the [param base] array. All arguments are required. - - [param type] is the built-in type as a [enum Variant.Type] constant, for example [constant TYPE_INT]. - - [param class_name] is the [b]native[/b] class name, for example [Node]. If [param type] is not [constant TYPE_OBJECT], must be an empty string. - - [param script] is the associated script. Must be a [Script] instance or [code]null[/code]. - Examples: + Creates a typed array from the [param base] array. A typed array can only contain elements of the given type, or that inherit from the given class, as described by this constructor's parameters: + - [param type] is the built-in [Variant] type, as one the [enum Variant.Type] constants. + - [param class_name] is the built-in class name (see [method Object.get_class]). + - [param script] is the associated script. It must be a [Script] instance or [code]null[/code]. + If [param type] is not [constant TYPE_OBJECT], [param class_name] must be an empty [StringName] and [param script] must be [code]null[/code]. [codeblock] - class_name MyNode + class_name Sword extends Node - class MyClass: + class Stats: pass func _ready(): - var a = Array([], TYPE_INT, &"", null) # Array[int] - var b = Array([], TYPE_OBJECT, &"Node", null) # Array[Node] - var c = Array([], TYPE_OBJECT, &"Node", MyNode) # Array[MyNode] - var d = Array([], TYPE_OBJECT, &"RefCounted", MyClass) # Array[MyClass] + var a = Array([], TYPE_INT, "", null) # Array[int] + var b = Array([], TYPE_OBJECT, "Node", null) # Array[Node] + var c = Array([], TYPE_OBJECT, "Node", Sword) # Array[Sword] + var d = Array([], TYPE_OBJECT, "RefCounted", Stats) # Array[Stats] [/codeblock] - [b]Note:[/b] This constructor can be useful if you want to create a typed array on the fly, but you are not required to use it. In GDScript you can use a temporary variable with the static type you need and then pass it: + The [param base] array's elements are converted when necessary. If this is not possible or [param base] is already typed, this constructor fails and returns an empty [Array]. + In GDScript, this constructor is usually not necessary, as it is possible to create a typed array through static typing: [codeblock] - func _ready(): - var a: Array[int] = [] - some_func(a) + var numbers: Array[float] = [] + var children: Array[Node] = [$Node, $Sprite2D, $RigidBody3D] + + var integers: Array[int] = [0.2, 4.5, -2.0] + print(integers) # Prints [0, 4, -2] [/codeblock] </description> </constructor> @@ -167,20 +160,44 @@ <return type="bool" /> <param index="0" name="method" type="Callable" /> <description> - Calls the provided [Callable] on each element in the array and returns [code]true[/code] if the [Callable] returns [code]true[/code] for [i]all[/i] elements in the array. If the [Callable] returns [code]false[/code] for one array element or more, this method returns [code]false[/code]. - The callable's method should take one [Variant] parameter (the current array element) and return a boolean value. - [codeblock] + Calls the given [Callable] on each element in the array and returns [code]true[/code] if the [Callable] returns [code]true[/code] for [i]all[/i] elements in the array. If the [Callable] returns [code]false[/code] for one array element or more, this method returns [code]false[/code]. + The [param method] should take one [Variant] parameter (the current array element) and return a [bool]. + [codeblocks] + [gdscript] + func greater_than_5(number): + return number > 5 + func _ready(): - print([6, 10, 6].all(greater_than_5)) # Prints True (3/3 elements evaluate to `true`). - print([4, 10, 4].all(greater_than_5)) # Prints False (1/3 elements evaluate to `true`). - print([4, 4, 4].all(greater_than_5)) # Prints False (0/3 elements evaluate to `true`). - print([].all(greater_than_5)) # Prints True (0/0 elements evaluate to `true`). + print([6, 10, 6].all(greater_than_5)) # Prints true (3/3 elements evaluate to true). + print([4, 10, 4].all(greater_than_5)) # Prints false (1/3 elements evaluate to true). + print([4, 4, 4].all(greater_than_5)) # Prints false (0/3 elements evaluate to true). + print([].all(greater_than_5)) # Prints true (0/0 elements evaluate to true). + + # Same as the first line above, but using a lambda function. + print([6, 10, 6].all(func(element): return element > 5)) # Prints true + [/gdscript] + [csharp] + private static bool GreaterThan5(int number) + { + return number > 5; + } - print([6, 10, 6].all(func(number): return number > 5)) # Prints True. Same as the first line above, but using lambda function. + public override void _Ready() + { + // Prints true (3/3 elements evaluate to true). + GD.Print(new Godot.Collections.Array>int< { 6, 10, 6 }.All(GreaterThan5)); + // Prints false (1/3 elements evaluate to true). + GD.Print(new Godot.Collections.Array>int< { 4, 10, 4 }.All(GreaterThan5)); + // Prints false (0/3 elements evaluate to true). + GD.Print(new Godot.Collections.Array>int< { 4, 4, 4 }.All(GreaterThan5)); + // Prints true (0/0 elements evaluate to true). + GD.Print(new Godot.Collections.Array>int< { }.All(GreaterThan5)); - func greater_than_5(number): - return number > 5 - [/codeblock] + // Same as the first line above, but using a lambda function. + GD.Print(new Godot.Collections.Array>int< { 6, 10, 6 }.All(element => element > 5)); // Prints true + } + [/csharp] + [/codeblocks] See also [method any], [method filter], [method map] and [method reduce]. [b]Note:[/b] Unlike relying on the size of an array returned by [method filter], this method will return as early as possible to improve performance (especially with large arrays). [b]Note:[/b] For an empty array, this method [url=https://en.wikipedia.org/wiki/Vacuous_truth]always[/url] returns [code]true[/code]. @@ -190,19 +207,20 @@ <return type="bool" /> <param index="0" name="method" type="Callable" /> <description> - Calls the provided [Callable] on each element in the array and returns [code]true[/code] if the [Callable] returns [code]true[/code] for [i]one or more[/i] elements in the array. If the [Callable] returns [code]false[/code] for all elements in the array, this method returns [code]false[/code]. - The callable's method should take one [Variant] parameter (the current array element) and return a boolean value. + Calls the given [Callable] on each element in the array and returns [code]true[/code] if the [Callable] returns [code]true[/code] for [i]one or more[/i] elements in the array. If the [Callable] returns [code]false[/code] for all elements in the array, this method returns [code]false[/code]. + The [param method] should take one [Variant] parameter (the current array element) and return a [bool]. [codeblock] - func _ready(): - print([6, 10, 6].any(greater_than_5)) # Prints True (3 elements evaluate to `true`). - print([4, 10, 4].any(greater_than_5)) # Prints True (1 elements evaluate to `true`). - print([4, 4, 4].any(greater_than_5)) # Prints False (0 elements evaluate to `true`). - print([].any(greater_than_5)) # Prints False (0 elements evaluate to `true`). - - print([6, 10, 6].any(func(number): return number > 5)) # Prints True. Same as the first line above, but using lambda function. - func greater_than_5(number): return number > 5 + + func _ready(): + print([6, 10, 6].any(greater_than_5)) # Prints true (3 elements evaluate to true). + print([4, 10, 4].any(greater_than_5)) # Prints true (1 elements evaluate to true). + print([4, 4, 4].any(greater_than_5)) # Prints false (0 elements evaluate to true). + print([].any(greater_than_5)) # Prints false (0 elements evaluate to true). + + # Same as the first line above, but using a lambda function. + print([6, 10, 6].any(func(number): return number > 5)) # Prints true [/codeblock] See also [method all], [method filter], [method map] and [method reduce]. [b]Note:[/b] Unlike relying on the size of an array returned by [method filter], this method will return as early as possible to improve performance (especially with large arrays). @@ -213,19 +231,19 @@ <return type="void" /> <param index="0" name="value" type="Variant" /> <description> - Appends an element at the end of the array (alias of [method push_back]). + Appends [param value] at the end of the array (alias of [method push_back]). </description> </method> <method name="append_array"> <return type="void" /> <param index="0" name="array" type="Array" /> <description> - Appends another array at the end of this array. + Appends another [param array] at the end of this array. [codeblock] - var array1 = [1, 2, 3] - var array2 = [4, 5, 6] - array1.append_array(array2) - print(array1) # Prints [1, 2, 3, 4, 5, 6]. + var numbers = [1, 2, 3] + var extra = [4, 5, 6] + numbers.append_array(extra) + print(nums) # Prints [1, 2, 3, 4, 5, 6] [/codeblock] </description> </method> @@ -239,8 +257,8 @@ <method name="back" qualifiers="const"> <return type="Variant" /> <description> - Returns the last element of the array. Prints an error and returns [code]null[/code] if the array is empty. - [b]Note:[/b] Calling this function is not the same as writing [code]array[-1][/code]. If the array is empty, accessing by index will pause project execution when running from the editor. + Returns the last element of the array. If the array is empty, fails and returns [code]null[/code]. See also [method front]. + [b]Note:[/b] Unlike with the [code][][/code] operator ([code]array[-1][/code]), an error is generated without stopping project execution. </description> </method> <method name="bsearch" qualifiers="const"> @@ -248,13 +266,20 @@ <param index="0" name="value" type="Variant" /> <param index="1" name="before" type="bool" default="true" /> <description> - Finds the index of an existing value (or the insertion index that maintains sorting order, if the value is not yet present in the array) using binary search. Optionally, a [param before] specifier can be passed. If [code]false[/code], the returned index comes after all existing entries of the value in the array. + Returns the index of [param value] in the sorted array. If it cannot be found, returns where [param value] should be inserted to keep the array sorted. The algorithm used is [url=https://en.wikipedia.org/wiki/Binary_search_algorithm]binary search[/url]. + If [param before] is [code]true[/code] (as by default), the returned index comes before all existing elements equal to [param value] in the array. [codeblock] - var array = ["a", "b", "c", "c", "d", "e"] - print(array.bsearch("c", true)) # Prints 2, at the first matching element. - print(array.bsearch("c", false)) # Prints 4, after the last matching element, pointing to "d". + var numbers = [2, 4, 8, 10] + var idx = numbers.bsearch(7) + + numbers.insert(idx, 7) + print(numbers) # Prints [2, 4, 7, 8, 10] + + var fruits = ["Apple", "Lemon", "Lemon", "Orange"] + print(fruits.bsearch("Lemon", true)) # Prints 1, points at the first "Lemon". + print(fruits.bsearch("Lemon", false)) # Prints 3, points at "Orange". [/codeblock] - [b]Note:[/b] Calling [method bsearch] on an unsorted array results in unexpected behavior. + [b]Note:[/b] Calling [method bsearch] on an [i]unsorted[/i] array will result in unexpected behavior. Use [method sort] before calling this method. </description> </method> <method name="bsearch_custom" qualifiers="const"> @@ -263,15 +288,36 @@ <param index="1" name="func" type="Callable" /> <param index="2" name="before" type="bool" default="true" /> <description> - Finds the index of an existing value (or the insertion index that maintains sorting order, if the value is not yet present in the array) using binary search and a custom comparison method. Optionally, a [param before] specifier can be passed. If [code]false[/code], the returned index comes after all existing entries of the value in the array. The custom method receives two arguments (an element from the array and the value searched for) and must return [code]true[/code] if the first argument is less than the second, and return [code]false[/code] otherwise. - [b]Note:[/b] The custom method must accept the two arguments in any order, you cannot rely on that the first argument will always be from the array. - [b]Note:[/b] Calling [method bsearch_custom] on an unsorted array results in unexpected behavior. + Returns the index of [param value] in the sorted array. If it cannot be found, returns where [param value] should be inserted to keep the array sorted (using [param func] for the comparisons). The algorithm used is [url=https://en.wikipedia.org/wiki/Binary_search_algorithm]binary search[/url]. + Similar to [method sort_custom], [param func] is called as many times as necessary, receiving one array element and [param value] as arguments. The function should return [code]true[/code] if the array element should be [i]behind[/i] [param value], otherwise it should return [code]false[/code]. + If [param before] is [code]true[/code] (as by default), the returned index comes before all existing elements equal to [param value] in the array. + [codeblock] + func sort_by_amount(a, b): + if a[1] < b[1]: + return true + return false + + func _ready(): + var my_items = [["Tomato", 2], ["Kiwi", 5], ["Rice", 9]] + + var apple = ["Apple", 5] + # "Apple" is inserted before "Kiwi". + my_items.insert(my_items.bsearch_custom(apple, sort_by_amount, true), apple) + + var banana = ["Banana", 5] + # "Banana" is inserted after "Kiwi". + my_items.insert(my_items.bsearch_custom(banana, sort_by_amount, false), banana) + + # Prints [["Tomato", 2], ["Apple", 5], ["Kiwi", 5], ["Banana", 5], ["Rice", 9]] + print(my_items) + [/codeblock] + [b]Note:[/b] Calling [method bsearch_custom] on an [i]unsorted[/i] array will result in unexpected behavior. Use [method sort_custom] with [param func] before calling this method. </description> </method> <method name="clear"> <return type="void" /> <description> - Clears the array. This is equivalent to using [method resize] with a size of [code]0[/code]. + Removes all elements from the array. This is equivalent to using [method resize] with a size of [code]0[/code]. </description> </method> <method name="count" qualifiers="const"> @@ -285,53 +331,57 @@ <return type="Array" /> <param index="0" name="deep" type="bool" default="false" /> <description> - Returns a copy of the array. - If [param deep] is [code]true[/code], a deep copy is performed: all nested arrays and dictionaries are duplicated and will not be shared with the original array. If [code]false[/code], a shallow copy is made and references to the original nested arrays and dictionaries are kept, so that modifying a sub-array or dictionary in the copy will also impact those referenced in the source array. Note that any [Object]-derived elements will be shallow copied regardless of the [param deep] setting. + Returns a new copy of the array. + By default, a [b]shallow[/b] copy is returned: all nested [Array] and [Dictionary] elements are shared with the original array. Modifying them in one array will also affect them in the other.[br]If [param deep] is [code]true[/code], a [b]deep[/b] copy is returned: all nested arrays and dictionaries are also duplicated (recursively). </description> </method> <method name="erase"> <return type="void" /> <param index="0" name="value" type="Variant" /> <description> - Removes the first occurrence of a value from the array. If the value does not exist in the array, nothing happens. To remove an element by index, use [method remove_at] instead. - [b]Note:[/b] This method acts in-place and doesn't return a modified array. - [b]Note:[/b] On large arrays, this method will be slower if the removed element is close to the beginning of the array (index 0). This is because all elements placed after the removed element have to be reindexed. - [b]Note:[/b] Do not erase entries while iterating over the array. + Finds and removes the first occurrence of [param value] from the array. If [param value] does not exist in the array, nothing happens. To remove an element by index, use [method remove_at] instead. + [b]Note:[/b] This method shifts every element's index after the removed [param value] back, which may have a noticeable performance cost, especially on larger arrays. + [b]Note:[/b] Erasing elements while iterating over arrays is [b]not[/b] supported and will result in unpredictable behavior. </description> </method> <method name="fill"> <return type="void" /> <param index="0" name="value" type="Variant" /> <description> - Assigns the given value to all elements in the array. This can typically be used together with [method resize] to create an array with a given size and initialized elements: + Assigns the given [param value] to all elements in the array. + This method can often be combined with [method resize] to create an array with a given size and initialized elements: [codeblocks] [gdscript] var array = [] - array.resize(10) - array.fill(0) # Initialize the 10 elements to 0. + array.resize(5) + array.fill(2) + print(array) # Prints [2, 2, 2, 2, 2] [/gdscript] [csharp] var array = new Godot.Collections.Array(); - array.Resize(10); - array.Fill(0); // Initialize the 10 elements to 0. + array.Resize(5); + array.Fill(2); + GD.Print(array); // Prints [2, 2, 2, 2, 2] [/csharp] [/codeblocks] - [b]Note:[/b] If [param value] is of a reference type ([Object]-derived, [Array], [Dictionary], etc.) then the array is filled with the references to the same object, i.e. no duplicates are created. + [b]Note:[/b] If [param value] is a [Variant] passed by reference ([Object]-derived, [Array], [Dictionary], etc.), the array will be filled with references to the same [param value], which are not duplicates. </description> </method> <method name="filter" qualifiers="const"> <return type="Array" /> <param index="0" name="method" type="Callable" /> <description> - Calls the provided [Callable] on each element in the array and returns a new array with the elements for which the method returned [code]true[/code]. - The callable's method should take one [Variant] parameter (the current array element) and return a boolean value. + Calls the given [Callable] on each element in the array and returns a new, filtered [Array]. + The [param method] receives one of the array elements as an argument, and should return [code]true[/code] to add the element to the filtered array, or [code]false[/code] to exclude it. [codeblock] + func is_even(number): + return number % 2 == 0 + func _ready(): - print([1, 2, 3].filter(remove_1)) # Prints [2, 3]. - print([1, 2, 3].filter(func(number): return number != 1)) # Same as above, but using lambda function. + print([1, 4, 5, 8].filter(is_even)) # Prints [4, 8] - func remove_1(number): - return number != 1 + # Same as above, but using a lambda function. + print([1, 4, 5, 8].filter(func(number): return number % 2 == 0)) [/codeblock] See also [method any], [method all], [method map] and [method reduce]. </description> @@ -341,78 +391,70 @@ <param index="0" name="what" type="Variant" /> <param index="1" name="from" type="int" default="0" /> <description> - Searches the array for a value and returns its index or [code]-1[/code] if not found. Optionally, the initial search index can be passed. + Returns the index of the [b]first[/b] occurrence of [param what] in this array, or [code]-1[/code] if there are none. The search's start can be specified with [param from], continuing to the end of the array. + [b]Note:[/b] If you just want to know whether the array contains [param what], use [method has] ([code]Contains[/code] in C#). In GDScript, you may also use the [code]in[/code] operator. + [b]Note:[/b] For performance reasons, the search is affected by [param what]'s [enum Variant.Type]. For example, [code]7[/code] ([int]) and [code]7.0[/code] ([float]) are not considered equal for this method. </description> </method> <method name="front" qualifiers="const"> <return type="Variant" /> <description> - Returns the first element of the array. Prints an error and returns [code]null[/code] if the array is empty. - [b]Note:[/b] Calling this function is not the same as writing [code]array[0][/code]. If the array is empty, accessing by index will pause project execution when running from the editor. + Returns the first element of the array. If the array is empty, fails and returns [code]null[/code]. See also [method back]. + [b]Note:[/b] Unlike with the [code][][/code] operator ([code]array[0][/code]), an error is generated without stopping project execution. </description> </method> <method name="get_typed_builtin" qualifiers="const"> <return type="int" /> <description> - Returns the built-in type of the typed array as a [enum Variant.Type] constant. If the array is not typed, returns [constant TYPE_NIL]. + Returns the built-in [Variant] type of the typed array as a [enum Variant.Type] constant. If the array is not typed, returns [constant TYPE_NIL]. See also [method is_typed]. </description> </method> <method name="get_typed_class_name" qualifiers="const"> <return type="StringName" /> <description> - Returns the [b]native[/b] class name of the typed array if the built-in type is [constant TYPE_OBJECT]. Otherwise, this method returns an empty string. + Returns the [b]built-in[/b] class name of the typed array, if the built-in [Variant] type [constant TYPE_OBJECT]. Otherwise, returns an empty [StringName]. See also [method is_typed] and [method Object.get_class]. </description> </method> <method name="get_typed_script" qualifiers="const"> <return type="Variant" /> <description> - Returns the script associated with the typed array. This method returns a [Script] instance or [code]null[/code]. + Returns the [Script] instance associated with this typed array, or [code]null[/code] if it does not exist. See also [method is_typed]. </description> </method> <method name="has" qualifiers="const" keywords="includes, contains"> <return type="bool" /> <param index="0" name="value" type="Variant" /> <description> - Returns [code]true[/code] if the array contains the given value. + Returns [code]true[/code] if the array contains the given [param value]. [codeblocks] [gdscript] - print(["inside", 7].has("inside")) # True - print(["inside", 7].has("outside")) # False - print(["inside", 7].has(7)) # True - print(["inside", 7].has("7")) # False + print(["inside", 7].has("inside")) # Prints true + print(["inside", 7].has("outside")) # Prints false + print(["inside", 7].has(7)) # Prints true + print(["inside", 7].has("7")) # Prints false [/gdscript] [csharp] var arr = new Godot.Collections.Array { "inside", 7 }; - // has is renamed to Contains - GD.Print(arr.Contains("inside")); // True - GD.Print(arr.Contains("outside")); // False - GD.Print(arr.Contains(7)); // True - GD.Print(arr.Contains("7")); // False - [/csharp] - [/codeblocks] - [b]Note:[/b] This is equivalent to using the [code]in[/code] operator as follows: - [codeblocks] - [gdscript] - # Will evaluate to `true`. - if 2 in [2, 4, 6, 8]: - print("Contains!") - [/gdscript] - [csharp] - // As there is no "in" keyword in C#, you have to use Contains - var array = new Godot.Collections.Array { 2, 4, 6, 8 }; - if (array.Contains(2)) - { - GD.Print("Contains!"); - } + // By C# convention, this method is renamed to `Contains`. + GD.Print(arr.Contains("inside")); // Prints true + GD.Print(arr.Contains("outside")); // Prints false + GD.Print(arr.Contains(7)); // Prints true + GD.Print(arr.Contains("7")); // Prints false [/csharp] [/codeblocks] + In GDScript, this is equivalent to the [code]in[/code] operator: + [codeblock] + if 4 in [2, 4, 6, 8]: + print("4 is here!") # Will be printed. + [/codeblock] + [b]Note:[/b] For performance reasons, the search is affected by the [param value]'s [enum Variant.Type]. For example, [code]7[/code] ([int]) and [code]7.0[/code] ([float]) are not considered equal for this method. </description> </method> <method name="hash" qualifiers="const"> <return type="int" /> <description> Returns a hashed 32-bit integer value representing the array and its contents. - [b]Note:[/b] [Array]s with equal content will always produce identical hash values. However, the reverse is not true. Returning identical hash values does [i]not[/i] imply the arrays are equal, because different arrays can have identical hash values due to hash collisions. + [b]Note:[/b] Arrays with equal hash values are [i]not[/i] guaranteed to be the same, as a result of hash collisions. On the countrary, arrays with different hash values are guaranteed to be different. </description> </method> <method name="insert"> @@ -420,55 +462,64 @@ <param index="0" name="position" type="int" /> <param index="1" name="value" type="Variant" /> <description> - Inserts a new element at a given position in the array. The position must be valid, or at the end of the array ([code]pos == size()[/code]). Returns [constant OK] on success, or one of the other [enum Error] values if the operation failed. - [b]Note:[/b] This method acts in-place and doesn't return a modified array. - [b]Note:[/b] On large arrays, this method will be slower if the inserted element is close to the beginning of the array (index 0). This is because all elements placed after the newly inserted element have to be reindexed. + Inserts a new element ([param value]) at a given index ([param position]) in the array. [param position] should be between [code]0[/code] and the array's [method size]. + Returns [constant OK] on success, or one of the other [enum Error] constants if this method fails. + [b]Note:[/b] Every element's index after [param position] needs to be shifted forward, which may have a noticeable performance cost, especially on larger arrays. </description> </method> <method name="is_empty" qualifiers="const"> <return type="bool" /> <description> - Returns [code]true[/code] if the array is empty. + Returns [code]true[/code] if the array is empty ([code][][/code]). See also [method size]. </description> </method> <method name="is_read_only" qualifiers="const"> <return type="bool" /> <description> - Returns [code]true[/code] if the array is read-only. See [method make_read_only]. Arrays are automatically read-only if declared with [code]const[/code] keyword. + Returns [code]true[/code] if the array is read-only. See [method make_read_only]. + In GDScript, arrays are automatically read-only if declared with the [code]const[/code] keyword. </description> </method> <method name="is_same_typed" qualifiers="const"> <return type="bool" /> <param index="0" name="array" type="Array" /> <description> - Returns [code]true[/code] if the array is typed the same as [param array]. + Returns [code]true[/code] if this array is typed the same as the given [param array]. See also [method is_typed]. </description> </method> <method name="is_typed" qualifiers="const"> <return type="bool" /> <description> - Returns [code]true[/code] if the array is typed. Typed arrays can only store elements of their associated type and provide type safety for the [code][][/code] operator. Methods of typed array still return [Variant]. + Returns [code]true[/code] if the array is typed. Typed arrays can only contain elements of a specific type, as defined by the typed array constructor. The methods of a typed array are still expected to return a generic [Variant]. + In GDScript, it is possible to define a typed array with static typing: + [codeblock] + var numbers: Array[float] = [0.2, 4.2, -2.0] + print(numbers.is_typed()) # Prints true + [/codeblock] </description> </method> <method name="make_read_only"> <return type="void" /> <description> - Makes the array read-only, i.e. disabled modifying of the array's elements. Does not apply to nested content, e.g. content of nested arrays. + Makes the array read-only. The array's elements cannot be overridden with different values, and their order cannot change. Does not apply to nested elements, such as dictionaries. + In GDScript, arrays are automatically read-only if declared with the [code]const[/code] keyword. </description> </method> <method name="map" qualifiers="const"> <return type="Array" /> <param index="0" name="method" type="Callable" /> <description> - Calls the provided [Callable] for each element in the array and returns a new array filled with values returned by the method. - The callable's method should take one [Variant] parameter (the current array element) and can return any [Variant]. + Calls the given [Callable] for each element in the array and returns a new array filled with values returned by the [param method]. + The [param method] should take one [Variant] parameter (the current array element) and can return any [Variant]. [codeblock] + func double(number): + return number * 2 + func _ready(): - print([1, 2, 3].map(negate)) # Prints [-1, -2, -3]. - print([1, 2, 3].map(func(number): return -number)) # Same as above, but using lambda function. + print([1, 2, 3].map(double)) # Prints [2, 4, 6] - func negate(number): - return -number + # Same as above, but using a lambda function. + print([1, 2, 3].map(func(element): return element * 2)) [/codeblock] See also [method filter], [method reduce], [method any] and [method all]. </description> @@ -476,61 +527,52 @@ <method name="max" qualifiers="const"> <return type="Variant" /> <description> - Returns the maximum value contained in the array if all elements are of comparable types. If the elements can't be compared, [code]null[/code] is returned. - To find the maximum value using a custom comparator, you can use [method reduce]. In this example every array element is checked and the first maximum value is returned: - [codeblock] - func _ready(): - var arr = [Vector2(0, 1), Vector2(2, 0), Vector2(1, 1), Vector2(1, 0), Vector2(0, 2)] - # In this example we compare the lengths. - print(arr.reduce(func(max, val): return val if is_length_greater(val, max) else max)) - - func is_length_greater(a, b): - return a.length() > b.length() - [/codeblock] + Returns the maximum value contained in the array, if all elements can be compared. Otherwise, returns [code]null[/code]. See also [method min]. + To find the maximum value using a custom comparator, you can use [method reduce]. </description> </method> <method name="min" qualifiers="const"> <return type="Variant" /> <description> - Returns the minimum value contained in the array if all elements are of comparable types. If the elements can't be compared, [code]null[/code] is returned. - See also [method max] for an example of using a custom comparator. + Returns the minimum value contained in the array, if all elements can be compared. Otherwise, returns [code]null[/code]. See also [method max]. </description> </method> <method name="pick_random" qualifiers="const"> <return type="Variant" /> <description> - Returns a random value from the target array. Prints an error and returns [code]null[/code] if the array is empty. + Returns a random element from the array. Generates an error and returns [code]null[/code] if the array is empty. [codeblocks] [gdscript] - var array: Array[int] = [1, 2, 3, 4] - print(array.pick_random()) # Prints either of the four numbers. + # May print 1, 2, 3.25, or "Hi". + print([1, 2, 3.25, "Hi"].pick_random()) [/gdscript] [csharp] - var array = new Godot.Collections.Array { 1, 2, 3, 4 }; - GD.Print(array.PickRandom()); // Prints either of the four numbers. + var array = new Godot.Collections.Array { 1, 2, 3.25f, "Hi" }; + GD.Print(array.PickRandom()); // May print 1, 2, 3.25, or "Hi". [/csharp] [/codeblocks] + [b]Note:[/b] Like many similar functions in the engine (such as [method @GlobalScope.randi] or [method shuffle]), this method uses a common, global random seed. To get a predictable outcome from this method, see [method @GlobalScope.seed]. </description> </method> <method name="pop_at"> <return type="Variant" /> <param index="0" name="position" type="int" /> <description> - Removes and returns the element of the array at index [param position]. If negative, [param position] is considered relative to the end of the array. Leaves the array unchanged and returns [code]null[/code] if the array is empty or if it's accessed out of bounds. An error message is printed when the array is accessed out of bounds, but not when the array is empty. - [b]Note:[/b] On large arrays, this method can be slower than [method pop_back] as it will reindex the array's elements that are located after the removed element. The larger the array and the lower the index of the removed element, the slower [method pop_at] will be. + Removes and returns the element of the array at index [param position]. If negative, [param position] is considered relative to the end of the array. Returns [code]null[/code] if the array is empty. If [param position] is out of bounds, an error message is also generated. + [b]Note:[/b] This method shifts every element's index after [param position] back, which may have a noticeable performance cost, especially on larger arrays. </description> </method> <method name="pop_back"> <return type="Variant" /> <description> - Removes and returns the last element of the array. Returns [code]null[/code] if the array is empty, without printing an error message. See also [method pop_front]. + Removes and returns the last element of the array. Returns [code]null[/code] if the array is empty, without generating an error. See also [method pop_front]. </description> </method> <method name="pop_front"> <return type="Variant" /> <description> - Removes and returns the first element of the array. Returns [code]null[/code] if the array is empty, without printing an error message. See also [method pop_back]. - [b]Note:[/b] On large arrays, this method is much slower than [method pop_back] as it will reindex all the array's elements every time it's called. The larger the array, the slower [method pop_front] will be. + Removes and returns the first element of the array. Returns [code]null[/code] if the array is empty, without generating an error. See also [method pop_back]. + [b]Note:[/b] This method shifts every other element's index back, which may have a noticeable performance cost, especially on larger arrays. </description> </method> <method name="push_back"> @@ -545,7 +587,7 @@ <param index="0" name="value" type="Variant" /> <description> Adds an element at the beginning of the array. See also [method push_back]. - [b]Note:[/b] On large arrays, this method is much slower than [method push_back] as it will reindex all the array's elements every time it's called. The larger the array, the slower [method push_front] will be. + [b]Note:[/b] This method shifts every other element's index forward, which may have a noticeable performance cost, especially on larger arrays. </description> </method> <method name="reduce" qualifiers="const"> @@ -553,15 +595,29 @@ <param index="0" name="method" type="Callable" /> <param index="1" name="accum" type="Variant" default="null" /> <description> - Calls the provided [Callable] for each element in array and accumulates the result in [param accum]. - The callable's method takes two arguments: the current value of [param accum] and the current array element. If [param accum] is [code]null[/code] (default value), the iteration will start from the second element, with the first one used as initial value of [param accum]. + Calls the given [Callable] for each element in array, accumulates the result in [param accum], then returns it. + The [param method] takes two arguments: the current value of [param accum] and the current array element. If [param accum] is [code]null[/code] (as by default), the iteration will start from the second element, with the first one used as initial value of [param accum]. [codeblock] - func _ready(): - print([1, 2, 3].reduce(sum, 10)) # Prints 16. - print([1, 2, 3].reduce(func(accum, number): return accum + number, 10)) # Same as above, but using lambda function. - func sum(accum, number): return accum + number + + func _ready(): + print([1, 2, 3].reduce(sum, 0)) # Prints 6 + print([1, 2, 3].reduce(sum, 10)) # Prints 16 + + # Same as above, but using a lambda function. + print([1, 2, 3].reduce(func(accum, number): return accum + number, 10)) + [/codeblock] + If [method max] is not desirable, this method may also be used to implement a custom comparator: + [codeblock] + func _ready(): + var arr = [Vector2(5, 0), Vector2(3, 4), Vector2(1, 2)] + + var longest_vec = arr.reduce(func(max, vec): return vec if is_length_greater(vec, max) else max) + print(longest_vec) # Prints Vector2(3, 4). + + func is_length_greater(a, b): + return a.length() > b.length() [/codeblock] See also [method map], [method filter], [method any] and [method all]. </description> @@ -570,25 +626,25 @@ <return type="void" /> <param index="0" name="position" type="int" /> <description> - Removes an element from the array by index. If the index does not exist in the array, nothing happens. To remove an element by searching for its value, use [method erase] instead. - [b]Note:[/b] This method acts in-place and doesn't return a modified array. - [b]Note:[/b] On large arrays, this method will be slower if the removed element is close to the beginning of the array (index 0). This is because all elements placed after the removed element have to be reindexed. - [b]Note:[/b] [param position] cannot be negative. To remove an element relative to the end of the array, use [code]arr.remove_at(arr.size() - (i + 1))[/code]. To remove the last element from the array without returning the value, use [code]arr.resize(arr.size() - 1)[/code]. + Removes the element from the array at the given index ([param position]). If the index is out of bounds, this method fails. + If you need to return the removed element, use [method pop_at]. To remove an element by value, use [method erase] instead. + [b]Note:[/b] This method shifts every element's index after [param position] back, which may have a noticeable performance cost, especially on larger arrays. + [b]Note:[/b] The [param position] cannot be negative. To remove an element relative to the end of the array, use [code]arr.remove_at(arr.size() - (i + 1))[/code]. To remove the last element from the array, use [code]arr.resize(arr.size() - 1)[/code]. </description> </method> <method name="resize"> <return type="int" /> <param index="0" name="size" type="int" /> <description> - Resizes the array to contain a different number of elements. If the array size is smaller, elements are cleared, if bigger, new elements are [code]null[/code]. Returns [constant OK] on success, or one of the other [enum Error] values if the operation failed. - Calling [method resize] once and assigning the new values is faster than adding new elements one by one. - [b]Note:[/b] This method acts in-place and doesn't return a modified array. + Sets the array's number of elements to [param size]. If [param size] is smaller than the array's current size, the elements at the end are removed. If [param size] is greater, new default elements (usually [code]null[/code]) are added, depending on the array's type. + Returns [constant OK] on success, or one of the other [enum Error] constants if this method fails. + [b]Note:[/b] Calling this method once and assigning the new values is faster than calling [method append] for every new element. </description> </method> <method name="reverse"> <return type="void" /> <description> - Reverses the order of the elements in the array. + Reverses the order of all elements in the array. </description> </method> <method name="rfind" qualifiers="const"> @@ -596,19 +652,20 @@ <param index="0" name="what" type="Variant" /> <param index="1" name="from" type="int" default="-1" /> <description> - Searches the array in reverse order. Optionally, a start search index can be passed. If negative, the start index is considered relative to the end of the array. + Returns the index of the [b]last[/b] occurrence of [param what] in this array, or [code]-1[/code] if there are none. The search's start can be specified with [param from], continuing to the beginning of the array. This method is the reverse of [method find]. </description> </method> <method name="shuffle"> <return type="void" /> <description> - Shuffles the array such that the items will have a random order. This method uses the global random number generator common to methods such as [method @GlobalScope.randi]. Call [method @GlobalScope.randomize] to ensure that a new seed will be used each time if you want non-reproducible shuffling. + Shuffles all elements of the array in a random order. + [b]Note:[/b] Like many similar functions in the engine (such as [method @GlobalScope.randi] or [method pick_random]), this method uses a common, global random seed. To get a predictable outcome from this method, see [method @GlobalScope.seed]. </description> </method> <method name="size" qualifiers="const"> <return type="int" /> <description> - Returns the number of elements in the array. + Returns the number of elements in the array. Empty arrays ([code][][/code]) always return [code]0[/code]. See also [method is_empty]. </description> </method> <method name="slice" qualifiers="const"> @@ -618,67 +675,71 @@ <param index="2" name="step" type="int" default="1" /> <param index="3" name="deep" type="bool" default="false" /> <description> - Returns the slice of the [Array], from [param begin] (inclusive) to [param end] (exclusive), as a new [Array]. - The absolute value of [param begin] and [param end] will be clamped to the array size, so the default value for [param end] makes it slice to the size of the array by default (i.e. [code]arr.slice(1)[/code] is a shorthand for [code]arr.slice(1, arr.size())[/code]). - If either [param begin] or [param end] are negative, they will be relative to the end of the array (i.e. [code]arr.slice(0, -2)[/code] is a shorthand for [code]arr.slice(0, arr.size() - 2)[/code]). - If specified, [param step] is the relative index between source elements. It can be negative, then [param begin] must be higher than [param end]. For example, [code][0, 1, 2, 3, 4, 5].slice(5, 1, -2)[/code] returns [code][5, 3][/code]. - If [param deep] is true, each element will be copied by value rather than by reference. - [b]Note:[/b] To include the first element when [param step] is negative, use [code]arr.slice(begin, -arr.size() - 1, step)[/code] (i.e. [code][0, 1, 2].slice(1, -4, -1)[/code] returns [code][1, 0][/code]). + Returns a new [Array] containing this array's elements, from index [param begin] (inclusive) to [param end] (exclusive), every [param step] elements. + If either [param begin] or [param end] are negative, their value is relative to the end of the array. + If [param step] is negative, this method iterates through the array in reverse, returning a slice ordered backwards. For this to work, [param begin] must be greater than [param end]. + If [param deep] is [code]true[/code], all nested [Array] and [Dictionary] elements in the slice are duplicated from the original, recursively. See also [method duplicate]). + [codeblock] + var letters = ["A", "B", "C", "D", "E", "F"] + + print(letters.slice(0, 2)) # Prints ["A", "B"] + print(letters.slice(2, -2)) # Prints ["C", "D"] + print(letters.slice(-2, 6)) # Prints ["E", "F"] + + print(letters.slice(0, 6, 2)) # Prints ["A", "C", "E"] + print(letters.slice(4, 1, -1)) # Prints ["E", "D", "C"] + [/codeblock] </description> </method> <method name="sort"> <return type="void" /> <description> - Sorts the array. - [b]Note:[/b] The sorting algorithm used is not [url=https://en.wikipedia.org/wiki/Sorting_algorithm#Stability]stable[/url]. This means that values considered equal may have their order changed when using [method sort]. - [b]Note:[/b] Strings are sorted in alphabetical order (as opposed to natural order). This may lead to unexpected behavior when sorting an array of strings ending with a sequence of numbers. Consider the following example: + Sorts the array in ascending order. The final order is dependent on the "less than" ([code]>[/code]) comparison between elements. [codeblocks] [gdscript] - var strings = ["string1", "string2", "string10", "string11"] - strings.sort() - print(strings) # Prints [string1, string10, string11, string2] + var numbers = [10, 5, 2.5, 8] + numbers.sort() + print(numbers) # Prints [2.5, 5, 8, 10] [/gdscript] [csharp] - var strings = new Godot.Collections.Array { "string1", "string2", "string10", "string11" }; - strings.Sort(); - GD.Print(strings); // Prints [string1, string10, string11, string2] + var numbers = new Godot.Collections.Array { 10, 5, 2.5, 8 }; + numbers.Sort(); + GD.Print(numbers); // Prints [2.5, 5, 8, 10] [/csharp] [/codeblocks] - To perform natural order sorting, you can use [method sort_custom] with [method String.naturalnocasecmp_to] as follows: - [codeblock] - var strings = ["string1", "string2", "string10", "string11"] - strings.sort_custom(func(a, b): return a.naturalnocasecmp_to(b) < 0) - print(strings) # Prints [string1, string2, string10, string11] - [/codeblock] + [b]Note:[/b] The sorting algorithm used is not [url=https://en.wikipedia.org/wiki/Sorting_algorithm#Stability]stable[/url]. This means that equivalent elements (such as [code]2[/code] and [code]2.0[/code]) may have their order changed when calling [method sort]. </description> </method> <method name="sort_custom"> <return type="void" /> <param index="0" name="func" type="Callable" /> <description> - Sorts the array using a custom method. The custom method receives two arguments (a pair of elements from the array) and must return either [code]true[/code] or [code]false[/code]. For two elements [code]a[/code] and [code]b[/code], if the given method returns [code]true[/code], element [code]b[/code] will be after element [code]a[/code] in the array. - [b]Note:[/b] The sorting algorithm used is not [url=https://en.wikipedia.org/wiki/Sorting_algorithm#Stability]stable[/url]. This means that values considered equal may have their order changed when using [method sort_custom]. - [b]Note:[/b] You cannot randomize the return value as the heapsort algorithm expects a deterministic result. Randomizing the return value will result in unexpected behavior. - [codeblocks] - [gdscript] + Sorts the array using a custom [Callable]. + [param func] is called as many times as necessary, receiving two array elements as arguments. The function should return [code]true[/code] if the first element should be moved [i]behind[/i] the second one, otherwise it should return [code]false[/code]. + [codeblock] func sort_ascending(a, b): - if a[0] < b[0]: + if a[1] < b[1]: return true return false func _ready(): - var my_items = [[5, "Potato"], [9, "Rice"], [4, "Tomato"]] + var my_items = [["Tomato", 5], ["Apple", 9], ["Rice", 4]] my_items.sort_custom(sort_ascending) - print(my_items) # Prints [[4, Tomato], [5, Potato], [9, Rice]]. + print(my_items) # Prints [["Rice", 4], ["Tomato", 5], ["Apple", 9]] - # Descending, lambda version. + # Sort descending, using a lambda function. my_items.sort_custom(func(a, b): return a[0] > b[0]) - print(my_items) # Prints [[9, Rice], [5, Potato], [4, Tomato]]. - [/gdscript] - [csharp] - // There is no custom sort support for Godot.Collections.Array - [/csharp] - [/codeblocks] + print(my_items) # Prints [["Apple", 9], ["Tomato", 5], ["Rice", 4]] + [/codeblock] + It may also be necessary to use this method to sort strings by natural order, with [method String.naturalnocasecmp_to], as in the following example: + [codeblock] + var files = ["newfile1", "newfile2", "newfile10", "newfile11"] + files.sort_custom(func(a, b): return a.naturalnocasecmp_to(b) < 0) + print(files) # Prints ["newfile1", "newfile2", "newfile10", "newfile11"] + [/codeblock] + [b]Note:[/b] In C#, this method is not supported. + [b]Note:[/b] The sorting algorithm used is not [url=https://en.wikipedia.org/wiki/Sorting_algorithm#Stability]stable[/url]. This means that values considered equal may have their order changed when calling this method. + [b]Note:[/b] You should not randomize the return value of [param func], as the heapsort algorithm expects a consistent result. Randomizing the return value will result in unexpected behavior. </description> </method> </methods> @@ -687,28 +748,44 @@ <return type="bool" /> <param index="0" name="right" type="Array" /> <description> - Compares the left operand [Array] against the [param right] [Array]. Returns [code]true[/code] if the sizes or contents of the arrays are [i]not[/i] equal, [code]false[/code] otherwise. + Returns [code]true[/code] if the array's size or its elements are different than [param right]'s. </description> </operator> <operator name="operator +"> <return type="Array" /> <param index="0" name="right" type="Array" /> <description> - Concatenates two [Array]s together, with the [param right] [Array] being added to the end of the [Array] specified in the left operand. For example, [code][1, 2] + [3, 4][/code] results in [code][1, 2, 3, 4][/code]. + Appends the [param right] array to the left operand, creating a new [Array]. This is also known as an array concatenation. + [codeblocks] + [gdscript] + var array1 = ["One", 2] + var array2 = [3, "Four"] + print(array1 + array2) # Prints ["One", 2, 3, "Four"] + [/gdscript] + [csharp] + // Note that concatenation is not possible with C#'s native Array type. + var array1 = new Godot.Collections.Array{"One", 2}; + var array2 = new Godot.Collections.Array{3, "Four"}; + GD.Print(array1 + array2); // Prints ["One", 2, 3, "Four"] + [/csharp] + [/codeblocks] + [b]Note:[/b] For existing arrays, [method append_array] is much more efficient than concatenation and assignment with the [code]+=[/code] operator. </description> </operator> <operator name="operator <"> <return type="bool" /> <param index="0" name="right" type="Array" /> <description> - Performs a comparison for each index between the left operand [Array] and the [param right] [Array], considering the highest common index of both arrays for this comparison: Returns [code]true[/code] on the first occurrence of an element that is less, or [code]false[/code] if the element is greater. Note that depending on the type of data stored, this function may be recursive. If all elements are equal, it compares the length of both arrays and returns [code]false[/code] if the left operand [Array] has fewer elements, otherwise it returns [code]true[/code]. + Compares the elements of both arrays in order, starting from index [code]0[/code] and ending on the last index in common between both arrays. For each pair of elements, returns [code]true[/code] if this array's element is less than [param right]'s, [code]false[/code] if this element is greater. Otherwise, continues to the next pair. + If all searched elements are equal, returns [code]true[/code] if this array's size is less than [param right]'s, otherwise returns [code]false[/code]. </description> </operator> <operator name="operator <="> <return type="bool" /> <param index="0" name="right" type="Array" /> <description> - Performs a comparison for each index between the left operand [Array] and the [param right] [Array], considering the highest common index of both arrays for this comparison: Returns [code]true[/code] on the first occurrence of an element that is less, or [code]false[/code] if the element is greater. Note that depending on the type of data stored, this function may be recursive. If all elements are equal, it compares the length of both arrays and returns [code]true[/code] if the left operand [Array] has the same number of elements or fewer, otherwise it returns [code]false[/code]. + Compares the elements of both arrays in order, starting from index [code]0[/code] and ending on the last index in common between both arrays. For each pair of elements, returns [code]true[/code] if this array's element is less than [param right]'s, [code]false[/code] if this element is greater. Otherwise, continues to the next pair. + If all searched elements are equal, returns [code]true[/code] if this array's size is less or equal to [param right]'s, otherwise returns [code]false[/code]. </description> </operator> <operator name="operator =="> @@ -722,21 +799,23 @@ <return type="bool" /> <param index="0" name="right" type="Array" /> <description> - Performs a comparison for each index between the left operand [Array] and the [param right] [Array], considering the highest common index of both arrays for this comparison: Returns [code]true[/code] on the first occurrence of an element that is greater, or [code]false[/code] if the element is less. Note that depending on the type of data stored, this function may be recursive. If all elements are equal, it compares the length of both arrays and returns [code]true[/code] if the [param right] [Array] has more elements, otherwise it returns [code]false[/code]. + Compares the elements of both arrays in order, starting from index [code]0[/code] and ending on the last index in common between both arrays. For each pair of elements, returns [code]true[/code] if this array's element is greater than [param right]'s, [code]false[/code] if this element is less. Otherwise, continues to the next pair. + If all searched elements are equal, returns [code]true[/code] if this array's size is greater than [param right]'s, otherwise returns [code]false[/code]. </description> </operator> <operator name="operator >="> <return type="bool" /> <param index="0" name="right" type="Array" /> <description> - Performs a comparison for each index between the left operand [Array] and the [param right] [Array], considering the highest common index of both arrays for this comparison: Returns [code]true[/code] on the first occurrence of an element that is greater, or [code]false[/code] if the element is less. Note that depending on the type of data stored, this function may be recursive. If all elements are equal, it compares the length of both arrays and returns [code]true[/code] if the [param right] [Array] has more or the same number of elements, otherwise it returns [code]false[/code]. + Compares the elements of both arrays in order, starting from index [code]0[/code] and ending on the last index in common between both arrays. For each pair of elements, returns [code]true[/code] if this array's element is greater than [param right]'s, [code]false[/code] if this element is less. Otherwise, continues to the next pair. + If all searched elements are equal, returns [code]true[/code] if this array's size is greater or equal to [param right]'s, otherwise returns [code]false[/code]. </description> </operator> <operator name="operator []"> <return type="Variant" /> <param index="0" name="index" type="int" /> <description> - Returns a reference to the element of type [Variant] at the specified location. Arrays start at index 0. [param index] can be a zero or positive value to start from the beginning, or a negative value to start from the end. Out-of-bounds array access causes a run-time error, which will result in an error being printed and the project execution pausing if run from the editor. + Returns the [Variant] element at the specified [param index]. Arrays start at index 0. If [param index] is greater or equal to [code]0[/code], the element is fetched starting from the beginning of the array. If [param index] is a negative value, the element is fetched starting from the end. Accessing an array out-of-bounds will cause a run-time error, pausing the project execution if run from the editor. </description> </operator> </operators> diff --git a/doc/classes/OS.xml b/doc/classes/OS.xml index 3d048e2f63..77caea9745 100644 --- a/doc/classes/OS.xml +++ b/doc/classes/OS.xml @@ -283,7 +283,7 @@ <return type="String" /> <description> Returns the file path to the current engine executable. - [b]Note:[/b] On macOS, always use [method create_instance] instead of relying on executable path. + [b]Note:[/b] On macOS, if you want to launch another instance of Godot, always use [method create_instance] instead of relying on the executable path. </description> </method> <method name="get_granted_permissions" qualifiers="const"> diff --git a/doc/classes/PopupMenu.xml b/doc/classes/PopupMenu.xml index 0f5687f091..004bbe2286 100644 --- a/doc/classes/PopupMenu.xml +++ b/doc/classes/PopupMenu.xml @@ -395,6 +395,12 @@ Returns [code]true[/code] if the specified item's shortcut is disabled. </description> </method> + <method name="is_native_menu" qualifiers="const"> + <return type="bool" /> + <description> + Returns [code]true[/code] if the system native menu is supported and currently used by this [PopupMenu]. + </description> + </method> <method name="is_system_menu" qualifiers="const"> <return type="bool" /> <description> @@ -636,6 +642,7 @@ </member> <member name="prefer_native_menu" type="bool" setter="set_prefer_native_menu" getter="is_prefer_native_menu" default="false"> If [code]true[/code], [MenuBar] will use native menu when supported. + [b]Note:[/b] If [PopupMenu] is linked to [StatusIndicator], [MenuBar], or another [PopupMenu] item it can use native menu regardless of this property, use [method is_native_menu] to check it. </member> <member name="submenu_popup_delay" type="float" setter="set_submenu_popup_delay" getter="get_submenu_popup_delay" default="0.3"> Sets the delay time in seconds for the submenu item to popup on mouse hovering. If the popup menu is added as a child of another (acting as a submenu), it will inherit the delay time of the parent menu item. diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index 5c97a975fe..b0f421e932 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -633,6 +633,9 @@ <member name="debug/settings/profiler/max_functions" type="int" setter="" getter="" default="16384"> Maximum number of functions per frame allowed when profiling. </member> + <member name="debug/settings/profiler/max_timestamp_query_elements" type="int" setter="" getter="" default="256"> + Maximum number of timestamp query elements allowed per frame for visual profiling. + </member> <member name="debug/settings/stdout/print_fps" type="bool" setter="" getter="" default="false"> Print frames per second to standard output every second. </member> @@ -897,8 +900,8 @@ <member name="display/window/stretch/mode" type="String" setter="" getter="" default=""disabled""> Defines how the base size is stretched to fit the resolution of the window or screen. [b]"disabled"[/b]: No stretching happens. One unit in the scene corresponds to one pixel on the screen. In this mode, [member display/window/stretch/aspect] has no effect. Recommended for non-game applications. - [b]"canvas_items"[/b]: The base size specified in width and height in the project settings is stretched to cover the whole screen (taking [member display/window/stretch/aspect] into account). This means that everything is rendered directly at the target resolution. 3D is unaffected, while in 2D, there is no longer a 1:1 correspondence between sprite pixels and screen pixels, which may result in scaling artifacts. Recommended for most games that don't use a pixel art esthetic, although it is possible to use this stretch mode for pixel art games too (especially in 3D). - [b]"viewport"[/b]: The size of the root [Viewport] is set precisely to the base size specified in the Project Settings' Display section. The scene is rendered to this viewport first. Finally, this viewport is scaled to fit the screen (taking [member display/window/stretch/aspect] into account). Recommended for games that use a pixel art esthetic. + [b]"canvas_items"[/b]: The base size specified in width and height in the project settings is stretched to cover the whole screen (taking [member display/window/stretch/aspect] into account). This means that everything is rendered directly at the target resolution. 3D is unaffected, while in 2D, there is no longer a 1:1 correspondence between sprite pixels and screen pixels, which may result in scaling artifacts. Recommended for most games that don't use a pixel art aesthetic, although it is possible to use this stretch mode for pixel art games too (especially in 3D). + [b]"viewport"[/b]: The size of the root [Viewport] is set precisely to the base size specified in the Project Settings' Display section. The scene is rendered to this viewport first. Finally, this viewport is scaled to fit the screen (taking [member display/window/stretch/aspect] into account). Recommended for games that use a pixel art aesthetic. </member> <member name="display/window/stretch/scale" type="float" setter="" getter="" default="1.0"> The scale factor multiplier to use for 2D elements. This multiplies the final scale factor determined by [member display/window/stretch/mode]. If using the [b]Disabled[/b] stretch mode, this scale factor is applied as-is. This can be adjusted to make the UI easier to read on certain displays. @@ -931,8 +934,9 @@ Changing this value allows setting up a multi-project scenario where there are multiple [code].csproj[/code]. Keep in mind that the Godot project is considered one of the C# projects in the workspace and it's root directory should contain the [code]project.godot[/code] and [code].csproj[/code] next to each other. </member> <member name="editor/export/convert_text_resources_to_binary" type="bool" setter="" getter="" default="true"> - If [code]true[/code], text resources are converted to a binary format on export. This decreases file sizes and speeds up loading slightly. - [b]Note:[/b] If [member editor/export/convert_text_resources_to_binary] is [code]true[/code], [method @GDScript.load] will not be able to return the converted files in an exported project. Some file paths within the exported PCK will also change, such as [code]project.godot[/code] becoming [code]project.binary[/code]. If you rely on run-time loading of files present within the PCK, set [member editor/export/convert_text_resources_to_binary] to [code]false[/code]. + If [code]true[/code], text resource ([code]tres[/code]) and text scene ([code]tscn[/code]) files are converted to their corresponding binary format on export. This decreases file sizes and speeds up loading slightly. + [b]Note:[/b] Because a resource's file extension may change in an exported project, it is heavily recommended to use [method @GDScript.load] or [ResourceLoader] instead of [FileAccess] to load resources dynamically. + [b]Note:[/b] The project settings file ([code]project.godot[/code]) will always be converted to binary on export, regardless of this setting. </member> <member name="editor/import/atlas_max_width" type="int" setter="" getter="" default="2048"> The maximum width to use when importing textures as an atlas. The value will be rounded to the nearest power of two when used. Use this to prevent imported textures from growing too large in the other direction. diff --git a/doc/classes/ScriptEditorBase.xml b/doc/classes/ScriptEditorBase.xml index 403608355a..638bc921d6 100644 --- a/doc/classes/ScriptEditorBase.xml +++ b/doc/classes/ScriptEditorBase.xml @@ -72,7 +72,7 @@ </description> </signal> <signal name="request_save_previous_state"> - <param index="0" name="line" type="int" /> + <param index="0" name="state" type="Dictionary" /> <description> Emitted when the user changes current script or moves caret by 10 or more columns within the same script. </description> diff --git a/drivers/gles3/rasterizer_scene_gles3.cpp b/drivers/gles3/rasterizer_scene_gles3.cpp index 8e89889fd1..a6796a1a6b 100644 --- a/drivers/gles3/rasterizer_scene_gles3.cpp +++ b/drivers/gles3/rasterizer_scene_gles3.cpp @@ -2499,7 +2499,9 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_ glColorMask(0, 0, 0, 0); RasterizerGLES3::clear_depth(0.0); glClear(GL_DEPTH_BUFFER_BIT); - glDrawBuffers(0, nullptr); + // Some desktop GL implementations fall apart when using Multiview with GL_NONE. + GLuint db = p_camera_data->view_count > 1 ? GL_COLOR_ATTACHMENT0 : GL_NONE; + glDrawBuffers(1, &db); uint64_t spec_constant = SceneShaderGLES3::DISABLE_FOG | SceneShaderGLES3::DISABLE_LIGHT_DIRECTIONAL | SceneShaderGLES3::DISABLE_LIGHTMAP | SceneShaderGLES3::DISABLE_LIGHT_OMNI | diff --git a/editor/animation_bezier_editor.cpp b/editor/animation_bezier_editor.cpp index aa8962b580..48b9e01fd8 100644 --- a/editor/animation_bezier_editor.cpp +++ b/editor/animation_bezier_editor.cpp @@ -965,7 +965,7 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) { real_t minimum_value = INFINITY; real_t maximum_value = -INFINITY; - for (const IntPair &E : selection) { + for (const IntPair &E : focused_keys) { IntPair key_pair = E; real_t time = animation->track_get_key_time(key_pair.first, key_pair.second); @@ -1096,7 +1096,8 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) { for (int i = 0; i < animation->track_get_key_count(track); ++i) { undo_redo->add_undo_method( this, - "_bezier_track_insert_key", + "_bezier_track_insert_key_at_anim", + animation, track, animation->track_get_key_time(track, i), animation->bezier_track_get_key_value(track, i), @@ -1370,7 +1371,8 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) { key[0] = h; undo_redo->add_do_method( this, - "_bezier_track_insert_key", + "_bezier_track_insert_key_at_anim", + animation, E->get().first, newpos, key[0], @@ -1391,7 +1393,8 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) { Array key = animation->track_get_key_value(E->get().first, E->get().second); undo_redo->add_undo_method( this, - "_bezier_track_insert_key", + "_bezier_track_insert_key_at_anim", + animation, E->get().first, oldpos, key[0], @@ -1409,7 +1412,8 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) { undo_redo->add_undo_method(animation.ptr(), "track_insert_key", amr.track, amr.time, amr.key, 1); undo_redo->add_undo_method( this, - "_bezier_track_insert_key", + "_bezier_track_insert_key_at_anim", + animation, amr.track, amr.time, key[0], @@ -1918,10 +1922,9 @@ void AnimationBezierTrackEdit::delete_selection() { } } -void AnimationBezierTrackEdit::_bezier_track_insert_key(int p_track, double p_time, real_t p_value, const Vector2 &p_in_handle, const Vector2 &p_out_handle, const Animation::HandleMode p_handle_mode) { - ERR_FAIL_COND(animation.is_null()); - int idx = animation->bezier_track_insert_key(p_track, p_time, p_value, p_in_handle, p_out_handle); - animation->bezier_track_set_key_handle_mode(p_track, idx, p_handle_mode); +void AnimationBezierTrackEdit::_bezier_track_insert_key_at_anim(const Ref<Animation> &p_anim, int p_track, double p_time, real_t p_value, const Vector2 &p_in_handle, const Vector2 &p_out_handle, const Animation::HandleMode p_handle_mode) { + int idx = p_anim->bezier_track_insert_key(p_track, p_time, p_value, p_in_handle, p_out_handle); + p_anim->bezier_track_set_key_handle_mode(p_track, idx, p_handle_mode); } void AnimationBezierTrackEdit::_bind_methods() { @@ -1930,7 +1933,7 @@ void AnimationBezierTrackEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("_select_at_anim"), &AnimationBezierTrackEdit::_select_at_anim); ClassDB::bind_method(D_METHOD("_update_hidden_tracks_after"), &AnimationBezierTrackEdit::_update_hidden_tracks_after); ClassDB::bind_method(D_METHOD("_update_locked_tracks_after"), &AnimationBezierTrackEdit::_update_locked_tracks_after); - ClassDB::bind_method(D_METHOD("_bezier_track_insert_key"), &AnimationBezierTrackEdit::_bezier_track_insert_key); + ClassDB::bind_method(D_METHOD("_bezier_track_insert_key_at_anim"), &AnimationBezierTrackEdit::_bezier_track_insert_key_at_anim); ADD_SIGNAL(MethodInfo("select_key", PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::BOOL, "single"), PropertyInfo(Variant::INT, "track"))); ADD_SIGNAL(MethodInfo("deselect_key", PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::INT, "track"))); diff --git a/editor/animation_bezier_editor.h b/editor/animation_bezier_editor.h index 3067c923b9..dd7e8758f3 100644 --- a/editor/animation_bezier_editor.h +++ b/editor/animation_bezier_editor.h @@ -221,7 +221,7 @@ public: void paste_keys(real_t p_ofs, bool p_ofs_valid); void delete_selection(); - void _bezier_track_insert_key(int p_track, double p_time, real_t p_value, const Vector2 &p_in_handle, const Vector2 &p_out_handle, const Animation::HandleMode p_handle_mode); + void _bezier_track_insert_key_at_anim(const Ref<Animation> &p_anim, int p_track, double p_time, real_t p_value, const Vector2 &p_in_handle, const Vector2 &p_out_handle, const Animation::HandleMode p_handle_mode); AnimationBezierTrackEdit(); }; diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp index 6810b802a1..b6636ca576 100644 --- a/editor/animation_track_editor.cpp +++ b/editor/animation_track_editor.cpp @@ -3690,7 +3690,7 @@ void AnimationTrackEditor::_name_limit_changed() { } void AnimationTrackEditor::_timeline_changed(float p_new_pos, bool p_timeline_only) { - emit_signal(SNAME("timeline_changed"), p_new_pos, p_timeline_only); + emit_signal(SNAME("timeline_changed"), p_new_pos, p_timeline_only, false); } void AnimationTrackEditor::_track_remove_request(int p_track) { @@ -3787,6 +3787,7 @@ void AnimationTrackEditor::set_anim_pos(float p_pos) { } _redraw_groups(); bezier_edit->set_play_position(p_pos); + emit_signal(SNAME("timeline_changed"), p_pos, true, true); } static bool track_type_is_resettable(Animation::TrackType p_type) { @@ -4428,7 +4429,7 @@ AnimationTrackEditor::TrackIndices AnimationTrackEditor::_confirm_insert(InsertD for (int i = 0; i < subindices.size(); i++) { InsertData id = p_id; id.type = Animation::TYPE_BEZIER; - id.value = p_id.value.get(subindices[i].substr(1, subindices[i].length())); + id.value = subindices[i].is_empty() ? p_id.value : p_id.value.get(subindices[i].substr(1, subindices[i].length())); id.path = String(p_id.path) + subindices[i]; p_next_tracks = _confirm_insert(id, p_next_tracks, p_reset_wanted, p_reset_anim, false); } diff --git a/editor/editor_dock_manager.cpp b/editor/editor_dock_manager.cpp index ccb47220db..75135532aa 100644 --- a/editor/editor_dock_manager.cpp +++ b/editor/editor_dock_manager.cpp @@ -277,7 +277,7 @@ void EditorDockManager::_restore_dock_to_saved_window(Control *p_dock, const Dic p_window_dump.get("window_screen_rect", Rect2i())); } -void EditorDockManager::_dock_move_to_bottom(Control *p_dock) { +void EditorDockManager::_dock_move_to_bottom(Control *p_dock, bool p_visible) { _move_dock(p_dock, nullptr); all_docks[p_dock].at_bottom = true; @@ -288,7 +288,7 @@ void EditorDockManager::_dock_move_to_bottom(Control *p_dock) { // Force docks moved to the bottom to appear first in the list, and give them their associated shortcut to toggle their bottom panel. Button *bottom_button = EditorNode::get_bottom_panel()->add_item(all_docks[p_dock].title, p_dock, all_docks[p_dock].shortcut, true); bottom_button->connect(SceneStringName(gui_input), callable_mp(this, &EditorDockManager::_bottom_dock_button_gui_input).bind(bottom_button).bind(p_dock)); - EditorNode::get_bottom_panel()->make_item_visible(p_dock); + EditorNode::get_bottom_panel()->make_item_visible(p_dock, p_visible); } void EditorDockManager::_dock_remove_from_bottom(Control *p_dock) { @@ -548,11 +548,13 @@ void EditorDockManager::load_docks_from_config(Ref<ConfigFile> p_layout, const S // Don't open disabled docks. continue; } + bool at_bottom = false; if (restore_window_on_load && floating_docks_dump.has(name)) { all_docks[dock].previous_at_bottom = dock_bottom.has(name); _restore_dock_to_saved_window(dock, floating_docks_dump[name]); } else if (dock_bottom.has(name)) { - _dock_move_to_bottom(dock); + _dock_move_to_bottom(dock, false); + at_bottom = true; } else if (i >= 0) { _move_dock(dock, dock_slot[i], 0); } @@ -564,7 +566,11 @@ void EditorDockManager::load_docks_from_config(Ref<ConfigFile> p_layout, const S } else { // Make sure it is open. all_docks[dock].open = true; - dock->show(); + // It's important to not update the visibility of bottom panels. + // Visibility of bottom panels are managed in EditorBottomPanel. + if (!at_bottom) { + dock->show(); + } } all_docks[dock].dock_slot_index = i; @@ -668,7 +674,7 @@ void EditorDockManager::open_dock(Control *p_dock, bool p_set_current) { // Open dock to its previous location. if (all_docks[p_dock].previous_at_bottom) { - _dock_move_to_bottom(p_dock); + _dock_move_to_bottom(p_dock, true); } else if (all_docks[p_dock].dock_slot_index != DOCK_SLOT_NONE) { TabContainer *slot = dock_slot[all_docks[p_dock].dock_slot_index]; int tab_index = all_docks[p_dock].previous_tab_index; @@ -899,7 +905,7 @@ void DockContextPopup::_float_dock() { void DockContextPopup::_move_dock_to_bottom() { hide(); - dock_manager->_dock_move_to_bottom(context_dock); + dock_manager->_dock_move_to_bottom(context_dock, true); dock_manager->_update_layout(); } diff --git a/editor/editor_dock_manager.h b/editor/editor_dock_manager.h index 226222c55a..1e6b413d14 100644 --- a/editor/editor_dock_manager.h +++ b/editor/editor_dock_manager.h @@ -121,7 +121,7 @@ private: void _open_dock_in_window(Control *p_dock, bool p_show_window = true, bool p_reset_size = false); void _restore_dock_to_saved_window(Control *p_dock, const Dictionary &p_window_dump); - void _dock_move_to_bottom(Control *p_dock); + void _dock_move_to_bottom(Control *p_dock, bool p_visible); void _dock_remove_from_bottom(Control *p_dock); bool _is_dock_at_bottom(Control *p_dock); diff --git a/editor/editor_file_system.cpp b/editor/editor_file_system.cpp index 65ae25e046..4664defa59 100644 --- a/editor/editor_file_system.cpp +++ b/editor/editor_file_system.cpp @@ -150,6 +150,11 @@ uint64_t EditorFileSystemDirectory::get_file_modified_time(int p_idx) const { return files[p_idx]->modified_time; } +uint64_t EditorFileSystemDirectory::get_file_import_modified_time(int p_idx) const { + ERR_FAIL_INDEX_V(p_idx, files.size(), 0); + return files[p_idx]->import_modified_time; +} + String EditorFileSystemDirectory::get_file_script_class_name(int p_idx) const { return files[p_idx]->script_class_name; } @@ -720,12 +725,22 @@ bool EditorFileSystem::_update_scan_actions() { int idx = ia.dir->find_file_index(ia.file); ERR_CONTINUE(idx == -1); String full_path = ia.dir->get_file_path(idx); - if (_test_for_reimport(full_path, false)) { + + bool need_reimport = _test_for_reimport(full_path, false); + if (!need_reimport && FileAccess::exists(full_path + ".import")) { + uint64_t import_mt = ia.dir->get_file_import_modified_time(idx); + if (import_mt != FileAccess::get_modified_time(full_path + ".import")) { + need_reimport = true; + } + } + + if (need_reimport) { //must reimport reimports.push_back(full_path); Vector<String> dependencies = _get_dependencies(full_path); - for (const String &dependency_path : dependencies) { - if (import_extensions.has(dependency_path.get_extension())) { + for (const String &dep : dependencies) { + const String &dependency_path = dep.contains("::") ? dep.get_slice("::", 0) : dep; + if (import_extensions.has(dep.get_extension())) { reimports.push_back(dependency_path); } } @@ -1748,7 +1763,8 @@ String EditorFileSystem::_get_global_script_class(const String &p_type, const St void EditorFileSystem::_update_file_icon_path(EditorFileSystemDirectory::FileInfo *file_info) { String icon_path; if (file_info->script_class_icon_path.is_empty() && !file_info->deps.is_empty()) { - const String &script_path = file_info->deps[0]; // Assuming the first dependency is a script. + const String &script_dep = file_info->deps[0]; // Assuming the first dependency is a script. + const String &script_path = script_dep.contains("::") ? script_dep.get_slice("::", 2) : script_dep; if (!script_path.is_empty()) { String *cached = file_icon_cache.getptr(script_path); if (cached) { diff --git a/editor/editor_file_system.h b/editor/editor_file_system.h index aee40ed23d..1bc24416eb 100644 --- a/editor/editor_file_system.h +++ b/editor/editor_file_system.h @@ -89,6 +89,7 @@ public: Vector<String> get_file_deps(int p_idx) const; bool get_file_import_is_valid(int p_idx) const; uint64_t get_file_modified_time(int p_idx) const; + uint64_t get_file_import_modified_time(int p_idx) const; String get_file_script_class_name(int p_idx) const; //used for scripts String get_file_script_class_extends(int p_idx) const; //used for scripts String get_file_script_class_icon_path(int p_idx) const; //used for scripts diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp index 00ac1c7c6f..5725129f65 100644 --- a/editor/editor_help.cpp +++ b/editor/editor_help.cpp @@ -3761,6 +3761,12 @@ EditorHelpBit::EditorHelpBit(const String &p_symbol) { /// EditorHelpBitTooltip /// +void EditorHelpBitTooltip::_start_timer() { + if (timer->is_inside_tree() && timer->is_stopped()) { + timer->start(); + } +} + void EditorHelpBitTooltip::_safe_queue_free() { if (_pushing_input > 0) { _need_free = true; @@ -3769,13 +3775,20 @@ void EditorHelpBitTooltip::_safe_queue_free() { } } +void EditorHelpBitTooltip::_target_gui_input(const Ref<InputEvent> &p_event) { + const Ref<InputEventMouse> mouse_event = p_event; + if (mouse_event.is_valid()) { + _start_timer(); + } +} + void EditorHelpBitTooltip::_notification(int p_what) { switch (p_what) { case NOTIFICATION_WM_MOUSE_ENTER: timer->stop(); break; case NOTIFICATION_WM_MOUSE_EXIT: - timer->start(); + _start_timer(); break; } } @@ -3783,7 +3796,7 @@ void EditorHelpBitTooltip::_notification(int p_what) { // Forwards non-mouse input to the parent viewport. void EditorHelpBitTooltip::_input_from_window(const Ref<InputEvent> &p_event) { if (p_event->is_action_pressed(SNAME("ui_cancel"), false, true)) { - hide(); // Will be deleted on its timer. + _safe_queue_free(); } else { const Ref<InputEventMouse> mouse_event = p_event; if (mouse_event.is_null()) { @@ -3801,7 +3814,7 @@ void EditorHelpBitTooltip::_input_from_window(const Ref<InputEvent> &p_event) { void EditorHelpBitTooltip::show_tooltip(EditorHelpBit *p_help_bit, Control *p_target) { ERR_FAIL_NULL(p_help_bit); EditorHelpBitTooltip *tooltip = memnew(EditorHelpBitTooltip(p_target)); - p_help_bit->connect("request_hide", callable_mp(static_cast<Window *>(tooltip), &Window::hide)); // Will be deleted on its timer. + p_help_bit->connect("request_hide", callable_mp(tooltip, &EditorHelpBitTooltip::_safe_queue_free)); tooltip->add_child(p_help_bit); p_target->get_viewport()->add_child(tooltip); p_help_bit->update_content_height(); @@ -3858,8 +3871,8 @@ EditorHelpBitTooltip::EditorHelpBitTooltip(Control *p_target) { add_child(timer); ERR_FAIL_NULL(p_target); - p_target->connect(SceneStringName(mouse_entered), callable_mp(timer, &Timer::stop)); - p_target->connect(SceneStringName(mouse_exited), callable_mp(timer, &Timer::start).bind(-1)); + p_target->connect(SceneStringName(mouse_exited), callable_mp(this, &EditorHelpBitTooltip::_start_timer)); + p_target->connect(SceneStringName(gui_input), callable_mp(this, &EditorHelpBitTooltip::_target_gui_input)); } #if defined(MODULE_GDSCRIPT_ENABLED) || defined(MODULE_MONO_ENABLED) diff --git a/editor/editor_help.h b/editor/editor_help.h index 8d1fec713e..93f74cb2c1 100644 --- a/editor/editor_help.h +++ b/editor/editor_help.h @@ -329,7 +329,9 @@ class EditorHelpBitTooltip : public PopupPanel { int _pushing_input = 0; bool _need_free = false; + void _start_timer(); void _safe_queue_free(); + void _target_gui_input(const Ref<InputEvent> &p_event); protected: void _notification(int p_what); diff --git a/editor/editor_log.cpp b/editor/editor_log.cpp index 6a016c217a..0dfbcd0e0d 100644 --- a/editor/editor_log.cpp +++ b/editor/editor_log.cpp @@ -58,8 +58,8 @@ void EditorLog::_error_handler(void *p_self, const char *p_func, const char *p_f MessageType message_type = p_type == ERR_HANDLER_WARNING ? MSG_TYPE_WARNING : MSG_TYPE_ERROR; - if (self->current != Thread::get_caller_id()) { - callable_mp(self, &EditorLog::add_message).call_deferred(err_str, message_type); + if (!Thread::is_main_thread()) { + MessageQueue::get_main_singleton()->push_callable(callable_mp(self, &EditorLog::add_message), err_str, message_type); } else { self->add_message(err_str, message_type); } @@ -557,8 +557,6 @@ EditorLog::EditorLog() { eh.errfunc = _error_handler; eh.userdata = this; add_error_handler(&eh); - - current = Thread::get_caller_id(); } void EditorLog::deinit() { diff --git a/editor/editor_log.h b/editor/editor_log.h index 7012a2a43c..9c652e912a 100644 --- a/editor/editor_log.h +++ b/editor/editor_log.h @@ -156,8 +156,6 @@ private: ErrorHandlerList eh; - Thread::ID current; - //void _dragged(const Point2& p_ofs); void _meta_clicked(const String &p_meta); void _clear_request(); diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 632b36c705..fd49920c6b 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -317,14 +317,10 @@ void EditorNode::shortcut_input(const Ref<InputEvent> &p_event) { Ref<InputEventKey> k = p_event; if ((k.is_valid() && k->is_pressed() && !k->is_echo()) || Object::cast_to<InputEventShortcut>(*p_event)) { - EditorPlugin *old_editor = editor_plugin_screen; - + bool is_handled = true; if (ED_IS_SHORTCUT("editor/filter_files", p_event)) { FileSystemDock::get_singleton()->focus_on_filter(); - get_tree()->get_root()->set_input_as_handled(); - } - - if (ED_IS_SHORTCUT("editor/editor_2d", p_event)) { + } else if (ED_IS_SHORTCUT("editor/editor_2d", p_event)) { editor_select(EDITOR_2D); } else if (ED_IS_SHORTCUT("editor/editor_3d", p_event)) { editor_select(EDITOR_3D); @@ -343,9 +339,10 @@ void EditorNode::shortcut_input(const Ref<InputEvent> &p_event) { } else if (ED_IS_SHORTCUT("editor/toggle_last_opened_bottom_panel", p_event)) { bottom_panel->toggle_last_opened_bottom_panel(); } else { + is_handled = false; } - if (old_editor != editor_plugin_screen) { + if (is_handled) { get_tree()->get_root()->set_input_as_handled(); } } @@ -5981,9 +5978,6 @@ void EditorNode::reload_instances_with_path_in_edited_scenes(const String &p_ins is_editable = owner->is_editable_instance(original_node); } - // For clear instance state for path recaching. - instantiated_node->set_scene_instance_state(Ref<SceneState>()); - bool original_node_is_displayed_folded = original_node->is_displayed_folded(); bool original_node_scene_instance_load_placeholder = original_node->get_scene_instance_load_placeholder(); diff --git a/editor/gui/editor_bottom_panel.cpp b/editor/gui/editor_bottom_panel.cpp index f2c4a13e05..3e74a3c94e 100644 --- a/editor/gui/editor_bottom_panel.cpp +++ b/editor/gui/editor_bottom_panel.cpp @@ -178,7 +178,11 @@ Button *EditorBottomPanel::add_item(String p_text, Control *p_item, const Ref<Sh bpi.button = tb; bpi.control = p_item; bpi.name = p_text; - items.push_back(bpi); + if (p_at_front) { + items.insert(0, bpi); + } else { + items.push_back(bpi); + } return tb; } diff --git a/editor/import/3d/resource_importer_obj.cpp b/editor/import/3d/resource_importer_obj.cpp index 6d68e93c75..a0c05598a2 100644 --- a/editor/import/3d/resource_importer_obj.cpp +++ b/editor/import/3d/resource_importer_obj.cpp @@ -425,9 +425,13 @@ static Error _parse_obj(const String &p_path, List<Ref<ImporterMesh>> &r_meshes, } if (!current_material.is_empty()) { - mesh->set_surface_name(mesh->get_surface_count() - 1, current_material.get_basename()); + if (mesh->get_surface_count() >= 1) { + mesh->set_surface_name(mesh->get_surface_count() - 1, current_material.get_basename()); + } } else if (!current_group.is_empty()) { - mesh->set_surface_name(mesh->get_surface_count() - 1, current_group); + if (mesh->get_surface_count() >= 1) { + mesh->set_surface_name(mesh->get_surface_count() - 1, current_group); + } } Array array = surf_tool->commit_to_arrays(); diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp index 484d2b1fff..02fa582da4 100644 --- a/editor/plugins/animation_player_editor_plugin.cpp +++ b/editor/plugins/animation_player_editor_plugin.cpp @@ -1395,23 +1395,14 @@ void AnimationPlayerEditor::_current_animation_changed(const String &p_name) { void AnimationPlayerEditor::_animation_key_editor_anim_len_changed(float p_len) { frame->set_max(p_len); } - -void AnimationPlayerEditor::_animation_key_editor_seek(float p_pos, bool p_timeline_only) { +void AnimationPlayerEditor::_animation_key_editor_seek(float p_pos, bool p_timeline_only, bool p_update_position_only) { timeline_position = p_pos; - if (!is_visible_in_tree()) { - return; - } - - if (!player) { - return; - } - - if (player->is_playing()) { - return; - } - - if (!player->has_animation(player->get_assigned_animation())) { + if (!is_visible_in_tree() || + p_update_position_only || + !player || + player->is_playing() || + !player->has_animation(player->get_assigned_animation())) { return; } diff --git a/editor/plugins/animation_player_editor_plugin.h b/editor/plugins/animation_player_editor_plugin.h index e624522566..4a3b1f37ab 100644 --- a/editor/plugins/animation_player_editor_plugin.h +++ b/editor/plugins/animation_player_editor_plugin.h @@ -214,7 +214,7 @@ class AnimationPlayerEditor : public VBoxContainer { void _animation_player_changed(Object *p_pl); void _animation_libraries_updated(); - void _animation_key_editor_seek(float p_pos, bool p_timeline_only = false); + void _animation_key_editor_seek(float p_pos, bool p_timeline_only = false, bool p_update_position_only = false); void _animation_key_editor_anim_len_changed(float p_len); virtual void shortcut_input(const Ref<InputEvent> &p_ev) override; diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index 4e2940a8cb..294df95874 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -5199,7 +5199,7 @@ CanvasItemEditor::CanvasItemEditor() { SceneTreeDock::get_singleton()->connect("node_created", callable_mp(this, &CanvasItemEditor::_adjust_new_node_position)); SceneTreeDock::get_singleton()->connect("add_node_used", callable_mp(this, &CanvasItemEditor::_reset_create_position)); - // Add some margin to the sides for better esthetics. + // Add some margin to the sides for better aesthetics. // This prevents the first button's hover/pressed effect from "touching" the panel's border, // which looks ugly. MarginContainer *toolbar_margin = memnew(MarginContainer); diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index c6a0dfb888..72eea8a27e 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -8474,7 +8474,7 @@ Node3DEditor::Node3DEditor() { camera_override_viewport_id = 0; - // Add some margin to the sides for better esthetics. + // Add some margin to the sides for better aesthetics. // This prevents the first button's hover/pressed effect from "touching" the panel's border, // which looks ugly. MarginContainer *toolbar_margin = memnew(MarginContainer); diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp index a670c7937b..c51eb44aee 100644 --- a/editor/plugins/script_editor_plugin.cpp +++ b/editor/plugins/script_editor_plugin.cpp @@ -272,7 +272,7 @@ void ScriptEditorBase::_bind_methods() { ADD_SIGNAL(MethodInfo("request_help", PropertyInfo(Variant::STRING, "topic"))); ADD_SIGNAL(MethodInfo("request_open_script_at_line", PropertyInfo(Variant::OBJECT, "script"), PropertyInfo(Variant::INT, "line"))); ADD_SIGNAL(MethodInfo("request_save_history")); - ADD_SIGNAL(MethodInfo("request_save_previous_state", PropertyInfo(Variant::INT, "line"))); + ADD_SIGNAL(MethodInfo("request_save_previous_state", PropertyInfo(Variant::DICTIONARY, "state"))); ADD_SIGNAL(MethodInfo("go_to_help", PropertyInfo(Variant::STRING, "what"))); ADD_SIGNAL(MethodInfo("search_in_files_requested", PropertyInfo(Variant::STRING, "text"))); ADD_SIGNAL(MethodInfo("replace_in_files_requested", PropertyInfo(Variant::STRING, "text"))); diff --git a/editor/plugins/tiles/tile_map_layer_editor.cpp b/editor/plugins/tiles/tile_map_layer_editor.cpp index d3afd25502..4a59530159 100644 --- a/editor/plugins/tiles/tile_map_layer_editor.cpp +++ b/editor/plugins/tiles/tile_map_layer_editor.cpp @@ -3630,9 +3630,16 @@ TileMapLayer *TileMapLayerEditor::_get_edited_layer() const { void TileMapLayerEditor::_find_tile_map_layers_in_scene(Node *p_current, const Node *p_owner, Vector<TileMapLayer *> &r_list) const { ERR_FAIL_COND(!p_current || !p_owner); - if (p_current != p_owner && p_current->get_owner() != p_owner) { - return; + + if (p_current != p_owner) { + if (!p_current->get_owner()) { + return; + } + if (p_current->get_owner() != p_owner && !p_owner->is_editable_instance(p_current->get_owner())) { + return; + } } + TileMapLayer *layer = Object::cast_to<TileMapLayer>(p_current); if (layer) { r_list.append(layer); diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index 94bd3e16d3..25de9facb2 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -2276,6 +2276,7 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V Vector<StringName> former_names; int inc = 0; + bool need_edit = false; for (int ni = 0; ni < p_nodes.size(); ni++) { // No undo implemented for this yet. @@ -2296,7 +2297,11 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V inc--; // If the child will generate a gap when moved, adjust. } - if (!same_parent) { + if (same_parent) { + // When node is reparented to the same parent, EditorSelection does not change. + // After hovering another node, the inspector has to be manually updated in this case. + need_edit = select_node_hovered_at_end_of_drag; + } else { undo_redo->add_do_method(node->get_parent(), "remove_child", node); undo_redo->add_do_method(new_parent, "add_child", node, true); } @@ -2401,6 +2406,10 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V perform_node_renames(nullptr, &path_renames); undo_redo->commit_action(); + + if (need_edit) { + EditorNode::get_singleton()->edit_current(); + } } void SceneTreeDock::_script_created(Ref<Script> p_script) { diff --git a/main/main.cpp b/main/main.cpp index e6be23034d..060b3fe2f6 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -4043,6 +4043,7 @@ bool Main::iteration() { } Engine::get_singleton()->_in_physics = true; + Engine::get_singleton()->_physics_frames++; uint64_t physics_begin = OS::get_singleton()->get_ticks_usec(); @@ -4090,7 +4091,6 @@ bool Main::iteration() { physics_process_ticks = MAX(physics_process_ticks, OS::get_singleton()->get_ticks_usec() - physics_begin); // keep the largest one for reference physics_process_max = MAX(OS::get_singleton()->get_ticks_usec() - physics_begin, physics_process_max); - Engine::get_singleton()->_physics_frames++; Engine::get_singleton()->_in_physics = false; } diff --git a/misc/extension_api_validation/4.2-stable.expected b/misc/extension_api_validation/4.2-stable.expected index 7b93df70fa..4b0d22a1aa 100644 --- a/misc/extension_api_validation/4.2-stable.expected +++ b/misc/extension_api_validation/4.2-stable.expected @@ -372,3 +372,10 @@ GH-91382 Validate extension JSON: Error: Field 'classes/AudioStreamPlaybackPolyphonic/methods/play_stream/arguments': size changed value in new API, from 4 to 6. Optional arguments added. Compatibility methods registered. + + +GH-93982 +-------- +Validate extension JSON: Error: Field 'classes/Sprite3D/properties/frame_coords': type changed value in new API, from "Vector2" to "Vector2i". + +The type was wrong to begin with and has been corrected. Vector2 and Vector2i are convertible, so it should be compatible. diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index f557727718..b58b44973e 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -1959,11 +1959,14 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, break; } - if (base.value.in(index.value)) { - Variant value = base.value.get(index.value); - r_type = _type_from_variant(value, p_context); - found = true; - break; + { + bool valid; + Variant value = base.value.get(index.value, &valid); + if (valid) { + r_type = _type_from_variant(value, p_context); + found = true; + break; + } } // Look if it is a dictionary node. diff --git a/modules/gdscript/gdscript_lambda_callable.cpp b/modules/gdscript/gdscript_lambda_callable.cpp index 42b0b066e1..2162a727b3 100644 --- a/modules/gdscript/gdscript_lambda_callable.cpp +++ b/modules/gdscript/gdscript_lambda_callable.cpp @@ -198,6 +198,10 @@ ObjectID GDScriptLambdaSelfCallable::get_object() const { return object->get_instance_id(); } +StringName GDScriptLambdaSelfCallable::get_method() const { + return function->get_name(); +} + int GDScriptLambdaSelfCallable::get_argument_count(bool &r_is_valid) const { if (function == nullptr) { r_is_valid = false; diff --git a/modules/gdscript/gdscript_lambda_callable.h b/modules/gdscript/gdscript_lambda_callable.h index 45c0235913..2d27b8d679 100644 --- a/modules/gdscript/gdscript_lambda_callable.h +++ b/modules/gdscript/gdscript_lambda_callable.h @@ -87,6 +87,7 @@ public: CompareEqualFunc get_compare_equal_func() const override; CompareLessFunc get_compare_less_func() const override; ObjectID get_object() const override; + StringName get_method() const override; int get_argument_count(bool &r_is_valid) const override; void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override; diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_array_assign_wrong_to_typed.out b/modules/gdscript/tests/scripts/runtime/errors/typed_array_assign_wrong_to_typed.out index 7b9f1066b0..9b38957101 100644 --- a/modules/gdscript/tests/scripts/runtime/errors/typed_array_assign_wrong_to_typed.out +++ b/modules/gdscript/tests/scripts/runtime/errors/typed_array_assign_wrong_to_typed.out @@ -1,4 +1,5 @@ GDTEST_RUNTIME_ERROR >> ERROR >> Method/function failed. +>> Unable to convert array index 0 from "Object" to "Object". not ok diff --git a/modules/gdscript/tests/scripts/runtime/features/lambda_get_method.gd b/modules/gdscript/tests/scripts/runtime/features/lambda_get_method.gd new file mode 100644 index 0000000000..160e43a797 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/lambda_get_method.gd @@ -0,0 +1,21 @@ +# https://github.com/godotengine/godot/issues/94074 + +func foo(): + pass + +func test(): + var lambda_self := func test() -> void: + foo() + var anon_lambda_self := func() -> void: + foo() + + print(lambda_self.get_method()) # Should print "test". + print(anon_lambda_self.get_method()) # Should print "<anonymous lambda>". + + var lambda_non_self := func test() -> void: + pass + var anon_lambda_non_self := func() -> void: + pass + + print(lambda_non_self.get_method()) # Should print "test". + print(anon_lambda_non_self.get_method()) # Should print "<anonymous lambda>". diff --git a/modules/gdscript/tests/scripts/runtime/features/lambda_get_method.out b/modules/gdscript/tests/scripts/runtime/features/lambda_get_method.out new file mode 100644 index 0000000000..17ee47fca2 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/lambda_get_method.out @@ -0,0 +1,5 @@ +GDTEST_OK +test +<anonymous lambda> +test +<anonymous lambda> diff --git a/modules/gltf/editor/editor_import_blend_runner.cpp b/modules/gltf/editor/editor_import_blend_runner.cpp index 330310d92a..22c8adfe88 100644 --- a/modules/gltf/editor/editor_import_blend_runner.cpp +++ b/modules/gltf/editor/editor_import_blend_runner.cpp @@ -43,6 +43,7 @@ from xmlrpc.server import SimpleXMLRPCServer req = threading.Condition() res = threading.Condition() info = None +export_err = None def xmlrpc_server(): server = SimpleXMLRPCServer(('127.0.0.1', %d)) server.register_function(export_gltf) @@ -54,6 +55,10 @@ def export_gltf(opts): req.notify() with res: res.wait() + if export_err: + raise export_err + # Important to return a value to prevent the error 'cannot marshal None unless allow_none is enabled'. + return 'BLENDER_GODOT_EXPORT_SUCCESSFUL' if bpy.app.version < (3, 0, 0): print('Blender 3.0 or higher is required.', file=sys.stderr) threading.Thread(target=xmlrpc_server).start() @@ -64,12 +69,13 @@ while True: method, opts = info if method == 'export_gltf': try: + export_err = None bpy.ops.wm.open_mainfile(filepath=opts['path']) if opts['unpack_all']: bpy.ops.file.unpack_all(method='USE_LOCAL') bpy.ops.export_scene.gltf(**opts['gltf_options']) - except: - pass + except Exception as e: + export_err = e info = None with res: res.notify() @@ -184,7 +190,9 @@ Error EditorImportBlendRunner::do_import(const Dictionary &p_options) { EditorSettings::get_singleton()->set_manually("filesystem/import/blender/rpc_port", 0); rpc_port = 0; } - err = do_import_direct(p_options); + if (err != ERR_QUERY_FAILED) { + err = do_import_direct(p_options); + } } return err; } else { @@ -259,6 +267,7 @@ Error EditorImportBlendRunner::do_import_rpc(const Dictionary &p_options) { // Wait for response. bool done = false; + PackedByteArray response; while (!done) { status = client->get_status(); switch (status) { @@ -268,7 +277,10 @@ Error EditorImportBlendRunner::do_import_rpc(const Dictionary &p_options) { } case HTTPClient::STATUS_BODY: { client->poll(); - // Parse response here if needed. For now we can just ignore it. + response.append_array(client->read_response_body_chunk()); + break; + } + case HTTPClient::STATUS_CONNECTED: { done = true; break; } @@ -278,9 +290,56 @@ Error EditorImportBlendRunner::do_import_rpc(const Dictionary &p_options) { } } + String response_text = "No response from Blender."; + if (response.size() > 0) { + response_text = String::utf8((const char *)response.ptr(), response.size()); + } + + if (client->get_response_code() != HTTPClient::RESPONSE_OK) { + ERR_FAIL_V_MSG(ERR_QUERY_FAILED, vformat("Error received from Blender - status code: %s, error: %s", client->get_response_code(), response_text)); + } else if (response_text.find("BLENDER_GODOT_EXPORT_SUCCESSFUL") < 0) { + // Previous versions of Godot used a Python script where the RPC function did not return + // a value, causing the error 'cannot marshal None unless allow_none is enabled'. + // If an older version of Godot is running and has started Blender with this script, + // we will receive the error, but there's a good chance that the import was successful. + // We are discarding this error to maintain backward compatibility and prevent situations + // where the user needs to close the older version of Godot or kill Blender. + if (response_text.find("cannot marshal None unless allow_none is enabled") < 0) { + String error_message; + if (_extract_error_message_xml(response, error_message)) { + ERR_FAIL_V_MSG(ERR_QUERY_FAILED, vformat("Blender exportation failed: %s", error_message)); + } else { + ERR_FAIL_V_MSG(ERR_QUERY_FAILED, vformat("Blender exportation failed: %s", response_text)); + } + } + } + return OK; } +bool EditorImportBlendRunner::_extract_error_message_xml(const Vector<uint8_t> &p_response_data, String &r_error_message) { + // Based on RPC Xml spec from: https://xmlrpc.com/spec.md + Ref<XMLParser> parser = memnew(XMLParser); + Error err = parser->open_buffer(p_response_data); + if (err) { + return false; + } + + r_error_message = String(); + while (parser->read() == OK) { + if (parser->get_node_type() == XMLParser::NODE_TEXT) { + if (parser->get_node_data().size()) { + if (r_error_message.size()) { + r_error_message += " "; + } + r_error_message += parser->get_node_data().trim_suffix("\n"); + } + } + } + + return r_error_message.size(); +} + Error EditorImportBlendRunner::do_import_direct(const Dictionary &p_options) { // Export glTF directly. String python = vformat(PYTHON_SCRIPT_DIRECT, dict_to_python(p_options)); diff --git a/modules/gltf/editor/editor_import_blend_runner.h b/modules/gltf/editor/editor_import_blend_runner.h index 626f3c9eba..b3b49ebfb2 100644 --- a/modules/gltf/editor/editor_import_blend_runner.h +++ b/modules/gltf/editor/editor_import_blend_runner.h @@ -47,6 +47,7 @@ class EditorImportBlendRunner : public Node { void _resources_reimported(const PackedStringArray &p_files); void _kill_blender(); void _notification(int p_what); + bool _extract_error_message_xml(const Vector<uint8_t> &p_response_data, String &r_error_message); protected: int rpc_port = 0; diff --git a/modules/gltf/editor/editor_scene_importer_blend.cpp b/modules/gltf/editor/editor_scene_importer_blend.cpp index 79a2184745..b474128fd6 100644 --- a/modules/gltf/editor/editor_scene_importer_blend.cpp +++ b/modules/gltf/editor/editor_scene_importer_blend.cpp @@ -132,12 +132,10 @@ Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_ } #endif - source_global = source_global.c_escape(); - const String blend_basename = p_path.get_file().get_basename(); const String sink = ProjectSettings::get_singleton()->get_imported_files_path().path_join( vformat("%s-%s.gltf", blend_basename, p_path.md5_text())); - const String sink_global = ProjectSettings::get_singleton()->globalize_path(sink).c_escape(); + const String sink_global = ProjectSettings::get_singleton()->globalize_path(sink); // Handle configuration options. @@ -188,10 +186,18 @@ Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_ } else { parameters_map["export_lights"] = false; } - if (p_options.has(SNAME("blender/meshes/colors")) && p_options[SNAME("blender/meshes/colors")]) { - parameters_map["export_colors"] = true; + if (blender_major_version > 4 || (blender_major_version == 4 && blender_minor_version >= 2)) { + if (p_options.has(SNAME("blender/meshes/colors")) && p_options[SNAME("blender/meshes/colors")]) { + parameters_map["export_vertex_color"] = "MATERIAL"; + } else { + parameters_map["export_vertex_color"] = "NONE"; + } } else { - parameters_map["export_colors"] = false; + if (p_options.has(SNAME("blender/meshes/colors")) && p_options[SNAME("blender/meshes/colors")]) { + parameters_map["export_colors"] = true; + } else { + parameters_map["export_colors"] = false; + } } if (p_options.has(SNAME("blender/nodes/visible"))) { int32_t visible = p_options["blender/nodes/visible"]; diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp index 0c87199635..499ddb703b 100644 --- a/modules/text_server_adv/text_server_adv.cpp +++ b/modules/text_server_adv/text_server_adv.cpp @@ -5248,7 +5248,7 @@ void TextServerAdvanced::_shaped_text_overrun_trim_to_width(const RID &p_shaped_ if ((trim_pos >= 0 && sd->width > p_width) || enforce_ellipsis) { if (add_ellipsis && (ellipsis_pos > 0 || enforce_ellipsis)) { - // Insert an additional space when cutting word bound for esthetics. + // Insert an additional space when cutting word bound for aesthetics. if (cut_per_word && (ellipsis_pos > 0)) { Glyph gl; gl.count = 1; diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp index 6cf6b236ed..b45c004011 100644 --- a/modules/text_server_fb/text_server_fb.cpp +++ b/modules/text_server_fb/text_server_fb.cpp @@ -4061,7 +4061,7 @@ void TextServerFallback::_shaped_text_overrun_trim_to_width(const RID &p_shaped_ if ((trim_pos >= 0 && sd->width > p_width) || enforce_ellipsis) { if (add_ellipsis && (ellipsis_pos > 0 || enforce_ellipsis)) { - // Insert an additional space when cutting word bound for esthetics. + // Insert an additional space when cutting word bound for aesthetics. if (cut_per_word && (ellipsis_pos > 0)) { Glyph gl; gl.count = 1; diff --git a/platform/linuxbsd/joypad_linux.cpp b/platform/linuxbsd/joypad_linux.cpp index 3534c1afee..a67428b9a4 100644 --- a/platform/linuxbsd/joypad_linux.cpp +++ b/platform/linuxbsd/joypad_linux.cpp @@ -374,6 +374,12 @@ void JoypadLinux::open_joypad(const char *p_path) { name = namebuf; } + for (const String &word : name.to_lower().split(" ")) { + if (banned_words.has(word)) { + return; + } + } + if (ioctl(fd, EVIOCGID, &inpid) < 0) { close(fd); return; diff --git a/platform/linuxbsd/joypad_linux.h b/platform/linuxbsd/joypad_linux.h index 26a9908d4e..bf24d8e5a5 100644 --- a/platform/linuxbsd/joypad_linux.h +++ b/platform/linuxbsd/joypad_linux.h @@ -94,6 +94,21 @@ private: Vector<String> attached_devices; + // List of lowercase words that will prevent the controller from being recognized if its name matches. + // This is done to prevent trackpads, graphics tablets and motherboard LED controllers from being + // recognized as controllers (and taking up controller ID slots as a result). + // Only whole words are matched within the controller name string. The match is case-insensitive. + const Vector<String> banned_words = { + "touchpad", // Matches e.g. "SynPS/2 Synaptics TouchPad", "Sony Interactive Entertainment DualSense Wireless Controller Touchpad" + "trackpad", + "clickpad", + "keyboard", // Matches e.g. "PG-90215 Keyboard", "Usb Keyboard Usb Keyboard Consumer Control" + "mouse", // Matches e.g. "Mouse passthrough" + "pen", // Matches e.g. "Wacom One by Wacom S Pen" + "finger", // Matches e.g. "Wacom HID 495F Finger" + "led", // Matches e.g. "ASRock LED Controller" + }; + static void monitor_joypads_thread_func(void *p_user); void monitor_joypads_thread_run(); diff --git a/platform/linuxbsd/wayland/wayland_thread.cpp b/platform/linuxbsd/wayland/wayland_thread.cpp index 341cc517e3..7bdc75db29 100644 --- a/platform/linuxbsd/wayland/wayland_thread.cpp +++ b/platform/linuxbsd/wayland/wayland_thread.cpp @@ -2049,9 +2049,14 @@ void WaylandThread::_wp_relative_pointer_on_relative_motion(void *data, struct z PointerData &pd = ss->pointer_data_buffer; + WindowState *ws = wl_surface_get_window_state(ss->pointed_surface); + ERR_FAIL_NULL(ws); + pd.relative_motion.x = wl_fixed_to_double(dx); pd.relative_motion.y = wl_fixed_to_double(dy); + pd.relative_motion *= window_state_get_scale_factor(ws); + pd.relative_motion_time = uptime_lo; } diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm index a1a91345ac..da45391995 100644 --- a/platform/macos/display_server_macos.mm +++ b/platform/macos/display_server_macos.mm @@ -568,23 +568,7 @@ void DisplayServerMacOS::menu_callback(id p_sender) { } GodotMenuItem *value = [p_sender representedObject]; - if (value) { - if (value->max_states > 0) { - value->state++; - if (value->state >= value->max_states) { - value->state = 0; - } - } - - if (value->checkable_type == CHECKABLE_TYPE_CHECK_BOX) { - if ([p_sender state] == NSControlStateValueOff) { - [p_sender setState:NSControlStateValueOn]; - } else { - [p_sender setState:NSControlStateValueOff]; - } - } - if (value->callback.is_valid()) { MenuCall mc; mc.tag = value->meta; diff --git a/platform/macos/godot_menu_item.h b/platform/macos/godot_menu_item.h index b6e2d41c08..e1af317259 100644 --- a/platform/macos/godot_menu_item.h +++ b/platform/macos/godot_menu_item.h @@ -52,6 +52,7 @@ enum GlobalMenuCheckType { Callable hover_callback; Variant meta; GlobalMenuCheckType checkable_type; + bool checked; int max_states; int state; Ref<Image> img; diff --git a/platform/macos/godot_menu_item.mm b/platform/macos/godot_menu_item.mm index 30dac9be9b..479542113a 100644 --- a/platform/macos/godot_menu_item.mm +++ b/platform/macos/godot_menu_item.mm @@ -31,4 +31,18 @@ #include "godot_menu_item.h" @implementation GodotMenuItem + +- (id)init { + self = [super init]; + + self->callback = Callable(); + self->key_callback = Callable(); + self->checkable_type = GlobalMenuCheckType::CHECKABLE_TYPE_NONE; + self->checked = false; + self->max_states = 0; + self->state = 0; + + return self; +} + @end diff --git a/platform/macos/native_menu_macos.mm b/platform/macos/native_menu_macos.mm index 1ae1137ca0..802d58dc26 100644 --- a/platform/macos/native_menu_macos.mm +++ b/platform/macos/native_menu_macos.mm @@ -373,12 +373,7 @@ int NativeMenuMacOS::add_submenu_item(const RID &p_rid, const String &p_label, c menu_item = [md->menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:nil keyEquivalent:@"" atIndex:p_index]; GodotMenuItem *obj = [[GodotMenuItem alloc] init]; - obj->callback = Callable(); - obj->key_callback = Callable(); obj->meta = p_tag; - obj->checkable_type = CHECKABLE_TYPE_NONE; - obj->max_states = 0; - obj->state = 0; [menu_item setRepresentedObject:obj]; [md_sub->menu setTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()]]; @@ -417,9 +412,6 @@ int NativeMenuMacOS::add_item(const RID &p_rid, const String &p_label, const Cal obj->callback = p_callback; obj->key_callback = p_key_callback; obj->meta = p_tag; - obj->checkable_type = CHECKABLE_TYPE_NONE; - obj->max_states = 0; - obj->state = 0; [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)]; [menu_item setRepresentedObject:obj]; } @@ -438,8 +430,6 @@ int NativeMenuMacOS::add_check_item(const RID &p_rid, const String &p_label, con obj->key_callback = p_key_callback; obj->meta = p_tag; obj->checkable_type = CHECKABLE_TYPE_CHECK_BOX; - obj->max_states = 0; - obj->state = 0; [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)]; [menu_item setRepresentedObject:obj]; } @@ -457,9 +447,6 @@ int NativeMenuMacOS::add_icon_item(const RID &p_rid, const Ref<Texture2D> &p_ico obj->callback = p_callback; obj->key_callback = p_key_callback; obj->meta = p_tag; - obj->checkable_type = CHECKABLE_TYPE_NONE; - obj->max_states = 0; - obj->state = 0; DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); if (ds && p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0 && p_icon->get_image().is_valid()) { obj->img = p_icon->get_image(); @@ -489,8 +476,6 @@ int NativeMenuMacOS::add_icon_check_item(const RID &p_rid, const Ref<Texture2D> obj->key_callback = p_key_callback; obj->meta = p_tag; obj->checkable_type = CHECKABLE_TYPE_CHECK_BOX; - obj->max_states = 0; - obj->state = 0; DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); if (ds && p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0 && p_icon->get_image().is_valid()) { obj->img = p_icon->get_image(); @@ -520,8 +505,6 @@ int NativeMenuMacOS::add_radio_check_item(const RID &p_rid, const String &p_labe obj->key_callback = p_key_callback; obj->meta = p_tag; obj->checkable_type = CHECKABLE_TYPE_RADIO_BUTTON; - obj->max_states = 0; - obj->state = 0; [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)]; [menu_item setRepresentedObject:obj]; } @@ -540,8 +523,6 @@ int NativeMenuMacOS::add_icon_radio_check_item(const RID &p_rid, const Ref<Textu obj->key_callback = p_key_callback; obj->meta = p_tag; obj->checkable_type = CHECKABLE_TYPE_RADIO_BUTTON; - obj->max_states = 0; - obj->state = 0; DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); if (ds && p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0 && p_icon->get_image().is_valid()) { obj->img = p_icon->get_image(); @@ -570,7 +551,6 @@ int NativeMenuMacOS::add_multistate_item(const RID &p_rid, const String &p_label obj->callback = p_callback; obj->key_callback = p_key_callback; obj->meta = p_tag; - obj->checkable_type = CHECKABLE_TYPE_NONE; obj->max_states = p_max_states; obj->state = p_default_state; [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)]; @@ -640,7 +620,10 @@ bool NativeMenuMacOS::is_item_checked(const RID &p_rid, int p_idx) const { ERR_FAIL_COND_V(p_idx >= item_start + item_count, false); const NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx]; if (menu_item) { - return ([menu_item state] == NSControlStateValueOn); + const GodotMenuItem *obj = [menu_item representedObject]; + if (obj) { + return obj->checked; + } } return false; } @@ -958,10 +941,14 @@ void NativeMenuMacOS::set_item_checked(const RID &p_rid, int p_idx, bool p_check ERR_FAIL_COND(p_idx >= item_start + item_count); NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx]; if (menu_item) { - if (p_checked) { - [menu_item setState:NSControlStateValueOn]; - } else { - [menu_item setState:NSControlStateValueOff]; + GodotMenuItem *obj = [menu_item representedObject]; + if (obj) { + obj->checked = p_checked; + if (p_checked) { + [menu_item setState:NSControlStateValueOn]; + } else { + [menu_item setState:NSControlStateValueOff]; + } } } } diff --git a/platform/web/audio_driver_web.cpp b/platform/web/audio_driver_web.cpp index dd986e650c..b24c6cb1fd 100644 --- a/platform/web/audio_driver_web.cpp +++ b/platform/web/audio_driver_web.cpp @@ -33,6 +33,8 @@ #include "godot_audio.h" #include "core/config/project_settings.h" +#include "core/object/object.h" +#include "scene/main/node.h" #include "servers/audio/audio_stream.h" #include <emscripten.h> @@ -51,6 +53,33 @@ void AudioDriverWeb::_latency_update_callback(float p_latency) { AudioDriverWeb::audio_context.output_latency = p_latency; } +void AudioDriverWeb::_sample_playback_finished_callback(const char *p_playback_object_id) { + const ObjectID playback_id = ObjectID(String::to_int(p_playback_object_id)); + + Object *playback_object = ObjectDB::get_instance(playback_id); + if (playback_object == nullptr) { + return; + } + Ref<AudioSamplePlayback> playback = Object::cast_to<AudioSamplePlayback>(playback_object); + if (playback.is_null()) { + return; + } + + Object *player_object = ObjectDB::get_instance(playback->player_id); + if (player_object == nullptr) { + return; + } + Node *player = Object::cast_to<Node>(player_object); + if (player == nullptr) { + return; + } + + const StringName finished = SNAME("finished"); + if (player->has_signal(finished)) { + player->emit_signal(finished); + } +} + void AudioDriverWeb::_audio_driver_process(int p_from, int p_samples) { int32_t *stream_buffer = reinterpret_cast<int32_t *>(output_rb); const int max_samples = memarr_len(output_rb); @@ -132,6 +161,9 @@ Error AudioDriverWeb::init() { if (!input_rb) { return ERR_OUT_OF_MEMORY; } + + godot_audio_sample_set_finished_callback(&_sample_playback_finished_callback); + return OK; } diff --git a/platform/web/audio_driver_web.h b/platform/web/audio_driver_web.h index 298ad90fae..46c5ce4de1 100644 --- a/platform/web/audio_driver_web.h +++ b/platform/web/audio_driver_web.h @@ -58,6 +58,7 @@ private: WASM_EXPORT static void _state_change_callback(int p_state); WASM_EXPORT static void _latency_update_callback(float p_latency); + WASM_EXPORT static void _sample_playback_finished_callback(const char *p_playback_object_id); static AudioDriverWeb *singleton; diff --git a/platform/web/detect.py b/platform/web/detect.py index cb4dac1125..79485ea28a 100644 --- a/platform/web/detect.py +++ b/platform/web/detect.py @@ -78,6 +78,7 @@ def get_flags(): # -Os reduces file size by around 5 MiB over -O3. -Oz only saves about # 100 KiB over -Os, which does not justify the negative impact on # run-time performance. + # Note that this overrides the "auto" behavior for target/dev_build. "optimize": "size", } diff --git a/platform/web/godot_audio.h b/platform/web/godot_audio.h index 8bebbcf7de..dd5bec00cf 100644 --- a/platform/web/godot_audio.h +++ b/platform/web/godot_audio.h @@ -57,6 +57,7 @@ extern void godot_audio_sample_set_pause(const char *p_playback_object_id, bool extern int godot_audio_sample_is_active(const char *p_playback_object_id); extern void godot_audio_sample_update_pitch_scale(const char *p_playback_object_id, float p_pitch_scale); extern void godot_audio_sample_set_volumes_linear(const char *p_playback_object_id, int *p_buses_buf, int p_buses_size, float *p_volumes_buf, int p_volumes_size); +extern void godot_audio_sample_set_finished_callback(void (*p_callback)(const char *)); extern void godot_audio_sample_bus_set_count(int p_count); extern void godot_audio_sample_bus_remove(int p_index); diff --git a/platform/web/js/libs/library_godot_audio.js b/platform/web/js/libs/library_godot_audio.js index 531dbdaeab..0b16b07261 100644 --- a/platform/web/js/libs/library_godot_audio.js +++ b/platform/web/js/libs/library_godot_audio.js @@ -687,9 +687,15 @@ class SampleNode { } switch (self.getSample().loopMode) { - case 'disabled': + case 'disabled': { + const id = this.id; self.stop(); - break; + if (GodotAudio.sampleFinishedCallback != null) { + const idCharPtr = GodotRuntime.allocString(id); + GodotAudio.sampleFinishedCallback(idCharPtr); + GodotRuntime.free(idCharPtr); + } + } break; case 'forward': case 'backward': self.restart(); @@ -1090,6 +1096,12 @@ const _GodotAudio = { busSolo: null, Bus, + /** + * Callback to signal that a sample has finished. + * @type {(playbackObjectIdPtr: number) => void | null} + */ + sampleFinishedCallback: null, + /** @type {AudioContext} */ ctx: null, input: null, @@ -1764,6 +1776,17 @@ const _GodotAudio = { godot_audio_sample_bus_set_mute: function (bus, enable) { GodotAudio.set_sample_bus_mute(bus, Boolean(enable)); }, + + godot_audio_sample_set_finished_callback__proxy: 'sync', + godot_audio_sample_set_finished_callback__sig: 'vi', + /** + * Sets the finished callback + * @param {Number} callbackPtr Finished callback pointer + * @returns {void} + */ + godot_audio_sample_set_finished_callback: function (callbackPtr) { + GodotAudio.sampleFinishedCallback = GodotRuntime.get_func(callbackPtr); + }, }; autoAddDeps(_GodotAudio, '$GodotAudio'); diff --git a/platform/web/js/libs/library_godot_input.js b/platform/web/js/libs/library_godot_input.js index 7ea89d553f..6e3b97023d 100644 --- a/platform/web/js/libs/library_godot_input.js +++ b/platform/web/js/libs/library_godot_input.js @@ -112,6 +112,7 @@ const GodotIME = { ime.style.top = '0px'; ime.style.width = '100%'; ime.style.height = '40px'; + ime.style.pointerEvents = 'none'; ime.style.display = 'none'; ime.contentEditable = 'true'; diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index 8d26a705a9..c1b3540f68 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -38,6 +38,7 @@ #include "core/version.h" #include "drivers/png/png_driver_common.h" #include "main/main.h" +#include "scene/resources/texture.h" #if defined(VULKAN_ENABLED) #include "rendering_context_driver_vulkan_windows.h" @@ -3807,9 +3808,9 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA case WM_ACTIVATE: { // Activation can happen just after the window has been created, even before the callbacks are set. // Therefore, it's safer to defer the delivery of the event. - if (!windows[window_id].activate_timer_id) { - windows[window_id].activate_timer_id = SetTimer(windows[window_id].hWnd, 1, USER_TIMER_MINIMUM, (TIMERPROC) nullptr); - } + // It's important to set an nIDEvent different from the SetTimer for move_timer_id because + // if the same nIDEvent is passed, the timer is replaced and the same timer_id is returned. + windows[window_id].activate_timer_id = SetTimer(windows[window_id].hWnd, DisplayServerWindows::TIMER_ID_WINDOW_ACTIVATION, USER_TIMER_MINIMUM, (TIMERPROC) nullptr); windows[window_id].activate_state = GET_WM_ACTIVATE_STATE(wParam, lParam); return 0; } break; @@ -4727,7 +4728,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA case WM_ENTERSIZEMOVE: { Input::get_singleton()->release_pressed_events(); - windows[window_id].move_timer_id = SetTimer(windows[window_id].hWnd, 1, USER_TIMER_MINIMUM, (TIMERPROC) nullptr); + windows[window_id].move_timer_id = SetTimer(windows[window_id].hWnd, DisplayServerWindows::TIMER_ID_MOVE_REDRAW, USER_TIMER_MINIMUM, (TIMERPROC) nullptr); } break; case WM_EXITSIZEMOVE: { KillTimer(windows[window_id].hWnd, windows[window_id].move_timer_id); diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h index 382f18c239..c2f4de7d81 100644 --- a/platform/windows/display_server_windows.h +++ b/platform/windows/display_server_windows.h @@ -332,6 +332,11 @@ class DisplayServerWindows : public DisplayServer { String tablet_driver; Vector<String> tablet_drivers; + enum TimerID { + TIMER_ID_MOVE_REDRAW = 1, + TIMER_ID_WINDOW_ACTIVATION = 2, + }; + enum { KEY_EVENT_BUFFER_SIZE = 512 }; diff --git a/platform/windows/native_menu_windows.cpp b/platform/windows/native_menu_windows.cpp index d9dc28e9d9..fde55918e4 100644 --- a/platform/windows/native_menu_windows.cpp +++ b/platform/windows/native_menu_windows.cpp @@ -81,22 +81,6 @@ void NativeMenuWindows::_menu_activate(HMENU p_menu, int p_index) const { if (GetMenuItemInfoW(md->menu, p_index, true, &item)) { MenuItemData *item_data = (MenuItemData *)item.dwItemData; if (item_data) { - if (item_data->max_states > 0) { - item_data->state++; - if (item_data->state >= item_data->max_states) { - item_data->state = 0; - } - } - - if (item_data->checkable_type == CHECKABLE_TYPE_CHECK_BOX) { - if ((item.fState & MFS_CHECKED) == MFS_CHECKED) { - item.fState &= ~MFS_CHECKED; - } else { - item.fState |= MFS_CHECKED; - } - SetMenuItemInfoW(md->menu, p_index, true, &item); - } - if (item_data->callback.is_valid()) { Variant ret; Callable::CallError ce; @@ -619,9 +603,12 @@ bool NativeMenuWindows::is_item_checked(const RID &p_rid, int p_idx) const { MENUITEMINFOW item; ZeroMemory(&item, sizeof(item)); item.cbSize = sizeof(item); - item.fMask = MIIM_STATE; + item.fMask = MIIM_STATE | MIIM_DATA; if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) { - return (item.fState & MFS_CHECKED) == MFS_CHECKED; + MenuItemData *item_data = (MenuItemData *)item.dwItemData; + if (item_data) { + return item_data->checked; + } } return false; } @@ -861,12 +848,16 @@ void NativeMenuWindows::set_item_checked(const RID &p_rid, int p_idx, bool p_che MENUITEMINFOW item; ZeroMemory(&item, sizeof(item)); item.cbSize = sizeof(item); - item.fMask = MIIM_STATE; + item.fMask = MIIM_STATE | MIIM_DATA; if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) { - if (p_checked) { - item.fState |= MFS_CHECKED; - } else { - item.fState &= ~MFS_CHECKED; + MenuItemData *item_data = (MenuItemData *)item.dwItemData; + if (item_data) { + item_data->checked = p_checked; + if (p_checked) { + item.fState |= MFS_CHECKED; + } else { + item.fState &= ~MFS_CHECKED; + } } SetMenuItemInfoW(md->menu, p_idx, true, &item); } diff --git a/platform/windows/native_menu_windows.h b/platform/windows/native_menu_windows.h index 5c4aaa52c8..235a4b332a 100644 --- a/platform/windows/native_menu_windows.h +++ b/platform/windows/native_menu_windows.h @@ -51,6 +51,7 @@ class NativeMenuWindows : public NativeMenu { Callable callback; Variant meta; GlobalMenuCheckType checkable_type; + bool checked = false; int max_states = 0; int state = 0; Ref<Image> img; diff --git a/scene/2d/camera_2d.cpp b/scene/2d/camera_2d.cpp index 514c5e7a8f..7020d162fe 100644 --- a/scene/2d/camera_2d.cpp +++ b/scene/2d/camera_2d.cpp @@ -302,6 +302,12 @@ void Camera2D::_notification(int p_what) { _interpolation_data.xform_prev = _interpolation_data.xform_curr; } break; + case NOTIFICATION_PAUSED: { + if (is_physics_interpolated_and_enabled()) { + _update_scroll(); + } + } break; + case NOTIFICATION_TRANSFORM_CHANGED: { if ((!position_smoothing_enabled && !is_physics_interpolated_and_enabled()) || _is_editing_in_editor()) { _update_scroll(); diff --git a/scene/2d/camera_2d.h b/scene/2d/camera_2d.h index 8754e35e88..be2da8b97a 100644 --- a/scene/2d/camera_2d.h +++ b/scene/2d/camera_2d.h @@ -108,7 +108,7 @@ protected: struct InterpolationData { Transform2D xform_curr; Transform2D xform_prev; - uint32_t last_update_physics_tick = 0; + uint32_t last_update_physics_tick = UINT32_MAX; // Ensure tick 0 is detected as a change. } _interpolation_data; void _ensure_update_interpolation_data(); diff --git a/scene/2d/navigation_agent_2d.cpp b/scene/2d/navigation_agent_2d.cpp index 9e3e6ea583..d0fae611d8 100644 --- a/scene/2d/navigation_agent_2d.cpp +++ b/scene/2d/navigation_agent_2d.cpp @@ -671,8 +671,6 @@ void NavigationAgent2D::_update_navigation() { return; } - update_frame_id = Engine::get_singleton()->get_physics_frames(); - Vector2 origin = agent_parent->get_global_position(); bool reload_path = false; @@ -767,7 +765,6 @@ void NavigationAgent2D::_request_repath() { target_reached = false; navigation_finished = false; last_waypoint_reached = false; - update_frame_id = 0; } bool NavigationAgent2D::_is_last_waypoint() const { diff --git a/scene/2d/navigation_agent_2d.h b/scene/2d/navigation_agent_2d.h index 0acfc82162..8741f578d0 100644 --- a/scene/2d/navigation_agent_2d.h +++ b/scene/2d/navigation_agent_2d.h @@ -91,8 +91,6 @@ class NavigationAgent2D : public Node { bool target_reached = false; bool navigation_finished = true; bool last_waypoint_reached = false; - // No initialized on purpose - uint32_t update_frame_id = 0; // Debug properties for exposed bindings bool debug_enabled = false; diff --git a/scene/2d/parallax_2d.cpp b/scene/2d/parallax_2d.cpp index fb04eec737..9dd9d4a376 100644 --- a/scene/2d/parallax_2d.cpp +++ b/scene/2d/parallax_2d.cpp @@ -91,11 +91,10 @@ void Parallax2D::_update_scroll() { } Point2 scroll_ofs = screen_offset; - Size2 vps = get_viewport_rect().size; - if (Engine::get_singleton()->is_editor_hint()) { - vps = Size2(GLOBAL_GET("display/window/size/viewport_width"), GLOBAL_GET("display/window/size/viewport_height")); - } else { + if (!Engine::get_singleton()->is_editor_hint()) { + Size2 vps = get_viewport_rect().size; + if (limit_begin.x <= limit_end.x - vps.x) { scroll_ofs.x = CLAMP(scroll_ofs.x, limit_begin.x, limit_end.x - vps.x); } diff --git a/scene/3d/navigation_agent_3d.cpp b/scene/3d/navigation_agent_3d.cpp index dff413f5d2..5bbb724e2f 100644 --- a/scene/3d/navigation_agent_3d.cpp +++ b/scene/3d/navigation_agent_3d.cpp @@ -737,8 +737,6 @@ void NavigationAgent3D::_update_navigation() { return; } - update_frame_id = Engine::get_singleton()->get_physics_frames(); - Vector3 origin = agent_parent->get_global_position(); bool reload_path = false; @@ -835,7 +833,6 @@ void NavigationAgent3D::_request_repath() { target_reached = false; navigation_finished = false; last_waypoint_reached = false; - update_frame_id = 0; } bool NavigationAgent3D::_is_last_waypoint() const { diff --git a/scene/3d/navigation_agent_3d.h b/scene/3d/navigation_agent_3d.h index ade6afd445..d5721a56c8 100644 --- a/scene/3d/navigation_agent_3d.h +++ b/scene/3d/navigation_agent_3d.h @@ -98,8 +98,6 @@ class NavigationAgent3D : public Node { bool target_reached = false; bool navigation_finished = true; bool last_waypoint_reached = false; - // No initialized on purpose - uint32_t update_frame_id = 0; // Debug properties for exposed bindings bool debug_enabled = false; diff --git a/scene/3d/sprite_3d.cpp b/scene/3d/sprite_3d.cpp index d08aeb1de2..8ac585719c 100644 --- a/scene/3d/sprite_3d.cpp +++ b/scene/3d/sprite_3d.cpp @@ -984,7 +984,7 @@ void Sprite3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "hframes", PROPERTY_HINT_RANGE, "1,16384,1"), "set_hframes", "get_hframes"); ADD_PROPERTY(PropertyInfo(Variant::INT, "vframes", PROPERTY_HINT_RANGE, "1,16384,1"), "set_vframes", "get_vframes"); ADD_PROPERTY(PropertyInfo(Variant::INT, "frame"), "set_frame", "get_frame"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "frame_coords", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_frame_coords", "get_frame_coords"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "frame_coords", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_frame_coords", "get_frame_coords"); ADD_GROUP("Region", "region_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "region_enabled"), "set_region_enabled", "is_region_enabled"); ADD_PROPERTY(PropertyInfo(Variant::RECT2, "region_rect", PROPERTY_HINT_NONE, "suffix:px"), "set_region_rect", "get_region_rect"); diff --git a/scene/audio/audio_stream_player_internal.cpp b/scene/audio/audio_stream_player_internal.cpp index b3a55ddee0..6653e01f25 100644 --- a/scene/audio/audio_stream_player_internal.cpp +++ b/scene/audio/audio_stream_player_internal.cpp @@ -152,6 +152,7 @@ Ref<AudioStreamPlayback> AudioStreamPlayerInternal::play_basic() { Ref<AudioSamplePlayback> sample_playback; sample_playback.instantiate(); sample_playback->stream = stream; + sample_playback->player_id = node->get_instance_id(); stream_playback->set_sample_playback(sample_playback); } } else if (!stream->is_meta_stream()) { diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp index 429fb2e64f..55a2c607e3 100644 --- a/scene/gui/graph_edit.cpp +++ b/scene/gui/graph_edit.cpp @@ -546,6 +546,11 @@ void GraphEdit::_graph_node_slot_updated(int p_index, Node *p_node) { GraphNode *graph_node = Object::cast_to<GraphNode>(p_node); ERR_FAIL_NULL(graph_node); + // Update all adjacent connections during the next redraw. + for (const Ref<Connection> &conn : connection_map[graph_node->get_name()]) { + conn->_cache.dirty = true; + } + minimap->queue_redraw(); queue_redraw(); connections_layer->queue_redraw(); diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp index 4f07fdb87b..7f795ea710 100644 --- a/scene/gui/popup_menu.cpp +++ b/scene/gui/popup_menu.cpp @@ -2314,6 +2314,16 @@ bool PopupMenu::is_prefer_native_menu() const { return prefer_native; } +bool PopupMenu::is_native_menu() const { +#ifdef TOOLS_ENABLED + if (is_part_of_edited_scene()) { + return false; + } +#endif + + return global_menu.is_valid(); +} + bool PopupMenu::activate_item_by_event(const Ref<InputEvent> &p_event, bool p_for_global_only) { ERR_FAIL_COND_V(p_event.is_null(), false); Key code = Key::NONE; @@ -2643,6 +2653,7 @@ void PopupMenu::_bind_methods() { ClassDB::bind_method(D_METHOD("set_prefer_native_menu", "enabled"), &PopupMenu::set_prefer_native_menu); ClassDB::bind_method(D_METHOD("is_prefer_native_menu"), &PopupMenu::is_prefer_native_menu); + ClassDB::bind_method(D_METHOD("is_native_menu"), &PopupMenu::is_native_menu); ClassDB::bind_method(D_METHOD("add_item", "label", "id", "accel"), &PopupMenu::add_item, DEFVAL(-1), DEFVAL(0)); ClassDB::bind_method(D_METHOD("add_icon_item", "texture", "label", "id", "accel"), &PopupMenu::add_icon_item, DEFVAL(-1), DEFVAL(0)); diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h index c6eef03aca..5313dae404 100644 --- a/scene/gui/popup_menu.h +++ b/scene/gui/popup_menu.h @@ -330,6 +330,8 @@ public: void set_prefer_native_menu(bool p_enabled); bool is_prefer_native_menu() const; + bool is_native_menu() const; + void scroll_to_item(int p_idx); bool activate_item_by_event(const Ref<InputEvent> &p_event, bool p_for_global_only = false); diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index 8ffa0f8c63..74978416c4 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -4947,10 +4947,10 @@ void RichTextLabel::append_text(const String &p_bbcode) { tag_stack.push_front("outline_size"); } else if (bbcode_name == "fade") { - int start_index = 0; + int start_index = brk_pos; OptionMap::Iterator start_option = bbcode_options.find("start"); if (start_option) { - start_index = start_option->value.to_int(); + start_index += start_option->value.to_int(); } int length = 10; diff --git a/scene/main/node.cpp b/scene/main/node.cpp index 5bbf8ebff4..0396f3ab4a 100644 --- a/scene/main/node.cpp +++ b/scene/main/node.cpp @@ -177,6 +177,12 @@ void Node::_notification(int p_notification) { } } break; + case NOTIFICATION_PAUSED: { + if (is_physics_interpolated_and_enabled() && is_inside_tree()) { + reset_physics_interpolation(); + } + } break; + case NOTIFICATION_PATH_RENAMED: { if (data.path_cache) { memdelete(data.path_cache); diff --git a/servers/audio/audio_stream.h b/servers/audio/audio_stream.h index 0ca4777d5c..9149109381 100644 --- a/servers/audio/audio_stream.h +++ b/servers/audio/audio_stream.h @@ -49,6 +49,7 @@ class AudioSamplePlayback : public RefCounted { public: Ref<AudioStream> stream; + ObjectID player_id; float offset = 0.0f; Vector<AudioFrame> volume_vector; StringName bus; diff --git a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp index 536fc7a04a..f97ed3d215 100644 --- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp +++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp @@ -587,7 +587,7 @@ void RenderForwardClustered::_render_list_with_draw_list(RenderListParameters *p RD::get_singleton()->draw_list_end(); } -void RenderForwardClustered::_setup_environment(const RenderDataRD *p_render_data, bool p_no_fog, const Size2i &p_screen_size, bool p_flip_y, const Color &p_default_bg_color, bool p_opaque_render_buffers, bool p_apply_alpha_multiplier, bool p_pancake_shadows, int p_index) { +void RenderForwardClustered::_setup_environment(const RenderDataRD *p_render_data, bool p_no_fog, const Size2i &p_screen_size, const Color &p_default_bg_color, bool p_opaque_render_buffers, bool p_apply_alpha_multiplier, bool p_pancake_shadows, int p_index) { RendererRD::LightStorage *light_storage = RendererRD::LightStorage::get_singleton(); Ref<RenderSceneBuffersRD> rd = p_render_data->render_buffers; @@ -603,7 +603,7 @@ void RenderForwardClustered::_setup_environment(const RenderDataRD *p_render_dat } } - p_render_data->scene_data->update_ubo(scene_state.uniform_buffers[p_index], get_debug_draw_mode(), env, reflection_probe_instance, p_render_data->camera_attributes, p_flip_y, p_pancake_shadows, p_screen_size, p_default_bg_color, _render_buffers_get_luminance_multiplier(), p_opaque_render_buffers, p_apply_alpha_multiplier); + p_render_data->scene_data->update_ubo(scene_state.uniform_buffers[p_index], get_debug_draw_mode(), env, reflection_probe_instance, p_render_data->camera_attributes, p_pancake_shadows, p_screen_size, p_default_bg_color, _render_buffers_get_luminance_multiplier(), p_opaque_render_buffers, p_apply_alpha_multiplier); // now do implementation UBO @@ -1732,7 +1732,7 @@ void RenderForwardClustered::_render_scene(RenderDataRD *p_render_data, const Co _setup_lightmaps(p_render_data, *p_render_data->lightmaps, p_render_data->scene_data->cam_transform); _setup_voxelgis(*p_render_data->voxel_gi_instances); - _setup_environment(p_render_data, is_reflection_probe, screen_size, !is_reflection_probe, p_default_bg_color, false); + _setup_environment(p_render_data, is_reflection_probe, screen_size, p_default_bg_color, false); // May have changed due to the above (light buffer enlarged, as an example). _update_render_base_uniform_set(); @@ -1995,7 +1995,7 @@ void RenderForwardClustered::_render_scene(RenderDataRD *p_render_data, const Co // Shadow pass can change the base uniform set samplers. _update_render_base_uniform_set(); - _setup_environment(p_render_data, is_reflection_probe, screen_size, !is_reflection_probe, p_default_bg_color, true, using_motion_pass); + _setup_environment(p_render_data, is_reflection_probe, screen_size, p_default_bg_color, true, using_motion_pass); RID rp_uniform_set = _setup_render_pass_uniform_set(RENDER_LIST_OPAQUE, p_render_data, radiance_texture, samplers, true); @@ -2209,7 +2209,7 @@ void RenderForwardClustered::_render_scene(RenderDataRD *p_render_data, const Co rp_uniform_set = _setup_render_pass_uniform_set(RENDER_LIST_ALPHA, p_render_data, radiance_texture, samplers, true); - _setup_environment(p_render_data, is_reflection_probe, screen_size, !is_reflection_probe, p_default_bg_color, false); + _setup_environment(p_render_data, is_reflection_probe, screen_size, p_default_bg_color, false); { uint32_t transparent_color_pass_flags = (color_pass_flags | COLOR_PASS_FLAG_TRANSPARENT) & ~(COLOR_PASS_FLAG_SEPARATE_SPECULAR); @@ -2562,6 +2562,7 @@ void RenderForwardClustered::_render_shadow_append(RID p_framebuffer, const Page SceneState::ShadowPass shadow_pass; RenderSceneDataRD scene_data; + scene_data.flip_y = !p_flip_y; // Q: Why is this inverted? Do we assume flip in shadow logic? scene_data.cam_projection = p_projection; scene_data.cam_transform = p_transform; scene_data.view_projection[0] = p_projection; @@ -2581,7 +2582,7 @@ void RenderForwardClustered::_render_shadow_append(RID p_framebuffer, const Page render_data.instances = &p_instances; render_data.render_info = p_render_info; - _setup_environment(&render_data, true, p_viewport_size, !p_flip_y, Color(), false, false, p_use_pancake, shadow_pass_index); + _setup_environment(&render_data, true, p_viewport_size, Color(), false, false, p_use_pancake, shadow_pass_index); if (get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_DISABLE_LOD) { scene_data.screen_mesh_lod_threshold = 0.0; @@ -2654,6 +2655,7 @@ void RenderForwardClustered::_render_particle_collider_heightfield(RID p_fb, con RD::get_singleton()->draw_command_begin_label("Render Collider Heightfield"); RenderSceneDataRD scene_data; + scene_data.flip_y = true; scene_data.cam_projection = p_cam_projection; scene_data.cam_transform = p_cam_transform; scene_data.view_projection[0] = p_cam_projection; @@ -2673,7 +2675,7 @@ void RenderForwardClustered::_render_particle_collider_heightfield(RID p_fb, con _update_render_base_uniform_set(); - _setup_environment(&render_data, true, Vector2(1, 1), true, Color(), false, false, false); + _setup_environment(&render_data, true, Vector2(1, 1), Color(), false, false, false); PassMode pass_mode = PASS_MODE_SHADOW; @@ -2720,7 +2722,7 @@ void RenderForwardClustered::_render_material(const Transform3D &p_cam_transform _update_render_base_uniform_set(); - _setup_environment(&render_data, true, Vector2(1, 1), false, Color()); + _setup_environment(&render_data, true, Vector2(1, 1), Color()); PassMode pass_mode = PASS_MODE_DEPTH_MATERIAL; _fill_render_list(RENDER_LIST_SECONDARY, &render_data, pass_mode); @@ -2771,7 +2773,7 @@ void RenderForwardClustered::_render_uv2(const PagedArray<RenderGeometryInstance _update_render_base_uniform_set(); - _setup_environment(&render_data, true, Vector2(1, 1), false, Color()); + _setup_environment(&render_data, true, Vector2(1, 1), Color()); PassMode pass_mode = PASS_MODE_DEPTH_MATERIAL; _fill_render_list(RENDER_LIST_SECONDARY, &render_data, pass_mode); @@ -2887,7 +2889,7 @@ void RenderForwardClustered::_render_sdfgi(Ref<RenderSceneBuffersRD> p_render_bu RendererRD::MaterialStorage::store_transform(to_bounds.affine_inverse() * scene_data.cam_transform, scene_state.ubo.sdf_to_bounds); scene_data.emissive_exposure_normalization = p_exposure_normalization; - _setup_environment(&render_data, true, Vector2(1, 1), false, Color()); + _setup_environment(&render_data, true, Vector2(1, 1), Color()); RID rp_uniform_set = _setup_sdfgi_render_pass_uniform_set(p_albedo_texture, p_emission_texture, p_emission_aniso_texture, p_geom_facing_texture, RendererRD::MaterialStorage::get_singleton()->samplers_rd_get_default()); diff --git a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h index ae9e5e7c10..0aa4a0667e 100644 --- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h +++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h @@ -361,7 +361,7 @@ class RenderForwardClustered : public RendererSceneRenderRD { static RenderForwardClustered *singleton; - void _setup_environment(const RenderDataRD *p_render_data, bool p_no_fog, const Size2i &p_screen_size, bool p_flip_y, const Color &p_default_bg_color, bool p_opaque_render_buffers = false, bool p_apply_alpha_multiplier = false, bool p_pancake_shadows = false, int p_index = 0); + void _setup_environment(const RenderDataRD *p_render_data, bool p_no_fog, const Size2i &p_screen_size, const Color &p_default_bg_color, bool p_opaque_render_buffers = false, bool p_apply_alpha_multiplier = false, bool p_pancake_shadows = false, int p_index = 0); void _setup_voxelgis(const PagedArray<RID> &p_voxelgis); void _setup_lightmaps(const RenderDataRD *p_render_data, const PagedArray<RID> &p_lightmaps, const Transform3D &p_cam_transform); diff --git a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp index 194a70dc22..0d83264bfb 100644 --- a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp +++ b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp @@ -804,7 +804,7 @@ void RenderForwardMobile::_render_scene(RenderDataRD *p_render_data, const Color RD::get_singleton()->draw_command_begin_label("Render Setup"); _setup_lightmaps(p_render_data, *p_render_data->lightmaps, p_render_data->scene_data->cam_transform); - _setup_environment(p_render_data, is_reflection_probe, screen_size, !is_reflection_probe, p_default_bg_color, false); + _setup_environment(p_render_data, is_reflection_probe, screen_size, p_default_bg_color, false); // May have changed due to the above (light buffer enlarged, as an example). _update_render_base_uniform_set(); @@ -953,7 +953,7 @@ void RenderForwardMobile::_render_scene(RenderDataRD *p_render_data, const Color // Shadow pass can change the base uniform set samplers. _update_render_base_uniform_set(); - _setup_environment(p_render_data, is_reflection_probe, screen_size, !is_reflection_probe, p_default_bg_color, p_render_data->render_buffers.is_valid()); + _setup_environment(p_render_data, is_reflection_probe, screen_size, p_default_bg_color, p_render_data->render_buffers.is_valid()); if (merge_transparent_pass && using_subpass_post_process) { RENDER_TIMESTAMP("Render Opaque + Transparent + Tonemap"); @@ -1075,7 +1075,7 @@ void RenderForwardMobile::_render_scene(RenderDataRD *p_render_data, const Color rp_uniform_set = _setup_render_pass_uniform_set(RENDER_LIST_ALPHA, p_render_data, radiance_texture, samplers, true); // this may be needed if we re-introduced steps that change info, not sure which do so in the previous implementation - //_setup_environment(p_render_data, is_reflection_probe, screen_size, !is_reflection_probe, p_default_bg_color, false); + //_setup_environment(p_render_data, is_reflection_probe, screen_size, p_default_bg_color, false); RenderListParameters render_list_params(render_list[RENDER_LIST_ALPHA].elements.ptr(), render_list[RENDER_LIST_ALPHA].element_info.ptr(), render_list[RENDER_LIST_ALPHA].elements.size(), reverse_cull, PASS_MODE_COLOR, rp_uniform_set, spec_constant_base_flags, get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_WIREFRAME, Vector2(), p_render_data->scene_data->lod_distance_multiplier, p_render_data->scene_data->screen_mesh_lod_threshold, p_render_data->scene_data->view_count); render_list_params.framebuffer_format = fb_format; @@ -1310,6 +1310,7 @@ void RenderForwardMobile::_render_shadow_append(RID p_framebuffer, const PagedAr } RenderSceneDataRD scene_data; + scene_data.flip_y = !p_flip_y; // Q: Why is this inverted? Do we assume flip in shadow logic? scene_data.cam_projection = p_projection; scene_data.cam_transform = p_transform; scene_data.view_projection[0] = p_projection; @@ -1327,7 +1328,7 @@ void RenderForwardMobile::_render_shadow_append(RID p_framebuffer, const PagedAr render_data.instances = &p_instances; render_data.render_info = p_render_info; - _setup_environment(&render_data, true, Vector2(1, 1), !p_flip_y, Color(), false, p_use_pancake, shadow_pass_index); + _setup_environment(&render_data, true, Vector2(1, 1), Color(), false, p_use_pancake, shadow_pass_index); if (get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_DISABLE_LOD) { scene_data.screen_mesh_lod_threshold = 0.0; @@ -1415,7 +1416,7 @@ void RenderForwardMobile::_render_material(const Transform3D &p_cam_transform, c render_data.scene_data = &scene_data; render_data.instances = &p_instances; - _setup_environment(&render_data, true, Vector2(1, 1), false, Color()); + _setup_environment(&render_data, true, Vector2(1, 1), Color()); PassMode pass_mode = PASS_MODE_DEPTH_MATERIAL; _fill_render_list(RENDER_LIST_SECONDARY, &render_data, pass_mode); @@ -1460,7 +1461,7 @@ void RenderForwardMobile::_render_uv2(const PagedArray<RenderGeometryInstance *> render_data.scene_data = &scene_data; render_data.instances = &p_instances; - _setup_environment(&render_data, true, Vector2(1, 1), false, Color()); + _setup_environment(&render_data, true, Vector2(1, 1), Color()); PassMode pass_mode = PASS_MODE_DEPTH_MATERIAL; _fill_render_list(RENDER_LIST_SECONDARY, &render_data, pass_mode); @@ -1526,6 +1527,7 @@ void RenderForwardMobile::_render_particle_collider_heightfield(RID p_fb, const _update_render_base_uniform_set(); RenderSceneDataRD scene_data; + scene_data.flip_y = true; scene_data.cam_projection = p_cam_projection; scene_data.cam_transform = p_cam_transform; scene_data.view_projection[0] = p_cam_projection; @@ -1541,7 +1543,7 @@ void RenderForwardMobile::_render_particle_collider_heightfield(RID p_fb, const render_data.scene_data = &scene_data; render_data.instances = &p_instances; - _setup_environment(&render_data, true, Vector2(1, 1), true, Color(), false, false); + _setup_environment(&render_data, true, Vector2(1, 1), Color(), false, false); PassMode pass_mode = PASS_MODE_SHADOW; @@ -1974,7 +1976,7 @@ void RenderForwardMobile::_fill_render_list(RenderListType p_render_list, const } } -void RenderForwardMobile::_setup_environment(const RenderDataRD *p_render_data, bool p_no_fog, const Size2i &p_screen_size, bool p_flip_y, const Color &p_default_bg_color, bool p_opaque_render_buffers, bool p_pancake_shadows, int p_index) { +void RenderForwardMobile::_setup_environment(const RenderDataRD *p_render_data, bool p_no_fog, const Size2i &p_screen_size, const Color &p_default_bg_color, bool p_opaque_render_buffers, bool p_pancake_shadows, int p_index) { RID env = is_environment(p_render_data->environment) ? p_render_data->environment : RID(); RID reflection_probe_instance = p_render_data->reflection_probe.is_valid() ? RendererRD::LightStorage::get_singleton()->reflection_probe_instance_get_probe(p_render_data->reflection_probe) : RID(); @@ -1987,7 +1989,7 @@ void RenderForwardMobile::_setup_environment(const RenderDataRD *p_render_data, } } - p_render_data->scene_data->update_ubo(scene_state.uniform_buffers[p_index], get_debug_draw_mode(), env, reflection_probe_instance, p_render_data->camera_attributes, p_flip_y, p_pancake_shadows, p_screen_size, p_default_bg_color, _render_buffers_get_luminance_multiplier(), p_opaque_render_buffers, false); + p_render_data->scene_data->update_ubo(scene_state.uniform_buffers[p_index], get_debug_draw_mode(), env, reflection_probe_instance, p_render_data->camera_attributes, p_pancake_shadows, p_screen_size, p_default_bg_color, _render_buffers_get_luminance_multiplier(), p_opaque_render_buffers, false); } /// RENDERING /// diff --git a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h index aa1b8f34b2..b0fe552449 100644 --- a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h +++ b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h @@ -197,7 +197,7 @@ private: void _fill_instance_data(RenderListType p_render_list, uint32_t p_offset = 0, int32_t p_max_elements = -1, bool p_update_buffer = true); void _fill_render_list(RenderListType p_render_list, const RenderDataRD *p_render_data, PassMode p_pass_mode, bool p_append = false); - void _setup_environment(const RenderDataRD *p_render_data, bool p_no_fog, const Size2i &p_screen_size, bool p_flip_y, const Color &p_default_bg_color, bool p_opaque_render_buffers = false, bool p_pancake_shadows = false, int p_index = 0); + void _setup_environment(const RenderDataRD *p_render_data, bool p_no_fog, const Size2i &p_screen_size, const Color &p_default_bg_color, bool p_opaque_render_buffers = false, bool p_pancake_shadows = false, int p_index = 0); void _setup_lightmaps(const RenderDataRD *p_render_data, const PagedArray<RID> &p_lightmaps, const Transform3D &p_cam_transform); RID render_base_uniform_set; diff --git a/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp b/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp index 96bcd72099..0ebed49ee9 100644 --- a/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp +++ b/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp @@ -1124,6 +1124,7 @@ void RendererSceneRenderRD::render_scene(const Ref<RenderSceneBuffers> &p_render scene_data.camera_visible_layers = p_camera_data->visible_layers; scene_data.taa_jitter = p_camera_data->taa_jitter; scene_data.main_cam_transform = p_camera_data->main_transform; + scene_data.flip_y = !p_reflection_probe.is_valid(); scene_data.view_count = p_camera_data->view_count; for (uint32_t v = 0; v < p_camera_data->view_count; v++) { diff --git a/servers/rendering/renderer_rd/storage_rd/render_scene_data_rd.cpp b/servers/rendering/renderer_rd/storage_rd/render_scene_data_rd.cpp index dc1e64ddcc..ba8aafda6d 100644 --- a/servers/rendering/renderer_rd/storage_rd/render_scene_data_rd.cpp +++ b/servers/rendering/renderer_rd/storage_rd/render_scene_data_rd.cpp @@ -42,7 +42,11 @@ Transform3D RenderSceneDataRD::get_cam_transform() const { } Projection RenderSceneDataRD::get_cam_projection() const { - return cam_projection; + Projection correction; + correction.set_depth_correction(flip_y); + correction.add_jitter_offset(taa_jitter); + + return correction * cam_projection; } uint32_t RenderSceneDataRD::get_view_count() const { @@ -58,14 +62,18 @@ Vector3 RenderSceneDataRD::get_view_eye_offset(uint32_t p_view) const { Projection RenderSceneDataRD::get_view_projection(uint32_t p_view) const { ERR_FAIL_UNSIGNED_INDEX_V(p_view, view_count, Projection()); - return view_projection[p_view]; + Projection correction; + correction.set_depth_correction(flip_y); + correction.add_jitter_offset(taa_jitter); + + return correction * view_projection[p_view]; } RID RenderSceneDataRD::create_uniform_buffer() { return RD::get_singleton()->uniform_buffer_create(sizeof(UBODATA)); } -void RenderSceneDataRD::update_ubo(RID p_uniform_buffer, RS::ViewportDebugDraw p_debug_mode, RID p_env, RID p_reflection_probe_instance, RID p_camera_attributes, bool p_flip_y, bool p_pancake_shadows, const Size2i &p_screen_size, const Color &p_default_bg_color, float p_luminance_multiplier, bool p_opaque_render_buffers, bool p_apply_alpha_multiplier) { +void RenderSceneDataRD::update_ubo(RID p_uniform_buffer, RS::ViewportDebugDraw p_debug_mode, RID p_env, RID p_reflection_probe_instance, RID p_camera_attributes, bool p_pancake_shadows, const Size2i &p_screen_size, const Color &p_default_bg_color, float p_luminance_multiplier, bool p_opaque_render_buffers, bool p_apply_alpha_multiplier) { RendererSceneRenderRD *render_scene_render = RendererSceneRenderRD::get_singleton(); UBODATA ubo_data; @@ -76,7 +84,7 @@ void RenderSceneDataRD::update_ubo(RID p_uniform_buffer, RS::ViewportDebugDraw p UBO &prev_ubo = ubo_data.prev_ubo; Projection correction; - correction.set_depth_correction(p_flip_y); + correction.set_depth_correction(flip_y); correction.add_jitter_offset(taa_jitter); Projection projection = correction * cam_projection; diff --git a/servers/rendering/renderer_rd/storage_rd/render_scene_data_rd.h b/servers/rendering/renderer_rd/storage_rd/render_scene_data_rd.h index f6785942ed..5579a97792 100644 --- a/servers/rendering/renderer_rd/storage_rd/render_scene_data_rd.h +++ b/servers/rendering/renderer_rd/storage_rd/render_scene_data_rd.h @@ -50,6 +50,7 @@ public: Vector2 taa_jitter; uint32_t camera_visible_layers; bool cam_orthogonal = false; + bool flip_y = false; // For billboards to cast correct shadows. Transform3D main_cam_transform; @@ -90,7 +91,7 @@ public: virtual Projection get_view_projection(uint32_t p_view) const override; RID create_uniform_buffer(); - void update_ubo(RID p_uniform_buffer, RS::ViewportDebugDraw p_debug_mode, RID p_env, RID p_reflection_probe_instance, RID p_camera_attributes, bool p_flip_y, bool p_pancake_shadows, const Size2i &p_screen_size, const Color &p_default_bg_color, float p_luminance_multiplier, bool p_opaque_render_buffers, bool p_apply_alpha_multiplier); + void update_ubo(RID p_uniform_buffer, RS::ViewportDebugDraw p_debug_mode, RID p_env, RID p_reflection_probe_instance, RID p_camera_attributes, bool p_pancake_shadows, const Size2i &p_screen_size, const Color &p_default_bg_color, float p_luminance_multiplier, bool p_opaque_render_buffers, bool p_apply_alpha_multiplier); virtual RID get_uniform_buffer() const override; private: diff --git a/servers/rendering/rendering_device.cpp b/servers/rendering/rendering_device.cpp index 59f7b3d9e1..801ad1b825 100644 --- a/servers/rendering/rendering_device.cpp +++ b/servers/rendering/rendering_device.cpp @@ -5380,7 +5380,7 @@ Error RenderingDevice::initialize(RenderingContextDriver *p_context, DisplayServ frame = 0; frames.resize(frame_count); - max_timestamp_query_elements = 256; + max_timestamp_query_elements = GLOBAL_GET("debug/settings/profiler/max_timestamp_query_elements"); device = context->device_get(device_index); err = driver->initialize(device_index, frame_count); @@ -5631,7 +5631,7 @@ void RenderingDevice::_free_rids(T &p_owner, const char *p_type) { void RenderingDevice::capture_timestamp(const String &p_name) { ERR_FAIL_COND_MSG(draw_list != nullptr && draw_list->state.draw_count > 0, "Capturing timestamps during draw list creation is not allowed. Offending timestamp was: " + p_name); ERR_FAIL_COND_MSG(compute_list != nullptr && compute_list->state.dispatch_count > 0, "Capturing timestamps during compute list creation is not allowed. Offending timestamp was: " + p_name); - ERR_FAIL_COND(frames[frame].timestamp_count >= max_timestamp_query_elements); + ERR_FAIL_COND_MSG(frames[frame].timestamp_count >= max_timestamp_query_elements, vformat("Tried capturing more timestamps than the configured maximum (%d). You can increase this limit in the project settings under 'Debug/Settings' called 'Max Timestamp Query Elements'.", max_timestamp_query_elements)); draw_graph.add_capture_timestamp(frames[frame].timestamp_pool, frames[frame].timestamp_count); diff --git a/servers/rendering/shader_language.cpp b/servers/rendering/shader_language.cpp index f5e0b811a2..0b1595d988 100644 --- a/servers/rendering/shader_language.cpp +++ b/servers/rendering/shader_language.cpp @@ -39,6 +39,8 @@ #define HAS_WARNING(flag) (warning_flags & flag) +int ShaderLanguage::instance_counter = 0; + String ShaderLanguage::get_operator_text(Operator p_op) { static const char *op_names[OP_MAX] = { "==", "!=", @@ -10812,17 +10814,16 @@ ShaderLanguage::ShaderLanguage() { nodes = nullptr; completion_class = TAG_GLOBAL; - int idx = 0; - while (builtin_func_defs[idx].name) { - if (builtin_func_defs[idx].tag == SubClassTag::TAG_GLOBAL) { - const StringName &name = StringName(builtin_func_defs[idx].name); - - if (!global_func_set.has(name)) { - global_func_set.insert(name); + if (instance_counter == 0) { + int idx = 0; + while (builtin_func_defs[idx].name) { + if (builtin_func_defs[idx].tag == SubClassTag::TAG_GLOBAL) { + global_func_set.insert(builtin_func_defs[idx].name); } + idx++; } - idx++; } + instance_counter++; #ifdef DEBUG_ENABLED warnings_check_map.insert(ShaderWarning::UNUSED_CONSTANT, &used_constants); @@ -10837,5 +10838,8 @@ ShaderLanguage::ShaderLanguage() { ShaderLanguage::~ShaderLanguage() { clear(); - global_func_set.clear(); + instance_counter--; + if (instance_counter == 0) { + global_func_set.clear(); + } } diff --git a/servers/rendering/shader_language.h b/servers/rendering/shader_language.h index edac819a1e..076bd8def4 100644 --- a/servers/rendering/shader_language.h +++ b/servers/rendering/shader_language.h @@ -800,6 +800,8 @@ public: static bool is_control_flow_keyword(String p_keyword); static void get_builtin_funcs(List<String> *r_keywords); + static int instance_counter; + struct BuiltInInfo { DataType type = TYPE_VOID; bool constant = false; diff --git a/thirdparty/README.md b/thirdparty/README.md index 4572687be2..5bc76026c7 100644 --- a/thirdparty/README.md +++ b/thirdparty/README.md @@ -882,7 +882,7 @@ instead of `miniz.h` as an external dependency. ## thorvg - Upstream: https://github.com/thorvg/thorvg -- Version: 0.14.0 (ae4e9d003c93325f1eba64319fa9852a0d764b4c, 2024) +- Version: 0.14.1 (70b2f2dad158316dd08166d613b425248b36fd27, 2024) - License: MIT Files extracted from upstream source: diff --git a/thirdparty/thorvg/inc/config.h b/thirdparty/thorvg/inc/config.h index 6218c18e68..4be7e3936d 100644 --- a/thirdparty/thorvg/inc/config.h +++ b/thirdparty/thorvg/inc/config.h @@ -15,5 +15,5 @@ // For internal debugging: //#define THORVG_LOG_ENABLED -#define THORVG_VERSION_STRING "0.14.0" +#define THORVG_VERSION_STRING "0.14.1" #endif diff --git a/thirdparty/thorvg/inc/thorvg.h b/thirdparty/thorvg/inc/thorvg.h index 0b7e9771b9..20f1942a57 100644 --- a/thirdparty/thorvg/inc/thorvg.h +++ b/thirdparty/thorvg/inc/thorvg.h @@ -631,6 +631,8 @@ public: * The Canvas rendering can be performed asynchronously. To make sure that rendering is finished, * the sync() must be called after the draw() regardless of threading. * + * @retval Result::InsufficientCondition: The canvas is either already in sync condition or in a damaged condition (a draw is required before syncing). + * * @see Canvas::draw() */ virtual Result sync() noexcept; diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwCommon.h b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwCommon.h index 231410cdac..05cbdc7f3a 100644 --- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwCommon.h +++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwCommon.h @@ -23,6 +23,7 @@ #ifndef _TVG_SW_COMMON_H_ #define _TVG_SW_COMMON_H_ +#include <algorithm> #include "tvgCommon.h" #include "tvgRender.h" diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwFill.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwFill.cpp index be1662daeb..bd0b5ffdcb 100644 --- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwFill.cpp +++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwFill.cpp @@ -63,6 +63,66 @@ static void _calculateCoefficients(const SwFill* fill, uint32_t x, uint32_t y, f } +static uint32_t _estimateAAMargin(const Fill* fdata) +{ + constexpr float marginScalingFactor = 800.0f; + if (fdata->identifier() == TVG_CLASS_ID_RADIAL) { + auto radius = P(static_cast<const RadialGradient*>(fdata))->r; + return mathZero(radius) ? 0 : static_cast<uint32_t>(marginScalingFactor / radius); + } + auto grad = P(static_cast<const LinearGradient*>(fdata)); + Point p1 {grad->x1, grad->y1}; + Point p2 {grad->x2, grad->y2}; + auto length = mathLength(&p1, &p2); + return mathZero(length) ? 0 : static_cast<uint32_t>(marginScalingFactor / length); +} + + +static void _adjustAAMargin(uint32_t& iMargin, uint32_t index) +{ + constexpr float threshold = 0.1f; + constexpr uint32_t iMarginMax = 40; + + auto iThreshold = static_cast<uint32_t>(index * threshold); + if (iMargin > iThreshold) iMargin = iThreshold; + if (iMargin > iMarginMax) iMargin = iMarginMax; +} + + +static inline uint32_t _alphaUnblend(uint32_t c) +{ + auto a = (c >> 24); + if (a == 255 || a == 0) return c; + auto invA = 255.0f / static_cast<float>(a); + auto c0 = static_cast<uint8_t>(static_cast<float>((c >> 16) & 0xFF) * invA); + auto c1 = static_cast<uint8_t>(static_cast<float>((c >> 8) & 0xFF) * invA); + auto c2 = static_cast<uint8_t>(static_cast<float>(c & 0xFF) * invA); + + return (a << 24) | (c0 << 16) | (c1 << 8) | c2; +} + + +static void _applyAA(const SwFill* fill, uint32_t begin, uint32_t end) +{ + if (begin == 0 || end == 0) return; + + auto i = GRADIENT_STOP_SIZE - end; + auto rgbaEnd = _alphaUnblend(fill->ctable[i]); + auto rgbaBegin = _alphaUnblend(fill->ctable[begin]); + + auto dt = 1.0f / (begin + end + 1.0f); + float t = dt; + while (i != begin) { + auto dist = 255 - static_cast<int32_t>(255 * t); + auto color = INTERPOLATE(rgbaEnd, rgbaBegin, dist); + fill->ctable[i++] = ALPHA_BLEND((color | 0xff000000), (color >> 24)); + + if (i == GRADIENT_STOP_SIZE) i = 0; + t += dt; + } +} + + static bool _updateColorTable(SwFill* fill, const Fill* fdata, const SwSurface* surface, uint8_t opacity) { if (!fill->ctable) { @@ -88,6 +148,11 @@ static bool _updateColorTable(SwFill* fill, const Fill* fdata, const SwSurface* auto pos = 1.5f * inc; uint32_t i = 0; + //If repeat is true, anti-aliasing must be applied between the last and the first colors. + auto repeat = fill->spread == FillSpread::Repeat; + uint32_t iAABegin = repeat ? _estimateAAMargin(fdata) : 0; + uint32_t iAAEnd = 0; + fill->ctable[i++] = ALPHA_BLEND(rgba | 0xff000000, a); while (pos <= pColors->offset) { @@ -97,6 +162,11 @@ static bool _updateColorTable(SwFill* fill, const Fill* fdata, const SwSurface* } for (uint32_t j = 0; j < cnt - 1; ++j) { + if (repeat && j == cnt - 2 && iAAEnd == 0) { + iAAEnd = iAABegin; + _adjustAAMargin(iAAEnd, GRADIENT_STOP_SIZE - i); + } + auto curr = colors + j; auto next = curr + 1; auto delta = 1.0f / (next->offset - curr->offset); @@ -118,14 +188,18 @@ static bool _updateColorTable(SwFill* fill, const Fill* fdata, const SwSurface* } rgba = rgba2; a = a2; + + if (repeat && j == 0) _adjustAAMargin(iAABegin, i - 1); } rgba = ALPHA_BLEND((rgba | 0xff000000), a); for (; i < GRADIENT_STOP_SIZE; ++i) fill->ctable[i] = rgba; - //Make sure the last color stop is represented at the end of the table - fill->ctable[GRADIENT_STOP_SIZE - 1] = rgba; + //For repeat fill spread apply anti-aliasing between the last and first colors, + //othewise make sure the last color stop is represented at the end of the table. + if (repeat) _applyAA(fill, iAABegin, iAAEnd); + else fill->ctable[GRADIENT_STOP_SIZE - 1] = rgba; return true; } diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRaster.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRaster.cpp index 04f36c727f..b3507acdc3 100644 --- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRaster.cpp +++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRaster.cpp @@ -1087,6 +1087,7 @@ static bool _rasterDirectScaledMaskedImage(SwSurface* surface, const SwImage* im static bool _rasterScaledMaskedImage(SwSurface* surface, const SwImage* image, const Matrix* itransform, const SwBBox& region, uint8_t opacity) { + TVGERR("SW_ENGINE", "Not supported ScaledMaskedImage!"); #if 0 //Enable it when GRAYSCALE image is supported TVGLOG("SW_ENGINE", "Scaled Masked(%d) Image [Region: %lu %lu %lu %lu]", (int)surface->compositor->method, region.min.x, region.min.y, region.max.x - region.min.x, region.max.y - region.min.y); @@ -1100,6 +1101,11 @@ static bool _rasterScaledMaskedImage(SwSurface* surface, const SwImage* image, c static bool _rasterScaledMattedImage(SwSurface* surface, const SwImage* image, const Matrix* itransform, const SwBBox& region, uint8_t opacity) { + if (surface->channelSize == sizeof(uint8_t)) { + TVGERR("SW_ENGINE", "Not supported grayscale scaled matted image!"); + return false; + } + auto dbuffer = surface->buf32 + (region.min.y * surface->stride + region.min.x); auto csize = surface->compositor->image.channelSize; auto cbuffer = surface->compositor->image.buf8 + (region.min.y * surface->compositor->image.stride + region.min.x) * csize; @@ -1130,6 +1136,11 @@ static bool _rasterScaledMattedImage(SwSurface* surface, const SwImage* image, c static bool _rasterScaledBlendingImage(SwSurface* surface, const SwImage* image, const Matrix* itransform, const SwBBox& region, uint8_t opacity) { + if (surface->channelSize == sizeof(uint8_t)) { + TVGERR("SW_ENGINE", "Not supported grayscale scaled blending image!"); + return false; + } + auto dbuffer = surface->buf32 + (region.min.y * surface->stride + region.min.x); auto scaleMethod = image->scale < DOWN_SCALE_TOLERANCE ? _interpDownScaler : _interpUpScaler; auto sampleSize = _sampleSize(image->scale); @@ -1152,19 +1163,33 @@ static bool _rasterScaledBlendingImage(SwSurface* surface, const SwImage* image, static bool _rasterScaledImage(SwSurface* surface, const SwImage* image, const Matrix* itransform, const SwBBox& region, uint8_t opacity) { - auto dbuffer = surface->buf32 + (region.min.y * surface->stride + region.min.x); auto scaleMethod = image->scale < DOWN_SCALE_TOLERANCE ? _interpDownScaler : _interpUpScaler; auto sampleSize = _sampleSize(image->scale); int32_t miny = 0, maxy = 0; - for (auto y = region.min.y; y < region.max.y; ++y, dbuffer += surface->stride) { - SCALED_IMAGE_RANGE_Y(y) - auto dst = dbuffer; - for (auto x = region.min.x; x < region.max.x; ++x, ++dst) { - SCALED_IMAGE_RANGE_X - auto src = scaleMethod(image->buf32, image->stride, image->w, image->h, sx, sy, miny, maxy, sampleSize); - if (opacity < 255) src = ALPHA_BLEND(src, opacity); - *dst = src + ALPHA_BLEND(*dst, IA(src)); + //32bits channels + if (surface->channelSize == sizeof(uint32_t)) { + auto buffer = surface->buf32 + (region.min.y * surface->stride + region.min.x); + for (auto y = region.min.y; y < region.max.y; ++y, buffer += surface->stride) { + SCALED_IMAGE_RANGE_Y(y) + auto dst = buffer; + for (auto x = region.min.x; x < region.max.x; ++x, ++dst) { + SCALED_IMAGE_RANGE_X + auto src = scaleMethod(image->buf32, image->stride, image->w, image->h, sx, sy, miny, maxy, sampleSize); + if (opacity < 255) src = ALPHA_BLEND(src, opacity); + *dst = src + ALPHA_BLEND(*dst, IA(src)); + } + } + } else if (surface->channelSize == sizeof(uint8_t)) { + auto buffer = surface->buf8 + (region.min.y * surface->stride + region.min.x); + for (auto y = region.min.y; y < region.max.y; ++y, buffer += surface->stride) { + SCALED_IMAGE_RANGE_Y(y) + auto dst = buffer; + for (auto x = region.min.x; x < region.max.x; ++x, ++dst) { + SCALED_IMAGE_RANGE_X + auto src = scaleMethod(image->buf32, image->stride, image->w, image->h, sx, sy, miny, maxy, sampleSize); + *dst = MULTIPLY(A(src), opacity); + } } } return true; @@ -1173,11 +1198,6 @@ static bool _rasterScaledImage(SwSurface* surface, const SwImage* image, const M static bool _scaledImage(SwSurface* surface, const SwImage* image, const Matrix* transform, const SwBBox& region, uint8_t opacity) { - if (surface->channelSize == sizeof(uint8_t)) { - TVGERR("SW_ENGINE", "Not supported grayscale Textmap polygon mesh!"); - return false; - } - Matrix itransform; if (transform) { diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp index 0a3f5ef7e7..350f333405 100644 --- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp +++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp @@ -20,6 +20,7 @@ * SOFTWARE. */ +#include <algorithm> #include "tvgMath.h" #include "tvgSwCommon.h" #include "tvgTaskScheduler.h" @@ -86,7 +87,7 @@ struct SwShapeTask : SwTask Additionally, the stroke style should not be dashed. */ bool antialiasing(float strokeWidth) { - return strokeWidth < 2.0f || rshape->stroke->dashCnt > 0 || rshape->stroke->strokeFirst || rshape->strokeTrim(); + return strokeWidth < 2.0f || rshape->stroke->dashCnt > 0 || rshape->stroke->strokeFirst || rshape->strokeTrim() || rshape->stroke->color[3] < 255;; } float validStrokeWidth() diff --git a/thirdparty/thorvg/src/renderer/tvgCanvas.h b/thirdparty/thorvg/src/renderer/tvgCanvas.h index 9d216e2f30..81fd1b7d6f 100644 --- a/thirdparty/thorvg/src/renderer/tvgCanvas.h +++ b/thirdparty/thorvg/src/renderer/tvgCanvas.h @@ -129,7 +129,7 @@ struct Canvas::Impl return Result::Success; } - return Result::InsufficientCondition; + return Result::Unknown; } Result viewport(int32_t x, int32_t y, int32_t w, int32_t h) diff --git a/thirdparty/thorvg/src/renderer/tvgRender.h b/thirdparty/thorvg/src/renderer/tvgRender.h index 8f28d37dbc..a915d68fec 100644 --- a/thirdparty/thorvg/src/renderer/tvgRender.h +++ b/thirdparty/thorvg/src/renderer/tvgRender.h @@ -103,7 +103,7 @@ struct RenderRegion void intersect(const RenderRegion& rhs); void add(const RenderRegion& rhs); - bool operator==(const RenderRegion& rhs) + bool operator==(const RenderRegion& rhs) const { if (x == rhs.x && y == rhs.y && w == rhs.w && h == rhs.h) return true; return false; diff --git a/thirdparty/thorvg/src/renderer/tvgShape.h b/thirdparty/thorvg/src/renderer/tvgShape.h index c45995a64d..ecc58b6cc0 100644 --- a/thirdparty/thorvg/src/renderer/tvgShape.h +++ b/thirdparty/thorvg/src/renderer/tvgShape.h @@ -296,6 +296,7 @@ struct Shape::Impl if (!rs.stroke) rs.stroke = new RenderStroke(); if (rs.stroke->fill && rs.stroke->fill != p) delete(rs.stroke->fill); rs.stroke->fill = p; + rs.stroke->color[3] = 0; flag |= RenderUpdateFlag::Stroke; flag |= RenderUpdateFlag::GradientStroke; diff --git a/thirdparty/thorvg/update-thorvg.sh b/thirdparty/thorvg/update-thorvg.sh index d08158bfe7..e964e5ab6d 100755 --- a/thirdparty/thorvg/update-thorvg.sh +++ b/thirdparty/thorvg/update-thorvg.sh @@ -1,6 +1,6 @@ #!/bin/bash -e -VERSION=0.14.0 +VERSION=0.14.1 cd thirdparty/thorvg/ || true rm -rf AUTHORS LICENSE inc/ src/ *.zip *.tar.gz tmp/ |