using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Collections.Contracts; using Misaki.HighPerformance.LowLevel.Utilities; using System.Collections; using System.Runtime.CompilerServices; namespace Misaki.HighPerformance.LowLevel.Collections; public unsafe struct UnsafeHashMap : IUnsafeHashCollection> where TKey : unmanaged, IEquatable where TValue : unmanaged { public struct Enumerator : IEnumerator> { internal HashMapHelper.Enumerator _enumerator; public KeyValuePair Current => _enumerator.GetCurrent(); object IEnumerator.Current => Current; public Enumerator(HashMapHelper* data) { _enumerator = new HashMapHelper.Enumerator(data); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool MoveNext() { return _enumerator.MoveNext(); } public void Reset() { _enumerator.Reset(); } public void Dispose() { } } private HashMapHelper _helper; public readonly int Count => _helper.Count; public readonly int Capacity => _helper.Capacity; public readonly bool IsCreated => _helper.IsCreated; /// /// Gets and sets values by key. /// /// Getting a key that is not present will throw. Setting a key that is not already present will add the key. /// The key to look up. /// The value associated with the key. /// For getting, thrown if the key was not present. public TValue this[TKey key] { get { if (!_helper.TryGetValue(key, out var result)) { throw new ArgumentException($"Key: {key} is not present."); } return result; } set { var idx = _helper.Find(key); if (-1 != idx) { UnsafeUtility.WriteArrayElement(_helper.Buffer, idx, value); return; } TryAdd(key, value); } } public Enumerator GetEnumerator() { return new((HashMapHelper*)UnsafeUtility.AddressOf(ref this)); } IEnumerator> IEnumerable>.GetEnumerator() { return GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } /// /// Invalid constructor, use or instead. /// public UnsafeHashMap() : this(0, Allocator.Invalid) { } public UnsafeHashMap(int capacity, AllocationHandle handle, AllocationOption allocationOption = AllocationOption.None) { _helper = new HashMapHelper(capacity, sizeof(TValue), (int)AlignOf(), HashMapHelper.MINIMAL_CAPACITY, handle, allocationOption); } public UnsafeHashMap(int capacity, Allocator allocator, AllocationOption allocationOption = AllocationOption.None) : this(capacity, AllocationManager.GetAllocationHandle(allocator), allocationOption) { } /// /// Adds a new key-value pair. /// /// If the key is already present, this method returns false without modifying the hash map. /// The key to add. /// The value to add. /// True if the key-value pair was added. [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool TryAdd(scoped in TKey key, TValue item) { var idx = _helper.TryAdd(key); if (idx != -1) { UnsafeUtility.WriteArrayElement(_helper.Buffer, idx, item); return true; } return false; } /// /// Adds a new key-value pair. /// /// If the key is already present, this method throws without modifying the hash map. /// The key to add. /// The value to add. /// Thrown if the key was already present. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Add(scoped in TKey key, TValue item) { var result = TryAdd(key, item); if (!result) { throw new ArgumentException($"An item with the same key has already been added: {key}"); } } /// /// Removes a particular key and its value. /// /// The value to remove. /// True if the value was present. [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Remove(scoped in TKey key) { return -1 != _helper.TryRemove(key); } /// /// Returns the value associated with a key. /// /// The key to look up. /// Outputs the value associated with the key. Outputs default if the key was not present. /// True if the key was present. [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool TryGetValue(scoped in TKey key, out TValue item) { return _helper.TryGetValue(key, out item); } /// /// Retrieves the value associated with the specified key, or returns a default value if the key is not found. /// /// The key whose value to retrieve. /// The value to return if the specified key does not exist. If not specified, the default value for the type is used. /// The value associated with the specified key if the key is found; otherwise, the specified default value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public TValue GetValueOrDefault(scoped in TKey key, TValue defaultValue = default) { if (_helper.TryGetValue(key, out var value)) { return value; } return defaultValue; } /// /// Returns a reference to the value associated with the specified key, and a boolean indicating whether the key exists in the hash map. /// /// The key whose value to retrieve. /// Outputs true if the key exists in the hash map; otherwise, false. /// A reference to the value associated with the specified key. [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref TValue GetValueRef(scoped in TKey key, out bool exists) { return ref _helper.GetValueRef(key, out exists); } /// /// Returns a reference to the value associated with the specified key if it exists; otherwise, adds a new key with a default value and returns a reference to that value. The method also outputs a boolean indicating whether the key already existed in the hash map. /// /// The key whose value to retrieve or add. /// Outputs true if the key already existed in the hash map; otherwise, false. /// A reference to the value associated with the specified key. [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref TValue GetValueRefOrAddDefault(scoped in TKey key, out bool exists) { return ref _helper.GetValueRefOrAddDefault(key, out exists); } /// /// Returns true if a given key is present in this hash map. /// /// The key to look up. /// True if the key was present. [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool ContainsKey(scoped in TKey key) { return -1 != _helper.Find(key); } /// /// Sets the capacity to match what it would be if it had been originally initialized with all its entries. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void TrimExcess() { _helper.TrimExcess(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Resize(int newSize, AllocationOption option = AllocationOption.None) { _helper.Resize(newSize); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Clear() { _helper.Clear(); } /// /// Retrieves an array of keys from the hash map. /// /// An array containing the keys stored in the hash map. [MethodImpl(MethodImplOptions.AggressiveInlining)] public UnsafeArray GetKeyArray(Allocator allocator) { return _helper.GetKeyArray(allocator); } /// /// Retrieves an array of values from the underlying hash map. /// /// An UnsafeArray containing the values stored in the hash map. [MethodImpl(MethodImplOptions.AggressiveInlining)] public UnsafeArray GetValueArray(Allocator allocator) { return _helper.GetValueArray(allocator); } /// /// Retrieves an array of key-value pairs from the hash map. The keys are of type TKey and the values are of type /// TValue. /// /// Returns an UnsafeArray containing KeyValuePair objects. [MethodImpl(MethodImplOptions.AggressiveInlining)] public UnsafeArray> GetKeyValueArrays(Allocator allocator) { return _helper.GetKeyValueArrays(allocator); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly void* GetUnsafePtr() { return _helper.Buffer; } public void Dispose() { _helper.Dispose(); } }