The JHCR Dependencies

lep

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: final dependencies 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. inner auto dependencies Looking carefully we can see two classes of cycles:

  1. The very obvious one between stubs and i2code.
  2. The cycle starting at the non-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. stubs3 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 natives 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.

lep . blog