• 🏆 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] Script Leaks

Status
Not open for further replies.
Below is the system for projectiles and effects that I am working on for a map. Within one of these systems there are leaks. I can tell there are leaks by the way because of degrading performance on the map when testing it with a spell that spawns 80 projectiles and each projectile spawns an effect when it dies. The spell itself, when cast the first time, causes no lag on my machine so I am sure that the spell itself is not the source of the degrading performance.

JASS:
globals
    boolean udg_Bool
    group udg_Effects
    timer udg_EffectTimer
    real udg_SpellA
    unit udg_SpellC
    trigger udg_Spellcast
    hashtable udg_SpellData
    hashtable udg_SpellIndex
    player udg_SpellP
    group udg_Spells
    timer udg_SpellTimer
    unit udg_SpellTU
    real udg_SpellTX
    real udg_SpellTY
    real udg_SpellX
    real udg_SpellY
    unit udg_Unit
    unit udg_Unit2
endglobals

JASS:
function ProjHit takes nothing returns boolean
    local unit U=GetFilterUnit()
    local player P=GetOwningPlayer(U)
    local integer id=GetHandleId(udg_Unit)
    local real dmg=LoadReal(udg_SpellData,id,1)
    local unit C=LoadUnitHandle(udg_SpellData,id,0)
    if GetWidgetLife(U)>0.405and IsUnitEnemy(udg_Unit,P)then
        set udg_Bool=true
        call SetWidgetLife(U,GetWidgetLife(U)-dmg)
        call SetHeroXP(C,GetHeroXP(C)+R2I(dmg),true)
    endif
    set C=null
    set U=null
    set P=null
    return false
endfunction

function Spell_Tick takes nothing returns nothing
    local group G=CreateGroup()
    local boolean E=false
    local real ang
    local real x
    local real y
    local real spd
    local real lif
    local integer id
    local group hit=CreateGroup()
    local conditionfunc con=Condition(function ProjHit)
    local integer i
    local player P
    local unit U
    local unit C
    call GroupAddGroup(udg_Spells,G)
    loop
        set udg_Unit=FirstOfGroup(G)
        exitwhen udg_Unit==null
        set E=true
        // projectile code
        set lif=GetWidgetLife(udg_Unit)
        set id=GetHandleId(udg_Unit)
        set ang=GetUnitFacing(udg_Unit)
        set spd=LoadReal(udg_SpellData,id,2)*0.03
        set x=GetUnitX(udg_Unit)+spd*Cos(ang*bj_DEGTORAD)
        set y=GetUnitY(udg_Unit)+spd*Sin(ang*bj_DEGTORAD)
        if lif>0.405then
            set udg_Bool=false
            call GroupEnumUnitsInRange(hit,x,y,LoadReal(udg_SpellData,id,3),con)
            if udg_Bool or IsTerrainPathable(x,y,PATHING_TYPE_WALKABILITY)or GetTerrainType(x,y)=='Xhdg'then
                call SetWidgetLife(udg_Unit,0)
            else
                // Trail
                if LoadBoolean(udg_SpellData,id,5)then
                    if GetRandomReal(0,1)<=LoadReal(udg_SpellData,id,70)then
                        set i=LoadInteger(udg_SpellData,id,72)
                        set P=GetOwningPlayer(udg_Unit)
                        loop
                            set U=CreateUnit(P,LoadInteger(udg_SpellData,id,71),x,y,ang)
                            if GetUnitAbilityLevel(U,'A001')>0then
                                set C=LoadUnitHandle(udg_SpellData,id,0)
                                call SaveUnitHandle(udg_SpellData,GetHandleId(U),0,C)
                                set C=null
                            endif
                            set U=null
                            set i=i-1
                            exitwhen i<1
                        endloop
                    endif
                endif
                call SetUnitX(udg_Unit,x)
                call SetUnitY(udg_Unit,y)
                call SetWidgetLife(udg_Unit,lif-spd)
            endif
        else // It died...
            // Explode
            if LoadBoolean(udg_SpellData,id,4)then
                call SaveReal(udg_SpellData,id,1,LoadReal(udg_SpellData,id,51))
                call GroupEnumUnitsInRange(hit,x,y,LoadReal(udg_SpellData,id,50),con)
            endif
            // Split
            if LoadBoolean(udg_SpellData,id,6)then
                set i=LoadInteger(udg_SpellData,id,91)
                set P=GetOwningPlayer(udg_Unit)
                loop
                    set U=CreateUnit(P,LoadInteger(udg_SpellData,id,90),x,y,ang)
                    if GetUnitAbilityLevel(U,'A001')>0then
                        set C=LoadUnitHandle(udg_SpellData,id,0)
                        call SaveUnitHandle(udg_SpellData,GetHandleId(U),0,C)
                        set C=null
                    endif
                    set U=null
                    set i=i-1
                    exitwhen i<1
                endloop
            endif
            // Remove It
            call FlushChildHashtable(udg_SpellData,id)
            call GroupRemoveUnit(udg_Spells,udg_Unit)
        endif
        // -----
        call GroupRemoveUnit(G,udg_Unit)
    endloop
    call DestroyGroup(G)
    set G=null
    call DestroyGroup(hit)
    set hit=null
    call DestroyCondition(con)
    set con=null
    set P=null
    set U=null
    set C=null
    if E then
        call TimerStart(udg_SpellTimer,0.03,false,function Spell_Tick)
    endif
endfunction

JASS:
function EffHit takes nothing returns boolean
    local unit U=GetFilterUnit()
    local player P=GetOwningPlayer(U)
    local integer id=GetHandleId(udg_Unit2)
    local real dmg=LoadReal(udg_SpellData,id,-1)*0.25
    local unit C=LoadUnitHandle(udg_SpellData,id,0)
    if GetWidgetLife(U)>0.405and IsUnitEnemy(udg_Unit2,P)then
        call SetWidgetLife(U,GetWidgetLife(U)-dmg)
        call SetHeroXP(C,GetHeroXP(C)+R2I(dmg),true)
    endif
    set C=null
    set U=null
    set P=null
    return false
endfunction

function Effect_Tick takes nothing returns nothing
    local group G=CreateGroup()
    local boolean E=false
    local real lif
    local group hit=CreateGroup()
    local conditionfunc con=Condition(function EffHit)
    local integer id
    call GroupAddGroup(udg_Effects,G)
    loop
        set udg_Unit2=FirstOfGroup(G)
        exitwhen udg_Unit2==null
        set E=true
        set id=GetHandleId(udg_Unit2)
        // effect code
        set lif=GetWidgetLife(udg_Unit2)
        if lif>0.405then
            call GroupEnumUnitsInRange(hit,GetUnitX(udg_Unit2),GetUnitY(udg_Unit2),LoadReal(udg_SpellData,id,-2),con)
            call SetWidgetLife(udg_Unit2,lif-0.25)
        else
            call FlushChildHashtable(udg_SpellData,id)
            call GroupRemoveUnit(udg_Effects,udg_Unit2)
        endif
        // -----
        call GroupRemoveUnit(G,udg_Unit2)
    endloop
    call DestroyGroup(G)
    set G=null
    call DestroyGroup(hit)
    set hit=null
    call DestroyCondition(con)
    set con=null
    if E then
        call TimerStart(udg_EffectTimer,0.25,false,function Effect_Tick)
    endif
endfunction

JASS:
function Spellcast takes nothing returns nothing
    local integer sid=GetSpellAbilityId()
    local triggercondition Tc
    if LoadBoolean(udg_SpellIndex,sid,0)then
        set udg_SpellC=GetSpellAbilityUnit()
        set udg_SpellP=GetOwningPlayer(udg_SpellC)
        set udg_SpellX=GetUnitX(udg_SpellC)
        set udg_SpellY=GetUnitY(udg_SpellC)
        set udg_SpellTX=GetSpellTargetX()
        set udg_SpellTY=GetSpellTargetY()
        set udg_SpellTU=GetSpellTargetUnit()
        set udg_SpellA=bj_RADTODEG*Atan2(udg_SpellTY-udg_SpellY,udg_SpellTX-udg_SpellX)
        set Tc=TriggerAddCondition(udg_Spellcast,LoadBooleanExprHandle(udg_SpellIndex,sid,1))
        call TriggerEvaluate(udg_Spellcast)
        call TriggerRemoveCondition(udg_Spellcast,Tc)
        call TimerStart(udg_SpellTimer,0.03,false,function Spell_Tick)
        set udg_SpellC=null
        set udg_SpellP=null
        set udg_SpellTU=null
    endif
    set Tc=null
endfunction

function InitTrig_Spellcast takes nothing returns nothing
    local integer i=15
    set gg_trg_Spellcast=CreateTrigger()
    loop
        call TriggerRegisterPlayerUnitEvent(gg_trg_Spellcast,Player(i),EVENT_PLAYER_UNIT_SPELL_EFFECT,null)
        set i=i-1
        exitwhen i<0
    endloop
    call TriggerAddCondition(gg_trg_Spellcast,Condition(function Spellcast))
endfunction

If you would like to see the trigger that adds a few spells, I can post those as well.
 
Level 37
Joined
Mar 6, 2006
Messages
9,240
Make sure the death type of the dummies is Can't raise, does not decay so they don't stay in the map for 90 seconds after dying.

Units always leak (a very small amount) and there is nothing you can do about that except not creating units.

What data are you saving into the hashtable? Maybe some handles aren't removed, flushing the child ht isn't enough.
 
Level 37
Joined
Mar 6, 2006
Messages
9,240
You can run this script and watch the memory usage of WC3 grow. Make the footman to not decay.

JASS:
library leakTest initializer onInit
    globals
        private timer t = CreateTimer()
        private constant integer dummies = 5
        private integer c = 0
    endglobals

    private function leaks takes nothing returns nothing
        local unit u
        local integer i = dummies
        
        loop
            exitwhen i == 0
            set u = CreateUnit(Player(0), 'hfoo', 0, 0, 0)
            call UnitApplyTimedLife(u, 1, 0.01)
            //call RemoveUnit(u)
            set u = null
            set c = c + 1
            set i = i - 1
        endloop
        set u = null
    endfunction
    
    private function pauseResume takes nothing returns nothing
        if ModuloInteger(GetTriggerExecCount(GetTriggeringTrigger()) , 2) == 1 then
            call BJDebugMsg("started")
            call TimerStart(t, 0.01, true, function leaks)
        else
            call BJDebugMsg("paused, " + "dummes created: " + I2S(c))
            call PauseTimer(t)
        endif
    endfunction

    private function onInit takes nothing returns nothing
        local trigger t = CreateTrigger()
        call TriggerRegisterPlayerEvent(t, Player(0), EVENT_PLAYER_END_CINEMATIC)
        call TriggerAddAction(t, function pauseResume)
    endfunction
endlibrary
 
Status
Not open for further replies.
Top