2018年12月7日金曜日

[C#, Unity]Coroutineを逐次処理するCoroutine

やりたいこと

逐次実行する複数のCoroutineの中で、一連の処理を全部中断したい。

CoroutineRunner.cs
public IEnumerator HogeCoroutine()
{
    yield return CoroutineA();
    yield return CoroutineB();
    yield return CoroutineC();
}

private IEnumerable CoroutineA()
{
    ...
}
private IEnumerable CoroutineB()
{
    ...
    // ここで一連の処理を全て中断したい
}
private IEnumerable CoroutineC()
{
    ...
}

愚直にやる

キャンセルフラグを持っておいてキャンセルされたら外側のCoroutineでyield breakします。
単純だけどCoroutineが更にネストしたりするとめんどくさい。

CoroutineRunner.cs
private isCancelled = false;

public IEnumerator HogeCoroutine()
{
    IEnumerator[] coroutines = new IEnumerator[]
    {
        CoroutineA(), CoroutineB(), CoroutineC(),
    }
    foreach(IEnumerator e in coroutines)
    {
        yield return e;
        if(isCancelled) yield break;
    }
}

private IEnumerable CoroutineA()
{
    ...
}
private IEnumerable CoroutineB()
{
    ...
    // ここで一連の処理を全て中断したい
    isCancelled = true;
    yield break;
}
private IEnumerable CoroutineC()
{
    ...
}

Coroutineを分解する

各CoroutineのMoveNextの呼び出しをひとつのループで呼び出す。
ここまでやると後からCoroutineを追加したり、タイムアウトや割り込みを仕込んだりできるので応用が利きます。

CoroutineRunner.cs
public class CoroutineRunner{
    private Queue<IEnumerator> queue = new Queue<IEnumerator>();
    private bool isRequestedCancel = false;
    
    public Append(params IEnumerator[] coroutines)
    {
        foreach(var e in coroutines) queue.Enqueue(e);
    }
    
    public void Cancel()
    {
        isRequestedCancel = true;
    }
    
    public IEnumerator Coroutine()
    {
        while (true)
        {
            if (isRequestedCancel) break;
            if (queue.Count == 0) break;
    
            if (peek.MoveNext())
            {
                yield return peek.Current;
            }
            else
            {
                queue.Dequeue();
            }
        }
    }
}

private CoroutineRunner runner = new CoroutineRunner();

public IEnumerator HogeCoroutine()
{
    runner.Append(CoroutineA(), CoroutineB(), CoroutineC());
    yield return runner.Coroutine();
}

private IEnumerable CoroutineA()
{
    ...
}
private IEnumerable CoroutineB()
{
    ...
    // ここで一連の処理を全て中断したい
    runner.Cancel();
}
private IEnumerable CoroutineC()
{
    ...
}

ちなみに僕が普段使いしているものはこちらです。

0 コメント:

コメントを投稿