<> <> <> <<>> DIRECTORY Atom, CedarProcess, IO, MessageWindow, Process, Rope, SlackProcess, TIPUser, Terminal, Interminal, ViewerClasses, ViewerOps; SlackProcessImpl: CEDAR MONITOR LOCKS handle USING handle: SlackHandle IMPORTS Atom, CedarProcess, IO, MessageWindow, Process, Terminal, Interminal, ViewerOps EXPORTS SlackProcess = BEGIN <> <<>> <> <<>> Point: TYPE = SlackProcess.Point; -- RECORD [x, y: REAL] SlackHandle: TYPE = REF SlackData; SlackData: PUBLIC TYPE = SlackProcess.SlackData; -- this is the monitored record type EventProc: TYPE = SlackProcess.EventProc; MouseEventProc: TYPE = SlackProcess.MouseEventProc; LoggingProc: TYPE = SlackProcess.LoggingProc; AbortProc: TYPE = SlackProcess.AbortProc; AbortData: TYPE = SlackProcess.AbortData; Log: TYPE = SlackProcess.Log; LogData: TYPE = SlackProcess.LogData; Queue: TYPE = SlackProcess.Queue; QueueData: TYPE = SlackProcess.QueueData; ClientDatas: TYPE = SlackProcess.ClientDatas; ClientDatasData: TYPE = SlackProcess.ClientDatasData; Actions: TYPE = SlackProcess.Actions; ActionsData: TYPE = SlackProcess.ActionsData; Points: TYPE = SlackProcess.Points; PointsData: TYPE = SlackProcess.PointsData; MouseProcs: TYPE = SlackProcess.MouseProcs; MouseProcsData: TYPE = SlackProcess.MouseProcsData; EventProcs: TYPE = SlackProcess.EventProcs; EventProcsData: TYPE = SlackProcess.EventProcsData; ReceivedData: TYPE = SlackProcess.ReceivedData; BashedData: TYPE = SlackProcess.BashedData; EventsData: TYPE = SlackProcess.EventsData; Viewer: TYPE = ViewerClasses.Viewer; Create: PUBLIC PROC [queueSize: NAT _ 50, logSize: NAT _ 50, 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 _ [] ], log: NEW[LogData _ [] ], abort: NEW[AbortData _ [] ] ] ]; handle.queue.clientDatas _ NEW[ClientDatasData[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; 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]; }; FlushQueue: PUBLIC ENTRY PROC [handle: SlackHandle] = { 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.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; }; Restart: PUBLIC ENTRY PROC [handle: SlackHandle] = TRUSTED { <> Process.Detach[handle.slackProcess _ FORK Actor[handle] ]; }; QueueInputAction: PUBLIC ENTRY PROC [handle: SlackHandle, callBack: MouseEventProc, inputAction: LIST OF REF ANY, point: Point, clientData: REF ANY] = { <> InternalQueueInputAction[handle, callBack, inputAction, point, clientData]; }; InternalQueueInputAction: INTERNAL PROC [handle: SlackHandle, callBack: MouseEventProc, inputAction: LIST OF REF ANY, point: Point, clientData: REF ANY] = TRUSTED { <> 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.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]]; }; IsBashable: INTERNAL PROC [atom: ATOM, bashable: LIST OF ATOM] RETURNS [BOOL] = { FOR list: LIST OF ATOM _ bashable, list.rest UNTIL list = NIL DO IF atom = list.first THEN RETURN[TRUE]; REPEAT FINISHED => RETURN[FALSE]; ENDLOOP; }; QueueOrBashInputAction: PUBLIC ENTRY PROC [handle: SlackHandle, callBack: MouseEventProc, inputAction: LIST OF REF ANY, bashable: LIST OF ATOM, point: Point, clientData: REF ANY] = TRUSTED { <> queue: Queue _ handle.queue; prevAction: ATOM _ PeekActionTail[queue]; qIndex: NAT _ (queue.tail - 1 + queue.size) MOD queue.size; IF IsBashable[prevAction, bashable] THEN { -- bash queue.clientDatas[qIndex] _ clientData; queue.actions[qIndex] _ inputAction; queue.points[qIndex] _ point; queue.mouseProcs[qIndex] _ callBack; queue.eventProcs[qIndex] _ NIL; LogReceived[log: handle.log, event: inputAction.first, point: point, bash: TRUE]; IF handle.slackProcess = NIL THEN Process.Detach[handle.slackProcess _ FORK Actor[handle]]; } ELSE InternalQueueInputAction[handle, callBack, inputAction, point, clientData]; -- queue. }; QueueInputActionNoPoint: PUBLIC ENTRY PROC [handle: SlackHandle, callBack: EventProc, inputAction: LIST OF REF ANY, clientData: REF ANY] = TRUSTED { <> InternalQueueInputActionNoPoint[handle, callBack, inputAction, clientData]; }; InternalQueueInputActionNoPoint: INTERNAL PROC [handle: SlackHandle, callBack: EventProc, inputAction: LIST OF REF ANY, clientData: REF ANY] = TRUSTED { <> 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.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]]; }; QueueOrBashInputActionNoPoint: PUBLIC ENTRY PROC [handle: SlackHandle, callBack: EventProc, inputAction: LIST OF REF ANY, bashable: LIST OF ATOM, clientData: REF ANY] = TRUSTED { <> queue: Queue _ handle.queue; prevAction: ATOM _ PeekActionTail[queue]; qIndex: NAT _ (queue.tail - 1 + queue.size) MOD queue.size; IF IsBashable[prevAction, bashable] THEN { -- bash queue.clientDatas[qIndex] _ clientData; queue.actions[qIndex] _ inputAction; queue.points[qIndex] _ [-9999.0, -9999.0]; queue.mouseProcs[qIndex] _ NIL; queue.eventProcs[qIndex] _ callBack; LogReceived[log: handle.log, event: inputAction.first, point: [-9999.0, -9999.0], bash: TRUE]; IF handle.slackProcess = NIL THEN Process.Detach[handle.slackProcess _ FORK Actor[handle]]; } ELSE InternalQueueInputActionNoPoint[handle, callBack, inputAction, clientData]; -- queue }; EmptyQ: INTERNAL PROC [queue: Queue] RETURNS [BOOL] = { RETURN[queue.tail = queue.head]; }; <<>> PeekActionTail: PRIVATE 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; }; NextAction: ENTRY PROC [handle: SlackHandle] RETURNS [mouseEventProc: MouseEventProc, eventProc: EventProc, inputAction: LIST OF REF ANY, point: Point, clientData: REF ANY] = { queue: Queue _ handle.queue; IF EmptyQ[queue] THEN { handle.slackProcess _ NIL; -- no mouse action. Let process die. Caller will terminate. inputAction _ NIL; } ELSE { [mouseEventProc, eventProc, inputAction, point, clientData] _ DeQueueAction[handle]; BROADCAST queue.notFull; }; }; DeQueueAction: INTERNAL PROC [handle: SlackHandle] RETURNS [mouseEventProc: MouseEventProc, eventProc: EventProc, inputAction: LIST OF REF ANY, point: Point, clientData: REF ANY] = { queue: Queue _ handle.queue; where: NAT _ queue.head; IF queue.tail = queue.head THEN ERROR; 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] = { 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] = { 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] = { IF handle.log.loggerEnabled THEN MessageWindow.Append["Warning: EnableSessionLogging called while already Enabled", TRUE] ELSE handle.log.loggerEnabled _ TRUE; }; DisableSessionLogging: PUBLIC ENTRY PROC [handle: SlackHandle] = { IF NOT handle.log.loggerEnabled THEN MessageWindow.Append["Warning: DisableSessionLogging called while already Disabled", TRUE] ELSE handle.log.loggerEnabled _ FALSE; }; RegisterLogger: PUBLIC ENTRY PROC [handle: SlackHandle, loggingProc: LoggingProc] = { IF handle.log.logger#NIL THEN MessageWindow.Append["Warning: RegisterLogger called while already Registered", TRUE] ELSE handle.log.logger _ loggingProc; }; EnableAborts: PUBLIC ENTRY PROC [handle: SlackHandle] = { IF handle.abort.enabled THEN MessageWindow.Append["Warning: EnableAborts called while already Enabled", TRUE] ELSE handle.abort.enabled _ TRUE; }; DisableAborts: PUBLIC ENTRY PROC [handle: SlackHandle] = { IF NOT handle.abort.enabled THEN MessageWindow.Append["Warning: DisableAborts called while already Disabled", TRUE] ELSE handle.abort.enabled _ FALSE; }; RegisterAbortProc: PUBLIC ENTRY PROC [handle: SlackHandle, abortProc: AbortProc _ NIL, abortData: REF ANY _ NIL, abortViewer: ViewerClasses.Viewer _ NIL] = { <> IF handle.abort.proc#NIL THEN MessageWindow.Append["Warning: RegisterAbortProc called while already Registered", TRUE] ELSE { handle.abort.proc _ abortProc; handle.abort.data _ abortData; handle.abort.viewer _ abortViewer; }; }; END.