npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

com.playdarium.unity.reflex

v1.1.4

Published

Blazing fast, minimal but complete dependency injection library for Unity

Downloads

2,150

Readme

Blazing fast, minimal but complete dependency injection library for Unity

Reflex is an Dependency Injection framework for Unity. Making your classes independent of its dependencies, granting better separation of concerns. It achieves that by decoupling the usage of an object from its creation. This helps you to follow SOLID’s dependency inversion and single responsibility principles. Making your project more readable, testable and scalable.

NPM package License: MIT



👀 Overview

  • Fast: ~3x faster than VContainer, ~7x faster than Zenject.
  • GC Friendly: ~2x less allocations than VContainer, ~9x less allocations than Zenject.
  • AOT Support: Basically there's no runtime Emit, so it works fine on IL2CPP builds. [*]
  • Contract Table: Allows usages of APIs like container.All<IDisposable>
  • Immutable Container: Performant thread safety free of lock plus predictable behavior.

Compatible with the following platforms:

  • iOS
  • Android
  • Windows/Mac/Linux
  • PS4/PS5
  • Xbox One/S/X and Xbox Series X/S
  • WebGL

💾 Installation

Using the native Unity Package Manager introduced in 2017.2, you can add this library as a package by modifying your manifest.json file found at /ProjectName/Packages/manifest.json to include it as a dependency. See the example below on how to reference it.

Install via OpenUPM

The package is available on the npmjs registry.

Add registry scope

{
  "dependencies": {
    "com.playdarium.unity.reflex": "x.x.x"
  },
  "scopedRegistries": [
    {
      "name": "Playdarium",
      "url": "https://registry.npmjs.org",
      "scopes": [
        "com.playdarium.unity"
      ]
    }
  ]
}

Add package in PackageManager

Open Window -> Package Manager choose Packages: My Regestries and install package

Install via GIT URL

"com.playdarium.unity.reflex": "https://gitlab.com/pd-packages/entitas-redux.git#upm"

🚀 Getting Started

  1. Install Reflex
  2. Create ProjectInstaller.cs with
using Reflex.Core;
using UnityEngine;

public class ProjectInstaller : MonoBehaviour, IInstaller
{
    public void InstallBindings(ContainerBuilder builder)
    {
        builder.AddSingleton("Hello");
    }
}
  1. In unity project window
  2. Create directory Assets/Resources
  3. Select just created Resources dir
  4. Right click, Create → Reflex → ProjectScope
  5. With just created ProjectScope selected
  6. Add ProjectInstaller.cs created at step 2 as a component
  7. Create new scene Greet
  8. Add Greet to Build SettingsScenes In Build
  9. Create Greeter.cs with
using UnityEngine;
using System.Collections.Generic;
using Reflex.Attributes;

public class Greeter : MonoBehaviour
{
    [Inject] private readonly string[] _strings;

    private void Start()
    {
        Debug.Log(string.Join(" ", _strings));
    }
}
  1. Add Greeter.cs to any gameobject in Greet scene
  2. Inside Greet scene, create a new empty gameobject named SceneScope and attach SceneScope component
  3. Create GreetInstaller.cs with
using Reflex.Core;
using UnityEngine;

public class GreetInstaller : MonoBehaviour, IInstaller
{
    public void InstallBindings(ContainerBuilder builder)
    {
        builder.AddSingleton("World");
    }
}
  1. Add GreetInstaller.cs to Greet.unity SceneScope
  2. Create new scene Boot
  3. Add Boot to Build SettingsScenes In Build
  4. Create Loader.cs with
using Reflex.Core;
using UnityEngine;
using UnityEngine.SceneManagement;

public class Loader : MonoBehaviour
{
    private void Start()
    {
	// If you are loading scenes without addressables
	var scene = SceneManager.LoadScene("Greet", new LoadSceneParameters(LoadSceneMode.Single));
	ReflexSceneManager.PreInstallScene(scene, builder => builder.AddSingleton("Beautiful"));

	// If you are loading scenes with addressables
	Addressables.LoadSceneAsync("Greet", activateOnLoad: false).Completed += handle =>
	{
		ReflexSceneManager.PreInstallScene(handle.Result.Scene, builder => builder.AddSingleton("Beautiful"));
		handle.Result.ActivateAsync();
	};
    }
}
  1. Assign it to any gameobject at Boot scene
  2. Thats it, hit play while on Boot scene
  3. When Greet scene is loaded, there should be 3 instances implementing string contract
  4. So when Greeter::Start is called, you should see the following log in the unity console: Hello Beautiful world

🎬 Execution Order


🎯 Injection Strategy

Beginning from version 8.0.0, Reflex stops injecting all scenes automatically on Start, to start injecting only scenes with a SceneScope on Awake. This allows users to consume injected dependencies on callbacks such as Awake and OnEnable while giving more granular control on which scenes must be injected or not.


🌱 Container Hierarchy

Default Behaviour

Reflex's default strategy for creating containers involves initially generating a root project container. For each newly loaded scene, an additional container is created, which always inherits from the root project container. This container hierarchy mirrors the flat hierarchy of Unity scenes. You can see how the structure looks like below:

graph
ProjectContainer --> BootScene
ProjectContainer --> LobbyScene
ProjectContainer --> GameModeOneScene
ProjectContainer --> GameModeTwoScene

Scene Parent Override

If you want a scene to inherit services from another scene, you can use the ReflexSceneManager::OverrideSceneParentContainer method. This feature provides developers with more granular control over which parent container is used for each newly loaded scene.

// Scene Manager Sample
var bootScene = SceneManager.GetSceneByName("Boot");  
var sessionScene = SceneManager.LoadScene("Session", new LoadSceneParameters(LoadSceneMode.Additive));  
ReflexSceneManager.OverrideSceneParentContainer(scene: sessionScene, parent: bootScene.GetSceneContainer());

// Addessable Sample
var handle = Addressables.LoadSceneAsync("Session", LoadSceneMode.Additive, activateOnLoad: false);
await handle.Task;
var bootScene = SceneManager.GetSceneByName("Boot");
ReflexSceneManager.OverrideSceneParentContainer(scene: handle.Result.Scene, parent: bootScene.GetSceneContainer());
handle.Result.ActivateAsync();

By utilizing this API, you can create hierarchical structures such as the one shown below:

graph
ProjectContainer-->BootScene
BootScene-->LobbyScene
LobbyScene-->GameModeOneScene
LobbyScene-->GameModeTwoScene
  1. Please note that it is not possible to override the parent container for the initial scene loaded by Unity.
  2. Exercise caution when managing the scene lifecycle with this type of hierarchy. For example, unloading a parent scene before its child scenes can lead to unexpected behavior, as the parent container will be disposed while the child scenes are still active. As a general rule, always unload the scene hierarchy from the bottom up, starting with the child scenes and progressing to the parent scenes.

📦 Scopes

Container scoping refers to the ability of being able to create a container inheriting the registrations of its parent container while also being able to extend it.

Project Scope

It is root scope. It is created just before first scene opens by relying on [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] To register bindings to it, create a prefab, name it how you wish, the name is not used as a identifier, put it inside any Resources folder, and attach a "ProjectScope" component to it. Then, create your installer as MonoBehaviour and implement IInstaller interface. Remember to attach your installer to the ProjectScope prefab, as ProjectScope searches for every child implementing IInstaller when it's time to create the ProjectScope container. There's a menu item to ease the process: Assets > Create > Reflex > ProjectScope You can create multiple ProjectScope in many different Resources folders across the project, and when its time to create the project container, all active ProjectScope prefabs will be merged, this allow a better separation of concerns if required. Note that ProjectScope prefab is not required, in case Reflex does not find any ProjectScope, an empty root will be created. ProjectScope instance will be disposed once app closes/app quits.

Note that unity does not call OnDestroy deterministically, so rule of thumb is do not rely on injected dependencies on OnDestroy event functions.

Scene Scope

It is scoped from ProjectScope, inheriting all bindings from ProjectScope. It is created and injected before Awake. To register bindings to it, create a gameobject on desired scene, name it "SceneScope", put it as root game object, and attach a "SceneScope" component to it. Then, create your installer as MonoBehaviour and implement IInstaller interface. Remember to attach your installer to your SceneScope gameobject, as SceneScope searches for every child implementing IInstaller when it's time to create the SceneScope container. There's a menu item to ease the process: GameObject > Reflex > Scene Context Remember to have a single SceneScope to avoid undesired behaviour. Note that SceneScope gameobject is required only if you want its scene to be injected, in case Reflex do not find SceneScope, the scene injection will be skipped for that specific scene missing SceneScope. SceneScope instance will be disposed once scene is unloaded.

Note that unity does not call OnDestroy deterministically, so rule of thumb is do not rely on injected dependencies on OnDestroy event functions.

Manual Scoping

using var scopedContainer = parentContainer.Scope(builder =>  
{  
  // Extend your scoped container by adding extra registrations here  
});

🔩 Bindings

AddSingleton (From Type)

ContainerBuilder::AddSingleton(Type concrete, params Type[] contracts)

Adds a deferred object creation based on the type to be constructed and its contracts. The object will be constructed lazily, once first request to resolve any of its contracts is called. Then same object will always be returned. If object implements IDisposable it will be disposed when its parent Container are disposed. There's no need to pass IDisposable as contract to have your object disposed, however, if you want to retrieve all IDisposable by any API Single<TContract>, Resolve<TContract> or All<TContract> then yes, you have to specify it.

AddSingleton (From Value)

ContainerBuilder::AddSingleton(object instance, params Type[] contracts)

Adds an object already constructed by the user to the container as a singleton, everytime the contracts given is asked to be resolved, the same object will be returned. If object implements IDisposable it will be disposed when its parent Container are disposed. There's no need to pass IDisposable as contract to have your object disposed, however, if you want to retrieve all IDisposable by any API Single<TContract>, Resolve<TContract> or All<TContract> then yes, you have to specify it.

AddSingleton (From Factory)

ContainerBuilder::AddSingleton<T>(Func<Container, T> factory, params Type[] contracts)

Adds a deferred object creation based on the given factory and its contracts. The object will be constructed lazily, once first request to resolve any of its contracts is called. The factory will be ran once, and then the same object will always be returned. If object implements IDisposable it will be disposed when its parent Container are disposed. There's no need to pass IDisposable as contract to have your object disposed, however, if you want to retrieve all IDisposable by any API Single<TContract>, Resolve<TContract> or All<TContract> then yes, you have to specify it.

AddTransient (From Type)

ContainerBuilder::AddTransient(Type concrete, params Type[] contracts)

Adds a deferred object creation based on the type to be constructed and its contracts. The object will be constructed lazily, once first request to resolve any of its contracts is called. Then for any request of any contract, a new object will be created, use this carefully. If object implements IDisposable it will be disposed when the container who constructed the instance are disposed, and eventually collected when GC kicks in. There's no need to pass IDisposable as contract to have your object disposed, however, if you want to retrieve all IDisposable by any API Single<TContract>, Resolve<TContract> or All<TContract> then yes, you have to specify it.

AddTransient (From Factory)

ContainerBuilder::AddTransient(Func<Container, T> factory, params Type[] contracts)

Adds a deferred object creation based on the given factory and its contracts. The object will be constructed lazily, once first request to resolve any of its contracts is called. Then for any request of any contract, a new object will be created, use this carefully. If object implements IDisposable it will be disposed when the container who constructed the instance are disposed, and eventually collected when GC kicks in. There's no need to pass IDisposable as contract to have your object disposed, however, if you want to retrieve all IDisposable by any API Single<TContract>, Resolve<TContract> or All<TContract> then yes, you have to specify it.

AddScoped (From Type)

ContainerBuilder::AddScoped(Type concrete, params Type[] contracts)

Very similar to AddSingleton API, however, instead of having a single global instance, AddScoped creates one object instance per container. If object implements IDisposable it will be disposed when the container who constructed the instance are disposed, and eventually collected when GC kicks in. There's no need to pass IDisposable as contract to have your object disposed, however, if you want to retrieve all IDisposable by any API Single<TContract>, Resolve<TContract> or All<TContract> then yes, you have to specify it.

AddScoped (From Factory)

ContainerBuilder::AddScoped(Func<Container, T> factory, params Type[] contracts)

Very similar to AddSingleton API, however, instead of having a single global instance, AddScoped creates one object instance per container. If object implements IDisposable it will be disposed when the container who constructed the instance are disposed, and eventually collected when GC kicks in. There's no need to pass IDisposable as contract to have your object disposed, however, if you want to retrieve all IDisposable by any API Single<TContract>, Resolve<TContract> or All<TContract> then yes, you have to specify it.

🔍 Resolving

Constructor

If your type is non-mono, and it's going to be created by the container, then the most recommended way to inject dependencies into it its by constructor injection. It's simply as just requesting the contracts you need as following example:

private class Foo
{  
	...
  
	public NumberManager(IInputManager inputManager, IEnumerable<IManager> managers)  
	{  
		...
	}  
}

Note that constructor injection relies on Resolve<TContract> API, so in case there's two objects with IInputManager contract, the last one will be injected.

Attribute

Attribute injection is the way to go for MonoBehaviours. You can use it to inject fields, writeable properties and methods like following:

class Foo : MonoBehaviour  
{  
	[Inject] private readonly IInputManager _inputManager;  
	[Inject] public IManager[] Managers { get; private set; }
  
	[Inject]  
	private void Inject(int[] numbers) // Method name here does not matter  
	{  
	  ...
	}  
}

Note that attribute injection also works on non-mono classes.

Single

Container::Single<TContract> actually validates that there's a single binding implementing given contract, and returns it. If there's more than one the following exception will be thrown.

InvalidOperationException: Sequence contains more than one element

It's recommended for every binding that you know that there should be a single binding implementing the contract.

Resolve

Container::Single<TContract> runs no validations, and return the last valid object implementing given contract.

All

Container::All<TContract> returns all objects implementing given contract. Example:

private void Documentation_Bindings()  
{
	var container = new ContainerBuilder()
		.AddSingleton(1)
		.AddSingleton(2)
		.AddSingleton(3)
		.Build();

	Debug.Log(string.Join(", ", container.All<int>())); // Prints: 1, 2, 3
}

🪝 Callbacks

ContainerBuilder::OnContainerBuilt

OnContainerBuilt is a instance callback of ContainerBuilder, it is called once the container is fully built and initialized properly.


🔖 Attributes

InjectAttribute

Should be used to inject fields, writeable properties and methods like following:

class Foo : MonoBehaviour  
{  
	[Inject] private readonly IInputManager _inputManager;  
	[Inject] public IEnumerable<IManager> Managers { get; private set; }  
  
	[Inject]  
	private void Inject(IEnumerable<int> numbers) // Method name here does not matter  
	{  
	  ...
	}  
}

Note that InjectAttribute also works on non-mono classes.

ReflexConstructorAttribute

Can be placed on constructors, telling reflex which constructor to use when instantiating an object. By default its not required, as usually injected classes have a single constructor, so by default reflex tries to find the constructor with most arguments. But sometimes this can be required if you need more granular control on which construtor reflex should use.


💉 Manual Injection

If objects (plain old c# objects or unity objects) are created during runtime, theres no way reflex can detect this creation to auto inject the object, this needs to be done manually using one of the following methods:

AttributeInjector::void Inject(object obj, Container container)
// Injects given object fields, properties and methods that was annotated with Inject attribute
ConstructorInjector::object Construct(Type concrete, Container container)
// construct object of given type, using the constructor with most parameters, using given container to pull the constructor arguments
GameObjectInjector::void InjectSingle(GameObject gameObject, Container container)
// Optimized code meant to find injectables (MonoBehaviours) from a given GameObject, to then, inject using AttributeInjector
// This option injects only the first MonoBehaviour found on the given GameObject
GameObjectInjector::void InjectObject(GameObject gameObject, Container container)
// Optimized code meant to find injectables (MonoBehaviours) from a given GameObject, to then, inject using AttributeInjector
// This option injects all MonoBehaviours found on the given GameObject (not recursively, so it does not account for children) 
GameObjectInjector::void InjectRecursive(GameObject gameObject, Container container)
// Optimized code meant to find injectables (MonoBehaviours) from a given GameObject, to then, inject using AttributeInjector
// This option injects all MonoBehaviours found on the given GameObject and its childrens recursively 
GameObjectInjector::void InjectRecursiveMany(List<GameObject> gameObject, Container container)
// Optimized code meant to find injectables (MonoBehaviours) from a given GameObject, to then, inject using AttributeInjector
// This option injects all MonoBehaviours found on the given list of GameObject and its childrens recursively 

Components

An alternative approach is to utilize the GameObjectSelfInjector, which can be attached to a prefab to resolve its dependencies at runtime. Through the inspector, you can select the injection strategy: Single, Object, or Recursive. Each strategy invokes the corresponding method in the GameObjectInjector class.


🧩 Extensions

// Allows you to get a scene container, allowing you to resolve/inject dependencies in a different way during runtime
SceneExtensions::GetSceneContainer(this Scene scene)
{
    return UnityInjector.ContainersPerScene[scene];
}

// Usage example:
var foo = gameObject.scene.GetSceneContainer().Resolve<IFoo>();

🐛 Debugger

It can be accessed from menu item Window → Analysis → Reflex Debugger, or from shortcut CTRL + E.
To enable reflex debug mode you must go to Edit → Project Settings → Player, then in the Other Settings panel, scroll down to Script Compilation → Scripting Define Symbols and add REFLEX_DEBUG. This can be easily achieved by clicking on the bug button at bottom right corner inside Reflex Debugger Window.

Note that debug mode reduces performance and increases memory pressure, so use it wisely.

Preview

Legend

| Icon | Name | Description | |---------------------------------------------------------------------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | | Name taken from Name property of a Container instance. Scene containers uses scene.name + scene.GetHashCode(), so you can differentiate between two instances of the same opened scene. | Represents a container, containers has a collection of bindings | | | Name created from the array of contracts you described your binding. | Represents a binding, bindings has a collection of instances, singleton will have only one instance, transients can have many instances and factories depends on your implementation | | | Name taken from Name property of the Type of the concrete object. | Represents a instance, it's the concrete object that were created by the parent binding and it's being injected to consumers |

Debugger window allows you to inspect the following:

  • Hierarchy of Containers, Bindings and Instances
  • Binding Contracts, Kind and Lifetime
  • Binding Resolution Count
  • Container construction call stack (who created the container)
  • Binding construction call stack (who created the binding)
  • Instance construction call stack (who resolved the binding making selected instance to be instantiated)

🪛 Settings

It's a ReflexSettings scriptable object instance, named ReflexSettings that should live inside a Resources folder. It can be created by asset menu item Assets → Create → Reflex → Settings.

Currently, logging verbosity is configured in this file, and default value is set to Info

Non-obligatory to have but projects without it will fallback using default settings


📊 Performance

Resolving ten thousand times a transient dependency with four levels of chained dependencies. See NestedBenchmarkReflex.cs.

Android + Mono

| | GC | Time | GC Ratio | Time Ratio | |-----------|--------:|--------:|---------:|-----------:| | Reflex | 54.7KB | 9.3ms | 1x | 1x | | Zenject | 512KB | 63.2ms | 9.36x | 6.79x | | VContainer| 128.9KB | 29.8ms | 2.35x | 3.20x |

Android + IL2CPP

| | GC | Time | GC Ratio | Time Ratio | |-----------|--------:|-------:|---------:|-----------:| | Reflex | 140.6KB | 7.4ms | 1x | 1x | | Zenject | 1024KB | 23.6ms | 7.28x | 3.18x | | VContainer| 257.8KB | 9.2ms | 1.83x | 1.24x |

Windows + Mono

| | GC | Time | GC Ratio | Time Ratio | |-----------|--------:|-------:|---------:|-----------:| | Reflex | 109.4KB | 1.2ms | 1x | 1x | | Zenject | 1024KB | 9.2ms | 9.36x | 7.66x | | VContainer| 257.8KB | 3.3ms | 2.35x | 2.75x |

Windows + IL2CPP

| | GC | Time | GC Ratio | Time Ratio | |-----------|--------:|-------:|---------:|-----------:| | Reflex | 140.6KB | 2.9ms | 1x | 1x | | Zenject | 1024KB | 9.3ms | 7.28x | 3.20x | | VContainer| 257.8KB | 5.1ms | 1.83x | 1.75x |

🤝 Support

Ask your questions and participate in discussions regarding Reflex related and dependency injection topics at the Reflex Discord server.


📜 License

Reflex is distributed under the terms of the MIT License. A complete version of the license is available in the LICENSE file in this repository. Any contribution made to this project will be licensed under the MIT License.