You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
167 lines
4.6 KiB
167 lines
4.6 KiB
using System; |
|
using System.Runtime.CompilerServices; |
|
using System.Runtime.InteropServices; |
|
using System.Text; |
|
using System.Threading; |
|
using static flecs_hub.flecs.Runtime; |
|
|
|
namespace gaemstone.Utility; |
|
|
|
public interface IAllocator |
|
{ |
|
nint Allocate(int byteCount); |
|
void Free(nint pointer); |
|
} |
|
|
|
public unsafe static class AllocatorExtensions |
|
{ |
|
public static Span<T> Allocate<T>(this IAllocator allocator, int count) where T : unmanaged |
|
=> new((void*)allocator.Allocate(sizeof(T) * count), count); |
|
public static void Free<T>(this IAllocator allocator, Span<T> span) where T : unmanaged |
|
=> allocator.Free((nint)Unsafe.AsPointer(ref span[0])); |
|
|
|
public static Span<T> AllocateCopy<T>(this IAllocator allocator, ReadOnlySpan<T> orig) where T : unmanaged |
|
{ var copy = allocator.Allocate<T>(orig.Length); orig.CopyTo(copy); return copy; } |
|
|
|
public static ref T Allocate<T>(this IAllocator allocator) where T : unmanaged |
|
=> ref Unsafe.AsRef<T>((void*)allocator.Allocate(sizeof(T))); |
|
public static void Free<T>(this IAllocator allocator, ref T value) where T : unmanaged |
|
=> allocator.Free((nint)Unsafe.AsPointer(ref value)); |
|
|
|
public static CString AllocateCString(this IAllocator allocator, string? value) |
|
{ |
|
if (value == null) return default; |
|
var bytes = Encoding.UTF8.GetByteCount(value); |
|
var span = allocator.Allocate<byte>(bytes + 1); |
|
Encoding.UTF8.GetBytes(value, span); |
|
span[^1] = 0; // In case the allocated span is not cleared. |
|
return new((nint)Unsafe.AsPointer(ref span[0])); |
|
} |
|
|
|
public static CString AllocateCString(this IAllocator allocator, ReadOnlySpan<byte> utf8) |
|
{ |
|
var copy = allocator.Allocate<byte>(utf8.Length + 1); |
|
utf8.CopyTo(copy); |
|
copy[^1] = 0; // In case the allocated span is not cleared. |
|
return new((nint)Unsafe.AsPointer(ref copy[0])); |
|
} |
|
} |
|
|
|
public static class TempAllocator |
|
{ |
|
public const int Capacity = 1024 * 1024; // 1 MB |
|
private static readonly ThreadLocal<ArenaAllocator> _allocator |
|
= new(() => new(Capacity)); |
|
|
|
public static ResetOnDispose Use() |
|
{ |
|
var allocator = _allocator.Value!; |
|
return new(allocator, allocator.Used); |
|
} |
|
|
|
public sealed class ResetOnDispose |
|
: IAllocator |
|
, IDisposable |
|
{ |
|
private readonly ArenaAllocator _allocator; |
|
private readonly int _start; |
|
|
|
public ResetOnDispose(ArenaAllocator allocator, int start) |
|
{ _allocator = allocator; _start = start; } |
|
|
|
// IAllocator implementation |
|
public nint Allocate(int byteCount) => _allocator.Allocate(byteCount); |
|
public void Free(nint pointer) { /* Do nothing. */ } |
|
|
|
// IDisposable implementation |
|
public void Dispose() => _allocator.Reset(_start); |
|
} |
|
} |
|
|
|
public class GlobalHeapAllocator |
|
: IAllocator |
|
{ |
|
public nint Allocate(int byteCount) |
|
=> Marshal.AllocHGlobal(byteCount); |
|
public void Free(nint pointer) |
|
=> Marshal.FreeHGlobal(pointer); |
|
} |
|
|
|
public sealed class ArenaAllocator |
|
: IAllocator |
|
, IDisposable |
|
{ |
|
private nint _buffer; |
|
public int Capacity { get; private set; } |
|
public int Used { get; private set; } = 0; |
|
|
|
public ArenaAllocator(int capacity) |
|
{ |
|
if (capacity <= 0) throw new ArgumentOutOfRangeException(nameof(capacity)); |
|
_buffer = Marshal.AllocHGlobal(capacity); |
|
Capacity = capacity; |
|
} |
|
|
|
public void Dispose() |
|
{ |
|
Marshal.FreeHGlobal(_buffer); |
|
_buffer = default; |
|
Capacity = 0; |
|
} |
|
|
|
public nint Allocate(int byteCount) |
|
{ |
|
if (_buffer == default) throw new ObjectDisposedException(nameof(ArenaAllocator)); |
|
if (Used + byteCount > Capacity) throw new InvalidOperationException( |
|
$"Cannot allocate more than {Capacity} bytes with this {nameof(ArenaAllocator)}"); |
|
var ptr = _buffer + Used; |
|
Used += byteCount; |
|
return ptr; |
|
} |
|
|
|
public void Free(nint pointer) |
|
{ /* Do nothing. */ } |
|
|
|
public unsafe void Reset(int start = 0) |
|
{ |
|
if (start > Used) throw new ArgumentOutOfRangeException(nameof(start)); |
|
new Span<byte>((void*)(_buffer + start), Used - start).Clear(); |
|
Used = start; |
|
} |
|
} |
|
|
|
public sealed class RingAllocator |
|
: IAllocator |
|
, IDisposable |
|
{ |
|
private nint _buffer; |
|
private int _current = 0; |
|
public int Capacity { get; private set; } |
|
|
|
public RingAllocator(int capacity) |
|
{ |
|
if (capacity <= 0) throw new ArgumentOutOfRangeException(nameof(capacity)); |
|
_buffer = Marshal.AllocHGlobal(capacity); |
|
Capacity = capacity; |
|
} |
|
|
|
public void Dispose() |
|
{ |
|
Marshal.FreeHGlobal(_buffer); |
|
_buffer = default; |
|
Capacity = 0; |
|
} |
|
|
|
public nint Allocate(int byteCount) |
|
{ |
|
if (_buffer == default) throw new ObjectDisposedException(nameof(RingAllocator)); |
|
if (byteCount > Capacity) throw new ArgumentOutOfRangeException(nameof(byteCount)); |
|
if (_current + byteCount > Capacity) _current = 0; |
|
var ptr = _buffer + _current; |
|
_current += byteCount; |
|
return ptr; |
|
} |
|
|
|
public void Free(nint pointer) |
|
{ /* IGNORE */ } |
|
}
|
|
|