summaryrefslogtreecommitdiff
path: root/engine
diff options
context:
space:
mode:
Diffstat (limited to 'engine')
-rw-r--r--engine/Game.ts4
-rw-r--r--engine/components/Control.ts7
-rw-r--r--engine/entities/Player.ts5
-rw-r--r--engine/structures/RefreshingCollisionFinderBehavior.ts1
-rw-r--r--engine/systems/NetworkUpdate.ts68
-rw-r--r--engine/systems/Physics.ts4
-rw-r--r--engine/utils/coding.ts15
7 files changed, 75 insertions, 29 deletions
diff --git a/engine/Game.ts b/engine/Game.ts
index cdd3507..19de398 100644
--- a/engine/Game.ts
+++ b/engine/Game.ts
@@ -56,8 +56,8 @@ export class Game {
this.systems.set(system.name, system);
}
- public getSystem(name: string): System | undefined {
- return this.systems.get(name);
+ public getSystem<T>(name: string): T {
+ return this.systems.get(name) as unknown as T;
}
public doGameLoop(timeStamp: number) {
diff --git a/engine/components/Control.ts b/engine/components/Control.ts
index d3987d7..b6a3dc3 100644
--- a/engine/components/Control.ts
+++ b/engine/components/Control.ts
@@ -3,16 +3,17 @@ import { Component, ComponentNames, Velocity } from '.';
export class Control extends Component {
public controlVelocityComponent: Velocity;
public controllableBy: string;
- public isControllable: boolean; // computed each update in the input system
+ public isControllable?: boolean; // updated by the input system
constructor(
controllableBy: string,
- controlVelocityComponent: Velocity = new Velocity()
+ controlVelocityComponent: Velocity = new Velocity(),
+ isControllable?: boolean
) {
super(ComponentNames.Control);
this.controllableBy = controllableBy;
+ this.isControllable = isControllable;
this.controlVelocityComponent = controlVelocityComponent;
- this.isControllable = false;
}
}
diff --git a/engine/entities/Player.ts b/engine/entities/Player.ts
index 4d91c6f..a7a41f8 100644
--- a/engine/entities/Player.ts
+++ b/engine/entities/Player.ts
@@ -82,7 +82,7 @@ export class Player extends Entity {
}
public setFrom(args: Record<string, any>) {
- const { control, velocity, forces, boundingBox } = args;
+ const { control, forces, velocity, boundingBox } = args;
let center = boundingBox.center;
@@ -92,7 +92,8 @@ export class Player extends Entity {
const distance = Math.sqrt(
Math.pow(center.y - myCenter.y, 2) + Math.pow(center.x - myCenter.x, 2)
);
- if (distance < 30) center = myCenter;
+ const clientServerPredictionCenterThreshold = 30;
+ if (distance < clientServerPredictionCenterThreshold) center = myCenter;
[
Object.assign(new Control(control.controllableBy), control),
diff --git a/engine/structures/RefreshingCollisionFinderBehavior.ts b/engine/structures/RefreshingCollisionFinderBehavior.ts
index 573ddd8..2215994 100644
--- a/engine/structures/RefreshingCollisionFinderBehavior.ts
+++ b/engine/structures/RefreshingCollisionFinderBehavior.ts
@@ -11,4 +11,5 @@ export interface RefreshingCollisionFinderBehavior {
insert(boxedEntry: BoxedEntry): void;
getNeighborIds(boxedEntry: BoxedEntry): Set<string>;
setTopLeft(topLeft: Coord2D): void;
+ setDimension(dimension: Dimension2D): void;
}
diff --git a/engine/systems/NetworkUpdate.ts b/engine/systems/NetworkUpdate.ts
index 6d13574..a54be2e 100644
--- a/engine/systems/NetworkUpdate.ts
+++ b/engine/systems/NetworkUpdate.ts
@@ -8,13 +8,16 @@ import {
MessageType,
EntityUpdateBody
} from '../network';
+import { stringify } from '../utils';
+
+type EntityUpdateInfo = { timer: number; hash: string };
export class NetworkUpdate extends System {
private queueProvider: MessageQueueProvider;
private publisher: MessagePublisher;
private messageProcessor: MessageProcessor;
- private entityUpdateTimers: Map<string, number>;
+ private entityUpdateInfo: Map<string, EntityUpdateInfo>;
constructor(
queueProvider: MessageQueueProvider,
@@ -27,10 +30,20 @@ export class NetworkUpdate extends System {
this.publisher = publisher;
this.messageProcessor = messageProcessor;
- this.entityUpdateTimers = new Map();
+ this.entityUpdateInfo = new Map();
}
public update(dt: number, game: Game) {
+ // 0. remove unnecessary info for removed entities
+ const networkUpdateableEntities = game.componentEntities.get(
+ ComponentNames.NetworkUpdateable
+ );
+ for (const entityId of this.entityUpdateInfo.keys()) {
+ if (!networkUpdateableEntities?.has(entityId)) {
+ this.entityUpdateInfo.delete(entityId);
+ }
+ }
+
// 1. process new messages
this.queueProvider
.getNewMessages()
@@ -39,34 +52,51 @@ export class NetworkUpdate extends System {
// 2. send entity updates
const updateMessages: EntityUpdateBody[] = [];
+
+ // todo: figure out if we can use the controllable component to determine if we should publish an update
game.forEachEntityWithComponent(
ComponentNames.NetworkUpdateable,
(entity) => {
- let timer = this.entityUpdateTimers.get(entity.id) ?? dt;
- timer -= dt;
- this.entityUpdateTimers.set(entity.id, timer);
-
- if (timer > 0) return;
- this.entityUpdateTimers.set(entity.id, this.getNextUpdateTimeMs());
-
- if (entity.hasComponent(ComponentNames.NetworkUpdateable)) {
- updateMessages.push({
- id: entity.id,
- args: entity.serialize()
- });
+ const newHash = stringify(entity.serialize());
+ let updateInfo: EntityUpdateInfo = this.entityUpdateInfo.get(
+ entity.id
+ ) ?? {
+ timer: this.getNextUpdateTimeMs(),
+ hash: newHash
+ };
+
+ // update timer
+ updateInfo.timer -= dt;
+ this.entityUpdateInfo.set(entity.id, updateInfo);
+ if (updateInfo.timer > 0) return;
+ updateInfo.timer = this.getNextUpdateTimeMs();
+ this.entityUpdateInfo.set(entity.id, updateInfo);
+
+ // maybe update if hash is not consitent
+ if (updateInfo.hash == newHash) {
+ return;
}
+ updateInfo.hash = newHash;
+ this.entityUpdateInfo.set(entity.id, updateInfo);
+
+ updateMessages.push({
+ id: entity.id,
+ args: entity.serialize()
+ });
}
);
- this.publisher.addMessage({
- type: MessageType.UPDATE_ENTITIES,
- body: updateMessages
- });
+
+ if (updateMessages.length)
+ this.publisher.addMessage({
+ type: MessageType.UPDATE_ENTITIES,
+ body: updateMessages
+ });
// 3. publish changes
this.publisher.publish();
}
private getNextUpdateTimeMs() {
- return Math.random() * 70 + 50;
+ return Math.random() * 30 + 50;
}
}
diff --git a/engine/systems/Physics.ts b/engine/systems/Physics.ts
index b5df459..4f8cc72 100644
--- a/engine/systems/Physics.ts
+++ b/engine/systems/Physics.ts
@@ -11,7 +11,7 @@ import {
Control
} from '../components';
import { PhysicsConstants } from '../config';
-import type { Force2D, Velocity2D } from '../interfaces';
+import type { Force2D } from '../interfaces';
import { Game } from '../Game';
export class Physics extends System {
@@ -98,7 +98,7 @@ export class Physics extends System {
? 360 + boundingBox.rotation
: boundingBox.rotation) % 360;
- // clear the control velocity
+ // clear the control velocity if and only if we are controlling
if (control && control.isControllable) {
control.controlVelocityComponent = new Velocity();
}
diff --git a/engine/utils/coding.ts b/engine/utils/coding.ts
index 3f78889..283844f 100644
--- a/engine/utils/coding.ts
+++ b/engine/utils/coding.ts
@@ -9,6 +9,18 @@ const replacer = (_key: any, value: any) => {
}
};
+const sortObj = (obj: any): any =>
+ obj === null || typeof obj !== 'object'
+ ? obj
+ : Array.isArray(obj)
+ ? obj.map(sortObj)
+ : Object.assign(
+ {},
+ ...Object.entries(obj)
+ .sort(([keyA], [keyB]) => keyA.localeCompare(keyB))
+ .map(([k, v]) => ({ [k]: sortObj(v) }))
+ );
+
const reviver = (_key: any, value: any) => {
if (typeof value === 'object' && value !== null) {
if (value.dataType === 'Map') {
@@ -18,8 +30,9 @@ const reviver = (_key: any, value: any) => {
return value;
};
+// "deterministic" stringify
export const stringify = (obj: any) => {
- return JSON.stringify(obj, replacer);
+ return JSON.stringify(sortObj(obj), replacer);
};
export const parse = <T>(str: string) => {