今回は、VContainer とUniRxを使って、一定間隔でメッセージを送信する処理を作ってみました。しかし、以下のエラーが発生し、うまく動作しませんでした。
VContainerException: Failed to resolve Assets.Script.MyObserver : No such registration of type: Assets.Script.MySubject
VContainer.Internal.ReflectionInjector.CreateInstance (VContainer.IObjectResolver resolver, System.Collections.Generic.IReadOnlyList`1[T] parameters)
原因を考えていきます。
- Windows10 Home 64bit
- Unity Version 2020.3.17f1 Personal
- Visual Studio Community 2019 16.11.2
- VContainer 1.11.1
- UniRx 7.1
失敗したコード
MySubject.cs
1秒間隔でメッセージを発行します。
using System;
using UniRx;
using VContainer.Unity;
namespace Assets.Script
{
public class MySubject : IStartable
{
private Subject<int> intervalSubject = new Subject<int>();
public IObservable<int> IntervalObservable => intervalSubject;
private int count = 0;
public void Start()
{
// 1秒間隔で送信回数を送信する
Observable
.Interval(System.TimeSpan.FromSeconds(1))
.Subscribe(_ =>
{
count++;
intervalSubject.OnNext(count);
});
}
}
}
MyObserver.cs
メッセージを受信したら、コンソールに出力します。
using UniRx;
using UnityEngine;
using VContainer;
using VContainer.Unity;
namespace Assets.Script
{
class MyObserver : IStartable
{
[Inject]
private MySubject mySubject;
public void Start()
{
mySubject.IntervalObservable.Subscribe(count =>
{
Debug.Log("Timeout, " + count);
});
}
}
}
MyLifeTimeScope.cs
LifeTimeScope です。MyObserverと、MySubject をEntryPoint に登録します。
using VContainer;
using VContainer.Unity;
namespace Assets.Script
{
class MyLifeTimeScope : LifetimeScope
{
protected override void Configure(IContainerBuilder builder)
{
builder.RegisterEntryPoint<MyObserver>();
builder.RegisterEntryPoint<MySubject>();
}
}
}
このスクリプトを空のゲームオブジェクトにコンポーネントとして追加します。
それでUnityをPlayモードとしたら、エラーが発生しました。最初に書いたエラーが発生しました。
RegisterEntryPoint として登録するとInjectされない
いろいろ試行錯誤して分かりました。VContainerでRegisterEntryPoint として登録すると、Inject されないようです。
Register として登録するとInjectされるようです。が、そうすると、Start()が呼ばれなくなります。
LifeTimeScopeにRegister 登録を追加
MyLifeTimeScope.cs にMySubjectをRegisterに追加するコードを書きました。
builder.RegisterEntryPoint<MyObserver>();
builder.Register<MySubject>(Lifetime.Singleton); // 追加
builder.RegisterEntryPoint<MySubject>();
このようにすると、先ほどのエラーは解消されました。
しかし、コンソールに何も表示されませんでした。
MySubject でメッセージ送信関数を読んでます。ところが、MyObject で受信していません。
そもそも、MySubjectのintervalSubject に登録されていないようです。MyObserver で登録をしていることは確認しました。どうしてだろうか?
ここもしばらく試行錯誤しました。
結果、MySubject が2つ作られていることが分かりました。VContainer で2回登録しているからです。MyObjserverから登録したMySubjectと、定期間隔でメッセージを送るMySubjectは別物でした。なので、こんな現象が発生したのです。
どうすればいいのだろうか?
一番簡単な解決方法は、intervalSubjectをstatic にすることです。
private static Subject<int> intervalSubject = new Subject<int>();
static にすると、コンソールに結果が表示されました。
しかし、あまりいい方法とはいえません。設計方針が変わっています。
2つのMySubjectがグローバルのintervalSubject を参照しています。数週間後、数年後は、MySubjectが2つ存在することは忘れられているでしょう。
intervalSubject を管理するクラスを作成するのが、1つの解決法と言えそうです。
しかし、なんとかクラスを増やさずやってみたいとも思います。設計を見直した方がいいのか?
コメント