Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
A compiler to allow hot code reload in WarCraft 3. This means you can update
your map script and see the changes in a running game without restarting it.
Do note though that this is alpha software. Expect bugs and make backups.
This works without memhack.
Compiler usage
The compiler has two commands: init and update. The first command you execute
has to be the init command. The init command expects atleast three arguments:
Path to common.j, path to Blizzard.j and path to your maps war3map.j .
If your map was compiled by jasshelper you should use the --jasshelper flag
for the compiler to work correctly.
Again, if you've used jasshelper to compile your map, pass the --jasshelper flag.
The update command will create a file called JHCR.txt in the path you've specified.
As this tool operates only on the maps script and not on the map itself you
have to extract and insert the war3map.j yourself. Personally i use zezulas
mpqeditor via the command line.
For example this script could be used to create the map tmp.w3x from the orignal
map test.w3x:
You can download the appropiate file below here. It is very important for jhcr that the common.j file that was used while building jhcr to match the common.j you use for developing the map.
So here are the md5 hashsums for the common.j files i used:
It's a multi step process. init takes the map script and injects the runtime and transforms the map script in such a way that the runtime can work with it. update looks which functions have changed since the last update or init and compiles those to a simple language. You can look at the generated code in human readable form by passing the --asm flag to update. This code is then loaded into the map via preload and interpreted by the runtime.
My concern would be if it uses some sort of 'clever game mechanics' in order to function, meaning a bugfix from blizzard could render it useless.
This really seems mind blowing as I cannot imagine how to update I'd imagine that updating the map's script file does not make reload it into memory.
So since I cannot imagine how to do it, I kinda think there is some questionable method at play.
My best guess is that the program does some memory editing, but then I do not see the point of using custom scripts to use the system.
Needless to say I do not understand x)
My concern would be if it uses some sort of 'clever game mechanics' in order to function, meaning a bugfix from blizzard could render it useless.
This really seems mind blowing as I cannot imagine how to update I'd imagine that updating the map's script file does not make reload it into memory.
So since I cannot imagine how to do it, I kinda think there is some questionable method at play.
My best guess is that the program does some memory editing, but then I do not see the point of using custom scripts to use the system.
Needless to say I do not understand x)
You could assume that i know better what i wrote than you))
You can look at the generated map script and search for memory hack stuff. I even developed this on 1.30.4. Also i never said it updates the maps script file.
I would advise you to reread my original post, as it really explains how it's done.
But if you've got other questions i'm happy to answer.
Unless Blizzard does something stupid like limiting the amount of nested ifs or something this should just continue to work with any coming patch as it really just is plain jass.
Also please give code arrays Blizzard. That would reduce the output of this by so so much.
By the sounds of LeP's explanation what it does is compile JASS into some custom language, a special form of bytecode, which then gets fed into a realtime interpreter. The bytecode is read using preload like save/load codes are and the interpreter changes to using the new code when an update is applied. The underlying JASS and JASS bytecode is not modified.
This is pretty awesome. It took me some time to set things up but I am pretty sure that hot reloading is faster than exiting the game, rebuilding the map and starting the game again.
I am not sure how many xs of overhead it is to run a bytecode vm (Lep) in a slow bytecode vm (Jass) but... hey it works!
The setup I ended up with (more or less):
Code:
// init
clijasshelper.exe --scriptonly --debug common.j blizzard.j main.j war3map.j
jhcr.exe init common.j blizzard.j war3map.j --jasshelper
clijasshelper.exe common.j blizzard.j jhcr_war3map.j test.w3x
// start map
"Warcraft III.exe" -window -loadfile test.w3x
// should we 'update' after starting the map?
loop
// update
clijasshelper.exe --scriptonly --debug common.j blizzard.j main.j war3map.j
jhcr.exe update war3map.j --jasshelper --preload-path "C:\Users\lep\Documents\Warcraft III\CustomMapData\
// This step can get annoying... maybe an optional jhcr.exe flag can be set
// to send an Esc key to Warcraft III.exe's window automatically?
// I guess this would require some Windows shenanigans.
//
call ExecuteFunc("JHCR_Init_parse") // e.g by pressing the Esc key
// make changes to main.j etc., then trigger an update
endloop
This is pretty awesome. It took me some time to set things up but I am pretty sure that hot reloading is faster than exiting the game, rebuilding the map and starting the game again.
I am not sure how many xs of overhead it is to run a bytecode vm (Lep) in a slow bytecode vm (Jass) but... hey it works!
The setup I ended up with (more or less):
Code:
// init
clijasshelper.exe --scriptonly --debug common.j blizzard.j main.j war3map.j
jhcr.exe init common.j blizzard.j war3map.j --jasshelper
clijasshelper.exe common.j blizzard.j jhcr_war3map.j test.w3x
// start map
"Warcraft III.exe" -window -loadfile test.w3x
// should we 'update' after starting the map?
loop
// update
clijasshelper.exe --scriptonly --debug common.j blizzard.j main.j war3map.j
jhcr.exe update war3map.j --jasshelper --preload-path "C:\Users\lep\Documents\Warcraft III\CustomMapData\
// This step can get annoying... maybe an optional jhcr.exe flag can be set
// to send an Esc key to Warcraft III.exe's window automatically?
// I guess this would require some Windows shenanigans.
//
call ExecuteFunc("JHCR_Init_parse") // e.g by pressing the Esc key
// make changes to main.j etc., then trigger an update
endloop
Speed seems OK to me; in my albeit small tests i reloaded a timer function which was executed 64 times a second. i also designed everything in such a way that it would be rather fast to execute (ofc there is always room to improve).
No need to directly update after init. in fact that would probably result in empty bytecode.
just run update when you changed something in your code.
also i don't plan on adding automatic reload on the warcraft side. you could probably use autohotkey or something after calling jhcr update. but i dont know about that stuff.
It seems that adding a global variable (or modifing its initial value) is not reflected after an "update".
On the other hand, it seems that new functions can be declared (functions that were not present at "init" time).
It's kind of hard to tell but I don't think integers come before the other types in the dispatch of the "Set/GetGlobal/Array" instructions. I think integers are the most common type.
PS: The productivity boost from this tool, scriptwise, is nuts! Most importantly, I think it brings back the fun of scripting!
It seems that adding a global variable (or modifing its initial value) is not reflected after an "update".
On the other hand, it seems that new functions can be declared (functions that were not present at "init" time).
It's kind of hard to tell but I don't think integers come before the other types in the dispatch of the "Set/GetGlobal/Array" instructions. I think integers are the most common type.
PS: The productivity boost from this tool, scriptwise, is nuts! Most importantly, I think it brings back the fun of scripting!
Yes, the current limitations are that you can define and use new globals but they're initialized to 0/0.0/""/null. Also you can define up to 100 new functions and use them except in ExecuteFunc. I plan on fixing both.
Also the reason i wrote this tool is exactly the fast iteration process when developing; always restarting maps costs so much time. And the first successfull reload was quite magical.
I don't think changing integers id to come first would result in any meaningfull performance boost but i will keep it in mind.
Most likely due to some optimization issues with how Warcraft III loads data. There is no other reason why a map should take more than 15 seconds to load in 2019.
private function on_reload takes nothing returns nothing
call writeln("Reload OK")
// call UnitAddItemById(g_unit, 'bspd') // not ok
call UnitAddItemById(g_unit, 0x62737064) // ok
endfunction
Negation:
JASS:
private function on_reload takes nothing returns nothing
local integer a
local integer b
call writeln("Reload OK")
set a = 0
set b = -48 // 0 - 48 seems ok
call writeln("a: " + I2S(a))
set a = a - b
call writeln("a: " + I2S(a))
set a = a - b
call writeln("a: " + I2S(a))
endfunction
Button stops working after an update (I have no idea what goes wrong):
JASS:
private function draw_loop takes nothing returns nothing
local integer x
local integer y
local integer w
local integer h
local Bd_Mouse m
local Bd_Mbtn lmb
set m = Bd_Mouse(0)
set lmb = m.lmb
call bd_draw_begin(0)
call bdw_auto_wid_base(1000)
set w = 256
set h = 64
set x = 0 - 1024
set y = 768
if bdw_text_button("click me", x, y, w, h, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, false, lmb) then
call writeln("A") // change "A" to "B" then trigger an "update"
endif
call bd_draw_end()
endfunction
private function on_start takes nothing returns nothing
call TimerStart(CreateTimer(), 1.0/10.0, true, function draw_loop)
endfunction
PS: Sending the Esc key to Warcraft III's window automatically requires some Windows minutia but doesn't seem that bad:
private function on_reload takes nothing returns nothing
call writeln("Reload OK")
// call UnitAddItemById(g_unit, 'bspd') // not ok
call UnitAddItemById(g_unit, 0x62737064) // ok
endfunction
Negation:
JASS:
private function on_reload takes nothing returns nothing
local integer a
local integer b
call writeln("Reload OK")
set a = 0
set b = -48 // 0 - 48 seems ok
call writeln("a: " + I2S(a))
set a = a - b
call writeln("a: " + I2S(a))
set a = a - b
call writeln("a: " + I2S(a))
endfunction
Button stops working after an update (I have no idea what goes wrong):
JASS:
private function draw_loop takes nothing returns nothing
local integer x
local integer y
local integer w
local integer h
local Bd_Mouse m
local Bd_Mbtn lmb
set m = Bd_Mouse(0)
set lmb = m.lmb
call bd_draw_begin(0)
call bdw_auto_wid_base(1000)
set w = 256
set h = 64
set x = 0 - 1024
set y = 768
if bdw_text_button("click me", x, y, w, h, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, false, lmb) then
call writeln("A") // change "A" to "B" then trigger an "update"
endif
call bd_draw_end()
endfunction
private function on_start takes nothing returns nothing
call TimerStart(CreateTimer(), 1.0/10.0, true, function draw_loop)
endfunction
PS: Sending the Esc key to Warcraft III's window automatically requires some Windows minutia but doesn't seem that bad:
Really appreciate your feedback. The first two bugs are already fixed. Uploaded to first post.
For the third, could you upload a map which i can just drop into WorldEditor and save there without jhcr already injected? Or the exact clijasshelper commands i guess.
Sorry, can't reproduce. Works for me (after adding init and reload trigger).
So i don't really know how to progress. Try downloading the new version, although from what i can see your code doesn't use the former bugged rawcodes and negate. Try printing a message before calling ExecuteFunc("JHCR_Init_parse") to make sure it really is called. Have you maybe made other changes than just printing something different?
Hm. I added some debug printing in bdw_text_button. Before an update the value of g_Wid stays 1001 (as it should be). After an update the value starts to be incremented by 1 on each draw_loop call.
JASS:
function bdw_text_button takes string text, integer x, integer y, integer w, integer h, integer br, integer bg, integer bb, integer ba, integer tr, integer tg, integer tb, integer ta, boolean eye_candy, Bd_Mbtn btn returns boolean
local integer p = bd_p
local integer wi = next_auto_wid(p)
local boolean clicked = false
local integer xx = x
local integer yy = y
local integer ww = w
local integer hh = h
local string s = ""
if g_Wi == 0 then
if btn.down and is_point_in_rect(btn.down_x, btn.down_y, x, y, w, h) then
set g_Wi = wi
call player_disable_drag_select(p)
endif
endif
if btn.up then
set s = "U"
else
set s = "D"
endif
call writeln("p: " + I2S(p) + ", wi: " + I2S(g_Wi) + ", g_Wi: " + I2S(g_Wi) + ", btn: " + s + ", g_Wid: " + I2S(g_Wid))
if wi == g_Wi then
if btn.up then
set g_Wi = 0
call player_enable_drag_select(p)
if is_point_in_rect(btn.up_x, btn.up_y, x, y, w, h) then
set clicked = true
endif
endif
if eye_candy then
set xx = R2I(xx + 0.05*ww)
set yy = R2I(yy + 0.05*hh)
set ww = R2I(ww - 0.1*ww)
set hh = R2I(hh - 0.1*hh)
endif
endif
call bd_rect(xx, yy, ww, hh, 2, br, bg, bb, ba)
call bd_text_centered(text, xx, yy, ww, hh, tr, tg, tb, ta)
return clicked
endfunction
This seems like a bug as well
JASS:
private function on_start takes nothing returns nothing
local code fn = function draw_loop
call TimerStart(CreateTimer(), 1.0/24.0, true, fn)
endfunction
function JHCR_lep__on_start takes nothing returns nothing
local integer fn= function lep__draw_loop
call TimerStart(CreateTimer(), ( 1.0 / 24.0 ), true, JHCR_Wrap_i2code(fn))
endfunction
Edit:
Adding the two dummy lines in this function seems to fix the problem =)?
JASS:
function bdw_auto_wid_base takes integer base returns nothing
local integer no_inline_1
local integer no_inline_2
set g_Wid[bd_p] = base
endfunction
Hm. I added some debug printing in bdw_text_button. Before an update the value of g_Wid stays 1001 (as it should be). After an update the value starts to be incremented by 1 on each draw_loop call.
JASS:
function bdw_text_button takes string text, integer x, integer y, integer w, integer h, integer br, integer bg, integer bb, integer ba, integer tr, integer tg, integer tb, integer ta, boolean eye_candy, Bd_Mbtn btn returns boolean
local integer p = bd_p
local integer wi = next_auto_wid(p)
local boolean clicked = false
local integer xx = x
local integer yy = y
local integer ww = w
local integer hh = h
local string s = ""
if g_Wi == 0 then
if btn.down and is_point_in_rect(btn.down_x, btn.down_y, x, y, w, h) then
set g_Wi = wi
call player_disable_drag_select(p)
endif
endif
if btn.up then
set s = "U"
else
set s = "D"
endif
call writeln("p: " + I2S(p) + ", wi: " + I2S(g_Wi) + ", g_Wi: " + I2S(g_Wi) + ", btn: " + s + ", g_Wid: " + I2S(g_Wid))
if wi == g_Wi then
if btn.up then
set g_Wi = 0
call player_enable_drag_select(p)
if is_point_in_rect(btn.up_x, btn.up_y, x, y, w, h) then
set clicked = true
endif
endif
if eye_candy then
set xx = R2I(xx + 0.05*ww)
set yy = R2I(yy + 0.05*hh)
set ww = R2I(ww - 0.1*ww)
set hh = R2I(hh - 0.1*hh)
endif
endif
call bd_rect(xx, yy, ww, hh, 2, br, bg, bb, ba)
call bd_text_centered(text, xx, yy, ww, hh, tr, tg, tb, ta)
return clicked
endfunction
I've attached the exact main.j I used for testing (it only depends on the library files from before).
At map init time the draw_loop function looks like this:
JASS:
private function draw_loop takes nothing returns nothing
local integer a
local integer x
local integer y
local integer w
local integer h
local integer dx
local integer dy
local integer font_size
local real scale
local Bd_Mouse m
local Bd_Mbtn lmb
local Bd_Mbtn mmb
local Bd_Mbtn rmb
local boolean any_changed
set m = Bd_Mouse(0)
set lmb = m.lmb
set mmb = m.mmb
set rmb = m.rmb
set font_size = 35
call bd_draw_begin(0)
call bdw_auto_wid_base(1000)
set w = 256
set h = 64
set x = -1024
set y = 768
set dx = 48
set dy = -128
set any_changed = false
// a lot of commented out lines
// a lot of commented out lines
// a lot of commented out lines
// ...
call bd_draw_end()
endfunction
I load the map right after an init (the draw_loop function at this point looks like the above).
Without changing anything I trigger an update and I get:
Code:
Updating function entrypoint__on_chat_input
Updating function ascii__chr_init
Updating function ascii__ord_init
Updating function baddraw__img_list_clear
Updating function baddraw__cache_string_bytes
Updating function bdw_slider
Updating function bdw_vscrollbar
Updating function unitsliderstuff__delayed_init
Updating function main
Writing bytecode
// many many lines of asm
I trigger an update (again without changing anything) and I get:
Code:
Writing bytecode
Writing state file
Ok.
// no asm
Now I uncomment the button and trigger an update:
JASS:
if bdw_text_button("click me", x, y, w, h, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, false, lmb) then
call writeln("foo")
endif
In-game, I can still only see the button, but not the sliders.
My guess is that the instructions for the function aren't properly updated by the runtime or something...
I load the map again right after an init (the draw_loop function starts with "everything" commented out, as before).
This time I don't trigger an update without changing anything.
I immediately uncomment the button and trigger an update, I get:
Code:
Updating function entrypoint__on_chat_input
Updating function ascii__chr_init
Updating function ascii__ord_init
Updating function baddraw__img_list_clear
Updating function baddraw__cache_string_bytes
Updating function bdw_slider
Updating function bdw_vscrollbar
Updating function unitsliderstuff__draw_loop
Updating function unitsliderstuff__delayed_init
Updating function main
Writing bytecode
// many many lines of asm
In-game, I don't see the button.
I add a dummy writeln after the button and trigger an update:
JASS:
if bdw_text_button("click me", x, y, w, h, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, false, lmb) then
call writeln("foo")
endif
call writeln("ABCD")
@Aniki
I have updated the first post with a new version.
I don't know what caused updating all the functions even if nothing had changed but it seems to be fixed now. Also i don't know how you compile your map but atleast with the way i do it your script seems to be working for me; can add/remove sliders and button.
I think the problem was that you've hit the limits on preload. It was six abilities with 500 bytes per ability. Now i've upped the limits to 24 abilities with 700bytes per ability. Also it should now report an error if you hit the limit.
Also nice widget library, i like it.
If you still have errors please give an exact description on how to compile your map.
Oh yes, don't know how i missed that. My compiler should replace all function literals with integer literals. If pjass doesn't accept the resulting .j-file it's a bug.
I don't know what caused updating all the functions even if nothing had changed but it seems to be fixed now. Also i don't know how you compile your map but atleast with the way i do it your script seems to be working for me; can add/remove sliders and button.
I was 'init'ing using jasshelper's '--debug' flag but 'update'ing without it. In 'non-debug'/'release' mode jasshelper does some inlining which Jhcr picked up as changes... sorry about that. Everything seems to work now. Thanks!
I think it still not too difficult to reach the reloadable bytecode limit though:
You've reached the limit of reloadable bytecode
CallStack (from HasCallStack):
error, called at Main.hs:247:30 in main:Main
The ridiculously long argument lists though... I am going to use structs to pack some of the arguments of some of the functions.
I can't help but notice (with the in-game command '/fps') the "slight" fps drop when the switch to running bytecode happens (in the 'draw_loop' function with the sliders). My guess is that I won't be able to do many many draw calls at the same time (ignoring the 'reloadable bytecode limit'). Can't really complain though, because it's pretty much impossible to write anything fast in Jass, let alone a VM!
Well at one point we would reach the oplimit again. Also it would be nice to know the exact limit on BlzSetAbilityTooltip because at one point it simply crashes wc3. I have reduced the encoded bytecode a bit already but nothing huge.
Also it now displays that error better. I just wanted to release a version for people to test.
I can't help but notice (with the in-game command '/fps') the "slight" fps drop when the switch to running bytecode happens (in the 'draw_loop' function with the sliders). My guess is that I won't be able to do many many draw calls at the same time (ignoring the 'reloadable bytecode limit'). Can't really complain though, because it's pretty much impossible to write anything fast in Jass, let alone a VM!
Personally i don't see much fps drops but it's in the nature of this to have a performance hit.
Also the resulting bytecode is very naively generated. One could def write an optimizer for it. It just isn't a priority right now.
As you may or may not have noticed, i have regularly uploaded new versions. Most of them were just bug fixes but two changes are of note:
1. ExecuteFunc now works correctly (with old or new functions)
2. globals are now correctly initialized. Once. That means only the value of the global when it is first loaded into the map is saved. After that the interpreter takes the value saved in the state.
TriggerRegisterVariableEvent doesn't work on newly defined globals yet though.
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
"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")).
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:
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.
Unless it breaks something that is essential and what the --jasshelper flag does. Since jasshelper inserts a random number of underscores but we need stable names for jhcr to work correctly.
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
"path\to\test-map-directory" is an arbitrary directory.
"0-run-after-save-map-before-game-is-running.bat", "1-run-before-save-map-while-game-is-running.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 "0-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 "1-run-before-save-map-while-game-is-running.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")).
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:
Now we can save our map as "path\to\test-map-directory\test-map.w3x". Then we run the "0-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 "0-run-after-save-map-before-game-is-running.bat".
Now instead of closing the game, changing the triggers and then restarting the game we could instead: run the "1-run-before-save-map-while-game-is-running.bat", 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.
Thanks for the guide and batch scripts (also i haven't looked at them yet).
We also have a guide for Wurst.
I would really enjoy if more people would be using this.
Oh sure, i didn't know people wanted it.
Here is jhcr for 1.31 just for you.
Included in this are also a bunch of optimisations for the generated bytecode, so you're able to load a bit more code into the map.
I have updated jhcr in the main post for patch 1.31. There was a bug in which reload didnt work. I think it was related to the now changed stuff in the next ptr of zero based indexing in natives as i use BlzSetAbilityTooltip to get the data into the game. I don't know if i have to revert that change once this ptr becomes the next official patch.
Just wanted to update the attached exe since i fixed some bugs since last release.
And you don't have to call JHCR_Init_init anymore; it's now handled automatically but it also doesn't cause any harm.
Some bugs that were fixed:
Code:
Fixed bug in local array handling
Correctly shadow globals
There is no reason for it to not work, since JASS is supported in reforged as it is in the original client.
If it doesn't work for you, then specify *what* isn't working.
I simply don't get ppl just writing "doesn't work" without any explanation. What do you expect to happen from that?
Thanks. I don't have access to reforge but i assume it is because they modified common.j
Unfortunately i have to compile the program for each different common.j
There is no reason for it to not work, since JASS is supported in reforged as it is in the original client.
If it doesn't work for you, then specify *what* isn't working.
I simply don't get ppl just writing "doesn't work" without any explanation. What do you expect to happen from that?
There is no reason for it to not work, since JASS is supported in reforged as it is in the original client.
If it doesn't work for you, then specify *what* isn't working.
I simply don't get ppl just writing "doesn't work" without any explanation. What do you expect to happen from that?
Sorry, i think the author already know this issue.
I'm using wusrt and hot-reload command form vscode, there is no compilation or runtime error with jhcr but nothing happend when press ESC key(only print debug message). In W3 classic, everything work fine!
Sorry, i think the author already know this issue.
I'm using wusrt and hot-reload command form vscode, there is no compilation or runtime error with jhcr but nothing happend when press ESC key(only print debug message). In W3 classic, everything work fine!
Updated first post to make jhcr compatible with patch 1.32
If you encouter any bugs please do tell me since i don't want to run 1.32 myself.
But i only added the two new types (minimapicon and commandbuttoneffect) so it shouldn't cause any trouble.
Well i wrote down how to use this in the first post but i will try to break it down a bit more.
First of all: this is a command-line application so would run it from some kind of console prompt.
Also it doesn't work with the map itself but only with the mapscript so you would have to extract and re-insert the mapscript. We can use ladiks mpq-editor to do this.
So to get started we should prepare the map like this:
Code:
# copy the our map to some temporary file for safety reasons
cp our-map.w3x tmp.w3x
# then we extract the maps script file
MPQEditor extract tmp.w3x war3map.j
# actually run jhcr for the init phase
# you need the common.j and Blizzard.j files in the same directory as your map
# this step produces the file jhcr_war3map.j
jhcr init common.j Blizzard.j war3map.j --jasshelper
# now we insert jhcr_war3map.j as war3map.j into tmp.w3x
MPQEditor add tmp.w3x jhcr_war3map.j war3map.j
# now we can start the map. but we copy it again to make sure we can later overwrite tmp.w3x again
# the path is ofcourse different for most of you
# also i don't know if -loadfile still works, might have changed with later patches
cp tmp.w3x run.w3x
/path/to/WarCraft\ 3.exe -loadfile run.w3x
The above steps are neccessary when you freshly start the map.
Now you edit your map in the worldeditor and at some point press save.
If you didn't have any syntax errors you can now reload the changes to the script for example as follows:
Code:
# copy our map again to tmp.w3x
cp our-map.w3x tmp.w3x
# extract the new script
MPQEditor extract tmp.w3x war3map.j
# now we use jhcr update instead of jhcr init
jhcr update war3map.j --jasshelper --preload-path='/path/to/Documents/Warcarft\ III/CustomMapData/'
If all went well you can now reload the changes in your map (however you set it up. see first post).
This is actually quite close to my setup. I run pjass over jhcr_war3map.j to make doubly sure that it should actually work and i use a Makefile to seperate those steps. But except that it's mostly the same.
----------
To be honest with you i don't know if this still works with the latest patch as i don't have it installed. There shouldn't be any inherent flaw why it shouldn't work anymore but small things might need to be fixed. If it doesn't work for you i am glad to fix it if you provide enough guidance.
Hello, i also have a problem with JHCR.exe.
I'm using Wurst as described on this Website here: https://wurstlang.org/.
I installed Java 1.8.0 and enabled the Wurst VSCode Extension. Naturally, i installed WurstSetup.jar, used the command <java -jar WurstSetup.jar install wurstscript> and added the wurst folder to the environment variables. To generate the Project i used <grill generate <projectName>> on the command line. Hope I didn't forget something.
Now, I tried the wurst-extension command which runs the projects standard wurst map example in it. This will start Wc3 and load the map as expected. There are no compilation errors, Wurst is succesfully converted to Jass. Looks good, until i stumbled over the hot-reloading section of the website. It links to this post with your JHCR.exe. I downloaded and set it up accordingly, meaning I added following to the settings.json in the projects root like described there:
"wurst.jhrcExe": "C:\\<Path>\\jhcr.exe" and
"wurst.customMapDataPath": "C:\\<Path>\Warcraft III"
Finally, i pressed F1 in VSCode (to run the command provided by wurst to compile the map with potential hot-reloading) and watched the show. Unfortunately, it failed and the wurst extension only throws the same exception over and over again. It states practically nothing to me, only that the process failed "when running external tool":
An exception was thrown when running the map:
java.io.IOException: Failure when running external tool.
Source: Wurst language support (Extension).
This is all. I can't figure out what exactly fails in there. I would really appreciate it if you were to look after or even fix this. It may as well be just an user error, i.e. my fault. But I can only use what is documented on the website, as this thread dosn't cover jhrc combined with a generated wurst-project with VSCode. At least that is how i understood it, did not read everything here, sorry if that is not the case.
Edit: I have the newest Warcraft 3 version installed.
Hello, i also have a problem with JHCR.exe.
I'm using Wurst as described on this Website here: https://wurstlang.org/.
I installed Java 1.8.0 and enabled the Wurst VSCode Extension. Naturally, i installed WurstSetup.jar, used the command <java -jar WurstSetup.jar install wurstscript> and added the wurst folder to the environment variables. To generate the Project i used <grill generate <projectName>> on the command line. Hope I didn't forget something.
Now, I tried the wurst-extension command which runs the projects standard wurst map example in it. This will start Wc3 and load the map as expected. There are no compilation errors, Wurst is succesfully converted to Jass. Looks good, until i stumbled over the hot-reloading section of the website. It links to this post with your JHCR.exe. I downloaded and set it up accordingly, meaning I added following to the settings.json in the projects root like described there:
"wurst.jhrcExe": "C:\\<Path>\\jhcr.exe" and
"wurst.customMapDataPath": "C:\\<Path>\Warcraft III"
Finally, i pressed F1 in VSCode (to run the command provided by wurst to compile the map with potential hot-reloading) and watched the show. Unfortunately, it failed and the wurst extension only throws the same exception over and over again. It states practically nothing to me, only that the process failed "when running external tool":
An exception was thrown when running the map:
java.io.IOException: Failure when running external tool.
Source: Wurst language support (Extension).
This is all. I can't figure out what exactly fails in there. I would really appreciate it if you were to look after or even fix this. It may as well be just an user error, i.e. my fault. But I can only use what is documented on the website, as this thread dosn't cover jhrc combined with a generated wurst-project with VSCode. At least that is how i understood it, did not read everything here, sorry if that is not the case.
Edit: I have the newest Warcraft 3 version installed.
Personally i don't use much wurst so i don't know if they have fixed this but in the past they didn't provide the error message of jhcr so i can't tell from afar what's going wrong. Normally wurst has more problems with updating the code and not with the initial compile step so there might be an interesting error at play here. The best course of action is to reach out to me on our channel #inwc.de-maps on quakenet.org (in reasonable europe hours).
The problem could be that i haven't compiled jhcr with the newest common.j as i think they added some new natives with the newest patch?
In any case i need the jass file fed to jhcr.
I was thinking about writing some blog-like posts to explain how JHCR actually works.
This first one will give a high-level overview. If you like this and want me to write
more please tell me.
The Problem
Modifying WarCraft 3 maps has a long iteration period due to closing WC3,
changing your code and restarting WC3. JHCR (Jass hot code reload) tries to
improve this cycle by replacing the slow actions of closing and starting WC3
with a hopefully faster compiler which pushes your code changes directly into
an already running WC3.
But how could something like that even work? The answer is multi faceted.
So let's start with some very basic mockups.
The most simple Idea
Let's say we want to reload a single function, how would that look like in code?
JASS:
function foo takes nothing returns nothing
if function_was_reloaded("foo") then
// somehow execute the new body
// call BJDebugMsg("New Foo")
else
// old body of foo
call BJDebugMsg("Foo")
endif
endfunction
While this has many holes it's conceptually correct already. But for the sake
of easy implementation we split up the function into two functions, the original
function but with a new name and a new function but with the old name:
JASS:
function JHCR_foo takes nothing returns nothing
call BJDebugMsg("Foo")
endfunction
function foo takes nothing returns nothing
if function_was_reloaded("foo") then
// somehow execute the new body
// call BJDebugMsg("New Foo")
else
call JHCR_foo()
endif
endfunction
This way we don't have to change other functions calling foo when we update it.
Everything from a simple call-statement over trigger-actions to such things as ExecuteFunc still work just fine.
The harder part is now how to actually execute the new body of foo.
To achieve this JHCR provides both a compiler to- and an interpreter for a
custom bytecode language which will be superficially introduced in the next
section.
The Bytecode
The bytecode was carefully designed to strike a balance between size, parsing
speed and execution speed as all of these factors play a big role in our limited
WC3 environment.
For example the original foo would be translated to this bytecode:
Code:
fun 3 foo
lit string -2 Foo
bind string 1 -2
call -1 2 BJDebugMsg
ret nothing
But this is of course way too verbose and also not very fast to parse.
So this format is only used for human consumption; to communicate with WC3
JHCR provides another format for this exact bytecode which looks like this:
I've annotated the different parts to show the opcode they represent.
This is way easier to parse programatically and takes up less space.
Every bytecode uses a fixed number and we encode numbers in a fixed width
with added padding to achieve a good tradeoff between size and parsing speed.
For fast parsing we use the fact that S2I("123asdf") == S2I(123) so we can move
in well defined chunks over the input string.
There are plenty more opcodes ofcourse but we'll save them for a future post.
Integrating
As we've seen previously we took special care to preserve the original name
of our function but this was mostly to integrate our system into the maps script.
Now we will talk about integrating the maps script into JHCR.
Functions
Take the aboves bytecode as an example: we call the BJ-function BJDebugMsg in
there. How do we actually achieve this?
As JHCR takes both common.j and Blizzard.j at compile time we assign each
function an unique id (BJDebugMsg has id 2 in our example). Now, to be able to
call this function we generate a huge if-then-else block for each id at compile
time to call out to different functions. If we limit ourself to only three functions,
that is DisplayTimedTextToPlayer, BJDebugMsg and foo we can look at the code
JHCR generates below.
JASS:
function JHCR_Auto_call_predefined takes integer JHCR_reg,integer JHCR_i,integer JHCR_ctx returns nothing
if (JHCR_i < 2) then
call DisplayTimedTextToPlayer (JHCR_Table_get_player (JHCR_Context_bindings[JHCR_ctx],1),JHCR_Table_get_real (JHCR_Context_bindings[JHCR_ctx],2),JHCR_Table_get_real (JHCR_Context_bindings[JHCR_ctx],3),JHCR_Table_get_real (JHCR_Context_bindings[JHCR_ctx],4),JHCR_Table_get_string (JHCR_Context_bindings[JHCR_ctx],5))
else
if (JHCR_i < 3) then
call BJDebugMsg (JHCR_Table_get_string (JHCR_Context_bindings[JHCR_ctx],1))
else
call foo ()
endif
endif
endfunction
This is ofcourse quite a mouthfull but it all follows the same pattern.
The first parameter is the register the return-value of the called function is
stored in. but since all our functions return nothing it wont be used here.
The next parameter is the id of the function we want to call. As you can see
we do a bunch of comparisons to get to the correct function.
The next parameter is a struct called context in which we store a bunch of
information for the interpreter, most importantly here the parameters we want
passed to the function we want to call.
Those parameters are passed via the bind opcode which you can see just before
the call opcode in our small example above.
Globals
We also need to be able to read and write globals from our interpreter and we
deploy a similar technique as we did for functions.
For each available type in JASS (handle, integer, unit, etc.) we generate
two functions to read and write a specific global variable of that type.
As with functions we assign each global variable a per-type unique id on which
we do a bunch of comparisons to set or read the correct value.
Let's again only look at a very small input script with only two global variables bj_MAX_PLAYERS and bj_forLoopAIndex where bj_MAX_PLAYERS is declared constant.
JHCR will generate the following functions:
JASS:
function JHCR_Auto_get_global_integer takes integer JHCR_i returns integer
if (JHCR_i < 2) then
return bj_MAX_PLAYERS
else
return bj_forLoopAIndex
endif
endfunction
function JHCR_Auto_set_global_integer takes integer JHCR_i,integer JHCR_v returns nothing
if (JHCR_i < 2) then
return
else
set bj_forLoopAIndex = JHCR_v
endif
endfunction
Note the then-case in JHCR_Auto_set_global_integer: since bj_MAX_PLAYERS is
constant we actually can't set it. But both can of course be read.
A similar approach is used for global arrays.
Update
To actually reload changed functions JHCR read the new script file and checks
for each function if the hash of the function has changed. If that happens
the bytecode like above is created and written to a file to be loaded by the Preload-native. For example the changed foo function would generate the
following preload-file:
The generated bytecode is of course very similar to the previously shown since
we only changed one string literal after all.
Now what happens with this next is topic of a future post.
We will end this little post here even though it only scratches the surface of
what JHCR has to do.
I hope you enjoyed this quick overview of JHCR.
This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
By continuing to use this site, you are consenting to our use of cookies.