Non-dispatching wait and event handling

Moderators: FourthWorld, heatherlaine, Klaus, kevinmiller, LCMark

Locked
LCMark
Livecode Staff Member
Livecode Staff Member
Posts: 1208
Joined: Thu Apr 11, 2013 11:27 am

Non-dispatching wait and event handling

Post by LCMark » Fri Jan 10, 2014 5:28 pm

The last couple of days I've been trying to sort out event handling in the Cocoa port of the engine - this has brought to foreground an issue which has been lurking around for quite a while: the fact the engine allows script to do 'wait without messages' (blocking-wait):

Typical uses of blocking-wait are things like:

Code: Select all

  on mouseDown
    wait until the mouse is up
    ... do stuff ...
  end mouseDown

  on myAnimation
    ... run first part of animation ...
    ... play sound ...
    wait until the sound is done
    ... further animations and sound playing ...
  end myAnimation
Then, indirectly, with things like:

Code: Select all

 
  on executeShellFunction
    get shell("... execute process that takes quite a while to finish ...")
  end executeShellFunction
All of these are valid uses, and indeed because you can do things like this it makes LiveCode coding easier - in other environments you have to do such things in a 'threaded' type of way:

Code: Select all

  on myAnimation
    ... run first part ...
    ... play sound ...
    send "myAnimation2" to me in ... sound length seconds ...
  end myAnimation

  on myAnimation2
    ... run second part ...
    ... etc. ...
  end myAnimation2
The problem here is that LiveCode is hiding a dirty secret (which is partly why it makes some things easier to code!) with its current semantics of 'wait' - one which you are forced to deal explicitly with in other environments: when 'wait (without messages)' is invoked (whether internally as a consequence of something like shell(), or from script directly) the engine still processes system events and queues them, dispatching them at the next point message dispatch can occur. Critically, the engine and its current functionality is designed so that it will not do anything that could cause script to be invoked whilst a 'wait' (without messages) is in effect - which is one of the reasons this style of coding is 'easy'.

In other environments, you don't have a nice simple 'wait' primitive like this - you have to ensure when you are doing something like 'myAnimation' above or calling a shell command (which would most likely be done as a polling mechanism - like using open process instead of shell), that your app is in a state in which the user can't do things that might cause problems (like turning off interaction events and such), and your code has to be designed to prevent re-entrancy into what is going on (for example, if myAnimation is called as a result of a button click then in the 'threaded' variant you'd end up with two concurrent invocations of the animation which would likely cause a mess).

[ I should point out here that 'wait with messages' does not cause a problem - you have to script around getting messages you can't handle at the time of the 'wait with messages' so you are in the same boat as other environments, except that you don't have to thread your handlers which makes them easier to write and maintain ].

However, the problem is that the world has not evolved to really work in a 'blocking-wait' kind of way - having to support wait-without-messages (at least in the full generality it currently works in) means that some things cannot be implemented or leveraged in LiveCode because they do not work in a way which can be deferred in the way that is needed. (By deferred here I mean that many things want a response right now - they don't go "I need this information, please reply to me when you're ready and I'll wait for you" which is what the engine needs for blocking-wait).

There's one very good example of a feature which cannot be currently utilized in the iOS engine - filtering loads into the WebView control. When a web page is about to be loaded into a UIWebView, the webView:shouldStartLoadWithRequest: delegate method is invoked. This method requires there and then a YES or NO answer as to whether to proceed. Unfortunately, though, the LiveCode UIWebView control delegate cannot give the answer at that time because the method might have been invoked whilst a blocking-wait is in effect - instead it attempts to defer the choice, which ends up meaning things don't work right in some cases (particularly if frames are involved). The reason this can happen is that the iOS engine can only defer interaction events for the views it owns - there is not enough control (at the API level) of the eventqueue to only process certain events *and* iOS does not like you trying to work around this, it wants to be the sole manager of the eventqueue (if you try and do it yourself in a modal loop, many things stop working - this is why iOS is twin-thread, so things can always fallback to the root event loop).

(Another even better example is the hoops the engine goes through on iOS and Android with orientation changes...)

Now, given that you'd think most actions that cause such delegate methods to be fired are tied to user-interaction, you might think that if you can control the eventqueue (which you can seem to be able to do to a better degree in Cocoa than iOS) then things would be fine... However, my recent pokings around Cocoa caused me to develop a simple (WebView-based) sample where things are still not fine. On the Mac Desktop, there is a similar thing to UIWebView - WebView - which is a great deal richer in functionality. For example, you can attach a UI delegate that allows the host to manage JavaScript popup windows. However, this is again an example where 'blocking-wait' kills off being able to leverage this facility in the general way you might like. The WebUIDelegate has a method which will be called whenever the 'confirm()' JavaScript command is invoked. This method expects the implementor to return there and then whether the user clicked 'Yes' or 'No'. However, if when invoked a 'blocking-wait' is running the engine cannot send a message and so script cannot be run as a result. Given that JavaScript has timeouts which you can set, a web-page can quite happily ask for a 'confirm()' dialog at any point - unrelated to user input - and it easy to engineer a situation where such a request comes in during a blocking-wait (the example I engineered in the cocoa port I've been working on is this - HTML button with setTimeout of 3 seconds, invoking 'confirm()' when it fires; LiveCode button with 'wait for 5 seconds' - click HTML button then LiveCode button immediately afterwards, request for confirm dialog comes in during blocking wait).

So, in a nut-shell, in the modern world we have the problem that things can happen during a lifetime of an application which need an immediate response, but due to having to support 'blocking-wait' in the way we do, we cannot execute script to get that response. This in turn means many useful features we could leverage are out of reach unless we do something about the semantics of all blocking-wait operations.

I think the problem we face is this - at the moment blocking-wait actions currently give you a very firm guarantee: whilst the blocking-wait is in effect no script will run at all - i.e. you can be absolutely sure that your script state is the same before the wait as it is afterwards and that no part of your app will have run in the interim period. (To my mind this is a pretty hefty guarantee and I'm sure we've all written scripts that rely on it even without realizing.) Of course, it would be nice if we could just declare 'wait never blocks messages any more', however the chaos and breakage of code that would ensure I think would be significant...

At the moment I'm not entirely sure what the best solution is - as far as I can see, we certainly need to ease the semantics of blocking-wait, or restrict its domain of blocking-ness, or *insert some other suggestion to do with it* if we want LiveCode to be as well integrated with other components and libraries (such as native OS controls) as other environments allow. Indeed, it is even a bar to porting LiveCode to platforms - thus far I've managed to figure out how to make the engine work with the platforms we currently support, but both iOS and Android have significant wrinkles as a result and Cocoa is certainly seems to be skirting close to the same situation as those.

Therefore, with this post I hope to open up a discussion about it - please do ask questions and suggest things, I'm hoping that in answering and analysing we'll gain greater understanding of how to move from a world with blocking-wait to world where we have something like blocking-wait which doesn't actually block (well, not entirely at least) - blocking-wait-lite if you will :).

monte
VIP Livecode Opensource Backer
VIP Livecode Opensource Backer
Posts: 1564
Joined: Fri Jan 13, 2012 1:47 am
Contact:

Re: Non-dispatching wait and event handling

Post by monte » Fri Jan 10, 2014 11:32 pm

Hmm... firstly the idea if a non-blocking shell call is very appealing. I have to go to significant trouble to run processes semi-asynchronously using open-process... really wish process and file operations had the with message style threading that sockets do. So being able to send in time and use callbacks sounds quite helpful.

Anyway... I personally find wait without messages to be a double edged sword unless you remember to call flush events at the end you can get into all sorts of trouble with impatient users.

Perhaps an analysis needs to be done about exactly what events really need to be queued during a blocking wait. Maybe just user interaction events like mouse, keys, resume/suspend etc? The list of events that are blocked could remain static while the platform can move forward.

I'd also propose the implementation of:

Code: Select all

wait with messages flushing <list of messages/message categories to flush>
LiveCode User Group on Facebook : http://FaceBook.com/groups/LiveCodeUsers/

LCMark
Livecode Staff Member
Livecode Staff Member
Posts: 1208
Joined: Thu Apr 11, 2013 11:27 am

Re: Non-dispatching wait and event handling

Post by LCMark » Tue Jan 28, 2014 7:57 pm

@monte: Thanks for the input - your suggestions are good ones, and I think you're right about the behavior of blocking wait. At the moment I've managed to get Cocoa to do what the engine currently does, so we've got more time to work out the details of the change.

monte
VIP Livecode Opensource Backer
VIP Livecode Opensource Backer
Posts: 1564
Joined: Fri Jan 13, 2012 1:47 am
Contact:

Re: Non-dispatching wait and event handling

Post by monte » Fri Feb 21, 2014 12:00 am

@runrevmark I posted to a feature request forum thread titled Threading and I think the comments tiesinto this discussion enough to draw your attention to the relevant parts:

There's certainly lots of tasks that are currently blocking that would be quite nice if they weren't so adding `with message` options to those commands would help reduce the need to run whole chunks of LiveCode scripts on separate threads.

Then there's actually running a block of LiveCode script on a thread. Perhaps something like:

Code: Select all

dispatch <commandName> to <objectReference> [with param1… n] [[in background] with message <callback>]
The callback is run on the main script thread. If in background isn't used then the command could be equivalent to send in -1 milliseconds but with nicer syntax and a completion callback.
LiveCode User Group on Facebook : http://FaceBook.com/groups/LiveCodeUsers/

BvG
VIP Livecode Opensource Backer
VIP Livecode Opensource Backer
Posts: 1236
Joined: Sat Apr 08, 2006 1:10 pm
Location: Zurich
Contact:

Re: Non-dispatching wait and event handling

Post by BvG » Mon Feb 24, 2014 8:45 pm

Well I read two questions here: First if the engine should be able to block stuff in the future, as it does now (considering that's hard to do in all of the current OSes and intervaces it's suppose to work with), and second if yes, how to do that without being unpredictably or running into weird threading problems.

I'd say it's worth to retain blocking behaviour. Not knowing (or aiming to learn) the underlying problem, I nonetheless would like to suggest some kind of queuing mechanism: If the engine is blocked for any reason, tasks that depend on immediate reaction are still getting handled behind the scenes. In the example of a webview, something similar to this:

1. Script waits for whatever to finish
2. webview update is finished, and wants to be handled
3. engine handles update, but only caches result (does not expose to runtime/scripting environment), and instead stores in some sort of queue (similar to how pending messages, but not exposed to scripting at all, instead it's hidden)
4. after block is resolved, cached event queue is resolved first come first serve

an approach like this allows the engine to block the script environment at any time, without appearing blocked to the OS or relevant threading-expecting interfaces. On the other side, it's probably a bitch to get to work correctly?

This not being my core knowledge space, I might have misunderstood the issue completely... sorry if that's the case. But maybe further explanation can help me to make a better fitting suggestion?
Various teststacks and stuff:
http://bjoernke.com

Chat with other RunRev developers:
chat.freenode.net:6666 #livecode

LCMark
Livecode Staff Member
Livecode Staff Member
Posts: 1208
Joined: Thu Apr 11, 2013 11:27 am

Re: Non-dispatching wait and event handling

Post by LCMark » Fri Mar 14, 2014 3:12 pm

@BvG: Thanks for your input. What you describe is precisely what the engine currently does on iOS (and Android) - however, the problem is that it only deals with requests from the operating system which are 'notifications' (things that don't require a response - a bit like doing a send in time message in LiveCode - you don't get the return value). There are numerous cases where the OS wants a response immediately, and this is where a problem starts to arise.

I do need to articulate the issue more clearly so we can work out a good path forward, however for now I've managed to get Cocoa to play nice with the current semantics the engine has so this project has been deferred slightly. I will return to it in due course, when Cocoa is out of the way :)

DarScott
Posts: 227
Joined: Fri Jul 28, 2006 12:23 am
Location: Albuquerque
Contact:

Re: Non-dispatching wait and event handling

Post by DarScott » Sun Apr 13, 2014 5:53 am

Having non-blocking ways of doing things is first. Any commands with implicit waiting that have no non-blocking variations or alternative methods can be enhanced.

Locked

Return to “Engine Contributors”