Add quick interactions locally

This commit is contained in:
2025-03-31 04:02:39 +02:00
parent 5bb7a207f2
commit b7d4531ec8
134 changed files with 15494 additions and 0 deletions

View File

@@ -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 { }
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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];
}
}
}

View File

@@ -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("------------------------------------------------------------------------------");
}
}
}

View File

@@ -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