@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
133 lines • 5.37 kB
TypeScript
/**
* Base class for synchronous, serializable, replicable game-state mutations.
*
* Distinct from `core/process/undo/Action.js` (async, designed for editor
* undo/redo). `SimAction` is the netcode-tier counterpart: sync `apply`,
* no `revert` (rewind is handled by the executor's prior-bytes capture),
* and serializable to/from a `BinaryBuffer`.
*
* **Contract**: any mutation to replicated state goes through a `SimAction`
* passed to {@link SimActionExecutor#execute}. Anything else must be a
* deterministic consequence of the simulation, or rewind / replay /
* replication will desync.
*
* Wire vs local: actions reference entities by `network_id` (peer-shared);
* `executor.slot_table.entity_for(network_id)` translates to the local id.
*
* @author Alex Goldring
* @copyright Company Named Limited (c) 2025
*/
export class SimAction {
/**
* Stable network identifier for this action class. Set by
* {@link SimActionRegistry.register}. Do not assign manually.
* @type {number}
*/
static type_id: number;
/**
* Apply this action's effect to the world. The executor has already captured
* prior bytes of the components reported by {@link affected_components} before
* this is called, so direct mutation of those components is safe.
*
* @param {EntityComponentDataset} world
* @param {SimActionExecutor} executor context — exposes `slot_table` for
* network_id ↔ entity_id translation, and other framework state
* @returns {void}
*/
apply(world: EntityComponentDataset, executor: SimActionExecutor): void;
/**
* Report each (local_entity_id, component_class) pair this action will mutate.
* The executor invokes `callback(local_entity_id, component_class)` for each
* and uses the result to capture prior bytes for rewind.
*
* If your action stores a `network_id` (the canonical wire form), translate it:
* ```
* affected_components(cb, executor) {
* const local = executor.slot_table.entity_for(this.network_id);
* cb(local, Transform);
* }
* ```
*
* Default: no affected components (e.g. for a pure event-style action).
*
* @param {function(number, Function): void} callback
* @param {SimActionExecutor} executor context
*/
affected_components(callback: (arg0: number, arg1: Function) => void, executor: SimActionExecutor): void;
/**
* Serialize this action's forward parameters into the buffer. Do NOT include
* prior state — the executor captures that via affected_components+adapters.
*
* @param {BinaryBuffer} buffer
* @returns {void}
*/
serialize(buffer: BinaryBuffer): void;
/**
* Deserialize forward parameters from the buffer into this instance.
*
* @param {BinaryBuffer} buffer
* @returns {void}
*/
deserialize(buffer: BinaryBuffer): void;
/**
* Reset to a blank state for reuse from the registry's pool.
* Override if your action holds non-trivial fields.
*/
reset(): void;
/**
* Fast type check, no instanceof needed.
* @readonly
* @type {boolean}
*/
readonly isSimAction: boolean;
}
export namespace SimAction {
/**
* Generate a `SimAction` subclass declaratively. The helper writes the
* constructor / serialize / deserialize / reset boilerplate via
* `FunctionCompiler` so each action ends up with a specialised, straight-
* line function instead of a generic schema-loop.
*
* ```js
* const MoveAction = SimAction.extend({
* type: 'Move',
* schema: { network_id: 'uintVar', dx: 'float32' },
* affects(executor) {
* const entity = executor.slot_table.entity_for(this.network_id);
* return entity < 0 ? [] : [[entity, Transform]];
* },
* apply(world, executor) {
* const entity = executor.slot_table.entity_for(this.network_id);
* if (entity < 0) return;
* world.getComponent(entity, Transform).position[0] += this.dx;
* },
* });
*
* new MoveAction(network_id, dx); // positional, in schema-key order
* ```
*
* Schema key insertion order is also wire-byte order and constructor
* positional order. Schema types must be one of {@link SCHEMA_CODEGEN}'s
* keys; for more complex shapes (vec3, quat, bitfields, variable length)
* subclass `SimAction` directly. `type` is retained as
* `action_type_name` for diagnostics; the wire `type_id` is assigned
* by the registry at registration time.
*
* @param {{
* type: string,
* schema?: Object<string, string>,
* affects?: (executor: SimActionExecutor) => Array<[number, Function]>,
* apply: (world: EntityComponentDataset, executor: SimActionExecutor) => void,
* }} spec
* @returns {Function} a `SimAction` subclass ready to register
*/
function extend({ type, schema, affects, apply }: {
type: string;
schema?: {
[x: string]: string;
};
affects?: (executor: SimActionExecutor) => [number, Function][];
apply: (world: EntityComponentDataset, executor: SimActionExecutor) => void;
}): Function;
}
//# sourceMappingURL=SimAction.d.ts.map