diff --git a/src/Hypercube.Ecs/IWorld.Dynamic.cs b/src/Hypercube.Ecs/IWorld.Dynamic.cs new file mode 100644 index 0000000..67667ab --- /dev/null +++ b/src/Hypercube.Ecs/IWorld.Dynamic.cs @@ -0,0 +1,36 @@ +namespace Hypercube.Ecs; + +public partial interface IWorld +{ + public object Add(Entity entity, Type type); + + /// + /// 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); + + /// + /// 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); + + /// + /// 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); + + /// + /// 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); +} \ No newline at end of file diff --git a/src/Hypercube.Ecs/IWorld.cs b/src/Hypercube.Ecs/IWorld.cs index 664d910..f06959d 100644 --- a/src/Hypercube.Ecs/IWorld.cs +++ b/src/Hypercube.Ecs/IWorld.cs @@ -4,7 +4,7 @@ namespace Hypercube.Ecs; -public interface IWorld +public partial interface IWorld { IEventBus Events { get; } diff --git a/src/Hypercube.Ecs/World.Dynamic.cs b/src/Hypercube.Ecs/World.Dynamic.cs new file mode 100644 index 0000000..68df0a0 --- /dev/null +++ b/src/Hypercube.Ecs/World.Dynamic.cs @@ -0,0 +1,115 @@ +using System.Reflection; +using JetBrains.Annotations; + +namespace Hypercube.Ecs; + +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(); + + /// + public object Add(Entity entity, Type type) + { + var method = GetGenericMethod(nameof(Add), type, Type.EmptyTypes); + return method.Invoke(this, [entity])!; + } + + /// + public void Add(Entity entity, object? value) + { + if (value is null) + return; + + var type = value.GetType(); + var method = GetGenericMethod(nameof(Add), type, [typeof(Entity), type.MakeByRefType()]); + method.Invoke(this, [entity, value]); + } + + /// + public object Get(Entity entity, Type type) + { + var method = GetGenericMethod(nameof(Get), type, [typeof(Entity)]); + return method.Invoke(this, [entity])!; + } + + /// + public bool Has(Entity entity, Type type) + { + var method = GetGenericMethod(nameof(Has), type, [typeof(Entity)]); + return (bool)method.Invoke(this, [entity])!; + } + + /// + 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; + } +}