Search Unity

[FREE] More Effective Coroutines

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

  1. imaginationrabbit

    imaginationrabbit

    Joined:
    Sep 23, 2013
    Posts:
    349
    Ah I didn't know about Timing.CurrentCoroutine! I'll do a compare- I bet that will fix it- thank you!
     
    Trinary likes this.
  2. imaginationrabbit

    imaginationrabbit

    Joined:
    Sep 23, 2013
    Posts:
    349
    Also sorry didn't see the above question about the version- I'm using 3.10.1
     
    Trinary likes this.
  3. imaginationrabbit

    imaginationrabbit

    Joined:
    Sep 23, 2013
    Posts:
    349
    Spoke too soon got the error again- I'll post the sequence of code that is executing so you can see what I'm doing wrong- I removed all the CancelWith calls as I added them out of desperation to fix the bug-

    This starts it
    Code (CSharp):
    1.   Plooper1Player = Timing.RunCoroutine(_LoopMaster1());

    Code (CSharp):
    1.  IEnumerator<float> _LoopMaster1()
    2.     {
    3.         yield return Timing.WaitUntilDone(Timing.RunCoroutine(_LoopWaiter1(Plooper1, Plooper1WaitTimes)));
    4.         Plooper1Player = Timing.RunCoroutine(_LoopMaster1());
    5.         yield break;
    6.     }
    Code (CSharp):
    1.  IEnumerator<float> _LoopWaiter1(List<CommandQueuer> theList, List<int> waitTimeList)
    2.     {
    3.         for (int index = 0; index < waitTimeList.Count; index++)
    4.         {
    5.  
    6.             var selectedWaitTime = waitTimeList[index];
    7.  
    8.             if (index >= 0 && index < theList.Count - 1)
    9.             {
    10.  
    11.                 var theCommand = theList[index];
    12.                 yield return Timing.WaitUntilDone(_WaitThenFireCommand(selectedWaitTime, theCommand));
    13.             }
    14.             else
    15.             {
    16.                 continue;
    17.             }
    18.         }
    19.  
    20.         yield break;
    21.     }
    Code (CSharp):
    1.  IEnumerator<float> _WaitThenFireCommand(int waitFrames, CommandQueuer theCommand)
    2.     {
    3.         int loopedFrames = 0;
    4.  
    5.         while (waitFrames > loopedFrames)
    6.         {
    7.             loopedFrames++;
    8.             yield return Timing.WaitForOneFrame;
    9.         }
    10.  
    11.             theCommand.FireCommand();
    12.  
    13.         yield break;
    14.     }
    The code is for running something like a looper pedal- I should note I get the error when two of these "looper pedals" are running at once- they are both calling the "_WaitThenFireCommand" Coroutine-

    To stop the Looper1 I call
    Code (CSharp):
    1. Plooper1IsPlaying = false;
     
    Trinary likes this.
  4. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    Please try upgrading to the latest version and see if that fixes it. I remember fixing a bug that sounds similar to what you're describing a little bit ago.
     
  5. larryPlayablStudios

    larryPlayablStudios

    Joined:
    May 15, 2018
    Posts:
    31
    In Unity I'm used to this pattern:


    Code (CSharp):
    1.         public void Caller()
    2.         {
    3.             Coroutine coroutine = StartCoroutine(ParentRoutine());
    4.             StopCoroutine(coroutine); // called when a condition is met
    5.         }
    6.  
    7.         public IEnumerator ParentRoutine()
    8.         {
    9.             yield return ChildRoutine(); // Wait on the child routine
    10.         }
    11.  
    12.         public IEnumerator ChildRoutine()
    13.         {
    14.             // Logic in a loop running for a duration
    15.         }
    In MEC, a similar series of methods would look like:
    Code (CSharp):
    1.         public void Caller()
    2.         {
    3.             CoroutineHandle handle = Timing.RunCoroutine(ParentRoutine())
    4.             Timing.KillCoroutines(handle); // Called when a condition is met
    5.         }
    6.  
    7.         public IEnumerator<float> ParentRoutine()
    8.         {
    9.             yield return ChildRoutine().WaitUntilDone(); // Wait on the child routine
    10.         }
    11.  
    12.         public IEnumerator<float> ChildRoutine()
    13.         {
    14.             // Logic in a loop running for a duration
    15.             yield break;
    16.         }
    However there's a key difference in the implementation. In the Unity coroutine example above, stopping the coroutine will stop both the parent and child. In the MEC implementation, the call to KillCoroutines on the handle will stop the parent while the child continues.

    I'm exploring these two options for replicating the Unity logic:

    Code (CSharp):
    1.         public IEnumerator<float> ParentRoutine()
    2.         {
    3.             // Option 1 - using KillWith
    4.             yield return Timing.WaitUntilDone(
    5.                 Timing.RunCoroutine(ChildRoutine().KillWith(Timing.CurrentCoroutine))
    6.             );
    7.  
    8.             // Option 2 - using LinkCoroutines
    9.             CoroutineHandle handle = Timing.RunCoroutine(ChildRoutine());
    10.             Timing.LinkCoroutines(Timing.CurrentCoroutine, handle);
    11.             yield return Timing.WaitUntilDone(handle);
    12.         }
    As far as I can tell these function the same and both replicate the logic I'm seeking, where killing the handle to the parent will stop the child coroutines along with it. Is there any reason that one is preferable over the other? I may have cases of firing off a chain of coroutines following this pattern where many will end up "childed" to the parent. Or is there another option that I should consider?

    Thanks!
     
    Last edited: Aug 13, 2020
  6. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    @larryPlayablStudios That's a great analysis. The only real functional difference is that you have to do KillWith when you run the coroutine, whereas links can be added at any point.

    I can see the value in making WaitUntilDone do this by default.. but I'm afraid that the change might break existing code. Links are kind of redundant. I'm tempted to remove the functionality.. but once again hesitant to potentially break existing code.
     
  7. larryPlayablStudios

    larryPlayablStudios

    Joined:
    May 15, 2018
    Posts:
    31
    @Trinary Thanks. I made a pass at implementing my own version of WaitUntilDone with this logic and did find an execution difference in the case of coroutines that are nested more than one level deep.

    Here's the summary of using KillWith:

    The KillWith WaitUntilDone Extension method:

    Code (CSharp):
    1. public static float WaitUntilDoneUsingKillWith(this IEnumerator<float> newCoroutine, CoroutineHandle parentHandle = default)
    2.         {
    3.             CoroutineHandle currentRoutine = Timing.CurrentCoroutine;
    4.             Logger.Log(string.Format("WaitUntilDoneUsingKillWith \n[Current==ParentHandle {0}] \n[Current {1}] \n[Current IsRunning {2}] \n[ParentHandle {3}] \n[ParentHandleIsRunning {4}] \n[newRoutine {5}]",
    5.                 parentHandle == currentRoutine,
    6.                 currentRoutine,
    7.                 currentRoutine.IsRunning,
    8.                 parentHandle == default ? "default parentHandle" : parentHandle.ToString(),
    9.                 parentHandle == default ? "false" : parentHandle.IsRunning.ToString(),
    10.                 newCoroutine));
    11.  
    12.             return Timing.WaitUntilDone(Timing.RunCoroutine(newCoroutine.KillWith(Timing.CurrentCoroutine)));
    13.         }
    The KillWith Test Code:

    Code (CSharp):
    1. private CoroutineHandle parentRoutineHandle;
    2.  
    3.         public void TestUsingKillWith()
    4.         {
    5.             parentRoutineHandle = Timing.RunCoroutine(ParentWaitUntilUsingKillWith());
    6.         }
    7.  
    8.         public IEnumerator<float> ParentWaitUntilUsingKillWith()
    9.         {
    10.             // yield return so that the parentHandle will be assigned
    11.             yield return Timing.WaitForOneFrame;
    12.  
    13.             Logger.Log("ParentWaitUntilUsingKillWith 1");
    14.             yield return ChildRoutineUsingKillWith().WaitUntilDoneUsingKillWith(parentHandle: parentRoutineHandle);
    15.             Logger.Log("ParentWaitUntilUsingKillWith 2");
    16.         }
    17.  
    18.         public IEnumerator<float> ChildRoutineUsingKillWith()
    19.         {
    20.             int numKilled = Timing.KillCoroutines(parentRoutineHandle);
    21.  
    22.             CoroutineHandle currentRoutine = Timing.CurrentCoroutine;
    23.  
    24.             Logger.Log(string.Format(
    25.                 "After killed parentRoutineHandle during ChildRoutineUsingKillWith \n[NumKilled {0}] \n[Current==ParentHandle {1}] \n[ParentHandle {2}] \n[IsParentRunning? {3}] \n[CurrentCoroutine {4}] \n[IsCurrentRunning? {5}]",
    26.                 numKilled,
    27.                 currentRoutine == parentRoutineHandle,
    28.                 parentRoutineHandle,
    29.                 parentRoutineHandle.IsRunning,
    30.                 currentRoutine,
    31.                 currentRoutine.IsRunning));
    32.  
    33.             Logger.Log("ChildRoutineUsingKillWith 1");
    34.             yield return GrandchildRoutine().WaitUntilDoneUsingKillWith(parentHandle: parentRoutineHandle);
    35.             Logger.Log("ChildRoutineUsingKillWith 2");
    36.         }
    37.  
    38.         public IEnumerator<float> GrandchildRoutine()
    39.         {
    40.             CoroutineHandle currentRoutine = Timing.CurrentCoroutine;
    41.  
    42.             Logger.Log(string.Format(
    43.                 "At start of GranchildRoutine \n[Current==ParentHandle {0}] \n[ParentHandle {1}] \n[IsParentRunning? {2}] \n[CurrentCoroutine {3}] \n[IsCurrentRunning? {4}]",
    44.                 currentRoutine == parentRoutineHandle,
    45.                 parentRoutineHandle,
    46.                 parentRoutineHandle.IsRunning,
    47.                 currentRoutine,
    48.                 currentRoutine.IsRunning));
    49.  
    50.             Logger.Log("GrandchildRoutine 1");
    51.             yield return Timing.WaitForOneFrame;
    52.             Logger.Log("GrandchildRoutine 2");
    53.         }
    The output - notice that GrandchildRoutine fully runs:

    Code (CSharp):
    1. ParentWaitUntilUsingKillWith 1
    2.  
    3. WaitUntilDoneUsingKillWith
    4. [Current==ParentHandle True]
    5. [Current test.MECTest+<ParentWaitUntilUsingKillWith>d__4]
    6. [Current IsRunning True]
    7. [ParentHandle test.MECTest+<ParentWaitUntilUsingKillWith>d__4]
    8. [ParentHandleIsRunning True]
    9. [newRoutine test.MECTest+<ChildRoutineUsingKillWith>d__5]
    10.  
    11. After killed parentRoutineHandle during ChildRoutineUsingKillWith
    12. [NumKilled 1]
    13. [Current==ParentHandle False]
    14. [ParentHandle ]
    15. [IsParentRunning? False]
    16. [CurrentCoroutine MECExtensionMethods2+<KillWith>d__12]
    17. [IsCurrentRunning? True]
    18.  
    19. ChildRoutineUsingKillWith 1
    20.  
    21. WaitUntilDoneUsingKillWith
    22. [Current==ParentHandle False]
    23. [Current MECExtensionMethods2+<KillWith>d__12]
    24. [Current IsRunning True]
    25. [ParentHandle ]
    26. [ParentHandleIsRunning False]
    27. [newRoutine test.MECTest+<GrandchildRoutine>d__6]
    28.  
    29. At start of GranchildRoutine
    30. [Current==ParentHandle False]
    31. [ParentHandle ]
    32. [IsParentRunning? False]
    33. [CurrentCoroutine MECExtensionMethods2+<KillWith>d__12]
    34. [IsCurrentRunning? True]
    35.  
    36. GrandchildRoutine 1
    37. GrandchildRoutine 2
    The Link WaitUntilDone Extension Method:

    Code (CSharp):
    1.         public static float WaitUntilDoneUsingLink(this IEnumerator<float> newCoroutine, CoroutineHandle parentHandle = default)
    2.         {
    3.             CoroutineHandle currentRoutine = Timing.CurrentCoroutine;
    4.             Logger.Log(string.Format("WaitUntilDoneUsingLink \n[Current==ParentHandle {0}] \n[Current {1}] \n[Current IsRunning {2}] \n[ParentHandle {3}] \n[ParentHandleIsRunning {4}] \n[newRoutine {5}]",
    5.                 parentHandle == currentRoutine,
    6.                 currentRoutine,
    7.                 currentRoutine.IsRunning,
    8.                 parentHandle == default ? "default parentHandle" : parentHandle.ToString(),
    9.                 parentHandle == default ? "false" : parentHandle.IsRunning.ToString(),
    10.                 newCoroutine));
    11.  
    12.             CoroutineHandle handle = Timing.RunCoroutine(newCoroutine);
    13.             Timing.LinkCoroutines(Timing.CurrentCoroutine, handle);
    14.             return Timing.WaitUntilDone(handle);
    15.         }
    The Link Test Code:

    Code (CSharp):
    1. public void TestUsingLink()
    2.         {
    3.             parentRoutineHandle = Timing.RunCoroutine(ParentWaitUntilUsingLink());
    4.         }
    5.  
    6.         public IEnumerator<float> ParentWaitUntilUsingLink()
    7.         {
    8.             // yield return so that the parentHandle will be assigned
    9.             yield return Timing.WaitForOneFrame;
    10.  
    11.             Logger.Log("ParentWaitUntilUsingLink 1");
    12.             yield return ChildRoutineUsingLink().WaitUntilDoneUsingLink(parentHandle: parentRoutineHandle);
    13.             Logger.Log("ParentWaitUntilUsingLink 2");
    14.         }
    15.  
    16.         public IEnumerator<float> ChildRoutineUsingLink()
    17.         {
    18.             int numKilled = Timing.KillCoroutines(parentRoutineHandle);
    19.  
    20.             CoroutineHandle currentRoutine = Timing.CurrentCoroutine;
    21.  
    22.             Logger.Log(string.Format(
    23.                 "After killed parentRoutineHandle during ChildRoutineUsingLink \n[NumKilled {0}] \n[Current==ParentHandle {1}] \n[ParentHandle {2}] \n[IsParentRunning? {3}] \n[CurrentCoroutine {4}] \n[IsCurrentRunning? {5}]",
    24.                 numKilled,
    25.                 currentRoutine == parentRoutineHandle,
    26.                 parentRoutineHandle,
    27.                 parentRoutineHandle.IsRunning,
    28.                 currentRoutine,
    29.                 currentRoutine.IsRunning));
    30.  
    31.             Logger.Log("ChildRoutineUsingLink 1");
    32.             yield return GrandchildRoutine().WaitUntilDoneUsingLink(parentHandle: parentRoutineHandle);
    33.             Logger.Log("ChildRoutineUsingLink 2");
    34.         }
    The Link Test Output. Notice that GrandchildRoutine stops execution at the yield:

    Code (CSharp):
    1. ParentWaitUntilUsingLink 1
    2.  
    3. WaitUntilDoneUsingLink
    4. [Current==ParentHandle True]
    5. [Current test.MECTest+<ParentWaitUntilUsingLink>d__7]
    6. [Current IsRunning True]
    7. [ParentHandle test.MECTest+<ParentWaitUntilUsingLink>d__7]
    8. [ParentHandleIsRunning True]
    9. [newRoutine test.MECTest+<ChildRoutineUsingLink>d__8]
    10.  
    11. After killed parentRoutineHandle during ChildRoutineUsingLink
    12. [NumKilled 1]
    13. [Current==ParentHandle False]
    14. [ParentHandle ]
    15. [IsParentRunning? False]
    16. [CurrentCoroutine test.MECTest+<ChildRoutineUsingLink>d__8]
    17. [IsCurrentRunning? True]
    18.  
    19. ChildRoutineUsingLink 1
    20.  
    21. WaitUntilDoneUsingLink
    22. [Current==ParentHandle False]
    23. [Current test.MECTest+<ChildRoutineUsingLink>d__8]
    24. [Current IsRunning True]
    25. [ParentHandle ]
    26. [ParentHandleIsRunning False]
    27. [newRoutine test.MECTest+<GrandchildRoutine>d__6]
    28.  
    29. At start of GranchildRoutine
    30. [Current==ParentHandle False]
    31. [ParentHandle ]
    32. [IsParentRunning? False]
    33. [CurrentCoroutine test.MECTest+<GrandchildRoutine>d__6]
    34. [IsCurrentRunning? True]
    35.  
    36. GrandchildRoutine 1
    I'm assuming there's a difference in timing such that the WaitUntilDoneUsingKillWith implementation "misses" the end of the parent routine so by that point CurrentRoutine is no longer going to attach it to the parent routine?

    Is there a way I could tweak the implementation of the KillWith extension so GrandchildRoutine would properly stop when it hits the yield?
     
    Last edited: Aug 13, 2020
  8. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    It took me a while, but I think I have a pretty good idea as to why you're seeing that strange behavior. Your WaitUntilDoneUsingKillWith function uses a return statement rather than a yield return statement. There are a lot of layers to unpack there and I don't have time to go through it step by step right now, but I believe it's causing Timing.CurrentCoroutine to be pointing to the new coroutine at that moment, rather than the enclosing coroutine. I hope to have the time to verify that this weekend.
     
  9. larryPlayablStudios

    larryPlayablStudios

    Joined:
    May 15, 2018
    Posts:
    31
    @Trinary Interesting. Thanks for looking into it. To do a yield return statement, wouldn't that mean the signature would need to return an IEnumerator<float>?

    If so, then the calling coroutine wouldn't be able to yield right on it, right?

    I definitely want a solution that can work like:

    Code (CSharp):
    1. IEnumerator<float> ParentRoutine()
    2. {
    3.    yield return ChildRoutine().WaitUntilDone();
    4. }
    But with the functionality where killing the parent routine will stop the child along with it.
     
  10. ben-rasooli

    ben-rasooli

    Joined:
    May 1, 2014
    Posts:
    40
    @Trinary Is there a way to cancel Timing.WaitUntilTrue() ?
    I'm using the Pro version
     
  11. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    Hmm.. that particular function doesn't have a way to return the handle. However, that functionality is pretty easy to replicate using your own coroutine.

    Code (CSharp):
    1. CoroutineHandle evalHandle = RunCoroutine(_Eval(() => myBool));
    2.  
    3. yield return Timing.WaitUntilDone(evalHandle);
    4.  
    5. ....
    6.  
    7. private IEnumerator<float> _Eval(System.Func<bool> evalFunc)
    8. {
    9.   while (!evalFunc())
    10.     yield return Timing.WaitForOneFrame;
    11. }
     
  12. larryPlayablStudios

    larryPlayablStudios

    Joined:
    May 15, 2018
    Posts:
    31
    @Trinary Any thoughts about our discussion above regarding the subtle distinction between KillWith and Link?
     
  13. ben-rasooli

    ben-rasooli

    Joined:
    May 1, 2014
    Posts:
    40
    What will happen to
    yield return Timing.WaitUntilDone(evalHandle);
    when I cancel the evalHandle? Is it going to run infinitely?
     
  14. larryPlayablStudios

    larryPlayablStudios

    Joined:
    May 15, 2018
    Posts:
    31
    @ben-rasooli When you cancel evalHandle the WaitUntilDone should complete the next time it's evaluated.
     
    Trinary and ben-rasooli like this.
  15. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    If links are working for you then use those. I don't think it would be a good idea to change the behavior of WaitUntilDone, because there are several use cases where you would want to wait using a handle and in those cases it would be strange to kill a coroutine just because some other coroutine was waiting to finish.
     
  16. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    WaitUntilDone watches the handle you pass in every frame and resumes once that coroutine ends for any reason.
     
    ben-rasooli likes this.
  17. CryReaper

    CryReaper

    Joined:
    Aug 7, 2015
    Posts:
    3
    @Trinary I have a warning in the console that shows up following coroutine execution. "AssertionException: Assertion failure. Value was True Expected: False The two coroutines are not running on the same MEC instance."

    In the log, it seems to point to a yield return WaitUntilDone I have in a coroutine. Do you have an idea of what would cause this warning to show up? Everything seems to function correctly.
     
    Last edited: Oct 22, 2020
  18. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    You would typically see that if you have accidentally created more than one instance of the Timing object and saved one or more of them in your scene. If you have purposefully created more than one instance then you can use the WaitUntilDoneOnInstance function and reference the correct instance, but most people don't use MEC that way. If you're just using a single instance of it then delete any Timing instances in your scene and it should clear that warning up.
     
  19. CryReaper

    CryReaper

    Joined:
    Aug 7, 2015
    Posts:
    3
    Thanks for the quick response. Turns out I missed a StartCoroutine when converting to MEC and just had to replace it with Timing.RunCoroutine. Warning went away after that.
     
    Trinary likes this.
  20. LudumCor

    LudumCor

    Joined:
    Nov 11, 2016
    Posts:
    8
    Hello @Trinary! I'm using MEC Pro and it is a really good asset, so far I'm really happy with!
    Is there a way to run a coroutine every x frames?
    I found Segment.ManualTimeframe but I didn't find anything in the documentation.
     
  21. larryPlayablStudios

    larryPlayablStudios

    Joined:
    May 15, 2018
    Posts:
    31
    @Trinary I'm seeing some behavior that I don't totally understand. I might just be misunderstanding some expected functionality or this may be an edge-case bug.

    Here is some example test code:

    Code (CSharp):
    1. private CoroutineHandle routineAHandle;
    2.  
    3.     // Update is called once per frame
    4.     void Update()
    5.     {
    6.         if (Input.GetKeyDown(KeyCode.Space))
    7.         {
    8.             routineAHandle = Timing.RunCoroutine(RoutineA());
    9.         }
    10.     }
    11.  
    12.     public IEnumerator<float> RoutineA()
    13.     {
    14.         yield return Timing.WaitForOneFrame;
    15.  
    16.         Debug.Log("MEC TEST before starting RoutineB");
    17.  
    18.         CoroutineHandle routineBHandle = Timing.RunCoroutine(RoutineB());
    19.         // If this yield is removed then everything works properly
    20.         yield return Timing.WaitUntilDone(routineBHandle);
    21.  
    22.         // Execution will never reach here because this routine is killed
    23.         Debug.Log("MEC TEST after starting RoutineB");
    24.     }
    25.  
    26.     public IEnumerator<float> RoutineB()
    27.     {
    28.         yield return Timing.WaitForOneFrame;
    29.  
    30.         Debug.Log("Before killing RoutineA");
    31.  
    32.         // Kill routineA, which is yielding on this routine's handle
    33.         Timing.KillCoroutines(routineAHandle);
    34.  
    35.         Debug.Log("After killing RoutineA");
    36.  
    37.         Debug.Log("Before starting RoutineC");
    38.  
    39.         CoroutineHandle routineCHandle = Timing.RunCoroutine(RoutineC());
    40.         yield return Timing.WaitUntilDone(routineCHandle);
    41.  
    42.         // Execution reaches here while routineC is still running when it should be waiting on it
    43.         Debug.Log("After RoutineC WaitUntilDone. Is RoutineC still running? " + routineCHandle.IsRunning);
    44.     }
    45.  
    46.     public IEnumerator<float> RoutineC()
    47.     {
    48.         Debug.Log("RoutineC Start");
    49.         yield return Timing.WaitForOneFrame;
    50.         Debug.Log("RoutineC Done");
    51.     }
    The general flow is:
    - StartRoutineA and store a handle to it
    - RoutineA starts RoutineB and yields on the routineB handle with a WaitUntilDone
    - After RoutineB starts, RoutineB is killed off (it's my understanding this shouldn't impact the other routines)
    - Routine B then starts RoutineC and yields on it with a WaitUntilDone

    As far as I can tell, this should all work, but what I'm seeing is that in RoutineB, the yield until RoutineC is done isn't working. Execution continues past the line and I see this printed:

    "After RoutineC WaitUntilDone. Is RoutineC still running? True"

    If I rewrite the code to remove this line from RoutineA:

    yield return Timing.WaitUntilDone(routineBHandle);

    the problem goes away and everything works properly.

    Any thoughts? Thanks!
     
    Last edited: Nov 25, 2020
  22. larryPlayablStudios

    larryPlayablStudios

    Joined:
    May 15, 2018
    Posts:
    31
    @Trinary Curious if you've possibly had a chance to check out my code from the last post to see if you can also reproduce this issue. Thanks!
     
  23. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    Hi Vykx, those are some of my favorite letters :)

    There isn't a specific function for it because it's really easy to do in a simple for loop:
    Code (CSharp):
    1. for (int i = 0;i < numFramesToWait;i++)
    2.     yield return Timing.WaitForOneFrame;
     
  24. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    Sorry, it looks like I wasn't getting notifications about messages on here.

    I can certainly see how killing a coroutine while it's waiting for another coroutine to finish could mess up the waiting stack. I'll dig through the code and see if I can't handle that case better as soon as I get a moment, but until it's fixed you can do a workaround by having coroutine A check a shared boolean value and end itself if it finds that value at true instead of calling KillCoroutines on it.
     
  25. larryPlayablStudios

    larryPlayablStudios

    Joined:
    May 15, 2018
    Posts:
    31
    @Trinary Thanks for the feedback and for looking into the fix.

    Yeah, I found that a good workaround is to just run the "sub" coroutine via Timing.RunCoroutine, and then just wait on the handle's IsRunning property to become false instead of yielding directly on the Routine method.

    Would be great if this worked as expected though. Thanks!
     
  26. MrLucid72

    MrLucid72

    Joined:
    Jan 12, 2016
    Posts:
    983
    How come WaitUntilDone() seems to not actually be waiting until done when nested (both with and without a handle)?

    Code (CSharp):
    1. void Start() => Timing.RunCoroutine(_Foo());
    2.  
    3. public IEnumerator _Foo()
    4. {
    5.     Debug.Log("Start");
    6.  
    7.     CoroutineHandle handle = Timing.RunCoroutine(_Bar());
    8.     yield return Timing.WaitUntilDone(handle);
    9.    
    10.     Debug.Log("Done");
    11. }
    12.  
    13. public IEnumerator _Bar()
    14. {
    15.     for (int i = 0; i < 5; i++)
    16.     {
    17.         Debug.Log(i);
    18.         yield return Timing.WaitUntilDone(
    19.             Timing.WaitForOneFrame(_bar2()));
    20.     }
    21. }
    22.  
    23. private IEnumerator _bar2()
    24. {
    25.     yield return Timing.WaitForOneFrame;
    26. }
    27.  
    28.  
    29. // Forgive typos: This is pseudocode
    30. // #############
    31. // Start
    32. // 0
    33. // Done // << WAY TOO EARLY! After just 1 yield?
    34. // 1
    35. // 2
    36. // 3
    37. // 4
    38. // #############
     
    Last edited: Jan 18, 2021
  27. larryPlayablStudios

    larryPlayablStudios

    Joined:
    May 15, 2018
    Posts:
    31
    @MrLucid72 Can you post the actual code you're running for this test? Your example is a bit confusing because it seems to be set up in a way that wouldn't compile. Happy to help if I see a runnable test, thanks.
     
  28. MrLucid72

    MrLucid72

    Joined:
    Jan 12, 2016
    Posts:
    983
    > // Forgive typos: This is pseudocode
    Ah that was just pseudocode from memory - I'll get back to you
     
    Last edited: Feb 2, 2021
  29. replicaJunction

    replicaJunction

    Joined:
    May 28, 2017
    Posts:
    2
    Newbie here. Forgive me if this is a silly question.

    I'm trying to use MEC (just bought MEC Pro) to wait for the completion of a DoTween Sequence. Their documentation offers both standard Unity coroutines or async tasks (.NET async/await, not Unity's AsyncOperation).

    Without using MEC, I can do this:

    Code (CSharp):
    1. public IEnumerator PlayASequence()
    2. {
    3.     var sequence = DOTween.Sequence();
    4.     // ... add steps ...
    5.     yield return sequence.WaitForCompletion();
    6. }
    7.  
    8. StartCoroutine(PlayASequence());
    9. // or within another coroutine
    10. yield return PlayASequence();
    However, I can't figure out how to adapt this to MEC.

    Code (CSharp):
    1. // cannot convert from UnityEngine.YieldInstruction to System.Collections.Generic.IEnumerable<float>
    2. MEC.Timing.WaitUntilDone(sequence.WaitForCompletion());
    3.  
    4. // cannot convert from System.Threading.Tasks.Task to System.Collections.Generic.IEnumerable<float>
    5. MEC.Timing.WaitUntilDone(sequence.AsyncWaitForCompletion());
    I was eventually able to roll my own solution, but I'm not sure whether it's the best way - I hate reinventing the wheel, and I don't know if this plugin already has something like this.

    Code (CSharp):
    1. var handle = sequence.AsyncWaitForCompletion();
    2. while (!handle.IsCompleted)
    3.     yield return Timing.WaitForOneFrame;
    Is there a better way to wait for completion of either a Unity coroutine or a Task?
     
  30. Przemyslaw_Zaworski

    Przemyslaw_Zaworski

    Joined:
    Jun 9, 2017
    Posts:
    328
    Maybe useful - example: MEC with Addressable Asset System:

    Code (CSharp):
    1. // Install "Addressable Asset System" package.
    2. // Install MEC (https://assetstore.unity.com/packages/tools/animation/more-effective-coroutines-free-54975)
    3. // Mark textures as "Addressable".
    4. // Window -> Asset Management -> Addressables -> Groups -> Build -> Build Player Content
    5. // To measure memory usage in Editor, set "Window -> Asset Management -> Addressables -> Groups -> Play Mode Script -> Use Existing Build"
    6. using System.Collections.Generic;
    7. using MEC;
    8. using UnityEngine;
    9. using UnityEngine.AddressableAssets;
    10. using UnityEngine.ResourceManagement.AsyncOperations;
    11.  
    12. public class AddressableManagerTexturesMEC : MonoBehaviour
    13. {
    14.     public AssetReferenceTexture2D[] Addressables;
    15.     private Texture2D[] _Textures;
    16.     private GameObject[] _Planes;
    17.  
    18.     IEnumerator<float> LoadTexture (AssetReferenceTexture2D addressable, System.Action<Texture2D> action)
    19.     {
    20.         AsyncOperationHandle<Texture2D> handle = addressable.LoadAssetAsync();
    21.         while (handle.Status != AsyncOperationStatus.Succeeded) yield return Timing.WaitForOneFrame;
    22.         action(handle.Result);
    23.         yield return 0.0f;
    24.     }
    25.  
    26.     void ReleaseTexture (AssetReferenceTexture2D addressable)
    27.     {
    28.         addressable.ReleaseAsset();
    29.     }
    30.  
    31.     IEnumerator<float> LoadTextures(AssetReferenceTexture2D[] addressables)
    32.     {
    33.         for (int i = 0; i < addressables.Length; i++)
    34.         {
    35.             if (_Textures[i] == null)
    36.             {
    37.                 yield return Timing.WaitUntilDone (Timing.RunCoroutine (LoadTexture(addressables[i], value => _Textures[i] = value)));
    38.                 _Planes[i].GetComponent<Renderer>().material.mainTexture = _Textures[i];
    39.             }
    40.         }
    41.         yield return 0.0f;
    42.     }  
    43.  
    44.     void Start()
    45.     {
    46.         _Textures = new Texture2D[Addressables.Length];
    47.         _Planes = new GameObject[Addressables.Length];
    48.         for (int i = 0; i < Addressables.Length; i++)
    49.         {
    50.             _Planes[i] = GameObject.CreatePrimitive(PrimitiveType.Plane);
    51.             _Planes[i].GetComponent<Renderer>().material = new Material(Shader.Find("Sprites/Default"));
    52.         }
    53.     }
    54.  
    55.     void Update()
    56.     {
    57.         if (Input.GetKeyDown(KeyCode.O))
    58.         {
    59.             Timing.RunCoroutine(LoadTextures(Addressables));
    60.         }
    61.         else if (Input.GetKeyDown(KeyCode.P))
    62.         {
    63.             for (int i = 0; i < Addressables.Length; i++)
    64.             {
    65.                 if (_Textures[i] != null)
    66.                 {
    67.                     _Planes[i].GetComponent<Renderer>().material.mainTexture = null;
    68.                     _Textures[i] = null;
    69.                     ReleaseTexture(Addressables[i]);
    70.                 }
    71.             }
    72.         }
    73.     }
    74. }
     
    jeromeWork likes this.
  31. jeromeWork

    jeromeWork

    Joined:
    Sep 1, 2015
    Posts:
    429
  32. apg7

    apg7

    Joined:
    Mar 23, 2021
    Posts:
    11
    Hi all,
    I'm trying to combine the functionality of MEC and Animancer for something fairly simple, but I have some difficulties when integrating the MEC plugin. Maybe I'm missing something obvious, so please excuse me if so.
    What I want to do is to play a sequence of animations in a row. Everything seems to work ok when using Animancer with the built-in Unity Coroutines.
    Below is the code snipped where I refer to Animancer to play the actual animation:

    Code (CSharp):
    1. public AnimancerState Play(string label)
    2. {
    3.     return animancer.TryPlay(label);
    4. }
    Here is the code using the built-in coroutines and everything works OK

    Code (CSharp):
    1. IEnumerator PlayAnims()
    2. {
    3.   yield return obj.Play("idle");
    4.   yield return obj.Play("move");
    5. }
    I activate this by calling
    StartCoroutine(PlayAnims());

    Below is the non working code where I try to integrate MEC

    Code (CSharp):
    1. IEnumerator<float> PlayAnims()
    2. {
    3.   yield return obj.Play("idle");
    4.   yield return obj.Play("move");
    5. }
    I activate this by calling
    Timing.RunCoroutine(PlayAnims());

    The code above is not compiling and throwing an error Cannot implicitly convert type 'Animancer.AnimancerState' to 'float'. This occur everywhere I call yield return
    obj.Play(string animName);
    from within
    IEnumerator<float> PlayAnims()

    I understand the error, but do not know how to fix it and why it did work with the regular coroutines.

    Thank you in advance.
     
  33. NeedsLoomis

    NeedsLoomis

    Joined:
    Mar 22, 2017
    Posts:
    57
    MEC expects a float to be returned, however normal Unity coroutines expect a basic Enumerator.

    Replace

    Code (CSharp):
    1. yield return obj.Play("idle");
    with something like

    Code (CSharp):
    1. obj.Play("idle");
    2.  
    3. while(obj.IsPlaying == true)
    4.    yield return Timing.WaitForOneFrame;
     
    Last edited: May 22, 2021
  34. NeedsLoomis

    NeedsLoomis

    Joined:
    Mar 22, 2017
    Posts:
    57
    Came here to say this. It would be nice to at least have an overload for async handles, something like


    Code (CSharp):
    1. AsyncOperationHandle<GameObject> goHandle = Addressables.LoadAssetAsync<GameObject>("gameObjectKey");
    2.    
    3. yield return Timing.WaitUntilDone(goHandle);
     
    dpt2 likes this.
  35. tictacf11

    tictacf11

    Joined:
    Feb 19, 2020
    Posts:
    1
    Hi,

    Can I wait for a unity normal coroutine inside a MEC one?
     
  36. dpt2

    dpt2

    Joined:
    Jun 15, 2021
    Posts:
    50
    How can I `.CancelWith()` for DESTROY only, rather than inactive OR destroy?
    upload_2021-6-23_18-13-51.png
     
  37. Menion-Leah

    Menion-Leah

    Joined:
    Nov 5, 2014
    Posts:
    189
    Sorry for necroing that, @Trinary, but I'm not able to find anything else on this topic.

    I'm using MEC since a while and I'm super happy with that, but I just discovered it broke some functionalities I had with default Unity coroutines. Basically, every time the user is switching to another app or the screen turns off or an ad is displayed, I need for it to call a REST service to update its online availability.

    It looks like MEC routines stop executing as soon as OnApplicationPause/Focus/Quit() is triggered.

    Is there any consolidated way to avoid that freeze?
    Thanks!

    UPDATE: I purchased MEC Threaded, and it doesn't work. The threaded code doesn't run while Unity is paused.
     
    Last edited: Jul 5, 2021
    chadfranklin47 and NeedsLoomis like this.
  38. SnaiperoG

    SnaiperoG

    Joined:
    Apr 6, 2015
    Posts:
    66
    Hello. i replaced almost all my coroutines with mec and now i have this picture on my clients and server. How can i debug it and why it might happend? All spikes by Timing
    upload_2021-12-19_15-54-50.png
     
  39. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    Sorry it's been a while since I've been here. I wasn't getting notifications and I've been quite busy.

    I'll go through and make sure any questions have been answered in a moment.
     
  40. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    DoTween has a function to return a regular AsyncOperation that can actually be used by other plugins like MEC. I believe you access it using WaitForCompletion(true)
     
  41. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    Use PauseWith rather than CancelWith.
     
  42. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    I *think* you can solve it by running the coroutine in the RealtimeUpdate segement.
     
  43. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    It looks like you're creating a great deal of short lived coroutines. MEC is more efficient with memory than Unity coroutines, but fundamentally it's best if you don't use corotuines this way. Every creation of a coroutine allocates at least 20 bytes of GC alloc, and there's no way to reduce that any further than MEC has done.

    I don't know what you're doing with your code, but if you have a bunch of characters doing a bunch of tasks then try to refactor to have each character run one coroutine that takes care of all tasks rather than a coroutine for each individual task.
     
  44. SnaiperoG

    SnaiperoG

    Joined:
    Apr 6, 2015
    Posts:
    66
    I found a problem, but only after i switched back to Unity Coroutines, because now i have in profiler source of problem to see. It tells me wich function and which file cause this gc alloc which mec doesnt. So its just imposible to get where is a problem with mec.
     
  45. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    I'm glad you were able to track down the issue.

    FYI MEC can separate the individual coroutines in the debugger too. If you apply tags to your corotuines it can also name them in the debugger. You just have to turn that on since it's off by default. While Your app is running you go to the inspector and find the Timing Controller that's running in the DontDestroyOnLoad scene and change the enum/dropdown on the Timing object to show more debugging info. You can also do a similar thing in code.
     
  46. SnaiperoG

    SnaiperoG

    Joined:
    Apr 6, 2015
    Posts:
    66
    Ok got it, will try
     
  47. mikeohc

    mikeohc

    Joined:
    Jul 1, 2020
    Posts:
    215
    I'm trying to use MEC from a inherited script but I can't seem to call the base.Coroutine.
    Code (CSharp):
    1. protected override IEnumerator<float> PlayPunchMoveTween()
    2.     {
    3.         Controller.ParentRoomsToAnchor();
    4.  
    5.         yield return Timing.WaitUntilDone(base.PlayPunchMoveTween());
    6.        
    7.        
    8.     }
    I get this error
    Cannot implicitly convert type 'MEC.CoroutineHandle' to 'float'


    How would I call the base of the virtual method? Thanks!
     
  48. WildStyle69

    WildStyle69

    Joined:
    Jul 20, 2016
    Posts:
    318
    Hello there @Trinary!

    I've recently been upgrading my project to Unity 2021.2.9f and have noticed some problems with More Effective Coroutines. I have the pro version and it's not working the same any more unfortunately.

    When going into play mode in the editor the first problem was with the function `OnEditorStart` always returning false so the coroutine would not start.

    Second issue is that `yield return Timing.WaitForOneFrame;` no longer works.. it does not continue the coroutines the next frame.

    Did you check your assets on the newer versions of Unity already?

    // Will
     
  49. ExtraCat

    ExtraCat

    Joined:
    Aug 30, 2019
    Posts:
    52
    I've contacted the author via form on his website to talk about an issue with MEC, and he replied very quickly. So I suggest to use it instead of this forum to solve your issues.
     
  50. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    I'm going to guess that PlayPunchMoveTween is a coroutine function that you are trying to run. In this case you need to start the coroutine using RunCoroutine rather than just referencing it.

    Code (CSharp):
    1. yield return Timing.WaitUntilDone(Timing.RunCoroutine(base.PlayPunchMoveTween()));
    2.  
     
    mikeohc likes this.