From 4d33e7dda99d98776ebe0211d9548b2ecfcbbb1b Mon Sep 17 00:00:00 2001 From: Steffen Date: Fri, 29 Jan 2021 21:54:27 +0100 Subject: [PATCH] optimized ping and gamestate messages --- server/index.js | 73 ++++++++++++++++++++++++++-------------- src/store/socket.js | 82 +++++++++++++++++++++++++++++---------------- vue.config.js | 2 ++ 3 files changed, 104 insertions(+), 53 deletions(-) diff --git a/server/index.js b/server/index.js index 0a82715..2a8ebc1 100644 --- a/server/index.js +++ b/server/index.js @@ -150,40 +150,63 @@ wss.on("connection", function connection(ws, req) { .substr(1) .split(",", 1) .pop(); - // don't log ping messages - if (messageType !== '"ping"') { - console.log(new Date(), wss.clients.size, ws.channel, ws.playerId, data); - } - // handle "direct" messages differently - if (messageType === '"direct"') { - try { - const dataToPlayer = JSON.parse(data)[1]; + switch (messageType) { + case '"ping"': + // ping messages will only be sent host -> all or all -> host channels[ws.channel].forEach(function each(client) { if ( client !== ws && client.readyState === WebSocket.OPEN && - dataToPlayer[client.playerId] + (ws.playerId === "host" || client.playerId === "host") ) { - client.send(JSON.stringify(dataToPlayer[client.playerId])); + client.send( + data.replace(/latency/, (client.latency || 0) + (ws.latency || 0)) + ); metrics.messages_outgoing.inc(); } }); - } catch (e) { - console.log("error parsing direct message JSON", e); - } - } else { - // all other messages - channels[ws.channel].forEach(function each(client) { - if (client !== ws && client.readyState === WebSocket.OPEN) { - // inject latency between both clients if ping message - if (messageType === '"ping"' && client.latency && ws.latency) { - client.send(data.replace(/latency/, client.latency + ws.latency)); - } else { - client.send(data); - } - metrics.messages_outgoing.inc(); + break; + case '"direct"': + // handle "direct" messages differently + console.log( + new Date(), + wss.clients.size, + ws.channel, + ws.playerId, + data + ); + try { + const dataToPlayer = JSON.parse(data)[1]; + channels[ws.channel].forEach(function each(client) { + if ( + client !== ws && + client.readyState === WebSocket.OPEN && + dataToPlayer[client.playerId] + ) { + client.send(JSON.stringify(dataToPlayer[client.playerId])); + metrics.messages_outgoing.inc(); + } + }); + } catch (e) { + console.log("error parsing direct message JSON", e); } - }); + break; + default: + // all other messages + console.log( + new Date(), + wss.clients.size, + ws.channel, + ws.playerId, + data + ); + channels[ws.channel].forEach(function each(client) { + if (client !== ws && client.readyState === WebSocket.OPEN) { + client.send(data); + metrics.messages_outgoing.inc(); + } + }); + break; } }); }); diff --git a/src/store/socket.js b/src/store/socket.js index bdab4c1..be5175e 100644 --- a/src/store/socket.js +++ b/src/store/socket.js @@ -62,13 +62,33 @@ class LiveSession { } } + /** + * Send a message directly to a single playerId, if provided. + * Otherwise broadcast it. + * @param playerId player ID or "host", optional + * @param command + * @param params + * @private + */ + _sendDirect(playerId, command, params) { + if (playerId) { + this._send("direct", { [playerId]: [command, params] }); + } else { + this._send(command, params); + } + } + /** * Open event handler for socket. * @private */ _onOpen() { if (this._isSpectator) { - this._send("req", "gs"); + this._sendDirect( + "host", + "getGamestate", + this._store.state.session.playerId + ); } else { this.sendGamestate(); } @@ -81,13 +101,12 @@ class LiveSession { */ _ping() { this._handlePing(); - if (this._isSpectator) { - this._send("direct", { - host: [this._store.state.session.playerId, "latency"] - }); - } else { - this._send("ping", [Object.keys(this._players).length, "latency"]); - } + this._send("ping", [ + this._isSpectator + ? this._store.state.session.playerId + : Object.keys(this._players).length, + "latency" + ]); clearTimeout(this._pingTimer); this._pingTimer = setTimeout(this._ping.bind(this), this._pingInterval); } @@ -105,10 +124,8 @@ class LiveSession { console.log("unsupported socket message", data); } switch (command) { - case "req": - if (params === "gs") { - this.sendGamestate(); - } + case "getGamestate": + this.sendGamestate(params); break; case "edition": this._updateEdition(params); @@ -206,7 +223,9 @@ class LiveSession { this._store.commit("session/setReconnecting", false); clearTimeout(this._reconnectTimer); if (this._socket) { - this._send("bye", this._store.state.session.playerId); + if (this._isSpectator) { + this._sendDirect("host", "bye", this._store.state.session.playerId); + } this._socket.close(1000); this._socket = null; } @@ -215,9 +234,10 @@ class LiveSession { /** * Publish the current gamestate. * Optional param to reduce traffic. (send only player data) + * @param playerId * @param isLightweight */ - sendGamestate(isLightweight = false) { + sendGamestate(playerId = "", isLightweight = false) { if (this._isSpectator) return; this._gamestate = this._store.state.players.players.map(player => ({ name: player.name, @@ -229,12 +249,15 @@ class LiveSession { : {}) })); if (isLightweight) { - this._send("gs", { gamestate: this._gamestate, isLightweight }); + this._sendDirect(playerId, "gs", { + gamestate: this._gamestate, + isLightweight + }); } else { const { session, grimoire } = this._store.state; const { fabled } = this._store.state.players; - this.sendEdition(); - this._send("gs", { + this.sendEdition(playerId); + this._sendDirect(playerId, "gs", { gamestate: this._gamestate, isNight: grimoire.isNight, nomination: session.nomination, @@ -324,15 +347,16 @@ class LiveSession { /** * Publish an edition update. ST only + * @param playerId */ - sendEdition() { + sendEdition(playerId = "") { if (this._isSpectator) return; const { edition } = this._store.state; let roles; if (!edition.isOfficial) { roles = Array.from(this._store.state.roles.keys()); } - this._send("edition", { + this._sendDirect(playerId, "edition", { edition: edition.isOfficial ? { id: edition.id } : Object.assign({}, edition, { logo: "" }), @@ -468,7 +492,7 @@ class LiveSession { * @param latency * @private */ - _handlePing([playerIdOrCount, latency] = []) { + _handlePing([playerIdOrCount = 0, latency] = []) { const now = new Date().getTime(); if (!this._isSpectator) { // remove players that haven't sent a ping in twice the timespan @@ -506,20 +530,22 @@ class LiveSession { // ping to ST this._store.commit("session/setPing", parseInt(latency, 10)); } - this._store.commit( - "session/setPlayerCount", - this._isSpectator - ? playerIdOrCount || 0 - : Object.keys(this._players).length - ); + // update player count + if (!this._isSpectator || playerIdOrCount) { + this._store.commit( + "session/setPlayerCount", + this._isSpectator ? playerIdOrCount : Object.keys(this._players).length + ); + } } /** - * Handle a player leaving the sessions + * Handle a player leaving the sessions. ST only * @param playerId * @private */ _handleBye(playerId) { + if (this._isSpectator) return; delete this._players[playerId]; this._store.commit( "session/setPlayerCount", @@ -786,7 +812,7 @@ export default store => { case "players/clear": case "players/remove": case "players/add": - session.sendGamestate(true); + session.sendGamestate("", true); break; case "players/update": session.sendPlayer(payload); diff --git a/vue.config.js b/vue.config.js index d61bd25..c18495d 100644 --- a/vue.config.js +++ b/vue.config.js @@ -1,3 +1,5 @@ module.exports = { + // if the app is supposed to run on Github Pages in a subfolder, use the following config: + // publicPath: process.env.NODE_ENV === "production" ? "/townsquare/" : "/" publicPath: process.env.NODE_ENV === "production" ? "/" : "/" };