• 🏆 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] Timer Problem

Status
Not open for further replies.
After many suggestions of those who know something about JASS, i decided to leave the evil SleepWaits(although some times they are really cool and I use them) and started learning timers.

My friend Blue_jeans made some codes and i studied them in order to accomplish a classic spell called Apocalypse - which makes meteors fall from the sky every X seconds.

To make this spell i read a tutorial that my idol (Daelin) made.
The spell was in GUI, was not MUI and had a few leaks, but it is was still a very cool spell.

So I decided to apply my vJASS to it and to make it JESP, MUI and leak free.

I must confess I am impressed with what i accomplished so far. But it is still not enough for what i want to do.
So, I present you my problem:
- When cast the spell starts a timer that will expire in "5 * GetUnitAbilityLevel(data.caster, 'A000') + 10" seconds.
- Every "3 / GetUnitAbilityLevel(data.caster, 'A000')" seconds a meteor will fall from the sky damaging enemy units.
-PROBLEM: the spell is meant to end when the timer expires or the hero dies.
But although the function is fired, the spell never stops, and meteors keep falling even after the heroe's death.

That is why i need your help guys, help me learning how to use timers, for THW sake ! =P

This spell uses vJASS and ABC timing system, suggested by my friend Blue_Jeans.
I just wanna know how to fix it, that's all.
Btw, don't get scared, the logic of the spell is actually pretty simple to understand - that is mainly why i created this spell, after all, i am just a beginner to the timing world =P.

JASS:
struct Mystruct
    unit caster = GetTriggerUnit()
    timer repeator = CreateTimer()
    timer expirer = CreateTimer()
    trigger end = CreateTrigger()
    triggeraction endAction
    triggercondition endCondition
endstruct
//==========================================================================
function endConds takes nothing returns boolean 
    local Mystruct data = GetTriggerStructB(GetTriggeringTrigger())
    return GetTriggerUnit() == data.caster
endfunction
//==========================================================================
function endActs takes nothing returns nothing 
    //catches the expired timer and runs this function
    local Mystruct data = GetTimerStructB(GetExpiredTimer())
    
    
    //a test message
    call DisplayTextToPlayer(GetLocalPlayer(),0,0, "Ending Spell ??")
    
    //removes the created trigger in the Code_acts function
    call TriggerRemoveCondition(data.end,data.endCondition)
    call TriggerRemoveAction(data.end,data.endAction)
    call DestroyTrigger(data.end)
    call ClearTriggerStructB(data.end)
    
    //IT SHOULD stop the timer repeator, and cease the spell, but nothing happens. Help ! 
    call PauseTimer(data.repeator)
    call DestroyTimer(data.repeator)
    call ClearTimerStructA(data.repeator)
endfunction
//==========================================================================
function Apoca takes nothing returns nothing
    //catches the expired timer and runs this function
    local Mystruct data = GetTimerStructA(GetExpiredTimer())
    
    //creates a dummy that makes the meteor fall
    local unit dummy = CreateUnit(GetOwningPlayer(data.caster), 'h000', GetUnitX(data.caster), GetUnitY(data.caster), 0)
    call UnitAddAbility(dummy, 'A001')
    call IssuePointOrderLocBJ( dummy, "flamestrike", PolarProjectionBJ(GetUnitLoc(data.caster), GetRandomReal(300.00, 600.00), GetRandomReal(0.00, 360.00)) )
    call UnitApplyTimedLife(dummy, 'BTLF', 3)
    set dummy = null
endfunction
//==========================================================================
function Code_conds takes nothing returns boolean
    return GetSpellAbilityId() == 'A000'
endfunction
//==========================================================================
function Code_acts takes nothing returns nothing
    local Mystruct data = Mystruct.create()
    
    //starts the effect of the spell, meteors start falling from sky
    call SetTimerStructA(data.repeator, data)
    call TimerStart(data.repeator, 3 / GetUnitAbilityLevel(data.caster, 'A000'), true, function Apoca) 
    
    //creates the timer that ends the spell when expired
    call SetTimerStructB(data.expirer, data)
    call TimerStart(data.expirer, 5 * GetUnitAbilityLevel(data.caster, 'A000') + 10, false, null) 
    
    //creates the trigger that will end the spell when the timer expires or the hero dies
    call SetTriggerStructB(data.end, data)
    call TriggerRegisterTimerExpireEvent( data.end, data.expirer )
    call TriggerRegisterAnyUnitEventBJ(data.end, EVENT_PLAYER_UNIT_DEATH )
    set data.endCondition = TriggerAddCondition( data.end, Condition( function endConds ) )
    set data.endAction = TriggerAddAction( data.end, function endActs )
endfunction
//===========================================================================
function InitTrig_Code takes nothing returns nothing
    set gg_trg_Code = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_Code, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( gg_trg_Code, Condition( function Code_conds ) )
    call TriggerAddAction( gg_trg_Code, function Code_acts )
endfunction
 
Level 2
Joined
Feb 24, 2007
Messages
37
I might be mistakening but i dont know if this part works:
unit caster = GetTriggerUnit()


I think you need to do something like this:
JASS:
function Code_acts takes nothing returns nothing
 local Mystruct data = Mystruct.create()
 set data.caster = GetTriggerUnit()
 //*rest of the function*
endfunction

Also if the hero dies there is no expiring timer so GetExpiredTimer() will return null. Maybe check if the caster is alive inside the meteor spawn function, that way the timer will continue but the meteor's will stop to spawn if the hero is dead. For example:

JASS:
function Apoca takes nothing returns nothing    
//catches the expired timer and runs this function    
local Mystruct data = GetTimerStructA(GetExpiredTimer())    
//creates a dummy that makes the meteor fall    
local unit dummy = CreateUnit(GetOwningPlayer(data.caster), 'h000', GetUnitX(data.caster), GetUnitY(data.caster), 0)    
if GetWidgetLife(data.caster) > 0 then
   call UnitAddAbility(dummy, 'A001')    
   call IssuePointOrderLocBJ( dummy, "flamestrike", PolarProjectionBJ(GetUnitLoc(data.caster), GetRandomReal(300.00, 600.00), GetRandomReal(0.00, 360.00)) )
endif    
call UnitApplyTimedLife(dummy, 'BTLF', 3)    
set dummy = null
endfunction

call TriggerRegisterTimerExpireEvent( data.end, data.expirer )


Looks quite useless to me since you can also do it here either:
call TimerStart(data.expirer, 5 * GetUnitAbilityLevel(data.caster, 'A000') + 10, false, function endActs)


I hope this helped.
 
Level 29
Joined
Jul 29, 2007
Messages
5,174
I don't have the patience to read this ("lol so why are you commenting, idiot ?") but why aren't you just using one timer which will fire every time you want a meteor to fall (I.E: 3 / GetUnitAbilityLevel(data.caster, 'A000')) and with it also raise a integer's value.
When that integer's value hits the maximum time, stop everything.
 
Level 5
Joined
Oct 27, 2007
Messages
158
After many suggestions of those who know something about JASS, i decided to leave the evil SleepWaits(although some times they are really cool and I use them) and started learning timers.

My friend Blue_jeans made some codes and i studied them in order to accomplish a classic spell called Apocalypse - which makes meteors fall from the sky every X seconds.

To make this spell i read a tutorial that my idol (Daelin) made.
The spell was in GUI, was not MUI and had a few leaks, but it is was still a very cool spell.

So I decided to apply my vJASS to it and to make it JESP, MUI and leak free.

I must confess I am impressed with what i accomplished so far. But it is still not enough for what i want to do.
So, I present you my problem:
- When cast the spell starts a timer that will expire in "5 * GetUnitAbilityLevel(data.caster, 'A000') + 10" seconds.
- Every "3 / GetUnitAbilityLevel(data.caster, 'A000')" seconds a meteor will fall from the sky damaging enemy units.
-PROBLEM: the spell is meant to end when the timer expires or the hero dies.
But although the function is fired, the spell never stops, and meteors keep falling even after the heroe's death.

That is why i need your help guys, help me learning how to use timers, for THW sake ! =P

This spell uses vJASS and ABC timing system, suggested by my friend Blue_Jeans.
I just wanna know how to fix it, that's all.
Btw, don't get scared, the logic of the spell is actually pretty simple to understand - that is mainly why i created this spell, after all, i am just a beginner to the timing world =P.

You are using a wrong way of stopping the meteors. Even if the timer expires, the actions of the end trigger will never run. You use GetTriggerUnit in a context where it does not exist, so it will allways return false if the timer expires.

You have the proper events setup, however you don't do proper checking in the condtions. What you need to do is use GetTriggerEventId() and check what kind of event it is. In case of unit death then you use the GetTriggerUnit. If the event is timer expired you return true, simple as that.

ps. By using GetTriggerEventId you can avoid retrieving an expired timer in the wrong context. The same goes for when the timer expires. You avoid using GetTriggerUnit in a wrong context. And by retrieving the struct from the trigger instead of the expired timer you avoid having to doublecheck the event twice. You can safely retrieve it from the trigger anyway because both events use the same struct.

JASS:
function endConds takes nothing returns boolean
    if GetTriggerEventId() == EVENT_GAME_TIMER_EXPIRED then
        return true
    else
        return GetTriggerUnit() == GetTriggerStructB(GetTriggeringTrigger()).caster
    endif
endfunction
//==========================================================================
function endActs takes nothing returns nothing
    //catches the expired timer and runs this function
    local Mystruct data = GetTriggerStructB(GetTriggeringTrigger())
    
    
    //a test message
    call DisplayTextToPlayer(GetLocalPlayer(),0,0, "Ending Spell ??")
    
    //removes the created trigger in the Code_acts function
    call TriggerRemoveCondition(data.end,data.endCondition)
    call TriggerRemoveAction(data.end,data.endAction)
    call DestroyTrigger(data.end)
    call ClearTriggerStructB(data.end)
    
    //IT SHOULD stop the timer repeator, and cease the spell, but nothing happens. Help !
    call PauseTimer(data.repeator)
    call DestroyTimer(data.repeator)
    call ClearTimerStructA(data.repeator)
endfunction

That should work...
 
Last edited:
Hatred
I set my variables correctly, you can be sure of that. It is one of the many tricks vJASS has and few people know. It is in a tutorial made by Blue_Jeans, and i can show it to you if you want.
Also your solution is now what i need.
I just want to know how to "cut" the repeator timer, as my function DOES catch the "expirer" timer.
I made a few tests. If yuo run the code you will see my only problem is accessing the "repeator" timer in the function, so i can terminate it.

Wolf
Ahh, good to see you are alive my friend =)
Well, i could use you solution, but i would still have the same problem, not to mention that the spell would count the number of meteors that fall and not how much time they fall.
Read the answer i gave to Hatred though.

Drone
My code is fine mate. You should know this is programming, that there are 1000000 ways of doing the same code. I could make this spell without timers (using Sleeps) and without vJass. I could (and made it) in GUI.
But i choose this new approach for me because i wanna learn.
About your suggestion. It is most complex and beyond my knowledge.
What do you do with the else part ?
GetTriggerUnit() == GetTriggerStructB(GetTriggeringTrigger()).caster

How does this return a boolean ?
Please explain in detail the working of such new element to me, as i wish to learn how it works, so i can apply it in other contests.
I shall test your code.

Thx for you help guys =)

EDIT
Drone
return GetTriggerUnit() == GetTriggerStructB(GetTriggeringTrigger()).caster
does not even compile. It does not allow .syntax
Your suggestion didn't work.

Any other suggestions on fixing the code ?
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
for me attachment systems are useless , because you can simply use a struct and link the index of the stuct and what you want in the cache.
i don't want to lose my time to know how to use all functions in an attachment system.
So sorry for the spam but i can't help you
 
Level 5
Joined
Oct 27, 2007
Messages
158
Drone
My code is fine mate. You should know this is programming, that there are 1000000 ways of doing the same code. I could make this spell without timers (using Sleeps) and without vJass. I could (and made it) in GUI.
But i choose this new approach for me because i wanna learn.
About your suggestion. It is most complex and beyond my knowledge.
What do you do with the else part ?
GetTriggerUnit() == GetTriggerStructB(GetTriggeringTrigger()).caster

How does this return a boolean ?
Please explain in detail the working of such new element to me, as i wish to learn how it works, so i can apply it in other contests.
I shall test your code.

Thx for you help guys =)

EDIT
Drone
return GetTriggerUnit() == GetTriggerStructB(GetTriggeringTrigger()).caster
does not even compile. It does not allow .syntax
Your suggestion didn't work.

Any other suggestions on fixing the code ?
JASS:
return GetTriggerUnit() == Mystruct(GetTriggerStructB(GetTriggeringTrigger())).caster
I haven't tested that myself, so try if it accepts a typecast or just put back the local var of type Mystruct. That should work. And why wouldn't that return a boolean? I see an evaluation being made, which will return a boolean...


I have added a few simple evaluations that can distinguish proper events. How would that be complex? Explain to me how there will be a triggering unit when a timer expires and the trigger conditions are checked? The GetTriggerUnit() will return null because there is no trigger unit when your timer expires. If your hero dies, the actions get executed, however how is the actions trigger going to get an expired timer when there is none? If your code is fine then it would work. Since it's not working, your code is not fine. What part of that logic do you not understand?
Why do you always debate it when people question your code? Do you want help or not? Everytime I see you posting, you question people's advice.
 
I haven't tested that myself, so try if it accepts a typecast or just put back the local var of type Mystruct. That should work. And why wouldn't that return a boolean? I see an evaluation being made, which will return a boolean...
You mean:
JASS:
local Mystruct data = GetTriggerStructB(GetTriggeringTrigger()
if 
    //code
else
    return data.caster
endif
Wouldn't that return a unit instead of a boolean ?

I have added a few simple evaluations that can distinguish proper events. How would that be complex? Explain to me how there will be a triggering unit when a timer expires and the trigger conditions are checked? The GetTriggerUnit() will return null because there is no trigger unit when your timer expires. If your hero dies, the actions get executed, however how is the actions trigger going to get an expired timer when there is none? If your code is fine then it would work. Since it's not working, your code is not fine. What part of that logic do you not understand?
Why do you always debate it when people question your code? Do you want help or not? Everytime I see you posting, you question people's advice.

I don't understand what you want me to explain you about the code.
About questioning your advice: it's just me being me. I always question people advices in order to understand how they think and why they think like they do.
Ask Blue_jeans or Wolf.
It is not meant to be any kind of insulting, it's just that there is to many things to learn and so little time, that i must question everything to learn fast.
 
Level 5
Joined
Oct 27, 2007
Messages
158
You mean:
JASS:
local Mystruct data = GetTriggerStructB(GetTriggeringTrigger()
if 
    //code
else
    return data.caster
endif
Wouldn't that return a unit instead of a boolean ?

This is fine...

JASS:
local Mystruct data = GetTriggerStructB(GetTriggeringTrigger())

return GetTriggerUnit() == data.caster


...But I'd do it like this. I don't use local vars when I don't have to. With a typecast it should allow this... Try it... and the condition and action code I put up earlier. It should work, just try it.

JASS:
return GetTriggerUnit() == Mystruct(GetTriggerStructB(GetTriggeringTrigger())).caster
 

Dr Super Good

Spell Reviewer
Level 63
Joined
Jan 18, 2005
Messages
27,192
A timer on loop runs one more time after it is destroyed. This can cause major bugs in spells if it left unknown however methods have been made to avoid it. It may also leak if paused and destroyed, I am not sure.

Timer recycling is one method.

Ill post such a system in a bit that I made where it basically never destroys a timer but returns a previously used one which is paused. All you need to do is then restart it (not even unpause it) and it will work as good as a freshly created one. Also as you recycle them you will never have more than you need.

JASS:
library TimerBank

    //-------------------Timer Bank-------------------//
    //This system is offically released and may be used and edited as long as . . .
    // - You do not blame me for bugs that occure as a fault of you using this system.
    //
    //------------------User manual-------------------//
    //The system basically acts as a method to store timers and fetch them later to prevent leaks or bugs.
    //You can fetch an infinate number of timers from this system but can only store a maximum of 8192 timers at once.
    //I saw no need to put any support for more than that number of timers since I see no way a normal map will rach that limate.
    //
    //You can use the following functions to interface with this.
    // Withdraw - Fetches a unique timer from the system.
    // Deposit  - Deposits a timer into the system and pauses it.
    //            Only use this on a timer when you do no longer need it and will not be using it any more.
    //------------------------------------------------//

    globals
        private timer array timers
        private integer n=0
    endglobals
    
    public function Withdraw takes nothing returns timer
        set n=n-1
        if n < 0 then
            set n=0
            return CreateTimer()
        endif
        return timers[n]
    endfunction
    
    public function Deposit takes timer t returns nothing
        call PauseTimer(t)
        set timers[n] = t
        set n=n+1
        set t = null
    endfunction
endlibrary

Also you can avoid having to store values by loop finding the object in the struct list. which can be usefull.

Also acording to hindy, conditions do not need to be removed from conditions and do not leak if not. THus you can avoid having to store anything by only having a condition and no action on a trigger. I used that for my damage shield system and it seemed to function flawlessly.
 
Last edited:
Level 5
Joined
Oct 27, 2007
Messages
158
Drone, your advice worked quite well.
However, the code the function runs two times instead of only one. Do you know why this happens ?


Endact is ran twice? You mean that you see Ending Spell ?? displayed twice? Dr. Super Good refers to the Apoca callback being run after its timer is destroyed. What function are you referring to? I do believe that if the callback apoca or endact is run twice, wc3 would crash, since a struct is no longer attached to the timer.
 
Level 9
Joined
Mar 25, 2005
Messages
252
Pausing the timer is what stops that (it from expiring after its death) from happening. Recycling timers is a good idea, but you don't need to do that if you only want to fix this issue. In other words you can just pause the timer and then destroy it (atleast to check if that really is the issue before implementing a timer recycler).
Recycling timers (and most of anything else you can recycle) is a good idea though so I'd recommend that you use a system that does that, such as the one Dr Super Good posted. There are a few other systems that work just the same though, the most widely used being CSSafety I believe, not that it's any better but it's more of a standard (imo) and people know it if you refer to it and more people use it so if you import their spells it might be a good idea to use it instead (but if you don't then there really is no reason to choose it over this TimerBank library or anything else).
I don't know if recycling timers is any faster than destroying and creating them but there is some stupid bug that can happen in rare situations when you null a local timer variable, but since timers that are recycled are never destroyed you don't need to null variables pointing to them, which is why you recycling them can save you from that bug.
 
Level 5
Joined
Oct 27, 2007
Messages
158
I don't know if recycling timers is any faster than destroying and creating them but there is some stupid bug that can happen in rare situations when you null a local timer variable, but since timers that are recycled are never destroyed you don't need to null variables pointing to them, which is why you recycling them can save you from that bug.


Just curious.... What bug exactly?
 
Level 5
Joined
Oct 27, 2007
Messages
158
I think it prevents some kind of leak, atleast people say its a good idea to do as they say pausing timers and destroying causes bugs or leaks.


Do you have a link where those leaks which are supposed to be caused by pausing/destroying timers are explained. I know for a fact that it doesn't leak handles when pausing/destroying. What bugs does it cause? I've never been able to produce a bug when pausing/destroying timers.

I just did a little test for the fun of it. I couldn't produce a bug...
Using the calls does cause some form of delay of course, but it shows at least that handles are recycled and that with pause/destroy a callback isn't run after timer destruction.
ps. I also tested with higher callback amounts. Things will get rather choppy ofc..... but still doesn't cause a bug.

JASS:
globals
    integer runs
endglobals

function Callback takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local integer i = GetHandleInt(t)

    set runs = runs - 1
    set i = i - 1
    if runs == 0 then
        call DisplayTextToPlayer(user, 0, 0, "Everything is fine, works just like it should!")
    elseif runs < 0 then
        call DisplayTextToPlayer(user, 0, 0, "Hmmm naughty naughty!")
    endif
    if i == 0 then
        call PauseTimer(t)
        call DestroyTimer(t)
    else
        call SetHandleInt(t, i)
    endif
    set t = null
endfunction

function Trig_TimerTest_Actions takes nothing returns nothing
    local timer t
    local integer i = 0

    set runs = 10000 // 1000 timers executing the callback 10 times each, a bit overkill....
    loop
        exitwhen i == 1000
        set t = CreateTimer()
        call SetHandleInt(t, 10)
        call DisplayTextToPlayer(user, 0, 0, I2S(H2I(t)))
        call TimerStart(t, .01, true, function Callback)
        set i = i + 1
    endloop
    set t = null
endfunction

//===========================================================================
function InitTrig_TimerTest takes nothing returns nothing
    local trigger t = CreateTrigger()

    call TriggerRegisterPlayerChatEvent(t, user, "-timer", true)
    call TriggerAddAction(t, function Trig_TimerTest_Actions)
    set t = null
endfunction
 
Last edited:
Level 9
Joined
Mar 25, 2005
Messages
252
Go to the following link and press page down once.
The ”Setting variables to null when using the return bug”-bug


I didn't actually remember that this bug had something to do with the return bug... hmm... but Im quite sure that that was the bug I was thinking of.

[EDIT] PS:
This bug is very rare and I personally have countered it only once. It doesn't happen randomly afaik, but no one knows how to predict when it happens, so if you have spells or systems that technically might be prone to this bug, but work fine, they should work fine in the future also.
 
Level 11
Joined
Apr 6, 2008
Messages
760
why do u do (havnt used vJass my self since i cant get darn newgen to work :/ )

JASS:
//creates the timer that ends the spell when expired
    call SetTimerStructB(data.expirer, data)
    call TimerStart(data.expirer, 5 * GetUnitAbilityLevel(data.caster, 'A000') + 10, false, null)

    //creates the trigger that will end the spell when the timer expires or the hero dies
    call SetTriggerStructB(data.end, data)
    call TriggerRegisterTimerExpireEvent( data.end, data.expirer )
    call TriggerRegisterAnyUnitEventBJ(data.end, EVENT_PLAYER_UNIT_DEATH )
    set data.endCondition = TriggerAddCondition( data.end, Condition( function endConds ) )
    set data.endAction = TriggerAddAction( data.end, function endActs )

use this instead and u wont need the second timer
JASS:
call TriggerRegisterUnitEvent(data.end,data.caster, EVENT_UNIT_SPELL_ENDCAST))

this event will go off if the unit dies 2
 
Level 5
Joined
Oct 27, 2007
Messages
158
Go to the following link and press page down once.
The ”Setting variables to null when using the return bug”-bug


I didn't actually remember that this bug had something to do with the return bug... hmm... but Im quite sure that that was the bug I was thinking of.

[EDIT] PS:
This bug is very rare and I personally have countered it only once. It doesn't happen randomly afaik, but no one knows how to predict when it happens, so if you have spells or systems that technically might be prone to this bug, but work fine, they should work fine in the future also.

The ”Setting variables to null when using the return bug”-bug

In very rare situations, setting a variable (most often a timer) which content’s you use with the return bug can cause problems. It seems like it sometimes resets the object’s handle id.

Resets as in the reference to the (timer) object is completely lost due to some reference count bug?
 

Dr Super Good

Spell Reviewer
Level 63
Joined
Jan 18, 2005
Messages
27,192
My theroy behind why recycling is better is that I think a leak occurs. BY the way a destroyed timer can still expire, it means that some data is still left in RAM that tells it when to expire. Pausing the timer before exparation means the data is paused but not stopped so leaks into the memory where it stays forever.

I have no proof for this but no one has not proved this and people keep say recycling timers is good.
 
Mmm, i don't know if my spell is a rare example of such bug. But I do know that the text "End Spell??" (or something like this) actually appears twice, which means that my function endActs actually runs twice, even after destroying the timer.
My Apoca function has no bugs and works very well (as far as I know). It's only problem is using a BJ, but I will fix that soon, just didn't had time to waste with such a small problem.

Also Ciebron, I need two timers. One to count the number of seconds the spell last (expirer) and the other to call Apoca evey X seconds (make a meteor falll evey 3, 1.5 and 1 seconds, depending on the level).
An alternative studied by me, would be to create a third trigger that would have as an event a repeating timer, but i discarded that idea because it would make the spell heavier for no good reason.

Also, you guys say i don't need to remove the condition of my trigger ?? That it won't leak? What about the actions ? Please explain it, as I didn't understood that part very well =S

Mmmm About recycling system .... I really don't see how i would use it here. I just wanna get rid of the two timers, like killing them for good, but according to your posts, it seems that such action causes bugs ... man I am confused lol... Is there any tutorial explaining how to use the system and the bugs ?

Well, anyway, here is the code I am using now, it may help you all understand my problem.
JASS:
struct Mystruct
    unit caster = GetTriggerUnit()
timer repeator = CreateTimer()
    timer expirer = CreateTimer()
    trigger end = CreateTrigger()
    triggeraction endAction
    triggercondition endCondition
endstruct
//==========================================================================
function endConds takes nothing returns boolean
    local Mystruct data = GetTriggerStructB(GetTriggeringTrigger())
    if GetTriggerEventId() == EVENT_GAME_TIMER_EXPIRED then
        return true
    else
        return GetTriggerUnit() == data.caster
    endif
endfunction
//==========================================================================
function endActs takes nothing returns nothing
    //catches the expired timer and runs this function
    local Mystruct data = GetTimerStructB(GetExpiredTimer())


    //a test message
    call DisplayTextToPlayer(GetLocalPlayer(),0,0, "Ending Spell ??")

    //removes the created trigger in the Code_acts function
    call TriggerRemoveCondition(data.end,data.endCondition)
    call TriggerRemoveAction(data.end,data.endAction)
    call DestroyTrigger(data.end)
    call ClearTriggerStructB(data.end)

    //IT SHOULD stop the timer repeator, and cease the spell, but nothing happens. Help !
    call PauseTimer(data.repeator)
    call DestroyTimer(data.repeator)
    call ClearTimerStructA(data.repeator)
endfunction
//==========================================================================
function Apoca takes nothing returns nothing
    //catches the expired timer and runs this function
    local Mystruct data = GetTimerStructA(GetExpiredTimer())
    
    //defines somelocal variables. This function is not 100% done yet, so don't get
    //scared if you see too much variables, as they will be needed later
    local integer infernal = GetRandomInt(0, 100)
    local real randomX = GetRandomReal(300.00, 600.00)
    local real randomY = GetRandomReal(300.00, 600.00)
    local unit dummy
    
    //yes I know I leak a location here, but that is not much of a problem rit now
    //I am more concerned about using timers, and I shall fix this later
    if (infernal <= 5 * GetUnitAbilityLevel(data.caster, 'A000')) then
        set dummy = CreateUnit(GetOwningPlayer(data.caster), 'h000', GetUnitX(data.caster), GetUnitY(data.caster), 0)
        call UnitAddAbility(dummy, 'A002')
        call SetUnitAbilityLevel(dummy, 'A002', GetUnitAbilityLevel(data.caster, 'A000'))
        call IssuePointOrderLocBJ( dummy, "dreadlordinferno", PolarProjectionBJ(GetUnitLoc(data.caster), randomX, randomY) )
        call UnitApplyTimedLife(dummy, 'BTLF', 3)
    else 
        set dummy = CreateUnit(GetOwningPlayer(data.caster), 'h000', GetUnitX(data.caster), GetUnitY(data.caster), 0)
        call UnitAddAbility(dummy, 'A001')
         call SetUnitAbilityLevel(dummy, 'A001', GetUnitAbilityLevel(data.caster, 'A000'))
        call IssuePointOrderLocBJ( dummy, "flamestrike", PolarProjectionBJ(GetUnitLoc(data.caster), randomX, randomY) )
        call UnitApplyTimedLife(dummy, 'BTLF', 3)
    endif
    
    set dummy = null
endfunction
//==========================================================================
function Code_conds takes nothing returns boolean
    return GetSpellAbilityId() == 'A000'
endfunction
//==========================================================================
function Code_acts takes nothing returns nothing
    local Mystruct data = Mystruct.create()

    //starts the effect of the spell, meteors start falling from sky
    call SetTimerStructA(data.repeator, data)
    call TimerStart(data.repeator, 3 / GetUnitAbilityLevel(data.caster, 'A000'), true, function Apoca)

//creates the timer that ends the spell when expired
    call SetTimerStructB(data.expirer, data)
    call TimerStart(data.expirer, 5 * GetUnitAbilityLevel(data.caster, 'A000') + 10, false, null)

    //creates the trigger that will end the spell when the timer expires or the hero dies
    call SetTriggerStructB(data.end, data)
    call TriggerRegisterTimerExpireEvent( data.end, data.expirer )
    call TriggerRegisterAnyUnitEventBJ(data.end, EVENT_PLAYER_UNIT_DEATH )
    set data.endCondition = TriggerAddCondition( data.end, Condition( function endConds ) )
    set data.endAction = TriggerAddAction( data.end, function endActs )
endfunction
//===========================================================================
function InitTrig_Code takes nothing returns nothing
    set gg_trg_Code = CreateTrigger( )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_Code, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( gg_trg_Code, Condition( function Code_conds ) )
    call TriggerAddAction( gg_trg_Code, function Code_acts )
endfunction

I can also post the map here if you guys want. However for now I would just like to know how the bug is created =S
 
Level 5
Joined
Oct 27, 2007
Messages
158
Mmm, i don't know if my spell is a rare example of such bug. But I do know that the text "End Spell??" (or something like this) actually appears twice, which means that my function endActs actually runs twice, even after destroying the timer.
My Apoca function has no bugs and works very well (as far as I know). It's only problem is using a BJ, but I will fix that soon, just didn't had time to waste with such a small problem.

Well, anyway, here is the code I am using now, it may help you all understand my problem.
JASS:
struct Mystruct
    unit caster = GetTriggerUnit()
timer repeator = CreateTimer()
    timer expirer = CreateTimer()
    trigger end = CreateTrigger()
    triggeraction endAction
    triggercondition endCondition
endstruct
//==========================================================================
function endConds takes nothing returns boolean
    local Mystruct data = GetTriggerStructB(GetTriggeringTrigger())
    if GetTriggerEventId() == EVENT_GAME_TIMER_EXPIRED then
        return true
    else
        return GetTriggerUnit() == data.caster
    endif
endfunction
//==========================================================================
function endActs takes nothing returns nothing
    //catches the expired timer and runs this function
    local Mystruct data = GetTriggerStructB(GetTriggeringTrigger())
    // Using GetExpiredTimer() here when the hero dies is out of context. There is no expired timer then. Just retrieve the struct from the trigger.


    //a test message
    call DisplayTextToPlayer(GetLocalPlayer(),0,0, "Ending Spell ??")

    //removes the created trigger in the Code_acts function
    call TriggerRemoveCondition(data.end,data.endCondition)
    call TriggerRemoveAction(data.end,data.endAction)
    call DestroyTrigger(data.end)
    call ClearTriggerStructB(data.end)

    //IT SHOULD stop the timer repeator, and cease the spell, but nothing happens. Help !
    call PauseTimer(data.repeator)
    call DestroyTimer(data.repeator)
    call ClearTimerStructA(data.repeator)
endfunction
//==========================================================================
function Apoca takes nothing returns nothing
    //catches the expired timer and runs this function
    local Mystruct data = GetTimerStructA(GetExpiredTimer())
    
    //defines somelocal variables. This function is not 100% done yet, so don't get
    //scared if you see too much variables, as they will be needed later
    local integer infernal = GetRandomInt(0, 100)
    local real randomX = GetRandomReal(300.00, 600.00)
    local real randomY = GetRandomReal(300.00, 600.00)
    local unit dummy
    
    //yes I know I leak a location here, but that is not much of a problem rit now
    //I am more concerned about using timers, and I shall fix this later
    if (infernal <= 5 * GetUnitAbilityLevel(data.caster, 'A000')) then
        set dummy = CreateUnit(GetOwningPlayer(data.caster), 'h000', GetUnitX(data.caster), GetUnitY(data.caster), 0)
        call UnitAddAbility(dummy, 'A002')
        call SetUnitAbilityLevel(dummy, 'A002', GetUnitAbilityLevel(data.caster, 'A000'))
        call IssuePointOrderLocBJ( dummy, "dreadlordinferno", PolarProjectionBJ(GetUnitLoc(data.caster), randomX, randomY) )
        call UnitApplyTimedLife(dummy, 'BTLF', 3)
    else 
        set dummy = CreateUnit(GetOwningPlayer(data.caster), 'h000', GetUnitX(data.caster), GetUnitY(data.caster), 0)
        call UnitAddAbility(dummy, 'A001')
         call SetUnitAbilityLevel(dummy, 'A001', GetUnitAbilityLevel(data.caster, 'A000'))
        call IssuePointOrderLocBJ( dummy, "flamestrike", PolarProjectionBJ(GetUnitLoc(data.caster), randomX, randomY) )
        call UnitApplyTimedLife(dummy, 'BTLF', 3)
    endif
    
    set dummy = null
endfunction
//==========================================================================
function Code_conds takes nothing returns boolean
    return GetSpellAbilityId() == 'A000'
endfunction
//==========================================================================
function Code_acts takes nothing returns nothing
    local Mystruct data = Mystruct.create()

    //starts the effect of the spell, meteors start falling from sky
    call SetTimerStructA(data.repeator, data)
    call TimerStart(data.repeator, 3 / GetUnitAbilityLevel(data.caster, 'A000'), true, function Apoca)

//creates the timer that ends the spell when expired
    call SetTimerStructB(data.expirer, data)
    call TimerStart(data.expirer, 5 * GetUnitAbilityLevel(data.caster, 'A000') + 10, false, null)

    //creates the trigger that will end the spell when the timer expires or the hero dies
    call SetTriggerStructB(data.end, data)
    call TriggerRegisterTimerExpireEvent( data.end, data.expirer )
    call TriggerRegisterAnyUnitEventBJ(data.end, EVENT_PLAYER_UNIT_DEATH )
    set data.endCondition = TriggerAddCondition( data.end, Condition( function endConds ) )
    set data.endAction = TriggerAddAction( data.end, function endActs )
endfunction
//===========================================================================
function InitTrig_Code takes nothing returns nothing
    set gg_trg_Code = CreateTrigger( )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_Code, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( gg_trg_Code, Condition( function Code_conds ) )
    call TriggerAddAction( gg_trg_Code, function Code_acts )
endfunction
I can also post the map here if you guys want. However for now I would just like to know how the bug is created =S


Well there are only two events registered for that trigger. So you could simply start elimination. Disable the timer expire event and let the hero die. See if the trigger still gets fired twice.

If not then try the timer. In the function pause the expire timer and destroy it. I would find it very strange that the expirer actually fires twice but, it's worth a try.

ps. You also didn't change the struct retrieve in the actions. You're still trying to retrieve a struct from an expired timer when the event is unit death.
 
Well there are only two events registered for that trigger. So you could simply start elimination. Disable the timer expire event and let the hero die. See if the trigger still gets fired twice.

If not then try the timer. In the function pause the expire timer and destroy it. I would find it very strange that the expirer actually fires twice but, it's worth a try.

ps. You also didn't change the struct retrieve in the actions. You're still trying to retrieve a struct from an expired timer when the event is unit death.

Well, the spell works in perfection if I just let the timer expire. I want 15 secs (lv 1) the timer expires, i read the text 1 time and the spell ends.

The problem is when I kill the hero, it seems to run 2 times.

ABout the structure, well, i really don't understand what I am doing wrong, or what I have to change =S
The only error for now, is the fact that I call endActs 2 times when the hero dies, everything else seems to be working fine.
 
Level 5
Joined
Oct 27, 2007
Messages
158
Well, the spell works in perfection if I just let the timer expire. I want 15 secs (lv 1) the timer expires, i read the text 1 time and the spell ends.

The problem is when I kill the hero, it seems to run 2 times.

ABout the structure, well, i really don't understand what I am doing wrong, or what I have to change =S
The only error for now, is the fact that I call endActs 2 times when the hero dies, everything else seems to be working fine.

That's what I'm trying so hard to explain to you......

EVENT: The hero dies

Condition check goes ok, actions are fired.

ACTIONS: You are now trying to retrieve an expired timer where there is no expired timer!!!!!!
What will happen is that the destroy will not execute because you can't retrieve the data structure from a null timer...

Later on the timer will expire and then you will have a valid expired timer because the event is the right one.
That explains why you get the message twice, but the destruct only happens once because you simply won't have a valid Mystruct when the hero dies.

Please Just just... do the change I suggested in my last post.. Look at the code. I thought you would've changed that by now....
 
Level 5
Joined
Oct 27, 2007
Messages
158
JASS:
 local Mystruct data = GetTriggerStructB(GetTriggeringTrigger())
    if GetTriggerEventId() == EVENT_GAME_TIMER_EXPIRED then
        return true
    else
        return GetTriggerUnit() == data.caster
    endif
You mean this modification ???
Maybe I am not seeing it rit.=S

Or do you mean the recycle timer thingy ?

Change this......

JASS:
function endActs takes nothing returns nothing
    //catches the expired timer and runs this function
    local Mystruct data = GetTimerStructB(GetExpiredTimer())


    //a test message
    call DisplayTextToPlayer(GetLocalPlayer(),0,0, "Ending Spell ??")

into this....

JASS:
function endActs takes nothing returns nothing
    //catches the expired timer and runs this function
    local Mystruct data = GetTriggerStructB(GetTriggeringTrigger())


    //a test message
    call DisplayTextToPlayer(GetLocalPlayer(),0,0, "Ending Spell ??")
 
Level 5
Joined
Oct 27, 2007
Messages
158
still dont get why u are using 2 timers...

That's because it isn't needed like you said earlier. You can set the death event for the caster unit only and use one timer to get the job done. You can simply calculate how many Apoca timer ticks are needed before it should expire, or be paused/destroyed when the hero dies. But if he wants to do it the hard way let him. But sometimes learning it the hard way can be a good way too, and Flame Phoenix is kind of stubborn :grin:
 

Dr Super Good

Spell Reviewer
Level 63
Joined
Jan 18, 2005
Messages
27,192
Ok the actual reason why to recycle timers.
Everytime a timer is created and destroyed, it leaks a bit (used handle adresses or something) which results in overall slow downs late game. Recycling them prevents this slowdown.

Recycling both timers and groups (which I do not do yet) also means that you do not have to null the locals which stored them, making up some of the lost efficency.

If you are still worried about the efficency, you can increase it even further by predifining timers and removing the if statement since if you predifine enough that you never run out, it is not needed.
 
That's because it isn't needed like you said earlier. You can set the death event for the caster unit only and use one timer to get the job done. You can simply calculate how many Apoca timer ticks are needed before it should expire, or be paused/destroyed when the hero dies. But if he wants to do it the hard way let him. But sometimes learning it the hard way can be a good way too, and Flame Phoenix is kind of stubborn

Next time i won't + rep you .... lol
You mean that instead of counting the seconds the spell last, I should count the ticks of the clock !? Isn't that the same thing ?

Ok the actual reason why to recycle timers.
Everytime a timer is created and destroyed, it leaks a bit (used handle adresses or something) which results in overall slow downs late game. Recycling them prevents this slowdown.

Recycling both timers and groups (which I do not do yet) also means that you do not have to null the locals which stored them, making up some of the lost efficency.

If you are still worried about the efficency, you can increase it even further by predifining timers and removing the if statement since if you predifine enough that you never run out, it is not needed.
So it basically means that my spell leaks !? NOOOOO
How can I use recycling on my spell ?
 
Level 5
Joined
Oct 27, 2007
Messages
158
Ok the actual reason why to recycle timers.
Everytime a timer is created and destroyed, it leaks a bit (used handle adresses or something) which results in overall slow downs late game. Recycling them prevents this slowdown.


So it basically means that my spell leaks !? NOOOOO
How can I use recycling on my spell ?
Well pausing and destroying the timer, cleaning up is just fine and I haven't encountered any problems using it that way. There are people who prefer to recycle timers. I personally don't do it that way.

I found out by testing that a timer behaves badly when the timer interval gets < .46 ms (at least on my PC) At those intervals callbacks get called after destroying the timer. Number of callbacks and timers didn't matter. Note however that pausing it before destroying at any interval prevents this! Those intervals aren't used much anyway. The only reason why I'd say that timer callbacks become a problem when not pausing a timer before destroying, is when you have many threaded actions going on in your map, where some expired timers are queued to get exec time. When a destruction occurs at that moment then it's probably too late to remove it from the exec queue, and that's also probably the reason why the callback gets executed after destruction. So pausing is a solid solution to prevent this kind of problem.

Next time i won't + rep you .... lol
You mean that instead of counting the seconds the spell last, I should count the ticks of the clock !? Isn't that the same thing ?

With ticks I simply mean the times the callback should execute. You know the interval at which the callback runs. You know the total expire time. So, then you know how many times the callback should run.

expire time / interval = runtimes

And if ... if the callback is queued for execution and right before that the hero died then it's probably safest to do the destroy in the timer callback. You can simply set a boolean in the attached struct, that the spell has ended and the timer should stop. You could also simply check the unit state of the caster before allowing the rest of the callback code to run. But then again simply pause and destroy in the trigger should work fine too.
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
the one in the link is the best i've ever seen and really simple.
it's the proof that the most simple things are often the best ones.

But like i said for "create" a timer or "destroy" it you need to call a function, if you really care about efficient you can use an textmacro or maybe with the new jasshelper an inline function, but i didn't really understood which functions get inlined or not.
 
Level 5
Joined
Oct 27, 2007
Messages
158
Quote from http://wc3campaigns.net/showpost.php?p=866375&postcount=1

Timer Nullifying
This is perhaps one of the most annoying issues with the timer variable. While the timer is very useful for jassers all over the world, there is a severe pitfall if it is used in conjunction with game cache storage. If you nullify a locally declared timer with set SomeTimer = null and you've set handles onto that timer, there is a chance the handles will return null the next time you attempt to recall them. This bug can be averted nearly entirely with the above timer recycling, since it removes the need to ever nullify timers. (As they are re-used)


Well its still vague as to what chance and fixing it nearly entirely. There could be other factors as well as to why it happens. But I for one don't use game cache. If you use lots of timers (almost) continuously for different purposes, then recycling would be a good idea. It's pretty pointless to destroy a timer when something else needs to create a timer again in short succession. But for non intensive timer usage I see no point in recycling.
 
Status
Not open for further replies.
Top