DIRECTORY
Atom, CedarProcess, IO, Process, Rope, SlackProcess, TIPUser, Terminal, TerminalDefs, Interminal, ViewerClasses, ViewerOps;

SlackProcessImpl: CEDAR MONITOR LOCKS handle USING handle: SlackHandle
IMPORTS Atom, CedarProcess, IO, Process, Terminal, Interminal, ViewerOps 
EXPORTS SlackProcess = BEGIN


AbortData: TYPE = SlackProcess.AbortData;
AbortProc: TYPE = SlackProcess.AbortProc;
Actions: TYPE = SlackProcess.Actions;
ActionsData: TYPE = SlackProcess.ActionsData;
BashedData: TYPE = SlackProcess.BashedData;
ClientDatas: TYPE = SlackProcess.ClientDatas;
ClientDatasData: TYPE = SlackProcess.ClientDatasData;
EventProc: TYPE = SlackProcess.EventProc;
EventProcs: TYPE = SlackProcess.EventProcs;
EventProcsData: TYPE = SlackProcess.EventProcsData;
EventsData: TYPE = SlackProcess.EventsData;
Log: TYPE = SlackProcess.Log;
LogData: TYPE = SlackProcess.LogData;
LoggingProc: TYPE = SlackProcess.LoggingProc;
MouseEventProc: TYPE = SlackProcess.MouseEventProc;
MouseProcs: TYPE = SlackProcess.MouseProcs;
MouseProcsData: TYPE = SlackProcess.MouseProcsData;
OptimizeProc: TYPE = SlackProcess.OptimizeProc;
OptimizeHints: TYPE = REF OptimizeHintsData;
OptimizeHintsData: TYPE = SlackProcess.OptimizeHintsData;
Point: TYPE = SlackProcess.Point; -- RECORD [x, y: REAL]
Points: TYPE = SlackProcess.Points;
PointsData: TYPE = SlackProcess.PointsData;
Queue: TYPE = SlackProcess.Queue;
QueueData: TYPE = SlackProcess.QueueData;
QueueEntryGenerator: TYPE = REF QueueEntryGeneratorObj;
QueueEntryGeneratorObj: TYPE = SlackProcess.QueueEntryGeneratorObj;
ReceivedData: TYPE = SlackProcess.ReceivedData;
Viewer: TYPE = ViewerClasses.Viewer;

SlackData: PUBLIC TYPE = SlackProcess.SlackData; -- this is the monitored record type
SlackHandle: TYPE = REF SlackData;
Problem: PUBLIC ERROR[msg: Rope.ROPE] = CODE;

NoOptimization: OptimizeProc = { -- do all of the actions on the queue
skipActions _ 0;
};

Create: PUBLIC 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] = {
handle _ NEW[SlackData _ [slackProcess: NIL, queue: NEW[QueueData _ [] ], qeGen: NEW[QueueEntryGeneratorObj], log: NEW[LogData _ [] ], abort: NEW[AbortData _ [] ] ] ];
handle.queue.clientDatas _ NEW[ClientDatasData[queueSize]];
handle.queue.optimizeHints _ NEW[OptimizeHintsData[queueSize]];
handle.queue.actions _ NEW[ActionsData[queueSize]];
handle.queue.points _ NEW[PointsData[queueSize]];
handle.queue.mouseProcs _ NEW[MouseProcsData[queueSize]];
handle.queue.eventProcs _ NEW[EventProcsData[queueSize]];
handle.queue.size _ queueSize;

IF optimizeProc = NIL THEN handle.optimizeProc _ NoOptimization
ELSE handle.optimizeProc _ optimizeProc;

handle.log.received _ NEW[ReceivedData[logSize]];
handle.log.bashed _ NEW[BashedData[logSize]];
handle.log.events _ NEW[EventsData[logSize]];
handle.log.points _ NEW[PointsData[logSize]];
handle.log.size _ logSize;
handle.log.logger _ loggingProc;

handle.abort^ _ [enabled: FALSE, viewer: abortViewer, proc: abortProc, data: abortData];
};

QueueInputActionWithPoint: PUBLIC ENTRY PROC [handle: SlackHandle, callBack: MouseEventProc, inputAction: LIST OF REF ANY, point: Point, clientData: REF ANY, optimizeHint: REF ANY] = TRUSTED {
ENABLE UNWIND => NULL;
queue: Queue _ handle.queue;
qIndex: NAT _ 0;
WHILE (queue.tail+1) MOD queue.size = queue.head DO WAIT queue.notFull ENDLOOP;
qIndex _ queue.tail;
queue.clientDatas[qIndex] _ clientData;
queue.optimizeHints[qIndex] _ optimizeHint;
queue.actions[qIndex] _ inputAction;
queue.points[qIndex] _ point;
queue.mouseProcs[qIndex] _ callBack;
queue.eventProcs[qIndex] _ NIL;
queue.tail _ (qIndex + 1) MOD queue.size;
LogReceived[log: handle.log, event: inputAction.first, point: point, bash: FALSE];
IF handle.slackProcess = NIL THEN Process.Detach[handle.slackProcess _ FORK Actor[handle]];
};

QueueInputAction: PUBLIC ENTRY PROC [handle: SlackHandle, callBack: EventProc, inputAction: LIST OF REF ANY, clientData: REF ANY, optimizeHint: REF ANY] = TRUSTED {
ENABLE UNWIND => NULL;
queue: Queue _ handle.queue;
qIndex: NAT _ 0;
WHILE (queue.tail+1) MOD queue.size = queue.head DO WAIT queue.notFull ENDLOOP;
qIndex _ queue.tail;
queue.clientDatas[qIndex] _ clientData;
queue.optimizeHints[qIndex] _ optimizeHint;
queue.actions[qIndex] _ inputAction;
queue.points[qIndex] _ [-9999.0, -9999.0];
queue.mouseProcs[qIndex] _ NIL;
queue.eventProcs[qIndex] _ callBack;
queue.tail _ (qIndex + 1) MOD queue.size;
LogReceived[log: handle.log, event: inputAction.first, point: [-9999.0, -9999.0], bash: FALSE];
IF handle.slackProcess = NIL THEN Process.Detach[handle.slackProcess _ FORK Actor[handle]];
};

Restart: PUBLIC ENTRY PROC [handle: SlackHandle] = TRUSTED  {
ENABLE UNWIND => NULL;
Process.Detach[handle.slackProcess _ FORK Actor[handle] ];
};

FlushQueue: PUBLIC ENTRY PROC [handle: SlackHandle] = {
ENABLE UNWIND => NULL;
FlushQueueInternal[handle];
};

FlushQueueInternal: INTERNAL PROC [handle: SlackHandle] = {
queueSize: NAT _ handle.queue.size;
FOR i: NAT IN [0..queueSize-1] DO
handle.queue.clientDatas[i] _ NIL;
handle.queue.optimizeHints[i] _ NIL;
handle.queue.actions[i] _ NIL;
handle.queue.points[i] _ [0,0];
handle.queue.mouseProcs[i] _ NIL;
handle.queue.eventProcs[i] _ NIL;
ENDLOOP;
handle.queue.head _ 0;
handle.queue.tail _ 0;
BROADCAST handle.queue.notFull;
};

PeekActionTail: INTERNAL PROC [queue: Queue] RETURNS [whatToDo: ATOM] = {
IF queue.head = queue.tail THEN RETURN[NIL];
whatToDo _ NARROW[queue.actions[(queue.tail - 1 + queue.size) MOD queue.size].first, ATOM];
};

Actor: PROC [handle: SlackHandle] = { -- runs in autonomous process(es) to service queue(s)
IF handle.abort.enabled THEN [] _ CedarProcess.Fork[Killer, handle, [excited, TRUE]];
DoActorsJob[handle ! UNWIND => NilIt[handle]];
NilIt[handle];
};

NilIt: PRIVATE ENTRY PROC [handle: SlackHandle] = TRUSTED {handle.slackProcess _ NIL};

Killer: PROC [data: REF] RETURNS [results: REF _ NIL] = {
handle: SlackHandle _ NARROW[data];
UNTIL handle.slackProcess = NIL DO
[] _ MaybeKill[handle];
Process.Pause[ticks: Process.SecondsToTicks[1]];
ENDLOOP;
};

MaybeKill: PRIVATE ENTRY PROC [handle: SlackHandle] RETURNS [killed: BOOL _ FALSE]= {
ClientWantsKill: PROC RETURNS [killIt: BOOL _ FALSE] = {
mouse: Interminal.MousePosition;
tsc: TIPUser.TIPScreenCoords _ NEW[TIPUser.TIPScreenCoordsRec];
vt: Terminal.Virtual _ Terminal.Current[];
keyBits: Terminal.KeyBits _ Terminal.GetKeys[vt: vt];
killBits: BOOL _ (keyBits[LeftShift]=down OR keyBits[RightShift]=down) AND keyBits[TerminalDefs.Spare3]=down;
IF  killBits THEN {
IF handle.abort.viewer=NIL THEN RETURN[TRUE]; -- viewer does not matter
mouse _ Interminal.GetMousePosition[];
tsc^ _ [
mouseX: mouse.mouseX,
mouseY: (IF mouse.color THEN vt.colorHeight ELSE vt.bwHeight) - mouse.mouseY,
color: mouse.color
];
RETURN[ViewerOps.MouseInViewer[tsc: tsc].viewer=handle.abort.viewer];
};
};

IF handle.slackProcess#NIL AND ClientWantsKill[] THEN TRUSTED {
Process.Abort[handle.slackProcess]; -- kill the actor slack process
handle.slackProcess _ NIL;
FlushQueueInternal[handle]; -- you have to do this HERE holding the handle lock!
IF handle.abort.proc#NIL THEN handle.abort.proc[handle.abort.data]; -- notify the user that abort happened. Hold the lock.
RETURN[TRUE];
};
};

DoActorsJob: PROC [handle: SlackHandle] = { -- autonomous process(es) to service queue(s)
inputAction: LIST OF REF ANY;
point: Point;
clientData: REF ANY;
mouseEventProc: MouseEventProc;
eventProc: EventProc;
[mouseEventProc, eventProc, inputAction, point, clientData] _ NextAction[handle];
WHILE inputAction # NIL DO
IF mouseEventProc = NIL THEN eventProc[inputAction, clientData]
ELSE mouseEventProc[inputAction, point, clientData];
[mouseEventProc, eventProc, inputAction, point, clientData] _ NextAction[handle];
ENDLOOP;
};

EmptyQ: INTERNAL PROC [queue: Queue] RETURNS [BOOL] = {
RETURN[queue.tail = queue.head];
};
NextAction: ENTRY PROC [handle: SlackHandle] RETURNS [mouseEventProc: MouseEventProc, eventProc: EventProc, inputAction: LIST OF REF ANY, point: Point, clientData: REF ANY] = {
queue: Queue _ handle.queue;
success: BOOL _ TRUE;
errorMsg: Rope.ROPE;
skipActions: NAT;
qeGen: QueueEntryGenerator;
qeGen _ PrepareGenerator[handle];
skipActions _ handle.optimizeProc[qeGen, qeGen.actionsInQueue
! Problem => {success _ FALSE; errorMsg _ msg; CONTINUE}];
IF NOT success THEN RETURN WITH ERROR Problem[msg: errorMsg];
[mouseEventProc, eventProc, inputAction, point, clientData] _ DeQueueAction[handle, skipActions];
IF inputAction = NIL THEN {
handle.slackProcess _ NIL; -- no mouse action.  Let process die. Caller will terminate.
};
BROADCAST queue.notFull;
};

QueueSize: INTERNAL PROC [queue: Queue] RETURNS [count: NAT] = {
count _ (queue.tail - queue.head + queue.size) MOD queue.size;
};

PrepareGenerator: INTERNAL PROC [handle: SlackHandle] RETURNS [qeGen: QueueEntryGenerator] = {
qeGen _ handle.qeGen;
qeGen.queue _ handle.queue;
qeGen.actionsInQueue _ QueueSize[handle.queue]
};

GetQueueEntry: PUBLIC PROC [qeGen: QueueEntryGenerator, index: NAT] RETURNS [clientData: REF ANY, optimizeHint: REF ANY, action: LIST OF REF ANY, point: Point] = {
queue: Queue _ qeGen.queue;
head: NAT _ queue.head;
realIndex: NAT;
IF index + 1 >  qeGen.actionsInQueue THEN ERROR Problem[msg: "GetQueueEntry called with index too high"];
realIndex _ (head + index) MOD queue.size;
clientData _ queue.clientDatas[realIndex];
optimizeHint _ queue.optimizeHints[realIndex];
action _ queue.actions[realIndex];
point _ queue.points[realIndex];
};

DeQueueAction: INTERNAL PROC [handle: SlackHandle, skipActions: NAT] RETURNS [mouseEventProc: MouseEventProc, eventProc: EventProc, inputAction: LIST OF REF ANY, point: Point, clientData: REF ANY] = {
queue: Queue _ handle.queue;
queueSize, where: NAT;

queueSize _ QueueSize[handle.queue];
IF skipActions > queueSize THEN ERROR;
IF skipActions = queueSize THEN RETURN[NIL, NIL, NIL, [0,0], NIL]; -- there is nothing left to do
queue.head _ (queue.head + skipActions) MOD queue.size;
where _ queue.head;
mouseEventProc _ queue.mouseProcs[where];
eventProc _ queue.eventProcs[where];
inputAction _ queue.actions[where];
point _ queue.points[where];
clientData _ queue.clientDatas[where];
queue.head _ (queue.head + 1) MOD queue.size;
LogActed[handle.log, inputAction, point, mouseEventProc#NIL, clientData];
};

LogRawMouse: PUBLIC ENTRY PROC [handle: SlackHandle, point: Point] = {
ENABLE UNWIND => NULL;
log: Log _ handle.log;
where: NAT _ log.head;
log.events[where] _ $RawMouse;
log.points[where] _ point;
log.head _ (log.head + 1) MOD log.size;
};

LogReceived: INTERNAL PROC [log: Log, event: REF ANY, point: Point, bash: BOOL] = {
where: NAT _ log.head;
log.received[where] _ TRUE;
log.bashed[where] _ bash;
log.events[where] _ event;
log.points[where] _ point;
log.head _ (log.head + 1) MOD log.size;
};

LogActed: INTERNAL PROC [log: Log, action: LIST OF REF ANY, point: Point, mouseEvent: BOOL, clientData: REF ANY] = {
where: NAT _ log.head;
log.received[where] _ FALSE;
log.bashed[where] _ FALSE;
log.events[where] _ action.first;
log.points[where] _ point;
log.head _ (log.head + 1) MOD log.size;
IF log.loggerEnabled THEN log.logger[point, action, mouseEvent, clientData];
};

debugLog: BOOL _ FALSE;
OutputLog: PUBLIC ENTRY PROC [handle: SlackHandle, stream: IO.STREAM] = {
ENABLE UNWIND => NULL;
log: Log _ handle.log;
i: NAT _ (log.head - 1 + log.size) MOD log.size; -- back up pointer one slot
IF debugLog THEN stream.PutF["\nLog Head = %g", IO.int[log.head] ];
stream.PutF["\nMost Recent Event: "];
WHILE i#log.head DO
IF log.events[i] = $RawMouse THEN
stream.PutF["\nRAW MOUSE [%g, %g]",
[real[log.points[i].x]],
[real[log.points[i].y]] ]
ELSE IF log.bashed[i] THEN
IF ISTYPE[log.events[i], ATOM] THEN
stream.PutF["\nBashed %g at [%g, %g]",
[rope[Atom.GetPName[NARROW[log.events[i]]]]],
[real[log.points[i].x]],
[real[log.points[i].y]] ]
ELSE stream.PutF["\nBashed %g at [%g, %g]",
[character[NARROW[log.events[i], REF CHAR]^]],
[real[log.points[i].x]],
[real[log.points[i].y]] ]
ELSE IF log.received[i] THEN
IF ISTYPE[log.events[i], ATOM] THEN
stream.PutF["\nReceived %g at [%g, %g]",
[rope[Atom.GetPName[NARROW[log.events[i]]]]],
[real[log.points[i].x]],
[real[log.points[i].y]] ]
ELSE stream.PutF["\nReceived %g at [%g, %g]",
[character[NARROW[log.events[i], REF CHAR]^]],
[real[log.points[i].x]],
[real[log.points[i].y]] ]
ELSE
IF ISTYPE[log.events[i], ATOM] THEN
stream.PutF["\nActed on %g at [%g, %g]",
[rope[Atom.GetPName[NARROW[log.events[i]]]]],
[real[log.points[i].x]],
[real[log.points[i].y]] ]
ELSE stream.PutF["\nActed on %g at [%g, %g]",
[character[NARROW[log.events[i], REF CHAR]^]],
[real[log.points[i].x]],
[real[log.points[i].y]] ];
i _ (i - 1 + log.size) MOD log.size;
ENDLOOP;
stream.PutF["\nLeast Recent Event.\n"];
};

EnableSessionLogging: PUBLIC ENTRY PROC [handle: SlackHandle] RETURNS [alreadyEnabled: BOOL _ FALSE] = {
ENABLE UNWIND => NULL;
IF handle.log.loggerEnabled THEN alreadyEnabled _  TRUE;
handle.log.loggerEnabled _ TRUE;
};

DisableSessionLogging: PUBLIC ENTRY PROC [handle: SlackHandle] RETURNS [alreadyDisabled: BOOL _ FALSE] = {
ENABLE UNWIND => NULL;
IF NOT handle.log.loggerEnabled THEN alreadyDisabled _  TRUE;
handle.log.loggerEnabled _ FALSE;
};

RegisterLogger: PUBLIC ENTRY PROC [handle: SlackHandle, loggingProc: LoggingProc] RETURNS [alreadyRegistered: BOOL _ FALSE] = {
ENABLE UNWIND => NULL;
IF handle.log.logger#NIL THEN alreadyRegistered _ TRUE;
handle.log.logger _ loggingProc;
};

EnableAborts: PUBLIC ENTRY PROC [handle: SlackHandle] RETURNS [alreadyEnabled: BOOL _ FALSE] = {
ENABLE UNWIND => NULL;
IF handle.abort.enabled THEN alreadyEnabled _  TRUE;
handle.abort.enabled _ TRUE;
};

DisableAborts: PUBLIC ENTRY PROC [handle: SlackHandle] RETURNS [alreadyDisabled: BOOL _ FALSE] = {
ENABLE UNWIND => NULL;
IF NOT handle.abort.enabled THEN alreadyDisabled _  TRUE;
handle.abort.enabled _ FALSE;
};

RegisterAbortProc: PUBLIC ENTRY PROC [handle: SlackHandle, abortProc: AbortProc _ NIL, abortData: REF ANY _ NIL, abortViewer: ViewerClasses.Viewer _ NIL] RETURNS [alreadyRegistered: BOOL _ FALSE] = {
ENABLE UNWIND => NULL;
IF handle.abort.proc#NIL THEN alreadyRegistered _ TRUE;
handle.abort.proc _ abortProc;
handle.abort.data _ abortData;
handle.abort.viewer _ abortViewer;
};

END.
����SlackProcessImpl.mesa
Copyright c 1986 by Xerox Corporation.  All rights reserved.
Last edited by Pier on November 5, 1986 7:05:50 pm PST
Bier, February 9, 1987 6:43:54 pm PST

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 CODE

Creating a SlackProcess
Queuing Actions
Add an action to the tail of the slack process queue.  Process.Detach is an unsafe operation.
Add an action to the slack processes queue.  Process.Detach is an unsafe operation.
Restarting a SlackProcess that has been ABORTed (e.g. from an ERROR window).
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.
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.
Utilities
May FORK a process that looks to see if SHIFT-SWAT is down and aborts Actor if it is.
IF MaybeKill[] THEN handle.abort.proc[handle.abortData]; -- WHERE TO DO THIS ??

PrepareSummary: INTERNAL PROC [handle: SlackHandle] = {
Take all of the events on the queue, in order, and place them on the QueueSummary, starting at location 0.
queueCount: NAT _ QueueSize[handle];
sizeQueue: NAT _ handle.queue.size;
head: NAT _ handle.queue.head;
index: NAT;
FOR i: NAT IN [0..queueCount) DO
index _ (head + i) MOD sizeQueue;
handle.summary.clientDatas[i] _ handle.queue.clientDatas[index];
handle.summary.optimizeHints[i] _ handle.queue.optimizeHints[index];
handle.summary.actions[i] _ handle.queue.actions[index];
handle.summary.points[i] _ handle.queue.points[index];
ENDLOOP;
handle.summary.count _ queueCount;
};

SlackProcess keeps a ring buffer of recent input actions (called the log) for debugging purposes.
Output the "LogSize-1" most recent events to the stream. The most recent event is output first.
SlackProcess supports reporting of all actions dequeued from the slack queue, which can be recorded and played back by the client to recreate the session.  The client must provide a logging procedure to be called whenever an action is dequeued for the application.  The registered LoggingProc will be called at every dequeue of an event.

Creating 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.
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.
�ÊD��˜�code™Kšœ
Ïmœ1™<Kšœ6™6K™%K™�—šÏk	˜	Kšœžœžœe˜{—K˜�š
Ðbnœžœžœžœžœ˜FKšžœžœ+˜IKšžœž˜—˜�IbodyšÏbœR œ 
œÒ 	œ±™L™ÃLšœO œ.™…LšœÈÏnœ¡œ™öLšœâ™âLšœÆ™ÆK™�K˜�—š 
™
K™�Kšœžœ˜)Kšœžœ˜)Kšœ	žœ˜%Kšœ
žœ˜-Kšœžœ˜+Kšœ
žœ˜-Kšœžœ ˜5Kšœžœ˜)Kšœžœ˜+Kšœžœ˜3Kšœžœ˜+Kšœžœ˜Kšœ	žœ˜%Kšœ
žœ˜-Kšœžœ˜3Kšœžœ˜+Kšœžœ˜3Kšœžœ˜/Kšœžœžœ˜,Kšœžœ"˜9KšœžœÏc˜8Kšœžœ˜#Kšœžœ˜+Kšœžœ˜!Kšœžœ˜)Kšœžœžœ˜7Kšœžœ'˜CKšœžœ˜/Kšœžœ˜$K˜�Kšœžœžœ¢$˜UKšœ
žœžœ˜"Kš
¡œžœžœžœžœ˜-K˜�—š¡œ¢%˜FKšœ˜K˜K˜�—K™š¡œžœžœ
žœžœ>žœžœ
žœžœžœ&žœžœ˜øKš
œ	žœžœ	žœžœžœžœ˜§Kšœžœ˜;Kšœžœ˜?Kšœžœ˜3Kšœžœ˜1Kšœžœ˜9Kšœžœ˜9Kšœ˜K˜�Kšžœžœžœ%˜?Kšžœ$˜(K˜�Kšœžœ˜1Kšœžœ˜-Kšœžœ˜-Kšœžœ˜-Kšœ˜Kšœ ˜ K˜�Kšœžœ9˜XKšœ˜—K˜�K™š¡œžœžœžœ>žœžœžœžœžœžœžœžœžœ˜ÀK™]Kšžœžœžœ˜K˜Kšœžœ˜Kš
žœžœžœžœžœ˜OKšœ˜Kšœ'˜'Kšœ+˜+Kšœ$˜$Kšœ˜Kšœ$˜$Kšœžœ˜Kšœžœ˜)KšœKžœ˜RKšžœžœžœ&žœ˜[K˜K˜�—šŸœžœžœžœ9žœžœžœžœžœžœžœžœžœ˜¤K™SKšžœžœžœ˜K˜Kšœžœ˜Kš
žœžœžœžœžœ˜OKšœ˜Kšœ'˜'Kšœ+˜+Kšœ$˜$Kšœ*˜*Kšœžœ˜Kšœ$˜$Kšœžœ˜)KšœXžœ˜_Kšžœžœžœ&žœ˜[K˜K˜�—K™Lš
¡œžœžœžœžœ˜=K™™Kšžœžœžœ˜Kšœ%žœ˜:K˜K˜�—K™$š¡
œžœžœžœ˜7Kšžœžœžœ˜Kšœ˜K˜K˜�—š¡œžœžœ˜;K™µKšœžœ˜#šžœžœžœž˜!Kšœžœ˜"Kšœ žœ˜$Kšœžœ˜Kšœ˜Kšœžœ˜!Kšœžœ˜!Kšžœ˜—Kšœ˜Kšœ˜Kšž	œ˜K˜—K˜�K™	š
¡œžœžœžœžœ˜IKšžœžœžœžœ˜,Kšœžœ-žœžœ˜[K˜K˜�—š¡œžœ¢5˜[K™UKšžœžœ2žœ˜UKšœžœ˜.Kšœ˜Kšœ˜K˜�—š
¡œÐbk
 £œžœžœ˜VK˜�—š¡œžœžœžœžœžœ˜9Kšœžœ˜#šžœžœž˜"Kšžœ
žœ&¢™OKš¡œ	˜K˜0Kšžœ˜—Kšœ˜K˜�—š¡	œ£
 £œžœ	žœžœ˜Uš
¡œžœžœ
žœžœ˜8K˜ Kšœžœ˜?K•StartOfExpansion[]˜*K˜5Kšœ
žœžœžœ#˜mšžœžœ˜Kšžœžœžœžœžœ¢˜GK˜&šœ˜K˜Kšœ	žœ
žœžœ˜MK˜Kšœ˜—K–[ViewerClasses.Viewer]šžœ?˜EKšœ˜—K˜K˜�—š
žœžœžœžœžœ˜?Kšœ$¢˜CKšœžœ˜Kš œ
¢4˜PKšžœžœžœ'¢6˜zKšžœžœ˜
Kšœ˜—Kšœ˜K˜�—š¡œžœ¢-˜YKš	œ
žœžœžœžœ˜Kšœ
˜
Kšœžœžœ˜Kšœ˜Kšœ˜KšœQ˜Qšžœžœž˜Kšžœžœžœ#˜?Kšžœ0˜4KšœQ˜QKšžœ˜—Kšœ˜K˜�—š
¡œžœžœžœžœ˜7Kšžœ˜ K˜K™�—š¡
œžœžœžœEžœžœžœžœžœžœ˜°K˜Kšœ	žœžœ˜Kšœžœ˜Kšœ
žœ˜Kšœ˜Kšœ!˜!šœ=˜=Kšœžœžœ˜:—Kš
žœžœžœžœžœ˜=Kšœa˜ašžœžœžœ˜Kšœžœ¢<˜WK˜—Kšž	œ˜Kšœ˜K˜�—š
¡	œžœžœžœ	žœ˜@Kšœ/žœ˜>K˜K˜�—š¡œžœžœ™7K™jKšœžœ™$Kšœžœ™#Kšœžœ™Kšœžœ™šžœžœžœž™ Kšœžœ™!Kšœ@™@KšœD™DKšœ8™8Kšœ6™6Kšžœ™—Kšœ"™"K™K™�—š¡œžœžœžœ!˜^Kšœ˜Kšœ˜Kšœ.˜.K˜K˜�—š¡
œžœžœ%žœžœžœžœžœžœ
žœžœžœžœ˜£Kšœ˜Kšœžœ˜Kšœžœ˜Kšžœ#žœžœ:˜iKšœžœ˜*Kšœ*˜*Kšœ.˜.Kšœ"˜"Kšœ ˜ K˜—K˜�š¡
œžœžœ$žœžœEžœžœžœžœžœžœ˜ÈKšœ˜Kšœžœ˜K˜�Kšœ$˜$Kšžœžœžœ˜&Kšžœžœžœžœžœžœ	žœ¢˜aKšœ(žœ˜7Kšœ˜Kšœ)˜)Kšœ$˜$Kšœ#˜#Kšœ˜Kšœ&˜&Kšœžœ˜-Kšœ8žœ˜IK˜—K˜�K™aš¡œžœžœžœ(˜FJšžœžœžœ˜K˜Kšœžœ˜Kšœ˜Kšœ˜Kšœžœ
˜'K˜K˜�—š¡œžœžœžœžœžœ˜SKšœžœ˜Kšœžœ˜Kšœ˜Kšœ˜Kšœ˜Kšœžœ
˜'K˜K˜�—š¡œžœžœžœžœžœžœžœžœžœ˜tKšœžœ˜Kšœžœ˜Kšœžœ˜Kšœ!˜!Kšœ˜Kšœžœ
˜'Kšžœžœ3˜LK˜K˜�—Kšœ
žœžœ˜š¡	œžœžœžœžœžœ˜IJšžœžœžœ˜K˜Kšœžœžœ¢˜LKšœ_™_Kšžœ
žœ žœ˜CKšœ%˜%šžœž˜šžœž˜!šœ#˜#Kšœ˜Kšœ˜——šžœžœž˜šžœžœžœž˜#šœ&˜&Kšœžœ˜-Kšœ˜Kšœ˜——šžœ'˜+Kšœžœžœžœ˜.Kšœ˜Kšœ˜——šžœžœž˜šžœžœžœž˜#šœ(˜(Kšœžœ˜-Kšœ˜Kšœ˜——šžœ)˜-Kšœžœžœžœ˜.Kšœ˜Kšœ˜——šž˜šžœžœžœž˜#šœ(˜(Kšœžœ˜-Kšœ˜Kšœ˜——šžœ)˜-Kšœžœžœžœ˜.Kšœ˜Kšœ˜——Kšœžœ
˜$Kšžœ˜—Kšœ'˜'K˜—˜�K™ÑK™�—K™š¡œžœžœžœžœžœžœ˜hJšžœžœžœ˜Kšžœžœžœ˜8Kšœžœ˜ K˜K˜�—š¡œžœžœžœžœžœ˜jJšžœžœžœ˜Kšžœžœžœžœ˜=Kšœžœ˜!K˜K˜�—š¡œžœžœžœ1žœžœžœ˜Kšœ[™[Jšžœžœžœ˜Kšžœžœžœžœ˜7Kšœ ˜ K˜—K˜�Kšœ}™}š
¡œžœžœžœžœ˜`Jšžœžœžœ˜Kšžœžœžœ˜4Kšœžœ˜Kšœ˜K˜�—š
¡
œžœžœžœžœ˜bJšžœžœžœ˜Kšžœžœžœžœ˜9Kšœžœ˜Kšœ˜K˜�—š¡œžœ.žœ
žœžœžœ&žœžœžœžœ˜ÇKšœÙ™ÙJšžœžœžœ˜Kšžœžœžœžœ˜7Kšœ˜Kšœ˜Kšœ"˜"K˜—K˜�Kšžœ˜—�…—����8&��^H��