Refactor and enhance math, memory, and utilities

Refactored `sincos` usage in `quaternion` to use tuple-based
returns for improved readability. Introduced a `random` struct
with methods for generating random values of various types
and dimensions, including ranges and directions. Added a
`DynamicArray` class for dynamic resizing and manipulation
of collections. Enhanced `SlotMap` with new methods for
safe access and updates.

Updated `uint` vector types with `NumericConvertable`
attributes for better type interoperability. Removed the
`MathUtilities` class and refactored `adj` and `adjInverse`
methods for encapsulation. Improved memory management
with `StackAllocator` and `UnsafeArray` enhancements.

Added geometry utilities like `AABB`, `OBB`, `Plane`, and
`SphereBounds` for 3D operations. Updated project
configuration for versioning and NuGet packaging.
Performed general code cleanup, improved validation, and
aligned with modern C# practices.
This commit is contained in:
2025-09-23 22:57:12 +09:00
parent 73a0c6e187
commit ac73e28f26
52 changed files with 3658 additions and 5150 deletions

View File

@@ -1,4 +1,4 @@
global using static Misaki.HighPerformance.LowLevel.Helpers.MemoryUtilities;
global using static Misaki.HighPerformance.LowLevel.Utilities.MemoryUtilities;
global using unsafe AllocFunc = delegate*<void*, nuint, nuint, Misaki.HighPerformance.LowLevel.Buffer.AllocationOption, void*>;

View File

@@ -77,6 +77,7 @@ public static unsafe class AllocationManager
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;
}
@@ -129,6 +130,7 @@ public static unsafe class AllocationManager
{
var newPtr = AlignedRealloc(ptr, size, alignment);
UpdateAllocation(ptr, newPtr, size, instance, &FreeBlock);
return newPtr;
}
@@ -139,10 +141,50 @@ public static unsafe class AllocationManager
}
}
private const uint _DEFAULT_ARENA_SIZE = 512 * 1024;
private unsafe struct StackAllocator : IAllocator
{
[ThreadStatic]
private static Stack s_stack;
private AllocationHandle _handle;
public readonly ref AllocationHandle Handle => ref Unsafe.AsRef(in _handle);
public void Init()
{
_handle = new(Unsafe.AsPointer(ref this), &Allocate, &Reallocate, &FreeBlock);
}
private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption)
{
var ptr = s_stack.Allocate(size, alignment, allocationOption);
return ptr;
}
private static void* Reallocate(void* instance, void* ptr, nuint size, nuint alignment)
{
var newPtr = s_stack.Allocate(size, alignment, AllocationOption.None);
MemCpy(newPtr, ptr, size);
// We do not free the old pointer here, as it is managed by the stack.
return newPtr;
}
private static void FreeBlock(void* instance, void* ptr)
{
// The stack allocator does not free individual blocks, as it manages memory in a stack-like manner.
}
public static Stack.Scope CreateScope()
{
return s_stack.CreateScope();
}
}
private const uint _DEFAULT_MEMORY_POOL_SIZE = 512 * 1024;
private static readonly ArenaAllocator* s_arenaAllocator;
private static readonly HeapAllocator* s_persistentAllocator;
private static readonly StackAllocator* s_stackAllocator;
private static bool s_debugLayer;
private static ConcurrentDictionary<nint, AllocationInfo>? s_allocated;
@@ -151,9 +193,11 @@ public static unsafe class AllocationManager
{
s_arenaAllocator = (ArenaAllocator*)NativeMemory.Alloc((nuint)sizeof(ArenaAllocator));
s_persistentAllocator = (HeapAllocator*)NativeMemory.Alloc((nuint)sizeof(HeapAllocator));
s_stackAllocator = (StackAllocator*)NativeMemory.Alloc((nuint)sizeof(StackAllocator));
s_arenaAllocator->Init(_DEFAULT_ARENA_SIZE);
s_arenaAllocator->Init(_DEFAULT_MEMORY_POOL_SIZE);
s_persistentAllocator->Init();
s_stackAllocator->Init();
}
/// <summary>
@@ -181,6 +225,8 @@ public static unsafe class AllocationManager
return ref s_arenaAllocator->Handle;
case Allocator.Persistent:
return ref s_persistentAllocator->Handle;
case Allocator.Stack:
return ref s_stackAllocator->Handle;
default:
throw new ArgumentException("Target allocator type does not support custom allocation.", nameof(allocator));
}
@@ -263,6 +309,16 @@ public static unsafe class AllocationManager
s_arenaAllocator->Reset();
}
/// <summary>
/// Creates a new thread local stack scope for managing temporary allocations within the current context.
/// </summary>
/// <returns>A <see cref="Stack.Scope"/> instance representing the newly created stack scope. The scope must be disposed when no longer needed to release allocated resources.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Stack.Scope CreateStackScope()
{
return StackAllocator.CreateScope();
}
/// <summary>
/// Disposes of the AllocationManager, freeing all allocated memory and resources.
/// </summary>
@@ -291,6 +347,11 @@ public static unsafe class AllocationManager
NativeMemory.Free(s_arenaAllocator);
}
if (s_stackAllocator != null)
{
NativeMemory.Free(s_stackAllocator);
}
if (s_persistentAllocator != null)
{
NativeMemory.Free(s_persistentAllocator);

View File

@@ -23,11 +23,15 @@ public enum Allocator : byte
// Make the first allocator as invalid because we don't want to user create a default collection without passing any parameters
Invalid,
/// <summary>
/// Allocator for temporary allocations. Allocations are cleared after use.
/// Allocator for temporary allocations. Allocations are released after use automatically.
/// </summary>
Temp,
/// <summary>
/// Allocator for persistent allocations. Allocations are not cleared after use.
/// Allocator for persistent allocations. Allocations are not released after use.
/// </summary>
Persistent
Persistent,
/// <summary>
/// Allocator for stack allocations. Must have at least one active stack scope. Allocations are released when the stack scope is exited.
/// </summary>
Stack
}

View File

@@ -16,11 +16,6 @@ public unsafe struct Arena : IDisposable
private nuint _offset;
public Arena(nuint size)
{
Initialize(size);
}
public void Initialize(nuint size)
{
if (_buffer != null)
{
@@ -46,7 +41,17 @@ public unsafe struct Arena : IDisposable
{
if (_buffer == null)
{
throw new ObjectDisposedException(nameof(DynamicArena));
throw new ObjectDisposedException(nameof(Arena));
}
if (size == 0)
{
return null;
}
if ((alignment & (alignment - 1)) != 0)
{
throw new ArgumentException("Alignment must be a power of two.", nameof(alignment));
}
nuint currentOffset, newOffset, alignedOffset;

View File

@@ -1,894 +0,0 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
namespace Misaki.HighPerformance.LowLevel.Buffer;
/// <summary>
/// Represents a heap allocated fixed-size UTF-8 encoded string of length 32 bytes.
/// </summary>
[StructLayout(LayoutKind.Sequential, Size = 32)]
public unsafe struct FixedString32 : IDisposable
{
private ushort _length;
private byte* _buffer;
public ushort Length => _length;
public string Value
{
readonly get
{
return Encoding.UTF8.GetString(_buffer, _length);
}
set
{
if (string.IsNullOrEmpty(value))
{
_length = 0;
return;
}
var maxBytes = Encoding.UTF8.GetByteCount(value);
if (maxBytes > 30)
{
throw new ArgumentException("Input string is too long to fit in FixedString32.");
}
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(_buffer, 30));
}
}
public FixedString32(ReadOnlySpan<char> input)
{
var maxBytes = Encoding.UTF8.GetByteCount(input);
if (maxBytes > 30)
{
throw new ArgumentException("Input string is too long to fit in FixedString32.");
}
_buffer = (byte*)NativeMemory.Alloc(30);
fixed (char* inputPtr = input)
{
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, _buffer, 30);
_length = (ushort)actualByteCount;
}
}
public FixedString32(string input)
: this(input.AsSpan())
{
}
public FixedString32(char* input, ushort length)
: this(new Span<char>(input, length))
{
}
public FixedString32(ReadOnlySpan<byte> input)
{
if (input.Length > 30)
{
throw new ArgumentException("Input byte array is too long to fit in FixedString32.");
}
_buffer = (byte*)NativeMemory.Alloc(30);
_length = (ushort)input.Length;
fixed (byte* inputPtr = input)
{
Unsafe.CopyBlockUnaligned(_buffer, inputPtr, _length);
}
}
public FixedString32(byte* input, ushort length)
: this(new ReadOnlySpan<byte>(input, length))
{
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly Span<byte> AsSpan()
{
return new(_buffer, _length);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly byte* GetUnsafePointer()
{
return _buffer;
}
public override string ToString()
{
return Value;
}
public void Dispose()
{
if (_buffer != null)
{
NativeMemory.Free(_buffer);
_length = 0;
_buffer = null;
}
}
}
/// <summary>
/// Represents a heap allocated fixed-size UTF-8 encoded string of length 64 bytes.
/// </summary>
[StructLayout(LayoutKind.Sequential, Size = 64)]
public unsafe struct FixedString64 : IDisposable
{
private ushort _length;
private byte* _buffer;
public ushort Length => _length;
public string Value
{
readonly get
{
return Encoding.UTF8.GetString(_buffer, _length);
}
set
{
if (string.IsNullOrEmpty(value))
{
_length = 0;
return;
}
var maxBytes = Encoding.UTF8.GetByteCount(value);
if (maxBytes > 62)
{
throw new ArgumentException("Input string is too long to fit in FixedString64.");
}
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(_buffer, 62));
}
}
public FixedString64(ReadOnlySpan<char> input)
{
var maxBytes = Encoding.UTF8.GetByteCount(input);
if (maxBytes > 62)
{
throw new ArgumentException("Input string is too long to fit in FixedString64.");
}
_buffer = (byte*)NativeMemory.Alloc(62);
fixed (char* inputPtr = input)
{
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, _buffer, 62);
_length = (ushort)actualByteCount;
}
}
public FixedString64(string input)
: this(input.AsSpan())
{
}
public FixedString64(char* input, ushort length)
: this(new Span<char>(input, length))
{
}
public FixedString64(ReadOnlySpan<byte> input)
{
if (input.Length > 62)
{
throw new ArgumentException("Input byte array is too long to fit in FixedString64.");
}
_buffer = (byte*)NativeMemory.Alloc(62);
_length = (ushort)input.Length;
fixed (byte* inputPtr = input)
{
Unsafe.CopyBlockUnaligned(_buffer, inputPtr, _length);
}
}
public FixedString64(byte* input, ushort length)
: this(new ReadOnlySpan<byte>(input, length))
{
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly Span<byte> AsSpan()
{
return new(_buffer, _length);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly byte* GetUnsafePointer()
{
return _buffer;
}
public override string ToString()
{
return Value;
}
public void Dispose()
{
if (_buffer != null)
{
NativeMemory.Free(_buffer);
_length = 0;
_buffer = null;
}
}
}
/// <summary>
/// Represents a heap allocated fixed-size UTF-8 encoded string of length 128 bytes.
/// </summary>
[StructLayout(LayoutKind.Sequential, Size = 128)]
public unsafe struct FixedString128 : IDisposable
{
private ushort _length;
private byte* _buffer;
public ushort Length => _length;
public string Value
{
readonly get
{
return Encoding.UTF8.GetString(_buffer, _length);
}
set
{
if (string.IsNullOrEmpty(value))
{
_length = 0;
return;
}
var maxBytes = Encoding.UTF8.GetByteCount(value);
if (maxBytes > 126)
{
throw new ArgumentException("Input string is too long to fit in FixedString128.");
}
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(_buffer, 126));
}
}
public FixedString128(ReadOnlySpan<char> input)
{
var maxBytes = Encoding.UTF8.GetByteCount(input);
if (maxBytes > 126)
{
throw new ArgumentException("Input string is too long to fit in FixedString128.");
}
_buffer = (byte*)NativeMemory.Alloc(126);
fixed (char* inputPtr = input)
{
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, _buffer, 126);
_length = (ushort)actualByteCount;
}
}
public FixedString128(string input)
: this(input.AsSpan())
{
}
public FixedString128(char* input, ushort length)
: this(new Span<char>(input, length))
{
}
public FixedString128(ReadOnlySpan<byte> input)
{
if (input.Length > 126)
{
throw new ArgumentException("Input byte array is too long to fit in FixedString128.");
}
_buffer = (byte*)NativeMemory.Alloc(126);
_length = (ushort)input.Length;
fixed (byte* inputPtr = input)
{
Unsafe.CopyBlockUnaligned(_buffer, inputPtr, _length);
}
}
public FixedString128(byte* input, ushort length)
: this(new ReadOnlySpan<byte>(input, length))
{
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly Span<byte> AsSpan()
{
return new(_buffer, _length);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly byte* GetUnsafePointer()
{
return _buffer;
}
public override string ToString()
{
return Value;
}
public void Dispose()
{
if (_buffer != null)
{
NativeMemory.Free(_buffer);
_length = 0;
_buffer = null;
}
}
}
/// <summary>
/// Represents a heap allocated fixed-size UTF-8 encoded string of length 256 bytes.
/// </summary>
[StructLayout(LayoutKind.Sequential, Size = 256)]
public unsafe struct FixedString256 : IDisposable
{
private ushort _length;
private byte* _buffer;
public ushort Length => _length;
public string Value
{
readonly get
{
return Encoding.UTF8.GetString(_buffer, _length);
}
set
{
if (string.IsNullOrEmpty(value))
{
_length = 0;
return;
}
var maxBytes = Encoding.UTF8.GetByteCount(value);
if (maxBytes > 254)
{
throw new ArgumentException("Input string is too long to fit in FixedString256.");
}
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(_buffer, 254));
}
}
public FixedString256(ReadOnlySpan<char> input)
{
var maxBytes = Encoding.UTF8.GetByteCount(input);
if (maxBytes > 254)
{
throw new ArgumentException("Input string is too long to fit in FixedString256.");
}
_buffer = (byte*)NativeMemory.Alloc(254);
fixed (char* inputPtr = input)
{
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, _buffer, 254);
_length = (ushort)actualByteCount;
}
}
public FixedString256(string input)
: this(input.AsSpan())
{
}
public FixedString256(char* input, ushort length)
: this(new Span<char>(input, length))
{
}
public FixedString256(ReadOnlySpan<byte> input)
{
if (input.Length > 254)
{
throw new ArgumentException("Input byte array is too long to fit in FixedString256.");
}
_buffer = (byte*)NativeMemory.Alloc(254);
_length = (ushort)input.Length;
fixed (byte* inputPtr = input)
{
Unsafe.CopyBlockUnaligned(_buffer, inputPtr, _length);
}
}
public FixedString256(byte* input, ushort length)
: this(new ReadOnlySpan<byte>(input, length))
{
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly Span<byte> AsSpan()
{
return new(_buffer, _length);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly byte* GetUnsafePointer()
{
return _buffer;
}
public override string ToString()
{
return Value;
}
public void Dispose()
{
if (_buffer != null)
{
NativeMemory.Free(_buffer);
_length = 0;
_buffer = null;
}
}
}
/// <summary>
/// Represents a heap allocated fixed-size UTF-8 encoded string of length 512 bytes.
/// </summary>
[StructLayout(LayoutKind.Sequential, Size = 512)]
public unsafe struct FixedString512 : IDisposable
{
private ushort _length;
private byte* _buffer;
public ushort Length => _length;
public string Value
{
readonly get
{
return Encoding.UTF8.GetString(_buffer, _length);
}
set
{
if (string.IsNullOrEmpty(value))
{
_length = 0;
return;
}
var maxBytes = Encoding.UTF8.GetByteCount(value);
if (maxBytes > 510)
{
throw new ArgumentException("Input string is too long to fit in FixedString512.");
}
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(_buffer, 510));
}
}
public FixedString512(ReadOnlySpan<char> input)
{
var maxBytes = Encoding.UTF8.GetByteCount(input);
if (maxBytes > 510)
{
throw new ArgumentException("Input string is too long to fit in FixedString512.");
}
_buffer = (byte*)NativeMemory.Alloc(510);
fixed (char* inputPtr = input)
{
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, _buffer, 510);
_length = (ushort)actualByteCount;
}
}
public FixedString512(string input)
: this(input.AsSpan())
{
}
public FixedString512(char* input, ushort length)
: this(new Span<char>(input, length))
{
}
public FixedString512(ReadOnlySpan<byte> input)
{
if (input.Length > 510)
{
throw new ArgumentException("Input byte array is too long to fit in FixedString512.");
}
_buffer = (byte*)NativeMemory.Alloc(510);
_length = (ushort)input.Length;
fixed (byte* inputPtr = input)
{
Unsafe.CopyBlockUnaligned(_buffer, inputPtr, _length);
}
}
public FixedString512(byte* input, ushort length)
: this(new ReadOnlySpan<byte>(input, length))
{
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly Span<byte> AsSpan()
{
return new(_buffer, _length);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly byte* GetUnsafePointer()
{
return _buffer;
}
public override string ToString()
{
return Value;
}
public void Dispose()
{
if (_buffer != null)
{
NativeMemory.Free(_buffer);
_length = 0;
_buffer = null;
}
}
}
/// <summary>
/// Represents a heap allocated fixed-size UTF-8 encoded string of length 1024 bytes.
/// </summary>
[StructLayout(LayoutKind.Sequential, Size = 1024)]
public unsafe struct FixedString1024 : IDisposable
{
private ushort _length;
private byte* _buffer;
public ushort Length => _length;
public string Value
{
readonly get
{
return Encoding.UTF8.GetString(_buffer, _length);
}
set
{
if (string.IsNullOrEmpty(value))
{
_length = 0;
return;
}
var maxBytes = Encoding.UTF8.GetByteCount(value);
if (maxBytes > 1022)
{
throw new ArgumentException("Input string is too long to fit in FixedString1024.");
}
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(_buffer, 1022));
}
}
public FixedString1024(ReadOnlySpan<char> input)
{
var maxBytes = Encoding.UTF8.GetByteCount(input);
if (maxBytes > 1022)
{
throw new ArgumentException("Input string is too long to fit in FixedString1024.");
}
_buffer = (byte*)NativeMemory.Alloc(1022);
fixed (char* inputPtr = input)
{
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, _buffer, 1022);
_length = (ushort)actualByteCount;
}
}
public FixedString1024(string input)
: this(input.AsSpan())
{
}
public FixedString1024(char* input, ushort length)
: this(new Span<char>(input, length))
{
}
public FixedString1024(ReadOnlySpan<byte> input)
{
if (input.Length > 1022)
{
throw new ArgumentException("Input byte array is too long to fit in FixedString1024.");
}
_buffer = (byte*)NativeMemory.Alloc(1022);
_length = (ushort)input.Length;
fixed (byte* inputPtr = input)
{
Unsafe.CopyBlockUnaligned(_buffer, inputPtr, _length);
}
}
public FixedString1024(byte* input, ushort length)
: this(new ReadOnlySpan<byte>(input, length))
{
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly Span<byte> AsSpan()
{
return new(_buffer, _length);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly byte* GetUnsafePointer()
{
return _buffer;
}
public override string ToString()
{
return Value;
}
public void Dispose()
{
if (_buffer != null)
{
NativeMemory.Free(_buffer);
_length = 0;
_buffer = null;
}
}
}
/// <summary>
/// Represents a heap allocated fixed-size UTF-8 encoded string of length 2048 bytes.
/// </summary>
[StructLayout(LayoutKind.Sequential, Size = 2048)]
public unsafe struct FixedString2048 : IDisposable
{
private ushort _length;
private byte* _buffer;
public ushort Length => _length;
public string Value
{
readonly get
{
return Encoding.UTF8.GetString(_buffer, _length);
}
set
{
if (string.IsNullOrEmpty(value))
{
_length = 0;
return;
}
var maxBytes = Encoding.UTF8.GetByteCount(value);
if (maxBytes > 2046)
{
throw new ArgumentException("Input string is too long to fit in FixedString2048.");
}
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(_buffer, 2046));
}
}
public FixedString2048(ReadOnlySpan<char> input)
{
var maxBytes = Encoding.UTF8.GetByteCount(input);
if (maxBytes > 2046)
{
throw new ArgumentException("Input string is too long to fit in FixedString2048.");
}
_buffer = (byte*)NativeMemory.Alloc(2046);
fixed (char* inputPtr = input)
{
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, _buffer, 2046);
_length = (ushort)actualByteCount;
}
}
public FixedString2048(string input)
: this(input.AsSpan())
{
}
public FixedString2048(char* input, ushort length)
: this(new Span<char>(input, length))
{
}
public FixedString2048(ReadOnlySpan<byte> input)
{
if (input.Length > 2046)
{
throw new ArgumentException("Input byte array is too long to fit in FixedString2048.");
}
_buffer = (byte*)NativeMemory.Alloc(2046);
_length = (ushort)input.Length;
fixed (byte* inputPtr = input)
{
Unsafe.CopyBlockUnaligned(_buffer, inputPtr, _length);
}
}
public FixedString2048(byte* input, ushort length)
: this(new ReadOnlySpan<byte>(input, length))
{
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly Span<byte> AsSpan()
{
return new(_buffer, _length);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly byte* GetUnsafePointer()
{
return _buffer;
}
public override string ToString()
{
return Value;
}
public void Dispose()
{
if (_buffer != null)
{
NativeMemory.Free(_buffer);
_length = 0;
_buffer = null;
}
}
}
/// <summary>
/// Represents a heap allocated fixed-size UTF-8 encoded string of length 4096 bytes.
/// </summary>
[StructLayout(LayoutKind.Sequential, Size = 4096)]
public unsafe struct FixedString4096 : IDisposable
{
private ushort _length;
private byte* _buffer;
public ushort Length => _length;
public string Value
{
readonly get
{
return Encoding.UTF8.GetString(_buffer, _length);
}
set
{
if (string.IsNullOrEmpty(value))
{
_length = 0;
return;
}
var maxBytes = Encoding.UTF8.GetByteCount(value);
if (maxBytes > 4094)
{
throw new ArgumentException("Input string is too long to fit in FixedString4096.");
}
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(_buffer, 4094));
}
}
public FixedString4096(ReadOnlySpan<char> input)
{
var maxBytes = Encoding.UTF8.GetByteCount(input);
if (maxBytes > 4094)
{
throw new ArgumentException("Input string is too long to fit in FixedString4096.");
}
_buffer = (byte*)NativeMemory.Alloc(4094);
fixed (char* inputPtr = input)
{
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, _buffer, 4094);
_length = (ushort)actualByteCount;
}
}
public FixedString4096(string input)
: this(input.AsSpan())
{
}
public FixedString4096(char* input, ushort length)
: this(new Span<char>(input, length))
{
}
public FixedString4096(ReadOnlySpan<byte> input)
{
if (input.Length > 4094)
{
throw new ArgumentException("Input byte array is too long to fit in FixedString4096.");
}
_buffer = (byte*)NativeMemory.Alloc(4094);
_length = (ushort)input.Length;
fixed (byte* inputPtr = input)
{
Unsafe.CopyBlockUnaligned(_buffer, inputPtr, _length);
}
}
public FixedString4096(byte* input, ushort length)
: this(new ReadOnlySpan<byte>(input, length))
{
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly Span<byte> AsSpan()
{
return new(_buffer, _length);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly byte* GetUnsafePointer()
{
return _buffer;
}
public override string ToString()
{
return Value;
}
public void Dispose()
{
if (_buffer != null)
{
NativeMemory.Free(_buffer);
_length = 0;
_buffer = null;
}
}
}

View File

@@ -1,125 +0,0 @@
<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".cs" #>
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
namespace Misaki.HighPerformance.LowLevel.Buffer;
<# for (int i = 32; i <= 4096; i *= 2) { #>
/// <summary>
/// Represents a heap allocated fixed-size UTF-8 encoded string of length <#= i #> bytes.
/// </summary>
[StructLayout(LayoutKind.Sequential, Size = <#= i #>)]
public unsafe struct FixedString<#= i #> : IDisposable
{
private ushort _length;
private byte* _buffer;
public ushort Length => _length;
public string Value
{
readonly get
{
return Encoding.UTF8.GetString(_buffer, _length);
}
set
{
if (string.IsNullOrEmpty(value))
{
_length = 0;
return;
}
var maxBytes = Encoding.UTF8.GetByteCount(value);
if (maxBytes > <#= i - 2 #>)
{
throw new ArgumentException("Input string is too long to fit in FixedString<#= i #>.");
}
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(_buffer, <#= i - 2 #>));
}
}
public FixedString<#= i #>(ReadOnlySpan<char> input)
{
var maxBytes = Encoding.UTF8.GetByteCount(input);
if (maxBytes > <#= i - 2 #>)
{
throw new ArgumentException("Input string is too long to fit in FixedString<#= i #>.");
}
_buffer = (byte*)NativeMemory.Alloc(<#= i - 2 #>);
fixed (char* inputPtr = input)
{
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, _buffer, <#= i - 2 #>);
_length = (ushort)actualByteCount;
}
}
public FixedString<#= i #>(string input)
: this(input.AsSpan())
{
}
public FixedString<#= i #>(char* input, ushort length)
: this(new Span<char>(input, length))
{
}
public FixedString<#= i #>(ReadOnlySpan<byte> input)
{
if (input.Length > <#= i - 2 #>)
{
throw new ArgumentException("Input byte array is too long to fit in FixedString<#= i #>.");
}
_buffer = (byte*)NativeMemory.Alloc(<#= i - 2 #>);
_length = (ushort)input.Length;
fixed (byte* inputPtr = input)
{
Unsafe.CopyBlockUnaligned(_buffer, inputPtr, _length);
}
}
public FixedString<#= i #>(byte* input, ushort length)
: this(new ReadOnlySpan<byte>(input, length))
{
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly Span<byte> AsSpan()
{
return new(_buffer, _length);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly byte* GetUnsafePointer()
{
return _buffer;
}
public override string ToString()
{
return Value;
}
public void Dispose()
{
if (_buffer != null)
{
NativeMemory.Free(_buffer);
_length = 0;
_buffer = null;
}
}
}
<# } #>

View File

@@ -186,7 +186,12 @@ public unsafe struct FreeList : IDisposable
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void* Allocate(nuint size, nuint alignment, AllocationOption allocationOption = AllocationOption.None)
{
if (_disposed != 0 || size == 0)
if (_disposed != 0)
{
throw new ObjectDisposedException(nameof(FreeList));
}
if (size == 0)
{
return null;
}
@@ -196,6 +201,11 @@ public unsafe struct FreeList : IDisposable
alignment = _alignment;
}
if ((alignment & (alignment - 1)) != 0)
{
throw new ArgumentException("Alignment must be a power of two.", nameof(alignment));
}
// Align size to alignment boundary
var alignedSize = (size + alignment - 1) & ~(alignment - 1);
alignedSize = Math.Max(alignedSize, _MIN_BLOCK_SIZE);

View File

@@ -0,0 +1,157 @@
using Misaki.HighPerformance.LowLevel.Utilities;
using System.Drawing;
using System.Runtime.CompilerServices;
namespace Misaki.HighPerformance.LowLevel.Buffer;
/// <summary>
/// Provides a stack-based memory allocator for unmanaged memory, enabling fast allocation and deallocation of memory
/// blocks within a preallocated buffer.
/// </summary>
/// <remarks>This is not a thread-safe implementation.</remarks>
public unsafe struct Stack : IDisposable
{
private const nuint _DEFAULT_SIZE = 1024 * 1024; // 1MB
public readonly ref struct Scope
{
private readonly Stack* _allocator;
private readonly nuint _originalOffset;
internal Scope(Stack* allocator)
{
_allocator = allocator;
_originalOffset = allocator->_offset;
_allocator->_activeScopeCount++;
}
public void Dispose()
{
if (_allocator != null)
{
_allocator->_offset = _allocator->_offset > _originalOffset ? _originalOffset : _allocator->_offset;
_allocator->_activeScopeCount--;
}
}
}
private byte* _buffer;
private nuint _size;
private nuint _offset;
private uint _activeScopeCount;
/// <summary>
/// Initializes a new instance of the StackAllocator class with a buffer of the specified size.
/// </summary>
/// <param name="size">The size, in bytes, of the memory buffer to allocate for stack-based allocations. Must be greater than zero.</param>
public Stack(nuint size)
{
if (size == 0)
{
throw new ArgumentException("Size must be greater than zero.", nameof(size));
}
Init(size);
}
private void Init(nuint size)
{
if (_buffer != null)
{
Free(_buffer);
}
_buffer = (byte*)Malloc(size);
_size = size;
_offset = 0;
_activeScopeCount = 0;
}
private readonly void ThrowIfNoScope()
{
if (_activeScopeCount == 0)
{
throw new InvalidOperationException("Allocations can only be made within an active memory scope.");
}
}
private void EnsureInitialize()
{
if (_buffer == null || _size == 0)
{
Init(_DEFAULT_SIZE);
}
}
/// <summary>
/// Creates a new scope instance associated with the current stack context.
/// </summary>
/// <returns>A <see cref="Scope"/> object that represents a scope tied to this stack.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Scope CreateScope()
{
EnsureInitialize();
return new Scope((Stack*)Unsafe.AsPointer(ref this));
}
/// <summary>
/// Allocates a block of memory of the specified size and alignment from the buffer.
/// </summary>
/// <param name="size">The number of bytes to allocate. Must be greater than zero and less than or equal to the remaining buffer size.</param>
/// <param name="alignment">The alignment, in bytes, for the allocated memory block. Must be a power of two and greater than zero.</param>
/// <param name="allocationOption">An option specifying additional allocation behavior, such as whether the allocated memory should be cleared. The
/// default is <see cref="AllocationOption.None"/>.</param>
/// <returns>A pointer to the beginning of the allocated memory block if successful; otherwise, <see langword="null"/> if
/// there is insufficient space in the buffer.</returns>
public void* Allocate(nuint size, nuint alignment, AllocationOption allocationOption = AllocationOption.None)
{
ThrowIfNoScope();
if (size == 0)
{
return null;
}
if ((alignment & (alignment - 1)) != 0)
{
throw new ArgumentException("Alignment must be a power of two.", nameof(alignment));
}
var alignedOffset = (_offset + alignment - 1) & ~(alignment - 1);
var newOffset = alignedOffset + size;
if (newOffset > _size)
{
throw new OutOfMemoryException("Insufficient memory in stack allocator.");
}
var ptr = _buffer + alignedOffset;
_offset = newOffset;
if (allocationOption.HasFlag(AllocationOption.Clear))
{
MemClear(ptr, size);
}
return ptr;
}
/// <summary>
/// Resets the internal offset to its initial position.
/// </summary>
public void Reset()
{
_offset = 0;
}
public void Dispose()
{
Free(_buffer);
_buffer = null;
_size = 0;
_offset = 0;
}
}

View File

@@ -36,6 +36,7 @@ public unsafe interface IUnsafeCollection<T> : IUnsafeCollection, IEnumerable<T>
/// <summary>
/// Changes the size of a collection to the specified value.
/// </summary>
/// <remarks>This is to adjust the element count of the collection, not the size of the underlying buffer in memory.</remarks>
/// <param name="newSize">Specifies the new size to which the collection should be adjusted.</param>
public void Resize(int newSize);
}

View File

@@ -2,7 +2,7 @@
using System.Runtime.InteropServices;
using System.Text;
namespace Misaki.HighPerformance.LowLevel.Buffer;
namespace Misaki.HighPerformance.LowLevel.Collections;
/// <summary>
/// Represents a stack allocated fixed-size UTF-8 encoded string of length 32 bytes.
@@ -12,12 +12,12 @@ namespace Misaki.HighPerformance.LowLevel.Buffer;
/// If you need a heap allocated fixed-size UTF-8 encoded string of length 32 bytes, consider using <see cref="Misaki.HighPerformance.Unsafe.Buffer.FixedString32"/>.
/// </remarks>
[StructLayout(LayoutKind.Sequential, Size = 32)]
public unsafe struct FixedStackString32
public unsafe struct FixedString32
{
private ushort _length;
private fixed byte _buffer[30];
public ushort Length => _length;
public readonly ushort Length => _length;
public string Value
{
get
@@ -38,7 +38,7 @@ public unsafe struct FixedStackString32
var maxBytes = Encoding.UTF8.GetByteCount(value);
if (maxBytes > 30)
{
throw new ArgumentException("Input string is too long to fit in FixedStackString32.");
throw new ArgumentException("Input string is too long to fit in FixedString32.");
}
fixed (byte* bufferPtr = _buffer)
@@ -48,7 +48,7 @@ public unsafe struct FixedStackString32
}
}
public FixedStackString32(ReadOnlySpan<char> input)
public FixedString32(ReadOnlySpan<char> input)
{
var maxBytes = Encoding.UTF8.GetByteCount(input);
if (maxBytes > 30)
@@ -64,17 +64,17 @@ public unsafe struct FixedStackString32
}
}
public FixedStackString32(string input)
public FixedString32(string input)
: this(input.AsSpan())
{
}
public FixedStackString32(char* input, ushort length)
public FixedString32(char* input, ushort length)
: this(new Span<char>(input, length))
{
}
public FixedStackString32(ReadOnlySpan<byte> input)
public FixedString32(ReadOnlySpan<byte> input)
{
if (input.Length > 30)
{
@@ -90,7 +90,7 @@ public unsafe struct FixedStackString32
}
}
public FixedStackString32(byte* input, ushort length)
public FixedString32(byte* input, ushort length)
: this(new ReadOnlySpan<byte>(input, length))
{
}
@@ -127,12 +127,12 @@ public unsafe struct FixedStackString32
/// If you need a heap allocated fixed-size UTF-8 encoded string of length 64 bytes, consider using <see cref="Misaki.HighPerformance.Unsafe.Buffer.FixedString64"/>.
/// </remarks>
[StructLayout(LayoutKind.Sequential, Size = 64)]
public unsafe struct FixedStackString64
public unsafe struct FixedString64
{
private ushort _length;
private fixed byte _buffer[62];
public ushort Length => _length;
public readonly ushort Length => _length;
public string Value
{
get
@@ -153,7 +153,7 @@ public unsafe struct FixedStackString64
var maxBytes = Encoding.UTF8.GetByteCount(value);
if (maxBytes > 62)
{
throw new ArgumentException("Input string is too long to fit in FixedStackString64.");
throw new ArgumentException("Input string is too long to fit in FixedString64.");
}
fixed (byte* bufferPtr = _buffer)
@@ -163,7 +163,7 @@ public unsafe struct FixedStackString64
}
}
public FixedStackString64(ReadOnlySpan<char> input)
public FixedString64(ReadOnlySpan<char> input)
{
var maxBytes = Encoding.UTF8.GetByteCount(input);
if (maxBytes > 62)
@@ -179,17 +179,17 @@ public unsafe struct FixedStackString64
}
}
public FixedStackString64(string input)
public FixedString64(string input)
: this(input.AsSpan())
{
}
public FixedStackString64(char* input, ushort length)
public FixedString64(char* input, ushort length)
: this(new Span<char>(input, length))
{
}
public FixedStackString64(ReadOnlySpan<byte> input)
public FixedString64(ReadOnlySpan<byte> input)
{
if (input.Length > 62)
{
@@ -205,7 +205,7 @@ public unsafe struct FixedStackString64
}
}
public FixedStackString64(byte* input, ushort length)
public FixedString64(byte* input, ushort length)
: this(new ReadOnlySpan<byte>(input, length))
{
}
@@ -242,12 +242,12 @@ public unsafe struct FixedStackString64
/// If you need a heap allocated fixed-size UTF-8 encoded string of length 128 bytes, consider using <see cref="Misaki.HighPerformance.Unsafe.Buffer.FixedString128"/>.
/// </remarks>
[StructLayout(LayoutKind.Sequential, Size = 128)]
public unsafe struct FixedStackString128
public unsafe struct FixedString128
{
private ushort _length;
private fixed byte _buffer[126];
public ushort Length => _length;
public readonly ushort Length => _length;
public string Value
{
get
@@ -268,7 +268,7 @@ public unsafe struct FixedStackString128
var maxBytes = Encoding.UTF8.GetByteCount(value);
if (maxBytes > 126)
{
throw new ArgumentException("Input string is too long to fit in FixedStackString128.");
throw new ArgumentException("Input string is too long to fit in FixedString128.");
}
fixed (byte* bufferPtr = _buffer)
@@ -278,7 +278,7 @@ public unsafe struct FixedStackString128
}
}
public FixedStackString128(ReadOnlySpan<char> input)
public FixedString128(ReadOnlySpan<char> input)
{
var maxBytes = Encoding.UTF8.GetByteCount(input);
if (maxBytes > 126)
@@ -294,17 +294,17 @@ public unsafe struct FixedStackString128
}
}
public FixedStackString128(string input)
public FixedString128(string input)
: this(input.AsSpan())
{
}
public FixedStackString128(char* input, ushort length)
public FixedString128(char* input, ushort length)
: this(new Span<char>(input, length))
{
}
public FixedStackString128(ReadOnlySpan<byte> input)
public FixedString128(ReadOnlySpan<byte> input)
{
if (input.Length > 126)
{
@@ -320,7 +320,7 @@ public unsafe struct FixedStackString128
}
}
public FixedStackString128(byte* input, ushort length)
public FixedString128(byte* input, ushort length)
: this(new ReadOnlySpan<byte>(input, length))
{
}
@@ -357,12 +357,12 @@ public unsafe struct FixedStackString128
/// If you need a heap allocated fixed-size UTF-8 encoded string of length 256 bytes, consider using <see cref="Misaki.HighPerformance.Unsafe.Buffer.FixedString256"/>.
/// </remarks>
[StructLayout(LayoutKind.Sequential, Size = 256)]
public unsafe struct FixedStackString256
public unsafe struct FixedString256
{
private ushort _length;
private fixed byte _buffer[254];
public ushort Length => _length;
public readonly ushort Length => _length;
public string Value
{
get
@@ -383,7 +383,7 @@ public unsafe struct FixedStackString256
var maxBytes = Encoding.UTF8.GetByteCount(value);
if (maxBytes > 254)
{
throw new ArgumentException("Input string is too long to fit in FixedStackString256.");
throw new ArgumentException("Input string is too long to fit in FixedString256.");
}
fixed (byte* bufferPtr = _buffer)
@@ -393,7 +393,7 @@ public unsafe struct FixedStackString256
}
}
public FixedStackString256(ReadOnlySpan<char> input)
public FixedString256(ReadOnlySpan<char> input)
{
var maxBytes = Encoding.UTF8.GetByteCount(input);
if (maxBytes > 254)
@@ -409,17 +409,17 @@ public unsafe struct FixedStackString256
}
}
public FixedStackString256(string input)
public FixedString256(string input)
: this(input.AsSpan())
{
}
public FixedStackString256(char* input, ushort length)
public FixedString256(char* input, ushort length)
: this(new Span<char>(input, length))
{
}
public FixedStackString256(ReadOnlySpan<byte> input)
public FixedString256(ReadOnlySpan<byte> input)
{
if (input.Length > 254)
{
@@ -435,7 +435,7 @@ public unsafe struct FixedStackString256
}
}
public FixedStackString256(byte* input, ushort length)
public FixedString256(byte* input, ushort length)
: this(new ReadOnlySpan<byte>(input, length))
{
}
@@ -472,12 +472,12 @@ public unsafe struct FixedStackString256
/// If you need a heap allocated fixed-size UTF-8 encoded string of length 512 bytes, consider using <see cref="Misaki.HighPerformance.Unsafe.Buffer.FixedString512"/>.
/// </remarks>
[StructLayout(LayoutKind.Sequential, Size = 512)]
public unsafe struct FixedStackString512
public unsafe struct FixedString512
{
private ushort _length;
private fixed byte _buffer[510];
public ushort Length => _length;
public readonly ushort Length => _length;
public string Value
{
get
@@ -498,7 +498,7 @@ public unsafe struct FixedStackString512
var maxBytes = Encoding.UTF8.GetByteCount(value);
if (maxBytes > 510)
{
throw new ArgumentException("Input string is too long to fit in FixedStackString512.");
throw new ArgumentException("Input string is too long to fit in FixedString512.");
}
fixed (byte* bufferPtr = _buffer)
@@ -508,7 +508,7 @@ public unsafe struct FixedStackString512
}
}
public FixedStackString512(ReadOnlySpan<char> input)
public FixedString512(ReadOnlySpan<char> input)
{
var maxBytes = Encoding.UTF8.GetByteCount(input);
if (maxBytes > 510)
@@ -524,17 +524,17 @@ public unsafe struct FixedStackString512
}
}
public FixedStackString512(string input)
public FixedString512(string input)
: this(input.AsSpan())
{
}
public FixedStackString512(char* input, ushort length)
public FixedString512(char* input, ushort length)
: this(new Span<char>(input, length))
{
}
public FixedStackString512(ReadOnlySpan<byte> input)
public FixedString512(ReadOnlySpan<byte> input)
{
if (input.Length > 510)
{
@@ -550,7 +550,7 @@ public unsafe struct FixedStackString512
}
}
public FixedStackString512(byte* input, ushort length)
public FixedString512(byte* input, ushort length)
: this(new ReadOnlySpan<byte>(input, length))
{
}
@@ -587,12 +587,12 @@ public unsafe struct FixedStackString512
/// If you need a heap allocated fixed-size UTF-8 encoded string of length 1024 bytes, consider using <see cref="Misaki.HighPerformance.Unsafe.Buffer.FixedString1024"/>.
/// </remarks>
[StructLayout(LayoutKind.Sequential, Size = 1024)]
public unsafe struct FixedStackString1024
public unsafe struct FixedString1024
{
private ushort _length;
private fixed byte _buffer[1022];
public ushort Length => _length;
public readonly ushort Length => _length;
public string Value
{
get
@@ -613,7 +613,7 @@ public unsafe struct FixedStackString1024
var maxBytes = Encoding.UTF8.GetByteCount(value);
if (maxBytes > 1022)
{
throw new ArgumentException("Input string is too long to fit in FixedStackString1024.");
throw new ArgumentException("Input string is too long to fit in FixedString1024.");
}
fixed (byte* bufferPtr = _buffer)
@@ -623,7 +623,7 @@ public unsafe struct FixedStackString1024
}
}
public FixedStackString1024(ReadOnlySpan<char> input)
public FixedString1024(ReadOnlySpan<char> input)
{
var maxBytes = Encoding.UTF8.GetByteCount(input);
if (maxBytes > 1022)
@@ -639,17 +639,17 @@ public unsafe struct FixedStackString1024
}
}
public FixedStackString1024(string input)
public FixedString1024(string input)
: this(input.AsSpan())
{
}
public FixedStackString1024(char* input, ushort length)
public FixedString1024(char* input, ushort length)
: this(new Span<char>(input, length))
{
}
public FixedStackString1024(ReadOnlySpan<byte> input)
public FixedString1024(ReadOnlySpan<byte> input)
{
if (input.Length > 1022)
{
@@ -665,7 +665,7 @@ public unsafe struct FixedStackString1024
}
}
public FixedStackString1024(byte* input, ushort length)
public FixedString1024(byte* input, ushort length)
: this(new ReadOnlySpan<byte>(input, length))
{
}
@@ -702,12 +702,12 @@ public unsafe struct FixedStackString1024
/// If you need a heap allocated fixed-size UTF-8 encoded string of length 2048 bytes, consider using <see cref="Misaki.HighPerformance.Unsafe.Buffer.FixedString2048"/>.
/// </remarks>
[StructLayout(LayoutKind.Sequential, Size = 2048)]
public unsafe struct FixedStackString2048
public unsafe struct FixedString2048
{
private ushort _length;
private fixed byte _buffer[2046];
public ushort Length => _length;
public readonly ushort Length => _length;
public string Value
{
get
@@ -728,7 +728,7 @@ public unsafe struct FixedStackString2048
var maxBytes = Encoding.UTF8.GetByteCount(value);
if (maxBytes > 2046)
{
throw new ArgumentException("Input string is too long to fit in FixedStackString2048.");
throw new ArgumentException("Input string is too long to fit in FixedString2048.");
}
fixed (byte* bufferPtr = _buffer)
@@ -738,7 +738,7 @@ public unsafe struct FixedStackString2048
}
}
public FixedStackString2048(ReadOnlySpan<char> input)
public FixedString2048(ReadOnlySpan<char> input)
{
var maxBytes = Encoding.UTF8.GetByteCount(input);
if (maxBytes > 2046)
@@ -754,17 +754,17 @@ public unsafe struct FixedStackString2048
}
}
public FixedStackString2048(string input)
public FixedString2048(string input)
: this(input.AsSpan())
{
}
public FixedStackString2048(char* input, ushort length)
public FixedString2048(char* input, ushort length)
: this(new Span<char>(input, length))
{
}
public FixedStackString2048(ReadOnlySpan<byte> input)
public FixedString2048(ReadOnlySpan<byte> input)
{
if (input.Length > 2046)
{
@@ -780,7 +780,7 @@ public unsafe struct FixedStackString2048
}
}
public FixedStackString2048(byte* input, ushort length)
public FixedString2048(byte* input, ushort length)
: this(new ReadOnlySpan<byte>(input, length))
{
}
@@ -817,12 +817,12 @@ public unsafe struct FixedStackString2048
/// If you need a heap allocated fixed-size UTF-8 encoded string of length 4096 bytes, consider using <see cref="Misaki.HighPerformance.Unsafe.Buffer.FixedString4096"/>.
/// </remarks>
[StructLayout(LayoutKind.Sequential, Size = 4096)]
public unsafe struct FixedStackString4096
public unsafe struct FixedString4096
{
private ushort _length;
private fixed byte _buffer[4094];
public ushort Length => _length;
public readonly ushort Length => _length;
public string Value
{
get
@@ -843,7 +843,7 @@ public unsafe struct FixedStackString4096
var maxBytes = Encoding.UTF8.GetByteCount(value);
if (maxBytes > 4094)
{
throw new ArgumentException("Input string is too long to fit in FixedStackString4096.");
throw new ArgumentException("Input string is too long to fit in FixedString4096.");
}
fixed (byte* bufferPtr = _buffer)
@@ -853,7 +853,7 @@ public unsafe struct FixedStackString4096
}
}
public FixedStackString4096(ReadOnlySpan<char> input)
public FixedString4096(ReadOnlySpan<char> input)
{
var maxBytes = Encoding.UTF8.GetByteCount(input);
if (maxBytes > 4094)
@@ -869,17 +869,17 @@ public unsafe struct FixedStackString4096
}
}
public FixedStackString4096(string input)
public FixedString4096(string input)
: this(input.AsSpan())
{
}
public FixedStackString4096(char* input, ushort length)
public FixedString4096(char* input, ushort length)
: this(new Span<char>(input, length))
{
}
public FixedStackString4096(ReadOnlySpan<byte> input)
public FixedString4096(ReadOnlySpan<byte> input)
{
if (input.Length > 4094)
{
@@ -895,7 +895,7 @@ public unsafe struct FixedStackString4096
}
}
public FixedStackString4096(byte* input, ushort length)
public FixedString4096(byte* input, ushort length)
: this(new ReadOnlySpan<byte>(input, length))
{
}

View File

@@ -8,7 +8,7 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
namespace Misaki.HighPerformance.LowLevel.Buffer;
namespace Misaki.HighPerformance.LowLevel.Collections;
<# for (int i = 32; i <= 4096; i *= 2) { #>
/// <summary>
@@ -19,12 +19,12 @@ namespace Misaki.HighPerformance.LowLevel.Buffer;
/// If you need a heap allocated fixed-size UTF-8 encoded string of length <#= i #> bytes, consider using <see cref="Misaki.HighPerformance.Unsafe.Buffer.FixedString<#= i #>"/>.
/// </remarks>
[StructLayout(LayoutKind.Sequential, Size = <#= i #>)]
public unsafe struct FixedStackString<#= i #>
public unsafe struct FixedString<#= i #>
{
private ushort _length;
private fixed byte _buffer[<#= i - 2 #>];
public ushort Length => _length;
public readonly ushort Length => _length;
public string Value
{
get
@@ -45,7 +45,7 @@ public unsafe struct FixedStackString<#= i #>
var maxBytes = Encoding.UTF8.GetByteCount(value);
if (maxBytes > <#= i - 2 #>)
{
throw new ArgumentException("Input string is too long to fit in FixedStackString<#= i #>.");
throw new ArgumentException("Input string is too long to fit in FixedString<#= i #>.");
}
fixed (byte* bufferPtr = _buffer)
@@ -55,7 +55,7 @@ public unsafe struct FixedStackString<#= i #>
}
}
public FixedStackString<#= i #>(ReadOnlySpan<char> input)
public FixedString<#= i #>(ReadOnlySpan<char> input)
{
var maxBytes = Encoding.UTF8.GetByteCount(input);
if (maxBytes > <#= i - 2 #>)
@@ -71,17 +71,17 @@ public unsafe struct FixedStackString<#= i #>
}
}
public FixedStackString<#= i #>(string input)
public FixedString<#= i #>(string input)
: this(input.AsSpan())
{
}
public FixedStackString<#= i #>(char* input, ushort length)
public FixedString<#= i #>(char* input, ushort length)
: this(new Span<char>(input, length))
{
}
public FixedStackString<#= i #>(ReadOnlySpan<byte> input)
public FixedString<#= i #>(ReadOnlySpan<byte> input)
{
if (input.Length > <#= i - 2 #>)
{
@@ -97,7 +97,7 @@ public unsafe struct FixedStackString<#= i #>
}
}
public FixedStackString<#= i #>(byte* input, ushort length)
public FixedString<#= i #>(byte* input, ushort length)
: this(new ReadOnlySpan<byte>(input, length))
{
}

View File

@@ -1,7 +1,7 @@
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
using Misaki.HighPerformance.LowLevel.Contracts;
using Misaki.HighPerformance.LowLevel.Helpers;
using Misaki.HighPerformance.LowLevel.Utilities;
using System.Runtime.CompilerServices;
namespace Misaki.HighPerformance.LowLevel.Collections;
@@ -26,7 +26,7 @@ public unsafe struct UnTypedArray : IUnTypedCollection
/// Constructs an UnsafeArray with a default size of 1 and uses the Persistent allocator.
/// </summary>
public UnTypedArray()
: this(1, 1, Allocator.Persistent)
: this(0, 8, Allocator.Invalid)
{
}

View File

@@ -1,7 +1,7 @@
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
using Misaki.HighPerformance.LowLevel.Contracts;
using Misaki.HighPerformance.LowLevel.Helpers;
using Misaki.HighPerformance.LowLevel.Utilities;
using System.Collections;
using System.Runtime.CompilerServices;
@@ -16,7 +16,7 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
{
public struct Enumerator : IEnumerator<T>
{
private UnsafeArray<T>* _collection;
private readonly UnsafeArray<T>* _collection;
private int _index;
private T _value;
@@ -76,7 +76,7 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
#if DISABLE_COLLECTION_CHECKS
#if ENABLE_COLLECTION_CHECKS
if (index < 0 || index >= _count)
{
throw new ArgumentOutOfRangeException(nameof(index), "Index is out of range.");
@@ -92,7 +92,7 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
#if DISABLE_COLLECTION_CHECKS
#if ENABLE_COLLECTION_CHECKS
if (index >= _count)
{
throw new ArgumentOutOfRangeException(nameof(index), "Index is out of range.");
@@ -113,7 +113,7 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
/// <summary>
/// Constructs an UnsafeArray with a default size of 1 and uses the Persistent allocator.
/// Invalid constructor, use <see cref="UnsafeArray(int, Allocator, AllocationOption)"/> or <see cref="UnsafeArray(int, ref AllocationHandle, AllocationOption)"/> instead.
/// </summary>
public UnsafeArray()
: this(0, Allocator.Invalid)
@@ -175,7 +175,7 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
return;
}
_buffer = (T*)_handle->Realloc(_handle->Allocator, _buffer, (uint)newSize, (uint)AlignOf<T>());
_buffer = (T*)_handle->Realloc(_handle->Allocator, _buffer, (nuint)newSize * SizeOf<T>(), AlignOf<T>());
_count = newSize;
}

View File

@@ -1,10 +1,12 @@
using System.Numerics;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Utilities;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Text;
namespace Misaki.HighPerformance.LowLevel.Collections;
public sealed class BitSet
public struct UnsafeBitSet : IDisposable
{
private const int _BIT_SIZE = sizeof(uint) * 8 - 1; // 31
private const int _INDEX_SIZE = 5; // log_2(BitSize + 1)
@@ -12,7 +14,7 @@ public sealed class BitSet
private static readonly int s_padding = Vector<uint>.Count; // The padding used for vectorization, the amount of uints required for being vectorized basically
/// <summary>
/// Determines the required length of an <see cref="BitSet"/> to hold the passed id or bit.
/// Determines the required length of an <see cref="UnsafeBitSet"/> to hold the passed id or bit.
/// </summary>
/// <param name="id">The id or bit.</param>
/// <returns>A size of required <see cref="uint"/>s for the bitset.</returns>
@@ -32,47 +34,49 @@ public sealed class BitSet
}
/// <summary>
/// The bits from the bitset.
/// The bits from the bitset.
/// </summary>
private uint[] _bits;
private UnsafeArray<uint> _bits;
/// <summary>
/// The highest bit set.
/// The highest bit set.
/// </summary>
private int _highestBit;
/// <summary>
/// The maximum <see cref="_bits"/>-index current in use.
/// The maximum <see cref="_bits"/>-index current in use.
/// </summary>
private int _max;
/// <summary>
/// Initializes a new instance of the <see cref="BitSet" /> class.
/// Initializes a new instance of the <see cref="UnsafeBitSet" /> class.
/// </summary>
public BitSet()
public UnsafeBitSet()
{
_bits = new uint[s_padding];
_bits = new UnsafeArray<uint>(s_padding, Allocator.Persistent);
}
/// <summary>
/// Initializes a new instance of the <see cref="BitSet" /> class.
/// Initializes a new instance of the <see cref="UnsafeBitSet" /> class.
/// </summary>
public BitSet(int minimalLength)
public UnsafeBitSet(int minimalLength)
{
var uints = (minimalLength >> _INDEX_SIZE) + int.Sign(minimalLength & _BIT_SIZE);
var length = RoundToPadding(uints);
_bits = new uint[length];
_bits = new UnsafeArray<uint>(length, Allocator.Persistent);
}
/// <summary>
/// Initializes a new instance of the <see cref="BitSet" /> class.
/// Initializes a new instance of the <see cref="UnsafeBitSet" /> class.
/// </summary>
public BitSet(params Span<uint> bits)
public UnsafeBitSet(params Span<uint> bits)
{
_bits = bits.ToArray();
_bits = new UnsafeArray<uint>(bits.Length, Allocator.Persistent);
_bits.CopyFrom(bits);
_highestBit = 0;
_max = _bits.Length * (_BIT_SIZE + 1) - 1; // Calculate the maximum index in use
for (var i = 0; i < _bits.Length; i++)
_max = _bits.Count * (_BIT_SIZE + 1) - 1; // Calculate the maximum index in use
for (var i = 0; i < _bits.Count; i++)
{
if (_bits[i] != 0)
{
@@ -82,7 +86,7 @@ public sealed class BitSet
}
/// <summary>
/// The highest uint index in use inside the <see cref="_bits"/>-array.
/// The highest uint index in use inside the <see cref="_bits"/>-array.
/// </summary>
public int HighestIndex
{
@@ -90,7 +94,7 @@ public sealed class BitSet
}
/// <summary>
/// The highest bit set.
/// The highest bit set.
/// </summary>
public int HighestBit
{
@@ -98,22 +102,22 @@ public sealed class BitSet
}
/// <summary>
/// Returns the length of the bitset, how many ints it consists of.
/// Returns the length of the bitset, how many ints it consists of.
/// </summary>
public int Length
{
get => _bits.Length;
get => _bits.Count;
}
/// <summary>
/// Checks whether a bit is set at the index.
/// Checks whether a bit is set at the index.
/// </summary>
/// <param name="index">The index.</param>
/// <returns>True if it is, otherwise false</returns>
public bool IsSet(int index)
{
var b = index >> _INDEX_SIZE;
if (b >= _bits.Length)
if (b >= _bits.Count)
{
return false;
}
@@ -122,16 +126,16 @@ public sealed class BitSet
}
/// <summary>
/// Sets a bit at the given index.
/// Resizes its internal array if necessary.
/// Sets a bit at the given index.
/// Resizes its internal array if necessary.
/// </summary>
/// <param name="index">The index.</param>
public void SetBit(int index)
{
var b = index >> _INDEX_SIZE;
if (b >= _bits.Length)
if (b >= _bits.Count)
{
Array.Resize(ref _bits, RoundToPadding(b));
_bits.Resize(RoundToPadding(b));
}
// Track highest set bit
@@ -141,13 +145,13 @@ public sealed class BitSet
}
/// <summary>
/// Clears the bit at the given index.
/// Clears the bit at the given index.
/// </summary>
/// <param name="index">The index.</param>
public void ClearBit(int index)
{
var b = index >> _INDEX_SIZE;
if (b >= _bits.Length)
if (b >= _bits.Count)
{
return;
}
@@ -156,26 +160,26 @@ public sealed class BitSet
}
/// <summary>
/// Sets all bits.
/// Sets all bits.
/// </summary>
public void SetAll()
{
var count = _bits.Length;
var count = _bits.Count;
for (var i = 0; i < count; i++)
{
_bits[i] = 0xffffffff;
}
_highestBit = _bits.Length * (_BIT_SIZE + 1) - 1;
_highestBit = _bits.Count * (_BIT_SIZE + 1) - 1;
_max = _highestBit / (_BIT_SIZE + 1) + 1;
}
/// <summary>
/// Clears all set bits.
/// Clears all set bits.
/// </summary>
public void ClearAll()
{
Array.Clear(_bits);
_bits.Clear();
_highestBit = 0;
_max = 0;
}
@@ -186,7 +190,7 @@ public sealed class BitSet
public int NextSetBit(int startIndex)
{
var wordIndex = startIndex >> _BIT_SIZE;
if (wordIndex >= _bits.Length)
if (wordIndex >= _bits.Count)
{
return -1;
}
@@ -203,7 +207,7 @@ public sealed class BitSet
}
wordIndex++;
if (wordIndex >= _bits.Length)
if (wordIndex >= _bits.Count)
{
return -1;
}
@@ -213,12 +217,12 @@ public sealed class BitSet
}
/// <summary>
/// Checks if all bits from this instance match those of the other instance.
/// Checks if all bits from this instance match those of the other instance.
/// </summary>
/// <param name="other">The other <see cref="BitSet"/>.</param>
/// <param name="other">The other <see cref="UnsafeBitSet"/>.</param>
/// <returns>True if they match, false if not.</returns>
[SkipLocalsInit]
public bool All(BitSet other)
public bool All(UnsafeBitSet other)
{
var min = Math.Min(Math.Min(Length, other.Length), _max);
if (!Vector.IsHardwareAccelerated || min < s_padding)
@@ -275,11 +279,11 @@ public sealed class BitSet
}
/// <summary>
/// Checks if any bits from this instance match those of the other instance.
/// Checks if any bits from this instance match those of the other instance.
/// </summary>
/// <param name="other">The other <see cref="BitSet"/>.</param>
/// <param name="other">The other <see cref="UnsafeBitSet"/>.</param>
/// <returns>True if they match, false if not.</returns>
public bool Any(BitSet other)
public bool Any(UnsafeBitSet other)
{
var min = Math.Min(Math.Min(Length, other.Length), _max);
if (!Vector.IsHardwareAccelerated || min < s_padding)
@@ -336,11 +340,11 @@ public sealed class BitSet
}
/// <summary>
/// Checks if none bits from this instance match those of the other instance.
/// Checks if none bits from this instance match those of the other instance.
/// </summary>
/// <param name="other">The other <see cref="BitSet"/>.</param>
/// <param name="other">The other <see cref="UnsafeBitSet"/>.</param>
/// <returns>True if none match, false if not.</returns>
public bool None(BitSet other)
public bool None(UnsafeBitSet other)
{
var min = Math.Min(Math.Min(Length, other.Length), _max);
if (!Vector.IsHardwareAccelerated || min < s_padding)
@@ -378,11 +382,11 @@ public sealed class BitSet
}
/// <summary>
/// Checks if exactly all bits from this instance match those of the other instance.
/// Checks if exactly all bits from this instance match those of the other instance.
/// </summary>
/// <param name="other">The other <see cref="BitSet"/>.</param>
/// <param name="other">The other <see cref="UnsafeBitSet"/>.</param>
/// <returns>True if they match, false if not.</returns>
public bool Exclusive(BitSet other)
public bool Exclusive(UnsafeBitSet other)
{
var min = Math.Min(Math.Min(Length, other.Length), _max);
@@ -439,10 +443,10 @@ public sealed class BitSet
return true;
}
public static BitSet operator &(BitSet left, BitSet right)
public static UnsafeBitSet operator &(UnsafeBitSet left, UnsafeBitSet right)
{
var min = Math.Min(left.Length, right.Length);
var result = new BitSet(min);
var result = new UnsafeBitSet(min);
if (!Vector.IsHardwareAccelerated || min < s_padding)
{
for (var i = 0; i < min; i++)
@@ -463,10 +467,10 @@ public sealed class BitSet
return result;
}
public static BitSet operator |(BitSet left, BitSet right)
public static UnsafeBitSet operator |(UnsafeBitSet left, UnsafeBitSet right)
{
var min = Math.Min(left.Length, right.Length);
var result = new BitSet(min);
var result = new UnsafeBitSet(min);
if (!Vector.IsHardwareAccelerated || min < s_padding)
{
for (var i = 0; i < min; i++)
@@ -487,7 +491,7 @@ public sealed class BitSet
return result;
}
public static BitSet operator ~(BitSet bitSet)
public static UnsafeBitSet operator ~(UnsafeBitSet bitSet)
{
if (!Vector.IsHardwareAccelerated || bitSet.Length < s_padding)
{
@@ -510,17 +514,17 @@ public sealed class BitSet
}
/// <summary>
/// Creates a <see cref="Span{T}"/> to access the <see cref="_bits"/>.
/// Creates a <see cref="Span{T}"/> to access the <see cref="_bits"/>.
/// </summary>
/// <returns>The hash.</returns>
public Span<uint> AsSpan()
public readonly Span<uint> AsSpan()
{
var max = _highestBit / (_BIT_SIZE + 1) + 1;
return _bits.AsSpan()[..max];
}
/// <summary>
/// Copies the bits into a <see cref="Span{T}"/> and returns a slice containing the copied <see cref="_bits"/>.
/// Copies the bits into a <see cref="Span{T}"/> and returns a slice containing the copied <see cref="_bits"/>.
/// </summary>
/// <param name="span">The <see cref="Span{T}"/> to copy into.</param>
/// <param name="zero">If true, it will zero the unused space from the <see cref="span"/>.</param>
@@ -543,10 +547,6 @@ public sealed class BitSet
return span[..Length];
}
/// <summary>
/// Prints the content of this instance.
/// </summary>
/// <returns>The string.</returns>
public override string ToString()
{
// Convert uint to binary form for pretty printing
@@ -559,12 +559,19 @@ public sealed class BitSet
return $"{nameof(_bits)}: {binaryBuilder}, {nameof(Length)}: {Length}";
}
public void Dispose()
{
_bits.Dispose();
_highestBit = 0;
_max = 0;
}
}
/// <summary>
/// The <see cref="SpanBitSet"/> struct
/// represents a non resizable collection of bits.
/// Used to set, check and clear bits on a allocated <see cref="BitSet"/> or on the stack.
/// The <see cref="SpanBitSet"/> struct
/// represents a non resizable collection of bits.
/// Used to set, check and clear bits on a allocated <see cref="UnsafeBitSet"/> or on the stack.
/// </summary>
public readonly ref struct SpanBitSet
{
@@ -573,12 +580,12 @@ public readonly ref struct SpanBitSet
private const int _BYTE_SIZE = 5; // log_2(BitSize + 1)
/// <summary>
/// The bits from the bitset.
/// The bits from the bitset.
/// </summary>
private readonly Span<uint> _bits;
/// <summary>
/// Initializes a new instance of the <see cref="BitSet" /> class.
/// Initializes a new instance of the <see cref="UnsafeBitSet" /> class.
/// </summary>
public SpanBitSet(Span<uint> bits)
{
@@ -586,7 +593,7 @@ public readonly ref struct SpanBitSet
}
/// <summary>
/// Checks whether a bit is set at the index.
/// Checks whether a bit is set at the index.
/// </summary>
/// <param name="index">The index.</param>
/// <returns>True if it is, otherwise false</returns>
@@ -603,8 +610,8 @@ public readonly ref struct SpanBitSet
}
/// <summary>
/// Sets a bit at the given index.
/// Resizes its internal array if necessary.
/// Sets a bit at the given index.
/// Resizes its internal array if necessary.
/// </summary>
/// <param name="index">The index.</param>
@@ -620,7 +627,7 @@ public readonly ref struct SpanBitSet
}
/// <summary>
/// Clears the bit at the given index.
/// Clears the bit at the given index.
/// </summary>
/// <param name="index">The index.</param>
@@ -636,7 +643,7 @@ public readonly ref struct SpanBitSet
}
/// <summary>
///
/// Sets all bits.
/// </summary>
public void SetAll()
@@ -649,7 +656,7 @@ public readonly ref struct SpanBitSet
}
/// <summary>
/// Clears all set bits.
/// Clears all set bits.
/// </summary>
public void ClearAll()
@@ -658,7 +665,7 @@ public readonly ref struct SpanBitSet
}
/// <summary>
/// Creates a <see cref="Span{T}"/> to access the <see cref="_bits"/>.
/// Creates a <see cref="Span{T}"/> to access the <see cref="_bits"/>.
/// </summary>
/// <returns>The hash.</returns>
@@ -668,7 +675,7 @@ public readonly ref struct SpanBitSet
}
/// <summary>
/// Copies the bits into a <see cref="Span{T}"/> and returns a slice containing the copied <see cref="_bits"/>.
/// Copies the bits into a <see cref="Span{T}"/> and returns a slice containing the copied <see cref="_bits"/>.
/// </summary>
/// <param name=""></param>
/// <returns>The hash.</returns>
@@ -691,11 +698,6 @@ public readonly ref struct SpanBitSet
return span[.._bits.Length];
}
/// <summary>
/// Prints the content of this instance.
/// </summary>
/// <returns>The string.</returns>
public override string ToString()
{
// Convert uint to binary form for pretty printing

View File

@@ -1,7 +1,7 @@
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
using Misaki.HighPerformance.LowLevel.Contracts;
using Misaki.HighPerformance.LowLevel.Helpers;
using Misaki.HighPerformance.LowLevel.Utilities;
using System.Collections;
using System.Runtime.CompilerServices;
@@ -95,6 +95,14 @@ public unsafe struct UnsafeHashMap<TKey, TValue> : IUnsafeCollection<KeyValuePai
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() => new Enumerator((HashMapHelper<TKey>*)UnsafeUtilities.AddressOf(ref _hashMap));
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
/// <summary>
/// Invalid constructor, use <see cref="UnsafeHashMap(int, Allocator, AllocationOption)"/> or <see cref="UnsafeHashMap(int, ref AllocationHandle, AllocationOption)"/> instead.
/// </summary>
public UnsafeHashMap()
: this(0, Allocator.Invalid)
{
}
public UnsafeHashMap(int capacity, ref AllocationHandle handle, AllocationOption allocationOption = AllocationOption.None)
{
_hashMap = new HashMapHelper<TKey>(capacity, sizeof(TValue), HashMapHelper<TKey>.MINIMAL_CAPACITY, ref handle, allocationOption);

View File

@@ -1,7 +1,7 @@
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
using Misaki.HighPerformance.LowLevel.Contracts;
using Misaki.HighPerformance.LowLevel.Helpers;
using Misaki.HighPerformance.LowLevel.Utilities;
using System.Collections;
using System.Runtime.CompilerServices;
@@ -51,6 +51,14 @@ public unsafe struct UnsafeHashSet<T> : IUnsafeCollection<T>, IEnumerable<T>
public IEnumerator<T> GetEnumerator() => new Enumerator((HashMapHelper<T>*)UnsafeUtilities.AddressOf(ref _hashMap));
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
/// <summary>
/// Invalid constructor. Use <see cref="UnsafeHashSet(int, Allocator, AllocationOption)"/> or <see cref="UnsafeHashSet(int, ref AllocationHandle, AllocationOption)"/> instead."/>
/// </summary>
public UnsafeHashSet()
: this(0, Allocator.Invalid)
{
}
public UnsafeHashSet(int capacity, ref AllocationHandle handle, AllocationOption allocationOption = AllocationOption.None)
{
_hashMap = new HashMapHelper<T>(capacity, 0, HashMapHelper<T>.MINIMAL_CAPACITY, ref handle, allocationOption);

View File

@@ -1,7 +1,7 @@
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
using Misaki.HighPerformance.LowLevel.Contracts;
using Misaki.HighPerformance.LowLevel.Helpers;
using Misaki.HighPerformance.LowLevel.Utilities;
using System.Collections;
using System.Runtime.CompilerServices;
@@ -16,7 +16,7 @@ public unsafe struct UnsafeList<T> : IUnsafeCollection<T>
{
public struct Enumerator : IEnumerator<T>
{
private UnsafeList<T>* _collection;
private readonly UnsafeList<T>* _collection;
private int _index;
private T _value;
@@ -152,25 +152,35 @@ public unsafe struct UnsafeList<T> : IUnsafeCollection<T>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly UnsafeArray<T> AsUnsafeArray() => new((T*)_array.GetUnsafePtr(), _count);
/// <summary>
/// Invalid constructor, use <see cref="UnsafeList(int, Allocator, AllocationOption)"/> or <see cref="UnsafeList(int, ref AllocationHandle, AllocationOption)"/> instead.
/// </summary>
public UnsafeList()
: this(0, Allocator.Invalid)
{
}
public UnsafeList(int capacity, ref AllocationHandle handle, AllocationOption allocationType = AllocationOption.None)
/// <summary>
/// Initializes a new instance of UnsafeList with a specified number of initial capacity and an allocation handle.
/// </summary>
/// <param name="capacity">Specifies the number of initial capacity to allocate in the list, 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>
public UnsafeList(int capacity, ref AllocationHandle handle, AllocationOption allocationOption = AllocationOption.None)
{
if (capacity <= 0)
{
throw new ArgumentOutOfRangeException(nameof(capacity), "Capacity must be greater than zero.");
}
_array = new UnsafeArray<T>(capacity, ref handle, allocationType);
_array = new UnsafeArray<T>(capacity, ref handle, allocationOption);
_count = 0;
}
public UnsafeList(int capacity, Allocator allocator, AllocationOption allocationType = AllocationOption.None)
/// <summary>
/// Initializes a new instance of UnsafeList with a specified number of initial capacity and an allocation type.
/// </summary>
/// <param name="capacity">Specifies the number of initial capacity to allocate in the list, 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>
public UnsafeList(int capacity, Allocator allocator, AllocationOption allocationOption = AllocationOption.None)
: this(capacity, ref AllocationManager.GetAllocationHandle(allocator), allocationOption)
{
_array = new UnsafeArray<T>(capacity, allocator, allocationType);
_count = 0;
}
private readonly void CheckNoResizeCapacity(int count)

View File

@@ -1,6 +1,7 @@
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
using Misaki.HighPerformance.LowLevel.Helpers;
using Misaki.HighPerformance.LowLevel.Contracts;
using Misaki.HighPerformance.LowLevel.Utilities;
using System.Collections;
using System.Runtime.CompilerServices;
@@ -15,7 +16,7 @@ public unsafe struct UnsafeQueue<T> : IUnsafeCollection<T>
{
public struct Enumerator : IEnumerator<T>
{
private UnsafeQueue<T>* _collection;
private readonly UnsafeQueue<T>* _collection;
private int _index;
private T _value;
@@ -82,13 +83,24 @@ public unsafe struct UnsafeQueue<T> : IUnsafeCollection<T>
public IEnumerator<T> GetEnumerator() => new Enumerator((UnsafeQueue<T>*)UnsafeUtilities.AddressOf(ref this));
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public UnsafeQueue() : this(1, Allocator.Persistent)
/// <summary>
/// Invalid constructor. Use <see cref="UnsafeQueue(int, Allocator, AllocationOption)"/> or <see cref="UnsafeQueue(int, ref AllocationHandle, AllocationOption)"/> instead."/>
/// </summary>
public UnsafeQueue()
: this(0, Allocator.Invalid)
{
}
public UnsafeQueue(int capacity, Allocator allocator, AllocationOption allocationType = AllocationOption.None)
public UnsafeQueue(int capacity, ref AllocationHandle handle, AllocationOption allocationOption = AllocationOption.None)
{
_array = new UnsafeArray<T>(capacity, ref handle, allocationOption);
_count = 0;
_offset = 0;
}
public UnsafeQueue(int capacity, Allocator allocator, AllocationOption allocationType = AllocationOption.None)
: this(capacity, ref AllocationManager.GetAllocationHandle(allocator), allocationType)
{
_array = new UnsafeArray<T>(capacity, allocator, allocationType);
}
/// <summary>

View File

@@ -0,0 +1,308 @@
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
using Misaki.HighPerformance.LowLevel.Contracts;
using Misaki.HighPerformance.LowLevel.Utilities;
using System.Collections;
using System.Runtime.CompilerServices;
namespace Misaki.HighPerformance.LowLevel.Collections;
/// <summary>
/// Provides an unsafe, high-performance slot map for storing and managing unmanaged values, supporting fast insertion,
/// removal, and lookup by slot index and generation.
/// </summary>
/// <typeparam name="T">The type of value to store in the slot map. Must be unmanaged.</typeparam>
public unsafe struct UnsafeSlotMap<T> : IUnsafeCollection<T>
where T : unmanaged
{
public struct Enumerator : IEnumerator<T>
{
private readonly UnsafeSlotMap<T>* _collection;
private int _currentIndex;
public Enumerator(UnsafeSlotMap<T>* collection)
{
_collection = collection;
_currentIndex = -1;
}
public readonly T Current => _collection->_data[_currentIndex].value;
readonly object? IEnumerator.Current => Current;
public bool MoveNext()
{
while (++_currentIndex < _collection->_capacity)
{
if (_collection->_data[_currentIndex].isValid)
{
return true;
}
}
return false;
}
public void Reset() => _currentIndex = -1;
public void Dispose()
{
}
}
private struct SlotData
{
public T value;
public int generation;
public bool isValid;
}
private UnsafeArray<SlotData> _data;
private UnsafeQueue<int> _freeSlots;
private int _count;
private int _capacity;
public readonly int Count => _count;
public readonly int Capacity => _capacity;
public readonly bool IsCreated => _data.IsCreated && _freeSlots.IsCreated;
public IEnumerator<T> GetEnumerator() => new Enumerator((UnsafeSlotMap<T>*)UnsafeUtilities.AddressOf(ref this));
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
/// <summary>
/// Invalid constructor. Use <see cref="UnsafeSlotMap(int, Allocator, AllocationOption)"/> or <see cref="UnsafeSlotMap(int, ref AllocationHandle, AllocationOption)"/> instead."/>
/// </summary>
public UnsafeSlotMap()
: this(0, Allocator.Invalid)
{
}
/// <summary>
/// Initializes a new instance of the UnsafeSlotMap class with the specified capacity, allocation handle, and
/// allocation options.
/// </summary>
/// <param name="capacity">The number of slots to allocate for the map. Must be greater than zero.</param>
/// <param name="handle">A reference to the allocation handle used to manage memory for the slot map.</param>
/// <param name="allocationOption">The allocation options to use when creating internal data structures. The default is AllocationOption.None.</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when capacity is less than or equal to zero.</exception>
public UnsafeSlotMap(int capacity, ref AllocationHandle handle, AllocationOption allocationOption = AllocationOption.None)
{
if (capacity <= 0)
{
throw new ArgumentOutOfRangeException(nameof(capacity), "Capacity must be greater than zero.");
}
_data = new UnsafeArray<SlotData>(capacity, ref handle, allocationOption);
_freeSlots = new UnsafeQueue<int>(capacity, ref handle, allocationOption);
_count = 0;
}
/// <summary>
/// Initializes a new instance of the UnsafeSlotMap class with the specified capacity, allocator, and allocation
/// options.
/// </summary>
/// <param name="capacity">The initial number of slots to allocate for the map. Must be greater than zero.</param>
/// <param name="allocator">The allocator to use for memory management of the slot map.</param>
/// <param name="allocationOption">The allocation option that determines how memory is allocated. The default is AllocationOption.None.</param>
public UnsafeSlotMap(int capacity, Allocator allocator, AllocationOption allocationOption = AllocationOption.None)
: this(capacity, ref AllocationManager.GetAllocationHandle(allocator), allocationOption)
{
}
/// <summary>
/// Adds the specified item to the collection and returns the index of the slot where it was stored.
/// </summary>
/// <param name="item">The item to add to the collection.</param>
/// <param name="generation">When this method returns, contains the generation number associated with the slot where the item was stored.</param>
/// <returns>The index of the slot in which the item was stored.</returns>
public int Add(T item, out int generation)
{
if (_count >= _capacity)
{
Resize(_capacity * 2);
}
int slotIndex;
if (_freeSlots.Count == 0)
{
slotIndex = _count;
}
else
{
slotIndex = _freeSlots.Dequeue();
}
ref var slot = ref _data[slotIndex];
slot.value = item;
slot.isValid = true;
generation = slot.generation;
_count++;
return slotIndex;
}
/// <summary>
/// Attempts to remove the item at the specified slot index and generation from the collection.
/// </summary>
/// <param name="slotIndex">The zero-based index of the slot to remove. Must be within the valid range of slot indices.</param>
/// <param name="generation">The generation value associated with the slot. Removal succeeds only if this matches the current generation of
/// the slot.</param>
/// <returns>true if the item was successfully removed; otherwise, false.</returns>
public bool Remove(int slotIndex, int generation)
{
if (slotIndex < 0 || slotIndex >= _capacity)
{
return false;
}
ref var slot = ref _data[slotIndex];
if (slot.generation != generation)
{
return false;
}
slot.generation++;
slot.isValid = false;
_freeSlots.Enqueue(slotIndex);
_count--;
return true;
}
/// <summary>
/// Determines whether the specified slot index contains a valid entry with the given generation.
/// </summary>
/// <param name="slotIndex">The zero-based index of the slot to check. Must be greater than or equal to 0 and less than the current capacity.</param>
/// <param name="generation">The generation value to compare against the slot's generation.</param>
/// <returns>true if the slot at the specified index is valid and its generation matches the specified value; otherwise, false.</returns>
public bool Contain(int slotIndex, int generation)
{
if (slotIndex < 0 || slotIndex >= Volatile.Read(ref _capacity))
{
return false;
}
ref var slot = ref _data[slotIndex];
if (slot.isValid && slot.generation == generation)
{
return true;
}
return false;
}
/// <summary>
/// Attempts to retrieve the element at the specified slot index and generation.
/// </summary>
/// <param name="slotIndex">The zero-based index of the slot to retrieve. Must be within the valid range of slots.</param>
/// <param name="generation">The generation identifier associated with the slot. Used to verify that the slot has not been replaced or
/// invalidated.</param>
/// <param name="value">When this method returns, contains the element at the specified slot and generation if found; otherwise, the
/// default value for type <typeparamref name="T"/>.</param>
/// <returns>true if the element at the specified slot index and generation is found; otherwise, false.</returns>
public bool TryGetElementAt(int slotIndex, int generation, out T value)
{
if (slotIndex < 0 || slotIndex >= _capacity)
{
value = default;
return false;
}
ref var slot = ref _data[slotIndex];
if (slot.generation != generation)
{
value = default;
return false;
}
value = slot.value;
return true;
}
/// <summary>
/// Retrieves the element stored at the specified slot index and generation.
/// </summary>
/// <param name="slotIndex">The zero-based index of the slot from which to retrieve the element. Must be within the valid range of allocated slots.</param>
/// <param name="generation">The generation identifier associated with the slot. Used to ensure the element has not been replaced or removed since allocation.</param>
/// <returns>The element of type <see cref="T"/> stored at the specified slot and generation.</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="slotIndex"/> is less than zero or greater than or equal to the capacity.</exception>
/// <exception cref="InvalidOperationException">Thrown when the specified slot is not occupied or the generation does not match.</exception>
public T GetElementAt(int slotIndex, int generation)
{
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 || slot.generation != generation)
{
throw new InvalidOperationException($"Slot {slotIndex} is not occupied.");
}
return slot.value;
}
/// <summary>
/// Returns a reference to the element at the specified slot index and generation, if it exists; otherwise, returns
/// a null reference.
/// </summary>
/// <param name="slotIndex">The zero-based index of the slot to retrieve. Must be within the valid range of allocated slots.</param>
/// <param name="generation">The expected generation value for the slot. Used to verify that the slot has not been recycled or replaced.</param>
/// <param name="exist">When this method returns, contains <see langword="true"/> if a valid element exists at the specified slot and generation; otherwise, <see langword="false"/>.</param>
/// <returns>A reference to the element of type <typeparamref name="T"/> at the specified slot and generation if it exists; otherwise, a null reference.</returns>
public ref T GetElementReferenceAt(int slotIndex, int generation, out bool exist)
{
if (slotIndex < 0 || slotIndex >= _capacity)
{
exist = false;
return ref Unsafe.NullRef<T>();
}
ref var slot = ref _data[slotIndex];
if (!slot.isValid|| slot.generation != generation)
{
exist = false;
return ref Unsafe.NullRef<T>();
}
exist = true;
return ref slot.value;
}
public void Resize(int newSize)
{
_data.Resize(newSize);
_freeSlots.Resize(newSize);
_capacity = newSize;
}
public void Clear()
{
_data.Clear();
_freeSlots.Clear();
_count = 0;
}
public unsafe readonly void* GetUnsafePtr()
{
return _data.GetUnsafePtr();
}
public void Dispose()
{
_data.Dispose();
_freeSlots.Dispose();
_count = 0;
_capacity = 0;
}
}

View File

@@ -1,7 +1,7 @@
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
using Misaki.HighPerformance.LowLevel.Contracts;
using Misaki.HighPerformance.LowLevel.Helpers;
using Misaki.HighPerformance.LowLevel.Utilities;
using System.Collections;
using System.Runtime.CompilerServices;
@@ -25,7 +25,7 @@ public unsafe struct UnsafeSparseSet<T> : IUnsafeCollection<T>
public struct Enumerator : IEnumerator<T>
{
private UnsafeSparseSet<T>* _collection;
private readonly UnsafeSparseSet<T>* _collection;
private int _index;
private T _value;

View File

@@ -1,11 +1,17 @@
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
using Misaki.HighPerformance.LowLevel.Helpers;
using Misaki.HighPerformance.LowLevel.Contracts;
using Misaki.HighPerformance.LowLevel.Utilities;
using System.Collections;
using System.Runtime.CompilerServices;
namespace Misaki.HighPerformance.LowLevel.Collections;
/// <summary>
/// Provides a high-performance, unsafe stack data structure for unmanaged types, supporting manual memory management
/// and allocation control.
/// </summary>
/// <typeparam name="T">The type of elements stored in the stack. Must be an unmanaged type.</typeparam>
public unsafe struct UnsafeStack<T> : IUnsafeCollection<T>
where T : unmanaged
{
@@ -24,15 +30,41 @@ public unsafe struct UnsafeStack<T> : IUnsafeCollection<T>
return GetEnumerator();
}
public UnsafeStack() : this(1, Allocator.Persistent)
/// <summary>
/// Invalid constructor, use <see cref="UnsafeStack(int, Allocator, AllocationOption)"/> or <see cref="UnsafeStack(int, ref AllocationHandle, AllocationOption)"/> instead.
/// </summary>
public UnsafeStack()
: this(0, Allocator.Invalid)
{
}
public UnsafeStack(int initialSize, Allocator allocator, AllocationOption allocationOption = AllocationOption.None)
/// <summary>
/// Initializes a new instance of the UnsafeStack class with the specified initial capacity and allocation options.
/// </summary>
/// <param name="initialCapacity">The number of elements the stack can initially hold. Must be greater than zero.</param>
/// <param name="handle">A reference to an AllocationHandle used to manage the underlying memory allocation for the stack.</param>
/// <param name="allocationOption">Specifies additional options for memory allocation. The default is AllocationOption.None.</param>
public UnsafeStack(int initialCapacity, ref AllocationHandle handle, AllocationOption allocationOption = AllocationOption.None)
{
_array = new UnsafeArray<T>(initialSize, allocator, allocationOption);
_array = new UnsafeArray<T>(initialCapacity, ref handle, allocationOption);
}
/// <summary>
/// Initializes a new instance of the UnsafeStack class with the specified initial capacity, allocator, and
/// allocation options.
/// </summary>
/// <param name="initialCapacity">The initial number of elements that the stack can hold. Must be greater than zero.</param>
/// <param name="allocator">The allocator to use for memory management of the stack's storage.</param>
/// <param name="allocationOption">The allocation option that determines how memory is allocated for the stack. The default is AllocationOption.None.</param>
public UnsafeStack(int initialCapacity, Allocator allocator, AllocationOption allocationOption = AllocationOption.None)
: this(initialCapacity, ref AllocationManager.GetAllocationHandle(allocator), allocationOption)
{
}
/// <summary>
/// Adds an element to the top of the stack.
/// </summary>
/// <param name="value">The element to add to the stack.</param>
public void Push(T value)
{
if (_count >= _array.Count)
@@ -44,6 +76,11 @@ public unsafe struct UnsafeStack<T> : IUnsafeCollection<T>
_count++;
}
/// <summary>
/// Removes and returns the object at the top of the stack.
/// </summary>
/// <returns>The object removed from the top of the stack.</returns>
/// <exception cref="InvalidOperationException">Thrown when the stack is empty.</exception>
public T Pop()
{
if (_count == 0)
@@ -55,6 +92,12 @@ public unsafe struct UnsafeStack<T> : IUnsafeCollection<T>
return _array[_count];
}
/// <summary>
/// Attempts to remove and return the object at the top of the stack.
/// </summary>
/// <param name="value">When this method returns, contains the object removed from the top of the stack, if the operation succeeded;
/// otherwise, the default value of <typeparamref name="T"/>.</param>
/// <returns>true if an object was successfully removed and returned from the stack; otherwise, false.</returns>
public bool TryPop(out T value)
{
if (_count == 0)
@@ -68,6 +111,11 @@ public unsafe struct UnsafeStack<T> : IUnsafeCollection<T>
return true;
}
/// <summary>
/// Returns the item at the top of the stack without removing it.
/// </summary>
/// <returns>The item of type <typeparamref name="T"/> at the top of the stack.</returns>
/// <exception cref="InvalidOperationException">Thrown when the stack is empty.</exception>
public readonly T Peek()
{
if (_count == 0)

View File

@@ -21,13 +21,9 @@
</ItemGroup>
<ItemGroup>
<None Update="Buffer\FixedString.tt">
<None Update="Collections\FixedString.tt">
<Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>FixedString.cs</LastGenOutput>
<Generator>TextTemplatingFileGenerator</Generator>
</None>
<None Update="Buffer\FixedStackString.tt">
<Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>FixedStackString.cs</LastGenOutput>
</None>
</ItemGroup>
@@ -36,15 +32,10 @@
</ItemGroup>
<ItemGroup>
<Compile Update="Buffer\FixedString.cs">
<Compile Update="Collections\FixedString.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>FixedString.tt</DependentUpon>
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
</Compile>
<Compile Update="Buffer\FixedStackString.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>FixedStackString.tt</DependentUpon>
</Compile>
</ItemGroup>

View File

@@ -4,7 +4,7 @@ using Misaki.HighPerformance.LowLevel.Contracts;
using System.Numerics;
using System.Runtime.CompilerServices;
namespace Misaki.HighPerformance.LowLevel.Helpers;
namespace Misaki.HighPerformance.LowLevel.Utilities;
public unsafe struct HashMapHelper<TKey> : IDisposable
where TKey : unmanaged, IEquatable<TKey>
@@ -135,12 +135,24 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
Clear();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int CeilPow2(int x)
{
x -= 1;
x |= x >> 1;
x |= x >> 2;
x |= x >> 4;
x |= x >> 8;
x |= x >> 16;
return x + 1;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private readonly int CalcCapacityCeilPow2(int capacity)
{
capacity = Math.Max(Math.Max(1, _count), capacity);
var newCapacity = Math.Max(capacity, 1 << _log2MinGrowth);
var result = MathUtilities.CeilPow2(newCapacity);
var result = CeilPow2(newCapacity);
return result;
}
@@ -204,7 +216,7 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
internal void Resize(int newCapacity)
{
newCapacity = Math.Max(newCapacity, _count);
var newBucketCapacity = MathUtilities.CeilPow2(newCapacity * 2);
var newBucketCapacity = CeilPow2(newCapacity * 2);
if (_capacity == newCapacity && _bucketCapacity == newBucketCapacity)
{

View File

@@ -3,7 +3,7 @@ using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
namespace Misaki.HighPerformance.LowLevel.Helpers;
namespace Misaki.HighPerformance.LowLevel.Utilities;
public static unsafe partial class MemoryUtilities
{

View File

@@ -1,7 +1,7 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Misaki.HighPerformance.LowLevel.Helpers;
namespace Misaki.HighPerformance.LowLevel.Utilities;
public static unsafe partial class MemoryUtilities
{

View File

@@ -3,7 +3,7 @@ using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
using System.Runtime.InteropServices;
namespace Misaki.HighPerformance.LowLevel.Helpers;
namespace Misaki.HighPerformance.LowLevel.Utilities;
/// <summary>
/// Provides extension methods for copying elements between unsafe collections and spans, converting collections to
@@ -208,6 +208,22 @@ public unsafe static class UnsafeCollectionExtensions
return new(source.GetUnsafePtr(), source.Count);
}
/// <summary>
/// Creates a span over a contiguous region of elements in the specified unsafe collection, starting at the given
/// index and covering the specified number of elements.
/// </summary>
/// <typeparam name="T">The type of elements in the collection. Must be an unmanaged type.</typeparam>
/// <param name="source">The unsafe collection from which to create the span. Must not be null.</param>
/// <param name="start">The zero-based index of the first element in the collection to include in the span. Must be greater than or equal to zero and less than the number of elements in the collection.</param>
/// <param name="length">The number of elements to include in the span. Must be greater than or equal to zero and the range defined by
/// <paramref name="start"/> and <paramref name="length"/> must not exceed the bounds of the collection.</param>
/// <returns>A <see cref="Span{T}"/> representing the specified region of the collection.</returns>
public static Span<T> AsSpan<T>(this IUnsafeCollection<T> source, int start, int length)
where T : unmanaged
{
return new((T*)source.GetUnsafePtr() + start, length);
}
/// <summary>
/// Converts an UnTypedCollection into a Span for efficient memory access.
/// </summary>
@@ -218,6 +234,20 @@ public unsafe static class UnsafeCollectionExtensions
return new(source.GetUnsafePtr(), (int)source.Size);
}
/// <summary>
/// Creates a span over a contiguous region of elements in the specified unsafe collection, starting at the given
/// index and covering the specified number of elements.
/// </summary>
/// <param name="source">The unsafe collection from which to create the span. Must not be null.</param>
/// <param name="start">The zero-based index of the first element in the collection to include in the span. Must be greater than or equal to zero and less than the number of elements in the collection.</param>
/// <param name="length">The number of elements to include in the span. Must be greater than or equal to zero and the range defined by
/// <paramref name="start"/> and <paramref name="length"/> must not exceed the bounds of the collection.</param>
/// <returns>A <see cref="Span{byte}"/> representing the specified region of the collection.</returns>
public static Span<byte> AsSpan(this IUnTypedCollection source, int start, int length)
{
return new((byte*)source.GetUnsafePtr() + start, length);
}
/// <summary>
/// Converts a managed array to an UnsafeArray by copying its elements to unmanaged memory.
/// </summary>

View File

@@ -1,7 +1,7 @@
using Misaki.HighPerformance.LowLevel.Collections;
using System.Runtime.CompilerServices;
namespace Misaki.HighPerformance.LowLevel.Helpers;
namespace Misaki.HighPerformance.LowLevel.Utilities;
public static unsafe class UnsafeUtilities
{