From fd0f398d6f9d06fa1fd04d3fffcf82bd4e9a6849 Mon Sep 17 00:00:00 2001 From: Aleksej Komarov Date: Tue, 19 Apr 2022 17:35:43 +0300 Subject: [PATCH] tgui-panel: Soft ping (#66299) * tgui-panel: Soft ping --- .github/CODEOWNERS | 1 + code/__DEFINES/subsystems.dm | 1 + code/controllers/subsystem/ping.dm | 39 +++++++++++++++++++++ code/modules/client/client_procs.dm | 1 + code/modules/tgui/tgui.dm | 2 +- code/modules/tgui/tgui_window.dm | 2 +- tgstation.dme | 1 + tgui/packages/tgui-panel/game/constants.js | 2 +- tgui/packages/tgui-panel/game/middleware.js | 13 ++++--- tgui/packages/tgui-panel/ping/actions.js | 30 ++++++++-------- tgui/packages/tgui-panel/ping/constants.js | 1 - tgui/packages/tgui-panel/ping/middleware.js | 25 ++++++++----- tgui/packages/tgui-panel/ping/reducer.js | 3 ++ tgui/packages/tgui/backend.ts | 2 +- 14 files changed, 90 insertions(+), 33 deletions(-) create mode 100644 code/controllers/subsystem/ping.dm diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 4f16b6cd7f0..153defdd5da 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -80,6 +80,7 @@ /code/__DEFINES/chat.dm @stylemistake /code/__DEFINES/tgui.dm @stylemistake /code/controllers/subsystem/chat.dm @stylemistake +/code/controllers/subsystem/ping.dm @stylemistake /code/controllers/subsystem/tgui.dm @stylemistake /code/modules/tgchat @stylemistake /code/modules/tgui @stylemistake diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm index b5a78abcce5..13f0d4aec30 100644 --- a/code/__DEFINES/subsystems.dm +++ b/code/__DEFINES/subsystems.dm @@ -165,6 +165,7 @@ // Subsystem fire priority, from lowest to highest priority // If the subsystem isn't listed here it's either DEFAULT or PROCESS (if it's a processing subsystem child) +#define FIRE_PRIORITY_PING 10 #define FIRE_PRIORITY_IDLE_NPC 10 #define FIRE_PRIORITY_SERVER_MAINT 10 #define FIRE_PRIORITY_RESEARCH 10 diff --git a/code/controllers/subsystem/ping.dm b/code/controllers/subsystem/ping.dm new file mode 100644 index 00000000000..78669c987ea --- /dev/null +++ b/code/controllers/subsystem/ping.dm @@ -0,0 +1,39 @@ +/*! + * Copyright (c) 2022 Aleksej Komarov + * SPDX-License-Identifier: MIT + */ + +SUBSYSTEM_DEF(ping) + name = "Ping" + priority = FIRE_PRIORITY_PING + wait = 4 SECONDS + flags = SS_NO_INIT + runlevels = RUNLEVEL_LOBBY | RUNLEVEL_SETUP | RUNLEVEL_GAME | RUNLEVEL_POSTGAME + + var/list/currentrun = list() + +/datum/controller/subsystem/ping/stat_entry() + ..("P:[GLOB.clients.len]") + +/datum/controller/subsystem/ping/fire(resumed = FALSE) + // Prepare the new batch of clients + if (!resumed) + src.currentrun = GLOB.clients.Copy() + + // De-reference the list for sanic speeds + var/list/currentrun = src.currentrun + + while (currentrun.len) + var/client/client = currentrun[currentrun.len] + currentrun.len-- + + if (client?.tgui_panel?.is_ready()) + // Send a soft ping + client.tgui_panel.window.send_message("ping/soft", list( + // Slightly less than the subsystem timer (somewhat arbitrary) + // to prevent incoming pings from resetting the afk state + "afk" = client.is_afk(3.5 SECONDS), + )) + + if (MC_TICK_CHECK) + return diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm index 7ba1a0f7a60..1d02f71b03d 100644 --- a/code/modules/client/client_procs.dm +++ b/code/modules/client/client_procs.dm @@ -545,6 +545,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( active_mousedown_item = null SSambience.remove_ambience_client(src) SSmouse_entered.hovers -= src + SSping.currentrun -= src QDEL_NULL(view_size) QDEL_NULL(void) QDEL_NULL(tooltips) diff --git a/code/modules/tgui/tgui.dm b/code/modules/tgui/tgui.dm index b73842544b4..1101c80daa1 100644 --- a/code/modules/tgui/tgui.dm +++ b/code/modules/tgui/tgui.dm @@ -317,7 +317,7 @@ if(initialized) send_full_update() initialized = TRUE - if("pingReply") + if("ping/reply") initialized = TRUE if("suspend") close(can_be_suspended = TRUE) diff --git a/code/modules/tgui/tgui_window.dm b/code/modules/tgui/tgui_window.dm index 18e87468325..40aef1b72ea 100644 --- a/code/modules/tgui/tgui_window.dm +++ b/code/modules/tgui/tgui_window.dm @@ -325,7 +325,7 @@ // If not locked, handle these message types switch(type) if("ping") - send_message("pingReply", payload) + send_message("ping/reply", payload) if("suspend") close(can_be_suspended = TRUE) if("close") diff --git a/tgstation.dme b/tgstation.dme index 1b02a5967c2..641792afea3 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -482,6 +482,7 @@ #include "code\controllers\subsystem\pathfinder.dm" #include "code\controllers\subsystem\persistence.dm" #include "code\controllers\subsystem\persistent_paintings.dm" +#include "code\controllers\subsystem\ping.dm" #include "code\controllers\subsystem\points_of_interest.dm" #include "code\controllers\subsystem\profiler.dm" #include "code\controllers\subsystem\radiation.dm" diff --git a/tgui/packages/tgui-panel/game/constants.js b/tgui/packages/tgui-panel/game/constants.js index 9df3a58d294..f40a529a101 100644 --- a/tgui/packages/tgui-panel/game/constants.js +++ b/tgui/packages/tgui-panel/game/constants.js @@ -4,4 +4,4 @@ * @license MIT */ -export const CONNECTION_LOST_AFTER = 15000; +export const CONNECTION_LOST_AFTER = 20000; diff --git a/tgui/packages/tgui-panel/game/middleware.js b/tgui/packages/tgui-panel/game/middleware.js index 854369dc54e..ba1ec63f9ec 100644 --- a/tgui/packages/tgui-panel/game/middleware.js +++ b/tgui/packages/tgui-panel/game/middleware.js @@ -4,7 +4,7 @@ * @license MIT */ -import { pingSuccess } from '../ping/actions'; +import { pingSoft, pingSuccess } from '../ping/actions'; import { connectionLost, connectionRestored, roundRestarted } from './actions'; import { selectGame } from './selectors'; import { CONNECTION_LOST_AFTER } from './constants'; @@ -19,6 +19,7 @@ const withTimestamp = action => ({ export const gameMiddleware = store => { let lastPingedAt; + setInterval(() => { const state = store.getState(); if (!state) { @@ -34,15 +35,19 @@ export const gameMiddleware = store => { store.dispatch(withTimestamp(connectionRestored())); } }, 1000); + return next => action => { - const { type, payload, meta } = action; - if (type === pingSuccess.type) { - lastPingedAt = meta.now; + const { type } = action; + + if (type === pingSuccess.type || type === pingSoft.type) { + lastPingedAt = Date.now(); return next(action); } + if (type === roundRestarted.type) { return next(withTimestamp(action)); } + return next(action); }; }; diff --git a/tgui/packages/tgui-panel/ping/actions.js b/tgui/packages/tgui-panel/ping/actions.js index ba3582f131c..27f103302c4 100644 --- a/tgui/packages/tgui-panel/ping/actions.js +++ b/tgui/packages/tgui-panel/ping/actions.js @@ -6,20 +6,20 @@ import { createAction } from 'common/redux'; -export const pingSuccess = createAction( - 'ping/success', - ping => { - const now = Date.now(); - const roundtrip = (now - ping.sentAt) * 0.5; - return { - payload: { - lastId: ping.id, - roundtrip, - }, - meta: { now }, - }; - } -); +export const pingReply = createAction('ping/reply'); + +/** + * Soft ping from the server. + * It's intended to send periodic server-side metadata about the client, + * e.g. its AFK status. + */ +export const pingSoft = createAction('ping/soft'); + +export const pingSuccess = createAction('ping/success', (ping) => ({ + payload: { + lastId: ping.id, + roundtrip: (Date.now() - ping.sentAt) * 0.5, + }, +})); export const pingFail = createAction('ping/fail'); -export const pingReply = createAction('ping/reply'); diff --git a/tgui/packages/tgui-panel/ping/constants.js b/tgui/packages/tgui-panel/ping/constants.js index 5d9a0472c5f..54fd42fcd7f 100644 --- a/tgui/packages/tgui-panel/ping/constants.js +++ b/tgui/packages/tgui-panel/ping/constants.js @@ -4,7 +4,6 @@ * @license MIT */ -export const PING_INTERVAL = 2500; export const PING_TIMEOUT = 2000; export const PING_MAX_FAILS = 3; export const PING_QUEUE_SIZE = 8; diff --git a/tgui/packages/tgui-panel/ping/middleware.js b/tgui/packages/tgui-panel/ping/middleware.js index 2d607c84174..fb86de9cc48 100644 --- a/tgui/packages/tgui-panel/ping/middleware.js +++ b/tgui/packages/tgui-panel/ping/middleware.js @@ -4,14 +4,14 @@ * @license MIT */ -import { pingFail, pingSuccess } from './actions'; -import { PING_INTERVAL, PING_QUEUE_SIZE, PING_TIMEOUT } from './constants'; +import { pingFail, pingReply, pingSoft, pingSuccess } from './actions'; +import { PING_QUEUE_SIZE, PING_TIMEOUT } from './constants'; export const pingMiddleware = store => { let initialized = false; let index = 0; - let interval; const pings = []; + const sendPing = () => { for (let i = 0; i < PING_QUEUE_SIZE; i++) { const ping = pings[i]; @@ -25,20 +25,26 @@ export const pingMiddleware = store => { Byond.sendMessage('ping', { index }); index = (index + 1) % PING_QUEUE_SIZE; }; + return next => action => { const { type, payload } = action; + if (!initialized) { initialized = true; - interval = setInterval(sendPing, PING_INTERVAL); sendPing(); } - if (type === 'roundrestart') { - // Stop pinging because dreamseeker is currently reconnecting. - // Topic calls in the middle of reconnect will crash the connection. - clearInterval(interval); + + if (type === pingSoft.type) { + const { afk } = payload; + // On each soft ping where client is not flagged as afk, + // initiate a new ping. + if (!afk) { + sendPing(); + } return next(action); } - if (type === 'pingReply') { + + if (type === pingReply.type) { const { index } = payload; const ping = pings[index]; // Received a timed out ping @@ -48,6 +54,7 @@ export const pingMiddleware = store => { pings[index] = null; return next(pingSuccess(ping)); } + return next(action); }; }; diff --git a/tgui/packages/tgui-panel/ping/reducer.js b/tgui/packages/tgui-panel/ping/reducer.js index 22d146f8b81..874cb75a9b8 100644 --- a/tgui/packages/tgui-panel/ping/reducer.js +++ b/tgui/packages/tgui-panel/ping/reducer.js @@ -10,6 +10,7 @@ import { PING_MAX_FAILS, PING_ROUNDTRIP_BEST, PING_ROUNDTRIP_WORST } from './con export const pingReducer = (state = {}, action) => { const { type, payload } = action; + if (type === pingSuccess.type) { const { roundtrip } = payload; const prevRoundtrip = state.roundtripAvg || roundtrip; @@ -23,6 +24,7 @@ export const pingReducer = (state = {}, action) => { networkQuality, }; } + if (type === pingFail.type) { const { failCount = 0 } = state; const networkQuality = clamp01(state.networkQuality @@ -38,5 +40,6 @@ export const pingReducer = (state = {}, action) => { } return nextState; } + return state; }; diff --git a/tgui/packages/tgui/backend.ts b/tgui/packages/tgui/backend.ts index 7ca6eeecde1..eb58470ae0c 100644 --- a/tgui/packages/tgui/backend.ts +++ b/tgui/packages/tgui/backend.ts @@ -134,7 +134,7 @@ export const backendMiddleware = store => { } if (type === 'ping') { - Byond.sendMessage('pingReply'); + Byond.sendMessage('ping/reply'); return; }