- Joined
- Dec 12, 2008
- Messages
- 7,379
Recursion Safety
Table of Contents
The Problem
Recursion-safety is often needed in cases where recursive execution of a function is possible
and harmful to an array of system data. Consider this example:
JASS:
function OnDamage takes nothing returns nothing
set source = GetEventDamageSource()
set target = GetTriggerUnit()
set amount = GetEventDamage()
call damageEvent.fire()
set source = null
set target = null
set amount = 0
endfunction
This is a function inside a damage detection system that runs whenever damage is dealt.
Take note of the 5th line of code that fires the event. This line of code will execute
all functions registered to the system to run upon a damage event. What if one of those
functions dealt damage to a unit? You guessed it, this OnDamage function would be called
again and it would refire all the functions registered to the system.
Sometimes, if there are no conditions for dealing damage in the registered functions,
this would lead to an infinite loop. When there are conditions, this leads to a recursion
that would repeat a finite number of times.
Do note that upon the first recursion, the source, target and amount variables will be
changed, so all the functions that still haven't been executed by the damageEvent.fire()
call would be using different source/target/amount information. This also affects the
function that dealt the damage. By dealing damage, you are changing the source, the
target and the amount.
The Solution
The solution to keep the data valid is to preserve the previous values before setting new ones
and restoring them after the functions are executed. We have to wind and unwind a stack. There are
two approaches:
1) The array approach: Instead of using regular variables to store our data, we can use
arrays. We'd have to have an index to represent the stack depth that we increment each time we
execute the function. We'd use that index when writing data to the arrays and subsequently,
reading data from the arrays:
JASS:
function OnDamage takes nothing returns nothing
set stackDepth = stackDepth + 1
set source[stackDepth] = GetEventDamageSource()
set target[stackDepth] = GetTriggerUnit()
set amount[stackDepth] = GetEventDamage()
call damageEvent.fire()
set source[stackDepth] = null
set target[stackDepth] = null
set amount[stackDepth] = 0
set stackDepth = stackDepth - 1
endfunction
This works quite well, but unfortunately, it's not the best solution. In fact, it's a bad one.
2) The locals approach: We can avoid arrays and stack depths altogether by taking advantage
of the automatic storage facilities provided by JASS. Yes, that's a fancy phrase for local variables.
JASS:
function OnDamage takes nothing returns nothing
local unit previousSource = source
local unit previousTarget = target
local real previousAmount = amount
set source = GetEventDamageSource()
set target = GetTriggerUnit()
set amount = GetEventDamage()
call damageEvent.fire()
set source = previousSource
set target = previousTarget
set amount = previousAmount
set previousSource = null
set previousTarget = null
endfunction
This is quite an effective solution because the winding and unwinding is abstracted. The handle
pointers are managed by the JASS VirtualMachine rather than our JASS code.
Wrap-Up
This concludes my tutorial on Recursion-safety in JASS. Stay in school.
~Magtheridon96