From 0d1ef29662ac01a41ee6fa999ee538f4529e7f97 Mon Sep 17 00:00:00 2001 From: Wayland-Smithy <64715958+Wayland-Smithy@users.noreply.github.com> Date: Wed, 7 Jul 2021 16:02:52 -0700 Subject: [PATCH] Drastic Lag Mitigation Subsystem: SSlag_switch (#59717) Requested by oranges and inspired by the upcoming event. A new subsyetem, non-processing (for now), aimed at providing some toggle switches that can be flipped as a last ditch effort to save some CPU cycles by sacrificing some non-critical mechanics. Below you can see each individual toggle. Screenshot of the admin panel: image Surely there are more opportunities for toggles I missed, but adding new ones is not very difficult at all. Why It's Good For The Game Better performance during extreme pop, I hope. Changelog cl code: Introduces the Lag Switch subsystem for when a smoother experience is worth trading a few bells and whistles for. Performance enhancement measures can be togged by admins with the Show Lag Switches admin verb or enabled automatically at a pop amount set via config. config: Added a new config var: number/auto_lag_switch_pop --- code/__DEFINES/dcs/signals.dm | 2 + code/__DEFINES/lag_switch.dm | 10 ++ code/__DEFINES/traits.dm | 2 + code/__HELPERS/icons.dm | 4 + code/_globalvars/lists/mobs.dm | 1 + code/_globalvars/traits.dm | 1 + .../configuration/entries/general.dm | 4 + code/controllers/subsystem/input.dm | 2 +- code/controllers/subsystem/lag_switch.dm | 133 ++++++++++++++++++ code/controllers/subsystem/ticker.dm | 1 - code/datums/chatmessage.dm | 2 + code/datums/world_topic.dm | 2 +- code/game/world.dm | 2 +- code/modules/admin/admin.dm | 46 ++++-- code/modules/admin/admin_verbs.dm | 1 + code/modules/admin/topic.dm | 52 +++++++ .../nukeop/equipment/nuclearbomb.dm | 2 +- code/modules/client/client_defines.dm | 2 + code/modules/client/client_procs.dm | 1 + .../modules/mob/dead/new_player/new_player.dm | 16 ++- code/modules/mob/dead/observer/observer.dm | 14 ++ code/modules/mob/living/death.dm | 3 + code/modules/mob/living/living_say.dm | 8 +- code/modules/mob/mob_lists.dm | 5 + code/modules/mob/mob_say.dm | 6 + config/admins.txt | 1 + config/config.txt | 3 + tgstation.dme | 2 + 28 files changed, 306 insertions(+), 22 deletions(-) create mode 100644 code/__DEFINES/lag_switch.dm create mode 100644 code/controllers/subsystem/lag_switch.dm diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm index 1fbd794856e..c71232b6d69 100644 --- a/code/__DEFINES/dcs/signals.dm +++ b/code/__DEFINES/dcs/signals.dm @@ -40,6 +40,8 @@ #define LINKED_UP (1<<0) /// an obj/item is created! (obj/item/created_item) #define COMSIG_GLOB_NEW_ITEM "!new_item" +/// a client (re)connected, after all /client/New() checks have passed : (client/connected_client) +#define COMSIG_GLOB_CLIENT_CONNECT "!client_connect" /// a weather event of some kind occured #define COMSIG_WEATHER_TELEGRAPH(event_type) "!weather_telegraph [event_type]" #define COMSIG_WEATHER_START(event_type) "!weather_start [event_type]" diff --git a/code/__DEFINES/lag_switch.dm b/code/__DEFINES/lag_switch.dm new file mode 100644 index 00000000000..fd1fc579a63 --- /dev/null +++ b/code/__DEFINES/lag_switch.dm @@ -0,0 +1,10 @@ +// All of the possible Lag Switch lag mitigation measures +// If you add more do not forget to update MEASURES_AMOUNT accordingly +#define DISABLE_DEAD_KEYLOOP 1 // Stops ghosts flying around freely, they can still jump and orbit, staff exempted +#define DISABLE_GHOST_ZOOM_TRAY 2 // Stops ghosts using zoom/t-ray verbs and resets their view if zoomed out, staff exempted +#define DISABLE_RUNECHAT 3 // Disable runechat and enable the bubbles, speaking mobs with TRAIT_BYPASS_MEASURES exempted +#define DISABLE_USR_ICON2HTML 4 // Disable icon2html procs from verbs like examine, mobs calling with TRAIT_BYPASS_MEASURES exempted +#define DISABLE_NON_OBSJOBS 5 // Prevents anyone from joining the game as anything but observer +#define SLOWMODE_SAY 6 // Limit IC/dchat spam to one message every x seconds per client, TRAIT_BYPASS_MEASURES exempted + +#define MEASURES_AMOUNT 6 // The total number of switches defined above diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm index 8971cfc322f..74d1ec23f83 100644 --- a/code/__DEFINES/traits.dm +++ b/code/__DEFINES/traits.dm @@ -323,6 +323,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define TRAIT_BLOODSHOT_EYES "bloodshot_eyes" /// This mob should never close UI even if it doesn't have a client #define TRAIT_PRESERVE_UI_WITHOUT_CLIENT "preserve_ui_without_client" +/// This mob overrides certian SSlag_switch measures with this special trait +#define TRAIT_BYPASS_MEASURES "bypass_lagswitch_measures" #define TRAIT_NOBLEED "nobleed" //This carbon doesn't bleed /// This atom can ignore the "is on a turf" check for simple AI datum attacks, allowing them to attack from bags or lockers as long as any other conditions are met diff --git a/code/__HELPERS/icons.dm b/code/__HELPERS/icons.dm index 0b78028f036..5d7f87ade1a 100644 --- a/code/__HELPERS/icons.dm +++ b/code/__HELPERS/icons.dm @@ -1124,6 +1124,8 @@ GLOBAL_LIST_INIT(freon_color_matrix, list("#2E5E69", "#60A2A8", "#A1AFB1", rgb(0 /proc/icon2html(thing, target, icon_state, dir = SOUTH, frame = 1, moving = FALSE, sourceonly = FALSE, extra_classes = null) if (!thing) return + if(SSlag_switch.measures[DISABLE_USR_ICON2HTML] && usr && !HAS_TRAIT(usr, TRAIT_BYPASS_MEASURES)) + return var/key var/icon/I = thing @@ -1225,6 +1227,8 @@ GLOBAL_LIST_INIT(freon_color_matrix, list("#2E5E69", "#60A2A8", "#A1AFB1", rgb(0 /proc/costly_icon2html(thing, target, sourceonly = FALSE) if (!thing) return + if(SSlag_switch.measures[DISABLE_USR_ICON2HTML] && usr && !HAS_TRAIT(usr, TRAIT_BYPASS_MEASURES)) + return if (isicon(thing)) return icon2html(thing, target) diff --git a/code/_globalvars/lists/mobs.dm b/code/_globalvars/lists/mobs.dm index 77fec9e6c88..ff6f2b79b25 100644 --- a/code/_globalvars/lists/mobs.dm +++ b/code/_globalvars/lists/mobs.dm @@ -17,6 +17,7 @@ GLOBAL_LIST_INIT(dangerous_turfs, typecacheof(list( //This is for procs to replace all the goddamn 'in world's that are chilling around the code GLOBAL_LIST_EMPTY(player_list) //all mobs **with clients attached**. +GLOBAL_LIST_EMPTY(keyloop_list) //as above but can be limited to boost performance GLOBAL_LIST_EMPTY(mob_list) //all mobs, including clientless GLOBAL_LIST_EMPTY(mob_directory) //mob_id -> mob GLOBAL_LIST_EMPTY(alive_mob_list) //all alive mobs, including clientless. Excludes /mob/dead/new_player diff --git a/code/_globalvars/traits.dm b/code/_globalvars/traits.dm index bd2fa7f736a..57fb14ae2d7 100644 --- a/code/_globalvars/traits.dm +++ b/code/_globalvars/traits.dm @@ -88,6 +88,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_CANNOT_OPEN_PRESENTS" = TRAIT_CANNOT_OPEN_PRESENTS, "TRAIT_PRESENT_VISION" = TRAIT_PRESENT_VISION, "TRAIT_DISK_VERIFIER" = TRAIT_DISK_VERIFIER, + "TRAIT_BYPASS_MEASURES" = TRAIT_BYPASS_MEASURES, "TRAIT_NOMOBSWAP" = TRAIT_NOMOBSWAP, "TRAIT_XRAY_VISION" = TRAIT_XRAY_VISION, "TRAIT_WEB_WEAVER" = TRAIT_WEB_WEAVER, diff --git a/code/controllers/configuration/entries/general.dm b/code/controllers/configuration/entries/general.dm index e0812de3637..c690e7e9efe 100644 --- a/code/controllers/configuration/entries/general.dm +++ b/code/controllers/configuration/entries/general.dm @@ -299,6 +299,10 @@ /datum/config_entry/flag/maprotation +/datum/config_entry/number/auto_lag_switch_pop //Number of clients at which drastic lag mitigation measures kick in + config_entry_value = null + min_val = 0 + /datum/config_entry/number/soft_popcap default = null min_val = 0 diff --git a/code/controllers/subsystem/input.dm b/code/controllers/subsystem/input.dm index 311eec6e684..dd389e0dfbc 100644 --- a/code/controllers/subsystem/input.dm +++ b/code/controllers/subsystem/input.dm @@ -35,5 +35,5 @@ SUBSYSTEM_DEF(input) user.set_macros() /datum/controller/subsystem/input/fire() - for(var/mob/user as anything in GLOB.player_list) + for(var/mob/user as anything in GLOB.keyloop_list) user.focus?.keyLoop(user.client) diff --git a/code/controllers/subsystem/lag_switch.dm b/code/controllers/subsystem/lag_switch.dm new file mode 100644 index 00000000000..f8bbd13e297 --- /dev/null +++ b/code/controllers/subsystem/lag_switch.dm @@ -0,0 +1,133 @@ +/// The subsystem for controlling drastic performance enhancements aimed at reducing server load for a smoother albeit slightly duller gaming experience +SUBSYSTEM_DEF(lag_switch) + name = "Lag Switch" + flags = SS_NO_FIRE + + /// If the lag switch measures should attempt to trigger automatically, TRUE if a config value exists + var/auto_switch = FALSE + /// Amount of connected clients at which the Lag Switch should engage, set via config or admin panel + var/trigger_pop = INFINITY - 1337 + /// List of bools corresponding to code/__DEFINES/lag_switch.dm + var/static/list/measures[MEASURES_AMOUNT] + /// List of measures that toggle automatically + var/list/auto_measures = list(DISABLE_GHOST_ZOOM_TRAY, DISABLE_RUNECHAT, DISABLE_USR_ICON2HTML) + /// Timer ID for the automatic veto period + var/veto_timer_id + /// Cooldown between say verb uses when slowmode is enabled + var/slowmode_cooldown = 3 SECONDS + +/datum/controller/subsystem/lag_switch/Initialize(start_timeofday) + for(var/i = 1, i <= measures.len, i++) + measures[i] = FALSE + var/auto_switch_pop = CONFIG_GET(number/auto_lag_switch_pop) + if(auto_switch_pop) + auto_switch = TRUE + trigger_pop = auto_switch_pop + RegisterSignal(SSdcs, COMSIG_GLOB_CLIENT_CONNECT, .proc/client_connected) + return ..() + +/datum/controller/subsystem/lag_switch/proc/client_connected(datum/source, client/connected) + SIGNAL_HANDLER + if(TGS_CLIENT_COUNT < trigger_pop) + return + + auto_switch = FALSE + UnregisterSignal(SSdcs, COMSIG_GLOB_CLIENT_CONNECT) + veto_timer_id = addtimer(CALLBACK(src, .proc/set_all_measures, TRUE, TRUE), 20 SECONDS, TIMER_STOPPABLE) + message_admins("Lag Switch population threshold reached. Automatic activation of lag mitigation measures occuring in 20 seconds. (CANCEL)") + log_admin("Lag Switch population threshold reached. Automatic activation of lag mitigation measures occuring in 20 seconds.") + +/// (En/Dis)able automatic triggering of switches based on client count +/datum/controller/subsystem/lag_switch/proc/toggle_auto_enable() + auto_switch = !auto_switch + if(auto_switch) + RegisterSignal(SSdcs, COMSIG_GLOB_CLIENT_CONNECT, .proc/client_connected) + else + UnregisterSignal(SSdcs, COMSIG_GLOB_CLIENT_CONNECT) + +/// Called from an admin chat link +/datum/controller/subsystem/lag_switch/proc/cancel_auto_enable_in_progress() + if(!veto_timer_id) + return FALSE + + deltimer(veto_timer_id) + veto_timer_id = null + return TRUE + +/// Update the slowmode timer length and clear existing ones if reduced +/datum/controller/subsystem/lag_switch/proc/change_slowmode_cooldown(length) + if(!length) + return FALSE + + var/length_secs = length SECONDS + if(length_secs <= 0) + length_secs = 1 // one tick because cooldowns do not like 0 + + if(length_secs < slowmode_cooldown) + for(var/client/C as anything in GLOB.clients) + COOLDOWN_RESET(C, say_slowmode) + + slowmode_cooldown = length_secs + if(measures[SLOWMODE_SAY]) + to_chat(world, span_boldannounce("Slowmode timer has been changed to [length] seconds by an admin.")) + return TRUE + +/// Handle the state change for individual measures +/datum/controller/subsystem/lag_switch/proc/set_measure(measure_key, state) + if(isnull(measure_key) || isnull(state)) + stack_trace("SSlag_switch.set_measure() was called with a null arg") + return FALSE + if(isnull(LAZYACCESS(measures, measure_key))) + stack_trace("SSlag_switch.set_measure() was called with a measure_key not in the list of measures") + return FALSE + if(measures[measure_key] == state) + return TRUE + + measures[measure_key] = state + + switch(measure_key) + if(DISABLE_DEAD_KEYLOOP) + if(state) + for(var/mob/user as anything in GLOB.player_list) + if(user.stat == DEAD && !user.client?.holder) + GLOB.keyloop_list -= user + deadchat_broadcast(span_big("To increase performance Observer freelook is now disabled. Please use Orbit, Teleport, and Jump to look around."), message_type = DEADCHAT_ANNOUNCEMENT) + else + GLOB.keyloop_list |= GLOB.player_list + deadchat_broadcast("Observer freelook has been re-enabled. Enjoy your wooshing.", message_type = DEADCHAT_ANNOUNCEMENT) + if(DISABLE_GHOST_ZOOM_TRAY) + if(state) // if enabling make sure current ghosts are updated + for(var/mob/dead/observer/ghost in GLOB.dead_mob_list) + if(!ghost.client) + continue + if(!ghost.client.holder && ghost.client.view_size.getView() != ghost.client.view_size.default) + ghost.client.view_size.resetToDefault() + if(SLOWMODE_SAY) + if(state) + to_chat(world, span_boldannounce("Slowmode for IC/dead chat has been enabled with [slowmode_cooldown/10] seconds between messages.")) + else + for(var/client/C as anything in GLOB.clients) + COOLDOWN_RESET(C, say_slowmode) + to_chat(world, span_boldannounce("Slowmode for IC/dead chat has been disabled by an admin.")) + if(DISABLE_NON_OBSJOBS) + world.update_status() + + return TRUE + +/// Helper to loop over all measures for mass changes +/datum/controller/subsystem/lag_switch/proc/set_all_measures(state, automatic = FALSE) + if(isnull(state)) + stack_trace("SSlag_switch.set_all_measures() was called with a null state arg") + return FALSE + + if(automatic) + message_admins("Lag Switch enabling automatic measures now.") + log_admin("Lag Switch enabling automatic measures now.") + veto_timer_id = null + for(var/i = 1, i <= auto_measures.len, i++) + set_measure(auto_measures[i], state) + return TRUE + + for(var/i = 1, i <= measures.len, i++) + set_measure(i, state) + return TRUE diff --git a/code/controllers/subsystem/ticker.dm b/code/controllers/subsystem/ticker.dm index 31dda2fc6cd..81add481df7 100755 --- a/code/controllers/subsystem/ticker.dm +++ b/code/controllers/subsystem/ticker.dm @@ -43,7 +43,6 @@ SUBSYSTEM_DEF(ticker) var/news_report - var/late_join_disabled var/roundend_check_paused = FALSE diff --git a/code/datums/chatmessage.dm b/code/datums/chatmessage.dm index 78152337492..eeccb32aaff 100644 --- a/code/datums/chatmessage.dm +++ b/code/datums/chatmessage.dm @@ -232,6 +232,8 @@ * * spans - Additional classes to be added to the message */ /mob/proc/create_chat_message(atom/movable/speaker, datum/language/message_language, raw_message, list/spans, runechat_flags = NONE) + if(SSlag_switch.measures[DISABLE_RUNECHAT] && !HAS_TRAIT(speaker, TRAIT_BYPASS_MEASURES)) + return // Ensure the list we are using, if present, is a copy so we don't modify the list provided to us spans = spans ? spans.Copy() : list() diff --git a/code/datums/world_topic.dm b/code/datums/world_topic.dm index 6663f223ce3..63dcd56a542 100644 --- a/code/datums/world_topic.dm +++ b/code/datums/world_topic.dm @@ -142,7 +142,7 @@ . = list() .["version"] = GLOB.game_version .["respawn"] = config ? !CONFIG_GET(flag/norespawn) : FALSE - .["enter"] = GLOB.enter_allowed + .["enter"] = !LAZYACCESS(SSlag_switch.measures, DISABLE_NON_OBSJOBS) .["ai"] = CONFIG_GET(flag/allow_ai) .["host"] = world.host ? world.host : null .["round_id"] = GLOB.round_id diff --git a/code/game/world.dm b/code/game/world.dm index de4a2a02af5..1bf6d27fc6c 100644 --- a/code/game/world.dm +++ b/code/game/world.dm @@ -276,7 +276,7 @@ GLOBAL_VAR(restart_counter) var/list/features = list() - if (!GLOB.enter_allowed) + if(LAZYACCESS(SSlag_switch.measures, DISABLE_NON_OBSJOBS)) features += "closed" var/s = "" diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm index c9bd7b74d7a..1138cba4f4e 100644 --- a/code/modules/admin/admin.dm +++ b/code/modules/admin/admin.dm @@ -587,15 +587,12 @@ set category = "Server" set desc="People can't enter" set name="Toggle Entering" - GLOB.enter_allowed = !( GLOB.enter_allowed ) - if (!( GLOB.enter_allowed )) - to_chat(world, "New players may no longer enter the game.", confidential = TRUE) - else - to_chat(world, "New players may now enter the game.", confidential = TRUE) - log_admin("[key_name(usr)] toggled new player game entering.") - message_admins(span_adminnotice("[key_name_admin(usr)] toggled new player game entering.")) - world.update_status() - SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Entering", "[GLOB.enter_allowed ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + if(!SSlag_switch.initialized) + return + SSlag_switch.set_measure(DISABLE_NON_OBSJOBS, !SSlag_switch.measures[DISABLE_NON_OBSJOBS]) + log_admin("[key_name(usr)] toggled new player game entering. Lag Switch at index ([DISABLE_NON_OBSJOBS])") + message_admins("[key_name_admin(usr)] toggled new player game entering [SSlag_switch.measures[DISABLE_NON_OBSJOBS] ? "OFF" : "ON"].") + SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Entering", "[!SSlag_switch.measures[DISABLE_NON_OBSJOBS] ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! /datum/admins/proc/toggleAI() set category = "Server" @@ -825,6 +822,9 @@ tgui_alert(usr, "You cannot manage jobs before the job subsystem is initialized!") return + if(SSlag_switch.measures[DISABLE_NON_OBSJOBS]) + dat += "