Search Unity

[FREE] More Effective Coroutines

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

  1. TiberiuMunteanu

    TiberiuMunteanu

    Joined:
    May 9, 2015
    Posts:
    18
    still, thanks for taking the time to answer :)
     
    Alverik likes this.
  2. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    @TiberiuMunteanu You are right that if you were already being careful about your memory allocs then MEC will only produce a little bit of a difference. MEC coroutines do allocate 40 bytes + any passed in variables vs 80 bytes + any passed in variables for Unity (at least on my computer, I'm not 100% sure it wouldn't change on a different system.)

    It is possible to pool your coroutine instances, you just have to never let the coroutine function end. Instead of ending the function you can "yield return float.NaN;" and MEC will take it out of the processing queue. You can then make another call to RunCoroutine and pass back in the handle to resume that instance. Quite awkward, IMO, but I can see you're no stranger to awkward but efficient methods so there it is.

    1. You can do it that way, but a handle won't automatically set itself to null when a coroutine finishes.
    2. The Timing class is not a singleton. You can instantiate multiple instances and treat each object like a group of coroutines. All functions have a form that works against a specific instance of the timing object for this purpose. You can then KillAllCoroutinesOnInstance to eliminate a particular group. Or you can combine that with tags for a two layered system.
    3. You can use instance IDs as tags. You can also use GameObject.name. Whatever makes sense for you. Personally, I like to tag mine by function.
    Bonus Questions: Since you have Pro I would use the CancelWith extension method and pass in a check for whichever variable keeps track of the pooling.

    @Alverik lerping, tweening, or doing any kind of transitions on integers doesn't make sense. To make a smooth transisition between "1" and "2" the number has to be able to exist as intermediate values between 1 and 2. If you want transitions between integers you need to use a float or a double and round it to an integer.

    It's better to use callbacks than to check the state periodically. In your case the camera could subscribe to an event like MenuDone, and then the menu would post that event when it was done, which would call code on the camera.

    @giraffe1 <3 Thank you so much for your support.
     
    Alverik likes this.
  3. Alverik

    Alverik

    Joined:
    Apr 15, 2016
    Posts:
    417
    Ah, the thing is that the numbers will get rather crazy over time... Think Disgaea with level 9999 and hundreds of thousands or millions of points per stat... So increasing linearly might be awkward if I want to show the stats incrementing (specially at higher levels, when you might get thousands of points in one go). But it would be cool if the increments started fast and slowed down when reaching the last numbers. About the camera, I haven't yet learned much about callbacks so I'll have to go read about it first. But fortunately the only thing I'm really good at is learning, heh.
     
  4. TiberiuMunteanu

    TiberiuMunteanu

    Joined:
    May 9, 2015
    Posts:
    18
    @Trinary Thanks for answering! I overlooked that "CancelWith".. it's just what I need.

    2 more questions now arise:

    1. Would having a separate Timing component on each object that needs coroutines be a good practice? This would allow me to use KillCoroutinesOnInstance for my desired effect. (The simpler alternative is the "CancelWith" but still..)

    2. You said that killing a coroutine does not automatically set the handle to null. I did not say that. That null check is there only for the first time i start a coroutine since my private field for the handle would be initialized to null. The question is: Does calling KillCoroutines(handle) on an already killed coroutine break something?
     
  5. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    1. It depends on your number of objects. Last I looked the Timing class uses 2kb - 3kb of system memory per instance. I think it would be more appropriate for if you had, say, three distinct types of objects. You might use an instance of Timing for each type.
    2. KillCoroutine(handle) does a search through all coroutines in the buffer, so it's not a fast function and it gets slower the more coroutines you have. That's why I suggest using tags since they are just a dictionary lookup which will always be fast no matter how many coroutines you have. I really want to deprecate the handle version but it seems like too many people are using it.

    The next thing on my todo list is to change the RunCoroutineSingleton function so that it aborts the run if there is an existing instance with that tag, rather than aborting the running coroutine like it does now. So in the next version RunCoroutineSingleton could do what you're talking about without needing to muck around with handles. I'll have to test it for memory allocs, but I think I can structure it so that it avoids the alloc if nothing runs.
     
  6. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    @Alverik It's all in the numbers. You have to adjust them to fit your situation, and you can add logic that changes the drag when the numbers get larger.
     
    Alverik likes this.
  7. TiberiuMunteanu

    TiberiuMunteanu

    Joined:
    May 9, 2015
    Posts:
    18
    Nice!

    I understand the usefullness of the tags now.

    But personally, I'm thinking about modifying the Timing class to use ints as tags instead of strings.
    This way I could have enums as tags and avoid the strings.
    I would also make the RunCoroutine function (the one with no tags) generate an int token (tag) (by incrementing or smth), add the coroutine instance to the same tags dictionary with that token as key and return the token as handle instead of the function pointer.
    That way, the KillCoroutines(handle) would work exactly as the one with tags and be as fast.
    I could just use the existing (but altered to ints) tag version, but i would have to specify (and generate) the tag myself each time.

    Would I be wrong in doing this?
     
  8. Shadeless

    Shadeless

    Joined:
    Jul 22, 2013
    Posts:
    136
    Hey, so is it possible to create the pooling solution inside MEC itself? It's obvious that we would end up using the same coroutines over and over again. And also we'd need to be able to stop coroutines with KillCoroutine and it still should work with the pooling. I'm also thinking of buying the asset and this would push me to do it.
     
  9. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    @TiberiuMunteanu You're welcome to try it. There are a couple of difficult issues with using ints that made me not go that way. I don't know how you would handle collisions between user defined and generated ints. Storing an int with every instance will increase the memory overhead (GC alloc) from instantiating all coroutines. Also, the handle is needed for the "yield return Timing.WaitUntilDone" function.

    @Shadeless Once you end a coroutine function .net locks off all access to it. If .net didn't do that the call stack could get messed up and there would be hell to pay. So pooling coroutines requires changing the way you structure your coroutine functions, which is not a constraint that I'm willing to put on most people. I put in that special flag to make it possible a while ago, but that's about as far as I'm willing to go.
     
  10. Shadeless

    Shadeless

    Joined:
    Jul 22, 2013
    Posts:
    136
    Hey, I see. Can you like make some examples of how to use the flag properly? If you yield return float.NaN at the end of the coroutine, how can you restart that same coroutine? Or how would I go about restarting the coroutine before it's done if I have a reference to it?

    And also, just to add to the ints as key thing because I asked about it as well, I've been told it's a bad idea to use structs as keys to dictionaries and that it's bad for performance on mobile.

    So the best way to use GetInstanceID is to cache the ToString from that int to a local variable, and use that for your tags.

    Cheers
     
  11. TiberiuMunteanu

    TiberiuMunteanu

    Joined:
    May 9, 2015
    Posts:
    18
    @Trinary
    - The collisions could easily be avoided by starting the incremented ones somewhat like halfway through the int range.
    - Another solution could be to keep separate dictionaries for user defined and generated tokens.
    - I trust that what you said about ints increasing memory alloc is valid.. but why would ints alloc more than strings?
    - Timing.WaitUntilDone could just as easily do a lookup and return the function pointer.

    - About what @Shadeless said about stopping and restarting the same coroutine.. my first gues would be to implement the .Reset() method on IEnumerator and reset the position back to the beginning.. but I might be wrong. If you have a bit of time, could you give us a bit more details about this topic or point us in the right direction with a link or something?

    @Shadeless
    - If you just use an enum for example as a key in the dictionary, it would allocate memory on each lookup because of the default comparer which boxes the enum to an object.. but that can easily be solved by passing a custom comparer that just compares their int values -> no allocs...
    - Otherwise I really don't see why some ints as dictionary keys could be worse in performance then some strings. If that is really the case, then I would like to know this :)
     
  12. TiberiuMunteanu

    TiberiuMunteanu

    Joined:
    May 9, 2015
    Posts:
    18
  13. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    @TiberiuMunteanu generating the hash only happens when a new element is added, it doesn't happen on access. The only performance differences that make any kind of an effective difference are the ones that happen on access (unless they are HUGE, but this isn't huge.)

    I'll think about the int thing. I'm still concerned about collisions. I wasn't saying that ints are larger than strings in memory, but tagging everything with something increases the memory profile vs the way it is now where if no tag is applied then nothing is stored.
     
  14. Shadeless

    Shadeless

    Joined:
    Jul 22, 2013
    Posts:
    136
    You're missing the point tho, I said I've been told that using structs as key has a performance impact on mobile. Not on standalone. And yeah I know about the enum thing..

    @Trinary because I've been told this I'm against using ints now, just to make it clear.

    But yeah I'd love to hear more about stoping/restarting coroutines, and using the float.NaN flag.

    Cheers
     
  15. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    @Shadeless It's not easy. You would have to do something like putting all your coroutine functions inside a while loop that never allows the function to end but instead returns nan and goes back up to the top. You have to make generous use of shared or reference type variables to control the timing of everything.

    It's messy, but it's on the roadmap for Movement/Time. Some day.
     
    PhoenixRising1 and Alverik like this.
  16. 2dgame

    2dgame

    Joined:
    Nov 24, 2014
    Posts:
    83
    Hi,
    I am trying to use the CallContinously helper function. Since I couldn't make it work on my code I decided to try out the example code from the documentation.
    Code (CSharp):
    1. private void PushOnGameObject(GameObject go, Vector3 amount)
    2. {
    3.     go.transform.position += amount * Time.deltaTime;
    4. }
    5. // This will push "myObject" forward one world unit per second for 4 seconds.
    6. Timing.CallContinuously(4f, PushOnGameObject(myObject, Vector3.forward), Segment.FixedUpdate);
    However this throws the following error:
    How can I resolve it?

    Furthermore I would like to know how I can stop the CallContinously Coroutine from running when a condition is met before the set time has passed?
     
  17. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    @2dgame Good catch. I don't know how that got in there like that. The CallContinously function takes a function with no parameters, and the PushOnGameObject function there takes two parameters. You can make it work by wrapping it in a delegate.
    Code (CSharp):
    1. Timing.CallContinuously(4f, delegate { PushOnGameObject(myObject, Vector3.forward); }, Segment.FixedUpdate);
    Watch out, though! In many cases this will create a ridiculous amount of GC alloc per frame. It's better to use a parameter-less function that reads from class variables here:
    Code (CSharp):
    1. public GameObject go;
    2. public float amount;
    3.  
    4. private void PushOnGameObject()
    5. {
    6.     go.transform.position += amount * Time.deltaTime;
    7. }
    8. // This will push "myObject" forward one world unit per second for 4 seconds.
    9. Timing.CallContinuously(4f, PushOnGameObject, Segment.FixedUpdate);
    I'll update the documentation, thanks for letting me know.

    About your second question: The easiest way is to put a check at the top of the function.
     
  18. 2dgame

    2dgame

    Joined:
    Nov 24, 2014
    Posts:
    83
    Thanks, but regarding the stopping of the continous coroutine I'm afraid you need to explain to me a little bit more.
    If I were writing a Coroutine I would stop it with a 'break;' as far as I know. But since I am continously calling an Action via the 'Timing.CallContinuously' helper function I am not sure where to stop calling the Action.

    Do I have to kill the Coroutine from within the Action? If so, how? I tried to identify the Timing.CallContinuously' helper function with a tag but failed to assign a tag to it. The documentation doesn't cover whether you can tag helper functions or not.
     
  19. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    Well.. there just isn't a system to tag helpers or kill them early. If you need to control it like a coroutine then I highly suggest you define a coroutine to do it. The Call functions were really only designed for simple use cases.

    I do get your point, though. I'll put it on my todo list to make an overload that takes a tag.
     
  20. TiberiuMunteanu

    TiberiuMunteanu

    Joined:
    May 9, 2015
    Posts:
    18
    @Trinary
    You said: "generating the hash only happens when a new element is added, it doesn't happen on access."

    This cannot be true.. Imagine adding a key "abc" to the dictionary. The hash is computed and let say it is "1ac3". So now we have a record in the dictionary ["1ac3"] : <some value>.
    Now I want to do a lookup for the key "aaa". At this point doesn't it have to get the hash for "aaa" in order to look for it in the dictionary? Even if I do a lookup for "abc"..same thing.
    I understand that the hash is used instead of the actual string in order to distribute keys more evenly.

    Take a look at these fine articles that explain this in detail:
    http://www.dotnetperls.com/iequalitycomparer
    http://www.dotnetperls.com/dictionary-stringcomparer
    http://www.dotnetperls.com/dictionary#t

    Sorry for going on and on about this, but I really like the asset and I think it could be even better.
     
  21. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    Yeah, you're right. You do have to know where you're trying to go in order to go there.
     
    Alverik likes this.
  22. TiberiuMunteanu

    TiberiuMunteanu

    Joined:
    May 9, 2015
    Posts:
    18
    Sorry, I don't understand the last phrase. What do you mean?
     
  23. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    I said you're right that the dictionary has to generate the hash again when you access an element. That's probably a large part of why accessing a dictionary element is slower than accessing an index of a list.

    I kind of like your idea of using integers instead of function pointers for handles. However, that's a far reaching structural change and I'm going to have to think about it for a while longer before committing to it. Right now I'm thinking that all integers would have to be generated by the framework and the user wouldn't be able to pass in their own custom handles.
     
    Alverik likes this.
  24. TiberiuMunteanu

    TiberiuMunteanu

    Joined:
    May 9, 2015
    Posts:
    18
  25. Shadeless

    Shadeless

    Joined:
    Jul 22, 2013
    Posts:
    136
    @Trinary hey check out this post about using structs as keys on dictionaries, it's what changed my mind. And also the performance impact on mobile.
    http://www.somasim.com/blog/2015/08/c-performance-tips-for-unity-part-2-structs-and-enums/

    Btw I figured out how you can add another Update Segment in MEC :D The Wait For End of Frame segment.
    So this just uses the Start Method as a coroutine in your Timing instance and it's only started once at the start. And with Unity removing per-frame GC allocations on their coroutines with the latest update I think it would be a nice addition.

    This is usefull because this is the absolute latest time in the execution order you can do something. After rendering.

    Code (CSharp):
    1. private WaitForEndOfFrame waitForEndOfFrame = new WaitForEndOfFrame();
    2.  
    3. IEnumerator Start()
    4. {
    5.     while (true)
    6.     {
    7.         yield return waitForEndOfFrame;
    8.  
    9.         // Wait For End Of Frame Update Segment
    10.     }
    11. }
    Cheers
     
    Alverik likes this.
  26. TiberiuMunteanu

    TiberiuMunteanu

    Joined:
    May 9, 2015
    Posts:
    18
    @Shadeless That article is exactly my base for that post in which I said that the only problem with structs is the boxing which can be avoided by custom equality comparer. I omitted the IEquatable for simplicity but, to me this became an automatic habbit when using structs, be they enums or not.
    In the article says absolutely nothing about ints beeing bad for performance on mobile...and frankly, I never found anything about that ever...and I strongly doubt that statements validity. You said that someone told you that...but I don't know..maybe you should research it a bit before believing it and most of all spreading the word about it. I'm not trying to be mean or rude..and I might be totally wrong and you totally right... If that's the case, I will give you all the credit for it.. but please give some details... That statement about ints beeing bad for performance on mobile is pretty serious and everyone should know about it if it is true. I'd hate to change my coding style based on a rumor.
     
  27. Shadeless

    Shadeless

    Joined:
    Jul 22, 2013
    Posts:
    136
    @TiberiuMunteanu this answer explains why http://stackoverflow.com/questions/14332013/monotouch-about-value-types-as-dictionary-keys

    Keep in mind that Unity doesn't use the latest version of Mono, so I think it has a similar issue to what's explained here.

    Besides that when it comes to the other thing you said:
    Not everyone will know and do this. That's why it should not be the default in the asset, to prevent people who don't know from running into issues that are tricky to figure out.

    Anyway, it's up to Trinary to decide what he want's to do and I'm just trying to point this stuff out so he has the information. That's all. If he changed it, I can always edit the code if I needed.
     
    Last edited: Aug 26, 2016
    Alverik likes this.
  28. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    @Shadeless Good looking out. I think this might become a problem if MEC went with using some kind of special coroutine handle type (like Unity does). What we were thinking of was just assigning the coroutines an integer as an id and referring to the coroutine via that integer. I think the general philosophy of using as primitive of types as possible avoids the issue that you pointed out.

    There is a "coindex" struct that is being used under the hood. I'll have to take a look and profile that struct specifically. There might already be a problem that needs to be addressed.

    About hooking into Unity's coroutines to provide a wrapper for WaitForEndOfFrame... it just seems kind of weird to me to to make my coroutine system dependent on Unity's in any way. It's better philosophically if Unity couldn't break MEC by changing its coroutine system. On the other hand it's also better (philosophically and otherwise) if MEC can access all the timing segments. Are you saying that you think it would be worth making a dependency? Do you use WaitForEndOfFrame?
     
  29. Shadeless

    Shadeless

    Joined:
    Jul 22, 2013
    Posts:
    136
    @Trinary Yeah WaitForEndOfFrame is extremely usefull because it happens right after the Rendering in terms of execution order, it's the absolute latest time you can do something based on the documentation. And I just remembered that I can use the Start function as an IEnumerator and yield WaitForEndOfFrame in it and basically I create a new update segment that I can use, in Start.

    So this gave me the idea of how MEC can also use this for another update segment. And because you manage everything in the Timing component it will be extremely cheap and we get the option to use the EndOfFrame segment.

    And I think that you could implement it in such a way that if Unity broke something you could easily remove it. I'm actually thinking of adding this to MEC myself if you don't want to, which is fine. :)

    Edit: An example of how I use it is I do a PixelSnap just for rendering in OnWillRenderObject, then I need to reset back to the original position so this needs to happen at the end of the frame so the objects position is ready for the animation/physics with the original position and no the snapped one.
    Just one example.

    Cheers
     
    Last edited: Aug 26, 2016
  30. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    Ok, I'll put EndOfFrame on the todo list. It will probably be a pro segment though.
     
    Alverik likes this.
  31. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    Well I looked, @Shadeless , but I wasn't able to find any memory allocs from the struct I'm using. I am using it as a key to a dictionary which generates a hash code but doesn't use equality operator, so that's probably why it hasn't been a problem for me.

    Performance is always tricky. Things change all the time, and a lot of the stuff doesn't work the way you expect it to. I've found that in the end I just have to test things myself. My first performance testing video was almost entirely me saying "here's something that doesn't make sense." (I deleted that one.)

    Like.. did you know that there's a memory alloc the first time you access GameObject.transform? I think unity uses GetComponenet and caches the value. That's the kind of thing you probably wouldn't know unless you test. I've seen a lot of people cache the transform themselves, which I now know is redundant. I never would have known that, though, if I didn't test for memory allocs in every way I could think of. So thanks for giving me another thing to test for. :)
     
    Alverik likes this.
  32. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    v1.9.0 came out today!

    I fixed an issue where time was being advanced whenever a new coroutine was started, and tracked down the source of a couple of edge case memory allocations. Thank you to everyone who helped with that.

    I've copied the CancelWith extension function from pro into free. This allows you to automatically make a coroutine quit when one or more gameobjects go out of scope (like Unity's coroutines do). I'll update the documentation soon, but for now you use it like this: "Timing.RunCoroutine(_MyCoroutine().CancelWith(gameObject));"

    Some pro only functions were also rewritten for efficiency. I added an option to RunCoroutineSingleton so you can pick whether it will overwrite or yield. I've also added a SwitchCoroutine function so you can change the current running corotuine's segment or tag.
     
    Alverik and PhoenixRising1 like this.
  33. Alverik

    Alverik

    Joined:
    Apr 15, 2016
    Posts:
    417
    Wow, amazing stuff! I can't wait to try them, I'll go update right now :)
     
    Trinary likes this.
  34. ilmario

    ilmario

    Joined:
    Feb 16, 2015
    Posts:
    71
    Hi, I just wanted to pop in and say I love your work on "More Effective Coroutines" (Pro) and "Movement / Time", and that I bought and use both happily.

    A semi-random question to @Trinary is: are you aware of this free multithread coroutine asset:
    https://www.assetstore.unity3d.com/en/#!/content/15717 Thread Ninja - Multithread Coroutine

    The asset seems oldish, but according to reviews it seems to work without problems even on the latest Unity releases. I was just wondering if it's possible for MEC to integrate it or offer something like that? Might lead to even bigger performance gains - or is that potentially complicating things too much and very much out of scope?
     
    Alverik likes this.
  35. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    Really nice to hear from you ilmario, it's always great hearing from users!

    Can I ask you a favor? Would you mind dropping a rating on the assets? It's been so long since anyone rated MoT that I kind of thought people might be just buying it and never using it. It would help me out a lot.

    Threads have some significant downsides that I think the Unity community in general is not familiar with. There's a whole new class of errors that you have to worry about, and I talk about that in this video. Creating a thread and switching between threads is also far more intensive than creating or switching between coroutines. They wouldn't be good for the most common use cases like moving buttons around on the screen.

    I certainly get a lot of requests about multi-threading, and it has its uses. Philosophically, a coroutine is supposed to be an alternative to threads. That said, screw philosophy. If I wrote some extensive documentation and made a few pieces of my code thread safe I could see creating a worker thread segment. In Unity I can imagine only two things that you would want to use threads for: #1 loading a bunch of objects without affecting framerate, and #2 swarming. I kind of think that a different structure than coroutines would be appropriate for those situations, perhaps a structure that allows the user to do those types of things without making them think that they should use threads to move buttons around.

    I guess you could say that I'm considering the problem. I really don't want to implement something that a lot of people might buy, but which could end up leading users down a bad path. Right now I'm working on being able to graffiti your coroutines with both tags and layers, but once I'm done with that I'll see if I can work out something for threading.
     
    ilmario, Alverik and PhoenixRising1 like this.
  36. rokel

    rokel

    Joined:
    Aug 29, 2015
    Posts:
    1
    Hello,

    Just to let you know there's a sneaky bug in the definition of CallContinuously<T> where the timeframe and period arguments are the wrong way round in the call to _CallContinuously (line 1818). I haven't checked if a similar thing is happening anywhere else.

    Thanks for making such a useful plugin free. :)
     
  37. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    Hi rokel, thanks for the report. I remember finding that particular error and fixing it about a month ago. I work in the Pro version first (because it's more complex) and I must have forgotten to transfer that fix into the free version.

    I'll be sure to transfer it over in the next version. There was only that one function affected.
     
    Last edited: Nov 5, 2016
    Alverik likes this.
  38. roboman5000

    roboman5000

    Joined:
    Sep 27, 2016
    Posts:
    19
    Hi Trinary -
    Does MEC work with nested coroutines?

    I have some functionality that worked with Unity coroutines, and when I switched over to MEC it started exhibiting odd behavior - it looks like a bunch of calls to asset.Process are being made in parallel.

    Would you be kind as to sharing thoughts on what might be happening, or if it might be something on my end?

    Example:
    Code (CSharp):
    1.  
    2. // somewhere else...
    3. Timing.RunCoroutine(LoadStuff("http://someurl.com"));
    4.  
    5. private IEnumerator<float> LoadStuff (string url) {
    6.     foreach (asset in assetList) {
    7.       Timing.RunCoroutine(asset.Download(asset.url));
    8.       while (!asset.IsDone) {
    9.          // do something...
    10.          yield return 0f;
    11.       }
    12.       asset.IsDone = false;
    13.       if (asset.IsDownloaded) {
    14.          Timing.RunCoroutine (asset.Process ());
    15.          while (!asset.IsDone) {
    16.             // do something...
    17.             yield return 0f;
    18.          }
    19.       }
    20.    }
    21. }
     
    Last edited: Dec 14, 2016
  39. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    Happy to help, but please also post asset.Download and asset.Process. Are they also coroutines?
     
  40. roboman5000

    roboman5000

    Joined:
    Sep 27, 2016
    Posts:
    19
    Yes, they are also coroutines. The basic structure for each method is below. Behavior with Unity coroutines is that it loops until it reaches a certain amount of ticks, then yields. This doesn't appear to work with MEC?

    Code (CSharp):
    1.  
    2. // elsewhere in the class
    3. Stopwatch s = new Stopwatch();
    4. long someSetTime = [some time in the future];
    5.  
    6. public IEnumerator<float> Process() {
    7.     for (int i=0; i < data.size; i++){
    8.         // do something
    9.         if (s.elapsedTicks > someSetTime)
    10.             yield return 0f;
    11.     }
    12. }
     
    Last edited: Dec 14, 2016
  41. Shadeless

    Shadeless

    Joined:
    Jul 22, 2013
    Posts:
    136
    @Trinary
    Hey so since the default behavior for the Timing instance is set to DontDestroyOnLoad, am I supposed to manually kill all coroutines when I do a scene load? Like what's the best way to get rid of all the coroutines that have ties to the scene?

    Cheers
     
  42. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    @roomera If you are using a unity coroutine with an untyped IEnumerator return type then you have to use Unity's StartCoroutine function. Timing.RunCoroutine is for running MEC coroutines only (coroutines with an IEnumerator<float> return type).

    @Shadeless Yes. Timing.KillAllCoroutines wipes the list. I was seeing too many people have issues while trying to run coroutines that changed scenes, so I decided that it would cause fewer problems to just require an explicit kill command.

    Alternatively, if you create a gameObject in your scene and add Timing to it then that gameObject won't be automatically marked DontDestroyOnLoad.
     
  43. roboman5000

    roboman5000

    Joined:
    Sep 27, 2016
    Posts:
    19
    @Trinary That was a typo on my part. The Process method does indeed return a typed Enumerator
     
  44. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    Ok, the statement "yield return 0;" yields and returns on the next frame, which is what is usually referred to as a tick. If I convert your Process function to words I get "loop through every element in data and check whether elapsed ticks is greater than someSetTime. If it is then wait for one frame and continue looking though the list." That flow doesn't seem to accomplish anything.
     
  45. roboman5000

    roboman5000

    Joined:
    Sep 27, 2016
    Posts:
    19
    What it's doing is limiting the time spent in Processing(). SomeSetTime is actually getting continuously set to be a few milliseconds after the frame update... so that once you go over that it defers processing until the next frame... But like I mentioned, it works when I'm using standard coroutines, but doesn't when I substitute with MEC.

    The basic intention here is to throttle processing so that it doesn't impact frame rates. Let me know if this doesn't make sense.
     
  46. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    Ok, I see what you are doing. It seems to me like you would need to reset s after waiting for a frame.

    There isn't really a need for the while(!asset.IsDone), instead you can use "yield return Timing.WaitUntilDone(Timing.RunCoroutine (asset.Process ()));" unless you are doing some other code while waiting.

    If your tasks entail deleting gameObjects then Unity coroutines may be aborting the running coroutines prematurely as soon as you delete the item. MEC coroutines don't abort when a gameObject is disabled or deleted unless you explicitly tell them to using the CancelWith extension method.
     
  47. 2dgame

    2dgame

    Joined:
    Nov 24, 2014
    Posts:
    83
    Back again with another noob question:
    I have a script attached to several gameobjects which starts a coroutine at a given point and and kills it before that, to let it run only once at a time on that object. It looks something like this:
    Code (CSharp):
    1. Timing.KillCoroutines("myTag");
    2. Timing.RunCoroutine(_MyCoroutine(),"myTag");
    Now what's happening is that KillCoroutines() seems to cancel the coroutines of the other gameobjects. How would I do it, so it only kills the coroutine of the object the code is called from?
     
  48. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    In MEC Free you can use the gameObject's instance ID:
    Code (CSharp):
    1. Timing.KillCoroutines(gameObject.GetInstanceID().ToString());
    2. Timing.RunCoroutine(_MyCoroutine(),gameObject.GetInstanceID().ToString());
    3.  
    4. //this is also an option:
    5.  
    6. Timing.KillCoroutines("MyTag" + gameObject.GetInstanceID().ToString());
    However, the above code will create a large memory alloc and not run as fast as it could. That's why MEC Pro allows you to graffiti a coroutine with both a string and an integer. MEC Pro also has a function that does exactly what you are trying to do:
    Code (CSharp):
    1. Timing.RunCoroutineSingleton(_MyCoroutine(), gameObject.GetInstanceID(), "MyTag");
     
    Alverik likes this.
  49. 2dgame

    2dgame

    Joined:
    Nov 24, 2014
    Posts:
    83
    That's good to hear as I am a pro user.
    However I think the RunCoroutineSingleton isn't applicable in my case since it doesn't seem to kill a coroutine if it's already running, which is what I want in case it's the own gameobject.
    I then did manage to achieve the behaviour with KillCoroutines() and RunCoroutine() with ID's and tags, so thanks!

    One suggestion: From an earlier post from you I understand that the RunCoroutineSingleton() used to kill a running coroutine before a certain patch. How about providing an optional argument to have it back this way?
     
    Last edited: Jan 12, 2017
  50. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    You pass in a true as the last parameter of RunCoroutineSingleton if you want to overwrite any existing coroutines.

    It's plural because if you have several coroutines that are tagged "myTag" it will kill all of them.
     
    Alverik likes this.