Improve performance and safety
This commit is contained in:
@@ -41,16 +41,22 @@ namespace Misaki.HighPerformance.Analyzer
|
|||||||
.OfType<ExpressionSyntax>()
|
.OfType<ExpressionSyntax>()
|
||||||
.First();
|
.First();
|
||||||
|
|
||||||
// Register a code action that will invoke the fix.
|
|
||||||
context.RegisterCodeFix(
|
context.RegisterCodeFix(
|
||||||
CodeAction.Create(
|
CodeAction.Create(
|
||||||
title: "Share",
|
"Share memory",
|
||||||
createChangedDocument: c => TransferOwnershipAsync(context.Document, expressionSyntax, c),
|
c => ShareAsync(context.Document, expressionSyntax, c),
|
||||||
equivalenceKey: nameof(StructCopyCodeFixProvider)),
|
"StructCopyCodeFixProvider_ShareMemory"),
|
||||||
|
diagnostic);
|
||||||
|
|
||||||
|
context.RegisterCodeFix(
|
||||||
|
CodeAction.Create(
|
||||||
|
"Transfer ownership",
|
||||||
|
c => TransferOwnershipAsync(context.Document, expressionSyntax, c),
|
||||||
|
"StructCopyCodeFixProvider_TransferOwnership"),
|
||||||
diagnostic);
|
diagnostic);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<Document> TransferOwnershipAsync(Document document, ExpressionSyntax expressionToFix, CancellationToken cancellationToken)
|
private async Task<Document> ShareAsync(Document document, ExpressionSyntax expressionToFix, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// 1. Get the semantic model to figure out exactly what type we are dealing with
|
// 1. Get the semantic model to figure out exactly what type we are dealing with
|
||||||
var semanticModel = await document.GetSemanticModelAsync(cancellationToken);
|
var semanticModel = await document.GetSemanticModelAsync(cancellationToken);
|
||||||
@@ -70,6 +76,33 @@ namespace Misaki.HighPerformance.Analyzer
|
|||||||
|
|
||||||
var detachInvocation = SyntaxFactory.InvocationExpression(detachMethod);
|
var detachInvocation = SyntaxFactory.InvocationExpression(detachMethod);
|
||||||
|
|
||||||
|
// 4. Replace the old node with the new node in the Syntax Tree
|
||||||
|
var root = await document.GetSyntaxRootAsync(cancellationToken);
|
||||||
|
var newRoot = root.ReplaceNode(expressionToFix, detachInvocation);
|
||||||
|
|
||||||
|
return document.WithSyntaxRoot(newRoot);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<Document> TransferOwnershipAsync(Document document, ExpressionSyntax expressionToFix, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
// 1. Get the semantic model to figure out exactly what type we are dealing with
|
||||||
|
var semanticModel = await document.GetSemanticModelAsync(cancellationToken);
|
||||||
|
var typeSymbol = semanticModel.GetTypeInfo(expressionToFix).Type;
|
||||||
|
|
||||||
|
// 2. Generate the type name string (e.g., "UniquePtr<ID3D12Device>")
|
||||||
|
// We use ToMinimalDisplayString so Roslyn handles namespaces/using directives automatically.
|
||||||
|
var typeName = typeSymbol.ToMinimalDisplayString(semanticModel, expressionToFix.SpanStart);
|
||||||
|
var typeSyntax = SyntaxFactory.ParseTypeName(typeName);
|
||||||
|
|
||||||
|
// 3. Create the "Detach()" invocation: expression.Detach()
|
||||||
|
var detachMethod = SyntaxFactory.MemberAccessExpression(
|
||||||
|
SyntaxKind.SimpleMemberAccessExpression,
|
||||||
|
expressionToFix, // The 'a' in 'b = a'
|
||||||
|
SyntaxFactory.IdentifierName("Detach")
|
||||||
|
);
|
||||||
|
|
||||||
|
var detachInvocation = SyntaxFactory.InvocationExpression(detachMethod);
|
||||||
|
|
||||||
// 4. Create the "new UniquePtr<T>(...)" expression
|
// 4. Create the "new UniquePtr<T>(...)" expression
|
||||||
var newObjectCreation = SyntaxFactory.ObjectCreationExpression(typeSyntax)
|
var newObjectCreation = SyntaxFactory.ObjectCreationExpression(typeSyntax)
|
||||||
.WithArgumentList(
|
.WithArgumentList(
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
using Microsoft.CodeAnalysis.Diagnostics;
|
||||||
|
using Microsoft.CodeAnalysis.Operations;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
|
||||||
|
namespace Misaki.HighPerformance.Analyzer
|
||||||
|
{
|
||||||
|
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||||
|
public class DefensiveCopyAnalyzer : DiagnosticAnalyzer
|
||||||
|
{
|
||||||
|
public const string DIAGNOSTIC_ID = "MHP002";
|
||||||
|
private static readonly DiagnosticDescriptor s_rule = new DiagnosticDescriptor(
|
||||||
|
DIAGNOSTIC_ID,
|
||||||
|
"Defensive copy detected",
|
||||||
|
"Calling non-readonly method '{0}' on readonly field or local '{1}' causes a silent defensive copy",
|
||||||
|
"Safety",
|
||||||
|
DiagnosticSeverity.Warning,
|
||||||
|
isEnabledByDefault: true);
|
||||||
|
|
||||||
|
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(s_rule);
|
||||||
|
|
||||||
|
public override void Initialize(AnalysisContext context)
|
||||||
|
{
|
||||||
|
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
|
||||||
|
context.EnableConcurrentExecution();
|
||||||
|
context.RegisterOperationAction(AnalyzeInvocation, OperationKind.Invocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AnalyzeInvocation(OperationAnalysisContext context)
|
||||||
|
{
|
||||||
|
var invocation = (IInvocationOperation)context.Operation;
|
||||||
|
var method = invocation.TargetMethod;
|
||||||
|
var instance = invocation.Instance;
|
||||||
|
|
||||||
|
// 1. Basic Filters: Must be an instance method on a Value Type (Struct)
|
||||||
|
if (method.IsStatic || instance == null)
|
||||||
|
return;
|
||||||
|
if (!instance.Type.IsValueType)
|
||||||
|
return; // Classes don't copy
|
||||||
|
if (instance.Type.IsReadOnly)
|
||||||
|
return; // Readonly structs are safe (compiler enforced)
|
||||||
|
|
||||||
|
// 2. If the method itself is 'readonly', it promises not to mutate, so no copy needed.
|
||||||
|
if (method.IsReadOnly)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// 3. CHECK THE CONTEXT: Is the variable we are calling on "Read Only"?
|
||||||
|
if (IsReadOnlyContext(instance, out var variableName))
|
||||||
|
{
|
||||||
|
var diagnostic = Diagnostic.Create(
|
||||||
|
s_rule,
|
||||||
|
invocation.Syntax.GetLocation(),
|
||||||
|
method.Name,
|
||||||
|
variableName);
|
||||||
|
|
||||||
|
context.ReportDiagnostic(diagnostic);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsReadOnlyContext(IOperation instance, out string name)
|
||||||
|
{
|
||||||
|
name = "";
|
||||||
|
|
||||||
|
switch (instance)
|
||||||
|
{
|
||||||
|
// CASE 1: Readonly Field
|
||||||
|
case IFieldReferenceOperation fieldRef:
|
||||||
|
if (fieldRef.Field.IsReadOnly)
|
||||||
|
{
|
||||||
|
name = fieldRef.Field.Name;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
// CASE 2: Locals (ref readonly var x)
|
||||||
|
case ILocalReferenceOperation localRef:
|
||||||
|
// RefKind.In covers 'ref readonly' locals
|
||||||
|
if (localRef.Local.RefKind == RefKind.In)
|
||||||
|
{
|
||||||
|
name = localRef.Local.Name;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
// CASE 3: Parameters (in MyStruct x)
|
||||||
|
case IParameterReferenceOperation paramRef:
|
||||||
|
// RefKind.In covers 'in' parameters
|
||||||
|
if (paramRef.Parameter.RefKind == RefKind.In)
|
||||||
|
{
|
||||||
|
name = paramRef.Parameter.Name;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,12 +11,13 @@ namespace Misaki.HighPerformance.Analyzer
|
|||||||
public class StructCopyCodeAnalyzer : DiagnosticAnalyzer
|
public class StructCopyCodeAnalyzer : DiagnosticAnalyzer
|
||||||
{
|
{
|
||||||
public const string DIAGNOSTIC_ID = "MHP001";
|
public const string DIAGNOSTIC_ID = "MHP001";
|
||||||
private const string _TITLE = "Struct marked as NonCopyable was copied";
|
|
||||||
private const string _MESSAGE_FORMAT = "The struct '{0}' is designed for unique ownership and cannot be copied. Use .Detach(), .Get(), .Share() or pass by reference.";
|
|
||||||
private const string _CATEGORY = "Safety";
|
|
||||||
|
|
||||||
private static readonly DiagnosticDescriptor s_rule = new DiagnosticDescriptor(
|
private static readonly DiagnosticDescriptor s_rule = new DiagnosticDescriptor(
|
||||||
DIAGNOSTIC_ID, _TITLE, _MESSAGE_FORMAT, _CATEGORY, DiagnosticSeverity.Error, isEnabledByDefault: true);
|
DIAGNOSTIC_ID,
|
||||||
|
"Struct marked as NonCopyable was copied",
|
||||||
|
"The struct '{0}' is designed for unique ownership and cannot be copied. Use .Detach(), .Get(), .Share() or pass by reference.",
|
||||||
|
"Safety",
|
||||||
|
DiagnosticSeverity.Error,
|
||||||
|
isEnabledByDefault: true);
|
||||||
|
|
||||||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(s_rule);
|
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(s_rule);
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||||
<AssemblyVersion>1.2.2</AssemblyVersion>
|
<AssemblyVersion>1.2.3</AssemblyVersion>
|
||||||
<Version>$(AssemblyVersion)</Version>
|
<Version>$(AssemblyVersion)</Version>
|
||||||
<Authors>Misaki</Authors>
|
<Authors>Misaki</Authors>
|
||||||
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||||
|
|||||||
@@ -8,8 +8,10 @@ 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 uint _ARENA_SIZE = 1024 * 1024; // 1 MB
|
||||||
|
private const int _MAGIC_ID = -559038737;
|
||||||
|
|
||||||
private DynamicArena* _pArena;
|
private DynamicArena* _pArena;
|
||||||
|
private int _currentFrameCount;
|
||||||
private int _currentFrameIndex;
|
private int _currentFrameIndex;
|
||||||
private fixed int _allocationsPerFrame[_FRAME_LATENCY];
|
private fixed int _allocationsPerFrame[_FRAME_LATENCY];
|
||||||
|
|
||||||
@@ -23,6 +25,7 @@ public unsafe struct TempJobAllocator : IAllocator, IDisposable
|
|||||||
var memoryHandle = default(MemoryHandle);
|
var memoryHandle = default(MemoryHandle);
|
||||||
|
|
||||||
_pArena = (DynamicArena*)AllocationManager.HeapAlloc((nuint)(sizeof(DynamicArena) * _FRAME_LATENCY), MemoryUtility.AlignOf<DynamicArena>(), AllocationOption.Clear, &memoryHandle);
|
_pArena = (DynamicArena*)AllocationManager.HeapAlloc((nuint)(sizeof(DynamicArena) * _FRAME_LATENCY), MemoryUtility.AlignOf<DynamicArena>(), AllocationOption.Clear, &memoryHandle);
|
||||||
|
_currentFrameCount = 0;
|
||||||
_currentFrameIndex = 0;
|
_currentFrameIndex = 0;
|
||||||
_memoryHandle = memoryHandle;
|
_memoryHandle = memoryHandle;
|
||||||
|
|
||||||
@@ -32,13 +35,20 @@ public unsafe struct TempJobAllocator : IAllocator, IDisposable
|
|||||||
_allocationsPerFrame[i] = 0;
|
_allocationsPerFrame[i] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
_handle = new(Unsafe.AsPointer(ref this), &Allocate, &Reallocate, &Free);
|
_handle = new AllocationHandle
|
||||||
|
{
|
||||||
|
State = Unsafe.AsPointer(ref this),
|
||||||
|
Alloc = &Allocate,
|
||||||
|
Realloc = &Reallocate,
|
||||||
|
Free = &Free,
|
||||||
|
IsValid = &IsValid,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption, MemoryHandle* pHandle)
|
private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption, MemoryHandle* pHandle)
|
||||||
{
|
{
|
||||||
var selfPtr = (TempJobAllocator*)instance;
|
var pSelf = (TempJobAllocator*)instance;
|
||||||
var pCurrentArena = selfPtr->_pArena + selfPtr->_currentFrameIndex;
|
var pCurrentArena = pSelf->_pArena + pSelf->_currentFrameIndex;
|
||||||
var ptr = pCurrentArena->Allocate(size, alignment, allocationOption);
|
var ptr = pCurrentArena->Allocate(size, alignment, allocationOption);
|
||||||
if (ptr == null)
|
if (ptr == null)
|
||||||
{
|
{
|
||||||
@@ -46,8 +56,8 @@ public unsafe struct TempJobAllocator : IAllocator, IDisposable
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Interlocked.Increment(ref selfPtr->_allocationsPerFrame[selfPtr->_currentFrameIndex]);
|
Interlocked.Increment(ref pSelf->_allocationsPerFrame[pSelf->_currentFrameIndex]);
|
||||||
*pHandle = AllocationManager.GetMagicHandle();
|
*pHandle = new MemoryHandle(_MAGIC_ID, pSelf->_currentFrameCount);
|
||||||
return ptr;
|
return ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,8 +68,8 @@ public unsafe struct TempJobAllocator : IAllocator, IDisposable
|
|||||||
return Allocate(instance, newSize, alignment, allocationOption, pHandle);
|
return Allocate(instance, newSize, alignment, allocationOption, pHandle);
|
||||||
}
|
}
|
||||||
|
|
||||||
var selfPtr = (TempJobAllocator*)instance;
|
var pSelf = (TempJobAllocator*)instance;
|
||||||
var pCurrentArena = selfPtr->_pArena + selfPtr->_currentFrameIndex;
|
var pCurrentArena = pSelf->_pArena + pSelf->_currentFrameIndex;
|
||||||
var newPtr = pCurrentArena->Allocate(newSize, alignment, allocationOption);
|
var newPtr = pCurrentArena->Allocate(newSize, alignment, allocationOption);
|
||||||
if (newPtr == null)
|
if (newPtr == null)
|
||||||
{
|
{
|
||||||
@@ -71,20 +81,27 @@ public unsafe struct TempJobAllocator : IAllocator, IDisposable
|
|||||||
return newPtr;
|
return newPtr;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void Free(void* instance, void* ptr, MemoryHandle pHandle)
|
private static void Free(void* instance, void* ptr, MemoryHandle handle)
|
||||||
{
|
{
|
||||||
// The arena allocator does not free individual blocks, as it manages memory in chunks.
|
// The arena allocator does not free individual blocks, as it manages memory in chunks.
|
||||||
var selfPtr = (TempJobAllocator*)instance;
|
var pSelf = (TempJobAllocator*)instance;
|
||||||
Interlocked.Decrement(ref selfPtr->_allocationsPerFrame[selfPtr->_currentFrameIndex]);
|
Interlocked.Decrement(ref pSelf->_allocationsPerFrame[pSelf->_currentFrameIndex]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsValid(void* instance, MemoryHandle handle)
|
||||||
|
{
|
||||||
|
var pSelf = (TempJobAllocator*)instance;
|
||||||
|
return handle.id == _MAGIC_ID && handle.generation > pSelf->_currentFrameCount - _FRAME_LATENCY;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int AdvanceFrame()
|
public int AdvanceFrame()
|
||||||
{
|
{
|
||||||
var allocations = Interlocked.Exchange(ref _allocationsPerFrame[_currentFrameIndex], 0);
|
var allocations = Interlocked.Exchange(ref _allocationsPerFrame[_currentFrameIndex], 0);
|
||||||
|
|
||||||
_currentFrameIndex = (_currentFrameIndex + 1) % _FRAME_LATENCY;
|
_currentFrameCount++;
|
||||||
var pCurrentArena = _pArena + _currentFrameIndex;
|
_currentFrameIndex = _currentFrameCount % _FRAME_LATENCY;
|
||||||
pCurrentArena->Reset();
|
|
||||||
|
(_pArena + _currentFrameIndex)->Reset();
|
||||||
|
|
||||||
return allocations;
|
return allocations;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,3 +3,4 @@ global using static Misaki.HighPerformance.LowLevel.Utilities.MemoryUtility;
|
|||||||
global using unsafe AllocFunc = delegate*<void*, nuint, nuint, Misaki.HighPerformance.LowLevel.Buffer.AllocationOption, Misaki.HighPerformance.LowLevel.Buffer.MemoryHandle*, void*>;
|
global using unsafe AllocFunc = delegate*<void*, nuint, nuint, Misaki.HighPerformance.LowLevel.Buffer.AllocationOption, Misaki.HighPerformance.LowLevel.Buffer.MemoryHandle*, void*>;
|
||||||
global using unsafe ReallocFunc = delegate*<void*, void*, nuint, nuint, nuint, Misaki.HighPerformance.LowLevel.Buffer.AllocationOption, Misaki.HighPerformance.LowLevel.Buffer.MemoryHandle*, void*>;
|
global using unsafe ReallocFunc = delegate*<void*, void*, nuint, nuint, nuint, Misaki.HighPerformance.LowLevel.Buffer.AllocationOption, Misaki.HighPerformance.LowLevel.Buffer.MemoryHandle*, void*>;
|
||||||
global using unsafe FreeFunc = delegate*<void*, void*, Misaki.HighPerformance.LowLevel.Buffer.MemoryHandle, void>;
|
global using unsafe FreeFunc = delegate*<void*, void*, Misaki.HighPerformance.LowLevel.Buffer.MemoryHandle, void>;
|
||||||
|
global using unsafe IsValidFunc = delegate*<void*, Misaki.HighPerformance.LowLevel.Buffer.MemoryHandle, bool>;
|
||||||
|
|||||||
@@ -1,57 +1,10 @@
|
|||||||
using Misaki.HighPerformance.Collections;
|
using Misaki.HighPerformance.Collections;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Misaki.HighPerformance.LowLevel.Buffer;
|
namespace Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
|
|
||||||
public readonly struct MemoryHandle : IEquatable<MemoryHandle>
|
|
||||||
{
|
|
||||||
public readonly int id;
|
|
||||||
public readonly int generation;
|
|
||||||
|
|
||||||
public readonly bool IsValid => AllocationManager.ContainsAllocation(this);
|
|
||||||
|
|
||||||
public readonly static MemoryHandle Invalid = new(-1, -1);
|
|
||||||
|
|
||||||
public MemoryHandle(int id, int generation)
|
|
||||||
{
|
|
||||||
this.id = id;
|
|
||||||
this.generation = generation;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Equals(MemoryHandle other)
|
|
||||||
{
|
|
||||||
return id == other.id && generation == other.generation;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool Equals([NotNullWhen(true)] object? obj)
|
|
||||||
{
|
|
||||||
return obj is MemoryHandle other && Equals(other);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override int GetHashCode()
|
|
||||||
{
|
|
||||||
return id ^ generation;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string? ToString()
|
|
||||||
{
|
|
||||||
return $"MemoryHandle(Id: {id}, Generation: {generation})";
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool operator ==(MemoryHandle left, MemoryHandle right)
|
|
||||||
{
|
|
||||||
return left.Equals(right);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool operator !=(MemoryHandle left, MemoryHandle right)
|
|
||||||
{
|
|
||||||
return !(left == right);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Holds information about a memory allocation.
|
/// Holds information about a memory allocation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -93,15 +46,27 @@ public static unsafe class AllocationManager
|
|||||||
|
|
||||||
private struct ArenaAllocator : IAllocator, IDisposable
|
private struct ArenaAllocator : IAllocator, IDisposable
|
||||||
{
|
{
|
||||||
|
private const int _ARENA_MAGIC_ID = -3941029;
|
||||||
|
|
||||||
private DynamicArena _arena;
|
private DynamicArena _arena;
|
||||||
private AllocationHandle _handle;
|
private AllocationHandle _handle;
|
||||||
|
private int _currentTick;
|
||||||
|
|
||||||
public readonly AllocationHandle Handle => _handle;
|
public readonly AllocationHandle Handle => _handle;
|
||||||
|
public readonly int CurrentTick => _currentTick;
|
||||||
|
|
||||||
public void Init(uint initialSize)
|
public void Init(uint initialSize)
|
||||||
{
|
{
|
||||||
_arena = new DynamicArena(initialSize);
|
_arena = new DynamicArena(initialSize);
|
||||||
_handle = new AllocationHandle(Unsafe.AsPointer(ref this), &Allocate, &Reallocate, &Free);
|
_handle = new AllocationHandle
|
||||||
|
{
|
||||||
|
State = Unsafe.AsPointer(ref this),
|
||||||
|
Alloc = &Allocate,
|
||||||
|
Realloc = &Reallocate,
|
||||||
|
Free = null,
|
||||||
|
IsValid = &IsValid
|
||||||
|
};
|
||||||
|
_currentTick = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption, MemoryHandle* pHandle)
|
private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption, MemoryHandle* pHandle)
|
||||||
@@ -114,7 +79,7 @@ public static unsafe class AllocationManager
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
*pHandle = GetMagicHandle();
|
*pHandle = new MemoryHandle(_ARENA_MAGIC_ID, selfPtr->_currentTick);
|
||||||
return ptr;
|
return ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,17 +99,20 @@ public static unsafe class AllocationManager
|
|||||||
|
|
||||||
MemCpy(newPtr, ptr, Math.Min(oldSize, newSize));
|
MemCpy(newPtr, ptr, Math.Min(oldSize, newSize));
|
||||||
|
|
||||||
|
*pHandle = new MemoryHandle(_ARENA_MAGIC_ID, selfPtr->_currentTick);
|
||||||
return newPtr;
|
return newPtr;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void Free(void* instance, void* ptr, MemoryHandle handle)
|
private static bool IsValid(void* instance, MemoryHandle handle)
|
||||||
{
|
{
|
||||||
// The arena allocator does not free individual blocks, as it manages memory in chunks.
|
var selfPtr = (ArenaAllocator*)instance;
|
||||||
|
return handle.id == _ARENA_MAGIC_ID && handle.generation == selfPtr->_currentTick;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Reset()
|
public void Reset()
|
||||||
{
|
{
|
||||||
_arena.Reset();
|
_arena.Reset();
|
||||||
|
_currentTick++;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
@@ -161,27 +129,33 @@ public static unsafe class AllocationManager
|
|||||||
|
|
||||||
public void Init()
|
public void Init()
|
||||||
{
|
{
|
||||||
_handle = new AllocationHandle(Unsafe.AsPointer(ref this), &Allocate, &Reallocate, &Free);
|
_handle = new AllocationHandle
|
||||||
|
{
|
||||||
|
State = null,
|
||||||
|
Alloc = &Allocate,
|
||||||
|
Realloc = &Reallocate,
|
||||||
|
Free = &Free,
|
||||||
|
IsValid = &IsValid
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption, MemoryHandle* pHandle)
|
private static void* Allocate(void* _, nuint size, nuint alignment, AllocationOption allocationOption, MemoryHandle* pHandle)
|
||||||
{
|
{
|
||||||
return HeapAlloc(size, alignment, allocationOption, pHandle);
|
return HeapAlloc(size, alignment, allocationOption, pHandle);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void* Reallocate(void* instance, void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption, MemoryHandle* pHandle)
|
private static void* Reallocate(void* _, void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption, MemoryHandle* pHandle)
|
||||||
{
|
{
|
||||||
if (ptr == null)
|
if (ptr == null)
|
||||||
{
|
{
|
||||||
return Allocate(instance, newSize, alignment, allocationOption, pHandle);
|
return Allocate(null, newSize, alignment, allocationOption, pHandle);
|
||||||
}
|
}
|
||||||
|
|
||||||
MemoryHandle newHandle;
|
MemoryHandle newHandle;
|
||||||
var newPtr = HeapAlloc(newSize, alignment, allocationOption, &newHandle);
|
var newPtr = HeapAlloc(newSize, alignment, allocationOption, &newHandle);
|
||||||
if (newPtr == null)
|
if (newPtr == null)
|
||||||
{
|
{
|
||||||
// Allocation failed, return original pointer
|
return null;
|
||||||
return ptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MemCpy(newPtr, ptr, Math.Min(oldSize, newSize));
|
MemCpy(newPtr, ptr, Math.Min(oldSize, newSize));
|
||||||
@@ -191,15 +165,21 @@ public static unsafe class AllocationManager
|
|||||||
return newPtr;
|
return newPtr;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void Free(void* instance, void* ptr, MemoryHandle handle)
|
private static void Free(void* _, void* ptr, MemoryHandle handle)
|
||||||
{
|
{
|
||||||
HeapFree(ptr, handle);
|
HeapFree(ptr, handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool IsValid(void* _, MemoryHandle handle)
|
||||||
|
{
|
||||||
|
return ContainsAllocation(handle);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct StackAllocator : IAllocator
|
private struct StackAllocator : IAllocator
|
||||||
{
|
{
|
||||||
// Thread-local stack for allocations. We does not track allocations across threads, which leads us to let system clean up the memory when thread exits.
|
private const int _STACK_MAGIC_ID = -6843541;
|
||||||
|
|
||||||
[ThreadStatic]
|
[ThreadStatic]
|
||||||
private static Stack s_stack;
|
private static Stack s_stack;
|
||||||
private AllocationHandle _handle;
|
private AllocationHandle _handle;
|
||||||
@@ -208,7 +188,14 @@ public static unsafe class AllocationManager
|
|||||||
|
|
||||||
public void Init()
|
public void Init()
|
||||||
{
|
{
|
||||||
_handle = new(Unsafe.AsPointer(ref this), &Allocate, &Reallocate, &Free);
|
_handle = new AllocationHandle
|
||||||
|
{
|
||||||
|
State = Unsafe.AsPointer(ref this),
|
||||||
|
Alloc = &Allocate,
|
||||||
|
Realloc = &Reallocate,
|
||||||
|
Free = null,
|
||||||
|
IsValid = &IsValid
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption, MemoryHandle* pHandle)
|
private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption, MemoryHandle* pHandle)
|
||||||
@@ -220,7 +207,7 @@ public static unsafe class AllocationManager
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
*pHandle = GetMagicHandle();
|
*pHandle = new MemoryHandle(_STACK_MAGIC_ID, (int)s_stack.Offset);
|
||||||
return ptr;
|
return ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,6 +218,24 @@ public static unsafe class AllocationManager
|
|||||||
return Allocate(instance, newSize, alignment, allocationOption, pHandle);
|
return Allocate(instance, newSize, alignment, allocationOption, pHandle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Optimize for last allocation. Set offset directly.
|
||||||
|
var oldBase = s_stack.Buffer + s_stack.Offset - oldSize;
|
||||||
|
if (ptr == oldBase)
|
||||||
|
{
|
||||||
|
if (newSize > oldSize)
|
||||||
|
{
|
||||||
|
var diff = newSize - oldSize;
|
||||||
|
s_stack.Offset += diff;
|
||||||
|
if (allocationOption.HasFlag(AllocationOption.Clear))
|
||||||
|
{
|
||||||
|
MemClear(s_stack.Buffer + s_stack.Offset - diff, diff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*pHandle = new MemoryHandle(_STACK_MAGIC_ID, (int)s_stack.Offset);
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
var newPtr = s_stack.Allocate(newSize, alignment, allocationOption);
|
var newPtr = s_stack.Allocate(newSize, alignment, allocationOption);
|
||||||
if (newPtr == null)
|
if (newPtr == null)
|
||||||
{
|
{
|
||||||
@@ -239,11 +244,13 @@ public static unsafe class AllocationManager
|
|||||||
|
|
||||||
MemCpy(newPtr, ptr, Math.Min(oldSize, newSize));
|
MemCpy(newPtr, ptr, Math.Min(oldSize, newSize));
|
||||||
|
|
||||||
|
*pHandle = new MemoryHandle(_STACK_MAGIC_ID, (int)s_stack.Offset);
|
||||||
return newPtr;
|
return newPtr;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void Free(void* instance, void* ptr, MemoryHandle handle)
|
private static bool IsValid(void* instance, MemoryHandle handle)
|
||||||
{
|
{
|
||||||
|
return handle.id == _STACK_MAGIC_ID && handle.generation <= (int)s_stack.Offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Stack.Scope CreateScope(StackAllocator* pSelf)
|
public static Stack.Scope CreateScope(StackAllocator* pSelf)
|
||||||
@@ -264,7 +271,9 @@ public static unsafe class AllocationManager
|
|||||||
private static AllocationHeader* s_pLiveHead;
|
private static AllocationHeader* s_pLiveHead;
|
||||||
private static SpinLock s_liveLock;
|
private static SpinLock s_liveLock;
|
||||||
|
|
||||||
private readonly static ConcurrentSlotMap<IntPtr> s_allocations;
|
private static readonly ConcurrentSlotMap<IntPtr> s_allocations;
|
||||||
|
|
||||||
|
public static readonly MemoryHandle MagicHandle = new MemoryHandle(int.MinValue, int.MinValue);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the number of live persistent heap allocations when the debug layer is disabled.
|
/// Gets the number of live persistent heap allocations when the debug layer is disabled.
|
||||||
@@ -276,11 +285,14 @@ public static unsafe class AllocationManager
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static bool IsDebugLayerEnabled => s_debugLayer;
|
public static bool IsDebugLayerEnabled => s_debugLayer;
|
||||||
|
|
||||||
|
|
||||||
static AllocationManager()
|
static AllocationManager()
|
||||||
{
|
{
|
||||||
s_pArenaAllocator = (ArenaAllocator*)NativeMemory.Alloc((nuint)sizeof(ArenaAllocator));
|
var allocatorTotalSize = (nuint)(sizeof(ArenaAllocator) + sizeof(HeapAllocator) + sizeof(StackAllocator));
|
||||||
s_pHeapAllocator = (HeapAllocator*)NativeMemory.Alloc((nuint)sizeof(HeapAllocator));
|
var basePtr = NativeMemory.Alloc(allocatorTotalSize);
|
||||||
s_pStackAllocator = (StackAllocator*)NativeMemory.Alloc((nuint)sizeof(StackAllocator));
|
s_pArenaAllocator = (ArenaAllocator*)basePtr;
|
||||||
|
s_pHeapAllocator = (HeapAllocator*)((byte*)basePtr + (nuint)sizeof(ArenaAllocator));
|
||||||
|
s_pStackAllocator = (StackAllocator*)((byte*)basePtr + (nuint)(sizeof(ArenaAllocator) + sizeof(HeapAllocator)));
|
||||||
|
|
||||||
s_liveLock = new SpinLock(false);
|
s_liveLock = new SpinLock(false);
|
||||||
|
|
||||||
@@ -429,7 +441,7 @@ public static unsafe class AllocationManager
|
|||||||
MemCpy(newUser, userPtr, newSize);
|
MemCpy(newUser, userPtr, newSize);
|
||||||
if (allocationOption.HasFlag(AllocationOption.Clear) && newSize > oldSize)
|
if (allocationOption.HasFlag(AllocationOption.Clear) && newSize > oldSize)
|
||||||
{
|
{
|
||||||
MemClear((byte*)newUser + oldSize, newSize - oldSize);
|
MemClear(newUser + oldSize, newSize - oldSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unlink and free the old block (without freeing the StackTrace pHandle again)
|
// Unlink and free the old block (without freeing the StackTrace pHandle again)
|
||||||
@@ -465,21 +477,21 @@ public static unsafe class AllocationManager
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static AllocationHandle GetAllocationHandle(Allocator allocator)
|
public static AllocationHandle GetAllocationHandle(Allocator allocator)
|
||||||
{
|
{
|
||||||
switch (allocator)
|
return allocator switch
|
||||||
{
|
{
|
||||||
case Allocator.Temp:
|
Allocator.Temp => s_pArenaAllocator->Handle,
|
||||||
return s_pArenaAllocator->Handle;
|
Allocator.Persistent => s_pHeapAllocator->Handle,
|
||||||
case Allocator.Persistent:
|
_ => throw new ArgumentException("Target allocator type does not support custom allocation.", nameof(allocator)),
|
||||||
return s_pHeapAllocator->Handle;
|
};
|
||||||
default:
|
|
||||||
throw new ArgumentException("Target allocator type does not support custom allocation.", nameof(allocator));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Allocates a block of memory from the heap with the specified size and alignment, using the given allocation
|
/// Allocates a block of memory from the heap with the specified size and alignment, using the given allocation options.
|
||||||
/// options.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This will allocate memory from the heap. If the debug layer is enabled, additional tracking information will be recorded.
|
||||||
|
/// The memory handle is always tracked unless the <see cref="AllocationOption.Untrack"/> flag is specified.
|
||||||
|
/// </remarks>
|
||||||
/// <param name="size">The number of bytes to allocate. Must be greater than zero.</param>
|
/// <param name="size">The number of bytes to allocate. Must be greater than zero.</param>
|
||||||
/// <param name="alignment">The alignment, in bytes, for the allocated memory block. Must be a power of two.</param>
|
/// <param name="alignment">The alignment, in bytes, for the allocated memory block. Must be a power of two.</param>
|
||||||
/// <param name="allocationOption">An optional set of flags that control allocation behavior, such as whether the memory should be cleared or
|
/// <param name="allocationOption">An optional set of flags that control allocation behavior, such as whether the memory should be cleared or
|
||||||
@@ -488,8 +500,10 @@ public static unsafe class AllocationManager
|
|||||||
/// <exception cref="OutOfMemoryException">Thrown if the allocation fails.</exception>
|
/// <exception cref="OutOfMemoryException">Thrown if the allocation fails.</exception>
|
||||||
public static void* HeapAlloc(nuint size, nuint alignment, AllocationOption allocationOption, MemoryHandle* pHandle)
|
public static void* HeapAlloc(nuint size, nuint alignment, AllocationOption allocationOption, MemoryHandle* pHandle)
|
||||||
{
|
{
|
||||||
|
var isUntrack = allocationOption.HasFlag(AllocationOption.Untrack);
|
||||||
|
|
||||||
void* ptr;
|
void* ptr;
|
||||||
if (s_debugLayer)
|
if (s_debugLayer && !isUntrack)
|
||||||
{
|
{
|
||||||
ptr = DebugAllocate(size, alignment);
|
ptr = DebugAllocate(size, alignment);
|
||||||
}
|
}
|
||||||
@@ -509,7 +523,15 @@ public static unsafe class AllocationManager
|
|||||||
MemClear(ptr, size);
|
MemClear(ptr, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isUntrack)
|
||||||
|
{
|
||||||
|
*pHandle = MagicHandle;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
*pHandle = AddAllocation((IntPtr)ptr);
|
*pHandle = AddAllocation((IntPtr)ptr);
|
||||||
|
}
|
||||||
|
|
||||||
return ptr;
|
return ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -521,7 +543,7 @@ public static unsafe class AllocationManager
|
|||||||
/// <param name="handle">The handle representing the memory allocation to free. The handle must be valid and previously allocated.</param>
|
/// <param name="handle">The handle representing the memory allocation to free. The handle must be valid and previously allocated.</param>
|
||||||
public static void HeapFree(void* ptr, MemoryHandle handle)
|
public static void HeapFree(void* ptr, MemoryHandle handle)
|
||||||
{
|
{
|
||||||
if (s_debugLayer)
|
if (s_debugLayer && handle != MagicHandle)
|
||||||
{
|
{
|
||||||
DebugFree(ptr);
|
DebugFree(ptr);
|
||||||
}
|
}
|
||||||
@@ -576,12 +598,6 @@ public static unsafe class AllocationManager
|
|||||||
return new MemoryHandle(id, generation);
|
return new MemoryHandle(id, generation);
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public static MemoryHandle GetMagicHandle()
|
|
||||||
{
|
|
||||||
return new MemoryHandle(int.MinValue, int.MinValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Removes the memory allocation associated with the specified handle.
|
/// Removes the memory allocation associated with the specified handle.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -608,14 +624,17 @@ public static unsafe class AllocationManager
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Determines whether the specified memory handle refers to a currently tracked allocation.
|
/// Determines whether the specified memory handle refers to a currently tracked allocation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This only validates the memory when you added the allocation via <see cref="AddAllocation(IntPtr)"/>.
|
||||||
|
/// For validating memory from <see cref="AllocationHandle"/>, use <see cref="AllocationHandle.IsValid"/> instead.
|
||||||
|
/// </remarks>
|
||||||
/// <param name="handle">The memory handle to check for an associated allocation.</param>
|
/// <param name="handle">The memory handle to check for an associated allocation.</param>
|
||||||
/// <returns>true if the allocation corresponding to the handle exists; otherwise, false.</returns>
|
/// <returns>true if the allocation corresponding to the handle exists; otherwise, false.</returns>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static bool ContainsAllocation(MemoryHandle handle)
|
public static bool ContainsAllocation(MemoryHandle handle)
|
||||||
{
|
{
|
||||||
if (handle.id == int.MinValue && handle.generation == int.MinValue)
|
if (handle == MagicHandle)
|
||||||
{
|
{
|
||||||
// Magic handle always valid
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -679,22 +698,16 @@ public static unsafe class AllocationManager
|
|||||||
throw new MemoryLeakException($"Found {LiveAllocationCount} memory lakes! Please enable debug layer for more informations.");
|
throw new MemoryLeakException($"Found {LiveAllocationCount} memory lakes! Please enable debug layer for more informations.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NOTE: Arena allocator holds the base ptr for all allocators, heap and stack allocators do not own any memory themselves.
|
||||||
if (s_pArenaAllocator != null)
|
if (s_pArenaAllocator != null)
|
||||||
{
|
{
|
||||||
s_pArenaAllocator->Dispose();
|
s_pArenaAllocator->Dispose();
|
||||||
|
|
||||||
|
Stack.DisposeAll();
|
||||||
|
|
||||||
NativeMemory.Free(s_pArenaAllocator);
|
NativeMemory.Free(s_pArenaAllocator);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (s_pHeapAllocator != null)
|
|
||||||
{
|
|
||||||
NativeMemory.Free(s_pHeapAllocator);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (s_pStackAllocator != null)
|
|
||||||
{
|
|
||||||
NativeMemory.Free(s_pStackAllocator);
|
|
||||||
}
|
|
||||||
|
|
||||||
s_disposed = true;
|
s_disposed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,10 @@ public enum AllocationOption : byte
|
|||||||
/// Clear the memory to zero upon allocation.
|
/// Clear the memory to zero upon allocation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Clear = 1 << 0,
|
Clear = 1 << 0,
|
||||||
|
/// <summary>
|
||||||
|
/// Specify that this memory allocation should not been tracked competly, which <see cref="AllocationManager"/> will not perform any safty check like use after free and leack detection.
|
||||||
|
/// </summary>
|
||||||
|
Untrack = 1 << 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum Allocator : byte
|
public enum Allocator : byte
|
||||||
|
|||||||
@@ -1,16 +1,62 @@
|
|||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
namespace Misaki.HighPerformance.LowLevel.Buffer;
|
namespace Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
|
|
||||||
|
public readonly struct MemoryHandle : IEquatable<MemoryHandle>
|
||||||
|
{
|
||||||
|
public readonly int id;
|
||||||
|
public readonly int generation;
|
||||||
|
|
||||||
|
public readonly static MemoryHandle Invalid = new(-1, -1);
|
||||||
|
|
||||||
|
public MemoryHandle(int id, int generation)
|
||||||
|
{
|
||||||
|
this.id = id;
|
||||||
|
this.generation = generation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Equals(MemoryHandle other)
|
||||||
|
{
|
||||||
|
return id == other.id && generation == other.generation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals([NotNullWhen(true)] object? obj)
|
||||||
|
{
|
||||||
|
return obj is MemoryHandle other && Equals(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return id ^ generation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string? ToString()
|
||||||
|
{
|
||||||
|
return $"MemoryHandle(Id: {id}, Generation: {generation})";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator ==(MemoryHandle left, MemoryHandle right)
|
||||||
|
{
|
||||||
|
return left.Equals(right);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator !=(MemoryHandle left, MemoryHandle right)
|
||||||
|
{
|
||||||
|
return !(left == right);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A structure that encapsulates function pointers for memory allocation operations.
|
/// A structure that encapsulates function pointers for memory allocation operations.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly unsafe struct AllocationHandle
|
public readonly unsafe struct AllocationHandle
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a pointer to the allocator instance associated with this allocation handle.
|
/// Gets a pointer to the state instance associated with this allocation handle.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void* pAllocator
|
public void* State
|
||||||
{
|
{
|
||||||
get;
|
get; init;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -18,7 +64,7 @@ public readonly unsafe struct AllocationHandle
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public AllocFunc Alloc
|
public AllocFunc Alloc
|
||||||
{
|
{
|
||||||
get;
|
get; init;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -26,7 +72,7 @@ public readonly unsafe struct AllocationHandle
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public ReallocFunc Realloc
|
public ReallocFunc Realloc
|
||||||
{
|
{
|
||||||
get;
|
get; init;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -34,37 +80,29 @@ public readonly unsafe struct AllocationHandle
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public FreeFunc Free
|
public FreeFunc Free
|
||||||
{
|
{
|
||||||
get;
|
get; init;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="AllocationHandle"/> struct with the specified allocator and memory
|
/// Gets a function pointer for validating a memory handle.
|
||||||
/// management functions.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="allocator">A pointer to the allocator instance used for memory management.</param>
|
public IsValidFunc IsValid
|
||||||
/// <param name="alloc">The function used to allocate memory.</param>
|
|
||||||
/// <param name="realloc">The function used to reallocate memory.</param>
|
|
||||||
/// <param name="free">The function used to free allocated memory.</param>
|
|
||||||
public AllocationHandle(void* allocator, AllocFunc alloc, ReallocFunc realloc, FreeFunc free)
|
|
||||||
{
|
{
|
||||||
pAllocator = allocator;
|
get; init;
|
||||||
Alloc = alloc;
|
|
||||||
Realloc = realloc;
|
|
||||||
Free = free;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents an allocator interface for managing memory allocations.
|
/// Represents an state interface for managing memory allocations.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// The allocator must be pined to a specific memory region.
|
/// The state must be pined to a specific memory region.
|
||||||
/// Otherwise the reference of the <see cref="AllocationHandle.pAllocator"/>, may become invalid and lead to undefined behavior.
|
/// Otherwise the reference of the <see cref="AllocationHandle.State"/>, may become invalid and lead to undefined behavior.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public interface IAllocator
|
public interface IAllocator
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a reference to the allocation handle associated with this allocator.
|
/// Gets a reference to the allocation handle associated with this state.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
AllocationHandle Handle
|
AllocationHandle Handle
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,12 +2,33 @@ 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++)
|
||||||
|
{
|
||||||
|
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.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>This is not a thread-safe implementation.</remarks>
|
/// <remarks>This is not a thread-safe implementation.</remarks>
|
||||||
public unsafe struct Stack : IDisposable
|
public unsafe partial struct Stack : IDisposable
|
||||||
{
|
{
|
||||||
private const nuint _DEFAULT_SIZE = 1024 * 1024; // 1MB
|
private const nuint _DEFAULT_SIZE = 1024 * 1024; // 1MB
|
||||||
|
|
||||||
@@ -42,6 +63,14 @@ public unsafe struct Stack : IDisposable
|
|||||||
private nuint _offset;
|
private nuint _offset;
|
||||||
private uint _activeScopeCount;
|
private uint _activeScopeCount;
|
||||||
|
|
||||||
|
internal readonly byte* Buffer => _buffer;
|
||||||
|
|
||||||
|
public nuint Offset
|
||||||
|
{
|
||||||
|
readonly get => _offset;
|
||||||
|
internal set => _offset = value;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the StackAllocator class with a buffer of the specified size.
|
/// Initializes a new instance of the StackAllocator class with a buffer of the specified size.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -67,6 +96,38 @@ public unsafe struct Stack : IDisposable
|
|||||||
_size = size;
|
_size = size;
|
||||||
_offset = 0;
|
_offset = 0;
|
||||||
_activeScopeCount = 0;
|
_activeScopeCount = 0;
|
||||||
|
|
||||||
|
var token = false;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
s_locker.Enter(ref token);
|
||||||
|
if (s_pStackBuffers == null)
|
||||||
|
{
|
||||||
|
s_pStackBuffers = (void**)Malloc((nuint)sizeof(void*) * 4u);
|
||||||
|
s_stackCapacity = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s_stackCount >= s_stackCapacity)
|
||||||
|
{
|
||||||
|
var pOld = s_pStackBuffers;
|
||||||
|
var newCapacity = s_stackCapacity * 2;
|
||||||
|
var pNew = (void**)Realloc(pOld, (nuint)sizeof(void*) * (nuint)newCapacity);
|
||||||
|
|
||||||
|
s_pStackBuffers = pNew;
|
||||||
|
s_stackCapacity = newCapacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
s_pStackBuffers[s_stackCount] = _buffer;
|
||||||
|
s_stackCount++;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (token)
|
||||||
|
{
|
||||||
|
s_locker.Exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly void ThrowIfNoScope()
|
private readonly void ThrowIfNoScope()
|
||||||
|
|||||||
758
Misaki.HighPerformance.LowLevel/Collections/FixedString.gen.cs
Normal file
758
Misaki.HighPerformance.LowLevel/Collections/FixedString.gen.cs
Normal file
@@ -0,0 +1,758 @@
|
|||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Misaki.HighPerformance.LowLevel.Collections;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a stack allocated fixed-size UTF-8 encoded string of length 32 bytes.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This struct is designed to hold data on the stack. Every copy of this struct causes a copy of the underlying data.
|
||||||
|
/// If you need a heap allocated fixed-size UTF-8 encoded string of length 32 bytes, consider using <see cref="Misaki.HighPerformance.Unsafe.Buffer.FixedString32"/>.
|
||||||
|
/// </remarks>
|
||||||
|
[StructLayout(LayoutKind.Sequential, Size = 32)]
|
||||||
|
public unsafe struct FixedString32
|
||||||
|
{
|
||||||
|
public const int MAX_LENGTH = 15;
|
||||||
|
|
||||||
|
private ushort _length;
|
||||||
|
private fixed char _buffer[MAX_LENGTH];
|
||||||
|
|
||||||
|
public readonly ushort Length => _length;
|
||||||
|
public string Value
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
fixed (char* bufferPtr = _buffer)
|
||||||
|
{
|
||||||
|
return new string(bufferPtr, 0, _length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(value))
|
||||||
|
{
|
||||||
|
_length = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.Length > MAX_LENGTH)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Input string is too long to fit in FixedString32.");
|
||||||
|
}
|
||||||
|
|
||||||
|
_length = (ushort)value.Length;
|
||||||
|
fixed (char* bufferPtr = _buffer)
|
||||||
|
fixed (char* valuePtr = value)
|
||||||
|
{
|
||||||
|
Unsafe.CopyBlockUnaligned(bufferPtr, valuePtr, (uint)(_length * sizeof(char)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public FixedString32(string input)
|
||||||
|
: this(input.AsSpan())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public FixedString32(ReadOnlySpan<char> input)
|
||||||
|
{
|
||||||
|
if (input.Length > MAX_LENGTH)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Input byte array is too long to fit in FixedString32.");
|
||||||
|
}
|
||||||
|
|
||||||
|
_length = (ushort)input.Length;
|
||||||
|
|
||||||
|
fixed (char* inputPtr = input)
|
||||||
|
fixed (char* bufferPtr = _buffer)
|
||||||
|
{
|
||||||
|
Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, (uint)(_length * sizeof(char)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public FixedString32(char* input, ushort length)
|
||||||
|
: this(new ReadOnlySpan<char>(input, length))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public Span<byte> AsSpan()
|
||||||
|
{
|
||||||
|
fixed (char* ptr = _buffer)
|
||||||
|
{
|
||||||
|
return new(ptr, _length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public readonly char* GetUnsafePtr()
|
||||||
|
{
|
||||||
|
return (char*)((ushort*)Unsafe.AsPointer(in this) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a stack allocated fixed-size UTF-8 encoded string of length 64 bytes.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This struct is designed to hold data on the stack. Every copy of this struct causes a copy of the underlying data.
|
||||||
|
/// If you need a heap allocated fixed-size UTF-8 encoded string of length 64 bytes, consider using <see cref="Misaki.HighPerformance.Unsafe.Buffer.FixedString64"/>.
|
||||||
|
/// </remarks>
|
||||||
|
[StructLayout(LayoutKind.Sequential, Size = 64)]
|
||||||
|
public unsafe struct FixedString64
|
||||||
|
{
|
||||||
|
public const int MAX_LENGTH = 31;
|
||||||
|
|
||||||
|
private ushort _length;
|
||||||
|
private fixed char _buffer[MAX_LENGTH];
|
||||||
|
|
||||||
|
public readonly ushort Length => _length;
|
||||||
|
public string Value
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
fixed (char* bufferPtr = _buffer)
|
||||||
|
{
|
||||||
|
return new string(bufferPtr, 0, _length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(value))
|
||||||
|
{
|
||||||
|
_length = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.Length > MAX_LENGTH)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Input string is too long to fit in FixedString64.");
|
||||||
|
}
|
||||||
|
|
||||||
|
_length = (ushort)value.Length;
|
||||||
|
fixed (char* bufferPtr = _buffer)
|
||||||
|
fixed (char* valuePtr = value)
|
||||||
|
{
|
||||||
|
Unsafe.CopyBlockUnaligned(bufferPtr, valuePtr, (uint)(_length * sizeof(char)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public FixedString64(string input)
|
||||||
|
: this(input.AsSpan())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public FixedString64(ReadOnlySpan<char> input)
|
||||||
|
{
|
||||||
|
if (input.Length > MAX_LENGTH)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Input byte array is too long to fit in FixedString64.");
|
||||||
|
}
|
||||||
|
|
||||||
|
_length = (ushort)input.Length;
|
||||||
|
|
||||||
|
fixed (char* inputPtr = input)
|
||||||
|
fixed (char* bufferPtr = _buffer)
|
||||||
|
{
|
||||||
|
Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, (uint)(_length * sizeof(char)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public FixedString64(char* input, ushort length)
|
||||||
|
: this(new ReadOnlySpan<char>(input, length))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public Span<byte> AsSpan()
|
||||||
|
{
|
||||||
|
fixed (char* ptr = _buffer)
|
||||||
|
{
|
||||||
|
return new(ptr, _length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public readonly char* GetUnsafePtr()
|
||||||
|
{
|
||||||
|
return (char*)((ushort*)Unsafe.AsPointer(in this) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a stack allocated fixed-size UTF-8 encoded string of length 128 bytes.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This struct is designed to hold data on the stack. Every copy of this struct causes a copy of the underlying data.
|
||||||
|
/// If you need a heap allocated fixed-size UTF-8 encoded string of length 128 bytes, consider using <see cref="Misaki.HighPerformance.Unsafe.Buffer.FixedString128"/>.
|
||||||
|
/// </remarks>
|
||||||
|
[StructLayout(LayoutKind.Sequential, Size = 128)]
|
||||||
|
public unsafe struct FixedString128
|
||||||
|
{
|
||||||
|
public const int MAX_LENGTH = 63;
|
||||||
|
|
||||||
|
private ushort _length;
|
||||||
|
private fixed char _buffer[MAX_LENGTH];
|
||||||
|
|
||||||
|
public readonly ushort Length => _length;
|
||||||
|
public string Value
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
fixed (char* bufferPtr = _buffer)
|
||||||
|
{
|
||||||
|
return new string(bufferPtr, 0, _length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(value))
|
||||||
|
{
|
||||||
|
_length = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.Length > MAX_LENGTH)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Input string is too long to fit in FixedString128.");
|
||||||
|
}
|
||||||
|
|
||||||
|
_length = (ushort)value.Length;
|
||||||
|
fixed (char* bufferPtr = _buffer)
|
||||||
|
fixed (char* valuePtr = value)
|
||||||
|
{
|
||||||
|
Unsafe.CopyBlockUnaligned(bufferPtr, valuePtr, (uint)(_length * sizeof(char)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public FixedString128(string input)
|
||||||
|
: this(input.AsSpan())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public FixedString128(ReadOnlySpan<char> input)
|
||||||
|
{
|
||||||
|
if (input.Length > MAX_LENGTH)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Input byte array is too long to fit in FixedString128.");
|
||||||
|
}
|
||||||
|
|
||||||
|
_length = (ushort)input.Length;
|
||||||
|
|
||||||
|
fixed (char* inputPtr = input)
|
||||||
|
fixed (char* bufferPtr = _buffer)
|
||||||
|
{
|
||||||
|
Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, (uint)(_length * sizeof(char)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public FixedString128(char* input, ushort length)
|
||||||
|
: this(new ReadOnlySpan<char>(input, length))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public Span<byte> AsSpan()
|
||||||
|
{
|
||||||
|
fixed (char* ptr = _buffer)
|
||||||
|
{
|
||||||
|
return new(ptr, _length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public readonly char* GetUnsafePtr()
|
||||||
|
{
|
||||||
|
return (char*)((ushort*)Unsafe.AsPointer(in this) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a stack allocated fixed-size UTF-8 encoded string of length 256 bytes.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This struct is designed to hold data on the stack. Every copy of this struct causes a copy of the underlying data.
|
||||||
|
/// If you need a heap allocated fixed-size UTF-8 encoded string of length 256 bytes, consider using <see cref="Misaki.HighPerformance.Unsafe.Buffer.FixedString256"/>.
|
||||||
|
/// </remarks>
|
||||||
|
[StructLayout(LayoutKind.Sequential, Size = 256)]
|
||||||
|
public unsafe struct FixedString256
|
||||||
|
{
|
||||||
|
public const int MAX_LENGTH = 127;
|
||||||
|
|
||||||
|
private ushort _length;
|
||||||
|
private fixed char _buffer[MAX_LENGTH];
|
||||||
|
|
||||||
|
public readonly ushort Length => _length;
|
||||||
|
public string Value
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
fixed (char* bufferPtr = _buffer)
|
||||||
|
{
|
||||||
|
return new string(bufferPtr, 0, _length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(value))
|
||||||
|
{
|
||||||
|
_length = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.Length > MAX_LENGTH)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Input string is too long to fit in FixedString256.");
|
||||||
|
}
|
||||||
|
|
||||||
|
_length = (ushort)value.Length;
|
||||||
|
fixed (char* bufferPtr = _buffer)
|
||||||
|
fixed (char* valuePtr = value)
|
||||||
|
{
|
||||||
|
Unsafe.CopyBlockUnaligned(bufferPtr, valuePtr, (uint)(_length * sizeof(char)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public FixedString256(string input)
|
||||||
|
: this(input.AsSpan())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public FixedString256(ReadOnlySpan<char> input)
|
||||||
|
{
|
||||||
|
if (input.Length > MAX_LENGTH)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Input byte array is too long to fit in FixedString256.");
|
||||||
|
}
|
||||||
|
|
||||||
|
_length = (ushort)input.Length;
|
||||||
|
|
||||||
|
fixed (char* inputPtr = input)
|
||||||
|
fixed (char* bufferPtr = _buffer)
|
||||||
|
{
|
||||||
|
Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, (uint)(_length * sizeof(char)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public FixedString256(char* input, ushort length)
|
||||||
|
: this(new ReadOnlySpan<char>(input, length))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public Span<byte> AsSpan()
|
||||||
|
{
|
||||||
|
fixed (char* ptr = _buffer)
|
||||||
|
{
|
||||||
|
return new(ptr, _length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public readonly char* GetUnsafePtr()
|
||||||
|
{
|
||||||
|
return (char*)((ushort*)Unsafe.AsPointer(in this) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a stack allocated fixed-size UTF-8 encoded string of length 512 bytes.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This struct is designed to hold data on the stack. Every copy of this struct causes a copy of the underlying data.
|
||||||
|
/// If you need a heap allocated fixed-size UTF-8 encoded string of length 512 bytes, consider using <see cref="Misaki.HighPerformance.Unsafe.Buffer.FixedString512"/>.
|
||||||
|
/// </remarks>
|
||||||
|
[StructLayout(LayoutKind.Sequential, Size = 512)]
|
||||||
|
public unsafe struct FixedString512
|
||||||
|
{
|
||||||
|
public const int MAX_LENGTH = 255;
|
||||||
|
|
||||||
|
private ushort _length;
|
||||||
|
private fixed char _buffer[MAX_LENGTH];
|
||||||
|
|
||||||
|
public readonly ushort Length => _length;
|
||||||
|
public string Value
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
fixed (char* bufferPtr = _buffer)
|
||||||
|
{
|
||||||
|
return new string(bufferPtr, 0, _length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(value))
|
||||||
|
{
|
||||||
|
_length = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.Length > MAX_LENGTH)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Input string is too long to fit in FixedString512.");
|
||||||
|
}
|
||||||
|
|
||||||
|
_length = (ushort)value.Length;
|
||||||
|
fixed (char* bufferPtr = _buffer)
|
||||||
|
fixed (char* valuePtr = value)
|
||||||
|
{
|
||||||
|
Unsafe.CopyBlockUnaligned(bufferPtr, valuePtr, (uint)(_length * sizeof(char)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public FixedString512(string input)
|
||||||
|
: this(input.AsSpan())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public FixedString512(ReadOnlySpan<char> input)
|
||||||
|
{
|
||||||
|
if (input.Length > MAX_LENGTH)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Input byte array is too long to fit in FixedString512.");
|
||||||
|
}
|
||||||
|
|
||||||
|
_length = (ushort)input.Length;
|
||||||
|
|
||||||
|
fixed (char* inputPtr = input)
|
||||||
|
fixed (char* bufferPtr = _buffer)
|
||||||
|
{
|
||||||
|
Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, (uint)(_length * sizeof(char)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public FixedString512(char* input, ushort length)
|
||||||
|
: this(new ReadOnlySpan<char>(input, length))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public Span<byte> AsSpan()
|
||||||
|
{
|
||||||
|
fixed (char* ptr = _buffer)
|
||||||
|
{
|
||||||
|
return new(ptr, _length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public readonly char* GetUnsafePtr()
|
||||||
|
{
|
||||||
|
return (char*)((ushort*)Unsafe.AsPointer(in this) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a stack allocated fixed-size UTF-8 encoded string of length 1024 bytes.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This struct is designed to hold data on the stack. Every copy of this struct causes a copy of the underlying data.
|
||||||
|
/// If you need a heap allocated fixed-size UTF-8 encoded string of length 1024 bytes, consider using <see cref="Misaki.HighPerformance.Unsafe.Buffer.FixedString1024"/>.
|
||||||
|
/// </remarks>
|
||||||
|
[StructLayout(LayoutKind.Sequential, Size = 1024)]
|
||||||
|
public unsafe struct FixedString1024
|
||||||
|
{
|
||||||
|
public const int MAX_LENGTH = 511;
|
||||||
|
|
||||||
|
private ushort _length;
|
||||||
|
private fixed char _buffer[MAX_LENGTH];
|
||||||
|
|
||||||
|
public readonly ushort Length => _length;
|
||||||
|
public string Value
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
fixed (char* bufferPtr = _buffer)
|
||||||
|
{
|
||||||
|
return new string(bufferPtr, 0, _length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(value))
|
||||||
|
{
|
||||||
|
_length = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.Length > MAX_LENGTH)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Input string is too long to fit in FixedString1024.");
|
||||||
|
}
|
||||||
|
|
||||||
|
_length = (ushort)value.Length;
|
||||||
|
fixed (char* bufferPtr = _buffer)
|
||||||
|
fixed (char* valuePtr = value)
|
||||||
|
{
|
||||||
|
Unsafe.CopyBlockUnaligned(bufferPtr, valuePtr, (uint)(_length * sizeof(char)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public FixedString1024(string input)
|
||||||
|
: this(input.AsSpan())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public FixedString1024(ReadOnlySpan<char> input)
|
||||||
|
{
|
||||||
|
if (input.Length > MAX_LENGTH)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Input byte array is too long to fit in FixedString1024.");
|
||||||
|
}
|
||||||
|
|
||||||
|
_length = (ushort)input.Length;
|
||||||
|
|
||||||
|
fixed (char* inputPtr = input)
|
||||||
|
fixed (char* bufferPtr = _buffer)
|
||||||
|
{
|
||||||
|
Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, (uint)(_length * sizeof(char)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public FixedString1024(char* input, ushort length)
|
||||||
|
: this(new ReadOnlySpan<char>(input, length))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public Span<byte> AsSpan()
|
||||||
|
{
|
||||||
|
fixed (char* ptr = _buffer)
|
||||||
|
{
|
||||||
|
return new(ptr, _length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public readonly char* GetUnsafePtr()
|
||||||
|
{
|
||||||
|
return (char*)((ushort*)Unsafe.AsPointer(in this) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a stack allocated fixed-size UTF-8 encoded string of length 2048 bytes.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This struct is designed to hold data on the stack. Every copy of this struct causes a copy of the underlying data.
|
||||||
|
/// If you need a heap allocated fixed-size UTF-8 encoded string of length 2048 bytes, consider using <see cref="Misaki.HighPerformance.Unsafe.Buffer.FixedString2048"/>.
|
||||||
|
/// </remarks>
|
||||||
|
[StructLayout(LayoutKind.Sequential, Size = 2048)]
|
||||||
|
public unsafe struct FixedString2048
|
||||||
|
{
|
||||||
|
public const int MAX_LENGTH = 1023;
|
||||||
|
|
||||||
|
private ushort _length;
|
||||||
|
private fixed char _buffer[MAX_LENGTH];
|
||||||
|
|
||||||
|
public readonly ushort Length => _length;
|
||||||
|
public string Value
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
fixed (char* bufferPtr = _buffer)
|
||||||
|
{
|
||||||
|
return new string(bufferPtr, 0, _length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(value))
|
||||||
|
{
|
||||||
|
_length = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.Length > MAX_LENGTH)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Input string is too long to fit in FixedString2048.");
|
||||||
|
}
|
||||||
|
|
||||||
|
_length = (ushort)value.Length;
|
||||||
|
fixed (char* bufferPtr = _buffer)
|
||||||
|
fixed (char* valuePtr = value)
|
||||||
|
{
|
||||||
|
Unsafe.CopyBlockUnaligned(bufferPtr, valuePtr, (uint)(_length * sizeof(char)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public FixedString2048(string input)
|
||||||
|
: this(input.AsSpan())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public FixedString2048(ReadOnlySpan<char> input)
|
||||||
|
{
|
||||||
|
if (input.Length > MAX_LENGTH)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Input byte array is too long to fit in FixedString2048.");
|
||||||
|
}
|
||||||
|
|
||||||
|
_length = (ushort)input.Length;
|
||||||
|
|
||||||
|
fixed (char* inputPtr = input)
|
||||||
|
fixed (char* bufferPtr = _buffer)
|
||||||
|
{
|
||||||
|
Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, (uint)(_length * sizeof(char)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public FixedString2048(char* input, ushort length)
|
||||||
|
: this(new ReadOnlySpan<char>(input, length))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public Span<byte> AsSpan()
|
||||||
|
{
|
||||||
|
fixed (char* ptr = _buffer)
|
||||||
|
{
|
||||||
|
return new(ptr, _length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public readonly char* GetUnsafePtr()
|
||||||
|
{
|
||||||
|
return (char*)((ushort*)Unsafe.AsPointer(in this) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a stack allocated fixed-size UTF-8 encoded string of length 4096 bytes.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This struct is designed to hold data on the stack. Every copy of this struct causes a copy of the underlying data.
|
||||||
|
/// If you need a heap allocated fixed-size UTF-8 encoded string of length 4096 bytes, consider using <see cref="Misaki.HighPerformance.Unsafe.Buffer.FixedString4096"/>.
|
||||||
|
/// </remarks>
|
||||||
|
[StructLayout(LayoutKind.Sequential, Size = 4096)]
|
||||||
|
public unsafe struct FixedString4096
|
||||||
|
{
|
||||||
|
public const int MAX_LENGTH = 2047;
|
||||||
|
|
||||||
|
private ushort _length;
|
||||||
|
private fixed char _buffer[MAX_LENGTH];
|
||||||
|
|
||||||
|
public readonly ushort Length => _length;
|
||||||
|
public string Value
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
fixed (char* bufferPtr = _buffer)
|
||||||
|
{
|
||||||
|
return new string(bufferPtr, 0, _length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(value))
|
||||||
|
{
|
||||||
|
_length = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.Length > MAX_LENGTH)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Input string is too long to fit in FixedString4096.");
|
||||||
|
}
|
||||||
|
|
||||||
|
_length = (ushort)value.Length;
|
||||||
|
fixed (char* bufferPtr = _buffer)
|
||||||
|
fixed (char* valuePtr = value)
|
||||||
|
{
|
||||||
|
Unsafe.CopyBlockUnaligned(bufferPtr, valuePtr, (uint)(_length * sizeof(char)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public FixedString4096(string input)
|
||||||
|
: this(input.AsSpan())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public FixedString4096(ReadOnlySpan<char> input)
|
||||||
|
{
|
||||||
|
if (input.Length > MAX_LENGTH)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Input byte array is too long to fit in FixedString4096.");
|
||||||
|
}
|
||||||
|
|
||||||
|
_length = (ushort)input.Length;
|
||||||
|
|
||||||
|
fixed (char* inputPtr = input)
|
||||||
|
fixed (char* bufferPtr = _buffer)
|
||||||
|
{
|
||||||
|
Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, (uint)(_length * sizeof(char)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public FixedString4096(char* input, ushort length)
|
||||||
|
: this(new ReadOnlySpan<char>(input, length))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public Span<byte> AsSpan()
|
||||||
|
{
|
||||||
|
fixed (char* ptr = _buffer)
|
||||||
|
{
|
||||||
|
return new(ptr, _length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public readonly char* GetUnsafePtr()
|
||||||
|
{
|
||||||
|
return (char*)((ushort*)Unsafe.AsPointer(in this) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
108
Misaki.HighPerformance.LowLevel/Collections/FixedString.tt
Normal file
108
Misaki.HighPerformance.LowLevel/Collections/FixedString.tt
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
<#@ template debug="false" hostspecific="false" language="C#" #>
|
||||||
|
<#@ assembly name="System.Core" #>
|
||||||
|
<#@ import namespace="System.Linq" #>
|
||||||
|
<#@ import namespace="System.Text" #>
|
||||||
|
<#@ import namespace="System.Collections.Generic" #>
|
||||||
|
<#@ output extension=".gen.cs" #>
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Misaki.HighPerformance.LowLevel.Collections;
|
||||||
|
|
||||||
|
<# for (int i = 32; i <= 4096; i *= 2) { #>
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a stack allocated fixed-size UTF-8 encoded string of length <#= i #> bytes.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This struct is designed to hold data on the stack. Every copy of this struct causes a copy of the underlying data.
|
||||||
|
/// If you need a heap allocated fixed-size UTF-8 encoded string of length <#= i #> bytes, consider using <see cref="Misaki.HighPerformance.Unsafe.Buffer.FixedString<#= i #>"/>.
|
||||||
|
/// </remarks>
|
||||||
|
[StructLayout(LayoutKind.Sequential, Size = <#= i #>)]
|
||||||
|
public unsafe struct FixedString<#= i #>
|
||||||
|
{
|
||||||
|
public const int MAX_LENGTH = <#= (i - 2) / 2 #>;
|
||||||
|
|
||||||
|
private ushort _length;
|
||||||
|
private fixed char _buffer[MAX_LENGTH];
|
||||||
|
|
||||||
|
public readonly ushort Length => _length;
|
||||||
|
public string Value
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
fixed (char* bufferPtr = _buffer)
|
||||||
|
{
|
||||||
|
return new string(bufferPtr, 0, _length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(value))
|
||||||
|
{
|
||||||
|
_length = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.Length > MAX_LENGTH)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Input string is too long to fit in FixedString<#= i #>.");
|
||||||
|
}
|
||||||
|
|
||||||
|
_length = (ushort)value.Length;
|
||||||
|
fixed (char* bufferPtr = _buffer)
|
||||||
|
fixed (char* valuePtr = value)
|
||||||
|
{
|
||||||
|
Unsafe.CopyBlockUnaligned(bufferPtr, valuePtr, (uint)(_length * sizeof(char)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public FixedString<#= i #>(string input)
|
||||||
|
: this(input.AsSpan())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public FixedString<#= i #>(ReadOnlySpan<char> input)
|
||||||
|
{
|
||||||
|
if (input.Length > MAX_LENGTH)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Input byte array is too long to fit in FixedString<#= i #>.");
|
||||||
|
}
|
||||||
|
|
||||||
|
_length = (ushort)input.Length;
|
||||||
|
|
||||||
|
fixed (char* inputPtr = input)
|
||||||
|
fixed (char* bufferPtr = _buffer)
|
||||||
|
{
|
||||||
|
Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, (uint)(_length * sizeof(char)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public FixedString<#= i #>(char* input, ushort length)
|
||||||
|
: this(new ReadOnlySpan<char>(input, length))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public Span<byte> AsSpan()
|
||||||
|
{
|
||||||
|
fixed (char* ptr = _buffer)
|
||||||
|
{
|
||||||
|
return new(ptr, _length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public readonly char* GetUnsafePtr()
|
||||||
|
{
|
||||||
|
return (char*)((ushort*)Unsafe.AsPointer(in this) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
<# } #>
|
||||||
@@ -14,8 +14,10 @@ namespace Misaki.HighPerformance.LowLevel.Collections;
|
|||||||
[StructLayout(LayoutKind.Sequential, Size = 32)]
|
[StructLayout(LayoutKind.Sequential, Size = 32)]
|
||||||
public unsafe struct FixedText32
|
public unsafe struct FixedText32
|
||||||
{
|
{
|
||||||
|
public const int MAX_LENGTH = 30;
|
||||||
|
|
||||||
private ushort _length;
|
private ushort _length;
|
||||||
private fixed byte _buffer[30];
|
private fixed byte _buffer[MAX_LENGTH];
|
||||||
|
|
||||||
public readonly ushort Length => _length;
|
public readonly ushort Length => _length;
|
||||||
public string Value
|
public string Value
|
||||||
@@ -36,14 +38,14 @@ public unsafe struct FixedText32
|
|||||||
}
|
}
|
||||||
|
|
||||||
var maxBytes = Encoding.UTF8.GetByteCount(value);
|
var maxBytes = Encoding.UTF8.GetByteCount(value);
|
||||||
if (maxBytes > 30)
|
if (maxBytes > MAX_LENGTH)
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Input string is too long to fit in FixedText32.");
|
throw new ArgumentException("Input string is too long to fit in FixedText32.");
|
||||||
}
|
}
|
||||||
|
|
||||||
fixed (byte* bufferPtr = _buffer)
|
fixed (byte* bufferPtr = _buffer)
|
||||||
{
|
{
|
||||||
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, 30));
|
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, MAX_LENGTH));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -51,7 +53,7 @@ public unsafe struct FixedText32
|
|||||||
public FixedText32(ReadOnlySpan<char> input)
|
public FixedText32(ReadOnlySpan<char> input)
|
||||||
{
|
{
|
||||||
var maxBytes = Encoding.UTF8.GetByteCount(input);
|
var maxBytes = Encoding.UTF8.GetByteCount(input);
|
||||||
if (maxBytes > 30)
|
if (maxBytes > MAX_LENGTH)
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Input string is too long to fit in FixedText32.");
|
throw new ArgumentException("Input string is too long to fit in FixedText32.");
|
||||||
}
|
}
|
||||||
@@ -59,7 +61,7 @@ public unsafe struct FixedText32
|
|||||||
fixed (char* inputPtr = input)
|
fixed (char* inputPtr = input)
|
||||||
fixed (byte* bufferPtr = _buffer)
|
fixed (byte* bufferPtr = _buffer)
|
||||||
{
|
{
|
||||||
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, 30);
|
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, MAX_LENGTH);
|
||||||
_length = (ushort)actualByteCount;
|
_length = (ushort)actualByteCount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -76,7 +78,7 @@ public unsafe struct FixedText32
|
|||||||
|
|
||||||
public FixedText32(ReadOnlySpan<byte> input)
|
public FixedText32(ReadOnlySpan<byte> input)
|
||||||
{
|
{
|
||||||
if (input.Length > 30)
|
if (input.Length > MAX_LENGTH)
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Input byte array is too long to fit in FixedText32.");
|
throw new ArgumentException("Input byte array is too long to fit in FixedText32.");
|
||||||
}
|
}
|
||||||
@@ -105,12 +107,9 @@ public unsafe struct FixedText32
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public byte* GetUnsafePointer()
|
public readonly byte* GetUnsafePtr()
|
||||||
{
|
{
|
||||||
fixed (byte* ptr = _buffer)
|
return (byte*)((ushort*)Unsafe.AsPointer(in this) + 1);
|
||||||
{
|
|
||||||
return ptr;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
@@ -129,8 +128,10 @@ public unsafe struct FixedText32
|
|||||||
[StructLayout(LayoutKind.Sequential, Size = 64)]
|
[StructLayout(LayoutKind.Sequential, Size = 64)]
|
||||||
public unsafe struct FixedText64
|
public unsafe struct FixedText64
|
||||||
{
|
{
|
||||||
|
public const int MAX_LENGTH = 62;
|
||||||
|
|
||||||
private ushort _length;
|
private ushort _length;
|
||||||
private fixed byte _buffer[62];
|
private fixed byte _buffer[MAX_LENGTH];
|
||||||
|
|
||||||
public readonly ushort Length => _length;
|
public readonly ushort Length => _length;
|
||||||
public string Value
|
public string Value
|
||||||
@@ -151,14 +152,14 @@ public unsafe struct FixedText64
|
|||||||
}
|
}
|
||||||
|
|
||||||
var maxBytes = Encoding.UTF8.GetByteCount(value);
|
var maxBytes = Encoding.UTF8.GetByteCount(value);
|
||||||
if (maxBytes > 62)
|
if (maxBytes > MAX_LENGTH)
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Input string is too long to fit in FixedText64.");
|
throw new ArgumentException("Input string is too long to fit in FixedText64.");
|
||||||
}
|
}
|
||||||
|
|
||||||
fixed (byte* bufferPtr = _buffer)
|
fixed (byte* bufferPtr = _buffer)
|
||||||
{
|
{
|
||||||
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, 62));
|
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, MAX_LENGTH));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -166,7 +167,7 @@ public unsafe struct FixedText64
|
|||||||
public FixedText64(ReadOnlySpan<char> input)
|
public FixedText64(ReadOnlySpan<char> input)
|
||||||
{
|
{
|
||||||
var maxBytes = Encoding.UTF8.GetByteCount(input);
|
var maxBytes = Encoding.UTF8.GetByteCount(input);
|
||||||
if (maxBytes > 62)
|
if (maxBytes > MAX_LENGTH)
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Input string is too long to fit in FixedText64.");
|
throw new ArgumentException("Input string is too long to fit in FixedText64.");
|
||||||
}
|
}
|
||||||
@@ -174,7 +175,7 @@ public unsafe struct FixedText64
|
|||||||
fixed (char* inputPtr = input)
|
fixed (char* inputPtr = input)
|
||||||
fixed (byte* bufferPtr = _buffer)
|
fixed (byte* bufferPtr = _buffer)
|
||||||
{
|
{
|
||||||
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, 62);
|
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, MAX_LENGTH);
|
||||||
_length = (ushort)actualByteCount;
|
_length = (ushort)actualByteCount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -191,7 +192,7 @@ public unsafe struct FixedText64
|
|||||||
|
|
||||||
public FixedText64(ReadOnlySpan<byte> input)
|
public FixedText64(ReadOnlySpan<byte> input)
|
||||||
{
|
{
|
||||||
if (input.Length > 62)
|
if (input.Length > MAX_LENGTH)
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Input byte array is too long to fit in FixedText64.");
|
throw new ArgumentException("Input byte array is too long to fit in FixedText64.");
|
||||||
}
|
}
|
||||||
@@ -220,12 +221,9 @@ public unsafe struct FixedText64
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public byte* GetUnsafePointer()
|
public readonly byte* GetUnsafePtr()
|
||||||
{
|
{
|
||||||
fixed (byte* ptr = _buffer)
|
return (byte*)((ushort*)Unsafe.AsPointer(in this) + 1);
|
||||||
{
|
|
||||||
return ptr;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
@@ -244,8 +242,10 @@ public unsafe struct FixedText64
|
|||||||
[StructLayout(LayoutKind.Sequential, Size = 128)]
|
[StructLayout(LayoutKind.Sequential, Size = 128)]
|
||||||
public unsafe struct FixedText128
|
public unsafe struct FixedText128
|
||||||
{
|
{
|
||||||
|
public const int MAX_LENGTH = 126;
|
||||||
|
|
||||||
private ushort _length;
|
private ushort _length;
|
||||||
private fixed byte _buffer[126];
|
private fixed byte _buffer[MAX_LENGTH];
|
||||||
|
|
||||||
public readonly ushort Length => _length;
|
public readonly ushort Length => _length;
|
||||||
public string Value
|
public string Value
|
||||||
@@ -266,14 +266,14 @@ public unsafe struct FixedText128
|
|||||||
}
|
}
|
||||||
|
|
||||||
var maxBytes = Encoding.UTF8.GetByteCount(value);
|
var maxBytes = Encoding.UTF8.GetByteCount(value);
|
||||||
if (maxBytes > 126)
|
if (maxBytes > MAX_LENGTH)
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Input string is too long to fit in FixedText128.");
|
throw new ArgumentException("Input string is too long to fit in FixedText128.");
|
||||||
}
|
}
|
||||||
|
|
||||||
fixed (byte* bufferPtr = _buffer)
|
fixed (byte* bufferPtr = _buffer)
|
||||||
{
|
{
|
||||||
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, 126));
|
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, MAX_LENGTH));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -281,7 +281,7 @@ public unsafe struct FixedText128
|
|||||||
public FixedText128(ReadOnlySpan<char> input)
|
public FixedText128(ReadOnlySpan<char> input)
|
||||||
{
|
{
|
||||||
var maxBytes = Encoding.UTF8.GetByteCount(input);
|
var maxBytes = Encoding.UTF8.GetByteCount(input);
|
||||||
if (maxBytes > 126)
|
if (maxBytes > MAX_LENGTH)
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Input string is too long to fit in FixedText128.");
|
throw new ArgumentException("Input string is too long to fit in FixedText128.");
|
||||||
}
|
}
|
||||||
@@ -289,7 +289,7 @@ public unsafe struct FixedText128
|
|||||||
fixed (char* inputPtr = input)
|
fixed (char* inputPtr = input)
|
||||||
fixed (byte* bufferPtr = _buffer)
|
fixed (byte* bufferPtr = _buffer)
|
||||||
{
|
{
|
||||||
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, 126);
|
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, MAX_LENGTH);
|
||||||
_length = (ushort)actualByteCount;
|
_length = (ushort)actualByteCount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -306,7 +306,7 @@ public unsafe struct FixedText128
|
|||||||
|
|
||||||
public FixedText128(ReadOnlySpan<byte> input)
|
public FixedText128(ReadOnlySpan<byte> input)
|
||||||
{
|
{
|
||||||
if (input.Length > 126)
|
if (input.Length > MAX_LENGTH)
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Input byte array is too long to fit in FixedText128.");
|
throw new ArgumentException("Input byte array is too long to fit in FixedText128.");
|
||||||
}
|
}
|
||||||
@@ -335,12 +335,9 @@ public unsafe struct FixedText128
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public byte* GetUnsafePointer()
|
public readonly byte* GetUnsafePtr()
|
||||||
{
|
{
|
||||||
fixed (byte* ptr = _buffer)
|
return (byte*)((ushort*)Unsafe.AsPointer(in this) + 1);
|
||||||
{
|
|
||||||
return ptr;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
@@ -359,8 +356,10 @@ public unsafe struct FixedText128
|
|||||||
[StructLayout(LayoutKind.Sequential, Size = 256)]
|
[StructLayout(LayoutKind.Sequential, Size = 256)]
|
||||||
public unsafe struct FixedText256
|
public unsafe struct FixedText256
|
||||||
{
|
{
|
||||||
|
public const int MAX_LENGTH = 254;
|
||||||
|
|
||||||
private ushort _length;
|
private ushort _length;
|
||||||
private fixed byte _buffer[254];
|
private fixed byte _buffer[MAX_LENGTH];
|
||||||
|
|
||||||
public readonly ushort Length => _length;
|
public readonly ushort Length => _length;
|
||||||
public string Value
|
public string Value
|
||||||
@@ -381,14 +380,14 @@ public unsafe struct FixedText256
|
|||||||
}
|
}
|
||||||
|
|
||||||
var maxBytes = Encoding.UTF8.GetByteCount(value);
|
var maxBytes = Encoding.UTF8.GetByteCount(value);
|
||||||
if (maxBytes > 254)
|
if (maxBytes > MAX_LENGTH)
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Input string is too long to fit in FixedText256.");
|
throw new ArgumentException("Input string is too long to fit in FixedText256.");
|
||||||
}
|
}
|
||||||
|
|
||||||
fixed (byte* bufferPtr = _buffer)
|
fixed (byte* bufferPtr = _buffer)
|
||||||
{
|
{
|
||||||
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, 254));
|
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, MAX_LENGTH));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -396,7 +395,7 @@ public unsafe struct FixedText256
|
|||||||
public FixedText256(ReadOnlySpan<char> input)
|
public FixedText256(ReadOnlySpan<char> input)
|
||||||
{
|
{
|
||||||
var maxBytes = Encoding.UTF8.GetByteCount(input);
|
var maxBytes = Encoding.UTF8.GetByteCount(input);
|
||||||
if (maxBytes > 254)
|
if (maxBytes > MAX_LENGTH)
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Input string is too long to fit in FixedText256.");
|
throw new ArgumentException("Input string is too long to fit in FixedText256.");
|
||||||
}
|
}
|
||||||
@@ -404,7 +403,7 @@ public unsafe struct FixedText256
|
|||||||
fixed (char* inputPtr = input)
|
fixed (char* inputPtr = input)
|
||||||
fixed (byte* bufferPtr = _buffer)
|
fixed (byte* bufferPtr = _buffer)
|
||||||
{
|
{
|
||||||
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, 254);
|
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, MAX_LENGTH);
|
||||||
_length = (ushort)actualByteCount;
|
_length = (ushort)actualByteCount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -421,7 +420,7 @@ public unsafe struct FixedText256
|
|||||||
|
|
||||||
public FixedText256(ReadOnlySpan<byte> input)
|
public FixedText256(ReadOnlySpan<byte> input)
|
||||||
{
|
{
|
||||||
if (input.Length > 254)
|
if (input.Length > MAX_LENGTH)
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Input byte array is too long to fit in FixedText256.");
|
throw new ArgumentException("Input byte array is too long to fit in FixedText256.");
|
||||||
}
|
}
|
||||||
@@ -450,12 +449,9 @@ public unsafe struct FixedText256
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public byte* GetUnsafePointer()
|
public readonly byte* GetUnsafePtr()
|
||||||
{
|
{
|
||||||
fixed (byte* ptr = _buffer)
|
return (byte*)((ushort*)Unsafe.AsPointer(in this) + 1);
|
||||||
{
|
|
||||||
return ptr;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
@@ -474,8 +470,10 @@ public unsafe struct FixedText256
|
|||||||
[StructLayout(LayoutKind.Sequential, Size = 512)]
|
[StructLayout(LayoutKind.Sequential, Size = 512)]
|
||||||
public unsafe struct FixedText512
|
public unsafe struct FixedText512
|
||||||
{
|
{
|
||||||
|
public const int MAX_LENGTH = 510;
|
||||||
|
|
||||||
private ushort _length;
|
private ushort _length;
|
||||||
private fixed byte _buffer[510];
|
private fixed byte _buffer[MAX_LENGTH];
|
||||||
|
|
||||||
public readonly ushort Length => _length;
|
public readonly ushort Length => _length;
|
||||||
public string Value
|
public string Value
|
||||||
@@ -496,14 +494,14 @@ public unsafe struct FixedText512
|
|||||||
}
|
}
|
||||||
|
|
||||||
var maxBytes = Encoding.UTF8.GetByteCount(value);
|
var maxBytes = Encoding.UTF8.GetByteCount(value);
|
||||||
if (maxBytes > 510)
|
if (maxBytes > MAX_LENGTH)
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Input string is too long to fit in FixedText512.");
|
throw new ArgumentException("Input string is too long to fit in FixedText512.");
|
||||||
}
|
}
|
||||||
|
|
||||||
fixed (byte* bufferPtr = _buffer)
|
fixed (byte* bufferPtr = _buffer)
|
||||||
{
|
{
|
||||||
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, 510));
|
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, MAX_LENGTH));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -511,7 +509,7 @@ public unsafe struct FixedText512
|
|||||||
public FixedText512(ReadOnlySpan<char> input)
|
public FixedText512(ReadOnlySpan<char> input)
|
||||||
{
|
{
|
||||||
var maxBytes = Encoding.UTF8.GetByteCount(input);
|
var maxBytes = Encoding.UTF8.GetByteCount(input);
|
||||||
if (maxBytes > 510)
|
if (maxBytes > MAX_LENGTH)
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Input string is too long to fit in FixedText512.");
|
throw new ArgumentException("Input string is too long to fit in FixedText512.");
|
||||||
}
|
}
|
||||||
@@ -519,7 +517,7 @@ public unsafe struct FixedText512
|
|||||||
fixed (char* inputPtr = input)
|
fixed (char* inputPtr = input)
|
||||||
fixed (byte* bufferPtr = _buffer)
|
fixed (byte* bufferPtr = _buffer)
|
||||||
{
|
{
|
||||||
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, 510);
|
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, MAX_LENGTH);
|
||||||
_length = (ushort)actualByteCount;
|
_length = (ushort)actualByteCount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -536,7 +534,7 @@ public unsafe struct FixedText512
|
|||||||
|
|
||||||
public FixedText512(ReadOnlySpan<byte> input)
|
public FixedText512(ReadOnlySpan<byte> input)
|
||||||
{
|
{
|
||||||
if (input.Length > 510)
|
if (input.Length > MAX_LENGTH)
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Input byte array is too long to fit in FixedText512.");
|
throw new ArgumentException("Input byte array is too long to fit in FixedText512.");
|
||||||
}
|
}
|
||||||
@@ -565,12 +563,9 @@ public unsafe struct FixedText512
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public byte* GetUnsafePointer()
|
public readonly byte* GetUnsafePtr()
|
||||||
{
|
{
|
||||||
fixed (byte* ptr = _buffer)
|
return (byte*)((ushort*)Unsafe.AsPointer(in this) + 1);
|
||||||
{
|
|
||||||
return ptr;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
@@ -589,8 +584,10 @@ public unsafe struct FixedText512
|
|||||||
[StructLayout(LayoutKind.Sequential, Size = 1024)]
|
[StructLayout(LayoutKind.Sequential, Size = 1024)]
|
||||||
public unsafe struct FixedText1024
|
public unsafe struct FixedText1024
|
||||||
{
|
{
|
||||||
|
public const int MAX_LENGTH = 1022;
|
||||||
|
|
||||||
private ushort _length;
|
private ushort _length;
|
||||||
private fixed byte _buffer[1022];
|
private fixed byte _buffer[MAX_LENGTH];
|
||||||
|
|
||||||
public readonly ushort Length => _length;
|
public readonly ushort Length => _length;
|
||||||
public string Value
|
public string Value
|
||||||
@@ -611,14 +608,14 @@ public unsafe struct FixedText1024
|
|||||||
}
|
}
|
||||||
|
|
||||||
var maxBytes = Encoding.UTF8.GetByteCount(value);
|
var maxBytes = Encoding.UTF8.GetByteCount(value);
|
||||||
if (maxBytes > 1022)
|
if (maxBytes > MAX_LENGTH)
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Input string is too long to fit in FixedText1024.");
|
throw new ArgumentException("Input string is too long to fit in FixedText1024.");
|
||||||
}
|
}
|
||||||
|
|
||||||
fixed (byte* bufferPtr = _buffer)
|
fixed (byte* bufferPtr = _buffer)
|
||||||
{
|
{
|
||||||
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, 1022));
|
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, MAX_LENGTH));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -626,7 +623,7 @@ public unsafe struct FixedText1024
|
|||||||
public FixedText1024(ReadOnlySpan<char> input)
|
public FixedText1024(ReadOnlySpan<char> input)
|
||||||
{
|
{
|
||||||
var maxBytes = Encoding.UTF8.GetByteCount(input);
|
var maxBytes = Encoding.UTF8.GetByteCount(input);
|
||||||
if (maxBytes > 1022)
|
if (maxBytes > MAX_LENGTH)
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Input string is too long to fit in FixedText1024.");
|
throw new ArgumentException("Input string is too long to fit in FixedText1024.");
|
||||||
}
|
}
|
||||||
@@ -634,7 +631,7 @@ public unsafe struct FixedText1024
|
|||||||
fixed (char* inputPtr = input)
|
fixed (char* inputPtr = input)
|
||||||
fixed (byte* bufferPtr = _buffer)
|
fixed (byte* bufferPtr = _buffer)
|
||||||
{
|
{
|
||||||
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, 1022);
|
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, MAX_LENGTH);
|
||||||
_length = (ushort)actualByteCount;
|
_length = (ushort)actualByteCount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -651,7 +648,7 @@ public unsafe struct FixedText1024
|
|||||||
|
|
||||||
public FixedText1024(ReadOnlySpan<byte> input)
|
public FixedText1024(ReadOnlySpan<byte> input)
|
||||||
{
|
{
|
||||||
if (input.Length > 1022)
|
if (input.Length > MAX_LENGTH)
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Input byte array is too long to fit in FixedText1024.");
|
throw new ArgumentException("Input byte array is too long to fit in FixedText1024.");
|
||||||
}
|
}
|
||||||
@@ -680,12 +677,9 @@ public unsafe struct FixedText1024
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public byte* GetUnsafePointer()
|
public readonly byte* GetUnsafePtr()
|
||||||
{
|
{
|
||||||
fixed (byte* ptr = _buffer)
|
return (byte*)((ushort*)Unsafe.AsPointer(in this) + 1);
|
||||||
{
|
|
||||||
return ptr;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
@@ -704,8 +698,10 @@ public unsafe struct FixedText1024
|
|||||||
[StructLayout(LayoutKind.Sequential, Size = 2048)]
|
[StructLayout(LayoutKind.Sequential, Size = 2048)]
|
||||||
public unsafe struct FixedText2048
|
public unsafe struct FixedText2048
|
||||||
{
|
{
|
||||||
|
public const int MAX_LENGTH = 2046;
|
||||||
|
|
||||||
private ushort _length;
|
private ushort _length;
|
||||||
private fixed byte _buffer[2046];
|
private fixed byte _buffer[MAX_LENGTH];
|
||||||
|
|
||||||
public readonly ushort Length => _length;
|
public readonly ushort Length => _length;
|
||||||
public string Value
|
public string Value
|
||||||
@@ -726,14 +722,14 @@ public unsafe struct FixedText2048
|
|||||||
}
|
}
|
||||||
|
|
||||||
var maxBytes = Encoding.UTF8.GetByteCount(value);
|
var maxBytes = Encoding.UTF8.GetByteCount(value);
|
||||||
if (maxBytes > 2046)
|
if (maxBytes > MAX_LENGTH)
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Input string is too long to fit in FixedText2048.");
|
throw new ArgumentException("Input string is too long to fit in FixedText2048.");
|
||||||
}
|
}
|
||||||
|
|
||||||
fixed (byte* bufferPtr = _buffer)
|
fixed (byte* bufferPtr = _buffer)
|
||||||
{
|
{
|
||||||
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, 2046));
|
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, MAX_LENGTH));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -741,7 +737,7 @@ public unsafe struct FixedText2048
|
|||||||
public FixedText2048(ReadOnlySpan<char> input)
|
public FixedText2048(ReadOnlySpan<char> input)
|
||||||
{
|
{
|
||||||
var maxBytes = Encoding.UTF8.GetByteCount(input);
|
var maxBytes = Encoding.UTF8.GetByteCount(input);
|
||||||
if (maxBytes > 2046)
|
if (maxBytes > MAX_LENGTH)
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Input string is too long to fit in FixedText2048.");
|
throw new ArgumentException("Input string is too long to fit in FixedText2048.");
|
||||||
}
|
}
|
||||||
@@ -749,7 +745,7 @@ public unsafe struct FixedText2048
|
|||||||
fixed (char* inputPtr = input)
|
fixed (char* inputPtr = input)
|
||||||
fixed (byte* bufferPtr = _buffer)
|
fixed (byte* bufferPtr = _buffer)
|
||||||
{
|
{
|
||||||
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, 2046);
|
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, MAX_LENGTH);
|
||||||
_length = (ushort)actualByteCount;
|
_length = (ushort)actualByteCount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -766,7 +762,7 @@ public unsafe struct FixedText2048
|
|||||||
|
|
||||||
public FixedText2048(ReadOnlySpan<byte> input)
|
public FixedText2048(ReadOnlySpan<byte> input)
|
||||||
{
|
{
|
||||||
if (input.Length > 2046)
|
if (input.Length > MAX_LENGTH)
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Input byte array is too long to fit in FixedText2048.");
|
throw new ArgumentException("Input byte array is too long to fit in FixedText2048.");
|
||||||
}
|
}
|
||||||
@@ -795,12 +791,9 @@ public unsafe struct FixedText2048
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public byte* GetUnsafePointer()
|
public readonly byte* GetUnsafePtr()
|
||||||
{
|
{
|
||||||
fixed (byte* ptr = _buffer)
|
return (byte*)((ushort*)Unsafe.AsPointer(in this) + 1);
|
||||||
{
|
|
||||||
return ptr;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
@@ -819,8 +812,10 @@ public unsafe struct FixedText2048
|
|||||||
[StructLayout(LayoutKind.Sequential, Size = 4096)]
|
[StructLayout(LayoutKind.Sequential, Size = 4096)]
|
||||||
public unsafe struct FixedText4096
|
public unsafe struct FixedText4096
|
||||||
{
|
{
|
||||||
|
public const int MAX_LENGTH = 4094;
|
||||||
|
|
||||||
private ushort _length;
|
private ushort _length;
|
||||||
private fixed byte _buffer[4094];
|
private fixed byte _buffer[MAX_LENGTH];
|
||||||
|
|
||||||
public readonly ushort Length => _length;
|
public readonly ushort Length => _length;
|
||||||
public string Value
|
public string Value
|
||||||
@@ -841,14 +836,14 @@ public unsafe struct FixedText4096
|
|||||||
}
|
}
|
||||||
|
|
||||||
var maxBytes = Encoding.UTF8.GetByteCount(value);
|
var maxBytes = Encoding.UTF8.GetByteCount(value);
|
||||||
if (maxBytes > 4094)
|
if (maxBytes > MAX_LENGTH)
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Input string is too long to fit in FixedText4096.");
|
throw new ArgumentException("Input string is too long to fit in FixedText4096.");
|
||||||
}
|
}
|
||||||
|
|
||||||
fixed (byte* bufferPtr = _buffer)
|
fixed (byte* bufferPtr = _buffer)
|
||||||
{
|
{
|
||||||
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, 4094));
|
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, MAX_LENGTH));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -856,7 +851,7 @@ public unsafe struct FixedText4096
|
|||||||
public FixedText4096(ReadOnlySpan<char> input)
|
public FixedText4096(ReadOnlySpan<char> input)
|
||||||
{
|
{
|
||||||
var maxBytes = Encoding.UTF8.GetByteCount(input);
|
var maxBytes = Encoding.UTF8.GetByteCount(input);
|
||||||
if (maxBytes > 4094)
|
if (maxBytes > MAX_LENGTH)
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Input string is too long to fit in FixedText4096.");
|
throw new ArgumentException("Input string is too long to fit in FixedText4096.");
|
||||||
}
|
}
|
||||||
@@ -864,7 +859,7 @@ public unsafe struct FixedText4096
|
|||||||
fixed (char* inputPtr = input)
|
fixed (char* inputPtr = input)
|
||||||
fixed (byte* bufferPtr = _buffer)
|
fixed (byte* bufferPtr = _buffer)
|
||||||
{
|
{
|
||||||
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, 4094);
|
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, MAX_LENGTH);
|
||||||
_length = (ushort)actualByteCount;
|
_length = (ushort)actualByteCount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -881,7 +876,7 @@ public unsafe struct FixedText4096
|
|||||||
|
|
||||||
public FixedText4096(ReadOnlySpan<byte> input)
|
public FixedText4096(ReadOnlySpan<byte> input)
|
||||||
{
|
{
|
||||||
if (input.Length > 4094)
|
if (input.Length > MAX_LENGTH)
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Input byte array is too long to fit in FixedText4096.");
|
throw new ArgumentException("Input byte array is too long to fit in FixedText4096.");
|
||||||
}
|
}
|
||||||
@@ -910,12 +905,9 @@ public unsafe struct FixedText4096
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public byte* GetUnsafePointer()
|
public readonly byte* GetUnsafePtr()
|
||||||
{
|
{
|
||||||
fixed (byte* ptr = _buffer)
|
return (byte*)((ushort*)Unsafe.AsPointer(in this) + 1);
|
||||||
{
|
|
||||||
return ptr;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
|
|||||||
@@ -21,8 +21,10 @@ namespace Misaki.HighPerformance.LowLevel.Collections;
|
|||||||
[StructLayout(LayoutKind.Sequential, Size = <#= i #>)]
|
[StructLayout(LayoutKind.Sequential, Size = <#= i #>)]
|
||||||
public unsafe struct FixedText<#= i #>
|
public unsafe struct FixedText<#= i #>
|
||||||
{
|
{
|
||||||
|
public const int MAX_LENGTH = <#= i - 2 #>;
|
||||||
|
|
||||||
private ushort _length;
|
private ushort _length;
|
||||||
private fixed byte _buffer[<#= i - 2 #>];
|
private fixed byte _buffer[MAX_LENGTH];
|
||||||
|
|
||||||
public readonly ushort Length => _length;
|
public readonly ushort Length => _length;
|
||||||
public string Value
|
public string Value
|
||||||
@@ -43,14 +45,14 @@ public unsafe struct FixedText<#= i #>
|
|||||||
}
|
}
|
||||||
|
|
||||||
var maxBytes = Encoding.UTF8.GetByteCount(value);
|
var maxBytes = Encoding.UTF8.GetByteCount(value);
|
||||||
if (maxBytes > <#= i - 2 #>)
|
if (maxBytes > MAX_LENGTH)
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Input string is too long to fit in FixedText<#= i #>.");
|
throw new ArgumentException("Input string is too long to fit in FixedText<#= i #>.");
|
||||||
}
|
}
|
||||||
|
|
||||||
fixed (byte* bufferPtr = _buffer)
|
fixed (byte* bufferPtr = _buffer)
|
||||||
{
|
{
|
||||||
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, <#= i - 2 #>));
|
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, MAX_LENGTH));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -58,7 +60,7 @@ public unsafe struct FixedText<#= i #>
|
|||||||
public FixedText<#= i #>(ReadOnlySpan<char> input)
|
public FixedText<#= i #>(ReadOnlySpan<char> input)
|
||||||
{
|
{
|
||||||
var maxBytes = Encoding.UTF8.GetByteCount(input);
|
var maxBytes = Encoding.UTF8.GetByteCount(input);
|
||||||
if (maxBytes > <#= i - 2 #>)
|
if (maxBytes > MAX_LENGTH)
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Input string is too long to fit in FixedText<#= i #>.");
|
throw new ArgumentException("Input string is too long to fit in FixedText<#= i #>.");
|
||||||
}
|
}
|
||||||
@@ -66,7 +68,7 @@ public unsafe struct FixedText<#= i #>
|
|||||||
fixed (char* inputPtr = input)
|
fixed (char* inputPtr = input)
|
||||||
fixed (byte* bufferPtr = _buffer)
|
fixed (byte* bufferPtr = _buffer)
|
||||||
{
|
{
|
||||||
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, <#= i - 2 #>);
|
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, MAX_LENGTH);
|
||||||
_length = (ushort)actualByteCount;
|
_length = (ushort)actualByteCount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -83,7 +85,7 @@ public unsafe struct FixedText<#= i #>
|
|||||||
|
|
||||||
public FixedText<#= i #>(ReadOnlySpan<byte> input)
|
public FixedText<#= i #>(ReadOnlySpan<byte> input)
|
||||||
{
|
{
|
||||||
if (input.Length > <#= i - 2 #>)
|
if (input.Length > MAX_LENGTH)
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Input byte array is too long to fit in FixedText<#= i #>.");
|
throw new ArgumentException("Input byte array is too long to fit in FixedText<#= i #>.");
|
||||||
}
|
}
|
||||||
@@ -112,12 +114,9 @@ public unsafe struct FixedText<#= i #>
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public byte* GetUnsafePointer()
|
public readonly byte* GetUnsafePtr()
|
||||||
{
|
{
|
||||||
fixed (byte* ptr = _buffer)
|
return (byte*)((ushort*)Unsafe.AsPointer(in this) + 1);
|
||||||
{
|
|
||||||
return ptr;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Misaki.HighPerformance.LowLevel.Utilities;
|
using Misaki.HighPerformance.LowLevel.Utilities;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace Misaki.HighPerformance.LowLevel.Collections;
|
namespace Misaki.HighPerformance.LowLevel.Collections;
|
||||||
@@ -50,18 +51,26 @@ public readonly unsafe struct ReadOnlyUnsafeCollection<T> : IEnumerable<T>
|
|||||||
private readonly T* _buffer;
|
private readonly T* _buffer;
|
||||||
private readonly int _count;
|
private readonly int _count;
|
||||||
|
|
||||||
public readonly int Count => _count;
|
public int Count => _count;
|
||||||
|
|
||||||
public readonly T this[int index]
|
public ref readonly T this[int index]
|
||||||
{
|
{
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
get => UnsafeUtility.ReadArrayElement<T>(_buffer, index);
|
get
|
||||||
|
{
|
||||||
|
CheckIndexBounds(index);
|
||||||
|
return ref UnsafeUtility.ReadArrayElementRef<T>(_buffer, index);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly T this[uint index]
|
public ref readonly T this[uint index]
|
||||||
{
|
{
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
get => UnsafeUtility.ReadArrayElement<T>(_buffer, index);
|
get
|
||||||
|
{
|
||||||
|
CheckIndexBounds((int)index);
|
||||||
|
return ref UnsafeUtility.ReadArrayElementRef<T>(_buffer, index);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Enumerator GetEnumerator() => new Enumerator(in this);
|
public Enumerator GetEnumerator() => new Enumerator(in this);
|
||||||
@@ -74,6 +83,16 @@ public readonly unsafe struct ReadOnlyUnsafeCollection<T> : IEnumerable<T>
|
|||||||
_count = count;
|
_count = count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
[Conditional("ENABLE_COLLECTION_CHECKS")]
|
||||||
|
private readonly void CheckIndexBounds(int index)
|
||||||
|
{
|
||||||
|
if (index >= _count)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(index), "Index is out of range.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns a read-only span that represents the valid elements in the underlying buffer.
|
/// Returns a read-only span that represents the valid elements in the underlying buffer.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -17,7 +17,25 @@ public unsafe struct UnTypedArray : IUnTypedCollection
|
|||||||
public readonly nuint Size => _size;
|
public readonly nuint Size => _size;
|
||||||
public readonly nuint Alignment => _alignment;
|
public readonly nuint Alignment => _alignment;
|
||||||
|
|
||||||
public readonly bool IsCreated => _buffer != null && _allocationHandle.pAllocator != null && _memoryHandle.IsValid;
|
public readonly bool IsCreated
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_buffer != null)
|
||||||
|
{
|
||||||
|
if (_allocationHandle.IsValid != null)
|
||||||
|
{
|
||||||
|
return _allocationHandle.IsValid(_allocationHandle.State, _memoryHandle);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructs an UnsafeArray with a default size of 1 and uses the Persistent allocator.
|
/// Constructs an UnsafeArray with a default size of 1 and uses the Persistent allocator.
|
||||||
@@ -34,8 +52,13 @@ public unsafe struct UnTypedArray : IUnTypedCollection
|
|||||||
throw new ArgumentOutOfRangeException(nameof(size), "Count must be greater than zero.");
|
throw new ArgumentOutOfRangeException(nameof(size), "Count must be greater than zero.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (handle.Alloc == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Target allocation handle does not support allocation.");
|
||||||
|
}
|
||||||
|
|
||||||
MemoryHandle memHandle;
|
MemoryHandle memHandle;
|
||||||
_buffer = handle.Alloc(_allocationHandle.pAllocator, size, alignment, allocationOption, &memHandle);
|
_buffer = handle.Alloc(_allocationHandle.State, size, alignment, allocationOption, &memHandle);
|
||||||
_size = size;
|
_size = size;
|
||||||
_alignment = alignment;
|
_alignment = alignment;
|
||||||
|
|
||||||
@@ -87,7 +110,7 @@ public unsafe struct UnTypedArray : IUnTypedCollection
|
|||||||
}
|
}
|
||||||
|
|
||||||
MemoryHandle memHandle = _memoryHandle;
|
MemoryHandle memHandle = _memoryHandle;
|
||||||
_buffer = _allocationHandle.Realloc(_allocationHandle.pAllocator, _buffer, _size, newSize, _alignment, option, &memHandle);
|
_buffer = _allocationHandle.Realloc(_allocationHandle.State, _buffer, _size, newSize, _alignment, option, &memHandle);
|
||||||
_size = newSize;
|
_size = newSize;
|
||||||
_memoryHandle = memHandle;
|
_memoryHandle = memHandle;
|
||||||
}
|
}
|
||||||
@@ -232,9 +255,9 @@ public unsafe struct UnTypedArray : IUnTypedCollection
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_allocationHandle.pAllocator != null)
|
if (_allocationHandle.Free != null)
|
||||||
{
|
{
|
||||||
_allocationHandle.Free(_allocationHandle.pAllocator, _buffer, _memoryHandle);
|
_allocationHandle.Free(_allocationHandle.State, _buffer, _memoryHandle);
|
||||||
}
|
}
|
||||||
|
|
||||||
_buffer = null;
|
_buffer = null;
|
||||||
|
|||||||
@@ -102,7 +102,25 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly bool IsCreated => _buffer != null && _allocationHandle.pAllocator != null && _memoryHandle.IsValid;
|
public readonly bool IsCreated
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_buffer != null)
|
||||||
|
{
|
||||||
|
if (_allocationHandle.IsValid != null)
|
||||||
|
{
|
||||||
|
return _allocationHandle.IsValid(_allocationHandle.State, _memoryHandle);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public Enumerator GetEnumerator() => new((UnsafeArray<T>*)UnsafeUtility.AddressOf(ref this));
|
public Enumerator GetEnumerator() => new((UnsafeArray<T>*)UnsafeUtility.AddressOf(ref this));
|
||||||
IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator();
|
IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator();
|
||||||
@@ -130,8 +148,13 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
|
|||||||
throw new ArgumentOutOfRangeException(nameof(count), "Count can not be less than zero.");
|
throw new ArgumentOutOfRangeException(nameof(count), "Count can not be less than zero.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (handle.Alloc == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Target allocation handle does not support allocation.");
|
||||||
|
}
|
||||||
|
|
||||||
MemoryHandle memHandle;
|
MemoryHandle memHandle;
|
||||||
var buff = handle.Alloc(handle.pAllocator, (nuint)(count * sizeof(T)), AlignOf<T>(), allocationOption, &memHandle);
|
var buff = handle.Alloc(handle.State, (nuint)(count * sizeof(T)), AlignOf<T>(), allocationOption, &memHandle);
|
||||||
|
|
||||||
_buffer = (T*)buff;
|
_buffer = (T*)buff;
|
||||||
_memoryHandle = memHandle;
|
_memoryHandle = memHandle;
|
||||||
@@ -168,6 +191,7 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
[Conditional("ENABLE_COLLECTION_CHECKS")]
|
||||||
private readonly void ThrowIfNotCreated()
|
private readonly void ThrowIfNotCreated()
|
||||||
{
|
{
|
||||||
if (!IsCreated)
|
if (!IsCreated)
|
||||||
@@ -202,6 +226,11 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
|
|||||||
{
|
{
|
||||||
ThrowIfNotCreated();
|
ThrowIfNotCreated();
|
||||||
|
|
||||||
|
if (_allocationHandle.Realloc == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Target allocation handle does not support reallocation.");
|
||||||
|
}
|
||||||
|
|
||||||
if (newSize == Count)
|
if (newSize == Count)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -209,7 +238,7 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
|
|||||||
|
|
||||||
MemoryHandle memHandle = _memoryHandle;
|
MemoryHandle memHandle = _memoryHandle;
|
||||||
var elemSize = SizeOf<T>();
|
var elemSize = SizeOf<T>();
|
||||||
_buffer = (T*)_allocationHandle.Realloc(_allocationHandle.pAllocator, _buffer, (nuint)Count * elemSize, (nuint)newSize * elemSize, AlignOf<T>(), option, &memHandle);
|
_buffer = (T*)_allocationHandle.Realloc(_allocationHandle.State, _buffer, (nuint)Count * elemSize, (nuint)newSize * elemSize, AlignOf<T>(), option, &memHandle);
|
||||||
_memoryHandle = memHandle;
|
_memoryHandle = memHandle;
|
||||||
_count = newSize;
|
_count = newSize;
|
||||||
}
|
}
|
||||||
@@ -273,9 +302,9 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_allocationHandle.pAllocator != null)
|
if (_allocationHandle.Free != null)
|
||||||
{
|
{
|
||||||
_allocationHandle.Free(_allocationHandle.pAllocator, _buffer, _memoryHandle);
|
_allocationHandle.Free(_allocationHandle.State, _buffer, _memoryHandle);
|
||||||
}
|
}
|
||||||
|
|
||||||
_buffer = null;
|
_buffer = null;
|
||||||
|
|||||||
@@ -55,7 +55,6 @@ public unsafe struct UnsafeHashMap<TKey, TValue> : IUnsafeHashCollection<KeyValu
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
var idx = _helper.Find(key);
|
var idx = _helper.Find(key);
|
||||||
@@ -98,7 +97,7 @@ public unsafe struct UnsafeHashMap<TKey, TValue> : IUnsafeHashCollection<KeyValu
|
|||||||
/// <param name="key">The key to add.</param>
|
/// <param name="key">The key to add.</param>
|
||||||
/// <param name="item">The value to add.</param>
|
/// <param name="item">The value to add.</param>
|
||||||
/// <returns>True if the key-value pair was added.</returns>
|
/// <returns>True if the key-value pair was added.</returns>
|
||||||
public bool TryAdd(TKey key, TValue item)
|
public bool TryAdd(in TKey key, TValue item)
|
||||||
{
|
{
|
||||||
var idx = _helper.TryAdd(key);
|
var idx = _helper.TryAdd(key);
|
||||||
if (idx != -1)
|
if (idx != -1)
|
||||||
@@ -117,7 +116,7 @@ public unsafe struct UnsafeHashMap<TKey, TValue> : IUnsafeHashCollection<KeyValu
|
|||||||
/// <param name="key">The key to add.</param>
|
/// <param name="key">The key to add.</param>
|
||||||
/// <param name="item">The value to add.</param>
|
/// <param name="item">The value to add.</param>
|
||||||
/// <exception cref="ArgumentException">Thrown if the key was already present.</exception>
|
/// <exception cref="ArgumentException">Thrown if the key was already present.</exception>
|
||||||
public void Add(TKey key, TValue item)
|
public void Add(in TKey key, TValue item)
|
||||||
{
|
{
|
||||||
var result = TryAdd(key, item);
|
var result = TryAdd(key, item);
|
||||||
if (!result)
|
if (!result)
|
||||||
@@ -131,7 +130,7 @@ public unsafe struct UnsafeHashMap<TKey, TValue> : IUnsafeHashCollection<KeyValu
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="item">The value to remove.</param>
|
/// <param name="item">The value to remove.</param>
|
||||||
/// <returns>True if the value was present.</returns>
|
/// <returns>True if the value was present.</returns>
|
||||||
public bool Remove(TKey key)
|
public bool Remove(in TKey key)
|
||||||
{
|
{
|
||||||
return -1 != _helper.TryRemove(key);
|
return -1 != _helper.TryRemove(key);
|
||||||
}
|
}
|
||||||
@@ -142,17 +141,38 @@ public unsafe struct UnsafeHashMap<TKey, TValue> : IUnsafeHashCollection<KeyValu
|
|||||||
/// <param name="key">The key to look up.</param>
|
/// <param name="key">The key to look up.</param>
|
||||||
/// <param name="item">Outputs the value associated with the key. Outputs default if the key was not present.</param>
|
/// <param name="item">Outputs the value associated with the key. Outputs default if the key was not present.</param>
|
||||||
/// <returns>True if the key was present.</returns>
|
/// <returns>True if the key was present.</returns>
|
||||||
public bool TryGetValue(TKey key, out TValue item)
|
public bool TryGetValue(in TKey key, out TValue item)
|
||||||
{
|
{
|
||||||
return _helper.TryGetValue(key, out item);
|
return _helper.TryGetValue(key, out item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves the value associated with the specified key, or returns a default value if the key is not found.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key">The key whose value to retrieve.</param>
|
||||||
|
/// <param name="defaultValue">The value to return if the specified key does not exist. If not specified, the default value for the type is used.</param>
|
||||||
|
/// <returns>The value associated with the specified key if the key is found; otherwise, the specified default value.</returns>
|
||||||
|
public TValue GetValueOrDefault(in TKey key, TValue defaultValue = default)
|
||||||
|
{
|
||||||
|
if (_helper.TryGetValue<TValue>(key, out var value))
|
||||||
|
{
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ref TValue GetValueRef(in TKey key, out bool exists)
|
||||||
|
{
|
||||||
|
return ref _helper.GetValueRef<TValue>(key, out exists);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns true if a given key is present in this hash map.
|
/// Returns true if a given key is present in this hash map.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="key">The key to look up.</param>
|
/// <param name="key">The key to look up.</param>
|
||||||
/// <returns>True if the key was present.</returns>
|
/// <returns>True if the key was present.</returns>
|
||||||
public bool ContainsKey(TKey key)
|
public bool ContainsKey(in TKey key)
|
||||||
{
|
{
|
||||||
return -1 != _helper.Find(key);
|
return -1 != _helper.Find(key);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||||
<Authors>Misaki</Authors>
|
<Authors>Misaki</Authors>
|
||||||
<AssemblyVersion>1.3.3</AssemblyVersion>
|
<AssemblyVersion>1.3.5</AssemblyVersion>
|
||||||
<Version>$(AssemblyVersion)</Version>
|
<Version>$(AssemblyVersion)</Version>
|
||||||
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||||
<PackageProjectUrl>https://git.personalnas.com/Misaki/Misaki.HighPerformance.git</PackageProjectUrl>
|
<PackageProjectUrl>https://git.personalnas.com/Misaki/Misaki.HighPerformance.git</PackageProjectUrl>
|
||||||
@@ -29,6 +29,10 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<None Update="Collections\FixedString.tt">
|
||||||
|
<Generator>TextTemplatingFileGenerator</Generator>
|
||||||
|
<LastGenOutput>FixedString.gen.cs</LastGenOutput>
|
||||||
|
</None>
|
||||||
<None Update="Collections\FixedText.tt">
|
<None Update="Collections\FixedText.tt">
|
||||||
<Generator>TextTemplatingFileGenerator</Generator>
|
<Generator>TextTemplatingFileGenerator</Generator>
|
||||||
<LastGenOutput>FixedText.gen.cs</LastGenOutput>
|
<LastGenOutput>FixedText.gen.cs</LastGenOutput>
|
||||||
@@ -40,6 +44,11 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Compile Update="Collections\FixedString.gen.cs">
|
||||||
|
<DesignTime>True</DesignTime>
|
||||||
|
<AutoGen>True</AutoGen>
|
||||||
|
<DependentUpon>FixedString.tt</DependentUpon>
|
||||||
|
</Compile>
|
||||||
<Compile Update="Collections\FixedText.gen.cs">
|
<Compile Update="Collections\FixedText.gen.cs">
|
||||||
<DesignTime>True</DesignTime>
|
<DesignTime>True</DesignTime>
|
||||||
<AutoGen>True</AutoGen>
|
<AutoGen>True</AutoGen>
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ public unsafe struct UniquePtr<T> : IEquatable<UniquePtr<T>>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ref struct Ref<T>
|
public ref struct Ref<T> : IEquatable<Ref<T>>
|
||||||
{
|
{
|
||||||
private ref T _value;
|
private ref T _value;
|
||||||
|
|
||||||
@@ -150,6 +150,11 @@ public ref struct Ref<T>
|
|||||||
return ref _value;
|
return ref _value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool Equals(Ref<T> other)
|
||||||
|
{
|
||||||
|
return Unsafe.AreSame(ref _value, ref other._value);
|
||||||
|
}
|
||||||
|
|
||||||
[Obsolete("Equals() on Ref will always throw an exception. Use the equality operator instead.")]
|
[Obsolete("Equals() on Ref will always throw an exception. Use the equality operator instead.")]
|
||||||
#pragma warning disable CS0809 // Obsolete member overrides non-obsolete member
|
#pragma warning disable CS0809 // Obsolete member overrides non-obsolete member
|
||||||
public override bool Equals(object? obj)
|
public override bool Equals(object? obj)
|
||||||
@@ -166,7 +171,7 @@ public ref struct Ref<T>
|
|||||||
|
|
||||||
public static bool operator ==(Ref<T> left, Ref<T> right)
|
public static bool operator ==(Ref<T> left, Ref<T> right)
|
||||||
{
|
{
|
||||||
return Unsafe.AreSame(ref left._value, ref right._value);
|
return left.Equals(right);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool operator !=(Ref<T> left, Ref<T> right)
|
public static bool operator !=(Ref<T> left, Ref<T> right)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
using Misaki.HighPerformance.LowLevel.Collections;
|
using Misaki.HighPerformance.LowLevel.Collections;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
@@ -89,7 +90,25 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
|
|||||||
|
|
||||||
public readonly bool IsEmpty => !IsCreated || _count == 0;
|
public readonly bool IsEmpty => !IsCreated || _count == 0;
|
||||||
|
|
||||||
public readonly bool IsCreated => _buffer != null && _allocationHandle.pAllocator != null && _memoryHandle.IsValid;
|
public readonly bool IsCreated
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_buffer != null)
|
||||||
|
{
|
||||||
|
if (_allocationHandle.IsValid != null)
|
||||||
|
{
|
||||||
|
return _allocationHandle.IsValid(_allocationHandle.State, _memoryHandle);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static int CalculateDataSize(int capacity, int bucketCapacity, int sizeOfTValue, out int outKeyOffset, out int outNextOffset, out int outBucketOffset)
|
private static int CalculateDataSize(int capacity, int bucketCapacity, int sizeOfTValue, out int outKeyOffset, out int outNextOffset, out int outBucketOffset)
|
||||||
{
|
{
|
||||||
@@ -142,6 +161,7 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
[Conditional("ENABLE_COLLECTION_CHECKS")]
|
||||||
private readonly void ThrowIfNotCreated()
|
private readonly void ThrowIfNotCreated()
|
||||||
{
|
{
|
||||||
if (!IsCreated)
|
if (!IsCreated)
|
||||||
@@ -191,8 +211,13 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private void AllocateBuffer(int totalSize, int keyOffset, int nextOffset, int bucketOffset, AllocationOption allocationOption)
|
private void AllocateBuffer(int totalSize, int keyOffset, int nextOffset, int bucketOffset, AllocationOption allocationOption)
|
||||||
{
|
{
|
||||||
|
if (_allocationHandle.Alloc == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Target allocation handle does not support allocation.");
|
||||||
|
}
|
||||||
|
|
||||||
MemoryHandle memHandle;
|
MemoryHandle memHandle;
|
||||||
var buf = (byte*)_allocationHandle.Alloc(_allocationHandle.pAllocator, (uint)totalSize, (nuint)_alignment, allocationOption, &memHandle);
|
var buf = (byte*)_allocationHandle.Alloc(_allocationHandle.State, (uint)totalSize, (nuint)_alignment, allocationOption, &memHandle);
|
||||||
|
|
||||||
_buffer = buf;
|
_buffer = buf;
|
||||||
_keys = (TKey*)(_buffer + keyOffset);
|
_keys = (TKey*)(_buffer + keyOffset);
|
||||||
@@ -228,7 +253,10 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_allocationHandle.Free(_allocationHandle.pAllocator, oldBuffer, oldMemoryHandle);
|
if (_allocationHandle.Free != null)
|
||||||
|
{
|
||||||
|
_allocationHandle.Free(_allocationHandle.State, oldBuffer, oldMemoryHandle);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Resize(int newCapacity)
|
public void Resize(int newCapacity)
|
||||||
@@ -399,6 +427,21 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ref TValue GetValueRef<TValue>(in TKey key, out bool exists)
|
||||||
|
where TValue : unmanaged
|
||||||
|
{
|
||||||
|
ThrowIfNotCreated();
|
||||||
|
var idx = Find(key);
|
||||||
|
if (idx != -1)
|
||||||
|
{
|
||||||
|
exists = true;
|
||||||
|
return ref UnsafeUtility.ReadArrayElementRef<TValue>(_buffer, idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
exists = false;
|
||||||
|
return ref Unsafe.NullRef<TValue>();
|
||||||
|
}
|
||||||
|
|
||||||
public bool MoveNextSearch(ref int bucketIndex, ref int nextIndex, out int index)
|
public bool MoveNextSearch(ref int bucketIndex, ref int nextIndex, out int index)
|
||||||
{
|
{
|
||||||
ThrowIfNotCreated();
|
ThrowIfNotCreated();
|
||||||
@@ -522,9 +565,9 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_allocationHandle.pAllocator != null)
|
if (_allocationHandle.Free != null)
|
||||||
{
|
{
|
||||||
_allocationHandle.Free(_allocationHandle.pAllocator, _buffer, _memoryHandle);
|
_allocationHandle.Free(_allocationHandle.State, _buffer, _memoryHandle);
|
||||||
}
|
}
|
||||||
|
|
||||||
_buffer = null;
|
_buffer = null;
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
using System.Numerics;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Runtime.Intrinsics;
|
||||||
|
|
||||||
namespace Misaki.HighPerformance.LowLevel.Utilities;
|
namespace Misaki.HighPerformance.LowLevel.Utilities;
|
||||||
|
|
||||||
@@ -20,9 +22,16 @@ public static unsafe partial class MemoryUtility
|
|||||||
/// <returns>Returns a pointer to the allocated memory block.</returns>
|
/// <returns>Returns a pointer to the allocated memory block.</returns>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static void* Malloc(nuint size)
|
public static void* Malloc(nuint size)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
return NativeMemory.Alloc(size);
|
return NativeMemory.Alloc(size);
|
||||||
}
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Allocates a block of memory of the specified size in bytes and initializes it to zero.
|
/// Allocates a block of memory of the specified size in bytes and initializes it to zero.
|
||||||
@@ -31,9 +40,16 @@ public static unsafe partial class MemoryUtility
|
|||||||
/// <returns>Returns a pointer to the allocated and zero-initialized memory block.</returns>
|
/// <returns>Returns a pointer to the allocated and zero-initialized memory block.</returns>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static void* Calloc(nuint size)
|
public static void* Calloc(nuint size)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
return NativeMemory.AllocZeroed(size);
|
return NativeMemory.AllocZeroed(size);
|
||||||
}
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Allocates a block of memory with a specified size and alignment.
|
/// Allocates a block of memory with a specified size and alignment.
|
||||||
@@ -43,9 +59,16 @@ public static unsafe partial class MemoryUtility
|
|||||||
/// <returns>Returns a pointer to the allocated memory block.</returns>
|
/// <returns>Returns a pointer to the allocated memory block.</returns>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static void* AlignedAlloc(nuint size, nuint alignment)
|
public static void* AlignedAlloc(nuint size, nuint alignment)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
return NativeMemory.AlignedAlloc(size, alignment);
|
return NativeMemory.AlignedAlloc(size, alignment);
|
||||||
}
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Resizes a previously allocated memory block to a new size. It returns a pointer to the reallocated memory.
|
/// Resizes a previously allocated memory block to a new size. It returns a pointer to the reallocated memory.
|
||||||
@@ -55,9 +78,16 @@ public static unsafe partial class MemoryUtility
|
|||||||
/// <returns>A pointer to the reallocated memory block, or null if the operation fails.</returns>
|
/// <returns>A pointer to the reallocated memory block, or null if the operation fails.</returns>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static void* Realloc(void* ptr, nuint size)
|
public static void* Realloc(void* ptr, nuint size)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
return NativeMemory.Realloc(ptr, size);
|
return NativeMemory.Realloc(ptr, size);
|
||||||
}
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reallocates memory to a specified size with a given alignment. It returns a pointer to the newly allocated
|
/// Reallocates memory to a specified size with a given alignment. It returns a pointer to the newly allocated
|
||||||
@@ -69,9 +99,16 @@ public static unsafe partial class MemoryUtility
|
|||||||
/// <returns>A pointer to the reallocated memory block, or null if the allocation fails.</returns>
|
/// <returns>A pointer to the reallocated memory block, or null if the allocation fails.</returns>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static void* AlignedRealloc(void* ptr, nuint size, nuint alignment)
|
public static void* AlignedRealloc(void* ptr, nuint size, nuint alignment)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
return NativeMemory.AlignedRealloc(ptr, size, alignment);
|
return NativeMemory.AlignedRealloc(ptr, size, alignment);
|
||||||
}
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Releases the allocated memory pointed to by the given pointer. This helps in managing memory usage effectively.
|
/// Releases the allocated memory pointed to by the given pointer. This helps in managing memory usage effectively.
|
||||||
@@ -130,6 +167,42 @@ public static unsafe partial class MemoryUtility
|
|||||||
NativeMemory.Copy(source, destination, size);
|
NativeMemory.Copy(source, destination, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Moves a block of memory from a source location to a destination location, handling overlapping regions correctly.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="destination">Indicates the memory address where the data will be moved to.</param>
|
||||||
|
/// <param name="source">Specifies the memory address from which data will be moved.</param>
|
||||||
|
/// <param name="size">Defines the number of bytes to be moved from the source to the destination.</param>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static void MemMove(void* destination, void* source, nuint size)
|
||||||
|
{
|
||||||
|
// NativeMemory.Copy use memmove internally.
|
||||||
|
NativeMemory.Copy(source, destination, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Compares two blocks of memory byte by byte for a specified length.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ptr1">A pointer to the first block of memory to compare.</param>
|
||||||
|
/// <param name="ptr2">A pointer to the second block of memory to compare.</param>
|
||||||
|
/// <param name="size">The number of bytes to compare. Must not exceed the length of either memory block.</param>
|
||||||
|
/// <returns>A signed integer that indicates the relative order of the memory blocks: less than zero if the first differing
|
||||||
|
/// byte in ptr1 is less than the corresponding byte in ptr2; zero if all compared bytes are equal; greater than
|
||||||
|
/// zero if the first differing byte in ptr1 is greater than the corresponding byte in ptr2.</returns>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static int MemCmp(void* ptr1, void* ptr2, nuint size)
|
||||||
|
{
|
||||||
|
if (ptr1 == ptr2)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var span1 = new ReadOnlySpan<byte>(ptr1, (int)size);
|
||||||
|
var span2 = new ReadOnlySpan<byte>(ptr2, (int)size);
|
||||||
|
|
||||||
|
return span1.SequenceCompareTo(span2);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Calculates the size in bytes of a specified unmanaged type.
|
/// Calculates the size in bytes of a specified unmanaged type.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -81,7 +81,6 @@ namespace {typeInfo.TypeSymbol.ContainingNamespace.ToDisplayString()}
|
|||||||
protected virtual void GenerateTypeStart()
|
protected virtual void GenerateTypeStart()
|
||||||
{
|
{
|
||||||
sourceBuilder.Append($@"
|
sourceBuilder.Append($@"
|
||||||
//[global::System.Runtime.CompilerServices.SkipLocalsInit]
|
|
||||||
public partial struct {typeInfo.TypeSymbol.Name} : global::System.IEquatable<{typeInfo.TypeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}>
|
public partial struct {typeInfo.TypeSymbol.Name} : global::System.IEquatable<{typeInfo.TypeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}>
|
||||||
{{");
|
{{");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,7 +61,8 @@ namespace Misaki.HighPerformance.Mathematics.CodeGen.Generators
|
|||||||
}
|
}
|
||||||
|
|
||||||
sourceBuilder.AppendLine();
|
sourceBuilder.AppendLine();
|
||||||
sourceBuilder.AppendLine($@"
|
|
||||||
|
sourceBuilder.AppendLine(@$"
|
||||||
public unsafe ref {typeInfo.ComponentTypeFullName} this[int index]
|
public unsafe ref {typeInfo.ComponentTypeFullName} this[int index]
|
||||||
{{
|
{{
|
||||||
{INLINE_METHOD_ATTRIBUTE}
|
{INLINE_METHOD_ATTRIBUTE}
|
||||||
@@ -72,7 +73,7 @@ namespace Misaki.HighPerformance.Mathematics.CodeGen.Generators
|
|||||||
}}
|
}}
|
||||||
}}");
|
}}");
|
||||||
|
|
||||||
sourceBuilder.AppendLine($@"
|
sourceBuilder.AppendLine(@$"
|
||||||
[global::System.Diagnostics.Conditional(""ENABLE_COLLECTION_CHECKS"")]
|
[global::System.Diagnostics.Conditional(""ENABLE_COLLECTION_CHECKS"")]
|
||||||
private void RangeCheck(int index)
|
private void RangeCheck(int index)
|
||||||
{{
|
{{
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ namespace Misaki.HighPerformance.Mathematics.CodeGen.Generators
|
|||||||
private int _vectorBitsSize;
|
private int _vectorBitsSize;
|
||||||
private int _missingComponentsCount;
|
private int _missingComponentsCount;
|
||||||
private string _componentTypePrefix = null!;
|
private string _componentTypePrefix = null!;
|
||||||
|
private bool CanUseVectorStorage => _missingComponentsCount == 0;
|
||||||
|
|
||||||
private readonly List<(string signature, List<string> assignment)> _constructorSignatures = new();
|
private readonly List<(string signature, List<string> assignment)> _constructorSignatures = new();
|
||||||
|
|
||||||
@@ -42,6 +43,20 @@ namespace Misaki.HighPerformance.Mathematics.CodeGen.Generators
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void GenerateTypeStart()
|
||||||
|
{
|
||||||
|
if (CanUseVectorStorage)
|
||||||
|
{
|
||||||
|
sourceBuilder.Append($@"
|
||||||
|
[global::System.Runtime.InteropServices.StructLayout(global::System.Runtime.InteropServices.LayoutKind.Explicit)]
|
||||||
|
public partial struct {typeInfo.TypeSymbol.Name} : global::System.IEquatable<{typeInfo.TypeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}>
|
||||||
|
{{");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
base.GenerateTypeStart();
|
||||||
|
}
|
||||||
|
|
||||||
protected override void GenerateBody()
|
protected override void GenerateBody()
|
||||||
{
|
{
|
||||||
GenerateField();
|
GenerateField();
|
||||||
@@ -81,14 +96,32 @@ namespace Misaki.HighPerformance.Mathematics.CodeGen.Generators
|
|||||||
|
|
||||||
private void GenerateField()
|
private void GenerateField()
|
||||||
{
|
{
|
||||||
|
var componentType = typeInfo.ComponentTypeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
|
||||||
|
|
||||||
|
if (CanUseVectorStorage)
|
||||||
|
{
|
||||||
|
sourceBuilder.AppendLine($@"
|
||||||
|
[global::System.Runtime.InteropServices.FieldOffset(0)]
|
||||||
|
internal global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}<{typeInfo.ComponentTypeFullName}> __v;");
|
||||||
|
|
||||||
for (var i = 0; i < typeInfo.Row; i++)
|
for (var i = 0; i < typeInfo.Row; i++)
|
||||||
{
|
{
|
||||||
sourceBuilder.Append($@"
|
sourceBuilder.AppendLine($@"
|
||||||
public {typeInfo.ComponentTypeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)} {s_vectorComponents[i]};");
|
[global::System.Runtime.InteropServices.FieldOffset({i * typeInfo.ComponentSize})]
|
||||||
|
public {componentType} {s_vectorComponents[i]};");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (var i = 0; i < typeInfo.Row; i++)
|
||||||
|
{
|
||||||
|
sourceBuilder.AppendLine($@"
|
||||||
|
public {componentType} {s_vectorComponents[i]};");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceBuilder.AppendLine();
|
sourceBuilder.AppendLine();
|
||||||
sourceBuilder.AppendLine($@"
|
sourceBuilder.AppendLine(@$"
|
||||||
public unsafe ref {typeInfo.ComponentTypeFullName} this[int index]
|
public unsafe ref {typeInfo.ComponentTypeFullName} this[int index]
|
||||||
{{
|
{{
|
||||||
{INLINE_METHOD_ATTRIBUTE}
|
{INLINE_METHOD_ATTRIBUTE}
|
||||||
@@ -99,7 +132,7 @@ namespace Misaki.HighPerformance.Mathematics.CodeGen.Generators
|
|||||||
}}
|
}}
|
||||||
}}");
|
}}");
|
||||||
|
|
||||||
sourceBuilder.AppendLine($@"
|
sourceBuilder.AppendLine(@$"
|
||||||
[global::System.Diagnostics.Conditional(""ENABLE_COLLECTION_CHECKS"")]
|
[global::System.Diagnostics.Conditional(""ENABLE_COLLECTION_CHECKS"")]
|
||||||
private void RangeCheck(int index)
|
private void RangeCheck(int index)
|
||||||
{{
|
{{
|
||||||
@@ -387,16 +420,59 @@ namespace Misaki.HighPerformance.Mathematics.CodeGen.Generators
|
|||||||
var componentType = typeInfo.ComponentTypeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
|
var componentType = typeInfo.ComponentTypeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
|
||||||
var asResult = $"As{typeSimpleName}()";
|
var asResult = $"As{typeSimpleName}()";
|
||||||
|
|
||||||
|
var canVectorizeBinaryArithmetic = CanUseVectorStorage;
|
||||||
|
var canVectorizeDivide = CanUseVectorStorage && (typeInfo.ComponentTypeSymbol.SpecialType == SpecialType.System_Single || typeInfo.ComponentTypeSymbol.SpecialType == SpecialType.System_Double);
|
||||||
|
|
||||||
StartRegion("Arithmetic Operators");
|
StartRegion("Arithmetic Operators");
|
||||||
|
|
||||||
// Add
|
// Add
|
||||||
sourceBuilder.Append($@"
|
if (canVectorizeBinaryArithmetic)
|
||||||
|
{
|
||||||
|
sourceBuilder.AppendLine($@"
|
||||||
|
{INLINE_METHOD_ATTRIBUTE}
|
||||||
|
public static {typeName} operator +({typeName} lhs, {typeName} rhs)
|
||||||
|
{{
|
||||||
|
return (global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}.Add(lhs.__v, rhs.__v)).{asResult};
|
||||||
|
}}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sourceBuilder.AppendLine($@"
|
||||||
{INLINE_METHOD_ATTRIBUTE}
|
{INLINE_METHOD_ATTRIBUTE}
|
||||||
public static {typeName} operator +({typeName} lhs, {typeName} rhs)
|
public static {typeName} operator +({typeName} lhs, {typeName} rhs)
|
||||||
{{
|
{{
|
||||||
return new {typeName}({string.Join(", ", s_vectorComponents.Take(typeInfo.Row).Select(c => $"lhs.{c} + rhs.{c}"))});
|
return new {typeName}({string.Join(", ", s_vectorComponents.Take(typeInfo.Row).Select(c => $"lhs.{c} + rhs.{c}"))});
|
||||||
|
}}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (canVectorizeBinaryArithmetic)
|
||||||
|
{
|
||||||
|
sourceBuilder.Append($@"
|
||||||
|
|
||||||
|
{INLINE_METHOD_ATTRIBUTE}
|
||||||
|
public static {typeName} operator +({typeName} lhs, {componentType} rhs)
|
||||||
|
{{
|
||||||
|
return (global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}.Add(lhs.__v, global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}.Create(rhs))).{asResult};
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
{INLINE_METHOD_ATTRIBUTE}
|
||||||
|
public static {typeName} operator +({componentType} lhs, {typeName} rhs)
|
||||||
|
{{
|
||||||
|
return (global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}.Add(global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}.Create(lhs), rhs.__v)).{asResult};
|
||||||
|
}}
|
||||||
|
|
||||||
|
#if NET10_0_OR_GREATER
|
||||||
|
{INLINE_METHOD_ATTRIBUTE}
|
||||||
|
public void operator +=({typeName} other)
|
||||||
|
{{
|
||||||
|
this.__v = global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}.Add(this.__v, other.__v);
|
||||||
|
}}
|
||||||
|
#endif");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sourceBuilder.Append($@"
|
||||||
|
|
||||||
{INLINE_METHOD_ATTRIBUTE}
|
{INLINE_METHOD_ATTRIBUTE}
|
||||||
public static {typeName} operator +({typeName} lhs, {componentType} rhs)
|
public static {typeName} operator +({typeName} lhs, {componentType} rhs)
|
||||||
{{
|
{{
|
||||||
@@ -421,15 +497,56 @@ namespace Misaki.HighPerformance.Mathematics.CodeGen.Generators
|
|||||||
sourceBuilder.AppendLine($@"
|
sourceBuilder.AppendLine($@"
|
||||||
}}
|
}}
|
||||||
#endif");
|
#endif");
|
||||||
|
}
|
||||||
|
|
||||||
// Subtract
|
// Subtract
|
||||||
|
if (canVectorizeBinaryArithmetic)
|
||||||
|
{
|
||||||
|
sourceBuilder.AppendLine($@"
|
||||||
|
{INLINE_METHOD_ATTRIBUTE}
|
||||||
|
public static {typeName} operator -({typeName} lhs, {typeName} rhs)
|
||||||
|
{{
|
||||||
|
return (global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}.Subtract(lhs.__v, rhs.__v)).{asResult};
|
||||||
|
}}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
sourceBuilder.AppendLine($@"
|
sourceBuilder.AppendLine($@"
|
||||||
{INLINE_METHOD_ATTRIBUTE}
|
{INLINE_METHOD_ATTRIBUTE}
|
||||||
public static {typeName} operator -({typeName} lhs, {typeName} rhs)
|
public static {typeName} operator -({typeName} lhs, {typeName} rhs)
|
||||||
{{
|
{{
|
||||||
return new {typeName}({string.Join(", ", s_vectorComponents.Take(typeInfo.Row).Select(c => $"lhs.{c} - rhs.{c}"))});
|
return new {typeName}({string.Join(", ", s_vectorComponents.Take(typeInfo.Row).Select(c => $"lhs.{c} - rhs.{c}"))});
|
||||||
|
}}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (canVectorizeBinaryArithmetic)
|
||||||
|
{
|
||||||
|
sourceBuilder.AppendLine($@"
|
||||||
|
|
||||||
|
{INLINE_METHOD_ATTRIBUTE}
|
||||||
|
public static {typeName} operator -({typeName} lhs, {componentType} rhs)
|
||||||
|
{{
|
||||||
|
return (global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}.Subtract(lhs.__v, global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}.Create(rhs))).{asResult};
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
{INLINE_METHOD_ATTRIBUTE}
|
||||||
|
public static {typeName} operator -({componentType} lhs, {typeName} rhs)
|
||||||
|
{{
|
||||||
|
return (global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}.Subtract(global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}.Create(lhs), rhs.__v)).{asResult};
|
||||||
|
}}
|
||||||
|
|
||||||
|
#if NET10_0_OR_GREATER
|
||||||
|
{INLINE_METHOD_ATTRIBUTE}
|
||||||
|
public void operator -=({typeName} other)
|
||||||
|
{{
|
||||||
|
this.__v = global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}.Subtract(this.__v, other.__v);
|
||||||
|
}}
|
||||||
|
#endif");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sourceBuilder.AppendLine($@"
|
||||||
|
|
||||||
{INLINE_METHOD_ATTRIBUTE}
|
{INLINE_METHOD_ATTRIBUTE}
|
||||||
public static {typeName} operator -({typeName} lhs, {componentType} rhs)
|
public static {typeName} operator -({typeName} lhs, {componentType} rhs)
|
||||||
{{
|
{{
|
||||||
@@ -455,15 +572,57 @@ namespace Misaki.HighPerformance.Mathematics.CodeGen.Generators
|
|||||||
sourceBuilder.AppendLine($@"
|
sourceBuilder.AppendLine($@"
|
||||||
}}
|
}}
|
||||||
#endif");
|
#endif");
|
||||||
|
}
|
||||||
|
|
||||||
// Multiply
|
// Multiply
|
||||||
|
if (canVectorizeBinaryArithmetic)
|
||||||
|
{
|
||||||
|
sourceBuilder.AppendLine($@"
|
||||||
|
{INLINE_METHOD_ATTRIBUTE}
|
||||||
|
public static {typeName} operator *({typeName} lhs, {typeName} rhs)
|
||||||
|
{{
|
||||||
|
return (global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}.Multiply(lhs.__v, rhs.__v)).{asResult};
|
||||||
|
}}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
sourceBuilder.AppendLine($@"
|
sourceBuilder.AppendLine($@"
|
||||||
{INLINE_METHOD_ATTRIBUTE}
|
{INLINE_METHOD_ATTRIBUTE}
|
||||||
public static {typeName} operator *({typeName} lhs, {typeName} rhs)
|
public static {typeName} operator *({typeName} lhs, {typeName} rhs)
|
||||||
{{
|
{{
|
||||||
return new {typeName}({string.Join(", ", s_vectorComponents.Take(typeInfo.Row).Select(c => $"lhs.{c} * rhs.{c}"))});
|
return new {typeName}({string.Join(", ", s_vectorComponents.Take(typeInfo.Row).Select(c => $"lhs.{c} * rhs.{c}"))});
|
||||||
|
}}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (canVectorizeBinaryArithmetic)
|
||||||
|
{
|
||||||
|
sourceBuilder.AppendLine($@"
|
||||||
|
|
||||||
|
{INLINE_METHOD_ATTRIBUTE}
|
||||||
|
public static {typeName} operator *({typeName} lhs, {componentType} rhs)
|
||||||
|
{{
|
||||||
|
return (global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}.Multiply(lhs.__v, global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}.Create(rhs))).{asResult};
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
{INLINE_METHOD_ATTRIBUTE}
|
||||||
|
public static {typeName} operator *({componentType} lhs, {typeName} rhs)
|
||||||
|
{{
|
||||||
|
return (global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}.Multiply(global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}.Create(lhs), rhs.__v)).{asResult};
|
||||||
|
}}
|
||||||
|
|
||||||
|
#if NET10_0_OR_GREATER
|
||||||
|
// Use scaler here to let JIT handle the simd optimization since we can not do a in-place vectorlization manually.
|
||||||
|
{INLINE_METHOD_ATTRIBUTE}
|
||||||
|
public void operator *=({typeName} other)
|
||||||
|
{{
|
||||||
|
this.__v = global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}.Multiply(this.__v, other.__v);
|
||||||
|
}}
|
||||||
|
#endif");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sourceBuilder.AppendLine($@"
|
||||||
|
|
||||||
{INLINE_METHOD_ATTRIBUTE}
|
{INLINE_METHOD_ATTRIBUTE}
|
||||||
public static {typeName} operator *({typeName} lhs, {componentType} rhs)
|
public static {typeName} operator *({typeName} lhs, {componentType} rhs)
|
||||||
{{
|
{{
|
||||||
@@ -490,15 +649,57 @@ namespace Misaki.HighPerformance.Mathematics.CodeGen.Generators
|
|||||||
sourceBuilder.AppendLine($@"
|
sourceBuilder.AppendLine($@"
|
||||||
}}
|
}}
|
||||||
#endif");
|
#endif");
|
||||||
|
}
|
||||||
|
|
||||||
// Divide
|
// Divide
|
||||||
|
if (canVectorizeDivide)
|
||||||
|
{
|
||||||
|
sourceBuilder.AppendLine($@"
|
||||||
|
{INLINE_METHOD_ATTRIBUTE}
|
||||||
|
public static {typeName} operator /({typeName} lhs, {typeName} rhs)
|
||||||
|
{{
|
||||||
|
return (global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}.Divide(lhs.__v, rhs.__v)).{asResult};
|
||||||
|
}}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
sourceBuilder.AppendLine($@"
|
sourceBuilder.AppendLine($@"
|
||||||
{INLINE_METHOD_ATTRIBUTE}
|
{INLINE_METHOD_ATTRIBUTE}
|
||||||
public static {typeName} operator /({typeName} lhs, {typeName} rhs)
|
public static {typeName} operator /({typeName} lhs, {typeName} rhs)
|
||||||
{{
|
{{
|
||||||
return new {typeName}({string.Join(", ", s_vectorComponents.Take(typeInfo.Row).Select(c => $"lhs.{c} / rhs.{c}"))});
|
return new {typeName}({string.Join(", ", s_vectorComponents.Take(typeInfo.Row).Select(c => $"lhs.{c} / rhs.{c}"))});
|
||||||
|
}}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (canVectorizeDivide)
|
||||||
|
{
|
||||||
|
sourceBuilder.AppendLine($@"
|
||||||
|
|
||||||
|
{INLINE_METHOD_ATTRIBUTE}
|
||||||
|
public static {typeName} operator /({typeName} lhs, {componentType} rhs)
|
||||||
|
{{
|
||||||
|
return (global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}.Divide(lhs.__v, global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}.Create(rhs))).{asResult};
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
{INLINE_METHOD_ATTRIBUTE}
|
||||||
|
public static {typeName} operator /({componentType} lhs, {typeName} rhs)
|
||||||
|
{{
|
||||||
|
return (global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}.Divide(global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}.Create(lhs), rhs.__v)).{asResult};
|
||||||
|
}}
|
||||||
|
|
||||||
|
#if NET10_0_OR_GREATER
|
||||||
|
// Use scaler here to let JIT handle the simd optimization since we can not do a in-place vectorlization manually.
|
||||||
|
{INLINE_METHOD_ATTRIBUTE}
|
||||||
|
public void operator /=({typeName} other)
|
||||||
|
{{
|
||||||
|
this.__v = global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}.Divide(this.__v, other.__v);
|
||||||
|
}}
|
||||||
|
#endif");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sourceBuilder.AppendLine($@"
|
||||||
|
|
||||||
{INLINE_METHOD_ATTRIBUTE}
|
{INLINE_METHOD_ATTRIBUTE}
|
||||||
public static {typeName} operator /({typeName} lhs, {componentType} rhs)
|
public static {typeName} operator /({typeName} lhs, {componentType} rhs)
|
||||||
{{
|
{{
|
||||||
@@ -525,6 +726,7 @@ namespace Misaki.HighPerformance.Mathematics.CodeGen.Generators
|
|||||||
sourceBuilder.AppendLine($@"
|
sourceBuilder.AppendLine($@"
|
||||||
}}
|
}}
|
||||||
#endif");
|
#endif");
|
||||||
|
}
|
||||||
|
|
||||||
// Modulus
|
// Modulus
|
||||||
sourceBuilder.AppendLine($@"
|
sourceBuilder.AppendLine($@"
|
||||||
@@ -735,13 +937,14 @@ namespace Misaki.HighPerformance.Mathematics.CodeGen.Generators
|
|||||||
public static partial class VectorInterop
|
public static partial class VectorInterop
|
||||||
{{
|
{{
|
||||||
{INLINE_METHOD_ATTRIBUTE}
|
{INLINE_METHOD_ATTRIBUTE}
|
||||||
public unsafe static global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}<{componentType}> AsVector{_vectorBitsSize}(this {typeName} value)
|
public unsafe static global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}<{componentType}> AsVector{_vectorBitsSize}(this in {typeName} value)
|
||||||
{{");
|
{{");
|
||||||
|
|
||||||
if (typeInfo.Row == 4)
|
if (CanUseVectorStorage)
|
||||||
{
|
{
|
||||||
sourceBuilder.Append($@"
|
sourceBuilder.Append($@"
|
||||||
return global::System.Runtime.CompilerServices.Unsafe.BitCast<{typeName}, global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}<{componentType}>>(value);");
|
ref var v = ref global::System.Runtime.CompilerServices.Unsafe.AsRef(in value);
|
||||||
|
return global::System.Runtime.CompilerServices.Unsafe.As<{typeName}, global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}<{componentType}>>(ref v);");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -754,12 +957,24 @@ namespace Misaki.HighPerformance.Mathematics.CodeGen.Generators
|
|||||||
{INLINE_METHOD_ATTRIBUTE}
|
{INLINE_METHOD_ATTRIBUTE}
|
||||||
public unsafe static {typeName} As{typeSimpleName}(this global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}<{componentType}> value)
|
public unsafe static {typeName} As{typeSimpleName}(this global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}<{componentType}> value)
|
||||||
{{");
|
{{");
|
||||||
|
if (CanUseVectorStorage)
|
||||||
|
{
|
||||||
|
sourceBuilder.AppendLine($@"
|
||||||
|
var result = default({typeName});
|
||||||
|
result.__v = value;
|
||||||
|
return result;
|
||||||
|
}}
|
||||||
|
}}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
sourceBuilder.AppendLine($@"
|
sourceBuilder.AppendLine($@"
|
||||||
ref var address = ref global::System.Runtime.CompilerServices.Unsafe.As<global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}<{componentType}>, byte>(ref value);
|
ref var address = ref global::System.Runtime.CompilerServices.Unsafe.As<global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}<{componentType}>, byte>(ref value);
|
||||||
return global::System.Runtime.CompilerServices.Unsafe.ReadUnaligned<{typeName}>(ref address);
|
return global::System.Runtime.CompilerServices.Unsafe.ReadUnaligned<{typeName}>(ref address);
|
||||||
}}
|
}}
|
||||||
}}");
|
}}");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void GenerateMathMethod()
|
private void GenerateMathMethod()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,44 +1,41 @@
|
|||||||
#define VECTOR_BENCHMARK
|
#define NOISE_BENCHMARK
|
||||||
|
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using Misaki.HighPerformance.Mathematics;
|
using Misaki.HighPerformance.Mathematics;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using System.Runtime.Intrinsics;
|
||||||
|
|
||||||
namespace Misaki.HighPerformance.Test.Benchmark;
|
namespace Misaki.HighPerformance.Test.Benchmark;
|
||||||
|
|
||||||
public class MathematicsBenchmark
|
public class MathematicsBenchmark
|
||||||
{
|
{
|
||||||
#if VECTOR_BENCHMARK
|
#if VECTOR_BENCHMARK
|
||||||
private Vector2 _v2a = new Vector2(1, 2);
|
private Vector4 _va = new Vector4(1, 2, 1, 2);
|
||||||
private Vector2 _v2b = new Vector2(3, 4);
|
private Vector4 _vb = new Vector4(3, 4, 3, 4);
|
||||||
|
|
||||||
private float2 _f2a = new float2(1, 2);
|
private float4 _fa = new float4(1, 2, 1, 2);
|
||||||
private float2 _f2b = new float2(3, 4);
|
private float4 _fb = new float4(3, 4, 3, 4);
|
||||||
|
|
||||||
[Benchmark]
|
[Benchmark]
|
||||||
public Vector2 VectorAdd()
|
public Vector4 VectorAdd()
|
||||||
{
|
{
|
||||||
var v = new Vector2(0, 0);
|
|
||||||
|
|
||||||
for (var i = 0; i < 10; i++)
|
for (var i = 0; i < 10; i++)
|
||||||
{
|
{
|
||||||
v = _v2a + _v2b;
|
_va += _vb;
|
||||||
}
|
}
|
||||||
|
|
||||||
return v;
|
return _va;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Benchmark]
|
[Benchmark]
|
||||||
public float2 float2Add()
|
public float4 floatAdd()
|
||||||
{
|
{
|
||||||
var v = new float2(0, 0);
|
|
||||||
|
|
||||||
for (var i = 0; i < 10; i++)
|
for (var i = 0; i < 10; i++)
|
||||||
{
|
{
|
||||||
v = _f2a + _f2b;
|
_fa += _fb;
|
||||||
}
|
}
|
||||||
|
|
||||||
return v;
|
return _fa;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -47,7 +44,7 @@ public class MathematicsBenchmark
|
|||||||
private const int _SIZE = 32;
|
private const int _SIZE = 32;
|
||||||
|
|
||||||
[Benchmark]
|
[Benchmark]
|
||||||
public void VectorNoise()
|
public unsafe void VectorNoise()
|
||||||
{
|
{
|
||||||
var buf = stackalloc float[_SIZE * _SIZE];
|
var buf = stackalloc float[_SIZE * _SIZE];
|
||||||
var job = new Misaki.HighPerformance.Test.Jobs.NoiseJobVector
|
var job = new Misaki.HighPerformance.Test.Jobs.NoiseJobVector
|
||||||
@@ -64,7 +61,7 @@ public class MathematicsBenchmark
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Benchmark]
|
[Benchmark]
|
||||||
public void MathNoise()
|
public unsafe void MathNoise()
|
||||||
{
|
{
|
||||||
var buf = stackalloc float[_SIZE * _SIZE];
|
var buf = stackalloc float[_SIZE * _SIZE];
|
||||||
var job = new Misaki.HighPerformance.Test.Jobs.NoiseJobMath
|
var job = new Misaki.HighPerformance.Test.Jobs.NoiseJobMath
|
||||||
@@ -79,6 +76,24 @@ public class MathematicsBenchmark
|
|||||||
job.Execute(i, 0);
|
job.Execute(i, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
// This is 10x faster than VectorNoise and MathNoise, but writing a burst like compiler to compile MathNoise into this is incredibly hard.
|
||||||
|
public unsafe void MathVNoise()
|
||||||
|
{
|
||||||
|
var buf = stackalloc float[_SIZE * _SIZE];
|
||||||
|
var job = new Misaki.HighPerformance.Test.Jobs.NoiseJobMathV
|
||||||
|
{
|
||||||
|
buffers = buf,
|
||||||
|
width = _SIZE,
|
||||||
|
height = _SIZE,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (var i = 0; i < _SIZE * _SIZE / 8; i++)
|
||||||
|
{
|
||||||
|
job.Execute(i, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if MATRIX_BENCHMARK
|
#if MATRIX_BENCHMARK
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ using Misaki.HighPerformance.Jobs;
|
|||||||
using Misaki.HighPerformance.Mathematics;
|
using Misaki.HighPerformance.Mathematics;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.Intrinsics;
|
||||||
|
using System.Runtime.Intrinsics.X86;
|
||||||
|
|
||||||
namespace Misaki.HighPerformance.Test.Jobs;
|
namespace Misaki.HighPerformance.Test.Jobs;
|
||||||
|
|
||||||
@@ -12,7 +14,7 @@ internal unsafe struct NoiseJobVector : IJobParallelFor
|
|||||||
public int height;
|
public int height;
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static float Frac(float x)
|
public static float Frac(float x)
|
||||||
{
|
{
|
||||||
return x - MathF.Truncate(x);
|
return x - MathF.Truncate(x);
|
||||||
}
|
}
|
||||||
@@ -88,3 +90,101 @@ internal unsafe struct NoiseJobMath : IJobParallelFor
|
|||||||
buffers[loopIndex] = GradientNoise(uv);
|
buffers[loopIndex] = GradientNoise(uv);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal unsafe struct NoiseJobMathV : IJobParallelFor
|
||||||
|
{
|
||||||
|
public float* buffers;
|
||||||
|
public int width;
|
||||||
|
public int height;
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static Vector256<float> Mod289(Vector256<float> x)
|
||||||
|
{
|
||||||
|
var div = x / Vector256.Create(289.0f);
|
||||||
|
var flr = Vector256.Floor(div);
|
||||||
|
return x - (flr * Vector256.Create(289.0f));
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static Vector256<float> Fade(Vector256<float> t)
|
||||||
|
{
|
||||||
|
return t * t * t * (t * (t * Vector256.Create(6.0f) - Vector256.Create(15.0f)) + Vector256.Create(10.0f));
|
||||||
|
}
|
||||||
|
|
||||||
|
// HELPER: Calculate gradients for 8 pixels at once
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static Vector256<float> GradDot(Vector256<float> ix, Vector256<float> iy, Vector256<float> fx, Vector256<float> fy)
|
||||||
|
{
|
||||||
|
var hx = Mod289(ix);
|
||||||
|
var hy = Mod289(iy);
|
||||||
|
|
||||||
|
var p = hx * Vector256.Create(34.0f) + Vector256.Create(1.0f);
|
||||||
|
p = Mod289(p * hx + hy);
|
||||||
|
p = p * Vector256.Create(34.0f) + Vector256.Create(1.0f);
|
||||||
|
p = Mod289(p * hx);
|
||||||
|
|
||||||
|
var r = (p / 41.0f);
|
||||||
|
r = (r - Vector256.Floor(r)) * 2.0f - Vector256<float>.One;
|
||||||
|
|
||||||
|
var gx = r - Vector256.Floor(r + Vector256.Create(0.5f));
|
||||||
|
var gy = Vector256.Abs(r) - Vector256.Create(0.5f);
|
||||||
|
|
||||||
|
// Normalize
|
||||||
|
var len = Vector256.Sqrt(gx * gx + gy * gy);
|
||||||
|
gx /= len;
|
||||||
|
gy /= len;
|
||||||
|
|
||||||
|
return gx * fx + gy * fy;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static Vector256<float> GradientNoiseAVX(Vector256<float> uvX, Vector256<float> uvY)
|
||||||
|
{
|
||||||
|
var ipX = Vector256.Floor(uvX);
|
||||||
|
var ipY = Vector256.Floor(uvY);
|
||||||
|
var fpX = uvX - ipX;
|
||||||
|
var fpY = uvY - ipY;
|
||||||
|
|
||||||
|
var uX = Fade(fpX);
|
||||||
|
var uY = Fade(fpY);
|
||||||
|
|
||||||
|
var d00 = GradDot(ipX, ipY, fpX, fpY);
|
||||||
|
var d01 = GradDot(ipX, ipY + Vector256<float>.One, fpX, fpY - Vector256<float>.One);
|
||||||
|
var d10 = GradDot(ipX + Vector256<float>.One, ipY, fpX - Vector256<float>.One, fpY);
|
||||||
|
var d11 = GradDot(ipX + Vector256<float>.One, ipY + Vector256<float>.One, fpX - Vector256<float>.One, fpY - Vector256<float>.One);
|
||||||
|
|
||||||
|
var lerpX1 = d00 + (d10 - d00) * uX;
|
||||||
|
var lerpX2 = d01 + (d11 - d01) * uX;
|
||||||
|
|
||||||
|
return lerpX1 + (lerpX2 - lerpX1) * uY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Execute(int loopIndex, int threadIndex)
|
||||||
|
{
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
// IMPORTANT: Loop Stride is now 8!
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
int baseIndex = loopIndex * 8;
|
||||||
|
|
||||||
|
// Safety check
|
||||||
|
if (baseIndex + 7 >= width * height)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Calculate Coords
|
||||||
|
int y = baseIndex / width;
|
||||||
|
int x = baseIndex % width;
|
||||||
|
|
||||||
|
// Sequence: 0, 1, 2, 3, 4, 5, 6, 7
|
||||||
|
var vSeqX = Vector256.Create(0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f);
|
||||||
|
var vBaseX = Vector256.Create((float)x) + vSeqX;
|
||||||
|
var vBaseY = Vector256.Create((float)y);
|
||||||
|
|
||||||
|
var vWidth = Vector256.Create((float)width);
|
||||||
|
var vHeight = Vector256.Create((float)height);
|
||||||
|
|
||||||
|
var result = GradientNoiseAVX(vBaseX / vWidth, vBaseY / vHeight);
|
||||||
|
|
||||||
|
// Store 8 floats (32 bytes)
|
||||||
|
Avx.Store(buffers + baseIndex, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
using Misaki.HighPerformance.Image;
|
using Misaki.HighPerformance;
|
||||||
|
using Misaki.HighPerformance.LowLevel;
|
||||||
|
using Misaki.HighPerformance.LowLevel.Utilities;
|
||||||
|
|
||||||
//BenchmarkDotNet.Running.BenchmarkRunner.Run<Misaki.HighPerformance.Test.Benchmark.ParallelNoiseBenchmark>();
|
BenchmarkDotNet.Running.BenchmarkRunner.Run<Misaki.HighPerformance.Test.Benchmark.MathematicsBenchmark>();
|
||||||
|
|
||||||
//using Misaki.HighPerformance.Collections;
|
//using Misaki.HighPerformance.Collections;
|
||||||
//using Misaki.HighPerformance.LowLevel.Buffer;
|
//using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
@@ -38,11 +40,14 @@ using Misaki.HighPerformance.Image;
|
|||||||
// 4, 3, 2, 1);
|
// 4, 3, 2, 1);
|
||||||
//Console.WriteLine(Matrix4x4.Multiply(ma, mb));
|
//Console.WriteLine(Matrix4x4.Multiply(ma, mb));
|
||||||
|
|
||||||
|
//int[] arr1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
||||||
|
//int[] arr2 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
||||||
|
|
||||||
const string _IMAGE_PATH = "C:/Users/Misaki/Downloads/Im/119683453_p2.jpg";
|
//unsafe
|
||||||
|
//{
|
||||||
using var stream = File.OpenRead(_IMAGE_PATH);
|
// fixed (int* p1 = arr1)
|
||||||
var imageInfo = ImageInfo.FromStream(stream);
|
// fixed (int* p2 = arr2)
|
||||||
using var image = ImageResult.FromStream(stream);
|
// {
|
||||||
|
// Console.WriteLine(MemoryUtility.MemCmp(p1, p2, (nuint)(arr1.Length * sizeof(int))));
|
||||||
Console.WriteLine($"{imageInfo.Width}x{imageInfo.Height} {imageInfo.ColorComponents}");
|
// }
|
||||||
|
//}
|
||||||
@@ -20,6 +20,12 @@ public class TestUnsafeArray
|
|||||||
_arr.Dispose();
|
_arr.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[GlobalTestCleanup]
|
||||||
|
public static void GlobalCleanup(TestContext ctx)
|
||||||
|
{
|
||||||
|
AllocationManager.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void TestIndexAccess()
|
public void TestIndexAccess()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,28 +7,24 @@ namespace Misaki.HighPerformance.Buffer
|
|||||||
where T : class
|
where T : class
|
||||||
{
|
{
|
||||||
private readonly Func<T> _factory;
|
private readonly Func<T> _factory;
|
||||||
|
private readonly Action<T>? _resetAction;
|
||||||
private readonly ConcurrentQueue<T> _pool = new();
|
private readonly ConcurrentQueue<T> _pool = new();
|
||||||
|
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
|
|
||||||
public uint InitialSize
|
public int InitialSize
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
}
|
}
|
||||||
|
|
||||||
public uint MaxSize
|
public ObjectPool(Func<T> factory, Action<T>? resetAction, int initialSize = 0)
|
||||||
{
|
|
||||||
get;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ObjectPool(Func<T> factory, uint initialSize = uint.MinValue, uint maxSize = uint.MaxValue)
|
|
||||||
{
|
{
|
||||||
_factory = factory;
|
_factory = factory;
|
||||||
|
_resetAction = resetAction;
|
||||||
|
|
||||||
InitialSize = initialSize;
|
InitialSize = initialSize;
|
||||||
MaxSize = maxSize;
|
|
||||||
|
|
||||||
if (initialSize != uint.MinValue)
|
if (initialSize > 0)
|
||||||
{
|
{
|
||||||
for (var i = 0; i < initialSize; i++)
|
for (var i = 0; i < initialSize; i++)
|
||||||
{
|
{
|
||||||
@@ -51,8 +47,6 @@ namespace Misaki.HighPerformance.Buffer
|
|||||||
}
|
}
|
||||||
|
|
||||||
var newInstance = _factory();
|
var newInstance = _factory();
|
||||||
_pool.Enqueue(newInstance);
|
|
||||||
|
|
||||||
return newInstance;
|
return newInstance;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,20 +67,15 @@ namespace Misaki.HighPerformance.Buffer
|
|||||||
{
|
{
|
||||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||||
|
|
||||||
if (_pool.Count < MaxSize)
|
_resetAction?.Invoke(obj);
|
||||||
{
|
|
||||||
_pool.Enqueue(obj);
|
_pool.Enqueue(obj);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public void Reset()
|
public void Reset()
|
||||||
{
|
{
|
||||||
foreach (var obj in _pool)
|
foreach (var obj in _pool)
|
||||||
{
|
{
|
||||||
if (obj is IDisposable disposable)
|
_resetAction?.Invoke(obj);
|
||||||
{
|
|
||||||
disposable.Dispose();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_pool.Clear();
|
_pool.Clear();
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||||
<Authors>Misaki</Authors>
|
<Authors>Misaki</Authors>
|
||||||
<AssemblyVersion>1.0.3</AssemblyVersion>
|
<AssemblyVersion>1.0.4</AssemblyVersion>
|
||||||
<Version>$(AssemblyVersion)</Version>
|
<Version>$(AssemblyVersion)</Version>
|
||||||
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||||
<PackageProjectUrl>https://git.personalnas.com/Misaki/Misaki.HighPerformance.git</PackageProjectUrl>
|
<PackageProjectUrl>https://git.personalnas.com/Misaki/Misaki.HighPerformance.git</PackageProjectUrl>
|
||||||
|
|||||||
15
Misaki.HighPerformance/Union.cs
Normal file
15
Misaki.HighPerformance/Union.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Misaki.HighPerformance;
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Explicit)]
|
||||||
|
public struct Union<T0, T1>
|
||||||
|
where T0 : unmanaged
|
||||||
|
where T1 : unmanaged
|
||||||
|
{
|
||||||
|
[FieldOffset(0)]
|
||||||
|
public T0 v0;
|
||||||
|
|
||||||
|
[FieldOffset(0)]
|
||||||
|
public T1 v1;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user