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.Collections.Generic; |
||||
using System.Linq; |
||||
using System.Reflection; |
||||
using System.Text.RegularExpressions; |
||||
using gaemstone.Utility; |
||||
|
||||
namespace gaemstone.ECS; |
||||
|
||||
[AttributeUsage(AttributeTargets.Class)] |
||||
public class ModuleAttribute : Attribute { } |
||||
public class ModuleAttribute : Attribute |
||||
{ |
||||
public string? Name { get; set; } |
||||
} |
||||
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] |
||||
public class DependsOnAttribute : Attribute |
||||
{ |
||||
public Type Target { get; } // TODO: Should probably move to string. |
||||
public DependsOnAttribute(Type target) => Target = target; |
||||
} |
||||
public string Name { get; } |
||||
|
||||
[Component] |
||||
public class Module |
||||
{ |
||||
public Type Type { get; } |
||||
public object? Instance { get; internal set; } |
||||
public Module(Type type) => Type = type; |
||||
public DependsOnAttribute(string name) |
||||
=> Name = name; |
||||
public DependsOnAttribute(Type type) |
||||
: this(ModuleManager.GetModuleName(type)) { } |
||||
} |
||||
|
||||
public static class ModuleExtensions |
||||
public interface IModuleInitializer |
||||
{ |
||||
private static readonly Regex _removeCommon = new("(Module|Components)$"); |
||||
// 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; |
||||
} |
||||
void Initialize(EntityRef entity); |
||||
} |
||||
|
@ -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.Collections.Generic; |
||||
using gaemstone.Utility; |
||||
using static flecs_hub.flecs; |
||||
|
||||
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 |
||||
{ |
||||
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) |
||||
=> TryLookup(default, path); |
||||
public Entity TryLookup(Entity parent, string path) |
||||
=> new(ecs_lookup_path_w_sep(this, parent, path.FlecsToCString(), ".", default, true)); |
||||
public Entity TryLookupSymbol(string symbol) |
||||
=> new(ecs_lookup_symbol(this, symbol.FlecsToCString(), false)); |
||||
|
||||
|
||||
public EntityRef Lookup<T>() => new(this, TryLookup<T>()); |
||||
public EntityRef Lookup(Type type) => new(this, TryLookup(type)); |
||||
|
||||
public EntityRef Lookup(Entity entity) => new(this, TryLookup(entity)); |
||||
public void AddLookupByType(Type type, Entity entity) |
||||
=> _lookupByType.Add(type, entity); |
||||
public void RemoveLookupByType(Type type) |
||||
{ if (!_lookupByType.Remove(type)) throw new InvalidOperationException( |
||||
$"Lookup for {type} does not exist"); } |
||||
|
||||
private EntityRef? GetOrNull(Entity entity) |
||||
{ var e = new EntityRef(this, entity, false); return e.IsValid ? e : null; } |
||||
|
||||
public EntityRef? Lookup<T>() |
||||
=> Lookup(typeof(T)); |
||||
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 EntityRef Lookup(Entity parent, string path) => new(this, TryLookup(parent, path)); |
||||
public EntityRef LookupSymbol(string symbol) => new(this, TryLookupSymbol(symbol)); |
||||
public static class LookupExtensions |
||||
{ |
||||
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.Runtime.InteropServices; |
||||
using gaemstone.ECS; |
||||
using static flecs_hub.flecs; |
||||
using System.Collections.Generic; |
||||
|
||||
namespace gaemstone.Utility; |
||||
|
||||
public static class CallbackContextHelper |
||||
{ |
||||
public readonly struct CallbackContext |
||||
{ |
||||
public Universe Universe { get; } |
||||
public Action<Iterator> Callback { get; } |
||||
private static readonly List<object> _contexts = new(); |
||||
|
||||
public CallbackContext(Universe universe, Action<Iterator> callback) |
||||
{ Universe = universe; Callback = callback; } |
||||
} |
||||
public static nint Create<T>(T context) where T : notnull |
||||
{ _contexts.Add(context); return _contexts.Count; } |
||||
|
||||
private static readonly object _lock = new(); |
||||
private static CallbackContext[] _contexts = new CallbackContext[64]; |
||||
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)); |
||||
} |
||||
public static T Get<T>(nint context) |
||||
=> (T)_contexts[(int)context - 1]; |
||||
} |
||||
|
@ -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