Flattened Traffic Light
A traffic light controller demonstrating flattened hierarchical machines. The controller has three modes (Broken, Working, Maintenance), and the Working mode contains a nested light cycle (Red → Green → Yellow → Red).
Key Concepts
Section titled “Key Concepts”defineSubmachine- Define a nested machine inline within a parent stateflattenMachineDefinition- Convert hierarchical definition to flat states with dot-notation keyscreateMachineFromFlat- Create a machine from the flattened definition
The flattened approach creates states like Working.Red, Working.Green, Working.Yellow instead of separate parent/child machine instances.
import { defineStates, defineMachine, defineSubmachine, flattenMachineDefinition, createMachineFromFlat} from "matchina";
/** * Traffic Light Controller using flattened hierarchical machine * * Structure: * - Broken: light is broken * - Working: contains nested light cycle (Red -> Green -> Yellow -> Red) * - Maintenance: under maintenance */
// Define the nested light cycle as a submachineconst lightCycle = defineSubmachine( defineStates({ Red: undefined, Green: undefined, Yellow: undefined }), { Red: { tick: "Green" }, Green: { tick: "Yellow" }, Yellow: { tick: "Red" }, }, "Red");
// Define the controller with Working state containing the light cycleconst controllerDef = defineMachine( defineStates({ Broken: undefined, Working: lightCycle, Maintenance: undefined, }), { Broken: { repair: "Working", maintenance: "Maintenance" }, Working: { break: "Broken", maintenance: "Maintenance" }, Maintenance: { complete: "Working" }, }, "Working");
// Flatten and create the machineconst flatDef = flattenMachineDefinition(controllerDef);
export function createTrafficLightMachine() { return createMachineFromFlat(flatDef);}
// Helper to parse hierarchical state keyexport function parseStateKey(key: string) { const parts = key.split("."); return { parent: parts[0], child: parts[1] || null, full: key, };}import { useMachine } from "matchina/react";import { createTrafficLightMachine, parseStateKey } from "./machine";
interface TrafficLightViewProps { machine: ReturnType<typeof createTrafficLightMachine>;}
export function TrafficLightView({ machine }: TrafficLightViewProps) { const change = useMachine(machine); const state = change.to; const send = (event: string) => machine.send(event); const { parent, child } = parseStateKey(state.key);
const isWorking = parent === "Working"; const lightColor = child || "off";
return ( <div className="p-4 space-y-4"> <div className="text-center"> <div className="text-sm text-gray-500 mb-2">Controller: {parent}</div>
{/* Traffic Light Display */} <div className="inline-block bg-gray-800 p-4 rounded-lg"> <div className="space-y-2"> <Light color="red" active={isWorking && lightColor === "Red"} /> <Light color="yellow" active={isWorking && lightColor === "Yellow"} /> <Light color="green" active={isWorking && lightColor === "Green"} /> </div> </div>
{child && ( <div className="text-sm text-gray-500 mt-2">Light: {child}</div> )} </div>
{/* Controls */} <div className="flex flex-wrap gap-2 justify-center"> {isWorking && ( <button onClick={() => send("tick")} className="px-3 py-1 bg-blue-500 text-white rounded hover:bg-blue-600" > Tick </button> )}
{parent === "Broken" && ( <button onClick={() => send("repair")} className="px-3 py-1 bg-green-500 text-white rounded hover:bg-green-600" > Repair </button> )}
{parent === "Maintenance" && ( <button onClick={() => send("complete")} className="px-3 py-1 bg-green-500 text-white rounded hover:bg-green-600" > Complete </button> )}
{(isWorking || parent === "Broken") && ( <button onClick={() => send("maintenance")} className="px-3 py-1 bg-yellow-500 text-white rounded hover:bg-yellow-600" > Maintenance </button> )}
{isWorking && ( <button onClick={() => send("break")} className="px-3 py-1 bg-red-500 text-white rounded hover:bg-red-600" > Break </button> )} </div>
{/* State Info */} <div className="text-xs text-gray-400 text-center"> Full state key: <code>{state.key}</code> </div> </div> );}
function Light({ color, active }: { color: string; active: boolean }) { const colorClasses: Record<string, string> = { red: active ? "bg-red-500" : "bg-red-900", yellow: active ? "bg-yellow-400" : "bg-yellow-900", green: active ? "bg-green-500" : "bg-green-900", };
return ( <div className={`w-12 h-12 rounded-full ${colorClasses[color]} transition-colors duration-300`} style={{ boxShadow: active ? `0 0 20px ${color}` : "none", }} /> );}
export default TrafficLightView;How It Works
Section titled “How It Works”-
Define the child machine using
defineSubmachine:const lightCycle = defineSubmachine(defineStates({ Red: undefined, Green: undefined, Yellow: undefined }),{ Red: { tick: "Green" }, Green: { tick: "Yellow" }, Yellow: { tick: "Red" } },"Red"); -
Use it as a state factory in the parent:
const controllerDef = defineMachine(defineStates({Broken: undefined,Working: lightCycle, // Nested machine hereMaintenance: undefined,}),{ /* transitions */ },"Working"); -
Flatten and create:
const flatDef = flattenMachineDefinition(controllerDef);const machine = createMachineFromFlat(flatDef);
The resulting machine has states: Broken, Working.Red, Working.Green, Working.Yellow, Maintenance.
Benefits of Flattening
Section titled “Benefits of Flattening”- Single machine instance - No parent/child coordination needed
- Simple state access - Just check
state.key(e.g.,"Working.Red") - All transitions visible - Both parent and child transitions work seamlessly
- Easy serialization - State is just a string key