Andrewgosu
The Silent Pandaren Helper
- Reaction score
- 716
Memory Leaks In JASS
There are plenty of memory leak tutorial around. Some of them are brilliant, others are average, but mainly, they are for GUI users. Experienced JASSers mainly know how to deal with memory leaks in JASS, but a lot of beginners tend to forgot them. This tutorial is designed to help the poor, lost souls in the vast lands of memory leaks in JASS.
There is no difference between a memory leak in JASS and a memory leak in GUI. A memory leak remains a memory leak. However, in order to clean a memory leak, one firstly has to recognize it. I’ll show most of the things, that leak, in an "question&answer" form, so, if You JASS, You can easily avoid code which leaks.
Note, please ignore the "pseudo-variables", which I haven’t declared. These functions are examples and the leaking part counts.
Leaks, which will be covered:
- Nulling local variables
- Location
- Group
- Force
- Special effect
- Lightning
- Trigger actions
- Timer
So, shall we begin?
- Nulling local variables
Before we get jiggly with removing and destroying, many beginners forget to do the simplest thing to avoid memory leaks – they forgot to null local handle variables. And what is a local handle variable? Handle variable is a variable that is not a string, real, integer, code or boolean type variable. Easy? Lets take an example.
JASS:
Now, if one forgets to nullify the unit variable, it will result in a leak – it will be floating in the memory for the rest of the game. So, to say over, all local variables except string, real, integer, code and boolean have to be nulled.
Note, handle variables have to be nulled after they have been destroyed removed. One cannot destroy a variable, which is null.
- Location leakage
JASS:
function LocationLeakage takes nothing returns nothing
call SetUnitPositionLoc( GetSpellTargetUnit(), GetUnitLoc(GetTriggerUnit()))
endfunction
The location leaks. To clean it up, one has to set it into a variable.
JASS:
native RemoveLocation takes location whichLocation returns nothing
JASS:
function LocationLeakage takes nothing returns nothing
local location loc = GetUnitLoc(GetTriggerUnit())
call SetUnitPositionLoc( GetSpellTargetUnit(), loc )
call RemoveLocation(loc)
set loc = null
endfunction
However, one doesn't need to use locations at all - locations consist of two reals - the x- and y-axis values. (Alot of native functions take 2 reals instead of location and using reals is faster, too!)
JASS:
function RealUsage takes nothing returns nothing
local unit a = GetTriggerUnit()
local real xPos = GetUnitX(a)
local real yPos = GetUnitY(a)
call SetUnitPosition( GetSpellTargetUnit(), xPos, yPos )
set a = null
endfunction
- Group leakage
JASS:
function GroupLeakage takes nothing returns nothing
call ForGroup( GetUnitsOfPlayerAll(Player(0)), function whichFunction )
endfunction
This function leaks a group. To fix it, set it into a variable.
JASS:
native DestroyGroup takes group whichGroup returns nothing
JASS:
function GroupLeakage takes nothing returns nothing
local group g = GetUnitsOfPlayerAll(Player(0))
call ForGroup( g, function whichFunction )
call DestroyGroup(g)
set g = null
endfunction
Or, use the "set bj_wantDestroyGroup" action, which destroys the group after it has been used. But setting the group into a variable is wiser, if one needs the same group several times.
JASS:
function GroupLeakage takes nothing returns nothing
set bj_wantDestroyGroup = true
call ForGroup( GetUnitsOfPlayerAll(Player(0)), function whichFunction )
endfunction
- Force leakage
JASS:
function ForceLeakage takes nothing returns nothing
call DisplayTimedTextToForce( GetForceOfPlayer(GetOwningPlayer(GetTriggerUnit())), 5., "Hey, I create a force every time and leak" )
endfunction
This leaks, because it creates a new force every time. To fix it, do,
JASS:
native DestroyForce takes force whichForce returns nothing
JASS:
function ForceLeakage takes nothing returns nothing
local force f = GetForceOfPlayer(GetOwningPlayer(GetTriggerUnit()))
call DisplayTimedTextToForce( f, 5., "Hey, I don039;t leak" )
call DestroyForce(f)
set f = null
endfunction
- Special effect leakage
JASS:
function EffectAndPointLeakage takes nothing returns nothing
call AddSpecialEffectLoc( modelName, GetUnitLoc(GetTriggerUnit()) )
endfunction
This leaks a location and an effect, if one forgets to remove them after usage. A common mistake when making spells.
JASS:
native DestroyEffect takes effect whichEffect returns nothing
JASS:
function EffectAndPointLeakage takes nothing returns nothing
local location loc = GetUnitLoc(GetTriggerUnit())
local effect e = AddSpecialEffectLoc( modelName, loc )
//Some actions.
call DestroyEffect(e)
call RemoveLocation(loc)
set loc = null
set e = null
endfunction
This takes care of the point and effect leak, however, if one needs the effect to be destroyed right after it has been created, the code can be shortened(Lets use a native, which takes 2 reals, too).
JASS:
function EffectLeakage takes nothing returns nothing
call DestroyEffect(AddSpecialEffect( modelName, x, y ))
endfunction
Note, effects created on units have to be removed as well.
JASS:
function EffectLeakage takes nothing returns nothing
local effect e = AddSpecialEffectTarget( modelName, GetTriggerUnit(), "overhead" )
//Some actions.
call DestroyEffect(e)
set e = null
endfunction
- Lightning leakage
The same deal is with lighting as it was with effects - they have to be destroyed.
JASS:
function LightningLeakage takes nothing returns nothing
call AddLightning( codeName, checkVisibility, x1, x2, y1, y2 )
endfunction
If not removed, it will cause a leak.
JASS:
native DestroyLightning takes lightning whichBolt returns boolean
JASS:
function LightningLeakage takes nothing returns nothing
local lightning light = AddLightning( codeName, checkVisibility, x1, x2, y1, y2 )
//Some actions.
call DestroyLightning(light)
set light = null
endfunction
- Trigger action leakage
Trigger action leaks are rare, mainly, when triggers are created via triggers. Removing trigger actions is quite tricky, one has to store the trigger action to access it later so it wouldn't leak.
Here is a modified piece of Kattanas Handle Variable system, which allows to store and access trigger actions(A global game cache variable named "Cache" is needed. Copy paste the following script into your maps header"). If You already have the system, just make sure You have the "GetHandleTriggerAction" function.
JASS:
function H2I takes handle h returns integer
return h
return 0
endfunction
function LocalVars takes nothing returns gamecache
if ( udg_Cache == null ) then
call FlushGameCache(InitGameCache("somename"))
set udg_Cache = InitGameCache("somename")
endif
return udg_Cache
endfunction
function SetHandleHandle takes handle subject, string name, handle value returns nothing
if ( value == null ) then
call FlushStoredInteger(LocalVars(),I2S(H2I(subject)),name)
else
call StoreInteger(LocalVars(), I2S(H2I(subject)), name, H2I(value))
endif
endfunction
function GetHandleTriggerAction takes handle subject, string name returns triggeraction
return GetStoredInteger(LocalVars(), I2S(H2I(subject)), name)
return null
endfunction
function FlushHandleLocals takes handle subject returns nothing
call FlushStoredMission(LocalVars(), I2S(H2I(subject)) )
endfunction
Now, we are prepared to clean trigger actions leaks.
JASS:
function ExampleAction takes nothing returns nothing
local unit a = GetTriggerUnit()
if ( IsUnitType( a, UNIT_TYPE_HERO ) == true ) then
call ReviveHero( a, x, y, doEyeCandy )
call DestroyTrigger(GetTriggeringTrigger())
endif
set a = null
endfunction
function TriggerActionLeakage takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerAddAction( t, function ExampleAction )
call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_DEATH )
set t = null
endfunction
Now, this destroys the trigger, but not the triggeraction. This causes a leak. To fix it, we have to attach the triggeraction to the created trigger and destroy it before we destroy the trigger.
JASS:
native TriggerRemoveAction takes trigger whichTrigger, triggeraction whichAction returns nothing
JASS:
function ExampleAction takes nothing returns nothing
local unit a = GetTriggerUnit()
local trigger t = GetTriggeringTrigger()
if ( IsUnitType( a, UNIT_TYPE_HERO ) == true ) then
call ReviveHero( a, x, y, doEyeCandy )
//This removes the triggeraction.
call TriggerRemoveAction( t, GetHandleTriggerAction( t, "ta" ) ))
//This removes the triggeraction from the gamecache.
call FlushHandleLocals(t)
call DestroyTrigger(t)
endif
set a = null
set t = null
endfunction
function TriggerActionLeakage takes nothing returns nothing
local trigger t = CreateTrigger()
local triggeraction ta = TriggerAddAction( t, function Example )
call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_DEATH )
//This attaches the triggeraction to the created trigger.
call SetHandleHandle( t, "ta" ta )
set t = null
set ta = null
endfunction
- Timer leakage
JASS:
function ExampleTimer takes nothing returns nothing
local integer nIndex = 0
local player p
loop
exitwhen ( nIndex == bj_MAX_PLAYERS )
set p = Player(nIndex)
call SetPlayerState( p, PLAYER_STATE_RESOURCE_GOLD, GetPlayerState( p, PLAYER_STATE_RESOURCE_GOLD ) + 100 )
set nIndex = nIndex + 1
endloop
set p = null
endfunction
function TimerLeakage takes nothing returns nothing
local timer t = CreateTimer()
call TimerStart( t, 30, false, function ExampleTimer )
set t = null
endfunction
This piece of code leaks a timer, because it is never removed. Destroy the timer to fix the leak.
JASS:
native DestroyTimer takes timer whichTimer returns nothing
JASS:
function ExampleTimer takes nothing returns nothing
local integer nIndex = 0
local player p
loop
exitwhen ( nIndex == bj_MAX_PLAYERS )
set p = Player(nIndex)
call SetPlayerState( p, PLAYER_STATE_RESOURCE_GOLD, GetPlayerState( p, PLAYER_STATE_RESOURCE_GOLD ) + 100 )
set nIndex = nIndex + 1
endloop
call DestroyTimer(GetExpiredTimer())
set p = null
endfunction
function TimerLeakage takes nothing returns nothing
local timer t = CreateTimer()
call TimerStart( t, 30, false, function ExampleTimer )
set t = null
endfunction