Initial Commit
This commit is contained in:
@@ -0,0 +1,392 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user