XStateJS Notes

Posted Wednesday, April 20, 2022 by Sri.Tagged MEMO
EDIT STATUS:new

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 for Machine.transition()
  • Parallel State Nodes - You can further define a states property inside a state 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 that context and event 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. The cond 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 optional subscribe() and stop() . I think a master Actor machine can be used as a template to spawn() 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: