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