Engine
The Engine<TState> base class is the state coordinator at the heart of every game.
Usage
import { Engine } from "quantum-forge-engine/engine";
interface GameState {
player: { x: number; y: number; health: number };
enemies: Enemy[];
score: number;
}
class MyEngine extends Engine<GameState> {
constructor(logger?: any) {
const initialState: GameState = {
player: { x: 400, y: 300, health: 100 },
enemies: [],
score: 0,
};
super(initialState, { logger });
}
getHelpers() {
return {
movePlayer: (dx: number, dy: number) => {
const state = this.getState();
this.setState({
...state,
player: { ...state.player, x: state.player.x + dx, y: state.player.y + dy },
});
},
spawnEnemy: (x: number, y: number) => {
const state = this.getState();
this.setState({
...state,
enemies: [...state.enemies, { x, y, health: 50 }],
});
},
addScore: (points: number) => {
const state = this.getState();
this.setState({ ...state, score: state.score + points });
},
};
}
reset() {
this.setState({
player: { x: 400, y: 300, health: 100 },
enemies: [],
score: 0,
});
}
}API
| Method | Description |
|---|---|
getState() | Returns Readonly<TState>. Spread it into a new object instead of mutating |
getHelpers() | Abstract. Implement it to return state-mutating functions |
reset() | Abstract. Implement it to reset to initial state |
setState(newState) is protected. It is the helper's tool, not part of the public surface, so callers outside the subclass cannot reach it.
Config
interface EngineConfig {
logger?: LoggerInterface;
eventBus?: EventBus<any>;
}With an eventBus, every setState() emits "state-changed" carrying { state }. That is the hook renderers and UI subscribe to instead of polling getState().
class MyEngine extends Engine<GameState> {
constructor(config: EngineConfig = {}) {
super(initialState, config);
}
// ...
}
const eventBus = new EventBus<MyGameEvents>();
const engine = new MyEngine({ logger, eventBus });
eventBus.on("state-changed", ({ state }) => hud.update(state));See Events.
Quantum state in the engine
Quantum handles live in the engine's state, on the objects they describe. Helpers create the handles, run gates on them, and dispose them when their object leaves:
import { quantum, type Quantum } from "quantum-forge/quantum";
interface Ball {
id: string;
x: number;
y: number;
exists?: Quantum<boolean>; // no handle: a classical ball that always exists
}
class PongEngine extends Engine<PongState> {
constructor(logger?: any) {
super(initialState, { logger });
}
reset() {
for (const ball of this.getState().balls) ball.exists?.dispose();
this.setState(initialState);
}
getHelpers() {
return {
enterQuantumZone: (ballId: string) => {
const state = this.getState();
const ball = state.balls.find((b) => b.id === ballId);
if (!ball || ball.exists) return;
const original = quantum([false, true]).flip(); // exists
const ghost = quantum([false, true]); // does not
original.iSwap(ghost, 0.5); // exactly one of them exists
this.setState({
...state,
balls: [
...state.balls.map((b) => (b.id === ballId ? { ...b, exists: original } : b)),
{ ...ball, id: `${ballId}-ghost`, exists: ghost },
],
});
},
scoreBall: (ballId: string) => {
const state = this.getState();
const ball = state.balls.find((b) => b.id === ballId);
if (!ball) return;
const exists = ball.exists ? ball.exists.measure() : true;
ball.exists?.dispose(); // the twin has already collapsed
this.setState({
...state,
score: state.score + (exists ? 1 : 0),
balls: state.balls.filter((b) => b.id !== ballId),
});
},
};
}
}A handle is a reference, so a shallow copy of the ball in setState keeps the same handle. Dispose a handle when the object it belongs to leaves the game, or keep the objects in an EntityManager, which disposes them for you. See Lifecycle.