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 Allocate(this IAllocator allocator, int count) where T : unmanaged => new((void*)allocator.Allocate(sizeof(T) * count), count); public static void Free(this IAllocator allocator, Span span) where T : unmanaged => allocator.Free((nint)Unsafe.AsPointer(ref span[0])); public static Span AllocateCopy(this IAllocator allocator, ReadOnlySpan orig) where T : unmanaged { var copy = allocator.Allocate(orig.Length); orig.CopyTo(copy); return copy; } public static ref T Allocate(this IAllocator allocator) where T : unmanaged => ref Unsafe.AsRef((void*)allocator.Allocate(sizeof(T))); public static void Free(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(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 utf8) { var copy = allocator.Allocate(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 _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((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 */ } }