393 lines
11 KiB
TypeScript
393 lines
11 KiB
TypeScript
/**
|
|
* 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<T extends BitPackerSchema>
|
|
implements GameIntegrationManagerInterface<T>
|
|
{
|
|
private positionPredictor: PositionPredictor | null = null;
|
|
private rotationPredictor: RotationPredictor | null = null;
|
|
private interpolator: GameInterpolator | null = null;
|
|
private reconciliation: StateReconciliation<T> | 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<T>(
|
|
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<T>): 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<I extends InputData>(
|
|
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<S extends GameState, I extends InputData>(
|
|
currentState: S,
|
|
applyPhysics: PhysicsFunction<S, I>,
|
|
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<S extends GameState>(
|
|
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<S extends GameState, I extends InputData>(
|
|
currentState: S,
|
|
applyPhysics: PhysicsFunction<S, I>
|
|
): S {
|
|
if (!this.reconciliation) {
|
|
return currentState;
|
|
}
|
|
return this.reconciliation.reconcileState(
|
|
currentState as unknown as PlayerState,
|
|
applyPhysics as unknown as PhysicsFunction<PlayerState, PlayerInput>
|
|
) 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<T>(
|
|
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;
|
|
}
|
|
}
|
|
}
|