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 { /// /// Will scan the object for properties with [Dependency] attribute that are mapped /// to something and inject them with instances of the resolved target type /// 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 entryPoints = new(); List 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()); /// /// Will InjectEverything for all types in provided assembly, /// /// if true will ignore classes without [HasStaticDependencies] public void InjectEverything(Assembly assembly, bool onlyIfHasStaticDependencies = false) { Stopwatch sw = new Stopwatch(); sw.Restart(); IEnumerable 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 entryPoints = new(); List 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); } } } }