Sunday, March 14, 2010

But the waiting makes me curious

Be patient, is very good advice, but the waiting makes me curious.
It's a line from the song 'Very Good advice' written for the original Disney 'Alice in Wonderland' movie and recently remade by The Cure's Robert Smith for the 'Almost Alice' soundtrack. And I just couldn't think of a better introduction to this blog entry on the revTalk 'wait' command.

When your script is in a tight loop, screen updates my not happen until after the whole script is finished.
on mouseUp
repeat with i = 1 to 5000
put i into field "Output"
end repeat
end mouseUp

Rather than continuously updating the content of the field, the field may only get updated after the repeat loop. While that's good enough in this silly example, it's not suitable for keeping the user updated on the progress of a real-world data crunching routine.

You can give the engine some breathing room by inserting a call to the 'wait' command.
on mouseUp
repeat with i = 1 to 5000
put i into field "Output"
-- allow screen to redraw
wait 0 milliseconds
end repeat
end mouseUp

Now you'll see the content of the field refreshed regularly; but it also makes your loop much slower.
on mouseUp
constant kRefresh = 25
repeat with i = 1 to 5000
put i into field "Output"
if i mod kRefresh is 0 then
-- allow screen to redraw
wait 0 milliseconds
end if
end repeat
end mouseUp

By only redrawing the screen every 25th iteration, we can still refresh the screen with less impact on performance. Play with the 'kRefresh' constant value to find the right redraw/performance ratio.

But that's not all the 'wait' command can do: if used properly, you can give your users a way to 'cancel' out of a tight loop.
on mouseUp
constant kRefresh = 25
enable button "Cancel"
set the uCancel of me to false
repeat with i = 1 to 5000
put i into field "Output"
if i mod kRefresh is 0 then
-- allow screen to redraw and user to cancel
wait 0 milliseconds with messages
if the uCancel of me is true then
answer "Are you sure you want to cancel?" \
with "Yes" or "No"
if it is "Yes"
then exit repeat
else set the uCancel of me to false
end if
end if
end repeat
disable button "Cancel"
end mouseUp

Now we add a button "Cancel" and give it the following script:
on mouseUp
set the uCancel of button "TightLoop" to true
end mouseUp

And presto! Not only does the screen redraw, but we now allow the user to stop the time-consuming process in our tight loop. Of course you'll have to add your logic to 'revert' any changes you've made in the process, but the above provides you with a skeleton script for handling this sort of situation.

As with all good things in life, 'wait with messages' comes at a price: as the user can now click your button twice, or trigger other scripts, it is very important to disable those parts of your user interface that shouldn't be executed 'concurrently' - my favourite method is to switch to a 'limited' environment, disabling all controls, displaying a blended white overlay over them as well as the 'Cancel' button making it the only enabled control. After the loop, the stack returns to the 'unlimited' environment, hiding the overlay and enabling the controls.

Some of you may wonder what happens when a script issues 'wait 1 second with messages' and the user clicks on another button: the other button's script is executed first, and then the engine checks if the wait timeout has passed, and resumes the original script. This means that if your other button script takes 5 seconds to complete, your first button script will remain suspended - the revEngine is not multi-threaded. But with a little planning, you can provide a much better user experience.