summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/os/rw_lock.h30
-rw-r--r--core/os/semaphore.h29
-rw-r--r--editor/input_event_configuration_dialog.cpp6
-rw-r--r--editor/plugins/text_shader_editor.cpp5
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp68
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/hard_variants.gd32
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/hard_variants.out5
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs127
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs112
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs57
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs4
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs49
-rw-r--r--modules/mono/glue/runtime_interop.cpp10
-rw-r--r--scene/main/window.cpp68
-rw-r--r--scene/main/window.h2
-rw-r--r--scene/resources/shader.cpp13
-rw-r--r--scene/resources/shader.h2
-rw-r--r--scene/resources/shader_include.cpp12
-rw-r--r--scene/resources/shader_include.h3
-rw-r--r--scene/resources/tile_set.cpp74
-rw-r--r--scene/resources/tile_set.h1
-rw-r--r--servers/rendering/shader_preprocessor.cpp5
22 files changed, 551 insertions, 163 deletions
diff --git a/core/os/rw_lock.h b/core/os/rw_lock.h
index 7b232600b7..a232fcb1ce 100644
--- a/core/os/rw_lock.h
+++ b/core/os/rw_lock.h
@@ -31,39 +31,41 @@
#ifndef RW_LOCK_H
#define RW_LOCK_H
+#include "core/typedefs.h"
+
#include <shared_mutex>
class RWLock {
mutable std::shared_timed_mutex mutex;
public:
- // Lock the rwlock, block if locked by someone else
- void read_lock() const {
+ // Lock the RWLock, block if locked by someone else.
+ _ALWAYS_INLINE_ void read_lock() const {
mutex.lock_shared();
}
- // Unlock the rwlock, let other threads continue
- void read_unlock() const {
+ // Unlock the RWLock, let other threads continue.
+ _ALWAYS_INLINE_ void read_unlock() const {
mutex.unlock_shared();
}
// Attempt to lock the RWLock for reading. True on success, false means it can't lock.
- bool read_try_lock() const {
+ _ALWAYS_INLINE_ bool read_try_lock() const {
return mutex.try_lock_shared();
}
- // Lock the rwlock, block if locked by someone else
- void write_lock() {
+ // Lock the RWLock, block if locked by someone else.
+ _ALWAYS_INLINE_ void write_lock() {
mutex.lock();
}
- // Unlock the rwlock, let other thwrites continue
- void write_unlock() {
+ // Unlock the RWLock, let other threads continue.
+ _ALWAYS_INLINE_ void write_unlock() {
mutex.unlock();
}
// Attempt to lock the RWLock for writing. True on success, false means it can't lock.
- bool write_try_lock() {
+ _ALWAYS_INLINE_ bool write_try_lock() {
return mutex.try_lock();
}
};
@@ -72,11 +74,11 @@ class RWLockRead {
const RWLock &lock;
public:
- RWLockRead(const RWLock &p_lock) :
+ _ALWAYS_INLINE_ RWLockRead(const RWLock &p_lock) :
lock(p_lock) {
lock.read_lock();
}
- ~RWLockRead() {
+ _ALWAYS_INLINE_ ~RWLockRead() {
lock.read_unlock();
}
};
@@ -85,11 +87,11 @@ class RWLockWrite {
RWLock &lock;
public:
- RWLockWrite(RWLock &p_lock) :
+ _ALWAYS_INLINE_ RWLockWrite(RWLock &p_lock) :
lock(p_lock) {
lock.write_lock();
}
- ~RWLockWrite() {
+ _ALWAYS_INLINE_ ~RWLockWrite() {
lock.write_unlock();
}
};
diff --git a/core/os/semaphore.h b/core/os/semaphore.h
index 6a290f21c6..a992a4587d 100644
--- a/core/os/semaphore.h
+++ b/core/os/semaphore.h
@@ -39,32 +39,33 @@
class Semaphore {
private:
- mutable std::mutex mutex_;
- mutable std::condition_variable condition_;
- mutable unsigned long count_ = 0; // Initialized as locked.
+ mutable std::mutex mutex;
+ mutable std::condition_variable condition;
+ mutable uint32_t count = 0; // Initialized as locked.
public:
_ALWAYS_INLINE_ void post() const {
- std::lock_guard<decltype(mutex_)> lock(mutex_);
- ++count_;
- condition_.notify_one();
+ std::lock_guard lock(mutex);
+ count++;
+ condition.notify_one();
}
_ALWAYS_INLINE_ void wait() const {
- std::unique_lock<decltype(mutex_)> lock(mutex_);
- while (!count_) { // Handle spurious wake-ups.
- condition_.wait(lock);
+ std::unique_lock lock(mutex);
+ while (!count) { // Handle spurious wake-ups.
+ condition.wait(lock);
}
- --count_;
+ count--;
}
_ALWAYS_INLINE_ bool try_wait() const {
- std::lock_guard<decltype(mutex_)> lock(mutex_);
- if (count_) {
- --count_;
+ std::lock_guard lock(mutex);
+ if (count) {
+ count--;
return true;
+ } else {
+ return false;
}
- return false;
}
};
diff --git a/editor/input_event_configuration_dialog.cpp b/editor/input_event_configuration_dialog.cpp
index 48e3759e57..37c2fd5f1e 100644
--- a/editor/input_event_configuration_dialog.cpp
+++ b/editor/input_event_configuration_dialog.cpp
@@ -685,9 +685,9 @@ InputEventConfigurationDialog::InputEventConfigurationDialog() {
// Key Mode Selection
key_mode = memnew(OptionButton);
- key_mode->add_item("Keycode (Latin equvialent)", KEYMODE_KEYCODE);
- key_mode->add_item("Physical Keycode (poistion of US QWERTY keyboard)", KEYMODE_PHY_KEYCODE);
- key_mode->add_item("Unicode (case-insencetive)", KEYMODE_UNICODE);
+ key_mode->add_item(TTR("Keycode (Latin Equivalent)"), KEYMODE_KEYCODE);
+ key_mode->add_item(TTR("Physical Keycode (Position on US QWERTY Keyboard)"), KEYMODE_PHY_KEYCODE);
+ key_mode->add_item(TTR("Key Label (Unicode, Case-Insensitive)"), KEYMODE_UNICODE);
key_mode->connect("item_selected", callable_mp(this, &InputEventConfigurationDialog::_key_mode_selected));
key_mode->hide();
additional_options_container->add_child(key_mode);
diff --git a/editor/plugins/text_shader_editor.cpp b/editor/plugins/text_shader_editor.cpp
index 1b29999796..ffd9564816 100644
--- a/editor/plugins/text_shader_editor.cpp
+++ b/editor/plugins/text_shader_editor.cpp
@@ -383,11 +383,12 @@ void ShaderTextEditor::_code_complete_script(const String &p_code, List<ScriptLa
List<ScriptLanguage::CodeCompletionOption> pp_defines;
ShaderPreprocessor preprocessor;
String code;
- complete_from_path = (shader.is_valid() ? shader->get_path() : shader_inc->get_path()).get_base_dir();
+ String resource_path = (shader.is_valid() ? shader->get_path() : shader_inc->get_path());
+ complete_from_path = resource_path.get_base_dir();
if (!complete_from_path.ends_with("/")) {
complete_from_path += "/";
}
- preprocessor.preprocess(p_code, "", code, nullptr, nullptr, nullptr, nullptr, &pp_options, &pp_defines, _complete_include_paths);
+ preprocessor.preprocess(p_code, resource_path, code, nullptr, nullptr, nullptr, nullptr, &pp_options, &pp_defines, _complete_include_paths);
complete_from_path = String();
if (pp_options.size()) {
for (const ScriptLanguage::CodeCompletionOption &E : pp_options) {
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index f6385dd132..8c3521c530 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -1999,7 +1999,7 @@ void GDScriptAnalyzer::resolve_return(GDScriptParser::ReturnNode *p_return) {
#ifdef DEBUG_ENABLED
} else if (expected_type.builtin_type == Variant::INT && result.builtin_type == Variant::FLOAT) {
parser->push_warning(p_return, GDScriptWarning::NARROWING_CONVERSION);
- } else if (result.is_variant()) {
+ } else if (result.is_variant() && !expected_type.is_variant()) {
mark_node_unsafe(p_return);
#endif
}
@@ -2375,43 +2375,41 @@ void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_o
GDScriptParser::DataType result;
- if (left_type.is_variant() || right_type.is_variant()) {
- // Cannot infer type because one operand can be anything.
- result.kind = GDScriptParser::DataType::VARIANT;
- mark_node_unsafe(p_binary_op);
- } else {
- if (p_binary_op->variant_op < Variant::OP_MAX) {
- bool valid = false;
- result = get_operation_type(p_binary_op->variant_op, left_type, right_type, valid, p_binary_op);
-
- if (!valid) {
- push_error(vformat(R"(Invalid operands "%s" and "%s" for "%s" operator.)", left_type.to_string(), right_type.to_string(), Variant::get_operator_name(p_binary_op->variant_op)), p_binary_op);
- }
- } else {
- if (p_binary_op->operation == GDScriptParser::BinaryOpNode::OP_TYPE_TEST) {
- GDScriptParser::DataType test_type = right_type;
- test_type.is_meta_type = false;
-
- if (!is_type_compatible(test_type, left_type, false)) {
- // Test reverse as well to consider for subtypes.
- if (!is_type_compatible(left_type, test_type, false)) {
- if (left_type.is_hard_type()) {
- push_error(vformat(R"(Expression is of type "%s" so it can't be of type "%s".)", left_type.to_string(), test_type.to_string()), p_binary_op->left_operand);
- } else {
- // TODO: Warning.
- mark_node_unsafe(p_binary_op);
- }
- }
- }
+ if (p_binary_op->operation == GDScriptParser::BinaryOpNode::OP_TYPE_TEST) {
+ GDScriptParser::DataType test_type = right_type;
+ test_type.is_meta_type = false;
- // "is" operator is always a boolean anyway.
- result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
- result.kind = GDScriptParser::DataType::BUILTIN;
- result.builtin_type = Variant::BOOL;
+ if (!is_type_compatible(test_type, left_type, false) && !is_type_compatible(left_type, test_type, false)) {
+ if (left_type.is_hard_type()) {
+ push_error(vformat(R"(Expression is of type "%s" so it can't be of type "%s".)", left_type.to_string(), test_type.to_string()), p_binary_op->left_operand);
} else {
- ERR_PRINT("Parser bug: unknown binary operation.");
+ // TODO: Warning.
+ mark_node_unsafe(p_binary_op);
}
}
+
+ // "is" operator is always a boolean anyway.
+ result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
+ result.kind = GDScriptParser::DataType::BUILTIN;
+ result.builtin_type = Variant::BOOL;
+ } else if ((p_binary_op->variant_op == Variant::OP_EQUAL || p_binary_op->variant_op == Variant::OP_NOT_EQUAL) &&
+ ((left_type.kind == GDScriptParser::DataType::BUILTIN && left_type.builtin_type == Variant::NIL) || (right_type.kind == GDScriptParser::DataType::BUILTIN && right_type.builtin_type == Variant::NIL))) {
+ // "==" and "!=" operators always return a boolean when comparing to null.
+ result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
+ result.kind = GDScriptParser::DataType::BUILTIN;
+ result.builtin_type = Variant::BOOL;
+ } else if (left_type.is_variant() || right_type.is_variant()) {
+ // Cannot infer type because one operand can be anything.
+ result.kind = GDScriptParser::DataType::VARIANT;
+ mark_node_unsafe(p_binary_op);
+ } else if (p_binary_op->variant_op < Variant::OP_MAX) {
+ bool valid = false;
+ result = get_operation_type(p_binary_op->variant_op, left_type, right_type, valid, p_binary_op);
+ if (!valid) {
+ push_error(vformat(R"(Invalid operands "%s" and "%s" for "%s" operator.)", left_type.to_string(), right_type.to_string(), Variant::get_operator_name(p_binary_op->variant_op)), p_binary_op);
+ }
+ } else {
+ ERR_PRINT("Parser bug: unknown binary operation.");
}
p_binary_op->set_datatype(result);
@@ -4242,7 +4240,7 @@ bool GDScriptAnalyzer::validate_call_arg(const List<GDScriptParser::DataType> &p
GDScriptParser::DataType par_type = p_par_types[i];
GDScriptParser::DataType arg_type = p_call->arguments[i]->get_datatype();
- if (arg_type.is_variant()) {
+ if (arg_type.is_variant() && !(par_type.is_hard_type() && par_type.is_variant())) {
// Argument can be anything, so this is unsafe.
mark_node_unsafe(p_call->arguments[i]);
} else if (par_type.is_hard_type() && !is_type_compatible(par_type, arg_type, true)) {
diff --git a/modules/gdscript/tests/scripts/analyzer/features/hard_variants.gd b/modules/gdscript/tests/scripts/analyzer/features/hard_variants.gd
new file mode 100644
index 0000000000..211115d9cb
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/hard_variants.gd
@@ -0,0 +1,32 @@
+func variant() -> Variant: return null
+
+var member_weak = variant()
+var member_typed: Variant = variant()
+var member_inferred := variant()
+
+func param_weak(param = variant()) -> void: print(param)
+func param_typed(param: Variant = variant()) -> void: print(param)
+func param_inferred(param := variant()) -> void: print(param)
+
+func return_untyped(): return variant()
+func return_typed() -> Variant: return variant()
+
+@warning_ignore(unused_variable)
+func test() -> void:
+ var weak = variant()
+ var typed: Variant = variant()
+ var inferred := variant()
+
+ weak = variant()
+ typed = variant()
+ inferred = variant()
+
+ param_weak(typed)
+ param_typed(typed)
+ param_inferred(typed)
+
+ if typed == null: pass
+ if typed != null: pass
+ if typed is Node: pass
+
+ print('ok')
diff --git a/modules/gdscript/tests/scripts/analyzer/features/hard_variants.out b/modules/gdscript/tests/scripts/analyzer/features/hard_variants.out
new file mode 100644
index 0000000000..08491efa07
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/hard_variants.out
@@ -0,0 +1,5 @@
+GDTEST_OK
+<null>
+<null>
+<null>
+ok
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs
index f4646d1d3c..a61c5403b9 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs
@@ -189,10 +189,15 @@ namespace Godot.Collections
/// <summary>
/// Resizes this <see cref="Array"/> to the given size.
/// </summary>
+ /// <exception cref="InvalidOperationException">
+ /// The array is read-only.
+ /// </exception>
/// <param name="newSize">The new size of the array.</param>
/// <returns><see cref="Error.Ok"/> if successful, or an error code.</returns>
public Error Resize(int newSize)
{
+ ThrowIfReadOnly();
+
var self = (godot_array)NativeValue;
return NativeFuncs.godotsharp_array_resize(ref self, newSize);
}
@@ -200,8 +205,13 @@ namespace Godot.Collections
/// <summary>
/// Shuffles the contents of this <see cref="Array"/> into a random order.
/// </summary>
+ /// <exception cref="InvalidOperationException">
+ /// The array is read-only.
+ /// </exception>
public void Shuffle()
{
+ ThrowIfReadOnly();
+
var self = (godot_array)NativeValue;
NativeFuncs.godotsharp_array_shuffle(ref self);
}
@@ -240,6 +250,9 @@ namespace Godot.Collections
/// <summary>
/// Returns the item at the given <paramref name="index"/>.
/// </summary>
+ /// <exception cref="InvalidOperationException">
+ /// The property is assigned and the array is read-only.
+ /// </exception>
/// <value>The <see cref="Variant"/> item at the given <paramref name="index"/>.</value>
public unsafe Variant this[int index]
{
@@ -250,8 +263,11 @@ namespace Godot.Collections
}
set
{
+ ThrowIfReadOnly();
+
if (index < 0 || index >= Count)
throw new ArgumentOutOfRangeException(nameof(index));
+
var self = (godot_array)NativeValue;
godot_variant* ptrw = NativeFuncs.godotsharp_array_ptrw(ref self);
godot_variant* itemPtr = &ptrw[index];
@@ -264,9 +280,14 @@ namespace Godot.Collections
/// Adds an item to the end of this <see cref="Array"/>.
/// This is the same as <c>append</c> or <c>push_back</c> in GDScript.
/// </summary>
+ /// <exception cref="InvalidOperationException">
+ /// The array is read-only.
+ /// </exception>
/// <param name="item">The <see cref="Variant"/> item to add.</param>
public void Add(Variant item)
{
+ ThrowIfReadOnly();
+
godot_variant variantValue = (godot_variant)item.NativeVar;
var self = (godot_array)NativeValue;
_ = NativeFuncs.godotsharp_array_add(ref self, variantValue);
@@ -282,6 +303,9 @@ namespace Godot.Collections
/// <summary>
/// Erases all items from this <see cref="Array"/>.
/// </summary>
+ /// <exception cref="InvalidOperationException">
+ /// The array is read-only.
+ /// </exception>
public void Clear() => Resize(0);
/// <summary>
@@ -303,10 +327,15 @@ namespace Godot.Collections
/// or the position at the end of the array.
/// Existing items will be moved to the right.
/// </summary>
+ /// <exception cref="InvalidOperationException">
+ /// The array is read-only.
+ /// </exception>
/// <param name="index">The index to insert at.</param>
/// <param name="item">The <see cref="Variant"/> item to insert.</param>
public void Insert(int index, Variant item)
{
+ ThrowIfReadOnly();
+
if (index < 0 || index > Count)
throw new ArgumentOutOfRangeException(nameof(index));
@@ -319,9 +348,14 @@ namespace Godot.Collections
/// Removes the first occurrence of the specified <paramref name="item"/>
/// from this <see cref="Array"/>.
/// </summary>
+ /// <exception cref="InvalidOperationException">
+ /// The array is read-only.
+ /// </exception>
/// <param name="item">The value to remove.</param>
public bool Remove(Variant item)
{
+ ThrowIfReadOnly();
+
int index = IndexOf(item);
if (index >= 0)
{
@@ -335,9 +369,14 @@ namespace Godot.Collections
/// <summary>
/// Removes an element from this <see cref="Array"/> by index.
/// </summary>
+ /// <exception cref="InvalidOperationException">
+ /// The array is read-only.
+ /// </exception>
/// <param name="index">The index of the element to remove.</param>
public void RemoveAt(int index)
{
+ ThrowIfReadOnly();
+
if (index < 0 || index > Count)
throw new ArgumentOutOfRangeException(nameof(index));
@@ -358,7 +397,28 @@ namespace Godot.Collections
object ICollection.SyncRoot => false;
- bool ICollection<Variant>.IsReadOnly => false;
+ /// <summary>
+ /// Returns <see langword="true"/> if the array is read-only.
+ /// See <see cref="MakeReadOnly"/>.
+ /// </summary>
+ public bool IsReadOnly => NativeValue.DangerousSelfRef.IsReadOnly;
+
+ /// <summary>
+ /// Makes the <see cref="Array"/> read-only, i.e. disabled modying of the
+ /// array's elements. Does not apply to nested content, e.g. content of
+ /// nested arrays.
+ /// </summary>
+ public void MakeReadOnly()
+ {
+ if (IsReadOnly)
+ {
+ // Avoid interop call when the array is already read-only.
+ return;
+ }
+
+ var self = (godot_array)NativeValue;
+ NativeFuncs.godotsharp_array_make_read_only(ref self);
+ }
/// <summary>
/// Copies the elements of this <see cref="Array"/> to the given
@@ -472,6 +532,14 @@ namespace Godot.Collections
{
elem = NativeValue.DangerousSelfRef.Elements[index];
}
+
+ private void ThrowIfReadOnly()
+ {
+ if (IsReadOnly)
+ {
+ throw new InvalidOperationException("Array instance is read-only.");
+ }
+ }
}
internal interface IGenericGodotArray
@@ -592,6 +660,9 @@ namespace Godot.Collections
/// <summary>
/// Resizes this <see cref="Array{T}"/> to the given size.
/// </summary>
+ /// <exception cref="InvalidOperationException">
+ /// The array is read-only.
+ /// </exception>
/// <param name="newSize">The new size of the array.</param>
/// <returns><see cref="Error.Ok"/> if successful, or an error code.</returns>
public Error Resize(int newSize)
@@ -602,6 +673,9 @@ namespace Godot.Collections
/// <summary>
/// Shuffles the contents of this <see cref="Array{T}"/> into a random order.
/// </summary>
+ /// <exception cref="InvalidOperationException">
+ /// The array is read-only.
+ /// </exception>
public void Shuffle()
{
_underlyingArray.Shuffle();
@@ -634,6 +708,9 @@ namespace Godot.Collections
/// <summary>
/// Returns the value at the given <paramref name="index"/>.
/// </summary>
+ /// <exception cref="InvalidOperationException">
+ /// The property is assigned and the array is read-only.
+ /// </exception>
/// <value>The value at the given <paramref name="index"/>.</value>
public unsafe T this[int index]
{
@@ -644,8 +721,11 @@ namespace Godot.Collections
}
set
{
+ ThrowIfReadOnly();
+
if (index < 0 || index >= Count)
throw new ArgumentOutOfRangeException(nameof(index));
+
var self = (godot_array)_underlyingArray.NativeValue;
godot_variant* ptrw = NativeFuncs.godotsharp_array_ptrw(ref self);
godot_variant* itemPtr = &ptrw[index];
@@ -673,10 +753,15 @@ namespace Godot.Collections
/// or the position at the end of the array.
/// Existing items will be moved to the right.
/// </summary>
+ /// <exception cref="InvalidOperationException">
+ /// The array is read-only.
+ /// </exception>
/// <param name="index">The index to insert at.</param>
/// <param name="item">The item to insert.</param>
public void Insert(int index, T item)
{
+ ThrowIfReadOnly();
+
if (index < 0 || index > Count)
throw new ArgumentOutOfRangeException(nameof(index));
@@ -688,6 +773,9 @@ namespace Godot.Collections
/// <summary>
/// Removes an element from this <see cref="Array{T}"/> by index.
/// </summary>
+ /// <exception cref="InvalidOperationException">
+ /// The array is read-only.
+ /// </exception>
/// <param name="index">The index of the element to remove.</param>
public void RemoveAt(int index)
{
@@ -703,16 +791,35 @@ namespace Godot.Collections
/// <returns>The number of elements.</returns>
public int Count => _underlyingArray.Count;
- bool ICollection<T>.IsReadOnly => false;
+ /// <summary>
+ /// Returns <see langword="true"/> if the array is read-only.
+ /// See <see cref="MakeReadOnly"/>.
+ /// </summary>
+ public bool IsReadOnly => _underlyingArray.IsReadOnly;
+
+ /// <summary>
+ /// Makes the <see cref="Array{T}"/> read-only, i.e. disabled modying of the
+ /// array's elements. Does not apply to nested content, e.g. content of
+ /// nested arrays.
+ /// </summary>
+ public void MakeReadOnly()
+ {
+ _underlyingArray.MakeReadOnly();
+ }
/// <summary>
/// Adds an item to the end of this <see cref="Array{T}"/>.
/// This is the same as <c>append</c> or <c>push_back</c> in GDScript.
/// </summary>
+ /// <exception cref="InvalidOperationException">
+ /// The array is read-only.
+ /// </exception>
/// <param name="item">The item to add.</param>
/// <returns>The new size after adding the item.</returns>
public void Add(T item)
{
+ ThrowIfReadOnly();
+
using var variantValue = VariantUtils.CreateFrom(item);
var self = (godot_array)_underlyingArray.NativeValue;
_ = NativeFuncs.godotsharp_array_add(ref self, variantValue);
@@ -721,6 +828,9 @@ namespace Godot.Collections
/// <summary>
/// Erases all items from this <see cref="Array{T}"/>.
/// </summary>
+ /// <exception cref="InvalidOperationException">
+ /// The array is read-only.
+ /// </exception>
public void Clear()
{
_underlyingArray.Clear();
@@ -769,10 +879,15 @@ namespace Godot.Collections
/// Removes the first occurrence of the specified value
/// from this <see cref="Array{T}"/>.
/// </summary>
+ /// <exception cref="InvalidOperationException">
+ /// The array is read-only.
+ /// </exception>
/// <param name="item">The value to remove.</param>
/// <returns>A <see langword="bool"/> indicating success or failure.</returns>
public bool Remove(T item)
{
+ ThrowIfReadOnly();
+
int index = IndexOf(item);
if (index >= 0)
{
@@ -812,5 +927,13 @@ namespace Godot.Collections
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator Array<T>(Variant from) => from.AsGodotArray<T>();
+
+ private void ThrowIfReadOnly()
+ {
+ if (IsReadOnly)
+ {
+ throw new InvalidOperationException("Array instance is read-only.");
+ }
+ }
}
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs
index fa304c1b94..923b2adafd 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs
@@ -93,10 +93,15 @@ namespace Godot.Collections
/// By default, duplicate keys are not copied over, unless <paramref name="overwrite"/>
/// is <see langword="true"/>.
/// </summary>
+ /// <exception cref="InvalidOperationException">
+ /// The dictionary is read-only.
+ /// </exception>
/// <param name="dictionary">Dictionary to copy entries from.</param>
/// <param name="overwrite">If duplicate keys should be copied over as well.</param>
public void Merge(Dictionary dictionary, bool overwrite = false)
{
+ ThrowIfReadOnly();
+
var self = (godot_dictionary)NativeValue;
var other = (godot_dictionary)dictionary.NativeValue;
NativeFuncs.godotsharp_dictionary_merge(ref self, in other, overwrite.ToGodotBool());
@@ -175,8 +180,12 @@ namespace Godot.Collections
/// <summary>
/// Returns the value at the given <paramref name="key"/>.
/// </summary>
+ /// <exception cref="InvalidOperationException">
+ /// The property is assigned and the dictionary is read-only.
+ /// </exception>
/// <exception cref="KeyNotFoundException">
- /// An entry for <paramref name="key"/> does not exist in the dictionary.
+ /// The property is retrieved and an entry for <paramref name="key"/>
+ /// does not exist in the dictionary.
/// </exception>
/// <value>The value at the given <paramref name="key"/>.</value>
public Variant this[Variant key]
@@ -197,6 +206,8 @@ namespace Godot.Collections
}
set
{
+ ThrowIfReadOnly();
+
var self = (godot_dictionary)NativeValue;
NativeFuncs.godotsharp_dictionary_set_value(ref self,
(godot_variant)key.NativeVar, (godot_variant)value.NativeVar);
@@ -207,6 +218,9 @@ namespace Godot.Collections
/// Adds an value <paramref name="value"/> at key <paramref name="key"/>
/// to this <see cref="Dictionary"/>.
/// </summary>
+ /// <exception cref="InvalidOperationException">
+ /// The dictionary is read-only.
+ /// </exception>
/// <exception cref="ArgumentException">
/// An entry for <paramref name="key"/> already exists in the dictionary.
/// </exception>
@@ -214,6 +228,8 @@ namespace Godot.Collections
/// <param name="value">The value to add.</param>
public void Add(Variant key, Variant value)
{
+ ThrowIfReadOnly();
+
var variantKey = (godot_variant)key.NativeVar;
var self = (godot_dictionary)NativeValue;
@@ -230,8 +246,13 @@ namespace Godot.Collections
/// <summary>
/// Clears the dictionary, removing all entries from it.
/// </summary>
+ /// <exception cref="InvalidOperationException">
+ /// The dictionary is read-only.
+ /// </exception>
public void Clear()
{
+ ThrowIfReadOnly();
+
var self = (godot_dictionary)NativeValue;
NativeFuncs.godotsharp_dictionary_clear(ref self);
}
@@ -267,15 +288,22 @@ namespace Godot.Collections
/// <summary>
/// Removes an element from this <see cref="Dictionary"/> by key.
/// </summary>
+ /// <exception cref="InvalidOperationException">
+ /// The dictionary is read-only.
+ /// </exception>
/// <param name="key">The key of the element to remove.</param>
public bool Remove(Variant key)
{
+ ThrowIfReadOnly();
+
var self = (godot_dictionary)NativeValue;
return NativeFuncs.godotsharp_dictionary_remove_key(ref self, (godot_variant)key.NativeVar).ToBool();
}
bool ICollection<KeyValuePair<Variant, Variant>>.Remove(KeyValuePair<Variant, Variant> item)
{
+ ThrowIfReadOnly();
+
godot_variant variantKey = (godot_variant)item.Key.NativeVar;
var self = (godot_dictionary)NativeValue;
bool found = NativeFuncs.godotsharp_dictionary_try_get_value(ref self,
@@ -311,7 +339,28 @@ namespace Godot.Collections
}
}
- bool ICollection<KeyValuePair<Variant, Variant>>.IsReadOnly => false;
+ /// <summary>
+ /// Returns <see langword="true"/> if the dictionary is read-only.
+ /// See <see cref="MakeReadOnly"/>.
+ /// </summary>
+ public bool IsReadOnly => NativeValue.DangerousSelfRef.IsReadOnly;
+
+ /// <summary>
+ /// Makes the <see cref="Dictionary"/> read-only, i.e. disabled modying of the
+ /// dictionary's elements. Does not apply to nested content, e.g. content of
+ /// nested dictionaries.
+ /// </summary>
+ public void MakeReadOnly()
+ {
+ if (IsReadOnly)
+ {
+ // Avoid interop call when the dictionary is already read-only.
+ return;
+ }
+
+ var self = (godot_dictionary)NativeValue;
+ NativeFuncs.godotsharp_dictionary_make_read_only(ref self);
+ }
/// <summary>
/// Gets the value for the given <paramref name="key"/> in the dictionary.
@@ -397,6 +446,14 @@ namespace Godot.Collections
using (str)
return Marshaling.ConvertStringToManaged(str);
}
+
+ private void ThrowIfReadOnly()
+ {
+ if (IsReadOnly)
+ {
+ throw new InvalidOperationException("Dictionary instance is read-only.");
+ }
+ }
}
internal interface IGenericGodotDictionary
@@ -508,6 +565,9 @@ namespace Godot.Collections
/// By default, duplicate keys are not copied over, unless <paramref name="overwrite"/>
/// is <see langword="true"/>.
/// </summary>
+ /// <exception cref="InvalidOperationException">
+ /// The dictionary is read-only.
+ /// </exception>
/// <param name="dictionary">Dictionary to copy entries from.</param>
/// <param name="overwrite">If duplicate keys should be copied over as well.</param>
public void Merge(Dictionary<TKey, TValue> dictionary, bool overwrite = false)
@@ -536,6 +596,13 @@ namespace Godot.Collections
/// <summary>
/// Returns the value at the given <paramref name="key"/>.
/// </summary>
+ /// <exception cref="InvalidOperationException">
+ /// The property is assigned and the dictionary is read-only.
+ /// </exception>
+ /// <exception cref="KeyNotFoundException">
+ /// The property is retrieved and an entry for <paramref name="key"/>
+ /// does not exist in the dictionary.
+ /// </exception>
/// <value>The value at the given <paramref name="key"/>.</value>
public TValue this[TKey key]
{
@@ -557,6 +624,8 @@ namespace Godot.Collections
}
set
{
+ ThrowIfReadOnly();
+
using var variantKey = VariantUtils.CreateFrom(key);
using var variantValue = VariantUtils.CreateFrom(value);
var self = (godot_dictionary)_underlyingDict.NativeValue;
@@ -616,10 +685,15 @@ namespace Godot.Collections
/// Adds an object <paramref name="value"/> at key <paramref name="key"/>
/// to this <see cref="Dictionary{TKey, TValue}"/>.
/// </summary>
+ /// <exception cref="InvalidOperationException">
+ /// The dictionary is read-only.
+ /// </exception>
/// <param name="key">The key at which to add the object.</param>
/// <param name="value">The object to add.</param>
public void Add(TKey key, TValue value)
{
+ ThrowIfReadOnly();
+
using var variantKey = VariantUtils.CreateFrom(key);
var self = (godot_dictionary)_underlyingDict.NativeValue;
@@ -645,9 +719,14 @@ namespace Godot.Collections
/// <summary>
/// Removes an element from this <see cref="Dictionary{TKey, TValue}"/> by key.
/// </summary>
+ /// <exception cref="InvalidOperationException">
+ /// The dictionary is read-only.
+ /// </exception>
/// <param name="key">The key of the element to remove.</param>
public bool Remove(TKey key)
{
+ ThrowIfReadOnly();
+
using var variantKey = VariantUtils.CreateFrom(key);
var self = (godot_dictionary)_underlyingDict.NativeValue;
return NativeFuncs.godotsharp_dictionary_remove_key(ref self, variantKey).ToBool();
@@ -683,7 +762,21 @@ namespace Godot.Collections
/// <returns>The number of elements.</returns>
public int Count => _underlyingDict.Count;
- bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly => false;
+ /// <summary>
+ /// Returns <see langword="true"/> if the dictionary is read-only.
+ /// See <see cref="MakeReadOnly"/>.
+ /// </summary>
+ public bool IsReadOnly => _underlyingDict.IsReadOnly;
+
+ /// <summary>
+ /// Makes the <see cref="Dictionary{TKey, TValue}"/> read-only, i.e. disabled
+ /// modying of the dictionary's elements. Does not apply to nested content,
+ /// e.g. content of nested dictionaries.
+ /// </summary>
+ public void MakeReadOnly()
+ {
+ _underlyingDict.MakeReadOnly();
+ }
void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item)
=> Add(item.Key, item.Value);
@@ -691,6 +784,9 @@ namespace Godot.Collections
/// <summary>
/// Clears the dictionary, removing all entries from it.
/// </summary>
+ /// <exception cref="InvalidOperationException">
+ /// The dictionary is read-only.
+ /// </exception>
public void Clear() => _underlyingDict.Clear();
bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item)
@@ -740,6 +836,8 @@ namespace Godot.Collections
bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item)
{
+ ThrowIfReadOnly();
+
using var variantKey = VariantUtils.CreateFrom(item.Key);
var self = (godot_dictionary)_underlyingDict.NativeValue;
bool found = NativeFuncs.godotsharp_dictionary_try_get_value(ref self,
@@ -789,5 +887,13 @@ namespace Godot.Collections
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator Dictionary<TKey, TValue>(Variant from) =>
from.AsGodotDictionary<TKey, TValue>();
+
+ private void ThrowIfReadOnly()
+ {
+ if (IsReadOnly)
+ {
+ throw new InvalidOperationException("Dictionary instance is read-only.");
+ }
+ }
}
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs
index c295e500de..43e7c7eb9a 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs
@@ -697,6 +697,9 @@ namespace Godot.NativeInterop
private uint _safeRefCount;
public VariantVector _arrayVector;
+
+ private unsafe godot_variant* _readOnly;
+
// There are more fields here, but we don't care as we never store this in C#
public readonly int Size
@@ -704,6 +707,12 @@ namespace Godot.NativeInterop
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _arrayVector.Size;
}
+
+ public readonly unsafe bool IsReadOnly
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => _readOnly != null;
+ }
}
[StructLayout(LayoutKind.Sequential)]
@@ -737,6 +746,12 @@ namespace Godot.NativeInterop
get => _p != null ? _p->Size : 0;
}
+ public readonly unsafe bool IsReadOnly
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => _p != null && _p->IsReadOnly;
+ }
+
public unsafe void Dispose()
{
if (_p == null)
@@ -766,35 +781,59 @@ namespace Godot.NativeInterop
// A correctly constructed value needs to call the native default constructor to allocate `_p`.
// Don't pass a C# default constructed `godot_dictionary` to native code, unless it's going to
// be re-assigned a new value (the copy constructor checks if `_p` is null so that's fine).
- [StructLayout(LayoutKind.Sequential)]
+ [StructLayout(LayoutKind.Explicit)]
// ReSharper disable once InconsistentNaming
public ref struct godot_dictionary
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal readonly unsafe godot_dictionary* GetUnsafeAddress()
- => (godot_dictionary*)Unsafe.AsPointer(ref Unsafe.AsRef(in _p));
+ => (godot_dictionary*)Unsafe.AsPointer(ref Unsafe.AsRef(in _getUnsafeAddressHelper));
- private IntPtr _p;
+ [FieldOffset(0)] private byte _getUnsafeAddressHelper;
- public readonly bool IsAllocated
+ [FieldOffset(0)] private unsafe DictionaryPrivate* _p;
+
+ [StructLayout(LayoutKind.Sequential)]
+ private struct DictionaryPrivate
+ {
+ private uint _safeRefCount;
+
+ private unsafe godot_variant* _readOnly;
+
+ // There are more fields here, but we don't care as we never store this in C#
+
+ public readonly unsafe bool IsReadOnly
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => _readOnly != null;
+ }
+ }
+
+ public readonly unsafe bool IsAllocated
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- get => _p != IntPtr.Zero;
+ get => _p != null;
}
- public void Dispose()
+ public readonly unsafe bool IsReadOnly
{
- if (_p == IntPtr.Zero)
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => _p != null && _p->IsReadOnly;
+ }
+
+ public unsafe void Dispose()
+ {
+ if (_p == null)
return;
NativeFuncs.godotsharp_dictionary_destroy(ref this);
- _p = IntPtr.Zero;
+ _p = null;
}
[StructLayout(LayoutKind.Sequential)]
// ReSharper disable once InconsistentNaming
internal struct movable
{
- private IntPtr _p;
+ private unsafe DictionaryPrivate* _p;
public static unsafe explicit operator movable(in godot_dictionary value)
=> *(movable*)CustomUnsafe.AsPointer(ref CustomUnsafe.AsRef(value));
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs
index d116284e1c..1e23689c95 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs
@@ -376,6 +376,8 @@ namespace Godot.NativeInterop
public static partial Error godotsharp_array_resize(ref godot_array p_self, int p_new_size);
+ public static partial void godotsharp_array_make_read_only(ref godot_array p_self);
+
public static partial void godotsharp_array_shuffle(ref godot_array p_self);
public static partial void godotsharp_array_to_string(ref godot_array p_self, out godot_string r_str);
@@ -416,6 +418,8 @@ namespace Godot.NativeInterop
public static partial godot_bool godotsharp_dictionary_remove_key(ref godot_dictionary p_self,
in godot_variant p_key);
+ public static partial void godotsharp_dictionary_make_read_only(ref godot_dictionary p_self);
+
public static partial void godotsharp_dictionary_to_string(ref godot_dictionary p_self, out godot_string r_str);
// StringExtensions
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs
index cb9525b49c..df67e075ac 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs
@@ -69,19 +69,6 @@ namespace Godot
}
/// <summary>
- /// Returns <see langword="true"/> if the strings begins
- /// with the given string <paramref name="text"/>.
- /// </summary>
- /// <param name="instance">The string to check.</param>
- /// <param name="text">The beginning string.</param>
- /// <returns>If the string begins with the given string.</returns>
- [Obsolete("Use string.StartsWith instead.")]
- public static bool BeginsWith(this string instance, string text)
- {
- return instance.StartsWith(text);
- }
-
- /// <summary>
/// Returns the bigrams (pairs of consecutive letters) of this string.
/// </summary>
/// <param name="instance">The string that will be used.</param>
@@ -618,7 +605,7 @@ namespace Godot
}
else
{
- if (instance.BeginsWith("/"))
+ if (instance.StartsWith('/'))
{
rs = instance.Substring(1);
directory = "/";
@@ -1199,23 +1186,6 @@ namespace Godot
}
/// <summary>
- /// Returns a copy of the string with characters removed from the left.
- /// The <paramref name="chars"/> argument is a string specifying the set of characters
- /// to be removed.
- /// Note: The <paramref name="chars"/> is not a prefix. See <see cref="TrimPrefix"/>
- /// method that will remove a single prefix string rather than a set of characters.
- /// </summary>
- /// <seealso cref="RStrip(string, string)"/>
- /// <param name="instance">The string to remove characters from.</param>
- /// <param name="chars">The characters to be removed.</param>
- /// <returns>A copy of the string with characters removed from the left.</returns>
- [Obsolete("Use string.TrimStart instead.")]
- public static string LStrip(this string instance, string chars)
- {
- return instance.TrimStart(chars.ToCharArray());
- }
-
- /// <summary>
/// Do a simple expression match, where '*' matches zero or more
/// arbitrary characters and '?' matches any single character except '.'.
/// </summary>
@@ -1504,23 +1474,6 @@ namespace Godot
}
/// <summary>
- /// Returns a copy of the string with characters removed from the right.
- /// The <paramref name="chars"/> argument is a string specifying the set of characters
- /// to be removed.
- /// Note: The <paramref name="chars"/> is not a suffix. See <see cref="TrimSuffix"/>
- /// method that will remove a single suffix string rather than a set of characters.
- /// </summary>
- /// <seealso cref="LStrip(string, string)"/>
- /// <param name="instance">The string to remove characters from.</param>
- /// <param name="chars">The characters to be removed.</param>
- /// <returns>A copy of the string with characters removed from the right.</returns>
- [Obsolete("Use string.TrimEnd instead.")]
- public static string RStrip(this string instance, string chars)
- {
- return instance.TrimEnd(chars.ToCharArray());
- }
-
- /// <summary>
/// Returns the SHA-1 hash of the string as an array of bytes.
/// </summary>
/// <seealso cref="Sha1Text(string)"/>
diff --git a/modules/mono/glue/runtime_interop.cpp b/modules/mono/glue/runtime_interop.cpp
index e7f50d2caa..d17fe3e75f 100644
--- a/modules/mono/glue/runtime_interop.cpp
+++ b/modules/mono/glue/runtime_interop.cpp
@@ -1012,6 +1012,10 @@ int32_t godotsharp_array_resize(Array *p_self, int32_t p_new_size) {
return (int32_t)p_self->resize(p_new_size);
}
+void godotsharp_array_make_read_only(Array *p_self) {
+ p_self->make_read_only();
+}
+
void godotsharp_array_shuffle(Array *p_self) {
p_self->shuffle();
}
@@ -1081,6 +1085,10 @@ bool godotsharp_dictionary_remove_key(Dictionary *p_self, const Variant *p_key)
return p_self->erase(*p_key);
}
+void godotsharp_dictionary_make_read_only(Dictionary *p_self) {
+ p_self->make_read_only();
+}
+
void godotsharp_dictionary_to_string(const Dictionary *p_self, String *r_str) {
*r_str = Variant(*p_self).operator String();
}
@@ -1439,6 +1447,7 @@ static const void *unmanaged_callbacks[]{
(void *)godotsharp_array_insert,
(void *)godotsharp_array_remove_at,
(void *)godotsharp_array_resize,
+ (void *)godotsharp_array_make_read_only,
(void *)godotsharp_array_shuffle,
(void *)godotsharp_array_to_string,
(void *)godotsharp_dictionary_try_get_value,
@@ -1454,6 +1463,7 @@ static const void *unmanaged_callbacks[]{
(void *)godotsharp_dictionary_merge,
(void *)godotsharp_dictionary_recursive_equal,
(void *)godotsharp_dictionary_remove_key,
+ (void *)godotsharp_dictionary_make_read_only,
(void *)godotsharp_dictionary_to_string,
(void *)godotsharp_string_simplify_path,
(void *)godotsharp_string_to_camel_case,
diff --git a/scene/main/window.cpp b/scene/main/window.cpp
index 3ceb27b3e6..869d12b4df 100644
--- a/scene/main/window.cpp
+++ b/scene/main/window.cpp
@@ -663,18 +663,18 @@ void Window::set_visible(bool p_visible) {
return;
}
- visible = p_visible;
-
if (!is_inside_tree()) {
+ visible = p_visible;
return;
}
- if (updating_child_controls) {
- _update_child_controls();
- }
-
ERR_FAIL_COND_MSG(get_parent() == nullptr, "Can't change visibility of main window.");
+ visible = p_visible;
+
+ // Stop any queued resizing, as the window will be resized right now.
+ updating_child_controls = false;
+
Viewport *embedder_vp = _get_embedder();
if (!embedder_vp) {
@@ -833,7 +833,7 @@ bool Window::is_visible() const {
}
void Window::_update_window_size() {
- // Force window to respect size limitations of rendering server
+ // Force window to respect size limitations of rendering server.
RenderingServer *rendering_server = RenderingServer::get_singleton();
if (rendering_server) {
Size2i max_window_size = rendering_server->get_maximum_viewport_size();
@@ -1130,6 +1130,7 @@ void Window::_notification(int p_what) {
case NOTIFICATION_READY: {
if (wrap_controls) {
+ // Finish any resizing immediately so it doesn't interfere on stuff overriding _ready().
_update_child_controls();
}
} break;
@@ -1138,9 +1139,7 @@ void Window::_notification(int p_what) {
_invalidate_theme_cache();
_update_theme_item_cache();
- if (embedder) {
- embedder->_sub_window_update(this);
- } else if (window_id != DisplayServer::INVALID_WINDOW_ID) {
+ if (!embedder && window_id != DisplayServer::INVALID_WINDOW_ID) {
String tr_title = atr(title);
#ifdef DEBUG_ENABLED
if (window_id == DisplayServer::MAIN_WINDOW_ID) {
@@ -1152,8 +1151,6 @@ void Window::_notification(int p_what) {
#endif
DisplayServer::get_singleton()->window_set_title(tr_title, window_id);
}
-
- child_controls_changed();
} break;
case NOTIFICATION_EXIT_TREE: {
@@ -1254,8 +1251,13 @@ Vector<Vector2> Window::get_mouse_passthrough_polygon() const {
void Window::set_wrap_controls(bool p_enable) {
wrap_controls = p_enable;
- if (wrap_controls) {
- child_controls_changed();
+
+ if (!is_inside_tree()) {
+ return;
+ }
+
+ if (updating_child_controls) {
+ _update_child_controls();
} else {
_update_window_size();
}
@@ -1282,23 +1284,23 @@ Size2 Window::_get_contents_minimum_size() const {
return max;
}
-void Window::_update_child_controls() {
- if (!updating_child_controls) {
+void Window::child_controls_changed() {
+ if (!is_inside_tree() || !visible || updating_child_controls) {
return;
}
- _update_window_size();
-
- updating_child_controls = false;
+ updating_child_controls = true;
+ call_deferred(SNAME("_update_child_controls"));
}
-void Window::child_controls_changed() {
- if (!is_inside_tree() || !visible || updating_child_controls) {
+void Window::_update_child_controls() {
+ if (!updating_child_controls) {
return;
}
- updating_child_controls = true;
- call_deferred(SNAME("_update_child_controls"));
+ _update_window_size();
+
+ updating_child_controls = false;
}
bool Window::_can_consume_input_events() const {
@@ -1647,7 +1649,24 @@ void Window::_invalidate_theme_cache() {
void Window::_update_theme_item_cache() {
// Request an update on the next frame to reflect theme changes.
// Updating without a delay can cause a lot of lag.
- child_controls_changed();
+ if (!wrap_controls) {
+ updating_embedded_window = true;
+ call_deferred(SNAME("_update_embedded_window"));
+ } else {
+ child_controls_changed();
+ }
+}
+
+void Window::_update_embedded_window() {
+ if (!updating_embedded_window) {
+ return;
+ }
+
+ if (embedder) {
+ embedder->_sub_window_update(this);
+ };
+
+ updating_embedded_window = false;
}
void Window::set_theme_type_variation(const StringName &p_theme_type) {
@@ -2201,6 +2220,7 @@ void Window::_bind_methods() {
ClassDB::bind_method(D_METHOD("child_controls_changed"), &Window::child_controls_changed);
ClassDB::bind_method(D_METHOD("_update_child_controls"), &Window::_update_child_controls);
+ ClassDB::bind_method(D_METHOD("_update_embedded_window"), &Window::_update_embedded_window);
ClassDB::bind_method(D_METHOD("set_theme", "theme"), &Window::set_theme);
ClassDB::bind_method(D_METHOD("get_theme"), &Window::get_theme);
diff --git a/scene/main/window.h b/scene/main/window.h
index 18841a4f1a..182caf5f0c 100644
--- a/scene/main/window.h
+++ b/scene/main/window.h
@@ -116,6 +116,7 @@ private:
bool exclusive = false;
bool wrap_controls = false;
bool updating_child_controls = false;
+ bool updating_embedded_window = false;
bool clamp_to_embedder = false;
LayoutDirection layout_dir = LAYOUT_DIRECTION_INHERITED;
@@ -123,6 +124,7 @@ private:
bool auto_translate = true;
void _update_child_controls();
+ void _update_embedded_window();
Size2i content_scale_size;
ContentScaleMode content_scale_mode = CONTENT_SCALE_MODE_DISABLED;
diff --git a/scene/resources/shader.cpp b/scene/resources/shader.cpp
index a09dbd50cf..fd2be9ba22 100644
--- a/scene/resources/shader.cpp
+++ b/scene/resources/shader.cpp
@@ -55,6 +55,12 @@ void Shader::set_path(const String &p_path, bool p_take_over) {
RS::get_singleton()->shader_set_path_hint(shader, p_path);
}
+void Shader::set_include_path(const String &p_path) {
+ // Used only if the shader does not have a resource path set,
+ // for example during loading stage or when created by code.
+ include_path = p_path;
+}
+
void Shader::set_code(const String &p_code) {
for (Ref<ShaderInclude> E : include_dependencies) {
E->disconnect(SNAME("changed"), callable_mp(this, &Shader::_dependency_changed));
@@ -80,11 +86,15 @@ void Shader::set_code(const String &p_code) {
HashSet<Ref<ShaderInclude>> new_include_dependencies;
{
+ String path = get_path();
+ if (path.is_empty()) {
+ path = include_path;
+ }
// Preprocessor must run here and not in the server because:
// 1) Need to keep track of include dependencies at resource level
// 2) Server does not do interaction with Resource filetypes, this is a scene level feature.
ShaderPreprocessor preprocessor;
- preprocessor.preprocess(p_code, "", pp_code, nullptr, nullptr, nullptr, &new_include_dependencies);
+ preprocessor.preprocess(p_code, path, pp_code, nullptr, nullptr, nullptr, &new_include_dependencies);
}
// This ensures previous include resources are not freed and then re-loaded during parse (which would make compiling slower)
@@ -231,6 +241,7 @@ Ref<Resource> ResourceFormatLoaderShader::load(const String &p_path, const Strin
String str;
str.parse_utf8((const char *)buffer.ptr(), buffer.size());
+ shader->set_include_path(p_path);
shader->set_code(str);
if (r_error) {
diff --git a/scene/resources/shader.h b/scene/resources/shader.h
index 55608b6c11..ca889940ef 100644
--- a/scene/resources/shader.h
+++ b/scene/resources/shader.h
@@ -56,6 +56,7 @@ private:
Mode mode = MODE_SPATIAL;
HashSet<Ref<ShaderInclude>> include_dependencies;
String code;
+ String include_path;
HashMap<StringName, HashMap<int, Ref<Texture2D>>> default_textures;
@@ -72,6 +73,7 @@ public:
virtual Mode get_mode() const;
virtual void set_path(const String &p_path, bool p_take_over = false) override;
+ void set_include_path(const String &p_path);
void set_code(const String &p_code);
String get_code() const;
diff --git a/scene/resources/shader_include.cpp b/scene/resources/shader_include.cpp
index cd5e9861f7..68137cbec0 100644
--- a/scene/resources/shader_include.cpp
+++ b/scene/resources/shader_include.cpp
@@ -45,9 +45,14 @@ void ShaderInclude::set_code(const String &p_code) {
}
{
+ String path = get_path();
+ if (path.is_empty()) {
+ path = include_path;
+ }
+
String pp_code;
ShaderPreprocessor preprocessor;
- preprocessor.preprocess(p_code, "", pp_code, nullptr, nullptr, nullptr, &new_dependencies);
+ preprocessor.preprocess(p_code, path, pp_code, nullptr, nullptr, nullptr, &new_dependencies);
}
// This ensures previous include resources are not freed and then re-loaded during parse (which would make compiling slower)
@@ -64,6 +69,10 @@ String ShaderInclude::get_code() const {
return code;
}
+void ShaderInclude::set_include_path(const String &p_path) {
+ include_path = p_path;
+}
+
void ShaderInclude::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_code", "code"), &ShaderInclude::set_code);
ClassDB::bind_method(D_METHOD("get_code"), &ShaderInclude::get_code);
@@ -86,6 +95,7 @@ Ref<Resource> ResourceFormatLoaderShaderInclude::load(const String &p_path, cons
String str;
str.parse_utf8((const char *)buffer.ptr(), buffer.size());
+ shader_inc->set_include_path(p_path);
shader_inc->set_code(str);
if (r_error) {
diff --git a/scene/resources/shader_include.h b/scene/resources/shader_include.h
index 04f4f5cf84..a8949b327e 100644
--- a/scene/resources/shader_include.h
+++ b/scene/resources/shader_include.h
@@ -42,6 +42,7 @@ class ShaderInclude : public Resource {
private:
String code;
+ String include_path;
HashSet<Ref<ShaderInclude>> dependencies;
void _dependency_changed();
@@ -51,6 +52,8 @@ protected:
public:
void set_code(const String &p_text);
String get_code() const;
+
+ void set_include_path(const String &p_path);
};
class ResourceFormatLoaderShaderInclude : public ResourceFormatLoader {
diff --git a/scene/resources/tile_set.cpp b/scene/resources/tile_set.cpp
index 56efee5ca6..58a638804d 100644
--- a/scene/resources/tile_set.cpp
+++ b/scene/resources/tile_set.cpp
@@ -2555,6 +2555,11 @@ void TileSet::_compatibility_conversion() {
bool flip_v = flags & 2;
bool transpose = flags & 4;
+ Transform2D xform;
+ xform = flip_h ? xform.scaled(Size2(-1, 1)) : xform;
+ xform = flip_v ? xform.scaled(Size2(1, -1)) : xform;
+ xform = transpose ? xform.rotated(Math_PI).scaled(Size2(-1, -1)) : xform;
+
int alternative_tile = 0;
if (!atlas_source->has_tile(coords)) {
atlas_source->create_tile(coords);
@@ -2591,14 +2596,26 @@ void TileSet::_compatibility_conversion() {
if (ctd->occluder.is_valid()) {
if (get_occlusion_layers_count() < 1) {
add_occlusion_layer();
+ };
+ Ref<OccluderPolygon2D> occluder = ctd->occluder->duplicate();
+ Vector<Vector2> polygon = ctd->occluder->get_polygon();
+ for (int index = 0; index < polygon.size(); index++) {
+ polygon.write[index] = xform.xform(polygon[index] - ctd->region.get_size() / 2.0);
}
- tile_data->set_occluder(0, ctd->occluder);
+ occluder->set_polygon(polygon);
+ tile_data->set_occluder(0, occluder);
}
if (ctd->navigation.is_valid()) {
if (get_navigation_layers_count() < 1) {
add_navigation_layer();
}
- tile_data->set_navigation_polygon(0, ctd->autotile_navpoly_map[coords]);
+ Ref<NavigationPolygon> navigation = ctd->navigation->duplicate();
+ Vector<Vector2> vertices = navigation->get_vertices();
+ for (int index = 0; index < vertices.size(); index++) {
+ vertices.write[index] = xform.xform(vertices[index] - ctd->region.get_size() / 2.0);
+ }
+ navigation->set_vertices(vertices);
+ tile_data->set_navigation_polygon(0, navigation);
}
tile_data->set_z_index(ctd->z_index);
@@ -2616,7 +2633,7 @@ void TileSet::_compatibility_conversion() {
if (convex_shape.is_valid()) {
Vector<Vector2> polygon = convex_shape->get_points();
for (int point_index = 0; point_index < polygon.size(); point_index++) {
- polygon.write[point_index] = csd.transform.xform(polygon[point_index]);
+ polygon.write[point_index] = xform.xform(csd.transform.xform(polygon[point_index]) - ctd->region.get_size() / 2.0);
}
tile_data->set_collision_polygons_count(0, tile_data->get_collision_polygons_count(0) + 1);
int index = tile_data->get_collision_polygons_count(0) - 1;
@@ -2627,6 +2644,11 @@ void TileSet::_compatibility_conversion() {
}
}
}
+ // Update the size count.
+ if (!compatibility_size_count.has(ctd->region.get_size())) {
+ compatibility_size_count[ctd->region.get_size()] = 0;
+ }
+ compatibility_size_count[ctd->region.get_size()]++;
} break;
case COMPATIBILITY_TILE_MODE_AUTO_TILE: {
// Not supported. It would need manual conversion.
@@ -2647,6 +2669,11 @@ void TileSet::_compatibility_conversion() {
bool flip_v = flags & 2;
bool transpose = flags & 4;
+ Transform2D xform;
+ xform = flip_h ? xform.scaled(Size2(-1, 1)) : xform;
+ xform = flip_v ? xform.scaled(Size2(1, -1)) : xform;
+ xform = transpose ? xform.rotated(Math_PI).scaled(Size2(-1, -1)) : xform;
+
int alternative_tile = 0;
if (!atlas_source->has_tile(coords)) {
atlas_source->create_tile(coords);
@@ -2684,13 +2711,25 @@ void TileSet::_compatibility_conversion() {
if (get_occlusion_layers_count() < 1) {
add_occlusion_layer();
}
- tile_data->set_occluder(0, ctd->autotile_occluder_map[coords]);
+ Ref<OccluderPolygon2D> occluder = ctd->autotile_occluder_map[coords]->duplicate();
+ Vector<Vector2> polygon = ctd->occluder->get_polygon();
+ for (int index = 0; index < polygon.size(); index++) {
+ polygon.write[index] = xform.xform(polygon[index] - ctd->region.get_size() / 2.0);
+ }
+ occluder->set_polygon(polygon);
+ tile_data->set_occluder(0, occluder);
}
if (ctd->autotile_navpoly_map.has(coords)) {
if (get_navigation_layers_count() < 1) {
add_navigation_layer();
}
- tile_data->set_navigation_polygon(0, ctd->autotile_navpoly_map[coords]);
+ Ref<NavigationPolygon> navigation = ctd->autotile_navpoly_map[coords]->duplicate();
+ Vector<Vector2> vertices = navigation->get_vertices();
+ for (int index = 0; index < vertices.size(); index++) {
+ vertices.write[index] = xform.xform(vertices[index] - ctd->region.get_size() / 2.0);
+ }
+ navigation->set_vertices(vertices);
+ tile_data->set_navigation_polygon(0, navigation);
}
if (ctd->autotile_priority_map.has(coords)) {
tile_data->set_probability(ctd->autotile_priority_map[coords]);
@@ -2712,7 +2751,7 @@ void TileSet::_compatibility_conversion() {
if (convex_shape.is_valid()) {
Vector<Vector2> polygon = convex_shape->get_points();
for (int point_index = 0; point_index < polygon.size(); point_index++) {
- polygon.write[point_index] = csd.transform.xform(polygon[point_index]);
+ polygon.write[point_index] = xform.xform(csd.transform.xform(polygon[point_index]) - ctd->autotile_tile_size / 2.0);
}
tile_data->set_collision_polygons_count(0, tile_data->get_collision_polygons_count(0) + 1);
int index = tile_data->get_collision_polygons_count(0) - 1;
@@ -2735,6 +2774,12 @@ void TileSet::_compatibility_conversion() {
}
}
}
+
+ // Update the size count.
+ if (!compatibility_size_count.has(ctd->region.get_size())) {
+ compatibility_size_count[ctd->autotile_tile_size] = 0;
+ }
+ compatibility_size_count[ctd->autotile_tile_size] += atlas_size.x * atlas_size.y;
} break;
}
@@ -2751,7 +2796,18 @@ void TileSet::_compatibility_conversion() {
}
}
- // Reset compatibility data
+ // Update the TileSet tile_size according to the most common size found.
+ Vector2i max_size = get_tile_size();
+ int max_count = 0;
+ for (KeyValue<Vector2i, int> kv : compatibility_size_count) {
+ if (kv.value > max_count) {
+ max_size = kv.key;
+ max_count = kv.value;
+ }
+ }
+ set_tile_size(max_size);
+
+ // Reset compatibility data (besides the histogram counts)
for (const KeyValue<int, CompatibilityTileData *> &E : compatibility_data) {
memdelete(E.value);
}
@@ -2932,6 +2988,10 @@ bool TileSet::_set(const StringName &p_name, const Variant &p_value) {
}
ctd->shapes.push_back(csd);
}
+ } else if (what == "occluder") {
+ ctd->occluder = p_value;
+ } else if (what == "navigation") {
+ ctd->navigation = p_value;
/*
// IGNORED FOR NOW, they seem duplicated data compared to the shapes array
diff --git a/scene/resources/tile_set.h b/scene/resources/tile_set.h
index 044b46a701..ad25629a1c 100644
--- a/scene/resources/tile_set.h
+++ b/scene/resources/tile_set.h
@@ -198,6 +198,7 @@ private:
HashMap<int, CompatibilityTileData *> compatibility_data;
HashMap<int, int> compatibility_tilemap_mapping_tile_modes;
HashMap<int, RBMap<Array, Array>> compatibility_tilemap_mapping;
+ HashMap<Vector2i, int> compatibility_size_count;
void _compatibility_conversion();
diff --git a/servers/rendering/shader_preprocessor.cpp b/servers/rendering/shader_preprocessor.cpp
index 816d937e9f..b45a7c0db3 100644
--- a/servers/rendering/shader_preprocessor.cpp
+++ b/servers/rendering/shader_preprocessor.cpp
@@ -674,6 +674,11 @@ void ShaderPreprocessor::process_include(Tokenizer *p_tokenizer) {
return;
}
+ path = path.simplify_path();
+ if (path.is_relative_path()) {
+ path = state->current_filename.get_base_dir().path_join(path);
+ }
+
Ref<Resource> res = ResourceLoader::load(path);
if (res.is_null()) {
set_error(RTR("Shader include load failed. Does the shader include exist? Is there a cyclic dependency?"), line);