DIRECTORY Imager, IO, Rope, ViewerClasses; SlackProcess: CEDAR DEFINITIONS = BEGIN Point: TYPE = Imager.VEC; BitVector: TYPE = REF BitVectorObj; BitVectorObj: TYPE = RECORD [bits: PACKED SEQUENCE len: NAT OF BOOL]; EventProc: TYPE = PROC [event: LIST OF REF ANY, clientData: REF ANY]; MouseEventProc: TYPE = PROC [event: LIST OF REF ANY, point: Point, clientData: REF ANY]; OptimizeProc: TYPE = PROC [summary: QueueSummary] RETURNS [skipActions: NAT]; LoggingProc: TYPE = PROC [point: Point, action: LIST OF REF ANY, mouseEvent: BOOL, clientData: REF ANY]; AbortProc: TYPE = PROC [data: REF ANY]; ClientDatas: TYPE = REF ClientDatasData; ClientDatasData: TYPE = RECORD[SEQUENCE len: NAT OF REF ANY]; OptimizeHints: TYPE = REF OptimizeHintsData; OptimizeHintsData: TYPE = RECORD[SEQUENCE len: NAT OF REF ANY]; Actions: TYPE = REF ActionsData; ActionsData: TYPE = RECORD[SEQUENCE len: NAT OF LIST OF REF ANY]; Points: TYPE = REF PointsData; PointsData: TYPE = RECORD[SEQUENCE len: NAT OF Point]; QueueSummary: TYPE = REF QueueSummaryObj; QueueSummaryObj: TYPE = RECORD [ count: NAT _ 0, -- how many events are currently in the queue clientDatas: ClientDatas, -- the clientData registered with each event optimizeHints: OptimizeHints, -- the REF ANY hint registered with each event actions: Actions, -- the LIST OF REF ANY action with each event points: Points -- the mouse point at which the action occurs (if the -- action was queued by QueueInputActionWithPoint) ]; SlackHandle: TYPE = REF SlackData; SlackData: TYPE = MONITORED RECORD [ slackProcess: PROCESS, queue: Queue, summary: QueueSummary, optimizeProc: OptimizeProc, log: Log, abort: Abort ]; -- eventually should be opaque Queue: TYPE = REF QueueData; QueueData: TYPE = RECORD [ notFull: CONDITION, head: NAT _ 0, tail: NAT _ 0, size: NAT _ 0, clientDatas: ClientDatas _ NIL, optimizeHints: OptimizeHints _ NIL, actions: Actions _ NIL, points: Points _ NIL, mouseProcs: MouseProcs _ NIL, eventProcs: EventProcs _ NIL ]; MouseProcs: TYPE = REF MouseProcsData; MouseProcsData: TYPE = RECORD[SEQUENCE len: NAT OF MouseEventProc]; EventProcs: TYPE = REF EventProcsData; EventProcsData: TYPE = RECORD[SEQUENCE len: NAT OF EventProc]; Log: TYPE = REF LogData; LogData: TYPE = RECORD[ head: NAT _ 0, size: NAT _ 0, loggerEnabled: BOOL _ FALSE, logger: LoggingProc _ NIL, received: Received, -- received an action versus performed an action. bashed: Bashed, -- bashed an action versus performed an action. events: Events, points: Points ]; Received: TYPE = REF ReceivedData; ReceivedData: TYPE = RECORD[SEQUENCE len: NAT OF BOOL]; Bashed: TYPE = REF BashedData; BashedData: TYPE = RECORD[SEQUENCE len: NAT OF BOOL]; Events: TYPE = REF EventsData; EventsData: TYPE = RECORD[SEQUENCE len: NAT OF REF ANY]; Abort: TYPE = REF AbortData; AbortData: TYPE = RECORD [ enabled: BOOL _ FALSE, proc: AbortProc, viewer: ViewerClasses.Viewer, data: REF ]; Create: PROC [queueSize: NAT _ 50, logSize: NAT _ 50, optimizeProc: OptimizeProc, loggingProc: LoggingProc _ NIL, abortProc: AbortProc _ NIL, abortData: REF ANY _ NIL, abortViewer: ViewerClasses.Viewer _ NIL] RETURNS[handle: SlackHandle]; QueueInputAction: PROC [handle: SlackHandle, callBack: EventProc, inputAction: LIST OF REF ANY, clientData: REF ANY, optimizeHint: REF ANY]; QueueInputActionWithPoint: PROC [handle: SlackHandle, callBack: MouseEventProc, inputAction: LIST OF REF ANY, point: Point, clientData: REF ANY, optimizeHint: REF ANY]; Restart: PROC [handle: SlackHandle]; FlushQueue: PROC [handle: SlackHandle]; LogRawMouse: PROC [handle: SlackHandle, point: Point]; OutputLog: PROC [handle: SlackHandle, stream: IO.STREAM]; 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]; 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]; END. ÌSlackProcess.mesa Copyright c 1986 by Xerox Corporation. All rights reserved. Last edited by Pier on November 5, 1986 7:05:11 pm PST Bier, November 4, 1986 11:27:38 pm PST 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 QueueInputAction or QueueInputActionWithPoint. 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. 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 (SHIFT-Spare3) on a Dorado 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. BEGIN DEFS Callback PROC TYPEs The client looks at the queue summary 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. PUBLIC TYPES The SlackData record. Should be considered PRIVATE by clients of SlackProcess. BEGIN PRIVATE STUFF ... The QueueData record and its fields should be considered PRIVATE by clients of SlackProcess The LogData record and its fields should be considered PRIVATE by clients of SlackProcess ... END PRIVATE STUFF Creating a SlackProcess 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). General-Purpose Queuing Action Queues a menu or keyboard action and returns immediately. When this action reaches the front 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 (if the slack process begins to get behind the user). Special Queuing Action Which Takes a Point Giving the Slack Process a mouse point as well as an inscrutable LIST OF REF ANY, makes it easier to debug the slack process. Gargoyle does this for all operations where mouse coordinates are part of the inputAction. Queues a mouse action and returns immediately. When this action reaches the front 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 (if the slack process begins to get behind the user). Restarting a SlackProcess that has been ABORTed (e.g. from an ERROR window). Creates a new slack process. Call this procedure if a bug in the slack process forces it to be aborted. Cancelling all actions on the queue. 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. SlackProcess keeps a ring buffer of recent input actions (called the log) for debugging purposes. May be called by a client's NotifyProc to simply record raw mouse or client coordinates on the queue. SlackProcess will dequeue them and do nothing. Useful to discover the queue delay between getting a mouse event and actually processing it. Output the "LogSize-1" most recent events to the stream. The most recent event is output first. Creating 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. If you didn't register a loggingProc when you called Create, this gives you another chance. The registered abortProc will be called (if aborts are enabled) with abortData as argument when the slack process is aborted. If you didn't register an abortProc when you called Create, this gives you another chance. ÊÕ˜code™Kšœ Ïmœ1™Kšœ¢,˜GKšœ¢.˜LKšœ¢-˜@Kšœ¢5˜EKšœ¢œ˜5K˜K˜——Kšœ žœžœ ˜"K˜Kš O™Oš ™šœ žœž œžœ˜$Kšœžœ˜Kšœ ˜ K˜Kšœ˜Kšœ ˜ K˜ Kšœ¢˜!K˜—Kšœžœžœ ˜Kšœ9Ðkoœ™[šœ žœžœ˜Kšœ ž œ˜Kšœžœ˜Kšœžœ˜Kšœžœ˜Kšœžœ˜Kšœžœ˜#Kšœžœ˜Kšœžœ˜Kšœžœ˜Kšœž˜K˜K˜—Kšœ žœžœ˜&Kš œžœžœžœžœžœ˜CKšœ žœžœ˜&Kš œžœžœžœžœžœ ˜>K˜Kšœžœžœ ˜KšœY™Yšœ žœžœ˜Kšœžœ˜Kšœžœ˜Kšœžœžœ˜Kšœžœ˜Kšœ¢1˜EKšœ¢/˜?Kšœ˜Kšœ˜Kšœ˜K˜—Kšœ žœžœ˜"Kš œžœžœžœžœžœžœ˜7Kšœžœžœ ˜Kš œ žœžœžœžœžœžœ˜5Kšœžœžœ ˜Kšœ žœžœžœžœžœžœžœ˜8K˜Kšœžœžœ ˜šœ žœžœ˜Kšœ žœžœ˜K˜K˜Kšœž˜ K˜——Kš ™K˜K™Kš¡œžœ žœžœ>žœžœ žœžœžœ&žœžœ˜îKšœí™íK™K™š¡œžœ9žœžœžœžœžœžœžœžœ˜ŒKšœœ™œ—K˜™*KšœÙ™Ù—š¡œžœ>žœžœžœžœžœžœžœžœ˜¨Kšœ‘™‘—K™K™Lš¡œžœ˜$K™h—K˜K™$š¡ œžœ˜'K™µ—K™K™aš¡ œžœ%˜6K™ñ—š¡ œžœžœžœ˜9Kšœ_™_—K˜™K™Ÿ—Kš ¡œžœžœžœžœ˜XKš ¡œžœžœžœžœ˜ZKš ¡œžœ1žœžœžœ˜oKšœ[™[K˜Kšœ}™}Kš ¡ œžœžœžœžœ˜PKš ¡ œžœžœžœžœ˜RKš¡œžœ.žœ žœžœžœ&žœžœžœžœ˜·KšœZ™ZK˜Kšžœ˜K˜—…—Ô1u