Staredit Network > Forums > SC1 UMS Mapmaking Assistance > Topic: Race conditions?
Race conditions?
Dec 4 2013, 3:35 am
By: sethmachine  

Dec 4 2013, 3:35 am sethmachine Post #1



Hi,

I was curious how Starcraft schedules triggers in terms of which run first and in what order in the trigger cycle. I think I read somewhere that it fires off P1's triggers, then P2, etc, until P8, which should mean P1's triggers will run slightly faster than all other players.

Now how does this work with all players (when we make a trigger for all players, is it just syntactic sugar for actually writing that trigger for each player?)

What I've noticed is that when two triggers have the same conditions but are mutually exclusive, e.g., if Trigger A fires before B, then B won't fire. Likewise if B goes before A, then A won't fire (assuming Triggers are atomic...), that not necessarily will they run in the same order. Sometimes A will go but other times B will (or perhaps my observations are incorrect, and the ordering is deterministic).

A problem which I did not understand is the following:

I have a deathcounter set to some value greater than 0.

Then I have a group of several triggers which basically run if the DC is some value >= 1.

At the end of this long series of triggers is a trigger that decrements the counter.

e.g.

Trigger A: if DC >= 1 do X

Trigger B: if DC >= 2 do Y

Final trigger: if DC >= 1 do DC--

Now interestingly the cases which check for DC >=2 (or 3, or 4) NEVER fire off. But, if DC == 4, then trigger A will run 4 times, but trigger B will still run 0 times! This does not make any sense to me why this should be so, because if the Final trigger was running out of order, it would also mess up the cycles of Trigger A.

Only when I added a second, inmutable DC did triggers like B begin fireing off properly...



None.

Dec 4 2013, 4:24 am jjf28 Post #2

Cartography Artisan

Trigger execution works to this effect:

Code
Begin trigger cycle

Set current player to p1
For every trigger:
    if the trigger is owned by player 1, run it
    if the trigger is owned by player 1's force, run it
    if the trigger is owned by all players, run it

Set current player to p2
For every trigger:
    if the trigger is owned by player 2, run it
    if the trigger is owned by player 2's force, run it
    if the trigger is owned by all players, run it
...
(Repeat for players 3-8)
...

End trigger cycle


When I say "run it" that means it will check the conditions, and if they all pass, it performs the actions.

If a trigger is owned by a player, the players force, and all players, it will run 3 times (which we sometimes use for quartic countoffs).
If a trigger is owned by multiple players, it will run for each of those players.

A trigger cycle normally runs every 2 game seconds, this is altered by waits and transmissions (which we sometimes use for hyper triggers, and which can also cause errors that are very tricky to find, when a wait's encountered the trigger execution stops immediately, and starts from the beginning the next cycle (not running the triggers below the wait until then)).



TheNitesWhoSay - Clan Aura - github

Reached the top of StarCraft theory crafting 2:12 AM CST, August 2nd, 2014.

Dec 4 2013, 4:52 am Moose Post #3

We live in a society.

Quote from sethmachine
I was curious how Starcraft schedules triggers in terms of which run first and in what order in the trigger cycle. I think I read somewhere that it fires off P1's triggers, then P2, etc, until P8, which should mean P1's triggers will run slightly faster than all other players.
It goes by order of players from least to greatest (1, 2, ..., 8, assuming they're all in the game). Then top to bottom according to position. Player 1's triggers do not run faster, but they have "priority" in the sense that they are checked first. Which means that if Player 1's trigger has actions that would make conditions not true for Player 2, then Player 1 will run his trigger all the time and Player 2 will never run his trigger.

Quote from sethmachine
Now how does this work with all players (when we make a trigger for all players, is it just syntactic sugar for actually writing that trigger for each player?)
Yes. If a Force owns a trigger, it's the same as having each player in the force own it. If All Players owns a trigger, it's the same as having each player 1 through 8 owning it. What matters is their position in the list of triggers and because Forces and All Players can obscure order when viewed separately, text triggers are the way to see the absolute ordering. (EG, In a Staredit.exe-based editor, (X-Tra, etc.), if Player 1 is in Force 1, you have no idea just by looking at the triggers if a trigger for Player 1, Force 1, or All Players is at the top or bottom of the list.)

As for your example, the crucial piece of information missing here is who owns each of those triggers. As jjf28 noted, if there are waits involved, then things can get more complicated.




Dec 4 2013, 8:34 pm NudeRaider Post #4

We can't explain the universe, just describe it; and we don't know whether our theories are true, we just know they're not wrong. >Harald Lesch

Quote from Mini Moose 2707
In a Staredit.exe-based editor, (X-Tra, etc.), if Player 1 is in Force 1, you have no idea just by looking at the triggers if a trigger for Player 1, Force 1, or All Players is at the top or bottom of the list.
If I'm not entirely mistaken you can see the actual order if you mark all (involved) players and then check the trigger list.
May only work in ScmDraft though.

Quote from Mini Moose 2707
if there are waits involved, then things can get more complicated.
If order matters, stay away from waits! (except hyper triggers)
Use death count timers instead. They are preferred anyway.




Dec 4 2013, 9:07 pm Wormer Post #5



In addition to what is said above. It's important to understand that the game runs in so called frames. Between frames the game state doesn't change (units don't change their position, don't use abilities, don't deal or receive damage and etc.). Triggers run exactly at this time, so you shouldn't expect a change during single triggers execution (except change that is triggered by triggers themselves).

Also, when relying on triggers order be careful with actions and conditions that deal with units on the map, because these changes might not be instant (i.e. only detected by triggers on the next tirgger run). For example a Kill Unit action just marks the unit as "killed" but consequent conditions still see it in the same trigger loop. A unit created by Create Unit action sometimes might not be detected by the following conditions Bring or Command, but the Remove Unit action is always instant. Actions Move Unit and Give Unit also ones that may cause trouble. On the other side all changes to unit deaths (death counters), score, countdown timer, resources or switches are always instant.

Post has been edited 7 time(s), last time on Dec 4 2013, 9:21 pm by Wormer.



Some.

Dec 5 2013, 9:30 am Lanthanide Post #6



None of you guyses replies are actually addressing the substantive point that seth made, which is how triggers with overlapping conditions behave.

It's been a long time since I've done any mapping, but I recall being confused by this as well. In the map DB, there's a demonstration map for how to get a random value between (I believe) 1 and 100, with data disc powerups and a mobile grid used to draw a distribution grid. Anyway, in this map, it has many triggers with otherwise similar conditions and forces them into blocks by adjusting a DC.

It is something like this;
'Randomize' DC-A
If DC-A between 1 and 100 & DC-B == 1, do X
If DC-A between 101 and 200 & DC-B == 1, do Y
If DC-A between 201 and 300 & DC-B == 1, do Z
Always, increment DC-B by 1
'Randomize' DC-A
If DC-A between 1 and 100 & DB-B == 2, do H
If DC-A between 101 and 200 & DC-B == 2, do I
If DC-A between 201 and 300 & DC-B == 2, do J

Now the point here is that DC-B strictly shouldn't be necessary if triggers were run from top to bottom in order. It should randomize DC-A, do one of actions X, Y and Z, then randomize DC-A again and do one of actions H, I and J. But actually if you take DC-B out completely then you end up getting X and H, or Y and I or Z or J happening 'simultaneously', and you may (can't recall for sure) even get them happening twice, due to two instances of DC-A being randomized.

Anyway, I believe this is the main point seth was getting at, which none of you guys have addressed. Also don't take what I'm saying here as 100% gospel truth, this is just my rememberings of some trigger behaviour that seemed non-linear: it seemed like triggers that were out of order were being executed when they shouldn't have been.

If you can find the map in the DLDB that I'm talking about it might make more sense.

Also seth if you could post actual triggers that showed the behaviour you're describing, people could try them out themselves and see what you're talking about.



None.

Dec 5 2013, 9:51 am Azrael Post #7



I can't speak for every possible example, or for your specific example if there were additional triggers in-between what you have there. What I can say is that, in the example given, B is unnecessary, not just in theory but in practice. I have a few maps with systems using that structure extensively, and never had any problems with it.

I'd be interested in seeing a set of conditions/actions which do cause an unexplainable conflict though, hopefully someone can dig up the map you mentioned.

Post has been edited 1 time(s), last time on Dec 5 2013, 9:57 am by Azrael.




Dec 5 2013, 10:50 pm NudeRaider Post #8

We can't explain the universe, just describe it; and we don't know whether our theories are true, we just know they're not wrong. >Harald Lesch

Quote from Lanthanide
None of you guyses replies are actually addressing the substantive point that seth made, which is how triggers with overlapping conditions behave.
Not to sound like a smartass, but I believe we did:
Quote from jjf28
[...] this is altered by waits and transmissions (which we sometimes use for hyper triggers, and which can also cause errors that are very tricky to find
Quote from Mini Moose 2707
As jjf28 noted, if there are waits involved, then things can get more complicated
Quote from NudeRaider
If order matters, stay away from waits! (except hyper triggers)


To elaborate, I'll use the outcome you described for your example:
Quote from Lanthanide
you end up getting X and H, or Y and I or Z or J happening 'simultaneously', and you may (can't recall for sure) even get them happening twice, due to two instances of DC-A being randomized.
What you describe isn't logical looking at the example you provided. And as Az said, if you code only your example lines I can guarantee you that the quirks you describe won't appear.
However they will appear, just as you described, if somewhere in between the lines you add waits.

The reason for this is, waits pause the current trigger loop and begin a new one right away (WITHOUT the trigger (or was it just that 1 wait action?) that called the 2nd loop!), without the usual 84ms delay between trigger loops. The first trigger loop will be continued after the wait time has expired. Expiration is checked every 84ms (42ms actually).
It isn't researched comprehensively, but apparently wait expiration is checked every 42ms, but new trigger loops can only be run every 84ms. (42ms = 1 frame duration on fastest)

Btw. if you know hyper triggers and understood the above you probably noticed that this is how they work: They call stacks of themselves and each new call happens after 84ms for low wait times (e.g. the usual wait(0)'s).
Btw2. if you know what NEO is: After all recursive stacks of waits have been processed you get 1 normal trigger cycle length and then the stacks are restarted. This is called a Next Ending Occurrence.

Since this is pretty complicated trigger theory we just stated that it's better to stay away from waits without fully explaining the consequences.




Dec 6 2013, 8:02 am Wormer Post #9



Quote from NudeRaider
The reason for this is, waits pause the current trigger loop and begin a new one right away (WITHOUT the trigger (or was it just that 1 wait action?) that called the 2nd loop!), without the usual 84ms delay between trigger loops. The first trigger loop will be continued after the wait time has expired. Expiration is checked every 84ms (42ms actually).
Actually not like this Nude :) because the first trigger loop always finishes right away. Only a single trigger with wait is getting postponed. Like you said, the next extra triggers check is shcheduled for the frame after next. I believe FartyHenermann posted code that explained why it happens the frame after next (those lazy Blizzard programmers :P put a strict inequation in condition instead of unstrict one or vice versa!).

For instance suppose you have triggers A, B, and C written for Player 1 in the designated order. Trigger B is the only trigger that has a wait 0 ms action. The trace of their execution will look like "A, B(start), C, pass 2 frames, A, B(end) C", but NOT like "A, B(start), A, (skip B), C, pass 2 frames, B(end), C". Essentially triggers order stays the same, but some actions that come after wait in B may have trouble with execution.

Let's say triggers are as follows:

Trigger A (Player 1)
Conditions:
Always.
Actions:
Display text message "A".
Preserve trigger.

Trigger B (Player 1)
Conditions:
Always.
Actions:
Display text message "B(start)".
Wait for 0ms.
Display text message "B(end)".
Preserve trigger.

Trigger C (Player 1)
Conditions:
Always.
Actions:
Display text message "C".
Preserve trigger.


In game you will obviously see:
A
B(start)
C
A
B(end)
C


I think the comparison with stack or nesting is harmful when talking about waits. In fact SC doesn't keep any kind of stack for trigger executions. It only works with alarms (separate alarm per player) and schedules extra trigger checks. What happens is very simple if you don't think about it as stack or recursion.

When the wait action is encountered SC checks if the alarm for current player is unused. If it isn't free the current trigger is postponed just before wait. If the alarm is free for use then it's getting wind up for the amount specified in wait, the trigger is then postponed inside wait. In either case an extra triggers check is scheduled after 2 frames. When SC encounters a postponed trigger it skips conditions check and goes right to the wait action where it checks if the alarm has given a ring. Then SC either continues with trigger's actions or pospones it further.

sethmachine,
If you have a long transmission in Y in your trigger B and if you use hyper triggers then the effect will be exactly as described: SC will continue to fire trigger A and final trigger while skipping trigger B for the period of the transmission (or even longer if you encounter an effect called wait blocks).

P.S.
I digged an old stuff, actually it was Heinermann (my hero! :flowers: ahahh :D carefree times! sob-sob :weep:) who explained why triggers execute every second frame.

Post has been edited 14 time(s), last time on Dec 6 2013, 9:04 am by Wormer.



Some.

Dec 6 2013, 8:46 pm NudeRaider Post #10

We can't explain the universe, just describe it; and we don't know whether our theories are true, we just know they're not wrong. >Harald Lesch

I got the details wrong, it seems. Thanks for clearing that up.




Dec 7 2013, 4:02 pm Lanthanide Post #11



Looks like I am indeed mistaken. I found the map in the DLDB: http://www.staredit.net/?file=1730 it's worth downloading and having a look at because it's pretty neat what it ends up producing.

Anyway, that map is by JaFF and has a "system" DC that is incremented between the stages of the algorithm. My first encounter with this map, and indeed with (modern) mapping for SC, was in my ill-fated Blood Pressure Marathon map, where I was adapting this system to generate the random unit a player got. Being a newb, that map was full of waits, which is presumably why the system didn't seem to work properly unless I maintained the lock-step DC as JaFF had done it. I (just now) edited that map and commented out all uses of the system DC and it appears to work flawlessly.

So that explains that. Sorry for calling you guyses out.

Post has been edited 1 time(s), last time on Dec 7 2013, 7:09 pm by Lanthanide.



None.

Dec 7 2013, 6:54 pm Wormer Post #12



I'm very happy :yahoo: now we got all pieces of trigger execution system that were making so much trouble to many people finally researched and understood: triggers execution order, locations invalidation and operation of waits.



Some.

Options
  Back to forum
Please log in to reply to this topic or to report it.
Members in this topic: None.
[01:24 pm]
Vrael -- NEED SOME SPORTBALL> WE GOT YOUR SPORTBALL EQUIPMENT MANUFACTURING
[2024-4-30. : 5:08 pm]
Oh_Man -- https://youtu.be/lGxUOgfmUCQ
[2024-4-30. : 7:43 am]
NudeRaider -- Vrael
Vrael shouted: if you're gonna link that shit at least link some quality shit: https://www.youtube.com/watch?v=uUV3KvnvT-w
Yeah I'm not a big fan of Westernhagen either, Fanta vier much better! But they didn't drop the lyrics that fit the situation. Farty: Ich bin wieder hier; nobody: in meinem Revier; Me: war nie wirklich weg
[2024-4-29. : 6:36 pm]
RIVE -- Nah, I'm still on Orange Box.
[2024-4-29. : 4:36 pm]
Oh_Man -- anyone play Outside the Box yet? it was a fun time
[2024-4-29. : 12:52 pm]
Vrael -- if you're gonna link that shit at least link some quality shit: https://www.youtube.com/watch?v=uUV3KvnvT-w
[2024-4-29. : 11:17 am]
Zycorax -- :wob:
[2024-4-27. : 9:38 pm]
NudeRaider -- Ultraviolet
Ultraviolet shouted: NudeRaider sing it brother
trust me, you don't wanna hear that. I defer that to the pros.
[2024-4-27. : 7:56 pm]
Ultraviolet -- NudeRaider
NudeRaider shouted: "War nie wirklich weg" 🎵
sing it brother
[2024-4-27. : 6:24 pm]
NudeRaider -- "War nie wirklich weg" 🎵
Please log in to shout.


Members Online: Roy