using System; using System.Collections.Generic; using System.Collections; using Godot.NativeInterop; using System.Diagnostics.CodeAnalysis; namespace Godot.Collections { /// /// Wrapper around Godot's Dictionary class, a dictionary of Variant /// typed elements allocated in the engine in C++. Useful when /// interfacing with the engine. /// public sealed class Dictionary : IDictionary, IDisposable { public godot_dictionary NativeValue; /// /// Constructs a new empty . /// public Dictionary() { NativeValue = NativeFuncs.godotsharp_dictionary_new(); } /// /// Constructs a new from the given dictionary's elements. /// /// The dictionary to construct from. /// A new Godot Dictionary. public Dictionary(IDictionary dictionary) : this() { if (dictionary == null) throw new NullReferenceException($"Parameter '{nameof(dictionary)} cannot be null.'"); foreach (DictionaryEntry entry in dictionary) Add(entry.Key, entry.Value); } private Dictionary(godot_dictionary nativeValueToOwn) { NativeValue = nativeValueToOwn; } // Explicit name to make it very clear internal static Dictionary CreateTakingOwnershipOfDisposableValue(godot_dictionary nativeValueToOwn) => new Dictionary(nativeValueToOwn); ~Dictionary() { 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.Dispose(); } /// /// Duplicates this . /// /// If , performs a deep copy. /// A new Godot Dictionary. public Dictionary Duplicate(bool deep = false) { godot_dictionary newDictionary; NativeFuncs.godotsharp_dictionary_duplicate(ref NativeValue, deep.ToGodotBool(), out newDictionary); return CreateTakingOwnershipOfDisposableValue(newDictionary); } // IDictionary /// /// Gets the collection of keys in this . /// public ICollection Keys { get { godot_array keysArray; NativeFuncs.godotsharp_dictionary_keys(ref NativeValue, out keysArray); return Array.CreateTakingOwnershipOfDisposableValue(keysArray); } } /// /// Gets the collection of elements in this . /// public ICollection Values { get { godot_array valuesArray; NativeFuncs.godotsharp_dictionary_values(ref NativeValue, out valuesArray); return Array.CreateTakingOwnershipOfDisposableValue(valuesArray); } } private (Array keys, Array values, int count) GetKeyValuePairs() { godot_array keysArray; NativeFuncs.godotsharp_dictionary_keys(ref NativeValue, out keysArray); var keys = Array.CreateTakingOwnershipOfDisposableValue(keysArray); godot_array valuesArray; NativeFuncs.godotsharp_dictionary_keys(ref NativeValue, out valuesArray); var values = Array.CreateTakingOwnershipOfDisposableValue(valuesArray); int count = NativeFuncs.godotsharp_dictionary_count(ref NativeValue); return (keys, values, count); } bool IDictionary.IsFixedSize => false; bool IDictionary.IsReadOnly => false; /// /// Returns the object at the given . /// /// The object at the given . public unsafe object this[object key] { get { using godot_variant variantKey = Marshaling.mono_object_to_variant(key); if (NativeFuncs.godotsharp_dictionary_try_get_value(ref NativeValue, &variantKey, out godot_variant value).ToBool()) { using (value) return Marshaling.variant_to_mono_object(&value); } else { throw new KeyNotFoundException(); } } set { using godot_variant variantKey = Marshaling.mono_object_to_variant(key); using godot_variant variantValue = Marshaling.mono_object_to_variant(value); NativeFuncs.godotsharp_dictionary_set_value(ref NativeValue, &variantKey, &variantValue); } } /// /// Adds an object at key /// to this . /// /// The key at which to add the object. /// The object to add. public unsafe void Add(object key, object value) { using godot_variant variantKey = Marshaling.mono_object_to_variant(key); if (NativeFuncs.godotsharp_dictionary_contains_key(ref NativeValue, &variantKey).ToBool()) throw new ArgumentException("An element with the same key already exists", nameof(key)); using godot_variant variantValue = Marshaling.mono_object_to_variant(value); NativeFuncs.godotsharp_dictionary_add(ref NativeValue, &variantKey, &variantValue); } /// /// Erases all items from this . /// public void Clear() => NativeFuncs.godotsharp_dictionary_clear(ref NativeValue); /// /// Checks if this contains the given key. /// /// The key to look for. /// Whether or not this dictionary contains the given key. public unsafe bool Contains(object key) { using godot_variant variantKey = Marshaling.mono_object_to_variant(key); return NativeFuncs.godotsharp_dictionary_contains_key(ref NativeValue, &variantKey).ToBool(); } /// /// Gets an enumerator for this . /// /// An enumerator. public IDictionaryEnumerator GetEnumerator() => new DictionaryEnumerator(this); /// /// Removes an element from this by key. /// /// The key of the element to remove. public unsafe void Remove(object key) { using godot_variant variantKey = Marshaling.mono_object_to_variant(key); NativeFuncs.godotsharp_dictionary_remove_key(ref NativeValue, &variantKey); } // ICollection object ICollection.SyncRoot => this; bool ICollection.IsSynchronized => false; /// /// Returns the number of elements in this . /// This is also known as the size or length of the dictionary. /// /// The number of elements. public int Count => NativeFuncs.godotsharp_dictionary_count(ref NativeValue); /// /// Copies the elements of this to the given /// untyped C# array, starting at the given index. /// /// The array to copy to. /// The index to start at. public void CopyTo(System.Array array, int index) { if (array == null) throw new ArgumentNullException(nameof(array), "Value cannot be null."); if (index < 0) throw new ArgumentOutOfRangeException(nameof(index), "Number was less than the array's lower bound in the first dimension."); var (keys, values, count) = GetKeyValuePairs(); if (array.Length < (index + 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.SetValue(new DictionaryEntry(keys[i], values[i]), index); index++; } } // IEnumerable IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); private class DictionaryEnumerator : IDictionaryEnumerator { private readonly Dictionary dictionary; private readonly int count; private int index = -1; private bool dirty = true; private DictionaryEntry entry; public DictionaryEnumerator(Dictionary dictionary) { this.dictionary = dictionary; count = dictionary.Count; } public object Current => Entry; public DictionaryEntry Entry { get { if (dirty) { UpdateEntry(); } return entry; } } private unsafe void UpdateEntry() { dirty = false; NativeFuncs.godotsharp_dictionary_key_value_pair_at(ref dictionary.NativeValue, index, out godot_variant key, out godot_variant value); using (key) using (value) { entry = new DictionaryEntry(Marshaling.variant_to_mono_object(&key), Marshaling.variant_to_mono_object(&value)); } } public object Key => Entry.Key; public object Value => Entry.Value; public bool MoveNext() { index++; dirty = true; return index < count; } public void Reset() { index = -1; dirty = true; } } /// /// Converts this to a string. /// /// A string representation of this dictionary. public override unsafe string ToString() { using godot_string str = default; NativeFuncs.godotsharp_dictionary_to_string(ref NativeValue, &str); return Marshaling.mono_string_from_godot(str); } } internal interface IGenericGodotDictionary { Dictionary UnderlyingDictionary { get; } Type TypeOfKeys { get; } Type TypeOfValues { get; } } // TODO: Now we should be able to avoid boxing /// /// Typed wrapper around Godot's Dictionary class, a dictionary of Variant /// typed elements allocated in the engine in C++. Useful when /// interfacing with the engine. Otherwise prefer .NET collections /// such as . /// /// The type of the dictionary's keys. /// The type of the dictionary's values. public sealed class Dictionary : IDictionary, IGenericGodotDictionary { private readonly Dictionary _underlyingDict; // ReSharper disable StaticMemberInGenericType // Warning is about unique static fields being created for each generic type combination: // https://www.jetbrains.com/help/resharper/StaticMemberInGenericType.html // In our case this is exactly what we want. private static readonly Type TypeOfKeys = typeof(TKey); private static readonly Type TypeOfValues = typeof(TValue); // ReSharper restore StaticMemberInGenericType Dictionary IGenericGodotDictionary.UnderlyingDictionary => _underlyingDict; Type IGenericGodotDictionary.TypeOfKeys => TypeOfKeys; Type IGenericGodotDictionary.TypeOfValues => TypeOfValues; /// /// Constructs a new empty . /// public Dictionary() { _underlyingDict = new Dictionary(); } /// /// Constructs a new from the given dictionary's elements. /// /// The dictionary to construct from. /// A new Godot Dictionary. public Dictionary(IDictionary dictionary) { _underlyingDict = new Dictionary(); if (dictionary == null) throw new NullReferenceException($"Parameter '{nameof(dictionary)} cannot be null.'"); foreach (KeyValuePair entry in dictionary) Add(entry.Key, entry.Value); } /// /// Constructs a new from the given dictionary's elements. /// /// The dictionary to construct from. /// A new Godot Dictionary. public Dictionary(Dictionary dictionary) { _underlyingDict = dictionary; } // Explicit name to make it very clear internal static Dictionary CreateTakingOwnershipOfDisposableValue( godot_dictionary nativeValueToOwn) => new Dictionary(Dictionary.CreateTakingOwnershipOfDisposableValue(nativeValueToOwn)); /// /// Converts this typed to an untyped . /// /// The typed dictionary to convert. public static explicit operator Dictionary(Dictionary from) { return from._underlyingDict; } /// /// Duplicates this . /// /// If , performs a deep copy. /// A new Godot Dictionary. public Dictionary Duplicate(bool deep = false) { return new Dictionary(_underlyingDict.Duplicate(deep)); } // IDictionary /// /// Returns the value at the given . /// /// The value at the given . public TValue this[TKey key] { get { unsafe { using godot_variant variantKey = Marshaling.mono_object_to_variant(key); if (NativeFuncs.godotsharp_dictionary_try_get_value(ref _underlyingDict.NativeValue, &variantKey, out godot_variant value).ToBool()) { using (value) return (TValue)Marshaling.variant_to_mono_object_of_type(&value, TypeOfValues); } else { throw new KeyNotFoundException(); } } } set => _underlyingDict[key] = value; } /// /// Gets the collection of keys in this . /// public ICollection Keys { get { godot_array keyArray; NativeFuncs.godotsharp_dictionary_keys(ref _underlyingDict.NativeValue, out keyArray); return Array.CreateTakingOwnershipOfDisposableValue(keyArray); } } /// /// Gets the collection of elements in this . /// public ICollection Values { get { godot_array valuesArray; NativeFuncs.godotsharp_dictionary_values(ref _underlyingDict.NativeValue, out valuesArray); return Array.CreateTakingOwnershipOfDisposableValue(valuesArray); } } private unsafe KeyValuePair GetKeyValuePair(int index) { NativeFuncs.godotsharp_dictionary_key_value_pair_at(ref _underlyingDict.NativeValue, index, out godot_variant key, out godot_variant value); using (key) using (value) { return new KeyValuePair((TKey)Marshaling.variant_to_mono_object(&key), (TValue)Marshaling.variant_to_mono_object(&value)); } } /// /// Adds an object at key /// to this . /// /// The key at which to add the object. /// The object to add. public void Add(TKey key, TValue value) { _underlyingDict.Add(key, value); } /// /// Checks if this contains the given key. /// /// The key to look for. /// Whether or not this dictionary contains the given key. public bool ContainsKey(TKey key) { return _underlyingDict.Contains(key); } /// /// Removes an element from this by key. /// /// The key of the element to remove. public unsafe bool Remove(TKey key) { using godot_variant variantKey = Marshaling.mono_object_to_variant(key); return NativeFuncs.godotsharp_dictionary_remove_key(ref _underlyingDict.NativeValue, &variantKey).ToBool(); } /// /// Gets the object at the given . /// /// The key of the element to get. /// The value at the given . /// If an object was found for the given . public unsafe bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) { using godot_variant variantKey = Marshaling.mono_object_to_variant(key); bool found = NativeFuncs.godotsharp_dictionary_try_get_value(ref _underlyingDict.NativeValue, &variantKey, out godot_variant retValue).ToBool(); using (retValue) { value = found ? (TValue)Marshaling.variant_to_mono_object_of_type(&retValue, TypeOfValues) : default; } return found; } // ICollection> /// /// Returns the number of elements in this . /// This is also known as the size or length of the dictionary. /// /// The number of elements. public int Count => _underlyingDict.Count; bool ICollection>.IsReadOnly => false; void ICollection>.Add(KeyValuePair item) { _underlyingDict.Add(item.Key, item.Value); } /// /// Erases all the items from this . /// public void Clear() { _underlyingDict.Clear(); } unsafe bool ICollection>.Contains(KeyValuePair item) { using godot_variant variantKey = Marshaling.mono_object_to_variant(item.Key); bool found = NativeFuncs.godotsharp_dictionary_try_get_value(ref _underlyingDict.NativeValue, &variantKey, out godot_variant retValue).ToBool(); using (retValue) { if (!found) return false; return NativeFuncs.godotsharp_variant_equals(&variantKey, &retValue).ToBool(); } } /// /// Copies the elements of this to the given /// untyped C# array, starting at the given index. /// /// The array to copy to. /// The index to start at. public void CopyTo(KeyValuePair[] 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] = GetKeyValuePair(i); arrayIndex++; } } unsafe bool ICollection>.Remove(KeyValuePair item) { using godot_variant variantKey = Marshaling.mono_object_to_variant(item.Key); bool found = NativeFuncs.godotsharp_dictionary_try_get_value(ref _underlyingDict.NativeValue, &variantKey, out godot_variant retValue).ToBool(); using (retValue) { if (!found) return false; if (NativeFuncs.godotsharp_variant_equals(&variantKey, &retValue).ToBool()) { return NativeFuncs.godotsharp_dictionary_remove_key( ref _underlyingDict.NativeValue, &variantKey).ToBool(); } return false; } } // IEnumerable> /// /// Gets an enumerator for this . /// /// An enumerator. public IEnumerator> GetEnumerator() { for (int i = 0; i < Count; i++) { yield return GetKeyValuePair(i); } } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); /// /// Converts this to a string. /// /// A string representation of this dictionary. public override string ToString() => _underlyingDict.ToString(); } }