• 🏆 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!

Bridging GUI with vJass - Initialization Issues

Status
Not open for further replies.

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
Hi all.

I've been working on a project getting all my major GUI systems compatible with vJass without breaking ANY of the GUI compatibility that they are known for. Stuff like Table just cannot be ported to GUI, but the rest certainly can.

However, there's been a pretty big hurdle I've been trying to work against, which is to do with the initialization priorities in GUI VS what JassHelper compiles.

Problem 1: GUI InitTrigs run after ALL vJass systems have initialized. If a system like GUI Unit Event initializes from a vJass initializer, any preplaced units will not fire off any of the "UnitIndexEvent" triggers that GUI might use.

Problem 2: vJass Initializes before the "InitGlobals" line is called. This means that if I use a GUI Configuration trigger (like in Damage Engine or Spell System) from a vJass initializer, all of those configurable udg_ values will be overwritten (not to mention the gg_trg values will also be overwritten, because they haven't been created yet).

Possible solution to problem 1) Force GUI users to use an interface that requires them to run their triggers manually from a bulk trigger. A very annoying solution which doesn't addess Problem 2 at all.

Possible solution to problem 2) Force vJass users to call to a function that basically makes them "wait" until the GUI stuff has initialized. This has the problem of timing - should I then call trigger from an InitTrig, or from a Map Initialization trigger? If it's the former, it's prone to errors with problem 1. But if it's the latter, any Map Initialization triggers might not have their shit together yet (in case they depend on those systems).

So I've found a weird "solution" to both problems, by using the "inject" feature with replacing every possible GUI Initialization function call wrapped in "ExecuteFunc" so as to not cause syntax errors. I then force the initialization to cooperate with both GUI and vJass as well as possible. This then introduces, however, a problem where InitTrigs will initialize before vJass - but this isn't a problem in GUI - only in custom vanilla JASS script. The solution is for any JASS initializer using an InitTrig to just switch to a scope/struct/library initializer.

So, here's what I've got (along with a cute description):

JASS:
    // The next lines of code exist because vJass initialization is an absolute pain in the ass
    // So this overwrites all calls in "main" instead of adding to them, so I have to use ExecuteFunc
    // I'm pretty sure I covered all the possible initializers which would be added to "main"
    // Any settings done in the "Map Properties - Options" will be overwritten, but there is a way to save them
    // Contact Bribe @ Hiveworkshop.com if you need any help or run into issues implementing this into your map.
    //! inject main
    call ExecuteFunc("InitSounds")
    call ExecuteFunc("CreateRegions")
    call ExecuteFunc("CreateCameras")
    call ExecuteFunc("CreateAllDestructables")
    call ExecuteFunc("CreateAllUnits")
    call ExecuteFunc("CreateAllItems")
    call ExecuteFunc("InitUpgrades")
    call ExecuteFunc("InitTechTree")
    call InitBlizzard()
    call ExecuteFunc("InitGlobals")
    call ExecuteFunc("InitCustomTriggers")
    //! dovjassinit
    call ExecuteFunc("RunInitializationTriggers")
    //! endinject

Of course the user has to then extract any kind of Map Options that get killed by the //! inject call, so I'm going to offer mapmakers the self-service of me tooling their Map Options into raw JASS for them to circumvent that issue.
 
Last edited:
Level 18
Joined
Nov 21, 2012
Messages
835
Hey Bribe!
I don't fully understand what you're trying to ahieve. However initialization order is
JASS:
function main takes nothing returns nothing
    call SetCameraBounds(- 3328.0 + GetCameraMargin(CAMERA_MARGIN_LEFT), - 3584.0 + GetCameraMargin(CAMERA_MARGIN_BOTTOM), 3328.0 - GetCameraMargin(CAMERA_MARGIN_RIGHT), 3072.0 - GetCameraMargin(CAMERA_MARGIN_TOP), - 3328.0 + GetCameraMargin(CAMERA_MARGIN_LEFT), 3072.0 - GetCameraMargin(CAMERA_MARGIN_TOP), 3328.0 - GetCameraMargin(CAMERA_MARGIN_RIGHT), - 3584.0 + GetCameraMargin(CAMERA_MARGIN_BOTTOM))
    call SetDayNightModels("Environment\\DNC\\DNCLordaeron\\DNCLordaeronTerrain\\DNCLordaeronTerrain.mdl", "Environment\\DNC\\DNCLordaeron\\DNCLordaeronUnit\\DNCLordaeronUnit.mdl")
    call NewSoundEnvironment("Default")
    call SetAmbientDaySound("LordaeronSummerDay")
    call SetAmbientNightSound("LordaeronSummerNight")
    call SetMapMusic("Music", true, 0)
    call CreateAllUnits() //preplaced units created, vars like gg_unit_Hgam_001 are set up.
    call InitBlizzard()

    call ExecuteFunc("TEST___Init") //library initialization "Init" function

    call InitGlobals() //sets GUI globals acording to "Variables" Ctrl+B
    call InitCustomTriggers() // sets trigger vars: gg_trg_xx, adding actions to the gg_trg_xx trigger
                                // may be triggers with event "UnitIndexEvent"
    call RunInitializationTriggers() //runs trigger actions with GUI event "map initialization"

endfunction

if you want to fire library Init function after RunInitializationTriggers() there's a way (just tested)
JASS:
library LIBTEST initializer Init
   
    private function UnitEventInit2 takes nothing returns nothing
        call BJDebugMsg(".library_proceed=true. "+I2S(R2I(udg_GUI_Real)))//prints "5"
    endfunction
   
    private function RunAtMapInitGUITrigger takes nothing returns nothing
        local timer t=GetExpiredTimer()
        if gg_trg_t4Config==null then
            call BJDebugMsg("waiting..")
        else
            call PauseTimer(t)
            call DestroyTimer(t)
            call UnitEventInit2()
        endif
        set t=null
    endfunction

    private function Init takes nothing returns nothing
        call TimerStart(CreateTimer(), 0.00, true, function RunAtMapInitGUITrigger)
    endfunction
   
endlibrary
t4Config.jpg

maybe 0sec timer without checking if gg_trg exists will work. It didn't print "waiting.." even once for me.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
So the biggest issue is that the InitGlobals and InitTriggers lines are placed after the //! dovjassinit line which is what runs things like your library init. This means using GUI configuration is only possible if one either:

1) uses ExecuteFunc to run the configuration action code

2) waits until the Init code has run, a la Map Initialization (ZiB this runs before the zero second timer has expired).

The issue with option 1 is that the InitGlobals call will later wipe out the GUI settings if called directly from a vJass trigger.

Option 2 has the problem that all vJass Initialization would have to change their code.

I have made GUI Unit Event fully vJass with a GUI Configuration, but it's difficult to figure out when to initialize, because launching UnitIndexEvent before InitTrigs have run means every single one won't fire.

So really a HYBRID solution is needed. Initializing exactly the way vJass indexers do via a phase 1, using ExecuteFunc to configure things like the unit type, but then also running a phase 2 initialization for those GUI triggers or whatever else that missed phase 1.

See how screwed up this all is? JassHelper was never written with the intention of supporting GUI, and goes out of its way to screw it up during Initialization. It's fixable with this //! injectmain, but requires tweaking to get back global fogmodifiers, music, sound, etc. So it puts a lot of probably too complicated work on the map maker, also breaking my philosophy of bare minimum implementation steps.

So ultimately this resource will not be used. I just had to get my thoughts out. I'm doing the leg work to make sure no one else has to screw around with their code. My new releases will be almost entirely backwards compatible, despite changing a lot behind the scenes.

This means:

1. fewer variables clogging up the variable manager.

2. Knowledge of which variables can be accessed in GUI (means no more burying variables with obscure names to keep people away).

3. Unit Event can actually supplant all vJass unit indexers from day 1, meaning not even vJass people have to change their code.

4. Finally GUI and vJass can have the same dependencies without breaking anything.

5. You know I like compatibility. I've made Damage Engine compatible with all other GUI damage systems. I made an AutoEvents and AIDS plugin for Nestharus' Unit Indexer.

6. I'm taking the release very slowly. Whenever I'm conflicted, I stop what I'm doing for a day or 2 and look at it again with a fresh mindset. This may get done in December, or it may get done next year.

7. Whatever improvements come with War3 Reforged, I intend to have everything working for the systems which currently could benefit. We'll see what the future holds.
 
//! inject sounds too painful.

In your case, I'd go with solution 2. vJass users are a lot more used to reading docs and figuring out how to set up a system. Make it easiest for GUI. About "when" to run those triggers--my memory is suuuuper foggy but can't you do something like this?

GUI - Map Initialization
-> start thread to a separate function (e.g. ExecuteFunc or ForForce)
-> in that function, fire some sort of event that vJass users can use to do initial setup

iirc, when an event like map initialization fires, it goes through each trigger in the queue and evaluates it. That queue is blocking, so when you start a new thread, it gets scheduled at the end of that queue. That way you'll know it comes after all map initialization triggers have finished, but without having to use a 0-second timer.

... i could be totally wrong though.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
Any sort of queued wait would actually be tacked on at the 0 second mark. Once the "main" thread completes, the map loading is done and those threads are long gone.

If one does ExecuteFunc, TriggerEvaluate, TriggerExecute or ForForce, it is going to run instantly (that is, holds the current thread in order to complete). The difference with ExecuteFunc/TriggerExecute is that if you do a TriggerSleepAction, it zaps you back to the original thread instead of making all threads wait.

I think this all worked differently in the distant past, but by the time I got into modding (a couple months before joining Hive) things like the returm bug were pretty much done.
 
Level 18
Joined
Nov 21, 2012
Messages
835
Unit Event can actually supplant all vJass unit indexers
Does it mean you will split UnitEvent into 2 parts?
By the way: having pre-placed units indexed as soon as possible will be nice change (at the moment when library Init function runs). And, maybe, firing UnitIndexEvent should happend when all "MapInitialization" triggers executed.

While testing I realized that DeathEvent==1 fires twice (don't know why)
Also it doesn't fire in some circumstances:
killevent.jpg

This peasant won't fire DeathEvent cause UnitEventConfig is executed after.
Do you know a way to detect when all "Map Initialization" triggers ran?

As for //!inject I share Purge's opinion.

edit: If I understand it right: UnitEvent is independant in position where it is placed vs other "Map Init" triggers by:
using Filter for Unit Enters Region, and Unit Issued Order which fires before an event itself.
But this doesnt work for Unit Dies event (as you're using Trigger Condition). Probably that's why DeathEvent does't fire in above example.
 
Last edited:

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
Does it mean you will split UnitEvent into 2 parts?
By the way: having pre-placed units indexed as soon as possible will be nice change (at the moment when library Init function runs). And, maybe, firing UnitIndexEvent should happend when all "MapInitialization" triggers executed.

While testing I realized that DeathEvent==1 fires twice (don't know why)
Also it doesn't fire in some circumstances:
View attachment 310406
This peasant won't fire DeathEvent cause UnitEventConfig is executed after.
Do you know a way to detect when all "Map Initialization" triggers ran?

As for //!inject I share Purge's opinion.

edit: If I understand it right: UnitEvent is independant in position where it is placed vs other "Map Init" triggers by:
using Filter for Unit Enters Region, and Unit Issued Order which fires before an event itself.
But this doesnt work for Unit Dies event (as you're using Trigger Condition). Probably that's why DeathEvent does't fire in above example.

In the trigger you showed you have to use the UnitIndexEvent Equal to 3.00 event. It is in the disclaimer that Map Initialization doesn't guarantee it has run.
 
Status
Not open for further replies.
Top