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 コメント

コメントの配信登録

コメント受付を終了しました。

  1. Marko Ravnjak

    1月 15, 2016 11:08 am

    Is it just me or are code snippets as images really un-useful?

    1. Yeah I didn’t realise we had support for code snippets in our blogs. I now know better :)

  2. 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?

    1. The ability to do this has existed for some time now but not many users were aware so its a way of making it more obvious. I also believe we have made some improvements to how Unity handles IEnumerator when yielding.

  3. 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.

    1. I dont know anything about memory issues with UnityEvents. Use the bug reporter so its made official, you can then keep track of the progress.

      1. Actually i did report that: (Case 738249) ArgumentCache.TidyAssemblyTypeName allocates lots of memory

        1. Thanks. QA have not verified it yet. I’ll try and take a look at it next week.

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

    1. 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.

  5. 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.

    1. 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

    2. That’s a sensible answer to a chnalengilg question

  6. Umm, is it a typo or is m_items.Clear() really in the for-loop?

    1. You are the third person to mention this ;)
      It is now fixed but the loop calls an empty virtual function which I guess no one uses as its never been reported as a bug.

  7. Marc-André Jutras

    12月 2, 2015 8:22 pm

    Why, oh why is that property a camel case?

  8. Stepan Stulov

    12月 2, 2015 6:21 pm

    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.

    1. Well we do have some learn material and the documentation also shows how to use UnityEvents but I guess It gives me something to write another blog post about ;)

        1. Also, it’d be really nice if we could edit posts we make here, in case we mess up the formatting :D

      1. With the bases loaded you struck us out with that anwsre!

    2. 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?

  9. 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

  10. 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.

    1. I wasn’t aware we had support for code in blog posts, apparently we do so I’ll make sure to use text in the future. Its not in the docs yet as its part of the 5.3 documentation which is not on-line yet.

      1. Thanks for the reply, I just loaded the 5.3 beta to look at the implementation :)

        One suggestion, it would be nice to have something like this in CustomYieldInstruction:
        public virtual void DidBeginYielding(MonoBehavior parentMonoBehaviour) {}

        That way, one could start and yield on other coroutines and wait for them using parentMonoBehaviour. That could be useful if I want to use stuff like yield return new WWW(url); inside my custom yield instruction.

        1. Interesting. I am not too familiar with the coroutine internals but it would be nice to support some events such as that(Stop/Pause/Cancel etc)
          Best thing to do is add them as feature requests as its not something I can do at the moment and I don’t want any ideas to get lost. https://feedback.unity3d.com/

        2. 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.

        3. TekuStudios

          12月 4, 2015 8:40 pm

          @KARL In fact, that feature request exists since quite some time ago ;)

          https://feedback.unity3d.com/suggestions/allow-us-to-pause-stop-and-resume-coroutines

  11. 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.

    1. 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.

  12. 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.

    1. Yes Shawn Halwes beat you to that one ;)

      1. They just _REALLY_ wanted to clear that list out.

  13. 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.

    1. Post your feature requests here https://feedback.unity3d.com/

      1. A feature like that has been requested at least more then once and more then a year ago, why not just make it happen? :(

        1. I’m not saying it isn’t happening, just wanted to make sure its captured as a feature request.
          I actually think its a good idea.

    2. Your thkining matches mine – great minds think alike!

    3. 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.

  14. Dmitriy Pyalov

    12月 1, 2015 11:36 pm

    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?

    1. Interesting question. I see no reason why you could not pool them, once false is returned we don’t use it any more so a pooling system seems quite possible and not all that hard to do.

      1. Dmitriy Pyalov

        12月 2, 2015 1:44 pm

        It would be a very nice feature, if CustomYieldInstruction received some kind of notification when it’s stopped by StopCoroutine or by destroying host object or any other way removed from the pending list.

        1. I agree that would be useful. I don’t think its possible at the moment. Its not something I can just add at the moment so please add it as a feature request ;) https://feedback.unity3d.com/

  15. Great stuff. Cant wait to update my task library. Should be able to reduce a bit of bloat.

  16. —–
    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.

    1. Its just an example, its not code in Unity.

  17. Is there a reason why a base class was used and not an interface?

    1. Well the base class does have a very simple implementation, we just override one part. There are 2 more functions that could be overridden so using an abstract class means we don’t need to implement the whole thing, just what we want to change.

      1. You could accomplish the same and not leak implementation details by making it an interface and then wrapping the user’s implementation in an internal class that tracks the state. This also avoids potential issues with inheritance hierarchies for us end users.

    2. 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).

  18. Thomas Mountainborn

    12月 1, 2015 7:49 pm

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

    1. We would have to refactor the whole engine then, our properties start with lower case :)

    2. Stepan Stulov

      12月 2, 2015 6:31 pm

      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

  19. Jonney Shih

    12月 1, 2015 6:49 pm

    Sounds like WaitForSecondsRealtime should be implemented by default in the API.

    1. It now is ;)
      Should be out in a future release.

  20. 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.

  21. Shawn Halwes

    12月 1, 2015 6:00 pm

    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.

    1. Yes that’s strange. DestroyItem doesn’t actually do anything, its an empty virtual function so that explains why its been missed.

  22. Nathan Warden

    12月 1, 2015 5:10 pm

    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

    1. Ah nice catch, thanks.

  23. Matthew Wegnerr

    12月 1, 2015 4:53 pm

    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…

    1. Good point

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

      1. “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).

        1. 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. :-)