Implement observers

wip/source-generators
copygirl 2 years ago
parent 36102b3536
commit 12f3ff451c
  1. 14
      src/Immersion/ObserverTest.cs
  2. 8
      src/Immersion/Program.cs
  3. 2
      src/gaemstone.Client/Modules/Input.cs
  4. 2
      src/gaemstone.Client/Modules/Windowing.cs
  5. 10
      src/gaemstone/ECS/Entity.cs
  6. 46
      src/gaemstone/ECS/Identifier.cs
  7. 32
      src/gaemstone/ECS/Observer.cs
  8. 65
      src/gaemstone/ECS/Registerable.cs
  9. 29
      src/gaemstone/ECS/System.cs
  10. 41
      src/gaemstone/ECS/Universe+Modules.cs
  11. 93
      src/gaemstone/ECS/Universe+Observers.cs
  12. 37
      src/gaemstone/ECS/Universe+Systems.cs
  13. 34
      src/gaemstone/ECS/Universe.cs

@ -0,0 +1,14 @@
using System;
using gaemstone.Bloxel;
using gaemstone.Client;
using gaemstone.ECS;
namespace Immersion;
[Module]
public class ObserverModule
{
[Observer(ObserverEvent.OnSet)]
public static void DoObserver(in Chunk chunk, in Mesh _)
=> Console.WriteLine($"Chunk at {chunk.Position} now has a Mesh!");
}

@ -19,7 +19,7 @@ Resources.ResourceAssembly = typeof(Program).Assembly;
var window = Window.Create(WindowOptions.Default with { var window = Window.Create(WindowOptions.Default with {
Title = "gæmstone", Title = "gæmstone",
Size = new(1280, 720), Size = new(1280, 720),
FramesPerSecond = 60.0, FramesPerSecond = 60.0,
PreferredDepthBufferBits = 24, PreferredDepthBufferBits = 24,
}); });
@ -45,13 +45,15 @@ universe.RegisterModule<Renderer>();
game.Set(new RawInput()); game.Set(new RawInput());
// TODO: Find a way to automatically register this chunk storage. // TODO: Find a way to automatically register chunk storage.
universe.RegisterComponent<ChunkPaletteStorage<ecs_entity_t>>(); universe.RegisterComponent<ChunkPaletteStorage<ecs_entity_t>>();
universe.RegisterAll(typeof(Chunk).Assembly); universe.RegisterAll(typeof(Chunk).Assembly);
universe.RegisterAll(typeof(Program).Assembly);
universe.Create("MainCamera") universe.Create("MainCamera")
.Set(Camera.Default3D) .Set(Camera.Default3D)
.Set((GlobalTransform)Matrix4X4.CreateTranslation(0.0F, 2.0F, 0.0F)) .Set((GlobalTransform) Matrix4X4.CreateTranslation(0.0F, 2.0F, 0.0F))
.Set(new CameraController { MouseSensitivity = 12.0F }); .Set(new CameraController { MouseSensitivity = 12.0F });
var heartMesh = MeshManager.Load(universe, "heart.glb"); var heartMesh = MeshManager.Load(universe, "heart.glb");

@ -35,7 +35,7 @@ public class Input
public bool Released; public bool Released;
} }
[System(Phase.OnLoad)] [System(SystemPhase.OnLoad)]
public static void ProcessInput(GameWindow window, RawInput input, TimeSpan delta) public static void ProcessInput(GameWindow window, RawInput input, TimeSpan delta)
{ {
window.Handle.DoEvents(); window.Handle.DoEvents();

@ -25,7 +25,7 @@ public class Windowing
public GameWindow(IWindow handle) => Handle = handle; public GameWindow(IWindow handle) => Handle = handle;
} }
[System(Phase.PreFrame)] [System(SystemPhase.PreFrame)]
public static void ProcessWindow(GameWindow window, Canvas canvas) public static void ProcessWindow(GameWindow window, Canvas canvas)
=> canvas.Size = window.Handle.Size; => canvas.Size = window.Handle.Size;
} }

@ -8,7 +8,10 @@ using static flecs_hub.flecs;
namespace gaemstone.ECS; namespace gaemstone.ECS;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
public class EntityAttribute : Attribute { } public class EntityAttribute : Attribute
{
public uint ID { get; set; }
}
public unsafe readonly struct Entity public unsafe readonly struct Entity
{ {
@ -134,7 +137,7 @@ public unsafe readonly struct Entity
{ {
var comp = Universe.Lookup<T>(); var comp = Universe.Lookup<T>();
var size = (ulong)Unsafe.SizeOf<T>(); var size = (ulong)Unsafe.SizeOf<T>();
ecs_add_id(Universe, this, Universe.ECS_OVERRIDE | comp); ecs_add_id(Universe, this, Identifier.Combine(IdentifierFlags.Override, comp));
fixed (T* ptr = &value) ecs_set_id(Universe, this, comp, size, ptr); fixed (T* ptr = &value) ecs_set_id(Universe, this, comp, size, ptr);
return this; return this;
} }
@ -154,7 +157,7 @@ public unsafe readonly struct Entity
{ {
var comp = Universe.Lookup<T>(); var comp = Universe.Lookup<T>();
var handle = (nint)GCHandle.Alloc(obj); var handle = (nint)GCHandle.Alloc(obj);
ecs_add_id(Universe, this, Universe.ECS_OVERRIDE | comp); ecs_add_id(Universe, this, Identifier.Combine(IdentifierFlags.Override, comp));
ecs_set_id(Universe, this, comp, (ulong)sizeof(nint), &handle); ecs_set_id(Universe, this, comp, (ulong)sizeof(nint), &handle);
// FIXME: Handle needs to be freed when component is removed! // FIXME: Handle needs to be freed when component is removed!
return this; return this;
@ -163,7 +166,6 @@ public unsafe readonly struct Entity
public static Identifier operator &(Entity first, Entity second) => Identifier.Pair(first, second); public static Identifier operator &(Entity first, Entity second) => Identifier.Pair(first, second);
public static Identifier operator &(ecs_entity_t first, Entity second) => Identifier.Pair(first, second); public static Identifier operator &(ecs_entity_t first, Entity second) => Identifier.Pair(first, second);
public static Identifier operator |(ecs_id_t left, Entity right) => new(right.Universe, left | right.Value.Data);
public static implicit operator ecs_id_t(Entity e) => e.Value.Data; public static implicit operator ecs_id_t(Entity e) => e.Value.Data;
public static implicit operator ecs_entity_t(Entity e) => e.Value; public static implicit operator ecs_entity_t(Entity e) => e.Value;

@ -3,25 +3,43 @@ using static flecs_hub.flecs;
namespace gaemstone.ECS; namespace gaemstone.ECS;
[Flags]
public enum IdentifierFlags : ulong
{
Pair = 1ul << 63,
Override = 1ul << 62,
Toggle = 1ul << 61,
Or = 1ul << 60,
And = 1ul << 59,
Not = 1ul << 58,
}
public unsafe readonly struct Identifier public unsafe readonly struct Identifier
{ {
public Universe Universe { get; } public Universe Universe { get; }
public ecs_id_t Value { get; } public ecs_id_t Value { get; }
public bool IsPair => ecs_id_is_pair(Value);
public IdentifierFlags Flags => (IdentifierFlags)(Value.Data & ECS_ID_FLAGS_MASK); public IdentifierFlags Flags => (IdentifierFlags)(Value.Data & ECS_ID_FLAGS_MASK);
public bool IsPair => ecs_id_is_pair(Value);
public Identifier(Universe universe, ecs_id_t value) public Identifier(Universe universe, ecs_id_t id)
{ Universe = universe; Value = value; } { Universe = universe; Value = id; }
public Identifier(Universe universe, ecs_id_t id, IdentifierFlags flags)
: this(universe, Combine(flags, id)) { }
public static ecs_id_t Combine(IdentifierFlags flags, ecs_id_t id)
=> (ulong)flags | id;
public static ecs_id_t Pair(ecs_id_t first, ecs_id_t second)
=> Combine(IdentifierFlags.Pair, (first << 32) + (uint)second);
public static Identifier Pair(Entity first, Entity second) public static Identifier Pair(Entity first, Entity second)
=> new(first.Universe, Universe.ECS_PAIR | ((first.Value.Data << 32) + (uint)second.Value.Data)); => new(first.Universe, Pair((ecs_id_t)first, (ecs_id_t)second));
public static Identifier Pair(ecs_entity_t first, Entity second) public static Identifier Pair(ecs_entity_t first, Entity second)
=> new(second.Universe, Universe.ECS_PAIR | ((first.Data << 32) + (uint)second.Value.Data)); => new(second.Universe, Pair((ecs_id_t)first, (ecs_id_t)second));
public (Entity, Entity) AsPair() public (Entity, Entity) AsPair()
=> (Universe.Lookup((ecs_id_t)((Value & ECS_COMPONENT_MASK) >> 32)), => (Universe.Lookup((ecs_id_t)((Value & ECS_COMPONENT_MASK) >> 32)),
Universe.Lookup((ecs_id_t)(Value & ECS_ENTITY_MASK))); Universe.Lookup((ecs_id_t) (Value & ECS_ENTITY_MASK)));
// public Entity AsComponent() // public Entity AsComponent()
// { // {
@ -34,20 +52,4 @@ public unsafe readonly struct Identifier
public static implicit operator ecs_id_t(Identifier e) => e.Value; public static implicit operator ecs_id_t(Identifier e) => e.Value;
public static Identifier operator |(ecs_id_t left, Identifier right)
=> new(right.Universe, left | right.Value);
public static Identifier operator |(Identifier left, Identifier right)
=> new(left.Universe, left.Value | right.Value);
}
[Flags]
public enum IdentifierFlags : ulong
{
Pair = 1ul << 63,
Override = 1ul << 62,
Toggle = 1ul << 61,
Or = 1ul << 60,
And = 1ul << 59,
Not = 1ul << 58,
} }

@ -1,18 +1,32 @@
using System; using System;
using static flecs_hub.flecs;
namespace gaemstone.ECS; namespace gaemstone.ECS;
[AttributeUsage(AttributeTargets.Method)] public enum ObserverEvent
public class ObserverAttribute : Attribute
{ {
public Event Event { get; } OnAdd = ECS_HI_COMPONENT_ID + 33,
public ObserverAttribute(Event @event) OnRemove = ECS_HI_COMPONENT_ID + 34,
=> Event = @event; OnSet = ECS_HI_COMPONENT_ID + 35,
UnSet = ECS_HI_COMPONENT_ID + 36,
OnDelete = ECS_HI_COMPONENT_ID + 37,
OnCreateTable = ECS_HI_COMPONENT_ID + 38,
OnDeleteTable = ECS_HI_COMPONENT_ID + 39,
OnTableEmpty = ECS_HI_COMPONENT_ID + 40,
OnTableFill = ECS_HI_COMPONENT_ID + 41,
OnCreateTrigger = ECS_HI_COMPONENT_ID + 42,
OnDeleteTrigger = ECS_HI_COMPONENT_ID + 43,
OnDeleteObservable = ECS_HI_COMPONENT_ID + 44,
OnComponentHooks = ECS_HI_COMPONENT_ID + 45,
OnDeleteTarget = ECS_HI_COMPONENT_ID + 46,
} }
public enum Event [AttributeUsage(AttributeTargets.Method)]
public class ObserverAttribute : Attribute
{ {
OnAdd, public ObserverEvent Event { get; }
OnSet, public string? Expression { get; }
OnRemove,
public ObserverAttribute(ObserverEvent @event)
=> Event = @event;
} }

@ -0,0 +1,65 @@
using System;
using System.Linq;
using System.Reflection;
using gaemstone.Utility;
namespace gaemstone.ECS;
public enum RegisterableKind
{
Entity,
Tag,
Component,
Relation,
System,
Observer,
Module,
}
public class RegisterableInfo
{
public Type Type { get; }
public RegisterableKind Kind { get; }
public bool? PartOfModule { get; }
internal Type[] AllowedWith { get; }
internal RegisterableInfo(Type type, RegisterableKind kind, bool? partOfModule, Type[]? allowedWith = null)
{ Type = type; Kind = kind; PartOfModule = partOfModule; AllowedWith = allowedWith ?? Array.Empty<Type>(); }
}
public static class RegisterableExtensions
{
// These are ordered by priority. For example a type marked with [Component, Relation]
// will result in RegisterableKind.Relation due to being first in the list.
private static readonly RegisterableInfo[] _knownAttributes = new RegisterableInfo[] {
new(typeof(RelationAttribute) , RegisterableKind.Relation , null, new[] { typeof(ComponentAttribute), typeof(TagAttribute) }),
new(typeof(ComponentAttribute) , RegisterableKind.Component , null, new[] { typeof(EntityAttribute) }),
new(typeof(TagAttribute) , RegisterableKind.Tag , null),
new(typeof(EntityAttribute) , RegisterableKind.Entity , null),
new(typeof(ModuleAttribute) , RegisterableKind.Module , false),
new(typeof(SystemAttribute) , RegisterableKind.System , true),
new(typeof(ObserverAttribute) , RegisterableKind.Observer , true),
};
public static RegisterableInfo? GetRegisterableInfo(this MemberInfo member, out bool isPartOfModule)
{
isPartOfModule = member.DeclaringType?.Has<ModuleAttribute>() == true;
var matched = _knownAttributes.Where(a => member.GetCustomAttribute(a.Type) != null).ToList();
if (matched.Count == 0) return null;
var attr = matched[0];
var disallowed = matched.Except(new[] { attr }).Select(a => a.Type).Except(attr.AllowedWith);
if (disallowed.Any()) throw new InvalidOperationException(
$"{member} marked with {attr.Type} may not be used together with " + string.Join(", ", disallowed));
if (attr.PartOfModule == true && !isPartOfModule) throw new InvalidOperationException(
$"{member} marked with {attr.Type} must be part of a module");
if (attr.PartOfModule == false && isPartOfModule) throw new InvalidOperationException(
$"{member} marked with {attr.Type} must not be part of a module");
return attr;
}
}

@ -1,15 +1,16 @@
using System; using System;
using static flecs_hub.flecs;
namespace gaemstone.ECS; namespace gaemstone.ECS;
[AttributeUsage(AttributeTargets.Method)] [AttributeUsage(AttributeTargets.Method)]
public class SystemAttribute : Attribute public class SystemAttribute : Attribute
{ {
public SystemPhase Phase { get; set; }
public string? Expression { get; set; } public string? Expression { get; set; }
public Phase Phase { get; set; }
public SystemAttribute() : this(Phase.OnUpdate) { } public SystemAttribute() : this(SystemPhase.OnUpdate) { }
public SystemAttribute(Phase phase) => Phase = phase; public SystemAttribute(SystemPhase phase) => Phase = phase;
} }
[AttributeUsage(AttributeTargets.Parameter)] [AttributeUsage(AttributeTargets.Parameter)]
@ -25,22 +26,22 @@ public class HasAttribute : Attribute { }
[AttributeUsage(AttributeTargets.Parameter)] [AttributeUsage(AttributeTargets.Parameter)]
public class NotAttribute : Attribute { } public class NotAttribute : Attribute { }
public enum Phase public enum SystemPhase
{ {
PreFrame, PreFrame = ECS_HI_COMPONENT_ID + 65,
/// <summary> /// <summary>
/// This phase contains all the systems that load data into your ECS. /// This phase contains all the systems that load data into your ECS.
/// This would be a good place to load keyboard and mouse inputs. /// This would be a good place to load keyboard and mouse inputs.
/// </summary> /// </summary>
OnLoad, OnLoad = ECS_HI_COMPONENT_ID + 66,
/// <summary> /// <summary>
/// Often the imported data needs to be processed. Maybe you want to associate /// Often the imported data needs to be processed. Maybe you want to associate
/// your keypresses with high level actions rather than comparing explicitly /// your keypresses with high level actions rather than comparing explicitly
/// in your game code if the user pressed the 'K' key. /// in your game code if the user pressed the 'K' key.
/// </summary> /// </summary>
PostLoad, PostLoad = ECS_HI_COMPONENT_ID + 67,
/// <summary> /// <summary>
/// Now that the input is loaded and processed, it's time to get ready to /// Now that the input is loaded and processed, it's time to get ready to
@ -49,13 +50,13 @@ public enum Phase
/// This can be a good place to prepare the frame, maybe clean up some /// This can be a good place to prepare the frame, maybe clean up some
/// things from the previous frame, etcetera. /// things from the previous frame, etcetera.
/// </summary> /// </summary>
PreUpdate, PreUpdate = ECS_HI_COMPONENT_ID + 68,
/// <summary> /// <summary>
/// This is usually where the magic happens! This is where you put all of /// This is usually where the magic happens! This is where you put all of
/// your gameplay systems. By default systems are added to this phase. /// your gameplay systems. By default systems are added to this phase.
/// </summary> /// </summary>
OnUpdate, OnUpdate = ECS_HI_COMPONENT_ID + 69,
/// <summary> /// <summary>
/// This phase was introduced to deal with validating the state of the game /// This phase was introduced to deal with validating the state of the game
@ -64,7 +65,7 @@ public enum Phase
/// This phase is for righting that wrong. A typical feature to implement /// This phase is for righting that wrong. A typical feature to implement
/// in this phase would be collision detection. /// in this phase would be collision detection.
/// </summary> /// </summary>
OnValidate, OnValidate = ECS_HI_COMPONENT_ID + 70,
/// <summary> /// <summary>
/// When your game logic has been updated, and your validation pass has ran, /// When your game logic has been updated, and your validation pass has ran,
@ -72,7 +73,7 @@ public enum Phase
/// detection system detected collisions in the <c>OnValidate</c> phase, /// detection system detected collisions in the <c>OnValidate</c> phase,
/// you may want to move the entities so that they no longer overlap. /// you may want to move the entities so that they no longer overlap.
/// </summary> /// </summary>
PostUpdate, PostUpdate = ECS_HI_COMPONENT_ID + 71,
/// <summary> /// <summary>
/// Now that all of the frame data is computed, validated and corrected for, /// Now that all of the frame data is computed, validated and corrected for,
@ -81,13 +82,13 @@ public enum Phase
/// A good example would be a system that calculates transform matrices from /// A good example would be a system that calculates transform matrices from
/// a scene graph. /// a scene graph.
/// </summary> /// </summary>
PreStore, PreStore = ECS_HI_COMPONENT_ID + 72,
/// <summary> /// <summary>
/// This is where it all comes together. Your frame is ready to be /// This is where it all comes together. Your frame is ready to be
/// rendered, and that is exactly what you would do in this phase. /// rendered, and that is exactly what you would do in this phase.
/// </summary> /// </summary>
OnStore, OnStore = ECS_HI_COMPONENT_ID + 73,
PostFrame, PostFrame = ECS_HI_COMPONENT_ID + 74,
} }

@ -45,8 +45,6 @@ public unsafe partial class Universe
{ {
internal readonly Dictionary<Type, ModuleInfo> _modules = new(); internal readonly Dictionary<Type, ModuleInfo> _modules = new();
internal readonly Dictionary<Type, ModuleBuilder> _deferred = new(); internal readonly Dictionary<Type, ModuleBuilder> _deferred = new();
internal UniverseModules(Universe universe) { }
} }
public class ModuleInfo public class ModuleInfo
@ -59,6 +57,7 @@ public unsafe partial class Universe
public IReadOnlyList<Entity> Tags { get; } public IReadOnlyList<Entity> Tags { get; }
public IReadOnlyList<Entity> Entities { get; } public IReadOnlyList<Entity> Entities { get; }
public IReadOnlyList<SystemInfo> Systems { get; } public IReadOnlyList<SystemInfo> Systems { get; }
public IReadOnlyList<ObserverInfo> Observers { get; }
internal ModuleInfo(ModuleBuilder builder) internal ModuleInfo(ModuleBuilder builder)
{ {
@ -67,12 +66,13 @@ public unsafe partial class Universe
? Activator.CreateInstance(builder.Type)! ? Activator.CreateInstance(builder.Type)!
: Activator.CreateInstance(builder.Type, Universe)!; : Activator.CreateInstance(builder.Type, Universe)!;
Relations = builder.Relations .Select(Universe.RegisterRelation ).ToImmutableList(); Relations = builder.Relations .Select(Universe.RegisterRelation).ToImmutableList();
Components = builder.Components.Select(Universe.RegisterComponent).ToImmutableList(); Components = builder.Components.Select(Universe.RegisterComponent).ToImmutableList();
Tags = builder.Tags .Select(Universe.RegisterTag ).ToImmutableList(); Tags = builder.Tags .Select(Universe.RegisterTag).ToImmutableList();
Entities = builder.Entities .Select(Universe.RegisterEntity ).ToImmutableList(); Entities = builder.Entities .Select(Universe.RegisterEntity).ToImmutableList();
Systems = builder.Systems.Select(s => Universe.RegisterSystem(Instance, s)).ToImmutableList(); Systems = builder.Systems .Select(s => Universe.RegisterSystem(Instance, s)).ToImmutableList();
Observers = builder.Observers.Select(s => Universe.RegisterObserver(Instance, s)).ToImmutableList();
} }
} }
@ -88,13 +88,14 @@ public unsafe partial class Universe
public IReadOnlyList<Type> Tags { get; } public IReadOnlyList<Type> Tags { get; }
public IReadOnlyList<Type> Entities { get; } public IReadOnlyList<Type> Entities { get; }
public IReadOnlyList<MethodInfo> Systems { get; } public IReadOnlyList<MethodInfo> Systems { get; }
public IReadOnlyList<MethodInfo> Observers { get; }
public HashSet<Type> UnmetDependencies { get; } public HashSet<Type> UnmetDependencies { get; }
internal ModuleBuilder(Universe universe, Type type) internal ModuleBuilder(Universe universe, Type type)
{ {
if (!type.IsClass || type.IsAbstract) throw new Exception( if (!type.IsClass || type.IsAbstract) throw new Exception(
"Module must be a non-abstract class"); "Module must be a non-abstract and non-static class");
if (!type.Has<ModuleAttribute>()) throw new Exception( if (!type.Has<ModuleAttribute>()) throw new Exception(
"Module must be marked with ModuleAttribute"); "Module must be marked with ModuleAttribute");
@ -113,18 +114,23 @@ public unsafe partial class Universe
var tags = new List<Type>(); var tags = new List<Type>();
var entities = new List<Type>(); var entities = new List<Type>();
var systems = new List<MethodInfo>(); var systems = new List<MethodInfo>();
var observers = new List<MethodInfo>();
foreach (var nested in Type.GetNestedTypes()) {
if (nested.Has<RelationAttribute>()) relations.Add(nested); foreach (var member in Type.GetNestedTypes().Concat<MemberInfo>(Type.GetMethods())) {
else if (nested.Has<ComponentAttribute>()) components.Add(nested); var info = member.GetRegisterableInfo(out var _);
else if (nested.Has<TagAttribute>()) tags.Add(nested); if (info == null) continue;
else if (nested.Has<EntityAttribute>()) entities.Add(nested); switch (info.Kind) {
case RegisterableKind.Entity: entities.Add((Type)member); break;
case RegisterableKind.Tag: tags.Add((Type)member); break;
case RegisterableKind.Component: components.Add((Type)member); break;
case RegisterableKind.Relation: relations.Add((Type)member); break;
case RegisterableKind.System: systems.Add((MethodInfo)member); break;
case RegisterableKind.Observer: observers.Add((MethodInfo)member); break;
default: throw new InvalidOperationException();
}
} }
foreach (var method in Type.GetMethods())
if (method.Has<SystemAttribute>())
systems.Add(method);
var elements = new IList[] { relations, components, tags, entities, systems }; var elements = new IList[] { entities, tags, components, relations, systems, observers };
if (elements.Sum(l => l.Count) == 0) throw new Exception( if (elements.Sum(l => l.Count) == 0) throw new Exception(
"Module must define at least one ECS related type or method"); "Module must define at least one ECS related type or method");
@ -133,6 +139,7 @@ public unsafe partial class Universe
Tags = tags.AsReadOnly(); Tags = tags.AsReadOnly();
Entities = entities.AsReadOnly(); Entities = entities.AsReadOnly();
Systems = systems.AsReadOnly(); Systems = systems.AsReadOnly();
Observers = observers.AsReadOnly();
UnmetDependencies = DependsOn.ToHashSet(); UnmetDependencies = DependsOn.ToHashSet();
} }

@ -0,0 +1,93 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using gaemstone.Utility;
using gaemstone.Utility.IL;
using static flecs_hub.flecs;
namespace gaemstone.ECS;
public unsafe partial class Universe
{
public ObserverInfo RegisterObserver(Action<Iterator> callback, string expression,
ObserverEvent @event, string? name = null)
=> RegisterObserver(name ?? callback.Method.Name, expression, @event, new() { expr = expression }, callback);
public ObserverInfo RegisterObserver(Action<Iterator> callback, ecs_filter_desc_t filter,
ObserverEvent @event, string? name = null)
=> RegisterObserver(name ?? callback.Method.Name, null, @event, filter, callback);
public ObserverInfo RegisterObserver(string name, string? expression,
ObserverEvent @event, ecs_filter_desc_t filter, Action<Iterator> callback)
{
var observerDesc = default(ecs_observer_desc_t);
observerDesc.filter = filter;
observerDesc.events[0] = (ecs_entity_t)(ecs_id_t)(uint)@event;
observerDesc.binding_ctx = (void*)UniverseSystems.CreateSystemCallbackContext(this, callback);
observerDesc.callback.Data.Pointer = &UniverseSystems.SystemCallback;
observerDesc.entity = Create(name);
var entity = new Entity(this, ecs_observer_init(Handle, &observerDesc));
var observer = new ObserverInfo(this, entity, name, expression, @event, filter, callback);
Observers._observers.Add(observer);
return observer;
}
public ObserverInfo RegisterObserver(object? instance, MethodInfo method)
{
var attr = method.Get<ObserverAttribute>() ?? throw new ArgumentException(
"Observer must specify ObserverAttribute", nameof(method));
var param = method.GetParameters();
if (param.Length == 1 && param[0].ParameterType == typeof(Iterator)) {
if (attr.Expression == null) throw new Exception(
"Observer must specify expression in ObserverAttribute");
var action = (Action<Iterator>)Delegate.CreateDelegate(typeof(Action<Iterator>), instance, method);
return RegisterObserver(method.Name, attr.Expression, attr.Event,
new() { expr = attr.Expression }, action);
} else {
var gen = QueryActionGenerator.GetOrBuild(this, method);
var filter = (attr.Expression == null) ? gen.Filter : new() { expr = attr.Expression };
return RegisterObserver(method.Name, attr.Expression, attr.Event,
filter, iter => gen.RunWithTryCatch(instance, iter));
}
}
public class UniverseObservers
: IReadOnlyCollection<ObserverInfo>
{
internal readonly List<ObserverInfo> _observers = new();
// IReadOnlyCollection implementation
public int Count => _observers.Count;
public IEnumerator<ObserverInfo> GetEnumerator() => _observers.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
public class ObserverInfo
{
public Universe Universe { get; }
public Entity Entity { get; }
public string Name { get; }
public string? Expression { get; }
public ObserverEvent Event { get; }
public ecs_filter_desc_t Filter { get; }
public Action<Iterator> Callback { get; }
internal ObserverInfo(Universe universe, Entity entity, string name, string? expression,
ObserverEvent @event, ecs_filter_desc_t filter, Action<Iterator> callback)
{
Universe = universe;
Entity = entity;
Name = name;
Expression = expression;
Event = @event;
Filter = filter;
Callback = callback;
}
}
}

@ -13,19 +13,19 @@ namespace gaemstone.ECS;
public unsafe partial class Universe public unsafe partial class Universe
{ {
public SystemInfo RegisterSystem(Action<Iterator> callback, string expression, public SystemInfo RegisterSystem(Action<Iterator> callback, string expression,
Phase? phase = null, string? name = null) SystemPhase? phase = null, string? name = null)
=> RegisterSystem(name ?? callback.Method.Name, expression, phase ?? Phase.OnUpdate, new() { expr = expression }, callback); => RegisterSystem(name ?? callback.Method.Name, expression, phase ?? SystemPhase.OnUpdate, new() { expr = expression }, callback);
public SystemInfo RegisterSystem(Action<Iterator> callback, ecs_filter_desc_t filter, public SystemInfo RegisterSystem(Action<Iterator> callback, ecs_filter_desc_t filter,
Phase? phase = null, string? name = null) SystemPhase? phase = null, string? name = null)
=> RegisterSystem(name ?? callback.Method.Name, null, phase ?? Phase.OnUpdate, filter, callback); => RegisterSystem(name ?? callback.Method.Name, null, phase ?? SystemPhase.OnUpdate, filter, callback);
public SystemInfo RegisterSystem(string name, string? expression, public SystemInfo RegisterSystem(string name, string? expression,
Phase phase, ecs_filter_desc_t filter, Action<Iterator> callback) SystemPhase phase, ecs_filter_desc_t filter, Action<Iterator> callback)
{ {
var _phase = Systems._phaseLookup[phase]; var _phase = (ecs_entity_t)(ecs_id_t)(uint)phase;
var entityDesc = default(ecs_entity_desc_t); var entityDesc = default(ecs_entity_desc_t);
entityDesc.name = name; entityDesc.name = name;
entityDesc.add[0] = !_phase.IsNone ? (EcsDependsOn & _phase) : default; entityDesc.add[0] = _phase.Data != 0 ? Identifier.Pair(EcsDependsOn, _phase) : default;
entityDesc.add[1] = _phase; entityDesc.add[1] = _phase;
// TODO: Provide a nice way to create these entity descriptors. // TODO: Provide a nice way to create these entity descriptors.
@ -45,7 +45,7 @@ public unsafe partial class Universe
{ {
var name = action.Method.Name; var name = action.Method.Name;
var attr = action.Method.Get<SystemAttribute>(); var attr = action.Method.Get<SystemAttribute>();
var phase = attr?.Phase ?? Phase.OnUpdate; var phase = attr?.Phase ?? SystemPhase.OnUpdate;
if (action is Action<Iterator> iterAction) { if (action is Action<Iterator> iterAction) {
if (attr?.Expression == null) throw new Exception( if (attr?.Expression == null) throw new Exception(
@ -63,7 +63,7 @@ public unsafe partial class Universe
public SystemInfo RegisterSystem(object? instance, MethodInfo method) public SystemInfo RegisterSystem(object? instance, MethodInfo method)
{ {
var attr = method.Get<SystemAttribute>(); var attr = method.Get<SystemAttribute>();
var phase = attr?.Phase ?? Phase.OnUpdate; var phase = attr?.Phase ?? SystemPhase.OnUpdate;
var param = method.GetParameters(); var param = method.GetParameters();
if (param.Length == 1 && param[0].ParameterType == typeof(Iterator)) { if (param.Length == 1 && param[0].ParameterType == typeof(Iterator)) {
@ -116,21 +116,6 @@ public unsafe partial class Universe
internal readonly List<SystemInfo> _systems = new(); internal readonly List<SystemInfo> _systems = new();
internal readonly Dictionary<Phase, Entity> _phaseLookup = new();
internal UniverseSystems(Universe universe)
{
_phaseLookup.Add(Phase.PreFrame, new(universe, pinvoke_EcsPreFrame()));
_phaseLookup.Add(Phase.OnLoad, new(universe, pinvoke_EcsOnLoad()));
_phaseLookup.Add(Phase.PostLoad, new(universe, pinvoke_EcsPostLoad()));
_phaseLookup.Add(Phase.PreUpdate, new(universe, pinvoke_EcsPreUpdate()));
_phaseLookup.Add(Phase.OnUpdate, new(universe, pinvoke_EcsOnUpdate()));
_phaseLookup.Add(Phase.OnValidate, new(universe, pinvoke_EcsOnValidate()));
_phaseLookup.Add(Phase.PostUpdate, new(universe, pinvoke_EcsPostUpdate()));
_phaseLookup.Add(Phase.PreStore, new(universe, pinvoke_EcsPreStore()));
_phaseLookup.Add(Phase.OnStore, new(universe, pinvoke_EcsOnStore()));
_phaseLookup.Add(Phase.PostFrame, new(universe, pinvoke_EcsPostFrame()));
}
// IReadOnlyCollection implementation // IReadOnlyCollection implementation
public int Count => _systems.Count; public int Count => _systems.Count;
@ -146,12 +131,12 @@ public unsafe partial class Universe
public string Name { get; } public string Name { get; }
public string? Expression { get; } public string? Expression { get; }
public Phase Phase { get; } public SystemPhase Phase { get; }
public ecs_filter_desc_t Filter { get; } public ecs_filter_desc_t Filter { get; }
public Action<Iterator> Callback { get; } public Action<Iterator> Callback { get; }
internal SystemInfo(Universe universe, Entity entity, string name, string? expression, internal SystemInfo(Universe universe, Entity entity, string name, string? expression,
Phase phase, ecs_filter_desc_t filter, Action<Iterator> callback) SystemPhase phase, ecs_filter_desc_t filter, Action<Iterator> callback)
{ {
Universe = universe; Universe = universe;
Entity = entity; Entity = entity;

@ -14,10 +14,6 @@ public struct Game { }
[Component] [Component]
public unsafe partial class Universe public unsafe partial class Universe
{ {
// Roles
public static ecs_id_t ECS_PAIR { get; } = pinvoke_ECS_PAIR();
public static ecs_id_t ECS_OVERRIDE { get; } = pinvoke_ECS_OVERRIDE();
// Relationships // Relationships
public static ecs_entity_t EcsIsA { get; } = pinvoke_EcsIsA(); public static ecs_entity_t EcsIsA { get; } = pinvoke_EcsIsA();
public static ecs_entity_t EcsDependsOn { get; } = pinvoke_EcsDependsOn(); public static ecs_entity_t EcsDependsOn { get; } = pinvoke_EcsDependsOn();
@ -32,8 +28,9 @@ public unsafe partial class Universe
public ecs_world_t* Handle { get; } public ecs_world_t* Handle { get; }
public UniverseSystems Systems { get; } public UniverseModules Modules { get; } = new();
public UniverseModules Modules { get; } public UniverseSystems Systems { get; } = new();
public UniverseObservers Observers { get; } = new();
public Universe(string[]? args = null) public Universe(string[]? args = null)
{ {
@ -53,9 +50,6 @@ public unsafe partial class Universe
Handle = ecs_init(); Handle = ecs_init();
} }
Systems = new(this);
Modules = new(this);
RegisterAll(typeof(Universe).Assembly); RegisterAll(typeof(Universe).Assembly);
} }
@ -92,12 +86,16 @@ public unsafe partial class Universe
{ {
from ??= Assembly.GetEntryAssembly()!; from ??= Assembly.GetEntryAssembly()!;
foreach (var type in from.GetTypes()) { foreach (var type in from.GetTypes()) {
var isPartOfModule = type.DeclaringType?.Has<ModuleAttribute>() == true; var info = type.GetRegisterableInfo(out var isPartOfModule);
if (type.Has<RelationAttribute>()) { if (!isPartOfModule) RegisterRelation(type); } if (info == null || isPartOfModule) continue;
else if (type.Has<ComponentAttribute>()) { if (!isPartOfModule) RegisterComponent(type); } switch (info.Kind) {
else if (type.Has<TagAttribute>()) { if (!isPartOfModule) RegisterTag(type); } case RegisterableKind.Entity: RegisterEntity(type); break;
else if (type.Has<EntityAttribute>()) { if (!isPartOfModule) RegisterEntity(type); } case RegisterableKind.Tag: RegisterTag(type); break;
else if (type.Has<ModuleAttribute>()) RegisterModule(type); case RegisterableKind.Component: RegisterComponent(type); break;
case RegisterableKind.Relation: RegisterRelation(type); break;
case RegisterableKind.Module: RegisterModule(type); break;
default: throw new InvalidOperationException();
}
} }
} }
@ -163,7 +161,11 @@ public unsafe partial class Universe
{ {
if (!type.IsValueType || type.IsPrimitive || type.GetFields().Length > 0) if (!type.IsValueType || type.IsPrimitive || type.GetFields().Length > 0)
throw new Exception("Entity must be an empty, used-defined struct."); throw new Exception("Entity must be an empty, used-defined struct.");
var entity = Create(type.GetFriendlyName()); var id = type.Get<EntityAttribute>()?.ID ?? 0;
var entity = Create(new ecs_entity_desc_t {
name = type.GetFriendlyName(),
id = new() { Data = id },
});
_byType.Add(type, entity); _byType.Add(type, entity);
return entity; return entity;
} }

Loading…
Cancel
Save