The task of hot-reloading mapscript changes into a running map is not simple. The mapscript has to be pre-processed and enriched with a custom runtime to achieve this task. Where to put which piece of code was actually non-trivial and this post shall educate and document the state of things.
Once the basic skeleton of compiler and run-time was written it actually needed to be tested somehow. Even if it was very non-functional back then i at least wanted syntactically correct code to not go too offroad with it. Back then JHCR produced some output files from the mapscript. Togehter with the runtime files (and common.j/Blizzard.j) it could be thrown into pjass. But since declaration order is important in JASS this wasn’t actually quite straight forward; some bits of the runtime are needed in the pre-processed mapscript and some bits of the mapscript are needed in other parts of the runtime.
The dependencies are documented in each runtime
file like this // REQUIRES Table StringTable
.
But this information is only used for our human understanding; it’s not
automatically processed as once we found a correct order of dependencies
it stays relativley static.
But we can still use this to produce some nice diagrams from this data: This reads: if there is an arrow from A to B it means A depends on B. So B must come before A in the final mapscript.
Every module here maps 1-to-1 onto a file in runtime
except the Auto
module. Auto
is the
name JHCR uses for its compiled mapscript.
And we can use some topological sorting to get the order for our final script:
List
Table
Types
Print
StringTable
Ins
Modified
Wrap
Context
Parser
Auto
Convert
Interpreter
Init
Which is almost the order used in jhcr
source code except that the print
-module is actually on
top as it used in the alloaction file which is not a real module but
included in the modules which need dynamic allocation.
Now the interesting (and only non-straightforward) module is the Wrap
module. In JASS you can only call functions which were defined before
yours. There are multiple ways 1 to circumvent this but
the easiest is to use a trigger
since you can
add actions to a trigger at any point using TriggerAddAction
2. This is a common
technique in JASS to break cycles. So let’s see where there are the
actual cycles in JHCR. Let’s zoom into the Auto
-module.
Looking carefully we can see two
classes of cycles:
stubs
and
i2code
.Auto
module Interpreter
going from predefined
over stubs
or
dummies
back to itself.Let’s start at the back and explain the 2nd item first.
stubs
3 are the pre-processed user-defined
JASS functions which have a check to see if they were reloaded. So once
they’ve been reloaded they have to use the
Interpreter
. (Dummy-Functions are empty functions which are
solely used to be able to load freshly defined functions into the
running map. They always use the Interpreter
.).
Now the Interpreter
uses the predefined
module
which is one giant function which approximately correspondeces to the
call
instruction4. The call
instruction of course has to be able to call native
s and user-defined functions alike, which were
transformed into stub-pairs in the stubs
module. Hence the
cyclic dependency.
Onto the second cycle: the i2code
module is used to map
integer ids to function literals. JASS doesn’t allow to store them in
arrays so we have to build a giant function which does a binary search
as a cascade of nested if
-statements on the id
to return the function literal. The cycle exists because user-defined
functions can work with function literals and code
data.