@carverjs/multiplayer 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/NetworkManager-DrKM2tEx.d.mts +369 -0
- package/dist/NetworkManager-nvVAOr1O.d.ts +369 -0
- package/dist/chunk-3KT73N2S.mjs +655 -0
- package/dist/chunk-3KT73N2S.mjs.map +1 -0
- package/dist/chunk-EO3YNPRQ.mjs +817 -0
- package/dist/chunk-EO3YNPRQ.mjs.map +1 -0
- package/dist/chunk-UD6FDZMX.mjs +581 -0
- package/dist/chunk-UD6FDZMX.mjs.map +1 -0
- package/dist/firebase-CPu87KA0.d.ts +100 -0
- package/dist/firebase-PE6MxGdJ.d.mts +100 -0
- package/dist/index.d.mts +316 -0
- package/dist/index.d.ts +316 -0
- package/dist/index.js +3817 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1743 -0
- package/dist/index.mjs.map +1 -0
- package/dist/strategy.d.mts +7 -0
- package/dist/strategy.d.ts +7 -0
- package/dist/strategy.js +619 -0
- package/dist/strategy.js.map +1 -0
- package/dist/strategy.mjs +11 -0
- package/dist/strategy.mjs.map +1 -0
- package/dist/sync.d.mts +212 -0
- package/dist/sync.d.ts +212 -0
- package/dist/sync.js +845 -0
- package/dist/sync.js.map +1 -0
- package/dist/sync.mjs +11 -0
- package/dist/sync.mjs.map +1 -0
- package/dist/transport.d.mts +159 -0
- package/dist/transport.d.ts +159 -0
- package/dist/transport.js +1274 -0
- package/dist/transport.js.map +1 -0
- package/dist/transport.mjs +19 -0
- package/dist/transport.mjs.map +1 -0
- package/dist/types-5LHBOW08.d.mts +74 -0
- package/dist/types-5LHBOW08.d.ts +74 -0
- package/dist/types.d.mts +2 -0
- package/dist/types.d.ts +2 -0
- package/dist/types.js +19 -0
- package/dist/types.js.map +1 -0
- package/dist/types.mjs +1 -0
- package/dist/types.mjs.map +1 -0
- package/package.json +73 -0
package/dist/sync.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/sync/index.ts","../src/sync/EventSync.ts","../src/core/HostAuthority.ts","../src/core/ClientReceiver.ts","../src/sync/SnapshotSync.ts","../src/sync/PredictionSync.ts"],"sourcesContent":["// Sync engines\nexport { EventSync } from \"./EventSync\";\nexport { SnapshotSync } from \"./SnapshotSync\";\nexport { PredictionSync } from \"./PredictionSync\";\nexport type { SnapshotSyncOptions } from \"./SnapshotSync\";\n\n// Re-export types\nexport type {\n SyncMode,\n EntityState,\n EntityState2D,\n EntityState3D,\n SnapshotPacket,\n InputPacket,\n EventPacket,\n} from \"../types\";\n","import type { CarverTransport, CarverChannel, EventPacket } from \"../types\";\n\n/**\n * Layer 1: Event-based messaging over a reliable+ordered channel.\n * Used for turn-based games, chat, and infrequent state changes.\n */\nexport class EventSync {\n private _transport: CarverTransport;\n private _channel: CarverChannel<string>;\n private _handlers = new Map<string, ((payload: unknown, peerId: string) => void)[]>();\n private _hostValidation: boolean;\n\n constructor(transport: CarverTransport, options?: { hostValidation?: boolean }) {\n this._transport = transport;\n this._hostValidation = options?.hostValidation ?? false;\n\n // Create a single reliable+ordered channel for events\n this._channel = transport.createChannel<string>('carver:events', {\n reliable: true,\n ordered: true,\n });\n\n // Listen for incoming events\n this._channel.onReceive((rawData: string, peerId: string) => {\n try {\n const packet: EventPacket = typeof rawData === 'string' ? JSON.parse(rawData) : rawData as unknown as EventPacket;\n\n // If host validation is enabled and we're the host, rebroadcast\n if (this._hostValidation && this._transport.isHost && packet.sender !== this._transport.peerId) {\n // Rebroadcast to all peers (except the original sender)\n const targets = Array.from(this._transport.peers).filter(p => p !== peerId);\n if (targets.length > 0) {\n this._channel.send(JSON.stringify(packet), targets);\n }\n }\n\n // If host validation is enabled and we're NOT the host,\n // only accept events from host (who rebroadcasts validated events)\n if (this._hostValidation && !this._transport.isHost && peerId !== this._transport.hostId) {\n return;\n }\n\n // Fire handlers for this event type\n const handlers = this._handlers.get(packet.type);\n if (handlers) {\n for (const handler of handlers) {\n handler(packet.payload, packet.sender);\n }\n }\n } catch {\n // Ignore malformed events\n }\n });\n }\n\n /**\n * Send a typed event to a specific peer or all peers.\n */\n sendEvent(type: string, payload: unknown, target?: string): void {\n const packet: EventPacket = {\n type,\n payload,\n sender: this._transport.peerId,\n target,\n };\n const serialized = JSON.stringify(packet);\n\n if (this._hostValidation && !this._transport.isHost) {\n // Route through host for validation\n this._channel.send(serialized, this._transport.hostId);\n } else if (target) {\n this._channel.send(serialized, target);\n } else {\n // Broadcast to all peers\n this._channel.send(serialized);\n }\n }\n\n /**\n * Broadcast a typed event to all connected peers.\n */\n broadcast(type: string, payload: unknown): void {\n this.sendEvent(type, payload);\n }\n\n /**\n * Register a handler for a specific event type.\n * Returns an unsubscribe function.\n */\n onEvent(type: string, callback: (payload: unknown, peerId: string) => void): () => void {\n let handlers = this._handlers.get(type);\n if (!handlers) {\n handlers = [];\n this._handlers.set(type, handlers);\n }\n handlers.push(callback);\n\n return () => {\n const arr = this._handlers.get(type);\n if (arr) {\n const idx = arr.indexOf(callback);\n if (idx >= 0) arr.splice(idx, 1);\n if (arr.length === 0) this._handlers.delete(type);\n }\n };\n }\n\n /**\n * Clean up the event channel.\n */\n destroy(): void {\n this._channel.close();\n this._handlers.clear();\n }\n}\n","import type { CarverTransport, CarverChannel, EntityState } from \"../types\";\nimport { Codec, SnapshotBuffer } from \"./codec\";\n\n/**\n * Host-side authority: reads networked actor states, serializes with delta compression,\n * and broadcasts to all clients at the configured broadcast rate.\n */\nexport class HostAuthority {\n private _transport: CarverTransport;\n private _codec: Codec;\n private _snapshotBuffer: SnapshotBuffer;\n private _snapshotChannel: CarverChannel<Uint8Array>;\n private _ackChannel: CarverChannel<string>;\n private _tick = 0;\n private _broadcastRate: number;\n private _broadcastAccumulator = 0;\n private _keyframeInterval: number;\n\n // Per-client last ACK'd tick for delta compression\n private _clientBaselines = new Map<string, number>();\n // Per-client last keyframe tick for scheduling\n private _clientLastKeyframeTick = new Map<string, number>();\n\n // Interest management callback (optional)\n private _interestFilter:\n | ((entityId: string, peerId: string) => boolean)\n | null = null;\n\n constructor(\n transport: CarverTransport,\n codec: Codec,\n snapshotBuffer: SnapshotBuffer,\n options?: {\n broadcastRate?: number;\n keyframeInterval?: number;\n },\n ) {\n this._transport = transport;\n this._codec = codec;\n this._snapshotBuffer = snapshotBuffer;\n this._broadcastRate = options?.broadcastRate ?? 20;\n this._keyframeInterval = options?.keyframeInterval ?? 300;\n\n // Unreliable channel for snapshots (fast, may drop)\n this._snapshotChannel = transport.createChannel<Uint8Array>(\n \"carver:snapshots\",\n {\n reliable: false,\n ordered: false,\n maxRetransmits: 0,\n },\n );\n\n // Reliable channel for ACKs\n this._ackChannel = transport.createChannel<string>(\"carver:acks\", {\n reliable: true,\n ordered: true,\n });\n\n // Listen for client ACKs\n this._ackChannel.onReceive((data: string, peerId: string) => {\n try {\n const ackTick =\n typeof data === \"string\"\n ? parseInt(data, 10)\n : (data as unknown as number);\n if (ackTick === -1) {\n // Client requesting keyframe\n this._clientBaselines.delete(peerId);\n } else {\n this._clientBaselines.set(peerId, ackTick);\n }\n } catch {\n /* ignore malformed ACKs */\n }\n });\n\n // Handle new peer joins — they need a keyframe\n transport.onPeerJoin((peerId) => {\n this._clientBaselines.delete(peerId); // Will force keyframe\n });\n\n transport.onPeerLeave((peerId) => {\n this._clientBaselines.delete(peerId);\n });\n }\n\n /** Set optional interest management filter */\n setInterestFilter(\n filter: ((entityId: string, peerId: string) => boolean) | null,\n ): void {\n this._interestFilter = filter;\n }\n\n /**\n * Called every fixed tick by the sync engine.\n * Collects entity states and decides whether to broadcast.\n */\n tick(\n currentTick: number,\n entities: Map<string, EntityState>,\n delta: number,\n ): void {\n this._tick = currentTick;\n\n // Store snapshot in ring buffer\n this._snapshotBuffer.store(currentTick, new Map(entities));\n\n // Check if we should broadcast this tick\n this._broadcastAccumulator += delta;\n const broadcastInterval = 1 / this._broadcastRate;\n if (this._broadcastAccumulator < broadcastInterval) return;\n this._broadcastAccumulator -= broadcastInterval;\n\n // Broadcast to each connected client\n for (const peerId of this._transport.peers) {\n this._broadcastToClient(peerId, currentTick, entities);\n }\n }\n\n /** Force a keyframe broadcast to all clients (e.g., after host migration) */\n forceKeyframe(\n currentTick: number,\n entities: Map<string, EntityState>,\n ): void {\n this._clientBaselines.clear();\n this._clientLastKeyframeTick.clear();\n this._snapshotBuffer.store(currentTick, new Map(entities));\n for (const peerId of this._transport.peers) {\n this._broadcastToClient(peerId, currentTick, entities);\n }\n }\n\n destroy(): void {\n this._snapshotChannel.close();\n this._ackChannel.close();\n this._clientBaselines.clear();\n this._clientLastKeyframeTick.clear();\n }\n\n private _broadcastToClient(\n peerId: string,\n currentTick: number,\n entities: Map<string, EntityState>,\n ): void {\n // Apply interest management filter\n let clientEntities = entities;\n if (this._interestFilter) {\n clientEntities = new Map<string, EntityState>();\n for (const [id, entity] of entities) {\n if (this._interestFilter(id, peerId)) {\n clientEntities.set(id, entity);\n }\n }\n }\n\n // Determine baseline for delta compression (per-client keyframe scheduling)\n const clientBaseTick = this._clientBaselines.get(peerId);\n const clientLastKeyframe = this._clientLastKeyframeTick.get(peerId) ?? 0;\n const needsKeyframe =\n clientBaseTick === undefined ||\n currentTick - clientLastKeyframe >= this._keyframeInterval;\n\n let baseline: Map<string, EntityState> | undefined;\n if (!needsKeyframe && clientBaseTick !== undefined) {\n baseline = this._snapshotBuffer.get(clientBaseTick);\n }\n\n if (needsKeyframe) {\n this._clientLastKeyframeTick.set(peerId, currentTick);\n }\n\n // Serialize (delta or keyframe)\n const packet = this._codec.serializeDelta(\n currentTick,\n needsKeyframe ? -1 : (clientBaseTick ?? -1),\n clientEntities,\n baseline,\n );\n\n if (packet) {\n this._snapshotChannel.send(packet, peerId);\n }\n }\n}\n","import type {\n CarverTransport,\n CarverChannel,\n EntityState,\n EntityState2D,\n EntityState3D,\n NetworkQuality,\n} from \"../types\";\nimport { Codec } from \"./codec\";\n\ninterface BufferedSnapshot {\n tick: number;\n entities: Map<string, EntityState>;\n receivedAt: number;\n}\n\n/**\n * Client-side state receiver: buffers incoming snapshots and interpolates\n * between them for smooth rendering.\n */\nexport class ClientReceiver {\n private _transport: CarverTransport;\n private _codec: Codec;\n private _snapshotChannel: CarverChannel<Uint8Array>;\n private _ackChannel: CarverChannel<string>;\n\n // Snapshot buffer (ring buffer of last N snapshots)\n private _buffer: BufferedSnapshot[] = [];\n private _bufferSize: number;\n\n // Interpolation settings\n private _method: \"hermite\" | \"linear\";\n private _extrapolateMs: number;\n\n // Current interpolated state\n private _interpolatedState = new Map<string, EntityState>();\n\n // Network quality tracking\n private _lastSnapshotTime = 0;\n private _networkQuality: NetworkQuality = \"good\";\n private _packetLossCount = 0;\n private _packetCount = 0;\n\n // Is2D mode\n private _is2D: boolean;\n\n // Entity state: full accumulated state from keyframes + deltas\n private _fullState = new Map<string, EntityState>();\n\n constructor(\n transport: CarverTransport,\n codec: Codec,\n options?: {\n bufferSize?: number;\n method?: \"hermite\" | \"linear\";\n extrapolateMs?: number;\n is2D?: boolean;\n },\n ) {\n this._transport = transport;\n this._codec = codec;\n this._bufferSize = options?.bufferSize ?? 3;\n this._method = options?.method ?? \"hermite\";\n this._extrapolateMs = options?.extrapolateMs ?? 250;\n this._is2D = options?.is2D ?? false;\n\n // Listen on the unreliable snapshot channel\n this._snapshotChannel = transport.createChannel<Uint8Array>(\n \"carver:snapshots\",\n {\n reliable: false,\n ordered: false,\n maxRetransmits: 0,\n },\n );\n\n this._ackChannel = transport.createChannel<string>(\"carver:acks\", {\n reliable: true,\n ordered: true,\n });\n\n this._snapshotChannel.onReceive((data: Uint8Array) => {\n this._handleSnapshot(data);\n });\n }\n\n /** Get the current interpolated entity states */\n get state(): Map<string, EntityState> {\n return this._interpolatedState;\n }\n\n get networkQuality(): NetworkQuality {\n return this._networkQuality;\n }\n\n /**\n * Called every render frame to interpolate between buffered snapshots.\n * @param renderTime - current render time in ms\n */\n interpolate(renderTime: number): Map<string, EntityState> {\n if (this._buffer.length < 2) {\n // Not enough snapshots to interpolate, return latest\n return this._buffer.length > 0\n ? this._buffer[this._buffer.length - 1].entities\n : this._interpolatedState;\n }\n\n // Find two snapshots to interpolate between\n // We render \"behind\" by one buffer interval\n const interpDelay = (this._bufferSize - 1) * (1000 / 20); // Assume ~20 pps\n const targetTime = renderTime - interpDelay;\n\n let from: BufferedSnapshot | null = null;\n let to: BufferedSnapshot | null = null;\n\n for (let i = 0; i < this._buffer.length - 1; i++) {\n if (\n this._buffer[i].receivedAt <= targetTime &&\n this._buffer[i + 1].receivedAt > targetTime\n ) {\n from = this._buffer[i];\n to = this._buffer[i + 1];\n break;\n }\n }\n\n if (!from || !to) {\n // Extrapolation case: we're past all buffered snapshots\n const latest = this._buffer[this._buffer.length - 1];\n const timeSinceLatest = renderTime - latest.receivedAt;\n\n if (timeSinceLatest > this._extrapolateMs) {\n // Too far behind, just use latest snapshot\n this._updateNetworkQuality(\"poor\");\n return latest.entities;\n }\n\n if (this._buffer.length >= 2) {\n from = this._buffer[this._buffer.length - 2];\n to = latest;\n this._updateNetworkQuality(\"degraded\");\n } else {\n return latest.entities;\n }\n } else {\n this._updateNetworkQuality(\"good\");\n }\n\n // Compute interpolation factor\n const range = to.receivedAt - from.receivedAt;\n const t =\n range > 0\n ? Math.min(1, Math.max(0, (targetTime - from.receivedAt) / range))\n : 1;\n\n // Interpolate all entities\n const result = new Map<string, EntityState>();\n const allIds = new Set([...from.entities.keys(), ...to.entities.keys()]);\n\n for (const id of allIds) {\n const fromEntity = from.entities.get(id);\n const toEntity = to.entities.get(id);\n\n if (toEntity && toEntity.c?.__removed) continue; // Removed entity\n\n if (fromEntity && toEntity) {\n result.set(id, this._interpolateEntity(fromEntity, toEntity, t));\n } else if (toEntity) {\n result.set(id, toEntity);\n }\n }\n\n this._interpolatedState = result;\n return result;\n }\n\n /** Request a keyframe from the host */\n requestKeyframe(): void {\n this._ackChannel.send(\"-1\");\n }\n\n destroy(): void {\n this._snapshotChannel.close();\n this._ackChannel.close();\n this._buffer = [];\n this._interpolatedState.clear();\n this._fullState.clear();\n }\n\n private _handleSnapshot(data: Uint8Array): void {\n try {\n const { tick, baseTick, entities } = this._codec.deserializePacket(data);\n const now = performance.now();\n\n if (baseTick === -1) {\n // Keyframe: replace full state\n this._fullState.clear();\n for (const entity of entities) {\n this._fullState.set(entity.id, entity);\n }\n } else {\n // Delta: apply changes to full state\n for (const entity of entities) {\n if (entity.c?.__removed) {\n this._fullState.delete(entity.id);\n } else {\n this._fullState.set(entity.id, entity);\n }\n }\n }\n\n // Buffer the full state snapshot\n this._buffer.push({\n tick,\n entities: new Map(this._fullState),\n receivedAt: now,\n });\n\n // Keep buffer bounded\n while (this._buffer.length > this._bufferSize * 2) {\n this._buffer.shift();\n }\n\n // Send ACK\n this._ackChannel.send(String(tick));\n\n // Track timing for network quality\n this._lastSnapshotTime = now;\n this._packetCount++;\n } catch {\n this._packetLossCount++;\n }\n }\n\n private _interpolateEntity(\n from: EntityState,\n to: EntityState,\n t: number,\n ): EntityState {\n if (this._is2D || !(\"z\" in from)) {\n return this._interpolateEntity2D(\n from as EntityState2D,\n to as EntityState2D,\n t,\n );\n }\n return this._interpolateEntity3D(\n from as EntityState3D,\n to as EntityState3D,\n t,\n );\n }\n\n private _interpolateEntity2D(\n from: EntityState2D,\n to: EntityState2D,\n t: number,\n ): EntityState2D {\n if (this._method === \"hermite\") {\n return {\n id: to.id,\n x: hermite(from.x, from.vx, to.x, to.vx, t),\n y: hermite(from.y, from.vy, to.y, to.vy, t),\n a: lerpAngle(from.a, to.a, t),\n vx: lerp(from.vx, to.vx, t),\n vy: lerp(from.vy, to.vy, t),\n va: lerp(from.va, to.va, t),\n c: interpolateCustom(from.c, to.c, t),\n };\n }\n // Linear\n return {\n id: to.id,\n x: lerp(from.x, to.x, t),\n y: lerp(from.y, to.y, t),\n a: lerpAngle(from.a, to.a, t),\n vx: lerp(from.vx, to.vx, t),\n vy: lerp(from.vy, to.vy, t),\n va: lerp(from.va, to.va, t),\n c: interpolateCustom(from.c, to.c, t),\n };\n }\n\n private _interpolateEntity3D(\n from: EntityState3D,\n to: EntityState3D,\n t: number,\n ): EntityState3D {\n // Quaternion SLERP for rotation\n const [qx, qy, qz, qw] = slerp(\n from.qx,\n from.qy,\n from.qz,\n from.qw,\n to.qx,\n to.qy,\n to.qz,\n to.qw,\n t,\n );\n\n if (this._method === \"hermite\") {\n return {\n id: to.id,\n x: hermite(from.x, from.vx, to.x, to.vx, t),\n y: hermite(from.y, from.vy, to.y, to.vy, t),\n z: hermite(from.z, from.vz, to.z, to.vz, t),\n qx,\n qy,\n qz,\n qw,\n vx: lerp(from.vx, to.vx, t),\n vy: lerp(from.vy, to.vy, t),\n vz: lerp(from.vz, to.vz, t),\n wx: lerp(from.wx, to.wx, t),\n wy: lerp(from.wy, to.wy, t),\n wz: lerp(from.wz, to.wz, t),\n c: interpolateCustom(from.c, to.c, t),\n };\n }\n\n // Linear\n return {\n id: to.id,\n x: lerp(from.x, to.x, t),\n y: lerp(from.y, to.y, t),\n z: lerp(from.z, to.z, t),\n qx,\n qy,\n qz,\n qw,\n vx: lerp(from.vx, to.vx, t),\n vy: lerp(from.vy, to.vy, t),\n vz: lerp(from.vz, to.vz, t),\n wx: lerp(from.wx, to.wx, t),\n wy: lerp(from.wy, to.wy, t),\n wz: lerp(from.wz, to.wz, t),\n c: interpolateCustom(from.c, to.c, t),\n };\n }\n\n private _updateNetworkQuality(quality: NetworkQuality): void {\n this._networkQuality = quality;\n }\n}\n\n// ── Math utilities ──\n\nfunction lerp(a: number, b: number, t: number): number {\n return a + (b - a) * t;\n}\n\nfunction lerpAngle(a: number, b: number, t: number): number {\n let diff = b - a;\n // Wrap to [-PI, PI]\n while (diff > Math.PI) diff -= Math.PI * 2;\n while (diff < -Math.PI) diff += Math.PI * 2;\n return a + diff * t;\n}\n\n/**\n * Hermite spline interpolation using velocity for smooth curves.\n * h(t) = (2t^3 - 3t^2 + 1)p0 + (t^3 - 2t^2 + t)v0 + (-2t^3 + 3t^2)p1 + (t^3 - t^2)v1\n */\nfunction hermite(\n p0: number,\n v0: number,\n p1: number,\n v1: number,\n t: number,\n): number {\n const t2 = t * t;\n const t3 = t2 * t;\n return (\n (2 * t3 - 3 * t2 + 1) * p0 +\n (t3 - 2 * t2 + t) * v0 +\n (-2 * t3 + 3 * t2) * p1 +\n (t3 - t2) * v1\n );\n}\n\n/**\n * Quaternion SLERP (Spherical Linear Interpolation).\n */\nfunction slerp(\n ax: number,\n ay: number,\n az: number,\n aw: number,\n bx: number,\n by: number,\n bz: number,\n bw: number,\n t: number,\n): [number, number, number, number] {\n // Compute dot product\n let dot = ax * bx + ay * by + az * bz + aw * bw;\n\n // Ensure shortest path\n if (dot < 0) {\n bx = -bx;\n by = -by;\n bz = -bz;\n bw = -bw;\n dot = -dot;\n }\n\n if (dot > 0.9995) {\n // Very close, use linear interpolation\n return [lerp(ax, bx, t), lerp(ay, by, t), lerp(az, bz, t), lerp(aw, bw, t)];\n }\n\n const theta = Math.acos(Math.min(1, Math.max(-1, dot)));\n const sinTheta = Math.sin(theta);\n const wa = Math.sin((1 - t) * theta) / sinTheta;\n const wb = Math.sin(t * theta) / sinTheta;\n\n return [\n ax * wa + bx * wb,\n ay * wa + by * wb,\n az * wa + bz * wb,\n aw * wa + bw * wb,\n ];\n}\n\n/**\n * Interpolate custom properties: lerp numbers, instant-swap others.\n */\nfunction interpolateCustom(\n from: Record<string, unknown> | undefined,\n to: Record<string, unknown> | undefined,\n t: number,\n): Record<string, unknown> | undefined {\n if (!from && !to) return undefined;\n if (!from) return to;\n if (!to) return from;\n\n const result: Record<string, unknown> = {};\n const allKeys = new Set([...Object.keys(from), ...Object.keys(to)]);\n\n for (const key of allKeys) {\n const fromVal = from[key];\n const toVal = to[key];\n if (typeof fromVal === \"number\" && typeof toVal === \"number\") {\n result[key] = lerp(fromVal, toVal, t);\n } else {\n // Instant swap: use target value after halfway\n result[key] = t >= 0.5 ? toVal : fromVal;\n }\n }\n\n return result;\n}\n","import type { CarverTransport, EntityState } from \"../types\";\nimport { Codec, SnapshotBuffer } from \"../core/codec\";\nimport { HostAuthority } from \"../core/HostAuthority\";\nimport { ClientReceiver } from \"../core/ClientReceiver\";\n\nexport interface SnapshotSyncOptions {\n broadcastRate?: number;\n keyframeInterval?: number;\n bufferSize?: number;\n interpolationMethod?: \"hermite\" | \"linear\";\n extrapolateMs?: number;\n is2D?: boolean;\n}\n\n/**\n * Layer 2: Snapshot interpolation sync engine.\n * Host broadcasts state at fixed intervals, clients interpolate.\n */\nexport class SnapshotSync {\n private _transport: CarverTransport;\n private _hostAuthority: HostAuthority | null = null;\n private _clientReceiver: ClientReceiver | null = null;\n private _codec: Codec;\n private _snapshotBuffer: SnapshotBuffer;\n\n constructor(\n transport: CarverTransport,\n codec: Codec,\n snapshotBuffer: SnapshotBuffer,\n options?: SnapshotSyncOptions,\n ) {\n this._transport = transport;\n this._codec = codec;\n this._snapshotBuffer = snapshotBuffer;\n\n if (transport.isHost) {\n this._hostAuthority = new HostAuthority(\n transport,\n codec,\n snapshotBuffer,\n {\n broadcastRate: options?.broadcastRate,\n keyframeInterval: options?.keyframeInterval,\n },\n );\n } else {\n this._clientReceiver = new ClientReceiver(transport, codec, {\n bufferSize: options?.bufferSize,\n method: options?.interpolationMethod,\n extrapolateMs: options?.extrapolateMs,\n is2D: options?.is2D,\n });\n }\n }\n\n get isHost(): boolean {\n return this._hostAuthority !== null;\n }\n\n get hostAuthority(): HostAuthority | null {\n return this._hostAuthority;\n }\n\n get clientReceiver(): ClientReceiver | null {\n return this._clientReceiver;\n }\n\n /** Host: called every fixed tick to potentially broadcast state */\n hostTick(\n tick: number,\n entities: Map<string, EntityState>,\n delta: number,\n ): void {\n this._hostAuthority?.tick(tick, entities, delta);\n }\n\n /** Client: called every render frame to interpolate */\n clientInterpolate(renderTime: number): Map<string, EntityState> {\n return this._clientReceiver?.interpolate(renderTime) ?? new Map();\n }\n\n /** Set interest filter on host authority */\n setInterestFilter(\n filter: ((entityId: string, peerId: string) => boolean) | null,\n ): void {\n this._hostAuthority?.setInterestFilter(filter);\n }\n\n /** Handle host migration: switch from client to host mode */\n promoteToHost(options?: SnapshotSyncOptions): void {\n this._clientReceiver?.destroy();\n this._clientReceiver = null;\n this._hostAuthority = new HostAuthority(\n this._transport,\n this._codec,\n this._snapshotBuffer,\n {\n broadcastRate: options?.broadcastRate,\n keyframeInterval: options?.keyframeInterval,\n },\n );\n }\n\n /** Handle host migration: switch from host to client mode */\n demoteToClient(options?: SnapshotSyncOptions): void {\n this._hostAuthority?.destroy();\n this._hostAuthority = null;\n this._clientReceiver = new ClientReceiver(\n this._transport,\n this._codec,\n {\n bufferSize: options?.bufferSize,\n method: options?.interpolationMethod,\n extrapolateMs: options?.extrapolateMs,\n is2D: options?.is2D,\n },\n );\n }\n\n destroy(): void {\n this._hostAuthority?.destroy();\n this._clientReceiver?.destroy();\n this._hostAuthority = null;\n this._clientReceiver = null;\n }\n}\n","import { pack, unpack } from \"msgpackr\";\nimport type {\n CarverTransport,\n CarverChannel,\n EntityState,\n EntityState3D,\n InputPacket,\n} from \"../types\";\nimport { Codec } from \"../core/codec\";\nimport { TickKeeper } from \"../core/TickKeeper\";\n\ninterface PredictionOptions {\n maxRewindTicks: number;\n errorSmoothingDecay: number;\n maxErrorPerFrame: number;\n snapThreshold: number;\n lagCompensation: boolean;\n}\n\ninterface InputBufferEntry {\n tick: number;\n input: unknown;\n}\n\ninterface ErrorCorrection {\n x: number;\n y: number;\n z: number;\n}\n\nconst DEFAULT_OPTIONS: PredictionOptions = {\n maxRewindTicks: 15,\n errorSmoothingDecay: 0.85,\n maxErrorPerFrame: 5,\n snapThreshold: 15,\n lagCompensation: false,\n};\n\n/**\n * Layer 3: Client-side prediction with server reconciliation.\n * Builds on top of Layer 2 (SnapshotSync).\n *\n * Flow:\n * Client: input -> apply locally (predict) -> store in buffer -> send to host\n * Host: receive input -> apply to simulation -> broadcast state + lastProcessedInputTick\n * Client: receive state -> compare with prediction ->\n * if mismatch: reset to server state + replay unacked inputs -> visual smoothing\n */\nexport class PredictionSync {\n private _transport: CarverTransport;\n private _codec: Codec;\n private _tickKeeper: TickKeeper;\n private _options: PredictionOptions;\n\n // Channels\n private _inputChannel: CarverChannel<string>;\n private _stateChannel: CarverChannel<Uint8Array>;\n private _ackChannel: CarverChannel<string>;\n\n // Input buffer: ring buffer of recent inputs keyed by tick\n private _inputBuffer: InputBufferEntry[] = [];\n // Per-client last processed input tick (host-side tracking)\n private _clientLastProcessedTick = new Map<string, number>();\n\n // Predicted state (client-side)\n private _predictedState = new Map<string, EntityState>();\n\n // Error correction vectors per entity\n private _errorCorrections = new Map<string, ErrorCorrection>();\n\n // Server state (last received authoritative snapshot)\n private _serverState = new Map<string, EntityState>();\n private _serverTick = 0;\n\n // Physics step callback (provided by developer)\n private _onPhysicsStep:\n | ((inputs: Map<string, unknown>, tick: number, isRollback: boolean) => void)\n | null = null;\n\n // Own input for current tick\n private _currentInput: unknown = null;\n\n // Is host\n private _isHost: boolean;\n\n constructor(\n transport: CarverTransport,\n codec: Codec,\n tickKeeper: TickKeeper,\n options?: Partial<PredictionOptions>,\n ) {\n this._transport = transport;\n this._codec = codec;\n this._tickKeeper = tickKeeper;\n this._options = { ...DEFAULT_OPTIONS, ...options };\n this._isHost = transport.isHost;\n\n // Reliable channel for inputs (client -> host)\n this._inputChannel = transport.createChannel<string>(\"carver:inputs\", {\n reliable: true,\n ordered: true,\n });\n\n // Unreliable channel for state (host -> clients)\n this._stateChannel = transport.createChannel<Uint8Array>(\"carver:pred-state\", {\n reliable: false,\n ordered: false,\n maxRetransmits: 0,\n });\n\n // Reliable ACK channel\n this._ackChannel = transport.createChannel<string>(\"carver:pred-acks\", {\n reliable: true,\n ordered: true,\n });\n\n if (this._isHost) {\n this._setupHostListeners();\n } else {\n this._setupClientListeners();\n }\n }\n\n /** Set the physics step callback (required for rollback re-simulation) */\n setPhysicsStep(\n cb: (inputs: Map<string, unknown>, tick: number, isRollback: boolean) => void,\n ): void {\n this._onPhysicsStep = cb;\n }\n\n /** Set the current input for this tick (client-side) */\n setInput(input: unknown): void {\n this._currentInput = input;\n }\n\n /**\n * Called every fixed tick on the client.\n * Applies input locally (prediction), buffers it, and sends to host.\n */\n clientTick(tick: number): void {\n if (this._isHost) return;\n\n // Buffer the input\n if (this._currentInput !== null) {\n this._inputBuffer.push({ tick, input: this._currentInput });\n\n // Send input to host\n const packet: InputPacket = {\n t: tick,\n i: this._currentInput,\n p: this._transport.peerId,\n };\n this._inputChannel.send(JSON.stringify(packet));\n\n // Apply input locally (prediction)\n if (this._onPhysicsStep) {\n const inputs = new Map<string, unknown>();\n inputs.set(this._transport.peerId, this._currentInput);\n this._onPhysicsStep(inputs, tick, false);\n }\n\n this._currentInput = null;\n }\n\n // Trim old inputs from buffer\n const minTick = tick - this._options.maxRewindTicks * 2;\n while (this._inputBuffer.length > 0 && this._inputBuffer[0].tick < minTick) {\n this._inputBuffer.shift();\n }\n }\n\n /**\n * Called every fixed tick on the host.\n * Processes received inputs and broadcasts authoritative state.\n */\n hostTick(tick: number, entities: Map<string, EntityState>, _delta: number): void {\n if (!this._isHost) return;\n\n // Broadcast authoritative state with per-client last processed input tick\n const stateArray = Array.from(entities.values());\n const data = this._codec.serialize(stateArray);\n\n // Send per-client packets with each client's own last processed input tick\n for (const peerId of this._transport.peers) {\n const lastTick = this._clientLastProcessedTick.get(peerId) ?? -1;\n const packet = {\n t: tick,\n s: data,\n li: lastTick,\n };\n this._stateChannel.send(pack(packet), peerId);\n }\n }\n\n /**\n * Called every render frame on the client to apply visual error smoothing.\n * Returns the corrected entity states.\n */\n applyErrorSmoothing(entities: Map<string, EntityState>): Map<string, EntityState> {\n const result = new Map<string, EntityState>();\n const decay = this._options.errorSmoothingDecay;\n\n for (const [id, entity] of entities) {\n const correction = this._errorCorrections.get(id);\n if (!correction) {\n result.set(id, entity);\n continue;\n }\n\n // Apply correction and decay\n const corrected = { ...entity };\n corrected.x += correction.x;\n corrected.y += correction.y;\n if (\"z\" in corrected) {\n (corrected as EntityState3D).z += correction.z;\n }\n\n // Decay the correction\n correction.x *= decay;\n correction.y *= decay;\n correction.z *= decay;\n\n // Remove if negligible\n const mag = Math.abs(correction.x) + Math.abs(correction.y) + Math.abs(correction.z);\n if (mag < 0.001) {\n this._errorCorrections.delete(id);\n }\n\n result.set(id, corrected);\n }\n\n return result;\n }\n\n get predictedState(): Map<string, EntityState> {\n return this._predictedState;\n }\n\n get serverTick(): number {\n return this._serverTick;\n }\n\n destroy(): void {\n this._inputChannel.close();\n this._stateChannel.close();\n this._ackChannel.close();\n this._inputBuffer = [];\n this._predictedState.clear();\n this._errorCorrections.clear();\n this._serverState.clear();\n }\n\n // ── Private: Host-side ──\n\n private _setupHostListeners(): void {\n // Receive inputs from clients\n this._inputChannel.onReceive((rawData: string, peerId: string) => {\n try {\n const packet: InputPacket = JSON.parse(rawData);\n // Apply input to simulation via callback\n if (this._onPhysicsStep) {\n const inputs = new Map<string, unknown>();\n inputs.set(peerId, packet.i);\n this._onPhysicsStep(inputs, packet.t, false);\n }\n const prevTick = this._clientLastProcessedTick.get(peerId) ?? -1;\n this._clientLastProcessedTick.set(peerId, Math.max(prevTick, packet.t));\n } catch (err) {\n if (typeof console !== 'undefined') console.debug('[CarverJS] Malformed input packet:', err);\n }\n });\n }\n\n // ── Private: Client-side ──\n\n private _setupClientListeners(): void {\n // Receive authoritative state from host\n this._stateChannel.onReceive((data: Uint8Array) => {\n try {\n const packet = unpack(data) as { t: number; s: Uint8Array; li: number };\n\n const entities = this._codec.deserialize(packet.s);\n const serverTick = packet.t;\n const lastInputTick = packet.li;\n\n this._serverTick = serverTick;\n this._tickKeeper.setServerTick(serverTick);\n\n // Build server state map\n this._serverState.clear();\n for (const entity of entities) {\n this._serverState.set(entity.id, entity);\n }\n\n // Reconciliation: compare predicted state with server state\n this._reconcile(lastInputTick);\n } catch (err) {\n if (typeof console !== 'undefined') console.debug('[CarverJS] Malformed state packet:', err);\n }\n });\n }\n\n private _reconcile(lastInputTick: number): void {\n // Remove acknowledged inputs from buffer\n this._inputBuffer = this._inputBuffer.filter((entry) => entry.tick > lastInputTick);\n\n // Compare predicted state with server state\n let needsRollback = false;\n let maxError = 0;\n\n for (const [id, serverEntity] of this._serverState) {\n const predicted = this._predictedState.get(id);\n if (!predicted) continue;\n\n const error = this._computeError(predicted, serverEntity);\n maxError = Math.max(maxError, error);\n\n if (error > this._options.maxErrorPerFrame) {\n needsRollback = true;\n }\n }\n\n if (maxError > this._options.snapThreshold) {\n // Error too large -- hard snap to server state\n this._predictedState = new Map(this._serverState);\n this._errorCorrections.clear();\n return;\n }\n\n if (needsRollback) {\n // Store current positions for visual smoothing\n const oldPositions = new Map<string, { x: number; y: number; z: number }>();\n for (const [id, entity] of this._predictedState) {\n oldPositions.set(id, {\n x: entity.x,\n y: entity.y,\n z: \"z\" in entity ? (entity as EntityState3D).z : 0,\n });\n }\n\n // Reset to server state\n this._predictedState = new Map(this._serverState);\n\n // Replay unacked inputs\n if (this._onPhysicsStep) {\n for (const entry of this._inputBuffer) {\n const inputs = new Map<string, unknown>();\n inputs.set(this._transport.peerId, entry.input);\n this._onPhysicsStep(inputs, entry.tick, true); // isRollback = true\n }\n }\n\n // Compute visual error correction vectors\n for (const [id, newEntity] of this._predictedState) {\n const oldPos = oldPositions.get(id);\n if (oldPos) {\n this._errorCorrections.set(id, {\n x: oldPos.x - newEntity.x,\n y: oldPos.y - newEntity.y,\n z: oldPos.z - (\"z\" in newEntity ? (newEntity as EntityState3D).z : 0),\n });\n }\n }\n }\n }\n\n private _computeError(predicted: EntityState, server: EntityState): number {\n const dx = predicted.x - server.x;\n const dy = predicted.y - server.y;\n let dz = 0;\n if (\"z\" in predicted && \"z\" in server) {\n dz = (predicted as EntityState3D).z - (server as EntityState3D).z;\n }\n return Math.sqrt(dx * dx + dy * dy + dz * dz);\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/sync.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { P as Player, C as CarverTransport, R as Room, a as ChannelOptions, b as CarverChannel, T as TransportConfig, c as RoomState } from './NetworkManager-DrKM2tEx.mjs';
|
|
2
|
+
import { S as SignalingStrategy } from './types-5LHBOW08.mjs';
|
|
3
|
+
export { F as FirebaseStrategyConfig, M as MqttStrategyConfig, P as PeerMetadata, R as RoomAnnouncement, a as StrategyConfig } from './types-5LHBOW08.mjs';
|
|
4
|
+
export { F as FirebaseStrategy, M as MqttStrategy } from './firebase-PE6MxGdJ.mjs';
|
|
5
|
+
export { generatePeerId } from './strategy.mjs';
|
|
6
|
+
|
|
7
|
+
/** Internal callback store for a transport implementation */
|
|
8
|
+
interface TransportCallbacks {
|
|
9
|
+
onPeerJoin: ((peerId: string) => void)[];
|
|
10
|
+
onPeerLeave: ((peerId: string) => void)[];
|
|
11
|
+
onPeerUpdated: ((player: Player) => void)[];
|
|
12
|
+
onHostChanged: ((newHostId: string) => void)[];
|
|
13
|
+
}
|
|
14
|
+
/** Configuration for rate limiting */
|
|
15
|
+
interface RateLimitConfig {
|
|
16
|
+
/** Maximum messages per second per peer. Default: 60 */
|
|
17
|
+
maxMessagesPerSecond: number;
|
|
18
|
+
/** Window size in ms for rate calculation. Default: 1000 */
|
|
19
|
+
windowMs: number;
|
|
20
|
+
}
|
|
21
|
+
/** Peer connection state */
|
|
22
|
+
type PeerState = 'connecting' | 'connected' | 'disconnected' | 'failed';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Implements CarverTransport using WebRTC data channels for game data
|
|
26
|
+
* and a pluggable SignalingStrategy for peer discovery + SDP/ICE relay.
|
|
27
|
+
*
|
|
28
|
+
* No WebSocket server required. The strategy handles signaling through
|
|
29
|
+
* MQTT brokers, Firebase RTDB, or any other network.
|
|
30
|
+
*/
|
|
31
|
+
declare class WebRTCTransport implements CarverTransport {
|
|
32
|
+
private _strategy;
|
|
33
|
+
private _peers;
|
|
34
|
+
private _peerSet;
|
|
35
|
+
private _peerId;
|
|
36
|
+
private _hostId;
|
|
37
|
+
private _isHost;
|
|
38
|
+
private _callbacks;
|
|
39
|
+
private _roomUpdatedCallbacks;
|
|
40
|
+
private _channels;
|
|
41
|
+
private _iceConfig;
|
|
42
|
+
private _rateLimitConfig;
|
|
43
|
+
private _rateLimitCounters;
|
|
44
|
+
private _connected;
|
|
45
|
+
private _room;
|
|
46
|
+
private _playerMap;
|
|
47
|
+
private _initialPeers;
|
|
48
|
+
private _strategyUnsubs;
|
|
49
|
+
/**
|
|
50
|
+
* @param strategy Shared SignalingStrategy instance (managed by MultiplayerProvider)
|
|
51
|
+
* @param iceServers Optional ICE servers (STUN + TURN). Defaults to public STUN.
|
|
52
|
+
* @param iceTransportPolicy 'all' (default) or 'relay' (force TURN only).
|
|
53
|
+
*/
|
|
54
|
+
constructor(strategy: SignalingStrategy, iceServers?: RTCIceServer[], iceTransportPolicy?: RTCIceTransportPolicy);
|
|
55
|
+
get peerId(): string;
|
|
56
|
+
get peers(): ReadonlySet<string>;
|
|
57
|
+
get hostId(): string;
|
|
58
|
+
get isHost(): boolean;
|
|
59
|
+
get room(): Room | undefined;
|
|
60
|
+
get initialPlayers(): Player[];
|
|
61
|
+
onPeerJoin(cb: (peerId: string) => void): void;
|
|
62
|
+
onPeerLeave(cb: (peerId: string) => void): void;
|
|
63
|
+
onPeerUpdated(cb: (player: Player) => void): void;
|
|
64
|
+
onRoomUpdated(cb: (room: Room) => void): void;
|
|
65
|
+
onHostChanged(cb: (newHostId: string) => void): void;
|
|
66
|
+
createChannel<T>(name: string, options?: ChannelOptions): CarverChannel<T>;
|
|
67
|
+
connect(roomId: string, config?: TransportConfig): Promise<void>;
|
|
68
|
+
disconnect(): void;
|
|
69
|
+
/** Expose strategy for lobby hooks */
|
|
70
|
+
get strategy(): SignalingStrategy;
|
|
71
|
+
/**
|
|
72
|
+
* Register a channel name and options without creating data channels yet.
|
|
73
|
+
* When _connectToPeer runs, it iterates this._channels and creates data
|
|
74
|
+
* channels for every registered name in the initial WebRTC offer.
|
|
75
|
+
* Later, when EventSync/SnapshotSync call createChannel(), the idempotent
|
|
76
|
+
* check returns the pre-registered entry and they just attach receivers.
|
|
77
|
+
*/
|
|
78
|
+
private _preRegisterChannel;
|
|
79
|
+
setReady(ready: boolean): void;
|
|
80
|
+
setMetadata(metadata: Record<string, unknown>): void;
|
|
81
|
+
setRoomMetadata(metadata: Record<string, unknown>): void;
|
|
82
|
+
kick(peerId: string, reason?: string): void;
|
|
83
|
+
transferHost(peerId: string): void;
|
|
84
|
+
setRoomState(state: RoomState): void;
|
|
85
|
+
setMaxPlayers(n: number): void;
|
|
86
|
+
lockRoom(): void;
|
|
87
|
+
unlockRoom(): void;
|
|
88
|
+
/** No-op: lobby uses strategy.subscribeToLobby() directly */
|
|
89
|
+
requestRoomList(): void;
|
|
90
|
+
private _onStrategyPeerDiscovered;
|
|
91
|
+
private _setupRoomControlChannel;
|
|
92
|
+
private _handleControlMessage;
|
|
93
|
+
private _sendControlMessage;
|
|
94
|
+
private _broadcastControlMessage;
|
|
95
|
+
private _electAndSetHost;
|
|
96
|
+
private _connectToPeer;
|
|
97
|
+
private _handleSignal;
|
|
98
|
+
private _createDataChannelOnPeer;
|
|
99
|
+
private _setupDataChannelReceiver;
|
|
100
|
+
private _sendOnChannel;
|
|
101
|
+
private _removePeer;
|
|
102
|
+
private _checkRateLimit;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Build RTCConfiguration from user-provided ICE servers.
|
|
107
|
+
*
|
|
108
|
+
* If the user provides `iceServers`, those are used as-is (STUN + TURN).
|
|
109
|
+
* Otherwise, default public STUN servers are used.
|
|
110
|
+
*
|
|
111
|
+
* TURN servers should be included in the `iceServers` array by the user:
|
|
112
|
+
* ```ts
|
|
113
|
+
* iceServers: [
|
|
114
|
+
* { urls: 'stun:stun.cloudflare.com:3478' },
|
|
115
|
+
* { urls: 'turn:turn.cloudflare.com:3478', username: '...', credential: '...' },
|
|
116
|
+
* ]
|
|
117
|
+
* ```
|
|
118
|
+
*/
|
|
119
|
+
declare function buildICEConfig(options?: {
|
|
120
|
+
iceServers?: RTCIceServer[];
|
|
121
|
+
iceTransportPolicy?: RTCIceTransportPolicy;
|
|
122
|
+
}): RTCConfiguration;
|
|
123
|
+
|
|
124
|
+
interface PeerConnectionEvents {
|
|
125
|
+
onStateChange: (state: PeerState) => void;
|
|
126
|
+
onDataChannel: (channel: RTCDataChannel) => void;
|
|
127
|
+
onIceCandidate: (candidate: RTCIceCandidate) => void;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Manages a single RTCPeerConnection to one remote peer.
|
|
131
|
+
*
|
|
132
|
+
* ICE candidates that arrive before the remote description is set are
|
|
133
|
+
* buffered and flushed automatically once setRemoteDescription completes.
|
|
134
|
+
* This is critical for Firebase/MQTT signaling where offer, answer, and
|
|
135
|
+
* candidates can arrive nearly simultaneously.
|
|
136
|
+
*/
|
|
137
|
+
declare class PeerConnection {
|
|
138
|
+
readonly peerId: string;
|
|
139
|
+
private _connection;
|
|
140
|
+
private _channels;
|
|
141
|
+
private _events;
|
|
142
|
+
private _state;
|
|
143
|
+
private _remoteDescriptionSet;
|
|
144
|
+
private _pendingCandidates;
|
|
145
|
+
constructor(peerId: string, config: RTCConfiguration, events: PeerConnectionEvents);
|
|
146
|
+
get state(): PeerState;
|
|
147
|
+
get connection(): RTCPeerConnection;
|
|
148
|
+
private _updateState;
|
|
149
|
+
createOffer(): Promise<RTCSessionDescriptionInit>;
|
|
150
|
+
handleOffer(offer: RTCSessionDescriptionInit): Promise<RTCSessionDescriptionInit>;
|
|
151
|
+
handleAnswer(answer: RTCSessionDescriptionInit): Promise<void>;
|
|
152
|
+
addIceCandidate(candidate: RTCIceCandidateInit): Promise<void>;
|
|
153
|
+
private _flushPendingCandidates;
|
|
154
|
+
createDataChannel(name: string, options?: ChannelOptions): RTCDataChannel;
|
|
155
|
+
getDataChannel(name: string): RTCDataChannel | undefined;
|
|
156
|
+
close(): void;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export { CarverChannel, CarverTransport, ChannelOptions, PeerConnection, type PeerState, type RateLimitConfig, SignalingStrategy, type TransportCallbacks, TransportConfig, WebRTCTransport, buildICEConfig };
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { P as Player, C as CarverTransport, R as Room, a as ChannelOptions, b as CarverChannel, T as TransportConfig, c as RoomState } from './NetworkManager-nvVAOr1O.js';
|
|
2
|
+
import { S as SignalingStrategy } from './types-5LHBOW08.js';
|
|
3
|
+
export { F as FirebaseStrategyConfig, M as MqttStrategyConfig, P as PeerMetadata, R as RoomAnnouncement, a as StrategyConfig } from './types-5LHBOW08.js';
|
|
4
|
+
export { F as FirebaseStrategy, M as MqttStrategy } from './firebase-CPu87KA0.js';
|
|
5
|
+
export { generatePeerId } from './strategy.js';
|
|
6
|
+
|
|
7
|
+
/** Internal callback store for a transport implementation */
|
|
8
|
+
interface TransportCallbacks {
|
|
9
|
+
onPeerJoin: ((peerId: string) => void)[];
|
|
10
|
+
onPeerLeave: ((peerId: string) => void)[];
|
|
11
|
+
onPeerUpdated: ((player: Player) => void)[];
|
|
12
|
+
onHostChanged: ((newHostId: string) => void)[];
|
|
13
|
+
}
|
|
14
|
+
/** Configuration for rate limiting */
|
|
15
|
+
interface RateLimitConfig {
|
|
16
|
+
/** Maximum messages per second per peer. Default: 60 */
|
|
17
|
+
maxMessagesPerSecond: number;
|
|
18
|
+
/** Window size in ms for rate calculation. Default: 1000 */
|
|
19
|
+
windowMs: number;
|
|
20
|
+
}
|
|
21
|
+
/** Peer connection state */
|
|
22
|
+
type PeerState = 'connecting' | 'connected' | 'disconnected' | 'failed';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Implements CarverTransport using WebRTC data channels for game data
|
|
26
|
+
* and a pluggable SignalingStrategy for peer discovery + SDP/ICE relay.
|
|
27
|
+
*
|
|
28
|
+
* No WebSocket server required. The strategy handles signaling through
|
|
29
|
+
* MQTT brokers, Firebase RTDB, or any other network.
|
|
30
|
+
*/
|
|
31
|
+
declare class WebRTCTransport implements CarverTransport {
|
|
32
|
+
private _strategy;
|
|
33
|
+
private _peers;
|
|
34
|
+
private _peerSet;
|
|
35
|
+
private _peerId;
|
|
36
|
+
private _hostId;
|
|
37
|
+
private _isHost;
|
|
38
|
+
private _callbacks;
|
|
39
|
+
private _roomUpdatedCallbacks;
|
|
40
|
+
private _channels;
|
|
41
|
+
private _iceConfig;
|
|
42
|
+
private _rateLimitConfig;
|
|
43
|
+
private _rateLimitCounters;
|
|
44
|
+
private _connected;
|
|
45
|
+
private _room;
|
|
46
|
+
private _playerMap;
|
|
47
|
+
private _initialPeers;
|
|
48
|
+
private _strategyUnsubs;
|
|
49
|
+
/**
|
|
50
|
+
* @param strategy Shared SignalingStrategy instance (managed by MultiplayerProvider)
|
|
51
|
+
* @param iceServers Optional ICE servers (STUN + TURN). Defaults to public STUN.
|
|
52
|
+
* @param iceTransportPolicy 'all' (default) or 'relay' (force TURN only).
|
|
53
|
+
*/
|
|
54
|
+
constructor(strategy: SignalingStrategy, iceServers?: RTCIceServer[], iceTransportPolicy?: RTCIceTransportPolicy);
|
|
55
|
+
get peerId(): string;
|
|
56
|
+
get peers(): ReadonlySet<string>;
|
|
57
|
+
get hostId(): string;
|
|
58
|
+
get isHost(): boolean;
|
|
59
|
+
get room(): Room | undefined;
|
|
60
|
+
get initialPlayers(): Player[];
|
|
61
|
+
onPeerJoin(cb: (peerId: string) => void): void;
|
|
62
|
+
onPeerLeave(cb: (peerId: string) => void): void;
|
|
63
|
+
onPeerUpdated(cb: (player: Player) => void): void;
|
|
64
|
+
onRoomUpdated(cb: (room: Room) => void): void;
|
|
65
|
+
onHostChanged(cb: (newHostId: string) => void): void;
|
|
66
|
+
createChannel<T>(name: string, options?: ChannelOptions): CarverChannel<T>;
|
|
67
|
+
connect(roomId: string, config?: TransportConfig): Promise<void>;
|
|
68
|
+
disconnect(): void;
|
|
69
|
+
/** Expose strategy for lobby hooks */
|
|
70
|
+
get strategy(): SignalingStrategy;
|
|
71
|
+
/**
|
|
72
|
+
* Register a channel name and options without creating data channels yet.
|
|
73
|
+
* When _connectToPeer runs, it iterates this._channels and creates data
|
|
74
|
+
* channels for every registered name in the initial WebRTC offer.
|
|
75
|
+
* Later, when EventSync/SnapshotSync call createChannel(), the idempotent
|
|
76
|
+
* check returns the pre-registered entry and they just attach receivers.
|
|
77
|
+
*/
|
|
78
|
+
private _preRegisterChannel;
|
|
79
|
+
setReady(ready: boolean): void;
|
|
80
|
+
setMetadata(metadata: Record<string, unknown>): void;
|
|
81
|
+
setRoomMetadata(metadata: Record<string, unknown>): void;
|
|
82
|
+
kick(peerId: string, reason?: string): void;
|
|
83
|
+
transferHost(peerId: string): void;
|
|
84
|
+
setRoomState(state: RoomState): void;
|
|
85
|
+
setMaxPlayers(n: number): void;
|
|
86
|
+
lockRoom(): void;
|
|
87
|
+
unlockRoom(): void;
|
|
88
|
+
/** No-op: lobby uses strategy.subscribeToLobby() directly */
|
|
89
|
+
requestRoomList(): void;
|
|
90
|
+
private _onStrategyPeerDiscovered;
|
|
91
|
+
private _setupRoomControlChannel;
|
|
92
|
+
private _handleControlMessage;
|
|
93
|
+
private _sendControlMessage;
|
|
94
|
+
private _broadcastControlMessage;
|
|
95
|
+
private _electAndSetHost;
|
|
96
|
+
private _connectToPeer;
|
|
97
|
+
private _handleSignal;
|
|
98
|
+
private _createDataChannelOnPeer;
|
|
99
|
+
private _setupDataChannelReceiver;
|
|
100
|
+
private _sendOnChannel;
|
|
101
|
+
private _removePeer;
|
|
102
|
+
private _checkRateLimit;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Build RTCConfiguration from user-provided ICE servers.
|
|
107
|
+
*
|
|
108
|
+
* If the user provides `iceServers`, those are used as-is (STUN + TURN).
|
|
109
|
+
* Otherwise, default public STUN servers are used.
|
|
110
|
+
*
|
|
111
|
+
* TURN servers should be included in the `iceServers` array by the user:
|
|
112
|
+
* ```ts
|
|
113
|
+
* iceServers: [
|
|
114
|
+
* { urls: 'stun:stun.cloudflare.com:3478' },
|
|
115
|
+
* { urls: 'turn:turn.cloudflare.com:3478', username: '...', credential: '...' },
|
|
116
|
+
* ]
|
|
117
|
+
* ```
|
|
118
|
+
*/
|
|
119
|
+
declare function buildICEConfig(options?: {
|
|
120
|
+
iceServers?: RTCIceServer[];
|
|
121
|
+
iceTransportPolicy?: RTCIceTransportPolicy;
|
|
122
|
+
}): RTCConfiguration;
|
|
123
|
+
|
|
124
|
+
interface PeerConnectionEvents {
|
|
125
|
+
onStateChange: (state: PeerState) => void;
|
|
126
|
+
onDataChannel: (channel: RTCDataChannel) => void;
|
|
127
|
+
onIceCandidate: (candidate: RTCIceCandidate) => void;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Manages a single RTCPeerConnection to one remote peer.
|
|
131
|
+
*
|
|
132
|
+
* ICE candidates that arrive before the remote description is set are
|
|
133
|
+
* buffered and flushed automatically once setRemoteDescription completes.
|
|
134
|
+
* This is critical for Firebase/MQTT signaling where offer, answer, and
|
|
135
|
+
* candidates can arrive nearly simultaneously.
|
|
136
|
+
*/
|
|
137
|
+
declare class PeerConnection {
|
|
138
|
+
readonly peerId: string;
|
|
139
|
+
private _connection;
|
|
140
|
+
private _channels;
|
|
141
|
+
private _events;
|
|
142
|
+
private _state;
|
|
143
|
+
private _remoteDescriptionSet;
|
|
144
|
+
private _pendingCandidates;
|
|
145
|
+
constructor(peerId: string, config: RTCConfiguration, events: PeerConnectionEvents);
|
|
146
|
+
get state(): PeerState;
|
|
147
|
+
get connection(): RTCPeerConnection;
|
|
148
|
+
private _updateState;
|
|
149
|
+
createOffer(): Promise<RTCSessionDescriptionInit>;
|
|
150
|
+
handleOffer(offer: RTCSessionDescriptionInit): Promise<RTCSessionDescriptionInit>;
|
|
151
|
+
handleAnswer(answer: RTCSessionDescriptionInit): Promise<void>;
|
|
152
|
+
addIceCandidate(candidate: RTCIceCandidateInit): Promise<void>;
|
|
153
|
+
private _flushPendingCandidates;
|
|
154
|
+
createDataChannel(name: string, options?: ChannelOptions): RTCDataChannel;
|
|
155
|
+
getDataChannel(name: string): RTCDataChannel | undefined;
|
|
156
|
+
close(): void;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export { CarverChannel, CarverTransport, ChannelOptions, PeerConnection, type PeerState, type RateLimitConfig, SignalingStrategy, type TransportCallbacks, TransportConfig, WebRTCTransport, buildICEConfig };
|