• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.

[vJASS] Reducing lag with multiple instances of high-dummy count spells?

Status
Not open for further replies.
Level 4
Joined
Sep 25, 2005
Messages
71
Ahoy there. So, I've been trying to create a "shotgun" sort of effect basically that fires off a cone made of a relatively high count of semi-random arrows. Single casts are fine, but simultaneous counts lag hard. I was wondering if someone could recognize something that would cause this problem in my code? I mean, it's not a big deal ultimately since it functions, but lagging with multiple uses seems bad.

I've tried reducing the move interval (didn't change anything) and the actual arrow count (helps, but the problem still persists with enough simultaneous casts.

Am I just going to have to deal with never having a few "shotgun" effects on map/same screen to reduce the lag?

JASS:
scope Arrowblast initializer init

globals
    private constant integer                ABIL_ID             = 'A00P'
    private constant integer                DUMMY_ID            = 'o00A'
    private constant integer                ARROW_NUMBER        = 20
    private constant real                   ANGLE_VARIATION     = 7.5
    private constant real                   CAST_RANGE          = 700.
    private constant real                   COLLISION_SIZE      = 94.
    private constant real                   DAMAGE_AMOUNT       = 5.
    private constant real                   DAMAGE_PER_LEVEL    = 0.
    private constant real                   RANGE_VARIATION     = 128.
    private constant real                   MOVESPEED           = 22.
    private constant real                   MOVESPEED_RANDOM    = 6.
    private constant real                   MOVE_INTERVAL       = 0.0125
    private constant real                   ARROW_Z_DEFAULT     = 56.
    private constant real                   ARROW_Z_RANDOM      = 38.
    private constant string                 HIT_FX              = "Objects\\Spawnmodels\\Critters\\Albatross\\CritterBloodAlbatross.mdl"
    private constant string                 HIT_ATTACH_POINT    = "chest"
    
endglobals

struct Arrowblast
    unit Caster
    unit array Arrow[ARROW_NUMBER]
    real TargetX
    real TargetY
    real array Distance[ARROW_NUMBER]
    real array MaxDist[ARROW_NUMBER]
    real array ArrowZ[ARROW_NUMBER]
    group array Hit[ARROW_NUMBER]
    group ArrowEnum = CreateGroup()
    timer Mover
    integer ArrowsMaxed
    
    private static method math takes real y2, real y1, real x2, real x1 returns real
        local real output = bj_RADTODEG*Atan2(y2 - y1, x2 - x1)
        return output
    endmethod
    
    private static method distancebetween takes real x2, real x1, real y2, real y1 returns real
        local real x = x2 - x1
        local real y = y2 - y1
        return SquareRoot(x*x+y*y)
    endmethod
    
    private static method move takes unit u, real speed, real angle returns nothing
        local real x
        local real y
        set x = GetUnitX(u) + speed * Cos(angle * bj_DEGTORAD)
        set y = GetUnitY(u) + speed * Sin(angle * bj_DEGTORAD)
        call SetUnitBoundedX(u, x)
        call SetUnitBoundedY(u, y)
    endmethod
    
    private static method validtarget takes unit caster, unit enemy, real z, real collisionsize returns boolean
        if IsUnitEnemy(enemy, GetOwningPlayer(caster)) == true /*
        */and GetWidgetLife(enemy) > 0.405 == true /*
        */and GetUnitFlyHeight(enemy) > z+collisionsize  == false /*
        */and GetUnitFlyHeight(enemy) < z-collisionsize == false /*
        */and IsUnitType(enemy,UNIT_TYPE_MAGIC_IMMUNE) == false /*
        */and IsUnitType(enemy,UNIT_TYPE_STRUCTURE) == false /*
        */and IsUnitType(enemy, UNIT_TYPE_MECHANICAL) == false then
            return true
        endif
        return false
    endmethod
    
    private static method arrowmove takes nothing returns nothing
        local timer t = GetExpiredTimer()
        local thistype this = LoadInteger(hash, 0, GetHandleId(t) )
        
        local effect e
        local location l
        local unit u
        local integer i = 0
        local real randomdist
        
        loop
            exitwhen i == ARROW_NUMBER

            set l = Location(GetUnitX(.Arrow[i]), GetUnitY(.Arrow[i]) )            
            
            if GetLocationZ(l) >= .ArrowZ[i] then
                call KillUnit(.Arrow[i])
                set .ArrowsMaxed = .ArrowsMaxed + 1
                set .Arrow[i] = null
            endif
            if .Distance[i] >= .MaxDist[i] and .Arrow[i] != null then
                call KillUnit(.Arrow[i])
                set .ArrowsMaxed = .ArrowsMaxed + 1
                set .Arrow[i] = null
            else
                set randomdist = GetRandomReal(-MOVESPEED_RANDOM,MOVESPEED_RANDOM)
                call thistype.move(.Arrow[i], MOVESPEED+randomdist, GetUnitFacing(.Arrow[i]) )
                call GroupEnumUnitsInRange(.ArrowEnum, GetUnitX(.Arrow[i]), GetUnitY(.Arrow[i]), COLLISION_SIZE, null)
                loop
                    set u = FirstOfGroup(.ArrowEnum)
                    exitwhen u == null
                    if not IsUnitInGroup(u, .Hit[i]) and thistype.validtarget(.Caster, u, .ArrowZ[i], COLLISION_SIZE) and .Arrow[i] != null then
                        call UnitDamageTarget(.Caster, u, DAMAGE_AMOUNT + DAMAGE_PER_LEVEL * GetUnitAbilityLevel(.Caster, ABIL_ID), false, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_FIRE, null)
                        call GroupAddUnit(.Hit[i], u)
                        set e = AddSpecialEffect(HIT_FX, GetUnitX(.Arrow[i]), GetUnitY(.Arrow[i]) )
                        call DestroyEffect(e)
                        call KillUnit(.Arrow[i])
                        set .ArrowsMaxed = .ArrowsMaxed + 1
                        set .Arrow[i] = null
                    endif
                    call GroupRemoveUnit(.ArrowEnum, u)
                endloop
                call SetUnitFlyHeight(.Arrow[i], .ArrowZ[i] - GetLocationZ(l), 0. )
                set .Distance[i] = .Distance[i] + MOVESPEED+randomdist
            endif
            
            set i = i + 1
        endloop
        
        call RemoveLocation(l)        

        set t = null
        set e = null
        set l = null
        set u = null
        
        if .ArrowsMaxed == ARROW_NUMBER then
            call .destroy()
        endif
    endmethod

    static method create takes unit caster, real targetx, real targety returns Arrowblast
        local thistype this = Arrowblast.allocate()
        local player p
        local location l
        local integer i = 0
        local real facing
    
        set .Caster = caster
        set .TargetX = targetx
        set .TargetY = targety
        set .Mover = CreateTimer()
        set .ArrowsMaxed = 0
        
        set p = GetOwningPlayer(.Caster)   
        
        loop
            exitwhen i == ARROW_NUMBER
 
            set .MaxDist[i] = thistype.distancebetween(.TargetX, GetUnitX(.Caster), .TargetY, GetUnitY(.Caster) ) + GetRandomReal(-RANGE_VARIATION, RANGE_VARIATION)
            if .MaxDist[i] <= RANGE_VARIATION then
                set .MaxDist[i] = .MaxDist[i]+2*RANGE_VARIATION
            endif
            set facing = this.math(.TargetY, GetUnitY(.Caster), .TargetX, GetUnitX(.Caster)) + GetRandomReal(-ANGLE_VARIATION, ANGLE_VARIATION) / (.MaxDist[i] / CAST_RANGE)
            set .Arrow[i] = CreateUnit(p, DUMMY_ID, GetUnitX(.Caster), GetUnitY(.Caster), facing)
            set .Distance[i] = 0.
            set .ArrowZ[i] = ARROW_Z_DEFAULT + GetRandomReal(-ARROW_Z_RANDOM, ARROW_Z_RANDOM) + GetUnitFlyHeight(.Caster)
            call UnitAddAbility(.Arrow[i], 'Amrf') 
            call UnitRemoveAbility(.Arrow[i], 'Amrf')
            call SetUnitFlyHeight(.Arrow[i], .ArrowZ[i], 0.)
            
            set i = i + 1
        endloop

        call SaveInteger(hash, 0, GetHandleId(.Mover), this)
        call TimerStart(.Mover, MOVE_INTERVAL, true, function thistype.arrowmove)
        
        set p = null
        return this
    endmethod
    
    method onDestroy takes nothing returns nothing
    local integer i = 0
    loop
        exitwhen i == 0
        call DestroyGroup(.Hit[i])
        
        set .Arrow[i] = null
        set .Hit[i] = null
        set i = i + 1
    endloop
    
    call DestroyTimer(.Mover)
    call DestroyGroup(.ArrowEnum)
    
    set .ArrowEnum = null
    set .Caster = null
    set .Mover = null
    endmethod
    
endstruct

private function Arrowcheck takes nothing returns boolean
    local location l = GetSpellTargetLoc()
    local unit u = GetTriggerUnit()
    if GetSpellAbilityId() == ABIL_ID then
        call Arrowblast.create(u, GetLocationX(l), GetLocationY(l))
    endif
    call RemoveLocation(l)
    
    set l = null
    set u = null
    return false
endfunction

private function init takes nothing returns nothing
	local trigger LocalTrig = CreateTrigger()
	call TriggerRegisterAnyUnitEventBJ(LocalTrig, EVENT_PLAYER_UNIT_SPELL_EFFECT)
	call TriggerAddCondition(LocalTrig, Condition(function Arrowcheck))
	set LocalTrig = null
endfunction

endscope
 
Level 18
Joined
Sep 14, 2012
Messages
3,413
I just wanted to clarify some things :
JASS:
local real output = bj_RADTODEG*Atan2(y2 - y1, x2 - x1)
return output
->
JASS:
return bj_RADTODEG * Atan2(y2-y1, x2-x1)

JASS:
local real x = x2 - x1
local real y = y2 - y1
return SquareRoot(x*x+y*y)
->
JASS:
return SquareRoot((x2-x1)*(x2-x1) + (y2-y1)*(y2-y1))

JASS:
if IsUnitEnemy(enemy, GetOwningPlayer(caster)) == true /*
        */and GetWidgetLife(enemy) > 0.405 == true /*
        */and GetUnitFlyHeight(enemy) > z+collisionsize  == false /*
        */and GetUnitFlyHeight(enemy) < z-collisionsize == false /*
        */and IsUnitType(enemy,UNIT_TYPE_MAGIC_IMMUNE) == false /*
        */and IsUnitType(enemy,UNIT_TYPE_STRUCTURE) == false /*
        */and IsUnitType(enemy, UNIT_TYPE_MECHANICAL) == false then
            return true
        endif
        return false
Simplify it by writing a long boolean equation.

Don't use locations use real coordinates.

I don't have the time to continue but start with this it will be more efficient.
 
Some of the lag may be out of your control (as in, it may be due to the effects).

Particle count, ubersplats, etc.. can all lead to worse performance. The resolution is to do anything to reduce the number of effects on screen--or to choose a less intense effect.

One thing you may want to do is try setting the effect path to "". Perform many simultaneous casts. If it lags a lot, then your code may be worth looking into. If it doesn't, then look into reducing the effect load.
 
Level 4
Joined
Sep 25, 2005
Messages
71
Some of the lag may be out of your control (as in, it may be due to the effects).

Particle count, ubersplats, etc.. can all lead to worse performance. The resolution is to do anything to reduce the number of effects on screen--or to choose a less intense effect.

One thing you may want to do is try setting the effect path to "". Perform many simultaneous casts. If it lags a lot, then your code may be worth looking into. If it doesn't, then look into reducing the effect load.

The effects don't usually change much unless it's say, the "chain lightning" projectile that has a few particles spawned from it.

Usually, the many simultaneous casts with 40+~ dummies is where you start to see it jam up but one to two to three are fine. It may have to do with the fact the individual arrow dummies are all iterating through the nearby arrows and such.

In particular, I saw a noticeable change when instead of calling GetLocationZ a bunch I simply did it and set it to a local variable and had it referenced from there. Not sure what else I'm using.

@Malhorne: was convinced there weren't natives for say, GetZAtPoint, so locations end up purely being used as necessity when I have to involve such things.
 
Possible sources [possible workaround]

Numerous amounts of units [reduce unit count]
Lots of function calls [use variables in cases that you can simply save the values of each function rather than calling them repetitively]
Leaks [clean it]

And yeah, if you need Z of a point, you're forced to use locations...

the == true can be removed, and you can just remove the if-then also...

JASS:
return (IsUnitEnemy(enemy, GetOwningPlayer(caster)) /*
        */and GetWidgetLife(enemy) > 0.405/*
        */and GetUnitFlyHeight(enemy) > z+collisionsize  == false /*
        */and GetUnitFlyHeight(enemy) < z-collisionsize == false /*
        */and IsUnitType(enemy,UNIT_TYPE_MAGIC_IMMUNE) == false /*
        */and IsUnitType(enemy,UNIT_TYPE_STRUCTURE) == false /*
        */and IsUnitType(enemy, UNIT_TYPE_MECHANICAL) == false)

or you can also do

JASS:
return (IsUnitEnemy(enemy, GetOwningPlayer(caster)) /*
        */and GetWidgetLife(enemy) > 0.405/*
        */and (not(GetUnitFlyHeight(enemy) > z+collisionsize)) /*
        */and (not(GetUnitFlyHeight(enemy) < z-collisionsize)) /*
        */and (not(IsUnitType(enemy,UNIT_TYPE_MAGIC_IMMUNE))) /*
        */and (not(IsUnitType(enemy,UNIT_TYPE_STRUCTURE))) /*
        */and (not(IsUnitType(enemy, UNIT_TYPE_MECHANICAL))))
 
Status
Not open for further replies.
Top