UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

134 lines 5.43 kB
/** * Forwards action records between peers. * * **Send path** ({@link pack_for_peer}): walk the local action log over a * frame range, filter records by scope, and write a packet that contains * only the action portion of each record (no prior state — receivers don't * need it; their executors capture their own prior state). * * **Receive path** ({@link unpack_from_peer}): parse the packet, deserialize * each action via the registry, and run it through the local executor. The * executor logs it into the receiver's action log just like locally-originated * actions, capturing the receiver's prior state for its own rewind purposes. * * The Replicator is transport-agnostic: it produces and consumes * `BinaryBuffer`s. The orchestrator hands the produced buffer to a transport * and feeds the inbound buffer in the other direction. * * Wire format: * ``` * loop while bytes remain: * varint: frame_number * varint: action_count * loop action_count times: * uint8: action_type_id * uint32: action_payload_len * bytes: action_payload * ``` * * @author Alex Goldring * @copyright Company Named Limited (c) 2025 */ export class Replicator { /** * @param {{ * action_log: ActionLog, * action_registry: SimActionRegistry, * executor: SimActionExecutor, * slot_table: ReplicationSlotTable, * scope_filter: { is_entity_in_scope(peer_id: number, network_id: number): boolean }, * }} options */ constructor({ action_log, action_registry, executor, slot_table, scope_filter }: { action_log: ActionLog; action_registry: SimActionRegistry; executor: SimActionExecutor; slot_table: ReplicationSlotTable; scope_filter: { is_entity_in_scope(peer_id: number, network_id: number): boolean; }; }); /** * @type {ActionLog} */ action_log: ActionLog; /** * @type {SimActionRegistry} */ action_registry: SimActionRegistry; /** * @type {SimActionExecutor} */ executor: SimActionExecutor; /** * @type {ReplicationSlotTable} */ slot_table: ReplicationSlotTable; /** * Receives `network_id` (peer-shared) — NOT local `entity_id`. Replicator * translates via `slot_table.network_for(entity_id)` before calling. * @type {{ is_entity_in_scope(peer_id: number, network_id: number): boolean }} */ scope_filter: { is_entity_in_scope(peer_id: number, network_id: number): boolean; }; /** * Fired after each per-frame action group is fully applied via * {@link unpack_from_peer}. Args: `(peer_id, frame_number)`. * * Subscribe to drive interpolation buffers, replay logs, diagnostics, * or anything else that needs to know "frame N from peer P has just * been applied to the local world." * * Fires in frame-ascending order within a single packet (the order * the wire format encodes them in). * * @type {Signal} */ onFrameApplied: Signal; /** * Optional deferral hook. When non-null, {@link unpack_from_peer} * does NOT touch the action log or executor — it parses the packet * and calls this function once per action with: * * `(peer_id, frame_number, action_type_id, in_buffer, payload_offset, payload_len)` * * The buffer is shared scratch; consumers must copy the payload bytes * if they need them to outlive the call. The orchestrator is then * responsible for executing the buffered actions in whatever order * the netcode model requires (e.g. rewind to oldest frame, stable- * sort by sender, replay forward). * * Leave null for "execute on arrival" semantics (the default and * what the loopback/single-orchestrator tests rely on). * * @type {((peer_id: number, frame_number: number, action_type_id: number, in_buffer: BinaryBuffer, payload_offset: number, payload_len: number) => void) | null} */ on_pending_action: (peer_id: number, frame_number: number, action_type_id: number, in_buffer: BinaryBuffer, payload_offset: number, payload_len: number) => void; /** * Pack actions from frames in `[start_frame, end_frame]` (inclusive) into * `out_buffer` for a given peer, applying the scope filter. * * Writes nothing if no in-scope actions are found. * * @param {number} peer_id * @param {number} start_frame inclusive; if no frame in range is in the log, that frame is skipped * @param {number} end_frame inclusive * @param {BinaryBuffer} out_buffer */ pack_for_peer(peer_id: number, start_frame: number, end_frame: number, out_buffer: BinaryBuffer): void; /** * Parse and apply actions from `in_buffer` into the local world via the executor. * Each frame in the packet opens a new entry in the local action log under the * sender's frame number, ensuring the rewind/replay machinery sees consistent * frame indexing across peers. * * @param {number} peer_id * @param {BinaryBuffer} in_buffer * @param {number} in_buffer_end byte position to stop reading at */ unpack_from_peer(peer_id: number, in_buffer: BinaryBuffer, in_buffer_end: number): void; #private; } import Signal from "../../../core/events/signal/Signal.js"; //# sourceMappingURL=Replicator.d.ts.map