• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.
  • It's time for the first HD Modeling Contest of 2025. Join the theme discussion for Hive's HD Modeling Contest #7! Click here to post your idea!

TriggerSleepAction inside TriggerEvaluate/TriggerExecute

Status
Not open for further replies.
Level 2
Joined
Feb 15, 2016
Messages
9
Let me preface here first what I'm trying to achieve.

I am making an advanced command system for a map of mine, and I use vJass's .evaluate() feature to run command callbacks (which in turn use function intefaces).

It looks something like this:
JASS:
callbacks[commands[StringHash(command)]].evaluate(runner, as);
I make it possible to chain commands in a single chat message, something like the following:
Code:
cmd1 arg1 arg2 arg3 | cmd2 arg1 | cmd3 arg1 arg2
I'm trying to add a simple "wait" command, which will put the current command execution on hold, but there are some issues that spring to my mind.

First off, as far as I know, TriggerExecute starts a separate thread and yields when the called function either stops, returns, or calls TriggerSleepAction. So if I call TriggerSleepAction within a TriggerExecuteFunc, the next command will run instantly, because the calling thread resumes immediately, despite the command callback not having finished.
I read somewhere that TriggerEvaluate blocks the calling thread until the underlying function returns, on the other hand, but I've also read that TriggerEvaluate doesn't play nice with TriggerSleepAction.

Can someone more experienced than me shed some light on this, please? I haven't tested this thoroughly, but I'm asking since there may be a lot of subtle caveats (like in so many other places in JASS) that I might miss and be unaware of until they become an issue.

Some other, slightly unrelated questions, while I'm on the topic:
What is the real difference between TriggerEvaluate and TriggerExecute, in terms of thread starting/thread blockage, if we put aside the various performance concerns (this isn't used in a performance-heavy context, so it doesn't really matter)?
I've also stumbled across an odd native, namely TriggerExecuteWait. Does anyone know how it is different from TriggerExecute? It seems that it has something to do with TriggerSleepAction, but it's not clear to me what exactly.

P.S. I'll probably just add a dirty workaround to make "wait" work like it should, but I was wondering if it is possible to block the calling thread from within a TriggerEvaluate/TriggerExecute using TriggerSleepAction.
 
Level 2
Joined
Feb 15, 2016
Messages
9
Well, I decided to go ahead and do a few simple tests, but I'm still quite unsure.

Here are the results:
1. Calling TriggerSleepAction inside TriggerEvaluate kills the thread and it never resumes.
2. Calling TriggerSleepAction inside TriggerExecute returns control to the calling thread.
3. Calling TriggerSleepAction inside TriggerExecuteWait does the exact same thing as TriggerExecute.
4. Calling a simple function blocks the calling thread until that function returns. Nothing too surprising here.

Seems it really is impossible to block a calling thread from TriggerEvaluate/TriggerExecute.

P.S. My tests are by no means extensive and conclusive, and I might have missed something.
 
Level 2
Joined
Feb 15, 2016
Messages
9
Suppose we had the following command chain:

foo | wait | bar

what do you want to happen?

The "wait" command should have an argument.
So in an example like
Code:
foo | wait 1 | bar
"foo" would run first, then a second would elapse, and then "bar" would run.

The issue isn't in implenting this behaviour, it already works in my prototype. I was just asking about using TriggerSleepAction within TriggerExecute/TriggerEvaluate and how it reacts with the calling thread.

this may help https://en.wikipedia.org/wiki/Chain-of-responsibility_pattern (sorry for the tiny answer but i'm at work, as far as i can see this should work).

Sorry, but how is this related at all? I was asking about specific JASS behaviour in regards to TriggerExecute/TriggerEvaluate, considering how so many things in JASS have a lot of subtlties to them (along with bugs).

EDIT: I'm sorry if I haven't made this clear, but the question was specifically about sleeps inside TriggerExecute/TriggerEvaluate, and not how to implement the aforementioned system.
 
Last edited:
2. Calling TriggerSleepAction inside TriggerExecute returns control to the calling thread.

Really? That is interesting. What happens if you make a trigger with the event "a unit dies" and put a wait as an action? Kill the unit in the calling thread--does it return control to the calling thread immediately or does it wait first? (sorry, I can't test at the moment)

Some notes about TriggerEvaluate() and TriggerExecute(), iirc:
- They both start a "new thread" to run all conditions (evaluate) or all actions (execute). By "new thread", I mean it resets the op-limit before calling.
- TriggerSleepAction() may have been designed specifically for triggeraction functions, hence its name. It might also work in ExecuteFunc(), but it may depend on where the calling thread is from (I don't remember). Anyway, that may play into why TriggerEvaluate() crashes whereas TriggerExecute() doesn't.

There aren't many other notable features.

As for TriggerExecuteWait:
http://www.wc3c.net/showpost.php?p=553592&postcount=11
 
Level 2
Joined
Feb 15, 2016
Messages
9
Really? That is interesting. What happens if you make a trigger with the event "a unit dies" and put a wait as an action? Kill the unit in the calling thread--does it return control to the calling thread immediately or does it wait first? (sorry, I can't test at the moment)

Some notes about TriggerEvaluate() and TriggerExecute(), iirc:
- They both start a "new thread" to run all conditions (evaluate) or all actions (execute). By "new thread", I mean it resets the op-limit before calling.
- TriggerSleepAction() may have been designed specifically for triggeraction functions, hence its name. It might also work in ExecuteFunc(), but it may depend on where the calling thread is from (I don't remember). Anyway, that may play into why TriggerEvaluate() crashes whereas TriggerExecute() doesn't.

There aren't many other notable features.

As for TriggerExecuteWait:
http://www.wc3c.net/showpost.php?p=553592&postcount=11

Here's the code I used for testing:
JASS:
// Zinc code
library Test {
    function Message(string s) {
        DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 6000, s);
    }

    function testEvaluate() -> boolean {
        Message("testEvaluate started.");
        TriggerSleepAction(2);
        Message("testEvaluated has waited 2 seconds.");
        
        return false;
    }
    
    function testExecute() {
        Message("testExecute started.");
        TriggerSleepAction(2);
        Message("testExecute has waited 2 seconds.");
    }
    
    function testExecuteWait() {
        Message("testExecuteWait started.");
        TriggerSleepAction(2);
        Message("testExecuteWait has waited 2 seconds.");
    }
    
    function testCall() {
        Message("testCall started.");
        TriggerSleepAction(3);
        Message("testCall has waited 3 seconds.");
    }

    function onInit() {
        trigger t1 = CreateTrigger();
        trigger t2 = CreateTrigger();
        trigger t3 = CreateTrigger();
        
        TriggerAddCondition(t1, Condition(function testEvaluate));
        TriggerAddAction(t2, function testExecute);
        TriggerAddAction(t3, function testExecuteWait);
        
        TriggerEvaluate(t1);
        TriggerExecute(t2);
        TriggerExecuteWait(t3);
        testCall();
        Message("Finished running init.");
    }
}

And here's the output from running it:
Code:
testEvaluate started.
testExecute started.
testExecuteWait started.
testCall started.
-- 2 seconds elapse here --
testExecuteWait has waited 2 seconds.
testExecute has waited 2 seconds.
-- 1 more second elapses here --
testCall has waited 3 seconds.
Finished running init.

As you can see, all three functions yield right after TriggerSleepAction, and the calling thread continues to run. The direct call causes the thread to wait, just as expected. The function in TriggerEvaluate never resumes. Everything else works as expected.

Now, I've tested the situation that you described, and the outcome was somewhat surprising to me, but I guess it makes sense. Here's the result.

This is the code I used for testing:
JASS:
library Test {
    function Message(string s) {
        DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60000, s);
    }

    function onUnitDies() {
        Message("Event - A unit has died.");
        TriggerSleepAction(1);
        Message("Event - We have waited 1 second after the unit died.");
    }
    
    function testCase1() {
        unit u = CreateUnit(Player(0), 'hfoo', 0, 0, 0);
        Message("Main - Test case 1.");
        Message("Main - A unit has been created.");
        
        KillUnit(u);
        Message("Main - A unit has been killed.");
        TriggerSleepAction(0.5);
        Message("Main - We have waited 0.5 seconds after killing a unit.");
        
        u = null;
    }
    
    function testCase2() {
        unit u = CreateUnit(Player(0), 'hfoo', 0, 0, 0);
        Message("Main - Test case 2.");
        Message("Main - A unit has been created.");
        
        KillUnit(u);
        Message("Main - A unit has been killed.");
        TriggerSleepAction(3);
        Message("Main - We have waited 3 seconds after killing a unit.");
        
        u = null;
    }
    
    function onInit() {
        trigger onDeath = CreateTrigger();
        TriggerRegisterPlayerUnitEvent(onDeath, Player(0), EVENT_PLAYER_UNIT_DEATH, null);
        TriggerAddAction(onDeath, function onUnitDies);
        
        TriggerSleepAction(5);
        testCase1();
        TriggerSleepAction(1);
        testCase2();
    }
}

And here's the output:
kuR1dqm.png


So, what seems to be happening is:
The main thread starts and creates a unit.
The main thread kills the unit, and control is shifted to the event callback immediately. When the callback waits, it returns control to the main thread immediately and waits. This is demonstrated by the tests pretty well.

So it seems that the control is returned to the calling thread as soon as the callback yields (via return or sleep).

P.S. I really didn't know that events fired instantly after the action. I thought the main thread would continue to run until it yielded (return/sleep), but KillUnit shifted control to the event callback instantly, and only then resumed the main thread.

EDIT: I did a quick comparison between TriggerExecute and ExecuteFunc, and they both appear to behave identically - instantly returning control to the main thread upon yielding.
 
The best way to do what you are trying to do might look like this:

- Trigger 1 executes trigger 2
- Trigger 2 issues a Wait. Before the TriggerSleepAction line, the wait's duration is set to global variable "waitTime"
- Trigger 1 assesses if waitTime is above 0. If it's more, wait for that duration.

Now, the test would be if a TSA in one thread followed by a TSA in another thread for the same duration will expire at the same time (since we know waits are innaccurate) and that Trigger 2 will complete its sleep before Trigger 1.

A loop would be used by Trigger 1 if multiple consecutive waits need to be used from Trigger 2:

JASS:
//trigger 1's loop:
local real time
call TriggerExecute(trigger2)
loop
    exitwhen waitTime == 0.00
    set time = waitTime
    set waitTime = 0.00
    call TriggerSleepAction(time)
endloop

//trigger 2's behaviour:
set waitTime = 2.00
call TriggerSleepAction(waitTime)
set waitTime = 3.00
call TriggerSleepAction(waitTime)
 
Level 2
Joined
Feb 15, 2016
Messages
9
The best way to do what you are trying to do might look like this:

- Trigger 1 executes trigger 2
- Trigger 2 issues a Wait. Before the TriggerSleepAction line, the wait's duration is set to global variable "waitTime"
- Trigger 1 assesses if waitTime is above 0. If it's more, wait for that duration.

Now, the test would be if a TSA in one thread followed by a TSA in another thread for the same duration will expire at the same time (since we know waits are innaccurate) and that Trigger 2 will complete its sleep before Trigger 1.

A loop would be used by Trigger 1 if multiple consecutive waits need to be used from Trigger 2:

JASS:
//trigger 1's loop:
local real time
call TriggerExecute(trigger2)
loop
    exitwhen waitTime == 0.00
    set time = waitTime
    set waitTime = 0.00
    call TriggerSleepAction(time)
endloop

//trigger 2's behaviour:
set waitTime = 2.00
call TriggerSleepAction(waitTime)
set waitTime = 3.00
call TriggerSleepAction(waitTime)

Yes, this is nearly identical to how I do it right now.
Like I said:
The issue isn't in implenting this behaviour, it already works in my prototype. I was just asking about using TriggerSleepAction within TriggerExecute/TriggerEvaluate and how it reacts with the calling thread.

I started this thread to talk about the behaviour of TriggerExecute vs TriggerEvaluate and their interactions with TriggerSleepAction, not to ask directions on how to implement my system.

EDIT: In conclusion, it seems there's no way to halt the calling thread besides issuing a separate TriggerSleepAction for it. This is exactly how I do it right now.
 
Last edited:
Level 13
Joined
Nov 7, 2014
Messages
571
I started this thread to talk about the behaviour of TriggerExecute vs TriggerEvaluate and their interactions with TriggerSleepAction, not to ask directions on how to implement my system.

Sure, but:

The best way to do what you are trying to do might look like this: ...

Yes, this is nearly identical to how I do it right now.

Why??? It seems to me that the following chain

foo | wait 1 | bar

can be execute like so:

call/run foo, then notice that the second command is a wait, start a timer for the wait's duration, then call/run next command (bar), etc.

I.e there isn't any point of using TSA (which is inaccurate).
 
Level 2
Joined
Feb 15, 2016
Messages
9
Sure, but:





Why??? It seems to me that the following chain

foo | wait 1 | bar

can be execute like so:

call/run foo, then notice that the second command is a wait, start a timer for the wait's duration, then call/run next command (bar), etc.

I.e there isn't any point of using TSA (which is inaccurate).

Because TSA is more convenient than having to track loads of different timers, and I don't need accuracy in this.
Anyway, this shouldn't concern you.
 
Level 6
Joined
Jul 30, 2013
Messages
282
hmm..
maybe just push all the commands in to a queue and have some periodic trigger consume it. and in case of wait just have it do nothing (peek at the queue before poping)

im not sure if its perfect but its bound to be at least 2 orders of magnitude more elegant than trying to emulate the broken TSA
 
Status
Not open for further replies.
Top