• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

Jass Hot Code Reload

Level 13
Joined
Nov 7, 2014
Messages
571
Modifying WarCraft 3 maps has a long iteration period due to closing WC3,
changing your code and restarting WC3.
A "hackish" sort of way to speed up the process is to close wc3's handle to the map file, update the map file, restart the map (while wc3 is running the map), hope wc3 doesn't crash. This avoids the delay of closing/restarting of wc3, opening the map and starting it. It's still much slower than hot code reloading though.

When using hot code reloading, global side effects like creating units/effects/etc need special handling, restarting the map gives one a clean slate (can also be annoying).

If you like this and want me to write
more please tell me.
I am telling you to please write more (love the 80 column limit =)).


PS: just noticed that jhcr.exe is ~2.7MiB packed, becomes ~15MiB unpacked O.O! With great power comes great file size, I guess =)
 

LeP

LeP

Level 13
Joined
Feb 13, 2008
Messages
539
In this post we're going to look at all the things related to the bytecode.
The bytecode was custom-designed to work reasonably fast in our jass context,
so that it would actually be useable and provide a net-positive for map
development.

One key insight is that the interpreter works on only one global hashtable.
We use the same technique as many other table libraries: use an unique integer
as the first index for the table and our provided key for the second.
Another great feature of hashtables that we use extensively is that you can use
any integer as any index. We will later see how that is usefull.


Now let's first look at the two definitions of our bytecode.
We have one in Haskell which we compile to and one corresponding file in Jass.
You can find them here and here.

Code:
data Instruction
    -- three regs
    = Lt                Type Register Register Register
    | Le                Type Register Register Register
    | Gt                Type Register Register Register
    | Ge                Type Register Register Register
    | Eq                Type Register Register Register
    | Neq               Type Register Register Register
    | Add               Type Register Register Register
    | Sub               Type Register Register Register
    | Mul               Type Register Register Register
    | Div               Type Register Register Register
  
    -- Mod only works on integers but for ease we still include the Type
    | Mod               Type Register Register Register

    | SetGlobalArray    Type Register Register Register
    | GetGlobalArray    Type Register Register Register
    | SetLocalArray     Type Register Register Register
    | GetLocalArray     Type Register Register Register
  
    -- two regs
    | Negate    Type Register Register
    | Set       Type Register Register
    | SetGlobal Type Register Register
    | GetGlobal Type Register Register
    | Bind      Type Register Register

    -- special
    | Literal Type Register (Hot.Ast Var Expr) -- encoded as: lit ty reg len string
    | Call Register Label Name
    | Convert Type Register Type Register

    -- one label
    | Label Label
    | Jmp Label
    | Function Label Name
  
    | JmpT Label Register
  
    | Not Register Register

    | Ret Type
    deriving (Show)

A quick glance and we can see that we use four different data-types in the
instructions: Type, Register, Label, Name.

Let's look at them one by one.

Type


Type is just a plain old jass type like handle, integer, widget
etc. There are two reasons we have a typed bytecode. The first one is once
again because we use hashtables to store everything and the
hashtable API has us use the correct native. The second reason is that we avoid
redundancy in our instruction set (only one Add-Instruction etc.).

Register


The Instructionset used in JHCR is a register based instructionset which follows
from our usage of hashtables since we can have effectivly unlimited registers.
This especially aids in the code-generation phase since we don't have to care
about register allocation (an NP-Hard Problem). It actually has some more
benefits of which i might speak about in a later post.

But even though we have more or less unlimited registers we still have to follow
some rules, which result in the calling convention used in JHCR. To explore this
let's have a look at a small example.

JASS:
native print_integer takes integer x returns nothing

function add takes integer a, integer b returns integer
    return a+b
endfunction

function main takes nothing returns nothing
    local integer r = add(1, 3)
    call print_integer(r)
endfunction

This snippet could be compiled to this bytecode:

Code:
fun 123 add
add integer 0 1 2
ret integer

fun 234 main
lit integer -1 1
lit integer -2 3
bind integer 1 -1
bind integer 2 -2
call 1 123 add
bind integer 1 1
call -3 567 print_integer
ret nothing

Let's look at the add function first. From this function we can see that
the parameters to a function are stored in registers 1 to n where n is the
amount of parameters a function takes. We can also see that the return value
is stored in register 0. And this is our calling convention.
So let's now look at the main function. From that we can see a few things
aswell: first of all we have to use the bind instruction to transfer values
from local "local" registers to the registers of the to-be-called function.
So unlike registers found in your CPU we have them scoped for each function.
The second thing we can notice is that temporary values are assigned to
"negative" registers -1, -2, etc. As i said before, this makes compiling just
a bit more easy and it doesn't cost us much since we can use the "endless"
hashtables.
But we can also see that local variables also use positive registers. In fact
they use registers n+1 .. n+m, where m is the amount of local variables
declared inside that function.
And we can see how the call instruction is used since the return value
is directly copied into the register of our local variable:
Code:
call 1 123 add
     ^  ^   ^
     |  |   |
     |  |   +- The Name of the function is ommited in the final bytecode but
     |  |   `- it's useful for debugging.
     |  |
     |  `- Just the internal id of the function to-be-called
     |
     +- The Interpreter takes whatever is stored in the called functions
     +- register 0 and puts it into this functions register 1 which is the
     +- the local variable r. Technically the transfer of data actually
     +- happens in when interpreting the ret instruction since we lack the
     `- type information in the call instruction.




Question to the reader: how do you do local arrays with this setup?

Label


To achieve loops and conditions and all that good stuff we use labels and jump
instructions. We actually don't have many at all: Label, Jmp, JmpT.
The Label instruction just creates the point in a list of instructions under
that "label". Do note that that label is static, so you can't create dynamic
labels to jump to. Once interpreted the interpreter simply ignores any label
instruction. The Jmp instruction on the other hand just jumps to the
corresponding label instruction. The following snippet is an infinite loop.

Code:
label 3
jmp 3

Now finally the JmpT instruction jumps to the label if and only if the value
stored under the register is true. For an example look at the code below.
That code is an if-statement compiled to our bytecode where the condition
is stored in register -1.
Code:
jmpt 2 -1
<else branch>
jmp 3
label 2
<if branch>
label 3



Name


The Name datatype is mostly ignored because we assign unique ids to everything
we can. There is only one case where we actually have to use an actual name.
Can you guess?



If we reload the script with a totaly new function that wasn't seen before,
that new function ofcourse also gets an id but to link up the name to the new
id in our already running map we have to transfer both. And we need that
mapping for ExecuteFunc only (i think).


fin


Now that we looked at the datatype representing the bytecode we can guess how
many of those instructions actually work. But as you might have guessed this
is still only a fairly high-level description of what JHCR is doing so i hope
i can shed some light on the nitty-gritty details in a later post.
 
Level 13
Joined
Nov 7, 2014
Messages
571
Question to the reader: how do you do local arrays with this setup?
It seems that local arrays use JASS_MAX_ARRAY_SIZE of "register space":
JASS:
local integer x // reg: 1
local integer array ys // reg: 2
local integer z // reg: 2+JASS_MAX_ARRAY_SIZE

To achieve loops and conditions and all that good stuff we use labels and jump
instructions.
I am not sure if it would be problematic if the jump instructions use relative jump offsets instead of labels.
JASS:
// with labels
jmpt 2 -1
<else branch>
jmp 3
label 2
<if branch>
label 3

// with relative offsets
jmpt <size-of-jmpt+size-of-else-branch> -1
<else branch>
jmp <size-of-jmp+size-of-if-branch> // included in the size-of-else-branch
<if branch>
That way the runtime doesn't have to "manage" labels, right?

There is only one case where we actually have to use an actual name.
...
for ExecuteFunc only (i think).
There's also this crazy thing in Jass called 'TriggerRegisterVariableEvent' which triggers when a global real variable is modified. Please don't add support for it.
 

LeP

LeP

Level 13
Joined
Feb 13, 2008
Messages
539
PS: just noticed that jhcr.exe is ~2.7MiB packed, becomes ~15MiB unpacked O.O! With great power comes great file size, I guess =)

That's mostly due to it being a Haskell program. They just take more space.

It seems that local arrays use JASS_MAX_ARRAY_SIZE of "register space":
JASS:
local integer x // reg: 1
local integer array ys // reg: 2
local integer z // reg: 2+JASS_MAX_ARRAY_SIZE

Correct.
JASS:
function a takes nothing returns integer
        local integer array x
        local integer i = 3
        return x[i]
endfunction

Code:
$ jhcr compile empty-common.j test.j --opt-asm
fun 2 a
lit integer 32769 3
gla integer 0 1 32769
ret integer

Here we see that the local variable i has register 32769 because the array x takes the space before it.

I am not sure if it would be problematic if the jump instructions use relative jump offsets instead of labels.
JASS:
// with labels
jmpt 2 -1
<else branch>
jmp 3
label 2
<if branch>
label 3

// with relative offsets
jmpt <size-of-jmpt+size-of-else-branch> -1
<else branch>
jmp <size-of-jmp+size-of-if-branch> // included in the size-of-else-branch
<if branch>
That way the runtime doesn't have to "manage" labels, right?

The thing is that instructions are stored in a linked list so relative offsets don't gain us much since we can't index them directly anyways. With named labels atleast the compiler is a bit more easy since we don't have to compute offsets. But if we'd used another architecture relative offsets ofc could be better.
Edit: Especially with the optimisations i do on the instructions i would have to recompute those offsets.


There's also this crazy thing in Jass called 'TriggerRegisterVariableEvent' which triggers when a global real variable is modified. Please don't add support for it.

:))
Let's just say TriggerRegisterVariableEvent already works for non-hot loaded globals. If they were actually demand for using it on newly defined globals it wouldn't be too bad i think to add it.
 
Last edited:
Level 13
Joined
Nov 7, 2014
Messages
571
Got it to work (was forgetting to initialize globals (not calling 'JHCR_Interpreter_exec_globals'), and 'jhcr_war3map.j' and 'final.j' weren't completely in sync), but I am getting some unexpected timings. The slowdown seems to be ~270x O.O! It's sort of the opposite of that movie in which time goes faster in each successive level, i.e execution speed gets slower with each level of interpreter nesting =).
 

Attachments

  • vanilla-vs-jhcr.zip
    81.4 KB · Views: 54
  • Like
Reactions: LeP

LeP

LeP

Level 13
Joined
Feb 13, 2008
Messages
539
Got it to work (was forgetting to initialize globals (not calling 'JHCR_Interpreter_exec_globals'), and 'jhcr_war3map.j' and 'final.j' weren't completely in sync), but I am getting some unexpected timings. The slowdown seems to be ~270x O.O! It's sort of the opposite of that movie in which time goes faster in each successive level, i.e execution speed gets slower with each level of interpreter nesting =).
Pretty cool! I don't know if the 270x slowdown would be the same for real wc3 but atleast that's one hard number we can use. And tbh i don't even know how to judge it. Is it high for what it does? Could it be reduced by a magnitude?
 
Level 13
Joined
Nov 7, 2014
Messages
571
I don't know if the 270x slowdown would be the same for real wc3
Yeah, it shouldn't be as dramatic because hashtable functions in wc3 are natives.

Is it high for what it does? Could it be reduced by a magnitude?
¯\_(ツ)_/¯

I was looking (despite not having a clue about Haskell) in 'Jass/Opt/Rewrite/SomeRules.hs' and the stuff there seemed like a very terse way of doing tree rewriting. But then I tried (jhcr compile empty-common.j main.j --opt-asm):
JASS:
// main.j
globals
    constant integer C = 1000
endglobals

function f1 takes nothing returns integer
    local integer a = 1234 + 5678 + C
    return a
endfunction

and got this:
Code:
fun 1 f1
lit integer -3 1234
lit integer -4 5678
add integer -2 -3 -4
gg integer -5 1
add integer 0 -2 -5
ret integer

I guess I was expecting something more like this (i.e constant folding):
Code:
fun 1 f1
lit integer 1 7912
set integer 0 1
ret integer

My thinking was "if there is tree rewriting, why isn't there constant folding o.0?".
 

LeP

LeP

Level 13
Joined
Feb 13, 2008
Messages
539
Yeah, it shouldn't be as dramatic because hashtable functions in wc3 are natives.


¯\_(ツ)_/¯

I was looking (despite not having a clue about Haskell) in 'Jass/Opt/Rewrite/SomeRules.hs' and the stuff there seemed like a very terse way of doing tree rewriting. But then I tried (jhcr compile empty-common.j main.j --opt-asm):
JASS:
// main.j
globals
    constant integer C = 1000
endglobals

function f1 takes nothing returns integer
    local integer a = 1234 + 5678 + C
    return a
endfunction

and got this:
Code:
fun 1 f1
lit integer -3 1234
lit integer -4 5678
add integer -2 -3 -4
gg integer -5 1
add integer 0 -2 -5
ret integer

I guess I was expecting something more like this (i.e constant folding):
Code:
fun 1 f1
lit integer 1 7912
set integer 0 1
ret integer

My thinking was "if there is tree rewriting, why isn't there constant folding o.0?".

Well you have to consider the tradeoffs of more optimisations. While having nice
flow-based optimizer would be sweet it would also increase loading times which
this project tries to minimize. The instruction rewrite engine is probably the
bigger improver in contrast to the tree rewrite engine. With the instruction
rewrite engine we can produce lesser quality asm but with an easy implementation
but have the rewrite engine clean up our mess.
And this is important because we have real usecases with it coming directly
from the compiler. And while constant propagation ofc comes up in real life code
i wager it doesn't cause the big slowdowns in wc3/jass.
An optimisation which i think would actually improve JHCR is some loop-independet
code moving (and lit-instruction simplification).

Btw. a simple constant folder (based on AST, not asm) is actually very easy,
but the keyword here is simple. To catch all the cases you would expect
it to catch is actually not quite as easy.
 
Level 13
Joined
Nov 7, 2014
Messages
571
It seems that 'jhcr' generates incorrect bytecode for 'and' expressions, it also generates pretty good bytecode for nested if statements, and that's a bit ironic =):

JASS:
function B1 takes nothing returns boolean
    call BJDebugMsg("B1()")
    return false
endfunction

function B2 takes nothing returns boolean
    call BJDebugMsg("B2()")
    return true
endfunction

function B3 takes nothing returns boolean
    call BJDebugMsg("B3()")
    return true
endfunction

function B4 takes nothing returns boolean
    call BJDebugMsg("B4()")
    return true
endfunction

function foo takes nothing returns boolean
    return B1() and B2() and B3() and B4()
endfunction

function bar takes nothing returns boolean
    local boolean b = false
    if B1() then
    if B2() then
    if B3() then
    if B4() then
        set b = true
    endif
    endif
    endif
    endif
    return b
endfunction
 
Level 13
Joined
Nov 7, 2014
Messages
571
Thanks! Should be fixed here.
Tried with the 'jhcr-1.29.zip' and common.j (1.26, 1.29 and 1.31) but in-game (running 1.31) it I am getting an error (when the map gets selected). Previously I was using 'jhcr-126b-128.zip' (v. git-c50f36) with common.j-1.26 (again running 1.31).

I still think the bytecode generated by the 'nested ifs' is better than that of the 'and's:
Code:
fun 5 foo
call -5 1 B1
not -6 -5
jmpt 4 -6
call -5 2 B2
label 4
set boolean -3 -5
not -4 -3
jmpt 3 -4
call -3 3 B3
label 3
set boolean -1 -3
not -2 -1
jmpt 2 -2
call -1 4 B4
label 2
set boolean 0 -1
ret boolean

When 'B1()' returns false the above executes a "cascade of falseness", while the 'nested ifs' jumps out =).
 

LeP

LeP

Level 13
Joined
Feb 13, 2008
Messages
539
Tried with the 'jhcr-1.29.zip' and common.j (1.26, 1.29 and 1.31) but in-game (running 1.31) it I am getting an error (when the map gets selected). Previously I was using 'jhcr-126b-128.zip' (v. git-c50f36) with common.j-1.26 (again running 1.31).
The mistake was that i included framehandle handing in table for 1.29 when they only appeared in 1.3x. Unfortunately i don't have a modern version of WC3 installed so i'm actually dependent on posts in here (and i'm thankful for them).


I still think the bytecode generated by the 'nested ifs' is better than that of the 'and's:
Code:
fun 5 foo
call -5 1 B1
not -6 -5
jmpt 4 -6
call -5 2 B2
label 4
set boolean -3 -5
not -4 -3
jmpt 3 -4
call -3 3 B3
label 3
set boolean -1 -3
not -2 -1
jmpt 2 -2
call -1 4 B4
label 2
set boolean 0 -1
ret boolean

When 'B1()' returns false the above executes a "cascade of falseness", while the 'nested ifs' jumps out =).

While not as good but a small improvement would be to group those ands to the right like this B1() and (B2() and (B3() and B4())).
You could even write a rewrite rule for that
Code:
mkRR ["a", "b","c"] "a and b and c" "a and (b and c)"
 
Level 13
Joined
Nov 7, 2014
Messages
571
The mistake was that i included framehandle handing in table for 1.29 when they only appeared in 1.3x.
Yeah, it works now with common.j-1.26.

Unfortunately i don't have a modern version of WC3 installed so i'm actually dependent on posts in here (and i'm thankful for them).
Have you tried this (not very modern though)?

While not as good but a small improvement would be to group those ands to the right like this B1() and (B2() and (B3() and B4())).
Hm... the order of evaluation is different. Well... actually it isn't =).

I'll be trying 'or' expressions next (to see if there are any bugs lurking there =)).

Edit: 'or' expressions seem fine (although they do enter in a 'cascade of trueness'):

JASS:
function B1 takes nothing returns boolean
    return false
endfunction

function B2 takes nothing returns boolean
    return false
endfunction

function B3 takes nothing returns boolean
    return false
endfunction

function B4 takes nothing returns boolean
    return false
endfunction

function foo takes nothing returns boolean
    // return B1() or B2() or B3() or B4()
    // =>
    // fun 5 foo
    // call -3 1 B1
    // jmpt 4 -3
    // call -3 2 B2
    // label 4
    // set boolean -2 -3
    // jmpt 3 -2
    // call -2 3 B3
    // label 3
    // set boolean -1 -2
    // jmpt 2 -1
    // call -1 4 B4
    // label 2
    // set boolean 0 -1
    // ret boolean

    // local boolean b = true
    // if B1() then
    // elseif B2() then
    // elseif B3() then
    // elseif B4() then
    // else
    //     set b = false
    // endif
    // return b
    // =>
    // fun 5 foo
    // lit boolean 1 true
    // call -2 1 B1
    // jmpt 2 -2
    // call -3 2 B2
    // jmpt 4 -3
    // call -4 3 B3
    // jmpt 6 -4
    // call -5 4 B4
    // jmpt 8 -5
    // lit boolean 1 false
    // jmp 9
    // label 8
    // label 9
    // jmp 7
    // label 6
    // label 7
    // jmp 5
    // label 4
    // label 5
    // jmp 3
    // label 2
    // label 3
    // set boolean 0 1
    // ret boolean
endfunction

The 'or' emulation I tried with 'elseif's starts up well but then ends with a strange jump cascade.

Edit2:
My "quest" for the optimized (but a bit verbose) 'or' is now complete:
JASS:
function foo takes nothing returns boolean
    local boolean b = true
    loop
    exitwhen B1()
    exitwhen B2()
    exitwhen B3()
    exitwhen B4()
    set b = false
    exitwhen true
    endloop
    return b
endfunction

Code:
fun 5 foo
lit boolean 1 true
label 2
call -2 1 B1
jmpt 3 -2
call -3 2 B2
jmpt 3 -3
call -4 3 B3
jmpt 3 -4
call -5 4 B4
jmpt 3 -5
lit boolean 1 false
lit boolean -7 true
label 3
set boolean 0 1
ret boolean
 
Last edited:

LeP

LeP

Level 13
Joined
Feb 13, 2008
Messages
539
Small update for jhcr to not die on debug statements.
I also provide both 1.31 as latest pre-reforged patch and 1.32.9 for the latest patch (as of writing).
Pls tell me if/what other patches you're using (if any).
 

LeP

LeP

Level 13
Joined
Feb 13, 2008
Messages
539
If anyone is interested i wrote another blog post about JHCR. You can find all the links in the first post.
 
Level 1
Joined
Oct 6, 2021
Messages
3
Using GUI triggers with JHCR

It is possible to use GUI triggers and JHCR because GUI triggers are really just Jass generated by the World Editor. It's not as easy and convenient as using [v]Jass directly, because there's some clicking involved, but it is possible.


Our setup uses 3 ".bat" (windows cmd.exe/shell) scripts. Also these 3 tools are required/invoked by the ".bat" scripts:
* JHCR (obviously) by LeP
* MPQEditor by zezula
* close-map-handle-war3

Our setup uses the following directory structure:
Code:
path\to\test-map-directory
|
|- 1-run-after-save-map-before-game-is-running.bat
|- 2-run-once-after-map-loads-ingame.bat
|- 3-run-after-save-map-while-game-is-running.bat
|- test-map.w3x
|- map-tools
    |
    |- jhcr.exe
    |- MPQEditor.exe
    |- close-map-handle-war3.exe
    |- files
        |
        |- Blizzard.j
        |- common.j

"path\to\test-map-directory" is an arbitrary directory.
"1-run-after-save-map-before-game-is-running.bat", "2-run-once-after-map-loads-ingame.bat", "3-run-after-save-map-while-game-is-running.bat" are the 3 ".bat" scripts.
"test-map.w3x" is the name we use for our test map.


We need to edit the "1-run-after-save-map-before-game-is-running.bat" file. Specifically we need to edit the path to "Warcraft III.exe" and the path to "test-map.w3x".

We don't have to edit the "2-run-once-after-map-loads-ingame.bat" file.

We need to edit the "3-run-after-save-map-while-game-is-running.bat" file. Specifically we need to edit the "--preload-path" (unless your user name is also Aniki).


We can now start the World Editor and create a new map.

We need to add the mandatory trigger to initialize JHCR, (by calling ExecuteFunc("JHCR_Init_init")).
jhcr-init-png.320625


We also need a way to tell JHCR that we've made changes to our map, and we would like these changes to take effect. We can do it with a trigger similar to this one:
jhcr-update-png.320627


Now we can save our map as "path\to\test-map-directory\test-map.w3x". Then we run the "1-run-after-save-map-before-game-is-running.bat" script (from the console, or clicking on it). It should start Warcraft III and load our test map.

Our test map doesn't do anything at the moment, so we need to close the game and make it do something. We could add a hero and a trigger that changes its armor by some amount X when we press the escape key. After we add the "something", we again save the map and run the "1-run-after-save-map-before-game-is-running.bat". After the map loads in-game we also run the "2-run-once-after-map-loads-ingame.bat".

Now instead of closing the game, changing the triggers and then restarting the game we could instead: make changes to our trigger (say change X to X + 1), save the map, run the "3-run-after-save-map-while-game-is-running.bat", in-game: press the escape key (or however we've decided to call ExecuteFunc("JHCR_Init_parse")), and observe our change.


The attached zip file has the ".bat" scripts, "map-tools", and the necessary directory structure.
Hello, I use version 1.27 of Warcraft 3. I follow your steps, but it can't be realized. It can run in the first bat file, but it doesn't print any messages. Can version 1.27 of Warcraft 3 be implemented? Can you do a video demonstration? I'm willing to pay for it. thank you.
 

LeP

LeP

Level 13
Joined
Feb 13, 2008
Messages
539
With the help of our chinese friends (moonlightss & haroun) i made jhcr work with patches 1.24b to 1.28 (roughly).
I think the chinese specifically use 1.27 so consider that the most well tested. Of course it comes with some caveats: the amount of code that can be reloaded is drastically decreased. And those older patches had a lower execution limit. To be honest i don't know how much that matters in practice but don't say i didn't warn you.
And you need to set your local files registry key for older patches.
And the path where wc3 stores the Preload-Data is different for those older patches.

But while bringing jhcr backwards in time we also found some bugs in current jhcr. So i have brought them in line aswell and updated the first post.
 
Last edited:

LeP

LeP

Level 13
Joined
Feb 13, 2008
Messages
539
Hello I tried to run JCHR with Wurst following this tutorial.
After running the command wurst: Run a Wurst map with Jass Hot Code Reload (JHCR), wc3 launches but not with the test map, I ended up on the game menu, being offline. If you have any idea why, I'd like some help, thank you.
Hard to say from afar but that sounds like wrong jass code for some definition of wrong. For example it might be that the common.j differ between what JHCR was compiled with and what you/wurst are providing. See the first post for some hints. A bug in JHCR code generation is another possibility. I would need your war3map.j file to have a closer look. If you're willing to provide that we can take it to private messages or matrix.
 

LeP

LeP

Level 13
Joined
Feb 13, 2008
Messages
539
I have been using this recently again and according to the jass.db Preloader is bugged atm such that only the first code change will be loaded into the game. So until this bug is fixed jhcr will simply create JHCR-0.txt, JHCR-1.txt, … and so forth. You are free to delete them between runs.
While using this extensivly the last couple of weeks i also identified some small bugs which i also fixed. See first post for downloads.

Also i cannot test this under windows (which i assume most of you are using), so if the exes don't run at all for example you have to report that to me.
It works perfectly under MacOS fwiw.
 
Last edited:

LeP

LeP

Level 13
Joined
Feb 13, 2008
Messages
539
So far jhcr had used, depending on the patch, either SetPlayerName or BlzSetAbilityTooltip to get the data from the Preload files into the running map. This lead to more or less restricting limits on how much data could be reloaded. But then i was told that using gamecache works in Preload aswell. And i'm quite happy with that as this removes both the limit and the distinction between the patches. Now of course you are still bound by the jass op limit (especially in older patches), but you're only limited by the jass op limit.
Otherwise there were a few bugs fixed aswell.

Updated files are in the OP as always.

whoops there is currently an error, removed files from OP for now

Updated OP again with the bug hopefully fixed.
Erroneous commit (jhcr -h displays commit) was affd6b5c536cff858d37f990e897ec5153b0ac50, new commit is 45d537a992a064a7d4617a408a914ba0309d2e5e.
 
Last edited:
Level 6
Joined
Jan 12, 2011
Messages
110
So far jhcr had used, depending on the patch, either SetPlayerName or BlzSetAbilityTooltip to get the data from the Preload files into the running map. This lead to more or less restricting limits on how much data could be reloaded. But then i was told that using gamecache works in Preload aswell. And i'm quite happy with that as this removes both the limit and the distinction between the patches. Now of course you are still bound by the jass op limit (especially in older patches), but you're only limited by the jass op limit.
Otherwise there were a few bugs fixed aswell.

Updated files are in the OP as always.

whoops there is currently an error, removed files from OP for now

Updated OP again with the bug hopefully fixed.
Erroneous commit (jhcr -h displays commit) was affd6b5c536cff858d37f990e897ec5153b0ac50, new commit is 45d537a992a064a7d4617a408a914ba0309d2e5e.
I'll test again on our massive codebase, see if it finally works for us, thanks for the update.
 
Level 23
Joined
Jan 1, 2009
Messages
1,608
I implemented a bunch of fixes and changes to better support JHCR in the latest Wurst update. It now works properly with larger scripts and wurst.build config data.
@Donach @Jaccouille you could try it again now, your issues should hopefully be resolved.
However, the latest JHCR version in the OP has some bugs. For now, you would need to use an older version or wait for @LeP to hopefully fix them soon. I used jhcr - #124 which worked better in my tests than the latest version.

Cheers 🍻
 

LeP

LeP

Level 13
Joined
Feb 13, 2008
Messages
539
Updated the OP with the newest version.
This is a bit of a bigger update so please report any kind of errors you get.
The most important fix is of course what frotty says above. But i also added a simple API and some sequence numbers so that game and jchr should stay in sync w/o human caution. You can read more on both in the readme.
 
Top