Skip to content

Instantly share code, notes, and snippets.

@Zeroto
Last active August 29, 2015 14:03
Show Gist options
  • Save Zeroto/7c3b15deda0121ff965e to your computer and use it in GitHub Desktop.
Save Zeroto/7c3b15deda0121ff965e to your computer and use it in GitHub Desktop.
Basic DI container for Unity
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
namespace UnityDI
{
public class DIContainer
{
/// <summary>
/// Create a new container
/// </summary>
/// <returns>The newly created container</returns>
static public DIContainer Create()
{
return new DIContainer();
}
private static Dictionary<string, DIContainer> containers = new Dictionary<string, DIContainer>();
// this is used to support multiple containers. when instantiating from a container this is set so the monobehaviour DI can do its work
private static DIContainer activeContainer;
/// <summary>
/// The last used container for instantiation. This is set when instantiating prefabs.
/// </summary>
public static DIContainer ActiveContainer { get { return activeContainer; } }
/// <summary>
/// Get a DIContainer by name. This will create the container if it does not exist
/// </summary>
/// <param name="identifier">The name of the container</param>
/// <returns>The contianer</returns>
static public DIContainer GetContainer(string identifier)
{
DIContainer container;
if (!containers.TryGetValue(identifier, out container))
{
container = Create();
containers[identifier] = container;
}
return container;
}
// check for available configuration functions. These are marked with the DIConfiguration attribute
static DIContainer()
{
// find static function with DIConfiguration attribute
List<MethodInfo> methods = new List<MethodInfo>();
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (var assembly in assemblies)
{
// ignore default assemblies
if (assembly.FullName.StartsWith("System") || assembly.FullName.StartsWith("UnityEngine"))
continue;
Type[] types = new Type[0];
try
{
types = assembly.GetTypes();
}
catch (ReflectionTypeLoadException e)
{
// this can happen because of security limitations. e.types has the types it did manage to load, but might have null entries.
types = e.Types;
}
foreach (var type in types)
{
if (type == null)
continue;
foreach (var method in type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic))
{
var attributes = method.GetCustomAttributes(typeof(DIConfigurationAttribute), false);
if (attributes.Length > 0)
methods.Add(method);
}
}
}
// execute all methods. If they return a DIContainer, then store it as the activeContainer. This is used for objects that are in the scene.
foreach (var method in methods)
{
var result = method.Invoke(null, null) as DIContainer;
if (result != null)
activeContainer = result;
}
}
private DIContainer()
{
// We add ourself as a service so other services can get a reference to the container they are in
services.Add(typeof(DIContainer), this);
}
private Dictionary<Type, object> services = new Dictionary<Type, object>();
/// <summary>
/// Instantiate a prefab
/// </summary>
/// <typeparam name="T">The prefab type</typeparam>
/// <param name="prefab">The prefab to instantiate</param>
/// <param name="position">The position</param>
/// <param name="rotation">The rotation</param>
/// <returns>The newly instantiated object</returns>
public T Instantiate<T>(T prefab, Vector3 position, Quaternion rotation) where T: UnityEngine.Object
{
activeContainer = this;
T result = (T)GameObject.Instantiate(prefab, position, rotation);
return result;
}
/// <summary>
/// Get a service from the container
/// </summary>
/// <param name="t">The service type</param>
/// <returns>The service if available or null otherwise</returns>
public object Get(Type t)
{
object result;
services.TryGetValue(t, out result);
return result;
}
/// <summary>
/// Get a service from the container
/// </summary>
/// <typeparam name="T">The service type</typeparam>
/// <returns>The service if available or null otherwise</returns>
public T Get<T>()
{
return (T)Get(typeof(T));
}
/// <summary>
/// Register a service with the container
/// </summary>
/// <typeparam name="T">The type to bind to. The service given needs to be assignable to this type</typeparam>
/// <param name="service">The service object to bind</param>
/// <exception cref="ArgumentException">Thrown when the service can't be cast the to type it binds to</exception>
public void Register<T>(object service)
{
if (!(service is T))
{
throw new ArgumentException("Service can't be cast to " + typeof(T).FullName);
}
object existing;
Type t = typeof(T);
services.TryGetValue(t, out existing);
if (existing != null)
{
throw new AlreadyExistingException();
}
services[t] = service;
}
internal void InjectProperties(object obj)
{
var type = obj.GetType();
var props = type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
foreach (var prop in props)
{
var attr = prop.GetCustomAttributes(typeof(ServiceAttribute), true);
if (attr.Length > 0)
{
var t = prop.PropertyType;
var s = this.Get(t);
prop.SetValue(obj, s, null);
}
}
}
/// <summary>
/// Create an instance of an object and inject services. If the object implements IService, then Init will be called on it.
/// </summary>
/// <typeparam name="T">The type to instantiate</typeparam>
/// <returns>The newly instantiated object</returns>
public T CreateInstance<T>() where T: new()
{
var result = new T();
InjectProperties(result);
IService service = result as IService;
if (service != null)
service.Init();
return result;
}
}
}
using System.Reflection;
namespace UnityDI
{
public class MonoBehaviourDI : ComponentDI.MonoBehaviourDI
{
protected override void Awake()
{
base.Awake();
if (DIContainer.ActiveContainer == null)
return;
DIContainer.ActiveContainer.InjectProperties(this);
}
}
}
namespace UnityDI
{
interface IService
{
/// <summary>
/// Called after DI properties injection
/// </summary>
void Init();
}
}
using System;
namespace UnityDI
{
class DIConfigurationAttribute : Attribute
{
}
class ServiceAttribute : Attribute
{
}
}
using System;
namespace UnityDI
{
class AlreadyExistingException : Exception
{
}
}
using BetterLogging;
using Events;
using log4net.Core;
using Superman.Game;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityDI;
namespace Superman
{
class Configuration
{
[DIConfiguration]
static DIContainer DIConfig()
{
DIContainer container = DIContainer.GetContainer("game");
container.Register<EventManager>(new EventManager());
container.Register<ObstacleService>(container.CreateInstance<ObstacleService>());
container.Register<ObstacleSpawner>(container.CreateInstance<ObstacleSpawner>());
container.Register<IBackgroundGenerator>(container.CreateInstance<ParkBackgroundGenerator>());
container.Register<World>(container.CreateInstance<World>());
return container;
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityDI;
using UnityEngine;
namespace Superman.Game
{
class World
{
private enum State
{
Stopped,
Running
}
[Service]
ObstacleService ObstacleService { get; set; }
[Service]
ObstacleSpawner ObstacleSpawner { get; set; }
[Service]
IBackgroundGenerator BackgroundGenerator { get; set; }
public Vector2 ObstacleSpeed { get; set; }
private State state = State.Running;
public void Tick()
{
if (state == State.Running)
{
ObstacleService.MoveObstactles(ObstacleSpeed);
ObstacleSpawner.Tick();
BackgroundGenerator.Tick();
}
}
public void Pause()
{
state = State.Stopped;
ObstacleService.Pause();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment