[Solved] Question about passing variables using globals

Status
Not open for further replies.
Level 2
Joined
Feb 27, 2021
Messages
10
Hey! I know using global variables for passing variables into callback functions is a popular idea. I've used it whenever I want to pass a variable into a "for group" callback function. However I just wonder is it possible that different threads of functions happen to use the same global variable at the same time? Should I use a unique global variable for each callback function to prevent global variable collides?
 
Hey! I know using global variables for passing variables into callback functions is a popular idea. I've used it whenever I want to pass a variable into a "for group" callback function. However I just wonder is it possible that different threads of functions happen to use the same global variable at the same time? Should I use a unique global variable for each callback function to prevent global variable collides?
It will never process in parallell, but you can get collisions due to how actions and events are evaluated anyways.
Say that you loop through all heroes in a group, add 1 to X (global variable), add an item and teleport the unit to location A.
Then you have a "on-item-aquired" event that teleports the unit to location B and set X to 15.
This "on-item-aquired" will run instantly when the "add item" is run.

I.E.
  • Set X = 0
  • Unit-group-loop
    • Hero 1
      • X = X + 1 (resulting in 1)
      • Add item
        • on-item-aquired-trigger is run, teleporting to B, X = 15.
      • Teleport to A
    • Hero 2
      • X = X + 1 (resulting in 16)
      • Add item
        • on-item-aquired-trigger is run, teleporting to B, X = 15.
      • Teleport to A

As you see, if you would've used the same global variable in the on-item-aquired trigger, it would've been overwritten.
You don't end up att location B and your X isn't 2 as "expected", but 16.
 
Level 2
Joined
Feb 27, 2021
Messages
10
It will never process in parallell, but you can get collisions due to how actions and events are evaluated anyways.
Say that you loop through all heroes in a group, add 1 to X (global variable), add an item and teleport the unit to location A.
Then you have a "on-item-aquired" event that teleports the unit to location B and set X to 15.
This "on-item-aquired" will run instantly when the "add item" is run.
Thanks Patron! Yes i can see that would be a problem that's hard to spot.

So you say it will never happen in parallel, even for functions in different threads? Like if two triggers have similar events and are somehow run at the same time, and they both run a pretty long code that loops through a large unit group and use the same global variable, I imagine they would use the global at the same time, no?

Nonetheless, do you think there is any smarter way for passing variables when using these callback functions?
 
Level 42
Joined
Feb 27, 2007
Messages
5,272
Wc3 is single-threaded. Nothing can run simultaneously. Even ForGroup() executes sequentially for each unit in the group. A trigger that runs when units die will run N times back-to-back when N units die to the same AoE effect, not N simultaneous instances of the trigger.

Aside from the scenario ThompZon outlined, the only time two triggers can be ‘in progress’ at once is if one cedes execution priority by hitting a Wait. During the wait other triggers in the queue will execute.
 
Nonetheless, do you think there is any smarter way for passing variables when using these callback functions?
For GUI-triggers, not really.

I use a "two tiered" solution for reusable temporary variables and have learned what action can result in events etc.
I use "g_int1", "g_unit1", etc. for "global scope actions" used in the "primary triggers" (hero abilities typically), and "t_int1" for temporary stuff (most stuff, including damage-events, items events, dummy abilities, etc.).

Variables are "cheap", so making many of them are usually not an issue and can make it easier to read the triggers if named in a good way.
 
Level 2
Joined
Feb 27, 2021
Messages
10
Wc3 is single-threaded. Nothing can run simultaneously. Even ForGroup() executes sequentially for each unit in the group. A trigger that runs when units die will run N times back-to-back when N units die to the same AoE effect, not N simultaneous instances of the trigger.

Aside from the scenario ThompZon outlined, the only time two triggers can be ‘in progress’ at once is if one cedes execution priority by hitting a Wait. During the wait other triggers in the queue will execute.
Wow thanks! That clears it up for me. Good to know.
I use a "two tiered" solution for reusable temporary variables and have learned what action can result in events etc.
I use "g_int1", "g_unit1", etc. for "global scope actions" used in the "primary triggers" (hero abilities typically), and "t_int1" for temporary stuff (most stuff, including damage-events, items events, dummy abilities, etc.).
best option it's hashtables.
Thanks guys! All very helpful suggestions indeed. Hashtable does sound pretty nice. I do wonder how one would go about using hashtable for passing variables into a "for group" callback function though. I know with timer callback you can bind variables to the timer, but with group enum callback, binding variables to the group won't be effective because you can't get the group variable inside the callback function.
Best option is Lua ;)
I'm sooo tempted to use Lua right now for various reasons. But the thought of rewriting all my scripts all over again frightens me (my map is currently in vjass). I will definitely try out Lua for my next map.
 
Last edited:
Level 42
Joined
Feb 27, 2007
Messages
5,272
Hashtable does sound pretty nice. I do wonder how one would go about using hashtable for passing variables into a "for group" callback function though. I know with timer callback you can bind variables to the timer, but with group enum callback, binding variables to the group won't be effective because you can't get the group variable inside the callback function.
You would bind them to the units directly. I don't personally think a hashtable is the sort of solution you're looking for here simply because it's more annoying to manage than some global variables, but I don't know specifically what you're trying to do.
 
Level 2
Joined
Feb 27, 2021
Messages
10
You would bind them to the units directly. I don't personally think a hashtable is the sort of solution you're looking for here simply because it's more annoying to manage than some global variables, but I don't know specifically what you're trying to do.
I don't think binding variables to the units is effective...
So what I was trying to do here is, for example, I want to implement a function that heals all units in a group for a certain amount, I will need to pass the amount variable into the callback function (HealEnumUnit) so that the callback function would know how much to heal each unit.
JASS:
function HealEnumUnit takes nothing returns nothing
    local unit u = GetEnumUnit()
    // (heal u for a certain value x)
endfunction

function HealAGroupOfUnits takes group g, real x returns nothing
    // I want to pass "x" into the function "HealEnumUnit"
    call ForGroup(g, function HealEnumUnit)
endfunction

call HealAGroupOfUnits(g, 50.0)
if I was using global variable to achieve it, I would do:
JASS:
globals
    real ref_real
endglobals

function HealEnumUnit takes nothing returns nothing
    local unit u = GetEnumUnit()
    local real amount = ref_real
    call Heal(u, amount)
endfunction

function HealAGroupOfUnits takes group g, real x returns nothing
    // I want to pass "x" into the function "HealEnumUnit"
    set ref_real = x
    call ForGroup(g, function HealEnumUnit)
endfunction

call HealAGroupOfUnits(g, 50.0)
But not sure how to do it with HashTable. I don't think bind the variables is feasible, because the only reference you can get from native functions inside the callback is the enum unit. And you can't bind the variable to the enum unit unless you're already in a callback function.
 
Level 42
Joined
Feb 27, 2007
Messages
5,272
  • If x is a constant with respect to the unit (not some function or expression that is dependent on the unit) or can be described by something like "24% of the unit's current health", there is no need for more than one global variable to pass along the healing amount.
  • ForGroup and ForForce both inherit all the event responses from the functions that call them, so you can use GetTriggerUnit() and GetSpellTargetLoc(), etc.. Think about how it plays out using just GUI: nobody realizes the ForGroup loop is its own separate function but everything works as expected.
  • Any function called directly by the triggeraction will also inherit event responses. Again, think about how GUI does conditions: it breaks each set of conditions into its own boolean functions that are all ANDed together without the user ever realizing they're separate.
  • Instead of ForGroup you can avoid the need to do this sort of stuff by using a FirstOfGroup loop and never leaving your main function:
    JASS:
    local group g = SomeGroup()
    local unit u
    
    loop
      set u = FirstOfGroup(g)
      exitwhen u == null
      call GroupRemoveUnit(g,u)
    
      //do stuff with u here
    endloop
    
    //When this point is reached, g is an empty group
    Note that this is less desirable when you don't want to clear the group, but there is the BJ function GroupAddGroup to easily duplicate a group. Also dead units that have decayed can remain in the group, and can potentially cause FirstOfGroup to return null prematurely... but that's not an issue I've ever actually had to worry about.
  • I don't think binding variables to the units is effective...
    In this instance, no. But after timers, units are the next most likely handle mapmakers wish to attach data to.
 
Level 2
Joined
Feb 27, 2021
Messages
10
  • If x is a constant with respect to the unit (not some function or expression that is dependent on the unit) or can be described by something like "24% of the unit's current health", there is no need for more than one global variable to pass along the healing amount.
  • ForGroup and ForForce both inherit all the event responses from the functions that call them, so you can use GetTriggerUnit() and GetSpellTargetLoc(), etc.. Think about how it plays out using just GUI: nobody realizes the ForGroup loop is its own separate function but everything works as expected.
  • Any function called directly by the triggeraction will also inherit event responses. Again, think about how GUI does conditions: it breaks each set of conditions into its own boolean functions that are all ANDed together without the user ever realizing they're separate.
  • Instead of ForGroup you can avoid the need to do this sort of stuff by using a FirstOfGroup loop and never leaving your main function
Wow, Thanks Pyro! A lot of interesting points that I wan't aware of. Definitely some very nice workarounds. I'll primarily be using globals for passing variables 'cause it's neater. Knowning that wc3 is single-threaded really puts me at ease. But these are definitely very helpful to know.
 
Status
Not open for further replies.
Top