diff --git a/Misaki.HighPerformance.Image/README.md b/Misaki.HighPerformance.Image/README.md new file mode 100644 index 0000000..acd2132 --- /dev/null +++ b/Misaki.HighPerformance.Image/README.md @@ -0,0 +1,50 @@ +# Misaki.HighPerformance.Image + +STB-based image loading and translation helpers for C#. + +This package focuses on practical image decoding with low-level control over memory ownership and result handling. + +## What it includes + +- image loading from streams and memory +- animated GIF frame enumeration +- image metadata and result wrappers +- runtime helpers and native memory statistics +- translation layers for STB image formats + +## Highlights + +- allocates image data in unmanaged memory +- exposes width, height, and component information alongside the pixel buffer +- useful when you need simple decoding without a heavy graphics dependency +- supports animated frame workflows in addition to single image loads + +## Main types + +- `StbImage` +- `ImageResult` +- `ImageInfo` +- `AnimatedGifEnumerator` +- `AnimatedFrameResult` +- `ImageResultFloat` +- `ColorComponents` + +## Example + +```csharp +using Misaki.HighPerformance.Image; +using var stream = File.OpenRead("image.png"); +using ImageResult image = ImageResult.FromStream(stream); + +Span pixels = image.AsSpan(); +``` + +## Package reference + +```bash +dotnet add package Misaki.HighPerformance.Image +``` + +## Notes + +This project targets `net10.0` and uses unsafe code for image memory handling. diff --git a/Misaki.HighPerformance.Jobs/JobScheduler.cs b/Misaki.HighPerformance.Jobs/JobScheduler.cs index abe7f20..2c973a2 100644 --- a/Misaki.HighPerformance.Jobs/JobScheduler.cs +++ b/Misaki.HighPerformance.Jobs/JobScheduler.cs @@ -220,10 +220,10 @@ public unsafe partial class JobScheduler /// public static AllocationHandle TempAllocatorHandle => pTempAllocator->Handle; - public static void InitTempAllocator() + public static void InitTempAllocator(nuint capacityPerFrame) { pTempAllocator = (TempJobAllocator*)MemoryUtility.Malloc((nuint)sizeof(TempJobAllocator)); - pTempAllocator->Init(); + pTempAllocator->Initialize(capacityPerFrame); } public static void ReleaseTempAllocator() diff --git a/Misaki.HighPerformance.Jobs/Misaki.HighPerformance.Jobs.csproj b/Misaki.HighPerformance.Jobs/Misaki.HighPerformance.Jobs.csproj index 69a77b5..e067d4e 100644 --- a/Misaki.HighPerformance.Jobs/Misaki.HighPerformance.Jobs.csproj +++ b/Misaki.HighPerformance.Jobs/Misaki.HighPerformance.Jobs.csproj @@ -6,7 +6,7 @@ enable True true - 1.5.2 + 1.5.3 $(AssemblyVersion) Misaki https://git.personalnas.com/Misaki/Misaki.HighPerformance.git diff --git a/Misaki.HighPerformance.Jobs/README.md b/Misaki.HighPerformance.Jobs/README.md new file mode 100644 index 0000000..7b7be7c --- /dev/null +++ b/Misaki.HighPerformance.Jobs/README.md @@ -0,0 +1,52 @@ +# Misaki.HighPerformance.Jobs + +A zero-allocation-oriented job system for C#. + +This package provides job contracts, scheduling, worker threads, dependency handling, and temporary allocation support for high-throughput work execution. + +## What it includes + +- single-job execution +- parallel-for job execution +- parallel range jobs +- job handles and dependency tracking +- worker thread management +- temporary job allocation support + +## Highlights + +- designed to minimize allocations during scheduling and execution +- supports dependency composition and wait operations +- suitable for frame-based engines, simulations, batch processing, and custom runtimes +- integrates with the low-level allocation layer + +## Main types + +- `IJob` +- `IJobParallelFor` +- `IJobParallel` +- `JobScheduler` +- `JobHandle` +- `JobExecutionContext` +- `JobState` +- `WorkerThread` +- `TempJobAllocator` + +## Example + +```csharp +using Misaki.HighPerformance.Jobs; + +// Implement IJob, IJobParallelFor, or IJobParallel and schedule the work through JobScheduler. +// The scheduler copies job data internally and tracks completion through JobHandle. +``` + +## Package reference + +```bash +dotnet add package Misaki.HighPerformance.Jobs +``` + +## Notes + +This project targets `net10.0`, enables unsafe code, and is packaged as content files for downstream consumption. diff --git a/Misaki.HighPerformance.Jobs/TempJobAllocator.cs b/Misaki.HighPerformance.Jobs/TempJobAllocator.cs index 71cd5e7..bc0695b 100644 --- a/Misaki.HighPerformance.Jobs/TempJobAllocator.cs +++ b/Misaki.HighPerformance.Jobs/TempJobAllocator.cs @@ -7,10 +7,9 @@ namespace Misaki.HighPerformance.Jobs; public unsafe struct TempJobAllocator : IAllocator, IDisposable { private const int _FRAME_LATENCY = 4; - private const uint _ARENA_SIZE = 1024 * 1024; // 1 MB private const int _MAGIC_ID = -559038737; - private DynamicArena* _pArena; + private VirtualArena* _pArena; private int _currentFrameCount; private int _currentFrameIndex; private fixed int _allocationsPerFrame[_FRAME_LATENCY]; @@ -20,18 +19,18 @@ public unsafe struct TempJobAllocator : IAllocator, IDisposable public readonly AllocationHandle Handle => _handle; - internal void Init() + public void Initialize(nuint capacity) { var memoryHandle = default(MemoryHandle); - _pArena = (DynamicArena*)AllocationManager.HeapAlloc((nuint)(sizeof(DynamicArena) * _FRAME_LATENCY), MemoryUtility.AlignOf(), AllocationOption.Clear, &memoryHandle); + _pArena = (VirtualArena*)AllocationManager.HeapAlloc((nuint)(sizeof(VirtualArena) * _FRAME_LATENCY), MemoryUtility.AlignOf(), AllocationOption.Clear, &memoryHandle); _currentFrameCount = 0; _currentFrameIndex = 0; _memoryHandle = memoryHandle; for (int i = 0; i < _FRAME_LATENCY; i++) { - _pArena[i].Initialize(_ARENA_SIZE); + _pArena[i] = new VirtualArena(capacity); _allocationsPerFrame[i] = 0; } diff --git a/Misaki.HighPerformance.LowLevel/Buffer/AllocationManager.cs b/Misaki.HighPerformance.LowLevel/Buffer/AllocationManager.cs index 6087162..5cfedba 100644 --- a/Misaki.HighPerformance.LowLevel/Buffer/AllocationManager.cs +++ b/Misaki.HighPerformance.LowLevel/Buffer/AllocationManager.cs @@ -1,6 +1,9 @@ using Misaki.HighPerformance.Collections; using System.Diagnostics; using System.Runtime.CompilerServices; +#if ENABLE_DEBUG_LAYER +using System.Runtime.InteropServices; +#endif namespace Misaki.HighPerformance.LowLevel.Buffer; @@ -26,6 +29,24 @@ public readonly struct AllocationInfo } } +public readonly struct AllocationManagerInitOpts +{ + public nuint ArenaCapacity + { + get; init; + } + + public nuint StackCapacity + { + get; init; + } + + public int FreeListConcurrencyLevel + { + get; init; + } +} + /// /// Provides memory allocation management for native memory allocations, with support for tracking, /// debugging, and custom allocation strategies. @@ -181,8 +202,13 @@ public static unsafe class AllocationManager { private const int _STACK_MAGIC_ID = -6843541; + private static void** s_pStackBuffers = null; + private static int s_stackCount = 0; + private static int s_stackCapacity = 0; + private static readonly SpinLock s_locker = new SpinLock(false); + [ThreadStatic] - private static Stack s_stack; + private static VirtualStack s_stack; private AllocationHandle _handle; public readonly AllocationHandle Handle => _handle; @@ -199,8 +225,49 @@ public static unsafe class AllocationManager }; } + private static void EnsureInitialize() + { + if (s_stack.Buffer == null) + { + s_stack = new VirtualStack(s_threadLocalStackDefaultSize); + + var token = false; + try + { + s_locker.Enter(ref token); + if (s_pStackBuffers == null) + { + s_pStackBuffers = (void**)Malloc((nuint)(sizeof(void*) * Environment.ProcessorCount)); + s_stackCapacity = Environment.ProcessorCount; + } + + if (s_stackCount >= s_stackCapacity) + { + var pOld = s_pStackBuffers; + var newCapacity = s_stackCapacity * 2; + var pNew = (void**)Realloc(pOld, (nuint)(sizeof(void*) * newCapacity)); + + s_pStackBuffers = pNew; + s_stackCapacity = newCapacity; + } + + s_pStackBuffers[s_stackCount] = s_stack.Buffer; + s_stackCount++; + } + finally + { + if (token) + { + s_locker.Exit(); + } + } + } + } + private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption, MemoryHandle* pHandle) { + EnsureInitialize(); + var ptr = s_stack.Allocate(size, alignment, allocationOption); if (ptr == null) { @@ -219,6 +286,8 @@ public static unsafe class AllocationManager return Allocate(instance, newSize, alignment, allocationOption, pHandle); } + EnsureInitialize(); + // Optimize for last allocation. Set offset directly. var oldBase = s_stack.Buffer + s_stack.Offset - oldSize; if (ptr == oldBase) @@ -254,14 +323,23 @@ public static unsafe class AllocationManager return handle.id == _STACK_MAGIC_ID && handle.generation <= (int)s_stack.Offset; } - public static Stack.Scope CreateScope(StackAllocator* pSelf) + public static VirtualStack.Scope CreateScope(StackAllocator* pSelf) { + EnsureInitialize(); return s_stack.CreateScope(pSelf->_handle); } public readonly void Dispose() { - Stack.DisposeAll(); + if (s_pStackBuffers == null) + { + return; + } + + for (var i = 0; i < s_stackCount; i++) + { + Free(s_pStackBuffers[i]); + } } } @@ -345,8 +423,6 @@ public static unsafe class AllocationManager private static StackAllocator* s_pStackAllocator; private static FreeListAllocator* s_pFreeListAllocator; - private static bool s_initialized; - #if ENABLE_DEBUG_LAYER private static SpinLock s_liveLock; private static AllocationHeader* s_pLiveHead; @@ -356,12 +432,16 @@ public static unsafe class AllocationManager public static readonly MemoryHandle MagicHandle = new MemoryHandle(int.MinValue, int.MinValue); + private static bool s_initialized; + + internal static nuint s_threadLocalStackDefaultSize; + /// - /// Gets the number of live persistent heap allocations when the debug layer is disabled. + /// Gets the number of live tracked heap allocations. /// public static int LiveAllocationCount => s_allocations.Count; - public static void Initialize(nuint arenaCapacity, int freeListConcurrencyLevel) + public static void Initialize(AllocationManagerInitOpts opts) { if (s_initialized) { @@ -382,10 +462,12 @@ public static unsafe class AllocationManager s_pStackAllocator = (StackAllocator*)(ptr + sizeof(ArenaAllocator) + sizeof(HeapAllocator)); s_pFreeListAllocator = (FreeListAllocator*)(ptr + sizeof(ArenaAllocator) + sizeof(HeapAllocator) + sizeof(StackAllocator)); - s_pArenaAllocator->Init(arenaCapacity); + s_pArenaAllocator->Init(opts.ArenaCapacity); s_pHeapAllocator->Init(); s_pStackAllocator->Init(); - s_pFreeListAllocator->Init(freeListConcurrencyLevel); + s_pFreeListAllocator->Init(opts.FreeListConcurrencyLevel); + + s_threadLocalStackDefaultSize = opts.StackCapacity; s_initialized = true; } @@ -652,7 +734,7 @@ public static unsafe class AllocationManager /// /// A instance representing the newly created stack scope. The scope must be disposed when no longer needed to release allocated resources. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Stack.Scope CreateStackScope() + public static VirtualStack.Scope CreateStackScope() { Debug.Assert(s_initialized, "AllocationManager is not initialized."); return StackAllocator.CreateScope(s_pStackAllocator); diff --git a/Misaki.HighPerformance.LowLevel/Buffer/Arena.cs b/Misaki.HighPerformance.LowLevel/Buffer/Arena.cs index 3dd3e05..b8bdcf3 100644 --- a/Misaki.HighPerformance.LowLevel/Buffer/Arena.cs +++ b/Misaki.HighPerformance.LowLevel/Buffer/Arena.cs @@ -7,14 +7,14 @@ namespace Misaki.HighPerformance.LowLevel.Buffer; /// A memory management structure that allocates and resets memory blocks with specified alignment. /// [StructLayout(LayoutKind.Explicit, Size = 64)] // Cache line aligned to prevent false sharing -public unsafe struct Arena : IMemoryAllocator +public unsafe struct Arena : IMemoryAllocator { - public struct CreateOptions + public struct CreationOptions { public nuint size; } - public static Arena Create(in CreateOptions opts) + public static Arena Create(in CreationOptions opts) { return new Arena(opts.size); } diff --git a/Misaki.HighPerformance.LowLevel/Buffer/DynamicArena.cs b/Misaki.HighPerformance.LowLevel/Buffer/DynamicArena.cs index 508c3c8..3dcee11 100644 --- a/Misaki.HighPerformance.LowLevel/Buffer/DynamicArena.cs +++ b/Misaki.HighPerformance.LowLevel/Buffer/DynamicArena.cs @@ -1,3 +1,4 @@ +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace Misaki.HighPerformance.LowLevel.Buffer; @@ -30,27 +31,17 @@ public unsafe struct DynamicArena : IMemoryAllocator /// Initializes a new instance of DynamicArena with the specified initial size. /// /// The initial size in bytes for the first arena block. - public DynamicArena(uint initialSize) + public DynamicArena(nuint initialSize) { - Initialize(initialSize); - } - - public void Initialize(uint initialSize) - { - if (_root != null) - { - return; - } - _initialSize = initialSize; _root = (ArenaNode*)Malloc(SizeOf()); _root->arena = new Arena(initialSize); @@ -143,6 +134,7 @@ public unsafe struct DynamicArena : IMemoryAllocator : IDisposable return Allocate(pAllocator, newSize, alignment, allocationOption, pHandle); } - MemoryHandle newHandle; - var newPtr = Allocate(pAllocator, newSize, alignment, allocationOption, &newHandle); + var newPtr = Allocate(pAllocator, newSize, alignment, allocationOption, pHandle); if (newPtr == null) { return null; @@ -48,7 +47,6 @@ public unsafe struct MemoryPool : IDisposable MemCpy(newPtr, ptr, Math.Min(oldSize, newSize)); Free(pAllocator, ptr, *pHandle); - *pHandle = newHandle; return newPtr; } diff --git a/Misaki.HighPerformance.LowLevel/Buffer/Stack.cs b/Misaki.HighPerformance.LowLevel/Buffer/Stack.cs index bf610b2..68a964f 100644 --- a/Misaki.HighPerformance.LowLevel/Buffer/Stack.cs +++ b/Misaki.HighPerformance.LowLevel/Buffer/Stack.cs @@ -1,29 +1,8 @@ -using Misaki.HighPerformance.LowLevel.Utilities; +using System.Diagnostics; using System.Runtime.CompilerServices; namespace Misaki.HighPerformance.LowLevel.Buffer; -public unsafe partial struct Stack -{ - private static void** s_pStackBuffers = null; - private static int s_stackCount = 0; - private static int s_stackCapacity = 0; - private static readonly SpinLock s_locker = new SpinLock(false); - - public static void DisposeAll() - { - if (s_pStackBuffers == null) - { - return; - } - - for (var i = 0; i < s_stackCount; i++) - { - MemoryUtility.Free(s_pStackBuffers[i]); - } - } -} - /// /// Provides a stack-based memory allocator for unmanaged memory, enabling fast allocation and deallocation of memory /// blocks within a preallocated buffer. @@ -41,8 +20,6 @@ public unsafe partial struct Stack : IMemoryAllocator return new Stack(opts.size); } - private const nuint _DEFAULT_SIZE = 1024 * 1024; // 1MB - public readonly ref struct Scope : IDisposable { private readonly Stack* _allocator; @@ -56,7 +33,9 @@ public unsafe partial struct Stack : IMemoryAllocator _allocator = allocator; _handle = handle; _originalOffset = allocator->_offset; +#if ENABLE_SAFETY_CHECKS _allocator->_activeScopeCount++; +#endif } public void Dispose() @@ -64,7 +43,9 @@ public unsafe partial struct Stack : IMemoryAllocator if (_allocator != null) { _allocator->_offset = _allocator->_offset > _originalOffset ? _originalOffset : _allocator->_offset; +#if ENABLE_SAFETY_CHECKS _allocator->_activeScopeCount--; +#endif } } } @@ -72,14 +53,15 @@ public unsafe partial struct Stack : IMemoryAllocator private byte* _buffer; private nuint _size; private nuint _offset; +#if ENABLE_SAFETY_CHECKS private uint _activeScopeCount; +#endif internal readonly byte* Buffer => _buffer; - - public nuint Offset + internal nuint Offset { readonly get => _offset; - internal set => _offset = value; + set => _offset = value; } /// @@ -87,81 +69,15 @@ public unsafe partial struct Stack : IMemoryAllocator /// /// The size, in bytes, of the memory buffer to allocate for stack-based allocations. Must be greater than zero. public Stack(nuint size) - { - if (size == 0) - { - throw new ArgumentException("Size must be greater than zero.", nameof(size)); - } - - Init(size); - } - - private void Init(nuint size) { ArgumentOutOfRangeException.ThrowIfNegative(size); - if (_buffer != null) - { - Free(_buffer); - } - _buffer = (byte*)Malloc(size); - if (_buffer == null) - { - throw new OutOfMemoryException("Failed to allocate memory for the stack."); - } - _size = size; _offset = 0; +#if ENABLE_SAFETY_CHECKS _activeScopeCount = 0; - - var token = false; - try - { - s_locker.Enter(ref token); - if (s_pStackBuffers == null) - { - s_pStackBuffers = (void**)Malloc((nuint)(sizeof(void*) * Environment.ProcessorCount)); - s_stackCapacity = Environment.ProcessorCount; - } - - if (s_stackCount >= s_stackCapacity) - { - var pOld = s_pStackBuffers; - var newCapacity = s_stackCapacity * 2; - var pNew = (void**)Realloc(pOld, (nuint)(sizeof(void*) * newCapacity)); - - s_pStackBuffers = pNew; - s_stackCapacity = newCapacity; - } - - s_pStackBuffers[s_stackCount] = _buffer; - s_stackCount++; - } - finally - { - if (token) - { - s_locker.Exit(); - } - } - - } - - private readonly void ThrowIfNoScope() - { - if (_activeScopeCount == 0) - { - throw new InvalidOperationException("Allocations can only be made within an active memory scope."); - } - } - - private void EnsureInitialize() - { - if (_buffer == null || _size == 0) - { - Init(_DEFAULT_SIZE); - } +#endif } /// @@ -174,7 +90,6 @@ public unsafe partial struct Stack : IMemoryAllocator [MethodImpl(MethodImplOptions.AggressiveInlining)] public Scope CreateScope(AllocationHandle handle) { - EnsureInitialize(); return new Scope((Stack*)Unsafe.AsPointer(ref this), handle); } @@ -189,7 +104,12 @@ public unsafe partial struct Stack : IMemoryAllocator /// there is insufficient space in the buffer. public void* Allocate(nuint size, nuint alignment, AllocationOption allocationOption = AllocationOption.None) { - ThrowIfNoScope(); +#if ENABLE_SAFETY_CHECKS + if (_activeScopeCount == 0) + { + throw new InvalidOperationException("Allocations can only be made within an active memory scope."); + } +#endif if (size == 0) { @@ -202,7 +122,6 @@ public unsafe partial struct Stack : IMemoryAllocator } var alignedOffset = (_offset + alignment - 1) & ~(alignment - 1); - var newOffset = alignedOffset + size; if (newOffset > _size) @@ -222,6 +141,7 @@ public unsafe partial struct Stack : IMemoryAllocator return ptr; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly void Free(void* ptr) { } @@ -229,6 +149,7 @@ public unsafe partial struct Stack : IMemoryAllocator /// /// Resets the internal offset to its initial position. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Reset() { _offset = 0; diff --git a/Misaki.HighPerformance.LowLevel/Buffer/VirtualArena.cs b/Misaki.HighPerformance.LowLevel/Buffer/VirtualArena.cs index d67d08f..eeedd08 100644 --- a/Misaki.HighPerformance.LowLevel/Buffer/VirtualArena.cs +++ b/Misaki.HighPerformance.LowLevel/Buffer/VirtualArena.cs @@ -6,8 +6,17 @@ namespace Misaki.HighPerformance.LowLevel.Buffer; /// /// A thread-safe memory management structure that reserves a large virtual address space and commits physical memory on demand as allocations are made. /// -public unsafe struct VirtualArena +public unsafe struct VirtualArena : IMemoryAllocator { + public struct CreationOptions + { + public nuint reserveCapacity; + } + public static VirtualArena Create(in CreationOptions opts) + { + return new VirtualArena(opts.reserveCapacity); + } + private const nuint _PAGE_SIZE = 64 * 1024; private byte* _baseAddress; @@ -95,6 +104,11 @@ public unsafe struct VirtualArena return ptr; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly void Free(void* ptr) + { + } + /// /// Resets the arena. /// diff --git a/Misaki.HighPerformance.LowLevel/Buffer/VirtualStack.cs b/Misaki.HighPerformance.LowLevel/Buffer/VirtualStack.cs new file mode 100644 index 0000000..f96f56b --- /dev/null +++ b/Misaki.HighPerformance.LowLevel/Buffer/VirtualStack.cs @@ -0,0 +1,180 @@ +using Misaki.HighPerformance.LowLevel.Utilities; +using System.Runtime.CompilerServices; + +namespace Misaki.HighPerformance.LowLevel.Buffer; + +public unsafe struct VirtualStack : IMemoryAllocator +{ + private const nuint _PAGE_SIZE = 64 * 1024; + + public struct CreationOpts + { + public nuint reserveCapacity; + } + + public static VirtualStack Create(in CreationOpts opts) + { + return new VirtualStack(opts.reserveCapacity); + } + + public readonly ref struct Scope : IDisposable + { + private readonly VirtualStack* _allocator; + private readonly AllocationHandle _handle; + private readonly nuint _originalOffset; + + public readonly AllocationHandle AllocationHandle => _handle; + + internal Scope(VirtualStack* allocator, AllocationHandle handle) + { + _allocator = allocator; + _handle = handle; + _originalOffset = allocator->_allocatedOffset; +#if ENABLE_SAFETY_CHECKS + _allocator->_activeScopeCount++; +#endif + } + + public void Dispose() + { + if (_allocator != null) + { + _allocator->_allocatedOffset = _allocator->_allocatedOffset > _originalOffset ? _originalOffset : _allocator->_allocatedOffset; +#if ENABLE_SAFETY_CHECKS + _allocator->_activeScopeCount--; +#endif + } + } + } + + private byte* _baseAddress; + private nuint _reserveCapacity; + private nuint _committedSize; + private nuint _allocatedOffset; +#if ENABLE_SAFETY_CHECKS + private uint _activeScopeCount; +#endif + + internal readonly byte* Buffer => _baseAddress; + internal nuint Offset + { + readonly get => _allocatedOffset; + set => _allocatedOffset = value; + } + + public VirtualStack(nuint reserveCapacity) + { + _reserveCapacity = (reserveCapacity + _PAGE_SIZE - 1) & ~(_PAGE_SIZE - 1); + _committedSize = 0; + _allocatedOffset = 0; + + _baseAddress = (byte*)Mmap(null, _reserveCapacity, VirtualAllocationFlags.Reserve); + +#if ENABLE_SAFETY_CHECKS + _activeScopeCount = 0; +#endif + } + + /// + /// Creates a new scope instance associated with the current stack context. + /// + /// + /// The instance of must be pinned or allocated on the native heap to ensure that the pointer remains valid for the lifetime of the scope. + /// + /// A object that represents a scope tied to this stack. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Scope CreateScope(AllocationHandle handle) + { + return new Scope((VirtualStack*)Unsafe.AsPointer(ref this), handle); + } + + /// + /// Allocates a block of memory of the specified size and alignment. + /// + /// + /// This is not thread-safe. It is designed for single-threaded or thread-local contexts. + /// + public void* Allocate(nuint size, nuint alignment, AllocationOption option = AllocationOption.None) + { +#if ENABLE_SAFETY_CHECKS + if (_activeScopeCount == 0) + { + throw new InvalidOperationException("Allocations can only be made within an active memory scope."); + } +#endif + + if (size == 0) + { + return null; + } + + if ((alignment & (alignment - 1)) != 0) + { + throw new ArgumentException("Alignment must be a power of two.", nameof(alignment)); + } + + // Align the requested offset + var alignedOffset = (_allocatedOffset + alignment - 1) & ~(alignment - 1); + var newAllocatedOffset = alignedOffset + size; + + if (newAllocatedOffset > _reserveCapacity) + { + return null; // Out of reserved space + } + + if (newAllocatedOffset > _committedSize) + { + var sizeToCommit = newAllocatedOffset - _committedSize; + + // Align the commit size to the 64KB OS Page Size + sizeToCommit = (sizeToCommit + _PAGE_SIZE - 1) & ~(_PAGE_SIZE - 1); + + var commitAddress = _baseAddress + _committedSize; + var result = Mmap(commitAddress, sizeToCommit, VirtualAllocationFlags.Commit); + + if (result == null) + { + return null; // Out of physical RAM + } + + _committedSize += sizeToCommit; + } + + var userPtr = _baseAddress + alignedOffset; + _allocatedOffset = newAllocatedOffset; + + if (option.HasFlag(AllocationOption.Clear)) + { + MemClear(userPtr, size); + } + + return userPtr; + } + + /// + /// Resets the internal offset to its initial position, keeping the committed physical memory intact for future reuse. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Reset() + { + _allocatedOffset = 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly void Free(void* ptr) + { + } + + public void Dispose() + { + if (_baseAddress != null) + { + Munmap(_baseAddress, _reserveCapacity); + + _baseAddress = null; + _reserveCapacity = 0; + _committedSize = 0; + _allocatedOffset = 0; + } + } +} diff --git a/Misaki.HighPerformance.LowLevel/Collections/HashMapHelper.cs b/Misaki.HighPerformance.LowLevel/Collections/HashMapHelper.cs index 891d9ef..e902e96 100644 --- a/Misaki.HighPerformance.LowLevel/Collections/HashMapHelper.cs +++ b/Misaki.HighPerformance.LowLevel/Collections/HashMapHelper.cs @@ -156,7 +156,7 @@ public unsafe struct HashMapHelper : IDisposable } [MethodImpl(MethodImplOptions.AggressiveInlining)] - [Conditional("ENABLE_COLLECTION_CHECKS")] + [Conditional("ENABLE_SAFETY_CHECKS")] private readonly void ThrowIfNotCreated() { if (!IsCreated) diff --git a/Misaki.HighPerformance.LowLevel/Collections/ReadOnlyUnsafeCollection.cs b/Misaki.HighPerformance.LowLevel/Collections/ReadOnlyUnsafeCollection.cs index 05098a2..b5c82eb 100644 --- a/Misaki.HighPerformance.LowLevel/Collections/ReadOnlyUnsafeCollection.cs +++ b/Misaki.HighPerformance.LowLevel/Collections/ReadOnlyUnsafeCollection.cs @@ -96,7 +96,7 @@ public readonly unsafe struct ReadOnlyUnsafeCollection : IEnumerable } [MethodImpl(MethodImplOptions.AggressiveInlining)] - [Conditional("ENABLE_COLLECTION_CHECKS")] + [Conditional("ENABLE_SAFETY_CHECKS")] private readonly void CheckIndexBounds(int index) { if (index >= _count) diff --git a/Misaki.HighPerformance.LowLevel/Collections/UnsafeArray.cs b/Misaki.HighPerformance.LowLevel/Collections/UnsafeArray.cs index 15ad8c6..e4124a6 100644 --- a/Misaki.HighPerformance.LowLevel/Collections/UnsafeArray.cs +++ b/Misaki.HighPerformance.LowLevel/Collections/UnsafeArray.cs @@ -200,7 +200,7 @@ public unsafe struct UnsafeArray : IUnsafeCollection } [MethodImpl(MethodImplOptions.AggressiveInlining)] - [Conditional("ENABLE_COLLECTION_CHECKS")] + [Conditional("ENABLE_SAFETY_CHECKS")] private readonly void ThrowIfNotCreated() { if (!IsCreated) @@ -210,7 +210,7 @@ public unsafe struct UnsafeArray : IUnsafeCollection } [MethodImpl(MethodImplOptions.AggressiveInlining)] - [Conditional("ENABLE_COLLECTION_CHECKS")] + [Conditional("ENABLE_SAFETY_CHECKS")] private readonly void CheckIndexBounds(int index) { ThrowIfNotCreated(); diff --git a/Misaki.HighPerformance.LowLevel/Collections/UnsafeList.cs b/Misaki.HighPerformance.LowLevel/Collections/UnsafeList.cs index 8445031..69c8ff2 100644 --- a/Misaki.HighPerformance.LowLevel/Collections/UnsafeList.cs +++ b/Misaki.HighPerformance.LowLevel/Collections/UnsafeList.cs @@ -207,14 +207,14 @@ public unsafe struct UnsafeList : IUnsafeCollection } [MethodImpl(MethodImplOptions.AggressiveInlining)] - [Conditional("ENABLE_COLLECTION_CHECKS")] + [Conditional("ENABLE_SAFETY_CHECKS")] private readonly void CheckNoResizeCapacity(int count) { CheckNoResizeCapacity(count, Count); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - [Conditional("ENABLE_COLLECTION_CHECKS")] + [Conditional("ENABLE_SAFETY_CHECKS")] private readonly void CheckNoResizeCapacity(int index, int count) { if (index + count > Capacity) diff --git a/Misaki.HighPerformance.LowLevel/Misaki.HighPerformance.LowLevel.csproj b/Misaki.HighPerformance.LowLevel/Misaki.HighPerformance.LowLevel.csproj index e8a107b..5375c33 100644 --- a/Misaki.HighPerformance.LowLevel/Misaki.HighPerformance.LowLevel.csproj +++ b/Misaki.HighPerformance.LowLevel/Misaki.HighPerformance.LowLevel.csproj @@ -7,7 +7,7 @@ true true Misaki - 1.5.1 + 1.5.2 $(AssemblyVersion) https://git.personalnas.com/Misaki/Misaki.HighPerformance.git https://git.personalnas.com/Misaki/Misaki.HighPerformance.git diff --git a/Misaki.HighPerformance.LowLevel/README.md b/Misaki.HighPerformance.LowLevel/README.md new file mode 100644 index 0000000..918e91c --- /dev/null +++ b/Misaki.HighPerformance.LowLevel/README.md @@ -0,0 +1,56 @@ +# Misaki.HighPerformance.LowLevel + +Unsafe collections, allocators, and memory-management primitives for high-performance C#. + +This package is the lowest-level layer in the solution. It is intended for code that needs explicit control over allocation, layout, and ownership. + +## What it includes + +- unsafe arrays, lists, queues, stacks, hash maps, hash sets, sparse sets, and slot maps +- arenas and allocation helpers +- fixed-size text and string primitives +- memory and unsafe utilities +- pointer wrappers and function pointers +- low-level buffer and lifetime management types + +## Highlights + +- explicit allocation control +- cache-friendly and allocation-aware data structures +- APIs suited for systems programming, jobs, and custom runtime components +- designed to work well with unsafe and AOT-friendly code paths + +## Main types + +- `UnsafeArray` +- `UnsafeList` +- `UnsafeQueue` +- `UnsafeStack` +- `UnsafeHashMap` +- `UnsafeHashSet` +- `UnsafeSparseSet` +- `UnsafeSlotMap` +- `VirtualArena` +- `DynamicArena` +- `MemoryPool` +- `AllocationManager` +- `UnsafeUtility` +- `FixedString` +- `FixedText` + +## Example + +```csharp +// The low-level layer is meant for advanced ownership and allocation scenarios. +// Prefer the higher-level packages when they already satisfy your use case. +``` + +## Package reference + +```bash +dotnet add package Misaki.HighPerformance.LowLevel +``` + +## Notes + +This project targets `net10.0`, enables unsafe code, and is packaged as content files for downstream consumption. diff --git a/Misaki.HighPerformance.Mathematics.SPMD/README.md b/Misaki.HighPerformance.Mathematics.SPMD/README.md new file mode 100644 index 0000000..227f0ec --- /dev/null +++ b/Misaki.HighPerformance.Mathematics.SPMD/README.md @@ -0,0 +1,46 @@ +# Misaki.HighPerformance.Mathematics.SPMD + +SPMD-oriented math abstractions built on top of the mathematics layer. + +This package is intended for code that wants to express vectorized work in a way that is portable across lane widths and easier to reason about than raw intrinsics alone. + +## What it includes + +- SPMD lane interfaces +- scalar and wide lane abstractions +- vector template helpers +- shuffle table generation support +- job-oriented SPMD helpers + +## Highlights + +- abstracts lane width through a common interface +- supports sequence creation, load/store, and compress-store style workflows +- built for vectorized algorithms and data-parallel execution +- useful when you need explicit lane semantics rather than ad hoc SIMD code + +## Main types + +- `ISPMD` +- `ISPMD` +- `ScalerLane` +- `WideLane` +- `IJobSPMD` +- `Vector{T}Helper` + +## Example + +```csharp +// Define an SPMD-friendly numeric lane type and use it to express data-parallel work. +// See the source templates for the current concrete lane implementations. +``` + +## Package reference + +```bash +dotnet add package Misaki.HighPerformance.Mathematics.SPMD +``` + +## Notes + +This project targets `net10.0` and depends on the mathematics project for shared numeric concepts. diff --git a/Misaki.HighPerformance.Mathematics/README.md b/Misaki.HighPerformance.Mathematics/README.md new file mode 100644 index 0000000..f5dd8cf --- /dev/null +++ b/Misaki.HighPerformance.Mathematics/README.md @@ -0,0 +1,57 @@ +# Misaki.HighPerformance.Mathematics + +Math helpers, geometry primitives, numeric types, and SIMD-friendly utilities for performance-sensitive C# code. + +This package focuses on fast scalar and vector math while keeping the API surface practical for game, simulation, rendering, and systems work. + +## What it includes + +- common math constants and helpers +- custom numeric types +- quaternion and random helpers +- geometry primitives +- SIMD-oriented vector utilities +- generated vector extensions and codegen-backed math support + +## Highlights + +- constants exposed for float and double workflows +- `System.Runtime.Intrinsics`-based helper code +- geometry types for bounds and spatial calculations +- designed to support vectorized and low-overhead numerical code + +## Main types + +- `math` +- `quaternion` +- `random` +- `svd` +- `float` +- `double` +- `int` +- `uint` +- `bool` +- `AABB` +- `OBB` +- `Plane` +- `SphereBounds` + +## Example + +```csharp +using Misaki.HighPerformance.Mathematics; + +float radians = math.radians(90f); +float degrees = math.degrees(radians); +float tau = math.TAU; +``` + +## Package reference + +```bash +dotnet add package Misaki.HighPerformance.Mathematics +``` + +## Notes + +This project targets `net10.0`, enables unsafe code, and uses generated source for parts of its vector API surface. diff --git a/Misaki.HighPerformance.Test/Program.cs b/Misaki.HighPerformance.Test/Program.cs index e78b1f3..68728a8 100644 --- a/Misaki.HighPerformance.Test/Program.cs +++ b/Misaki.HighPerformance.Test/Program.cs @@ -32,7 +32,7 @@ using Misaki.HighPerformance.LowLevel.Collections; // } //} -using var pool = new MemoryPool(new Stack.CreationOpts() { size = 1024 * 1024 }); +using var pool = new MemoryPool(new VirtualStack.CreationOpts() { reserveCapacity = 1024 * 1024 }); using var scope = pool.Allocator.CreateScope(pool.AllocationHandle); var arr = new UnsafeArray(1000, scope.AllocationHandle); diff --git a/Misaki.HighPerformance/README.md b/Misaki.HighPerformance/README.md new file mode 100644 index 0000000..8d4ef22 --- /dev/null +++ b/Misaki.HighPerformance/README.md @@ -0,0 +1,52 @@ +# Misaki.HighPerformance + +Core collection utilities and shared helpers for high-performance C# code. + +This package provides lightweight, allocation-conscious building blocks that are useful across the rest of the solution and in standalone projects. + +## What it includes + +- dynamic and reusable collection primitives +- slot maps and sparse sets +- object pooling helpers +- atomic counters +- collection utilities and shared result types + +## Highlights + +- designed for performance-sensitive code paths +- minimal abstraction over common data-structure patterns +- useful as a small runtime dependency for other packages in this solution + +## Main types + +- `DynamicArray` +- `SlotMap` +- `ConcurrentSlotMap` +- `SparseSet` +- `AtomicCounter` +- `ObjectPool` +- `Result` + +## Example + +```csharp +using Misaki.HighPerformance.Collections; + +var values = new DynamicArray(); +values.Add(10); +values.Add(20); +values.Add(30); + +Span span = values.AsSpan(); +``` + +## Package reference + +```bash +dotnet add package Misaki.HighPerformance +``` + +## Notes + +This project targets `net10.0` and enables unsafe code where needed by the broader solution. diff --git a/Misaki.HighPerformance/Result.cs b/Misaki.HighPerformance/Result.cs deleted file mode 100644 index ba25d1d..0000000 --- a/Misaki.HighPerformance/Result.cs +++ /dev/null @@ -1,398 +0,0 @@ -using System.Runtime.CompilerServices; - -namespace Misaki.HighPerformance; - -public readonly struct Result -{ - private readonly string? _message; - private readonly bool _isSuccess; - - public readonly string? Message => _message; - public readonly bool IsSuccess => _isSuccess; - public readonly bool IsFailure => !IsSuccess; - - public Result(bool success, string? message = null) - { - _isSuccess = success; - _message = message; - } - - public static Result Success() - { - return new Result(true); - } - - public static Result Failure(string? message = null) - { - return new Result(false, message); - } - - public static Result Failure(Error status) - { - return new Result(false, status.ToString()); - } - - public static Result Success(T value) - { - return Result.Success(value); - } - - public static Result Failure(string? message = null) - { - return Result.Failure(message); - } - - public static Result Failure(Error status) - { - return Result.Failure(status.ToString()); - } - - public void Deconstruct(out bool success, out string? message) - { - success = IsSuccess; - message = Message; - } - - public override string ToString() => IsSuccess ? "OK" : $"Error: {Message}"; - - public static implicit operator bool(Result result) => result.IsSuccess; -} - -public readonly struct Result -{ - private readonly T _value; - private readonly string? _message; - private readonly bool _isSuccess; - - /// - /// Gets the value. Undefined if the result is a failure. - /// - public T Value - { - get - { -#if DEBUG - if (IsFailure) - { - throw new InvalidOperationException($"Cannot access Value when Result is a failure. {_message}"); - } -#endif - return _value; - } - } - - public readonly string? Message => _message; - public readonly bool IsSuccess => _isSuccess; - public readonly bool IsFailure => !IsSuccess; - - public Result(bool success, T value, string? message = null) - { - _isSuccess = success; - _value = value; - _message = message; - } - - public static Result Success(T value) - { - return new Result(true, value); - } - - public static Result Failure(string? message = null) - { - return new Result(false, default!, message); - } - - public void Deconstruct(out bool success, out T value, out string? message) - { - success = IsSuccess; - value = Value; - message = Message; - } - - public override string ToString() => IsSuccess ? $"OK: {Value}" : $"Error: {Message}"; - - public static implicit operator Result(T? data) => data is not null ? Success(data) : Failure(null); - public static implicit operator Result(Result result) => result.IsSuccess ? Success(default!) : Failure(result.Message); - public static implicit operator bool(Result result) => result.IsSuccess; -} - -public enum Error : byte -{ - None, - NotFound, - InvalidArgument, - InvalidState, - InternalError, - PermissionDenied, - NotSupported, - OutOfMemory, - Timeout, - Cancelled, - UnknownError, - - Success = None, -} - -public readonly struct Result - where E : struct, Enum -{ - private readonly T _value; - private readonly E _error; - - /// - /// Gets the value. Undefined if the result is a failure. - /// - public T Value - { - get - { -#if DEBUG - if (IsFailure) - { - throw new InvalidOperationException($"Cannot access Value when Result is a failure. Error: {_error}"); - } -#endif - return _value; - } - } - - public E Error => _error; - public bool IsSuccess => EqualityComparer.Default.Equals(_error, default); - public bool IsFailure => !IsSuccess; - - public Result(T value, E status) - { - _value = value; - _error = status; - } - - public static Result Success(T value) - { - return new Result(value, default); - } - - public static Result Failure(E status) - { - return new Result(default!, status); - } - - public void Deconstruct(out T value, out E status) - { - value = Value; - status = Error; - } - - public override string ToString() => $"Value: {_value}, Status: {_error}"; - - public static implicit operator Result(T data) => new(data, default); - public static implicit operator Result(E status) => new(default!, status); - public static implicit operator bool(Result result) => result.IsSuccess; -} - -public readonly ref struct RefResult - where E : struct, Enum -{ - private readonly ref T _value; - private readonly E _error; - - /// - /// Gets a reference to the value. Undefined if the result is a failure. - /// - public ref T Value - { - get - { -#if DEBUG - if (IsFailure) - { - throw new InvalidOperationException($"Cannot access Value when Result is a failure. Error: {_error}"); - } -#endif - return ref _value; - } - } - - public E Error => _error; - public bool IsSuccess => EqualityComparer.Default.Equals(_error, default); - public bool IsFailure => !IsSuccess; - - public RefResult(ref T value, E error) - { - _value = ref value; - _error = error; - } - - public static RefResult Success(ref T value) - { - return new RefResult(ref value, default); - } - - public static RefResult Failure(E error) - { - return new RefResult(ref Unsafe.NullRef(), error); - } - - public override string ToString() => $"Value: {_value}, Status: {_error}"; - - public static implicit operator RefResult(E error) => new(ref Unsafe.NullRef(), error); - public static implicit operator bool(RefResult result) => result.IsSuccess; -} - -public static class ResultExtensions -{ - public static void ThrowIfFailed(this Error result, [CallerArgumentExpression(nameof(result))] string? op = null) - { - if (result != Error.None) - { - throw new InvalidOperationException($"{op} failed: {result}"); - } - } - - public static void ThrowIfFailed(this Result result, [CallerArgumentExpression(nameof(result))] string? op = null) - { - if (!result.IsSuccess) - { - throw new InvalidOperationException($"{op} failed: {result.Message}"); - } - } - - public static T GetValueOrThrow(this Result result, [CallerArgumentExpression(nameof(result))] string? op = null) - { - if (!result.IsSuccess) - { - throw new InvalidOperationException($"{op} failed: {result.Message}"); - } - - return result.Value; - } - - public static T GetValueOrThrow(this Result result, [CallerArgumentExpression(nameof(result))] string? op = null) - where S : struct, Enum - { - if (!result.IsSuccess) - { - throw new InvalidOperationException($"{op} failed: status {result.Error}"); - } - - return result.Value; - } - - public static T? GetValueOrDefault(this Result result, T? defaultValue = default) - { - return result.IsSuccess ? result.Value : defaultValue; - } - - public static T? GetValueOrDefault(this Result result, T? defaultValue = default) - where S : struct, Enum - { - return result.IsSuccess ? result.Value : defaultValue; - } - - public static bool TryGetValue(this Result result, out T value) - { - if (result.IsSuccess) - { - value = result.Value; - return true; - } - - value = default!; - return false; - } - - public static bool TryGetValue(this Result result, out T value) - where S : struct, Enum - { - if (result.IsSuccess) - { - value = result.Value; - return true; - } - - value = default!; - return false; - } - - public static Result OnSuccess(this Result result, Action action) - { - if (result.IsSuccess) - { - action(); - } - - return result; - } - - public static Result OnSuccess(this Result result, Action action) - { - if (result.IsSuccess) - { - action(result.Value); - } - - return result; - } - - public static Result OnSuccess(this Result result, Action action) - where E : struct, Enum - { - if (result.IsSuccess) - { - action(result.Value); - } - - return result; - } - - public static Result OnFailed(this Result result, Action action) - { - if (result.IsFailure) - { - action(result.Message); - } - - return result; - } - - public static Result OnFailed(this Result result, Action action) - { - if (result.IsFailure) - { - action(result.Message); - } - - return result; - } - - public static Result OnFailed(this Result result, Action action) - where E : struct, Enum - { - if (result.IsFailure) - { - action(result.Error); - } - - return result; - } - - public static Result Then(this Result result, Func> func) - { - if (result.IsFailure) - { - return Result.Failure(result.Message); - } - - return func(result.Value); - } - - public static Result Then(this Result result, Func> func) - where E : struct, Enum - { - if (result.IsFailure) - { - return Result.Failure(result.Error); - } - - return func(result.Value); - } -} diff --git a/README.md b/README.md new file mode 100644 index 0000000..c96bb8c --- /dev/null +++ b/README.md @@ -0,0 +1,155 @@ +# Misaki.HighPerformance + +A collection of high-performance C# libraries focused on low allocation, low overhead, and systems-level control. + +The solution is organized around a few core goals: + +- fast collections and reusable memory primitives +- unsafe and low-level building blocks when performance matters +- SIMD/SPMD-friendly mathematics +- a zero-allocation job system +- STB-based image loading and translation helpers +- analyzers and code fixes that help avoid accidental performance regressions + +Most runtime projects target **.NET 10** and enable **unsafe** code where needed. Analyzer packages target **.NET Standard 2.0**. + +## Packages + +Each major package has its own README for project-specific guidance. + +| Project | Purpose | +| --- | --- | +| [`Misaki.HighPerformance`](Misaki.HighPerformance/README.md) | Core collection utilities, pools, slot maps, sparse sets, and shared helpers. | +| [`Misaki.HighPerformance.LowLevel`](Misaki.HighPerformance.LowLevel/README.md) | Unsafe collections, allocators, arenas, fixed strings, memory utilities, and other low-level primitives. | +| [`Misaki.HighPerformance.Mathematics`](Misaki.HighPerformance.Mathematics/README.md) | Math helpers, geometry types, SIMD-friendly helpers, numeric constants, and vector operations. | +| [`Misaki.HighPerformance.Mathematics.SPMD`](Misaki.HighPerformance.Mathematics.SPMD/README.md) | SPMD-oriented math helpers, lane abstractions, and vector template infrastructure. | +| [`Misaki.HighPerformance.Jobs`](Misaki.HighPerformance.Jobs/README.md) | Job scheduling, worker threads, handles, dependency management, and temporary job allocation. | +| [`Misaki.HighPerformance.Image`](Misaki.HighPerformance.Image/README.md) | STB image decoding/translation utilities, image metadata, animated image support, and runtime helpers. | + +## Highlights + +### Low-level collections and memory management + +The low-level layer includes building blocks such as: + +- `UnsafeArray` +- `UnsafeList` +- `UnsafeQueue` +- `UnsafeStack` +- `UnsafeHashMap` +- `UnsafeHashSet` +- `UnsafeSparseSet` +- `UnsafeSlotMap` +- `MemoryPool` +- `VirtualArena` +- `DynamicArena` +- `AllocationManager` +- `FreeList` +- `FixedString` and `FixedText` + +These types are designed for scenarios where allocation patterns, data locality, and explicit lifetime management matter. + +### Mathematics for performance-sensitive code + +The mathematics layer provides: + +- `math` constants and helpers +- custom numeric types and vector extensions +- geometry primitives such as `AABB`, `OBB`, `Plane`, and `SphereBounds` +- SIMD-friendly operations through `System.Runtime.Intrinsics` + +### Job system + +The job system is designed for work scheduling with minimal overhead and dependency tracking. +It supports: + +- single jobs +- parallel-for style jobs +- parallel jobs with batching +- job dependencies +- worker threads +- temporary frame-based allocation + +### Image loading + +The image package wraps STB-based decoding workflows and includes support for image metadata, animated frames, and runtime statistics. + +## Getting started + +### Install the packages + +```bash +dotnet add package Misaki.HighPerformance +dotnet add package Misaki.HighPerformance.LowLevel +dotnet add package Misaki.HighPerformance.Mathematics +dotnet add package Misaki.HighPerformance.Jobs +dotnet add package Misaki.HighPerformance.Image +``` + +### Reference only what you need + +The solution is split into smaller packages so you can bring in only the pieces required by your application. +For example: + +- use `Misaki.HighPerformance` for lightweight collection utilities +- add `Misaki.HighPerformance.LowLevel` when you need unsafe memory primitives +- use `Misaki.HighPerformance.Mathematics` for fast numeric and geometry work +- use `Misaki.HighPerformance.Jobs` for background work scheduling +- use `Misaki.HighPerformance.Image` for image decoding + +## Quick examples + +### Dynamic array + +```csharp +using Misaki.HighPerformance.Collections; + +var values = new DynamicArray(); +values.Add(10); +values.Add(20); + +Span span = values.AsSpan(); +``` + +### Math helpers + +```csharp +using Misaki.HighPerformance.Mathematics; + +float angle = math.radians(90f); +float tau = math.TAU; +``` + +### Low-level memory usage + +The low-level project is intended for advanced scenarios where explicit allocation and lifetime control are required. +Prefer the safer higher-level projects when they already meet your needs. + +## Building the solution + +Open the solution in Visual Studio or build from the command line: + +```bash +dotnet build +``` + +Some projects use unsafe code and source generation. Make sure you are building with a compatible .NET SDK and that your environment allows those features. + +## Design goals + +- keep allocations under control +- favor cache-friendly data structures +- expose low-level primitives without unnecessary abstraction +- support high-performance math and job execution paths +- provide analyzers to help keep code fast + +## Repository notes + +- Most runtime projects target `net10.0` +- Analyzer projects target `netstandard2.0` +- Several projects enable packaging on build +- Unsafe code is used intentionally in the low-level, math, job, and image layers + +## License + +See the repository license if one is provided.