Skip to content

Latest commit

 

History

History
249 lines (192 loc) · 7.17 KB

File metadata and controls

249 lines (192 loc) · 7.17 KB
id what-is-vcontainer
sidebar_label What is VContainer
title About
description VContainer - the fastest DI (Dependency Injection) for Unity Game Engine
slug /
hide_table_of_contents false
hide_title true

VContainer

import {BenchmarkGraph} from "../../src/components/BenchmarkGraph" import {GCAllocGraph} from "../../src/components/GCAllocGraph"

The extra fast DI (Dependency Injection) for Unity Game Engine. "V" stands for making Unity's initial "U" thinner and more 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.
  • Promotes correct DI patterns: Provides a simple and transparent API with carefully selected features. This prevents DI declarations from becoming overly complex.
  • Immutable Container: Thread safety and robustness.

Features

DI + Inversion of Control for Unity

With DI containers, we can make pure C# classes the entry point (instead of 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, 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 Usage

First, create a scope. References are automatically resolved for types registered here.

public class GameLifetimeScope : LifetimeScope
{
    protected override void Configure(IContainerBuilder builder)
    {
        builder.RegisterEntryPoint<ActorPresenter>();

        builder.Register<CharacterService>(Lifetime.Scoped);
        builder.Register<IRouteSearch, AStarRouteSearch>(Lifetime.Singleton);

        builder.RegisterComponentInHierarchy<ActorsView>();
    }
}

Where the class definitions 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 field of CharacterService is automatically set as the instance of AStarRouteSearch when CharacterService is resolved.
  • Furthermore, VContainer can use 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 async

LifetimeScope 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 a 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)
    {
        this.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(currentScope))
        {
            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..
}

See scoping for more information.

UniTask

public class FooController : IAsyncStartable
{
    public async UniTask StartAsync(CancellationToken cancellation)
    {
        await LoadSomethingAsync(cancellation);
        await ...
        ...
    }
}
builder.RegisterEntryPoint<FooController>();

See integration for more information.

Diagnostics Window

See diagnostics for more information.

Getting Started