sync `send` works

main
Brian Sakal 2 years ago
parent 0495f1d87a
commit e328533629

2
dist/index.js vendored

@ -0,0 +1,2 @@
var d=function(...t){return{states:t}},y=function(t,...e){return{name:t,ons:e}},v=function(t,...e){return{eventName:t,reactions:e}},D=function(t){return{type:"Do",fn:t}},k=function(t){return{type:"Goto",targetStateName:t}},c=function(t){return{doFunctions:t}};function l(t,e){let{state:o,context:s}=e;return typeof o>"u"&&(o=t.states[0].name),{machine:t,state:o,context:s,tickQueues:[]}}function i(t){return t.machine.states.find(e=>e.name===t.state)}function _(t,e){return t.ons.filter(o=>o.eventName===e[0])}function C(){}function G(t,e){let o=i(t),r=_(o,e).map(n=>n.reactions).flat(),a=r.findIndex(n=>n.type==="Goto"),E=a===-1?r.length-1:a,S=r.slice(0,E+1).map(n=>n.type==="Do"?n.fn:n.type==="Goto"?(p,f,T)=>{T.state=n.targetStateName}:C);c(S).doFunctions.forEach(n=>{n(t.context,e,t)})}var m=function(){},A=function(){};export{D as Do,k as Goto,d as Machine,v as On,m as Spawn,y as State,A as Unspawn,l as interpret,G as send};
//# sourceMappingURL=index.js.map

7
dist/index.js.map vendored

@ -0,0 +1,7 @@
{
"version": 3,
"sources": ["../src/index.ts"],
"sourcesContent": ["type Event_T = [name:string, payload:any];\nexport interface Machine_T<S,E extends Event_T,C> {\n states: Array<State_T<S,E,C>>\n}\nexport interface State_T<S,E extends Event_T,C> {\n name: S;\n ons: Array<On_T<S,E,C>>;\n}\nexport interface On_T<S,E extends Event_T,C> {\n eventName: E[0];\n reactions: Array<Do_T<S,E,C> | Goto_T<S>>;\n};\nexport interface Do_T<S,E extends Event_T, C> {\n type: 'Do';\n fn: DoFn_T<S,E,C>;\n};\nexport type DoFn_T<S,E extends Event_T, C> = (ctx:C,e:E,self:Interpreter_T<S,E,C>)=>any;\nexport interface Goto_T<S> {\n type: 'Goto';\n targetStateName: S;\n};\n\nexport const Machine = function<S,E extends Event_T,C>(...states:Array<State_T<S,E,C>>) : Machine_T<S,E,C> { return {states}; };\nexport const State = function<S,E extends Event_T,C>(name:S, ...ons:Array<On_T<S,E,C>>) : State_T<S,E,C>{ return {name, ons}; };\nexport const On = function<S,E extends Event_T,C>(eventName:E[0], ...reactions:Array<Do_T<S,E,C> | Goto_T<S>>) : On_T<S,E,C>{ return {eventName, reactions}; };\nexport const Do = function<S,E extends Event_T, C>(fn:DoFn_T<S,E,C>) : Do_T<S,E,C>{ return {type:'Do', fn}; };\nexport const Goto = function<S>(targetStateName:S) : Goto_T<S> { return {type:'Goto', targetStateName} };\n\ninterface Tick_T<S,E extends Event_T, C> {\n doFunctions: Array<DoFn_T<S,E,C>>\n};\nconst Tick = function<S,E extends Event_T,C>(doFunctions : Array<DoFn_T<S,E,C>>) : Tick_T<S,E,C>{ return {doFunctions}; };\n\n\nexport interface Interpreter_T<S,E extends Event_T,C> {\n machine: Machine_T<S,E,C>;\n state: S;\n context: C;\n tickQueues:Array<TickQueue_T<S,E,C>>\n}\ntype TickQueue_T<S,E extends Event_T,C> = Array<Tick_T<S,E,C>>;\nexport function interpret<S,E extends Event_T,C>(machine:Machine_T<S,E,C>, options:{state?:S, context:C}) : Interpreter_T<S,E,C>{\n let {state, context} = options;\n if(typeof state === 'undefined'){ state = machine.states[0].name; }\n return {machine, state, context, tickQueues:[]};\n}\n\n/** Helper function for `send()`\n */\nfunction getState<S,E extends Event_T,C>(interpreter : Interpreter_T<S,E,C>) : State_T<S,E,C>{\n return interpreter.machine.states.find((state)=>state.name===interpreter.state) as unknown as State_T<S,E,C>;\n}\n/** Helper function for `send()`\n */\nfunction getOns<S,E extends Event_T,C>(state : State_T<S,E,C>, event:E) : Array<On_T<S,E,C>>{\n return state.ons.filter((on)=>on.eventName===event[0]);\n}\n/** Helper function for `send()`\n */\nfunction noop(){}\n/** Inject an Event into the Interpreter's \"tick queue\".\n * \n * An event can be signify something \"new\" happening, such that its reactions should run on the next Tick;\n * or it can signify a milestone \"within\" the current Tick, such that a Tick can be thought of as having \n * \"sub-Ticks\".\n * \n * This distinction is significant for proper ordering of reaction execution, and also for determining\n * whether to run a reaction at all. If an Event is received, and is specified to be applied on a past \n * Tick, it is discarded.\n */\nexport function send<S,E extends Event_T,C>(interpreter : Interpreter_T<S,E,C>, event:E){\n const state = getState(interpreter);\n const onsTree = getOns(state, event);\n const reactions = onsTree\n .map((on)=>on.reactions)\n .flat();\n const indexOfFirstGoto = reactions.findIndex((reaction)=>reaction.type==='Goto');\n const indexOfFinalReaction = indexOfFirstGoto === -1 ? reactions.length-1 : indexOfFirstGoto;\n const reactionsUntilFirstGoto = reactions.slice(0, indexOfFinalReaction+1);\n const functionsToRunInTick = reactionsUntilFirstGoto\n .map((reaction)=>{\n if(reaction.type === 'Do'){\n return reaction.fn;\n }\n else if(reaction.type === 'Goto'){\n return (ctx:C,e:E,self:Interpreter_T<S,E,C>)=>{\n self.state = reaction.targetStateName;\n };\n }\n else{\n return noop;\n }\n });\n const tick = Tick(functionsToRunInTick);\n tick.doFunctions.forEach((fn)=>{ fn(interpreter.context, event, interpreter); });\n}\n\nexport const Spawn = function(){};\nexport const Unspawn = function(){};"],
"mappings": "AAsBO,IAAMA,EAAU,YAAmCC,EAAiD,CAAE,MAAO,CAAC,OAAAA,CAAM,CAAG,EACjHC,EAAQ,SAAgCC,KAAWC,EAAwC,CAAE,MAAO,CAAC,KAAAD,EAAM,IAAAC,CAAG,CAAG,EACjHC,EAAK,SAAgCC,KAAmBC,EAAuD,CAAE,MAAO,CAAC,UAAAD,EAAW,UAAAC,CAAS,CAAG,EAChJC,EAAK,SAAiCC,EAA+B,CAAE,MAAO,CAAC,KAAK,KAAM,GAAAA,CAAE,CAAG,EAC/FC,EAAO,SAAYC,EAA+B,CAAE,MAAO,CAAC,KAAK,OAAQ,gBAAAA,CAAe,CAAE,EAKjGC,EAAO,SAAgCC,EAAmD,CAAE,MAAO,CAAC,YAAAA,CAAW,CAAG,EAUjH,SAASC,EAAiCC,EAA0BC,EAAqD,CAC9H,GAAI,CAAC,MAAAC,EAAO,QAAAC,CAAO,EAAIF,EACvB,OAAG,OAAOC,EAAU,MAAcA,EAAQF,EAAQ,OAAO,CAAC,EAAE,MACrD,CAAC,QAAAA,EAAS,MAAAE,EAAO,QAAAC,EAAS,WAAW,CAAC,CAAC,CAChD,CAIA,SAASC,EAAgCC,EAAoD,CAC3F,OAAOA,EAAY,QAAQ,OAAO,KAAMH,GAAQA,EAAM,OAAOG,EAAY,KAAK,CAChF,CAGA,SAASC,EAA8BJ,EAAwBK,EAA6B,CAC1F,OAAOL,EAAM,IAAI,OAAQM,GAAKA,EAAG,YAAYD,EAAM,CAAC,CAAC,CACvD,CAGA,SAASE,GAAM,CAAC,CAWT,SAASC,EAA4BL,EAAoCE,EAAQ,CACtF,IAAML,EAAQE,EAASC,CAAW,EAE5Bb,EADUc,EAAOJ,EAAOK,CAAK,EAEhC,IAAKC,GAAKA,EAAG,SAAS,EACtB,KAAK,EACFG,EAAmBnB,EAAU,UAAWoB,GAAWA,EAAS,OAAO,MAAM,EACzEC,EAAuBF,IAAqB,GAAKnB,EAAU,OAAO,EAAImB,EAEtEG,EAD0BtB,EAAU,MAAM,EAAGqB,EAAqB,CAAC,EAEtE,IAAKD,GACDA,EAAS,OAAS,KACZA,EAAS,GAEVA,EAAS,OAAS,OACjB,CAACG,EAAMC,EAAIC,IAA4B,CAC5CA,EAAK,MAAQL,EAAS,eACxB,EAGOH,CAEV,EACUZ,EAAKiB,CAAoB,EACjC,YAAY,QAASpB,GAAK,CAAEA,EAAGW,EAAY,QAASE,EAAOF,CAAW,CAAG,CAAC,CACjF,CAEO,IAAMa,EAAQ,UAAU,CAAC,EACnBC,EAAU,UAAU,CAAC",
"names": ["Machine", "states", "State", "name", "ons", "On", "eventName", "reactions", "Do", "fn", "Goto", "targetStateName", "Tick", "doFunctions", "interpret", "machine", "options", "state", "context", "getState", "interpreter", "getOns", "event", "on", "noop", "send", "indexOfFirstGoto", "reaction", "indexOfFinalReaction", "functionsToRunInTick", "ctx", "e", "self", "Spawn", "Unspawn"]
}

@ -0,0 +1 @@
(()=>{var x=function(...t){return{states:t}},i=function(t,...e){return{name:t,ons:e}},o=function(t,...e){return{eventName:t,reactions:e}},s=function(t){return{type:"Do",fn:t}},S=function(t){return{type:"Goto",targetStateName:t}},l=function(t){return{doFunctions:t}};function u(t,e){let{state:n,context:C}=e;typeof n>"u"&&(n=t.states[0].name);let E={machine:t,state:n,context:C,tickQueues:[]};return c(E,["entry",null]),E}function m(t){return t.machine.states.find(e=>e.name===t.state)}function v(t,e){return t.ons.filter(n=>n.eventName===e[0])}function h(){}function c(t,e){let n=m(t),E=v(n,e).map(r=>r.reactions).flat(),_=E.findIndex(r=>r.type==="Goto"),f=_===-1?E.length-1:_,y=E.slice(0,f+1).map(r=>r.type==="Do"?r.fn:r.type==="Goto"?(k,d,p)=>{p.state=r.targetStateName,c(p,["entry",d])}:h);l(y).doFunctions.forEach(r=>{r(t.context,e,t)})}var a=(t,e,n)=>{setTimeout(()=>{c(n,["timer-finished",null])},800)},T=(t,e,n)=>{console.log(n.state)},D=x(i("green",o("entry",s(a),s(T)),o("timer-finished",S("yellow"))),i("yellow",o("entry",s(a),s(T)),o("timer-finished",S("red"))),i("red",o("entry",s(a),s(T)),o("timer-finished",S("green")))),M=u(D,{context:{}});})();

@ -10,7 +10,7 @@
},
"scripts": {
"build": "esbuild --bundle --format=esm --minify --sourcemap --outdir=dist src/index.ts",
"build-tests": "esbuild --bundle --minify --outdir=dist/tests tests/*.ts"
"build-tests": "esbuild --bundle --minify --outdir=dist/tests src/tests/*.ts"
},
"devDependencies": {
"esbuild": "^0.17.18"

@ -1,4 +1,4 @@
type Event_T = [name:string, payload:any];
type Event_T = [name:string, payload:any] | ['entry', any];
export interface Machine_T<S,E extends Event_T,C> {
states: Array<State_T<S,E,C>>
}
@ -8,13 +8,13 @@ export interface State_T<S,E extends Event_T,C> {
}
export interface On_T<S,E extends Event_T,C> {
eventName: E[0];
reactions: Array<Do_T<E,C> | Goto_T<S>>;
reactions: Array<Do_T<S,E,C> | Goto_T<S>>;
};
export interface Do_T<E extends Event_T, C> {
export interface Do_T<S,E extends Event_T, C> {
type: 'Do';
fn: DoFn_T<E,C>;
fn: DoFn_T<S,E,C>;
};
export type DoFn_T<E extends Event_T, C> = (ctx:C,e:E,self)=>any;
export type DoFn_T<S,E extends Event_T, C> = (ctx:C,e:E,self:Interpreter_T<S,E,C>)=>any;
export interface Goto_T<S> {
type: 'Goto';
targetStateName: S;
@ -22,26 +22,30 @@ export interface Goto_T<S> {
export const Machine = function<S,E extends Event_T,C>(...states:Array<State_T<S,E,C>>) : Machine_T<S,E,C> { return {states}; };
export const State = function<S,E extends Event_T,C>(name:S, ...ons:Array<On_T<S,E,C>>) : State_T<S,E,C>{ return {name, ons}; };
export const On = function<S,E extends Event_T,C>(eventName:E[0], ...reactions:Array<Do_T<E,C> | Goto_T<S>>) : On_T<S,E,C>{ return {eventName, reactions}; };
export const Do = function<E extends Event_T, C>(fn:DoFn_T<E,C>) : Do_T<E,C>{ return {type:'Do', fn}; };
export const On = function<S,E extends Event_T,C>(eventName:E[0], ...reactions:Array<Do_T<S,E,C> | Goto_T<S>>) : On_T<S,E,C>{ return {eventName, reactions}; };
export const Do = function<S,E extends Event_T, C>(fn:DoFn_T<S,E,C>) : Do_T<S,E,C>{ return {type:'Do', fn}; };
export const Goto = function<S>(targetStateName:S) : Goto_T<S> { return {type:'Goto', targetStateName} };
interface Tick_T<E extends Event_T, C> {
doFunctions: Array<DoFn_T<E,C>>
interface Tick_T<S,E extends Event_T, C> {
doFunctions: Array<DoFn_T<S,E,C>>
};
const Tick = function<E extends Event_T,C>(doFunctions : Array<DoFn_T<E,C>>) : Tick_T<E,C>{ return {doFunctions}; };
const Tick = function<S,E extends Event_T,C>(doFunctions : Array<DoFn_T<S,E,C>>) : Tick_T<S,E,C>{ return {doFunctions}; };
export interface Interpreter_T<S,E extends Event_T,C> {
machine: Machine_T<S,E,C>;
state: S;
context: C | {};
context: C;
tickQueues:Array<TickQueue_T<S,E,C>>
}
export function interpret<S,E extends Event_T,C>(machine:Machine_T<S,E,C>, options?:{state?:S, context?:C | {}}) : Interpreter_T<S,E,C>{
let {state, context} = options || {};
type TickQueue_T<S,E extends Event_T,C> = Array<Tick_T<S,E,C>>;
export function interpret<S,E extends Event_T,C>(machine:Machine_T<S,E,C>, options:{state?:S, context:C}) : Interpreter_T<S,E,C>{
let {state, context} = options;
if(typeof state === 'undefined'){ state = machine.states[0].name; }
if(typeof context === 'undefined'){ context = {}; }
return {machine, state, context};
const interpreter = {machine, state, context, tickQueues:[]}
//@ts-ignore
send(interpreter, ['entry', null] );
return interpreter;
}
/** Helper function for `send()`
@ -54,6 +58,9 @@ function getState<S,E extends Event_T,C>(interpreter : Interpreter_T<S,E,C>) : S
function getOns<S,E extends Event_T,C>(state : State_T<S,E,C>, event:E) : Array<On_T<S,E,C>>{
return state.ons.filter((on)=>on.eventName===event[0]);
}
/** Helper function for `send()`
*/
function noop(){}
/** Inject an Event into the Interpreter's "tick queue".
*
* An event can be signify something "new" happening, such that its reactions should run on the next Tick;
@ -66,22 +73,31 @@ function getOns<S,E extends Event_T,C>(state : State_T<S,E,C>, event:E) : Array<
*/
export function send<S,E extends Event_T,C>(interpreter : Interpreter_T<S,E,C>, event:E){
const state = getState(interpreter);
const ons = getOns(state, event);
const tick = Tick(ons
.map((on)=>on.reactions)
.flat()
.map((reaction)=>{
if(reaction.type === 'Do'){
return reaction.fn;
}
else if(reaction.type === 'Goto'){
return (ctx:C,e:E,self)=>{};
}
else{
return (ctx:C,e:E,self)=>{};
}
})
);
const onsTree = getOns(state, event);
const reactions = onsTree
.map((on)=>on.reactions)
.flat();
const indexOfFirstGoto = reactions.findIndex((reaction)=>reaction.type==='Goto');
const indexOfFinalReaction = indexOfFirstGoto === -1 ? reactions.length-1 : indexOfFirstGoto;
const reactionsUntilFirstGoto = reactions.slice(0, indexOfFinalReaction+1);
const functionsToRunInTick = reactionsUntilFirstGoto
.map((reaction)=>{
if(reaction.type === 'Do'){
return reaction.fn;
}
else if(reaction.type === 'Goto'){
return (ctx:C,e:E,self:Interpreter_T<S,E,C>)=>{
self.state = reaction.targetStateName;
//@ts-ignore
send(self, ['entry', e] );
};
}
else{
return noop;
}
});
const tick = Tick(functionsToRunInTick);
tick.doFunctions.forEach((fn)=>{ fn(interpreter.context, event, interpreter); });
}
export const Spawn = function(){};

@ -1,35 +1,41 @@
import { Machine, State, On, Do, Goto, Spawn, Unspawn } from '../index';
import { Machine, State, On, Do, Goto, Spawn, Unspawn, interpret, Interpreter_T, send } from '../index';
const beginTimer = (ctx:C, e:E)=>{};
const beginTimer = (ctx:C, e:E, self:Interpreter_T<S,E,C>)=>{ setTimeout(()=>{ send(self, ['timer-finished',null]); }, 800); };
const log = (ctx:C, e:E, self:Interpreter_T<S,E,C>)=>{ console.log(self.state); };
type S = 'green' | 'yellow' | 'red';
type E = ['entry',null] | ['timer-finished',null];
type C = null;
type C = {};
const machine =
Machine<S,E,C>(
State('green',
On('entry',
Do(beginTimer)
On<S,E,C>('entry',
Do(beginTimer),
Do(log)
),
On('timer-finished',
On<S,E,C>('timer-finished',
Goto('yellow')
)
),
State('yellow',
On('entry',
Do(beginTimer)
On<S,E,C>('entry',
Do(beginTimer),
Do(log)
),
On('timer-finished',
On<S,E,C>('timer-finished',
Goto('red')
)
),
State('red',
On('entry',
Do(beginTimer)
On<S,E,C>('entry',
Do(beginTimer),
Do(log)
),
On('timer-finished',
On<S,E,C>('timer-finished',
Goto('green')
)
),
);
);
const actor = interpret(machine, {context:{}});
Loading…
Cancel
Save