Skip to content

Operations

The Operation pattern provides an extensible, testable, replayable way to handle complex game actions. Use it when your game has many distinct operations that need guards, async support, or undo.

When to Use

The Operation pattern is opt-in — for complex games only. Simple games can use engine helpers directly. Consider operations when:

  • You have 10+ distinct game actions
  • Actions need guard conditions (can this action happen now?)
  • You want undo/redo
  • Actions have async components (network, animation callbacks)

Registry + Executor

typescript
import { OperationRegistry, OperationExecutor } from "quantum-forge/operations";

const registry = new OperationRegistry();

// Register an operation
registry.register({
  key: "move",
  canApply: (ctx) => {
    const state = ctx.getState();
    return state.player.canMove;
  },
  apply: (ctx) => {
    const state = ctx.getState();
    state.player.x += ctx.params.dx;
    state.player.y += ctx.params.dy;
    ctx.setState({ ...state });
    return { type: "sync" };
  },
});

// Register an async operation
registry.register({
  key: "attack",
  canApply: (ctx) => ctx.getState().player.energy > 0,
  apply: (ctx) => {
    const state = ctx.getState();
    state.player.energy -= 10;
    ctx.setState({ ...state });
    return {
      type: "deferred",
      promise: animateAttack().then(() => {
        // Post-animation state update
        const s = ctx.getState();
        s.enemy.health -= 25;
        ctx.setState({ ...s });
      }),
    };
  },
});

// Execute
const executor = new OperationExecutor(registry);

executor.executeSync("move", context);       // fire-and-forget deferred
await executor.execute("attack", context);   // await deferred promise

Operation Structure

Each operation has:

FieldTypeDescription
keystringUnique identifier
canApply(ctx) => booleanGuard — can this run now?
apply(ctx) => ResultExecute the operation

The apply function returns:

  • { type: "sync" } — completed immediately
  • { type: "deferred", promise: Promise } — has async follow-up

Context Object

The executor passes a context to each operation. Your game defines the context shape:

typescript
interface GameContext {
  getState(): GameState;
  setState(state: GameState): void;
  params: Record<string, any>;
  registry: QuantumRegistry;  // quantum access
}

Why Operations?

  • Extensible — add new operations without modifying core code
  • Testable — operations are data, easy to unit test
  • Replayable — store operations for undo/replay
  • GuardedcanApply prevents invalid state transitions
  • Async-friendly — deferred pattern handles animation callbacks

Powered by Quantum Forge