From 6207f14e80c70c299a79a067dbd96ce68f15fb58 Mon Sep 17 00:00:00 2001 From: Kylerace Date: Fri, 24 Dec 2021 21:37:37 -0800 Subject: [PATCH] creates SSspeech_controller so the MC can control say() execution (#63118) makes say_verb() not execute immediately and instead put itself in the queue of the subsystem meant to process it: SSspeech_controller so that it executes at the start of the next tick during the MC's run instead of outside of it. i considered making this part of #61422 (d005d76f0bd201060b6ee515678a4b6950d9f0eb) but this is good regardless of how that one pans out and i already overstuffed that one. tldr the REAL cause of get_hearers_in_view() overtime is an effect of how byond schedules verbs within the tick. when the server is overloaded there isnt much time between when SendMaps finishes and when the next tick was supposed to start, but verbs still need to be executed. So verbs will eat into the time the next tick should have started and the MC cant compensate for it at all since it uses byonds internal tick scheduler instead of our external tick scheduler. --- code/__DEFINES/speech_controller.dm | 12 +++++ code/__DEFINES/subsystems.dm | 2 + code/controllers/master.dm | 2 +- code/controllers/subsystem.dm | 14 ++--- .../subsystem/speech_controller.dm | 54 +++++++++++++++++++ code/modules/client/verbs/ooc.dm | 1 + code/modules/mob/living/living_defines.dm | 3 +- code/modules/mob/mob_say.dm | 21 +++++--- tgstation.dme | 2 + 9 files changed, 95 insertions(+), 16 deletions(-) create mode 100644 code/__DEFINES/speech_controller.dm create mode 100644 code/controllers/subsystem/speech_controller.dm diff --git a/code/__DEFINES/speech_controller.dm b/code/__DEFINES/speech_controller.dm new file mode 100644 index 00000000000..e758cca60f6 --- /dev/null +++ b/code/__DEFINES/speech_controller.dm @@ -0,0 +1,12 @@ + +#define SPEECH_CONTROLLER_QUEUE_SAY_VERB "say_verb" + +#define SPEECH_CONTROLLER_QUEUE_WHISPER_VERB "whisper_verb" + +#define SPEECH_CONTROLLER_QUEUE_EMOTE_VERB "emote_verb" + +#define MOB_INDEX 1 + +#define MESSAGE_INDEX 2 + +#define CATEGORY_INDEX 3 diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm index 8afb146fdca..8848ce0f450 100644 --- a/code/__DEFINES/subsystems.dm +++ b/code/__DEFINES/subsystems.dm @@ -113,6 +113,7 @@ #define INIT_ORDER_DBCORE 95 #define INIT_ORDER_BLACKBOX 94 #define INIT_ORDER_SERVER_MAINT 93 +#define INIT_ORDER_SPEECH_CONTROLLER 92 #define INIT_ORDER_INPUT 85 #define INIT_ORDER_SOUNDS 83 #define INIT_ORDER_INSTRUMENTS 82 @@ -200,6 +201,7 @@ #define FIRE_PRIORITY_EXPLOSIONS 666 #define FIRE_PRIORITY_TIMER 700 #define FIRE_PRIORITY_SOUND_LOOPS 800 +#define FIRE_PRIORITY_SPEECH_CONTROLLER 900 #define FIRE_PRIORITY_INPUT 1000 // This must always always be the max highest priority. Player input must never be lost. diff --git a/code/controllers/master.dm b/code/controllers/master.dm index 8e0d916247e..ed83dc8d3d2 100644 --- a/code/controllers/master.dm +++ b/code/controllers/master.dm @@ -463,7 +463,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new . = 1 -// Run thru the queue of subsystems to run, running them while balancing out their allocated tick precentage +/// Run thru the queue of subsystems to run, running them while balancing out their allocated tick precentage /datum/controller/master/proc/RunQueue() . = 0 var/datum/controller/subsystem/queue_node diff --git a/code/controllers/subsystem.dm b/code/controllers/subsystem.dm index 7b47daa2a9a..95c562d3c0c 100644 --- a/code/controllers/subsystem.dm +++ b/code/controllers/subsystem.dm @@ -99,7 +99,7 @@ /datum/controller/subsystem/proc/PreInit() return -//This is used so the mc knows when the subsystem sleeps. do not override. +///This is used so the mc knows when the subsystem sleeps. do not override. /datum/controller/subsystem/proc/ignite(resumed = FALSE) SHOULD_NOT_OVERRIDE(TRUE) set waitfor = FALSE @@ -114,9 +114,9 @@ state = SS_PAUSED queued_time = QT -//previously, this would have been named 'process()' but that name is used everywhere for different things! -//fire() seems more suitable. This is the procedure that gets called every 'wait' deciseconds. -//Sleeping in here prevents future fires until returned. +///previously, this would have been named 'process()' but that name is used everywhere for different things! +///fire() seems more suitable. This is the procedure that gets called every 'wait' deciseconds. +///Sleeping in here prevents future fires until returned. /datum/controller/subsystem/proc/fire(resumed = FALSE) flags |= SS_NO_FIRE CRASH("Subsystem [src]([type]) does not fire() but did not set the SS_NO_FIRE flag. Please add the SS_NO_FIRE flag to any subsystem that doesn't fire so it doesn't get added to the processing list and waste cpu.") @@ -154,9 +154,9 @@ next_fire = queued_time + wait + (world.tick_lag * (tick_overrun/100)) -//Queue it to run. -// (we loop thru a linked list until we get to the end or find the right point) -// (this lets us sort our run order correctly without having to re-sort the entire already sorted list) +///Queue it to run. +/// (we loop thru a linked list until we get to the end or find the right point) +/// (this lets us sort our run order correctly without having to re-sort the entire already sorted list) /datum/controller/subsystem/proc/enqueue() var/SS_priority = priority var/SS_flags = flags diff --git a/code/controllers/subsystem/speech_controller.dm b/code/controllers/subsystem/speech_controller.dm new file mode 100644 index 00000000000..96476127f41 --- /dev/null +++ b/code/controllers/subsystem/speech_controller.dm @@ -0,0 +1,54 @@ +SUBSYSTEM_DEF(speech_controller) + name = "Speech Controller" + wait = 1 + flags = SS_TICKER + priority = FIRE_PRIORITY_SPEECH_CONTROLLER//has to be high priority, second in priority ONLY to SSinput + init_order = INIT_ORDER_SPEECH_CONTROLLER + runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY + + ///used so that an admin can force all speech verbs to execute immediately instead of queueing + var/FOR_ADMINS_IF_BROKE_immediately_execute_all_speech = FALSE + + ///list of the form: list(client mob, message that mob is queued to say, other say arguments (if any)). + ///this is our process queue, processed every tick. + var/list/queued_says_to_execute = list() + +///queues mob_to_queue into our process list so they say(message) near the start of the next tick +/datum/controller/subsystem/speech_controller/proc/queue_say_for_mob(mob/mob_to_queue, message, message_type) + + if(!TICK_CHECK || FOR_ADMINS_IF_BROKE_immediately_execute_all_speech) + process_single_say(mob_to_queue, message, message_type) + return TRUE + + queued_says_to_execute += list(list(mob_to_queue, message, message_type)) + + return TRUE + +/datum/controller/subsystem/speech_controller/fire(resumed) + + /// cache for sanic speed (lists are references anyways) + var/list/says_to_process = queued_says_to_execute.Copy() + queued_says_to_execute.Cut()//we should be going through the entire list every single iteration + + for(var/list/say_to_process as anything in says_to_process) + + var/mob/mob_to_speak = say_to_process[MOB_INDEX]//index 1 is the mob, 2 is the message, 3 is the message category + var/message = say_to_process[MESSAGE_INDEX] + var/message_category = say_to_process[CATEGORY_INDEX] + + process_single_say(mob_to_speak, message, message_category) + +///used in fire() to process a single mobs message through the relevant proc. +///only exists so that sleeps in the message pipeline dont cause the whole queue to wait +/datum/controller/subsystem/speech_controller/proc/process_single_say(mob/mob_to_speak, message, message_category) + set waitfor = FALSE + + switch(message_category) + if(SPEECH_CONTROLLER_QUEUE_SAY_VERB) + mob_to_speak.say(message) + + if(SPEECH_CONTROLLER_QUEUE_WHISPER_VERB) + mob_to_speak.whisper(message) + + if(SPEECH_CONTROLLER_QUEUE_EMOTE_VERB) + mob_to_speak.emote("me",1,message,TRUE) diff --git a/code/modules/client/verbs/ooc.dm b/code/modules/client/verbs/ooc.dm index c49881756d2..4837dc037cd 100644 --- a/code/modules/client/verbs/ooc.dm +++ b/code/modules/client/verbs/ooc.dm @@ -1,6 +1,7 @@ GLOBAL_VAR_INIT(OOC_COLOR, null)//If this is null, use the CSS for OOC. Otherwise, use a custom colour. GLOBAL_VAR_INIT(normal_ooc_colour, "#002eb8") +///talking in OOC uses this /client/verb/ooc(msg as text) set name = "OOC" //Gave this shit a shorter name so you only have to time out "ooc" rather than "ooc message" to use it --NeoFite set category = "OOC" diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm index 0f8d45789b0..5f82e92cb27 100644 --- a/code/modules/mob/living/living_defines.dm +++ b/code/modules/mob/living/living_defines.dm @@ -73,7 +73,8 @@ var/cameraFollow = null - var/tod = null /// Time of death + /// Time of death + var/tod = null var/on_fire = FALSE ///The "Are we on fire?" var var/fire_stacks = 0 ///Tracks how many stacks of fire we have on, max is usually 20 diff --git a/code/modules/mob/mob_say.dm b/code/modules/mob/mob_say.dm index 8ce7a821ade..1b6f51f1c02 100644 --- a/code/modules/mob/mob_say.dm +++ b/code/modules/mob/mob_say.dm @@ -1,27 +1,36 @@ //Speech verbs. -///Say verb +///what clients use to speak. when you type a message into the chat bar in say mode, this is the first thing that goes off serverside. /mob/verb/say_verb(message as text) set name = "Say" set category = "IC" + set instant = TRUE + if(GLOB.say_disabled) //This is here to try to identify lag problems to_chat(usr, span_danger("Speech is currently admin-disabled.")) return + + //queue this message because verbs are scheduled to process after SendMaps in the tick and speech is pretty expensive when it happens. + //by queuing this for next tick the mc can compensate for its cost instead of having speech delay the start of the next tick if(message) - say(message) + SSspeech_controller.queue_say_for_mob(src, message, SPEECH_CONTROLLER_QUEUE_SAY_VERB) ///Whisper verb /mob/verb/whisper_verb(message as text) set name = "Whisper" set category = "IC" + set instant = TRUE + if(GLOB.say_disabled) //This is here to try to identify lag problems to_chat(usr, span_danger("Speech is currently admin-disabled.")) return - whisper(message) + + if(message) + SSspeech_controller.queue_say_for_mob(src, message, SPEECH_CONTROLLER_QUEUE_WHISPER_VERB) ///whisper a message /mob/proc/whisper(message, datum/language/language=null) - say(message, language) //only living mobs actually whisper, everything else just talks + say(message, language = language) ///The me emote verb /mob/verb/me_verb(message as text) @@ -34,7 +43,7 @@ message = trim(copytext_char(sanitize(message), 1, MAX_MESSAGE_LEN)) - usr.emote("me",1,message,TRUE) + SSspeech_controller.queue_say_for_mob(src, message, SPEECH_CONTROLLER_QUEUE_EMOTE_VERB) ///Speak as a dead person (ghost etc) /mob/proc/say_dead(message) @@ -53,8 +62,6 @@ to_chat(src, span_danger("You have been banned from deadchat.")) return - - if (src.client) if(src.client.prefs.muted & MUTE_DEADCHAT) to_chat(src, span_danger("You cannot talk in deadchat (muted).")) diff --git a/tgstation.dme b/tgstation.dme index ddbba99af56..372eca4e65d 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -145,6 +145,7 @@ #include "code\__DEFINES\spaceman_dmm.dm" #include "code\__DEFINES\span.dm" #include "code\__DEFINES\spatial_gridmap.dm" +#include "code\__DEFINES\speech_controller.dm" #include "code\__DEFINES\stat.dm" #include "code\__DEFINES\stat_tracking.dm" #include "code\__DEFINES\station.dm" @@ -463,6 +464,7 @@ #include "code\controllers\subsystem\sounds.dm" #include "code\controllers\subsystem\spacedrift.dm" #include "code\controllers\subsystem\spatial_gridmap.dm" +#include "code\controllers\subsystem\speech_controller.dm" #include "code\controllers\subsystem\statpanel.dm" #include "code\controllers\subsystem\stickyban.dm" #include "code\controllers\subsystem\sun.dm"