Search Unity

たくさんの輝く新機能の数々のなかで、Unity 5.3のリリースノートにぽつりと1行書かれているもののとても役にたつ機能があったので、多分皆さんにも役にたつだろうと思い紹介することにしました。それは、カスタムコルーチン…正式には CustomYieldInstruction クラスです。これは何かといういうと、自分でコルーチンのyieldオペレーションをとても簡単に実装することができるようにしてくれるものです。早速実際の使用例を見てみましょう。

実例 – バグ修正

わたしは最近、Unity 5.2で追加されたUIのドロップダウンコンポーネントのバグについて調べていました。Time.timescaleが0に設定されているとドロップダウンコンポーネントは最初の1回だけ動作し、そして以降timescaleが0以外に設定されるまで表示されない、というものです。

多少のデバッグの後、問題はShow 関数の中にあることがわかりました。

2

m_Dropdown がnullにならないため、ドロップダウンの表示をせずにメソッドが返ってしまいます。

一度表示されると、 m_Dropdown コンポーネントは最小化されて破棄されます。…えーと、破棄されるべきなんですが、timescaleが0の時は破棄されないのです。

破棄を実際に行う関数を確認して、問題が特定できるか見てみましょう。

1

このブログ記事のタイトルからもうお察しかもしれませんが、犯人はWaitForSecondsでした。WaitForSecondsはスケールされた時間を使います。これはつまりWaitForSeconds に1秒待つように指定した時、timescaleが0.5になっていれば、実世界では2秒(1 / 0.5 = 2)待つことになるということです。もしWaitForSecondsをtimescaleを0に指定しながら使用したら、(timescaleが0以外に再び戻るまで) 永遠に待ち続けることになるのです。まとめると、WaitForSecondsのyieldがずっと終わらずに待ち続けるので、ドロップダウンは最初の使用から全く破棄されないままになるわけです。

解決策

この問題を解決するには、スケールされていない時間で待つ必要があります。もっともエレガントなアプローチはここに新しいyieldの処理、つまりWaitForSecondsRealtimeクラスを導入することでしょう。はっきりしていることが一つあって、Unity自身の開発者でも WaitForSeconds がスケールされた時間を使うということに気がつかないのであれば、それ自体をどうにかして解決しなければなりません。WaitForSecondsRealtime が存在することはWaitForSecondsが想定とは違う動作をするかもしれないということを気がつかせる、一つのメッセージにはなるでしょう。また、WaitForSecondsのドキュメントも更新する必要があります。現状のドキュメントではスケールされた時間についての記述がまったくないので!

これが、わたしが最近追加された独自のyield処理を作るための機能である CustomYieldInstruction を発見した経緯です。

新しいyield処理を追加するのはとても簡単です。上記の問題であれば次のように解決できます:

3

すべてのカスタムyield処理はkeepWaiting プロパティをオーバーライドする必要があります。そして、yield処理を抜けて次に進んで欲しい状況になったら、単にfalseを返せばいいだけです。

こちらが修正されたコードです:

4

この例では、毎回スケールされていない実時間を取得してチェックします。これ以上ないほど簡単です – と思ったら、そんなことはありませんでした!実は、さらに新しく WaitUntilWaitWhile yield関数が追加されているからです。これらを使えば、delegateを使ってyield処理を組み立てることができます。

こちらが今回の問題に対するもう一つの解決方法です。ここでは5秒間待ちたいという前提で書いています。

5

今回紹介したものは簡単な機能ですが、多くのポテンシャルを持っています。ここで学んだ教訓は、「リリースノートを読もう。何か有用なものが見つかるかもしれない!」ということでしょうか。もしカスタムコルーチンが気に入ったら、ぜひ UnityEventsも見てみてください。ちょっと見過ごされがちな機能ですが、わたしのもう一つのお気に入りです。

67 replies on “カスタムコルーチン”

Thanks for the post, Karl!

But from what you wrote, I was really worried that CustomYieldInstruction was too limited an API, especially for basic cases where you want to reuse a yield instruction instance to prevent too much GC pressure from high-frequency or persistent waits.

I’m SUPER happy to know, from reading the new documentation, that you can also implement custom yield instructions by just implementing IEnumerator in any arbitrary class.

But then I wondered, was this always the case even before 5.3?
If so, I feel so stupid for not doing it that way before. At the same time though, nobody else seemed to catch on and post about it online in Unity tips blogs so was there something else to that?

Is this only “sugar” ? since it seems that it’s perfectly possible to achieve the same using your own IEnumerator returning method (which will block until certain conditions are met).

With regards to the metnioned UnityEvents – i believe there are some horrendous things going on under-the-hood… something with the ArgumentCache class and its implementation of ISerializationCallbackReceiver. It allocates ridiculous amounts of memory… I think i reported this or only asked about it on the forums. I wonder if there’s something being done about it.

Unless I’m missing something, it seems strange for keepWaiting to be implemented as a property instead of a method.

whydoidoit November 28, 2012 – 10:15 pm That happens when yiendilg in a coroutine the compiler has generated code that MonoDevelop cannot break on it does cause the debugger to appear to step to exit code don’t worry about it, it’s just the magic class doing its return.

It’s great to see CustomYieldInstruction and WaitForSecondsRealtime finally make it into Unity! They will make a big difference.

Hopefully another lesson learned here is the importance of eating your down dog food! The need for CustomYieldInstructions and an unscaled version of WaitForSeconds come up often when using coroutines in a project. Both of which have been requested many times over the past many years. Now if we can just get you to go ahead and add pause, exception handling, return values, and a few other tweaks to coroutines, that’d be great.

I’m really sorry to ask for coriifacatiln on this I wrote an algorithm that creates an rtf javascript editor in a web page. The editor works flawlessly when the javascript file unminified is < 1000 lines. The work around I found for parsing large scripts was to setinterval and time my execution. If it took too long I'd set an interval to release the thread and reenter such that the browser wouldn't display the unresponsive script error.Are you saying this threading code will unlock the HTML ui layer and allow background processing to occur? If what you say is true, and the previous comment about ie supporting a variant of this is true, you could have just rocked my world let me know.M

The problem with nearly everybody having missed Unity Events is that they were never advertised individually and independently from the new Unity 4.6 UI. People only know them from injecting click handlers into buttons and don’t even suspect that you can enable the whole new “mouse only” level of behavioural level design for non-techs. You can barely find some geeky pioneers on the YouTube sharing their “revelations” that, hey, it appears you can actually use Unity Events without the UI.

mick November 28, 2012 – 9:57 pm Question about stepping torhugh DoHijack() with the monodevelop debugger:50IEnumerator DoHijack()51{52while(true)53{54//Check if we have a current coroutine and MoveNext on it if we do55if(_current != null && _current.MoveNext())56{57//Return whatever the coroutine yielded, so we will yield the58//same thing59yield return _current.Current;60}61else62//Otherwise wait for the next frame63yield return null;64}65}When I break on line 59 and then step over (f10), the current instruction caret moves to line 63 even though it doesn’t appear to be executing that line (ie if I set a breakpoint on line 63 it wont’ get hit). What is going on here?

Thank you for this important information.

For a while now, the Timer class in my Waitress library has been supporting unscaled time. It can also be used in non-coroutine methods. You don’t have to implement and start a coroutine to be able to wait for something.

Please see the code examples in documentation if you are interested in more info : https://docs.google.com/document/d/1eGd3DuOe4Bs70O_AKImTLOUisa0GbBIIKVDYpvnvL_o

Waitress on Asset Store : https://www.assetstore.unity3d.com/en/#!/content/45719

Is it really necessary in 2015 to embed code as an image on a website? Especially on a HiDPI screen these code blocks look like a blurry mess in comparison to the sharp text surrounding it…

Also, where is the documentation of CustomYieldInstruction? I couldn’t find it in the API reference.

I’m trying to work on nested coroutine these days by passing a stack to trace these nested coroutines and their real executing monobehavior. It’s working if I stop all the coroutines by clear the stack, however if I want to stop the top of the stack, the parent coroutine won’t proceed for the terminated coroutine just stopped without any break yield instructions. With this feature I think I can make something useful. However, it would be better if nested coroutine could be supported by Unity natively.

This will be possible, since now Unity properly handles IEnumerator when yielding. Actually CustomYieldInstruction class is just a small handy wrapper based on it:

abstract CustomYieldInstruction: IEnumerator {
public abstract bool keepWaiting { get; }

public object Current { get { return null; } }
public bool MoveNext() { return keepWaiting; }
public void Reset() {}
}

If you were to return another IEnumerator through Current, then coroutine would be suspended on that.

Not sure but, isn’t the line “m_Items.Clear();” also a bug? For it is inside the “for” instruction so it will clear all the list in the first for step.

It is good news.
but i really miss some features, especially when I do work on state machine code.
I don’t think it big issue to implement for you it is so easy
like
PauseCoroutine, ResumeCoroutine, Get progress of Coroutine, WaitCoroutine ( make Coroutine pause till another done) , GetCoroutine , … , … and so on
May be also if you add editor window to show all current running Coroutines.

This will make Coroutines amazing full solution for state machine.

Yup. My only point here really is that they are motsly equivalent.Holy cow though I find CPS code to be impossible to read and impossible to debug.We’re getting more and more of it in the world and good god is it ugly.Callbacks are a nasty nasty way to write code.

Does returning ‘false’ from ‘keepWaiting’ guarantee that this object is never used again inside Unity internals, so we can organize a pooling mechanism for these custom yieldables to reduce GC pressure?

—–
With these, we can provide a delegate to be called for our yield instruction test.

yield return new WaitWhile(() => Time.realtimeSinceStartup < waitTime);
—–

Isn't this code generating an anonymous delegate? Means it allocates memory with every execution. I hope that's not real code found in Unity, as I prefer zero GC allocs. Sad enough that using yield already generates garbage, don't need even more of it.

Otherwise it's another feature you can hardly use for periodically called code, unless you don't care about framerate hiccups caused by the GC kicking in every now and then.

This is just a helper class to make it easier to implement simple yield instructions. If you want flexibility or more control, you can now derive from IEnumerator (this is what CustomYieldCoroutine is actually doing, see my answers above).

Very useful indeed, thanks for highlighting it. Any chance the devs could refactor “keepWaiting” to “KeepWaiting”? It is a property, after all.

That would contradict to Unity’s own naming standards, although (which is requested by you) match those of .NET/Mono. I guess Unity guys prefer to stick to, well, Unity standards. Why they introduced their own standards back in the day is now history. There is no and cannot be any final solution about this question, as your code always contradicts either Unity standards, or .NET standards. Unless you invent a time machine, return to the past and change it, your request is meaningless and satisfying it would introduce more inconsistency. Cheers

This looks really cool, thanks for making a blogpost about it! Eventhough i try to read the changelog carefully this one slipped through, and its super useful.

Your code example is clearing the list you are iterating over inside the for loop, so you will not be calling DestroyItem on all the items in the list. Looks like you want to clear the list after the for loop.

Thanks Karl for sharing the new feature. It looks like it will be very useful.

You have a small mistake in your post. 1 x 0.5 = 2 should be 1 / 0.5 = 2

You should actually use Time.unscaledTime (or accumulate Time.unscaledDeltaTime).

Using Time.realTimeSinceStartup will jump ahead unexpectedly if the player pauses the game by tabbing out…

In that case they should also add WaitForSecondsUnscaledTime and not replace the new WaitForSecondsRealtime

“the new WaitForSecondsRealtime” is actually not a new built-in Unity feature but rather an example of how one would build a class inheriting from the new CustomYieldInstruction class (which is a new built-in Unity feature).

Oh, I see what you mean. If they implemented WaitForSecondsRealtime may as well implement WaitForSecondsUnscaledTime in the API.

I still get confused by Coroutines without the custom yield. :-)

[…] an article about how to use it, please follow this […]

Comments are closed.