diff --git a/assets/js/app.js b/assets/js/app.js index 0bd5d05..a3b3ab6 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -121,7 +121,7 @@ export class App { // Handle the last known state (if any) as an incoming update // This triggers the main update pipeline and enables centralized update handling - serverRegistration.updateServerStatus(latestPing, true, this.publicConfig.minecraftVersions) + serverRegistration.updateServerStatus(latestPing, latestPing.timestamp, true, this.publicConfig.minecraftVersions) // Allow the ServerRegistration to bind any DOM events with app instance context serverRegistration.initEventListeners() diff --git a/assets/js/servers.js b/assets/js/servers.js index 8c530e0..f0717da 100644 --- a/assets/js/servers.js +++ b/assets/js/servers.js @@ -113,14 +113,14 @@ export class ServerRegistration { this._plotInstance = $.plot('#chart_' + this.serverId, [this._graphData], SERVER_GRAPH_OPTIONS) } - handlePing (payload, pushToGraph) { + handlePing (payload, timestamp, pushToGraph) { if (payload.result) { this.playerCount = payload.result.players.online if (pushToGraph) { // Only update graph for successful pings // This intentionally pauses the server graph when pings begin to fail - this._graphData.push([payload.timestamp, this.playerCount]) + this._graphData.push([timestamp, this.playerCount]) // Trim graphData to within the max length by shifting out the leading elements if (this._graphData.length > SERVER_GRAPH_DATA_MAX_LENGTH) { @@ -169,10 +169,10 @@ export class ServerRegistration { this.lastPeakData = data } - updateServerStatus (ping, isInitialUpdate, minecraftVersions) { + updateServerStatus (ping, timestamp, isInitialUpdate, minecraftVersions) { // Only pushToGraph when initialUpdate === false // Otherwise the ping value is pushed into the graphData when already present - this.handlePing(ping, !isInitialUpdate) + this.handlePing(ping, timestamp, !isInitialUpdate) if (ping.versions) { const versionsElement = document.getElementById('version_' + this.serverId) diff --git a/assets/js/socket.js b/assets/js/socket.js index 256e9a0..9a30864 100644 --- a/assets/js/socket.js +++ b/assets/js/socket.js @@ -79,27 +79,30 @@ export class SocketManager { break - case 'updateServer': { - // The backend may send "update" events prior to receiving all "add" events - // A server has only been added once it's ServerRegistration is defined - // Checking undefined protects from this race condition - const serverRegistration = this._app.serverRegistry.getServerRegistration(payload.serverId) + case 'updateServers': { + for (let serverId = 0; serverId < payload.updates.length; serverId++) { + // The backend may send "update" events prior to receiving all "add" events + // A server has only been added once it's ServerRegistration is defined + // Checking undefined protects from this race condition + const serverRegistration = this._app.serverRegistry.getServerRegistration(serverId) + const serverUpdate = payload.updates[serverId] - if (serverRegistration) { - serverRegistration.updateServerStatus(payload, false, this._app.publicConfig.minecraftVersions) - } + if (serverRegistration) { + serverRegistration.updateServerStatus(serverUpdate, payload.timestamp, false, this._app.publicConfig.minecraftVersions) + } - // Use update payloads to conditionally append data to graph - // Skip any incoming updates if the graph is disabled - if (payload.updateHistoryGraph && this._app.graphDisplayManager.isVisible) { - // Update may not be successful, safely append 0 points - const playerCount = payload.result ? payload.result.players.online : 0 + // Use update payloads to conditionally append data to graph + // Skip any incoming updates if the graph is disabled + if (serverUpdate.updateHistoryGraph && this._app.graphDisplayManager.isVisible) { + // Update may not be successful, safely append 0 points + const playerCount = serverUpdate.result ? serverUpdate.result.players.online : 0 - this._app.graphDisplayManager.addGraphPoint(serverRegistration.serverId, payload.timestamp, playerCount) + this._app.graphDisplayManager.addGraphPoint(serverRegistration.serverId, payload.timestamp, playerCount) - // Only redraw the graph if not mutating hidden data - if (serverRegistration.isVisible) { - this._app.graphDisplayManager.requestRedraw() + // Only redraw the graph if not mutating hidden data + if (serverRegistration.isVisible) { + this._app.graphDisplayManager.requestRedraw() + } } } break diff --git a/lib/ping.js b/lib/ping.js index 2f8216b..31b0548 100644 --- a/lib/ping.js +++ b/lib/ping.js @@ -97,6 +97,40 @@ class PingController { } pingAll = () => { + const timestamp = new Date().getTime() + + this.startPingTasks(results => { + const updates = [] + + for (const serverRegistration of this._app.serverRegistrations) { + const result = results[serverRegistration.serverId] + + // Log to database if enabled + if (config.logToDatabase) { + const playerCount = result.resp ? result.resp.players.online : 0 + this._app.database.insertPing(serverRegistration.data.ip, timestamp, playerCount) + } + + // Generate a combined update payload + // This includes any modified fields and flags used by the frontend + // This will not be cached and can contain live metadata + const update = serverRegistration.handlePing(timestamp, result.resp, result.err, result.version) + updates[serverRegistration.serverId] = update + } + + // Send object since updates uses serverIds as keys + // Send a single timestamp entry since it is shared + this._app.server.broadcast(MessageOf('updateServers', { + timestamp, + updates + })) + }) + } + + startPingTasks = (callback) => { + const results = [] + let remainingTasks = this._app.serverRegistrations.length + for (const serverRegistration of this._app.serverRegistrations) { const version = serverRegistration.getNextProtocolVersion() @@ -105,36 +139,18 @@ class PingController { logger.log('error', 'Failed to ping %s: %s', serverRegistration.data.ip, err.message) } - this.handlePing(serverRegistration, resp, err, version) + results[serverRegistration.serverId] = { + resp, + err, + version + } + + if (--remainingTasks === 0) { + callback(results) + } }, version.protocolId) } } - - handlePing (serverRegistration, resp, err, version) { - const timestamp = new Date().getTime() - - serverRegistration.addPing(timestamp, resp) - - let updateHistoryGraph = false - - if (config.logToDatabase) { - const playerCount = resp ? resp.players.online : 0 - - // Log to database - this._app.database.insertPing(serverRegistration.data.ip, timestamp, playerCount) - - if (serverRegistration.addGraphPoint(resp !== undefined, playerCount, timestamp)) { - updateHistoryGraph = true - } - } - - // Generate a combined update payload - // This includes any modified fields and flags used by the frontend - // This will not be cached and can contain live metadata - const updateMessage = serverRegistration.getUpdate(timestamp, resp, err, version, updateHistoryGraph) - - this._app.server.broadcast(MessageOf('updateServer', updateMessage)) - } } module.exports = PingController diff --git a/lib/servers.js b/lib/servers.js index 7668866..5d18729 100644 --- a/lib/servers.js +++ b/lib/servers.js @@ -14,12 +14,30 @@ class ServerRegistration { this._pingHistory = [] } - getUpdate (timestamp, resp, err, version, updateHistoryGraph) { - const update = { - serverId: this.serverId, - timestamp: timestamp + handlePing (timestamp, resp, err, version) { + // Store into in-memory ping data + this.addPing(timestamp, resp) + + // Only notify the frontend to append to the historical graph + // if both the graphing behavior is enabled and the backend agrees + // that the ping is eligible for addition + let updateHistoryGraph = false + + if (config.logToDatabase) { + const playerCount = resp ? resp.players.online : 0 + + if (this.addGraphPoint(resp !== undefined, playerCount, timestamp)) { + updateHistoryGraph = true + } } + // Delegate out update payload generation + return this.getUpdate(timestamp, resp, err, version, updateHistoryGraph) + } + + getUpdate (timestamp, resp, err, version, updateHistoryGraph) { + const update = {} + if (resp) { if (resp.version && this.updateProtocolVersionCompat(resp.version, version.protocolId, version.protocolIndex)) { // Append an updated version listing