use eventQueue; process Reactions in proper order
This commit is contained in:
Vendored
+50
-38
@@ -3,62 +3,74 @@
|
||||
var Machine = function(...states) {
|
||||
return { states };
|
||||
};
|
||||
var State = function(name, ...ons) {
|
||||
return { name, ons };
|
||||
var State = function(name, ...eventReactionCouplings) {
|
||||
return { name, eventReactionCouplings };
|
||||
};
|
||||
var On = function(eventName, ...reactions) {
|
||||
return { eventName, reactions };
|
||||
};
|
||||
var Do = function(fn) {
|
||||
return { type: "Do", fn };
|
||||
var SideEffect = function(fn) {
|
||||
return { type: "SideEffect", fn };
|
||||
};
|
||||
var Goto = function(targetStateName) {
|
||||
return { type: "Goto", targetStateName };
|
||||
};
|
||||
var Tick = function(doFunctions) {
|
||||
return { doFunctions };
|
||||
};
|
||||
function interpret(machine2, options) {
|
||||
let { state, context } = options;
|
||||
if (typeof state === "undefined") {
|
||||
state = machine2.states[0].name;
|
||||
}
|
||||
const interpreter = { machine: machine2, state, context, tickQueues: [] };
|
||||
console.log(interpreter);
|
||||
const interpreter = { machine: machine2, state, context, eventQueue: [], isTransitioning: false };
|
||||
send(interpreter, ["entry", null]);
|
||||
return interpreter;
|
||||
}
|
||||
function getState(interpreter) {
|
||||
return interpreter.machine.states.find((state) => state.name === interpreter.state);
|
||||
}
|
||||
function getOns(state, event) {
|
||||
return state.ons.filter((on) => on.eventName === event[0]);
|
||||
}
|
||||
function noop() {
|
||||
function getMatchingEventReactionCouplings(state, event) {
|
||||
return state.eventReactionCouplings.filter((eventReactionCoupling) => eventReactionCoupling.eventName === event[0]);
|
||||
}
|
||||
function send(interpreter, event) {
|
||||
const state = getState(interpreter);
|
||||
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;
|
||||
interpreter.eventQueue.push(event);
|
||||
if (interpreter.isTransitioning === false) {
|
||||
interpreter.isTransitioning = true;
|
||||
while (interpreter.eventQueue.length > 0) {
|
||||
processNextEvent(interpreter);
|
||||
}
|
||||
interpreter.isTransitioning = false;
|
||||
}
|
||||
}
|
||||
function processNextEvent(interpreter) {
|
||||
const nextEvent = interpreter.eventQueue.shift();
|
||||
if (typeof nextEvent !== "undefined") {
|
||||
const state = getState(interpreter);
|
||||
const eventReactionCouplings = getMatchingEventReactionCouplings(state, nextEvent);
|
||||
const reactions = eventReactionCouplings.map((eventReactionCoupling) => eventReactionCoupling.reactions).flat();
|
||||
const { sideEffects, contextMutations, goto_ } = categorizeReactions(reactions);
|
||||
sideEffects.forEach((sideEffect) => {
|
||||
sideEffect.fn(interpreter.context, nextEvent, interpreter);
|
||||
});
|
||||
contextMutations.forEach((contextMutation) => {
|
||||
interpreter.context = contextMutation.fn(interpreter.context, nextEvent, interpreter);
|
||||
});
|
||||
if (goto_ !== null) {
|
||||
interpreter.state = goto_.targetStateName;
|
||||
send(interpreter, ["entry", null]);
|
||||
}
|
||||
}
|
||||
}
|
||||
function categorizeReactions(reactions) {
|
||||
let sideEffects = [], contextMutations = [], goto_ = null;
|
||||
reactions.forEach((reaction) => {
|
||||
if (reaction.type === "SideEffect") {
|
||||
sideEffects.push(reaction);
|
||||
} else if (reaction.type === "ContextMutation") {
|
||||
contextMutations.push(reaction);
|
||||
} else if (reaction.type === "Goto") {
|
||||
return (ctx, e, self) => {
|
||||
self.state = reaction.targetStateName;
|
||||
send(self, ["entry", e]);
|
||||
};
|
||||
} else {
|
||||
return noop;
|
||||
goto_ = reaction;
|
||||
}
|
||||
});
|
||||
const tick = Tick(functionsToRunInTick);
|
||||
tick.doFunctions.forEach((fn) => {
|
||||
fn(interpreter.context, event, interpreter);
|
||||
});
|
||||
return { sideEffects, contextMutations, goto_ };
|
||||
}
|
||||
|
||||
// src/tests/00-basic.ts
|
||||
@@ -75,8 +87,8 @@
|
||||
"green",
|
||||
On(
|
||||
"entry",
|
||||
Do(beginTimer),
|
||||
Do(log)
|
||||
SideEffect(beginTimer),
|
||||
SideEffect(log)
|
||||
),
|
||||
On(
|
||||
"timer-finished",
|
||||
@@ -87,8 +99,8 @@
|
||||
"yellow",
|
||||
On(
|
||||
"entry",
|
||||
Do(beginTimer),
|
||||
Do(log)
|
||||
SideEffect(beginTimer),
|
||||
SideEffect(log)
|
||||
),
|
||||
On(
|
||||
"timer-finished",
|
||||
@@ -99,8 +111,8 @@
|
||||
"red",
|
||||
On(
|
||||
"entry",
|
||||
Do(beginTimer),
|
||||
Do(log)
|
||||
SideEffect(beginTimer),
|
||||
SideEffect(log)
|
||||
),
|
||||
On(
|
||||
"timer-finished",
|
||||
|
||||
Vendored
+55
-43
@@ -3,62 +3,74 @@
|
||||
var Machine = function(...states) {
|
||||
return { states };
|
||||
};
|
||||
var State = function(name, ...ons) {
|
||||
return { name, ons };
|
||||
var State = function(name, ...eventReactionCouplings) {
|
||||
return { name, eventReactionCouplings };
|
||||
};
|
||||
var On = function(eventName, ...reactions) {
|
||||
return { eventName, reactions };
|
||||
};
|
||||
var Do = function(fn) {
|
||||
return { type: "Do", fn };
|
||||
var SideEffect = function(fn) {
|
||||
return { type: "SideEffect", fn };
|
||||
};
|
||||
var Goto = function(targetStateName) {
|
||||
return { type: "Goto", targetStateName };
|
||||
};
|
||||
var Tick = function(doFunctions) {
|
||||
return { doFunctions };
|
||||
};
|
||||
function interpret(machine, options) {
|
||||
let { state, context } = options;
|
||||
if (typeof state === "undefined") {
|
||||
state = machine.states[0].name;
|
||||
}
|
||||
const interpreter = { machine, state, context, tickQueues: [] };
|
||||
console.log(interpreter);
|
||||
const interpreter = { machine, state, context, eventQueue: [], isTransitioning: false };
|
||||
send(interpreter, ["entry", null]);
|
||||
return interpreter;
|
||||
}
|
||||
function getState(interpreter) {
|
||||
return interpreter.machine.states.find((state) => state.name === interpreter.state);
|
||||
}
|
||||
function getOns(state, event) {
|
||||
return state.ons.filter((on) => on.eventName === event[0]);
|
||||
}
|
||||
function noop() {
|
||||
function getMatchingEventReactionCouplings(state, event) {
|
||||
return state.eventReactionCouplings.filter((eventReactionCoupling) => eventReactionCoupling.eventName === event[0]);
|
||||
}
|
||||
function send(interpreter, event) {
|
||||
const state = getState(interpreter);
|
||||
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;
|
||||
interpreter.eventQueue.push(event);
|
||||
if (interpreter.isTransitioning === false) {
|
||||
interpreter.isTransitioning = true;
|
||||
while (interpreter.eventQueue.length > 0) {
|
||||
processNextEvent(interpreter);
|
||||
}
|
||||
interpreter.isTransitioning = false;
|
||||
}
|
||||
}
|
||||
function processNextEvent(interpreter) {
|
||||
const nextEvent = interpreter.eventQueue.shift();
|
||||
if (typeof nextEvent !== "undefined") {
|
||||
const state = getState(interpreter);
|
||||
const eventReactionCouplings = getMatchingEventReactionCouplings(state, nextEvent);
|
||||
const reactions = eventReactionCouplings.map((eventReactionCoupling) => eventReactionCoupling.reactions).flat();
|
||||
const { sideEffects, contextMutations, goto_ } = categorizeReactions(reactions);
|
||||
sideEffects.forEach((sideEffect) => {
|
||||
sideEffect.fn(interpreter.context, nextEvent, interpreter);
|
||||
});
|
||||
contextMutations.forEach((contextMutation) => {
|
||||
interpreter.context = contextMutation.fn(interpreter.context, nextEvent, interpreter);
|
||||
});
|
||||
if (goto_ !== null) {
|
||||
interpreter.state = goto_.targetStateName;
|
||||
send(interpreter, ["entry", null]);
|
||||
}
|
||||
}
|
||||
}
|
||||
function categorizeReactions(reactions) {
|
||||
let sideEffects = [], contextMutations = [], goto_ = null;
|
||||
reactions.forEach((reaction) => {
|
||||
if (reaction.type === "SideEffect") {
|
||||
sideEffects.push(reaction);
|
||||
} else if (reaction.type === "ContextMutation") {
|
||||
contextMutations.push(reaction);
|
||||
} else if (reaction.type === "Goto") {
|
||||
return (ctx, e, self) => {
|
||||
self.state = reaction.targetStateName;
|
||||
send(self, ["entry", e]);
|
||||
};
|
||||
} else {
|
||||
return noop;
|
||||
goto_ = reaction;
|
||||
}
|
||||
});
|
||||
const tick = Tick(functionsToRunInTick);
|
||||
tick.doFunctions.forEach((fn) => {
|
||||
fn(interpreter.context, event, interpreter);
|
||||
});
|
||||
return { sideEffects, contextMutations, goto_ };
|
||||
}
|
||||
|
||||
// src/tests/01-ping-pong.ts
|
||||
@@ -85,11 +97,11 @@
|
||||
"idle",
|
||||
On(
|
||||
"entry",
|
||||
Do(log)
|
||||
SideEffect(log)
|
||||
),
|
||||
On(
|
||||
"server-created",
|
||||
Do((_ctx, [_eventName, serverActor2], self) => {
|
||||
SideEffect((_ctx, [_eventName, serverActor2], self) => {
|
||||
self.context.serverActor = serverActor2;
|
||||
}),
|
||||
Goto("making-request")
|
||||
@@ -99,8 +111,8 @@
|
||||
"making-request",
|
||||
On(
|
||||
"entry",
|
||||
Do(log),
|
||||
Do(makeRequest),
|
||||
SideEffect(log),
|
||||
SideEffect(makeRequest),
|
||||
Goto("awaiting-response")
|
||||
)
|
||||
),
|
||||
@@ -108,11 +120,11 @@
|
||||
"awaiting-response",
|
||||
On(
|
||||
"entry",
|
||||
Do(log)
|
||||
SideEffect(log)
|
||||
),
|
||||
On(
|
||||
"received-response",
|
||||
Do(log),
|
||||
SideEffect(log),
|
||||
Goto("making-request")
|
||||
)
|
||||
)
|
||||
@@ -122,11 +134,11 @@
|
||||
"awaiting-request",
|
||||
On(
|
||||
"entry",
|
||||
Do(log)
|
||||
SideEffect(log)
|
||||
),
|
||||
On(
|
||||
"received-request",
|
||||
Do((_ctx, [_eventName, clientActor2], self) => {
|
||||
SideEffect((_ctx, [_eventName, clientActor2], self) => {
|
||||
self.context.clientActor = clientActor2;
|
||||
}),
|
||||
Goto("sending-response")
|
||||
@@ -136,12 +148,12 @@
|
||||
"sending-response",
|
||||
On(
|
||||
"entry",
|
||||
Do(log),
|
||||
Do(startTimer)
|
||||
SideEffect(log),
|
||||
SideEffect(startTimer)
|
||||
),
|
||||
On(
|
||||
"timer-finished",
|
||||
Do(sendResponse),
|
||||
SideEffect(sendResponse),
|
||||
Goto("awaiting-request")
|
||||
)
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user