parent
46e171940e
commit
c6613679cc
22 changed files with 331 additions and 38 deletions
@ -1,23 +0,0 @@ |
||||
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
||||
<PropertyGroup> |
||||
<TargetFramework>net7.0</TargetFramework> |
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> |
||||
<ImplicitUsings>disable</ImplicitUsings> |
||||
<Nullable>enable</Nullable> |
||||
</PropertyGroup> |
||||
|
||||
<PropertyGroup> |
||||
<EnableDefaultItems>false</EnableDefaultItems> |
||||
</PropertyGroup> |
||||
|
||||
<ItemGroup> |
||||
<Compile Include="src/gaemstone.ECS/**/*.cs" /> |
||||
<Compile Include="src/gaemstone.Utility/**/*.cs" /> |
||||
</ItemGroup> |
||||
|
||||
<ItemGroup> |
||||
<ProjectReference Include="src/flecs-cs/src/cs/production/Flecs/Flecs.csproj" /> |
||||
</ItemGroup> |
||||
|
||||
</Project> |
@ -0,0 +1,171 @@ |
||||
using System; |
||||
using System.Runtime.CompilerServices; |
||||
using System.Runtime.InteropServices; |
||||
using System.Text; |
||||
using System.Threading; |
||||
using static flecs_hub.flecs.Runtime; |
||||
|
||||
namespace gaemstone.ECS.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; } |
||||
|
||||
// TODO: Print warning in finalizer if Dispose wasn't called manually. |
||||
|
||||
// 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 static GlobalHeapAllocator Instance { get; } = new(); |
||||
|
||||
public nint Allocate(int byteCount) |
||||
=> Marshal.AllocHGlobal(byteCount); |
||||
public void Free(nint pointer) |
||||
=> Marshal.FreeHGlobal(pointer); |
||||
} |
||||
|
||||
public 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 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) |
||||
{ /* Do nothing. */ } |
||||
} |
@ -0,0 +1,34 @@ |
||||
using System; |
||||
using System.Runtime.InteropServices; |
||||
using static flecs_hub.flecs; |
||||
using static flecs_hub.flecs.Runtime; |
||||
|
||||
namespace gaemstone.ECS.Utility; |
||||
|
||||
public unsafe static class CStringExtensions |
||||
{ |
||||
public static CString Empty { get; } = (CString)""; |
||||
public static CString ETX { get; } = (CString)"\x3"; // FIXME: Temporary, until flecs supports Empty. |
||||
|
||||
public static unsafe byte[]? FlecsToBytes(this CString str) |
||||
{ |
||||
if (str.IsNull) return null; |
||||
var pointer = (byte*)(nint)str; |
||||
// Find length of the string by locating the NUL character. |
||||
var length = 0; while (true) if (pointer[length++] == 0) break; |
||||
// Create span over the region, NUL included, copy it into an array. |
||||
return new Span<byte>(pointer, length).ToArray(); |
||||
} |
||||
|
||||
public static byte[]? FlecsToBytesAndFree(this CString str) |
||||
{ var result = str.FlecsToBytes(); str.FlecsFree(); return result; } |
||||
|
||||
public static string? FlecsToString(this CString str) |
||||
=> Marshal.PtrToStringUTF8(str); |
||||
|
||||
public static string? FlecsToStringAndFree(this CString str) |
||||
{ var result = str.FlecsToString(); str.FlecsFree(); return result; } |
||||
|
||||
public static void FlecsFree(this CString str) |
||||
{ if (!str.IsNull) ecs_os_get_api().free_.Data.Pointer((void*)(nint)str); } |
||||
} |
@ -0,0 +1,30 @@ |
||||
using System.Collections.Generic; |
||||
|
||||
namespace gaemstone.ECS.Utility; |
||||
|
||||
public static class CallbackContextHelper |
||||
{ |
||||
private static readonly Dictionary<nint, object> _contexts = new(); |
||||
private static nint _counter = 0; |
||||
|
||||
public static nint Create<T>(T context) where T : notnull |
||||
{ |
||||
lock (_contexts) { |
||||
var id = _counter++; |
||||
_contexts.Add(id, context); |
||||
return id; |
||||
} |
||||
} |
||||
|
||||
public static T Get<T>(nint id) |
||||
{ |
||||
lock (_contexts) |
||||
return (T)_contexts[id]; |
||||
} |
||||
|
||||
public static void Free(nint id) |
||||
{ |
||||
lock (_contexts) |
||||
_contexts.Remove(id); |
||||
} |
||||
} |
@ -0,0 +1,26 @@ |
||||
using System; |
||||
using System.Diagnostics; |
||||
using System.Runtime.InteropServices; |
||||
|
||||
namespace gaemstone.ECS.Utility; |
||||
|
||||
public class FlecsException |
||||
: Exception |
||||
{ |
||||
public FlecsException() : base() { } |
||||
public FlecsException(string message) : base(message) { } |
||||
} |
||||
|
||||
public class FlecsAbortException |
||||
: FlecsException |
||||
{ |
||||
private readonly string _stackTrace = new StackTrace(2, true).ToString(); |
||||
public override string? StackTrace => _stackTrace; |
||||
|
||||
private FlecsAbortException() |
||||
: base("Abort was called by flecs") { } |
||||
|
||||
[UnmanagedCallersOnly] |
||||
internal static void Callback() |
||||
=> throw new FlecsAbortException(); |
||||
} |
@ -0,0 +1,41 @@ |
||||
using System; |
||||
|
||||
namespace gaemstone.ECS.Utility; |
||||
|
||||
public static class SpanExtensions |
||||
{ |
||||
public static T? GetOrNull<T>(this Span<T> span, int index) |
||||
where T : struct => GetOrNull((ReadOnlySpan<T>)span, index); |
||||
public static T? GetOrNull<T>(this ReadOnlySpan<T> span, int index) |
||||
where T : struct => (index >= 0 && index < span.Length) ? span[index] : null; |
||||
|
||||
|
||||
public static ReadOnlySpanSplitEnumerator<T> Split<T>(this ReadOnlySpan<T> span, T splitOn) |
||||
where T : IEquatable<T> => new(span, splitOn); |
||||
|
||||
public ref struct ReadOnlySpanSplitEnumerator<T> |
||||
where T : IEquatable<T> |
||||
{ |
||||
private readonly ReadOnlySpan<T> _span; |
||||
private readonly T _splitOn; |
||||
private int _index = -1; |
||||
private int _end = -1; |
||||
|
||||
public ReadOnlySpanSplitEnumerator(ReadOnlySpan<T> span, T splitOn) |
||||
{ _span = span; _splitOn = splitOn; } |
||||
|
||||
public ReadOnlySpanSplitEnumerator<T> GetEnumerator() => this; |
||||
|
||||
public ReadOnlySpan<T> Current |
||||
=> (_end >= 0) && (_end <= _span.Length) |
||||
? _span[_index.._end] : throw new InvalidOperationException(); |
||||
|
||||
public bool MoveNext() |
||||
{ |
||||
if (_end == _span.Length) return false; |
||||
_end++; _index = _end; |
||||
while (_end < _span.Length && !_span[_end].Equals(_splitOn)) _end++; |
||||
return true; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,14 @@ |
||||
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
||||
<PropertyGroup> |
||||
<TargetFramework>net7.0</TargetFramework> |
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> |
||||
<ImplicitUsings>disable</ImplicitUsings> |
||||
<Nullable>enable</Nullable> |
||||
</PropertyGroup> |
||||
|
||||
<ItemGroup> |
||||
<ProjectReference Include="../flecs-cs/src/cs/production/Flecs/Flecs.csproj" /> |
||||
</ItemGroup> |
||||
|
||||
</Project> |
Loading…
Reference in new issue