Snippet ExecuteSoon

Builder Bob

Live free or don't
Reaction score
249
What is this?
ExecuteSoon() will execute a function within 0 seconds, but not right away.
This allows all the current fired events to finish before executing your function.​

Why?
Some events have after effects. Here are just a few examples.
  • Functions triggered by damage events will finish before any damage is dealt.
  • Functions triggered when moving an item in the inventory (order event) will finish before the item has actually moved.

Blue Flavor:
JASS:
//==============================================================================
//  ExecuteSoon (Blue flavor) -- by Builder Bob -- v1.1
//==============================================================================


//==============================================================================
//  FUNCTION INDEX:
//==============================================================================
//
//    -------------------------------------------------------------
//
//      ExecuteSoon(condition, data)
//
//      GetExecuteSoonData()
//      returns integer
//
//-----------------------------------------------------------------------------


//==============================================================================
//  DOCUMENTATION:
//==============================================================================
//
//  WHAT IS THIS?
//      * ExecuteSoon() will execute a function within 0 seconds, but not right away.
//         This allows all the current fired events to finish before executing your function
//
//  BLUE FLAVOR PROS:
//      * More user friendly.
//
//      * GetExecuteSoonData() is optional, rather than mandatory.
//
//      * Consequtive ExecuteSoon calls within functions called via ExecuteSoon
//         will also trigger after other queued functions finish, as designed.
//
//  BLUE FLAVOR CONS:
//      * Slightly less efficient.
//
//  DETAILS:
//      * The function passed as argument in ExecuteSoon must be made a condition
//         like Condition(function whichFunction)
//
//      * The condition used as argument in ExecuteSoon MUST return a boolean.
//         true or false is irrelevant.
//
//      * GetExecuteSoonData() is optional, and will return the data you pass
//         along with the queued function
//
//  THANKS TO:
//      * Cohadar for good documentation standard
//
//  HOW TO IMPORT:
//      * create a trigger
//      * convert it to text and replace the whole trigger text with this one
//
//==============================================================================

library ExecuteSoon initializer onInit

private struct Data
    Data next
    trigger trig
    integer data
endstruct

globals
    private timer Timer = CreateTimer()
    private Data First = 0
    private Data Last = 0
    private Data This = 0
    private Data Prev = 0
endglobals

private function Execute takes nothing returns nothing
    set This = First.next
    call First.destroy()
    set First = Data.create()
    set First.next = 0
    set Last = First
    loop
        exitwhen(This == 0)
        call TriggerEvaluate(This.trig)
        call TriggerClearConditions(This.trig)
        call This.destroy()
        set Prev = This
        set This = Prev.next
    endloop
endfunction

function ExecuteSoon takes boolexpr condition, integer data returns nothing
    set Last.next = Data.create()
    set Last = Last.next
    set Last.next = 0
    set Last.data = data
    if Last.trig == null then
        set Last.trig = CreateTrigger()
    endif
    
    call TriggerAddCondition(Last.trig, condition)
    call ResumeTimer(Timer)
endfunction

function GetExecuteSoonData takes nothing returns integer
    return This.data
endfunction

private function onInit takes nothing returns nothing
    set First = Data.create()
    set First.next = 0
    set Last = First
    call TimerStart(Timer, .0, false, function Execute)
endfunction

endlibrary



Red Flavor:
JASS:
//==============================================================================
//  ExecuteSoon (Red flavor) -- by Builder Bob -- v1.1
//==============================================================================


//==============================================================================
//  FUNCTION INDEX:
//==============================================================================
//
//    -------------------------------------------------------------
//
//      ExecuteSoon(condition, data)
//
//      GetExecuteSoonData()
//      returns integer
//
//-----------------------------------------------------------------------------


//==============================================================================
//  DOCUMENTATION:
//==============================================================================
//
//  WHAT IS THIS?
//      * ExecuteSoon() will execute a function within 0 seconds, but not right away.
//         This allows all the current fired events to finish before executing your function
//
//  RED FLAVOR PROS:
//      * Faster than blue flavor
//
//  RED FLAVOR CONS:
//      * GetExecuteSoonData MUST be called once and only once in
//         every function you call via ExecuteSoon.
//
//      * Consequtive ExecuteSoon calls within functions called via ExecuteSoon
//         will trigger instantly, rather than after other queued functions finish.
//
//  DETAILS:
//      * The function passed as argument in ExecuteSoon must be made a condition
//         like Condition(function whichFunction)
//
//      * The condition used as argument in ExecuteSoon MUST return a boolean.
//         true or false is irrelevant.
//
//      * GetExecuteSoonData MUST be called once and only once in
//         every function you call via ExecuteSoon.
//
//  THANKS TO:
//      * Jesus4Lyf for help on Red Flavor for maximum speed
//      * Cohadar for good documentation standard
//
//  HOW TO IMPORT:
//      * create a trigger
//      * convert it to text and replace the whole trigger text with this one
//
//==============================================================================

library ExecuteSoon initializer onInit

globals
    private timer Timer = CreateTimer()
    private trigger Trigger = CreateTrigger()
    private integer array Data
    private integer Index = 0
    private integer TopIndex = 0
endglobals

private function Execute takes nothing returns nothing
    set Index = -1
    call TriggerEvaluate(Trigger)
    call TriggerClearConditions(Trigger)
    set TopIndex = 0
endfunction

function ExecuteSoon takes boolexpr condition, integer data returns nothing
    call TriggerAddCondition(Trigger, condition)
    set Data[TopIndex] = data
    
    set TopIndex = TopIndex + 1
    call ResumeTimer(Timer)
endfunction

function GetExecuteSoonData takes nothing returns integer
    set Index = Index + 1
    return Data[Index]
endfunction

private function onInit takes nothing returns nothing
    call TimerStart(Timer, .0, false, function Execute)
endfunction

endlibrary


I'd consider this a snippet as it'll probably make more sense to just code it directly into other systems, than to call these functions. Use whatever works best for you.
 

Jesus4Lyf

Good Idea™
Reaction score
397
KT2's execution is faster, but the good news is you can steal it to beat it for this specialised case. Put all the conditions on one trigger, and call TriggerClearConditions at the end of it. The order you add the conditions is the order they shall fire, so you can make GetData tick the data up, so next time it is called it will get the next data.

I wanted to write this myself, but you beat me to it. So I'll just give you my ideas for optimisation instead. :)
 

Builder Bob

Live free or don't
Reaction score
249
KT2's execution is faster, but the good news is you can steal it to beat it for this specialised case. Put all the conditions on one trigger, and call TriggerClearConditions at the end of it. The order you add the conditions is the order they shall fire, so you can make GetData tick the data up, so next time it is called it will get the next data.

I wanted to write this myself, but you beat me to it. So I'll just give you my ideas for optimisation instead. :)

Oh, that's really clever. I'll do that optimization at the next free moment.
 

Jesus4Lyf

Good Idea™
Reaction score
397
JASS:
private function InitExecuteSoon takes nothing returns nothing
    call TimerStart(Timer, .0, false, function Execute)
endfunction

And then ResumeTimer(...)? Haven't tried it, give it a shot. I expect it's more efficient, and I'm not sure that timers like being double started... :)

Anyway, this will be fun to see because your execute function is going to go down to about 3 lines.

Note that you should document somewhere that GetExecuteSoonData() must be called exactly once within the condition.
 

Builder Bob

Live free or don't
Reaction score
249
JASS:
private function InitExecuteSoon takes nothing returns nothing
    call TimerStart(Timer, .0, false, function Execute)
endfunction

And then ResumeTimer(...)? Haven't tried it, give it a shot. I expect it's more efficient, and I'm not sure that timers like being double started... :)

Anyway, this will be fun to see because your execute function is going to go down to about 3 lines.

Note that you should document somewhere that GetExecuteSoonData() must be called exactly once within the condition.

Oh, right. You must retrieve the data to reduce the Index. As much as efficiency is great and all, it'll make it less user friendly, which I'm not that fond of.


Here is a draft:
JASS:
library ExecuteSoon initializer onInit

globals
    private timer Timer = CreateTimer()
    private trigger Trigger = CreateTrigger()
    private integer array Data
    private integer Index = 0
endglobals

private function Execute takes nothing returns nothing
    call TriggerEvaluate(Trigger)
    call TriggerClearConditions(Trigger)
endfunction

function ExecuteSoon takes conditionfunc condition, integer data returns nothing
    call TriggerAddCondition(Trigger, condition)
    set Data[Index] = data
    
    set Index = Index + 1
    call ResumeTimer(Timer)
endfunction

function GetExecuteSoonData takes nothing returns integer
    set Index = Index - 1
    return Data[Index]
endfunction

private function onInit takes nothing returns nothing
    call TimerStart(Timer, .0, false, function Execute)
endfunction

endlibrary



By the way, out of curiosity. How would the efficiency of the version below compare to the rest? I would guess slower than looping through an array of triggers, but who knows.
JASS:
library ExecuteSoon initializer onInit

globals
    private timer Timer = CreateTimer()
    private trigger Trigger = CreateTrigger()
    private integer array Data
    private integer Index = 0
endglobals

private function Execute takes nothing returns nothing
    call TriggerEvaluate(Trigger)
    call TriggerClearConditions(Trigger)
endfunction

private function DecreaseIndex takes nothing returns boolean
    set Index = Index - 1
    return false
endfunction

function ExecuteSoon takes conditionfunc condition, integer data returns nothing
    call TriggerAddCondition(Trigger, Condition(function DecreaseIndex))
    call TriggerAddCondition(Trigger, condition)
    set Data[Index] = data
    
    set Index = Index + 1
    call ResumeTimer(Timer)
endfunction

function GetExecuteSoonData takes nothing returns integer
    return Data[Index]
endfunction

private function onInit takes nothing returns nothing
    call TimerStart(Timer, .0, false, function Execute)
endfunction

endlibrary



About the timer being double started, I have no idea how that affects speed at all. I guess it's just a habit I have since it works to start it as well as resume it all in one line. Has it ever been benchmarked?
 

Jesus4Lyf

Good Idea™
Reaction score
397
>Oh, right. You must retrieve the data to reduce the Index. As much as efficiency is great and all, it'll make it less user friendly, which I'm not that fond of.
You could always do red/blue versions. I'd personally take the red thing every time for efficiency. You're nearly -always- going to want to "attach" data...

Dunno about the benchmarking of TimerStart versus ResumeTimer... Just thought it may be cool to try out.

>How would the efficiency of the version below compare to the rest?
Slower. I tried that for KT2, naturally. :(

By the way, your indexing currently doesn't work, because the order you retrieve the data is the REVERSE of the order you add it. Don't worry, I made that mistake while writing KT2, too. :)
You need to make the order the same, which really means two variables... One which will need to be reset before calling TriggerExecute. :p

Otherwise, nice progress! ^_^
 

Builder Bob

Live free or don't
Reaction score
249
You could always do red/blue versions. I'd personally take the red thing every time for efficiency. You're nearly -always- going to want to "attach" data...
Red and blue sounds good. I usually sacrifice a little speed unless in very frequently run code. For those situations, speed is preferable.

Slower. I tried that for KT2, naturally. :(
Right -- idea discarded.


By the way, your indexing currently doesn't work, because the order you retrieve the data is the REVERSE of the order you add it. Don't worry, I made that mistake while writing KT2, too. :)
You need to make the order the same, which really means two variables... One which will need to be reset before calling TriggerExecute. :p

Otherwise, nice progress! ^_^
So true. Didn't think of that.

Here's the next draft.

By the way, I came to think of a potential problem. If ExecuteSoon is called within one of the functions called when the timer has expired, the condition will be added, but cleared as the function Execute finishes. The original code I made also has a similar problem. Although I know how it can be fixed there.
JASS:
library ExecuteSoon initializer onInit

globals
    private timer Timer = CreateTimer()
    private trigger Trigger = CreateTrigger()
    private integer array Data
    private integer Index = 0
endglobals

private function Execute takes nothing returns nothing
    set Index = -1
    call TriggerEvaluate(Trigger)
    call TriggerClearConditions(Trigger)
    set Index = 0
endfunction

function ExecuteSoon takes conditionfunc condition, integer data returns nothing
    call TriggerAddCondition(Trigger, condition)
    set Data[Index] = data
    
    set Index = Index + 1
    call ResumeTimer(Timer)
endfunction

function GetExecuteSoonData takes nothing returns integer
    set Index = Index + 1
    return Data[Index]
endfunction

private function onInit takes nothing returns nothing
    call TimerStart(Timer, .0, false, function Execute)
endfunction

endlibrary
 

Jesus4Lyf

Good Idea™
Reaction score
397
VERY clever. I'm quite impressed. I was wondering if it could be done with only one variable. You've definitely done well. :)

>If ExecuteSoon is called within one of the functions called when the timer has expired, the condition will be added, but cleared as the function Execute finishes.
Actually, I believe you'll find it will even be RUN immediately, but the nasty thing is it will screw up the Index. Make it another con of the Red version, that you can't call it from within itself (which is highly unlikely to happen - probably useless - anyway).

In KT2, I made a list of things to be attached after the timer fires, so it then attaches things afterwards if they were added while the timer was firing. I don't think it's necessary to account for here, but that's just my opinion. I prefer the "blindingly fast" thing for this kind of system.

Be sure to test the code with an example before releasing it, naturally. Try two different funcs just displaying some message or something, make sure all the data attachment works correctly, etc. As I kinda said, I've never tried ResumeTimer in this kind of circumstance in the first place. :)

PS. Don't s'pose I score a credit/thank you note? ;)
 

Builder Bob

Live free or don't
Reaction score
249
VERY clever. I'm quite impressed. I was wondering if it could be done with only one variable. You've definitely done well. :)
Thanks

>If ExecuteSoon is called within one of the functions called when the timer has expired, the condition will be added, but cleared as the function Execute finishes.
Actually, I believe you'll find it will even be RUN immediately, but the nasty thing is it will screw up the Index. Make it another con of the Red version, that you can't call it from within itself (which is highly unlikely - probably useless - anyway).
I'll sort it out so it doesn't screw up the index at the very least. This will require two variables as you first suggested. Possibly that is why you used two in the first place?

In KT2, I made a list of things to be attached after the timer fires, so it then attaches things afterwards if they were added while the timer was firing. I don't think it's necessary to account for here, but that's just my opinion. I prefer the "blindingly fast" thing for this kind of system.
The red flavor is only for speed anyway, so that would be kind of redundant. I'll be sure the blue version can handle it though. Personally I will choose to use blue.

Be sure to test the code with an example before releasing it, naturally. Try two different funcs just displaying some message or something, make sure all the data attachment works correctly, etc. As I kinda said, I've never tried ResumeTimer in this kind of circumstance in the first place. :)
I'll run it for every situation I can think of.

PS. Don't s'pose I score a credit/thank you note? ;)
Of course! I like the good suggestions you've given for the red flavor :)
 

Romek

Super Moderator
Reaction score
963
Could you make the function take a boolexpr instead of a conditionfunc?

Nothing major, but allows people to use Filter(code) as well.
 

Jesus4Lyf

Good Idea™
Reaction score
397
Cool.

>I'll sort it out so it doesn't screw up the index at the very least.
Y'know, I actually wouldn't bother? There's no way to realistically protect against calling GetData the wrong number of times, so I'd just leave this there too. After all, Red is going to be all out speed, and a case of read the cons yourself. :p

But that's just how I'd like red, which would be my preference. And of course, I'd appreciate it if it was max speed, but it is kind of up to you. :p

>Could you make the function take a boolexpr instead of a conditionfunc?
I was going to mention that too. XD
All you have to do is rename conditionfunc to boolexpr. I'm rather sure. :)
 

Builder Bob

Live free or don't
Reaction score
249
Could you make the function take a boolexpr instead of a conditionfunc?

Nothing major, but allows people to use Filter(code) as well.

Of course. I didn't know there was a difference.

I've posted the current incarnations for red and blue flavor in the first post. I went with linked lists in the blue one to make sure functions were fired in the correct order.

They check out in testing so far, but let me know what I've missed. There usually is something.

>I'll sort it out so it doesn't screw up the index at the very least.
Y'know, I actually wouldn't bother? There's no way to realistically protect against calling GetData the wrong number of times, so I'd just leave this there too. After all, Red is going to be all out speed, and a case of read the cons yourself. :p

But that's just how I'd like red, which would be my preference. And of course, I'd appreciate it if it was max speed, but it is kind of up to you. :p
It won't go any slower. It's the exact same amount of operations, just with one more variable instead. Anyone who cares to change it will know how anyway ;)


Would QueueFunction() be a more suitable function name by the way? It's like you call for a function, but put it at the end of the queue.
 

Romek

Super Moderator
Reaction score
963
...And why can't anyone think of something more original than "Blue Flavour" and "Red Flavour".
You're not Vexorian. Honestly. :)
 

Azlier

Old World Ghost
Reaction score
461
Red and Blue are common, natural colors. Flavors Chocolate and Rainbow are original, but do you really want those?
 

Builder Bob

Live free or don't
Reaction score
249
...And why can't anyone think of something more original than "Blue Flavour" and "Red Flavour".
You're not Vexorian. Honestly. :)

The color flavors was a request, and I think red vs blue fit well with fast vs safe as well. Didn't try to be original, nor Vexorian. I'm just going with something already known and easily comprehensible.
 

Troll-Brain

You can change this now in User CP.
Reaction score
85
Here is an alternative way, which should be more efficient in a real world scenario, but i could be wrong, and i reckon it will fail if you don't use ExecuteSoon_GetData exactly one time.
You have to choice an enough high number of timers, or it will fail.

JASS:
library ExecuteSoon initializer init

globals
    private constant integer MAX_TIMERS = 64
endglobals

globals
    private timer array Tim
    private integer I = 0
endglobals

function ExecuteSoon takes code func, integer data returns nothing
    set I = I-1
    call TimerStart(Tim<i>,-data,false,func)
endfunction

public function GetData takes nothing returns integer
    set I = I+1
    return R2I(-TimerGetTimeout(GetExpiredTimer()))
endfunction

private function init takes nothing returns nothing
    
    loop
    exitwhen I == MAX_TIMERS
    
        set Tim<i> = CreateTimer()
        set I = I+1
    
    endloop
endfunction

endlibrary</i></i>


Example :

JASS:
scope Test initializer init

private struct s_data
    unit u

    static method create takes nothing returns thistype
        local thistype this = .allocate()
        set .u = GetOrderedUnit()
        return this
    endmethod
    
    method onDestroy takes nothing returns nothing
        set .u = null
    endmethod
    
endstruct



private function StopPointOrders takes nothing returns nothing
    local s_data s = ExecuteSoon_GetData()
    call IssueImmediateOrder(s.u,&quot;stop&quot;)
    call s.destroy()
    
endfunction

private function Actions takes nothing returns nothing
    local s_data s = s_data.create()
    call ExecuteSoon(function StopPointOrders,s)
endfunction


private function init takes nothing returns nothing
    local trigger trig = CreateTrigger()
    
    call TriggerRegisterAnyUnitEventBJ(trig,EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER)
    call TriggerAddAction(trig,function Actions)
endfunction

endscope
 

Jesus4Lyf

Good Idea™
Reaction score
397
Have you seen his Red flavour? I wrote it with him, it's complete hax. You won't get faster than that - it uses KT2's principle.

Is this useful enough to approve? Hm. I think it would be if people would use it in systems that require it... But how often is it needed? Just things like Damage? And then you can code it better because you don't need to pass the callback in...
 

Troll-Brain

You can change this now in User CP.
Reaction score
85
Have you seen his Red flavour? I wrote it with him, it's complete hax. You won't get faster than that - it uses KT2's principle.
Still waiting for a proof.Actually i believe this would be faster, but as i say i can be wrong.

Is this useful enough to approve? Hm. I think it would be if people would use it in systems that require it... But how often is it needed? Just things like Damage?
I'm just posting an alternative way i perfectly know that's not the code will save your life :)
Also i don't like Damage since you must use struct arrays (because of AIDS).Anyway it's far off-topic.

And then you can code it better because you don't need to pass the callback in...
I think you are lost.
 

Jesus4Lyf

Good Idea™
Reaction score
397
I was talking about the main system there.

And in regards to not needing to pass the callback... in a real situation a system like this that attaches to a trigger isn't needed because you can hard code the callback - the right way to do it. So I was saying this will probably get graveyarded anyway, unless I hear some reason for it existing...

Edit: Just understood how your awesome code works.
JASS:
public function GetData takes nothing returns integer
    return R2I(-TimerGetTimeout(GetExpiredTimer()))
endfunction

public function End takes nothing returns nothing
    set Tim<i>=GetExpiredTimer()
    set I = I+1
endfunction</i>

-->
JASS:
public function GetData takes nothing returns integer
    set I = I+1 // Zomg!
    return R2I(-TimerGetTimeout(GetExpiredTimer()))
endfunction

I still think the other would be faster, after a native count. TriggerAddCondition and ResumeTimer vs TimerStart, GetExpiredTimer and TimerGetTimeout (and maybe R2I). :p
 

Troll-Brain

You can change this now in User CP.
Reaction score
85
And in regards to not needing to pass the callback... in a real situation a system like this that attaches to a trigger isn't needed because you can hard code the callback - the right way to do it. So I was saying this will probably get graveyarded anyway, unless I hear some reason for it existing...
I still don't understand what you want mean, could you give an example plz ?

I still think the other would be faster, after a native count. TriggerAddCondition and ResumeTimer vs TimerStart, GetExpiredTimer and TimerGetTimeout (and maybe R2I).
You didn't mention TriggerEvaluate and TriggerClearConditions, the calling of stuct creating, and many set of variables, i hardly think that would be faster.

JASS:
public function GetData takes nothing returns integer
    set I = I+1 // Zomg!
    return R2I(-TimerGetTimeout(GetExpiredTimer()))
endfunction


Hmm yes you're right i can do all the stuff in the GetData function, good idea.
 
General chit-chat
Help Users
  • No one is chatting at the moment.

      The Helper Discord

      Members online

      No members online now.

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top