Refactor and enhance math, memory, and utilities
Refactored `sincos` usage in `quaternion` to use tuple-based returns for improved readability. Introduced a `random` struct with methods for generating random values of various types and dimensions, including ranges and directions. Added a `DynamicArray` class for dynamic resizing and manipulation of collections. Enhanced `SlotMap` with new methods for safe access and updates. Updated `uint` vector types with `NumericConvertable` attributes for better type interoperability. Removed the `MathUtilities` class and refactored `adj` and `adjInverse` methods for encapsulation. Improved memory management with `StackAllocator` and `UnsafeArray` enhancements. Added geometry utilities like `AABB`, `OBB`, `Plane`, and `SphereBounds` for 3D operations. Updated project configuration for versioning and NuGet packaging. Performed general code cleanup, improved validation, and aligned with modern C# practices.
This commit is contained in:
492
Misaki.HighPerformance.LowLevel/Utilities/HashMapHelper.cs
Normal file
492
Misaki.HighPerformance.LowLevel/Utilities/HashMapHelper.cs
Normal file
@@ -0,0 +1,492 @@
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using Misaki.HighPerformance.LowLevel.Contracts;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Misaki.HighPerformance.LowLevel.Utilities;
|
||||
|
||||
public unsafe struct HashMapHelper<TKey> : IDisposable
|
||||
where TKey : unmanaged, IEquatable<TKey>
|
||||
{
|
||||
internal unsafe struct Enumerator
|
||||
{
|
||||
public HashMapHelper<TKey>* buffer;
|
||||
public int index;
|
||||
public int bucketIndex;
|
||||
public int nextIndex;
|
||||
|
||||
public unsafe 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], UnsafeUtilities.ReadArrayElement<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 _sizeOfTValue;
|
||||
private readonly int _log2MinGrowth;
|
||||
|
||||
private AllocationHandle* _handle;
|
||||
|
||||
public const int MINIMAL_CAPACITY = 64;
|
||||
|
||||
public readonly byte* Buffer => _buffer;
|
||||
|
||||
public readonly int Count => _count;
|
||||
|
||||
public readonly int Capacity => _capacity;
|
||||
|
||||
public readonly bool IsCreated => _buffer != null;
|
||||
|
||||
public readonly bool IsEmpty => !IsCreated || _count == 0;
|
||||
|
||||
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, uint minGrowth, ref AllocationHandle handle, AllocationOption allocationOption)
|
||||
{
|
||||
if (capacity <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(capacity), "Capacity must be greater than zero.");
|
||||
}
|
||||
|
||||
if (sizeOfTValue <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(sizeOfTValue), "Size of TValue must be greater than zero.");
|
||||
}
|
||||
|
||||
_capacity = CalcCapacityCeilPow2(capacity);
|
||||
_bucketCapacity = _capacity * 2;
|
||||
|
||||
_sizeOfTValue = sizeOfTValue;
|
||||
_log2MinGrowth = BitOperations.Log2(minGrowth);
|
||||
|
||||
_handle = (AllocationHandle*)Unsafe.AsPointer(ref 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)]
|
||||
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(in TKey key)
|
||||
{
|
||||
var h = (uint)key.GetHashCode();
|
||||
return (int)(h & (uint)(_bucketCapacity - 1));
|
||||
}
|
||||
|
||||
[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 void AllocateBuffer(int totalSize, int keyOffset, int nextOffset, int bucketOffset, AllocationOption allocationOption)
|
||||
{
|
||||
var alignSize = sizeof(TKey) > sizeof(int) ? AlignOf<TKey>() : AlignOf<int>();
|
||||
|
||||
_buffer = (byte*)_handle->Alloc(_handle->Allocator, (uint)totalSize, (uint)alignSize, allocationOption);
|
||||
_keys = (TKey*)(_buffer + keyOffset);
|
||||
_next = (int*)(_buffer + nextOffset);
|
||||
_buckets = (int*)(_buffer + bucketOffset);
|
||||
}
|
||||
|
||||
internal 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;
|
||||
|
||||
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 = TryAdd(oldKeys[idx]);
|
||||
MemCpy(_buffer + _sizeOfTValue * newIdx, oldBuffer + _sizeOfTValue * idx, (nuint)_sizeOfTValue);
|
||||
}
|
||||
}
|
||||
|
||||
_handle->Free(_handle->Allocator, oldBuffer);
|
||||
}
|
||||
|
||||
internal void Resize(int newCapacity)
|
||||
{
|
||||
newCapacity = Math.Max(newCapacity, _count);
|
||||
var newBucketCapacity = CeilPow2(newCapacity * 2);
|
||||
|
||||
if (_capacity == newCapacity && _bucketCapacity == newBucketCapacity)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ResizeExact(newCapacity, newBucketCapacity);
|
||||
}
|
||||
|
||||
public void TrimExcess()
|
||||
{
|
||||
var capacity = CalcCapacityCeilPow2(_count);
|
||||
ResizeExact(capacity, capacity * 2);
|
||||
}
|
||||
|
||||
public int Find(in TKey key)
|
||||
{
|
||||
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 (!UnsafeUtilities.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)
|
||||
{
|
||||
var k = key;
|
||||
if (Find(in key) != -1)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Allocate an entry from the free list
|
||||
int idx;
|
||||
int* next;
|
||||
|
||||
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);
|
||||
|
||||
UnsafeUtilities.WriteArrayElement(_keys, idx, key);
|
||||
var bucket = GetBucket(key);
|
||||
|
||||
// Add the index to the hash-map
|
||||
next = _next;
|
||||
next[idx] = _buckets[bucket];
|
||||
_buckets[bucket] = idx;
|
||||
_count++;
|
||||
|
||||
return idx;
|
||||
}
|
||||
|
||||
public int TryRemove(in TKey key)
|
||||
{
|
||||
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 (UnsafeUtilities.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 bool TryGetValue<TValue>(in TKey key, out TValue item)
|
||||
where TValue : unmanaged
|
||||
{
|
||||
var idx = Find(key);
|
||||
|
||||
if (idx != -1)
|
||||
{
|
||||
item = UnsafeUtilities.ReadArrayElement<TValue>(_buffer, idx);
|
||||
return true;
|
||||
}
|
||||
|
||||
item = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool MoveNextSearch(ref int bucketIndex, ref int nextIndex, out int index)
|
||||
{
|
||||
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)
|
||||
{
|
||||
if (nextIndex != -1)
|
||||
{
|
||||
index = nextIndex;
|
||||
nextIndex = _next[nextIndex];
|
||||
return true;
|
||||
}
|
||||
|
||||
return MoveNextSearch(ref bucketIndex, ref nextIndex, out index);
|
||||
}
|
||||
|
||||
internal UnsafeArray<TKey> GetKeyArray(Allocator allocator)
|
||||
{
|
||||
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++] = UnsafeUtilities.ReadArrayElement<TKey>(_keys, bucket);
|
||||
bucket = _next[bucket];
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
internal UnsafeArray<TValue> GetValueArray<TValue>(Allocator allocator)
|
||||
where TValue : unmanaged
|
||||
{
|
||||
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++] = UnsafeUtilities.ReadArrayElement<TValue>(_buffer, bucket);
|
||||
bucket = _next[bucket];
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public UnsafeArray<KeyValuePair<TKey, TValue>> GetKeyValueArrays<TValue>(Allocator allocator)
|
||||
where TValue : unmanaged
|
||||
{
|
||||
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(UnsafeUtilities.ReadArrayElement<TKey>(_keys, bucket),
|
||||
UnsafeUtilities.ReadArrayElement<TValue>(_buffer, bucket));
|
||||
|
||||
count++;
|
||||
bucket = _next[bucket];
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
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)
|
||||
{
|
||||
_handle->Free(_handle->Allocator, _buffer);
|
||||
|
||||
_buffer = null;
|
||||
_keys = null;
|
||||
_next = null;
|
||||
_buckets = null;
|
||||
|
||||
_count = 0;
|
||||
_capacity = 0;
|
||||
_bucketCapacity = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,352 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.Intrinsics;
|
||||
|
||||
namespace Misaki.HighPerformance.LowLevel.Utilities;
|
||||
|
||||
public static unsafe partial class MemoryUtilities
|
||||
{
|
||||
[DoesNotReturn]
|
||||
private static void ThrowMustBeNullTerminatedString()
|
||||
{
|
||||
throw new ArgumentException("Arg_MustBeNullTerminatedString");
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static Vector128<byte> LoadVector128(ref byte start, nuint offset)
|
||||
=> Unsafe.ReadUnaligned<Vector128<byte>>(ref Unsafe.AddByteOffset(ref start, offset));
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static Vector256<byte> LoadVector256(ref byte start, nuint offset)
|
||||
=> Unsafe.ReadUnaligned<Vector256<byte>>(ref Unsafe.AddByteOffset(ref start, offset));
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static nuint GetByteVector128SpanLength(nuint offset, int length)
|
||||
=> (uint)((length - (int)offset) & ~(Vector128<byte>.Count - 1));
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static nuint GetByteVector256SpanLength(nuint offset, int length)
|
||||
=> (uint)((length - (int)offset) & ~(Vector256<byte>.Count - 1));
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static nuint GetByteVector512SpanLength(nuint offset, int length)
|
||||
=> (uint)((length - (int)offset) & ~(Vector512<byte>.Count - 1));
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static unsafe nuint UnalignedCountVector128(byte* searchSpace)
|
||||
{
|
||||
var unaligned = (nint)searchSpace & (Vector128<byte>.Count - 1);
|
||||
return (uint)((Vector128<byte>.Count - unaligned) & (Vector128<byte>.Count - 1));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Searches for the first occurrence of a null byte (0x00) in a given byte array.
|
||||
/// </summary>
|
||||
/// <param name="searchSpace">A pointer to the byte array where the search will be performed.</param>
|
||||
/// <returns>Returns the index of the first null byte found in the array..</returns>
|
||||
/// <exception cref="ArgumentException">Thrown if the byte array is not null-terminated.</exception>"
|
||||
public static unsafe int IndexOfNullByte(byte* searchSpace)
|
||||
{
|
||||
const int Length = int.MaxValue;
|
||||
const uint uValue = 0; // Use uint for comparisons to avoid unnecessary 8->32 extensions
|
||||
nuint offset = 0; // Use nuint for arithmetic to avoid unnecessary 64->32->64 truncations
|
||||
var lengthToExamine = (nuint)(uint)Length;
|
||||
|
||||
if (Vector128.IsHardwareAccelerated)
|
||||
{
|
||||
// Avx2 branch also operates on Sse2 sizes, so check is combined.
|
||||
lengthToExamine = UnalignedCountVector128(searchSpace);
|
||||
}
|
||||
|
||||
SequentialScan:
|
||||
while (lengthToExamine >= 8)
|
||||
{
|
||||
lengthToExamine -= 8;
|
||||
|
||||
if (uValue == searchSpace[offset])
|
||||
goto Found;
|
||||
if (uValue == searchSpace[offset + 1])
|
||||
goto Found1;
|
||||
if (uValue == searchSpace[offset + 2])
|
||||
goto Found2;
|
||||
if (uValue == searchSpace[offset + 3])
|
||||
goto Found3;
|
||||
if (uValue == searchSpace[offset + 4])
|
||||
goto Found4;
|
||||
if (uValue == searchSpace[offset + 5])
|
||||
goto Found5;
|
||||
if (uValue == searchSpace[offset + 6])
|
||||
goto Found6;
|
||||
if (uValue == searchSpace[offset + 7])
|
||||
goto Found7;
|
||||
|
||||
offset += 8;
|
||||
}
|
||||
|
||||
if (lengthToExamine >= 4)
|
||||
{
|
||||
lengthToExamine -= 4;
|
||||
|
||||
if (uValue == searchSpace[offset])
|
||||
goto Found;
|
||||
if (uValue == searchSpace[offset + 1])
|
||||
goto Found1;
|
||||
if (uValue == searchSpace[offset + 2])
|
||||
goto Found2;
|
||||
if (uValue == searchSpace[offset + 3])
|
||||
goto Found3;
|
||||
|
||||
offset += 4;
|
||||
}
|
||||
|
||||
while (lengthToExamine > 0)
|
||||
{
|
||||
lengthToExamine -= 1;
|
||||
|
||||
if (uValue == searchSpace[offset])
|
||||
goto Found;
|
||||
|
||||
offset += 1;
|
||||
}
|
||||
|
||||
// We get past SequentialScan only if IsHardwareAccelerated is true; and remain length is greater than Vector length.
|
||||
// However, we still have the redundant check to allow the JIT to see that the code is unreachable and eliminate it when the platform does not
|
||||
// have hardware accelerated. After processing Vector lengths we return to SequentialScan to finish any remaining.
|
||||
if (Vector512.IsHardwareAccelerated)
|
||||
{
|
||||
if (offset < Length)
|
||||
{
|
||||
if ((((uint)searchSpace + offset) & (nuint)(Vector256<byte>.Count - 1)) != 0)
|
||||
{
|
||||
// Not currently aligned to Vector256 (is aligned to Vector128); this can cause a problem for searches
|
||||
// with no upper bound e.g. String.strlen.
|
||||
// Start with a check on Vector128 to align to Vector256, before moving to processing Vector256.
|
||||
// This ensures we do not fault across memory pages while searching for an end of string.
|
||||
var search = Vector128.Load(searchSpace + offset);
|
||||
|
||||
// Same method as below
|
||||
var matches = Vector128.Equals(Vector128<byte>.Zero, search).ExtractMostSignificantBits();
|
||||
if (matches == 0)
|
||||
{
|
||||
// Zero flags set so no matches
|
||||
offset += (nuint)Vector128<byte>.Count;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Find bitflag offset of first match and add to current offset
|
||||
return (int)(offset + (uint)BitOperations.TrailingZeroCount(matches));
|
||||
}
|
||||
}
|
||||
|
||||
if ((((uint)searchSpace + offset) & (nuint)(Vector512<byte>.Count - 1)) != 0)
|
||||
{
|
||||
// Not currently aligned to Vector512 (is aligned to Vector256); this can cause a problem for searches
|
||||
// with no upper bound e.g. String.strlen.
|
||||
// Start with a check on Vector256 to align to Vector512, before moving to processing Vector256.
|
||||
// This ensures we do not fault across memory pages while searching for an end of string.
|
||||
var search = Vector256.Load(searchSpace + offset);
|
||||
|
||||
// Same method as below
|
||||
var matches = Vector256.Equals(Vector256<byte>.Zero, search).ExtractMostSignificantBits();
|
||||
if (matches == 0)
|
||||
{
|
||||
// Zero flags set so no matches
|
||||
offset += (nuint)Vector256<byte>.Count;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Find bitflag offset of first match and add to current offset
|
||||
return (int)(offset + (uint)BitOperations.TrailingZeroCount(matches));
|
||||
}
|
||||
}
|
||||
lengthToExamine = GetByteVector512SpanLength(offset, Length);
|
||||
if (lengthToExamine > offset)
|
||||
{
|
||||
do
|
||||
{
|
||||
var search = Vector512.Load(searchSpace + offset);
|
||||
var matches = Vector512.Equals(Vector512<byte>.Zero, search).ExtractMostSignificantBits();
|
||||
// Note that MoveMask has converted the equal vector elements into a set of bit flags,
|
||||
// So the bit position in 'matches' corresponds to the element offset.
|
||||
if (matches == 0)
|
||||
{
|
||||
// Zero flags set so no matches
|
||||
offset += (nuint)Vector512<byte>.Count;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find bitflag offset of first match and add to current offset
|
||||
return (int)(offset + (uint)BitOperations.TrailingZeroCount(matches));
|
||||
} while (lengthToExamine > offset);
|
||||
}
|
||||
|
||||
lengthToExamine = GetByteVector256SpanLength(offset, Length);
|
||||
if (lengthToExamine > offset)
|
||||
{
|
||||
var search = Vector256.Load(searchSpace + offset);
|
||||
|
||||
// Same method as above
|
||||
var matches = Vector256.Equals(Vector256<byte>.Zero, search).ExtractMostSignificantBits();
|
||||
if (matches == 0)
|
||||
{
|
||||
// Zero flags set so no matches
|
||||
offset += (nuint)Vector256<byte>.Count;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Find bitflag offset of first match and add to current offset
|
||||
return (int)(offset + (uint)BitOperations.TrailingZeroCount(matches));
|
||||
}
|
||||
}
|
||||
|
||||
lengthToExamine = GetByteVector128SpanLength(offset, Length);
|
||||
if (lengthToExamine > offset)
|
||||
{
|
||||
var search = Vector128.Load(searchSpace + offset);
|
||||
|
||||
// Same method as above
|
||||
var matches = Vector128.Equals(Vector128<byte>.Zero, search).ExtractMostSignificantBits();
|
||||
if (matches == 0)
|
||||
{
|
||||
// Zero flags set so no matches
|
||||
offset += (nuint)Vector128<byte>.Count;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Find bitflag offset of first match and add to current offset
|
||||
return (int)(offset + (uint)BitOperations.TrailingZeroCount(matches));
|
||||
}
|
||||
}
|
||||
|
||||
if (offset < Length)
|
||||
{
|
||||
lengthToExamine = (Length - offset);
|
||||
goto SequentialScan;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (Vector256.IsHardwareAccelerated)
|
||||
{
|
||||
if (offset < Length)
|
||||
{
|
||||
if ((((uint)searchSpace + offset) & (nuint)(Vector256<byte>.Count - 1)) != 0)
|
||||
{
|
||||
// Not currently aligned to Vector256 (is aligned to Vector128); this can cause a problem for searches
|
||||
// with no upper bound e.g. String.strlen.
|
||||
// Start with a check on Vector128 to align to Vector256, before moving to processing Vector256.
|
||||
// This ensures we do not fault across memory pages while searching for an end of string.
|
||||
var search = Vector128.Load(searchSpace + offset);
|
||||
|
||||
// Same method as below
|
||||
var matches = Vector128.Equals(Vector128<byte>.Zero, search).ExtractMostSignificantBits();
|
||||
if (matches == 0)
|
||||
{
|
||||
// Zero flags set so no matches
|
||||
offset += (nuint)Vector128<byte>.Count;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Find bitflag offset of first match and add to current offset
|
||||
return (int)(offset + (uint)BitOperations.TrailingZeroCount(matches));
|
||||
}
|
||||
}
|
||||
|
||||
lengthToExamine = GetByteVector256SpanLength(offset, Length);
|
||||
if (lengthToExamine > offset)
|
||||
{
|
||||
do
|
||||
{
|
||||
var search = Vector256.Load(searchSpace + offset);
|
||||
var matches = Vector256.Equals(Vector256<byte>.Zero, search).ExtractMostSignificantBits();
|
||||
// Note that MoveMask has converted the equal vector elements into a set of bit flags,
|
||||
// So the bit position in 'matches' corresponds to the element offset.
|
||||
if (matches == 0)
|
||||
{
|
||||
// Zero flags set so no matches
|
||||
offset += (nuint)Vector256<byte>.Count;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find bitflag offset of first match and add to current offset
|
||||
return (int)(offset + (uint)BitOperations.TrailingZeroCount(matches));
|
||||
} while (lengthToExamine > offset);
|
||||
}
|
||||
|
||||
lengthToExamine = GetByteVector128SpanLength(offset, Length);
|
||||
if (lengthToExamine > offset)
|
||||
{
|
||||
var search = Vector128.Load(searchSpace + offset);
|
||||
|
||||
// Same method as above
|
||||
var matches = Vector128.Equals(Vector128<byte>.Zero, search).ExtractMostSignificantBits();
|
||||
if (matches == 0)
|
||||
{
|
||||
// Zero flags set so no matches
|
||||
offset += (nuint)Vector128<byte>.Count;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Find bitflag offset of first match and add to current offset
|
||||
return (int)(offset + (uint)BitOperations.TrailingZeroCount(matches));
|
||||
}
|
||||
}
|
||||
|
||||
if (offset < Length)
|
||||
{
|
||||
lengthToExamine = (Length - offset);
|
||||
goto SequentialScan;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (Vector128.IsHardwareAccelerated)
|
||||
{
|
||||
if (offset < Length)
|
||||
{
|
||||
lengthToExamine = GetByteVector128SpanLength(offset, Length);
|
||||
|
||||
while (lengthToExamine > offset)
|
||||
{
|
||||
var search = Vector128.Load(searchSpace + offset);
|
||||
|
||||
// Same method as above
|
||||
var compareResult = Vector128.Equals(Vector128<byte>.Zero, search);
|
||||
if (compareResult == Vector128<byte>.Zero)
|
||||
{
|
||||
// Zero flags set so no matches
|
||||
offset += (nuint)Vector128<byte>.Count;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find bitflag offset of first match and add to current offset
|
||||
var matches = compareResult.ExtractMostSignificantBits();
|
||||
return (int)(offset + (uint)BitOperations.TrailingZeroCount(matches));
|
||||
}
|
||||
|
||||
if (offset < Length)
|
||||
{
|
||||
lengthToExamine = (Length - offset);
|
||||
goto SequentialScan;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ThrowMustBeNullTerminatedString();
|
||||
Found: // Workaround for https://github.com/dotnet/runtime/issues/8795
|
||||
return (int)offset;
|
||||
Found1:
|
||||
return (int)(offset + 1);
|
||||
Found2:
|
||||
return (int)(offset + 2);
|
||||
Found3:
|
||||
return (int)(offset + 3);
|
||||
Found4:
|
||||
return (int)(offset + 4);
|
||||
Found5:
|
||||
return (int)(offset + 5);
|
||||
Found6:
|
||||
return (int)(offset + 6);
|
||||
Found7:
|
||||
return (int)(offset + 7);
|
||||
}
|
||||
}
|
||||
181
Misaki.HighPerformance.LowLevel/Utilities/MemoryUtilities.cs
Normal file
181
Misaki.HighPerformance.LowLevel/Utilities/MemoryUtilities.cs
Normal file
@@ -0,0 +1,181 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Misaki.HighPerformance.LowLevel.Utilities;
|
||||
|
||||
public static unsafe partial class MemoryUtilities
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct AlignOfHelper<T>
|
||||
where T : struct
|
||||
{
|
||||
public byte dummy;
|
||||
public T data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allocates a block of memory of the specified size in bytes.
|
||||
/// </summary>
|
||||
/// <param name="size">Specifies the number of bytes to allocate in memory.</param>
|
||||
/// <returns>Returns a pointer to the allocated memory block.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void* Malloc(nuint size)
|
||||
{
|
||||
return NativeMemory.Alloc(size);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allocates a block of memory with a specified size and alignment.
|
||||
/// </summary>
|
||||
/// <param name="size">Specifies the total number of bytes to allocate for the memory block.</param>
|
||||
/// <param name="alignment">Defines the required alignment for the allocated memory address.</param>
|
||||
/// <returns>Returns a pointer to the allocated memory block.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void* AlignedAlloc(nuint size, nuint alignment)
|
||||
{
|
||||
return NativeMemory.AlignedAlloc(size, alignment);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resizes a previously allocated memory block to a new size. It returns a pointer to the reallocated memory.
|
||||
/// </summary>
|
||||
/// <param name="ptr">The pointer to the memory block that needs to be resized.</param>
|
||||
/// <param name="size">The new size for the memory block after resizing.</param>
|
||||
/// <returns>A pointer to the reallocated memory block, or null if the operation fails.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void* Realloc(void* ptr, nuint size)
|
||||
{
|
||||
return NativeMemory.Realloc(ptr, size);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reallocates memory to a specified size with a given alignment. It returns a pointer to the newly allocated
|
||||
/// memory.
|
||||
/// </summary>
|
||||
/// <param name="ptr">The pointer to the existing memory block that needs to be reallocated.</param>
|
||||
/// <param name="size">The new size for the memory allocation.</param>
|
||||
/// <param name="alignment">The required alignment for the new memory allocation.</param>
|
||||
/// <returns>A pointer to the reallocated memory block, or null if the allocation fails.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void* AlignedRealloc(void* ptr, nuint size, nuint alignment)
|
||||
{
|
||||
return NativeMemory.AlignedRealloc(ptr, size, alignment);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases the allocated memory pointed to by the given pointer. This helps in managing memory usage effectively.
|
||||
/// </summary>
|
||||
/// <param name="ptr">The pointer to the memory block that needs to be freed.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Free(void* ptr)
|
||||
{
|
||||
if (ptr == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
NativeMemory.Free(ptr);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases memory that was allocated with alignment requirements. It ensures proper deallocation of aligned memory
|
||||
/// blocks.
|
||||
/// </summary>
|
||||
/// <param name="ptr">The pointer to the memory block that needs to be freed.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void AlignedFree(void* ptr)
|
||||
{
|
||||
if (ptr == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
NativeMemory.AlignedFree(ptr);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears a block of memory by setting it to zero. It initializes a specified number of bytes at a given memory
|
||||
/// address.
|
||||
/// </summary>
|
||||
/// <param name="ptr">Specifies the memory address where the clearing operation will begin.</param>
|
||||
/// <param name="size">Indicates the number of bytes to be cleared in the memory block.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void MemClear(void* ptr, nuint size)
|
||||
{
|
||||
if (ptr == null || size == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
NativeMemory.Clear(ptr, size);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a block of memory to a specified byte value for a given size.
|
||||
/// </summary>
|
||||
/// <param name="ptr">The memory address where the byte value will be set.</param>
|
||||
/// <param name="size">The number of bytes to set to the specified value.</param>
|
||||
/// <param name="value">The byte value to which the memory block will be initialized.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void MemSet(void* ptr, byte value, nuint size)
|
||||
{
|
||||
if (ptr == null || size == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
NativeMemory.Fill(ptr, size, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies a block of memory from a source location to a destination location.
|
||||
/// </summary>
|
||||
/// <param name="source">Indicates the memory address from which data will be copied.</param>
|
||||
/// <param name="destination">Specifies the memory address where the copied data will be stored.</param>
|
||||
/// <param name="size">Defines the number of bytes to be copied from the source to the destination.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void MemCpy(void* source, void* destination, nuint size)
|
||||
{
|
||||
if (source == null || destination == null || size == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
NativeMemory.Copy(source, destination, size);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the size in bytes of a specified unmanaged type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Represents an unmanaged type for which the size is being calculated.</typeparam>
|
||||
/// <returns>Returns the size of the specified type as an unsigned integer.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static nuint SizeOf<T>()
|
||||
where T : unmanaged
|
||||
{
|
||||
return (nuint)sizeof(T);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the alignment size of a specified unmanaged type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Represents an unmanaged type for which the alignment size is being calculated.</typeparam>
|
||||
/// <returns>Returns the difference in size between a helper structure and the specified type.</returns>
|
||||
public static nuint AlignOf<T>()
|
||||
where T : unmanaged
|
||||
{
|
||||
return (nuint)(sizeof(AlignOfHelper<T>) - sizeof(T));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the alignment size of a specified struct.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Represents a value type that is used to determine the alignment size.</typeparam>
|
||||
/// <returns>Returns the size difference in bytes as an integer.</returns>
|
||||
public static int MarshalAlignOf<T>()
|
||||
where T : struct
|
||||
{
|
||||
return Marshal.SizeOf<AlignOfHelper<T>>() - Marshal.SizeOf<T>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,288 @@
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Misaki.HighPerformance.LowLevel.Utilities;
|
||||
|
||||
/// <summary>
|
||||
/// Provides extension methods for copying elements between unsafe collections and spans, converting collections to
|
||||
/// arrays or lists, and searching for values.
|
||||
/// </summary>
|
||||
public unsafe static class UnsafeCollectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Copies elements from a source UnsafeCollection to a destination Span, ensuring both have the same size.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Specifies the type of elements being copied, which must be unmanaged.</typeparam>
|
||||
/// <param name="source">Represents the source collection from which elements are copied.</param>
|
||||
/// <param name="destination">Represents the target span where elements are copied to.</param>
|
||||
/// <exception cref="ArgumentException">Thrown when the sizes of the source collection and destination span do not match.</exception>
|
||||
public static void CopyTo<T>(this IUnsafeCollection<T> source, Span<T> destination)
|
||||
where T : unmanaged
|
||||
{
|
||||
if (source.Count > destination.Length)
|
||||
{
|
||||
throw new ArgumentException("Source collection is larger than the destination span.");
|
||||
}
|
||||
|
||||
fixed (T* pDest = destination)
|
||||
{
|
||||
MemCpy(source.GetUnsafePtr(), pDest, (uint)(source.Count * sizeof(T)));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies a range of elements from a source collection to a destination span, ensuring both are adequately sized.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Specifies the type of elements being copied, which must be a value type.</typeparam>
|
||||
/// <param name="source">The collection from which elements are copied.</param>
|
||||
/// <param name="destination">The span where the elements will be copied to.</param>
|
||||
/// <param name="sourceIndex">The starting index in the source collection for the copy operation.</param>
|
||||
/// <param name="destinationIndex">The starting index in the destination span where the elements will be placed.</param>
|
||||
/// <param name="length">The number of elements to copy from the source to the destination.</param>
|
||||
/// <exception cref="ArgumentException">Thrown when the specified range exceeds the bounds of the source collection or destination span.</exception>
|
||||
public static void CopyTo<T>(this IUnsafeCollection<T> source, Span<T> destination, uint sourceIndex, uint destinationIndex, uint length)
|
||||
where T : unmanaged
|
||||
{
|
||||
if (sourceIndex + length > source.Count || destinationIndex + length > destination.Length)
|
||||
{
|
||||
throw new ArgumentException("Source collection or destination span is too small for the specified range.");
|
||||
}
|
||||
|
||||
fixed (T* pDest = destination)
|
||||
{
|
||||
MemCpy((byte*)source.GetUnsafePtr() + sourceIndex * sizeof(T), pDest + destinationIndex, (uint)(length * sizeof(T)));
|
||||
}
|
||||
}
|
||||
|
||||
/// <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="source">The untyped collection from which data is copied.</param>
|
||||
/// <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 static void CopyTo<T>(this IUnTypedCollection source, Span<T> destination)
|
||||
where T : unmanaged
|
||||
{
|
||||
var destSize = (uint)destination.Length * (uint)sizeof(T);
|
||||
if (source.Size > destSize)
|
||||
{
|
||||
throw new ArgumentException("Source collection is larger than the destination span.");
|
||||
}
|
||||
|
||||
fixed (T* pDest = destination)
|
||||
{
|
||||
MemCpy(source.GetUnsafePtr(), pDest, source.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="source">The untyped collection from which bytes are copied.</param>
|
||||
/// <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 static void CopyTo<T>(this IUnTypedCollection source, Span<T> destination, uint sourceOffset, uint destinationIndex, uint length)
|
||||
where T : unmanaged
|
||||
{
|
||||
var sizeOfElement = (uint)sizeof(T);
|
||||
if (sourceOffset + (length * sizeOfElement) > source.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((byte*)source.GetUnsafePtr() + sourceOffset, pDest + destinationIndex, length * sizeOfElement);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies elements from a source span to a destination unsafe collection, ensuring both have the same size.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Specifies the type of elements being copied, which must be unmanaged.</typeparam>
|
||||
/// <param name="destination">Represents the unsafe collection that will receive the copied elements.</param>
|
||||
/// <param name="source">Represents the span containing the elements to be copied to the unsafe collection.</param>
|
||||
/// <exception cref="ArgumentException">Thrown when the source span and destination collection have different sizes.</exception>
|
||||
public static void CopyFrom<T>(this IUnsafeCollection<T> destination, ReadOnlySpan<T> source)
|
||||
where T : unmanaged
|
||||
{
|
||||
if (destination.Count < source.Length)
|
||||
{
|
||||
throw new ArgumentException("Destination collection is smaller than the source span.");
|
||||
}
|
||||
|
||||
fixed (T* pSrc = source)
|
||||
{
|
||||
MemCpy(pSrc, destination.GetUnsafePtr(), (uint)(source.Length * sizeof(T)));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies a specified range of elements from a source span to a destination collection.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Represents the type of elements being copied, which must be unmanaged.</typeparam>
|
||||
/// <param name="destination">The collection where elements will be copied to.</param>
|
||||
/// <param name="source">The span containing the elements to be copied.</param>
|
||||
/// <param name="sourceIndex">The starting index in the source span from which to begin copying.</param>
|
||||
/// <param name="destinationIndex">The starting index in the destination collection where the elements will be placed.</param>
|
||||
/// <param name="length">The number of elements to copy from the source span to the destination collection.</param>
|
||||
/// <exception cref="ArgumentException">Thrown when the specified range exceeds the bounds of the source span or destination collection.</exception>
|
||||
public static void CopyFrom<T>(this IUnsafeCollection<T> destination, ReadOnlySpan<T> source, uint sourceIndex, uint destinationIndex, uint length)
|
||||
where T : unmanaged
|
||||
{
|
||||
if (sourceIndex + length > source.Length || destinationIndex + length > destination.Count)
|
||||
{
|
||||
throw new ArgumentException("Source span or destination collection is too small for the specified range.");
|
||||
}
|
||||
|
||||
fixed (T* pSrc = source)
|
||||
{
|
||||
MemCpy(pSrc + sourceIndex, (byte*)destination.GetUnsafePtr() + destinationIndex * sizeof(T), (uint)(length * sizeof(T)));
|
||||
}
|
||||
}
|
||||
|
||||
/// <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="destination">The untyped collection that will receive the copied data.</param>
|
||||
/// <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 static void CopyFrom<T>(this IUnTypedCollection destination, ReadOnlySpan<T> source)
|
||||
where T : unmanaged
|
||||
{
|
||||
var sourceSize = (uint)(source.Length * sizeof(T));
|
||||
|
||||
if (destination.Size < sourceSize)
|
||||
{
|
||||
throw new ArgumentException("Destination collection is smaller than the source span.");
|
||||
}
|
||||
|
||||
fixed (T* pSrc = source)
|
||||
{
|
||||
MemCpy(pSrc, destination.GetUnsafePtr(), 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="destination">The untyped collection where the data will be copied to.</param>
|
||||
/// <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 static void CopyFrom<T>(this IUnTypedCollection destination, 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) > destination.Size)
|
||||
{
|
||||
throw new ArgumentException("Source span or destination collection is too small for the specified range.");
|
||||
}
|
||||
|
||||
fixed (T* pSrc = source)
|
||||
{
|
||||
MemCpy(pSrc + sourceIndex, (byte*)destination.GetUnsafePtr() + destinationOffset, length * sizeOfElement);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an UnsafeCollection into a Span for efficient memory access.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Represents a type that can be stored in unmanaged memory.</typeparam>
|
||||
/// <param name="source">The UnsafeCollection instance to be converted into a Span.</param>
|
||||
/// <returns>A Span that provides a view over the elements of the UnsafeCollection.</returns>
|
||||
public static Span<T> AsSpan<T>(this IUnsafeCollection<T> source)
|
||||
where T : unmanaged
|
||||
{
|
||||
return new(source.GetUnsafePtr(), source.Count);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <typeparam name="T">The type of elements in the collection. Must be an unmanaged type.</typeparam>
|
||||
/// <param name="source">The unsafe collection from which to create the span. Must not be null.</param>
|
||||
/// <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{T}"/> representing the specified region of the collection.</returns>
|
||||
public static Span<T> AsSpan<T>(this IUnsafeCollection<T> source, int start, int length)
|
||||
where T : unmanaged
|
||||
{
|
||||
return new((T*)source.GetUnsafePtr() + start, length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an UnTypedCollection into a Span for efficient memory access.
|
||||
/// </summary>
|
||||
/// <param name="source">The UnTypedCollection instance to be converted into a Span.</param>
|
||||
/// <returns>A Span that provides a view over the elements of the UnsafeCollection.</returns>
|
||||
public static Span<byte> AsSpan(this IUnTypedCollection source)
|
||||
{
|
||||
return new(source.GetUnsafePtr(), (int)source.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="source">The unsafe collection from which to create the span. Must not be null.</param>
|
||||
/// <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 static Span<byte> AsSpan(this IUnTypedCollection source, int start, int length)
|
||||
{
|
||||
return new((byte*)source.GetUnsafePtr() + start, length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a managed array to an UnsafeArray by copying its elements to unmanaged memory.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of elements in the array, which must be unmanaged.</typeparam>
|
||||
/// <param name="source">The managed array to convert.</param>
|
||||
/// <param name="allocator">The allocator to use for memory allocation of the UnsafeArray.</param>
|
||||
/// <returns>A new UnsafeArray containing a copy of the source array elements.</returns>
|
||||
public static UnsafeArray<T> ToUnsafeArray<T>(this T[] source, Allocator allocator)
|
||||
where T : unmanaged
|
||||
{
|
||||
var array = new UnsafeArray<T>(source.Length, allocator);
|
||||
fixed (T* pSrc = source)
|
||||
{
|
||||
MemCpy(pSrc, array.GetUnsafePtr(), (uint)(source.Length * sizeof(T)));
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a managed List to an UnsafeList by copying its elements to unmanaged memory.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of elements in the list, which must be unmanaged.</typeparam>
|
||||
/// <param name="source">The managed List to convert.</param>
|
||||
/// <param name="allocator">The allocator to use for memory allocation of the UnsafeList.</param>
|
||||
/// <returns>A new UnsafeList containing a copy of the source list elements.</returns>
|
||||
public static UnsafeList<T> ToUnsafeList<T>(this List<T> source, Allocator allocator)
|
||||
where T : unmanaged
|
||||
{
|
||||
var list = new UnsafeList<T>(source.Count, allocator);
|
||||
fixed (T* pSrc = CollectionsMarshal.AsSpan(source))
|
||||
{
|
||||
MemCpy(pSrc, list.GetUnsafePtr(), (uint)(source.Count * sizeof(T)));
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
159
Misaki.HighPerformance.LowLevel/Utilities/UnsafeUtilities.cs
Normal file
159
Misaki.HighPerformance.LowLevel/Utilities/UnsafeUtilities.cs
Normal file
@@ -0,0 +1,159 @@
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Misaki.HighPerformance.LowLevel.Utilities;
|
||||
|
||||
public static unsafe class UnsafeUtilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts a pointer to a reference of a specified type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Specifies the type of the reference to be created from the pointer.</typeparam>
|
||||
/// <param name="ptr">Represents the memory address to be converted into a reference.</param>
|
||||
/// <returns>Returns a reference of the specified type pointing to the given memory address.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref T AsRef<T>(void* ptr)
|
||||
where T : unmanaged
|
||||
{
|
||||
return ref *(T*)ptr;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the address of a specified variable in memory.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Represents the type of the variable whose address is being retrieved.</typeparam>
|
||||
/// <param name="value">The variable whose memory address is to be obtained.</param>
|
||||
/// <returns>A pointer to the memory address of the specified variable.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void* AddressOf<T>(ref T value)
|
||||
where T : unmanaged
|
||||
{
|
||||
return Unsafe.AsPointer(ref value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an element from an unmanaged array at a specified index using a pointer.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Specifies the type of elements in the unmanaged array.</typeparam>
|
||||
/// <param name="ptr">Points to the start of the unmanaged array from which the element is read.</param>
|
||||
/// <param name="index">Indicates the position of the element to be accessed within the array.</param>
|
||||
/// <returns>Returns a pointer to the element located at the specified index.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static T* ReadArrayElementUnsafe<T>(void* ptr, int index)
|
||||
where T : unmanaged
|
||||
{
|
||||
return (T*)((byte*)ptr + index * sizeof(T));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an element from an unmanaged array at a specified index using a pointer.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Specifies the type of elements in the unmanaged array.</typeparam>
|
||||
/// <param name="ptr">Points to the start of the unmanaged array from which the element is read.</param>
|
||||
/// <param name="index">Indicates the position of the element to be accessed within the array.</param>
|
||||
/// <returns>Returns a pointer to the element located at the specified index.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static T* ReadArrayElementUnsafe<T>(void* ptr, uint index)
|
||||
where T : unmanaged
|
||||
{
|
||||
return (T*)((byte*)ptr + index * sizeof(T));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an element from an unmanaged array using a pointer and index, returning a reference to the element.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Specifies the type of the elements in the unmanaged array.</typeparam>
|
||||
/// <param name="ptr">Points to the start of the unmanaged array from which the element is read.</param>
|
||||
/// <param name="index">Indicates the position of the element to be accessed in the array.</param>
|
||||
/// <returns>A reference to the specified element in the unmanaged array.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref T ReadArrayElementRef<T>(void* ptr, int index)
|
||||
where T : unmanaged
|
||||
{
|
||||
return ref AsRef<T>(ReadArrayElementUnsafe<T>(ptr, index));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an element from an unmanaged array using a pointer and index, returning a reference to the element.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Specifies the type of the elements in the unmanaged array.</typeparam>
|
||||
/// <param name="ptr">Points to the start of the unmanaged array from which the element is read.</param>
|
||||
/// <param name="index">Indicates the position of the element to be accessed in the array.</param>
|
||||
/// <returns>A reference to the specified element in the unmanaged array.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref T ReadArrayElementRef<T>(void* ptr, uint index)
|
||||
where T : unmanaged
|
||||
{
|
||||
return ref AsRef<T>(ReadArrayElementUnsafe<T>(ptr, index));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an element from an array at a specified index using a pointer to the array.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Specifies the type of the elements in the array, which must be unmanaged.</typeparam>
|
||||
/// <param name="ptr">Points to the start of the array from which an element will be read.</param>
|
||||
/// <param name="index">Indicates the position of the element to be accessed within the array.</param>
|
||||
/// <returns>The element located at the specified index in the array.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static T ReadArrayElement<T>(void* ptr, int index)
|
||||
where T : unmanaged
|
||||
{
|
||||
return *ReadArrayElementUnsafe<T>(ptr, index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an element from an array at a specified index using a pointer to the array.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Specifies the type of the elements in the array, which must be unmanaged.</typeparam>
|
||||
/// <param name="ptr">Points to the start of the array from which an element will be read.</param>
|
||||
/// <param name="index">Indicates the position of the element to be accessed within the array.</param>
|
||||
/// <returns>The element located at the specified index in the array.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static T ReadArrayElement<T>(void* ptr, uint index)
|
||||
where T : unmanaged
|
||||
{
|
||||
return *ReadArrayElementUnsafe<T>(ptr, index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a value to a specified index of an unmanaged array using a pointer.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Specifies the type of the value being written to the array, which must be an unmanaged type.</typeparam>
|
||||
/// <param name="ptr">Points to the beginning of the unmanaged array where the value will be written.</param>
|
||||
/// <param name="index">Indicates the position in the array where the value should be stored.</param>
|
||||
/// <param name="value">Represents the value to be written to the specified index of the array.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void WriteArrayElement<T>(void* ptr, int index, T value)
|
||||
where T : unmanaged
|
||||
{
|
||||
*ReadArrayElementUnsafe<T>(ptr, index) = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a value to a specified index of an unmanaged array using a pointer.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Specifies the type of the value being written to the array, which must be an unmanaged type.</typeparam>
|
||||
/// <param name="ptr">Points to the beginning of the unmanaged array where the value will be written.</param>
|
||||
/// <param name="index">Indicates the position in the array where the value should be stored.</param>
|
||||
/// <param name="value">Represents the value to be written to the specified index of the array.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void WriteArrayElement<T>(void* ptr, uint index, T value)
|
||||
where T : unmanaged
|
||||
{
|
||||
*ReadArrayElementUnsafe<T>(ptr, index) = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an UnsafeArray of one unmanaged type to another unmanaged type without copying the elements.
|
||||
/// </summary>
|
||||
/// <typeparam name="TIn">Represents the type of elements in the input array.</typeparam>
|
||||
/// <typeparam name="TOut">Represents the type of elements in the output array.</typeparam>
|
||||
/// <param name="array">The input array containing elements of the specified input type.</param>
|
||||
/// <returns>An UnsafeArray containing elements of the specified output type.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static UnsafeArray<TOut> CastArray<TIn, TOut>(UnsafeArray<TIn> array)
|
||||
where TIn : unmanaged where TOut : unmanaged
|
||||
{
|
||||
return new UnsafeArray<TOut>((TOut*)array.GetUnsafePtr(), array.Count * sizeof(TIn) / sizeof(TOut));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user