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: PROCESSNIL;
globalQ: ActionQueue;
globalEventNotifyProc: EventNotifyProc ← NIL;
sessionLog: IO.STREAM;
auditOn: BOOLFALSE;
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];
};
NotFull: CONDITION;
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[];
};
Init[];
END.