Add image processing and memory management features
Added new namespace `Misaki.HighPerformance.Image` for image processing, including classes for animated GIF handling and memory management. Added `AnimatedFrameResult` class for individual frames in animated images. Added `AnimatedGifEnumerator` class for enumerating frames in animated GIFs. Added `ColorComponents` enum for different color formats. Added `ImageInfo` struct for image dimensions and color components. Added `CRuntime` class for low-level memory management functions. Added `MemoryStats` class to track memory allocation statistics. Added utility functions for creating multi-dimensional arrays. Added new structures for fixed-size UTF-8 encoded strings. Added benchmarking classes to test new memory management features. Changed `StbImage.cs` to include new namespaces and functionality for image data manipulation. Changed project files to target .NET 9.0 and enable new features. Changed `Arena.cs` and `DynamicArena.cs` to use `nuint` for size parameters. Changed `BitSet.cs` to enhance bit manipulation methods. Changed `Program.cs` to run `FunctionPtrBenchmark` for performance testing. Removed memory tracking code from `AllocationManager.cs`, including the `_allocated` dictionary and related logic. Removed `Free` method from `IAllocator.cs` interface. Removed `UNSAFE_COLLECTION_CHECK` preprocessor directive from the codebase. Refactored various files to improve organization, moving from `Unsafe` to `LowLevel` namespace. Refactored `MemoryUtilities` class to include new memory operation methods. Refactored `UnsafeUtilities.cs` to support new collection structures.
This commit is contained in:
2
Misaki.HighPerformance.LowLevel/AssemblyInfo.cs
Normal file
2
Misaki.HighPerformance.LowLevel/AssemblyInfo.cs
Normal file
@@ -0,0 +1,2 @@
|
||||
global using static Misaki.HighPerformance.LowLevel.Helpers.MemoryUtilities;
|
||||
global using SystemUnsfae = System.Runtime.CompilerServices.Unsafe;
|
||||
263
Misaki.HighPerformance.LowLevel/Buffer/AllocationManager.cs
Normal file
263
Misaki.HighPerformance.LowLevel/Buffer/AllocationManager.cs
Normal file
@@ -0,0 +1,263 @@
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using Misaki.HighPerformance.LowLevel.Contracts;
|
||||
using Misaki.HighPerformance.LowLevel.Exceptions;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Misaki.HighPerformance.LowLevel.Buffer;
|
||||
|
||||
using unsafe FreeFunc = delegate* unmanaged<void*, void*, void>;
|
||||
|
||||
public unsafe struct ArenaAllocator : IAllocator, IDisposable
|
||||
{
|
||||
private DynamicArena _arena;
|
||||
private AllocationHandle _handle;
|
||||
|
||||
public readonly ref AllocationHandle Handle => ref Unsafe.AsRef(in _handle);
|
||||
|
||||
public ArenaAllocator(uint initialSize)
|
||||
{
|
||||
_arena = new DynamicArena(initialSize);
|
||||
_handle = new AllocationHandle(Unsafe.AsPointer(ref this), &Allocate, &Reallocate, &FreeBlock);
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption)
|
||||
{
|
||||
var selfPtr = (ArenaAllocator*)instance;
|
||||
var ptr = selfPtr->_arena.Allocate(size, alignment, allocationOption);
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
private static void* Reallocate(void* instance, void* ptr, nuint size, nuint alignment)
|
||||
{
|
||||
var selfPtr = (ArenaAllocator*)instance;
|
||||
var newPtr = selfPtr->_arena.Allocate(size, alignment, AllocationOption.None);
|
||||
MemCpy(newPtr, ptr, size);
|
||||
// NOTE: We do not free the old pointer here, as it is managed by the arena.
|
||||
return newPtr;
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
private static void FreeBlock(void* instance, void* ptr)
|
||||
{
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_arena.Reset();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_arena.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe struct DefaultAllocator : IAllocator
|
||||
{
|
||||
private AllocationHandle _handle;
|
||||
|
||||
public ref AllocationHandle Handle => ref Unsafe.AsRef(in _handle);
|
||||
|
||||
public DefaultAllocator()
|
||||
{
|
||||
_handle = new AllocationHandle(Unsafe.AsPointer(ref this), &Allocate, &Reallocate, &FreeBlock);
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption)
|
||||
{
|
||||
var ptr = AlignedAlloc(size, alignment);
|
||||
AllocationManager.TrackAllocation(ptr, size, instance, &FreeBlock);
|
||||
if (allocationOption.HasFlag(AllocationOption.Clear))
|
||||
{
|
||||
MemClear(ptr, size);
|
||||
}
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
private static void* Reallocate(void* instance, void* ptr, nuint size, nuint alignment)
|
||||
{
|
||||
var newPtr = AlignedRealloc(ptr, size, alignment);
|
||||
AllocationManager.UpdateAllocation(ptr, newPtr, size, instance, &FreeBlock);
|
||||
return newPtr;
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
private static void FreeBlock(void* instance, void* ptr)
|
||||
{
|
||||
AlignedFree(ptr);
|
||||
AllocationManager.RemoveAllocation(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe struct EmptyAllocator : IAllocator
|
||||
{
|
||||
private AllocationHandle _handle;
|
||||
|
||||
public ref AllocationHandle Handle => ref Unsafe.AsRef(in _handle);
|
||||
|
||||
public EmptyAllocator()
|
||||
{
|
||||
_handle = new AllocationHandle(Unsafe.AsPointer(ref this), &Allocate, &Reallocate, &FreeBlock);
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
private static void* Reallocate(void* instance, void* ptr, nuint size, nuint alignment)
|
||||
{
|
||||
return ptr;
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
private static void FreeBlock(void* instance, void* ptr)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public static unsafe class AllocationManager
|
||||
{
|
||||
public readonly struct AllocationInfo
|
||||
{
|
||||
public nuint Size
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
|
||||
public void* Allocator
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
|
||||
public FreeFunc FreeHandler
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
|
||||
public StackTrace StackTrace
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
}
|
||||
|
||||
private const uint _DEFAULT_ARENA_SIZE = 512 * 1024;
|
||||
|
||||
private static ArenaAllocator s_arenaAllocator = new(_DEFAULT_ARENA_SIZE);
|
||||
private static DefaultAllocator s_persistentAllocator = new();
|
||||
private static EmptyAllocator s_emptyAllocator = new();
|
||||
|
||||
private static bool s_debugLayer;
|
||||
private static Dictionary<nint, AllocationInfo>? s_allocated;
|
||||
|
||||
public static ArenaAllocator TempAllocator => s_arenaAllocator;
|
||||
public static DefaultAllocator PersistentAllocator => s_persistentAllocator;
|
||||
public static EmptyAllocator EmptyAllocator => s_emptyAllocator;
|
||||
|
||||
public static void EnableDebugLayer()
|
||||
{
|
||||
s_debugLayer = true;
|
||||
s_allocated ??= new Dictionary<nint, AllocationInfo>(64);
|
||||
}
|
||||
|
||||
public static ref AllocationHandle GetAllocationHandle(Allocator allocator)
|
||||
{
|
||||
switch (allocator)
|
||||
{
|
||||
case Allocator.Temp:
|
||||
return ref s_arenaAllocator.Handle;
|
||||
case Allocator.Persistent:
|
||||
return ref s_persistentAllocator.Handle;
|
||||
case Allocator.External:
|
||||
return ref s_emptyAllocator.Handle;
|
||||
default:
|
||||
throw new ArgumentException("Invalid allocator type.", nameof(allocator));
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void TrackAllocation(void* ptr, nuint allocationSize, void* allocator, FreeFunc freeFunc)
|
||||
{
|
||||
if (!s_debugLayer || s_allocated == null || ptr == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
s_allocated[(nint)ptr] = new AllocationInfo
|
||||
{
|
||||
Size = allocationSize,
|
||||
Allocator = allocator,
|
||||
FreeHandler = freeFunc,
|
||||
StackTrace = new StackTrace(true)
|
||||
};
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void UpdateAllocation(void* oldPtr, void* newPtr, nuint allocationSize, void* allocator, FreeFunc freeFunc)
|
||||
{
|
||||
if (!s_debugLayer || s_allocated == null || oldPtr == null || newPtr == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (s_allocated.Remove((nint)oldPtr, out var info))
|
||||
{
|
||||
s_allocated[(nint)newPtr] = new AllocationInfo
|
||||
{
|
||||
Size = allocationSize,
|
||||
Allocator = allocator,
|
||||
FreeHandler = freeFunc,
|
||||
StackTrace = info.StackTrace
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
TrackAllocation(newPtr, allocationSize, allocator, freeFunc);
|
||||
}
|
||||
}
|
||||
|
||||
public static void RemoveAllocation(void* ptr)
|
||||
{
|
||||
if (s_allocated == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
s_allocated.Remove((nint)ptr);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes of the AllocationManager, freeing all allocated memory and resources.
|
||||
/// </summary>
|
||||
public static void Dispose()
|
||||
{
|
||||
s_arenaAllocator.Dispose();
|
||||
|
||||
if (s_allocated != null)
|
||||
{
|
||||
nuint unfreeBytes = 0u;
|
||||
foreach (var pair in s_allocated)
|
||||
{
|
||||
unfreeBytes += pair.Value.Size;
|
||||
pair.Value.FreeHandler(pair.Value.Allocator, (void*)pair.Key);
|
||||
}
|
||||
|
||||
if (unfreeBytes > 0u)
|
||||
{
|
||||
throw new MemoryLeakException([.. s_allocated.Values]);
|
||||
}
|
||||
|
||||
s_allocated.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
92
Misaki.HighPerformance.LowLevel/Buffer/Arena .cs
Normal file
92
Misaki.HighPerformance.LowLevel/Buffer/Arena .cs
Normal file
@@ -0,0 +1,92 @@
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
|
||||
namespace Misaki.HighPerformance.LowLevel.Buffer;
|
||||
|
||||
/// <summary>
|
||||
/// A memory management structure that allocates and resets memory blocks with specified alignment.
|
||||
/// </summary>
|
||||
public unsafe struct Arena : IDisposable
|
||||
{
|
||||
private byte* _buffer;
|
||||
private nuint _size;
|
||||
private nuint _offset;
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
public Arena(nuint size)
|
||||
{
|
||||
Initialize(size);
|
||||
}
|
||||
|
||||
public void Initialize(nuint size)
|
||||
{
|
||||
if (_buffer != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_buffer = (byte*)Malloc(size);
|
||||
_size = size;
|
||||
_offset = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allocates a block of memory of a specified size with a given alignment. Returns a pointer to the allocated
|
||||
/// memory or null if allocation fails.
|
||||
/// You don't need to free the memory manually, it will be freed when the arena is disposed.
|
||||
/// </summary>
|
||||
/// <param name="size">Specifies the amount of memory to allocate in bytes.</param>
|
||||
/// <param name="alignment">Defines the alignment requirement for the allocated memory.</param>
|
||||
/// <param name="allocationOption">The option when allocating memory.</param>
|
||||
/// <returns>A pointer to the allocated memory block or null if the allocation cannot be fulfilled.</returns>
|
||||
/// <exception cref="ObjectDisposedException">Thrown if the arena has been disposed.</exception>
|
||||
public void* Allocate(nuint size, nuint alignment, AllocationOption allocationOption)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(DynamicArena));
|
||||
}
|
||||
|
||||
var offset = _offset + alignment - 1 & ~(alignment - 1);
|
||||
if (offset + size > _size)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
_offset = offset + size;
|
||||
var ptr = _buffer + offset;
|
||||
|
||||
if (allocationOption.HasFlag(AllocationOption.Clear))
|
||||
{
|
||||
MemClear(ptr, size);
|
||||
}
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the arena, optionally clearing the allocated memory.
|
||||
/// </summary>
|
||||
/// <param name="clear">If true, the allocated memory will be cleared; otherwise, it will not be cleared.</param>
|
||||
/// <exception cref="ObjectDisposedException">Thrown if the arena has been disposed.</exception>
|
||||
public void Reset()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(DynamicArena));
|
||||
}
|
||||
|
||||
_offset = 0;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Free(_buffer);
|
||||
|
||||
_buffer = null;
|
||||
_size = 0;
|
||||
_offset = 0;
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
143
Misaki.HighPerformance.LowLevel/Buffer/DynamicArena.cs
Normal file
143
Misaki.HighPerformance.LowLevel/Buffer/DynamicArena.cs
Normal file
@@ -0,0 +1,143 @@
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
|
||||
namespace Misaki.HighPerformance.LowLevel.Buffer;
|
||||
|
||||
/// <summary>
|
||||
/// A dynamic memory management structure that automatically grows by creating linked arenas
|
||||
/// when more space is needed.
|
||||
/// </summary>
|
||||
public unsafe struct DynamicArena : IDisposable
|
||||
{
|
||||
private struct ArenaNode
|
||||
{
|
||||
public Arena arena;
|
||||
public ArenaNode* next;
|
||||
}
|
||||
|
||||
private ArenaNode* _root;
|
||||
private ArenaNode* _current;
|
||||
private uint _initialSize;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of DynamicArena with the specified initial size.
|
||||
/// </summary>
|
||||
/// <param name="initialSize">The initial size in bytes for the first arena block.</param>
|
||||
public DynamicArena(uint initialSize)
|
||||
{
|
||||
_initialSize = initialSize;
|
||||
_root = (ArenaNode*)Malloc(SizeOf<ArenaNode>());
|
||||
_root->arena = new Arena(initialSize);
|
||||
_root->next = null;
|
||||
_current = _root;
|
||||
}
|
||||
|
||||
public void Initialize(uint initialSize)
|
||||
{
|
||||
if (_root != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_initialSize = initialSize;
|
||||
_root = (ArenaNode*)Malloc(SizeOf<ArenaNode>());
|
||||
_root->arena = new Arena(initialSize);
|
||||
_root->next = null;
|
||||
_current = _root;
|
||||
}
|
||||
|
||||
private bool CreateNewNode(nuint size)
|
||||
{
|
||||
var newNode = (ArenaNode*)Malloc(SizeOf<ArenaNode>());
|
||||
try
|
||||
{
|
||||
newNode->arena = new Arena(size);
|
||||
newNode->next = null;
|
||||
|
||||
_current->next = newNode;
|
||||
_current = newNode;
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
Free(newNode);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allocates a block of memory with specified size and alignment. Creates a new arena if current one is full.
|
||||
/// </summary>
|
||||
/// <param name="size">Size of the memory block to allocate in bytes.</param>
|
||||
/// <param name="alignment">Alignment requirement for the memory block.</param>
|
||||
/// <returns>Pointer to the allocated memory block.</returns>
|
||||
/// <exception cref="ObjectDisposedException">Thrown if the arena has been disposed.</exception>
|
||||
public void* Allocate(nuint size, nuint alignment, AllocationOption allocationOption)
|
||||
{
|
||||
if (_root == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
void* result = null;
|
||||
var current = _current;
|
||||
|
||||
while (current != null)
|
||||
{
|
||||
result = current->arena.Allocate(size, alignment, allocationOption);
|
||||
if (result != null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
if (current->next == null && !CreateNewNode(Math.Max(size, _initialSize)))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
current = current->next;
|
||||
}
|
||||
|
||||
_current = current;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets all arenas in the chain, optionally clearing their memory.
|
||||
/// </summary>
|
||||
/// <param name="clear">If true, memory will be cleared during reset.</param>
|
||||
/// <exception cref="ObjectDisposedException">Thrown if the arena has been disposed.</exception>
|
||||
public void Reset()
|
||||
{
|
||||
var current = _root;
|
||||
while (current != null)
|
||||
{
|
||||
current->arena.Reset();
|
||||
current = current->next;
|
||||
}
|
||||
|
||||
_current = _root;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes all arenas and frees associated memory.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_root == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var current = _root;
|
||||
while (current != null)
|
||||
{
|
||||
var next = current->next;
|
||||
current->arena.Dispose();
|
||||
Free(current);
|
||||
current = next;
|
||||
}
|
||||
|
||||
_root = null;
|
||||
_current = null;
|
||||
}
|
||||
}
|
||||
926
Misaki.HighPerformance.LowLevel/Buffer/FixedStackString.cs
Normal file
926
Misaki.HighPerformance.LowLevel/Buffer/FixedStackString.cs
Normal file
@@ -0,0 +1,926 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Misaki.HighPerformance.LowLevel.Buffer;
|
||||
|
||||
/// <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 FixedStackString32
|
||||
{
|
||||
private ushort _length;
|
||||
private fixed byte _buffer[30];
|
||||
|
||||
public ushort Length => _length;
|
||||
public string Value
|
||||
{
|
||||
get
|
||||
{
|
||||
fixed (byte* bufferPtr = _buffer)
|
||||
{
|
||||
return Encoding.UTF8.GetString(bufferPtr, _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 FixedStackString32.");
|
||||
}
|
||||
|
||||
fixed (byte* bufferPtr = _buffer)
|
||||
{
|
||||
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, 30));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public FixedStackString32(ReadOnlySpan<char> input)
|
||||
{
|
||||
var maxBytes = Encoding.UTF8.GetByteCount(input);
|
||||
if (maxBytes > 30)
|
||||
{
|
||||
throw new ArgumentException("Input string is too long to fit in FixedString32.");
|
||||
}
|
||||
|
||||
fixed (char* inputPtr = input)
|
||||
fixed (byte* bufferPtr = _buffer)
|
||||
{
|
||||
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, 30);
|
||||
_length = (ushort)actualByteCount;
|
||||
}
|
||||
}
|
||||
|
||||
public FixedStackString32(string input)
|
||||
: this(input.AsSpan())
|
||||
{
|
||||
}
|
||||
|
||||
public FixedStackString32(char* input, ushort length)
|
||||
: this(new Span<char>(input, length))
|
||||
{
|
||||
}
|
||||
|
||||
public FixedStackString32(ReadOnlySpan<byte> input)
|
||||
{
|
||||
if (input.Length > 30)
|
||||
{
|
||||
throw new ArgumentException("Input byte array is too long to fit in FixedString32.");
|
||||
}
|
||||
|
||||
_length = (ushort)input.Length;
|
||||
|
||||
fixed (byte* inputPtr = input)
|
||||
fixed (byte* bufferPtr = _buffer)
|
||||
{
|
||||
SystemUnsfae.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
|
||||
}
|
||||
}
|
||||
|
||||
public FixedStackString32(byte* input, ushort length)
|
||||
: this(new ReadOnlySpan<byte>(input, length))
|
||||
{
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Span<byte> AsSpan()
|
||||
{
|
||||
fixed (byte* ptr = _buffer)
|
||||
{
|
||||
return new(ptr, _length);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public byte* GetUnsafePointer()
|
||||
{
|
||||
fixed (byte* ptr = _buffer)
|
||||
{
|
||||
return ptr;
|
||||
}
|
||||
}
|
||||
|
||||
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 FixedStackString64
|
||||
{
|
||||
private ushort _length;
|
||||
private fixed byte _buffer[62];
|
||||
|
||||
public ushort Length => _length;
|
||||
public string Value
|
||||
{
|
||||
get
|
||||
{
|
||||
fixed (byte* bufferPtr = _buffer)
|
||||
{
|
||||
return Encoding.UTF8.GetString(bufferPtr, _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 FixedStackString64.");
|
||||
}
|
||||
|
||||
fixed (byte* bufferPtr = _buffer)
|
||||
{
|
||||
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, 62));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public FixedStackString64(ReadOnlySpan<char> input)
|
||||
{
|
||||
var maxBytes = Encoding.UTF8.GetByteCount(input);
|
||||
if (maxBytes > 62)
|
||||
{
|
||||
throw new ArgumentException("Input string is too long to fit in FixedString64.");
|
||||
}
|
||||
|
||||
fixed (char* inputPtr = input)
|
||||
fixed (byte* bufferPtr = _buffer)
|
||||
{
|
||||
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, 62);
|
||||
_length = (ushort)actualByteCount;
|
||||
}
|
||||
}
|
||||
|
||||
public FixedStackString64(string input)
|
||||
: this(input.AsSpan())
|
||||
{
|
||||
}
|
||||
|
||||
public FixedStackString64(char* input, ushort length)
|
||||
: this(new Span<char>(input, length))
|
||||
{
|
||||
}
|
||||
|
||||
public FixedStackString64(ReadOnlySpan<byte> input)
|
||||
{
|
||||
if (input.Length > 62)
|
||||
{
|
||||
throw new ArgumentException("Input byte array is too long to fit in FixedString64.");
|
||||
}
|
||||
|
||||
_length = (ushort)input.Length;
|
||||
|
||||
fixed (byte* inputPtr = input)
|
||||
fixed (byte* bufferPtr = _buffer)
|
||||
{
|
||||
SystemUnsfae.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
|
||||
}
|
||||
}
|
||||
|
||||
public FixedStackString64(byte* input, ushort length)
|
||||
: this(new ReadOnlySpan<byte>(input, length))
|
||||
{
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Span<byte> AsSpan()
|
||||
{
|
||||
fixed (byte* ptr = _buffer)
|
||||
{
|
||||
return new(ptr, _length);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public byte* GetUnsafePointer()
|
||||
{
|
||||
fixed (byte* ptr = _buffer)
|
||||
{
|
||||
return ptr;
|
||||
}
|
||||
}
|
||||
|
||||
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 FixedStackString128
|
||||
{
|
||||
private ushort _length;
|
||||
private fixed byte _buffer[126];
|
||||
|
||||
public ushort Length => _length;
|
||||
public string Value
|
||||
{
|
||||
get
|
||||
{
|
||||
fixed (byte* bufferPtr = _buffer)
|
||||
{
|
||||
return Encoding.UTF8.GetString(bufferPtr, _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 FixedStackString128.");
|
||||
}
|
||||
|
||||
fixed (byte* bufferPtr = _buffer)
|
||||
{
|
||||
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, 126));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public FixedStackString128(ReadOnlySpan<char> input)
|
||||
{
|
||||
var maxBytes = Encoding.UTF8.GetByteCount(input);
|
||||
if (maxBytes > 126)
|
||||
{
|
||||
throw new ArgumentException("Input string is too long to fit in FixedString128.");
|
||||
}
|
||||
|
||||
fixed (char* inputPtr = input)
|
||||
fixed (byte* bufferPtr = _buffer)
|
||||
{
|
||||
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, 126);
|
||||
_length = (ushort)actualByteCount;
|
||||
}
|
||||
}
|
||||
|
||||
public FixedStackString128(string input)
|
||||
: this(input.AsSpan())
|
||||
{
|
||||
}
|
||||
|
||||
public FixedStackString128(char* input, ushort length)
|
||||
: this(new Span<char>(input, length))
|
||||
{
|
||||
}
|
||||
|
||||
public FixedStackString128(ReadOnlySpan<byte> input)
|
||||
{
|
||||
if (input.Length > 126)
|
||||
{
|
||||
throw new ArgumentException("Input byte array is too long to fit in FixedString128.");
|
||||
}
|
||||
|
||||
_length = (ushort)input.Length;
|
||||
|
||||
fixed (byte* inputPtr = input)
|
||||
fixed (byte* bufferPtr = _buffer)
|
||||
{
|
||||
SystemUnsfae.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
|
||||
}
|
||||
}
|
||||
|
||||
public FixedStackString128(byte* input, ushort length)
|
||||
: this(new ReadOnlySpan<byte>(input, length))
|
||||
{
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Span<byte> AsSpan()
|
||||
{
|
||||
fixed (byte* ptr = _buffer)
|
||||
{
|
||||
return new(ptr, _length);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public byte* GetUnsafePointer()
|
||||
{
|
||||
fixed (byte* ptr = _buffer)
|
||||
{
|
||||
return ptr;
|
||||
}
|
||||
}
|
||||
|
||||
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 FixedStackString256
|
||||
{
|
||||
private ushort _length;
|
||||
private fixed byte _buffer[254];
|
||||
|
||||
public ushort Length => _length;
|
||||
public string Value
|
||||
{
|
||||
get
|
||||
{
|
||||
fixed (byte* bufferPtr = _buffer)
|
||||
{
|
||||
return Encoding.UTF8.GetString(bufferPtr, _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 FixedStackString256.");
|
||||
}
|
||||
|
||||
fixed (byte* bufferPtr = _buffer)
|
||||
{
|
||||
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, 254));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public FixedStackString256(ReadOnlySpan<char> input)
|
||||
{
|
||||
var maxBytes = Encoding.UTF8.GetByteCount(input);
|
||||
if (maxBytes > 254)
|
||||
{
|
||||
throw new ArgumentException("Input string is too long to fit in FixedString256.");
|
||||
}
|
||||
|
||||
fixed (char* inputPtr = input)
|
||||
fixed (byte* bufferPtr = _buffer)
|
||||
{
|
||||
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, 254);
|
||||
_length = (ushort)actualByteCount;
|
||||
}
|
||||
}
|
||||
|
||||
public FixedStackString256(string input)
|
||||
: this(input.AsSpan())
|
||||
{
|
||||
}
|
||||
|
||||
public FixedStackString256(char* input, ushort length)
|
||||
: this(new Span<char>(input, length))
|
||||
{
|
||||
}
|
||||
|
||||
public FixedStackString256(ReadOnlySpan<byte> input)
|
||||
{
|
||||
if (input.Length > 254)
|
||||
{
|
||||
throw new ArgumentException("Input byte array is too long to fit in FixedString256.");
|
||||
}
|
||||
|
||||
_length = (ushort)input.Length;
|
||||
|
||||
fixed (byte* inputPtr = input)
|
||||
fixed (byte* bufferPtr = _buffer)
|
||||
{
|
||||
SystemUnsfae.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
|
||||
}
|
||||
}
|
||||
|
||||
public FixedStackString256(byte* input, ushort length)
|
||||
: this(new ReadOnlySpan<byte>(input, length))
|
||||
{
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Span<byte> AsSpan()
|
||||
{
|
||||
fixed (byte* ptr = _buffer)
|
||||
{
|
||||
return new(ptr, _length);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public byte* GetUnsafePointer()
|
||||
{
|
||||
fixed (byte* ptr = _buffer)
|
||||
{
|
||||
return ptr;
|
||||
}
|
||||
}
|
||||
|
||||
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 FixedStackString512
|
||||
{
|
||||
private ushort _length;
|
||||
private fixed byte _buffer[510];
|
||||
|
||||
public ushort Length => _length;
|
||||
public string Value
|
||||
{
|
||||
get
|
||||
{
|
||||
fixed (byte* bufferPtr = _buffer)
|
||||
{
|
||||
return Encoding.UTF8.GetString(bufferPtr, _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 FixedStackString512.");
|
||||
}
|
||||
|
||||
fixed (byte* bufferPtr = _buffer)
|
||||
{
|
||||
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, 510));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public FixedStackString512(ReadOnlySpan<char> input)
|
||||
{
|
||||
var maxBytes = Encoding.UTF8.GetByteCount(input);
|
||||
if (maxBytes > 510)
|
||||
{
|
||||
throw new ArgumentException("Input string is too long to fit in FixedString512.");
|
||||
}
|
||||
|
||||
fixed (char* inputPtr = input)
|
||||
fixed (byte* bufferPtr = _buffer)
|
||||
{
|
||||
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, 510);
|
||||
_length = (ushort)actualByteCount;
|
||||
}
|
||||
}
|
||||
|
||||
public FixedStackString512(string input)
|
||||
: this(input.AsSpan())
|
||||
{
|
||||
}
|
||||
|
||||
public FixedStackString512(char* input, ushort length)
|
||||
: this(new Span<char>(input, length))
|
||||
{
|
||||
}
|
||||
|
||||
public FixedStackString512(ReadOnlySpan<byte> input)
|
||||
{
|
||||
if (input.Length > 510)
|
||||
{
|
||||
throw new ArgumentException("Input byte array is too long to fit in FixedString512.");
|
||||
}
|
||||
|
||||
_length = (ushort)input.Length;
|
||||
|
||||
fixed (byte* inputPtr = input)
|
||||
fixed (byte* bufferPtr = _buffer)
|
||||
{
|
||||
SystemUnsfae.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
|
||||
}
|
||||
}
|
||||
|
||||
public FixedStackString512(byte* input, ushort length)
|
||||
: this(new ReadOnlySpan<byte>(input, length))
|
||||
{
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Span<byte> AsSpan()
|
||||
{
|
||||
fixed (byte* ptr = _buffer)
|
||||
{
|
||||
return new(ptr, _length);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public byte* GetUnsafePointer()
|
||||
{
|
||||
fixed (byte* ptr = _buffer)
|
||||
{
|
||||
return ptr;
|
||||
}
|
||||
}
|
||||
|
||||
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 FixedStackString1024
|
||||
{
|
||||
private ushort _length;
|
||||
private fixed byte _buffer[1022];
|
||||
|
||||
public ushort Length => _length;
|
||||
public string Value
|
||||
{
|
||||
get
|
||||
{
|
||||
fixed (byte* bufferPtr = _buffer)
|
||||
{
|
||||
return Encoding.UTF8.GetString(bufferPtr, _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 FixedStackString1024.");
|
||||
}
|
||||
|
||||
fixed (byte* bufferPtr = _buffer)
|
||||
{
|
||||
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, 1022));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public FixedStackString1024(ReadOnlySpan<char> input)
|
||||
{
|
||||
var maxBytes = Encoding.UTF8.GetByteCount(input);
|
||||
if (maxBytes > 1022)
|
||||
{
|
||||
throw new ArgumentException("Input string is too long to fit in FixedString1024.");
|
||||
}
|
||||
|
||||
fixed (char* inputPtr = input)
|
||||
fixed (byte* bufferPtr = _buffer)
|
||||
{
|
||||
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, 1022);
|
||||
_length = (ushort)actualByteCount;
|
||||
}
|
||||
}
|
||||
|
||||
public FixedStackString1024(string input)
|
||||
: this(input.AsSpan())
|
||||
{
|
||||
}
|
||||
|
||||
public FixedStackString1024(char* input, ushort length)
|
||||
: this(new Span<char>(input, length))
|
||||
{
|
||||
}
|
||||
|
||||
public FixedStackString1024(ReadOnlySpan<byte> input)
|
||||
{
|
||||
if (input.Length > 1022)
|
||||
{
|
||||
throw new ArgumentException("Input byte array is too long to fit in FixedString1024.");
|
||||
}
|
||||
|
||||
_length = (ushort)input.Length;
|
||||
|
||||
fixed (byte* inputPtr = input)
|
||||
fixed (byte* bufferPtr = _buffer)
|
||||
{
|
||||
SystemUnsfae.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
|
||||
}
|
||||
}
|
||||
|
||||
public FixedStackString1024(byte* input, ushort length)
|
||||
: this(new ReadOnlySpan<byte>(input, length))
|
||||
{
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Span<byte> AsSpan()
|
||||
{
|
||||
fixed (byte* ptr = _buffer)
|
||||
{
|
||||
return new(ptr, _length);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public byte* GetUnsafePointer()
|
||||
{
|
||||
fixed (byte* ptr = _buffer)
|
||||
{
|
||||
return ptr;
|
||||
}
|
||||
}
|
||||
|
||||
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 FixedStackString2048
|
||||
{
|
||||
private ushort _length;
|
||||
private fixed byte _buffer[2046];
|
||||
|
||||
public ushort Length => _length;
|
||||
public string Value
|
||||
{
|
||||
get
|
||||
{
|
||||
fixed (byte* bufferPtr = _buffer)
|
||||
{
|
||||
return Encoding.UTF8.GetString(bufferPtr, _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 FixedStackString2048.");
|
||||
}
|
||||
|
||||
fixed (byte* bufferPtr = _buffer)
|
||||
{
|
||||
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, 2046));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public FixedStackString2048(ReadOnlySpan<char> input)
|
||||
{
|
||||
var maxBytes = Encoding.UTF8.GetByteCount(input);
|
||||
if (maxBytes > 2046)
|
||||
{
|
||||
throw new ArgumentException("Input string is too long to fit in FixedString2048.");
|
||||
}
|
||||
|
||||
fixed (char* inputPtr = input)
|
||||
fixed (byte* bufferPtr = _buffer)
|
||||
{
|
||||
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, 2046);
|
||||
_length = (ushort)actualByteCount;
|
||||
}
|
||||
}
|
||||
|
||||
public FixedStackString2048(string input)
|
||||
: this(input.AsSpan())
|
||||
{
|
||||
}
|
||||
|
||||
public FixedStackString2048(char* input, ushort length)
|
||||
: this(new Span<char>(input, length))
|
||||
{
|
||||
}
|
||||
|
||||
public FixedStackString2048(ReadOnlySpan<byte> input)
|
||||
{
|
||||
if (input.Length > 2046)
|
||||
{
|
||||
throw new ArgumentException("Input byte array is too long to fit in FixedString2048.");
|
||||
}
|
||||
|
||||
_length = (ushort)input.Length;
|
||||
|
||||
fixed (byte* inputPtr = input)
|
||||
fixed (byte* bufferPtr = _buffer)
|
||||
{
|
||||
SystemUnsfae.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
|
||||
}
|
||||
}
|
||||
|
||||
public FixedStackString2048(byte* input, ushort length)
|
||||
: this(new ReadOnlySpan<byte>(input, length))
|
||||
{
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Span<byte> AsSpan()
|
||||
{
|
||||
fixed (byte* ptr = _buffer)
|
||||
{
|
||||
return new(ptr, _length);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public byte* GetUnsafePointer()
|
||||
{
|
||||
fixed (byte* ptr = _buffer)
|
||||
{
|
||||
return ptr;
|
||||
}
|
||||
}
|
||||
|
||||
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 FixedStackString4096
|
||||
{
|
||||
private ushort _length;
|
||||
private fixed byte _buffer[4094];
|
||||
|
||||
public ushort Length => _length;
|
||||
public string Value
|
||||
{
|
||||
get
|
||||
{
|
||||
fixed (byte* bufferPtr = _buffer)
|
||||
{
|
||||
return Encoding.UTF8.GetString(bufferPtr, _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 FixedStackString4096.");
|
||||
}
|
||||
|
||||
fixed (byte* bufferPtr = _buffer)
|
||||
{
|
||||
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, 4094));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public FixedStackString4096(ReadOnlySpan<char> input)
|
||||
{
|
||||
var maxBytes = Encoding.UTF8.GetByteCount(input);
|
||||
if (maxBytes > 4094)
|
||||
{
|
||||
throw new ArgumentException("Input string is too long to fit in FixedString4096.");
|
||||
}
|
||||
|
||||
fixed (char* inputPtr = input)
|
||||
fixed (byte* bufferPtr = _buffer)
|
||||
{
|
||||
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, 4094);
|
||||
_length = (ushort)actualByteCount;
|
||||
}
|
||||
}
|
||||
|
||||
public FixedStackString4096(string input)
|
||||
: this(input.AsSpan())
|
||||
{
|
||||
}
|
||||
|
||||
public FixedStackString4096(char* input, ushort length)
|
||||
: this(new Span<char>(input, length))
|
||||
{
|
||||
}
|
||||
|
||||
public FixedStackString4096(ReadOnlySpan<byte> input)
|
||||
{
|
||||
if (input.Length > 4094)
|
||||
{
|
||||
throw new ArgumentException("Input byte array is too long to fit in FixedString4096.");
|
||||
}
|
||||
|
||||
_length = (ushort)input.Length;
|
||||
|
||||
fixed (byte* inputPtr = input)
|
||||
fixed (byte* bufferPtr = _buffer)
|
||||
{
|
||||
SystemUnsfae.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
|
||||
}
|
||||
}
|
||||
|
||||
public FixedStackString4096(byte* input, ushort length)
|
||||
: this(new ReadOnlySpan<byte>(input, length))
|
||||
{
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Span<byte> AsSpan()
|
||||
{
|
||||
fixed (byte* ptr = _buffer)
|
||||
{
|
||||
return new(ptr, _length);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public byte* GetUnsafePointer()
|
||||
{
|
||||
fixed (byte* ptr = _buffer)
|
||||
{
|
||||
return ptr;
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Value;
|
||||
}
|
||||
}
|
||||
|
||||
129
Misaki.HighPerformance.LowLevel/Buffer/FixedStackString.tt
Normal file
129
Misaki.HighPerformance.LowLevel/Buffer/FixedStackString.tt
Normal file
@@ -0,0 +1,129 @@
|
||||
<#@ 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 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 FixedStackString<#= i #>
|
||||
{
|
||||
private ushort _length;
|
||||
private fixed byte _buffer[<#= i - 2 #>];
|
||||
|
||||
public ushort Length => _length;
|
||||
public string Value
|
||||
{
|
||||
get
|
||||
{
|
||||
fixed (byte* bufferPtr = _buffer)
|
||||
{
|
||||
return Encoding.UTF8.GetString(bufferPtr, _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 FixedStackString<#= i #>.");
|
||||
}
|
||||
|
||||
fixed (byte* bufferPtr = _buffer)
|
||||
{
|
||||
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, <#= i - 2 #>));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public FixedStackString<#= 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 #>.");
|
||||
}
|
||||
|
||||
fixed (char* inputPtr = input)
|
||||
fixed (byte* bufferPtr = _buffer)
|
||||
{
|
||||
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, <#= i - 2 #>);
|
||||
_length = (ushort)actualByteCount;
|
||||
}
|
||||
}
|
||||
|
||||
public FixedStackString<#= i #>(string input)
|
||||
: this(input.AsSpan())
|
||||
{
|
||||
}
|
||||
|
||||
public FixedStackString<#= i #>(char* input, ushort length)
|
||||
: this(new Span<char>(input, length))
|
||||
{
|
||||
}
|
||||
|
||||
public FixedStackString<#= i #>(ReadOnlySpan<byte> input)
|
||||
{
|
||||
if (input.Length > <#= i - 2 #>)
|
||||
{
|
||||
throw new ArgumentException("Input byte array is too long to fit in FixedString<#= i #>.");
|
||||
}
|
||||
|
||||
_length = (ushort)input.Length;
|
||||
|
||||
fixed (byte* inputPtr = input)
|
||||
fixed (byte* bufferPtr = _buffer)
|
||||
{
|
||||
SystemUnsfae.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
|
||||
}
|
||||
}
|
||||
|
||||
public FixedStackString<#= i #>(byte* input, ushort length)
|
||||
: this(new ReadOnlySpan<byte>(input, length))
|
||||
{
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Span<byte> AsSpan()
|
||||
{
|
||||
fixed (byte* ptr = _buffer)
|
||||
{
|
||||
return new(ptr, _length);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public byte* GetUnsafePointer()
|
||||
{
|
||||
fixed (byte* ptr = _buffer)
|
||||
{
|
||||
return ptr;
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Value;
|
||||
}
|
||||
}
|
||||
|
||||
<# } #>
|
||||
894
Misaki.HighPerformance.LowLevel/Buffer/FixedString.cs
Normal file
894
Misaki.HighPerformance.LowLevel/Buffer/FixedString.cs
Normal file
@@ -0,0 +1,894 @@
|
||||
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)
|
||||
{
|
||||
SystemUnsfae.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)
|
||||
{
|
||||
SystemUnsfae.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)
|
||||
{
|
||||
SystemUnsfae.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)
|
||||
{
|
||||
SystemUnsfae.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)
|
||||
{
|
||||
SystemUnsfae.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)
|
||||
{
|
||||
SystemUnsfae.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)
|
||||
{
|
||||
SystemUnsfae.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)
|
||||
{
|
||||
SystemUnsfae.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
125
Misaki.HighPerformance.LowLevel/Buffer/FixedString.tt
Normal file
125
Misaki.HighPerformance.LowLevel/Buffer/FixedString.tt
Normal file
@@ -0,0 +1,125 @@
|
||||
<#@ 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)
|
||||
{
|
||||
SystemUnsfae.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
<# } #>
|
||||
19
Misaki.HighPerformance.LowLevel/Buffer/UnsafeArrayPool.cs
Normal file
19
Misaki.HighPerformance.LowLevel/Buffer/UnsafeArrayPool.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
|
||||
namespace Misaki.HighPerformance.LowLevel.Buffer;
|
||||
|
||||
// TODO: Implement a pool for UnsafeArray<T>.
|
||||
public unsafe static class UnsafeArrayPool
|
||||
{
|
||||
public static UnsafeArray<T> Rent<T>(int minimalSize)
|
||||
where T : unmanaged
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public static void Return<T>(UnsafeArray<T> array)
|
||||
where T : unmanaged
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
namespace Misaki.HighPerformance.LowLevel.Collections;
|
||||
|
||||
[Flags]
|
||||
public enum AllocationOption : byte
|
||||
{
|
||||
None = 0,
|
||||
/// <summary>
|
||||
/// Allocator for initialized memory.
|
||||
/// </summary>
|
||||
Clear = 1 << 0,
|
||||
/// <summary>
|
||||
/// Allocator for untracked memory. It always allocates memory without using the allocation manager.
|
||||
/// Always free it manually even if you use the <see cref="Allocator.Temp"/> allocator.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Use this option carefully, as the allocation manager will not track the memory.
|
||||
/// No warning will be given if the memory is not freed.
|
||||
/// </remarks>
|
||||
UnTracked = 1 << 1,
|
||||
}
|
||||
|
||||
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.
|
||||
/// </summary>
|
||||
Temp,
|
||||
/// <summary>
|
||||
/// Allocator for persistent allocations. Allocations are not cleared after use.
|
||||
/// </summary>
|
||||
Persistent,
|
||||
/// <summary>
|
||||
/// Allocator for external memory. Allocations are not cleared after use.
|
||||
/// </summary>
|
||||
External
|
||||
}
|
||||
711
Misaki.HighPerformance.LowLevel/Collections/BitSet.cs
Normal file
711
Misaki.HighPerformance.LowLevel/Collections/BitSet.cs
Normal file
@@ -0,0 +1,711 @@
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Misaki.HighPerformance.LowLevel.Collections;
|
||||
|
||||
public sealed class BitSet
|
||||
{
|
||||
private const int _BIT_SIZE = sizeof(uint) * 8 - 1; // 31
|
||||
private const int _INDEX_SIZE = 5; // log_2(BitSize + 1)
|
||||
private const int _MASK = (1 << 5) - 1; // 0x1F, the mask to get the bit index inside a uint
|
||||
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.
|
||||
/// </summary>
|
||||
/// <param name="id">The id or bit.</param>
|
||||
/// <returns>A size of required <see cref="uint"/>s for the bitset.</returns>
|
||||
public static int RequiredLength(int id)
|
||||
{
|
||||
return (id >> 5) + int.Sign(id & _BIT_SIZE);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rounds the given length to the next padding size.
|
||||
/// </summary>
|
||||
/// <param name="length">The length to round.</param>
|
||||
/// <returns>The rounded length.</returns>
|
||||
public static int RoundToPadding(int length)
|
||||
{
|
||||
return (length + s_padding - 1) / s_padding * s_padding;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The bits from the bitset.
|
||||
/// </summary>
|
||||
private uint[] _bits;
|
||||
|
||||
/// <summary>
|
||||
/// The highest bit set.
|
||||
/// </summary>
|
||||
private int _highestBit;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum <see cref="_bits"/>-index current in use.
|
||||
/// </summary>
|
||||
private int _max;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BitSet" /> class.
|
||||
/// </summary>
|
||||
public BitSet()
|
||||
{
|
||||
_bits = new uint[s_padding];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BitSet" /> class.
|
||||
/// </summary>
|
||||
public BitSet(int minimalLength)
|
||||
{
|
||||
var uints = (minimalLength >> _INDEX_SIZE) + int.Sign(minimalLength & _BIT_SIZE);
|
||||
var length = RoundToPadding(uints);
|
||||
_bits = new uint[length];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BitSet" /> class.
|
||||
/// </summary>
|
||||
public BitSet(params Span<uint> bits)
|
||||
{
|
||||
_bits = bits.ToArray();
|
||||
_highestBit = 0;
|
||||
_max = _bits.Length * (_BIT_SIZE + 1) - 1; // Calculate the maximum index in use
|
||||
for (var i = 0; i < _bits.Length; i++)
|
||||
{
|
||||
if (_bits[i] != 0)
|
||||
{
|
||||
_highestBit = Math.Max(_highestBit, i * (_BIT_SIZE + 1) + BitOperations.Log2(_bits[i]) + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The highest uint index in use inside the <see cref="_bits"/>-array.
|
||||
/// </summary>
|
||||
public int HighestIndex
|
||||
{
|
||||
get => _max;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The highest bit set.
|
||||
/// </summary>
|
||||
public int HighestBit
|
||||
{
|
||||
get => _highestBit;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the length of the bitset, how many ints it consists of.
|
||||
/// </summary>
|
||||
public int Length
|
||||
{
|
||||
get => _bits.Length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return (_bits[b] & 1 << (index & _BIT_SIZE)) != 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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)
|
||||
{
|
||||
Array.Resize(ref _bits, RoundToPadding(b));
|
||||
}
|
||||
|
||||
// Track highest set bit
|
||||
_highestBit = Math.Max(_highestBit, index);
|
||||
_max = _highestBit / (_BIT_SIZE + 1) + 1;
|
||||
_bits[b] |= 1u << (index & _BIT_SIZE);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_bits[b] &= ~(1u << (index & _BIT_SIZE));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets all bits.
|
||||
/// </summary>
|
||||
public void SetAll()
|
||||
{
|
||||
var count = _bits.Length;
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
_bits[i] = 0xffffffff;
|
||||
}
|
||||
|
||||
_highestBit = _bits.Length * (_BIT_SIZE + 1) - 1;
|
||||
_max = _highestBit / (_BIT_SIZE + 1) + 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all set bits.
|
||||
/// </summary>
|
||||
public void ClearAll()
|
||||
{
|
||||
Array.Clear(_bits);
|
||||
_highestBit = 0;
|
||||
_max = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the next set bit at or after `startIndex`, or -1 if none.
|
||||
/// </summary>
|
||||
public int NextSetBit(int startIndex)
|
||||
{
|
||||
var wordIndex = startIndex >> _BIT_SIZE;
|
||||
if (wordIndex >= _bits.Length)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Mask off bits below startIndex in the first word:
|
||||
var word = _bits[wordIndex] & ~0u << (startIndex & _MASK);
|
||||
while (true)
|
||||
{
|
||||
if (word != 0)
|
||||
{
|
||||
// get the least-significant set bit
|
||||
var bit = BitOperations.TrailingZeroCount(word);
|
||||
return (wordIndex << _BIT_SIZE) + bit;
|
||||
}
|
||||
|
||||
wordIndex++;
|
||||
if (wordIndex >= _bits.Length)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
word = _bits[wordIndex];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if all bits from this instance match those of the other instance.
|
||||
/// </summary>
|
||||
/// <param name="other">The other <see cref="BitSet"/>.</param>
|
||||
/// <returns>True if they match, false if not.</returns>
|
||||
[SkipLocalsInit]
|
||||
public bool All(BitSet other)
|
||||
{
|
||||
var min = Math.Min(Math.Min(Length, other.Length), _max);
|
||||
if (!Vector.IsHardwareAccelerated || min < s_padding)
|
||||
{
|
||||
var bits = _bits.AsSpan();
|
||||
var otherBits = other._bits.AsSpan();
|
||||
|
||||
// Bitwise and
|
||||
for (var i = 0; i < min; i++)
|
||||
{
|
||||
var bit = bits[i];
|
||||
if ((bit & otherBits[i]) != bit)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle extra bits on our side that might just be all zero.
|
||||
for (var i = min; i < _max; i++)
|
||||
{
|
||||
if (bits[i] != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Vectorized bitwise and
|
||||
for (var i = 0; i < min; i += s_padding)
|
||||
{
|
||||
var vector = new Vector<uint>(_bits.AsSpan()[i..]);
|
||||
var otherVector = new Vector<uint>(other._bits.AsSpan()[i..]);
|
||||
|
||||
var resultVector = Vector.BitwiseAnd(vector, otherVector);
|
||||
if (!Vector.EqualsAll(resultVector, vector))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle extra bits on our side that might just be all zero.
|
||||
for (var i = min; i < _max; i += s_padding)
|
||||
{
|
||||
var vector = new Vector<uint>(_bits.AsSpan()[i..]);
|
||||
if (!Vector.EqualsAll(vector, Vector<uint>.Zero)) // Vectors are not zero bits[0] != 0 basically
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if any bits from this instance match those of the other instance.
|
||||
/// </summary>
|
||||
/// <param name="other">The other <see cref="BitSet"/>.</param>
|
||||
/// <returns>True if they match, false if not.</returns>
|
||||
public bool Any(BitSet other)
|
||||
{
|
||||
var min = Math.Min(Math.Min(Length, other.Length), _max);
|
||||
if (!Vector.IsHardwareAccelerated || min < s_padding)
|
||||
{
|
||||
var bits = _bits.AsSpan();
|
||||
var otherBits = other._bits.AsSpan();
|
||||
|
||||
// Bitwise and, return true since any is met
|
||||
for (var i = 0; i < min; i++)
|
||||
{
|
||||
var bit = bits[i];
|
||||
if ((bit & otherBits[i]) > 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle extra bits on our side that might just be all zero.
|
||||
for (var i = min; i < _max; i++)
|
||||
{
|
||||
if (bits[i] > 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Vectorized bitwise and, return true since any is met
|
||||
for (var i = 0; i < min; i += s_padding)
|
||||
{
|
||||
var vector = new Vector<uint>(_bits.AsSpan()[i..]);
|
||||
var otherVector = new Vector<uint>(other._bits.AsSpan()[i..]);
|
||||
|
||||
var resultVector = Vector.BitwiseAnd(vector, otherVector);
|
||||
if (!Vector.EqualsAll(resultVector, Vector<uint>.Zero))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle extra bits on our side that might just be all zero.
|
||||
for (var i = min; i < _max; i += s_padding)
|
||||
{
|
||||
var vector = new Vector<uint>(_bits.AsSpan()[i..]);
|
||||
if (!Vector.EqualsAll(vector, Vector<uint>.Zero)) // Vectors are not zero bits[0] != 0 basically
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return _highestBit <= 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if none bits from this instance match those of the other instance.
|
||||
/// </summary>
|
||||
/// <param name="other">The other <see cref="BitSet"/>.</param>
|
||||
/// <returns>True if none match, false if not.</returns>
|
||||
public bool None(BitSet other)
|
||||
{
|
||||
var min = Math.Min(Math.Min(Length, other.Length), _max);
|
||||
if (!Vector.IsHardwareAccelerated || min < s_padding)
|
||||
{
|
||||
var bits = _bits.AsSpan();
|
||||
var otherBits = other._bits.AsSpan();
|
||||
|
||||
// Bitwise and, return true since any is met
|
||||
for (var i = 0; i < min; i++)
|
||||
{
|
||||
var bit = bits[i];
|
||||
if ((bit & otherBits[i]) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Vectorized bitwise and, return true since any is met
|
||||
for (var i = 0; i < min; i += s_padding)
|
||||
{
|
||||
var vector = new Vector<uint>(_bits.AsSpan()[i..]);
|
||||
var otherVector = new Vector<uint>(other._bits.AsSpan()[i..]);
|
||||
|
||||
var resultVector = Vector.BitwiseAnd(vector, otherVector);
|
||||
if (!Vector.EqualsAll(resultVector, Vector<uint>.Zero))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if exactly all bits from this instance match those of the other instance.
|
||||
/// </summary>
|
||||
/// <param name="other">The other <see cref="BitSet"/>.</param>
|
||||
/// <returns>True if they match, false if not.</returns>
|
||||
public bool Exclusive(BitSet other)
|
||||
{
|
||||
var min = Math.Min(Math.Min(Length, other.Length), _max);
|
||||
|
||||
if (!Vector.IsHardwareAccelerated || min < s_padding)
|
||||
{
|
||||
var bits = _bits.AsSpan();
|
||||
var otherBits = other._bits.AsSpan();
|
||||
|
||||
// Bitwise xor, if both are not totally equal, return false
|
||||
for (var i = 0; i < min; i++)
|
||||
{
|
||||
var bit = bits[i];
|
||||
if ((bit ^ otherBits[i]) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// handle extra bits on our side that might just be all zero
|
||||
for (var i = min; i < _max; i++)
|
||||
{
|
||||
if (bits[i] != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Vectorized bitwise xor, return true since any is met
|
||||
for (var i = 0; i < min; i += s_padding)
|
||||
{
|
||||
var vector = new Vector<uint>(_bits.AsSpan()[i..]);
|
||||
var otherVector = new Vector<uint>(other._bits.AsSpan()[i..]);
|
||||
|
||||
var resultVector = Vector.Xor(vector, otherVector);
|
||||
if (!Vector.EqualsAll(resultVector, Vector<uint>.Zero))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle extra bits on our side that might just be all zero.
|
||||
for (var i = min; i < _max; i += s_padding)
|
||||
{
|
||||
var vector = new Vector<uint>(_bits.AsSpan()[i..]);
|
||||
if (!Vector.EqualsAll(vector, Vector<uint>.Zero)) // Vectors are not zero bits[0] != 0 basically
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static BitSet operator &(BitSet left, BitSet right)
|
||||
{
|
||||
var min = Math.Min(left.Length, right.Length);
|
||||
var result = new BitSet(min);
|
||||
if (!Vector.IsHardwareAccelerated || min < s_padding)
|
||||
{
|
||||
for (var i = 0; i < min; i++)
|
||||
{
|
||||
result._bits[i] = left._bits[i] & right._bits[i];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var i = 0; i < min; i += s_padding)
|
||||
{
|
||||
var vectorLeft = new Vector<uint>(left._bits.AsSpan()[i..]);
|
||||
var vectorRight = new Vector<uint>(right._bits.AsSpan()[i..]);
|
||||
var resultVector = Vector.BitwiseAnd(vectorLeft, vectorRight);
|
||||
resultVector.CopyTo(result._bits.AsSpan(i, s_padding));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static BitSet operator |(BitSet left, BitSet right)
|
||||
{
|
||||
var min = Math.Min(left.Length, right.Length);
|
||||
var result = new BitSet(min);
|
||||
if (!Vector.IsHardwareAccelerated || min < s_padding)
|
||||
{
|
||||
for (var i = 0; i < min; i++)
|
||||
{
|
||||
result._bits[i] = left._bits[i] | right._bits[i];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var i = 0; i < min; i += s_padding)
|
||||
{
|
||||
var vectorLeft = new Vector<uint>(left._bits.AsSpan()[i..]);
|
||||
var vectorRight = new Vector<uint>(right._bits.AsSpan()[i..]);
|
||||
var resultVector = Vector.BitwiseOr(vectorLeft, vectorRight);
|
||||
resultVector.CopyTo(result._bits.AsSpan(i, s_padding));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static BitSet operator ~(BitSet bitSet)
|
||||
{
|
||||
if (!Vector.IsHardwareAccelerated || bitSet.Length < s_padding)
|
||||
{
|
||||
for (var i = 0; i < bitSet.Length; i++)
|
||||
{
|
||||
bitSet._bits[i] = ~bitSet._bits[i];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var i = 0; i < bitSet.Length; i += s_padding)
|
||||
{
|
||||
var vector = new Vector<uint>(bitSet._bits.AsSpan()[i..]);
|
||||
var resultVector = ~vector;
|
||||
resultVector.CopyTo(bitSet._bits.AsSpan(i, s_padding));
|
||||
}
|
||||
}
|
||||
|
||||
return bitSet;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="Span{T}"/> to access the <see cref="_bits"/>.
|
||||
/// </summary>
|
||||
/// <returns>The hash.</returns>
|
||||
public 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"/>.
|
||||
/// </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>
|
||||
/// <returns>The <see cref="Span{T}"/>.</returns>
|
||||
public Span<uint> AsSpan(Span<uint> span, bool zero = true)
|
||||
{
|
||||
// Copy everything thats possible from one to another
|
||||
var length = Math.Min(Length, span.Length);
|
||||
for (var index = 0; index < length; index++)
|
||||
{
|
||||
span[index] = _bits[index];
|
||||
}
|
||||
|
||||
// Zero the rest space which was not overriden due to the copy.
|
||||
for (var index = length; zero && index < span.Length; index++)
|
||||
{
|
||||
span[index] = 0;
|
||||
}
|
||||
|
||||
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
|
||||
var binaryBuilder = new StringBuilder();
|
||||
foreach (var bit in _bits)
|
||||
{
|
||||
binaryBuilder.Append(Convert.ToString(bit, 2).PadLeft(32, '0')).Append(',');
|
||||
}
|
||||
binaryBuilder.Length--;
|
||||
|
||||
return $"{nameof(_bits)}: {binaryBuilder}, {nameof(Length)}: {Length}";
|
||||
}
|
||||
}
|
||||
|
||||
/// <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.
|
||||
/// </summary>
|
||||
public readonly ref struct SpanBitSet
|
||||
{
|
||||
private const int _BIT_SIZE = sizeof(uint) * 8 - 1; // 31
|
||||
// NOTE: Is a byte not 8 bits?
|
||||
private const int _BYTE_SIZE = 5; // log_2(BitSize + 1)
|
||||
|
||||
/// <summary>
|
||||
/// The bits from the bitset.
|
||||
/// </summary>
|
||||
private readonly Span<uint> _bits;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BitSet" /> class.
|
||||
/// </summary>
|
||||
public SpanBitSet(Span<uint> bits)
|
||||
{
|
||||
_bits = bits;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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 >> _BYTE_SIZE;
|
||||
if (b >= _bits.Length)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return (_bits[b] & 1 << (index & _BIT_SIZE)) != 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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 >> _BYTE_SIZE;
|
||||
if (b >= _bits.Length)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_bits[b] |= 1u << (index & _BIT_SIZE);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the bit at the given index.
|
||||
/// </summary>
|
||||
/// <param name="index">The index.</param>
|
||||
|
||||
public void ClearBit(int index)
|
||||
{
|
||||
var b = index >> _BYTE_SIZE;
|
||||
if (b >= _bits.Length)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_bits[b] &= ~(1u << (index & _BIT_SIZE));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
|
||||
public void SetAll()
|
||||
{
|
||||
var count = _bits.Length;
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
_bits[i] = 0xffffffff;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all set bits.
|
||||
/// </summary>
|
||||
|
||||
public void ClearAll()
|
||||
{
|
||||
_bits.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="Span{T}"/> to access the <see cref="_bits"/>.
|
||||
/// </summary>
|
||||
/// <returns>The hash.</returns>
|
||||
|
||||
public Span<uint> AsSpan()
|
||||
{
|
||||
return _bits;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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>
|
||||
|
||||
public Span<uint> AsSpan(Span<uint> span, bool zero = true)
|
||||
{
|
||||
// Prevent exception because target array is to small for copy operation
|
||||
var length = Math.Min(_bits.Length, span.Length);
|
||||
for (var index = 0; index < length; index++)
|
||||
{
|
||||
span[index] = _bits[index];
|
||||
}
|
||||
|
||||
// Zero the rest space which was not overriden due to the copy.
|
||||
for (var index = length; zero && index < span.Length; index++)
|
||||
{
|
||||
span[index] = 0;
|
||||
}
|
||||
|
||||
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
|
||||
var binaryBuilder = new StringBuilder();
|
||||
foreach (var bit in _bits)
|
||||
{
|
||||
binaryBuilder.Append(Convert.ToString(bit, 2).PadLeft(32, '0')).Append(',');
|
||||
}
|
||||
binaryBuilder.Length--;
|
||||
|
||||
return $"{nameof(_bits)}: {string.Join(",", binaryBuilder)}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
namespace Misaki.HighPerformance.LowLevel.Collections.Contracts;
|
||||
|
||||
public unsafe interface IUnsafeCollection<T> : IEnumerable<T>, IDisposable
|
||||
where T : unmanaged
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the number of elements in a collection. The value is read-only.
|
||||
/// </summary>
|
||||
public int Count
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the object has been created. Returns true if the object is created, otherwise false.
|
||||
/// </summary>
|
||||
public bool IsCreated
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all elements from the collection. The collection will be empty after this operation.
|
||||
/// </summary>
|
||||
public void Clear();
|
||||
|
||||
/// <summary>
|
||||
/// Changes the size of a collection or array to the specified value.
|
||||
/// </summary>
|
||||
/// <param name="newSize">Specifies the new size to which the collection or array should be adjusted.</param>
|
||||
public void Resize(int newSize);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a pointer to an unmanaged memory location. This pointer can be used for low-level memory operations.
|
||||
/// </summary>
|
||||
/// <returns>The method returns a void pointer to the unsafe memory location.</returns>
|
||||
public void* GetUnsafePtr();
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
namespace Misaki.HighPerformance.LowLevel.Collections.Contracts;
|
||||
internal class IUnsafeSet<T>
|
||||
where T : unmanaged, IEquatable<T>
|
||||
{
|
||||
}
|
||||
186
Misaki.HighPerformance.LowLevel/Collections/UnsafeArray.cs
Normal file
186
Misaki.HighPerformance.LowLevel/Collections/UnsafeArray.cs
Normal file
@@ -0,0 +1,186 @@
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
|
||||
using Misaki.HighPerformance.LowLevel.Contracts;
|
||||
using Misaki.HighPerformance.LowLevel.Helpers;
|
||||
using System.Collections;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Misaki.HighPerformance.LowLevel.Collections;
|
||||
|
||||
/// <summary>
|
||||
/// A structure for managing an array of unmanaged types with unsafe memory operations.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Represents a type that can be stored in an unmanaged memory context.</typeparam>
|
||||
public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
|
||||
where T : unmanaged
|
||||
{
|
||||
public struct Enumerator : IEnumerator<T>
|
||||
{
|
||||
private UnsafeArray<T>* _collection;
|
||||
private int _index;
|
||||
private T _value;
|
||||
|
||||
public Enumerator(UnsafeArray<T>* collection)
|
||||
{
|
||||
_collection = collection;
|
||||
_index = -1;
|
||||
_value = default;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool MoveNext()
|
||||
{
|
||||
_index++;
|
||||
if (_index < _collection->_count)
|
||||
{
|
||||
_value = UnsafeUtilities.ReadArrayElement<T>(_collection->_buffer, _index);
|
||||
return true;
|
||||
}
|
||||
|
||||
_value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_index = -1;
|
||||
}
|
||||
|
||||
// Let NativeArray indexer check for out of range.
|
||||
public readonly T Current
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _value;
|
||||
}
|
||||
|
||||
readonly object IEnumerator.Current
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => Current;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private T* _buffer;
|
||||
private int _count;
|
||||
|
||||
private AllocationHandle* _handle;
|
||||
|
||||
public readonly int Count => _count;
|
||||
|
||||
public readonly ref T this[int index]
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
if (index < 0 || index >= _count)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(index), "Index is out of range.");
|
||||
}
|
||||
|
||||
return ref _buffer[index];
|
||||
}
|
||||
}
|
||||
|
||||
public readonly bool IsCreated
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _buffer != null;
|
||||
}
|
||||
|
||||
public IEnumerator<T> GetEnumerator() => new Enumerator((UnsafeArray<T>*)UnsafeUtilities.AddressOf(ref this));
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
/// <summary>
|
||||
/// Constructs an UnsafeArray with a default size of 1 and uses the Persistent allocator.
|
||||
/// </summary>
|
||||
public UnsafeArray()
|
||||
: this(1, Allocator.Persistent)
|
||||
{
|
||||
}
|
||||
|
||||
public UnsafeArray(int count, ref AllocationHandle handle, AllocationOption allocationOption = AllocationOption.None)
|
||||
{
|
||||
if (count <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(count), "Count must be greater than zero.");
|
||||
}
|
||||
|
||||
_handle = (AllocationHandle*)Unsafe.AsPointer(ref handle);
|
||||
_buffer = (T*)handle.Alloc(_handle->Allocator, (uint)count * (uint)sizeof(T), (uint)AlignOf<T>(), allocationOption);
|
||||
_count = count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of UnsafeArray with a specified number of elements and an allocation type.
|
||||
/// </summary>
|
||||
/// <param name="count">Specifies the number of elements to allocate in the array, which must be greater than zero.</param>
|
||||
/// <param name="allocator">Specifies the allocator to use for memory allocation, which determines the memory management strategy.</param>
|
||||
/// <param name="allocationOption">Determines how the memory should be allocated.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when the specified number of elements is less than or equal to zero.</exception>
|
||||
public UnsafeArray(int count, Allocator allocator, AllocationOption allocationOption = AllocationOption.None)
|
||||
: this(count, ref AllocationManager.GetAllocationHandle(allocator), allocationOption)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes an UnsafeArray with a pointer to a buffer and a count of elements. This does not copy the data.
|
||||
/// </summary>
|
||||
/// <param name="buffer">A pointer to the memory location that holds the elements of the array.</param>
|
||||
/// <param name="count">The total size of the data.</param>
|
||||
/// <remarks>
|
||||
/// When using this constructor, the user is responsible for managing the memory pointed to by the buffer.
|
||||
/// Disposing of the UnsafeArray does not free the memory and only release the reference. The memory should be freed manually when no longer needed.
|
||||
/// Use <see cref="UnsafeArray(int, Allocator, AllocationOption)"/> constructor and <see cref="MemCpy(void*, void*, nuint)"/> if you are not sure what you are doing.
|
||||
/// </remarks>
|
||||
public UnsafeArray(void* buffer, int count)
|
||||
{
|
||||
_buffer = (T*)buffer;
|
||||
_count = count;
|
||||
_handle = (AllocationHandle*)Unsafe.AsPointer(ref AllocationManager.EmptyAllocator.Handle);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Resize(int newSize)
|
||||
{
|
||||
if (newSize == _count)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_buffer = (T*)_handle->Realloc(_handle->Allocator, _buffer, (uint)newSize, (uint)AlignOf<T>());
|
||||
_count = newSize;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly void Clear()
|
||||
{
|
||||
MemClear(_buffer, (nuint)(_count * sizeof(T)));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly void* GetUnsafePtr()
|
||||
{
|
||||
return _buffer;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
if (!IsCreated)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_handle->Free(_handle->Allocator, _buffer);
|
||||
|
||||
_handle = null;
|
||||
_buffer = null;
|
||||
_count = 0;
|
||||
}
|
||||
}
|
||||
223
Misaki.HighPerformance.LowLevel/Collections/UnsafeHashMap.cs
Normal file
223
Misaki.HighPerformance.LowLevel/Collections/UnsafeHashMap.cs
Normal file
@@ -0,0 +1,223 @@
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
|
||||
using Misaki.HighPerformance.LowLevel.Contracts;
|
||||
using Misaki.HighPerformance.LowLevel.Helpers;
|
||||
using System.Collections;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Misaki.HighPerformance.LowLevel.Collections;
|
||||
|
||||
public unsafe struct UnsafeHashMap<TKey, TValue> : IUnsafeCollection<KeyValuePair<TKey, TValue>>
|
||||
where TKey : unmanaged, IEquatable<TKey> where TValue : unmanaged
|
||||
{
|
||||
public struct Enumerator : IEnumerator<KeyValuePair<TKey, TValue>>
|
||||
{
|
||||
internal HashMapHelper<TKey>.Enumerator _enumerator;
|
||||
|
||||
public Enumerator(HashMapHelper<TKey>* data)
|
||||
{
|
||||
_enumerator = new HashMapHelper<TKey>.Enumerator(data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The current key-value pair.
|
||||
/// </summary>
|
||||
/// <value>The current key-value pair.</value>
|
||||
public KeyValuePair<TKey, TValue> Current
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _enumerator.GetCurrent<TValue>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the element at the current position of the enumerator in the container.
|
||||
/// </summary>
|
||||
object IEnumerator.Current => Current;
|
||||
|
||||
/// <summary>
|
||||
/// Advances the enumerator to the next key-value pair.
|
||||
/// </summary>
|
||||
/// <returns>True if <see cref="Current"/> is valid to read after the call.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool MoveNext() => _enumerator.MoveNext();
|
||||
|
||||
/// <summary>
|
||||
/// Resets the enumerator to its initial state.
|
||||
/// </summary>
|
||||
public void Reset() => _enumerator.Reset();
|
||||
|
||||
/// <summary>
|
||||
/// Does nothing.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private HashMapHelper<TKey> _hashMap;
|
||||
|
||||
public readonly int Count => _hashMap.Count;
|
||||
public readonly int Capacity => _hashMap.Capacity;
|
||||
public readonly bool IsCreated => _hashMap.IsCreated;
|
||||
|
||||
/// <summary>
|
||||
/// Gets and sets values by key.
|
||||
/// </summary>
|
||||
/// <remarks>Getting a key that is not present will throw. Setting a key that is not already present will add the key.</remarks>
|
||||
/// <param name="key">The key to look up.</param>
|
||||
/// <value>The value associated with the key.</value>
|
||||
/// <exception cref="ArgumentException">For getting, thrown if the key was not present.</exception>
|
||||
public TValue this[TKey key]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!_hashMap.TryGetValue<TValue>(key, out var result))
|
||||
{
|
||||
throw new ArgumentException($"Key: {key} is not present.");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
var idx = _hashMap.Find(key);
|
||||
if (-1 != idx)
|
||||
{
|
||||
UnsafeUtilities.WriteArrayElement(_hashMap.Buffer, idx, value);
|
||||
return;
|
||||
}
|
||||
|
||||
TryAdd(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() => new Enumerator((HashMapHelper<TKey>*)UnsafeUtilities.AddressOf(ref _hashMap));
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public UnsafeHashMap(int capacity, Allocator allocator, AllocationOption allocationOption = AllocationOption.None)
|
||||
: this(capacity, ref AllocationManager.GetAllocationHandle(allocator), allocationOption)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new key-value pair.
|
||||
/// </summary>
|
||||
/// <remarks>If the key is already present, this method returns false without modifying the hash map.</remarks>
|
||||
/// <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)
|
||||
{
|
||||
var idx = _hashMap.TryAdd(key);
|
||||
if (idx != -1)
|
||||
{
|
||||
UnsafeUtilities.WriteArrayElement(_hashMap.Buffer, idx, item);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new key-value pair.
|
||||
/// </summary>
|
||||
/// <remarks>If the key is already present, this method throws without modifying the hash map.</remarks>
|
||||
/// <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)
|
||||
{
|
||||
var result = TryAdd(key, item);
|
||||
if (!result)
|
||||
{
|
||||
throw new ArgumentException($"An item with the same key has already been added: {key}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a particular key and its value.
|
||||
/// </summary>
|
||||
/// <param name="item">The value to remove.</param>
|
||||
/// <returns>True if the value was present.</returns>
|
||||
public bool Remove(TKey key)
|
||||
{
|
||||
return -1 != _hashMap.TryRemove(key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the value associated with a key.
|
||||
/// </summary>
|
||||
/// <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)
|
||||
{
|
||||
return _hashMap.TryGetValue(key, out item);
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
return -1 != _hashMap.Find(key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the capacity to match what it would be if it had been originally initialized with all its entries.
|
||||
/// </summary>
|
||||
public void TrimExcess() => _hashMap.TrimExcess();
|
||||
|
||||
public void Resize(int newSize)
|
||||
{
|
||||
_hashMap.Resize(newSize);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_hashMap.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves an array of keys from the hash map.
|
||||
/// </summary>
|
||||
/// <returns>An array containing the keys stored in the hash map.</returns>
|
||||
public UnsafeArray<TKey> GetKeyArray(Allocator allocator) => _hashMap.GetKeyArray(allocator);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves an array of values from the underlying hash map.
|
||||
/// </summary>
|
||||
/// <returns>An UnsafeArray containing the values stored in the hash map.</returns>
|
||||
public UnsafeArray<TValue> GetValueArray(Allocator allocator) => _hashMap.GetValueArray<TValue>(allocator);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves an array of key-value pairs from the hash map. The keys are of type TKey and the values are of type
|
||||
/// TValue.
|
||||
/// </summary>
|
||||
/// <returns>Returns an UnsafeArray containing KeyValuePair objects.</returns>
|
||||
public UnsafeArray<KeyValuePair<TKey, TValue>> GetKeyValueArrays(Allocator allocator) => _hashMap.GetKeyValueArrays<TValue>(allocator);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly void* GetUnsafePtr()
|
||||
{
|
||||
return _hashMap.Buffer;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_hashMap.Dispose();
|
||||
}
|
||||
|
||||
public void Test(ref HashMapHelper<TKey> t)
|
||||
{
|
||||
Console.WriteLine(t.Equals(_hashMap));
|
||||
}
|
||||
}
|
||||
129
Misaki.HighPerformance.LowLevel/Collections/UnsafeHashSet.cs
Normal file
129
Misaki.HighPerformance.LowLevel/Collections/UnsafeHashSet.cs
Normal file
@@ -0,0 +1,129 @@
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
|
||||
using Misaki.HighPerformance.LowLevel.Contracts;
|
||||
using Misaki.HighPerformance.LowLevel.Helpers;
|
||||
using System.Collections;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Misaki.HighPerformance.LowLevel.Collections;
|
||||
|
||||
/// <summary>
|
||||
/// A collection that provides fast, unsafe operations for managing a set of unmanaged types. It supports adding,
|
||||
/// removing, and checking for values.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Represents an unmanaged type that can be compared for equality.</typeparam>
|
||||
public unsafe struct UnsafeHashSet<T> : IUnsafeCollection<T>, IEnumerable<T>
|
||||
where T : unmanaged, IEquatable<T>
|
||||
{
|
||||
public struct Enumerator : IEnumerator<T>
|
||||
{
|
||||
internal HashMapHelper<T>.Enumerator _enumerator;
|
||||
|
||||
public Enumerator(HashMapHelper<T>* hashMap)
|
||||
{
|
||||
_enumerator = new HashMapHelper<T>.Enumerator(hashMap);
|
||||
}
|
||||
|
||||
public T Current
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _enumerator.buffer->_keys[_enumerator.index];
|
||||
}
|
||||
|
||||
object IEnumerator.Current => Current;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool MoveNext() => _enumerator.MoveNext();
|
||||
|
||||
public void Reset() => _enumerator.Reset();
|
||||
|
||||
public readonly void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private HashMapHelper<T> _hashMap;
|
||||
|
||||
public readonly int Count => _hashMap.Count;
|
||||
public readonly int Capacity => _hashMap.Capacity;
|
||||
public readonly bool IsCreated => _hashMap.IsCreated;
|
||||
|
||||
public IEnumerator<T> GetEnumerator() => new Enumerator((HashMapHelper<T>*)UnsafeUtilities.AddressOf(ref _hashMap));
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
public UnsafeHashSet(int capacity, ref AllocationHandle handle, AllocationOption allocationOption = AllocationOption.None)
|
||||
{
|
||||
_hashMap = new HashMapHelper<T>(capacity, 0, HashMapHelper<T>.MINIMAL_CAPACITY, ref handle, allocationOption);
|
||||
}
|
||||
|
||||
public UnsafeHashSet(int capacity, Allocator allocator, AllocationOption allocationOption = AllocationOption.None)
|
||||
: this(capacity, ref AllocationManager.GetAllocationHandle(allocator), allocationOption)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new value (unless it is already present).
|
||||
/// </summary>
|
||||
/// <param name="item">The value to add.</param>
|
||||
/// <returns>True if the value was not already present.</returns>
|
||||
public bool Add(T item)
|
||||
{
|
||||
return -1 != _hashMap.TryAdd(item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a particular value.
|
||||
/// </summary>
|
||||
/// <param name="item">The value to remove.</param>
|
||||
/// <returns>True if the value was present.</returns>
|
||||
public bool Remove(T item)
|
||||
{
|
||||
return -1 != _hashMap.TryRemove(item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if a particular value is present.
|
||||
/// </summary>
|
||||
/// <param name="item">The value to check for.</param>
|
||||
/// <returns>True if the value was present.</returns>
|
||||
public bool Contains(T item)
|
||||
{
|
||||
return -1 != _hashMap.Find(item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the capacity to match what it would be if it had been originally initialized with all its entries.
|
||||
/// </summary>
|
||||
public void TrimExcess() => _hashMap.TrimExcess();
|
||||
|
||||
/// <summary>
|
||||
/// Returns an array with a copy of this set's values (in no particular order).
|
||||
/// </summary>
|
||||
/// <param name="allocator">The allocator to use.</param>
|
||||
/// <returns>An array with a copy of the set's values.</returns>
|
||||
public UnsafeArray<T> ToNativeArray(Allocator allocator)
|
||||
{
|
||||
return _hashMap.GetKeyArray(allocator);
|
||||
}
|
||||
|
||||
public void Resize(int newSize)
|
||||
{
|
||||
_hashMap.Resize(newSize);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_hashMap.Clear();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly void* GetUnsafePtr()
|
||||
{
|
||||
return _hashMap.Buffer;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_hashMap.Dispose();
|
||||
}
|
||||
}
|
||||
321
Misaki.HighPerformance.LowLevel/Collections/UnsafeList.cs
Normal file
321
Misaki.HighPerformance.LowLevel/Collections/UnsafeList.cs
Normal file
@@ -0,0 +1,321 @@
|
||||
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
|
||||
using Misaki.HighPerformance.LowLevel.Helpers;
|
||||
using System.Collections;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Misaki.HighPerformance.LowLevel.Collections;
|
||||
|
||||
/// <summary>
|
||||
/// A collection that allows for unsafe operations on a list of unmanaged types.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Represents a type that can be stored in the collection, constrained to unmanaged types for performance and safety.</typeparam>
|
||||
public unsafe struct UnsafeList<T> : IUnsafeCollection<T>
|
||||
where T : unmanaged
|
||||
{
|
||||
public struct Enumerator : IEnumerator<T>
|
||||
{
|
||||
private UnsafeList<T>* _collection;
|
||||
private int _index;
|
||||
private T _value;
|
||||
|
||||
public Enumerator(UnsafeList<T>* collection)
|
||||
{
|
||||
_collection = collection;
|
||||
_index = -1;
|
||||
_value = default;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool MoveNext()
|
||||
{
|
||||
_index++;
|
||||
if (_index < _collection->_count)
|
||||
{
|
||||
_value = UnsafeUtilities.ReadArrayElement<T>(_collection->_array.GetUnsafePtr(), _index);
|
||||
return true;
|
||||
}
|
||||
|
||||
_value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_index = -1;
|
||||
}
|
||||
|
||||
// Let NativeArray indexer check for out of range.
|
||||
public readonly T Current
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
return _value;
|
||||
}
|
||||
}
|
||||
|
||||
readonly object IEnumerator.Current
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
return Current;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A parallel writer for an UnsafeList.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Use <see cref="AsParallelWriter"/> to create a parallel writer for a list.
|
||||
/// </remarks>
|
||||
public unsafe struct ParallelWriter
|
||||
{
|
||||
/// <summary>
|
||||
/// The UnsafeList to write to.
|
||||
/// </summary>
|
||||
public UnsafeList<T>* listData;
|
||||
|
||||
internal unsafe ParallelWriter(UnsafeList<T>* list)
|
||||
{
|
||||
listData = list;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a value to a collection without resizing it, ensuring capacity is checked before insertion.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to be added to the collection.</param>
|
||||
public void AddNoResize(T value)
|
||||
{
|
||||
var idx = Interlocked.Increment(ref listData->_count) - 1;
|
||||
listData->CheckNoResizeCapacity(idx, 1);
|
||||
UnsafeUtilities.WriteArrayElement(listData->_array.GetUnsafePtr(), idx, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a specified number of elements from a pointer to a buffer without resizing the underlying storage.
|
||||
/// </summary>
|
||||
/// <param name="ptr">Points to the source data to be copied into the buffer.</param>
|
||||
/// <param name="count">Indicates the number of elements to be added from the source data.</param>
|
||||
public void AddRangeNoResize(T* ptr, int count)
|
||||
{
|
||||
var idx = Interlocked.Add(ref listData->_count, count) - count;
|
||||
listData->CheckNoResizeCapacity(idx, count);
|
||||
MemCpy(UnsafeUtilities.ReadArrayElementUnsafe<T>(listData->_array.GetUnsafePtr(), idx), ptr, (uint)(count * sizeof(T)));
|
||||
}
|
||||
}
|
||||
|
||||
private UnsafeArray<T> _array;
|
||||
|
||||
private int _count;
|
||||
|
||||
public readonly int Count => _count;
|
||||
public readonly int Capacity => _array.Count;
|
||||
public readonly bool IsCreated => _array.IsCreated;
|
||||
|
||||
public readonly ref T this[int index]
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => ref _array[index];
|
||||
}
|
||||
|
||||
public IEnumerator<T> GetEnumerator() => new Enumerator((UnsafeList<T>*)UnsafeUtilities.AddressOf(ref this));
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
public ParallelWriter AsParallelWriter() => new((UnsafeList<T>*)UnsafeUtilities.AddressOf(ref this));
|
||||
|
||||
public UnsafeList() : this(1, Allocator.Persistent)
|
||||
{
|
||||
}
|
||||
|
||||
public UnsafeList(int capacity, Allocator allocator, AllocationOption allocationType = AllocationOption.None)
|
||||
{
|
||||
_array = new UnsafeArray<T>(capacity, allocator, allocationType);
|
||||
}
|
||||
|
||||
private readonly void CheckNoResizeCapacity(int count)
|
||||
{
|
||||
CheckNoResizeCapacity(count, Count);
|
||||
}
|
||||
|
||||
private readonly void CheckNoResizeCapacity(int index, int count)
|
||||
{
|
||||
if (index + count > Capacity)
|
||||
{
|
||||
throw new Exception(
|
||||
$"AddNoResize assumes that list capacity is sufficient (Capacity {Capacity}, Size {Count}), requested count {count}!"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly void CheckIndexCount(int index, int count)
|
||||
{
|
||||
if (count < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException($"Value for count {count} must be positive.");
|
||||
}
|
||||
|
||||
if (index < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException($"Value for index {index} must be positive.");
|
||||
}
|
||||
|
||||
if (index > Count)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException($"Value for index {index} is out of bounds.");
|
||||
}
|
||||
|
||||
if (index + count > Count)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException($"Value for count {count} is out of bounds.");
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(T value)
|
||||
{
|
||||
if (_count >= Capacity)
|
||||
{
|
||||
Resize(Capacity + (int)(Capacity * 0.5f));
|
||||
}
|
||||
|
||||
UnsafeUtilities.WriteArrayElement(_array.GetUnsafePtr(), _count, value);
|
||||
_count++;
|
||||
}
|
||||
|
||||
public void AddNoResize(T value)
|
||||
{
|
||||
CheckNoResizeCapacity(1);
|
||||
|
||||
UnsafeUtilities.WriteArrayElement(_array.GetUnsafePtr(), _count, value);
|
||||
_count++;
|
||||
}
|
||||
|
||||
public void AddRange(Span<T> values, int count)
|
||||
{
|
||||
var newSize = _count + count;
|
||||
if (newSize > Capacity)
|
||||
{
|
||||
Resize(Capacity + count);
|
||||
}
|
||||
|
||||
fixed (T* ptr = values)
|
||||
{
|
||||
MemCpy(UnsafeUtilities.ReadArrayElementUnsafe<T>(_array.GetUnsafePtr(), _count), ptr, (uint)(count * sizeof(T)));
|
||||
}
|
||||
|
||||
_count += count;
|
||||
}
|
||||
|
||||
public void AddRangeNoResize(ReadOnlySpan<T> values)
|
||||
{
|
||||
CheckNoResizeCapacity(values.Length);
|
||||
|
||||
fixed (T* ptr = values)
|
||||
{
|
||||
MemCpy(UnsafeUtilities.ReadArrayElementUnsafe<T>(_array.GetUnsafePtr(), _count), ptr, (uint)(values.Length * sizeof(T)));
|
||||
}
|
||||
|
||||
_count += values.Length;
|
||||
}
|
||||
|
||||
public void AddRangeNoResize(T* ptr, int count)
|
||||
{
|
||||
CheckNoResizeCapacity(count);
|
||||
|
||||
MemCpy(UnsafeUtilities.ReadArrayElementUnsafe<T>(_array.GetUnsafePtr(), _count), ptr, (uint)(count * sizeof(T)));
|
||||
_count += count;
|
||||
}
|
||||
|
||||
public void RemoveRange(int start, int length)
|
||||
{
|
||||
CheckIndexCount(start, length);
|
||||
|
||||
if (length <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var copyFrom = Math.Min(start + length, _count);
|
||||
MemCpy(UnsafeUtilities.ReadArrayElementUnsafe<T>(_array.GetUnsafePtr(), start),
|
||||
UnsafeUtilities.ReadArrayElementUnsafe<T>(_array.GetUnsafePtr(), copyFrom),
|
||||
(uint)((_count - copyFrom) * sizeof(T))
|
||||
);
|
||||
_count -= length;
|
||||
}
|
||||
|
||||
public void RemoveAt(int index)
|
||||
{
|
||||
RemoveRange(index, 1);
|
||||
}
|
||||
|
||||
public void RemoveRangeSwapBack(int start, int length)
|
||||
{
|
||||
CheckIndexCount(start, length);
|
||||
|
||||
if (length <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var copyFrom = Math.Min(_count - length, start + length);
|
||||
MemCpy(UnsafeUtilities.ReadArrayElementUnsafe<T>(_array.GetUnsafePtr(), start),
|
||||
UnsafeUtilities.ReadArrayElementUnsafe<T>(_array.GetUnsafePtr(), copyFrom),
|
||||
(uint)((_count - copyFrom) * sizeof(T))
|
||||
);
|
||||
_count -= length;
|
||||
}
|
||||
|
||||
public void RemoveAtSwapBack(int index)
|
||||
{
|
||||
RemoveRangeSwapBack(index, 1);
|
||||
}
|
||||
|
||||
public void Resize(int newSize)
|
||||
{
|
||||
_array.Resize(newSize);
|
||||
|
||||
if (_count > newSize)
|
||||
{
|
||||
_count = newSize;
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_array.Clear();
|
||||
_count = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a pointer to the underlying data of the array in an unsafe manner. This method is optimized for
|
||||
/// performance.
|
||||
/// </summary>
|
||||
/// <returns>A pointer to the array's data.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly void* GetUnsafePtr()
|
||||
{
|
||||
return _array.GetUnsafePtr();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the current array to an UnsafeArray representation using its pointer and count.
|
||||
/// </summary>
|
||||
/// <returns>Returns a new UnsafeArray instance initialized with the array's unsafe pointer and its count.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly UnsafeArray<T> AsUnsafeArray()
|
||||
{
|
||||
return new UnsafeArray<T>(_array.GetUnsafePtr(), _count);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_array.Dispose();
|
||||
_count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
174
Misaki.HighPerformance.LowLevel/Collections/UnsafeQueue.cs
Normal file
174
Misaki.HighPerformance.LowLevel/Collections/UnsafeQueue.cs
Normal file
@@ -0,0 +1,174 @@
|
||||
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
|
||||
using Misaki.HighPerformance.LowLevel.Helpers;
|
||||
using System.Collections;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Misaki.HighPerformance.LowLevel.Collections;
|
||||
|
||||
/// <summary>
|
||||
/// A structure that implements a queue using unmanaged types for efficient memory management.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Represents the type of elements stored in the queue, which must be an unmanaged type for performance and safety.</typeparam>
|
||||
public unsafe struct UnsafeQueue<T> : IUnsafeCollection<T>
|
||||
where T : unmanaged
|
||||
{
|
||||
public struct Enumerator : IEnumerator<T>
|
||||
{
|
||||
private UnsafeQueue<T>* _collection;
|
||||
private int _index;
|
||||
private T _value;
|
||||
|
||||
public Enumerator(UnsafeQueue<T>* collection)
|
||||
{
|
||||
_collection = collection;
|
||||
_index = -1;
|
||||
_value = default;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool MoveNext()
|
||||
{
|
||||
_index++;
|
||||
if (_index < _collection->_count)
|
||||
{
|
||||
_value = UnsafeUtilities.ReadArrayElement<T>(_collection->_array.GetUnsafePtr(), _index);
|
||||
return true;
|
||||
}
|
||||
|
||||
_value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_index = -1;
|
||||
}
|
||||
|
||||
// Let NativeArray indexer check for out of range.
|
||||
public readonly T Current
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _value;
|
||||
}
|
||||
|
||||
readonly object IEnumerator.Current
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => Current;
|
||||
}
|
||||
|
||||
public readonly void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private UnsafeArray<T> _array;
|
||||
private int _count;
|
||||
private int _offset;
|
||||
|
||||
public readonly int Count => _count;
|
||||
public readonly int Capacity => _array.Count;
|
||||
public readonly bool IsCreated => _array.IsCreated;
|
||||
|
||||
public readonly T this[int index]
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _array[index];
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
set => _array[index] = value;
|
||||
}
|
||||
|
||||
public IEnumerator<T> GetEnumerator() => new Enumerator((UnsafeQueue<T>*)UnsafeUtilities.AddressOf(ref this));
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
public UnsafeQueue() : this(1, Allocator.Persistent)
|
||||
{
|
||||
}
|
||||
|
||||
public UnsafeQueue(int capacity, Allocator allocator, AllocationOption allocationType = AllocationOption.None)
|
||||
{
|
||||
_array = new UnsafeArray<T>(capacity, allocator, allocationType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an element to the end of a collection, resizing if the current capacity is reached. The new element is
|
||||
/// stored in a circular buffer.
|
||||
/// </summary>
|
||||
/// <param name="value">The item to be added to the collection.</param>
|
||||
public void Enqueue(T value)
|
||||
{
|
||||
if (_count >= Capacity)
|
||||
{
|
||||
Resize(Capacity + (int)(Capacity * 0.5f));
|
||||
}
|
||||
|
||||
UnsafeUtilities.WriteArrayElement(_array.GetUnsafePtr(), (_offset + _count) % Capacity, value);
|
||||
_count++;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes and returns the element at the front of the queue. If the queue is empty, an exception is thrown.
|
||||
/// </summary>
|
||||
/// <returns>The element that was removed from the front of the queue.</returns>
|
||||
/// <exception cref="InvalidOperationException">Thrown when attempting to dequeue from an empty queue.</exception>
|
||||
public T Dequeue()
|
||||
{
|
||||
if (_count == 0)
|
||||
{
|
||||
throw new InvalidOperationException("Queue is empty.");
|
||||
}
|
||||
|
||||
var value = UnsafeUtilities.ReadArrayElement<T>(_array.GetUnsafePtr(), _offset);
|
||||
_offset = (_offset + 1) % Capacity;
|
||||
_count--;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to remove and return an item from a collection. Returns a boolean indicating success or failure.
|
||||
/// </summary>
|
||||
/// <param name="value">The output variable that will hold the dequeued item if the operation is successful.</param>
|
||||
/// <returns>True if an item was successfully dequeued, otherwise false.</returns>
|
||||
public bool TryDequeue(out T value)
|
||||
{
|
||||
if (_count == 0)
|
||||
{
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
value = Dequeue();
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Resize(int newSize)
|
||||
{
|
||||
_array.Resize(newSize);
|
||||
|
||||
if (_count > newSize)
|
||||
{
|
||||
_count = newSize;
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_array.Clear();
|
||||
_count = 0;
|
||||
_offset = 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly void* GetUnsafePtr()
|
||||
{
|
||||
return _array.GetUnsafePtr();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_array.Dispose();
|
||||
_count = 0;
|
||||
_offset = 0;
|
||||
}
|
||||
}
|
||||
107
Misaki.HighPerformance.LowLevel/Collections/UnsafeStack.cs
Normal file
107
Misaki.HighPerformance.LowLevel/Collections/UnsafeStack.cs
Normal file
@@ -0,0 +1,107 @@
|
||||
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
|
||||
using Misaki.HighPerformance.LowLevel.Helpers;
|
||||
using System.Collections;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Misaki.HighPerformance.LowLevel.Collections;
|
||||
|
||||
public unsafe struct UnsafeStack<T> : IUnsafeCollection<T>
|
||||
where T : unmanaged
|
||||
{
|
||||
private UnsafeArray<T> _array;
|
||||
private int _count;
|
||||
|
||||
public readonly int Count => _count;
|
||||
public readonly bool IsCreated => _array.IsCreated;
|
||||
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
public UnsafeStack() : this(1, Allocator.Persistent)
|
||||
{
|
||||
}
|
||||
|
||||
public UnsafeStack(int initialSize, Allocator allocator, AllocationOption allocationOption = AllocationOption.None)
|
||||
{
|
||||
_array = new UnsafeArray<T>(initialSize, allocator, allocationOption);
|
||||
}
|
||||
|
||||
public void Push(T value)
|
||||
{
|
||||
if (_count >= _array.Count)
|
||||
{
|
||||
Resize(_array.Count + (int)(_array.Count * 0.5f));
|
||||
}
|
||||
|
||||
UnsafeUtilities.WriteArrayElement(_array.GetUnsafePtr(), _count, value);
|
||||
_count++;
|
||||
}
|
||||
|
||||
public T Pop()
|
||||
{
|
||||
if (_count == 0)
|
||||
{
|
||||
throw new InvalidOperationException("Stack is empty.");
|
||||
}
|
||||
|
||||
_count--;
|
||||
return _array[_count];
|
||||
}
|
||||
|
||||
public bool TryPop(out T value)
|
||||
{
|
||||
if (_count == 0)
|
||||
{
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
_count--;
|
||||
value = _array[_count];
|
||||
return true;
|
||||
}
|
||||
|
||||
public readonly T Peek()
|
||||
{
|
||||
if (_count == 0)
|
||||
{
|
||||
throw new InvalidOperationException("Stack is empty.");
|
||||
}
|
||||
|
||||
return _array[_count - 1];
|
||||
}
|
||||
|
||||
public void Resize(int newSize)
|
||||
{
|
||||
_array.Resize(newSize);
|
||||
|
||||
if (_count > newSize)
|
||||
{
|
||||
_count = newSize;
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_array.Clear();
|
||||
_count = 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly void* GetUnsafePtr()
|
||||
{
|
||||
return _array.GetUnsafePtr();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_array.Dispose();
|
||||
_count = 0;
|
||||
}
|
||||
}
|
||||
52
Misaki.HighPerformance.LowLevel/Contracts/IAllocator.cs
Normal file
52
Misaki.HighPerformance.LowLevel/Contracts/IAllocator.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
|
||||
namespace Misaki.HighPerformance.LowLevel.Contracts;
|
||||
|
||||
using unsafe AllocFunc = delegate* unmanaged<void*, nuint, nuint, AllocationOption, void*>;
|
||||
using unsafe FreeFunc = delegate* unmanaged<void*, void*, void>;
|
||||
using unsafe ReallocFunc = delegate* unmanaged<void*, void*, nuint, nuint, void*>;
|
||||
|
||||
public unsafe readonly struct AllocationHandle
|
||||
{
|
||||
public void* Allocator
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public AllocFunc Alloc
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public ReallocFunc Realloc
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public FreeFunc Free
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public AllocationHandle(void* allocator, AllocFunc alloc, ReallocFunc realloc, FreeFunc free)
|
||||
{
|
||||
Allocator = allocator;
|
||||
Alloc = alloc;
|
||||
Realloc = realloc;
|
||||
Free = free;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents an allocator interface for managing memory allocations.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The allocator must be static or pined to a specific memory region.
|
||||
/// </remarks>
|
||||
public unsafe interface IAllocator
|
||||
{
|
||||
public ref AllocationHandle Handle
|
||||
{
|
||||
get;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
|
||||
namespace Misaki.HighPerformance.LowLevel.Exceptions;
|
||||
|
||||
|
||||
public class MemoryLeakException(params AllocationManager.AllocationInfo[] Infos) : Exception
|
||||
{
|
||||
private static string GetMessage(StackTrace? stackTrace)
|
||||
{
|
||||
if (stackTrace == null)
|
||||
{
|
||||
return "No stack trace available.";
|
||||
}
|
||||
|
||||
var stringBuilder = new StringBuilder();
|
||||
stringBuilder.AppendLine("Memory leak detected at: ");
|
||||
|
||||
for (var i = 0; i < stackTrace.FrameCount; i++)
|
||||
{
|
||||
var frame = stackTrace.GetFrame(i);
|
||||
if (frame != null)
|
||||
{
|
||||
stringBuilder.AppendLine($"File: {frame.GetFileName()}, Method: {DiagnosticMethodInfo.Create(frame)?.Name}, Line: {frame.GetFileLineNumber()}");
|
||||
}
|
||||
}
|
||||
|
||||
return stringBuilder.ToString();
|
||||
}
|
||||
|
||||
public override string Message
|
||||
{
|
||||
get
|
||||
{
|
||||
var stringBuilder = new StringBuilder();
|
||||
stringBuilder.AppendLine($"Found {Infos.Length} memory lakes!");
|
||||
foreach (var info in Infos)
|
||||
{
|
||||
stringBuilder.AppendLine(GetMessage(info.StackTrace));
|
||||
}
|
||||
|
||||
return stringBuilder.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
34
Misaki.HighPerformance.LowLevel/FunctionPointer.cs
Normal file
34
Misaki.HighPerformance.LowLevel/FunctionPointer.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Misaki.HighPerformance.LowLevel;
|
||||
|
||||
public readonly struct FunctionPointer<T>
|
||||
where T : Delegate
|
||||
{
|
||||
private readonly nint _ptr;
|
||||
private readonly T _delegate;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the native function pointer associated with this function pointer instance.
|
||||
/// </summary>
|
||||
public readonly nint Pointer => _ptr;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the delegate instance associated with the specified function pointer.
|
||||
/// </summary>
|
||||
/// <remarks>This property uses <see
|
||||
/// cref="Marshal.GetDelegateForFunctionPointer{TDelegate}"/> to convert the function
|
||||
/// pointer to a delegate. Ensure that the function pointer is valid and compatible with the delegate type
|
||||
/// <typeparamref name="T"/>.</remarks>
|
||||
public T Invoke => _delegate;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of this function pointer with the following native pointer.
|
||||
/// </summary>
|
||||
/// <param name="ptr"></param>
|
||||
public FunctionPointer(nint ptr)
|
||||
{
|
||||
_ptr = ptr;
|
||||
_delegate = Marshal.GetDelegateForFunctionPointer<T>(ptr);
|
||||
}
|
||||
}
|
||||
499
Misaki.HighPerformance.LowLevel/Helpers/HashMapHelper.cs
Normal file
499
Misaki.HighPerformance.LowLevel/Helpers/HashMapHelper.cs
Normal file
@@ -0,0 +1,499 @@
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using Misaki.HighPerformance.LowLevel.Contracts;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Misaki.HighPerformance.LowLevel.Helpers;
|
||||
|
||||
public unsafe struct HashMapHelper<TKey> : IDisposable
|
||||
where TKey : unmanaged, IEquatable<TKey>
|
||||
{
|
||||
internal unsafe struct Enumerator
|
||||
{
|
||||
public HashMapHelper<TKey>* buffer;
|
||||
public int index;
|
||||
public int bucketIndex;
|
||||
public int nextIndex;
|
||||
|
||||
public unsafe Enumerator(HashMapHelper<TKey>* data)
|
||||
{
|
||||
buffer = data;
|
||||
index = -1;
|
||||
bucketIndex = 0;
|
||||
nextIndex = -1;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool MoveNext()
|
||||
{
|
||||
return buffer->MoveNext(ref bucketIndex, ref nextIndex, out index);
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
index = -1;
|
||||
bucketIndex = 0;
|
||||
nextIndex = -1;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public KeyValuePair<TKey, TValue> GetCurrent<TValue>()
|
||||
where TValue : unmanaged
|
||||
{
|
||||
return new KeyValuePair<TKey, TValue>(buffer->_keys[index], UnsafeUtilities.ReadArrayElement<TValue>(buffer->_buffer, index));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public TKey GetCurrentKey()
|
||||
{
|
||||
if (index != -1)
|
||||
{
|
||||
return buffer->_keys[index];
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
// This buffer has 4 parts: TValue, TKey, Next, Buckets.
|
||||
private byte* _buffer;
|
||||
|
||||
internal TKey* _keys;
|
||||
internal int* _next;
|
||||
internal int* _buckets;
|
||||
|
||||
private int _count;
|
||||
private int _capacity;
|
||||
private int _bucketCapacity;
|
||||
private int _allocatedIndex;
|
||||
private int _firstFreeIndex;
|
||||
|
||||
private readonly int _sizeOfTValue;
|
||||
private readonly int _log2MinGrowth;
|
||||
|
||||
private AllocationHandle* _handle;
|
||||
|
||||
public const int MINIMAL_CAPACITY = 64;
|
||||
|
||||
public readonly byte* Buffer
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _buffer;
|
||||
}
|
||||
|
||||
public readonly int Count
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _count;
|
||||
}
|
||||
|
||||
public readonly int Capacity
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _capacity;
|
||||
}
|
||||
|
||||
public readonly bool IsCreated
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _buffer != null;
|
||||
}
|
||||
|
||||
public readonly bool IsEmpty
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => !IsCreated || _count == 0;
|
||||
}
|
||||
|
||||
private static int CalculateDataSize(int capacity, int bucketCapacity, int sizeOfTValue, out int outKeyOffset, out int outNextOffset, out int outBucketOffset)
|
||||
{
|
||||
var sizeOfTKey = sizeof(TKey);
|
||||
var sizeOfInt = sizeof(int);
|
||||
|
||||
var valuesSize = sizeOfTValue * capacity;
|
||||
var keysSize = sizeOfTKey * capacity;
|
||||
var nextSize = sizeOfInt * capacity;
|
||||
var bucketSize = sizeOfInt * bucketCapacity;
|
||||
var totalSize = valuesSize + keysSize + nextSize + bucketSize;
|
||||
|
||||
outKeyOffset = 0 + valuesSize;
|
||||
outNextOffset = outKeyOffset + keysSize;
|
||||
outBucketOffset = outNextOffset + nextSize;
|
||||
|
||||
return totalSize;
|
||||
}
|
||||
|
||||
public HashMapHelper(int capacity, int sizeOfTValue, uint minGrowth, ref AllocationHandle handle, AllocationOption allocationOption)
|
||||
{
|
||||
if (capacity <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(capacity), "Capacity must be greater than zero.");
|
||||
}
|
||||
|
||||
if (sizeOfTValue <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(sizeOfTValue), "Size of TValue must be greater than zero.");
|
||||
}
|
||||
|
||||
_capacity = CalcCapacityCeilPow2(capacity);
|
||||
_bucketCapacity = _capacity * 2;
|
||||
|
||||
_sizeOfTValue = sizeOfTValue;
|
||||
_log2MinGrowth = BitOperations.Log2(minGrowth);
|
||||
|
||||
_handle = (AllocationHandle*)Unsafe.AsPointer(ref handle);
|
||||
|
||||
var totalSize = CalculateDataSize(_capacity, _bucketCapacity, sizeOfTValue,
|
||||
out var keyOffset, out var nextOffset, out var bucketOffset);
|
||||
|
||||
AllocateBuffer(totalSize, keyOffset, nextOffset, bucketOffset, allocationOption);
|
||||
Clear();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private readonly int CalcCapacityCeilPow2(int capacity)
|
||||
{
|
||||
capacity = Math.Max(Math.Max(1, _count), capacity);
|
||||
var newCapacity = Math.Max(capacity, 1 << _log2MinGrowth);
|
||||
var result = MathUtilities.CeilPow2(newCapacity);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private readonly int GetBucket(in TKey key)
|
||||
{
|
||||
var h = (uint)key.GetHashCode();
|
||||
return (int)(h & (uint)(_bucketCapacity - 1));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private readonly void CheckIndexOutOfBounds(int idx)
|
||||
{
|
||||
if ((uint)idx >= (uint)_capacity)
|
||||
{
|
||||
throw new InvalidOperationException($"Index {idx} is out of bounds for the hash map with capacity {_capacity}.");
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void AllocateBuffer(int totalSize, int keyOffset, int nextOffset, int bucketOffset, AllocationOption allocationOption)
|
||||
{
|
||||
var alignSize = sizeof(TKey) > sizeof(int) ? AlignOf<TKey>() : AlignOf<int>();
|
||||
|
||||
_buffer = (byte*)_handle->Alloc(_handle->Allocator, (uint)totalSize, (uint)alignSize, allocationOption);
|
||||
_keys = (TKey*)(_buffer + keyOffset);
|
||||
_next = (int*)(_buffer + nextOffset);
|
||||
_buckets = (int*)(_buffer + bucketOffset);
|
||||
}
|
||||
|
||||
internal void ResizeExact(int newCapacity, int newBucketCapacity)
|
||||
{
|
||||
var totalSize = CalculateDataSize(newCapacity, newBucketCapacity, _sizeOfTValue,
|
||||
out var keyOffset, out var nextOffset, out var bucketOffset);
|
||||
|
||||
var oldBuffer = _buffer;
|
||||
var oldKeys = _keys;
|
||||
var oldNext = _next;
|
||||
var oldBuckets = _buckets;
|
||||
var oldBucketCapacity = _bucketCapacity;
|
||||
|
||||
AllocateBuffer(totalSize, keyOffset, nextOffset, bucketOffset, AllocationOption.None);
|
||||
_capacity = newCapacity;
|
||||
_bucketCapacity = newBucketCapacity;
|
||||
|
||||
Clear();
|
||||
|
||||
for (int i = 0, num = oldBucketCapacity; i < num; ++i)
|
||||
{
|
||||
for (var idx = oldBuckets[i]; idx != -1; idx = oldNext[idx])
|
||||
{
|
||||
var newIdx = TryAdd(oldKeys[idx]);
|
||||
MemCpy(_buffer + _sizeOfTValue * newIdx, oldBuffer + _sizeOfTValue * idx, (nuint)_sizeOfTValue);
|
||||
}
|
||||
}
|
||||
|
||||
_handle->Free(_handle->Allocator, oldBuffer);
|
||||
}
|
||||
|
||||
internal void Resize(int newCapacity)
|
||||
{
|
||||
newCapacity = Math.Max(newCapacity, _count);
|
||||
var newBucketCapacity = MathUtilities.CeilPow2(newCapacity * 2);
|
||||
|
||||
if (_capacity == newCapacity && _bucketCapacity == newBucketCapacity)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ResizeExact(newCapacity, newBucketCapacity);
|
||||
}
|
||||
|
||||
public void TrimExcess()
|
||||
{
|
||||
var capacity = CalcCapacityCeilPow2(_count);
|
||||
ResizeExact(capacity, capacity * 2);
|
||||
}
|
||||
|
||||
public int Find(in TKey key)
|
||||
{
|
||||
if (_allocatedIndex <= 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
// First find the slot based on the hash
|
||||
var bucket = GetBucket(key);
|
||||
var entryIdx = _buckets[bucket];
|
||||
|
||||
if ((uint)entryIdx < (uint)_capacity)
|
||||
{
|
||||
var nextPtrs = _next;
|
||||
while (!UnsafeUtilities.ReadArrayElement<TKey>(_keys, entryIdx).Equals(key))
|
||||
{
|
||||
entryIdx = nextPtrs[entryIdx];
|
||||
if ((uint)entryIdx >= (uint)_capacity)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return entryIdx;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public int TryAdd(in TKey key)
|
||||
{
|
||||
var k = key;
|
||||
if (Find(in key) != -1)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Allocate an entry from the free list
|
||||
int idx;
|
||||
int* next;
|
||||
|
||||
if (_allocatedIndex >= _capacity && _firstFreeIndex < 0)
|
||||
{
|
||||
var newCap = CalcCapacityCeilPow2(_capacity + (1 << _log2MinGrowth));
|
||||
Resize(newCap);
|
||||
}
|
||||
|
||||
idx = _firstFreeIndex;
|
||||
|
||||
if (idx >= 0)
|
||||
{
|
||||
_firstFreeIndex = _next[idx];
|
||||
}
|
||||
else
|
||||
{
|
||||
idx = _allocatedIndex++;
|
||||
}
|
||||
|
||||
CheckIndexOutOfBounds(idx);
|
||||
|
||||
UnsafeUtilities.WriteArrayElement(_keys, idx, key);
|
||||
var bucket = GetBucket(key);
|
||||
|
||||
// Add the index to the hash-map
|
||||
next = _next;
|
||||
next[idx] = _buckets[bucket];
|
||||
_buckets[bucket] = idx;
|
||||
_count++;
|
||||
|
||||
return idx;
|
||||
}
|
||||
|
||||
public int TryRemove(in TKey key)
|
||||
{
|
||||
if (_capacity == 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
var removed = 0;
|
||||
|
||||
// First find the slot based on the hash
|
||||
var bucket = GetBucket(key);
|
||||
|
||||
var prevEntry = -1;
|
||||
var entryIdx = _buckets[bucket];
|
||||
|
||||
while (entryIdx >= 0 && entryIdx < _capacity)
|
||||
{
|
||||
if (UnsafeUtilities.ReadArrayElement<TKey>(_keys, entryIdx).Equals(key))
|
||||
{
|
||||
removed++;
|
||||
|
||||
// Found matching element, remove it
|
||||
if (prevEntry < 0)
|
||||
{
|
||||
_buckets[bucket] = _next[entryIdx];
|
||||
}
|
||||
else
|
||||
{
|
||||
_next[prevEntry] = _next[entryIdx];
|
||||
}
|
||||
|
||||
// And free the index
|
||||
var nextIdx = _next[entryIdx];
|
||||
_next[entryIdx] = _firstFreeIndex;
|
||||
_firstFreeIndex = entryIdx;
|
||||
entryIdx = nextIdx;
|
||||
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
prevEntry = entryIdx;
|
||||
entryIdx = _next[entryIdx];
|
||||
}
|
||||
}
|
||||
|
||||
_count -= removed;
|
||||
return 0 != removed ? removed : -1;
|
||||
}
|
||||
|
||||
public bool TryGetValue<TValue>(in TKey key, out TValue item)
|
||||
where TValue : unmanaged
|
||||
{
|
||||
var idx = Find(key);
|
||||
|
||||
if (idx != -1)
|
||||
{
|
||||
item = UnsafeUtilities.ReadArrayElement<TValue>(_buffer, idx);
|
||||
return true;
|
||||
}
|
||||
|
||||
item = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool MoveNextSearch(ref int bucketIndex, ref int nextIndex, out int index)
|
||||
{
|
||||
for (int i = bucketIndex, num = _bucketCapacity; i < num; ++i)
|
||||
{
|
||||
var idx = _buckets[i];
|
||||
|
||||
if (idx != -1)
|
||||
{
|
||||
index = idx;
|
||||
bucketIndex = i + 1;
|
||||
nextIndex = _next[idx];
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
index = -1;
|
||||
bucketIndex = _bucketCapacity;
|
||||
nextIndex = -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool MoveNext(ref int bucketIndex, ref int nextIndex, out int index)
|
||||
{
|
||||
if (nextIndex != -1)
|
||||
{
|
||||
index = nextIndex;
|
||||
nextIndex = _next[nextIndex];
|
||||
return true;
|
||||
}
|
||||
|
||||
return MoveNextSearch(ref bucketIndex, ref nextIndex, out index);
|
||||
}
|
||||
|
||||
internal UnsafeArray<TKey> GetKeyArray(Allocator allocator)
|
||||
{
|
||||
var result = new UnsafeArray<TKey>(_count, allocator);
|
||||
|
||||
for (int i = 0, count = 0, max = result.Count, capacity = _bucketCapacity; i < capacity && count < max; i++)
|
||||
{
|
||||
var bucket = _buckets[i];
|
||||
|
||||
while (bucket != -1)
|
||||
{
|
||||
result[count++] = UnsafeUtilities.ReadArrayElement<TKey>(_keys, bucket);
|
||||
bucket = _next[bucket];
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
internal UnsafeArray<TValue> GetValueArray<TValue>(Allocator allocator)
|
||||
where TValue : unmanaged
|
||||
{
|
||||
var result = new UnsafeArray<TValue>(_count, allocator);
|
||||
|
||||
for (int i = 0, count = 0, max = result.Count, capacity = _bucketCapacity; i < capacity && count < max; ++i)
|
||||
{
|
||||
var bucket = _buckets[i];
|
||||
|
||||
while (bucket != -1)
|
||||
{
|
||||
result[count++] = UnsafeUtilities.ReadArrayElement<TValue>(_buffer, bucket);
|
||||
bucket = _next[bucket];
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public UnsafeArray<KeyValuePair<TKey, TValue>> GetKeyValueArrays<TValue>(Allocator allocator)
|
||||
where TValue : unmanaged
|
||||
{
|
||||
var result = new UnsafeArray<KeyValuePair<TKey, TValue>>(_count, allocator);
|
||||
|
||||
for (int i = 0, count = 0, max = result.Count, capacity = _bucketCapacity; i < capacity && count < max; i++)
|
||||
{
|
||||
var bucket = _buckets[i];
|
||||
|
||||
while (bucket != -1)
|
||||
{
|
||||
result[count] = new(UnsafeUtilities.ReadArrayElement<TKey>(_keys, bucket),
|
||||
UnsafeUtilities.ReadArrayElement<TValue>(_buffer, bucket));
|
||||
|
||||
count++;
|
||||
bucket = _next[bucket];
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
MemSet(_buckets, 0xff, (nuint)_bucketCapacity * sizeof(int));
|
||||
MemSet(_next, 0xff, (nuint)_capacity * sizeof(int));
|
||||
|
||||
_count = 0;
|
||||
_firstFreeIndex = -1;
|
||||
_allocatedIndex = 0;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (IsCreated)
|
||||
{
|
||||
_handle->Free(_handle->Allocator, _buffer);
|
||||
|
||||
_buffer = null;
|
||||
_keys = null;
|
||||
_next = null;
|
||||
_buckets = null;
|
||||
|
||||
_count = 0;
|
||||
_capacity = 0;
|
||||
_bucketCapacity = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
352
Misaki.HighPerformance.LowLevel/Helpers/MemoryUtilities.Byte.cs
Normal file
352
Misaki.HighPerformance.LowLevel/Helpers/MemoryUtilities.Byte.cs
Normal file
@@ -0,0 +1,352 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.Intrinsics;
|
||||
|
||||
namespace Misaki.HighPerformance.LowLevel.Helpers;
|
||||
|
||||
public static unsafe partial class MemoryUtilities
|
||||
{
|
||||
[DoesNotReturn]
|
||||
private static void ThrowMustBeNullTerminatedString()
|
||||
{
|
||||
throw new ArgumentException("Arg_MustBeNullTerminatedString");
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static Vector128<byte> LoadVector128(ref byte start, nuint offset)
|
||||
=> Unsafe.ReadUnaligned<Vector128<byte>>(ref Unsafe.AddByteOffset(ref start, offset));
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static Vector256<byte> LoadVector256(ref byte start, nuint offset)
|
||||
=> Unsafe.ReadUnaligned<Vector256<byte>>(ref Unsafe.AddByteOffset(ref start, offset));
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static nuint GetByteVector128SpanLength(nuint offset, int length)
|
||||
=> (uint)((length - (int)offset) & ~(Vector128<byte>.Count - 1));
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static nuint GetByteVector256SpanLength(nuint offset, int length)
|
||||
=> (uint)((length - (int)offset) & ~(Vector256<byte>.Count - 1));
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static nuint GetByteVector512SpanLength(nuint offset, int length)
|
||||
=> (uint)((length - (int)offset) & ~(Vector512<byte>.Count - 1));
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static unsafe nuint UnalignedCountVector128(byte* searchSpace)
|
||||
{
|
||||
var unaligned = (nint)searchSpace & (Vector128<byte>.Count - 1);
|
||||
return (uint)((Vector128<byte>.Count - unaligned) & (Vector128<byte>.Count - 1));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Searches for the first occurrence of a null byte (0x00) in a given byte array.
|
||||
/// </summary>
|
||||
/// <param name="searchSpace">A pointer to the byte array where the search will be performed.</param>
|
||||
/// <returns>Returns the index of the first null byte found in the array..</returns>
|
||||
/// <exception cref="ArgumentException">Thrown if the byte array is not null-terminated.</exception>"
|
||||
public static unsafe int IndexOfNullByte(byte* searchSpace)
|
||||
{
|
||||
const int Length = int.MaxValue;
|
||||
const uint uValue = 0; // Use uint for comparisons to avoid unnecessary 8->32 extensions
|
||||
nuint offset = 0; // Use nuint for arithmetic to avoid unnecessary 64->32->64 truncations
|
||||
var lengthToExamine = (nuint)(uint)Length;
|
||||
|
||||
if (Vector128.IsHardwareAccelerated)
|
||||
{
|
||||
// Avx2 branch also operates on Sse2 sizes, so check is combined.
|
||||
lengthToExamine = UnalignedCountVector128(searchSpace);
|
||||
}
|
||||
|
||||
SequentialScan:
|
||||
while (lengthToExamine >= 8)
|
||||
{
|
||||
lengthToExamine -= 8;
|
||||
|
||||
if (uValue == searchSpace[offset])
|
||||
goto Found;
|
||||
if (uValue == searchSpace[offset + 1])
|
||||
goto Found1;
|
||||
if (uValue == searchSpace[offset + 2])
|
||||
goto Found2;
|
||||
if (uValue == searchSpace[offset + 3])
|
||||
goto Found3;
|
||||
if (uValue == searchSpace[offset + 4])
|
||||
goto Found4;
|
||||
if (uValue == searchSpace[offset + 5])
|
||||
goto Found5;
|
||||
if (uValue == searchSpace[offset + 6])
|
||||
goto Found6;
|
||||
if (uValue == searchSpace[offset + 7])
|
||||
goto Found7;
|
||||
|
||||
offset += 8;
|
||||
}
|
||||
|
||||
if (lengthToExamine >= 4)
|
||||
{
|
||||
lengthToExamine -= 4;
|
||||
|
||||
if (uValue == searchSpace[offset])
|
||||
goto Found;
|
||||
if (uValue == searchSpace[offset + 1])
|
||||
goto Found1;
|
||||
if (uValue == searchSpace[offset + 2])
|
||||
goto Found2;
|
||||
if (uValue == searchSpace[offset + 3])
|
||||
goto Found3;
|
||||
|
||||
offset += 4;
|
||||
}
|
||||
|
||||
while (lengthToExamine > 0)
|
||||
{
|
||||
lengthToExamine -= 1;
|
||||
|
||||
if (uValue == searchSpace[offset])
|
||||
goto Found;
|
||||
|
||||
offset += 1;
|
||||
}
|
||||
|
||||
// We get past SequentialScan only if IsHardwareAccelerated is true; and remain length is greater than Vector length.
|
||||
// However, we still have the redundant check to allow the JIT to see that the code is unreachable and eliminate it when the platform does not
|
||||
// have hardware accelerated. After processing Vector lengths we return to SequentialScan to finish any remaining.
|
||||
if (Vector512.IsHardwareAccelerated)
|
||||
{
|
||||
if (offset < Length)
|
||||
{
|
||||
if ((((uint)searchSpace + offset) & (nuint)(Vector256<byte>.Count - 1)) != 0)
|
||||
{
|
||||
// Not currently aligned to Vector256 (is aligned to Vector128); this can cause a problem for searches
|
||||
// with no upper bound e.g. String.strlen.
|
||||
// Start with a check on Vector128 to align to Vector256, before moving to processing Vector256.
|
||||
// This ensures we do not fault across memory pages while searching for an end of string.
|
||||
var search = Vector128.Load(searchSpace + offset);
|
||||
|
||||
// Same method as below
|
||||
var matches = Vector128.Equals(Vector128<byte>.Zero, search).ExtractMostSignificantBits();
|
||||
if (matches == 0)
|
||||
{
|
||||
// Zero flags set so no matches
|
||||
offset += (nuint)Vector128<byte>.Count;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Find bitflag offset of first match and add to current offset
|
||||
return (int)(offset + (uint)BitOperations.TrailingZeroCount(matches));
|
||||
}
|
||||
}
|
||||
|
||||
if ((((uint)searchSpace + offset) & (nuint)(Vector512<byte>.Count - 1)) != 0)
|
||||
{
|
||||
// Not currently aligned to Vector512 (is aligned to Vector256); this can cause a problem for searches
|
||||
// with no upper bound e.g. String.strlen.
|
||||
// Start with a check on Vector256 to align to Vector512, before moving to processing Vector256.
|
||||
// This ensures we do not fault across memory pages while searching for an end of string.
|
||||
var search = Vector256.Load(searchSpace + offset);
|
||||
|
||||
// Same method as below
|
||||
var matches = Vector256.Equals(Vector256<byte>.Zero, search).ExtractMostSignificantBits();
|
||||
if (matches == 0)
|
||||
{
|
||||
// Zero flags set so no matches
|
||||
offset += (nuint)Vector256<byte>.Count;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Find bitflag offset of first match and add to current offset
|
||||
return (int)(offset + (uint)BitOperations.TrailingZeroCount(matches));
|
||||
}
|
||||
}
|
||||
lengthToExamine = GetByteVector512SpanLength(offset, Length);
|
||||
if (lengthToExamine > offset)
|
||||
{
|
||||
do
|
||||
{
|
||||
var search = Vector512.Load(searchSpace + offset);
|
||||
var matches = Vector512.Equals(Vector512<byte>.Zero, search).ExtractMostSignificantBits();
|
||||
// Note that MoveMask has converted the equal vector elements into a set of bit flags,
|
||||
// So the bit position in 'matches' corresponds to the element offset.
|
||||
if (matches == 0)
|
||||
{
|
||||
// Zero flags set so no matches
|
||||
offset += (nuint)Vector512<byte>.Count;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find bitflag offset of first match and add to current offset
|
||||
return (int)(offset + (uint)BitOperations.TrailingZeroCount(matches));
|
||||
} while (lengthToExamine > offset);
|
||||
}
|
||||
|
||||
lengthToExamine = GetByteVector256SpanLength(offset, Length);
|
||||
if (lengthToExamine > offset)
|
||||
{
|
||||
var search = Vector256.Load(searchSpace + offset);
|
||||
|
||||
// Same method as above
|
||||
var matches = Vector256.Equals(Vector256<byte>.Zero, search).ExtractMostSignificantBits();
|
||||
if (matches == 0)
|
||||
{
|
||||
// Zero flags set so no matches
|
||||
offset += (nuint)Vector256<byte>.Count;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Find bitflag offset of first match and add to current offset
|
||||
return (int)(offset + (uint)BitOperations.TrailingZeroCount(matches));
|
||||
}
|
||||
}
|
||||
|
||||
lengthToExamine = GetByteVector128SpanLength(offset, Length);
|
||||
if (lengthToExamine > offset)
|
||||
{
|
||||
var search = Vector128.Load(searchSpace + offset);
|
||||
|
||||
// Same method as above
|
||||
var matches = Vector128.Equals(Vector128<byte>.Zero, search).ExtractMostSignificantBits();
|
||||
if (matches == 0)
|
||||
{
|
||||
// Zero flags set so no matches
|
||||
offset += (nuint)Vector128<byte>.Count;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Find bitflag offset of first match and add to current offset
|
||||
return (int)(offset + (uint)BitOperations.TrailingZeroCount(matches));
|
||||
}
|
||||
}
|
||||
|
||||
if (offset < Length)
|
||||
{
|
||||
lengthToExamine = (Length - offset);
|
||||
goto SequentialScan;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (Vector256.IsHardwareAccelerated)
|
||||
{
|
||||
if (offset < Length)
|
||||
{
|
||||
if ((((uint)searchSpace + offset) & (nuint)(Vector256<byte>.Count - 1)) != 0)
|
||||
{
|
||||
// Not currently aligned to Vector256 (is aligned to Vector128); this can cause a problem for searches
|
||||
// with no upper bound e.g. String.strlen.
|
||||
// Start with a check on Vector128 to align to Vector256, before moving to processing Vector256.
|
||||
// This ensures we do not fault across memory pages while searching for an end of string.
|
||||
var search = Vector128.Load(searchSpace + offset);
|
||||
|
||||
// Same method as below
|
||||
var matches = Vector128.Equals(Vector128<byte>.Zero, search).ExtractMostSignificantBits();
|
||||
if (matches == 0)
|
||||
{
|
||||
// Zero flags set so no matches
|
||||
offset += (nuint)Vector128<byte>.Count;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Find bitflag offset of first match and add to current offset
|
||||
return (int)(offset + (uint)BitOperations.TrailingZeroCount(matches));
|
||||
}
|
||||
}
|
||||
|
||||
lengthToExamine = GetByteVector256SpanLength(offset, Length);
|
||||
if (lengthToExamine > offset)
|
||||
{
|
||||
do
|
||||
{
|
||||
var search = Vector256.Load(searchSpace + offset);
|
||||
var matches = Vector256.Equals(Vector256<byte>.Zero, search).ExtractMostSignificantBits();
|
||||
// Note that MoveMask has converted the equal vector elements into a set of bit flags,
|
||||
// So the bit position in 'matches' corresponds to the element offset.
|
||||
if (matches == 0)
|
||||
{
|
||||
// Zero flags set so no matches
|
||||
offset += (nuint)Vector256<byte>.Count;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find bitflag offset of first match and add to current offset
|
||||
return (int)(offset + (uint)BitOperations.TrailingZeroCount(matches));
|
||||
} while (lengthToExamine > offset);
|
||||
}
|
||||
|
||||
lengthToExamine = GetByteVector128SpanLength(offset, Length);
|
||||
if (lengthToExamine > offset)
|
||||
{
|
||||
var search = Vector128.Load(searchSpace + offset);
|
||||
|
||||
// Same method as above
|
||||
var matches = Vector128.Equals(Vector128<byte>.Zero, search).ExtractMostSignificantBits();
|
||||
if (matches == 0)
|
||||
{
|
||||
// Zero flags set so no matches
|
||||
offset += (nuint)Vector128<byte>.Count;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Find bitflag offset of first match and add to current offset
|
||||
return (int)(offset + (uint)BitOperations.TrailingZeroCount(matches));
|
||||
}
|
||||
}
|
||||
|
||||
if (offset < Length)
|
||||
{
|
||||
lengthToExamine = (Length - offset);
|
||||
goto SequentialScan;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (Vector128.IsHardwareAccelerated)
|
||||
{
|
||||
if (offset < Length)
|
||||
{
|
||||
lengthToExamine = GetByteVector128SpanLength(offset, Length);
|
||||
|
||||
while (lengthToExamine > offset)
|
||||
{
|
||||
var search = Vector128.Load(searchSpace + offset);
|
||||
|
||||
// Same method as above
|
||||
var compareResult = Vector128.Equals(Vector128<byte>.Zero, search);
|
||||
if (compareResult == Vector128<byte>.Zero)
|
||||
{
|
||||
// Zero flags set so no matches
|
||||
offset += (nuint)Vector128<byte>.Count;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find bitflag offset of first match and add to current offset
|
||||
var matches = compareResult.ExtractMostSignificantBits();
|
||||
return (int)(offset + (uint)BitOperations.TrailingZeroCount(matches));
|
||||
}
|
||||
|
||||
if (offset < Length)
|
||||
{
|
||||
lengthToExamine = (Length - offset);
|
||||
goto SequentialScan;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ThrowMustBeNullTerminatedString();
|
||||
Found: // Workaround for https://github.com/dotnet/runtime/issues/8795
|
||||
return (int)offset;
|
||||
Found1:
|
||||
return (int)(offset + 1);
|
||||
Found2:
|
||||
return (int)(offset + 2);
|
||||
Found3:
|
||||
return (int)(offset + 3);
|
||||
Found4:
|
||||
return (int)(offset + 4);
|
||||
Found5:
|
||||
return (int)(offset + 5);
|
||||
Found6:
|
||||
return (int)(offset + 6);
|
||||
Found7:
|
||||
return (int)(offset + 7);
|
||||
}
|
||||
}
|
||||
181
Misaki.HighPerformance.LowLevel/Helpers/MemoryUtilities.cs
Normal file
181
Misaki.HighPerformance.LowLevel/Helpers/MemoryUtilities.cs
Normal file
@@ -0,0 +1,181 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Misaki.HighPerformance.LowLevel.Helpers;
|
||||
|
||||
public static unsafe partial class MemoryUtilities
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct AlignOfHelper<T>
|
||||
where T : struct
|
||||
{
|
||||
public byte dummy;
|
||||
public T data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allocates a block of memory of the specified size in bytes.
|
||||
/// </summary>
|
||||
/// <param name="size">Specifies the number of bytes to allocate in memory.</param>
|
||||
/// <returns>Returns a pointer to the allocated memory block.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void* Malloc(nuint size)
|
||||
{
|
||||
return NativeMemory.Alloc(size);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allocates a block of memory with a specified size and alignment.
|
||||
/// </summary>
|
||||
/// <param name="size">Specifies the total number of bytes to allocate for the memory block.</param>
|
||||
/// <param name="alignment">Defines the required alignment for the allocated memory address.</param>
|
||||
/// <returns>Returns a pointer to the allocated memory block.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void* AlignedAlloc(nuint size, nuint alignment)
|
||||
{
|
||||
return NativeMemory.AlignedAlloc(size, alignment);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resizes a previously allocated memory block to a new size. It returns a pointer to the reallocated memory.
|
||||
/// </summary>
|
||||
/// <param name="ptr">The pointer to the memory block that needs to be resized.</param>
|
||||
/// <param name="size">The new size for the memory block after resizing.</param>
|
||||
/// <returns>A pointer to the reallocated memory block, or null if the operation fails.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void* Realloc(void* ptr, nuint size)
|
||||
{
|
||||
return NativeMemory.Realloc(ptr, size);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reallocates memory to a specified size with a given alignment. It returns a pointer to the newly allocated
|
||||
/// memory.
|
||||
/// </summary>
|
||||
/// <param name="ptr">The pointer to the existing memory block that needs to be reallocated.</param>
|
||||
/// <param name="size">The new size for the memory allocation.</param>
|
||||
/// <param name="alignment">The required alignment for the new memory allocation.</param>
|
||||
/// <returns>A pointer to the reallocated memory block, or null if the allocation fails.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void* AlignedRealloc(void* ptr, nuint size, nuint alignment)
|
||||
{
|
||||
return NativeMemory.AlignedRealloc(ptr, size, alignment);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases the allocated memory pointed to by the given pointer. This helps in managing memory usage effectively.
|
||||
/// </summary>
|
||||
/// <param name="ptr">The pointer to the memory block that needs to be freed.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Free(void* ptr)
|
||||
{
|
||||
if (ptr == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
NativeMemory.Free(ptr);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases memory that was allocated with alignment requirements. It ensures proper deallocation of aligned memory
|
||||
/// blocks.
|
||||
/// </summary>
|
||||
/// <param name="ptr">The pointer to the memory block that needs to be freed.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void AlignedFree(void* ptr)
|
||||
{
|
||||
if (ptr == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
NativeMemory.AlignedFree(ptr);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears a block of memory by setting it to zero. It initializes a specified number of bytes at a given memory
|
||||
/// address.
|
||||
/// </summary>
|
||||
/// <param name="ptr">Specifies the memory address where the clearing operation will begin.</param>
|
||||
/// <param name="size">Indicates the number of bytes to be cleared in the memory block.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void MemClear(void* ptr, nuint size)
|
||||
{
|
||||
if (ptr == null || size == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
NativeMemory.Clear(ptr, size);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a block of memory to a specified byte value for a given size.
|
||||
/// </summary>
|
||||
/// <param name="ptr">The memory address where the byte value will be set.</param>
|
||||
/// <param name="size">The number of bytes to set to the specified value.</param>
|
||||
/// <param name="value">The byte value to which the memory block will be initialized.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void MemSet(void* ptr, byte value, nuint size)
|
||||
{
|
||||
if (ptr == null || size == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
NativeMemory.Fill(ptr, size, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies a block of memory from a source location to a destination location.
|
||||
/// </summary>
|
||||
/// <param name="source">Indicates the memory address from which data will be copied.</param>
|
||||
/// <param name="destination">Specifies the memory address where the copied data will be stored.</param>
|
||||
/// <param name="size">Defines the number of bytes to be copied from the source to the destination.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void MemCpy(void* source, void* destination, nuint size)
|
||||
{
|
||||
if (source == null || destination == null || size == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
NativeMemory.Copy(source, destination, size);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the size in bytes of a specified unmanaged type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Represents an unmanaged type for which the size is being calculated.</typeparam>
|
||||
/// <returns>Returns the size of the specified type as an unsigned integer.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static nuint SizeOf<T>()
|
||||
where T : unmanaged
|
||||
{
|
||||
return (nuint)sizeof(T);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the alignment size of a specified unmanaged type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Represents an unmanaged type for which the alignment size is being calculated.</typeparam>
|
||||
/// <returns>Returns the difference in size between a helper structure and the specified type.</returns>
|
||||
public static nuint AlignOf<T>()
|
||||
where T : unmanaged
|
||||
{
|
||||
return (nuint)(sizeof(AlignOfHelper<T>) - sizeof(T));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the alignment size difference between a specified struct and a helper struct.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Represents a value type that is used to determine the alignment size.</typeparam>
|
||||
/// <returns>Returns the size difference in bytes as an integer.</returns>
|
||||
public static int MarshalAlignOf<T>()
|
||||
where T : struct
|
||||
{
|
||||
return Marshal.SizeOf<AlignOfHelper<T>>() - Marshal.SizeOf<T>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Misaki.HighPerformance.LowLevel.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Provides extension methods for copying elements between unsafe collections and spans, converting collections to
|
||||
/// arrays or lists, and searching for values.
|
||||
/// </summary>
|
||||
public unsafe static class UnsafeCollectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Copies elements from a source UnsafeCollection to a destination Span, ensuring both have the same size.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Specifies the type of elements being copied, which must be unmanaged.</typeparam>
|
||||
/// <param name="source">Represents the source collection from which elements are copied.</param>
|
||||
/// <param name="destination">Represents the target span where elements are copied to.</param>
|
||||
/// <exception cref="ArgumentException">Thrown when the sizes of the source collection and destination span do not match.</exception>
|
||||
public static void CopyTo<T>(this IUnsafeCollection<T> source, Span<T> destination)
|
||||
where T : unmanaged
|
||||
{
|
||||
if (source.Count > destination.Length)
|
||||
{
|
||||
throw new ArgumentException("Source collection is larger than the destination span.");
|
||||
}
|
||||
|
||||
fixed (T* ptr = destination)
|
||||
{
|
||||
SystemUnsfae.CopyBlock(ptr, source.GetUnsafePtr(), (uint)(source.Count * sizeof(T)));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies a range of elements from a source collection to a destination span, ensuring both are adequately sized.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Specifies the type of elements being copied, which must be a value type.</typeparam>
|
||||
/// <param name="source">The collection from which elements are copied.</param>
|
||||
/// <param name="destination">The span where the elements will be copied to.</param>
|
||||
/// <param name="sourceIndex">The starting index in the source collection for the copy operation.</param>
|
||||
/// <param name="destinationIndex">The starting index in the destination span where the elements will be placed.</param>
|
||||
/// <param name="length">The number of elements to copy from the source to the destination.</param>
|
||||
/// <exception cref="ArgumentException">Thrown when the specified range exceeds the bounds of the source collection or destination span.</exception>
|
||||
public static void CopyTo<T>(this IUnsafeCollection<T> source, Span<T> destination, int sourceIndex, int destinationIndex, int length)
|
||||
where T : unmanaged
|
||||
{
|
||||
if (sourceIndex + length > source.Count || destinationIndex + length > destination.Length)
|
||||
{
|
||||
throw new ArgumentException("Source collection or destination span is too small for the specified range.");
|
||||
}
|
||||
|
||||
fixed (T* ptr = destination)
|
||||
{
|
||||
SystemUnsfae.CopyBlock(ptr + destinationIndex, (byte*)source.GetUnsafePtr() + sourceIndex * sizeof(T), (uint)(length * sizeof(T)));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies elements from a source span to a destination unsafe collection, ensuring both have the same size.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Specifies the type of elements being copied, which must be unmanaged.</typeparam>
|
||||
/// <param name="destination">Represents the unsafe collection that will receive the copied elements.</param>
|
||||
/// <param name="source">Represents the span containing the elements to be copied to the unsafe collection.</param>
|
||||
/// <exception cref="ArgumentException">Thrown when the source span and destination collection have different sizes.</exception>
|
||||
public static void CopyFrom<T>(this IUnsafeCollection<T> destination, Span<T> source)
|
||||
where T : unmanaged
|
||||
{
|
||||
if (destination.Count > source.Length)
|
||||
{
|
||||
throw new ArgumentException("Destination collection is larger than the source span.");
|
||||
}
|
||||
|
||||
fixed (T* ptr = source)
|
||||
{
|
||||
SystemUnsfae.CopyBlock(destination.GetUnsafePtr(), ptr, (uint)(source.Length * sizeof(T)));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies a specified range of elements from a source span to a destination collection.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Represents the type of elements being copied, which must be unmanaged.</typeparam>
|
||||
/// <param name="destination">The collection where elements will be copied to.</param>
|
||||
/// <param name="source">The span containing the elements to be copied.</param>
|
||||
/// <param name="sourceIndex">The starting index in the source span from which to begin copying.</param>
|
||||
/// <param name="destinationIndex">The starting index in the destination collection where the elements will be placed.</param>
|
||||
/// <param name="length">The number of elements to copy from the source span to the destination collection.</param>
|
||||
/// <exception cref="ArgumentException">Thrown when the specified range exceeds the bounds of the source span or destination collection.</exception>
|
||||
public static void CopyFrom<T>(this IUnsafeCollection<T> destination, Span<T> source, int sourceIndex, int destinationIndex, int length)
|
||||
where T : unmanaged
|
||||
{
|
||||
if (sourceIndex + length > source.Length || destinationIndex + length > destination.Count)
|
||||
{
|
||||
throw new ArgumentException("Source span or destination collection is too small for the specified range.");
|
||||
}
|
||||
|
||||
fixed (T* ptr = source)
|
||||
{
|
||||
SystemUnsfae.CopyBlock((byte*)destination.GetUnsafePtr() + destinationIndex * sizeof(T), ptr + sourceIndex, (uint)(length * sizeof(T)));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an UnsafeCollection into a Span for efficient memory access.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Represents a type that can be stored in unmanaged memory.</typeparam>
|
||||
/// <param name="source">The UnsafeCollection instance to be converted into a Span.</param>
|
||||
/// <returns>A Span that provides a view over the elements of the UnsafeCollection.</returns>
|
||||
public static Span<T> AsSpan<T>(this IUnsafeCollection<T> source)
|
||||
where T : unmanaged
|
||||
{
|
||||
return new(source.GetUnsafePtr(), source.Count);
|
||||
}
|
||||
|
||||
public static UnsafeArray<T> ToUnsafeArray<T>(this T[] source, Allocator allocator)
|
||||
where T : unmanaged
|
||||
{
|
||||
var array = new UnsafeArray<T>(source.Length, allocator);
|
||||
fixed (T* ptr = source)
|
||||
{
|
||||
MemCpy(array.GetUnsafePtr(), ptr, (uint)(source.Length * sizeof(T)));
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
public static UnsafeList<T> ToUnsafeList<T>(this List<T> source, Allocator allocator)
|
||||
where T : unmanaged
|
||||
{
|
||||
var list = new UnsafeList<T>(source.Count, allocator);
|
||||
fixed (T* ptr = CollectionsMarshal.AsSpan(source))
|
||||
{
|
||||
MemCpy(list.GetUnsafePtr(), ptr, (uint)(source.Count * sizeof(T)));
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
97
Misaki.HighPerformance.LowLevel/Helpers/UnsafeUtilities.cs
Normal file
97
Misaki.HighPerformance.LowLevel/Helpers/UnsafeUtilities.cs
Normal file
@@ -0,0 +1,97 @@
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Misaki.HighPerformance.LowLevel.Helpers;
|
||||
|
||||
public static unsafe class UnsafeUtilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts a pointer to a reference of a specified type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Specifies the type of the reference to be created from the pointer.</typeparam>
|
||||
/// <param name="ptr">Represents the memory address to be converted into a reference.</param>
|
||||
/// <returns>Returns a reference of the specified type pointing to the given memory address.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref T AsRef<T>(void* ptr)
|
||||
{
|
||||
return ref SystemUnsfae.AsRef<T>(ptr);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the address of a specified variable in memory.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Represents the type of the variable whose address is being retrieved.</typeparam>
|
||||
/// <param name="value">The variable whose memory address is to be obtained.</param>
|
||||
/// <returns>A pointer to the memory address of the specified variable.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void* AddressOf<T>(ref T value)
|
||||
{
|
||||
return SystemUnsfae.AsPointer(ref value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an element from an unmanaged array at a specified index using a pointer.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Specifies the type of elements in the unmanaged array.</typeparam>
|
||||
/// <param name="ptr">Points to the start of the unmanaged array from which the element is read.</param>
|
||||
/// <param name="index">Indicates the position of the element to be accessed within the array.</param>
|
||||
/// <returns>Returns a pointer to the element located at the specified index.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static T* ReadArrayElementUnsafe<T>(void* ptr, int index) where T : unmanaged
|
||||
{
|
||||
return (T*)((byte*)ptr + index * sizeof(T));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an element from an unmanaged array using a pointer and index, returning a reference to the element.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Specifies the type of the elements in the unmanaged array.</typeparam>
|
||||
/// <param name="ptr">Points to the start of the unmanaged array from which the element is read.</param>
|
||||
/// <param name="index">Indicates the position of the element to be accessed in the array.</param>
|
||||
/// <returns>A reference to the specified element in the unmanaged array.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref T ReadArrayElementRef<T>(void* ptr, int index) where T : unmanaged
|
||||
{
|
||||
return ref AsRef<T>(ReadArrayElementUnsafe<T>(ptr, index));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an element from an array at a specified index using a pointer to the array.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Specifies the type of the elements in the array, which must be unmanaged.</typeparam>
|
||||
/// <param name="ptr">Points to the start of the array from which an element will be read.</param>
|
||||
/// <param name="index">Indicates the position of the element to be accessed within the array.</param>
|
||||
/// <returns>The element located at the specified index in the array.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static T ReadArrayElement<T>(void* ptr, int index) where T : unmanaged
|
||||
{
|
||||
return *ReadArrayElementUnsafe<T>(ptr, index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a value to a specified index of an unmanaged array using a pointer.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Specifies the type of the value being written to the array, which must be an unmanaged type.</typeparam>
|
||||
/// <param name="ptr">Points to the beginning of the unmanaged array where the value will be written.</param>
|
||||
/// <param name="index">Indicates the position in the array where the value should be stored.</param>
|
||||
/// <param name="value">Represents the value to be written to the specified index of the array.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void WriteArrayElement<T>(void* ptr, int index, T value) where T : unmanaged
|
||||
{
|
||||
*ReadArrayElementUnsafe<T>(ptr, index) = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an UnsafeArray of one unmanaged type to another unmanaged type without copying the elements.
|
||||
/// </summary>
|
||||
/// <typeparam name="TIn">Represents the type of elements in the input array.</typeparam>
|
||||
/// <typeparam name="TOut">Represents the type of elements in the output array.</typeparam>
|
||||
/// <param name="array">The input array containing elements of the specified input type.</param>
|
||||
/// <returns>An UnsafeArray containing elements of the specified output type.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static UnsafeArray<TOut> CastArray<TIn, TOut>(UnsafeArray<TIn> array)
|
||||
where TIn : unmanaged where TOut : unmanaged
|
||||
{
|
||||
return new UnsafeArray<TOut>(array.GetUnsafePtr(), array.Count * sizeof(TIn) / sizeof(TOut));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<IsAotCompatible>True</IsAotCompatible>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<IsAotCompatible>True</IsAotCompatible>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Misaki.HighPerformance\Misaki.HighPerformance.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="Buffer\FixedString.tt">
|
||||
<LastGenOutput>FixedString.cs</LastGenOutput>
|
||||
<Generator>TextTemplatingFileGenerator</Generator>
|
||||
</None>
|
||||
<None Update="Buffer\FixedStackString.tt">
|
||||
<Generator>TextTemplatingFileGenerator</Generator>
|
||||
<LastGenOutput>FixedStackString.cs</LastGenOutput>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="Buffer\FixedString.cs">
|
||||
<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>
|
||||
|
||||
</Project>
|
||||
Reference in New Issue
Block a user