add subscription types
This commit is contained in:
+36
-9
@@ -41,12 +41,15 @@ export interface Interpreter_T<C> {
|
|||||||
machine: Machine_T<C>;
|
machine: Machine_T<C>;
|
||||||
state: string;
|
state: string;
|
||||||
context: C;
|
context: C;
|
||||||
|
peers: Record<string, Interpreter_T<unknown> | Array<Interpreter_T<unknown>>>;
|
||||||
|
peerSubscriptionIds: Map<Interpreter_T<unknown>,string>;
|
||||||
eventQueue:Array<Event_T>;
|
eventQueue:Array<Event_T>;
|
||||||
subscriptions: Record<string, SubscriptionCallbackFunction_T<C>>;
|
subscriptionsToEvents: Record<string, EventsSubscriptionCallbackFunction_T<C>>; // called upon every event
|
||||||
|
subscriptionsToState: Record<string, StateSubscriptionCallbackFunction_T<C>>; // every time state changes, even if it's transient
|
||||||
|
subscriptionsToSettledState: Record<string, SettledStateSubscriptionCallbackFunction_T<C>>; // only called when tick settles
|
||||||
isTransitioning: boolean;
|
isTransitioning: boolean;
|
||||||
isPaused: boolean;
|
isPaused: boolean;
|
||||||
start: ()=>Interpreter_T<C>;
|
start: ()=>Interpreter_T<C>;
|
||||||
subscribe: (callback:SubscriptionCallbackFunction_T<C>)=>Interpreter_T<C>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -61,9 +64,8 @@ export interface Interpreter_T<C> {
|
|||||||
export function Interpreter<C>(machine:Machine_T<C>, initialContext:any, initialStateName?:string) : Interpreter_T<C>{
|
export function Interpreter<C>(machine:Machine_T<C>, initialContext:any, initialStateName?:string) : Interpreter_T<C>{
|
||||||
if(typeof initialStateName === 'undefined'){ initialStateName = machine.states[0].name; }
|
if(typeof initialStateName === 'undefined'){ initialStateName = machine.states[0].name; }
|
||||||
//@ts-expect-error
|
//@ts-expect-error
|
||||||
const interpreter : Interpreter_T<C> = {machine, state: initialStateName, context:initialContext, eventQueue:[], isTransitioning:false, subscriptions: {}, isPaused: true};
|
const interpreter : Interpreter_T<C> = {machine, state: initialStateName, context:initialContext, eventQueue:[], isTransitioning:false, subscriptionsToEvents: {}, subscriptionsToState: {}, subscriptionsToSettledState: {}, isPaused: true};
|
||||||
interpreter.start = ()=>{ start(interpreter); return interpreter; }
|
interpreter.start = ()=>{ start(interpreter); return interpreter; }
|
||||||
interpreter.subscribe = (callback)=>{ subscribe(interpreter,callback); return interpreter; }
|
|
||||||
send(interpreter, ['entry', null] );
|
send(interpreter, ['entry', null] );
|
||||||
return interpreter;
|
return interpreter;
|
||||||
}
|
}
|
||||||
@@ -113,7 +115,7 @@ function processEvents<C>(interpreter:Interpreter_T<C>){
|
|||||||
}
|
}
|
||||||
interpreter.isTransitioning = false;
|
interpreter.isTransitioning = false;
|
||||||
// only run subscriptions here, once the machine's state has settled:
|
// only run subscriptions here, once the machine's state has settled:
|
||||||
Object.values(interpreter.subscriptions).forEach((subscriptionCallbackFunction)=>{ subscriptionCallbackFunction(interpreter); });
|
Object.values(interpreter.subscriptionsToSettledState).forEach((callbackFunction)=>{ callbackFunction(interpreter); });
|
||||||
}
|
}
|
||||||
function processNextEvent<C>(interpreter:Interpreter_T<C>){
|
function processNextEvent<C>(interpreter:Interpreter_T<C>){
|
||||||
const event = interpreter.eventQueue.shift();
|
const event = interpreter.eventQueue.shift();
|
||||||
@@ -130,6 +132,8 @@ function processNextEvent<C>(interpreter:Interpreter_T<C>){
|
|||||||
contextMutations.forEach((contextMutation)=>{
|
contextMutations.forEach((contextMutation)=>{
|
||||||
interpreter.context = contextMutation.fn(interpreter.context, event, interpreter);
|
interpreter.context = contextMutation.fn(interpreter.context, event, interpreter);
|
||||||
});
|
});
|
||||||
|
// run subscription-to-events callbacks (can be in parallel), since an event just happened:
|
||||||
|
Object.values(interpreter.subscriptionsToEvents).forEach((callbackFunction)=>{ callbackFunction(event, interpreter); });
|
||||||
// can process sideEffects in parallel (though we currently don't due to the overhead of doing so in Node.js):
|
// can process sideEffects in parallel (though we currently don't due to the overhead of doing so in Node.js):
|
||||||
// they're processed *after* the context changes, since that's what most sideEffects would be interested in; but nevertheless the original context is passed in case this sideEffect needs it:
|
// they're processed *after* the context changes, since that's what most sideEffects would be interested in; but nevertheless the original context is passed in case this sideEffect needs it:
|
||||||
sideEffects.forEach((sideEffect)=>{
|
sideEffects.forEach((sideEffect)=>{
|
||||||
@@ -139,6 +143,8 @@ function processNextEvent<C>(interpreter:Interpreter_T<C>){
|
|||||||
if(goto_ !== null){
|
if(goto_ !== null){
|
||||||
send(interpreter, ['exit', null]);
|
send(interpreter, ['exit', null]);
|
||||||
interpreter.state = goto_.targetStateName;
|
interpreter.state = goto_.targetStateName;
|
||||||
|
// run subscription-to-state callbacks (can be in parallel), since state just changed, possibly transiently (depends on whether the loop in `processEvents()` runs again):
|
||||||
|
Object.values(interpreter.subscriptionsToState).forEach((callbackFunction)=>{ callbackFunction(event, interpreter); });
|
||||||
send(interpreter, ['entry', null]);
|
send(interpreter, ['entry', null]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -162,17 +168,38 @@ function categorizeReactions<C>(reactions:Array<Reaction_T<C>>) : {sideEffects:A
|
|||||||
return {sideEffects, contextMutations, goto_};
|
return {sideEffects, contextMutations, goto_};
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SubscriptionCallbackFunction_T<C> = (self:Interpreter_T<C>)=>void;
|
export type EventsSubscriptionCallbackFunction_T<C> = (e:Event_T, self:Interpreter_T<C>)=>void;
|
||||||
|
export type StateSubscriptionCallbackFunction_T<C> = (e:Event_T, self:Interpreter_T<C>)=>void;
|
||||||
|
export type SettledStateSubscriptionCallbackFunction_T<C> = (self:Interpreter_T<C>)=>void; // we don't pass an event, because these only run once state settles, so a whole chain of events could have been responsible for that; it's unlikely a subscriber is interested only in the final one
|
||||||
|
// TODO: add subscribeToContext and subscribeToSettledContext functions, to get only changes to context, regardless of events happening or state changing
|
||||||
let subscriptionId : number = 0;
|
let subscriptionId : number = 0;
|
||||||
export function subscribe<C>(interpreter:Interpreter_T<C>, callback:SubscriptionCallbackFunction_T<C>){
|
export function subscribe<C>(interpreter:Interpreter_T<C>, callback:SettledStateSubscriptionCallbackFunction_T<C>){
|
||||||
subscriptionId++;
|
subscriptionId++;
|
||||||
interpreter.subscriptions[subscriptionId.toString()] = callback;
|
interpreter.subscriptionsToSettledState[subscriptionId.toString()] = callback;
|
||||||
|
return subscriptionId.toString();
|
||||||
|
}
|
||||||
|
export const subscribeToSettledState = subscribe;
|
||||||
|
export function subscribeToState<C>(interpreter:Interpreter_T<C>, callback:StateSubscriptionCallbackFunction_T<C>){
|
||||||
|
subscriptionId++;
|
||||||
|
interpreter.subscriptionsToState[subscriptionId.toString()] = callback;
|
||||||
|
return subscriptionId.toString();
|
||||||
|
}
|
||||||
|
export function subscribeToEvents<C>(interpreter:Interpreter_T<C>, callback:StateSubscriptionCallbackFunction_T<C>){
|
||||||
|
subscriptionId++;
|
||||||
|
interpreter.subscriptionsToEvents[subscriptionId.toString()] = callback;
|
||||||
return subscriptionId.toString();
|
return subscriptionId.toString();
|
||||||
}
|
}
|
||||||
export function unsubscribe<C>(interpreter:Interpreter_T<C>, subscriptionId:string){
|
export function unsubscribe<C>(interpreter:Interpreter_T<C>, subscriptionId:string){
|
||||||
delete interpreter.subscriptions[subscriptionId.toString()];
|
delete interpreter.subscriptionsToSettledState[subscriptionId.toString()];
|
||||||
|
delete interpreter.subscriptionsToState[subscriptionId.toString()];
|
||||||
|
delete interpreter.subscriptionsToEvents[subscriptionId.toString()];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function addPeer<C, C_Peer>(self:Interpreter_T<C>, peer:Interpreter_T<C_Peer>, name:string){
|
||||||
|
self.peers[name] = peer;
|
||||||
|
}
|
||||||
|
export function addPeers(){}
|
||||||
|
|
||||||
export const Spawn = function(){};
|
export const Spawn = function(){};
|
||||||
export const Unspawn = function(){};
|
export const Unspawn = function(){};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user