• 🏆 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] Can't find the leak

Status
Not open for further replies.
Level 9
Joined
Jun 7, 2007
Messages
195
First this works ok, but it starts to lag after some casts so there seems to be leaks, can someone point them out?

Also is there a way to make it run more smoothly with more projectiles flying at a time without using structs?

(I know it targets dead units atm. forgot to add that to the filter list.)

Bouncing Projectile
JASS:
//===========================================================================
library Common initializer Init
//===========================================================================
    globals
        boolexpr noFilter

        real PlayableAreaMinX
        real PlayableAreaMaxX
        real PlayableAreaMinY
        real PlayableAreaMaxY
    endglobals
//===========================================================================
    function ReturnTrue takes nothing returns boolean
        return true
    endfunction
//===========================================================================   
    private function Init takes nothing returns nothing
        set noFilter = Condition(function ReturnTrue)
        
        set PlayableAreaMinX = GetRectMinX(bj_mapInitialPlayableArea)
        set PlayableAreaMaxX = GetRectMaxX(bj_mapInitialPlayableArea)
        set PlayableAreaMinY = GetRectMinY(bj_mapInitialPlayableArea)
        set PlayableAreaMaxY = GetRectMaxY(bj_mapInitialPlayableArea)
    endfunction
//===========================================================================
    function PolarProjectionX takes real x, real dist, real angle returns real
        return ( x + ( dist * Cos( angle * bj_DEGTORAD ) ) )
    endfunction
//===========================================================================
    function PolarProjectionY takes real y, real dist, real angle returns real
        return ( y + ( dist * Sin( angle * bj_DEGTORAD ) ) )
    endfunction
//===========================================================================
    function DistanceBetweenCoordinates takes real x1, real y1, real x2, real y2 returns real
        return ( SquareRoot( ( x1 - x2 ) * ( x1 - x2 ) + ( y1 - y2 ) * ( y1 - y2 ) ) )
    endfunction
//===========================================================================
    function AngleBetweenCoordinates takes real x1, real y1, real x2, real y2 returns real
        return ( bj_RADTODEG * Atan2(y2 - y1, x2 - x1) )
    endfunction
//===========================================================================
    function FixAngle takes real angle returns real
        if (angle < 0) then
            return 180 + ( 180 - angle * -1 )
        endif
        return angle
    endfunction    
//=========================================================================== 
    function CopyGroup takes group g returns group
        set bj_groupAddGroupDest=CreateGroup()
        call ForGroup(g, function GroupAddGroupEnum)
        return bj_groupAddGroupDest
    endfunction
//===========================================================================
endlibrary
//===========================================================================


//===========================================================================
library BouncingProjectile initializer Init requires Common
//===========================================================================
    globals
        private hashtable HASH = InitHashtable()
        private group GROUP = CreateGroup()
        private timer TIMER = CreateTimer()
        private constant string EFFECT_PATH = "Abilities\\Spells\\Other\\Stampede\\StampedeMissileDeath.mdl"
    endglobals
//===========================================================================
    function FilterFunc takes unit u, unit u2 returns boolean
        return (GetUnitTypeId(u2) != 'udum') and (IsUnitEnemy(u2, GetOwningPlayer(u))) and (not(IsUnitType(u2, UNIT_TYPE_FLYING)))
    endfunction
//===========================================================================
    function Loop takes nothing returns nothing
        local unit u
        local unit u2
        local unit caster
        local boolean bounceX
        local boolean bounceY
        local integer cliff
        local integer lifespan
        local real x
        local real y
        local real x2
        local real y2
        local real angle
        local real speed
        local group g = CopyGroup(GROUP)
        local group g2
        
        loop
            set u = FirstOfGroup(g)
            exitwhen (u == null)
            call GroupRemoveUnit(g, u)
        
            set x = GetUnitX(u)
            set y = GetUnitY(u)
            set caster = LoadUnitHandle(HASH, StringHash("Caster"), GetHandleId(u))
            set cliff = LoadInteger(HASH, StringHash("Cliff"), GetHandleId(u))
            set lifespan = LoadInteger(HASH, StringHash("Lifespan"), GetHandleId(u))
            set angle = LoadReal(HASH, StringHash("Angle"), GetHandleId(u))
            set speed = LoadReal(HASH, StringHash("Speed"), GetHandleId(u))

            set bounceX = false
            set bounceY = false
            
            set x2 = PolarProjectionX(x, speed, angle)
            set y2 = PolarProjectionY(y, speed, angle)
            if (GetTerrainCliffLevel(x2, y) > cliff) or (x2 <= PlayableAreaMinX) or (x2 >= PlayableAreaMaxX) then
                set bounceX = true
            endif
            if (GetTerrainCliffLevel(x, y2) > cliff) or (y2 <= PlayableAreaMinY) or (y2 >= PlayableAreaMaxY) then
                set bounceY = true
            endif
            
            if (bounceX) or (bounceY) then
                if (bounceX) and (bounceY) then
                    set angle = 180 + angle 
                elseif (bounceX) then
                    set angle = 180 + ( 360 - angle )
                else
                    set angle = 360 - angle
                endif
                set x2 = PolarProjectionX(x, speed, angle)
                set y2 = PolarProjectionY(y, speed, angle)
            endif
            call SaveReal(HASH, StringHash("Angle"), GetHandleId(u), angle)
            call SaveInteger(HASH, StringHash("Cliff"), GetHandleId(u), GetTerrainCliffLevel(x2, y2))
            
            call SetUnitPosition(u, x2, y2)
                        
            set g2 = CreateGroup()
            call GroupEnumUnitsInRange(g2, x2, y2, 100.000, noFilter)
            loop
                set u2 = FirstOfGroup(g2)
                exitwhen (u2 == null)
                call GroupRemoveUnit(g2, u2)
                if (FilterFunc(u, u2)) then
                    call DestroyEffect(AddSpecialEffectTarget(EFFECT_PATH, u2, "origin"))
                    call UnitDamageTarget(caster, u2, 10.000, false, true, ATTACK_TYPE_MELEE, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS)
                endif
            endloop
            call DestroyGroup(g2)
            set g2 = null
            
            call SaveInteger(HASH, StringHash("Lifespan"), GetHandleId(u), lifespan - 1)
            if (lifespan <= 0) then
                call FlushChildHashtable(HASH, GetHandleId(u))
                call KillUnit(u)
                call GroupRemoveUnit(GROUP, u)
                if (FirstOfGroup(GROUP) == null) then
                call PauseTimer(TIMER)
            endif
            endif
            
            set u = null
            set caster = null
        endloop
        
        call DestroyGroup(g)
        set g = null
    endfunction
//===========================================================================   
    function Start takes nothing returns nothing
        local unit u = GetTriggerUnit()
        local unit u2
        local player pl = GetOwningPlayer(u)
        local real x = GetUnitX(u)
        local real y = GetUnitY(u)
        local real x2 = GetSpellTargetX()
        local real y2 = GetSpellTargetY()
        local real angle = FixAngle( AngleBetweenCoordinates(x, y, x2, y2) )
        
        if (GetSpellAbilityId() == 'A000') then
        
            set x2 = PolarProjectionX(x, 50, angle)
            set y2 = PolarProjectionY(y, 50, angle)
            set u2 = CreateUnit(pl, 'udum', x2, y2, angle)
        
            call SaveUnitHandle(HASH, StringHash("Caster"), GetHandleId(u2), u)
            call SaveInteger(HASH, StringHash("Cliff"), GetHandleId(u2), GetTerrainCliffLevel(x2, y2))
            call SaveInteger(HASH, StringHash("Lifespan"), GetHandleId(u2), 250)
            call SaveReal(HASH, StringHash("Angle"), GetHandleId(u2), angle)
            call SaveReal(HASH, StringHash("Speed"), GetHandleId(u2), 25.000)

            if (FirstOfGroup(GROUP) == null) then
                call TimerStart(TIMER, 0.05, true, function Loop)
            endif
            call GroupAddUnit(GROUP, u2)
            
        endif
        
        set u = null
        set u2 = null
        set pl = null
    endfunction
//===========================================================================
    function Init takes nothing returns nothing
        local trigger t = CreateTrigger()
        
        call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddAction( t, function Start)
        
        set t = null
    endfunction
//===========================================================================
endlibrary
//===========================================================================
 
Level 18
Joined
Jan 21, 2006
Messages
2,552
I'm curious why you don't use a condition rather than that if-statement in the actions. You can add a condition by using a boolean expression function that takes nothing and returns a boolean.

JASS:
TriggerAddCondition( trig, Filter( function filterFunc ) )
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
One serious problem I found with your code is that you were using the handle id of the triggering unit as a child key when it needs to be stored as a parent key. I have reversed these functions and I've also cleaned up some things.

I also recommend that you change your updater to something faster than .05 seconds, as it is slightly choppy. I recommend you bump that down to 0.03 and adjust the numbers (lifespan, damage dealt, etc.) accordingly.

I deleted your filter "noFilter" as it is useless. An empty handle (null) returns true indiscriminately. Also, I deleted that function "copygroup" as you really should just use ForGroup instead.

Using StringHash is fun, but in a loop this quick you really profit to use as few function calls as possible. I've converted each StringHash("string") into a constant variable as this performs much more quickly.

Your local variables are now global variables, so you never have to null them, which in turn saves on performance.

Also, I deleted "FixAngle" and embedded a natural angle-fix into the AngleBetweenCoordinates function.

JASS:
//________________________________________________________________new library
  library Common
//¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
//________________________________________________________________new globals
    globals
        constant real PlayableAreaMinX = GetCameraBoundMinX() -GetCameraMargin(0)
        constant real PlayableAreaMaxX = GetCameraBoundMaxX() +GetCameraMargin(1)
        constant real PlayableAreaMinY = GetCameraBoundMinY() -GetCameraMargin(3)
        constant real PlayableAreaMaxY = GetCameraBoundMaxY() +GetCameraMargin(2)
    endglobals
//¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
    function PolarProjectionX takes real x, real dist, real angle returns real
        return x+dist*Cos (angle*bj_DEGTORAD)
    endfunction
//———————————————————————————————————————————————————————————————————————————
    function PolarProjectionY takes real y, real dist, real angle returns real
        return y+dist*Sin (angle*bj_DEGTORAD)
    endfunction
//———————————————————————————————————————————————————————————————————————————
    function DistanceBetweenCoordinates takes real x1, real y1, real x2, real y2 returns real
        return SquareRoot ((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2))
    endfunction
//———————————————————————————————————————————————————————————————————————————
    function AngleBetweenCoordinates takes real x1, real y1, real x2, real y2 returns real
        local real fix = bj_RADTODEG*Atan2 (y2-y1,x2-x1)
        if (fix < 0.) then
            return 360.-fix
        endif
        return fix
    endfunction
//________________________________________________________________end library
  endlibrary
//¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
 
//________________________________________________________________new library
  library BouncingProjectile initializer Init requires Common
//¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
//________________________________________________________________new globals
    globals
 
        private hashtable HASH = InitHashtable()
        private integer projectile = 0
        private integer HashCliff = 1
        private integer HashLifespan = 2
        private integer HashAngle = 3
        private integer HashSpeed = 4
        private filterfunc FilterDamage
        private string PATH = "Abilities\\Spells\\Other\\Stampede\\StampedeMissileDeath.mdl"
    endglobals
//¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
    private function BounceUtil takes unit v returns boolean
        if (GetUnitTypeId (v) != 'udum') and (IsUnitEnemy (v,GetOwningPlayer (bj_lastLoadedUnit) )) and not (IsUnitType (v,UNIT_TYPE_FLYING)) then
            call DestroyEffect (AddSpecialEffectTarget (PATH,v,"origin") )
            call UnitDamageTarget (bj_lastLoadedUnit,v,10.,false,true,ATTACK_TYPE_MELEE,DAMAGE_TYPE_NORMAL,WEAPON_TYPE_WHOKNOWS)
        endif
        return false
    endfunction
//———————————————————————————————————————————————————————————————————————————
    private function BounceDamage takes nothing returns boolean
        return BounceUtil (GetFilterUnit() )
    endfunction
//———————————————————————————————————————————————————————————————————————————
    private function looputilx takes real x,real y returns boolean
        if (GetTerrainCliffLevel (x,y) > cliff) or (x <= PlayableAreaMinX) or (x >= PlayableAreaMaxX) then
            return true
        endif
        return false
    endfunction
//———————————————————————————————————————————————————————————————————————————
    private function looputily takes real y,real x returns boolean
        if (GetTerrainCliffLevel (x,y) > cliff) or (y <= PlayableAreaMinY) or (y >= PlayableAreaMaxY) then
            return true
        endif
        return false
    endfunction
//———————————————————————————————————————————————————————————————————————————
    private function looputil takes unit u,integer ID returns nothing
        local real x = GetWidgetX (u)
        local real y = GetWidgetY (u)
        local integer cliff = LoadInteger (HASH,ID,HashCliff)
        local real angle = LoadReal (HASH,ID,HashAngle)
        local real speed = LoadReal (HASH,ID,HashSpeed)
        local real lifespan = LoadInteger (HASH,ID,HashLifespan)
        local boolean bounceX = looputilx ( PolarProjectionX (x, speed, angle) ,y)
        local boolean bounceY = looputily ( PolarProjectionY (y, speed, angle) ,x)
 
        if (bounceX) or (bounceY) then
            if (bounceX) and (bounceY) then
                set angle = 180.+angle 
            elseif (bounceX) then
                set angle = 360.-angle+180.
            else
                set angle = 360.-angle
            endif
            set x = PolarProjectionX (x,speed,angle)
            set y = PolarProjectionY (y,speed,angle)
        endif
 
        call SetUnitPosition (u,x,y)
        set bj_lastLoadedUnit = LoadUnitHandle (HASH,ID,5)
        call GroupEnumUnitsInRange (bj_lastCreatedGroup,x,y,100.,FilterDamage)
 
        if (lifespan < 1) then
            call FlushChildHashtable (HASH,ID)
            call KillUnit (u)
            call DestroyTimer (GetExpiredTimer() )
        else
            call SaveReal (HASH,ID,HashAngle,angle)
            call SaveInteger (HASH,ID,HashCliff,GetTerrainCliffLevel (x,y) )
            call SaveInteger (HASH,ID,HashLifespan,lifespan - 1)
        endif
    endfunction
//———————————————————————————————————————————————————————————————————————————
    private function Loop takes nothing returns nothing
        local integer ID = GetHandleId (GetExpiredTimer() )
        call looputil (LoadUnitHandle (HASH,ID,projectile) ,ID)
    endfunction
//———————————————————————————————————————————————————————————————————————————
    private function startutil takes timer t,unit u returns timer
        local real ID = GetHandleId (t)
        local real x = GetWidgetX (u)
        local real y = GetWidgetY (u)
        local real angle = AngleBetweenCoordinates (x,y,GetSpellTargetX() ,GetSpellTargetY() )
        set x = PolarProjectionX (x,50.,angle)
        set y = PolarProjectionY (y,50.,angle)
        call SaveAgentHandle (HASH,ID,5,u)
        call SaveAgentHandle (HASH,ID,projectile,CreateUnit (GetOwningPlayer (u) ,'udum',x,y,angle) )
        call SaveInteger (HASH,ID,HashCliff,GetTerrainCliffLevel (x,y))
        call SaveInteger (HASH,ID,HashLifespan,250)
        call SaveReal (HASH,ID,HashAngle,angle)
        call SaveReal (HASH,ID,HashSpeed,25.)
        return t
    endfunction
//———————————————————————————————————————————————————————————————————————————
    private function Start takes unit u returns nothing
        call TimerStart (startutil (CreateTimer() ,u) ,0.05,true,function Loop)
    endfunction
//———————————————————————————————————————————————————————————————————————————
    private function Evaluate takes nothing returns nothing
        if (GetSpellAbilityId() == 'A000') then
            call Start (GetTriggerUnit() )
        endif
    endfunction
//———————————————————————————————————————————————————————————————————————————
    private function Init takes nothing returns nothing
        local trigger bouncetrig = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ (bouncetrig,EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddAction (bouncetrig,function Evaluate)
        set FilterDamage = Filter (function BounceDamage)
    endfunction
//________________________________________________________________end library
  endlibrary
//¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
 
Last edited:
Level 9
Joined
Jun 7, 2007
Messages
195
Thanks a lot, I'll test it now.

Btw. Is there a reason why you didn't make most of those globals 'constant' most of them retain their initial values and ain't constants faster? Also I think I read a thread that concluded, using 'null' as a filter leaks.

EDIT: Tested but there's errors. You forgot to deal with the local in the CliffLevel check. The projectile is supposed to be allowed flying downhill but not uphill. So if the new cliff level is above the old one it should bounce off.
JASS:
if (GetTerrainCliffLevel (x,y) > cliff) or (y <= PlayableAreaMinY) or (y >= PlayableAreaMaxY) then
 
Last edited:

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
Null as a filter used to leak, a long time ago, but they fixed it in one of the recent patches. You can change the variables to constant if you want (private constant)

JASS:
private function looputily takes real y,real x

I dealt with the local. This is a straight-out copy of your code, I just passed it to this function.

Adding constant as a prefix doesn't make functions faster, according to people here, so I don't know if constant variables are really faster.
 
Level 12
Joined
Apr 15, 2008
Messages
1,063
Yes exactly that, you can use bigger array size (720 or 1440) for more precise movement. However, if you use angles just to bounce, 360 could be enough (if you wanted to change the angle by a really small number in the middle of the flight, then you should use more precision)

Also, if you used vector-based movement (saving x and y speed separately), you wouldn't need to deal with this (and bouncing of cliffs would be much easier)
 
Level 9
Joined
Jun 7, 2007
Messages
195
Thanks for the info and for the idea of using x and y speeds. This is my first bouncing projectile test so I just want to get it work efficiently some way before moving to more advanced things. I'll try that when I make another version.

Incase someone was going to point it out, I am well aware that these things have been done before and I could just fetch a pre-made system, however using those without fully understanding how they work won't teach me anything. So I do these things to learn.

EDIT: Actually my original spell worked just fine, the lag was caused by the hashtable that wasn't flushed after it was used. It wasn't getting flushed because I had been using the two values in eachothers' places.
JASS:
call SaveInteger(Hashtable, integer_1, integer_2, value)
I just swapped 'integer_1' and 'integer_2' and the flushing is now being done correctly. Tested in single player and I cannot make it lag with moderate amount of simuntaious casts. It just might lag in multiplayer but if this is used as an ability with cooldown of atleast several seconds and each player can only have 1 unit that casts this, there is NO way this is ever going to cause lag by itself. Great!
 
Last edited:

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
EDIT: Actually my original spell worked just fine, the lag was caused by the hashtable that wasn't flushed after it was used. It wasn't getting flushed because I had been using the two values in eachothers' places.
Yeah, I told you that already:

One serious problem I found with your code is that you were using the handle id of the triggering unit as a child key when it needs to be stored as a parent key. I have reversed these functions and I've also cleaned up some things.
 
Status
Not open for further replies.
Top