Enhance mathematical capabilities and job system

Added new numeric types for unsigned integers, including uint2, uint3, and uint4, along with their matrix types.
Added a new `quaternion` struct with constructors and methods for creating and manipulating quaternions.
Added methods for projecting and reflecting vectors, enhancing geometric operations.
Added utility functions for generating orthonormal bases and changing vector signs.
Added comprehensive unit tests for new mathematical functions and quaternion operations.
Added a high-performance job scheduling system with job management features and worker thread management.
Added new structs for job execution, allowing efficient job scheduling and execution.
Added utility functions for job execution, including methods for obtaining unique job IDs.

Changed access modifiers and property definitions in several files for improved clarity and maintainability.
Changed property definitions and method implementations in `ImageInfo.cs`, `ImageResult.cs`, and `ImageResultFloat.cs` for better readability.
Changed memory management functions in `CRuntime.cs` and improved memory allocation tracking in `MemoryStats.cs`.
Changed the project file to include references to necessary projects and enable unsafe code blocks.

Removed the `WorkerThreadPool.cs` file, integrating worker thread management directly into the `JobScheduler`.
Removed the `float4` struct and its associated methods and properties, transitioning to a new code generation strategy.
Removed the `float4.tt` template and other related files, indicating a shift in code generation approach.
Removed the `Vectorize.cs` file, indicating a change in how vector operations are handled.

Updated the `.gitignore` file to include IDE-specific settings.
Updated various XML files to define project components and structure.
Updated the `AllocationManager.cs` to improve memory allocation management and introduce new strategies.
Updated the `UnsafeArray.cs`, `UnsafeHashMap.cs`, and `UnsafeList.cs` to enhance performance and safety in unsafe contexts.
Updated error handling and function pointer management in `MemoryLeakException.cs` and `FunctionPointer.cs`.
Updated the `AssemblyInfo.cs` file to include global using directives for better code organization.
This commit is contained in:
2025-09-06 12:07:02 +09:00
parent eeff3313b5
commit a2a760594e
114 changed files with 20826 additions and 7217 deletions

View File

@@ -1,2 +1,6 @@
global using static Misaki.HighPerformance.LowLevel.Helpers.MemoryUtilities;
global using SystemUnsfae = System.Runtime.CompilerServices.Unsafe;
global using unsafe AllocFunc = delegate* unmanaged<void*, nuint, nuint, Misaki.HighPerformance.LowLevel.Buffer.AllocationOption, void*>;
global using unsafe FreeFunc = delegate* unmanaged<void*, void*, void>;
global using unsafe ReallocFunc = delegate* unmanaged<void*, void*, nuint, nuint, void*>;

View File

@@ -1,190 +1,213 @@
using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Contracts;
using Misaki.HighPerformance.LowLevel.Contracts;
using Misaki.HighPerformance.LowLevel.Exceptions;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Misaki.HighPerformance.LowLevel.Buffer;
using unsafe FreeFunc = delegate* unmanaged<void*, void*, void>;
public unsafe struct ArenaAllocator : IAllocator, IDisposable
/// <summary>
/// Holds information about a memory allocation.
/// </summary>
public readonly unsafe struct AllocationInfo
{
private DynamicArena _arena;
private AllocationHandle _handle;
public readonly ref AllocationHandle Handle => ref Unsafe.AsRef(in _handle);
public ArenaAllocator(uint initialSize)
/// <summary>
/// Get the size of the allocation in bytes.
/// </summary>
public nuint Size
{
_arena = new DynamicArena(initialSize);
_handle = new AllocationHandle(Unsafe.AsPointer(ref this), &Allocate, &Reallocate, &FreeBlock);
get; init;
}
[UnmanagedCallersOnly]
private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption)
/// <summary>
/// Get the allocator used for the allocation.
/// </summary>
public void* Allocator
{
var selfPtr = (ArenaAllocator*)instance;
var ptr = selfPtr->_arena.Allocate(size, alignment, allocationOption);
return ptr;
get; init;
}
[UnmanagedCallersOnly]
private static void* Reallocate(void* instance, void* ptr, nuint size, nuint alignment)
/// <summary>
/// Get the function pointer used to free the allocated memory.
/// </summary>
public FreeFunc FreeHandler
{
var selfPtr = (ArenaAllocator*)instance;
var newPtr = selfPtr->_arena.Allocate(size, alignment, AllocationOption.None);
MemCpy(newPtr, ptr, size);
// NOTE: We do not free the old pointer here, as it is managed by the arena.
return newPtr;
get; init;
}
[UnmanagedCallersOnly]
private static void FreeBlock(void* instance, void* ptr)
{
}
public void Reset()
{
_arena.Reset();
}
public void Dispose()
{
_arena.Dispose();
}
}
public unsafe struct DefaultAllocator : IAllocator
{
private AllocationHandle _handle;
public ref AllocationHandle Handle => ref Unsafe.AsRef(in _handle);
public DefaultAllocator()
{
_handle = new AllocationHandle(Unsafe.AsPointer(ref this), &Allocate, &Reallocate, &FreeBlock);
}
[UnmanagedCallersOnly]
private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption)
{
var ptr = AlignedAlloc(size, alignment);
AllocationManager.TrackAllocation(ptr, size, instance, &FreeBlock);
if (allocationOption.HasFlag(AllocationOption.Clear))
{
MemClear(ptr, size);
}
return ptr;
}
[UnmanagedCallersOnly]
private static void* Reallocate(void* instance, void* ptr, nuint size, nuint alignment)
{
var newPtr = AlignedRealloc(ptr, size, alignment);
AllocationManager.UpdateAllocation(ptr, newPtr, size, instance, &FreeBlock);
return newPtr;
}
[UnmanagedCallersOnly]
private static void FreeBlock(void* instance, void* ptr)
{
AlignedFree(ptr);
AllocationManager.RemoveAllocation(ptr);
}
}
public unsafe struct EmptyAllocator : IAllocator
{
private AllocationHandle _handle;
public ref AllocationHandle Handle => ref Unsafe.AsRef(in _handle);
public EmptyAllocator()
{
_handle = new AllocationHandle(Unsafe.AsPointer(ref this), &Allocate, &Reallocate, &FreeBlock);
}
[UnmanagedCallersOnly]
private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption)
{
return null;
}
[UnmanagedCallersOnly]
private static void* Reallocate(void* instance, void* ptr, nuint size, nuint alignment)
{
return ptr;
}
[UnmanagedCallersOnly]
private static void FreeBlock(void* instance, void* ptr)
/// <summary>
/// Get the stack trace at the time of allocation for debugging purposes.
/// </summary>
public StackTrace StackTrace
{
get; init;
}
}
/// <summary>
/// Provides memory allocation management for native memory allocations, with support for tracking,
/// debugging, and custom allocation strategies.
/// </summary>
public static unsafe class AllocationManager
{
public readonly struct AllocationInfo
private unsafe struct ArenaAllocator : IAllocator, IDisposable
{
public nuint Size
private DynamicArena _arena;
private AllocationHandle _handle;
public readonly ref AllocationHandle Handle => ref Unsafe.AsRef(in _handle);
public void Init(uint initialSize)
{
get; init;
_arena = new DynamicArena(initialSize);
_handle = new AllocationHandle(Unsafe.AsPointer(ref this), &Allocate, &Reallocate, &FreeBlock);
}
public void* Allocator
[UnmanagedCallersOnly]
private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption)
{
get; init;
var selfPtr = (ArenaAllocator*)instance;
var ptr = selfPtr->_arena.Allocate(size, alignment, allocationOption);
return ptr;
}
public FreeFunc FreeHandler
[UnmanagedCallersOnly]
private static void* Reallocate(void* instance, void* ptr, nuint size, nuint alignment)
{
get; init;
var selfPtr = (ArenaAllocator*)instance;
var newPtr = selfPtr->_arena.Allocate(size, alignment, AllocationOption.None);
MemCpy(newPtr, ptr, size);
// We do not free the old pointer here, as it is managed by the arena.
return newPtr;
}
public StackTrace StackTrace
[UnmanagedCallersOnly]
private static void FreeBlock(void* instance, void* ptr)
{
get; init;
// The arena allocator does not free individual blocks, as it manages memory in chunks.
}
public void Reset()
{
_arena.Reset();
}
public void Dispose()
{
_arena.Dispose();
}
}
private unsafe struct HeapAllocator : IAllocator
{
private AllocationHandle _handle;
public readonly ref AllocationHandle Handle => ref Unsafe.AsRef(in _handle);
public void Init()
{
_handle = new AllocationHandle(Unsafe.AsPointer(ref this), &Allocate, &Reallocate, &FreeBlock);
}
[UnmanagedCallersOnly]
private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption)
{
var ptr = AlignedAlloc(size, alignment);
if (!allocationOption.HasFlag(AllocationOption.UnTracked))
{
TrackAllocation(ptr, size, instance, &FreeBlock);
}
if (allocationOption.HasFlag(AllocationOption.Clear))
{
MemClear(ptr, size);
}
return ptr;
}
[UnmanagedCallersOnly]
private static void* Reallocate(void* instance, void* ptr, nuint size, nuint alignment)
{
var newPtr = AlignedRealloc(ptr, size, alignment);
UpdateAllocation(ptr, newPtr, size, instance, &FreeBlock);
return newPtr;
}
[UnmanagedCallersOnly]
private static void FreeBlock(void* instance, void* ptr)
{
AlignedFree(ptr);
UntrackAllocation(ptr);
}
}
private const uint _DEFAULT_ARENA_SIZE = 512 * 1024;
private static ArenaAllocator s_arenaAllocator = new(_DEFAULT_ARENA_SIZE);
private static DefaultAllocator s_persistentAllocator = new();
private static EmptyAllocator s_emptyAllocator = new();
private static readonly ArenaAllocator* s_arenaAllocator;
private static readonly HeapAllocator* s_persistentAllocator;
private static bool s_debugLayer;
private static Dictionary<nint, AllocationInfo>? s_allocated;
private static ConcurrentDictionary<nint, AllocationInfo>? s_allocated;
public static ArenaAllocator TempAllocator => s_arenaAllocator;
public static DefaultAllocator PersistentAllocator => s_persistentAllocator;
public static EmptyAllocator EmptyAllocator => s_emptyAllocator;
/// <summary>
/// Gets a reference to the allocation handle for temporary allocations.
/// </summary>
public static ref AllocationHandle TempHandle => ref s_arenaAllocator->Handle;
/// <summary>
/// Gets a reference to the persistent allocation handle.
/// </summary>
public static ref AllocationHandle PersistentHandle => ref s_persistentAllocator->Handle;
static AllocationManager()
{
s_arenaAllocator = (ArenaAllocator*)NativeMemory.Alloc((nuint)sizeof(ArenaAllocator));
s_persistentAllocator = (HeapAllocator*)NativeMemory.Alloc((nuint)sizeof(HeapAllocator));
s_arenaAllocator->Init(_DEFAULT_ARENA_SIZE);
s_persistentAllocator->Init();
}
/// <summary>
/// Enables the debug layer, allowing additional diagnostic information to be collected.
/// </summary>
public static void EnableDebugLayer()
{
s_debugLayer = true;
s_allocated ??= new Dictionary<nint, AllocationInfo>(64);
s_allocated ??= new(-1, 64);
}
/// <summary>
/// Gets a reference to the allocation handle for the specified allocator type.
/// </summary>
/// <param name="allocator">The allocator type for which to retrieve the allocation handle.</param>
/// <returns>A reference to the allocation handle associated with the specified allocator type.</returns>
/// <exception cref="ArgumentException"></exception>
public static ref AllocationHandle GetAllocationHandle(Allocator allocator)
{
switch (allocator)
{
case Allocator.Temp:
return ref s_arenaAllocator.Handle;
return ref TempHandle;
case Allocator.Persistent:
return ref s_persistentAllocator.Handle;
case Allocator.External:
return ref s_emptyAllocator.Handle;
return ref PersistentHandle;
default:
throw new ArgumentException("Invalid allocator type.", nameof(allocator));
throw new ArgumentException("Target allocator type does not support custom allocation.", nameof(allocator));
}
}
/// <summary>
/// Tracks a memory allocation in the allocation manager.
/// </summary>
/// <param name="ptr">The pointer to the allocated memory.</param>
/// <param name="allocationSize">The size of the allocation in bytes.</param>
/// <param name="allocator">The allocator used for the allocation.</param>
/// <param name="freeFunc">The function pointer used to free the allocated memory.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void TrackAllocation(void* ptr, nuint allocationSize, void* allocator, FreeFunc freeFunc)
{
@@ -202,6 +225,14 @@ public static unsafe class AllocationManager
};
}
/// <summary>
/// Updates the allocation tracking information by replacing the old pointer with a new pointer.
/// </summary>
/// <param name="oldPtr">A pointer to the previously allocated memory. If <paramref name="oldPtr"/> is not tracked, the method does nothing.</param>
/// <param name="newPtr">A pointer to the newly allocated memory. This pointer will replace <paramref name="oldPtr"/> in the allocation tracking.</param>
/// <param name="allocationSize">The size, in bytes, of the new allocation.</param>
/// <param name="allocator">A pointer to the allocator responsible for the new allocation.</param>
/// <param name="freeFunc">A delegate or function pointer used to free the memory associated with the allocation.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void UpdateAllocation(void* oldPtr, void* newPtr, nuint allocationSize, void* allocator, FreeFunc freeFunc)
{
@@ -210,6 +241,7 @@ public static unsafe class AllocationManager
return;
}
// If we don't find the allocation info, that means the oldPtr was not tracked
if (s_allocated.Remove((nint)oldPtr, out var info))
{
s_allocated[(nint)newPtr] = new AllocationInfo
@@ -220,20 +252,20 @@ public static unsafe class AllocationManager
StackTrace = info.StackTrace
};
}
else
{
TrackAllocation(newPtr, allocationSize, allocator, freeFunc);
}
}
public static void RemoveAllocation(void* ptr)
/// <summary>
/// Removes the specified memory allocation from the tracking system.
/// </summary>
/// <param name="ptr">A pointer to the memory allocation to untrack.</param>
public static void UntrackAllocation(void* ptr)
{
if (s_allocated == null)
{
return;
}
s_allocated.Remove((nint)ptr);
s_allocated.Remove((nint)ptr, out _);
}
/// <summary>
@@ -241,8 +273,6 @@ public static unsafe class AllocationManager
/// </summary>
public static void Dispose()
{
s_arenaAllocator.Dispose();
if (s_allocated != null)
{
nuint unfreeBytes = 0u;
@@ -259,5 +289,16 @@ public static unsafe class AllocationManager
s_allocated.Clear();
}
if (s_arenaAllocator != null)
{
s_arenaAllocator->Dispose();
NativeMemory.Free(s_arenaAllocator);
}
if (s_persistentAllocator != null)
{
NativeMemory.Free(s_persistentAllocator);
}
}
}

View File

@@ -1,4 +1,4 @@
namespace Misaki.HighPerformance.LowLevel.Collections;
namespace Misaki.HighPerformance.LowLevel.Buffer;
[Flags]
public enum AllocationOption : byte
@@ -9,8 +9,7 @@ public enum AllocationOption : byte
/// </summary>
Clear = 1 << 0,
/// <summary>
/// Allocator for untracked memory. It always allocates memory without using the allocation manager.
/// Always free it manually even if you use the <see cref="Allocator.Temp"/> allocator.
/// Allocator for untracked memory.
/// </summary>
/// <remarks>
/// Use this option carefully, as the allocation manager will not track the memory.
@@ -30,9 +29,5 @@ public enum Allocator : byte
/// <summary>
/// Allocator for persistent allocations. Allocations are not cleared after use.
/// </summary>
Persistent,
/// <summary>
/// Allocator for external memory. Allocations are not cleared after use.
/// </summary>
External
Persistent
}

View File

@@ -1,18 +1,20 @@
using Misaki.HighPerformance.LowLevel.Collections;
using System.Runtime.InteropServices;
namespace Misaki.HighPerformance.LowLevel.Buffer;
/// <summary>
/// A memory management structure that allocates and resets memory blocks with specified alignment.
/// </summary>
[StructLayout(LayoutKind.Explicit, Size = 64)] // Cache line aligned to prevent false sharing
public unsafe struct Arena : IDisposable
{
[FieldOffset(0)]
private byte* _buffer;
[FieldOffset(8)]
private nuint _size;
[FieldOffset(16)]
private nuint _offset;
private bool _disposed;
public Arena(nuint size)
{
Initialize(size);
@@ -42,20 +44,36 @@ public unsafe struct Arena : IDisposable
/// <exception cref="ObjectDisposedException">Thrown if the arena has been disposed.</exception>
public void* Allocate(nuint size, nuint alignment, AllocationOption allocationOption)
{
if (_disposed)
if (_buffer == null)
{
throw new ObjectDisposedException(nameof(DynamicArena));
}
var offset = _offset + alignment - 1 & ~(alignment - 1);
if (offset + size > _size)
//var offset = _offset + alignment - 1 & ~(alignment - 1);
//if (offset + size > _size)
//{
// return null;
//}
//_offset = offset + size;
//var ptr = _buffer + offset;
nuint currentOffset, newOffset, alignedOffset;
do
{
return null;
}
currentOffset = _offset;
alignedOffset = (currentOffset + alignment - 1) & ~(alignment - 1);
newOffset = alignedOffset + size;
_offset = offset + size;
var ptr = _buffer + offset;
if (newOffset > _size)
{
return null;
}
} while (Interlocked.CompareExchange(ref _offset, newOffset, currentOffset) != currentOffset);
var ptr = _buffer + alignedOffset;
if (allocationOption.HasFlag(AllocationOption.Clear))
{
MemClear(ptr, size);
@@ -71,7 +89,7 @@ public unsafe struct Arena : IDisposable
/// <exception cref="ObjectDisposedException">Thrown if the arena has been disposed.</exception>
public void Reset()
{
if (_disposed)
if (_buffer == null)
{
throw new ObjectDisposedException(nameof(DynamicArena));
}
@@ -81,12 +99,15 @@ public unsafe struct Arena : IDisposable
public void Dispose()
{
if (_buffer == null)
{
return;
}
Free(_buffer);
_buffer = null;
_size = 0;
_offset = 0;
_disposed = true;
}
}

View File

@@ -1,4 +1,4 @@
using Misaki.HighPerformance.LowLevel.Collections;
using System.Runtime.InteropServices;
namespace Misaki.HighPerformance.LowLevel.Buffer;
@@ -6,29 +6,33 @@ namespace Misaki.HighPerformance.LowLevel.Buffer;
/// A dynamic memory management structure that automatically grows by creating linked arenas
/// when more space is needed.
/// </summary>
[StructLayout(LayoutKind.Explicit, Size = 128)]
public unsafe struct DynamicArena : IDisposable
{
[StructLayout(LayoutKind.Sequential)]
private struct ArenaNode
{
public Arena arena;
public ArenaNode* next;
}
[FieldOffset(0)]
private ArenaNode* _root;
[FieldOffset(8)]
private ArenaNode* _current;
[FieldOffset(16)]
private uint _initialSize;
[FieldOffset(20)]
private volatile int _nodeCreationLock;
/// <summary>
/// Initializes a new instance of DynamicArena with the specified initial size.
/// </summary>
/// <param name="initialSize">The initial size in bytes for the first arena block.</param>
public DynamicArena(uint initialSize)
{
_initialSize = initialSize;
_root = (ArenaNode*)Malloc(SizeOf<ArenaNode>());
_root->arena = new Arena(initialSize);
_root->next = null;
_current = _root;
Initialize(initialSize);
}
public void Initialize(uint initialSize)
@@ -45,23 +49,63 @@ public unsafe struct DynamicArena : IDisposable
_current = _root;
}
private bool CreateNewNode(nuint size)
private bool TryCreateNewNode(nuint size)
{
var newNode = (ArenaNode*)Malloc(SizeOf<ArenaNode>());
while (Interlocked.CompareExchange(ref _nodeCreationLock, 1, 0) != 0)
{
Thread.SpinWait(1);
}
try
{
newNode->arena = new Arena(size);
newNode->next = null;
var current = _current;
if (current->next != null)
{
// Another thread created a node while we were waiting
_current = current->next;
return true;
}
_current->next = newNode;
_current = newNode;
return true;
var newNode = (ArenaNode*)Malloc(SizeOf<ArenaNode>());
try
{
newNode->arena = new Arena(size);
newNode->next = null;
// Atomically link the new node
current->next = newNode;
// Update current pointer
_current = newNode;
return true;
}
catch
{
Free(newNode);
return false;
}
}
catch
finally
{
Free(newNode);
return false;
// Release the spinlock
Interlocked.Exchange(ref _nodeCreationLock, 0);
}
//var newNode = (ArenaNode*)Malloc(SizeOf<ArenaNode>());
//try
//{
// newNode->arena = new Arena(size);
// newNode->next = null;
// _current->next = newNode;
// _current = newNode;
// return true;
//}
//catch
//{
// Free(newNode);
// return false;
//}
}
/// <summary>
@@ -89,7 +133,7 @@ public unsafe struct DynamicArena : IDisposable
return result;
}
if (current->next == null && !CreateNewNode(Math.Max(size, _initialSize)))
if (current->next == null && !TryCreateNewNode(Math.Max(size, _initialSize)))
{
return null;
}

View File

@@ -86,7 +86,7 @@ public unsafe struct FixedStackString32
fixed (byte* inputPtr = input)
fixed (byte* bufferPtr = _buffer)
{
SystemUnsfae.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
}
}
@@ -201,7 +201,7 @@ public unsafe struct FixedStackString64
fixed (byte* inputPtr = input)
fixed (byte* bufferPtr = _buffer)
{
SystemUnsfae.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
}
}
@@ -316,7 +316,7 @@ public unsafe struct FixedStackString128
fixed (byte* inputPtr = input)
fixed (byte* bufferPtr = _buffer)
{
SystemUnsfae.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
}
}
@@ -431,7 +431,7 @@ public unsafe struct FixedStackString256
fixed (byte* inputPtr = input)
fixed (byte* bufferPtr = _buffer)
{
SystemUnsfae.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
}
}
@@ -546,7 +546,7 @@ public unsafe struct FixedStackString512
fixed (byte* inputPtr = input)
fixed (byte* bufferPtr = _buffer)
{
SystemUnsfae.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
}
}
@@ -661,7 +661,7 @@ public unsafe struct FixedStackString1024
fixed (byte* inputPtr = input)
fixed (byte* bufferPtr = _buffer)
{
SystemUnsfae.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
}
}
@@ -776,7 +776,7 @@ public unsafe struct FixedStackString2048
fixed (byte* inputPtr = input)
fixed (byte* bufferPtr = _buffer)
{
SystemUnsfae.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
}
}
@@ -891,7 +891,7 @@ public unsafe struct FixedStackString4096
fixed (byte* inputPtr = input)
fixed (byte* bufferPtr = _buffer)
{
SystemUnsfae.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
}
}

View File

@@ -93,7 +93,7 @@ public unsafe struct FixedStackString<#= i #>
fixed (byte* inputPtr = input)
fixed (byte* bufferPtr = _buffer)
{
SystemUnsfae.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
}
}

View File

@@ -77,7 +77,7 @@ public unsafe struct FixedString32 : IDisposable
fixed (byte* inputPtr = input)
{
SystemUnsfae.CopyBlockUnaligned(_buffer, inputPtr, _length);
Unsafe.CopyBlockUnaligned(_buffer, inputPtr, _length);
}
}
@@ -97,7 +97,7 @@ public unsafe struct FixedString32 : IDisposable
{
return _buffer;
}
public override string ToString()
{
return Value;
@@ -188,7 +188,7 @@ public unsafe struct FixedString64 : IDisposable
fixed (byte* inputPtr = input)
{
SystemUnsfae.CopyBlockUnaligned(_buffer, inputPtr, _length);
Unsafe.CopyBlockUnaligned(_buffer, inputPtr, _length);
}
}
@@ -208,7 +208,7 @@ public unsafe struct FixedString64 : IDisposable
{
return _buffer;
}
public override string ToString()
{
return Value;
@@ -299,7 +299,7 @@ public unsafe struct FixedString128 : IDisposable
fixed (byte* inputPtr = input)
{
SystemUnsfae.CopyBlockUnaligned(_buffer, inputPtr, _length);
Unsafe.CopyBlockUnaligned(_buffer, inputPtr, _length);
}
}
@@ -319,7 +319,7 @@ public unsafe struct FixedString128 : IDisposable
{
return _buffer;
}
public override string ToString()
{
return Value;
@@ -410,7 +410,7 @@ public unsafe struct FixedString256 : IDisposable
fixed (byte* inputPtr = input)
{
SystemUnsfae.CopyBlockUnaligned(_buffer, inputPtr, _length);
Unsafe.CopyBlockUnaligned(_buffer, inputPtr, _length);
}
}
@@ -430,7 +430,7 @@ public unsafe struct FixedString256 : IDisposable
{
return _buffer;
}
public override string ToString()
{
return Value;
@@ -521,7 +521,7 @@ public unsafe struct FixedString512 : IDisposable
fixed (byte* inputPtr = input)
{
SystemUnsfae.CopyBlockUnaligned(_buffer, inputPtr, _length);
Unsafe.CopyBlockUnaligned(_buffer, inputPtr, _length);
}
}
@@ -541,7 +541,7 @@ public unsafe struct FixedString512 : IDisposable
{
return _buffer;
}
public override string ToString()
{
return Value;
@@ -632,7 +632,7 @@ public unsafe struct FixedString1024 : IDisposable
fixed (byte* inputPtr = input)
{
SystemUnsfae.CopyBlockUnaligned(_buffer, inputPtr, _length);
Unsafe.CopyBlockUnaligned(_buffer, inputPtr, _length);
}
}
@@ -652,7 +652,7 @@ public unsafe struct FixedString1024 : IDisposable
{
return _buffer;
}
public override string ToString()
{
return Value;
@@ -743,7 +743,7 @@ public unsafe struct FixedString2048 : IDisposable
fixed (byte* inputPtr = input)
{
SystemUnsfae.CopyBlockUnaligned(_buffer, inputPtr, _length);
Unsafe.CopyBlockUnaligned(_buffer, inputPtr, _length);
}
}
@@ -763,7 +763,7 @@ public unsafe struct FixedString2048 : IDisposable
{
return _buffer;
}
public override string ToString()
{
return Value;
@@ -854,7 +854,7 @@ public unsafe struct FixedString4096 : IDisposable
fixed (byte* inputPtr = input)
{
SystemUnsfae.CopyBlockUnaligned(_buffer, inputPtr, _length);
Unsafe.CopyBlockUnaligned(_buffer, inputPtr, _length);
}
}
@@ -874,7 +874,7 @@ public unsafe struct FixedString4096 : IDisposable
{
return _buffer;
}
public override string ToString()
{
return Value;

View File

@@ -84,7 +84,7 @@ public unsafe struct FixedString<#= i #> : IDisposable
fixed (byte* inputPtr = input)
{
SystemUnsfae.CopyBlockUnaligned(_buffer, inputPtr, _length);
Unsafe.CopyBlockUnaligned(_buffer, inputPtr, _length);
}
}

View File

@@ -0,0 +1,479 @@
using Misaki.HighPerformance.LowLevel.Helpers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Misaki.HighPerformance.LowLevel.Buffer;
/// <summary>
/// A lock-free, thread-safe variable-size allocator that manages memory blocks of different sizes.
/// Optimized for high-performance scenarios with frequent allocations and deallocations.
///
/// Example usage:
/// <code>
/// // Create a free list with multiple size buckets
/// var freeList = new FreeList();
///
/// // Allocate a 70-byte block
/// var block = freeList.Allocate(70);
/// if (block.IsValid)
/// {
/// // Use the memory block...
///
/// // Free the block when done
/// freeList.Free(block);
/// }
///
/// // Dispose when finished
/// freeList.Dispose();
/// </code>
/// </summary>
[StructLayout(LayoutKind.Explicit, Size = 256)] // Cache line aligned to prevent false sharing
public unsafe struct FreeList : IDisposable
{
/// <summary>
/// Node structure for the lock-free free list with size information.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
private struct FreeNode
{
public FreeNode* next;
public nuint size;
}
/// <summary>
/// Memory chunk that contains variable-size blocks.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
private struct MemoryChunk
{
public MemoryChunk* next;
public byte* memory;
public nuint size;
public nuint used; // Amount of memory used in this chunk
}
/// <summary>
/// Size bucket for different allocation sizes.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
private struct SizeBucket
{
public nint freeHead; // Free list head for this size
public nuint blockSize; // Fixed size for this bucket
public long freeCount; // Number of free blocks
}
private const int _MAX_BUCKETS = 16; // Number of size buckets
private const nuint _MIN_BLOCK_SIZE = 16; // Minimum block size
private const nuint _DEFAULT_CHUNK_SIZE = 64 * 1024; // 64KB chunks
[FieldOffset(0)]
private fixed byte _buckets[_MAX_BUCKETS * 32]; // SizeBucket array (32 bytes per bucket)
[FieldOffset(512)]
private DynamicArena _chunkArena; // 128
[FieldOffset(640)]
private MemoryChunk* _chunks; // 8
[FieldOffset(648)]
private nuint _chunkSize; // 8
[FieldOffset(656)]
private nuint _alignment; // 8
[FieldOffset(664)]
private long _totalAllocatedBytes; // 8
[FieldOffset(672)]
private long _totalFreeBytes; // 8
[FieldOffset(676)]
private volatile int _disposed; // 4
[FieldOffset(680)]
private volatile int _chunkCreationLock; // 4
/// <summary>
/// Gets the alignment requirement for allocations.
/// </summary>
public readonly nuint Alignment => _alignment;
/// <summary>
/// Gets the total number of allocated bytes.
/// </summary>
public readonly long TotalAllocatedBytes => Interlocked.Read(ref Unsafe.AsRef(in _totalAllocatedBytes));
/// <summary>
/// Gets the total number of free bytes available.
/// </summary>
public readonly long TotalFreeBytes => Interlocked.Read(ref Unsafe.AsRef(in _totalFreeBytes));
/// <summary>
/// Gets whether the allocator has been disposed.
/// </summary>
public readonly bool IsDisposed => _disposed != 0;
/// <summary>
/// Gets the chunk size used by this allocator.
/// </summary>
public readonly nuint ChunkSize => _chunkSize;
/// <summary>
/// Initializes a new variable-size FreeList allocator with the specified parameters.
/// </summary>
/// <param name="alignment">Alignment requirement for blocks (must be power of 2).</param>
/// <param name="chunkSize">Size of memory chunks to allocate (default: 64KB).</param>
public FreeList(nuint alignment = 8, nuint chunkSize = _DEFAULT_CHUNK_SIZE)
{
if (alignment == 0 || (alignment & (alignment - 1)) != 0)
throw new ArgumentException("Alignment must be a power of 2", nameof(alignment));
if (chunkSize < 1024)
throw new ArgumentException("Chunk size must be at least 1KB", nameof(chunkSize));
_alignment = alignment;
_chunkSize = chunkSize;
_chunks = null;
_totalAllocatedBytes = 0;
_totalFreeBytes = 0;
_disposed = 0;
_chunkCreationLock = 0;
_chunkArena = new DynamicArena(1024);
InitializeBuckets();
}
/// <summary>
/// Initializes the size buckets with exponential sizes.
/// </summary>
private readonly void InitializeBuckets()
{
var buckets = GetBuckets();
var size = _MIN_BLOCK_SIZE;
for (var i = 0; i < _MAX_BUCKETS; i++)
{
buckets[i].blockSize = size;
buckets[i].freeHead = 0;
buckets[i].freeCount = 0;
size *= 2; // Exponential size increase
}
}
/// <summary>
/// Gets a pointer to the size buckets array.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private readonly SizeBucket* GetBuckets()
{
fixed (byte* ptr = _buckets)
{
return (SizeBucket*)ptr;
}
}
/// <summary>
/// Finds the appropriate bucket for the given size.
/// </summary>
/// <param name="size">Size to find bucket for.</param>
/// <returns>Bucket index, or -1 if too large for buckets.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private readonly int FindBucket(nuint size)
{
var buckets = GetBuckets();
for (var i = 0; i < _MAX_BUCKETS; i++)
{
if (size <= buckets[i].blockSize)
return i;
}
return -1; // Size too large for buckets
}
/// <summary>
/// Allocates a memory block of the specified size. Thread-safe using lock-free algorithms.
/// </summary>
/// <param name="size">Size of memory to allocate in bytes.</param>
/// <param name="alignment">Alignment requirement (0 = use default).</param>
/// <param name="allocationOption">Options for allocation (e.g., clear memory).</param>
/// <returns>MemoryBlock containing allocated memory, or Invalid if allocation fails.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public MemoryBlock Allocate(nuint size, nuint alignment = 0, AllocationOption allocationOption = AllocationOption.None)
{
if (_disposed != 0 || size == 0)
return MemoryBlock.Invalid;
if (alignment == 0)
alignment = _alignment;
// Align size to alignment boundary
var alignedSize = (size + alignment - 1) & ~(alignment - 1);
alignedSize = Math.Max(alignedSize, _MIN_BLOCK_SIZE);
var bucketIndex = FindBucket(alignedSize);
void* ptr = null;
if (bucketIndex >= 0)
{
// Try to allocate from bucket
ptr = TryPopFromBucket(bucketIndex);
if (ptr == null)
{
// Create new blocks for this bucket
if (TryCreateBlocksForBucket(bucketIndex))
{
ptr = TryPopFromBucket(bucketIndex);
}
}
}
if (ptr == null)
{
// Fallback to direct allocation from chunk
ptr = AllocateFromChunk(alignedSize, alignment);
}
if (ptr != null)
{
Interlocked.Add(ref _totalAllocatedBytes, (long)alignedSize);
if (allocationOption.HasFlag(AllocationOption.Clear))
{
MemClear(ptr, alignedSize);
}
return new MemoryBlock(ptr, alignedSize, alignment);
}
return MemoryBlock.Invalid;
}
/// <summary>
/// Frees a previously allocated memory block. Thread-safe using lock-free algorithms.
/// </summary>
/// <param name="block">MemoryBlock to free.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Free(MemoryBlock block)
{
if (!block.IsValid || _disposed != 0)
return;
if (!IsValidBlock(block.Ptr))
return; // Invalid pointer, ignore
var bucketIndex = FindBucket(block.Size);
if (bucketIndex >= 0)
{
PushToBucket(bucketIndex, block.Ptr, block.Size);
}
Interlocked.Add(ref _totalAllocatedBytes, -(long)block.Size);
}
/// <summary>
/// Tries to pop a free block from the specified bucket.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private readonly void* TryPopFromBucket(int bucketIndex)
{
var buckets = GetBuckets();
var bucket = &buckets[bucketIndex];
nint head, newHead;
FreeNode* headPtr;
do
{
head = bucket->freeHead;
if (head == 0)
return null;
headPtr = (FreeNode*)head;
newHead = (nint)headPtr->next;
} while (Interlocked.CompareExchange(ref bucket->freeHead, newHead, head) != head);
Interlocked.Decrement(ref bucket->freeCount);
return (void*)head;
}
/// <summary>
/// Pushes a block to the specified bucket's free list.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private readonly void PushToBucket(int bucketIndex, void* ptr, nuint size)
{
var buckets = GetBuckets();
var bucket = &buckets[bucketIndex];
var node = (FreeNode*)ptr;
node->size = size;
nint head;
do
{
head = bucket->freeHead;
node->next = (FreeNode*)head;
} while (Interlocked.CompareExchange(ref bucket->freeHead, (nint)node, head) != head);
Interlocked.Increment(ref bucket->freeCount);
}
/// <summary>
/// Creates new blocks for the specified bucket.
/// </summary>
private bool TryCreateBlocksForBucket(int bucketIndex)
{
while (Interlocked.CompareExchange(ref _chunkCreationLock, 1, 0) != 0)
{
Thread.SpinWait(1);
}
try
{
var buckets = GetBuckets();
var blockSize = buckets[bucketIndex].blockSize;
var blocksToCreate = Math.Min(_chunkSize / blockSize, 256); // Limit number of blocks
if (blocksToCreate == 0)
return false;
var totalSize = blocksToCreate * blockSize;
var memory = (byte*)AlignedAlloc(totalSize, _alignment);
if (memory == null)
return false;
var chunk = (MemoryChunk*)_chunkArena.Allocate(SizeOf<MemoryChunk>(), AlignOf<MemoryChunk>(), AllocationOption.None);
if (chunk == null)
{
AlignedFree(memory);
return false;
}
chunk->memory = memory;
chunk->size = totalSize;
chunk->used = totalSize;
chunk->next = _chunks;
_chunks = chunk;
// Add all blocks to the bucket's free list
for (nuint i = 0; i < blocksToCreate; i++)
{
var blockPtr = memory + (i * blockSize);
PushToBucket(bucketIndex, blockPtr, blockSize);
}
return true;
}
finally
{
Interlocked.Exchange(ref _chunkCreationLock, 0);
}
}
/// <summary>
/// Allocates memory directly from a chunk (for large allocations).
/// </summary>
private void* AllocateFromChunk(nuint size, nuint alignment)
{
while (Interlocked.CompareExchange(ref _chunkCreationLock, 1, 0) != 0)
{
Thread.SpinWait(1);
}
try
{
// Try to find space in existing chunks first
var chunk = _chunks;
while (chunk != null)
{
var available = chunk->size - chunk->used;
var alignedOffset = (chunk->used + alignment - 1) & ~(alignment - 1);
var totalNeeded = alignedOffset - chunk->used + size;
if (totalNeeded <= available)
{
var ptr = chunk->memory + alignedOffset;
chunk->used += totalNeeded;
return ptr;
}
chunk = chunk->next;
}
// Create new chunk
var newChunkSize = Math.Max(_chunkSize, size + alignment);
var newMemory = (byte*)AlignedAlloc(newChunkSize, alignment);
if (newMemory == null)
return null;
var newChunk = (MemoryChunk*)_chunkArena.Allocate(SizeOf<MemoryChunk>(), AlignOf<MemoryChunk>(), AllocationOption.None);
if (newChunk == null)
{
AlignedFree(newMemory);
return null;
}
newChunk->memory = newMemory;
newChunk->size = newChunkSize;
newChunk->used = size;
newChunk->next = _chunks;
_chunks = newChunk;
return newMemory;
}
finally
{
Interlocked.Exchange(ref _chunkCreationLock, 0);
}
}
/// <summary>
/// Validates that a pointer belongs to one of our memory chunks.
/// </summary>
private readonly bool IsValidBlock(void* ptr)
{
var chunk = _chunks;
while (chunk != null)
{
var chunkStart = (nuint)chunk->memory;
var chunkEnd = chunkStart + chunk->size;
var ptrValue = (nuint)ptr;
if (ptrValue >= chunkStart && ptrValue < chunkEnd)
return true;
chunk = chunk->next;
}
return false;
}
/// <summary>
/// Disposes the free list and frees all allocated memory.
/// Note: This method is NOT thread-safe by design as requested.
/// </summary>
public void Dispose()
{
if (Interlocked.CompareExchange(ref _disposed, 1, 0) == 0)
{
// Free all memory chunks
var chunk = _chunks;
while (chunk != null)
{
var next = chunk->next;
AlignedFree(chunk->memory);
MemoryUtilities.Free(chunk);
chunk = next;
}
_chunks = null;
_totalAllocatedBytes = 0;
_totalFreeBytes = 0;
}
}
}

View File

@@ -0,0 +1,68 @@
using System.Runtime.InteropServices;
namespace Misaki.HighPerformance.LowLevel.Buffer;
/// <summary>
/// Represents an allocated memory block with metadata.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public unsafe readonly struct MemoryBlock
{
/// <summary>
/// Pointer to the actual allocated memory.
/// </summary>
public void* Ptr
{
get;
}
/// <summary>
/// Size of the allocated memory in bytes.
/// </summary>
public nuint Size
{
get;
}
/// <summary>
/// Alignment of the allocated memory.
/// </summary>
public nuint Alignment
{
get;
}
/// <summary>
/// Indicates whether this memory block is valid.
/// </summary>
public readonly bool IsValid => Ptr != null && Size > 0;
/// <summary>
/// Creates a new MemoryBlock with the specified parameters.
/// </summary>
/// <param name="ptr">Pointer to the allocated memory.</param>
/// <param name="size">Size of the allocated memory.</param>
/// <param name="alignment">Alignment of the allocated memory.</param>
public MemoryBlock(void* ptr, nuint size, nuint alignment)
{
Ptr = ptr;
Size = size;
Alignment = alignment;
}
/// <summary>
/// Creates an invalid MemoryBlock.
/// </summary>
public static MemoryBlock Invalid => new(null, 0, 0);
public Span<T> AsSpan<T>()
where T : unmanaged
{
if (!IsValid)
{
throw new InvalidOperationException("Cannot create span from invalid MemoryBlock.");
}
return new Span<T>(Ptr, (int)(Size / SizeOf<T>()));
}
}

View File

@@ -1,19 +0,0 @@
using Misaki.HighPerformance.LowLevel.Collections;
namespace Misaki.HighPerformance.LowLevel.Buffer;
// TODO: Implement a pool for UnsafeArray<T>.
public unsafe static class UnsafeArrayPool
{
public static UnsafeArray<T> Rent<T>(int minimalSize)
where T : unmanaged
{
throw new NotImplementedException();
}
public static void Return<T>(UnsafeArray<T> array)
where T : unmanaged
{
throw new NotImplementedException();
}
}

View File

@@ -1,16 +1,7 @@
namespace Misaki.HighPerformance.LowLevel.Collections.Contracts;
public unsafe interface IUnsafeCollection<T> : IEnumerable<T>, IDisposable
where T : unmanaged
public unsafe interface IUnsafeCollection : IDisposable
{
/// <summary>
/// Gets the number of elements in a collection. The value is read-only.
/// </summary>
public int Count
{
get;
}
/// <summary>
/// Indicates whether the object has been created. Returns true if the object is created, otherwise false.
/// </summary>
@@ -24,15 +15,41 @@ public unsafe interface IUnsafeCollection<T> : IEnumerable<T>, IDisposable
/// </summary>
public void Clear();
/// <summary>
/// Changes the size of a collection or array to the specified value.
/// </summary>
/// <param name="newSize">Specifies the new size to which the collection or array should be adjusted.</param>
public void Resize(int newSize);
/// <summary>
/// Returns a pointer to an unmanaged memory location. This pointer can be used for low-level memory operations.
/// </summary>
/// <returns>The method returns a void pointer to the unsafe memory location.</returns>
public void* GetUnsafePtr();
}
public unsafe interface IUnsafeCollection<T> : IUnsafeCollection, IEnumerable<T>
where T : unmanaged
{
/// <summary>
/// Gets the number of elements in a collection. The value is read-only.
/// </summary>
public int Count
{
get;
}
/// <summary>
/// Changes the size of a collection to the specified value.
/// </summary>
/// <param name="newSize">Specifies the new size to which the collection should be adjusted.</param>
public void Resize(int newSize);
}
public unsafe interface IUnTypedCollection : IUnsafeCollection
{
/// <summary>
/// The total size of the buffer in bytes.
/// </summary>
public uint Size
{
get;
}
public ref T GetElementAt<T>(uint index)
where T : unmanaged;
}

View File

@@ -0,0 +1,152 @@
using System.Collections;
namespace Misaki.HighPerformance.LowLevel.Collections;
public class SlotMap<T> : IEnumerable<T>
{
public struct Enumerator : IEnumerator<T>
{
private readonly SlotMap<T> _slotMap;
private int _currentIndex;
public Enumerator(SlotMap<T> slotMap)
{
_slotMap = slotMap;
_currentIndex = -1;
}
public readonly T Current => _slotMap._data[_currentIndex].value;
readonly object? IEnumerator.Current => Current;
public bool MoveNext()
{
while (++_currentIndex < _slotMap._capacity)
{
if (_slotMap._data[_currentIndex].isValid)
{
return true;
}
}
return false;
}
public void Reset() => _currentIndex = -1;
public void Dispose()
{
}
}
private struct SlotData
{
public T value;
public bool isValid;
}
private SlotData[] _data;
private readonly Queue<int> _freeSlots;
private int _count;
private int _capacity;
public int Count => _count;
public int Capacity => _capacity;
public ref T this[int slotIndex]
{
get
{
if (slotIndex < 0 || slotIndex >= _capacity)
{
throw new ArgumentOutOfRangeException(nameof(slotIndex), "Slot index is out of range.");
}
ref var slot = ref _data[slotIndex];
if (!slot.isValid)
{
throw new InvalidOperationException($"Slot {slotIndex} is not occupied.");
}
return ref slot.value;
}
}
public IEnumerator<T> GetEnumerator() => new Enumerator(this);
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public SlotMap(int initialCapacity = 16)
{
_capacity = initialCapacity;
_data = new SlotData[initialCapacity];
_freeSlots = new Queue<int>(initialCapacity);
}
private void Resize()
{
var newCapacity = _capacity * 2;
Array.Resize(ref _data, newCapacity);
_freeSlots.EnsureCapacity(newCapacity);
_capacity = newCapacity;
}
public int Add(T item)
{
if (_count >= _capacity)
{
Resize();
}
int slotIndex;
if (_freeSlots.Count == 0)
{
slotIndex = _count;
}
else
{
slotIndex = _freeSlots.Dequeue();
}
ref var slot = ref _data[slotIndex];
slot.value = item;
slot.isValid = true;
_count++;
return slotIndex;
}
public bool Remove(int slotIndex)
{
if (slotIndex < 0 || slotIndex >= _capacity)
{
return false;
}
ref var slot = ref _data[slotIndex];
if (!slot.isValid)
{
return false;
}
slot.isValid = false;
_freeSlots.Enqueue(slotIndex);
_count--;
return true;
}
public void Clear()
{
_count = 0;
_data.AsSpan().Clear();
_freeSlots.Clear();
}
}

View File

@@ -0,0 +1,125 @@
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
using Misaki.HighPerformance.LowLevel.Contracts;
using Misaki.HighPerformance.LowLevel.Helpers;
using System.Runtime.CompilerServices;
namespace Misaki.HighPerformance.LowLevel.Collections;
public unsafe struct UnTypedArray : IUnTypedCollection
{
private void* _buffer;
private uint _size;
private uint _alignment;
private AllocationHandle* _handle;
public readonly uint Size => _size;
public readonly uint Alignment => _alignment;
public readonly bool IsCreated
{
get => _buffer != null;
}
/// <summary>
/// Constructs an UnsafeArray with a default size of 1 and uses the Persistent allocator.
/// </summary>
public UnTypedArray()
: this(1, 1, Allocator.Persistent)
{
}
public UnTypedArray(uint size, uint alignment, ref AllocationHandle handle, AllocationOption allocationOption = AllocationOption.None)
{
if (size <= 0)
{
throw new ArgumentOutOfRangeException(nameof(size), "Count must be greater than zero.");
}
_handle = (AllocationHandle*)Unsafe.AsPointer(ref handle);
_buffer = handle.Alloc(_handle->Allocator, size, alignment, allocationOption);
_size = size;
_alignment = alignment;
}
/// <summary>
/// Initializes a new instance of UnsafeArray with a specified number of elements and an allocation type.
/// </summary>
/// <param name="count">Specifies the number of elements to allocate in the array, which must be greater than zero.</param>
/// <param name="allocator">Specifies the allocator to use for memory allocation, which determines the memory management strategy.</param>
/// <param name="allocationOption">Determines how the memory should be allocated.</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when the specified number of elements is less than or equal to zero.</exception>
public UnTypedArray(uint size, uint alignment, Allocator allocator, AllocationOption allocationOption = AllocationOption.None)
: this(size, alignment, ref AllocationManager.GetAllocationHandle(allocator), allocationOption)
{
}
/// <summary>
/// Initializes an UnsafeArray with a pointer to a buffer and a count of elements. This does not copy the data.
/// </summary>
/// <param name="buffer">A pointer to the memory location that holds the elements of the array.</param>
/// <param name="count">The total size of the data.</param>
/// <remarks>
/// When using this constructor, the user is responsible for managing the memory pointed to by the buffer.
/// Disposing of the UnsafeArray does not free the memory and only release the reference. The memory should be freed manually when no longer needed.
/// Use <see cref="UnsafeArray(int, Allocator, AllocationOption)"/> constructor and <see cref="MemCpy(void*, void*, nuint)"/> if you are not sure what you are doing.
/// </remarks>
public UnTypedArray(void* buffer, uint size)
{
_buffer = buffer;
_size = size;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly ref T GetElementAt<T>(uint index)
where T : unmanaged
{
return ref UnsafeUtilities.ReadArrayElementRef<T>(_buffer, index);
}
/// <inheritdoc/>
public void Resize(uint newSize)
{
if (newSize == _size)
{
return;
}
_buffer = _handle->Realloc(_handle->Allocator, _buffer, newSize, _alignment);
_size = newSize;
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void Clear()
{
MemClear(_buffer, _size);
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void* GetUnsafePtr()
{
return _buffer;
}
/// <inheritdoc/>
public void Dispose()
{
if (!IsCreated)
{
return;
}
if (_handle != null)
{
_handle->Free(_handle->Allocator, _buffer);
}
_handle = null;
_buffer = null;
_size = 0;
_alignment = 0;
}
}

View File

@@ -81,7 +81,21 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
throw new ArgumentOutOfRangeException(nameof(index), "Index is out of range.");
}
return ref _buffer[index];
return ref UnsafeUtilities.ReadArrayElementRef<T>(_buffer, index);
}
}
public readonly ref T this[uint index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
if (index >= _count)
{
throw new ArgumentOutOfRangeException(nameof(index), "Index is out of range.");
}
return ref UnsafeUtilities.ReadArrayElementRef<T>(_buffer, index);
}
}
@@ -98,10 +112,17 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
/// Constructs an UnsafeArray with a default size of 1 and uses the Persistent allocator.
/// </summary>
public UnsafeArray()
: this(1, Allocator.Persistent)
: this(0, Allocator.Invalid)
{
}
/// <summary>
/// Initializes a new instance of UnsafeArray with a specified number of elements and an allocation handle.
/// </summary>
/// <param name="count">Specifies the number of elements to allocate in the array, which must be greater than zero.</param>
/// <param name="handle">A reference to an AllocationHandle that manages the memory allocation for the array.</param>
/// <param name="allocationOption">Specifies how the memory should be allocated.</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when the specified number of elements is less than or equal to zero.</exception>
public UnsafeArray(int count, ref AllocationHandle handle, AllocationOption allocationOption = AllocationOption.None)
{
if (count <= 0)
@@ -136,11 +157,10 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
/// Disposing of the UnsafeArray does not free the memory and only release the reference. The memory should be freed manually when no longer needed.
/// Use <see cref="UnsafeArray(int, Allocator, AllocationOption)"/> constructor and <see cref="MemCpy(void*, void*, nuint)"/> if you are not sure what you are doing.
/// </remarks>
public UnsafeArray(void* buffer, int count)
public UnsafeArray(T* buffer, int count)
{
_buffer = (T*)buffer;
_buffer = buffer;
_count = count;
_handle = (AllocationHandle*)Unsafe.AsPointer(ref AllocationManager.EmptyAllocator.Handle);
}
/// <inheritdoc/>
@@ -177,7 +197,10 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
return;
}
_handle->Free(_handle->Allocator, _buffer);
if (_handle != null)
{
_handle->Free(_handle->Allocator, _buffer);
}
_handle = null;
_buffer = null;

View File

@@ -215,9 +215,4 @@ public unsafe struct UnsafeHashMap<TKey, TValue> : IUnsafeCollection<KeyValuePai
{
_hashMap.Dispose();
}
public void Test(ref HashMapHelper<TKey> t)
{
Console.WriteLine(t.Equals(_hashMap));
}
}

View File

@@ -1,4 +1,6 @@
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
using Misaki.HighPerformance.LowLevel.Contracts;
using Misaki.HighPerformance.LowLevel.Helpers;
using System.Collections;
using System.Runtime.CompilerServices;
@@ -81,7 +83,7 @@ public unsafe struct UnsafeList<T> : IUnsafeCollection<T>
/// </summary>
public UnsafeList<T>* listData;
internal unsafe ParallelWriter(UnsafeList<T>* list)
internal ParallelWriter(UnsafeList<T>* list)
{
listData = list;
}
@@ -102,11 +104,15 @@ public unsafe struct UnsafeList<T> : IUnsafeCollection<T>
/// </summary>
/// <param name="ptr">Points to the source data to be copied into the buffer.</param>
/// <param name="count">Indicates the number of elements to be added from the source data.</param>
public void AddRangeNoResize(T* ptr, int count)
public void AddRangeNoResize(ReadOnlySpan<T> collection, int count)
{
var idx = Interlocked.Add(ref listData->_count, count) - count;
listData->CheckNoResizeCapacity(idx, count);
MemCpy(UnsafeUtilities.ReadArrayElementUnsafe<T>(listData->_array.GetUnsafePtr(), idx), ptr, (uint)(count * sizeof(T)));
var index = Interlocked.Add(ref listData->_count, count) - count;
listData->CheckNoResizeCapacity(index, count);
fixed (T* pCollection = collection)
{
MemCpy(UnsafeUtilities.ReadArrayElementUnsafe<T>(listData->_array.GetUnsafePtr(), index), pCollection, (uint)(count * sizeof(T)));
}
}
}
@@ -124,18 +130,47 @@ public unsafe struct UnsafeList<T> : IUnsafeCollection<T>
get => ref _array[index];
}
public readonly ref T this[uint index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => ref _array[index];
}
public IEnumerator<T> GetEnumerator() => new Enumerator((UnsafeList<T>*)UnsafeUtilities.AddressOf(ref this));
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
/// <summary>
/// Provides a parallel writer for the current list, enabling thread-safe additions to the list.
/// </summary>
/// <returns>A <see cref="ParallelWriter"/> instance that can be used to add items to the list in a thread-safe manner.</returns>
public ParallelWriter AsParallelWriter() => new((UnsafeList<T>*)UnsafeUtilities.AddressOf(ref this));
public UnsafeList() : this(1, Allocator.Persistent)
/// <summary>
/// Converts the current list to an UnsafeArray representation.
/// </summary>
/// <returns>A new <see cref="UnsafeArray{T}"/> instance.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly UnsafeArray<T> AsUnsafeArray() => new((T*)_array.GetUnsafePtr(), _count);
public UnsafeList()
: this(0, Allocator.Invalid)
{
}
public UnsafeList(int capacity, ref AllocationHandle handle, AllocationOption allocationType = AllocationOption.None)
{
if (capacity <= 0)
{
throw new ArgumentOutOfRangeException(nameof(capacity), "Capacity must be greater than zero.");
}
_array = new UnsafeArray<T>(capacity, ref handle, allocationType);
_count = 0;
}
public UnsafeList(int capacity, Allocator allocator, AllocationOption allocationType = AllocationOption.None)
{
_array = new UnsafeArray<T>(capacity, allocator, allocationType);
_count = 0;
}
private readonly void CheckNoResizeCapacity(int count)
@@ -176,6 +211,10 @@ public unsafe struct UnsafeList<T> : IUnsafeCollection<T>
}
}
/// <summary>
/// Adds a new element to the end of the list, resizing the internal array if necessary.
/// </summary>
/// <param name="value">The element to be added to the list.</param>
public void Add(T value)
{
if (_count >= Capacity)
@@ -187,6 +226,10 @@ public unsafe struct UnsafeList<T> : IUnsafeCollection<T>
_count++;
}
/// <summary>
/// Adds the specified value to the collection without resizing the underlying storage.
/// </summary>
/// <param name="value">The value to add to the collection.</param>
public void AddNoResize(T value)
{
CheckNoResizeCapacity(1);
@@ -195,6 +238,12 @@ public unsafe struct UnsafeList<T> : IUnsafeCollection<T>
_count++;
}
/// <summary>
/// Adds a range of elements to the collection.
/// </summary>
/// <param name="values">A span containing the elements to add. The span must not exceed the specified <paramref name="count"/>.</param>
/// <param name="count">The number of elements to add from the <paramref name="values"/> span. Must be non-negative and less than or
/// equal to the length of <paramref name="values"/>.</param>
public void AddRange(Span<T> values, int count)
{
var newSize = _count + count;
@@ -211,18 +260,27 @@ public unsafe struct UnsafeList<T> : IUnsafeCollection<T>
_count += count;
}
public void AddRangeNoResize(ReadOnlySpan<T> values)
/// <summary>
/// Adds the elements of the specified collection to the current list without resizing the underlying storage.
/// </summary>
/// <param name="collection">A read-only span containing the elements to add. The span must not exceed the available capacity.</param>
public void AddRangeNoResize(ReadOnlySpan<T> collection)
{
CheckNoResizeCapacity(values.Length);
CheckNoResizeCapacity(collection.Length);
fixed (T* ptr = values)
fixed (T* pCollection = collection)
{
MemCpy(UnsafeUtilities.ReadArrayElementUnsafe<T>(_array.GetUnsafePtr(), _count), ptr, (uint)(values.Length * sizeof(T)));
MemCpy(UnsafeUtilities.ReadArrayElementUnsafe<T>(_array.GetUnsafePtr(), _count), pCollection, (uint)(collection.Length * sizeof(T)));
}
_count += values.Length;
_count += collection.Length;
}
/// <summary>
/// Adds a range of elements from a pointer to the collection without resizing the underlying storage.
/// </summary>
/// <param name="ptr">Points to the source data to be copied into the collection.</param>
/// <param name="count">Indicates the number of elements to be added from the source data.</param>
public void AddRangeNoResize(T* ptr, int count)
{
CheckNoResizeCapacity(count);
@@ -231,6 +289,11 @@ public unsafe struct UnsafeList<T> : IUnsafeCollection<T>
_count += count;
}
/// <summary>
/// Removes a range of elements from the list starting at the specified index.
/// </summary>
/// <param name="start">The zero-based index at which to start removing elements.</param>
/// <param name="length">The number of elements to remove.</param>
public void RemoveRange(int start, int length)
{
CheckIndexCount(start, length);
@@ -248,11 +311,20 @@ public unsafe struct UnsafeList<T> : IUnsafeCollection<T>
_count -= length;
}
/// <summary>
/// Removes the element at the specified index from the collection.
/// </summary>
/// <param name="index">The zero-based index of the element to remove.</param>
public void RemoveAt(int index)
{
RemoveRange(index, 1);
}
/// <summary>
/// Removes a range of elements from the list starting at the specified index by swapping them with the last elements.
/// </summary>
/// <param name="start">The zero-based index at which to start removing elements.</param>
/// <param name="length">The number of elements to remove.</param>
public void RemoveRangeSwapBack(int start, int length)
{
CheckIndexCount(start, length);
@@ -270,6 +342,11 @@ public unsafe struct UnsafeList<T> : IUnsafeCollection<T>
_count -= length;
}
/// <summary>
/// Removes the element at the specified index by swapping it with the last element and reducing the collection
/// size.
/// </summary>
/// <param name="index">The zero-based index of the element to remove. Must be within the bounds of the collection.</param>
public void RemoveAtSwapBack(int index)
{
RemoveRangeSwapBack(index, 1);
@@ -291,27 +368,12 @@ public unsafe struct UnsafeList<T> : IUnsafeCollection<T>
_count = 0;
}
/// <summary>
/// Returns a pointer to the underlying data of the array in an unsafe manner. This method is optimized for
/// performance.
/// </summary>
/// <returns>A pointer to the array's data.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void* GetUnsafePtr()
{
return _array.GetUnsafePtr();
}
/// <summary>
/// Converts the current array to an UnsafeArray representation using its pointer and count.
/// </summary>
/// <returns>Returns a new UnsafeArray instance initialized with the array's unsafe pointer and its count.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly UnsafeArray<T> AsUnsafeArray()
{
return new UnsafeArray<T>(_array.GetUnsafePtr(), _count);
}
public void Dispose()
{
_array.Dispose();

View File

@@ -1,3 +1,4 @@
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
using Misaki.HighPerformance.LowLevel.Helpers;
using System.Collections;
@@ -90,6 +91,21 @@ public unsafe struct UnsafeQueue<T> : IUnsafeCollection<T>
_array = new UnsafeArray<T>(capacity, allocator, allocationType);
}
/// <summary>
/// Returns a reference to the item at the front of the queue without removing it.
/// </summary>
/// <returns>A reference to the item at the front of the queue.</returns>
/// <exception cref="InvalidOperationException">Thrown if the queue is empty.</exception>
public readonly ref T Peek()
{
if (_count == 0)
{
throw new InvalidOperationException("Queue is empty.");
}
return ref UnsafeUtilities.ReadArrayElementRef<T>(_array.GetUnsafePtr(), _offset);
}
/// <summary>
/// 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.

View File

@@ -0,0 +1,528 @@
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
using Misaki.HighPerformance.LowLevel.Contracts;
using Misaki.HighPerformance.LowLevel.Helpers;
using System.Collections;
using System.Runtime.CompilerServices;
namespace Misaki.HighPerformance.LowLevel.Collections;
/// <summary>
/// A sparse set data structure that provides O(1) insertion, deletion, and lookup operations.
/// The sparse set uses three arrays: a dense array for storing values, a sparse array for mapping indices,
/// and a reverse array for mapping dense indices back to sparse indices.
/// Sparse indices work like entity IDs and are automatically generated.
/// </summary>
/// <typeparam name="T">Represents a type that can be stored in the sparse set, constrained to unmanaged types for performance and safety.</typeparam>
public unsafe struct UnsafeSparseSet<T> : IUnsafeCollection<T>
where T : unmanaged
{
public struct Enumerator : IEnumerator<T>
{
private UnsafeSparseSet<T>* _collection;
private int _index;
private T _value;
public Enumerator(UnsafeSparseSet<T>* collection)
{
_collection = collection;
_index = -1;
_value = default;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext()
{
_index++;
if (_index < _collection->_count)
{
_value = UnsafeUtilities.ReadArrayElement<T>(_collection->_dense.GetUnsafePtr(), _index);
return true;
}
_value = default;
return false;
}
public void Reset()
{
_index = -1;
}
public readonly T Current
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _value;
}
readonly object IEnumerator.Current
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => Current;
}
public unsafe readonly void Dispose()
{
}
}
public struct ParallelWriter
{
private UnsafeSparseSet<T>* _sparseSet;
internal ParallelWriter(UnsafeSparseSet<T>* sparseSet)
{
_sparseSet = sparseSet;
}
/// <summary>
/// Adds a value to the sparse set without resizing the internal arrays.
/// </summary>
/// <param name="value">The value to add to the sparse set.</param>
/// <returns> Returns the sparse index assigned to the value. -1 if the sparse index is out of bounds.</returns>
public int AddNoResize(T value)
{
int sparseIndex;
if (_sparseSet->_freeCount > 0)
{
var index = Interlocked.Decrement(ref _sparseSet->_freeCount);
sparseIndex = _sparseSet->_freeList[index];
}
else
{
sparseIndex = Interlocked.Increment(ref _sparseSet->_nextId) - 1;
}
if (sparseIndex >= _sparseSet->_sparse.Count)
{
return -1;
}
var count = Interlocked.Increment(ref _sparseSet->_count) - 1;
_sparseSet->_dense[count] = value;
_sparseSet->_sparse[sparseIndex] = count;
_sparseSet->_reverse[count] = sparseIndex;
return sparseIndex;
}
/// <summary>
/// Attempts to add a value at the specified sparse index without resizing the underlying collection.
/// </summary>
/// <param name="sparseIndex">The index in the sparse array where the value should be added. Must be within the valid range of the sparse
/// array.</param>
/// <param name="value">The value to add to the collection.</param>
/// <returns><see langword="true"/> if the value was successfully added at the specified index; otherwise, <see
/// langword="false"/>.</returns>
public bool AddAtNoResize(int sparseIndex, T value)
{
if (sparseIndex < 0 || sparseIndex >= _sparseSet->_sparse.Count)
{
return false;
}
if (_sparseSet->Contains(sparseIndex))
{
return false;
}
if (_sparseSet->_count >= _sparseSet->_dense.Count)
{
return false;
}
var count = Interlocked.Increment(ref _sparseSet->_count) - 1;
_sparseSet->_dense[count] = value;
_sparseSet->_sparse[sparseIndex] = count;
_sparseSet->_reverse[count] = sparseIndex;
return true;
}
/// <summary>
/// Removes a value at the specified sparse index without resizing the internal arrays.
/// </summary>
/// <param name="sparseIndex">The sparse index of the value to remove.</param>
/// <returns>Returns <see langword="true"/> if the value was successfully removed; otherwise, <see langword="false"/>.</returns>
public bool RemoveNoResize(int sparseIndex)
{
if (!_sparseSet->Contains(sparseIndex))
{
return false;
}
var denseIndex = _sparseSet->_sparse[sparseIndex];
var lastIndex = _sparseSet->_count - 1;
if (denseIndex != lastIndex)
{
var lastValue = _sparseSet->_dense[lastIndex];
var lastSparseIndex = _sparseSet->_reverse[lastIndex];
_sparseSet->_dense[denseIndex] = lastValue;
_sparseSet->_reverse[denseIndex] = lastSparseIndex;
_sparseSet->_sparse[lastSparseIndex] = denseIndex;
}
_sparseSet->_sparse[sparseIndex] = -1;
if (_sparseSet->_freeCount >= _sparseSet->_freeList.Count)
{
return false;
}
_sparseSet->_freeList[_sparseSet->_freeCount] = sparseIndex;
Interlocked.Increment(ref _sparseSet->_freeCount);
Interlocked.Decrement(ref _sparseSet->_count);
return true;
}
}
private UnsafeArray<T> _dense;
private UnsafeArray<int> _sparse;
private UnsafeArray<int> _reverse; // Maps dense index to sparse index
private UnsafeArray<int> _freeList; // Stack of available sparse indices
private int _count;
private int _nextId; // Next available sparse index
private int _freeCount; // Number of items in the free list
public readonly int Count => _count;
public readonly int Capacity => _dense.Count;
public readonly bool IsCreated => _dense.IsCreated && _sparse.IsCreated && _reverse.IsCreated && _freeList.IsCreated;
public readonly ref T this[int index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => ref _dense[index];
}
public IEnumerator<T> GetEnumerator() => new Enumerator((UnsafeSparseSet<T>*)UnsafeUtilities.AddressOf(ref this));
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
/// <summary>
/// Constructs an UnsafeSparseSet with a default size of 1 and uses the Persistent allocator.
/// </summary>
public UnsafeSparseSet()
: this(1, Allocator.Persistent)
{
}
/// <summary>
/// Initializes a new instance of UnsafeSparseSet with a specified capacity and an allocation handle.
/// </summary>
/// <param name="capacity">Specifies the initial capacity of the sparse set, which must be greater than zero.</param>
/// <param name="handle">A reference to an AllocationHandle that manages the memory allocation for the sparse set.</param>
/// <param name="allocationOption">Specifies how the memory should be allocated.</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when the specified capacity is less than or equal to zero.</exception>
public UnsafeSparseSet(int capacity, ref AllocationHandle handle, AllocationOption allocationOption = AllocationOption.None)
{
if (capacity <= 0)
{
throw new ArgumentOutOfRangeException(nameof(capacity), "Capacity must be greater than zero.");
}
_dense = new UnsafeArray<T>(capacity, ref handle, allocationOption);
_sparse = new UnsafeArray<int>(capacity, ref handle, allocationOption);
_reverse = new UnsafeArray<int>(capacity, ref handle, allocationOption);
_freeList = new UnsafeArray<int>(capacity, ref handle, allocationOption);
_count = 0;
_nextId = 0;
_freeCount = 0;
Clear();
}
/// <summary>
/// Initializes a new instance of UnsafeSparseSet with a specified capacity and an allocation type.
/// </summary>
/// <param name="capacity">Specifies the initial capacity of the sparse set, which must be greater than zero.</param>
/// <param name="allocator">Specifies the allocator to use for memory allocation, which determines the memory management strategy.</param>
/// <param name="allocationOption">Determines how the memory should be allocated.</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when the specified capacity is less than or equal to zero.</exception>
public UnsafeSparseSet(int capacity, Allocator allocator, AllocationOption allocationOption = AllocationOption.None)
: this(capacity, ref AllocationManager.GetAllocationHandle(allocator), allocationOption)
{
}
/// <summary>
/// Adds a value to the sparse set and returns a unique sparse index for the value.
/// </summary>
/// <param name="value">The value to add to the sparse set.</param>
/// <returns>A unique sparse index that can be used to reference this value.</returns>
public int Add(T value)
{
int sparseIndex;
// Try to reuse a freed sparse index first
if (_freeCount > 0)
{
_freeCount--;
sparseIndex = _freeList[_freeCount];
}
else
{
// Use the next available ID
sparseIndex = _nextId++;
// Resize sparse array if necessary
if (sparseIndex >= _sparse.Count)
{
ResizeSparse(sparseIndex + 1);
}
}
// Resize dense arrays if necessary
if (_count >= _dense.Count)
{
var newCapacity = _dense.Count + Math.Max(1, _dense.Count / 2);
_dense.Resize(newCapacity);
_reverse.Resize(newCapacity);
}
// Add the value to the dense array and update mappings
_dense[_count] = value;
_sparse[sparseIndex] = _count;
_reverse[_count] = sparseIndex;
_count++;
return sparseIndex;
}
/// <summary>
/// Adds a value to the sparse set at the specified sparse index.
/// This method is provided for compatibility when you need to specify the exact sparse index.
/// </summary>
/// <param name="sparseIndex">The index in the sparse array where the value should be mapped.</param>
/// <param name="value">The value to add to the sparse set.</param>
/// <returns>True if the value was added, false if the sparse index is already occupied.</returns>
public bool AddAt(int sparseIndex, T value)
{
if (sparseIndex < 0)
{
throw new ArgumentOutOfRangeException(nameof(sparseIndex), "Sparse index must be non-negative.");
}
if (sparseIndex >= _sparse.Count)
{
ResizeSparse(sparseIndex + 1);
}
if (Contains(sparseIndex))
{
return false;
}
if (_count >= _dense.Count)
{
var newCapacity = _dense.Count + Math.Max(1, _dense.Count / 2);
_dense.Resize(newCapacity);
_reverse.Resize(newCapacity);
}
// Add the value to the dense array and update mappings
_dense[_count] = value;
_sparse[sparseIndex] = _count;
_reverse[_count] = sparseIndex; // Store reverse mapping
_count++;
// Update _nextId if we're using a higher ID
if (sparseIndex >= _nextId)
{
_nextId = sparseIndex + 1;
}
return true;
}
/// <summary>
/// Removes the value at the specified sparse index.
/// </summary>
/// <param name="sparseIndex">The sparse index of the value to remove.</param>
/// <returns>True if the value was removed, false if the sparse index was not found.</returns>
public bool Remove(int sparseIndex)
{
if (!Contains(sparseIndex))
{
return false;
}
var denseIndex = _sparse[sparseIndex];
var lastIndex = _count - 1;
if (denseIndex != lastIndex)
{
// Move the last element to the position of the removed element
var lastValue = _dense[lastIndex];
var lastSparseIndex = _reverse[lastIndex]; // Get sparse index of last element
_dense[denseIndex] = lastValue;
_reverse[denseIndex] = lastSparseIndex;
// Update the sparse mapping for the moved element
_sparse[lastSparseIndex] = denseIndex;
}
// Mark the sparse index as unused and add to free list
_sparse[sparseIndex] = -1;
// Add the freed sparse index to the free list for reuse
if (_freeCount >= _freeList.Count)
{
_freeList.Resize(_freeList.Count + Math.Max(1, _freeList.Count / 2));
}
_freeList[_freeCount] = sparseIndex;
_freeCount++;
_count--;
return true;
}
/// <summary>
/// Checks if the sparse set contains a value at the specified sparse index.
/// </summary>
/// <param name="sparseIndex">The sparse index to check.</param>
/// <returns>True if the sparse index is valid and contains a value, false otherwise.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly bool Contains(int sparseIndex)
{
if (sparseIndex < 0 || sparseIndex >= _sparse.Count)
{
return false;
}
var denseIndex = _sparse[sparseIndex];
return denseIndex >= 0 && denseIndex < _count;
}
/// <summary>
/// Gets the value at the specified sparse index.
/// </summary>
/// <param name="sparseIndex">The sparse index to retrieve the value from.</param>
/// <param name="value">When this method returns, contains the value at the specified sparse index, if found.</param>
/// <returns>True if the sparse index contains a value, false otherwise.</returns>
public readonly bool TryGetValue(int sparseIndex, out T value)
{
if (Contains(sparseIndex))
{
var denseIndex = _sparse[sparseIndex];
value = _dense[denseIndex];
return true;
}
value = default;
return false;
}
/// <summary>
/// Gets the value at the specified sparse index.
/// </summary>
/// <param name="sparseIndex">The sparse index to retrieve the value from.</param>
/// <returns>The value at the specified sparse index.</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when the sparse index is not found.</exception>
public readonly T GetValue(int sparseIndex)
{
if (!Contains(sparseIndex))
{
throw new ArgumentOutOfRangeException(nameof(sparseIndex), "Sparse index not found in the set.");
}
var denseIndex = _sparse[sparseIndex];
return _dense[denseIndex];
}
/// <summary>
/// Updates the value at the specified sparse index.
/// </summary>
/// <param name="sparseIndex">The sparse index of the value to update.</param>
/// <param name="value">The new value.</param>
/// <returns>True if the value was updated, false if the sparse index was not found.</returns>
public bool SetValue(int sparseIndex, T value)
{
if (!Contains(sparseIndex))
{
return false;
}
var denseIndex = _sparse[sparseIndex];
_dense[denseIndex] = value;
return true;
}
private void ResizeSparse(int newSize)
{
var oldSize = _sparse.Count;
_sparse.Resize(newSize);
_sparse.AsSpan()[oldSize..newSize].Fill(-1);
}
/// <inheritdoc/>
public void Clear()
{
if (!IsCreated)
{
return;
}
_sparse.AsSpan().Fill(-1);
_count = 0;
_nextId = 0;
_freeCount = 0;
}
/// <inheritdoc/>
public void Resize(int newSize)
{
if (newSize <= 0)
{
throw new ArgumentOutOfRangeException(nameof(newSize), "New size must be greater than zero.");
}
_dense.Resize(newSize);
_reverse.Resize(newSize);
_freeList.Resize(newSize);
if (newSize > _sparse.Count)
{
ResizeSparse(newSize);
}
if (_count > newSize)
{
_count = newSize;
}
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void* GetUnsafePtr()
{
return _dense.GetUnsafePtr();
}
/// <summary>
/// Converts the current sparse set to an UnsafeArray representation using its dense array.
/// </summary>
/// <returns>Returns a new UnsafeArray instance initialized with the dense array's pointer and count.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly UnsafeArray<T> AsUnsafeArray()
{
return new UnsafeArray<T>((T*)_dense.GetUnsafePtr(), _count);
}
/// <inheritdoc/>
public void Dispose()
{
_dense.Dispose();
_sparse.Dispose();
_reverse.Dispose();
_freeList.Dispose();
_count = 0;
_nextId = 0;
_freeCount = 0;
}
}

View File

@@ -1,4 +1,5 @@
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
using Misaki.HighPerformance.LowLevel.Helpers;
using System.Collections;
using System.Runtime.CompilerServices;

View File

@@ -1,33 +1,50 @@
using Misaki.HighPerformance.LowLevel.Collections;
namespace Misaki.HighPerformance.LowLevel.Contracts;
using unsafe AllocFunc = delegate* unmanaged<void*, nuint, nuint, AllocationOption, void*>;
using unsafe FreeFunc = delegate* unmanaged<void*, void*, void>;
using unsafe ReallocFunc = delegate* unmanaged<void*, void*, nuint, nuint, void*>;
namespace Misaki.HighPerformance.LowLevel.Contracts;
/// <summary>
/// A structure that encapsulates function pointers for memory allocation operations.
/// </summary>
public unsafe readonly struct AllocationHandle
{
/// <summary>
/// Gets a pointer to the allocator instance associated with this allocation handle.
/// </summary>
public void* Allocator
{
get;
}
/// <summary>
/// Gets a function pointer for allocating memory.
/// </summary>
public AllocFunc Alloc
{
get;
}
/// <summary>
/// Gets a function pointer for reallocating memory.
/// </summary>
public ReallocFunc Realloc
{
get;
}
/// <summary>
/// Gets a function pointer for freeing allocated memory.
/// </summary>
public FreeFunc Free
{
get;
}
/// <summary>
/// Initializes a new instance of the <see cref="AllocationHandle"/> struct with the specified allocator and memory
/// management functions.
/// </summary>
/// <param name="allocator">A pointer to the allocator instance used for memory management.</param>
/// <param name="alloc">The function used to allocate memory.</param>
/// <param name="realloc">The function used to reallocate memory.</param>
/// <param name="free">The function used to free allocated memory.</param>
public AllocationHandle(void* allocator, AllocFunc alloc, ReallocFunc realloc, FreeFunc free)
{
Allocator = allocator;
@@ -42,9 +59,13 @@ public unsafe readonly struct AllocationHandle
/// </summary>
/// <remarks>
/// The allocator must be static or pined to a specific memory region.
/// Otherwise the pointer of the allocator, <see cref="AllocationHandle.Allocator"/>, may become invalid and lead to undefined behavior.
/// </remarks>
public unsafe interface IAllocator
{
/// <summary>
/// Gets a reference to the allocation handle associated with this allocator.
/// </summary>
public ref AllocationHandle Handle
{
get;

View File

@@ -4,8 +4,11 @@ using System.Text;
namespace Misaki.HighPerformance.LowLevel.Exceptions;
public class MemoryLeakException(params AllocationManager.AllocationInfo[] Infos) : Exception
/// <summary>
/// An exception that is thrown when a memory leak is detected.
/// </summary>
/// <param name="Infos">An array of AllocationInfo containing details about the memory leaks.</param>
public class MemoryLeakException(params AllocationInfo[] Infos) : Exception
{
private static string GetMessage(StackTrace? stackTrace)
{

View File

@@ -2,11 +2,18 @@
namespace Misaki.HighPerformance.LowLevel;
/// <summary>
/// A structure that encapsulates a function pointer and provides methods to convert between
/// </summary>
/// <remarks>
/// This structure used marshalling to convert between function pointers and delegates, which is not ideal for high-performance scenarios.
/// Use this only when necessary, and prefer using <c>delegate* unmanaged</c> for better performance.
/// </remarks>
/// <typeparam name="T">The delegate type that the function pointer represents.</typeparam>
public readonly struct FunctionPointer<T>
where T : Delegate
{
private readonly nint _ptr;
private readonly T _delegate;
/// <summary>
/// Gets the native function pointer associated with this function pointer instance.
@@ -20,15 +27,14 @@ public readonly struct FunctionPointer<T>
/// cref="Marshal.GetDelegateForFunctionPointer{TDelegate}"/> to convert the function
/// pointer to a delegate. Ensure that the function pointer is valid and compatible with the delegate type
/// <typeparamref name="T"/>.</remarks>
public T Invoke => _delegate;
public T Delegate => Marshal.GetDelegateForFunctionPointer<T>(_ptr);
/// <summary>
/// Creates a new instance of this function pointer with the following native pointer.
/// </summary>
/// <param name="ptr"></param>
public FunctionPointer(nint ptr)
public FunctionPointer(T func)
{
_ptr = ptr;
_delegate = Marshal.GetDelegateForFunctionPointer<T>(ptr);
_ptr = Marshal.GetFunctionPointerForDelegate<T>(func);
}
}

View File

@@ -1,3 +1,4 @@
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Contracts;
using System.Numerics;
@@ -79,35 +80,15 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
public const int MINIMAL_CAPACITY = 64;
public readonly byte* Buffer
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _buffer;
}
public readonly byte* Buffer => _buffer;
public readonly int Count
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _count;
}
public readonly int Count => _count;
public readonly int Capacity
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _capacity;
}
public readonly int Capacity => _capacity;
public readonly bool IsCreated
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _buffer != null;
}
public readonly bool IsCreated => _buffer != null;
public readonly bool IsEmpty
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => !IsCreated || _count == 0;
}
public readonly bool IsEmpty => !IsCreated || _count == 0;
private static int CalculateDataSize(int capacity, int bucketCapacity, int sizeOfTValue, out int outKeyOffset, out int outNextOffset, out int outBucketOffset)
{

View File

@@ -15,7 +15,7 @@ public static unsafe partial class MemoryUtilities
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector128<byte> LoadVector128(ref byte start, nuint offset)
=> Unsafe.ReadUnaligned<Vector128<byte>>(ref Unsafe.AddByteOffset(ref start, offset));
=> Unsafe.ReadUnaligned<Vector128<byte>>(ref Unsafe.AddByteOffset(ref start, offset));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector256<byte> LoadVector256(ref byte start, nuint offset)

View File

@@ -168,7 +168,7 @@ public static unsafe partial class MemoryUtilities
}
/// <summary>
/// Calculates the alignment size difference between a specified struct and a helper struct.
/// Calculates the alignment size of a specified struct.
/// </summary>
/// <typeparam name="T">Represents a value type that is used to determine the alignment size.</typeparam>
/// <returns>Returns the size difference in bytes as an integer.</returns>

View File

@@ -1,4 +1,5 @@
using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
using System.Runtime.InteropServices;
@@ -25,9 +26,9 @@ public unsafe static class UnsafeCollectionExtensions
throw new ArgumentException("Source collection is larger than the destination span.");
}
fixed (T* ptr = destination)
fixed (T* pDest = destination)
{
SystemUnsfae.CopyBlock(ptr, source.GetUnsafePtr(), (uint)(source.Count * sizeof(T)));
MemCpy(source.GetUnsafePtr(), pDest, (uint)(source.Count * sizeof(T)));
}
}
@@ -41,7 +42,7 @@ public unsafe static class UnsafeCollectionExtensions
/// <param name="destinationIndex">The starting index in the destination span where the elements will be placed.</param>
/// <param name="length">The number of elements to copy from the source to the destination.</param>
/// <exception cref="ArgumentException">Thrown when the specified range exceeds the bounds of the source collection or destination span.</exception>
public static void CopyTo<T>(this IUnsafeCollection<T> source, Span<T> destination, int sourceIndex, int destinationIndex, int length)
public static void CopyTo<T>(this IUnsafeCollection<T> source, Span<T> destination, uint sourceIndex, uint destinationIndex, uint length)
where T : unmanaged
{
if (sourceIndex + length > source.Count || destinationIndex + length > destination.Length)
@@ -49,9 +50,56 @@ public unsafe static class UnsafeCollectionExtensions
throw new ArgumentException("Source collection or destination span is too small for the specified range.");
}
fixed (T* ptr = destination)
fixed (T* pDest = destination)
{
SystemUnsfae.CopyBlock(ptr + destinationIndex, (byte*)source.GetUnsafePtr() + sourceIndex * sizeof(T), (uint)(length * sizeof(T)));
MemCpy((byte*)source.GetUnsafePtr() + sourceIndex * sizeof(T), pDest + destinationIndex, (uint)(length * sizeof(T)));
}
}
/// <summary>
/// Copies elements from an untyped source collection to a destination span of a specific type.
/// </summary>
/// <typeparam name="T">Specifies the type of elements in the destination span, which must be unmanaged.</typeparam>
/// <param name="source">The untyped collection from which data is copied.</param>
/// <param name="destination">The typed span where the data will be copied to.</param>
/// <exception cref="ArgumentException">Thrown when the source collection size exceeds the destination span capacity.</exception>
public static void CopyTo<T>(this IUnTypedCollection source, Span<T> destination)
where T : unmanaged
{
var destSize = (uint)destination.Length * (uint)sizeof(T);
if (source.Size > destSize)
{
throw new ArgumentException("Source collection is larger than the destination span.");
}
fixed (T* pDest = destination)
{
MemCpy(source.GetUnsafePtr(), pDest, source.Size);
}
}
/// <summary>
/// Copies a range of bytes from an untyped source collection to a destination span, interpreting the bytes as elements of type T.
/// </summary>
/// <typeparam name="T">Specifies the type of elements in the destination span, which must be unmanaged.</typeparam>
/// <param name="source">The untyped collection from which bytes are copied.</param>
/// <param name="destination">The typed span where the elements will be placed.</param>
/// <param name="sourceOffset">The byte offset in the source collection from which to start copying.</param>
/// <param name="destinationIndex">The element index in the destination span where copying will begin.</param>
/// <param name="length">The number of elements of type T to copy.</param>
/// <exception cref="ArgumentException">Thrown when the specified range exceeds the bounds of the source collection or destination span.</exception>
public static void CopyTo<T>(this IUnTypedCollection source, Span<T> destination, uint sourceOffset, uint destinationIndex, uint length)
where T : unmanaged
{
var sizeOfElement = (uint)sizeof(T);
if (sourceOffset + (length * sizeOfElement) > source.Size || destinationIndex + length > destination.Length)
{
throw new ArgumentException("Source collection or destination span is too small for the specified range.");
}
fixed (T* pDest = destination)
{
MemCpy((byte*)source.GetUnsafePtr() + sourceOffset, pDest + destinationIndex, length * sizeOfElement);
}
}
@@ -62,17 +110,17 @@ public unsafe static class UnsafeCollectionExtensions
/// <param name="destination">Represents the unsafe collection that will receive the copied elements.</param>
/// <param name="source">Represents the span containing the elements to be copied to the unsafe collection.</param>
/// <exception cref="ArgumentException">Thrown when the source span and destination collection have different sizes.</exception>
public static void CopyFrom<T>(this IUnsafeCollection<T> destination, Span<T> source)
public static void CopyFrom<T>(this IUnsafeCollection<T> destination, ReadOnlySpan<T> source)
where T : unmanaged
{
if (destination.Count > source.Length)
if (destination.Count < source.Length)
{
throw new ArgumentException("Destination collection is larger than the source span.");
throw new ArgumentException("Destination collection is smaller than the source span.");
}
fixed (T* ptr = source)
fixed (T* pSrc = source)
{
SystemUnsfae.CopyBlock(destination.GetUnsafePtr(), ptr, (uint)(source.Length * sizeof(T)));
MemCpy(pSrc, destination.GetUnsafePtr(), (uint)(source.Length * sizeof(T)));
}
}
@@ -86,7 +134,7 @@ public unsafe static class UnsafeCollectionExtensions
/// <param name="destinationIndex">The starting index in the destination collection where the elements will be placed.</param>
/// <param name="length">The number of elements to copy from the source span to the destination collection.</param>
/// <exception cref="ArgumentException">Thrown when the specified range exceeds the bounds of the source span or destination collection.</exception>
public static void CopyFrom<T>(this IUnsafeCollection<T> destination, Span<T> source, int sourceIndex, int destinationIndex, int length)
public static void CopyFrom<T>(this IUnsafeCollection<T> destination, ReadOnlySpan<T> source, uint sourceIndex, uint destinationIndex, uint length)
where T : unmanaged
{
if (sourceIndex + length > source.Length || destinationIndex + length > destination.Count)
@@ -94,9 +142,57 @@ public unsafe static class UnsafeCollectionExtensions
throw new ArgumentException("Source span or destination collection is too small for the specified range.");
}
fixed (T* ptr = source)
fixed (T* pSrc = source)
{
SystemUnsfae.CopyBlock((byte*)destination.GetUnsafePtr() + destinationIndex * sizeof(T), ptr + sourceIndex, (uint)(length * sizeof(T)));
MemCpy(pSrc + sourceIndex, (byte*)destination.GetUnsafePtr() + destinationIndex * sizeof(T), (uint)(length * sizeof(T)));
}
}
/// <summary>
/// Copies elements from a typed source span to an untyped destination collection.
/// </summary>
/// <typeparam name="T">Specifies the type of elements in the source span, which must be unmanaged.</typeparam>
/// <param name="destination">The untyped collection that will receive the copied data.</param>
/// <param name="source">The typed span containing the elements to be copied.</param>
/// <exception cref="ArgumentException">Thrown when the destination collection is smaller than the source span data size.</exception>
public static void CopyFrom<T>(this IUnTypedCollection destination, ReadOnlySpan<T> source)
where T : unmanaged
{
var sourceSize = (uint)(source.Length * sizeof(T));
if (destination.Size < sourceSize)
{
throw new ArgumentException("Destination collection is smaller than the source span.");
}
fixed (T* pSrc = source)
{
MemCpy(pSrc, destination.GetUnsafePtr(), sourceSize);
}
}
/// <summary>
/// Copies a range of elements from a typed source span to an untyped destination collection at a specified byte offset.
/// </summary>
/// <typeparam name="T">Specifies the type of elements in the source span, which must be unmanaged.</typeparam>
/// <param name="destination">The untyped collection where the data will be copied to.</param>
/// <param name="source">The typed span containing the elements to be copied.</param>
/// <param name="sourceIndex">The starting element index in the source span from which to begin copying.</param>
/// <param name="destinationOffset">The byte offset in the destination collection where the data will be placed.</param>
/// <param name="length">The number of elements to copy from the source span.</param>
/// <exception cref="ArgumentException">Thrown when the specified range exceeds the bounds of the source span or destination collection.</exception>
public static void CopyFrom<T>(this IUnTypedCollection destination, ReadOnlySpan<T> source, uint sourceIndex, uint destinationOffset, uint length)
where T : unmanaged
{
var sizeOfElement = (uint)sizeof(T);
if (sourceIndex + length > source.Length || destinationOffset + (length * sizeOfElement) > destination.Size)
{
throw new ArgumentException("Source span or destination collection is too small for the specified range.");
}
fixed (T* pSrc = source)
{
MemCpy(pSrc + sourceIndex, (byte*)destination.GetUnsafePtr() + destinationOffset, length * sizeOfElement);
}
}
@@ -112,25 +208,49 @@ public unsafe static class UnsafeCollectionExtensions
return new(source.GetUnsafePtr(), source.Count);
}
/// <summary>
/// Converts an UnTypedCollection into a Span for efficient memory access.
/// </summary>
/// <param name="source">The UnTypedCollection instance to be converted into a Span.</param>
/// <returns>A Span that provides a view over the elements of the UnsafeCollection.</returns>
public static Span<byte> AsSpan(this IUnTypedCollection source)
{
return new(source.GetUnsafePtr(), (int)source.Size);
}
/// <summary>
/// Converts a managed array to an UnsafeArray by copying its elements to unmanaged memory.
/// </summary>
/// <typeparam name="T">The type of elements in the array, which must be unmanaged.</typeparam>
/// <param name="source">The managed array to convert.</param>
/// <param name="allocator">The allocator to use for memory allocation of the UnsafeArray.</param>
/// <returns>A new UnsafeArray containing a copy of the source array elements.</returns>
public static UnsafeArray<T> ToUnsafeArray<T>(this T[] source, Allocator allocator)
where T : unmanaged
{
var array = new UnsafeArray<T>(source.Length, allocator);
fixed (T* ptr = source)
fixed (T* pSrc = source)
{
MemCpy(array.GetUnsafePtr(), ptr, (uint)(source.Length * sizeof(T)));
MemCpy(pSrc, array.GetUnsafePtr(), (uint)(source.Length * sizeof(T)));
}
return array;
}
/// <summary>
/// Converts a managed List to an UnsafeList by copying its elements to unmanaged memory.
/// </summary>
/// <typeparam name="T">The type of elements in the list, which must be unmanaged.</typeparam>
/// <param name="source">The managed List to convert.</param>
/// <param name="allocator">The allocator to use for memory allocation of the UnsafeList.</param>
/// <returns>A new UnsafeList containing a copy of the source list elements.</returns>
public static UnsafeList<T> ToUnsafeList<T>(this List<T> source, Allocator allocator)
where T : unmanaged
{
var list = new UnsafeList<T>(source.Count, allocator);
fixed (T* ptr = CollectionsMarshal.AsSpan(source))
fixed (T* pSrc = CollectionsMarshal.AsSpan(source))
{
MemCpy(list.GetUnsafePtr(), ptr, (uint)(source.Count * sizeof(T)));
MemCpy(pSrc, list.GetUnsafePtr(), (uint)(source.Count * sizeof(T)));
}
return list;

View File

@@ -13,8 +13,9 @@ public static unsafe class UnsafeUtilities
/// <returns>Returns a reference of the specified type pointing to the given memory address.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref T AsRef<T>(void* ptr)
where T : unmanaged
{
return ref SystemUnsfae.AsRef<T>(ptr);
return ref *(T*)ptr;
}
/// <summary>
@@ -25,8 +26,9 @@ public static unsafe class UnsafeUtilities
/// <returns>A pointer to the memory address of the specified variable.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void* AddressOf<T>(ref T value)
where T : unmanaged
{
return SystemUnsfae.AsPointer(ref value);
return Unsafe.AsPointer(ref value);
}
/// <summary>
@@ -37,7 +39,22 @@ public static unsafe class UnsafeUtilities
/// <param name="index">Indicates the position of the element to be accessed within the array.</param>
/// <returns>Returns a pointer to the element located at the specified index.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T* ReadArrayElementUnsafe<T>(void* ptr, int index) where T : unmanaged
public static T* ReadArrayElementUnsafe<T>(void* ptr, int index)
where T : unmanaged
{
return (T*)((byte*)ptr + index * sizeof(T));
}
/// <summary>
/// Reads an element from an unmanaged array at a specified index using a pointer.
/// </summary>
/// <typeparam name="T">Specifies the type of elements in the unmanaged array.</typeparam>
/// <param name="ptr">Points to the start of the unmanaged array from which the element is read.</param>
/// <param name="index">Indicates the position of the element to be accessed within the array.</param>
/// <returns>Returns a pointer to the element located at the specified index.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T* ReadArrayElementUnsafe<T>(void* ptr, uint index)
where T : unmanaged
{
return (T*)((byte*)ptr + index * sizeof(T));
}
@@ -50,7 +67,22 @@ public static unsafe class UnsafeUtilities
/// <param name="index">Indicates the position of the element to be accessed in the array.</param>
/// <returns>A reference to the specified element in the unmanaged array.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref T ReadArrayElementRef<T>(void* ptr, int index) where T : unmanaged
public static ref T ReadArrayElementRef<T>(void* ptr, int index)
where T : unmanaged
{
return ref AsRef<T>(ReadArrayElementUnsafe<T>(ptr, index));
}
/// <summary>
/// Reads an element from an unmanaged array using a pointer and index, returning a reference to the element.
/// </summary>
/// <typeparam name="T">Specifies the type of the elements in the unmanaged array.</typeparam>
/// <param name="ptr">Points to the start of the unmanaged array from which the element is read.</param>
/// <param name="index">Indicates the position of the element to be accessed in the array.</param>
/// <returns>A reference to the specified element in the unmanaged array.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref T ReadArrayElementRef<T>(void* ptr, uint index)
where T : unmanaged
{
return ref AsRef<T>(ReadArrayElementUnsafe<T>(ptr, index));
}
@@ -63,7 +95,22 @@ public static unsafe class UnsafeUtilities
/// <param name="index">Indicates the position of the element to be accessed within the array.</param>
/// <returns>The element located at the specified index in the array.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T ReadArrayElement<T>(void* ptr, int index) where T : unmanaged
public static T ReadArrayElement<T>(void* ptr, int index)
where T : unmanaged
{
return *ReadArrayElementUnsafe<T>(ptr, index);
}
/// <summary>
/// Reads an element from an array at a specified index using a pointer to the array.
/// </summary>
/// <typeparam name="T">Specifies the type of the elements in the array, which must be unmanaged.</typeparam>
/// <param name="ptr">Points to the start of the array from which an element will be read.</param>
/// <param name="index">Indicates the position of the element to be accessed within the array.</param>
/// <returns>The element located at the specified index in the array.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T ReadArrayElement<T>(void* ptr, uint index)
where T : unmanaged
{
return *ReadArrayElementUnsafe<T>(ptr, index);
}
@@ -76,7 +123,22 @@ public static unsafe class UnsafeUtilities
/// <param name="index">Indicates the position in the array where the value should be stored.</param>
/// <param name="value">Represents the value to be written to the specified index of the array.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteArrayElement<T>(void* ptr, int index, T value) where T : unmanaged
public static void WriteArrayElement<T>(void* ptr, int index, T value)
where T : unmanaged
{
*ReadArrayElementUnsafe<T>(ptr, index) = value;
}
/// <summary>
/// Writes a value to a specified index of an unmanaged array using a pointer.
/// </summary>
/// <typeparam name="T">Specifies the type of the value being written to the array, which must be an unmanaged type.</typeparam>
/// <param name="ptr">Points to the beginning of the unmanaged array where the value will be written.</param>
/// <param name="index">Indicates the position in the array where the value should be stored.</param>
/// <param name="value">Represents the value to be written to the specified index of the array.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteArrayElement<T>(void* ptr, uint index, T value)
where T : unmanaged
{
*ReadArrayElementUnsafe<T>(ptr, index) = value;
}
@@ -92,6 +154,6 @@ public static unsafe class UnsafeUtilities
public static UnsafeArray<TOut> CastArray<TIn, TOut>(UnsafeArray<TIn> array)
where TIn : unmanaged where TOut : unmanaged
{
return new UnsafeArray<TOut>(array.GetUnsafePtr(), array.Count * sizeof(TIn) / sizeof(TOut));
return new UnsafeArray<TOut>((TOut*)array.GetUnsafePtr(), array.Count * sizeof(TIn) / sizeof(TOut));
}
}