Added UnsafeMultiHashMap
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
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;
|
||||
}
|
||||
758
Misaki.HighPerformance.LowLevel/Collections/FixedString.gen.cs
Normal file
758
Misaki.HighPerformance.LowLevel/Collections/FixedString.gen.cs
Normal file
@@ -0,0 +1,758 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Misaki.HighPerformance.LowLevel.Collections;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a stack allocated fixed-size UTF-8 encoded string of length 32 bytes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This struct is designed to hold data on the stack. Every copy of this struct causes a copy of the underlying data.
|
||||
/// If you need a heap allocated fixed-size UTF-8 encoded string of length 32 bytes, consider using <see cref="Misaki.HighPerformance.Unsafe.Buffer.FixedString32"/>.
|
||||
/// </remarks>
|
||||
[StructLayout(LayoutKind.Sequential, Size = 32)]
|
||||
public unsafe struct FixedString32
|
||||
{
|
||||
public const int MAX_LENGTH = 15;
|
||||
|
||||
private ushort _length;
|
||||
private fixed char _buffer[MAX_LENGTH];
|
||||
|
||||
public readonly ushort Length => _length;
|
||||
public string Value
|
||||
{
|
||||
get
|
||||
{
|
||||
fixed (char* bufferPtr = _buffer)
|
||||
{
|
||||
return new string(bufferPtr, 0, _length);
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
_length = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (value.Length > MAX_LENGTH)
|
||||
{
|
||||
throw new ArgumentException("Input string is too long to fit in FixedString32.");
|
||||
}
|
||||
|
||||
_length = (ushort)value.Length;
|
||||
fixed (char* bufferPtr = _buffer)
|
||||
fixed (char* valuePtr = value)
|
||||
{
|
||||
Unsafe.CopyBlockUnaligned(bufferPtr, valuePtr, (uint)(_length * sizeof(char)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public FixedString32(string input)
|
||||
: this(input.AsSpan())
|
||||
{
|
||||
}
|
||||
|
||||
public FixedString32(ReadOnlySpan<char> input)
|
||||
{
|
||||
if (input.Length > MAX_LENGTH)
|
||||
{
|
||||
throw new ArgumentException("Input byte array is too long to fit in FixedString32.");
|
||||
}
|
||||
|
||||
_length = (ushort)input.Length;
|
||||
|
||||
fixed (char* inputPtr = input)
|
||||
fixed (char* bufferPtr = _buffer)
|
||||
{
|
||||
Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, (uint)(_length * sizeof(char)));
|
||||
}
|
||||
}
|
||||
|
||||
public FixedString32(char* input, ushort length)
|
||||
: this(new ReadOnlySpan<char>(input, length))
|
||||
{
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Span<byte> AsSpan()
|
||||
{
|
||||
fixed (char* ptr = _buffer)
|
||||
{
|
||||
return new(ptr, _length);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly char* GetUnsafePtr()
|
||||
{
|
||||
return (char*)((ushort*)Unsafe.AsPointer(in this) + 1);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a stack allocated fixed-size UTF-8 encoded string of length 64 bytes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This struct is designed to hold data on the stack. Every copy of this struct causes a copy of the underlying data.
|
||||
/// If you need a heap allocated fixed-size UTF-8 encoded string of length 64 bytes, consider using <see cref="Misaki.HighPerformance.Unsafe.Buffer.FixedString64"/>.
|
||||
/// </remarks>
|
||||
[StructLayout(LayoutKind.Sequential, Size = 64)]
|
||||
public unsafe struct FixedString64
|
||||
{
|
||||
public const int MAX_LENGTH = 31;
|
||||
|
||||
private ushort _length;
|
||||
private fixed char _buffer[MAX_LENGTH];
|
||||
|
||||
public readonly ushort Length => _length;
|
||||
public string Value
|
||||
{
|
||||
get
|
||||
{
|
||||
fixed (char* bufferPtr = _buffer)
|
||||
{
|
||||
return new string(bufferPtr, 0, _length);
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
_length = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (value.Length > MAX_LENGTH)
|
||||
{
|
||||
throw new ArgumentException("Input string is too long to fit in FixedString64.");
|
||||
}
|
||||
|
||||
_length = (ushort)value.Length;
|
||||
fixed (char* bufferPtr = _buffer)
|
||||
fixed (char* valuePtr = value)
|
||||
{
|
||||
Unsafe.CopyBlockUnaligned(bufferPtr, valuePtr, (uint)(_length * sizeof(char)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public FixedString64(string input)
|
||||
: this(input.AsSpan())
|
||||
{
|
||||
}
|
||||
|
||||
public FixedString64(ReadOnlySpan<char> input)
|
||||
{
|
||||
if (input.Length > MAX_LENGTH)
|
||||
{
|
||||
throw new ArgumentException("Input byte array is too long to fit in FixedString64.");
|
||||
}
|
||||
|
||||
_length = (ushort)input.Length;
|
||||
|
||||
fixed (char* inputPtr = input)
|
||||
fixed (char* bufferPtr = _buffer)
|
||||
{
|
||||
Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, (uint)(_length * sizeof(char)));
|
||||
}
|
||||
}
|
||||
|
||||
public FixedString64(char* input, ushort length)
|
||||
: this(new ReadOnlySpan<char>(input, length))
|
||||
{
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Span<byte> AsSpan()
|
||||
{
|
||||
fixed (char* ptr = _buffer)
|
||||
{
|
||||
return new(ptr, _length);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly char* GetUnsafePtr()
|
||||
{
|
||||
return (char*)((ushort*)Unsafe.AsPointer(in this) + 1);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a stack allocated fixed-size UTF-8 encoded string of length 128 bytes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This struct is designed to hold data on the stack. Every copy of this struct causes a copy of the underlying data.
|
||||
/// If you need a heap allocated fixed-size UTF-8 encoded string of length 128 bytes, consider using <see cref="Misaki.HighPerformance.Unsafe.Buffer.FixedString128"/>.
|
||||
/// </remarks>
|
||||
[StructLayout(LayoutKind.Sequential, Size = 128)]
|
||||
public unsafe struct FixedString128
|
||||
{
|
||||
public const int MAX_LENGTH = 63;
|
||||
|
||||
private ushort _length;
|
||||
private fixed char _buffer[MAX_LENGTH];
|
||||
|
||||
public readonly ushort Length => _length;
|
||||
public string Value
|
||||
{
|
||||
get
|
||||
{
|
||||
fixed (char* bufferPtr = _buffer)
|
||||
{
|
||||
return new string(bufferPtr, 0, _length);
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
_length = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (value.Length > MAX_LENGTH)
|
||||
{
|
||||
throw new ArgumentException("Input string is too long to fit in FixedString128.");
|
||||
}
|
||||
|
||||
_length = (ushort)value.Length;
|
||||
fixed (char* bufferPtr = _buffer)
|
||||
fixed (char* valuePtr = value)
|
||||
{
|
||||
Unsafe.CopyBlockUnaligned(bufferPtr, valuePtr, (uint)(_length * sizeof(char)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public FixedString128(string input)
|
||||
: this(input.AsSpan())
|
||||
{
|
||||
}
|
||||
|
||||
public FixedString128(ReadOnlySpan<char> input)
|
||||
{
|
||||
if (input.Length > MAX_LENGTH)
|
||||
{
|
||||
throw new ArgumentException("Input byte array is too long to fit in FixedString128.");
|
||||
}
|
||||
|
||||
_length = (ushort)input.Length;
|
||||
|
||||
fixed (char* inputPtr = input)
|
||||
fixed (char* bufferPtr = _buffer)
|
||||
{
|
||||
Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, (uint)(_length * sizeof(char)));
|
||||
}
|
||||
}
|
||||
|
||||
public FixedString128(char* input, ushort length)
|
||||
: this(new ReadOnlySpan<char>(input, length))
|
||||
{
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Span<byte> AsSpan()
|
||||
{
|
||||
fixed (char* ptr = _buffer)
|
||||
{
|
||||
return new(ptr, _length);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly char* GetUnsafePtr()
|
||||
{
|
||||
return (char*)((ushort*)Unsafe.AsPointer(in this) + 1);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a stack allocated fixed-size UTF-8 encoded string of length 256 bytes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This struct is designed to hold data on the stack. Every copy of this struct causes a copy of the underlying data.
|
||||
/// If you need a heap allocated fixed-size UTF-8 encoded string of length 256 bytes, consider using <see cref="Misaki.HighPerformance.Unsafe.Buffer.FixedString256"/>.
|
||||
/// </remarks>
|
||||
[StructLayout(LayoutKind.Sequential, Size = 256)]
|
||||
public unsafe struct FixedString256
|
||||
{
|
||||
public const int MAX_LENGTH = 127;
|
||||
|
||||
private ushort _length;
|
||||
private fixed char _buffer[MAX_LENGTH];
|
||||
|
||||
public readonly ushort Length => _length;
|
||||
public string Value
|
||||
{
|
||||
get
|
||||
{
|
||||
fixed (char* bufferPtr = _buffer)
|
||||
{
|
||||
return new string(bufferPtr, 0, _length);
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
_length = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (value.Length > MAX_LENGTH)
|
||||
{
|
||||
throw new ArgumentException("Input string is too long to fit in FixedString256.");
|
||||
}
|
||||
|
||||
_length = (ushort)value.Length;
|
||||
fixed (char* bufferPtr = _buffer)
|
||||
fixed (char* valuePtr = value)
|
||||
{
|
||||
Unsafe.CopyBlockUnaligned(bufferPtr, valuePtr, (uint)(_length * sizeof(char)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public FixedString256(string input)
|
||||
: this(input.AsSpan())
|
||||
{
|
||||
}
|
||||
|
||||
public FixedString256(ReadOnlySpan<char> input)
|
||||
{
|
||||
if (input.Length > MAX_LENGTH)
|
||||
{
|
||||
throw new ArgumentException("Input byte array is too long to fit in FixedString256.");
|
||||
}
|
||||
|
||||
_length = (ushort)input.Length;
|
||||
|
||||
fixed (char* inputPtr = input)
|
||||
fixed (char* bufferPtr = _buffer)
|
||||
{
|
||||
Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, (uint)(_length * sizeof(char)));
|
||||
}
|
||||
}
|
||||
|
||||
public FixedString256(char* input, ushort length)
|
||||
: this(new ReadOnlySpan<char>(input, length))
|
||||
{
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Span<byte> AsSpan()
|
||||
{
|
||||
fixed (char* ptr = _buffer)
|
||||
{
|
||||
return new(ptr, _length);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly char* GetUnsafePtr()
|
||||
{
|
||||
return (char*)((ushort*)Unsafe.AsPointer(in this) + 1);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a stack allocated fixed-size UTF-8 encoded string of length 512 bytes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This struct is designed to hold data on the stack. Every copy of this struct causes a copy of the underlying data.
|
||||
/// If you need a heap allocated fixed-size UTF-8 encoded string of length 512 bytes, consider using <see cref="Misaki.HighPerformance.Unsafe.Buffer.FixedString512"/>.
|
||||
/// </remarks>
|
||||
[StructLayout(LayoutKind.Sequential, Size = 512)]
|
||||
public unsafe struct FixedString512
|
||||
{
|
||||
public const int MAX_LENGTH = 255;
|
||||
|
||||
private ushort _length;
|
||||
private fixed char _buffer[MAX_LENGTH];
|
||||
|
||||
public readonly ushort Length => _length;
|
||||
public string Value
|
||||
{
|
||||
get
|
||||
{
|
||||
fixed (char* bufferPtr = _buffer)
|
||||
{
|
||||
return new string(bufferPtr, 0, _length);
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
_length = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (value.Length > MAX_LENGTH)
|
||||
{
|
||||
throw new ArgumentException("Input string is too long to fit in FixedString512.");
|
||||
}
|
||||
|
||||
_length = (ushort)value.Length;
|
||||
fixed (char* bufferPtr = _buffer)
|
||||
fixed (char* valuePtr = value)
|
||||
{
|
||||
Unsafe.CopyBlockUnaligned(bufferPtr, valuePtr, (uint)(_length * sizeof(char)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public FixedString512(string input)
|
||||
: this(input.AsSpan())
|
||||
{
|
||||
}
|
||||
|
||||
public FixedString512(ReadOnlySpan<char> input)
|
||||
{
|
||||
if (input.Length > MAX_LENGTH)
|
||||
{
|
||||
throw new ArgumentException("Input byte array is too long to fit in FixedString512.");
|
||||
}
|
||||
|
||||
_length = (ushort)input.Length;
|
||||
|
||||
fixed (char* inputPtr = input)
|
||||
fixed (char* bufferPtr = _buffer)
|
||||
{
|
||||
Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, (uint)(_length * sizeof(char)));
|
||||
}
|
||||
}
|
||||
|
||||
public FixedString512(char* input, ushort length)
|
||||
: this(new ReadOnlySpan<char>(input, length))
|
||||
{
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Span<byte> AsSpan()
|
||||
{
|
||||
fixed (char* ptr = _buffer)
|
||||
{
|
||||
return new(ptr, _length);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly char* GetUnsafePtr()
|
||||
{
|
||||
return (char*)((ushort*)Unsafe.AsPointer(in this) + 1);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a stack allocated fixed-size UTF-8 encoded string of length 1024 bytes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This struct is designed to hold data on the stack. Every copy of this struct causes a copy of the underlying data.
|
||||
/// If you need a heap allocated fixed-size UTF-8 encoded string of length 1024 bytes, consider using <see cref="Misaki.HighPerformance.Unsafe.Buffer.FixedString1024"/>.
|
||||
/// </remarks>
|
||||
[StructLayout(LayoutKind.Sequential, Size = 1024)]
|
||||
public unsafe struct FixedString1024
|
||||
{
|
||||
public const int MAX_LENGTH = 511;
|
||||
|
||||
private ushort _length;
|
||||
private fixed char _buffer[MAX_LENGTH];
|
||||
|
||||
public readonly ushort Length => _length;
|
||||
public string Value
|
||||
{
|
||||
get
|
||||
{
|
||||
fixed (char* bufferPtr = _buffer)
|
||||
{
|
||||
return new string(bufferPtr, 0, _length);
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
_length = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (value.Length > MAX_LENGTH)
|
||||
{
|
||||
throw new ArgumentException("Input string is too long to fit in FixedString1024.");
|
||||
}
|
||||
|
||||
_length = (ushort)value.Length;
|
||||
fixed (char* bufferPtr = _buffer)
|
||||
fixed (char* valuePtr = value)
|
||||
{
|
||||
Unsafe.CopyBlockUnaligned(bufferPtr, valuePtr, (uint)(_length * sizeof(char)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public FixedString1024(string input)
|
||||
: this(input.AsSpan())
|
||||
{
|
||||
}
|
||||
|
||||
public FixedString1024(ReadOnlySpan<char> input)
|
||||
{
|
||||
if (input.Length > MAX_LENGTH)
|
||||
{
|
||||
throw new ArgumentException("Input byte array is too long to fit in FixedString1024.");
|
||||
}
|
||||
|
||||
_length = (ushort)input.Length;
|
||||
|
||||
fixed (char* inputPtr = input)
|
||||
fixed (char* bufferPtr = _buffer)
|
||||
{
|
||||
Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, (uint)(_length * sizeof(char)));
|
||||
}
|
||||
}
|
||||
|
||||
public FixedString1024(char* input, ushort length)
|
||||
: this(new ReadOnlySpan<char>(input, length))
|
||||
{
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Span<byte> AsSpan()
|
||||
{
|
||||
fixed (char* ptr = _buffer)
|
||||
{
|
||||
return new(ptr, _length);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly char* GetUnsafePtr()
|
||||
{
|
||||
return (char*)((ushort*)Unsafe.AsPointer(in this) + 1);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a stack allocated fixed-size UTF-8 encoded string of length 2048 bytes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This struct is designed to hold data on the stack. Every copy of this struct causes a copy of the underlying data.
|
||||
/// If you need a heap allocated fixed-size UTF-8 encoded string of length 2048 bytes, consider using <see cref="Misaki.HighPerformance.Unsafe.Buffer.FixedString2048"/>.
|
||||
/// </remarks>
|
||||
[StructLayout(LayoutKind.Sequential, Size = 2048)]
|
||||
public unsafe struct FixedString2048
|
||||
{
|
||||
public const int MAX_LENGTH = 1023;
|
||||
|
||||
private ushort _length;
|
||||
private fixed char _buffer[MAX_LENGTH];
|
||||
|
||||
public readonly ushort Length => _length;
|
||||
public string Value
|
||||
{
|
||||
get
|
||||
{
|
||||
fixed (char* bufferPtr = _buffer)
|
||||
{
|
||||
return new string(bufferPtr, 0, _length);
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
_length = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (value.Length > MAX_LENGTH)
|
||||
{
|
||||
throw new ArgumentException("Input string is too long to fit in FixedString2048.");
|
||||
}
|
||||
|
||||
_length = (ushort)value.Length;
|
||||
fixed (char* bufferPtr = _buffer)
|
||||
fixed (char* valuePtr = value)
|
||||
{
|
||||
Unsafe.CopyBlockUnaligned(bufferPtr, valuePtr, (uint)(_length * sizeof(char)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public FixedString2048(string input)
|
||||
: this(input.AsSpan())
|
||||
{
|
||||
}
|
||||
|
||||
public FixedString2048(ReadOnlySpan<char> input)
|
||||
{
|
||||
if (input.Length > MAX_LENGTH)
|
||||
{
|
||||
throw new ArgumentException("Input byte array is too long to fit in FixedString2048.");
|
||||
}
|
||||
|
||||
_length = (ushort)input.Length;
|
||||
|
||||
fixed (char* inputPtr = input)
|
||||
fixed (char* bufferPtr = _buffer)
|
||||
{
|
||||
Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, (uint)(_length * sizeof(char)));
|
||||
}
|
||||
}
|
||||
|
||||
public FixedString2048(char* input, ushort length)
|
||||
: this(new ReadOnlySpan<char>(input, length))
|
||||
{
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Span<byte> AsSpan()
|
||||
{
|
||||
fixed (char* ptr = _buffer)
|
||||
{
|
||||
return new(ptr, _length);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly char* GetUnsafePtr()
|
||||
{
|
||||
return (char*)((ushort*)Unsafe.AsPointer(in this) + 1);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a stack allocated fixed-size UTF-8 encoded string of length 4096 bytes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This struct is designed to hold data on the stack. Every copy of this struct causes a copy of the underlying data.
|
||||
/// If you need a heap allocated fixed-size UTF-8 encoded string of length 4096 bytes, consider using <see cref="Misaki.HighPerformance.Unsafe.Buffer.FixedString4096"/>.
|
||||
/// </remarks>
|
||||
[StructLayout(LayoutKind.Sequential, Size = 4096)]
|
||||
public unsafe struct FixedString4096
|
||||
{
|
||||
public const int MAX_LENGTH = 2047;
|
||||
|
||||
private ushort _length;
|
||||
private fixed char _buffer[MAX_LENGTH];
|
||||
|
||||
public readonly ushort Length => _length;
|
||||
public string Value
|
||||
{
|
||||
get
|
||||
{
|
||||
fixed (char* bufferPtr = _buffer)
|
||||
{
|
||||
return new string(bufferPtr, 0, _length);
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
_length = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (value.Length > MAX_LENGTH)
|
||||
{
|
||||
throw new ArgumentException("Input string is too long to fit in FixedString4096.");
|
||||
}
|
||||
|
||||
_length = (ushort)value.Length;
|
||||
fixed (char* bufferPtr = _buffer)
|
||||
fixed (char* valuePtr = value)
|
||||
{
|
||||
Unsafe.CopyBlockUnaligned(bufferPtr, valuePtr, (uint)(_length * sizeof(char)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public FixedString4096(string input)
|
||||
: this(input.AsSpan())
|
||||
{
|
||||
}
|
||||
|
||||
public FixedString4096(ReadOnlySpan<char> input)
|
||||
{
|
||||
if (input.Length > MAX_LENGTH)
|
||||
{
|
||||
throw new ArgumentException("Input byte array is too long to fit in FixedString4096.");
|
||||
}
|
||||
|
||||
_length = (ushort)input.Length;
|
||||
|
||||
fixed (char* inputPtr = input)
|
||||
fixed (char* bufferPtr = _buffer)
|
||||
{
|
||||
Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, (uint)(_length * sizeof(char)));
|
||||
}
|
||||
}
|
||||
|
||||
public FixedString4096(char* input, ushort length)
|
||||
: this(new ReadOnlySpan<char>(input, length))
|
||||
{
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Span<byte> AsSpan()
|
||||
{
|
||||
fixed (char* ptr = _buffer)
|
||||
{
|
||||
return new(ptr, _length);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly char* GetUnsafePtr()
|
||||
{
|
||||
return (char*)((ushort*)Unsafe.AsPointer(in this) + 1);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Value;
|
||||
}
|
||||
}
|
||||
|
||||
108
Misaki.HighPerformance.LowLevel/Collections/FixedString.tt
Normal file
108
Misaki.HighPerformance.LowLevel/Collections/FixedString.tt
Normal file
@@ -0,0 +1,108 @@
|
||||
<#@ template debug="false" hostspecific="false" language="C#" #>
|
||||
<#@ assembly name="System.Core" #>
|
||||
<#@ import namespace="System.Linq" #>
|
||||
<#@ import namespace="System.Text" #>
|
||||
<#@ import namespace="System.Collections.Generic" #>
|
||||
<#@ output extension=".gen.cs" #>
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Misaki.HighPerformance.LowLevel.Collections;
|
||||
|
||||
<# for (int i = 32; i <= 4096; i *= 2) { #>
|
||||
/// <summary>
|
||||
/// Represents a stack allocated fixed-size UTF-8 encoded string of length <#= i #> bytes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This struct is designed to hold data on the stack. Every copy of this struct causes a copy of the underlying data.
|
||||
/// If you need a heap allocated fixed-size UTF-8 encoded string of length <#= i #> bytes, consider using <see cref="Misaki.HighPerformance.Unsafe.Buffer.FixedString<#= i #>"/>.
|
||||
/// </remarks>
|
||||
[StructLayout(LayoutKind.Sequential, Size = <#= i #>)]
|
||||
public unsafe struct FixedString<#= i #>
|
||||
{
|
||||
public const int MAX_LENGTH = <#= (i - 2) / 2 #>;
|
||||
|
||||
private ushort _length;
|
||||
private fixed char _buffer[MAX_LENGTH];
|
||||
|
||||
public readonly ushort Length => _length;
|
||||
public string Value
|
||||
{
|
||||
get
|
||||
{
|
||||
fixed (char* bufferPtr = _buffer)
|
||||
{
|
||||
return new string(bufferPtr, 0, _length);
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
_length = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (value.Length > MAX_LENGTH)
|
||||
{
|
||||
throw new ArgumentException("Input string is too long to fit in FixedString<#= i #>.");
|
||||
}
|
||||
|
||||
_length = (ushort)value.Length;
|
||||
fixed (char* bufferPtr = _buffer)
|
||||
fixed (char* valuePtr = value)
|
||||
{
|
||||
Unsafe.CopyBlockUnaligned(bufferPtr, valuePtr, (uint)(_length * sizeof(char)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public FixedString<#= i #>(string input)
|
||||
: this(input.AsSpan())
|
||||
{
|
||||
}
|
||||
|
||||
public FixedString<#= i #>(ReadOnlySpan<char> input)
|
||||
{
|
||||
if (input.Length > MAX_LENGTH)
|
||||
{
|
||||
throw new ArgumentException("Input byte array is too long to fit in FixedString<#= i #>.");
|
||||
}
|
||||
|
||||
_length = (ushort)input.Length;
|
||||
|
||||
fixed (char* inputPtr = input)
|
||||
fixed (char* bufferPtr = _buffer)
|
||||
{
|
||||
Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, (uint)(_length * sizeof(char)));
|
||||
}
|
||||
}
|
||||
|
||||
public FixedString<#= i #>(char* input, ushort length)
|
||||
: this(new ReadOnlySpan<char>(input, length))
|
||||
{
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Span<byte> AsSpan()
|
||||
{
|
||||
fixed (char* ptr = _buffer)
|
||||
{
|
||||
return new(ptr, _length);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly char* GetUnsafePtr()
|
||||
{
|
||||
return (char*)((ushort*)Unsafe.AsPointer(in this) + 1);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Value;
|
||||
}
|
||||
}
|
||||
|
||||
<# } #>
|
||||
918
Misaki.HighPerformance.LowLevel/Collections/FixedText.gen.cs
Normal file
918
Misaki.HighPerformance.LowLevel/Collections/FixedText.gen.cs
Normal file
@@ -0,0 +1,918 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
128
Misaki.HighPerformance.LowLevel/Collections/FixedText.tt
Normal file
128
Misaki.HighPerformance.LowLevel/Collections/FixedText.tt
Normal file
@@ -0,0 +1,128 @@
|
||||
<#@ 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;
|
||||
}
|
||||
}
|
||||
|
||||
<# } #>
|
||||
728
Misaki.HighPerformance.LowLevel/Collections/HashMapHelper.cs
Normal file
728
Misaki.HighPerformance.LowLevel/Collections/HashMapHelper.cs
Normal file
@@ -0,0 +1,728 @@
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Utilities;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Misaki.HighPerformance.LowLevel.Collections;
|
||||
|
||||
public unsafe struct HashMapHelper<TKey> : IDisposable
|
||||
where TKey : unmanaged, IEquatable<TKey>
|
||||
{
|
||||
internal struct Enumerator
|
||||
{
|
||||
public HashMapHelper<TKey>* buffer;
|
||||
public int index;
|
||||
public int bucketIndex;
|
||||
public int nextIndex;
|
||||
|
||||
public Enumerator(HashMapHelper<TKey>* data)
|
||||
{
|
||||
buffer = data;
|
||||
index = -1;
|
||||
bucketIndex = 0;
|
||||
nextIndex = -1;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool MoveNext()
|
||||
{
|
||||
return buffer->MoveNext(ref bucketIndex, ref nextIndex, out index);
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
index = -1;
|
||||
bucketIndex = 0;
|
||||
nextIndex = -1;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public KeyValuePair<TKey, TValue> GetCurrent<TValue>()
|
||||
where TValue : unmanaged
|
||||
{
|
||||
return new KeyValuePair<TKey, TValue>(buffer->_keys[index], UnsafeUtility.ReadArrayElementRef<TValue>(buffer->_buffer, index));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public TKey GetCurrentKey()
|
||||
{
|
||||
if (index != -1)
|
||||
{
|
||||
return buffer->_keys[index];
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
// This buffer has 4 parts: TValue, TKey, Next, Buckets.
|
||||
private byte* _buffer;
|
||||
|
||||
internal TKey* _keys;
|
||||
internal int* _next;
|
||||
internal int* _buckets;
|
||||
|
||||
private int _count;
|
||||
private int _capacity;
|
||||
private int _bucketCapacity;
|
||||
private int _allocatedIndex;
|
||||
private int _firstFreeIndex;
|
||||
|
||||
private readonly int _alignment;
|
||||
private readonly int _sizeOfTValue;
|
||||
private readonly int _log2MinGrowth;
|
||||
|
||||
private MemoryHandle _memoryHandle;
|
||||
private AllocationHandle _allocationHandle;
|
||||
|
||||
public const int MINIMAL_CAPACITY = 64;
|
||||
|
||||
public readonly byte* Buffer => _buffer;
|
||||
|
||||
public readonly int Count => _count;
|
||||
|
||||
public readonly int Capacity => _capacity;
|
||||
|
||||
public readonly bool IsEmpty => !IsCreated || _count == 0;
|
||||
|
||||
public readonly bool IsCreated
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_buffer != null)
|
||||
{
|
||||
if (_allocationHandle.IsValid != null)
|
||||
{
|
||||
return _allocationHandle.IsValid(_allocationHandle.State, _memoryHandle);
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static int CalculateDataSize(int capacity, int bucketCapacity, int sizeOfTValue, out int outKeyOffset, out int outNextOffset, out int outBucketOffset)
|
||||
{
|
||||
var sizeOfTKey = sizeof(TKey);
|
||||
var sizeOfInt = sizeof(int);
|
||||
|
||||
var valuesSize = sizeOfTValue * capacity;
|
||||
var keysSize = sizeOfTKey * capacity;
|
||||
var nextSize = sizeOfInt * capacity;
|
||||
var bucketSize = sizeOfInt * bucketCapacity;
|
||||
var totalSize = valuesSize + keysSize + nextSize + bucketSize;
|
||||
|
||||
outKeyOffset = 0 + valuesSize;
|
||||
outNextOffset = outKeyOffset + keysSize;
|
||||
outBucketOffset = outNextOffset + nextSize;
|
||||
|
||||
return totalSize;
|
||||
}
|
||||
|
||||
public HashMapHelper(int capacity, int sizeOfTValue, int alignOfTValue, uint minGrowth, AllocationHandle handle, AllocationOption allocationOption)
|
||||
{
|
||||
if (capacity <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(capacity), "Capacity must be greater than zero.");
|
||||
}
|
||||
|
||||
if (sizeOfTValue < 0 || alignOfTValue < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(sizeOfTValue), "Size or alignment of TValue can not be less than zero.");
|
||||
}
|
||||
|
||||
_capacity = CalcCapacityCeilPow2(capacity);
|
||||
_bucketCapacity = _capacity * 2;
|
||||
|
||||
var alignOfKey = (int)AlignOf<TKey>();
|
||||
var alignOfInt = (int)AlignOf<int>();
|
||||
var maxDataAlign = Math.Max(Math.Max(alignOfTValue, alignOfKey), alignOfInt);
|
||||
|
||||
_alignment = maxDataAlign;
|
||||
_sizeOfTValue = sizeOfTValue;
|
||||
_log2MinGrowth = BitOperations.Log2(minGrowth);
|
||||
|
||||
_allocationHandle = handle;
|
||||
|
||||
var totalSize = CalculateDataSize(_capacity, _bucketCapacity, sizeOfTValue,
|
||||
out var keyOffset, out var nextOffset, out var bucketOffset);
|
||||
|
||||
AllocateBuffer(totalSize, keyOffset, nextOffset, bucketOffset, allocationOption);
|
||||
Clear();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[Conditional("ENABLE_COLLECTION_CHECKS")]
|
||||
private readonly void ThrowIfNotCreated()
|
||||
{
|
||||
if (!IsCreated)
|
||||
{
|
||||
throw new InvalidOperationException("The HashMapHelper is not created.");
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int CeilPow2(int x)
|
||||
{
|
||||
x -= 1;
|
||||
x |= x >> 1;
|
||||
x |= x >> 2;
|
||||
x |= x >> 4;
|
||||
x |= x >> 8;
|
||||
x |= x >> 16;
|
||||
return x + 1;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private readonly int CalcCapacityCeilPow2(int capacity)
|
||||
{
|
||||
capacity = Math.Max(Math.Max(1, _count), capacity);
|
||||
var newCapacity = Math.Max(capacity, 1 << _log2MinGrowth);
|
||||
var result = CeilPow2(newCapacity);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private readonly int GetBucket(int hash)
|
||||
{
|
||||
return hash & (_bucketCapacity - 1);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private readonly int GetBucket(in TKey key)
|
||||
{
|
||||
var h = key.GetHashCode();
|
||||
return GetBucket(h);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private readonly void CheckIndexOutOfBounds(int idx)
|
||||
{
|
||||
if ((uint)idx >= (uint)_capacity)
|
||||
{
|
||||
throw new InvalidOperationException($"Index {idx} is out of bounds for the hash map with capacity {_capacity}.");
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private int AllocateEntry(in TKey key)
|
||||
{
|
||||
int idx;
|
||||
|
||||
if (_allocatedIndex >= _capacity && _firstFreeIndex < 0)
|
||||
{
|
||||
var newCap = CalcCapacityCeilPow2(_capacity + (1 << _log2MinGrowth));
|
||||
Resize(newCap);
|
||||
}
|
||||
|
||||
idx = _firstFreeIndex;
|
||||
|
||||
if (idx >= 0)
|
||||
{
|
||||
_firstFreeIndex = _next[idx];
|
||||
}
|
||||
else
|
||||
{
|
||||
idx = _allocatedIndex++;
|
||||
}
|
||||
|
||||
CheckIndexOutOfBounds(idx);
|
||||
|
||||
UnsafeUtility.WriteArrayElement(_keys, idx, key);
|
||||
var bucket = GetBucket(key);
|
||||
|
||||
_next[idx] = _buckets[bucket];
|
||||
_buckets[bucket] = idx;
|
||||
_count++;
|
||||
|
||||
return idx;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void AllocateBuffer(int totalSize, int keyOffset, int nextOffset, int bucketOffset, AllocationOption allocationOption)
|
||||
{
|
||||
if (_allocationHandle.Alloc == null)
|
||||
{
|
||||
throw new InvalidOperationException("Target allocation handle does not support allocation.");
|
||||
}
|
||||
|
||||
MemoryHandle memHandle;
|
||||
var buf = (byte*)_allocationHandle.Alloc(_allocationHandle.State, (uint)totalSize, (nuint)_alignment, allocationOption, &memHandle);
|
||||
|
||||
_buffer = buf;
|
||||
_keys = (TKey*)(_buffer + keyOffset);
|
||||
_next = (int*)(_buffer + nextOffset);
|
||||
_buckets = (int*)(_buffer + bucketOffset);
|
||||
_memoryHandle = memHandle;
|
||||
}
|
||||
|
||||
private void ResizeExact(int newCapacity, int newBucketCapacity)
|
||||
{
|
||||
var totalSize = CalculateDataSize(newCapacity, newBucketCapacity, _sizeOfTValue,
|
||||
out var keyOffset, out var nextOffset, out var bucketOffset);
|
||||
|
||||
var oldBuffer = _buffer;
|
||||
var oldKeys = _keys;
|
||||
var oldNext = _next;
|
||||
var oldBuckets = _buckets;
|
||||
var oldBucketCapacity = _bucketCapacity;
|
||||
var oldMemoryHandle = _memoryHandle;
|
||||
|
||||
AllocateBuffer(totalSize, keyOffset, nextOffset, bucketOffset, AllocationOption.None);
|
||||
_capacity = newCapacity;
|
||||
_bucketCapacity = newBucketCapacity;
|
||||
|
||||
Clear();
|
||||
|
||||
for (int i = 0, num = oldBucketCapacity; i < num; ++i)
|
||||
{
|
||||
for (var idx = oldBuckets[i]; idx != -1; idx = oldNext[idx])
|
||||
{
|
||||
var newIdx = Add(oldKeys[idx]);
|
||||
MemCpy(_buffer + _sizeOfTValue * newIdx, oldBuffer + _sizeOfTValue * idx, (nuint)_sizeOfTValue);
|
||||
}
|
||||
}
|
||||
|
||||
if (_allocationHandle.Free != null)
|
||||
{
|
||||
_allocationHandle.Free(_allocationHandle.State, oldBuffer, oldMemoryHandle);
|
||||
}
|
||||
}
|
||||
|
||||
public void Resize(int newCapacity)
|
||||
{
|
||||
ThrowIfNotCreated();
|
||||
|
||||
newCapacity = Math.Max(newCapacity, _count);
|
||||
var newBucketCapacity = CeilPow2(newCapacity * 2);
|
||||
|
||||
if (_capacity == newCapacity && _bucketCapacity == newBucketCapacity)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ResizeExact(newCapacity, newBucketCapacity);
|
||||
}
|
||||
|
||||
public void TrimExcess()
|
||||
{
|
||||
ThrowIfNotCreated();
|
||||
|
||||
var capacity = CalcCapacityCeilPow2(_count);
|
||||
ResizeExact(capacity, capacity * 2);
|
||||
}
|
||||
|
||||
public int Find(in TKey key)
|
||||
{
|
||||
ThrowIfNotCreated();
|
||||
|
||||
if (_allocatedIndex <= 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
// First find the slot based on the hash
|
||||
var bucket = GetBucket(key);
|
||||
var entryIdx = _buckets[bucket];
|
||||
|
||||
if ((uint)entryIdx < (uint)_capacity)
|
||||
{
|
||||
var nextPtrs = _next;
|
||||
while (!UnsafeUtility.ReadArrayElement<TKey>(_keys, entryIdx).Equals(key))
|
||||
{
|
||||
entryIdx = nextPtrs[entryIdx];
|
||||
if ((uint)entryIdx >= (uint)_capacity)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return entryIdx;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public int TryAdd(in TKey key)
|
||||
{
|
||||
ThrowIfNotCreated();
|
||||
|
||||
if (Find(in key) != -1)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return AllocateEntry(key);
|
||||
}
|
||||
|
||||
public int Add(in TKey key)
|
||||
{
|
||||
ThrowIfNotCreated();
|
||||
|
||||
return AllocateEntry(key);
|
||||
}
|
||||
|
||||
public int TryRemove(in TKey key)
|
||||
{
|
||||
ThrowIfNotCreated();
|
||||
|
||||
if (_capacity == 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
var removed = 0;
|
||||
|
||||
// First find the slot based on the hash
|
||||
var bucket = GetBucket(key);
|
||||
|
||||
var prevEntry = -1;
|
||||
var entryIdx = _buckets[bucket];
|
||||
|
||||
while (entryIdx >= 0 && entryIdx < _capacity)
|
||||
{
|
||||
if (UnsafeUtility.ReadArrayElement<TKey>(_keys, entryIdx).Equals(key))
|
||||
{
|
||||
removed++;
|
||||
|
||||
// Found matching element, remove it
|
||||
if (prevEntry < 0)
|
||||
{
|
||||
_buckets[bucket] = _next[entryIdx];
|
||||
}
|
||||
else
|
||||
{
|
||||
_next[prevEntry] = _next[entryIdx];
|
||||
}
|
||||
|
||||
// And free the index
|
||||
var nextIdx = _next[entryIdx];
|
||||
_next[entryIdx] = _firstFreeIndex;
|
||||
_firstFreeIndex = entryIdx;
|
||||
entryIdx = nextIdx;
|
||||
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
prevEntry = entryIdx;
|
||||
entryIdx = _next[entryIdx];
|
||||
}
|
||||
}
|
||||
|
||||
_count -= removed;
|
||||
return 0 != removed ? removed : -1;
|
||||
}
|
||||
|
||||
public int RemoveAll(in TKey key)
|
||||
{
|
||||
ThrowIfNotCreated();
|
||||
|
||||
if (_capacity == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var removed = 0;
|
||||
var bucket = GetBucket(key);
|
||||
var prevEntry = -1;
|
||||
var entryIdx = _buckets[bucket];
|
||||
|
||||
while (entryIdx >= 0 && entryIdx < _capacity)
|
||||
{
|
||||
if (UnsafeUtility.ReadArrayElement<TKey>(_keys, entryIdx).Equals(key))
|
||||
{
|
||||
removed++;
|
||||
|
||||
var nextIdx = _next[entryIdx];
|
||||
if (prevEntry < 0)
|
||||
{
|
||||
_buckets[bucket] = nextIdx;
|
||||
}
|
||||
else
|
||||
{
|
||||
_next[prevEntry] = nextIdx;
|
||||
}
|
||||
|
||||
_next[entryIdx] = _firstFreeIndex;
|
||||
_firstFreeIndex = entryIdx;
|
||||
entryIdx = nextIdx;
|
||||
continue;
|
||||
}
|
||||
|
||||
prevEntry = entryIdx;
|
||||
entryIdx = _next[entryIdx];
|
||||
}
|
||||
|
||||
_count -= removed;
|
||||
return removed;
|
||||
}
|
||||
|
||||
public bool TryGetValue<TValue>(in TKey key, out TValue item)
|
||||
where TValue : unmanaged
|
||||
{
|
||||
ThrowIfNotCreated();
|
||||
|
||||
var idx = Find(key);
|
||||
|
||||
if (idx != -1)
|
||||
{
|
||||
item = UnsafeUtility.ReadArrayElement<TValue>(_buffer, idx);
|
||||
return true;
|
||||
}
|
||||
|
||||
item = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public int FindNext(int entryIdx, in TKey key)
|
||||
{
|
||||
ThrowIfNotCreated();
|
||||
|
||||
if ((uint)entryIdx >= (uint)_capacity)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
var nextIndex = _next[entryIdx];
|
||||
while ((uint)nextIndex < (uint)_capacity)
|
||||
{
|
||||
if (UnsafeUtility.ReadArrayElement<TKey>(_keys, nextIndex).Equals(key))
|
||||
{
|
||||
return nextIndex;
|
||||
}
|
||||
|
||||
nextIndex = _next[nextIndex];
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public int CountValuesForKey(in TKey key)
|
||||
{
|
||||
ThrowIfNotCreated();
|
||||
|
||||
var count = 0;
|
||||
for (var idx = Find(key); idx != -1; idx = FindNext(idx, key))
|
||||
{
|
||||
count++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
[UnscopedRef]
|
||||
public ref TValue GetValueRef<TValue>(in TKey key, out bool exists)
|
||||
where TValue : unmanaged
|
||||
{
|
||||
ThrowIfNotCreated();
|
||||
|
||||
var idx = Find(key);
|
||||
if (idx != -1)
|
||||
{
|
||||
exists = true;
|
||||
return ref UnsafeUtility.ReadArrayElementRef<TValue>(_buffer, idx);
|
||||
}
|
||||
|
||||
exists = false;
|
||||
return ref Unsafe.NullRef<TValue>();
|
||||
}
|
||||
|
||||
[UnscopedRef]
|
||||
public ref TValue GetValueRefOrAddDefault<TValue>(in TKey key, out bool exists)
|
||||
where TValue : unmanaged
|
||||
{
|
||||
ThrowIfNotCreated();
|
||||
|
||||
var idx = -1;
|
||||
var bucket = -1;
|
||||
var hash = key.GetHashCode();
|
||||
|
||||
if (_allocatedIndex > 0)
|
||||
{
|
||||
// First find the slot based on the hash
|
||||
bucket = GetBucket(hash);
|
||||
var entryIdx = _buckets[bucket];
|
||||
|
||||
if ((uint)entryIdx < (uint)_capacity)
|
||||
{
|
||||
var nextPtrs = _next;
|
||||
while (!UnsafeUtility.ReadArrayElement<TKey>(_keys, entryIdx).Equals(key))
|
||||
{
|
||||
entryIdx = nextPtrs[entryIdx];
|
||||
if ((uint)entryIdx >= (uint)_capacity)
|
||||
{
|
||||
goto Found;
|
||||
}
|
||||
}
|
||||
|
||||
idx = entryIdx;
|
||||
goto Found;
|
||||
}
|
||||
}
|
||||
|
||||
Found:
|
||||
|
||||
if (idx != -1)
|
||||
{
|
||||
exists = true;
|
||||
return ref UnsafeUtility.ReadArrayElementRef<TValue>(_buffer, idx);
|
||||
}
|
||||
|
||||
idx = AllocateEntry(key);
|
||||
|
||||
UnsafeUtility.WriteArrayElement(_buffer, idx, default(TValue));
|
||||
|
||||
exists = false;
|
||||
return ref UnsafeUtility.ReadArrayElementRef<TValue>(_buffer, idx);
|
||||
}
|
||||
|
||||
public bool MoveNextSearch(ref int bucketIndex, ref int nextIndex, out int index)
|
||||
{
|
||||
ThrowIfNotCreated();
|
||||
|
||||
for (int i = bucketIndex, num = _bucketCapacity; i < num; ++i)
|
||||
{
|
||||
var idx = _buckets[i];
|
||||
|
||||
if (idx != -1)
|
||||
{
|
||||
index = idx;
|
||||
bucketIndex = i + 1;
|
||||
nextIndex = _next[idx];
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
index = -1;
|
||||
bucketIndex = _bucketCapacity;
|
||||
nextIndex = -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool MoveNext(ref int bucketIndex, ref int nextIndex, out int index)
|
||||
{
|
||||
ThrowIfNotCreated();
|
||||
|
||||
if (nextIndex != -1)
|
||||
{
|
||||
index = nextIndex;
|
||||
nextIndex = _next[nextIndex];
|
||||
return true;
|
||||
}
|
||||
|
||||
return MoveNextSearch(ref bucketIndex, ref nextIndex, out index);
|
||||
}
|
||||
|
||||
internal UnsafeArray<TKey> GetKeyArray(Allocator allocator)
|
||||
{
|
||||
ThrowIfNotCreated();
|
||||
|
||||
var result = new UnsafeArray<TKey>(_count, allocator);
|
||||
|
||||
for (int i = 0, count = 0, max = result.Count, capacity = _bucketCapacity; i < capacity && count < max; i++)
|
||||
{
|
||||
var bucket = _buckets[i];
|
||||
|
||||
while (bucket != -1)
|
||||
{
|
||||
result[count++] = UnsafeUtility.ReadArrayElement<TKey>(_keys, bucket);
|
||||
bucket = _next[bucket];
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
internal UnsafeArray<TValue> GetValueArray<TValue>(Allocator allocator)
|
||||
where TValue : unmanaged
|
||||
{
|
||||
ThrowIfNotCreated();
|
||||
|
||||
var result = new UnsafeArray<TValue>(_count, allocator);
|
||||
|
||||
for (int i = 0, count = 0, max = result.Count, capacity = _bucketCapacity; i < capacity && count < max; ++i)
|
||||
{
|
||||
var bucket = _buckets[i];
|
||||
|
||||
while (bucket != -1)
|
||||
{
|
||||
result[count++] = UnsafeUtility.ReadArrayElement<TValue>(_buffer, bucket);
|
||||
bucket = _next[bucket];
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public UnsafeArray<KeyValuePair<TKey, TValue>> GetKeyValueArrays<TValue>(Allocator allocator)
|
||||
where TValue : unmanaged
|
||||
{
|
||||
ThrowIfNotCreated();
|
||||
|
||||
var result = new UnsafeArray<KeyValuePair<TKey, TValue>>(_count, allocator);
|
||||
|
||||
for (int i = 0, count = 0, max = result.Count, capacity = _bucketCapacity; i < capacity && count < max; i++)
|
||||
{
|
||||
var bucket = _buckets[i];
|
||||
|
||||
while (bucket != -1)
|
||||
{
|
||||
result[count] = new(UnsafeUtility.ReadArrayElement<TKey>(_keys, bucket),
|
||||
UnsafeUtility.ReadArrayElement<TValue>(_buffer, bucket));
|
||||
|
||||
count++;
|
||||
bucket = _next[bucket];
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
ThrowIfNotCreated();
|
||||
|
||||
MemSet(_buckets, 0xff, (nuint)_bucketCapacity * sizeof(int));
|
||||
MemSet(_next, 0xff, (nuint)_capacity * sizeof(int));
|
||||
|
||||
_count = 0;
|
||||
_firstFreeIndex = -1;
|
||||
_allocatedIndex = 0;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!IsCreated)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_allocationHandle.Free != null)
|
||||
{
|
||||
_allocationHandle.Free(_allocationHandle.State, _buffer, _memoryHandle);
|
||||
}
|
||||
|
||||
_buffer = null;
|
||||
_keys = null;
|
||||
_next = null;
|
||||
_buckets = null;
|
||||
|
||||
_count = 0;
|
||||
_capacity = 0;
|
||||
_bucketCapacity = 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
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()
|
||||
{
|
||||
return new Enumerator(in this);
|
||||
}
|
||||
|
||||
IEnumerator<T> IEnumerable<T>.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return 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);
|
||||
}
|
||||
}
|
||||
267
Misaki.HighPerformance.LowLevel/Collections/UnTypedArray.cs
Normal file
267
Misaki.HighPerformance.LowLevel/Collections/UnTypedArray.cs
Normal file
@@ -0,0 +1,267 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
324
Misaki.HighPerformance.LowLevel/Collections/UnsafeArray.cs
Normal file
324
Misaki.HighPerformance.LowLevel/Collections/UnsafeArray.cs
Normal file
@@ -0,0 +1,324 @@
|
||||
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()
|
||||
{
|
||||
return new((UnsafeArray<T>*)UnsafeUtility.AddressOf(ref this));
|
||||
}
|
||||
|
||||
IEnumerator<T> IEnumerable<T>.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invalid constructor, use <see cref="UnsafeArray(int, Allocator, AllocationOption)"/> or <see cref="UnsafeArray(int, 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;
|
||||
}
|
||||
}
|
||||
1034
Misaki.HighPerformance.LowLevel/Collections/UnsafeBitSet.cs
Normal file
1034
Misaki.HighPerformance.LowLevel/Collections/UnsafeBitSet.cs
Normal file
File diff suppressed because it is too large
Load Diff
262
Misaki.HighPerformance.LowLevel/Collections/UnsafeHashMap.cs
Normal file
262
Misaki.HighPerformance.LowLevel/Collections/UnsafeHashMap.cs
Normal file
@@ -0,0 +1,262 @@
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
|
||||
using Misaki.HighPerformance.LowLevel.Utilities;
|
||||
using System.Collections;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
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()
|
||||
{
|
||||
return _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()
|
||||
{
|
||||
return new((HashMapHelper<TKey>*)UnsafeUtility.AddressOf(ref this));
|
||||
}
|
||||
|
||||
IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invalid constructor, use <see cref="UnsafeHashMap(int, Allocator, AllocationOption)"/> or <see cref="UnsafeHashMap(int, 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;
|
||||
}
|
||||
|
||||
[UnscopedRef]
|
||||
public ref TValue GetValueRef(in TKey key, out bool exists)
|
||||
{
|
||||
return ref _helper.GetValueRef<TValue>(key, out exists);
|
||||
}
|
||||
|
||||
[UnscopedRef]
|
||||
public ref TValue GetValueRefOrAddDefault(in TKey key, out bool exists)
|
||||
{
|
||||
return ref _helper.GetValueRefOrAddDefault<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)
|
||||
{
|
||||
return _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)
|
||||
{
|
||||
return _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)
|
||||
{
|
||||
return _helper.GetKeyValueArrays<TValue>(allocator);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly void* GetUnsafePtr()
|
||||
{
|
||||
return _helper.Buffer;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_helper.Dispose();
|
||||
}
|
||||
}
|
||||
152
Misaki.HighPerformance.LowLevel/Collections/UnsafeHashSet.cs
Normal file
152
Misaki.HighPerformance.LowLevel/Collections/UnsafeHashSet.cs
Normal file
@@ -0,0 +1,152 @@
|
||||
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()
|
||||
{
|
||||
return _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()
|
||||
{
|
||||
return new((HashMapHelper<T>*)UnsafeUtility.AddressOf(ref this));
|
||||
}
|
||||
|
||||
IEnumerator<T> IEnumerable<T>.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invalid constructor. Use <see cref="UnsafeHashSet(int, Allocator, AllocationOption)"/> or <see cref="UnsafeHashSet(int, 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();
|
||||
}
|
||||
}
|
||||
489
Misaki.HighPerformance.LowLevel/Collections/UnsafeList.cs
Normal file
489
Misaki.HighPerformance.LowLevel/Collections/UnsafeList.cs
Normal file
@@ -0,0 +1,489 @@
|
||||
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 (var 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 reader for an UnsafeList.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Use <see cref="AsParallelReader"/> to create a parallel reader for a list.
|
||||
/// The list must live at least as long as the parallel reader, and the parallel reader must not be used after the list is disposed.
|
||||
/// </remarks>
|
||||
public readonly unsafe struct ParallelReader
|
||||
{
|
||||
public readonly UnsafeList<T>* listData;
|
||||
public readonly int Count => listData->_count;
|
||||
|
||||
public ref readonly T this[int index]
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => ref listData->_array[index];
|
||||
}
|
||||
|
||||
public ref readonly T this[uint index]
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => ref listData->_array[index];
|
||||
}
|
||||
|
||||
internal ParallelReader(UnsafeList<T>* list)
|
||||
{
|
||||
listData = list;
|
||||
}
|
||||
|
||||
public readonly Enumerator GetEnumerator()
|
||||
{
|
||||
return new Enumerator(listData);
|
||||
}
|
||||
|
||||
public readonly ReadOnlySpan<T> AsSpan()
|
||||
{
|
||||
return new ReadOnlySpan<T>(listData->_array.GetUnsafePtr(), listData->_count);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A parallel writer for an UnsafeList.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Use <see cref="AsParallelWriter"/> to create a parallel writer for a list.
|
||||
/// The list must live at least as long as the parallel writer, and the parallel writer must not be used after the list is disposed.
|
||||
/// </remarks>
|
||||
public readonly unsafe struct ParallelWriter
|
||||
{
|
||||
public readonly 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];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invalid constructor, use <see cref="UnsafeList(int, Allocator, AllocationOption)"/> or <see cref="UnsafeList(int, 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 Enumerator GetEnumerator()
|
||||
{
|
||||
return new((UnsafeList<T>*)UnsafeUtility.AddressOf(ref this));
|
||||
}
|
||||
|
||||
IEnumerator<T> IEnumerable<T>.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides a parallel reader for the current list, enabling thread-safe read operations.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The list must live at least as long as the parallel reader, and the parallel reader must not be used after the list is disposed.
|
||||
/// For example, if you need to access the list in job system and wait that job in another stack frame, please always allocate the list struct itself on heap.
|
||||
/// Otherwise the parallel reader will be invalid after the stack frame that creates the list is popped, even if the list's internal array is still valid.
|
||||
/// </remarks>
|
||||
/// <returns>A <see cref="ParallelReader"/> instance that can be used to read items from the list in a thread-safe manner.</returns>
|
||||
public ParallelReader AsParallelReader()
|
||||
{
|
||||
return new((UnsafeList<T>*)UnsafeUtility.AddressOf(ref this));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides a parallel writer for the current list, enabling thread-safe additions to the list.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The list must live at least as long as the parallel writer, and the parallel writer must not be used after the list is disposed.
|
||||
/// For example, if you need to access the list in job system and wait that job in another stack frame, please always allocate the list struct itself on heap.
|
||||
/// Otherwise the parallel writer will be invalid after the stack frame that creates the list is popped, even if the list's internal array is still valid.
|
||||
/// </remarks>
|
||||
/// <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()
|
||||
{
|
||||
return new((UnsafeList<T>*)UnsafeUtility.AddressOf(ref this));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the current list to an UnsafeArray representation.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The returned <see cref="UnsafeArray{T}"/> shares the same underlying data as the list and does not own the memory.
|
||||
/// </remarks>
|
||||
/// <returns>A new <see cref="UnsafeArray{T}"/> instance.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly UnsafeArray<T> AsUnsafeArray()
|
||||
{
|
||||
return new UnsafeArray<T>((T*)_array.GetUnsafePtr(), _count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the current list to a read-only collection that provides unsafe access to its elements.
|
||||
/// </summary>
|
||||
/// <returns>A new <see cref="ReadOnlyUnsafeCollection{T}"/> instance that allows for read-only access to the list's elements without copying.</returns>
|
||||
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>
|
||||
public void AddRange(Span<T> values)
|
||||
{
|
||||
var newSize = _count + values.Length;
|
||||
if (newSize > Capacity)
|
||||
{
|
||||
Resize(Capacity + values.Length);
|
||||
}
|
||||
|
||||
fixed (T* ptr = values)
|
||||
{
|
||||
MemCpy(UnsafeUtility.ReadArrayElementUnsafe<T>(_array.GetUnsafePtr(), _count), ptr, (uint)(values.Length * sizeof(T)));
|
||||
}
|
||||
|
||||
_count += values.Length;
|
||||
}
|
||||
|
||||
/// <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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
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 UnsafeMultiHashMap<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 readonly KeyValuePair<TKey, TValue> Current => _enumerator.GetCurrent<TValue>();
|
||||
readonly object IEnumerator.Current => Current;
|
||||
|
||||
public Enumerator(HashMapHelper<TKey>* data)
|
||||
{
|
||||
_enumerator = new HashMapHelper<TKey>.Enumerator(data);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool MoveNext()
|
||||
{
|
||||
return _enumerator.MoveNext();
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_enumerator.Reset();
|
||||
}
|
||||
|
||||
public readonly void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public struct Iterator
|
||||
{
|
||||
internal TKey _key;
|
||||
internal int _entryIndex;
|
||||
|
||||
internal Iterator(in TKey key, int entryIndex)
|
||||
{
|
||||
_key = key;
|
||||
_entryIndex = entryIndex;
|
||||
}
|
||||
}
|
||||
|
||||
public struct ValueEnumerable
|
||||
{
|
||||
private readonly HashMapHelper<TKey>* _data;
|
||||
private readonly TKey _key;
|
||||
|
||||
internal ValueEnumerable(HashMapHelper<TKey>* data, in TKey key)
|
||||
{
|
||||
_data = data;
|
||||
_key = key;
|
||||
}
|
||||
|
||||
public readonly ValueEnumerator GetEnumerator()
|
||||
{
|
||||
return new(_data, _key);
|
||||
}
|
||||
}
|
||||
|
||||
public struct ValueEnumerator : IEnumerator<TValue>
|
||||
{
|
||||
private readonly HashMapHelper<TKey>* _data;
|
||||
private readonly TKey _key;
|
||||
private int _entryIndex;
|
||||
private bool _started;
|
||||
|
||||
public readonly TValue Current => UnsafeUtility.ReadArrayElement<TValue>(_data->Buffer, _entryIndex);
|
||||
readonly object IEnumerator.Current => Current;
|
||||
|
||||
internal ValueEnumerator(HashMapHelper<TKey>* data, in TKey key)
|
||||
{
|
||||
_data = data;
|
||||
_key = key;
|
||||
_entryIndex = -1;
|
||||
_started = false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (!_started)
|
||||
{
|
||||
_entryIndex = _data->Find(_key);
|
||||
_started = true;
|
||||
return _entryIndex != -1;
|
||||
}
|
||||
|
||||
if (_entryIndex == -1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_entryIndex = _data->FindNext(_entryIndex, _key);
|
||||
return _entryIndex != -1;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_entryIndex = -1;
|
||||
_started = false;
|
||||
}
|
||||
|
||||
public readonly void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private HashMapHelper<TKey> _helper;
|
||||
|
||||
public readonly int Count => _helper.Count;
|
||||
public readonly int Capacity => _helper.Capacity;
|
||||
public readonly bool IsCreated => _helper.IsCreated;
|
||||
|
||||
public Enumerator GetEnumerator()
|
||||
{
|
||||
return new((HashMapHelper<TKey>*)UnsafeUtility.AddressOf(ref this));
|
||||
}
|
||||
|
||||
IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
public UnsafeMultiHashMap()
|
||||
: this(0, Allocator.Invalid)
|
||||
{
|
||||
}
|
||||
|
||||
public UnsafeMultiHashMap(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 UnsafeMultiHashMap(int capacity, Allocator allocator, AllocationOption allocationOption = AllocationOption.None)
|
||||
: this(capacity, AllocationManager.GetAllocationHandle(allocator), allocationOption)
|
||||
{
|
||||
}
|
||||
|
||||
public void Add(in TKey key, TValue item)
|
||||
{
|
||||
var idx = _helper.Add(key);
|
||||
UnsafeUtility.WriteArrayElement(_helper.Buffer, idx, item);
|
||||
}
|
||||
|
||||
public bool Remove(in TKey key)
|
||||
{
|
||||
return _helper.RemoveAll(key) != 0;
|
||||
}
|
||||
|
||||
public bool TryGetFirstValue(in TKey key, out TValue item, out Iterator iterator)
|
||||
{
|
||||
var entryIndex = _helper.Find(key);
|
||||
if (entryIndex == -1)
|
||||
{
|
||||
item = default;
|
||||
iterator = new(default, -1);
|
||||
return false;
|
||||
}
|
||||
|
||||
item = UnsafeUtility.ReadArrayElement<TValue>(_helper.Buffer, entryIndex);
|
||||
iterator = new(key, entryIndex);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryGetNextValue(out TValue item, ref Iterator iterator)
|
||||
{
|
||||
if (iterator._entryIndex == -1)
|
||||
{
|
||||
item = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
var entryIndex = _helper.FindNext(iterator._entryIndex, iterator._key);
|
||||
if (entryIndex == -1)
|
||||
{
|
||||
item = default;
|
||||
iterator._entryIndex = -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
iterator._entryIndex = entryIndex;
|
||||
item = UnsafeUtility.ReadArrayElement<TValue>(_helper.Buffer, entryIndex);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryGetValue(in TKey key, out TValue item)
|
||||
{
|
||||
return _helper.TryGetValue(key, out item);
|
||||
}
|
||||
|
||||
public TValue GetValueOrDefault(in TKey key, TValue defaultValue = default)
|
||||
{
|
||||
if (_helper.TryGetValue<TValue>(key, out var value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public ValueEnumerable GetValuesForKey(in TKey key)
|
||||
{
|
||||
return new((HashMapHelper<TKey>*)UnsafeUtility.AddressOf(ref this), key);
|
||||
}
|
||||
|
||||
public int CountValuesForKey(in TKey key)
|
||||
{
|
||||
return _helper.CountValuesForKey(key);
|
||||
}
|
||||
|
||||
public bool ContainsKey(in TKey key)
|
||||
{
|
||||
return _helper.Find(key) != -1;
|
||||
}
|
||||
|
||||
public void TrimExcess()
|
||||
{
|
||||
_helper.TrimExcess();
|
||||
}
|
||||
|
||||
public void Resize(int newSize, AllocationOption option = AllocationOption.None)
|
||||
{
|
||||
_helper.Resize(newSize);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_helper.Clear();
|
||||
}
|
||||
|
||||
public UnsafeArray<TKey> GetKeyArray(Allocator allocator)
|
||||
{
|
||||
return _helper.GetKeyArray(allocator);
|
||||
}
|
||||
|
||||
public UnsafeArray<TValue> GetValueArray(Allocator allocator)
|
||||
{
|
||||
return _helper.GetValueArray<TValue>(allocator);
|
||||
}
|
||||
|
||||
public UnsafeArray<KeyValuePair<TKey, TValue>> GetKeyValueArrays(Allocator allocator)
|
||||
{
|
||||
return _helper.GetKeyValueArrays<TValue>(allocator);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly void* GetUnsafePtr()
|
||||
{
|
||||
return _helper.Buffer;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_helper.Dispose();
|
||||
}
|
||||
}
|
||||
213
Misaki.HighPerformance.LowLevel/Collections/UnsafeQueue.cs
Normal file
213
Misaki.HighPerformance.LowLevel/Collections/UnsafeQueue.cs
Normal file
@@ -0,0 +1,213 @@
|
||||
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()
|
||||
{
|
||||
return new((UnsafeQueue<T>*)UnsafeUtility.AddressOf(ref this));
|
||||
}
|
||||
|
||||
IEnumerator<T> IEnumerable<T>.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invalid constructor. Use <see cref="UnsafeQueue(int, Allocator, AllocationOption)"/> or <see cref="UnsafeQueue(int, 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;
|
||||
}
|
||||
}
|
||||
346
Misaki.HighPerformance.LowLevel/Collections/UnsafeSlotMap.cs
Normal file
346
Misaki.HighPerformance.LowLevel/Collections/UnsafeSlotMap.cs
Normal file
@@ -0,0 +1,346 @@
|
||||
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()
|
||||
{
|
||||
return new((UnsafeSlotMap<T>*)UnsafeUtility.AddressOf(ref this));
|
||||
}
|
||||
|
||||
IEnumerator<T> IEnumerable<T>.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invalid constructor. Use <see cref="UnsafeSlotMap(int, Allocator, AllocationOption)"/> or <see cref="UnsafeSlotMap(int, 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>
|
||||
/// <param name="item">When this method returns, contains the item that was removed if the removal was successful; otherwise, the default value for type <typeparamref name="T"/>.</param>
|
||||
/// <returns>true if the item was successfully removed; otherwise, false.</returns>
|
||||
public bool Remove(int slotIndex, int generation, out T item)
|
||||
{
|
||||
item = default;
|
||||
if (slotIndex < 0 || slotIndex >= _capacity)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ref var gen = ref _generations[slotIndex];
|
||||
if (gen != generation)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
item = _data[slotIndex];
|
||||
|
||||
gen++;
|
||||
_validBits.ClearBit(slotIndex);
|
||||
_freeSlots.Enqueue(slotIndex);
|
||||
|
||||
_count--;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
return Remove(slotIndex, generation, out _);
|
||||
}
|
||||
|
||||
/// <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;
|
||||
}
|
||||
}
|
||||
452
Misaki.HighPerformance.LowLevel/Collections/UnsafeSparseSet.cs
Normal file
452
Misaki.HighPerformance.LowLevel/Collections/UnsafeSparseSet.cs
Normal file
@@ -0,0 +1,452 @@
|
||||
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()
|
||||
{
|
||||
return new((UnsafeSparseSet<T>*)UnsafeUtility.AddressOf(ref this));
|
||||
}
|
||||
|
||||
IEnumerator<T> IEnumerable<T>.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return 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>
|
||||
/// <param name="item">When this method returns, contains the item that was removed if the removal was successful; otherwise, the default value for type <typeparamref name="T"/>.</param>
|
||||
/// <returns>True if the value was removed, false if the sparse index was not found.</returns>
|
||||
public bool Remove(int sparseIndex, int generation, out T item)
|
||||
{
|
||||
item = default;
|
||||
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
|
||||
|
||||
item = _dense[denseIndex];
|
||||
|
||||
_freeSparse.Push(sparseIndex);
|
||||
_count--;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <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;
|
||||
}
|
||||
}
|
||||
239
Misaki.HighPerformance.LowLevel/Collections/UnsafeStack.cs
Normal file
239
Misaki.HighPerformance.LowLevel/Collections/UnsafeStack.cs
Normal file
@@ -0,0 +1,239 @@
|
||||
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()
|
||||
{
|
||||
return new((UnsafeStack<T>*)UnsafeUtility.AddressOf(ref this));
|
||||
}
|
||||
|
||||
IEnumerator<T> IEnumerable<T>.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invalid constructor, use <see cref="UnsafeStack(int, Allocator, AllocationOption)"/> or <see cref="UnsafeStack(int, 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user