fix order of execution
This commit is contained in:
Vendored
+21
-7
@@ -10,7 +10,7 @@ export interface EventReactionCouplings_T<C> {
|
|||||||
eventName: string;
|
eventName: string;
|
||||||
reactions: Array<Reaction_T<C>>;
|
reactions: Array<Reaction_T<C>>;
|
||||||
}
|
}
|
||||||
export type Reaction_T<C> = SideEffect_T<C> | ContextMutation_T<C> | Goto_T;
|
export type Reaction_T<C> = SideEffect_T<C> | ContextMutation_T<C> | Peering_T<C, unknown> | Goto_T;
|
||||||
export interface SideEffect_T<C> {
|
export interface SideEffect_T<C> {
|
||||||
type: 'SideEffect';
|
type: 'SideEffect';
|
||||||
fn: SideEffectFunction_T<C>;
|
fn: SideEffectFunction_T<C>;
|
||||||
@@ -21,6 +21,12 @@ export interface ContextMutation_T<C> {
|
|||||||
fn: ContextMutationFunction_T<C>;
|
fn: ContextMutationFunction_T<C>;
|
||||||
}
|
}
|
||||||
export type ContextMutationFunction_T<C> = (ctx: C, e: Event_T, self: Interpreter_T<C>) => C;
|
export type ContextMutationFunction_T<C> = (ctx: C, e: Event_T, self: Interpreter_T<C>) => C;
|
||||||
|
export interface Peering_T<C, C_Peer> {
|
||||||
|
type: 'Peering';
|
||||||
|
name: string;
|
||||||
|
peerCreationFunction: PeerCreationFunction_T<C, C_Peer>;
|
||||||
|
}
|
||||||
|
export type PeerCreationFunction_T<C, C_Peer> = (ctx: C, e: Event_T, self: Interpreter_T<C>) => Interpreter_T<C_Peer>;
|
||||||
export interface Goto_T {
|
export interface Goto_T {
|
||||||
type: 'Goto';
|
type: 'Goto';
|
||||||
targetStateName: string;
|
targetStateName: string;
|
||||||
@@ -31,16 +37,20 @@ export declare const On: <C>(eventName: string, ...reactions: Reaction_T<C>[]) =
|
|||||||
export declare const SideEffect: <C>(fn: SideEffectFunction_T<C>) => SideEffect_T<C>;
|
export declare const SideEffect: <C>(fn: SideEffectFunction_T<C>) => SideEffect_T<C>;
|
||||||
export declare const Goto: (targetStateName: string) => Goto_T;
|
export declare const Goto: (targetStateName: string) => Goto_T;
|
||||||
export declare const Context: <C>(fn: ContextMutationFunction_T<C>) => ContextMutation_T<C>;
|
export declare const Context: <C>(fn: ContextMutationFunction_T<C>) => ContextMutation_T<C>;
|
||||||
|
export declare const Peer: <C, C_Peer>(name: string, peerCreationFunction: PeerCreationFunction_T<C, C_Peer>) => Peering_T<C, C_Peer>;
|
||||||
export interface Interpreter_T<C> {
|
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>>;
|
||||||
|
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>>;
|
||||||
|
subscriptionsToState: Record<string, StateSubscriptionCallbackFunction_T<C>>;
|
||||||
|
subscriptionsToSettledState: Record<string, SettledStateSubscriptionCallbackFunction_T<C>>;
|
||||||
isTransitioning: boolean;
|
isTransitioning: boolean;
|
||||||
isPaused: boolean;
|
isPaused: boolean;
|
||||||
start: () => Interpreter_T<C>;
|
start: () => Interpreter_T<C>;
|
||||||
subscribe: (callback: SubscriptionCallbackFunction_T<C>) => Interpreter_T<C>;
|
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Description placeholder
|
* Description placeholder
|
||||||
@@ -66,8 +76,12 @@ export declare function pause<C>(interpreter: Interpreter_T<C>): void;
|
|||||||
*/
|
*/
|
||||||
export declare function send<C>(interpreter: Interpreter_T<C>, event: Event_T): void;
|
export declare function send<C>(interpreter: Interpreter_T<C>, event: Event_T): void;
|
||||||
export declare const enqueue: typeof send;
|
export declare const enqueue: typeof send;
|
||||||
export type SubscriptionCallbackFunction_T<C> = (self: Interpreter_T<C>) => void;
|
export type EventsSubscriptionCallbackFunction_T<C> = (e: Event_T, self: Interpreter_T<C>) => void;
|
||||||
export declare function subscribe<C>(interpreter: Interpreter_T<C>, callback: SubscriptionCallbackFunction_T<C>): string;
|
export type StateSubscriptionCallbackFunction_T<C> = (e: Event_T, self: Interpreter_T<C>) => void;
|
||||||
|
export type SettledStateSubscriptionCallbackFunction_T<C> = (self: Interpreter_T<C>) => void;
|
||||||
|
export declare function subscribe<C>(interpreter: Interpreter_T<C>, callback: SettledStateSubscriptionCallbackFunction_T<C>): string;
|
||||||
|
export declare const subscribeToSettledState: typeof subscribe;
|
||||||
|
export declare function subscribeToState<C>(interpreter: Interpreter_T<C>, callback: StateSubscriptionCallbackFunction_T<C>): string;
|
||||||
|
export declare function subscribeToEvents<C>(interpreter: Interpreter_T<C>, callback: StateSubscriptionCallbackFunction_T<C>): string;
|
||||||
export declare function unsubscribe<C>(interpreter: Interpreter_T<C>, subscriptionId: string): void;
|
export declare function unsubscribe<C>(interpreter: Interpreter_T<C>, subscriptionId: string): void;
|
||||||
export declare const Spawn: () => void;
|
export declare function addPeer<C, C_Peer>(self: Interpreter_T<C>, name: string, peer: Interpreter_T<C_Peer>): void;
|
||||||
export declare const Unspawn: () => void;
|
|
||||||
|
|||||||
Vendored
+1
-1
@@ -1,2 +1,2 @@
|
|||||||
var y=function(...t){return{states:t}},E=function(t,...e){return{name:t,eventReactionCouplings:e}},v=function(t,...e){return{eventName:t,reactions:e}},S=function(t){return{type:"SideEffect",fn:t}},b=function(t){return{type:"Goto",targetStateName:t}},M=function(t){return{type:"ContextMutation",fn:t}};function I(t,e,o){typeof o>"u"&&(o=t.states[0].name);let n={machine:t,state:o,context:e,eventQueue:[],isTransitioning:!1,subscriptions:{},isPaused:!0};return n.start=()=>(_(n),n),n.subscribe=i=>(d(n,i),n),a(n,["entry",null]),n}function _(t){t.isPaused===!0&&(t.isPaused=!1,u(t))}function h(t){t.isPaused===!1&&(t.isPaused=!0)}function p(t){return t.machine.states.find(e=>e.name===t.state)}function l(t,e){return t.eventReactionCouplings.filter(o=>o.eventName===e[0])}function a(t,e){t.eventQueue.push(e),t.isTransitioning===!1&&u(t)}var R=a;function u(t){for(t.isTransitioning=!0;t.eventQueue.length>0&&t.isPaused===!1;)x(t);t.isTransitioning=!1,Object.values(t.subscriptions).forEach(e=>{e(t)})}function x(t){let e=t.eventQueue.shift();if(typeof e<"u"){let o=p(t),i=l(o,e).map(s=>s.reactions).flat(),{sideEffects:C,contextMutations:f,goto_:c}=g(i),T=t.context;f.forEach(s=>{t.context=s.fn(t.context,e,t)}),C.forEach(s=>{s.fn(t.context,e,t,T)}),c!==null&&(a(t,["exit",null]),t.state=c.targetStateName,a(t,["entry",null]))}}function g(t){let e=[],o=[],n=null;return t.forEach(i=>{i.type==="SideEffect"?e.push(i):i.type==="ContextMutation"?o.push(i):i.type==="Goto"&&(n=i)}),{sideEffects:e,contextMutations:o,goto_:n}}var r=0;function d(t,e){return r++,t.subscriptions[r.toString()]=e,r.toString()}function A(t,e){delete t.subscriptions[e.toString()]}var F=function(){},m=function(){};export{M as Context,b as Goto,I as Interpreter,y as Machine,v as On,S as SideEffect,F as Spawn,E as State,m as Unspawn,R as enqueue,h as pause,a as send,_ as start,d as subscribe,A as unsubscribe};
|
var v=function(...t){return{states:t}},y=function(t,...e){return{name:t,eventReactionCouplings:e}},P=function(t,...e){return{eventName:t,reactions:e}},I=function(t){return{type:"SideEffect",fn:t}},M=function(t){return{type:"Goto",targetStateName:t}},h=function(t){return{type:"ContextMutation",fn:t}},F=function(t,e){return{type:"Peering",name:t,peerCreationFunction:e}};function R(t,e,n){typeof n>"u"&&(n=t.states[0].name);let o={machine:t,state:n,context:e,eventQueue:[],isTransitioning:!1,peers:{},peerSubscriptionIds:new Map,subscriptionsToEvents:{},subscriptionsToState:{},subscriptionsToSettledState:{},isPaused:!0};return o.start=()=>(f(o),o),c(o,["entry",null]),o}function f(t){t.isPaused===!0&&(t.isPaused=!1,C(t))}function k(t){t.isPaused===!1&&(t.isPaused=!0)}function l(t){return t.machine.states.find(e=>e.name===t.state)}function S(t,e){return t.eventReactionCouplings.filter(n=>n.eventName===e[0])}function c(t,e){t.eventQueue.push(e),t.isTransitioning===!1&&C(t)}var A=c;function C(t){for(t.isTransitioning=!0;t.eventQueue.length>0&&t.isPaused===!1;)g(t);t.isTransitioning=!1,Object.values(t.subscriptionsToSettledState).forEach(e=>{e(t)})}function g(t){let e=t.eventQueue.shift();if(typeof e<"u"){let n=l(t),a=S(n,e).map(i=>i.reactions).flat(),{sideEffects:r,contextMutations:T,peerings:p,goto_:u}=x(a),_=t.context;T.forEach(i=>{t.context=i.fn(t.context,e,t)}),u!==null&&(c(t,["exit",null]),t.state=u.targetStateName,Object.values(t.subscriptionsToState).forEach(i=>{i(e,t)}),c(t,["entry",null])),p.forEach(i=>{E(t,i.name,i.peerCreationFunction(t.context,e,t))}),Object.values(t.subscriptionsToEvents).forEach(i=>{i(e,t)}),r.forEach(i=>{i.fn(t.context,e,t,_)})}}function x(t){let e=[],n=[],o=[],a=null;return t.forEach(r=>{r.type==="SideEffect"?e.push(r):r.type==="ContextMutation"?n.push(r):r.type==="Peering"?o.push(r):r.type==="Goto"&&(a=r)}),{sideEffects:e,contextMutations:n,peerings:o,goto_:a}}var s=0;function d(t,e){return s++,t.subscriptionsToSettledState[s.toString()]=e,s.toString()}var m=d;function G(t,e){return s++,t.subscriptionsToState[s.toString()]=e,s.toString()}function b(t,e){return s++,t.subscriptionsToEvents[s.toString()]=e,s.toString()}function w(t,e){delete t.subscriptionsToSettledState[e.toString()],delete t.subscriptionsToState[e.toString()],delete t.subscriptionsToEvents[e.toString()]}function E(t,e,n){t.peers[e]=n,b(n,(o,a)=>{t.isTransitioning===!1&&c(t,[e+"."+o[0],o[1]])})}export{h as Context,M as Goto,R as Interpreter,v as Machine,P as On,F as Peer,I as SideEffect,y as State,E as addPeer,A as enqueue,k as pause,c as send,f as start,d as subscribe,b as subscribeToEvents,m as subscribeToSettledState,G as subscribeToState,w as unsubscribe};
|
||||||
//# sourceMappingURL=index.js.map
|
//# sourceMappingURL=index.js.map
|
||||||
|
|||||||
Vendored
+3
-3
File diff suppressed because one or more lines are too long
Vendored
+63
-21
@@ -15,15 +15,24 @@
|
|||||||
var Goto = function(targetStateName) {
|
var Goto = function(targetStateName) {
|
||||||
return { type: "Goto", targetStateName };
|
return { type: "Goto", targetStateName };
|
||||||
};
|
};
|
||||||
function interpret(machine2, options) {
|
function Interpreter(machine2, initialContext, initialStateName) {
|
||||||
let { state, context } = options;
|
if (typeof initialStateName === "undefined") {
|
||||||
if (typeof state === "undefined") {
|
initialStateName = machine2.states[0].name;
|
||||||
state = machine2.states[0].name;
|
|
||||||
}
|
}
|
||||||
const interpreter = { machine: machine2, state, context, eventQueue: [], isTransitioning: false };
|
const interpreter = { machine: machine2, state: initialStateName, context: initialContext, eventQueue: [], isTransitioning: false, peers: {}, peerSubscriptionIds: /* @__PURE__ */ new Map(), subscriptionsToEvents: {}, subscriptionsToState: {}, subscriptionsToSettledState: {}, isPaused: true };
|
||||||
|
interpreter.start = () => {
|
||||||
|
start(interpreter);
|
||||||
|
return interpreter;
|
||||||
|
};
|
||||||
send(interpreter, ["entry", null]);
|
send(interpreter, ["entry", null]);
|
||||||
return interpreter;
|
return interpreter;
|
||||||
}
|
}
|
||||||
|
function start(interpreter) {
|
||||||
|
if (interpreter.isPaused === true) {
|
||||||
|
interpreter.isPaused = false;
|
||||||
|
processEvents(interpreter);
|
||||||
|
}
|
||||||
|
}
|
||||||
function getState(interpreter) {
|
function getState(interpreter) {
|
||||||
return interpreter.machine.states.find((state) => state.name === interpreter.state);
|
return interpreter.machine.states.find((state) => state.name === interpreter.state);
|
||||||
}
|
}
|
||||||
@@ -33,44 +42,77 @@
|
|||||||
function send(interpreter, event) {
|
function send(interpreter, event) {
|
||||||
interpreter.eventQueue.push(event);
|
interpreter.eventQueue.push(event);
|
||||||
if (interpreter.isTransitioning === false) {
|
if (interpreter.isTransitioning === false) {
|
||||||
interpreter.isTransitioning = true;
|
processEvents(interpreter);
|
||||||
while (interpreter.eventQueue.length > 0) {
|
|
||||||
processNextEvent(interpreter);
|
|
||||||
}
|
|
||||||
interpreter.isTransitioning = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function processEvents(interpreter) {
|
||||||
|
interpreter.isTransitioning = true;
|
||||||
|
while (interpreter.eventQueue.length > 0 && interpreter.isPaused === false) {
|
||||||
|
processNextEvent(interpreter);
|
||||||
|
}
|
||||||
|
interpreter.isTransitioning = false;
|
||||||
|
Object.values(interpreter.subscriptionsToSettledState).forEach((callbackFunction) => {
|
||||||
|
callbackFunction(interpreter);
|
||||||
|
});
|
||||||
|
}
|
||||||
function processNextEvent(interpreter) {
|
function processNextEvent(interpreter) {
|
||||||
const nextEvent = interpreter.eventQueue.shift();
|
const event = interpreter.eventQueue.shift();
|
||||||
if (typeof nextEvent !== "undefined") {
|
if (typeof event !== "undefined") {
|
||||||
const state = getState(interpreter);
|
const state = getState(interpreter);
|
||||||
const eventReactionCouplings = getMatchingEventReactionCouplings(state, nextEvent);
|
const eventReactionCouplings = getMatchingEventReactionCouplings(state, event);
|
||||||
const reactions = eventReactionCouplings.map((eventReactionCoupling) => eventReactionCoupling.reactions).flat();
|
const reactions = eventReactionCouplings.map((eventReactionCoupling) => eventReactionCoupling.reactions).flat();
|
||||||
const { sideEffects, contextMutations, goto_ } = categorizeReactions(reactions);
|
const { sideEffects, contextMutations, peerings, goto_ } = categorizeReactions(reactions);
|
||||||
sideEffects.forEach((sideEffect) => {
|
const originalContext = interpreter.context;
|
||||||
sideEffect.fn(interpreter.context, nextEvent, interpreter);
|
|
||||||
});
|
|
||||||
contextMutations.forEach((contextMutation) => {
|
contextMutations.forEach((contextMutation) => {
|
||||||
interpreter.context = contextMutation.fn(interpreter.context, nextEvent, interpreter);
|
interpreter.context = contextMutation.fn(interpreter.context, event, interpreter);
|
||||||
});
|
});
|
||||||
if (goto_ !== null) {
|
if (goto_ !== null) {
|
||||||
|
send(interpreter, ["exit", null]);
|
||||||
interpreter.state = goto_.targetStateName;
|
interpreter.state = goto_.targetStateName;
|
||||||
|
Object.values(interpreter.subscriptionsToState).forEach((callbackFunction) => {
|
||||||
|
callbackFunction(event, interpreter);
|
||||||
|
});
|
||||||
send(interpreter, ["entry", null]);
|
send(interpreter, ["entry", null]);
|
||||||
}
|
}
|
||||||
|
peerings.forEach((peering) => {
|
||||||
|
addPeer(interpreter, peering.name, peering.peerCreationFunction(interpreter.context, event, interpreter));
|
||||||
|
});
|
||||||
|
Object.values(interpreter.subscriptionsToEvents).forEach((callbackFunction) => {
|
||||||
|
callbackFunction(event, interpreter);
|
||||||
|
});
|
||||||
|
sideEffects.forEach((sideEffect) => {
|
||||||
|
sideEffect.fn(interpreter.context, event, interpreter, originalContext);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function categorizeReactions(reactions) {
|
function categorizeReactions(reactions) {
|
||||||
let sideEffects = [], contextMutations = [], goto_ = null;
|
let sideEffects = [], contextMutations = [], peerings = [], goto_ = null;
|
||||||
reactions.forEach((reaction) => {
|
reactions.forEach((reaction) => {
|
||||||
if (reaction.type === "SideEffect") {
|
if (reaction.type === "SideEffect") {
|
||||||
sideEffects.push(reaction);
|
sideEffects.push(reaction);
|
||||||
} else if (reaction.type === "ContextMutation") {
|
} else if (reaction.type === "ContextMutation") {
|
||||||
contextMutations.push(reaction);
|
contextMutations.push(reaction);
|
||||||
|
} else if (reaction.type === "Peering") {
|
||||||
|
peerings.push(reaction);
|
||||||
} else if (reaction.type === "Goto") {
|
} else if (reaction.type === "Goto") {
|
||||||
goto_ = reaction;
|
goto_ = reaction;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return { sideEffects, contextMutations, goto_ };
|
return { sideEffects, contextMutations, peerings, goto_ };
|
||||||
|
}
|
||||||
|
var subscriptionId = 0;
|
||||||
|
function subscribeToEvents(interpreter, callback) {
|
||||||
|
subscriptionId++;
|
||||||
|
interpreter.subscriptionsToEvents[subscriptionId.toString()] = callback;
|
||||||
|
return subscriptionId.toString();
|
||||||
|
}
|
||||||
|
function addPeer(self, name, peer) {
|
||||||
|
self.peers[name] = peer;
|
||||||
|
subscribeToEvents(peer, (e, peer2) => {
|
||||||
|
if (self.isTransitioning === false) {
|
||||||
|
send(self, [name + "." + e[0], e[1]]);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/tests/00-basic.ts
|
// src/tests/00-basic.ts
|
||||||
@@ -120,5 +162,5 @@
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
var actor = interpret(machine, { context: {} });
|
var actor = Interpreter(machine, { context: {} }).start();
|
||||||
})();
|
})();
|
||||||
|
|||||||
Vendored
+106
-43
@@ -18,15 +18,27 @@
|
|||||||
var Context = function(fn) {
|
var Context = function(fn) {
|
||||||
return { type: "ContextMutation", fn };
|
return { type: "ContextMutation", fn };
|
||||||
};
|
};
|
||||||
function interpret(machine, options) {
|
var Peer = function(name, peerCreationFunction) {
|
||||||
let { state, context } = options;
|
return { type: "Peering", name, peerCreationFunction };
|
||||||
if (typeof state === "undefined") {
|
};
|
||||||
state = machine.states[0].name;
|
function Interpreter(machine, initialContext, initialStateName) {
|
||||||
|
if (typeof initialStateName === "undefined") {
|
||||||
|
initialStateName = machine.states[0].name;
|
||||||
}
|
}
|
||||||
const interpreter = { machine, state, context, eventQueue: [], isTransitioning: false };
|
const interpreter = { machine, state: initialStateName, context: initialContext, eventQueue: [], isTransitioning: false, peers: {}, peerSubscriptionIds: /* @__PURE__ */ new Map(), subscriptionsToEvents: {}, subscriptionsToState: {}, subscriptionsToSettledState: {}, isPaused: true };
|
||||||
|
interpreter.start = () => {
|
||||||
|
start(interpreter);
|
||||||
|
return interpreter;
|
||||||
|
};
|
||||||
send(interpreter, ["entry", null]);
|
send(interpreter, ["entry", null]);
|
||||||
return interpreter;
|
return interpreter;
|
||||||
}
|
}
|
||||||
|
function start(interpreter) {
|
||||||
|
if (interpreter.isPaused === true) {
|
||||||
|
interpreter.isPaused = false;
|
||||||
|
processEvents(interpreter);
|
||||||
|
}
|
||||||
|
}
|
||||||
function getState(interpreter) {
|
function getState(interpreter) {
|
||||||
return interpreter.machine.states.find((state) => state.name === interpreter.state);
|
return interpreter.machine.states.find((state) => state.name === interpreter.state);
|
||||||
}
|
}
|
||||||
@@ -36,44 +48,77 @@
|
|||||||
function send(interpreter, event) {
|
function send(interpreter, event) {
|
||||||
interpreter.eventQueue.push(event);
|
interpreter.eventQueue.push(event);
|
||||||
if (interpreter.isTransitioning === false) {
|
if (interpreter.isTransitioning === false) {
|
||||||
interpreter.isTransitioning = true;
|
processEvents(interpreter);
|
||||||
while (interpreter.eventQueue.length > 0) {
|
|
||||||
processNextEvent(interpreter);
|
|
||||||
}
|
|
||||||
interpreter.isTransitioning = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function processEvents(interpreter) {
|
||||||
|
interpreter.isTransitioning = true;
|
||||||
|
while (interpreter.eventQueue.length > 0 && interpreter.isPaused === false) {
|
||||||
|
processNextEvent(interpreter);
|
||||||
|
}
|
||||||
|
interpreter.isTransitioning = false;
|
||||||
|
Object.values(interpreter.subscriptionsToSettledState).forEach((callbackFunction) => {
|
||||||
|
callbackFunction(interpreter);
|
||||||
|
});
|
||||||
|
}
|
||||||
function processNextEvent(interpreter) {
|
function processNextEvent(interpreter) {
|
||||||
const nextEvent = interpreter.eventQueue.shift();
|
const event = interpreter.eventQueue.shift();
|
||||||
if (typeof nextEvent !== "undefined") {
|
if (typeof event !== "undefined") {
|
||||||
const state = getState(interpreter);
|
const state = getState(interpreter);
|
||||||
const eventReactionCouplings = getMatchingEventReactionCouplings(state, nextEvent);
|
const eventReactionCouplings = getMatchingEventReactionCouplings(state, event);
|
||||||
const reactions = eventReactionCouplings.map((eventReactionCoupling) => eventReactionCoupling.reactions).flat();
|
const reactions = eventReactionCouplings.map((eventReactionCoupling) => eventReactionCoupling.reactions).flat();
|
||||||
const { sideEffects, contextMutations, goto_ } = categorizeReactions(reactions);
|
const { sideEffects, contextMutations, peerings, goto_ } = categorizeReactions(reactions);
|
||||||
sideEffects.forEach((sideEffect) => {
|
const originalContext = interpreter.context;
|
||||||
sideEffect.fn(interpreter.context, nextEvent, interpreter);
|
|
||||||
});
|
|
||||||
contextMutations.forEach((contextMutation) => {
|
contextMutations.forEach((contextMutation) => {
|
||||||
interpreter.context = contextMutation.fn(interpreter.context, nextEvent, interpreter);
|
interpreter.context = contextMutation.fn(interpreter.context, event, interpreter);
|
||||||
});
|
});
|
||||||
if (goto_ !== null) {
|
if (goto_ !== null) {
|
||||||
|
send(interpreter, ["exit", null]);
|
||||||
interpreter.state = goto_.targetStateName;
|
interpreter.state = goto_.targetStateName;
|
||||||
|
Object.values(interpreter.subscriptionsToState).forEach((callbackFunction) => {
|
||||||
|
callbackFunction(event, interpreter);
|
||||||
|
});
|
||||||
send(interpreter, ["entry", null]);
|
send(interpreter, ["entry", null]);
|
||||||
}
|
}
|
||||||
|
peerings.forEach((peering) => {
|
||||||
|
addPeer(interpreter, peering.name, peering.peerCreationFunction(interpreter.context, event, interpreter));
|
||||||
|
});
|
||||||
|
Object.values(interpreter.subscriptionsToEvents).forEach((callbackFunction) => {
|
||||||
|
callbackFunction(event, interpreter);
|
||||||
|
});
|
||||||
|
sideEffects.forEach((sideEffect) => {
|
||||||
|
sideEffect.fn(interpreter.context, event, interpreter, originalContext);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function categorizeReactions(reactions) {
|
function categorizeReactions(reactions) {
|
||||||
let sideEffects = [], contextMutations = [], goto_ = null;
|
let sideEffects = [], contextMutations = [], peerings = [], goto_ = null;
|
||||||
reactions.forEach((reaction) => {
|
reactions.forEach((reaction) => {
|
||||||
if (reaction.type === "SideEffect") {
|
if (reaction.type === "SideEffect") {
|
||||||
sideEffects.push(reaction);
|
sideEffects.push(reaction);
|
||||||
} else if (reaction.type === "ContextMutation") {
|
} else if (reaction.type === "ContextMutation") {
|
||||||
contextMutations.push(reaction);
|
contextMutations.push(reaction);
|
||||||
|
} else if (reaction.type === "Peering") {
|
||||||
|
peerings.push(reaction);
|
||||||
} else if (reaction.type === "Goto") {
|
} else if (reaction.type === "Goto") {
|
||||||
goto_ = reaction;
|
goto_ = reaction;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return { sideEffects, contextMutations, goto_ };
|
return { sideEffects, contextMutations, peerings, goto_ };
|
||||||
|
}
|
||||||
|
var subscriptionId = 0;
|
||||||
|
function subscribeToEvents(interpreter, callback) {
|
||||||
|
subscriptionId++;
|
||||||
|
interpreter.subscriptionsToEvents[subscriptionId.toString()] = callback;
|
||||||
|
return subscriptionId.toString();
|
||||||
|
}
|
||||||
|
function addPeer(self, name, peer) {
|
||||||
|
self.peers[name] = peer;
|
||||||
|
subscribeToEvents(peer, (e, peer2) => {
|
||||||
|
if (self.isTransitioning === false) {
|
||||||
|
send(self, [name + "." + e[0], e[1]]);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/tests/01-ping-pong.ts
|
// src/tests/01-ping-pong.ts
|
||||||
@@ -83,30 +128,36 @@
|
|||||||
}, ms);
|
}, ms);
|
||||||
});
|
});
|
||||||
var makeRequest = (ctx, e, self) => {
|
var makeRequest = (ctx, e, self) => {
|
||||||
send(ctx.serverActor, ["received-request", self]);
|
send(self.peers.server, ["received-request", self]);
|
||||||
};
|
};
|
||||||
var sendResponse = (ctx, e, self) => {
|
var sendResponse = (ctx, e, self) => {
|
||||||
send(ctx.clientActor, ["received-response", self]);
|
send(ctx.client, ["received-response", self]);
|
||||||
};
|
};
|
||||||
var startTimer = async (ctx, e, self) => {
|
var startTimer = async (ctx, e, self) => {
|
||||||
await wait(1500);
|
await wait(1500);
|
||||||
|
console.log(" timer actually finished");
|
||||||
send(self, ["timer-finished", null]);
|
send(self, ["timer-finished", null]);
|
||||||
};
|
};
|
||||||
var log = (ctx, e, self) => {
|
var logServerStats = (ctx, e, self) => {
|
||||||
console.log(self.state, ctx);
|
console.log("server", ctx.requestsReceived, ctx.responsesSent);
|
||||||
};
|
};
|
||||||
|
var saveClient = (ctx, e, self) => ({ ...ctx, client: e[1] });
|
||||||
|
var createServer = (ctx, e, self) => Interpreter(server, { requestsReceived: 0, responsesSent: 0 }).start();
|
||||||
var client = Machine(
|
var client = Machine(
|
||||||
|
State(
|
||||||
|
"initializing",
|
||||||
|
On(
|
||||||
|
"entry",
|
||||||
|
Peer("server", createServer),
|
||||||
|
//SideEffect(log('client')),
|
||||||
|
Goto("idle")
|
||||||
|
)
|
||||||
|
),
|
||||||
State(
|
State(
|
||||||
"idle",
|
"idle",
|
||||||
On(
|
On(
|
||||||
"entry",
|
"entry",
|
||||||
SideEffect(log)
|
//SideEffect(log('client')),
|
||||||
),
|
|
||||||
On(
|
|
||||||
"server-created",
|
|
||||||
SideEffect((_ctx, [_eventName, serverActor2], self) => {
|
|
||||||
self.context.serverActor = serverActor2;
|
|
||||||
}),
|
|
||||||
Goto("making-request")
|
Goto("making-request")
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
@@ -114,7 +165,7 @@
|
|||||||
"making-request",
|
"making-request",
|
||||||
On(
|
On(
|
||||||
"entry",
|
"entry",
|
||||||
SideEffect(log),
|
//SideEffect(log('client')),
|
||||||
SideEffect(makeRequest),
|
SideEffect(makeRequest),
|
||||||
Context((ctx) => ({ ...ctx, requestsMade: ctx.requestsMade + 1 })),
|
Context((ctx) => ({ ...ctx, requestsMade: ctx.requestsMade + 1 })),
|
||||||
Goto("awaiting-response")
|
Goto("awaiting-response")
|
||||||
@@ -123,13 +174,13 @@
|
|||||||
State(
|
State(
|
||||||
"awaiting-response",
|
"awaiting-response",
|
||||||
On(
|
On(
|
||||||
"entry",
|
"entry"
|
||||||
SideEffect(log)
|
//SideEffect(log('client')),
|
||||||
),
|
),
|
||||||
On(
|
On(
|
||||||
"received-response",
|
"received-response",
|
||||||
SideEffect(log),
|
|
||||||
Context((ctx) => ({ ...ctx, responsesReceived: ctx.responsesReceived + 1 })),
|
Context((ctx) => ({ ...ctx, responsesReceived: ctx.responsesReceived + 1 })),
|
||||||
|
//SideEffect(log('client')),
|
||||||
Goto("making-request")
|
Goto("making-request")
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -139,13 +190,13 @@
|
|||||||
"awaiting-request",
|
"awaiting-request",
|
||||||
On(
|
On(
|
||||||
"entry",
|
"entry",
|
||||||
SideEffect(log)
|
//SideEffect(log('server')),
|
||||||
|
Context((ctx) => ({ ...ctx, requestsReceived: ctx.requestsReceived + 1 }))
|
||||||
),
|
),
|
||||||
On(
|
On(
|
||||||
"received-request",
|
"received-request",
|
||||||
SideEffect((_ctx, [_eventName, clientActor2], self) => {
|
//SideEffect(log('server')),
|
||||||
self.context.clientActor = clientActor2;
|
Context(saveClient),
|
||||||
}),
|
|
||||||
Goto("sending-response")
|
Goto("sending-response")
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
@@ -153,17 +204,29 @@
|
|||||||
"sending-response",
|
"sending-response",
|
||||||
On(
|
On(
|
||||||
"entry",
|
"entry",
|
||||||
SideEffect(log),
|
//SideEffect(log('server')),
|
||||||
SideEffect(startTimer)
|
SideEffect(startTimer)
|
||||||
),
|
),
|
||||||
On(
|
On(
|
||||||
"timer-finished",
|
"timer-finished",
|
||||||
|
//SideEffect(log('server')),
|
||||||
|
SideEffect(logServerStats),
|
||||||
SideEffect(sendResponse),
|
SideEffect(sendResponse),
|
||||||
|
Context((ctx) => ({ ...ctx, responsesSent: ctx.responsesSent + 1 })),
|
||||||
Goto("awaiting-request")
|
Goto("awaiting-request")
|
||||||
|
// for some reason, at this point there's a "received-request" waiting in the eventQueue, which gets processed before the "exit" then "entry" that get appended to the queue due to this Goto, which makes the Interpreter come right back to this State
|
||||||
|
/*
|
||||||
|
Server gets timer-finished, which sends response to client.
|
||||||
|
|
||||||
|
But client, at the time, is not transitioning, so it immediately begins
|
||||||
|
processing that event. The problem is that one of the sideeffects involved
|
||||||
|
in processing that event is to send another request to the server,
|
||||||
|
which hasn't yet even queued `exit`-then-`entry` events for its next state!
|
||||||
|
|
||||||
|
So we have to ensure they get queued first, before processing the client.
|
||||||
|
*/
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
var clientActor = interpret(client, { context: { requestsMade: 0, responsesReceived: 0 } });
|
var clientActor = Interpreter(client, { requestsMade: 0, responsesReceived: 0 }).start();
|
||||||
var serverActor = interpret(server, { context: {} });
|
|
||||||
send(clientActor, ["server-created", serverActor]);
|
|
||||||
})();
|
})();
|
||||||
|
|||||||
+34
-21
@@ -10,7 +10,7 @@ export interface EventReactionCouplings_T<C> {
|
|||||||
eventName: string;
|
eventName: string;
|
||||||
reactions: Array<Reaction_T<C>>;
|
reactions: Array<Reaction_T<C>>;
|
||||||
};
|
};
|
||||||
export type Reaction_T<C> = SideEffect_T<C> | ContextMutation_T<C> | Goto_T;
|
export type Reaction_T<C> = SideEffect_T<C> | ContextMutation_T<C> | Peering_T<C,unknown> | Goto_T;
|
||||||
export interface SideEffect_T<C> {
|
export interface SideEffect_T<C> {
|
||||||
type: 'SideEffect';
|
type: 'SideEffect';
|
||||||
fn: SideEffectFunction_T<C>;
|
fn: SideEffectFunction_T<C>;
|
||||||
@@ -21,6 +21,12 @@ export interface ContextMutation_T<C> {
|
|||||||
fn: ContextMutationFunction_T<C>;
|
fn: ContextMutationFunction_T<C>;
|
||||||
};
|
};
|
||||||
export type ContextMutationFunction_T<C> = (ctx:C,e:Event_T,self:Interpreter_T<C>)=>C;
|
export type ContextMutationFunction_T<C> = (ctx:C,e:Event_T,self:Interpreter_T<C>)=>C;
|
||||||
|
export interface Peering_T<C,C_Peer> {
|
||||||
|
type: 'Peering';
|
||||||
|
name: string;
|
||||||
|
peerCreationFunction: PeerCreationFunction_T<C,C_Peer>
|
||||||
|
};
|
||||||
|
export type PeerCreationFunction_T<C,C_Peer> = (ctx:C,e:Event_T,self:Interpreter_T<C>) => Interpreter_T<C_Peer>;
|
||||||
export interface Goto_T {
|
export interface Goto_T {
|
||||||
type: 'Goto';
|
type: 'Goto';
|
||||||
targetStateName: string;
|
targetStateName: string;
|
||||||
@@ -32,16 +38,14 @@ export const On = function<C>(eventName:string, ...reactions:Array<Reaction_T<C>
|
|||||||
export const SideEffect = function<C>(fn:SideEffectFunction_T<C>) : SideEffect_T<C>{ return {type:'SideEffect', fn}; };
|
export const SideEffect = function<C>(fn:SideEffectFunction_T<C>) : SideEffect_T<C>{ return {type:'SideEffect', fn}; };
|
||||||
export const Goto = function(targetStateName:string) : Goto_T { return {type:'Goto', targetStateName} };
|
export const Goto = function(targetStateName:string) : Goto_T { return {type:'Goto', targetStateName} };
|
||||||
export const Context = function<C>(fn:ContextMutationFunction_T<C>) : ContextMutation_T<C> { return {type:'ContextMutation', fn} };
|
export const Context = function<C>(fn:ContextMutationFunction_T<C>) : ContextMutation_T<C> { return {type:'ContextMutation', fn} };
|
||||||
|
export const Peer = function<C,C_Peer>(name:string, peerCreationFunction:PeerCreationFunction_T<C,C_Peer>) : Peering_T<C,C_Peer>{ return {type:'Peering', name, peerCreationFunction}; }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export interface Interpreter_T<C> {
|
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>>>;
|
peers: Record<string, Interpreter_T<unknown>>;
|
||||||
peerSubscriptionIds: Map<Interpreter_T<unknown>,string>;
|
peerSubscriptionIds: Map<Interpreter_T<unknown>,string>;
|
||||||
eventQueue:Array<Event_T>;
|
eventQueue:Array<Event_T>;
|
||||||
subscriptionsToEvents: Record<string, EventsSubscriptionCallbackFunction_T<C>>; // called upon every event
|
subscriptionsToEvents: Record<string, EventsSubscriptionCallbackFunction_T<C>>; // called upon every event
|
||||||
@@ -64,7 +68,7 @@ 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, subscriptionsToEvents: {}, subscriptionsToState: {}, subscriptionsToSettledState: {}, isPaused: true};
|
const interpreter : Interpreter_T<C> = {machine, state: initialStateName, context:initialContext, eventQueue:[], isTransitioning:false, peers:{}, peerSubscriptionIds:new Map(), subscriptionsToEvents: {}, subscriptionsToState: {}, subscriptionsToSettledState: {}, isPaused: true};
|
||||||
interpreter.start = ()=>{ start(interpreter); return interpreter; }
|
interpreter.start = ()=>{ start(interpreter); return interpreter; }
|
||||||
send(interpreter, ['entry', null] );
|
send(interpreter, ['entry', null] );
|
||||||
return interpreter;
|
return interpreter;
|
||||||
@@ -125,20 +129,13 @@ function processNextEvent<C>(interpreter:Interpreter_T<C>){
|
|||||||
const reactions = eventReactionCouplings
|
const reactions = eventReactionCouplings
|
||||||
.map((eventReactionCoupling)=>eventReactionCoupling.reactions)
|
.map((eventReactionCoupling)=>eventReactionCoupling.reactions)
|
||||||
.flat();
|
.flat();
|
||||||
const {sideEffects, contextMutations, goto_} = categorizeReactions(reactions);
|
const {sideEffects, contextMutations, peerings, goto_} = categorizeReactions(reactions);
|
||||||
// save the current context, before it's mutated, so as to pass it to sideEffects below:
|
// save the current context, before it's mutated, so as to pass it to sideEffects below:
|
||||||
const originalContext = interpreter.context;
|
const originalContext = interpreter.context;
|
||||||
// must process contextMutations in-series:
|
// must process contextMutations in-series:
|
||||||
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):
|
|
||||||
// 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)=>{
|
|
||||||
sideEffect.fn(interpreter.context, event, interpreter, originalContext);
|
|
||||||
});
|
|
||||||
// processing of `goto` must be last:
|
// processing of `goto` must be last:
|
||||||
if(goto_ !== null){
|
if(goto_ !== null){
|
||||||
send(interpreter, ['exit', null]);
|
send(interpreter, ['exit', null]);
|
||||||
@@ -147,12 +144,23 @@ function processNextEvent<C>(interpreter:Interpreter_T<C>){
|
|||||||
Object.values(interpreter.subscriptionsToState).forEach((callbackFunction)=>{ callbackFunction(event, interpreter); });
|
Object.values(interpreter.subscriptionsToState).forEach((callbackFunction)=>{ callbackFunction(event, interpreter); });
|
||||||
send(interpreter, ['entry', null]);
|
send(interpreter, ['entry', null]);
|
||||||
}
|
}
|
||||||
|
// now that "internal" stuff has been run, we can run "external" stuff:
|
||||||
|
// process peerings (possibly in parallel):
|
||||||
|
peerings.forEach((peering)=>{ addPeer(interpreter, peering.name, peering.peerCreationFunction(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)=>{
|
||||||
|
sideEffect.fn(interpreter.context, event, interpreter, originalContext);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function categorizeReactions<C>(reactions:Array<Reaction_T<C>>) : {sideEffects:Array<SideEffect_T<C>>, contextMutations:Array<ContextMutation_T<C>>, goto_:Goto_T|null}{
|
function categorizeReactions<C>(reactions:Array<Reaction_T<C>>) : {sideEffects:Array<SideEffect_T<C>>, contextMutations:Array<ContextMutation_T<C>>, peerings:Array<Peering_T<C,unknown>>, goto_:Goto_T|null}{
|
||||||
let
|
let
|
||||||
sideEffects:Array<SideEffect_T<C>> = [],
|
sideEffects:Array<SideEffect_T<C>> = [],
|
||||||
contextMutations:Array<ContextMutation_T<C>> = [],
|
contextMutations:Array<ContextMutation_T<C>> = [],
|
||||||
|
peerings:Array<Peering_T<C,unknown>> = [],
|
||||||
goto_:Goto_T|null = null;
|
goto_:Goto_T|null = null;
|
||||||
reactions.forEach((reaction)=>{
|
reactions.forEach((reaction)=>{
|
||||||
if(reaction.type === 'SideEffect'){
|
if(reaction.type === 'SideEffect'){
|
||||||
@@ -161,11 +169,14 @@ function categorizeReactions<C>(reactions:Array<Reaction_T<C>>) : {sideEffects:A
|
|||||||
else if(reaction.type === 'ContextMutation'){
|
else if(reaction.type === 'ContextMutation'){
|
||||||
contextMutations.push(reaction);
|
contextMutations.push(reaction);
|
||||||
}
|
}
|
||||||
|
else if(reaction.type === 'Peering'){
|
||||||
|
peerings.push(reaction);
|
||||||
|
}
|
||||||
else if(reaction.type === 'Goto'){
|
else if(reaction.type === 'Goto'){
|
||||||
goto_ = reaction;
|
goto_ = reaction;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return {sideEffects, contextMutations, goto_};
|
return {sideEffects, contextMutations, peerings, goto_};
|
||||||
}
|
}
|
||||||
|
|
||||||
export type EventsSubscriptionCallbackFunction_T<C> = (e:Event_T, self:Interpreter_T<C>)=>void;
|
export type EventsSubscriptionCallbackFunction_T<C> = (e:Event_T, self:Interpreter_T<C>)=>void;
|
||||||
@@ -195,13 +206,15 @@ export function unsubscribe<C>(interpreter:Interpreter_T<C>, subscriptionId:stri
|
|||||||
delete interpreter.subscriptionsToEvents[subscriptionId.toString()];
|
delete interpreter.subscriptionsToEvents[subscriptionId.toString()];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addPeer<C, C_Peer>(self:Interpreter_T<C>, peer:Interpreter_T<C_Peer>, name:string){
|
export function addPeer<C, C_Peer>(self:Interpreter_T<C>, name:string, peer:Interpreter_T<C_Peer>){
|
||||||
self.peers[name] = peer;
|
self.peers[name] = peer;
|
||||||
|
subscribeToEvents(peer, (e, peer)=>{
|
||||||
|
// this `if` prevents infinite loops due to mutually-subscribed peers (cyclical dependencies):
|
||||||
|
if(self.isTransitioning === false){
|
||||||
|
send(self, [name+'.'+e[0], e[1]]);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
export function addPeers(){}
|
|
||||||
|
|
||||||
export const Spawn = function(){};
|
|
||||||
export const Unspawn = function(){};
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
export function useMachine(machine, options){
|
export function useMachine(machine, options){
|
||||||
|
|||||||
@@ -43,4 +43,4 @@ const machine =
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
const actor = Interpreter<C>(machine, {context:{}});
|
const actor = Interpreter<C>(machine, {context:{}}).start();
|
||||||
+45
-22
@@ -1,34 +1,44 @@
|
|||||||
import { Machine, State, On, SideEffect, Goto, Spawn, Unspawn, Interpreter, Interpreter_T, send, Event_T, Context, SideEffectFunction_T } from '../index';
|
import { Machine, State, On, SideEffect, Goto, Interpreter, Interpreter_T, send, Event_T, Context, SideEffectFunction_T, Peer, PeerCreationFunction_T, ContextMutationFunction_T } from '../index';
|
||||||
|
|
||||||
const wait = (ms:number)=>new Promise((resolve)=>{ setTimeout(()=>{ resolve(1); }, ms); });
|
const wait = (ms:number)=>new Promise((resolve)=>{ setTimeout(()=>{ resolve(1); }, ms); });
|
||||||
const makeRequest : SideEffectFunction_T<Cc> = (ctx,e,self)=>{ send(ctx.serverActor, ['received-request',self]); };
|
const makeRequest : SideEffectFunction_T<Cc> = (ctx,e,self)=>{ send(self.peers.server, ['received-request',self]); };
|
||||||
const sendResponse : SideEffectFunction_T<Cs> = (ctx,e,self)=>{ send(ctx.clientActor, ['received-response',self]); };
|
const sendResponse : SideEffectFunction_T<Cs> = (ctx,e,self)=>{ send(ctx.client, ['received-response',self]); };
|
||||||
const startTimer : SideEffectFunction_T<Cs> = async (ctx,e,self)=>{ await wait(1500); send(self, ['timer-finished',null]); }
|
const startTimer : SideEffectFunction_T<Cs> = async (ctx,e,self)=>{ await wait(1500); console.log(' timer actually finished'); send(self, ['timer-finished',null]); }
|
||||||
const log : SideEffectFunction_T<Cc|Cs> = (ctx, e, self)=>{ console.log(self.state, ctx); };
|
const log = (namespace:string)=>(ctx, e, self)=>{ console.log(namespace, self.state, e[0]); };
|
||||||
|
const logClientStats : SideEffectFunction_T<Cc> = (ctx,e,self)=>{ console.log('client', ctx.requestsMade, ctx.responsesReceived); }
|
||||||
|
const logServerStats : SideEffectFunction_T<Cs> = (ctx,e,self)=>{ console.log('server', ctx.requestsReceived, ctx.responsesSent); }
|
||||||
|
const logEventQueue = (namespace:string)=>(ctx,e,self)=>{ console.log(namespace+'.eventQueue', [e[0]], self.eventQueue.map(([eventName])=>eventName)); }
|
||||||
|
const saveClient : ContextMutationFunction_T<Cs> = (ctx, e, self)=>({...ctx, client:e[1]});
|
||||||
|
const createServer : PeerCreationFunction_T<Cc,Cs> = (ctx, e, self)=>Interpreter(server,{requestsReceived:0, responsesSent:0}).start();
|
||||||
|
|
||||||
type Cc = {
|
type Cc = {
|
||||||
requestsMade: number;
|
requestsMade: number;
|
||||||
responsesReceived: number;
|
responsesReceived: number;
|
||||||
serverActor: Interpreter_T<Cs>;
|
|
||||||
};
|
};
|
||||||
type Cs = {
|
type Cs = {
|
||||||
clientActor: Interpreter_T<Cc>
|
client: Interpreter_T<Cc>;
|
||||||
|
requestsReceived:number;
|
||||||
|
responsesSent:number;
|
||||||
};
|
};
|
||||||
|
|
||||||
const client =
|
const client =
|
||||||
Machine<Cc>(
|
Machine<Cc>(
|
||||||
|
State('initializing',
|
||||||
|
On('entry',
|
||||||
|
Peer('server', createServer),
|
||||||
|
//SideEffect(log('client')),
|
||||||
|
Goto('idle'),
|
||||||
|
)
|
||||||
|
),
|
||||||
State<Cc>('idle',
|
State<Cc>('idle',
|
||||||
On<Cc>('entry',
|
On<Cc>('entry',
|
||||||
SideEffect(log),
|
//SideEffect(log('client')),
|
||||||
),
|
|
||||||
On('server-created',
|
|
||||||
SideEffect((_ctx,[_eventName,serverActor],self)=>{ self.context.serverActor=serverActor; }),
|
|
||||||
Goto('making-request')
|
Goto('making-request')
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
State<Cc>('making-request',
|
State<Cc>('making-request',
|
||||||
On<Cc>('entry',
|
On<Cc>('entry',
|
||||||
SideEffect(log),
|
//SideEffect(log('client')),
|
||||||
SideEffect(makeRequest),
|
SideEffect(makeRequest),
|
||||||
Context<Cc>((ctx)=>({...ctx, requestsMade: ctx.requestsMade+1})),
|
Context<Cc>((ctx)=>({...ctx, requestsMade: ctx.requestsMade+1})),
|
||||||
Goto('awaiting-response')
|
Goto('awaiting-response')
|
||||||
@@ -36,11 +46,11 @@ const client =
|
|||||||
),
|
),
|
||||||
State<Cc>('awaiting-response',
|
State<Cc>('awaiting-response',
|
||||||
On<Cc>('entry',
|
On<Cc>('entry',
|
||||||
SideEffect(log),
|
//SideEffect(log('client')),
|
||||||
),
|
),
|
||||||
On<Cc>('received-response',
|
On<Cc>('received-response',
|
||||||
SideEffect(log),
|
|
||||||
Context<Cc>((ctx)=>({...ctx, responsesReceived: ctx.responsesReceived+1})),
|
Context<Cc>((ctx)=>({...ctx, responsesReceived: ctx.responsesReceived+1})),
|
||||||
|
//SideEffect(log('client')),
|
||||||
Goto('making-request')
|
Goto('making-request')
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -50,25 +60,38 @@ const server =
|
|||||||
Machine<Cs>(
|
Machine<Cs>(
|
||||||
State<Cs>('awaiting-request',
|
State<Cs>('awaiting-request',
|
||||||
On<Cs>('entry',
|
On<Cs>('entry',
|
||||||
SideEffect(log),
|
//SideEffect(log('server')),
|
||||||
|
Context<Cs>((ctx)=>({...ctx, requestsReceived: ctx.requestsReceived+1})),
|
||||||
),
|
),
|
||||||
On('received-request',
|
On('received-request',
|
||||||
SideEffect((_ctx,[_eventName,clientActor],self)=>{ self.context.clientActor=clientActor; }),
|
//SideEffect(log('server')),
|
||||||
|
Context<Cs>(saveClient),
|
||||||
Goto('sending-response')
|
Goto('sending-response')
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
State<Cs>('sending-response',
|
State<Cs>('sending-response',
|
||||||
On<Cs>('entry',
|
On<Cs>('entry',
|
||||||
SideEffect(log),
|
//SideEffect(log('server')),
|
||||||
SideEffect(startTimer)
|
SideEffect(startTimer)
|
||||||
),
|
),
|
||||||
On('timer-finished',
|
On('timer-finished',
|
||||||
|
//SideEffect(log('server')),
|
||||||
|
SideEffect(logServerStats),
|
||||||
SideEffect(sendResponse),
|
SideEffect(sendResponse),
|
||||||
Goto('awaiting-request')
|
Context<Cs>((ctx)=>({...ctx, responsesSent: ctx.responsesSent+1})),
|
||||||
)
|
Goto('awaiting-request') // for some reason, at this point there's a "received-request" waiting in the eventQueue, which gets processed before the "exit" then "entry" that get appended to the queue due to this Goto, which makes the Interpreter come right back to this State
|
||||||
|
/*
|
||||||
|
Server gets timer-finished, which sends response to client.
|
||||||
|
|
||||||
|
But client, at the time, is not transitioning, so it immediately begins
|
||||||
|
processing that event. The problem is that one of the sideeffects involved
|
||||||
|
in processing that event is to send another request to the server,
|
||||||
|
which hasn't yet even queued `exit`-then-`entry` events for its next state!
|
||||||
|
|
||||||
|
So we have to ensure they get queued first, before processing the client.
|
||||||
|
*/
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
const clientActor = Interpreter(client, {context:{requestsMade:0, responsesReceived:0}});
|
const clientActor = Interpreter(client, {requestsMade:0, responsesReceived:0}).start();
|
||||||
const serverActor = Interpreter(server, {context:{}});
|
|
||||||
send(clientActor, ['server-created', serverActor]);
|
|
||||||
Reference in New Issue
Block a user