We make software for humans. Custom Mac, Windows, iOS and Android solutions in HyperCard, MetaCard, and RunRev LiveCode
(or) How to wreck perfectly good software
by Jacqueline Landman Gay
HyperActive Software
the mouseLoc
in order to repeatedly set an object to the mouse location, or to check in a loop for a mouse condition to determine when to leave the loop. Here is a common example:
repeat until the mouse is up set the loc of btn "dragBtn" to the mouseLoc end repeat
In MetaCard and LiveCode, this kind of mouse polling is emphatically discouraged. Scott Raney of MetaCard Corporation has this to say about the above repeat construct:
This loop uses 100% of the CPU time, regardless of the speed of the processor, bringing the system to its knees, causing poor feedback for your app, and making your system unresponsive to any other processes running on it. You see, on a fast system, 99.99% of the time through that loop you're setting the loc to exactly the same coordinate!I think the biggest problem here is that OSs have evolved way beyond the point where even developers (or at least most of our customers) understand how they work. There's nothing wrong at all with things like "the mouse" on single-user single-tasking OSs like MacOS, but that's just not the world we live in anymore...
I'm certainly sympathetic to the "easier way" and "programming for the rest of us" arguments, but none of us can afford to be classed as developers of system-hostile applications... Developing in a high level language will become a stigma that we'll never live down...
Some of the processes that can slow down or stop when a script uses this kind of processor-intensive repeat loop are: file and printer sharing, HTTP/FTP servers, network management tools, and on UNIX systems (including Mac OS X), people telnetting in from other systems. Another problem on Mac OS X is the spinning rainbow cursor that may appear while the loop is in progress if the operation takes too long the system puts that up whenever it thinks an application is unresponsive. That means if a user drags an object around the card for a while, or keeps the mouse button depressed for too long while the script is in a loop, the system may think your application has gone dead.
The problem is not limited to just checking the mouseloc
or the mouse's up or down state. Checking any of the following mouse events within a loop can cause a bottleneck and should be avoided:
You may be wondering at this point, "If I shouldn't be using a repeat loop to determine the state of the mouse, what do I use?"
The answer lies in the mouseMove
system message, sent by the MetaCard/LiveCode engine whenever the user moves the mouse. The mouseMove
message carries two parameters with it: the horizontal and vertical coordinates of the current mouse position. By writing a handler that traps this system message, you can create an OS-friendly script that will not bottleneck the CPU and which will run far more efficiently than any repeat loop.
There is an additional advantage to using the mouseMove
approach: it allows your stack to continue sending events and reporting other user actions simultaneously. Unlike a repeat loop, which locks out everything else until the handler ends, trapping a system message takes an insignificant amount of time and allows other system messages and all your stack's features to continue operating at the same time. You may not always want to use the other messages that are being sent, but they are there if you need them.
In the above example, all we want to do is move a button to the same location as the current mouse position, for as long as the mouse button is depressed. If the mouse is "up", the script should do nothing. A short handler in the button script could take care of it:
on mouseMove x,y if the mouse is down then set the loc of me to x,y end mouseMove
Very easy. At the same time that mouseMove
is catching the position of the mouse and handling the drag, you can also have other handlers running normally to catch various system messages or handle other events. You can think of mouseMove
as a sort of customized, single-purpose idle
message. (Note though that idle
has its own set of processor-hogging problems, and should be avoided at all costs too.)
But wait. The mouseMove
handler is still polling the mouse, checking for its down state repeatedly. So while we have improved the script's efficiency, it is still using more resources than necessary. We can fix that by adding a variable that stores the state of the mouse, so that the engine won't have to check it each time. We can set a flag on mouseDown
, and turn it off on mouseRelease
and mouseUp
. While we're at it, we'll also add a line to the script that passes the ID of the object we are dragging so that more than one button can respond to the mouseMove
message.
These handlers would go in the card or stack script:
on mouseDown global gDragging,gDragObj,gDeltaX,gDeltaY -- (you could use local script variables instead) if the name of the target contains "button" then put true into gDragging put the long id of the target into gDragObj -- the following adjusts for the mouse position on the object; -- it isn't part of what we're explaining here but it enhances the drag: put item 1 of the loc of gDragObj - the mouseH into gDeltaX put item 2 of the loc of gDragObj - the mouseV into gDeltaY end if end mouseDown on mouseRelease global gDragging put false into gDragging end mouseRelease on mouseUp global gDragging put false into gDragging end mouseUp on mouseMove x,y global gDragging,gDragObj,gDeltaX,gDeltaY if gDragging = true then add gDeltaX to x add gDeltaY to y set the loc of gDragObj to x,y end if end mouseMove
Most HyperCard and SuperCard scripters are familiar with mouseDown
and mouseUp
, but mouseRelease
may be new. The mouseRelease
message is sent to an object when the mouse button goes up but the mouse is no longer within the borders of the object that received the original mouseDown
message. This covers the case where the user may move the mouse faster than the button can keep up. Even if the mouse is released somewhere outside the button, this script will still set the global gDragging
to false.
Now you've got a fast, efficient, CPU-friendly set of handlers that won't bog down the OS or hog system resources. Applications running in the background will still get their fair share of CPU cycles with no performance hit. Your stack will respond much faster and will allow other user actions and scripts to be handled simultaneously. There's no down side, there's lots to gain, and your stack will run like a pro.