summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/static_checks.yml2
-rw-r--r--core/config/project_settings.cpp5
-rw-r--r--core/input/input.cpp119
-rw-r--r--core/input/input.h19
-rw-r--r--core/input/input_map.cpp18
-rw-r--r--core/input/input_map.h5
-rw-r--r--core/io/resource.cpp4
-rw-r--r--core/io/resource.h1
-rw-r--r--core/io/resource_format_binary.cpp2
-rw-r--r--core/io/resource_loader.cpp2
-rw-r--r--doc/classes/@GlobalScope.xml2
-rw-r--r--doc/classes/CPUParticles2D.xml2
-rw-r--r--doc/classes/CPUParticles3D.xml2
-rw-r--r--doc/classes/GPUParticles2D.xml3
-rw-r--r--doc/classes/GPUParticles3D.xml3
-rw-r--r--doc/classes/ProjectSettings.xml3
-rw-r--r--doc/classes/RefCounted.xml1
-rw-r--r--doc/classes/RenderingServer.xml3
-rw-r--r--drivers/vulkan/rendering_device_vulkan.cpp2
-rw-r--r--editor/plugins/animation_state_machine_editor.cpp10
-rw-r--r--editor/plugins/texture_region_editor_plugin.cpp2
-rw-r--r--main/main.cpp3
-rwxr-xr-xmisc/scripts/codespell.sh2
-rw-r--r--modules/gdscript/gdscript.cpp109
-rw-r--r--modules/gdscript/gdscript.h14
-rw-r--r--modules/gdscript/gdscript_lambda_callable.cpp4
-rw-r--r--modules/gdscript/gdscript_lambda_callable.h4
-rw-r--r--modules/jpg/image_loader_jpegd.cpp6
-rw-r--r--modules/openxr/openxr_api.cpp2
-rw-r--r--modules/text_server_adv/gdextension_build/methods.py4
-rw-r--r--modules/text_server_fb/gdextension_build/methods.py4
-rw-r--r--modules/webp/webp_common.cpp4
-rw-r--r--platform/macos/display_server_macos.mm2
-rw-r--r--platform/web/js/libs/library_godot_input.js4
-rw-r--r--scene/resources/resource_format_text.cpp2
-rw-r--r--scene/resources/visual_shader_nodes.cpp2
-rw-r--r--servers/xr_server.cpp4
37 files changed, 278 insertions, 102 deletions
diff --git a/.github/workflows/static_checks.yml b/.github/workflows/static_checks.yml
index 4b7f721a23..e7ebd2fb64 100644
--- a/.github/workflows/static_checks.yml
+++ b/.github/workflows/static_checks.yml
@@ -112,5 +112,5 @@ jobs:
uses: codespell-project/actions-codespell@v2
with:
skip: "./bin,./thirdparty,*.desktop,*.gen.*,*.po,*.pot,*.rc,./AUTHORS.md,./COPYRIGHT.txt,./DONORS.md,./core/input/gamecontrollerdb.txt,./core/string/locales.h,./editor/project_converter_3_to_4.cpp,./misc/scripts/codespell.sh,./platform/android/java/lib/src/com,./platform/web/node_modules,./platform/web/package-lock.json"
- ignore_words_list: "curvelinear,doubleclick,expct,findn,gird,hel,inout,lod,mis,nd,numer,ot,requestor,te,vai"
+ ignore_words_list: "breaked,curvelinear,doubleclick,expct,findn,gird,hel,inout,lod,mis,nd,numer,ot,requestor,te,vai"
path: ${{ env.CHANGED_FILES }}
diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp
index cbbfe3de75..93934f2320 100644
--- a/core/config/project_settings.cpp
+++ b/core/config/project_settings.cpp
@@ -852,8 +852,8 @@ Error ProjectSettings::_save_settings_binary(const String &p_file, const RBMap<S
}
if (!p_custom_features.is_empty()) {
+ // Store how many properties are saved, add one for custom features, which must always go first.
file->store_32(count + 1);
- //store how many properties are saved, add one for custom featuers, which must always go first
String key = CoreStringNames::get_singleton()->_custom_features;
file->store_pascal_string(key);
@@ -870,7 +870,8 @@ Error ProjectSettings::_save_settings_binary(const String &p_file, const RBMap<S
file->store_buffer(buff.ptr(), buff.size());
} else {
- file->store_32(count); //store how many properties are saved
+ // Store how many properties are saved.
+ file->store_32(count);
}
for (const KeyValue<String, List<String>> &E : p_props) {
diff --git a/core/input/input.cpp b/core/input/input.cpp
index 2d48bdd4cf..257452b3d8 100644
--- a/core/input/input.cpp
+++ b/core/input/input.cpp
@@ -695,53 +695,34 @@ void Input::_parse_input_event_impl(const Ref<InputEvent> &p_event, bool p_is_em
}
for (const KeyValue<StringName, InputMap::Action> &E : InputMap::get_singleton()->get_action_map()) {
- if (InputMap::get_singleton()->event_is_action(p_event, E.key)) {
- Action &action = action_state[E.key];
- bool is_joypad_axis = jm.is_valid();
- bool is_pressed = false;
- if (!p_event->is_echo()) {
- if (p_event->is_action_pressed(E.key)) {
- bool is_joypad_axis_valid_zone_enter = false;
- if (is_joypad_axis) {
- if (!action.axis_pressed) {
- is_joypad_axis_valid_zone_enter = true;
- action.pressed++;
- action.axis_pressed = true;
- }
- } else {
- action.pressed++;
- }
- if (action.pressed == 1 && (is_joypad_axis_valid_zone_enter || !is_joypad_axis)) {
- action.pressed_physics_frame = Engine::get_singleton()->get_physics_frames();
- action.pressed_process_frame = Engine::get_singleton()->get_process_frames();
- }
- is_pressed = true;
- } else {
- bool is_released = true;
- if (is_joypad_axis) {
- if (action.axis_pressed) {
- action.axis_pressed = false;
- } else {
- is_released = false;
- }
- }
+ const int event_index = InputMap::get_singleton()->event_get_index(p_event, E.key);
+ if (event_index == -1) {
+ continue;
+ }
- if (is_released) {
- if (action.pressed == 1) {
- action.released_physics_frame = Engine::get_singleton()->get_physics_frames();
- action.released_process_frame = Engine::get_singleton()->get_process_frames();
- }
- action.pressed = MAX(action.pressed - 1, 0);
- }
+ Action &action = action_state[E.key];
+ if (!p_event->is_echo()) {
+ if (p_event->is_action_pressed(E.key)) {
+ if (!action.pressed) {
+ action.pressed_physics_frame = Engine::get_singleton()->get_physics_frames();
+ action.pressed_process_frame = Engine::get_singleton()->get_process_frames();
}
- action.exact = InputMap::get_singleton()->event_is_action(p_event, E.key, true);
- }
+ action.pressed |= ((uint64_t)1 << event_index);
+ } else {
+ action.pressed &= ~((uint64_t)1 << event_index);
+ action.pressed &= ~(1 << MAX_EVENT); // Always release the event from action_press() method.
- if (is_pressed || action.pressed == 0) {
- action.strength = p_event->get_action_strength(E.key);
- action.raw_strength = p_event->get_action_raw_strength(E.key);
+ if (!action.pressed) {
+ action.released_physics_frame = Engine::get_singleton()->get_physics_frames();
+ action.released_process_frame = Engine::get_singleton()->get_process_frames();
+ }
+ _update_action_strength(action, MAX_EVENT, 0.0);
+ _update_action_raw_strength(action, MAX_EVENT, 0.0);
}
+ action.exact = InputMap::get_singleton()->event_is_action(p_event, E.key, true);
}
+ _update_action_strength(action, event_index, p_event->get_action_strength(E.key));
+ _update_action_raw_strength(action, event_index, p_event->get_action_raw_strength(E.key));
}
if (event_dispatch_function) {
@@ -858,13 +839,13 @@ void Input::action_press(const StringName &p_action, float p_strength) {
// Create or retrieve existing action.
Action &action = action_state[p_action];
- action.pressed++;
- if (action.pressed == 1) {
+ if (!action.pressed) {
action.pressed_physics_frame = Engine::get_singleton()->get_physics_frames();
action.pressed_process_frame = Engine::get_singleton()->get_process_frames();
}
- action.strength = p_strength;
- action.raw_strength = p_strength;
+ action.pressed |= 1 << MAX_EVENT;
+ _update_action_strength(action, MAX_EVENT, p_strength);
+ _update_action_raw_strength(action, MAX_EVENT, p_strength);
action.exact = true;
}
@@ -872,13 +853,15 @@ void Input::action_release(const StringName &p_action) {
// Create or retrieve existing action.
Action &action = action_state[p_action];
- action.pressed--;
- if (action.pressed == 0) {
- action.released_physics_frame = Engine::get_singleton()->get_physics_frames();
- action.released_process_frame = Engine::get_singleton()->get_process_frames();
+ action.pressed = 0;
+ action.strength = 0.0;
+ action.raw_strength = 0.0;
+ action.released_physics_frame = Engine::get_singleton()->get_physics_frames();
+ action.released_process_frame = Engine::get_singleton()->get_process_frames();
+ for (uint64_t i = 0; i <= MAX_EVENT; i++) {
+ action.strengths[i] = 0.0;
+ action.raw_strengths[i] = 0.0;
}
- action.strength = 0.0f;
- action.raw_strength = 0.0f;
action.exact = true;
}
@@ -1207,6 +1190,38 @@ void Input::_axis_event(int p_device, JoyAxis p_axis, float p_value) {
parse_input_event(ievent);
}
+void Input::_update_action_strength(Action &p_action, int p_event_index, float p_strength) {
+ ERR_FAIL_INDEX(p_event_index, (int)MAX_EVENT + 1);
+
+ float old_strength = p_action.strengths[p_event_index];
+ p_action.strengths[p_event_index] = p_strength;
+
+ if (p_strength > p_action.strength) {
+ p_action.strength = p_strength;
+ } else if (Math::is_equal_approx(old_strength, p_action.strength)) {
+ p_action.strength = p_strength;
+ for (uint64_t i = 0; i <= MAX_EVENT; i++) {
+ p_action.strength = MAX(p_action.strength, p_action.strengths[i]);
+ }
+ }
+}
+
+void Input::_update_action_raw_strength(Action &p_action, int p_event_index, float p_strength) {
+ ERR_FAIL_INDEX(p_event_index, (int)MAX_EVENT + 1);
+
+ float old_strength = p_action.raw_strengths[p_event_index];
+ p_action.raw_strengths[p_event_index] = p_strength;
+
+ if (p_strength > p_action.raw_strength) {
+ p_action.raw_strength = p_strength;
+ } else if (Math::is_equal_approx(old_strength, p_action.raw_strength)) {
+ p_action.raw_strength = p_strength;
+ for (uint64_t i = 0; i <= MAX_EVENT; i++) {
+ p_action.raw_strength = MAX(p_action.raw_strength, p_action.raw_strengths[i]);
+ }
+ }
+}
+
Input::JoyEvent Input::_get_mapped_button_event(const JoyDeviceMapping &mapping, JoyButton p_button) {
JoyEvent event;
diff --git a/core/input/input.h b/core/input/input.h
index bedc3fa0e3..dd613c4877 100644
--- a/core/input/input.h
+++ b/core/input/input.h
@@ -44,6 +44,8 @@ class Input : public Object {
static Input *singleton;
+ static constexpr uint64_t MAX_EVENT = 31;
+
public:
enum MouseMode {
MOUSE_MODE_VISIBLE,
@@ -103,11 +105,22 @@ private:
uint64_t pressed_process_frame = UINT64_MAX;
uint64_t released_physics_frame = UINT64_MAX;
uint64_t released_process_frame = UINT64_MAX;
- int pressed = 0;
- bool axis_pressed = false;
+ uint64_t pressed = 0;
bool exact = true;
float strength = 0.0f;
float raw_strength = 0.0f;
+ LocalVector<float> strengths;
+ LocalVector<float> raw_strengths;
+
+ Action() {
+ strengths.resize(MAX_EVENT + 1);
+ raw_strengths.resize(MAX_EVENT + 1);
+
+ for (uint64_t i = 0; i <= MAX_EVENT; i++) {
+ strengths[i] = 0.0;
+ raw_strengths[i] = 0.0;
+ }
+ }
};
HashMap<StringName, Action> action_state;
@@ -227,6 +240,8 @@ private:
JoyAxis _get_output_axis(String output);
void _button_event(int p_device, JoyButton p_index, bool p_pressed);
void _axis_event(int p_device, JoyAxis p_axis, float p_value);
+ void _update_action_strength(Action &p_action, int p_event_index, float p_strength);
+ void _update_action_raw_strength(Action &p_action, int p_event_index, float p_strength);
void _parse_input_event_impl(const Ref<InputEvent> &p_event, bool p_is_emulated);
diff --git a/core/input/input_map.cpp b/core/input/input_map.cpp
index ddfde0e7cd..78b9ada884 100644
--- a/core/input/input_map.cpp
+++ b/core/input/input_map.cpp
@@ -127,16 +127,21 @@ List<StringName> InputMap::get_actions() const {
return actions;
}
-List<Ref<InputEvent>>::Element *InputMap::_find_event(Action &p_action, const Ref<InputEvent> &p_event, bool p_exact_match, bool *r_pressed, float *r_strength, float *r_raw_strength) const {
+List<Ref<InputEvent>>::Element *InputMap::_find_event(Action &p_action, const Ref<InputEvent> &p_event, bool p_exact_match, bool *r_pressed, float *r_strength, float *r_raw_strength, int *r_event_index) const {
ERR_FAIL_COND_V(!p_event.is_valid(), nullptr);
+ int i = 0;
for (List<Ref<InputEvent>>::Element *E = p_action.inputs.front(); E; E = E->next()) {
int device = E->get()->get_device();
if (device == ALL_DEVICES || device == p_event->get_device()) {
if (E->get()->action_match(p_event, p_exact_match, p_action.deadzone, r_pressed, r_strength, r_raw_strength)) {
+ if (r_event_index) {
+ *r_event_index = i;
+ }
return E;
}
}
+ i++;
}
return nullptr;
@@ -179,6 +184,7 @@ void InputMap::action_erase_event(const StringName &p_action, const Ref<InputEve
List<Ref<InputEvent>>::Element *E = _find_event(input_map[p_action], p_event, true);
if (E) {
input_map[p_action].inputs.erase(E);
+
if (Input::get_singleton()->is_action_pressed(p_action)) {
Input::get_singleton()->action_release(p_action);
}
@@ -216,7 +222,13 @@ bool InputMap::event_is_action(const Ref<InputEvent> &p_event, const StringName
return event_get_action_status(p_event, p_action, p_exact_match);
}
-bool InputMap::event_get_action_status(const Ref<InputEvent> &p_event, const StringName &p_action, bool p_exact_match, bool *r_pressed, float *r_strength, float *r_raw_strength) const {
+int InputMap::event_get_index(const Ref<InputEvent> &p_event, const StringName &p_action, bool p_exact_match) const {
+ int index = -1;
+ event_get_action_status(p_event, p_action, p_exact_match, nullptr, nullptr, nullptr, &index);
+ return index;
+}
+
+bool InputMap::event_get_action_status(const Ref<InputEvent> &p_event, const StringName &p_action, bool p_exact_match, bool *r_pressed, float *r_strength, float *r_raw_strength, int *r_event_index) const {
HashMap<StringName, Action>::Iterator E = input_map.find(p_action);
ERR_FAIL_COND_V_MSG(!E, false, suggest_actions(p_action));
@@ -236,7 +248,7 @@ bool InputMap::event_get_action_status(const Ref<InputEvent> &p_event, const Str
return input_event_action->get_action() == p_action;
}
- List<Ref<InputEvent>>::Element *event = _find_event(E->value, p_event, p_exact_match, r_pressed, r_strength, r_raw_strength);
+ List<Ref<InputEvent>>::Element *event = _find_event(E->value, p_event, p_exact_match, r_pressed, r_strength, r_raw_strength, r_event_index);
return event != nullptr;
}
diff --git a/core/input/input_map.h b/core/input/input_map.h
index b4d5beacb3..6407ea489e 100644
--- a/core/input/input_map.h
+++ b/core/input/input_map.h
@@ -61,7 +61,7 @@ private:
HashMap<String, List<Ref<InputEvent>>> default_builtin_cache;
HashMap<String, List<Ref<InputEvent>>> default_builtin_with_overrides_cache;
- List<Ref<InputEvent>>::Element *_find_event(Action &p_action, const Ref<InputEvent> &p_event, bool p_exact_match = false, bool *r_pressed = nullptr, float *r_strength = nullptr, float *r_raw_strength = nullptr) const;
+ List<Ref<InputEvent>>::Element *_find_event(Action &p_action, const Ref<InputEvent> &p_event, bool p_exact_match = false, bool *r_pressed = nullptr, float *r_strength = nullptr, float *r_raw_strength = nullptr, int *r_event_index = nullptr) const;
TypedArray<InputEvent> _action_get_events(const StringName &p_action);
TypedArray<StringName> _get_actions();
@@ -86,7 +86,8 @@ public:
const List<Ref<InputEvent>> *action_get_events(const StringName &p_action);
bool event_is_action(const Ref<InputEvent> &p_event, const StringName &p_action, bool p_exact_match = false) const;
- bool event_get_action_status(const Ref<InputEvent> &p_event, const StringName &p_action, bool p_exact_match = false, bool *r_pressed = nullptr, float *r_strength = nullptr, float *r_raw_strength = nullptr) const;
+ int event_get_index(const Ref<InputEvent> &p_event, const StringName &p_action, bool p_exact_match = false) const;
+ bool event_get_action_status(const Ref<InputEvent> &p_event, const StringName &p_action, bool p_exact_match = false, bool *r_pressed = nullptr, float *r_strength = nullptr, float *r_raw_strength = nullptr, int *r_event_index = nullptr) const;
const HashMap<StringName, Action> &get_action_map() const;
void load_from_project_settings();
diff --git a/core/io/resource.cpp b/core/io/resource.cpp
index e0d42a274a..64fa597a67 100644
--- a/core/io/resource.cpp
+++ b/core/io/resource.cpp
@@ -90,6 +90,10 @@ String Resource::get_path() const {
return path_cache;
}
+void Resource::set_path_cache(const String &p_path) {
+ path_cache = p_path;
+}
+
String Resource::generate_scene_unique_id() {
// Generate a unique enough hash, but still user-readable.
// If it's not unique it does not matter because the saver will try again.
diff --git a/core/io/resource.h b/core/io/resource.h
index a9b1a88f6b..610c2150db 100644
--- a/core/io/resource.h
+++ b/core/io/resource.h
@@ -103,6 +103,7 @@ public:
virtual void set_path(const String &p_path, bool p_take_over = false);
String get_path() const;
+ void set_path_cache(const String &p_path); // Set raw path without involving resource cache.
_FORCE_INLINE_ bool is_built_in() const { return path_cache.is_empty() || path_cache.contains("::") || path_cache.begins_with("local://"); }
static String generate_scene_unique_id();
diff --git a/core/io/resource_format_binary.cpp b/core/io/resource_format_binary.cpp
index ea97e5ecce..2a33f723dc 100644
--- a/core/io/resource_format_binary.cpp
+++ b/core/io/resource_format_binary.cpp
@@ -774,6 +774,8 @@ Error ResourceLoaderBinary::load() {
res = Ref<Resource>(r);
if (!path.is_empty() && cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE) {
r->set_path(path, cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE); //if got here because the resource with same path has different type, replace it
+ } else if (!path.is_resource_file()) {
+ r->set_path_cache(path);
}
r->set_scene_unique_id(id);
}
diff --git a/core/io/resource_loader.cpp b/core/io/resource_loader.cpp
index 6721ec0953..b1ebdff91f 100644
--- a/core/io/resource_loader.cpp
+++ b/core/io/resource_loader.cpp
@@ -341,6 +341,8 @@ void ResourceLoader::_thread_load_function(void *p_userdata) {
if (load_task.resource.is_valid()) {
if (load_task.cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE) {
load_task.resource->set_path(load_task.local_path);
+ } else if (!load_task.local_path.is_resource_file()) {
+ load_task.resource->set_path_cache(load_task.local_path);
}
if (load_task.xl_remapped) {
diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml
index b06c28311a..1ae3674f65 100644
--- a/doc/classes/@GlobalScope.xml
+++ b/doc/classes/@GlobalScope.xml
@@ -1477,7 +1477,7 @@
<return type="Variant" />
<param index="0" name="obj" type="Variant" />
<description>
- Returns a weak reference to an object, or [code]null[/code] if [param obj] is invalid.
+ Returns a [WeakRef] instance holding a weak reference to [param obj]. Returns an empty [WeakRef] instance if [param obj] is [code]null[/code]. Prints an error and returns [code]null[/code] if [param obj] is neither [Object]-derived nor [code]null[/code].
A weak reference to an object is not enough to keep the object alive: when the only remaining references to a referent are weak references, garbage collection is free to destroy the referent and reuse its memory for something else. However, until the object is actually destroyed the weak reference may return the object even if there are no strong references to it.
</description>
</method>
diff --git a/doc/classes/CPUParticles2D.xml b/doc/classes/CPUParticles2D.xml
index 051635cb48..92a57007bd 100644
--- a/doc/classes/CPUParticles2D.xml
+++ b/doc/classes/CPUParticles2D.xml
@@ -297,7 +297,7 @@
Particles are drawn in the order emitted.
</constant>
<constant name="DRAW_ORDER_LIFETIME" value="1" enum="DrawOrder">
- Particles are drawn in order of remaining lifetime.
+ Particles are drawn in order of remaining lifetime. In other words, the particle with the highest lifetime is drawn at the front.
</constant>
<constant name="PARAM_INITIAL_LINEAR_VELOCITY" value="0" enum="Parameter">
Use with [method set_param_min], [method set_param_max], and [method set_param_curve] to set initial velocity properties.
diff --git a/doc/classes/CPUParticles3D.xml b/doc/classes/CPUParticles3D.xml
index 9d16f3e8a4..27404b68bb 100644
--- a/doc/classes/CPUParticles3D.xml
+++ b/doc/classes/CPUParticles3D.xml
@@ -322,7 +322,7 @@
Particles are drawn in the order emitted.
</constant>
<constant name="DRAW_ORDER_LIFETIME" value="1" enum="DrawOrder">
- Particles are drawn in order of remaining lifetime.
+ Particles are drawn in order of remaining lifetime. In other words, the particle with the highest lifetime is drawn at the front.
</constant>
<constant name="DRAW_ORDER_VIEW_DEPTH" value="2" enum="DrawOrder">
Particles are drawn in order of depth.
diff --git a/doc/classes/GPUParticles2D.xml b/doc/classes/GPUParticles2D.xml
index 08dc6a5331..2308ec43c5 100644
--- a/doc/classes/GPUParticles2D.xml
+++ b/doc/classes/GPUParticles2D.xml
@@ -141,9 +141,10 @@
Particles are drawn in the order emitted.
</constant>
<constant name="DRAW_ORDER_LIFETIME" value="1" enum="DrawOrder">
- Particles are drawn in order of remaining lifetime.
+ Particles are drawn in order of remaining lifetime. In other words, the particle with the highest lifetime is drawn at the front.
</constant>
<constant name="DRAW_ORDER_REVERSE_LIFETIME" value="2" enum="DrawOrder">
+ Particles are drawn in reverse order of remaining lifetime. In other words, the particle with the lowest lifetime is drawn at the front.
</constant>
<constant name="EMIT_FLAG_POSITION" value="1" enum="EmitFlags">
Particle starts at the specified position.
diff --git a/doc/classes/GPUParticles3D.xml b/doc/classes/GPUParticles3D.xml
index a982c7e40e..b5af63a8f4 100644
--- a/doc/classes/GPUParticles3D.xml
+++ b/doc/classes/GPUParticles3D.xml
@@ -166,9 +166,10 @@
Particles are drawn in the order emitted.
</constant>
<constant name="DRAW_ORDER_LIFETIME" value="1" enum="DrawOrder">
- Particles are drawn in order of remaining lifetime.
+ Particles are drawn in order of remaining lifetime. In other words, the particle with the highest lifetime is drawn at the front.
</constant>
<constant name="DRAW_ORDER_REVERSE_LIFETIME" value="2" enum="DrawOrder">
+ Particles are drawn in reverse order of remaining lifetime. In other words, the particle with the lowest lifetime is drawn at the front.
</constant>
<constant name="DRAW_ORDER_VIEW_DEPTH" value="3" enum="DrawOrder">
Particles are drawn in order of depth.
diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml
index 93e0ed5491..3fc9e94427 100644
--- a/doc/classes/ProjectSettings.xml
+++ b/doc/classes/ProjectSettings.xml
@@ -328,7 +328,8 @@
Changes to this setting will only be applied upon restarting the application.
</member>
<member name="application/run/frame_delay_msec" type="int" setter="" getter="" default="0">
- Forces a delay between frames in the main loop (in milliseconds). This may be useful if you plan to disable vertical synchronization.
+ Forces a [i]constant[/i] delay between frames in the main loop (in milliseconds). In most situations, [member application/run/max_fps] should be preferred as an FPS limiter as it's more precise.
+ This setting can be overridden using the [code]--frame-delay &lt;ms;&gt;[/code] command line argument.
</member>
<member name="application/run/low_processor_mode" type="bool" setter="" getter="" default="false">
If [code]true[/code], enables low-processor usage mode. This setting only works on desktop platforms. The screen is not redrawn if nothing changes visually. This is meant for writing applications and editors, but is pretty useless (and can hurt performance) in most games.
diff --git a/doc/classes/RefCounted.xml b/doc/classes/RefCounted.xml
index fe10f84449..3ef87e462d 100644
--- a/doc/classes/RefCounted.xml
+++ b/doc/classes/RefCounted.xml
@@ -6,6 +6,7 @@
<description>
Base class for any object that keeps a reference count. [Resource] and many other helper objects inherit this class.
Unlike other [Object] types, [RefCounted]s keep an internal reference counter so that they are automatically released when no longer in use, and only then. [RefCounted]s therefore do not need to be freed manually with [method Object.free].
+ [RefCounted] instances caught in a cyclic reference will [b]not[/b] be freed automatically. For example, if a node holds a reference to instance [code]A[/code], which directly or indirectly holds a reference back to [code]A[/code], [code]A[/code]'s reference count will be 2. Destruction of the node will leave [code]A[/code] dangling with a reference count of 1, and there will be a memory leak. To prevent this, one of the references in the cycle can be made weak with [method @GlobalScope.weakref].
In the vast majority of use cases, instantiating and using [RefCounted]-derived types is all you need to do. The methods provided in this class are only for advanced users, and can cause issues if misused.
[b]Note:[/b] In C#, reference-counted objects will not be freed instantly after they are no longer in use. Instead, garbage collection will run periodically and will free reference-counted objects that are no longer in use. This means that unused ones will linger on for a while before being removed.
</description>
diff --git a/doc/classes/RenderingServer.xml b/doc/classes/RenderingServer.xml
index e5409a6e8a..da07582773 100644
--- a/doc/classes/RenderingServer.xml
+++ b/doc/classes/RenderingServer.xml
@@ -4505,9 +4505,10 @@
Draw particles in the order that they appear in the particles array.
</constant>
<constant name="PARTICLES_DRAW_ORDER_LIFETIME" value="1" enum="ParticlesDrawOrder">
- Sort particles based on their lifetime.
+ Sort particles based on their lifetime. In other words, the particle with the highest lifetime is drawn at the front.
</constant>
<constant name="PARTICLES_DRAW_ORDER_REVERSE_LIFETIME" value="2" enum="ParticlesDrawOrder">
+ Sort particles based on the inverse of their lifetime. In other words, the particle with the lowest lifetime is drawn at the front.
</constant>
<constant name="PARTICLES_DRAW_ORDER_VIEW_DEPTH" value="3" enum="ParticlesDrawOrder">
Sort particles based on their distance to the camera.
diff --git a/drivers/vulkan/rendering_device_vulkan.cpp b/drivers/vulkan/rendering_device_vulkan.cpp
index a9f77c9072..c3a365a3b8 100644
--- a/drivers/vulkan/rendering_device_vulkan.cpp
+++ b/drivers/vulkan/rendering_device_vulkan.cpp
@@ -2105,7 +2105,7 @@ RID RenderingDeviceVulkan::texture_create_shared(const TextureView &p_view, RID
VkImageViewUsageCreateInfo usage_info;
if (context->is_device_extension_enabled(VK_KHR_MAINTENANCE_2_EXTENSION_NAME)) {
- // May need to make VK_KHR_maintenance2 manditory and thus has Vulkan 1.1 be our minimum supported version
+ // May need to make VK_KHR_maintenance2 mandatory and thus has Vulkan 1.1 be our minimum supported version
// if we require setting this information. Vulkan 1.0 may simply not care..
usage_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO;
diff --git a/editor/plugins/animation_state_machine_editor.cpp b/editor/plugins/animation_state_machine_editor.cpp
index 11b5cd488b..e9c8b0c610 100644
--- a/editor/plugins/animation_state_machine_editor.cpp
+++ b/editor/plugins/animation_state_machine_editor.cpp
@@ -76,6 +76,11 @@ void AnimationNodeStateMachineEditor::edit(const Ref<AnimationNode> &p_node) {
_update_graph();
}
+ if (read_only) {
+ tool_create->set_pressed(false);
+ tool_connect->set_pressed(false);
+ }
+
tool_create->set_disabled(read_only);
tool_connect->set_disabled(read_only);
}
@@ -1591,6 +1596,11 @@ void AnimationNodeStateMachineEditor::_update_mode() {
selection_tools_hb->hide();
}
+ if (read_only) {
+ tool_create->set_pressed(false);
+ tool_connect->set_pressed(false);
+ }
+
if (tool_connect->is_pressed()) {
transition_tools_hb->show();
} else {
diff --git a/editor/plugins/texture_region_editor_plugin.cpp b/editor/plugins/texture_region_editor_plugin.cpp
index f5bff6f3df..73de15631a 100644
--- a/editor/plugins/texture_region_editor_plugin.cpp
+++ b/editor/plugins/texture_region_editor_plugin.cpp
@@ -1095,7 +1095,7 @@ TextureRegionEditor::TextureRegionEditor() {
snap_mode_button->add_item(TTR("Pixel Snap"), 1);
snap_mode_button->add_item(TTR("Grid Snap"), 2);
snap_mode_button->add_item(TTR("Auto Slice"), 3);
- snap_mode_button->select(0);
+ snap_mode_button->select(snap_mode);
snap_mode_button->connect("item_selected", callable_mp(this, &TextureRegionEditor::_set_snap_mode));
hb_grid = memnew(HBoxContainer);
diff --git a/main/main.cpp b/main/main.cpp
index 7ca22d90ca..271791f368 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -2128,6 +2128,9 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
if (frame_delay == 0) {
frame_delay = GLOBAL_DEF(PropertyInfo(Variant::INT, "application/run/frame_delay_msec", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), 0);
+ if (Engine::get_singleton()->is_editor_hint()) {
+ frame_delay = 0;
+ }
}
if (audio_output_latency >= 1) {
diff --git a/misc/scripts/codespell.sh b/misc/scripts/codespell.sh
index a55a39bf99..2de440679f 100755
--- a/misc/scripts/codespell.sh
+++ b/misc/scripts/codespell.sh
@@ -3,6 +3,6 @@ SKIP_LIST="./.*,./**/.*,./bin,./thirdparty,*.desktop,*.gen.*,*.po,*.pot,*.rc,./A
SKIP_LIST+="./core/input/gamecontrollerdb.txt,./core/string/locales.h,./editor/renames_map_3_to_4.cpp,./misc/scripts/codespell.sh,"
SKIP_LIST+="./platform/android/java/lib/src/com,./platform/web/node_modules,./platform/web/package-lock.json,"
-IGNORE_LIST="curvelinear,doubleclick,expct,findn,gird,hel,inout,lod,mis,nd,numer,ot,requestor,te,vai"
+IGNORE_LIST="breaked,curvelinear,doubleclick,expct,findn,gird,hel,inout,lod,mis,nd,numer,ot,requestor,te,vai"
codespell -w -q 3 -S "${SKIP_LIST}" -L "${IGNORE_LIST}" --builtin "clear,rare,en-GB_to_en-US"
diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp
index 6245cc85a0..4accdb4d21 100644
--- a/modules/gdscript/gdscript.cpp
+++ b/modules/gdscript/gdscript.cpp
@@ -992,6 +992,7 @@ void GDScript::set_path(const String &p_path, bool p_take_over) {
String old_path = path;
path = p_path;
+ path_valid = true;
GDScriptCache::move_script(old_path, p_path);
for (KeyValue<StringName, Ref<GDScript>> &kv : subclasses) {
@@ -1000,6 +1001,9 @@ void GDScript::set_path(const String &p_path, bool p_take_over) {
}
String GDScript::get_script_path() const {
+ if (!path_valid && !get_path().is_empty()) {
+ return get_path();
+ }
return path;
}
@@ -1035,6 +1039,7 @@ Error GDScript::load_source_code(const String &p_path) {
source = s;
path = p_path;
+ path_valid = true;
#ifdef TOOLS_ENABLED
source_changed_cache = true;
set_edited(false);
@@ -1387,33 +1392,106 @@ String GDScript::debug_get_script_name(const Ref<Script> &p_script) {
#endif
thread_local GDScript::UpdatableFuncPtr GDScript::func_ptrs_to_update_thread_local;
+GDScript::UpdatableFuncPtr *GDScript::func_ptrs_to_update_main_thread = &func_ptrs_to_update_thread_local;
+
+GDScript::UpdatableFuncPtrElement *GDScript::_add_func_ptr_to_update(GDScriptFunction **p_func_ptr_ptr) {
+ MutexLock lock(func_ptrs_to_update_mutex);
-GDScript::UpdatableFuncPtrElement GDScript::_add_func_ptr_to_update(GDScriptFunction **p_func_ptr_ptr) {
- UpdatableFuncPtrElement result = {};
+ List<UpdatableFuncPtrElement>::Element *result = func_ptrs_to_update_elems.push_back(UpdatableFuncPtrElement());
{
- MutexLock lock(func_ptrs_to_update_thread_local.mutex);
- result.element = func_ptrs_to_update_thread_local.ptrs.push_back(p_func_ptr_ptr);
- result.mutex = &func_ptrs_to_update_thread_local.mutex;
+ MutexLock lock2(func_ptrs_to_update_thread_local.mutex);
+ result->get().element = func_ptrs_to_update_thread_local.ptrs.push_back(p_func_ptr_ptr);
+ result->get().mutex = &func_ptrs_to_update_thread_local.mutex;
if (likely(func_ptrs_to_update_thread_local.initialized)) {
- return result;
+ return &result->get();
}
func_ptrs_to_update_thread_local.initialized = true;
}
- MutexLock lock(func_ptrs_to_update_mutex);
func_ptrs_to_update.push_back(&func_ptrs_to_update_thread_local);
- return result;
+ return &result->get();
+}
+
+void GDScript::_remove_func_ptr_to_update(const UpdatableFuncPtrElement *p_func_ptr_element) {
+ // None of these checks should ever fail, unless there's a bug.
+ // They can be removed once we are sure they never catch anything.
+ // Left here now due to extra safety needs late in the release cycle.
+ ERR_FAIL_NULL(p_func_ptr_element);
+ MutexLock lock(*p_func_ptr_element->mutex);
+ ERR_FAIL_NULL(p_func_ptr_element->element);
+ ERR_FAIL_NULL(p_func_ptr_element->mutex);
+ p_func_ptr_element->element->erase();
}
-void GDScript::_remove_func_ptr_to_update(const UpdatableFuncPtrElement p_func_ptr_element) {
- ERR_FAIL_NULL(p_func_ptr_element.element);
- ERR_FAIL_NULL(p_func_ptr_element.mutex);
- MutexLock lock(*p_func_ptr_element.mutex);
- p_func_ptr_element.element->erase();
+void GDScript::_fixup_thread_function_bookkeeping() {
+ // Transfer the ownership of these update items to the main thread,
+ // because the current one is dying, leaving theirs orphan, dangling.
+
+ HashSet<GDScript *> scripts;
+
+ DEV_ASSERT(!Thread::is_main_thread());
+ MutexLock lock(func_ptrs_to_update_main_thread->mutex);
+
+ {
+ MutexLock lock2(func_ptrs_to_update_thread_local.mutex);
+
+ while (!func_ptrs_to_update_thread_local.ptrs.is_empty()) {
+ // Transfer the thread-to-script records from the dying thread to the main one.
+
+ List<GDScriptFunction **>::Element *E = func_ptrs_to_update_thread_local.ptrs.front();
+ List<GDScriptFunction **>::Element *new_E = func_ptrs_to_update_main_thread->ptrs.push_front(E->get());
+
+ GDScript *script = (*E->get())->get_script();
+ if (!scripts.has(script)) {
+ scripts.insert(script);
+
+ // Replace dying thread by the main thread in the script-to-thread records.
+
+ MutexLock lock3(script->func_ptrs_to_update_mutex);
+ DEV_ASSERT(script->func_ptrs_to_update.find(&func_ptrs_to_update_thread_local));
+ {
+ for (List<UpdatableFuncPtrElement>::Element *F = script->func_ptrs_to_update_elems.front(); F; F = F->next()) {
+ bool is_dying_thread_entry = F->get().mutex == &func_ptrs_to_update_thread_local.mutex;
+ if (is_dying_thread_entry) {
+ // This may lead to multiple main-thread entries, but that's not a problem
+ // and allows to reuse the element, which is needed, since it's tracked by pointer.
+ F->get().element = new_E;
+ F->get().mutex = &func_ptrs_to_update_main_thread->mutex;
+ }
+ }
+ }
+ }
+
+ E->erase();
+ }
+ }
+ func_ptrs_to_update_main_thread->initialized = true;
+
+ {
+ // Remove orphan thread-to-script entries from every script.
+ // FIXME: This involves iterating through every script whenever a thread dies.
+ // While it's OK that thread creation/destruction are heavy operations,
+ // additional bookkeeping can be used to outperform this brute-force approach.
+
+ GDScriptLanguage *gd_lang = GDScriptLanguage::get_singleton();
+
+ MutexLock lock2(gd_lang->mutex);
+
+ for (SelfList<GDScript> *s = gd_lang->script_list.first(); s; s = s->next()) {
+ GDScript *script = s->self();
+ for (List<UpdatableFuncPtr *>::Element *E = script->func_ptrs_to_update.front(); E; E = E->next()) {
+ bool is_dying_thread_entry = &E->get()->mutex == &func_ptrs_to_update_thread_local.mutex;
+ if (is_dying_thread_entry) {
+ E->erase();
+ break;
+ }
+ }
+ }
+ }
}
void GDScript::clear(ClearData *p_clear_data) {
@@ -1441,6 +1519,7 @@ void GDScript::clear(ClearData *p_clear_data) {
*func_ptr_ptr = nullptr;
}
}
+ func_ptrs_to_update_elems.clear();
}
RBSet<GDScript *> must_clear_dependencies = get_must_clear_dependencies();
@@ -2060,6 +2139,10 @@ void GDScriptLanguage::remove_named_global_constant(const StringName &p_name) {
named_globals.erase(p_name);
}
+void GDScriptLanguage::thread_exit() {
+ GDScript::_fixup_thread_function_bookkeeping();
+}
+
void GDScriptLanguage::init() {
//populate global constants
int gcc = CoreConstants::get_global_constant_count();
diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h
index 04b0a1d786..9b99f5ca0b 100644
--- a/modules/gdscript/gdscript.h
+++ b/modules/gdscript/gdscript.h
@@ -128,11 +128,16 @@ class GDScript : public Script {
Mutex *mutex = nullptr;
};
static thread_local UpdatableFuncPtr func_ptrs_to_update_thread_local;
+ static thread_local LocalVector<List<UpdatableFuncPtr *>::Element> func_ptrs_to_update_entries_thread_local;
+ static UpdatableFuncPtr *func_ptrs_to_update_main_thread;
List<UpdatableFuncPtr *> func_ptrs_to_update;
+ List<UpdatableFuncPtrElement> func_ptrs_to_update_elems;
Mutex func_ptrs_to_update_mutex;
- UpdatableFuncPtrElement _add_func_ptr_to_update(GDScriptFunction **p_func_ptr_ptr);
- static void _remove_func_ptr_to_update(const UpdatableFuncPtrElement p_func_ptr_element);
+ UpdatableFuncPtrElement *_add_func_ptr_to_update(GDScriptFunction **p_func_ptr_ptr);
+ static void _remove_func_ptr_to_update(const UpdatableFuncPtrElement *p_func_ptr_element);
+
+ static void _fixup_thread_function_bookkeeping();
#ifdef TOOLS_ENABLED
// For static data storage during hot-reloading.
@@ -171,6 +176,7 @@ class GDScript : public Script {
//exported members
String source;
String path;
+ bool path_valid = false; // False if using default path.
StringName local_name; // Inner class identifier or `class_name`.
StringName global_name; // `class_name`.
String fully_qualified_name;
@@ -553,6 +559,10 @@ public:
virtual void add_named_global_constant(const StringName &p_name, const Variant &p_value) override;
virtual void remove_named_global_constant(const StringName &p_name) override;
+ /* MULTITHREAD FUNCTIONS */
+
+ virtual void thread_exit() override;
+
/* DEBUGGER FUNCTIONS */
virtual String debug_get_error() const override;
diff --git a/modules/gdscript/gdscript_lambda_callable.cpp b/modules/gdscript/gdscript_lambda_callable.cpp
index 547f5607d3..339d1ac08e 100644
--- a/modules/gdscript/gdscript_lambda_callable.cpp
+++ b/modules/gdscript/gdscript_lambda_callable.cpp
@@ -296,5 +296,7 @@ GDScriptLambdaSelfCallable::GDScriptLambdaSelfCallable(Object *p_self, GDScriptF
}
GDScriptLambdaSelfCallable::~GDScriptLambdaSelfCallable() {
- GDScript::_remove_func_ptr_to_update(updatable_func_ptr_element);
+ if (updatable_func_ptr_element) {
+ GDScript::_remove_func_ptr_to_update(updatable_func_ptr_element);
+ }
}
diff --git a/modules/gdscript/gdscript_lambda_callable.h b/modules/gdscript/gdscript_lambda_callable.h
index ee7d547544..d961f18852 100644
--- a/modules/gdscript/gdscript_lambda_callable.h
+++ b/modules/gdscript/gdscript_lambda_callable.h
@@ -45,7 +45,7 @@ class GDScriptLambdaCallable : public CallableCustom {
GDScriptFunction *function = nullptr;
Ref<GDScript> script;
uint32_t h;
- GDScript::UpdatableFuncPtrElement updatable_func_ptr_element;
+ GDScript::UpdatableFuncPtrElement *updatable_func_ptr_element = nullptr;
Vector<Variant> captures;
@@ -72,7 +72,7 @@ class GDScriptLambdaSelfCallable : public CallableCustom {
Ref<RefCounted> reference; // For objects that are RefCounted, keep a reference.
Object *object = nullptr; // For non RefCounted objects, use a direct pointer.
uint32_t h;
- GDScript::UpdatableFuncPtrElement updatable_func_ptr_element;
+ GDScript::UpdatableFuncPtrElement *updatable_func_ptr_element = nullptr;
Vector<Variant> captures;
diff --git a/modules/jpg/image_loader_jpegd.cpp b/modules/jpg/image_loader_jpegd.cpp
index 0b9fcf4455..e7fa909706 100644
--- a/modules/jpg/image_loader_jpegd.cpp
+++ b/modules/jpg/image_loader_jpegd.cpp
@@ -156,7 +156,11 @@ public:
static Error _jpgd_save_to_output_stream(jpge::output_stream *p_output_stream, const Ref<Image> &p_img, float p_quality) {
ERR_FAIL_COND_V(p_img.is_null() || p_img->is_empty(), ERR_INVALID_PARAMETER);
- Ref<Image> image = p_img;
+ Ref<Image> image = p_img->duplicate();
+ if (image->is_compressed()) {
+ Error error = image->decompress();
+ ERR_FAIL_COND_V_MSG(error != OK, error, "Couldn't decompress image.");
+ }
if (image->get_format() != Image::FORMAT_RGB8) {
image->convert(Image::FORMAT_RGB8);
}
diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp
index dc3ccccd08..3c606de670 100644
--- a/modules/openxr/openxr_api.cpp
+++ b/modules/openxr/openxr_api.cpp
@@ -289,7 +289,7 @@ bool OpenXRAPI::create_instance() {
for (KeyValue<String, bool *> &requested_extension : requested_extensions) {
if (!is_extension_supported(requested_extension.key)) {
if (requested_extension.value == nullptr) {
- // Null means this is a manditory extension so we fail.
+ // Null means this is a mandatory extension so we fail.
ERR_FAIL_V_MSG(false, String("OpenXR: OpenXR Runtime does not support ") + requested_extension.key + String(" extension!"));
} else {
// Set this extension as not supported.
diff --git a/modules/text_server_adv/gdextension_build/methods.py b/modules/text_server_adv/gdextension_build/methods.py
index 3c5229462c..e58bc3abec 100644
--- a/modules/text_server_adv/gdextension_build/methods.py
+++ b/modules/text_server_adv/gdextension_build/methods.py
@@ -99,8 +99,8 @@ def make_icu_data(target, source, env):
def write_macos_plist(target, binary_name, identifier, name):
- os.makedirs(f"{target}/Resourece/", exist_ok=True)
- f = open(f"{target}/Resourece/Info.plist", "w")
+ os.makedirs(f"{target}/Resource/", exist_ok=True)
+ f = open(f"{target}/Resource/Info.plist", "w")
f.write(f'<?xml version="1.0" encoding="UTF-8"?>\n')
f.write(f'<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n')
diff --git a/modules/text_server_fb/gdextension_build/methods.py b/modules/text_server_fb/gdextension_build/methods.py
index 3c5229462c..e58bc3abec 100644
--- a/modules/text_server_fb/gdextension_build/methods.py
+++ b/modules/text_server_fb/gdextension_build/methods.py
@@ -99,8 +99,8 @@ def make_icu_data(target, source, env):
def write_macos_plist(target, binary_name, identifier, name):
- os.makedirs(f"{target}/Resourece/", exist_ok=True)
- f = open(f"{target}/Resourece/Info.plist", "w")
+ os.makedirs(f"{target}/Resource/", exist_ok=True)
+ f = open(f"{target}/Resource/Info.plist", "w")
f.write(f'<?xml version="1.0" encoding="UTF-8"?>\n')
f.write(f'<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n')
diff --git a/modules/webp/webp_common.cpp b/modules/webp/webp_common.cpp
index bc34a25733..3a2ac5a90e 100644
--- a/modules/webp/webp_common.cpp
+++ b/modules/webp/webp_common.cpp
@@ -59,6 +59,10 @@ Vector<uint8_t> _webp_packer(const Ref<Image> &p_image, float p_quality, bool p_
compression_method = CLAMP(compression_method, 0, 6);
Ref<Image> img = p_image->duplicate();
+ if (img->is_compressed()) {
+ Error error = img->decompress();
+ ERR_FAIL_COND_V_MSG(error != OK, Vector<uint8_t>(), "Couldn't decompress image.");
+ }
if (img->detect_alpha()) {
img->convert(Image::FORMAT_RGBA8);
} else {
diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm
index d76e41f61c..cb4c80cc75 100644
--- a/platform/macos/display_server_macos.mm
+++ b/platform/macos/display_server_macos.mm
@@ -2488,7 +2488,7 @@ void DisplayServerMacOS::warp_mouse(const Point2i &p_position) {
NSRect pointInWindowRect = NSMakeRect(p_position.x / scale, contentRect.size.height - (p_position.y / scale), scale, scale);
NSPoint pointOnScreen = [[wd.window_view window] convertRectToScreen:pointInWindowRect].origin;
- // Point in scren coords.
+ // Point in screen coords.
CGPoint lMouseWarpPos = { pointOnScreen.x, CGDisplayBounds(CGMainDisplayID()).size.height - pointOnScreen.y };
// Do the warping.
diff --git a/platform/web/js/libs/library_godot_input.js b/platform/web/js/libs/library_godot_input.js
index 92113e85c9..eaff40f89c 100644
--- a/platform/web/js/libs/library_godot_input.js
+++ b/platform/web/js/libs/library_godot_input.js
@@ -135,9 +135,9 @@ const GodotInputGamepads = {
}
const id = pad.id;
- // Chrom* style: NAME (Vendor: xxxx Product: xxxx)
+ // Chrom* style: NAME (Vendor: xxxx Product: xxxx).
const exp1 = /vendor: ([0-9a-f]{4}) product: ([0-9a-f]{4})/i;
- // Firefox/Safari style (safari may remove leading zeores)
+ // Firefox/Safari style (Safari may remove leading zeroes).
const exp2 = /^([0-9a-f]+)-([0-9a-f]+)-/i;
let vendor = '';
let product = '';
diff --git a/scene/resources/resource_format_text.cpp b/scene/resources/resource_format_text.cpp
index dd39f88352..19718f12be 100644
--- a/scene/resources/resource_format_text.cpp
+++ b/scene/resources/resource_format_text.cpp
@@ -577,6 +577,8 @@ Error ResourceLoaderText::load() {
if (do_assign) {
if (cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE) {
res->set_path(path, cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE);
+ } else if (!path.is_resource_file()) {
+ res->set_path_cache(path);
}
res->set_scene_unique_id(id);
}
diff --git a/scene/resources/visual_shader_nodes.cpp b/scene/resources/visual_shader_nodes.cpp
index 71b8af6625..ccd730eef2 100644
--- a/scene/resources/visual_shader_nodes.cpp
+++ b/scene/resources/visual_shader_nodes.cpp
@@ -6346,6 +6346,7 @@ String get_sampler_hint(VisualShaderNodeTextureParameter::TextureType p_texture_
if (!repeat_code.is_empty()) {
if (!has_colon) {
code += " : ";
+ has_colon = true;
} else {
code += ", ";
}
@@ -6353,6 +6354,7 @@ String get_sampler_hint(VisualShaderNodeTextureParameter::TextureType p_texture_
}
}
+ // source
{
String source_code;
diff --git a/servers/xr_server.cpp b/servers/xr_server.cpp
index 7e2c512554..dae342a037 100644
--- a/servers/xr_server.cpp
+++ b/servers/xr_server.cpp
@@ -198,9 +198,7 @@ void XRServer::remove_interface(const Ref<XRInterface> &p_interface) {
};
ERR_FAIL_COND_MSG(idx == -1, "Interface not found.");
-
- print_verbose("XR: Removed interface" + p_interface->get_name());
-
+ print_verbose("XR: Removed interface \"" + p_interface->get_name() + "\"");
emit_signal(SNAME("interface_removed"), p_interface->get_name());
interfaces.remove_at(idx);
};