GGDrawProcessImpl.mesa
Author: Eric Bier on June 7, 1985 3:12:16 pm PDT
Copyright © 1985 by Xerox Corporation. All rights reserved.
Last edited by Bier on August 21, 1985 6:43:49 pm PDT
Contents: A flexible setup, suggested by Scott McGregor, for processing mouse input as fast as you can (but no faster). I will use it to synchronize my mouse point processing with the mousepoint. If the processing algorithms become faster, this procedure will still do the right thing.
DIRECTORY
Atom,
FS,
GGDrawProcess,
GGError,
GGInterfaceTypes,
GGModelTypes,
GGSessionLog,
IO,
Process,
Rope;
GGDrawProcessImpl: CEDAR MONITOR
IMPORTS Atom, FS, GGError, GGSessionLog, IO, Process
EXPORTS GGDrawProcess =
BEGIN
Point: TYPE = GGModelTypes.Point;
GargoyleData: TYPE = GGInterfaceTypes.GargoyleData;
globalProcess: PROCESSNIL;
globalQ: ActionQueue;
sessionLog: IO.STREAM;
auditOn: BOOLFALSE;
EventProc: TYPE = GGDrawProcess.EventProc;
MouseEventProc: TYPE = GGDrawProcess.MouseEventProc;
ActionQueueMax: NAT = 100;
ActionQueue: TYPE = REF ActionQueueObj;
ActionQueueObj: TYPE = RECORD [
head: NAT ← 0,
tail: NAT ← 0,
size: NAT ← ActionQueueMax,
gargoyleData: GargoyleData,
actions: ARRAY [0..ActionQueueMax] OF LIST OF REF ANY,
points: ARRAY [0..ActionQueueMax] OF Point,
mouseProcs: ARRAY [0..ActionQueueMax] OF MouseEventProc,
eventProcs: ARRAY [0..ActionQueueMax] OF EventProc
];
LogLength: NAT = 20;
ActionLog: TYPE = RECORD [
received: ARRAY[0..LogLength-1] OF BOOL, -- received an action versus performed an action.
bashed: ARRAY[0..LogLength-1] OF BOOL,
events: ARRAY[0..LogLength-1] OF ATOM,
points: ARRAY [0..LogLength-1] OF Point
];
globalLog: ActionLog;
logHead: NAT;
LogRawMouse: PUBLIC ENTRY PROC [point: Point] = {
globalLog.events[logHead] ← $RawMouse;
globalLog.points[logHead] ← point;
logHead ← (logHead + 1) MOD LogLength;
};
LogReceived: PROC [event: ATOM, point: Point, bash: BOOL] = {
globalLog.received[logHead] ← TRUE;
globalLog.bashed[logHead] ← bash;
globalLog.events[logHead] ← event;
globalLog.points[logHead] ← point;
logHead ← (logHead + 1) MOD LogLength;
};
LogActed: PROC [action: LIST OF REF ANY, point: Point, mouseEvent: BOOL] = {
atom: ATOMNARROW[action.first];
globalLog.received[logHead] ← FALSE;
globalLog.bashed[logHead] ← FALSE;
globalLog.events[logHead] ← atom;
globalLog.points[logHead] ← point;
logHead ← (logHead + 1) MOD LogLength;
IF auditOn THEN GGSessionLog.EnterAction[point, action, mouseEvent];
};
OutputLog: PUBLIC ENTRY PROC [] = {
i: NAT ← logHead;
msgRope: Rope.ROPE;
WHILE i#((logHead + 1) MOD LogLength) DO
IF globalLog.events[i] = $RawMouse THEN
msgRope ← IO.PutFR["RAW MOUSE [%g, %g]",
[real[globalLog.points[i][1]]],
[real[globalLog.points[i][2]]] ]
ELSE IF globalLog.bashed[i] THEN
msgRope ← IO.PutFR["Bashed %g at [%g, %g]",
[rope[Atom.GetPName[globalLog.events[i]]]],
[real[globalLog.points[i][1]]],
[real[globalLog.points[i][2]]] ]
ELSE IF globalLog.received[i] THEN
msgRope ← IO.PutFR["Received %g at [%g, %g]",
[rope[Atom.GetPName[globalLog.events[i]]]],
[real[globalLog.points[i][1]]],
[real[globalLog.points[i][2]]] ]
ELSE
msgRope ← IO.PutFR["Acted on %g at [%g, %g]",
[rope[Atom.GetPName[globalLog.events[i]]]],
[real[globalLog.points[i][1]]],
[real[globalLog.points[i][2]]] ];
GGError.AppendTypescript[msgRope];
i ← (i - 1 + LogLength) MOD LogLength;
ENDLOOP;
};
Init: PROC = {
globalQ ← NEW[ActionQueueObj];
logHead ← 0;
FOR i: NAT IN [0..LogLength-1] DO
globalLog.events[i] ← $None;
globalLog.received[i] ← TRUE;
ENDLOOP;
};
ActionDone: CONDITION;
QueueInputAction: PUBLIC ENTRY PROC [callBack: MouseEventProc, inputAction: LIST OF REF ANY, worldPt: Point, gargoyleData: GargoyleData] = TRUSTED {
Add an action to the draw processes queue. This action will definitely be performed, in its turn. Process.Detach is an unsafe operation.
globalQ.gargoyleData ← gargoyleData;
WHILE (globalQ.tail+1) MOD globalQ.size = globalQ.head DO WAIT NotFull ENDLOOP;
globalQ.actions[globalQ.tail] ← inputAction;
globalQ.points[globalQ.tail] ← worldPt;
globalQ.mouseProcs[globalQ.tail] ← callBack;
globalQ.eventProcs[globalQ.tail] ← NIL;
globalQ.tail ← (globalQ.tail + 1) MOD globalQ.size;
LogReceived [event: NARROW[inputAction.first], point: worldPt, bash: FALSE];
IF globalProcess = NIL THEN Process.Detach[globalProcess ← FORK Painter];
};
QueueOrBashInputAction: PUBLIC ENTRY PROC [callBack: MouseEventProc, inputAction: LIST OF REF ANY, worldPt: Point, gargoyleData: GargoyleData] = TRUSTED {
Add an action to the draw processes queue. If the previous action is of the same sort, then overwrite it instead of adding a new one (useful for dragging).
prevAction: ATOM ← PeekPaintActionTail[globalQ];
globalQ.gargoyleData ← gargoyleData;
IF prevAction = NARROW[inputAction.first, ATOM] THEN {
globalQ.actions[(globalQ.tail - 1 + globalQ.size) MOD globalQ.size] ← inputAction;
globalQ.points[(globalQ.tail - 1 + globalQ.size) MOD globalQ.size] ← worldPt;
globalQ.mouseProcs[(globalQ.tail - 1 + globalQ.size) MOD globalQ.size] ← callBack;
globalQ.eventProcs[(globalQ.tail - 1 + globalQ.size) MOD globalQ.size] ← NIL;
IF globalProcess = NIL THEN Process.Detach[globalProcess ← FORK Painter];
LogReceived [event: NARROW[inputAction.first], point: worldPt, bash: TRUE];
RETURN;
};
WHILE (globalQ.tail+1) MOD globalQ.size = globalQ.head DO WAIT NotFull ENDLOOP;
globalQ.actions[globalQ.tail] ← inputAction;
globalQ.points[globalQ.tail] ← worldPt;
globalQ.mouseProcs[globalQ.tail] ← callBack;
globalQ.eventProcs[globalQ.tail] ← NIL;
globalQ.tail ← (globalQ.tail + 1) MOD globalQ.size;
LogReceived [event: NARROW[inputAction.first], point: worldPt, bash: FALSE];
IF globalProcess = NIL THEN Process.Detach[globalProcess ← FORK Painter];
};
QueueInputActionNoPoint: PUBLIC ENTRY PROC [callBack: EventProc, inputAction: LIST OF REF ANY, gargoyleData: GargoyleData] = TRUSTED {
Add an action to the draw processes queue. This action will definitely be performed, in its turn.
globalQ.gargoyleData ← gargoyleData;
WHILE (globalQ.tail+1) MOD globalQ.size = globalQ.head DO WAIT NotFull ENDLOOP;
globalQ.actions[globalQ.tail] ← inputAction;
globalQ.points[globalQ.tail] ← [0.0, 0.0];
globalQ.mouseProcs[globalQ.tail] ← NIL;
globalQ.eventProcs[globalQ.tail] ← callBack;
globalQ.tail ← (globalQ.tail + 1) MOD globalQ.size;
IF globalProcess = NIL THEN Process.Detach[globalProcess ← FORK Painter];
};
QueueOrBashInputActionNoPoint: PUBLIC ENTRY PROC [callBack: EventProc, inputAction: LIST OF REF ANY, gargoyleData: GargoyleData] = TRUSTED {
Add an action to the draw processes queue. If the previous action is of the same sort, then overwrite it instead of adding a new one (useful for testing Gravity).
prevAction: ATOM ← PeekPaintActionTail[globalQ];
globalQ.gargoyleData ← gargoyleData;
IF prevAction = NARROW[inputAction.first, ATOM] THEN {
globalQ.actions[(globalQ.tail - 1 + globalQ.size) MOD globalQ.size] ← inputAction;
globalQ.points[(globalQ.tail - 1 + globalQ.size) MOD globalQ.size] ← [0.0, 0.0];
globalQ.mouseProcs[(globalQ.tail - 1 + globalQ.size) MOD globalQ.size] ← NIL;
globalQ.eventProcs[(globalQ.tail - 1 + globalQ.size) MOD globalQ.size] ← callBack;
IF globalProcess = NIL THEN Process.Detach[globalProcess ← FORK Painter];
RETURN;
};
WHILE (globalQ.tail+1) MOD globalQ.size = globalQ.head DO WAIT NotFull ENDLOOP;
globalQ.actions[globalQ.tail] ← inputAction;
globalQ.points[globalQ.tail] ← [0.0, 0.0];
globalQ.mouseProcs[globalQ.tail] ← NIL;
globalQ.eventProcs[globalQ.tail] ← callBack;
globalQ.tail ← (globalQ.tail + 1) MOD globalQ.size;
IF globalProcess = NIL THEN Process.Detach[globalProcess ← FORK Painter];
};
DeQueuePaintAction: PRIVATE PROC [q: ActionQueue] RETURNS [mouseEventProc: MouseEventProc, eventProc: EventProc, inputAction: LIST OF REF ANY, worldPt: Point, gargoyleData: GargoyleData] = {
IF q.tail = q.head THEN ERROR;
mouseEventProc ← q.mouseProcs[q.head];
eventProc ← q.eventProcs[q.head];
inputAction ← q.actions[q.head];
worldPt ← q.points[q.head];
gargoyleData ← q.gargoyleData;
q.head ← (q.head + 1) MOD q.size;
LogActed[inputAction, worldPt, mouseEventProc#NIL];
};
NotFull: CONDITION;
EmptyQ: PROC [q: ActionQueue] RETURNS [BOOL] = {
RETURN[q.tail = q.head];
};
PeekPaintActionHead: PRIVATE PROC [q: ActionQueue] RETURNS [whatToPaint: ATOM] = {
IF q.tail = q.head THEN RETURN[NIL];
whatToPaint ← NARROW[q.actions[q.head].first, ATOM];
};
PeekPaintActionTail: PRIVATE PROC [q: ActionQueue] RETURNS [whatToPaint: ATOM] = {
IF q.head = q.tail THEN RETURN[NIL];
whatToPaint ← NARROW[q.actions[(q.tail - 1 + q.size) MOD q.size].first, ATOM];
};
Restart: PUBLIC ENTRY PROC [] = TRUSTED {
Sometimes the Painter process is aborted because of a bug in one of the dispatched procedures. This procedure allows the user to get things going again.
Process.Detach[globalProcess ← FORK Painter];
};
Notify: ENTRY PROC [] = {
NOTIFY ActionDone;
};
Painter: PROC = {
inputAction: LIST OF REF ANY;
worldPt: Point;
gargoyleData: GargoyleData;
mouseEventProc: MouseEventProc;
eventProc: EventProc;
[mouseEventProc, eventProc, inputAction, worldPt, gargoyleData] ← NextAction[];
WHILE inputAction # NIL DO
IF mouseEventProc = NIL THEN eventProc[inputAction, gargoyleData]
ELSE mouseEventProc[inputAction, gargoyleData, worldPt];
[mouseEventProc, eventProc, inputAction, worldPt, gargoyleData] ← NextAction[];
ENDLOOP;
Notify[];
};
NextAction: PUBLIC ENTRY PROC RETURNS [mouseEventProc: MouseEventProc, eventProc: EventProc, inputAction: LIST OF REF ANY, worldPt: Point, gargoyleData: GargoyleData] = {
IF EmptyQ[globalQ] THEN {
globalProcess ← NIL; -- no mouse action. Let process die.
inputAction ← NIL;
}
ELSE {
[mouseEventProc, eventProc, inputAction, worldPt, gargoyleData] ← DeQueuePaintAction[globalQ];
BROADCAST NotFull;
};
};
AuditOn: PRIVATE ENTRY PROC [f: IO.STREAM] = {
sessionLog ← f;
auditOn ← TRUE;
};
AuditOff: PRIVATE ENTRY PROC [] RETURNS [f: IO.STREAM] = {
f ← sessionLog;
sessionLog ← NIL;
auditOn ← FALSE;
};
OpenSessionLog: PUBLIC PROC [fileName: Rope.ROPE] = {
Open a file, grab the Monitor and turn on auditing, RETURN.
f: IO.STREAM;
success: BOOL;
[f, success] ← OpenFile[fileName];
IF NOT success THEN RETURN;
GGSessionLog.SetStream[f];
AuditOn[f];
};
OpenFile: PROC [name: Rope.ROPE] RETURNS [f: IO.STREAM, success: BOOL] = {
success ← TRUE;
Two possiblilities
1) File doesn't exist. Print error message.
2) File does exist. File it in. Succeed.
f ← FS.StreamOpen[name, $create
! FS.Error => {
IF error.group = user THEN {
success ← FALSE;
GGError.Append["Picture file problem: ", oneLiner];
GGError.Append[error.explanation, oneLiner];
}
ELSE ERROR;
CONTINUE}];
};
CloseSessionLog: PUBLIC PROC [] = {
f: IO.STREAM;
grab the Monitor, turn off auditing, Close the log file, RETURN.
f ← AuditOff[];
f.Close[];
};
Init[];
END.