optimized ping and gamestate messages

This commit is contained in:
Steffen 2021-01-29 21:54:27 +01:00
parent ea0e6d2d72
commit 4d33e7dda9
3 changed files with 104 additions and 53 deletions

View File

@ -150,40 +150,63 @@ wss.on("connection", function connection(ws, req) {
.substr(1) .substr(1)
.split(",", 1) .split(",", 1)
.pop(); .pop();
// don't log ping messages switch (messageType) {
if (messageType !== '"ping"') { case '"ping"':
console.log(new Date(), wss.clients.size, ws.channel, ws.playerId, data); // ping messages will only be sent host -> all or all -> host
}
// handle "direct" messages differently
if (messageType === '"direct"') {
try {
const dataToPlayer = JSON.parse(data)[1];
channels[ws.channel].forEach(function each(client) { channels[ws.channel].forEach(function each(client) {
if ( if (
client !== ws && client !== ws &&
client.readyState === WebSocket.OPEN && 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(); metrics.messages_outgoing.inc();
} }
}); });
} catch (e) { break;
console.log("error parsing direct message JSON", e); case '"direct"':
} // handle "direct" messages differently
} else { console.log(
// all other messages new Date(),
channels[ws.channel].forEach(function each(client) { wss.clients.size,
if (client !== ws && client.readyState === WebSocket.OPEN) { ws.channel,
// inject latency between both clients if ping message ws.playerId,
if (messageType === '"ping"' && client.latency && ws.latency) { data
client.send(data.replace(/latency/, client.latency + ws.latency)); );
} else { try {
client.send(data); const dataToPlayer = JSON.parse(data)[1];
} channels[ws.channel].forEach(function each(client) {
metrics.messages_outgoing.inc(); 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;
} }
}); });
}); });

View File

@ -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. * Open event handler for socket.
* @private * @private
*/ */
_onOpen() { _onOpen() {
if (this._isSpectator) { if (this._isSpectator) {
this._send("req", "gs"); this._sendDirect(
"host",
"getGamestate",
this._store.state.session.playerId
);
} else { } else {
this.sendGamestate(); this.sendGamestate();
} }
@ -81,13 +101,12 @@ class LiveSession {
*/ */
_ping() { _ping() {
this._handlePing(); this._handlePing();
if (this._isSpectator) { this._send("ping", [
this._send("direct", { this._isSpectator
host: [this._store.state.session.playerId, "latency"] ? this._store.state.session.playerId
}); : Object.keys(this._players).length,
} else { "latency"
this._send("ping", [Object.keys(this._players).length, "latency"]); ]);
}
clearTimeout(this._pingTimer); clearTimeout(this._pingTimer);
this._pingTimer = setTimeout(this._ping.bind(this), this._pingInterval); this._pingTimer = setTimeout(this._ping.bind(this), this._pingInterval);
} }
@ -105,10 +124,8 @@ class LiveSession {
console.log("unsupported socket message", data); console.log("unsupported socket message", data);
} }
switch (command) { switch (command) {
case "req": case "getGamestate":
if (params === "gs") { this.sendGamestate(params);
this.sendGamestate();
}
break; break;
case "edition": case "edition":
this._updateEdition(params); this._updateEdition(params);
@ -206,7 +223,9 @@ class LiveSession {
this._store.commit("session/setReconnecting", false); this._store.commit("session/setReconnecting", false);
clearTimeout(this._reconnectTimer); clearTimeout(this._reconnectTimer);
if (this._socket) { 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.close(1000);
this._socket = null; this._socket = null;
} }
@ -215,9 +234,10 @@ class LiveSession {
/** /**
* Publish the current gamestate. * Publish the current gamestate.
* Optional param to reduce traffic. (send only player data) * Optional param to reduce traffic. (send only player data)
* @param playerId
* @param isLightweight * @param isLightweight
*/ */
sendGamestate(isLightweight = false) { sendGamestate(playerId = "", isLightweight = false) {
if (this._isSpectator) return; if (this._isSpectator) return;
this._gamestate = this._store.state.players.players.map(player => ({ this._gamestate = this._store.state.players.players.map(player => ({
name: player.name, name: player.name,
@ -229,12 +249,15 @@ class LiveSession {
: {}) : {})
})); }));
if (isLightweight) { if (isLightweight) {
this._send("gs", { gamestate: this._gamestate, isLightweight }); this._sendDirect(playerId, "gs", {
gamestate: this._gamestate,
isLightweight
});
} else { } else {
const { session, grimoire } = this._store.state; const { session, grimoire } = this._store.state;
const { fabled } = this._store.state.players; const { fabled } = this._store.state.players;
this.sendEdition(); this.sendEdition(playerId);
this._send("gs", { this._sendDirect(playerId, "gs", {
gamestate: this._gamestate, gamestate: this._gamestate,
isNight: grimoire.isNight, isNight: grimoire.isNight,
nomination: session.nomination, nomination: session.nomination,
@ -324,15 +347,16 @@ class LiveSession {
/** /**
* Publish an edition update. ST only * Publish an edition update. ST only
* @param playerId
*/ */
sendEdition() { sendEdition(playerId = "") {
if (this._isSpectator) return; if (this._isSpectator) return;
const { edition } = this._store.state; const { edition } = this._store.state;
let roles; let roles;
if (!edition.isOfficial) { if (!edition.isOfficial) {
roles = Array.from(this._store.state.roles.keys()); roles = Array.from(this._store.state.roles.keys());
} }
this._send("edition", { this._sendDirect(playerId, "edition", {
edition: edition.isOfficial edition: edition.isOfficial
? { id: edition.id } ? { id: edition.id }
: Object.assign({}, edition, { logo: "" }), : Object.assign({}, edition, { logo: "" }),
@ -468,7 +492,7 @@ class LiveSession {
* @param latency * @param latency
* @private * @private
*/ */
_handlePing([playerIdOrCount, latency] = []) { _handlePing([playerIdOrCount = 0, latency] = []) {
const now = new Date().getTime(); const now = new Date().getTime();
if (!this._isSpectator) { if (!this._isSpectator) {
// remove players that haven't sent a ping in twice the timespan // remove players that haven't sent a ping in twice the timespan
@ -506,20 +530,22 @@ class LiveSession {
// ping to ST // ping to ST
this._store.commit("session/setPing", parseInt(latency, 10)); this._store.commit("session/setPing", parseInt(latency, 10));
} }
this._store.commit( // update player count
"session/setPlayerCount", if (!this._isSpectator || playerIdOrCount) {
this._isSpectator this._store.commit(
? playerIdOrCount || 0 "session/setPlayerCount",
: Object.keys(this._players).length this._isSpectator ? playerIdOrCount : Object.keys(this._players).length
); );
}
} }
/** /**
* Handle a player leaving the sessions * Handle a player leaving the sessions. ST only
* @param playerId * @param playerId
* @private * @private
*/ */
_handleBye(playerId) { _handleBye(playerId) {
if (this._isSpectator) return;
delete this._players[playerId]; delete this._players[playerId];
this._store.commit( this._store.commit(
"session/setPlayerCount", "session/setPlayerCount",
@ -786,7 +812,7 @@ export default store => {
case "players/clear": case "players/clear":
case "players/remove": case "players/remove":
case "players/add": case "players/add":
session.sendGamestate(true); session.sendGamestate("", true);
break; break;
case "players/update": case "players/update":
session.sendPlayer(payload); session.sendPlayer(payload);

View File

@ -1,3 +1,5 @@
module.exports = { 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" ? "/" : "/" publicPath: process.env.NODE_ENV === "production" ? "/" : "/"
}; };