diff options
80 files changed, 1004 insertions, 487 deletions
diff --git a/core/math/aabb.h b/core/math/aabb.h index 9a74266ff7..cb358ca7ef 100644 --- a/core/math/aabb.h +++ b/core/math/aabb.h @@ -41,7 +41,7 @@ class Variant; -struct _NO_DISCARD_ AABB { +struct [[nodiscard]] AABB { Vector3 position; Vector3 size; diff --git a/core/math/basis.h b/core/math/basis.h index 918cbc18d4..5c1a5fbdda 100644 --- a/core/math/basis.h +++ b/core/math/basis.h @@ -34,7 +34,7 @@ #include "core/math/quaternion.h" #include "core/math/vector3.h" -struct _NO_DISCARD_ Basis { +struct [[nodiscard]] Basis { Vector3 rows[3] = { Vector3(1, 0, 0), Vector3(0, 1, 0), diff --git a/core/math/color.h b/core/math/color.h index 65d7377c1c..e17b8c9fd7 100644 --- a/core/math/color.h +++ b/core/math/color.h @@ -35,7 +35,7 @@ class String; -struct _NO_DISCARD_ Color { +struct [[nodiscard]] Color { union { struct { float r; diff --git a/core/math/face3.h b/core/math/face3.h index 3dd47d0226..519dcb6414 100644 --- a/core/math/face3.h +++ b/core/math/face3.h @@ -36,7 +36,7 @@ #include "core/math/transform_3d.h" #include "core/math/vector3.h" -struct _NO_DISCARD_ Face3 { +struct [[nodiscard]] Face3 { enum Side { SIDE_OVER, SIDE_UNDER, diff --git a/core/math/plane.h b/core/math/plane.h index 8159f25342..6529fea60a 100644 --- a/core/math/plane.h +++ b/core/math/plane.h @@ -35,7 +35,7 @@ class Variant; -struct _NO_DISCARD_ Plane { +struct [[nodiscard]] Plane { Vector3 normal; real_t d = 0; diff --git a/core/math/projection.h b/core/math/projection.h index f3ed9d7b1c..5af43561c0 100644 --- a/core/math/projection.h +++ b/core/math/projection.h @@ -43,7 +43,7 @@ struct Rect2; struct Transform3D; struct Vector2; -struct _NO_DISCARD_ Projection { +struct [[nodiscard]] Projection { enum Planes { PLANE_NEAR, PLANE_FAR, diff --git a/core/math/quaternion.h b/core/math/quaternion.h index 868a2916f5..655e55e0a2 100644 --- a/core/math/quaternion.h +++ b/core/math/quaternion.h @@ -35,7 +35,7 @@ #include "core/math/vector3.h" #include "core/string/ustring.h" -struct _NO_DISCARD_ Quaternion { +struct [[nodiscard]] Quaternion { union { struct { real_t x; diff --git a/core/math/rect2.h b/core/math/rect2.h index b4069ae86a..9cb341b689 100644 --- a/core/math/rect2.h +++ b/core/math/rect2.h @@ -38,7 +38,7 @@ class String; struct Rect2i; struct Transform2D; -struct _NO_DISCARD_ Rect2 { +struct [[nodiscard]] Rect2 { Point2 position; Size2 size; diff --git a/core/math/rect2i.h b/core/math/rect2i.h index a1338da0bb..5f3a3d54f5 100644 --- a/core/math/rect2i.h +++ b/core/math/rect2i.h @@ -37,7 +37,7 @@ class String; struct Rect2; -struct _NO_DISCARD_ Rect2i { +struct [[nodiscard]] Rect2i { Point2i position; Size2i size; diff --git a/core/math/transform_2d.h b/core/math/transform_2d.h index 4ec2dc119c..476577508f 100644 --- a/core/math/transform_2d.h +++ b/core/math/transform_2d.h @@ -38,7 +38,7 @@ class String; -struct _NO_DISCARD_ Transform2D { +struct [[nodiscard]] Transform2D { // Warning #1: basis of Transform2D is stored differently from Basis. In terms of columns array, the basis matrix looks like "on paper": // M = (columns[0][0] columns[1][0]) // (columns[0][1] columns[1][1]) diff --git a/core/math/transform_3d.h b/core/math/transform_3d.h index 7d89b86c75..b1de233445 100644 --- a/core/math/transform_3d.h +++ b/core/math/transform_3d.h @@ -36,7 +36,7 @@ #include "core/math/plane.h" #include "core/templates/vector.h" -struct _NO_DISCARD_ Transform3D { +struct [[nodiscard]] Transform3D { Basis basis; Vector3 origin; diff --git a/core/math/vector2.h b/core/math/vector2.h index 8851942cdd..edb47db6fd 100644 --- a/core/math/vector2.h +++ b/core/math/vector2.h @@ -37,7 +37,7 @@ class String; struct Vector2i; -struct _NO_DISCARD_ Vector2 { +struct [[nodiscard]] Vector2 { static const int AXIS_COUNT = 2; enum Axis { diff --git a/core/math/vector2i.h b/core/math/vector2i.h index aca9ae8272..fff9b0a658 100644 --- a/core/math/vector2i.h +++ b/core/math/vector2i.h @@ -37,7 +37,7 @@ class String; struct Vector2; -struct _NO_DISCARD_ Vector2i { +struct [[nodiscard]] Vector2i { static const int AXIS_COUNT = 2; enum Axis { diff --git a/core/math/vector3.h b/core/math/vector3.h index 2313eb557a..14bc44c4e7 100644 --- a/core/math/vector3.h +++ b/core/math/vector3.h @@ -39,7 +39,7 @@ struct Basis; struct Vector2; struct Vector3i; -struct _NO_DISCARD_ Vector3 { +struct [[nodiscard]] Vector3 { static const int AXIS_COUNT = 3; enum Axis { diff --git a/core/math/vector3i.h b/core/math/vector3i.h index 035cfcf9e2..40d0700bf7 100644 --- a/core/math/vector3i.h +++ b/core/math/vector3i.h @@ -37,7 +37,7 @@ class String; struct Vector3; -struct _NO_DISCARD_ Vector3i { +struct [[nodiscard]] Vector3i { static const int AXIS_COUNT = 3; enum Axis { diff --git a/core/math/vector4.h b/core/math/vector4.h index f69b4752bb..8632f69f57 100644 --- a/core/math/vector4.h +++ b/core/math/vector4.h @@ -38,7 +38,7 @@ class String; struct Vector4i; -struct _NO_DISCARD_ Vector4 { +struct [[nodiscard]] Vector4 { static const int AXIS_COUNT = 4; enum Axis { diff --git a/core/math/vector4i.h b/core/math/vector4i.h index 8a9c580bc1..a9036d684a 100644 --- a/core/math/vector4i.h +++ b/core/math/vector4i.h @@ -37,7 +37,7 @@ class String; struct Vector4; -struct _NO_DISCARD_ Vector4i { +struct [[nodiscard]] Vector4i { static const int AXIS_COUNT = 4; enum Axis { 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 fa7f23ded0..40d8601af3 100644 --- a/core/os/os.cpp +++ b/core/os/os.cpp @@ -513,6 +513,10 @@ bool OS::has_feature(const String &p_feature) { if (p_feature == "threads") { return true; } +#else + if (p_feature == "nothreads") { + return true; + } #endif if (_check_internal_feature_support(p_feature)) { diff --git a/core/typedefs.h b/core/typedefs.h index 2b90a911cd..0de803293d 100644 --- a/core/typedefs.h +++ b/core/typedefs.h @@ -71,12 +71,7 @@ #endif #endif -// No discard allows the compiler to flag warnings if we don't use the return value of functions / classes -#ifndef _NO_DISCARD_ -#define _NO_DISCARD_ [[nodiscard]] -#endif - -// In some cases _NO_DISCARD_ will get false positives, +// In some cases [[nodiscard]] will get false positives, // we can prevent the warning in specific cases by preceding the call with a cast. #ifndef _ALLOW_DISCARD_ #define _ALLOW_DISCARD_ (void) diff --git a/doc/classes/AudioStreamWAV.xml b/doc/classes/AudioStreamWAV.xml index 3df814cb7f..8a28514ed6 100644 --- a/doc/classes/AudioStreamWAV.xml +++ b/doc/classes/AudioStreamWAV.xml @@ -29,10 +29,10 @@ Audio format. See [enum Format] constants for values. </member> <member name="loop_begin" type="int" setter="set_loop_begin" getter="get_loop_begin" default="0"> - The loop start point (in number of samples, relative to the beginning of the sample). This information will be imported automatically from the WAV file if present. + The loop start point (in number of samples, relative to the beginning of the stream). This information will be imported automatically from the WAV file if present. </member> <member name="loop_end" type="int" setter="set_loop_end" getter="get_loop_end" default="0"> - The loop end point (in number of samples, relative to the beginning of the sample). This information will be imported automatically from the WAV file if present. + The loop end point (in number of samples, relative to the beginning of the stream). This information will be imported automatically from the WAV file if present. </member> <member name="loop_mode" type="int" setter="set_loop_mode" getter="get_loop_mode" enum="AudioStreamWAV.LoopMode" default="0"> The loop mode. This information will be imported automatically from the WAV file if present. See [enum LoopMode] constants for values. diff --git a/doc/classes/EditorSettings.xml b/doc/classes/EditorSettings.xml index aa1ac566ef..c5737b235f 100644 --- a/doc/classes/EditorSettings.xml +++ b/doc/classes/EditorSettings.xml @@ -1043,6 +1043,9 @@ The indentation style to use (tabs or spaces). [b]Note:[/b] The [url=$DOCS_URL/tutorials/scripting/gdscript/gdscript_styleguide.html]GDScript style guide[/url] recommends using tabs for indentation. It is advised to change this setting only if you need to work on a project that currently uses spaces for indentation. </member> + <member name="text_editor/behavior/navigation/custom_word_separators" type="String" setter="" getter=""> + The characters to consider as word delimiters if [member text_editor/behavior/navigation/use_custom_word_separators] is [code]true[/code]. The characters should be defined without separation, for example [code]#_![/code]. + </member> <member name="text_editor/behavior/navigation/drag_and_drop_selection" type="bool" setter="" getter=""> If [code]true[/code], allows drag-and-dropping text in the script editor to move text. Disable this if you find yourself accidentally drag-and-dropping text in the script editor. </member> @@ -1062,6 +1065,12 @@ <member name="text_editor/behavior/navigation/stay_in_script_editor_on_node_selected" type="bool" setter="" getter=""> If [code]true[/code], prevents automatically switching between the Script and 2D/3D screens when selecting a node in the Scene tree dock. </member> + <member name="text_editor/behavior/navigation/use_custom_word_separators" type="bool" setter="" getter=""> + If [code]false[/code], using [kbd]Ctrl + Left[/kbd] or [kbd]Ctrl + Right[/kbd] ([kbd]Cmd + Left[/kbd] or [kbd]Cmd + Right[/kbd] on macOS) bindings will use the behavior of [member text_editor/behavior/navigation/use_default_word_separators]. If [code]true[/code], it will also stop the caret if a character within [member text_editor/behavior/navigation/custom_word_separators] is detected. Useful for subword moving. This behavior also will be applied to the behavior of text selection. + </member> + <member name="text_editor/behavior/navigation/use_default_word_separators" type="bool" setter="" getter=""> + If [code]false[/code], using [kbd]Ctrl + Left[/kbd] or [kbd]Ctrl + Right[/kbd] ([kbd]Cmd + Left[/kbd] or [kbd]Cmd + Right[/kbd] on macOS) bindings will stop moving caret only if a space or punctuation is detected. If [code]true[/code], it will also stop the caret if a character is [code]´`~$^=+|<>[/code], a General Punctuation, or CJK Punctuation. Useful for subword moving. This behavior also will be applied to the behavior of text selection. + </member> <member name="text_editor/behavior/navigation/v_scroll_speed" type="int" setter="" getter=""> The number of pixels to scroll with every mouse wheel increment. Higher values make the script scroll by faster when using the mouse wheel. [b]Note:[/b] You can hold down [kbd]Alt[/kbd] while using the mouse wheel to temporarily scroll 5 times faster. 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/InputEvent.xml b/doc/classes/InputEvent.xml index 6f2e6aac20..a970f63c6e 100644 --- a/doc/classes/InputEvent.xml +++ b/doc/classes/InputEvent.xml @@ -77,10 +77,11 @@ Returns [code]true[/code] if this input event has been canceled. </description> </method> - <method name="is_echo" qualifiers="const"> + <method name="is_echo" qualifiers="const" keywords="is_repeat"> <return type="bool" /> <description> - Returns [code]true[/code] if this input event is an echo event (only for events of type [InputEventKey]). Any other event type returns [code]false[/code]. + Returns [code]true[/code] if this input event is an echo event (only for events of type [InputEventKey]). An echo event is a repeated key event sent when the user is holding down the key. Any other event type returns [code]false[/code]. + [b]Note:[/b] The rate at which echo events are sent is typically around 20 events per second (after holding down the key for roughly half a second). However, the key repeat delay/speed can be changed by the user or disabled entirely in the operating system settings. To ensure your project works correctly on all configurations, do not assume the user has a specific key repeat configuration in your project's behavior. </description> </method> <method name="is_match" qualifiers="const"> diff --git a/doc/classes/InputEventKey.xml b/doc/classes/InputEventKey.xml index dc4872ba03..69c447ba01 100644 --- a/doc/classes/InputEventKey.xml +++ b/doc/classes/InputEventKey.xml @@ -59,8 +59,9 @@ </method> </methods> <members> - <member name="echo" type="bool" setter="set_echo" getter="is_echo" default="false"> - If [code]true[/code], the key was already pressed before this event. It means the user is holding the key down. + <member name="echo" type="bool" setter="set_echo" getter="is_echo" default="false" keywords="repeat"> + If [code]true[/code], the key was already pressed before this event. An echo event is a repeated key event sent when the user is holding down the key. + [b]Note:[/b] The rate at which echo events are sent is typically around 20 events per second (after holding down the key for roughly half a second). However, the key repeat delay/speed can be changed by the user or disabled entirely in the operating system settings. To ensure your project works correctly on all configurations, do not assume the user has a specific key repeat configuration in your project's behavior. </member> <member name="key_label" type="int" setter="set_key_label" getter="get_key_label" enum="Key" default="0"> Represents the localized label printed on the key in the current keyboard layout, which corresponds to one of the [enum Key] constants or any valid Unicode character. 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]>= 0[/code]). </description> <tutorials> </tutorials> 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/doc/classes/TextEdit.xml b/doc/classes/TextEdit.xml index 75cad4d08b..f82671b533 100644 --- a/doc/classes/TextEdit.xml +++ b/doc/classes/TextEdit.xml @@ -1278,6 +1278,9 @@ <member name="context_menu_enabled" type="bool" setter="set_context_menu_enabled" getter="is_context_menu_enabled" default="true"> If [code]true[/code], a right-click displays the context menu. </member> + <member name="custom_word_separators" type="String" setter="set_custom_word_separators" getter="get_custom_word_separators" default=""""> + The characters to consider as word delimiters if [member use_custom_word_separators] is [code]true[/code]. The characters should be defined without separation, for example [code]#_![/code]. + </member> <member name="deselect_on_focus_loss_enabled" type="bool" setter="set_deselect_on_focus_loss_enabled" getter="is_deselect_on_focus_loss_enabled" default="true"> If [code]true[/code], the selected text will be deselected when focus is lost. </member> @@ -1363,6 +1366,12 @@ <member name="text_direction" type="int" setter="set_text_direction" getter="get_text_direction" enum="Control.TextDirection" default="0"> Base text writing direction. </member> + <member name="use_custom_word_separators" type="bool" setter="set_use_custom_word_separators" getter="is_custom_word_separators_enabled" default="false"> + If [code]false[/code], using [kbd]Ctrl + Left[/kbd] or [kbd]Ctrl + Right[/kbd] ([kbd]Cmd + Left[/kbd] or [kbd]Cmd + Right[/kbd] on macOS) bindings will use the behavior of [member use_default_word_separators]. If [code]true[/code], it will also stop the caret if a character within [member custom_word_separators] is detected. Useful for subword moving. This behavior also will be applied to the behavior of text selection. + </member> + <member name="use_default_word_separators" type="bool" setter="set_use_default_word_separators" getter="is_default_word_separators_enabled" default="true"> + If [code]false[/code], using [kbd]Ctrl + Left[/kbd] or [kbd]Ctrl + Right[/kbd] ([kbd]Cmd + Left[/kbd] or [kbd]Cmd + Right[/kbd] on macOS) bindings will stop moving caret only if a space or punctuation is detected. If [code]true[/code], it will also stop the caret if a character is [code]´`~$^=+|<>[/code], a General Punctuation, or CJK Punctuation. Useful for subword moving. This behavior also will be applied to the behavior of text selection. + </member> <member name="virtual_keyboard_enabled" type="bool" setter="set_virtual_keyboard_enabled" getter="is_virtual_keyboard_enabled" default="true"> If [code]true[/code], the native virtual keyboard is shown when focused on platforms that support it. </member> 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/gles3/rasterizer_gles3.cpp b/drivers/gles3/rasterizer_gles3.cpp index ae39c86d44..5ff95391a2 100644 --- a/drivers/gles3/rasterizer_gles3.cpp +++ b/drivers/gles3/rasterizer_gles3.cpp @@ -72,7 +72,7 @@ #if !defined(IOS_ENABLED) && !defined(WEB_ENABLED) // We include EGL below to get debug callback on GLES2 platforms, -// but EGL is not available on iOS. +// but EGL is not available on iOS or the web. #define CAN_DEBUG #endif diff --git a/drivers/gles3/rasterizer_scene_gles3.cpp b/drivers/gles3/rasterizer_scene_gles3.cpp index 9ea030bbd4..8e89889fd1 100644 --- a/drivers/gles3/rasterizer_scene_gles3.cpp +++ b/drivers/gles3/rasterizer_scene_gles3.cpp @@ -2247,7 +2247,6 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_ bool glow_enabled = false; if (p_environment.is_valid()) { glow_enabled = environment_get_glow_enabled(p_environment); - rb->ensure_internal_buffers(); // Ensure our intermediate buffer is available if glow is enabled if (glow_enabled) { // If glow is enabled, we apply tonemapping etc. in post, so disable it during rendering apply_color_adjustments_in_post = true; @@ -2339,7 +2338,6 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_ if (render_data.environment.is_valid()) { bool use_bcs = environment_get_adjustments_enabled(render_data.environment); if (use_bcs) { - rb->ensure_internal_buffers(); apply_color_adjustments_in_post = true; } @@ -2473,6 +2471,7 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_ if (is_reflection_probe) { fbo = GLES3::LightStorage::get_singleton()->reflection_probe_instance_get_framebuffer(render_data.reflection_probe, render_data.reflection_probe_pass); } else { + rb->set_apply_color_adjustments_in_post(apply_color_adjustments_in_post); fbo = rb->get_render_fbo(); } diff --git a/drivers/gles3/storage/config.cpp b/drivers/gles3/storage/config.cpp index 1a14902c7c..a28b050bf8 100644 --- a/drivers/gles3/storage/config.cpp +++ b/drivers/gles3/storage/config.cpp @@ -35,6 +35,10 @@ #include "../rasterizer_gles3.h" #include "texture_storage.h" +#ifdef WEB_ENABLED +#include <emscripten/html5_webgl.h> +#endif + using namespace GLES3; #define _GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF @@ -44,6 +48,23 @@ Config *Config::singleton = nullptr; Config::Config() { singleton = this; +#ifdef WEB_ENABLED + // Starting with Emscripten 3.1.51, glGetStringi(GL_EXTENSIONS, i) will only ever return + // a fixed list of extensions, regardless of what additional extensions are enabled. This + // isn't very useful for us in determining which extensions we can rely on here. So, instead + // we use emscripten_webgl_get_supported_extensions() to get all supported extensions, which + // is what Emscripten 3.1.50 and earlier do. + { + char *extension_array_string = emscripten_webgl_get_supported_extensions(); + PackedStringArray extension_array = String((const char *)extension_array_string).split(" "); + extensions.reserve(extension_array.size() * 2); + for (const String &s : extension_array) { + extensions.insert(s); + extensions.insert("GL_" + s); + } + free(extension_array_string); + } +#else { GLint max_extensions = 0; glGetIntegerv(GL_NUM_EXTENSIONS, &max_extensions); @@ -55,6 +76,7 @@ Config::Config() { extensions.insert((const char *)s); } } +#endif bptc_supported = extensions.has("GL_ARB_texture_compression_bptc") || extensions.has("EXT_texture_compression_bptc"); astc_supported = extensions.has("GL_KHR_texture_compression_astc") || extensions.has("GL_OES_texture_compression_astc") || extensions.has("GL_KHR_texture_compression_astc_ldr") || extensions.has("GL_KHR_texture_compression_astc_hdr"); diff --git a/drivers/gles3/storage/render_scene_buffers_gles3.cpp b/drivers/gles3/storage/render_scene_buffers_gles3.cpp index e4f1a01f68..c91547d2b1 100644 --- a/drivers/gles3/storage/render_scene_buffers_gles3.cpp +++ b/drivers/gles3/storage/render_scene_buffers_gles3.cpp @@ -194,7 +194,7 @@ void RenderSceneBuffersGLES3::_check_render_buffers() { ERR_FAIL_COND(view_count == 0); - bool use_internal_buffer = scaling_3d_mode != RS::VIEWPORT_SCALING_3D_MODE_OFF || needs_internal_buffers; + bool use_internal_buffer = scaling_3d_mode != RS::VIEWPORT_SCALING_3D_MODE_OFF || apply_color_adjustments_in_post; uint32_t depth_format_size = 3; bool use_multiview = view_count > 1; @@ -558,8 +558,8 @@ void RenderSceneBuffersGLES3::_clear_back_buffers() { } } -void RenderSceneBuffersGLES3::ensure_internal_buffers() { - needs_internal_buffers = true; +void RenderSceneBuffersGLES3::set_apply_color_adjustments_in_post(bool p_apply_in_post) { + apply_color_adjustments_in_post = p_apply_in_post; } void RenderSceneBuffersGLES3::check_glow_buffers() { diff --git a/drivers/gles3/storage/render_scene_buffers_gles3.h b/drivers/gles3/storage/render_scene_buffers_gles3.h index 8273c18b8e..a7a676ad33 100644 --- a/drivers/gles3/storage/render_scene_buffers_gles3.h +++ b/drivers/gles3/storage/render_scene_buffers_gles3.h @@ -50,7 +50,7 @@ public: //bool use_taa = false; //bool use_debanding = false; uint32_t view_count = 1; - bool needs_internal_buffers = false; + bool apply_color_adjustments_in_post = false; RID render_target; @@ -106,12 +106,12 @@ public: virtual void set_fsr_sharpness(float p_fsr_sharpness) override{}; virtual void set_texture_mipmap_bias(float p_texture_mipmap_bias) override{}; virtual void set_use_debanding(bool p_use_debanding) override{}; + void set_apply_color_adjustments_in_post(bool p_apply_in_post); void free_render_buffer_data(); void check_backbuffer(bool p_need_color, bool p_need_depth); // Check if we need to initialize our backbuffer. void check_glow_buffers(); // Check if we need to initialize our glow buffers. - void ensure_internal_buffers(); GLuint get_render_fbo(); GLuint get_msaa3d_fbo() { 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/code_editor.cpp b/editor/code_editor.cpp index 595aff3bd0..7e07e626b1 100644 --- a/editor/code_editor.cpp +++ b/editor/code_editor.cpp @@ -1049,6 +1049,9 @@ void CodeTextEditor::update_editor_settings() { text_editor->set_smooth_scroll_enabled(EDITOR_GET("text_editor/behavior/navigation/smooth_scrolling")); text_editor->set_v_scroll_speed(EDITOR_GET("text_editor/behavior/navigation/v_scroll_speed")); text_editor->set_drag_and_drop_selection_enabled(EDITOR_GET("text_editor/behavior/navigation/drag_and_drop_selection")); + text_editor->set_use_default_word_separators(EDITOR_GET("text_editor/behavior/navigation/use_default_word_separators")); + text_editor->set_use_custom_word_separators(EDITOR_GET("text_editor/behavior/navigation/use_custom_word_separators")); + text_editor->set_custom_word_separators(EDITOR_GET("text_editor/behavior/navigation/custom_word_separators")); // Behavior: Indent set_indent_using_spaces(EDITOR_GET("text_editor/behavior/indent/type")); 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_audio_buses.cpp b/editor/editor_audio_buses.cpp index 74f911f07d..3b337997e0 100644 --- a/editor/editor_audio_buses.cpp +++ b/editor/editor_audio_buses.cpp @@ -799,6 +799,7 @@ EditorAudioBus::EditorAudioBus(EditorAudioBuses *p_buses, bool p_is_master) { set_tooltip_text(TTR("Drag & drop to rearrange.")); VBoxContainer *vb = memnew(VBoxContainer); + vb->add_theme_constant_override("separation", 4 * EDSCALE); add_child(vb); set_v_size_flags(SIZE_EXPAND_FILL); @@ -854,8 +855,17 @@ EditorAudioBus::EditorAudioBus(EditorAudioBuses *p_buses, bool p_is_master) { separator->set_mouse_filter(MOUSE_FILTER_PASS); vb->add_child(separator); + Control *spacer_top = memnew(Control); + spacer_top->set_custom_minimum_size(Size2(0, 6 * EDSCALE)); + vb->add_child(spacer_top); + HBoxContainer *hb = memnew(HBoxContainer); vb->add_child(hb); + + Control *spacer_bottom = memnew(Control); + spacer_bottom->set_custom_minimum_size(Size2(0, 2 * EDSCALE)); + vb->add_child(spacer_bottom); + slider = memnew(VSlider); slider->set_min(0.0); slider->set_max(1.0); diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp index 6c26231a4b..81d9510033 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 diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 1560fe5636..ae7066fea9 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -679,6 +679,8 @@ void EditorNode::_notification(int p_what) { } break; case NOTIFICATION_EXIT_TREE: { + singleton->active_plugins.clear(); + if (progress_dialog) { progress_dialog->queue_free(); } @@ -1735,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; @@ -1781,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; @@ -1810,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); @@ -2726,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. @@ -6446,6 +6456,7 @@ EditorNode::EditorNode() { ED_SHORTCUT("editor/ungroup_selected_nodes", TTR("Ungroup Selected Node(s)"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::G); // Used in the GPUParticles/CPUParticles 2D/3D editor plugins. + // The shortcut is Ctrl + R even on macOS, as Cmd + R is used to run the current scene on macOS. ED_SHORTCUT("particles/restart_emission", TTR("Restart Emission"), KeyModifierMask::CTRL | Key::R); FileAccess::set_backup_save(EDITOR_GET("filesystem/on_save/safe_save_on_backup_then_rename")); 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_settings.cpp b/editor/editor_settings.cpp index a431cf93ba..e021be9668 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -639,6 +639,9 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) { _initial_set("text_editor/behavior/navigation/drag_and_drop_selection", true); _initial_set("text_editor/behavior/navigation/stay_in_script_editor_on_node_selected", true); _initial_set("text_editor/behavior/navigation/open_script_when_connecting_signal_to_existing_method", true); + _initial_set("text_editor/behavior/navigation/use_default_word_separators", true); // Includes ´`~$^=+|<> General punctuation and CJK punctuation. + _initial_set("text_editor/behavior/navigation/use_custom_word_separators", false); + _initial_set("text_editor/behavior/navigation/custom_word_separators", ""); // Custom word separators. // Behavior: Indent EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "text_editor/behavior/indent/type", 0, "Tabs,Spaces") 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/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/editor_plugin.cpp b/editor/plugins/editor_plugin.cpp index 2b3c187352..005407e188 100644 --- a/editor/plugins/editor_plugin.cpp +++ b/editor/plugins/editor_plugin.cpp @@ -420,7 +420,9 @@ void EditorPlugin::add_import_plugin(const Ref<EditorImportPlugin> &p_importer, void EditorPlugin::remove_import_plugin(const Ref<EditorImportPlugin> &p_importer) { ERR_FAIL_COND(!p_importer.is_valid()); ResourceFormatImporter::get_singleton()->remove_importer(p_importer); - callable_mp(EditorFileSystem::get_singleton(), &EditorFileSystem::scan).call_deferred(); + if (!EditorNode::get_singleton()->is_exiting()) { + callable_mp(EditorFileSystem::get_singleton(), &EditorFileSystem::scan).call_deferred(); + } } void EditorPlugin::add_export_plugin(const Ref<EditorExportPlugin> &p_exporter) { diff --git a/editor/plugins/packed_scene_translation_parser_plugin.cpp b/editor/plugins/packed_scene_translation_parser_plugin.cpp index e266a3241b..118cba53d2 100644 --- a/editor/plugins/packed_scene_translation_parser_plugin.cpp +++ b/editor/plugins/packed_scene_translation_parser_plugin.cpp @@ -200,6 +200,7 @@ PackedSceneEditorTranslationParserPlugin::PackedSceneEditorTranslationParserPlug lookup_properties.insert("title"); lookup_properties.insert("filters"); lookup_properties.insert("script"); + lookup_properties.insert("item_*/text"); // Exception list (to prevent false positives). exception_list.insert("LineEdit", { "text" }); 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/misc/scripts/godot_gdb_pretty_print.py b/misc/utility/godot_gdb_pretty_print.py index 932831d24e..7edd668016 100755..100644 --- a/misc/scripts/godot_gdb_pretty_print.py +++ b/misc/utility/godot_gdb_pretty_print.py @@ -1,28 +1,30 @@ -#!/usr/bin/env python3 -# Load this file to your GDB session to enable pretty-printing -# of some Godot C++ types. -# GDB command: source misc/scripts/godot_gdb_pretty_print.py -# -# To load these automatically in Visual Studio Code, -# add the source command to the setupCommands of your configuration -# in launch.json. -# "setupCommands": [ -# ... -# { -# "description": "Load custom pretty-printers for Godot types.", -# "text": "source ${workspaceRoot}/misc/scripts/godot_gdb_pretty_print.py" -# } -# ] -# Other UI:s that use GDB under the hood are likely to have their own ways to achieve this. -# -# To debug this script it's easiest to use the interactive python from a command-line -# GDB session. Stop at a breakpoint, then use -# python-interactive to enter the python shell and -# acquire a Value object using gdb.selected_frame().read_var("variable name"). -# From there you can figure out how to print it nicely. +""" +Load this file to your GDB session to enable pretty-printing of some Godot C++ types. + +GDB command: `source misc/utility/godot_gdb_pretty_print.py`. + +To load these automatically in Visual Studio Code, add the source command to +the `setupCommands` of your configuration in `launch.json`: +```json +"setupCommands": [ +... + { + "description": "Load custom pretty-printers for Godot types.", + "text": "source ${workspaceFolder}/misc/utility/godot_gdb_pretty_print.py" + } +] +``` +Other UIs that use GDB under the hood are likely to have their own ways to achieve this. + +To debug this script it's easiest to use the interactive python from a command-line +GDB session. Stop at a breakpoint, then use python-interactive to enter the python shell +and acquire a `Value` object using `gdb.selected_frame().read_var("variable name")`. +From there you can figure out how to print it nicely. +""" + import re -import gdb +import gdb # type: ignore # Printer for Godot StringName variables. diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index aa26bb222d..c60672d3f4 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -5527,6 +5527,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/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/native/library_godot_webxr.js b/modules/webxr/native/library_godot_webxr.js index 031530a047..581da4d56f 100644 --- a/modules/webxr/native/library_godot_webxr.js +++ b/modules/webxr/native/library_godot_webxr.js @@ -320,7 +320,8 @@ 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(',')); + const enabled_features = 'enabledFeatures' in session ? Array.from(session.enabledFeatures) : []; + const enabled_features_c_str = GodotRuntime.allocString(enabled_features.join(',')); onstarted(reference_space_c_str, enabled_features_c_str); GodotRuntime.free(reference_space_c_str); GodotRuntime.free(enabled_features_c_str); diff --git a/modules/webxr/native/webxr.externs.js b/modules/webxr/native/webxr.externs.js index 35ad33fa93..80a7f8d2de 100644 --- a/modules/webxr/native/webxr.externs.js +++ b/modules/webxr/native/webxr.externs.js @@ -78,6 +78,11 @@ XRSession.prototype.frameRate; XRSession.prototype.supportedFrameRates; /** + * @type {Array<string>} + */ +XRSession.prototype.enabledFeatures; + +/** * @type {?function (Event)} */ XRSession.prototype.onend; diff --git a/modules/webxr/webxr_interface_js.cpp b/modules/webxr/webxr_interface_js.cpp index 916566fc1b..6c18b650ee 100644 --- a/modules/webxr/webxr_interface_js.cpp +++ b/modules/webxr/webxr_interface_js.cpp @@ -740,12 +740,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/platform/web/detect.py b/platform/web/detect.py index 1b15ff8e2e..cb4dac1125 100644 --- a/platform/web/detect.py +++ b/platform/web/detect.py @@ -207,11 +207,10 @@ def configure(env: "SConsEnvironment"): env.Append(LINKFLAGS=["-sMAX_WEBGL_VERSION=2"]) # Allow use to take control of swapping WebGL buffers. env.Append(LINKFLAGS=["-sOFFSCREEN_FRAMEBUFFER=1"]) - # Breaking change since emscripten 3.1.51 - # https://github.com/emscripten-core/emscripten/blob/main/ChangeLog.md#3151---121323 + # Disables the use of *glGetProcAddress() which is inefficient. + # See https://emscripten.org/docs/tools_reference/settings_reference.html#gl-enable-get-proc-address if cc_semver >= (3, 1, 51): - # Enables the use of *glGetProcAddress() - env.Append(LINKFLAGS=["-sGL_ENABLE_GET_PROC_ADDRESS=1"]) + env.Append(LINKFLAGS=["-sGL_ENABLE_GET_PROC_ADDRESS=0"]) if env["javascript_eval"]: env.Append(CPPDEFINES=["JAVASCRIPT_EVAL_ENABLED"]) 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/platform/web/platform_gl.h b/platform/web/platform_gl.h index be6e1462a7..8aadab81de 100644 --- a/platform/web/platform_gl.h +++ b/platform/web/platform_gl.h @@ -35,6 +35,10 @@ #define GLES_API_ENABLED // Allow using GLES. #endif +// Make using *glGetProcAddress() an error on the web. +#define glGetProcAddress(n) static_assert(false, "Usage of glGetProcessAddress() on the web is a bug.") +#define eglGetProcAddress(n) static_assert(false, "Usage of eglGetProcessAddress() on the web is a bug.") + #include "platform/web/godot_webgl2.h" #endif // PLATFORM_GL_H 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 21e82adf47..b5263add48 100644 --- a/scene/3d/skeleton_3d.cpp +++ b/scene/3d/skeleton_3d.cpp @@ -308,6 +308,7 @@ void Skeleton3D::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { _process_changed(); + _make_dirty(); _make_modifiers_dirty(); force_update_all_dirty_bones(); #ifndef DISABLE_DEPRECATED @@ -333,6 +334,13 @@ void Skeleton3D::_notification(int p_what) { _process_modifiers(); } + // Abort if pose is not changed. + if (!(update_flags & UPDATE_FLAG_POSE)) { + updating = false; + update_flags = UPDATE_FLAG_NONE; + return; + } + emit_signal(SceneStringName(skeleton_updated)); // Update skins. @@ -400,13 +408,13 @@ void Skeleton3D::_notification(int p_what) { } updating = false; - is_update_needed = false; + update_flags = UPDATE_FLAG_NONE; } break; case NOTIFICATION_INTERNAL_PROCESS: case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { _find_modifiers(); if (!modifiers.is_empty()) { - _update_deferred(); + _update_deferred(UPDATE_FLAG_MODIFIER); } } break; } @@ -436,7 +444,7 @@ void Skeleton3D::_process_changed() { void Skeleton3D::_make_modifiers_dirty() { modifiers_dirty = true; - _update_deferred(); + _update_deferred(UPDATE_FLAG_MODIFIER); } Transform3D Skeleton3D::get_bone_global_pose(int p_bone) const { @@ -745,10 +753,12 @@ void Skeleton3D::_make_dirty() { _update_deferred(); } -void Skeleton3D::_update_deferred() { - if (!is_update_needed && !updating && is_inside_tree()) { - is_update_needed = true; - notify_deferred_thread_group(NOTIFICATION_UPDATE_SKELETON); // It must never be called more than once in a single frame. +void Skeleton3D::_update_deferred(UpdateFlag p_update_flag) { + if (is_inside_tree()) { + if (update_flags == UPDATE_FLAG_NONE && !updating) { + notify_deferred_thread_group(NOTIFICATION_UPDATE_SKELETON); // It must never be called more than once in a single frame. + } + update_flags |= p_update_flag; } } @@ -1130,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 { @@ -1138,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() { @@ -1147,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 b8e38242b9..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 @@ -80,8 +81,14 @@ public: private: friend class SkinReference; - void _update_deferred(); - bool is_update_needed = false; // Is updating reserved? + enum UpdateFlag { + UPDATE_FLAG_NONE = 1, + UPDATE_FLAG_MODIFIER = 2, + UPDATE_FLAG_POSE = 4, + }; + + void _update_deferred(UpdateFlag p_update_flag = UPDATE_FLAG_POSE); + uint8_t update_flags = UPDATE_FLAG_NONE; bool updating = false; // Is updating now? struct Bone { 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/animation/animation_mixer.cpp b/scene/animation/animation_mixer.cpp index 1c4a5ff20e..c74348c2e7 100644 --- a/scene/animation/animation_mixer.cpp +++ b/scene/animation/animation_mixer.cpp @@ -150,6 +150,7 @@ void AnimationMixer::_animation_set_cache_update() { ad.name = key; ad.last_update = animation_set_update_pass; animation_set.insert(ad.name, ad); + cache_valid = false; // No need to delete the cache, but it must be updated to add track caches. } else { AnimationData &ad = animation_set[key]; if (ad.last_update != animation_set_update_pass) { diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index 9b7c6249e6..233c7200ff 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -207,6 +207,8 @@ void TextEdit::Text::invalidate_cache(int p_line, int p_column, bool p_text_chan text.write[p_line].data_buf->set_direction((TextServer::Direction)direction); text.write[p_line].data_buf->set_break_flags(flags); text.write[p_line].data_buf->set_preserve_control(draw_control_chars); + text.write[p_line].data_buf->set_custom_punctuation(get_enabled_word_separators()); + if (p_ime_text.length() > 0) { if (p_text_changed) { text.write[p_line].data_buf->add_string(p_ime_text, font, font_size, language); @@ -275,6 +277,8 @@ void TextEdit::Text::invalidate_all_lines() { } text.write[i].data_buf->set_width(width); text.write[i].data_buf->set_break_flags(flags); + text.write[i].data_buf->set_custom_punctuation(get_enabled_word_separators()); + if (tab_size_dirty) { if (tab_size > 0) { Vector<float> tabs; @@ -439,6 +443,57 @@ void TextEdit::Text::move_gutters(int p_from_line, int p_to_line) { text.write[p_from_line].gutters.resize(gutter_count); } +void TextEdit::Text::set_use_default_word_separators(bool p_enabled) { + if (use_default_word_separators == p_enabled) { + return; + } + use_default_word_separators = p_enabled; + invalidate_all_lines(); +} + +void TextEdit::Text::set_use_custom_word_separators(bool p_enabled) { + if (use_custom_word_separators == p_enabled) { + return; + } + use_custom_word_separators = p_enabled; + invalidate_all_lines(); +} + +bool TextEdit::Text::is_default_word_separators_enabled() const { + return use_default_word_separators; +} + +bool TextEdit::Text::is_custom_word_separators_enabled() const { + return use_custom_word_separators; +} + +String TextEdit::Text::get_custom_word_separators() const { + return custom_word_separators; +} + +String TextEdit::Text::get_default_word_separators() const { + String concat_separators = "´`~$^=+|<>"; + for (char32_t ch = 0x2000; ch <= 0x206F; ++ch) { // General punctuation block. + concat_separators += ch; + } + for (char32_t ch = 0x3000; ch <= 0x303F; ++ch) { // CJK punctuation block. + concat_separators += ch; + } + return concat_separators; +} + +// Get default and/or custom word separators depending on the option enabled. +String TextEdit::Text::get_enabled_word_separators() const { + String all_separators; + if (use_default_word_separators) { + all_separators += get_default_word_separators(); + } + if (use_custom_word_separators) { + all_separators += get_custom_word_separators(); + } + return all_separators; +} + /////////////////////////////////////////////////////////////////////////////// /// TEXT EDIT /// /////////////////////////////////////////////////////////////////////////////// @@ -6277,6 +6332,44 @@ bool TextEdit::is_highlight_all_occurrences_enabled() const { return highlight_all_occurrences; } +void TextEdit::set_use_default_word_separators(bool p_enabled) { + text.set_use_default_word_separators(p_enabled); +} + +bool TextEdit::is_default_word_separators_enabled() const { + return text.is_default_word_separators_enabled(); +} + +// Set word separators. Combine default separators with custom separators if those options are enabled. +void TextEdit::set_custom_word_separators(const String &p_separators) { + text.set_custom_word_separators(p_separators); +} + +void TextEdit::Text::set_custom_word_separators(const String &p_separators) { + if (custom_word_separators == p_separators) { + return; + } + custom_word_separators = p_separators; + invalidate_all_lines(); +} + +bool TextEdit::is_custom_word_separators_enabled() const { + return text.is_custom_word_separators_enabled(); +} + +String TextEdit::get_custom_word_separators() const { + return text.get_custom_word_separators(); +} + +// Enable or disable custom word separators. +void TextEdit::set_use_custom_word_separators(bool p_enabled) { + text.set_use_custom_word_separators(p_enabled); +} + +String TextEdit::get_default_word_separators() const { + return text.get_default_word_separators(); +} + void TextEdit::set_draw_control_chars(bool p_enabled) { if (draw_control_chars != p_enabled) { draw_control_chars = p_enabled; @@ -6551,6 +6644,14 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("get_word_under_caret", "caret_index"), &TextEdit::get_word_under_caret, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("set_use_default_word_separators", "enabled"), &TextEdit::set_use_default_word_separators); + ClassDB::bind_method(D_METHOD("is_default_word_separators_enabled"), &TextEdit::is_default_word_separators_enabled); + + ClassDB::bind_method(D_METHOD("set_use_custom_word_separators", "enabled"), &TextEdit::set_use_custom_word_separators); + ClassDB::bind_method(D_METHOD("is_custom_word_separators_enabled"), &TextEdit::is_custom_word_separators_enabled); + ClassDB::bind_method(D_METHOD("set_custom_word_separators", "custom_word_separators"), &TextEdit::set_custom_word_separators); + ClassDB::bind_method(D_METHOD("get_custom_word_separators"), &TextEdit::get_custom_word_separators); + /* Selection. */ BIND_ENUM_CONSTANT(SELECTION_MODE_NONE); BIND_ENUM_CONSTANT(SELECTION_MODE_SHIFT); @@ -6774,6 +6875,10 @@ void TextEdit::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_mid_grapheme"), "set_caret_mid_grapheme_enabled", "is_caret_mid_grapheme_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_multiple"), "set_multiple_carets_enabled", "is_multiple_carets_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_default_word_separators"), "set_use_default_word_separators", "is_default_word_separators_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_custom_word_separators"), "set_use_custom_word_separators", "is_custom_word_separators_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "custom_word_separators"), "set_custom_word_separators", "get_custom_word_separators"); + ADD_GROUP("Highlighting", ""); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "syntax_highlighter", PROPERTY_HINT_RESOURCE_TYPE, "SyntaxHighlighter", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_ALWAYS_DUPLICATE), "set_syntax_highlighter", "get_syntax_highlighter"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "highlight_all_occurrences"), "set_highlight_all_occurrences", "is_highlight_all_occurrences_enabled"); diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h index efade39876..6ed5cf4bdc 100644 --- a/scene/gui/text_edit.h +++ b/scene/gui/text_edit.h @@ -174,6 +174,9 @@ private: TextServer::Direction direction = TextServer::DIRECTION_AUTO; BitField<TextServer::LineBreakFlag> brk_flags = TextServer::BREAK_MANDATORY; bool draw_control_chars = false; + String custom_word_separators; + bool use_default_word_separators = true; + bool use_custom_word_separators = false; int line_height = -1; int max_width = -1; @@ -201,6 +204,18 @@ private: int get_line_width(int p_line, int p_wrap_index = -1) const; int get_max_width() const; + void set_use_default_word_separators(bool p_enabled); + bool is_default_word_separators_enabled() const; + + void set_use_custom_word_separators(bool p_enabled); + bool is_custom_word_separators_enabled() const; + + void set_word_separators(const String &p_separators); + void set_custom_word_separators(const String &p_separators); + String get_enabled_word_separators() const; + String get_custom_word_separators() const; + String get_default_word_separators() const; + void set_width(float p_width); float get_width() const; void set_brk_flags(BitField<TextServer::LineBreakFlag> p_flags); @@ -1068,6 +1083,19 @@ public: Color get_font_color() const; + /* Behavior */ + + String get_default_word_separators() const; + + void set_use_default_word_separators(bool p_enabled); + bool is_default_word_separators_enabled() const; + + void set_custom_word_separators(const String &p_separators); + void set_use_custom_word_separators(bool p_enabled); + bool is_custom_word_separators_enabled() const; + + String get_custom_word_separators() const; + /* Deprecated. */ #ifndef DISABLE_DEPRECATED Vector<int> get_caret_index_edit_order(); 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/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/shader_language.cpp b/servers/rendering/shader_language.cpp index 9aa54d0bb7..1e9690a8ae 100644 --- a/servers/rendering/shader_language.cpp +++ b/servers/rendering/shader_language.cpp @@ -1238,6 +1238,7 @@ void ShaderLanguage::clear() { include_positions.push_back(FilePosition()); include_markers_handled.clear(); + calls_info.clear(); #ifdef DEBUG_ENABLED keyword_completion_context = CF_UNSPECIFIED; @@ -1445,8 +1446,12 @@ bool ShaderLanguage::_find_identifier(const BlockNode *p_block, bool p_allow_rea *r_struct_name = shader->constants[p_identifier].struct_name; } if (r_constant_value) { - if (shader->constants[p_identifier].initializer && shader->constants[p_identifier].initializer->values.size() == 1) { - *r_constant_value = shader->constants[p_identifier].initializer->values[0]; + if (shader->constants[p_identifier].initializer && shader->constants[p_identifier].initializer->type == Node::NODE_TYPE_CONSTANT) { + ConstantNode *cnode = static_cast<ConstantNode *>(shader->constants[p_identifier].initializer); + + if (cnode->values.size() == 1) { + *r_constant_value = cnode->values[0]; + } } } if (r_type) { @@ -3085,6 +3090,19 @@ const ShaderLanguage::BuiltinFuncConstArgs ShaderLanguage::builtin_func_const_ar { nullptr, 0, 0, 0 } }; +const ShaderLanguage::BuiltinEntry ShaderLanguage::frag_only_func_defs[] = { + { "dFdx" }, + { "dFdxCoarse" }, + { "dFdxFine" }, + { "dFdy" }, + { "dFdyCoarse" }, + { "dFdyFine" }, + { "fwidth" }, + { "fwidthCoarse" }, + { "fwidthFine" }, + { nullptr } +}; + bool ShaderLanguage::is_const_suffix_lut_initialized = false; bool ShaderLanguage::_validate_function_call(BlockNode *p_block, const FunctionInfo &p_function_info, OperatorNode *p_func, DataType *r_ret_type, StringName *r_ret_type_str, bool *r_is_custom_function) { @@ -4610,6 +4628,58 @@ bool ShaderLanguage::_check_node_constness(const Node *p_node) const { return true; } +bool ShaderLanguage::_check_restricted_func(const StringName &p_name, const StringName &p_current_function) const { + int idx = 0; + + while (frag_only_func_defs[idx].name) { + if (StringName(frag_only_func_defs[idx].name) == p_name) { + if (is_supported_frag_only_funcs) { + if (p_current_function == "vertex" && stages->has(p_current_function)) { + return true; + } + } else { + return true; + } + break; + } + idx++; + } + + return false; +} + +bool ShaderLanguage::_validate_restricted_func(const StringName &p_name, const CallInfo *p_func_info, bool p_is_builtin_hint) { + const bool is_in_restricted_function = p_func_info->name == "vertex"; + + // No need to check up the hierarchy if it's a built-in. + if (!p_is_builtin_hint) { + for (const CallInfo *func_info : p_func_info->calls) { + if (is_in_restricted_function && func_info->name != p_name) { + // Skips check for non-called method. + continue; + } + + if (!_validate_restricted_func(p_name, func_info)) { + return false; + } + } + } + + if (!p_func_info->uses_restricted_functions.is_empty()) { + const Pair<StringName, TkPos> &first_element = p_func_info->uses_restricted_functions.get(0); + _set_tkpos(first_element.second); + + if (is_in_restricted_function) { + _set_error(vformat(RTR("'%s' cannot be used within the '%s' processor function."), first_element.first, "vertex")); + } else { + _set_error(vformat(RTR("'%s' cannot be used here, because '%s' is called by the '%s' processor function (which is not allowed)."), first_element.first, p_func_info->name, "vertex")); + } + return false; + } + + return true; +} + bool ShaderLanguage::_validate_assign(Node *p_node, const FunctionInfo &p_function_info, String *r_message) { if (p_node->type == Node::NODE_TYPE_OPERATOR) { OperatorNode *op = static_cast<OperatorNode *>(p_node); @@ -5266,6 +5336,36 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons const StringName &name = identifier; + if (name != current_function) { // Recursion is not allowed. + // Register call. + if (calls_info.has(name)) { + calls_info[current_function].calls.push_back(&calls_info[name]); + } + + int idx = 0; + bool is_builtin = false; + + while (frag_only_func_defs[idx].name) { + if (frag_only_func_defs[idx].name == name) { + // If a built-in function not found for the current shader type, then it shouldn't be parsed further. + if (!is_supported_frag_only_funcs) { + _set_error(vformat(RTR("Built-in function '%s' is not supported for the '%s' shader type."), name, shader_type_identifier)); + return nullptr; + } + // Register usage of the restricted function. + calls_info[current_function].uses_restricted_functions.push_back(Pair<StringName, TkPos>(name, _get_tkpos())); + is_builtin = true; + break; + } + idx++; + } + + // Recursively checks for the restricted function call. + if (is_supported_frag_only_funcs && current_function == "vertex" && stages->has(current_function) && !_validate_restricted_func(name, &calls_info[current_function], is_builtin)) { + return nullptr; + } + } + OperatorNode *func = alloc_node<OperatorNode>(); func->op = OP_CALL; VariableNode *funcname = alloc_node<VariableNode>(); @@ -8099,6 +8199,8 @@ Error ShaderLanguage::_parse_shader(const HashMap<StringName, FunctionInfo> &p_f ShaderNode::Uniform::Scope uniform_scope = ShaderNode::Uniform::SCOPE_LOCAL; stages = &p_functions; + is_supported_frag_only_funcs = shader_type_identifier == "canvas_item" || shader_type_identifier == "spatial" || shader_type_identifier == "sky"; + const FunctionInfo &constants = p_functions.has("constants") ? p_functions["constants"] : FunctionInfo(); HashMap<String, String> defined_modes; @@ -9443,7 +9545,7 @@ Error ShaderLanguage::_parse_shader(const HashMap<StringName, FunctionInfo> &p_f } } - constant.initializer = static_cast<ConstantNode *>(expr); + constant.initializer = expr; if (!_compare_datatypes(type, struct_name, 0, expr->get_datatype(), expr->get_datatype_name(), expr->get_array_size())) { return ERR_PARSE_ERROR; @@ -9541,6 +9643,11 @@ Error ShaderLanguage::_parse_shader(const HashMap<StringName, FunctionInfo> &p_f shader->functions.insert(name, function); shader->vfunctions.push_back(function); + CallInfo call_info; + call_info.name = name; + + calls_info.insert(name, call_info); + func_node->name = name; func_node->return_type = type; func_node->return_struct_name = struct_name; @@ -10325,10 +10432,11 @@ Error ShaderLanguage::complete(const String &p_code, const ShaderCompileInfo &p_ } while (builtin_func_defs[idx].name) { - if (low_end && builtin_func_defs[idx].high_end) { + if ((low_end && builtin_func_defs[idx].high_end) || _check_restricted_func(builtin_func_defs[idx].name, skip_function)) { idx++; continue; } + matches.insert(String(builtin_func_defs[idx].name), ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION); idx++; } @@ -10490,7 +10598,7 @@ Error ShaderLanguage::complete(const String &p_code, const ShaderCompileInfo &p_ } while (builtin_func_defs[idx].name) { - if (low_end && builtin_func_defs[idx].high_end) { + if ((low_end && builtin_func_defs[idx].high_end) || _check_restricted_func(builtin_func_defs[idx].name, block_function)) { idx++; continue; } diff --git a/servers/rendering/shader_language.h b/servers/rendering/shader_language.h index 5615d7f457..edac819a1e 100644 --- a/servers/rendering/shader_language.h +++ b/servers/rendering/shader_language.h @@ -620,7 +620,7 @@ public: DataType type; StringName struct_name; DataPrecision precision; - ConstantNode *initializer = nullptr; + Node *initializer = nullptr; int array_size; }; @@ -913,6 +913,15 @@ private: Vector<FilePosition> include_positions; HashSet<String> include_markers_handled; + // Additional function information (eg. call hierarchy). No need to expose it to compiler. + struct CallInfo { + StringName name; + List<Pair<StringName, TkPos>> uses_restricted_functions; + List<CallInfo *> calls; + }; + + RBMap<StringName, CallInfo> calls_info; + #ifdef DEBUG_ENABLED struct Usage { int decl_line; @@ -1036,6 +1045,10 @@ private: bool _validate_assign(Node *p_node, const FunctionInfo &p_function_info, String *r_message = nullptr); bool _validate_operator(OperatorNode *p_op, DataType *r_ret_type = nullptr, int *r_ret_size = nullptr); + struct BuiltinEntry { + const char *name; + }; + struct BuiltinFuncDef { enum { MAX_ARGS = 5 }; const char *name; @@ -1078,11 +1091,13 @@ private: #endif // DEBUG_ENABLED const HashMap<StringName, FunctionInfo> *stages = nullptr; + bool is_supported_frag_only_funcs = false; bool _get_completable_identifier(BlockNode *p_block, CompletionType p_type, StringName &identifier); static const BuiltinFuncDef builtin_func_defs[]; static const BuiltinFuncOutArgs builtin_func_out_args[]; static const BuiltinFuncConstArgs builtin_func_const_args[]; + static const BuiltinEntry frag_only_func_defs[]; static bool is_const_suffix_lut_initialized; @@ -1097,6 +1112,9 @@ private: bool _validate_varying_assign(ShaderNode::Varying &p_varying, String *r_message); bool _check_node_constness(const Node *p_node) const; + bool _check_restricted_func(const StringName &p_name, const StringName &p_current_function) const; + bool _validate_restricted_func(const StringName &p_call_name, const CallInfo *p_func_info, bool p_is_builtin_hint = false); + Node *_parse_expression(BlockNode *p_block, const FunctionInfo &p_function_info); Error _parse_array_size(BlockNode *p_block, const FunctionInfo &p_function_info, bool p_forbid_unknown_size, Node **r_size_expression, int *r_array_size, bool *r_unknown_size); Node *_parse_array_constructor(BlockNode *p_block, const FunctionInfo &p_function_info); |