/** * Integration manager for GameBitPacker features */ import { type NetworkSimulationOptions, type NetworkSimulationStats, NetworkSimulator, PacketStatus, } from "@suaveplan/bitpacker-network"; import type { BitPackerSchema } from "@suaveplan/types"; import { GameInterpolator } from "../interpolation/interpolation.js"; import { PositionPredictor, RotationPredictor, } from "../prediction/prediction.js"; import * as GameQuantization from "../quantization/quantization.js"; import { StateReconciliation } from "../reconciliation/reconciliation.js"; import type { GameBitPackerOptions, GameState, IGameBitPacker, InputData, InterpolationMethod, InterpolationMethodEnum, NetworkStats, PhysicsFunction, PlayerInput, PlayerState, Rotation, Vector3, } from "../types.js"; import type { GameIntegrationManagerInterface, IntegrationManagerFeatures, } from "./integration-manager.types.js"; export class GameIntegrationManager implements GameIntegrationManagerInterface { private positionPredictor: PositionPredictor | null = null; private rotationPredictor: RotationPredictor | null = null; private interpolator: GameInterpolator | null = null; private reconciliation: StateReconciliation | null = null; private networkSimulator: NetworkSimulator | null = null; constructor( private options: GameBitPackerOptions, private schema: T ) { this.initializeFeatures(); } /** * Initialize enhanced features based on options */ private initializeFeatures(): void { if (this.options.enablePrediction) { this.positionPredictor = new PositionPredictor( this.options.predictionOptions ); this.rotationPredictor = new RotationPredictor( this.options.predictionOptions ); } if (this.options.enableInterpolation) { this.interpolator = new GameInterpolator( this.options.interpolationOptions ); } if (this.options.enableReconciliation) { this.reconciliation = new StateReconciliation( this.schema, this.options.reconciliationOptions ); } if (this.options.enableNetworkSimulation) { this.networkSimulator = new NetworkSimulator( this.options.networkSimulationOptions ); } } /** * Set BitPacker reference for reconciliation */ public setBitPacker(bitPacker: IGameBitPacker): void { if (this.reconciliation) { this.reconciliation.setBitPacker(bitPacker); } } /** * Interpolate position */ public interpolatePosition( from: Vector3, to: Vector3, t: number, method?: InterpolationMethod | InterpolationMethodEnum ): Vector3 { if (!this.interpolator) { // Simple linear interpolation fallback return { x: from.x + (to.x - from.x) * t, y: from.y + (to.y - from.y) * t, z: from.z + (to.z - from.z) * t, }; } return this.interpolator.interpolatePosition(from, to, t, method); } /** * Interpolate rotation */ public interpolateRotation( from: Rotation, to: Rotation, t: number, method?: InterpolationMethod | InterpolationMethodEnum ): Rotation { if (!this.interpolator) { // Simple linear interpolation fallback with angle wrapping if ( "yaw" in from && "pitch" in from && "yaw" in to && "pitch" in to ) { return { yaw: GameQuantization.interpolateAngle(from.yaw, to.yaw, t), pitch: GameQuantization.interpolateAngle( from.pitch, to.pitch, t ), }; } return to; } return this.interpolator.interpolateRotation(from, to, t, method); } /** * Predict position */ public predictPosition(deltaTime: number): Vector3 | null { if (!this.positionPredictor) { return null; } return this.positionPredictor.predictPosition(deltaTime); } /** * Predict rotation */ public predictRotation(deltaTime: number): Rotation | null { if (!this.rotationPredictor) { return null; } return this.rotationPredictor.predictRotation(deltaTime); } /** * Add position to history */ public addPositionToHistory( position: Vector3, timestamp = Date.now() ): void { if (this.positionPredictor) { this.positionPredictor.addPosition(position, timestamp); } } /** * Add rotation to history */ public addRotationToHistory( rotation: Rotation, timestamp = Date.now() ): void { if (this.rotationPredictor) { this.rotationPredictor.addRotation(rotation, timestamp); } } /** * Process input */ public processInput( input: I, timestamp = Date.now() ): number { if (!this.reconciliation) { return -1; } return this.reconciliation.processInput( input as unknown as PlayerInput, timestamp ); } /** * Apply client-side prediction */ public predictClientState( currentState: S, applyPhysics: PhysicsFunction, deltaTime: number ): S { if (!this.reconciliation) { return currentState; } return this.reconciliation.predictClientState( currentState as unknown as PlayerState, applyPhysics as unknown as PhysicsFunction< PlayerState, PlayerInput >, deltaTime ) as unknown as S; } /** * Process server update */ public processServerUpdate( serverState: S, serverSequence: number, serverTimestamp: number ): boolean { if (!this.reconciliation) { return false; } return this.reconciliation.processServerUpdate( serverState as unknown as PlayerState, serverSequence, serverTimestamp ); } /** * Reconcile state */ public reconcileState( currentState: S, applyPhysics: PhysicsFunction ): S { if (!this.reconciliation) { return currentState; } return this.reconciliation.reconcileState( currentState as unknown as PlayerState, applyPhysics as unknown as PhysicsFunction ) as unknown as S; } /** * Send packet with network simulation */ public sendPacket( data: Uint8Array, callback: (data: Uint8Array) => void, dropCallback?: () => void ): void { if (!this.networkSimulator) { callback(data); return; } this.networkSimulator.sendPacket(data, (status, resultData) => { if (status === PacketStatus.LOST && dropCallback) { dropCallback(); } else if (resultData) { callback(resultData); } }); } /** * Set network options */ public setNetworkOptions(options: NetworkSimulationOptions): void { if (this.networkSimulator) { this.networkSimulator.setOptions(options); } } /** * Get network statistics */ public getNetworkStats(): NetworkStats { if (!this.networkSimulator) { return { packetsSent: 0, packetsReceived: 0, packetsLost: 0, packetsDelayed: 0, packetsReordered: 0, averageLatency: 0, averageJitter: 0, totalBytes: 0, }; } const stats = this.networkSimulator.getStats() as NetworkSimulationStats; return { packetsSent: stats.packetsSent, packetsReceived: stats.packetsReceived, packetsLost: stats.packetsLost, packetsDelayed: 0, // Not in NetworkSimulationStats packetsReordered: stats.packetsReordered, averageLatency: stats.averageLatency, averageJitter: 0, // Not in NetworkSimulationStats totalBytes: stats.bytesTransferred, }; } /** * Set features dynamically */ public setFeatures(features: IntegrationManagerFeatures): void { // Update prediction if (features.prediction !== undefined) { this.options.enablePrediction = features.prediction; if (features.prediction && !this.positionPredictor) { this.positionPredictor = new PositionPredictor( this.options.predictionOptions ); this.rotationPredictor = new RotationPredictor( this.options.predictionOptions ); } else if (!features.prediction) { this.positionPredictor = null; this.rotationPredictor = null; } } // Update interpolation if (features.interpolation !== undefined) { this.options.enableInterpolation = features.interpolation; if (features.interpolation && !this.interpolator) { this.interpolator = new GameInterpolator( this.options.interpolationOptions ); } else if (!features.interpolation) { this.interpolator = null; } } // Update reconciliation if (features.reconciliation !== undefined) { this.options.enableReconciliation = features.reconciliation; if (features.reconciliation && !this.reconciliation) { this.reconciliation = new StateReconciliation( this.schema, this.options.reconciliationOptions ); } else if (!features.reconciliation) { this.reconciliation = null; } } // Update network simulation if (features.networkSimulation !== undefined) { this.options.enableNetworkSimulation = features.networkSimulation; if (features.networkSimulation && !this.networkSimulator) { this.networkSimulator = new NetworkSimulator( this.options.networkSimulationOptions ); } else if (!features.networkSimulation) { if (this.networkSimulator) { this.networkSimulator.destroy(); } this.networkSimulator = null; } } } /** * Clean up resources */ public destroy(): void { if (this.networkSimulator) { this.networkSimulator.destroy(); this.networkSimulator = null; } } }