feat(lowlevel): add VirtualStack, update allocators, docs

Introduce VirtualStack allocator, refactor memory management to use virtual memory stacks, and update documentation.

Added VirtualStack as a new stack allocator using virtual memory, replaced Stack with VirtualStack in allocation manager and related APIs, and updated TempJobAllocator to use VirtualArena. Introduced AllocationManagerInitOpts for allocator configuration. Replaced ENABLE_COLLECTION_CHECKS with ENABLE_SAFETY_CHECKS for safety checks. Removed Result.cs and updated project files and examples. Added comprehensive README files for all major packages and improved root documentation.

BREAKING CHANGE: Stack allocator replaced by VirtualStack; TempJobAllocator and AllocationManager initialization signatures changed; Result types removed.
This commit is contained in:
2026-03-19 15:38:23 +09:00
parent faf87953a3
commit 69b054e81d
24 changed files with 798 additions and 542 deletions

View File

@@ -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<byte> 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.

View File

@@ -220,10 +220,10 @@ public unsafe partial class JobScheduler
/// </remarks> /// </remarks>
public static AllocationHandle TempAllocatorHandle => pTempAllocator->Handle; public static AllocationHandle TempAllocatorHandle => pTempAllocator->Handle;
public static void InitTempAllocator() public static void InitTempAllocator(nuint capacityPerFrame)
{ {
pTempAllocator = (TempJobAllocator*)MemoryUtility.Malloc((nuint)sizeof(TempJobAllocator)); pTempAllocator = (TempJobAllocator*)MemoryUtility.Malloc((nuint)sizeof(TempJobAllocator));
pTempAllocator->Init(); pTempAllocator->Initialize(capacityPerFrame);
} }
public static void ReleaseTempAllocator() public static void ReleaseTempAllocator()

View File

@@ -6,7 +6,7 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks> <AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild> <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<AssemblyVersion>1.5.2</AssemblyVersion> <AssemblyVersion>1.5.3</AssemblyVersion>
<Version>$(AssemblyVersion)</Version> <Version>$(AssemblyVersion)</Version>
<Authors>Misaki</Authors> <Authors>Misaki</Authors>
<PackageProjectUrl>https://git.personalnas.com/Misaki/Misaki.HighPerformance.git</PackageProjectUrl> <PackageProjectUrl>https://git.personalnas.com/Misaki/Misaki.HighPerformance.git</PackageProjectUrl>

View File

@@ -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.

View File

@@ -7,10 +7,9 @@ namespace Misaki.HighPerformance.Jobs;
public unsafe struct TempJobAllocator : IAllocator, IDisposable public unsafe struct TempJobAllocator : IAllocator, IDisposable
{ {
private const int _FRAME_LATENCY = 4; private const int _FRAME_LATENCY = 4;
private const uint _ARENA_SIZE = 1024 * 1024; // 1 MB
private const int _MAGIC_ID = -559038737; private const int _MAGIC_ID = -559038737;
private DynamicArena* _pArena; private VirtualArena* _pArena;
private int _currentFrameCount; private int _currentFrameCount;
private int _currentFrameIndex; private int _currentFrameIndex;
private fixed int _allocationsPerFrame[_FRAME_LATENCY]; private fixed int _allocationsPerFrame[_FRAME_LATENCY];
@@ -20,18 +19,18 @@ public unsafe struct TempJobAllocator : IAllocator, IDisposable
public readonly AllocationHandle Handle => _handle; public readonly AllocationHandle Handle => _handle;
internal void Init() public void Initialize(nuint capacity)
{ {
var memoryHandle = default(MemoryHandle); var memoryHandle = default(MemoryHandle);
_pArena = (DynamicArena*)AllocationManager.HeapAlloc((nuint)(sizeof(DynamicArena) * _FRAME_LATENCY), MemoryUtility.AlignOf<DynamicArena>(), AllocationOption.Clear, &memoryHandle); _pArena = (VirtualArena*)AllocationManager.HeapAlloc((nuint)(sizeof(VirtualArena) * _FRAME_LATENCY), MemoryUtility.AlignOf<VirtualArena>(), AllocationOption.Clear, &memoryHandle);
_currentFrameCount = 0; _currentFrameCount = 0;
_currentFrameIndex = 0; _currentFrameIndex = 0;
_memoryHandle = memoryHandle; _memoryHandle = memoryHandle;
for (int i = 0; i < _FRAME_LATENCY; i++) for (int i = 0; i < _FRAME_LATENCY; i++)
{ {
_pArena[i].Initialize(_ARENA_SIZE); _pArena[i] = new VirtualArena(capacity);
_allocationsPerFrame[i] = 0; _allocationsPerFrame[i] = 0;
} }

View File

@@ -1,6 +1,9 @@
using Misaki.HighPerformance.Collections; using Misaki.HighPerformance.Collections;
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
#if ENABLE_DEBUG_LAYER
using System.Runtime.InteropServices;
#endif
namespace Misaki.HighPerformance.LowLevel.Buffer; 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;
}
}
/// <summary> /// <summary>
/// Provides memory allocation management for native memory allocations, with support for tracking, /// Provides memory allocation management for native memory allocations, with support for tracking,
/// debugging, and custom allocation strategies. /// debugging, and custom allocation strategies.
@@ -181,8 +202,13 @@ public static unsafe class AllocationManager
{ {
private const int _STACK_MAGIC_ID = -6843541; 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] [ThreadStatic]
private static Stack s_stack; private static VirtualStack s_stack;
private AllocationHandle _handle; private AllocationHandle _handle;
public readonly AllocationHandle Handle => _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) private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption, MemoryHandle* pHandle)
{ {
EnsureInitialize();
var ptr = s_stack.Allocate(size, alignment, allocationOption); var ptr = s_stack.Allocate(size, alignment, allocationOption);
if (ptr == null) if (ptr == null)
{ {
@@ -219,6 +286,8 @@ public static unsafe class AllocationManager
return Allocate(instance, newSize, alignment, allocationOption, pHandle); return Allocate(instance, newSize, alignment, allocationOption, pHandle);
} }
EnsureInitialize();
// Optimize for last allocation. Set offset directly. // Optimize for last allocation. Set offset directly.
var oldBase = s_stack.Buffer + s_stack.Offset - oldSize; var oldBase = s_stack.Buffer + s_stack.Offset - oldSize;
if (ptr == oldBase) if (ptr == oldBase)
@@ -254,14 +323,23 @@ public static unsafe class AllocationManager
return handle.id == _STACK_MAGIC_ID && handle.generation <= (int)s_stack.Offset; 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); return s_stack.CreateScope(pSelf->_handle);
} }
public readonly void Dispose() 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 StackAllocator* s_pStackAllocator;
private static FreeListAllocator* s_pFreeListAllocator; private static FreeListAllocator* s_pFreeListAllocator;
private static bool s_initialized;
#if ENABLE_DEBUG_LAYER #if ENABLE_DEBUG_LAYER
private static SpinLock s_liveLock; private static SpinLock s_liveLock;
private static AllocationHeader* s_pLiveHead; 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); public static readonly MemoryHandle MagicHandle = new MemoryHandle(int.MinValue, int.MinValue);
private static bool s_initialized;
internal static nuint s_threadLocalStackDefaultSize;
/// <summary> /// <summary>
/// Gets the number of live persistent heap allocations when the debug layer is disabled. /// Gets the number of live tracked heap allocations.
/// </summary> /// </summary>
public static int LiveAllocationCount => s_allocations.Count; public static int LiveAllocationCount => s_allocations.Count;
public static void Initialize(nuint arenaCapacity, int freeListConcurrencyLevel) public static void Initialize(AllocationManagerInitOpts opts)
{ {
if (s_initialized) if (s_initialized)
{ {
@@ -382,10 +462,12 @@ public static unsafe class AllocationManager
s_pStackAllocator = (StackAllocator*)(ptr + sizeof(ArenaAllocator) + sizeof(HeapAllocator)); s_pStackAllocator = (StackAllocator*)(ptr + sizeof(ArenaAllocator) + sizeof(HeapAllocator));
s_pFreeListAllocator = (FreeListAllocator*)(ptr + sizeof(ArenaAllocator) + sizeof(HeapAllocator) + sizeof(StackAllocator)); s_pFreeListAllocator = (FreeListAllocator*)(ptr + sizeof(ArenaAllocator) + sizeof(HeapAllocator) + sizeof(StackAllocator));
s_pArenaAllocator->Init(arenaCapacity); s_pArenaAllocator->Init(opts.ArenaCapacity);
s_pHeapAllocator->Init(); s_pHeapAllocator->Init();
s_pStackAllocator->Init(); s_pStackAllocator->Init();
s_pFreeListAllocator->Init(freeListConcurrencyLevel); s_pFreeListAllocator->Init(opts.FreeListConcurrencyLevel);
s_threadLocalStackDefaultSize = opts.StackCapacity;
s_initialized = true; s_initialized = true;
} }
@@ -652,7 +734,7 @@ public static unsafe class AllocationManager
/// </summary> /// </summary>
/// <returns>A <see cref="Stack.Scope"/> instance representing the newly created stack scope. The scope must be disposed when no longer needed to release allocated resources.</returns> /// <returns>A <see cref="Stack.Scope"/> instance representing the newly created stack scope. The scope must be disposed when no longer needed to release allocated resources.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Stack.Scope CreateStackScope() public static VirtualStack.Scope CreateStackScope()
{ {
Debug.Assert(s_initialized, "AllocationManager is not initialized."); Debug.Assert(s_initialized, "AllocationManager is not initialized.");
return StackAllocator.CreateScope(s_pStackAllocator); return StackAllocator.CreateScope(s_pStackAllocator);

View File

@@ -7,14 +7,14 @@ namespace Misaki.HighPerformance.LowLevel.Buffer;
/// A memory management structure that allocates and resets memory blocks with specified alignment. /// A memory management structure that allocates and resets memory blocks with specified alignment.
/// </summary> /// </summary>
[StructLayout(LayoutKind.Explicit, Size = 64)] // Cache line aligned to prevent false sharing [StructLayout(LayoutKind.Explicit, Size = 64)] // Cache line aligned to prevent false sharing
public unsafe struct Arena : IMemoryAllocator<Arena, Arena.CreateOptions> public unsafe struct Arena : IMemoryAllocator<Arena, Arena.CreationOptions>
{ {
public struct CreateOptions public struct CreationOptions
{ {
public nuint size; public nuint size;
} }
public static Arena Create(in CreateOptions opts) public static Arena Create(in CreationOptions opts)
{ {
return new Arena(opts.size); return new Arena(opts.size);
} }

View File

@@ -1,3 +1,4 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace Misaki.HighPerformance.LowLevel.Buffer; namespace Misaki.HighPerformance.LowLevel.Buffer;
@@ -30,27 +31,17 @@ public unsafe struct DynamicArena : IMemoryAllocator<DynamicArena, DynamicArena.
[FieldOffset(8)] [FieldOffset(8)]
private ArenaNode* _current; private ArenaNode* _current;
[FieldOffset(16)] [FieldOffset(16)]
private uint _initialSize; private readonly nuint _initialSize;
[FieldOffset(20)] [FieldOffset(24)]
private volatile int _nodeCreationLock; private volatile int _nodeCreationLock;
/// <summary> /// <summary>
/// Initializes a new instance of DynamicArena with the specified initial size. /// Initializes a new instance of DynamicArena with the specified initial size.
/// </summary> /// </summary>
/// <param name="initialSize">The initial size in bytes for the first arena block.</param> /// <param name="initialSize">The initial size in bytes for the first arena block.</param>
public DynamicArena(uint initialSize) public DynamicArena(nuint initialSize)
{ {
Initialize(initialSize);
}
public void Initialize(uint initialSize)
{
if (_root != null)
{
return;
}
_initialSize = initialSize; _initialSize = initialSize;
_root = (ArenaNode*)Malloc(SizeOf<ArenaNode>()); _root = (ArenaNode*)Malloc(SizeOf<ArenaNode>());
_root->arena = new Arena(initialSize); _root->arena = new Arena(initialSize);
@@ -143,6 +134,7 @@ public unsafe struct DynamicArena : IMemoryAllocator<DynamicArena, DynamicArena.
return result; return result;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void Free(void* ptr) public readonly void Free(void* ptr)
{ {
} }

View File

@@ -38,8 +38,7 @@ public unsafe struct MemoryPool<T, TOpts> : IDisposable
return Allocate(pAllocator, newSize, alignment, allocationOption, pHandle); return Allocate(pAllocator, newSize, alignment, allocationOption, pHandle);
} }
MemoryHandle newHandle; var newPtr = Allocate(pAllocator, newSize, alignment, allocationOption, pHandle);
var newPtr = Allocate(pAllocator, newSize, alignment, allocationOption, &newHandle);
if (newPtr == null) if (newPtr == null)
{ {
return null; return null;
@@ -48,7 +47,6 @@ public unsafe struct MemoryPool<T, TOpts> : IDisposable
MemCpy(newPtr, ptr, Math.Min(oldSize, newSize)); MemCpy(newPtr, ptr, Math.Min(oldSize, newSize));
Free(pAllocator, ptr, *pHandle); Free(pAllocator, ptr, *pHandle);
*pHandle = newHandle;
return newPtr; return newPtr;
} }

View File

@@ -1,29 +1,8 @@
using Misaki.HighPerformance.LowLevel.Utilities; using System.Diagnostics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
namespace Misaki.HighPerformance.LowLevel.Buffer; 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]);
}
}
}
/// <summary> /// <summary>
/// Provides a stack-based memory allocator for unmanaged memory, enabling fast allocation and deallocation of memory /// Provides a stack-based memory allocator for unmanaged memory, enabling fast allocation and deallocation of memory
/// blocks within a preallocated buffer. /// blocks within a preallocated buffer.
@@ -41,8 +20,6 @@ public unsafe partial struct Stack : IMemoryAllocator<Stack, Stack.CreationOpts>
return new Stack(opts.size); return new Stack(opts.size);
} }
private const nuint _DEFAULT_SIZE = 1024 * 1024; // 1MB
public readonly ref struct Scope : IDisposable public readonly ref struct Scope : IDisposable
{ {
private readonly Stack* _allocator; private readonly Stack* _allocator;
@@ -56,7 +33,9 @@ public unsafe partial struct Stack : IMemoryAllocator<Stack, Stack.CreationOpts>
_allocator = allocator; _allocator = allocator;
_handle = handle; _handle = handle;
_originalOffset = allocator->_offset; _originalOffset = allocator->_offset;
#if ENABLE_SAFETY_CHECKS
_allocator->_activeScopeCount++; _allocator->_activeScopeCount++;
#endif
} }
public void Dispose() public void Dispose()
@@ -64,7 +43,9 @@ public unsafe partial struct Stack : IMemoryAllocator<Stack, Stack.CreationOpts>
if (_allocator != null) if (_allocator != null)
{ {
_allocator->_offset = _allocator->_offset > _originalOffset ? _originalOffset : _allocator->_offset; _allocator->_offset = _allocator->_offset > _originalOffset ? _originalOffset : _allocator->_offset;
#if ENABLE_SAFETY_CHECKS
_allocator->_activeScopeCount--; _allocator->_activeScopeCount--;
#endif
} }
} }
} }
@@ -72,14 +53,15 @@ public unsafe partial struct Stack : IMemoryAllocator<Stack, Stack.CreationOpts>
private byte* _buffer; private byte* _buffer;
private nuint _size; private nuint _size;
private nuint _offset; private nuint _offset;
#if ENABLE_SAFETY_CHECKS
private uint _activeScopeCount; private uint _activeScopeCount;
#endif
internal readonly byte* Buffer => _buffer; internal readonly byte* Buffer => _buffer;
internal nuint Offset
public nuint Offset
{ {
readonly get => _offset; readonly get => _offset;
internal set => _offset = value; set => _offset = value;
} }
/// <summary> /// <summary>
@@ -87,81 +69,15 @@ public unsafe partial struct Stack : IMemoryAllocator<Stack, Stack.CreationOpts>
/// </summary> /// </summary>
/// <param name="size">The size, in bytes, of the memory buffer to allocate for stack-based allocations. Must be greater than zero.</param> /// <param name="size">The size, in bytes, of the memory buffer to allocate for stack-based allocations. Must be greater than zero.</param>
public Stack(nuint size) 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); ArgumentOutOfRangeException.ThrowIfNegative(size);
if (_buffer != null)
{
Free(_buffer);
}
_buffer = (byte*)Malloc(size); _buffer = (byte*)Malloc(size);
if (_buffer == null)
{
throw new OutOfMemoryException("Failed to allocate memory for the stack.");
}
_size = size; _size = size;
_offset = 0; _offset = 0;
#if ENABLE_SAFETY_CHECKS
_activeScopeCount = 0; _activeScopeCount = 0;
#endif
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);
}
} }
/// <summary> /// <summary>
@@ -174,7 +90,6 @@ public unsafe partial struct Stack : IMemoryAllocator<Stack, Stack.CreationOpts>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public Scope CreateScope(AllocationHandle handle) public Scope CreateScope(AllocationHandle handle)
{ {
EnsureInitialize();
return new Scope((Stack*)Unsafe.AsPointer(ref this), handle); return new Scope((Stack*)Unsafe.AsPointer(ref this), handle);
} }
@@ -189,7 +104,12 @@ public unsafe partial struct Stack : IMemoryAllocator<Stack, Stack.CreationOpts>
/// there is insufficient space in the buffer.</returns> /// there is insufficient space in the buffer.</returns>
public void* Allocate(nuint size, nuint alignment, AllocationOption allocationOption = AllocationOption.None) 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) if (size == 0)
{ {
@@ -202,7 +122,6 @@ public unsafe partial struct Stack : IMemoryAllocator<Stack, Stack.CreationOpts>
} }
var alignedOffset = (_offset + alignment - 1) & ~(alignment - 1); var alignedOffset = (_offset + alignment - 1) & ~(alignment - 1);
var newOffset = alignedOffset + size; var newOffset = alignedOffset + size;
if (newOffset > _size) if (newOffset > _size)
@@ -222,6 +141,7 @@ public unsafe partial struct Stack : IMemoryAllocator<Stack, Stack.CreationOpts>
return ptr; return ptr;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void Free(void* ptr) public readonly void Free(void* ptr)
{ {
} }
@@ -229,6 +149,7 @@ public unsafe partial struct Stack : IMemoryAllocator<Stack, Stack.CreationOpts>
/// <summary> /// <summary>
/// Resets the internal offset to its initial position. /// Resets the internal offset to its initial position.
/// </summary> /// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Reset() public void Reset()
{ {
_offset = 0; _offset = 0;

View File

@@ -6,8 +6,17 @@ namespace Misaki.HighPerformance.LowLevel.Buffer;
/// <summary> /// <summary>
/// A thread-safe memory management structure that reserves a large virtual address space and commits physical memory on demand as allocations are made. /// A thread-safe memory management structure that reserves a large virtual address space and commits physical memory on demand as allocations are made.
/// </summary> /// </summary>
public unsafe struct VirtualArena public unsafe struct VirtualArena : IMemoryAllocator<VirtualArena, VirtualArena.CreationOptions>
{ {
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 const nuint _PAGE_SIZE = 64 * 1024;
private byte* _baseAddress; private byte* _baseAddress;
@@ -95,6 +104,11 @@ public unsafe struct VirtualArena
return ptr; return ptr;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void Free(void* ptr)
{
}
/// <summary> /// <summary>
/// Resets the arena. /// Resets the arena.
/// </summary> /// </summary>

View File

@@ -0,0 +1,180 @@
using Misaki.HighPerformance.LowLevel.Utilities;
using System.Runtime.CompilerServices;
namespace Misaki.HighPerformance.LowLevel.Buffer;
public unsafe struct VirtualStack : IMemoryAllocator<VirtualStack, VirtualStack.CreationOpts>
{
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
}
/// <summary>
/// Creates a new scope instance associated with the current stack context.
/// </summary>
/// <remarks>
/// The instance of <see cref="VirtualStack"/> must be pinned or allocated on the native heap to ensure that the pointer remains valid for the lifetime of the scope.
/// </remarks>
/// <returns>A <see cref="Scope"/> object that represents a scope tied to this stack.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Scope CreateScope(AllocationHandle handle)
{
return new Scope((VirtualStack*)Unsafe.AsPointer(ref this), handle);
}
/// <summary>
/// Allocates a block of memory of the specified size and alignment.
/// </summary>
/// <remarks>
/// This is not thread-safe. It is designed for single-threaded or thread-local contexts.
/// </remarks>
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;
}
/// <summary>
/// Resets the internal offset to its initial position, keeping the committed physical memory intact for future reuse.
/// </summary>
[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;
}
}
}

View File

@@ -156,7 +156,7 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
[Conditional("ENABLE_COLLECTION_CHECKS")] [Conditional("ENABLE_SAFETY_CHECKS")]
private readonly void ThrowIfNotCreated() private readonly void ThrowIfNotCreated()
{ {
if (!IsCreated) if (!IsCreated)

View File

@@ -96,7 +96,7 @@ public readonly unsafe struct ReadOnlyUnsafeCollection<T> : IEnumerable<T>
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
[Conditional("ENABLE_COLLECTION_CHECKS")] [Conditional("ENABLE_SAFETY_CHECKS")]
private readonly void CheckIndexBounds(int index) private readonly void CheckIndexBounds(int index)
{ {
if (index >= _count) if (index >= _count)

View File

@@ -200,7 +200,7 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
[Conditional("ENABLE_COLLECTION_CHECKS")] [Conditional("ENABLE_SAFETY_CHECKS")]
private readonly void ThrowIfNotCreated() private readonly void ThrowIfNotCreated()
{ {
if (!IsCreated) if (!IsCreated)
@@ -210,7 +210,7 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
[Conditional("ENABLE_COLLECTION_CHECKS")] [Conditional("ENABLE_SAFETY_CHECKS")]
private readonly void CheckIndexBounds(int index) private readonly void CheckIndexBounds(int index)
{ {
ThrowIfNotCreated(); ThrowIfNotCreated();

View File

@@ -207,14 +207,14 @@ public unsafe struct UnsafeList<T> : IUnsafeCollection<T>
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
[Conditional("ENABLE_COLLECTION_CHECKS")] [Conditional("ENABLE_SAFETY_CHECKS")]
private readonly void CheckNoResizeCapacity(int count) private readonly void CheckNoResizeCapacity(int count)
{ {
CheckNoResizeCapacity(count, Count); CheckNoResizeCapacity(count, Count);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
[Conditional("ENABLE_COLLECTION_CHECKS")] [Conditional("ENABLE_SAFETY_CHECKS")]
private readonly void CheckNoResizeCapacity(int index, int count) private readonly void CheckNoResizeCapacity(int index, int count)
{ {
if (index + count > Capacity) if (index + count > Capacity)

View File

@@ -7,7 +7,7 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild> <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Authors>Misaki</Authors> <Authors>Misaki</Authors>
<AssemblyVersion>1.5.1</AssemblyVersion> <AssemblyVersion>1.5.2</AssemblyVersion>
<Version>$(AssemblyVersion)</Version> <Version>$(AssemblyVersion)</Version>
<PackageProjectUrl>https://git.personalnas.com/Misaki/Misaki.HighPerformance.git</PackageProjectUrl> <PackageProjectUrl>https://git.personalnas.com/Misaki/Misaki.HighPerformance.git</PackageProjectUrl>
<RepositoryUrl>https://git.personalnas.com/Misaki/Misaki.HighPerformance.git</RepositoryUrl> <RepositoryUrl>https://git.personalnas.com/Misaki/Misaki.HighPerformance.git</RepositoryUrl>

View File

@@ -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<T>`
- `UnsafeList<T>`
- `UnsafeQueue<T>`
- `UnsafeStack<T>`
- `UnsafeHashMap<TKey, TValue>`
- `UnsafeHashSet<T>`
- `UnsafeSparseSet<T>`
- `UnsafeSlotMap<T>`
- `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.

View File

@@ -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<TSelf, TNumber>`
- `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.

View File

@@ -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.

View File

@@ -32,7 +32,7 @@ using Misaki.HighPerformance.LowLevel.Collections;
// } // }
//} //}
using var pool = new MemoryPool<Stack, Stack.CreationOpts>(new Stack.CreationOpts() { size = 1024 * 1024 }); using var pool = new MemoryPool<VirtualStack, VirtualStack.CreationOpts>(new VirtualStack.CreationOpts() { reserveCapacity = 1024 * 1024 });
using var scope = pool.Allocator.CreateScope(pool.AllocationHandle); using var scope = pool.Allocator.CreateScope(pool.AllocationHandle);
var arr = new UnsafeArray<int>(1000, scope.AllocationHandle); var arr = new UnsafeArray<int>(1000, scope.AllocationHandle);

View File

@@ -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<T>`
- `SlotMap<T>`
- `ConcurrentSlotMap<T>`
- `SparseSet<T>`
- `AtomicCounter`
- `ObjectPool<T>`
- `Result<T>`
## Example
```csharp
using Misaki.HighPerformance.Collections;
var values = new DynamicArray<int>();
values.Add(10);
values.Add(20);
values.Add(30);
Span<int> 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.

View File

@@ -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<T> Success<T>(T value)
{
return Result<T>.Success(value);
}
public static Result<T> Failure<T>(string? message = null)
{
return Result<T>.Failure(message);
}
public static Result<T> Failure<T>(Error status)
{
return Result<T>.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<T>
{
private readonly T _value;
private readonly string? _message;
private readonly bool _isSuccess;
/// <summary>
/// Gets the value. Undefined if the result is a failure.
/// </summary>
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<T> Success(T value)
{
return new Result<T>(true, value);
}
public static Result<T> Failure(string? message = null)
{
return new Result<T>(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>(T? data) => data is not null ? Success(data) : Failure(null);
public static implicit operator Result<T>(Result result) => result.IsSuccess ? Success(default!) : Failure(result.Message);
public static implicit operator bool(Result<T> result) => result.IsSuccess;
}
public enum Error : byte
{
None,
NotFound,
InvalidArgument,
InvalidState,
InternalError,
PermissionDenied,
NotSupported,
OutOfMemory,
Timeout,
Cancelled,
UnknownError,
Success = None,
}
public readonly struct Result<T, E>
where E : struct, Enum
{
private readonly T _value;
private readonly E _error;
/// <summary>
/// Gets the value. Undefined if the result is a failure.
/// </summary>
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<E>.Default.Equals(_error, default);
public bool IsFailure => !IsSuccess;
public Result(T value, E status)
{
_value = value;
_error = status;
}
public static Result<T, E> Success(T value)
{
return new Result<T, E>(value, default);
}
public static Result<T, E> Failure(E status)
{
return new Result<T, E>(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, E>(T data) => new(data, default);
public static implicit operator Result<T, E>(E status) => new(default!, status);
public static implicit operator bool(Result<T, E> result) => result.IsSuccess;
}
public readonly ref struct RefResult<T, E>
where E : struct, Enum
{
private readonly ref T _value;
private readonly E _error;
/// <summary>
/// Gets a reference to the value. Undefined if the result is a failure.
/// </summary>
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<E>.Default.Equals(_error, default);
public bool IsFailure => !IsSuccess;
public RefResult(ref T value, E error)
{
_value = ref value;
_error = error;
}
public static RefResult<T, E> Success(ref T value)
{
return new RefResult<T, E>(ref value, default);
}
public static RefResult<T, E> Failure(E error)
{
return new RefResult<T, E>(ref Unsafe.NullRef<T>(), error);
}
public override string ToString() => $"Value: {_value}, Status: {_error}";
public static implicit operator RefResult<T, E>(E error) => new(ref Unsafe.NullRef<T>(), error);
public static implicit operator bool(RefResult<T, E> 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<T>(this Result<T> result, [CallerArgumentExpression(nameof(result))] string? op = null)
{
if (!result.IsSuccess)
{
throw new InvalidOperationException($"{op} failed: {result.Message}");
}
return result.Value;
}
public static T GetValueOrThrow<T, S>(this Result<T, S> 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<T>(this Result<T> result, T? defaultValue = default)
{
return result.IsSuccess ? result.Value : defaultValue;
}
public static T? GetValueOrDefault<T, S>(this Result<T, S> result, T? defaultValue = default)
where S : struct, Enum
{
return result.IsSuccess ? result.Value : defaultValue;
}
public static bool TryGetValue<T>(this Result<T> result, out T value)
{
if (result.IsSuccess)
{
value = result.Value;
return true;
}
value = default!;
return false;
}
public static bool TryGetValue<T, S>(this Result<T, S> 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<T> OnSuccess<T>(this Result<T> result, Action<T> action)
{
if (result.IsSuccess)
{
action(result.Value);
}
return result;
}
public static Result<T, E> OnSuccess<T, E>(this Result<T, E> result, Action<T> action)
where E : struct, Enum
{
if (result.IsSuccess)
{
action(result.Value);
}
return result;
}
public static Result OnFailed(this Result result, Action<string?> action)
{
if (result.IsFailure)
{
action(result.Message);
}
return result;
}
public static Result<T> OnFailed<T>(this Result<T> result, Action<string?> action)
{
if (result.IsFailure)
{
action(result.Message);
}
return result;
}
public static Result<T, E> OnFailed<T, E>(this Result<T, E> result, Action<E> action)
where E : struct, Enum
{
if (result.IsFailure)
{
action(result.Error);
}
return result;
}
public static Result<U> Then<T, U>(this Result<T> result, Func<T, Result<U>> func)
{
if (result.IsFailure)
{
return Result<U>.Failure(result.Message);
}
return func(result.Value);
}
public static Result<U, E> Then<T, U, E>(this Result<T, E> result, Func<T, Result<U, E>> func)
where E : struct, Enum
{
if (result.IsFailure)
{
return Result<U, E>.Failure(result.Error);
}
return func(result.Value);
}
}

155
README.md Normal file
View File

@@ -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<T>`
- `UnsafeList<T>`
- `UnsafeQueue<T>`
- `UnsafeStack<T>`
- `UnsafeHashMap<TKey, TValue>`
- `UnsafeHashSet<T>`
- `UnsafeSparseSet<T>`
- `UnsafeSlotMap<T>`
- `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<int>();
values.Add(10);
values.Add(20);
Span<int> 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.