summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/android_builds.yml54
-rw-r--r--core/io/resource_format_binary.cpp68
-rw-r--r--core/io/resource_loader.cpp36
-rw-r--r--core/io/resource_loader.h4
-rw-r--r--core/os/midi_driver.cpp199
-rw-r--r--core/os/midi_driver.h68
-rw-r--r--core/os/os.cpp5
-rw-r--r--core/variant/typed_array.h22
-rw-r--r--doc/classes/EditorUndoRedoManager.xml7
-rw-r--r--doc/classes/HashingContext.xml10
-rw-r--r--doc/classes/PhysicalBone3D.xml1
-rw-r--r--doc/classes/ProjectSettings.xml1
-rw-r--r--doc/classes/Skeleton3D.xml2
-rw-r--r--drivers/alsamidi/midi_driver_alsamidi.cpp177
-rw-r--r--drivers/alsamidi/midi_driver_alsamidi.h41
-rw-r--r--drivers/coremidi/midi_driver_coremidi.cpp79
-rw-r--r--drivers/coremidi/midi_driver_coremidi.h21
-rw-r--r--drivers/winmidi/midi_driver_winmidi.cpp55
-rw-r--r--drivers/winmidi/midi_driver_winmidi.h8
-rw-r--r--editor/create_dialog.cpp9
-rw-r--r--editor/editor_inspector.cpp6
-rw-r--r--editor/editor_node.cpp20
-rw-r--r--editor/editor_node.h2
-rw-r--r--editor/editor_properties_array_dict.cpp4
-rw-r--r--editor/editor_resource_picker.cpp8
-rw-r--r--editor/editor_undo_redo_manager.cpp16
-rw-r--r--editor/editor_undo_redo_manager.h2
-rw-r--r--editor/filesystem_dock.cpp2
-rw-r--r--editor/icons/AudioStreamPlayer.svg2
-rw-r--r--editor/icons/AudioStreamPlayer2D.svg2
-rw-r--r--editor/icons/AudioStreamPlayer3D.svg2
-rw-r--r--editor/plugins/cast_2d_editor_plugin.cpp1
-rw-r--r--editor/plugins/node_3d_editor_plugin.cpp33
-rw-r--r--editor/plugins/node_3d_editor_plugin.h2
-rw-r--r--editor/plugins/script_editor_plugin.cpp9
-rw-r--r--editor/plugins/sprite_frames_editor_plugin.cpp4
-rw-r--r--editor/plugins/visual_shader_editor_plugin.cpp6
-rw-r--r--editor/themes/editor_fonts.cpp2
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp12
-rw-r--r--modules/gdscript/gdscript_editor.cpp27
-rw-r--r--modules/gridmap/editor/grid_map_editor_plugin.cpp21
-rw-r--r--modules/webxr/doc_classes/WebXRInterface.xml17
-rw-r--r--modules/webxr/godot_webxr.h2
-rw-r--r--modules/webxr/native/library_godot_webxr.js8
-rw-r--r--modules/webxr/native/webxr.externs.js10
-rw-r--r--modules/webxr/webxr_interface_js.cpp62
-rw-r--r--modules/webxr/webxr_interface_js.h8
-rw-r--r--platform/web/export/export_plugin.cpp122
-rw-r--r--platform/web/export/export_plugin.h10
-rw-r--r--scene/2d/animated_sprite_2d.cpp5
-rw-r--r--scene/3d/physics/physical_bone_3d.cpp12
-rw-r--r--scene/3d/skeleton_3d.cpp7
-rw-r--r--scene/3d/skeleton_3d.h1
-rw-r--r--scene/3d/sprite_3d.cpp5
-rw-r--r--scene/main/viewport.cpp11
-rw-r--r--scene/main/viewport.h1
-rw-r--r--scene/resources/resource_format_text.cpp59
-rw-r--r--servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp9
-rw-r--r--servers/rendering/renderer_rd/shaders/particles.glsl18
-rw-r--r--servers/rendering/rendering_device_binds.cpp3
-rw-r--r--tests/core/variant/test_array.h37
61 files changed, 929 insertions, 528 deletions
diff --git a/.github/workflows/android_builds.yml b/.github/workflows/android_builds.yml
index 9a488bd095..f99a31179e 100644
--- a/.github/workflows/android_builds.yml
+++ b/.github/workflows/android_builds.yml
@@ -13,9 +13,30 @@ concurrency:
cancel-in-progress: true
jobs:
- android-template:
+ build-android:
runs-on: "ubuntu-20.04"
- name: Template (target=template_release)
+ name: ${{ matrix.name }}
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - name: Editor (target=editor)
+ cache-name: android-editor
+ target: editor
+ tests: false
+ sconsflags: arch=arm64 production=yes
+
+ - name: Template arm32 (target=template_release, arch=arm32)
+ cache-name: android-template-arm32
+ target: template_release
+ tests: false
+ sconsflags: arch=arm32
+
+ - name: Template arm64 (target=template_release, arch=arm64)
+ cache-name: android-template-arm64
+ target: template_release
+ tests: false
+ sconsflags: arch=arm64
steps:
- uses: actions/checkout@v4
@@ -30,33 +51,38 @@ jobs:
- name: Setup Godot build cache
uses: ./.github/actions/godot-cache
+ with:
+ cache-name: ${{ matrix.cache-name }}
continue-on-error: true
- name: Setup Python and SCons
uses: ./.github/actions/godot-deps
- - name: Compilation (arm32)
- uses: ./.github/actions/godot-build
- with:
- sconsflags: ${{ env.SCONSFLAGS }} arch=arm32
- platform: android
- target: template_release
- tests: false
-
- - name: Compilation (arm64)
+ - name: Compilation
uses: ./.github/actions/godot-build
with:
- sconsflags: ${{ env.SCONSFLAGS }} arch=arm64
+ sconsflags: ${{ env.SCONSFLAGS }} ${{ matrix.sconsflags }}
platform: android
- target: template_release
- tests: false
+ target: ${{ matrix.target }}
+ tests: ${{ matrix.tests }}
- name: Generate Godot templates
+ if: matrix.target == 'template_release'
run: |
cd platform/android/java
./gradlew generateGodotTemplates
cd ../../..
ls -l bin/
+ - name: Generate Godot editor
+ if: matrix.target == 'editor'
+ run: |
+ cd platform/android/java
+ ./gradlew generateGodotEditor
+ cd ../../..
+ ls -l bin/android_editor_builds/
+
- name: Upload artifact
uses: ./.github/actions/upload-artifact
+ with:
+ name: ${{ matrix.cache-name }}
diff --git a/core/io/resource_format_binary.cpp b/core/io/resource_format_binary.cpp
index ab460c5f4c..f71257fa76 100644
--- a/core/io/resource_format_binary.cpp
+++ b/core/io/resource_format_binary.cpp
@@ -749,44 +749,54 @@ Error ResourceLoaderBinary::load() {
String t = get_unicode_string();
Ref<Resource> res;
+ Resource *r = nullptr;
- if (cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE && ResourceCache::has(path)) {
- //use the existing one
- Ref<Resource> cached = ResourceCache::get_ref(path);
- if (cached->get_class() == t) {
- cached->reset_state();
- res = cached;
- }
+ MissingResource *missing_resource = nullptr;
+
+ if (main) {
+ res = ResourceLoader::get_resource_ref_override(local_path);
+ r = res.ptr();
}
+ if (!r) {
+ if (cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE && ResourceCache::has(path)) {
+ //use the existing one
+ Ref<Resource> cached = ResourceCache::get_ref(path);
+ if (cached->get_class() == t) {
+ cached->reset_state();
+ res = cached;
+ }
+ }
- MissingResource *missing_resource = nullptr;
+ if (res.is_null()) {
+ //did not replace
+
+ Object *obj = ClassDB::instantiate(t);
+ if (!obj) {
+ if (ResourceLoader::is_creating_missing_resources_if_class_unavailable_enabled()) {
+ //create a missing resource
+ missing_resource = memnew(MissingResource);
+ missing_resource->set_original_class(t);
+ missing_resource->set_recording_properties(true);
+ obj = missing_resource;
+ } else {
+ error = ERR_FILE_CORRUPT;
+ ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, local_path + ":Resource of unrecognized type in file: " + t + ".");
+ }
+ }
- if (res.is_null()) {
- //did not replace
-
- Object *obj = ClassDB::instantiate(t);
- if (!obj) {
- if (ResourceLoader::is_creating_missing_resources_if_class_unavailable_enabled()) {
- //create a missing resource
- missing_resource = memnew(MissingResource);
- missing_resource->set_original_class(t);
- missing_resource->set_recording_properties(true);
- obj = missing_resource;
- } else {
+ r = Object::cast_to<Resource>(obj);
+ if (!r) {
+ String obj_class = obj->get_class();
error = ERR_FILE_CORRUPT;
- ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, local_path + ":Resource of unrecognized type in file: " + t + ".");
+ memdelete(obj); //bye
+ ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, local_path + ":Resource type in resource field not a resource, type is: " + obj_class + ".");
}
- }
- Resource *r = Object::cast_to<Resource>(obj);
- if (!r) {
- String obj_class = obj->get_class();
- error = ERR_FILE_CORRUPT;
- memdelete(obj); //bye
- ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, local_path + ":Resource type in resource field not a resource, type is: " + obj_class + ".");
+ res = Ref<Resource>(r);
}
+ }
- res = Ref<Resource>(r);
+ if (r) {
if (!path.is_empty()) {
if (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.
diff --git a/core/io/resource_loader.cpp b/core/io/resource_loader.cpp
index eb3b117297..ed5e482296 100644
--- a/core/io/resource_loader.cpp
+++ b/core/io/resource_loader.cpp
@@ -272,6 +272,7 @@ Ref<Resource> ResourceLoader::_load(const String &p_path, const String &p_origin
}
load_paths_stack->resize(load_paths_stack->size() - 1);
+ res_ref_overrides.erase(load_nesting);
load_nesting--;
if (!res.is_null()) {
@@ -730,6 +731,40 @@ Ref<Resource> ResourceLoader::_load_complete_inner(LoadToken &p_load_token, Erro
}
}
+Ref<Resource> ResourceLoader::ensure_resource_ref_override_for_outer_load(const String &p_path, const String &p_res_type) {
+ ERR_FAIL_COND_V(load_nesting == 0, Ref<Resource>()); // It makes no sense to use this from nesting level 0.
+ const String &local_path = _validate_local_path(p_path);
+ HashMap<String, Ref<Resource>> &overrides = res_ref_overrides[load_nesting - 1];
+ HashMap<String, Ref<Resource>>::Iterator E = overrides.find(local_path);
+ if (E) {
+ return E->value;
+ } else {
+ Object *obj = ClassDB::instantiate(p_res_type);
+ ERR_FAIL_NULL_V(obj, Ref<Resource>());
+ Ref<Resource> res(obj);
+ if (!res.is_valid()) {
+ memdelete(obj);
+ ERR_FAIL_V(Ref<Resource>());
+ }
+ overrides[local_path] = res;
+ return res;
+ }
+}
+
+Ref<Resource> ResourceLoader::get_resource_ref_override(const String &p_path) {
+ DEV_ASSERT(p_path == _validate_local_path(p_path));
+ HashMap<int, HashMap<String, Ref<Resource>>>::Iterator E = res_ref_overrides.find(load_nesting);
+ if (!E) {
+ return nullptr;
+ }
+ HashMap<String, Ref<Resource>>::Iterator F = E->value.find(p_path);
+ if (!F) {
+ return nullptr;
+ }
+
+ return F->value;
+}
+
bool ResourceLoader::exists(const String &p_path, const String &p_type_hint) {
String local_path = _validate_local_path(p_path);
@@ -1222,6 +1257,7 @@ bool ResourceLoader::timestamp_on_load = false;
thread_local int ResourceLoader::load_nesting = 0;
thread_local WorkerThreadPool::TaskID ResourceLoader::caller_task_id = 0;
thread_local Vector<String> *ResourceLoader::load_paths_stack;
+thread_local HashMap<int, HashMap<String, Ref<Resource>>> ResourceLoader::res_ref_overrides;
template <>
thread_local uint32_t SafeBinaryMutex<ResourceLoader::BINARY_MUTEX_TAG>::count = 0;
diff --git a/core/io/resource_loader.h b/core/io/resource_loader.h
index 11abb4dc18..c48f39b5cc 100644
--- a/core/io/resource_loader.h
+++ b/core/io/resource_loader.h
@@ -187,6 +187,7 @@ private:
static thread_local int load_nesting;
static thread_local WorkerThreadPool::TaskID caller_task_id;
+ static thread_local HashMap<int, HashMap<String, Ref<Resource>>> res_ref_overrides; // Outermost key is nesting level.
static thread_local Vector<String> *load_paths_stack; // A pointer to avoid broken TLS implementations from double-running the destructor.
static SafeBinaryMutex<BINARY_MUTEX_TAG> thread_load_mutex;
static HashMap<String, ThreadLoadTask> thread_load_tasks;
@@ -272,6 +273,9 @@ public:
static void set_create_missing_resources_if_class_unavailable(bool p_enable);
_FORCE_INLINE_ static bool is_creating_missing_resources_if_class_unavailable_enabled() { return create_missing_resources_if_class_unavailable; }
+ static Ref<Resource> ensure_resource_ref_override_for_outer_load(const String &p_path, const String &p_res_type);
+ static Ref<Resource> get_resource_ref_override(const String &p_path);
+
static bool is_cleaning_tasks();
static void initialize();
diff --git a/core/os/midi_driver.cpp b/core/os/midi_driver.cpp
index 6870c84b49..6c748b1498 100644
--- a/core/os/midi_driver.cpp
+++ b/core/os/midi_driver.cpp
@@ -38,88 +38,167 @@ MIDIDriver *MIDIDriver::get_singleton() {
return singleton;
}
-void MIDIDriver::set_singleton() {
+MIDIDriver::MIDIDriver() {
singleton = this;
}
-void MIDIDriver::receive_input_packet(int device_index, uint64_t timestamp, uint8_t *data, uint32_t length) {
- Ref<InputEventMIDI> event;
- event.instantiate();
- event->set_device(device_index);
- uint32_t param_position = 1;
-
- if (length >= 1) {
- if (data[0] >= 0xF0) {
- // channel does not apply to system common messages
- event->set_channel(0);
- event->set_message(MIDIMessage(data[0]));
- last_received_message = data[0];
- } else if ((data[0] & 0x80) == 0x00) {
- // running status
- event->set_channel(last_received_message & 0xF);
- event->set_message(MIDIMessage(last_received_message >> 4));
- param_position = 0;
+MIDIDriver::MessageCategory MIDIDriver::Parser::category(uint8_t p_midi_fragment) {
+ if (p_midi_fragment >= 0xf8) {
+ return MessageCategory::RealTime;
+ } else if (p_midi_fragment >= 0xf0) {
+ // System Exclusive begin/end are specified as System Common Category
+ // messages, but we separate them here and give them their own categories
+ // as their behavior is significantly different.
+ if (p_midi_fragment == 0xf0) {
+ return MessageCategory::SysExBegin;
+ } else if (p_midi_fragment == 0xf7) {
+ return MessageCategory::SysExEnd;
+ }
+ return MessageCategory::SystemCommon;
+ } else if (p_midi_fragment >= 0x80) {
+ return MessageCategory::Voice;
+ }
+ return MessageCategory::Data;
+}
+
+MIDIMessage MIDIDriver::Parser::status_to_msg_enum(uint8_t p_status_byte) {
+ if (p_status_byte & 0x80) {
+ if (p_status_byte < 0xf0) {
+ return MIDIMessage(p_status_byte >> 4);
} else {
- event->set_channel(data[0] & 0xF);
- event->set_message(MIDIMessage(data[0] >> 4));
- param_position = 1;
- last_received_message = data[0];
+ return MIDIMessage(p_status_byte);
}
}
+ return MIDIMessage::NONE;
+}
- switch (event->get_message()) {
- case MIDIMessage::AFTERTOUCH:
- if (length >= 2 + param_position) {
- event->set_pitch(data[param_position]);
- event->set_pressure(data[param_position + 1]);
- }
- break;
+size_t MIDIDriver::Parser::expected_data(uint8_t p_status_byte) {
+ return expected_data(status_to_msg_enum(p_status_byte));
+}
+size_t MIDIDriver::Parser::expected_data(MIDIMessage p_msg_type) {
+ switch (p_msg_type) {
+ case MIDIMessage::NOTE_OFF:
+ case MIDIMessage::NOTE_ON:
+ case MIDIMessage::AFTERTOUCH:
case MIDIMessage::CONTROL_CHANGE:
- if (length >= 2 + param_position) {
- event->set_controller_number(data[param_position]);
- event->set_controller_value(data[param_position + 1]);
- }
- break;
+ case MIDIMessage::PITCH_BEND:
+ case MIDIMessage::SONG_POSITION_POINTER:
+ return 2;
+ case MIDIMessage::PROGRAM_CHANGE:
+ case MIDIMessage::CHANNEL_PRESSURE:
+ case MIDIMessage::QUARTER_FRAME:
+ case MIDIMessage::SONG_SELECT:
+ return 1;
+ default:
+ return 0;
+ }
+}
- case MIDIMessage::NOTE_ON:
+uint8_t MIDIDriver::Parser::channel(uint8_t p_status_byte) {
+ if (category(p_status_byte) == MessageCategory::Voice) {
+ return p_status_byte & 0x0f;
+ }
+ return 0;
+}
+
+void MIDIDriver::send_event(int p_device_index, uint8_t p_status,
+ const uint8_t *p_data, size_t p_data_len) {
+ const MIDIMessage msg = Parser::status_to_msg_enum(p_status);
+ ERR_FAIL_COND(p_data_len < Parser::expected_data(msg));
+
+ Ref<InputEventMIDI> event;
+ event.instantiate();
+ event->set_device(p_device_index);
+ event->set_channel(Parser::channel(p_status));
+ event->set_message(msg);
+ switch (msg) {
case MIDIMessage::NOTE_OFF:
- if (length >= 2 + param_position) {
- event->set_pitch(data[param_position]);
- event->set_velocity(data[param_position + 1]);
- }
+ case MIDIMessage::NOTE_ON:
+ event->set_pitch(p_data[0]);
+ event->set_velocity(p_data[1]);
break;
-
- case MIDIMessage::PITCH_BEND:
- if (length >= 2 + param_position) {
- event->set_pitch((data[param_position + 1] << 7) | data[param_position]);
- }
+ case MIDIMessage::AFTERTOUCH:
+ event->set_pitch(p_data[0]);
+ event->set_pressure(p_data[1]);
+ break;
+ case MIDIMessage::CONTROL_CHANGE:
+ event->set_controller_number(p_data[0]);
+ event->set_controller_value(p_data[1]);
break;
-
case MIDIMessage::PROGRAM_CHANGE:
- if (length >= 1 + param_position) {
- event->set_instrument(data[param_position]);
- }
+ event->set_instrument(p_data[0]);
break;
-
case MIDIMessage::CHANNEL_PRESSURE:
- if (length >= 1 + param_position) {
- event->set_pressure(data[param_position]);
- }
+ event->set_pressure(p_data[0]);
+ break;
+ case MIDIMessage::PITCH_BEND:
+ event->set_pitch((p_data[1] << 7) | p_data[0]);
break;
+ // QUARTER_FRAME, SONG_POSITION_POINTER, and SONG_SELECT not yet implemented.
default:
break;
}
-
- Input *id = Input::get_singleton();
- id->parse_input_event(event);
+ Input::get_singleton()->parse_input_event(event);
}
-PackedStringArray MIDIDriver::get_connected_inputs() {
- PackedStringArray list;
- return list;
+void MIDIDriver::Parser::parse_fragment(uint8_t p_fragment) {
+ switch (category(p_fragment)) {
+ case MessageCategory::RealTime:
+ // Real-Time messages are single byte messages that can
+ // occur at any point and do not interrupt other messages.
+ // We pass them straight through.
+ MIDIDriver::send_event(device_index, p_fragment);
+ break;
+
+ case MessageCategory::SysExBegin:
+ status_byte = p_fragment;
+ skipping_sys_ex = true;
+ break;
+
+ case MessageCategory::SysExEnd:
+ status_byte = 0;
+ skipping_sys_ex = false;
+ break;
+
+ case MessageCategory::Voice:
+ case MessageCategory::SystemCommon:
+ skipping_sys_ex = false; // If we were in SysEx, assume it was aborted.
+ received_data_len = 0;
+ status_byte = 0;
+ ERR_FAIL_COND(expected_data(p_fragment) > DATA_BUFFER_SIZE);
+ if (expected_data(p_fragment) == 0) {
+ // No data bytes needed, post it now.
+ MIDIDriver::send_event(device_index, p_fragment);
+ } else {
+ status_byte = p_fragment;
+ }
+ break;
+
+ case MessageCategory::Data:
+ // We don't currently process SysEx messages, so ignore their data.
+ if (!skipping_sys_ex) {
+ const size_t expected = expected_data(status_byte);
+ if (received_data_len < expected) {
+ data_buffer[received_data_len] = p_fragment;
+ received_data_len++;
+ if (received_data_len == expected) {
+ MIDIDriver::send_event(device_index, status_byte,
+ data_buffer, expected);
+ received_data_len = 0;
+ // Voice messages can use 'running status', sending further
+ // messages without resending their status byte.
+ // For other messages types we clear the cached status byte.
+ if (category(status_byte) != MessageCategory::Voice) {
+ status_byte = 0;
+ }
+ }
+ }
+ }
+ break;
+ }
}
-MIDIDriver::MIDIDriver() {
- set_singleton();
+PackedStringArray MIDIDriver::get_connected_inputs() const {
+ return connected_input_names;
}
diff --git a/core/os/midi_driver.h b/core/os/midi_driver.h
index cad3d8189e..ddce63f9c8 100644
--- a/core/os/midi_driver.h
+++ b/core/os/midi_driver.h
@@ -42,19 +42,73 @@ class MIDIDriver {
static MIDIDriver *singleton;
static uint8_t last_received_message;
+protected:
+ // Categories of message for parser logic.
+ enum class MessageCategory {
+ Data,
+ Voice,
+ SysExBegin,
+ SystemCommon, // excluding System Exclusive Begin/End
+ SysExEnd,
+ RealTime,
+ };
+
+ // Convert midi data to InputEventMIDI and send it to Input.
+ // p_data_len is the length of the buffer passed at p_data, this must be
+ // at least equal to the data required by the passed message type, but
+ // may be larger. Only the required data will be read.
+ static void send_event(int p_device_index, uint8_t p_status,
+ const uint8_t *p_data = nullptr, size_t p_data_len = 0);
+
+ class Parser {
+ public:
+ Parser() = default;
+ Parser(int p_device_index) :
+ device_index{ p_device_index } {}
+ virtual ~Parser() = default;
+
+ // Push a byte of MIDI stream. Any completed messages will be
+ // forwarded to MIDIDriver::send_event.
+ void parse_fragment(uint8_t p_fragment);
+
+ static MessageCategory category(uint8_t p_midi_fragment);
+
+ // If the byte is a Voice Message status byte return the contained
+ // channel number, otherwise zero.
+ static uint8_t channel(uint8_t p_status_byte);
+
+ // If the byte is a status byte for a message with a fixed number of
+ // additional data bytes, return the number expected, otherwise zero.
+ static size_t expected_data(uint8_t p_status_byte);
+ static size_t expected_data(MIDIMessage p_msg_type);
+
+ // If the fragment is a status byte return the message type
+ // represented, otherwise MIDIMessage::NONE.
+ static MIDIMessage status_to_msg_enum(uint8_t p_status_byte);
+
+ private:
+ int device_index = 0;
+
+ static constexpr size_t DATA_BUFFER_SIZE = 2;
+
+ uint8_t status_byte = 0;
+ uint8_t data_buffer[DATA_BUFFER_SIZE] = { 0 };
+ size_t received_data_len = 0;
+ bool skipping_sys_ex = false;
+ };
+
+ PackedStringArray connected_input_names;
+
public:
static MIDIDriver *get_singleton();
- void set_singleton();
+
+ MIDIDriver();
+ virtual ~MIDIDriver() = default;
virtual Error open() = 0;
virtual void close() = 0;
- virtual PackedStringArray get_connected_inputs();
-
- static void receive_input_packet(int device_index, uint64_t timestamp, uint8_t *data, uint32_t length);
-
- MIDIDriver();
- virtual ~MIDIDriver() {}
+ PackedStringArray get_connected_inputs() const;
};
#endif // MIDI_DRIVER_H
diff --git a/core/os/os.cpp b/core/os/os.cpp
index 40d8601af3..642de11a9f 100644
--- a/core/os/os.cpp
+++ b/core/os/os.cpp
@@ -247,7 +247,10 @@ String OS::get_safe_dir_name(const String &p_dir_name, bool p_allow_paths) const
for (int i = 0; i < invalid_chars.size(); i++) {
safe_dir_name = safe_dir_name.replace(invalid_chars[i], "-");
}
- return safe_dir_name;
+
+ // Trim trailing periods from the returned value as it's not valid for folder names on Windows.
+ // This check is still applied on non-Windows platforms so the returned value is consistent across platforms.
+ return safe_dir_name.rstrip(".");
}
// Path to data, config, cache, etc. OS-specific folders
diff --git a/core/variant/typed_array.h b/core/variant/typed_array.h
index e00947ed1e..07bf8afa7b 100644
--- a/core/variant/typed_array.h
+++ b/core/variant/typed_array.h
@@ -46,10 +46,15 @@ public:
_ref(p_array);
}
_FORCE_INLINE_ TypedArray(const Variant &p_variant) :
- Array(Array(p_variant), Variant::OBJECT, T::get_class_static(), Variant()) {
+ TypedArray(Array(p_variant)) {
}
- _FORCE_INLINE_ TypedArray(const Array &p_array) :
- Array(p_array, Variant::OBJECT, T::get_class_static(), Variant()) {
+ _FORCE_INLINE_ TypedArray(const Array &p_array) {
+ set_typed(Variant::OBJECT, T::get_class_static(), Variant());
+ if (is_same_typed(p_array)) {
+ _ref(p_array);
+ } else {
+ assign(p_array);
+ }
}
_FORCE_INLINE_ TypedArray() {
set_typed(Variant::OBJECT, T::get_class_static(), Variant());
@@ -78,10 +83,15 @@ struct VariantInternalAccessor<const TypedArray<T> &> {
_ref(p_array); \
} \
_FORCE_INLINE_ TypedArray(const Variant &p_variant) : \
- Array(Array(p_variant), m_variant_type, StringName(), Variant()) { \
+ TypedArray(Array(p_variant)) { \
} \
- _FORCE_INLINE_ TypedArray(const Array &p_array) : \
- Array(p_array, m_variant_type, StringName(), Variant()) { \
+ _FORCE_INLINE_ TypedArray(const Array &p_array) { \
+ set_typed(m_variant_type, StringName(), Variant()); \
+ if (is_same_typed(p_array)) { \
+ _ref(p_array); \
+ } else { \
+ assign(p_array); \
+ } \
} \
_FORCE_INLINE_ TypedArray() { \
set_typed(m_variant_type, StringName(), Variant()); \
diff --git a/doc/classes/EditorUndoRedoManager.xml b/doc/classes/EditorUndoRedoManager.xml
index 26580bbf06..5ac0d790a2 100644
--- a/doc/classes/EditorUndoRedoManager.xml
+++ b/doc/classes/EditorUndoRedoManager.xml
@@ -88,6 +88,13 @@
The way undo operation are ordered in actions is dictated by [param backward_undo_ops]. When [param backward_undo_ops] is [code]false[/code] undo option are ordered in the same order they were added. Which means the first operation to be added will be the first to be undone.
</description>
</method>
+ <method name="force_fixed_history">
+ <return type="void" />
+ <description>
+ Forces the next operation (e.g. [method add_do_method]) to use the action's history rather than guessing it from the object. This is sometimes needed when a history can't be correctly determined, like for a nested resource that doesn't have a path yet.
+ This method should only be used when absolutely necessary, otherwise it might cause invalid history state. For most of complex cases, the [code]custom_context[/code] parameter of [method create_action] is sufficient.
+ </description>
+ </method>
<method name="get_history_undo_redo" qualifiers="const">
<return type="UndoRedo" />
<param index="0" name="id" type="int" />
diff --git a/doc/classes/HashingContext.xml b/doc/classes/HashingContext.xml
index f2681ae7b3..b42acb2b99 100644
--- a/doc/classes/HashingContext.xml
+++ b/doc/classes/HashingContext.xml
@@ -20,8 +20,9 @@
# Open the file to hash.
var file = FileAccess.open(path, FileAccess.READ)
# Update the context after reading each chunk.
- while not file.eof_reached():
- ctx.update(file.get_buffer(CHUNK_SIZE))
+ while file.get_position() &lt; file.get_length():
+ var remaining = file.get_length() - file.get_position()
+ ctx.update(file.get_buffer(min(remaining, CHUNK_SIZE)))
# Get the computed hash.
var res = ctx.finish()
# Print the result as hex string and array.
@@ -43,9 +44,10 @@
// Open the file to hash.
using var file = FileAccess.Open(path, FileAccess.ModeFlags.Read);
// Update the context after reading each chunk.
- while (!file.EofReached())
+ while (file.GetPosition() &lt; file.GetLength())
{
- ctx.Update(file.GetBuffer(ChunkSize));
+ int remaining = (int)(file.GetLength() - file.GetPosition());
+ ctx.Update(file.GetBuffer(Mathf.Min(remaining, ChunkSize)));
}
// Get the computed hash.
byte[] res = ctx.Finish();
diff --git a/doc/classes/PhysicalBone3D.xml b/doc/classes/PhysicalBone3D.xml
index bce1a80526..ca1948e8e1 100644
--- a/doc/classes/PhysicalBone3D.xml
+++ b/doc/classes/PhysicalBone3D.xml
@@ -5,6 +5,7 @@
</brief_description>
<description>
The [PhysicalBone3D] node is a physics body that can be used to make bones in a [Skeleton3D] react to physics.
+ [b]Note:[/b] In order to detect physical bones with raycasts, the [member SkeletonModifier3D.active] property of the parent [PhysicalBoneSimulator3D] must be [code]true[/code] and the [Skeleton3D]'s bone must be assigned to [PhysicalBone3D] correctly; it means that [method get_bone_id] should return a valid id ([code]&gt;= 0[/code]).
</description>
<tutorials>
</tutorials>
diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml
index 405e8340f3..8085b20730 100644
--- a/doc/classes/ProjectSettings.xml
+++ b/doc/classes/ProjectSettings.xml
@@ -271,6 +271,7 @@
<member name="application/config/custom_user_dir_name" type="String" setter="" getter="" default="&quot;&quot;">
This user directory is used for storing persistent data ([code]user://[/code] filesystem). If a custom directory name is defined, this name will be appended to the system-specific user data directory (same parent folder as the Godot configuration folder documented in [method OS.get_user_data_dir]).
The [member application/config/use_custom_user_dir] setting must be enabled for this to take effect.
+ [b]Note:[/b] If [member application/config/custom_user_dir_name] contains trailing periods, they will be stripped as folder names ending with a period are not allowed on Windows.
</member>
<member name="application/config/description" type="String" setter="" getter="" default="&quot;&quot;">
The project's description, displayed as a tooltip in the Project Manager when hovering the project.
diff --git a/doc/classes/Skeleton3D.xml b/doc/classes/Skeleton3D.xml
index 1167b70c8d..5829a787a1 100644
--- a/doc/classes/Skeleton3D.xml
+++ b/doc/classes/Skeleton3D.xml
@@ -243,6 +243,8 @@
<return type="void" />
<param index="0" name="enabled" type="bool" />
<description>
+ This method exists for compatibility with old structures in which the [Skeleton3D] does not have a [PhysicalBoneSimulator3D] as a child, but directly has [PhysicalBone3D]s as children.
+ In case you need to raycast to it without running [method physical_bones_start_simulation], call this method with [code]enabled == true[/code].
</description>
</method>
<method name="set_bone_enabled">
diff --git a/drivers/alsamidi/midi_driver_alsamidi.cpp b/drivers/alsamidi/midi_driver_alsamidi.cpp
index b87be69cc5..445fc4a993 100644
--- a/drivers/alsamidi/midi_driver_alsamidi.cpp
+++ b/drivers/alsamidi/midi_driver_alsamidi.cpp
@@ -37,137 +37,36 @@
#include <errno.h>
-MIDIDriverALSAMidi::MessageCategory MIDIDriverALSAMidi::msg_category(uint8_t msg_part) {
- if (msg_part >= 0xf8) {
- return MessageCategory::RealTime;
- } else if (msg_part >= 0xf0) {
- // System Exclusive begin/end are specified as System Common Category messages,
- // but we separate them here and give them their own categories as their
- // behavior is significantly different.
- if (msg_part == 0xf0) {
- return MessageCategory::SysExBegin;
- } else if (msg_part == 0xf7) {
- return MessageCategory::SysExEnd;
- }
- return MessageCategory::SystemCommon;
- } else if (msg_part >= 0x80) {
- return MessageCategory::Voice;
- }
- return MessageCategory::Data;
-}
-
-size_t MIDIDriverALSAMidi::msg_expected_data(uint8_t status_byte) {
- if (msg_category(status_byte) == MessageCategory::Voice) {
- // Voice messages have a channel number in the status byte, mask it out.
- status_byte &= 0xf0;
- }
-
- switch (status_byte) {
- case 0x80: // Note Off
- case 0x90: // Note On
- case 0xA0: // Polyphonic Key Pressure (Aftertouch)
- case 0xB0: // Control Change (CC)
- case 0xE0: // Pitch Bend Change
- case 0xF2: // Song Position Pointer
- return 2;
-
- case 0xC0: // Program Change
- case 0xD0: // Channel Pressure (Aftertouch)
- case 0xF1: // MIDI Time Code Quarter Frame
- case 0xF3: // Song Select
- return 1;
- }
+MIDIDriverALSAMidi::InputConnection::InputConnection(int p_device_index,
+ snd_rawmidi_t *p_rawmidi) :
+ parser(p_device_index), rawmidi_ptr(p_rawmidi) {}
- return 0;
-}
-
-void MIDIDriverALSAMidi::InputConnection::parse_byte(uint8_t byte, MIDIDriverALSAMidi &driver,
- uint64_t timestamp, int device_index) {
- switch (msg_category(byte)) {
- case MessageCategory::RealTime:
- // Real-Time messages are single byte messages that can
- // occur at any point.
- // We pass them straight through.
- driver.receive_input_packet(device_index, timestamp, &byte, 1);
- break;
-
- case MessageCategory::Data:
- // We don't currently forward System Exclusive messages so skip their data.
- // Collect any expected data for other message types.
- if (!skipping_sys_ex && expected_data > received_data) {
- buffer[received_data + 1] = byte;
- received_data++;
-
- // Forward a complete message and reset relevant state.
- if (received_data == expected_data) {
- driver.receive_input_packet(device_index, timestamp, buffer, received_data + 1);
- received_data = 0;
-
- if (msg_category(buffer[0]) != MessageCategory::Voice) {
- // Voice Category messages can be sent with "running status".
- // This means they don't resend the status byte until it changes.
- // For other categories, we reset expected data, to require a new status byte.
- expected_data = 0;
- }
- }
- }
- break;
-
- case MessageCategory::SysExBegin:
- buffer[0] = byte;
- skipping_sys_ex = true;
- break;
-
- case MessageCategory::SysExEnd:
- expected_data = 0;
- skipping_sys_ex = false;
- break;
-
- case MessageCategory::Voice:
- case MessageCategory::SystemCommon:
- buffer[0] = byte;
- received_data = 0;
- expected_data = msg_expected_data(byte);
- skipping_sys_ex = false;
- if (expected_data == 0) {
- driver.receive_input_packet(device_index, timestamp, &byte, 1);
- }
- break;
- }
-}
-
-int MIDIDriverALSAMidi::InputConnection::read_in(MIDIDriverALSAMidi &driver, uint64_t timestamp, int device_index) {
- int ret;
+void MIDIDriverALSAMidi::InputConnection::read() {
+ int read_count;
do {
- uint8_t byte = 0;
- ret = snd_rawmidi_read(rawmidi_ptr, &byte, 1);
+ uint8_t buffer[32];
+ read_count = snd_rawmidi_read(rawmidi_ptr, buffer, sizeof(buffer));
- if (ret < 0) {
- if (ret != -EAGAIN) {
- ERR_PRINT("snd_rawmidi_read error: " + String(snd_strerror(ret)));
+ if (read_count < 0) {
+ if (read_count != -EAGAIN) {
+ ERR_PRINT("snd_rawmidi_read error: " + String(snd_strerror(read_count)));
}
} else {
- parse_byte(byte, driver, timestamp, device_index);
+ for (int i = 0; i < read_count; i++) {
+ parser.parse_fragment(buffer[i]);
+ }
}
- } while (ret > 0);
-
- return ret;
+ } while (read_count > 0);
}
void MIDIDriverALSAMidi::thread_func(void *p_udata) {
MIDIDriverALSAMidi *md = static_cast<MIDIDriverALSAMidi *>(p_udata);
- uint64_t timestamp = 0;
while (!md->exit_thread.is_set()) {
md->lock();
-
- InputConnection *connections = md->connected_inputs.ptrw();
- size_t connection_count = md->connected_inputs.size();
-
- for (size_t i = 0; i < connection_count; i++) {
- connections[i].read_in(*md, timestamp, (int)i);
+ for (InputConnection &conn : md->connected_inputs) {
+ conn.read();
}
-
md->unlock();
OS::get_singleton()->delay_usec(1000);
@@ -181,15 +80,25 @@ Error MIDIDriverALSAMidi::open() {
return ERR_CANT_OPEN;
}
- int i = 0;
- for (void **n = hints; *n != nullptr; n++) {
- char *name = snd_device_name_get_hint(*n, "NAME");
+ lock();
+ int device_index = 0;
+ for (void **h = hints; *h != nullptr; h++) {
+ char *name = snd_device_name_get_hint(*h, "NAME");
if (name != nullptr) {
snd_rawmidi_t *midi_in;
int ret = snd_rawmidi_open(&midi_in, nullptr, name, SND_RAWMIDI_NONBLOCK);
if (ret >= 0) {
- connected_inputs.insert(i++, InputConnection(midi_in));
+ // Get display name.
+ snd_rawmidi_info_t *info;
+ snd_rawmidi_info_malloc(&info);
+ snd_rawmidi_info(midi_in, info);
+ connected_input_names.push_back(snd_rawmidi_info_get_name(info));
+ snd_rawmidi_info_free(info);
+
+ connected_inputs.push_back(InputConnection(device_index, midi_in));
+ // Only increment device_index for successfully connected devices.
+ device_index++;
}
}
@@ -198,6 +107,7 @@ Error MIDIDriverALSAMidi::open() {
}
}
snd_device_name_free_hint(hints);
+ unlock();
exit_thread.clear();
thread.start(MIDIDriverALSAMidi::thread_func, this);
@@ -211,11 +121,12 @@ void MIDIDriverALSAMidi::close() {
thread.wait_to_finish();
}
- for (int i = 0; i < connected_inputs.size(); i++) {
- snd_rawmidi_t *midi_in = connected_inputs[i].rawmidi_ptr;
- snd_rawmidi_close(midi_in);
+ for (const InputConnection &conn : connected_inputs) {
+ snd_rawmidi_close(conn.rawmidi_ptr);
}
+
connected_inputs.clear();
+ connected_input_names.clear();
}
void MIDIDriverALSAMidi::lock() const {
@@ -226,24 +137,6 @@ void MIDIDriverALSAMidi::unlock() const {
mutex.unlock();
}
-PackedStringArray MIDIDriverALSAMidi::get_connected_inputs() {
- PackedStringArray list;
-
- lock();
- for (int i = 0; i < connected_inputs.size(); i++) {
- snd_rawmidi_t *midi_in = connected_inputs[i].rawmidi_ptr;
- snd_rawmidi_info_t *info;
-
- snd_rawmidi_info_malloc(&info);
- snd_rawmidi_info(midi_in, info);
- list.push_back(snd_rawmidi_info_get_name(info));
- snd_rawmidi_info_free(info);
- }
- unlock();
-
- return list;
-}
-
MIDIDriverALSAMidi::MIDIDriverALSAMidi() {
exit_thread.clear();
}
diff --git a/drivers/alsamidi/midi_driver_alsamidi.h b/drivers/alsamidi/midi_driver_alsamidi.h
index 95ded3b1c9..45811bec47 100644
--- a/drivers/alsamidi/midi_driver_alsamidi.h
+++ b/drivers/alsamidi/midi_driver_alsamidi.h
@@ -51,24 +51,15 @@ class MIDIDriverALSAMidi : public MIDIDriver {
Thread thread;
Mutex mutex;
- class InputConnection {
- public:
+ struct InputConnection {
InputConnection() = default;
- InputConnection(snd_rawmidi_t *midi_in) :
- rawmidi_ptr{ midi_in } {}
-
- // Read in and parse available data, forwarding any complete messages through the driver.
- int read_in(MIDIDriverALSAMidi &driver, uint64_t timestamp, int device_index);
+ InputConnection(int p_device_index, snd_rawmidi_t *p_rawmidi);
+ Parser parser;
snd_rawmidi_t *rawmidi_ptr = nullptr;
- private:
- static const size_t MSG_BUFFER_SIZE = 3;
- uint8_t buffer[MSG_BUFFER_SIZE] = { 0 };
- size_t expected_data = 0;
- size_t received_data = 0;
- bool skipping_sys_ex = false;
- void parse_byte(uint8_t byte, MIDIDriverALSAMidi &driver, uint64_t timestamp, int device_index);
+ // Read in and parse available data, forwarding complete events to Input.
+ void read();
};
Vector<InputConnection> connected_inputs;
@@ -77,30 +68,12 @@ class MIDIDriverALSAMidi : public MIDIDriver {
static void thread_func(void *p_udata);
- enum class MessageCategory {
- Data,
- Voice,
- SysExBegin,
- SystemCommon, // excluding System Exclusive Begin/End
- SysExEnd,
- RealTime,
- };
-
- // If the passed byte is a status byte, return the associated message category,
- // else return MessageCategory::Data.
- static MessageCategory msg_category(uint8_t msg_part);
-
- // Return the number of data bytes expected for the provided status byte.
- static size_t msg_expected_data(uint8_t status_byte);
-
void lock() const;
void unlock() const;
public:
- virtual Error open();
- virtual void close();
-
- virtual PackedStringArray get_connected_inputs();
+ virtual Error open() override;
+ virtual void close() override;
MIDIDriverALSAMidi();
virtual ~MIDIDriverALSAMidi();
diff --git a/drivers/coremidi/midi_driver_coremidi.cpp b/drivers/coremidi/midi_driver_coremidi.cpp
index 87fc7612f7..f6cc59471e 100644
--- a/drivers/coremidi/midi_driver_coremidi.cpp
+++ b/drivers/coremidi/midi_driver_coremidi.cpp
@@ -37,16 +37,30 @@
#import <CoreAudio/HostTime.h>
#import <CoreServices/CoreServices.h>
+Mutex MIDIDriverCoreMidi::mutex;
+bool MIDIDriverCoreMidi::core_midi_closed = false;
+
+MIDIDriverCoreMidi::InputConnection::InputConnection(int p_device_index, MIDIEndpointRef p_source) :
+ parser(p_device_index), source(p_source) {}
+
void MIDIDriverCoreMidi::read(const MIDIPacketList *packet_list, void *read_proc_ref_con, void *src_conn_ref_con) {
- MIDIPacket *packet = const_cast<MIDIPacket *>(packet_list->packet);
- int *device_index = static_cast<int *>(src_conn_ref_con);
- for (UInt32 i = 0; i < packet_list->numPackets; i++) {
- receive_input_packet(*device_index, packet->timeStamp, packet->data, packet->length);
- packet = MIDIPacketNext(packet);
+ MutexLock lock(mutex);
+ if (!core_midi_closed) {
+ InputConnection *source = static_cast<InputConnection *>(src_conn_ref_con);
+ const MIDIPacket *packet = packet_list->packet;
+ for (UInt32 packet_index = 0; packet_index < packet_list->numPackets; packet_index++) {
+ for (UInt16 data_index = 0; data_index < packet->length; data_index++) {
+ source->parser.parse_fragment(packet->data[data_index]);
+ }
+ packet = MIDIPacketNext(packet);
+ }
}
}
Error MIDIDriverCoreMidi::open() {
+ ERR_FAIL_COND_V_MSG(client || core_midi_closed, FAILED,
+ "MIDIDriverCoreMidi cannot be reopened.");
+
CFStringRef name = CFStringCreateWithCString(nullptr, "Godot", kCFStringEncodingASCII);
OSStatus result = MIDIClientCreate(name, nullptr, nullptr, &client);
CFRelease(name);
@@ -61,12 +75,27 @@ Error MIDIDriverCoreMidi::open() {
return ERR_CANT_OPEN;
}
- int sources = MIDIGetNumberOfSources();
- for (int i = 0; i < sources; i++) {
+ int source_count = MIDIGetNumberOfSources();
+ int connection_index = 0;
+ for (int i = 0; i < source_count; i++) {
MIDIEndpointRef source = MIDIGetSource(i);
if (source) {
- MIDIPortConnectSource(port_in, source, static_cast<void *>(&i));
- connected_sources.insert(i, source);
+ InputConnection *conn = memnew(InputConnection(connection_index, source));
+ const OSStatus res = MIDIPortConnectSource(port_in, source, static_cast<void *>(conn));
+ if (res != noErr) {
+ memdelete(conn);
+ } else {
+ connected_sources.push_back(conn);
+
+ CFStringRef nameRef = nullptr;
+ char name[256];
+ MIDIObjectGetStringProperty(source, kMIDIPropertyDisplayName, &nameRef);
+ CFStringGetCString(nameRef, name, sizeof(name), kCFStringEncodingUTF8);
+ CFRelease(nameRef);
+ connected_input_names.push_back(name);
+
+ connection_index++; // Contiguous index for successfully connected inputs.
+ }
}
}
@@ -74,11 +103,17 @@ Error MIDIDriverCoreMidi::open() {
}
void MIDIDriverCoreMidi::close() {
- for (int i = 0; i < connected_sources.size(); i++) {
- MIDIEndpointRef source = connected_sources[i];
- MIDIPortDisconnectSource(port_in, source);
+ mutex.lock();
+ core_midi_closed = true;
+ mutex.unlock();
+
+ for (InputConnection *conn : connected_sources) {
+ MIDIPortDisconnectSource(port_in, conn->source);
+ memdelete(conn);
}
+
connected_sources.clear();
+ connected_input_names.clear();
if (port_in != 0) {
MIDIPortDispose(port_in);
@@ -91,26 +126,6 @@ void MIDIDriverCoreMidi::close() {
}
}
-PackedStringArray MIDIDriverCoreMidi::get_connected_inputs() {
- PackedStringArray list;
-
- for (int i = 0; i < connected_sources.size(); i++) {
- MIDIEndpointRef source = connected_sources[i];
- CFStringRef ref = nullptr;
- char name[256];
-
- MIDIObjectGetStringProperty(source, kMIDIPropertyDisplayName, &ref);
- CFStringGetCString(ref, name, sizeof(name), kCFStringEncodingUTF8);
- CFRelease(ref);
-
- list.push_back(name);
- }
-
- return list;
-}
-
-MIDIDriverCoreMidi::MIDIDriverCoreMidi() {}
-
MIDIDriverCoreMidi::~MIDIDriverCoreMidi() {
close();
}
diff --git a/drivers/coremidi/midi_driver_coremidi.h b/drivers/coremidi/midi_driver_coremidi.h
index 38fb515664..02cbc6234c 100644
--- a/drivers/coremidi/midi_driver_coremidi.h
+++ b/drivers/coremidi/midi_driver_coremidi.h
@@ -34,6 +34,7 @@
#ifdef COREMIDI_ENABLED
#include "core/os/midi_driver.h"
+#include "core/os/mutex.h"
#include "core/templates/vector.h"
#import <CoreMIDI/CoreMIDI.h>
@@ -43,17 +44,25 @@ class MIDIDriverCoreMidi : public MIDIDriver {
MIDIClientRef client = 0;
MIDIPortRef port_in;
- Vector<MIDIEndpointRef> connected_sources;
+ struct InputConnection {
+ InputConnection() = default;
+ InputConnection(int p_device_index, MIDIEndpointRef p_source);
+ Parser parser;
+ MIDIEndpointRef source;
+ };
+
+ Vector<InputConnection *> connected_sources;
+
+ static Mutex mutex;
+ static bool core_midi_closed;
static void read(const MIDIPacketList *packet_list, void *read_proc_ref_con, void *src_conn_ref_con);
public:
- virtual Error open();
- virtual void close();
-
- PackedStringArray get_connected_inputs();
+ virtual Error open() override;
+ virtual void close() override;
- MIDIDriverCoreMidi();
+ MIDIDriverCoreMidi() = default;
virtual ~MIDIDriverCoreMidi();
};
diff --git a/drivers/winmidi/midi_driver_winmidi.cpp b/drivers/winmidi/midi_driver_winmidi.cpp
index 07f0226c5d..0f37f63ccd 100644
--- a/drivers/winmidi/midi_driver_winmidi.cpp
+++ b/drivers/winmidi/midi_driver_winmidi.cpp
@@ -36,26 +36,42 @@
void MIDIDriverWinMidi::read(HMIDIIN hMidiIn, UINT wMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2) {
if (wMsg == MIM_DATA) {
- receive_input_packet((int)dwInstance, (uint64_t)dwParam2, (uint8_t *)&dwParam1, 3);
+ // For MIM_DATA: dwParam1 = wMidiMessage, dwParam2 = dwTimestamp.
+ // Windows implementation has already unpacked running status and dropped any SysEx,
+ // so we can just forward straight to the event.
+ const uint8_t *midi_msg = (uint8_t *)&dwParam1;
+ send_event((int)dwInstance, midi_msg[0], &midi_msg[1], 2);
}
}
Error MIDIDriverWinMidi::open() {
+ int device_index = 0;
for (UINT i = 0; i < midiInGetNumDevs(); i++) {
HMIDIIN midi_in;
+ MIDIINCAPS caps;
- MMRESULT res = midiInOpen(&midi_in, i, (DWORD_PTR)read, (DWORD_PTR)i, CALLBACK_FUNCTION);
- if (res == MMSYSERR_NOERROR) {
+ MMRESULT open_res = midiInOpen(&midi_in, i, (DWORD_PTR)read,
+ (DWORD_PTR)device_index, CALLBACK_FUNCTION);
+ MMRESULT caps_res = midiInGetDevCaps(i, &caps, sizeof(MIDIINCAPS));
+
+ if (open_res == MMSYSERR_NOERROR) {
midiInStart(midi_in);
- connected_sources.insert(i, midi_in);
+ connected_sources.push_back(midi_in);
+ if (caps_res == MMSYSERR_NOERROR) {
+ connected_input_names.push_back(caps.szPname);
+ } else {
+ // Should push something even if we don't get a name,
+ // so that the IDs line up correctly on the script side.
+ connected_input_names.push_back("ERROR");
+ }
+ // Only increment device index for successfully connected devices.
+ device_index++;
} else {
char err[256];
- midiInGetErrorText(res, err, 256);
+ midiInGetErrorText(open_res, err, 256);
ERR_PRINT("midiInOpen error: " + String(err));
- MIDIINCAPS caps;
- res = midiInGetDevCaps(i, &caps, sizeof(MIDIINCAPS));
- if (res == MMSYSERR_NOERROR) {
+ if (caps_res == MMSYSERR_NOERROR) {
ERR_PRINT("Can't open MIDI device \"" + String(caps.szPname) + "\", is it being used by another application?");
}
}
@@ -64,25 +80,6 @@ Error MIDIDriverWinMidi::open() {
return OK;
}
-PackedStringArray MIDIDriverWinMidi::get_connected_inputs() {
- PackedStringArray list;
-
- for (int i = 0; i < connected_sources.size(); i++) {
- HMIDIIN midi_in = connected_sources[i];
- UINT id = 0;
- MMRESULT res = midiInGetID(midi_in, &id);
- if (res == MMSYSERR_NOERROR) {
- MIDIINCAPS caps;
- res = midiInGetDevCaps(i, &caps, sizeof(MIDIINCAPS));
- if (res == MMSYSERR_NOERROR) {
- list.push_back(caps.szPname);
- }
- }
- }
-
- return list;
-}
-
void MIDIDriverWinMidi::close() {
for (int i = 0; i < connected_sources.size(); i++) {
HMIDIIN midi_in = connected_sources[i];
@@ -90,9 +87,7 @@ void MIDIDriverWinMidi::close() {
midiInClose(midi_in);
}
connected_sources.clear();
-}
-
-MIDIDriverWinMidi::MIDIDriverWinMidi() {
+ connected_input_names.clear();
}
MIDIDriverWinMidi::~MIDIDriverWinMidi() {
diff --git a/drivers/winmidi/midi_driver_winmidi.h b/drivers/winmidi/midi_driver_winmidi.h
index f3e016f378..7a75252233 100644
--- a/drivers/winmidi/midi_driver_winmidi.h
+++ b/drivers/winmidi/midi_driver_winmidi.h
@@ -48,12 +48,10 @@ class MIDIDriverWinMidi : public MIDIDriver {
static void CALLBACK read(HMIDIIN hMidiIn, UINT wMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2);
public:
- virtual Error open();
- virtual void close();
+ virtual Error open() override;
+ virtual void close() override;
- virtual PackedStringArray get_connected_inputs();
-
- MIDIDriverWinMidi();
+ MIDIDriverWinMidi() = default;
virtual ~MIDIDriverWinMidi();
};
diff --git a/editor/create_dialog.cpp b/editor/create_dialog.cpp
index 56500c71e2..204636e128 100644
--- a/editor/create_dialog.cpp
+++ b/editor/create_dialog.cpp
@@ -160,8 +160,13 @@ bool CreateDialog::_should_hide_type(const StringName &p_type) const {
String script_path = ScriptServer::get_global_class_path(p_type);
if (script_path.begins_with("res://addons/")) {
- if (!EditorNode::get_singleton()->is_addon_plugin_enabled(script_path.get_slicec('/', 3))) {
- return true; // Plugin is not enabled.
+ int i = script_path.find("/", 13); // 13 is length of "res://addons/".
+ while (i > -1) {
+ const String plugin_path = script_path.substr(0, i).path_join("plugin.cfg");
+ if (FileAccess::exists(plugin_path)) {
+ return !EditorNode::get_singleton()->is_addon_plugin_enabled(plugin_path);
+ }
+ i = script_path.find("/", i + 1);
}
}
}
diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp
index 6c26231a4b..467a19ebb1 100644
--- a/editor/editor_inspector.cpp
+++ b/editor/editor_inspector.cpp
@@ -2598,6 +2598,10 @@ int EditorInspector::inspector_plugin_count = 0;
EditorProperty *EditorInspector::instantiate_property_editor(Object *p_object, const Variant::Type p_type, const String &p_path, PropertyHint p_hint, const String &p_hint_text, const uint32_t p_usage, const bool p_wide) {
for (int i = inspector_plugin_count - 1; i >= 0; i--) {
+ if (!inspector_plugins[i]->can_handle(p_object)) {
+ continue;
+ }
+
inspector_plugins[i]->parse_property(p_object, p_type, p_path, p_hint, p_hint_text, p_usage, p_wide);
if (inspector_plugins[i]->added_editors.size()) {
for (List<EditorInspectorPlugin::AddedEditor>::Element *E = inspector_plugins[i]->added_editors.front()->next(); E; E = E->next()) { //only keep first one
@@ -3133,6 +3137,7 @@ void EditorInspector::update_tree() {
// Recreate the category vbox if it was reset.
if (category_vbox == nullptr) {
category_vbox = memnew(VBoxContainer);
+ category_vbox->hide();
main_vbox->add_child(category_vbox);
}
@@ -3205,6 +3210,7 @@ void EditorInspector::update_tree() {
// If we did not find a section to add the property to, add it to the category vbox instead (the category vbox handles margins correctly).
if (current_vbox == main_vbox) {
+ category_vbox->show();
current_vbox = category_vbox;
}
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index 55487f7896..ae7066fea9 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -1737,7 +1737,7 @@ bool EditorNode::_validate_scene_recursive(const String &p_filename, Node *p_nod
return false;
}
-int EditorNode::_save_external_resources() {
+int EditorNode::_save_external_resources(bool p_also_save_external_data) {
// Save external resources and its subresources if any was modified.
int flg = 0;
@@ -1783,6 +1783,16 @@ int EditorNode::_save_external_resources() {
saved++;
}
+ if (p_also_save_external_data) {
+ for (int i = 0; i < editor_data.get_editor_plugin_count(); i++) {
+ EditorPlugin *plugin = editor_data.get_editor_plugin(i);
+ if (!plugin->get_unsaved_status().is_empty()) {
+ plugin->save_external_data();
+ saved++;
+ }
+ }
+ }
+
EditorUndoRedoManager::get_singleton()->set_history_as_saved(EditorUndoRedoManager::GLOBAL_HISTORY);
return saved;
@@ -1812,9 +1822,7 @@ static void _reset_animation_mixers(Node *p_node, List<Pair<AnimationMixer *, Re
}
void EditorNode::_save_scene(String p_file, int idx) {
- if (!saving_scene.is_empty() && saving_scene == p_file) {
- return;
- }
+ ERR_FAIL_COND_MSG(!saving_scene.is_empty() && saving_scene == p_file, "Scene saved while already being saved!");
Node *scene = editor_data.get_edited_scene_root(idx);
@@ -2728,10 +2736,10 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) {
ScriptEditor::get_singleton()->save_current_script();
}
- const int saved = _save_external_resources();
+ const int saved = _save_external_resources(true);
if (saved > 0) {
show_accept(
- vformat(TTR("The current scene has no root node, but %d modified external resource(s) were saved anyway."), saved),
+ vformat(TTR("The current scene has no root node, but %d modified external resource(s) and/or plugin data were saved anyway."), saved),
TTR("OK"));
} else if (p_option == FILE_SAVE_AS_SCENE) {
// Don't show this dialog when pressing Ctrl + S to avoid interfering with script saving.
diff --git a/editor/editor_node.h b/editor/editor_node.h
index b7b4dff74e..49674dd1c1 100644
--- a/editor/editor_node.h
+++ b/editor/editor_node.h
@@ -573,7 +573,7 @@ private:
void _update_undo_redo_allowed();
- int _save_external_resources();
+ int _save_external_resources(bool p_also_save_external_data = false);
void _set_current_scene(int p_idx);
void _set_current_scene_nocheck(int p_idx);
diff --git a/editor/editor_properties_array_dict.cpp b/editor/editor_properties_array_dict.cpp
index 127bca9bbf..633f6abad9 100644
--- a/editor/editor_properties_array_dict.cpp
+++ b/editor/editor_properties_array_dict.cpp
@@ -435,7 +435,7 @@ void EditorPropertyArray::update_property() {
editor->setup("Object");
new_prop = editor;
} else {
- new_prop = EditorInspector::instantiate_property_editor(nullptr, value_type, "", subtype_hint, subtype_hint_string, PROPERTY_USAGE_NONE);
+ new_prop = EditorInspector::instantiate_property_editor(this, value_type, "", subtype_hint, subtype_hint_string, PROPERTY_USAGE_NONE);
}
new_prop->set_selectable(false);
new_prop->set_use_folding(is_using_folding());
@@ -1064,7 +1064,7 @@ void EditorPropertyDictionary::update_property() {
editor->setup("Object");
new_prop = editor;
} else {
- new_prop = EditorInspector::instantiate_property_editor(nullptr, value_type, "", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE);
+ new_prop = EditorInspector::instantiate_property_editor(this, value_type, "", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE);
}
new_prop->set_selectable(false);
new_prop->set_use_folding(is_using_folding());
diff --git a/editor/editor_resource_picker.cpp b/editor/editor_resource_picker.cpp
index a895219e6f..4cd44e3020 100644
--- a/editor/editor_resource_picker.cpp
+++ b/editor/editor_resource_picker.cpp
@@ -1065,7 +1065,7 @@ EditorResourcePicker::EditorResourcePicker(bool p_hide_assign_button_controls) {
}
edit_button = memnew(Button);
- edit_button->set_flat(true);
+ edit_button->set_flat(false);
edit_button->set_toggle_mode(true);
edit_button->connect(SceneStringName(pressed), callable_mp(this, &EditorResourcePicker::_update_menu));
add_child(edit_button);
@@ -1268,8 +1268,6 @@ void EditorAudioStreamPicker::_preview_draw() {
if (audio_stream->get_length() > 0 && size.width > 0) {
rect.size.height *= 0.5;
- stream_preview_rect->draw_rect(rect, Color(0, 0, 0, 1));
-
Ref<AudioStreamPreview> preview = AudioStreamPreviewGenerator::get_singleton()->generate_preview(audio_stream);
float preview_len = preview->get_length();
@@ -1325,8 +1323,8 @@ void EditorAudioStreamPicker::_preview_draw() {
text = audio_stream->get_class().replace_first("AudioStream", "");
}
- stream_preview_rect->draw_texture(icon, Point2i(EDSCALE * 2, rect.position.y + (rect.size.height - icon->get_height()) / 2), icon_modulate);
- stream_preview_rect->draw_string(font, Point2i(EDSCALE * 2 + icon->get_width(), rect.position.y + font->get_ascent(font_size) + (rect.size.height - font->get_height(font_size)) / 2), text, HORIZONTAL_ALIGNMENT_CENTER, size.width - 4 * EDSCALE - icon->get_width());
+ stream_preview_rect->draw_texture(icon, Point2i(EDSCALE * 4, rect.position.y + (rect.size.height - icon->get_height()) / 2), icon_modulate);
+ stream_preview_rect->draw_string(font, Point2i(EDSCALE * 4 + icon->get_width(), rect.position.y + font->get_ascent(font_size) + (rect.size.height - font->get_height(font_size)) / 2), text, HORIZONTAL_ALIGNMENT_CENTER, size.width - 4 * EDSCALE - icon->get_width(), font_size, get_theme_color(SNAME("font_color"), EditorStringName(Editor)));
}
EditorAudioStreamPicker::EditorAudioStreamPicker() :
diff --git a/editor/editor_undo_redo_manager.cpp b/editor/editor_undo_redo_manager.cpp
index 94f76dbc41..55bc198dfb 100644
--- a/editor/editor_undo_redo_manager.cpp
+++ b/editor/editor_undo_redo_manager.cpp
@@ -104,8 +104,13 @@ int EditorUndoRedoManager::get_history_id_for_object(Object *p_object) const {
}
EditorUndoRedoManager::History &EditorUndoRedoManager::get_history_for_object(Object *p_object) {
- int history_id = get_history_id_for_object(p_object);
- ERR_FAIL_COND_V_MSG(pending_action.history_id != INVALID_HISTORY && history_id != pending_action.history_id, get_or_create_history(pending_action.history_id), vformat("UndoRedo history mismatch: expected %d, got %d.", pending_action.history_id, history_id));
+ int history_id;
+ if (!forced_history) {
+ history_id = get_history_id_for_object(p_object);
+ ERR_FAIL_COND_V_MSG(pending_action.history_id != INVALID_HISTORY && history_id != pending_action.history_id, get_or_create_history(pending_action.history_id), vformat("UndoRedo history mismatch: expected %d, got %d.", pending_action.history_id, history_id));
+ } else {
+ history_id = pending_action.history_id;
+ }
History &history = get_or_create_history(history_id);
if (pending_action.history_id == INVALID_HISTORY) {
@@ -116,6 +121,11 @@ EditorUndoRedoManager::History &EditorUndoRedoManager::get_history_for_object(Ob
return history;
}
+void EditorUndoRedoManager::force_fixed_history() {
+ ERR_FAIL_COND_MSG(pending_action.history_id == INVALID_HISTORY, "The current action has no valid history assigned.");
+ forced_history = true;
+}
+
void EditorUndoRedoManager::create_action_for_history(const String &p_name, int p_history_id, UndoRedo::MergeMode p_mode, bool p_backward_undo_ops) {
if (pending_action.history_id != INVALID_HISTORY) {
// Nested action.
@@ -236,6 +246,7 @@ void EditorUndoRedoManager::commit_action(bool p_execute) {
return; // Empty action, do nothing.
}
+ forced_history = false;
is_committing = true;
History &history = get_or_create_history(pending_action.history_id);
@@ -469,6 +480,7 @@ void EditorUndoRedoManager::_bind_methods() {
ClassDB::bind_method(D_METHOD("create_action", "name", "merge_mode", "custom_context", "backward_undo_ops"), &EditorUndoRedoManager::create_action, DEFVAL(UndoRedo::MERGE_DISABLE), DEFVAL((Object *)nullptr), DEFVAL(false));
ClassDB::bind_method(D_METHOD("commit_action", "execute"), &EditorUndoRedoManager::commit_action, DEFVAL(true));
ClassDB::bind_method(D_METHOD("is_committing_action"), &EditorUndoRedoManager::is_committing_action);
+ ClassDB::bind_method(D_METHOD("force_fixed_history"), &EditorUndoRedoManager::force_fixed_history);
{
MethodInfo mi;
diff --git a/editor/editor_undo_redo_manager.h b/editor/editor_undo_redo_manager.h
index e8c782871c..219d5e0702 100644
--- a/editor/editor_undo_redo_manager.h
+++ b/editor/editor_undo_redo_manager.h
@@ -67,6 +67,7 @@ private:
HashMap<int, History> history_map;
Action pending_action;
+ bool forced_history = false;
bool is_committing = false;
History *_get_newest_undo();
@@ -79,6 +80,7 @@ public:
UndoRedo *get_history_undo_redo(int p_idx) const;
int get_history_id_for_object(Object *p_object) const;
History &get_history_for_object(Object *p_object);
+ void force_fixed_history();
void create_action_for_history(const String &p_name, int p_history_id, UndoRedo::MergeMode p_mode = UndoRedo::MERGE_DISABLE, bool p_backward_undo_ops = false);
void create_action(const String &p_name = "", UndoRedo::MergeMode p_mode = UndoRedo::MERGE_DISABLE, Object *p_custom_context = nullptr, bool p_backward_undo_ops = false);
diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp
index 44ff95b0f9..29ca1279b2 100644
--- a/editor/filesystem_dock.cpp
+++ b/editor/filesystem_dock.cpp
@@ -758,6 +758,8 @@ void FileSystemDock::navigate_to_path(const String &p_path) {
// Ensure that the FileSystem dock is visible.
EditorDockManager::get_singleton()->focus_dock(this);
+ import_dock_needs_update = true;
+ _update_import_dock();
}
void FileSystemDock::_file_list_thumbnail_done(const String &p_path, const Ref<Texture2D> &p_preview, const Ref<Texture2D> &p_small_preview, const Variant &p_udata) {
diff --git a/editor/icons/AudioStreamPlayer.svg b/editor/icons/AudioStreamPlayer.svg
index 98a1650f6f..a3c4ad8e35 100644
--- a/editor/icons/AudioStreamPlayer.svg
+++ b/editor/icons/AudioStreamPlayer.svg
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="16" height="16" version="1.0" viewBox="0 0 2.4 2.4"><path fill="#e0e0e0" stroke="#e0e0e0" stroke-linejoin="round" stroke-width=".176" d="M1.382.335.777.858H.204v.673h.564l.614.531Z"/><path fill="none" stroke="#e0e0e0" stroke-linecap="round" stroke-width=".176" d="M1.718.572c.275.374.275.882 0 1.256M1.947.343c.402.5.402 1.213 0 1.714"/></svg> \ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="16" height="16" version="1.0" viewBox="0 0 2.4 2.4"><path fill="#e0e0e0" stroke="#e0e0e0" stroke-linejoin="round" stroke-width=".176" d="M1.382.335.777.858H.204v.673h.564l.614.531Z"/><path fill="none" stroke="#e0e0e0" stroke-linecap="round" stroke-width=".176" d="M1.718.572a1.06 1.06 0 0 1 0 1.256M1.947.343c.402.5.402 1.213 0 1.714"/></svg> \ No newline at end of file
diff --git a/editor/icons/AudioStreamPlayer2D.svg b/editor/icons/AudioStreamPlayer2D.svg
index 3a5c5c29af..fa60e30238 100644
--- a/editor/icons/AudioStreamPlayer2D.svg
+++ b/editor/icons/AudioStreamPlayer2D.svg
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="16" height="16" version="1.0" viewBox="0 0 2.4 2.4"><path fill="#8da5f3" stroke="#8da5f3" stroke-linejoin="round" stroke-width=".176" d="M1.382.335.777.858H.204v.673h.564l.614.531Z"/><path fill="none" stroke="#8da5f3" stroke-linecap="round" stroke-width=".176" d="M1.718.572c.275.374.275.882 0 1.256M1.947.343c.402.5.402 1.213 0 1.714"/></svg> \ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="16" height="16" version="1.0" viewBox="0 0 2.4 2.4"><path fill="#8da5f3" stroke="#8da5f3" stroke-linejoin="round" stroke-width=".176" d="M1.382.335.777.858H.204v.673h.564l.614.531Z"/><path fill="none" stroke="#8da5f3" stroke-linecap="round" stroke-width=".176" d="M1.718.572a1.06 1.06 0 0 1 0 1.256M1.947.343c.402.5.402 1.213 0 1.714"/></svg> \ No newline at end of file
diff --git a/editor/icons/AudioStreamPlayer3D.svg b/editor/icons/AudioStreamPlayer3D.svg
index c0480d0c7d..f6be929e61 100644
--- a/editor/icons/AudioStreamPlayer3D.svg
+++ b/editor/icons/AudioStreamPlayer3D.svg
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="16" height="16" version="1.0" viewBox="0 0 2.4 2.4"><path fill="#fc7f7f" stroke="#fc7f7f" stroke-linejoin="round" stroke-width=".176" d="M1.382.335.777.858H.204v.673h.564l.614.531Z"/><path fill="none" stroke="#fc7f7f" stroke-linecap="round" stroke-width=".176" d="M1.718.572c.275.374.275.882 0 1.256M1.947.343c.402.5.402 1.213 0 1.714"/></svg> \ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="16" height="16" version="1.0" viewBox="0 0 2.4 2.4"><path fill="#fc7f7f" stroke="#fc7f7f" stroke-linejoin="round" stroke-width=".176" d="M1.382.335.777.858H.204v.673h.564l.614.531Z"/><path fill="none" stroke="#fc7f7f" stroke-linecap="round" stroke-width=".176" d="M1.718.572a1.06 1.06 0 0 1 0 1.256M1.947.343c.402.5.402 1.213 0 1.714"/></svg> \ No newline at end of file
diff --git a/editor/plugins/cast_2d_editor_plugin.cpp b/editor/plugins/cast_2d_editor_plugin.cpp
index c9d7ff8e08..3da7d4a7dc 100644
--- a/editor/plugins/cast_2d_editor_plugin.cpp
+++ b/editor/plugins/cast_2d_editor_plugin.cpp
@@ -102,7 +102,6 @@ bool Cast2DEditor::forward_canvas_gui_input(const Ref<InputEvent> &p_event) {
node->set("target_position", point);
canvas_item_editor->update_viewport();
- node->notify_property_list_changed();
return true;
}
diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp
index d7d51d6a04..3d7647ca5b 100644
--- a/editor/plugins/node_3d_editor_plugin.cpp
+++ b/editor/plugins/node_3d_editor_plugin.cpp
@@ -325,11 +325,11 @@ void ViewportRotationControl::_notification(int p_what) {
}
void ViewportRotationControl::_draw() {
- const Vector2i center = get_size() / 2.0;
+ const Vector2 center = get_size() / 2.0;
const real_t radius = get_size().x / 2.0;
if (focused_axis > -2 || orbiting_index != -1) {
- draw_circle(center, radius, Color(0.5, 0.5, 0.5, 0.25));
+ draw_circle(center, radius, Color(0.5, 0.5, 0.5, 0.25), true, -1.0, true);
}
Vector<Axis2D> axis_to_draw;
@@ -345,34 +345,42 @@ void ViewportRotationControl::_draw_axis(const Axis2D &p_axis) {
const int direction = p_axis.axis % 3;
const Color axis_color = axis_colors[direction];
- const double alpha = focused ? 1.0 : ((p_axis.z_axis + 1.0) / 2.0) * 0.5 + 0.5;
- const Color c = focused ? Color(0.9, 0.9, 0.9) : Color(axis_color, alpha);
+ const double min_alpha = 0.35;
+ const double alpha = focused ? 1.0 : Math::remap((p_axis.z_axis + 1.0) / 2.0, 0, 0.5, min_alpha, 1.0);
+ const Color c = focused ? Color(axis_color.lightened(0.75), 1.0) : Color(axis_color, alpha);
if (positive) {
// Draw axis lines for the positive axes.
- const Vector2i center = get_size() / 2.0;
- draw_line(center, p_axis.screen_point, c, 1.5 * EDSCALE);
+ const Vector2 center = get_size() / 2.0;
+ const Vector2 diff = p_axis.screen_point - center;
+ const float line_length = MAX(diff.length() - AXIS_CIRCLE_RADIUS - 0.5 * EDSCALE, 0);
- draw_circle(p_axis.screen_point, AXIS_CIRCLE_RADIUS, c);
+ draw_line(center + diff.limit_length(0.5 * EDSCALE), center + diff.limit_length(line_length), c, 1.5 * EDSCALE, true);
+
+ draw_circle(p_axis.screen_point, AXIS_CIRCLE_RADIUS, c, true, -1.0, true);
// Draw the axis letter for the positive axes.
const String axis_name = direction == 0 ? "X" : (direction == 1 ? "Y" : "Z");
- draw_char(get_theme_font(SNAME("rotation_control"), EditorStringName(EditorFonts)), p_axis.screen_point + Vector2i(Math::round(-4.0 * EDSCALE), Math::round(5.0 * EDSCALE)), axis_name, get_theme_font_size(SNAME("rotation_control_size"), EditorStringName(EditorFonts)), Color(0.0, 0.0, 0.0, alpha));
+ const Ref<Font> &font = get_theme_font(SNAME("rotation_control"), EditorStringName(EditorFonts));
+ const int font_size = get_theme_font_size(SNAME("rotation_control_size"), EditorStringName(EditorFonts));
+ const Size2 char_size = font->get_char_size(axis_name[0], font_size);
+ const Vector2 char_offset = Vector2(-char_size.width / 2.0, char_size.height * 0.25);
+ draw_char(font, p_axis.screen_point + char_offset, axis_name, font_size, Color(0.0, 0.0, 0.0, alpha * 0.6));
} else {
// Draw an outline around the negative axes.
- draw_circle(p_axis.screen_point, AXIS_CIRCLE_RADIUS, c);
- draw_circle(p_axis.screen_point, AXIS_CIRCLE_RADIUS * 0.8, c.darkened(0.4));
+ draw_circle(p_axis.screen_point, AXIS_CIRCLE_RADIUS, c, true, -1.0, true);
+ draw_circle(p_axis.screen_point, AXIS_CIRCLE_RADIUS * 0.8, c.darkened(0.4), true, -1.0, true);
}
}
void ViewportRotationControl::_get_sorted_axis(Vector<Axis2D> &r_axis) {
- const Vector2i center = get_size() / 2.0;
+ const Vector2 center = get_size() / 2.0;
const real_t radius = get_size().x / 2.0 - AXIS_CIRCLE_RADIUS - 2.0 * EDSCALE;
const Basis camera_basis = viewport->to_camera_transform(viewport->cursor).get_basis().inverse();
for (int i = 0; i < 3; ++i) {
Vector3 axis_3d = camera_basis.get_column(i);
- Vector2i axis_vector = Vector2(axis_3d.x, -axis_3d.y) * radius;
+ Vector2 axis_vector = Vector2(axis_3d.x, -axis_3d.y) * radius;
if (Math::abs(axis_3d.z) <= 1.0) {
Axis2D pos_axis;
@@ -5432,6 +5440,7 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, int p
frame_time_gradient->add_point(0.5, Color());
top_right_vbox = memnew(VBoxContainer);
+ top_right_vbox->add_theme_constant_override("separation", 10.0 * EDSCALE);
top_right_vbox->set_anchors_and_offsets_preset(PRESET_TOP_RIGHT, PRESET_MODE_MINSIZE, 10.0 * EDSCALE);
top_right_vbox->set_h_grow_direction(GROW_DIRECTION_BEGIN);
diff --git a/editor/plugins/node_3d_editor_plugin.h b/editor/plugins/node_3d_editor_plugin.h
index 859d075732..580c001238 100644
--- a/editor/plugins/node_3d_editor_plugin.h
+++ b/editor/plugins/node_3d_editor_plugin.h
@@ -65,7 +65,7 @@ class ViewportRotationControl : public Control {
GDCLASS(ViewportRotationControl, Control);
struct Axis2D {
- Vector2i screen_point;
+ Vector2 screen_point;
float z_axis = -99.0;
int axis = -1;
};
diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp
index 3d043909b2..9d5dbb4a4f 100644
--- a/editor/plugins/script_editor_plugin.cpp
+++ b/editor/plugins/script_editor_plugin.cpp
@@ -1012,8 +1012,6 @@ void ScriptEditor::_resave_scripts(const String &p_str) {
se->trim_final_newlines();
}
- se->insert_final_newline();
-
if (convert_indent_on_save) {
se->convert_indent();
}
@@ -1410,8 +1408,6 @@ void ScriptEditor::_menu_option(int p_option) {
current->trim_final_newlines();
}
- current->insert_final_newline();
-
if (convert_indent_on_save) {
current->convert_indent();
}
@@ -2614,8 +2610,6 @@ void ScriptEditor::save_current_script() {
current->trim_final_newlines();
}
- current->insert_final_newline();
-
if (convert_indent_on_save) {
current->convert_indent();
}
@@ -2662,8 +2656,6 @@ void ScriptEditor::save_all_scripts() {
se->trim_final_newlines();
}
- se->insert_final_newline();
-
if (!se->is_unsaved()) {
continue;
}
@@ -2713,6 +2705,7 @@ void ScriptEditor::apply_scripts() const {
if (!se) {
continue;
}
+ se->insert_final_newline();
se->apply_code();
}
}
diff --git a/editor/plugins/sprite_frames_editor_plugin.cpp b/editor/plugins/sprite_frames_editor_plugin.cpp
index b33250bcb5..27056a6cc4 100644
--- a/editor/plugins/sprite_frames_editor_plugin.cpp
+++ b/editor/plugins/sprite_frames_editor_plugin.cpp
@@ -1056,10 +1056,12 @@ void SpriteFramesEditor::_rename_node_animation(EditorUndoRedoManager *undo_redo
for (Node *E : nodes) {
String current_name = E->call("get_animation");
if (current_name == p_filter) {
+ undo_redo->force_fixed_history(); // Fixes corner-case when editing SpriteFrames stored as separate file.
undo_redo->add_undo_method(E, "set_animation", p_new_animation);
}
String autoplay_name = E->call("get_autoplay");
if (autoplay_name == p_filter) {
+ undo_redo->force_fixed_history();
undo_redo->add_undo_method(E, "set_autoplay", p_new_autoplay);
}
}
@@ -1067,10 +1069,12 @@ void SpriteFramesEditor::_rename_node_animation(EditorUndoRedoManager *undo_redo
for (Node *E : nodes) {
String current_name = E->call("get_animation");
if (current_name == p_filter) {
+ undo_redo->force_fixed_history();
undo_redo->add_do_method(E, "set_animation", p_new_animation);
}
String autoplay_name = E->call("get_autoplay");
if (autoplay_name == p_filter) {
+ undo_redo->force_fixed_history();
undo_redo->add_do_method(E, "set_autoplay", p_new_autoplay);
}
}
diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp
index 3f6927c02a..607c446e1b 100644
--- a/editor/plugins/visual_shader_editor_plugin.cpp
+++ b/editor/plugins/visual_shader_editor_plugin.cpp
@@ -3649,12 +3649,15 @@ void VisualShaderEditor::_add_node(int p_idx, const Vector<Variant> &p_ops, cons
if (output_port_type == VisualShaderNode::PORT_TYPE_SAMPLER) {
if (is_texture2d) {
+ undo_redo->force_fixed_history(); // vsnode is freshly created and has no path, so history can't be correctly determined.
undo_redo->add_do_method(vsnode.ptr(), "set_source", VisualShaderNodeTexture::SOURCE_PORT);
}
if (is_texture3d || is_texture2d_array) {
+ undo_redo->force_fixed_history();
undo_redo->add_do_method(vsnode.ptr(), "set_source", VisualShaderNodeSample3D::SOURCE_PORT);
}
if (is_cubemap) {
+ undo_redo->force_fixed_history();
undo_redo->add_do_method(vsnode.ptr(), "set_source", VisualShaderNodeCubemap::SOURCE_PORT);
}
}
@@ -3754,16 +3757,19 @@ void VisualShaderEditor::_add_node(int p_idx, const Vector<Variant> &p_ops, cons
//post-initialization
if (is_texture2d || is_texture3d || is_curve || is_curve_xyz) {
+ undo_redo->force_fixed_history();
undo_redo->add_do_method(vsnode.ptr(), "set_texture", ResourceLoader::load(p_resource_path));
return;
}
if (is_cubemap) {
+ undo_redo->force_fixed_history();
undo_redo->add_do_method(vsnode.ptr(), "set_cube_map", ResourceLoader::load(p_resource_path));
return;
}
if (is_texture2d_array) {
+ undo_redo->force_fixed_history();
undo_redo->add_do_method(vsnode.ptr(), "set_texture_array", ResourceLoader::load(p_resource_path));
}
}
diff --git a/editor/themes/editor_fonts.cpp b/editor/themes/editor_fonts.cpp
index 71a050bc23..fc994a17d1 100644
--- a/editor/themes/editor_fonts.cpp
+++ b/editor/themes/editor_fonts.cpp
@@ -443,7 +443,7 @@ void editor_register_fonts(const Ref<Theme> &p_theme) {
p_theme->set_font("rulers", EditorStringName(EditorFonts), default_fc);
// Rotation widget font
- p_theme->set_font_size("rotation_control_size", EditorStringName(EditorFonts), 14 * EDSCALE);
+ p_theme->set_font_size("rotation_control_size", EditorStringName(EditorFonts), 13 * EDSCALE);
p_theme->set_font("rotation_control", EditorStringName(EditorFonts), default_fc);
// Code font
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index aa26bb222d..76e690a083 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -4299,7 +4299,8 @@ void GDScriptAnalyzer::reduce_preload(GDScriptParser::PreloadNode *p_preload) {
// Must load GDScript separately to permit cyclic references
// as ResourceLoader::load() detects and rejects those.
- if (ResourceLoader::get_resource_type(p_preload->resolved_path) == "GDScript") {
+ const String &res_type = ResourceLoader::get_resource_type(p_preload->resolved_path);
+ if (res_type == "GDScript") {
Error err = OK;
Ref<GDScript> res = GDScriptCache::get_shallow_script(p_preload->resolved_path, err, parser->script_path);
p_preload->resource = res;
@@ -4307,7 +4308,11 @@ void GDScriptAnalyzer::reduce_preload(GDScriptParser::PreloadNode *p_preload) {
push_error(vformat(R"(Could not preload resource script "%s".)", p_preload->resolved_path), p_preload->path);
}
} else {
- p_preload->resource = ResourceLoader::load(p_preload->resolved_path);
+ Error err = OK;
+ p_preload->resource = ResourceLoader::load(p_preload->resolved_path, res_type, ResourceFormatLoader::CACHE_MODE_REUSE, &err);
+ if (err == ERR_BUSY) {
+ p_preload->resource = ResourceLoader::ensure_resource_ref_override_for_outer_load(p_preload->resolved_path, res_type);
+ }
if (p_preload->resource.is_null()) {
push_error(vformat(R"(Could not preload resource file "%s".)", p_preload->resolved_path), p_preload->path);
}
@@ -5527,6 +5532,9 @@ bool GDScriptAnalyzer::check_type_compatibility(const GDScriptParser::DataType &
// A script type cannot be a subtype of a GDScript class.
return false;
}
+ if (p_source.script_type.is_null()) {
+ return false;
+ }
if (p_source.is_meta_type) {
src_native = p_source.script_type->get_class_name();
} else {
diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp
index 8da829907e..f557727718 100644
--- a/modules/gdscript/gdscript_editor.cpp
+++ b/modules/gdscript/gdscript_editor.cpp
@@ -3657,11 +3657,21 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co
return OK;
}
- StringName enum_name = ClassDB::get_integer_constant_enum(class_name, p_symbol, true);
- if (enum_name != StringName()) {
- r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_ENUM;
+ List<StringName> enums;
+ ClassDB::get_enum_list(class_name, &enums);
+ for (const StringName &E : enums) {
+ if (E == p_symbol) {
+ r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_ENUM;
+ r_result.class_name = base_type.native_type;
+ r_result.class_member = p_symbol;
+ return OK;
+ }
+ }
+
+ if (!String(ClassDB::get_integer_constant_enum(class_name, p_symbol, true)).is_empty()) {
+ r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_CONSTANT;
r_result.class_name = base_type.native_type;
- r_result.class_member = enum_name;
+ r_result.class_member = p_symbol;
return OK;
}
@@ -3735,6 +3745,15 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co
return OK;
}
} break;
+ case GDScriptParser::DataType::ENUM: {
+ if (base_type.enum_values.has(p_symbol)) {
+ r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_CONSTANT;
+ r_result.class_name = String(base_type.native_type).get_slicec('.', 0);
+ r_result.class_member = p_symbol;
+ return OK;
+ }
+ base_type.kind = GDScriptParser::DataType::UNRESOLVED;
+ } break;
default: {
base_type.kind = GDScriptParser::DataType::UNRESOLVED;
} break;
diff --git a/modules/gridmap/editor/grid_map_editor_plugin.cpp b/modules/gridmap/editor/grid_map_editor_plugin.cpp
index b93b7c1a8c..f402e2a583 100644
--- a/modules/gridmap/editor/grid_map_editor_plugin.cpp
+++ b/modules/gridmap/editor/grid_map_editor_plugin.cpp
@@ -59,10 +59,18 @@ void GridMapEditor::_menu_option(int p_option) {
switch (p_option) {
case MENU_OPTION_PREV_LEVEL: {
floor->set_value(floor->get_value() - 1);
+ if (selection.active && input_action == INPUT_SELECT) {
+ selection.current[edit_axis]--;
+ _validate_selection();
+ }
} break;
case MENU_OPTION_NEXT_LEVEL: {
floor->set_value(floor->get_value() + 1);
+ if (selection.active && input_action == INPUT_SELECT) {
+ selection.current[edit_axis]++;
+ _validate_selection();
+ }
} break;
case MENU_OPTION_X_AXIS:
@@ -754,19 +762,6 @@ EditorPlugin::AfterGUIInput GridMapEditor::forward_spatial_input_event(Camera3D
}
}
}
-
- if (k->is_shift_pressed() && selection.active && input_action != INPUT_PASTE) {
- if (k->get_keycode() == (Key)options->get_popup()->get_item_accelerator(options->get_popup()->get_item_index(MENU_OPTION_PREV_LEVEL))) {
- selection.click[edit_axis]--;
- _validate_selection();
- return EditorPlugin::AFTER_GUI_INPUT_STOP;
- }
- if (k->get_keycode() == (Key)options->get_popup()->get_item_accelerator(options->get_popup()->get_item_index(MENU_OPTION_NEXT_LEVEL))) {
- selection.click[edit_axis]++;
- _validate_selection();
- return EditorPlugin::AFTER_GUI_INPUT_STOP;
- }
- }
}
}
diff --git a/modules/webxr/doc_classes/WebXRInterface.xml b/modules/webxr/doc_classes/WebXRInterface.xml
index 9fd4511d2b..bd7192520a 100644
--- a/modules/webxr/doc_classes/WebXRInterface.xml
+++ b/modules/webxr/doc_classes/WebXRInterface.xml
@@ -54,9 +54,10 @@
# supported.
webxr_interface.requested_reference_space_types = 'bounded-floor, local-floor, local'
# In order to use 'local-floor' or 'bounded-floor' we must also
- # mark the features as required or optional.
+ # mark the features as required or optional. By including 'hand-tracking'
+ # as an optional feature, it will be enabled if supported.
webxr_interface.required_features = 'local-floor'
- webxr_interface.optional_features = 'bounded-floor'
+ webxr_interface.optional_features = 'bounded-floor, hand-tracking'
# This will return false if we're unable to even request the session,
# however, it can still fail asynchronously later in the process, so we
@@ -73,7 +74,10 @@
# This will be the reference space type you ultimately got, out of the
# types that you requested above. This is useful if you want the game to
# work a little differently in 'bounded-floor' versus 'local-floor'.
- print ("Reference space type: " + webxr_interface.reference_space_type)
+ print("Reference space type: ", webxr_interface.reference_space_type)
+ # This will be the list of features that were successfully enabled
+ # (except on browsers that don't support this property).
+ print("Enabled features: ", webxr_interface.enabled_features)
func _webxr_session_ended():
$Button.visible = true
@@ -155,13 +159,14 @@
<members>
<member name="enabled_features" type="String" setter="" getter="get_enabled_features">
A comma-separated list of features that were successfully enabled by [method XRInterface.initialize] when setting up the WebXR session.
- This may include features requested by setting [member required_features] and [member optional_features].
+ This may include features requested by setting [member required_features] and [member optional_features], and will only be available after [signal session_started] has been emitted.
+ [b]Note:[/b] This may not be support by all web browsers, in which case it will be an empty string.
</member>
<member name="optional_features" type="String" setter="set_optional_features" getter="get_optional_features">
A comma-seperated list of optional features used by [method XRInterface.initialize] when setting up the WebXR session.
If a user's browser or device doesn't support one of the given features, initialization will continue, but you won't be able to use the requested feature.
This doesn't have any effect on the interface when already initialized.
- Possible values come from [url=https://developer.mozilla.org/en-US/docs/Web/API/XRReferenceSpaceType]WebXR's XRReferenceSpaceType[/url]. If you want to use a particular reference space type, it must be listed in either [member required_features] or [member optional_features].
+ Possible values come from [url=https://developer.mozilla.org/en-US/docs/Web/API/XRReferenceSpaceType]WebXR's XRReferenceSpaceType[/url], or include other features like [code]"hand-tracking"[/code] to enable hand tracking.
</member>
<member name="reference_space_type" type="String" setter="" getter="get_reference_space_type">
The reference space type (from the list of requested types set in the [member requested_reference_space_types] property), that was ultimately used by [method XRInterface.initialize] when setting up the WebXR session.
@@ -177,7 +182,7 @@
A comma-seperated list of required features used by [method XRInterface.initialize] when setting up the WebXR session.
If a user's browser or device doesn't support one of the given features, initialization will fail and [signal session_failed] will be emitted.
This doesn't have any effect on the interface when already initialized.
- Possible values come from [url=https://developer.mozilla.org/en-US/docs/Web/API/XRReferenceSpaceType]WebXR's XRReferenceSpaceType[/url]. If you want to use a particular reference space type, it must be listed in either [member required_features] or [member optional_features].
+ Possible values come from [url=https://developer.mozilla.org/en-US/docs/Web/API/XRReferenceSpaceType]WebXR's XRReferenceSpaceType[/url], or include other features like [code]"hand-tracking"[/code] to enable hand tracking.
</member>
<member name="session_mode" type="String" setter="set_session_mode" getter="get_session_mode">
The session mode used by [method XRInterface.initialize] when setting up the WebXR session.
diff --git a/modules/webxr/godot_webxr.h b/modules/webxr/godot_webxr.h
index caa7f217af..1b3e3b6a41 100644
--- a/modules/webxr/godot_webxr.h
+++ b/modules/webxr/godot_webxr.h
@@ -45,7 +45,7 @@ enum WebXRInputEvent {
};
typedef void (*GodotWebXRSupportedCallback)(char *p_session_mode, int p_supported);
-typedef void (*GodotWebXRStartedCallback)(char *p_reference_space_type, char *p_enabled_features);
+typedef void (*GodotWebXRStartedCallback)(char *p_reference_space_type, char *p_enabled_features, char *p_environment_blend_mode);
typedef void (*GodotWebXREndedCallback)();
typedef void (*GodotWebXRFailedCallback)(char *p_message);
typedef void (*GodotWebXRInputEventCallback)(int p_event_type, int p_input_source_id);
diff --git a/modules/webxr/native/library_godot_webxr.js b/modules/webxr/native/library_godot_webxr.js
index 031530a047..155409f931 100644
--- a/modules/webxr/native/library_godot_webxr.js
+++ b/modules/webxr/native/library_godot_webxr.js
@@ -320,10 +320,14 @@ const GodotWebXR = {
// next reference space.
window.setTimeout(function () {
const reference_space_c_str = GodotRuntime.allocString(reference_space_type);
- const enabled_features_c_str = GodotRuntime.allocString(Array.from(session.enabledFeatures).join(','));
- onstarted(reference_space_c_str, enabled_features_c_str);
+ const enabled_features = 'enabledFeatures' in session ? Array.from(session.enabledFeatures) : [];
+ const enabled_features_c_str = GodotRuntime.allocString(enabled_features.join(','));
+ const environment_blend_mode = 'environmentBlendMode' in session ? session.environmentBlendMode : '';
+ const environment_blend_mode_c_str = GodotRuntime.allocString(environment_blend_mode);
+ onstarted(reference_space_c_str, enabled_features_c_str, environment_blend_mode_c_str);
GodotRuntime.free(reference_space_c_str);
GodotRuntime.free(enabled_features_c_str);
+ GodotRuntime.free(environment_blend_mode_c_str);
}, 0);
}
diff --git a/modules/webxr/native/webxr.externs.js b/modules/webxr/native/webxr.externs.js
index 35ad33fa93..18125d7869 100644
--- a/modules/webxr/native/webxr.externs.js
+++ b/modules/webxr/native/webxr.externs.js
@@ -78,6 +78,16 @@ XRSession.prototype.frameRate;
XRSession.prototype.supportedFrameRates;
/**
+ * @type {Array<string>}
+ */
+XRSession.prototype.enabledFeatures;
+
+/**
+ * @type {string}
+ */
+XRSession.prototype.environmentBlendMode;
+
+/**
* @type {?function (Event)}
*/
XRSession.prototype.onend;
diff --git a/modules/webxr/webxr_interface_js.cpp b/modules/webxr/webxr_interface_js.cpp
index 916566fc1b..78a9db9b19 100644
--- a/modules/webxr/webxr_interface_js.cpp
+++ b/modules/webxr/webxr_interface_js.cpp
@@ -58,7 +58,7 @@ void _emwebxr_on_session_supported(char *p_session_mode, int p_supported) {
interface->emit_signal(SNAME("session_supported"), session_mode, p_supported ? true : false);
}
-void _emwebxr_on_session_started(char *p_reference_space_type, char *p_enabled_features) {
+void _emwebxr_on_session_started(char *p_reference_space_type, char *p_enabled_features, char *p_environment_blend_mode) {
XRServer *xr_server = XRServer::get_singleton();
ERR_FAIL_NULL(xr_server);
@@ -68,6 +68,7 @@ void _emwebxr_on_session_started(char *p_reference_space_type, char *p_enabled_f
String reference_space_type = String(p_reference_space_type);
interface->_set_reference_space_type(reference_space_type);
interface->_set_enabled_features(p_enabled_features);
+ interface->_set_environment_blend_mode(p_environment_blend_mode);
interface->emit_signal(SNAME("session_started"));
}
@@ -230,6 +231,44 @@ Array WebXRInterfaceJS::get_available_display_refresh_rates() const {
return ret;
}
+Array WebXRInterfaceJS::get_supported_environment_blend_modes() {
+ Array blend_modes;
+ // The blend mode can't be changed, so return the current blend mode as the only supported one.
+ blend_modes.push_back(environment_blend_mode);
+ return blend_modes;
+}
+
+XRInterface::EnvironmentBlendMode WebXRInterfaceJS::get_environment_blend_mode() const {
+ return environment_blend_mode;
+}
+
+bool WebXRInterfaceJS::set_environment_blend_mode(EnvironmentBlendMode p_new_environment_blend_mode) {
+ if (environment_blend_mode == p_new_environment_blend_mode) {
+ // Environment blend mode can't be changed, but we'll consider it a success to set it
+ // to what it already is.
+ return true;
+ }
+ return false;
+}
+
+void WebXRInterfaceJS::_set_environment_blend_mode(String p_blend_mode_string) {
+ if (p_blend_mode_string == "opaque") {
+ environment_blend_mode = XRInterface::XR_ENV_BLEND_MODE_OPAQUE;
+ } else if (p_blend_mode_string == "additive") {
+ environment_blend_mode = XRInterface::XR_ENV_BLEND_MODE_ADDITIVE;
+ } else if (p_blend_mode_string == "alpha-blend") {
+ environment_blend_mode = XRInterface::XR_ENV_BLEND_MODE_ALPHA_BLEND;
+ } else {
+ // Not all browsers can give us this information, so as a fallback,
+ // we'll make some guesses about the blend mode.
+ if (session_mode == "immersive-ar") {
+ environment_blend_mode = XRInterface::XR_ENV_BLEND_MODE_ALPHA_BLEND;
+ } else {
+ environment_blend_mode = XRInterface::XR_ENV_BLEND_MODE_OPAQUE;
+ }
+ }
+}
+
StringName WebXRInterfaceJS::get_name() const {
return "WebXR";
};
@@ -336,6 +375,7 @@ void WebXRInterfaceJS::uninitialize() {
texture_cache.clear();
reference_space_type.clear();
enabled_features.clear();
+ environment_blend_mode = XRInterface::XR_ENV_BLEND_MODE_OPAQUE;
initialized = false;
};
};
@@ -740,12 +780,20 @@ void WebXRInterfaceJS::_update_input_source(int p_input_source_id) {
// WebXR doesn't have a palm joint, so we calculate it by finding the middle of the middle finger metacarpal bone.
{
- // 10 is the WebXR middle finger metacarpal joint, and 12 is the offset to the transform origin.
- const float *start_pos = hand_joints + (10 * 16) + 12;
- // 11 is the WebXR middle finger phalanx proximal joint, and 12 is the offset to the transform origin.
- const float *end_pos = hand_joints + (11 * 16) + 12;
- Transform3D palm_transform;
- palm_transform.origin = (Vector3(start_pos[0], start_pos[1], start_pos[2]) + Vector3(end_pos[0], end_pos[1], end_pos[2])) / 2.0;
+ // Start by getting the middle finger metacarpal joint.
+ // Note: 10 is the WebXR middle finger metacarpal joint.
+ Transform3D palm_transform = _js_matrix_to_transform(hand_joints + (10 * 16));
+ palm_transform.basis *= bone_adjustment;
+
+ // Get the middle finger phalanx position.
+ // Note: 11 is the WebXR middle finger phalanx proximal joint and 12 is the origin offset.
+ const float *phalanx_pos = hand_joints + (11 * 16) + 12;
+ Vector3 phalanx(phalanx_pos[0], phalanx_pos[1], phalanx_pos[2]);
+
+ // Offset the palm half-way towards the phalanx joint.
+ palm_transform.origin = (palm_transform.origin + phalanx) / 2.0;
+
+ // Set the palm joint and the pose.
hand_tracker->set_hand_joint_transform(XRHandTracker::HAND_JOINT_PALM, palm_transform);
hand_tracker->set_pose("default", palm_transform, Vector3(), Vector3());
}
diff --git a/modules/webxr/webxr_interface_js.h b/modules/webxr/webxr_interface_js.h
index afce28d410..d02c8d2677 100644
--- a/modules/webxr/webxr_interface_js.h
+++ b/modules/webxr/webxr_interface_js.h
@@ -60,6 +60,8 @@ private:
String reference_space_type;
String enabled_features;
+ XRInterface::EnvironmentBlendMode environment_blend_mode = XRInterface::XR_ENV_BLEND_MODE_OPAQUE;
+
Size2 render_targetsize;
RBMap<unsigned int, RID> texture_cache;
struct Touch {
@@ -113,6 +115,10 @@ public:
virtual void set_display_refresh_rate(float p_refresh_rate) override;
virtual Array get_available_display_refresh_rates() const override;
+ virtual Array get_supported_environment_blend_modes() override;
+ virtual XRInterface::EnvironmentBlendMode get_environment_blend_mode() const override;
+ virtual bool set_environment_blend_mode(EnvironmentBlendMode p_new_environment_blend_mode) override;
+
virtual StringName get_name() const override;
virtual uint32_t get_capabilities() const override;
@@ -136,8 +142,10 @@ public:
void _on_input_event(int p_event_type, int p_input_source_id);
+ // Internal setters used by callbacks from Emscripten.
inline void _set_reference_space_type(String p_reference_space_type) { reference_space_type = p_reference_space_type; }
inline void _set_enabled_features(String p_enabled_features) { enabled_features = p_enabled_features; }
+ void _set_environment_blend_mode(String p_blend_mode_string);
WebXRInterfaceJS();
~WebXRInterfaceJS();
diff --git a/platform/web/export/export_plugin.cpp b/platform/web/export/export_plugin.cpp
index 8941c26ec0..d83e465e8e 100644
--- a/platform/web/export/export_plugin.cpp
+++ b/platform/web/export/export_plugin.cpp
@@ -334,10 +334,14 @@ void EditorExportPlatformWeb::get_preset_features(const Ref<EditorExportPreset>
if (p_preset->get("vram_texture_compression/for_desktop")) {
r_features->push_back("s3tc");
}
-
if (p_preset->get("vram_texture_compression/for_mobile")) {
r_features->push_back("etc2");
}
+ if (p_preset->get("variant/thread_support").operator bool()) {
+ r_features->push_back("threads");
+ } else {
+ r_features->push_back("nothreads");
+ }
r_features->push_back("wasm32");
}
@@ -345,7 +349,7 @@ void EditorExportPlatformWeb::get_export_options(List<ExportOption> *r_options)
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/debug", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/release", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), ""));
- r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "variant/extensions_support"), false)); // Export type.
+ r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "variant/extensions_support"), false)); // GDExtension support.
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "variant/thread_support"), false)); // Thread support (i.e. run with or without COEP/COOP headers).
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "vram_texture_compression/for_desktop"), true)); // S3TC
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "vram_texture_compression/for_mobile"), false)); // ETC or ETC2, depending on renderer
@@ -585,35 +589,50 @@ bool EditorExportPlatformWeb::poll_export() {
}
}
- int prev = menu_options;
- menu_options = preset.is_valid();
- HTTPServerState prev_server_state = server_state;
- server_state = HTTP_SERVER_STATE_OFF;
- if (server->is_listening()) {
- if (preset.is_null() || menu_options == 0) {
- server->stop();
- } else {
- server_state = HTTP_SERVER_STATE_ON;
- menu_options += 1;
+ RemoteDebugState prev_remote_debug_state = remote_debug_state;
+ remote_debug_state = REMOTE_DEBUG_STATE_UNAVAILABLE;
+
+ if (preset.is_valid()) {
+ const bool debug = true;
+ // Throwaway variables to pass to `can_export`.
+ String err;
+ bool missing_templates;
+
+ if (can_export(preset, err, missing_templates, debug)) {
+ if (server->is_listening()) {
+ remote_debug_state = REMOTE_DEBUG_STATE_SERVING;
+ } else {
+ remote_debug_state = REMOTE_DEBUG_STATE_AVAILABLE;
+ }
}
}
- return server_state != prev_server_state || menu_options != prev;
+ if (remote_debug_state != REMOTE_DEBUG_STATE_SERVING && server->is_listening()) {
+ server->stop();
+ }
+
+ return remote_debug_state != prev_remote_debug_state;
}
Ref<ImageTexture> EditorExportPlatformWeb::get_option_icon(int p_index) const {
Ref<ImageTexture> play_icon = EditorExportPlatform::get_option_icon(p_index);
- switch (server_state) {
- case HTTP_SERVER_STATE_OFF: {
+ switch (remote_debug_state) {
+ case REMOTE_DEBUG_STATE_UNAVAILABLE: {
+ return nullptr;
+ } break;
+
+ case REMOTE_DEBUG_STATE_AVAILABLE: {
switch (p_index) {
case 0:
case 1:
return play_icon;
+ default:
+ ERR_FAIL_V(nullptr);
}
} break;
- case HTTP_SERVER_STATE_ON: {
+ case REMOTE_DEBUG_STATE_SERVING: {
switch (p_index) {
case 0:
return play_icon;
@@ -621,18 +640,31 @@ Ref<ImageTexture> EditorExportPlatformWeb::get_option_icon(int p_index) const {
return restart_icon;
case 2:
return stop_icon;
+ default:
+ ERR_FAIL_V(nullptr);
}
} break;
}
- ERR_FAIL_V_MSG(nullptr, vformat(R"(EditorExportPlatformWeb option icon index "%s" is invalid.)", p_index));
+ return nullptr;
}
int EditorExportPlatformWeb::get_options_count() const {
- if (server_state == HTTP_SERVER_STATE_ON) {
- return 3;
+ switch (remote_debug_state) {
+ case REMOTE_DEBUG_STATE_UNAVAILABLE: {
+ return 0;
+ } break;
+
+ case REMOTE_DEBUG_STATE_AVAILABLE: {
+ return 2;
+ } break;
+
+ case REMOTE_DEBUG_STATE_SERVING: {
+ return 3;
+ } break;
}
- return 2;
+
+ return 0;
}
String EditorExportPlatformWeb::get_option_label(int p_index) const {
@@ -641,17 +673,22 @@ String EditorExportPlatformWeb::get_option_label(int p_index) const {
String reexport_project = TTR("Re-export Project");
String stop_http_server = TTR("Stop HTTP Server");
- switch (server_state) {
- case HTTP_SERVER_STATE_OFF: {
+ switch (remote_debug_state) {
+ case REMOTE_DEBUG_STATE_UNAVAILABLE:
+ return "";
+
+ case REMOTE_DEBUG_STATE_AVAILABLE: {
switch (p_index) {
case 0:
return run_in_browser;
case 1:
return start_http_server;
+ default:
+ ERR_FAIL_V("");
}
} break;
- case HTTP_SERVER_STATE_ON: {
+ case REMOTE_DEBUG_STATE_SERVING: {
switch (p_index) {
case 0:
return run_in_browser;
@@ -659,11 +696,13 @@ String EditorExportPlatformWeb::get_option_label(int p_index) const {
return reexport_project;
case 2:
return stop_http_server;
+ default:
+ ERR_FAIL_V("");
}
} break;
}
- ERR_FAIL_V_MSG("", vformat(R"(EditorExportPlatformWeb option label index "%s" is invalid.)", p_index));
+ return "";
}
String EditorExportPlatformWeb::get_option_tooltip(int p_index) const {
@@ -672,17 +711,22 @@ String EditorExportPlatformWeb::get_option_tooltip(int p_index) const {
String reexport_project = TTR("Export project again to account for updates.");
String stop_http_server = TTR("Stop the HTTP server.");
- switch (server_state) {
- case HTTP_SERVER_STATE_OFF: {
+ switch (remote_debug_state) {
+ case REMOTE_DEBUG_STATE_UNAVAILABLE:
+ return "";
+
+ case REMOTE_DEBUG_STATE_AVAILABLE: {
switch (p_index) {
case 0:
return run_in_browser;
case 1:
return start_http_server;
+ default:
+ ERR_FAIL_V("");
}
} break;
- case HTTP_SERVER_STATE_ON: {
+ case REMOTE_DEBUG_STATE_SERVING: {
switch (p_index) {
case 0:
return run_in_browser;
@@ -690,11 +734,13 @@ String EditorExportPlatformWeb::get_option_tooltip(int p_index) const {
return reexport_project;
case 2:
return stop_http_server;
+ default:
+ ERR_FAIL_V("");
}
} break;
}
- ERR_FAIL_V_MSG("", vformat(R"(EditorExportPlatformWeb option tooltip index "%s" is invalid.)", p_index));
+ return "";
}
Error EditorExportPlatformWeb::run(const Ref<EditorExportPreset> &p_preset, int p_option, int p_debug_flags) {
@@ -703,8 +749,12 @@ Error EditorExportPlatformWeb::run(const Ref<EditorExportPreset> &p_preset, int
const String bind_host = EDITOR_GET("export/web/http_host");
const bool use_tls = EDITOR_GET("export/web/use_tls");
- switch (server_state) {
- case HTTP_SERVER_STATE_OFF: {
+ switch (remote_debug_state) {
+ case REMOTE_DEBUG_STATE_UNAVAILABLE: {
+ return FAILED;
+ } break;
+
+ case REMOTE_DEBUG_STATE_AVAILABLE: {
switch (p_option) {
// Run in Browser.
case 0: {
@@ -727,10 +777,14 @@ Error EditorExportPlatformWeb::run(const Ref<EditorExportPreset> &p_preset, int
}
return _start_server(bind_host, bind_port, use_tls);
} break;
+
+ default: {
+ ERR_FAIL_V_MSG(FAILED, vformat(R"(Invalid option "%s" for the current state.)", p_option));
+ }
}
} break;
- case HTTP_SERVER_STATE_ON: {
+ case REMOTE_DEBUG_STATE_SERVING: {
switch (p_option) {
// Run in Browser.
case 0: {
@@ -750,11 +804,15 @@ Error EditorExportPlatformWeb::run(const Ref<EditorExportPreset> &p_preset, int
case 2: {
return _stop_server();
} break;
+
+ default: {
+ ERR_FAIL_V_MSG(FAILED, vformat(R"(Invalid option "%s" for the current state.)", p_option));
+ }
}
} break;
}
- ERR_FAIL_V_MSG(ERR_INVALID_PARAMETER, vformat(R"(Trying to run EditorExportPlatformWeb, but option "%s" isn't known.)", p_option));
+ return FAILED;
}
Error EditorExportPlatformWeb::_export_project(const Ref<EditorExportPreset> &p_preset, int p_debug_flags) {
diff --git a/platform/web/export/export_plugin.h b/platform/web/export/export_plugin.h
index d3d2083a23..2f67d8107f 100644
--- a/platform/web/export/export_plugin.h
+++ b/platform/web/export/export_plugin.h
@@ -46,19 +46,19 @@
class EditorExportPlatformWeb : public EditorExportPlatform {
GDCLASS(EditorExportPlatformWeb, EditorExportPlatform);
- enum HTTPServerState {
- HTTP_SERVER_STATE_OFF,
- HTTP_SERVER_STATE_ON,
+ enum RemoteDebugState {
+ REMOTE_DEBUG_STATE_UNAVAILABLE,
+ REMOTE_DEBUG_STATE_AVAILABLE,
+ REMOTE_DEBUG_STATE_SERVING,
};
Ref<ImageTexture> logo;
Ref<ImageTexture> run_icon;
Ref<ImageTexture> stop_icon;
Ref<ImageTexture> restart_icon;
- HTTPServerState server_state = HTTP_SERVER_STATE_OFF;
+ RemoteDebugState remote_debug_state = REMOTE_DEBUG_STATE_UNAVAILABLE;
Ref<EditorHTTPServer> server;
- int menu_options = 0;
String _get_template_name(bool p_extension, bool p_thread_support, bool p_debug) const {
String name = "web";
diff --git a/scene/2d/animated_sprite_2d.cpp b/scene/2d/animated_sprite_2d.cpp
index 6d380aed3c..3506a0df2b 100644
--- a/scene/2d/animated_sprite_2d.cpp
+++ b/scene/2d/animated_sprite_2d.cpp
@@ -473,9 +473,10 @@ void AnimatedSprite2D::play(const StringName &p_name, float p_custom_scale, bool
playing = true;
custom_speed_scale = p_custom_scale;
- int end_frame = MAX(0, frames->get_frame_count(animation) - 1);
if (name != animation) {
animation = name;
+ int end_frame = MAX(0, frames->get_frame_count(animation) - 1);
+
if (p_from_end) {
set_frame_and_progress(end_frame, 1.0);
} else {
@@ -483,7 +484,9 @@ void AnimatedSprite2D::play(const StringName &p_name, float p_custom_scale, bool
}
emit_signal(SceneStringName(animation_changed));
} else {
+ int end_frame = MAX(0, frames->get_frame_count(animation) - 1);
bool is_backward = signbit(speed_scale * custom_speed_scale);
+
if (p_from_end && is_backward && frame == 0 && frame_progress <= 0.0) {
set_frame_and_progress(end_frame, 1.0);
} else if (!p_from_end && !is_backward && frame == end_frame && frame_progress >= 1.0) {
diff --git a/scene/3d/physics/physical_bone_3d.cpp b/scene/3d/physics/physical_bone_3d.cpp
index c290f16c0d..294690a89a 100644
--- a/scene/3d/physics/physical_bone_3d.cpp
+++ b/scene/3d/physics/physical_bone_3d.cpp
@@ -764,7 +764,7 @@ void PhysicalBone3D::_notification(int p_what) {
case NOTIFICATION_EXIT_TREE: {
PhysicalBoneSimulator3D *simulator = get_simulator();
if (simulator) {
- if (-1 != bone_id) {
+ if (bone_id != -1) {
simulator->unbind_physical_bone_from_bone(bone_id);
bone_id = -1;
}
@@ -816,7 +816,7 @@ void PhysicalBone3D::_body_state_changed(PhysicsDirectBodyState3D *p_state) {
PhysicalBoneSimulator3D *simulator = get_simulator();
Skeleton3D *skeleton = get_skeleton();
if (simulator && skeleton) {
- if (-1 != bone_id) {
+ if (bone_id != -1) {
simulator->set_bone_global_pose(bone_id, skeleton->get_global_transform().affine_inverse() * (global_transform * body_offset_inverse));
}
}
@@ -1293,7 +1293,7 @@ void PhysicalBone3D::update_bone_id() {
const int new_bone_id = simulator->find_bone(bone_name);
if (new_bone_id != bone_id) {
- if (-1 != bone_id) {
+ if (bone_id != -1) {
// Assert the unbind from old node
simulator->unbind_physical_bone_from_bone(bone_id);
}
@@ -1313,7 +1313,7 @@ void PhysicalBone3D::update_offset() {
Skeleton3D *skeleton = get_skeleton();
if (simulator && skeleton) {
Transform3D bone_transform(skeleton->get_global_transform());
- if (-1 != bone_id) {
+ if (bone_id != -1) {
bone_transform *= simulator->get_bone_global_pose(bone_id);
}
@@ -1328,7 +1328,7 @@ void PhysicalBone3D::update_offset() {
}
void PhysicalBone3D::_start_physics_simulation() {
- if (_internal_simulate_physics || !simulator_id.is_valid()) {
+ if (_internal_simulate_physics || !simulator_id.is_valid() || bone_id == -1) {
return;
}
reset_to_rest_position();
@@ -1344,7 +1344,7 @@ void PhysicalBone3D::_start_physics_simulation() {
void PhysicalBone3D::_stop_physics_simulation() {
PhysicalBoneSimulator3D *simulator = get_simulator();
if (simulator) {
- if (simulator->is_simulating_physics()) {
+ if (simulator->is_active() && bone_id != -1) {
set_body_mode(PhysicsServer3D::BODY_MODE_KINEMATIC);
PhysicsServer3D::get_singleton()->body_set_collision_layer(get_rid(), get_collision_layer());
PhysicsServer3D::get_singleton()->body_set_collision_mask(get_rid(), get_collision_mask());
diff --git a/scene/3d/skeleton_3d.cpp b/scene/3d/skeleton_3d.cpp
index 7c9fbd3b6d..b5263add48 100644
--- a/scene/3d/skeleton_3d.cpp
+++ b/scene/3d/skeleton_3d.cpp
@@ -1140,7 +1140,8 @@ void Skeleton3D::set_animate_physical_bones(bool p_enabled) {
if (!sim) {
return;
}
- sim->set_active(p_enabled);
+ animate_physical_bones = p_enabled;
+ sim->set_active(animate_physical_bones || sim->is_simulating_physics());
}
bool Skeleton3D::get_animate_physical_bones() const {
@@ -1148,7 +1149,7 @@ bool Skeleton3D::get_animate_physical_bones() const {
if (!sim) {
return false;
}
- return sim->is_active();
+ return animate_physical_bones;
}
void Skeleton3D::physical_bones_stop_simulation() {
@@ -1157,7 +1158,7 @@ void Skeleton3D::physical_bones_stop_simulation() {
return;
}
sim->physical_bones_stop_simulation();
- sim->set_active(false);
+ sim->set_active(animate_physical_bones || sim->is_simulating_physics());
}
void Skeleton3D::physical_bones_start_simulation_on(const TypedArray<StringName> &p_bones) {
diff --git a/scene/3d/skeleton_3d.h b/scene/3d/skeleton_3d.h
index eef7c9318d..2d70aafcad 100644
--- a/scene/3d/skeleton_3d.h
+++ b/scene/3d/skeleton_3d.h
@@ -67,6 +67,7 @@ class Skeleton3D : public Node3D {
GDCLASS(Skeleton3D, Node3D);
#ifndef DISABLE_DEPRECATED
+ bool animate_physical_bones = false;
Node *simulator = nullptr;
void setup_simulator();
#endif // _DISABLE_DEPRECATED
diff --git a/scene/3d/sprite_3d.cpp b/scene/3d/sprite_3d.cpp
index ba3b32a031..d08aeb1de2 100644
--- a/scene/3d/sprite_3d.cpp
+++ b/scene/3d/sprite_3d.cpp
@@ -1334,9 +1334,10 @@ void AnimatedSprite3D::play(const StringName &p_name, float p_custom_scale, bool
playing = true;
custom_speed_scale = p_custom_scale;
- int end_frame = MAX(0, frames->get_frame_count(animation) - 1);
if (name != animation) {
animation = name;
+ int end_frame = MAX(0, frames->get_frame_count(animation) - 1);
+
if (p_from_end) {
set_frame_and_progress(end_frame, 1.0);
} else {
@@ -1344,7 +1345,9 @@ void AnimatedSprite3D::play(const StringName &p_name, float p_custom_scale, bool
}
emit_signal(SceneStringName(animation_changed));
} else {
+ int end_frame = MAX(0, frames->get_frame_count(animation) - 1);
bool is_backward = signbit(speed_scale * custom_speed_scale);
+
if (p_from_end && is_backward && frame == 0 && frame_progress <= 0.0) {
set_frame_and_progress(end_frame, 1.0);
} else if (!p_from_end && !is_backward && frame == end_frame && frame_progress >= 1.0) {
diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp
index 4d75e06ff9..1302e3c53e 100644
--- a/scene/main/viewport.cpp
+++ b/scene/main/viewport.cpp
@@ -309,7 +309,8 @@ void Viewport::_sub_window_update(Window *p_window) {
int index = _sub_window_find(p_window);
ERR_FAIL_COND(index == -1);
- const SubWindow &sw = gui.sub_windows[index];
+ SubWindow &sw = gui.sub_windows.write[index];
+ sw.pending_window_update = false;
Transform2D pos;
pos.set_origin(p_window->get_position());
@@ -972,6 +973,14 @@ void Viewport::update_canvas_items() {
return;
}
+ if (is_embedding_subwindows()) {
+ for (Viewport::SubWindow w : gui.sub_windows) {
+ if (w.window && !w.pending_window_update) {
+ w.pending_window_update = true;
+ callable_mp(this, &Viewport::_sub_window_update).call_deferred(w.window);
+ }
+ }
+ }
_update_canvas_items(this);
}
diff --git a/scene/main/viewport.h b/scene/main/viewport.h
index df09755d4f..0d31c07e57 100644
--- a/scene/main/viewport.h
+++ b/scene/main/viewport.h
@@ -340,6 +340,7 @@ private:
Window *window = nullptr;
RID canvas_item;
Rect2i parent_safe_rect;
+ bool pending_window_update = false;
};
// VRS
diff --git a/scene/resources/resource_format_text.cpp b/scene/resources/resource_format_text.cpp
index 7c13e623c2..90102e44e4 100644
--- a/scene/resources/resource_format_text.cpp
+++ b/scene/resources/resource_format_text.cpp
@@ -191,8 +191,10 @@ Error ResourceLoaderText::_parse_ext_resource(VariantParser::Stream *p_stream, R
}
Ref<PackedScene> ResourceLoaderText::_parse_node_tag(VariantParser::ResourceParser &parser) {
- Ref<PackedScene> packed_scene;
- packed_scene.instantiate();
+ Ref<PackedScene> packed_scene = ResourceLoader::get_resource_ref_override(local_path);
+ if (packed_scene.is_null()) {
+ packed_scene.instantiate();
+ }
while (true) {
if (next_tag.name == "node") {
@@ -664,39 +666,42 @@ Error ResourceLoaderText::load() {
return error;
}
- Ref<Resource> cache = ResourceCache::get_ref(local_path);
- if (cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE && cache.is_valid() && cache->get_class() == res_type) {
- cache->reset_state();
- resource = cache;
- }
-
MissingResource *missing_resource = nullptr;
- if (!resource.is_valid()) {
- Object *obj = ClassDB::instantiate(res_type);
- if (!obj) {
- if (ResourceLoader::is_creating_missing_resources_if_class_unavailable_enabled()) {
- missing_resource = memnew(MissingResource);
- missing_resource->set_original_class(res_type);
- missing_resource->set_recording_properties(true);
- obj = missing_resource;
- } else {
- error_text += "Can't create sub resource of type: " + res_type;
+ resource = ResourceLoader::get_resource_ref_override(local_path);
+ if (resource.is_null()) {
+ Ref<Resource> cache = ResourceCache::get_ref(local_path);
+ if (cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE && cache.is_valid() && cache->get_class() == res_type) {
+ cache->reset_state();
+ resource = cache;
+ }
+
+ if (!resource.is_valid()) {
+ Object *obj = ClassDB::instantiate(res_type);
+ if (!obj) {
+ if (ResourceLoader::is_creating_missing_resources_if_class_unavailable_enabled()) {
+ missing_resource = memnew(MissingResource);
+ missing_resource->set_original_class(res_type);
+ missing_resource->set_recording_properties(true);
+ obj = missing_resource;
+ } else {
+ error_text += "Can't create sub resource of type: " + res_type;
+ _printerr();
+ error = ERR_FILE_CORRUPT;
+ return error;
+ }
+ }
+
+ Resource *r = Object::cast_to<Resource>(obj);
+ if (!r) {
+ error_text += "Can't create sub resource of type, because not a resource: " + res_type;
_printerr();
error = ERR_FILE_CORRUPT;
return error;
}
- }
- Resource *r = Object::cast_to<Resource>(obj);
- if (!r) {
- error_text += "Can't create sub resource of type, because not a resource: " + res_type;
- _printerr();
- error = ERR_FILE_CORRUPT;
- return error;
+ resource = Ref<Resource>(r);
}
-
- resource = Ref<Resource>(r);
}
Dictionary missing_resource_properties;
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 aae32f0b3e..536fc7a04a 100644
--- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp
+++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp
@@ -3585,22 +3585,31 @@ RID RenderForwardClustered::_render_buffers_get_velocity_texture(Ref<RenderScene
}
void RenderForwardClustered::environment_set_ssao_quality(RS::EnvironmentSSAOQuality p_quality, bool p_half_size, float p_adaptive_target, int p_blur_passes, float p_fadeout_from, float p_fadeout_to) {
+ ERR_FAIL_NULL(ss_effects);
+ ERR_FAIL_COND(p_quality < RS::EnvironmentSSAOQuality::ENV_SSAO_QUALITY_VERY_LOW || p_quality > RS::EnvironmentSSAOQuality::ENV_SSAO_QUALITY_ULTRA);
ss_effects->ssao_set_quality(p_quality, p_half_size, p_adaptive_target, p_blur_passes, p_fadeout_from, p_fadeout_to);
}
void RenderForwardClustered::environment_set_ssil_quality(RS::EnvironmentSSILQuality p_quality, bool p_half_size, float p_adaptive_target, int p_blur_passes, float p_fadeout_from, float p_fadeout_to) {
+ ERR_FAIL_NULL(ss_effects);
+ ERR_FAIL_COND(p_quality < RS::EnvironmentSSILQuality::ENV_SSIL_QUALITY_VERY_LOW || p_quality > RS::EnvironmentSSILQuality::ENV_SSIL_QUALITY_ULTRA);
ss_effects->ssil_set_quality(p_quality, p_half_size, p_adaptive_target, p_blur_passes, p_fadeout_from, p_fadeout_to);
}
void RenderForwardClustered::environment_set_ssr_roughness_quality(RS::EnvironmentSSRRoughnessQuality p_quality) {
+ ERR_FAIL_NULL(ss_effects);
+ ERR_FAIL_COND(p_quality < RS::EnvironmentSSRRoughnessQuality::ENV_SSR_ROUGHNESS_QUALITY_DISABLED || p_quality > RS::EnvironmentSSRRoughnessQuality::ENV_SSR_ROUGHNESS_QUALITY_HIGH);
ss_effects->ssr_set_roughness_quality(p_quality);
}
void RenderForwardClustered::sub_surface_scattering_set_quality(RS::SubSurfaceScatteringQuality p_quality) {
+ ERR_FAIL_NULL(ss_effects);
+ ERR_FAIL_COND(p_quality < RS::SubSurfaceScatteringQuality::SUB_SURFACE_SCATTERING_QUALITY_DISABLED || p_quality > RS::SubSurfaceScatteringQuality::SUB_SURFACE_SCATTERING_QUALITY_HIGH);
ss_effects->sss_set_quality(p_quality);
}
void RenderForwardClustered::sub_surface_scattering_set_scale(float p_scale, float p_depth_scale) {
+ ERR_FAIL_NULL(ss_effects);
ss_effects->sss_set_scale(p_scale, p_depth_scale);
}
diff --git a/servers/rendering/renderer_rd/shaders/particles.glsl b/servers/rendering/renderer_rd/shaders/particles.glsl
index 60c49bacae..7c5291038f 100644
--- a/servers/rendering/renderer_rd/shaders/particles.glsl
+++ b/servers/rendering/renderer_rd/shaders/particles.glsl
@@ -292,6 +292,24 @@ void main() {
PARTICLE.velocity = particles.data[src_idx].velocity;
PARTICLE.flags = PARTICLE_FLAG_TRAILED | ((frame_history.data[0].frame & PARTICLE_FRAME_MASK) << PARTICLE_FRAME_SHIFT); //mark it as trailed, save in which frame it will start
PARTICLE.xform = particles.data[src_idx].xform;
+#ifdef USERDATA1_USED
+ PARTICLE.userdata1 = particles.data[src_idx].userdata1;
+#endif
+#ifdef USERDATA2_USED
+ PARTICLE.userdata2 = particles.data[src_idx].userdata2;
+#endif
+#ifdef USERDATA3_USED
+ PARTICLE.userdata3 = particles.data[src_idx].userdata3;
+#endif
+#ifdef USERDATA4_USED
+ PARTICLE.userdata4 = particles.data[src_idx].userdata4;
+#endif
+#ifdef USERDATA5_USED
+ PARTICLE.userdata5 = particles.data[src_idx].userdata5;
+#endif
+#ifdef USERDATA6_USED
+ PARTICLE.userdata6 = particles.data[src_idx].userdata6;
+#endif
}
if (!bool(particles.data[src_idx].flags & PARTICLE_FLAG_ACTIVE)) {
// Disable the entire trail if the parent is no longer active.
diff --git a/servers/rendering/rendering_device_binds.cpp b/servers/rendering/rendering_device_binds.cpp
index 3678b70254..986f01a52c 100644
--- a/servers/rendering/rendering_device_binds.cpp
+++ b/servers/rendering/rendering_device_binds.cpp
@@ -157,9 +157,6 @@ Error RDShaderFile::parse_versions_from_text(const String &p_text, const String
}
}
- Ref<RDShaderFile> shader_file;
- shader_file.instantiate();
-
if (base_error.is_empty()) {
if (stage_found[RD::SHADER_STAGE_COMPUTE] && stages_found > 1) {
ERR_FAIL_V_MSG(ERR_PARSE_ERROR, "When writing compute shaders, [compute] mustbe the only stage present.");
diff --git a/tests/core/variant/test_array.h b/tests/core/variant/test_array.h
index c54854e4d7..787b8f39d9 100644
--- a/tests/core/variant/test_array.h
+++ b/tests/core/variant/test_array.h
@@ -597,6 +597,43 @@ TEST_CASE("[Array] Iteration and modification") {
a4.clear();
}
+TEST_CASE("[Array] Typed copying") {
+ TypedArray<int> a1;
+ a1.push_back(1);
+
+ TypedArray<double> a2;
+ a2.push_back(1.0);
+
+ Array a3 = a1;
+ TypedArray<int> a4 = a3;
+
+ Array a5 = a2;
+ TypedArray<int> a6 = a5;
+
+ a3[0] = 2;
+ a4[0] = 3;
+
+ // Same typed TypedArray should be shared.
+ CHECK_EQ(a1[0], Variant(3));
+ CHECK_EQ(a3[0], Variant(3));
+ CHECK_EQ(a4[0], Variant(3));
+
+ a5[0] = 2.0;
+ a6[0] = 3.0;
+
+ // Different typed TypedArray should not be shared.
+ CHECK_EQ(a2[0], Variant(2.0));
+ CHECK_EQ(a5[0], Variant(2.0));
+ CHECK_EQ(a6[0], Variant(3.0));
+
+ a1.clear();
+ a2.clear();
+ a3.clear();
+ a4.clear();
+ a5.clear();
+ a6.clear();
+}
+
} // namespace TestArray
#endif // TEST_ARRAY_H