diff --git a/Runtime/ClassicalUsages/ServiceContainer.cs b/Runtime/ClassicalUsages/ServiceContainer.cs new file mode 100644 index 0000000..3eb3577 --- /dev/null +++ b/Runtime/ClassicalUsages/ServiceContainer.cs @@ -0,0 +1,106 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +namespace com.bbbirder.injection +{ + using static ServiceScopeMode; + public enum ServiceScopeMode + { + Single, + Transient, + } + + + // public interface IServiceContainer + // { + // public void AddTransient(); + // public void AddSingle(); + // public ServiceScopeMode GetScopeMode(); + // public T Get(); + // } + + + public static class ServiceContainer //: IServiceContainer + { + static ServiceScopeMode DefaultScopeMode => Single; + static Dictionary singletons = new(); + static Dictionary lutInfos = new(); + public static void ClearInstances(){ + singletons.Clear(); + } + public static object Get(Type type) + { + if (!lutInfos.TryGetValue(type, out var info)) + { + var subtypes = Retriever.GetAllSubtypes(type) + .Where(t => !t.IsInterface) + .Where(t => !t.IsAbstract) + .ToArray() + ; + if (subtypes.Length == 0) + { + throw new ArgumentException($"type {type} doesn't has an implement"); + } + if (subtypes.Length > 1) + { + Debug.LogWarning($"type {type} exists more than one implements"); + } + lutInfos[type] = info = new Info() + { + resultType = subtypes[0], + scopeMode = DefaultScopeMode, + }; + } + if (info.scopeMode == Single && singletons.TryGetValue(type, out var existing)) + { + return existing; + } + var inst = Activator.CreateInstance(info.resultType); + if (info.scopeMode == Single && inst != null) + { + singletons[type] = inst; + } + return inst; + } + + public static T Get() + { + return (T)Get(typeof(T)); + } + + public static void AddTransient() + { + lutInfos[typeof(TContract)] = new() + { + resultType = typeof(TResult), + scopeMode = Transient, + }; + } + + public static void AddSingle(bool noLazy = false) + { + lutInfos[typeof(TContract)] = new() + { + resultType = typeof(TResult), + scopeMode = Single, + }; + if(noLazy) Get(); + } + + public static ServiceScopeMode GetScopeMode() + { + if (lutInfos.TryGetValue(typeof(TContract), out var info)) + { + return info.scopeMode; + } + return DefaultScopeMode; + } + + } + struct Info + { + public Type resultType; + public ServiceScopeMode scopeMode; + } +} \ No newline at end of file diff --git a/Runtime/ClassicalUsages/SimpleDI.cs.meta b/Runtime/ClassicalUsages/ServiceContainer.cs.meta similarity index 83% rename from Runtime/ClassicalUsages/SimpleDI.cs.meta rename to Runtime/ClassicalUsages/ServiceContainer.cs.meta index 34b7548..9c10b86 100644 --- a/Runtime/ClassicalUsages/SimpleDI.cs.meta +++ b/Runtime/ClassicalUsages/ServiceContainer.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 212792f716f238a4583b5235cd1d6025 +guid: 6a7627286c3518f479366b70cdab0f90 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Runtime/ClassicalUsages/SimpleDI.cs b/Runtime/ClassicalUsages/SimpleDI.cs deleted file mode 100644 index 0c4a2ee..0000000 --- a/Runtime/ClassicalUsages/SimpleDI.cs +++ /dev/null @@ -1,83 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Reflection; -using UnityEngine; - -namespace com.bbbirder.injection -{ - [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] - public class SimpleDIAttribute : InjectionAttribute - { - bool isSingle; - static MethodInfo s_MetaMethodInfo; - public SimpleDIAttribute(bool isSingle = true){ - this.isSingle = isSingle; - } - public override IEnumerable ProvideInjections() - { - s_MetaMethodInfo??= typeof(SimpleDIAttribute).GetMethod(nameof(MetaGet),BindingFlags.Static|BindingFlags.NonPublic); - var propInfo = targetMember as PropertyInfo; - - yield return InjectionInfo.Create( - propInfo.GetMethod, - s_MetaMethodInfo.MakeGenericMethod(propInfo.PropertyType) - ); - } - static T MetaGet(){ - return DefaultDependencyContainer.Get(); - } - } - - public interface IDependencyContainer - { - public void AddRule(Func getter); - public T Get(); - public object Get(Type type); - } - - public static class DefaultDependencyContainer - { - static Dictionary> getters = new(); - static Dictionary instances = new(); - - static object GetByDefault(Type type) - { - if(!instances.TryGetValue(type,out var inst)){ - var subtypes = Retriever.GetAllSubtypes(type); - if (subtypes.Length == 0) - { - throw new ArgumentException($"type {type} doesn't has an implement"); - } - if (subtypes.Length > 1) - { - Debug.LogWarning($"type {type} exists more than one implements"); - } - var targetType = subtypes[0]; - instances[type] = inst = Activator.CreateInstance(targetType); - } - return inst; - } - - static public void AddRule(Func getter) - { - getters[typeof(T)] = () => getter(); - } - - static public T Get() - { - return (T)Get(typeof(T)); - } - - static public object Get(Type type) - { - if (getters.TryGetValue(type, out var getter)) - { - return getter(); - } - else - { - return GetByDefault(type); - } - } - } -} \ No newline at end of file diff --git a/Runtime/ClassicalUsages/SimpleDIAttribute.cs b/Runtime/ClassicalUsages/SimpleDIAttribute.cs new file mode 100644 index 0000000..c4a1f81 --- /dev/null +++ b/Runtime/ClassicalUsages/SimpleDIAttribute.cs @@ -0,0 +1,196 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using com.bbbirder.injection; +using UnityEngine.Assertions; +using UnityEngine.Scripting; + + +namespace com.bbbirder.injection +{ + using static System.Reflection.BindingFlags; + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)] + public class SimpleDIAttribute : InjectionAttribute + { + static MethodInfo s_MetaMethodInfo; + static MethodInfo s_StaticMetaMethodInfo; + static MethodInfo s_miMetaConstructor; + public SimpleDIAttribute() + { + } + + bool IsStatic(MemberInfo memberInfo) + { + if (memberInfo is FieldInfo fieldInfo) return fieldInfo.IsStatic; + if (memberInfo is PropertyInfo propertyInfo) + { + if (!propertyInfo.CanRead) + { + return propertyInfo.GetSetMethod(nonPublic: true).IsStatic; + } + return propertyInfo.GetGetMethod(nonPublic: true).IsStatic; + } + return false; + } + + [Preserve] + static TFunc MetaConstructor(Action action, Type instType, bool isStatic) + where TFunc : Delegate + { + var argtypes = typeof(TFunc).GetGenericArguments(); + var act = Expression.Constant(action); + var args = new List(argtypes.Length + 1); + // if (!isStatic) args.Add(Expression.Parameter(instType, "inst")); + for (int i = 0; i < argtypes.Length; i++) + { + args.Add(Expression.Parameter(argtypes[i], "arg" + i)); + } + var ivk = Expression.Call(act, + typeof(Action).GetMethod("Invoke"), + isStatic ? Expression.Constant(null) : args[0]); + var lambda = Expression.Lambda(ivk, args); + return lambda.Compile(); + } + + ConstructorInfo[] Get_Ctors(Type type) + { + return type.GetConstructors(Public | NonPublic | Instance); + } + + ConstructorInfo[] Get_CCtor(Type type) + { + return type.GetConstructors(Public | NonPublic | Static); + } + + bool CanWrite(MemberInfo memberInfo) + { + if (memberInfo is FieldInfo) return true; + if (memberInfo is PropertyInfo propertyInfo) return propertyInfo.CanWrite; + return false; + } + void SetMemberValue(MemberInfo memberInfo, object inst, object value) + { + if (memberInfo is FieldInfo fi) + { + fi.SetValue(inst, value); + return; + } + if (memberInfo is PropertyInfo pi) + { + pi.SetValue(inst, value); + return; + } + } + Type GetMemberType(MemberInfo memberInfo) + { + if (memberInfo is FieldInfo fi) return fi.FieldType; + if (memberInfo is PropertyInfo pi) return pi.PropertyType; + return default; + } + public override IEnumerable ProvideInjections() + { + s_MetaMethodInfo ??= typeof(SimpleDIAttribute).GetMethod(nameof(MetaGet), Static | NonPublic); + s_StaticMetaMethodInfo ??= typeof(SimpleDIAttribute).GetMethod(nameof(StaticMetaGet), Static | NonPublic); + s_miMetaConstructor ??= typeof(SimpleDIAttribute).GetMethod(nameof(MetaConstructor), Static | NonPublic); + if (targetMember is not PropertyInfo and not FieldInfo) + throw new Exception($"cannot inject {targetMember} on type {targetType}, only fields and properties allowed"); + var memberType = GetMemberType(targetMember); + var isStatic = IsStatic(targetMember); + var canWrite = CanWrite(targetMember); + if (!Retriever.IsTypeRetrievable(memberType)) + { + throw new Exception($"cannot inject {targetMember} on type {targetType}, type {memberType} is not retrievable"); + } + if (isStatic) + { + if (canWrite) + { + // set on fix instantly + yield return InjectionInfo.Create(()=>{ + SetMemberValue(targetMember, null, GetContainerInst(memberType)); + }); + } + else + { + // inject get method + var propertyInfo = targetMember as PropertyInfo; + var fixingMethod = s_StaticMetaMethodInfo.MakeGenericMethod(propertyInfo.PropertyType); + yield return InjectionInfo.Create( + propertyInfo.GetGetMethod(nonPublic: true), + fixingMethod + ); + } + } + else + { + if (canWrite) + { + // inject constructor + var constructors = Get_Ctors(targetType); + var argtypes = new List(); + foreach (var constructor in constructors) + { + Delegate rawAction = default; + argtypes.Clear(); + argtypes.Add(targetType); + foreach (var p in constructor.GetParameters()) + { + argtypes.Add(p.ParameterType); + } + var miInstAction = default(Type); + if (argtypes.Count == 0) + { + miInstAction = typeof(System.Action); + } + else + { + var miGenericAction = Type.GetType("System.Action`" + argtypes.Count); + miInstAction = miGenericAction.MakeGenericType(argtypes.ToArray()); + } + var miCtorInst = s_miMetaConstructor.MakeGenericMethod(miInstAction); + var fixingFunc = miCtorInst.Invoke(null, new object[]{ + (Action)fixedContructor,targetType,isStatic + }) as Delegate; + yield return InjectionInfo.Create( + constructor, + fixingFunc, + f => rawAction = f + ); + void fixedContructor(object inst) + { + SetMemberValue(targetMember, inst, GetContainerInst(memberType)); + rawAction.GetType().GetMethod("Invoke").Invoke(rawAction, new[] { inst }); + } + } + } + else + { + // inject get method + var propertyInfo = targetMember as PropertyInfo; + var fixingMethod = s_MetaMethodInfo.MakeGenericMethod(targetType, propertyInfo.PropertyType); + yield return InjectionInfo.Create( + propertyInfo.GetGetMethod(nonPublic: true), + fixingMethod + ); + } + } + } + + static TRet MetaGet(T _) where T : class where TRet : class + { + return GetContainerInst(typeof(TRet)) as TRet; + } + + static TRet StaticMetaGet() where TRet : class + { + return GetContainerInst(typeof(TRet)) as TRet; + } + + static object GetContainerInst(Type type) + { + return ServiceContainer.Get(type); + } + } +} \ No newline at end of file diff --git a/Runtime/ClassicalUsages/SimpleDIAttribute.cs.meta b/Runtime/ClassicalUsages/SimpleDIAttribute.cs.meta new file mode 100644 index 0000000..31041ac --- /dev/null +++ b/Runtime/ClassicalUsages/SimpleDIAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9eccd17b69df0684891b0a878431acbb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/package.json b/package.json index 424978e..a0f1d28 100644 --- a/package.json +++ b/package.json @@ -2,11 +2,11 @@ "name": "com.bbbirder.injection", "displayName": "Unity Injection", "description": "Unity注入模块,可以运行时改变被注入函数实现。", - "version": "1.3.9", + "version": "1.3.11", "hideInEditor": false, "author": "bbbirder <502100554@qq.com>", "dependencies": { - "com.bbbirder.directattribute": "1.1.1", + "com.bbbirder.directattribute": "1.1.3", "com.unity.nuget.mono-cecil": "1.10.2" }, "samples": [