diff --git a/src/Hypercube.Ecs/Entities/EntityRefAction.cs b/src/Hypercube.Ecs/Entities/EntityRefAction.cs index 712082d..12213fb 100644 --- a/src/Hypercube.Ecs/Entities/EntityRefAction.cs +++ b/src/Hypercube.Ecs/Entities/EntityRefAction.cs @@ -10,3 +10,6 @@ public delegate void EntityRefAction(Entity entity, ref T1 component1, r public delegate void EntityRefAction(Entity entity, ref T1 component1, ref T2 component2, ref T3 component3) where T1 : IComponent where T2 : IComponent where T3 : IComponent; + +public delegate void EntityRefAction(Entity entity, ref T1 component1, ref T2 component2, ref T3 component3, ref T4 component4) + where T1 : IComponent where T2 : IComponent where T3 : IComponent where T4 : IComponent; diff --git a/src/Hypercube.Ecs/Events/Handling/EventHandlerList.cs b/src/Hypercube.Ecs/Events/Handling/EventHandlerList.cs index 4e4264b..76e92a3 100644 --- a/src/Hypercube.Ecs/Events/Handling/EventHandlerList.cs +++ b/src/Hypercube.Ecs/Events/Handling/EventHandlerList.cs @@ -29,13 +29,14 @@ private void AddInternal(object handler, int priority) try { var entry = new HandlerEntry(priority, handler); - if (priority == NoPriority) + var index = _handlers.FindIndex(h => priority > h.Priority); + + if (index == -1) { _handlers.Add(entry); return; } - - var index = _handlers.FindIndex(h => priority > h.Priority); + _handlers.Insert(index, entry); } finally diff --git a/src/Hypercube.Ecs/World.Dynamic.cs b/src/Hypercube.Ecs/World.Dynamic.cs new file mode 100644 index 0000000..bacca80 --- /dev/null +++ b/src/Hypercube.Ecs/World.Dynamic.cs @@ -0,0 +1,143 @@ +using System.Reflection; +using JetBrains.Annotations; + +namespace Hypercube.Ecs; + +/// +/// Provides a dynamic bridge for component manipulation using runtime information. +/// +public partial class World +{ + /// + /// Caches fully constructed generic methods to avoid repeated calls. + /// Key: (Method Name, Component Type). + /// + private readonly Dictionary<(string Name, Type GenericType), MethodInfo> _methodCache = new(); + + /// + /// Caches open generic method definitions to avoid expensive lookups and LINQ filtering. + /// Key: (Method Name, Parameter Count). + /// + private readonly Dictionary<(string Name, int ParamCount), MethodInfo> _genericDefinitions = new(); + + /// + /// Dynamically adds a component of the specified to an entity. + /// Uses the default constructor for the component type. + /// + /// The target entity. + /// The runtime type of the component to add. + /// The newly created component instance. + public object Add(Entity entity, Type type) + { + var method = GetGenericMethod(nameof(Add), type, Type.EmptyTypes); + return method.Invoke(this, [entity])!; + } + + /// + /// Dynamically adds a specific component instance to an entity. + /// + /// The target entity. + /// The component instance to add. If null, the operation is ignored. + public void Add(Entity entity, object? value) + { + if (value is null) + return; + + var type = value.GetType(); + // Finds the overload: Add(Entity entity, ref T component) + var method = GetGenericMethod(nameof(Add), type, [typeof(Entity), type.MakeByRefType()]); + method.Invoke(this, [entity, value]); + } + + /// + /// Dynamically retrieves a component instance of the specified from an entity. + /// + /// The target entity. + /// The runtime type of the component to retrieve. + /// The component instance. + public object Get(Entity entity, Type type) + { + var method = GetGenericMethod(nameof(Get), type, [typeof(Entity)]); + return method.Invoke(this, [entity])!; + } + + /// + /// Dynamically checks if an entity has a component of the specified . + /// + /// The target entity. + /// The runtime type of the component to check. + /// True if the entity possesses the component; otherwise, false. + public bool Has(Entity entity, Type type) + { + var method = GetGenericMethod(nameof(Has), type, [typeof(Entity)]); + return (bool)method.Invoke(this, [entity])!; + } + + /// + /// Dynamically removes a component of the specified from an entity. + /// + /// The target entity. + /// The runtime type of the component to remove. + public void Remove(Entity entity, Type type) + { + var method = GetGenericMethod(nameof(Remove), type, [typeof(Entity)]); + method.Invoke(this, [entity]); + } + + /// + /// Resolves and caches a generic method using a two-tier caching strategy. + /// + /// The name of the method to resolve. + /// The type argument for the generic method. + /// The types of the method parameters used to resolve overloads. + /// A constructed ready for invocation. + private MethodInfo GetGenericMethod(string name, Type genericType, Type[] parameterTypes) + { + var cacheKey = (name, genericType); + if (_methodCache.TryGetValue(cacheKey, out var cached)) + return cached; + + var definitionKey = (name, parameterTypes.Length); + if (!_genericDefinitions.TryGetValue(definitionKey, out var methodDefinition)) + { + methodDefinition = GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance) + .First(m => m.Name == name && + m.IsGenericMethod && + m.GetGenericArguments().Length == 1 && + MatchParameters(m.GetParameters(), parameterTypes)); + + _genericDefinitions[definitionKey] = methodDefinition; + } + + var genericMethod = methodDefinition.MakeGenericMethod(genericType); + _methodCache[cacheKey] = genericMethod; + + return genericMethod; + } + + /// + /// Validates if a method's parameters match the expected types, + /// accounting for generic placeholders and reference types. + /// + /// The parameters from the . + /// The expected runtime types. + /// True if the signatures match; otherwise, false. + private bool MatchParameters(ParameterInfo[] parameters, Type[] parameterTypes) + { + if (parameters.Length != parameterTypes.Length) + return false; + + for (var i = 0; i < parameters.Length; i++) + { + var paramType = parameters[i].ParameterType; + + if (paramType.IsGenericParameter || (paramType.IsByRef && paramType.GetElementType()!.IsGenericParameter)) + continue; + + if (paramType != parameterTypes[i]) + return false; + } + + return true; + } +} \ No newline at end of file