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: PROCESS ← NIL;
globalQ: ActionQueue;
sessionLog: IO.STREAM;
auditOn: BOOL ← FALSE;
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;
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: ATOM ← NARROW[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];
};
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[];
};
END.