From 65a2f446588f1b210b330ec130ee2e5971714e0e Mon Sep 17 00:00:00 2001 From: Mikhail <99481254+DCFApixels@users.noreply.github.com> Date: Sat, 10 Jun 2023 02:12:22 +0800 Subject: [PATCH] add inject to properties and through methods --- src/Attributes/EcsInjectAttribute.cs | 2 +- src/AutoInjectSystem.cs | 183 +++++++++++++++++++++------ src/Utils/Exceptions.cs | 14 ++ 3 files changed, 161 insertions(+), 38 deletions(-) create mode 100644 src/Utils/Exceptions.cs diff --git a/src/Attributes/EcsInjectAttribute.cs b/src/Attributes/EcsInjectAttribute.cs index cf6f3b2..1ca7db3 100644 --- a/src/Attributes/EcsInjectAttribute.cs +++ b/src/Attributes/EcsInjectAttribute.cs @@ -2,7 +2,7 @@ namespace DCFApixels.DragonECS { - [AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)] + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Method, Inherited = false, AllowMultiple = false)] public sealed class EcsInjectAttribute : Attribute { public readonly Type notNullDummyType; diff --git a/src/AutoInjectSystem.cs b/src/AutoInjectSystem.cs index 1ef3e37..491f66a 100644 --- a/src/AutoInjectSystem.cs +++ b/src/AutoInjectSystem.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Reflection; namespace DCFApixels.DragonECS @@ -11,7 +12,7 @@ namespace DCFApixels.DragonECS internal class AutoInjectionMap { private readonly EcsPipeline _source; - private Dictionary> _systems; + private Dictionary> _systemProoperties; private HashSet _notInjected; private Type _dummyInstance = typeof(DummyInstance<>); private bool _isDummyInjected = false; @@ -20,38 +21,64 @@ namespace DCFApixels.DragonECS { _source = source; var allsystems = _source.AllSystems; - _systems = new Dictionary>(); + _systemProoperties = new Dictionary>(); _notInjected = new HashSet(); foreach (var system in allsystems) { Type systemType = system.GetType(); - foreach (var field in GetAllFieldsFor(systemType)) + foreach (var property in GetAllPropertiesFor(systemType)) { - EcsInjectAttribute autoInjectAttribute = field.GetCustomAttribute(); - if (autoInjectAttribute != null) - { - Type fieldType = field.FieldType; - List list; - if (!_systems.TryGetValue(fieldType, out list)) - { - list = new List(); - _systems.Add(fieldType, list); - } + if (property.GetAutoInjectAttribute() == null) + continue; - list.Add(new FieldRecord(system, field, autoInjectAttribute)); + Type fieldType = property.PropertyType; + List list; + if (!_systemProoperties.TryGetValue(fieldType, out list)) + { + list = new List(); + _systemProoperties.Add(fieldType, list); } + + list.Add(new InjectedPropertyRecord(system, property)); } } - foreach (var item in _systems.Keys) + foreach (var item in _systemProoperties.Keys) _notInjected.Add(item); } - private static List GetAllFieldsFor(Type type) + private static List GetAllPropertiesFor(Type type) { - List result = new List(); + const BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; + List result = new List(); Do(type, result); - static void Do(Type type, List result) + static void Do(Type type, List result) { - result.AddRange(type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)); + result.AddRange(type.GetFields(bindingFlags) + .Where(o => o.GetCustomAttribute() != null) + .Select(o => new InjectedField(o))); + result.AddRange(type.GetProperties(bindingFlags) + .Where(o =>{ + if (o.GetCustomAttribute() == null) + return false; +#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS + if (o.CanWrite == false) + throw new EcsAutoInjectionException($"{o.Name} property is cant write"); +#endif + return true; + }) + .Select(o => new InjectedProperty(o))); + result.AddRange(type.GetMethods(bindingFlags) + .Where(o => { + if (o.GetCustomAttribute() == null) + return false; +#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS + if (o.IsGenericMethod) + throw new EcsAutoInjectionException($"{o.Name} method is Generic"); + if (o.GetParameters().Length != 1) + throw new EcsAutoInjectionException($"{o.Name} method Arguments != 1"); +#endif + return true; + }) + .Select(o => new InjectedMethod(o))); if (type.BaseType != null) Do(type.BaseType, result); } @@ -64,10 +91,10 @@ namespace DCFApixels.DragonECS if (baseType != null) Inject(baseType, obj); - if (_systems.TryGetValue(fieldType, out List list)) + if (_systemProoperties.TryGetValue(fieldType, out List list)) { foreach (var item in list) - item.field.SetValue(item.target, obj); + item.property.Inject(item.target, obj); } } @@ -78,20 +105,20 @@ namespace DCFApixels.DragonECS _isDummyInjected = true; foreach (var notInjectedItem in _notInjected) { - foreach (var systemRecord in _systems[notInjectedItem]) + foreach (var systemRecord in _systemProoperties[notInjectedItem]) { - if (systemRecord.attribute.notNullDummyType == null) + if (systemRecord.Attribute.notNullDummyType == null) continue; - if (systemRecord.field.GetValue(systemRecord.target) != null) + if (systemRecord.property.IsInjected) continue; - if (systemRecord.field.FieldType.IsAssignableFrom(systemRecord.attribute.notNullDummyType) == false) + if (systemRecord.property.PropertyType.IsAssignableFrom(systemRecord.Attribute.notNullDummyType) == false) { - EcsDebug.Print(EcsConsts.DEBUG_ERROR_TAG, $"The {systemRecord.attribute.notNullDummyType} dummy cannot be assigned to the {systemRecord.field.FieldType.Name} field"); + EcsDebug.Print(EcsConsts.DEBUG_ERROR_TAG, $"The {systemRecord.Attribute.notNullDummyType} dummy cannot be assigned to the {systemRecord.property.PropertyType.Name} field"); continue; } - systemRecord.field.SetValue(systemRecord.target, - _dummyInstance.MakeGenericType(systemRecord.attribute.notNullDummyType).GetField("intsance", BindingFlags.Static | BindingFlags.Public).GetValue(null)); + systemRecord.property.Inject(systemRecord.target, + _dummyInstance.MakeGenericType(systemRecord.Attribute.notNullDummyType).GetField("intsance", BindingFlags.Static | BindingFlags.Public).GetValue(null)); } } WarningMissedInjections(); @@ -104,24 +131,21 @@ namespace DCFApixels.DragonECS #if DEBUG foreach (var item in _notInjected) { - foreach (var systemRecord in _systems[item]) - { + foreach (var systemRecord in _systemProoperties[item]) EcsDebug.PrintWarning($"in system {EcsDebugUtility.GetGenericTypeFullName(systemRecord.target.GetType(), 1)} is missing an injection of {EcsDebugUtility.GetGenericTypeFullName(item, 1)}."); - } } #endif } - private readonly struct FieldRecord + private readonly struct InjectedPropertyRecord { public readonly IEcsProcess target; - public readonly FieldInfo field; - public readonly EcsInjectAttribute attribute; - public FieldRecord(IEcsProcess target, FieldInfo field, EcsInjectAttribute attribute) + public readonly IInjectedProperty property; + public EcsInjectAttribute Attribute => property.GetAutoInjectAttribute(); + public InjectedPropertyRecord(IEcsProcess target, IInjectedProperty property) { this.target = target; - this.field = field; - this.attribute = attribute; + this.property = property; } } } @@ -172,6 +196,91 @@ namespace DCFApixels.DragonECS { _autoInjectionMap.InjectDummy(); } + ClearUsless(); + } + + private void ClearUsless() + { + _autoInjectionMap = null; + GC.Collect(0); //Собрать все хламовые созданне время мини классы } } + + #region Utils + internal interface IInjectedProperty + { + public bool IsCanSet { get; } + public bool IsCanGet { get; } + public bool IsInjected { get; } + public Type PropertyType { get; } + MemberInfo GetMember(); + EcsInjectAttribute GetAutoInjectAttribute(); + void Inject(object target, object value); + } + internal class InjectedField : IInjectedProperty + { + private FieldInfo _member; + private EcsInjectAttribute _injectAttribute; + public bool IsCanSet => true; + public bool IsCanGet => true; + public bool IsInjected { get; private set; } + public Type PropertyType => _member.FieldType; + public InjectedField(FieldInfo member) + { + _member = member; + _injectAttribute = member.GetCustomAttribute(); + } + public MemberInfo GetMember() => _member; + public EcsInjectAttribute GetAutoInjectAttribute() => _injectAttribute; + public void Inject(object target, object value) + { + _member.SetValue(target, value); + IsInjected = true; + } + } + internal class InjectedProperty : IInjectedProperty + { + private PropertyInfo _member; + private EcsInjectAttribute _injectAttribute; + public bool IsCanSet => _member.CanWrite; + public bool IsCanGet => _member.CanRead; + public bool IsInjected { get; private set; } + public Type PropertyType => _member.PropertyType; + public InjectedProperty(PropertyInfo member) + { + _member = member; + _injectAttribute = member.GetCustomAttribute(); + } + public MemberInfo GetMember() => _member; + public EcsInjectAttribute GetAutoInjectAttribute() => _injectAttribute; + public void Inject(object target, object value) + { + _member.SetValue(target, value); + IsInjected = true; + } + } + internal class InjectedMethod : IInjectedProperty + { + private MethodInfo _member; + private EcsInjectAttribute _injectAttribute; + private Type propertyType; + public bool IsCanSet => true; + public bool IsCanGet => false; + public bool IsInjected { get; private set; } + public Type PropertyType => propertyType; + public InjectedMethod(MethodInfo member) + { + _member = member; + _injectAttribute = member.GetCustomAttribute(); + propertyType = _member.GetParameters()[0].ParameterType; + } + public MemberInfo GetMember() => _member; + public EcsInjectAttribute GetAutoInjectAttribute() => _injectAttribute; + public void Inject(object target, object value) + { + _member.Invoke(target, new object[] { value }); + IsInjected = true; + } + } + #endregion } \ No newline at end of file diff --git a/src/Utils/Exceptions.cs b/src/Utils/Exceptions.cs new file mode 100644 index 0000000..84f5ea0 --- /dev/null +++ b/src/Utils/Exceptions.cs @@ -0,0 +1,14 @@ +п»їusing System; +using System.Runtime.Serialization; + +namespace DCFApixels.DragonECS +{ + [Serializable] + public class EcsAutoInjectionException : Exception + { + public EcsAutoInjectionException() { } + public EcsAutoInjectionException(string message) : base(EcsConsts.EXCEPTION_MESSAGE_PREFIX + message) { } + public EcsAutoInjectionException(string message, Exception inner) : base(EcsConsts.EXCEPTION_MESSAGE_PREFIX + message, inner) { } + protected EcsAutoInjectionException(SerializationInfo info, StreamingContext context) : base(info, context) { } + } +}