Add quick interactions locally
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Diagnostics;
|
||||
using Barotrauma;
|
||||
|
||||
namespace QIDependencyInjection
|
||||
{
|
||||
/// <summary>
|
||||
/// Properties marked with [Dependency] will be injected,
|
||||
/// and their own properties will be injected
|
||||
/// </summary>
|
||||
public class DependencyAttribute : System.Attribute { }
|
||||
/// <summary>
|
||||
/// Properties marked with [EntryPoint] will be created if == null
|
||||
/// and then their own properties will be injected
|
||||
/// </summary>
|
||||
public class EntryPointAttribute : System.Attribute { }
|
||||
/// <summary>
|
||||
/// Properties marked with [Singleton] will be created, added to Singletons and injected
|
||||
/// </summary>
|
||||
public class SingletonAttribute : System.Attribute { }
|
||||
/// <summary>
|
||||
/// it's a stupid optimization attempt
|
||||
/// Classes marked with [HasStaticDependencies] will be scanned in InjectStaticProperties(Assembly assembly, bool onlyIfHasStaticDependencies = true)
|
||||
/// </summary>
|
||||
public class HasStaticDependenciesAttribute : System.Attribute { }
|
||||
/// <summary>
|
||||
/// Classes marked with [DontInjectStatic] won't be injected by InjectStaticProperties(Assembly assembly, bool onlyIfHasStaticDependencies = true)
|
||||
/// For test classes
|
||||
/// </summary>
|
||||
public class DontInjectStaticAttribute : System.Attribute { }
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Diagnostics;
|
||||
using Barotrauma;
|
||||
|
||||
namespace QIDependencyInjection
|
||||
{
|
||||
|
||||
public partial class ServiceCollection
|
||||
{
|
||||
/// <summary>
|
||||
/// Will return an object of ImplementationType that this ServiceType resolves to
|
||||
/// </summary>
|
||||
/// <typeparam name="ServiceType"></typeparam>
|
||||
/// <param name="args"> will be passed to the new instance, not singleton </param>
|
||||
/// <returns></returns>/
|
||||
public ServiceType GetService<ServiceType>(object?[]? args = null) => (ServiceType)GetService(typeof(ServiceType), args);
|
||||
public object GetService(Type ServiceType, object?[]? args = null) => GetServiceRec(ServiceType, 0, args);
|
||||
private object GetServiceRec(Type ServiceType, int depth = 0, object?[]? args = null)
|
||||
{
|
||||
if (depth > MaxRecursionDepth)
|
||||
{
|
||||
Log($"GetService {ServiceType} stuck in a loop", Color.Orange);
|
||||
return null;
|
||||
}
|
||||
|
||||
object o = null;
|
||||
|
||||
Type TargetType = GetTargetType(ServiceType);
|
||||
|
||||
if (TargetType != null)
|
||||
{
|
||||
if (Singletons.ContainsKey(TargetType))
|
||||
{
|
||||
o = Singletons[TargetType];
|
||||
}
|
||||
else
|
||||
{
|
||||
o = Activator.CreateInstance(TargetType, args);
|
||||
InjectProperties(o, depth + 1);
|
||||
}
|
||||
}
|
||||
|
||||
return o;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Diagnostics;
|
||||
using Barotrauma;
|
||||
using HarmonyLib;
|
||||
|
||||
namespace QIDependencyInjection
|
||||
{
|
||||
|
||||
public partial class ServiceCollection
|
||||
{
|
||||
/// <summary>
|
||||
/// Will scan the object for properties with [Dependency] attribute that are mapped
|
||||
/// to something and inject them with instances of the resolved target type
|
||||
/// </summary>
|
||||
public void InjectProperties(object o, int depth = 0)
|
||||
{
|
||||
if (o is null) return;
|
||||
|
||||
if (depth > MaxRecursionDepth)
|
||||
{
|
||||
Log($"InjectProperties {o} stuck in a loop", Color.Orange);
|
||||
return;
|
||||
}
|
||||
|
||||
List<PropertyInfo> entryPoints = new();
|
||||
List<PropertyInfo> dependencies = new();
|
||||
|
||||
foreach (PropertyInfo pi in o.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
|
||||
{
|
||||
if (Attribute.IsDefined(pi, typeof(EntryPointAttribute))) entryPoints.Add(pi);
|
||||
if (Attribute.IsDefined(pi, typeof(SingletonAttribute))) dependencies.Add(pi);
|
||||
if (Attribute.IsDefined(pi, typeof(DependencyAttribute))) dependencies.Add(pi);
|
||||
}
|
||||
|
||||
foreach (PropertyInfo pi in entryPoints)
|
||||
{
|
||||
|
||||
object value = pi.GetValue(o);
|
||||
if (value is null)
|
||||
{
|
||||
try
|
||||
{
|
||||
value = Activator.CreateInstance(pi.PropertyType);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log($"Failed to create instance of {pi.PropertyType}");
|
||||
Log($"{e.Message}");
|
||||
}
|
||||
Info($"injecting EntryPoint {pi.Name} -> {o}", new Color(255, 64, 255));
|
||||
pi.SetValue(o, value);
|
||||
}
|
||||
InjectProperties(value, depth + 1);
|
||||
}
|
||||
|
||||
foreach (PropertyInfo pi in dependencies)
|
||||
{
|
||||
object service = GetServiceRec(pi.PropertyType, depth + 1);
|
||||
Info($"injecting {pi.Name} -> {o}", new Color(255, 255, 0));
|
||||
pi.SetValue(o, service);
|
||||
}
|
||||
|
||||
|
||||
MethodInfo afterInject = o.GetType().GetMethod(AfterInjectMethodName, AccessTools.all);
|
||||
//Log($"{o} afterInject{afterInject}");
|
||||
try
|
||||
{
|
||||
afterInject?.Invoke(o, null);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log($"AfterInject on {o} failed", Color.Red);
|
||||
Log($"{e.Message}", Color.Red);
|
||||
}
|
||||
}
|
||||
|
||||
public void InjectEverything() => InjectEverything(Assembly.GetCallingAssembly());
|
||||
/// <summary>
|
||||
/// Will InjectEverything for all types in provided assembly,
|
||||
/// </summary>
|
||||
/// <param name="onlyIfHasStaticDependencies"> if true will ignore classes without [HasStaticDependencies] </param>
|
||||
public void InjectEverything(Assembly assembly, bool onlyIfHasStaticDependencies = false)
|
||||
{
|
||||
Stopwatch sw = new Stopwatch();
|
||||
sw.Restart();
|
||||
|
||||
IEnumerable<Type> types = assembly.GetTypes().Where(T =>
|
||||
{
|
||||
if (T.DeclaringType != null)
|
||||
{
|
||||
if (Attribute.IsDefined(T.DeclaringType, typeof(DontInjectStaticAttribute)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return !Attribute.IsDefined(T, typeof(DontInjectStaticAttribute));
|
||||
});
|
||||
|
||||
if (onlyIfHasStaticDependencies)
|
||||
{
|
||||
types = types.Where(T => Attribute.IsDefined(T, typeof(HasStaticDependenciesAttribute)));
|
||||
}
|
||||
|
||||
InjectEverything(types.ToArray());
|
||||
|
||||
sw.Stop();
|
||||
Info($"InjectEverything for assembly [{assembly.GetName().Name}] took {sw.ElapsedMilliseconds}ms");
|
||||
if (Debug) PrintState();
|
||||
}
|
||||
public void InjectEverything(Type type) => InjectEverything(new Type[] { type });
|
||||
public void InjectEverything(Type[] types)
|
||||
{
|
||||
FindAndCreateAllSingletons(types);
|
||||
InjectStaticProperties(types);
|
||||
}
|
||||
|
||||
public void InjectStaticProperties(Type[] types)
|
||||
{
|
||||
foreach (Type T in types)
|
||||
{
|
||||
List<PropertyInfo> entryPoints = new();
|
||||
List<PropertyInfo> dependencies = new();
|
||||
|
||||
foreach (PropertyInfo pi in T.GetProperties(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic))
|
||||
{
|
||||
if (Attribute.IsDefined(pi, typeof(EntryPointAttribute))) entryPoints.Add(pi);
|
||||
if (Attribute.IsDefined(pi, typeof(SingletonAttribute))) dependencies.Add(pi);
|
||||
if (Attribute.IsDefined(pi, typeof(DependencyAttribute))) dependencies.Add(pi);
|
||||
}
|
||||
|
||||
foreach (PropertyInfo pi in entryPoints)
|
||||
{
|
||||
Info($"injecting static EntryPoint {pi.Name} -> {pi.DeclaringType.Name}", new Color(0, 255, 255));
|
||||
object value = pi.GetValue(null);
|
||||
if (value is null)
|
||||
{
|
||||
try
|
||||
{
|
||||
value = Activator.CreateInstance(pi.PropertyType);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log($"Failed to create instance of {pi.PropertyType}");
|
||||
Log($"{e.Message}");
|
||||
}
|
||||
pi.SetValue(null, value);
|
||||
}
|
||||
InjectProperties(value);
|
||||
}
|
||||
|
||||
foreach (PropertyInfo pi in dependencies)
|
||||
{
|
||||
Info($"injecting static dependency {pi.Name} -> {pi.DeclaringType.Name}", new Color(0, 255, 255));
|
||||
pi.SetValue(null, GetService(pi.PropertyType));
|
||||
}
|
||||
|
||||
|
||||
MethodInfo afterInject = T.GetMethod(AfterInjectStaticMethodName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
try
|
||||
{
|
||||
afterInject?.Invoke(null, null);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log($"{AfterInjectStaticMethodName} on {T} failed", Color.Red);
|
||||
Log($"{e.Message}", Color.Red);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public void FindAndCreateAllSingletons(Type[] types)
|
||||
{
|
||||
foreach (Type T in types)
|
||||
{
|
||||
if (Attribute.IsDefined(T, typeof(SingletonAttribute))) CreateSingleton(T);
|
||||
|
||||
foreach (PropertyInfo pi in T.GetProperties(AccessTools.all))
|
||||
{
|
||||
if (Attribute.IsDefined(pi, typeof(SingletonAttribute)))
|
||||
{
|
||||
CreateSingleton(pi.PropertyType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var (T, singleton) in Singletons)
|
||||
{
|
||||
InjectProperties(singleton);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Diagnostics;
|
||||
using Barotrauma;
|
||||
|
||||
namespace QIDependencyInjection
|
||||
{
|
||||
|
||||
public partial class ServiceCollection
|
||||
{
|
||||
/// <summary>
|
||||
/// Will create mapping to itself.
|
||||
/// Usefull if you have object that doesn't implement any interface but has
|
||||
/// dependencies and should be injected
|
||||
/// </summary>
|
||||
public ServiceCollection Map<ImplementationType>() => Map<ImplementationType, ImplementationType>();
|
||||
/// <summary>
|
||||
/// Maps one type to another.
|
||||
/// GetService for ServiceType will return an object of ImplementationType.
|
||||
/// If ImplementationType is also in the mapping final type in the chain will be used as target type
|
||||
/// </summary>
|
||||
public ServiceCollection Map<ServiceType, ImplementationType>() => Map(typeof(ServiceType), typeof(ImplementationType));
|
||||
public ServiceCollection Map(Type ImplementationType) => Map(ImplementationType, ImplementationType);
|
||||
public ServiceCollection Map(Type ServiceType, Type ImplementationType)
|
||||
{
|
||||
Mapping[ServiceType] = ImplementationType;
|
||||
return this;
|
||||
}
|
||||
/// <summary>
|
||||
/// Will create a single object for that target type and map it to itself
|
||||
/// if ServiceType is pointing to a type that has a singleton, that singleton will be returned
|
||||
/// instead of a new instance
|
||||
/// If T is a ServiceType, singleton will be created for resolved target type
|
||||
/// </summary>
|
||||
/// <typeparam name="T"> ImplementationType or ServiceType that is already mapped </typeparam>
|
||||
/// <param name="args"> for TargetType constructor </param>
|
||||
public T CreateSingleton<T>(object?[]? args = null) => (T)CreateSingleton(typeof(T), args);
|
||||
public object CreateSingleton(Type T, object?[]? args = null)
|
||||
{
|
||||
Type TargetType = GetTargetType(T);
|
||||
|
||||
if (!Singletons.ContainsKey(TargetType))
|
||||
{
|
||||
Info($"creating a singleton for {TargetType}", new Color(255, 64, 255));
|
||||
Singletons[TargetType] = Activator.CreateInstance(TargetType, args);
|
||||
Map(TargetType);
|
||||
}
|
||||
|
||||
return Singletons[TargetType];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Diagnostics;
|
||||
using Barotrauma;
|
||||
|
||||
namespace QIDependencyInjection
|
||||
{
|
||||
/// <summary>
|
||||
/// Stores type mapping and creates injected objects with GetService.
|
||||
/// It works as both ServiceCollection and ServiceProvider from
|
||||
/// Microsoft.Extensions.QIDependencyInjection
|
||||
/// </summary>
|
||||
public partial class ServiceCollection
|
||||
{
|
||||
/// <summary>
|
||||
/// Safety guard for potential infinite loops
|
||||
/// </summary>
|
||||
public static int MaxRecursionDepth = 10;
|
||||
/// <summary>
|
||||
/// Method with this name will be called after injecting all props
|
||||
/// </summary>
|
||||
public static string AfterInjectMethodName = "AfterInject";
|
||||
/// <summary>
|
||||
/// Method with this name will be called after injecting all static props
|
||||
/// </summary>
|
||||
public static string AfterInjectStaticMethodName = "AfterInjectStatic";
|
||||
/// <summary>
|
||||
/// If true it will log actions to console
|
||||
/// </summary>
|
||||
public bool Debug { get; set; }
|
||||
/// <summary>
|
||||
/// Single instances for types.
|
||||
/// If target type maps to a singleton it will be used instead of a new instance
|
||||
/// </summary>
|
||||
public Dictionary<Type, object> Singletons = new();
|
||||
/// <summary>
|
||||
/// The type mapping.
|
||||
/// If target type also in the mapping then collection will
|
||||
/// recursivelly resolve target type until it reaches the end.
|
||||
/// If there's a loop it will warn you
|
||||
/// </summary>
|
||||
public Dictionary<Type, Type> Mapping = new();
|
||||
/// <summary>
|
||||
/// Just clears all data
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
Singletons.Clear();
|
||||
Mapping.Clear();
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns final type that chain of mappings is pointing to
|
||||
/// </summary>
|
||||
/// <typeparam name="ServiceType"></typeparam>
|
||||
/// <returns></returns>
|
||||
public Type GetTargetType<ServiceType>() => GetTargetType(typeof(ServiceType));
|
||||
public Type GetTargetType(Type ServiceType)
|
||||
{
|
||||
if (!Mapping.ContainsKey(ServiceType)) return ServiceType;
|
||||
|
||||
int depth = 0;
|
||||
|
||||
Type TargetType = Mapping[ServiceType];
|
||||
if (TargetType == ServiceType) return TargetType;
|
||||
|
||||
while (Mapping.ContainsKey(TargetType))
|
||||
{
|
||||
ServiceType = TargetType;
|
||||
TargetType = Mapping[ServiceType];
|
||||
if (TargetType == ServiceType) return TargetType;
|
||||
|
||||
if (depth++ > MaxRecursionDepth)
|
||||
{
|
||||
Log($"ServiceCollection: There seems to be a loop in your mapping", Color.Orange);
|
||||
Log($"Can't find target type for {ServiceType}", Color.Orange);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return TargetType;
|
||||
}
|
||||
|
||||
public string WrapInColor(object msg, string color) => $"‖color:{color}‖{msg}‖end‖";
|
||||
public static void Log(object msg, Color? color = null)
|
||||
{
|
||||
color ??= new Color(128, 255, 128);
|
||||
LuaCsLogger.LogMessage($"{msg ?? "null"}", color * 0.8f, color);
|
||||
}
|
||||
public void Info(object msg, Color? color = null)
|
||||
{
|
||||
if (Debug == true) Log(msg, color);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Will print Mapping and Singletons to console if Debug == true
|
||||
/// </summary>
|
||||
public void PrintState()
|
||||
{
|
||||
if (!Debug) return;
|
||||
Log("--------------------------------- Mapping ----------------------------------");
|
||||
foreach (var (s, i) in Mapping) Log($"{s.Name} -> {i.Name}");
|
||||
Log("--------------------------------- Singletons --------------------------------");
|
||||
foreach (var (i, o) in Singletons) Log($"{o}({o.GetHashCode()})");
|
||||
Log("------------------------------------------------------------------------------");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
[dependency] should always give instance
|
||||
[singleton] should always give singleton
|
||||
|
||||
Resolve singleton circular dependencies
|
||||
|
||||
make sure non singleton instances are inected correctly
|
||||
|
||||
Fix creating a singleton when instance already exist
|
||||
Reference in New Issue
Block a user