|
|
|
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<T>(this EntityRef entity)
|
|
|
|
{
|
|
|
|
if (typeof(T).IsPrimitive) throw new ArgumentException(
|
|
|
|
"Must not be primitive");
|
|
|
|
if (typeof(T).IsValueType && RuntimeHelpers.IsReferenceOrContainsReferences<T>()) 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<T>() : 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<ReferenceHandle>(ptr, count).Clear();
|
|
|
|
|
|
|
|
[UnmanagedCallersOnly]
|
|
|
|
internal static void Destruct(void* ptr, int count, ecs_type_info_t* _)
|
|
|
|
{
|
|
|
|
var span = new Span<ReferenceHandle>(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<ReferenceHandle>(dstPtr, count);
|
|
|
|
var src = new Span<ReferenceHandle>(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<ReferenceHandle>(dstPtr, count);
|
|
|
|
var src = new Span<ReferenceHandle>(srcPtr, count);
|
|
|
|
for (var i = 0; i < count; i++) {
|
|
|
|
dst[i].Dispose();
|
|
|
|
dst[i] = src[i].Clone();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|