VContainer
import {BenchmarkGraph} from "../../src/components/BenchmarkGraph"
import {GCAllocGraph} from "../../src/components/GCAllocGraph"
VContainer 是 Unity 游戏引擎中极快的 DI(依赖注入)工具。"V" 意味着让 Unity 的初始 "U" 变得更瘦、更坚固!
- 快速解析: 基本上比 Zenject 快 5-10 倍。
- 最小化 GC 分配: 在解析时,如果没有生成实例,我们实现了零分配。
- 代码量小: 内部类型少,.callvirt 调用少。
- 辅助正确的 DI 方式: 提供简单透明的 API,并精心选择功能。这可以防止 DI 声明变得过于复杂。
- 不可变容器: 线程安全和健壮性。
功能
- 构造函数注入 / 方法注入 / 属性和字段注入
- 在自定义 PlayerLoopSystem 上的纯 C# 入口点
- 灵活的作用域
- 应用程序可以自由创建嵌套的生命周期作用域,支持任何异步方式。
- 通过 Roslyn 源代码生成器加速模式
- 诊断窗口
- UniTask 集成
- ECS 集成 beta
Unity 中的 DI + 控制反转
DI 容器可以让我们将纯 C# 类作为入口点(而不是 MonoBehaviour)。这意味着控制流和其他领域逻辑可以与 MonoBehaviour 作为视图组件的功能分离开来。
进一步阅读:
- Manning | .NET 中的依赖注入
- Unity 的轻量级 IoC 容器 - Seba's Lab
性能
10,000 次迭代的基准测试结果(Unity 2019.x / IL2CPP Standalone macOS)
- 默认情况下,VContainer 和 Zenject 都在运行时使用反射。
- "VContainer (CodeGen)" 表示通过 ILPostProcessor 预生成注入方法的 IL 代码进行优化。更多信息请参见 优化 部分。
解析复杂测试用例中的 GC 分配结果(Unity Editor 分析)
基本用法
首先,创建一个作用域。在这里注册的类型会自动解析引用。
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>();}
}
类的定义如下:
public interface IRouteSearch
{
}public class AStarRouteSearch : IRouteSearch
{
}public class CharacterService
{readonly IRouteSearch routeSearch;public CharacterService(IRouteSearch routeSearch){this.routeSearch = routeSearch;}
}
public class ActorsView : MonoBehaviour
{
}
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(){// 在 VContainer 的自定义 PlayerLoopSystem 上调度 Start()。}
}
- 在这个例子中,当 CharacterService 被解析时,它的 routeSearch 会自动设置为 AStarRouteSearch 的实例。
- 此外,VContainer 可以将纯 C# 类作为入口点。(可以指定 Start、Update 等各种时机。)这有助于“领域逻辑与表现的分离”。
异步灵活作用域
LifetimeScope 可以动态创建子作用域。这使你可以处理游戏中常见的异步资源加载。
public void LoadLevel()
{// ... 加载一些资源// 创建子作用域instantScope = currentScope.CreateChild();// 使用 LifetimeScope 预制件创建子作用域instantScope = currentScope.CreateChildFromPrefab(lifetimeScopePrefab);// 创建带有额外注册的子作用域instantScope = currentScope.CreateChildFromPrefab(lifetimeScopePrefab,builder =>{// 额外注册 ...});instantScope = currentScope.CreateChild(builder =>{// 额外注册 ...});instantScope = currentScope.CreateChild(extraInstaller);
}public void UnloadLevel()
{instantScope.Dispose();
}
此外,你可以在附加场景中创建 LifetimeScope 的父子关系。
class SceneLoader
{readonly LifetimeScope currentScope;public SceneLoader(LifetimeScope currentScope){currentScope = currentScope; // 注入该类所属的 LifetimeScope}IEnumerator LoadSceneAsync(){// 在此块中生成的 LifetimeScope 将以 `this.lifetimeScope` 为父级using (LifetimeScope.EnqueueParent(currentScope)){// 如果此场景有 LifetimeScope,其父级将是 `parent`。var loading = SceneManager.LoadSceneAsync("...", LoadSceneMode.Additive);while (!loading.isDone){yield return null;}}}// UniTask 示例async UniTask LoadSceneAsync(){using (LifetimeScope.EnqueueParent(parent)){await SceneManager.LoadSceneAsync("...", LoadSceneMode.Additive);}}
}
// 在此块中生成的 LifetimeScope 将额外注册。
using (LifetimeScope.Enqueue(builder =>
{// 为尚未加载的下一个场景注册builder.RegisterInstance(extraInstance);
}))
{// 加载场景...
}
更多信息请参见 作用域。
UniTask
public class FooController : IAsyncStartable
{public async UniTask StartAsync(CancellationToken cancellation){await LoadSomethingAsync(cancellation);await ......}
}
builder.RegisterEntryPoint<FooController>();
更多信息请参见 集成。
诊断窗口
更多信息请参见 诊断。
入门
- 安装
- Hello World
- 与 Zenject 的比较