Frame rate dropped after using spell

270898

Active Member
Reaction score
3
After using the spell for a few times, the game's frame rate dropped. :(
Then I went to the task manager and found out that the processor is serving 50% of this power to warcraft. (Its a duo core CPU)
I checked for leaks and it doesn't seemed to have any...
Or maybe I have missed out some of them :eek:

Here is the code

JASS:
scope PiercingThunder initializer init    
scope PiercingThunder initializer init    
globals
    private constant integer AbiId='A004'
    private constant integer Range=100
endglobals
        
private struct dat
    unit u
    integer level
    real x
    real y
    real ox
    real oy
    real oz
    group g = CreateGroup()
    real angle
    lightning bolt
    static method create takes unit cu,location loc returns dat
        local dat this=.allocate()
        set .u=cu
        set .level=GetUnitAbilityLevel(cu,AbiId)
        set .x=GetLocationX(loc)
        set .y=GetLocationY(loc)
        set .oz=GetLocationZ(loc)
        set .ox=GetUnitX(.u)
        set .oy=GetUnitY(.u)
        set .oz=GetUnitZ(.u)
        set .angle=bj_RADTODEG*Atan2(.y - GetUnitY(.u), .x - GetUnitX(.u))
        set .bolt=AddLightningEx("FORK",true,.ox,.oy,.oz,GetUnitX(.u),GetUnitY(.u),GetUnitZ(.u))
        return this
    endmethod
    
    static method setLightning takes dat this returns nothing
        call MoveLightningEx(.bolt,true,.ox,.oy,.oz,GetUnitX(.u),GetUnitY(.u),GetUnitZ(.u))
    endmethod
    
    method onDestroy takes nothing returns nothing
        call GroupClear(.g)
        call DestroyLightning(.bolt)
    endmethod
endstruct
    
private function damage takes integer i returns real
    if i==1 then
        return 100.
    endif
    if i==2 then
        return 150.
    endif
    if i==3 then
        return 225.
    endif
    return 300.
endfunction

private function Check takes nothing returns boolean
    return IsUnitType(GetFilterUnit(), UNIT_TYPE_STRUCTURE)!=true and GetWidgetLife(GetFilterUnit())> .405
endfunction
    
private function tick takes nothing returns boolean
    local dat d=KT_GetData()
    local unit tempu
    local real rx=GetUnitX(d.u)
    local real ry=GetUnitY(d.u)
    local real x = rx-d.x
    local real y = ry-d.y
    local real dis=SquareRoot(x*x+y*y)    
    local group tempg=CreateGroup()
    local rect re = Rect( rx-Range, ry-Range,rx+Range, ry+Range )
    call d.setLightning(d)
//Check unit distance   
    if dis<50 then
        call SetUnitPosition(d.u,d.x,d.y)
        call SetUnitPathing(d.u,true)
        call GroupClear(tempg)
        call RemoveRect(re)
        call d.destroy()
        return true
    else
        call SetUnitPosition(d.u,rx+50*Cos(d.angle*bj_DEGTORAD),ry+50*Sin(d.angle*bj_DEGTORAD))
    endif
//Get unit in range    
    call GroupEnumUnitsInRect(tempg,re,Condition(function Check))
//Damage loop    
    loop
        set tempu=FirstOfGroup(tempg)
        exitwhen tempu==null
        if IsUnitInGroup(tempu,d.g) == false and IsUnitEnemy(tempu,GetOwningPlayer(d.u)) then
            call UnitDamageTarget(d.u,tempu,damage(d.level),true,false,ATTACK_TYPE_HERO,DAMAGE_TYPE_NORMAL,null)
        endif
        call GroupRemoveUnit(tempg,tempu)
        call GroupAddUnit(d.g,tempu)
    endloop
    call GroupClear(tempg)
    call RemoveRect(re)
    return false
endfunction

private function a takes nothing returns nothing
    local dat d=dat.create(GetTriggerUnit(),GetSpellTargetLoc())
    call KT_Add(function tick,d,0.0125)
    call SetUnitPathing(d.u,false)
endfunction

private function c takes nothing returns boolean
    if GetSpellAbilityId()==AbiId then
        call a()
    endif
    return false
endfunction 

private function init takes nothing returns nothing
    local trigger t=CreateTrigger()
    local integer index=0
    loop
        call TriggerRegisterPlayerUnitEvent( t, Player(index), EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
        set index = index + 1
        exitwhen index == bj_MAX_PLAYER_SLOTS
    endloop
    call TriggerAddCondition(t,Condition(function c))
endfunction
endscope


Edit: Updated with destroying groups, but still lags
Edit2: Updated another time with a few leaks removed, but still lags even after the spell is cast.
Edit3: Something amusing, when I removed most of the functions in the trigger (except the init and condition) the processing power rose and never comes down! I am 100% sure its with this code as the problem didn't occur when I cast different spells with the same unit.

The new code
JASS:
scope PiercingThunder initializer init    
globals
    private constant integer AbiId='A004'
    private constant integer Range=100
endglobals

private function a takes nothing returns nothing
    local location spellTar=GetSpellTargetLoc()
    //local dat d=dat.create(GetTriggerUnit(),spellTar)
    call RemoveLocation(spellTar)
    //call KT_Add(function tick,d,0.0125)
    //call SetUnitPathing(d.u,false)
endfunction

private function c takes nothing returns boolean
    if GetSpellAbilityId()==AbiId then
        call a()
    endif
    return false
endfunction 

private function init takes nothing returns nothing
    local trigger t=CreateTrigger()
    local integer index=0
    loop
        call TriggerRegisterPlayerUnitEvent( t, Player(index), EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
        set index = index + 1
        exitwhen index == bj_MAX_PLAYER_SLOTS
    endloop
    call TriggerAddCondition(t,Condition(function c))
endfunction
endscope
 

Executor

I see you
Reaction score
57
JASS:
//...
private function a takes nothing returns nothing
    local dat d=dat.create(GetTriggerUnit(),GetSpellTargetLoc())
    // why not GetSpellTargetX() and GetSpellTargetY() directly?
    // because, you NEVER destroy the location you create with GetSpellTargetLoc()
    //...


JASS:
private function tick takes nothing returns boolean
    //...   
    local group tempg=CreateGroup() // the group is never destroyed.
    //...
    call GroupEnumUnitsInRect(tempg,re,Condition(function Check)) // could be that the result of Condition() also leaks, dunno ..
    //...
    call GroupClear(tempg) // and you generate a NEW group EVERY tick
    call RemoveRect(re)
    return false
endfunction
 

270898

Active Member
Reaction score
3
I added those DestroyGroup() functions but the processor is still at 50% constant.

I didn't manage to find GetSpellTargetX() nor Y...
 

Viikuna

No Marlo no game.
Reaction score
265
DestroyGroup is bad.

There is one cool tutorial about how to properly use groups in our tutorial database.

Read it.
 

Executor

I see you
Reaction score
57
JASS:
scope PiercingThunder

/*function GetFloorHeight takes real x, real y returns real
    globals
        private location L = Location(0,0)
    endglobals
    call MoveLocation(L,x,y)
    return GetLocationZ(L)
endfunction*/
        
private struct Spell
    static constant integer ABIL_ID     = 'A004'
    static constant real    PERIOD      = 0.0125
    static constant real    HIT_RANGE   = 100
    static constant real    BREAK_RANGE = 50.
    static constant real    SPEED       = 25
    static constant string  SFX_MODEL   = "FORK"
    
    static conditionfunc enumCond
    static thistype      tempInstance
    static group         ENUM
    
    private static method DamagePerLvl takes integer lvl returns real
        if lvl==1 then
            return 100.
        elseif lvl==2 then
            return 150.
        elseif lvl==3 then
            return 225.
        endif
        return 300.
    endmethod
    
    unit        caster
    integer     lvl
    real        a
    real        cx
    real        cy
    real        cz
    
    real        tx
    real        ty
    real        tz
    
    group       damaged      
    lightning   sfx
    

    static method create takes unit caster, real tx, real ty returns thistype
        local thistype this = thistype.allocate()
        set .caster = caster
        set .lvl    = GetUnitAbilityLevel(caster,.ABIL_ID)
        set .tx     = tx
        set .ty     = ty
        set .tz     = GetFloorHeight(tx,ty)
        
        set .cx     = GetUnitX(.caster)
        set .cy     = GetUnitY(.caster)
        set .cz     = GetFloorHeight(.cx,.cy)
        
        set .a      = Atan2(.ty - .cy, .tx - .cx)
        
        if .damaged == null then
            set .damaged = CreateGroup() // recycle
        endif
        
        call SetUnitPathing(.caster,false)
        
        set .sfx=AddLightningEx(SFX_MODEL,true,.tx,.ty,.tz,.cx,.cy,.cz)
        return this
    endmethod
    
    private static method enumFunc takes nothing returns boolean
        local unit u = GetFilterUnit()
        local thistype this = .tempInstance
        if (not IsUnitType(u, UNIT_TYPE_STRUCTURE)) and ((GetWidgetLife(u))> .405)/*
        */ and (not IsUnitInGroup(u,.damaged)) and (IsUnitEnemy(u,GetOwningPlayer(.caster))) then
        
            call UnitDamageTarget(.caster,u,.DamagePerLvl(.lvl),true,false,ATTACK_TYPE_HERO,DAMAGE_TYPE_NORMAL,null)
            call GroupAddUnit(.damaged,u)
        endif
        return false
    endmethod
    
    private static method onTimer takes nothing returns boolean
        local thistype this = KT_GetData()
        
        local real x = GetUnitX(.caster)
        local real y = GetUnitY(.caster)
        
        local real vx = x - .tx
        local real vy = y - .ty
        local real dis = SquareRoot(vx*vx+vy*vy)  
         
        if dis < .BREAK_RANGE then
            call SetUnitPosition(.caster,.cx,.cy)
            call SetUnitPathing(.caster,true)
            
            // destroy :
            call GroupClear(.damaged)
            call DestroyLightning(.sfx)
            call .destroy()
            return true
        else
            call SetUnitPosition(.caster,x+.SPEED*Cos(.a),y+.SPEED*Sin(.a))
        endif  
        
        set .tempInstance = this
        call GroupEnumUnitsInRange(.ENUM,x,y,HIT_RANGE,.enumCond)  
        
        call MoveLightningEx(.sfx,true,.tx,.ty,.tz,x,y,.cz)
        
        return false
    endmethod
    
    private static method onCast takes nothing returns boolean
        if GetSpellAbilityId() == ABIL_ID then
            call KT_Add(function thistype.onTimer,thistype.create(GetTriggerUnit(),GetSpellTargetX(),GetSpellTargetY()),.PERIOD)
        endif
        return false
    endmethod

    private static method onInit takes nothing returns nothing
        local trigger t = CreateTrigger()
        local integer i = 0
        loop
            call TriggerRegisterPlayerUnitEvent( t, Player(i), EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
            set i = i + 1
            exitwhen i == bj_MAX_PLAYER_SLOTS
        endloop
        call TriggerAddCondition(t,Condition(function thistype.onCast))
        
        set .enumCond = Condition(function thistype.enumFunc)
        set .ENUM = CreateGroup()
    endmethod
endstruct

endscope


I'm still working on the optimization. There should still be some errors.

//Edit: Finished. Should work now.
 

No_exit

Regular User (What is Custom User Title?)
Reaction score
40
You cannot get a dual core to sweat with simple leaks (well ... you can but you need LOTS of them).



You need something like:
- A couple of timers running 50+ times per second that never gets stopped.

- 100+ visible WC3 objects that are created but never removed (units, effects, lightning, doodads, any other I possibly forgot. Integers, arrays, groups ... are not visible and thus not included here.)

- Really bad Leaks of non-visible objects in loops that run (we are talking >1000 per second). This is most likely to happen when you have an infinite loop that has a leak in it (infinite loops are automatically broken down by WC3 after around 8000 loops. Any code after the loop is never executed.)


So at first glance I see 4 possible problems:
1) Never ending the stop condition of "dis < 50" and thus having a timer going wild. This should give an increase in lag every time the function is run, but after that stable.

2) Reaching the end condition of "dis < 50" but never stopping the main timer. Note that if this is the case, you will get some leaks (a factor 80 per second) because for example the group you create at the start of your time never gets removed.

3) MoveLightningEx doesn't move the lightning effect but actually creates a new one each second. This type of lag should be less when you are not looking at the spot where the 100+ lightning effects are.
(Never used this function but you never know.)

4) If the following loop
JASS:
loop
        set tempu=FirstOfGroup(tempg)
        exitwhen tempu==null
        if IsUnitInGroup(tempu,d.g) == false and IsUnitEnemy(tempu,GetOwningPlayer(d.u)) then
            call UnitDamageTarget(d.u,tempu,damage(d.level),true,false,ATTACK_TYPE_HERO,DAMAGE_TYPE_NORMAL,null)
        endif
        call GroupRemoveUnit(tempg,tempu)
        call GroupAddUnit(d.g,tempu)
    endloop

is infinite (actually, any inifnite loop ends after around 8100 times) and creates some major leak (remember that the timer tick is 80 times per second so an infinite loop creates around 80*8000 = 640000 memory leaks per second, that is what I call a leak that could take down a dual core.)

So it is up to you to find which of the 4 it is. My first guess is 2), my second is 1).

Good luck.
 

Executor

I see you
Reaction score
57
1) Never ending the stop condition of "dis < 50" and thus having a timer going wild. This should give an increase in lag every time the function is run, but after that stable.

If "dis < 50" wouldn't be reached, casting "few times" is not possible.

2) Reaching the end condition of "dis < 50" but never stopping the main timer. Note that if this is the case, you will get some leaks (a factor 80 per second) because for example the group you create at the start of your time never gets removed.

Why should the "main" timer not stop?!

3) MoveLightningEx doesn't move the lightning effect but actually creates a new one each second. This type of lag should be less when you are not looking at the spot where the 100+ lightning effects are.
(Never used this function but you never know.)

At least I know.

4) If the following loop
JASS:
loop
        set tempu=FirstOfGroup(tempg)
        exitwhen tempu==null
        if IsUnitInGroup(tempu,d.g) == false and IsUnitEnemy(tempu,GetOwningPlayer(d.u)) then
            call UnitDamageTarget(d.u,tempu,damage(d.level),true,false,ATTACK_TYPE_HERO,DAMAGE_TYPE_NORMAL,null)
        endif
        call GroupRemoveUnit(tempg,tempu)
        call GroupAddUnit(d.g,tempu)
    endloop

is infinite (actually, any inifnite loop ends after around 8100 times) and creates some major leak (remember that the timer tick is 80 times per second so an infinite loop creates around 80*8000 = 640000 memory leaks per second, that is what I call a leak that could take down a dual core.)

HOW can this loop be infinite? That's like guessing
loop i = i - 1; exitwhen i < 0 endloop would be infinite too.
 

270898

Active Member
Reaction score
3
Its neither of the 4. I placed DebugMsg in them and tried running, the whole trigger is stopped after everything ends. Thats why I am troubled.

@Executor: Thanks for the rewritten code but I don't really love coding in structs >.>
 

Executor

I see you
Reaction score
57
Lol. Okey, you don't have to use my code, but you see what you can do better.
For example it is faster not to use action&condition and to pack all in the condition.
The best way to enum, is to use one global group and do the enum action in the filter. etc.
 

270898

Active Member
Reaction score
3
Ok, I have updated slightly but i think the point of putting everything into conditions wouldn't actually help much in solving the lagness which still remains after the spell is casted.
I really think the problem lies somewhere with the function calls not ending..
 

INCINERATE

New Member
Reaction score
12
see if you can put in a handle checker to more identify when this is actually creating the most amount of lag or leaks , who knows, it might not even be this trigg :p , did u mess around with keytimers a lil? maybe u can try .0225 instead
 

270898

Active Member
Reaction score
3
handle checker??
I will try the keytimers, but I used this value for another trigger which worked fine
Edit: Doesn't have any difference...
 

Zwiebelchen

You can change this now in User CP.
Reaction score
60
Some suggestions to increase performance and maybe fix that problem:

1) try to do the same spell without art and graphics (no create or removal of units, lightnings, etc.) and check performance again

2) You run 3 loops every tick, which is way more than you would actually need (First, you enumerate all units in rect, checking the Filter function, then, you loop through the added members AGAIN and do what you need, using IsUnitInGroup, which is, again, some kind of loop operation (it is not really a loop operation, but its almost as slow).
You could put all those loops into one single filter function for GroupEnumUnitsInRect.

conclusion:
Looping through already enumed groups is just wrong. Always run your actions directly inside the filter and return false in your filter so that the group always remains empty - especially when the function is called 40 times per second! IsUnitInGroup is also a bad choice for high frequency enumerations.

Also, there are LOTS of leaks in your spell, some of them being handle leaks (for example your struct member group, which is never destroyed), some memory leaks.
And why is setLightning static and why do you pass the struct to it? It's a method. You don't need to pass the struct to it.
 

270898

Active Member
Reaction score
3
I have to say that there is actually no lag when I first cast it afew times, around 10.
But after around say 20, it starts lagging throughout the game and stays laggy even after the spell effect is over.

destroying groups as in DestroyGroup()??
There was some guy who said it isnt good, and some which said good.

@Executor, i tried using your codes while disabling mine, it still had the same problem, the processor is still at 50% constant
 

270898

Active Member
Reaction score
3
hm... then something is really wrong, how come it only applies to my comp!
I can play DotA fine too.
 
General chit-chat
Help Users
  • No one is chatting at the moment.

      The Helper Discord

      Staff online

      Members online

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top