Triggering an event immediately

Do you have questions about writing plugins or scripts in Python? Meet the coders here.
Post Reply
jprovan
Posts: 5
Joined: Mon Jan 04, 2010 5:02 pm

Triggering an event immediately

Post by jprovan » Mon Jan 04, 2010 5:05 pm

I am new to EventGhost (and python), but not a new programmer.

I would like to trigger an event similar in fashion to calling a subroutine. However, when I trigger the event, my code continues executing and the event is triggered after my code is done.

Is there a way to trigger an event and wait for it to complete ?

User avatar
jinxdone
Plugin Developer
Posts: 443
Joined: Tue Jan 02, 2007 4:08 pm

Re: Triggering an event immediately

Post by jinxdone » Sun Jan 17, 2010 12:23 pm

I was tinkering a bit with this problem and tried using python generators to work around it. A generator function is handy since you can pause the execution and resume it later. All that is needed is to resume the execution with a callback after the TriggerEvent has finished.

For testing purposes I modified EG a bit, I added a parameter "callback" to eg.TriggerEvent and then pass the value of that to the EventGhostEvent class. The value of the callback is set None if it's not specified.

So now, at the end of def Execute(self): in EventGhostEvent.py:

Code: Select all

...
        self.SetStarted()
        eg.SetProcessingState(1, self)
        if self.callback != None and self.shouldEnd.isSet():
            self.callback()
Then, I have this in my python script, which works quite nicely:

Code: Select all

class GeneratorScript:
    def __init__(self):
        self.generator = self.Script()

    def __call__(self):
        self.generator.next()
    
    def CallEvent(self, event):
        eg.TriggerEvent(event, callback=self)
        
class testscript(GeneratorScript):
    def Script(self):
        print "Hello World!"
        print "Event string: %s, Event Payload: %s" % (eg.event.string, eg.event.payload)
        # Call event 'test1', and resume execution of this script after it calls our generator.next()
        self.CallEvent('test1')
        # Yield stops running the script for now, but the state is saved. It can be resumed with .next()
        yield

        # When we get here the 'test1' event has been finished.   
        print "Event string: %s, Event Payload: %s" % (eg.event.string, eg.event.payload)
        print "Again!"
        yield

myscript = testscript()
myscript()
So currently I have the event's callback calling a generator's next() routine, which is actually the python script that I wanted to pause. This is pretty ugly, but it works. I'm not familiar with stackless python which supposedly makes writing threads much easier, so probably there is some much neater way of doing this.

It's a pretty big issue and I for one would really like to see the 'callable event' done properly at some point. I think it's up to BitMonster to create a good solution for this. I'm sure it has to be done with some kind of callbacks though.

-jinxdone

stottle
Plugin Developer
Posts: 636
Joined: Sun Apr 26, 2009 10:59 pm

Re: Triggering an event immediately

Post by stottle » Sun Jan 17, 2010 3:51 pm

I think that both of you have specific examples in mind ("Use cases"), would you mind listing a couple?

I can't speak for Bitmonster, but it sure looks like the design of the EventThread and ActionThread was to prevent this type of thing. Both are threads that act as Queues, so the items get buffered until they can run, first-in/first-out. Several problems come to mind when you try to get around this.
1) What happens if other events are in the queue between the initial trigger and the new event you are waiting for? If you create a new event, these other events will run first since the new event will be pushed to the end of the line. Is this desired/expected (again, it goes to use case)?
2) What if the event has multiple actions associated with it? It looks like the jinxdone's code will "pause" one macro, I would expect any other macros for the same action would then run (since other events in the queue don't run until all actions associated with an event are run). Then any other events in the queue, then the new event which would then complete the original action. Again, is this expected/desired?
3) There are definite threading issues, as you can't have a thread pause (the eventThread) waiting for an event to complete, when the completion depends on something else running in the paused thread.

My comments aren't meant to discourage, just point out some of the issues. And depending on the use case, I think there are potentially good workarounds.

First question, do you really need to trigger a new event? If you already know what new actions to run, can't you run them directly in the call (where you can easily avoid the threading problems and wait for the result)?

If you need to run a new event, can you create two events ("NewEvent" and "NewEventCompletion") and run any follow-up/clean-up code from the "NewEventCompletion" event?

There's also an option to add a jumpto in a macro, that runs another macro serially, without creating a new event.

I don't think there's any way to have a new event "jump to the front of the line" so to speak, so any option that creates a new event will be run after any intervening events run.

Hope this helps,
Brett

User avatar
Bitmonster
Site Admin
Posts: 2239
Joined: Mon Feb 06, 2006 10:28 pm

Re: Triggering an event immediately

Post by Bitmonster » Sun Jan 17, 2010 9:40 pm

I guess jprovan now thinks "what the hell are they talking about here?".

To make it short:
If you want to execute some actions in-between a macro, the "Jump" action can do it easily. But this action can't be used inside a python script, so you have to first end your python script, use a jump action and after that use another python script to complete your job. The jump action can be controlled by the eg.result variable, so your macro can look like this:

- My Super Macro
-- PythonScript
-- If successful jump to "My Submacro" and return
-- PythonScript

Now to the jinxdone idea:
I also don't see the use case currently. And as stottle rightly said, your solution will have many issues, that easily produce a crash. And using threading or generators are much beyond the average skill level of the users.

I think the main problem you want to workaround is that a PythonScript can't use a EventGhost.JumpIf() action (and also all actions that change the "enable" state) directly. That's because JumpIf finds the target through an ID. It has to do so, because the user might move/rename the target macro in the tree, so a direct path to the target won't work. And since EG can't see that you reference the particular target in your PythonScript, it can't take care to ensure this target ID will stay alive.

There are two solutions I can think of currently:
1. Ensure that every item ID will stay forever as long as the item is part of the tree. For example by using a GUID for every item. But this will produce a quite heavily XML representation of the tree. Just isn't that readable as currently. And it will not prevent the user from deleting the target.
2. Add a "targets list" configuration to the PythonScript actions that give a you a Python name for a target. So you first have to choose explicitly what targets your script wants to interact with. This way EG can see which targets you reference and keep them alive and prevent deletion of them by the user. But here the user might forget to delete the targets from the list if he no longer uses them.
(There is a third solution that came to my mind lately:
3. Parsing the AST representation of the PythonScript source code, to find all targets the source refers to. I guess such source code analyses might be possible, but it's really hard to code something like this. But maybe sometime I will experiment with this.)

So none of them is perfect and therefore I have retained it the way it is currently.
Please post software-related questions in the forum - PMs will only be answered, if really private, thanks!

Post Reply