Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use gamecache to load data via Preloader #7

Merged
merged 4 commits into from
Sep 10, 2023
Merged

Use gamecache to load data via Preloader #7

merged 4 commits into from
Sep 10, 2023

Conversation

lep
Copy link
Owner

@lep lep commented Sep 4, 2023

Based off of #6
I tested this under patch 1.33. But the FlushGameCache line doesn't work under 1.33. Does it really work under 1.26?

lep added 2 commits September 4, 2023 18:33
This means we use StoreString etc. to do stuff in the Preload file and not that
we actually modifiy a gamecache file directly (allthough that could be fun).
Previously Interpreter#_exec_globals would run until the context became zero
but i don't think this was ever possible as the code generated for globals
never had any kind of `ret` instruction or alike in it. So now we simply
loop until the instruction becomes zero.
@shuen4
Copy link
Contributor

shuen4 commented Sep 4, 2023

my fault
i was expecting FlushGameCache to be similar to FlushStoredInteger to remove all save data types, so i didnt test again after changing it to FlushGameCache
what actually happen:
FlushStoredInteger deletes saved data
FlushGameCache deletes all saved data and makes the handle no longer valid

function onEvent takes nothing returns nothing
    if HaveStoredInteger(GC, "test", "test") then
        call FlushStoredInteger(GC, "test", "test")
        call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 10, "previous onEvent failed")
    else
        call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 10, "previous onEvent successed")
    endif
    call StoreInteger(InitGameCache("test.w3v"), "test", "test", 0)
    set i = i + 1
    if i - i / 2 * 2 == 1 then // i % 2 == 1
        // crash this thread
        call I2S(1 / 0) 
    endif
    call FlushStoredInteger(GC, "test", "test")
endfunction

log:

successed
failed
successed
failed
successed
failed
// repeat N time

after change FlushStoredInteger to FlushGameCache

successed
failed
successed
successed
// repeat N time successed

going to try if adding set GC = InitGameCache("test.w3v") after FlushGameCache(GC) produce same log as using FlushStoredInteger

edit:
it does produce same log as using FlushStoredInteger

@shuen4
Copy link
Contributor

shuen4 commented Sep 4, 2023

test for modify gamecache file on 1.26a:

function init takes nothing returns nothing
    local gamecache GC = InitGameCache("test.w3v")
    call StoreString(GC, "test_missionKey", "test_valueKey", "test_value")
    call SaveGameCache(GC)
    // didnt test if this was necessary
    call ReloadGameCachesFromDisk()
    call BJDebugMsg(GetStoredString(InitGameCache("test.w3v"), "test_missionKey", "test_valueKey")) // test_value
    // copy Warcraft III\Save\Profile*\Campaigns.w3v
    // 1.32+ path (just guessing): Documents\Warcraft III\BattleNet\*\Campaigns\Classic\Campaigns.w3v
    // didnt test this too
    call ReloadGameCachesFromDisk()
    // and this
    set GC = InitGameCache("test.w3v")
    call StoreString(GC, "test_missionKey", "test_valueKey", "test_value123")
    // and this
    call SaveGameCache(GC)
    call BJDebugMsg(GetStoredString(InitGameCache("test.w3v"), "test_missionKey", "test_valueKey")) // test_value123
    // restore copied Campaigns.w3v
    call ReloadGameCachesFromDisk()
    call BJDebugMsg(GetStoredString(InitGameCache("test.w3v"), "test_missionKey", "test_valueKey")) // test_value
endfunction

pros:
no OP limit in Preloader script (wont exceed anyways unless very very huge bytecode generated and interpreter would exceed this OP limit even Preloader script didnt)

cons:
no longer possible for LAN reload
OP limit exceed in JHCR_Init_parse would make partial reload (currently limited by bytecode reload limit (12 or 24 depend on patch) and probably wont cause this)

some features might possible to add if modify gamecache directly (if patch 1.33+ didnt cache gamecache):
auto reload if detected modified (a global variable in map script to track reloaded count and a gamecache data to track how many time file generated and if detected global variable + 1 == gameacache data preform reload)
warn the user if missing intermediate bytecode (global variable != gamecache data && global variable + 1 != gameacache data (currently possible but useless on patch 1.33+ due to Preloader caching))

@shuen4
Copy link
Contributor

shuen4 commented Sep 4, 2023

by using TriggerSleepAction we can reset executed OP count but it may let other trigger running partial reloaded bytecode and passing bad value to nfunction therefore making game crashing

by using ExecuteFunc wont have such issue but it wont simple as using TriggerSleepAction
some code about using ExecuteFunc to repeat a operation that usually would endup OP limit exceeded

// usual code
// OP limit = default
scope test initializer init
globals
    integer i
endglobals
private function test takes nothing returns nothing
    loop
        set i = i + 1
    endloop
endfunction
private function onEvent takes nothing returns nothing
    set i = 0
    call ExecuteFunc(SCOPE_PRIVATE + "test")
    call BJDebugMsg(I2S(i)) // OP limit exceeded  (forgot value but it was around 27000 ~ 30000)
endfunction
private function init takes nothing returns nothing
    local trigger t = CreateTrigger()
    call TriggerRegisterPlayerChatEvent(t, GetLocalPlayer(), "", false)
    call TriggerAddAction(t, function onEvent)
endfunction
endscope

// bypass OP limit
// OP limit = logically infinity but will cause stack overflow if nested too deep
scope test initializer init
globals
    integer i
endglobals
private function test takes nothing returns nothing
    loop
        exitwhen i == 9999999 // 1 more digit war3 will stack overflow due to nested ExecuteFunc
        set i = i + 1
        if i - i / 9374 * 9374 == 0 then // on 300000 opcode limit thread OP limit exceeded at 9375th loop
            call ExecuteFunc(SCOPE_PRIVATE + "test") // change to call AbilityId(SCOPE_PRIVATE + "test") to find the number
        endif
    endloop
endfunction
private function onEvent takes nothing returns nothing
    set i = 0
    call ExecuteFunc(SCOPE_PRIVATE + "test")
    call BJDebugMsg(I2S(i)) // war3 hang ~1s then display 9999999
endfunction
private function init takes nothing returns nothing
    local trigger t = CreateTrigger()
    call TriggerRegisterPlayerChatEvent(t, GetLocalPlayer(), "", false)
    call TriggerAddAction(t, function onEvent)
endfunction
endscope

// bypass a little OP limit
// OP limit = default ^ 2
scope test initializer init
globals
    integer i
endglobals
private function test takes nothing returns nothing
    loop
        set i = i + 1
        exitwhen i == 999999999
    endloop
endfunction
private function onEvent takes nothing returns nothing
    set i = 0
    loop
        exitwhen i == 999999999
        call ExecuteFunc(SCOPE_PRIVATE + "test")
    endloop
    call BJDebugMsg(I2S(i)) // war3 hang 3 min then OP limit exceeded (forgot to check the value)
endfunction
private function init takes nothing returns nothing
    local trigger t = CreateTrigger()
    call TriggerRegisterPlayerChatEvent(t, GetLocalPlayer(), "", false)
    call TriggerAddAction(t, function onEvent)
endfunction
endscope

// bypass a little more OP limit - just testing, the above one should already sufficient
// OP limit = default ^ 3
scope test initializer init
globals
    integer i
endglobals
private function test takes nothing returns nothing
    loop
        exitwhen i == 999999999
        set i = i + 1
    endloop
endfunction
private function test1 takes nothing returns nothing
    loop
        exitwhen i == 999999999
        set i = i + 1
        call ExecuteFunc(SCOPE_PRIVATE + "test")
    endloop
endfunction
private function onEvent takes nothing returns nothing
    set i = 0
    loop
        exitwhen i == 999999999
        call ExecuteFunc(SCOPE_PRIVATE + "test1")
    endloop
    call BJDebugMsg(I2S(i)) // war3 hang 5min then display 999999999
endfunction
private function init takes nothing returns nothing
    local trigger t = CreateTrigger()
    call TriggerRegisterPlayerChatEvent(t, GetLocalPlayer(), "", false)
    call TriggerAddAction(t, function onEvent)
endfunction
endscope

by default 1.26a OP limit is 300000

@lep
Copy link
Owner Author

lep commented Sep 8, 2023

There are many interesting possibilities when using gamecache. The greatest point to me is to remove the dumb difference when compiling jhcr. But also that -- ignoring the op limit -- we can now load bigger chunks without worrying about running out of ability ids.
I'm aware of all the stuff like directly modifying a GC file or techniques to circumvent the op limit. I say one step at a time. Some parts i wrote with re-entrance in mind (i think) but not the reloading itself. I don't think it would be too bad but i haven't taken a closer look yet.
I propose that i merge this as is, that is just using GC to transfer stuff. I tested it under latest bnet patch and you tested it under 1.26a. That's good enough for me right now.

@lep lep merged commit 2c6c32a into master Sep 10, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants