using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using static flecs_hub.flecs; namespace gaemstone.ECS; public static unsafe class ComponentExtensions { public static EntityRef InitComponent(this EntityRef entity) { if (typeof(T).IsPrimitive) throw new ArgumentException( "Must not be primitive"); if (typeof(T).IsValueType && RuntimeHelpers.IsReferenceOrContainsReferences()) throw new ArgumentException( "Struct component must satisfy the unmanaged constraint. " + "Consider making it a class if you need to store references."); var size = typeof(T).IsValueType ? Unsafe.SizeOf() : sizeof(ReferenceHandle); var typeInfo = new ecs_type_info_t { size = size, alignment = size }; var componentDesc = new ecs_component_desc_t { entity = entity, type = typeInfo }; ecs_component_init(entity.World, &componentDesc); if (!typeof(T).IsValueType) { // Set up component hooks for proper freeing of GCHandles. // Without them, managed classes would never be garbage collected. var typeHooks = new ecs_type_hooks_t { ctor = new() { Data = new() { Pointer = &ReferenceHandle.Construct } }, dtor = new() { Data = new() { Pointer = &ReferenceHandle.Destruct } }, move = new() { Data = new() { Pointer = &ReferenceHandle.Move } }, copy = new() { Data = new() { Pointer = &ReferenceHandle.Copy } }, }; ecs_set_hooks_id(entity.World, entity, &typeHooks); } return entity; } } public unsafe readonly struct ReferenceHandle : IDisposable { public static int NumActiveHandles { get; private set; } private readonly nint _value; public object? Target => (_value != default) ? ((GCHandle)_value).Target : null; private ReferenceHandle(nint value) => _value = value; public static ReferenceHandle Alloc(object? target) { if (target == null) return default; NumActiveHandles++; return new((nint)GCHandle.Alloc(target)); } public ReferenceHandle Clone() => Alloc(Target); public void Dispose() { if (_value == default) return; NumActiveHandles--; ((GCHandle)_value).Free(); } [UnmanagedCallersOnly] internal static void Construct(void* ptr, int count, ecs_type_info_t* _) => new Span(ptr, count).Clear(); [UnmanagedCallersOnly] internal static void Destruct(void* ptr, int count, ecs_type_info_t* _) { var span = new Span(ptr, count); foreach (var handle in span) handle.Dispose(); span.Clear(); } [UnmanagedCallersOnly] internal static void Move(void* dstPtr, void* srcPtr, int count, ecs_type_info_t* _) { var dst = new Span(dstPtr, count); var src = new Span(srcPtr, count); foreach (var handle in dst) handle.Dispose(); src.CopyTo(dst); src.Clear(); } [UnmanagedCallersOnly] internal static void Copy(void* dstPtr, void* srcPtr, int count, ecs_type_info_t* _) { var dst = new Span(dstPtr, count); var src = new Span(srcPtr, count); for (var i = 0; i < count; i++) { dst[i].Dispose(); dst[i] = src[i].Clone(); } } }