make next update interval a property on network update component instead of inheritable attribute on entities

This commit is contained in:
Elizabeth Hunt 2023-09-02 14:40:46 -06:00
parent c551f519ca
commit 29ba1c29d7
Signed by: simponic
GPG Key ID: 52B3774857EB24B1
7 changed files with 42 additions and 33 deletions

View File

@ -16,6 +16,7 @@ export class BoundingBox extends Component {
} }
public isCollidingWith(box: BoundingBox): boolean { public isCollidingWith(box: BoundingBox): boolean {
// optimization; when neither rotates just check if they overlap
if (this.rotation == 0 && box.rotation == 0) { if (this.rotation == 0 && box.rotation == 0) {
const thisTopLeft = this.getTopLeft(); const thisTopLeft = this.getTopLeft();
const thisBottomRight = this.getBottomRight(); const thisBottomRight = this.getBottomRight();

View File

@ -1,7 +1,23 @@
import { Component, ComponentNames } from '.'; import { Component, ComponentNames } from '.';
export class NetworkUpdateable extends Component { export class NetworkUpdateable extends Component {
constructor() { static DEFAULT_UPDATE_JITTER_MS = 30;
static DEFAULT_THRESHOLD_TIME_MS = 20;
public updateThreshold: number;
public jitter: number;
constructor(
updateThreshold = NetworkUpdateable.DEFAULT_THRESHOLD_TIME_MS,
jitter = NetworkUpdateable.DEFAULT_UPDATE_JITTER_MS
) {
super(ComponentNames.NetworkUpdateable); super(ComponentNames.NetworkUpdateable);
this.updateThreshold = updateThreshold;
this.jitter = jitter;
}
public getNextUpdateTime() {
return Math.random() * this.jitter + this.updateThreshold;
} }
} }

View File

@ -1,8 +1,7 @@
import { EntityNames, Floor, Player } from '.'; import { EntityNames, Floor, Player } from '.';
import { type Component } from '../components'; import { type Component } from '../components';
const randomId = () => const randomId = () => (Math.random() * 1_000_000_000).toString();
(performance.now() + Math.random() * 10_000_000).toString();
export abstract class Entity { export abstract class Entity {
public id: string; public id: string;
@ -59,6 +58,4 @@ export abstract class Entity {
public abstract setFrom(args: Record<string, any>): void; public abstract setFrom(args: Record<string, any>): void;
public abstract serialize(): Record<string, any>; public abstract serialize(): Record<string, any>;
public abstract getNextUpdateInterval(): number;
} }

View File

@ -17,7 +17,7 @@ export class Floor extends Entity {
this.addComponent( this.addComponent(
new Sprite( new Sprite(
IMAGES.get((Floor.spriteSpec?.states?.get(width) as SpriteSpec).sheet), IMAGES.get(Floor.spriteSpec!.states!.get(width)!.sheet!)!,
{ x: 0, y: 0 }, { x: 0, y: 0 },
{ width, height: Floor.spriteSpec.height }, { width, height: Floor.spriteSpec.height },
Floor.spriteSpec.msPerFrame, Floor.spriteSpec.msPerFrame,
@ -45,8 +45,4 @@ export class Floor extends Entity {
) )
); );
} }
public getNextUpdateInterval() {
return Math.random() * 500;
}
} }

View File

@ -60,7 +60,7 @@ export class Player extends Entity {
const [leftSprite, rightSprite] = [Direction.LEFT, Direction.RIGHT].map( const [leftSprite, rightSprite] = [Direction.LEFT, Direction.RIGHT].map(
(direction) => (direction) =>
new Sprite( new Sprite(
IMAGES.get(Player.spriteSpec.states?.get(direction)?.sheet as string), IMAGES.get(Player.spriteSpec.states!.get(direction)!.sheet!)!,
{ x: 0, y: 0 }, { x: 0, y: 0 },
{ width: Player.spriteSpec.width, height: Player.spriteSpec.height }, { width: Player.spriteSpec.width, height: Player.spriteSpec.height },
Player.spriteSpec.msPerFrame, Player.spriteSpec.msPerFrame,
@ -102,8 +102,4 @@ export class Player extends Entity {
new BoundingBox(center, boundingBox.dimension, boundingBox.rotation) new BoundingBox(center, boundingBox.dimension, boundingBox.rotation)
].forEach((component) => this.addComponent(component)); ].forEach((component) => this.addComponent(component));
} }
public getNextUpdateInterval() {
return Math.random() * 30 + 50;
}
} }

View File

@ -1,6 +1,6 @@
import { System, SystemNames } from '.'; import { System, SystemNames } from '.';
import { Game } from '../Game'; import { Game } from '../Game';
import { ComponentNames } from '../components'; import { ComponentNames, NetworkUpdateable } from '../components';
import { import {
type MessageQueueProvider, type MessageQueueProvider,
type MessagePublisher, type MessagePublisher,
@ -55,15 +55,20 @@ export class NetworkUpdate extends System {
// 2. send entity updates // 2. send entity updates
const updateMessages: EntityUpdateBody[] = []; const updateMessages: EntityUpdateBody[] = [];
// todo: figure out if we can use the controllable component to determine if we should publish an update
game.forEachEntityWithComponent( game.forEachEntityWithComponent(
ComponentNames.NetworkUpdateable, ComponentNames.NetworkUpdateable,
(entity) => { (entity) => {
const networkUpdateableComponent =
entity.getComponent<NetworkUpdateable>(
ComponentNames.NetworkUpdateable
);
const nextUpdateTime = networkUpdateableComponent.getNextUpdateTime();
const newHash = stringify(entity.serialize()); const newHash = stringify(entity.serialize());
let updateInfo: EntityUpdateInfo = this.entityUpdateInfo.get( let updateInfo: EntityUpdateInfo = this.entityUpdateInfo.get(
entity.id entity.id
) ?? { ) ?? {
timer: this.getNextUpdateTimeMs(), timer: nextUpdateTime,
hash: newHash hash: newHash
}; };
@ -71,13 +76,11 @@ export class NetworkUpdate extends System {
updateInfo.timer -= dt; updateInfo.timer -= dt;
this.entityUpdateInfo.set(entity.id, updateInfo); this.entityUpdateInfo.set(entity.id, updateInfo);
if (updateInfo.timer > 0) return; if (updateInfo.timer > 0) return;
updateInfo.timer = entity.getNextUpdateInterval(); updateInfo.timer = nextUpdateTime;
this.entityUpdateInfo.set(entity.id, updateInfo); this.entityUpdateInfo.set(entity.id, updateInfo);
// maybe update if hash is not consitent // maybe update, if hash is not consistent
if (updateInfo.hash == newHash) { if (updateInfo.hash == newHash) return;
return;
}
updateInfo.hash = newHash; updateInfo.hash = newHash;
this.entityUpdateInfo.set(entity.id, updateInfo); this.entityUpdateInfo.set(entity.id, updateInfo);
@ -102,7 +105,7 @@ export class NetworkUpdate extends System {
} }
} }
private getNextUpdateInterval(): number { private getNextUpdateInterval() {
return Math.random() * 30; return Math.random() * 30 + 20;
} }
} }

View File

@ -42,7 +42,9 @@ export class GameServer {
fetch: (req, srv) => this.fetchHandler(req, srv), fetch: (req, srv) => this.fetchHandler(req, srv),
websocket: { websocket: {
open: (ws) => this.openWebsocket(ws), open: (ws) => this.openWebsocket(ws),
message: (ws, msg) => this.websocketMessage(ws, msg), message: (ws, msg) => {
if (typeof msg === 'string') this.websocketMessage(ws, msg);
},
close: (ws) => this.closeWebsocket(ws) close: (ws) => this.closeWebsocket(ws)
} }
}); });
@ -54,21 +56,19 @@ export class GameServer {
private websocketMessage( private websocketMessage(
websocket: ServerWebSocket<SessionData>, websocket: ServerWebSocket<SessionData>,
message: string | Uint8Array message: string
) { ) {
if (typeof message == 'string') {
const receivedMessage = parse<ServerMessage>(message); const receivedMessage = parse<ServerMessage>(message);
receivedMessage.sessionData = websocket.data; receivedMessage.sessionData = websocket.data;
this.messageReceiver.addMessage(receivedMessage); this.messageReceiver.addMessage(receivedMessage);
} }
}
private closeWebsocket(websocket: ServerWebSocket<SessionData>) { private closeWebsocket(websocket: ServerWebSocket<SessionData>) {
const { sessionId } = websocket.data; const { sessionId } = websocket.data;
const sessionEntities = const sessionEntities =
this.sessionManager.getSession(sessionId)!.controllableEntities; this.sessionManager.getSession(sessionId)?.controllableEntities;
this.sessionManager.removeSession(sessionId); this.sessionManager.removeSession(sessionId);
if (!sessionEntities) return; if (!sessionEntities) return;