|
Article
|
Difficulty
Rating
|
8
|
Using
the Send
Command
|
|
Using the "Send" Command to
Schedule Future Events and
Animation
You wouldn't
guess from the name, but the send
command it is actually one of
MetaCard's most important
features. This article details
using it to schedule events and
animations.
This is a
basic send command:
send "mouseUp" to me in 5 seconds
That statement
causes the message "mouseUp" to
be delivered to the same object
that sent it, 5 seconds from when
the command was executed. The
MetaCard engine is not occupied
during that period, and is
therefore free to do other
things. After 5 seconds have
elapsed, that message will be
delivered to the
object.
The advantages
of this approach are many. You
can cause messages to be sent at
any time in future. There is no
measurable performance decrease
in the interim between sending
and receiving the message.
MetaCard remains free to do other
tasks during that period. This
completely eliminates the need to
use messages such as "idle", a
common, complicated and processor
intensive kludge used in many
other tools.
Scheduling a Repeat
Loop
Setting
something to occur regularly is
easy. The following script
updates a field on screen to show
the time, once every ten
seconds.
|
on mouseUp
--this handler starts the timer
setClock
end mouseUp
on setClock
put the time into fld 1
send "setClock" to me in 10 seconds
end setClock
This example
would repeat forever, putting the
time into field 1 every 10
seconds.
We can easily
modify this example to be more
useful. We'll include a facility
to cancel the message, and also
make the time update much more
frequently, and include
seconds.
|
local lTimerID
on mouseUp
--this handler starts the timer
setClock
end mouseUp
on setClock
put the long time into fld 1 --long time also has seconds
send "setClock" to me in 600 milliSeconds --600 milliseconds or 0.6 seconds
--will be enough to keep the seconds on the clock current
put the result into lTimerID
--the above line stores the "ID" of the message just sent
end setClock
The result
contains a unique ID for the
message you have sent. You can
stop that message from being
delivered at any time by passing
that ID to the cancel
command:
cancel lTimerID
The
pendingMessages function contains
a list of all the messages
scheduled to be delivered in the
future. Messages are added to it
every time an event is scheduled,
and removed when the message is
delivered (or cancelled with the
cancel command).
Here is a line
from the
pendingMessages:
12,913382037.933333,setClock,button id 1003 of card id 1002
of stack "Stack 913381993"
The first item
is the ID of the message. You can
cancel the message using this ID
just in the same way you would
cancel it using the result
function to retrieve and store
the id.
cancel (item 1 of the pendingMessages)
The second
item contains the time the
message will be delivered at. The
third item contains the name of
the message being delivered, and
the fourth item contains the path
for the object that the message
will be delivered to.
The
pendingMessages function can be
used to check if a message has
already been scheduled. In the
above example of updating a
clock, a problem may arise if the
user presses the original button
used to trigger the script for a
second time. Two sets of the same
message will then start being
sent, and the clock will be
updated twice as frequently as
originally specified. Repeated
clicking of the button will
eventually cause messages to be
sent almost constantly, locking
up MetaCard. Inserting the
following line into the top of
the mouseUp message prevents this
problem:
if "setClock" is in the pendingMessages then exit mouseUp
This will
prevent the mouseUp handler from
executing if the cycle has
already been started. Of course,
advanced users reading this may
have noticed it is a few
milliSeconds more efficient to
set up a variable the first time
the handler is executed, and
check that instead of reading a
function such as the
pendingMessages.
Checking the
pendingMessages
is simpler and more English like,
so this kind of optimisation will
only matter if the statement is
being performed repeatedly in an
extremely time sensitive
script.
Be sure to
cancel any pending messages when
they are not going to be
required. It is not atypical to
want to cancel all messages when
a card or stack is closed. The
best technique for doing this is
to cancel each message using its
ID, from a list of variables you
created and updated whenever you
sent a message. This results in
accurate cancellation and won't
affect any messages still running
in other stacks.
However, there
is also a brute-force way of
cancelling all pending
messages:
|
on closeCard
repeat for each line l in the pendingMessages
cancel (item 1 of l)
end repeat
end closeCard
That repeat
loop is useful to remember. Run
it in the message box or keep it
handy in a button when you're
debugging any complex send based
script. You may want to use it in
an emergency to clear the
pendingMessages
when you get into something you
find you can't get out
of.
Doing animation with
"send"
An obvious use
for the send command is to do
animation. Carefully done,
animation can be virtually
asynchronous, meaning that
MetaCard can respond to events
while the animation is taking
place. For a smooth animation, it
is important that the individual
handlers executed by the send
command are as short as possible.
Where multiple things need to be
done, it is best split into
multiple handlers.
The reason for
this is simple: only one script
can be executing at a time. Send
is no exception. Like any other
command, when a send command is
running, other scripts cannot
run. In fact, a send command can
only be delivered if there is no
other handler running. Otherwise,
it will wait until the end of
whatever handler is running to be
delivered. Keeping all the
handlers that execute short, and
splitting longer scripts into
multiple handlers delivered by
send,
allows you to maintain user
interaction whilst updating the
screen.
Why is
MetaCard not truly
multi-threaded? A multi-threaded
engine would be capable of
running multiple scripts at once.
MetaCard does not support this,
because of the complexities of
creating and debugging a
multi-threaded application. The
send
command is provided as an
alternative, and if used well,
can achieve excellent results
without the hassle of keeping
track of multi-threaded script or
code. Note that the GIF animation
commands and the
move
command are exceptions to this:
they will both continue to run
when other scripts are running
(with a few exceptions such as
using the file access commands to
read in huge files).
It is a good
idea to use the
move
command in conjunction with
animated GIFs to do the bulk of
any animation wherever possible.
You may want to start and stop
GIF animations, start and stop
move commands, manually alter the
frame being shown in a GIF
animation, alter button icons in
a moving button, start and stop
sounds, show and hide objects,
scroll fields, or use various
other techniques or a combination
of all of these to produce a
presentation. All of these can be
controlled, while still allowing
user interaction, with a series
of carefully scheduled send
commands.
In this
example, we will cause a field to
scroll from start to finish. You
can use this effect on a locked
field without a scrollbar to
animate a typical "credits"
screen with rolling credits. You
could also set the hScroll
property of the field instead of
the vScroll property to scroll
horizontally, a technique used in
the MetaCard demo stack. Finally,
this script example can be
applied to other animation
related activities that must be
scheduled to occur smoothly and
reliably.
We'll start
with a basic script, just like in
the previous example.
|
local lCurrentFieldScroll, lCurrentEndValue
on mouseUp
put 0 into lcurrentFieldScroll --start with the field scrolled to 0
put 500 into lCurrentEndValue --the vScroll of the field when fully scrolled down
updateField
end mouseUp
on updateField
add 10 to lCurrentFieldScroll
if lCurrentFieldScroll > lCurrentEndValue then exit updateField
set the vScroll of fld "example" to lCurrentFieldScroll
send "updateField" to me in 100 milliseconds
end updateField
This example
will scroll a field, incrementing
every 100 milliseconds. For basic
animation, this is often enough.
The script will take the same
order of magnitude of time to run
regardless of the speed of
machine it is run on, and varying
the delay between messages can be
used to alter the speed of the
animation.
However, if
you need to guarantee that the
script will take a certain amount
of time to run, you need to do a
little more work. Other messages
could get delivered when that
field is scrolling, or the
machine might be busy and
therefore slow down slightly. If
you are playing back audio or
other parts of a presentation to
be exactly in sync, you cannot
allow this field to be left
behind or gradually become out of
sync if anything eles on the
computer causes a glitch or slow
down.
Here is a new
script that will ensure that the
field scrolling is complete
inside ten seconds. If there is a
delay or interruption causing
messages to be delivered late,
the scrolling will jump to the
correct position when processor
time is returned, rather than
increase the overall time taken
to complete the
effect.
|
local lCurrentFieldScroll, lCurrentEndValue, lTotalTime, lStartTime
on mouseUp
put 10000 into lTotalTime -- the total time allowed in milliseconds
put 0 into lcurrentFieldScroll --start with the field scrolled to 0
put 500 into lCurrentEndValue --the vScroll of the field when fully
--scrolled down
put the milliseconds into lStartTime --measure the animation from
--this start time
updateField
end mouseUp
on updateField
put the milliSeconds - lStartTime into tCurrentTime
if tCurrentTime > lTotalTime then --we've reached the end
set the vScroll of fld "example" to tEndValue --ensure that the
--field is at the end
exit updateField --don't send this message again
end if
put tCurrentTime / lTotalTime * lCurrentEndValue into lCurrentFieldScroll
--thats the position the scroll bar should be based on the time elapsed
set the vScroll of fld "example" to round(lCurrentFieldScroll)
-- rounding is required or the script will not work
send "updateField" to me in 50 milliseconds
end updateField
You can use
the above script for just about
any time related activity that
needs to be performed on time.
Just alter the variables in the
first handler, such as the total
length, the total number of
frames (in this case the total
scroll value of the field). You
can also change how frequently
the message is delivered. This
message was delivered every 50
milliseconds, changing that value
down the way will result in a
smoother animation, and changing
the value up the way will leave
more processor time free. But
altering that interval will
not alter the total
length of time time to complete
the animation.
Enjoy
experimenting and using the send
command! And look out for a
timeline animation tool that
automates writing the scripts in
our forthcoming Editor for
MetaCard.
|
Did you find this article useful?
Have any ideas for future topics?
Email
Us!
|
|