Closed peq closed 9 years ago
Here would be an example using a special annotation @withRuntime
. The meaning of this would be that all calls to native functions, which are not understood by the compiletime interpreter, are recorded and added to map initialization time.
@compiletime @withRuntime
function example()
for i = 1 to 8
new ItemDefinition('I000' + i, 'Ibla')
..setName("Tome of attack " + i.toString())
createItem('I000' + i, vec2(0., 0. + 128. * i))
// would generate:
init
CreateItem(1227894833, 0.0, 128.0)
CreateItem(1227894834, 0.0, 256.0)
CreateItem(1227894835, 0.0, 384.0)
CreateItem(1227894836, 0.0, 512.0)
CreateItem(1227894837, 0.0, 640.0)
CreateItem(1227894838, 0.0, 768.0)
CreateItem(1227894839, 0.0, 896.0)
CreateItem(1227894840, 0.0, 1024.0)
Open questions:
@compiletime @withRuntime
function example_problem()
for i = 1 to 8
new ItemDefinition('I000' + i, 'Ibla')
..setName("Tome of attack " + i.toString())
createItem('I000' + i, GetPlayableMapRect().getCenter().polarOffset(angle(0), 128.*i))
or even:
for i = 1 to GetPlayers()
...
My first idea would be too throw an error if a return value from such a function is used somewhere, but that is probably too restrictive. If we make it more flexible, it would be very hard to explain the semantics of this construct, I guess.
@compiletime @withRuntime function example_problem() for i = 1 to 8 new ItemDefinition('I000' + i, 'Ibla') ..setName("Tome of attack " + i.toString()) createItem('I000' + i, GetPlayableMapRect().getCenter().polarOffset(angle(0), 128.*i))
There is no reason why non-compiletime init blocks can't use natives, so this example is equivalent to two functions, generated by a preparsing stage:
@compiletime
function example_problem_gen()
for i = 1 to 8
new ItemDefinition('I000' + i, 'Ibla')
..setName("Tome of attack " + i.toString())
init
for i = 1 to 8
createItem('I000' + i, GetPlayableMapRect().getCenter().polarOffset(angle(0), 128.*i))
for i = 1 to GetPlayers() ...
There are a couple ways to deal with this:
Some constructs are just not natural for compiletime functions, just as
@compiletime function not_allowed() for i = 1 to GetPlayers() ...
Thus, an example with such a call whose contents contain both compiletime and initialization level code, should error.
GetPlayers()
into 12 (or 11? 15?) for
the compiletime block.I'm not a big fan of this choice because it could cause unintentional
collisions for users who expect GetPlayers()
to behave as their native.
Consider a map with 6 players and they want to organise groups of items in 3
sets:
@compiletime
fn what_happens_here?()
names = ['Bob', 'Joe', 'Bill', 'Skooma', 'Peq', 'Frotty']
types = ['Sword', 'Shield', 'Helmet']
for i = 1 to GetPlayers()
new ItemDefinition('I000' + i, 'Ibla')
..setName(types[0] + ' of ' + names.sample)
new ItemDefinition('I007' + i, 'Iblb')
..setName(types[1] + ' of ' + names.sample)
new ItemDefinition('I00E' + i, 'Iblc')
..setName(types[2] + ' of ' + names.sample)
Then you will have a collision if GetPlayers() in compiletime resolves to 12 or 15 - and this could be a potentially difficult bug to solve.
It also sets a bad precedent if other natives require special cases - better just raise error.
- Often one wants init code to be executed delayed, so that load time is not affected and the maximum number of instructions are not a problem in the init. How could this be done here?
This is an earlier stage in init block processing. The init
block generated
by @withRuntime
blocks should get fed into the optimiser.
So the compiler would basically do this:
I think that would not be too much work to implement, but I still think the behaviour would be a bit weird.
This is an earlier stage in init block processing. The init block generated by @withRuntime blocks should get fed into the optimiser.
The optimizer cannot (and should not) move initialization to a later phase.
So the compiler would basically do this
Yes, I think something like this is what I'm getting at.
Side note: part of our discussion was that I was suggesting removing init blocks in favor of using annotations.
Another option would be to just remove the @compiletime annotation, and make all init blocks valid for @withruntime.
The optimizer cannot (and should not) move initialization to a later phase.
What I was talking about here is related to the problem you said:
Often one wants init code to be executed delayed, so that load time is not affected and the maximum number of instructions are not a problem in the init. How could this be done here?
Any news on this?
So Crigges tested that the compiletime function works. Meaning you can now do something like this:
init
doInit()
@compiletime
function doInit()
for i = 1 to 100
if compiletime
// create item object
else
// place item on map
I think this is enough for this feature so I am closing this ticket.
I am also adding the discussion from IRC today, as it might be interesting for other readers:
(12:27:15) Cokemonkey11: peq, hi
(12:27:31) peq: hi
(12:27:35) Cokemonkey11: i still want compiletime functions and init blocks in the same scope :P
(12:27:50) peq: got it :)
(12:28:43) Cokemonkey11: is that a positive or a negative response...
(12:29:16) peq: depends
(12:29:35) peq: I will add a constant so that you can use "if compiletime ...."
(12:29:45) peq: but probably not the other things
(12:33:04) Cokemonkey11: peq that is so nasty...
(12:33:09) Cokemonkey11: you must agree that this is a language smell
(12:33:32) WaterKnight: how are compiletime funcs mixed with init funcs
(12:33:39) peq: no
(12:33:45) peq: I don't agree
(12:33:56) peq: mixing two concepts would be confusing and a language smell
(12:34:17) WaterKnight: remember to clean your wurst
(12:34:36) Frotty [~frottyzd@ip-178-200-132-42.hsi07.unitymediagroup.de] entered the room.
(12:34:36) mode (+o Frotty) by Q
(12:35:30) Cokemonkey11: WaterKnight, compiletime actions should unambiguously run during compile time. Other actions should unambiguously not run during compiletime.
(12:35:42) Cokemonkey11: Adding language features to describe the two kinds of action is a language smell
(12:35:55) Cokemonkey11: The init block is already a language smell
(12:36:10) WaterKnight: [12:35] if compiletime
though
(13:09:20) Cokemonkey11: this is definitely solvable
(13:09:26) Cokemonkey11: but not anymore reasonable
(13:09:29) WaterKnight: i guess the problem is that you dont see it from outside
(13:09:31) Cokemonkey11: now it's complicated
(13:09:35) Crigges: i did a lot of compiletime stuff and there are nice ways to solve this without chaning wurst
(13:09:42) Cokemonkey11: solving yes
(13:09:45) Cokemonkey11: writing nice code, no
(13:09:56) Cokemonkey11: I saw some of your examples and I wanted to stop writing wurst
(13:10:06) Cokemonkey11: too much indirection
(13:10:31) WaterKnight: i am not sure if you should have compiletime funcs in the code
(13:10:49) WaterKnight: since i am planning atm an object editor
(13:12:01) WaterKnight: the idea there is more like you set yourself scopes and may statically or by script create the objects
(13:12:07) WaterKnight: but not directly in the mapscript
(13:12:09) Crigges: okay why this code isn't nice?
(13:12:14) Cokemonkey11: wait
(13:12:15) Cokemonkey11: im still here
(13:12:15) WaterKnight: and then that's like catalogues
(13:12:21) Cokemonkey11: what if you can just say
(13:12:29) Cokemonkey11: if you use @initcompile
(13:12:43) Cokemonkey11: that, any variable that is used by compiletime action
(13:12:50) Cokemonkey11: must not have been affected by a function that has side-effects
(13:12:57) Cokemonkey11: that solves the problem and would be easier to implement
(13:13:01) Cokemonkey11: but might be hard to explain in a tutorial
(13:13:35) WaterKnight: but that restricts a lot
(13:13:39) Cokemonkey11: i.e. if side-effects change a variable and a compiletime action uses that variable: compiletime error
(13:13:39) Crigges: Cokemonkey11: what is better? You decide yourself what is executet @compiltime and what is executet @runtime OR the compiler solves this problem and you don't know what exactly happens?
(13:13:57) Cokemonkey11: Crigges, I'm not suggesting we remove @compiletime annotation
(13:14:05) Crigges: i know
(13:14:28) WaterKnight: when you cannot have side effects, you basically have to disconnect runtime and compiletime again
(13:14:30) Cokemonkey11: I think you should let good programmers write code that will clearly do what they want during both phases
(13:14:40) Crigges: you want to combine compiletime AND runtime
(13:14:45) Cokemonkey11: I'm happy to disconnect them - not happy to write 2 functions
(13:14:55) Crigges: and the compiler should seperate this stuff
(13:15:05) Cokemonkey11: with the @compileinit annotation, yes!
(13:15:14) Crigges: you don't have to
(13:15:22) Cokemonkey11: I don't even need the compiletime errors
(13:15:25) Crigges: just create your own annotation
(13:15:29) Crigges: and use
(13:15:32) Crigges: if compiletime
(13:15:37) Cokemonkey11: if compiletime doesnt exis
(13:15:42) Crigges: use my package
(13:15:52) Crigges: https://github.com/Crigges/HotN/blob/080bce449d51e9d63e8d4011292586935031e011/HotN/wurst/helper/CompiletimeInit.wurst
(13:15:53) Cokemonkey11: put it in the standard library and use it
(13:16:06) Crigges: and change dinit to compileinit
(13:16:12) Crigges: and youve got what you want
(13:16:23) Crigges: https://github.com/Crigges/HotN/blob/master/HotN/wurst/Heros/SampleHero/SampleHero.wurst
(13:16:53) Crigges: this code runs @compile and @runtime
(13:17:15) Crigges: and the class behind decides what to do
(13:17:19) WaterKnight: we need more times
(13:17:47) Cokemonkey11: I'm basically saying
(13:17:49) Cokemonkey11: I want to use this code
(13:17:53) Cokemonkey11: but I don't want this code in my map
(13:17:54) Cokemonkey11: LOL
(13:18:04) Crigges: woot?
(13:18:06) Cokemonkey11: yeah, okay I'll just do this and stop complaining
(13:18:18) Crigges: so because it isn't inside the standard lib
(13:18:21) Cokemonkey11: no
(13:18:21) Cokemonkey11: no
(13:18:24) Cokemonkey11: I will use this
(13:18:30) Crigges: okay
(13:18:35) Cokemonkey11: the thing that makes me uncomfortable is that
(13:18:45) Cokemonkey11: encouraging the use of this will discourage adding it to the compiler
(13:19:00) Cokemonkey11: and that means you'll never get compiletime errors for the weird cases we talked about previously
(13:19:14) Cokemonkey11: I would prefer to have the same functionality built-in
(13:19:24) Crigges: i get compiletime erros
(13:19:25) peq: currently you get compiletime errors for all the weird cases we talked about
(13:19:36) Crigges: since i throw them myself
(13:19:43) Cokemonkey11: do you get compiletime errors for this example? http://peeeq.de/code.php?id=9025
(13:20:06) Cokemonkey11: crigges, you wrote an annotation and made all compiletime code require non-side-effect calls only?
(13:20:30) Cokemonkey11: if compiletime is still unnecessary indirection
(13:20:42) Crigges: nah not realy
(13:20:42) peq: you would get an error in placeItem()
(13:20:56) peq: because it would use a runtime function at compiletime
(13:21:04) Crigges: yes
(13:21:18) Cokemonkey11: okay but change the code to use if compiletime
(13:21:25) Cokemonkey11: without changing the behavior of the incrementor
(13:21:40) Cokemonkey11: and you have erroneous code without compiletime error
(13:21:43) Cokemonkey11: and ugly indirection
(13:21:55) Cokemonkey11: and no strategy for resolution in future
(13:21:58) peq: but it would be quite obvious that it is wrong
(13:22:00) Cokemonkey11: in the general case
(13:22:19) Crigges: https://github.com/Crigges/HotN/blob/master/HotN/wurst/UnitStuff/UnitTypes.wurst
(13:22:32) Cokemonkey11: i would argue that using @initcompile like I suggest, should also have obvious erroneous code
(13:23:33) Crigges: but just for the readability why?
(13:23:47) Crigges: it would be like one of this vjass features
(13:24:04) Crigges: nobody knows what's the code does exactly
(13:24:31) Crigges: take a look @the last function of the UnitTypes package
(13:24:47) Cokemonkey11: you can write unintelligible code in most languages
(13:24:48) Cokemonkey11: yes I saw that
(13:24:52) Cokemonkey11: I see how it works
(13:24:53) Crigges: i throw compile errors myself if something is wrong
(13:26:07) Crigges: and since the compiletime code is captured inside this class
(13:26:19) Crigges: you can't destroy anything from outside
(13:26:54) Cokemonkey11: yes but you don't have any strategy for compiletime errors if local variables in the init case differ from the compiletime case
(13:27:07) Cokemonkey11: and there's not any reasonable way you can add that
(13:27:23) Cokemonkey11: actually there is, but its so complicated
(13:27:48) Cokemonkey11: you would need to somehow require that all variables used in the compiletime case were only used by methods marked as not having side-effects
(13:27:57) Cokemonkey11: reasonable in a compiler, not in wurst
(13:29:08) Cokemonkey11: and writing if/else is almost as ugly as having 2 independent functions
(13:29:13) Crigges: i don't need a way for that
(13:29:25) Cokemonkey11: I don't need it either, but someone might
(13:29:31) Cokemonkey11: okay, I finally go now, be back in a bit
(13:29:42) Crigges: wow
(13:30:06) Crigges: ^I don't need it either, but someone might and this is why we arguing
Crigges has made a design pattern that a lot of people will use.
It works by injecting a compiletime
boolean into the scope of the function, and runs that function twice.
This API is not ideal, and requires careful explanation.
It is still prone to the counterexamples from before.
It has no roadmap for improving compiletime errors, because wurst should not be used for validating side-effects of functions.
I am not yet sure how this could be done. Some discussion is below:
(15:45:35) Cokemonkey11: peq, why can't I create a unit in a compiletime function? (15:46:05) peq: you mean a new unit type? (15:46:09) peq: or a real unit? (15:46:31) peq: the first one should be possible (15:49:49) Cokemonkey11: i mean createunit() (15:50:14) Cokemonkey11: it would be a nice feature if compiletime functions could automaticaly detect non-compiletime events (15:50:21) Cokemonkey11: and build initialization functions instead (15:50:38) Cokemonkey11: because it would be useful for example i have a unit which is only for decoration (15:50:49) Cokemonkey11: i compiletime initialize it and then place it in the same function (15:55:32) WaterKnight: how are comptiletime functions scheduled (16:02:34) ***Cokemonkey11 slaps peq around a bit with a large fishbot (16:04:04) peq: hm, I think we could do that (16:04:12) peq: but I don't see much benefit in it (16:04:23) peq: you can do the same thing in normal code (16:05:17) Cokemonkey11: yeah its just a pain in the ass (16:05:21) Cokemonkey11: if i have a compile time function for 10 units (16:05:27) Cokemonkey11: and also a single placement for each of those 10 units (16:05:39) Cokemonkey11: it isn't very pragmatic to have a big set of function pairs (16:06:49) peq: one function to generate the objects + one init block (16:07:22) Cokemonkey11: right but the data is associated regardless (16:07:28) Cokemonkey11: i have 10 units that each need to be placed once (16:08:21) peq: I think it would be confusing to mix compiletime and runtime like this (16:08:55) peq: then you have to know for each function which one belongs to compiletime and which to runtime (16:09:21) Cokemonkey11: just remove @ compiletime completely (16:09:31) Cokemonkey11: compiletime events should be implicit (16:09:44) Cokemonkey11: functions should support both types of event (16:09:49) Cokemonkey11: and the compiler should arbitrate that (16:10:13) peq: functions can be used in both types (16:10:23) peq: the annotation is just to say which ones are called (16:10:52) peq: it wold not make sense to just call all the functions in the script (16:11:40) Cokemonkey11: just merge compiletime and initialization functions (16:11:48) Cokemonkey11: initialization can never perform compiletime events (16:11:54) Cokemonkey11: and compiletime events can never be run during initialization (16:12:04) Cokemonkey11: so the distinction is just overly verbose (16:12:37) peq: so you want to write compiletime code in normal init blocks? (16:12:58) peq: and the compilere should check the contents of init blocks to see which ones should be run at compiletime? (16:13:10) Cokemonkey11: i want to be able to write good functional code that takes a set of data (unit type and location) and both initialized and instantiates them (16:13:19) Cokemonkey11: instead of writing two functions to perform half of those actions on the same data (16:13:26) Cokemonkey11: yes exactly (16:14:45) peq: hmm (16:15:24) peq: someone has done something like this by using if blocks (16:15:30) peq: maybe it was Crigges (16:15:35) peq: something like (16:15:45) Cokemonkey11: if blocks? what do you mean (16:15:48) Cokemonkey11: i dont understand how it can be (16:15:58) peq: if compiletime then else
(16:16:31) Cokemonkey11: how is that possible
(16:17:29) peq: https://github.com/Crigges/HotN/blob/080bce449d51e9d63e8d4011292586935031e011/HotN/wurst/helper/CompiletimeInit.wurst
(16:17:29) Cokemonkey11: is the @ compiletime annotation just an alias for something else?
(16:18:13) Cokemonkey11: that.. that is spagetti
(16:18:30) peq: and https://github.com/Crigges/HotN/blob/080bce449d51e9d63e8d4011292586935031e011/HotN/wurst/OeSetup.wurst#L26
(16:20:16) Cokemonkey11: that is useful
(16:20:26) lep: kek
(16:20:28) Cokemonkey11: i think that this is a language smell though
(16:21:02) WaterKnight left the room (quit: Quit).
(16:22:18) peq: i don't like it much
(16:23:42) Cokemonkey11: what don't you like, the second example? it's just an ugly version of my suggestion
(16:23:45) Cokemonkey11: still better than two functions though
(16:24:06) peq: it's both one example
(16:24:20) peq: the first is to set the "compiletime" variable
(16:24:24) peq: that is a bit ugly
(16:24:40) peq: would be nicer if there was a builtin constant for that
(16:25:08) peq: and I also don't like the mixing of compiletime and runtime
(16:25:29) peq: I think it even depends on the optimizer to eliminate the if-statements
(16:29:30) Cokemonkey11: he shouldnt have to do that imo
(16:29:43) Cokemonkey11: it should be part of the compiler to do compiletime actions during compile time
(16:29:48) Cokemonkey11: and everything else during map load
(16:30:04) Cokemonkey11: unless you have really good example of an action that has a good reason to run in both
(16:32:01) peq: usually there are some functions shared by both
(16:32:15) peq: but the actions are separate
(16:35:09) Cokemonkey11: right, some actions can occur in either case, that's fair
(16:35:30) Cokemonkey11: actually, which actions are those?
(16:36:09) peq: setting up the data
(16:37:17) peq: so data which you need to create the objects and which you also need at runtime
(16:41:57) Cokemonkey11: such values should be constants
(16:42:04) Cokemonkey11: or equivalent for both
(16:42:05) Cokemonkey11: no?
(16:42:22) peq: yes
(16:42:37) Cokemonkey11: so the compiler could just duplicate this part of the code
(16:42:39) Cokemonkey11: in the generated output
(16:43:10) peq: but how should it know which parts are required by the runtime?
(16:43:16) peq: and which by the compiletime?
(16:45:15) Cokemonkey11: for example, each initialization function could be parsed twice - once as though it were compiletime, and once as though it were initialization time
(16:45:45) Cokemonkey11: actions which have effect on the compiletime one are stripped in the initialization time one
(16:45:54) Cokemonkey11: any extra code like conditions and loops would be stripped automatically
(16:46:13) Cokemonkey11: once that compiletime stuff is gone in the initialization pass that is stripped of compile-time only actions
(16:46:27) Cokemonkey11: im sorry im not very good at describing this, i never wrote a compiler before