Optimize performance

This commit is contained in:
schmelczerandras 2020-10-26 14:18:36 +01:00
parent e4cd3284b7
commit b8ef90c100
31 changed files with 319 additions and 206 deletions

View file

@ -1,6 +1,6 @@
{
"name": "declared-server",
"version": "0.0.7",
"version": "0.0.8",
"description": "Game server for decla.red",
"keywords": [],
"author": "András Schmelczer <andras@schmelczer.dev> (https://schmelczer.dev/)",
@ -20,6 +20,7 @@
"gl-matrix": "^3.3.0",
"minimist": "^1.2.5",
"socket.io": "^2.3.0",
"socket.io-msgpack-parser": "^2.0.0",
"uws": "^10.148.1"
},
"devDependencies": {

View file

@ -4,7 +4,8 @@ export const defaultOptions: Options = {
port: 3000,
name: 'Test server',
playerLimit: 8,
npcCount: 4,
seed: 0,
scoreLimit: 500,
worldSize: 15000,
worldSize: 8000,
};

View file

@ -11,6 +11,7 @@ import {
CharacterTeam,
GameEnd,
GameStart,
Command,
} from 'shared';
import { createWorld } from './map/create-world';
import { DeltaTimeCalculator } from './helper/delta-time-calculator';
@ -39,14 +40,19 @@ export class GameServer {
this.objects = new PhysicalContainer();
createWorld(this.objects, this.options.worldSize);
this.objects.initialize();
this.players = new PlayerContainer(this.objects, this.options.playerLimit);
this.players = new PlayerContainer(
this.objects,
this.options.playerLimit,
this.options.npcCount,
);
this.deltaTimeCalculator = new DeltaTimeCalculator();
this.deltaTimes = [];
this.declaPoints = 0;
this.redPoints = 0;
this.isInEndGame = false;
this.timeScaling = 1;
previousPlayers?.sendOnSocket(serialize(new GameStart()));
previousPlayers?.queueCommandForEachClient(new GameStart());
previousPlayers?.sendQueuedCommands();
}
constructor(private readonly io: ioserver.Server, private options: Options) {
@ -60,8 +66,12 @@ export class GameServer {
try {
const player = this.players.createPlayer(playerInfo, socket);
socket.on(TransportEvents.PlayerToServer, (json: string) => {
const command = deserialize(json);
player.sendCommand(command);
try {
const commands: Array<Command> = deserialize(json);
commands.forEach((c) => player.sendCommand(c));
} catch (e) {
console.log('Error while processing command', e);
}
});
this.sendServerStateUpdate();
@ -94,9 +104,6 @@ export class GameServer {
}
private updatePoints() {
if (this.isInEndGame) {
return;
}
const { decla, red } = this.objects.getPointsGenerated();
this.declaPoints += decla;
this.redPoints += red;
@ -110,8 +117,8 @@ export class GameServer {
private endGame(winningTeam: CharacterTeam) {
this.isInEndGame = true;
const endTitleLength = 6000;
this.players.sendOnSocket(
serialize(new GameEnd(winningTeam, endTitleLength / 1000, true)),
this.players.queueCommandForEachClient(
new GameEnd(winningTeam, endTitleLength / 1000, true),
);
setTimeout(() => this.destroy(), endTitleLength * 1.1);
}
@ -120,6 +127,7 @@ export class GameServer {
this.initialize();
}
private timeSinceLastPointUpdate = 0;
private handlePhysics() {
let delta = this.deltaTimeCalculator.getNextDeltaTimeInSeconds();
const framesBetweenDeltaTimeCalculation = 1000;
@ -137,26 +145,31 @@ export class GameServer {
this.deltaTimes = [];
}
if ((this.timeSinceLastServerStateUpdate += delta) > 5) {
if ((this.timeSinceLastServerStateUpdate += delta) > 4) {
this.timeSinceLastServerStateUpdate = 0;
this.sendServerStateUpdate();
}
if ((this.timeSinceLastPointUpdate += delta) > 0.5) {
this.timeSinceLastPointUpdate = 0;
this.players.queueCommandForEachClient(
new UpdateGameState(this.declaPoints, this.redPoints, this.options.scoreLimit),
);
}
if (this.isInEndGame) {
this.timeScaling *= Math.pow(settings.endGameDeltaScaling, delta);
delta /= this.timeScaling;
} else {
this.updatePoints();
}
this.objects.stepObjects(delta);
this.updatePoints();
this.players.sendOnSocket(
serialize(
new UpdateGameState(this.declaPoints, this.redPoints, this.options.scoreLimit),
),
);
this.players.step(delta);
this.objects.resetRemoteCalls();
this.players.sendQueuedCommands();
const physicsDelta = this.deltaTimeCalculator.getDeltaTimeInSeconds() * 1000;
this.deltaTimes.push(physicsDelta);
const sleepTime = settings.targetPhysicsDeltaTimeInMilliseconds - physicsDelta;

View file

@ -7,6 +7,7 @@ import minimist from 'minimist';
import { glMatrix } from 'gl-matrix';
import { GameServer } from './game-server';
import { defaultOptions } from './default-options';
import parser from 'socket.io-msgpack-parser';
glMatrix.setMatrixArrayType(Array);
applyArrayPlugins();
@ -21,7 +22,7 @@ Random.seed = options.seed;
const app = express();
const server = new Server(app);
const io = ioserver(server);
const io = ioserver(server, { parser } as any);
const gameServer = new GameServer(io, options);

View file

@ -104,6 +104,10 @@ export class CirclePhysical implements Circle, DynamicPhysical, ReactsToCollisio
const delta = vec2.scale(vec2.create(), this.velocity, deltaTimeInSeconds);
this.radius += vec2.length(delta);
const intersecting = this.container.findIntersecting(this.boundingBox);
this.radius -= vec2.length(delta);
const stepCount = Math.ceil(vec2.length(delta) / settings.physicsMaxStep);
vec2.scale(delta, delta, 1 / stepCount);
@ -115,9 +119,6 @@ export class CirclePhysical implements Circle, DynamicPhysical, ReactsToCollisio
this.velocity,
deltaTimeInSeconds / stepCount,
);
this.radius += vec2.length(distance);
const intersecting = this.container.findIntersecting(this.boundingBox);
this.radius -= vec2.length(distance);
const { normal, hitSurface, hitObject } = moveCircle(
this,
@ -144,16 +145,16 @@ export class CirclePhysical implements Circle, DynamicPhysical, ReactsToCollisio
}
public tryMove(delta: vec2): GameObject | undefined {
this.radius += vec2.length(delta);
const intersecting = this.container.findIntersecting(this.boundingBox);
this.radius -= vec2.length(delta);
const stepCount = Math.ceil(vec2.length(delta) / settings.physicsMaxStep);
vec2.scale(delta, delta, 1 / stepCount);
let lastHit: GameObject | undefined;
for (let i = 0; i < stepCount; i++) {
this.radius += vec2.length(delta);
const intersecting = this.container.findIntersecting(this.boundingBox);
this.radius -= vec2.length(delta);
const { tangent, hitSurface, hitObject } = moveCircle(
this,
vec2.clone(delta),

View file

@ -3,6 +3,7 @@ export interface Options {
name: string;
playerLimit: number;
scoreLimit: number;
npcCount: number;
seed: number;
worldSize: number;
}

View file

@ -1,10 +1,4 @@
import {
CharacterTeam,
PlayerInformation,
Random,
TransportEvents,
settings,
} from 'shared';
import { CharacterTeam, PlayerInformation, Random, settings, Command } from 'shared';
import { PhysicalContainer } from '../physics/containers/physical-container';
import { NPC } from './npc';
import { Player } from './player';
@ -17,12 +11,16 @@ export class PlayerContainer {
constructor(
private readonly objects: PhysicalContainer,
private readonly playerMaxCount: number,
private readonly npcMaxCount: number,
) {
this.createNPCs();
}
public createNPCs() {
const newNpcCount = this.playerMaxCount - this._players.length - this._npcs.length;
const newNpcCount = Math.min(
this.playerMaxCount - this._players.length - this._npcs.length,
this.npcMaxCount - this._npcs.length,
);
for (let i = 0; i < newNpcCount; i++) {
const name = `BOT ${Random.choose(settings.npcNames)}`;
this._npcs.push(
@ -62,8 +60,12 @@ export class PlayerContainer {
this.players.forEach((p) => p.step(deltaTimeInSeconds));
}
public sendOnSocket(message: any) {
this._players.forEach((p) => p.socket.emit(TransportEvents.ServerToPlayer, message));
public queueCommandForEachClient(command: Command) {
this._players.forEach((p) => p.queueCommandSend(command));
}
public sendQueuedCommands() {
this._players.forEach((p) => p.sendQueuedCommandsToClient());
}
private getTeamOfNextPlayer(isNpc = false): CharacterTeam {

View file

@ -55,7 +55,7 @@ export class Player extends PlayerBase {
playerContainer: PlayerContainer,
objectContainer: PhysicalContainer,
team: CharacterTeam,
public readonly socket: SocketIO.Socket,
private readonly socket: SocketIO.Socket,
) {
super(playerInfo, playerContainer, objectContainer, team);
@ -86,7 +86,7 @@ export class Player extends PlayerBase {
super.createCharacter();
this.objectsPreviouslyInViewArea.push(this.character!);
this.sendToPlayer(new CreatePlayerCommand(this.character!));
this.queueCommandSend(new CreatePlayerCommand(this.character!));
}
private timeUntilRespawn = 0;
@ -98,21 +98,18 @@ export class Player extends PlayerBase {
this.sumDeaths++;
this.sumKills = this.character.killCount;
this.socket.emit(
TransportEvents.ServerToPlayer,
serialize(new PlayerDiedCommand(settings.playerDiedTimeout)),
);
this.queueCommandSend(new PlayerDiedCommand(settings.playerDiedTimeout));
this.character = null;
this.timeUntilRespawn = settings.playerDiedTimeout;
}
} else {
this.sendToPlayer(
this.queueCommandSend(
new ServerAnnouncement(`Reviving in ${Math.round(this.timeUntilRespawn)}`),
);
if ((this.timeUntilRespawn -= deltaTimeInSeconds) < 0) {
this.createCharacter();
this.center = this.character!.center;
this.sendToPlayer(new ServerAnnouncement(''));
this.queueCommandSend(new ServerAnnouncement(''));
}
}
@ -136,14 +133,16 @@ export class Player extends PlayerBase {
this.objectsPreviouslyInViewArea = objectsInViewArea;
if (noLongerIntersecting.length > 0) {
this.sendToPlayer(new DeleteObjectsCommand(noLongerIntersecting.map((g) => g.id)));
this.queueCommandSend(
new DeleteObjectsCommand(noLongerIntersecting.map((g) => g.id)),
);
}
if (newlyIntersecting.length > 0) {
this.sendToPlayer(new CreateObjectsCommand(newlyIntersecting));
this.queueCommandSend(new CreateObjectsCommand(newlyIntersecting));
}
this.sendToPlayer(
this.queueCommandSend(
new RemoteCallsForObjects(
this.objectsPreviouslyInViewArea.map(
(g) => new RemoteCallsForObject(g.id, g.getRemoteCalls()),
@ -151,7 +150,7 @@ export class Player extends PlayerBase {
),
);
this.sendToPlayer(new UpdateOtherPlayerDirections(this.getOtherPlayers()));
this.queueCommandSend(new UpdateOtherPlayerDirections(this.getOtherPlayers()));
}
private getOtherPlayers(): Array<OtherPlayerDirection> {
@ -185,8 +184,14 @@ export class Player extends PlayerBase {
);
}
private sendToPlayer(command: Command) {
this.socket.emit(TransportEvents.ServerToPlayer, serialize(command));
private commandsToBeSent: Array<Command> = [];
public queueCommandSend(command: Command) {
this.commandsToBeSent.push(command);
}
public sendQueuedCommandsToClient() {
this.socket.emit(TransportEvents.ServerToPlayer, serialize(this.commandsToBeSent));
this.commandsToBeSent = [];
}
public destroy() {

View file

@ -10,7 +10,9 @@
"moduleResolution": "node",
"module": "commonjs",
"composite": true,
"lib": ["dom", "es2017"]
"lib": ["dom", "es2017"],
"typeRoots": ["./types", "./node_modules/@types"]
},
"include": ["src/**/*.ts"]
"include": ["src/**/*.ts"],
"exclude": ["node_modules", "typings"]
}

View file

@ -0,0 +1 @@
declare module 'socket.io-msgpack-parser';