Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.

[FREE] More Effective Coroutines

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

  1. ratking

    ratking

    Joined:
    Feb 24, 2010
    Posts:
    320
    No, the problems seems to be that you or Unity accidentally uploaded the Free version of MEC instead of Pro.
     
    Last edited: Oct 24, 2017
  2. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    386
    It looks like you're right. I've submitted a correction and marked it as urgent, which often means it gets approved within a day. If you want the correct files immediately than that you can download with a 2017.2 version of Unity.
     
  3. johnny-tictoc

    johnny-tictoc

    Joined:
    Nov 15, 2016
    Posts:
    2
    On iPad 2, i get "Attempting to JIT compile method". I haven't tried on higher end iOS devices yet. Is this a known issue?
     
  4. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    386
    That sort of error is usually related to having a high stripping level set in your project. I would really like to know more about your configuration, since there's a good chance that I can remove the source of the error if you give me details. If you can tell me exactly what line is causing it, in addition to what version of Unity and MEC you are using that would be very helpful. You can send me an email through this form if you don't have my email address already.
     
  5. johnny-tictoc

    johnny-tictoc

    Joined:
    Nov 15, 2016
    Posts:
    2
    I'm gonna make a test project for you.
     
  6. zee_ola05

    zee_ola05

    Joined:
    Feb 2, 2014
    Posts:
    166
    Is MEC still more efficient than Unity's Coroutines? I came across this thread so I decided to test.

    I ran Timing.RunCoroutine and StartCoroutine 1000x each TWICE. (Pressed space bar 2x between 1sec interval)
    Code (CSharp):
    1. public class MECTest : MonoBehaviour
    2. {
    3.     void Update()
    4.     {
    5.         if (Input.GetKeyDown(KeyCode.Space))
    6.         {
    7.             for(int i=0; i<1000; i++)
    8.                 Timing.RunCoroutine(MECCoroutine());
    9.  
    10.             for(int i=0; i<1000; i++)
    11.                 StartCoroutine(UnityCoroutine());
    12.         }
    13.     }
    14.  
    15.     // Timing object is on the scene, and InitialBufferSizeLarge was set to 1024 to initialize the pool.. should be all good
    16.     IEnumerator<float> MECCoroutine()
    17.     {
    18.         yield break;
    19.     }
    20.  
    21.     IEnumerator UnityCoroutine()
    22.     {
    23.         yield break;
    24.     }
    25. }
    I ran profiler in the editor with Deep Profile on.

    Result:

    First Spacebar: Unity's StartCoroutine seems to be 5x faster than Timing.RunCoroutine. MEC seems to be still initializing when first used; Note that I already have Timing object on the scene and set InitialBufferSizeLarge constant to 1024. I thought it would help with MEC's initialization.

    UnityCoroutine has a bit more GC Alloc than MECCoroutine. A very small win for MEC.


    Second Spacebar: No more initial alloc for MEC. But Unity is still about 3x faster.

    Can anyone confirm that Unity's Coroutines are now faster than MEC?

    I ran the test on Unity 5.5.2f1. MEC Pro version 2.04.2
     
    Last edited: Nov 18, 2017
  7. zee_ola05

    zee_ola05

    Joined:
    Feb 2, 2014
    Posts:
    166
    I ran another test, but this time I changed yield return break; to yield return 0f; and yield return null; on MEC and Unity's Coroutine, respectively.

    Code (CSharp):
    1. public class MECTest : MonoBehaviour
    2. {
    3.     void Update()
    4.     {
    5.         if (Input.GetKeyDown(KeyCode.Space))
    6.         {
    7.             for(int i=0; i<1000; i++)
    8.                 Timing.RunCoroutine(MECCoroutine());
    9.  
    10.             for(int i=0; i<1000; i++)
    11.                 StartCoroutine(UnityCoroutine());
    12.         }
    13.     }
    14.  
    15.     // Timing object is on the scene, and InitialBufferSizeLarge was set to 1024 to initialize the pool.. should be all good
    16.     IEnumerator<float> MECCoroutine()
    17.     {
    18.         yield return 0f;
    19.     }
    20.  
    21.     IEnumerator UnityCoroutine()
    22.     {
    23.         yield return null;
    24.     }
    25. }
    Result:

    Unity's StartCoroutine started having GC Allocs. But overall, Unity's still ran faster than MEC's Coroutine.

    Another thing to note is that yield return null; seems to be faster than yield return 0f; Another win for Unity.
     
  8. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    386
    Hello zee, thank you for speed testing. It is a huge boon to the Unity community when someone like you steps up and verifies performance. You are correct that the arrays that hold the list of coroutines are not allocated until first access. This is helpful in practical environments since it avoids allocating the arrays for any segments that you never use.

    I do have a couple of notes about your tests:
    1. What you are testing here is the creation speed of the coroutines, not the running speed. Running speed is a more impactful metric to test, since most coroutines are created once but ran often. In any case, creation speed is also a good thing to think about.
    2. Unity does most of the work for coroutines inside unmanaged (C++) code. Even when using the deep profiler this unmanaged work is not attributed to the coroutine created process. Most or all of the "Overhead" task must be added into the Unity processing side, and there are other sections in the profiler that also do more work when a Unity coroutine is created (if work is created directly by doing a particular action then it should be attributed to that action), however MEC coroutines show all the work under one section. This means that your test is comparing the time it takes to set up and add a MEC corotuine to the queue to the time it takes to only set up a Unity corotuine without adding it to the queue. If you add the "Overhead" section that I can see in your screenshots to the Unity default coroutine section then they already take longer to create, and I believe there is probably an additional header that work related to unity corotuine creation is coming under as well. (and it's possible that not all of overhead is created by Unity's coroutines, a small amount of it is created by using the Update function in your test for instance.)
    3. The way to test the full amount of work is to run the two tests separately and measure the total ms for that frame (which is shown just above the list of processes as "CPU:17.39" in your last screenshot.) You could do this by using a different key for the MEC coroutines vs the Unity default and pressing those keys in separate frames.
     
  9. zee_ola05

    zee_ola05

    Joined:
    Feb 2, 2014
    Posts:
    166
    @Trinary
    I do have spikes in my game because I run Timing.RunCoroutine 500+ in a frame (I'm working on refactoring that right now). That is why I created this test.

    I would like to request for a feature that allows me to control the initialization of MEC. Like a simple public static Init() method. Probably with a parameter for poolSize for UpdateProcesses. In my game, I would like to initialize MEC during loading rather than during gameplay.

    You are right, I should look at the total ms. I'm gonna modify my test and post again.
     
    Trinary likes this.
  10. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    386
    Ok, I can put that feature on the TODO list. Btw, if you create a single empty coroutine during loading it will initialize the array. It seems strange to me to create 500 each frame. If you send me an email I can walk you through a method to recycle your coroutines rather than creating new ones, which will speed that up considerably.
     
  11. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    386
    @zee_ola05 I just remembered, you can do Timing.KillAllCoroutines() which will initialize and reset all the variables.
     
  12. jeromeWork

    jeromeWork

    Joined:
    Sep 1, 2015
    Posts:
    414
    I tried the support form on the asset website but although I ticketed 'send me a copy', I've received nothing. I'm guessing maybe the form is broken and you won't receive my query? Apologies if I'm just duplicating an email you've already received.

    Bought MEC pro ages ago and finally starting to use it :)

    Sorry for the stupid newbie question... can I ask for clarification on avoiding the .CancelWith GC allocation?

    The manual says to add the if statement after every yield return statement, but that doesn't make sense... if what?

    I'm using MEC to optimise an AI system, the AICycle moves the player so I'm assuming I do need CancelWith.


    My code is as follows:
    Code (CSharp):
    1. IEnumerator<float> AICycle()
    2.         {
    3.             //Will stop if the script is disabled at any point.
    4.             while (this.enabled)
    5.             {
    6.                 if (currentBehaviour != null)
    7.                 {
    8.                     currentBehaviour.AICycle();
    9.                 }
    10.  
    11.                 //Check to see if we should dodge.
    12.                 //Dodges are automatically triggered after spending time in their target's line of sight
    13.                 if (!isDodging)
    14.                 {
    15.                     if ((myTransform && targetTransform && canMelee && !isMeleeing && Vector3.SqrMagnitude(myTransform.position - targetTransform.position) < meleeRange))
    16.                     {
    17.                         isSprinting = false;
    18.                         animationScript.StopSprinting();
    19.                         SetProperSpeed();
    20.                         //headLookScript.Deactivate();
    21.                         StartCoroutine(AttackInMelee());
    22.                     }
    23.                     else
    24.                     {
    25.                         if (!isMeleeing && Vector3.SqrMagnitude(myTransform.position - currentBehaviour.targetVector) > distFromTargetToSprint && canSprint && engaging)
    26.                         {
    27.                             animationScript.StartSprinting();
    28.                             SetSprintSpeed();
    29.                             headLookScript.Deactivate();
    30.                         }
    31.                     }
    32.                 }
    33.                 MoveAI();
    34.  
    35.                 if ((!targetTransform && !engaging))
    36.                 {
    37.                     isSprinting = false;
    38.                     animationScript.StopSprinting();
    39.                 }
    40.                 else if (Vector3.SqrMagnitude(myTransform.position - currentBehaviour.targetVector) < distFromTargetToSprint && engaging && !isDodging)
    41.                 {
    42.                     animationScript.StopSprinting();
    43.                     SetProperSpeed();
    44.                     if(!usingDynamicObject && !isMeleeing && !inParkour)
    45.                         headLookScript.Activate();
    46.                 }
    47.                 yield return Timing.WaitForSeconds(cycleTime);
    48.  
    49.                 if(gameObject != null && gameObject.activeInHierarchy) // ... what ???
    50.             }
    51.         }
    Any guidance much appreciated
     
  13. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    386
    I do receive messages through that form, and I just tested it and I received my test message. Sorry you had trouble with it. MEC Pro also has my direct email at the top of the file, so you could use that too.

    To answer your question: In your case the while loop should test for it:
    Code (CSharp):
    1. while(gameObject != null && gameObject.activeInHierarchy)
    Make sure that your yield return statement is always at the end of the while loop and that you have no other yield return statements in the middle if you're going to do that.
     
    Last edited: Jan 28, 2018
  14. jeromeWork

    jeromeWork

    Joined:
    Sep 1, 2015
    Posts:
    414
    No worries, Sorry I didn't spot the direct email. Was trying admin@trinary.tech and all sorts, without any luck!

    Thanks for the advice and solution.

    In light of the recents comments, I can certainly confirm that in my game I've halved GC allocation just by optimising this one Coroutine. For me at least it's def better that Unity's.
     
  15. magique

    magique

    Joined:
    May 2, 2014
    Posts:
    3,984
    I need clarification on the KillCoroutines function. The help says "This will kill all coroutines running on the main MEC instance and reset the context." Does this main MEC instance mean the entire running application? So this will kill coroutines on other components also? I'm converting over from Unity StopAllCoroutines and that one just kills coroutines on the component being called from. I just want to clarify that this is the behavior so I can code around it.
     
  16. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    386
    So MEC doesn't store the which gameObject you were running when you started the coroutine like Unity's default coroutines do. When you first start the coroutine you can pass in the gameObject that you want the coroutine to be associated with (storing that tag uses an extra 4 bytes of memory). If you do that on all your coroutines then you can do KillCoroutines(gameObject) to kill only the ones associated with a particular gameObject. The empty KillCoroutines function is typically only used when switching scenes.
     
    magique likes this.
  17. magique

    magique

    Joined:
    May 2, 2014
    Posts:
    3,984
    I don't see that functionality. I see a string tag parameter, but not a GameObject parameter.
     
  18. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    386
    Ok, you must be using the free version. Just use gameObject.GetInstanceID() instead, that's what the Pro version does under the hood anyway.
     
  19. magique

    magique

    Joined:
    May 2, 2014
    Posts:
    3,984
    OK, thanks. Yeah, I'm just trying the free version for now. I might upgrade to Pro, but that would be later on.
     
  20. boscohyun

    boscohyun

    Joined:
    Jan 2, 2013
    Posts:
    2
    I have a problem to use mec in my project.
    I use More Effective coroutines Pro v3.00.1 in Unity3D 5.6.4f1.
    This is my code.

    protected virtual IEnumerator<float> CoInitialize()
    {
    yield return Timing.WaitUntilDone(CoLoadModel());
    if (fsm.next == PresenterState.InitializeFailed)
    {
    yield return Timing.WaitUntilDone(CoUnloadModel());
    yield break;
    }
    yield return Timing.WaitUntilDone(CoLoadViewModel());
    if (fsm.next == PresenterState.InitializeFailed)
    {
    yield return Timing.WaitUntilDone(CoUnloadViewModel());
    yield return Timing.WaitUntilDone(CoUnloadModel());
    yield break;
    }
    // yield return Timing.WaitUntilDone(CoLoadView(parent)); // case #1.
    yield return Timing.WaitUntilDone(CoLoadView(parent), Segment.EndOfFrame); // case #2.
    if (fsm.next == PresenterState.InitializeFailed)
    {
    yield return Timing.WaitUntilDone(CoUnloadView());
    yield return Timing.WaitUntilDone(CoUnloadViewModel());
    yield return Timing.WaitUntilDone(CoUnloadModel());
    yield break;
    }

    fsm.next = PresenterState.Initialized;
    }

    When I use case #1, Timing is wait 57~59 frame after "CoLoadView(parent)" ended. It's weird.
    When I use case #2, Timing is wait just 1 frame after "CoLoadView(parent)" ended.

    This is "CoLoadview(parent)" part.

    public abstract class Presenter
    {
    protected abstract IEnumerator<float> CoLoadView(Transform parent);
    }

    public class TestPresenter : Presenter
    {
    protected override IEnumerator<float> CoLoadView(Transform parent)
    {
    bool inProgress = true;

    ResourceManager.LoadAssetAsync<GameObject>(_showRoomEditViewAssetBundleName, loadedPrefab =>
    {
    // Do something..
    inProgress = false;
    }));

    yield return Timing.WaitUntilFalse(() => inProgress);
    }
    }

    How can I solve this?
     
  21. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    386
    Hi @boscohyun

    v 3.01.0 included a fix for that exact bug, just update to the latest version.
     
  22. boscohyun

    boscohyun

    Joined:
    Jan 2, 2013
    Posts:
    2
    Thank you for your fast answer.
    I updated mec to v3.01.2 but it still same.
    I use "Segment.EndOfFrame" now and I hope to fixed this issue soon.
     
  23. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    386
    So your issue is that there's sometimes a small delay before they resume when you start them on the same segment?

    I think I have an idea of what might be causing that. I'll work on a fix.
     
  24. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    386
    @boscohyun I took away the cause of that delay. You should see the updated version in the asset store in the next couple of days.
     
  25. ratking

    ratking

    Joined:
    Feb 24, 2010
    Posts:
    320
    A simple question, do delayed calls via Timing.CallDelayed() also get paused when calling Timing.PauseCoroutines()? Somehow I don't think they are and I wonder if that's intended or not.
     
  26. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    386
    Hi, please update to the latest version. PauseCoroutines with no arguments wasn't delaying the coroutines that are waiting until v3.01.2
     
  27. ratking

    ratking

    Joined:
    Feb 24, 2010
    Posts:
    320
    Thanks for the reply. I always use the latest version from the asset store, in this case v3.01.3. BTW, I use PauseCoroutines(gameObject).
     
  28. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    386
    OK, well so far as I recall I don't think that calldelayed has an overload that allows you to specify the game object to tag with the coroutine. It does return the handle, and with pro you can add the tag on the next line: handle.layer = gameObject.GetInstanceID();
     
  29. ratking

    ratking

    Joined:
    Feb 24, 2010
    Posts:
    320
    I see. I guess I misinterpreted the fact that you can give a GameObject as parameter to CallDelayed.
     
    Last edited: Mar 7, 2018
  30. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    386
    I see your point. Since that is a convenience function I can make it also tag the coroutine. I'll put that on the list for the next update.
     
  31. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    386
    @ratking The asset store team have been approving updates super quickly lately, so the new version also tags the coroutine if you pass in a game object. I also added a similar overload to call continuously and call periodically, so I think you're going to like that.
     
    ratking and Alverik like this.
  32. hungrybelome

    hungrybelome

    Joined:
    Dec 31, 2014
    Posts:
    333
    Hello, the action passed to CallPeriodically does not seemed to get called, because it calls
    Code (CSharp):
    1.         public static CoroutineHandle CallPeriodically<T>(T reference, float timeframe, float period,
    2.             System.Action<T> action, System.Action<T> onDone = null)
    3.         {
    4.             return action == null ? new CoroutineHandle() : RunCoroutine(Instance._CallContinuously(reference, timeframe, period, action, null, onDone));
    5.         }
    passing in null as the gameObject to _CallContinuously, which then only calls the action with this condition
    Code (CSharp):
    1.                 if (gObject != null && gObject.activeInHierarchy)
    2.                     action();
    I'm using 3.01.4.
     
  33. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    386
    Darn it, you're right. That should be
    Code (CSharp):
    1. if (ReferenceEquals(gObject, null) || (gObject != null && gObject.activeInHierarchy))
    I'll submit the fix tonight and hopefully they approve it tomorrow since they've been so fast lately.
     
    hungrybelome likes this.
  34. kathode

    kathode

    Joined:
    Jan 15, 2013
    Posts:
    63
    Hi, I'm trying to change over a project to using MEC Pro and running into one common issue. I have a number of coroutines that relied on while(true) loops. It seems in Unity default, I could rely on StopAllCoroutines() to kill these loops. That doesn't seem to be the case with MEC using Timing.KillCoroutines(gameObject.GetInstanceID()). The while loop will keep running.

    Could you verify that's the case, and whether there's any kind of global change I can make to address that situation? Right now I'm having to address each one of these individually and it's a bit of a pain. Thank you!
     
  35. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    386
    @kathode You can use Timing.KillCoroutines that way, but you'll need to supply the data about what gameObject each coroutine should be tagged with explicitly when you run the coroutine. In MEC Pro you can do that by passing gameObject into each Run statement (that overload is shorthand which applies the instance ID to the layer of that coroutine), if you're using the free version then you have to pass in gameObject.GetInstanceID().ToString() and compare against that.

    I realize that it's an extra bit to remember, but it can be very helpful in a lot of cases to not have to associate every coroutine with a particular gameObject. Some coroutines, like those to deal with networking, don't need to be associated with a gameObject.
     
    Last edited: Apr 8, 2018
    kathode likes this.
  36. kathode

    kathode

    Joined:
    Jan 15, 2013
    Posts:
    63
    Thank you! I took another pass back over the docs and realized the issue was that I wasn't including the instance ID when starting the coroutines. Doh. At least I know now.
     
    Trinary likes this.
  37. nicloay

    nicloay

    Joined:
    Jul 11, 2012
    Posts:
    521
    Hello @Trinary

    Is this correct way to wait end of frame in MEC-Pro?
    I'm doing blur from camera to render texture and then show popup on top of that cached texture.
    So workflow is following - first end of frame i'm taking render from camera, and then in couple of frames I apply blur shader tho that texture which actually not use render from camera anymore, so I do that in standard update cycle.
    Code (csharp):
    1.  
    2. private CoroutineHandle _handle;
    3. public void SetupRTForTarget(RawImage target)
    4. {            
    5.     _handle = Timing.RunCoroutineSingleton(_DoSetupRTForTarget(target), _handle, SingletonBehavior.Overwrite);
    6. }
    7.  
    8. private IEnumerator<float> _EndOfFrameStub()
    9. {
    10.     yield break;
    11. }
    12.  
    13. private IEnumerator<float> _DoSetupRTForTarget(RawImage target)
    14. {
    15.    ...
    16.     yield return Timing.WaitUntilDone(Timing.RunCoroutine(_EndOfFrameStub(), Segment.EndOfFrame));
    17.    ...
    18.     for (int i = 0; i < blurIterations; i++)
    19.     {
    20.    ...
    21.         Graphics.Blit(targetRT, rt2, blurMaterial, 1 + passOffs);
    22.    ...  
    23.         yield return Timing.WaitForOneFrame;
    24.     }
    25. }
    26.  
    I'm just not sure. Do i need to run the whole _DoSetupRTForTarget in endOfFrame loop, or this code is ok. Another questions is _EndOfFrameStub correct, or maybe there is a better way or shothand for this.

    Thanks.
     
  38. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    386
    Hi nicloay,

    I think what you are trying to do is switch your coroutine back and forth between the update and end of frame segments. If that's what you want then you should use the command "yield return Timing.SwitchCoroutine(segment);" MEC coroutines will continue to run in the segment they're defined in, rather than switching back to Update every frame like Unity's default coroutines do. This means that when you want to go back to the update segment you'll have to call switch again.

    I hope that helps.
     
  39. nicloay

    nicloay

    Joined:
    Jul 11, 2012
    Posts:
    521
    @Trinary
    So is this correct way to wait current end of frame?
    Code (csharp):
    1.  
    2. private CoroutineHandle _handle;
    3. public void SetupRTForTarget(RawImage target)
    4. {  
    5.     _handle = Timing.RunCoroutineSingleton(_DoSetupRTForTarget(target), _handle, SingletonBehavior.Overwrite);
    6. }
    7.  
    8. private IEnumerator<float> _DoSetupRTForTarget(RawImage target)
    9. {
    10. ...
    11.     yield return Timing.SwitchCoroutine(Segment.EndOfFrame);
    12.     yield return Timing.WaitForOneFrame;
    13.     yield return Timing.SwitchCoroutine(Segment.Update);
    14.  
    Do I need to return 0.0f on line 13?

    With code above I'm getting following error
    Code (csharp):
    1.  
    2. NullReferenceException: Object reference not set to an instance of an object
    3. MEC.Timing.RunCoroutineInternal (IEnumerator`1 coroutine, Segment segment, Nullable`1 layer, System.String tag, CoroutineHandle handle, Boolean prewarm) (at Assets/3DParty/Trinary Software/Timing.cs:2247)
    4. MEC.Timing.RunCoroutineSingleton (IEnumerator`1 coroutine, CoroutineHandle handle, SingletonBehavior behaviorOnCollision) (at Assets/3DParty/Trinary Software/Timing.cs:1494)
    5.  
     
    Last edited: May 3, 2018
  40. nicloay

    nicloay

    Joined:
    Jul 11, 2012
    Posts:
    521
    @Trinary

    Is there any way to make nesting coroutine works exact same way as unity does. Please check following test
    Code (csharp):
    1.  
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using MEC;
    5. using UnityEngine;
    6.  
    7. public class MecTestFrames : MonoBehaviour {
    8.  
    9.    // Use this for initialization
    10.    IEnumerator Start ()
    11.    {
    12.       Debug.Log("======testWith timing====");
    13.       TestWithTiming();
    14.       yield return new WaitForSeconds(0.5f);
    15.       Debug.Log("======testWith native====");
    16.       TestWithCoroutine();
    17.    }
    18.  
    19.  
    20.    void TestWithCoroutine()
    21.    {
    22.       StartCoroutine(_CNative1());
    23.    }
    24.    
    25.  
    26.    void TestWithTiming()
    27.    {
    28.       Timing.RunCoroutine(_CTiming1());
    29.    }
    30.    
    31.    IEnumerator<float> _CTiming1()
    32.    {
    33.       Debug.Log("C1.1 Framenumber" + Time.frameCount);
    34.       yield return Timing.WaitUntilDone( _CTiming2());      
    35.       Debug.Log("C1.2 Framenumber" + Time.frameCount);
    36.  
    37.    }
    38.  
    39.    IEnumerator<float> _CTiming2()
    40.    {
    41.       Debug.Log("C2.1 Framenumber" + Time.frameCount);
    42.       yield return Timing.WaitForOneFrame;
    43.       Debug.Log("C2.2 Framenumber" + Time.frameCount);
    44.    }
    45.    
    46.    
    47.    IEnumerator _CNative1()
    48.    {
    49.       Debug.Log("C1.1 Framenumber" + Time.frameCount);
    50.       yield return StartCoroutine(_CNative2());    
    51.       Debug.Log("C1.2 Framenumber" + Time.frameCount);
    52.    }
    53.  
    54.    IEnumerator _CNative2()
    55.    {
    56.       Debug.Log("C2.1 Framenumber" + Time.frameCount);
    57.       yield return null;
    58.       Debug.Log("C2.2 Framenumber" + Time.frameCount);
    59.    }
    60. }
    61.  
    And here is a result. Please note that C2.2 == C1.2 in case of native coroutines and different with MEC

    Code (csharp):
    1.  
    2. ======testWith timing====
    3. C1.1 Framenumber1
    4. C2.1 Framenumber1
    5. C2.2 Framenumber2
    6. C1.2 Framenumber3
    7.  
    8. ======testWith native====
    9. C1.1 Framenumber4
    10. C2.1 Framenumber4
    11. C2.2 Framenumber5
    12. C1.2 Framenumber5
    13.  
     
  41. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    386
    @nicloay That is how you do a SwitchCoroutine, but you're right that you don't need the extra yield return in there on line 2.

    That's interesting about the frame timing. MEC sets a pause bit when you do a WaitUntilDone and unsets that pause bit when the coroutine finishes. Coroutines are also executed each frame in the order in which they were created. That means that C1 had already been evaluated and was still paused by the time C2 finished. I can put it on the TODO list to tweak the framework slightly to make it behave the same way on that test as Unity's do. For now you can make it do that by calling RunCoroutine on C2 before C1, storing the return of RunCoroutine, and calling WaitUntilDone on that stored handle.
     
  42. nicloay

    nicloay

    Joined:
    Jul 11, 2012
    Posts:
    521
    Sorry to bother you again. It's just a quick hint. That even if you iterate coroutine by hands it has the same behaviour as Unity.

    Code (csharp):
    1.  
    2. IEnumerator Start ()
    3. {
    4.    Debug.Log("======testWith timing====");
    5.    TestWithTiming();
    6.    yield return new WaitForSeconds(0.5f);
    7.    Debug.Log("======testWith native====");
    8.    TestWithCoroutine();
    9.  
    10.    yield return new WaitForSeconds(0.5f);
    11.    Debug.Log("========iterate by hands=====");
    12.    StartCoroutine( IterateByHands());
    13. }
    14.  
    15. private IEnumerator IterateByHands()
    16. {
    17.    IEnumerator n1 = _CNative1();
    18.    while (n1.MoveNext())
    19.    {
    20.       yield return null;
    21.    }
    22. }
    23.  
    and this is a result
    Code (csharp):
    1.  
    2. ======testWith timing====
    3. C1.1 Framenumber1
    4. C2.1 Framenumber1
    5. C2.2 Framenumber2
    6. C1.2 Framenumber3
    7.  
    8. ======testWith native====
    9. C1.1 Framenumber4
    10. C2.1 Framenumber4
    11. C2.2 Framenumber5
    12. C1.2 Framenumber5
    13.  
    14. ========iterate by hands=====
    15. C1.1 Framenumber10
    16. C2.1 Framenumber10
    17. C2.2 Framenumber11
    18. C1.2 Framenumber11
    19.  
    20.  
     
  43. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    386
    Perhaps Unity is using the replacement method to make one coroutine wait for another. MEC doesn't use replacement because replacement only allows for a single coroutine to be waiting for any one particular coroutine to finish. MEC allows you to set coroutines to wait for each other using the handle, which means that that 1 to 1 relationship is not guaranteed. That's why MEC uses a different method. I can rearrange things to simulate this behavior in an upcoming update though.
     
  44. nicloay

    nicloay

    Joined:
    Jul 11, 2012
    Posts:
    521
    Sorry to bother you again.
    Could you give me example. I don't understand how I can Call C2 prior C1. Or there is no workaround right now to fix C2 from C1 body? something like in the code below (which works the same wrong right now)

    Code (csharp):
    1.  
    2. void TestWithTiming()
    3. {
    4.    Timing.RunCoroutine(_CTiming1());
    5. }
    6.  
    7. IEnumerator<float> _CTiming1()
    8. {
    9.    Debug.Log("C1.1 Framenumber" + Time.frameCount);
    10.    CoroutineHandle handle = Timing.RunCoroutine(_CTiming2());    
    11.    yield return Timing.WaitUntilDone(handle);          
    12.    Debug.Log("C1.2 Framenumber" + Time.frameCount);      
    13. }
    14.  
    15. IEnumerator<float> _CTiming2()
    16. {
    17.    Debug.Log("C2.1 Framenumber" + Time.frameCount);
    18.    yield return Timing.WaitForOneFrame;
    19.    Debug.Log("C2.2 Framenumber" + Time.frameCount);
    20. }
    21.  
     
  45. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    386
    As a temporary measure until the next update, you can use SwitchCoroutine to move the current coroutine to the end of the execution list:

    Code (CSharp):
    1. IEnumerator<float> _CTiming1()
    2. {
    3.    Debug.Log("C1.1 Framenumber" + Time.frameCount);
    4.    CoroutineHandle handle = Timing.RunCoroutine(_CTiming2());
    5.    yield return Timing.SwitchCoroutine(Segment.Update);  
    6.    yield return Timing.WaitUntilDone(handle);        
    7.    Debug.Log("C1.2 Framenumber" + Time.frameCount);    
    8. }
     
    nicloay likes this.
  46. nicloay

    nicloay

    Joined:
    Jul 11, 2012
    Posts:
    521
    @Trinary, I just tried your code, but it drop me NullRefference exception.

    Here is a full source code for this test
    Code (csharp):
    1.  
    2. using System.Collections.Generic;
    3. using MEC;
    4. using UnityEngine;
    5.  
    6. public class MecTestFrames : MonoBehaviour {
    7.    
    8.    void Start ()
    9.    {
    10.       Debug.Log("======testWith timing====");
    11.       Timing.RunCoroutine(_CTiming1());
    12.       Debug.Log("========iterate by hands=====");      
    13.    }  
    14.    
    15.    IEnumerator<float> _CTiming1()
    16.    {
    17.       Debug.Log("C1.1 Framenumber" + Time.frameCount);
    18.       CoroutineHandle handle = Timing.RunCoroutine(_CTiming2());    
    19.       yield return Timing.SwitchCoroutine(Segment.Update);
    20.       yield return Timing.WaitUntilDone(handle);
    21.       Debug.Log("C1.2 Framenumber" + Time.frameCount);      
    22.    }
    23.  
    24.    IEnumerator<float> _CTiming2()
    25.    {
    26.       Debug.Log("C2.1 Framenumber" + Time.frameCount);
    27.       yield return Timing.WaitForOneFrame;
    28.       Debug.Log("C2.2 Framenumber" + Time.frameCount);
    29.    }    
    30. }
    31.  
    this is output
    upload_2018-5-14_14-17-59.png

    And here is a stacktrace
    Code (csharp):
    1.  
    2. NullReferenceException: Object reference not set to an instance of an object
    3. MEC.Timing.RunCoroutineInternal (IEnumerator`1 coroutine, Segment segment, Nullable`1 layer, System.String tag, CoroutineHandle handle, Boolean prewarm) (at Assets/3DParty/Trinary Software/Timing.cs:2247)
    4. MEC.Timing.RunCoroutine (IEnumerator`1 coroutine) (at Assets/3DParty/Trinary Software/Timing.cs:1168)
    5. MecTestFrames.Start () (at Assets/Tests/MecTest/MecTestFrames.cs:11)
    6.  
    Hope this helps. waiting for fix or another solution from you.

    Thanks.
     
  47. nicloay

    nicloay

    Joined:
    Jul 11, 2012
    Posts:
    521
    @Trinary, sorry to rush you. but is there any updates?

    By the way. What I'm doing is coding sequences like GameOver sequence where many tweens goes one by one or several at once (ShowPanel->move caption-> scale icons size-> move something else). And some delays between tweens are really critical as sometime I need to switch sprites, and with one frame delay it become quite glitchy.

    Maybe its' possible to hack Timing class so all "Waitings" will not use that one frame delay between nested coroutines?

    upd1:

    Do you think is it ok if I'll change iteration across existing coroutines order inside Timing.Update() loop

    e.g. for Update segment on line 396
    from
    Code (csharp):
    1. for (coindex.i = 0; coindex.i < _lastUpdateProcessSlot; coindex.i++)
    to
    Code (csharp):
    1. for (coindex.i = _lastUpdateProcessSlot - 1; coindex.i >=0; coindex.i--)
    As i understand is that the problem that you use FIFO order for existing coroutines, and main coroutine iterate earlier than nested coroutine.
    with LIFO loop(as in code above coindex.i--) my test seems passed, but you know your code better. so please confirm that it won't break anything critical

    Thanks in advance.
    //Nikolay.
     
    Last edited: May 16, 2018
  48. nicloay

    nicloay

    Joined:
    Jul 11, 2012
    Posts:
    521
    Is this possible to WaitUntilDone all coroutines with the same layer or tag?
    right now I save all handles and then use
    Code (csharp):
    1.  
    2. yield return Timing.WaitUntilFalse(() => windowHandle.IsRunning || captionHandle.IsRunning || buttonHandles[0].IsRunning || buttonHandles[1].IsRunning || buttonHandles[2].IsRunning);
    3.  
    Which is probably not a best way.

    Thanks.
     
  49. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    386
    Hi @nicloay

    Sorry for the delay in responding, the forums didn't notify me that there were replies.

    There shouldn't be any null reference exceptions, I'm going to have to fix that. Using WaitUntilFalse will work, but I would think you would run into the same problem with the waiting coroutine not continuing execution until the next frame. If you like you can send me an email with your invoice number (my email address is at the top of the Timing script) and I'll send you an updated version as soon as I finish it. That way you won't have to wait until probably next week for the new version to be approved by the asset store in order to test it.
     
    nicloay likes this.
  50. RazaTech

    RazaTech

    Joined:
    Feb 27, 2015
    Posts:
    178
    Hi there,

    I'm using your courtine library. i use it with unity web request to load asset from streaming assets and cache them. In typical scenrario, if there are 100 assets and only 1 asset is requested at a time as its a gallery view so i run a cortine with unity we request to load the asset and cache it if already not cached. Which means if there are 100 assets ill run 100 courtines.
    So i was thinking if i just run 1 courtine that wait for 1 second and then see if there is new asset requested and then load it and wait again, which means there will b no garbage generation except for 1 courtine (instead of 100)

    is this right approach?

    Thanks