• 🏆 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] Handle counter exploding when using fast loops [SOLVED]

Hello there. I'm making a bunch of custom faction and optimizing the shit out of them so games can run with no problems even at 24.
In that perspective I'm hunting every single leak I could find. My knowledge is limited to Hashtable usage (no object oriented yet, beside imported libraries).
I am using a Handle counter to see which ability could have leaks, which would appear like a sharp increase.
I have a function which always seems to explode the amount, I don't know why.
And no, the amount doesn't get cleaned up by like 3000 when they are around.
Is the engine not handling cleaning when there's too much going on ? Because I don't see any kind of leak.

The ability makes 5 rotating effects around some buildings of the player.
When the effect is too close of another building, it "disappears" by being TP under the building.
Only the tail is moved in front of the head, so only one effect is moved per loop per building.
I also put the other concerned function for clarity. The system is working as intended, but it tends to explode the handle counter.
JASS:
// Takes all units from ug and move them to ug2
// returns the target group
function groupTransfer takes group ug, group ug2 returns group
    local unit u=FirstOfGroup(ug)
    loop
        exitwhen u==null
        call GroupAddUnit(ug2,u)
        call GroupRemoveUnit(ug,u)
        set u=FirstOfGroup(ug)
    endloop
    set u=null
    return ug2
endfunction
// Takes all units from ug and move them to ug2
// Then it deletes ug
// returns the target group
function groupFuse takes group ug, group ug2 returns group
    call groupTransfer(ug,ug2)
    call ReleaseGroup(ug)
    return ug2
endfunction
// Takes all units from ug and add them to ug2
// returns the target group
function groupAddGroup takes group ug, group ug2 returns group
    local unit u=FirstOfGroup(ug)
    local group ug3=NewGroup()
    loop
        exitwhen u==null
        call GroupAddUnit(ug2,u)
        call GroupAddUnit(ug3,u)
        call GroupRemoveUnit(ug,u)
        set u=FirstOfGroup(ug)
    endloop
    call groupFuse(ug3,ug)
    set u=null
    set ug3=null
    return ug2
endfunction
function groupCopy takes group ug returns group
    if ug==null then
        return null
    endif
    return groupAddGroup(ug,NewGroup())
endfunction
/*
   Effect timer indexes :
                    > 0  start of effect array
                    > -1 building
                    > -2 head index in the effect array
                    > -3 head index on the circle
*/
function changeGetBranched takes unit u returns group
    return LoadGroupHandle(udg_Hash_change,GetHandleId(u),'h01Y')
endfunction
function changeGetEffectTimer takes unit u returns timer
    return LoadTimerHandle(udg_Hash_change,GetHandleId(u),0)
endfunction
// Creates the effects for the building if they don't exist
function changeEffectCreate takes unit u returns nothing
    local timer t=changeGetEffectTimer(u)
    local integer kT=GetHandleId(t)
    local integer i=0
    local effect e
    loop
        exitwhen i>=GUIDANCE_NUMBER
        set e=LoadEffectHandle(udg_Hash_change,kT,i)
        if e==null then
            set e=AddSpecialEffect(GUIDANCE_EFFECT,0,0)
            call BlzSetSpecialEffectZ(e,50)
            call SaveEffectHandle(udg_Hash_change,kT,i,e)
            call BlzSetSpecialEffectAlpha(e,0)
        endif
        set i=i+1
    endloop
    set e=null
    set t=null
endfunction
function changeGetIndex takes unit u returns integer
    return LoadInteger(udg_Hash_change,GetHandleId(changeGetEffectTimer(u)),-3)
endfunction
function changeGetEffect takes unit u, integer i returns effect
    return LoadEffectHandle(udg_Hash_change,GetHandleId(changeGetEffectTimer(u)),i)
endfunction
function changeGetHead takes unit u returns integer
    return LoadInteger(udg_Hash_change,GetHandleId(changeGetEffectTimer(u)),-2)
endfunction
function changeGetHeadEffect takes unit u returns effect
    return changeGetEffect(u,changeGetHead(u))
endfunction
function changeShiftHead takes unit u returns integer
    local integer head=changeGetHead(u)+1
    if head>=GUIDANCE_NUMBER then
        set head=0
    endif
    call SaveInteger(udg_Hash_change,GetHandleId(changeGetEffectTimer(u)),-2,head)
    return head
endfunction
function changeMove takes unit u returns integer
    local integer i=changeGetIndex(u)+6
    if i>=360 then
        set i=i-360
    endif
    call SaveInteger(udg_Hash_change,GetHandleId(changeGetEffectTimer(u)),-3,i)
    return i
endfunction
function changeIsHeadHidden takes unit u returns boolean
    local integer kU=GetHandleId(u)
    local group ug=changeGetBranched(u)
    local group ug2
    local unit u2=null
    local effect e=changeGetHeadEffect(u)
    local integer i=changeGetIndex(u)
    local real x=GetUnitX(u)+circleX[i]*900
    local real y=GetUnitY(u)+circleY[i]*900
    if ug==null then
        set e=null
        set ug=null
        set ug2=null
        return false
    endif
    set ug2=NewGroup()
    loop
        set u2=FirstOfGroup(ug)
        exitwhen u2==null
        if UnitAlive(u2) and distanceBetweenCoords(x,y,GetUnitX(u2),GetUnitY(u2))<GUIDANCE_RANGE-25 and u!=u2 then
            call groupFuse(ug2,ug)
            set e=null
            set ug=null
            set ug2=null
            set u2=null
            return true
        endif
        call GroupRemoveUnit(ug,u2)
        call GroupAddUnit(ug2,u2)
    endloop
    call groupFuse(ug2,ug)
    set e=null
    set ug=null
    set ug2=null
    set u2=null
    return false
endfunction
function changeEffectIteration takes nothing returns nothing
    local timer t=GetExpiredTimer()
    local integer kT=GetHandleId(t)
    local unit u=LoadUnitHandle(udg_Hash_change,kT,-1)
    local integer kU=GetHandleId(u)
    local integer i=0
    local integer j=0
    local integer k=0
    local effect e
    local integer head
    if GetUnitAbilityLevel(u,'A04R')==1 then
        set i=changeMove(u)
        set head=changeShiftHead(u)
        set j=head
        set e=LoadEffectHandle(udg_Hash_change,kT,head)
        call BlzSetSpecialEffectX(e,GetUnitX(u)+circleX[i]*900)
        call BlzSetSpecialEffectY(e,GetUnitY(u)+circleY[i]*900)
        call BlzSetSpecialEffectYaw(e,Deg2Rad(i+90))
        if changeIsHeadHidden(u) then
            call BlzSetSpecialEffectScale(e,0)
            call BlzSetSpecialEffectAlpha(e,0)
            call BlzSetSpecialEffectX(e,GetUnitX(u))
            call BlzSetSpecialEffectY(e,GetUnitY(u))
        else
            call BlzSetSpecialEffectAlpha(e,100)
        endif
        // Set scales
        loop
            set e=changeGetEffect(u,j)
            call BlzSetSpecialEffectScale(e,1.0-1.0*(I2R(k)/I2R(GUIDANCE_NUMBER)))
            call BlzSetSpecialEffectYaw(e,Deg2Rad(i-k*6+90))
            set j=j-1
            if j<0 then
                set j=GUIDANCE_NUMBER-1
            endif
            set k=k+1
            exitwhen k>=GUIDANCE_NUMBER
        endloop
    else
        loop
            exitwhen i>=GUIDANCE_NUMBER
            set e=changeGetEffect(u,j)
            call BlzSetSpecialEffectX(e,GetUnitX(u))
            call BlzSetSpecialEffectY(e,GetUnitY(u))
            set i=i+1
        endloop
        call PauseTimer(t)
    endif
    set e=null
    set t=null
    set u=null
endfunction
function changeAddEffect takes unit u returns nothing
    local integer kU=GetHandleId(u)
    local timer t=changeGetEffectTimer(u)
    if t==null then
        set t=CreateTimer()
        call SaveTimerHandle(udg_Hash_change,kU,0,t)
        call TimerStart(t,GUIDANCE_PERIOD,true,function changeEffectIteration)
        call SaveUnitHandle(udg_Hash_change,GetHandleId(t),-1,u)
        call changeEffectCreate(u)
    endif
    set t=null
endfunction

Thank you in advance. If the handle counter is just messed up well my bad, but I might have forgotten a crucial detail ; who knows ?
 
Last edited:
Level 39
Joined
Feb 27, 2007
Messages
5,023
I don't see a DestroyTimer anywhere in that code. Whenever/wherever you're done with it you need to be destroying it else the handle will leak.. Also not a huge deal but when returning handles the returned variable is a reference leak like not setting = null at the end of the function. You can get around that by using a global variable and returning that variable:
JASS:
globals
  unit Superman
endglobals

function foo takes nothing returns unit
  set Superman = Something()
  return Superman
endfunction
 
I don't see a DestroyTimer anywhere in that code.
The timer is destroyed at building death. So there's only one timer per building.
Also not a huge deal but when returning handles the returned variable is a reference leak like not setting = null at the end of the function.
Oh, damned, that could be the source of my problems !
I'm gonna clean everything then and go back if it settled the problem.

EDIT : Welp, that hasn't fixed the issue.
Triggers still work as intended, but i still get ~10 handle leaks per second.

EDIT 2 : Welp, the problem was between the screen and the chair.
Another function had one ref leak and one group leak.
JASS:
function groupHealFromMana takes group ug returns nothing
    local group ugCopy=groupCopy(ug)
    local unit u=null
    loop
        set u=FirstOfGroup(ugCopy)
        exitwhen u==null
        call GroupRemoveUnit(ugCopy,u)
        call healFromMana(u)
        set u=FirstOfGroup(ugCopy)
    endloop
    call ReleaseGroup(ugCopy) // AND THIS LINE TOO
    set u=null
    set ugCopy=null // FORGOT THIS LINE
endfunction
 
Last edited:
Top