Skip to content

Commit

Permalink
feat: denpendency injection
Browse files Browse the repository at this point in the history
  • Loading branch information
labbbirder committed Sep 27, 2023
1 parent 191b012 commit dec731f
Show file tree
Hide file tree
Showing 6 changed files with 316 additions and 86 deletions.
106 changes: 106 additions & 0 deletions Runtime/ClassicalUsages/ServiceContainer.cs
Original file line number Diff line number Diff line change
@@ -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<TContract, TResult>();
// public void AddSingle<TContract, TResult>();
// public ServiceScopeMode GetScopeMode<TContract>();
// public T Get<T>();
// }


public static class ServiceContainer //: IServiceContainer
{
static ServiceScopeMode DefaultScopeMode => Single;
static Dictionary<Type, object> singletons = new();
static Dictionary<Type, Info> 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<T>()
{
return (T)Get(typeof(T));
}

public static void AddTransient<TContract, TResult>()
{
lutInfos[typeof(TContract)] = new()
{
resultType = typeof(TResult),
scopeMode = Transient,
};
}

public static void AddSingle<TContract, TResult>(bool noLazy = false)
{
lutInfos[typeof(TContract)] = new()
{
resultType = typeof(TResult),
scopeMode = Single,
};
if(noLazy) Get<TContract>();
}

public static ServiceScopeMode GetScopeMode<TContract>()
{
if (lutInfos.TryGetValue(typeof(TContract), out var info))
{
return info.scopeMode;
}
return DefaultScopeMode;
}

}
struct Info
{
public Type resultType;
public ServiceScopeMode scopeMode;
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

83 changes: 0 additions & 83 deletions Runtime/ClassicalUsages/SimpleDI.cs

This file was deleted.

196 changes: 196 additions & 0 deletions Runtime/ClassicalUsages/SimpleDIAttribute.cs
Original file line number Diff line number Diff line change
@@ -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<TFunc>(Action<object> action, Type instType, bool isStatic)
where TFunc : Delegate
{
var argtypes = typeof(TFunc).GetGenericArguments();
var act = Expression.Constant(action);
var args = new List<ParameterExpression>(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<object>).GetMethod("Invoke"),
isStatic ? Expression.Constant(null) : args[0]);
var lambda = Expression.Lambda<TFunc>(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<InjectionInfo> 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<Type>();
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<object>)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, TRet>(T _) where T : class where TRet : class
{
return GetContainerInst(typeof(TRet)) as TRet;
}

static TRet StaticMetaGet<TRet>() where TRet : class
{
return GetContainerInst(typeof(TRet)) as TRet;
}

static object GetContainerInst(Type type)
{
return ServiceContainer.Get(type);
}
}
}
Loading

0 comments on commit dec731f

Please sign in to comment.