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 {
Title = "gæmstone",
Size = new(1280, 720),
Size = new(1280, 720),
FramesPerSecond = 60.0,
PreferredDepthBufferBits = 24,
});
@ -45,13 +45,15 @@ universe.RegisterModule<Renderer>();
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.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");

@ -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();

@ -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;
}

@ -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<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);
return this;
}
@ -154,7 +157,7 @@ public unsafe readonly struct Entity
{
var comp = Universe.Lookup<T>();
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;

@ -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,
}

@ -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;
}

@ -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 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,
/// <summary>
/// This phase contains all the systems that load data into your ECS.
/// This would be a good place to load keyboard and mouse inputs.
/// </summary>
OnLoad,
OnLoad = ECS_HI_COMPONENT_ID + 66,
/// <summary>
/// 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.
/// </summary>
PostLoad,
PostLoad = ECS_HI_COMPONENT_ID + 67,
/// <summary>
/// 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.
/// </summary>
PreUpdate,
PreUpdate = ECS_HI_COMPONENT_ID + 68,
/// <summary>
/// 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.
/// </summary>
OnUpdate,
OnUpdate = ECS_HI_COMPONENT_ID + 69,
/// <summary>
/// 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.
/// </summary>
OnValidate,
OnValidate = ECS_HI_COMPONENT_ID + 70,
/// <summary>
/// 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,
/// you may want to move the entities so that they no longer overlap.
/// </summary>
PostUpdate,
PostUpdate = ECS_HI_COMPONENT_ID + 71,
/// <summary>
/// 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.
/// </summary>
PreStore,
PreStore = ECS_HI_COMPONENT_ID + 72,
/// <summary>
/// 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.
/// </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, ModuleBuilder> _deferred = new();
internal UniverseModules(Universe universe) { }
}
public class ModuleInfo
@ -59,6 +57,7 @@ public unsafe partial class Universe
public IReadOnlyList<Entity> Tags { get; }
public IReadOnlyList<Entity> Entities { get; }
public IReadOnlyList<SystemInfo> Systems { get; }
public IReadOnlyList<ObserverInfo> 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<Type> Tags { get; }
public IReadOnlyList<Type> Entities { get; }
public IReadOnlyList<MethodInfo> Systems { get; }
public IReadOnlyList<MethodInfo> Observers { get; }
public HashSet<Type> 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<ModuleAttribute>()) throw new Exception(
"Module must be marked with ModuleAttribute");
@ -113,18 +114,23 @@ public unsafe partial class Universe
var tags = new List<Type>();
var entities = new List<Type>();
var systems = new List<MethodInfo>();
foreach (var nested in Type.GetNestedTypes()) {
if (nested.Has<RelationAttribute>()) relations.Add(nested);
else if (nested.Has<ComponentAttribute>()) components.Add(nested);
else if (nested.Has<TagAttribute>()) tags.Add(nested);
else if (nested.Has<EntityAttribute>()) entities.Add(nested);
var observers = new List<MethodInfo>();
foreach (var member in Type.GetNestedTypes().Concat<MemberInfo>(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<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(
"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();
}

@ -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 SystemInfo RegisterSystem(Action<Iterator> 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<Iterator> 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<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);
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<SystemAttribute>();
var phase = attr?.Phase ?? Phase.OnUpdate;
var phase = attr?.Phase ?? SystemPhase.OnUpdate;
if (action is Action<Iterator> 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<SystemAttribute>();
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<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
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<Iterator> Callback { get; }
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;
Entity = entity;

@ -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<ModuleAttribute>() == true;
if (type.Has<RelationAttribute>()) { if (!isPartOfModule) RegisterRelation(type); }
else if (type.Has<ComponentAttribute>()) { if (!isPartOfModule) RegisterComponent(type); }
else if (type.Has<TagAttribute>()) { if (!isPartOfModule) RegisterTag(type); }
else if (type.Has<EntityAttribute>()) { if (!isPartOfModule) RegisterEntity(type); }
else if (type.Has<ModuleAttribute>()) 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<EntityAttribute>()?.ID ?? 0;
var entity = Create(new ecs_entity_desc_t {
name = type.GetFriendlyName(),
id = new() { Data = id },
});
_byType.Add(type, entity);
return entity;
}

Loading…
Cancel
Save