|
On May 02 2017 02:59 Powder_Keg wrote: Maybe it's just me, and maybe it's just that your language is meant to be understood differently from what people are used to, but here's what I had trouble understanding while reading through your code:
I think you have a misunderstanding of what the langums language is. It's an imperative language (meaning statements are executed sequentially, never out of order) not unlike C or JavaScript. It is not meant to be read like scmdraft trigger lists. Control starts at the top of the main() function and executes statements one by one. Typically you'll see something like
while(true) { poll_events(); }
at the end of the main() function. This makes it so all event handlers get executed one after another indefinitely. Again, there is no out of order execution, everything is sequential and linearizable.
[*]What variables are deathcounters and what are just integers (similarly boolians and switches)
All variables are unsigned integers but the underlying storage is death counters. This is abstracted away from the user more or less.
[*]Which players own the triggers
You can set that with the --triggers-owner command-line argument.
[*]When triggers start and when they end
The emitted triggers are vastly different than the langums code and really hard to follow for a human in the same way machine code is hard to follow for a human. You don't really care about that when using this tool.
|
I think you have a misunderstanding of what the langums language is. It's an imperative language (meaning statements are executed sequentially, never out of order) not unlike C or JavaScript. It is not meant to be read like scmdraft trigger lists.
Your code generates triggers which the map will run through sequentially, that I understand (or am I wrong here? I don't see how I could be.)
What I don't understand from your code is when it generates those triggers.
|
What I don't understand from your code is when it generates those triggers.
You really shouldn't care about what actual triggers are emitted as long as you can observe the behavior described by the LangUMS code you've written. Well, unless you're working on the compiler itself.
|
hmmm Can you run through the logic behind how your code decides what triggers to generate please? Without that I just won't understand what I'm doing.
I know how something like
if( conditions ){ deathcounter1 += deathcounter2; }
would work using switches.
(e.g. the triggers outputted would have "Set Switch1" as an action, and then it would be 32 or so triggers to do a binary addition or w/e it's called adding deathcounter2 to deathcounter1 with each trigger having "Switch 1 is Set" as one of their condition)
Edit: Also, for example, if I just write
var integer = 5
does that take up a deathcounter slot?
|
if( conditions ){ deathcounter1 += deathcounter2; }
The if condition is implemented using two triggers - one will have the false condition, the other the true condition. Depending on which one executes the "instruction counter" will be set to a different location effectively jumping to another point of execution. Basically every emitted trigger begins with a check if the current value of the instruction counter is equal to its address. This way we turn the inherently parallel triggers into a sequential machine. No switches are used for this. Switches are used as mutexes for some operations internally but they are not exposed to the user in any way I think.
Also, for example, if I just write
var integer = 5
does that take up a deathcounter slot?
Yes. Every variable takes up a death count slot and every array takes up as many slots as its size. Local variables exist only for the duration of their block (code surrounded by {}) and their storage gets reused.
|
So each trigger outputted has a "Deathcounter == X" where X is the line of the code, basically?
Also, triggers are by default sequential - StarCraft runs through them from the top to the bottom, but you probably already knew that.
Edit: Would that sometimes cause some triggers to not be run until the next trigger cycle though? for example, lets say you are at line 500 and then now you want to next execute line 200 - to do that you have to wait until the next trigger cycle, which takes up like 1/11 of a second O_O
|
On May 02 2017 04:41 Powder_Keg wrote: So each trigger outputted has a "Deathcounter == X" where X is the line of the code, basically?
This is a relatively simplistic way to put it but yeah.
Also, triggers are by default sequential - StarCraft runs through them from the top to the bottom, but you probably already knew that.
They are executed in sequence by the game code by virtue of the game code being sequential but they are inherently a parallel machine. This "design" makes it easy to introduce race conditions (the bane of sane programming) in your logic. LangUMS actively prevents you from introducing race conditions unless you try really hard to twist it. That's the biggest gain from all this. In the end it's all just automation for something absolutely doable by hand but very tedious and error-prone.
|
What is a race condition?
Edit: nvm, found it https://en.wikipedia.org/wiki/Race_condition
Thanks for answering all my questions btw, I appreciate it :D
What about this question:
Edit: Would that sometimes cause some triggers to not be run until the next trigger cycle though? for example, lets say you are at line 500 and then now you want to next execute line 200 - to do that you have to wait until the next trigger cycle, which takes up like 1/11 of a second O_O
|
Would that sometimes cause some triggers to not be run until the next trigger cycle though? for example, lets say you are at line 500 and then now you want to next execute line 200 - to do that you have to wait until the next trigger cycle, which takes up like 1/11 of a second O_O
Yeah, jumps take one full cycle to execute in the worst case, though that is amortized by some compiler optimizations and it will only get better at it. This isn't any better if you sequence the triggers by hand.
EDIT: On second thought, maybe you're asking doesn't that mean you will miss in-game events if your code happens to be doing something else at that time. The answer is yes and no. All events are currently buffered but only once. Say you have a "bring marines to x" condition in your code. If the player brings the marines but 20 cycles pass between the calls to poll_events() it won't fire off the event 20 times but just 1. This can be an advantage or a drawback depending on where you're coming from. It's totally possible to buffer all 20 events and fire them off but I think it's more useful the other way around. I can turn this into an option eventually.
|
If you still need/want testers, I'm all for it. Just PM me.
|
On May 05 2017 02:06 ZurgMaster wrote: If you still need/want testers, I'm all for it. Just PM me.
Join the Discord @ https://discord.gg/TNehfve that's where we hang out.
Also new features in langums - repeat templates, so you don't have to repeat code for lists of things. Use like:
fn spawn_bonus_items() { for <Loc> in (BonusLocation1, BonusLocation2, BonusLocation3) { spawn(PowerupKhalisCrystal, Player8, 1, Loc); } }
The bigger news is that a live debugger with breakpoints and variable inspection for VS Code is almost ready and will be available to test soon.
|
I am confused on why you say SC is inherently parallel.
SC runs triggers in the following way:
1. It first unpacks all triggers belonging to "Force 1," Force 2," etc., and changes them to belong to just the players in those forces - e.g. if Player 1 and Player 2 are in Force 1, then a trigger owned by Force 1 is the same thing as a trigger owned by Player 1 and Player 2. Similarly, a trigger owned by Current Player or All Players is the same thing as a trigger owned by Player 1, Player 2, etc. all the way to Player 8.
2. It then runs through all triggers owned by Player 1, from the top of the list to the bottom of the list. After this has finished it runs through all triggers owned Player 2, and then Player 3, etc. all the way to Player 8.
3. It then repeats this process.
^ How is that not sequential?
EDIT: On second thought, maybe you're asking doesn't that mean you will miss in-game events if your code happens to be doing something else at that time. The answer is yes and no. All events are currently buffered but only once. Say you have a "bring marines to x" condition in your code. If the player brings the marines but 20 cycles pass between the calls to poll_events() it won't fire off the event 20 times but just 1. This can be an advantage or a drawback depending on where you're coming from. It's totally possible to buffer all 20 events and fire them off but I think it's more useful the other way around. I can turn this into an option eventually.
I gotta be honest, I don't understand a lot of what you just said. What does it mean "All events are currently buffered but only once?" What does poll_events() do? If the player brings 20 marines to the location, and this poll_events() doesn't fire, and then the player removes 20 marines from the location, and then poll_events() does fire, will it not realize that in the meantime the player has brought 20 marines to the location?
Yeah, jumps take one full cycle to execute in the worst case, though that is amortized by some compiler optimizations and it will only get better at it. This isn't any better if you sequence the triggers by hand.
So does this mean that sometimes the triggers wont run immediately? e.g. if I have a "Player brings at least 1 Terran marine to location X" condition, and in game player brings a marine to location X, that there is a chance that the trigger still wont execute because there's some inner workings that have to be sorted out first? This sounds like something that could actually totally break a map, and is something that using normal triggers no one would ever have to worry about - i.e. it sounds like it would just be more difficult to make stuff using this than it is otherwise
|
^ How is that not sequential?
Triggers may be executed sequentially in reality but they are inherently a parallel machine i.e. two unrelated statements in different parts of the program can, in effect, act at once and modify the same parts of memory. This is essentially parallelism and suffers from the drawbacks of any parallel machine (you need locking to avoid data races).
If the player brings 20 marines to the location, and this poll_events() doesn't fire, and then the player removes 20 marines from the location, and then poll_events() does fire, will it not realize that in the meantime the player has brought 20 marines to the location?
Yes it will that's the point of buffering the events so you don't lose any.
So does this mean that sometimes the triggers wont run immediately?
Nothing ever runs "immediately". Your CPU needs time to execute instructions after all.
To answer your question, if the main langums loop is busy with something else and you're not calling poll_events() quickly enough it may delay stuff (but not break it). This can be anywhere from one trigger cycle to many depending on what you're doing in the code.
if I have a "Player brings at least 1 Terran marine to location X" condition, and in game player brings a marine to location X, that there is a chance that the trigger still wont execute because there's some inner workings that have to be sorted out first?
I don't know where you got that from. LangUMS doesn't roll dice to figure out if your stuff will run properly or not. It has pretty well defined semantics and the code does what it says. The trigger will be executed for sure on the next poll_events() call.
|
Triggers may be executed sequentially in reality but they are inherently a parallel machine i.e. two unrelated statements in different parts of the program can, in effect, act at once and modify the same parts of memory. This is essentially parallelism and suffers from the drawbacks of any parallel machine (you need locking to avoid data races).
. . . But two triggers are never run at the same time. They are always checked in the same cycle, and they can both run in the same cycle, but even then SC will just run the first one first in the cycle and the second one second in the cycle. I still don't understand why you say it is parallel. Can you give a concrete example of SC executing triggers in parallel please?
Yes it will that's the point of buffering the events so you don't lose any.
Then why not poll_events() every trigger cycle? I don't understand why this was introduced - what is it's purpose?
Nothing ever runs "immediately". Your CPU needs time to execute instructions after all.
I meant in the same trigger cycle that you meet those conditions. It sounds like with langUMS all the conditions of a trigger can be met for a trigger cycle or two and then become unmet and the triggers would never know.
I don't know where you got that from. LangUMS doesn't roll dice to figure out if your stuff will run properly or not. It has pretty well defined semantics and the code does what it says. The trigger will be executed for sure on the next poll_events() call.
I get that the code determines what happens, but I guess the problem here is I don't understand the code, and from what it sounds like the expected behavior is difficult to determine. I mean, a lot of trigger systems require things to be 'trigger-cycle' perfect (i.e. for triggers to execute as soon as the conditions are met), but with this it seems like sometimes this won't happen?
|
I'm going to try to get involved in this and make some test maps this weekend, I think.
I am still confused in the same way Powder Keg is confused though.
|
On May 06 2017 03:02 Powder_Keg wrote: Triggers may be executed sequentially in reality but they are inherently a parallel machine i.e. two unrelated statements in different parts of the program can, in effect, act at once and modify the same parts of memory. This is essentially parallelism and suffers from the drawbacks of any parallel machine (you need locking to avoid data races).
That's what I mean by parallel. Two triggers can execute during the same cycle and without some form of locking if they access the same "memory" (death counts or whatever else) it creates a data race. This is an issue that experienced map makers are just used to dealing with but it's not simple and is prone to mistakes.
Then why not poll_events() every trigger cycle? I don't understand why this was introduced - what is it's purpose?
The purpose of poll_events() is to run the associated code for any event handlers whose conditions have been fulfilled since the last time poll_events() was called. This is necessary since LangUMS code is sequential and control can't just jump to random places whenever something in-game happens.
You can't poll_events() until the previous one has finished. So the minimum time between poll_events() is the time it takes to execute all your event handlers. This will never be zero unless you have zero event handlers in which case poll_events() emits no instructions.
I meant in the same trigger cycle that you meet those conditions. It sounds like with langUMS all the conditions of a trigger can be met for a trigger cycle or two and then become unmet and the triggers would never know.
The events are buffered so if the conditions are met the event handler will fire the next poll_events() cycle.
I get that the code determines what happens, but I guess the problem here is I don't understand the code, and from what it sounds like the expected behavior is difficult to determine.
The expected behavior is trivial to determine as the code is sequential. You have to read it like any other programming language, each statement follows the preceding one starting from the top of your main() function. Control never just jumps around the place for no reason.
On May 06 2017 03:57 KhaosKreator wrote: I'm going to try to get involved in this and make some test maps this weekend, I think.
Come to the Discord - https://discord.gg/TNehfve so we can chat about it.
|
|
|
|