Created
March 4, 2020 23:29
-
-
Save GoodNovember/dd5fa109bc6a4bdd621216a30bc00754 to your computer and use it in GitHub Desktop.
Custom Finite State Machine Stuff
This file contains hidden or 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
// see: https://kentcdodds.com/blog/implementing-a-simple-state-machine-library-in-javascript | |
// mine is different, but that's where I got the original logic. | |
// is this an object? | |
const isObject = something => { | |
if(typeof something === 'object' && something){ | |
return true | |
}else{ | |
return false | |
} | |
} | |
// is this a function? | |
const isFunction = something => { | |
if(typeof something === 'function'){ | |
return true | |
}else{ | |
return false | |
} | |
} | |
// transform a regular old object into a Map | |
const objectToMap = plainObject => { | |
const internalMap = new Map() | |
if(isObject(plainObject)){ | |
const keys = Object.keys(plainObject) | |
for(const key of keys){ | |
internalMap.set(key, plainObject[key]) | |
} | |
} | |
return internalMap | |
} | |
// create a potential machine state. | |
const createState = ({ actions, transitions }) => { | |
const possibleTransitionsMap = objectToMap(transitions) | |
const possibleActionsMap = objectToMap(actions) | |
const canTransitionTo = transitionName => { | |
return possibleTransitionsMap.has(transitionName) | |
} | |
const canPerformAction = actionName => { | |
return possibleActionsMap.has(actionName) | |
} | |
return { | |
actions, | |
transitions, | |
canTransitionTo, | |
canPerformAction | |
} | |
} | |
// create a transition from a state. | |
const createTransition = ({ target, action }) => { | |
return { | |
target, | |
action | |
} | |
} | |
// create a finite state machine | |
const createMachine = ({ | |
initialState, | |
...otherStates | |
}) => { | |
let possibleStateMap = objectToMap(otherStates) | |
let internalStateName = null | |
if(possibleStateMap.has(initialState)){ | |
internalStateName = initialState | |
}else{ | |
console.error(`createMachine Error: The initialState provided: "${initialState}" does not match any of the possible states in this machine.`) | |
} | |
const transition = (currentStateName, transitionName) => { | |
if(possibleStateMap.has(currentStateName)){ | |
const currentStateInstance = possibleStateMap.get(currentStateName) | |
if(currentStateInstance.canTransitionTo(transitionName)){ | |
const { target, action } = currentStateInstance.transitions[transitionName] | |
if (possibleStateMap.has(target)){ | |
const destinationState = possibleStateMap.get(target) | |
if(isFunction(action)){ | |
action() | |
} | |
if(currentStateInstance.canPerformAction('onExit')){ | |
currentStateInstance.actions.onExit() | |
} | |
if(destinationState.canPerformAction('onEnter')){ | |
destinationState.actions.onEnter() | |
} | |
internalStateName = target | |
}else{ | |
console.error(`Transition Error: Failed to transition as there is no state with the name "${target}".`) | |
} | |
}else{ | |
console.error(`Transition Error: The State: "${currentStateName}" cannot perform transition: "${transitionName}".`) | |
} | |
} else { | |
console.error(`Transition Error: There is not a state in this machine with the name: "${currentStateName}".`) | |
} | |
return internalStateName | |
} | |
return { | |
get state(){ | |
return internalStateName | |
}, | |
transition | |
} | |
} | |
// an example state machine. | |
const machine = createMachine({ | |
initialState: 'off', | |
off: createState({ | |
actions:{ | |
onEnter(){ | |
console.log('off: onEnter') | |
}, | |
onExit(){ | |
console.log('off: onExit') | |
} | |
}, | |
transitions:{ | |
toggle: createTransition({ | |
target: 'on', | |
action(){ | |
console.log('Transition action for "toggle" while in the "off" state moving to the "on" state.') | |
} | |
}) | |
} | |
}), | |
on: createState({ | |
actions:{ | |
onEnter(){ | |
console.log('on: onEnter') | |
}, | |
onExit(){ | |
console.log('on: onExit') | |
} | |
}, | |
transitions:{ | |
toggle:createTransition({ | |
target: 'off', | |
action(){ | |
console.log('Transition action for "toggle" while in the "on" state moving to the "off" state.') | |
} | |
}) | |
} | |
}) | |
}) | |
let state = machine.state | |
console.log(`current state: ${state}`) | |
state = machine.transition(state, 'toggle') | |
console.log(`current state: ${state}`) | |
state = machine.transition(state, 'toggle') | |
console.log(`current state: ${state}`) | |
console.log('--- --- ---') | |
const machine2 = createMachine({ | |
initialState: 'unplugged', | |
unplugged: createState({ | |
actions:{ | |
onEnter(){ | |
console.log('The device is now unplugged. Try "plugIn" to make this device more useful.') | |
} | |
}, | |
transitions:{ | |
plugIn: createTransition({ | |
target: 'pluggedIn', | |
action(){ | |
console.log('Plugging in the device...') | |
} | |
}) | |
} | |
}), | |
pluggedIn: createState({ | |
actions:{ | |
onEnter(){ | |
console.log('The device is now plugged in and waiting try "startShow".') | |
} | |
}, | |
transitions:{ | |
unplug: createTransition({ | |
target: 'unplugged', | |
action(){ | |
console.log('Yanking the power cable.') | |
} | |
}), | |
startShow: createTransition({ | |
target: 'showStarted', | |
action(){ | |
console.log('Starting the Light Show!') | |
} | |
}) | |
} | |
}), | |
showStarted: createState({ | |
actions:{ | |
onEnter(){ | |
console.log('You see a beautiful lightshow begin to dance around the stage.') | |
} | |
}, | |
transitions:{ | |
unplug: createTransition({ | |
target: 'unplugged', | |
action(){ | |
console.log('Yanking the power cable.') | |
console.log('The light show suddenly shuts off abruptly.') | |
} | |
}), | |
stopShow: createTransition({ | |
target: 'pluggedIn', | |
action(){ | |
console.log('The light show gracefully fades out.') | |
} | |
}) | |
} | |
}) | |
}) | |
let state2 = machine2.state | |
console.log('m2: ', state2) | |
state2 = machine2.transition(state2, 'plugIn') | |
console.log('m2: ', state2) | |
state2 = machine2.transition(state2, 'unplug') | |
console.log('m2: ', state2) | |
state2 = machine2.transition(state2, 'plugIn') | |
console.log('m2: ', state2) | |
state2 = machine2.transition(state2, 'startShow') | |
console.log('m2: ', state2) | |
state2 = machine2.transition(state2, 'unplug') | |
console.log('m2: ', state2) | |
state2 = machine2.transition(state2, 'plugIn') | |
console.log('m2: ', state2) | |
state2 = machine2.transition(state2, 'startShow') | |
console.log('m2: ', state2) | |
state2 = machine2.transition(state2, 'stopShow') | |
console.log('m2: ', state2) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment