[vJASS] What does Scope initializer do? no need for initializer if scope is for non-MUI spells?

Level 3
Joined
Feb 11, 2024
Messages
22
I recently tried moving on from GUI to vJASS, and learned that scopes are quite useful for making convenient private globals;
not "global unit spell_A_Caster", etc but just "private global unit c" within "scope spell_A".

And then I found out that "TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_CAST)" is very inefficient cuz it fires event for every player.
So I tried to change it to "TriggerRegisterUnitEvent(t,u,EVENT_UNIT_SPELL_EFFECT)" and call the registering function when a Hero is selected. (AOS style map)

Then I realized that I have intializers for current spells' scopes. Initializers require InitTrig, and InitTrig is not compatiable with TriggerRegisterUnitEvent.
So I just removed initializer and scope worked well.

Then what does a initializer do? Is it safe to remove it?

Below are the spell trigger examples, one with initializer and one without, both are performing well (at least in my perspective).

JASS:
scope spell_A initializer InitTrig_spell_A

globals
    private unit u
endglobals

private function spell_A_Conditions takes nothing returns boolean
    if ((GetSpellAbilityId() == 'A001')) then
        set u = GetTriggerUnit()
    endif
    return false
endfunction

private function InitTrig_spell_A takes nothing returns nothing
    local trigger t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition(t, Condition( function spell_A_Conditions ) )
    set t = null
endfunction
endscope

JASS:
scope spell_A

globals
    private unit u
endglobals

private function spell_A_Conditions takes nothing returns boolean
    if GetSpellAbilityId() == 'A001' then
        set u = GetTriggerUnit()
    endif
    return false
endfunction

function spell_A_register takes unit u returns nothing
    local trigger t = CreateTrigger()
    call TriggerRegisterUnitEvent(t,u,EVENT_UNIT_SPELL_EFFECT)
    call TriggerAddCondition(t, Condition( function spell_A_Conditions ) )
    set t = null
endfunction
endscope
// call spell_A_register(u) is called when the hero using this spell is selected.
 
Level 24
Joined
Jun 26, 2020
Messages
1,928
You understand it wrong, the initializer is just the function that runs at map initialization (not sure if before of after the InitTrig_ normal functions):
vJASS:
scope spell_A

globals
    private unit u
endglobals

private function spell_A_Conditions takes nothing returns boolean
    if GetSpellAbilityId() == 'A001' then
        set u = GetTriggerUnit()
    endif
    return false
endfunction

function spell_A_register takes unit u returns nothing
    local trigger t = CreateTrigger()
    call TriggerRegisterUnitEvent(t,u,EVENT_UNIT_SPELL_EFFECT)
    call TriggerAddCondition(t, Condition( function spell_A_Conditions ) )
    set t = null
endfunction
endscope
This doesn't work because if the spell_A_register runs at the map initialization the variable u wasn't assigned yet, and no, it doesn't overrides whenever you change the value of u, because that is not how it works, you only pass the reference stored on u at map init, but the event doesn't actually use the variable u, so changing it won't do anything to the trigger.

In your case is better just use the TriggerRegisterAnyUnitEventBJ.

Or if you know beforehand what units will use the spell, yeah, you can just use this:
vJASS:
// call spell_A_register(u) is called when the hero using this spell is selected.
 
Last edited:
Level 45
Joined
Feb 27, 2007
Messages
5,578
And then I found out that "TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_CAST)" is very inefficient cuz it fires event for every player.
This is a massive red herring and you should immediately stop trying to hyperoptmize every bit of every trigger. It's not worth it because it will literally never matter or appear to improve performance. Don't do it. If you find a bottleneck, investigate it, but don't preemptively fix things that won't actually bottleneck. Non-wrapper BJ functions are often quite helpful for reducing the amount of simple, repetitive code you have to write, and the vast majority of the time doing so will never matter.

I'm sorry to sound so rude here but people do this all the goddamn time with WC3 code and it is completely pointless drivel. WC3 map efficiency is limited in so many different ways by so many other factors that are not things like this. Consider how many thousands of spell casts per second you would have to achieve for this to produce any noticeable effect; other problems within the game like pathfinding or visual lag or ai bottlenecking would start to occur far in advance of any slowdown due to the events firing more often than they 'need' to.

In extremely high-performance situations for specific applications there are absolutely benefits to hyperoptimizing the entire process.
 
Level 3
Joined
Feb 11, 2024
Messages
22
This doesn't work because if the spell_A_register runs at the map initialization the variable u wasn't assigned yet,
@HerlySQR, thanks for the reply, but as I said that spell_A_register is not called when the map initializes, but it is called when the specific unit(Hero, and only one hero) who will use that spell is selected. It actually works well, so I was curious that if it's safe or not; but as you said, cuz I know beforehand which unit will use the spell, I can say that this has no problems regarding using scope, I assume?


Consider how many thousands of spell casts per second you would have
@Pyrogasm, also thanks for the reply, I certainly don't feel you are rude lol I can feel your frustration about this kind of over-optimizing things.
I think you have the point here, cuz my maximum spell-trigger calling in one time will be 10 cuz it's 5v5 AOS map.

But then again, other than spell-triggers, is it safe to have many TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_DAMAGED) events?

I don't use any kind of DDS and just use the basic editor event for various on-hit effect skills. Should I disable all damage event triggers and enable it only when the hero using that trigger is selected? Or is this also some kind of red herring?
 
Level 45
Joined
Feb 27, 2007
Messages
5,578
You should be perfectly fine to have as many spell cast triggers as you want without encountering some negative behavior. Triggers failing at the ‘conditions’ level (ConditionFunc returns false) are quite fast and without overhead compared to the trigger evaluating (in its Actions) some If/Then/Else and ending its execution in the Else.

Damage events are more suspect because of their high frequency, yes. I would estimate that damage events occur multiple orders of magnitude more frequently in maps than spell events (10-1000x frequency).

With that knowledge just make sure you’re not doing something very costly in one of these triggers. An example would be like searching over the entire map to find the closest of a particular type of enemy; performing that on every damage instance is bad; performing that on only damage instances that target a specific unit type is probably a non-issue.

I don’t think you need to disable triggers like you described just for single heroes.
 
Level 3
Joined
Feb 11, 2024
Messages
22
@Pyrogasm, it's good to know that "ConditionFunc returns false" triggers are not slow! I'm relieved to hear that.

For damaged events, I don't have searching-all-map kind of skills, only dealing some AOE damage around the normal attack target such as below.

  • flamebladeE
    • Events
      • Unit - A unit Takes damage
    • Conditions
      • ((Damage source) has buff Inferno) Equal to True
      • (Damage From Normal Attack) Equal to True
    • Actions
      • Custom script: local unit f=null
      • Custom script: call DestroyEffect(AddSpecialEffect("war3mapImported\\Flameerupt.mdx", GetUnitX(BlzGetEventDamageTarget()), GetUnitY(BlzGetEventDamageTarget())))
      • Custom script: call GroupEnumUnitsInRange(grp, GetUnitX(BlzGetEventDamageTarget()), GetUnitY(BlzGetEventDamageTarget()), 175., null)
      • Custom script: loop
      • Custom script: set f=FirstOfGroup(grp)
      • Custom script: exitwhen f==null
      • Custom script: if IsUnitEnemy(f, GetOwningPlayer(GetEventDamageSource())) == true and UnitAlive(f) == true then
      • Custom script: call UnitDamageTarget(GetEventDamageSource(), f, 35.+15.*I2R(GetUnitAbilityLevel(GetEventDamageSource(),'A09B'))), false, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, null)
      • Custom script: call DestroyEffect(AddSpecialEffectTarget("Abilities\\Spells\\Items\\AIfb\\AIfbSpecialArt.mdl", f, "chest"))
      • Custom script: endif
      • Custom script: call GroupRemoveUnit(grp,f)
      • Custom script: endloop
I don't use any DDSs but just use the editor-inbuilt event Unit - A unit Takes damage for all of my damage event skills.
I think this will fire all the time when any unit is damaged, including lane units('minions'), jungle creeps, etc.
(Not that much in fact, happen to have at most ~50 units fighting and damaging at a time)

Having a lot of (20+, maybe more later) this kind of damage event triggers is OK? Even if the skill is only for one hero, there are many heroes that have their own damage event triggers...

I think I can disable them all, and enable them only when the Hero using that skill is selected; but this is no needed?
 
Level 45
Joined
Feb 27, 2007
Messages
5,578
Yes, it’s my estimation that you will not experience any issues related to those numerous damage event triggers.

Consider that the conditions in such triggers will filter out most of the event firings without really doing any operations. The way GUI constructs its conditionfuncs, the first condition that fails will immediately end the evaluation without checking the remaining conditions (convert one with multiple conditions and you’ll see how/why). You can intentionally order conditions to take advantage of this and check the least or most likely conditions first for efficiency.
 
Level 3
Joined
Feb 11, 2024
Messages
22
Great, big thanks for the confirmation! No more tiresome-and-pointless works.

Also good to know that GUI conditionfuncs are quite efficient in its own way; I knew that GUI conditionfuncs were all-unfolded (if this is correct depiction) when I converted it, but now I know what this means really.
 
Top