Fix source only package issue in nuget

This commit is contained in:
2026-02-22 12:36:51 +09:00
parent 49dc44605b
commit ffac1c643e
35 changed files with 69 additions and 8 deletions

View File

@@ -1,92 +0,0 @@
using Misaki.HighPerformance.LowLevel.Buffer;
namespace Misaki.HighPerformance.LowLevel.Collections.Contracts;
public unsafe interface IUnsafeCollection : IDisposable
{
/// <summary>
/// Indicates whether the object has been created. Returns true if the object is created, otherwise false.
/// </summary>
bool IsCreated
{
get;
}
/// <summary>
/// Removes all elements from the collection. The collection will be empty after this operation.
/// </summary>
void Clear();
/// <summary>
/// Returns a pointer to an unmanaged memory location. This pointer can be used for low-level memory operations.
/// </summary>
/// <returns>The method returns a void pointer to the unsafe memory location.</returns>
void* GetUnsafePtr();
}
public interface IUnsafeCollection<T> : IUnsafeCollection, IEnumerable<T>
where T : unmanaged
{
/// <summary>
/// Gets the number of elements in a collection. The value is read-only.
/// </summary>
int Count
{
get;
}
/// <summary>
/// Changes the size of a collection to the specified value.
/// </summary>
/// <remarks>This is to adjust the element count of the collection, not the size of the underlying buffer in memory.</remarks>
/// <param name="newSize">Specifies the new size to which the collection should be adjusted.</param>
/// <param name="option">Specifies allocation options that may affect how memory is managed during the resize operation.</param>
void Resize(int newSize, AllocationOption option);
}
public interface IUnsafeHashCollection<T> : IEnumerable<T>, IDisposable
where T : unmanaged
{
/// <summary>
/// Indicates whether the object has been created. Returns true if the object is created, otherwise false.
/// </summary>
bool IsCreated
{
get;
}
/// <summary>
/// Gets the number of elements in a collection. The value is read-only.
/// </summary>
int Count
{
get;
}
/// <summary>
/// Removes all elements from the collection. The collection will be empty after this operation.
/// </summary>
void Clear();
/// <summary>
/// Changes the size of a collection to the specified value.
/// </summary>
/// <remarks>This is to adjust the element count of the collection, not the size of the underlying buffer in memory.</remarks>
/// <param name="newSize">Specifies the new size to which the collection should be adjusted.</param>
/// <param name="option">Specifies allocation options that may affect how memory is managed during the resize operation.</param>
void Resize(int newSize, AllocationOption option);
}
public interface IUnTypedCollection : IUnsafeCollection
{
/// <summary>
/// The total size of the buffer in bytes.
/// </summary>
nuint Size
{
get;
}
ref T GetElementAt<T>(nuint index)
where T : unmanaged;
}

View File

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

View File

@@ -1,108 +0,0 @@
<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".gen.cs" #>
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
namespace Misaki.HighPerformance.LowLevel.Collections;
<# for (int i = 32; i <= 4096; i *= 2) { #>
/// <summary>
/// Represents a stack allocated fixed-size UTF-8 encoded string of length <#= i #> bytes.
/// </summary>
/// <remarks>
/// This struct is designed to hold data on the stack. Every copy of this struct causes a copy of the underlying data.
/// If you need a heap allocated fixed-size UTF-8 encoded string of length <#= i #> bytes, consider using <see cref="Misaki.HighPerformance.Unsafe.Buffer.FixedString<#= i #>"/>.
/// </remarks>
[StructLayout(LayoutKind.Sequential, Size = <#= i #>)]
public unsafe struct FixedString<#= i #>
{
public const int MAX_LENGTH = <#= (i - 2) / 2 #>;
private ushort _length;
private fixed char _buffer[MAX_LENGTH];
public readonly ushort Length => _length;
public string Value
{
get
{
fixed (char* bufferPtr = _buffer)
{
return new string(bufferPtr, 0, _length);
}
}
set
{
if (string.IsNullOrEmpty(value))
{
_length = 0;
return;
}
if (value.Length > MAX_LENGTH)
{
throw new ArgumentException("Input string is too long to fit in FixedString<#= i #>.");
}
_length = (ushort)value.Length;
fixed (char* bufferPtr = _buffer)
fixed (char* valuePtr = value)
{
Unsafe.CopyBlockUnaligned(bufferPtr, valuePtr, (uint)(_length * sizeof(char)));
}
}
}
public FixedString<#= i #>(string input)
: this(input.AsSpan())
{
}
public FixedString<#= i #>(ReadOnlySpan<char> input)
{
if (input.Length > MAX_LENGTH)
{
throw new ArgumentException("Input byte array is too long to fit in FixedString<#= i #>.");
}
_length = (ushort)input.Length;
fixed (char* inputPtr = input)
fixed (char* bufferPtr = _buffer)
{
Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, (uint)(_length * sizeof(char)));
}
}
public FixedString<#= i #>(char* input, ushort length)
: this(new ReadOnlySpan<char>(input, length))
{
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<byte> AsSpan()
{
fixed (char* ptr = _buffer)
{
return new(ptr, _length);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly char* GetUnsafePtr()
{
return (char*)((ushort*)Unsafe.AsPointer(in this) + 1);
}
public override string ToString()
{
return Value;
}
}
<# } #>

View File

@@ -1,918 +0,0 @@
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.FixedText32"/>.
/// </remarks>
[StructLayout(LayoutKind.Sequential, Size = 32)]
public unsafe struct FixedText32
{
public const int MAX_LENGTH = 30;
private ushort _length;
private fixed byte _buffer[MAX_LENGTH];
public readonly ushort Length => _length;
public string Value
{
get
{
fixed (byte* bufferPtr = _buffer)
{
return Encoding.UTF8.GetString(bufferPtr, _length);
}
}
set
{
if (string.IsNullOrEmpty(value))
{
_length = 0;
return;
}
var maxBytes = Encoding.UTF8.GetByteCount(value);
if (maxBytes > MAX_LENGTH)
{
throw new ArgumentException("Input string is too long to fit in FixedText32.");
}
fixed (byte* bufferPtr = _buffer)
{
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, MAX_LENGTH));
}
}
}
public FixedText32(ReadOnlySpan<char> input)
{
var maxBytes = Encoding.UTF8.GetByteCount(input);
if (maxBytes > MAX_LENGTH)
{
throw new ArgumentException("Input string is too long to fit in FixedText32.");
}
fixed (char* inputPtr = input)
fixed (byte* bufferPtr = _buffer)
{
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, MAX_LENGTH);
_length = (ushort)actualByteCount;
}
}
public FixedText32(string input)
: this(input.AsSpan())
{
}
public FixedText32(char* input, ushort length)
: this(new Span<char>(input, length))
{
}
public FixedText32(ReadOnlySpan<byte> input)
{
if (input.Length > MAX_LENGTH)
{
throw new ArgumentException("Input byte array is too long to fit in FixedText32.");
}
_length = (ushort)input.Length;
fixed (byte* inputPtr = input)
fixed (byte* bufferPtr = _buffer)
{
Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
}
}
public FixedText32(byte* input, ushort length)
: this(new ReadOnlySpan<byte>(input, length))
{
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<byte> AsSpan()
{
fixed (byte* ptr = _buffer)
{
return new(ptr, _length);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly byte* GetUnsafePtr()
{
return (byte*)((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.FixedText64"/>.
/// </remarks>
[StructLayout(LayoutKind.Sequential, Size = 64)]
public unsafe struct FixedText64
{
public const int MAX_LENGTH = 62;
private ushort _length;
private fixed byte _buffer[MAX_LENGTH];
public readonly ushort Length => _length;
public string Value
{
get
{
fixed (byte* bufferPtr = _buffer)
{
return Encoding.UTF8.GetString(bufferPtr, _length);
}
}
set
{
if (string.IsNullOrEmpty(value))
{
_length = 0;
return;
}
var maxBytes = Encoding.UTF8.GetByteCount(value);
if (maxBytes > MAX_LENGTH)
{
throw new ArgumentException("Input string is too long to fit in FixedText64.");
}
fixed (byte* bufferPtr = _buffer)
{
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, MAX_LENGTH));
}
}
}
public FixedText64(ReadOnlySpan<char> input)
{
var maxBytes = Encoding.UTF8.GetByteCount(input);
if (maxBytes > MAX_LENGTH)
{
throw new ArgumentException("Input string is too long to fit in FixedText64.");
}
fixed (char* inputPtr = input)
fixed (byte* bufferPtr = _buffer)
{
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, MAX_LENGTH);
_length = (ushort)actualByteCount;
}
}
public FixedText64(string input)
: this(input.AsSpan())
{
}
public FixedText64(char* input, ushort length)
: this(new Span<char>(input, length))
{
}
public FixedText64(ReadOnlySpan<byte> input)
{
if (input.Length > MAX_LENGTH)
{
throw new ArgumentException("Input byte array is too long to fit in FixedText64.");
}
_length = (ushort)input.Length;
fixed (byte* inputPtr = input)
fixed (byte* bufferPtr = _buffer)
{
Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
}
}
public FixedText64(byte* input, ushort length)
: this(new ReadOnlySpan<byte>(input, length))
{
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<byte> AsSpan()
{
fixed (byte* ptr = _buffer)
{
return new(ptr, _length);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly byte* GetUnsafePtr()
{
return (byte*)((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.FixedText128"/>.
/// </remarks>
[StructLayout(LayoutKind.Sequential, Size = 128)]
public unsafe struct FixedText128
{
public const int MAX_LENGTH = 126;
private ushort _length;
private fixed byte _buffer[MAX_LENGTH];
public readonly ushort Length => _length;
public string Value
{
get
{
fixed (byte* bufferPtr = _buffer)
{
return Encoding.UTF8.GetString(bufferPtr, _length);
}
}
set
{
if (string.IsNullOrEmpty(value))
{
_length = 0;
return;
}
var maxBytes = Encoding.UTF8.GetByteCount(value);
if (maxBytes > MAX_LENGTH)
{
throw new ArgumentException("Input string is too long to fit in FixedText128.");
}
fixed (byte* bufferPtr = _buffer)
{
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, MAX_LENGTH));
}
}
}
public FixedText128(ReadOnlySpan<char> input)
{
var maxBytes = Encoding.UTF8.GetByteCount(input);
if (maxBytes > MAX_LENGTH)
{
throw new ArgumentException("Input string is too long to fit in FixedText128.");
}
fixed (char* inputPtr = input)
fixed (byte* bufferPtr = _buffer)
{
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, MAX_LENGTH);
_length = (ushort)actualByteCount;
}
}
public FixedText128(string input)
: this(input.AsSpan())
{
}
public FixedText128(char* input, ushort length)
: this(new Span<char>(input, length))
{
}
public FixedText128(ReadOnlySpan<byte> input)
{
if (input.Length > MAX_LENGTH)
{
throw new ArgumentException("Input byte array is too long to fit in FixedText128.");
}
_length = (ushort)input.Length;
fixed (byte* inputPtr = input)
fixed (byte* bufferPtr = _buffer)
{
Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
}
}
public FixedText128(byte* input, ushort length)
: this(new ReadOnlySpan<byte>(input, length))
{
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<byte> AsSpan()
{
fixed (byte* ptr = _buffer)
{
return new(ptr, _length);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly byte* GetUnsafePtr()
{
return (byte*)((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.FixedText256"/>.
/// </remarks>
[StructLayout(LayoutKind.Sequential, Size = 256)]
public unsafe struct FixedText256
{
public const int MAX_LENGTH = 254;
private ushort _length;
private fixed byte _buffer[MAX_LENGTH];
public readonly ushort Length => _length;
public string Value
{
get
{
fixed (byte* bufferPtr = _buffer)
{
return Encoding.UTF8.GetString(bufferPtr, _length);
}
}
set
{
if (string.IsNullOrEmpty(value))
{
_length = 0;
return;
}
var maxBytes = Encoding.UTF8.GetByteCount(value);
if (maxBytes > MAX_LENGTH)
{
throw new ArgumentException("Input string is too long to fit in FixedText256.");
}
fixed (byte* bufferPtr = _buffer)
{
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, MAX_LENGTH));
}
}
}
public FixedText256(ReadOnlySpan<char> input)
{
var maxBytes = Encoding.UTF8.GetByteCount(input);
if (maxBytes > MAX_LENGTH)
{
throw new ArgumentException("Input string is too long to fit in FixedText256.");
}
fixed (char* inputPtr = input)
fixed (byte* bufferPtr = _buffer)
{
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, MAX_LENGTH);
_length = (ushort)actualByteCount;
}
}
public FixedText256(string input)
: this(input.AsSpan())
{
}
public FixedText256(char* input, ushort length)
: this(new Span<char>(input, length))
{
}
public FixedText256(ReadOnlySpan<byte> input)
{
if (input.Length > MAX_LENGTH)
{
throw new ArgumentException("Input byte array is too long to fit in FixedText256.");
}
_length = (ushort)input.Length;
fixed (byte* inputPtr = input)
fixed (byte* bufferPtr = _buffer)
{
Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
}
}
public FixedText256(byte* input, ushort length)
: this(new ReadOnlySpan<byte>(input, length))
{
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<byte> AsSpan()
{
fixed (byte* ptr = _buffer)
{
return new(ptr, _length);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly byte* GetUnsafePtr()
{
return (byte*)((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.FixedText512"/>.
/// </remarks>
[StructLayout(LayoutKind.Sequential, Size = 512)]
public unsafe struct FixedText512
{
public const int MAX_LENGTH = 510;
private ushort _length;
private fixed byte _buffer[MAX_LENGTH];
public readonly ushort Length => _length;
public string Value
{
get
{
fixed (byte* bufferPtr = _buffer)
{
return Encoding.UTF8.GetString(bufferPtr, _length);
}
}
set
{
if (string.IsNullOrEmpty(value))
{
_length = 0;
return;
}
var maxBytes = Encoding.UTF8.GetByteCount(value);
if (maxBytes > MAX_LENGTH)
{
throw new ArgumentException("Input string is too long to fit in FixedText512.");
}
fixed (byte* bufferPtr = _buffer)
{
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, MAX_LENGTH));
}
}
}
public FixedText512(ReadOnlySpan<char> input)
{
var maxBytes = Encoding.UTF8.GetByteCount(input);
if (maxBytes > MAX_LENGTH)
{
throw new ArgumentException("Input string is too long to fit in FixedText512.");
}
fixed (char* inputPtr = input)
fixed (byte* bufferPtr = _buffer)
{
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, MAX_LENGTH);
_length = (ushort)actualByteCount;
}
}
public FixedText512(string input)
: this(input.AsSpan())
{
}
public FixedText512(char* input, ushort length)
: this(new Span<char>(input, length))
{
}
public FixedText512(ReadOnlySpan<byte> input)
{
if (input.Length > MAX_LENGTH)
{
throw new ArgumentException("Input byte array is too long to fit in FixedText512.");
}
_length = (ushort)input.Length;
fixed (byte* inputPtr = input)
fixed (byte* bufferPtr = _buffer)
{
Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
}
}
public FixedText512(byte* input, ushort length)
: this(new ReadOnlySpan<byte>(input, length))
{
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<byte> AsSpan()
{
fixed (byte* ptr = _buffer)
{
return new(ptr, _length);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly byte* GetUnsafePtr()
{
return (byte*)((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.FixedText1024"/>.
/// </remarks>
[StructLayout(LayoutKind.Sequential, Size = 1024)]
public unsafe struct FixedText1024
{
public const int MAX_LENGTH = 1022;
private ushort _length;
private fixed byte _buffer[MAX_LENGTH];
public readonly ushort Length => _length;
public string Value
{
get
{
fixed (byte* bufferPtr = _buffer)
{
return Encoding.UTF8.GetString(bufferPtr, _length);
}
}
set
{
if (string.IsNullOrEmpty(value))
{
_length = 0;
return;
}
var maxBytes = Encoding.UTF8.GetByteCount(value);
if (maxBytes > MAX_LENGTH)
{
throw new ArgumentException("Input string is too long to fit in FixedText1024.");
}
fixed (byte* bufferPtr = _buffer)
{
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, MAX_LENGTH));
}
}
}
public FixedText1024(ReadOnlySpan<char> input)
{
var maxBytes = Encoding.UTF8.GetByteCount(input);
if (maxBytes > MAX_LENGTH)
{
throw new ArgumentException("Input string is too long to fit in FixedText1024.");
}
fixed (char* inputPtr = input)
fixed (byte* bufferPtr = _buffer)
{
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, MAX_LENGTH);
_length = (ushort)actualByteCount;
}
}
public FixedText1024(string input)
: this(input.AsSpan())
{
}
public FixedText1024(char* input, ushort length)
: this(new Span<char>(input, length))
{
}
public FixedText1024(ReadOnlySpan<byte> input)
{
if (input.Length > MAX_LENGTH)
{
throw new ArgumentException("Input byte array is too long to fit in FixedText1024.");
}
_length = (ushort)input.Length;
fixed (byte* inputPtr = input)
fixed (byte* bufferPtr = _buffer)
{
Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
}
}
public FixedText1024(byte* input, ushort length)
: this(new ReadOnlySpan<byte>(input, length))
{
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<byte> AsSpan()
{
fixed (byte* ptr = _buffer)
{
return new(ptr, _length);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly byte* GetUnsafePtr()
{
return (byte*)((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.FixedText2048"/>.
/// </remarks>
[StructLayout(LayoutKind.Sequential, Size = 2048)]
public unsafe struct FixedText2048
{
public const int MAX_LENGTH = 2046;
private ushort _length;
private fixed byte _buffer[MAX_LENGTH];
public readonly ushort Length => _length;
public string Value
{
get
{
fixed (byte* bufferPtr = _buffer)
{
return Encoding.UTF8.GetString(bufferPtr, _length);
}
}
set
{
if (string.IsNullOrEmpty(value))
{
_length = 0;
return;
}
var maxBytes = Encoding.UTF8.GetByteCount(value);
if (maxBytes > MAX_LENGTH)
{
throw new ArgumentException("Input string is too long to fit in FixedText2048.");
}
fixed (byte* bufferPtr = _buffer)
{
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, MAX_LENGTH));
}
}
}
public FixedText2048(ReadOnlySpan<char> input)
{
var maxBytes = Encoding.UTF8.GetByteCount(input);
if (maxBytes > MAX_LENGTH)
{
throw new ArgumentException("Input string is too long to fit in FixedText2048.");
}
fixed (char* inputPtr = input)
fixed (byte* bufferPtr = _buffer)
{
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, MAX_LENGTH);
_length = (ushort)actualByteCount;
}
}
public FixedText2048(string input)
: this(input.AsSpan())
{
}
public FixedText2048(char* input, ushort length)
: this(new Span<char>(input, length))
{
}
public FixedText2048(ReadOnlySpan<byte> input)
{
if (input.Length > MAX_LENGTH)
{
throw new ArgumentException("Input byte array is too long to fit in FixedText2048.");
}
_length = (ushort)input.Length;
fixed (byte* inputPtr = input)
fixed (byte* bufferPtr = _buffer)
{
Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
}
}
public FixedText2048(byte* input, ushort length)
: this(new ReadOnlySpan<byte>(input, length))
{
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<byte> AsSpan()
{
fixed (byte* ptr = _buffer)
{
return new(ptr, _length);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly byte* GetUnsafePtr()
{
return (byte*)((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.FixedText4096"/>.
/// </remarks>
[StructLayout(LayoutKind.Sequential, Size = 4096)]
public unsafe struct FixedText4096
{
public const int MAX_LENGTH = 4094;
private ushort _length;
private fixed byte _buffer[MAX_LENGTH];
public readonly ushort Length => _length;
public string Value
{
get
{
fixed (byte* bufferPtr = _buffer)
{
return Encoding.UTF8.GetString(bufferPtr, _length);
}
}
set
{
if (string.IsNullOrEmpty(value))
{
_length = 0;
return;
}
var maxBytes = Encoding.UTF8.GetByteCount(value);
if (maxBytes > MAX_LENGTH)
{
throw new ArgumentException("Input string is too long to fit in FixedText4096.");
}
fixed (byte* bufferPtr = _buffer)
{
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, MAX_LENGTH));
}
}
}
public FixedText4096(ReadOnlySpan<char> input)
{
var maxBytes = Encoding.UTF8.GetByteCount(input);
if (maxBytes > MAX_LENGTH)
{
throw new ArgumentException("Input string is too long to fit in FixedText4096.");
}
fixed (char* inputPtr = input)
fixed (byte* bufferPtr = _buffer)
{
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, MAX_LENGTH);
_length = (ushort)actualByteCount;
}
}
public FixedText4096(string input)
: this(input.AsSpan())
{
}
public FixedText4096(char* input, ushort length)
: this(new Span<char>(input, length))
{
}
public FixedText4096(ReadOnlySpan<byte> input)
{
if (input.Length > MAX_LENGTH)
{
throw new ArgumentException("Input byte array is too long to fit in FixedText4096.");
}
_length = (ushort)input.Length;
fixed (byte* inputPtr = input)
fixed (byte* bufferPtr = _buffer)
{
Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
}
}
public FixedText4096(byte* input, ushort length)
: this(new ReadOnlySpan<byte>(input, length))
{
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<byte> AsSpan()
{
fixed (byte* ptr = _buffer)
{
return new(ptr, _length);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly byte* GetUnsafePtr()
{
return (byte*)((ushort*)Unsafe.AsPointer(in this) + 1);
}
public override string ToString()
{
return Value;
}
}

View File

@@ -1,128 +0,0 @@
<#@ 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.FixedText<#= i #>"/>.
/// </remarks>
[StructLayout(LayoutKind.Sequential, Size = <#= i #>)]
public unsafe struct FixedText<#= i #>
{
public const int MAX_LENGTH = <#= i - 2 #>;
private ushort _length;
private fixed byte _buffer[MAX_LENGTH];
public readonly ushort Length => _length;
public string Value
{
get
{
fixed (byte* bufferPtr = _buffer)
{
return Encoding.UTF8.GetString(bufferPtr, _length);
}
}
set
{
if (string.IsNullOrEmpty(value))
{
_length = 0;
return;
}
var maxBytes = Encoding.UTF8.GetByteCount(value);
if (maxBytes > MAX_LENGTH)
{
throw new ArgumentException("Input string is too long to fit in FixedText<#= i #>.");
}
fixed (byte* bufferPtr = _buffer)
{
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, MAX_LENGTH));
}
}
}
public FixedText<#= i #>(ReadOnlySpan<char> input)
{
var maxBytes = Encoding.UTF8.GetByteCount(input);
if (maxBytes > MAX_LENGTH)
{
throw new ArgumentException("Input string is too long to fit in FixedText<#= i #>.");
}
fixed (char* inputPtr = input)
fixed (byte* bufferPtr = _buffer)
{
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, MAX_LENGTH);
_length = (ushort)actualByteCount;
}
}
public FixedText<#= i #>(string input)
: this(input.AsSpan())
{
}
public FixedText<#= i #>(char* input, ushort length)
: this(new Span<char>(input, length))
{
}
public FixedText<#= i #>(ReadOnlySpan<byte> input)
{
if (input.Length > MAX_LENGTH)
{
throw new ArgumentException("Input byte array is too long to fit in FixedText<#= i #>.");
}
_length = (ushort)input.Length;
fixed (byte* inputPtr = input)
fixed (byte* bufferPtr = _buffer)
{
Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
}
}
public FixedText<#= i #>(byte* input, ushort length)
: this(new ReadOnlySpan<byte>(input, length))
{
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<byte> AsSpan()
{
fixed (byte* ptr = _buffer)
{
return new(ptr, _length);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly byte* GetUnsafePtr()
{
return (byte*)((ushort*)Unsafe.AsPointer(in this) + 1);
}
public override string ToString()
{
return Value;
}
}
<# } #>

View File

@@ -1,123 +0,0 @@
using Misaki.HighPerformance.LowLevel.Utilities;
using System.Collections;
using System.Diagnostics;
using System.Runtime.CompilerServices;
namespace Misaki.HighPerformance.LowLevel.Collections;
/// <summary>
/// Provides a read-only, unsafe view over a contiguous region of unmanaged memory as an array of elements of type T.
/// Enables efficient, low-level access to memory without copying or additional safety checks.
/// </summary>
/// <remarks>
/// This read only collection does not own the memory it points to. The user is responsible for ensuring the memory remains valid for the lifetime of this structure.
/// The goal of this struc is similar to <see cref="ReadOnlySpan{T}"/>, but it can be used in contexts where spans are not allowed, such as fields in structs and shared across threads.
/// </remarks>
/// <typeparam name="T">The type of elements in the collection. Must be an unmanaged type.</typeparam>
public readonly unsafe struct ReadOnlyUnsafeCollection<T> : IEnumerable<T>
where T : unmanaged
{
public struct Enumerator : IEnumerator<T>
{
private readonly ReadOnlyUnsafeCollection<T> _collection;
private int _index;
public readonly T Current => _collection[_index];
readonly object IEnumerator.Current => Current;
public Enumerator(ref readonly ReadOnlyUnsafeCollection<T> array)
{
_collection = array;
_index = -1;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext()
{
_index++;
return _index < _collection.Count;
}
public void Reset()
{
_index = -1;
}
public void Dispose()
{
}
}
private readonly T* _buffer;
private readonly int _count;
public int Count => _count;
public ref readonly T this[int index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
CheckIndexBounds(index);
return ref UnsafeUtility.ReadArrayElementRef<T>(_buffer, index);
}
}
public ref readonly T this[uint index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
CheckIndexBounds((int)index);
return ref UnsafeUtility.ReadArrayElementRef<T>(_buffer, index);
}
}
public Enumerator GetEnumerator() => new Enumerator(in this);
IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public ReadOnlyUnsafeCollection(T* buffer, int count)
{
_buffer = buffer;
_count = count;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Conditional("ENABLE_COLLECTION_CHECKS")]
private readonly void CheckIndexBounds(int index)
{
if (index >= _count)
{
throw new ArgumentOutOfRangeException(nameof(index), "Index is out of range.");
}
}
/// <summary>
/// Returns a read-only span that represents the valid elements in the underlying buffer.
/// </summary>
/// <returns>A <see cref="ReadOnlySpan{T}"/> containing the elements of the buffer up to the current count.</returns>
public ReadOnlySpan<T> AsSpan()
{
return new ReadOnlySpan<T>(_buffer, _count);
}
/// <summary>
/// Reinterprets the underlying collection as a read-only collection of a different unmanaged type without copying the data.
/// </summary>
/// <typeparam name="U">The unmanaged type to reinterpret the collection elements as.</typeparam>
/// <returns>A new ReadOnlyUnsafeCollection<U> that provides a read-only view of the same memory, interpreted as elements of type U.</returns>
/// <exception cref="InvalidOperationException">Thrown if the total size of the underlying collection is not a multiple of the size of type U, making the reinterpretation invalid.</exception>
public ReadOnlyUnsafeCollection<U> Reinterpret<U>()
where U : unmanaged
{
var totalSize = (nuint)(Count * sizeof(T));
if (totalSize % (nuint)sizeof(U) != 0)
{
throw new InvalidOperationException("Cannot reinterpret collection: size mismatch.");
}
var newCount = (int)(totalSize / (nuint)sizeof(U));
return new ReadOnlyUnsafeCollection<U>((U*)_buffer, newCount);
}
}

View File

@@ -1,267 +0,0 @@
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
using Misaki.HighPerformance.LowLevel.Utilities;
using System.Runtime.CompilerServices;
namespace Misaki.HighPerformance.LowLevel.Collections;
public unsafe struct UnTypedArray : IUnTypedCollection
{
private void* _buffer;
private nuint _size;
private nuint _alignment;
private MemoryHandle _memoryHandle;
private AllocationHandle _allocationHandle;
public readonly nuint Size => _size;
public readonly nuint Alignment => _alignment;
public readonly bool IsCreated
{
get
{
if (_buffer != null)
{
if (_allocationHandle.IsValid != null)
{
return _allocationHandle.IsValid(_allocationHandle.State, _memoryHandle);
}
else
{
return true;
}
}
return false;
}
}
/// <summary>
/// Constructs an UnsafeArray with a default size of 1 and uses the Persistent allocator.
/// </summary>
public UnTypedArray()
: this(0, 8, Allocator.Invalid)
{
}
public UnTypedArray(nuint size, nuint alignment, AllocationHandle handle, AllocationOption allocationOption = AllocationOption.None)
{
if (size <= 0)
{
throw new ArgumentOutOfRangeException(nameof(size), "Count must be greater than zero.");
}
if (handle.Alloc == null)
{
throw new InvalidOperationException("Target allocation handle does not support allocation.");
}
MemoryHandle memHandle;
_buffer = handle.Alloc(_allocationHandle.State, size, alignment, allocationOption, &memHandle);
_size = size;
_alignment = alignment;
_memoryHandle = memHandle;
_allocationHandle = handle;
}
/// <summary>
/// Initializes a new instance of UnsafeArray with a specified number of elements and an allocation type.
/// </summary>
/// <param name="count">Specifies the number of elements to allocate in the array, which must be greater than zero.</param>
/// <param name="allocator">Specifies the allocator to use for memory allocation, which determines the memory management strategy.</param>
/// <param name="allocationOption">Determines how the memory should be allocated.</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when the specified number of elements is less than or equal to zero.</exception>
public UnTypedArray(nuint size, nuint alignment, Allocator allocator, AllocationOption allocationOption = AllocationOption.None)
: this(size, alignment, AllocationManager.GetAllocationHandle(allocator), allocationOption)
{
}
/// <summary>
/// Initializes an UnsafeArray with a pointer to a buffer and a count of elements. This does not copy the data.
/// </summary>
/// <param name="buffer">A pointer to the memory location that holds the elements of the array.</param>
/// <param name="count">The total size of the data.</param>
/// <remarks>
/// When using this constructor, the user is responsible for managing the memory pointed to by the buffer.
/// Disposing of the UnsafeArray does not free the memory and only release the reference. The memory should be freed manually when no longer needed.
/// Use <see cref="UnsafeArray(int, Allocator, AllocationOption)"/> constructor and <see cref="MemCpy(void*, void*, nuint)"/> if you are not sure what you are doing.
/// </remarks>
public UnTypedArray(void* buffer, uint size)
{
_buffer = buffer;
_size = size;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly ref T GetElementAt<T>(nuint index)
where T : unmanaged
{
return ref UnsafeUtility.ReadArrayElementRef<T>(_buffer, index);
}
/// <inheritdoc/>
public void Resize(uint newSize, AllocationOption option = AllocationOption.None)
{
if (newSize == _size)
{
return;
}
MemoryHandle memHandle = _memoryHandle;
_buffer = _allocationHandle.Realloc(_allocationHandle.State, _buffer, _size, newSize, _alignment, option, &memHandle);
_size = newSize;
_memoryHandle = memHandle;
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void Clear()
{
MemClear(_buffer, _size);
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void* GetUnsafePtr()
{
return _buffer;
}
/// <summary>
/// Copies elements from an untyped source collection to a destination span of a specific type.
/// </summary>
/// <typeparam name="T">Specifies the type of elements in the destination span, which must be unmanaged.</typeparam>
/// <param name="destination">The typed span where the data will be copied to.</param>
/// <exception cref="ArgumentException">Thrown when the source collection size exceeds the destination span capacity.</exception>
public readonly void CopyTo<C, T>(Span<T> destination)
where T : unmanaged
{
var destSize = (uint)destination.Length * (uint)sizeof(T);
if (_size > destSize)
{
throw new ArgumentException("Source collection is larger than the destination span.");
}
fixed (T* pDest = destination)
{
MemCpy(pDest, _buffer, _size);
}
}
/// <summary>
/// Copies a range of bytes from an untyped source collection to a destination span, interpreting the bytes as elements of type T.
/// </summary>
/// <typeparam name="T">Specifies the type of elements in the destination span, which must be unmanaged.</typeparam>
/// <param name="destination">The typed span where the elements will be placed.</param>
/// <param name="sourceOffset">The byte offset in the source collection from which to start copying.</param>
/// <param name="destinationIndex">The element index in the destination span where copying will begin.</param>
/// <param name="length">The number of elements of type T to copy.</param>
/// <exception cref="ArgumentException">Thrown when the specified range exceeds the bounds of the source collection or destination span.</exception>
public readonly void CopyTo<T>(Span<T> destination, uint sourceOffset, uint destinationIndex, uint length)
where T : unmanaged
{
var sizeOfElement = (uint)sizeof(T);
if (sourceOffset + (length * sizeOfElement) > _size || destinationIndex + length > destination.Length)
{
throw new ArgumentException("Source collection or destination span is too small for the specified range.");
}
fixed (T* pDest = destination)
{
MemCpy(pDest + destinationIndex, (byte*)_buffer + sourceOffset, length * sizeOfElement);
}
}
/// <summary>
/// Copies elements from a typed source span to an untyped destination collection.
/// </summary>
/// <typeparam name="T">Specifies the type of elements in the source span, which must be unmanaged.</typeparam>
/// <param name="source">The typed span containing the elements to be copied.</param>
/// <exception cref="ArgumentException">Thrown when the destination collection is smaller than the source span data size.</exception>
public readonly void CopyFrom<T>(ReadOnlySpan<T> source)
where T : unmanaged
{
var sourceSize = (uint)(source.Length * sizeof(T));
if (_size < sourceSize)
{
throw new ArgumentException("Destination collection is smaller than the source span.");
}
fixed (T* pSrc = source)
{
MemCpy(_buffer, pSrc, sourceSize);
}
}
/// <summary>
/// Copies a range of elements from a typed source span to an untyped destination collection at a specified byte offset.
/// </summary>
/// <typeparam name="T">Specifies the type of elements in the source span, which must be unmanaged.</typeparam>
/// <param name="source">The typed span containing the elements to be copied.</param>
/// <param name="sourceIndex">The starting element index in the source span from which to begin copying.</param>
/// <param name="destinationOffset">The byte offset in the destination collection where the data will be placed.</param>
/// <param name="length">The number of elements to copy from the source span.</param>
/// <exception cref="ArgumentException">Thrown when the specified range exceeds the bounds of the source span or destination collection.</exception>
public readonly void CopyFrom<T>(ReadOnlySpan<T> source, uint sourceIndex, uint destinationOffset, uint length)
where T : unmanaged
{
var sizeOfElement = (uint)sizeof(T);
if (sourceIndex + length > source.Length || destinationOffset + (length * sizeOfElement) > _size)
{
throw new ArgumentException("Source span or destination collection is too small for the specified range.");
}
fixed (T* pSrc = source)
{
MemCpy((byte*)_buffer + destinationOffset, pSrc + sourceIndex, length * sizeOfElement);
}
}
/// <summary>
/// Converts into a Span for efficient memory access.
/// </summary>
/// <returns>A Span that provides a view over the elements of the UnsafeCollection.</returns>
public readonly Span<byte> AsSpan<C>()
{
return new(_buffer, (int)_size);
}
/// <summary>
/// Creates a span over a contiguous region of elements in the specified unsafe collection, starting at the given index and covering the specified number of elements.
/// </summary>
/// <param name="start">The zero-based index of the first element in the collection to include in the span. Must be greater than or equal to zero and less than the number of elements in the collection.</param>
/// <param name="length">The number of elements to include in the span. Must be greater than or equal to zero and the range defined by
/// <paramref name="start"/> and <paramref name="length"/> must not exceed the bounds of the collection.</param>
/// <returns>A <see cref="Span{byte}"/> representing the specified region of the collection.</returns>
public readonly Span<byte> AsSpan(int start, int length)
{
if (start < 0 || length < 0 || (nuint)(start + length) > _size)
{
throw new ArgumentOutOfRangeException(nameof(start), "The specified range is out of bounds of the collection.");
}
return new Span<byte>((byte*)_buffer + start, length);
}
/// <inheritdoc/>
public void Dispose()
{
if (!IsCreated)
{
return;
}
if (_allocationHandle.Free != null)
{
_allocationHandle.Free(_allocationHandle.State, _buffer, _memoryHandle);
}
_buffer = null;
_size = 0;
_alignment = 0;
}
}

View File

@@ -1,313 +0,0 @@
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
using Misaki.HighPerformance.LowLevel.Utilities;
using System.Collections;
using System.Diagnostics;
using System.Runtime.CompilerServices;
namespace Misaki.HighPerformance.LowLevel.Collections;
internal class UnsafeArrayDebugView<T>
where T : unmanaged
{
private readonly UnsafeArray<T> _array;
public UnsafeArrayDebugView(UnsafeArray<T> array)
{
_array = array;
}
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
public T[] Items
{
get
{
var count = _array.Count;
var result = new T[count];
for (int i = 0; i < count; i++)
{
result[i] = _array[i];
}
return result;
}
}
}
/// <summary>
/// A structure for managing an array of unmanaged types with unsafe memory operations.
/// </summary>
/// <typeparam name="T">Represents a type that can be stored in an unmanaged memory context.</typeparam>
[DebuggerTypeProxy(typeof(UnsafeArrayDebugView<>))]
public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
where T : unmanaged
{
public struct Enumerator : IEnumerator<T>
{
private readonly UnsafeArray<T>* _collection;
private int _index;
public readonly ref T Current => ref _collection->_buffer[_index];
readonly T IEnumerator<T>.Current => Current;
readonly object IEnumerator.Current => Current;
public Enumerator(UnsafeArray<T>* collection)
{
_collection = collection;
_index = -1;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext()
{
_index++;
return _index < _collection->_count;
}
public void Reset()
{
_index = -1;
}
public void Dispose()
{
}
}
private T* _buffer;
private int _count;
private MemoryHandle _memoryHandle;
private AllocationHandle _allocationHandle;
public readonly int Count => _count;
public readonly int Length => _count;
public readonly ref T this[int index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
CheckIndexBounds(index);
return ref UnsafeUtility.ReadArrayElementRef<T>(_buffer, index);
}
}
public readonly ref T this[uint index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
CheckIndexBounds((int)index);
return ref UnsafeUtility.ReadArrayElementRef<T>(_buffer, index);
}
}
public readonly bool IsCreated
{
get
{
if (_buffer != null)
{
if (_allocationHandle.IsValid != null)
{
return _allocationHandle.IsValid(_allocationHandle.State, _memoryHandle);
}
else
{
return true;
}
}
return false;
}
}
public Enumerator GetEnumerator() => new((UnsafeArray<T>*)UnsafeUtility.AddressOf(ref this));
IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
/// <summary>
/// Invalid constructor, use <see cref="UnsafeArray(int, Allocator, AllocationOption)"/> or <see cref="UnsafeArray(int, ref AllocationHandle, AllocationOption)"/> instead.
/// </summary>
public UnsafeArray()
: this(0, Allocator.Invalid)
{
}
/// <summary>
/// Initializes a new instance of UnsafeArray with a specified number of elements and an allocation handle.
/// </summary>
/// <param name="count">Specifies the number of elements to allocate in the array, which must be greater than zero.</param>
/// <param name="handle">A reference to an AllocationHandle that manages the memory allocation for the array.</param>
/// <param name="allocationOption">Specifies how the memory should be allocated.</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when the specified number of elements is less than or equal to zero.</exception>
public UnsafeArray(int count, AllocationHandle handle, AllocationOption allocationOption = AllocationOption.None)
{
if (count < 0)
{
throw new ArgumentOutOfRangeException(nameof(count), "Count can not be less than zero.");
}
if (handle.Alloc == null)
{
throw new InvalidOperationException("Target allocation handle does not support allocation.");
}
MemoryHandle memHandle;
var buff = handle.Alloc(handle.State, (nuint)(count * sizeof(T)), AlignOf<T>(), allocationOption, &memHandle);
_buffer = (T*)buff;
_memoryHandle = memHandle;
_allocationHandle = handle;
_count = count;
}
/// <summary>
/// Initializes a new instance of UnsafeArray with a specified number of elements and an allocation type.
/// </summary>
/// <param name="count">Specifies the number of elements to allocate in the array, which must be greater than zero.</param>
/// <param name="allocator">Specifies the allocator to use for memory allocation, which determines the memory management strategy.</param>
/// <param name="allocationOption">Determines how the memory should be allocated.</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when the specified number of elements is less than or equal to zero.</exception>
public UnsafeArray(int count, Allocator allocator, AllocationOption allocationOption = AllocationOption.None)
: this(count, AllocationManager.GetAllocationHandle(allocator), allocationOption)
{
}
/// <summary>
/// Initializes an UnsafeArray with a pointer to a buffer and a count of elements. This does not copy the data.
/// </summary>
/// <param name="buffer">A pointer to the memory location that holds the elements of the array.</param>
/// <param name="count">The total size of the data.</param>
/// <remarks>
/// When using this constructor, the user is responsible for managing the memory pointed to by the buffer.
/// Disposing of the UnsafeArray does not free the memory and only release the reference. The memory should be freed manually when no longer needed.
/// Use <see cref="UnsafeArray(int, Allocator, AllocationOption)"/> constructor and <see cref="MemCpy(void*, void*, nuint)"/> if you are not sure what you are doing.
/// </remarks>
public UnsafeArray(T* buffer, int count)
{
_buffer = buffer;
_count = count;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Conditional("ENABLE_COLLECTION_CHECKS")]
private readonly void ThrowIfNotCreated()
{
if (!IsCreated)
{
throw new InvalidOperationException("The UnsafeArray is not created.");
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Conditional("ENABLE_COLLECTION_CHECKS")]
private readonly void CheckIndexBounds(int index)
{
ThrowIfNotCreated();
if (index >= _count)
{
throw new ArgumentOutOfRangeException(nameof(index), "Index is out of range.");
}
}
/// <summary>
/// Returns a read-only view of the current collection.
/// </summary>
/// <returns>A <see cref="ReadOnlyUnsafeCollection{T}"/> that provides a read-only view of the elements in the current collection.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly ReadOnlyUnsafeCollection<T> AsReadOnly()
{
return new ReadOnlyUnsafeCollection<T>(_buffer, _count);
}
/// <inheritdoc/>
public void Resize(int newSize, AllocationOption option = AllocationOption.None)
{
ThrowIfNotCreated();
if (_allocationHandle.Realloc == null)
{
throw new InvalidOperationException("Target allocation handle does not support reallocation.");
}
if (newSize == Count)
{
return;
}
MemoryHandle memHandle = _memoryHandle;
var elemSize = SizeOf<T>();
_buffer = (T*)_allocationHandle.Realloc(_allocationHandle.State, _buffer, (nuint)Count * elemSize, (nuint)newSize * elemSize, AlignOf<T>(), option, &memHandle);
_memoryHandle = memHandle;
_count = newSize;
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void Clear()
{
ThrowIfNotCreated();
MemClear(_buffer, (nuint)(Count * sizeof(T)));
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void* GetUnsafePtr()
{
ThrowIfNotCreated();
return _buffer;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly Span<T> AsSpan()
{
ThrowIfNotCreated();
return new Span<T>(_buffer, _count);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly Span<T> AsSpan(int start, int length)
{
ThrowIfNotCreated();
return new Span<T>(_buffer + start, length);
}
/// <summary>
/// Reinterprets the underlying buffer as an array of a different unmanaged type without copying the data.
/// </summary>
/// <typeparam name="U">The unmanaged type to reinterpret the buffer as.</typeparam>
/// <returns>An UnsafeArray<U> that views the same memory as the original array, but as elements of type U.</returns>
/// <exception cref="InvalidOperationException">Thrown if the total size of the buffer in bytes is not a multiple of the size of type U.</exception>
public readonly UnsafeArray<U> Reinterpret<U>()
where U : unmanaged
{
ThrowIfNotCreated();
var totalSize = (nuint)(Count * sizeof(T));
if (totalSize % (nuint)sizeof(U) != 0)
{
throw new InvalidOperationException("Cannot reinterpret array: size mismatch.");
}
var newCount = (int)(totalSize / (nuint)sizeof(U));
return new UnsafeArray<U>((U*)_buffer, newCount);
}
/// <inheritdoc/>
public void Dispose()
{
if (!IsCreated)
{
return;
}
if (_allocationHandle.Free != null)
{
_allocationHandle.Free(_allocationHandle.State, _buffer, _memoryHandle);
}
_buffer = null;
_count = 0;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,224 +0,0 @@
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
using Misaki.HighPerformance.LowLevel.Utilities;
using System.Collections;
using System.Runtime.CompilerServices;
namespace Misaki.HighPerformance.LowLevel.Collections;
public unsafe struct UnsafeHashMap<TKey, TValue> : IUnsafeHashCollection<KeyValuePair<TKey, TValue>>
where TKey : unmanaged, IEquatable<TKey> where TValue : unmanaged
{
public struct Enumerator : IEnumerator<KeyValuePair<TKey, TValue>>
{
internal HashMapHelper<TKey>.Enumerator _enumerator;
public KeyValuePair<TKey, TValue> Current => _enumerator.GetCurrent<TValue>();
object IEnumerator.Current => Current;
public Enumerator(HashMapHelper<TKey>* data)
{
_enumerator = new HashMapHelper<TKey>.Enumerator(data);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext() => _enumerator.MoveNext();
public void Reset() => _enumerator.Reset();
public void Dispose()
{
}
}
private HashMapHelper<TKey> _helper;
public readonly int Count => _helper.Count;
public readonly int Capacity => _helper.Capacity;
public readonly bool IsCreated => _helper.IsCreated;
/// <summary>
/// Gets and sets values by key.
/// </summary>
/// <remarks>Getting a key that is not present will throw. Setting a key that is not already present will add the key.</remarks>
/// <param name="key">The key to look up.</param>
/// <value>The value associated with the key.</value>
/// <exception cref="ArgumentException">For getting, thrown if the key was not present.</exception>
public TValue this[TKey key]
{
get
{
if (!_helper.TryGetValue<TValue>(key, out var result))
{
throw new ArgumentException($"Key: {key} is not present.");
}
return result;
}
set
{
var idx = _helper.Find(key);
if (-1 != idx)
{
UnsafeUtility.WriteArrayElement(_helper.Buffer, idx, value);
return;
}
TryAdd(key, value);
}
}
public Enumerator GetEnumerator() => new((HashMapHelper<TKey>*)UnsafeUtility.AddressOf(ref this));
IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator() => GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
/// <summary>
/// Invalid constructor, use <see cref="UnsafeHashMap(int, Allocator, AllocationOption)"/> or <see cref="UnsafeHashMap(int, ref AllocationHandle, AllocationOption)"/> instead.
/// </summary>
public UnsafeHashMap()
: this(0, Allocator.Invalid)
{
}
public UnsafeHashMap(int capacity, AllocationHandle handle, AllocationOption allocationOption = AllocationOption.None)
{
_helper = new HashMapHelper<TKey>(capacity, sizeof(TValue), (int)AlignOf<TValue>(), HashMapHelper<TKey>.MINIMAL_CAPACITY, handle, allocationOption);
}
public UnsafeHashMap(int capacity, Allocator allocator, AllocationOption allocationOption = AllocationOption.None)
: this(capacity, AllocationManager.GetAllocationHandle(allocator), allocationOption)
{
}
/// <summary>
/// Adds a new key-value pair.
/// </summary>
/// <remarks>If the key is already present, this method returns false without modifying the hash map.</remarks>
/// <param name="key">The key to add.</param>
/// <param name="item">The value to add.</param>
/// <returns>True if the key-value pair was added.</returns>
public bool TryAdd(in TKey key, TValue item)
{
var idx = _helper.TryAdd(key);
if (idx != -1)
{
UnsafeUtility.WriteArrayElement(_helper.Buffer, idx, item);
return true;
}
return false;
}
/// <summary>
/// Adds a new key-value pair.
/// </summary>
/// <remarks>If the key is already present, this method throws without modifying the hash map.</remarks>
/// <param name="key">The key to add.</param>
/// <param name="item">The value to add.</param>
/// <exception cref="ArgumentException">Thrown if the key was already present.</exception>
public void Add(in TKey key, TValue item)
{
var result = TryAdd(key, item);
if (!result)
{
throw new ArgumentException($"An item with the same key has already been added: {key}");
}
}
/// <summary>
/// Removes a particular key and its value.
/// </summary>
/// <param name="item">The value to remove.</param>
/// <returns>True if the value was present.</returns>
public bool Remove(in TKey key)
{
return -1 != _helper.TryRemove(key);
}
/// <summary>
/// Returns the value associated with a key.
/// </summary>
/// <param name="key">The key to look up.</param>
/// <param name="item">Outputs the value associated with the key. Outputs default if the key was not present.</param>
/// <returns>True if the key was present.</returns>
public bool TryGetValue(in TKey key, out TValue item)
{
return _helper.TryGetValue(key, out item);
}
/// <summary>
/// Retrieves the value associated with the specified key, or returns a default value if the key is not found.
/// </summary>
/// <param name="key">The key whose value to retrieve.</param>
/// <param name="defaultValue">The value to return if the specified key does not exist. If not specified, the default value for the type is used.</param>
/// <returns>The value associated with the specified key if the key is found; otherwise, the specified default value.</returns>
public TValue GetValueOrDefault(in TKey key, TValue defaultValue = default)
{
if (_helper.TryGetValue<TValue>(key, out var value))
{
return value;
}
return defaultValue;
}
public ref TValue GetValueRef(in TKey key, out bool exists)
{
return ref _helper.GetValueRef<TValue>(key, out exists);
}
/// <summary>
/// Returns true if a given key is present in this hash map.
/// </summary>
/// <param name="key">The key to look up.</param>
/// <returns>True if the key was present.</returns>
public bool ContainsKey(in TKey key)
{
return -1 != _helper.Find(key);
}
/// <summary>
/// Sets the capacity to match what it would be if it had been originally initialized with all its entries.
/// </summary>
public void TrimExcess() => _helper.TrimExcess();
public void Resize(int newSize, AllocationOption option = AllocationOption.None)
{
_helper.Resize(newSize);
}
public void Clear()
{
_helper.Clear();
}
/// <summary>
/// Retrieves an array of keys from the hash map.
/// </summary>
/// <returns>An array containing the keys stored in the hash map.</returns>
public UnsafeArray<TKey> GetKeyArray(Allocator allocator) => _helper.GetKeyArray(allocator);
/// <summary>
/// Retrieves an array of values from the underlying hash map.
/// </summary>
/// <returns>An UnsafeArray containing the values stored in the hash map.</returns>
public UnsafeArray<TValue> GetValueArray(Allocator allocator) => _helper.GetValueArray<TValue>(allocator);
/// <summary>
/// Retrieves an array of key-value pairs from the hash map. The keys are of type TKey and the values are of type
/// TValue.
/// </summary>
/// <returns>Returns an UnsafeArray containing KeyValuePair objects.</returns>
public UnsafeArray<KeyValuePair<TKey, TValue>> GetKeyValueArrays(Allocator allocator) => _helper.GetKeyValueArrays<TValue>(allocator);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void* GetUnsafePtr()
{
return _helper.Buffer;
}
public void Dispose()
{
_helper.Dispose();
}
}

View File

@@ -1,132 +0,0 @@
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
using Misaki.HighPerformance.LowLevel.Utilities;
using System.Collections;
using System.Runtime.CompilerServices;
namespace Misaki.HighPerformance.LowLevel.Collections;
/// <summary>
/// A collection that provides fast, unsafe operations for managing a set of unmanaged types. It supports adding,
/// removing, and checking for values.
/// </summary>
/// <typeparam name="T">Represents an unmanaged type that can be compared for equality.</typeparam>
public unsafe struct UnsafeHashSet<T> : IUnsafeHashCollection<T>, IEnumerable<T>
where T : unmanaged, IEquatable<T>
{
public struct Enumerator : IEnumerator<T>
{
internal HashMapHelper<T>.Enumerator _enumerator;
public readonly T Current => _enumerator.buffer->_keys[_enumerator.index];
readonly object IEnumerator.Current => Current;
public Enumerator(HashMapHelper<T>* hashMap)
{
_enumerator = new HashMapHelper<T>.Enumerator(hashMap);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext() => _enumerator.MoveNext();
public void Reset() => _enumerator.Reset();
public readonly void Dispose()
{
}
}
private HashMapHelper<T> _helper;
public readonly int Count => _helper.Count;
public readonly int Capacity => _helper.Capacity;
public readonly bool IsCreated => _helper.IsCreated;
public Enumerator GetEnumerator() => new((HashMapHelper<T>*)UnsafeUtility.AddressOf(ref this));
IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
/// <summary>
/// Invalid constructor. Use <see cref="UnsafeHashSet(int, Allocator, AllocationOption)"/> or <see cref="UnsafeHashSet(int, ref AllocationHandle, AllocationOption)"/> instead."/>
/// </summary>
public UnsafeHashSet()
: this(0, Allocator.Invalid)
{
}
public UnsafeHashSet(int capacity, AllocationHandle handle, AllocationOption allocationOption = AllocationOption.None)
{
_helper = new HashMapHelper<T>(capacity, 0, 0, HashMapHelper<T>.MINIMAL_CAPACITY, handle, allocationOption);
}
public UnsafeHashSet(int capacity, Allocator allocator, AllocationOption allocationOption = AllocationOption.None)
: this(capacity, AllocationManager.GetAllocationHandle(allocator), allocationOption)
{
}
/// <summary>
/// Adds a new value (unless it is already present).
/// </summary>
/// <param name="item">The value to add.</param>
/// <returns>True if the value was not already present.</returns>
public bool Add(T item)
{
return -1 != _helper.TryAdd(item);
}
/// <summary>
/// Removes a particular value.
/// </summary>
/// <param name="item">The value to remove.</param>
/// <returns>True if the value was present.</returns>
public bool Remove(T item)
{
return -1 != _helper.TryRemove(item);
}
/// <summary>
/// Returns true if a particular value is present.
/// </summary>
/// <param name="item">The value to check for.</param>
/// <returns>True if the value was present.</returns>
public bool Contains(T item)
{
return -1 != _helper.Find(item);
}
/// <summary>
/// Sets the capacity to match what it would be if it had been originally initialized with all its entries.
/// </summary>
public void TrimExcess() => _helper.TrimExcess();
/// <summary>
/// Returns an array with a copy of this set's values (in no particular order).
/// </summary>
/// <param name="allocator">The allocator to use.</param>
/// <returns>An array with a copy of the set's values.</returns>
public UnsafeArray<T> ToNativeArray(Allocator allocator)
{
return _helper.GetKeyArray(allocator);
}
public void Resize(int newSize, AllocationOption option = AllocationOption.None)
{
_helper.Resize(newSize);
}
public void Clear()
{
_helper.Clear();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void* GetUnsafePtr()
{
return _helper.Buffer;
}
public void Dispose()
{
_helper.Dispose();
}
}

View File

@@ -1,412 +0,0 @@
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
using Misaki.HighPerformance.LowLevel.Utilities;
using System.Collections;
using System.Diagnostics;
using System.Runtime.CompilerServices;
namespace Misaki.HighPerformance.LowLevel.Collections;
internal class UnsafeListDebugView<T>
where T : unmanaged
{
private readonly UnsafeList<T> _list;
public UnsafeListDebugView(UnsafeList<T> list)
{
_list = list;
}
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
public T[] Items
{
get
{
var array = new T[_list.Count];
for (int i = 0; i < _list.Count; i++)
{
array[i] = _list[i];
}
return array;
}
}
}
/// <summary>
/// A collection that allows for unsafe operations on a list of unmanaged types.
/// </summary>
/// <typeparam name="T">Represents a type that can be stored in the collection, constrained to unmanaged types for performance and safety.</typeparam>
[DebuggerTypeProxy(typeof(UnsafeListDebugView<>))]
public unsafe struct UnsafeList<T> : IUnsafeCollection<T>
where T : unmanaged
{
public struct Enumerator : IEnumerator<T>
{
private readonly UnsafeList<T>* _collection;
private int _index;
public readonly ref T Current => ref _collection->_array[_index];
readonly T IEnumerator<T>.Current => Current;
readonly object IEnumerator.Current => Current;
public Enumerator(UnsafeList<T>* collection)
{
_collection = collection;
_index = -1;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext()
{
_index++;
return _index < _collection->_count;
}
public void Reset()
{
_index = -1;
}
public readonly void Dispose()
{
}
}
/// <summary>
/// A parallel writer for an UnsafeList.
/// </summary>
/// <remarks>
/// Use <see cref="AsParallelWriter"/> to create a parallel writer for a list.
/// </remarks>
public unsafe struct ParallelWriter
{
/// <summary>
/// The UnsafeList to write to.
/// </summary>
public UnsafeList<T>* listData;
internal ParallelWriter(UnsafeList<T>* list)
{
listData = list;
}
/// <summary>
/// Adds a value to a collection without resizing it, ensuring capacity is checked before insertion.
/// </summary>
/// <param name="value">The value to be added to the collection.</param>
public void AddNoResize(T value)
{
var idx = Interlocked.Increment(ref listData->_count) - 1;
listData->CheckNoResizeCapacity(idx, 1);
UnsafeUtility.WriteArrayElement(listData->_array.GetUnsafePtr(), idx, value);
}
/// <summary>
/// Adds a specified number of elements from a pointer to a buffer without resizing the underlying storage.
/// </summary>
/// <param name="ptr">Points to the source data to be copied into the buffer.</param>
/// <param name="count">Indicates the number of elements to be added from the source data.</param>
public void AddRangeNoResize(ReadOnlySpan<T> collection, int count)
{
var index = Interlocked.Add(ref listData->_count, count) - count;
listData->CheckNoResizeCapacity(index, count);
fixed (T* pCollection = collection)
{
MemCpy(UnsafeUtility.ReadArrayElementUnsafe<T>(listData->_array.GetUnsafePtr(), index), pCollection, (uint)(count * sizeof(T)));
}
}
}
private UnsafeArray<T> _array;
private int _count;
public readonly int Count => _count;
public readonly int Capacity => _array.Count;
public readonly bool IsCreated => _array.IsCreated;
public readonly ref T this[int index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => ref _array[index];
}
public readonly ref T this[uint index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => ref _array[index];
}
public Enumerator GetEnumerator() => new ((UnsafeList<T>*)UnsafeUtility.AddressOf(ref this));
IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
/// <summary>
/// Provides a parallel writer for the current list, enabling thread-safe additions to the list.
/// </summary>
/// <returns>A <see cref="ParallelWriter"/> instance that can be used to add items to the list in a thread-safe manner.</returns>
public ParallelWriter AsParallelWriter() => new((UnsafeList<T>*)UnsafeUtility.AddressOf(ref this));
/// <summary>
/// Converts the current list to an UnsafeArray representation.
/// </summary>
/// <returns>A new <see cref="UnsafeArray{T}"/> instance.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly UnsafeArray<T> AsUnsafeArray() => new((T*)_array.GetUnsafePtr(), _count);
/// <summary>
/// Invalid constructor, use <see cref="UnsafeList(int, Allocator, AllocationOption)"/> or <see cref="UnsafeList(int, ref AllocationHandle, AllocationOption)"/> instead.
/// </summary>
public UnsafeList()
: this(0, Allocator.Invalid)
{
}
/// <summary>
/// Initializes a new instance of UnsafeList with a specified number of initial capacity and an allocation handle.
/// </summary>
/// <param name="capacity">Specifies the number of initial capacity to allocate in the list, which must be greater than zero.</param>
/// <param name="handle">A reference to an AllocationHandle that manages the memory allocation for the array.</param>
/// <param name="allocationOption">Specifies how the memory should be allocated.</param>
public UnsafeList(int capacity, AllocationHandle handle, AllocationOption allocationOption = AllocationOption.None)
{
_array = new UnsafeArray<T>(capacity, handle, allocationOption);
_count = 0;
}
/// <summary>
/// Initializes a new instance of UnsafeList with a specified number of initial capacity and an allocation type.
/// </summary>
/// <param name="capacity">Specifies the number of initial capacity to allocate in the list, which must be greater than zero.</param>
/// <param name="allocator">Specifies the allocator to use for memory allocation, which determines the memory management strategy.</param>
/// <param name="allocationOption">Determines how the memory should be allocated.</param>
public UnsafeList(int capacity, Allocator allocator, AllocationOption allocationOption = AllocationOption.None)
: this(capacity, AllocationManager.GetAllocationHandle(allocator), allocationOption)
{
}
private readonly void CheckNoResizeCapacity(int count)
{
CheckNoResizeCapacity(count, Count);
}
private readonly void CheckNoResizeCapacity(int index, int count)
{
if (index + count > Capacity)
{
throw new Exception(
$"AddNoResize assumes that list capacity is sufficient (Capacity {Capacity}, Size {Count}), requested count {count}!"
);
}
}
private readonly void CheckIndexCount(int index, int count)
{
if (count < 0)
{
throw new ArgumentOutOfRangeException($"Value for count {count} must be positive.");
}
if (index < 0)
{
throw new ArgumentOutOfRangeException($"Value for index {index} must be positive.");
}
if (index > Count)
{
throw new ArgumentOutOfRangeException($"Value for index {index} is out of bounds.");
}
if (index + count > Count)
{
throw new ArgumentOutOfRangeException($"Value for count {count} is out of bounds.");
}
}
public readonly ReadOnlyUnsafeCollection<T> AsReadOnly()
{
return new ReadOnlyUnsafeCollection<T>((T*)_array.GetUnsafePtr(), _count);
}
/// <summary>
/// Adds a new element to the end of the list, resizing the internal array if necessary.
/// </summary>
/// <param name="value">The element to be added to the list.</param>
public void Add(T value)
{
if (_count >= Capacity)
{
Resize(Math.Max(1, Capacity * 2));
}
UnsafeUtility.WriteArrayElement(_array.GetUnsafePtr(), _count, value);
_count++;
}
/// <summary>
/// Adds the specified value to the collection without resizing the underlying storage.
/// </summary>
/// <param name="value">The value to add to the collection.</param>
public void AddNoResize(T value)
{
CheckNoResizeCapacity(1);
UnsafeUtility.WriteArrayElement(_array.GetUnsafePtr(), _count, value);
_count++;
}
/// <summary>
/// Adds a range of elements to the collection.
/// </summary>
/// <param name="values">A span containing the elements to add. The span must not exceed the specified <paramref name="count"/>.</param>
/// <param name="count">The number of elements to add from the <paramref name="values"/> span. Must be non-negative and less than or
/// equal to the length of <paramref name="values"/>.</param>
public void AddRange(Span<T> values, int count)
{
var newSize = _count + count;
if (newSize > Capacity)
{
Resize(Capacity + count);
}
fixed (T* ptr = values)
{
MemCpy(UnsafeUtility.ReadArrayElementUnsafe<T>(_array.GetUnsafePtr(), _count), ptr, (uint)(count * sizeof(T)));
}
_count += count;
}
/// <summary>
/// Adds the elements of the specified collection to the current list without resizing the underlying storage.
/// </summary>
/// <param name="collection">A read-only span containing the elements to add. The span must not exceed the available capacity.</param>
public void AddRangeNoResize(ReadOnlySpan<T> collection)
{
CheckNoResizeCapacity(collection.Length);
fixed (T* pCollection = collection)
{
MemCpy(UnsafeUtility.ReadArrayElementUnsafe<T>(_array.GetUnsafePtr(), _count), pCollection, (uint)(collection.Length * sizeof(T)));
}
_count += collection.Length;
}
/// <summary>
/// Adds a range of elements from a pointer to the collection without resizing the underlying storage.
/// </summary>
/// <param name="ptr">Points to the source data to be copied into the collection.</param>
/// <param name="count">Indicates the number of elements to be added from the source data.</param>
public void AddRangeNoResize(T* ptr, int count)
{
CheckNoResizeCapacity(count);
MemCpy(UnsafeUtility.ReadArrayElementUnsafe<T>(_array.GetUnsafePtr(), _count), ptr, (uint)(count * sizeof(T)));
_count += count;
}
/// <summary>
/// Removes a range of elements from the list starting at the specified index.
/// </summary>
/// <param name="start">The zero-based index at which to start removing elements.</param>
/// <param name="length">The number of elements to remove.</param>
public void RemoveRange(int start, int length)
{
CheckIndexCount(start, length);
if (length <= 0)
{
return;
}
var copyFrom = Math.Min(start + length, _count);
MemCpy(UnsafeUtility.ReadArrayElementUnsafe<T>(_array.GetUnsafePtr(), start),
UnsafeUtility.ReadArrayElementUnsafe<T>(_array.GetUnsafePtr(), copyFrom),
(uint)((_count - copyFrom) * sizeof(T))
);
_count -= length;
}
/// <summary>
/// Removes the element at the specified index from the collection.
/// </summary>
/// <param name="index">The zero-based index of the element to remove.</param>
public void RemoveAt(int index)
{
RemoveRange(index, 1);
}
/// <summary>
/// Removes a range of elements from the list starting at the specified index by swapping them with the last elements.
/// </summary>
/// <param name="start">The zero-based index at which to start removing elements.</param>
/// <param name="length">The number of elements to remove.</param>
public void RemoveRangeSwapBack(int start, int length)
{
CheckIndexCount(start, length);
if (length <= 0)
{
return;
}
var copyFrom = Math.Min(_count - length, start + length);
MemCpy(UnsafeUtility.ReadArrayElementUnsafe<T>(_array.GetUnsafePtr(), start),
UnsafeUtility.ReadArrayElementUnsafe<T>(_array.GetUnsafePtr(), copyFrom),
(uint)((_count - copyFrom) * sizeof(T))
);
_count -= length;
}
/// <summary>
/// Removes the element at the specified index by swapping it with the last element and reducing the collection
/// size.
/// </summary>
/// <param name="index">The zero-based index of the element to remove. Must be within the bounds of the collection.</param>
public void RemoveAtSwapBack(int index)
{
RemoveRangeSwapBack(index, 1);
}
public void Resize(int newSize, AllocationOption option = AllocationOption.None)
{
_array.Resize(newSize, option);
if (_count > newSize)
{
_count = newSize;
}
}
public void Clear()
{
_array.Clear();
_count = 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void* GetUnsafePtr()
{
return _array.GetUnsafePtr();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly Span<T> AsSpan()
{
return _array.AsSpan(0, _count);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly Span<T> AsSpan(int start, int length)
{
CheckIndexCount(start, length);
return _array.AsSpan(start, length);
}
public void Dispose()
{
_array.Dispose();
_count = 0;
}
}

View File

@@ -1,202 +0,0 @@
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
using Misaki.HighPerformance.LowLevel.Utilities;
using System.Collections;
using System.Runtime.CompilerServices;
namespace Misaki.HighPerformance.LowLevel.Collections;
/// <summary>
/// A structure that implements a queue using unmanaged types for efficient memory management.
/// </summary>
/// <typeparam name="T">Represents the type of elements stored in the queue, which must be an unmanaged type for performance and safety.</typeparam>
public unsafe struct UnsafeQueue<T> : IUnsafeCollection<T>
where T : unmanaged
{
public struct Enumerator : IEnumerator<T>
{
private readonly UnsafeQueue<T>* _collection;
private int _currentIndex;
// We assume _currentIndex will always be in range when accessed.
public readonly ref T Current => ref _collection->_array[(_collection->_offset + _currentIndex) % _collection->Capacity];
readonly T IEnumerator<T>.Current => Current;
readonly object IEnumerator.Current => Current;
public Enumerator(UnsafeQueue<T>* collection)
{
_collection = collection;
_currentIndex = -1;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext()
{
_currentIndex++;
return _currentIndex < _collection->_count;
}
public void Reset()
{
_currentIndex = -1;
}
public readonly void Dispose()
{
}
}
private UnsafeArray<T> _array;
private int _count;
private int _offset;
public readonly int Count => _count;
public readonly int Capacity => _array.Count;
public readonly bool IsCreated => _array.IsCreated;
public readonly T this[int index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _array[index];
[MethodImpl(MethodImplOptions.AggressiveInlining)]
set => _array[index] = value;
}
public Enumerator GetEnumerator() => new((UnsafeQueue<T>*)UnsafeUtility.AddressOf(ref this));
IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
/// <summary>
/// Invalid constructor. Use <see cref="UnsafeQueue(int, Allocator, AllocationOption)"/> or <see cref="UnsafeQueue(int, ref AllocationHandle, AllocationOption)"/> instead."/>
/// </summary>
public UnsafeQueue()
: this(0, Allocator.Invalid)
{
}
public UnsafeQueue(int capacity, AllocationHandle handle, AllocationOption allocationOption = AllocationOption.None)
{
_array = new UnsafeArray<T>(capacity, handle, allocationOption);
_count = 0;
_offset = 0;
}
public UnsafeQueue(int capacity, Allocator allocator, AllocationOption allocationType = AllocationOption.None)
: this(capacity, AllocationManager.GetAllocationHandle(allocator), allocationType)
{
}
/// <summary>
/// Returns a reference to the item at the front of the queue without removing it.
/// </summary>
/// <returns>A reference to the item at the front of the queue.</returns>
/// <exception cref="InvalidOperationException">Thrown if the queue is empty.</exception>
public readonly ref T Peek()
{
if (_count == 0)
{
throw new InvalidOperationException("Queue is empty.");
}
return ref UnsafeUtility.ReadArrayElementRef<T>(_array.GetUnsafePtr(), _offset);
}
/// <summary>
/// Attempts to return the object at the top of the collection without removing it.
/// </summary>
/// <param name="value">The item at the front of the queue if the operation is successful; otherwise, the default value of <typeparamref name="T"/>.</param>
/// <returns><see langword="true"/> if an object was returned successfully; otherwise, <see langword="false"/>.</returns>
public readonly bool TryPeek(out T value)
{
if (_count == 0)
{
value = default;
return false;
}
value = _array[_offset];
return true;
}
/// <summary>
/// Adds an element to the end of a collection, resizing if the current capacity is reached. The new element is
/// stored in a circular buffer.
/// </summary>
/// <param name="value">The item to be added to the collection.</param>
public void Enqueue(T value)
{
if (_count >= Capacity)
{
Resize(Math.Max(1, Capacity * 2));
}
UnsafeUtility.WriteArrayElement(_array.GetUnsafePtr(), (_offset + _count) % Capacity, value);
_count++;
}
/// <summary>
/// Removes and returns the element at the front of the queue. If the queue is empty, an exception is thrown.
/// </summary>
/// <returns>The element that was removed from the front of the queue.</returns>
/// <exception cref="InvalidOperationException">Thrown when attempting to dequeue from an empty queue.</exception>
public T Dequeue()
{
if (_count == 0)
{
throw new InvalidOperationException("Queue is empty.");
}
var value = UnsafeUtility.ReadArrayElement<T>(_array.GetUnsafePtr(), _offset);
_offset = (_offset + 1) % Capacity;
_count--;
return value;
}
/// <summary>
/// Attempts to remove and return an item from a collection. Returns a boolean indicating success or failure.
/// </summary>
/// <param name="value">The output variable that will hold the dequeued item if the operation is successful.</param>
/// <returns>True if an item was successfully dequeued, otherwise false.</returns>
public bool TryDequeue(out T value)
{
if (_count == 0)
{
value = default;
return false;
}
value = Dequeue();
return true;
}
public void Resize(int newSize, AllocationOption option = AllocationOption.None)
{
_array.Resize(newSize, option);
if (_count > newSize)
{
_count = newSize;
}
}
public void Clear()
{
_array.Clear();
_count = 0;
_offset = 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void* GetUnsafePtr()
{
return _array.GetUnsafePtr();
}
public void Dispose()
{
_array.Dispose();
_count = 0;
_offset = 0;
}
}

View File

@@ -1,320 +0,0 @@
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
using Misaki.HighPerformance.LowLevel.Utilities;
using System.Collections;
using System.Diagnostics;
using System.Runtime.CompilerServices;
namespace Misaki.HighPerformance.LowLevel.Collections;
internal class UnsafeSlotMapDebugView<T>
where T : unmanaged
{
private readonly UnsafeSlotMap<T> _slotMap;
public UnsafeSlotMapDebugView(UnsafeSlotMap<T> slotMap)
{
_slotMap = slotMap;
}
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
public T[] Items
{
get
{
var items = new List<T>(_slotMap.Count);
var enumerator = _slotMap.GetEnumerator();
while (enumerator.MoveNext())
{
items.Add(enumerator.Current);
}
return items.ToArray();
}
}
}
/// <summary>
/// Provides an unsafe, high-performance slot map for storing and managing unmanaged values, supporting fast insertion,
/// removal, and lookup by slot index and generation.
/// </summary>
/// <typeparam name="T">The type of value to store in the slot map. Must be unmanaged.</typeparam>
[DebuggerTypeProxy(typeof(UnsafeSlotMapDebugView<>))]
public unsafe struct UnsafeSlotMap<T> : IUnsafeCollection<T>
where T : unmanaged
{
public struct Enumerator : IEnumerator<T>
{
private readonly UnsafeSlotMap<T>* _collection;
private int _currentIndex;
public readonly ref T Current => ref _collection->_data[_currentIndex];
readonly T IEnumerator<T>.Current => Current;
readonly object? IEnumerator.Current => Current;
public Enumerator(UnsafeSlotMap<T>* collection)
{
_collection = collection;
_currentIndex = 0;
}
public bool MoveNext()
{
_currentIndex = _collection->_validBits.NextSetBit(_currentIndex + 1);
return _currentIndex != -1;
}
public void Reset()
{
_currentIndex = 0;
}
public void Dispose()
{
}
}
private UnsafeArray<T> _data;
private UnsafeArray<int> _generations;
private UnsafeQueue<int> _freeSlots;
private UnsafeBitSet _validBits;
private int _count;
private int _capacity;
public readonly int Count => _count;
public readonly int Capacity => _capacity;
public readonly bool IsCreated => _data.IsCreated && _generations.IsCreated && _freeSlots.IsCreated && _validBits.IsCreated;
public Enumerator GetEnumerator() => new((UnsafeSlotMap<T>*)UnsafeUtility.AddressOf(ref this));
IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
/// <summary>
/// Invalid constructor. Use <see cref="UnsafeSlotMap(int, Allocator, AllocationOption)"/> or <see cref="UnsafeSlotMap(int, ref AllocationHandle, AllocationOption)"/> instead."/>
/// </summary>
public UnsafeSlotMap()
: this(0, Allocator.Invalid)
{
}
/// <summary>
/// Initializes a new instance of the UnsafeSlotMap class with the specified capacity, allocation handle, and
/// allocation options.
/// </summary>
/// <param name="capacity">The number of slots to allocate for the map. Must be greater than zero.</param>
/// <param name="handle">A reference to the allocation handle used to manage memory for the slot map.</param>
/// <param name="allocationOption">The allocation options to use when creating internal data structures. The default is AllocationOption.None.</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when capacity is less than or equal to zero.</exception>
public UnsafeSlotMap(int capacity, AllocationHandle handle, AllocationOption allocationOption = AllocationOption.None)
{
if (capacity <= 0)
{
throw new ArgumentOutOfRangeException(nameof(capacity), "Capacity must be greater than zero.");
}
_data = new UnsafeArray<T>(capacity, handle, allocationOption);
_generations = new UnsafeArray<int>(capacity, handle, allocationOption);
_freeSlots = new UnsafeQueue<int>(capacity, handle, allocationOption);
_validBits = new UnsafeBitSet(capacity, handle, allocationOption);
if (!allocationOption.HasFlag(AllocationOption.Clear))
{
_generations.AsSpan().Clear();
}
_validBits.ClearAll();
_count = 0;
_capacity = capacity;
}
/// <summary>
/// Initializes a new instance of the UnsafeSlotMap class with the specified capacity, allocator, and allocation
/// options.
/// </summary>
/// <param name="capacity">The initial number of slots to allocate for the map. Must be greater than zero.</param>
/// <param name="allocator">The allocator to use for memory management of the slot map.</param>
/// <param name="allocationOption">The allocation option that determines how memory is allocated. The default is AllocationOption.None.</param>
public UnsafeSlotMap(int capacity, Allocator allocator, AllocationOption allocationOption = AllocationOption.None)
: this(capacity, AllocationManager.GetAllocationHandle(allocator), allocationOption)
{
}
/// <summary>
/// Adds the specified item to the collection and returns the index of the slot where it was stored.
/// </summary>
/// <param name="item">The item to add to the collection.</param>
/// <param name="generation">When this method returns, contains the generation number associated with the slot where the item was stored.</param>
/// <returns>The index of the slot in which the item was stored.</returns>
public int Add(T item, out int generation)
{
if (_count >= _capacity)
{
Resize(Math.Max(1, _capacity * 2));
}
int index;
if (_freeSlots.Count == 0)
{
index = _count;
}
else
{
index = _freeSlots.Dequeue();
}
_data[index] = item;
_validBits.SetBit(index);
_count++;
generation = _generations[index];
return index;
}
/// <summary>
/// Attempts to remove the item at the specified slot index and generation from the collection.
/// </summary>
/// <param name="slotIndex">The zero-based index of the slot to remove. Must be within the valid range of slot indices.</param>
/// <param name="generation">The generation value associated with the slot. Removal succeeds only if this matches the current generation of
/// the slot.</param>
/// <returns>true if the item was successfully removed; otherwise, false.</returns>
public bool Remove(int slotIndex, int generation)
{
if (slotIndex < 0 || slotIndex >= _capacity)
{
return false;
}
ref var gen = ref _generations[slotIndex];
if (gen != generation)
{
return false;
}
gen++;
_validBits.ClearBit(slotIndex);
_freeSlots.Enqueue(slotIndex);
_count--;
return true;
}
/// <summary>
/// Determines whether the specified slot index contains a valid entry with the given generation.
/// </summary>
/// <param name="slotIndex">The zero-based index of the slot to check. Must be greater than or equal to 0 and less than the current capacity.</param>
/// <param name="generation">The generation value to compare against the slot's generation.</param>
/// <returns>true if the slot at the specified index is valid and its generation matches the specified value; otherwise, false.</returns>
public readonly bool Contains(int slotIndex, int generation)
{
if (slotIndex < 0 || slotIndex >= _capacity)
{
return false;
}
if (_validBits.IsSet(slotIndex) && _generations[slotIndex] == generation)
{
return true;
}
return false;
}
/// <summary>
/// Attempts to retrieve the element at the specified slot index and generation.
/// </summary>
/// <param name="slotIndex">The zero-based index of the slot to retrieve. Must be within the valid range of slots.</param>
/// <param name="generation">The generation identifier associated with the slot. Used to verify that the slot has not been replaced or
/// invalidated.</param>
/// <param name="value">When this method returns, contains the element at the specified slot and generation if found; otherwise, the
/// default value for type <typeparamref name="T"/>.</param>
/// <returns>true if the element at the specified slot index and generation is found; otherwise, false.</returns>
public readonly bool TryGetElementAt(int slotIndex, int generation, out T value)
{
if (!Contains(slotIndex, generation))
{
value = default;
return false;
}
value = _data[slotIndex];
return true;
}
/// <summary>
/// Retrieves the element stored at the specified slot index and generation.
/// </summary>
/// <param name="slotIndex">The zero-based index of the slot from which to retrieve the element. Must be within the valid range of allocated slots.</param>
/// <param name="generation">The generation identifier associated with the slot. Used to ensure the element has not been replaced or removed since allocation.</param>
/// <returns>The element of type <see cref="T"/> stored at the specified slot and generation.</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="slotIndex"/> is less than zero or greater than or equal to the capacity.</exception>
/// <exception cref="InvalidOperationException">Thrown when the specified slot is not occupied or the generation does not match.</exception>
public readonly T GetElementAt(int slotIndex, int generation)
{
if (!Contains(slotIndex, generation))
{
throw new InvalidOperationException("The specified slot is not occupied or the generation does not match.");
}
return _data[slotIndex];
}
/// <summary>
/// Returns a reference to the element at the specified slot index and generation, if it exists; otherwise, returns
/// a null reference.
/// </summary>
/// <param name="slotIndex">The zero-based index of the slot to retrieve. Must be within the valid range of allocated slots.</param>
/// <param name="generation">The expected generation value for the slot. Used to verify that the slot has not been recycled or replaced.</param>
/// <param name="exist">When this method returns, contains <see langword="true"/> if a valid element exists at the specified slot and generation; otherwise, <see langword="false"/>.</param>
/// <returns>A reference to the element of type <typeparamref name="T"/> at the specified slot and generation if it exists; otherwise, a null reference.</returns>
public ref T GetElementReferenceAt(int slotIndex, int generation, out bool exist)
{
if (!Contains(slotIndex, generation))
{
exist = false;
return ref Unsafe.NullRef<T>();
}
exist = true;
return ref _data[slotIndex];
}
public void Resize(int newSize, AllocationOption option = AllocationOption.None)
{
_data.Resize(newSize, option);
_generations.Resize(newSize, option | AllocationOption.Clear);
_freeSlots.Resize(newSize, option);
_validBits.Resize(newSize, option);
_capacity = newSize;
}
public void Clear()
{
_generations.Clear();
_freeSlots.Clear();
_validBits.ClearAll();
_count = 0;
Add(default, out _);
}
public readonly void* GetUnsafePtr()
{
return (T*)_data.GetUnsafePtr() + 1;
}
public void Dispose()
{
_data.Dispose();
_generations.Dispose();
_freeSlots.Dispose();
_validBits.Dispose();
_count = 0;
_capacity = 0;
}
}

View File

@@ -1,398 +0,0 @@
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
using Misaki.HighPerformance.LowLevel.Utilities;
using System.Collections;
using System.Diagnostics;
using System.Runtime.CompilerServices;
namespace Misaki.HighPerformance.LowLevel.Collections;
internal class ConcurrentSparseSetDebugView<T>
where T : unmanaged
{
private readonly UnsafeSparseSet<T> _set;
public ConcurrentSparseSetDebugView(UnsafeSparseSet<T> set)
{
_set = set;
}
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
public T[] Items
{
get
{
var items = new T[_set.Count];
var index = 0;
foreach (var item in _set)
{
items[index++] = item;
}
return items;
}
}
}
/// <summary>
/// A sparse set data structure that provides O(1) insertion, deletion, and lookup operations.
/// The sparse set uses three arrays: a dense array for storing values, a sparse array for mapping indices,
/// and a reverse array for mapping dense indices back to sparse indices.
/// Sparse indices work like entity IDs and are automatically generated.
/// </summary>
/// <typeparam name="T">Represents a type that can be stored in the sparse set, constrained to unmanaged types for performance and safety.</typeparam>
[DebuggerTypeProxy(typeof(ConcurrentSparseSetDebugView<>))]
public unsafe struct UnsafeSparseSet<T> : IUnsafeCollection<T>
where T : unmanaged
{
public struct Enumerator : IEnumerator<T>
{
private readonly UnsafeSparseSet<T>* _collection;
private int _currentIndex;
public readonly ref T Current => ref _collection->_dense[_currentIndex];
readonly T IEnumerator<T>.Current => Current;
readonly object IEnumerator.Current => Current;
public Enumerator(UnsafeSparseSet<T>* collection)
{
_collection = collection;
_currentIndex = 0;
}
public bool MoveNext()
{
_currentIndex++;
return _currentIndex < _collection->_count;
}
public void Reset()
{
_currentIndex = 0;
}
public readonly void Dispose()
{
}
}
private UnsafeArray<T> _dense;
private UnsafeArray<int> _generations;
private UnsafeArray<int> _sparse;
private UnsafeArray<int> _reverse; // Maps dense index to sparse index. Since this is a general purpose sparse set, we have to include reverse array. In real world ecs, this should be replaced with entity id array.
private UnsafeStack<int> _freeSparse;
private int _count;
private int _nextId; // Next available sparse index
private int _capacity;
public readonly int Count => _count;
public readonly int Capacity => _capacity;
public readonly bool IsCreated => _dense.IsCreated && _sparse.IsCreated && _reverse.IsCreated;
public Enumerator GetEnumerator() => new((UnsafeSparseSet<T>*)UnsafeUtility.AddressOf(ref this));
IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
/// <summary>
/// Constructs an UnsafeSparseSet with a default size of 1 and uses the Persistent allocator.
/// </summary>
public UnsafeSparseSet()
: this(1, Allocator.Persistent)
{
}
/// <summary>
/// Initializes a new instance of UnsafeSparseSet with a specified capacity and an allocation handle.
/// </summary>
/// <param name="capacity">Specifies the initial capacity of the sparse set, which must be greater than zero.</param>
/// <param name="handle">A reference to an AllocationHandle that manages the memory allocation for the sparse set.</param>
/// <param name="allocationOption">Specifies how the memory should be allocated.</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when the specified capacity is less than or equal to zero.</exception>
public UnsafeSparseSet(int capacity, AllocationHandle handle, AllocationOption allocationOption = AllocationOption.None)
{
if (capacity <= 0)
{
throw new ArgumentOutOfRangeException(nameof(capacity), "Capacity must be greater than zero.");
}
_dense = new UnsafeArray<T>(capacity, handle, allocationOption);
_generations = new UnsafeArray<int>(capacity, handle, allocationOption);
_sparse = new UnsafeArray<int>(capacity, handle, allocationOption);
_reverse = new UnsafeArray<int>(capacity, handle, allocationOption);
_freeSparse = new UnsafeStack<int>(capacity, handle, allocationOption);
if (!allocationOption.HasFlag(AllocationOption.Clear))
{
_generations.AsSpan().Clear();
_sparse.AsSpan().Clear();
}
_count = 0;
_nextId = 0;
_capacity = capacity;
_sparse.AsSpan().Fill(-1);
_generations.Clear();
}
/// <summary>
/// Initializes a new instance of UnsafeSparseSet with a specified capacity and an allocation type.
/// </summary>
/// <param name="capacity">Specifies the initial capacity of the sparse set, which must be greater than zero.</param>
/// <param name="allocator">Specifies the allocator to use for memory allocation, which determines the memory management strategy.</param>
/// <param name="allocationOption">Determines how the memory should be allocated.</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when the specified capacity is less than or equal to zero.</exception>
public UnsafeSparseSet(int capacity, Allocator allocator, AllocationOption allocationOption = AllocationOption.None)
: this(capacity, AllocationManager.GetAllocationHandle(allocator), allocationOption)
{
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private readonly ref T GetDenseReferenceUnchecked(int sparseIndex)
{
return ref _dense[_sparse[sparseIndex]];
}
/// <summary>
/// Adds a value to the sparse set and returns a unique sparse index for the value.
/// </summary>
/// <param name="value">The value to add to the sparse set.</param>
/// <param name="generation">Outputs the generation number associated with the added value.</param>
/// <returns>A unique sparse index that can be used to reference this value.</returns>
public int Add(T value, out int generation)
{
if (!_freeSparse.TryPop(out var sparseIndex))
{
// Use the next available ID
sparseIndex = _nextId++;
// Resize sparse array if necessary
if (sparseIndex >= _sparse.Count)
{
ResizeSparse(sparseIndex + 1);
}
}
// Resize dense arrays if necessary
if (_count >= _capacity)
{
Resize(Math.Max(1, _capacity * 2));
}
// Add the value to the dense array and update mappings
_dense[_count] = value;
_sparse[sparseIndex] = _count;
_reverse[_count] = sparseIndex;
_count++;
generation = _generations[sparseIndex];
return sparseIndex;
}
/// <summary>
/// Removes the value at the specified sparse index.
/// </summary>
/// <param name="sparseIndex">The sparse index of the value to remove.</param>
/// <param name="generation">The generation number associated with the sparse index to validate.</param>
/// <returns>True if the value was removed, false if the sparse index was not found.</returns>
public bool Remove(int sparseIndex, int generation)
{
if (!Contains(sparseIndex, generation))
{
return false;
}
var denseIndex = _sparse[sparseIndex];
var lastIndex = _count - 1;
if (denseIndex != lastIndex)
{
// Move the last element to the position of the removed element
var lastValue = _dense[lastIndex];
var lastSparseIndex = _reverse[lastIndex]; // Get sparse index of last element
_dense[denseIndex] = lastValue;
_reverse[denseIndex] = lastSparseIndex;
// Update the sparse mapping for the moved element
_sparse[lastSparseIndex] = denseIndex;
}
// Mark the sparse index as unused and add to free list
_sparse[sparseIndex] = -1;
_generations[sparseIndex]++; // Increment generation to invalidate old references
_freeSparse.Push(sparseIndex);
_count--;
return true;
}
/// <summary>
/// Checks if the sparse set contains a value at the specified sparse index.
/// </summary>
/// <param name="sparseIndex">The sparse index to check.</param>
/// <param name="generation">The generation number to validate against the stored generation.</param>
/// <returns>True if the sparse index is valid and contains a value, false otherwise.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly bool Contains(int sparseIndex, int generation)
{
if (sparseIndex < 0 || sparseIndex >= _sparse.Count)
{
return false;
}
var denseIndex = _sparse[sparseIndex];
return denseIndex >= 0 && denseIndex < _count && _generations[denseIndex] == generation;
}
/// <summary>
/// Gets the value at the specified sparse index and generation.
/// </summary>
/// <param name="sparseIndex">The sparse index to retrieve the value from.</param>
/// <param name="generation">The generation number to validate against the stored generation.</param>
/// <param name="value">When this method returns, contains the value at the specified sparse index, if found.</param>
/// <returns>True if the sparse index contains a value, false otherwise.</returns>
public readonly bool TryGetValue(int sparseIndex, int generation, out T value)
{
if (Contains(sparseIndex, generation))
{
value = GetDenseReferenceUnchecked(sparseIndex);
return true;
}
value = default;
return false;
}
/// <summary>
/// Gets the value at the specified sparse index and generation.
/// </summary>
/// <param name="sparseIndex">The sparse index to retrieve the value from.</param>
/// <param name="generation">The generation number to validate against the stored generation.</param>
/// <returns>The value at the specified sparse index.</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when the sparse index is not found.</exception>
public readonly T GetValue(int sparseIndex, int generation)
{
if (!Contains(sparseIndex, generation))
{
throw new ArgumentOutOfRangeException(nameof(sparseIndex), "Sparse index and feneration not found in the set.");
}
return GetDenseReferenceUnchecked(sparseIndex);
}
/// <summary>
/// Gets reference of the value at the specified sparse index and generation.
/// </summary>
/// <param name="sparseIndex">The sparse index to retrieve the value from.</param>
/// <param name="generation">The generation number to validate against the stored generation.</param>
/// <param name="exist">Outputs whether the sparse index exists in the set.</param>
/// <returns>Reference of the value at the specified sparse index.</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when the sparse index is not found.</exception>
public readonly ref T GetValueReference(int sparseIndex, int generation, out bool exist)
{
if (!Contains(sparseIndex, generation))
{
exist = false;
return ref Unsafe.NullRef<T>();
}
exist = true;
return ref GetDenseReferenceUnchecked(sparseIndex);
}
/// <summary>
/// Updates the value at the specified sparse index.
/// </summary>
/// <param name="sparseIndex">The sparse index of the value to update.</param>
/// <param name="generation">The generation number to validate against the stored generation.</param>
/// <param name="value">The new value.</param>
/// <returns>True if the value was updated, false if the sparse index was not found.</returns>
public bool SetValue(int sparseIndex, int generation, T value)
{
if (!Contains(sparseIndex, generation))
{
return false;
}
GetDenseReferenceUnchecked(sparseIndex) = value;
return true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ResizeSparse(int newSize)
{
var oldSize = _sparse.Count;
_sparse.Resize(newSize);
_sparse.AsSpan()[oldSize..newSize].Fill(-1);
}
/// <inheritdoc/>
public void Clear()
{
if (!IsCreated)
{
return;
}
_sparse.AsSpan().Fill(-1);
_generations.AsSpan().Clear();
_count = 0;
_nextId = 0;
Add(default, out _);
}
/// <inheritdoc/>
public void Resize(int newSize, AllocationOption option = AllocationOption.None)
{
if (newSize <= 0)
{
throw new ArgumentOutOfRangeException(nameof(newSize), "New size must be greater than zero.");
}
_dense.Resize(newSize, option);
_generations.Resize(newSize, option | AllocationOption.Clear);
_reverse.Resize(newSize, option);
if (newSize > _sparse.Count)
{
ResizeSparse(newSize);
}
_capacity = newSize;
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void* GetUnsafePtr()
{
return (T*)_dense.GetUnsafePtr() + 1;
}
/// <summary>
/// Converts the current sparse set to an UnsafeArray representation using its dense array.
/// </summary>
/// <returns>Returns a new UnsafeArray instance initialized with the dense array's pointer and count.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly UnsafeArray<T> AsUnsafeArray()
{
return new UnsafeArray<T>((T*)_dense.GetUnsafePtr(), _count);
}
/// <inheritdoc/>
public void Dispose()
{
_dense.Dispose();
_generations.Dispose();
_sparse.Dispose();
_reverse.Dispose();
_freeSparse.Dispose();
_count = 0;
_nextId = 0;
}
}

View File

@@ -1,228 +0,0 @@
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
using Misaki.HighPerformance.LowLevel.Utilities;
using System.Collections;
using System.Diagnostics;
using System.Runtime.CompilerServices;
namespace Misaki.HighPerformance.LowLevel.Collections;
internal class UnsafeStackDebugView<T>
where T : unmanaged
{
private readonly UnsafeStack<T> _stack;
public UnsafeStackDebugView(UnsafeStack<T> stack)
{
_stack = stack;
}
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
public unsafe T[] Items
{
get
{
var items = new T[_stack.Count];
var pItems = (T*)_stack.GetUnsafePtr();
for (int i = 0; i < _stack.Count; i++)
{
items[i] = pItems[i];
}
return items;
}
}
}
/// <summary>
/// Provides a high-performance, unsafe stack data structure for unmanaged types, supporting manual memory management
/// and allocation control.
/// </summary>
/// <typeparam name="T">The type of elements stored in the stack. Must be an unmanaged type.</typeparam>
[DebuggerTypeProxy(typeof(UnsafeStackDebugView<>))]
public unsafe struct UnsafeStack<T> : IUnsafeCollection<T>
where T : unmanaged
{
public struct Enumerator : IEnumerator<T>
{
private readonly UnsafeStack<T>* _collection;
private int _index;
public readonly ref T Current => ref _collection->_array[_index];
readonly T IEnumerator<T>.Current => Current;
readonly object IEnumerator.Current => Current;
public Enumerator(UnsafeStack<T>* collection)
{
_collection = collection;
_index = collection->Count;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext()
{
_index--;
return _index >= 0;
}
public void Reset()
{
_index = _collection->Count;
}
public readonly void Dispose()
{
}
}
private UnsafeArray<T> _array;
private int _count;
public readonly int Count => _count;
public readonly int Capacity => _array.Count;
public readonly bool IsCreated => _array.IsCreated;
public Enumerator GetEnumerator() => new((UnsafeStack<T>*)UnsafeUtility.AddressOf(ref this));
IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
/// <summary>
/// Invalid constructor, use <see cref="UnsafeStack(int, Allocator, AllocationOption)"/> or <see cref="UnsafeStack(int, ref AllocationHandle, AllocationOption)"/> instead.
/// </summary>
public UnsafeStack()
: this(0, Allocator.Invalid)
{
}
/// <summary>
/// Initializes a new instance of the UnsafeStack class with the specified initial capacity and allocation options.
/// </summary>
/// <param name="capacity">The number of elements the stack can initially hold. Must be greater than zero.</param>
/// <param name="handle">A reference to an AllocationHandle used to manage the underlying memory allocation for the stack.</param>
/// <param name="allocationOption">Specifies additional options for memory allocation. The default is AllocationOption.None.</param>
public UnsafeStack(int capacity, AllocationHandle handle, AllocationOption allocationOption = AllocationOption.None)
{
_array = new UnsafeArray<T>(capacity, handle, allocationOption);
}
/// <summary>
/// Initializes a new instance of the UnsafeStack class with the specified initial capacity, allocator, and
/// allocation options.
/// </summary>
/// <param name="capacity">The initial number of elements that the stack can hold. Must be greater than zero.</param>
/// <param name="allocator">The allocator to use for memory management of the stack's storage.</param>
/// <param name="allocationOption">The allocation option that determines how memory is allocated for the stack. The default is AllocationOption.None.</param>
public UnsafeStack(int capacity, Allocator allocator, AllocationOption allocationOption = AllocationOption.None)
: this(capacity, AllocationManager.GetAllocationHandle(allocator), allocationOption)
{
}
/// <summary>
/// Adds an element to the top of the stack.
/// </summary>
/// <param name="value">The element to add to the stack.</param>
public void Push(T value)
{
if (_count >= Capacity)
{
Resize(Math.Max(1, Capacity * 2));
}
UnsafeUtility.WriteArrayElement(_array.GetUnsafePtr(), _count, value);
_count++;
}
/// <summary>
/// Removes and returns the object at the top of the stack.
/// </summary>
/// <returns>The object removed from the top of the stack.</returns>
/// <exception cref="InvalidOperationException">Thrown when the stack is empty.</exception>
public T Pop()
{
if (_count == 0)
{
throw new InvalidOperationException("Stack is empty.");
}
_count--;
return _array[_count];
}
/// <summary>
/// Attempts to remove and return the object at the top of the stack.
/// </summary>
/// <param name="value">When this method returns, contains the object removed from the top of the stack, if the operation succeeded;
/// otherwise, the default value of <typeparamref name="T"/>.</param>
/// <returns>true if an object was successfully removed and returned from the stack; otherwise, false.</returns>
public bool TryPop(out T value)
{
if (_count == 0)
{
value = default;
return false;
}
_count--;
value = _array[_count];
return true;
}
/// <summary>
/// Returns the item at the top of the stack without removing it.
/// </summary>
/// <returns>The item of type <typeparamref name="T"/> at the top of the stack.</returns>
/// <exception cref="InvalidOperationException">Thrown when the stack is empty.</exception>
public readonly T Peek()
{
if (_count == 0)
{
throw new InvalidOperationException("Stack is empty.");
}
return _array[_count - 1];
}
/// <summary>
/// Attempts to return the item at the top of the stack without removing it.
/// </summary>
/// <param name="value">When this method returns, contains the item at the top of the stack if the stack is not empty; otherwise, the default value of <typeparamref name="T"/>.</param>
/// <returns><see langword="true"/> if an item was successfully returned; otherwise, <see langword="false"/>.</returns>
public readonly bool TryPeek(out T value)
{
if (_count == 0)
{
value = default;
return false;
}
value = _array[_count - 1];
return true;
}
public void Resize(int newSize, AllocationOption option = AllocationOption.None)
{
_array.Resize(newSize, option);
if (_count > newSize)
{
_count = newSize;
}
}
public void Clear()
{
_array.Clear();
_count = 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void* GetUnsafePtr()
{
return _array.GetUnsafePtr();
}
public void Dispose()
{
_array.Dispose();
_count = 0;
}
}