<> <> <> <> <<>> DIRECTORY Atom, CedarProcess, IO, Process, Rope, SlackProcess, TIPUser, Terminal, 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[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.