Search Unity

[FREE] More Effective Coroutines

Discussion in 'Assets and Asset Store' started by Trinary, Feb 23, 2016.

  1. ScottSummers

    ScottSummers

    Joined:
    Jul 12, 2015
    Posts:
    27
    I love MEC, thanks!


    From my tests, it seems that Timing.KillCoroutines() can't kill coroutines that are in a while loop. Is that true?

    For example, KillCoroutines would have no effect on this:
    Code (csharp):
    1.  
    2. IEnumerator<float> MyCoroutine()
    3. {
    4.    while (true)
    5.    {
    6.        print("Running");
    7.        yield return Timing.WaitForOneFrame;
    8.    }
    9. }
    10.  
    In some cases, instead of a while loop, users could simply restart the coroutine at the end. But would that be less performant?
    Example:
    Code (csharp):
    1.  
    2. IEnumerator<float> MyCoroutine()
    3. {
    4.        print("Running");
    5.        yield return Timing.WaitForOneFrame;
    6.  
    7.        Timing.StartCoroutine(MyCoroutine)();
    8. }
    9.  
     
  2. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    Hi Scott, thank you. ;)

    Restarting the coroutine is definitely far less performant, you shouldn't do that unless you have no other choice.

    KillCoroutines absolutely can kill coroutines that are in a while loop. The only caveat with KillCoroutines is that if you use it inside a coroutine to kill the coroutine that is currently running then that coroutine will continue executing after the kill command until it reaches the next yield return statement. That can make it look as though the kill command didn't work. If you want to kill the coroutine that is currently running the right command for that is "yield break;"
     
  3. ScottSummers

    ScottSummers

    Joined:
    Jul 12, 2015
    Posts:
    27
    Thanks!

    One more question:
    To kill a coroutine "by handle" instead of by tag, what's the handle?

    I expected it to be the name of the coroutine, but that doesn't seem to work.
     
  4. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    The handle is an object of type CoroutineHandle that is returned by the Timing.StartCoroutine function, so you can catch that variable when you start the coroutine if you like and then later use the handle to stop that instance.
     
  5. Deleted User

    Deleted User

    Guest

    Very nice of you to make this tool free, thanks. I have a question. If I kill a coroutine the same frame I started it it still continues to run. Is it an easy fix?
     
  6. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    You're welcome. :)

    When you RunCoroutine that command executes the first frame of that coroutine right then (unless you do it from within a coroutine in the same segment). If you then do a kill command that first frame will have already ran. Is that what you are running into?
     
  7. Deleted User

    Deleted User

    Guest

    It continues to run indefinitely, not only the first frame. I'll try it in a separate project.

    Ok now I think I get it. It's killed indirectly by the coroutine itself. It does work after the first frame though so how come it doesn't for the first?
     
    Last edited by a moderator: Jul 13, 2017
  8. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    Ok, then it could be a timing thing. If the kill command is somehow being executed before the start command then it won't be killed. Check the return value of the kill command, if it's returning 0 then nothing was killed.
     
  9. Quatum1000

    Quatum1000

    Joined:
    Oct 5, 2014
    Posts:
    889
    Hi, did found a solution for the jerk issue?
     
  10. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    Actually I did, but I haven't submitted that update yet. There's a namespace issue with windows store targets and the memory alloc that I also wanted to fix. I'll try to finish that update and submit it this weekend.

    Thanks for the reminder.
     
  11. chyen

    chyen

    Joined:
    Sep 1, 2014
    Posts:
    9
    Hi Trinary,
    I just implemented MEC in my games and i see a very good improvement. Thank you.

    However I have some problems with DOTween which rely on tween status:-

    IEnumerator<float> Chase(){
    myTween = thisRigid.DOMove (posA, moveTime,);
    yield return myTween.WaitForCompletion ();
    ....
    }

    This will not work anymore and it comes with this error:-
    error CS0029: Cannot implicitly convert type `UnityEngine.YieldInstruction' to `float'

    I am not really good in coding therefore I have a hard time solving this.

    Hope that someone can help me out. Thanks!
     
  12. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    Hi @chyen

    DOTween makes heavy use of unity's default coroutines. UnityEngine.YieldInstruction does not have an interface within managed code, so there is no way for anything written in C# to correctly handle it (this includes MEC). The only option is to convert that one coroutine back to a Unity default coroutine so that the Unity coroutine can handle the YieldInstruction within C++ code.

    I have my own version of a tween engine on the asset store called Movement over Time that improves on the tween concept in several areas (one of which is using MEC rather than unity's default coroutines). However, the interface is somewhat less simplistic than DOTween, so it's not for everyone.
     
    Last edited: Jul 25, 2017
  13. chyen

    chyen

    Joined:
    Sep 1, 2014
    Posts:
    9
    Ya i also noticed that there is no scripts of YieldInstruction to be play around with. Anyway thanks for your suggestion and i will try to look into it. Thanks @Trinary.
     
  14. zee_ola05

    zee_ola05

    Joined:
    Feb 2, 2014
    Posts:
    166
    @Trinary

    Is there a way to know whether a coroutine is currently running?

    Before using MEC, I used this code:

    Code (CSharp):
    1. Coroutine handle = StartCoroutine(MyCoroutine());
    2.  
    3. if(handle != null)
    4. {
    5.     // running
    6. }
    How do I do this check with MEC?
     
  15. zee_ola05

    zee_ola05

    Joined:
    Feb 2, 2014
    Posts:
    166
    Another question... In this example,
    Should I be using Time.deltaTime or Timer.DeltaTime? And why?
    Code (CSharp):
    1. IEnumerator<float> MyCoroutine()
    2. {
    3.     float elapsedTime = 0f;
    4.     while (elapsedTime < 10f)
    5.     {
    6.         yield return 0f;
    7.         elapsedTime += Time.deltaTime;
    8.     }
    9. }
     
  16. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    Sorry zee, if you're running MEC Free then there isn't a function to check if a coroutine is running. In Pro you can just query "handle.IsRunning". The only option in MEC free is to use a private boolean in your class that you set to true at the top of the coroutine and false at the bottom.

    Timing.DeltaTime is analogous to Time.deltaTime when you run your coroutine in the Update segment. However, if you run your coroutine in a few other segments (like SlowUpdate) then Time.deltaTime won't actually hold the time since the last call, while Timing.DeltaTime will. I always use Timing.DeltaTime because it's consistent across segments.

    Also Timing.LocalTime can be reset to 0, which means that it won't lose precision if your program runs for hours... but I'm not sure how many people care about that.
     
  17. Suzuka91

    Suzuka91

    Joined:
    May 7, 2014
    Posts:
    39
    The day I always feared has finally arrived! Haha. (Explanation below)

    I'm using MEC for a long time (it's awesome, I already told you through Youtube, I thought, but I'll never be enough (it's awesome)).
    The problem is that I relied a lot of my code in MEC, since Extension Methods to Scripts I use in each project.
    Before, I could write:
    Code (CSharp):
    1. IEnumerator <float> ienum = Timing.RunCoroutine (_SomeCoroutine ());
    I recently started a new project and downloaded the latest version of MEC (I can not remember which update I was using), and now Timing.RunCoroutine returns a "CoroutineHandle", which is the first time I saw it.


    I do not blame you for changing it, by the way, it's my fault.

    I have been reading the pdf in the new version and it says nothing about CoroutineHandle.
    In fact, on page 14, for example, it still shows:
    Code (CSharp):
    1. IEnumerator <float> handle =
    2. Timing.RunCoroutine (_RunFor10Seconds ());
    But it is no longer possible.

    I wanted to ask what are the advantages of the new CoroutineHandle comparing to IEnumerator <float>, and if there have been more significant changes I am missing.

    Once again, MEC is awesome, congratulations.

    Regards
     
    Last edited: Aug 6, 2017
    Trinary likes this.
  18. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    Suzuka,

    You must have been using a very old version indeed before. For the most part you can just change
    Code (CSharp):
    1.  IEnumerator <float> handle =
    2. Timing.RunCoroutine (_RunFor10Seconds ());
    to
    Code (CSharp):
    1. CoroutineHandle handle =
    2. Timing.RunCoroutine (_RunFor10Seconds ());
    That change was made for a couple of reasons, the main one is that it was possible to accidentally use the extension methods (like CancelWith) incorrectly when you were passing the same variable type into the function that it was returning. Another is that with the old system KillCoroutines would take an increasingly long time to execute as you defined more coroutines (this is no longer true). Lastly, there are a lot of extra functions implemented for the coroutine handle (mostly in pro) like handle.isRunning that weren't possible to implement for a built in data type.

    I have added a lot of features to MEC, but most of the new features are in Pro. That change was made to MEC Free to maintain compatibility, and also so there wouldn't be hidden slowdowns as a project got larger in size.
     
  19. Suzuka91

    Suzuka91

    Joined:
    May 7, 2014
    Posts:
    39
    Great, then updating code should be fast.
    Thanks for the answer :)

    PS: I've been trying the new things and are great! :D
     
    Trinary likes this.
  20. zee_ola05

    zee_ola05

    Joined:
    Feb 2, 2014
    Posts:
    166
    Hi, I noticed that calling Timing.RunCoroutine() 500x during the same frame causes a huge spike (compared to Unity's StartCoroutine()).

    This is my test:
    Code (CSharp):
    1. void Update ()
    2. {
    3.     if (Input.GetKeyDown (KeyCode.Space))
    4.     {
    5.         for (int i = 0; i < 500; i++)
    6.         {
    7.             StartCoroutine (TestUnityCoroutine ());
    8.         }
    9.     }
    10.  
    11.     if (Input.GetKeyDown (KeyCode.S))
    12.     {
    13.         for (int i = 0; i < 500; i++)
    14.         {
    15.             Timing.RunCoroutine (TestMEC ());
    16.         }
    17.     }
    18. }
    19.  
    20. IEnumerator TestUnityCoroutine()
    21. {
    22.     yield return null;
    23. }
    24.  
    25. IEnumerator<float> TestMEC()
    26. {
    27.     yield return 0f;
    28. }
    Note that executing Timing.RunCoroutine() 500x again after a few frames wouldn't cause the same spike (smaller spike). It looks like MEC is doing some kind of Pooling.

    What is the recommended way to avoid this spike during gameplay? It is a real issue for me because my game actually calls Timing.RunCoroutine() hundreds of times during the same frame.
     
    Trinary likes this.
  21. zee_ola05

    zee_ola05

    Joined:
    Feb 2, 2014
    Posts:
    166
    Looking deeper in the profiler, it looks like the BIG spike is caused by Timing's initialization. It is triggered the first time Timing.RunCoroutine() is called.

    I think that running a dummy coroutine in the beginning could solve this. Or is there a way to manually add Timing object to the scene?
     
    Trinary likes this.
  22. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    @zee_ola05

    It's actually quite easy to initialize MEC on project load rather than on first use. The easiest way is to 1) run your game and press ctrl-c on the Movement Effects gameObject and then 2) stop it and ctrl-v the object into your starting scene. (you may want to make sure it's in the DontDestroyOnLoad section). Alternately, you can just create a gameObject in your scene and add the Timing class to it.

    MEC does heavily employ pooling. There are three constants that you can increase the size of if you want to increase the initial size of the coroutine pools, ln 216:
    Code (CSharp):
    1.         private const int InitialBufferSizeLarge = 256;
    2.         private const int InitialBufferSizeMedium = 64;
    3.         private const int InitialBufferSizeSmall = 8;
    You can also pass an initial size as an integer into the dictionaries on ln 184-189. If you pass a large enough value in those places you can avoid any object resizing.
     
  23. Quatum1000

    Quatum1000

    Joined:
    Oct 5, 2014
    Posts:
    889
    MEC Threading:

    Hi, I have some question about MEC and Threading.

    I'd like to update a reflection probe for testing purposes after returning from play mode in editor.
    This works perfect:
    Code (CSharp):
    1. private IEnumerator <float> _UpdateReflectionProbe() {
    2.        while (WaterReflectionProbe == null) {
    3.            yield return Timing.WaitForOneFrame;
    4.        }
    5.        WaterReflectionProbe.RenderProbe();
    6.        Debug.Log("Ref Updated");
    7.    }
    8. }
    9.  
    1) Only to test, I wanted to wait update the reflection probe after 5 secs, but I never get "Ref Updated" response after stop. Any reason for this?

    Code (CSharp):
    1.     void Start() {
    2. #if UNITY_EDITOR
    3.        if (Application.isPlaying == false) {
    4.            Timing.RunCoroutine(_UpdateReflectionProbe());
    5.        }
    6. #endif
    7. }
    8.  
    9. private IEnumerator <float> _UpdateReflectionProbe() {
    10.        yield return Timing.WaitForSeconds(5);
    11.        WaterReflectionProbe.RenderProbe();
    12.        Debug.Log("Ref Updated");
    13. }
    14.  
    2) Threading: Now I want to update the reflection probe INSIDE another thread.
    The logic should be

    While (WaterReflectionProbe == null) {
    // stay inside the thread without burning the cpu down.
    }

    WaterReflectionProbe.RenderProbe();

    Code (CSharp):
    1.     private IEnumerator <float> _UpdateReflectionProbe() {
    2.         yield return Threading.SwitchToExternalThread((System.Threading.ThreadPriority)Random.Range(0, 5));
    3.         #region External Thread
    4. ...
    5. ...
    6. ...
    7. ...
    8.         #endregion
    9.         yield return Threading.SwitchBackToGUIThread;
    10.     }
    11.  
    Some questions anyway.

    Perhaps I miss understand something on external threading.
    Does an external thread runs in a loop like the main thread and must be terminated by a command, or does an external thread behave like oneshot or a function?

    a) If it behave like a function, how can I wait in an ExternalThread inside of a loop?
    Eg. with yield return Timing.WaitForOneFrame; as well?

    b) How can I terminate an ExternalThread by it self from the while loop inside the region?
    Eg: while (true) { if (timer > t) Thread.TerminateAndReturn(anyvalue) }
    And can I return any code as well?

    c) How can I terminate ExternalThread by an external command if required?
    And how to get a thread handle to determine the correct ExternalThread to terminate?


    Thank you.


    Edit:1
    Additional this co-routine works but didn't respond an exception.
    But "Ref Updated" is displayed in the console.

    Code (CSharp):
    1.     private IEnumerator <float> _UpdateReflectionProbe() {
    2.         while (WaterReflectionProbe == null) {
    3.             yield return Timing.WaitForOneFrame;
    4.         }
    5.      
    6.         WaterReflectionProbe.RenderProbe();
    7.         Debug.Log("Ref Updated");
    8.  
    9.         go = null;
    10.         go.transform.position = go.transform.position;
    11.     }
    12.  
    Edit2:

    Sometime I got an :
    NullReferenceException: Object reference not set to an instance of an object
    MovementEffects.Threading.OnDestroy () (at Assets/Plugins/Trinary Software/Threading.cs:79)
     
    Last edited: Aug 8, 2017
  24. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    Are you running your coroutine as an editor coroutine? Most segments don't run while not playing, and editor coroutines don't run while in play mode (by design).
    Don't do that, it will have the opposite effect from the one you are going for. Coroutines are so much better for a quick check like this than threads are because they will just execute once per frame. When you are operating in threads you are working outside of the frame pump. You would end up checking it far more often than necessary and use more CPU time rather than less... also you can't use the equality operator on a MonoBehavior from an outside thread.
    MEC Threaded allows you to start a coroutine on the GUI thread, switch over to an external thread to do processing, and then switch back to the GUI thread using the yield return structure. If you're in a thread you can use Threading.Sleep to wait.
    You terminate the coroutine the same way you would normally terminate the coroutine. "yield break" or end the function. If you want to pass info back then "yield return Threading.SwitchBackToGUIThread" and then you can set variables like normal.
    I would hope that you wouldn't have to do this.

    You aren't switching to an external thread in that coroutine.
    I can look into that, but more context would be helpful.
     
  25. Quatum1000

    Quatum1000

    Joined:
    Oct 5, 2014
    Posts:
    889
    What you mean "With don't do that"? Not running a loop in an External Thread or not calling for WaterReflectionProbe.RenderProbe();

    A condition with == doesn't work? Are there any other small restrictions we should know?

    It's a monobehavior but not an editor script, if that should be the difference.
    This following example does not work. No reflection probe update nor debug info.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using MovementEffects;
    5. #if UNITY_EDITOR
    6. using UnityEditor;
    7. #endif
    8.  
    9. [ExecuteInEditMode]
    10. public class TEST : MonoBehaviour {
    11.  
    12.     public ReflectionProbe WaterReflectionProbe;
    13.  
    14.     void Start() {
    15. #if UNITY_EDITOR
    16.         if (Application.isPlaying == false) {
    17.             Debug.Log("Start + Application.isPlaying = false  WRP :" + WaterReflectionProbe);
    18.             Timing.RunCoroutine(_UpdateReflectionProbe());
    19.         }
    20. #endif
    21.     }
    22.  
    23.     private IEnumerator<float> _UpdateReflectionProbe() {
    24.         yield return Timing.WaitForSeconds(5);
    25.         WaterReflectionProbe.RenderProbe();
    26.         Debug.Log("Ref Updated");
    27.     }
    28. }
    29.  
     
  26. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    The above function won't run faster in another thread. Checking for null and then doing a yield return is actually really quick and it uses hardly any CPU time (like, if you put it in the profiler by itself it would show 0ms). If you did check it in another thread it would throw an exception, but even if you were checking an object that wouldn't throw an exception (like a class that doesn't inherit from MonoBehivor, perhaps one that only holds data) it would still be faster to check it in a coroutine (like above) than to switch to another thread before checking it.

    I mean that this is not a situation where you can get a benefit from threading.
    Some things you cannot do in a separate thread

    - You cannot read or write the values of anything in your scene. This includes gameObjects, transforms, buttons, gui text components, etc.
    - You cannot query the Random class for a random number.

    WaterReflectionProbe is an object in your scene, and querying its value using the == operator will not work. I tried to make it clear in the asset description that threads are very difficult to use correctly in Unity. Most of the objects you typically work with break as soon as you reference them from a separate thread. The list of things that actually work better in a separate thread is very short. The big one is saving your game state without creating a frame-rate hitch.
    You have to run editor coroutines in the EditorUpdate segment.
     
  27. Quatum1000

    Quatum1000

    Joined:
    Oct 5, 2014
    Posts:
    889
    On my game development machine your demo hitches (stops) from time to time., for max about 4-7 frames at 60frames.
    I'm doing some test and start different unity instances and other games parallel.

    I tried your demo as build running in 2 instances (marked running in background) side by side in window mode.
    I thought about that both instances jerks at the same time so its my system, but it does not. Each instance jerking at its own time asynchronous. (From 60 to 55 frames and back) The next is to test is if it's based on MEC or Unity.

    There is no other way to fiddle from where it's coming testing with and without MEC. I think it's Unity. But "Running in Background" eliminates 60% of the jerkiness, but not all.
     
  28. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    @Quatum1000 I updated the version about a week ago, are you using 2.05.0?
     
  29. Quatum1000

    Quatum1000

    Joined:
    Oct 5, 2014
    Posts:
    889
    exactly 2.05.0.

    Edit:
    On Jet 4.6 and "run background" I got less jerking results on exactly the same scene and standard framerate at 60 fixed. Drops approximately from 60 to 59 fps. On Jet 3.5 and "run background" from 60 to about 55. It's not perfect like in other quality games, but a lot better with MEC.
     
    Last edited: Aug 9, 2017
  30. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
  31. Maeslezo

    Maeslezo

    Joined:
    Jun 16, 2015
    Posts:
    330
    Hello!

    Thank you for this great tool.

    I was using WaitForFixedUpdate. I have seen MEC doesn't have it.

    Any workarround or equivalent?

    Thank you
     
  32. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    You're welcome ;)

    In MEC you would start the coroutine in the FixedUpdate segment and then it runs there. You don't need to use a special yield return statement to keep it there like Unity's default coroutines do.
    Code (CSharp):
    1. Timing.RunCoroutine(_UpdateReflectionProbe(), Segment.FixedUpdate);
     
    Maeslezo likes this.
  33. breban1

    breban1

    Joined:
    Jun 7, 2016
    Posts:
    194
    Hi, first time using MEC here so bear with me. Using the docs, I converted 4 of my coroutines over to MEC (2.05.1) and everything worked great. Then I added this new one and now I am receiving an error.

    ArgumentException: An element with the same key already exists in the dictionary.
    System.Collections.Generic.Dictionary`2[MovementEffects.Timing+ProcessIndex,MovementEffects.CoroutineHandle].Add (ProcessIndex key, CoroutineHandle value) (at /Users/builduser/buildslave/mono/build/mcs/class/corlib/System.Collections.Generic/Dictionary.cs:404)
    MovementEffects.Timing.MoveTag (ProcessIndex coindexFrom, ProcessIndex coindexTo) (at Assets/Plugins/Trinary Software/Timing.cs:1538)
    MovementEffects.Timing.RemoveUnused () (at Assets/Plugins/Trinary Software/Timing.cs:501)
    MovementEffects.Timing.Update () (at Assets/Plugins/Trinary Software/Timing.cs:335)

    Here is the coroutine I changed from Unity to MEC:
    Code (csharp):
    1.  
    2.     public IEnumerator<float> InterpolateWorldPositionOverTime(float startTime, float interpSeconds, Vector3 startVector, Vector3 endVector, Transform interpTransform, GenericCallback func) {
    3.         float perc;
    4.  
    5.         perc = (Time.time - startTime) / interpSeconds;
    6.  
    7.         while(perc < 1.0f) {
    8.             perc = (Time.time - startTime) / interpSeconds;
    9.             interpTransform.position = Vector3.Lerp(startVector, endVector, perc);
    10.             yield return 0;
    11.         }
    12.  
    13.         if(func != null)
    14.             func(interpTransform);
    15.  
    16.         yield break;
    17.     }
    18.  
    Me calling it a couple times in a row in Update():
    Code (CSharp):
    1.  
    2.                         Timing.RunCoroutine(InterpolateWorldPositionOverTime(Time.time, cameraMoveDownDuration, underwaterCameraStartPosition, underwaterCameraEndPosition, underwaterCamera.transform, UnderwaterCameraDoneMovingCallback));
    3.  
    4.                         Timing.RunCoroutine(InterpolateWorldPositionOverTime(Time.time, 5.0f, catchBassLureParent.transform.position, bassGoToPosition.transform.position, catchBassLureParent.transform, null));
    5.  
    I'm probably doing something stupid or not understanding how it works, but I *think* I followed the docs verbatim for this coroutine. Any ideas?
     
  34. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    Oops, it looks like that's a bug that slipped through in the last update that went out two days ago. I've already submitted an update and the new version should be out in the next couple of days. Just hold tight.
     
    breban1 likes this.
  35. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    @breban1 If you want to fix it yourself without waiting for the new version then just delete the MoveTag function in Timing around line 1538 and the 4 references to it around line 500. It's just doing something that doesn't need to be done any longer.
     
  36. breban1

    breban1

    Joined:
    Jun 7, 2016
    Posts:
    194
    That did the trick, thanks!
     
    Trinary likes this.
  37. Tivec

    Tivec

    Joined:
    Jun 20, 2015
    Posts:
    13
    Well, okay, I feel dumb now. I sent in a ticket about this via your support site, when I should just have checked this thread :) Thank you for letting us know how to fix it ourselves! :)
     
    Trinary likes this.
  38. EricM

    EricM

    Joined:
    Aug 17, 2013
    Posts:
    10
    Great asset. I have since moved everything over to MEC (initialization, loading, etc.). Huge improvement over the native Unity options. Nice job. Two quick questions:
    1. Are there any tricks for getting more Debug info when using MEC? When I get an error the stack trace stops in Timing not letting me see where the problem is in my own code.
    2. I use MEC for loading/saving the scene since the files can be large and/or over the network. Unfortunately, every time I try to save data using OnApplicationPause() the coroutines for saving get called but don't finish until the app is resumed. It looks like the App kills everything on the main thread until it receives focus a gain. Any advice for using MEC for saving/loading routines and still being able to call them in OnApplicationPause/Focus/Quit?
    Thanks!
     
  39. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    Thanks for your questions Eric!

    1) Unfortunately, in order for an exception in one coroutine to not interrupt the others I have to catch any exceptions and re-throw them later. This is what causes the Timing class to show up at the top line of the exception stack trace. Personally, I use a plugin called Console Enhanced (I don't have any relationship with the author of that plugin, I just find it helpful.) ConsoleE allows you to jump to any line in the stack trace, rather than just the first line.

    At some point soon Unity is going to upgrade the version of .NET in the scripting engine. New versions of .net have the ability for me to store exceptions and re-throw them without getting into the stack trace. I'll be sure to do it that way once I'm able, but until the compiler is upgraded there's nothing I can do.

    2) This is a hard one. I actually struggle with the same issue. Saving data is actually the best application for using other threads in Unity. You can use MEC Threaded for that, which will allow you to switch to another thread inside a MEC coroutine using a yield return statement. I'm not sure if other threads will also be paused on focus lost, I think they might, but in another thread it won't effect your performance during the save operation.
     
  40. blablaalb

    blablaalb

    Joined:
    Oct 28, 2015
    Posts:
    53
    Hi Trinary your implementation of coroutines is great, BUT unfortunately it does seems to work on Android devices.

    My entire implementation of Finite State Machine is based on coroutines and it is fine when i running my game inside Unity Editor. But when i try to run game on Android device coroutine doesn't run..

    I have implemented my FSM like this:
    Code (CSharp):
    1.     IEnumerator<float> FSM(){
    2.         while (runFSM) {
    3.             switch (currentState) {
    4.                        //FSM logic here
    5.             }
    6.             yield return Timing.WaitForOneFrame;
    7.         }
    8.     }
    and then i run it from Start method:
    Code (CSharp):
    1.  
    2. void Start(){
    3. Timing.RunCoroutine (FSM ());
    4. }
     
    Last edited: Sep 17, 2017
  41. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    blabla, coroutines do work on Android. I use them all the time. The code you posted seems fine, so your problem must be in a place you're not looking at right now. Perhaps your gameObject or the script is disabled? If it is then the Start function won't run.
     
  42. blablaalb

    blablaalb

    Joined:
    Oct 28, 2015
    Posts:
    53
    Everything runs fine when i press Play button inside Unity Editor. But when i build it and run on android device coroutine doesn't start, i duno why.
     
  43. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    Perhaps you are using a different scene in your build process than the one you are running on the editor. That different scene could be starting with the object disabled.

    If you're not using editor coroutines (which it looks like you are not) then there are no lines of code that work differently on device than in the editor.
     
  44. blablaalb

    blablaalb

    Joined:
    Oct 28, 2015
    Posts:
    53
    I've determined what the problem was. I was running old version of Unity. I have updated Unity and now everything works fine.
    Thank you Trinary for your feedback.
     
    Last edited: Sep 17, 2017
    Trinary likes this.
  45. zngb

    zngb

    Joined:
    Aug 5, 2015
    Posts:
    20
    Hello,

    I checked this thread but didn't find any mentions of being able to CancelWith on a CallDelayed call.

    I use CallDelayed to run some future action on my GameObject that might not exist by then. With a regular Coroutine I can use its CancelWith method which works great, but I just noticed I got some exceptions because of my use inside CallDelayed on the destroyed object.

    Is this missing functionality or an update for MEC I missed? Should I write a regular Coroutine that just delays itself and use CancelWith? Am I doing something ridiculous?
     
  46. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    Yeah, CallDelayed can't do everything unfortunately. It's meant as a helper function for simple use cases.

    If you are using MEC Pro and the function you are trying to run is a coroutine then you can chain extension methods to create the same functionality as CallDelayed would have: Timing.RunCoroutine(_Foo().Delay(3f).CancelWith(gameObject));

    If you're trying to run a regular function then you'll have to create your own little delay coroutine and run that.
     
  47. EricM

    EricM

    Joined:
    Aug 17, 2013
    Posts:
    10
    Thanks! This makes sense. I use quite a few nested coroutines, blocking, etc. Sometimes when an exception is thrown I only see MEC and none of the other routines leading up to it. I'll give the plugin you recommended a try to help with those situations where I still see the full trace.

    I'll give it a try. Thanks!
     
    Trinary likes this.
  48. zngb

    zngb

    Joined:
    Aug 5, 2015
    Posts:
    20
    Thanks for the info!
     
    Trinary likes this.
  49. ratking

    ratking

    Joined:
    Feb 24, 2010
    Posts:
    350
    In the newest update suddenly my scripts broke because WaitUntilTrue and RealtimeUpdate went missing. (I use Pro.) The changelog doesn't mention this, any reason why?
     
  50. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    Those functions shouldn't be missing, they are still in the files. You must have either a merge error or Unity hasn't finished compiling due to some other console error. See if you can fix any unrelated errors or try re importing.
     
    Alverik likes this.