@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
134 lines • 5.43 kB
TypeScript
/**
* 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