using System; using System.Collections; using System.Collections.Generic; using System.Reflection; using System.Runtime.InteropServices; using System.Threading; using gaemstone.Utility; using gaemstone.Utility.IL; using static flecs_hub.flecs; 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); 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); public SystemInfo RegisterSystem(string name, string? expression, Phase phase, ecs_filter_desc_t filter, Action callback) { var _phase = Systems._phaseLookup[phase]; var entityDesc = default(ecs_entity_desc_t); entityDesc.name = name; entityDesc.add[0] = !_phase.IsNone ? (EcsDependsOn & _phase) : default; entityDesc.add[1] = _phase; // TODO: Provide a nice way to create these entity descriptors. var systemDesc = default(ecs_system_desc_t); systemDesc.entity = Create(entityDesc); systemDesc.binding_ctx = (void*)UniverseSystems.CreateSystemCallbackContext(this, callback); systemDesc.callback.Data.Pointer = &UniverseSystems.SystemCallback; systemDesc.query.filter = filter; var entity = new Entity(this, ecs_system_init(Handle, &systemDesc)); var system = new SystemInfo(this, entity, name, expression, phase, filter, callback); Systems._systems.Add(system); return system; } public SystemInfo RegisterSystem(Delegate action) { var name = action.Method.Name; var attr = action.Method.Get(); var phase = attr?.Phase ?? Phase.OnUpdate; if (action is Action iterAction) { if (attr?.Expression == null) throw new Exception( "System must specify expression in SystemAttribute"); return RegisterSystem(name, attr.Expression, phase, new() { expr = attr.Expression }, iterAction); } else { var method = action.GetType().GetMethod("Invoke")!; var gen = QueryActionGenerator.GetOrBuild(this, method); var filter = (attr?.Expression == null) ? gen.Filter : new() { expr = attr.Expression }; return RegisterSystem(name, attr?.Expression, phase, filter, iter => gen.RunWithTryCatch(action.Target, iter)); } } public SystemInfo RegisterSystem(object? instance, MethodInfo method) { var attr = method.Get(); var phase = attr?.Phase ?? Phase.OnUpdate; var param = method.GetParameters(); if (param.Length == 1 && param[0].ParameterType == typeof(Iterator)) { if (attr?.Expression == null) throw new Exception( "System must specify expression in SystemAttribute"); var action = (Action)Delegate.CreateDelegate(typeof(Action), instance, method); return RegisterSystem(method.Name, attr.Expression, phase, new() { expr = attr.Expression }, action); } else { var gen = QueryActionGenerator.GetOrBuild(this, method); var filter = (attr?.Expression == null) ? gen.Filter : new() { expr = attr.Expression }; return RegisterSystem(method.Name, attr?.Expression, phase, filter, iter => gen.RunWithTryCatch(instance, iter)); } } public class UniverseSystems : IReadOnlyCollection { public readonly struct SystemCallbackContext { public Universe Universe { get; } public Action Callback { get; } public SystemCallbackContext(Universe universe, Action callback) { Universe = universe; Callback = callback; } } private static SystemCallbackContext[] _systemCallbackContexts = new SystemCallbackContext[64]; private static int _systemCallbackContextsCount = 0; public static nint CreateSystemCallbackContext(Universe universe, Action callback) { var data = new SystemCallbackContext(universe, callback); var count = Interlocked.Increment(ref _systemCallbackContextsCount); if (count > _systemCallbackContexts.Length) Array.Resize(ref _systemCallbackContexts, count * 2); _systemCallbackContexts[count - 1] = data; return count; } public static SystemCallbackContext GetSystemCallbackContext(nint context) => _systemCallbackContexts[(int)context - 1]; [UnmanagedCallersOnly] internal static void SystemCallback(ecs_iter_t* iter) { var data = GetSystemCallbackContext((nint)iter->binding_ctx); data.Callback(new Iterator(data.Universe, null, *iter)); } 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; public IEnumerator GetEnumerator() => _systems.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } public class SystemInfo { public Universe Universe { get; } public Entity Entity { get; } public string Name { get; } public string? Expression { get; } public Phase 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) { Universe = universe; Entity = entity; Name = name; Expression = expression; Phase = phase; Filter = filter; Callback = callback; } } }