From 6a8abd4f80ae8d9b0c244dd85505fa542a33cf35 Mon Sep 17 00:00:00 2001 From: Leshana Date: Wed, 7 Jun 2017 17:50:07 -0400 Subject: [PATCH] Ports tg improvements in StonedMC subsystem * Ports /tg StonedMC Enhancement - Runlevels - Lets services more precicely define when they want to fire - in the lobby, during game, only after, etc. Includes bugfixes from: * https://github.com/tgstation/tgstation/pull/27132 * https://github.com/tgstation/tgstation/pull/27338 * https://github.com/tgstation/tgstation/pull/27576 * https://github.com/tgstation/tgstation/pull/27519 * Ports Standardizes subsystem Shutdown order - https://github.com/tgstation/tgstation/pull/26228 --- code/__defines/MC.dm | 19 +++--- code/__defines/subsystems.dm | 13 ++++ code/controllers/master.dm | 94 +++++++++++++++----------- code/controllers/subsystem.dm | 1 + code/controllers/subsystems/garbage.dm | 3 +- polaris.dme | 1 + 6 files changed, 79 insertions(+), 52 deletions(-) create mode 100644 code/__defines/subsystems.dm diff --git a/code/__defines/MC.dm b/code/__defines/MC.dm index 59fb0d4db73..297f04c148c 100644 --- a/code/__defines/MC.dm +++ b/code/__defines/MC.dm @@ -21,39 +21,36 @@ //SubSystem flags (Please design any new flags so that the default is off, to make adding flags to subsystems easier) -//subsystem should fire during pre-game lobby. -#define SS_FIRE_IN_LOBBY 1 - //subsystem does not initialize. -#define SS_NO_INIT 2 +#define SS_NO_INIT 1 //subsystem does not fire. // (like can_fire = 0, but keeps it from getting added to the processing subsystems list) // (Requires a MC restart to change) -#define SS_NO_FIRE 4 +#define SS_NO_FIRE 2 //subsystem only runs on spare cpu (after all non-background subsystems have ran that tick) // SS_BACKGROUND has its own priority bracket -#define SS_BACKGROUND 8 +#define SS_BACKGROUND 4 //subsystem does not tick check, and should not run unless there is enough time (or its running behind (unless background)) -#define SS_NO_TICK_CHECK 16 +#define SS_NO_TICK_CHECK 8 //Treat wait as a tick count, not DS, run every wait ticks. // (also forces it to run first in the tick, above even SS_NO_TICK_CHECK subsystems) -// (implies SS_FIRE_IN_LOBBY because of how it works) +// (implies all runlevels because of how it works) // (overrides SS_BACKGROUND) // This is designed for basically anything that works as a mini-mc (like SStimer) -#define SS_TICKER 32 +#define SS_TICKER 16 //keep the subsystem's timing on point by firing early if it fired late last fire because of lag // ie: if a 20ds subsystem fires say 5 ds late due to lag or what not, its next fire would be in 15ds, not 20ds. -#define SS_KEEP_TIMING 64 +#define SS_KEEP_TIMING 32 //Calculate its next fire after its fired. // (IE: if a 5ds wait SS takes 2ds to run, its next fire should be 5ds away, not 3ds like it normally would be) // This flag overrides SS_KEEP_TIMING -#define SS_POST_FIRE_TIMING 128 +#define SS_POST_FIRE_TIMING 64 //SUBSYSTEM STATES #define SS_IDLE 0 //aint doing shit. diff --git a/code/__defines/subsystems.dm b/code/__defines/subsystems.dm new file mode 100644 index 00000000000..8c546c9135a --- /dev/null +++ b/code/__defines/subsystems.dm @@ -0,0 +1,13 @@ + +// SS runlevels + +#define RUNLEVEL_INIT 0 // "Initialize Only" - Used for subsystems that should never be fired (Should also have SS_NO_FIRE set) +#define RUNLEVEL_LOBBY 1 // Initial runlevel before setup. Returns to here if setup fails. +#define RUNLEVEL_SETUP 2 // While the gamemode setup is running. I.E gameticker.setup() +#define RUNLEVEL_GAME 4 // After successful game ticker setup, while the round is running. +#define RUNLEVEL_POSTGAME 8 // When round completes but before reboot + +#define RUNLEVELS_DEFAULT (RUNLEVEL_SETUP | RUNLEVEL_GAME | RUNLEVEL_POSTGAME) + +var/global/list/runlevel_flags = list(RUNLEVEL_LOBBY, RUNLEVEL_SETUP, RUNLEVEL_GAME, RUNLEVEL_POSTGAME) +#define RUNLEVEL_FLAG_TO_INDEX(flag) (log(2, flag) + 1) // Convert from the runlevel bitfield constants to index in runlevel_flags list diff --git a/code/controllers/master.dm b/code/controllers/master.dm index fbf97f730c0..6bcfb6ff0aa 100644 --- a/code/controllers/master.dm +++ b/code/controllers/master.dm @@ -40,8 +40,6 @@ var/CURRENT_TICKLIMIT = TICK_LIMIT_RUNNING var/make_runtime = 0 var/initializations_finished_with_no_players_logged_in //I wonder what this could be? - // Has round started? (So we know what subsystems to run) - var/round_started = 0 // The type of the last subsystem to be process()'d. var/last_type_processed @@ -52,6 +50,8 @@ var/CURRENT_TICKLIMIT = TICK_LIMIT_RUNNING var/queue_priority_count_bg = 0 //Same, but for background subsystems var/map_loading = FALSE //Are we loading in a new map? + var/current_runlevel //for scheduling different subsystems for different stages of the round + /datum/controller/master/New() // Highlander-style: there can only be one! Kill off the old and replace it with the new. subsystems = list() @@ -70,6 +70,8 @@ var/CURRENT_TICKLIMIT = TICK_LIMIT_RUNNING /datum/controller/master/Shutdown() processing = FALSE + sortTim(subsystems, /proc/cmp_subsystem_init) + reverseRange(subsystems) for(var/datum/controller/subsystem/ss in subsystems) ss.Shutdown() @@ -128,6 +130,7 @@ var/CURRENT_TICKLIMIT = TICK_LIMIT_RUNNING if(FireHim) Master.subsystems += new BadBoy.type //NEW_SS_GLOBAL will remove the old one subsystems = Master.subsystems + current_runlevel = Master.current_runlevel StartProcessing(10) else to_chat(world, "The Master Controller is having some issues, we will need to re-initialize EVERYTHING") @@ -165,6 +168,9 @@ var/CURRENT_TICKLIMIT = TICK_LIMIT_RUNNING to_chat(world, "[msg]") log_world(msg) + if (!current_runlevel) + SetRunLevel(RUNLEVEL_LOBBY) + // Sort subsystems by display setting for easy access. sortTim(subsystems, /proc/cmp_subsystem_display) // Set world options. @@ -180,16 +186,12 @@ var/CURRENT_TICKLIMIT = TICK_LIMIT_RUNNING // Loop. Master.StartProcessing(0) -// Notify the MC that the round has started. -/datum/controller/master/proc/RoundStart() - round_started = 1 - var/timer = world.time - for (var/datum/controller/subsystem/SS in subsystems) - if (SS.flags & SS_FIRE_IN_LOBBY || SS.flags & SS_TICKER) - continue //already firing - // Stagger subsystems. - timer += world.tick_lag * rand(1, 5) - SS.next_fire = timer +/datum/controller/master/proc/SetRunLevel(new_runlevel) + var/old_runlevel = isnull(current_runlevel) ? "NULL" : runlevel_flags[current_runlevel] + testing("MC: Runlevel changed from [old_runlevel] to [new_runlevel]") + current_runlevel = RUNLEVEL_FLAG_TO_INDEX(new_runlevel) + if(current_runlevel < 1) + CRASH("Attempted to set invalid runlevel: [new_runlevel]") // Starts the mc, and sticks around to restart it if the loop ever ends. /datum/controller/master/proc/StartProcessing(delay) @@ -214,12 +216,9 @@ var/CURRENT_TICKLIMIT = TICK_LIMIT_RUNNING //Prep the loop (most of this is because we want MC restarts to reset as much state as we can, and because // local vars rock - // Schedule the first run of the Subsystems. - round_started = world.has_round_started() //all this shit is here so that flag edits can be refreshed by restarting the MC. (and for speed) var/list/tickersubsystems = list() - var/list/normalsubsystems = list() - var/list/lobbysubsystems = list() + var/list/runlevel_sorted_subsystems = list(list()) //ensure we always have at least one runlevel var/timer = world.time for (var/thing in subsystems) var/datum/controller/subsystem/SS = thing @@ -234,25 +233,29 @@ var/CURRENT_TICKLIMIT = TICK_LIMIT_RUNNING timer += world.tick_lag * rand(1, 5) SS.next_fire = timer continue - if (SS.flags & SS_FIRE_IN_LOBBY) - lobbysubsystems += SS - timer += world.tick_lag * rand(1, 5) - SS.next_fire = timer - else if (round_started) - timer += world.tick_lag * rand(1, 5) - SS.next_fire = timer - normalsubsystems += SS + + var/ss_runlevels = SS.runlevels + var/added_to_any = FALSE + for(var/I in 1 to global.runlevel_flags.len) + if(ss_runlevels & global.runlevel_flags[I]) + while(runlevel_sorted_subsystems.len < I) + runlevel_sorted_subsystems += list(list()) + runlevel_sorted_subsystems[I] += SS + added_to_any = TRUE + if(!added_to_any) + WARNING("[SS.name] subsystem is not SS_NO_FIRE but also does not have any runlevels set!") queue_head = null queue_tail = null //these sort by lower priorities first to reduce the number of loops needed to add subsequent SS's to the queue //(higher subsystems will be sooner in the queue, adding them later in the loop means we don't have to loop thru them next queue add) sortTim(tickersubsystems, /proc/cmp_subsystem_priority) - sortTim(normalsubsystems, /proc/cmp_subsystem_priority) - sortTim(lobbysubsystems, /proc/cmp_subsystem_priority) + for(var/I in runlevel_sorted_subsystems) + sortTim(runlevel_sorted_subsystems, /proc/cmp_subsystem_priority) + I += tickersubsystems - normalsubsystems += tickersubsystems - lobbysubsystems += tickersubsystems + var/cached_runlevel = current_runlevel + var/list/current_runlevel_subsystems = runlevel_sorted_subsystems[cached_runlevel] init_timeofday = REALTIMEOFDAY init_time = world.time @@ -289,14 +292,23 @@ var/CURRENT_TICKLIMIT = TICK_LIMIT_RUNNING if (!Failsafe || (Failsafe.processing_interval > 0 && (Failsafe.lasttick+(Failsafe.processing_interval*5)) < world.time)) new/datum/controller/failsafe() // (re)Start the failsafe. if (!queue_head || !(iteration % 3)) - if (round_started) - subsystems_to_check = normalsubsystems - else - subsystems_to_check = lobbysubsystems + var/checking_runlevel = current_runlevel + if(cached_runlevel != checking_runlevel) + //resechedule subsystems + cached_runlevel = checking_runlevel + current_runlevel_subsystems = runlevel_sorted_subsystems[cached_runlevel] + var/stagger = world.time + for(var/I in current_runlevel_subsystems) + var/datum/controller/subsystem/SS = I + if(SS.next_fire <= world.time) + stagger += world.tick_lag * rand(1, 5) + SS.next_fire = stagger + + subsystems_to_check = current_runlevel_subsystems else subsystems_to_check = tickersubsystems if (CheckQueue(subsystems_to_check) <= 0) - if (!SoftReset(tickersubsystems, normalsubsystems, lobbysubsystems)) + if (!SoftReset(tickersubsystems, runlevel_sorted_subsystems)) log_world("MC: SoftReset() failed, crashing") return if (!error_level) @@ -308,7 +320,7 @@ var/CURRENT_TICKLIMIT = TICK_LIMIT_RUNNING if (queue_head) if (RunQueue() <= 0) - if (!SoftReset(tickersubsystems, normalsubsystems, lobbysubsystems)) + if (!SoftReset(tickersubsystems, runlevel_sorted_subsystems)) log_world("MC: SoftReset() failed, crashing") return if (!error_level) @@ -479,13 +491,15 @@ var/CURRENT_TICKLIMIT = TICK_LIMIT_RUNNING //resets the queue, and all subsystems, while filtering out the subsystem lists // called if any mc's queue procs runtime or exit improperly. -/datum/controller/master/proc/SoftReset(list/ticker_SS, list/normal_SS, list/lobby_SS) +/datum/controller/master/proc/SoftReset(list/ticker_SS, list/runlevel_SS) . = 0 log_world("MC: SoftReset called, resetting MC queue state.") - if (!istype(subsystems) || !istype(ticker_SS) || !istype(normal_SS) || !istype(lobby_SS)) - log_world("MC: SoftReset: Bad list contents: '[subsystems]' '[ticker_SS]' '[normal_SS]' '[lobby_SS]' Crashing!") + if (!istype(subsystems) || !istype(ticker_SS) || !istype(runlevel_SS)) + log_world("MC: SoftReset: Bad list contents: '[subsystems]' '[ticker_SS]' '[runlevel_SS]'") return - var/subsystemstocheck = subsystems + ticker_SS + normal_SS + lobby_SS + var/subsystemstocheck = subsystems + ticker_SS + for(var/I in runlevel_SS) + subsystemstocheck |= I for (var/thing in subsystemstocheck) var/datum/controller/subsystem/SS = thing @@ -493,8 +507,8 @@ var/CURRENT_TICKLIMIT = TICK_LIMIT_RUNNING //list(SS) is so if a list makes it in the subsystem list, we remove the list, not the contents subsystems -= list(SS) ticker_SS -= list(SS) - normal_SS -= list(SS) - lobby_SS -= list(SS) + for(var/I in runlevel_SS) + I -= list(SS) log_world("MC: SoftReset: Found bad entry in subsystem list, '[SS]'") continue if (SS.queue_next && !istype(SS.queue_next)) diff --git a/code/controllers/subsystem.dm b/code/controllers/subsystem.dm index b3e27a01752..7dff609c76d 100644 --- a/code/controllers/subsystem.dm +++ b/code/controllers/subsystem.dm @@ -6,6 +6,7 @@ var/priority = 50 //When mutiple subsystems need to run in the same tick, higher priority subsystems will run first and be given a higher share of the tick before MC_TICK_CHECK triggers a sleep var/flags = 0 //see MC.dm in __DEFINES Most flags must be set on world start to take full effect. (You can also restart the mc to force them to process again) + var/runlevels = RUNLEVELS_DEFAULT //points of the game at which the SS can fire //set to 0 to prevent fire() calls, mostly for admin use or subsystems that may be resumed later // use the SS_NO_FIRE flag instead for systems that never fire to keep it from even being added to the list diff --git a/code/controllers/subsystems/garbage.dm b/code/controllers/subsystems/garbage.dm index b07337e8035..d6db860d5de 100644 --- a/code/controllers/subsystems/garbage.dm +++ b/code/controllers/subsystems/garbage.dm @@ -5,7 +5,8 @@ SUBSYSTEM_DEF(garbage) name = "Garbage" priority = 15 wait = 5 - flags = SS_FIRE_IN_LOBBY|SS_POST_FIRE_TIMING|SS_BACKGROUND|SS_NO_INIT + flags = SS_POST_FIRE_TIMING|SS_BACKGROUND|SS_NO_INIT + runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY var/collection_timeout = 3000// deciseconds to wait to let running procs finish before we just say fuck it and force del() the object var/delslasttick = 0 // number of del()'s we've done this tick diff --git a/polaris.dme b/polaris.dme index 3b49b9566ba..83159a46213 100644 --- a/polaris.dme +++ b/polaris.dme @@ -40,6 +40,7 @@ #include "code\__defines\qdel.dm" #include "code\__defines\research.dm" #include "code\__defines\species_languages.dm" +#include "code\__defines\subsystems.dm" #include "code\__defines\targeting.dm" #include "code\__defines\tick.dm" #include "code\__defines\turfs.dm"