• 🏆 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] using structs..

Status
Not open for further replies.
Level 2
Joined
Jun 25, 2006
Messages
18
Today I finally took the time to learn the usage of structs. A blessing over local handle vars indeed, and I recoded a few of my spells. I have one concern, and that is when using groups in your struct.

The below spell works perfectly. It is called erratic entangle, you cast roots on a unit and every .75 seconds it roots a nearby unit. I'm worried this leaks when handling the groups in the functions "GaiaEntangle" and "GaiaEntangleFunc2". If I destroy the group, even after setting it to "data.entangled", the spell does not work. But currently, to me it seems the local groups are not being destroyed, leaking the groups every time its cast and every tick on the timer. My questions are, is this leaking and if it is, how can I easily resolve the issue?

JASS:
scope ErraticEntangle
    struct entangle
        player caster
        group entangled
        integer duration
    endstruct
    function GaiaEntangleCond takes nothing returns boolean
        local integer id = GetSpellAbilityId()
        local unit u = GetSpellAbilityUnit()
        local integer i = GetUnitTypeId(u)
        set u = null
        return (id == 'A02U') and (i == 'e006')
    endfunction
    function GaiaEntangleFunc1 takes nothing returns boolean
        return not(IsUnitType(GetFilterUnit(), UNIT_TYPE_FLYING)) and not(IsUnitType(GetFilterUnit(), UNIT_TYPE_ANCIENT)) and IsUnitEnemy(GetFilterUnit(), GetTriggerPlayer())
    endfunction
    function GaiaEntangleFunc2 takes nothing returns nothing
      local timer t         = GetExpiredTimer()
      local entangle data   = entangle(GetHandleInt(t,"struct"))
      local unit u          = GroupPickRandomUnit(data.entangled)
      local unit v
      local real x          = GetUnitX(u)
      local real y          = GetUnitY(u)
      local group g         = CreateGroup()
      local boolexpr b      = Condition(function GaiaEntangleFunc1)
        if (data.duration > 0) then
            set v = CreateUnit(data.caster, 'h062', x, y, 270)
            call UnitAddAbility(v, 'A02U')
            call IssueTargetOrder(v, "entanglingroots", u)
            call UnitApplyTimedLife(v, 'BTLF', 1)
            call GroupEnumUnitsInRange(g, x, y, 400, b)
            call GroupRemoveUnit(g, u)
            set data.entangled = g
            set data.duration = data.duration - 1
            call SetHandleInt(t, "struct", data)
        else
	    call data.destroy()
            call PauseTimer(t)
            call FlushHandleLocals(t)
            call DestroyTimer(t)
        endif
        call DestroyBoolExpr(b)
      set t = null
      set g = null
      set u = null
      set v = null
      set b = null
    endfunction
    function GaiaEntangle takes nothing returns nothing
      local entangle data   = entangle.create()
      local unit s          = GetSpellAbilityUnit()
      local unit u          = GetSpellTargetUnit()
      local player p        = GetOwningPlayer(s)
      local real x          = GetUnitX(u)
      local real y          = GetUnitY(u)
      local group g         = CreateGroup()
      local timer t         = CreateTimer()
      local boolexpr b      = Condition(function GaiaEntangleFunc1)
        call GroupEnumUnitsInRange(g, x, y, 400, b)
        set data.caster = p
        set data.entangled = g
        set data.duration = 40
        call SetHandleInt(t, "struct", data)
        call TimerStart(t, .75, true, function GaiaEntangleFunc2)
        call DestroyBoolExpr(b)
      set s = null
      set u = null
      set p = null
      set g = null
      set t = null
      set b = null
    endfunction
    function InitTrig_EarthEntangleCast takes nothing returns nothing
      local integer i = 0
      local player p
        set gg_trg_EarthEntangleCast = CreateTrigger()
        loop
            exitwhen i > 11
            set p = Player(i)
            call TriggerRegisterPlayerUnitEvent(gg_trg_EarthEntangleCast, p, EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
            set i = i + 1
        endloop
        call TriggerAddCondition(gg_trg_EarthEntangleCast, Condition(function GaiaEntangleCond))
        call TriggerAddAction(gg_trg_EarthEntangleCast, function GaiaEntangle)
      set p = null
    endfunction
endscope
 
Level 11
Joined
Feb 22, 2006
Messages
752
If you destroy a group, it ceases to exist, so obviously if you destroy a group and then try to use it later you're gonna run into issues. If you save the group to two variables and destroy the group using the first pointer, the second pointer will now point to a null object. In other words, saving a handle to multiple variables does not create multiple copies, one for each variable. Instead, you just have several different variables all pointing to the same handle.

Make an onDestroy method in your struct that destroys the group, and while you're changing stuff you might as well change some code to make your script less cluttered:

JASS:
scope ErraticEntangle

    struct entangle
        timer t = CreateTimer()
        player caster
        group entangled = CreateGroup()
        integer duration = 40
        method onDestroy takes nothing returns nothing //the method called "onDestroy" in any struct will run when you destroy the struct (always)
            call FlushHandleLocals( .t )
            call DestroyTimer( .t )
            call DestroyGroup( .entangled )
        endmethod
    endstruct

    function GaiaEntangleCond takes nothing returns boolean
        local integer id = GetSpellAbilityId()
        local unit u = GetSpellAbilityUnit()
        local integer i = GetUnitTypeId(u)
        set u = null
        return (id == 'A02U') and (i == 'e006')
    endfunction
    function GaiaEntangleFunc1 takes nothing returns boolean
        return not(IsUnitType(GetFilterUnit(), UNIT_TYPE_FLYING)) and not(IsUnitType(GetFilterUnit(), UNIT_TYPE_ANCIENT)) and IsUnitEnemy(GetFilterUnit(), GetTriggerPlayer())
    endfunction

    function GaiaEntangleFunc2 takes nothing returns nothing
      local timer t         = GetExpiredTimer()
      local entangle data   = entangle(GetHandleInt(t,"struct"))
      local unit u          = GroupPickRandomUnit(data.entangled)
      local unit v
      local real x          = GetUnitX(u)
      local real y          = GetUnitY(u)
      local boolexpr b      = Condition(function GaiaEntangleFunc1)
        if (data.duration > 0) then
            set v = CreateUnit(data.caster, 'h062', x, y, 270)
            call UnitAddAbility(v, 'A02U')
            call IssueTargetOrder(v, "entanglingroots", u)
            call UnitApplyTimedLife(v, 'BTLF', 1)
            call GroupClear( data.entangled ) //clear the group of all units
            call GroupEnumUnitsInRange(data.entangled, x, y, 400, b) //fill the group with the new units you want in it
            call GroupRemoveUnit(data.entangled, u)
            set data.duration = data.duration - 1
        else
            call data.destroy() //Because of the onDestroy method, timer and group will be destroyed when the struct is destroyed (no need to nullify since struct pointers will just be recycled later)
        endif
        call DestroyBoolExpr(b)
      set t = null
      set g = null
      set u = null
      set v = null
      set b = null
    endfunction

    function GaiaEntangle takes nothing returns nothing
      local entangle data   = entangle.create() //when you call this timer data.t and group data.entangled are initialized (created)
      local unit s          = GetSpellAbilityUnit()
      local unit u          = GetSpellTargetUnit()
      local player p        = GetOwningPlayer(s)
      local real x          = GetUnitX(u)
      local real y          = GetUnitY(u)
      local boolexpr b      = Condition(function GaiaEntangleFunc1)
        call GroupEnumUnitsInRange(data.entangled, x, y, 400, b) //group has already been initialized inside struct
        set data.caster = p
        call SetHandleInt(data.t, "struct", data)
        call TimerStart(data.t, .75, true, function GaiaEntangleFunc2) //timer has already been initialized inside struct
        call DestroyBoolExpr(b)
      set s = null
      set u = null
      set p = null
      set b = null
    endfunction

    function InitTrig_EarthEntangleCast takes nothing returns nothing
      local integer i = 0
      local player p
        set gg_trg_EarthEntangleCast = CreateTrigger()
        loop
            exitwhen i > 11
            set p = Player(i)
            call TriggerRegisterPlayerUnitEvent(gg_trg_EarthEntangleCast, p, EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
            set i = i + 1
        endloop
        call TriggerAddCondition(gg_trg_EarthEntangleCast, Condition(function GaiaEntangleCond))
        call TriggerAddAction(gg_trg_EarthEntangleCast, function GaiaEntangle)
      set p = null
    endfunction

endscope

Basically in your Func2 you didn't need to create a new group; since the old group saved in data.entangled wasn't going to be used again, you might as well just clear the group and recycle it (that way you don't have to create and destroy a group every 0.75 seconds).

EDIT: Why exactly are using a scope? I'm just wondering cuz you don't have any private or public functions or keywords or anything else like that.
 
Level 2
Joined
Jun 25, 2006
Messages
18
thanks for all the advice, very very helpful. and the only reason i was using a scope is because i was unsure if use the same variable name in different structs outside of a scopes it would create problems.
 
Level 4
Joined
Nov 23, 2007
Messages
113
n/m I see I must be posting in the wrong thread as perhaps my question was unrelated.
 
Last edited:
Level 6
Joined
Jun 30, 2006
Messages
230
For your information, MANY of the best coders around do NOT inline the TriggerRegisterAnyPlayerEvent stuff. It's not bad, but it changes your pretty code into a semi-beast. You turned 1 line into 9 lines, and it will run faster, but just BARELY. I like my code smaller in this case.

Also, inside of a scope, you can just name your trigger InitTrig. When it gets processed, it will be rename InitTrig_NameOfScope, so it won't conflict with other spells. Plus, it gives you an excuse to stick your trigger inside of a scope... Also, it is important to name your scope the same as the name of the trigger!

A few things. You create local variables for code that gets called once, maybe twice. It is faster to just directly call the code itself. I've taken the code aznricepuff posted and went through it and did practical optimization as far as this aspect goes. I've also modified a few things and added some comments. I can't test this as I do not have CSData or whatever system it is you are using. I have always created my own systems, so cannot test to see if this works. You seem to be a smart guy, though, you should be able to fix it in the event it doesn't quite work.

I feel like there is something critically important I am skipping, but I cannot think of it at the moment.

JASS:
scope ErraticEntangle

globals
    private integer AID_ERRATIC_ENTANGLE='A02U'
    private integer DUMMY_ERRACTIC_ENTANGLE='h062' //I am assuming this is your dummy caster?
    //I use a global for my code values. This is just good programming practice,
    //as they are easy to change here, as well as you only have to change it in
    //one place, rather than searching out code.  Again, it is in all caps just
    //because it is considered good practice.
endglobals

struct entangle
    timer t = CreateTimer()
    player caster group
    entangled = CreateGroup()
    integer duration = 40
    
    method onDestroy takes nothing returns nothing //the method called "onDestroy" in any struct will run when you destroy the struct (always)
        call FlushHandleLocals(.t)
        call DestroyTimer(.t)
        call DestroyGroup(.entangled)
    endmethod
    
endstruct

function Conditions takes nothing returns boolean
    return (GetSpellAbilityId() == AID_ERRATIC_ENTANGLE) and (GetUnitTypeId(GetSpellAbilityUnit()) == 'e006')
endfunction
function Filter takes nothing returns boolean
    return not(IsUnitType(GetFilterUnit(), UNIT_TYPE_FLYING)) and not(IsUnitType(GetFilterUnit(), UNIT_TYPE_ANCIENT)) and IsUnitEnemy(GetFilterUnit(), GetTriggerPlayer())
endfunction
function Entangle takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local entangle data = entangle(GetHandleInt(t,"struct"))
    local unit u = GroupPickRandomUnit(data.entangled)
    local unit v
    local boolexpr b = Condition(function Filter)
    if (data.duration > 0) then
        set v = CreateUnit(data.caster, DUMMY_ERRACTIC_ENTANGLE, x, y, 270) 
        call UnitAddAbility(v, AID_ERRATIC_ENTANGLE)
        call IssueTargetOrder(v, "entanglingroots", u)
        call UnitApplyTimedLife(v, GetLastCreatedUnit(), 1)
        call GroupClear(data.entangled) //clear the group of all units
        call GroupEnumUnitsInRange(data.entangled, GetUnitX(u), GetUnitY(u), 400, b)  //fill the group with the new units you want in it
        call GroupRemoveUnit(data.entangled, u)
        set data.duration = data.duration - 1
    else
        call data.destroy()
        //Because of the onDestroy method, timer and group will be destroyed when
        //the struct is destroyed (no need to nullify since struct pointers will just be recycled later)
    endif
    call DestroyBoolExpr(b)
    set t = null
    set u = null
    set v = null
    set b = null
endfunction
function Actions takes nothing returns nothing
    local entangle data = entangle.create()  //when you call this timer data.t and group data.entangled are initialized (created)
    local boolexpr b = Condition(function Filter)
    call GroupEnumUnitsInRange(data.entangled, GetUnitX(GetSpellTargetUnit()), GetUnitY(GetSpellTargetUnit()), 400, b)
    //group has already been initialized inside struct
    set data.caster = GetOwningPlayer(GetSpellAbilityUnit())
    call SetHandleInt(data.t, "struct", data)
    call TimerStart(data.t, .75, true, function Entangle) //timer has already been initialized inside struct
    call DestroyBoolExpr(b)
    set b = null
endfunction 
function InitTrig takes nothing returns nothing
    local integer i = 0
    local player p
    local trigger trig=CreateTrigger()
    set trig = CreateTrigger()
    loop
        exitwhen i > 11
        set p = Player(i)
        call TriggerRegisterPlayerUnitEvent(trig, p, EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
        set i = i + 1
    endloop
    call TriggerAddCondition(trig, Condition(function Conditions))
    call TriggerAddAction(trig, function Actions)
    set p = null
endfunction 

endscope
 
Status
Not open for further replies.
Top