TeaTime: A fast queue for callbacks, to solve common coroutines patterns

Hi!

I have been experimenting, trying to improve my workflow with coroutines, looking for a fast and safe shortcut to achieve all those common patterns; locks, timers, sequences, animations, tweens, all of them mixed, etc.

And after a fun week of coding and some testing, I just finished this dark magic: TeaTime, a fast & simple queue for timed callbacks, fashioned as a MonoBehaviour extension set.

Just put ‘TeaTime.cs’ somewhere in your project and call it inside any MonoBehaviour using ‘this.tt’ (and trust the autocomplete).

TeaTime.zip (Current version: v0.7.4 beta)
GitHub - alvivar/TeaTime: TeaTime is a fast and straightforward queue for timed callbacks, specifically designed to address common coroutine patterns in Unity games.

Examples
http://github.com/alvivar/TeaTime/tree/master/Examples

Github
GitHub - alvivar/TeaTime: TeaTime is a fast and straightforward queue for timed callbacks, specifically designed to address common coroutine patterns in Unity games.

Classic example:

    // In MonoBehaviours
    TeaTime queue = this.tt().Add(1, () =>
    {
        // The queue autoplays by default
        Debug.Log("Once second later! " + Time.time);
    })
    .Loop(3, (ttHandler loop) =>
    {
        // This callback is repeated per frame during the loop duration
        transform.position =
            Vector3.Lerp(
                transform.position,
                transform.position + Random.insideUnitSphere,
                loop.deltaTime // deltaTime sincronized with the loop duration
            );
    })
    .Add(() =>
    {
        Debug.Log("The loop is done! " + Time.time);
    })
    .Repeat(); // Repeats forever!

    // And more!
    queue.Pause();
    queue.Play();
    queue.Stop();

And that’s it!

By Andrés Villalobos

2 Likes

What wizardry is this? Haven’t looked into it too much, but looks like voodoo.

Will toss it into my projects and see what happens. Thanks!

Cool! If you have any questions just drop it here and I’ll try my best.

Something is wrong with your link.
https://raw.githubusercontent.com/alvivar/TeaTime/master/TeaTime.cs

It’s the raw from Github, the browser thinks it’s a text file. I just changed it for a better one! (A zipped version).

New version: TeaTime 0.5.4!

  • Some optimizations!

  • API renamed for faster writing and cleaner chains.

  • ttAppend renamed to ttAdd.
  • ttAppendLoop renamed to ttLoop.
  • ttInvoke renamed to ttNow.
  • ttLock renamed to ttWaitForCompletion.

The current version was heavily tested during the latest Global Game Jam by me and my friend, I believe it’s stable enough for you all to try it!

I hope you enjoyed it!

NEW TeaTime 0.6!
DOWNLOAD // GITHUB

Check out Examples.cs to learn with practical patterns! (More to come)

Featuring .ttRepeat(n)

  • Repeats the current queue n times or infinite (n <= -1).

Changes

  • .ttAdd( & .ttLoop(, can’t create/change the current queue now, use .tt( instead.

Hope you like it!

Is it possible to queue things up without processing them, and then process the queue on command at a later time?

No, by design, because you can mimic that behavior with a function.

Example:

void DebugTextDelay(int delay, string text) {
    this.tt("queueName").ttAdd(delay, delegate()
    {
        Debug.Log(text);
    });
}

This way, you can contain complex queues with safety, and every time you call the function is like re-calling the queue.

Tell me if it works for you.

Unless I misunderstand, this will immediately start counting down as soon as you call the function DebugTextDelay, and then after delay, the Debug.Log(text) will execute?

This is not the same thing that I’m suggesting. I was thinking that you could add in a simple flag “enabled” or whatever, to control the processing of items in the given queue. In the above case it could be:

this.tt("queueName").Paused = true;
this.tt("queueName").Add(delay, () => Debug.Log("Hello!"));
...
//lots of wonderful stuff happening until we're ready to process the queue
...
this.tt("queueName").Paused = false;

This would be relevant when, say, users are causing a bunch of required actions that I only want to execute when they are done. So, I add each action to a queue, which gets processed when they click “done”, for example.

Hm, yes, I can see how that could be useful.

It’s now on my to-do for the next version!

It will be probably like this:

this.tt("queueName").ttStop();
this.tt("queueName").ttAdd(delay, () => Debug.Log("Hello!"));
...
// lots of wonderful stuff happening until we're ready to process the queue
...
this.tt("queueName").ttPlay();

Thanks for your suggestion!

1 Like

=> Debug.Log (“Emit garbage”)?
This looks like a very PC specific tool, given the emissions, is it?

Excellent work however, just checking the specifics

In this case “Debug.Log” is just an example, that parameter is an Action used as a callback for whatever code you want to queue on an specific time.

TeaTime is just a MonoBehaviour extension, it should work in all the platforms supporting coroutines.

To make that question clearer. Many of the things I see appear to create garbage.
Would this be appropriate for work on mobile, given this constraint.

Got it, you are right.

Right now I cannot assure you TeaTime is optimal for mobile, I have been working mostly on the algorithm, workflow and patterns, and I still have some things to solve there. But of course this kind of optimizations are on my to-do for next versions, I’ll try my best handling those delegates.

Thanks for commenting about it!

1 Like

NEW UPDATE TeaTime 0.6.5!
DOWNLOAD // GITHUB

Featuring ttPause, ttStop and ttPlay! And they work just like the word says, you can always see the changes log here.

I have this particular example showing those new features. The rest of the examples here, as usual (I’m starting to rethink the examples, maybe a tutorial, I have lots to do!)

Feel free to post your doubts here, or you can ask me personally on twitter.

Hope you like this update!

BONUS: I did made this game for Ludum Dare 32, Drybreed, play it and tell me what you think, powered by TeaTime v0.6.5 of course :smile:

New version!
TeaTime v0.7.4 beta!

There has been lots of changes since the last post here, I did a complete code rewrite, there is a cleaner API, better performance and TeaTime is now fully type-safe object-oriented. I’m so happy with this experiment!

Here are some examples / patterns! Give it a try!

    // A simple 2 seconds delay.
    TeaTime simpleDelay = this.tt().Add(2, () =>
    {
        Debug.Log("simpleDelay: Two seconds later, just once!" + Time.time);
    });


    // Something that repeats itself every 3 seconds.
    TeaTime repeatDelay = this.tt().Add(() =>
    {
        Debug.Log("repeatDelay: Every 3 seconds, repeats forever! " + Time.time);
    })
    .Add(3).Repeat();


    // A controlled frame by frame loop (update-like) with 1.5 seconds duration!
    TeaTime updateLike = this.tt().Loop(1.5f, (ttHandler loop) =>
    {
        Debug.Log("updateLike: Frame by frame during 1.5 seconds, just once! " + Time.time);
    });


    // A simple delay without autoplay.
    TeaTime somethingForLater = this.tt().Pause().Add(3, () =>
    {
        Debug.Log("somethingForLater: Someone called 'somethingForLater.Play()' 3 second ago! " + Time.time);
    });

    somethingForLater.Play();


    // A tween-like with before and after setup.
    TeaTime tweenLike = this.tt().Add(() =>
    {
        Debug.Log("tweenLike: Just before the 4 seconds loop! " + Time.time);
        transform.position = new Vector3(999, 999, 999);
    })
    .Loop(4, (ttHandler loop) =>
    {
        transform.position = Vector3.Lerp(
            transform.position,
            Vector3.zero,
            loop.deltaTime);
    })
    .Add(() =>
    {
        Debug.Log("tweenLike: Just after the 4 seconds loop! " + Time.time);
    });

More examples here!

And feel free to ask me about it!
http://twitter.com/matnesis

You know, I would LOVE to see some compatibility with something like https://www.assetstore.unity3d.com/en/#!/content/31225

Have you done any stress testing on this? I’m curious to see how many timer operations it could handle easily.

And with the new refactor, how do you think it’ll perform on mobile now?

@SirStompsalot

Probably my worst sin with TeaTime is that I’m using a generic list to store the queue. I should use an Array that automatically grows instead, because Unity doesn’t create garbage with arrays.

List<ttTask> _tasks = new List<ttTask>();

I haven’t made this change yet because it makes the code uglier, but it’s something that I’m planning to do! :frowning:

Right now, by default, a TeaTime queue is non-destructive, so, every time you define a queue, the queue will stay that way, so you can use Play(), Pause(), Stop(), Repeat() or Immutable() to control your queue without worry about garbage.

But, if you are using Consume() mode (that deletes tasks after execution, a queue without memory) or Reset() that cleans everything, this will generates garbage. This will be fixed once I migrate to arrays.

Yes, the Chronos support should be possible by creating a TeaTime global manager, something that I’m experimenting with, to improve other stuff.

Doing a decent stress testing on most platforms and make the change to arrays is on my to-do! Soon I hope! :wink: