【Unity】コルーチン内でサブコルーチンをStartCoroutineを使わず待つ方法【IEnumerator】
2020年6月4日
はじめに
コルーチン内で更にコルーチンを呼んで終了待ちするシチュエーションはよくあります。
更に StartCoroutine() を呼んで待ちたいところですが、なにかメモリ的に無駄なので少し考えてみました。
案
結局は IEnumerator インターフェイスですから 直接操作してしまえばいいわけです。
例えばこのような状況
// メインコルーチン
private System.Collections.IEnumerator coMain()
{
// 何かしらの処理
// サブコルーチンが終わるのを待つ
// yield return StartCoroutine( coSub());
// ↓こう書き換える
for( var ie = coSub(); ie.MoveNext();) yield return null;
// 終了
yield break;
}
// サブコルーチン
private System.Collections.IEnumerator coSub()
{
// なんか Load とか重い処理
while( loop)
{
yield return null;
}
// 終了
yield break;
}
yield return StartCoroutine( coSub());
の部分を
for( var ie = coSub(); ie.MoveNext();) yield return null;
と書き換え coMain() 自身が coSub() を回して終了待ちしています。
coSub の実行タイミングが多少ずれるかと思いますが、この例ではさほど問題ありません。
応用編
これを利用すると、ミクロなタスクシステムが作れます。
コルーチンの入れ子できるシステムです。
Stack に コルーチンの IEnumerator をPush()し、 自力で回して終了なら Pop() します。
using System;
using System.Linq;
using System.Collections.Generic;
using UnityEngine;
namespace test{
public class MicroTaskTest : MonoBehaviour
{
private Stack<System.Collections.IEnumerator> m_CoroutineStack = new Stack<System.Collections.IEnumerator>();
//----------------------------------------------------------------------------
// コルーチン登録
private void PushCoroutine( System.Collections.IEnumerator routine) => m_CoroutineStack.Push( routine);
//----------------------------------------------------------------------------
// コルーチン終了
private void PopCoroutine() => m_CoroutineStack.Pop();
//----------------------------------------------------------------------------
private void Start()
{
// task1 を起動
PushCoroutine( coTask1() );
}
//----------------------------------------------------------------------------
private void Update()
{
// ミクロなタスクシステムを回す
if( m_CoroutineStack.Count > 0)
{
var nowCproutin = m_CoroutineStack.Peek();
if( !nowCproutin.MoveNext())
{
// 終わったらしい
PopCoroutine();
}
}
}
//----------------------------------------------------------------------------
// タスク1
private System.Collections.IEnumerator coTask1()
{
UnityEngine.Debug.Log( $"begin task1");
for( int i = 0; i < 10; ++i)
{
UnityEngine.Debug.Log( $"task1 i={i}");
yield return null;
}
PushCoroutine( coTask2());
yield return null;
UnityEngine.Debug.Log( $"end task1");
}
//----------------------------------------------------------------------------
// タスク2
private System.Collections.IEnumerator coTask2()
{
UnityEngine.Debug.Log( $"begin task2");
for( int i = 0; i < 10; ++i)
{
UnityEngine.Debug.Log( $"task2 i={i}");
PushCoroutine( coTask3());
yield return null;
}
PushCoroutine( coTask4());
yield return null;
UnityEngine.Debug.Log( $"end task2");
}
//----------------------------------------------------------------------------
// タスク3
private System.Collections.IEnumerator coTask3()
{
UnityEngine.Debug.Log( $"task3");
yield break;
}
//----------------------------------------------------------------------------
// タスク4
private System.Collections.IEnumerator coTask4()
{
UnityEngine.Debug.Log( $"task4");
yield break;
}
}//class MicroTaskTest
}//namespace test
出力結果
begin task1
task1 i=0
task1 i=1
task1 i=2
task1 i=3
task1 i=4
task1 i=5
task1 i=6
task1 i=7
task1 i=8
task1 i=9
begin task2
task2 i=0
task3
task2 i=1
task3
task2 i=2
task3
task2 i=3
task3
task2 i=4
task3
task2 i=5
task3
task2 i=6
task3
task2 i=7
task3
task2 i=8
task3
task2 i=9
task3
task4
end task2
end task1
task1 の終わりに task2 に飛びます。
task2 は task3 を 10回 呼んで 最後に task4 に飛びます。
後に task2 task1 と終了してます。
このような入れ子構造
task1
{
task2
{
task3
task3
task3
task3
task3
task3
task3
task3
task3
task3
task4
}
}
注意としては PushCoroutine() を呼んですぐに yield break すると Stack に積んだ途端 Pop() され意図通り動かないので yield return null; でクッションを挟むと良いです。