Added UnsafeMultiHashMap

This commit is contained in:
2026-03-08 15:38:00 +09:00
parent 37d548085e
commit 21e755a56e
40 changed files with 619 additions and 156 deletions

View File

@@ -1,6 +1,7 @@
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Utilities;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using System.Runtime.CompilerServices;
@@ -214,6 +215,40 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int AllocateEntry(in TKey key)
{
int idx;
if (_allocatedIndex >= _capacity && _firstFreeIndex < 0)
{
var newCap = CalcCapacityCeilPow2(_capacity + (1 << _log2MinGrowth));
Resize(newCap);
}
idx = _firstFreeIndex;
if (idx >= 0)
{
_firstFreeIndex = _next[idx];
}
else
{
idx = _allocatedIndex++;
}
CheckIndexOutOfBounds(idx);
UnsafeUtility.WriteArrayElement(_keys, idx, key);
var bucket = GetBucket(key);
_next[idx] = _buckets[bucket];
_buckets[bucket] = idx;
_count++;
return idx;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void AllocateBuffer(int totalSize, int keyOffset, int nextOffset, int bucketOffset, AllocationOption allocationOption)
{
@@ -254,7 +289,7 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
{
for (var idx = oldBuckets[i]; idx != -1; idx = oldNext[idx])
{
var newIdx = TryAdd(oldKeys[idx]);
var newIdx = Add(oldKeys[idx]);
MemCpy(_buffer + _sizeOfTValue * newIdx, oldBuffer + _sizeOfTValue * idx, (nuint)_sizeOfTValue);
}
}
@@ -323,45 +358,19 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
{
ThrowIfNotCreated();
var k = key;
if (Find(in key) != -1)
{
return -1;
}
// Allocate an entry from the free list
int idx;
int* next;
return AllocateEntry(key);
}
if (_allocatedIndex >= _capacity && _firstFreeIndex < 0)
{
var newCap = CalcCapacityCeilPow2(_capacity + (1 << _log2MinGrowth));
Resize(newCap);
}
public int Add(in TKey key)
{
ThrowIfNotCreated();
idx = _firstFreeIndex;
if (idx >= 0)
{
_firstFreeIndex = _next[idx];
}
else
{
idx = _allocatedIndex++;
}
CheckIndexOutOfBounds(idx);
UnsafeUtility.WriteArrayElement(_keys, idx, key);
var bucket = GetBucket(key);
// Add the index to the hash-map
next = _next;
next[idx] = _buckets[bucket];
_buckets[bucket] = idx;
_count++;
return idx;
return AllocateEntry(key);
}
public int TryRemove(in TKey key)
@@ -416,6 +425,50 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
return 0 != removed ? removed : -1;
}
public int RemoveAll(in TKey key)
{
ThrowIfNotCreated();
if (_capacity == 0)
{
return 0;
}
var removed = 0;
var bucket = GetBucket(key);
var prevEntry = -1;
var entryIdx = _buckets[bucket];
while (entryIdx >= 0 && entryIdx < _capacity)
{
if (UnsafeUtility.ReadArrayElement<TKey>(_keys, entryIdx).Equals(key))
{
removed++;
var nextIdx = _next[entryIdx];
if (prevEntry < 0)
{
_buckets[bucket] = nextIdx;
}
else
{
_next[prevEntry] = nextIdx;
}
_next[entryIdx] = _firstFreeIndex;
_firstFreeIndex = entryIdx;
entryIdx = nextIdx;
continue;
}
prevEntry = entryIdx;
entryIdx = _next[entryIdx];
}
_count -= removed;
return removed;
}
public bool TryGetValue<TValue>(in TKey key, out TValue item)
where TValue : unmanaged
{
@@ -433,6 +486,43 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
return false;
}
public int FindNext(int entryIdx, in TKey key)
{
ThrowIfNotCreated();
if ((uint)entryIdx >= (uint)_capacity)
{
return -1;
}
var nextIndex = _next[entryIdx];
while ((uint)nextIndex < (uint)_capacity)
{
if (UnsafeUtility.ReadArrayElement<TKey>(_keys, nextIndex).Equals(key))
{
return nextIndex;
}
nextIndex = _next[nextIndex];
}
return -1;
}
public int CountValuesForKey(in TKey key)
{
ThrowIfNotCreated();
var count = 0;
for (var idx = Find(key); idx != -1; idx = FindNext(idx, key))
{
count++;
}
return count;
}
[UnscopedRef]
public ref TValue GetValueRef<TValue>(in TKey key, out bool exists)
where TValue : unmanaged
{
@@ -449,6 +539,7 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
return ref Unsafe.NullRef<TValue>();
}
[UnscopedRef]
public ref TValue GetValueRefOrAddDefault<TValue>(in TKey key, out bool exists)
where TValue : unmanaged
{
@@ -489,35 +580,7 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
return ref UnsafeUtility.ReadArrayElementRef<TValue>(_buffer, idx);
}
int* next;
if (_allocatedIndex >= _capacity && _firstFreeIndex < 0)
{
var newCap = CalcCapacityCeilPow2(_capacity + (1 << _log2MinGrowth));
Resize(newCap);
}
idx = _firstFreeIndex;
if (idx >= 0)
{
_firstFreeIndex = _next[idx];
}
else
{
idx = _allocatedIndex++;
}
CheckIndexOutOfBounds(idx);
UnsafeUtility.WriteArrayElement(_keys, idx, key);
bucket = GetBucket(hash);
// Add the index to the hash-map
next = _next;
next[idx] = _buckets[bucket];
_buckets[bucket] = idx;
_count++;
idx = AllocateEntry(key);
UnsafeUtility.WriteArrayElement(_buffer, idx, default(TValue));

View File

@@ -138,7 +138,7 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
}
/// <summary>
/// Invalid constructor, use <see cref="UnsafeArray(int, Allocator, AllocationOption)"/> or <see cref="UnsafeArray(int, ref AllocationHandle, AllocationOption)"/> instead.
/// Invalid constructor, use <see cref="UnsafeArray(int, Allocator, AllocationOption)"/> or <see cref="UnsafeArray(int, AllocationHandle, AllocationOption)"/> instead.
/// </summary>
public UnsafeArray()
: this(0, Allocator.Invalid)

View File

@@ -2,6 +2,7 @@ using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
using Misaki.HighPerformance.LowLevel.Utilities;
using System.Collections;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
namespace Misaki.HighPerformance.LowLevel.Collections;
@@ -91,7 +92,7 @@ public unsafe struct UnsafeHashMap<TKey, TValue> : IUnsafeHashCollection<KeyValu
}
/// <summary>
/// Invalid constructor, use <see cref="UnsafeHashMap(int, Allocator, AllocationOption)"/> or <see cref="UnsafeHashMap(int, ref AllocationHandle, AllocationOption)"/> instead.
/// Invalid constructor, use <see cref="UnsafeHashMap(int, Allocator, AllocationOption)"/> or <see cref="UnsafeHashMap(int, AllocationHandle, AllocationOption)"/> instead.
/// </summary>
public UnsafeHashMap()
: this(0, Allocator.Invalid)
@@ -180,11 +181,13 @@ public unsafe struct UnsafeHashMap<TKey, TValue> : IUnsafeHashCollection<KeyValu
return defaultValue;
}
[UnscopedRef]
public ref TValue GetValueRef(in TKey key, out bool exists)
{
return ref _helper.GetValueRef<TValue>(key, out exists);
}
[UnscopedRef]
public ref TValue GetValueRefOrAddDefault(in TKey key, out bool exists)
{
return ref _helper.GetValueRefOrAddDefault<TValue>(key, out exists);

View File

@@ -64,7 +64,7 @@ public unsafe struct UnsafeHashSet<T> : IUnsafeHashCollection<T>, IEnumerable<T>
}
/// <summary>
/// Invalid constructor. Use <see cref="UnsafeHashSet(int, Allocator, AllocationOption)"/> or <see cref="UnsafeHashSet(int, ref AllocationHandle, AllocationOption)"/> instead."/>
/// Invalid constructor. Use <see cref="UnsafeHashSet(int, Allocator, AllocationOption)"/> or <see cref="UnsafeHashSet(int, AllocationHandle, AllocationOption)"/> instead."/>
/// </summary>
public UnsafeHashSet()
: this(0, Allocator.Invalid)

View File

@@ -176,7 +176,7 @@ public unsafe struct UnsafeList<T> : IUnsafeCollection<T>
}
/// <summary>
/// Invalid constructor, use <see cref="UnsafeList(int, Allocator, AllocationOption)"/> or <see cref="UnsafeList(int, ref AllocationHandle, AllocationOption)"/> instead.
/// Invalid constructor, use <see cref="UnsafeList(int, Allocator, AllocationOption)"/> or <see cref="UnsafeList(int, AllocationHandle, AllocationOption)"/> instead.
/// </summary>
public UnsafeList()
: this(0, Allocator.Invalid)

View File

@@ -0,0 +1,271 @@
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 UnsafeMultiHashMap<TKey, TValue> : IUnsafeHashCollection<KeyValuePair<TKey, TValue>>
where TKey : unmanaged, IEquatable<TKey>
where TValue : unmanaged
{
public struct Enumerator : IEnumerator<KeyValuePair<TKey, TValue>>
{
internal HashMapHelper<TKey>.Enumerator _enumerator;
public readonly KeyValuePair<TKey, TValue> Current => _enumerator.GetCurrent<TValue>();
readonly object IEnumerator.Current => Current;
public Enumerator(HashMapHelper<TKey>* data)
{
_enumerator = new HashMapHelper<TKey>.Enumerator(data);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext()
{
return _enumerator.MoveNext();
}
public void Reset()
{
_enumerator.Reset();
}
public readonly void Dispose()
{
}
}
public struct Iterator
{
internal TKey _key;
internal int _entryIndex;
internal Iterator(in TKey key, int entryIndex)
{
_key = key;
_entryIndex = entryIndex;
}
}
public struct ValueEnumerable
{
private readonly HashMapHelper<TKey>* _data;
private readonly TKey _key;
internal ValueEnumerable(HashMapHelper<TKey>* data, in TKey key)
{
_data = data;
_key = key;
}
public readonly ValueEnumerator GetEnumerator()
{
return new(_data, _key);
}
}
public struct ValueEnumerator : IEnumerator<TValue>
{
private readonly HashMapHelper<TKey>* _data;
private readonly TKey _key;
private int _entryIndex;
private bool _started;
public readonly TValue Current => UnsafeUtility.ReadArrayElement<TValue>(_data->Buffer, _entryIndex);
readonly object IEnumerator.Current => Current;
internal ValueEnumerator(HashMapHelper<TKey>* data, in TKey key)
{
_data = data;
_key = key;
_entryIndex = -1;
_started = false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext()
{
if (!_started)
{
_entryIndex = _data->Find(_key);
_started = true;
return _entryIndex != -1;
}
if (_entryIndex == -1)
{
return false;
}
_entryIndex = _data->FindNext(_entryIndex, _key);
return _entryIndex != -1;
}
public void Reset()
{
_entryIndex = -1;
_started = false;
}
public readonly void Dispose()
{
}
}
private HashMapHelper<TKey> _helper;
public readonly int Count => _helper.Count;
public readonly int Capacity => _helper.Capacity;
public readonly bool IsCreated => _helper.IsCreated;
public Enumerator GetEnumerator()
{
return new((HashMapHelper<TKey>*)UnsafeUtility.AddressOf(ref this));
}
IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator()
{
return GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public UnsafeMultiHashMap()
: this(0, Allocator.Invalid)
{
}
public UnsafeMultiHashMap(int capacity, AllocationHandle handle, AllocationOption allocationOption = AllocationOption.None)
{
_helper = new HashMapHelper<TKey>(capacity, sizeof(TValue), (int)AlignOf<TValue>(), HashMapHelper<TKey>.MINIMAL_CAPACITY, handle, allocationOption);
}
public UnsafeMultiHashMap(int capacity, Allocator allocator, AllocationOption allocationOption = AllocationOption.None)
: this(capacity, AllocationManager.GetAllocationHandle(allocator), allocationOption)
{
}
public void Add(in TKey key, TValue item)
{
var idx = _helper.Add(key);
UnsafeUtility.WriteArrayElement(_helper.Buffer, idx, item);
}
public bool Remove(in TKey key)
{
return _helper.RemoveAll(key) != 0;
}
public bool TryGetFirstValue(in TKey key, out TValue item, out Iterator iterator)
{
var entryIndex = _helper.Find(key);
if (entryIndex == -1)
{
item = default;
iterator = new(default, -1);
return false;
}
item = UnsafeUtility.ReadArrayElement<TValue>(_helper.Buffer, entryIndex);
iterator = new(key, entryIndex);
return true;
}
public bool TryGetNextValue(out TValue item, ref Iterator iterator)
{
if (iterator._entryIndex == -1)
{
item = default;
return false;
}
var entryIndex = _helper.FindNext(iterator._entryIndex, iterator._key);
if (entryIndex == -1)
{
item = default;
iterator._entryIndex = -1;
return false;
}
iterator._entryIndex = entryIndex;
item = UnsafeUtility.ReadArrayElement<TValue>(_helper.Buffer, entryIndex);
return true;
}
public bool TryGetValue(in TKey key, out TValue item)
{
return _helper.TryGetValue(key, out item);
}
public TValue GetValueOrDefault(in TKey key, TValue defaultValue = default)
{
if (_helper.TryGetValue<TValue>(key, out var value))
{
return value;
}
return defaultValue;
}
public ValueEnumerable GetValuesForKey(in TKey key)
{
return new((HashMapHelper<TKey>*)UnsafeUtility.AddressOf(ref this), key);
}
public int CountValuesForKey(in TKey key)
{
return _helper.CountValuesForKey(key);
}
public bool ContainsKey(in TKey key)
{
return _helper.Find(key) != -1;
}
public void TrimExcess()
{
_helper.TrimExcess();
}
public void Resize(int newSize, AllocationOption option = AllocationOption.None)
{
_helper.Resize(newSize);
}
public void Clear()
{
_helper.Clear();
}
public UnsafeArray<TKey> GetKeyArray(Allocator allocator)
{
return _helper.GetKeyArray(allocator);
}
public UnsafeArray<TValue> GetValueArray(Allocator allocator)
{
return _helper.GetValueArray<TValue>(allocator);
}
public UnsafeArray<KeyValuePair<TKey, TValue>> GetKeyValueArrays(Allocator allocator)
{
return _helper.GetKeyValueArrays<TValue>(allocator);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void* GetUnsafePtr()
{
return _helper.Buffer;
}
public void Dispose()
{
_helper.Dispose();
}
}

View File

@@ -78,7 +78,7 @@ public unsafe struct UnsafeQueue<T> : IUnsafeCollection<T>
}
/// <summary>
/// Invalid constructor. Use <see cref="UnsafeQueue(int, Allocator, AllocationOption)"/> or <see cref="UnsafeQueue(int, ref AllocationHandle, AllocationOption)"/> instead."/>
/// Invalid constructor. Use <see cref="UnsafeQueue(int, Allocator, AllocationOption)"/> or <see cref="UnsafeQueue(int, AllocationHandle, AllocationOption)"/> instead."/>
/// </summary>
public UnsafeQueue()
: this(0, Allocator.Invalid)

View File

@@ -102,7 +102,7 @@ public unsafe struct UnsafeSlotMap<T> : IUnsafeCollection<T>
}
/// <summary>
/// Invalid constructor. Use <see cref="UnsafeSlotMap(int, Allocator, AllocationOption)"/> or <see cref="UnsafeSlotMap(int, ref AllocationHandle, AllocationOption)"/> instead."/>
/// Invalid constructor. Use <see cref="UnsafeSlotMap(int, Allocator, AllocationOption)"/> or <see cref="UnsafeSlotMap(int, AllocationHandle, AllocationOption)"/> instead."/>
/// </summary>
public UnsafeSlotMap()
: this(0, Allocator.Invalid)

View File

@@ -97,7 +97,7 @@ public unsafe struct UnsafeStack<T> : IUnsafeCollection<T>
}
/// <summary>
/// Invalid constructor, use <see cref="UnsafeStack(int, Allocator, AllocationOption)"/> or <see cref="UnsafeStack(int, ref AllocationHandle, AllocationOption)"/> instead.
/// Invalid constructor, use <see cref="UnsafeStack(int, Allocator, AllocationOption)"/> or <see cref="UnsafeStack(int, AllocationHandle, AllocationOption)"/> instead.
/// </summary>
public UnsafeStack()
: this(0, Allocator.Invalid)

View File

@@ -7,12 +7,13 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Authors>Misaki</Authors>
<AssemblyVersion>1.4.0</AssemblyVersion>
<AssemblyVersion>1.4.1</AssemblyVersion>
<Version>$(AssemblyVersion)</Version>
<PackageProjectUrl>https://git.personalnas.com/Misaki/Misaki.HighPerformance.git</PackageProjectUrl>
<RepositoryUrl>https://git.personalnas.com/Misaki/Misaki.HighPerformance.git</RepositoryUrl>
<IncludeBuildOutput>false</IncludeBuildOutput>
<ContentTargetFolders>contentFiles</ContentTargetFolders>
<PackageType>Dependency</PackageType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
@@ -27,22 +28,13 @@
<ItemGroup>
<ProjectReference Include="..\Misaki.HighPerformance\Misaki.HighPerformance.csproj" />
</ItemGroup>
<ItemGroup>
<Content Include="contentFiles\cs\any\**\*.cs">
<Pack>true</Pack>
<PackagePath>contentFiles\cs\any\Misaki.HighPerformance.LowLevel\</PackagePath>
<PackageCopyToOutput>false</PackageCopyToOutput>
<BuildAction>Compile</BuildAction>
</Content>
</ItemGroup>
<ItemGroup>
<None Update="contentFiles\cs\any\Collections\FixedString.tt">
<None Update="Collections\FixedString.tt">
<Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>FixedString.gen.cs</LastGenOutput>
</None>
<None Update="contentFiles\cs\any\Collections\FixedText.tt">
<None Update="Collections\FixedText.tt">
<Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>FixedText.gen.cs</LastGenOutput>
</None>
@@ -53,12 +45,12 @@
</ItemGroup>
<ItemGroup>
<Compile Update="contentFiles\cs\any\Collections\FixedString.gen.cs">
<Compile Update="Collections\FixedString.gen.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>FixedString.tt</DependentUpon>
</Compile>
<Compile Update="contentFiles\cs\any\Collections\FixedText.gen.cs">
<Compile Update="Collections\FixedText.gen.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>FixedText.tt</DependentUpon>