From c6613679cc1a98d71ba5373b9a3ce013876ea8cb Mon Sep 17 00:00:00 2001 From: copygirl Date: Sun, 8 Jan 2023 15:00:24 +0100 Subject: [PATCH] Move .csproj and Utility namespace --- README.md | 2 +- gaemstone.ECS.csproj | 23 --- src/gaemstone.ECS/EntityBuilder.cs | 2 +- src/gaemstone.ECS/EntityPath.cs | 2 +- src/gaemstone.ECS/EntityRef.cs | 2 +- src/gaemstone.ECS/EntityType.cs | 2 +- src/gaemstone.ECS/Filter.cs | 2 +- src/gaemstone.ECS/IdRef.cs | 2 +- src/gaemstone.ECS/Iterator.cs | 2 +- src/gaemstone.ECS/Observer.cs | 2 +- src/gaemstone.ECS/Query.cs | 2 +- src/gaemstone.ECS/Rule.cs | 2 +- src/gaemstone.ECS/System.cs | 2 +- src/gaemstone.ECS/Term.cs | 2 +- src/gaemstone.ECS/Utility/Allocators.cs | 171 ++++++++++++++++++ .../Utility/CStringExtensions.cs | 34 ++++ .../Utility/CallbackContextHelper.cs | 30 +++ src/gaemstone.ECS/Utility/FlecsException.cs | 26 +++ src/gaemstone.ECS/Utility/SpanExtensions.cs | 41 +++++ src/gaemstone.ECS/World+Lookup.cs | 2 +- src/gaemstone.ECS/World.cs | 2 +- src/gaemstone.ECS/gaemstone.ECS.csproj | 14 ++ 22 files changed, 331 insertions(+), 38 deletions(-) delete mode 100644 gaemstone.ECS.csproj create mode 100644 src/gaemstone.ECS/Utility/Allocators.cs create mode 100644 src/gaemstone.ECS/Utility/CStringExtensions.cs create mode 100644 src/gaemstone.ECS/Utility/CallbackContextHelper.cs create mode 100644 src/gaemstone.ECS/Utility/FlecsException.cs create mode 100644 src/gaemstone.ECS/Utility/SpanExtensions.cs create mode 100644 src/gaemstone.ECS/gaemstone.ECS.csproj diff --git a/README.md b/README.md index 02fe46e..0748b2f 100644 --- a/README.md +++ b/README.md @@ -104,7 +104,7 @@ git clone --recurse-submodules https://git.mcft.net/copygirl/gaemstone.ECS.git git submodule add https://git.mcft.net/copygirl/gaemstone.ECS.git # To add a reference to this library to your .NET project: -dotnet add reference gaemstone.ECS/gaemstone.ECS.csproj +dotnet add reference gaemstone.ECS/src/gaemstone.ECS/gaemstone.ECS.csproj # To generate flecs-cs' bindings: ./gaemstone.ECS/src/flecs-cs/library.sh diff --git a/gaemstone.ECS.csproj b/gaemstone.ECS.csproj deleted file mode 100644 index d9995da..0000000 --- a/gaemstone.ECS.csproj +++ /dev/null @@ -1,23 +0,0 @@ - - - - net7.0 - true - disable - enable - - - - false - - - - - - - - - - - - diff --git a/src/gaemstone.ECS/EntityBuilder.cs b/src/gaemstone.ECS/EntityBuilder.cs index 5890379..7cd8e50 100644 --- a/src/gaemstone.ECS/EntityBuilder.cs +++ b/src/gaemstone.ECS/EntityBuilder.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -using gaemstone.Utility; +using gaemstone.ECS.Utility; using static flecs_hub.flecs; namespace gaemstone.ECS; diff --git a/src/gaemstone.ECS/EntityPath.cs b/src/gaemstone.ECS/EntityPath.cs index bcbd57d..0940fee 100644 --- a/src/gaemstone.ECS/EntityPath.cs +++ b/src/gaemstone.ECS/EntityPath.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text; -using gaemstone.Utility; +using gaemstone.ECS.Utility; using static flecs_hub.flecs; namespace gaemstone.ECS; diff --git a/src/gaemstone.ECS/EntityRef.cs b/src/gaemstone.ECS/EntityRef.cs index 9dc0e48..01d4152 100644 --- a/src/gaemstone.ECS/EntityRef.cs +++ b/src/gaemstone.ECS/EntityRef.cs @@ -2,7 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; -using gaemstone.Utility; +using gaemstone.ECS.Utility; using static flecs_hub.flecs; namespace gaemstone.ECS; diff --git a/src/gaemstone.ECS/EntityType.cs b/src/gaemstone.ECS/EntityType.cs index fb34df3..39e490a 100644 --- a/src/gaemstone.ECS/EntityType.cs +++ b/src/gaemstone.ECS/EntityType.cs @@ -1,6 +1,6 @@ using System.Collections; using System.Collections.Generic; -using gaemstone.Utility; +using gaemstone.ECS.Utility; using static flecs_hub.flecs; namespace gaemstone.ECS; diff --git a/src/gaemstone.ECS/Filter.cs b/src/gaemstone.ECS/Filter.cs index 9437446..91288a1 100644 --- a/src/gaemstone.ECS/Filter.cs +++ b/src/gaemstone.ECS/Filter.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using System.Runtime.CompilerServices; -using gaemstone.Utility; +using gaemstone.ECS.Utility; using static flecs_hub.flecs; namespace gaemstone.ECS; diff --git a/src/gaemstone.ECS/IdRef.cs b/src/gaemstone.ECS/IdRef.cs index 1099c49..9ee5137 100644 --- a/src/gaemstone.ECS/IdRef.cs +++ b/src/gaemstone.ECS/IdRef.cs @@ -1,5 +1,5 @@ using System; -using gaemstone.Utility; +using gaemstone.ECS.Utility; using static flecs_hub.flecs; namespace gaemstone.ECS; diff --git a/src/gaemstone.ECS/Iterator.cs b/src/gaemstone.ECS/Iterator.cs index b35d818..09886f9 100644 --- a/src/gaemstone.ECS/Iterator.cs +++ b/src/gaemstone.ECS/Iterator.cs @@ -2,7 +2,7 @@ using System; using System.Collections; using System.Collections.Generic; using System.Runtime.CompilerServices; -using gaemstone.Utility; +using gaemstone.ECS.Utility; using static flecs_hub.flecs; namespace gaemstone.ECS; diff --git a/src/gaemstone.ECS/Observer.cs b/src/gaemstone.ECS/Observer.cs index 849ab42..80a4c52 100644 --- a/src/gaemstone.ECS/Observer.cs +++ b/src/gaemstone.ECS/Observer.cs @@ -1,6 +1,6 @@ using System; using System.Runtime.InteropServices; -using gaemstone.Utility; +using gaemstone.ECS.Utility; using static flecs_hub.flecs; namespace gaemstone.ECS; diff --git a/src/gaemstone.ECS/Query.cs b/src/gaemstone.ECS/Query.cs index a215a74..3a421ea 100644 --- a/src/gaemstone.ECS/Query.cs +++ b/src/gaemstone.ECS/Query.cs @@ -1,5 +1,5 @@ using System; -using gaemstone.Utility; +using gaemstone.ECS.Utility; using static flecs_hub.flecs; namespace gaemstone.ECS; diff --git a/src/gaemstone.ECS/Rule.cs b/src/gaemstone.ECS/Rule.cs index 142905d..6228f8d 100644 --- a/src/gaemstone.ECS/Rule.cs +++ b/src/gaemstone.ECS/Rule.cs @@ -1,7 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; -using gaemstone.Utility; +using gaemstone.ECS.Utility; using static flecs_hub.flecs; namespace gaemstone.ECS; diff --git a/src/gaemstone.ECS/System.cs b/src/gaemstone.ECS/System.cs index 4497ba1..4275a59 100644 --- a/src/gaemstone.ECS/System.cs +++ b/src/gaemstone.ECS/System.cs @@ -1,6 +1,6 @@ using System; using System.Runtime.InteropServices; -using gaemstone.Utility; +using gaemstone.ECS.Utility; using static flecs_hub.flecs; namespace gaemstone.ECS; diff --git a/src/gaemstone.ECS/Term.cs b/src/gaemstone.ECS/Term.cs index bf79298..b31a3a2 100644 --- a/src/gaemstone.ECS/Term.cs +++ b/src/gaemstone.ECS/Term.cs @@ -1,5 +1,5 @@ using System; -using gaemstone.Utility; +using gaemstone.ECS.Utility; using static flecs_hub.flecs; namespace gaemstone.ECS; diff --git a/src/gaemstone.ECS/Utility/Allocators.cs b/src/gaemstone.ECS/Utility/Allocators.cs new file mode 100644 index 0000000..526a89b --- /dev/null +++ b/src/gaemstone.ECS/Utility/Allocators.cs @@ -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 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; } + + // 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((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. */ } +} diff --git a/src/gaemstone.ECS/Utility/CStringExtensions.cs b/src/gaemstone.ECS/Utility/CStringExtensions.cs new file mode 100644 index 0000000..96c691a --- /dev/null +++ b/src/gaemstone.ECS/Utility/CStringExtensions.cs @@ -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(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); } +} diff --git a/src/gaemstone.ECS/Utility/CallbackContextHelper.cs b/src/gaemstone.ECS/Utility/CallbackContextHelper.cs new file mode 100644 index 0000000..9c838bb --- /dev/null +++ b/src/gaemstone.ECS/Utility/CallbackContextHelper.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; + +namespace gaemstone.ECS.Utility; + +public static class CallbackContextHelper +{ + private static readonly Dictionary _contexts = new(); + private static nint _counter = 0; + + public static nint Create(T context) where T : notnull + { + lock (_contexts) { + var id = _counter++; + _contexts.Add(id, context); + return id; + } + } + + public static T Get(nint id) + { + lock (_contexts) + return (T)_contexts[id]; + } + + public static void Free(nint id) + { + lock (_contexts) + _contexts.Remove(id); + } +} diff --git a/src/gaemstone.ECS/Utility/FlecsException.cs b/src/gaemstone.ECS/Utility/FlecsException.cs new file mode 100644 index 0000000..13f2868 --- /dev/null +++ b/src/gaemstone.ECS/Utility/FlecsException.cs @@ -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(); +} diff --git a/src/gaemstone.ECS/Utility/SpanExtensions.cs b/src/gaemstone.ECS/Utility/SpanExtensions.cs new file mode 100644 index 0000000..7636a6a --- /dev/null +++ b/src/gaemstone.ECS/Utility/SpanExtensions.cs @@ -0,0 +1,41 @@ +using System; + +namespace gaemstone.ECS.Utility; + +public static class SpanExtensions +{ + public static T? GetOrNull(this Span span, int index) + where T : struct => GetOrNull((ReadOnlySpan)span, index); + public static T? GetOrNull(this ReadOnlySpan span, int index) + where T : struct => (index >= 0 && index < span.Length) ? span[index] : null; + + + public static ReadOnlySpanSplitEnumerator Split(this ReadOnlySpan span, T splitOn) + where T : IEquatable => new(span, splitOn); + + public ref struct ReadOnlySpanSplitEnumerator + where T : IEquatable + { + private readonly ReadOnlySpan _span; + private readonly T _splitOn; + private int _index = -1; + private int _end = -1; + + public ReadOnlySpanSplitEnumerator(ReadOnlySpan span, T splitOn) + { _span = span; _splitOn = splitOn; } + + public ReadOnlySpanSplitEnumerator GetEnumerator() => this; + + public ReadOnlySpan 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; + } + } +} diff --git a/src/gaemstone.ECS/World+Lookup.cs b/src/gaemstone.ECS/World+Lookup.cs index 088bdb5..1357ff7 100644 --- a/src/gaemstone.ECS/World+Lookup.cs +++ b/src/gaemstone.ECS/World+Lookup.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -using gaemstone.Utility; +using gaemstone.ECS.Utility; using static flecs_hub.flecs; namespace gaemstone.ECS; diff --git a/src/gaemstone.ECS/World.cs b/src/gaemstone.ECS/World.cs index c52d731..64dd4e8 100644 --- a/src/gaemstone.ECS/World.cs +++ b/src/gaemstone.ECS/World.cs @@ -1,5 +1,5 @@ using System; -using gaemstone.Utility; +using gaemstone.ECS.Utility; using static flecs_hub.flecs; namespace gaemstone.ECS; diff --git a/src/gaemstone.ECS/gaemstone.ECS.csproj b/src/gaemstone.ECS/gaemstone.ECS.csproj new file mode 100644 index 0000000..d1b876c --- /dev/null +++ b/src/gaemstone.ECS/gaemstone.ECS.csproj @@ -0,0 +1,14 @@ + + + + net7.0 + true + disable + enable + + + + + + +