Improve performance and safety

This commit is contained in:
2026-02-01 01:56:17 +09:00
parent 1fee890329
commit c36405645b
32 changed files with 2050 additions and 360 deletions

View File

@@ -3,3 +3,4 @@ global using static Misaki.HighPerformance.LowLevel.Utilities.MemoryUtility;
global using unsafe AllocFunc = delegate*<void*, nuint, nuint, Misaki.HighPerformance.LowLevel.Buffer.AllocationOption, Misaki.HighPerformance.LowLevel.Buffer.MemoryHandle*, void*>;
global using unsafe ReallocFunc = delegate*<void*, void*, nuint, nuint, nuint, Misaki.HighPerformance.LowLevel.Buffer.AllocationOption, Misaki.HighPerformance.LowLevel.Buffer.MemoryHandle*, void*>;
global using unsafe FreeFunc = delegate*<void*, void*, Misaki.HighPerformance.LowLevel.Buffer.MemoryHandle, void>;
global using unsafe IsValidFunc = delegate*<void*, Misaki.HighPerformance.LowLevel.Buffer.MemoryHandle, bool>;

View File

@@ -1,57 +1,10 @@
using Misaki.HighPerformance.Collections;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Misaki.HighPerformance.LowLevel.Buffer;
public readonly struct MemoryHandle : IEquatable<MemoryHandle>
{
public readonly int id;
public readonly int generation;
public readonly bool IsValid => AllocationManager.ContainsAllocation(this);
public readonly static MemoryHandle Invalid = new(-1, -1);
public MemoryHandle(int id, int generation)
{
this.id = id;
this.generation = generation;
}
public bool Equals(MemoryHandle other)
{
return id == other.id && generation == other.generation;
}
public override bool Equals([NotNullWhen(true)] object? obj)
{
return obj is MemoryHandle other && Equals(other);
}
public override int GetHashCode()
{
return id ^ generation;
}
public override string? ToString()
{
return $"MemoryHandle(Id: {id}, Generation: {generation})";
}
public static bool operator ==(MemoryHandle left, MemoryHandle right)
{
return left.Equals(right);
}
public static bool operator !=(MemoryHandle left, MemoryHandle right)
{
return !(left == right);
}
}
/// <summary>
/// Holds information about a memory allocation.
/// </summary>
@@ -93,15 +46,27 @@ public static unsafe class AllocationManager
private struct ArenaAllocator : IAllocator, IDisposable
{
private const int _ARENA_MAGIC_ID = -3941029;
private DynamicArena _arena;
private AllocationHandle _handle;
private int _currentTick;
public readonly AllocationHandle Handle => _handle;
public readonly int CurrentTick => _currentTick;
public void Init(uint initialSize)
{
_arena = new DynamicArena(initialSize);
_handle = new AllocationHandle(Unsafe.AsPointer(ref this), &Allocate, &Reallocate, &Free);
_handle = new AllocationHandle
{
State = Unsafe.AsPointer(ref this),
Alloc = &Allocate,
Realloc = &Reallocate,
Free = null,
IsValid = &IsValid
};
_currentTick = 0;
}
private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption, MemoryHandle* pHandle)
@@ -114,7 +79,7 @@ public static unsafe class AllocationManager
return null;
}
*pHandle = GetMagicHandle();
*pHandle = new MemoryHandle(_ARENA_MAGIC_ID, selfPtr->_currentTick);
return ptr;
}
@@ -134,17 +99,20 @@ public static unsafe class AllocationManager
MemCpy(newPtr, ptr, Math.Min(oldSize, newSize));
*pHandle = new MemoryHandle(_ARENA_MAGIC_ID, selfPtr->_currentTick);
return newPtr;
}
private static void Free(void* instance, void* ptr, MemoryHandle handle)
private static bool IsValid(void* instance, MemoryHandle handle)
{
// The arena allocator does not free individual blocks, as it manages memory in chunks.
var selfPtr = (ArenaAllocator*)instance;
return handle.id == _ARENA_MAGIC_ID && handle.generation == selfPtr->_currentTick;
}
public void Reset()
{
_arena.Reset();
_currentTick++;
}
public void Dispose()
@@ -161,27 +129,33 @@ public static unsafe class AllocationManager
public void Init()
{
_handle = new AllocationHandle(Unsafe.AsPointer(ref this), &Allocate, &Reallocate, &Free);
_handle = new AllocationHandle
{
State = null,
Alloc = &Allocate,
Realloc = &Reallocate,
Free = &Free,
IsValid = &IsValid
};
}
private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption, MemoryHandle* pHandle)
private static void* Allocate(void* _, nuint size, nuint alignment, AllocationOption allocationOption, MemoryHandle* pHandle)
{
return HeapAlloc(size, alignment, allocationOption, pHandle);
}
private static void* Reallocate(void* instance, void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption, MemoryHandle* pHandle)
private static void* Reallocate(void* _, void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption, MemoryHandle* pHandle)
{
if (ptr == null)
{
return Allocate(instance, newSize, alignment, allocationOption, pHandle);
return Allocate(null, newSize, alignment, allocationOption, pHandle);
}
MemoryHandle newHandle;
var newPtr = HeapAlloc(newSize, alignment, allocationOption, &newHandle);
if (newPtr == null)
{
// Allocation failed, return original pointer
return ptr;
return null;
}
MemCpy(newPtr, ptr, Math.Min(oldSize, newSize));
@@ -191,15 +165,21 @@ public static unsafe class AllocationManager
return newPtr;
}
private static void Free(void* instance, void* ptr, MemoryHandle handle)
private static void Free(void* _, void* ptr, MemoryHandle handle)
{
HeapFree(ptr, handle);
}
private static bool IsValid(void* _, MemoryHandle handle)
{
return ContainsAllocation(handle);
}
}
private struct StackAllocator : IAllocator
{
// Thread-local stack for allocations. We does not track allocations across threads, which leads us to let system clean up the memory when thread exits.
private const int _STACK_MAGIC_ID = -6843541;
[ThreadStatic]
private static Stack s_stack;
private AllocationHandle _handle;
@@ -208,7 +188,14 @@ public static unsafe class AllocationManager
public void Init()
{
_handle = new(Unsafe.AsPointer(ref this), &Allocate, &Reallocate, &Free);
_handle = new AllocationHandle
{
State = Unsafe.AsPointer(ref this),
Alloc = &Allocate,
Realloc = &Reallocate,
Free = null,
IsValid = &IsValid
};
}
private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption, MemoryHandle* pHandle)
@@ -220,7 +207,7 @@ public static unsafe class AllocationManager
return null;
}
*pHandle = GetMagicHandle();
*pHandle = new MemoryHandle(_STACK_MAGIC_ID, (int)s_stack.Offset);
return ptr;
}
@@ -231,6 +218,24 @@ public static unsafe class AllocationManager
return Allocate(instance, newSize, alignment, allocationOption, pHandle);
}
// Optimize for last allocation. Set offset directly.
var oldBase = s_stack.Buffer + s_stack.Offset - oldSize;
if (ptr == oldBase)
{
if (newSize > oldSize)
{
var diff = newSize - oldSize;
s_stack.Offset += diff;
if (allocationOption.HasFlag(AllocationOption.Clear))
{
MemClear(s_stack.Buffer + s_stack.Offset - diff, diff);
}
}
*pHandle = new MemoryHandle(_STACK_MAGIC_ID, (int)s_stack.Offset);
return ptr;
}
var newPtr = s_stack.Allocate(newSize, alignment, allocationOption);
if (newPtr == null)
{
@@ -239,11 +244,13 @@ public static unsafe class AllocationManager
MemCpy(newPtr, ptr, Math.Min(oldSize, newSize));
*pHandle = new MemoryHandle(_STACK_MAGIC_ID, (int)s_stack.Offset);
return newPtr;
}
private static void Free(void* instance, void* ptr, MemoryHandle handle)
private static bool IsValid(void* instance, MemoryHandle handle)
{
return handle.id == _STACK_MAGIC_ID && handle.generation <= (int)s_stack.Offset;
}
public static Stack.Scope CreateScope(StackAllocator* pSelf)
@@ -264,7 +271,9 @@ public static unsafe class AllocationManager
private static AllocationHeader* s_pLiveHead;
private static SpinLock s_liveLock;
private readonly static ConcurrentSlotMap<IntPtr> s_allocations;
private static readonly ConcurrentSlotMap<IntPtr> s_allocations;
public static readonly MemoryHandle MagicHandle = new MemoryHandle(int.MinValue, int.MinValue);
/// <summary>
/// Gets the number of live persistent heap allocations when the debug layer is disabled.
@@ -276,11 +285,14 @@ public static unsafe class AllocationManager
/// </summary>
public static bool IsDebugLayerEnabled => s_debugLayer;
static AllocationManager()
{
s_pArenaAllocator = (ArenaAllocator*)NativeMemory.Alloc((nuint)sizeof(ArenaAllocator));
s_pHeapAllocator = (HeapAllocator*)NativeMemory.Alloc((nuint)sizeof(HeapAllocator));
s_pStackAllocator = (StackAllocator*)NativeMemory.Alloc((nuint)sizeof(StackAllocator));
var allocatorTotalSize = (nuint)(sizeof(ArenaAllocator) + sizeof(HeapAllocator) + sizeof(StackAllocator));
var basePtr = NativeMemory.Alloc(allocatorTotalSize);
s_pArenaAllocator = (ArenaAllocator*)basePtr;
s_pHeapAllocator = (HeapAllocator*)((byte*)basePtr + (nuint)sizeof(ArenaAllocator));
s_pStackAllocator = (StackAllocator*)((byte*)basePtr + (nuint)(sizeof(ArenaAllocator) + sizeof(HeapAllocator)));
s_liveLock = new SpinLock(false);
@@ -429,7 +441,7 @@ public static unsafe class AllocationManager
MemCpy(newUser, userPtr, newSize);
if (allocationOption.HasFlag(AllocationOption.Clear) && newSize > oldSize)
{
MemClear((byte*)newUser + oldSize, newSize - oldSize);
MemClear(newUser + oldSize, newSize - oldSize);
}
// Unlink and free the old block (without freeing the StackTrace pHandle again)
@@ -465,21 +477,21 @@ public static unsafe class AllocationManager
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static AllocationHandle GetAllocationHandle(Allocator allocator)
{
switch (allocator)
return allocator switch
{
case Allocator.Temp:
return s_pArenaAllocator->Handle;
case Allocator.Persistent:
return s_pHeapAllocator->Handle;
default:
throw new ArgumentException("Target allocator type does not support custom allocation.", nameof(allocator));
}
Allocator.Temp => s_pArenaAllocator->Handle,
Allocator.Persistent => s_pHeapAllocator->Handle,
_ => throw new ArgumentException("Target allocator type does not support custom allocation.", nameof(allocator)),
};
}
/// <summary>
/// Allocates a block of memory from the heap with the specified size and alignment, using the given allocation
/// options.
/// Allocates a block of memory from the heap with the specified size and alignment, using the given allocation options.
/// </summary>
/// <remarks>
/// This will allocate memory from the heap. If the debug layer is enabled, additional tracking information will be recorded.
/// The memory handle is always tracked unless the <see cref="AllocationOption.Untrack"/> flag is specified.
/// </remarks>
/// <param name="size">The number of bytes to allocate. Must be greater than zero.</param>
/// <param name="alignment">The alignment, in bytes, for the allocated memory block. Must be a power of two.</param>
/// <param name="allocationOption">An optional set of flags that control allocation behavior, such as whether the memory should be cleared or
@@ -488,8 +500,10 @@ public static unsafe class AllocationManager
/// <exception cref="OutOfMemoryException">Thrown if the allocation fails.</exception>
public static void* HeapAlloc(nuint size, nuint alignment, AllocationOption allocationOption, MemoryHandle* pHandle)
{
var isUntrack = allocationOption.HasFlag(AllocationOption.Untrack);
void* ptr;
if (s_debugLayer)
if (s_debugLayer && !isUntrack)
{
ptr = DebugAllocate(size, alignment);
}
@@ -509,7 +523,15 @@ public static unsafe class AllocationManager
MemClear(ptr, size);
}
*pHandle = AddAllocation((IntPtr)ptr);
if (isUntrack)
{
*pHandle = MagicHandle;
}
else
{
*pHandle = AddAllocation((IntPtr)ptr);
}
return ptr;
}
@@ -521,7 +543,7 @@ public static unsafe class AllocationManager
/// <param name="handle">The handle representing the memory allocation to free. The handle must be valid and previously allocated.</param>
public static void HeapFree(void* ptr, MemoryHandle handle)
{
if (s_debugLayer)
if (s_debugLayer && handle != MagicHandle)
{
DebugFree(ptr);
}
@@ -576,12 +598,6 @@ public static unsafe class AllocationManager
return new MemoryHandle(id, generation);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static MemoryHandle GetMagicHandle()
{
return new MemoryHandle(int.MinValue, int.MinValue);
}
/// <summary>
/// Removes the memory allocation associated with the specified handle.
/// </summary>
@@ -608,14 +624,17 @@ public static unsafe class AllocationManager
/// <summary>
/// Determines whether the specified memory handle refers to a currently tracked allocation.
/// </summary>
/// <remarks>
/// This only validates the memory when you added the allocation via <see cref="AddAllocation(IntPtr)"/>.
/// For validating memory from <see cref="AllocationHandle"/>, use <see cref="AllocationHandle.IsValid"/> instead.
/// </remarks>
/// <param name="handle">The memory handle to check for an associated allocation.</param>
/// <returns>true if the allocation corresponding to the handle exists; otherwise, false.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool ContainsAllocation(MemoryHandle handle)
{
if (handle.id == int.MinValue && handle.generation == int.MinValue)
if (handle == MagicHandle)
{
// Magic handle always valid
return true;
}
@@ -679,22 +698,16 @@ public static unsafe class AllocationManager
throw new MemoryLeakException($"Found {LiveAllocationCount} memory lakes! Please enable debug layer for more informations.");
}
// NOTE: Arena allocator holds the base ptr for all allocators, heap and stack allocators do not own any memory themselves.
if (s_pArenaAllocator != null)
{
s_pArenaAllocator->Dispose();
Stack.DisposeAll();
NativeMemory.Free(s_pArenaAllocator);
}
if (s_pHeapAllocator != null)
{
NativeMemory.Free(s_pHeapAllocator);
}
if (s_pStackAllocator != null)
{
NativeMemory.Free(s_pStackAllocator);
}
s_disposed = true;
}
}

View File

@@ -11,6 +11,10 @@ public enum AllocationOption : byte
/// Clear the memory to zero upon allocation.
/// </summary>
Clear = 1 << 0,
/// <summary>
/// Specify that this memory allocation should not been tracked competly, which <see cref="AllocationManager"/> will not perform any safty check like use after free and leack detection.
/// </summary>
Untrack = 1 << 1,
}
public enum Allocator : byte

View File

@@ -1,16 +1,62 @@
using System.Diagnostics.CodeAnalysis;
namespace Misaki.HighPerformance.LowLevel.Buffer;
public readonly struct MemoryHandle : IEquatable<MemoryHandle>
{
public readonly int id;
public readonly int generation;
public readonly static MemoryHandle Invalid = new(-1, -1);
public MemoryHandle(int id, int generation)
{
this.id = id;
this.generation = generation;
}
public bool Equals(MemoryHandle other)
{
return id == other.id && generation == other.generation;
}
public override bool Equals([NotNullWhen(true)] object? obj)
{
return obj is MemoryHandle other && Equals(other);
}
public override int GetHashCode()
{
return id ^ generation;
}
public override string? ToString()
{
return $"MemoryHandle(Id: {id}, Generation: {generation})";
}
public static bool operator ==(MemoryHandle left, MemoryHandle right)
{
return left.Equals(right);
}
public static bool operator !=(MemoryHandle left, MemoryHandle right)
{
return !(left == right);
}
}
/// <summary>
/// A structure that encapsulates function pointers for memory allocation operations.
/// </summary>
public readonly unsafe struct AllocationHandle
{
/// <summary>
/// Gets a pointer to the allocator instance associated with this allocation handle.
/// Gets a pointer to the state instance associated with this allocation handle.
/// </summary>
public void* pAllocator
public void* State
{
get;
get; init;
}
/// <summary>
@@ -18,7 +64,7 @@ public readonly unsafe struct AllocationHandle
/// </summary>
public AllocFunc Alloc
{
get;
get; init;
}
/// <summary>
@@ -26,7 +72,7 @@ public readonly unsafe struct AllocationHandle
/// </summary>
public ReallocFunc Realloc
{
get;
get; init;
}
/// <summary>
@@ -34,37 +80,29 @@ public readonly unsafe struct AllocationHandle
/// </summary>
public FreeFunc Free
{
get;
get; init;
}
/// <summary>
/// Initializes a new instance of the <see cref="AllocationHandle"/> struct with the specified allocator and memory
/// management functions.
/// Gets a function pointer for validating a memory handle.
/// </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)
public IsValidFunc IsValid
{
pAllocator = allocator;
Alloc = alloc;
Realloc = realloc;
Free = free;
get; init;
}
}
/// <summary>
/// Represents an allocator interface for managing memory allocations.
/// Represents an state interface for managing memory allocations.
/// </summary>
/// <remarks>
/// The allocator must be pined to a specific memory region.
/// Otherwise the reference of the <see cref="AllocationHandle.pAllocator"/>, may become invalid and lead to undefined behavior.
/// The state must be pined to a specific memory region.
/// Otherwise the reference of the <see cref="AllocationHandle.State"/>, may become invalid and lead to undefined behavior.
/// </remarks>
public interface IAllocator
{
/// <summary>
/// Gets a reference to the allocation handle associated with this allocator.
/// Gets a reference to the allocation handle associated with this state.
/// </summary>
AllocationHandle Handle
{

View File

@@ -2,12 +2,33 @@ using System.Runtime.CompilerServices;
namespace Misaki.HighPerformance.LowLevel.Buffer;
public unsafe partial struct Stack
{
private static void** s_pStackBuffers = null;
private static int s_stackCount = 0;
private static int s_stackCapacity = 0;
private static readonly SpinLock s_locker = new SpinLock(false);
public static void DisposeAll()
{
if (s_pStackBuffers == null)
{
return;
}
for (var i = 0; i < s_stackCount; i++)
{
Free(s_pStackBuffers[i]);
}
}
}
/// <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
public unsafe partial struct Stack : IDisposable
{
private const nuint _DEFAULT_SIZE = 1024 * 1024; // 1MB
@@ -42,6 +63,14 @@ public unsafe struct Stack : IDisposable
private nuint _offset;
private uint _activeScopeCount;
internal readonly byte* Buffer => _buffer;
public nuint Offset
{
readonly get => _offset;
internal set => _offset = value;
}
/// <summary>
/// Initializes a new instance of the StackAllocator class with a buffer of the specified size.
/// </summary>
@@ -67,6 +96,38 @@ public unsafe struct Stack : IDisposable
_size = size;
_offset = 0;
_activeScopeCount = 0;
var token = false;
try
{
s_locker.Enter(ref token);
if (s_pStackBuffers == null)
{
s_pStackBuffers = (void**)Malloc((nuint)sizeof(void*) * 4u);
s_stackCapacity = 4;
}
if (s_stackCount >= s_stackCapacity)
{
var pOld = s_pStackBuffers;
var newCapacity = s_stackCapacity * 2;
var pNew = (void**)Realloc(pOld, (nuint)sizeof(void*) * (nuint)newCapacity);
s_pStackBuffers = pNew;
s_stackCapacity = newCapacity;
}
s_pStackBuffers[s_stackCount] = _buffer;
s_stackCount++;
}
finally
{
if (token)
{
s_locker.Exit();
}
}
}
private readonly void ThrowIfNoScope()

View File

@@ -0,0 +1,758 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
namespace Misaki.HighPerformance.LowLevel.Collections;
/// <summary>
/// Represents a stack allocated fixed-size UTF-8 encoded string of length 32 bytes.
/// </summary>
/// <remarks>
/// This struct is designed to hold data on the stack. Every copy of this struct causes a copy of the underlying data.
/// 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 FixedString32
{
public const int MAX_LENGTH = 15;
private ushort _length;
private fixed char _buffer[MAX_LENGTH];
public readonly ushort Length => _length;
public string Value
{
get
{
fixed (char* bufferPtr = _buffer)
{
return new string(bufferPtr, 0, _length);
}
}
set
{
if (string.IsNullOrEmpty(value))
{
_length = 0;
return;
}
if (value.Length > MAX_LENGTH)
{
throw new ArgumentException("Input string is too long to fit in FixedString32.");
}
_length = (ushort)value.Length;
fixed (char* bufferPtr = _buffer)
fixed (char* valuePtr = value)
{
Unsafe.CopyBlockUnaligned(bufferPtr, valuePtr, (uint)(_length * sizeof(char)));
}
}
}
public FixedString32(string input)
: this(input.AsSpan())
{
}
public FixedString32(ReadOnlySpan<char> input)
{
if (input.Length > MAX_LENGTH)
{
throw new ArgumentException("Input byte array is too long to fit in FixedString32.");
}
_length = (ushort)input.Length;
fixed (char* inputPtr = input)
fixed (char* bufferPtr = _buffer)
{
Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, (uint)(_length * sizeof(char)));
}
}
public FixedString32(char* input, ushort length)
: this(new ReadOnlySpan<char>(input, length))
{
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<byte> AsSpan()
{
fixed (char* ptr = _buffer)
{
return new(ptr, _length);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly char* GetUnsafePtr()
{
return (char*)((ushort*)Unsafe.AsPointer(in this) + 1);
}
public override string ToString()
{
return Value;
}
}
/// <summary>
/// Represents a stack allocated fixed-size UTF-8 encoded string of length 64 bytes.
/// </summary>
/// <remarks>
/// This struct is designed to hold data on the stack. Every copy of this struct causes a copy of the underlying data.
/// 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 FixedString64
{
public const int MAX_LENGTH = 31;
private ushort _length;
private fixed char _buffer[MAX_LENGTH];
public readonly ushort Length => _length;
public string Value
{
get
{
fixed (char* bufferPtr = _buffer)
{
return new string(bufferPtr, 0, _length);
}
}
set
{
if (string.IsNullOrEmpty(value))
{
_length = 0;
return;
}
if (value.Length > MAX_LENGTH)
{
throw new ArgumentException("Input string is too long to fit in FixedString64.");
}
_length = (ushort)value.Length;
fixed (char* bufferPtr = _buffer)
fixed (char* valuePtr = value)
{
Unsafe.CopyBlockUnaligned(bufferPtr, valuePtr, (uint)(_length * sizeof(char)));
}
}
}
public FixedString64(string input)
: this(input.AsSpan())
{
}
public FixedString64(ReadOnlySpan<char> input)
{
if (input.Length > MAX_LENGTH)
{
throw new ArgumentException("Input byte array is too long to fit in FixedString64.");
}
_length = (ushort)input.Length;
fixed (char* inputPtr = input)
fixed (char* bufferPtr = _buffer)
{
Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, (uint)(_length * sizeof(char)));
}
}
public FixedString64(char* input, ushort length)
: this(new ReadOnlySpan<char>(input, length))
{
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<byte> AsSpan()
{
fixed (char* ptr = _buffer)
{
return new(ptr, _length);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly char* GetUnsafePtr()
{
return (char*)((ushort*)Unsafe.AsPointer(in this) + 1);
}
public override string ToString()
{
return Value;
}
}
/// <summary>
/// Represents a stack allocated fixed-size UTF-8 encoded string of length 128 bytes.
/// </summary>
/// <remarks>
/// This struct is designed to hold data on the stack. Every copy of this struct causes a copy of the underlying data.
/// 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 FixedString128
{
public const int MAX_LENGTH = 63;
private ushort _length;
private fixed char _buffer[MAX_LENGTH];
public readonly ushort Length => _length;
public string Value
{
get
{
fixed (char* bufferPtr = _buffer)
{
return new string(bufferPtr, 0, _length);
}
}
set
{
if (string.IsNullOrEmpty(value))
{
_length = 0;
return;
}
if (value.Length > MAX_LENGTH)
{
throw new ArgumentException("Input string is too long to fit in FixedString128.");
}
_length = (ushort)value.Length;
fixed (char* bufferPtr = _buffer)
fixed (char* valuePtr = value)
{
Unsafe.CopyBlockUnaligned(bufferPtr, valuePtr, (uint)(_length * sizeof(char)));
}
}
}
public FixedString128(string input)
: this(input.AsSpan())
{
}
public FixedString128(ReadOnlySpan<char> input)
{
if (input.Length > MAX_LENGTH)
{
throw new ArgumentException("Input byte array is too long to fit in FixedString128.");
}
_length = (ushort)input.Length;
fixed (char* inputPtr = input)
fixed (char* bufferPtr = _buffer)
{
Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, (uint)(_length * sizeof(char)));
}
}
public FixedString128(char* input, ushort length)
: this(new ReadOnlySpan<char>(input, length))
{
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<byte> AsSpan()
{
fixed (char* ptr = _buffer)
{
return new(ptr, _length);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly char* GetUnsafePtr()
{
return (char*)((ushort*)Unsafe.AsPointer(in this) + 1);
}
public override string ToString()
{
return Value;
}
}
/// <summary>
/// Represents a stack allocated fixed-size UTF-8 encoded string of length 256 bytes.
/// </summary>
/// <remarks>
/// This struct is designed to hold data on the stack. Every copy of this struct causes a copy of the underlying data.
/// 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 FixedString256
{
public const int MAX_LENGTH = 127;
private ushort _length;
private fixed char _buffer[MAX_LENGTH];
public readonly ushort Length => _length;
public string Value
{
get
{
fixed (char* bufferPtr = _buffer)
{
return new string(bufferPtr, 0, _length);
}
}
set
{
if (string.IsNullOrEmpty(value))
{
_length = 0;
return;
}
if (value.Length > MAX_LENGTH)
{
throw new ArgumentException("Input string is too long to fit in FixedString256.");
}
_length = (ushort)value.Length;
fixed (char* bufferPtr = _buffer)
fixed (char* valuePtr = value)
{
Unsafe.CopyBlockUnaligned(bufferPtr, valuePtr, (uint)(_length * sizeof(char)));
}
}
}
public FixedString256(string input)
: this(input.AsSpan())
{
}
public FixedString256(ReadOnlySpan<char> input)
{
if (input.Length > MAX_LENGTH)
{
throw new ArgumentException("Input byte array is too long to fit in FixedString256.");
}
_length = (ushort)input.Length;
fixed (char* inputPtr = input)
fixed (char* bufferPtr = _buffer)
{
Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, (uint)(_length * sizeof(char)));
}
}
public FixedString256(char* input, ushort length)
: this(new ReadOnlySpan<char>(input, length))
{
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<byte> AsSpan()
{
fixed (char* ptr = _buffer)
{
return new(ptr, _length);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly char* GetUnsafePtr()
{
return (char*)((ushort*)Unsafe.AsPointer(in this) + 1);
}
public override string ToString()
{
return Value;
}
}
/// <summary>
/// Represents a stack allocated fixed-size UTF-8 encoded string of length 512 bytes.
/// </summary>
/// <remarks>
/// This struct is designed to hold data on the stack. Every copy of this struct causes a copy of the underlying data.
/// 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 FixedString512
{
public const int MAX_LENGTH = 255;
private ushort _length;
private fixed char _buffer[MAX_LENGTH];
public readonly ushort Length => _length;
public string Value
{
get
{
fixed (char* bufferPtr = _buffer)
{
return new string(bufferPtr, 0, _length);
}
}
set
{
if (string.IsNullOrEmpty(value))
{
_length = 0;
return;
}
if (value.Length > MAX_LENGTH)
{
throw new ArgumentException("Input string is too long to fit in FixedString512.");
}
_length = (ushort)value.Length;
fixed (char* bufferPtr = _buffer)
fixed (char* valuePtr = value)
{
Unsafe.CopyBlockUnaligned(bufferPtr, valuePtr, (uint)(_length * sizeof(char)));
}
}
}
public FixedString512(string input)
: this(input.AsSpan())
{
}
public FixedString512(ReadOnlySpan<char> input)
{
if (input.Length > MAX_LENGTH)
{
throw new ArgumentException("Input byte array is too long to fit in FixedString512.");
}
_length = (ushort)input.Length;
fixed (char* inputPtr = input)
fixed (char* bufferPtr = _buffer)
{
Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, (uint)(_length * sizeof(char)));
}
}
public FixedString512(char* input, ushort length)
: this(new ReadOnlySpan<char>(input, length))
{
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<byte> AsSpan()
{
fixed (char* ptr = _buffer)
{
return new(ptr, _length);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly char* GetUnsafePtr()
{
return (char*)((ushort*)Unsafe.AsPointer(in this) + 1);
}
public override string ToString()
{
return Value;
}
}
/// <summary>
/// Represents a stack allocated fixed-size UTF-8 encoded string of length 1024 bytes.
/// </summary>
/// <remarks>
/// This struct is designed to hold data on the stack. Every copy of this struct causes a copy of the underlying data.
/// 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 FixedString1024
{
public const int MAX_LENGTH = 511;
private ushort _length;
private fixed char _buffer[MAX_LENGTH];
public readonly ushort Length => _length;
public string Value
{
get
{
fixed (char* bufferPtr = _buffer)
{
return new string(bufferPtr, 0, _length);
}
}
set
{
if (string.IsNullOrEmpty(value))
{
_length = 0;
return;
}
if (value.Length > MAX_LENGTH)
{
throw new ArgumentException("Input string is too long to fit in FixedString1024.");
}
_length = (ushort)value.Length;
fixed (char* bufferPtr = _buffer)
fixed (char* valuePtr = value)
{
Unsafe.CopyBlockUnaligned(bufferPtr, valuePtr, (uint)(_length * sizeof(char)));
}
}
}
public FixedString1024(string input)
: this(input.AsSpan())
{
}
public FixedString1024(ReadOnlySpan<char> input)
{
if (input.Length > MAX_LENGTH)
{
throw new ArgumentException("Input byte array is too long to fit in FixedString1024.");
}
_length = (ushort)input.Length;
fixed (char* inputPtr = input)
fixed (char* bufferPtr = _buffer)
{
Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, (uint)(_length * sizeof(char)));
}
}
public FixedString1024(char* input, ushort length)
: this(new ReadOnlySpan<char>(input, length))
{
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<byte> AsSpan()
{
fixed (char* ptr = _buffer)
{
return new(ptr, _length);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly char* GetUnsafePtr()
{
return (char*)((ushort*)Unsafe.AsPointer(in this) + 1);
}
public override string ToString()
{
return Value;
}
}
/// <summary>
/// Represents a stack allocated fixed-size UTF-8 encoded string of length 2048 bytes.
/// </summary>
/// <remarks>
/// This struct is designed to hold data on the stack. Every copy of this struct causes a copy of the underlying data.
/// 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 FixedString2048
{
public const int MAX_LENGTH = 1023;
private ushort _length;
private fixed char _buffer[MAX_LENGTH];
public readonly ushort Length => _length;
public string Value
{
get
{
fixed (char* bufferPtr = _buffer)
{
return new string(bufferPtr, 0, _length);
}
}
set
{
if (string.IsNullOrEmpty(value))
{
_length = 0;
return;
}
if (value.Length > MAX_LENGTH)
{
throw new ArgumentException("Input string is too long to fit in FixedString2048.");
}
_length = (ushort)value.Length;
fixed (char* bufferPtr = _buffer)
fixed (char* valuePtr = value)
{
Unsafe.CopyBlockUnaligned(bufferPtr, valuePtr, (uint)(_length * sizeof(char)));
}
}
}
public FixedString2048(string input)
: this(input.AsSpan())
{
}
public FixedString2048(ReadOnlySpan<char> input)
{
if (input.Length > MAX_LENGTH)
{
throw new ArgumentException("Input byte array is too long to fit in FixedString2048.");
}
_length = (ushort)input.Length;
fixed (char* inputPtr = input)
fixed (char* bufferPtr = _buffer)
{
Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, (uint)(_length * sizeof(char)));
}
}
public FixedString2048(char* input, ushort length)
: this(new ReadOnlySpan<char>(input, length))
{
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<byte> AsSpan()
{
fixed (char* ptr = _buffer)
{
return new(ptr, _length);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly char* GetUnsafePtr()
{
return (char*)((ushort*)Unsafe.AsPointer(in this) + 1);
}
public override string ToString()
{
return Value;
}
}
/// <summary>
/// Represents a stack allocated fixed-size UTF-8 encoded string of length 4096 bytes.
/// </summary>
/// <remarks>
/// This struct is designed to hold data on the stack. Every copy of this struct causes a copy of the underlying data.
/// 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 FixedString4096
{
public const int MAX_LENGTH = 2047;
private ushort _length;
private fixed char _buffer[MAX_LENGTH];
public readonly ushort Length => _length;
public string Value
{
get
{
fixed (char* bufferPtr = _buffer)
{
return new string(bufferPtr, 0, _length);
}
}
set
{
if (string.IsNullOrEmpty(value))
{
_length = 0;
return;
}
if (value.Length > MAX_LENGTH)
{
throw new ArgumentException("Input string is too long to fit in FixedString4096.");
}
_length = (ushort)value.Length;
fixed (char* bufferPtr = _buffer)
fixed (char* valuePtr = value)
{
Unsafe.CopyBlockUnaligned(bufferPtr, valuePtr, (uint)(_length * sizeof(char)));
}
}
}
public FixedString4096(string input)
: this(input.AsSpan())
{
}
public FixedString4096(ReadOnlySpan<char> input)
{
if (input.Length > MAX_LENGTH)
{
throw new ArgumentException("Input byte array is too long to fit in FixedString4096.");
}
_length = (ushort)input.Length;
fixed (char* inputPtr = input)
fixed (char* bufferPtr = _buffer)
{
Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, (uint)(_length * sizeof(char)));
}
}
public FixedString4096(char* input, ushort length)
: this(new ReadOnlySpan<char>(input, length))
{
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<byte> AsSpan()
{
fixed (char* ptr = _buffer)
{
return new(ptr, _length);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly char* GetUnsafePtr()
{
return (char*)((ushort*)Unsafe.AsPointer(in this) + 1);
}
public override string ToString()
{
return Value;
}
}

View File

@@ -0,0 +1,108 @@
<#@ 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=".gen.cs" #>
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
namespace Misaki.HighPerformance.LowLevel.Collections;
<# for (int i = 32; i <= 4096; i *= 2) { #>
/// <summary>
/// Represents a stack allocated fixed-size UTF-8 encoded string of length <#= i #> bytes.
/// </summary>
/// <remarks>
/// This struct is designed to hold data on the stack. Every copy of this struct causes a copy of the underlying data.
/// 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 FixedString<#= i #>
{
public const int MAX_LENGTH = <#= (i - 2) / 2 #>;
private ushort _length;
private fixed char _buffer[MAX_LENGTH];
public readonly ushort Length => _length;
public string Value
{
get
{
fixed (char* bufferPtr = _buffer)
{
return new string(bufferPtr, 0, _length);
}
}
set
{
if (string.IsNullOrEmpty(value))
{
_length = 0;
return;
}
if (value.Length > MAX_LENGTH)
{
throw new ArgumentException("Input string is too long to fit in FixedString<#= i #>.");
}
_length = (ushort)value.Length;
fixed (char* bufferPtr = _buffer)
fixed (char* valuePtr = value)
{
Unsafe.CopyBlockUnaligned(bufferPtr, valuePtr, (uint)(_length * sizeof(char)));
}
}
}
public FixedString<#= i #>(string input)
: this(input.AsSpan())
{
}
public FixedString<#= i #>(ReadOnlySpan<char> input)
{
if (input.Length > MAX_LENGTH)
{
throw new ArgumentException("Input byte array is too long to fit in FixedString<#= i #>.");
}
_length = (ushort)input.Length;
fixed (char* inputPtr = input)
fixed (char* bufferPtr = _buffer)
{
Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, (uint)(_length * sizeof(char)));
}
}
public FixedString<#= i #>(char* input, ushort length)
: this(new ReadOnlySpan<char>(input, length))
{
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<byte> AsSpan()
{
fixed (char* ptr = _buffer)
{
return new(ptr, _length);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly char* GetUnsafePtr()
{
return (char*)((ushort*)Unsafe.AsPointer(in this) + 1);
}
public override string ToString()
{
return Value;
}
}
<# } #>

View File

@@ -14,8 +14,10 @@ namespace Misaki.HighPerformance.LowLevel.Collections;
[StructLayout(LayoutKind.Sequential, Size = 32)]
public unsafe struct FixedText32
{
public const int MAX_LENGTH = 30;
private ushort _length;
private fixed byte _buffer[30];
private fixed byte _buffer[MAX_LENGTH];
public readonly ushort Length => _length;
public string Value
@@ -36,14 +38,14 @@ public unsafe struct FixedText32
}
var maxBytes = Encoding.UTF8.GetByteCount(value);
if (maxBytes > 30)
if (maxBytes > MAX_LENGTH)
{
throw new ArgumentException("Input string is too long to fit in FixedText32.");
}
fixed (byte* bufferPtr = _buffer)
{
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, 30));
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, MAX_LENGTH));
}
}
}
@@ -51,7 +53,7 @@ public unsafe struct FixedText32
public FixedText32(ReadOnlySpan<char> input)
{
var maxBytes = Encoding.UTF8.GetByteCount(input);
if (maxBytes > 30)
if (maxBytes > MAX_LENGTH)
{
throw new ArgumentException("Input string is too long to fit in FixedText32.");
}
@@ -59,7 +61,7 @@ public unsafe struct FixedText32
fixed (char* inputPtr = input)
fixed (byte* bufferPtr = _buffer)
{
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, 30);
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, MAX_LENGTH);
_length = (ushort)actualByteCount;
}
}
@@ -76,7 +78,7 @@ public unsafe struct FixedText32
public FixedText32(ReadOnlySpan<byte> input)
{
if (input.Length > 30)
if (input.Length > MAX_LENGTH)
{
throw new ArgumentException("Input byte array is too long to fit in FixedText32.");
}
@@ -105,12 +107,9 @@ public unsafe struct FixedText32
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public byte* GetUnsafePointer()
public readonly byte* GetUnsafePtr()
{
fixed (byte* ptr = _buffer)
{
return ptr;
}
return (byte*)((ushort*)Unsafe.AsPointer(in this) + 1);
}
public override string ToString()
@@ -129,8 +128,10 @@ public unsafe struct FixedText32
[StructLayout(LayoutKind.Sequential, Size = 64)]
public unsafe struct FixedText64
{
public const int MAX_LENGTH = 62;
private ushort _length;
private fixed byte _buffer[62];
private fixed byte _buffer[MAX_LENGTH];
public readonly ushort Length => _length;
public string Value
@@ -151,14 +152,14 @@ public unsafe struct FixedText64
}
var maxBytes = Encoding.UTF8.GetByteCount(value);
if (maxBytes > 62)
if (maxBytes > MAX_LENGTH)
{
throw new ArgumentException("Input string is too long to fit in FixedText64.");
}
fixed (byte* bufferPtr = _buffer)
{
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, 62));
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, MAX_LENGTH));
}
}
}
@@ -166,7 +167,7 @@ public unsafe struct FixedText64
public FixedText64(ReadOnlySpan<char> input)
{
var maxBytes = Encoding.UTF8.GetByteCount(input);
if (maxBytes > 62)
if (maxBytes > MAX_LENGTH)
{
throw new ArgumentException("Input string is too long to fit in FixedText64.");
}
@@ -174,7 +175,7 @@ public unsafe struct FixedText64
fixed (char* inputPtr = input)
fixed (byte* bufferPtr = _buffer)
{
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, 62);
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, MAX_LENGTH);
_length = (ushort)actualByteCount;
}
}
@@ -191,7 +192,7 @@ public unsafe struct FixedText64
public FixedText64(ReadOnlySpan<byte> input)
{
if (input.Length > 62)
if (input.Length > MAX_LENGTH)
{
throw new ArgumentException("Input byte array is too long to fit in FixedText64.");
}
@@ -220,12 +221,9 @@ public unsafe struct FixedText64
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public byte* GetUnsafePointer()
public readonly byte* GetUnsafePtr()
{
fixed (byte* ptr = _buffer)
{
return ptr;
}
return (byte*)((ushort*)Unsafe.AsPointer(in this) + 1);
}
public override string ToString()
@@ -244,8 +242,10 @@ public unsafe struct FixedText64
[StructLayout(LayoutKind.Sequential, Size = 128)]
public unsafe struct FixedText128
{
public const int MAX_LENGTH = 126;
private ushort _length;
private fixed byte _buffer[126];
private fixed byte _buffer[MAX_LENGTH];
public readonly ushort Length => _length;
public string Value
@@ -266,14 +266,14 @@ public unsafe struct FixedText128
}
var maxBytes = Encoding.UTF8.GetByteCount(value);
if (maxBytes > 126)
if (maxBytes > MAX_LENGTH)
{
throw new ArgumentException("Input string is too long to fit in FixedText128.");
}
fixed (byte* bufferPtr = _buffer)
{
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, 126));
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, MAX_LENGTH));
}
}
}
@@ -281,7 +281,7 @@ public unsafe struct FixedText128
public FixedText128(ReadOnlySpan<char> input)
{
var maxBytes = Encoding.UTF8.GetByteCount(input);
if (maxBytes > 126)
if (maxBytes > MAX_LENGTH)
{
throw new ArgumentException("Input string is too long to fit in FixedText128.");
}
@@ -289,7 +289,7 @@ public unsafe struct FixedText128
fixed (char* inputPtr = input)
fixed (byte* bufferPtr = _buffer)
{
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, 126);
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, MAX_LENGTH);
_length = (ushort)actualByteCount;
}
}
@@ -306,7 +306,7 @@ public unsafe struct FixedText128
public FixedText128(ReadOnlySpan<byte> input)
{
if (input.Length > 126)
if (input.Length > MAX_LENGTH)
{
throw new ArgumentException("Input byte array is too long to fit in FixedText128.");
}
@@ -335,12 +335,9 @@ public unsafe struct FixedText128
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public byte* GetUnsafePointer()
public readonly byte* GetUnsafePtr()
{
fixed (byte* ptr = _buffer)
{
return ptr;
}
return (byte*)((ushort*)Unsafe.AsPointer(in this) + 1);
}
public override string ToString()
@@ -359,8 +356,10 @@ public unsafe struct FixedText128
[StructLayout(LayoutKind.Sequential, Size = 256)]
public unsafe struct FixedText256
{
public const int MAX_LENGTH = 254;
private ushort _length;
private fixed byte _buffer[254];
private fixed byte _buffer[MAX_LENGTH];
public readonly ushort Length => _length;
public string Value
@@ -381,14 +380,14 @@ public unsafe struct FixedText256
}
var maxBytes = Encoding.UTF8.GetByteCount(value);
if (maxBytes > 254)
if (maxBytes > MAX_LENGTH)
{
throw new ArgumentException("Input string is too long to fit in FixedText256.");
}
fixed (byte* bufferPtr = _buffer)
{
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, 254));
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, MAX_LENGTH));
}
}
}
@@ -396,7 +395,7 @@ public unsafe struct FixedText256
public FixedText256(ReadOnlySpan<char> input)
{
var maxBytes = Encoding.UTF8.GetByteCount(input);
if (maxBytes > 254)
if (maxBytes > MAX_LENGTH)
{
throw new ArgumentException("Input string is too long to fit in FixedText256.");
}
@@ -404,7 +403,7 @@ public unsafe struct FixedText256
fixed (char* inputPtr = input)
fixed (byte* bufferPtr = _buffer)
{
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, 254);
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, MAX_LENGTH);
_length = (ushort)actualByteCount;
}
}
@@ -421,7 +420,7 @@ public unsafe struct FixedText256
public FixedText256(ReadOnlySpan<byte> input)
{
if (input.Length > 254)
if (input.Length > MAX_LENGTH)
{
throw new ArgumentException("Input byte array is too long to fit in FixedText256.");
}
@@ -450,12 +449,9 @@ public unsafe struct FixedText256
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public byte* GetUnsafePointer()
public readonly byte* GetUnsafePtr()
{
fixed (byte* ptr = _buffer)
{
return ptr;
}
return (byte*)((ushort*)Unsafe.AsPointer(in this) + 1);
}
public override string ToString()
@@ -474,8 +470,10 @@ public unsafe struct FixedText256
[StructLayout(LayoutKind.Sequential, Size = 512)]
public unsafe struct FixedText512
{
public const int MAX_LENGTH = 510;
private ushort _length;
private fixed byte _buffer[510];
private fixed byte _buffer[MAX_LENGTH];
public readonly ushort Length => _length;
public string Value
@@ -496,14 +494,14 @@ public unsafe struct FixedText512
}
var maxBytes = Encoding.UTF8.GetByteCount(value);
if (maxBytes > 510)
if (maxBytes > MAX_LENGTH)
{
throw new ArgumentException("Input string is too long to fit in FixedText512.");
}
fixed (byte* bufferPtr = _buffer)
{
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, 510));
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, MAX_LENGTH));
}
}
}
@@ -511,7 +509,7 @@ public unsafe struct FixedText512
public FixedText512(ReadOnlySpan<char> input)
{
var maxBytes = Encoding.UTF8.GetByteCount(input);
if (maxBytes > 510)
if (maxBytes > MAX_LENGTH)
{
throw new ArgumentException("Input string is too long to fit in FixedText512.");
}
@@ -519,7 +517,7 @@ public unsafe struct FixedText512
fixed (char* inputPtr = input)
fixed (byte* bufferPtr = _buffer)
{
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, 510);
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, MAX_LENGTH);
_length = (ushort)actualByteCount;
}
}
@@ -536,7 +534,7 @@ public unsafe struct FixedText512
public FixedText512(ReadOnlySpan<byte> input)
{
if (input.Length > 510)
if (input.Length > MAX_LENGTH)
{
throw new ArgumentException("Input byte array is too long to fit in FixedText512.");
}
@@ -565,12 +563,9 @@ public unsafe struct FixedText512
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public byte* GetUnsafePointer()
public readonly byte* GetUnsafePtr()
{
fixed (byte* ptr = _buffer)
{
return ptr;
}
return (byte*)((ushort*)Unsafe.AsPointer(in this) + 1);
}
public override string ToString()
@@ -589,8 +584,10 @@ public unsafe struct FixedText512
[StructLayout(LayoutKind.Sequential, Size = 1024)]
public unsafe struct FixedText1024
{
public const int MAX_LENGTH = 1022;
private ushort _length;
private fixed byte _buffer[1022];
private fixed byte _buffer[MAX_LENGTH];
public readonly ushort Length => _length;
public string Value
@@ -611,14 +608,14 @@ public unsafe struct FixedText1024
}
var maxBytes = Encoding.UTF8.GetByteCount(value);
if (maxBytes > 1022)
if (maxBytes > MAX_LENGTH)
{
throw new ArgumentException("Input string is too long to fit in FixedText1024.");
}
fixed (byte* bufferPtr = _buffer)
{
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, 1022));
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, MAX_LENGTH));
}
}
}
@@ -626,7 +623,7 @@ public unsafe struct FixedText1024
public FixedText1024(ReadOnlySpan<char> input)
{
var maxBytes = Encoding.UTF8.GetByteCount(input);
if (maxBytes > 1022)
if (maxBytes > MAX_LENGTH)
{
throw new ArgumentException("Input string is too long to fit in FixedText1024.");
}
@@ -634,7 +631,7 @@ public unsafe struct FixedText1024
fixed (char* inputPtr = input)
fixed (byte* bufferPtr = _buffer)
{
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, 1022);
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, MAX_LENGTH);
_length = (ushort)actualByteCount;
}
}
@@ -651,7 +648,7 @@ public unsafe struct FixedText1024
public FixedText1024(ReadOnlySpan<byte> input)
{
if (input.Length > 1022)
if (input.Length > MAX_LENGTH)
{
throw new ArgumentException("Input byte array is too long to fit in FixedText1024.");
}
@@ -680,12 +677,9 @@ public unsafe struct FixedText1024
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public byte* GetUnsafePointer()
public readonly byte* GetUnsafePtr()
{
fixed (byte* ptr = _buffer)
{
return ptr;
}
return (byte*)((ushort*)Unsafe.AsPointer(in this) + 1);
}
public override string ToString()
@@ -704,8 +698,10 @@ public unsafe struct FixedText1024
[StructLayout(LayoutKind.Sequential, Size = 2048)]
public unsafe struct FixedText2048
{
public const int MAX_LENGTH = 2046;
private ushort _length;
private fixed byte _buffer[2046];
private fixed byte _buffer[MAX_LENGTH];
public readonly ushort Length => _length;
public string Value
@@ -726,14 +722,14 @@ public unsafe struct FixedText2048
}
var maxBytes = Encoding.UTF8.GetByteCount(value);
if (maxBytes > 2046)
if (maxBytes > MAX_LENGTH)
{
throw new ArgumentException("Input string is too long to fit in FixedText2048.");
}
fixed (byte* bufferPtr = _buffer)
{
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, 2046));
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, MAX_LENGTH));
}
}
}
@@ -741,7 +737,7 @@ public unsafe struct FixedText2048
public FixedText2048(ReadOnlySpan<char> input)
{
var maxBytes = Encoding.UTF8.GetByteCount(input);
if (maxBytes > 2046)
if (maxBytes > MAX_LENGTH)
{
throw new ArgumentException("Input string is too long to fit in FixedText2048.");
}
@@ -749,7 +745,7 @@ public unsafe struct FixedText2048
fixed (char* inputPtr = input)
fixed (byte* bufferPtr = _buffer)
{
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, 2046);
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, MAX_LENGTH);
_length = (ushort)actualByteCount;
}
}
@@ -766,7 +762,7 @@ public unsafe struct FixedText2048
public FixedText2048(ReadOnlySpan<byte> input)
{
if (input.Length > 2046)
if (input.Length > MAX_LENGTH)
{
throw new ArgumentException("Input byte array is too long to fit in FixedText2048.");
}
@@ -795,12 +791,9 @@ public unsafe struct FixedText2048
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public byte* GetUnsafePointer()
public readonly byte* GetUnsafePtr()
{
fixed (byte* ptr = _buffer)
{
return ptr;
}
return (byte*)((ushort*)Unsafe.AsPointer(in this) + 1);
}
public override string ToString()
@@ -819,8 +812,10 @@ public unsafe struct FixedText2048
[StructLayout(LayoutKind.Sequential, Size = 4096)]
public unsafe struct FixedText4096
{
public const int MAX_LENGTH = 4094;
private ushort _length;
private fixed byte _buffer[4094];
private fixed byte _buffer[MAX_LENGTH];
public readonly ushort Length => _length;
public string Value
@@ -841,14 +836,14 @@ public unsafe struct FixedText4096
}
var maxBytes = Encoding.UTF8.GetByteCount(value);
if (maxBytes > 4094)
if (maxBytes > MAX_LENGTH)
{
throw new ArgumentException("Input string is too long to fit in FixedText4096.");
}
fixed (byte* bufferPtr = _buffer)
{
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, 4094));
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, MAX_LENGTH));
}
}
}
@@ -856,7 +851,7 @@ public unsafe struct FixedText4096
public FixedText4096(ReadOnlySpan<char> input)
{
var maxBytes = Encoding.UTF8.GetByteCount(input);
if (maxBytes > 4094)
if (maxBytes > MAX_LENGTH)
{
throw new ArgumentException("Input string is too long to fit in FixedText4096.");
}
@@ -864,7 +859,7 @@ public unsafe struct FixedText4096
fixed (char* inputPtr = input)
fixed (byte* bufferPtr = _buffer)
{
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, 4094);
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, MAX_LENGTH);
_length = (ushort)actualByteCount;
}
}
@@ -881,7 +876,7 @@ public unsafe struct FixedText4096
public FixedText4096(ReadOnlySpan<byte> input)
{
if (input.Length > 4094)
if (input.Length > MAX_LENGTH)
{
throw new ArgumentException("Input byte array is too long to fit in FixedText4096.");
}
@@ -910,12 +905,9 @@ public unsafe struct FixedText4096
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public byte* GetUnsafePointer()
public readonly byte* GetUnsafePtr()
{
fixed (byte* ptr = _buffer)
{
return ptr;
}
return (byte*)((ushort*)Unsafe.AsPointer(in this) + 1);
}
public override string ToString()

View File

@@ -21,8 +21,10 @@ namespace Misaki.HighPerformance.LowLevel.Collections;
[StructLayout(LayoutKind.Sequential, Size = <#= i #>)]
public unsafe struct FixedText<#= i #>
{
public const int MAX_LENGTH = <#= i - 2 #>;
private ushort _length;
private fixed byte _buffer[<#= i - 2 #>];
private fixed byte _buffer[MAX_LENGTH];
public readonly ushort Length => _length;
public string Value
@@ -43,14 +45,14 @@ public unsafe struct FixedText<#= i #>
}
var maxBytes = Encoding.UTF8.GetByteCount(value);
if (maxBytes > <#= i - 2 #>)
if (maxBytes > MAX_LENGTH)
{
throw new ArgumentException("Input string is too long to fit in FixedText<#= i #>.");
}
fixed (byte* bufferPtr = _buffer)
{
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, <#= i - 2 #>));
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, MAX_LENGTH));
}
}
}
@@ -58,7 +60,7 @@ public unsafe struct FixedText<#= i #>
public FixedText<#= i #>(ReadOnlySpan<char> input)
{
var maxBytes = Encoding.UTF8.GetByteCount(input);
if (maxBytes > <#= i - 2 #>)
if (maxBytes > MAX_LENGTH)
{
throw new ArgumentException("Input string is too long to fit in FixedText<#= i #>.");
}
@@ -66,7 +68,7 @@ public unsafe struct FixedText<#= i #>
fixed (char* inputPtr = input)
fixed (byte* bufferPtr = _buffer)
{
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, <#= i - 2 #>);
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, MAX_LENGTH);
_length = (ushort)actualByteCount;
}
}
@@ -83,7 +85,7 @@ public unsafe struct FixedText<#= i #>
public FixedText<#= i #>(ReadOnlySpan<byte> input)
{
if (input.Length > <#= i - 2 #>)
if (input.Length > MAX_LENGTH)
{
throw new ArgumentException("Input byte array is too long to fit in FixedText<#= i #>.");
}
@@ -112,12 +114,9 @@ public unsafe struct FixedText<#= i #>
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public byte* GetUnsafePointer()
public readonly byte* GetUnsafePtr()
{
fixed (byte* ptr = _buffer)
{
return ptr;
}
return (byte*)((ushort*)Unsafe.AsPointer(in this) + 1);
}
public override string ToString()

View File

@@ -1,5 +1,6 @@
using Misaki.HighPerformance.LowLevel.Utilities;
using System.Collections;
using System.Diagnostics;
using System.Runtime.CompilerServices;
namespace Misaki.HighPerformance.LowLevel.Collections;
@@ -50,18 +51,26 @@ public readonly unsafe struct ReadOnlyUnsafeCollection<T> : IEnumerable<T>
private readonly T* _buffer;
private readonly int _count;
public readonly int Count => _count;
public int Count => _count;
public readonly T this[int index]
public ref readonly T this[int index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => UnsafeUtility.ReadArrayElement<T>(_buffer, index);
get
{
CheckIndexBounds(index);
return ref UnsafeUtility.ReadArrayElementRef<T>(_buffer, index);
}
}
public readonly T this[uint index]
public ref readonly T this[uint index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => UnsafeUtility.ReadArrayElement<T>(_buffer, index);
get
{
CheckIndexBounds((int)index);
return ref UnsafeUtility.ReadArrayElementRef<T>(_buffer, index);
}
}
public Enumerator GetEnumerator() => new Enumerator(in this);
@@ -74,6 +83,16 @@ public readonly unsafe struct ReadOnlyUnsafeCollection<T> : IEnumerable<T>
_count = count;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Conditional("ENABLE_COLLECTION_CHECKS")]
private readonly void CheckIndexBounds(int index)
{
if (index >= _count)
{
throw new ArgumentOutOfRangeException(nameof(index), "Index is out of range.");
}
}
/// <summary>
/// Returns a read-only span that represents the valid elements in the underlying buffer.
/// </summary>

View File

@@ -17,7 +17,25 @@ public unsafe struct UnTypedArray : IUnTypedCollection
public readonly nuint Size => _size;
public readonly nuint Alignment => _alignment;
public readonly bool IsCreated => _buffer != null && _allocationHandle.pAllocator != null && _memoryHandle.IsValid;
public readonly bool IsCreated
{
get
{
if (_buffer != null)
{
if (_allocationHandle.IsValid != null)
{
return _allocationHandle.IsValid(_allocationHandle.State, _memoryHandle);
}
else
{
return true;
}
}
return false;
}
}
/// <summary>
/// Constructs an UnsafeArray with a default size of 1 and uses the Persistent allocator.
@@ -34,8 +52,13 @@ public unsafe struct UnTypedArray : IUnTypedCollection
throw new ArgumentOutOfRangeException(nameof(size), "Count must be greater than zero.");
}
if (handle.Alloc == null)
{
throw new InvalidOperationException("Target allocation handle does not support allocation.");
}
MemoryHandle memHandle;
_buffer = handle.Alloc(_allocationHandle.pAllocator, size, alignment, allocationOption, &memHandle);
_buffer = handle.Alloc(_allocationHandle.State, size, alignment, allocationOption, &memHandle);
_size = size;
_alignment = alignment;
@@ -87,7 +110,7 @@ public unsafe struct UnTypedArray : IUnTypedCollection
}
MemoryHandle memHandle = _memoryHandle;
_buffer = _allocationHandle.Realloc(_allocationHandle.pAllocator, _buffer, _size, newSize, _alignment, option, &memHandle);
_buffer = _allocationHandle.Realloc(_allocationHandle.State, _buffer, _size, newSize, _alignment, option, &memHandle);
_size = newSize;
_memoryHandle = memHandle;
}
@@ -232,9 +255,9 @@ public unsafe struct UnTypedArray : IUnTypedCollection
return;
}
if (_allocationHandle.pAllocator != null)
if (_allocationHandle.Free != null)
{
_allocationHandle.Free(_allocationHandle.pAllocator, _buffer, _memoryHandle);
_allocationHandle.Free(_allocationHandle.State, _buffer, _memoryHandle);
}
_buffer = null;

View File

@@ -102,7 +102,25 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
}
}
public readonly bool IsCreated => _buffer != null && _allocationHandle.pAllocator != null && _memoryHandle.IsValid;
public readonly bool IsCreated
{
get
{
if (_buffer != null)
{
if (_allocationHandle.IsValid != null)
{
return _allocationHandle.IsValid(_allocationHandle.State, _memoryHandle);
}
else
{
return true;
}
}
return false;
}
}
public Enumerator GetEnumerator() => new((UnsafeArray<T>*)UnsafeUtility.AddressOf(ref this));
IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator();
@@ -130,8 +148,13 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
throw new ArgumentOutOfRangeException(nameof(count), "Count can not be less than zero.");
}
if (handle.Alloc == null)
{
throw new InvalidOperationException("Target allocation handle does not support allocation.");
}
MemoryHandle memHandle;
var buff = handle.Alloc(handle.pAllocator, (nuint)(count * sizeof(T)), AlignOf<T>(), allocationOption, &memHandle);
var buff = handle.Alloc(handle.State, (nuint)(count * sizeof(T)), AlignOf<T>(), allocationOption, &memHandle);
_buffer = (T*)buff;
_memoryHandle = memHandle;
@@ -168,6 +191,7 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Conditional("ENABLE_COLLECTION_CHECKS")]
private readonly void ThrowIfNotCreated()
{
if (!IsCreated)
@@ -202,6 +226,11 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
{
ThrowIfNotCreated();
if (_allocationHandle.Realloc == null)
{
throw new InvalidOperationException("Target allocation handle does not support reallocation.");
}
if (newSize == Count)
{
return;
@@ -209,7 +238,7 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
MemoryHandle memHandle = _memoryHandle;
var elemSize = SizeOf<T>();
_buffer = (T*)_allocationHandle.Realloc(_allocationHandle.pAllocator, _buffer, (nuint)Count * elemSize, (nuint)newSize * elemSize, AlignOf<T>(), option, &memHandle);
_buffer = (T*)_allocationHandle.Realloc(_allocationHandle.State, _buffer, (nuint)Count * elemSize, (nuint)newSize * elemSize, AlignOf<T>(), option, &memHandle);
_memoryHandle = memHandle;
_count = newSize;
}
@@ -273,9 +302,9 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
return;
}
if (_allocationHandle.pAllocator != null)
if (_allocationHandle.Free != null)
{
_allocationHandle.Free(_allocationHandle.pAllocator, _buffer, _memoryHandle);
_allocationHandle.Free(_allocationHandle.State, _buffer, _memoryHandle);
}
_buffer = null;

View File

@@ -55,7 +55,6 @@ public unsafe struct UnsafeHashMap<TKey, TValue> : IUnsafeHashCollection<KeyValu
return result;
}
set
{
var idx = _helper.Find(key);
@@ -98,7 +97,7 @@ public unsafe struct UnsafeHashMap<TKey, TValue> : IUnsafeHashCollection<KeyValu
/// <param name="key">The key to add.</param>
/// <param name="item">The value to add.</param>
/// <returns>True if the key-value pair was added.</returns>
public bool TryAdd(TKey key, TValue item)
public bool TryAdd(in TKey key, TValue item)
{
var idx = _helper.TryAdd(key);
if (idx != -1)
@@ -117,7 +116,7 @@ public unsafe struct UnsafeHashMap<TKey, TValue> : IUnsafeHashCollection<KeyValu
/// <param name="key">The key to add.</param>
/// <param name="item">The value to add.</param>
/// <exception cref="ArgumentException">Thrown if the key was already present.</exception>
public void Add(TKey key, TValue item)
public void Add(in TKey key, TValue item)
{
var result = TryAdd(key, item);
if (!result)
@@ -131,7 +130,7 @@ public unsafe struct UnsafeHashMap<TKey, TValue> : IUnsafeHashCollection<KeyValu
/// </summary>
/// <param name="item">The value to remove.</param>
/// <returns>True if the value was present.</returns>
public bool Remove(TKey key)
public bool Remove(in TKey key)
{
return -1 != _helper.TryRemove(key);
}
@@ -142,17 +141,38 @@ public unsafe struct UnsafeHashMap<TKey, TValue> : IUnsafeHashCollection<KeyValu
/// <param name="key">The key to look up.</param>
/// <param name="item">Outputs the value associated with the key. Outputs default if the key was not present.</param>
/// <returns>True if the key was present.</returns>
public bool TryGetValue(TKey key, out TValue item)
public bool TryGetValue(in TKey key, out TValue item)
{
return _helper.TryGetValue(key, out item);
}
/// <summary>
/// Retrieves the value associated with the specified key, or returns a default value if the key is not found.
/// </summary>
/// <param name="key">The key whose value to retrieve.</param>
/// <param name="defaultValue">The value to return if the specified key does not exist. If not specified, the default value for the type is used.</param>
/// <returns>The value associated with the specified key if the key is found; otherwise, the specified default value.</returns>
public TValue GetValueOrDefault(in TKey key, TValue defaultValue = default)
{
if (_helper.TryGetValue<TValue>(key, out var value))
{
return value;
}
return defaultValue;
}
public ref TValue GetValueRef(in TKey key, out bool exists)
{
return ref _helper.GetValueRef<TValue>(key, out exists);
}
/// <summary>
/// Returns true if a given key is present in this hash map.
/// </summary>
/// <param name="key">The key to look up.</param>
/// <returns>True if the key was present.</returns>
public bool ContainsKey(TKey key)
public bool ContainsKey(in TKey key)
{
return -1 != _helper.Find(key);
}

View File

@@ -6,7 +6,7 @@
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<Authors>Misaki</Authors>
<AssemblyVersion>1.3.3</AssemblyVersion>
<AssemblyVersion>1.3.5</AssemblyVersion>
<Version>$(AssemblyVersion)</Version>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<PackageProjectUrl>https://git.personalnas.com/Misaki/Misaki.HighPerformance.git</PackageProjectUrl>
@@ -29,6 +29,10 @@
</ItemGroup>
<ItemGroup>
<None Update="Collections\FixedString.tt">
<Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>FixedString.gen.cs</LastGenOutput>
</None>
<None Update="Collections\FixedText.tt">
<Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>FixedText.gen.cs</LastGenOutput>
@@ -40,6 +44,11 @@
</ItemGroup>
<ItemGroup>
<Compile Update="Collections\FixedString.gen.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>FixedString.tt</DependentUpon>
</Compile>
<Compile Update="Collections\FixedText.gen.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>

View File

@@ -136,7 +136,7 @@ public unsafe struct UniquePtr<T> : IEquatable<UniquePtr<T>>
}
}
public ref struct Ref<T>
public ref struct Ref<T> : IEquatable<Ref<T>>
{
private ref T _value;
@@ -150,6 +150,11 @@ public ref struct Ref<T>
return ref _value;
}
public bool Equals(Ref<T> other)
{
return Unsafe.AreSame(ref _value, ref other._value);
}
[Obsolete("Equals() on Ref will always throw an exception. Use the equality operator instead.")]
#pragma warning disable CS0809 // Obsolete member overrides non-obsolete member
public override bool Equals(object? obj)
@@ -166,7 +171,7 @@ public ref struct Ref<T>
public static bool operator ==(Ref<T> left, Ref<T> right)
{
return Unsafe.AreSame(ref left._value, ref right._value);
return left.Equals(right);
}
public static bool operator !=(Ref<T> left, Ref<T> right)

View File

@@ -1,5 +1,6 @@
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using System.Diagnostics;
using System.Numerics;
using System.Runtime.CompilerServices;
@@ -89,7 +90,25 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
public readonly bool IsEmpty => !IsCreated || _count == 0;
public readonly bool IsCreated => _buffer != null && _allocationHandle.pAllocator != null && _memoryHandle.IsValid;
public readonly bool IsCreated
{
get
{
if (_buffer != null)
{
if (_allocationHandle.IsValid != null)
{
return _allocationHandle.IsValid(_allocationHandle.State, _memoryHandle);
}
else
{
return true;
}
}
return false;
}
}
private static int CalculateDataSize(int capacity, int bucketCapacity, int sizeOfTValue, out int outKeyOffset, out int outNextOffset, out int outBucketOffset)
{
@@ -142,6 +161,7 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Conditional("ENABLE_COLLECTION_CHECKS")]
private readonly void ThrowIfNotCreated()
{
if (!IsCreated)
@@ -191,8 +211,13 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void AllocateBuffer(int totalSize, int keyOffset, int nextOffset, int bucketOffset, AllocationOption allocationOption)
{
if (_allocationHandle.Alloc == null)
{
throw new InvalidOperationException("Target allocation handle does not support allocation.");
}
MemoryHandle memHandle;
var buf = (byte*)_allocationHandle.Alloc(_allocationHandle.pAllocator, (uint)totalSize, (nuint)_alignment, allocationOption, &memHandle);
var buf = (byte*)_allocationHandle.Alloc(_allocationHandle.State, (uint)totalSize, (nuint)_alignment, allocationOption, &memHandle);
_buffer = buf;
_keys = (TKey*)(_buffer + keyOffset);
@@ -228,7 +253,10 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
}
}
_allocationHandle.Free(_allocationHandle.pAllocator, oldBuffer, oldMemoryHandle);
if (_allocationHandle.Free != null)
{
_allocationHandle.Free(_allocationHandle.State, oldBuffer, oldMemoryHandle);
}
}
public void Resize(int newCapacity)
@@ -399,6 +427,21 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
return false;
}
public ref TValue GetValueRef<TValue>(in TKey key, out bool exists)
where TValue : unmanaged
{
ThrowIfNotCreated();
var idx = Find(key);
if (idx != -1)
{
exists = true;
return ref UnsafeUtility.ReadArrayElementRef<TValue>(_buffer, idx);
}
exists = false;
return ref Unsafe.NullRef<TValue>();
}
public bool MoveNextSearch(ref int bucketIndex, ref int nextIndex, out int index)
{
ThrowIfNotCreated();
@@ -522,9 +565,9 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
return;
}
if (_allocationHandle.pAllocator != null)
if (_allocationHandle.Free != null)
{
_allocationHandle.Free(_allocationHandle.pAllocator, _buffer, _memoryHandle);
_allocationHandle.Free(_allocationHandle.State, _buffer, _memoryHandle);
}
_buffer = null;

View File

@@ -1,5 +1,7 @@
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
namespace Misaki.HighPerformance.LowLevel.Utilities;
@@ -21,7 +23,14 @@ public static unsafe partial class MemoryUtility
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void* Malloc(nuint size)
{
return NativeMemory.Alloc(size);
try
{
return NativeMemory.Alloc(size);
}
catch (Exception)
{
return null;
}
}
/// <summary>
@@ -32,7 +41,14 @@ public static unsafe partial class MemoryUtility
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void* Calloc(nuint size)
{
return NativeMemory.AllocZeroed(size);
try
{
return NativeMemory.AllocZeroed(size);
}
catch (Exception)
{
return null;
}
}
/// <summary>
@@ -44,7 +60,14 @@ public static unsafe partial class MemoryUtility
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void* AlignedAlloc(nuint size, nuint alignment)
{
return NativeMemory.AlignedAlloc(size, alignment);
try
{
return NativeMemory.AlignedAlloc(size, alignment);
}
catch (Exception)
{
return null;
}
}
/// <summary>
@@ -56,7 +79,14 @@ public static unsafe partial class MemoryUtility
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void* Realloc(void* ptr, nuint size)
{
return NativeMemory.Realloc(ptr, size);
try
{
return NativeMemory.Realloc(ptr, size);
}
catch (Exception)
{
return null;
}
}
/// <summary>
@@ -70,7 +100,14 @@ public static unsafe partial class MemoryUtility
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void* AlignedRealloc(void* ptr, nuint size, nuint alignment)
{
return NativeMemory.AlignedRealloc(ptr, size, alignment);
try
{
return NativeMemory.AlignedRealloc(ptr, size, alignment);
}
catch (Exception)
{
return null;
}
}
/// <summary>
@@ -130,6 +167,42 @@ public static unsafe partial class MemoryUtility
NativeMemory.Copy(source, destination, size);
}
/// <summary>
/// Moves a block of memory from a source location to a destination location, handling overlapping regions correctly.
/// </summary>
/// <param name="destination">Indicates the memory address where the data will be moved to.</param>
/// <param name="source">Specifies the memory address from which data will be moved.</param>
/// <param name="size">Defines the number of bytes to be moved from the source to the destination.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void MemMove(void* destination, void* source, nuint size)
{
// NativeMemory.Copy use memmove internally.
NativeMemory.Copy(source, destination, size);
}
/// <summary>
/// Compares two blocks of memory byte by byte for a specified length.
/// </summary>
/// <param name="ptr1">A pointer to the first block of memory to compare.</param>
/// <param name="ptr2">A pointer to the second block of memory to compare.</param>
/// <param name="size">The number of bytes to compare. Must not exceed the length of either memory block.</param>
/// <returns>A signed integer that indicates the relative order of the memory blocks: less than zero if the first differing
/// byte in ptr1 is less than the corresponding byte in ptr2; zero if all compared bytes are equal; greater than
/// zero if the first differing byte in ptr1 is greater than the corresponding byte in ptr2.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int MemCmp(void* ptr1, void* ptr2, nuint size)
{
if (ptr1 == ptr2)
{
return 0;
}
var span1 = new ReadOnlySpan<byte>(ptr1, (int)size);
var span2 = new ReadOnlySpan<byte>(ptr2, (int)size);
return span1.SequenceCompareTo(span2);
}
/// <summary>
/// Calculates the size in bytes of a specified unmanaged type.
/// </summary>