環境
- Unity 2020.3.25 LTS
- VContainer 1.10.0
解決法
[Inject]されるだけで他のクラスに注入されないクラスなら EntryPoint を呼べるので、
必要な EntryPoint へのインターフェイスだけを付けたクラスを用意してそのクラスに注入し、間接的に動かしてもらえばよい。
状況
UniRx + VContainerを使ってMV(R)Pで設計を進めると、基本的にはViewはuGUIと紐づくので MonoBehaviour に、それ以外をできるだけピュアC#クラスにしてイベント駆動する、という方針になるかと思います。
(私もまだ初心者なので何がベストプラクティスなのかは把握できていません)
表示されるものにだけ MonoBehaviour をつけて、それ以外はピュアC#クラスで書いておき、依存関係はすべてDIに任せる、整った世界ですね。
ところで、JobSystemなどで「1フレーム以内には終わらない処理なので定期的に進捗を確認する」というような処理をMVPのどれが担当するかを考えると、ロジックなのでModelに持たせたくなると思います。
MonoBehaviour なしに PlayerLoop に相当するタイミングで任意のC#クラスを動かす仕組みとして、VContainer は EntryPoint を提供しています。
じゃあそれを使おう、ということで Model クラスに Update() のタイミングで呼ばれるインターフェイス、Tick() をつけて、無事……動きません。
原因
VContainer に依存関係を教える LifeTimeScope 内では、注入用のクラスの定義とEntryPoint用のクラスの定義が別になっているためです。
using VContainer;
using VContainer.Unity;
public sealed class YourLifeTimeScope : LifeTimeScope
{
protected override void Configure(IContainerBuilder builder)
{
// 注入されるクラスの生成方法の定義
builder.Register<Model>(LifeTime.Singleton);
// EntryPoint を呼ぶためのクラスの定義
builder.RegisterEntryPoint<Presenter>();
// 両方書くことはできるが Register<T>() とは別にもう一つ生成され、
// DI されるものと EntryPoint されるものが異なる状態になる
builder.RegisterEntryPoint<Model>();
// Scene 中に配置した GameObject についている Component に
// Register<T>() 済みのクラスを注入するための定義
builder.RegisterComponentInHierarchy<View>()
}
}
理由としては、EntryPoint が呼ばれるクラスをLifeTime.Singleton以外で好き勝手生成していたら、副作用が生じる関数を実装していた場合実行回数が定まらなくなり大変なことになってしまうので、EntryPoint は専用で管理して注入用のクラスとは別けておく設計にしたと考えられます。
対策
要は、「ほかのクラスに注入するクラスにEntryPointをつけなければいい」というわけで、
EntryPoint を呼ぶための専用のクラス、上図では ModelEntryPoint を作り、Model は ModelEntryPoint に注入して間接的に動かしてもらえばよい、ということになります。
一応動かすだけなら
-
ModelもMonobehaviourにして Unity標準のPlayerLoopを使う
(ただしSceneに配置しておく必要はある) -
PresenterにEntryPointの定義も任せる
(ただしPresenterの本来の責務: イベント処理の結合を定義 とは関連の薄い処理が混ざる)
といった方法もありますが、クリーンな設計を保つ意味でこのやり方がよいと思います。
参考
Official:
VContainer
