Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

[FREE] More Effective Coroutines

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

  1. 2dgame

    2dgame

    Joined:
    Nov 24, 2014
    Posts:
    83
    So the suggestion was already implemented, thanks so much.
    Brb, need to write 5 star review.
     
    Trinary likes this.
  2. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    v2.00.0 will be out soon. Handles are back, and now the performance won't slow down when you have a whole lot of coroutines!

    Also, new video.
     
    Last edited: Jan 19, 2017
  3. ratking

    ratking

    Joined:
    Feb 24, 2010
    Posts:
    349
    I updated the Pro version to 2.0.0 and changed one instance of "Enumerator<float> myRoutine;" to "MovementEffects.CoroutineHandle myRoutine. Suddenly I get this error after a while:

    "ArgumentException: An element with the same key already exists in the dictionary."

    In the stack trace it tells me this is the offensive line:

    MovementEffects.Timing.CallDelayed(Time.deltaTime, () => onDraw += callback);

    It's only called once. I don't know how to fix this, as it worked before the update.

    [editI just noticed it also happens with a simple "RunCoroutine(UpdateCR());". In both cases only after a (second, not the same) scene load.
     
    Last edited: Jan 20, 2017
  4. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    Ok.. I'll definitely look into this as soon as I get off of work today. When you change scenes are you calling Timing.KillAllCoroutines()? If not then that could be a temporary fix.
     
  5. ratking

    ratking

    Joined:
    Feb 24, 2010
    Posts:
    349
    I actually already call KillAllCoroutines() in one of the Awake()s.
     
  6. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    Ok, I suspect that the issue may be related to that function then. I added a few dictionaries in the last update and one of them may not be being cleared properly. What happens if you comment out the KillAll command and/or replace it with Destroy(TIming.Instance.gameObject)? (maybe DestroyImmediate)
     
  7. ratking

    ratking

    Joined:
    Feb 24, 2010
    Posts:
    349
    Yes, when I remove the KillAll commands it works. (Of course I get other bugs, but I hacked around them for testing it.)
     
  8. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    Yeah, I just looked at it and there was an issue with KillAllCoroutines. I'll submit an update, but it probably won't be approved until around Wednesday of next week. For today, you can add the following two lines to the KillAllCoroutines function:

    _handleToIndex.Clear();
    _indexToHandle.Clear();
     
    ratking likes this.
  9. ratking

    ratking

    Joined:
    Feb 24, 2010
    Posts:
    349
    I updated again and it's fixed now. Thanks.
     
    Trinary likes this.
  10. Michal666

    Michal666

    Joined:
    Nov 20, 2016
    Posts:
    1
    Hi, I think coroutines in segment Segment.EndOfFrame are not working properly.

    To try just run one coroutine in Segment.EndOfFrame - nothing happens - probably _EOFPumpWatcher should run after coroutine is added to EndOfFrameProcesses array.

    Thank you
     
  11. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    @Michal666 Good catch. Go ahead and move line 1901 down to line 1922 and I'll make sure that change comes out in the next version. Thanks.
     
  12. ratking

    ratking

    Joined:
    Feb 24, 2010
    Posts:
    349
    Somehow Timing.IsRunning() doesn't return true even if the handle is not null and the coroutine still running? Is there a bug with this method?
     
  13. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    @ratking I just added that function. It isn't in the docs and I haven't written any validation tests for it yet, so I don't know. The next patch update is currently waiting in the approval queue, you should try it again after that comes out.
     
  14. Gamrek

    Gamrek

    Joined:
    Sep 28, 2010
    Posts:
    164
    @Trinary Hi, can you please show me how I run coroutine after the update?

    Do I change:

    to this:

    And just run Timing.RunCoroutine(myRoutine());?
     
  15. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    You keep that part the way it was before. The Timing.RunCoroutine function now returns an object of type CoroutineHandle, rather than the object of type IEnumerator<float> that it used to return.

    It still takes an object of the same type.
     
    Last edited: Jan 26, 2017
  16. Gamrek

    Gamrek

    Joined:
    Sep 28, 2010
    Posts:
    164
    Hi, thanks you for your quick reply. I did actually try to change it the way you said.

    And when I type RunCoroutine, it only accept function that only return IEnumerator<float> type. Do I need to reimport the plugin or something?

    I know this could be a pain for you, could you please type in here what function should be like and how I should run it? Just to make I am on the right track. Thanks.
     
  17. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    @Gamrek
    if it used to be like this:
    IEnumerator<float> handle;
    handle = Timing.RunCoroutine(_Coroutine().CancelWith(gameObject));

    it should now be like this:
    CoroutineHandle handle;
    handle = Timing.RunCoroutine(_Coroutine().CancelWith(gameObject));

    Also, if you had accidentally mistyped it like this then it will now break:
    handle = Timing.RunCoroutine(_Coroutine()).CancelWith(gameObject);

    Previously the above line wouldn't throw an error, it would just not work as expected.. which is one of the chief reasons why I changed the return type.


    The function signature does not change, it is still like this:
    IEnumerator<float> _Coroutine()
    {
    yield break;
    }
     
  18. Shadeless

    Shadeless

    Joined:
    Jul 22, 2013
    Posts:
    136
    @Trinary

    Hey so now that CoroutineHandles are a thing. Is it better to use them instead of string tags? And how is their GC handled?

    Also I noticed that the _CallContinuously implementations are private, and their public counterparts don't allow you to specify string tags. I can just set them to public in the code and use them right?

    Cheers
     
  19. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    I would suggest using the handle now whenever it's appropriate. You pay for the GC alloc from the handle every time you start a coroutine, whether you use the handle or not. On the other hand, a handle is literally an empty class with no variables, so it's as small as it can possibly be. I did some basic tests and found that the alloc generated when you start a MEC coroutine is still smaller than the alloc generated when you start a Unity coroutine.

    The handles allocate references to themselves in two dictionaries. but this only creates what looks like a GC alloc when the dictionaries need to expand (so if you end one coroutine and start another that's no alloc from these dictionaries), and the GC will only actually need to clean it up if you KillAllCoroutines or delete the MovementEffects game object.

    You can make those functions public if you like. One of my design principals is to never make a coroutine function public, but always wrap them in a public function that calls RunCoroutine. That way you can't get sloppy and accidentally try to call the function without wrapping it in Run. So you'll have the set them back to public every time you update. CallContinously now returns a coroutine handle, so you can set the tag using SetTag on the handle (in the pro version at least). That's my plan for addressing that use case.
     
    twobob and Shadeless like this.
  20. ratking

    ratking

    Joined:
    Feb 24, 2010
    Posts:
    349
    Is this a known problem? I finally upgraded from 5.4 to 5.5.1 and often (not always) when I change scene I get the error "Non matching Profiler.EndSample (BeginSample and EndSample count must match)", which is kinda annoying.
     
  21. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    That's interesting. Are you calling KillAllCoroutines from within a coroutine?

    EDIT: Actually, I don't think that would cause that error. The only way I can see that error happining is if Timing.Instance.ProfilerDebugAmount was changed while inside a coroutine function.
     
    Last edited: Feb 9, 2017
  22. ratking

    ratking

    Joined:
    Feb 24, 2010
    Posts:
    349
    Hmm, I will investigate further. I actually call MovementEffects.Timing.KillAllCoroutines(); inside a Coroutine, but I'll have to check if this is causing it.
     
    Trinary likes this.
  23. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    Ok, well in any case if you set the profiler debug amount to none then that won't happen.
     
  24. ratking

    ratking

    Joined:
    Feb 24, 2010
    Posts:
    349
    Okay, I'll do that. If you're interested, the last Profiler.EndSample(); before the error appears is the second one in TriggerManualTimeframeUpdate() it seems.
    EDIT: Hm, I guess MoreEffectiveCoroutines is not to blame. I put "Timing.Instance.ProfilerDebugAmount = Timing.DebugInfoType.None;" in my code and the error still appears. Sorry for the false alarm.
     
    Last edited: Feb 12, 2017
    Trinary likes this.
  25. TiberiuMunteanu

    TiberiuMunteanu

    Joined:
    May 9, 2015
    Posts:
    18
    Hi,

    I updated to 2.02 and every now and then I get a crash on Timing.KillCoroutines(instanceId, tag) saying HashSet have been modified while it was iterating over.
     
  26. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    I just submitted the fix for that.. and Unity was good enough to bump the patch release to the top of the queue, so now 2.02.1 is out which fixes your issue.
     
    Last edited: Feb 15, 2017
    Alverik likes this.
  27. Lordinarius

    Lordinarius

    Joined:
    Sep 3, 2012
    Posts:
    94
    Hello,

    I am using MEC on my project almost everywhere. I have an issue. On iOS. i am having JIT exception when i Build on Mono2X scripting backend. Here full Xcode log.

    Code (CSharp):
    1. ExecutionEngineException: Attempting to JIT compile method 'System.Collections.Generic.GenericEqualityComparer`1<MovementEffects.Timing/ProcessIndex>:.ctor ()' while running with --aot-only.
    2.  
    3.   at System.Reflection.MonoCMethod.Invoke (System.Object obj, BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x00000] in <filename unknown>:0
    4. Rethrow as TargetInvocationException: Exception has been thrown by the target of an invocation.
    5.   at System.Reflection.MonoCMethod.Invoke (System.Object obj, BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x00000] in <filename unknown>:0
    6.   at System.Reflection.MonoCMethod.Invoke (BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x00000] in <filename unknown>:0
    7.   at System.Reflection.ConstructorInfo.Invoke (System.Object[] parameters) [0x00000] in <filename unknown>:0
    8.   at System.Activator.CreateInstance (System.Type type, Boolean nonPublic) [0x00000] in <filename unknown>:0
    9.   at System.Activator.CreateInstance (System.Type type) [0x00000] in <filename unknown>:0
    10.   at System.Collections.Generic.EqualityComparer`1[MovementEffects.Timing+ProcessIndex]..cctor () [0x00000] in <filename unknown>:0
    11. Rethrow as TypeInitializationException: An exception was thrown by the type initializer for System.Collections.Generic.EqualityComparer`1
    12.   at System.Collections.Generic.Dictionary`2[MovementEffects.Timing+ProcessIndex,MovementEffects.CoroutineHandle].Init (Int32 capacity, IEqualityComparer`1 hcp) [0x00000] in <filename unknown>:0
    13.   at System.Collections.Generic.Dictionary`2[MovementEffects.Timing+ProcessIndex,MovementEffects.CoroutineHandle]..ctor () [0x00000] in <filename unknown>:0
    14.   at MovementEffects.Timing..ctor () [0x00000] in <filename unknown>:0
    15. UnityEngine.GameObject:Internal_AddComponentWithType(Type)
    16. UnityEngine.GameObject:AddComponent(Type)
    17. UnityEngine.GameObject:AddComponent()
    18. MovementEffects.Timing:get_Instance()
    19. MovementEffects.Timing:RunCoroutine(IEnumerator`1, Segment)
    20. SimpleTrafficVehicle:OnReset()
    21. AIController:OnEnable()
     
    Last edited: Mar 29, 2017
  28. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    Based on that stack trace, it appears that you are accessing the Timing object and/or creating a coroutine inside an initialization function that is running before the Unity environment is ready. MEC creates an object in your scene that it can live on the first time you access it, and if the scene is not ready yet then it will create an error like this.

    It looks like the function that is creating a coroutine too early is AIController.OnEnable()
     
  29. Lordinarius

    Lordinarius

    Joined:
    Sep 3, 2012
    Posts:
    94
    Hey, thanks for the enlightenment. But i want to add some other details. Actually i am calling that at least 0.2 sec after scene fully loaded. I am making some async operation like loading asset async so that time may increase

    If this is not enough for you. I made another test on an empty scene that i just gave Timing component on a gameObject. It is giving same ExecutionEngineException with some different stack trace (Because at this time Timing component already exist). I would like to draw your attention here because this is happening when i only use Mono2X scripting backend for iOS there is not any problem if i build on IL2CPP backend (I am using Mono2X backend because it is faster to build to make quick check and review on device)

    It seems there is nothing wrong with MEC. It is caused by backend compatibilty. But the question is why only on iOS, there is not any problem on Anrdroid

    i've found that.
    http://forum.photonengine.com/discu...ineexception-attempting-to-jit-compile-method
     
    Last edited: Mar 30, 2017
  30. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    Ok, well that was the easy possibility. The next possibility is that it has to do with your stripping level. I'll look into it this weekend.
     
  31. jashan

    jashan

    Joined:
    Mar 9, 2007
    Posts:
    3,307
    Just a quick question (I just got the Pro version): When I'm using WaitForSecondsRealtime most of the time, I should probably use Segment.RealtimeUpdate, right? I'm just converting things right now and haven't gone into the depths of MEC ... but it sure does look awesome and just what I needed ;-)
     
  32. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    That's right. MEC does that part sightly differently than Unity's default coroutines. With Unity's default you select which segment the following lines will be run in each time you yield return. MEC, however, runs your coroutines in a particular segment and you "yield return" the same way no matter which segment you are using. So to WaitForSecondsRealtime you would run the coroutine in the RealtimeUpdate segment and do a regular Timing.WaitForSeconds.

    FYI, SlowUpdate is also a realtime segment.
     
    Alverik likes this.
  33. jashan

    jashan

    Joined:
    Mar 9, 2007
    Posts:
    3,307
    Ok, cool, that helps, thank you!

    EDIT: I saw RunCoroutineSingleton just now ... oh well ;-)

    I believe I just noticed another difference compared to how Unity Coroutines work: In Unity, when you start a Coroutine and then the object gets disabled, the Coroutine immediately stops. For that reason, I had moved starting a lot of Coroutines that need to run whenever an object is active into OnEnable (instead of Awake or Start). That way, I can be sure the Coroutines always run when the object is active, and Unity makes sure they don't run otherwise ... and also, that I'm not getting them running multiple times.

    It seems that MEC keeps them running. Is that correct?

    If so, that's a fairly tricky thing when moving over to MEC. Not an issue that cannot be solved (I guess I can simply tag the coroutines and simply always kill them before starting them - unless I want more than one to run). But definitely something one needs to be aware of.
     
    Last edited: Apr 6, 2017
  34. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    By default MEC avoids checking the state of the object every frame to see if it's still enabled (like Unity's coroutines do). You normally only want to do that with UI elements, so it seems rather wasteful to have it turned on for every coroutine all the time. There's a built in function, CancelWith, which turns on that check.
     
  35. Izina

    Izina

    Joined:
    Nov 26, 2013
    Posts:
    15
    Hey Trinary,

    Thanks for your work, it's a great tool :)
    I have a question about the CallPeriodically method. I see how to use it with anonymous delegates as actions but when I try to use a named delegate, I can't find what my delegate needs to return as Action and if I return null, the action is not called. It's more a problem about delegate knowledge but I can't find any example about it :/

    Thanks for your help,
     
  36. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    Glad you like it Izina,

    The function should have a return type of void, so you don't return anything. "return null;" is a statement that returns a value (of type object) that happens to be "null." That's not what you want. You want to change the function +to something like this:
    Code (CSharp):
    1. private void Foo()
    2. {
    3.     Debug.Log("Hello World!");
    4. }
    You can then pass it in like this
    Code (CSharp):
    1. Timing.CallPeriodically(1f, 5f, Foo);
     
    Izina likes this.
  37. Izina

    Izina

    Joined:
    Nov 26, 2013
    Posts:
    15
    Thanks for answering so quickly.

    I was trying to use a delegate like this :

    Code (CSharp):
    1. public event CallOnEachCooldownTickDelegate OnEachCountdownTickEvent;
    2. private CallOnEachCooldownTickDelegate OnEachCountdownTickDelegate;
    3.  
    4. public void Awake() {
    5.         OnEachCountdownTickDelegate = OnCooldownTick;
    6.     }
    7.  
    8. private void Start() {
    9.         Timing.CallPeriodically(countdown + 1, 1f, OnEachCountdownTickDelegate, CountdownEnded );
    10.     }
    11.  
    12. private void OnCooldownTick() {
    13.         --_countdown;
    14.     }
    If I define the method directly like you said, it compiles.

    edit : I found a way to use events for those interested. I'm sure that's basic stuff for most of you guys but since I didn't found it before, hope it can help someone :)

    Code (CSharp):
    1. public event Action OnCountdownTickEvent;
    2.  
    3. public void Awake() {
    4.         OnCountdownTickEvent = OnCooldownTick;
    5.     }
    6.  
    7. private void Start() {
    8.         Timing.CallPeriodically(countdown + 1, 1f, OnCountdownTickEvent, OnCountdownEndedEvent);
    9.     }
    10.  
    11. private void OnCooldownTick() {
    12.         _countdown--;
    13.     }
    Thanks a lot !
     
    Last edited: Apr 6, 2017
    Trinary likes this.
  38. jashan

    jashan

    Joined:
    Mar 9, 2007
    Posts:
    3,307
    I'm getting a strange effect with MEC. Here's the code causing this:

    Code (csharp):
    1. Timing.RunCoroutine(ReturnToPoolAfter(clip.length), Segment.RealtimeUpdate);
    2.  
    3.         private IEnumerator<float> ReturnToPoolAfter(float seconds) {
    4.             float startTime = Time.realtimeSinceStartup;
    5.             yield return Timing.WaitForSeconds(seconds);
    6.             myPool.PushToPool(this);
    7.             Debug.LogFormat("ReturnToPoolAfter(seconds={2:0.000}): Audio source {0} return to pool after {1:0.000} seconds.",
    8.                 this.name, Time.realtimeSinceStartup - startTime, seconds);
    9.         }
    If I'm not missing something, this should give me logging statements where the value in "(seconds={0:0.000}" is always equal are smaller than "after {1:0.000}".

    What I'm getting, however, is this:

    Code (csharp):
    1. ReturnToPoolAfter(seconds=0.047): Audio source PooledAudioSourceHitsound (24) return to pool after 0.026 seconds.
    And not just one time, but consistently. The symptom in the game is that audio gets cut off.

    Am I doing something wrong or is this a bug in MEC?
     
  39. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    @jashan The code that you've posted doesn't look wrong. Are you setting Unity's timescale to 0? If you are, can you please try it with the timescale set to something like 0.0001f instead?
     
  40. jashan

    jashan

    Joined:
    Mar 9, 2007
    Posts:
    3,307
    Nope, this is in ordinary gameplay, with timescale = 1F. I do have a pause mode in the game where this code is also used; haven't tried that, yet, but I do scale time down to a very low value instead of 0 (might even be 0.001F).

    Tbh, I haven't looked at your code, yet, but when using Segment.RealtimeUpdate, I'd rather have everything be based directly on Time.realtimeSinceStartup without any multiplications or divisions. Would that be feasible? floats can be quite the beast with precision and this is a rhythm game, timing needs to be very precise.
     
  41. jashan

    jashan

    Joined:
    Mar 9, 2007
    Posts:
    3,307
    And that did the trick. Basically, I simply got completely rid of _lastRealtimeUpdateTime and replaced it with Time.realtimeSinceStartup - and I'm no longer getting premature terminations of WaitForSeconds. Now, as expected, it's always a little later than the time waiting.

    I don't know if this may have any unintended side-effects but so far, things look good.
     
  42. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    well, MEC is using Time.unscaledDeltaTime rather than realtimeSinceStartup to count the time each frame. There are a couple of reasons for that, the main one is that Unity's time will get less percise the longer the app remains running. Adding ticks together with deltaTime allows the time counter to be reset to 0 and remove those errors.

    If there's a bug in unscaledDeltaTime then I can stop using it for time calculations. Having an app that can run for a few days at a time doesn't appear to be on very many people's wish list.
     
  43. jashan

    jashan

    Joined:
    Mar 9, 2007
    Posts:
    3,307
    Unfortunately, I didn't have the time to do some in-depth testing. But it does seem that this occurs significantly more likely with very short wait times (less than 0.1s) and possibly also with many Coroutines running in the same Segment simultaneously. I have one section in the game with several Coroutines running for about 0.25s, one at a time, and in those sections, while I did see one case where it continued after 0.101s, that was very rare.

    In the other section of the game, where I ran into this, there are several Coroutines running at the same time (all in the RealtimeUpdate segment), and one of those has a looped wait time of 0.006s, which is below framerate (0.011s). The reason I switched to Time.realtimeSinceStartup the way I did was because I assumed that maybe the issue is some sort of interaction between the different Coroutines or something like that, messing with time.

    The first thing I tried was replacing += unscaledDeltaTime with keeping _lastRealtimeUpdateTimeStartTime that I set to Time.realTimeSinceStartup in the reset method that usually sets _lastRealtimeUpdateTime to 0. This did seem to improve the issue a bit but not quite fix it. I would have needed to do more thorough testing to be sure there actually was an improvement, though.

    My guess was that the issue arises from WaitForSeconds() maybe having a slightly wrong LocalTime to start with, and that error adding up in unexpected ways when dealing with very short wait times.

    Actually, for multiplayer servers, I can see how that would be really useful/important. But then, you might rather want to offer something based on .NET time, if Unity's time isn't reliable. I actually have a case where I need to be able to wait for network synced time to let certain things start synchronously across different machines. But that's not something I would expect a package to deliver (unless it's a package for very specific multiplayer needs ;-) ).
     
  44. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    Ok, I did some testing on it and it's possible that Unity is losing quite a bit of precision on unscaledDeltaTime, and that could be causing rounding errors.

    If you want to use time since startup you shouldn't change a bunch of places. The one line you would want to change is ln 2566 from _lastRealtimeUpdateTime += deltaTime; to _lastRealtimeUpdateTime = Time.realtimeSinceStartup;
     
    Last edited: Apr 17, 2017
    Alverik likes this.
  45. jashan

    jashan

    Joined:
    Mar 9, 2007
    Posts:
    3,307
    No. I just did that and the issue returned. Reverted the change to not use _lastRealtimeUpdateTime at all and always directly use Time.realtimeSinceStartup, and it works correctly again.

    Also, I believe there are actually two places you need to change: The one in 2566 but also in ResetTimeCountOnInstance(), _lastRealtimeUpdateTime needs to be set to Time.realtimeSinceStartup instead of 0d (as far as I understand purpose of that code, I might be wrong).

    So I guess this confirms what I kind of suspected: Something apparently is wrong with only setting _lastRealtimeUpdateTime in UpdateTimeValues(...). Because for some odd reason, the check against time otherwise sometimes fails.

    And it may actually not be that odd: Maybe the issue is about the timing of the call to WaitForSeconds. In my code, regardless of when this is called, it will always use Time.realtimeSinceStartup. In the version using _lastRealtimeUpdateTime, it may use a cached value which is older than the current time, which would result in the waiting time being too short which is exactly the behavior that I see.

    Maybe I found the problem: There is UpdateTimeValues and SetTimeValues. In my code, SetTimeValues will put Time.realtimeSinceStartup into localTime; in the code you suggested, SetTimeValues will use _lastRealtimeUpdateTime, which was assigned in UpdateTimeValues. If time passes between the last call to UpdateTimeValues and RunCoroutineInternal, which apparently is the case, WaitTimeForSeconds uses an outdated base time to add the waiting time to and consequently returns early.

    So, if that's true, there are at least 3 places where you need to change it. The only other use of _lastRealtimeUpdateTime that I found was in GetSegmentTime. Might as well change it there as well to be safe.

    The only case I am aware of where you'd want something else is when running in FixedUpdate because FixedUpdate will often get several calls which have the same Time.realtimeSinceStartup. But for the simulation, they will use a different time on each call, namely time used in previous call + Time.fixedDeltaTime. Not sure if this is relevant to MEC, though, because Segment.RealtimeUpdate is run in Update and not FixedUpdate ... but I thought I'd mention it because I once pulled my hair out because I wasn't aware of this.
     
  46. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    ResetTimeCount doesn't matter for your purposes, it isn't called internally.

    It sounds like you're experiencing a race condition. I'll see what I can do to fix it without introducing other bugs that I previously solved. Thank you for the detailed report.
     
  47. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    @jashan The easy way to solve your issue would be to go to Project Settings/Script Execution Order and set MovementEffects.Timing to be earlier than the other scripts. I'm working on a better solution, but that should work for now.
     
  48. jashan

    jashan

    Joined:
    Mar 9, 2007
    Posts:
    3,307
    Ah, ok, that makes sense. Thank you for the hint.
     
  49. imaginationrabbit

    imaginationrabbit

    Joined:
    Sep 23, 2013
    Posts:
    349
    I just purchased your threading asset along with Mec pro and I can't seem to find any docs/examples for the threading beyond whats in the threading script itself- is there any other information available?

    Is this the correct usage if I want to run a Mec coroutine- execute a function on another thread and return to the mec coroutine and repeat it? Thanks.

    Code (CSharp):
    1. void Start()
    2.     {
    3.         Timing.RunCoroutine(MyCor());
    4.     }
    5.  
    6.     IEnumerator<float> MyCor()
    7.     {
    8.         yield return Timing.WaitForSeconds(2f);
    9.         pos1 = transform.position;
    10.         pos2 = target.transform.position;
    11.         yield return Threading.SwitchToExternalThread();
    12.         GetDist();
    13.         yield return Threading.SwitchBackToGUIThread;
    14.  
    15.         if (distance > 20)
    16.         {
    17.             Debug.Log("I'M SO FAR");
    18.         }
    19.         Timing.RunCoroutine(MyCor());
    20.     }
    21.  
    22.     public void GetDist()
    23.     {
    24.         distance = Vector3.Distance(pos1, pos2);
    25.     }
     
    Last edited: Apr 25, 2017
  50. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    @mdotstrange I'm sorry about that. I was meaning to put up some documentation last weekend, but I got busy with other things. I'll plan to be able to do it this weekend.

    About your example: You shouldn't set shared variables from other threads. distance should be a local variable. If you want to use a separate function like you have there then you can have the function return a float value. After the call to SwithBackToGUIThread you can set the shared distance variable to the value of the local variable.
     
    imaginationrabbit likes this.