Do triggers get executed in Galaxy sequentially in order of their events' arrival, or are they performed in parallel?
As much as I'd like them to run sequentially (and it is logical for an event based system that keeps track of state), in light of more recent discoveries and experiences with Galaxy I'm afraid the answer isn't obvious.
What do you mean by registered?
Lets say I've registered a trigger T on a Roach being hit. Lets say I have two roaches, R1 and R2.
Assuming R1 was hit before R2 (or any linearization), will Galaxy process T(R1) first and T(R2) afterwards, or will it process them in parallel (thus assuming no shared state)?
Everything that happens in Galaxy happens sequentially. However, if any code in that trigger causes another trigger to fire, the current trigger will be interrupted at that point and the new trigger will begin executing. It will continue until it either finishes or hits a Wait, at which point it will yield to the previous trigger's thread.
In addition, if you have the same event registered to multiple triggers, they will be served in order of registration as s3rius mentioned.
Everything that happens in Galaxy happens sequentially. However, if any code in that trigger causes another trigger to fire, the current trigger will be interrupted at that point and the new trigger will begin executing.
And if another event is triggered while the script is already running through a trigger? Will the current trigger be interrupted or will it be allowed to finish before the new event is processed?
And if another event is triggered while the script is already running through a trigger? Will the current trigger be interrupted or will it be allowed to finish before the new event is processed?
From my understanding, there is no such thing as triggering an event, while the script is already running another trigger. The script will finish the trigger, before even being able to process a new event (which does not mean, that the event is not triggered, its just that all script is already finished, when the event can be run). If the game cannot keep this up, it will result in the game lagging and waiting for the script to finish before being able to process another event.
Exceptions being wait (but that means the trigger is already interrupted) and triggering an event by actions of the trigger (like said earlier, this will halt the trigger and finish the newly executed one first).
Kakuen, that's incorrect. MTops, every time anything happens which would cause a new event to fire, it pushes the current thread into a stack where it lies dormant until the new thread is complete or hits a wait, at which point the previous thread is popped from the stack, etc., until no current threads remain. It works the same way as calling a function from within a function from within a function... etc.
To complicate things, there is an exception. Due to some GUI-related limitations that go back to WC3, Blizzard specifically makes unit creation events non-interrupting, and places the event at the end of the thread stack instead. However that is the only non-interrupting trigger event that I know of, and it may in fact be the only one.
So if I create two time-interval based triggers and set one to fire every 0.5 seconds and the other at every 2.0 seconds, it is possible one of the two is triggered while the other is running, causing it to interrupt the execution.
I guess I have to build a manual mutex to prevent unwanted data sharing...
Kakuen, that's incorrect. MTops, every time anything happens which would cause a new event to fire, it pushes the current thread into a stack where it lies dormant until the new thread is complete or hits a wait, at which point the previous thread is popped from the stack, etc., until no current threads remain. It works the same way as calling a function from within a function from within a function... etc.
Can you give a code example of this happening?
Because my Starcraft always finishes the current thread (assuming no waits) before it starts execution of a new one.
I'm not sure if this backs up your statement, but I was messing around with some multi-threading operations awhile back and was using some global variables within my trigger. I was originally under the impression that I needed a mutex to protect my shared globals, but to my surprise, not having a mutex didn't mess up the code execution as I expected it to.
Basically I had a trigger, that could be interrupted by another instance of itself. If each trigger creates one thread whenever it fires, and a second instance fires while the first is still running, I should find that the second instance is modifying the shared global I was using, but it didn't. In fact, it seemed to complete the execution of the first instance before calling the second.
So I do lean toward belief that unless there is a wait within the trigger, the thread runs til its finish before allowing the next thread in the stack to execute.
MTops, your example is flawed because time does not elapse while script is executing.
OK, I went ahead and did some conclusive testing on this subject so we can all stop speculating. I will admit that I assumed it worked the same way as in WC3, because that is the most sensible and correct way to handle events. Instead, SC2 does something shitty and places new events at the end of the trigger queue.
Everything else mentioned in the thread is correct. Triggers are first-come-first-serve, and they will yield to other triggers in the queue as soon as they hit a wait.
I was a wc3 mapper back then and I can't remember it being any different from how things go in SC2.
Anyway, you don't want concurrent threads. You want serialized threads. It's a trigger-action system and not a game engine in itself.
The way that threads worked in WC3 was very well known, and they worked as I just described. It was an excellent design. The way they work now will cause a large number of problems for non-GUI users creating maps of sufficient complexity. (Example: being forced to use Wait 0 when you want the side-effects of an action to be processed before continuing). The only way to deal with this is to use native wrappers that force side-effects to be processed immediately, and to avoid relying on triggers for certain things. Once code sharing gets thrown into the mix, a compiler that hooks natives will be the only way to deal with it that doesn't require people to go around massively editing libraries that they import.
Also, there has never been such a thing as concurrency in WC3 or SC2.
Hm.. Just wanna get this clear. If the buffs aren't added immediately, are they added when the trigger execution completes? I'm imagining you have something like this:
triggerstartaddbufftriggerend
If this is the case, wouldn't the delay for the buff to be added be rather minuscule if there was a short execution time between the add buff function and the end of the trigger? I believe I'm failing to see why its a problem to have queued events here.. although theory-wise i do agree that it is bad. Just need a better example of how it's bad thats all :/
Why do people continue to believe that any amount of time at all passes while code is executing? The amount of time that passes is something on the scale of nanoseconds, which far too miniscule to affect anything. Furthermore, the game does not simulate the progression of time until it is done with all current threads.
The actual problem is that, if you have a trigger that's supposed to execute when a unit receives a buff, and the code in the part of the original trigger after the buff is applied needs that buff application trigger to have already run, you will need to use a Wait(0) to allow the queued trigger to actually occur.
Another problematic example is a system that indexes units when they enter the map. Due to the fact that the unit creation trigger does not run instantly, anytime you create a unit, it would not have an index assigned until after a wait. This forces the index retrieval function to do a check for whether the unit has an index or not, rather than just returning the unit's custom value without any safety checks.
In general, triggers would be more useful if they applied side effects instantly, whereas this arrangement will make you have to worry about whether or not side effects have actually been processed yet.
Herp derp, do I feel like an idiot right now. The fact that no game time progresses while script code is running is nothing new to me. If the script is stuck in an infinite while loop, you can see the game halting for a few seconds before an engine fail-safe kicks in that kills the trigger.
So why I ever thought it would be possible for a trigger to be fired while another one was running, I have no idea. Thanks for the smack on the head, grim!
The good news is, this means no code will ever run in parallel in my project (I never use waits), so I can split the different agents into different interval based triggers to improve performance and improve AI reaction time.
Rollback Post to RevisionRollBack
To post a comment, please login or register a new account.
Do triggers get executed in Galaxy sequentially in order of their events' arrival, or are they performed in parallel? As much as I'd like them to run sequentially (and it is logical for an event based system that keeps track of state), in light of more recent discoveries and experiences with Galaxy I'm afraid the answer isn't obvious.
Triggers are executed sequentially, in order of event registeration.
First registered, first served.
What do you mean by registered? Lets say I've registered a trigger T on a Roach being hit. Lets say I have two roaches, R1 and R2. Assuming R1 was hit before R2 (or any linearization), will Galaxy process T(R1) first and T(R2) afterwards, or will it process them in parallel (thus assuming no shared state)?
@moshewe: Go
Everything that happens in Galaxy happens sequentially. However, if any code in that trigger causes another trigger to fire, the current trigger will be interrupted at that point and the new trigger will begin executing. It will continue until it either finishes or hits a Wait, at which point it will yield to the previous trigger's thread.
In addition, if you have the same event registered to multiple triggers, they will be served in order of registration as s3rius mentioned.
And if another event is triggered while the script is already running through a trigger? Will the current trigger be interrupted or will it be allowed to finish before the new event is processed?
From my understanding, there is no such thing as triggering an event, while the script is already running another trigger. The script will finish the trigger, before even being able to process a new event (which does not mean, that the event is not triggered, its just that all script is already finished, when the event can be run). If the game cannot keep this up, it will result in the game lagging and waiting for the script to finish before being able to process another event.
Exceptions being wait (but that means the trigger is already interrupted) and triggering an event by actions of the trigger (like said earlier, this will halt the trigger and finish the newly executed one first).
Thanks guys! Really makes my work much easier. At least Blizz got this one right, and saved us the trouble of writing an event linearizer.
Kakuen, that's incorrect. MTops, every time anything happens which would cause a new event to fire, it pushes the current thread into a stack where it lies dormant until the new thread is complete or hits a wait, at which point the previous thread is popped from the stack, etc., until no current threads remain. It works the same way as calling a function from within a function from within a function... etc.
@grim001: Go
Thanks for the correction, so my understanding is flawed, after all ;).
To complicate things, there is an exception. Due to some GUI-related limitations that go back to WC3, Blizzard specifically makes unit creation events non-interrupting, and places the event at the end of the thread stack instead. However that is the only non-interrupting trigger event that I know of, and it may in fact be the only one.
@grim001: Go
So if I create two time-interval based triggers and set one to fire every 0.5 seconds and the other at every 2.0 seconds, it is possible one of the two is triggered while the other is running, causing it to interrupt the execution.
I guess I have to build a manual mutex to prevent unwanted data sharing...
Can you give a code example of this happening?
Because my Starcraft always finishes the current thread (assuming no waits) before it starts execution of a new one.
@s3rius: Go
I'm not sure if this backs up your statement, but I was messing around with some multi-threading operations awhile back and was using some global variables within my trigger. I was originally under the impression that I needed a mutex to protect my shared globals, but to my surprise, not having a mutex didn't mess up the code execution as I expected it to.
Basically I had a trigger, that could be interrupted by another instance of itself. If each trigger creates one thread whenever it fires, and a second instance fires while the first is still running, I should find that the second instance is modifying the shared global I was using, but it didn't. In fact, it seemed to complete the execution of the first instance before calling the second.
So I do lean toward belief that unless there is a wait within the trigger, the thread runs til its finish before allowing the next thread in the stack to execute.
MTops, your example is flawed because time does not elapse while script is executing.
OK, I went ahead and did some conclusive testing on this subject so we can all stop speculating. I will admit that I assumed it worked the same way as in WC3, because that is the most sensible and correct way to handle events. Instead, SC2 does something shitty and places new events at the end of the trigger queue.
Everything else mentioned in the thread is correct. Triggers are first-come-first-serve, and they will yield to other triggers in the queue as soon as they hit a wait.
I was a wc3 mapper back then and I can't remember it being any different from how things go in SC2.
Anyway, you don't want concurrent threads. You want serialized threads. It's a trigger-action system and not a game engine in itself.
The way that threads worked in WC3 was very well known, and they worked as I just described. It was an excellent design. The way they work now will cause a large number of problems for non-GUI users creating maps of sufficient complexity. (Example: being forced to use Wait 0 when you want the side-effects of an action to be processed before continuing). The only way to deal with this is to use native wrappers that force side-effects to be processed immediately, and to avoid relying on triggers for certain things. Once code sharing gets thrown into the mix, a compiler that hooks natives will be the only way to deal with it that doesn't require people to go around massively editing libraries that they import.
Also, there has never been such a thing as concurrency in WC3 or SC2.
@grim001: Go
Ah, now I actually understand what you're getting at.
Yea, I noticed that when Sc2 didn't apply buffs added via triggers immediately, but needed that Wait.
@s3rius: Go
Hm.. Just wanna get this clear. If the buffs aren't added immediately, are they added when the trigger execution completes? I'm imagining you have something like this:
If this is the case, wouldn't the delay for the buff to be added be rather minuscule if there was a short execution time between the add buff function and the end of the trigger? I believe I'm failing to see why its a problem to have queued events here.. although theory-wise i do agree that it is bad. Just need a better example of how it's bad thats all :/
Why do people continue to believe that any amount of time at all passes while code is executing? The amount of time that passes is something on the scale of nanoseconds, which far too miniscule to affect anything. Furthermore, the game does not simulate the progression of time until it is done with all current threads.
The actual problem is that, if you have a trigger that's supposed to execute when a unit receives a buff, and the code in the part of the original trigger after the buff is applied needs that buff application trigger to have already run, you will need to use a Wait(0) to allow the queued trigger to actually occur.
Another problematic example is a system that indexes units when they enter the map. Due to the fact that the unit creation trigger does not run instantly, anytime you create a unit, it would not have an index assigned until after a wait. This forces the index retrieval function to do a check for whether the unit has an index or not, rather than just returning the unit's custom value without any safety checks.
In general, triggers would be more useful if they applied side effects instantly, whereas this arrangement will make you have to worry about whether or not side effects have actually been processed yet.
@grim001: Go
Herp derp, do I feel like an idiot right now. The fact that no game time progresses while script code is running is nothing new to me. If the script is stuck in an infinite while loop, you can see the game halting for a few seconds before an engine fail-safe kicks in that kills the trigger.
So why I ever thought it would be possible for a trigger to be fired while another one was running, I have no idea. Thanks for the smack on the head, grim!
The good news is, this means no code will ever run in parallel in my project (I never use waits), so I can split the different agents into different interval based triggers to improve performance and improve AI reaction time.