parent
b4a769e3a6
commit
c859bd37a1
45 changed files with 934 additions and 824 deletions
@ -0,0 +1,22 @@ |
|||||||
|
using System.Reflection; |
||||||
|
|
||||||
|
namespace gaemstone.ECS; |
||||||
|
|
||||||
|
[Module(Name = "flecs.core")] |
||||||
|
public static class ObserverEvent |
||||||
|
{ |
||||||
|
[Entity] public struct OnAdd { } |
||||||
|
[Entity] public struct OnRemove { } |
||||||
|
[Entity] public struct OnSet { } |
||||||
|
[Entity] public struct UnSet { } |
||||||
|
// [Entity] public struct OnDelete { } |
||||||
|
// [Entity] public struct OnCreateTable { } |
||||||
|
// [Entity] public struct OnDeleteTable { } |
||||||
|
[Entity] public struct OnTableEmpty { } |
||||||
|
[Entity] public struct OnTableFilled { } |
||||||
|
// [Entity] public struct OnCreateTrigger { } |
||||||
|
// [Entity] public struct OnDeleteTrigger { } |
||||||
|
// [Entity] public struct OnDeleteObservable { } |
||||||
|
// [Entity] public struct OnComponentHooks { } |
||||||
|
// [Entity] public struct OnDeleteTarget { } |
||||||
|
} |
@ -0,0 +1,30 @@ |
|||||||
|
namespace gaemstone.ECS; |
||||||
|
|
||||||
|
[Module(Name = "flecs.core")] |
||||||
|
public static class BuiltIn |
||||||
|
{ |
||||||
|
// Entity Tags |
||||||
|
[Tag] public struct Module { } |
||||||
|
[Tag] public struct Prefab { } |
||||||
|
[Tag] public struct SlotOf { } |
||||||
|
[Tag] public struct Disabled { } |
||||||
|
[Tag] public struct Empty { } |
||||||
|
|
||||||
|
// Entity Relationships |
||||||
|
[Tag, Relation] public struct IsA { } |
||||||
|
[Tag, Relation] public struct ChildOf { } |
||||||
|
[Tag, Relation] public struct DependsOn { } |
||||||
|
|
||||||
|
// Component / Relationship Properties |
||||||
|
[Tag] public struct Transitive { } |
||||||
|
[Tag] public struct Reflexive { } |
||||||
|
[Tag] public struct Symmetric { } |
||||||
|
[Tag] public struct Final { } |
||||||
|
[Tag] public struct DontInherit { } |
||||||
|
[Tag] public struct Tag { } |
||||||
|
[Tag] public struct Union { } |
||||||
|
[Tag] public struct Exclusive { } |
||||||
|
[Tag] public struct Acyclic { } |
||||||
|
[Tag, Relation] public struct With { } |
||||||
|
[Tag] public struct OneOf { } |
||||||
|
} |
@ -0,0 +1,90 @@ |
|||||||
|
using System; |
||||||
|
using System.Runtime.InteropServices; |
||||||
|
using gaemstone.Utility; |
||||||
|
using static flecs_hub.flecs; |
||||||
|
|
||||||
|
namespace gaemstone.ECS; |
||||||
|
|
||||||
|
// FIXME: This API is flawed. |
||||||
|
public unsafe class ComponentHooks |
||||||
|
{ |
||||||
|
public EntityRef Entity { get; } |
||||||
|
public ecs_type_hooks_t* Handle { get; } |
||||||
|
|
||||||
|
private Action<Iterator>? _onAdd; |
||||||
|
public event Action<Iterator>? OnAdd { |
||||||
|
add { if ((_onAdd += value) != null) Handle->on_add.Data.Pointer = &CallbackOnAdd; } |
||||||
|
remove { if ((_onAdd -= value) == null) Handle->on_add.Data.Pointer = null; } |
||||||
|
} |
||||||
|
private Action<Iterator>? _onSet; |
||||||
|
public event Action<Iterator>? OnSet { |
||||||
|
add { if ((_onSet += value) != null) Handle->on_set.Data.Pointer = &CallbackOnSet; } |
||||||
|
remove { if ((_onSet -= value) == null) Handle->on_set.Data.Pointer = null; } |
||||||
|
} |
||||||
|
private Action<Iterator>? _onRemove; |
||||||
|
public event Action<Iterator>? OnRemove { |
||||||
|
add { if ((_onRemove += value) != null) Handle->on_remove.Data.Pointer = &CallbackOnRemove; } |
||||||
|
remove { if ((_onRemove -= value) == null) Handle->on_remove.Data.Pointer = null; } |
||||||
|
} |
||||||
|
|
||||||
|
private Action<nint, int>? _constructor; |
||||||
|
public event Action<nint, int>? Construct { |
||||||
|
add { if ((_constructor += value) != null) Handle->ctor.Data.Pointer = &CallbackConstructor; } |
||||||
|
remove { if ((_constructor -= value) == null) Handle->ctor.Data.Pointer = null; } |
||||||
|
} |
||||||
|
private Action<nint, int>? _destructor; |
||||||
|
public event Action<nint, int>? Destruct { |
||||||
|
add { if ((_destructor += value) != null) Handle->dtor.Data.Pointer = &CallbackDestructor; } |
||||||
|
remove { if ((_destructor -= value) == null) Handle->dtor.Data.Pointer = null; } |
||||||
|
} |
||||||
|
private Action<nint, nint, int>? _copy; |
||||||
|
public event Action<nint, nint, int>? Copy { |
||||||
|
add { if ((_copy += value) != null) Handle->copy.Data.Pointer = &CallbackCopy; } |
||||||
|
remove { if ((_copy -= value) == null) Handle->copy.Data.Pointer = null; } |
||||||
|
} |
||||||
|
private Action<nint, nint, int>? _move; |
||||||
|
public event Action<nint, nint, int>? Move { |
||||||
|
add { if ((_move += value) != null) Handle->move.Data.Pointer = &CallbackMove; } |
||||||
|
remove { if ((_move -= value) == null) Handle->move.Data.Pointer = null; } |
||||||
|
} |
||||||
|
|
||||||
|
internal ComponentHooks(EntityRef entity, ecs_type_hooks_t* handle) |
||||||
|
{ Entity = entity; Handle = handle; } |
||||||
|
|
||||||
|
|
||||||
|
private static ComponentHooks Get(void* ptr) |
||||||
|
=> CallbackContextHelper.Get<ComponentHooks>((nint)ptr); |
||||||
|
|
||||||
|
[UnmanagedCallersOnly] private static void CallbackOnAdd(ecs_iter_t* it) |
||||||
|
{ var hooks = Get(it->binding_ctx); hooks._onAdd?.Invoke(new(hooks.Entity.Universe, null, *it)); } |
||||||
|
[UnmanagedCallersOnly] private static void CallbackOnSet(ecs_iter_t* it) |
||||||
|
{ var hooks = Get(it->binding_ctx); hooks._onSet?.Invoke(new(hooks.Entity.Universe, null, *it)); } |
||||||
|
[UnmanagedCallersOnly] private static void CallbackOnRemove(ecs_iter_t* it) |
||||||
|
{ var hooks = Get(it->binding_ctx); hooks._onRemove?.Invoke(new(hooks.Entity.Universe, null, *it)); } |
||||||
|
|
||||||
|
[UnmanagedCallersOnly] private static void CallbackConstructor(void* ptr, int count, ecs_type_info_t* type) |
||||||
|
=> Get(type->hooks.binding_ctx)._constructor?.Invoke((nint)ptr, count); |
||||||
|
[UnmanagedCallersOnly] private static void CallbackDestructor(void* ptr, int count, ecs_type_info_t* type) |
||||||
|
=> Get(type->hooks.binding_ctx)._destructor?.Invoke((nint)ptr, count); |
||||||
|
[UnmanagedCallersOnly] private static void CallbackCopy(void* dest, void* src, int count, ecs_type_info_t* type) |
||||||
|
=> Get(type->hooks.binding_ctx)._copy?.Invoke((nint)dest, (nint)src, count); |
||||||
|
[UnmanagedCallersOnly] private static void CallbackMove(void* dest, void* src, int count, ecs_type_info_t* type) |
||||||
|
=> Get(type->hooks.binding_ctx)._move?.Invoke((nint)dest, (nint)src, count); |
||||||
|
} |
||||||
|
|
||||||
|
public unsafe static class ComponentHooksExtensions |
||||||
|
{ |
||||||
|
public static ComponentHooks GetComponentHooks(this EntityRef entity) |
||||||
|
{ |
||||||
|
var handle = ecs_get_hooks_id(entity.Universe, entity); |
||||||
|
if (handle == null) throw new ArgumentException($"No type info found for {entity}"); |
||||||
|
|
||||||
|
if (handle->binding_ctx == null) { |
||||||
|
var hooks = new ComponentHooks(entity, handle); |
||||||
|
handle->binding_ctx = (void*)CallbackContextHelper.Create(hooks); |
||||||
|
return hooks; |
||||||
|
} else { |
||||||
|
return CallbackContextHelper.Get<ComponentHooks>((nint)handle->binding_ctx); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -1,43 +0,0 @@ |
|||||||
using static flecs_hub.flecs; |
|
||||||
|
|
||||||
namespace gaemstone.ECS; |
|
||||||
|
|
||||||
public unsafe readonly partial struct EntityRef |
|
||||||
{ |
|
||||||
public EntityRef Add(Identifier id) { ecs_add_id(Universe, this, id); return this; } |
|
||||||
public EntityRef Add(Entity relation, Entity target) => Add(relation & target); |
|
||||||
public EntityRef Remove(Identifier id) { ecs_remove_id(Universe, this, id); return this; } |
|
||||||
public EntityRef Remove(Entity relation, Entity target) => Remove(relation & target); |
|
||||||
public bool Has(Identifier id) => ecs_has_id(Universe, this, id); |
|
||||||
public bool Has(Entity relation, Entity target) => Has(relation & target); |
|
||||||
// public EntityRef Override(Identifier id) { ecs_override_id(Universe, this, id); return this; } |
|
||||||
// public EntityRef Override(Entity relation, Entity target) => Override(relation & target); |
|
||||||
|
|
||||||
public EntityRef Add<T>() |
|
||||||
=> Add(Universe.Lookup<T>()); |
|
||||||
public EntityRef Add<TRelation, TTarget>() |
|
||||||
=> Add(Universe.Lookup<TRelation>(), Universe.Lookup<TTarget>()); |
|
||||||
public EntityRef Add<TRelation>(Entity target) |
|
||||||
=> Add(Universe.Lookup<TRelation>(), target); |
|
||||||
|
|
||||||
public EntityRef Remove<T>() |
|
||||||
=> Remove(Universe.Lookup<T>()); |
|
||||||
public EntityRef Remove<TRelation, TTarget>() |
|
||||||
=> Remove(Universe.Lookup<TRelation>(), Universe.Lookup<TTarget>()); |
|
||||||
public EntityRef Remove<TRelation>(Entity target) |
|
||||||
=> Remove(Universe.Lookup<TRelation>(), target); |
|
||||||
|
|
||||||
public bool Has<T>() |
|
||||||
=> Has(Universe.Lookup<T>()); |
|
||||||
public bool Has<TRelation, TTarget>() |
|
||||||
=> Has(Universe.Lookup<TRelation>(), Universe.Lookup<TTarget>()); |
|
||||||
public bool Has<TRelation>(Entity target) |
|
||||||
=> Has(Universe.Lookup<TRelation>(), target); |
|
||||||
|
|
||||||
// public EntityRef Override<T>() |
|
||||||
// => Override(Universe.Lookup<T>()); |
|
||||||
// public EntityRef Override<TRelation, TTarget>() |
|
||||||
// => Override(Universe.Lookup<TRelation>(), Universe.Lookup<TTarget>()); |
|
||||||
// public EntityRef Override<TRelation>(Entity target) |
|
||||||
// => Override(Universe.Lookup<TRelation>(), target); |
|
||||||
} |
|
@ -1,86 +0,0 @@ |
|||||||
using System; |
|
||||||
using System.Runtime.CompilerServices; |
|
||||||
using System.Runtime.InteropServices; |
|
||||||
using static flecs_hub.flecs; |
|
||||||
|
|
||||||
namespace gaemstone.ECS; |
|
||||||
|
|
||||||
public unsafe readonly partial struct EntityRef |
|
||||||
{ |
|
||||||
/// <summary> |
|
||||||
/// Gets a component value from this entity. If the component is a value |
|
||||||
/// type, this will return a copy. If the component is a reference type, |
|
||||||
/// it will return the reference itself. |
|
||||||
/// When modifying a reference, consider calling <see cref="Modified"/>. |
|
||||||
/// </summary> |
|
||||||
public T Get<T>() |
|
||||||
{ |
|
||||||
var comp = Universe.Lookup<T>(); |
|
||||||
var ptr = ecs_get_id(Universe, this, comp); |
|
||||||
return (typeof(T).IsValueType) ? Unsafe.Read<T>(ptr) |
|
||||||
: (T)((GCHandle)Unsafe.Read<nint>(ptr)).Target!; |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary> |
|
||||||
/// Gets a reference to a component value from this entity. Only works for |
|
||||||
/// value types. When modifying, consider calling <see cref="Modified"/>. |
|
||||||
/// </summary> |
|
||||||
public ref T GetRef<T>() |
|
||||||
where T : unmanaged |
|
||||||
{ |
|
||||||
var comp = Universe.Lookup<T>(); |
|
||||||
var ptr = ecs_get_mut_id(Universe, this, comp); |
|
||||||
return ref Unsafe.AsRef<T>(ptr); |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary> |
|
||||||
/// Marks a component as modified. Do this after getting a reference to |
|
||||||
/// it with <see cref="Get"/> or <see cref="GetRef"/>, making sure change |
|
||||||
/// detection will kick in. |
|
||||||
/// </summary> |
|
||||||
public void Modified<T>() |
|
||||||
=> ecs_modified_id(Universe, this, Universe.Lookup<T>()); |
|
||||||
|
|
||||||
|
|
||||||
public EntityRef Set<T>(in T value) |
|
||||||
where T : unmanaged |
|
||||||
{ |
|
||||||
var comp = Universe.Lookup<T>(); |
|
||||||
var size = (ulong)Unsafe.SizeOf<T>(); |
|
||||||
fixed (T* ptr = &value) |
|
||||||
ecs_set_id(Universe, this, comp, size, ptr); |
|
||||||
return this; |
|
||||||
} |
|
||||||
|
|
||||||
// public Entity SetOverride<T>(in T value) |
|
||||||
// where T : unmanaged |
|
||||||
// { |
|
||||||
// var comp = Universe.Lookup<T>(); |
|
||||||
// var size = (ulong)Unsafe.SizeOf<T>(); |
|
||||||
// ecs_add_id(Universe, this, Identifier.Combine(IdentifierFlags.Override, comp)); |
|
||||||
// fixed (T* ptr = &value) ecs_set_id(Universe, this, comp, size, ptr); |
|
||||||
// return this; |
|
||||||
// } |
|
||||||
|
|
||||||
public EntityRef Set<T>(T obj) where T : class
|
|
||||||
=> Set(typeof(T), obj); |
|
||||||
public EntityRef Set(Type type, object obj) |
|
||||||
{ |
|
||||||
var comp = Universe.Lookup(type); |
|
||||||
var handle = (nint)GCHandle.Alloc(obj); |
|
||||||
ecs_set_id(Universe, this, comp, (ulong)sizeof(nint), &handle); |
|
||||||
// FIXME: Handle needs to be freed when component is removed! |
|
||||||
return this; |
|
||||||
} |
|
||||||
|
|
||||||
// public EntityRef SetOverride<T>(T obj) |
|
||||||
// where T : class |
|
||||||
// { |
|
||||||
// var comp = Universe.Lookup<T>(); |
|
||||||
// var handle = (nint)GCHandle.Alloc(obj); |
|
||||||
// ecs_add_id(Universe, this, Identifier.Combine(IdentifierFlags.Override, comp)); |
|
||||||
// ecs_set_id(Universe, this, comp, (ulong)sizeof(nint), &handle); |
|
||||||
// // FIXME: Handle needs to be freed when component is removed! |
|
||||||
// return this; |
|
||||||
// } |
|
||||||
} |
|
@ -0,0 +1,44 @@ |
|||||||
|
using static gaemstone.ECS.BuiltIn; |
||||||
|
|
||||||
|
namespace gaemstone.ECS; |
||||||
|
|
||||||
|
public abstract class EntityBase<TReturn> |
||||||
|
{ |
||||||
|
public abstract Universe Universe { get; } |
||||||
|
|
||||||
|
public abstract TReturn Add(Identifier id); |
||||||
|
public abstract TReturn Remove(Identifier id); |
||||||
|
public abstract bool Has(Identifier id); |
||||||
|
|
||||||
|
public abstract T Get<T>(); |
||||||
|
public abstract ref T GetRef<T>() where T : unmanaged; |
||||||
|
public abstract void Modified<T>(); |
||||||
|
|
||||||
|
public abstract TReturn Set<T>(in T value) where T : unmanaged; |
||||||
|
public abstract TReturn Set<T>(T obj) where T : class; |
||||||
|
|
||||||
|
public TReturn Add(string name) => Add(Universe.LookupOrThrow(name)); |
||||||
|
public TReturn Add<T>() => Add(Universe.LookupOrThrow(typeof(T))); |
||||||
|
public TReturn Add(Entity relation, Entity target) => Add(relation & target); |
||||||
|
public TReturn Add<TRelation>(Entity target) => Add(Universe.LookupOrThrow<TRelation>(), target); |
||||||
|
public TReturn Add<TRelation, TTarget>() => Add(Universe.LookupOrThrow<TRelation>(), Universe.LookupOrThrow<TTarget>()); |
||||||
|
|
||||||
|
public TReturn Remove(string name) => Remove(Universe.LookupOrThrow(name)); |
||||||
|
public TReturn Remove<T>() => Remove(Universe.LookupOrThrow(typeof(T))); |
||||||
|
public TReturn Remove(Entity relation, Entity target) => Remove(relation & target); |
||||||
|
public TReturn Remove<TRelation>(Entity target) => Remove(Universe.LookupOrThrow<TRelation>(), target); |
||||||
|
public TReturn Remove<TRelation, TTarget>() => Remove(Universe.LookupOrThrow<TRelation>(), Universe.LookupOrThrow<TTarget>()); |
||||||
|
|
||||||
|
public bool Has(string name) => Has(Universe.LookupOrThrow(name)); |
||||||
|
public bool Has<T>() => Has(Universe.LookupOrThrow(typeof(T))); |
||||||
|
public bool Has(Entity relation, Entity target) => Has(relation & target); |
||||||
|
public bool Has<TRelation>(Entity target) => Has(Universe.LookupOrThrow<TRelation>(), target); |
||||||
|
public bool Has<TRelation, TTarget>() => Has(Universe.LookupOrThrow<TRelation>(), Universe.LookupOrThrow<TTarget>()); |
||||||
|
|
||||||
|
public TReturn ChildOf(Entity parent) => Add<ChildOf>(parent); |
||||||
|
public TReturn ChildOf<TParent>() => Add<ChildOf, TParent>(); |
||||||
|
|
||||||
|
public TReturn Disable() => Add<Disabled>(); |
||||||
|
public TReturn Enable() => Remove<Disabled>(); |
||||||
|
public bool IsDisabled => Has<Disabled>(); |
||||||
|
} |
@ -0,0 +1,105 @@ |
|||||||
|
using System; |
||||||
|
using System.Runtime.CompilerServices; |
||||||
|
using System.Runtime.InteropServices; |
||||||
|
using static flecs_hub.flecs; |
||||||
|
|
||||||
|
namespace gaemstone.ECS; |
||||||
|
|
||||||
|
public unsafe sealed class EntityRef |
||||||
|
: EntityBase<EntityRef> |
||||||
|
, IEquatable<EntityRef> |
||||||
|
{ |
||||||
|
public override Universe Universe { get; } |
||||||
|
public Entity Entity { get; } |
||||||
|
|
||||||
|
public bool IsValid => ecs_is_valid(Universe, this); |
||||||
|
public bool IsAlive => ecs_is_alive(Universe, this); |
||||||
|
public EntityType Type => new(Universe, ecs_get_type(Universe, this)); |
||||||
|
public string FullPath => ecs_get_path_w_sep(Universe, default, this, ".", default).FlecsToStringAndFree()!; |
||||||
|
|
||||||
|
public string? Name { |
||||||
|
get => ecs_get_name(Universe, this).FlecsToString()!; |
||||||
|
set => ecs_set_name(Universe, this, value.FlecsToCStringThenFree()); |
||||||
|
} |
||||||
|
public string? Symbol { |
||||||
|
get => ecs_get_symbol(Universe, this).FlecsToString()!; |
||||||
|
set => ecs_set_symbol(Universe, this, value.FlecsToCStringThenFree()); |
||||||
|
} |
||||||
|
|
||||||
|
// TODO: public IEnumerable<Entity> Children => ... |
||||||
|
|
||||||
|
public EntityRef(Universe universe, Entity entity, bool throwOnInvalid = true) |
||||||
|
{ |
||||||
|
Universe = universe; |
||||||
|
Entity = entity; |
||||||
|
if (throwOnInvalid && !IsValid) throw new InvalidOperationException( |
||||||
|
$"The entity {entity} is not valid"); |
||||||
|
} |
||||||
|
|
||||||
|
public void Delete() => ecs_delete(Universe, this); |
||||||
|
|
||||||
|
public EntityBuilder NewChild(string? name = null, string? symbol = null) |
||||||
|
=> Universe.New(name, symbol).ChildOf(this); |
||||||
|
|
||||||
|
public override EntityRef Add(Identifier id) { ecs_add_id(Universe, this, id); return this; } |
||||||
|
public override EntityRef Remove(Identifier id) { ecs_remove_id(Universe, this, id); return this; } |
||||||
|
public override bool Has(Identifier id) => ecs_has_id(Universe, this, id); |
||||||
|
|
||||||
|
public override T Get<T>() |
||||||
|
{ |
||||||
|
var comp = Universe.LookupOrThrow<T>(); |
||||||
|
var ptr = ecs_get_id(Universe, this, comp); |
||||||
|
if (ptr == null) throw new Exception($"Component {typeof(T)} not found on {this}"); |
||||||
|
return (typeof(T).IsValueType) ? Unsafe.Read<T>(ptr) |
||||||
|
: (T)((GCHandle)Unsafe.Read<nint>(ptr)).Target!; |
||||||
|
} |
||||||
|
|
||||||
|
public override ref T GetRef<T>() |
||||||
|
{ |
||||||
|
var comp = Universe.LookupOrThrow<T>(); |
||||||
|
var ptr = ecs_get_mut_id(Universe, this, comp); |
||||||
|
if (ptr == null) throw new Exception($"Component {typeof(T)} not found on {this}"); |
||||||
|
return ref Unsafe.AsRef<T>(ptr); |
||||||
|
} |
||||||
|
|
||||||
|
public override void Modified<T>() |
||||||
|
=> ecs_modified_id(Universe, this, Universe.LookupOrThrow<T>()); |
||||||
|
|
||||||
|
public override EntityRef Set<T>(in T value) |
||||||
|
{ |
||||||
|
var comp = Universe.LookupOrThrow<T>(); |
||||||
|
var size = (ulong)Unsafe.SizeOf<T>(); |
||||||
|
fixed (T* ptr = &value) |
||||||
|
if (ecs_set_id(Universe, this, comp, size, ptr).Data == 0) |
||||||
|
throw new InvalidOperationException(); |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
public override EntityRef Set<T>(T obj) where T : class
|
||||||
|
{ |
||||||
|
var comp = Universe.LookupOrThrow<T>(); |
||||||
|
var handle = (nint)GCHandle.Alloc(obj); |
||||||
|
// FIXME: Previous handle needs to be freed. |
||||||
|
if (ecs_set_id(Universe, this, comp, (ulong)sizeof(nint), &handle).Data == 0) |
||||||
|
throw new InvalidOperationException(); |
||||||
|
// FIXME: Handle needs to be freed when component is removed! |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
public bool Equals(EntityRef? other) => (other is not null) && Universe == other.Universe && Entity == other.Entity; |
||||||
|
public override bool Equals(object? obj) => Equals(obj as EntityRef); |
||||||
|
public override int GetHashCode() => HashCode.Combine(Universe, Entity); |
||||||
|
public override string? ToString() => ecs_entity_str(Universe, this).FlecsToStringAndFree()!; |
||||||
|
|
||||||
|
public static bool operator ==(EntityRef? left, EntityRef? right) => ReferenceEquals(left, right) || (left?.Equals(right) ?? false); |
||||||
|
public static bool operator !=(EntityRef? left, EntityRef? right) => !(left == right); |
||||||
|
|
||||||
|
public static IdentifierRef operator &(EntityRef relation, Entity target) => IdentifierRef.Pair(relation, target); |
||||||
|
public static IdentifierRef operator &(Entity relation, EntityRef target) => IdentifierRef.Pair(relation, target); |
||||||
|
|
||||||
|
public static implicit operator Entity(EntityRef e) => e.Entity; |
||||||
|
public static implicit operator ecs_entity_t(EntityRef e) => e.Entity.Value; |
||||||
|
|
||||||
|
public static implicit operator Identifier(EntityRef e) => new(e.Entity.Value.Data); |
||||||
|
public static implicit operator ecs_id_t(EntityRef e) => e.Entity.Value.Data; |
||||||
|
} |
@ -0,0 +1,24 @@ |
|||||||
|
using System.Collections; |
||||||
|
using System.Collections.Generic; |
||||||
|
using static flecs_hub.flecs; |
||||||
|
|
||||||
|
namespace gaemstone.ECS; |
||||||
|
|
||||||
|
public unsafe readonly struct EntityType |
||||||
|
: IReadOnlyList<IdentifierRef> |
||||||
|
{ |
||||||
|
public Universe Universe { get; } |
||||||
|
public ecs_type_t* Handle { get; } |
||||||
|
|
||||||
|
public EntityType(Universe universe, ecs_type_t* handle) |
||||||
|
{ Universe = universe; Handle = handle; } |
||||||
|
|
||||||
|
public override string ToString() |
||||||
|
=> ecs_type_str(Universe, Handle).FlecsToStringAndFree()!; |
||||||
|
|
||||||
|
// IReadOnlyList implementation |
||||||
|
public int Count => Handle->count; |
||||||
|
public IdentifierRef this[int index] => new(Universe, new(Handle->array[index])); |
||||||
|
public IEnumerator<IdentifierRef> GetEnumerator() { for (var i = 0; i < Count; i++) yield return this[i]; } |
||||||
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); |
||||||
|
} |
@ -1,41 +0,0 @@ |
|||||||
namespace gaemstone.ECS; |
|
||||||
|
|
||||||
public static class Flecs |
|
||||||
{ |
|
||||||
// Entities |
|
||||||
|
|
||||||
// [BuiltIn] public struct World { } |
|
||||||
[BuiltIn] public struct Flag { } |
|
||||||
|
|
||||||
// Tags |
|
||||||
|
|
||||||
[BuiltIn] public struct Prefab { } |
|
||||||
[BuiltIn] public struct SlotOf { } |
|
||||||
[BuiltIn] public struct Disabled { } |
|
||||||
[BuiltIn] public struct Empty { } |
|
||||||
|
|
||||||
// Component / relationship properties |
|
||||||
|
|
||||||
[BuiltIn(256 + 10)] public struct Wildcard { } |
|
||||||
[BuiltIn(256 + 11)] public struct Any { } |
|
||||||
[BuiltIn(256 + 12)] public struct This { } |
|
||||||
[BuiltIn(256 + 13)] public struct Variable { } |
|
||||||
|
|
||||||
[BuiltIn] public struct Transitive { } |
|
||||||
[BuiltIn] public struct Reflexive { } |
|
||||||
[BuiltIn] public struct Symmetric { } |
|
||||||
[BuiltIn] public struct Final { } |
|
||||||
[BuiltIn] public struct DontInherit { } |
|
||||||
[BuiltIn] public struct Tag { } |
|
||||||
[BuiltIn] public struct Union { } |
|
||||||
[BuiltIn] public struct Exclusive { } |
|
||||||
[BuiltIn] public struct Acyclic { } |
|
||||||
[BuiltIn] public struct With { } |
|
||||||
[BuiltIn] public struct OneOf { } |
|
||||||
|
|
||||||
// Relationships |
|
||||||
|
|
||||||
[BuiltIn] public struct IsA { } |
|
||||||
[BuiltIn] public struct ChildOf { } |
|
||||||
[BuiltIn] public struct DependsOn { } |
|
||||||
} |
|
@ -0,0 +1,41 @@ |
|||||||
|
using System; |
||||||
|
using static flecs_hub.flecs; |
||||||
|
|
||||||
|
namespace gaemstone.ECS; |
||||||
|
|
||||||
|
public unsafe class IdentifierRef |
||||||
|
: IEquatable<IdentifierRef> |
||||||
|
{ |
||||||
|
public Universe Universe { get; } |
||||||
|
public Identifier ID { get; } |
||||||
|
|
||||||
|
public IdentifierFlags Flags => ID.Flags; |
||||||
|
public bool IsPair => ID.IsPair; |
||||||
|
public bool IsWildcard => ID.IsWildcard; |
||||||
|
public bool IsValid => ecs_id_is_valid(Universe, ID); |
||||||
|
|
||||||
|
public IdentifierRef(Universe universe, Identifier id) |
||||||
|
{ Universe = universe; ID = id; } |
||||||
|
|
||||||
|
public static IdentifierRef Combine(IdentifierFlags flags, IdentifierRef id) |
||||||
|
=> new(id.Universe, Identifier.Combine(flags, id)); |
||||||
|
public static IdentifierRef Pair(EntityRef relation, Entity target) |
||||||
|
=> new(relation.Universe, Identifier.Pair(relation, target)); |
||||||
|
public static IdentifierRef Pair(Entity relation, EntityRef target) |
||||||
|
=> new(target.Universe, Identifier.Pair(relation, target)); |
||||||
|
|
||||||
|
public (EntityRef Relation, EntityRef Target) AsPair() |
||||||
|
=> (Universe.LookupOrThrow(new Entity(new() { Data = (ID.Value & ECS_COMPONENT_MASK) >> 32 })), |
||||||
|
Universe.LookupOrThrow(new Entity(new() { Data = ID.Value & ECS_ENTITY_MASK }))); |
||||||
|
|
||||||
|
public bool Equals(IdentifierRef? other) => (other is not null) && Universe == other.Universe && ID == other.ID; |
||||||
|
public override bool Equals(object? obj) => Equals(obj as IdentifierRef); |
||||||
|
public override int GetHashCode() => HashCode.Combine(Universe, ID); |
||||||
|
public override string? ToString() => ecs_id_str(Universe, this).FlecsToStringAndFree()!; |
||||||
|
|
||||||
|
public static bool operator ==(IdentifierRef? left, IdentifierRef? right) => ReferenceEquals(left, right) || (left?.Equals(right) ?? false); |
||||||
|
public static bool operator !=(IdentifierRef? left, IdentifierRef? right) => !(left == right); |
||||||
|
|
||||||
|
public static implicit operator Identifier(IdentifierRef i) => i.ID; |
||||||
|
public static implicit operator ecs_id_t(IdentifierRef i) => i.ID.Value; |
||||||
|
} |
@ -1,133 +1,25 @@ |
|||||||
using System; |
using System; |
||||||
using System.Collections.Generic; |
|
||||||
using System.Linq; |
|
||||||
using System.Reflection; |
|
||||||
using System.Text.RegularExpressions; |
|
||||||
using gaemstone.Utility; |
|
||||||
|
|
||||||
namespace gaemstone.ECS; |
namespace gaemstone.ECS; |
||||||
|
|
||||||
[AttributeUsage(AttributeTargets.Class)] |
[AttributeUsage(AttributeTargets.Class)] |
||||||
public class ModuleAttribute : Attribute { } |
public class ModuleAttribute : Attribute |
||||||
|
{ |
||||||
|
public string? Name { get; set; } |
||||||
|
} |
||||||
|
|
||||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] |
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] |
||||||
public class DependsOnAttribute : Attribute |
public class DependsOnAttribute : Attribute |
||||||
{ |
{ |
||||||
public Type Target { get; } // TODO: Should probably move to string. |
public string Name { get; } |
||||||
public DependsOnAttribute(Type target) => Target = target; |
|
||||||
} |
|
||||||
|
|
||||||
[Component] |
public DependsOnAttribute(string name) |
||||||
public class Module |
=> Name = name; |
||||||
{ |
public DependsOnAttribute(Type type) |
||||||
public Type Type { get; } |
: this(ModuleManager.GetModuleName(type)) { } |
||||||
public object? Instance { get; internal set; } |
|
||||||
public Module(Type type) => Type = type; |
|
||||||
} |
} |
||||||
|
|
||||||
public static class ModuleExtensions |
public interface IModuleInitializer |
||||||
{ |
{ |
||||||
private static readonly Regex _removeCommon = new("(Module|Components)$"); |
void Initialize(EntityRef entity); |
||||||
// TODO: This wouldn't work with multiple universes. Find a different way to do it. |
|
||||||
private static Query? _disabledModulesQuery; |
|
||||||
private static Rule? _disabledModulesWithDisabledDepsRule; |
|
||||||
|
|
||||||
public static EntityRef RegisterModule<T>(this Universe universe) |
|
||||||
where T : class => universe.RegisterModule(typeof(T)); |
|
||||||
public static EntityRef RegisterModule(this Universe universe, Type type) |
|
||||||
{ |
|
||||||
// TODO: Would be nice to make this more straight-forward, especially for in-module code. |
|
||||||
var Module = universe.Lookup<Module>(); |
|
||||||
var Disabled = universe.Lookup<Flecs.Disabled>(); |
|
||||||
var DependsOn = universe.Lookup<Flecs.DependsOn>(); |
|
||||||
|
|
||||||
if (!type.IsClass || type.IsAbstract) throw new Exception( |
|
||||||
"Module must be a non-abstract and non-static class"); |
|
||||||
if (!type.Has<ModuleAttribute>()) throw new Exception( |
|
||||||
"Module must be marked with ModuleAttribute"); |
|
||||||
|
|
||||||
var hasSimpleCtor = type.GetConstructor(Type.EmptyTypes) != null; |
|
||||||
var hasUniverseCtor = type.GetConstructor(new[] { typeof(Universe) }) != null; |
|
||||||
if (!hasSimpleCtor && !hasUniverseCtor) throw new Exception( |
|
||||||
$"Module {type} must define public new() or new(Universe)"); |
|
||||||
|
|
||||||
static EntityRef LookupOrCreateModule(Universe universe, Type type) |
|
||||||
{ |
|
||||||
var entity = universe.TryLookup(type); |
|
||||||
if (entity.IsValid) return new(universe, entity); |
|
||||||
|
|
||||||
// Using startat: 1, regex shouldn't match strings whose entire name matches. |
|
||||||
if (type.Namespace == null) throw new NotSupportedException("Module must have a namespace"); |
|
||||||
var name = _removeCommon.Replace(type.GetFriendlyName(), "", 1, startat: 1); |
|
||||||
var newEntity = new EntityBuilder(universe, $"{type.Namespace}.{name}") |
|
||||||
// NOTE: Modules should not be accessed by symbol. |
|
||||||
// (Could collide with component symbols.) |
|
||||||
// { Symbol = symbol } |
|
||||||
.Disabled().Build(); |
|
||||||
universe.RegisterLookup(type, newEntity); |
|
||||||
return newEntity; |
|
||||||
} |
|
||||||
|
|
||||||
// Create an entity for the module that is being registered. |
|
||||||
var entity = LookupOrCreateModule(universe, type); |
|
||||||
var dependencies = type.GetMultiple<DependsOnAttribute>() |
|
||||||
.Select(attr => LookupOrCreateModule(universe, attr.Target)); |
|
||||||
foreach (var dep in dependencies) entity.Add(DependsOn, dep); |
|
||||||
entity.Set(new Module(type)); |
|
||||||
|
|
||||||
// Collect all currently disabled modules. |
|
||||||
var disabledModules = new Dictionary<Entity, Module>(); |
|
||||||
_disabledModulesQuery ??= new Query(universe, new( |
|
||||||
"DisabledModulesQuery", |
|
||||||
Module, |
|
||||||
Disabled |
|
||||||
)); |
|
||||||
foreach (var iter in _disabledModulesQuery) { |
|
||||||
var modules = iter.FieldRef<Module>(1); |
|
||||||
for (var i = 0; i < iter.Count; i++) |
|
||||||
disabledModules.Add(iter.Entity(i), modules[i]); |
|
||||||
} |
|
||||||
|
|
||||||
while (true) { |
|
||||||
|
|
||||||
// Collect all modules that can now be enabled. |
|
||||||
var modulesToEnable = new Dictionary<Entity, Module>(disabledModules); |
|
||||||
_disabledModulesWithDisabledDepsRule ??= new Rule(universe, new( |
|
||||||
"DisabledModulesWithDisabledDepsQuery", |
|
||||||
Module, |
|
||||||
Disabled, |
|
||||||
new(DependsOn, "$Dependency"), |
|
||||||
new(Disabled) { Source = "$Dependency" } |
|
||||||
)); |
|
||||||
foreach (var iter in _disabledModulesWithDisabledDepsRule) |
|
||||||
for (var i = 0; i < iter.Count; i++) |
|
||||||
modulesToEnable.Remove(iter.Entity(i)); |
|
||||||
|
|
||||||
if (modulesToEnable.Count == 0) break; |
|
||||||
|
|
||||||
foreach (var (e, c) in modulesToEnable) { |
|
||||||
var module = new EntityRef(universe, e); |
|
||||||
c.Instance = EnableModule(module, c.Type); |
|
||||||
disabledModules.Remove(e); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
return entity; |
|
||||||
} |
|
||||||
|
|
||||||
private static object EnableModule(EntityRef module, Type type) |
|
||||||
{ |
|
||||||
var instance = (type.GetConstructor(Type.EmptyTypes) != null) |
|
||||||
? Activator.CreateInstance(type)! |
|
||||||
: Activator.CreateInstance(type, module.Universe)!; |
|
||||||
|
|
||||||
foreach (var member in type.GetNestedTypes().Concat<MemberInfo>(type.GetMethods())) |
|
||||||
member.GetRegisterableInfo(out var _)? |
|
||||||
.Register(module.Universe, instance, member) |
|
||||||
.Add<Flecs.ChildOf>(module); |
|
||||||
|
|
||||||
module.Remove<Flecs.Disabled>(); |
|
||||||
return instance; |
|
||||||
} |
|
||||||
} |
} |
||||||
|
@ -1,56 +0,0 @@ |
|||||||
using System; |
|
||||||
using System.Collections.Generic; |
|
||||||
using System.Linq; |
|
||||||
using System.Reflection; |
|
||||||
using gaemstone.Utility; |
|
||||||
|
|
||||||
namespace gaemstone.ECS; |
|
||||||
|
|
||||||
// TODO: Make this return an EntityBuilder instead. |
|
||||||
public delegate EntityRef RegisterFunc(Universe universe, object? instance, MemberInfo member); |
|
||||||
|
|
||||||
public class RegisterableInfo |
|
||||||
{ |
|
||||||
public bool? PartOfModule { get; } // true/false = must (not) be part of module; null = doesn't matter |
|
||||||
public RegisterFunc Register { get; } |
|
||||||
public Type[] AllowedWith { get; } |
|
||||||
|
|
||||||
public RegisterableInfo(bool? partOfModule, RegisterFunc register, params Type[] allowedWith) |
|
||||||
{ PartOfModule = partOfModule; Register = register; AllowedWith = allowedWith; } |
|
||||||
} |
|
||||||
|
|
||||||
public static class RegisterableExtensions |
|
||||||
{ |
|
||||||
// Ordered by priority. For example a type with [Component, Relation] |
|
||||||
// will result in a Relation due to being first in the list. |
|
||||||
private static readonly Dictionary<Type, RegisterableInfo> _knownAttributes = new() { |
|
||||||
[typeof(RelationAttribute) ] = new(null, (u, i, m) => u.RegisterRelation ((Type)m), typeof(ComponentAttribute), typeof(TagAttribute)), |
|
||||||
[typeof(ComponentAttribute)] = new(null, (u, i, m) => u.RegisterComponent((Type)m), typeof(EntityAttribute)), |
|
||||||
[typeof(TagAttribute) ] = new(null, (u, i, m) => u.RegisterTag ((Type)m)), |
|
||||||
[typeof(EntityAttribute) ] = new(null, (u, i, m) => u.RegisterEntity ((Type)m)), |
|
||||||
|
|
||||||
[typeof(ModuleAttribute) ] = new(false, (u, i, m) => u.RegisterModule((Type)m)), |
|
||||||
[typeof(SystemAttribute) ] = new(true , (u, i, m) => u.RegisterSystem (i, (MethodInfo)m)), |
|
||||||
[typeof(ObserverAttribute)] = new(true , (u, i, m) => u.RegisterObserver(i, (MethodInfo)m)), |
|
||||||
}; |
|
||||||
|
|
||||||
public static RegisterableInfo? GetRegisterableInfo(this MemberInfo member, out bool isPartOfModule) |
|
||||||
{ |
|
||||||
isPartOfModule = member.DeclaringType?.Has<ModuleAttribute>() == true; |
|
||||||
var matched = _knownAttributes.Where(e => member.GetCustomAttribute(e.Key) != null).ToList(); |
|
||||||
|
|
||||||
if (matched.Count == 0) return null; |
|
||||||
var (type, info) = matched[0]; |
|
||||||
|
|
||||||
var disallowed = matched.Skip(1).Select(a => a.Key).Except(info.AllowedWith); |
|
||||||
if (disallowed.Any()) throw new InvalidOperationException( |
|
||||||
$"{member} marked with {type} may not be used together with " + string.Join(", ", disallowed)); |
|
||||||
|
|
||||||
if (info.PartOfModule == true && !isPartOfModule) throw new InvalidOperationException( |
|
||||||
$"{member} marked with {type} must be part of a module"); |
|
||||||
if (info.PartOfModule == false && isPartOfModule) throw new InvalidOperationException( |
|
||||||
$"{member} marked with {type} must not be part of a module"); |
|
||||||
|
|
||||||
return info; |
|
||||||
} |
|
||||||
} |
|
@ -1,14 +0,0 @@ |
|||||||
using System; |
|
||||||
|
|
||||||
namespace gaemstone.ECS; |
|
||||||
|
|
||||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] |
|
||||||
public class RelationAttribute : Attribute { } |
|
||||||
|
|
||||||
public static class RelationExtensions |
|
||||||
{ |
|
||||||
public static EntityRef RegisterRelation<T>(this Universe universe) |
|
||||||
=> universe.RegisterRelation(typeof(T)); |
|
||||||
public unsafe static EntityRef RegisterRelation(this Universe universe, Type type) |
|
||||||
=> throw new NotImplementedException(); // TODO: Implement me. |
|
||||||
} |
|
@ -1,23 +0,0 @@ |
|||||||
using System; |
|
||||||
using gaemstone.Utility; |
|
||||||
|
|
||||||
namespace gaemstone.ECS; |
|
||||||
|
|
||||||
[AttributeUsage(AttributeTargets.Struct)] |
|
||||||
public class TagAttribute : Attribute { } |
|
||||||
|
|
||||||
public static class TagExtensions |
|
||||||
{ |
|
||||||
public static EntityRef RegisterTag<T>(this Universe universe) |
|
||||||
where T : unmanaged => universe.RegisterTag(typeof(T)); |
|
||||||
public static EntityRef RegisterTag(this Universe universe, Type type) |
|
||||||
{ |
|
||||||
if (!type.IsValueType || type.IsPrimitive || type.GetFields().Length > 0) |
|
||||||
throw new Exception("Tag must be an empty, used-defined struct."); |
|
||||||
var name = type.GetFriendlyName(); |
|
||||||
var entity = new EntityBuilder(universe, name) { Symbol = name } .Build(); |
|
||||||
entity.Add<Flecs.Tag>(); |
|
||||||
universe.RegisterLookup(type, entity); |
|
||||||
return entity; |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,31 @@ |
|||||||
|
using System; |
||||||
|
|
||||||
|
namespace gaemstone.ECS; |
||||||
|
|
||||||
|
// TODO: Make it possible to use [Source] on systems. |
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Parameter)] |
||||||
|
public class SourceAttribute : Attribute |
||||||
|
{ |
||||||
|
public Type Type { get; } |
||||||
|
public SourceAttribute(Type type) => Type = type; |
||||||
|
} |
||||||
|
|
||||||
|
// Parameters types marked with [Tag] are equivalent to [Has]. |
||||||
|
[AttributeUsage(AttributeTargets.Parameter)] |
||||||
|
public class HasAttribute : Attribute { } |
||||||
|
|
||||||
|
// Parameters with "in" modifier are equivalent to [In]. |
||||||
|
[AttributeUsage(AttributeTargets.Parameter)] |
||||||
|
public class InAttribute : Attribute { } |
||||||
|
|
||||||
|
// Parameters with "out" modifier are equivalent to [Out]. |
||||||
|
[AttributeUsage(AttributeTargets.Parameter)] |
||||||
|
public class OutAttribute : Attribute { } |
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Parameter)] |
||||||
|
public class NotAttribute : Attribute { } |
||||||
|
|
||||||
|
// Parameters with nullable syntax are equivalent to [Optional]. |
||||||
|
[AttributeUsage(AttributeTargets.Parameter)] |
||||||
|
public class OptionalAttribute : Attribute { } |
@ -1,62 +1,62 @@ |
|||||||
using System; |
using System; |
||||||
using System.Collections.Generic; |
using System.Collections.Generic; |
||||||
using gaemstone.Utility; |
|
||||||
using static flecs_hub.flecs; |
using static flecs_hub.flecs; |
||||||
|
|
||||||
namespace gaemstone.ECS; |
namespace gaemstone.ECS; |
||||||
|
|
||||||
[AttributeUsage(AttributeTargets.Struct)] |
|
||||||
public class BuiltInAttribute : Attribute |
|
||||||
{ |
|
||||||
public string? Name { get; } |
|
||||||
public uint ID { get; } |
|
||||||
|
|
||||||
internal BuiltInAttribute() { } // Defaults to type's name. |
|
||||||
internal BuiltInAttribute(string name) => Name = name; |
|
||||||
internal BuiltInAttribute(uint id) => ID = id; |
|
||||||
} |
|
||||||
|
|
||||||
public unsafe partial class Universe |
public unsafe partial class Universe |
||||||
{ |
{ |
||||||
private readonly Dictionary<Type, Entity> _lookupByType = new(); |
private readonly Dictionary<Type, Entity> _lookupByType = new(); |
||||||
|
|
||||||
private void RegisterBuiltInLookups() |
|
||||||
{ |
|
||||||
foreach (var type in typeof(Universe).Assembly.GetTypes()) |
|
||||||
if (type.Get<BuiltInAttribute>() is BuiltInAttribute attr) |
|
||||||
RegisterLookup(type, (attr.ID != 0) |
|
||||||
? TryLookup(new Entity(new() { Data = attr.ID })) |
|
||||||
: TryLookup("flecs.core." + (attr.Name ?? type.Name))); |
|
||||||
} |
|
||||||
|
|
||||||
public void RegisterLookup(Type type, Entity entity) |
|
||||||
=> _lookupByType.Add(type, entity.ThrowIfInvalid()); |
|
||||||
public void RemoveLookup(Type type) |
|
||||||
{ if (!_lookupByType.Remove(type)) throw new InvalidOperationException( |
|
||||||
$"Type {type} was not present in lookups"); } |
|
||||||
|
|
||||||
public Entity TryLookup<T>() |
|
||||||
=> TryLookup(typeof(T)); |
|
||||||
public Entity TryLookup(Type type) |
|
||||||
=> _lookupByType.TryGetValue(type, out var e) ? new(e) : default; |
|
||||||
|
|
||||||
public Entity TryLookup(Entity value) |
|
||||||
=> new(ecs_get_alive(this, value)); |
|
||||||
|
|
||||||
public Entity TryLookup(string path) |
public void AddLookupByType(Type type, Entity entity) |
||||||
=> TryLookup(default, path); |
=> _lookupByType.Add(type, entity); |
||||||
public Entity TryLookup(Entity parent, string path) |
public void RemoveLookupByType(Type type) |
||||||
=> new(ecs_lookup_path_w_sep(this, parent, path.FlecsToCString(), ".", default, true)); |
{ if (!_lookupByType.Remove(type)) throw new InvalidOperationException( |
||||||
public Entity TryLookupSymbol(string symbol) |
$"Lookup for {type} does not exist"); } |
||||||
=> new(ecs_lookup_symbol(this, symbol.FlecsToCString(), false)); |
|
||||||
|
private EntityRef? GetOrNull(Entity entity) |
||||||
|
{ var e = new EntityRef(this, entity, false); return e.IsValid ? e : null; } |
||||||
public EntityRef Lookup<T>() => new(this, TryLookup<T>()); |
|
||||||
public EntityRef Lookup(Type type) => new(this, TryLookup(type)); |
public EntityRef? Lookup<T>() |
||||||
|
=> Lookup(typeof(T)); |
||||||
public EntityRef Lookup(Entity entity) => new(this, TryLookup(entity)); |
public EntityRef? Lookup(Type type) |
||||||
|
=> Lookup(_lookupByType.GetValueOrDefault(type)); |
||||||
|
|
||||||
|
public EntityRef? Lookup(Entity value) |
||||||
|
=> GetOrNull(new(ecs_get_alive(this, value))); |
||||||
|
|
||||||
|
public EntityRef? Lookup(string path) |
||||||
|
=> Lookup(default, path); |
||||||
|
public EntityRef? Lookup(Entity parent, string path) |
||||||
|
=> GetOrNull(new(ecs_lookup_path_w_sep( |
||||||
|
this, parent, path.FlecsToCString(), ".", default, true))); |
||||||
|
public EntityRef? LookupSymbol(string symbol) |
||||||
|
=> GetOrNull(new(ecs_lookup_symbol( |
||||||
|
this, symbol.FlecsToCString(), false))); |
||||||
|
|
||||||
|
|
||||||
|
public EntityRef LookupOrThrow<T>() => LookupOrThrow(typeof(T)); |
||||||
|
public EntityRef LookupOrThrow(Type type) => Lookup(type) |
||||||
|
?? throw new EntityNotFoundException($"Entity of type {type} not found"); |
||||||
|
public EntityRef LookupOrThrow(Entity entity) => Lookup(entity) |
||||||
|
?? throw new EntityNotFoundException($"Entity {entity} not alive"); |
||||||
|
public EntityRef LookupOrThrow(string path) => Lookup(default, path) |
||||||
|
?? throw new EntityNotFoundException($"Entity '{path}' not found"); |
||||||
|
public EntityRef LookupOrThrow(Entity parent, string path) => Lookup(parent, path) |
||||||
|
?? throw new EntityNotFoundException($"Child entity of {parent} '{path}' not found"); |
||||||
|
public EntityRef LookupSymbolOrThrow(string symbol) => LookupSymbol(symbol) |
||||||
|
?? throw new EntityNotFoundException($"Entity with symbol '{symbol}' not found"); |
||||||
|
|
||||||
|
|
||||||
|
public class EntityNotFoundException : Exception |
||||||
|
{ public EntityNotFoundException(string message) : base(message) { } } |
||||||
|
} |
||||||
|
|
||||||
public EntityRef Lookup(string path) => new(this, TryLookup(path)); |
public static class LookupExtensions |
||||||
public EntityRef Lookup(Entity parent, string path) => new(this, TryLookup(parent, path)); |
{ |
||||||
public EntityRef LookupSymbol(string symbol) => new(this, TryLookupSymbol(symbol)); |
public static EntityRef CreateLookup<T>(this EntityRef entity) |
||||||
|
=> entity.CreateLookup(typeof(T)); |
||||||
|
public static EntityRef CreateLookup(this EntityRef entity, Type type) |
||||||
|
{ entity.Universe.AddLookupByType(type, entity); return entity; } |
||||||
} |
} |
||||||
|
@ -0,0 +1,188 @@ |
|||||||
|
using System; |
||||||
|
using System.Collections.Generic; |
||||||
|
using gaemstone.Utility; |
||||||
|
using static gaemstone.ECS.BuiltIn; |
||||||
|
|
||||||
|
namespace gaemstone.ECS; |
||||||
|
|
||||||
|
public class ModuleManager |
||||||
|
{ |
||||||
|
private readonly Dictionary<Entity, ModuleInfo> _modules = new(); |
||||||
|
|
||||||
|
public Universe Universe { get; } |
||||||
|
public ModuleManager(Universe universe) |
||||||
|
=> Universe = universe; |
||||||
|
|
||||||
|
internal ModuleInfo? Lookup(Entity entity) |
||||||
|
=> _modules.GetValueOrDefault(entity); |
||||||
|
|
||||||
|
public EntityRef Register<T>() |
||||||
|
where T : class => Register(typeof(T)); |
||||||
|
public EntityRef Register(Type type) |
||||||
|
{ |
||||||
|
if (!type.IsClass || type.IsGenericType || type.IsGenericTypeDefinition) throw new Exception( |
||||||
|
$"Module {type} must be a non-generic class"); |
||||||
|
if (type.Get<ModuleAttribute>() is not ModuleAttribute attr) throw new Exception( |
||||||
|
$"Module {type} must be marked with ModuleAttribute"); |
||||||
|
|
||||||
|
var moduleName = type.Get<ModuleAttribute>()?.Name ?? type.FullName!; |
||||||
|
|
||||||
|
// Check if module type is static. |
||||||
|
if (type.IsAbstract && type.IsSealed) { |
||||||
|
|
||||||
|
// Static modules represent existing modules, as such they don't |
||||||
|
// create entities, only look up existing ones to add type lookups |
||||||
|
// for use with the Lookup(Type) method. |
||||||
|
|
||||||
|
if (attr.Name == null) throw new Exception( |
||||||
|
$"Existing module {type} must have ModuleAttribute.Name set"); |
||||||
|
var entity = Universe.Lookup(attr.Name) ?? throw new Exception( |
||||||
|
$"Existing module {type} with name '{attr.Name}' not found"); |
||||||
|
|
||||||
|
// This implementation is pretty naive. It simply gets all nested |
||||||
|
// types which are tagged with [Entity] attribute or a subtype |
||||||
|
// thereof and creates a lookup mapping. No sanity checking. |
||||||
|
|
||||||
|
foreach (var nested in type.GetNestedTypes()) { |
||||||
|
if (nested.Get<EntityAttribute>() is not EntityAttribute nestedAttr) continue; |
||||||
|
|
||||||
|
var name = nestedAttr.Name ?? nested.Name; |
||||||
|
if (name.Contains('.')) throw new Exception( |
||||||
|
$"EntityAttribute.Name for {type} must not contain a dot (path separator)"); |
||||||
|
|
||||||
|
Universe.LookupOrThrow($"{moduleName}.{name}") |
||||||
|
.CreateLookup(nested); |
||||||
|
} |
||||||
|
|
||||||
|
return entity; |
||||||
|
|
||||||
|
} else { |
||||||
|
|
||||||
|
var name = GetModuleName(type); |
||||||
|
if (Universe.Lookup(name) != null) throw new Exception( |
||||||
|
$"Can't register module {type}: '{type.FullName}' is already in use"); |
||||||
|
|
||||||
|
var module = new ModuleInfo(Universe, type, name); |
||||||
|
_modules.Add(module.Entity, module); |
||||||
|
TryEnableModule(module); |
||||||
|
|
||||||
|
return module.Entity; |
||||||
|
|
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void TryEnableModule(ModuleInfo module) |
||||||
|
{ |
||||||
|
if (module.UnmetDependencies.Count > 0) return; |
||||||
|
module.Enable(); |
||||||
|
|
||||||
|
// Find other modules that might be missing this module as a dependency. |
||||||
|
foreach (var other in _modules.Values) { |
||||||
|
if (!other.IsActive) continue; |
||||||
|
if (!other.UnmetDependencies.Contains(module.Entity)) continue; |
||||||
|
|
||||||
|
// Move the just enabled module from unmet to met depedencies. |
||||||
|
other.UnmetDependencies.Remove(module.Entity); |
||||||
|
other.MetDependencies.Add(module); |
||||||
|
|
||||||
|
TryEnableModule(other); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public static string GetModuleName(Type type) |
||||||
|
{ |
||||||
|
var attr = type.Get<ModuleAttribute>(); |
||||||
|
if (attr == null) throw new ArgumentException( |
||||||
|
$"Module {type} must be marked with ModuleAttribute", nameof(type)); |
||||||
|
return attr.Name ?? type.FullName!; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
internal class ModuleInfo |
||||||
|
{ |
||||||
|
public Universe Universe { get; } |
||||||
|
public Type ModuleType { get; } |
||||||
|
public string ModuleName { get; } |
||||||
|
|
||||||
|
public EntityRef Entity { get; } |
||||||
|
public object? Instance { get; internal set; } |
||||||
|
public bool IsActive => Instance != null; |
||||||
|
|
||||||
|
public HashSet<ModuleInfo> MetDependencies { get; } = new(); |
||||||
|
public HashSet<Entity> UnmetDependencies { get; } = new(); |
||||||
|
|
||||||
|
public ModuleInfo(Universe universe, Type type, string name) |
||||||
|
{ |
||||||
|
Universe = universe; |
||||||
|
ModuleType = type; |
||||||
|
ModuleName = name; |
||||||
|
|
||||||
|
if (ModuleType.IsAbstract || ModuleType.IsSealed) throw new Exception( |
||||||
|
$"Module {ModuleType} must not be abstract or sealed"); |
||||||
|
if (ModuleType.GetConstructor(Type.EmptyTypes) == null) throw new Exception( |
||||||
|
$"Module {ModuleType} must define public parameterless constructor"); |
||||||
|
|
||||||
|
var entity = Universe.New(name).Add<Module>(); |
||||||
|
|
||||||
|
// Add module dependencies from [DependsOn] attributes. |
||||||
|
foreach (var dependsAttr in ModuleType.GetMultiple<DependsOnAttribute>()) { |
||||||
|
var dependency = Universe.Lookup(dependsAttr.Name) ?? |
||||||
|
Universe.New(dependsAttr.Name).Add<Module>().Disable().Build(); |
||||||
|
|
||||||
|
var depModule = Universe.Modules.Lookup(dependency); |
||||||
|
if (depModule?.IsActive == true) MetDependencies.Add(depModule); |
||||||
|
else { UnmetDependencies.Add(dependency); entity.Disable(); } |
||||||
|
|
||||||
|
entity.Add<DependsOn>(dependency); |
||||||
|
} |
||||||
|
|
||||||
|
Entity = entity.Build().CreateLookup(type); |
||||||
|
} |
||||||
|
|
||||||
|
public void Enable() |
||||||
|
{ |
||||||
|
Entity.Enable(); |
||||||
|
Instance = Activator.CreateInstance(ModuleType)!; |
||||||
|
RegisterNestedTypes(); |
||||||
|
(Instance as IModuleInitializer)?.Initialize(Entity); |
||||||
|
RegisterMethods(Instance); |
||||||
|
} |
||||||
|
|
||||||
|
private void RegisterNestedTypes() |
||||||
|
{ |
||||||
|
foreach (var type in ModuleType.GetNestedTypes()) { |
||||||
|
if (type.Get<EntityAttribute>() is not EntityAttribute attr) continue; |
||||||
|
var name = attr.Name ?? type.Name; |
||||||
|
var entity = Universe.New($"{ModuleName}.{name}", name); |
||||||
|
switch (attr) { |
||||||
|
|
||||||
|
case TagAttribute: |
||||||
|
if (!type.IsValueType || type.GetFields().Length > 0) throw new Exception( |
||||||
|
$"Tag {type} must be an empty, used-defined struct."); |
||||||
|
entity.Add<Tag>().Build().CreateLookup(type); |
||||||
|
break; |
||||||
|
|
||||||
|
case ComponentAttribute: |
||||||
|
entity.Build().CreateComponent(type); |
||||||
|
break; |
||||||
|
|
||||||
|
default: |
||||||
|
if (!type.IsValueType || type.GetFields().Length > 0) throw new Exception( |
||||||
|
$"Entity {type} must be an empty, used-defined struct."); |
||||||
|
entity.Build().CreateLookup(type); |
||||||
|
break; |
||||||
|
|
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void RegisterMethods(object? instance) |
||||||
|
{ |
||||||
|
foreach (var method in ModuleType.GetMethods()) { |
||||||
|
if (method.Has<SystemAttribute>()) |
||||||
|
Universe.RegisterSystem(instance, method).ChildOf(Entity); |
||||||
|
if (method.Has<ObserverAttribute>()) |
||||||
|
Universe.RegisterObserver(instance, method).ChildOf(Entity); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -1,43 +1,14 @@ |
|||||||
using System; |
using System.Collections.Generic; |
||||||
using System.Runtime.InteropServices; |
|
||||||
using gaemstone.ECS; |
|
||||||
using static flecs_hub.flecs; |
|
||||||
|
|
||||||
namespace gaemstone.Utility; |
namespace gaemstone.Utility; |
||||||
|
|
||||||
public static class CallbackContextHelper |
public static class CallbackContextHelper |
||||||
{ |
{ |
||||||
public readonly struct CallbackContext |
private static readonly List<object> _contexts = new(); |
||||||
{ |
|
||||||
public Universe Universe { get; } |
|
||||||
public Action<Iterator> Callback { get; } |
|
||||||
|
|
||||||
public CallbackContext(Universe universe, Action<Iterator> callback) |
public static nint Create<T>(T context) where T : notnull |
||||||
{ Universe = universe; Callback = callback; } |
{ _contexts.Add(context); return _contexts.Count; } |
||||||
} |
|
||||||
|
|
||||||
private static readonly object _lock = new(); |
public static T Get<T>(nint context) |
||||||
private static CallbackContext[] _contexts = new CallbackContext[64]; |
=> (T)_contexts[(int)context - 1]; |
||||||
private static int _count = 0; |
|
||||||
|
|
||||||
public static nint Create(Universe universe, Action<Iterator> callback) |
|
||||||
{ |
|
||||||
var data = new CallbackContext(universe, callback); |
|
||||||
lock (_lock) { |
|
||||||
if (++_count >= _contexts.Length) |
|
||||||
Array.Resize(ref _contexts, _count * 2); |
|
||||||
_contexts[_count - 1] = data; |
|
||||||
return _count - 1; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public static CallbackContext Get(nint context) |
|
||||||
=> _contexts[(int)context]; |
|
||||||
|
|
||||||
[UnmanagedCallersOnly] |
|
||||||
internal static unsafe void Callback(ecs_iter_t* iter) |
|
||||||
{ |
|
||||||
var data = Get((nint)iter->binding_ctx); |
|
||||||
data.Callback(new Iterator(data.Universe, null, *iter)); |
|
||||||
} |
|
||||||
} |
} |
||||||
|
@ -0,0 +1,19 @@ |
|||||||
|
using System; |
||||||
|
using System.Runtime.InteropServices; |
||||||
|
|
||||||
|
namespace gaemstone.Utility; |
||||||
|
|
||||||
|
public readonly ref struct SpanToRef<T> |
||||||
|
where T : class
|
||||||
|
{ |
||||||
|
private readonly Span<nint> _span; |
||||||
|
|
||||||
|
public int Length => _span.Length; |
||||||
|
public T? this[int index] => (_span[index] != 0) |
||||||
|
? (T)((GCHandle)_span[index]).Target! : null; |
||||||
|
|
||||||
|
internal SpanToRef(Span<nint> span) => _span = span; |
||||||
|
|
||||||
|
public void Clear() => _span.Clear(); |
||||||
|
public void CopyTo(SpanToRef<T> dest) => _span.CopyTo(dest._span); |
||||||
|
} |
Loading…
Reference in new issue