Search Unity

  1. The 2022.1 beta is now available for testing. To find out what's new, have a look at our 2022.1 beta blog post.
    Dismiss Notice

[FREE] More Effective Coroutines

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

  1. greengremline

    greengremline

    Joined:
    Sep 16, 2015
    Posts:
    183
    So as far as I could tell, the issue has something to do with running a MEC on a prefab instance that is saved in another scene. I was able to workaround the issue by instantiating the prefab each time the game loads, instead of just storing it in the scene and loading the scene
     
  2. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    384
    ok, @zornor90 ..is there any reason that you need to put it in a prefab? It works quite well when you let MEC initialize itself.
     
  3. Bamboy

    Bamboy

    Joined:
    Sep 4, 2012
    Posts:
    49
    Is it possible to manually call/evaluate a coroutine? I have a game where I am separating 99% of my game logic away from Update(), LateUpdate(), physics, exc.
    MEC relies on traditional unity update logic, which does not work for me since I might need to do multiple passes of game logic on a single render frame.
     
  4. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    384
    That's a pro feature. There is a ManualUpdate loop on pro that you can trigger yourself. Keep in mind that if you're not working on the main thread then a lot of functionality in unity will break. For mec CancelWith will only work from the main thread.
     
    Bamboy likes this.
  5. greengremline

    greengremline

    Joined:
    Sep 16, 2015
    Posts:
    183
    Oh, MEC isn't on a prefab, the thing that was calling the coroutine was - I think that it isn't correctly refreshing when the scene unloads. Probably related to unity's new prefab workflow
     
  6. Curwen

    Curwen

    Joined:
    Jan 12, 2015
    Posts:
    4
    Hello @Trinary

    I'm having an issue with MEC Pro, I'm trying to use the Editor routines for the first time and I keep running into the following error happening every frame:

    [Exception] KeyNotFoundException: The given key was not present in the dictionary.
    System.Collections.Generic.Dictionary`2[TKey,TValue].get_Item()


    Timing.OnEditorUpdate() Assets/Plugins/Trinary Software/Timing.cs:800
    798: for (coindex.i = 0; coindex.i < _lastEditorUpdateProcessSlot; coindex.i++)
    799: {
    -->800: currentCoroutine = _indexToHandle[coindex];
    802: try

    EditorApplication.Internal_CallUpdateFunctions() C:/buildslave/unity/build/Editor/Mono/EditorApplication.cs:303

    To make sure I wasn't doing something stupid in my setup, I copy pasted your demo code for the Editor routines and the same happens right away.

    If I restart the editor with the scripts in place and active, it seems to work without errors, but as soon as it recompiles the problem happens again and keeps happening after.

    Could you recommend any quick fix? I'm hesitating going with old-fashioned Update() methods for now, but it might be something simple to fix, and I like using MEC for all my continuous updates.

    Note: I'm using the latest MEC release, and Unity 2019.2.0f1

    Thank you!

    EDIT: I think I managed to fix it by turning line 737 in OnEditorUpdate() from
    Code (CSharp):
    1. if (EditorApplication.isPlayingOrWillChangePlaymode)
    to
    Code (CSharp):
    1. if (EditorApplication.isPlayingOrWillChangePlaymode || EditorApplication.isCompiling )
    because I noticed that the editor process slot variables kept getting increased with reach rebuild, so I assume it wasn't resetting those.
     
    Last edited: Oct 11, 2019
    Trinary likes this.
  7. TanselAltinel

    TanselAltinel

    Joined:
    Jan 7, 2015
    Posts:
    161
    Hi,

    I've recently stumbled upon this, and it looks much better option for me. I immediately tried the free version but it seems missing WaitUntilFalse and WaitUntilTrue functions. Are those for Pro version only?

    Regards.
     
  8. loadexfa

    loadexfa

    Joined:
    Sep 2, 2008
    Posts:
    214
    Yeah, I'm pretty sure those are MEC Pro only.
     
    Trinary likes this.
  9. loadexfa

    loadexfa

    Joined:
    Sep 2, 2008
    Posts:
    214
    It is possible to cancel Timing.WaitForSeconds? I have MEC Pro if that matters.
     
  10. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    384
    Sorry I didn't get to your question earlier, I didn't get the notification. It looks like you've already found a solution. I'll test it this weekend and see about including it in the next update.

    FYI, the best way to contact me quickly is via email. My address is in the comment block at the top of the Timing file, or there's a "contact us" link on my website http://trinary.tech
     
  11. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    384
    That's right. Free has the mininum viable feature set. Pro has a LOT of extra options. So many that I can't even remember them all most of the time.
     
  12. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    384
    In order to control a coroutine you first have to get the handle to it. That handle is returned by the RunCoroutine call when you first start the coroutine, or you can get it from within the coroutine function using Timing.Instance.currentCorutine. Once you have the handle you can kill the coroutine or pause it even if it's waiting for seconds.

    But maybe you mean that you want to resume the coroutine early in some case even though you've told it to wait. In that case then I suggest you have the coroutine yield return to another coroutine function that just waits for those seconds and then ends. That way you can kill the second coroutine function if you want to make your first resume early. You could also use pause and resume to achieve the same effect.
     
  13. Curwen

    Curwen

    Joined:
    Jan 12, 2015
    Posts:
    4
    That 'fix' gets rid of the problem after most recompiles, but it can still happen. I ended up wrapping the line in question in a try/catch and calling 'continue;' in the catch. I'm sure there's a better fix, as it still means it can happen that the collections aren't reset properly when recompiling.
     
  14. justtime

    justtime

    Joined:
    Oct 6, 2013
    Posts:
    394
    Hi there! How to replace "yield return uwr.SendWebRequest();" in this case?
     
  15. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    384
    justtime likes this.
  16. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    384
    bugfinders likes this.
  17. idurvesh

    idurvesh

    Joined:
    Jun 9, 2014
    Posts:
    495
    WIll it support ECS?
     
  18. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    384
    Fundamentally a coroutine requires a function pointer to be created. ECS doesn't allow function pointers. Coroutines are a solution that will only ever be applicable to object oriented programming. Leaving OOP behind also means leaving nice constructs like coroutines behind.

    So no, neither MEC coroutines nor Unity coroutines will ever have an ECS variant.

    However, that doesn't stop you from controlling your ECS components using coroutines.
     
  19. Nir_Tevel

    Nir_Tevel

    Joined:
    Apr 23, 2019
    Posts:
    8
    Hi,

    How could I define the time frame for Segment.ManualTimeFrame? I didn't find anything in the documentation. I would like to run the coroutine at 100 fps.

    Thanks.
     
  20. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    384
    ManualTimeframe can run slower than the framerate, but not faster. I hear that 2019.3 has a feature that allows you to run your logic and rendering at a different framerate, but prior to that release you have to run your framerate at at least 100fps in order to trigger the logic.

    Assuming your framerate is less than 100fps then I think the only way to accomplish that pre-2019.3 would be to spin up a separate thread and queue up its results before sending them to the main thread. That would get very complicated very quickly.
     
  21. Player7

    Player7

    Joined:
    Oct 21, 2015
    Posts:
    1,533
    Got a bunch of warnings like this ....
    Tried deleting the library for recompile of the dll as recently added mec pro but guess that's not the issue.
     
  22. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    384
    It looks like you've defined the class "CoroutineHandle" in your project somewhere and you haven't put it inside a namespace. If you put your definition of CoroutineHandle inside a namespace or renamed it the conflict would go away.
     
  23. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    384
    Also, just to let you know @Player7 the Plugins directory is a special directory for code that doesn't frequently change (and other things). It would speed up your compile times if you didn't put Plugins inside the K directory.
     
  24. Player7

    Player7

    Joined:
    Oct 21, 2015
    Posts:
    1,533
    aaah yeah forgot about that, that K directory is actually a symlinked from another project Asset folder.. so on that project it is /Assets/Plugins.... will have to sort that one out, though compile times are just long with Unity anyway might save a few seconds? if that?
     
  25. ratking

    ratking

    Joined:
    Feb 24, 2010
    Posts:
    278
    Just wanted to give a heads-up that this doesn't seem to work anymore? It tells me I can't change the return value because it's not a variable. And when I actually use the CoroutineHandle returned by CallDelayed and change its segment, I get a runtime error.
     
  26. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    384
    :( I wish there was something I could do about that. Ever since Unity started using the new .net compiler it has not allowed reference type setters on a single line.

    This is basically the same thing as how you now have to save the transform to a local variable, change it's values, and then set it now. The handle only needs two lines, but yeah.. it's still a lot more annoying than one.
     
  27. ratking

    ratking

    Joined:
    Feb 24, 2010
    Posts:
    278
    How about adding an overload to CallDelayed which takes the Segment as parameter? Or a specific CallDelayedRealtime() method?

    I actually don't understand. What changed?

    As I said before, this solution didn't work for me. I get a runtime error when I do that (something about a key set twice in the dictionary). [edit]Tried it again, now it works. Might have been a strange glitch.[/edit] [edit2]What I notice though is that in this case the delay is much longer than it should be. In my test case I have a delay of 3 seconds, and it feels more like 6 seconds.[/edit2]
     
    Last edited: Mar 12, 2020
  28. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    384
    You're right. I'll put it on the list for the next update.
    The new (ish) .net compiler is more restrictive. Back in Unity 5.3 the compiler would let you do that.
    The two lines would be:
    Code (CSharp):
    1. CoroutineHandle handle = Timing.CallDelayed(2f, delegate { Timing.RunCoroutine(_RunFor5Seconds(handle)); });
    2. handle.Segment = Segment.RealtimeUpdate;
    I'll have to check into the delay time, I could see that being an issue in some circumstances.
     
  29. ratking

    ratking

    Joined:
    Feb 24, 2010
    Posts:
    278
    But what with transform? I didn't understand that.

    I know. I did that. At some point it gave me errors about double keys in a Dictionary though, in my last try it didn't. Really strange.
     
  30. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    384
    @ratking I'll add that functionality in the next update, and see if I can replicate the dictionary error you mentioned. For now I suggest you copy the CallDelayedOnInstance code into a local coroutine and run that coroutine in the RealTimeUpdate segment. You'll see the best results from that.
     
  31. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    384
    I was talking about how you now have to do
    Code (CSharp):
    1. Vector3 pos = transform.position;
    2. pos.x = 0f;
    3. transform.position = pos;
    There was a time when you could do that all in one line, but those days are long gone now.
     
  32. CosmicGiant

    CosmicGiant

    Joined:
    Jul 22, 2014
    Posts:
    23
    I didn't know such a time ever existed, but you can still do it inline with the right extension method:
    Code (CSharp):
    1. transform.position = transform.position.WithX(0);
    Or straight to the transform if you wanna go that far:
    Code (CSharp):
    1. transform.SetPositionX(0);
     
    Trinary likes this.
  33. UNHO

    UNHO

    Joined:
    Apr 12, 2017
    Posts:
    2
    Unity 2018.4.5f1, More Effective Coroutines Pro v3.10.0
    Android IL2CPP build issue

    I am restarting the game by calling `LoadScene`.

    `KillCoroutines()` is called on restart.

    `Assertion failed: WaitUntilDone cannot hold, the coroutine handle that was passed in is invalid: Expired coroutine
    Assertion failure. Value was True`

    assert is raised.

    Has been modified. `while (handle.IsRunning) yield return Timing.WaitForFrame;`

    Don't wait.

    I tried logging to `RunCoroutineInternal` for debugging.


    Code (csharp):
    1.  
    2. start
    3.  
    4. CHECK [RunCoroutineInternal] Start IsRunning: False, Key: 1, Layer: , Segment: Invalid, IsValid: True, IsAliveAndPaused: False
    5. CHECK [RunCoroutineInternal] coroutine: $GameMaster+<_InitMEC>d__31, Segment: Update, layer: 0, layerHasValue: False, handle: Expired coroutine, prewarm: True
    6. CHECK [RunCoroutineInternal] Segment.Update _nextUpdateProcessSlot 0, UpdateProcesses.Length 256
    7. CHECK [RunCoroutineInternal] Segment.Update UpdateTimeValues(slot.seg) == true
    8. CHECK [RunCoroutineInternal] Segment.Update _indexToHandle.Add slot 0
    9. CHECK [RunCoroutineInternal] Segment.Update _indexToHandle.Add slot 0
    10. CHECK [RunCoroutineInternal] Segment.Update prewarm == true
    11. CHECK [RunCoroutineInternal] Segment.Update prewarm = false
    12. CHECK [RunCoroutineInternal] Segment.Update done
    13. CHECK [RunCoroutineInternal] Done IsRunning: True, Key: 1, Layer: , Segment: Update, IsValid: True, IsAliveAndPaused: False
    14.  
    Code (csharp):
    1.  
    2. restart
    3.  
    4. CHECK [RunCoroutineInternal] Start IsRunning: False, Key: 1, Layer: , Segment: Invalid, IsValid: True, IsAliveAndPaused: False
    5. CHECK [RunCoroutineInternal] coroutine: $MyCameraController+<CoSingletonBindingCheck>d__70, Segment: Update, layer: 0, layerHasValue: False, handle: Expired coroutine, prewarm: True
    6. CHECK [RunCoroutineInternal] Segment.Update _nextUpdateProcessSlot 0, UpdateProcesses.Length 256
    7. CHECK [RunCoroutineInternal] Segment.Update UpdateTimeValues(slot.seg) == true
    8. CHECK [RunCoroutineInternal] Segment.Update _indexToHandle.Add slot 0
    9. CHECK [RunCoroutineInternal] Segment.Update _indexToHandle.Add slot 0
    10. CHECK [RunCoroutineInternal] Segment.Update prewarm == true
    11. CHECK [RunCoroutineInternal] Segment.Update UpdateProcesses[slot.i].MoveNext() == false
    12. CHECK [RunCoroutineInternal] Segment.Update _indexToHandle.ContainsKey(slot) == true
    13. CHECK [RunCoroutineInternal] Segment.Update done
    14. CHECK [RunCoroutineInternal] Done IsRunning: False, Key: 1, Layer: , Segment: Invalid, IsValid: True, IsAliveAndPaused: False
    15.  
    Am I missing something? I need help.
     
  34. ratking

    ratking

    Joined:
    Feb 24, 2010
    Posts:
    278
    Ah. That only existed in JavaScript (aka UnityScript), never in C#.
    At least I never saw that in C#, with Unity 3 being my earliest Unity.
     
    jashan likes this.
  35. jashan

    jashan

    Joined:
    Mar 9, 2007
    Posts:
    3,296
    Yup, that only ever worked in UnityScript aka JavaScript (I started at Unity 2.0 ... when the Unity editor still only worked on Mac OS ;-) ). In C#, structs have always been value types and setting those directly would only set the copy that would next be running out of scope. The same issue (but not checked by the compiler) occurs when you put struct instances into arrays. That can be super-nasty because you think that you change the values in the array but the changed instances don't go back into the array unless you explicitly put them into the array. A very nasty kind of bug will result.

    One notable exception is Shuriken-scripting: There, UT did some magic under the hood that will push the changes directly into the native subsystem. So there, getting a struct instance from the particle system and changing it will have the intended effect (but it's kind of hard to trust it, tbh, but you can't set it back into the system, so after scratching your head and testing it successfully, you learn to live with it).
     
  36. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    384
    The "co" in coroutine is for cooperative so if you call KillCoroutines from within a coroutine then that coroutine will continue to run until the next yield statement. MEC has no ability to stop it from continuing. If that next yield statement happens to be a WaitUntilDone command then you'll get this warning. So it sounds like after the while loop you mentioned is a WaitUntilDone command.

    You said you do a yield return while handle.IsRunning. Try the opposite:
    Code (CSharp):
    1. if (!Timing.CurrentCoroutine.IsRunning)
    2.     yield return Timing.WaitForOneFrame;
    or you could use yield break since that will also stop the coroutine in that case.

    Probably the easiest way to avoid this issue would be to put a "yield break;" after KillCoroutines whenever you use KillCoroutines within a coroutine. If you still have more stuff you want that coroutine to do you can start a "part2" coroutine right after the KillCoroutines statement.

    I'm thinking about making KillCoroutines kill all except the currently running coroutine in the next version. EDIT: After looking into it, it looks like that change would break too much.
     
    Last edited: May 27, 2020
  37. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    384
    I don't know why I remember having to go back into old code and redoing those lines around unity 4. Maybe I just wasn't paying as close of attention as I thought I was.o_O

    I would like to do some magic under the hood in this case. It seems like some times the compiler will let you set the value and other times it won't, but in every case setting those values actually calls a function. The handle itself never gets modified. It's just a unique key in a dictionary. I haven't found the magic that will make the compiler happy though.
     
    jashan likes this.
  38. ratking

    ratking

    Joined:
    Feb 24, 2010
    Posts:
    278
    How about making Segment readonly, but adding this method to CoroutineHandle?
    Code (CSharp):
    1. public CoroutineHandle SetSegment(Segment segment) {
    2.     Timing.SetSegment(this, segment);
    3.     return this;
    4. }
     
    Trinary likes this.
  39. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    384
    I'll make that function, but I won't delete the setter because I don't want to break any existing code and it hurts no one to have more ways to do things.

    I'm looking at the code now and I can see the reason for the inconsistent delays when switching segments and also the reason for the dictionary error that you would encounter if you had a tag or a layer set on the coroutine. I'll fix both of those issues in the next version. If you would like to test the next version before the asset store approves it then send me an email (my email is at the top of the Timing.cs script).
     
    ratking likes this.
  40. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    5,377
    converting an old game made to console that suffers from hiccups, benchmarking a few replacement body parts, MEC highly recommended so today it's on the chopping block.
    benchmark done with 10000 coroutines doing 100 wait for next frame each:
    coroutines:
    upload_2020-5-26_16-52-56.png
    MEC:
    upload_2020-5-26_16-52-6.png
    startupe: MEC and coroutines seem equal
    allocation: MEC wins hands down
    during coroutines: MEC wins hands down
    end of coroutines: MEC spikes so hard that it's unusable

    Since MEC is used a lot and everybody raves about it, there could be something I'm doing wrong:
    Code (CSharp):
    1. using MEC;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class TestCoroutines : MonoBehaviour
    6. {
    7.     public int numberOfCoroutines;
    8.     public bool doMEC = false;
    9.     public int maxCalculation = 100000;
    10.     public bool doIt = false;
    11.     bool _oldDoIt;
    12.  
    13.     void Update()
    14.     {
    15.         if(_oldDoIt != doIt)
    16.         {
    17.             _oldDoIt = doIt;
    18.             for(int i = 0; i < numberOfCoroutines; i++)
    19.             {
    20.                 if(doMEC)
    21.                     Timing.RunCoroutine(_runMEC());
    22.                 else
    23.                     StartCoroutine(_RunCO());
    24.             }
    25.         }
    26.     }
    27.  
    28.  
    29.     System.Collections.IEnumerator _CheckForStuffCO()
    30.     {
    31.         int counter = 0;
    32.         while(counter < maxCalculation)
    33.         {
    34.             counter++;
    35.             yield return new WaitForEndOfFrame();
    36.         }
    37.     }
    38.  
    39.     System.Collections.IEnumerator _RunCO()
    40.     {
    41.         yield return _CheckForStuffCO();
    42.     }
    43.  
    44.  
    45.     IEnumerator<float> _CheckForStuff()
    46.     {
    47.         int counter = 0;
    48.         while(counter < maxCalculation)
    49.         {
    50.             counter++;
    51.             yield return Timing.WaitForOneFrame;
    52.         }
    53.     }
    54.  
    55.     IEnumerator<float> _runMEC()
    56.     {
    57.         yield return Timing.WaitUntilDone(Timing.RunCoroutine(_CheckForStuff()));
    58.     }
    59. }
    60.  
     
    jashan and Trinary like this.
  41. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    384
    That's an interesting test, thanks for doing it.

    1. As much as I hate to point out an error that is probably making MEC look better in your test, I feel like it's important to point this out anyway: Unity's WaitForEndOfFrame object is not equivalent to MEC's Timing.WaitForOneFrame constant. The way that you're using it is the way that a LOT of people mistakenly use it, but the equivalent statement in Unity's implementation would actually be "yield return null;".
    2. That spike at the end is probably from all the WaitUntilDone statements ending at the same time. I've had to do a fair amount of under-the-hood stuff in order to make it function reliably with the added functionality that MEC has. (with MEC you can grab the coroutine handle and have more than one coroutine waiting for the same coroutine to finish when you pass the handle to more than one coroutine, unity doesn't allow that) Obviously that added functionality has created a delay. I'll look into refactoring to remove the delay in that case.
    If I'm right about the cause then the only way to notice that spike is to have thousands of coroutines all returning from a WaitUntilDone all on the same frame. I'll verify that that's the issue as soon as I get a free moment.
     
  42. jeromeWork

    jeromeWork

    Joined:
    Sep 1, 2015
    Posts:
    384
    @laurentlavigne This is why everybody raves about MEC. :) Because people like to test its performance claims, as you have done, and because @Trinary is always so genuinely receptive to feedback and proactive in making it better. Means that people like me, who don't like to delve too deep, can just blindly rely on it and benefit from its functionality. :D
     
    Emre_U, rrahim, DwinTeimlon and 3 others like this.
  43. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    5,377
    Very cool, I'll stay tuned.
     
    jeromeWork likes this.
  44. larryPlayablStudios

    larryPlayablStudios

    Joined:
    May 15, 2018
    Posts:
    19
    Is there any first-class method for creating an "empty" yet valid CoroutineHandle.

    For example, I might have a caller routine with something like this:

    Code (CSharp):
    1. IEnumerator<float> CallerRoutine() {
    2.  
    3. yield return RoutineWrapper(TargetRoutine()).WaitUntilDone();
    4.  
    5. }
    In RoutineWrapper, depending on state I'd either like to start TargetRoutine() and return its handle or just return an empty CoroutineHandle so the caller's execution will continue. The closest I've got so far is to have the wrapper run an empty routine that immediately hits a yield break, but wondering if MEC support a first-class way of doing this. Thanks!
     
  45. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    384
    Hi Larry,

    Returning "new CoroutineHandle();" will wait for one frame like you want, BUT it will throw a warning. The good news is that you can pass "true" into WaitUntilDone to skip the warning, the bad news is that when you put ".WaitUntilDone()" at the end there's no version that allows you to pass in that true. So you're going to have to use the longer form version of the syntax to make it work correctly:
    Code (CSharp):
    1.  
    2. public bool shouldWait;
    3. //......
    4.  
    5. yield return Timing.WaitUntilDone(shouldWait ? Timing.RunCoroutine(TargetRoutine()) : new CoroutineHandle(), true);
    ^^ You'll need the explicit "RunCoroutine" in there as well.

    I'll put it on my TODO list to add that function to the fluid version of WaitUntilDone, but it might take me a while to get to it at this point (I've been pretty busy lately).
     
    Last edited: Aug 5, 2020
  46. imaginationrabbit

    imaginationrabbit

    Joined:
    Sep 23, 2013
    Posts:
    331
    Hello- longtime happy user of Pro :)

    I'm trying to track down why I randomly get this error when using WaitUntilDone

    Code (CSharp):
    1. KeyNotFoundException: The given key was not present in the dictionary.
    2. System.ThrowHelper.ThrowKeyNotFoundException () (at <96a2980540ac4006b7a04455c3e3ad39>:0)
    3. System.Collections.Generic.Dictionary`2[TKey,TValue].get_Item (TKey key) (at <96a2980540ac4006b7a04455c3e3ad39>:0)
    4. MEC.Timing.SwapToLast (MEC.CoroutineHandle firstHandle, MEC.CoroutineHandle lastHandle) (at Assets/Plugins/Trinary Software/Timing.cs:5276)
    5. MEC.Timing.SwapToLast (MEC.CoroutineHandle firstHandle, MEC.CoroutineHandle lastHandle) (at Assets/Plugins/Trinary Software/Timing.cs:5298)
    6. MEC.Timing.WaitUntilDone (MEC.CoroutineHandle otherCoroutine, System.Boolean warnOnIssue) (at Assets/Plugins/Trinary Software/Timing.cs:5172)
    7. MEC.Timing.WaitUntilDone (System.Collections.Generic.IEnumerator`1[T] newCoroutine) (at Assets/Plugins/Trinary Software/Timing.cs:5040)
    Here is what the method that calls it looks like

    Code (CSharp):
    1.  IEnumerator<float> _LoopWaiter(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).CancelWith(God.IamGod.ActiveSceneMaster._transform.gameObject));                  
    13.             }
    14.             else
    15.             {
    16.                 continue;
    17.             }
    18.         }
    19.  
    20.         yield break;
    21.     }
    Any idea or hints? Thank you for your time.
     
  47. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    384
    Is there any chance that "God.IamGod.ActiveSceneMaster._transform.gameObject" could be disabled when you run the coroutine? If it was then CancelWith would cause the coroutine to exit immediately. If that's the cause then I can make a fix for the next version.

    Also, just to make sure: You're using the latest version, right? 3.11.0?
     
  48. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    384
    Actually, looking at the code the only way I can see that exception happening is if the coroutine you are calling the WaitUntilDone function from has been killed by a call to KillCoroutine during the coroutine's execution. Do you have any code that calls KillCoroutine that could be executed by that coroutine's for loop?

    In other words, if one of your commands triggers code that kills the _LoopWaiter coroutine then that could cause this.
     
  49. imaginationrabbit

    imaginationrabbit

    Joined:
    Sep 23, 2013
    Posts:
    331
    Thank you for the reply-

    I'm not calling KillCoroutine anywhere- I am calling IsRunning = false on some connected to this method- would that have the same effects?
     
    Trinary likes this.
  50. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    384
    Yeah, that's a shortcut to calling KillCoroutine.

    One way you can fix it is to compare your handle to Timing.CurrentCoroutine. If you set the current running coroutine to not running then you should follow that with a "yield break;" MEC has no way of forcing the currently running function to quit, so it'll just keep running until the next yield statement.

    I'll look into either putting a warning in the KillCoroutines function, or checking for that situation in WaitUntilDone (or both). Thanks :)

    EDIT: Hmmm.. now that I think about it I could throw an exception if the handles matched...
     
unityunity