# State Machine (FSM) `@esengine/fsm` provides a type-safe finite state machine implementation for characters, AI, or any scenario requiring state management. ## Installation ```bash npm install @esengine/fsm ``` ## Quick Start ```typescript import { createStateMachine } from '@esengine/fsm'; // Define state types type PlayerState = 'idle' | 'walk' | 'run' | 'jump'; // Create state machine const fsm = createStateMachine('idle'); // Define states with callbacks fsm.defineState('idle', { onEnter: (ctx, from) => console.log(`Entered idle from ${from}`), onExit: (ctx, to) => console.log(`Exiting idle to ${to}`), onUpdate: (ctx, dt) => { /* Update every frame */ } }); fsm.defineState('walk', { onEnter: () => console.log('Started walking') }); // Manual transition fsm.transition('walk'); console.log(fsm.current); // 'walk' ``` ## Core Concepts ### State Configuration Each state can be configured with the following callbacks: ```typescript interface StateConfig { name: TState; // State name onEnter?: (context: TContext, from: TState | null) => void; // Enter callback onExit?: (context: TContext, to: TState) => void; // Exit callback onUpdate?: (context: TContext, deltaTime: number) => void; // Update callback tags?: string[]; // State tags metadata?: Record; // Metadata } ``` ### Transition Conditions Define conditional state transitions: ```typescript interface Context { isMoving: boolean; isRunning: boolean; isGrounded: boolean; } const fsm = createStateMachine('idle', { context: { isMoving: false, isRunning: false, isGrounded: true } }); // Define transition conditions fsm.defineTransition('idle', 'walk', (ctx) => ctx.isMoving); fsm.defineTransition('walk', 'run', (ctx) => ctx.isRunning); fsm.defineTransition('walk', 'idle', (ctx) => !ctx.isMoving); // Automatically evaluate and execute matching transitions fsm.evaluateTransitions(); ``` ### Transition Priority When multiple transitions are valid, higher priority executes first: ```typescript // Higher priority number = higher priority fsm.defineTransition('idle', 'attack', (ctx) => ctx.isAttacking, 10); fsm.defineTransition('idle', 'walk', (ctx) => ctx.isMoving, 1); // If both conditions are met, 'attack' (priority 10) is tried first ``` ## API Reference ### createStateMachine ```typescript function createStateMachine( initialState: TState, options?: StateMachineOptions ): IStateMachine ``` **Parameters:** - `initialState` - Initial state - `options.context` - Context object, accessible in callbacks - `options.maxHistorySize` - Maximum history entries (default 100) - `options.enableHistory` - Enable history tracking (default true) ### State Machine Properties | Property | Type | Description | |----------|------|-------------| | `current` | `TState` | Current state | | `previous` | `TState \| null` | Previous state | | `context` | `TContext` | Context object | | `isTransitioning` | `boolean` | Whether currently transitioning | | `currentStateDuration` | `number` | Current state duration (ms) | ### State Machine Methods #### State Definition ```typescript // Define state fsm.defineState('idle', { onEnter: (ctx, from) => {}, onExit: (ctx, to) => {}, onUpdate: (ctx, dt) => {} }); // Check if state exists fsm.hasState('idle'); // true // Get state configuration fsm.getStateConfig('idle'); // Get all states fsm.getStates(); // ['idle', 'walk', ...] ``` #### Transition Operations ```typescript // Define transition fsm.defineTransition('idle', 'walk', condition, priority); // Remove transition fsm.removeTransition('idle', 'walk'); // Get transitions from state fsm.getTransitionsFrom('idle'); // Check if transition is possible fsm.canTransition('walk'); // true/false // Manual transition fsm.transition('walk'); // Force transition (ignore conditions) fsm.transition('walk', true); // Auto-evaluate transition conditions fsm.evaluateTransitions(); ``` #### Lifecycle ```typescript // Update state machine (calls current state's onUpdate) fsm.update(deltaTime); // Reset state machine fsm.reset(); // Reset to current state fsm.reset('idle'); // Reset to specified state ``` #### Event Listeners ```typescript // Listen to entering specific state const unsubscribe = fsm.onEnter('walk', (from) => { console.log(`Entered walk from ${from}`); }); // Listen to exiting specific state fsm.onExit('walk', (to) => { console.log(`Exiting walk to ${to}`); }); // Listen to any state change fsm.onChange((event) => { console.log(`${event.from} -> ${event.to} at ${event.timestamp}`); }); // Unsubscribe unsubscribe(); ``` #### Debugging ```typescript // Get state history const history = fsm.getHistory(); // [{ from: 'idle', to: 'walk', timestamp: 1234567890 }, ...] // Clear history fsm.clearHistory(); // Get debug info const info = fsm.getDebugInfo(); // { current, previous, duration, stateCount, transitionCount, historySize } ``` ## Practical Examples ### Character State Machine ```typescript import { createStateMachine } from '@esengine/fsm'; type CharacterState = 'idle' | 'walk' | 'run' | 'jump' | 'fall' | 'attack'; interface CharacterContext { velocity: { x: number; y: number }; isGrounded: boolean; isAttacking: boolean; speed: number; } const characterFSM = createStateMachine('idle', { context: { velocity: { x: 0, y: 0 }, isGrounded: true, isAttacking: false, speed: 0 } }); // Define states characterFSM.defineState('idle', { onEnter: (ctx) => { ctx.speed = 0; } }); characterFSM.defineState('walk', { onEnter: (ctx) => { ctx.speed = 100; } }); characterFSM.defineState('run', { onEnter: (ctx) => { ctx.speed = 200; } }); // Define transitions characterFSM.defineTransition('idle', 'walk', (ctx) => Math.abs(ctx.velocity.x) > 0); characterFSM.defineTransition('walk', 'idle', (ctx) => ctx.velocity.x === 0); characterFSM.defineTransition('walk', 'run', (ctx) => Math.abs(ctx.velocity.x) > 150); // Jump has highest priority characterFSM.defineTransition('idle', 'jump', (ctx) => !ctx.isGrounded, 10); characterFSM.defineTransition('walk', 'jump', (ctx) => !ctx.isGrounded, 10); // Game loop usage function gameUpdate(dt: number) { // Update context characterFSM.context.velocity.x = getInputVelocity(); characterFSM.context.isGrounded = checkGrounded(); // Evaluate transitions characterFSM.evaluateTransitions(); // Update current state characterFSM.update(dt); } ``` ### ECS Integration ```typescript import { Component, EntitySystem, Matcher } from '@esengine/ecs-framework'; import { createStateMachine, type IStateMachine } from '@esengine/fsm'; // State machine component class FSMComponent extends Component { fsm: IStateMachine; constructor(initialState: string) { super(); this.fsm = createStateMachine(initialState); } } // State machine system class FSMSystem extends EntitySystem { constructor() { super(Matcher.all(FSMComponent)); } protected processEntity(entity: Entity, dt: number): void { const fsmComp = entity.getComponent(FSMComponent); fsmComp.fsm.evaluateTransitions(); fsmComp.fsm.update(dt); } } ``` ## Blueprint Nodes The FSM module provides blueprint nodes for visual scripting: - `GetCurrentState` - Get current state - `TransitionTo` - Transition to specified state - `CanTransition` - Check if transition is possible - `IsInState` - Check if in specified state - `WasInState` - Check if was ever in specified state - `GetStateDuration` - Get state duration - `EvaluateTransitions` - Evaluate transition conditions - `ResetStateMachine` - Reset state machine