• 🏆 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] Double Free on this system.

Status
Not open for further replies.
Level 13
Joined
Jul 26, 2008
Messages
1,009
I feel I understand vaguely what a doublefree is. However, I'm not always able to detect them.

This system was created for me by ... Bribe or Berb. I wish I could remember. However it's creating Double Frees in the map (Probably harmless, but better safe than sorry.)

It isn't long, and it's well put together. The DoubleFrees come a little while after the spells are used.

Help with it is highly appreciated, thanks :D

Also a little extra help, but what is the best way to decrease a Hero's EXP by 1 every second (Set or Add with a negative value?).

JASS:
library UnitAbility requires AutoIndex, TimerUtils

    globals
    
        public constant integer     STORED_ABILITIES        = 3
        
    endglobals

    struct abilitydata
        readonly    unit     caster      = null
        readonly    unit     target      = null
        
        readonly    real     castx
        readonly    real     casty
        readonly    real     targetx
        readonly    real     targety
        readonly    real     castAngle
        readonly    real     castRange
        
        readonly    integer  abilityid
        readonly    string   orderstring
        
        method copy takes nothing returns thistype
            local thistype copy = allocate()
            
            set copy.caster     = caster
            set copy.castx      = castx
            set copy.casty      = casty
            set copy.target     = target
            set copy.targetx    = targetx
            set copy.targety    = targety
            set copy.abilityid  = abilityid
            set copy.orderstring= orderstring
            set copy.castAngle  = castAngle
            set copy.castRange  = castRange
            
            return copy
        endmethod
        
        static method create takes nothing returns thistype
            local thistype abil=allocate()
            
            set abil.caster=GetTriggerUnit()
            set abil.castx=GetUnitX(abil.caster)
            set abil.casty=GetUnitY(abil.caster)
            set abil.target=GetSpellTargetUnit()
            set abil.targetx=GetSpellTargetX()
            set abil.targety=GetSpellTargetY()
            set abil.abilityid=GetSpellAbilityId()
            set abil.orderstring=OrderId2String(GetUnitCurrentOrder(abil.caster))
            
            //misc spell-data
            set abil.castAngle=Atan2(abil.targety-abil.casty, abil.targetx-abil.castx) //in radians
            set abil.castRange=SquareRoot((abil.targetx-abil.castx)*(abil.targetx-abil.castx) + (abil.targety-abil.casty)*(abil.targety-abil.casty))
            
            return abil
        endmethod
    endstruct

    struct unitabilities
        readonly    abilitydata array   entry       [STORED_ABILITIES]
        readonly    integer             stored      = 0
        
        method onDestroy takes nothing returns nothing
            local integer i=0
            loop
                exitwhen(i==stored)
                if(entry[i]!=0) then
                    call entry[i].destroy()
                endif
                set i=i+1
            endloop
        endmethod
        
        method addAbility takes abilitydata dat returns boolean
            local integer i=0
            loop
                exitwhen(i==stored)
                if(entry[i]==dat) then
                    return false
                endif
                set i=i+1
            endloop
            if(stored==STORED_ABILITIES) then
                call entry[0].destroy()
                
                set entry[0]=entry[1]
                set entry[1]=entry[2]
                set entry[2]=dat
            else
                set entry[stored]=dat
                set stored=stored+1
            endif
            return true
        endmethod
    endstruct
    
    private struct savedata
        public  unit                source      = null
        public  unit                target      = null
        public  abilitydata         abilData
        
        static method create takes unit source, unit target, abilitydata data1 returns thistype
            local thistype data = allocate()
            
            set data.source     = source
            set data.target     = target
            set data.abilData   = data1
            
            return data
        endmethod
    endstruct
    
    struct unitabilityhistory //requires AutoIndex, TimerUtils
        public  unitabilities   out     
        public  unitabilities   in
    
        private trigger         abilevent   = CreateTrigger() 
        private timer           threadDelay = null
        
        private boolean         skipFlag    = false
        
        method freeze takes boolean flag returns nothing
            if flag then
                call EnableTrigger(abilevent)
            else
                call DisableTrigger(abilevent)
            endif
        endmethod
        method skip takes nothing returns nothing
            set skipFlag = true
        endmethod
        
        method onDestroy takes nothing returns nothing
            call out.destroy()
            call in.destroy()
            
            call DestroyTrigger(abilevent)
            if threadDelay != null then
                call ReleaseTimer(threadDelay)
            endif
        endmethod
    
        static method onAbilEventThreadDelay takes nothing returns nothing
            local savedata data = GetTimerData(GetExpiredTimer())
            
            call thistype[data.source].out.addAbility(data.abilData.copy())
            call thistype[data.target].in.addAbility(data.abilData.copy())
            
            call data.abilData.destroy()
            call ReleaseTimer(GetExpiredTimer())
        endmethod

        static method onAbilEvent takes nothing returns boolean
            local thistype data = thistype[GetTriggerUnit()]
            
            if not data.skipFlag then
                set data.threadDelay = NewTimer()
                
                call SetTimerData(data.threadDelay, savedata.create(GetTriggerUnit(), GetSpellTargetUnit(), abilitydata.create()))
                call TimerStart(data.threadDelay, 0, false, function thistype.onAbilEventThreadDelay)
            endif
            set data.skipFlag = false
            
            return false
        endmethod

        method onCreate takes nothing returns nothing
            set out = unitabilities.create()
            set in  = unitabilities.create()
            call TriggerRegisterUnitEvent(abilevent, me, EVENT_UNIT_SPELL_EFFECT) 
            call TriggerAddCondition(abilevent, Filter(function thistype.onAbilEvent)) 
        endmethod
    
        implement AutoCreate
        implement AutoDestroy
    endstruct

endlibrary
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
struct abilitydata

It runs on regular structs, which have double free protection built in. I'd also disagree with how well it is coded ;P, and that code is by Berb. Bribe would never code such a thing ;D, that is def Berb's style.

JASS:
native GetHeroXP takes unit whichHero returns integer
native SetHeroXP takes unit whichHero, integer newXpVal, boolean showEyeCandy returns nothing

Runs on unique timers so as to make each hero lose xp at the precise right times. Can be changed into a linked list to save on overhead at the cost of accuracy.

I wouldn't suggest just cnping this into the map ; P.
JASS:
library Lowering

globals
    private hashtable data = InitHashtable()
    private timer array timers
endglobals

private function Lower takes nothing returns nothing
    local unit u = GetUnitUserData(LoadInteger(data, GetHandleId(GetExpiredTimer(), 0))
    call SetHeroXP(u, GetHeroXP(u)-1)
    set u = null
endfunction

function RegisterUnit takes unit u returns nothing
    local integer id = GetUnitUserData(u)
    set timers[id] = CreateTimer()
    call SaveInteger(data, GetHandleId(timers[id]), 0, id)
    call TimerStart(timers[id], 1, true, function Lower)
endfunction
function UnregisterUnit takes unit u returns nothing
    local integer id = GetUnitUserData(u)
    call PauseTimer(timers[id])
    call RemovesavedInteger(data, GetHandleId(timers[id]), 0)
    call DestroyTimer(timers[id])
    set timers[id] = null
endfunction

endlibrary
 
Level 18
Joined
Jan 21, 2006
Messages
2,552
Nestharus said:
JASS:
    local unit u = GetUnitUserData(LoadInteger(data, GetHandleId(GetExpiredTimer(), 0))

You set a unit to an integer value. That's overlooking the fact that GetHandleId does not take two parameters.

You really should use TimerUtils though, there's no reason not to. It would save you the trouble of having to use a hashtable where it really isn't necessary (that also allocates the overhead of referencing timer-data to an actual timer-specific library - this is just good coding style).

I find this to be a far prettier interpretation of what he needs.

JASS:
library BurnHeroXP requires TimerUtils, AutoIndex

    private struct burnxp
        timer ticker = null
        
        static method onTick takes nothing returns nothing
            local thistype d = GetTimerData(GetExpiredTimer())
            call SetHeroXP(d.me, GetHeroXP(d.me) - 1, false)
        endmethod
        
        method onDestroy takes nothing returns nothing
            call ReleaseTimer(ticker)
        endmethod
        method onCreate takes nothing returns nothing
            if (thistype[me] == 0) then
                set ticker = NewTimer()
                call SetTimerData(ticker, this)
                call TimerStart(ticker, 1, true, function thistype.onTick)
            endif
        endmethod
        
        implement AutoData
    endstruct
    
    //***
    //* - User Utility Interface -
    //* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
    //*
    //******
    public function Register takes unit subject returns nothing
        call burnxp.create(subject)
    endfunction
    public function Unregister takes unit subject returns nothing
        call burnxp[subject].destroy()
    endfunction
    
endlibrary

It doesn't fail when the same unit is registered twice, nor does it fail when two units with the same custom user-data values are registered. It properly allocates work amongst the necessary libraries, rather than including a redundant indexing system and a hashtable.
 
Last edited:
Level 13
Joined
Jul 26, 2008
Messages
1,009
Haha no worries, there's some cleanup that needs to be done still on that, I can see that.
(missing the boolean in SetHeroXP)

I've got this in my map. It is the actual devour skill and cargo skill. I want it to lower EXP for the sake of balance. However it's not reducing EXP (But it does increase hitpoint regen): Uses TimerUtils.

JASS:
scope Devour initializer InitTrig_Devour

globals
    private constant integer SPELLID    = 'devo'
endglobals

private struct Data
    
    unit c
    real h

    static method Timer takes nothing returns nothing
     local timer tim = GetExpiredTimer()
     local Data D = GetTimerData(tim)
        if GetUnitAbilityLevel(D.c, 'Bdvv') > 0 then
            call SetHeroXP(D.c, GetHeroXP(D.c) - GetRandomInt(0,1), false)
            call SetWidgetLife(D.c, GetWidgetLife(D.c) + D.h)
        else
            call ReleaseTimer(tim)
        endif
    endmethod

    static method create takes unit caster returns Data
     local integer lvl = GetUnitAbilityLevel(caster, SPELLID)
     local timer tim = NewTimer()
     local Data D = Data.allocate()
        set D.c = caster
        set D.h = 1. + 1.5 * lvl
        call SetTimerData(tim,D)
        call TimerStart(tim, 1, true, function Data.Timer)
     return D
    endmethod
    
endstruct

private function Actions takes nothing returns boolean
    if GetSpellAbilityId() == SPELLID then
        call Data.create(GetTriggerUnit())
    endif
 return false
endfunction

//===========================================================================
public function InitTrig_Devour takes nothing returns nothing
 local trigger t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( t, function Actions )
endfunction

endscope
 
Last edited:
Level 18
Joined
Jan 21, 2006
Messages
2,552
It's weird, Titanhex, I have similar mechanics setup in my system (based off of what I posted above) and when using TimerUtils it seems to crash the thread when I try to use NewTimer.

I'm still trying to figure out why on earth it's doing this.

JASS:
library BurnHeroXP requires TimerUtils, AutoIndex


    private struct burnxp
        timer ticker = null
        
        implement AutoData
        
        method onDestroy takes nothing returns nothing
            call ReleaseTimer(ticker)
        endmethod
        
        static method onTick takes nothing returns nothing
            local thistype d = GetTimerData(GetExpiredTimer())
            call AddHeroXP(d.me, -1, false)
            
            call BJDebugMsg("onTick (XP reduced to " + I2S(GetHeroXP(d.me)) + ")")
        endmethod
        
        static method create takes unit u returns thistype
            local thistype d = 0
            
            if (thistype[u] == 0) then
                set d = allocate()
                set d.me = u
                set d.ticker = NewTimer()
                call SetTimerData(d.ticker, d)
                call TimerStart(d.ticker, 1, true, function thistype.onTick)
            endif
            
            return d
        endmethod
        
    endstruct
    
    
    
    //***
    //* - User Utility Interface -
    //* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
    //*
    //******
    public function Register takes unit subject returns nothing
        call BJDebugMsg(GetUnitName(subject) + " has been registered.")
        call burnxp.create(subject)
    endfunction
    public function Unregister takes unit subject returns nothing
        call burnxp[subject].destroy()
    endfunction

    
    

    private struct debg
    
        static method onInit takes nothing returns nothing
            local unit h = CreateUnit(Player(0), 'Hpal', 0, 0, 0)
            
            call BJDebugMsg("Initialization...")
            
            call SetHeroXP(h, GetHeroXP(h)+100, false)
            call Register(h)
            
            call BJDebugMsg("Finished.")
            
            set h = null
        endmethod
        
    endstruct

    
endlibrary

This seems to work properly, but it throws a couple of errors. One of them is a debug message because of the options I have selected for TimerUtils, but the other seems to say that I am creating too many timers (despite only one being created).

Can you see anywhere that I may be using TimerUtils improperly? I find that changing the options in the TimerUtils library such as USE_HASH_TABLE and USE_FLEXIBLE_OFFSET significantly alters the outcome of units being registered.

I'm finding it very strange how changing irrelevant options (such as USE_HASH_TABLE) actually changes whether or not new timers are able to be allocated. I only allocate a single timer in the above code yet TimerUtils seems to think that the quantity of timers allocated exceeds the limit. Doesn't make any sense.

Oh, pfft. I know why, it's because I'm using it on a struct initialization.

JASS:
library BurnHeroXP requires TimerUtils, AutoIndex


    private struct burnxp
    
        timer ticker = null
        
        implement AutoData
        
        method onDestroy takes nothing returns nothing
            call ReleaseTimer(ticker)
        endmethod
        
        static method onTick takes nothing returns nothing
            local thistype d = GetTimerData(GetExpiredTimer())
            call AddHeroXP(d.me, -1, false)
        endmethod
        
        static method create takes unit u returns thistype
            local thistype d = allocate()
            
            set d.me = u
            set d.ticker = NewTimer()
            call SetTimerData(d.ticker, d)
            call TimerStart(d.ticker, 1, true, function thistype.onTick)
            
            return d
        endmethod
        
    endstruct
    

    
    //***
    //* - User Utility Interface -
    //* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
    //*
    //******
    public function Register takes unit subject returns nothing
        if (burnxp[subject] == 0) then
            call burnxp.create(subject)
        endif
    endfunction
    public function Unregister takes unit subject returns nothing
        if (burnxp[subject] != 0) then
            call burnxp[subject].destroy()
        endif
    endfunction

    
endlibrary

Now, regarding the first problem; you said that it gives you a double-free error a few seconds after a spell is used. Which struct is it exactly that is giving a double-free? It should say in the error.
 
Last edited:
Level 13
Joined
Jul 26, 2008
Messages
1,009
I don't see anything on it used improperly. However, have you tried a debug message that calls the unit name of d.me to see if it's bringing up the right data?

private constant boolean USE_HASH_TABLE = true
private constant boolean USE_FLEXIBLE_OFFSET = true

Is what I have for my settings. The settings do seem to alter a lot of things that use them. I once had a system completely not work until I changed those settings. But it seems like the SetUnitXP function doesn't want to work at all under these settings.

It must be like Nestharus says: The XP functions run on timers, so when placed within timers they won't sync up and thus they don't get removed.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
I wonder

->It must be like Nestharus says: The XP functions run on timers, so when placed within timers they won't sync up and thus they don't get removed.

When exactly did I say that? ; P

I certainly don't remember saying that ^_^.

That's not the problem.


Honestly, I'm not a fan of TimerUtils or AutoIndex, so I helped with the xp stuff and let you know that the struct allocation and deallocation itself was fine.
 
Level 18
Joined
Jan 21, 2006
Messages
2,552
It seemed to work for me when I tried decrementing a hero's XP by 1 every second. I initially set the hero's experience to 500 though and I believe he was still level 1 so perhaps you cannot decrement a hero's level through its experience.
 
Status
Not open for further replies.
Top