XState
XStateJS is a state machine implementation of State Chart XML. The claimed advantages of using it is that handles edge cases for state machines that we otherwise might have to discover the hard way. There are also some interesting support packages for React, graphing, and a visualizer which might be helpful for working through logic.
There's a simple implementation called xstate-fsm for a finite state machine which is good to start with. The full xstate packages adds all kinds of things like history, activity, etc.
Using XState FSM
First you create a machine, and then you create a service that uses the machine.
Machine
The machine is the data structure that defines all the states. It's comprised of id, initial state, and a list of states. Each state is a property of the states object, defining the transitions from one state to another via events.
import { createMachine } from '@xstate/fsm';
// Stateless finite state machine definition
// machine.transition(...) is a pure function.
const toggleMachine = createMachine({
id: 'toggle',
initial: 'inactive',
states: {
inactive: { on: { TOGGLE: 'active' } }, // transition to 'active' on TOGGLE event
active: { on: { TOGGLE: 'inactive' } } // transition to 'inactive' on TOGGLE event
}
});
// access the machine data structures
const { initialState } = toggleMachine;
const toggledState = toggleMachine.transition(initialState, 'TOGGLE');
toggledState.value;
// => 'active'
const untoggledState = toggleMachine.transition(toggledState, 'TOGGLE');
untoggledState.value;
// => 'inactive'
Note that the machine definition is only a data structure with accessor methods. To run the machine, you need an implementor service to interpret it.
Service
A service implements the finite state machine by attaching code that can subscribe to a state change and send events.
import { interpret } from '@xstate/fsm';
// toggleMachine was defined in previous code block
const toggleService = interpret(toggleMachine).start();
toggleService.subscribe(state => {
console.log(state.value);
});
toggleService.send('TOGGLE');
// => logs 'active'
toggleService.send('TOGGLE');
// => logs 'inactive'
toggleService.stop();
Using XState
When a Finite State Machine isn't enough, the full XStateJS package provides these additional features.
- Hierarchical State Nodes - You can nest a state inside another one. The returned state name will have a dot in the name (e.g. a 'walk' state that is nested in a 'red' state would be called 'red.walk'). The object notation version can be extracted using the
.value
getter forMachine.transition()
- Parallel State Nodes - You can further define a
states
property inside astate
definition, creating multiple machines that can run in parallel. - History State Nodes - A special state node that looks up the last active state, or the initial state if there is no history. A history state node isn't a destination in itself.
- Actions - are instantaneous "fire and forget" side effects. They can happen on entry, exit, and transition. The actions are defined in an
actions
object of function properties that receives thatcontext
andevent
that spawned it. - Activities - an action that takes a non-zero amount of time. The machine definition can include an
activity
property that initialize the activity in code, and returns a function to stop it. - Guarded Transitions - You can specify
guards
that receive context, event and return true. Thecond
property can be specified in a state transition definition to "guard" if the condition isn't met. - Context - can specify properties related to the state machine
- Invoking Services - It's encouraged to create multiple machines that talk to each other. A parent machine invokes a child machine and listens to events from
sendParent()
or waits for child to complete. These are always hierarchical relationships. - Actors - Similar to invoking services, actors can send messages to other actors, spawn new actors, or change its local state depending on behavior. Actors have their own
send()
interface, and also an optionalsubscribe()
andstop()
. I think a master Actor machine can be used as a template tospawn()
derivatives. There aren't many examples of this other than communicating with spawned actors. - Interpreters - This is what actually interprets the state machine in a useful way.
Looking for examples of xstate in game development:
- This discussion is a small poll of who is using XState in anything.
- This article by create David K Piano discusses the shortcomings of Redux and compares to the Actor model.
- This description of What's new in XState 4.7 is also a nice overview of XState.
- On YCombinator Ask HN: Do You use State Machines for UI Dev, has some interesting comments!
- Making User Interfaces with Finate Automata is highly watchable (video 2017 by DavidK)
- This article on State patterns for video games gives a good overview of FSMs and the extensions that make them more useful. State machines don't replace [Behavior trees]