- Home /
Queuing functions then releasing them on command
I'm attempting to create a beat-locked system. I have an master game object using "InvokeRepeating" to send messages to "enemies" on a set BPM that trigger the "beatTrigger" function on the enemies every time it invokes. What I want to happen is when a "beatTrigger" function becomes invoked on an enemy, it executes all the functions that were accumulated in a queue during the period between the pulses dictated by the previously mentioned master game object. In other words, I want to make a function buffer.
Being relatively new to C# and Unity coming from basic C++, I have been learning constantly about cool new things such as Queues and Delegates. Based on my understanding of these, I thought I'd be able to simply store functions that are executed in between pulses as delegates, then execute them on the next pulse. However, I realize I may be missing something, such as threading. If you are so inclined, please guide me through understanding these features, or redirect me if I seem to be on the wrong track.
Here is some code illustrating an example in my real code that didn't work (made up on spot, don't mind any typos):
Queue myQ = new Queue();
delegate void Del();
Del del;
bool laserActive;
bool thrusterActive;
void BeatMessage() {
foreach (Del d in myQ)
d();
}
void WhenPlayerClicks() {
del = ActivateLaser;
myQ.Enqueue(del);
}
void WhenPlayerPressesSpace() {
del = ThrustersOn;
myQ.Enqueue(del);
}
void ActivateLaser() {
laserActive = true;
}
void ThrustersOn() {
thrusterActive = true;
}
Answer by steakpinball · Jul 09, 2014 at 07:14 AM
This can be accomplished with only a single delegate. No need for a queue. This is because delegates can be combined using the +
operator.
delegate void Del();
Del del;
bool laserActive;
bool thrusterActive;
void BeatMessage() {
if (del != null)
del();
del = null;
}
void WhenPlayerClicks() {
del += ActivateLaser;
}
void WhenPlayerPressesSpace() {
del += ThrustersOn;
}
void ActivateLaser() {
laserActive = true;
}
void ThrustersOn() {
thrusterActive = true;
}
This is because the methods are called in the order they were composed into the delegate.
Ahh, I wasn't aware you can combine delegates. This solves my problem. Thanks!
Answer by CHPedersen · Jul 09, 2014 at 06:58 AM
Well written question. :)
I took a look at your code, and it mostly looks fine to me, actually. One thing I did notice is that you need to dequeue the functions off of your buffer again when you call them. Otherwise, the reference to the function will remain, and it will simply be called again next time you call BeatMessage, and the loop iterates over the same function reference. So, maybe BeatMessage should be changed like this:
void BeatMessage()
{
while (myQ.Count > 0)
((Del)myQ.Dequeue())();
}
Now, the Dequeue pops a waiting function off of the buffer and calls it. It does so until the queue is empty. This code immediately begs another question, though: Why use an old school Queue? Those are remnants of .Net 2.0 and are, along with things like ArrayList, only present in .Net for backwards compatibility reasons. These kinds of data structures have been deprecated in favour of the faster generic versions (similar to Templates, if you've worked with those in your time with C++). Queue has a generic version that allows you to specify the type, so you can get rid of that ugly cast. Add this:
using System.Collections.Generic;
And change the Queue declaration to this:
Queue<Del> myQ = new Queue<Del>();
Now, BeatMessage can be implemented without the cast:
void BeatMessage()
{
while (myQ.Count > 0)
myQ.Dequeue()();
}
Don't try to multithread this, unless you can be absolutely sure none of the functions stored in myQ need access to Unity's API. Unity's render state is not threadsafe, and its API will throw exceptions in almost all cases (exempting things like Mathf and the Color class) if you attempt to access it from a separate thread.
Finally, just for reference, please take a look at the TaskExecutorScript I wrote a long time ago. Its code is in the answer I provided in this question:
http://answers.unity3d.com/questions/200176/multithreading-and-messaging.html
The TaskExecutorScript solves pretty much exactly the same problem you describe here (it implements a function queue), except that I implemented it to have slightly different behaviour, and for slightly different reasons. The purpose of mine is to push functions back onto the main thread when separate threads need work done that uses the Unity API. It is designed to execute the functions in the Update loop, but will not accept more than 100 functions, because it otherwise runs the risk of overloading the main thread.
I hope some of these pointers helped. In general, though, it looks like your C++ experience is serving you well, and you'll be fine. :)
Thanks for clarifying my confusion about queues. I was wondering why I wasn't able to use the Queue template version in my script, I hadn't included the generic collection library.
Your answer
Follow this Question
Related Questions
Multiple Cars not working 1 Answer
Distribute terrain in zones 3 Answers
Finding a method by custom attribute and passing to a UI button 1 Answer
How to make rhythm game long notes? 0 Answers