SlackProcess.mesa
Copyright Ó 1986, 1992 by Xerox Corporation. All rights reserved.
Last edited by Pier on November 5, 1986 7:05:11 pm PST
Bier, March 13, 1991 6:09 pm PST
Michael Plass, March 25, 1992 11:20 am PST
DIRECTORY
IO, Process, Rope, SlackProcessTypes, ViewerClasses;
SlackProcess: CEDAR DEFINITIONS = BEGIN
Overview
SlackProcess solves a problem in user interfaces that employ smooth motion. For instance, in Gargoyle and Solidviews, the user can move objects smoothly by moving the mouse. Problem: how often should the frames be redrawn? The easiest solution to implement is to redraw the scene once for each mouse motion action from the TIP table. Unfortunately, for complex frames, this results in the redrawing getting behind the user. A simple improvement, suggested years ago by Scott McGregor, is to queue up the TIP table actions, and provide a second process, the slack process, to dequeue and execute them. If the slack process gets behind, it drops some actions on the floor in order to catch up. Thus the motion is "as smooth as possible" but doesn't get behind. Scott points out that, as more processing power becomes available, this algorithm allows motion to get smoother without recoding.
The name SlackProcess refers to the user's perception, with the naive algorithm, that the objects he is dragging are lagging farther and farther from the cursor, like a dog with too long a leash.
SlackProcess also serves to synchronize user actions. There is no need to use MBQueues and similar packages if SlackProcess is used.
Multiple slack processes and their associated queues may be used by many clients; the slack process issues a handle containing MONITORED data for use by the client. Actions may be queued by calls to QueueAction or QueueAtHead.
Each SlackProcess provides a logging feature for clients. A client can register a Logging procedure and enable/disable Session logging. With logging enabled, the registered Logging procedure will be called every time an action is dequeued. The client may then save a log or script of the session in progress for later playback to a future SlackProcess.
Each SlackProcess provides an abort feature for clients. [Note: Abort does not work yet in PCedar (March 13, 1991).] A client can register an Abort procedure and some abort data and enable/disable aborting. With aborting enabled, the slack queue will be flushed and the registered abort procedure will be called (with the abort data as argument) every time an abort occurs. The current trigger for the abort action is holding down SHIFT-SWAT with the cursor in the viewer to be aborted. If the viewer specified when aborting is registered is NIL, then the cursor position will be ignored when checking for abort.
QueueEntryGenerator: TYPE = SlackProcessTypes.QueueEntryGenerator;
ROPE: TYPE = Rope.ROPE;
SlackHandle: TYPE = SlackProcessTypes.SlackHandle;
AbortProc: TYPE = PROC [data: REF];
ActionProc: TYPE = PROC [clientData: REF, inputAction: REF];
LoggingProc: TYPE = PROC [clientData: REF, inputAction: REF];
Problem: ERROR [msg: ROPE];
Create:
PROC [queueSize:
NAT ¬ 50, logSize:
NAT ¬ 50, optimizeProc: OptimizeProc, loggingProc: LoggingProc ¬
NIL, abortProc: AbortProc ¬
NIL, abortData:
REF
ANY ¬
NIL, abortViewer: ViewerClasses.Viewer ¬
NIL, priority: Process.Priority ¬ Process.priorityNormal]
RETURNS[handle: SlackHandle];
Create a new queue and slack process. The default queueSize and logSize should be adequate for most applications. The optimizeProc is called whenever the slack process finishes an action, before performing the next action. The optimizeProc allows the client to look at the queue and decide how many (if any) of the actions on the front of the queue may be skipped. A loggingProc can be provided that will be called whenever a new action is about to be performed (See EnableSessionLogging below). SlackProcess will abort itself when SHIFT-SWAT are down and the mouse is in a specified viewer (see EnableAborts below. Broken in PCedar as of March 13, 1991, see ViewerAbortStubImpl.). "priority" specifies the default process priority of the slack process. It can be changed using ChangePriority or QueueAction below.
ChangePriority:
PROC [handle: SlackHandle, priority: Process.Priority];
Subsequent actions will be handled at this priority (unless they explicitly name a priority), until further notice.
QueueAction:
PROC [handle: SlackHandle, callBack: ActionProc, inputAction:
REF, clientData:
REF, optimizeHint:
REF, priority: Process.Priority ¬
LAST[
CARD32]];
Places a menu or keyboard action on the tail of the queue and returns immediately. If this action reaches the head of the queue and is not skipped by the OptimizeProc, callBack[inputAction, clientData] will be called. optimizeHints are queued with their corresponding input action and passed to the OptimizeProc (see Create above) to help decide if this action should be skipped. The SlackProcess will run with the stated priority for the duration of this action. If no priority is specified, uses the priority for this SlackHandle.
QueueAtHead:
PROC [handle: SlackHandle, callBack: ActionProc, inputAction:
REF, clientData:
REF, optimizeHint:
REF, priority: Process.Priority ¬
LAST[
CARD32]];
Like QueueAction, but places the new action at the front of the queue, so that it will be the next action performed once the current action (if any) is finished.
QueueIsBusy:
PROC [handle: SlackHandle]
RETURNS [
BOOL];
Returns TRUE if either the queue is non-empty, the slack process is actively executing an event, or both. Returns FALSE otherwise.
FlushQueue:
PROC [handle: SlackHandle];
Removes all unprocessed actions from the slack queue. CAUTION: If the caller is counting on a certain set of actions to be done atomically, this call could destroy its invariants.
Optimizing the Queue
The optimizeProc registered with Create will be called whenever the slack process finishes an action. As argument, it receives a QueueEntryGenerator. Call GetQueueEntry to see what is on the queue. GetQueueEntry[qeGen, 0] is the entry first in line to be done, GetQueueEntry[qeGen, 1] is second in line and so on. Index should be in the range [0..actionsOnQueue-1].
OptimizeProc:
TYPE =
PROC [qeGen: QueueEntryGenerator, actionsOnQueue:
NAT]
RETURNS [skipActions:
NAT];
The client looks at the queue summary (using the GetQueueEntry routine below) and decides how many of the actions should be skipped. If the next action on the queue should be performed, this routine should return 0.
Caution: the OptimizeProc will be called with the SlackProcess MONITOR locked. Because the Cedar InputNotifier calls this MONITOR, your Cedar world will lock up if any ERROR is raised by by your OptimizeProc. To ease the pain of debugging, SlackProcess will catch the ERROR SlackProcess.Problem and release the lock. If possible, catch all ERRORs that your OptimizeProc may generate and raise SlackProcess.Problem instead, putting a helpful ROPE in its message field. You will not be able to browse your stack, but you'll have some idea of what went wrong, and your world won't freeze up.
GetQueueEntry: PROC [qeGen: QueueEntryGenerator, index: NAT] RETURNS [clientData: REF, optimizeHint: REF, inputAction: REF, priority: Process.Priority];
Restarting a SlackProcess that has been ABORTed (e.g. from an ERROR window).
Restart:
PROC [handle: SlackHandle];
Creates a new slack process. Call this procedure if a bug in the slack process forces it to be aborted.
Session Logs
SlackProcess supports reporting of all actions actually performed via slack process. The client provides a logging procedure to be called whenever an action is performed for the application (see Create above). Playback scripts for interactive systems may be created using Session logs.
EnableSessionLogging: PROC [handle: SlackHandle] RETURNS [alreadyEnabled: BOOL ¬ FALSE];
DisableSessionLogging: PROC [handle: SlackHandle] RETURNS [alreadyDisabled: BOOL ¬ FALSE];
RegisterLogger:
PROC [handle: SlackHandle, loggingProc: LoggingProc]
RETURNS [alreadyRegistered:
BOOL ¬
FALSE];
If you didn't register a loggingProc when you called Create, this gives you another chance.
Aborting from the Keyboard
The registered abortProc will be called (if aborts are enabled) with abortData as argument when the slack process is aborted.
EnableAborts: PROC [handle: SlackHandle] RETURNS [alreadyEnabled: BOOL ¬ FALSE];
DisableAborts: PROC [handle: SlackHandle] RETURNS [alreadyDisabled: BOOL ¬ FALSE];
RegisterAbortProc:
PROC [handle: SlackHandle, abortProc: AbortProc ¬
NIL, abortData:
REF
ANY ¬
NIL, abortViewer: ViewerClasses.Viewer ¬
NIL]
RETURNS [alreadyRegistered:
BOOL ¬
FALSE];
If you didn't register an abortProc when you called Create, this gives you another chance.
Pausing
Pause:
PROC [handle: SlackHandle];
Request that the dequeuing process stop as soon as it has finished its current action (if any).
Continue:
PROC [handle: SlackHandle];
Cancels a pause request, allowing the dequeuing process to resume processing actions.
PauseRequested:
PROC [handle: SlackHandle]
RETURNS [
BOOL];
Returns TRUE if Pause has been called on this handle since the last Create or Continue was performed on this handle.
ProcessIsBusy:
PROC [handle: SlackHandle]
RETURNS [
BOOL];
RETURNS TRUE if the dequeuing process associated with handle is currently active.
HasPaused:
PROC [handle: SlackHandle]
RETURNS [
BOOL] =
INLINE {
RETURN[PauseRequested[handle] AND (NOT ProcessIsBusy[handle])];
};
WaitUntilPaused:
PROC [handle: SlackHandle, timeoutTicks: Process.Ticks ¬
LAST[
CARD32]]
RETURNS [success:
BOOL ¬
TRUE];
Causes the calling process to wait on a condition variable until the dequeuing process has stopped processing actions. If no pause has been requested, we wait anyway hoping another process will request one. If timeoutTicks = LAST[CARD32], always waits for the pause. Otherwise, if no pause occurs in timeoutTicks ticks, returns success = FALSE.
ProcessState: TYPE = {paused, idle, timeout};
WaitUntilPausedOrIdle:
PROC [handle: SlackHandle, timeoutTicks: Process.Ticks ¬
LAST[
CARD32]]
RETURNS [state: ProcessState];
Causes the calling process to wait on a condition variable until the dequeuing process has stopped processing actions. If timeoutTicks = LAST[CARD32], always waits for pause or idle. Otherwise, if no pause or idle occurs in timeoutTicks ticks, returns state = timeout.
QueueReadOnly:
PROC [handle: SlackHandle, callBack: ActionProc, inputAction:
REF, clientData:
REF, optimizeHint:
REF, priority: Process.Priority ¬
LAST[
CARD32]];
Like SlackProcess.QueueAction but marks this action as one that will only read data structures, not modify them.
QueueOrTimeout:
PROC [handle: SlackHandle, callBack: ActionProc, inputAction:
REF, clientData:
REF, optimizeHint:
REF, timeoutTicks: Process.Ticks, priority: Process.Priority ¬
LAST[
CARD32]]
RETURNS [success:
BOOL ¬
TRUE];
Like SlackProcess.QueueAction, but includes a timeout. If the action cannot be queued within this time period, the routine returns success = FALSE.
AllQueuedAreReadOnly:
PROC [handle: SlackHandle]
RETURNS [
BOOL];
Returns TRUE if all currently queued actions (including the currently active action, if any) were queued using QueueReadOnly.
InspectProc: TYPE = PROC [qeGen: SlackProcessTypes.QueueEntryGenerator, actionsOnQueue: NAT];
InspectQueue:
PROC [handle: SlackHandle, inspectProc: InspectProc];
Gives client a chance to inspect the queue at a time other than when the slack process is trying to decide what to do next (i.e., an InspectProc is much like an OptimizeProc).
END.