@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
120 lines (109 loc) • 4.3 kB
JavaScript
import Signal from "../../../core/events/signal/Signal.js";
/**
* Base class for transport adapters that ferry opaque byte buffers between two
* endpoints. Assumes UDP-style semantics: unreliable, unordered, no flow
* control. Reliability or ordering guarantees layer above (see {@link Channel}).
*
* Subclasses override `send` (mandatory) and may override `connect`,
* `disconnect`, or `getStats` (defaults provided). They can write to
* `onReceive` directly when bytes arrive.
*
* Concrete subclasses in this codebase:
* - {@link LoopbackTransport} — paired in-process queue for unit tests
* - {@link SimulatedTransport} — paired in-process queue with simulated
* latency / jitter / loss for prototypes and stress tests
* - {@link WebRTCDataChannelTransport} — browser RTCDataChannel wrapper
* - {@link WebTransportTransport} — browser WebTransport datagrams (HTTP/3)
* - {@link NodeUDPTransport} — Node.js dgram wrapper
* - {@link WebSocketTransport} — browser WebSocket wrapper (lobby/chat only;
* not for game state — TCP head-of-line blocking)
*
* The duck-typed contract was previously documented here as JSDoc; it has been
* promoted to this concrete base so that `instanceof Transport` works, IDEs
* complete the interface, and the relationship between adapters is visible.
*
* @author Alex Goldring
* @copyright Company Named Limited (c) 2025
*/
export class Transport {
/**
* Whether this transport guarantees delivery of every packet sent.
* `true` for TCP-style (WebSocket, WebTransport streams);
* `false` for UDP-style (raw UDP, WebRTC unreliable data channels,
* WebTransport datagrams).
*
* Higher layers (e.g. `Channel`, `ReliableCommandPipeline`) can
* skip retransmit / dedup work when this is `true`. Defaults to
* `false` (the conservative assumption — assume loss is possible).
* @type {boolean}
*/
reliable = false;
/**
* Whether this transport guarantees in-order delivery between any
* two packets sent on it. `true` for TCP-style;
* `false` for UDP-style.
*
* Higher layers can skip reorder buffering when this is `true`.
* Defaults to `false`.
* @type {boolean}
*/
ordered = false;
/**
* Fired when a packet arrives. Handlers receive `(bytes, length)` where
* `bytes` is a `Uint8Array` valid for the duration of the call only;
* copy if you need to keep it.
* @type {Signal}
*/
onReceive = new Signal();
/**
* Fired when the transport detects that its connection has been lost
* (peer-disconnected, link-dropped, socket-closed). Subscribers
* (typically {@link NetworkSession}) react by entering the
* reconnect ladder. Adapters that never detect this on their own
* (loopback) only fire when explicitly told to.
*
* Handlers receive `(reason)` where `reason` is an optional
* implementation-specific string for diagnostics.
*
* @type {Signal}
*/
onDisconnect = new Signal();
/**
* Synchronously enqueue `length` bytes from `bytes` for delivery.
* Caller may reuse `bytes` immediately after the call returns; transport
* implementations must copy if they need to defer delivery.
*
* @param {Uint8Array} bytes
* @param {number} length
* @returns {void}
*/
send(bytes, length) {
throw new Error('Transport subclasses must override send(bytes, length).');
}
/**
* Optional connection lifecycle. Default no-op for transports that are
* already-open at construction (e.g. in-process loopback).
* @returns {Promise<void>|void}
*/
connect() {
}
/**
* Optional teardown. Default no-op.
*/
disconnect() {
}
/**
* Read-only stats snapshot. Default returns zeroes; subclasses are expected
* to track their own counters.
* @returns {{bytes_in: number, bytes_out: number, packets_in: number, packets_out: number}}
*/
getStats() {
return { bytes_in: 0, bytes_out: 0, packets_in: 0, packets_out: 0 };
}
}
/**
* Fast type check; cheaper than `instanceof Transport` in tight loops.
* @readonly
* @type {boolean}
*/
Transport.prototype.isTransport = true;