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

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 */ }
}