【Unity2018】Entity Component System(ECS)とUniRx.Asyncを和解させる
問題点
ECSを使っているプロジェクトにUniRxをぶちこむと、ECSとUniRxのPlayerLoopが喧嘩してUniRxが勝ちます(ECSが動かなくなります)
やはりECSでも(相性が良いかはともかく)Rxしたいので、UniRxを使う方法を探ってみました。
内部のコードを読む
PlayerLoopにGetCurrentがないため、UniRxとECSがそれぞれ初期化時にnew PlayerLoopSystem
した上で、PlayerLoop.SetPlayerLoop(playerLoop)
しています。
どちらもRuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)
のタイミングで呼ばれていますが、たまたまUniRxが後に呼ばれているため、そちらのPlayerLoopが優先されているようです。
ECS陣営
GameBootstrap.cs
using UnityEngine; namespace Unity.Entities { #if !UNITY_DISABLE_AUTOMATIC_SYSTEM_BOOTSTRAP static class AutomaticWorldBootstrap { [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] static void Initialize() { DefaultWorldInitialization.Initialize("Default World", false); } } #endif }
ScriptBehaviourUpdateOrder.cs
private static void SetPlayerLoop(PlayerLoopSystem playerLoop) { PlayerLoop.SetPlayerLoop(playerLoop); currentPlayerLoop = playerLoop; }
public static void UpdatePlayerLoop(params World[] worlds) { var defaultLoop = PlayerLoop.GetDefaultPlayerLoop(); if (worlds?.Length > 0) { var ecsLoop = InsertWorldManagersInPlayerLoop(defaultLoop, worlds.Where(x => x != null).ToArray()); SetPlayerLoop(ecsLoop); } else { SetPlayerLoop(defaultLoop); } }
UniRx陣営
PlayerLoopHelper.cs
public static void Initialize(ref PlayerLoopSystem playerLoop) { yielders = new ContinuationQueue[7]; runners = new PlayerLoopRunner[7]; var copyList = playerLoop.subSystemList.ToArray(); copyList[0].subSystemList = InsertRunner(copyList[0], typeof(UniTaskLoopRunners.UniTaskLoopRunnerYieldInitialization), yielders[0] = new ContinuationQueue(), typeof(UniTaskLoopRunners.UniTaskLoopRunnerInitialization), runners[0] = new PlayerLoopRunner()); copyList[1].subSystemList = InsertRunner(copyList[1], typeof(UniTaskLoopRunners.UniTaskLoopRunnerYieldEarlyUpdate), yielders[1] = new ContinuationQueue(), typeof(UniTaskLoopRunners.UniTaskLoopRunnerEarlyUpdate), runners[1] = new PlayerLoopRunner()); copyList[2].subSystemList = InsertRunner(copyList[2], typeof(UniTaskLoopRunners.UniTaskLoopRunnerYieldFixedUpdate), yielders[2] = new ContinuationQueue(), typeof(UniTaskLoopRunners.UniTaskLoopRunnerFixedUpdate), runners[2] = new PlayerLoopRunner()); copyList[3].subSystemList = InsertRunner(copyList[3], typeof(UniTaskLoopRunners.UniTaskLoopRunnerYieldPreUpdate), yielders[3] = new ContinuationQueue(), typeof(UniTaskLoopRunners.UniTaskLoopRunnerPreUpdate), runners[3] = new PlayerLoopRunner()); copyList[4].subSystemList = InsertRunner(copyList[4], typeof(UniTaskLoopRunners.UniTaskLoopRunnerYieldUpdate), yielders[4] = new ContinuationQueue(), typeof(UniTaskLoopRunners.UniTaskLoopRunnerUpdate), runners[4] = new PlayerLoopRunner()); copyList[5].subSystemList = InsertRunner(copyList[5], typeof(UniTaskLoopRunners.UniTaskLoopRunnerYieldPreLateUpdate), yielders[5] = new ContinuationQueue(), typeof(UniTaskLoopRunners.UniTaskLoopRunnerPreLateUpdate), runners[5] = new PlayerLoopRunner()); copyList[6].subSystemList = InsertRunner(copyList[6], typeof(UniTaskLoopRunners.UniTaskLoopRunnerYieldPostLateUpdate), yielders[6] = new ContinuationQueue(), typeof(UniTaskLoopRunners.UniTaskLoopRunnerPostLateUpdate), runners[6] = new PlayerLoopRunner()); playerLoop.subSystemList = copyList; PlayerLoop.SetPlayerLoop(playerLoop); }
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] static void Init() { // capture default(unity) sync-context. unitySynchronizationContetext = SynchronizationContext.Current; mainThreadId = Thread.CurrentThread.ManagedThreadId; if (runners != null) return; // already initialized var playerLoop = PlayerLoop.GetDefaultPlayerLoop(); Initialize(ref playerLoop); }
解決方法
ゲームの初期化を行うRuntimeInitializeLoadType.BeforeSceneLoad
より後のタイミングで、下記のように、ScriptBehaviourUpdateOrder.CurrentPlayerLoop
でECS側のPlayerLoopを取得し、PlayerLoopHelper.Initialize
でUniRx側のPlayerLoopを初期化しなおしてあげることで共存させられます。
閉廷!!!
var playerLoop = ScriptBehaviourUpdateOrder.CurrentPlayerLoop;
PlayerLoopHelper.Initialize(ref playerLoop);