• 🏆 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!
  • 🏆 Hive's 6th HD Modeling Contest: Mechanical is now open! Design and model a mechanical creature, mechanized animal, a futuristic robotic being, or anything else your imagination can tinker with! 📅 Submissions close on June 30, 2024. Don't miss this opportunity to let your creativity shine! Enter now and show us your mechanical masterpiece! 🔗 Click here to enter!

Custom Rain of Fire lagging, possible leak.

Status
Not open for further replies.
Level 20
Joined
May 16, 2012
Messages
635
Hello Hive! I created a custom Rain of Fire for one of my Heroes in my map, because i don't want it to be channeled, and i'm in a bit of a dead end now, because i think something is leaking but i cant find out what. There's nothing else being executed in the map, and i pre-placed a couple dozen units to test the ability, and it seems that the more i use it the less fps i got. i start with around 130 fps and after 10 casts or so, my fps is down to 60 or less. I'm using BPower Missile System and the core destructor for it, is returning true in one of the methods, which i am. I also add a couple of debug messages to be sure that the timers i initialize are being stopped and destroyed, and they are. If someone has any idea, plz tell me.

JASS:
scope Monnoroth initializer Init

globals
    integer array RoFDot
endglobals

private function DoRoFDot takes nothing returns nothing
    local timer t          = GetExpiredTimer()
    local unit source    = LoadUnitHandle(udg_HeroHash, GetHandleId(t), 1)
    local unit target     = LoadUnitHandle(udg_HeroHash, GetHandleId(t), 2)
    local real dot         = LoadReal(udg_HeroHash, GetHandleId(t), 3)
    local integer index = GetUnitUserData(target)

    set RoFDot[index] = RoFDot[index] - 1
    call UnitDamageTarget(source, target, dot, true, false, ATTACK_TYPE_HERO, DAMAGE_TYPE_UNIVERSAL, null)

    if RoFDot[index] <= 0 or not UnitAlive(target) then
        call DebugMsg("DoRoFDot!")
        set RoFDot[index] = 0
        call DestroyEffect(LoadEffectHandle(udg_HeroHash, GetHandleId(target), 46))
        call RemoveSavedHandle(udg_HeroHash, GetHandleId(target), 46)
        call FlushChildHashtable(udg_HeroHash, GetHandleId(t))
        call PauseTimer(t)
        call DestroyTimer(t)
    endif
   
    set t = null
    set source = null
    set target = null
endfunction

private function RainOfFireDot takes unit source, unit target, real dot returns nothing
    local timer t
    local effect e
    local integer index   = GetUnitUserData(target)
    local integer counter = RoFDot[index] 

    if counter == 0 then
        set t = CreateTimer()
        set e = AddSpecialEffectTarget("BurnLarge.mdx", target, "origin")

        call SaveEffectHandle(udg_HeroHash, GetHandleId(target), 46, e)
        call SaveUnitHandle(udg_HeroHash, GetHandleId(t), 1, source)
        call SaveUnitHandle(udg_HeroHash, GetHandleId(t), 2, target)
        call SaveReal(udg_HeroHash, GetHandleId(t), 3, dot)
        call TimerStart(t, 1, true, function DoRoFDot)
    endif

    if (counter + 4) > 4 then
        set RoFDot[index] = RoFDot[index] + (4 - counter)
    else
        set RoFDot[index] = RoFDot[index] + 4
    endif

    set t = null
    set e = null
endfunction

private struct FireMeteor extends array
    real dot

    static method onFinish takes Missile missile returns boolean
        local thistype this = thistype(missile)
        local group g = CreateGroup()
        local unit u

        call GroupEnumUnitsInRange(g, missile.x, missile.y, 100, null)
        loop
            set u = FirstOfGroup(g)
            exitwhen u == null
                if UnitAlive(u) and IsUnitEnemy(u, missile.owner) then
                    if UnitDamageTarget(missile.source, u, missile.damage, false, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_MAGIC, null) then
                        call RainOfFireDot(missile.source, u, this.dot)
                    endif
                endif
            call GroupRemoveUnit(g, u)
        endloop
        call DestroyGroup(g)

        set g = null
        return true
    endmethod
   
    implement MissileStruct
endstruct

private function LaunchMeteor takes Missile meteor, unit caster, integer level, string fx returns nothing
    set meteor.scale = 1.5
    set meteor.damage = (500*level / (6 - level))
    set meteor.model = fx
    set meteor.source = caster
    set meteor.owner = GetOwningPlayer(caster)
    set meteor.acceleration = 0.0
    set FireMeteor(meteor).dot = (250*level / (6 - level))
    call meteor.flightTime2Speed(1.0)
    call FireMeteor.launch(meteor)
endfunction

private function GetRandomRange takes real radius returns real
    local real r = GetRandomReal(0, 1) + GetRandomReal(0, 1)

    if r > 1 then
        return (2 - r)*radius
    endif

    return r*radius
endfunction

private function RainOfFire takes nothing returns nothing
    local timer t         = GetExpiredTimer()
    local unit u         = LoadUnitHandle(udg_HeroHash, GetHandleId(t), 1)
    local integer level = LoadInteger(udg_HeroHash, GetHandleId(t), 2)
    local real interval = LoadReal(udg_HeroHash, GetHandleId(t), 3)
    local real centerX  = LoadReal(udg_HeroHash, GetHandleId(t), 4)
    local real centerY  = LoadReal(udg_HeroHash, GetHandleId(t), 5)
    local real duration = LoadReal(udg_HeroHash, GetHandleId(t), 6)
    //parameters
    local real maxRange
    local real theta
    local real radius
    local real posX
    local real posY
    local real angle
    local real maxX
    local real maxY
    local real dX 
    local real dY
    //missile                                               
    local Missile meteor

    set duration = duration + interval
    call SaveReal(udg_HeroHash, GetHandleId(t), 6, duration)
   
    if duration < 10 then
        set maxRange = (250 + 25*level)
        set theta     = 2*bj_PI*GetRandomReal(0, 1)
        set radius     = GetRandomRange(maxRange)
        set posX     = centerX + radius*Cos(theta)
        set posY     = centerY + radius*Sin(theta)
        set angle     = 2*bj_PI*GetRandomReal(0, 1)
        set maxX     = centerX + maxRange*Cos(angle)
        set maxY     = centerY + maxRange*Sin(angle)
        set dX         = posX - maxX
        set dY         = posY - maxY 
        set meteor  = Missile.createXYZ(posX, posY, 2000, posX, posY, 0)

        call LaunchMeteor(meteor, u, level, "Rain of Fire Vol. II Missile.mdx")
    else
        call DebugMsg("RainOfFire!")
        call FlushChildHashtable(udg_HeroHash, GetHandleId(t))
        call PauseTimer(t)
        call DestroyTimer(t)
    endif

    set t = null
    set u = null
endfunction

private function DoRainOfFire takes nothing returns nothing
    local unit u         = GetTriggerUnit()
    local integer level = GetUnitAbilityLevel(u, 'A02N')
    local real interval = 0.05*(6 - level)
    local real centerX  = GetSpellTargetX()
    local real centerY  = GetSpellTargetY()
    local timer t         = CreateTimer()

    call SaveUnitHandle(udg_HeroHash, GetHandleId(t), 1, u)
    call SaveInteger(udg_HeroHash, GetHandleId(t), 2, level)
    call SaveReal(udg_HeroHash, GetHandleId(t), 3, interval)
    call SaveReal(udg_HeroHash, GetHandleId(t), 4, centerX)
    call SaveReal(udg_HeroHash, GetHandleId(t), 5, centerY)
    call SaveReal(udg_HeroHash, GetHandleId(t), 6, 0)
    call TimerStart(t, interval, true, function RainOfFire)

    set t = null
    set u = null
endfunction
//===========================================================================
private function Init takes nothing returns nothing
    call RegisterSpellEffectEvent('A02N', function DoRainOfFire)
endfunction

endscope
 
Level 20
Joined
May 16, 2012
Messages
635
I didn’t look at your code but if all you were trying to do was not make it channeling, why didn’t you just make a fake spell based on Channel and dummy cast RoF on the target location?

Because then, it will not be the hero dealing the damage, and there's a lot of important stuff that happen on damage that would not be correctly executed this way. I thought of it, its simpler and easier, but do not suit my need.
 
Level 39
Joined
Feb 27, 2007
Messages
5,033
You could store the 'owning' unit of the dummy caster in a hashtable whenever the dummy is created. Then in any on-damage triggers detect if the source is a dummy; if so, load the proper 'owner' unit from the hashtable and do whatever you want with it.
 
Level 20
Joined
Aug 13, 2013
Messages
1,696
Specify how's the performance behaving on start, during, end.
If it's continuous, (non-stop FPS breakdown) even the visuals are gone, obviously there's a repeatedly intensive operations on-going in the map.

There are some factors you may consider;
- Make sure each instance you do is properly created and destroyed. (Including the systems you're using)
- Check intervals, timeouts, iteration times.
- Make sure dummies are removed properly. (Replace its model to any kind of default unit models inside the object editor)
- Special effects are destroyed properly.
- Model performance tests.

These may help to track down the problem.
 
Level 20
Joined
May 16, 2012
Messages
635
i Found the problem, but not sure how to solve it. It's one of the required libraries of the BPower Missile System, Missile Recycler from @Bribe . While recycling the missile dummies it misses a few with every cast, and the units remain in position, resulting in the problem. I dont know if the problem is the library it self or something else in my map.

Edit 1: Was trying to debug the MissileRecycler, and it seems that for some reason when it tries to remove surplus units by adding a timed life to the dummy, it does not. just doesn't. right here:

JASS:
if queueStackN == 0 then
                call Print("Stack is full - removing surplus unit")
                call UnitApplyTimedLife(u, 'BTLF', DEATH_TIME)
                return
            endif

the message is shown, but the unit remains after the 2.0 seconds default death time.

Edit 2: I tried a solution that worked, but i'm yet to see if its going to become a problem in the future. So, Instead of doing
JASS:
call UnitApplyTimedLife(u, 'BTLF', DEATH_TIME)
, i changed it to
JASS:
call KillUnit(u)
and in the Missile dummy unit, i changed the "Death Time" field to 2.0 seconds. So far so good.
 
Last edited:

Dr Super Good

Spell Reviewer
Level 64
Joined
Jan 18, 2005
Messages
27,202
For pure visuals only there are special effect solutions that avoid dummy units now thanks to all the new actions and functions. Dummies are still needed to cast abilities though, but these could be recycled from a pool of generic, invisible same type dummies.
 
Level 20
Joined
Aug 13, 2013
Messages
1,696
It should work, which version do you use, you can easily debug that function on your map by the way (outside the system's scope). If it doesn't work then there's something on the map, so create a new empty map then test.
 
Level 20
Joined
May 16, 2012
Messages
635
It should work, which version do you use, you can easily debug that function on your map by the way (outside the system's scope). If it doesn't work then there's something on the map, so create a new empty map then test.

Latest Reforged version. It work outside the system, just in that conditional block that it's bugged. The unit exists, and match all the conditions, but it stay alive after the call.
 
Status
Not open for further replies.
Top