Skip to content

Instantly share code, notes, and snippets.

@prime31
Created March 17, 2021 23:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save prime31/f519f6ebb89f3c60261c360097f34b2c to your computer and use it in GitHub Desktop.
Save prime31/f519f6ebb89f3c60261c360097f34b2c to your computer and use it in GitHub Desktop.
Unmanaged Resources
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Nez.Unmanaged
{
unsafe public struct BlobAllocator : IDisposable
{
byte* m_RootPtr;
byte* m_Ptr;
long m_Size;
public BlobAllocator(int sizeHint)
{
var size = 1024 * 1024 * 256;
m_RootPtr = m_Ptr = (byte*)Marshal.AllocHGlobal(size);
m_Size = size;
}
public ref T ConstructRoot<T>() where T : struct
{
byte* returnPtr = m_Ptr;
m_Ptr += Marshal.SizeOf<T>();
return ref UnsafeUtility.AsRef<T>(returnPtr);
}
public BlobAssetReference<T> CreateBlobAssetReference<T>() where T : struct
{
Debug.Insist.IsTrue(12 == sizeof(BlobAssetHeader));
long dataSize = (m_Ptr - m_RootPtr);
Debug.Insist.IsTrue(dataSize <= 0x7FFFFFFF);
byte* buffer = (byte*)Marshal.AllocHGlobal(sizeof(BlobAssetHeader) + (int)dataSize);
Unsafe.CopyBlock(buffer + sizeof(BlobAssetHeader), m_RootPtr, (uint)dataSize);
BlobAssetHeader* header = (BlobAssetHeader*)buffer;
*header = new BlobAssetHeader();
header->Length = (int)dataSize;
BlobAssetReference<T> blobAssetReference;
header->ValidationPtr = blobAssetReference._data._pointer = buffer + sizeof(BlobAssetHeader);
return blobAssetReference;
}
public void Dispose()
{
Marshal.FreeHGlobal((IntPtr)m_RootPtr);
}
}
[StructLayout(LayoutKind.Explicit, Size = 12)]
unsafe struct BlobAssetHeader
{
[FieldOffset(0)] public void* ValidationPtr;
[FieldOffset(8)] public int Length;
public void Invalidate()
{
ValidationPtr = (void*)0xdddddddddddddddd;
}
}
internal unsafe struct BlobAssetReferenceData
{
public byte* _pointer;
internal BlobAssetHeader* Header => ((BlobAssetHeader*) _pointer) - 1;
}
public unsafe struct BlobAssetReference<T> : IEquatable<BlobAssetReference<T>> where T : struct
{
internal BlobAssetReferenceData _data;
public void* GetUnsafePtr() => _data._pointer;
public ref T Value => ref Unsafe.AsRef<T>(_data._pointer);
/// <summary>
/// returns the reinterpreted value of this BlobAssetReference.
/// </summary>
/// <typeparam name="TType"></typeparam>
/// <returns></returns>
public ref TType Reinterpreted<TType>() => ref Unsafe.AsRef<TType>(_data._pointer);
public static BlobAssetReference<T> Null => new BlobAssetReference<T>();
/// <summary>
/// creates an unmanaged pointer to an object of Type TType reinterpreted as Type T. Note that T should have the
/// same memory layout as TType and be of the same size or smaller.
/// </summary>
/// <param name="asset"></param>
/// <typeparam name="TType"></typeparam>
/// <returns></returns>
public static BlobAssetReference<T> Create<TType>(ref TType asset) where TType : unmanaged
{
var dataSize = Unsafe.SizeOf<TType>();
byte* buffer = (byte*)UnsafeUtility.Alloc(sizeof(BlobAssetHeader) + (int)dataSize, Allocator.Persistent);
BlobAssetHeader* header = (BlobAssetHeader*)buffer;
*header = new BlobAssetHeader();
header->Length = (int)dataSize;
buffer += sizeof(BlobAssetHeader);
TType* assetPointer = (TType*)buffer;
*assetPointer = asset;
BlobAssetReference<T> blobAssetReference;
header->ValidationPtr = blobAssetReference._data._pointer = buffer;
return blobAssetReference;
}
public void Release()
{
var header = _data.Header;
Marshal.FreeHGlobal((IntPtr)header);
_data._pointer = null;
}
public static bool operator ==(BlobAssetReference<T> lhs, BlobAssetReference<T> rhs)
{
return lhs._data._pointer == rhs._data._pointer;
}
public static bool operator !=(BlobAssetReference<T> lhs, BlobAssetReference<T> rhs)
{
return lhs._data._pointer != rhs._data._pointer;
}
public bool Equals(BlobAssetReference<T> other) => _data.Equals(other._data);
public override bool Equals(object obj) => this == (BlobAssetReference<T>)obj;
public override int GetHashCode() => _data.GetHashCode();
}
}
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Nez.Unmanaged.Collections
{
[StructLayout(LayoutKind.Explicit)]
internal unsafe struct BufferHeader
{
public const int kMinimumCapacity = 8;
[FieldOffset(0)] public byte* Pointer;
[FieldOffset(8)] public int Length;
[FieldOffset(12)] public int Capacity;
public static byte* GetElementPointer(BufferHeader* header)
{
if (header->Pointer != null)
return header->Pointer;
return (byte*)(header + 1);
}
public static void EnsureCapacity(BufferHeader* header, int count, int typeSize, bool retainOldData)
{
if (header->Capacity >= count)
return;
var newCapacity = Math.Max(Math.Max(2 * header->Capacity, count), kMinimumCapacity);
var newBlockSize = newCapacity * typeSize;
byte* oldData = GetElementPointer(header);
byte* newData = (byte*)UnsafeUtility.Alloc(newBlockSize, Allocator.Persistent);
if (retainOldData)
{
var oldBlockSize = header->Capacity * typeSize;
Unsafe.CopyBlock(newData, oldData, (uint)oldBlockSize);
}
// Note we're freeing the old buffer only if it was not using the internal capacity.
// Don't change this to 'oldData', because that would be a bug.
if (header->Pointer != null)
UnsafeUtility.Free((IntPtr)header->Pointer, Allocator.Persistent);
header->Pointer = newData;
header->Capacity = newCapacity;
}
public static void Assign(BufferHeader* header, byte* source, int count, int typeSize, int alignment)
{
EnsureCapacity(header, count, typeSize, false);
byte* elementPtr = GetElementPointer(header);
Unsafe.CopyBlock(elementPtr, source, (uint)(typeSize * count));
header->Length = count;
}
public static void Initialize(BufferHeader* header, int bufferCapacity)
{
header->Pointer = null;
header->Length = 0;
header->Capacity = bufferCapacity;
}
public static void Destroy(BufferHeader* header)
{
if (header->Pointer != null)
UnsafeUtility.Free((IntPtr)header->Pointer, Allocator.Persistent);
Initialize(header, 0);
}
}
}
namespace Nez.Unmanaged
{
public class DisposeSentinel
{
int _isCreated;
private DisposeSentinel()
{}
public static void Dispose(ref DisposeSentinel sentinel)
{
Clear(ref sentinel);
}
public static void Create(out DisposeSentinel sentinel, Allocator allocator)
{
if (NativeLeakDetection.Mode == NativeLeakDetectionMode.Enabled && allocator != Allocator.Temp)
{
sentinel = new DisposeSentinel
{
_isCreated = 1
};
}
else
{
sentinel = null;
}
}
~DisposeSentinel()
{
if (_isCreated != 0)
System.Console.WriteLine("A Native Collection has not been disposed, resulting in a memory leak.");
}
public static void Clear(ref DisposeSentinel sentinel)
{
if (sentinel != null)
{
sentinel._isCreated = 0;
sentinel = null;
}
}
}
}
using System;
using System.Runtime.CompilerServices;
namespace Nez.Unmanaged.Collections
{
public unsafe struct DynamicBuffer<T> : IDisposable where T : unmanaged
{
public int Length => _Buffer->Length;
public int Capacity => _Buffer->Capacity;
public bool IsCreated => _Buffer != null;
BufferHeader* _Buffer;
internal DynamicBuffer(BufferHeader* header) => _Buffer = header;
public T this [int index]
{
get => UnsafeUtility.ReadArrayElement<T>(BufferHeader.GetElementPointer(_Buffer), index);
set => UnsafeUtility.WriteArrayElement<T>(BufferHeader.GetElementPointer(_Buffer), index, value);
}
public void* GetUnsafePtr() => BufferHeader.GetElementPointer(_Buffer);
public void ResizeUninitialized(int length)
{
BufferHeader.EnsureCapacity(_Buffer, length, Unsafe.SizeOf<T>(), true);
_Buffer->Length = length;
}
public void Clear() => _Buffer->Length = 0;
public void Add(T elem)
{
var tmpLength = Length;
ResizeUninitialized(tmpLength + 1);
this[tmpLength] = elem;
}
public void Insert(int index, T elem)
{
var length = Length;
ResizeUninitialized(length + 1);
var elemSize = Unsafe.SizeOf<T>();
byte* basePtr = BufferHeader.GetElementPointer(_Buffer);
var dest = basePtr + (index + 1) * elemSize;
var src = basePtr + index * elemSize;
Unsafe.CopyBlock(dest, src, (uint)(elemSize * (length - index)));
this[index] = elem;
}
public void Dispose() => BufferHeader.Destroy(_Buffer);
}
}
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Nez.Unmanaged.Collections
{
[DebuggerDisplay("Length = {" + nameof(Length) + "}")]
[DebuggerTypeProxy(typeof(NativeArrayDebugView<>))]
public unsafe struct NativeArray<T> : IDisposable, IEnumerable<T>, IEnumerable where T : unmanaged
{
public struct Enumerator : IEnumerator<T>, IEnumerator, IDisposable
{
private NativeArray<T> _array;
private int _index;
object IEnumerator.Current => Current;
public T Current => _array[_index];
public Enumerator(ref NativeArray<T> array)
{
_array = array;
_index = -1;
}
public void Dispose()
{ }
public bool MoveNext()
{
_index++;
return _index < _array.Length;
}
public void Reset() => _index = -1;
}
public int Length => _length;
internal unsafe void* _buffer;
internal int _length;
internal Allocator _allocator;
internal DisposeSentinel _DisposeSentinel;
public unsafe T this[int index]
{
get => UnsafeUtility.ReadArrayElement<T>(_buffer, index);
set => UnsafeUtility.WriteArrayElement<T>(_buffer, index, value);
}
public unsafe NativeArray(int length, Allocator allocator = Allocator.Persistent, NativeArrayOptions options = NativeArrayOptions.ClearMemory)
{
var bufferSize = Marshal.SizeOf<T>() * length;
_buffer = (void*)UnsafeUtility.Alloc(bufferSize, allocator);
if (options == NativeArrayOptions.ClearMemory)
UnsafeUtility.Memset(_buffer, 0, bufferSize);
_length = length;
_allocator = allocator;
DisposeSentinel.Create(out _DisposeSentinel, allocator);
}
public unsafe void* GetUnsafePtr() => _buffer;
public void Dispose()
{
DisposeSentinel.Dispose(ref _DisposeSentinel);
UnsafeUtility.Free((IntPtr)_buffer);
_buffer = null;
_length = 0;
}
public T[] ToArray()
{
var array = new T[Length];
Copy(this, array, Length);
return array;
}
#region IEnumerable
public Enumerator GetEnumerator() => new Enumerator(ref this);
IEnumerator<T> IEnumerable<T>.GetEnumerator() => new Enumerator(ref this);
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
#endregion
#region Equals
public static bool operator ==(NativeArray<T> left, NativeArray<T> right) => left.Equals(right);
public static bool operator !=(NativeArray<T> left, NativeArray<T> right) => !left.Equals(right);
public bool Equals(NativeArray<T> other)
{
return _buffer == other._buffer && _length == other._length;
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
return false;
return obj is NativeArray<T> && Equals((NativeArray<T>)obj);
}
public override int GetHashCode()
{
unchecked
{
return ((int)_buffer * 397) ^ _length;
}
}
#endregion
#region Copying
public static void Copy(T[] src, NativeArray<T> dst)
{
if (src.Length != dst.Length)
throw new ArgumentException("source and destination length must be the same");
Copy(src, 0, dst, 0, src.Length);
}
public static void Copy(NativeArray<T> src, T[] dst)
{
if (src.Length != dst.Length)
throw new ArgumentException("source and destination length must be the same");
Copy(src, 0, dst, 0, src.Length);
}
public static void Copy(NativeArray<T> src, NativeArray<T> dst)
{
if (src.Length != dst.Length)
throw new ArgumentException("source and destination length must be the same");
Copy(src, 0, dst, 0, src.Length);
}
public static void Copy(NativeArray<T> src, T[] dst, int length)
{
Copy(src, 0, dst, 0, length);
}
public static void Copy(NativeArray<T> src, NativeArray<T> dst, int length)
{
Copy(src, 0, dst, 0, length);
}
public static void Copy(NativeArray<T> src, int srcIndex, NativeArray<T> dst, int dstIndex, int length)
{
var size = Unsafe.SizeOf<T>();
var destAddr = (byte*)dst._buffer + dstIndex * size;
var srcAddr = (byte*)src._buffer + srcIndex * size;
Unsafe.CopyBlock(destAddr, srcAddr, (uint)(length * size));
}
public static void Copy(T[] src, int srcIndex, NativeArray<T> dst, int dstIndex, int length)
{
var handle = GCHandle.Alloc(src, GCHandleType.Pinned);
var addr = handle.AddrOfPinnedObject();
var size = Unsafe.SizeOf<T>();
var destAddr = (byte*)dst._buffer + dstIndex * size;
var srcAddr = (byte*)addr + srcIndex * size;
Unsafe.CopyBlock(destAddr, srcAddr, (uint)(length * size));
handle.Free();
}
public static void Copy(NativeArray<T> src, int srcIndex, T[] dst, int dstIndex, int length)
{
var handle = GCHandle.Alloc(dst, GCHandleType.Pinned);
var addr = handle.AddrOfPinnedObject();
var size = Unsafe.SizeOf<T>();
var srcAddr = (byte*)src._buffer + srcIndex * size;
var dstAddr = (byte*)addr + dstIndex * size;
Unsafe.CopyBlock(dstAddr, srcAddr, (uint)(length * size));
handle.Free();
}
#endregion
internal sealed class NativeArrayDebugView<TU> where TU : unmanaged
{
private NativeArray<TU> _array;
public NativeArrayDebugView(NativeArray<TU> array) => _array = array;
public TU[] Items => _array.ToArray();
}
}
}
namespace Nez.Unmanaged
{
public enum NativeLeakDetectionMode
{
Enabled,
Disabled
}
public static class NativeLeakDetection
{
public static NativeLeakDetectionMode Mode = NativeLeakDetectionMode.Enabled;
}
}
using System;
using System.Runtime.CompilerServices;
namespace Nez.Unmanaged.Collections
{
// Jakson Dunstan: https://jacksondunstan.com/articles/4734
public unsafe struct NativeList<T> : IDisposable where T : unmanaged
{
NativeArray<T> _array;
int _count;
public int Count => _count;
public NativeList(int capacity, Allocator allocator = Allocator.Persistent, NativeArrayOptions options = NativeArrayOptions.ClearMemory)
{
_array = new NativeArray<T>(capacity, allocator, options);
_count = 0;
}
public unsafe T this[int index]
{
get => _array[index];
set => _array[index] = value;
}
public void Add(T value)
{
var insertIndex = _count;
if (insertIndex == _array.Length)
{
var newLength = insertIndex * 2;
var newArray = new NativeArray<T>(newLength);
NativeArray<T>.Copy(_array, newArray, _array.Length);
_array.Dispose();
_array = newArray;
}
_array[insertIndex] = value;
_count++;
}
public void RemoveAt(int index)
{
var numElementsToShift = _count - index - 1;
if (numElementsToShift > 0)
{
var elementSize = Unsafe.SizeOf<T>();
void* buffer = _array.GetUnsafePtr();
byte* source = (byte*)buffer + elementSize * (index + 1);
var shiftSize = numElementsToShift * elementSize;
Unsafe.CopyBlock((void*)(source - elementSize), (void*)source, (uint)shiftSize);
}
_count--;
}
public T[] ToArray()
{
var array = new T[_count];
NativeArray<T>.Copy(_array, array);
return array;
}
public void Dispose() => _array.Dispose();
}
}
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using Nez.Debug;
namespace Nez.Modules.Core
{
[DebuggerDisplay("string = {ToString()}")]
public struct NativeString64
{
public const int MaxLength = (64 - sizeof(int)) / sizeof(char);
public int Length;
unsafe fixed uint buffer[MaxLength/2];
public NativeString64(String source)
{
Length = 0;
unsafe
{
fixed(char *s = source)
CopyFrom(s, source.Length);
}
}
unsafe void CopyFrom(char* source, int length)
{
Insist.IsTrue(length <= MaxLength);
Length = length;
fixed (uint* pBuffer = buffer)
{
var dest = (char*)pBuffer;
var size = length * sizeof(char);
Buffer.MemoryCopy(source, dest, size, size);
}
}
public unsafe void CopyTo(char* dest, int maxLength)
{
Insist.IsTrue(Length <= maxLength);
fixed (uint* pBuffer = buffer)
{
var source = (char*)pBuffer;
var size = Length * sizeof(char);
Buffer.MemoryCopy(source, dest, size, size);
}
}
public static implicit operator NativeString64(string str) => new NativeString64(str);
public static implicit operator string(NativeString64 str) => str.ToString();
unsafe public override string ToString()
{
fixed (uint* pBuffer = buffer)
{
var c = (char*)pBuffer;
return new string(c, 0, Length);
}
}
}
}
namespace Nez.Unmanaged
{
// c/o Jackson Dunstan: https://jacksondunstan.com/articles/3770
unsafe public struct UnmanagedMemoryPool
{
/// <summary>
/// Unmanaged memory containing all the blocks
/// </summary>
public byte* Alloc;
/// <summary>
/// Pointer to the next free block
/// </summary>
public void* Free;
/// <summary>
/// Size of a single block. May include extra bytes for internal usage, such as a sentinel.
/// </summary>
public int BlockSize;
/// <summary>
/// Number of blocks
/// </summary>
public int NumBlocks;
}
}
using System;
using System.Diagnostics;
namespace Nez.Unmanaged
{
/// <summary>
/// // Allocate a pool of 1000 blocks, each 256 bytes long
/// var pool = UnmanagedMemoryPoolManager.AllocPool(256, 1000);
///
/// // Allocate a block from the pool
/// void* ptr = UnmanagedMemoryPoolManager.Alloc(&pool);
///
/// MyStruct thingTwo;
/// var thing = new MyStruct { Float = 3.14f, Byte = 2, Int = 42, Str = "what you fook" };
/// Unsafe.Copy(ptr, ref thing);
/// thingTwo = Unsafe.AsRef<MyStruct>(ptr);
/// thingTwo = *((MyStruct*)ptr);
///
/// // Free a block back into the pool
/// UnmanagedMemoryPoolManager.Free(&pool, ptr);
///
/// // Free all the blocks in the pool
/// UnmanagedMemoryPoolManager.FreeAll(&pool);
///
/// // Free the pool itself
/// UnmanagedMemoryPoolManager.FreePool(&pool);
/// </summary>
public unsafe class UnmanagedMemoryPoolManager
{
#if UNMANAGED_MEMORY_DEBUG
/// <summary>
/// Value added to the end of an <see cref="UnmanagedMemoryPool"/> block. Used to detect
/// out-of-bound memory writes.
/// </summary>
private const ulong SentinelValue = 0x8899AABBCCDDEEFF;
#endif
/// <summary>
/// Allocate a pool of memory. The pool is made up of a fixed number of equal-sized blocks.
/// Allocations from the pool return one of these blocks.
/// </summary>
/// <returns>The allocated pool</returns>
/// <param name="blockSize">Size of each block, in bytes</param>
/// <param name="numBlocks">The number of blocks in the pool</param>
public static UnmanagedMemoryPool AllocPool(int blockSize, int numBlocks)
{
#if UNMANAGED_MEMORY_DEBUG
// Add room for the sentinel
blockSize += sizeof(ulong);
#endif
var pool = new UnmanagedMemoryPool();
pool.Free = null;
pool.NumBlocks = numBlocks;
pool.BlockSize = blockSize;
// Allocate unmanaged memory large enough to fit all the blocks
pool.Alloc = (byte*)UnsafeUtility.Alloc(blockSize * numBlocks);
#if UNMANAGED_MEMORY_DEBUG
{
// Set the sentinel value at the end of each block
byte* pCur = pool.Alloc + blockSize - sizeof(ulong);
for (int i = 0; i < numBlocks; ++i)
{
*((ulong*)pCur) = SentinelValue;
pCur += blockSize;
}
}
#endif
// Reset the free list
FreeAll(&pool);
return pool;
}
/// <summary>
/// Allocate a block of memory from a pool
/// </summary>
/// <param name="pool">Pool to allocate from</param>
public static void* Alloc(UnmanagedMemoryPool* pool)
{
void* pRet = pool->Free;
// Make sure the sentinel is still intact
#if UNMANAGED_MEMORY_DEBUG
if (*((ulong*)(((byte*)pRet)+pool->BlockSize-sizeof(ulong))) != SentinelValue)
Assert(false, "UnmanagedMemoryExceptionType.SentinelOverwritten", pRet);
#endif
// Return the head of the free list and advance the free list pointer
pool->Free = *((byte**)pool->Free);
#if UNMANAGED_MEMORY_DEBUG
*((ulong*)(((byte*)pRet)+pool->BlockSize-sizeof(ulong))) = SentinelValue;
#endif
return pRet;
}
/// <summary>
/// Free a block from a pool
/// </summary>
/// <param name="pool">Pool the block is from</param>
/// <param name="ptr">Pointer to the block to free. If null, this is a no-op.</param>
public static void Free(UnmanagedMemoryPool* pool, void* ptr)
{
// Freeing a null pointer is a no-op, not an error
if (ptr != null)
{
// Pointer must be in the pool and on a block boundary
Debug.Insist.IsTrue(
ptr >= pool->Alloc
&& ptr < pool->Alloc + pool->BlockSize * pool->NumBlocks
&& (((uint)((byte*)ptr - pool->Alloc)) % pool->BlockSize) == 0,
"UnmanagedMemoryExceptionType.PointerDoesNotPointToBlockInPool"
);
// Make sure the sentinel is still intact for this block and the one before it
#if UNMANAGED_MEMORY_DEBUG
if (*((ulong*)(((byte*)ptr)+pool->BlockSize-sizeof(ulong))) != SentinelValue)
{
Assert(false, UnmanagedMemoryExceptionType.SentinelOverwritten, ptr );
}
if (ptr != pool->Alloc && *((ulong*)(((byte*)ptr)-sizeof(ulong))) != SentinelValue)
{
Assert(false, UnmanagedMemoryExceptionType.SentinelOverwritten, (((byte*)ptr)-sizeof(ulong)));
}
#endif
// Insert the block to free at the start of the free list
void** pHead = (void**)ptr;
*pHead = pool->Free;
pool->Free = pHead;
}
}
/// <summary>
/// Free all the blocks of a pool. This does not free the pool itself, but rather makes all of
/// its blocks available for allocation again.
/// </summary>
/// <param name="pool">Pool whose blocks should be freed</param>
public static void FreeAll(UnmanagedMemoryPool* pool)
{
// Point each block except the last one to the next block. Check their sentinels while we're at it.
void** pCur = (void**)pool->Alloc;
byte* pNext = pool->Alloc + pool->BlockSize;
#if UNMANAGED_MEMORY_DEBUG
byte* pSentinel = pool->Alloc + pool->BlockSize - sizeof(ulong);
#endif
for (int i = 0, count = pool->NumBlocks - 1; i < count; ++i)
{
#if UNMANAGED_MEMORY_DEBUG
if (*((ulong*)pSentinel) != SentinelValue)
{
Assert(false, UnmanagedMemoryExceptionType.SentinelOverwritten, pCur);
}
pSentinel += pool->BlockSize;
#endif
*pCur = pNext;
pCur = (void**)pNext;
pNext += pool->BlockSize;
}
// Check the last block's sentinel.
#if UNMANAGED_MEMORY_DEBUG
if (*((ulong*)pSentinel) != SentinelValue)
Assert(false, UnmanagedMemoryExceptionType.SentinelOverwritten, pCur);
#endif
// Point the last block to null
*pCur = default(void*);
// The first block is now the head of the free list
pool->Free = pool->Alloc;
}
/// <summary>
/// Free a pool and all of its blocks. Double-freeing a pool is a no-op.
/// </summary>
/// <param name="pool">Pool to free</param>
public static void FreePool(UnmanagedMemoryPool* pool)
{
// Free the unmanaged memory for all the blocks and set to null to allow double-Destroy()
UnsafeUtility.Free((IntPtr)pool->Alloc);
pool->Alloc = null;
pool->Free = null;
}
}
}
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Nez.Unmanaged
{
public enum Allocator
{
Temp,
TempJob,
Persistent
}
public enum NativeArrayOptions
{
UninitializedMemory,
ClearMemory
}
unsafe public static class UnsafeUtility
{
public unsafe static T ReadArrayElement<T>(void* source, int index) where T : unmanaged
{
return *(T*)((byte*)source + index * sizeof(T));
}
public unsafe static void WriteArrayElement<T>(void* destination, int index, T value) where T : unmanaged
{
*(T*)((byte*)destination + index * sizeof(T)) = value;
}
public static ref T AsRef<T>(void* ptr) where T : struct
{
return ref Unsafe.AsRef<T>(ptr);
}
public static ref T ArrayElementAsRef<T>(void* ptr, int index) where T : struct
{
return ref Unsafe.AsRef<T>((byte*)ptr + index * Marshal.SizeOf<T>());
}
// TODO: add Allocator enum with options for Temp or Permanent. Temp could use MemoryPool<T> and Perm Marshal
public unsafe static IntPtr Alloc(int size, Allocator allocator = Allocator.Persistent)
{
return Marshal.AllocHGlobal(size);
}
public unsafe static IntPtr Calloc(int size, Allocator allocator = Allocator.Persistent)
{
var ptr = Alloc(size, allocator);
Memset((void*)ptr, 0, size);
return ptr;
}
public unsafe static void Free(IntPtr ptr, Allocator allocator = Allocator.Persistent)
{
if (ptr != IntPtr.Zero)
Marshal.FreeHGlobal(ptr);
}
public static void Memset(void* ptr, byte value, int count)
{
Unsafe.InitBlock(ptr, 1, (uint)count);
// byte* pCur = (byte*)ptr;
// for (int i = 0; i < count; ++i)
// *pCur++ = value;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment