【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);