- Home /
What's the proper way to queue and space function calls?
I do a bunch of function calls to display stuff, but I would like to be able to make it so that all of them don't display at the same time but:
That there is a spacing between subsequent displays
That I can put something in before each call (like a throbber)
What would be a good way to do this in Unity? Something with Coroutines might work, but those are fairly annoying to call with multiple parameters, right?
Update: To be clearer, how would this work for a sequence of calls such as:
chat.addMessage("Bla");
chat.addMessage("Blabla");
I don't want to have to space stuff out on this side, so keep the interface to chat as simple as it is now. How would I lay out the chat object internally so that it doesn't display both messages directly after each other but puts them in a timed-release queue?
It looks like you have asked 10 questions already but haven't accepted any answers so far. It would be nice when you could go through your questions and see if they are answered / solved and accept an answer if it answers / solves the problem.
You might want to take a look at the FAQs and the user guide.
Ok, I have accepted answers where relevant but:
I can't really see all questions that I've asked. I've gone through my activity feed but haven't found 10 questions there.
Not all of the ten questions have been answered so I can't accept those.
Answer by Bunny83 · Sep 23, 2015 at 04:36 PM
If you need a coroutine queue you can use something like this:
using UnityEngine;
using System.Collections.Generic;
public class CoroutineQueue
{
MonoBehaviour m_Owner = null;
Coroutine m_InternalCoroutine = null;
Queue<IEnumerator> actions = new Queue<IEnumerator>();
public CoroutineQueue(MonoBehaviour aCoroutineOwner)
{
m_Owner = aCoroutineOwner;
}
public void StartLoop()
{
m_InternalCoroutine = m_Owner.StartCoroutine(Process());
}
public void StopLoop()
{
m_Owner.StopCoroutine(m_InternalCoroutine);
m_InternalCoroutine = null;
}
public void EnqueueAction(IEnumerator aAction)
{
actions.Enqueue(aAction);
}
private IEnumerator Process()
{
while (true)
{
if (actions.Count > 0)
yield return m_Owner.StartCoroutine(actions.Dequeue());
else
yield return null;
}
}
}
This helper will run a coroutine which simply "idles" if no actions are in the queue. As soon as a coroutine gets enqueued it's started on the owner MonoBehaviour and once it's finished it will grab the next queued item. I deliberately wanted to keed it simple, so i did not add any waiting for each element. To wait in between two coroutines you can simply queue another coroutine which does nothing but waiting.
You could add those two methods to the class above:
public void EnqueueWait(float aWaitTime)
{
actions.Enqueue(Wait(aWaitTime));
}
private IEnumerator Wait(float aWaitTime)
{
yield return new WaitForSeconds(aWaitTime);
}
When you call EnqueueWait you simply add another item to the queue which does just wait for the specified time.
Example:
private CoroutineQueue queue;
void Start()
{
// use "this" monobehaviour as the coroutine owner
queue = new CoroutineQueue( this );
queue.StartLoop();
}
// [...]
queue.EnqueueAction(YourCoroutine(blubb, blah));
queue.EnqueueWait(2f);
queue.EnqueueAction(YourSecondCoroutine(blah, blubb));
queue.EnqueueWait(2f);
In this case when the 4 Enqueue calls are made your first coroutine will start the next frame. When it's finished the internal "Wait" coroutine will run for 2 seconds and then your second coroutine will be started. Finally another 2 sec Wait is started once the second coroutine has finised. If you enqueue something while the Wait (or any other coroutine) is still executing it will be executed once everything before that item has finished.
You can create more simple utility coroutines which you can enqueue in the order you like. You can create a coroutine that waits for some input or for an object reaching a certain point.
I've made the CoroutineHelper class on the wiki which has a different aim in mind. It doesn't support queueing but has some other useful concepts.
This is awesome. I'm using it in my project and it works perfectly out-of-the-box. Thanks so much!! :)
Hi! Sorry to bump something so old, but I just came across this while looking for a way to sequence Coroutines—it works very well out of the box, thanks for sharing!
I'm planning on using multiple CoroutineQueues in my scene to handle queueing UI transitions/animations, and I was a little concerned about the overhead of having multiple queues all spinning yield return null
every scene. I tried to change your code so that Process
would kill itself when the queue was empty, and EnqueueAction
would restart Process
if it wasn't already running. That looked like this:
public class CoroutineQueue
{
$$anonymous$$onoBehaviour m_Owner = null;
Coroutine m_InternalCoroutine = null;
Queue<IEnumerator> actions = new Queue<IEnumerator>();
public CoroutineQueue($$anonymous$$onoBehaviour aCoroutineOwner)
{
m_Owner = aCoroutineOwner;
}
void StartLoop()
{
m_InternalCoroutine = m_Owner.StartCoroutine(Process());
}
public void StopLoop()
{
m_Owner.StopCoroutine(m_InternalCoroutine);
m_InternalCoroutine = null;
}
public void EnqueueAction(IEnumerator aAction)
{
actions.Enqueue(aAction);
if (m_InternalCoroutine == null)
{
StartLoop();
}
}
private IEnumerator Process()
{
while (true)
{
if (actions.Count > 0)
yield return m_Owner.StartCoroutine(actions.Dequeue());
else
StopLoop();
yield break;
}
}
}
Unfortunately, this doesn't work: If multiple Coroutines are queued at once, it runs the first and then quits—I think I'm misunderstanding something about order of execution. Do you have any advice for getting this to work? Or should I just ignore the cost of having multiple yield return null
s going?
Thank you!
Answer by cjdev · Sep 02, 2015 at 07:19 PM
Coroutines are actually pretty easy to call with parameters and would do what you are looking for I think. Here's an example of how they work:
void Start()
{
StartCoroutine(DelayedFunction(2f, "Foo"));
}
public IEnumerator DelayedFunction(float delayTime, string param1)
{
yield return new WaitForSeconds(delayTime);
Debug.Log(param1);
}
EDIT:
A Coroutine queue system is a bit more complicated and requires your own implementation. You have to account for the delay and add to the queue manually, although it's not overly complex. Here's what it might look like:
List<Message> messages;
public Button button;
void Start()
{
messages = new List<Message>();
button.onClick.AddListener(() => AddMessage("Foo", 2f));
}
public void AddMessage(string param1, float delayTime = 2f)
{
messages.Add(new Message { param = param1, time = delayTime });
if (messages.Count == 1) StartCoroutine(NextMessage());
}
public IEnumerator NextMessage()
{
yield return new WaitForSeconds(messages[0].time);
Debug.Log(messages[0].param);
messages.RemoveAt(0);
if (messages.Count != 0) StartCoroutine(NextMessage());
}
struct Message
{
public string param;
public float time;
}
Could you see the example I added? Doing stuff slightly delayed is something I can do already.
$$anonymous$$uch appreciated! That means I need to turn my function calls into objects that can be passed around?
Also: adding two messages in quick succession looks like it will start two coroutines at the same time. So some locking might be in order?
But I guess that may be fixed with a: while (true) { if (element in Queue) { yield return StartCoroutine(queue.pop()); } } That would enable sequential processing of the queue as subcoroutines.
But after Add$$anonymous$$essage I need a button added, so I would need to turn the wrapping function in a coroutine as well if I don't want the button to appear immediately.
GameObject hello = cw.AddButton("Hoi");
Depending on a click on the button more stuff should happen in sequence, but I actually cannot yield from a listener function
hello.GetComponentInChildren<Button>().onClick.AddListener(() => {
cw.ClearButtons();
StartCoroutine(cw.AddPlayerBubble("Hoi $$anonymous$$atja!"));
Invoke("ShowHello", 0.5f);
});
So I don't really know what to do now anymore.
Answer by AltIvan · Mar 09, 2018 at 02:33 AM
The top answer is way too complicated in my opinion; it is much simple to hold a variable and only free it after each corroutine; making it work just like a queue.
public static class ExampleClass{
static bool onProgress = false;
public static IEnumerator ExampleMethod(float argument){
while (onProgress) {
yield return null;
}
onProgress = true;
// Change next couple of line for your actual yield statements (e.g an animation)
yield new WaitForSeconds(argument);
// Any further logic to may be added right here (e.g. an extra wait)
onProgress = false;
}
}
Of course you would call it like this:
StartCoroutine(ExampleClass.ExampleMethod(1f));
Of course if one of the yields throw an error/exception the next ones will never execute but you shouldn't have those in your code; you may enclose the yield statement in a try/catch block if you do; as in
try { yield new WaitForSeconds(argument); } catch {}
This is perfect! I just tested it and it executed the calls to coroutine sequentially, just as expected.
Answer by Jessespike · Sep 02, 2015 at 06:28 PM
Coroutines can do this easily. Don't use multiple parameters if you find them annoying.
I added a clarification. I didn't know you could pass functions with multiple parameters to coroutines.
So it turns out that if I turn everything into Coroutines, that I can't yield from the anonymous listener functions for my clicks.
Your answer
![](https://koobas.hobune.stream/wayback/20220612002409im_/https://answers.unity.com/themes/thub/images/avi.jpg)
Follow this Question
Related Questions
Disable sorting on a transparent queue? 0 Answers
Queue and Threading 1 Answer
is there a fast alternative to "Queue" = "Transparent" ? 0 Answers
Trying to use a derived Function with C# 1 Answer
"Shoot" function not being called 1 Answer