• 🏆 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] How to do both - optimize Jass Trigger Events and maximize working efficiency?

Level 8
Joined
May 12, 2018
Messages
106
I recently learned that if there are many events, even if they have strict conditions, it can burden to the computation. ( [Mapping] - A comprehensive guide to code and trigger optimization #post-361596 )

But I didn't prioritize computational speed and optimization in-game to improve my work speed and map development progress.
My campaign project could easily experience frame-drop during combats due to the presence of many players in a map, their food max are large(200), and also a lot of custom JASS Spell and effects.

I commonly wrote a script like this. (case; work speed first)
JASS:
scope TransferVitality initializer init
    globals
        private constant integer SpellId = 'A02A'
        private constant real Amount = 10
    endglobals

    private constant function Conditions takes nothing returns boolean
        return GetSpellAbilityId() == SpellId
    endfunction

    private function Actions takes nothing returns nothing
        local unit u = GetSpellAbilityUnit()

        call SetWidgetLife(u, GetWidgetLife(u) - Amount )

        set u = null
    endfunction

    private function init takes nothing returns nothing
        local trigger t = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_SPELL_EFFECT )
        call TriggerAddCondition( t, Condition( function Conditions ) )
        call TriggerAddAction( t, function Actions)
        set t = null
    endfunction
endscope

The problem with this trigger I guess:
1. Because the trigger for this spell is loaded at the map initialization stage, the computation is overloaded and there is a risk of missing other triggers. Essential variables like global integers and strings must be brought first, and these spell triggers for combat must be loaded later.
2. Therefore, units existing in the map with this type event, the more computation is needed

Is my guess correct?

Here are the another spell made using SpellEffectEvent and RegisterPlayerUnitEvent that written by Bribe and Magtheridon96.
( [Snippet] SpellEffectEvent ) ( [Snippet] RegisterPlayerUnitEvent )
JASS:
library DestLockShadowfury requires RegisterPlayerUnitEvent, SpellEffectEvent, PluginSpellEffect, Utilities, TimerUtils

    globals
        private constant integer AID = 'A07J'
        private constant integer AID_DUMMY = 'A608'
        private constant real AOE = 353
        private constant string model = "Abilities\\Spells\\Human\\Thunderclap\\ThunderClapCaster.mdl"
        private constant string model2 = "Abilities\\Spells\\Orc\\WarStomp\\WarStompCaster.mdl"
        private constant real EFFECTHEIGHT = 20
    endglobals

    private struct Shadowfury
 
        private unit source
        private player player
        private integer level

        static method onCast takes nothing returns nothing
            local real x
            local real y
            local unit locust
            local integer i = 0
            local integer size = 0

            local thistype this = thistype.allocate()
            set source = Spell.source.unit
            set player = Spell.source.player
            set x = Spell.x
            set y = Spell.y
            set level = Spell.level

            set locust = CreateUnit(player, 'u003', x, y, dummylife1)
            call UnitApplyTimedLife(locust, 'BTLF', 2)
            call UnitAddAbility( locust, AID_DUMMY)
            call SetUnitAbilityLevel(locust, AID_DUMMY, level)
            call IssuePointOrderById( locust, OrderId_clusterrockets, x, y )
       
            call DestroyEffect(AddSpecialEffectEx(model, x, y, EFFECTHEIGHT, 1 ) )
            call DestroyEffect(AddSpecialEffectEx(model2, x, y, EFFECTHEIGHT, 1 ) )

            set source = null
            set player = null
            set locust = null
            call this.destroy()
        endmethod

        private static method onInit takes nothing returns nothing
            call RegisterSpellEffectEvent(AID, function thistype.onCast)
        endmethod
    endstruct
endlibrary
I presume that this is the following.
call RegisterSpellEffectEvent(AID, function thistype.onCast)
generated event like this accurately identifies the AbilityId, so there is a few risk of computational overload than Generic Unit Event.
However, the method's name, "onInit" , is bothering me. Which seems to be that name makes this library is loaded when the map initializes at once. Can I postpone this to a subordinated process?

I'm looking for a way to work with both optimization and speed of work. Maybe the second method approach is closest to this, but I don't know the exact way, so I need help.
 
Last edited:
Level 8
Joined
May 12, 2018
Messages
106
Additionally, I'm also using Bribe's Damage Engine. It's convenient and fast to work with GUI and JASS, but I guess there will be computational overload problem like the above. ( Damage Engine 5.A.0.0 )

But This Damage Engine is giving me so many useful options that I don't know how to fix and convert this if I have to fix the related triggers for optimization.

In my scripting skills, I failed works without Damage Engine, to trigger to calculate pure amount of damage before damage fluctuations applied by game engine.

JASS:
library NagaUnderseaLordImpalingSpines initializer init requires SpellEffectEvent, RegisterPlayerUnitEvent, Utilities

    globals
        private constant integer AID = 'A0B4'
        private constant integer AIDDUMMY = 'A0B5'
        private constant real PERIOD = 0.05
        private constant real DURATION = 25
        private constant real DAMAGECOUNTER = 250
        private constant string EFFECTMODEL = "Abilities\\Spells\\Undead\\FrostNova\\FrostNovaTarget.mdl"
        private constant real RANGE = 425
        private constant real MISSILESPEED = 1100
        private constant string MISSILEMODEL = "Abilities\\Spells\\NightElf\\FanOfKnives\\FanOfKnivesMissile.mdl"
        private constant real MISSILESIZE = 1.2
        private constant real MISSILECOLLISION = 89
    endglobals
   
    private function GetDamage takes unit source, integer level returns real
        return 12. + ( 12.0 * ( level - 1 ) )
    endfunction
   
    private function DamageFilter0 takes player owner, unit filtered returns boolean
        return UnitAlive(filtered) and IsUnitEnemy(filtered, owner) and not IsUnitHidden(filtered) and not BlzIsUnitInvulnerable(filtered) and GetUnitAbilityLevel(filtered, 'Aloc') < 1 and GetUnitTypeId(filtered) != 'h60C' and GetUnitTypeId(filtered) != 'n60J'
    endfunction

    private struct SpineBolt extends Missiles

        private method onHit takes unit u returns boolean
            if DamageFilter0(owner, u) then
                call UnitDamageTarget(source, u, damage, false, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_UNIVERSAL, null)
                //call GroupAddUnit(gothit, u)
                return true
            endif
            return false
        endmethod

        private method onRemove takes nothing returns nothing
            /*local unit FoG
            loop
                set FoG = FirstOfGroup(gothit)
                exitwhen FoG == null
                call GroupRemoveUnit(gothit, FoG)
                set FoG = null
            endloop*/
        endmethod

    endstruct

    private struct ImpalingSpinesActivation
        static thistype array impaling
       
        timer timer
        integer id
        integer level
        unit source
        player owner
        real count
        real amount
       
        method destroy takes nothing returns nothing
            set impaling[id] = 0
            call UnitRemoveAbility(source, AIDDUMMY)
           
            call ReleaseTimer(timer)
            call deallocate()
           
            set timer = null
            set source = null
            set owner = null
        endmethod
       
        private static method onDeath takes nothing returns nothing
            local integer id = GetUnitUserData(GetTriggerUnit())
            local thistype this
           
            if impaling[id] != 0 then
                set this = impaling[id]
                call destroy()
            endif
        endmethod
       
        static method Loop takes nothing returns nothing
            local thistype this = GetTimerData(GetExpiredTimer())
            local SpineBolt bolt
            local real a = 0
            local real i = 0
            local real x = GetUnitX(source)
            local real y = GetUnitY(source)

            if amount >= DAMAGECOUNTER then
                set amount = amount - DAMAGECOUNTER
                loop
                    exitwhen i >= 15
                        set bolt = SpineBolt.create(x, y, 25, x + RANGE*Cos(a), y + RANGE*Sin(a), 25)
                        set bolt.model = MISSILEMODEL
                        set bolt.scale = MISSILESIZE
                        set bolt.speed = MISSILESPEED
                        set bolt.source = source
                        set bolt.owner = GetOwningPlayer(source)
                        set bolt.collision = MISSILECOLLISION
                        set a = a + 24*bj_DEGTORAD
                        set bolt.damage = GetDamage(source, level)
                       
                        call bolt.launch()
                    set i = i + 1
                endloop
            endif
            if count < DURATION and not udg_IS_GAMEOVER then
                set count = count + PERIOD
            else
                call destroy()
            endif
        endmethod
       
        static method onCast takes nothing returns nothing
            local thistype this
           
            if impaling[Spell.source.id] != 0 then
                set this = impaling[Spell.source.id]
            else
                set this = thistype.allocate()
                set timer = NewTimerEx(this)
                set id = Spell.source.id
                set impaling[id] = this
            endif
            set source = Spell.source.unit
            set owner = Spell.source.player
            set level = Spell.level
            set count = 0
            set amount = 0
            call TimerStart(timer, PERIOD, true, function thistype.Loop)
            if GetUnitAbilityLevel(source, AIDDUMMY) < 1  and not udg_IS_GAMEOVER then
                call UnitAddAbility(source, AIDDUMMY)
                call SetUnitAbilityLevel(source, AIDDUMMY, level)
                call BlzUnitHideAbility( source, AIDDUMMY, true )
            endif
           
        endmethod
   
        private static method onInit takes nothing returns nothing
            call RegisterSpellEffectEvent(AID, function thistype. onCast)
            call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_DEATH, function thistype.onDeath)
        endmethod
    endstruct
   
    private function OnDamage takes nothing returns nothing
        local unit target = udg_DamageEventTarget
        local integer id = GetUnitUserData(target)
        local real damage = udg_DamageEventPrevAmt
        local ImpalingSpinesActivation Imp = ImpalingSpinesActivation.impaling[id]
       
        set Imp.amount = Imp.amount + damage
               
        set target = null
    endfunction
   
    private function Check takes nothing returns boolean
        return GetUnitAbilityLevel(udg_DamageEventTarget, AIDDUMMY) > 0 and not IsUnitIllusion(udg_DamageEventTarget)
    endfunction
   
    private function init takes nothing returns nothing
        local trigger t0 = CreateTrigger(  )
        call TriggerRegisterVariableEvent( t0, "udg_OnDamageEvent", EQUAL, 0.00 )
        call TriggerAddCondition( t0, Condition(function Check) )
        call TriggerAddAction( t0, function OnDamage )
    endfunction
endlibrary

Also, I don't know how to store the amount of damage after applied defense or any other various damage fluctuations without Damage Engine.
Calculating damage after applying armor and any other damage fluctuations
JASS:
scope HighMageFlameShield initializer init
    globals
        private constant integer FLAMESHIELD = 'B01T'
        private constant real DAMAGE_MULTIPLY = 0.5
        private constant string MODEL = "Abilities\\Spells\\Other\\Incinerate\\FireLordDeathExplode.mdl"
        private constant string ATTACH = "origin"
    endglobals
   
    private function Act0 takes nothing returns nothing
        if UnitAlive(udg_DamageEventTarget) then
            call DestroyEffect(AddSpecialEffectTarget(MODEL, udg_DamageEventSource, ATTACH))
            call UnitDamageTarget( udg_DamageEventTarget, udg_DamageEventSource, udg_DamageEventAmount * DAMAGE_MULTIPLY, true, false, udg_CONVERTED_ATTACK_TYPE[udg_ATTACK_TYPE_SPELLS], udg_CONVERTED_DAMAGE_TYPE[udg_DAMAGE_TYPE_UNIVERSAL], null )
        endif
    endfunction
   
    private function Cond0 takes nothing returns boolean
        return GetUnitAbilityLevel( udg_DamageEventTarget, FLAMESHIELD ) > 0
    endfunction
   
    private function init takes nothing returns nothing
        local trigger t = CreateTrigger()
       
        call TriggerRegisterVariableEvent( t, "udg_ArmorDamageEvent", LESS_THAN, 0.00 )
        call TriggerAddCondition( t, Condition(function Cond0) )
        call TriggerAddAction( t, function Act0 )
       
        set t = null
    endfunction
endscope
 
Last edited:
Level 8
Joined
May 12, 2018
Messages
106
I have more questions... sorry for making your eyes pain.
I pre-declared the variables that are common to most of triggers on the map to a library and then placed it at the top of the trigger.
JASS:
library Global
    globals
        constant integer OrderId_dustappear = 852625
        constant integer OrderId_illusion = 852274
        
        constant integer Orb_harvest = 'I00G'
        
        constant integer Abil_NecroDegen = 'A05B'
        constant integer Buff_HlRdceOoF = 'BIhm'
        
        constant integer SUCCUBUS = 'n031'
    endglobals
endlibrary
This 'Global' library has not declared an initializer init and has only declared variables as global, but there is no situation where other triggers cannot read the variables.
If so, is the global variable declaration through "global" together with the map initialization? Does it put a burden on the process at the moment the map is first loaded and executed?

JASS:
library EnumFilters initializer init
    function filter_alive takes nothing returns boolean
        return UnitAlive(GetFilterUnit())
    endfunction
    function filter_dead takes nothing returns boolean
        return ( GetUnitTypeId(GetFilterUnit()) == 0 or IsUnitType(GetFilterUnit(), UNIT_TYPE_DEAD) ) and GetUnitTypeId(GetFilterUnit()) != 'u003' and GetUnitTypeId(GetFilterUnit()) != 'dumi' and GetUnitTypeId(GetFilterUnit()) != 'u00H' and GetUnitTypeId(GetFilterUnit()) != 'u00I' and GetUnitTypeId(GetFilterUnit()) != 'u01Q' and GetUnitTypeId(GetFilterUnit()) != 'u00C' and GetUnitTypeId(GetFilterUnit()) != 'u009' and GetUnitTypeId(GetFilterUnit()) != 'n00P' and GetUnitAbilityLevel(GetFilterUnit(), 'Aloc') <= 0
    endfunction
    function filter_nothidden takes nothing returns boolean
        return not IsUnitHidden(GetFilterUnit()) and GetUnitAbilityLevel(GetFilterUnit(), 'Aloc') <= 0 and GetUnitTypeId(GetFilterUnit()) != 'h60C' and GetUnitTypeId(GetFilterUnit()) != 'n60J'
    endfunction

    function filterG_NdNhid takes nothing returns boolean
        return filter_alive() and filter_nothidden()
    endfunction

    function GetAllTrees takes nothing returns boolean
        if ( ( GetDestructableTypeId(GetFilterDestructable()) == 'LTlt' ) ) then
            return true
        endif
    if ( ( GetDestructableTypeId(GetFilterDestructable()) == 'ATtr' ) ) then
        return true
    endif
    if ( ( GetDestructableTypeId(GetFilterDestructable()) == 'ATtc' ) ) then
        return true
    endif
        return false
    endfunction

    function GrowAllTrees takes nothing returns nothing
        call DestructableRestoreLife( GetEnumDestructable(), GetDestructableMaxLife(GetEnumDestructable()), true )
    endfunction

    function DestroyAllTrees takes nothing returns nothing
        call KillDestructable( GetEnumDestructable() )
    endfunction

    private function init takes nothing returns nothing
        local integer index
        set index = 0
        loop
        // upgraded units
        // Berserker Upgrade - W2 Troll Berserkers
            if (not GetPlayerTechResearched(Player(index), 'R00K', true)) then
                call SetPlayerTechMaxAllowed(Player(index), 'n00K', 0)
            endif
        // W2 Ogre Magi
            if (not GetPlayerTechResearched(Player(index), 'R60J', true)) then
                call SetPlayerTechMaxAllowed(Player(index), 'n013', 0)
            endif
        // Abomination Tech Research
            if (not GetPlayerTechResearched(Player(index), 'R600', true)) then
                call SetPlayerTechMaxAllowed(Player(index), 'u00R', 0)
            endif
        // Demon Infernal Tech Research
            if (not GetPlayerTechResearched(Player(index), 'R010', true)) then
                call SetPlayerTechMaxAllowed(Player(index), 'n032', 0)
            endif
            if (not GetPlayerTechResearched(Player(index), 'R600', true)) then
                call SetPlayerTechMaxAllowed(Player(index), 'u608', 0)
            endif
            if (not GetPlayerTechResearched(Player(index), 'R625', true)) then
                call SetPlayerTechMaxAllowed(Player(index), 'u60G', 0)
            endif
        // W2 Warlock Transform Detection
            call SetPlayerAbilityAvailable(Player(index), 'A07A', false)
        // AdvNecro Transform Detection
            call SetPlayerAbilityAvailable(Player(index), 'A63G', false)
            call SetPlayerTechResearched(Player(index), 'R60M', 1)

            set index = index + 1
            exitwhen index == bj_MAX_PLAYER_SLOTS
        endloop
    endfunction
endlibrary
This library is a collection of many types of Filters used for many triggers (usually in custom spell or cinematics unit group). Is there a way to load them sequentially in the next order instead of the map initialization without causing errors within the trigger?
 

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,564
The problem with this trigger I guess:
1. Because the trigger for this spell is loaded at the map initialization stage, the computation is overloaded and there is a risk of missing other triggers. Essential variables like global integers and strings must be brought first, and these spell triggers for combat must be loaded later.
2. Therefore, units existing in the map with this type event, the more computation is needed
Whenever a unit casts a spell every single trigger that uses the Event "A unit casts an ability" will run. That could be A LOT of triggers in a large map with many custom spells.

A nice optimization here would be to use a system where you have one single "A unit starts the effect of an ability" Trigger + Event and you store your custom spell triggers (or a function) in a table using the SpellAbilityId() as the key. The end result would be something like this:
  • Events
    • Unit - A unit Starts the effect of an ability
  • Conditions
  • Actions
    • Custom script: set udg_SpellId = GetSpellAbilityId()
    • Trigger - Run StartsEffectTrigger[SpellId] (checking conditions)
So this single trigger will control every single spell that uses this Event. Of course you'll need to then Register these custom triggers beforehand, but that's generally how optimizations are made, you rely on information stored in memory at initialization in one big calculation as opposed to making those big calculations on the fly.

To avoid some kind of initialization overload, if that's even a thing, you can setup your map to have an in-game "loading screen". In other words, at Elapsed Game time of say 0.01 seconds, have a trigger initialize everything over the course of the next few seconds:
  • Events
    • Time - Elapsed time is 0.01 seconds
  • Conditions
  • Actions
    • Player - Disable user control for (All players)
    • Set Variable Index = 0
    • For each (Integer A) from 1 to 50 do Actions
      • Loop - Actions
        • Trigger - Run InitTrigger[Index] (ignoring conditions)
        • Trigger - Run InitTrigger[Index + 1] (ignoring conditions)
        • Trigger - Run InitTrigger[Index + 2] (ignoring conditions)
        • Set Variable Index = (Index + 3)
        • Wait 0.10 game-time seconds
    • Player - Enable user control for (All players)
I forget if Waits work in these GUI loops, use a Timer if need be, either way the logic is what's important.

Regarding Damage Engine, it introduces a lot of overhead, but it's hard to give up since it's so useful. For optimizations here you could take the same approach as the above Spell trigger table suggestion and Register your damage events in a similar fashion. Also, you could try to use a more lightweight version of the system, maybe request one from Bribe? Or try to do it yourself, he documented just about everything.
 
Last edited:
Level 8
Joined
May 12, 2018
Messages
106
  • events.gif
    Events
  • unit.gif
    Unit - A unit Starts the effect of an ability
  • cond.gif
    Conditions
  • actions.gif
    Actions
    • join.gif
      page.gif
      Custom script: set udg_SpellId = GetSpellAbilityId()
    • joinbottom.gif
      page.gif
      Trigger - Run StartsEffectTrigger[SpellId] (checking conditions)
Thanks so much always for your replies kind and detailed!
There is a lot of work to be done, first of all I will start with optimizing and integrated managing custom spells.
 
Level 8
Joined
May 12, 2018
Messages
106
I faced with problems with library and struct. I made a simple example of library and struct and ran the test, but it doesn't work at all.

JASS:
library LoadHero
    struct LoadHeroArray
        public static LoadHeroArray array heroarray
        static unit array Hero
        static real array HeroX
        static real array HeroY

        method destroy takes nothing returns nothing
            call SetWidgetLife(Hero[0], 1)
            set Hero[0] = null

            call deallocate()
        endmethod

        static method load takes nothing returns LoadHeroArray
            local LoadHeroArray lh = LoadHeroArray.allocate()

            set lh.heroarray[1] = lh
            //set Hero[0] = gg_unit_Hjai_0002
            set Hero[0] = udg_jaina
            call AddHeroXPSwapped( 200, Hero[0], true )

            return lh
        endmethod
    endstruct
    public function initfunc takes nothing returns nothing
        local LoadHeroArray lh = LoadHeroArray.load
    endfunction
    public function clearfunc takes nothing returns nothing
        local LoadHeroArray lh = LoadHeroArray.heroarray[1]
      
        call lh.destroy()
    endfunction
endlibrary
  • Load Jass Library A
    • Events
      • Player - Player 1 (Red) types a chat message containing load as An exact match
    • Conditions
    • Actions
      • Custom script: call LoadHero_initfunc()
      • Game - Display to (All players) the text: Testing Load
  • Clear Jass Library A
    • Events
      • Player - Player 1 (Red) types a chat message containing clear as An exact match
    • Conditions
    • Actions
      • Custom script: call LoadHero_clearfunc()
      • Game - Display to (All players) the text: Testing Clear
If I type "load" in chat message, it must run 'initfunc' and store Jaina's data in struct and be able to check they works fine by Jaina gaining exp.

If I type "clear", it must run the clearfunc function and remove the instance of that struct, and at the same time, Jaina's HP will become 1. But these doesn't exactly work even in the load phase, and I don't know how to track the instance of the struct exactly. (Clear phase is required elsewhere, variables that are no longer useful, etc.)

This is the knowledge I need to ensure that ability scripts written with SpellEffectEvent can be loaded in the next order rather than onInit.
 

Dr Super Good

Spell Reviewer
Level 64
Joined
Jan 18, 2005
Messages
27,201
the computation is overloaded and there is a risk of missing other triggers.
There is no "risk". Either everything finishes executing on the same update, or it does not, with 100% reliability. If it does not finish then likely a thread crash occurred, either due to hitting the op-limit or a logic error such as division by 0. Correctly working computers are as good as completely reliable no matter how much work you throw at them.

"Overloading" is only really an issue when it comes to scheduling work, especially with real time systems. In the case of overloading a real time system, the real time guarantees cannot be met so it fails to perform as required. In the case of Warcraft III, this usually means skipped frames, frame pacing issues, reduced game speed, e.t.c. The game state itself is not impacted by this due to the engine being deterministic, only the player experience is. If this was a medical ventilator then such an overload to any real time system could result in the machine malfunctioning, putting the person it is attached to at risk, but with a game there is no such risk so they are usually thought of as soft real time systems where failures can happen from time to time without significant consequence.
2. Therefore, units existing in the map with this type event, the more computation is needed
The event only fires when an appropriate unit performs the action. In the example given it would be when any unit starts the effect of a spell. A unit just existing will not generate any overhead with that event.

generated event like this accurately identifies the AbilityId, so there is a few risk of computational overload than Generic Unit Event.
It is my understanding that the system is designed to more efficiently resolve the code to run for a specific ability being cast. Instead of running N events that perform N total checks for N custom abilities (O(N) complexity), it instead uses 1 event which performs 1 check using a mapping data structure for N custom abilities (O(1) complexity). This results in a lot better scaling as 1,000 trigger enhanced abilities would add the same overhead per cast as just 1 ability.
Which seems to be that name makes this library is loaded when the map initializes at once.
This should not be a problem given the trivial nature of the code being run. Thousands of such functions could be run at map initialization without causing a frame to be skipped or even hitting the op limit.
If so, is the global variable declaration through "global" together with the map initialization?
I think they are run in different JASS/Lua threads. I am not aware of global declarations ever causing a thread crash. I have heard of them causing performance issues, but that was when a person was purposely pushing limits to encounter them, such as having hundreds of thousands, or even millions, of global variables.
Does it put a burden on the process at the moment the map is first loaded and executed?
I think the global variable declarations are processed during the map loading screen, when the JASS/Lua script is compiled. I think map initialization happens at the frame transition between the loading screen and gameplay.

I do not think you need to care about the overhead of global declarations. The execution time is likely trivial compared with actually parsing and compiling the JASS script file.

Map initialization should be kept reasonable. I think that if it results in clients freezing for too long it can cause the "waiting for player" dialog to show which is not ideal. Code such as initializing data or registering/linking systems together should be fine as it is fairly trivial to execute. Code that processes a lot of data, or that forces Warcraft III to load in a lot of data or assets, should probably be avoided and deferred to after map initialization and possibly even staggered over a period of time.

Is there a way to load them sequentially in the next order instead of the map initialization without causing errors within the trigger?
The function definitions are loaded at compile time, after which their bytecode just sits inside the virtual machine producing as good as no overhead. This all happens during the loading screen. Functions only require execution time during gameplay if they are called.

Only the "init" function should be called at map initialization. This seems to be configuring players, such as setting limits on specific unit types. This is all fine to run in map initialization as it can be considered trivial, especially at the small scale involved.
So this single trigger will control every single spell that uses this Event. Of course you'll need to then Register these custom triggers beforehand, but that's generally how optimizations are made, you rely on information stored in memory at initialization in one big calculation as opposed to making those big calculations on the fly.
Is the largest JASS array index big enough to reliably support this? I recall 4 character raw type codes resulting in fairly large numbers, in the order of millions, which is far outside the maximum JASS array index even with Reforged.

Lua tables will not have an issue with this. But I think Lua GUI does not use them for arrays and instead uses a special type designed to emulate JASS arrays, possibly with the same maximum index restriction.

The way to implement this in JASS is usually using a hashtable to lookup a trigger value. Hashtables can handle very large and even negative indices without issue.
I forget if Waits work in these GUI loops
Yes they work in loops. It will run through the loop sequentially and then wait some amount of network synchronised real time seconds approximating 0.1 game time seconds, but almost certainly a lot longer, before going onto the next iteration of the loop.

Waits do not work in callback based loops, which GUI confusingly also puts inside the same style loop decorations. These are usually involved in "pick every X in Y" style actions such as with unit groups, player groups, and destructables/items in an area.
I faced with problems with library and struct. I made a simple example of library and struct and ran the test, but it doesn't work at all.
The following line does not make sense.
local LoadHeroArray lh = LoadHeroArray.load
It is my understanding that this should throw a syntax error as you are trying to set a LoadHeroArray instance pointer to a pointer to the LoadHeroArray.load static method. I am guessing that since vJASS treats these both as integers internally it is allowing this nonsense conversion without throwing an error?

You probably want to call the function.
Code:
local LoadHeroArray lh = LoadHeroArray.load()
Since you are not using the return value it could be simplified to.
Code:
call LoadHeroArray.load()
And possibly the static method definition changed so that it returns nothing.
local LoadHeroArray lh = LoadHeroArray.allocate()
This operation and associated operations is pointless? Your struct has no member variables so holds no state information. I am not even sure why your struct exists as all the static arrays could be moved up into the containing library and manipulated directly.

Are you sure you mean for all those arrays to be static? If they are meant to be member variables then you need to define a size due to how vJASS implements member arrays as 2D arrays.

If each instance is meant to hold the data associated with 1 unit then you probably do not mean to have static arrays at all. Instead you would use the struct as an array, with the appropriate vJASS array syntax.
This is the knowledge I need to ensure that ability scripts written with SpellEffectEvent can be loaded in the next order rather than onInit.
How would this help improve your map performance? Anything after the first frame after the loading screen is the result of code running after on map initialization. If you get lag after that time, it is not caused by the "onInit" functions.
 

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,564
Is the largest JASS array index big enough to reliably support this? I recall 4 character raw type codes resulting in fairly large numbers, in the order of millions, which is far outside the maximum JASS array index even with Reforged.
Yeah, you'd probably have to use a Hashtable in GUI to support the larger id's. I just didn't feel like writing any code hence the pseudo-GUI triggers, lol. But I should've made it more clear for others.
 
Last edited:
Level 8
Joined
May 12, 2018
Messages
106
Thank you for your detailed answer!
Currently, I am actively working on optimization using the SpellEffectEvent module.
JASS:
library herowarstomp requires SpellEffectEvent, Utilities
    struct HeroWarStomp
        private static method onCast takes nothing returns nothing
            local unit source = GetSpellAbilityUnit()
            local real x = GetUnitX(source)
            local real y = GetUnitY(source)
            local integer level = GetUnitAbilityLevel(source, 'A60C' )
            local unit locust = CreateUnit(GetOwningPlayer(source), 'u003', x,y, 1.5)
            call UnitApplyTimedLife(locust, 'BTLF', 2)
            call UnitAddAbility( locust, 'A60D' )
            call SetUnitAbilityLevel(locust, 'A60D' , level)
            call IssuePointOrderById( locust, OrderId_clusterrockets, x, y )
     
            set source = null
            set locust = null
        endmethod
 
        static method onCreate takes nothing returns nothing
            call RegisterSpellEffectEvent( 'A60C', function thistype.onCast)
        endmethod
    endstruct
endlibrary
Also, I found out that next priority initialization declarations is possible with structname.methodname(), so I am processing it as follows
  • Load CustomSpell BasicModifying
    • Events
    • Conditions
    • Actions
      • -------- Spell BasicModifying --------
      • Custom script: call HeroThunderClap.onCreate()
      • Custom script: call DoomGuardWarStomp.onCreate()
      • Custom script: call HeroWarStomp.onCreate()
      • Custom script: call CharmExp.onCreate()
      • Custom script: call TranquilityLv3.onCreate()
I also made SpellCastEvent now by refering, copying, and modifying the SpellEffectEvent module.
JASS:
library SpellCastEvent requires RegisterPlayerUnitEvent, optional Table
 
    //============================================================================
    private module M
    
        static if LIBRARY_Table then
            static Table tb
        else
            static hashtable ht = InitHashtable()
        endif
    
        static method onCast takes nothing returns nothing
            static if LIBRARY_Table then
                call TriggerEvaluate(.tb.trigger[GetSpellAbilityId()])
            else
                call TriggerEvaluate(LoadTriggerHandle(.ht, 0, GetSpellAbilityId()))
            endif
        endmethod
  
        private static method onInit takes nothing returns nothing
            static if LIBRARY_Table then
                set .tb = Table.create()
            endif
            call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_SPELL_CAST, function thistype.onCast)
        endmethod
    endmodule
  
    //============================================================================
    private struct S extends array
        implement M
    endstruct
  
    //============================================================================
    function RegisterSpellCastEvent takes integer abil, code onCast returns nothing
        static if LIBRARY_Table then
            if not S.tb.handle.has(abil) then
                set S.tb.trigger[abil] = CreateTrigger()
            endif
            call TriggerAddCondition(S.tb.trigger[abil], Filter(onCast))
        else
            if not HaveSavedHandle(S.ht, 0, abil) then
                call SaveTriggerHandle(S.ht, 0, abil, CreateTrigger())
            endif
            call TriggerAddCondition(LoadTriggerHandle(S.ht, 0, abil), Filter(onCast))
        endif
    endfunction
endlibrary
This was made to correctly identify only specific abilities, to force units to stop immediately when they try to use those abilities outside of my intended situation.
I have very little knowledge of modules and code resources, but this seems to be fine for me now.

The other problems I'm facing now is whether it is possible to optimize and efficiently integrate and manage triggers-written with GUI Unit Event and Damage Engine. I don't know at the moment whether I need a completely new advanced module for JASS skilled people, or if I can make them as accurate as SpellEffectEvent.
 
Last edited:

Dr Super Good

Spell Reviewer
Level 64
Joined
Jan 18, 2005
Messages
27,201
The other problems I'm facing now is whether it is possible to optimize and efficiently integrate and manage triggers-written with GUI Unit Event and Damage Engine. I don't know at the moment whether I need a completely new advanced module for JASS skilled people, or if I can make them as accurate as SpellEffectEvent.
Generally if you have the skill you should work on porting all your abilities and complicated triggers to JASS. Warcraft III GUI is very limiting with the features it offers, and does not play too well or sensibly with custom scripts.

You will want only 1 damage detection/event system in the map. If your map has multiple, this could explain some of the performance issues you are encountering. Units taking damage can be very common events, possibly firing hundreds or even thousands of times a second.
 
Top