BluejaySmartsImpl.mesa
Last modified by D. Swinehart, December 28, 1983 10:14 pm
DIRECTORY
BluejaySmarts,
IO,
Jukebox  USING [ bytesPerChirp, CloseTune, CreateTune, Handle, OpenTune, Tune, TuneSize ],
Lark   USING [ noMachine, VoiceSocket ],
Nice,
Process  USING [ Detach, MsecToTicks, Pause, SecondsToTicks, SetTimeout ],
PupDefs  USING [ MsToTocks, PupSocket, PupSocketDestroy, PupSocketMake ],
Rope,
Log   USING [ Problem, Report ],
ThParty  USING [ Advance, DescribeParty, SetInterval ],
Thrush  USING [ ConvEvent, ConversationHandle, Disposition, IntervalSpec, NB, newTune, nullConvHandle, nullHandle, nullTune, PartyHandle, SHHH, Reason, SmartsHandle, StateInConv, Tune ],
ThSmarts,
ThSmartsPrivate USING [ ConvDesc, ConvDescBody, OpenConversations ],
VoiceStream USING [ AddPiece, Close, FlushPieces, Handle, IsEmpty, NotifyProc, Open, SetSocket, wholeTune ]
;
BluejaySmartsImpl: CEDAR MONITOR
IMPORTS IO, Jukebox, Nice, Process, PupDefs, Log, Rope, ThParty, VoiceStream
EXPORTS BluejaySmarts, ThSmarts = {
OPEN BluejaySmarts, IO;
Types, Definitions
ConvDesc: TYPE = ThSmartsPrivate.ConvDesc;
ConversationHandle: TYPE = Thrush.ConversationHandle;
nullConvHandle: ConversationHandle = Thrush.nullConvHandle;
Disposition: TYPE = Thrush.Disposition;
PartyHandle: TYPE = Thrush.PartyHandle;
SHHH: TYPE = Thrush.SHHH;
SmartsHandle: TYPE = Thrush.SmartsHandle;
StateInConv: TYPE = Thrush.StateInConv;
jayShh: PUBLIC SHHH;
interfaceIsImported: PUBLIC BOOLEANFALSE;
encryptionRequested: PUBLIC BOOLEANTRUE;
handle: PUBLIC Jukebox.Handle;
debug: PUBLIC BOOLEANTRUE;
pERROR: ERROR=CODE;
NB: TYPE = Thrush.NB;
noActiveSocket: Lark.VoiceSocket = [
net: [Lark.noMachine.net], host: [Lark.noMachine.host], socket: [0,0]];
infos: PUBLIC ARRAY[0..100) OF JayInfo←ALL[NIL];
smartses: PUBLIC ARRAY[0..100) OF Thrush.SmartsHandle ← ALL[Thrush.nullHandle];
numParties: PUBLIC NAT𡤀
haveJuke: PUBLIC BOOLTRUE;
timeoutNoAction: INTEGER ← 30;
Ctr: TYPE = RECORD[
count: INT,
stop: INT
];
PD: TYPE = RECORD [
cPr: REF Ctr←NEW[Ctr←[0,1000000]],
cSp: REF Ctr←NEW[Ctr←[0,1000000]],
cRs: REF Ctr←NEW[Ctr←[0,1000000]],
cZp: REF Ctr←NEW[Ctr←[0,1000000]],
cNw: REF Ctr←NEW[Ctr←[0,1000000]],
numReportDones: INT𡤀,
numReportDoneEs: INT𡤀,
doReports: BOOLFALSE,
doNice: BOOLFALSE
];
pd: REF PDNEW[PD←[]];
Report: PROC[what: ROPE, ctr: REF Ctr] = {
IF NOT pd.doReports THEN RETURN;
Log.Report[what, $Bluejay];
ctr.count𡤌tr.count+1;
IF ctr.count>ctr.stop THEN { Log.Problem["Report overflow", $Bluejay]; };
};
BeNice: PROC[r: REF, d: INT] = {
IF NOT pd.doNice THEN RETURN;
Nice.BeNice[r, d, $Bluejay, NIL];
};
Call Supervision
Progress: PUBLIC ENTRY PROC[
shh: SHHH,
smartsID: Thrush.SmartsHandle,
event: Thrush.ConvEvent,
yourParty: BOOL,
latestEvent: BOOL,
informationOnly: BOOL
] RETURNS [ d: Thrush.Disposition ] = {
info: JayInfo ← InfoForSmarts[smartsID: smartsID];
cDesc: ThSmartsPrivate.ConvDesc;
IF info=NIL THEN RETURN[pass];
d�tedAndStop;
Update local state information
cDesc ← GetConv[info, event.credentials.convID, FALSE];
IF pd.doReports THEN Report[
Rope.Concat[
IO.PutFR["---- JyProg: %t(%d) %g %g yr=%g ",
time[event.credentials.convID], int[event.credentials.stateID],
refAny[NEW[StateInConv𡤎vent.state]], DParty[info, cDesc], bool[yourParty]],
IO.PutFR["lt=%g in=%g, ky=%g\n",
bool[latestEvent], bool[event.intervalSpec#NIL], bool[event.keyTable#NIL]]],
pd.cPr];
IF event.credentials.stateID <= cDesc.cState.credentials.stateID THEN RETURN[pass]; -- Old news!
Fields always extracted
cDesc.cState.credentials.smartsID ← smartsID;
cDesc.cState.credentials.stateID ← event.credentials.stateID;
<<Need to check for increasing stateID here, and reject old ones? If so, enable
test in alrt case below.>>
Extracted if present
IF event.keyTable#NIL THEN {
cDesc.cState.keyTable ← event.keyTable; cDesc.newKeys←TRUE; };
IF event.intervalSpec#NIL AND event.intervalSpec.type=request THEN
EnqueueInterval[cDesc, event.intervalSpec];
IF event.address#NIL THEN { cDesc.cState.address𡤎vent.address; cDesc.newAddress←TRUE; };
Extracted if the event changes our state.
IF yourParty THEN {
cDesc.descValid ← TRUE;
cDesc.cState.credentials.partyID ← event.credentials.partyID;
cDesc.cState.state ← event.state;
cDesc.cState.comment ← event.comment;
cDesc.cState.reason ← event.reason;
IF event.spec#NIL THEN { cDesc.cState.spec ← event.spec; cDesc.newSpec←TRUE; };
Extracted if non-standard?
IF event.comment#NIL THEN cDesc.cState.comment ← event.comment;
IF event.urgency#normal THEN cDesc.cState.urgency ← event.urgency;
IF event.alertKind#standard THEN cDesc.cState.alertKind ← event.alertKind;
};
BeNice[event, 4];
BeNice[cDesc, 6];
IF latestEvent THEN Apprise[info]; -- Wait for the last report to wake process.
};
Supervise: PUBLIC ENTRY PROC[info: JayInfo ] = {
TRUSTED {Process.SetTimeout[@info.thAction, Process.SecondsToTicks[timeoutNoAction]]; };
IF info.apprise THEN DO
ENABLE {
UNWIND => NULL;
ANY => { Log.Problem[NIL, $Bluejay]; GOTO Failing; };
};
prevL: ThSmartsPrivate.OpenConversations ← NIL;
nb: NB←success;
convL: ThSmartsPrivate.OpenConversations;
trans: Transition;
info.apprise ← FALSE;
FOR convL ← info.conversations,
convL.rest WHILE convL#NIL DO
cDesc: ConvDesc = convL.first;
stateNow: StateInConv = cDesc.cState.state;
ours: BOOL ← ( cDesc.cState.credentials.convID = info.currentConvID );
IF pd.doReports THEN Report[
Rope.Concat[
IO.PutFR["**** JySup: %t(%d) %g %g->%g",
time[cDesc.cState.credentials.convID], int[cDesc.cState.credentials.stateID],
DParty[info, cDesc], refAny[NEW[StateInConv←stateNow]], refAny[NEW[StateInConv�sc.desiredState]]],
IO.PutFR[" %g%g\n",
refAny[NEW[Transition←transForStates[stateNow][cDesc.desiredState]]],
rope[IF ours THEN " (ours)" ELSE ""]]],
pd.cSp];
BeNice[cDesc, 6];
IF NOT cDesc.descValid THEN LOOP;
IF info.currentConvID = nullConvHandle AND stateNow#idle THEN {
<<This stuff feels like it's in the wrong place. Will feel more so when multi-calls
are dealt with. >>
ours←TRUE; info.currentConvID ← cDesc.cState.credentials.convID;
};
IF stateNow#idle AND (NOT ours) THEN
Conv isn't the one we're interested in, and doesn't look like it's going idle: idle it.
Should probably consult transForStates for validity.
nb ← ThParty.Advance[
shhh: info.shh,
credentials: cDesc.cState.credentials,
state: idle,
reason: busy,
comment: "One conversation at a time, please."
]
ELSE {
State and substate (below) and desired state indicate there's something to do. Do it:
SELECT (trans←transForStates[stateNow][cDesc.desiredState]) FROM
noop => NULL;
elim => {
Conv. is now idle: forget about it or re-use it, as a reserved conversation.
Remove dead conversation from list.
Report[IO.PutFR[" ** Zap: %t\n", time[cDesc.cState.credentials.convID]], pd.cZp];
IF prevL#NIL THEN { prevL.rest ← convL.rest; convL ← prevL; }
ELSE info.conversations𡤌onvL.rest;
IF ours THEN {
info.currentConvID ← nullConvHandle;
CloseConnection[info];
};
};
idle => nb ← AdvanceToDesired[info, cDesc];
actv => {
IF cDesc.signallingStarted THEN LOOP;
cDesc.signallingStarted ← TRUE;
cDesc.desiredState ← active;
nb ← AdvanceToDesired[info, cDesc];
};
nrvl => { -- report new interval accepted
IF cDesc.newSpec THEN OpenConnection[info, cDesc];
IF cDesc.newIntervals#NIL THEN {
IF DoSetInterval[info, cDesc] THEN
nb ← ThParty.SetInterval[
shhh: info.shh,
credentials: cDesc.cState.credentials,
intervalSpec: cDesc.newIntervals.first
];
IF nb=success THEN []�queueInterval[cDesc];
info.apprise ← TRUE; -- one more time, in case more intervals exist.
};
};
invl, ntiy => {
Log.Problem["LarkSmarts: Invalid or not yet implemented state transition request.", $Bluejay];
info.apprise←TRUE;
cDesc.desiredState ← idle;
cDesc.desiredReason ← error;
cDesc.desiredComment ← "Invalid state transition";
};
ENDCASE => pERROR;
};
Analyze any results of trying to reach a different state.
IF nb#success THEN
Report[IO.PutFR[" ** Results: nb=%g\n", refAny[NEW[Thrush.NB←nb]]], pd.cRs];
SELECT nb FROM
success, stateMismatch => NULL;
partyNotEnabled => ours←FALSE;
invalidTransition, convNotActive, convStillActive => {
Complain and transition to idle
comment: ROPE="Party-level detected invalid state transition request";
Log.Problem[comment, $Bluejay];
cDesc.desiredState ← idle;
cDesc.cState.comment ← comment;
cDesc.cState.reason ← error;
info.apprise←TRUE;
};
notInConv, noSuchConv => { -- Complain, zap, go idle and repeat.
comment: ROPE="NotInConv or NoSuchConv";
Log.Problem[comment, $Bluejay];
IF ours THEN info.currentConvID ← nullConvHandle;
cDesc.cState.credentials.convID ← nullConvHandle;
cDesc.cState.credentials.stateID ← 0;
cDesc.desiredState ← idle;
cDesc.cState.comment ← comment;
cDesc.cState.reason ← error;
info.apprise←TRUE;
};
noSuchParty, noSuchSmarts => { -- Complain, deregister, we gone! Needs tuning.
Log.Problem[
"NoSuchParty or NoSuchSmarts reported, must try to go away", $Bluejay];
GOTO Failing;
};
noSuchParty2, narcissism => pERROR; -- shouldn't be provoking these
ENDCASE => pERROR;
prevL ← convL;
ENDLOOP;
IF NOT info.apprise THEN WAIT info.thAction;
IF info.conversations = NIL THEN EXIT;
REPEAT Failing => NULL; -- ThSmartsPrivate.Deregister[info]; now Failed
ENDLOOP;
info.thProcess ← NIL;
};
Utilities
GetConv: PUBLIC INTERNAL PROC[info: JayInfo, convID: ConversationHandle, validIfNew: BOOL
] RETURNS [ cDesc: ConvDesc←NIL ] = --INLINE-- {
FOR convs: ThSmartsPrivate.OpenConversations ← info.conversations, convs.rest WHILE convs#NIL DO
IF convs.first.cState.credentials.convID = convID THEN RETURN[convs.first];
ENDLOOP;
cDesc ← NEW[ThSmartsPrivate.ConvDescBody←[]];
cDesc.descValid ← validIfNew;
cDesc.cState.credentials.convID ← convID;
cDesc.cState.credentials.smartsID ← info.smartsID;
cDesc.cState.credentials.partyID ← info.partyID;
info.conversations ← CONS[cDesc, info.conversations];
IF pd.doReports THEN
Report[IO.PutFR[" ** NewConv, %t %g, vl=%g\n", time[convID], DParty[info, cDesc], bool[validIfNew]], pd.cNw];
};
GetCDesc: PROC[info: JayInfo] RETURNS [ cDesc: ConvDesc←NIL ] = {
Not at present protected by monitor -- LarkStateImpl back-pointer problem.
convID: ConversationHandle=info.currentConvID;
IF convID=nullConvHandle THEN RETURN;
FOR convs: ThSmartsPrivate.OpenConversations ← info.conversations, convs.rest WHILE convs#NIL DO
IF convs.first.cState.credentials.convID = convID THEN {
cDesc ← convs.first; EXIT; };
ENDLOOP;
RETURN[IF cDesc#NIL AND cDesc.descValid THEN cDesc ELSE NIL];
};
EnqueueInterval: INTERNAL PROC[cDesc: ConvDesc, int: Thrush.IntervalSpec] = INLINE {
iL: LIST OF Thrush.IntervalSpec = LIST[int];
IF cDesc.newIntervals#NIL THEN cDesc.iTail.rest ← iL ELSE cDesc.newIntervals ← iL;
cDesc.iTail ← iL;
};
DequeueInterval: INTERNAL PROC[cDesc: ConvDesc] RETURNS [ int: Thrush.IntervalSpec←NIL ] = INLINE {
IF cDesc.newIntervals=NIL THEN RETURN;
int�sc.newIntervals.first;
cDesc.newIntervals ← cDesc.newIntervals.rest;
};
GetSIC: PUBLIC INTERNAL PROC[info: JayInfo] RETURNS [ state: StateInConv ] = {
cDesc: ConvDesc = GetCDesc[info];
RETURN[IF cDesc=NIL THEN idle ELSE cDesc.cState.state];
};
Apprise: PUBLIC INTERNAL PROC[info: JayInfo] = TRUSTED --INLINE-- {
IF info.thProcess=NIL THEN
Process.Detach[info.thProcess ← FORK Supervise[info]];
info.apprise ← TRUE;
NOTIFY info.thAction;
};
AdvanceToDesired: INTERNAL PROC[info: JayInfo, cDesc: ConvDesc] RETURNS [nb: NB] = {
RETURN[nb ← ThParty.Advance[
shhh: info.shh,
credentials: cDesc.cState.credentials,
state: cDesc.desiredState,
reason: cDesc.desiredReason,
comment: cDesc.desiredComment
]];
};
InfoForSmarts: INTERNAL PROC [ smartsID: SmartsHandle ] RETURNS [ info: JayInfo ] = {
FOR i: NAT IN [0..numParties) DO IF smartsID=smartses[i] THEN RETURN [ infos[i] ]; ENDLOOP;
RETURN[ NIL ]; };
OpenConnection: INTERNAL PROC[info: JayInfo, cDesc: ConvDesc] = TRUSTED {
IF NOT cDesc.newSpec THEN RETURN;
IF info.stream=NIL THEN info.stream ← VoiceStream.Open[jukebox: handle, proc: ReportDone, clientData: info];
Log.Report[IO.PutFR["C %d ", card[info.smartsID]], $Bluejay];
info.socket ← PupDefs.PupSocketMake[
local: cDesc.cState.spec.localSocket.socket,
remote: cDesc.cState.spec.remoteSocket,
ticks: PupDefs.MsToTocks[100]];
VoiceStream.SetSocket[socket: info.socket, handle: info.stream];
info.lastIntervalSpec ← NIL;
cDesc.newSpec ← FALSE;
};
CloseConnection: INTERNAL PROC[info: JayInfo] = TRUSTED {
IF info.stream#NIL THEN VoiceStream.Close[info.stream];
info.stream ← NIL;
IF info.socket#NIL THEN PupDefs.PupSocketDestroy[info.socket];
info.lastIntervalSpec ← NIL;
Log.Report[IO.PutFR["D %d ", card[info.smartsID]], $Bluejay];
};
Tunes: control of recording and playback
DoSetInterval: INTERNAL PROC[info: JayInfo, cDesc: ConvDesc]
RETURNS[didSomething: BOOL] = TRUSTED {
jTune: Jukebox.Tune;
intervalSpec: Thrush.IntervalSpec = cDesc.newIntervals.first;
tune: Thrush.Tune;
flush: BOOL;
IF intervalSpec=NIL THEN pERROR;
flush ← intervalSpec.interval.length=0 AND (NOT intervalSpec.queueIt);
tune ← intervalSpec.tune;
SELECT intervalSpec.type FROM
request => NULL; -- Let's go start it.
started => RETURN[TRUE]; -- was started before, but not successfully reported (mismatch?)
finished => RETURN[NOT flush]; -- report unless due to flushing (does this happen?)
ENDCASE => pERROR;
IF info.stream=NIL THEN info.stream ← VoiceStream.Open[jukebox: handle, proc: ReportDone, clientData: info];
IF flush THEN {
VoiceStream.FlushPieces[handle: info.stream];
RETURN[FALSE]; -- no report
};
IF tune = Thrush.newTune THEN
IF intervalSpec.direction = record THEN {
jTune ← Jukebox.CreateTune[handle, -1];
tune ← jTune.tuneId;
intervalSpec.tune←tune;
Jukebox.CloseTune[ handle, jTune ]; }
ELSE RETURN[TRUE]--<<??>>--;
IF intervalSpec.direction = play THEN Process.Pause[Process.MsecToTicks[100]];
VoiceStream.AddPiece[
handle: info.stream,
tuneId: tune,
firstByte: intervalSpec.interval.start,
nBytes: IF intervalSpec.interval.length=-1 THEN VoiceStream.wholeTune
ELSE intervalSpec.interval.length,
create: intervalSpec.direction = record,
playback: intervalSpec.direction # record,
keyIndex: intervalSpec.keyIndex,
flush: NOT intervalSpec.queueIt ];
intervalSpec.type ← started;
info.lastIntervalSpec ← intervalSpec;
RETURN[TRUE];
<< Perhaps should be able to specify, in request, whether started/finished notes are needed.>>
};
ReportDone: VoiceStream.NotifyProc = TRUSTED {
info: JayInfo ← NARROW[clientData];
pd.numReportDones ← pd.numReportDones+1;
IF ~VoiceStream.IsEmpty[self] OR info=NIL THEN RETURN;
Process.Detach[FORK ReportDoneEntry[info]];
};
ReportDoneEntry: ENTRY PROC[info: JayInfo] = TRUSTED {
cDesc: ConvDesc;
intervalSpec: Thrush.IntervalSpec;
tune: Thrush.Tune←Thrush.nullTune;
totLen, start: INT𡤀
jTune: Jukebox.Tune←NIL;
pd.numReportDoneEs ← pd.numReportDoneEs+1;
cDesc ← GetCDesc[info];
IF cDesc=NIL OR cDesc.cState.state#active THEN RETURN;
intervalSpec ← info.lastIntervalSpec;
IF intervalSpec = NIL THEN { Log.Problem["No interval spec.", $Bluejay]; RETURN; };
<<Need list of specs, else multiple requests can confuse this!!!!!>>
tune ← intervalSpec.tune;
IF tune>=0 THEN jTune ← Jukebox.OpenTune[handle, tune, FALSE];
IF jTune#NIL THEN {
totLen ← Jukebox.TuneSize[jTune]*Jukebox.bytesPerChirp;
Jukebox.CloseTune[handle, jTune];
start ← MIN[intervalSpec.interval.start, totLen];
intervalSpec.interval ← [start: start, length: MAX[totLen-start, 0]];
};
intervalSpec.type ← finished;
info.lastIntervalSpec ← NIL;
EnqueueInterval[cDesc, intervalSpec]; -- Notify client
Apprise[info];
};
DParty: PROC[info: JayInfo, cDesc: ConvDesc] RETURNS [IO.Value] = {
RETURN[rope[ThParty.DescribeParty[shh: info.shh, partyID: cDesc.cState.credentials.partyID]]];
};
State Transition Tables
Transition: TYPE = {
Just codes to dispatch on in Supervisor; explained there
noop, elim, idle, actv, nrvl, invl, ntiy
};
transForStates: ARRAY StateInConv OF ARRAY StateInConv OF Transition ← [
idle resrv pars init pend mayb ring canAc activ inact any -- desired
[ elim, invl, invl, invl, invl, invl, invl, invl, elim, ntiy, elim ], -- idle  (current)
[ idle, invl, invl, invl, invl, invl, invl, invl, invl, ntiy, invl ], -- reserved
[ idle, invl, invl, invl, invl, invl, invl, invl, invl, ntiy, invl ], -- parsing
[ idle, invl, invl, invl, invl, invl, invl, invl, invl, ntiy, invl ], -- initiating
[ idle, invl, invl, invl, invl, invl, invl, invl, actv, ntiy, actv ], -- pending
[ idle, invl, invl, invl, invl, invl, invl, invl, invl, ntiy, invl ], -- maybe
[ idle, invl, invl, invl, invl, invl, invl, invl, invl, ntiy, invl ], -- ringing
[ idle, invl, invl, invl, invl, invl, invl, invl, ntiy, ntiy, ntiy ], -- canActivate
[ idle, invl, invl, invl, invl, invl, invl, invl, nrvl, ntiy, nrvl ], -- active
[ idle, invl, invl, invl, invl, invl, invl, invl, ntiy, ntiy, noop ], -- inactive
[ invl, invl, invl, invl, invl, invl, invl, invl, invl, invl, invl ] -- any (nonex)
];
NB: examine relationship to validity table in PartyOpsImpl someday.
Debugging nonsense
Nice.View[pd, "Bluejay PD"];
}.