HomeUser Control Panel (unavailable in archive)ForumsTutorialsArt GalleryResourcesMaps

Optimizing groupenum code

02-11-2008, 11:54 AM#1
SockSquirrelMouthwash
http://www.wc3campaigns.net/showthread.php?t=90186

In response to this, it helped answer my question of why my map slowed down and got choppy at times. This function, for example runs every 2 seconds on a timer while the building's ability is active for at most 30 seconds:
Collapse JASS:
private function FrostInfliction takes nothing returns nothing
    local integer t = ST_GetExpiredTimer()
    local integer level = ST_GetInt1(t)
    local unit cast = ST_GetUnit1(t)
    local unit u
    local group g = CreateGroup()
    local real x = ST_GetReal1(t)
    local real y = ST_GetReal2(t)
    local integer i = GetPlayerId(GetOwningPlayer(cast))
    local boolexpr cond = Condition(function FrostFilter)
    
    if GetUnitAbilityLevel(cast,'B00I') > 0 and GetUnitState(cast, UNIT_STATE_LIFE) > 0 then
        call GroupEnumUnitsInRangeCounted(g,x,y,1000.00, cond, 5)
        
        loop
            set u = FirstOfGroup(g)
            exitwhen u == null
            if (GetUnitAbilityLevel(u,'B00C') > 0) then
                call UnitRemoveAbility(u,'B00C')
                call CasterCastAbility(Player(i), 'A02O', "cripple", u, true) // Level 2 Slow
            elseif (GetUnitAbilityLevel(u,'B00E') > 0) then
                call UnitRemoveAbility(u,'B00E')
                call CasterCastAbility(Player(i), 'A02R', "cripple", u, true) // Level 3 Slow
            elseif (GetUnitAbilityLevel(u,'B00H') > 0) then
                call UnitRemoveAbility(u,'B00H')
                call CasterCastAbility(Player(i), 'A02G', "thunderbolt", u, true) // Stun
            elseif (GetUnitAbilityLevel(u,'B00G') > 0) then 
                call UnitRemoveAbility(u,'B00G')
                call UnitDamageTarget(cast, u, 50, true, false, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_NORMAL, null ) 
                call SpecialEffectTarget( "Abilities\\Spells\\Undead\\FrostNova\\FrostNovaTarget.mdl", u, "origin", 2)
            else
                call CasterCastAbility(Player(i), 'A02H', "cripple", u, true) // Level 1 Slow
            endif
            call GroupRemoveUnit(g,u)
        endloop     
        
        call DestroyGroup(g)
    else
        call ST_Destroy(t)
    endif
    
    call DestroyBoolExpr(cond)
    set cond = null
    set u = null
    set g = null
    set cast = null
endfunction
There can be a lot more than 1 buildings with this ability on the map so I can see how it gets taxing. So my question is, what can be done to make this piece of code a lot faster, if any?
02-11-2008, 12:08 PM#2
Themerion
What attachment system are you using? I have never heard of ST.

If you are using vJASS (new gen jass), then a better approach than attaching everything is to use structs for this. Might give you quite a speed boost.

Collapse JASS:
struct FrostInflictionData
    integer level
    unit unit1
endstruct

function FrostInfliction takes nothing returns nothing
    local FrostInflictionData t=ST_GetExpiredTimer()
    local integer level=t.level
    local integer cast=t.unit1
// etc...
endfunction

Vexorian has published a nice manual on structs here.

If you feel really at home with vJASS, then the currently best option would probably be Cohadar's collections (since you mentioned that this script may be used by a lot of units at the same time). You can find them under resources.
02-11-2008, 12:16 PM#3
SockSquirrelMouthwash
Quote:
Originally Posted by Themerion
What attachment system are you using? I have never heard of ST.

Oh I'm using SafeTimers from the resources section.

I never really had a good grasp on structs, and it's still confusing me on how to use it (object oriented programming class never really sunk in). How can I use it to relieve the groupenum spam?
02-11-2008, 12:34 PM#4
Themerion
You cannot use it to relieve "GroupEnumSpam". Still, I think that the main issue with your code is not GroupEnum. Attaching is a known performance eater, and most people around here seems to try to reduce it as much as possible.

In other words, I think your script would run a bit faster if you reduced the number of attachments.

With structs, your code would look somthing like:

Collapse JASS:
struct FrostInflictionData
    integer level
    unit cast
    real x
    real y
endstruct

private function FrostInfliction takes nothing returns nothing
    local integer i = ST_GetExpiredTimer()
    local FrostInflictionData t=ST_GetInt1(i)
    local integer level=t.level
    local unit cast=t.cast
    local unit u
    local group g = CreateGroup()
    local real x=t.x
    local real y=t.y
    local integer i=GetPlayerId(GetOwningPlayer(cast))
    local boolexpr cond=Condition(function FrostFilter)
    
    if GetUnitAbilityLevel(cast,'B00I') > 0 and GetUnitState(cast, UNIT_STATE_LIFE) > 0 then
        call GroupEnumUnitsInRangeCounted(g,x,y,1000.00, cond, 5)
        
        loop
            set u = FirstOfGroup(g)
            exitwhen u == null
            if (GetUnitAbilityLevel(u,'B00C') > 0) then
                call UnitRemoveAbility(u,'B00C')
                call CasterCastAbility(Player(i), 'A02O', "cripple", u, true) // Level 2 Slow
            elseif (GetUnitAbilityLevel(u,'B00E') > 0) then
                call UnitRemoveAbility(u,'B00E')
                call CasterCastAbility(Player(i), 'A02R', "cripple", u, true) // Level 3 Slow
            elseif (GetUnitAbilityLevel(u,'B00H') > 0) then
                call UnitRemoveAbility(u,'B00H')
                call CasterCastAbility(Player(i), 'A02G', "thunderbolt", u, true) // Stun
            elseif (GetUnitAbilityLevel(u,'B00G') > 0) then 
                call UnitRemoveAbility(u,'B00G')
                call UnitDamageTarget(cast, u, 50, true, false, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_NORMAL, null ) 
                call SpecialEffectTarget( "Abilities\\Spells\\Undead\\FrostNova\\FrostNovaTarget.mdl", u, "origin", 2)
            else
                call CasterCastAbility(Player(i), 'A02H', "cripple", u, true) // Level 1 Slow
            endif
            call GroupRemoveUnit(g,u)
        endloop     
        
        call DestroyGroup(g)
    else
        call t.destroy()
        call ST_Destroy(i)
    endif
    
    call DestroyBoolExpr(cond)
    set cond = null
    set u = null
    set g = null
    set cast = null
endfunction

And the creation:

Collapse JASS:
local FrostInflictionData t=FrostInflictionData.create()
//set t.cast=The Casting Unit
//set t.x=The X Point
//set t.y=The Y Point
// etc.

// IMPORTANT
// You have to attach the FrostInflictionData as Int1. 

Also, I think I recall that Vexorian mentioned something about function ForGroup(group g,code functionName) in combination with temporary global variables being faster than the standard method of looping.


EDIT
The point of this struct thingy is that there can be multiple instances of each struct. If you have a struct containing a unit and an integer; then for each time a struct of that type, a new unit variable and a new integer variable is assigned for the newly created struct.
02-11-2008, 12:53 PM#5
SockSquirrelMouthwash
Gotcha I'll give it a shot, but one thing that I'm still confused about is how there would be multiple instances of this function running on different timers (since more than one of the buildings w/ ability can exist) - what is it that I can do to ensure each struct I create is unique only to that function it was created for:

ability cast -> run function -> create struct and start associated timer that runs FrostInfliction -> when ability is over destroy that specific struct
02-11-2008, 01:10 PM#6
Themerion
Let's make an analogy with a unit.

Each unit has a lot of different values...
  • Hit points
  • Mana Points
  • X and Y position
  • Player Owner

When you create a new unit; it will have it's own values for all of the above.

It is the same with structs. Each struct of a certain type has it's own variables!

In the following example, I use 1 struct type, but three individual structs of that type.

Collapse JASS:
struct song
    integer seconds
    string name
    string author
endstruct

function MySongs takes nothing returns nothing
    local song s1=song.create()
    local song s2=song.create()
    local song lullaby=song.create()

// s1 has it's own variables...
    set s1.seconds=3954
    set s1.name="requiem"
    set s1.author="mozart"

// s2 has it's own variables...
    set s2.seconds=190
    set s2.name="She loves you"
    set s2.author="The Beetles"

// lullaby has it's own variables...
    set lullaby.seconds=32
    set lullaby.name="Zelda's Lullaby"
    set lullaby.author="Koji Japaneese"

    call BJDebugMsg(s1.name)
    call BJDebugMsg(s2.name)
    call BJDebugMsg(lullaby.name)

// This will display:
//
// requiem
// She Loves You
// Zelda's Lullaby

endfunction
02-11-2008, 01:25 PM#7
SockSquirrelMouthwash
wait a sec, when you put in:
Collapse JASS:
local FrostInflictionData t=ST_GetInt1(i)
were you just merely passing the "unique id" for that particular struct through the timer?
02-11-2008, 02:04 PM#8
Fireeye
Actually strcuts are only arrays working parallel.
So the struct returns the integer for the corrisponding slot.
I personally prefer using loops for structs instead of attachment systems.
I wrote how i would do your triggers, however i'm kinda lazy atm to explain everything detailed, also i don't have WE atm so i can not check for any syntax errors, etc.
Expand JASS:
02-11-2008, 02:35 PM#9
Themerion
Quote:
Originally Posted by SockSquirrelMouthwash
wait a sec, when you put in:
Collapse JASS:
local FrostInflictionData t=ST_GetInt1(i)
were you just merely passing the "unique id" for that particular struct through the timer?

Yes!
02-11-2008, 03:28 PM#10
SockSquirrelMouthwash
Quote:
Originally Posted by Themerion
Yes!

Nice! Much appreciated man for the crash course on structs!

I'm gonna have to start using these more often