Skip to content

Instantly share code, notes, and snippets.

@mpolichette
Last active May 10, 2018 19:18
Show Gist options
  • Save mpolichette/152249d2d95a9f25be8e5c8c99c274e3 to your computer and use it in GitHub Desktop.
Save mpolichette/152249d2d95a9f25be8e5c8c99c274e3 to your computer and use it in GitHub Desktop.
An xstate interpreter
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)
}
}
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