VContainer
The extra fast DI (Dependency Injection) for Unity Game Engine. "V" means making Unity's initial "U" more thinner and solid..!
- Fast Resolve: Basically 5-10x faster than Zenject.
- Minimum GC Allocation: In Resolve, we have zero allocation without spawned instances.
- Small code size: Few internal types and few .callvirt.
- Assisting correct DI way: Provides simple and transparent API, and carefully select features. This prevents the DI declaration from becoming overly complex.
- Immutable Container: Thread safety and robustness.
#
Features- Constructor Injection / Method Injection / Property & Field Injection
- Plain C# entry point on own PlayerLoopSystem
- Flexible scoping
- Application can freely create nested Lifetime Scope with any async way for you like.
- Pre IL Code generation optimization mode
- UniTask Integration
- ECS Integration beta
#
DI + Inversion of Control for UnityDI containers we can make pure C # classes the entry point (not MonoBehaviour). This means that the control flow and other domain logic can be separated from the function of MonoBehaviour as a view component.
Further reading:
#
Performance#
Benchmark result for 10,000 iterations for each test case (Unity 2019.x / IL2CPP Standalone macOS)- By default, both VContainer and Zenject use reflection at runtime.
- "VContainer (CodeGen)" means optimization by pre-generating IL code of Inject methods by ILPostProcessor. See Optimization section for more information.
#
GC Alloc result in the Resolve Complex test case (Unity Editor profiled)#
Basic UsageFirst, create a scope. References are automatically resolved for types registered here.
public class GameLifetimeScope : LifetimeScope{ public override void Configure(IContainerBuilder builder) { builder.RegisterEntryPoint<ActorPresenter>();
builder.Register<CharacterService>(Lifetime.Scoped); builder.Register<IRouteSearch, AStarRouteSearch>(Lifetime.Singleton);
builder.RegisterComponentInHierarchy<ActorsView>(); }}
Where definitions of classes are
public interface IRouteSearch{}
public class AStarRouteSearch : IRouteSearch{}
public class CharacterService{ readonly IRouteSearch routeSearch;
public CharacterService(IRouteSearch routeSearch) { this.routeSearch = routeSearch; }}
public class ActorsView : MonoBehaviour{}
and
public class ActorPresenter : IStartable{ readonly CharacterService service; readonly ActorsView actorsView;
public ActorPresenter( CharacterService service, ActorsView actorsView) { this.service = service; this.actorsView = actorsView; }
void IStartable.Start() { // Scheduled at Start () on VContainer's own PlayerLoopSystem. }}
- In this example, the routeSearch of CharacterService is automatically set as the instance of AStarRouteSearch when CharacterService is resolved.
- Further, VContainer can have a Pure C# class as an entry point. (Various timings such as Start, Update, etc. can be specified.) This facilitates "separation of domain logic and presentation".
#
Flexible Scoping with asyncLifetimeScope can dynamically create children. This allows you to deal with the asynchronous resource loading that often occurs in games.
public void LoadLevel(){ // ... Loading some assets
// Create a child scope instantScope = currentScope.CreateChild();
// Create a child scope with LifetimeScope prefab instantScope = currentScope.CreateChildFromPrefab(lifetimeScopePrefab);
// Create a child with additional registration instantScope = currentScope.CreateChildFromPrefab( lifetimeScopePrefab, builder => { // Extra Registrations ... });
instantScope = currentScope.CreateChild(builder => { // ExtraRegistrations ... });
instantScope = currentScope.CreateChild(extraInstaller);}
public void UnloadLevel(){ instantScope.Dispose();}
In addition, you can create a parent-child relationship with LifetimeScope in an Additive scene.
class SceneLoader{ readonly LifetimeScope currentScope;
public SceneLoader(LifetimeScope currentScope) { currentScope = currentScope; // Inject the LifetimeScope to which this class belongs }
IEnumerator LoadSceneAsync() { // LifetimeScope generated in this block will be parented by `this.lifetimeScope` using (LifetimeScope.EnqueueParent(currentScope)) { // If this scene has a LifetimeScope, its parent will be `parent`. var loading = SceneManager.LoadSceneAsync("...", LoadSceneMode.Additive); while (!loading.isDone) { yield return null; } } }
// UniTask example async UniTask LoadSceneAsync() { using (LifetimeScope.EnqueueParent(parent)) { await SceneManager.LoadSceneAsync("...", LoadSceneMode.Additive); } }}
// LifetimeScopes generated during this block will be additionally Registered.using (LifetimeScope.Enqueue(builder =>{ // Register for the next scene not yet loaded builder.RegisterInstance(extraInstance);})){ // Loading the scene..}
#
UniTaskpublic class FooController : IAsyncStartable{ public async UniTask StartAsync(CancellationToken cancellation) { await LoadSomethingAsync(cancellation); await ... ... }}
builder.RegisterEntryPoint<FooController>();