From cfe67d268165aec85f0eacc56d6bedbccdfe8a47 Mon Sep 17 00:00:00 2001 From: Matt Enad Date: Sun, 15 Sep 2024 12:16:50 -0400 Subject: [PATCH] CSharp struct with unique internals --- core/variant/variant.cpp | 12 + core/variant/variant.h | 2 + modules/mono/editor/bindings_generator.cpp | 9 +- modules/mono/editor/bindings_generator.h | 3 +- .../Core/NativeInterop/CustomUnsafe.cs | 16 + .../Core/NativeInterop/InteropStructs.cs | 100 + .../Core/NativeInterop/Marshaling.cs | 3 + .../Core/NativeInterop/NativeFuncs.cs | 50 + .../NativeInterop/NativeFuncs.extended.cs | 12 + .../Core/NativeInterop/VariantUtils.cs | 23 + .../glue/GodotSharp/GodotSharp/Core/Struct.cs | 1708 +++++++++++++++++ .../GodotSharp/GodotSharp/Core/Variant.cs | 15 + .../GodotSharp/GodotSharp/GodotSharp.csproj | 1 + modules/mono/glue/runtime_interop.cpp | 121 ++ modules/mono/interop_types.h | 6 + 15 files changed, 2079 insertions(+), 2 deletions(-) create mode 100644 modules/mono/glue/GodotSharp/GodotSharp/Core/Struct.cs diff --git a/core/variant/variant.cpp b/core/variant/variant.cpp index ce7b76af487b..ccdb479311ed 100644 --- a/core/variant/variant.cpp +++ b/core/variant/variant.cpp @@ -2331,6 +2331,18 @@ Variant::operator PackedVector4Array() const { } } +Variant::operator StructInfo() const { + if (is_struct()) { + //StructInfo info = operator StructInfo(); + //return Array(&info); + //TODO: Confusion + } + + //return _convert_array_from_variant(*this); + Array va = operator Array(); + return *va.get_struct_info(); +} + /* helpers */ Variant::operator Vector<::RID>() const { diff --git a/core/variant/variant.h b/core/variant/variant.h index 3d876296ac91..a2fe5cf7fb5c 100644 --- a/core/variant/variant.h +++ b/core/variant/variant.h @@ -419,6 +419,8 @@ class Variant { operator PackedColorArray() const; operator PackedVector4Array() const; + operator StructInfo() const; + operator Vector<::RID>() const; operator Vector() const; operator Vector() const; diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index 2ec073e4fa41..de09db7a862c 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -2656,6 +2656,10 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf ERR_FAIL_COND_V_MSG(return_type->is_singleton, ERR_BUG, "Method return type is a singleton: '" + p_itype.name + "." + p_imethod.name + "'."); + if (p_imethod.proxy_name == "ClassGetSignal") { + _log("YO: %s\n", p_imethod.name); + } + if (p_itype.api_type == ClassDB::API_CORE) { ERR_FAIL_COND_V_MSG(return_type->api_type == ClassDB::API_EDITOR, ERR_BUG, "Method '" + p_itype.name + "." + p_imethod.name + "' has return type '" + return_type->name + @@ -3389,7 +3393,7 @@ const String BindingsGenerator::_get_generic_type_parameters(const TypeInterface String params = "<"; for (const TypeReference ¶m_type : p_generic_type_parameters) { const TypeInterface *param_itype = _get_type_or_singleton_or_null(param_type); - ERR_FAIL_NULL_V(param_itype, ""); // Parameter type not found + ERR_FAIL_NULL_V_MSG(param_itype, "", "Parameter type '" + param_type.cname + "' was not found."); ERR_FAIL_COND_V_MSG(param_itype->is_singleton, "", "Generic type parameter is a singleton: '" + param_itype->name + "'."); @@ -4371,6 +4375,7 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { INSERT_STRUCT_TYPE(Vector4, Vector4) INSERT_STRUCT_TYPE(Vector4i, Vector4I) INSERT_STRUCT_TYPE(Projection, Projection) + INSERT_STRUCT_TYPE(StructInfo, StructInfo) #undef INSERT_STRUCT_TYPE @@ -4607,6 +4612,8 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { INSERT_ARRAY(PackedVector3Array, godot_packed_vector3_array, Vector3); INSERT_ARRAY(PackedVector4Array, godot_packed_vector4_array, Vector4); + INSERT_ARRAY(Struct, godot_struct, Struct) + #undef INSERT_ARRAY // Array diff --git a/modules/mono/editor/bindings_generator.h b/modules/mono/editor/bindings_generator.h index 556d287af4ef..3dd74aecb208 100644 --- a/modules/mono/editor/bindings_generator.h +++ b/modules/mono/editor/bindings_generator.h @@ -705,7 +705,7 @@ class BindingsGenerator { StringName type_Vector4i = StaticCString::create("Vector4i"); // Object not included as it must be checked for all derived classes - static constexpr int nullable_types_count = 19; + static constexpr int nullable_types_count = 20; StringName nullable_types[nullable_types_count] = { type_String, type_StringName, @@ -714,6 +714,7 @@ class BindingsGenerator { type_Array_generic, type_Dictionary_generic, StaticCString::create(_STR(Array)), + StaticCString::create(_STR(Struct)), StaticCString::create(_STR(Dictionary)), StaticCString::create(_STR(Callable)), StaticCString::create(_STR(Signal)), diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/CustomUnsafe.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/CustomUnsafe.cs index 171cf86edb1d..2493e87c4b80 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/CustomUnsafe.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/CustomUnsafe.cs @@ -151,6 +151,22 @@ public static unsafe ref godot_array AsRef(godot_array* source) public static unsafe ref godot_array AsRef(in godot_array source) => ref *ReadOnlyRefAsPointer(in source); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_struct* AsPointer(ref godot_struct value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_struct* ReadOnlyRefAsPointer(in godot_struct value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_struct AsRef(godot_struct* source) + => ref *source; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_struct AsRef(in godot_struct source) + => ref *ReadOnlyRefAsPointer(in source); + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe godot_dictionary* AsPointer(ref godot_dictionary value) => value.GetUnsafeAddress(); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs index 7e5c01d0f883..7762ca63ee2e 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs @@ -818,6 +818,106 @@ public static unsafe explicit operator godot_array(movable value) } } + // A correctly constructed value needs to call the native default constructor to allocate `_p`. + // Don't pass a C# default constructed `godot_struct` 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.Explicit)] + public ref struct godot_struct + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal readonly unsafe godot_struct* GetUnsafeAddress() + => (godot_struct*)Unsafe.AsPointer(ref Unsafe.AsRef(in _getUnsafeAddressHelper)); + + [FieldOffset(0)] private byte _getUnsafeAddressHelper; + + // Internally, a Struct is just an Array which utilizes ArrayPrivate + [FieldOffset(0)] private unsafe ArrayPrivate* _p; + + [StructLayout(LayoutKind.Sequential)] + private struct ArrayPrivate + { + 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 + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _arrayVector.Size; + } + + public readonly unsafe bool IsReadOnly + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _readOnly != null; + } + } + + [StructLayout(LayoutKind.Sequential)] + private struct VariantVector + { + private IntPtr _writeProxy; + public unsafe godot_variant* _ptr; + + public readonly unsafe int Size + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _ptr != null ? (int)(*((ulong*)_ptr - 1)) : 0; + } + } + + public readonly unsafe godot_variant* Elements + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _p->_arrayVector._ptr; + } + + public readonly unsafe bool IsAllocated + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _p != null; + } + + public readonly unsafe int Size + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + 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) + return; + NativeFuncs.godotsharp_struct_destroy(ref this); + _p = null; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct movable + { + private unsafe ArrayPrivate* _p; + + public static unsafe explicit operator movable(in godot_struct value) + => *(movable*)CustomUnsafe.AsPointer(ref CustomUnsafe.AsRef(value)); + + public static unsafe explicit operator godot_struct(movable value) + => *(godot_struct*)Unsafe.AsPointer(ref value); + + public unsafe ref godot_struct DangerousSelfRef => + ref CustomUnsafe.AsRef((godot_struct*)Unsafe.AsPointer(ref this)); + } + } + // IMPORTANT: // 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 diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs index 15b7ce7c73f4..593dbe7ebb7a 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs @@ -189,6 +189,9 @@ internal static Variant.Type ConvertManagedTypeToVariantType(Type type, out bool if (typeof(Collections.Array) == type) return Variant.Type.Array; + + if (typeof(Collections.Struct) == type) + return Variant.Type.Array; } break; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs index 6a643833f6a5..9d9e88ab1cf2 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs @@ -203,6 +203,8 @@ public static partial void godotsharp_variant_new_dictionary(out godot_variant r public static partial void godotsharp_variant_new_array(out godot_variant r_dest, in godot_array p_arr); + public static partial void godotsharp_variant_new_struct(out godot_variant r_dest, in godot_struct p_arr); + public static partial void godotsharp_variant_new_packed_byte_array(out godot_variant r_dest, in godot_packed_byte_array p_pba); @@ -287,6 +289,8 @@ public static partial void godotsharp_variant_new_packed_color_array(out godot_v public static partial godot_array godotsharp_variant_as_array(in godot_variant p_self); + public static partial godot_struct godotsharp_variant_as_struct(in godot_variant p_self); + public static partial godot_packed_byte_array godotsharp_variant_as_packed_byte_array(in godot_variant p_self); public static partial godot_packed_int32_array godotsharp_variant_as_packed_int32_array(in godot_variant p_self); @@ -336,6 +340,14 @@ public static partial void godotsharp_string_name_new_copy(out godot_string_name public static partial godot_variant* godotsharp_array_ptrw(ref godot_array p_self); + // struct.h + + public static partial void godotsharp_struct_new(out godot_struct r_dest); + + public static partial void godotsharp_struct_new_copy(out godot_struct r_dest, in godot_struct p_src); + + public static partial godot_variant* godotsharp_struct_ptrw(ref godot_struct p_self); + // dictionary.h public static partial void godotsharp_dictionary_new(out godot_dictionary r_dest); @@ -379,6 +391,8 @@ public static partial void godotsharp_dictionary_new_copy(out godot_dictionary r public static partial void godotsharp_array_destroy(ref godot_array p_self); + public static partial void godotsharp_struct_destroy(ref godot_struct p_self); + public static partial void godotsharp_dictionary_destroy(ref godot_dictionary p_self); // Array @@ -425,6 +439,42 @@ public static partial void godotsharp_array_slice(ref godot_array p_self, int p_ public static partial void godotsharp_array_to_string(ref godot_array p_self, out godot_string r_str); + // Struct + + public static partial int godotsharp_struct_add(ref godot_struct p_self, in godot_variant p_item); + + public static partial int godotsharp_struct_add_range(ref godot_struct p_self, in godot_struct p_collection); + + public static partial int godotsharp_struct_binary_search(ref godot_struct p_self, int p_index, int p_count, in godot_variant p_value); + + public static partial void + godotsharp_struct_duplicate(ref godot_struct p_self, godot_bool p_deep, out godot_struct r_dest); + + public static partial void godotsharp_struct_fill(ref godot_struct p_self, in godot_variant p_value); + + public static partial int godotsharp_struct_index_of(ref godot_struct p_self, in godot_variant p_item, int p_index = 0); + + public static partial void godotsharp_struct_insert(ref godot_struct p_self, int p_index, in godot_variant p_item); + + public static partial int godotsharp_struct_last_index_of(ref godot_struct p_self, in godot_variant p_item, int p_index); + + public static partial void godotsharp_struct_make_read_only(ref godot_struct p_self); + + public static partial void godotsharp_struct_max(ref godot_struct p_self, out godot_variant r_value); + + public static partial void godotsharp_struct_min(ref godot_struct p_self, out godot_variant r_value); + + public static partial godot_bool godotsharp_struct_recursive_equal(ref godot_struct p_self, in godot_struct p_other); + + public static partial void godotsharp_struct_remove_at(ref godot_struct p_self, int p_index); + + public static partial Error godotsharp_struct_resize(ref godot_struct p_self, int p_new_size); + + public static partial void godotsharp_struct_slice(ref godot_struct p_self, int p_start, int p_end, + int p_step, godot_bool p_deep, out godot_struct r_dest); + + public static partial void godotsharp_struct_to_string(ref godot_struct p_self, out godot_string r_str); + // Dictionary public static partial godot_bool godotsharp_dictionary_try_get_value(ref godot_dictionary p_self, diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.extended.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.extended.cs index 9f237e4d00cb..e79732b58337 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.extended.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.extended.cs @@ -76,6 +76,18 @@ public static godot_array godotsharp_array_new_copy(in godot_array src) return ret; } + public static godot_struct godotsharp_struct_new() + { + godotsharp_struct_new(out godot_struct ret); + return ret; + } + + public static godot_struct godotsharp_struct_new_copy(in godot_struct src) + { + godotsharp_struct_new_copy(out godot_struct ret, src); + return ret; + } + public static godot_dictionary godotsharp_dictionary_new() { godotsharp_dictionary_new(out godot_dictionary ret); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantUtils.cs index dc151e2c3eee..7e7efc179d89 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantUtils.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantUtils.cs @@ -293,6 +293,20 @@ public static godot_variant CreateFromArray(Collections.Array? from) public static godot_variant CreateFromArray<[MustBeVariant] T>(Array? from) => from != null ? CreateFromArray((godot_array)((Collections.Array)from).NativeValue) : default; + public static godot_variant CreateFromStruct(godot_struct from) + { + NativeFuncs.godotsharp_variant_new_struct(out godot_variant ret, from); + return ret; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static godot_variant CreateFromStruct(Collections.Struct? from) + => from != null ? CreateFromStruct((godot_struct)from.NativeValue) : default; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static godot_variant CreateFromStruct<[MustBeVariant] T>(Struct? from) + => from != null ? CreateFromStruct((godot_struct)((Collections.Struct)from).NativeValue) : default; + public static godot_variant CreateFromDictionary(godot_dictionary from) { NativeFuncs.godotsharp_variant_new_dictionary(out godot_variant ret, from); @@ -557,6 +571,15 @@ public static Collections.Array ConvertToArray(in godot_variant p_var) public static Array ConvertToArray<[MustBeVariant] T>(in godot_variant p_var) => Array.CreateTakingOwnershipOfDisposableValue(ConvertToNativeArray(p_var)); + // TODO: should perhaps convert array to struct if type == Type.Array like + // godot_array ConvertToNativeArray above + public static godot_struct ConvertToNativeStruct(in godot_variant p_var) + => NativeFuncs.godotsharp_variant_as_struct(p_var); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Struct ConvertToStruct<[MustBeVariant] T>(in godot_variant p_var) + => Struct.CreateTakingOwnershipOfDisposableValue(ConvertToNativeStruct(p_var)); + public static godot_dictionary ConvertToNativeDictionary(in godot_variant p_var) => p_var.Type == Variant.Type.Dictionary ? NativeFuncs.godotsharp_dictionary_new_copy(p_var.Dictionary) : diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Struct.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Struct.cs new file mode 100644 index 000000000000..e42333a0e0e8 --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Struct.cs @@ -0,0 +1,1708 @@ +using System; +using System.Collections.Generic; +using System.Collections; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Runtime.CompilerServices; +using Godot.NativeInterop; +using System.Diagnostics; + +#nullable enable + +namespace Godot.Collections +{ + public sealed class StructInfo + { + + } + /// + /// Wrapper around Godot's Struct class, a collection of Variant + /// typed elements allocated in the engine in C++. Useful when + /// interfacing with the engine or exporting data to the editor. + /// Otherwise prefer .NET collections or use + /// built-in C# structs or classes."/>. + /// + [DebuggerTypeProxy(typeof(ArrayDebugView))] + [DebuggerDisplay("Count = {Count}")] +#pragma warning disable CA1710 // Identifiers should have correct suffix + public sealed class Struct : +#pragma warning restore CA1710 + IList, + IReadOnlyList, + ICollection, + IDisposable + { + internal godot_struct.movable NativeValue; + + private WeakReference? _weakReferenceToSelf; + + /// + /// Constructs a new empty . + /// + public Struct() + { + NativeValue = (godot_struct.movable)NativeFuncs.godotsharp_struct_new(); + _weakReferenceToSelf = DisposablesTracker.RegisterDisposable(this); + } + + /// + /// Constructs a new from the given collection's elements. + /// + /// + /// The is . + /// + /// The collection of elements to construct from. + /// A new Godot Struct. + public Struct(IEnumerable collection) : this() + { + if (collection == null) + throw new ArgumentNullException(nameof(collection)); + + foreach (Variant element in collection) + Add(element); + } + + /// + /// Constructs a new from the given objects. + /// + /// + /// The is . + /// + /// The objects to put in the new array. + /// A new Godot Struct. + public Struct(Variant[] array) + { + if (array == null) + throw new ArgumentNullException(nameof(array)); + + NativeValue = (godot_struct.movable)NativeFuncs.godotsharp_struct_new(); + _weakReferenceToSelf = DisposablesTracker.RegisterDisposable(this); + + int length = array.Length; + + Resize(length); + + for (int i = 0; i < length; i++) + this[i] = array[i]; + } + + /// + /// Constructs a new from the given span's elements. + /// + /// + /// The is . + /// + /// A new Godot Struct. + public Struct(Span array) + { + if (array == null) + throw new ArgumentNullException(nameof(array)); + + NativeValue = (godot_struct.movable)NativeFuncs.godotsharp_struct_new(); + _weakReferenceToSelf = DisposablesTracker.RegisterDisposable(this); + + int length = array.Length; + + Resize(length); + + for (int i = 0; i < length; i++) + this[i] = array[i]; + } + + /// + /// Constructs a new from the given span's elements. + /// + /// + /// The is . + /// + /// A new Godot Struct. + public Struct(Span array) + { + if (array == null) + throw new ArgumentNullException(nameof(array)); + + NativeValue = (godot_struct.movable)NativeFuncs.godotsharp_struct_new(); + _weakReferenceToSelf = DisposablesTracker.RegisterDisposable(this); + + int length = array.Length; + + Resize(length); + + for (int i = 0; i < length; i++) + this[i] = array[i]; + } + + /// + /// Constructs a new from the given span's elements. + /// + /// + /// The is . + /// + /// A new Godot Struct. + public Struct(Span array) + { + if (array == null) + throw new ArgumentNullException(nameof(array)); + + NativeValue = (godot_struct.movable)NativeFuncs.godotsharp_struct_new(); + _weakReferenceToSelf = DisposablesTracker.RegisterDisposable(this); + + int length = array.Length; + + Resize(length); + + for (int i = 0; i < length; i++) + this[i] = array[i]; + } + + // We must use ReadOnlySpan instead of Span here as this can accept implicit conversions + // from derived types (e.g.: Node[]). Implicit conversion from Derived[] to Base[] are + // fine as long as the array is not mutated. However, Span does this type checking at + // instantiation, so it's not possible to use it even when not mutating anything. + /// + /// Constructs a new from the given ReadOnlySpan's elements. + /// + /// + /// The is . + /// + /// A new Godot Struct. + public Struct(ReadOnlySpan array) + { + if (array == null) + throw new ArgumentNullException(nameof(array)); + + NativeValue = (godot_struct.movable)NativeFuncs.godotsharp_struct_new(); + _weakReferenceToSelf = DisposablesTracker.RegisterDisposable(this); + + int length = array.Length; + + Resize(length); + + for (int i = 0; i < length; i++) + this[i] = array[i]; + } + + private Struct(godot_struct nativeValueToOwn) + { + NativeValue = (godot_struct.movable)(nativeValueToOwn.IsAllocated ? + nativeValueToOwn : + NativeFuncs.godotsharp_struct_new()); + _weakReferenceToSelf = DisposablesTracker.RegisterDisposable(this); + } + + // Explicit name to make it very clear + internal static Struct CreateTakingOwnershipOfDisposableValue(godot_struct nativeValueToOwn) + => new Struct(nativeValueToOwn); + + ~Struct() + { + Dispose(false); + } + + /// + /// Disposes of this . + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + public void Dispose(bool disposing) + { + // Always dispose `NativeValue` even if disposing is true + NativeValue.DangerousSelfRef.Dispose(); + + if (_weakReferenceToSelf != null) + { + DisposablesTracker.UnregisterDisposable(_weakReferenceToSelf); + } + } + + /// + /// Returns a copy of the . + /// If is , a deep copy if performed: + /// all nested arrays and dictionaries are duplicated and will not be shared with + /// the original array. If , a shallow copy is made and + /// references to the original nested arrays and dictionaries are kept, so that + /// modifying a sub-array or dictionary in the copy will also impact those + /// referenced in the source array. Note that any derived + /// elements will be shallow copied regardless of the + /// setting. + /// + /// If , performs a deep copy. + /// A new Godot Struct. + public Struct Duplicate(bool deep = false) + { + godot_struct newStruct; + var self = (godot_struct)NativeValue; + NativeFuncs.godotsharp_struct_duplicate(ref self, deep.ToGodotBool(), out newStruct); + return CreateTakingOwnershipOfDisposableValue(newStruct); + } + + /// + /// Assigns the given value to all elements in the struct. This can typically be + /// used together with to create a struct with a given + /// size and initialized elements. + /// Note: If is of a reference type ( + /// derived, or , etc.) then the array + /// is filled with the references to the same object, i.e. no duplicates are + /// created. + /// + /// + /// The array is read-only. + /// + /// The value to fill the array with. + public void Fill(Variant value) + { + ThrowIfReadOnly(); + + godot_variant variantValue = (godot_variant)value.NativeVar; + var self = (godot_struct)NativeValue; + NativeFuncs.godotsharp_struct_fill(ref self, variantValue); + } + + /// + /// Returns the maximum value contained in the struct if all elements are of + /// comparable types. If the elements can't be compared, + /// is returned. + /// + /// The maximum value contained in the array. + public Variant Max() + { + godot_variant resVariant; + var self = (godot_struct)NativeValue; + NativeFuncs.godotsharp_struct_max(ref self, out resVariant); + return Variant.CreateTakingOwnershipOfDisposableValue(resVariant); + } + + /// + /// Returns the minimum value contained in the array if all elements are of + /// comparable types. If the elements can't be compared, + /// is returned. + /// + /// The minimum value contained in the array. + public Variant Min() + { + godot_variant resVariant; + var self = (godot_struct)NativeValue; + NativeFuncs.godotsharp_struct_min(ref self, out resVariant); + return Variant.CreateTakingOwnershipOfDisposableValue(resVariant); + } + + /// + /// Compares this against the + /// recursively. Returns if the + /// sizes and contents of the arrays are equal, + /// otherwise. + /// + /// The other array to compare against. + /// + /// if the sizes and contents of the arrays are equal, + /// otherwise. + /// + public bool RecursiveEqual(Struct other) + { + var self = (godot_struct)NativeValue; + var otherVariant = (godot_struct)other.NativeValue; + return NativeFuncs.godotsharp_struct_recursive_equal(ref self, otherVariant).ToBool(); + } + + /// + /// Resizes the array to contain a different number of elements. If the array + /// size is smaller, elements are cleared, if bigger, new elements are + /// . + /// + /// + /// The array is read-only. + /// + /// The new size of the array. + /// if successful, or an error code. + public Error Resize(int newSize) + { + ThrowIfReadOnly(); + + var self = (godot_struct)NativeValue; + return NativeFuncs.godotsharp_struct_resize(ref self, newSize); + } + + /// + /// Creates a shallow copy of a range of elements in the source . + /// + /// + /// is less than 0 or greater than the array's size. + /// + /// The zero-based index at which the range starts. + /// A new array that contains the elements inside the slice range. + public Struct Slice(int start) + { + if (start < 0 || start > Count) + throw new ArgumentOutOfRangeException(nameof(start)); + + return GetSliceRange(start, Count, step: 1, deep: false); + } + + /// + /// Creates a shallow copy of a range of elements in the source . + /// + /// + /// is less than 0 or greater than the array's size. + /// -or- + /// is less than 0 or greater than the array's size. + /// + /// The zero-based index at which the range starts. + /// The length of the range. + /// A new array that contains the elements inside the slice range. + // The Slice method must have this signature to get implicit Range support. + public Struct Slice(int start, int length) + { + if (start < 0 || start > Count) + throw new ArgumentOutOfRangeException(nameof(start)); + + if (length < 0 || length > Count) + throw new ArgumentOutOfRangeException(nameof(start)); + + return GetSliceRange(start, start + length, step: 1, deep: false); + } + + /// + /// Returns the slice of the , from + /// (inclusive) to (exclusive), as a new . + /// The absolute value of and + /// will be clamped to the array size. + /// If either or are negative, they + /// will be relative to the end of the array (i.e. arr.GetSliceRange(0, -2) + /// is a shorthand for arr.GetSliceRange(0, arr.Count - 2)). + /// If specified, is the relative index between source + /// elements. It can be negative, then must be higher than + /// . For example, [0, 1, 2, 3, 4, 5].GetSliceRange(5, 1, -2) + /// returns [5, 3]. + /// If is true, each element will be copied by value + /// rather than by reference. + /// + /// The zero-based index at which the range starts. + /// The zero-based index at which the range ends. + /// The relative index between source elements to take. + /// If , performs a deep copy. + /// A new array that contains the elements inside the slice range. + public Struct GetSliceRange(int start, int end, int step = 1, bool deep = false) + { + godot_struct newStruct; + var self = (godot_struct)NativeValue; + NativeFuncs.godotsharp_struct_slice(ref self, start, end, step, deep.ToGodotBool(), out newStruct); + return CreateTakingOwnershipOfDisposableValue(newStruct); + } + + /// + /// Concatenates two s together, with the + /// being added to the end of the specified in . + /// For example, [1, 2] + [3, 4] results in [1, 2, 3, 4]. + /// + /// The first array. + /// The second array. + /// A new Godot Struct with the contents of both arrays. + public static Struct operator +(Struct left, Struct right) + { + if (left == null) + { + if (right == null) + return new Struct(); + + return right.Duplicate(deep: false); + } + + if (right == null) + return left.Duplicate(deep: false); + + int leftCount = left.Count; + int rightCount = right.Count; + + Struct newStruct = left.Duplicate(deep: false); + newStruct.Resize(leftCount + rightCount); + + for (int i = 0; i < rightCount; i++) + newStruct[i + leftCount] = right[i]; + + return newStruct; + } + + /// + /// Returns the item at the given . + /// + /// + /// The property is assigned and the array is read-only. + /// + /// + /// is less than 0 or greater than the array's size. + /// + /// The item at the given . + public unsafe Variant this[int index] + { + get + { + GetVariantBorrowElementAt(index, out godot_variant borrowElem); + return Variant.CreateCopyingBorrowed(borrowElem); + } + set + { + ThrowIfReadOnly(); + + if (index < 0 || index >= Count) + throw new ArgumentOutOfRangeException(nameof(index)); + + var self = (godot_struct)NativeValue; + godot_variant* ptrw = NativeFuncs.godotsharp_struct_ptrw(ref self); + godot_variant* itemPtr = &ptrw[index]; + (*itemPtr).Dispose(); + *itemPtr = value.CopyNativeVariant(); + } + } + + /// + /// Adds an item to the end of this . + /// This is the same as append or push_back in GDScript. + /// + /// + /// The array is read-only. + /// + /// The item to add. + public void Add(Variant item) + { + ThrowIfReadOnly(); + + godot_variant variantValue = (godot_variant)item.NativeVar; + var self = (godot_struct)NativeValue; + _ = NativeFuncs.godotsharp_struct_add(ref self, variantValue); + } + + /// + /// Adds the elements of the specified collection to the end of this . + /// + /// + /// The array is read-only. + /// + /// + /// The is . + /// + /// Collection of items to add. + public void AddRange<[MustBeVariant] T>(IEnumerable collection) + { + ThrowIfReadOnly(); + + if (collection == null) + throw new ArgumentNullException(nameof(collection), "Value cannot be null."); + + // If the collection is another Godot Struct, we can add the items + // with a single interop call. + if (collection is Struct array) + { + var self = (godot_struct)NativeValue; + var collectionNative = (godot_struct)array.NativeValue; + _ = NativeFuncs.godotsharp_struct_add_range(ref self, collectionNative); + return; + } + /* + if (collection is Struct typedStruct) + { + var self = (godot_struct)NativeValue; + var collectionNative = (godot_struct)typedStruct.NativeValue; + _ = NativeFuncs.godotsharp_struct_add_range(ref self, collectionNative); + return; + } + */ + + // If we can retrieve the count of the collection without enumerating it + // (e.g.: the collections is a List), use it to resize the array once + // instead of growing it as we add items. + if (collection.TryGetNonEnumeratedCount(out int count)) + { + int oldCount = Count; + Resize(Count + count); + + using var enumerator = collection.GetEnumerator(); + + for (int i = 0; i < count; i++) + { + enumerator.MoveNext(); + this[oldCount + i] = Variant.From(enumerator.Current); + } + + return; + } + + foreach (var item in collection) + { + Add(Variant.From(item)); + } + } + + /// + /// Finds the index of an existing value using binary search. + /// If the value is not present in the array, it returns the bitwise + /// complement of the insertion index that maintains sorting order. + /// Note: Calling on an + /// unsorted array results in unexpected behavior. + /// + /// + /// is less than 0. + /// -or- + /// is less than 0. + /// + /// + /// and do not denote + /// a valid range in the . + /// + /// The starting index of the range to search. + /// The length of the range to search. + /// The object to locate. + /// + /// The index of the item in the array, if is found; + /// otherwise, a negative number that is the bitwise complement of the index + /// of the next element that is larger than or, if + /// there is no larger element, the bitwise complement of . + /// + public int BinarySearch(int index, int count, Variant item) + { + if (index < 0) + throw new ArgumentOutOfRangeException(nameof(index), "index cannot be negative."); + if (count < 0) + throw new ArgumentOutOfRangeException(nameof(count), "count cannot be negative."); + if (Count - index < count) + throw new ArgumentException("length is out of bounds or count is greater than the number of elements."); + + if (Count == 0) + { + // Special case for empty array to avoid an interop call. + return -1; + } + + godot_variant variantValue = (godot_variant)item.NativeVar; + var self = (godot_struct)NativeValue; + return NativeFuncs.godotsharp_struct_binary_search(ref self, index, count, variantValue); + } + + /// + /// Finds the index of an existing value using binary search. + /// If the value is not present in the array, it returns the bitwise + /// complement of the insertion index that maintains sorting order. + /// Note: Calling on an unsorted + /// array results in unexpected behavior. + /// + /// The object to locate. + /// + /// The index of the item in the array, if is found; + /// otherwise, a negative number that is the bitwise complement of the index + /// of the next element that is larger than or, if + /// there is no larger element, the bitwise complement of . + /// + public int BinarySearch(Variant item) + { + return BinarySearch(0, Count, item); + } + + /// + /// Returns if the array contains the given value. + /// + /// + /// + /// var arr = new Godot.Collections.Struct { "inside", 7 }; + /// GD.Print(arr.Contains("inside")); // True + /// GD.Print(arr.Contains("outside")); // False + /// GD.Print(arr.Contains(7)); // True + /// GD.Print(arr.Contains("7")); // False + /// + /// + /// The item to look for. + /// Whether or not this array contains the given item. + public bool Contains(Variant item) => IndexOf(item) != -1; + + /// + /// Clears the array. This is the equivalent to using + /// with a size of 0 + /// + /// + /// The array is read-only. + /// + public void Clear() => Resize(0); + + /// + /// Searches the array for a value and returns its index or -1 if not found. + /// + /// The item to search for. + /// The index of the item, or -1 if not found. + public int IndexOf(Variant item) + { + if (Count == 0) + { + // Special case for empty array to avoid an interop call. + return -1; + } + + godot_variant variantValue = (godot_variant)item.NativeVar; + var self = (godot_struct)NativeValue; + return NativeFuncs.godotsharp_struct_index_of(ref self, variantValue); + } + + /// + /// Searches the array for a value and returns its index or -1 if not found. + /// + /// + /// is less than 0 or greater than the array's size. + /// + /// The item to search for. + /// The initial search index to start from. + /// The index of the item, or -1 if not found. + public int IndexOf(Variant item, int index) + { + if (index < 0 || index > Count) + throw new ArgumentOutOfRangeException(nameof(index)); + + if (Count == 0) + { + // Special case for empty array to avoid an interop call. + return -1; + } + + godot_variant variantValue = (godot_variant)item.NativeVar; + var self = (godot_struct)NativeValue; + return NativeFuncs.godotsharp_struct_index_of(ref self, variantValue, index); + } + + /// + /// Searches the array for a value in reverse order and returns its index + /// or -1 if not found. + /// + /// The item to search for. + /// The index of the item, or -1 if not found. + public int LastIndexOf(Variant item) + { + if (Count == 0) + { + // Special case for empty array to avoid an interop call. + return -1; + } + + godot_variant variantValue = (godot_variant)item.NativeVar; + var self = (godot_struct)NativeValue; + return NativeFuncs.godotsharp_struct_last_index_of(ref self, variantValue, Count - 1); + } + + /// + /// Searches the array for a value in reverse order and returns its index + /// or -1 if not found. + /// + /// + /// is less than 0 or greater than the array's size. + /// + /// The item to search for. + /// The initial search index to start from. + /// The index of the item, or -1 if not found. + public int LastIndexOf(Variant item, int index) + { + if (index < 0 || index >= Count) + throw new ArgumentOutOfRangeException(nameof(index)); + + if (Count == 0) + { + // Special case for empty array to avoid an interop call. + return -1; + } + + godot_variant variantValue = (godot_variant)item.NativeVar; + var self = (godot_struct)NativeValue; + return NativeFuncs.godotsharp_struct_last_index_of(ref self, variantValue, index); + } + + /// + /// Inserts a new element at a given position in the array. The position + /// must be valid, or at the end of the array (pos == Count - 1). + /// Existing items will be moved to the right. + /// + /// + /// The array is read-only. + /// + /// + /// is less than 0 or greater than the array's size. + /// + /// The index to insert at. + /// The item to insert. + public void Insert(int index, Variant item) + { + ThrowIfReadOnly(); + + if (index < 0 || index > Count) + throw new ArgumentOutOfRangeException(nameof(index)); + + godot_variant variantValue = (godot_variant)item.NativeVar; + var self = (godot_struct)NativeValue; + NativeFuncs.godotsharp_struct_insert(ref self, index, variantValue); + } + + /// + /// Removes the first occurrence of the specified + /// from this . + /// + /// + /// The array is read-only. + /// + /// The value to remove. + public bool Remove(Variant item) + { + ThrowIfReadOnly(); + + int index = IndexOf(item); + if (index >= 0) + { + RemoveAt(index); + return true; + } + + return false; + } + + /// + /// Removes an element from the array by index. + /// To remove an element by searching for its value, use + /// instead. + /// + /// + /// The array is read-only. + /// + /// + /// is less than 0 or greater than the array's size. + /// + /// The index of the element to remove. + public void RemoveAt(int index) + { + ThrowIfReadOnly(); + + if (index < 0 || index > Count) + throw new ArgumentOutOfRangeException(nameof(index)); + + var self = (godot_struct)NativeValue; + NativeFuncs.godotsharp_struct_remove_at(ref self, index); + } + + // ICollection + + /// + /// Returns the number of elements in this . + /// This is also known as the size or length of the array. + /// + /// The number of elements. + public int Count => NativeValue.DangerousSelfRef.Size; + + bool ICollection.IsSynchronized => false; + + object ICollection.SyncRoot => false; + + /// + /// Returns if the array is read-only. + /// See . + /// + public bool IsReadOnly => NativeValue.DangerousSelfRef.IsReadOnly; + + /// + /// Makes the read-only, i.e. disabled modying of the + /// array's elements. Does not apply to nested content, e.g. content of + /// nested arrays. + /// + public void MakeReadOnly() + { + if (IsReadOnly) + { + // Avoid interop call when the array is already read-only. + return; + } + + var self = (godot_struct)NativeValue; + NativeFuncs.godotsharp_struct_make_read_only(ref self); + } + + /// + /// Copies the elements of this to the given + /// C# array, starting at the given index. + /// + /// + /// The is . + /// + /// + /// is less than 0 or greater than the array's size. + /// + /// + /// The destination array was not long enough. + /// + /// The array to copy to. + /// The index to start at. + public void CopyTo(Variant[] array, int arrayIndex) + { + if (array == null) + throw new ArgumentNullException(nameof(array), "Value cannot be null."); + + if (arrayIndex < 0) + { + throw new ArgumentOutOfRangeException(nameof(arrayIndex), + "Number was less than the array's lower bound in the first dimension."); + } + + int count = Count; + + if (array.Length < (arrayIndex + count)) + { + throw new ArgumentException( + "Destination array was not long enough. Check destIndex and length, and the array's lower bounds."); + } + + unsafe + { + for (int i = 0; i < count; i++) + { + array[arrayIndex] = Variant.CreateCopyingBorrowed(NativeValue.DangerousSelfRef.Elements[i]); + arrayIndex++; + } + } + } + + void ICollection.CopyTo(System.Array copyArray, int index) + { + if (copyArray == null) + throw new ArgumentNullException(nameof(copyArray), "Value cannot be null."); + + if (index < 0) + { + throw new ArgumentOutOfRangeException(nameof(index), + "Number was less than the struct's lower bound in the first dimension."); + } + + int count = Count; + + if (copyArray.Length < (index + count)) + { + throw new ArgumentException( + "Destination struct was not long enough. Check destIndex and length, and the struct's lower bounds."); + } + + unsafe + { + for (int i = 0; i < count; i++) + { + object boxedVariant = Variant.CreateCopyingBorrowed(NativeValue.DangerousSelfRef.Elements[i]); + copyArray.SetValue(boxedVariant, index); + index++; + } + } + } + + // IEnumerable + + /// + /// Gets an enumerator for this . + /// + /// An enumerator. + public IEnumerator GetEnumerator() + { + int count = Count; + + for (int i = 0; i < count; i++) + { + yield return this[i]; + } + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + /// + /// Converts this to a string. + /// + /// A string representation of this struct. + public override string ToString() + { + var self = (godot_struct)NativeValue; + NativeFuncs.godotsharp_struct_to_string(ref self, out godot_string str); + using (str) + return Marshaling.ConvertStringToManaged(str); + } + + /// + /// The variant returned via the parameter is owned by the Array and must not be disposed. + /// + /// + /// is less than 0 or greater than the array's size. + /// + internal void GetVariantBorrowElementAt(int index, out godot_variant elem) + { + if (index < 0 || index >= Count) + throw new ArgumentOutOfRangeException(nameof(index)); + GetVariantBorrowElementAtUnchecked(index, out elem); + } + + /// + /// The variant returned via the parameter is owned by the Array and must not be disposed. + /// + internal unsafe void GetVariantBorrowElementAtUnchecked(int index, out godot_variant elem) + { + elem = NativeValue.DangerousSelfRef.Elements[index]; + } + + private void ThrowIfReadOnly() + { + if (IsReadOnly) + { + throw new InvalidOperationException("Struct instance is read-only."); + } + } + } + + internal interface IGenericGodotStruct + { + public Struct UnderlyingStruct { get; } + } + + /// + /// Typed wrapper around Godot's Struct class, a collection of Variant + /// typed elements allocated in the engine in C++. Useful when + /// interfacing with the engine. Otherwise prefer .NET collections + /// or use built-in structs or classes."/>. + /// + /// The type of the array. + [DebuggerTypeProxy(typeof(ArrayDebugView<>))] + [DebuggerDisplay("Count = {Count}")] + [SuppressMessage("ReSharper", "RedundantExtendsListEntry")] + [SuppressMessage("Naming", "CA1710", MessageId = "Identifiers should have correct suffix")] + public sealed class Struct<[MustBeVariant] T> : + IList, + IReadOnlyList, + ICollection, + IEnumerable, + IGenericGodotStruct + { + private static godot_variant ToVariantFunc(in Array godotStruct) => + VariantUtils.CreateFromArray(godotStruct); + + private static Array FromVariantFunc(in godot_variant variant) => + VariantUtils.ConvertToArray(variant); + + static unsafe Struct() + { + VariantUtils.GenericConversion>.ToVariantCb = &ToVariantFunc; + VariantUtils.GenericConversion>.FromVariantCb = &FromVariantFunc; + } + + private readonly Struct _underlyingStruct; + + Struct IGenericGodotStruct.UnderlyingStruct => _underlyingStruct; + + internal ref godot_struct.movable NativeValue + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => ref _underlyingStruct.NativeValue; + } + + /// + /// Constructs a new empty . + /// + /// A new Godot Array. + public Struct() + { + _underlyingStruct = new Struct(); + } + + /// + /// Constructs a new from the given collection's elements. + /// + /// + /// The is . + /// + /// The collection of elements to construct from. + /// A new Godot Array. + public Struct(IEnumerable collection) + { + if (collection == null) + throw new ArgumentNullException(nameof(collection)); + + _underlyingStruct = new Struct(); + + foreach (T element in collection) + Add(element); + } + + /// + /// Constructs a new from the given items. + /// + /// + /// The is . + /// + /// The items to put in the new array. + /// A new Godot Array. + public Struct(T[] array) + { + if (array == null) + throw new ArgumentNullException(nameof(array)); + + _underlyingStruct = new Struct(); + + foreach (T element in array) + Add(element); + } + + /// + /// Constructs a typed from an untyped . + /// + /// + /// The is . + /// + /// The untyped array to construct from. + /// A new Godot Array. + public Struct(Struct fromStruct) + { + if (fromStruct == null) + throw new ArgumentNullException(nameof(fromStruct)); + + _underlyingStruct = fromStruct; + } + + // Explicit name to make it very clear + internal static Struct CreateTakingOwnershipOfDisposableValue(godot_struct nativeValueToOwn) + => new Struct(Struct.CreateTakingOwnershipOfDisposableValue(nativeValueToOwn)); + + /// + /// Converts this typed to an untyped . + /// + /// The typed array to convert. + /// A new Godot Array, or if was null. + [return: NotNullIfNotNull("from")] + public static explicit operator Struct?(Struct? from) + { + return from?._underlyingStruct; + } + + /// + /// Duplicates this . + /// + /// If , performs a deep copy. + /// A new Godot Array. + public Struct Duplicate(bool deep = false) + { + return new Struct(_underlyingStruct.Duplicate(deep)); + } + + /// + /// Assigns the given value to all elements in the array. This can typically be + /// used together with to create an array with a given + /// size and initialized elements. + /// Note: If is of a reference type ( + /// derived, or , etc.) then the array + /// is filled with the references to the same object, i.e. no duplicates are + /// created. + /// + /// + /// The array is read-only. + /// + /// The value to fill the array with. + public void Fill(T value) + { + ThrowIfReadOnly(); + + godot_variant variantValue = VariantUtils.CreateFrom(value); + var self = (godot_struct)_underlyingStruct.NativeValue; + NativeFuncs.godotsharp_struct_fill(ref self, variantValue); + } + + /// + /// Returns the maximum value contained in the array if all elements are of + /// comparable types. If the elements can't be compared, + /// is returned. + /// + /// The maximum value contained in the array. + public T Max() + { + godot_variant resVariant; + var self = (godot_struct)_underlyingStruct.NativeValue; + NativeFuncs.godotsharp_struct_max(ref self, out resVariant); + return VariantUtils.ConvertTo(resVariant); + } + + /// + /// Returns the minimum value contained in the array if all elements are of + /// comparable types. If the elements can't be compared, + /// is returned. + /// + /// The minimum value contained in the array. + public T Min() + { + godot_variant resVariant; + var self = (godot_struct)_underlyingStruct.NativeValue; + NativeFuncs.godotsharp_struct_min(ref self, out resVariant); + return VariantUtils.ConvertTo(resVariant); + } + + /// + /// Compares this against the + /// recursively. Returns if the + /// sizes and contents of the arrays are equal, + /// otherwise. + /// + /// The other array to compare against. + /// + /// if the sizes and contents of the arrays are equal, + /// otherwise. + /// + public bool RecursiveEqual(Struct other) + { + return _underlyingStruct.RecursiveEqual(other._underlyingStruct); + } + + /// + /// Resizes this to the given size. + /// + /// + /// The array is read-only. + /// + /// The new size of the array. + /// if successful, or an error code. + public Error Resize(int newSize) + { + return _underlyingStruct.Resize(newSize); + } + + /// + /// Creates a shallow copy of a range of elements in the source . + /// + /// + /// is less than 0 or greater than the array's size. + /// + /// The zero-based index at which the range starts. + /// A new array that contains the elements inside the slice range. + public Struct Slice(int start) + { + return GetSliceRange(start, Count, step: 1, deep: false); + } + + /// + /// Creates a shallow copy of a range of elements in the source . + /// + /// + /// is less than 0 or greater than the array's size. + /// -or- + /// is less than 0 or greater than the array's size. + /// + /// The zero-based index at which the range starts. + /// The length of the range. + /// A new array that contains the elements inside the slice range. + // The Slice method must have this signature to get implicit Range support. + public Struct Slice(int start, int length) + { + return GetSliceRange(start, start + length, step: 1, deep: false); + } + + /// + /// Returns the slice of the , from + /// (inclusive) to (exclusive), as a new . + /// The absolute value of and + /// will be clamped to the array size. + /// If either or are negative, they + /// will be relative to the end of the array (i.e. arr.GetSliceRange(0, -2) + /// is a shorthand for arr.GetSliceRange(0, arr.Count - 2)). + /// If specified, is the relative index between source + /// elements. It can be negative, then must be higher than + /// . For example, [0, 1, 2, 3, 4, 5].GetSliceRange(5, 1, -2) + /// returns [5, 3]. + /// If is true, each element will be copied by value + /// rather than by reference. + /// + /// The zero-based index at which the range starts. + /// The zero-based index at which the range ends. + /// The relative index between source elements to take. + /// If , performs a deep copy. + /// A new array that contains the elements inside the slice range. + public Struct GetSliceRange(int start, int end, int step = 1, bool deep = false) + { + return new Struct(_underlyingStruct.GetSliceRange(start, end, step, deep)); + } + + /// + /// Concatenates two s together, with the + /// being added to the end of the specified in . + /// For example, [1, 2] + [3, 4] results in [1, 2, 3, 4]. + /// + /// The first array. + /// The second array. + /// A new Godot Array with the contents of both arrays. + public static Struct operator +(Struct left, Struct right) + { + if (left == null) + { + if (right == null) + return new Struct(); + + return right.Duplicate(deep: false); + } + + if (right == null) + return left.Duplicate(deep: false); + + return new Struct(left._underlyingStruct + right._underlyingStruct); + } + + // IList + + /// + /// Returns the item at the given . + /// + /// + /// The property is assigned and the array is read-only. + /// + /// + /// is less than 0 or greater than the array's size. + /// + /// The item at the given . + public unsafe T this[int index] + { + get + { + _underlyingStruct.GetVariantBorrowElementAt(index, out godot_variant borrowElem); + return VariantUtils.ConvertTo(borrowElem); + } + set + { + ThrowIfReadOnly(); + + if (index < 0 || index >= Count) + throw new ArgumentOutOfRangeException(nameof(index)); + + var self = (godot_struct)_underlyingStruct.NativeValue; + godot_variant* ptrw = NativeFuncs.godotsharp_struct_ptrw(ref self); + godot_variant* itemPtr = &ptrw[index]; + (*itemPtr).Dispose(); + *itemPtr = VariantUtils.CreateFrom(value); + } + } + + /// + /// Searches the array for a value and returns its index or -1 if not found. + /// + /// The item to search for. + /// The index of the item, or -1 if not found. + public int IndexOf(T item) + { + if (Count == 0) + { + // Special case for empty array to avoid an interop call. + return -1; + } + + using var variantValue = VariantUtils.CreateFrom(item); + var self = (godot_struct)_underlyingStruct.NativeValue; + return NativeFuncs.godotsharp_struct_index_of(ref self, variantValue); + } + + /// + /// Searches the array for a value and returns its index or -1 if not found. + /// + /// + /// is less than 0 or greater than the array's size. + /// + /// The item to search for. + /// The initial search index to start from. + /// The index of the item, or -1 if not found. + public int IndexOf(T item, int index) + { + if (index < 0 || index > Count) + throw new ArgumentOutOfRangeException(nameof(index)); + + if (Count == 0) + { + // Special case for empty array to avoid an interop call. + return -1; + } + + godot_variant variantValue = VariantUtils.CreateFrom(item); + var self = (godot_struct)_underlyingStruct.NativeValue; + return NativeFuncs.godotsharp_struct_index_of(ref self, variantValue, index); + } + + /// + /// Searches the array for a value in reverse order and returns its index + /// or -1 if not found. + /// + /// The item to search for. + /// The index of the item, or -1 if not found. + public int LastIndexOf(Variant item) + { + if (Count == 0) + { + // Special case for empty array to avoid an interop call. + return -1; + } + + godot_variant variantValue = VariantUtils.CreateFrom(item); + var self = (godot_struct)_underlyingStruct.NativeValue; + return NativeFuncs.godotsharp_struct_last_index_of(ref self, variantValue, Count - 1); + } + + /// + /// Searches the array for a value in reverse order and returns its index + /// or -1 if not found. + /// + /// + /// is less than 0 or greater than the array's size. + /// + /// The item to search for. + /// The initial search index to start from. + /// The index of the item, or -1 if not found. + public int LastIndexOf(Variant item, int index) + { + if (index < 0 || index >= Count) + throw new ArgumentOutOfRangeException(nameof(index)); + + if (Count == 0) + { + // Special case for empty array to avoid an interop call. + return -1; + } + + godot_variant variantValue = VariantUtils.CreateFrom(item); + var self = (godot_struct)_underlyingStruct.NativeValue; + return NativeFuncs.godotsharp_struct_last_index_of(ref self, variantValue, index); + } + + /// + /// Inserts a new element at a given position in the array. The position + /// must be valid, or at the end of the array (pos == Count - 1). + /// Existing items will be moved to the right. + /// + /// + /// The array is read-only. + /// + /// + /// is less than 0 or greater than the array's size. + /// + /// The index to insert at. + /// The item to insert. + public void Insert(int index, T item) + { + ThrowIfReadOnly(); + + if (index < 0 || index > Count) + throw new ArgumentOutOfRangeException(nameof(index)); + + using var variantValue = VariantUtils.CreateFrom(item); + var self = (godot_struct)_underlyingStruct.NativeValue; + NativeFuncs.godotsharp_struct_insert(ref self, index, variantValue); + } + + /// + /// Removes an element from the array by index. + /// To remove an element by searching for its value, use + /// instead. + /// + /// + /// The array is read-only. + /// + /// + /// is less than 0 or greater than the array's size. + /// + /// The index of the element to remove. + public void RemoveAt(int index) + { + _underlyingStruct.RemoveAt(index); + } + + // ICollection + + /// + /// Returns the number of elements in this . + /// This is also known as the size or length of the array. + /// + /// The number of elements. + public int Count => _underlyingStruct.Count; + + /// + /// Returns if the array is read-only. + /// See . + /// + public bool IsReadOnly => _underlyingStruct.IsReadOnly; + + /// + /// Makes the read-only, i.e. disabled modying of the + /// array's elements. Does not apply to nested content, e.g. content of + /// nested arrays. + /// + public void MakeReadOnly() + { + _underlyingStruct.MakeReadOnly(); + } + + /// + /// Adds an item to the end of this . + /// This is the same as append or push_back in GDScript. + /// + /// + /// The array is read-only. + /// + /// The item to add. + public void Add(T item) + { + ThrowIfReadOnly(); + + using var variantValue = VariantUtils.CreateFrom(item); + var self = (godot_struct)_underlyingStruct.NativeValue; + _ = NativeFuncs.godotsharp_struct_add(ref self, variantValue); + } + + /// + /// Adds the elements of the specified collection to the end of this . + /// + /// + /// The array is read-only. + /// + /// + /// The is . + /// + /// Collection of items to add. + public void AddRange(IEnumerable collection) + { + ThrowIfReadOnly(); + + if (collection == null) + throw new ArgumentNullException(nameof(collection), "Value cannot be null."); + + // If the collection is another Godot Array, we can add the items + // with a single interop call. + if (collection is Struct array) + { + var self = (godot_struct)_underlyingStruct.NativeValue; + var collectionNative = (godot_struct)array.NativeValue; + _ = NativeFuncs.godotsharp_struct_add_range(ref self, collectionNative); + return; + } + /* + if (collection is Array typedArray) + { + var self = (godot_array)_underlyingStruct.NativeValue; + var collectionNative = (godot_array)typedArray._underlyingArray.NativeValue; + _ = NativeFuncs.godotsharp_array_add_range(ref self, collectionNative); + return; + } + */ + + // If we can retrieve the count of the collection without enumerating it + // (e.g.: the collections is a List), use it to resize the array once + // instead of growing it as we add items. + if (collection.TryGetNonEnumeratedCount(out int count)) + { + int oldCount = Count; + Resize(Count + count); + + using var enumerator = collection.GetEnumerator(); + + for (int i = 0; i < count; i++) + { + enumerator.MoveNext(); + this[oldCount + i] = enumerator.Current; + } + + return; + } + + foreach (var item in collection) + { + Add(item); + } + } + + /// + /// Finds the index of an existing value using binary search. + /// If the value is not present in the array, it returns the bitwise + /// complement of the insertion index that maintains sorting order. + /// Note: Calling on an unsorted + /// array results in unexpected behavior. + /// + /// + /// is less than 0. + /// -or- + /// is less than 0. + /// + /// + /// and do not denote + /// a valid range in the . + /// + /// The starting index of the range to search. + /// The length of the range to search. + /// The object to locate. + /// + /// The index of the item in the array, if is found; + /// otherwise, a negative number that is the bitwise complement of the index + /// of the next element that is larger than or, if + /// there is no larger element, the bitwise complement of . + /// + public int BinarySearch(int index, int count, T item) + { + if (index < 0) + throw new ArgumentOutOfRangeException(nameof(index), "index cannot be negative."); + if (count < 0) + throw new ArgumentOutOfRangeException(nameof(count), "count cannot be negative."); + if (Count - index < count) + throw new ArgumentException("length is out of bounds or count is greater than the number of elements."); + + if (Count == 0) + { + // Special case for empty array to avoid an interop call. + return -1; + } + + using var variantValue = VariantUtils.CreateFrom(item); + var self = (godot_struct)_underlyingStruct.NativeValue; + return NativeFuncs.godotsharp_struct_binary_search(ref self, index, count, variantValue); + } + + /// + /// Finds the index of an existing value using binary search. + /// If the value is not present in the array, it returns the bitwise + /// complement of the insertion index that maintains sorting order. + /// Note: Calling on an unsorted + /// array results in unexpected behavior. + /// + /// The object to locate. + /// + /// The index of the item in the array, if is found; + /// otherwise, a negative number that is the bitwise complement of the index + /// of the next element that is larger than or, if + /// there is no larger element, the bitwise complement of . + /// + public int BinarySearch(T item) + { + return BinarySearch(0, Count, item); + } + + /// + /// Clears the array. This is the equivalent to using + /// with a size of 0 + /// + /// + /// The array is read-only. + /// + public void Clear() + { + _underlyingStruct.Clear(); + } + + /// + /// Returns if the array contains the given value. + /// + /// + /// + /// var arr = new Godot.Collections.Array<string> { "inside", "7" }; + /// GD.Print(arr.Contains("inside")); // True + /// GD.Print(arr.Contains("outside")); // False + /// GD.Print(arr.Contains(7)); // False + /// GD.Print(arr.Contains("7")); // True + /// + /// + /// The item to look for. + /// Whether or not this array contains the given item. + public bool Contains(T item) => IndexOf(item) != -1; + + /// + /// Copies the elements of this to the given + /// C# array, starting at the given index. + /// + /// + /// The is . + /// + /// + /// is less than 0 or greater than the array's size. + /// + /// + /// The destination array was not long enough. + /// + /// The C# array to copy to. + /// The index to start at. + public void CopyTo(T[] array, int arrayIndex) + { + if (array == null) + throw new ArgumentNullException(nameof(array), "Value cannot be null."); + + if (arrayIndex < 0) + { + throw new ArgumentOutOfRangeException(nameof(arrayIndex), + "Number was less than the array's lower bound in the first dimension."); + } + + int count = Count; + + if (array.Length < (arrayIndex + count)) + { + throw new ArgumentException( + "Destination array was not long enough. Check destIndex and length, and the array's lower bounds."); + } + + for (int i = 0; i < count; i++) + { + array[arrayIndex] = this[i]; + arrayIndex++; + } + } + + /// + /// Removes the first occurrence of the specified + /// from this . + /// + /// + /// The array is read-only. + /// + /// The value to remove. + /// A indicating success or failure. + public bool Remove(T item) + { + ThrowIfReadOnly(); + + int index = IndexOf(item); + if (index >= 0) + { + RemoveAt(index); + return true; + } + + return false; + } + + // IEnumerable + + /// + /// Gets an enumerator for this . + /// + /// An enumerator. + public IEnumerator GetEnumerator() + { + int count = _underlyingStruct.Count; + + for (int i = 0; i < count; i++) + { + yield return this[i]; + } + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + /// + /// Converts this to a string. + /// + /// A string representation of this array. + public override string ToString() => _underlyingStruct.ToString(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Struct from) => Variant.CreateFrom(from); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Struct(Variant from) => from.AsGodotStruct(); + + private void ThrowIfReadOnly() + { + if (IsReadOnly) + { + throw new InvalidOperationException("Array instance is read-only."); + } + } + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Variant.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Variant.cs index b40f524859db..a3407b7acc1b 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Variant.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Variant.cs @@ -353,6 +353,10 @@ public NodePath[] AsSystemArrayOfNodePath() => public Rid[] AsSystemArrayOfRid() => VariantUtils.ConvertToSystemArrayOfRid((godot_variant)NativeVar); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Collections.Struct AsGodotStruct<[MustBeVariant] T>() => + VariantUtils.ConvertToStruct((godot_variant)NativeVar); + [MethodImpl(MethodImplOptions.AggressiveInlining)] public GodotObject AsGodotObject() => VariantUtils.ConvertToGodotObject((godot_variant)NativeVar); @@ -666,6 +670,10 @@ public static Variant CreateFrom<[MustBeVariant] TKey, [MustBeVariant] TValue>(C public static Variant CreateFrom<[MustBeVariant] T>(Collections.Array from) => CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromArray(from)); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom<[MustBeVariant] T>(Collections.Struct from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromStruct(from)); + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Variant CreateFrom(Span from) => from; @@ -693,6 +701,9 @@ public static Variant CreateFrom<[MustBeVariant] T>(Collections.Array from) = [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Variant CreateFrom(Collections.Array from) => from; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Collections.Struct from) => from; + // Implicit conversion operators [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -950,4 +961,8 @@ public static implicit operator Variant(Collections.Dictionary from) => [MethodImpl(MethodImplOptions.AggressiveInlining)] public static implicit operator Variant(Collections.Array from) => CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromArray(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Collections.Struct from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromStruct(from)); } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj index 3a3134d1604c..a312588d6b0a 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj +++ b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj @@ -122,6 +122,7 @@ + diff --git a/modules/mono/glue/runtime_interop.cpp b/modules/mono/glue/runtime_interop.cpp index 73c10eba830a..1024c6bc23b6 100644 --- a/modules/mono/glue/runtime_interop.cpp +++ b/modules/mono/glue/runtime_interop.cpp @@ -624,6 +624,10 @@ void godotsharp_variant_new_array(godot_variant *r_dest, const Array *p_arr) { memnew_placement(r_dest, Variant(*p_arr)); } +void godotsharp_variant_new_struct(godot_variant *r_dest, const Array *p_arr) { + memnew_placement(r_dest, Variant(*p_arr)); +} + void godotsharp_variant_new_packed_byte_array(godot_variant *r_dest, const PackedByteArray *p_pba) { memnew_placement(r_dest, Variant(*p_pba)); } @@ -844,6 +848,17 @@ godot_array godotsharp_variant_as_array(const Variant *p_self) { return raw_dest; } +godot_struct godotsharp_variant_as_struct(const Variant *p_self) { + godot_struct raw_dest; + /* + StructInfo *dest = (StructInfo *)&raw_dest; + memnew_placement(dest, StructInfo(p_self->operator StructInfo())); + */ + Array *dest = (Array *)&raw_dest; + memnew_placement(dest, Array(p_self->operator Array())); + return raw_dest; +} + godot_packed_array godotsharp_variant_as_packed_byte_array(const Variant *p_self) { godot_packed_array raw_dest; PackedByteArray *dest = (PackedByteArray *)&raw_dest; @@ -951,6 +966,20 @@ godot_variant *godotsharp_array_ptrw(godot_array *p_self) { return reinterpret_cast(&reinterpret_cast(p_self)->operator[](0)); } +// struct.h + +void godotsharp_struct_new(Array *r_dest) { + memnew_placement(r_dest, Array); +} + +void godotsharp_struct_new_copy(Array *r_dest, const Array *p_src) { + memnew_placement(r_dest, Array(*p_src)); +} + +godot_variant *godotsharp_struct_ptrw(godot_struct *p_self) { + return reinterpret_cast(&reinterpret_cast(p_self)->operator[](0)); +} + // dictionary.h void godotsharp_dictionary_new(Dictionary *r_dest) { @@ -1031,6 +1060,10 @@ void godotsharp_array_destroy(Array *p_self) { p_self->~Array(); } +void godotsharp_struct_destroy(Array *p_self) { + p_self->~Array(); +} + void godotsharp_dictionary_destroy(Dictionary *p_self) { p_self->~Dictionary(); } @@ -1142,6 +1175,72 @@ void godotsharp_array_to_string(const Array *p_self, String *r_str) { *r_str = Variant(*p_self).operator String(); } +// Struct + +int32_t godotsharp_struct_add(Array *p_self, const Variant *p_item) { + return godotsharp_array_add(p_self, p_item); +} + +int32_t godotsharp_struct_add_range(Array *p_self, const Array *p_collection) { + return godotsharp_array_add_range(p_self, p_collection); +} + +int32_t godotsharp_struct_binary_search(const Array *p_self, int32_t p_index, int32_t p_length, const Variant *p_value) { + return godotsharp_array_binary_search(p_self, p_index, p_length, p_value); +} + +void godotsharp_struct_duplicate(const Array *p_self, bool p_deep, Array *r_dest) { + godotsharp_array_duplicate(p_self, p_deep, r_dest); +} + +void godotsharp_struct_fill(Array *p_self, const Variant *p_value) { + godotsharp_array_fill(p_self, p_value); +} + +int32_t godotsharp_struct_index_of(const Array *p_self, const Variant *p_item, int32_t p_index = 0) { + return godotsharp_array_index_of(p_self, p_item, p_index); +} + +void godotsharp_struct_insert(Array *p_self, int32_t p_index, const Variant *p_item) { + godotsharp_array_insert(p_self, p_index, p_item); +} + +int32_t godotsharp_struct_last_index_of(const Array *p_self, const Variant *p_item, int32_t p_index) { + return godotsharp_array_last_index_of(p_self, p_item, p_index); +} + +void godotsharp_struct_make_read_only(Array *p_self) { + godotsharp_array_make_read_only(p_self); +} + +void godotsharp_struct_max(const Array *p_self, Variant *r_value) { + godotsharp_array_max(p_self, r_value); +} + +void godotsharp_struct_min(const Array *p_self, Variant *r_value) { + godotsharp_array_min(p_self, r_value); +} + +bool godotsharp_struct_recursive_equal(const Array *p_self, const Array *p_other) { + return godotsharp_array_recursive_equal(p_self, p_other); +} + +void godotsharp_struct_remove_at(Array *p_self, int32_t p_index) { + godotsharp_array_remove_at(p_self, p_index); +} + +int32_t godotsharp_struct_resize(Array *p_self, int32_t p_new_size) { + return godotsharp_array_resize(p_self, p_new_size); +} + +void godotsharp_struct_slice(Array *p_self, int32_t p_start, int32_t p_end, int32_t p_step, bool p_deep, Array *r_dest) { + godotsharp_array_slice(p_self, p_start, p_end, p_step, p_deep, r_dest); +} + +void godotsharp_struct_to_string(const Array *p_self, String *r_str) { + godotsharp_array_to_string(p_self, r_str); +} + // Dictionary bool godotsharp_dictionary_try_get_value(const Dictionary *p_self, const Variant *p_key, Variant *r_value) { @@ -1502,6 +1601,7 @@ static const void *unmanaged_callbacks[]{ (void *)godotsharp_variant_new_aabb, (void *)godotsharp_variant_new_dictionary, (void *)godotsharp_variant_new_array, + (void *)godotsharp_variant_new_struct, (void *)godotsharp_variant_new_packed_byte_array, (void *)godotsharp_variant_new_packed_int32_array, (void *)godotsharp_variant_new_packed_int64_array, @@ -1539,6 +1639,7 @@ static const void *unmanaged_callbacks[]{ (void *)godotsharp_variant_as_signal, (void *)godotsharp_variant_as_dictionary, (void *)godotsharp_variant_as_array, + (void *)godotsharp_variant_as_struct, (void *)godotsharp_variant_as_packed_byte_array, (void *)godotsharp_variant_as_packed_int32_array, (void *)godotsharp_variant_as_packed_int64_array, @@ -1556,6 +1657,9 @@ static const void *unmanaged_callbacks[]{ (void *)godotsharp_array_new, (void *)godotsharp_array_new_copy, (void *)godotsharp_array_ptrw, + (void *)godotsharp_struct_new, + (void *)godotsharp_struct_new_copy, + (void *)godotsharp_struct_ptrw, (void *)godotsharp_dictionary_new, (void *)godotsharp_dictionary_new_copy, (void *)godotsharp_packed_byte_array_destroy, @@ -1575,6 +1679,7 @@ static const void *unmanaged_callbacks[]{ (void *)godotsharp_signal_destroy, (void *)godotsharp_callable_destroy, (void *)godotsharp_array_destroy, + (void *)godotsharp_struct_destroy, (void *)godotsharp_dictionary_destroy, (void *)godotsharp_array_add, (void *)godotsharp_array_add_range, @@ -1596,6 +1701,22 @@ static const void *unmanaged_callbacks[]{ (void *)godotsharp_array_slice, (void *)godotsharp_array_sort, (void *)godotsharp_array_to_string, + (void *)godotsharp_struct_add, + (void *)godotsharp_struct_add_range, + (void *)godotsharp_struct_binary_search, + (void *)godotsharp_struct_duplicate, + (void *)godotsharp_struct_fill, + (void *)godotsharp_struct_index_of, + (void *)godotsharp_struct_insert, + (void *)godotsharp_struct_last_index_of, + (void *)godotsharp_struct_make_read_only, + (void *)godotsharp_struct_max, + (void *)godotsharp_struct_min, + (void *)godotsharp_struct_recursive_equal, + (void *)godotsharp_struct_remove_at, + (void *)godotsharp_struct_resize, + (void *)godotsharp_struct_slice, + (void *)godotsharp_struct_to_string, (void *)godotsharp_dictionary_try_get_value, (void *)godotsharp_dictionary_set_value, (void *)godotsharp_dictionary_keys, diff --git a/modules/mono/interop_types.h b/modules/mono/interop_types.h index 811c4441406e..12cc11cf86e7 100644 --- a/modules/mono/interop_types.h +++ b/modules/mono/interop_types.h @@ -54,6 +54,12 @@ typedef struct { uint8_t _dont_touch_that[GODOT_ARRAY_SIZE]; } godot_array; +#define GODOT_STRUCT_SIZE sizeof(void *) + +typedef struct { + uint8_t _dont_touch_that[GODOT_STRUCT_SIZE]; +} godot_struct; + #define GODOT_DICTIONARY_SIZE sizeof(void *) typedef struct {