Created
March 17, 2021 23:07
-
-
Save prime31/f519f6ebb89f3c60261c360097f34b2c to your computer and use it in GitHub Desktop.
Unmanaged Resources
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace Nez.Unmanaged | |
{ | |
public enum NativeLeakDetectionMode | |
{ | |
Enabled, | |
Disabled | |
} | |
public static class NativeLeakDetection | |
{ | |
public static NativeLeakDetectionMode Mode = NativeLeakDetectionMode.Enabled; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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