Compare commits

...

29 commits

Author SHA1 Message Date
Yuri Rubinsky 0810ecaafd
Merge pull request #72436 from Chaosus/vs_switch_improvement 2023-01-31 22:55:42 +03:00
Rémi Verschelde 935a6ef46b
Merge pull request #72425 from smix8/default_physics_space_on_demand_only_4.x
Create default World physics spaces on demand only
2023-01-31 18:56:35 +01:00
Rémi Verschelde ea3566f2ed
Merge pull request #69120 from souplamp/audio-function-rename
Rename references to audio device, capture_device to output_device, input_device respectively
2023-01-31 18:56:27 +01:00
Rémi Verschelde 14a4408e02
Merge pull request #65698 from KoBeWi/cameraman
Rework how current Camera2D is determined
2023-01-31 18:56:20 +01:00
smix8 026549db2e Create default World physics spaces on demand only
Changes that the default physics spaces of World resources are only created on first use.
2023-01-31 18:26:52 +01:00
souplamp 5300daaff2
Audio rename (device, capture_device) -> (output_device, input_device)
Change instances of audio properties 'device' to 'output_device',
and instances of audio properties 'capture_device' to 'input_device',
as well as their subsequent getter & setter functions.

Update the docs to reflect these changes, as well as the
3-to-4 converter for GDScript and CSharp to make proper
conversions (only exception is 'device' since that name
is too vague and might replace non-AudioServer related
instances, such as user comments and variables).

This does not change internal references to references like
'Render Client' and 'Capture Client' in WASAPI; such is outside the
scope of this commit. This also does not change ALSA's references,
considering that it uses 'device' to mean input and output
interchangeably.

Other references are changed, however where applicable,
to be consistent with the new AudioServer methods and property
names.
2023-01-31 18:25:11 +01:00
Rémi Verschelde 2b710bc336
Fix MSVC shadow local warning 2023-01-31 18:06:21 +01:00
Rémi Verschelde 8612c12be6
Merge pull request #72452 from akien-mga/global_class_always_create
Always create global class list, even if empty
2023-01-31 16:00:12 +01:00
Rémi Verschelde 925784df03
Merge pull request #72454 from dalexeev/gds-fix-icon-annotation
GDScript: Fix `@icon` annotation
2023-01-31 15:58:49 +01:00
Rémi Verschelde 1c42e141d0
Merge pull request #59310 from Sauermann/proposal-event-transform
Calculate window input event transform only on window change
2023-01-31 15:57:07 +01:00
Rémi Verschelde cf2bf08b41
Merge pull request #71972 from Sauermann/fix-test-ds
[unittests] Send Mouse events via DisplayServer instead of push_input
2023-01-31 15:56:50 +01:00
Rémi Verschelde 534369d267
Merge pull request #64423 from RandomShaper/safe_input_synth
Warn users about unsafe usage of `InputEvent`
2023-01-31 15:56:43 +01:00
Rémi Verschelde b8ef55a427
Merge pull request #71174 from RedMser/fix-confined-mouse-mode-update
Fix confined mouse mode not updating on resize
2023-01-31 15:56:29 +01:00
Rémi Verschelde 92a6586fb6
Merge pull request #72396 from TokageItLab/bs1dconsist
Consistent with NodeBlendSpace1D option NodeBlendSpace2D
2023-01-31 15:55:53 +01:00
Rémi Verschelde 4cfdd25384
Merge pull request #67507 from Sauermann/fix-toplevel-root-control-node
Fix event propagation to child after set_as_toplevel
2023-01-31 15:55:46 +01:00
Rémi Verschelde 6d9c9d30b9
Merge pull request #72206 from vnen/gdscript-allow-void-return-shorthand
GDScript: Allow void functions to return calls to other void functions
2023-01-31 15:55:35 +01:00
Rémi Verschelde 361f3f1721
Merge pull request #57520 from jordigcs/gd-rename-map
Add hint for identifiers renamed from 3.x to 4.0
2023-01-31 15:55:25 +01:00
Danil Alexeev 83cb968965
GDScript: Fix @icon annotation 2023-01-31 17:43:54 +03:00
George Marques a47d4d57ca
GDScript: Allow void functions to return calls to other void functions 2023-01-31 11:37:30 -03:00
Rémi Verschelde 38a806e13f
Always create global class list, even if empty
Fixes #72451.
2023-01-31 15:28:53 +01:00
Pedro J. Estébanez a2af839a59 Warn users about unsafe usage of InputEvent 2023-01-31 14:39:51 +01:00
Yuri Rubinsky bcecb4fe37 Use mix for vector types in switch node in the visual shader 2023-01-31 09:45:27 +03:00
Silc Renew a0c4f849e0 Consistent with NodeBlendSpace1D option NodeBlendSpace2D
Co-authored-by: Skrapion <rick@firefang.com>
2023-01-31 02:14:55 +09:00
Markus Sauermann 7de1b1a40d [unittests] Send Mouse events via DisplayServer instead of push_input
Currently Unittests simplify mouse-events by just pushing them to Viewports.
For dealing with mouse-screen-coordinates (caused by the introduction of
multiple native Windows) it becomes necessary to extend the
DisplayServer functionality for unittests.

This PR introduces DisplayServerMock based on DisplayServerHeadless,
which additionally supports basic Mouse-Input handling.
2023-01-29 21:29:39 +01:00
jordi bb9a00889a Add hint for identifiers renamed since Godot 3 2023-01-24 12:33:55 -06:00
RedMser 951349c481 Fix confined mouse mode not updating on resize 2023-01-10 20:00:52 +01:00
kobewi 7e2a8afb57 Rework how current Camera2D is determined 2022-12-02 00:05:02 +01:00
Markus Sauermann 13b87c13c7 Fix set_as_toplevel event propagation to child
In certain conditions events did not get propagated to Control childs of
Node2D nodes when setting a parent of the Node2D to toplevel.

This patch makes sure that such Control nodes become root control in the
viewport.
2022-10-16 21:42:16 +02:00
Markus Sauermann 2bf2217b1a Calculate window input event transform only on window change 2022-10-01 09:54:38 +02:00
56 changed files with 1199 additions and 525 deletions

View file

@ -1138,7 +1138,7 @@ Array ProjectSettings::get_global_class_list() {
Ref<ConfigFile> cf;
cf.instantiate();
if (cf->load(get_global_class_list_path()) == OK) {
script_classes = cf->get_value("", "list");
script_classes = cf->get_value("", "list", Array());
} else {
#ifndef TOOLS_ENABLED
// Script classes can't be recreated in exported project, so print an error.

View file

@ -891,6 +891,31 @@ void Input::parse_input_event(const Ref<InputEvent> &p_event) {
ERR_FAIL_COND(p_event.is_null());
#ifdef DEBUG_ENABLED
uint64_t curr_frame = Engine::get_singleton()->get_process_frames();
if (curr_frame != last_parsed_frame) {
frame_parsed_events.clear();
last_parsed_frame = curr_frame;
frame_parsed_events.insert(p_event);
} else if (frame_parsed_events.has(p_event)) {
// It would be technically safe to send the same event in cases such as:
// - After an explicit flush.
// - In platforms using buffering when agile flushing is enabled, after one of the mid-frame flushes.
// - If platform doesn't use buffering and event accumulation is disabled.
// - If platform doesn't use buffering and the event type is not accumulable.
// However, it wouldn't be reasonable to ask users to remember the full ruleset and be aware at all times
// of the possibilites of the target platform, project settings and engine internals, which may change
// without prior notice.
// Therefore, the guideline is, "don't send the same event object more than once per frame".
WARN_PRINT_ONCE(
"An input event object is being parsed more than once in the same frame, which is unsafe.\n"
"If you are generating events in a script, you have to instantiate a new event instead of sending the same one more than once, unless the original one was sent on an earlier frame.\n"
"You can call duplicate() on the event to get a new instance with identical values.");
} else {
frame_parsed_events.insert(p_event);
}
#endif
if (use_accumulated_input) {
if (buffered_events.is_empty() || !buffered_events.back()->get()->accumulate(p_event)) {
buffered_events.push_back(p_event);

View file

@ -223,6 +223,10 @@ private:
void _parse_input_event_impl(const Ref<InputEvent> &p_event, bool p_is_emulated);
List<Ref<InputEvent>> buffered_events;
#ifdef DEBUG_ENABLED
HashSet<Ref<InputEvent>> frame_parsed_events;
uint64_t last_parsed_frame = UINT64_MAX;
#endif
friend class DisplayServer;

View file

@ -67,6 +67,9 @@
</method>
</methods>
<members>
<member name="blend_mode" type="int" setter="set_blend_mode" getter="get_blend_mode" enum="AnimationNodeBlendSpace1D.BlendMode" default="0">
Controls the interpolation between animations. See [enum BlendMode] constants.
</member>
<member name="max_space" type="float" setter="set_max_space" getter="get_max_space" default="1.0">
The blend space's axis's upper limit for the points' position. See [method add_blend_point].
</member>
@ -84,4 +87,15 @@
Label of the virtual axis of the blend space.
</member>
</members>
<constants>
<constant name="BLEND_MODE_INTERPOLATED" value="0" enum="BlendMode">
The interpolation between animations is linear.
</constant>
<constant name="BLEND_MODE_DISCRETE" value="1" enum="BlendMode">
The blend space plays the animation of the node the blending position is closest to. Useful for frame-by-frame 2D animations.
</constant>
<constant name="BLEND_MODE_DISCRETE_CARRY" value="2" enum="BlendMode">
Similar to [constant BLEND_MODE_DISCRETE], but starts the new animation at the last animation's playback position.
</constant>
</constants>
</class>

View file

@ -29,13 +29,6 @@
Adds an [AudioEffect] effect to the bus [param bus_idx] at [param at_position].
</description>
</method>
<method name="capture_get_device_list">
<return type="PackedStringArray" />
<description>
Returns the names of all audio input devices detected on the system.
[b]Note:[/b] [member ProjectSettings.audio/driver/enable_input] must be [code]true[/code] for audio input to work. See also that setting's description for caveats related to permissions and operating system privacy settings.
</description>
</method>
<method name="generate_bus_layout" qualifiers="const">
<return type="AudioBusLayout" />
<description>
@ -117,10 +110,11 @@
Returns the volume of the bus at index [param bus_idx] in dB.
</description>
</method>
<method name="get_device_list">
<method name="get_input_device_list">
<return type="PackedStringArray" />
<description>
Returns the names of all audio devices detected on the system.
Returns the names of all audio input devices detected on the system.
[b]Note:[/b] [member ProjectSettings.audio/driver/enable_input] must be [code]true[/code] for audio input to work. See also that setting's description for caveats related to permissions and operating system privacy settings.
</description>
</method>
<method name="get_mix_rate" qualifiers="const">
@ -129,6 +123,12 @@
Returns the sample rate at the output of the [AudioServer].
</description>
</method>
<method name="get_output_device_list">
<return type="PackedStringArray" />
<description>
Returns the names of all audio output devices detected on the system.
</description>
</method>
<method name="get_output_latency" qualifiers="const">
<return type="float" />
<description>
@ -302,12 +302,12 @@
<member name="bus_count" type="int" setter="set_bus_count" getter="get_bus_count" default="1">
Number of available audio buses.
</member>
<member name="capture_device" type="String" setter="capture_set_device" getter="capture_get_device" default="&quot;Default&quot;">
Name of the current device for audio input (see [method capture_get_device_list]). On systems with multiple audio inputs (such as analog, USB and HDMI audio), this can be used to select the audio input device. The value [code]"Default"[/code] will record audio on the system-wide default audio input. If an invalid device name is set, the value will be reverted back to [code]"Default"[/code].
<member name="input_device" type="String" setter="set_input_device" getter="get_input_device" default="&quot;Default&quot;">
Name of the current device for audio input (see [method get_input_device_list]). On systems with multiple audio inputs (such as analog, USB and HDMI audio), this can be used to select the audio input device. The value [code]"Default"[/code] will record audio on the system-wide default audio input. If an invalid device name is set, the value will be reverted back to [code]"Default"[/code].
[b]Note:[/b] [member ProjectSettings.audio/driver/enable_input] must be [code]true[/code] for audio input to work. See also that setting's description for caveats related to permissions and operating system privacy settings.
</member>
<member name="device" type="String" setter="set_device" getter="get_device" default="&quot;Default&quot;">
Name of the current device for audio output (see [method get_device_list]). On systems with multiple audio outputs (such as analog, USB and HDMI audio), this can be used to select the audio output device. The value [code]"Default"[/code] will play audio on the system-wide default audio output. If an invalid device name is set, the value will be reverted back to [code]"Default"[/code].
<member name="output_device" type="String" setter="set_output_device" getter="get_output_device" default="&quot;Default&quot;">
Name of the current device for audio output (see [method get_output_device_list]). On systems with multiple audio outputs (such as analog, USB and HDMI audio), this can be used to select the audio output device. The value [code]"Default"[/code] will play audio on the system-wide default audio output. If an invalid device name is set, the value will be reverted back to [code]"Default"[/code].
</member>
<member name="playback_speed_scale" type="float" setter="set_playback_speed_scale" getter="get_playback_speed_scale" default="1.0">
Scales the rate at which audio is played (i.e. setting it to [code]0.5[/code] will make the audio be played at half its speed).

View file

@ -55,6 +55,18 @@
[b]Note:[/b] The returned value is not the same as [member Node2D.global_position], as it is affected by the drag properties. It is also not the same as the current position if [member position_smoothing_enabled] is [code]true[/code] (see [method get_screen_center_position]).
</description>
</method>
<method name="is_current" qualifiers="const">
<return type="bool" />
<description>
Returns [code]true[/code] if this [Camera2D] is the active camera (see [method Viewport.get_camera_2d]).
</description>
</method>
<method name="make_current">
<return type="void" />
<description>
Forces this [Camera2D] to become the current active one. [member enabled] must be [code]true[/code].
</description>
</method>
<method name="reset_smoothing">
<return type="void" />
<description>
@ -83,9 +95,6 @@
<member name="anchor_mode" type="int" setter="set_anchor_mode" getter="get_anchor_mode" enum="Camera2D.AnchorMode" default="1">
The Camera2D's anchor point. See [enum AnchorMode] constants.
</member>
<member name="current" type="bool" setter="set_current" getter="is_current" default="false">
If [code]true[/code], the camera acts as the active camera for its [Viewport] ancestor. Only one camera can be current in a given viewport, so setting a different camera in the same viewport [code]current[/code] will disable whatever camera was already active in that viewport.
</member>
<member name="custom_viewport" type="Node" setter="set_custom_viewport" getter="get_custom_viewport">
The custom [Viewport] node attached to the [Camera2D]. If [code]null[/code] or not a [Viewport], uses the default viewport instead.
</member>
@ -124,6 +133,10 @@
<member name="editor_draw_screen" type="bool" setter="set_screen_drawing_enabled" getter="is_screen_drawing_enabled" default="true">
If [code]true[/code], draws the camera's screen rectangle in the editor.
</member>
<member name="enabled" type="bool" setter="set_enabled" getter="is_enabled" default="true">
Controls whether the camera can be active or not. If [code]true[/code], the [Camera2D] will become the main camera when it enters the scene tree and there is no active camera currently (see [method Viewport.get_camera_2d]).
When the camera is currently active and [member enabled] is set to [code]false[/code], the next enabled [Camera2D] in the scene tree will become active.
</member>
<member name="ignore_rotation" type="bool" setter="set_ignore_rotation" getter="is_ignoring_rotation" default="true">
If [code]true[/code], the camera's rendered view is not affected by its [member Node2D.rotation] and [member Node2D.global_rotation].
</member>

View file

@ -426,6 +426,9 @@
<member name="debug/gdscript/warnings/redundant_await" type="int" setter="" getter="" default="1">
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when a function that is not a coroutine is called with await.
</member>
<member name="debug/gdscript/warnings/renamed_in_godot_4_hint" type="bool" setter="" getter="" default="1">
When enabled, using a property, enum, or function that was renamed since Godot 3 will produce a hint if an error occurs.
</member>
<member name="debug/gdscript/warnings/return_value_discarded" type="int" setter="" getter="" default="0">
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when calling a function without using its return value (by assigning it to a variable or using it as a function argument). Such return values are sometimes used to denote possible errors using the [enum Error] enum.
</member>
@ -474,6 +477,9 @@
<member name="debug/gdscript/warnings/unsafe_property_access" type="int" setter="" getter="" default="0">
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when accessing a property whose presence is not guaranteed at compile-time in the class.
</member>
<member name="debug/gdscript/warnings/unsafe_void_return" type="int" setter="" getter="" default="0">
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when returning a call from a [code]void[/code] function when such call cannot be guaranteed to be also [code]void[/code].
</member>
<member name="debug/gdscript/warnings/unused_local_constant" type="int" setter="" getter="" default="1">
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when a local constant is never used.
</member>

View file

@ -44,10 +44,10 @@ OSStatus AudioDriverCoreAudio::input_device_address_cb(AudioObjectID inObjectID,
void *inClientData) {
AudioDriverCoreAudio *driver = static_cast<AudioDriverCoreAudio *>(inClientData);
// If our selected device is the Default call set_device to update the
// If our selected input device is the Default, call set_input_device to update the
// kAudioOutputUnitProperty_CurrentDevice property
if (driver->capture_device_name == "Default") {
driver->capture_set_device("Default");
if (driver->input_device_name == "Default") {
driver->set_input_device("Default");
}
return noErr;
@ -58,10 +58,10 @@ OSStatus AudioDriverCoreAudio::output_device_address_cb(AudioObjectID inObjectID
void *inClientData) {
AudioDriverCoreAudio *driver = static_cast<AudioDriverCoreAudio *>(inClientData);
// If our selected device is the Default call set_device to update the
// If our selected output device is the Default call set_output_device to update the
// kAudioOutputUnitProperty_CurrentDevice property
if (driver->device_name == "Default") {
driver->set_device("Default");
if (driver->output_device_name == "Default") {
driver->set_output_device("Default");
}
return noErr;
@ -495,7 +495,7 @@ Error AudioDriverCoreAudio::capture_stop() {
#ifdef MACOS_ENABLED
PackedStringArray AudioDriverCoreAudio::_get_device_list(bool capture) {
PackedStringArray AudioDriverCoreAudio::_get_device_list(bool input) {
PackedStringArray list;
list.push_back("Default");
@ -514,7 +514,7 @@ PackedStringArray AudioDriverCoreAudio::_get_device_list(bool capture) {
UInt32 deviceCount = size / sizeof(AudioDeviceID);
for (UInt32 i = 0; i < deviceCount; i++) {
prop.mScope = capture ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput;
prop.mScope = input ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput;
prop.mSelector = kAudioDevicePropertyStreamConfiguration;
AudioObjectGetPropertyDataSize(audioDevices[i], &prop, 0, nullptr, &size);
@ -555,10 +555,10 @@ PackedStringArray AudioDriverCoreAudio::_get_device_list(bool capture) {
return list;
}
void AudioDriverCoreAudio::_set_device(const String &device, bool capture) {
void AudioDriverCoreAudio::_set_device(const String &output_device, bool input) {
AudioDeviceID deviceId;
bool found = false;
if (device != "Default") {
if (output_device != "Default") {
AudioObjectPropertyAddress prop;
prop.mSelector = kAudioHardwarePropertyDevices;
@ -573,7 +573,7 @@ void AudioDriverCoreAudio::_set_device(const String &device, bool capture) {
UInt32 deviceCount = size / sizeof(AudioDeviceID);
for (UInt32 i = 0; i < deviceCount && !found; i++) {
prop.mScope = capture ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput;
prop.mScope = input ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput;
prop.mSelector = kAudioDevicePropertyStreamConfiguration;
AudioObjectGetPropertyDataSize(audioDevices[i], &prop, 0, nullptr, &size);
@ -602,7 +602,7 @@ void AudioDriverCoreAudio::_set_device(const String &device, bool capture) {
ERR_FAIL_NULL_MSG(buffer, "Out of memory.");
if (CFStringGetCString(cfname, buffer, maxSize, kCFStringEncodingUTF8)) {
String name = String::utf8(buffer) + " (" + itos(audioDevices[i]) + ")";
if (name == device) {
if (name == output_device) {
deviceId = audioDevices[i];
found = true;
}
@ -618,7 +618,7 @@ void AudioDriverCoreAudio::_set_device(const String &device, bool capture) {
if (!found) {
// If we haven't found the desired device get the system default one
UInt32 size = sizeof(AudioDeviceID);
UInt32 elem = capture ? kAudioHardwarePropertyDefaultInputDevice : kAudioHardwarePropertyDefaultOutputDevice;
UInt32 elem = input ? kAudioHardwarePropertyDefaultInputDevice : kAudioHardwarePropertyDefaultOutputDevice;
AudioObjectPropertyAddress property = { elem, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
OSStatus result = AudioObjectGetPropertyData(kAudioObjectSystemObject, &property, 0, nullptr, &size, &deviceId);
@ -628,10 +628,10 @@ void AudioDriverCoreAudio::_set_device(const String &device, bool capture) {
}
if (found) {
OSStatus result = AudioUnitSetProperty(capture ? input_unit : audio_unit, kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global, 0, &deviceId, sizeof(AudioDeviceID));
OSStatus result = AudioUnitSetProperty(input ? input_unit : audio_unit, kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global, 0, &deviceId, sizeof(AudioDeviceID));
ERR_FAIL_COND(result != noErr);
if (capture) {
if (input) {
// Reset audio input to keep synchronization.
input_position = 0;
input_size = 0;
@ -639,34 +639,34 @@ void AudioDriverCoreAudio::_set_device(const String &device, bool capture) {
}
}
PackedStringArray AudioDriverCoreAudio::get_device_list() {
PackedStringArray AudioDriverCoreAudio::get_output_device_list() {
return _get_device_list();
}
String AudioDriverCoreAudio::get_device() {
return device_name;
String AudioDriverCoreAudio::get_output_device() {
return output_device_name;
}
void AudioDriverCoreAudio::set_device(String device) {
device_name = device;
void AudioDriverCoreAudio::set_output_device(String output_device) {
output_device_name = output_device;
if (active) {
_set_device(device_name);
_set_device(output_device_name);
}
}
void AudioDriverCoreAudio::capture_set_device(const String &p_name) {
capture_device_name = p_name;
void AudioDriverCoreAudio::set_input_device(const String &p_name) {
input_device_name = p_name;
if (active) {
_set_device(capture_device_name, true);
_set_device(input_device_name, true);
}
}
PackedStringArray AudioDriverCoreAudio::capture_get_device_list() {
PackedStringArray AudioDriverCoreAudio::get_input_device_list() {
return _get_device_list(true);
}
String AudioDriverCoreAudio::capture_get_device() {
return capture_device_name;
String AudioDriverCoreAudio::get_input_device() {
return input_device_name;
}
#endif

View file

@ -47,8 +47,8 @@ class AudioDriverCoreAudio : public AudioDriver {
bool active = false;
Mutex mutex;
String device_name = "Default";
String capture_device_name = "Default";
String output_device_name = "Default";
String input_device_name = "Default";
int mix_rate = 0;
unsigned int channels = 2;
@ -60,7 +60,7 @@ class AudioDriverCoreAudio : public AudioDriver {
#ifdef MACOS_ENABLED
PackedStringArray _get_device_list(bool capture = false);
void _set_device(const String &device, bool capture = false);
void _set_device(const String &output_device, bool capture = false);
static OSStatus input_device_address_cb(AudioObjectID inObjectID,
UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses,
@ -107,13 +107,13 @@ public:
void stop();
#ifdef MACOS_ENABLED
virtual PackedStringArray get_device_list();
virtual String get_device();
virtual void set_device(String device);
virtual PackedStringArray get_output_device_list();
virtual String get_output_device();
virtual void set_output_device(String output_device);
virtual PackedStringArray capture_get_device_list();
virtual void capture_set_device(const String &p_name);
virtual String capture_get_device();
virtual PackedStringArray get_input_device_list();
virtual void set_input_device(const String &p_name);
virtual String get_input_device();
#endif
AudioDriverCoreAudio();

View file

@ -106,15 +106,15 @@ void AudioDriverPulseAudio::pa_server_info_cb(pa_context *c, const pa_server_inf
ERR_FAIL_COND_MSG(!i, "PulseAudio server info is null.");
AudioDriverPulseAudio *ad = static_cast<AudioDriverPulseAudio *>(userdata);
ad->capture_default_device = i->default_source_name;
ad->default_device = i->default_sink_name;
ad->default_input_device = i->default_source_name;
ad->default_output_device = i->default_sink_name;
ad->pa_status++;
}
Error AudioDriverPulseAudio::detect_channels(bool capture) {
pa_channel_map_init_stereo(capture ? &pa_rec_map : &pa_map);
Error AudioDriverPulseAudio::detect_channels(bool input) {
pa_channel_map_init_stereo(input ? &pa_rec_map : &pa_map);
String device = capture ? capture_device_name : device_name;
String device = input ? input_device_name : output_device_name;
if (device == "Default") {
// Get the default output device name
pa_status = 0;
@ -136,7 +136,7 @@ Error AudioDriverPulseAudio::detect_channels(bool capture) {
char dev[1024];
if (device == "Default") {
strcpy(dev, capture ? capture_default_device.utf8().get_data() : default_device.utf8().get_data());
strcpy(dev, input ? default_input_device.utf8().get_data() : default_output_device.utf8().get_data());
} else {
strcpy(dev, device.utf8().get_data());
}
@ -145,7 +145,7 @@ Error AudioDriverPulseAudio::detect_channels(bool capture) {
// Now using the device name get the amount of channels
pa_status = 0;
pa_operation *pa_op;
if (capture) {
if (input) {
pa_op = pa_context_get_source_info_by_name(pa_ctx, dev, &AudioDriverPulseAudio::pa_source_info_cb, (void *)this);
} else {
pa_op = pa_context_get_sink_info_by_name(pa_ctx, dev, &AudioDriverPulseAudio::pa_sink_info_cb, (void *)this);
@ -165,7 +165,7 @@ Error AudioDriverPulseAudio::detect_channels(bool capture) {
return FAILED;
}
} else {
if (capture) {
if (input) {
ERR_PRINT("pa_context_get_source_info_by_name error");
} else {
ERR_PRINT("pa_context_get_sink_info_by_name error");
@ -175,13 +175,13 @@ Error AudioDriverPulseAudio::detect_channels(bool capture) {
return OK;
}
Error AudioDriverPulseAudio::init_device() {
// If there is a specified device check that it is really present
if (device_name != "Default") {
PackedStringArray list = get_device_list();
if (list.find(device_name) == -1) {
device_name = "Default";
new_device = "Default";
Error AudioDriverPulseAudio::init_output_device() {
// If there is a specified output device, check that it is really present
if (output_device_name != "Default") {
PackedStringArray list = get_output_device_list();
if (list.find(output_device_name) == -1) {
output_device_name = "Default";
new_output_device = "Default";
}
}
@ -192,7 +192,7 @@ Error AudioDriverPulseAudio::init_device() {
Error err = detect_channels();
if (err != OK) {
// This most likely means there are no sinks.
ERR_PRINT("PulseAudio: init device failed to detect number of output channels");
ERR_PRINT("PulseAudio: init_output_device failed to detect number of output channels");
return err;
}
@ -256,7 +256,7 @@ Error AudioDriverPulseAudio::init_device() {
attr.maxlength = (uint32_t)-1;
attr.minreq = (uint32_t)-1;
const char *dev = device_name == "Default" ? nullptr : device_name.utf8().get_data();
const char *dev = output_device_name == "Default" ? nullptr : output_device_name.utf8().get_data();
pa_stream_flags flags = pa_stream_flags(PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_ADJUST_LATENCY | PA_STREAM_AUTO_TIMING_UPDATE);
int error_code = pa_stream_connect_playback(pa_str, dev, &attr, flags, nullptr, nullptr);
ERR_FAIL_COND_V(error_code < 0, ERR_CANT_OPEN);
@ -346,7 +346,7 @@ Error AudioDriverPulseAudio::init() {
return ERR_CANT_OPEN;
}
init_device();
init_output_device();
thread.start(AudioDriverPulseAudio::thread_func, this);
return OK;
@ -448,18 +448,18 @@ void AudioDriverPulseAudio::thread_func(void *p_udata) {
}
}
// User selected a new device, finish the current one so we'll init the new device
if (ad->device_name != ad->new_device) {
ad->device_name = ad->new_device;
ad->finish_device();
// User selected a new output device, finish the current one so we'll init the new output device
if (ad->output_device_name != ad->new_output_device) {
ad->output_device_name = ad->new_output_device;
ad->finish_output_device();
Error err = ad->init_device();
Error err = ad->init_output_device();
if (err != OK) {
ERR_PRINT("PulseAudio: init_device error");
ad->device_name = "Default";
ad->new_device = "Default";
ERR_PRINT("PulseAudio: init_output_device error");
ad->output_device_name = "Default";
ad->new_output_device = "Default";
err = ad->init_device();
err = ad->init_output_device();
if (err != OK) {
ad->active.clear();
ad->exit_thread.set();
@ -471,11 +471,11 @@ void AudioDriverPulseAudio::thread_func(void *p_udata) {
write_ofs = 0;
}
// If we're using the default device check that the current device is still the default
if (ad->device_name == "Default") {
// If we're using the default output device, check that the current output device is still the default
if (ad->output_device_name == "Default") {
uint64_t msec = OS::get_singleton()->get_ticks_msec();
if (msec > (default_device_msec + 1000)) {
String old_default_device = ad->default_device;
String old_default_device = ad->default_output_device;
default_device_msec = msec;
@ -494,12 +494,12 @@ void AudioDriverPulseAudio::thread_func(void *p_udata) {
ERR_PRINT("pa_context_get_server_info error: " + String(pa_strerror(pa_context_errno(ad->pa_ctx))));
}
if (old_default_device != ad->default_device) {
ad->finish_device();
if (old_default_device != ad->default_output_device) {
ad->finish_output_device();
Error err = ad->init_device();
Error err = ad->init_output_device();
if (err != OK) {
ERR_PRINT("PulseAudio: init_device error");
ERR_PRINT("PulseAudio: init_output_device error");
ad->active.clear();
ad->exit_thread.set();
break;
@ -541,18 +541,18 @@ void AudioDriverPulseAudio::thread_func(void *p_udata) {
}
}
// User selected a new device, finish the current one so we'll init the new device
if (ad->capture_device_name != ad->capture_new_device) {
ad->capture_device_name = ad->capture_new_device;
ad->capture_finish_device();
// User selected a new input device, finish the current one so we'll init the new input device
if (ad->input_device_name != ad->new_input_device) {
ad->input_device_name = ad->new_input_device;
ad->finish_input_device();
Error err = ad->capture_init_device();
Error err = ad->init_input_device();
if (err != OK) {
ERR_PRINT("PulseAudio: capture_init_device error");
ad->capture_device_name = "Default";
ad->capture_new_device = "Default";
ERR_PRINT("PulseAudio: init_input_device error");
ad->input_device_name = "Default";
ad->new_input_device = "Default";
err = ad->capture_init_device();
err = ad->init_input_device();
if (err != OK) {
ad->active.clear();
ad->exit_thread.set();
@ -596,7 +596,7 @@ void AudioDriverPulseAudio::pa_sinklist_cb(pa_context *c, const pa_sink_info *l,
ad->pa_status++;
}
PackedStringArray AudioDriverPulseAudio::get_device_list() {
PackedStringArray AudioDriverPulseAudio::get_output_device_list() {
pa_devices.clear();
pa_devices.push_back("Default");
@ -606,7 +606,7 @@ PackedStringArray AudioDriverPulseAudio::get_device_list() {
lock();
// Get the device list
// Get the output device list
pa_status = 0;
pa_operation *pa_op = pa_context_get_sink_info_list(pa_ctx, pa_sinklist_cb, (void *)this);
if (pa_op) {
@ -627,13 +627,13 @@ PackedStringArray AudioDriverPulseAudio::get_device_list() {
return pa_devices;
}
String AudioDriverPulseAudio::get_device() {
return device_name;
String AudioDriverPulseAudio::get_output_device() {
return output_device_name;
}
void AudioDriverPulseAudio::set_device(String device) {
void AudioDriverPulseAudio::set_output_device(String output_device) {
lock();
new_device = device;
new_output_device = output_device;
unlock();
}
@ -645,7 +645,7 @@ void AudioDriverPulseAudio::unlock() {
mutex.unlock();
}
void AudioDriverPulseAudio::finish_device() {
void AudioDriverPulseAudio::finish_output_device() {
if (pa_str) {
pa_stream_disconnect(pa_str);
pa_stream_unref(pa_str);
@ -661,7 +661,7 @@ void AudioDriverPulseAudio::finish() {
exit_thread.set();
thread.wait_to_finish();
finish_device();
finish_output_device();
if (pa_ctx) {
pa_context_disconnect(pa_ctx);
@ -675,13 +675,13 @@ void AudioDriverPulseAudio::finish() {
}
}
Error AudioDriverPulseAudio::capture_init_device() {
// If there is a specified device check that it is really present
if (capture_device_name != "Default") {
PackedStringArray list = capture_get_device_list();
if (list.find(capture_device_name) == -1) {
capture_device_name = "Default";
capture_new_device = "Default";
Error AudioDriverPulseAudio::init_input_device() {
// If there is a specified input device, check that it is really present
if (input_device_name != "Default") {
PackedStringArray list = get_input_device_list();
if (list.find(input_device_name) == -1) {
input_device_name = "Default";
new_input_device = "Default";
}
}
@ -718,7 +718,7 @@ Error AudioDriverPulseAudio::capture_init_device() {
ERR_FAIL_V(ERR_CANT_OPEN);
}
const char *dev = capture_device_name == "Default" ? nullptr : capture_device_name.utf8().get_data();
const char *dev = input_device_name == "Default" ? nullptr : input_device_name.utf8().get_data();
pa_stream_flags flags = pa_stream_flags(PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_ADJUST_LATENCY | PA_STREAM_AUTO_TIMING_UPDATE);
int error_code = pa_stream_connect_record(pa_rec_str, dev, &attr, flags);
if (error_code < 0) {
@ -734,7 +734,7 @@ Error AudioDriverPulseAudio::capture_init_device() {
return OK;
}
void AudioDriverPulseAudio::capture_finish_device() {
void AudioDriverPulseAudio::finish_input_device() {
if (pa_rec_str) {
int ret = pa_stream_disconnect(pa_rec_str);
if (ret != 0) {
@ -745,25 +745,25 @@ void AudioDriverPulseAudio::capture_finish_device() {
}
}
Error AudioDriverPulseAudio::capture_start() {
Error AudioDriverPulseAudio::input_start() {
lock();
Error err = capture_init_device();
Error err = init_input_device();
unlock();
return err;
}
Error AudioDriverPulseAudio::capture_stop() {
Error AudioDriverPulseAudio::input_stop() {
lock();
capture_finish_device();
finish_input_device();
unlock();
return OK;
}
void AudioDriverPulseAudio::capture_set_device(const String &p_name) {
void AudioDriverPulseAudio::set_input_device(const String &p_name) {
lock();
capture_new_device = p_name;
new_input_device = p_name;
unlock();
}
@ -782,7 +782,7 @@ void AudioDriverPulseAudio::pa_sourcelist_cb(pa_context *c, const pa_source_info
ad->pa_status++;
}
PackedStringArray AudioDriverPulseAudio::capture_get_device_list() {
PackedStringArray AudioDriverPulseAudio::get_input_device_list() {
pa_rec_devices.clear();
pa_rec_devices.push_back("Default");
@ -813,9 +813,9 @@ PackedStringArray AudioDriverPulseAudio::capture_get_device_list() {
return pa_rec_devices;
}
String AudioDriverPulseAudio::capture_get_device() {
String AudioDriverPulseAudio::get_input_device() {
lock();
String name = capture_device_name;
String name = input_device_name;
unlock();
return name;

View file

@ -51,13 +51,13 @@ class AudioDriverPulseAudio : public AudioDriver {
pa_channel_map pa_map = {};
pa_channel_map pa_rec_map = {};
String device_name = "Default";
String new_device = "Default";
String default_device;
String output_device_name = "Default";
String new_output_device = "Default";
String default_output_device;
String capture_device_name;
String capture_new_device;
String capture_default_device;
String input_device_name;
String new_input_device;
String default_input_device;
Vector<int32_t> samples_in;
Vector<int16_t> samples_out;
@ -83,11 +83,11 @@ class AudioDriverPulseAudio : public AudioDriver {
static void pa_sinklist_cb(pa_context *c, const pa_sink_info *l, int eol, void *userdata);
static void pa_sourcelist_cb(pa_context *c, const pa_source_info *l, int eol, void *userdata);
Error init_device();
void finish_device();
Error init_output_device();
void finish_output_device();
Error capture_init_device();
void capture_finish_device();
Error init_input_device();
void finish_input_device();
Error detect_channels(bool capture = false);
@ -103,13 +103,13 @@ public:
virtual int get_mix_rate() const;
virtual SpeakerMode get_speaker_mode() const;
virtual PackedStringArray get_device_list();
virtual String get_device();
virtual void set_device(String device);
virtual PackedStringArray get_output_device_list();
virtual String get_output_device();
virtual void set_output_device(String output_device);
virtual PackedStringArray capture_get_device_list();
virtual void capture_set_device(const String &p_name);
virtual String capture_get_device();
virtual PackedStringArray get_input_device_list();
virtual void set_input_device(const String &p_name);
virtual String get_input_device();
virtual void lock();
virtual void unlock();
@ -117,8 +117,8 @@ public:
virtual float get_latency();
virtual Error capture_start();
virtual Error capture_stop();
virtual Error input_start();
virtual Error input_stop();
AudioDriverPulseAudio();
~AudioDriverPulseAudio() {}

View file

@ -118,8 +118,8 @@ const IID IID_IAudioCaptureClient = __uuidof(IAudioCaptureClient);
#define CAPTURE_BUFFER_CHANNELS 2
static bool default_render_device_changed = false;
static bool default_capture_device_changed = false;
static bool default_output_device_changed = false;
static bool default_input_device_changed = false;
// Silence warning due to a COM API weirdness (GH-35194).
#if defined(__GNUC__) && !defined(__clang__)
@ -181,9 +181,9 @@ public:
HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR pwstrDeviceId) {
if (role == eConsole) {
if (flow == eRender) {
default_render_device_changed = true;
default_output_device_changed = true;
} else if (flow == eCapture) {
default_capture_device_changed = true;
default_input_device_changed = true;
}
}
@ -201,10 +201,10 @@ public:
static CMMNotificationClient notif_client;
Error AudioDriverWASAPI::audio_device_init(AudioDeviceWASAPI *p_device, bool p_capture, bool p_reinit, bool p_no_audio_client_3) {
Error AudioDriverWASAPI::audio_device_init(AudioDeviceWASAPI *p_device, bool p_input, bool p_reinit, bool p_no_audio_client_3) {
WAVEFORMATEX *pwfex;
IMMDeviceEnumerator *enumerator = nullptr;
IMMDevice *device = nullptr;
IMMDevice *output_device = nullptr;
CoInitialize(nullptr);
@ -212,11 +212,11 @@ Error AudioDriverWASAPI::audio_device_init(AudioDeviceWASAPI *p_device, bool p_c
ERR_FAIL_COND_V(hr != S_OK, ERR_CANT_OPEN);
if (p_device->device_name == "Default") {
hr = enumerator->GetDefaultAudioEndpoint(p_capture ? eCapture : eRender, eConsole, &device);
hr = enumerator->GetDefaultAudioEndpoint(p_input ? eCapture : eRender, eConsole, &output_device);
} else {
IMMDeviceCollection *devices = nullptr;
hr = enumerator->EnumAudioEndpoints(p_capture ? eCapture : eRender, DEVICE_STATE_ACTIVE, &devices);
hr = enumerator->EnumAudioEndpoints(p_input ? eCapture : eRender, DEVICE_STATE_ACTIVE, &devices);
ERR_FAIL_COND_V(hr != S_OK, ERR_CANT_OPEN);
LPWSTR strId = nullptr;
@ -255,20 +255,20 @@ Error AudioDriverWASAPI::audio_device_init(AudioDeviceWASAPI *p_device, bool p_c
}
if (found) {
hr = enumerator->GetDevice(strId, &device);
hr = enumerator->GetDevice(strId, &output_device);
}
if (strId) {
CoTaskMemFree(strId);
}
if (device == nullptr) {
hr = enumerator->GetDefaultAudioEndpoint(p_capture ? eCapture : eRender, eConsole, &device);
if (output_device == nullptr) {
hr = enumerator->GetDefaultAudioEndpoint(p_input ? eCapture : eRender, eConsole, &output_device);
}
}
if (p_reinit) {
// In case we're trying to re-initialize the device prevent throwing this error on the console,
// In case we're trying to re-initialize the device, prevent throwing this error on the console,
// otherwise if there is currently no device available this will spam the console.
if (hr != S_OK) {
return ERR_CANT_OPEN;
@ -284,28 +284,28 @@ Error AudioDriverWASAPI::audio_device_init(AudioDeviceWASAPI *p_device, bool p_c
ERR_PRINT("WASAPI: RegisterEndpointNotificationCallback error");
}
using_audio_client_3 = !p_capture; // IID_IAudioClient3 is only used for adjustable output latency (not input)
using_audio_client_3 = !p_input; // IID_IAudioClient3 is only used for adjustable output latency (not input)
if (p_no_audio_client_3) {
using_audio_client_3 = false;
}
if (using_audio_client_3) {
hr = device->Activate(IID_IAudioClient3, CLSCTX_ALL, nullptr, (void **)&p_device->audio_client);
hr = output_device->Activate(IID_IAudioClient3, CLSCTX_ALL, nullptr, (void **)&p_device->audio_client);
if (hr != S_OK) {
// IID_IAudioClient3 will never activate on OS versions before Windows 10.
// Older Windows versions should fall back gracefully.
using_audio_client_3 = false;
print_verbose("WASAPI: Couldn't activate device with IAudioClient3 interface, falling back to IAudioClient interface");
print_verbose("WASAPI: Couldn't activate output_device with IAudioClient3 interface, falling back to IAudioClient interface");
} else {
print_verbose("WASAPI: Activated device using IAudioClient3 interface");
print_verbose("WASAPI: Activated output_device using IAudioClient3 interface");
}
}
if (!using_audio_client_3) {
hr = device->Activate(IID_IAudioClient, CLSCTX_ALL, nullptr, (void **)&p_device->audio_client);
hr = output_device->Activate(IID_IAudioClient, CLSCTX_ALL, nullptr, (void **)&p_device->audio_client);
}
SAFE_RELEASE(device)
SAFE_RELEASE(output_device)
if (p_reinit) {
if (hr != S_OK) {
@ -339,7 +339,7 @@ Error AudioDriverWASAPI::audio_device_init(AudioDeviceWASAPI *p_device, bool p_c
WAVEFORMATEX *closest = nullptr;
hr = p_device->audio_client->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, pwfex, &closest);
if (hr == S_FALSE) {
WARN_PRINT("WASAPI: Mix format is not supported by the Device");
WARN_PRINT("WASAPI: Mix format is not supported by the output_device");
if (closest) {
print_verbose("WASAPI: closest->wFormatTag = " + itos(closest->wFormatTag));
print_verbose("WASAPI: closest->nChannels = " + itos(closest->nChannels));
@ -385,14 +385,14 @@ Error AudioDriverWASAPI::audio_device_init(AudioDeviceWASAPI *p_device, bool p_c
pwfex->nSamplesPerSec = mix_rate;
pwfex->nAvgBytesPerSec = pwfex->nSamplesPerSec * pwfex->nChannels * (pwfex->wBitsPerSample / 8);
}
hr = p_device->audio_client->Initialize(AUDCLNT_SHAREMODE_SHARED, streamflags, p_capture ? REFTIMES_PER_SEC : 0, 0, pwfex, nullptr);
hr = p_device->audio_client->Initialize(AUDCLNT_SHAREMODE_SHARED, streamflags, p_input ? REFTIMES_PER_SEC : 0, 0, pwfex, nullptr);
ERR_FAIL_COND_V_MSG(hr != S_OK, ERR_CANT_OPEN, "WASAPI: Initialize failed with error 0x" + String::num_uint64(hr, 16) + ".");
UINT32 max_frames;
hr = p_device->audio_client->GetBufferSize(&max_frames);
ERR_FAIL_COND_V(hr != S_OK, ERR_CANT_OPEN);
// Due to WASAPI Shared Mode we have no control of the buffer size
if (!p_capture) {
if (!p_input) {
buffer_frames = max_frames;
int64_t latency = 0;
@ -421,8 +421,8 @@ Error AudioDriverWASAPI::audio_device_init(AudioDeviceWASAPI *p_device, bool p_c
if (hr != S_OK) {
print_verbose("WASAPI: GetSharedModeEnginePeriod failed with error 0x" + String::num_uint64(hr, 16) + ", falling back to IAudioClient.");
CoTaskMemFree(pwfex);
SAFE_RELEASE(device)
return audio_device_init(p_device, p_capture, p_reinit, true);
SAFE_RELEASE(output_device)
return audio_device_init(p_device, p_input, p_reinit, true);
}
// Period frames must be an integral multiple of fundamental_period_frames or IAudioClient3 initialization will fail,
@ -443,8 +443,8 @@ Error AudioDriverWASAPI::audio_device_init(AudioDeviceWASAPI *p_device, bool p_c
if (hr != S_OK) {
print_verbose("WASAPI: InitializeSharedAudioStream failed with error 0x" + String::num_uint64(hr, 16) + ", falling back to IAudioClient.");
CoTaskMemFree(pwfex);
SAFE_RELEASE(device);
return audio_device_init(p_device, p_capture, p_reinit, true);
SAFE_RELEASE(output_device);
return audio_device_init(p_device, p_input, p_reinit, true);
} else {
uint32_t output_latency_in_frames;
WAVEFORMATEX *current_pwfex;
@ -455,13 +455,13 @@ Error AudioDriverWASAPI::audio_device_init(AudioDeviceWASAPI *p_device, bool p_c
} else {
print_verbose("WASAPI: GetCurrentSharedModeEnginePeriod failed with error 0x" + String::num_uint64(hr, 16) + ", falling back to IAudioClient.");
CoTaskMemFree(pwfex);
SAFE_RELEASE(device);
return audio_device_init(p_device, p_capture, p_reinit, true);
SAFE_RELEASE(output_device);
return audio_device_init(p_device, p_input, p_reinit, true);
}
}
}
if (p_capture) {
if (p_input) {
hr = p_device->audio_client->GetService(IID_IAudioCaptureClient, (void **)&p_device->capture_client);
} else {
hr = p_device->audio_client->GetService(IID_IAudioRenderClient, (void **)&p_device->render_client);
@ -470,12 +470,12 @@ Error AudioDriverWASAPI::audio_device_init(AudioDeviceWASAPI *p_device, bool p_c
// Free memory
CoTaskMemFree(pwfex);
SAFE_RELEASE(device)
SAFE_RELEASE(output_device)
return OK;
}
Error AudioDriverWASAPI::init_render_device(bool p_reinit) {
Error AudioDriverWASAPI::init_output_device(bool p_reinit) {
Error err = audio_device_init(&audio_output, false, p_reinit);
if (err != OK) {
return err;
@ -507,7 +507,7 @@ Error AudioDriverWASAPI::init_render_device(bool p_reinit) {
return OK;
}
Error AudioDriverWASAPI::init_capture_device(bool p_reinit) {
Error AudioDriverWASAPI::init_input_device(bool p_reinit) {
Error err = audio_device_init(&audio_input, true, p_reinit);
if (err != OK) {
return err;
@ -538,11 +538,11 @@ Error AudioDriverWASAPI::audio_device_finish(AudioDeviceWASAPI *p_device) {
return OK;
}
Error AudioDriverWASAPI::finish_render_device() {
Error AudioDriverWASAPI::finish_output_device() {
return audio_device_finish(&audio_output);
}
Error AudioDriverWASAPI::finish_capture_device() {
Error AudioDriverWASAPI::finish_input_device() {
return audio_device_finish(&audio_input);
}
@ -551,9 +551,9 @@ Error AudioDriverWASAPI::init() {
target_latency_ms = GLOBAL_GET("audio/driver/output_latency");
Error err = init_render_device();
Error err = init_output_device();
if (err != OK) {
ERR_PRINT("WASAPI: init_render_device error");
ERR_PRINT("WASAPI: init_output_device error");
}
exit_thread.clear();
@ -575,7 +575,7 @@ AudioDriver::SpeakerMode AudioDriverWASAPI::get_speaker_mode() const {
return get_speaker_mode_by_total_channels(channels);
}
PackedStringArray AudioDriverWASAPI::audio_device_get_list(bool p_capture) {
PackedStringArray AudioDriverWASAPI::audio_device_get_list(bool p_input) {
PackedStringArray list;
IMMDeviceCollection *devices = nullptr;
IMMDeviceEnumerator *enumerator = nullptr;
@ -587,7 +587,7 @@ PackedStringArray AudioDriverWASAPI::audio_device_get_list(bool p_capture) {
HRESULT hr = CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_ALL, IID_IMMDeviceEnumerator, (void **)&enumerator);
ERR_FAIL_COND_V(hr != S_OK, PackedStringArray());
hr = enumerator->EnumAudioEndpoints(p_capture ? eCapture : eRender, DEVICE_STATE_ACTIVE, &devices);
hr = enumerator->EnumAudioEndpoints(p_input ? eCapture : eRender, DEVICE_STATE_ACTIVE, &devices);
ERR_FAIL_COND_V(hr != S_OK, PackedStringArray());
UINT count = 0;
@ -595,13 +595,13 @@ PackedStringArray AudioDriverWASAPI::audio_device_get_list(bool p_capture) {
ERR_FAIL_COND_V(hr != S_OK, PackedStringArray());
for (ULONG i = 0; i < count; i++) {
IMMDevice *device = nullptr;
IMMDevice *output_device = nullptr;
hr = devices->Item(i, &device);
hr = devices->Item(i, &output_device);
ERR_BREAK(hr != S_OK);
IPropertyStore *props = nullptr;
hr = device->OpenPropertyStore(STGM_READ, &props);
hr = output_device->OpenPropertyStore(STGM_READ, &props);
ERR_BREAK(hr != S_OK);
PROPVARIANT propvar;
@ -614,7 +614,7 @@ PackedStringArray AudioDriverWASAPI::audio_device_get_list(bool p_capture) {
PropVariantClear(&propvar);
props->Release();
device->Release();
output_device->Release();
}
devices->Release();
@ -622,11 +622,11 @@ PackedStringArray AudioDriverWASAPI::audio_device_get_list(bool p_capture) {
return list;
}
PackedStringArray AudioDriverWASAPI::get_device_list() {
PackedStringArray AudioDriverWASAPI::get_output_device_list() {
return audio_device_get_list(false);
}
String AudioDriverWASAPI::get_device() {
String AudioDriverWASAPI::get_output_device() {
lock();
String name = audio_output.device_name;
unlock();
@ -634,9 +634,9 @@ String AudioDriverWASAPI::get_device() {
return name;
}
void AudioDriverWASAPI::set_device(String device) {
void AudioDriverWASAPI::set_output_device(String output_device) {
lock();
audio_output.new_device = device;
audio_output.new_device = output_device;
unlock();
}
@ -769,13 +769,13 @@ void AudioDriverWASAPI::thread_func(void *p_udata) {
avail_frames -= write_frames;
written_frames += write_frames;
} else if (hr == AUDCLNT_E_DEVICE_INVALIDATED) {
// Device is not valid anymore, reopen it
// output_device is not valid anymore, reopen it
Error err = ad->finish_render_device();
Error err = ad->finish_output_device();
if (err != OK) {
ERR_PRINT("WASAPI: finish_render_device error");
ERR_PRINT("WASAPI: finish_output_device error");
} else {
// We reopened the device and samples_in may have resized, so invalidate the current avail_frames
// We reopened the output device and samples_in may have resized, so invalidate the current avail_frames
avail_frames = 0;
}
} else {
@ -790,37 +790,37 @@ void AudioDriverWASAPI::thread_func(void *p_udata) {
}
if (invalidated) {
// Device is not valid anymore
WARN_PRINT("WASAPI: Current device invalidated, closing device");
// output_device is not valid anymore
WARN_PRINT("WASAPI: Current output_device invalidated, closing output_device");
Error err = ad->finish_render_device();
Error err = ad->finish_output_device();
if (err != OK) {
ERR_PRINT("WASAPI: finish_render_device error");
ERR_PRINT("WASAPI: finish_output_device error");
}
}
}
// If we're using the Default device and it changed finish it so we'll re-init the device
if (ad->audio_output.device_name == "Default" && default_render_device_changed) {
Error err = ad->finish_render_device();
// If we're using the Default output device and it changed finish it so we'll re-init the output device
if (ad->audio_output.device_name == "Default" && default_output_device_changed) {
Error err = ad->finish_output_device();
if (err != OK) {
ERR_PRINT("WASAPI: finish_render_device error");
ERR_PRINT("WASAPI: finish_output_device error");
}
default_render_device_changed = false;
default_output_device_changed = false;
}
// User selected a new device, finish the current one so we'll init the new device
// User selected a new output device, finish the current one so we'll init the new output device
if (ad->audio_output.device_name != ad->audio_output.new_device) {
ad->audio_output.device_name = ad->audio_output.new_device;
Error err = ad->finish_render_device();
Error err = ad->finish_output_device();
if (err != OK) {
ERR_PRINT("WASAPI: finish_render_device error");
ERR_PRINT("WASAPI: finish_output_device error");
}
}
if (!ad->audio_output.audio_client) {
Error err = ad->init_render_device(true);
Error err = ad->init_output_device(true);
if (err == OK) {
ad->start();
}
@ -873,29 +873,29 @@ void AudioDriverWASAPI::thread_func(void *p_udata) {
}
}
// If we're using the Default device and it changed finish it so we'll re-init the device
if (ad->audio_input.device_name == "Default" && default_capture_device_changed) {
Error err = ad->finish_capture_device();
// If we're using the Default output device and it changed finish it so we'll re-init the output device
if (ad->audio_input.device_name == "Default" && default_input_device_changed) {
Error err = ad->finish_input_device();
if (err != OK) {
ERR_PRINT("WASAPI: finish_capture_device error");
ERR_PRINT("WASAPI: finish_input_device error");
}
default_capture_device_changed = false;
default_input_device_changed = false;
}
// User selected a new device, finish the current one so we'll init the new device
// User selected a new input device, finish the current one so we'll init the new input device
if (ad->audio_input.device_name != ad->audio_input.new_device) {
ad->audio_input.device_name = ad->audio_input.new_device;
Error err = ad->finish_capture_device();
Error err = ad->finish_input_device();
if (err != OK) {
ERR_PRINT("WASAPI: finish_capture_device error");
ERR_PRINT("WASAPI: finish_input_device error");
}
}
if (!ad->audio_input.audio_client) {
Error err = ad->init_capture_device(true);
Error err = ad->init_input_device(true);
if (err == OK) {
ad->capture_start();
ad->input_start();
}
}
}
@ -933,14 +933,14 @@ void AudioDriverWASAPI::finish() {
exit_thread.set();
thread.wait_to_finish();
finish_capture_device();
finish_render_device();
finish_input_device();
finish_output_device();
}
Error AudioDriverWASAPI::capture_start() {
Error err = init_capture_device();
Error AudioDriverWASAPI::input_start() {
Error err = init_input_device();
if (err != OK) {
ERR_PRINT("WASAPI: init_capture_device error");
ERR_PRINT("WASAPI: init_input_device error");
return err;
}
@ -953,7 +953,7 @@ Error AudioDriverWASAPI::capture_start() {
return OK;
}
Error AudioDriverWASAPI::capture_stop() {
Error AudioDriverWASAPI::input_stop() {
if (audio_input.active.is_set()) {
audio_input.audio_client->Stop();
audio_input.active.clear();
@ -964,17 +964,17 @@ Error AudioDriverWASAPI::capture_stop() {
return FAILED;
}
void AudioDriverWASAPI::capture_set_device(const String &p_name) {
void AudioDriverWASAPI::set_input_device(const String &p_name) {
lock();
audio_input.new_device = p_name;
unlock();
}
PackedStringArray AudioDriverWASAPI::capture_get_device_list() {
PackedStringArray AudioDriverWASAPI::get_input_device_list() {
return audio_device_get_list(true);
}
String AudioDriverWASAPI::capture_get_device() {
String AudioDriverWASAPI::get_input_device() {
lock();
String name = audio_input.device_name;
unlock();

View file

@ -47,8 +47,8 @@ class AudioDriverWASAPI : public AudioDriver {
class AudioDeviceWASAPI {
public:
IAudioClient *audio_client = nullptr;
IAudioRenderClient *render_client = nullptr;
IAudioCaptureClient *capture_client = nullptr;
IAudioRenderClient *render_client = nullptr; // Output
IAudioCaptureClient *capture_client = nullptr; // Input
SafeFlag active;
WORD format_tag = 0;
@ -56,8 +56,8 @@ class AudioDriverWASAPI : public AudioDriver {
unsigned int channels = 0;
unsigned int frame_size = 0;
String device_name = "Default";
String new_device = "Default";
String device_name = "Default"; // Output OR Input
String new_device = "Default"; // Output OR Input
AudioDeviceWASAPI() {}
};
@ -83,15 +83,15 @@ class AudioDriverWASAPI : public AudioDriver {
static _FORCE_INLINE_ int32_t read_sample(WORD format_tag, int bits_per_sample, BYTE *buffer, int i);
static void thread_func(void *p_udata);
Error init_render_device(bool p_reinit = false);
Error init_capture_device(bool p_reinit = false);
Error init_output_device(bool p_reinit = false);
Error init_input_device(bool p_reinit = false);
Error finish_render_device();
Error finish_capture_device();
Error finish_output_device();
Error finish_input_device();
Error audio_device_init(AudioDeviceWASAPI *p_device, bool p_capture, bool p_reinit, bool p_no_audio_client_3 = false);
Error audio_device_init(AudioDeviceWASAPI *p_device, bool p_input, bool p_reinit, bool p_no_audio_client_3 = false);
Error audio_device_finish(AudioDeviceWASAPI *p_device);
PackedStringArray audio_device_get_list(bool p_capture);
PackedStringArray audio_device_get_list(bool p_input);
public:
virtual const char *get_name() const {
@ -103,18 +103,18 @@ public:
virtual int get_mix_rate() const;
virtual float get_latency();
virtual SpeakerMode get_speaker_mode() const;
virtual PackedStringArray get_device_list();
virtual String get_device();
virtual void set_device(String device);
virtual PackedStringArray get_output_device_list();
virtual String get_output_device();
virtual void set_output_device(String output_device);
virtual void lock();
virtual void unlock();
virtual void finish();
virtual Error capture_start();
virtual Error capture_stop();
virtual PackedStringArray capture_get_device_list();
virtual void capture_set_device(const String &p_name);
virtual String capture_get_device();
virtual Error input_start();
virtual Error input_stop();
virtual PackedStringArray get_input_device_list();
virtual void set_input_device(const String &p_name);
virtual String get_input_device();
AudioDriverWASAPI();
};

View file

@ -1593,7 +1593,7 @@ void EditorFileSystem::_update_pending_script_classes() {
_update_script_classes();
} else {
// In case the class cache file was removed somehow, regenerate it.
if (ScriptServer::has_global_classes() && !FileAccess::exists(ScriptServer::get_global_class_cache_file_path())) {
if (!FileAccess::exists(ScriptServer::get_global_class_cache_file_path())) {
ScriptServer::save_global_classes();
}
}

View file

@ -38,6 +38,7 @@
#include "editor/editor_undo_redo_manager.h"
#include "scene/animation/animation_blend_tree.h"
#include "scene/gui/check_box.h"
#include "scene/gui/option_button.h"
#include "scene/gui/panel_container.h"
StringName AnimationNodeBlendSpace1DEditor::get_blend_position_path() const {
@ -335,6 +336,7 @@ void AnimationNodeBlendSpace1DEditor::_update_space() {
min_value->set_value(blend_space->get_min_space());
sync->set_pressed(blend_space->is_using_sync());
interpolation->select(blend_space->get_blend_mode());
label_value->set_text(blend_space->get_value_label());
@ -361,6 +363,8 @@ void AnimationNodeBlendSpace1DEditor::_config_changed(double) {
undo_redo->add_undo_method(blend_space.ptr(), "set_snap", blend_space->get_snap());
undo_redo->add_do_method(blend_space.ptr(), "set_use_sync", sync->is_pressed());
undo_redo->add_undo_method(blend_space.ptr(), "set_use_sync", blend_space->is_using_sync());
undo_redo->add_do_method(blend_space.ptr(), "set_blend_mode", interpolation->get_selected());
undo_redo->add_undo_method(blend_space.ptr(), "set_blend_mode", blend_space->get_blend_mode());
undo_redo->add_do_method(this, "_update_space");
undo_redo->add_undo_method(this, "_update_space");
undo_redo->commit_action();
@ -579,6 +583,10 @@ void AnimationNodeBlendSpace1DEditor::_notification(int p_what) {
tool_erase->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons")));
snap->set_icon(get_theme_icon(SNAME("SnapGrid"), SNAME("EditorIcons")));
open_editor->set_icon(get_theme_icon(SNAME("Edit"), SNAME("EditorIcons")));
interpolation->clear();
interpolation->add_icon_item(get_theme_icon(SNAME("TrackContinuous"), SNAME("EditorIcons")), "", 0);
interpolation->add_icon_item(get_theme_icon(SNAME("TrackDiscrete"), SNAME("EditorIcons")), "", 1);
interpolation->add_icon_item(get_theme_icon(SNAME("TrackCapture"), SNAME("EditorIcons")), "", 2);
} break;
case NOTIFICATION_PROCESS: {
@ -639,6 +647,7 @@ void AnimationNodeBlendSpace1DEditor::edit(const Ref<AnimationNode> &p_node) {
min_value->set_editable(!read_only);
max_value->set_editable(!read_only);
sync->set_disabled(read_only);
interpolation->set_disabled(read_only);
}
AnimationNodeBlendSpace1DEditor *AnimationNodeBlendSpace1DEditor::singleton = nullptr;
@ -707,6 +716,13 @@ AnimationNodeBlendSpace1DEditor::AnimationNodeBlendSpace1DEditor() {
top_hb->add_child(sync);
sync->connect("toggled", callable_mp(this, &AnimationNodeBlendSpace1DEditor::_config_changed));
top_hb->add_child(memnew(VSeparator));
top_hb->add_child(memnew(Label(TTR("Blend:"))));
interpolation = memnew(OptionButton);
top_hb->add_child(interpolation);
interpolation->connect("item_selected", callable_mp(this, &AnimationNodeBlendSpace1DEditor::_config_changed));
edit_hb = memnew(HBoxContainer);
top_hb->add_child(edit_hb);
edit_hb->add_child(memnew(VSeparator));

View file

@ -41,6 +41,7 @@
#include "scene/gui/tree.h"
class CheckBox;
class OptionButton;
class PanelContainer;
class AnimationNodeBlendSpace1DEditor : public AnimationTreeNodeEditorPlugin {
@ -66,6 +67,7 @@ class AnimationNodeBlendSpace1DEditor : public AnimationTreeNodeEditorPlugin {
SpinBox *min_value = nullptr;
CheckBox *sync = nullptr;
OptionButton *interpolation = nullptr;
HBoxContainer *edit_hb = nullptr;
SpinBox *edit_value = nullptr;

View file

@ -32,10 +32,11 @@
#include "modules/modules_enabled.gen.h"
const int ERROR_CODE = 77;
#ifndef DISABLE_DEPRECATED
#ifdef MODULE_REGEX_ENABLED
const int ERROR_CODE = 77;
#include "modules/regex/regex.h"
#include "core/io/dir_access.h"
@ -44,7 +45,7 @@ const int ERROR_CODE = 77;
#include "core/templates/list.h"
#include "core/templates/local_vector.h"
static const char *enum_renames[][2] = {
const char *ProjectConverter3To4::enum_renames[][2] = {
//// constants
{ "TYPE_COLOR_ARRAY", "TYPE_PACKED_COLOR_ARRAY" },
{ "TYPE_FLOAT64_ARRAY", "TYPE_PACKED_FLOAT64_ARRAY" },
@ -164,7 +165,7 @@ static const char *enum_renames[][2] = {
{ nullptr, nullptr },
};
static const char *gdscript_function_renames[][2] = {
const char *ProjectConverter3To4::gdscript_function_renames[][2] = {
// { "_set_name", "get_tracker_name"}, // XRPositionalTracker - CameraFeed use this
// { "_unhandled_input", "_unhandled_key_input"}, // BaseButton, ViewportContainer broke Node, FileDialog,SubViewportContainer
// { "create_gizmo", "_create_gizmo"}, // EditorNode3DGizmoPlugin - may be used
@ -240,6 +241,9 @@ static const char *gdscript_function_renames[][2] = {
{ "can_generate_small_preview", "_can_generate_small_preview" }, // EditorResourcePreviewGenerator
{ "can_instance", "can_instantiate" }, // PackedScene, Script
{ "canvas_light_set_scale", "canvas_light_set_texture_scale" }, // RenderingServer
{ "capture_get_device", "get_input_device" }, // AudioServer
{ "capture_get_device_list", "get_input_device_list" }, // AudioServer
{ "capture_set_device", "set_input_device" }, // AudioServer
{ "center_viewport_to_cursor", "center_viewport_to_caret" }, // TextEdit
{ "change_scene", "change_scene_to_file" }, // SceneTree
{ "change_scene_to", "change_scene_to_packed" }, // SceneTree
@ -300,6 +304,8 @@ static const char *gdscript_function_renames[][2] = {
{ "get_cursor_position", "get_caret_column" }, // LineEdit
{ "get_d", "get_distance" }, // LineShape2D
{ "get_depth_bias_enable", "get_depth_bias_enabled" }, // RDPipelineRasterizationState
{ "get_device", "get_output_device" }, // AudioServer
{ "get_device_list", "get_output_device_list" }, // AudioServer
{ "get_drag_data", "_get_drag_data" }, // Control
{ "get_editor_viewport", "get_editor_main_screen" }, // EditorPlugin
{ "get_enabled_focus_mode", "get_focus_mode" }, // BaseButton
@ -498,6 +504,7 @@ static const char *gdscript_function_renames[][2] = {
{ "set_cursor_position", "set_caret_column" }, // LineEdit
{ "set_d", "set_distance" }, // WorldMarginShape2D
{ "set_depth_bias_enable", "set_depth_bias_enabled" }, // RDPipelineRasterizationState
{ "set_device", "set_output_device" }, // AudioServer
{ "set_doubleclick", "set_double_click" }, // InputEventMouseButton
{ "set_draw_red", "set_draw_warning" }, // EditorProperty
{ "set_enable_follow_smoothing", "set_position_smoothing_enabled" }, // Camera2D
@ -619,7 +626,7 @@ static const char *gdscript_function_renames[][2] = {
};
// gdscript_function_renames clone with CamelCase
static const char *csharp_function_renames[][2] = {
const char *ProjectConverter3To4::csharp_function_renames[][2] = {
// { "_SetName", "GetTrackerName"}, // XRPositionalTracker - CameraFeed use this
// { "_UnhandledInput", "_UnhandledKeyInput"}, // BaseButton, ViewportContainer broke Node, FileDialog,SubViewportContainer
// { "CreateGizmo", "_CreateGizmo"}, // EditorNode3DGizmoPlugin - may be used
@ -696,6 +703,9 @@ static const char *csharp_function_renames[][2] = {
{ "CanGenerateSmallPreview", "_CanGenerateSmallPreview" }, // EditorResourcePreviewGenerator
{ "CanInstance", "CanInstantiate" }, // PackedScene, Script
{ "CanvasLightSetScale", "CanvasLightSetTextureScale" }, // RenderingServer
{ "CaptureGetDevice", "GetInputDevice" }, // AudioServer
{ "CaptureGetDeviceList", "GetInputDeviceList" }, // AudioServer
{ "CaptureSetDevice", "SetInputDevice" }, // AudioServer
{ "CenterViewportToCursor", "CenterViewportToCaret" }, // TextEdit
{ "ChangeScene", "ChangeSceneToFile" }, // SceneTree
{ "ChangeSceneTo", "ChangeSceneToPacked" }, // SceneTree
@ -753,6 +763,8 @@ static const char *csharp_function_renames[][2] = {
{ "GetCursorPosition", "GetCaretColumn" }, // LineEdit
{ "GetD", "GetDistance" }, // LineShape2D
{ "GetDepthBiasEnable", "GetDepthBiasEnabled" }, // RDPipelineRasterizationState
{ "GetDevice", "GetOutputDevice" }, // AudioServer
{ "GetDeviceList", "GetOutputDeviceList" }, // AudioServer
{ "GetDragDataFw", "_GetDragDataFw" }, // ScriptEditor
{ "GetEditorViewport", "GetViewport" }, // EditorPlugin
{ "GetEnabledFocusMode", "GetFocusMode" }, // BaseButton
@ -941,6 +953,7 @@ static const char *csharp_function_renames[][2] = {
{ "SetCursorPosition", "SetCaretColumn" }, // LineEdit
{ "SetD", "SetDistance" }, // WorldMarginShape2D
{ "SetDepthBiasEnable", "SetDepthBiasEnabled" }, // RDPipelineRasterizationState
{ "SetDevice", "SetOutputDevice" }, // AudioServer
{ "SetDoubleclick", "SetDoubleClick" }, // InputEventMouseButton
{ "SetEnableFollowSmoothing", "SetFollowSmoothingEnabled" }, // Camera2D
{ "SetEnabledFocusMode", "SetFocusMode" }, // BaseButton
@ -1056,7 +1069,7 @@ static const char *csharp_function_renames[][2] = {
};
// Some needs to be disabled, because users can use this names as variables
static const char *gdscript_properties_renames[][2] = {
const char *ProjectConverter3To4::gdscript_properties_renames[][2] = {
// // { "d", "distance" }, //WorldMarginShape2D - TODO, looks that polish letters ą ę are treaten as space, not as letter, so `będą` are renamed to `będistanceą`
// // {"alt","alt_pressed"}, // This may broke a lot of comments and user variables
// // {"command","command_pressed"},// This may broke a lot of comments and user variables
@ -1069,6 +1082,7 @@ static const char *gdscript_properties_renames[][2] = {
// // {"shift","shift_pressed"},// This may broke a lot of comments and user variables
// { "autowrap", "autowrap_mode" }, // Label
// { "cast_to", "target_position" }, // RayCast2D, RayCast3D
// { "device", "output_device"}, // AudioServer - Too vague, most likely breaks comments & variables
// { "doubleclick", "double_click" }, // InputEventMouseButton
// { "group", "button_group" }, // BaseButton
// { "process_mode", "process_callback" }, // AnimationTree, Camera2D
@ -1084,6 +1098,7 @@ static const char *gdscript_properties_renames[][2] = {
{ "bbcode_text", "text" }, // RichTextLabel
{ "bg", "panel" }, // Theme
{ "bg_focus", "focus" }, // Theme
{ "capture_device", "input_device" }, // AudioServer
{ "caret_blink_speed", "caret_blink_interval" }, // TextEdit, LineEdit
{ "caret_moving_by_right_click", "caret_move_on_right_click" }, // TextEdit
{ "caret_position", "caret_column" }, // LineEdit
@ -1173,7 +1188,7 @@ static const char *gdscript_properties_renames[][2] = {
};
// Some needs to be disabled, because users can use this names as variables
static const char *csharp_properties_renames[][2] = {
const char *ProjectConverter3To4::csharp_properties_renames[][2] = {
// // { "D", "Distance" }, //WorldMarginShape2D - TODO, looks that polish letters ą ę are treaten as space, not as letter, so `będą` are renamed to `będistanceą`
// // {"Alt","AltPressed"}, // This may broke a lot of comments and user variables
// // {"Command","CommandPressed"},// This may broke a lot of comments and user variables
@ -1278,7 +1293,7 @@ static const char *csharp_properties_renames[][2] = {
{ nullptr, nullptr },
};
static const char *gdscript_signals_renames[][2] = {
const char *ProjectConverter3To4::gdscript_signals_renames[][2] = {
// {"instantiate","instance"}, // FileSystemDock
// { "hide", "hidden" }, // CanvasItem - function with same name exists
// { "tween_all_completed","loop_finished"}, // Tween - TODO, not sure
@ -1303,7 +1318,7 @@ static const char *gdscript_signals_renames[][2] = {
{ nullptr, nullptr },
};
static const char *csharp_signals_renames[][2] = {
const char *ProjectConverter3To4::csharp_signals_renames[][2] = {
// {"Instantiate","Instance"}, // FileSystemDock
// { "Hide", "Hidden" }, // CanvasItem - function with same name exists
// { "TweenAllCompleted","LoopFinished"}, // Tween - TODO, not sure
@ -1327,7 +1342,7 @@ static const char *csharp_signals_renames[][2] = {
};
static const char *project_settings_renames[][2] = {
const char *ProjectConverter3To4::project_settings_renames[][2] = {
{ "audio/channel_disable_threshold_db", "audio/buses/channel_disable_threshold_db" },
{ "audio/channel_disable_time", "audio/buses/channel_disable_time" },
{ "audio/default_bus_layout", "audio/buses/default_bus_layout" },
@ -1372,7 +1387,7 @@ static const char *project_settings_renames[][2] = {
{ nullptr, nullptr },
};
static const char *input_map_renames[][2] = {
const char *ProjectConverter3To4::input_map_renames[][2] = {
{ ",\"alt\":", ",\"alt_pressed\":" },
{ ",\"shift\":", ",\"shift_pressed\":" },
{ ",\"control\":", ",\"ctrl_pressed\":" },
@ -1384,7 +1399,7 @@ static const char *input_map_renames[][2] = {
{ nullptr, nullptr },
};
static const char *builtin_types_renames[][2] = {
const char *ProjectConverter3To4::builtin_types_renames[][2] = {
{ "PoolByteArray", "PackedByteArray" },
{ "PoolColorArray", "PackedColorArray" },
{ "PoolIntArray", "PackedInt32Array" },
@ -1398,7 +1413,7 @@ static const char *builtin_types_renames[][2] = {
{ nullptr, nullptr },
};
static const char *shaders_renames[][2] = {
const char *ProjectConverter3To4::shaders_renames[][2] = {
{ "ALPHA_SCISSOR", "ALPHA_SCISSOR_THRESHOLD" },
{ "CAMERA_MATRIX", "INV_VIEW_MATRIX" },
{ "INV_CAMERA_MATRIX", "VIEW_MATRIX" },
@ -1416,7 +1431,7 @@ static const char *shaders_renames[][2] = {
{ nullptr, nullptr },
};
static const char *class_renames[][2] = {
const char *ProjectConverter3To4::class_renames[][2] = {
// { "BulletPhysicsDirectBodyState", "BulletPhysicsDirectBodyState3D" }, // Class is not visible in ClassDB
// { "BulletPhysicsServer", "BulletPhysicsServer3D" }, // Class is not visible in ClassDB
// { "GDScriptFunctionState", "Node3D" }, // TODO - not sure to which should be changed
@ -1641,7 +1656,7 @@ static const char *class_renames[][2] = {
{ nullptr, nullptr },
};
static const char *color_renames[][2] = {
const char *ProjectConverter3To4::ProjectConverter3To4::color_renames[][2] = {
{ "aliceblue", "ALICE_BLUE" },
{ "antiquewhite", "ANTIQUE_WHITE" },
{ "aqua", "AQUA" },
@ -4350,3 +4365,4 @@ int ProjectConverter3To4::validate_conversion() {
}
#endif // MODULE_REGEX_ENABLED
#endif // DISABLE_DEPRECATED

View file

@ -29,6 +29,7 @@
/**************************************************************************/
#ifndef PROJECT_CONVERTER_3_TO_4_H
#ifndef DISABLE_DEPRECATED
#define PROJECT_CONVERTER_3_TO_4_H
#include "core/io/file_access.h"
@ -41,6 +42,19 @@ class RegEx;
class ProjectConverter3To4 {
public:
class RegExContainer;
static const char *enum_renames[][2];
static const char *gdscript_function_renames[][2];
static const char *csharp_function_renames[][2];
static const char *gdscript_properties_renames[][2];
static const char *csharp_properties_renames[][2];
static const char *gdscript_signals_renames[][2];
static const char *csharp_signals_renames[][2];
static const char *project_settings_renames[][2];
static const char *input_map_renames[][2];
static const char *builtin_types_renames[][2];
static const char *shaders_renames[][2];
static const char *class_renames[][2];
static const char *color_renames[][2];
private:
uint64_t maximum_file_size;
@ -97,4 +111,6 @@ public:
int convert();
};
#endif // DISABLE_DEPRECATED
#endif // PROJECT_CONVERTER_3_TO_4_H

View file

@ -563,6 +563,7 @@
[/codeblock]
[b]Note:[/b] Only the script can have a custom icon. Inner classes are not supported.
[b]Note:[/b] As annotations describe their subject, the [code]@icon[/code] annotation must be placed before the class definition and inheritance.
[b]Note:[/b] Unlike other annotations, the argument of the [code]@icon[/code] annotation must be a string literal (constant expressions are not supported).
</description>
</annotation>
<annotation name="@onready">

View file

@ -1972,17 +1972,40 @@ void GDScriptAnalyzer::resolve_return(GDScriptParser::ReturnNode *p_return) {
}
if (p_return->return_value != nullptr) {
reduce_expression(p_return->return_value);
if (p_return->return_value->type == GDScriptParser::Node::ARRAY && has_expected_type && expected_type.has_container_element_type()) {
update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_return->return_value), expected_type.get_container_element_type());
bool is_void_function = has_expected_type && expected_type.is_hard_type() && expected_type.kind == GDScriptParser::DataType::BUILTIN && expected_type.builtin_type == Variant::NIL;
bool is_call = p_return->return_value->type == GDScriptParser::Node::CALL;
if (is_void_function && is_call) {
// Pretend the call is a root expression to allow those that are "void".
reduce_call(static_cast<GDScriptParser::CallNode *>(p_return->return_value), false, true);
} else {
reduce_expression(p_return->return_value);
}
if (has_expected_type && expected_type.is_hard_type() && expected_type.kind == GDScriptParser::DataType::BUILTIN && expected_type.builtin_type == Variant::NIL) {
push_error("A void function cannot return a value.", p_return);
if (is_void_function) {
p_return->void_return = true;
const GDScriptParser::DataType &return_type = p_return->return_value->datatype;
if (is_call && !return_type.is_hard_type()) {
String function_name = parser->current_function->identifier ? parser->current_function->identifier->name.operator String() : String("<anonymous function>");
String called_function_name = static_cast<GDScriptParser::CallNode *>(p_return->return_value)->function_name.operator String();
#ifdef DEBUG_ENABLED
parser->push_warning(p_return, GDScriptWarning::UNSAFE_VOID_RETURN, function_name, called_function_name);
#endif
mark_node_unsafe(p_return);
} else if (!is_call) {
push_error("A void function cannot return a value.", p_return);
}
result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
result.kind = GDScriptParser::DataType::BUILTIN;
result.builtin_type = Variant::NIL;
result.is_constant = true;
} else {
if (p_return->return_value->type == GDScriptParser::Node::ARRAY && has_expected_type && expected_type.has_container_element_type()) {
update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_return->return_value), expected_type.get_container_element_type());
}
if (has_expected_type && expected_type.is_hard_type() && p_return->return_value->is_constant) {
update_const_expression_builtin_type(p_return->return_value, expected_type, "return");
}
result = p_return->return_value->get_datatype();
}
if (has_expected_type && expected_type.is_hard_type() && p_return->return_value->is_constant) {
update_const_expression_builtin_type(p_return->return_value, expected_type, "return");
}
result = p_return->return_value->get_datatype();
} else {
// Return type is null by default.
result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
@ -2464,6 +2487,62 @@ void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_o
p_binary_op->set_datatype(result);
}
#ifdef TOOLS_ENABLED
#ifndef DISABLE_DEPRECATED
const char *GDScriptAnalyzer::get_rename_from_map(const char *map[][2], String key) {
for (int index = 0; map[index][0]; index++) {
if (map[index][0] == key) {
return map[index][1];
}
}
return nullptr;
}
// Checks if an identifier/function name has been renamed in Godot 4, uses ProjectConverter3To4 for rename map.
// Returns the new name if found, nullptr otherwise.
const char *GDScriptAnalyzer::check_for_renamed_identifier(String identifier, GDScriptParser::Node::Type type) {
switch (type) {
case GDScriptParser::Node::IDENTIFIER: {
// Check properties
const char *result = get_rename_from_map(ProjectConverter3To4::gdscript_properties_renames, identifier);
if (result) {
return result;
}
// Check enum values
result = get_rename_from_map(ProjectConverter3To4::enum_renames, identifier);
if (result) {
return result;
}
// Check color constants
result = get_rename_from_map(ProjectConverter3To4::color_renames, identifier);
if (result) {
return result;
}
// Check type names
result = get_rename_from_map(ProjectConverter3To4::class_renames, identifier);
if (result) {
return result;
}
return get_rename_from_map(ProjectConverter3To4::builtin_types_renames, identifier);
}
case GDScriptParser::Node::CALL: {
const char *result = get_rename_from_map(ProjectConverter3To4::gdscript_function_renames, identifier);
if (result) {
return result;
}
// Built-in Types are mistaken for function calls when the built-in type is not found.
// Check built-in types if function rename not found
return get_rename_from_map(ProjectConverter3To4::builtin_types_renames, identifier);
}
// Signal references don't get parsed through the GDScriptAnalyzer. No support for signal rename hints.
default:
// No rename found, return null
return nullptr;
}
}
#endif // DISABLE_DEPRECATED
#endif // TOOLS_ENABLED
void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_await, bool p_is_root) {
bool all_is_constant = true;
HashMap<int, GDScriptParser::ArrayNode *> arrays; // For array literal to potentially type when passing.
@ -2881,7 +2960,22 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
}
if (!found && (is_self || (base_type.is_hard_type() && base_type.kind == GDScriptParser::DataType::BUILTIN))) {
String base_name = is_self && !p_call->is_super ? "self" : base_type.to_string();
#ifdef TOOLS_ENABLED
#ifndef DISABLE_DEPRECATED
String rename_hint = String();
if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::Code::RENAMED_IN_GD4_HINT)).booleanize()) {
const char *renamed_function_name = check_for_renamed_identifier(p_call->function_name, p_call->type);
if (renamed_function_name) {
rename_hint = " " + vformat(R"(Did you mean to use "%s"?)", String(renamed_function_name) + "()");
}
}
push_error(vformat(R"*(Function "%s()" not found in base %s.%s)*", p_call->function_name, base_name, rename_hint), p_call->is_super ? p_call : p_call->callee);
#else // !DISABLE_DEPRECATED
push_error(vformat(R"*(Function "%s()" not found in base %s.)*", p_call->function_name, base_name), p_call->is_super ? p_call : p_call->callee);
#endif // DISABLE_DEPRECATED
#else
push_error(vformat(R"*(Function "%s()" not found in base %s.)*", p_call->function_name, base_name), p_call->is_super ? p_call : p_call->callee);
#endif
} else if (!found && (!p_call->is_super && base_type.is_hard_type() && base_type.kind == GDScriptParser::DataType::NATIVE && base_type.is_meta_type)) {
push_error(vformat(R"*(Static function "%s()" not found in base "%s".)*", p_call->function_name, base_type.native_type), p_call);
}
@ -3071,7 +3165,22 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
p_identifier->reduced_value = result;
p_identifier->set_datatype(type_from_variant(result, p_identifier));
} else if (base.is_hard_type()) {
push_error(vformat(R"(Cannot find constant "%s" on type "%s".)", name, base.to_string()), p_identifier);
#ifdef TOOLS_ENABLED
#ifndef DISABLE_DEPRECATED
String rename_hint = String();
if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::Code::RENAMED_IN_GD4_HINT)).booleanize()) {
const char *renamed_identifier_name = check_for_renamed_identifier(name, p_identifier->type);
if (renamed_identifier_name) {
rename_hint = " " + vformat(R"(Did you mean to use "%s"?)", renamed_identifier_name);
}
}
push_error(vformat(R"(Cannot find constant "%s" on base "%s".%s)", name, base.to_string(), rename_hint), p_identifier);
#else // !DISABLE_DEPRECATED
push_error(vformat(R"(Cannot find constant "%s" on base "%s".)", name, base.to_string()), p_identifier);
#endif // DISABLE_DEPRECATED
#else
push_error(vformat(R"(Cannot find constant "%s" on base "%s".)", name, base.to_string()), p_identifier);
#endif
}
} else {
switch (base.builtin_type) {
@ -3100,7 +3209,22 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
}
}
if (base.is_hard_type()) {
#ifdef TOOLS_ENABLED
#ifndef DISABLE_DEPRECATED
String rename_hint = String();
if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::Code::RENAMED_IN_GD4_HINT)).booleanize()) {
const char *renamed_identifier_name = check_for_renamed_identifier(name, p_identifier->type);
if (renamed_identifier_name) {
rename_hint = " " + vformat(R"(Did you mean to use "%s"?)", renamed_identifier_name);
}
}
push_error(vformat(R"(Cannot find property "%s" on base "%s".%s)", name, base.to_string(), rename_hint), p_identifier);
#else // !DISABLE_DEPRECATED
push_error(vformat(R"(Cannot find property "%s" on base "%s".)", name, base.to_string()), p_identifier);
#endif // DISABLE_DEPRECATED
#else
push_error(vformat(R"(Cannot find property "%s" on base "%s".)", name, base.to_string()), p_identifier);
#endif
}
}
}
@ -3439,7 +3563,22 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident
if (GDScriptUtilityFunctions::function_exists(name)) {
push_error(vformat(R"(Built-in function "%s" cannot be used as an identifier.)", name), p_identifier);
} else {
#ifdef TOOLS_ENABLED
#ifndef DISABLE_DEPRECATED
String rename_hint = String();
if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::Code::RENAMED_IN_GD4_HINT)).booleanize()) {
const char *renamed_identifier_name = check_for_renamed_identifier(name, p_identifier->type);
if (renamed_identifier_name) {
rename_hint = " " + vformat(R"(Did you mean to use "%s"?)", renamed_identifier_name);
}
}
push_error(vformat(R"(Identifier "%s" not declared in the current scope.%s)", name, rename_hint), p_identifier);
#else // !DISABLE_DEPRECATED
push_error(vformat(R"(Identifier "%s" not declared in the current scope.)", name), p_identifier);
#endif // DISABLE_DEPRECATED
#else
push_error(vformat(R"(Identifier "%s" not declared in the current scope.)", name), p_identifier);
#endif
}
GDScriptParser::DataType dummy;
dummy.kind = GDScriptParser::DataType::VARIANT;
@ -4708,11 +4847,6 @@ Ref<GDScriptParserRef> GDScriptAnalyzer::get_parser_for(const String &p_path) {
}
Error GDScriptAnalyzer::resolve_inheritance() {
// Apply annotations.
for (GDScriptParser::AnnotationNode *&E : parser->head->annotations) {
resolve_annotation(E);
E->apply(parser, parser->head);
}
return resolve_class_inheritance(parser->head, true);
}
@ -4746,6 +4880,12 @@ Error GDScriptAnalyzer::analyze() {
return err;
}
// Apply annotations.
for (GDScriptParser::AnnotationNode *&E : parser->head->annotations) {
resolve_annotation(E);
E->apply(parser, parser->head);
}
resolve_interface();
resolve_body();
if (!parser->errors.is_empty()) {

View file

@ -37,6 +37,10 @@
#include "gdscript_cache.h"
#include "gdscript_parser.h"
#ifdef TOOLS_ENABLED
#include "editor/project_converter_3_to_4.h"
#endif
class GDScriptAnalyzer {
GDScriptParser *parser = nullptr;
HashMap<String, Ref<GDScriptParserRef>> depended_parsers;
@ -133,6 +137,13 @@ class GDScriptAnalyzer {
bool is_shadowing(GDScriptParser::IdentifierNode *p_local, const String &p_context);
#endif
#ifdef TOOLS_ENABLED
#ifndef DISABLE_DEPRECATED
const char *get_rename_from_map(const char *map[][2], String key);
const char *check_for_renamed_identifier(String identifier, GDScriptParser::Node::Type type);
#endif // DISABLE_DEPRECATED
#endif // TOOLS_ENABLED
public:
Error resolve_inheritance();
Error resolve_interface();

View file

@ -1859,7 +1859,12 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
}
}
gen->write_return(return_value);
if (return_n->void_return) {
// Always return "null", even if the expression is a call to a void function.
gen->write_return(codegen.add_constant(Variant()));
} else {
gen->write_return(return_value);
}
if (return_value.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
codegen.generator->pop_temporary();
}

View file

@ -491,7 +491,12 @@ void GDScriptParser::parse_program() {
AnnotationNode *annotation = parse_annotation(AnnotationInfo::SCRIPT | AnnotationInfo::STANDALONE | AnnotationInfo::CLASS_LEVEL);
if (annotation != nullptr) {
if (annotation->applies_to(AnnotationInfo::SCRIPT)) {
head->annotations.push_back(annotation);
// `@icon` needs to be applied in the parser. See GH-72444.
if (annotation->name == SNAME("@icon")) {
annotation->apply(this, head);
} else {
head->annotations.push_back(annotation);
}
} else {
annotation_stack.push_back(annotation);
// This annotation must appear after script-level annotations
@ -809,7 +814,7 @@ void GDScriptParser::parse_class_body(bool p_is_multiline) {
if (previous.type != GDScriptTokenizer::Token::NEWLINE) {
push_error(R"(Expected newline after a standalone annotation.)");
}
if (annotation->name == "@export_category" || annotation->name == "@export_group" || annotation->name == "@export_subgroup") {
if (annotation->name == SNAME("@export_category") || annotation->name == SNAME("@export_group") || annotation->name == SNAME("@export_subgroup")) {
current_class->add_member_group(annotation);
} else {
// For potential non-group standalone annotations.
@ -1436,7 +1441,7 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali
match(GDScriptTokenizer::Token::NEWLINE); // Newline after annotation is optional.
if (valid) {
valid = validate_annotation_argument_count(annotation);
valid = validate_annotation_arguments(annotation);
}
return valid ? annotation : nullptr;
@ -3551,7 +3556,7 @@ bool GDScriptParser::AnnotationNode::applies_to(uint32_t p_target_kinds) const {
return (info->target_kind & p_target_kinds) > 0;
}
bool GDScriptParser::validate_annotation_argument_count(AnnotationNode *p_annotation) {
bool GDScriptParser::validate_annotation_arguments(AnnotationNode *p_annotation) {
ERR_FAIL_COND_V_MSG(!valid_annotations.has(p_annotation->name), false, vformat(R"(Annotation "%s" not found to validate.)", p_annotation->name));
const MethodInfo &info = valid_annotations[p_annotation->name].info;
@ -3566,6 +3571,27 @@ bool GDScriptParser::validate_annotation_argument_count(AnnotationNode *p_annota
return false;
}
// `@icon`'s argument needs to be resolved in the parser. See GH-72444.
if (p_annotation->name == SNAME("@icon")) {
ExpressionNode *argument = p_annotation->arguments[0];
if (argument->type != Node::LITERAL) {
push_error(R"(Argument 1 of annotation "@icon" must be a string literal.)", argument);
return false;
}
Variant value = static_cast<LiteralNode *>(argument)->value;
if (value.get_type() != Variant::STRING) {
push_error(R"(Argument 1 of annotation "@icon" must be a string literal.)", argument);
return false;
}
p_annotation->resolved_arguments.push_back(value);
}
// For other annotations, see `GDScriptAnalyzer::resolve_annotation()`.
return true;
}

View file

@ -970,6 +970,7 @@ public:
struct ReturnNode : public Node {
ExpressionNode *return_value = nullptr;
bool void_return = false;
ReturnNode() {
type = RETURN;
@ -1401,7 +1402,7 @@ private:
// Annotations
AnnotationNode *parse_annotation(uint32_t p_valid_targets);
bool register_annotation(const MethodInfo &p_info, uint32_t p_target_kinds, AnnotationAction p_apply, const Vector<Variant> &p_default_arguments = Vector<Variant>(), bool p_is_vararg = false);
bool validate_annotation_argument_count(AnnotationNode *p_annotation);
bool validate_annotation_arguments(AnnotationNode *p_annotation);
void clear_unused_annotations();
bool tool_annotation(const AnnotationNode *p_annotation, Node *p_target);
bool icon_annotation(const AnnotationNode *p_annotation, Node *p_target);

View file

@ -125,6 +125,10 @@ String GDScriptWarning::get_message() const {
CHECK_SYMBOLS(4);
return "The argument '" + symbols[0] + "' of the function '" + symbols[1] + "' requires a the subtype '" + symbols[2] + "' but the supertype '" + symbols[3] + "' was provided";
} break;
case UNSAFE_VOID_RETURN: {
CHECK_SYMBOLS(2);
return "The method '" + symbols[0] + "()' returns 'void' but it's trying to return a call to '" + symbols[1] + "()' that can't be ensured to also be 'void'.";
} break;
case DEPRECATED_KEYWORD: {
CHECK_SYMBOLS(2);
return "The '" + symbols[0] + "' keyword is deprecated and will be removed in a future release, please replace its uses by '" + symbols[1] + "'.";
@ -163,6 +167,9 @@ String GDScriptWarning::get_message() const {
CHECK_SYMBOLS(1);
return vformat(R"(The identifier "%s" has misleading characters and might be confused with something else.)", symbols[0]);
}
case RENAMED_IN_GD4_HINT: {
break; // Renamed identifier hint is taken care of by the GDScriptAnalyzer. No message needed here.
}
case WARNING_MAX:
break; // Can't happen, but silences warning
}
@ -184,6 +191,9 @@ int GDScriptWarning::get_default_value(Code p_code) {
PropertyInfo GDScriptWarning::get_property_info(Code p_code) {
// Making this a separate function in case a warning needs different PropertyInfo in the future.
if (p_code == Code::RENAMED_IN_GD4_HINT) {
return PropertyInfo(Variant::BOOL, get_settings_path_from_code(p_code));
}
return PropertyInfo(Variant::INT, get_settings_path_from_code(p_code), PROPERTY_HINT_ENUM, "Ignore,Warn,Error");
}
@ -218,6 +228,7 @@ String GDScriptWarning::get_name_from_code(Code p_code) {
"UNSAFE_METHOD_ACCESS",
"UNSAFE_CAST",
"UNSAFE_CALL_ARGUMENT",
"UNSAFE_VOID_RETURN",
"DEPRECATED_KEYWORD",
"STANDALONE_TERNARY",
"ASSERT_ALWAYS_TRUE",
@ -229,6 +240,7 @@ String GDScriptWarning::get_name_from_code(Code p_code) {
"INT_AS_ENUM_WITHOUT_MATCH",
"STATIC_CALLED_ON_INSTANCE",
"CONFUSABLE_IDENTIFIER",
"RENAMED_IN_GODOT_4_HINT"
};
static_assert((sizeof(names) / sizeof(*names)) == WARNING_MAX, "Amount of warning types don't match the amount of warning names.");

View file

@ -69,6 +69,7 @@ public:
UNSAFE_METHOD_ACCESS, // Function not found in the detected type (but can be in subtypes).
UNSAFE_CAST, // Cast used in an unknown type.
UNSAFE_CALL_ARGUMENT, // Function call argument is of a supertype of the require argument.
UNSAFE_VOID_RETURN, // Function returns void but returned a call to a function that can't be type checked.
DEPRECATED_KEYWORD, // The keyword is deprecated and should be replaced.
STANDALONE_TERNARY, // Return value of ternary expression is discarded.
ASSERT_ALWAYS_TRUE, // Expression for assert argument is always true.
@ -80,6 +81,7 @@ public:
INT_AS_ENUM_WITHOUT_MATCH, // An integer value was used as an enum value without matching enum member.
STATIC_CALLED_ON_INSTANCE, // A static method was called on an instance of a class instead of on the class itself.
CONFUSABLE_IDENTIFIER, // The identifier contains misleading characters that can be confused. E.g. "usеr" (has Cyrillic "е" instead of Latin "e").
RENAMED_IN_GD4_HINT, // A variable or function that could not be found has been renamed in Godot 4
WARNING_MAX,
};

View file

@ -0,0 +1,20 @@
func test():
return_call()
return_nothing()
return_side_effect()
var r = return_side_effect.call() # Untyped call to check return value.
prints(r, typeof(r) == TYPE_NIL)
print("end")
func side_effect(v):
print("effect")
return v
func return_call() -> void:
return print("hello")
func return_nothing() -> void:
return
func return_side_effect() -> void:
return side_effect("x")

View file

@ -0,0 +1,10 @@
GDTEST_OK
>> WARNING
>> Line: 20
>> UNSAFE_VOID_RETURN
>> The method 'return_side_effect()' returns 'void' but it's trying to return a call to 'side_effect()' that can't be ensured to also be 'void'.
hello
effect
effect
<null> true
end

View file

@ -991,15 +991,6 @@ void DisplayServerWindows::window_set_current_screen(int p_screen, WindowID p_wi
wpos.y = CLAMP(wpos.y, srect.position.y, srect.position.y + srect.size.height - wsize.height / 3);
window_set_position(wpos, p_window);
}
// Don't let the mouse leave the window when resizing to a smaller resolution.
if (mouse_mode == MOUSE_MODE_CONFINED || mouse_mode == MOUSE_MODE_CONFINED_HIDDEN) {
RECT crect;
GetClientRect(wd.hWnd, &crect);
ClientToScreen(wd.hWnd, (POINT *)&crect.left);
ClientToScreen(wd.hWnd, (POINT *)&crect.right);
ClipCursor(&crect);
}
}
Point2i DisplayServerWindows::window_get_position(WindowID p_window) const {
@ -1077,15 +1068,6 @@ void DisplayServerWindows::window_set_position(const Point2i &p_position, Window
AdjustWindowRectEx(&rc, style, false, exStyle);
MoveWindow(wd.hWnd, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, TRUE);
// Don't let the mouse leave the window when moved.
if (mouse_mode == MOUSE_MODE_CONFINED || mouse_mode == MOUSE_MODE_CONFINED_HIDDEN) {
RECT rect;
GetClientRect(wd.hWnd, &rect);
ClientToScreen(wd.hWnd, (POINT *)&rect.left);
ClientToScreen(wd.hWnd, (POINT *)&rect.right);
ClipCursor(&rect);
}
wd.last_pos = p_position;
_update_real_mouse_position(p_window);
}
@ -1227,15 +1209,6 @@ void DisplayServerWindows::window_set_size(const Size2i p_size, WindowID p_windo
}
MoveWindow(wd.hWnd, rect.left, rect.top, w, h, TRUE);
// Don't let the mouse leave the window when resizing to a smaller resolution.
if (mouse_mode == MOUSE_MODE_CONFINED || mouse_mode == MOUSE_MODE_CONFINED_HIDDEN) {
RECT crect;
GetClientRect(wd.hWnd, &crect);
ClientToScreen(wd.hWnd, (POINT *)&crect.left);
ClientToScreen(wd.hWnd, (POINT *)&crect.right);
ClipCursor(&crect);
}
}
Size2i DisplayServerWindows::window_get_size(WindowID p_window) const {
@ -1423,15 +1396,6 @@ void DisplayServerWindows::window_set_mode(WindowMode p_mode, WindowID p_window)
SystemParametersInfoA(SPI_SETMOUSETRAILS, 0, 0, 0);
}
}
// Don't let the mouse leave the window when resizing to a smaller resolution.
if (mouse_mode == MOUSE_MODE_CONFINED || mouse_mode == MOUSE_MODE_CONFINED_HIDDEN) {
RECT crect;
GetClientRect(wd.hWnd, &crect);
ClientToScreen(wd.hWnd, (POINT *)&crect.left);
ClientToScreen(wd.hWnd, (POINT *)&crect.right);
ClipCursor(&crect);
}
}
DisplayServer::WindowMode DisplayServerWindows::window_get_mode(WindowID p_window) const {
@ -3381,6 +3345,15 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
Callable::CallError ce;
window.rect_changed_callback.callp(args, 1, ret, ce);
}
// Update cursor clip region after window rect has changed.
if (mouse_mode == MOUSE_MODE_CAPTURED || mouse_mode == MOUSE_MODE_CONFINED || mouse_mode == MOUSE_MODE_CONFINED_HIDDEN) {
RECT crect;
GetClientRect(window.hWnd, &crect);
ClientToScreen(window.hWnd, (POINT *)&crect.left);
ClientToScreen(window.hWnd, (POINT *)&crect.right);
ClipCursor(&crect);
}
}
// Return here to prevent WM_MOVE and WM_SIZE from being sent

View file

@ -50,7 +50,7 @@ void Camera2D::_update_scroll() {
return;
}
if (current) {
if (is_current()) {
ERR_FAIL_COND(custom_viewport && !ObjectDB::get_instance(custom_viewport_id));
Transform2D xform = get_camera_transform();
@ -241,10 +241,6 @@ void Camera2D::_notification(int p_what) {
viewport = get_viewport();
}
if (is_current()) {
viewport->_camera_2d_set(this);
}
canvas = get_canvas();
RID vp = viewport->get_viewport_rid();
@ -254,6 +250,10 @@ void Camera2D::_notification(int p_what) {
add_to_group(group_name);
add_to_group(canvas_group_name);
if (enabled && !viewport->get_camera_2d()) {
make_current();
}
_update_process_callback();
first = true;
_update_scroll();
@ -261,11 +261,7 @@ void Camera2D::_notification(int p_what) {
case NOTIFICATION_EXIT_TREE: {
if (is_current()) {
if (viewport && !(custom_viewport && !ObjectDB::get_instance(custom_viewport_id))) {
viewport->set_canvas_transform(Transform2D());
clear_current();
current = true;
}
clear_current();
}
remove_from_group(group_name);
remove_from_group(canvas_group_name);
@ -397,19 +393,31 @@ void Camera2D::set_process_callback(Camera2DProcessCallback p_mode) {
_update_process_callback();
}
void Camera2D::set_enabled(bool p_enabled) {
enabled = p_enabled;
if (enabled && is_inside_tree() && !viewport->get_camera_2d()) {
make_current();
} else if (!enabled && is_current()) {
clear_current();
}
}
bool Camera2D::is_enabled() const {
return enabled;
}
Camera2D::Camera2DProcessCallback Camera2D::get_process_callback() const {
return process_callback;
}
void Camera2D::_make_current(Object *p_which) {
if (p_which == this) {
current = true;
if (is_inside_tree()) {
get_viewport()->_camera_2d_set(this);
queue_redraw();
}
} else {
current = false;
if (is_inside_tree()) {
if (get_viewport()->get_camera_2d() == this) {
get_viewport()->_camera_2d_set(nullptr);
@ -419,45 +427,34 @@ void Camera2D::_make_current(Object *p_which) {
}
}
void Camera2D::set_current(bool p_current) {
if (p_current) {
make_current();
} else {
if (current) {
clear_current();
}
}
}
void Camera2D::_update_process_internal_for_smoothing() {
bool is_not_in_scene_or_editor = !(is_inside_tree() && Engine::get_singleton()->is_editor_hint());
bool is_any_smoothing_valid = position_smoothing_speed > 0 || rotation_smoothing_speed > 0;
bool enabled = is_any_smoothing_valid && is_not_in_scene_or_editor;
set_process_internal(enabled);
}
bool Camera2D::is_current() const {
return current;
bool enable = is_any_smoothing_valid && is_not_in_scene_or_editor;
set_process_internal(enable);
}
void Camera2D::make_current() {
if (is_inside_tree()) {
get_tree()->call_group(group_name, "_make_current", this);
} else {
current = true;
}
ERR_FAIL_COND(!enabled || !is_inside_tree());
get_tree()->call_group(group_name, "_make_current", this);
_update_scroll();
}
void Camera2D::clear_current() {
if (is_inside_tree()) {
get_tree()->call_group(group_name, "_make_current", (Object *)nullptr);
} else {
current = false;
ERR_FAIL_COND(!is_current());
if (viewport && !(custom_viewport && !ObjectDB::get_instance(custom_viewport_id))) {
viewport->assign_next_enabled_camera_2d(group_name);
}
}
bool Camera2D::is_current() const {
if (viewport && !(custom_viewport && !ObjectDB::get_instance(custom_viewport_id))) {
return viewport->get_camera_2d() == this;
}
return false;
}
void Camera2D::set_limit(Side p_side, int p_limit) {
ERR_FAIL_INDEX((int)p_side, 4);
limit[p_side] = p_limit;
@ -715,7 +712,10 @@ void Camera2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_process_callback", "mode"), &Camera2D::set_process_callback);
ClassDB::bind_method(D_METHOD("get_process_callback"), &Camera2D::get_process_callback);
ClassDB::bind_method(D_METHOD("set_current", "current"), &Camera2D::set_current);
ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &Camera2D::set_enabled);
ClassDB::bind_method(D_METHOD("is_enabled"), &Camera2D::is_enabled);
ClassDB::bind_method(D_METHOD("make_current"), &Camera2D::make_current);
ClassDB::bind_method(D_METHOD("is_current"), &Camera2D::is_current);
ClassDB::bind_method(D_METHOD("_make_current"), &Camera2D::_make_current);
@ -779,7 +779,7 @@ void Camera2D::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "offset", PROPERTY_HINT_NONE, "suffix:px"), "set_offset", "get_offset");
ADD_PROPERTY(PropertyInfo(Variant::INT, "anchor_mode", PROPERTY_HINT_ENUM, "Fixed TopLeft,Drag Center"), "set_anchor_mode", "get_anchor_mode");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "ignore_rotation"), "set_ignore_rotation", "is_ignoring_rotation");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "current"), "set_current", "is_current");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "is_enabled");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "zoom", PROPERTY_HINT_LINK), "set_zoom", "get_zoom");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "custom_viewport", PROPERTY_HINT_RESOURCE_TYPE, "Viewport", PROPERTY_USAGE_NONE), "set_custom_viewport", "get_custom_viewport");
ADD_PROPERTY(PropertyInfo(Variant::INT, "process_callback", PROPERTY_HINT_ENUM, "Physics,Idle"), "set_process_callback", "get_process_callback");

View file

@ -64,7 +64,7 @@ protected:
Vector2 zoom_scale = Vector2(1, 1);
AnchorMode anchor_mode = ANCHOR_MODE_DRAG_CENTER;
bool ignore_rotation = true;
bool current = false;
bool enabled = true;
real_t position_smoothing_speed = 5.0;
bool follow_smoothing_enabled = false;
@ -88,7 +88,6 @@ protected:
void _update_scroll();
void _make_current(Object *p_which);
void set_current(bool p_current);
void _set_old_smoothing(real_t p_enable);
@ -155,6 +154,9 @@ public:
void set_process_callback(Camera2DProcessCallback p_mode);
Camera2DProcessCallback get_process_callback() const;
void set_enabled(bool p_enabled);
bool is_enabled() const;
void make_current();
void clear_current();
bool is_current() const;

View file

@ -30,12 +30,20 @@
#include "animation_blend_space_1d.h"
#include "animation_blend_tree.h"
void AnimationNodeBlendSpace1D::get_parameter_list(List<PropertyInfo> *r_list) const {
r_list->push_back(PropertyInfo(Variant::FLOAT, blend_position));
r_list->push_back(PropertyInfo(Variant::INT, closest, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE));
r_list->push_back(PropertyInfo(Variant::FLOAT, length_internal, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE));
}
Variant AnimationNodeBlendSpace1D::get_parameter_default_value(const StringName &p_parameter) const {
return 0;
if (p_parameter == closest) {
return -1;
} else {
return 0;
}
}
Ref<AnimationNode> AnimationNodeBlendSpace1D::get_child_by_name(const StringName &p_name) {
@ -77,6 +85,9 @@ void AnimationNodeBlendSpace1D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_value_label", "text"), &AnimationNodeBlendSpace1D::set_value_label);
ClassDB::bind_method(D_METHOD("get_value_label"), &AnimationNodeBlendSpace1D::get_value_label);
ClassDB::bind_method(D_METHOD("set_blend_mode", "mode"), &AnimationNodeBlendSpace1D::set_blend_mode);
ClassDB::bind_method(D_METHOD("get_blend_mode"), &AnimationNodeBlendSpace1D::get_blend_mode);
ClassDB::bind_method(D_METHOD("set_use_sync", "enable"), &AnimationNodeBlendSpace1D::set_use_sync);
ClassDB::bind_method(D_METHOD("is_using_sync"), &AnimationNodeBlendSpace1D::is_using_sync);
@ -91,7 +102,12 @@ void AnimationNodeBlendSpace1D::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_space", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_max_space", "get_max_space");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "snap", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_snap", "get_snap");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "value_label", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_value_label", "get_value_label");
ADD_PROPERTY(PropertyInfo(Variant::INT, "blend_mode", PROPERTY_HINT_ENUM, "Interpolated,Discrete,Carry", PROPERTY_USAGE_NO_EDITOR), "set_blend_mode", "get_blend_mode");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sync", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_use_sync", "is_using_sync");
BIND_ENUM_CONSTANT(BLEND_MODE_INTERPOLATED);
BIND_ENUM_CONSTANT(BLEND_MODE_DISCRETE);
BIND_ENUM_CONSTANT(BLEND_MODE_DISCRETE_CARRY);
}
void AnimationNodeBlendSpace1D::get_child_nodes(List<ChildNode> *r_child_nodes) {
@ -214,6 +230,14 @@ String AnimationNodeBlendSpace1D::get_value_label() const {
return value_label;
}
void AnimationNodeBlendSpace1D::set_blend_mode(BlendMode p_blend_mode) {
blend_mode = p_blend_mode;
}
AnimationNodeBlendSpace1D::BlendMode AnimationNodeBlendSpace1D::get_blend_mode() const {
return blend_mode;
}
void AnimationNodeBlendSpace1D::set_use_sync(bool p_sync) {
sync = p_sync;
}
@ -241,79 +265,125 @@ double AnimationNodeBlendSpace1D::process(double p_time, bool p_seek, bool p_is_
}
double blend_pos = get_parameter(blend_position);
float weights[MAX_BLEND_POINTS] = {};
int point_lower = -1;
float pos_lower = 0.0;
int point_higher = -1;
float pos_higher = 0.0;
// find the closest two points to blend between
for (int i = 0; i < blend_points_used; i++) {
float pos = blend_points[i].position;
if (pos <= blend_pos) {
if (point_lower == -1) {
point_lower = i;
pos_lower = pos;
} else if ((blend_pos - pos) < (blend_pos - pos_lower)) {
point_lower = i;
pos_lower = pos;
}
} else {
if (point_higher == -1) {
point_higher = i;
pos_higher = pos;
} else if ((pos - blend_pos) < (pos_higher - blend_pos)) {
point_higher = i;
pos_higher = pos;
}
}
}
// fill in weights
if (point_lower == -1 && point_higher != -1) {
// we are on the left side, no other point to the left
// we just play the next point.
weights[point_higher] = 1.0;
} else if (point_higher == -1) {
// we are on the right side, no other point to the right
// we just play the previous point
weights[point_lower] = 1.0;
} else {
// we are between two points.
// figure out weights, then blend the animations
float distance_between_points = pos_higher - pos_lower;
float current_pos_inbetween = blend_pos - pos_lower;
float blend_percentage = current_pos_inbetween / distance_between_points;
float blend_lower = 1.0 - blend_percentage;
float blend_higher = blend_percentage;
weights[point_lower] = blend_lower;
weights[point_higher] = blend_higher;
}
// actually blend the animations now
int cur_closest = get_parameter(closest);
double cur_length_internal = get_parameter(length_internal);
double max_time_remaining = 0.0;
for (int i = 0; i < blend_points_used; i++) {
if (i == point_lower || i == point_higher) {
double remaining = blend_node(blend_points[i].name, blend_points[i].node, p_time, p_seek, p_is_external_seeking, weights[i], FILTER_IGNORE, true);
max_time_remaining = MAX(max_time_remaining, remaining);
} else if (sync) {
blend_node(blend_points[i].name, blend_points[i].node, p_time, p_seek, p_is_external_seeking, 0, FILTER_IGNORE, true);
if (blend_mode == BLEND_MODE_INTERPOLATED) {
float weights[MAX_BLEND_POINTS] = {};
int point_lower = -1;
float pos_lower = 0.0;
int point_higher = -1;
float pos_higher = 0.0;
// find the closest two points to blend between
for (int i = 0; i < blend_points_used; i++) {
float pos = blend_points[i].position;
if (pos <= blend_pos) {
if (point_lower == -1) {
point_lower = i;
pos_lower = pos;
} else if ((blend_pos - pos) < (blend_pos - pos_lower)) {
point_lower = i;
pos_lower = pos;
}
} else {
if (point_higher == -1) {
point_higher = i;
pos_higher = pos;
} else if ((pos - blend_pos) < (pos_higher - blend_pos)) {
point_higher = i;
pos_higher = pos;
}
}
}
// fill in weights
if (point_lower == -1 && point_higher != -1) {
// we are on the left side, no other point to the left
// we just play the next point.
weights[point_higher] = 1.0;
} else if (point_higher == -1) {
// we are on the right side, no other point to the right
// we just play the previous point
weights[point_lower] = 1.0;
} else {
// we are between two points.
// figure out weights, then blend the animations
float distance_between_points = pos_higher - pos_lower;
float current_pos_inbetween = blend_pos - pos_lower;
float blend_percentage = current_pos_inbetween / distance_between_points;
float blend_lower = 1.0 - blend_percentage;
float blend_higher = blend_percentage;
weights[point_lower] = blend_lower;
weights[point_higher] = blend_higher;
}
// actually blend the animations now
for (int i = 0; i < blend_points_used; i++) {
if (i == point_lower || i == point_higher) {
double remaining = blend_node(blend_points[i].name, blend_points[i].node, p_time, p_seek, p_is_external_seeking, weights[i], FILTER_IGNORE, true);
max_time_remaining = MAX(max_time_remaining, remaining);
} else if (sync) {
blend_node(blend_points[i].name, blend_points[i].node, p_time, p_seek, p_is_external_seeking, 0, FILTER_IGNORE, true);
}
}
} else {
int new_closest = -1;
double new_closest_dist = 1e20;
for (int i = 0; i < blend_points_used; i++) {
double d = abs(blend_points[i].position - blend_pos);
if (d < new_closest_dist) {
new_closest = i;
new_closest_dist = d;
}
}
if (new_closest != cur_closest && new_closest != -1) {
double from = 0.0;
if (blend_mode == BLEND_MODE_DISCRETE_CARRY && cur_closest != -1) {
//for ping-pong loop
Ref<AnimationNodeAnimation> na_c = static_cast<Ref<AnimationNodeAnimation>>(blend_points[cur_closest].node);
Ref<AnimationNodeAnimation> na_n = static_cast<Ref<AnimationNodeAnimation>>(blend_points[new_closest].node);
if (!na_c.is_null() && !na_n.is_null()) {
na_n->set_backward(na_c->is_backward());
}
//see how much animation remains
from = cur_length_internal - blend_node(blend_points[cur_closest].name, blend_points[cur_closest].node, p_time, false, p_is_external_seeking, 0.0, FILTER_IGNORE, true);
}
max_time_remaining = blend_node(blend_points[new_closest].name, blend_points[new_closest].node, from, true, p_is_external_seeking, 1.0, FILTER_IGNORE, true);
cur_length_internal = from + max_time_remaining;
cur_closest = new_closest;
} else {
max_time_remaining = blend_node(blend_points[cur_closest].name, blend_points[cur_closest].node, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, true);
}
if (sync) {
for (int i = 0; i < blend_points_used; i++) {
if (i != cur_closest) {
blend_node(blend_points[i].name, blend_points[i].node, p_time, p_seek, p_is_external_seeking, 0, FILTER_IGNORE, true);
}
}
}
}
set_parameter(this->closest, cur_closest);
set_parameter(this->length_internal, cur_length_internal);
return max_time_remaining;
}

View file

@ -36,6 +36,14 @@
class AnimationNodeBlendSpace1D : public AnimationRootNode {
GDCLASS(AnimationNodeBlendSpace1D, AnimationRootNode);
public:
enum BlendMode {
BLEND_MODE_INTERPOLATED,
BLEND_MODE_DISCRETE,
BLEND_MODE_DISCRETE_CARRY,
};
protected:
enum {
MAX_BLEND_POINTS = 64
};
@ -61,6 +69,10 @@ class AnimationNodeBlendSpace1D : public AnimationRootNode {
void _tree_changed();
StringName blend_position = "blend_position";
StringName closest = "closest";
StringName length_internal = "length_internal";
BlendMode blend_mode = BLEND_MODE_INTERPOLATED;
protected:
bool sync = false;
@ -95,6 +107,9 @@ public:
void set_value_label(const String &p_label);
String get_value_label() const;
void set_blend_mode(BlendMode p_blend_mode);
BlendMode get_blend_mode() const;
void set_use_sync(bool p_sync);
bool is_using_sync() const;
@ -107,4 +122,6 @@ public:
~AnimationNodeBlendSpace1D();
};
VARIANT_ENUM_CAST(AnimationNodeBlendSpace1D::BlendMode)
#endif // ANIMATION_BLEND_SPACE_1D_H

View file

@ -692,6 +692,12 @@ Transform2D Control::get_transform() const {
return xform;
}
void Control::_toplevel_changed_on_parent() {
// Update root control status.
_notification(NOTIFICATION_EXIT_CANVAS);
_notification(NOTIFICATION_ENTER_CANVAS);
}
/// Anchors and offsets.
void Control::_set_anchor(Side p_side, real_t p_anchor) {

View file

@ -292,6 +292,9 @@ private:
void _update_minimum_size();
void _size_changed();
void _toplevel_changed() override{}; // Controls don't need to do anything, only other CanvasItems.
void _toplevel_changed_on_parent() override;
void _clear_size_warning();
// Input events.

View file

@ -400,11 +400,28 @@ void CanvasItem::set_as_top_level(bool p_top_level) {
_exit_canvas();
top_level = p_top_level;
_toplevel_changed();
_enter_canvas();
_notify_transform();
}
void CanvasItem::_toplevel_changed() {
// Inform children that toplevel status has changed on a parent.
int childs = get_child_count();
for (int i = 0; i < childs; i++) {
CanvasItem *child = Object::cast_to<CanvasItem>(get_child(i));
if (child) {
child->_toplevel_changed_on_parent();
}
}
}
void CanvasItem::_toplevel_changed_on_parent() {
// Inform children that toplevel status has changed on a parent.
_toplevel_changed();
}
bool CanvasItem::is_set_as_top_level() const {
return top_level;
}

View file

@ -125,6 +125,9 @@ private:
void _propagate_visibility_changed(bool p_parent_visible_in_tree);
void _handle_visibility_change(bool p_visible);
virtual void _toplevel_changed();
virtual void _toplevel_changed_on_parent();
void _redraw_callback();
void _enter_canvas();

View file

@ -1045,6 +1045,25 @@ Transform2D Viewport::get_final_transform() const {
return _get_input_pre_xform().affine_inverse() * stretch_transform * global_canvas_transform;
}
void Viewport::assign_next_enabled_camera_2d(const StringName &p_camera_group) {
List<Node *> camera_list;
get_tree()->get_nodes_in_group(p_camera_group, &camera_list);
Camera2D *new_camera = nullptr;
for (const Node *E : camera_list) {
const Camera2D *cam = Object::cast_to<Camera2D>(E);
if (cam->is_enabled()) {
new_camera = const_cast<Camera2D *>(cam);
break;
}
}
_camera_2d_set(new_camera);
if (!camera_2d) {
set_canvas_transform(Transform2D());
}
}
void Viewport::_update_canvas_items(Node *p_node) {
if (p_node != this) {
Window *w = Object::cast_to<Window>(p_node);
@ -1110,14 +1129,11 @@ Viewport::PositionalShadowAtlasQuadrantSubdiv Viewport::get_positional_shadow_at
}
Transform2D Viewport::_get_input_pre_xform() const {
Transform2D pre_xf;
if (to_screen_rect.size.x != 0 && to_screen_rect.size.y != 0) {
pre_xf.columns[2] = -to_screen_rect.position;
pre_xf.scale(Vector2(size) / to_screen_rect.size);
const Window *this_window = Object::cast_to<Window>(this);
if (this_window) {
return this_window->window_transform.affine_inverse();
}
return pre_xf;
return Transform2D();
}
Ref<InputEvent> Viewport::_make_input_local(const Ref<InputEvent> &ev) {

View file

@ -511,6 +511,7 @@ public:
Transform2D get_global_canvas_transform() const;
Transform2D get_final_transform() const;
void assign_next_enabled_camera_2d(const StringName &p_camera_group);
void gui_set_root_order_dirty();

View file

@ -907,6 +907,7 @@ void Window::_update_viewport_size() {
Rect2i attach_to_screen_rect(Point2i(), size);
Transform2D stretch_transform_new;
float font_oversampling = 1.0;
window_transform = Transform2D();
if (content_scale_mode == CONTENT_SCALE_MODE_DISABLED || content_scale_size.x == 0 || content_scale_size.y == 0) {
font_oversampling = content_scale_factor;
@ -993,11 +994,18 @@ void Window::_update_viewport_size() {
Size2 scale = Vector2(screen_size) / Vector2(final_size_override);
stretch_transform_new.scale(scale);
window_transform.translate_local(margin);
} break;
case CONTENT_SCALE_MODE_VIEWPORT: {
final_size = (viewport_size / content_scale_factor).floor();
attach_to_screen_rect = Rect2(margin, screen_size);
window_transform.translate_local(margin);
if (final_size.x != 0 && final_size.y != 0) {
Transform2D scale_transform;
scale_transform.scale(Vector2(attach_to_screen_rect.size) / Vector2(final_size));
window_transform *= scale_transform;
}
} break;
}
}
@ -2127,13 +2135,13 @@ Transform2D Window::get_popup_base_transform() const {
if (is_embedding_subwindows()) {
return Transform2D();
}
Transform2D window_transform;
window_transform.set_origin(get_position());
window_transform *= Viewport::get_screen_transform();
Transform2D popup_base_transform;
popup_base_transform.set_origin(get_position());
popup_base_transform *= Viewport::get_screen_transform();
if (_get_embedder()) {
return _get_embedder()->get_popup_base_transform() * window_transform;
return _get_embedder()->get_popup_base_transform() * popup_base_transform;
}
return window_transform;
return popup_base_transform;
}
void Window::_bind_methods() {

View file

@ -173,6 +173,8 @@ private:
Viewport *embedder = nullptr;
Transform2D window_transform;
friend class Viewport; //friend back, can call the methods below
void _window_input(const Ref<InputEvent> &p_ev);

View file

@ -6923,15 +6923,34 @@ void VisualShaderNodeSwitch::_bind_methods() { // static
}
String VisualShaderNodeSwitch::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const {
bool use_mix = false;
switch (op_type) {
case OP_TYPE_FLOAT: {
use_mix = true;
} break;
case OP_TYPE_VECTOR_2D: {
use_mix = true;
} break;
case OP_TYPE_VECTOR_3D: {
use_mix = true;
} break;
case OP_TYPE_VECTOR_4D: {
use_mix = true;
} break;
default: {
} break;
}
String code;
code += " if(" + p_input_vars[0] + ")\n";
code += " {\n";
code += " " + p_output_vars[0] + " = " + p_input_vars[1] + ";\n";
code += " }\n";
code += " else\n";
code += " {\n";
code += " " + p_output_vars[0] + " = " + p_input_vars[2] + ";\n";
code += " }\n";
if (use_mix) {
code += " " + p_output_vars[0] + " = mix(" + p_input_vars[2] + ", " + p_input_vars[1] + ", float(" + p_input_vars[0] + "));\n";
} else {
code += " if (" + p_input_vars[0] + ") {\n";
code += " " + p_output_vars[0] + " = " + p_input_vars[1] + ";\n";
code += " } else {\n";
code += " " + p_output_vars[0] + " = " + p_input_vars[2] + ";\n";
code += " }\n";
}
return code;
}

View file

@ -43,6 +43,14 @@ RID World2D::get_canvas() const {
}
RID World2D::get_space() const {
if (space.is_null()) {
space = PhysicsServer2D::get_singleton()->space_create();
PhysicsServer2D::get_singleton()->space_set_active(space, true);
PhysicsServer2D::get_singleton()->area_set_param(space, PhysicsServer2D::AREA_PARAM_GRAVITY, GLOBAL_GET("physics/2d/default_gravity"));
PhysicsServer2D::get_singleton()->area_set_param(space, PhysicsServer2D::AREA_PARAM_GRAVITY_VECTOR, GLOBAL_GET("physics/2d/default_gravity_vector"));
PhysicsServer2D::get_singleton()->area_set_param(space, PhysicsServer2D::AREA_PARAM_LINEAR_DAMP, GLOBAL_GET("physics/2d/default_linear_damp"));
PhysicsServer2D::get_singleton()->area_set_param(space, PhysicsServer2D::AREA_PARAM_ANGULAR_DAMP, GLOBAL_GET("physics/2d/default_angular_damp"));
}
return space;
}
@ -71,19 +79,11 @@ void World2D::_bind_methods() {
}
PhysicsDirectSpaceState2D *World2D::get_direct_space_state() {
return PhysicsServer2D::get_singleton()->space_get_direct_state(space);
return PhysicsServer2D::get_singleton()->space_get_direct_state(get_space());
}
World2D::World2D() {
canvas = RenderingServer::get_singleton()->canvas_create();
// Create and configure space2D to be more friendly with pixels than meters
space = PhysicsServer2D::get_singleton()->space_create();
PhysicsServer2D::get_singleton()->space_set_active(space, true);
PhysicsServer2D::get_singleton()->area_set_param(space, PhysicsServer2D::AREA_PARAM_GRAVITY, GLOBAL_DEF_BASIC("physics/2d/default_gravity", 980.0));
PhysicsServer2D::get_singleton()->area_set_param(space, PhysicsServer2D::AREA_PARAM_GRAVITY_VECTOR, GLOBAL_DEF_BASIC("physics/2d/default_gravity_vector", Vector2(0, 1)));
PhysicsServer2D::get_singleton()->area_set_param(space, PhysicsServer2D::AREA_PARAM_LINEAR_DAMP, GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "physics/2d/default_linear_damp", PROPERTY_HINT_RANGE, "-1,100,0.001,or_greater"), 0.1));
PhysicsServer2D::get_singleton()->area_set_param(space, PhysicsServer2D::AREA_PARAM_ANGULAR_DAMP, GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "physics/2d/default_angular_damp", PROPERTY_HINT_RANGE, "-1,100,0.001,or_greater"), 1.0));
}
World2D::~World2D() {
@ -91,7 +91,9 @@ World2D::~World2D() {
ERR_FAIL_NULL(PhysicsServer2D::get_singleton());
ERR_FAIL_NULL(NavigationServer2D::get_singleton());
RenderingServer::get_singleton()->free(canvas);
PhysicsServer2D::get_singleton()->free(space);
if (space.is_valid()) {
PhysicsServer2D::get_singleton()->free(space);
}
if (navigation_map.is_valid()) {
NavigationServer2D::get_singleton()->free(navigation_map);
}

View file

@ -43,7 +43,7 @@ class World2D : public Resource {
GDCLASS(World2D, Resource);
RID canvas;
RID space;
mutable RID space;
mutable RID navigation_map;
HashSet<Viewport *> viewports;

View file

@ -51,6 +51,14 @@ void World3D::_remove_camera(Camera3D *p_camera) {
}
RID World3D::get_space() const {
if (space.is_null()) {
space = PhysicsServer3D::get_singleton()->space_create();
PhysicsServer3D::get_singleton()->space_set_active(space, true);
PhysicsServer3D::get_singleton()->area_set_param(space, PhysicsServer3D::AREA_PARAM_GRAVITY, GLOBAL_GET("physics/3d/default_gravity"));
PhysicsServer3D::get_singleton()->area_set_param(space, PhysicsServer3D::AREA_PARAM_GRAVITY_VECTOR, GLOBAL_GET("physics/3d/default_gravity_vector"));
PhysicsServer3D::get_singleton()->area_set_param(space, PhysicsServer3D::AREA_PARAM_LINEAR_DAMP, GLOBAL_GET("physics/3d/default_linear_damp"));
PhysicsServer3D::get_singleton()->area_set_param(space, PhysicsServer3D::AREA_PARAM_ANGULAR_DAMP, GLOBAL_GET("physics/3d/default_angular_damp"));
}
return space;
}
@ -121,7 +129,7 @@ Ref<CameraAttributes> World3D::get_camera_attributes() const {
}
PhysicsDirectSpaceState3D *World3D::get_direct_space_state() {
return PhysicsServer3D::get_singleton()->space_get_direct_state(space);
return PhysicsServer3D::get_singleton()->space_get_direct_state(get_space());
}
void World3D::_bind_methods() {
@ -145,22 +153,18 @@ void World3D::_bind_methods() {
}
World3D::World3D() {
space = PhysicsServer3D::get_singleton()->space_create();
scenario = RenderingServer::get_singleton()->scenario_create();
PhysicsServer3D::get_singleton()->space_set_active(space, true);
PhysicsServer3D::get_singleton()->area_set_param(space, PhysicsServer3D::AREA_PARAM_GRAVITY, GLOBAL_DEF_BASIC("physics/3d/default_gravity", 9.8));
PhysicsServer3D::get_singleton()->area_set_param(space, PhysicsServer3D::AREA_PARAM_GRAVITY_VECTOR, GLOBAL_DEF_BASIC("physics/3d/default_gravity_vector", Vector3(0, -1, 0)));
PhysicsServer3D::get_singleton()->area_set_param(space, PhysicsServer3D::AREA_PARAM_LINEAR_DAMP, GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "physics/3d/default_linear_damp", PROPERTY_HINT_RANGE, "0,100,0.001,or_greater"), 0.1));
PhysicsServer3D::get_singleton()->area_set_param(space, PhysicsServer3D::AREA_PARAM_ANGULAR_DAMP, GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "physics/3d/default_angular_damp", PROPERTY_HINT_RANGE, "0,100,0.001,or_greater"), 0.1));
}
World3D::~World3D() {
ERR_FAIL_NULL(PhysicsServer3D::get_singleton());
ERR_FAIL_NULL(RenderingServer::get_singleton());
ERR_FAIL_NULL(PhysicsServer3D::get_singleton());
ERR_FAIL_NULL(NavigationServer3D::get_singleton());
PhysicsServer3D::get_singleton()->free(space);
RenderingServer::get_singleton()->free(scenario);
if (space.is_valid()) {
PhysicsServer3D::get_singleton()->free(space);
}
if (navigation_map.is_valid()) {
NavigationServer3D::get_singleton()->free(navigation_map);
}

View file

@ -45,8 +45,8 @@ class World3D : public Resource {
GDCLASS(World3D, Resource);
private:
RID space;
RID scenario;
mutable RID space;
mutable RID navigation_map;
Ref<Environment> environment;

View file

@ -144,7 +144,7 @@ int AudioDriver::get_total_channels_by_speaker_mode(AudioDriver::SpeakerMode p_m
ERR_FAIL_V(2);
}
PackedStringArray AudioDriver::get_device_list() {
PackedStringArray AudioDriver::get_output_device_list() {
PackedStringArray list;
list.push_back("Default");
@ -152,11 +152,11 @@ PackedStringArray AudioDriver::get_device_list() {
return list;
}
String AudioDriver::get_device() {
String AudioDriver::get_output_device() {
return "Default";
}
PackedStringArray AudioDriver::capture_get_device_list() {
PackedStringArray AudioDriver::get_input_device_list() {
PackedStringArray list;
list.push_back("Default");
@ -238,7 +238,7 @@ void AudioServer::_driver_process(int p_frames, int32_t *p_buffer) {
#endif
if (channel_count != get_channel_count()) {
// Amount of channels changed due to a device change
// Amount of channels changed due to a output_device change
// reinitialize the buses channels and buffers
init_channels_and_buffers();
}
@ -1632,28 +1632,28 @@ Ref<AudioBusLayout> AudioServer::generate_bus_layout() const {
return state;
}
PackedStringArray AudioServer::get_device_list() {
return AudioDriver::get_singleton()->get_device_list();
PackedStringArray AudioServer::get_output_device_list() {
return AudioDriver::get_singleton()->get_output_device_list();
}
String AudioServer::get_device() {
return AudioDriver::get_singleton()->get_device();
String AudioServer::get_output_device() {
return AudioDriver::get_singleton()->get_output_device();
}
void AudioServer::set_device(String device) {
AudioDriver::get_singleton()->set_device(device);
void AudioServer::set_output_device(String output_device) {
AudioDriver::get_singleton()->set_output_device(output_device);
}
PackedStringArray AudioServer::capture_get_device_list() {
return AudioDriver::get_singleton()->capture_get_device_list();
PackedStringArray AudioServer::get_input_device_list() {
return AudioDriver::get_singleton()->get_input_device_list();
}
String AudioServer::capture_get_device() {
return AudioDriver::get_singleton()->capture_get_device();
String AudioServer::get_input_device() {
return AudioDriver::get_singleton()->get_input_device();
}
void AudioServer::capture_set_device(const String &p_name) {
AudioDriver::get_singleton()->capture_set_device(p_name);
void AudioServer::set_input_device(const String &p_name) {
AudioDriver::get_singleton()->set_input_device(p_name);
}
void AudioServer::set_enable_tagging_used_audio_streams(bool p_enable) {
@ -1711,17 +1711,17 @@ void AudioServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_speaker_mode"), &AudioServer::get_speaker_mode);
ClassDB::bind_method(D_METHOD("get_mix_rate"), &AudioServer::get_mix_rate);
ClassDB::bind_method(D_METHOD("get_device_list"), &AudioServer::get_device_list);
ClassDB::bind_method(D_METHOD("get_device"), &AudioServer::get_device);
ClassDB::bind_method(D_METHOD("set_device", "device"), &AudioServer::set_device);
ClassDB::bind_method(D_METHOD("get_output_device_list"), &AudioServer::get_output_device_list);
ClassDB::bind_method(D_METHOD("get_output_device"), &AudioServer::get_output_device);
ClassDB::bind_method(D_METHOD("set_output_device", "output_device"), &AudioServer::set_output_device);
ClassDB::bind_method(D_METHOD("get_time_to_next_mix"), &AudioServer::get_time_to_next_mix);
ClassDB::bind_method(D_METHOD("get_time_since_last_mix"), &AudioServer::get_time_since_last_mix);
ClassDB::bind_method(D_METHOD("get_output_latency"), &AudioServer::get_output_latency);
ClassDB::bind_method(D_METHOD("capture_get_device_list"), &AudioServer::capture_get_device_list);
ClassDB::bind_method(D_METHOD("capture_get_device"), &AudioServer::capture_get_device);
ClassDB::bind_method(D_METHOD("capture_set_device", "name"), &AudioServer::capture_set_device);
ClassDB::bind_method(D_METHOD("get_input_device_list"), &AudioServer::get_input_device_list);
ClassDB::bind_method(D_METHOD("get_input_device"), &AudioServer::get_input_device);
ClassDB::bind_method(D_METHOD("set_input_device", "name"), &AudioServer::set_input_device);
ClassDB::bind_method(D_METHOD("set_bus_layout", "bus_layout"), &AudioServer::set_bus_layout);
ClassDB::bind_method(D_METHOD("generate_bus_layout"), &AudioServer::generate_bus_layout);
@ -1729,11 +1729,11 @@ void AudioServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_enable_tagging_used_audio_streams", "enable"), &AudioServer::set_enable_tagging_used_audio_streams);
ADD_PROPERTY(PropertyInfo(Variant::INT, "bus_count"), "set_bus_count", "get_bus_count");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "device"), "set_device", "get_device");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "capture_device"), "capture_set_device", "capture_get_device");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "output_device"), "set_output_device", "get_output_device");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "input_device"), "set_input_device", "get_input_device");
// The default value may be set to an empty string by the platform-specific audio driver.
// Override for class reference generation purposes.
ADD_PROPERTY_DEFAULT("capture_device", "Default");
ADD_PROPERTY_DEFAULT("input_device", "Default");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "playback_speed_scale"), "set_playback_speed_scale", "get_playback_speed_scale");
ADD_SIGNAL(MethodInfo("bus_layout_changed"));

View file

@ -94,18 +94,18 @@ public:
virtual void start() = 0;
virtual int get_mix_rate() const = 0;
virtual SpeakerMode get_speaker_mode() const = 0;
virtual PackedStringArray get_device_list();
virtual String get_device();
virtual void set_device(String device) {}
virtual PackedStringArray get_output_device_list();
virtual String get_output_device();
virtual void set_output_device(String output_device) {}
virtual void lock() = 0;
virtual void unlock() = 0;
virtual void finish() = 0;
virtual Error capture_start() { return FAILED; }
virtual Error capture_stop() { return FAILED; }
virtual void capture_set_device(const String &p_name) {}
virtual String capture_get_device() { return "Default"; }
virtual PackedStringArray capture_get_device_list();
virtual void set_input_device(const String &p_name) {}
virtual String get_input_device() { return "Default"; }
virtual PackedStringArray get_input_device_list();
virtual float get_latency() { return 0; }
@ -419,13 +419,13 @@ public:
void set_bus_layout(const Ref<AudioBusLayout> &p_bus_layout);
Ref<AudioBusLayout> generate_bus_layout() const;
PackedStringArray get_device_list();
String get_device();
void set_device(String device);
PackedStringArray get_output_device_list();
String get_output_device();
void set_output_device(String output_device);
PackedStringArray capture_get_device_list();
String capture_get_device();
void capture_set_device(const String &p_name);
PackedStringArray get_input_device_list();
String get_input_device();
void set_input_device(const String &p_name);
void set_enable_tagging_used_audio_streams(bool p_enable);

View file

@ -1216,15 +1216,15 @@ GodotPhysicsDirectSpaceState2D *GodotSpace2D::get_direct_state() {
}
GodotSpace2D::GodotSpace2D() {
body_linear_velocity_sleep_threshold = GLOBAL_DEF("physics/2d/sleep_threshold_linear", 2.0);
body_angular_velocity_sleep_threshold = GLOBAL_DEF("physics/2d/sleep_threshold_angular", Math::deg_to_rad(8.0));
body_time_to_sleep = GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "physics/2d/time_before_sleep", PROPERTY_HINT_RANGE, "0,5,0.01,or_greater"), 0.5);
solver_iterations = GLOBAL_DEF(PropertyInfo(Variant::INT, "physics/2d/solver/solver_iterations", PROPERTY_HINT_RANGE, "1,32,1,or_greater"), 16);
contact_recycle_radius = GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "physics/2d/solver/contact_recycle_radius", PROPERTY_HINT_RANGE, "0,10,0.01,or_greater"), 1.0);
contact_max_separation = GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "physics/2d/solver/contact_max_separation", PROPERTY_HINT_RANGE, "0,10,0.01,or_greater"), 1.5);
contact_max_allowed_penetration = GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "physics/2d/solver/contact_max_allowed_penetration", PROPERTY_HINT_RANGE, "0,10,0.01,or_greater"), 0.3);
contact_bias = GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "physics/2d/solver/default_contact_bias", PROPERTY_HINT_RANGE, "0,1,0.01"), 0.8);
constraint_bias = GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "physics/2d/solver/default_constraint_bias", PROPERTY_HINT_RANGE, "0,1,0.01"), 0.2);
body_linear_velocity_sleep_threshold = GLOBAL_GET("physics/2d/sleep_threshold_linear");
body_angular_velocity_sleep_threshold = GLOBAL_GET("physics/2d/sleep_threshold_angular");
body_time_to_sleep = GLOBAL_GET("physics/2d/time_before_sleep");
solver_iterations = GLOBAL_GET("physics/2d/solver/solver_iterations");
contact_recycle_radius = GLOBAL_GET("physics/2d/solver/contact_recycle_radius");
contact_max_separation = GLOBAL_GET("physics/2d/solver/contact_max_separation");
contact_max_allowed_penetration = GLOBAL_GET("physics/2d/solver/contact_max_allowed_penetration");
contact_bias = GLOBAL_GET("physics/2d/solver/default_contact_bias");
constraint_bias = GLOBAL_GET("physics/2d/solver/default_constraint_bias");
broadphase = GodotBroadPhase2D::create_func();
broadphase->set_pair_callback(_broadphase_pair, this);

View file

@ -1250,14 +1250,14 @@ GodotPhysicsDirectSpaceState3D *GodotSpace3D::get_direct_state() {
}
GodotSpace3D::GodotSpace3D() {
body_linear_velocity_sleep_threshold = GLOBAL_DEF("physics/3d/sleep_threshold_linear", 0.1);
body_angular_velocity_sleep_threshold = GLOBAL_DEF("physics/3d/sleep_threshold_angular", Math::deg_to_rad(8.0));
body_time_to_sleep = GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "physics/3d/time_before_sleep", PROPERTY_HINT_RANGE, "0,5,0.01,or_greater"), 0.5);
solver_iterations = GLOBAL_DEF(PropertyInfo(Variant::INT, "physics/3d/solver/solver_iterations", PROPERTY_HINT_RANGE, "1,32,1,or_greater"), 16);
contact_recycle_radius = GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "physics/3d/solver/contact_recycle_radius", PROPERTY_HINT_RANGE, "0,0.1,0.01,or_greater"), 0.01);
contact_max_separation = GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "physics/3d/solver/contact_max_separation", PROPERTY_HINT_RANGE, "0,0.1,0.01,or_greater"), 0.05);
contact_max_allowed_penetration = GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "physics/3d/solver/contact_max_allowed_penetration", PROPERTY_HINT_RANGE, "0,0.1,0.01,or_greater"), 0.01);
contact_bias = GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "physics/3d/solver/default_contact_bias", PROPERTY_HINT_RANGE, "0,1,0.01"), 0.8);
body_linear_velocity_sleep_threshold = GLOBAL_GET("physics/3d/sleep_threshold_linear");
body_angular_velocity_sleep_threshold = GLOBAL_GET("physics/3d/sleep_threshold_angular");
body_time_to_sleep = GLOBAL_GET("physics/3d/time_before_sleep");
solver_iterations = GLOBAL_GET("physics/3d/solver/solver_iterations");
contact_recycle_radius = GLOBAL_GET("physics/3d/solver/contact_recycle_radius");
contact_max_separation = GLOBAL_GET("physics/3d/solver/contact_max_separation");
contact_max_allowed_penetration = GLOBAL_GET("physics/3d/solver/contact_max_allowed_penetration");
contact_bias = GLOBAL_GET("physics/3d/solver/default_contact_bias");
broadphase = GodotBroadPhase3D::create_func();
broadphase->set_pair_callback(_broadphase_pair, this);

View file

@ -879,6 +879,23 @@ void PhysicsServer2D::_bind_methods() {
PhysicsServer2D::PhysicsServer2D() {
singleton = this;
// World2D physics space
GLOBAL_DEF_BASIC("physics/2d/default_gravity", 980.0);
GLOBAL_DEF_BASIC("physics/2d/default_gravity_vector", Vector2(0, 1));
GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "physics/2d/default_linear_damp", PROPERTY_HINT_RANGE, "-1,100,0.001,or_greater"), 0.1);
GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "physics/2d/default_angular_damp", PROPERTY_HINT_RANGE, "-1,100,0.001,or_greater"), 1.0);
// PhysicsServer2D
GLOBAL_DEF("physics/2d/sleep_threshold_linear", 2.0);
GLOBAL_DEF("physics/2d/sleep_threshold_angular", Math::deg_to_rad(8.0));
GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "physics/2d/time_before_sleep", PROPERTY_HINT_RANGE, "0,5,0.01,or_greater"), 0.5);
GLOBAL_DEF(PropertyInfo(Variant::INT, "physics/2d/solver/solver_iterations", PROPERTY_HINT_RANGE, "1,32,1,or_greater"), 16);
GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "physics/2d/solver/contact_recycle_radius", PROPERTY_HINT_RANGE, "0,10,0.01,or_greater"), 1.0);
GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "physics/2d/solver/contact_max_separation", PROPERTY_HINT_RANGE, "0,10,0.01,or_greater"), 1.5);
GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "physics/2d/solver/contact_max_allowed_penetration", PROPERTY_HINT_RANGE, "0,10,0.01,or_greater"), 0.3);
GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "physics/2d/solver/default_contact_bias", PROPERTY_HINT_RANGE, "0,1,0.01"), 0.8);
GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "physics/2d/solver/default_constraint_bias", PROPERTY_HINT_RANGE, "0,1,0.01"), 0.2);
}
PhysicsServer2D::~PhysicsServer2D() {

View file

@ -1047,6 +1047,22 @@ void PhysicsServer3D::_bind_methods() {
PhysicsServer3D::PhysicsServer3D() {
singleton = this;
// World3D physics space
GLOBAL_DEF_BASIC("physics/3d/default_gravity", 9.8);
GLOBAL_DEF_BASIC("physics/3d/default_gravity_vector", Vector3(0, -1, 0));
GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "physics/3d/default_linear_damp", PROPERTY_HINT_RANGE, "0,100,0.001,or_greater"), 0.1);
GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "physics/3d/default_angular_damp", PROPERTY_HINT_RANGE, "0,100,0.001,or_greater"), 0.1);
// PhysicsServer3D
GLOBAL_DEF("physics/3d/sleep_threshold_linear", 0.1);
GLOBAL_DEF("physics/3d/sleep_threshold_angular", Math::deg_to_rad(8.0));
GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "physics/3d/time_before_sleep", PROPERTY_HINT_RANGE, "0,5,0.01,or_greater"), 0.5);
GLOBAL_DEF(PropertyInfo(Variant::INT, "physics/3d/solver/solver_iterations", PROPERTY_HINT_RANGE, "1,32,1,or_greater"), 16);
GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "physics/3d/solver/contact_recycle_radius", PROPERTY_HINT_RANGE, "0,0.1,0.01,or_greater"), 0.01);
GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "physics/3d/solver/contact_max_separation", PROPERTY_HINT_RANGE, "0,0.1,0.01,or_greater"), 0.05);
GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "physics/3d/solver/contact_max_allowed_penetration", PROPERTY_HINT_RANGE, "0,0.1,0.01,or_greater"), 0.01);
GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "physics/3d/solver/default_contact_bias", PROPERTY_HINT_RANGE, "0,1,0.01"), 0.8);
}
PhysicsServer3D::~PhysicsServer3D() {

150
tests/display_server_mock.h Normal file
View file

@ -0,0 +1,150 @@
/**************************************************************************/
/* display_server_mock.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef DISPLAY_SERVER_MOCK_H
#define DISPLAY_SERVER_MOCK_H
#include "servers/display_server_headless.h"
#include "servers/rendering/dummy/rasterizer_dummy.h"
// Specialized DisplayServer for unittests based on DisplayServerHeadless, that
// additionally supports rudimentary InputEvent handling and mouse position.
class DisplayServerMock : public DisplayServerHeadless {
private:
friend class DisplayServer;
Point2i mouse_position = Point2i(-1, -1); // Outside of Window.
bool window_over = false;
Callable event_callback;
Callable input_event_callback;
static Vector<String> get_rendering_drivers_func() {
Vector<String> drivers;
drivers.push_back("dummy");
return drivers;
}
static DisplayServer *create_func(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Error &r_error) {
r_error = OK;
RasterizerDummy::make_current();
return memnew(DisplayServerMock());
}
static void _dispatch_input_events(const Ref<InputEvent> &p_event) {
static_cast<DisplayServerMock *>(get_singleton())->_dispatch_input_event(p_event);
}
void _dispatch_input_event(const Ref<InputEvent> &p_event) {
Variant ev = p_event;
Variant *evp = &ev;
Variant ret;
Callable::CallError ce;
if (input_event_callback.is_valid()) {
input_event_callback.callp((const Variant **)&evp, 1, ret, ce);
}
}
void _set_mouse_position(const Point2i &p_position) {
if (mouse_position == p_position) {
return;
}
mouse_position = p_position;
_set_window_over(Rect2i(Point2i(0, 0), window_get_size()).has_point(p_position));
}
void _set_window_over(bool p_over) {
if (p_over == window_over) {
return;
}
window_over = p_over;
_send_window_event(p_over ? WINDOW_EVENT_MOUSE_ENTER : WINDOW_EVENT_MOUSE_EXIT);
}
void _send_window_event(WindowEvent p_event) {
if (!event_callback.is_null()) {
Variant event = int(p_event);
Variant *eventp = &event;
Variant ret;
Callable::CallError ce;
event_callback.callp((const Variant **)&eventp, 1, ret, ce);
}
}
public:
bool has_feature(Feature p_feature) const override {
switch (p_feature) {
case FEATURE_MOUSE:
return true;
default: {
}
}
return false;
}
String get_name() const override { return "mock"; }
// You can simulate DisplayServer-events by calling this function.
// The events will be deliverd to Godot's Input-system.
// Mouse-events (Button & Motion) will additionally update the DisplayServer's mouse position.
void simulate_event(Ref<InputEvent> p_event) {
Ref<InputEventMouse> me = p_event;
if (me.is_valid()) {
_set_mouse_position(me->get_position());
}
Input::get_singleton()->parse_input_event(p_event);
}
virtual Point2i mouse_get_position() const override { return mouse_position; }
virtual Size2i window_get_size(WindowID p_window = MAIN_WINDOW_ID) const override {
return Size2i(1920, 1080);
}
virtual void window_set_window_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override {
event_callback = p_callable;
}
virtual void window_set_input_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override {
input_event_callback = p_callable;
}
static void register_mock_driver() {
register_create_function("mock", create_func, get_rendering_drivers_func);
}
DisplayServerMock() {
Input::get_singleton()->set_event_dispatch_function(_dispatch_input_events);
}
~DisplayServerMock() {}
};
#endif // DISPLAY_SERVER_MOCK_H

View file

@ -3271,6 +3271,7 @@ TEST_CASE("[SceneTree][TextEdit] mouse") {
TEST_CASE("[SceneTree][TextEdit] caret") {
TextEdit *text_edit = memnew(TextEdit);
text_edit->set_context_menu_enabled(false); // Prohibit sending InputEvents to the context menu.
SceneTree::get_singleton()->get_root()->add_child(text_edit);
text_edit->set_size(Size2(800, 200));

View file

@ -31,6 +31,8 @@
#ifndef TEST_MACROS_H
#define TEST_MACROS_H
#include "display_server_mock.h"
#include "core/core_globals.h"
#include "core/input/input_map.h"
#include "core/object/message_queue.h"
@ -139,13 +141,15 @@ int register_test_command(String p_command, TestFunc p_function);
// SEND_GUI_MOUSE_MOTION_EVENT - takes an object, position, mouse mask and modifiers e.g SEND_GUI_MOUSE_MOTION_EVENT(code_edit, Vector2(50, 50), MouseButtonMask::LEFT, KeyModifierMask::META);
// SEND_GUI_DOUBLE_CLICK - takes an object, position and modifiers. e.g SEND_GUI_DOUBLE_CLICK(code_edit, Vector2(50, 50), KeyModifierMask::META);
#define _SEND_DISPLAYSERVER_EVENT(m_event) ((DisplayServerMock *)(DisplayServer::get_singleton()))->simulate_event(m_event);
#define SEND_GUI_ACTION(m_object, m_action) \
{ \
const List<Ref<InputEvent>> *events = InputMap::get_singleton()->action_get_events(m_action); \
const List<Ref<InputEvent>>::Element *first_event = events->front(); \
Ref<InputEventKey> event = first_event->get(); \
event->set_pressed(true); \
m_object->get_viewport()->push_input(event); \
_SEND_DISPLAYSERVER_EVENT(event); \
MessageQueue::get_singleton()->flush(); \
}
@ -153,7 +157,7 @@ int register_test_command(String p_command, TestFunc p_function);
{ \
Ref<InputEventKey> event = InputEventKey::create_reference(m_input); \
event->set_pressed(true); \
m_object->get_viewport()->push_input(event); \
_SEND_DISPLAYSERVER_EVENT(event); \
MessageQueue::get_singleton()->flush(); \
}
@ -176,7 +180,7 @@ int register_test_command(String p_command, TestFunc p_function);
#define SEND_GUI_MOUSE_BUTTON_EVENT(m_object, m_local_pos, m_input, m_mask, m_modifers) \
{ \
_CREATE_GUI_MOUSE_EVENT(m_object, m_local_pos, m_input, m_mask, m_modifers); \
m_object->get_viewport()->push_input(event); \
_SEND_DISPLAYSERVER_EVENT(event); \
MessageQueue::get_singleton()->flush(); \
}
@ -184,7 +188,7 @@ int register_test_command(String p_command, TestFunc p_function);
{ \
_CREATE_GUI_MOUSE_EVENT(m_object, m_local_pos, m_input, m_mask, m_modifers); \
event->set_pressed(false); \
m_object->get_viewport()->push_input(event); \
_SEND_DISPLAYSERVER_EVENT(event); \
MessageQueue::get_singleton()->flush(); \
}
@ -192,7 +196,7 @@ int register_test_command(String p_command, TestFunc p_function);
{ \
_CREATE_GUI_MOUSE_EVENT(m_object, m_local_pos, MouseButton::LEFT, 0, m_modifers); \
event->set_double_click(true); \
m_object->get_viewport()->push_input(event); \
_SEND_DISPLAYSERVER_EVENT(event); \
MessageQueue::get_singleton()->flush(); \
}
@ -207,7 +211,7 @@ int register_test_command(String p_command, TestFunc p_function);
event->set_button_mask(m_mask); \
event->set_relative(Vector2(10, 10)); \
_UPDATE_EVENT_MODIFERS(event, m_modifers); \
m_object->get_viewport()->push_input(event); \
_SEND_DISPLAYSERVER_EVENT(event); \
MessageQueue::get_singleton()->flush(); \
CoreGlobals::print_error_enabled = errors_enabled; \
}

View file

@ -107,6 +107,7 @@
#include "modules/modules_tests.gen.h"
#include "tests/display_server_mock.h"
#include "tests/test_macros.h"
#include "scene/theme/theme_db.h"
@ -126,6 +127,7 @@ int test_main(int argc, char *argv[]) {
args.push_back(String::utf8(argv[i]));
}
OS::get_singleton()->set_cmdline("", args, List<String>());
DisplayServerMock::register_mock_driver();
// Run custom test tools.
if (test_commands) {
@ -200,11 +202,12 @@ struct GodotTestCaseListener : public doctest::IReporter {
memnew(MessageQueue);
memnew(Input);
Input::get_singleton()->set_use_accumulated_input(false);
Error err = OK;
OS::get_singleton()->set_has_server_feature_callback(nullptr);
for (int i = 0; i < DisplayServer::get_create_function_count(); i++) {
if (String("headless") == DisplayServer::get_create_function_name(i)) {
if (String("mock") == DisplayServer::get_create_function_name(i)) {
DisplayServer::create(i, "", DisplayServer::WindowMode::WINDOW_MODE_MINIMIZED, DisplayServer::VSyncMode::VSYNC_ENABLED, 0, nullptr, Vector2i(0, 0), DisplayServer::SCREEN_PRIMARY, err);
break;
}