diff --git a/src/Immersion/ObserverTest.cs b/src/Immersion/ObserverTest.cs new file mode 100644 index 0000000..db1abb4 --- /dev/null +++ b/src/Immersion/ObserverTest.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!"); +} diff --git a/src/Immersion/Program.cs b/src/Immersion/Program.cs index db08a8d..e05756b 100644 --- a/src/Immersion/Program.cs +++ b/src/Immersion/Program.cs @@ -19,7 +19,7 @@ Resources.ResourceAssembly = typeof(Program).Assembly; var window = Window.Create(WindowOptions.Default with { Title = "gæmstone", - Size = new(1280, 720), + Size = new(1280, 720), FramesPerSecond = 60.0, PreferredDepthBufferBits = 24, }); @@ -45,13 +45,15 @@ universe.RegisterModule(); 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>(); universe.RegisterAll(typeof(Chunk).Assembly); +universe.RegisterAll(typeof(Program).Assembly); + universe.Create("MainCamera") .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 }); var heartMesh = MeshManager.Load(universe, "heart.glb"); diff --git a/src/gaemstone.Client/Modules/Input.cs b/src/gaemstone.Client/Modules/Input.cs index b8d60f1..f8d3415 100644 --- a/src/gaemstone.Client/Modules/Input.cs +++ b/src/gaemstone.Client/Modules/Input.cs @@ -35,7 +35,7 @@ public class Input public bool Released; } - [System(Phase.OnLoad)] + [System(SystemPhase.OnLoad)] public static void ProcessInput(GameWindow window, RawInput input, TimeSpan delta) { window.Handle.DoEvents(); diff --git a/src/gaemstone.Client/Modules/Windowing.cs b/src/gaemstone.Client/Modules/Windowing.cs index 82d3345..d40b922 100644 --- a/src/gaemstone.Client/Modules/Windowing.cs +++ b/src/gaemstone.Client/Modules/Windowing.cs @@ -25,7 +25,7 @@ public class Windowing public GameWindow(IWindow handle) => Handle = handle; } - [System(Phase.PreFrame)] + [System(SystemPhase.PreFrame)] public static void ProcessWindow(GameWindow window, Canvas canvas) => canvas.Size = window.Handle.Size; } diff --git a/src/gaemstone/ECS/Entity.cs b/src/gaemstone/ECS/Entity.cs index 87732f6..97cfab3 100644 --- a/src/gaemstone/ECS/Entity.cs +++ b/src/gaemstone/ECS/Entity.cs @@ -8,7 +8,10 @@ using static flecs_hub.flecs; namespace gaemstone.ECS; [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] -public class EntityAttribute : Attribute { } +public class EntityAttribute : Attribute +{ + public uint ID { get; set; } +} public unsafe readonly struct Entity { @@ -134,7 +137,7 @@ public unsafe readonly struct Entity { var comp = Universe.Lookup(); var size = (ulong)Unsafe.SizeOf(); - 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); return this; } @@ -154,7 +157,7 @@ public unsafe readonly struct Entity { var comp = Universe.Lookup(); 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); // FIXME: Handle needs to be freed when component is removed! 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 &(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_entity_t(Entity e) => e.Value; diff --git a/src/gaemstone/ECS/Identifier.cs b/src/gaemstone/ECS/Identifier.cs index fa459ba..7fe35ea 100644 --- a/src/gaemstone/ECS/Identifier.cs +++ b/src/gaemstone/ECS/Identifier.cs @@ -3,25 +3,43 @@ using static flecs_hub.flecs; 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 Universe Universe { 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 bool IsPair => ecs_id_is_pair(Value); - public Identifier(Universe universe, ecs_id_t value) - { Universe = universe; Value = value; } + public Identifier(Universe universe, ecs_id_t id) + { 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) - => 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) - => 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() => (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() // { @@ -34,20 +52,4 @@ public unsafe readonly struct Identifier 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, } diff --git a/src/gaemstone/ECS/Observer.cs b/src/gaemstone/ECS/Observer.cs index 2b03487..d5599a2 100644 --- a/src/gaemstone/ECS/Observer.cs +++ b/src/gaemstone/ECS/Observer.cs @@ -1,18 +1,32 @@ using System; +using static flecs_hub.flecs; namespace gaemstone.ECS; -[AttributeUsage(AttributeTargets.Method)] -public class ObserverAttribute : Attribute +public enum ObserverEvent { - public Event Event { get; } - public ObserverAttribute(Event @event) - => Event = @event; + OnAdd = ECS_HI_COMPONENT_ID + 33, + OnRemove = ECS_HI_COMPONENT_ID + 34, + 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, - OnSet, - OnRemove, + public ObserverEvent Event { get; } + public string? Expression { get; } + + public ObserverAttribute(ObserverEvent @event) + => Event = @event; } diff --git a/src/gaemstone/ECS/Registerable.cs b/src/gaemstone/ECS/Registerable.cs new file mode 100644 index 0000000..e959882 --- /dev/null +++ b/src/gaemstone/ECS/Registerable.cs @@ -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(); } +} + +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() == 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; + } +} diff --git a/src/gaemstone/ECS/System.cs b/src/gaemstone/ECS/System.cs index 05f8da3..eced974 100644 --- a/src/gaemstone/ECS/System.cs +++ b/src/gaemstone/ECS/System.cs @@ -1,15 +1,16 @@ using System; +using static flecs_hub.flecs; namespace gaemstone.ECS; [AttributeUsage(AttributeTargets.Method)] public class SystemAttribute : Attribute { + public SystemPhase Phase { get; set; } public string? Expression { get; set; } - public Phase Phase { get; set; } - public SystemAttribute() : this(Phase.OnUpdate) { } - public SystemAttribute(Phase phase) => Phase = phase; + public SystemAttribute() : this(SystemPhase.OnUpdate) { } + public SystemAttribute(SystemPhase phase) => Phase = phase; } [AttributeUsage(AttributeTargets.Parameter)] @@ -25,22 +26,22 @@ public class HasAttribute : Attribute { } [AttributeUsage(AttributeTargets.Parameter)] public class NotAttribute : Attribute { } -public enum Phase +public enum SystemPhase { - PreFrame, + PreFrame = ECS_HI_COMPONENT_ID + 65, /// /// This phase contains all the systems that load data into your ECS. /// This would be a good place to load keyboard and mouse inputs. /// - OnLoad, + OnLoad = ECS_HI_COMPONENT_ID + 66, /// /// Often the imported data needs to be processed. Maybe you want to associate /// your keypresses with high level actions rather than comparing explicitly /// in your game code if the user pressed the 'K' key. /// - PostLoad, + PostLoad = ECS_HI_COMPONENT_ID + 67, /// /// 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 /// things from the previous frame, etcetera. /// - PreUpdate, + PreUpdate = ECS_HI_COMPONENT_ID + 68, /// /// 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. /// - OnUpdate, + OnUpdate = ECS_HI_COMPONENT_ID + 69, /// /// 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 /// in this phase would be collision detection. /// - OnValidate, + OnValidate = ECS_HI_COMPONENT_ID + 70, /// /// 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 OnValidate phase, /// you may want to move the entities so that they no longer overlap. /// - PostUpdate, + PostUpdate = ECS_HI_COMPONENT_ID + 71, /// /// 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 scene graph. /// - PreStore, + PreStore = ECS_HI_COMPONENT_ID + 72, /// /// 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. /// - OnStore, + OnStore = ECS_HI_COMPONENT_ID + 73, - PostFrame, + PostFrame = ECS_HI_COMPONENT_ID + 74, } diff --git a/src/gaemstone/ECS/Universe+Modules.cs b/src/gaemstone/ECS/Universe+Modules.cs index a6a0be0..889b603 100644 --- a/src/gaemstone/ECS/Universe+Modules.cs +++ b/src/gaemstone/ECS/Universe+Modules.cs @@ -45,8 +45,6 @@ public unsafe partial class Universe { internal readonly Dictionary _modules = new(); internal readonly Dictionary _deferred = new(); - - internal UniverseModules(Universe universe) { } } public class ModuleInfo @@ -59,6 +57,7 @@ public unsafe partial class Universe public IReadOnlyList Tags { get; } public IReadOnlyList Entities { get; } public IReadOnlyList Systems { get; } + public IReadOnlyList Observers { get; } internal ModuleInfo(ModuleBuilder builder) { @@ -67,12 +66,13 @@ public unsafe partial class Universe ? Activator.CreateInstance(builder.Type)! : 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(); - Tags = builder.Tags .Select(Universe.RegisterTag ).ToImmutableList(); - Entities = builder.Entities .Select(Universe.RegisterEntity ).ToImmutableList(); + Tags = builder.Tags .Select(Universe.RegisterTag).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 Tags { get; } public IReadOnlyList Entities { get; } public IReadOnlyList Systems { get; } + public IReadOnlyList Observers { get; } public HashSet UnmetDependencies { get; } internal ModuleBuilder(Universe universe, Type type) { 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()) throw new Exception( "Module must be marked with ModuleAttribute"); @@ -113,18 +114,23 @@ public unsafe partial class Universe var tags = new List(); var entities = new List(); var systems = new List(); - - foreach (var nested in Type.GetNestedTypes()) { - if (nested.Has()) relations.Add(nested); - else if (nested.Has()) components.Add(nested); - else if (nested.Has()) tags.Add(nested); - else if (nested.Has()) entities.Add(nested); + var observers = new List(); + + foreach (var member in Type.GetNestedTypes().Concat(Type.GetMethods())) { + var info = member.GetRegisterableInfo(out var _); + if (info == null) continue; + 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()) - 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( "Module must define at least one ECS related type or method"); @@ -133,6 +139,7 @@ public unsafe partial class Universe Tags = tags.AsReadOnly(); Entities = entities.AsReadOnly(); Systems = systems.AsReadOnly(); + Observers = observers.AsReadOnly(); UnmetDependencies = DependsOn.ToHashSet(); } diff --git a/src/gaemstone/ECS/Universe+Observers.cs b/src/gaemstone/ECS/Universe+Observers.cs new file mode 100644 index 0000000..a9a1d63 --- /dev/null +++ b/src/gaemstone/ECS/Universe+Observers.cs @@ -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 callback, string expression, + ObserverEvent @event, string? name = null) + => RegisterObserver(name ?? callback.Method.Name, expression, @event, new() { expr = expression }, callback); + public ObserverInfo RegisterObserver(Action 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 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() ?? 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)Delegate.CreateDelegate(typeof(Action), 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 + { + internal readonly List _observers = new(); + + // IReadOnlyCollection implementation + public int Count => _observers.Count; + public IEnumerator 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 Callback { get; } + + internal ObserverInfo(Universe universe, Entity entity, string name, string? expression, + ObserverEvent @event, ecs_filter_desc_t filter, Action callback) + { + Universe = universe; + Entity = entity; + + Name = name; + Expression = expression; + + Event = @event; + Filter = filter; + Callback = callback; + } + } +} diff --git a/src/gaemstone/ECS/Universe+Systems.cs b/src/gaemstone/ECS/Universe+Systems.cs index 1266efc..436e1f9 100644 --- a/src/gaemstone/ECS/Universe+Systems.cs +++ b/src/gaemstone/ECS/Universe+Systems.cs @@ -13,19 +13,19 @@ namespace gaemstone.ECS; public unsafe partial class Universe { public SystemInfo RegisterSystem(Action callback, string expression, - Phase? phase = null, string? name = null) - => RegisterSystem(name ?? callback.Method.Name, expression, phase ?? Phase.OnUpdate, new() { expr = expression }, callback); + SystemPhase? phase = null, string? name = null) + => RegisterSystem(name ?? callback.Method.Name, expression, phase ?? SystemPhase.OnUpdate, new() { expr = expression }, callback); public SystemInfo RegisterSystem(Action callback, ecs_filter_desc_t filter, - Phase? phase = null, string? name = null) - => RegisterSystem(name ?? callback.Method.Name, null, phase ?? Phase.OnUpdate, filter, callback); + SystemPhase? phase = null, string? name = null) + => RegisterSystem(name ?? callback.Method.Name, null, phase ?? SystemPhase.OnUpdate, filter, callback); public SystemInfo RegisterSystem(string name, string? expression, - Phase phase, ecs_filter_desc_t filter, Action callback) + SystemPhase phase, ecs_filter_desc_t filter, Action callback) { - var _phase = Systems._phaseLookup[phase]; + var _phase = (ecs_entity_t)(ecs_id_t)(uint)phase; var entityDesc = default(ecs_entity_desc_t); 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; // 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 attr = action.Method.Get(); - var phase = attr?.Phase ?? Phase.OnUpdate; + var phase = attr?.Phase ?? SystemPhase.OnUpdate; if (action is Action iterAction) { if (attr?.Expression == null) throw new Exception( @@ -63,7 +63,7 @@ public unsafe partial class Universe public SystemInfo RegisterSystem(object? instance, MethodInfo method) { var attr = method.Get(); - var phase = attr?.Phase ?? Phase.OnUpdate; + var phase = attr?.Phase ?? SystemPhase.OnUpdate; var param = method.GetParameters(); if (param.Length == 1 && param[0].ParameterType == typeof(Iterator)) { @@ -116,21 +116,6 @@ public unsafe partial class Universe internal readonly List _systems = new(); - internal readonly Dictionary _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 public int Count => _systems.Count; @@ -146,12 +131,12 @@ public unsafe partial class Universe public string Name { get; } public string? Expression { get; } - public Phase Phase { get; } + public SystemPhase Phase { get; } public ecs_filter_desc_t Filter { get; } public Action Callback { get; } internal SystemInfo(Universe universe, Entity entity, string name, string? expression, - Phase phase, ecs_filter_desc_t filter, Action callback) + SystemPhase phase, ecs_filter_desc_t filter, Action callback) { Universe = universe; Entity = entity; diff --git a/src/gaemstone/ECS/Universe.cs b/src/gaemstone/ECS/Universe.cs index a223636..8934f0a 100644 --- a/src/gaemstone/ECS/Universe.cs +++ b/src/gaemstone/ECS/Universe.cs @@ -14,10 +14,6 @@ public struct Game { } [Component] 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 public static ecs_entity_t EcsIsA { get; } = pinvoke_EcsIsA(); 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 UniverseSystems Systems { get; } - public UniverseModules Modules { get; } + public UniverseModules Modules { get; } = new(); + public UniverseSystems Systems { get; } = new(); + public UniverseObservers Observers { get; } = new(); public Universe(string[]? args = null) { @@ -53,9 +50,6 @@ public unsafe partial class Universe Handle = ecs_init(); } - Systems = new(this); - Modules = new(this); - RegisterAll(typeof(Universe).Assembly); } @@ -92,12 +86,16 @@ public unsafe partial class Universe { from ??= Assembly.GetEntryAssembly()!; foreach (var type in from.GetTypes()) { - var isPartOfModule = type.DeclaringType?.Has() == true; - if (type.Has()) { if (!isPartOfModule) RegisterRelation(type); } - else if (type.Has()) { if (!isPartOfModule) RegisterComponent(type); } - else if (type.Has()) { if (!isPartOfModule) RegisterTag(type); } - else if (type.Has()) { if (!isPartOfModule) RegisterEntity(type); } - else if (type.Has()) RegisterModule(type); + var info = type.GetRegisterableInfo(out var isPartOfModule); + if (info == null || isPartOfModule) continue; + switch (info.Kind) { + case RegisterableKind.Entity: RegisterEntity(type); break; + case RegisterableKind.Tag: RegisterTag(type); break; + 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) throw new Exception("Entity must be an empty, used-defined struct."); - var entity = Create(type.GetFriendlyName()); + var id = type.Get()?.ID ?? 0; + var entity = Create(new ecs_entity_desc_t { + name = type.GetFriendlyName(), + id = new() { Data = id }, + }); _byType.Add(type, entity); return entity; }