Skip to content

Instantly share code, notes, and snippets.

@instance-id
Last active February 15, 2024 04:59
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save instance-id/e98ddf640e71449e84109f72fffa5fd9 to your computer and use it in GitHub Desktop.
Save instance-id/e98ddf640e71449e84109f72fffa5fd9 to your computer and use it in GitHub Desktop.
OneShot - Dependency Injection: Example Registration and Injection Handler
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using instance.id.DataTypes;
using instance.id.Logging;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityObject = UnityEngine.Object;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace instance.id
{
[RelativePath("instance.id", "DependencyInjection")]
[CreateAssetMenu(fileName = "DIContainer.asset", menuName = "instance.id/DI Container", order = 1)]
public class DIContainer : AssetInstance<DIContainer>
{
public Container Container { get; } = new Container();
#if !UNITY_EDITOR
private void Awake() => SetupDIContainer();
#endif
#if UNITY_EDITOR
private void OnEnable() => RunInjection();
[InitializeOnLoadMethod]
#endif
public static void SetupDIContainer() => RegistrationHandler();
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
private static void RunInjection() => I.InjectionHandler();
// --| Registration Receiver --------------------------------
// --| ------------------------------------------------------
public ResolverBuilder Register<T>(T instance) =>
I.Container.RegisterInstance<T>(instance);
// --| DI Registration Initializer --------------------------
// --| ------------------------------------------------------
private static void RegistrationHandler()
{
var initializers = new List<MethodInfo>();
#if UNITY_EDITOR
try
{
EditorApplication.update += EditorUpdate;
// --| Editor Only TypeCache ------------------------
initializers = TypeCache.GetMethodsWithAttribute<DIInitializer>().ToList();
#else
// --| Runtime Type/Method Cache --------------------
initializers = RuntimeMethodCache.GetMethodsWithAttribute<DIInitializer>().ToList();
#endif
if (initializers.Count == 0)
{
StaticLog("No DIInitializer Attributes found.", Level.Warning, Channel.EditorDebug);
return;
}
for (var i = 0; i < initializers.Count; i++)
{
var mInfo = initializers[i];
// --| Search for loaded instances --------------
var instances = FindObjectsOfType(mInfo.DeclaringType).ToList();
// --| Editor/AssetDatabase, else/Resources -----
if (instances.Count == 0)
instances = FindAssets<UnityObject>(mInfo.DeclaringType);
if (instances.Count == 0)
{
StaticLog($"No instances of {mInfo.DeclaringType?.Name} found.", Level.Warning, Channel.EditorDebug);
continue;
}
// --| Invoke DI Registration Initializer -------
for (var j = 0; j < instances.Count; j++)
mInfo.Invoke(instances[j], null);
}
#if UNITY_EDITOR
}
finally
{
EditorApplication.update -= EditorUpdate;
StaticLog("DI Registration: Complete.", Level.Debug, Channel.EditorDebug);
}
#endif
}
// --| DI Injection Handler ---------------------------------
// --| ------------------------------------------------------
public void InjectionHandler()
{
var injectable = new List<Type>();
#if UNITY_EDITOR
try
{
EditorApplication.update += EditorUpdate;
injectable = TypeCache.GetTypesWithAttribute<InjectableAttribute>().ToList();
#else
injectable = RuntimeTypeCache.GetTypesWithAttribute<InjectableAttribute>().ToList();
#endif
for (var i = 0; i < injectable.Count; i++)
{
var instances = FindObjectsOfType(injectable[i]).ToList();
if (instances.Count == 0) continue;
for (var j = 0; j < instances.Count; j++)
Container.InjectAll(instances[j], instances[j].GetType());
}
#if UNITY_EDITOR
}
finally
{
EditorApplication.update -= EditorUpdate;
Log("DI Injection: Complete.", Level.Debug, Channel.EditorDebug);
}
#endif
}
// --| Scene Injection Performance Comparison ---------------
// --| ------------------------------------------------------
public void SceneInjection()
{
var injectable = new List<Type>();
#if UNITY_EDITOR
try
{
EditorApplication.update += EditorUpdate;
var testName = "SceneInjection";
Timer timer = new Timer("SceneSearch_Injection", typeof(DIContainer), true);
timer.Toggle(testName);
injectable = TypeCache.GetTypesWithAttribute<InjectableAttribute>().ToList();
#else
injectable = RuntimeTypeCache.GetTypesWithAttribute<InjectableAttribute>().ToList();
#endif
var objects = SceneManager.GetActiveScene().GetRootGameObjects();
for (var i = 0; i < objects.Length; i++)
{
var components = objects[i].GetComponents<MonoBehaviour>();
for (var j = 0; j < components.Length; j++)
{
if (!injectable.Contains(components[j].GetType())) continue;
Container.InjectAll(components[j], components[j].GetType());
#if UNITY_EDITOR
Log($"Injecting {components[j].name}", Level.Debug, Channel.EditorDebug);
#endif
}
}
#if UNITY_EDITOR
timer.Toggle(testName);
timer.PrintAll(Timer.DisplayType.Console);
}
finally
{
Log("Scene Injection Process: Complete", Level.Debug, Channel.EditorDebug);
EditorApplication.update -= EditorUpdate;
}
#endif
}
#if UNITY_EDITOR
// --| Editor Update Loop -------------------------
static void EditorUpdate() => EditorApplication.QueuePlayerLoopUpdate();
#endif
}
}
using System;
using instance.id.DataTypes;
using instance.id.Validator;
using UnityEngine;
namespace instance.id
{
[AttributeUsage(AttributeTargets.Class)]
public class InjectableAttribute : Attribute { }
[Injectable]
[Serializable] [ExecuteInEditMode]
public class InjectionTest : MonoBehaviour
{
[Inject]
public LoadingTrigger injectTest01;
[SerializeField]
private GameObject injectTest02;
public GameObject LoadingTrigger
{
get => injectTest02;
set => injectTest02 = value;
}
[Inject] [SerializeField]
private SectionCollection injectTest03;
[Inject]
internal void Inject(LoadingTrigger loadTrigger)
{
Debug.Log($"Injecting {loadTrigger.name}");
LoadingTrigger = loadTrigger.gameObject;
}
}
}
using System;
using instance.id.Collections;
using instance.id.DataTypes;
using UnityEngine;
namespace instance.id
{
[AttributeUsage(AttributeTargets.Method)]
public class DIInitializer : Attribute { }
[Serializable]
[CreateAssetMenu(menuName = "instance.id/Collections/Create SectionCollection", fileName = "SectionCollection", order = 0)]
public class SectionCollection : ScriptableObjectCollection<Section>
{
[DIInitializer]
public void RegisterDI()
{
id.di.RegisterInstance<SectionCollection>(CollectionsRegistry.Instance.TryGetCollectionOfType<SectionCollection>()).AsSelf();
#if UNITY_EDITOR
Debug.Log($"RegisterDI Invoked on {nameof(SectionCollection)}");
#endif
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment