diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..85fd5ce
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,2 @@
+dotnet_sort_system_directives_first = false
+max_line_length = 400
\ No newline at end of file
diff --git a/Misaki.HighPerformance.Math/MathUtilities.cs b/Misaki.HighPerformance.Math/MathUtilities.cs
new file mode 100644
index 0000000..90e96ce
--- /dev/null
+++ b/Misaki.HighPerformance.Math/MathUtilities.cs
@@ -0,0 +1,19 @@
+using System.Runtime.CompilerServices;
+
+namespace Misaki.HighPerformance.Mathematics;
+
+public static class MathUtilities
+{
+ /// Returns the smallest power of two that is greater than or equal to the specified number.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int CeilPow2(int x)
+ {
+ x -= 1;
+ x |= x >> 1;
+ x |= x >> 2;
+ x |= x >> 4;
+ x |= x >> 8;
+ x |= x >> 16;
+ return x + 1;
+ }
+}
\ No newline at end of file
diff --git a/Misaki.HighPerformance.Math/Misaki.HighPerformance.Mathematics.csproj b/Misaki.HighPerformance.Math/Misaki.HighPerformance.Mathematics.csproj
new file mode 100644
index 0000000..125f4c9
--- /dev/null
+++ b/Misaki.HighPerformance.Math/Misaki.HighPerformance.Mathematics.csproj
@@ -0,0 +1,9 @@
+
+
+
+ net9.0
+ enable
+ enable
+
+
+
diff --git a/Misaki.HighPerformance.Test/CollectionBenchmark.cs b/Misaki.HighPerformance.Test/CollectionBenchmark.cs
new file mode 100644
index 0000000..4169fee
--- /dev/null
+++ b/Misaki.HighPerformance.Test/CollectionBenchmark.cs
@@ -0,0 +1,41 @@
+using BenchmarkDotNet.Attributes;
+using Misaki.HighPerformance.Unsafe.Collections;
+using Misaki.HighPerformance.Unsafe.Collections.Services;
+
+namespace Misaki.HighPerformance.Test;
+
+[MemoryDiagnoser]
+public class CollectionBenchmark
+{
+ [Params(10, 100, 1000)]
+ public int count = 100;
+
+ [Benchmark]
+ public void Array()
+ {
+ for (var i = 0; i < count; i++)
+ {
+ var array = new int[count];
+ }
+ }
+
+ [Benchmark]
+ public void UnsafeArray()
+ {
+ for (var i = 0; i < count; i++)
+ {
+ var array = new UnsafeArray(count, Allocator.Temp);
+ for (var j = 0; j < count; j++)
+ {
+ array[j] = j;
+ }
+
+ foreach (var item in array)
+ {
+ Console.WriteLine(item);
+ }
+
+ AllocationManager.Reset();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Misaki.HighPerformance.Test/HashCodeBenchmark.cs b/Misaki.HighPerformance.Test/HashCodeBenchmark.cs
new file mode 100644
index 0000000..f4c1dc7
--- /dev/null
+++ b/Misaki.HighPerformance.Test/HashCodeBenchmark.cs
@@ -0,0 +1,95 @@
+using BenchmarkDotNet.Attributes;
+using System.Numerics;
+
+namespace Misaki.HighPerformance.Test;
+
+public class HashCodeBenchmark
+{
+ private struct Component
+ {
+ public int Value;
+ public int Value2;
+ public float Value3;
+ public Guid Value4;
+ public Matrix4x4 Value5;
+ public Vector4 Value6;
+ }
+
+ [Params(100, 1000, 10000)]
+ public int count;
+
+ private Component _component = new Component()
+ {
+ Value = 0,
+ Value2 = 1,
+ Value3 = 2,
+ Value4 = Guid.NewGuid(),
+ Value5 = Matrix4x4.Identity,
+ Value6 = Vector4.One
+ };
+
+ private Dictionary _hashCache = new();
+ //private UnsafeHashMap _hashMap = new(16);
+
+ private bool _disposed;
+
+ //~HashCodeBenchmark()
+ //{
+ // Dispose();
+ //}
+
+ [Benchmark]
+ public void Hash()
+ {
+ for (var i = 0; i < count; i++)
+ {
+ _ = _component.GetHashCode();
+ }
+ }
+
+ [Benchmark]
+ public void HashWithCache()
+ {
+ for (var i = 0; i < count; i++)
+ {
+ var type = typeof(Component);
+ if (!_hashCache.TryGetValue(type, out var hash))
+ {
+ hash = type.GetHashCode();
+ _hashCache[type] = hash;
+ }
+
+ _ = hash;
+ }
+ }
+
+ //[Benchmark]
+ //public void HashWithUnsafeHashMap()
+ //{
+ // for (var i = 0; i < count; i++)
+ // {
+ // var type = _component.GetType();
+ // var guid = type.GUID;
+ // if (!_hashMap.TryGetValue(guid, out var hash))
+ // {
+ // hash = type.GetHashCode();
+ // _hashMap.Add(guid, hash);
+ // _hashMap.Test(ref _hashMap._hashMap);
+ // }
+
+ // _ = hash;
+ // }
+ //}
+
+ //public void Dispose()
+ //{
+ // if (_disposed)
+ // {
+ // return;
+ // }
+
+ // _hashMap.Dispose();
+
+ // GC.SuppressFinalize(this);
+ //}
+}
\ No newline at end of file
diff --git a/Misaki.HighPerformance.Test/Misaki.HighPerformance.Test.csproj b/Misaki.HighPerformance.Test/Misaki.HighPerformance.Test.csproj
index 8a86be8..ded352b 100644
--- a/Misaki.HighPerformance.Test/Misaki.HighPerformance.Test.csproj
+++ b/Misaki.HighPerformance.Test/Misaki.HighPerformance.Test.csproj
@@ -6,6 +6,7 @@
enable
enable
True
+ True
diff --git a/Misaki.HighPerformance.Test/ParallelNoiseBenchmark.cs b/Misaki.HighPerformance.Test/ParallelNoiseBenchmark.cs
index 75062a3..3aa8d73 100644
--- a/Misaki.HighPerformance.Test/ParallelNoiseBenchmark.cs
+++ b/Misaki.HighPerformance.Test/ParallelNoiseBenchmark.cs
@@ -21,12 +21,6 @@ public class ParallelNoiseBenchmark
return x - MathF.Truncate(x);
}
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static float Lerp(float a, float b, float t)
- {
- return a + t * (b - a);
- }
-
private static Vector2 GradientNoiseDirect(Vector2 uv)
{
uv.X %= 289;
@@ -48,7 +42,7 @@ public class ParallelNoiseBenchmark
var d11 = Vector2.Dot(GradientNoiseDirect(ip + new Vector2(1, 1)), fp - new Vector2(1, 1));
fp = fp * fp * fp * (fp * (fp * new Vector2(6.0f) - new Vector2(15.0f)) + new Vector2(10.0f));
- return Lerp(Lerp(d00, d10, fp.Y), Lerp(d01, d11, fp.Y), fp.X);
+ return float.Lerp(float.Lerp(d00, d10, fp.Y), float.Lerp(d01, d11, fp.Y), fp.X);
}
public void Execute(int index)
@@ -65,9 +59,9 @@ public class ParallelNoiseBenchmark
private const int _LENGTH = _WIDTH * _HEIGHT;
[Benchmark]
- public void JobSystem()
+ public static void JobSystem()
{
- using var buffers = new UnsafeArray(_LENGTH, AllocationType.UnInitialized);
+ using var buffers = new UnsafeArray(_LENGTH, Allocator.Persistent, AllocationType.UnInitialized);
var job = new NoiseJob()
{
buffers = buffers,
@@ -80,9 +74,9 @@ public class ParallelNoiseBenchmark
}
[Benchmark]
- public void ParallelFor()
+ public static void ParallelFor()
{
- using var buffers = new UnsafeArray(_LENGTH, AllocationType.UnInitialized);
+ using var buffers = new UnsafeArray(_LENGTH, Allocator.Persistent, AllocationType.UnInitialized);
Parallel.For(0, _LENGTH, i =>
{
@@ -94,9 +88,9 @@ public class ParallelNoiseBenchmark
}
[Benchmark]
- public void For()
+ public static void For()
{
- using var buffers = new UnsafeArray(_LENGTH, AllocationType.UnInitialized);
+ using var buffers = new UnsafeArray(_LENGTH, Allocator.Persistent, AllocationType.UnInitialized);
for (var i = 0; i < _LENGTH; i++)
{
var x = i % _WIDTH;
diff --git a/Misaki.HighPerformance.Test/Program.cs b/Misaki.HighPerformance.Test/Program.cs
index 43dae1b..0b7b2e1 100644
--- a/Misaki.HighPerformance.Test/Program.cs
+++ b/Misaki.HighPerformance.Test/Program.cs
@@ -1,37 +1,7 @@
-//using BenchmarkDotNet.Running;
-//using Misaki.HighPerformance.Test;
+using Misaki.HighPerformance.Test;
+using Misaki.HighPerformance.Unsafe.Collections.Services;
-//BenchmarkRunner.Run();
-
-using Misaki.HighPerformance.Unsafe.Collections;
-
-using var test = new UnsafeArray(10, AllocationType.UnInitialized);
-
-for (var i = 0; i < 10; i++)
-{
- var t = new Test();
- t.buffers[0] = i;
- test[i] = t;
-}
-
-test.ReAlloc(20);
-
-for (var i = 0; i < 10; i++)
-{
- Console.WriteLine(test[i].buffers[0]);
-}
-
-struct Test : IDisposable
-{
- public UnsafeArray buffers;
-
- public Test()
- {
- buffers = new UnsafeArray(1, AllocationType.UnInitialized);
- }
-
- public void Dispose()
- {
- buffers.Dispose();
- }
-}
\ No newline at end of file
+AllocationManager.Initialize(512_000);
+var test = new CollectionBenchmark();
+test.UnsafeArray();
+AllocationManager.Dispose();
diff --git a/Misaki.HighPerformance.Unsafe/Buffer/Arena .cs b/Misaki.HighPerformance.Unsafe/Buffer/Arena .cs
index ae908ae..e06de3d 100644
--- a/Misaki.HighPerformance.Unsafe/Buffer/Arena .cs
+++ b/Misaki.HighPerformance.Unsafe/Buffer/Arena .cs
@@ -8,7 +8,7 @@ namespace Misaki.HighPerformance.Unsafe.Buffer;
///
public unsafe struct Arena : IDisposable
{
- private void* _buffer;
+ private byte* _buffer;
private uint _size;
private uint _offset;
@@ -16,7 +16,7 @@ public unsafe struct Arena : IDisposable
public Arena(uint size)
{
- _buffer = NativeMemory.Alloc(size);
+ _buffer = (byte*)Malloc(size);
_size = size;
_offset = 0;
}
@@ -70,7 +70,7 @@ public unsafe struct Arena : IDisposable
public void Dispose()
{
- NativeMemory.Free(_buffer);
+ Free(_buffer);
_buffer = null;
_size = 0;
diff --git a/Misaki.HighPerformance.Unsafe/Buffer/DynamicArena.cs b/Misaki.HighPerformance.Unsafe/Buffer/DynamicArena.cs
index 2877f22..e276490 100644
--- a/Misaki.HighPerformance.Unsafe/Buffer/DynamicArena.cs
+++ b/Misaki.HighPerformance.Unsafe/Buffer/DynamicArena.cs
@@ -1,5 +1,4 @@
using Misaki.HighPerformance.Unsafe.Collections;
-using System.Runtime.InteropServices;
namespace Misaki.HighPerformance.Unsafe.Buffer;
@@ -27,18 +26,17 @@ public unsafe struct DynamicArena : IDisposable
public DynamicArena(uint initialSize)
{
_initialSize = initialSize;
- _root = (ArenaNode*)NativeMemory.Alloc(SizeOf());
+ _root = (ArenaNode*)Malloc(SizeOf());
_root->arena = new Arena(initialSize);
_root->next = null;
_current = _root;
- _disposed = false;
}
private bool CreateNewNode(uint size)
{
try
{
- var newNode = (ArenaNode*)NativeMemory.Alloc(SizeOf());
+ var newNode = (ArenaNode*)Malloc(SizeOf());
newNode->arena = new Arena(size);
newNode->next = null;
@@ -70,10 +68,14 @@ public unsafe struct DynamicArena : IDisposable
{
result = current->arena.Allocate(size, alignSize, allocationType);
if (result != null)
+ {
return result;
+ }
if (current->next == null && !CreateNewNode(Math.Max(size, _initialSize)))
+ {
return null;
+ }
current = current->next;
}
@@ -107,14 +109,16 @@ public unsafe struct DynamicArena : IDisposable
public void Dispose()
{
if (_disposed)
+ {
return;
+ }
var current = _root;
while (current != null)
{
var next = current->next;
current->arena.Dispose();
- NativeMemory.Free(current);
+ Free(current);
current = next;
}
diff --git a/Misaki.HighPerformance.Unsafe/Collections/AllocationType.cs b/Misaki.HighPerformance.Unsafe/Collections/AllocationType.cs
index eae23ef..eeac6e1 100644
--- a/Misaki.HighPerformance.Unsafe/Collections/AllocationType.cs
+++ b/Misaki.HighPerformance.Unsafe/Collections/AllocationType.cs
@@ -4,4 +4,10 @@ public enum AllocationType
{
UnInitialized,
Clear
+}
+
+public enum Allocator
+{
+ Temp,
+ Persistent
}
\ No newline at end of file
diff --git a/Misaki.HighPerformance.Unsafe/Collections/Contracts/IUnsafeCollection.cs b/Misaki.HighPerformance.Unsafe/Collections/Contracts/IUnsafeCollection.cs
index 3230985..aebaadc 100644
--- a/Misaki.HighPerformance.Unsafe/Collections/Contracts/IUnsafeCollection.cs
+++ b/Misaki.HighPerformance.Unsafe/Collections/Contracts/IUnsafeCollection.cs
@@ -1,21 +1,38 @@
namespace Misaki.HighPerformance.Unsafe.Collections.Contracts;
-public unsafe interface IUnsafeCollection : IDisposable
+public unsafe interface IUnsafeCollection : IEnumerable, IDisposable
where T : unmanaged
{
- public T* Buffer
+ ///
+ /// Gets the number of elements in a collection. The value is read-only.
+ ///
+ public int Count
{
get;
}
- public int Size
+ ///
+ /// Indicates whether the object has been created. Returns true if the object is created, otherwise false.
+ ///
+ public bool IsCreated
{
get;
}
- public ref T this[int index] { get; }
-
+ ///
+ /// Removes all elements from the collection. The collection will be empty after this operation.
+ ///
public void Clear();
- public void ReAlloc(int newSize);
-}
+ ///
+ /// Changes the size of a collection or array to the specified value.
+ ///
+ /// Specifies the new size to which the collection or array should be adjusted.
+ public void Resize(int newSize);
+
+ ///
+ /// Returns a pointer to an unmanaged memory location. This pointer can be used for low-level memory operations.
+ ///
+ /// The method returns a void pointer to the unsafe memory location.
+ public void* GetUnsafePtr();
+}
\ No newline at end of file
diff --git a/Misaki.HighPerformance.Unsafe/Collections/Services/AllocationManager.cs b/Misaki.HighPerformance.Unsafe/Collections/Services/AllocationManager.cs
new file mode 100644
index 0000000..e2e8339
--- /dev/null
+++ b/Misaki.HighPerformance.Unsafe/Collections/Services/AllocationManager.cs
@@ -0,0 +1,51 @@
+using Misaki.HighPerformance.Unsafe.Buffer;
+
+namespace Misaki.HighPerformance.Unsafe.Collections.Services;
+
+public static unsafe class AllocationManager
+{
+ private static DynamicArena _arena;
+ private static bool _initialized;
+
+ private static readonly Lock _lock = new();
+
+ public static void Initialize(uint initialSize)
+ {
+ _arena = new DynamicArena(initialSize);
+ _initialized = true;
+ }
+
+ public static T* Allocate(uint size, uint alignSize, Allocator allocator, AllocationType allocationType)
+ where T : unmanaged
+ {
+ if (!_initialized)
+ {
+ throw new InvalidOperationException("The AllocationManager has not been initialized.");
+ }
+
+ lock (_lock)
+ {
+ return allocator switch
+ {
+ Allocator.Temp => (T*)_arena.Allocate(size * (uint)sizeof(T), alignSize, allocationType),
+ Allocator.Persistent => (T*)AlignedAlloc((nuint)(size * sizeof(T)), alignSize),
+ _ => throw new ArgumentOutOfRangeException(nameof(allocator), "Invalid allocator type."),
+ };
+ }
+ }
+
+ public static void Reset(bool clear = false)
+ {
+ if (!_initialized)
+ {
+ throw new InvalidOperationException("The AllocationManager has not been initialized.");
+ }
+
+ _arena.Reset(clear);
+ }
+
+ public static void Dispose()
+ {
+ _arena.Dispose();
+ }
+}
\ No newline at end of file
diff --git a/Misaki.HighPerformance.Unsafe/Collections/UnsafeArray.cs b/Misaki.HighPerformance.Unsafe/Collections/UnsafeArray.cs
index af6c46a..97cf129 100644
--- a/Misaki.HighPerformance.Unsafe/Collections/UnsafeArray.cs
+++ b/Misaki.HighPerformance.Unsafe/Collections/UnsafeArray.cs
@@ -1,21 +1,25 @@
using Misaki.HighPerformance.Unsafe.Collections.Contracts;
+using Misaki.HighPerformance.Unsafe.Collections.Services;
using Misaki.HighPerformance.Unsafe.Helpers;
using System.Collections;
using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
namespace Misaki.HighPerformance.Unsafe.Collections;
-public unsafe struct UnsafeArray : IUnsafeCollection, IEnumerable
+///
+/// A structure for managing an array of unmanaged types with unsafe memory operations.
+///
+/// Represents a type that can be stored in an unmanaged memory context.
+public unsafe struct UnsafeArray : IUnsafeCollection
where T : unmanaged
{
private struct Enumerator : IEnumerator
{
- private UnsafeArray _collection;
+ private UnsafeArray* _collection;
private int _index;
private T _value;
- public Enumerator(ref UnsafeArray collection)
+ public Enumerator(UnsafeArray* collection)
{
_collection = collection;
_index = -1;
@@ -26,9 +30,9 @@ public unsafe struct UnsafeArray : IUnsafeCollection, IEnumerable
public bool MoveNext()
{
_index++;
- if (_index < _collection.Size)
+ if (_index < _collection->_count)
{
- _value = UnsafeUtilities.ReadArrayElement(_collection.Buffer, _index);
+ _value = UnsafeUtilities.ReadArrayElement(_collection->_buffer, _index);
return true;
}
@@ -42,22 +46,16 @@ public unsafe struct UnsafeArray : IUnsafeCollection, IEnumerable
}
// Let NativeArray indexer check for out of range.
- public T Current
+ public readonly T Current
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- get
- {
- return _value;
- }
+ get => _value;
}
- object IEnumerator.Current
+ readonly object IEnumerator.Current
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- get
- {
- return Current;
- }
+ get => Current;
}
public void Dispose()
@@ -66,10 +64,9 @@ public unsafe struct UnsafeArray : IUnsafeCollection, IEnumerable
}
private T* _buffer;
- private int _size;
+ private int _count;
- public readonly T* Buffer => _buffer;
- public readonly int Size => _size;
+ public readonly int Count => _count;
public readonly ref T this[int index]
{
@@ -77,14 +74,31 @@ public unsafe struct UnsafeArray : IUnsafeCollection, IEnumerable
get => ref UnsafeUtilities.ReadArrayElementRef(_buffer, index);
}
- public IEnumerator GetEnumerator() => new Enumerator(ref this);
+ public readonly bool IsCreated
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => _buffer != null;
+ }
+ public IEnumerator GetEnumerator() => new Enumerator((UnsafeArray*)UnsafeUtilities.AddressOf(ref this));
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
- public UnsafeArray(int size, AllocationType allocationType)
+ ///
+ /// Initializes a new instance of UnsafeArray with a specified number of elements and an allocation type. It
+ /// allocates memory and optionally clears it.
+ ///
+ /// Specifies the number of elements to allocate in the array, which must be greater than zero.
+ /// Determines how the allocated memory should be initialized, either uninitialized or cleared.
+ /// Thrown when the specified number of elements is less than or equal to zero.
+ public UnsafeArray(int count, Allocator allocator, AllocationType allocationType = AllocationType.UnInitialized)
{
- _size = size;
- _buffer = (T*)NativeMemory.AlignedAlloc((nuint)(size * sizeof(T)), AlignOf());
+ if (count <= 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count), "Count must be greater than zero.");
+ }
+
+ _buffer = AllocationManager.Allocate((uint)count, (uint)AlignOf(), allocator, allocationType);
+ _count = count;
if (allocationType == AllocationType.Clear)
{
@@ -92,29 +106,47 @@ public unsafe struct UnsafeArray : IUnsafeCollection, IEnumerable
}
}
- public void ReAlloc(int newSize)
+ ///
+ /// Initializes an UnsafeArray with a pointer to a buffer and a count of elements. The count is adjusted based on
+ /// the size of the type T.
+ ///
+ /// A pointer to the memory location that holds the elements of the array.
+ /// The total size of the data in bytes, which is divided by the size of type T to determine the number of elements.
+ public UnsafeArray(void* buffer, int count)
{
- if (newSize == _size)
+ _buffer = (T*)buffer;
+ _count = count;
+ }
+
+ public void Resize(int newSize)
+ {
+ if (newSize == _count)
{
return;
}
- _buffer = (T*)NativeMemory.AlignedRealloc(_buffer, (nuint)(newSize * sizeof(T)), AlignOf());
- _size = newSize;
+ _buffer = (T*)AlignedRealloc(_buffer, (nuint)(newSize * sizeof(T)), AlignOf());
+ _count = newSize;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void Clear()
+ public readonly void Clear()
{
- MemClear(_buffer, (uint)(_size * sizeof(T)));
+ MemClear(_buffer, (uint)(_count * sizeof(T)));
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public readonly void* GetUnsafePtr()
+ {
+ return _buffer;
}
public void Dispose()
{
- NativeMemory.AlignedFree(_buffer);
+ AlignedFree(_buffer);
_buffer = null;
- _size = 0;
+ _count = 0;
}
}
diff --git a/Misaki.HighPerformance.Unsafe/Collections/UnsafeHashMap.cs b/Misaki.HighPerformance.Unsafe/Collections/UnsafeHashMap.cs
new file mode 100644
index 0000000..69cd556
--- /dev/null
+++ b/Misaki.HighPerformance.Unsafe/Collections/UnsafeHashMap.cs
@@ -0,0 +1,205 @@
+using Misaki.HighPerformance.Unsafe.Collections.Contracts;
+using Misaki.HighPerformance.Unsafe.Helpers;
+using System.Collections;
+using System.Runtime.CompilerServices;
+
+namespace Misaki.HighPerformance.Unsafe.Collections;
+
+public unsafe struct UnsafeHashMap : IUnsafeCollection> where TKey : unmanaged, IEquatable where TValue : unmanaged
+{
+ private struct Enumerator : IEnumerator>
+ {
+ internal HashMapHelper.Enumerator _enumerator;
+
+ public Enumerator(HashMapHelper* data)
+ {
+ _enumerator = new HashMapHelper.Enumerator(data);
+ }
+
+ ///
+ /// The current key-value pair.
+ ///
+ /// The current key-value pair.
+ public KeyValuePair Current
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => _enumerator.GetCurrent();
+ }
+
+ ///
+ /// Gets the element at the current position of the enumerator in the container.
+ ///
+ object IEnumerator.Current => Current;
+
+ ///
+ /// Advances the enumerator to the next key-value pair.
+ ///
+ /// True if is valid to read after the call.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool MoveNext() => _enumerator.MoveNext();
+
+ ///
+ /// Resets the enumerator to its initial state.
+ ///
+ public void Reset() => _enumerator.Reset();
+
+ ///
+ /// Does nothing.
+ ///
+ public void Dispose()
+ {
+ }
+ }
+
+ private HashMapHelper _hashMap;
+
+ public readonly int Count => _hashMap.Count;
+ public readonly int Capacity => _hashMap.Capacity;
+ public readonly bool IsCreated => _hashMap.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 (!_hashMap.TryGetValue(key, out var result))
+ {
+ throw new ArgumentException($"Key: {key} is not present.");
+ }
+
+ return result;
+ }
+
+ set
+ {
+ var idx = _hashMap.Find(key);
+ if (-1 != idx)
+ {
+ UnsafeUtilities.WriteArrayElement(_hashMap.Buffer, idx, value);
+ return;
+ }
+
+ TryAdd(key, value);
+ }
+ }
+
+ public IEnumerator> GetEnumerator() => new Enumerator((HashMapHelper*)UnsafeUtilities.AddressOf(ref _hashMap));
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+
+ public UnsafeHashMap(int capacity)
+ {
+ _hashMap = new HashMapHelper(capacity, sizeof(TValue), HashMapHelper.MINIMAL_CAPACITY);
+ }
+
+ ///
+ /// 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.
+ public bool TryAdd(TKey key, TValue item)
+ {
+ var idx = _hashMap.TryAdd(key);
+ if (idx != -1)
+ {
+ UnsafeUtilities.WriteArrayElement(_hashMap.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.
+ public void Add(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}");
+ }
+ }
+
+ ///
+ /// 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.
+ public bool TryGetValue(TKey key, out TValue item)
+ {
+ return _hashMap.TryGetValue(key, out item);
+ }
+
+ ///
+ /// Returns true if a given key is present in this hash map.
+ ///
+ /// The key to look up.
+ /// True if the key was present.
+ public bool ContainsKey(TKey key)
+ {
+ return -1 != _hashMap.Find(key);
+ }
+
+ ///
+ /// Sets the capacity to match what it would be if it had been originally initialized with all its entries.
+ ///
+ public void TrimExcess() => _hashMap.TrimExcess();
+
+ public void Resize(int newSize)
+ {
+ _hashMap.Resize(newSize);
+ }
+
+ public void Clear()
+ {
+ _hashMap.Clear();
+ }
+
+ ///
+ /// Retrieves an array of keys from the hash map.
+ ///
+ /// An array containing the keys stored in the hash map.
+ public UnsafeArray GetKeyArray(Allocator allocator) => _hashMap.GetKeyArray(allocator);
+
+ ///
+ /// Retrieves an array of values from the underlying hash map.
+ ///
+ /// An UnsafeArray containing the values stored in the hash map.
+ public UnsafeArray GetValueArray(Allocator allocator) => _hashMap.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.
+ public UnsafeArray> GetKeyValueArrays(Allocator allocator) => _hashMap.GetKeyValueArrays(allocator);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public readonly void* GetUnsafePtr()
+ {
+ return _hashMap.Buffer;
+ }
+
+ public void Dispose()
+ {
+ _hashMap.Dispose();
+ }
+
+ public void Test(ref HashMapHelper t)
+ {
+ Console.WriteLine(t.Equals(_hashMap));
+ }
+}
diff --git a/Misaki.HighPerformance.Unsafe/Collections/UnsafeHashSet.cs b/Misaki.HighPerformance.Unsafe/Collections/UnsafeHashSet.cs
new file mode 100644
index 0000000..50c81be
--- /dev/null
+++ b/Misaki.HighPerformance.Unsafe/Collections/UnsafeHashSet.cs
@@ -0,0 +1,122 @@
+using Misaki.HighPerformance.Unsafe.Collections.Contracts;
+using Misaki.HighPerformance.Unsafe.Helpers;
+using System.Collections;
+using System.Runtime.CompilerServices;
+
+namespace Misaki.HighPerformance.Unsafe.Collections;
+
+///
+/// A collection that provides fast, unsafe operations for managing a set of unmanaged types. It supports adding,
+/// removing, and checking for values.
+///
+/// Represents an unmanaged type that can be compared for equality.
+public unsafe struct UnsafeHashSet : IUnsafeCollection, IEnumerable
+ where T : unmanaged, IEquatable
+{
+ private struct Enumerator : IEnumerator
+ {
+ internal HashMapHelper.Enumerator _enumerator;
+
+ public Enumerator(HashMapHelper* hashMap)
+ {
+ _enumerator = new HashMapHelper.Enumerator(hashMap);
+ }
+
+ public T Current
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => _enumerator.buffer->_keys[_enumerator.index];
+ }
+
+ object IEnumerator.Current => Current;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool MoveNext() => _enumerator.MoveNext();
+
+ public void Reset() => _enumerator.Reset();
+
+ public readonly void Dispose()
+ {
+ }
+ }
+
+ private HashMapHelper _hashMap;
+
+ public readonly int Count => _hashMap.Count;
+ public readonly int Capacity => _hashMap.Capacity;
+ public readonly bool IsCreated => _hashMap.IsCreated;
+
+ public IEnumerator GetEnumerator() => new Enumerator((HashMapHelper*)UnsafeUtilities.AddressOf(ref _hashMap));
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+
+ public UnsafeHashSet(int capacity)
+ {
+ _hashMap = new HashMapHelper(capacity, 0, HashMapHelper.MINIMAL_CAPACITY);
+ }
+
+ ///
+ /// Adds a new value (unless it is already present).
+ ///
+ /// The value to add.
+ /// True if the value was not already present.
+ public bool Add(T item)
+ {
+ return -1 != _hashMap.TryAdd(item);
+ }
+
+ ///
+ /// Removes a particular value.
+ ///
+ /// The value to remove.
+ /// True if the value was present.
+ public bool Remove(T item)
+ {
+ return -1 != _hashMap.TryRemove(item);
+ }
+
+ ///
+ /// Returns true if a particular value is present.
+ ///
+ /// The value to check for.
+ /// True if the value was present.
+ public bool Contains(T item)
+ {
+ return -1 != _hashMap.Find(item);
+ }
+
+ ///
+ /// Sets the capacity to match what it would be if it had been originally initialized with all its entries.
+ ///
+ public void TrimExcess() => _hashMap.TrimExcess();
+
+ ///
+ /// Returns an array with a copy of this set's values (in no particular order).
+ ///
+ /// The allocator to use.
+ /// An array with a copy of the set's values.
+ public UnsafeArray ToNativeArray(Allocator allocator)
+ {
+ return _hashMap.GetKeyArray(allocator);
+ }
+
+ public void Resize(int newSize)
+ {
+ _hashMap.Resize(newSize);
+ }
+
+ public void Clear()
+ {
+ _hashMap.Clear();
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public readonly void* GetUnsafePtr()
+ {
+ return _hashMap.Buffer;
+ }
+
+ public void Dispose()
+ {
+ _hashMap.Dispose();
+ }
+}
diff --git a/Misaki.HighPerformance.Unsafe/Collections/UnsafeList.cs b/Misaki.HighPerformance.Unsafe/Collections/UnsafeList.cs
index 3a6155f..92f1fe3 100644
--- a/Misaki.HighPerformance.Unsafe/Collections/UnsafeList.cs
+++ b/Misaki.HighPerformance.Unsafe/Collections/UnsafeList.cs
@@ -5,16 +5,20 @@ using System.Runtime.CompilerServices;
namespace Misaki.HighPerformance.Unsafe.Collections;
-public unsafe struct UnsafeList : IUnsafeCollection, IEnumerable
+///
+/// A collection that allows for unsafe operations on a list of unmanaged types.
+///
+/// Represents a type that can be stored in the collection, constrained to unmanaged types for performance and safety.
+public unsafe struct UnsafeList : IUnsafeCollection
where T : unmanaged
{
private struct Enumerator : IEnumerator
{
- private UnsafeList _collection;
+ private UnsafeList* _collection;
private int _index;
private T _value;
- public Enumerator(ref UnsafeList collection)
+ public Enumerator(UnsafeList* collection)
{
_collection = collection;
_index = -1;
@@ -25,9 +29,9 @@ public unsafe struct UnsafeList : IUnsafeCollection, IEnumerable
public bool MoveNext()
{
_index++;
- if (_index < _collection.Size)
+ if (_index < _collection->_count)
{
- _value = UnsafeUtilities.ReadArrayElement(_collection.Buffer, _index);
+ _value = UnsafeUtilities.ReadArrayElement(_collection->_array.GetUnsafePtr(), _index);
return true;
}
@@ -88,9 +92,9 @@ public unsafe struct UnsafeList : IUnsafeCollection, IEnumerable
/// The value to be added to the collection.
public void AddNoResize(T value)
{
- var idx = Interlocked.Increment(ref listData->_size) - 1;
+ var idx = Interlocked.Increment(ref listData->_count) - 1;
listData->CheckNoResizeCapacity(idx, 1);
- UnsafeUtilities.WriteArrayElement(listData->Buffer, idx, value);
+ UnsafeUtilities.WriteArrayElement(listData->_array.GetUnsafePtr(), idx, value);
}
///
@@ -100,19 +104,19 @@ public unsafe struct UnsafeList : IUnsafeCollection, IEnumerable
/// Indicates the number of elements to be added from the source data.
public void AddRangeNoResize(T* ptr, int count)
{
- var idx = Interlocked.Add(ref listData->_size, count) - count;
+ var idx = Interlocked.Add(ref listData->_count, count) - count;
listData->CheckNoResizeCapacity(idx, count);
- MemCpy(listData->Buffer + idx, ptr, (uint)(count * sizeof(T)));
+ MemCpy(UnsafeUtilities.ReadArrayElementUnsafe(listData->_array.GetUnsafePtr(), idx), ptr, (uint)(count * sizeof(T)));
}
}
private UnsafeArray _array;
- private int _size;
+ private int _count;
- public readonly T* Buffer => _array.Buffer;
- public readonly int Size => _size;
- public readonly int Capacity => _array.Size;
+ public readonly int Count => _count;
+ public readonly int Capacity => _array.Count;
+ public readonly bool IsCreated => _array.IsCreated;
public readonly ref T this[int index]
{
@@ -120,17 +124,15 @@ public unsafe struct UnsafeList : IUnsafeCollection, IEnumerable
get => ref _array[index];
}
- public IEnumerator GetEnumerator() => new Enumerator(ref this);
-
+ public IEnumerator GetEnumerator() => new Enumerator((UnsafeList*)UnsafeUtilities.AddressOf(ref this));
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
- public ParallelWriter AsParallelWriter() =>
- new((UnsafeList*)UnsafeUtilities.AddressOf(ref this));
+ public ParallelWriter AsParallelWriter() => new((UnsafeList*)UnsafeUtilities.AddressOf(ref this));
- public UnsafeList(int capacity, AllocationType allocationType)
+ public UnsafeList(int capacity, Allocator allocator, AllocationType allocationType = AllocationType.UnInitialized)
{
- _array = new UnsafeArray(capacity, allocationType);
- _size = 0;
+ _array = new UnsafeArray(capacity, allocator, allocationType);
+ _count = 0;
if (allocationType == AllocationType.Clear)
{
@@ -140,7 +142,7 @@ public unsafe struct UnsafeList : IUnsafeCollection, IEnumerable
private readonly void CheckNoResizeCapacity(int count)
{
- CheckNoResizeCapacity(count, Size);
+ CheckNoResizeCapacity(count, Count);
}
private readonly void CheckNoResizeCapacity(int index, int count)
@@ -148,7 +150,7 @@ public unsafe struct UnsafeList : IUnsafeCollection, IEnumerable
if (index + count > Capacity)
{
throw new Exception(
- $"AddNoResize assumes that list capacity is sufficient (Capacity {Capacity}, Size {Size}), requested count {count}!"
+ $"AddNoResize assumes that list capacity is sufficient (Capacity {Capacity}, Size {Count}), requested count {count}!"
);
}
}
@@ -165,12 +167,12 @@ public unsafe struct UnsafeList : IUnsafeCollection, IEnumerable
throw new ArgumentOutOfRangeException($"Value for index {index} must be positive.");
}
- if (index > Size)
+ if (index > Count)
{
throw new ArgumentOutOfRangeException($"Value for index {index} is out of bounds.");
}
- if (index + count > Size)
+ if (index + count > Count)
{
throw new ArgumentOutOfRangeException($"Value for count {count} is out of bounds.");
}
@@ -178,37 +180,37 @@ public unsafe struct UnsafeList : IUnsafeCollection, IEnumerable
public void Add(T value)
{
- if (_size >= Capacity)
+ if (_count >= Capacity)
{
- ReAlloc(Capacity + (int)(Capacity * 0.5f));
+ Resize(Capacity + (int)(Capacity * 0.5f));
}
- UnsafeUtilities.WriteArrayElement(Buffer, _size, value);
- _size++;
+ UnsafeUtilities.WriteArrayElement(_array.GetUnsafePtr(), _count, value);
+ _count++;
}
public void AddNoResize(T value)
{
CheckNoResizeCapacity(1);
- UnsafeUtilities.WriteArrayElement(Buffer, _size, value);
- _size++;
+ UnsafeUtilities.WriteArrayElement(_array.GetUnsafePtr(), _count, value);
+ _count++;
}
public void AddRange(Span values, int count)
{
- var newSize = _size + count;
+ var newSize = _count + count;
if (newSize > Capacity)
{
- ReAlloc(Capacity + count);
+ Resize(Capacity + count);
}
fixed (T* ptr = values)
{
- MemCpy(_array.Buffer + _size, ptr, (uint)(count * sizeof(T)));
+ MemCpy(UnsafeUtilities.ReadArrayElementUnsafe(_array.GetUnsafePtr(), _count), ptr, (uint)(count * sizeof(T)));
}
- _size += count;
+ _count += count;
}
public void AddRangeNoResize(ReadOnlySpan values)
@@ -217,18 +219,18 @@ public unsafe struct UnsafeList : IUnsafeCollection, IEnumerable
fixed (T* ptr = values)
{
- MemCpy(_array.Buffer + _size, ptr, (uint)(values.Length * sizeof(T)));
+ MemCpy(UnsafeUtilities.ReadArrayElementUnsafe(_array.GetUnsafePtr(), _count), ptr, (uint)(values.Length * sizeof(T)));
}
- _size += values.Length;
+ _count += values.Length;
}
public void AddRangeNoResize(T* ptr, int count)
{
CheckNoResizeCapacity(count);
- MemCpy(_array.Buffer + _size, ptr, (uint)(count * sizeof(T)));
- _size += count;
+ MemCpy(UnsafeUtilities.ReadArrayElementUnsafe(_array.GetUnsafePtr(), _count), ptr, (uint)(count * sizeof(T)));
+ _count += count;
}
public void RemoveRange(int start, int length)
@@ -240,13 +242,12 @@ public unsafe struct UnsafeList : IUnsafeCollection, IEnumerable
return;
}
- var copyFrom = Math.Min(start + length, _size);
- MemCpy(
- _array.Buffer + start,
- _array.Buffer + copyFrom,
- (uint)((_size - copyFrom) * sizeof(T))
+ var copyFrom = Math.Min(start + length, _count);
+ MemCpy(UnsafeUtilities.ReadArrayElementUnsafe(_array.GetUnsafePtr(), start),
+ UnsafeUtilities.ReadArrayElementUnsafe(_array.GetUnsafePtr(), copyFrom),
+ (uint)((_count - copyFrom) * sizeof(T))
);
- _size -= length;
+ _count -= length;
}
public void RemoveAt(int index)
@@ -263,13 +264,12 @@ public unsafe struct UnsafeList : IUnsafeCollection, IEnumerable
return;
}
- var copyFrom = Math.Min(_size - length, start + length);
- MemCpy(
- _array.Buffer + start,
- _array.Buffer + copyFrom,
- (uint)((_size - copyFrom) * sizeof(T))
+ var copyFrom = Math.Min(_count - length, start + length);
+ MemCpy(UnsafeUtilities.ReadArrayElementUnsafe(_array.GetUnsafePtr(), start),
+ UnsafeUtilities.ReadArrayElementUnsafe(_array.GetUnsafePtr(), copyFrom),
+ (uint)((_count - copyFrom) * sizeof(T))
);
- _size -= length;
+ _count -= length;
}
public void RemoveAtSwapBack(int index)
@@ -277,26 +277,47 @@ public unsafe struct UnsafeList : IUnsafeCollection, IEnumerable
RemoveRangeSwapBack(index, 1);
}
- public void ReAlloc(int newSize)
+ public void Resize(int newSize)
{
- _array.ReAlloc(newSize);
+ _array.Resize(newSize);
- if (_size > newSize)
+ if (_count > newSize)
{
- _size = newSize;
+ _count = newSize;
}
}
public void Clear()
{
_array.Clear();
- _size = 0;
+ _count = 0;
+ }
+
+ ///
+ /// Returns a pointer to the underlying data of the array in an unsafe manner. This method is optimized for
+ /// performance.
+ ///
+ /// A pointer to the array's data.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public readonly void* GetUnsafePtr()
+ {
+ return _array.GetUnsafePtr();
+ }
+
+ ///
+ /// Converts the current array to an UnsafeArray representation using its pointer and count.
+ ///
+ /// Returns a new UnsafeArray instance initialized with the array's unsafe pointer and its count.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public readonly UnsafeArray AsUnsafeArray()
+ {
+ return new UnsafeArray(_array.GetUnsafePtr(), _count);
}
public void Dispose()
{
_array.Dispose();
- _size = 0;
+ _count = 0;
}
}
diff --git a/Misaki.HighPerformance.Unsafe/Collections/UnsafeQueue.cs b/Misaki.HighPerformance.Unsafe/Collections/UnsafeQueue.cs
index 117b122..d1b2a21 100644
--- a/Misaki.HighPerformance.Unsafe/Collections/UnsafeQueue.cs
+++ b/Misaki.HighPerformance.Unsafe/Collections/UnsafeQueue.cs
@@ -1,20 +1,75 @@
using Misaki.HighPerformance.Unsafe.Collections.Contracts;
using Misaki.HighPerformance.Unsafe.Helpers;
+using System.Collections;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
namespace Misaki.HighPerformance.Unsafe.Collections;
+///
+/// A structure that implements a queue using unmanaged types for efficient memory management.
+///
+/// Represents the type of elements stored in the queue, which must be an unmanaged type for performance and safety.
public unsafe struct UnsafeQueue : IUnsafeCollection
where T : unmanaged
{
+ private struct Enumerator : IEnumerator
+ {
+ private UnsafeQueue* _collection;
+ private int _index;
+ private T _value;
+
+ public Enumerator(UnsafeQueue* collection)
+ {
+ _collection = collection;
+ _index = -1;
+ _value = default;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool MoveNext()
+ {
+ _index++;
+ if (_index < _collection->_count)
+ {
+ _value = UnsafeUtilities.ReadArrayElement(_collection->_array.GetUnsafePtr(), _index);
+ return true;
+ }
+
+ _value = default;
+ return false;
+ }
+
+ public void Reset()
+ {
+ _index = -1;
+ }
+
+ // Let NativeArray indexer check for out of range.
+ public readonly T Current
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => _value;
+ }
+
+ readonly object IEnumerator.Current
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => Current;
+ }
+
+ public readonly void Dispose()
+ {
+ }
+ }
+
private UnsafeArray _array;
- private int _size;
+ private int _count;
private int _offset;
- public readonly T* Buffer => _array.Buffer;
- public readonly int Size => _size;
- public readonly int Capacity => _array.Size;
+ public readonly int Count => _count;
+ public readonly int Capacity => _array.Count;
+ public readonly bool IsCreated => _array.IsCreated;
public readonly ref T this[int index]
{
@@ -22,10 +77,13 @@ public unsafe struct UnsafeQueue : IUnsafeCollection
get => ref _array[index];
}
- public UnsafeQueue(int capacity, AllocationType allocationType)
+ public IEnumerator GetEnumerator() => new Enumerator((UnsafeQueue*)UnsafeUtilities.AddressOf(ref this));
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+
+ public UnsafeQueue(int capacity, Allocator allocator, AllocationType allocationType = AllocationType.UnInitialized)
{
- _array = new UnsafeArray(capacity, allocationType);
- _size = 0;
+ _array = new UnsafeArray(capacity, allocator, allocationType);
+ _count = 0;
_offset = 0;
if (allocationType == AllocationType.Clear)
@@ -34,34 +92,49 @@ public unsafe struct UnsafeQueue : IUnsafeCollection
}
}
+ ///
+ /// Adds an element to the end of a collection, resizing if the current capacity is reached. The new element is
+ /// stored in a circular buffer.
+ ///
+ /// The item to be added to the collection.
public void Enqueue(T value)
{
- if (_size >= Capacity)
+ if (_count >= Capacity)
{
- ReAlloc(Capacity + (int)(Capacity * 0.5f));
+ Resize(Capacity + (int)(Capacity * 0.5f));
}
- UnsafeUtilities.WriteArrayElement(Buffer, (_offset + _size) % Capacity, value);
- _size++;
+ UnsafeUtilities.WriteArrayElement(_array.GetUnsafePtr(), (_offset + _count) % Capacity, value);
+ _count++;
}
+ ///
+ /// Removes and returns the element at the front of the queue. If the queue is empty, an exception is thrown.
+ ///
+ /// The element that was removed from the front of the queue.
+ /// Thrown when attempting to dequeue from an empty queue.
public T Dequeue()
{
- if (_size == 0)
+ if (_count == 0)
{
throw new InvalidOperationException("Queue is empty.");
}
- var value = UnsafeUtilities.ReadArrayElement(Buffer, _offset);
+ var value = UnsafeUtilities.ReadArrayElement(_array.GetUnsafePtr(), _offset);
_offset = (_offset + 1) % Capacity;
- _size--;
+ _count--;
return value;
}
+ ///
+ /// Attempts to remove and return an item from a collection. Returns a boolean indicating success or failure.
+ ///
+ /// The output variable that will hold the dequeued item if the operation is successful.
+ /// True if an item was successfully dequeued, otherwise false.
public bool TryDequeue([MaybeNullWhen(false)] out T value)
{
- if (_size == 0)
+ if (_count == 0)
{
value = default;
return false;
@@ -71,27 +144,33 @@ public unsafe struct UnsafeQueue : IUnsafeCollection
return true;
}
- public void ReAlloc(int newSize)
+ public void Resize(int newSize)
{
- _array.ReAlloc(newSize);
+ _array.Resize(newSize);
- if (_size > newSize)
+ if (_count > newSize)
{
- _size = newSize;
+ _count = newSize;
}
}
public void Clear()
{
_array.Clear();
- _size = 0;
+ _count = 0;
_offset = 0;
}
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public readonly void* GetUnsafePtr()
+ {
+ return _array.GetUnsafePtr();
+ }
+
public void Dispose()
{
_array.Dispose();
- _size = 0;
+ _count = 0;
_offset = 0;
}
}
diff --git a/Misaki.HighPerformance.Unsafe/Helpers/HashMapHelper.cs b/Misaki.HighPerformance.Unsafe/Helpers/HashMapHelper.cs
new file mode 100644
index 0000000..e65a400
--- /dev/null
+++ b/Misaki.HighPerformance.Unsafe/Helpers/HashMapHelper.cs
@@ -0,0 +1,491 @@
+using Misaki.HighPerformance.Mathematics;
+using Misaki.HighPerformance.Unsafe.Collections;
+using System.Numerics;
+using System.Runtime.CompilerServices;
+
+namespace Misaki.HighPerformance.Unsafe.Helpers;
+
+public unsafe struct HashMapHelper : IDisposable
+ where TKey : unmanaged, IEquatable
+{
+ internal unsafe struct Enumerator
+ {
+ public HashMapHelper* buffer;
+ public int index;
+ public int bucketIndex;
+ public int nextIndex;
+
+ public unsafe Enumerator(HashMapHelper* data)
+ {
+ buffer = data;
+ index = -1;
+ bucketIndex = 0;
+ nextIndex = -1;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool MoveNext()
+ {
+ return buffer->MoveNext(ref bucketIndex, ref nextIndex, out index);
+ }
+
+ public void Reset()
+ {
+ index = -1;
+ bucketIndex = 0;
+ nextIndex = -1;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public KeyValuePair GetCurrent()
+ where TValue : unmanaged
+ {
+ return new KeyValuePair(buffer->_keys[index], UnsafeUtilities.ReadArrayElement(buffer->_buffer, index));
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public TKey GetCurrentKey()
+ {
+ if (index != -1)
+ {
+ return buffer->_keys[index];
+ }
+
+ return default;
+ }
+
+ public void Dispose()
+ {
+ }
+ }
+
+ // This buffer has 4 parts: TValue, TKey, Next, Buckets.
+ private byte* _buffer;
+
+ internal TKey* _keys;
+ internal int* _next;
+ internal int* _buckets;
+
+ private int _count;
+ private int _capacity;
+ private int _bucketCapacity;
+ private int _allocatedIndex;
+ private int _firstFreeIndex;
+
+ private readonly int _sizeOfTValue;
+ private readonly int _log2MinGrowth;
+
+ public const int MINIMAL_CAPACITY = 64;
+
+ public readonly byte* Buffer
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => _buffer;
+ }
+
+ public readonly int Count
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => _count;
+ }
+
+ public readonly int Capacity
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => _capacity;
+ }
+
+ public readonly bool IsCreated
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => _buffer != null;
+ }
+
+ public readonly bool IsEmpty
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => !IsCreated || _count == 0;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private readonly int CalcCapacityCeilPow2(int capacity)
+ {
+ capacity = Math.Max(Math.Max(1, _count), capacity);
+ var newCapacity = Math.Max(capacity, 1 << _log2MinGrowth);
+ var result = MathUtilities.CeilPow2(newCapacity);
+
+ return result;
+ }
+
+ private static int CalculateDataSize(int capacity, int bucketCapacity, int sizeOfTValue, out int outKeyOffset, out int outNextOffset, out int outBucketOffset)
+ {
+ var sizeOfTKey = sizeof(TKey);
+ var sizeOfInt = sizeof(int);
+
+ var valuesSize = sizeOfTValue * capacity;
+ var keysSize = sizeOfTKey * capacity;
+ var nextSize = sizeOfInt * capacity;
+ var bucketSize = sizeOfInt * bucketCapacity;
+ var totalSize = valuesSize + keysSize + nextSize + bucketSize;
+
+ outKeyOffset = 0 + valuesSize;
+ outNextOffset = outKeyOffset + keysSize;
+ outBucketOffset = outNextOffset + nextSize;
+
+ return totalSize;
+ }
+
+ public HashMapHelper(int capacity, int sizeOfTValue, uint minGrowth)
+ {
+ if (capacity <= 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(capacity), "Capacity must be greater than zero.");
+ }
+
+ if (sizeOfTValue <= 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(sizeOfTValue), "Size of TValue must be greater than zero.");
+ }
+
+ _capacity = CalcCapacityCeilPow2(capacity);
+ _bucketCapacity = _capacity * 2;
+
+ var totalSize = CalculateDataSize(_capacity, _bucketCapacity, sizeOfTValue,
+ out var keyOffset, out var nextOffset, out var bucketOffset);
+
+ _buffer = (byte*)Malloc((nuint)totalSize);
+ _keys = (TKey*)(_buffer + keyOffset);
+ _next = (int*)(_buffer + nextOffset);
+ _buckets = (int*)(_buffer + bucketOffset);
+
+ Clear();
+
+ _sizeOfTValue = sizeOfTValue;
+ _log2MinGrowth = BitOperations.Log2(minGrowth);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private readonly int GetBucket(in TKey key)
+ {
+ return (int)((uint)key.GetHashCode() & _bucketCapacity - 1);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private readonly void CheckIndexOutOfBounds(int idx)
+ {
+ if ((uint)idx >= (uint)_capacity)
+ {
+ throw new InvalidOperationException($"Internal HashMap error. idx {idx}");
+ }
+ }
+
+ internal void ResizeExact(int newCapacity, int newBucketCapacity)
+ {
+ var totalSize = CalculateDataSize(newCapacity, newBucketCapacity, _sizeOfTValue,
+ out var keyOffset, out var nextOffset, out var bucketOffset);
+
+ var oldBuffer = _buffer;
+ var oldKeys = _keys;
+ var oldNext = _next;
+ var oldBuckets = _buckets;
+ var oldBucketCapacity = _bucketCapacity;
+
+ _buffer = (byte*)Malloc((nuint)totalSize);
+ _keys = (TKey*)(_buffer + keyOffset);
+ _next = (int*)(_buffer + nextOffset);
+ _buckets = (int*)(_buffer + bucketOffset);
+ _capacity = newCapacity;
+ _bucketCapacity = newBucketCapacity;
+
+ Clear();
+
+ for (int i = 0, num = oldBucketCapacity; i < num; ++i)
+ {
+ for (var idx = oldBuckets[i]; idx != -1; idx = oldNext[idx])
+ {
+ var newIdx = TryAdd(oldKeys[idx]);
+ MemCpy(_buffer + _sizeOfTValue * newIdx, oldBuffer + _sizeOfTValue * idx, (nuint)_sizeOfTValue);
+ }
+ }
+
+ Free(oldBuffer);
+ }
+
+ internal void Resize(int newCapacity)
+ {
+ newCapacity = Math.Max(newCapacity, _count);
+ var newBucketCapacity = MathUtilities.CeilPow2(newCapacity * 2);
+
+ if (_capacity == newCapacity && _bucketCapacity == newBucketCapacity)
+ {
+ return;
+ }
+
+ ResizeExact(newCapacity, newBucketCapacity);
+ }
+
+ public void TrimExcess()
+ {
+ var capacity = CalcCapacityCeilPow2(_count);
+ ResizeExact(capacity, capacity * 2);
+ }
+
+ public int Find(TKey key)
+ {
+ if (_allocatedIndex <= 0)
+ {
+ return -1;
+ }
+
+ // First find the slot based on the hash
+ var bucket = GetBucket(key);
+ var entryIdx = _buckets[bucket];
+
+ if ((uint)entryIdx < (uint)_capacity)
+ {
+ var nextPtrs = _next;
+ var test = UnsafeUtilities.ReadArrayElement(_keys, entryIdx);
+ var test2 = UnsafeUtilities.ReadArrayElement(_next, 0);
+ while (!UnsafeUtilities.ReadArrayElement(_keys, entryIdx).Equals(key))
+ {
+ entryIdx = nextPtrs[entryIdx];
+ if ((uint)entryIdx >= (uint)_capacity)
+ {
+ return -1;
+ }
+ }
+
+ return entryIdx;
+ }
+
+ return -1;
+ }
+
+ public int TryAdd(in TKey key)
+ {
+ if (Find(key) != -1)
+ {
+ return -1;
+ }
+
+ // Allocate an entry from the free list
+ int idx;
+ int* next;
+
+ if (_allocatedIndex >= _capacity && _firstFreeIndex < 0)
+ {
+ var newCap = Math.Min(MINIMAL_CAPACITY, CalcCapacityCeilPow2(_capacity + (1 << _log2MinGrowth)));
+ Resize(newCap);
+ }
+
+ idx = _firstFreeIndex;
+
+ if (idx >= 0)
+ {
+ _firstFreeIndex = _next[idx];
+ }
+ else
+ {
+ idx = _allocatedIndex++;
+ }
+
+ CheckIndexOutOfBounds(idx);
+
+ UnsafeUtilities.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;
+ }
+
+ public int TryRemove(TKey key)
+ {
+ if (_capacity == 0)
+ {
+ return -1;
+ }
+
+ var removed = 0;
+
+ // First find the slot based on the hash
+ var bucket = GetBucket(key);
+
+ var prevEntry = -1;
+ var entryIdx = _buckets[bucket];
+
+ while (entryIdx >= 0 && entryIdx < _capacity)
+ {
+ if (UnsafeUtilities.ReadArrayElement(_keys, entryIdx).Equals(key))
+ {
+ ++removed;
+
+ // Found matching element, remove it
+ if (prevEntry < 0)
+ {
+ _buckets[bucket] = _next[entryIdx];
+ }
+ else
+ {
+ _next[prevEntry] = _next[entryIdx];
+ }
+
+ // And free the index
+ var nextIdx = _next[entryIdx];
+ _next[entryIdx] = _firstFreeIndex;
+ _firstFreeIndex = entryIdx;
+ entryIdx = nextIdx;
+
+ break;
+ }
+ else
+ {
+ prevEntry = entryIdx;
+ entryIdx = _next[entryIdx];
+ }
+ }
+
+ _count -= removed;
+ return 0 != removed ? removed : -1;
+ }
+
+ public bool TryGetValue(TKey key, out TValue item)
+ where TValue : unmanaged
+ {
+ var idx = Find(key);
+
+ if (idx != -1)
+ {
+ item = UnsafeUtilities.ReadArrayElement(_buffer, idx);
+ return true;
+ }
+
+ item = default;
+ return false;
+ }
+
+ public bool MoveNextSearch(ref int bucketIndex, ref int nextIndex, out int index)
+ {
+ for (int i = bucketIndex, num = _bucketCapacity; i < num; ++i)
+ {
+ var idx = _buckets[i];
+
+ if (idx != -1)
+ {
+ index = idx;
+ bucketIndex = i + 1;
+ nextIndex = _next[idx];
+
+ return true;
+ }
+ }
+
+ index = -1;
+ bucketIndex = _bucketCapacity;
+ nextIndex = -1;
+ return false;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool MoveNext(ref int bucketIndex, ref int nextIndex, out int index)
+ {
+ if (nextIndex != -1)
+ {
+ index = nextIndex;
+ nextIndex = _next[nextIndex];
+ return true;
+ }
+
+ return MoveNextSearch(ref bucketIndex, ref nextIndex, out index);
+ }
+
+ internal UnsafeArray GetKeyArray(Allocator allocator)
+ {
+ var result = new UnsafeArray(_count, allocator, AllocationType.UnInitialized);
+
+ for (int i = 0, count = 0, max = result.Count, capacity = _bucketCapacity; i < capacity && count < max; i++)
+ {
+ var bucket = _buckets[i];
+
+ while (bucket != -1)
+ {
+ result[count++] = UnsafeUtilities.ReadArrayElement(_keys, bucket);
+ bucket = _next[bucket];
+ }
+ }
+
+ return result;
+ }
+
+ internal UnsafeArray GetValueArray(Allocator allocator)
+ where TValue : unmanaged
+ {
+ var result = new UnsafeArray(_count, allocator, AllocationType.UnInitialized);
+
+ for (int i = 0, count = 0, max = result.Count, capacity = _bucketCapacity; i < capacity && count < max; ++i)
+ {
+ var bucket = _buckets[i];
+
+ while (bucket != -1)
+ {
+ result[count++] = UnsafeUtilities.ReadArrayElement(_buffer, bucket);
+ bucket = _next[bucket];
+ }
+ }
+
+ return result;
+ }
+
+ public UnsafeArray> GetKeyValueArrays(Allocator allocator)
+ where TValue : unmanaged
+ {
+ var result = new UnsafeArray>(_count, allocator, AllocationType.UnInitialized);
+
+ for (int i = 0, count = 0, max = result.Count, capacity = _bucketCapacity; i < capacity && count < max; i++)
+ {
+ var bucket = _buckets[i];
+
+ while (bucket != -1)
+ {
+ result[count] = new(UnsafeUtilities.ReadArrayElement(_keys, bucket),
+ UnsafeUtilities.ReadArrayElement(_buffer, bucket));
+
+ count++;
+ bucket = _next[bucket];
+ }
+ }
+
+ return result;
+ }
+
+ public void Clear()
+ {
+ MemSet(_buckets, 0xff, (nuint)_bucketCapacity * sizeof(int));
+ MemSet(_next, 0xff, (nuint)_capacity * sizeof(int));
+
+ _count = 0;
+ _firstFreeIndex = -1;
+ _allocatedIndex = 0;
+ }
+
+ public void Dispose()
+ {
+ if (IsCreated)
+ {
+ Free(_buffer);
+
+ _buffer = null;
+ _keys = null;
+ _next = null;
+ _buckets = null;
+
+ _count = 0;
+ _capacity = 0;
+ _bucketCapacity = 0;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Misaki.HighPerformance.Unsafe/Helpers/MemoryUtilities.cs b/Misaki.HighPerformance.Unsafe/Helpers/MemoryUtilities.cs
index 39c9de0..4509fc0 100644
--- a/Misaki.HighPerformance.Unsafe/Helpers/MemoryUtilities.cs
+++ b/Misaki.HighPerformance.Unsafe/Helpers/MemoryUtilities.cs
@@ -13,6 +13,76 @@ public static unsafe class MemoryUtilities
public T data;
}
+ ///
+ /// Allocates a block of memory of the specified size in bytes.
+ ///
+ /// Specifies the number of bytes to allocate in memory.
+ /// Returns a pointer to the allocated memory block.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void* Malloc(nuint size)
+ {
+ return NativeMemory.Alloc(size);
+ }
+
+ ///
+ /// Allocates a block of memory with a specified size and alignment.
+ ///
+ /// Specifies the total number of bytes to allocate for the memory block.
+ /// Defines the required alignment for the allocated memory address.
+ /// Returns a pointer to the allocated memory block.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void* AlignedAlloc(nuint size, nuint alignment)
+ {
+ return NativeMemory.AlignedAlloc(size, alignment);
+ }
+
+ ///
+ /// Resizes a previously allocated memory block to a new size. It returns a pointer to the reallocated memory.
+ ///
+ /// The pointer to the memory block that needs to be resized.
+ /// The new size for the memory block after resizing.
+ /// A pointer to the reallocated memory block, or null if the operation fails.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void* Realloc(void* ptr, nuint size)
+ {
+ return NativeMemory.Realloc(ptr, size);
+ }
+
+ ///
+ /// Reallocates memory to a specified size with a given alignment. It returns a pointer to the newly allocated
+ /// memory.
+ ///
+ /// The pointer to the existing memory block that needs to be reallocated.
+ /// The new size for the memory allocation.
+ /// The required alignment for the new memory allocation.
+ /// A pointer to the reallocated memory block, or null if the allocation fails.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void* AlignedRealloc(void* ptr, nuint size, nuint alignment)
+ {
+ return NativeMemory.AlignedRealloc(ptr, size, alignment);
+ }
+
+ ///
+ /// Releases the allocated memory pointed to by the given pointer. This helps in managing memory usage effectively.
+ ///
+ /// The pointer to the memory block that needs to be freed.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Free(void* ptr)
+ {
+ NativeMemory.Free(ptr);
+ }
+
+ ///
+ /// Releases memory that was allocated with alignment requirements. It ensures proper deallocation of aligned memory
+ /// blocks.
+ ///
+ /// The pointer to the memory block that needs to be freed.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void AlignedFree(void* ptr)
+ {
+ NativeMemory.AlignedFree(ptr);
+ }
+
///
/// Clears a block of memory by setting it to zero. It initializes a specified number of bytes at a given memory
/// address.
@@ -32,7 +102,7 @@ public static unsafe class MemoryUtilities
/// The number of bytes to set to the specified value.
/// The byte value to which the memory block will be initialized.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void MemSet(void* ptr, nuint size, byte value)
+ public static void MemSet(void* ptr, byte value, nuint size)
{
NativeMemory.Fill(ptr, size, value);
}
diff --git a/Misaki.HighPerformance.Unsafe/Helpers/UnsafeCollectionExtensions.cs b/Misaki.HighPerformance.Unsafe/Helpers/UnsafeCollectionExtensions.cs
index 2a42eb3..0bc2fd4 100644
--- a/Misaki.HighPerformance.Unsafe/Helpers/UnsafeCollectionExtensions.cs
+++ b/Misaki.HighPerformance.Unsafe/Helpers/UnsafeCollectionExtensions.cs
@@ -2,6 +2,10 @@
namespace Misaki.HighPerformance.Unsafe.Helpers;
+///
+/// Provides extension methods for copying elements between unsafe collections and spans, converting collections to
+/// arrays or lists, and searching for values.
+///
public unsafe static class UnsafeCollectionExtensions
{
///
@@ -13,14 +17,37 @@ public unsafe static class UnsafeCollectionExtensions
/// Thrown when the sizes of the source collection and destination span do not match.
public static void CopyTo(this IUnsafeCollection source, Span destination) where T : unmanaged
{
- if (source.Size > destination.Length)
+ if (source.Count > destination.Length)
{
throw new ArgumentException("Source collection is larger than the destination span.");
}
fixed (T* ptr = destination)
{
- SystemUnsfae.CopyBlock(ptr, source.Buffer, (uint)(source.Size * sizeof(T)));
+ SystemUnsfae.CopyBlock(ptr, source.GetUnsafePtr(), (uint)(source.Count * sizeof(T)));
+ }
+ }
+
+ ///
+ /// Copies a range of elements from a source collection to a destination span, ensuring both are adequately sized.
+ ///
+ /// Specifies the type of elements being copied, which must be a value type.
+ /// The collection from which elements are copied.
+ /// The span where the elements will be copied to.
+ /// The starting index in the source collection for the copy operation.
+ /// The starting index in the destination span where the elements will be placed.
+ /// The number of elements to copy from the source to the destination.
+ /// Thrown when the specified range exceeds the bounds of the source collection or destination span.
+ public static void CopyTo(this IUnsafeCollection source, Span destination, int sourceIndex, int destinationIndex, int length) where T : unmanaged
+ {
+ if (sourceIndex + length > source.Count || destinationIndex + length > destination.Length)
+ {
+ throw new ArgumentException("Source collection or destination span is too small for the specified range.");
+ }
+
+ fixed (T* ptr = destination)
+ {
+ SystemUnsfae.CopyBlock(ptr + destinationIndex, (byte*)source.GetUnsafePtr() + (sourceIndex * sizeof(T)), (uint)(length * sizeof(T)));
}
}
@@ -33,14 +60,37 @@ public unsafe static class UnsafeCollectionExtensions
/// Thrown when the source span and destination collection have different sizes.
public static void CopyFrom(this IUnsafeCollection destination, Span source) where T : unmanaged
{
- if (destination.Size > source.Length)
+ if (destination.Count > source.Length)
{
throw new ArgumentException("Destination collection is larger than the source span.");
}
fixed (T* ptr = source)
{
- SystemUnsfae.CopyBlock(destination.Buffer, ptr, (uint)(source.Length * sizeof(T)));
+ SystemUnsfae.CopyBlock(destination.GetUnsafePtr(), ptr, (uint)(source.Length * sizeof(T)));
+ }
+ }
+
+ ///
+ /// Copies a specified range of elements from a source span to a destination collection.
+ ///
+ /// Represents the type of elements being copied, which must be unmanaged.
+ /// The collection where elements will be copied to.
+ /// The span containing the elements to be copied.
+ /// The starting index in the source span from which to begin copying.
+ /// The starting index in the destination collection where the elements will be placed.
+ /// The number of elements to copy from the source span to the destination collection.
+ /// Thrown when the specified range exceeds the bounds of the source span or destination collection.
+ public static void CopyFrom(this IUnsafeCollection destination, Span source, int sourceIndex, int destinationIndex, int length) where T : unmanaged
+ {
+ if (sourceIndex + length > source.Length || destinationIndex + length > destination.Count)
+ {
+ throw new ArgumentException("Source span or destination collection is too small for the specified range.");
+ }
+
+ fixed (T* ptr = source)
+ {
+ SystemUnsfae.CopyBlock((byte*)destination.GetUnsafePtr() + (destinationIndex * sizeof(T)), ptr + sourceIndex, (uint)(length * sizeof(T)));
}
}
@@ -52,10 +102,10 @@ public unsafe static class UnsafeCollectionExtensions
/// A new collection containing the elements from the UnsafeCollection.
public static T[] ToArray(this IUnsafeCollection source) where T : unmanaged
{
- var array = new T[source.Size];
+ var array = new T[source.Count];
fixed (T* ptr = array)
{
- SystemUnsfae.CopyBlock(ptr, source.Buffer, (uint)(source.Size * sizeof(T)));
+ SystemUnsfae.CopyBlock(ptr, source.GetUnsafePtr(), (uint)(source.Count * sizeof(T)));
}
return array;
@@ -69,10 +119,10 @@ public unsafe static class UnsafeCollectionExtensions
/// A list containing the elements from the specified unmanaged collection.
public static List ToList(this IUnsafeCollection source) where T : unmanaged
{
- var list = new List(source.Size);
+ var list = new List(source.Count);
fixed (T* ptr = list.ToArray())
{
- SystemUnsfae.CopyBlock(ptr, source.Buffer, (uint)(source.Size * sizeof(T)));
+ SystemUnsfae.CopyBlock(ptr, source.GetUnsafePtr(), (uint)(source.Count * sizeof(T)));
}
return list;
}
@@ -85,6 +135,39 @@ public unsafe static class UnsafeCollectionExtensions
/// A Span that provides a view over the elements of the UnsafeCollection.
public static Span AsSpan(this IUnsafeCollection source) where T : unmanaged
{
- return new(source.Buffer, source.Size);
+ return new(source.GetUnsafePtr(), source.Count);
+ }
+
+ ///
+ /// Finds the index of a specified value in a collection. Returns -1 if the value is not found.
+ ///
+ /// The type of elements in the collection, which must support equality comparison.
+ /// The collection to search for the specified value.
+ /// The value to locate within the collection.
+ /// Outputs the index of the found value or -1 if not found.
+ public static void IndexOf(this IUnsafeCollection source, T value, out int index) where T : unmanaged, IEquatable
+ {
+ for (var i = 0; i < source.Count; i++)
+ {
+ if (UnsafeUtilities.ReadArrayElement(source.GetUnsafePtr(), i).Equals(value))
+ {
+ index = i;
+ return;
+ }
+ }
+ index = -1;
+ }
+
+ ///
+ /// Checks if a specified value exists within an unsafe collection of unmanaged types.
+ ///
+ /// Represents a type that is unmanaged and supports equality comparison.
+ /// The collection being searched for the specified value.
+ /// The value being searched for within the collection.
+ /// Returns true if the value is found; otherwise, returns false.
+ public static bool Conations(this IUnsafeCollection source, T value) where T : unmanaged, IEquatable
+ {
+ source.IndexOf(value, out var index);
+ return index != -1;
}
}
\ No newline at end of file
diff --git a/Misaki.HighPerformance.Unsafe/Helpers/UnsafeUtilities.cs b/Misaki.HighPerformance.Unsafe/Helpers/UnsafeUtilities.cs
index 77d21bc..48411c6 100644
--- a/Misaki.HighPerformance.Unsafe/Helpers/UnsafeUtilities.cs
+++ b/Misaki.HighPerformance.Unsafe/Helpers/UnsafeUtilities.cs
@@ -1,4 +1,5 @@
-using System.Runtime.CompilerServices;
+using Misaki.HighPerformance.Unsafe.Collections;
+using System.Runtime.CompilerServices;
namespace Misaki.HighPerformance.Unsafe.Helpers;
@@ -79,4 +80,18 @@ public static unsafe class UnsafeUtilities
{
*ReadArrayElementUnsafe(ptr, index) = value;
}
+
+ ///
+ /// Converts an UnsafeArray of one unmanaged type to another unmanaged type without copying the elements.
+ ///
+ /// Represents the type of elements in the input array.
+ /// Represents the type of elements in the output array.
+ /// The input array containing elements of the specified input type.
+ /// An UnsafeArray containing elements of the specified output type.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static UnsafeArray CastArray(UnsafeArray array)
+ where TIn : unmanaged where TOut : unmanaged
+ {
+ return new UnsafeArray(array.GetUnsafePtr(), array.Count * sizeof(TIn) / sizeof(TOut));
+ }
}
\ No newline at end of file
diff --git a/Misaki.HighPerformance.Unsafe/Misaki.HighPerformance.Unsafe.csproj b/Misaki.HighPerformance.Unsafe/Misaki.HighPerformance.Unsafe.csproj
index 9373d81..42b3bd0 100644
--- a/Misaki.HighPerformance.Unsafe/Misaki.HighPerformance.Unsafe.csproj
+++ b/Misaki.HighPerformance.Unsafe/Misaki.HighPerformance.Unsafe.csproj
@@ -15,4 +15,8 @@
True
+
+
+
+
diff --git a/Misaki.HighPerformance.sln b/Misaki.HighPerformance.sln
index 1ed6d0a..a1d7bbd 100644
--- a/Misaki.HighPerformance.sln
+++ b/Misaki.HighPerformance.sln
@@ -9,6 +9,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Misaki.HighPerformance.Unsa
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Misaki.HighPerformance.Test", "Misaki.HighPerformance.Test\Misaki.HighPerformance.Test.csproj", "{90EFF5B8-22CD-4B6A-83AB-48E0E97610EA}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Misaki.HighPerformance.Mathematics", "Misaki.HighPerformance.Math\Misaki.HighPerformance.Mathematics.csproj", "{861F9574-2063-4B54-8D6A-AA32B26B6583}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -27,6 +29,10 @@ Global
{90EFF5B8-22CD-4B6A-83AB-48E0E97610EA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{90EFF5B8-22CD-4B6A-83AB-48E0E97610EA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{90EFF5B8-22CD-4B6A-83AB-48E0E97610EA}.Release|Any CPU.Build.0 = Release|Any CPU
+ {861F9574-2063-4B54-8D6A-AA32B26B6583}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {861F9574-2063-4B54-8D6A-AA32B26B6583}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {861F9574-2063-4B54-8D6A-AA32B26B6583}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {861F9574-2063-4B54-8D6A-AA32B26B6583}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/Misaki.HighPerformance/Buffer/ObjectPool.cs b/Misaki.HighPerformance/Buffer/ObjectPool.cs
index fd0d2b2..289a191 100644
--- a/Misaki.HighPerformance/Buffer/ObjectPool.cs
+++ b/Misaki.HighPerformance/Buffer/ObjectPool.cs
@@ -8,9 +8,6 @@ namespace Misaki.HighPerformance.Buffer
private readonly Func _factory;
private readonly ConcurrentQueue _objects = new();
- private readonly bool _autoCleanup;
- private readonly int _autoCleanupInterval;
-
private bool _disposed;
public uint InitialSize
@@ -24,13 +21,10 @@ namespace Misaki.HighPerformance.Buffer
private set;
}
- public ObjectPool(Func factory, uint initialSize = uint.MinValue, uint maxSize = uint.MaxValue, bool autoCleanup = false, int autoCleanupInterval = 1000 * 60 * 5)
+ public ObjectPool(Func factory, uint initialSize = uint.MinValue, uint maxSize = uint.MaxValue)
{
_factory = factory;
- _autoCleanup = autoCleanup;
- _autoCleanupInterval = autoCleanupInterval;
-
InitialSize = initialSize;
MaxSize = maxSize;
@@ -41,40 +35,11 @@ namespace Misaki.HighPerformance.Buffer
_objects.Enqueue(_factory());
}
}
-
- SetupAutoCleanup();
}
- private void PoolCleanup()
+ ~ObjectPool()
{
- foreach (var obj in _objects)
- {
- if (obj is IDisposable disposable)
- {
- disposable.Dispose();
- }
- }
-
- _objects.Clear();
-
- GC.Collect();
- }
-
- private void SetupAutoCleanup()
- {
- if (!_autoCleanup)
- {
- return;
- }
-
- Task.Run(async () =>
- {
- while (true)
- {
- await Task.Delay(_autoCleanupInterval);
- PoolCleanup();
- }
- });
+ Dispose();
}
public bool TryRent([MaybeNullWhen(false)] out T obj)
@@ -100,11 +65,32 @@ namespace Misaki.HighPerformance.Buffer
}
}
+ public void Reset()
+ {
+ foreach (var obj in _objects)
+ {
+ if (obj is IDisposable disposable)
+ {
+ disposable.Dispose();
+ }
+ }
+
+ _objects.Clear();
+ GC.Collect();
+ }
+
public void Dispose()
{
- PoolCleanup();
+ if (_disposed)
+ {
+ return;
+ }
+
+ Reset();
_disposed = true;
+
+ GC.SuppressFinalize(this);
}
}
}
diff --git a/Misaki.HighPerformance/Jobs/JobExtensions.cs b/Misaki.HighPerformance/Jobs/JobExtensions.cs
index 7afbd7a..9e92e79 100644
--- a/Misaki.HighPerformance/Jobs/JobExtensions.cs
+++ b/Misaki.HighPerformance/Jobs/JobExtensions.cs
@@ -2,7 +2,8 @@
public static class JobExtensions
{
- public static JobHandle Schedule(this T job, bool preferLocal = false) where T : struct, IJob
+ public static JobHandle Schedule(this T job, bool preferLocal = false)
+ where T : struct, IJob
{
var handle = new JobHandle(1);
var worker = new JobWorker(job, handle);
@@ -11,7 +12,8 @@ public static class JobExtensions
return handle;
}
- public static JobHandle Schedule(this T job, ReadOnlySpan dependencies, bool preferLocal = false) where T : struct, IJob
+ public static JobHandle Schedule(this T job, ReadOnlySpan dependencies, bool preferLocal = false)
+ where T : struct, IJob
{
foreach (var dependency in dependencies)
{
@@ -21,7 +23,8 @@ public static class JobExtensions
return job.Schedule(preferLocal);
}
- public static JobHandle Schedule