GGSlackProcessImpl.mesa
Formerly called: GGDrawProcessImpl.mesa
Copyright © 1985 by Xerox Corporation. All rights reserved.
Last edited by Pier on May 20, 1986 2:06:47 pm PDT
Last edited by Bier on February 18, 1986 10:33:17 pm PST
Contents: A flexible setup, suggested by Scott McGregor, for processing mouse input as fast as you can (but no faster). Used to synchronize mouse point processing with the mousepoint. If the processing algorithms become faster, this procedure will still do the right thing.
DIRECTORY
Atom,
FS,
GGBasicTypes,
GGSlackProcess,
GGError,
GGInterfaceTypes,
GGSessionLog,
IO,
Process,
Rope;
GGSlackProcessImpl:
CEDAR
MONITOR
IMPORTS Atom, FS, GGError, GGSessionLog, IO, Process
EXPORTS GGSlackProcess = BEGIN
Point: TYPE = GGBasicTypes.Point;
GargoyleData: TYPE = GGInterfaceTypes.GargoyleData;
EventProc: TYPE = GGSlackProcess.EventProc;
MouseEventProc: TYPE = GGSlackProcess.MouseEventProc;
EventNotifyProc: TYPE = GGSlackProcess.EventNotifyProc;
ActionQueueMax: NAT = 100;
ActionQueue: TYPE = REF ActionQueueObj;
ActionQueueObj:
TYPE =
RECORD [
head: NAT ← 0,
tail: NAT ← 0,
size: NAT ← ActionQueueMax,
gargoyleData: GargoyleData, KAP February 18, 1986
gDatas: ARRAY [0..ActionQueueMax] OF 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 REF ANY,
points: ARRAY [0..LogLength-1] OF Point
];
globalLog: ActionLog;
logHead: NAT;
globalProcess: PROCESS ← NIL;
globalQ: ActionQueue;
globalEventNotifyProc: EventNotifyProc ← NIL;
sessionLog: IO.STREAM;
auditOn: BOOL ← FALSE;
LogRawMouse:
PUBLIC
ENTRY
PROC [point: Point] = {
globalLog.events[logHead] ← $RawMouse;
globalLog.points[logHead] ← point;
logHead ← (logHead + 1) MOD LogLength;
};
LogReceived:
PROC [event:
REF ANY, 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] = {
globalLog.received[logHead] ← FALSE;
globalLog.bashed[logHead] ← FALSE;
globalLog.events[logHead] ← action.first;
globalLog.points[logHead] ← point;
logHead ← (logHead + 1) MOD LogLength;
IF auditOn THEN GGSessionLog.EnterAction[point, action, mouseEvent];
};
OutputLog:
PUBLIC
ENTRY
PROC [] = {
i: NAT ← (logHead - 1 + LogLength) MOD LogLength; -- back up pointer one slot
msgRope: Rope.ROPE;
the following loop prints out the "LogLength-1" most recent events. The most recent event is printed first.
msgRope ← IO.PutFR["logHead = %g", IO.int[logHead] ];
GGError.AppendTypescript[msgRope];
GGError.AppendTypescript["Most Recent Event: "];
WHILE i#logHead
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
IF
ISTYPE[globalLog.events[i],
ATOM]
THEN
msgRope ←
IO.PutFR["Bashed %g at [%g, %g]",
[rope[Atom.GetPName[NARROW[globalLog.events[i]]]]],
[real[globalLog.points[i][1]]],
[real[globalLog.points[i][2]]] ]
ELSE msgRope ←
IO.PutFR["Bashed %g at [%g, %g]",
[character[NARROW[globalLog.events[i], REF CHAR]^]],
[real[globalLog.points[i][1]]],
[real[globalLog.points[i][2]]] ]
ELSE
IF globalLog.received[i]
THEN
IF
ISTYPE[globalLog.events[i],
ATOM]
THEN
msgRope ←
IO.PutFR["Received %g at [%g, %g]",
[rope[Atom.GetPName[NARROW[globalLog.events[i]]]]],
[real[globalLog.points[i][1]]],
[real[globalLog.points[i][2]]] ]
ELSE msgRope ←
IO.PutFR["Received %g at [%g, %g]",
[character[NARROW[globalLog.events[i], REF CHAR]^]],
[real[globalLog.points[i][1]]],
[real[globalLog.points[i][2]]] ]
ELSE
IF
ISTYPE[globalLog.events[i],
ATOM]
THEN
msgRope ←
IO.PutFR["Acted on %g at [%g, %g]",
[rope[Atom.GetPName[NARROW[globalLog.events[i]]]]],
[real[globalLog.points[i][1]]],
[real[globalLog.points[i][2]]] ]
ELSE msgRope ←
IO.PutFR["Acted on %g at [%g, %g]",
[character[NARROW[globalLog.events[i], REF CHAR]^]],
[real[globalLog.points[i][1]]],
[real[globalLog.points[i][2]]] ];
GGError.AppendTypescript[msgRope];
i ← (i - 1 + LogLength) MOD LogLength;
ENDLOOP;
GGError.AppendTypescript["Least Recent Event\n"];
};
Init:
PROC = {
globalQ ← NEW[ActionQueueObj];
logHead ← 0;
FOR i:
NAT
IN [0..LogLength-1]
DO
globalLog.events[i] ← $None;
globalLog.received[i] ← TRUE;
ENDLOOP;
};
RegisterEventNotifyProc:
PUBLIC PROC [proc: EventNotifyProc] = {
globalEventNotifyProc ← proc;
};
EventNotify:
PUBLIC PROC [notice:
LIST
OF
REF
ANY] = {
globalEventNotifyProc[notice];
};
ActionDone: CONDITION;
InternalQueueInputAction:
INTERNAL
PROC [callBack: MouseEventProc, inputAction:
LIST
OF
REF
ANY, worldPt: Point, gargoyleData: GargoyleData] =
TRUSTED {
Add an action to the tail of the slack process queue. This action will definitely be performed, in its turn. Process.Detach is an unsafe operation.
globalQ.gargoyleData ← gargoyleData; --KAP. February 18, 1986
qIndex: NAT ← 0;
WHILE (globalQ.tail+1) MOD globalQ.size = globalQ.head DO WAIT NotFull ENDLOOP;
qIndex ← globalQ.tail;
globalQ.gDatas[qIndex] ← gargoyleData;
globalQ.actions[qIndex] ← inputAction;
globalQ.points[qIndex] ← worldPt;
globalQ.mouseProcs[qIndex] ← callBack;
globalQ.eventProcs[qIndex] ← NIL;
globalQ.tail ← (qIndex + 1) MOD globalQ.size;
LogReceived[event: inputAction.first, point: worldPt, bash: FALSE];
IF globalProcess = NIL THEN Process.Detach[globalProcess ← FORK Actor];
};
QueueInputAction:
PUBLIC
ENTRY
PROC [callBack: MouseEventProc, inputAction:
LIST
OF
REF
ANY, worldPt: Point, gargoyleData: GargoyleData] = {
Add an action to the slack process queue.
InternalQueueInputAction[callBack, inputAction, worldPt, gargoyleData];
};
QueueOrBashInputAction:
PUBLIC
ENTRY
PROC [callBack: MouseEventProc, inputAction:
LIST
OF
REF
ANY, worldPt: Point, gargoyleData: GargoyleData] =
TRUSTED {
Add an action to the slack process queue. If the previous action is of the same sort, then overwrite it instead of adding a new one (useful for dragging).
prevAction: ATOM ← PeekActionTail[globalQ];
qIndex: NAT ← (globalQ.tail - 1 + globalQ.size) MOD globalQ.size;
IF prevAction =
NARROW[inputAction.first,
ATOM]
THEN {
-- bash
globalQ.gDatas[qIndex] ← gargoyleData;
globalQ.actions[qIndex] ← inputAction;
globalQ.points[qIndex] ← worldPt;
globalQ.mouseProcs[qIndex] ← callBack;
globalQ.eventProcs[qIndex] ← NIL;
LogReceived[event: inputAction.first, point: worldPt, bash: TRUE];
IF globalProcess = NIL THEN Process.Detach[globalProcess ← FORK Actor];
}
ELSE InternalQueueInputAction[callBack, inputAction, worldPt, gargoyleData]; -- queue.
};
InternalQueueInputActionNoPoint:
INTERNAL
PROC [callBack: EventProc, inputAction:
LIST
OF
REF
ANY, gargoyleData: GargoyleData] =
TRUSTED {
Add an action to the tail of the slack process queue. This action will definitely be performed, in its turn.
qIndex: NAT ← 0;
WHILE (globalQ.tail+1) MOD globalQ.size = globalQ.head DO WAIT NotFull ENDLOOP;
qIndex ← globalQ.tail;
globalQ.gDatas[qIndex] ← gargoyleData;
globalQ.actions[qIndex] ← inputAction;
globalQ.points[qIndex] ← [-999.0, -999.0];
globalQ.mouseProcs[qIndex] ← NIL;
globalQ.eventProcs[qIndex] ← callBack;
globalQ.tail ← (qIndex + 1) MOD globalQ.size;
LogReceived[event: inputAction.first, point: [-999.0, -999.0], bash: FALSE];
IF globalProcess = NIL THEN Process.Detach[globalProcess ← FORK Actor];
};
QueueInputActionNoPoint:
PUBLIC
ENTRY
PROC [callBack: EventProc, inputAction:
LIST
OF
REF
ANY, gargoyleData: GargoyleData] =
TRUSTED {
Add an action to the slack processes queue. This action will definitely be performed, in its turn.
InternalQueueInputActionNoPoint[callBack, inputAction, gargoyleData];
};
QueueOrBashInputActionNoPoint:
PUBLIC
ENTRY
PROC [callBack: EventProc, inputAction:
LIST
OF
REF
ANY, gargoyleData: GargoyleData] =
TRUSTED {
Add an action to the slack 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 ← PeekActionTail[globalQ];
qIndex: NAT ← (globalQ.tail - 1 + globalQ.size) MOD globalQ.size;
IF prevAction =
NARROW[inputAction.first,
ATOM]
THEN {
-- bash
globalQ.gDatas[qIndex] ← gargoyleData;
globalQ.actions[qIndex] ← inputAction;
globalQ.points[qIndex] ← [0.0, 0.0];
globalQ.mouseProcs[qIndex] ← NIL;
globalQ.eventProcs[qIndex] ← callBack;
IF globalProcess = NIL THEN Process.Detach[globalProcess ← FORK Actor];
}
ELSE InternalQueueInputActionNoPoint[callBack, inputAction, gargoyleData]; -- queue
};
DeQueueAction:
INTERNAL
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.gDatas[q.head];
q.head ← (q.head + 1) MOD q.size;
LogActed[inputAction, worldPt, mouseEventProc#NIL];
};
EmptyQ:
INTERNAL PROC [q: ActionQueue]
RETURNS [
BOOL] = {
RETURN[q.tail = q.head];
};
PeekActionHead: PRIVATE PROC [q: ActionQueue] RETURNS [whatToDo: ATOM] = {
IF q.tail = q.head THEN RETURN[NIL];
whatToDo ← NARROW[q.actions[q.head].first, ATOM];
};
PeekActionTail:
PRIVATE
PROC [q: ActionQueue]
RETURNS [whatToDo:
ATOM] = {
IF q.head = q.tail THEN RETURN[NIL];
whatToDo ← 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 Actor];
};
Notify:
ENTRY
PROC [] = {
NOTIFY ActionDone;
};
Actor:
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] ← DeQueueAction[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[];
IF f#NIL THEN f.Close[];
};
END.