Last active
May 10, 2018 19:18
-
-
Save mpolichette/152249d2d95a9f25be8e5c8c99c274e3 to your computer and use it in GitHub Desktop.
An xstate interpreter
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import EventEmitter from 'eventemitter3' | |
import { Machine } from 'xstate' | |
import fibonacci from 'shared/lib/fibonacci' | |
const CORE_EVENT_REGX = /^core:(.*)/ | |
export default class MachineCore extends EventEmitter { | |
constructor(chart) { | |
super() | |
this.machine = new Machine(chart) | |
this.initialState = this.machine.initialState | |
this.events = this.machine.events | |
this.data = {} | |
this.config = { | |
fibTimer: { | |
initalDelay: 100, | |
maxDelay: 20000, | |
}, | |
} | |
} | |
processEvent(currentState, event) { | |
const nextState = this.machine.transition(currentState, event) | |
console.log(`FSM:`, currentState, `--[${event}]-->`, nextState.value) | |
this.handleStateActions(nextState) | |
return nextState | |
} | |
handleAction(action, state) { | |
const hasListeners = this.listenerCount(action) > 0 | |
const hasHandler = this[action] && typeof this[action] === 'function' | |
if (!hasListeners && !hasHandler) { | |
throw new Error(`No action or listeners for: ${action}`) | |
} | |
this.emit(action, state) | |
if (hasHandler) this[action](state) | |
} | |
handleCoreAction(actionTriplet, state) { | |
// eslint-disable-next-line no-unused-vars | |
const [_, action, dataFrom] = actionTriplet.split(':') | |
const hasHandler = this[action] && typeof this[action] === 'function' | |
if (!hasHandler) throw new Error(`No core action for: ${action}`) | |
let dataState = state | |
if (dataFrom === 'exit') dataState = state.history | |
const actionData = this.getActionData(action, dataState, dataFrom) | |
this[action](state, actionData) | |
} | |
handleStateActions(nextState) { | |
if (nextState.actions && nextState.actions.length) { | |
nextState.actions.forEach(action => { | |
// Check if we're a core action: | |
const match = action.match(CORE_EVENT_REGX) | |
if (match) { | |
this.handleCoreAction(action, nextState) | |
} else { | |
this.handleAction(action, nextState) | |
} | |
}) | |
} | |
} | |
getStateData(state) { | |
// Note: This is a bit of a hack | |
const node = this.machine.getState(state) | |
return node.data | |
} | |
getActionData(action, state) { | |
const stateData = this.getStateData(state) || {} | |
const actionData = stateData[action] | |
// Assume it is an error if the action requested data and it was not provided | |
if (!actionData) | |
throw new Error(`Action [${action}] expected associated data`) | |
return actionData | |
} | |
getCoreData(name) { | |
return this.data[name] | |
} | |
setCoreData(name, value) { | |
this.data[name] = value | |
} | |
// Core fuctions | |
// ============= | |
timer(state, actionData) { | |
const { name, delay, thenEmit } = actionData | |
console.log(`Setting timer [${name}] for ${delay}ms`) | |
const timer = setTimeout(() => this.emit('timer', thenEmit), delay) | |
if (name) this.setCoreData(name, timer) | |
} | |
clearTimer(state, actionData) { | |
const { name } = actionData | |
const timer = this.getCoreData(name) | |
console.log(`Clearing timer [${name}]`, timer) | |
clearTimeout(timer) | |
} | |
fibTimer(state, actionData) { | |
const { fibTimer: { initalDelay, maxDelay } } = this.config | |
const { name, counter, thenEmit } = actionData | |
const index = this.getCoreData(counter) || 0 | |
const delay = Math.min(initalDelay * fibonacci(index), maxDelay) | |
console.log(`Waiting ${delay}ms`) | |
this.timer(state, { name, delay, thenEmit }) | |
} | |
incrementCounter(state, actionData) { | |
const { counter } = actionData | |
const value = this.getCoreData(counter) || 0 | |
this.setCoreData(counter, value + 1) | |
} | |
clearCounter(state, actionData) { | |
const { counter } = actionData | |
this.setCoreData(counter, 0) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const lightMachine = Machine({ | |
key: 'light', | |
initial: 'green', | |
states: { | |
green: { | |
onEntry: 'core:timer', | |
data: { timer: { delay: 180000, thenEmit: 'TIMER' }}, | |
on: { | |
TIMER: 'yellow', | |
POWER_OUTAGE: 'red' | |
} | |
}, | |
yellow: { | |
onEntry: 'core:timer', | |
data: { timer: { delay: 15000, thenEmit: 'TIMER' }}, | |
on: { | |
TIMER: 'red', | |
POWER_OUTAGE: 'red' | |
} | |
}, | |
red: { | |
onEntry: 'core:timer', | |
data: { timer: { delay: 120000, thenEmit: 'TIMER' }}, | |
on: { | |
TIMER: 'green', | |
POWER_OUTAGE: 'red' | |
} | |
} | |
} | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment