@ -41,12 +41,15 @@ export interface Interpreter_T<C> {
machine : Machine_T < C > ;
state : string ;
context : C ;
peers : Record < string , Interpreter_T < unknown > | Array < Interpreter_T < unknown > >> ;
peerSubscriptionIds : Map < Interpreter_T < unknown > , string > ;
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 ;
isPaused : boolean ;
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 > {
if ( typeof initialStateName === 'undefined' ) { initialStateName = machine . states [ 0 ] . name ; }
//@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 , subscriptions ToEvents: { } , subscriptionsToState : { } , subscriptionsToSettledState : { } , isPaused : true } ;
interpreter . start = ( ) = > { start ( interpreter ) ; return interpreter ; }
interpreter . subscribe = ( callback ) = > { subscribe ( interpreter , callback ) ; return interpreter ; }
send ( interpreter , [ 'entry' , null ] ) ;
return interpreter ;
}
@ -113,7 +115,7 @@ function processEvents<C>(interpreter:Interpreter_T<C>){
}
interpreter . isTransitioning = false ;
// only run subscriptions here, once the machine's state has settled:
Object . values ( interpreter . subscriptions ) . forEach ( ( subs criptionC allbackFunction) = > { subs criptionC allbackFunction( interpreter ) ; } ) ;
Object . values ( interpreter . subscriptions ToSettledState ) . forEach ( ( callbackFunction) = > { callbackFunction( interpreter ) ; } ) ;
}
function processNextEvent < C > ( interpreter :Interpreter_T < C > ) {
const event = interpreter . eventQueue . shift ( ) ;
@ -130,6 +132,8 @@ function processNextEvent<C>(interpreter:Interpreter_T<C>){
contextMutations . forEach ( ( contextMutation ) = > {
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):
// 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 ) = > {
@ -139,6 +143,8 @@ function processNextEvent<C>(interpreter:Interpreter_T<C>){
if ( goto_ !== null ) {
send ( interpreter , [ 'exit' , null ] ) ;
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 ] ) ;
}
}
@ -162,16 +168,37 @@ function categorizeReactions<C>(reactions:Array<Reaction_T<C>>) : {sideEffects:A
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 ;
export function subscribe < C > ( interpreter :Interpreter_T < C > , callback :SubscriptionCallbackFunction_T < C > ) {
export function subscribe < C > ( interpreter :Interpreter_T < C > , callback :S ettledStateS ubscriptionCallbackFunction_T< C > ) {
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 ( ) ;
}
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 Unspawn = function ( ) { } ;