diff --git a/code/__DEFINES/_tick.dm b/code/__DEFINES/_tick.dm index fc6147c4304..46676a9f527 100644 --- a/code/__DEFINES/_tick.dm +++ b/code/__DEFINES/_tick.dm @@ -1,14 +1,19 @@ /// Percentage of tick to leave for master controller to run -#define MAPTICK_MC_MIN_RESERVE 70 +#define MAPTICK_MC_MIN_RESERVE 60 /// internal_tick_usage is updated every tick by extools -#define MAPTICK_LAST_INTERNAL_TICK_USAGE ((GLOB.internal_tick_usage / world.tick_lag) * 100) -/// Tick limit while running normally +#define MAPTICK_LAST_INTERNAL_TICK_USAGE ((Master.normalized_internal_tick_usage / world.tick_lag) * 100) +/// Amount of a tick to reserve for byond regardless of its reported internal_tick_usage #define TICK_BYOND_RESERVE 2 -#define TICK_LIMIT_RUNNING (max(100 - TICK_BYOND_RESERVE - MAPTICK_LAST_INTERNAL_TICK_USAGE, MAPTICK_MC_MIN_RESERVE)) +/// Tick limit while running normally +#define TICK_LIMIT_RUNNING (max(TICK_LIMIT_RUNNING_BACKGROUND, MAPTICK_MC_MIN_RESERVE)) +/// Tick limit for background things, strictly obeys the internal_tick_usage metric +#define TICK_LIMIT_RUNNING_BACKGROUND (100 - TICK_BYOND_RESERVE - MAPTICK_LAST_INTERNAL_TICK_USAGE) +/// Precent of a tick to require to even bother running anything. (10 percent of the tick_limit_running by default) +#define TICK_MIN_RUNTIME (TICK_LIMIT_RUNNING * 0.1) /// Tick limit used to resume things in stoplag -#define TICK_LIMIT_TO_RUN 70 +#define TICK_LIMIT_TO_RUN (Master.current_ticklimit - TICK_MIN_RUNTIME) /// Tick limit for MC while running -#define TICK_LIMIT_MC 70 +#define TICK_LIMIT_MC (TICK_LIMIT_RUNNING - TICK_MIN_RUNTIME) /// Tick limit while initializing #define TICK_LIMIT_MC_INIT_DEFAULT (100 - TICK_BYOND_RESERVE) diff --git a/code/controllers/master.dm b/code/controllers/master.dm index 59ac68960cb..ed8e0c83409 100644 --- a/code/controllers/master.dm +++ b/code/controllers/master.dm @@ -61,9 +61,10 @@ GLOBAL_REAL(Master, /datum/controller/master) = new var/static/random_seed - //current tick limit, assigned before running a subsystem. - //used by CHECK_TICK as well so that the procs subsystems call can obey that SS's tick limits + ///current tick limit, assigned before running a subsystem. used by CHECK_TICK as well so that the procs subsystems call can obey that SS's tick limits var/static/current_ticklimit = TICK_LIMIT_RUNNING + /// normalized version of the byond internal tick usage metric to smooth out peaks. + var/normalized_internal_tick_usage = 0 /datum/controller/master/New() if(!config) @@ -173,7 +174,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new // Please don't stuff random bullshit here, // Make a subsystem, give it the SS_NO_FIRE flag, and do your work in it's Initialize() /datum/controller/master/Initialize(delay, init_sss, tgs_prime) - set waitfor = 0 + set waitfor = FALSE if(delay) sleep(delay) @@ -235,7 +236,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new // Starts the mc, and sticks around to restart it if the loop ever ends. /datum/controller/master/proc/StartProcessing(delay) - set waitfor = 0 + set waitfor = FALSE if(delay) sleep(delay) testing("Master starting processing") @@ -309,12 +310,15 @@ GLOBAL_REAL(Master, /datum/controller/master) = new while (1) tickdrift = max(0, MC_AVERAGE_FAST(tickdrift, (((REALTIMEOFDAY - init_timeofday) - (world.time - init_time)) / world.tick_lag))) + normalized_internal_tick_usage = max(0, MC_AVG_FAST_UP_SLOW_DOWN(normalized_internal_tick_usage, GLOB.internal_tick_usage)) var/starting_tick_usage = TICK_USAGE if (processing <= 0) current_ticklimit = TICK_LIMIT_RUNNING sleep(10) continue + sleep_delta = MC_AVERAGE_FAST(sleep_delta, 1) //decay sleep_delta + //Anti-tick-contention heuristics: //if there are mutiple sleeping procs running before us hogging the cpu, we have to run later. // (because sleeps are processed in the order received, longer sleeps are more likely to run first) @@ -328,15 +332,13 @@ GLOBAL_REAL(Master, /datum/controller/master) = new if (last_run + CEILING(world.tick_lag * (processing * sleep_delta), world.tick_lag) < world.time) sleep_delta += 1 - sleep_delta = MC_AVERAGE_FAST(sleep_delta, 1) //decay sleep_delta - if (starting_tick_usage > (TICK_LIMIT_MC*0.75)) //we ran 3/4 of the way into the tick sleep_delta += 1 //debug if (make_runtime) var/datum/controller/subsystem/SS - SS.can_fire = 0 + SS.can_fire = FALSE if (!Failsafe || (Failsafe.processing_interval > 0 && (Failsafe.lasttick+(Failsafe.processing_interval*5)) < world.time)) new/datum/controller/failsafe() // (re)Start the failsafe. @@ -359,7 +361,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new else subsystems_to_check = tickersubsystems - if (CheckQueue(subsystems_to_check) <= 0) + if (!CheckQueue(subsystems_to_check)) if (!SoftReset(tickersubsystems, runlevel_sorted_subsystems)) log_world("MC: SoftReset() failed, crashing") return @@ -371,7 +373,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new continue if (queue_head) - if (RunQueue() <= 0) + if (!RunQueue()) if (!SoftReset(tickersubsystems, runlevel_sorted_subsystems)) log_world("MC: SoftReset() failed, crashing") return @@ -392,8 +394,6 @@ GLOBAL_REAL(Master, /datum/controller/master) = new skip_ticks-- src.sleep_delta = MC_AVERAGE_FAST(src.sleep_delta, sleep_delta) current_ticklimit = TICK_LIMIT_RUNNING - if (processing * sleep_delta <= world.tick_lag) - current_ticklimit -= (TICK_LIMIT_RUNNING * 0.25) //reserve the tail 1/4 of the next tick for the mc if we plan on running next tick sleep(world.tick_lag * (processing * sleep_delta)) @@ -401,8 +401,6 @@ GLOBAL_REAL(Master, /datum/controller/master) = new // This is what decides if something should run. /datum/controller/master/proc/CheckQueue(list/subsystemstocheck) - . = 0 //so the mc knows if we runtimed - //we create our variables outside of the loops to save on overhead var/datum/controller/subsystem/SS var/SS_flags @@ -424,139 +422,139 @@ GLOBAL_REAL(Master, /datum/controller/master) = new if ((SS_flags & (SS_TICKER|SS_KEEP_TIMING)) == SS_KEEP_TIMING && SS.last_fire + (SS.wait * 0.75) > world.time) continue SS.enqueue() - . = 1 + return TRUE // so MC knows if we runtime // 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 - var/queue_node_flags - var/queue_node_priority + var/datum/controller/subsystem/queue_node = queue_head //The subsystem we're running right now + var/queue_node_flags //Cache of queue node flags + var/queue_node_priority //Cache of queue node priority var/queue_node_paused - var/current_tick_budget - var/tick_precentage - var/tick_remaining - var/ran = TRUE //this is right - var/ran_non_ticker = FALSE - var/bg_calc //have we swtiched current_tick_budget to background mode yet? - var/tick_usage + var/current_tick_budget = queue_priority_count + var/tick_precentage //tick % used, used for calculating tick remaining + var/tick_remaining //How much of the tick we've got left over to do stuff + var/ran_non_ticker = FALSE //Whether we've started running non-ticker subsystems yet + var/bg_calc = FALSE //have we swtiched current_tick_budget to background mode yet? + var/tick_usage //How much of a tick we're using in this queue node //keep running while we have stuff to run and we haven't gone over a tick - // this is so subsystems paused eariler can use tick time that later subsystems never used - while (ran && queue_head && TICK_USAGE < TICK_LIMIT_MC) - ran = FALSE - bg_calc = FALSE - current_tick_budget = queue_priority_count - queue_node = queue_head - while (queue_node) - if (ran && TICK_USAGE > TICK_LIMIT_RUNNING) - break - queue_node_flags = queue_node.flags - queue_node_priority = queue_node.queued_priority - - if (!(queue_node_flags & SS_TICKER) && skip_ticks) - queue_node = queue_node.queue_next - continue - //super special case, subsystems where we can't make them pause mid way through - //if we can't run them this tick (without going over a tick) - //we bump up their priority and attempt to run them next tick - //(unless we haven't even ran anything this tick, since its unlikely they will ever be able run - // in those cases, so we just let them run) - if (queue_node_flags & SS_NO_TICK_CHECK) - if (queue_node.tick_usage > TICK_LIMIT_RUNNING - TICK_USAGE && ran_non_ticker) - if (!(queue_node_flags & SS_BACKGROUND)) - queue_node.queued_priority += queue_priority_count * 0.1 - queue_priority_count -= queue_node_priority - queue_priority_count += queue_node.queued_priority - current_tick_budget -= queue_node_priority - queue_node = queue_node.queue_next - continue - - if (!bg_calc && (queue_node_flags & SS_BACKGROUND)) - current_tick_budget = queue_priority_count_bg - bg_calc = TRUE - - tick_remaining = TICK_LIMIT_RUNNING - TICK_USAGE - - if (current_tick_budget > 0 && queue_node_priority > 0) - tick_precentage = tick_remaining / (current_tick_budget / queue_node_priority) - else - tick_precentage = tick_remaining - - tick_precentage = max(tick_precentage*0.5, tick_precentage-queue_node.tick_overrun) - - current_ticklimit = round(TICK_USAGE + tick_precentage) - - if (!(queue_node_flags & SS_TICKER)) - ran_non_ticker = TRUE - ran = TRUE - - queue_node_paused = (queue_node.state == SS_PAUSED || queue_node.state == SS_PAUSING) - last_type_processed = queue_node - - queue_node.state = SS_RUNNING - - tick_usage = TICK_USAGE - var/state = queue_node.ignite(queue_node_paused) - tick_usage = TICK_USAGE - tick_usage - - if (state == SS_RUNNING) - state = SS_IDLE - current_tick_budget -= queue_node_priority + //this is so subsystems paused eariler can use tick time that later subsystems never used + while (queue_head && queue_node && TICK_USAGE < TICK_LIMIT_MC) + queue_node_flags = queue_node.flags + queue_node_priority = queue_node.queued_priority - if (tick_usage < 0) - tick_usage = 0 - queue_node.tick_overrun = max(0, MC_AVG_FAST_UP_SLOW_DOWN(queue_node.tick_overrun, tick_usage-tick_precentage)) - queue_node.state = state - - if (state == SS_PAUSED) - queue_node.paused_ticks++ - queue_node.paused_tick_usage += tick_usage - queue_node = queue_node.queue_next - continue - - queue_node.ticks = MC_AVERAGE(queue_node.ticks, queue_node.paused_ticks) - tick_usage += queue_node.paused_tick_usage - - queue_node.tick_usage = MC_AVERAGE_FAST(queue_node.tick_usage, tick_usage) - - queue_node.cost = MC_AVERAGE_FAST(queue_node.cost, TICK_DELTA_TO_MS(tick_usage)) - queue_node.paused_ticks = 0 - queue_node.paused_tick_usage = 0 - - if (bg_calc) //update our running total - queue_priority_count_bg -= queue_node_priority - else - queue_priority_count -= queue_node_priority - - queue_node.last_fire = world.time - queue_node.times_fired++ - - if (queue_node_flags & SS_TICKER) - queue_node.next_fire = world.time + (world.tick_lag * queue_node.wait) - else if (queue_node_flags & SS_POST_FIRE_TIMING) - queue_node.next_fire = world.time + queue_node.wait + (world.tick_lag * (queue_node.tick_overrun/100)) - else if (queue_node_flags & SS_KEEP_TIMING) - queue_node.next_fire += queue_node.wait - else - queue_node.next_fire = queue_node.queued_time + queue_node.wait + (world.tick_lag * (queue_node.tick_overrun/100)) - - queue_node.queued_time = 0 - - //remove from queue - queue_node.dequeue() - + if(!(queue_node_flags & SS_TICKER) && skip_ticks) queue_node = queue_node.queue_next + continue - . = 1 + //super special case, subsystems where we can't make them pause mid way through + //if we can't run them this tick (without going over a tick) + //we bump up their priority and attempt to run them next tick + //(unless we haven't even ran anything this tick, since its unlikely they will ever be able run + // in those cases, so we just let them run) + + + if (queue_node_flags & SS_NO_TICK_CHECK) + if (queue_node.tick_usage > TICK_LIMIT_RUNNING - TICK_USAGE && ran_non_ticker) + if (!(queue_node_flags & SS_BACKGROUND)) + queue_node.queued_priority += queue_priority_count * 0.1 + queue_priority_count -= queue_node_priority + queue_priority_count += queue_node.queued_priority + current_tick_budget -= queue_node_priority + queue_node = queue_node.queue_next + continue + + //Checks if we're in background calculation mode yet and sets us to it if we are + if (!bg_calc && (queue_node_flags & SS_BACKGROUND)) + current_tick_budget = queue_priority_count_bg + bg_calc = TRUE + + tick_remaining = ((bg_calc) ? (TICK_LIMIT_RUNNING_BACKGROUND) : (TICK_LIMIT_RUNNING)) - TICK_USAGE + + if (tick_remaining < TICK_MIN_RUNTIME) + current_tick_budget -= queue_node_priority + queue_node = queue_node.queue_next + continue + + if (current_tick_budget > 0 && queue_node_priority > 0) + tick_precentage = tick_remaining / (current_tick_budget / queue_node_priority) + else + tick_precentage = tick_remaining + + tick_precentage = max(TICK_MIN_RUNTIME, tick_precentage*0.5, tick_precentage-queue_node.tick_overrun) + + current_ticklimit = round(TICK_USAGE + tick_precentage) + + //Check for if we ran a non-ticker ss, used for check if we can start processing SS_NO_TICK_CHECK stuff yet + if(!(queue_node_flags & SS_TICKER)) + ran_non_ticker = TRUE + + queue_node_paused = (queue_node.state == SS_PAUSED || queue_node.state == SS_PAUSING) + last_type_processed = queue_node + + queue_node.state = SS_RUNNING + + tick_usage = TICK_USAGE + var/state = queue_node.ignite(queue_node_paused) + tick_usage = TICK_USAGE - tick_usage + + if(state == SS_RUNNING) + state = SS_IDLE + current_tick_budget -= queue_node_priority + + if(tick_usage < 0) + tick_usage = 0 + queue_node.tick_overrun = max(0, MC_AVG_FAST_UP_SLOW_DOWN(queue_node.tick_overrun, tick_usage-tick_precentage)) + queue_node.state = state + + if(state == SS_PAUSED) + queue_node.paused_ticks++ + queue_node.paused_tick_usage += tick_usage + queue_node = queue_node.queue_next + continue + + queue_node.ticks = MC_AVERAGE(queue_node.ticks, queue_node.paused_ticks) + tick_usage += queue_node.paused_tick_usage + + queue_node.tick_usage = MC_AVERAGE_FAST(queue_node.tick_usage, tick_usage) + + queue_node.cost = MC_AVERAGE_FAST(queue_node.cost, TICK_DELTA_TO_MS(tick_usage)) + queue_node.paused_ticks = 0 + queue_node.paused_tick_usage = 0 + + if (bg_calc) //update our running total + queue_priority_count_bg -= queue_node_priority + else + queue_priority_count -= queue_node_priority + + queue_node.last_fire = world.time + queue_node.times_fired++ + + //Calculate next fire + if (queue_node_flags & SS_TICKER) + queue_node.next_fire = world.time + (world.tick_lag * queue_node.wait) + else if (queue_node_flags & SS_POST_FIRE_TIMING) + queue_node.next_fire = world.time + queue_node.wait + (world.tick_lag * (queue_node.tick_overrun/100)) + else if (queue_node_flags & SS_KEEP_TIMING) + queue_node.next_fire += queue_node.wait + else + queue_node.next_fire = queue_node.queued_time + queue_node.wait + (world.tick_lag * (queue_node.tick_overrun/100)) + + queue_node.queued_time = 0 + + //remove from queue + queue_node.dequeue() + + queue_node = queue_node.queue_next + + return TRUE // so MC knows if we runtime //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/runlevel_SS) - . = 0 log_world("MC: SoftReset called, resetting MC queue state.") if (!istype(subsystems) || !istype(ticker_SS) || !istype(runlevel_SS)) log_world("MC: SoftReset: Bad list contents: '[subsystems]' '[ticker_SS]' '[runlevel_SS]'") @@ -593,7 +591,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new queue_priority_count = 0 queue_priority_count_bg = 0 log_world("MC: SoftReset: Finished.") - . = 1 + return TRUE // so MC knows if we runtime /// Warns us that the end of tick byond map_update will be laggier then normal, so that we can just skip running subsystems this tick. /datum/controller/master/proc/laggy_byond_map_update_incoming() diff --git a/code/controllers/subsystem/adjacent_air.dm b/code/controllers/subsystem/adjacent_air.dm index df0a5cab4e6..253c3fc1ed6 100644 --- a/code/controllers/subsystem/adjacent_air.dm +++ b/code/controllers/subsystem/adjacent_air.dm @@ -1,6 +1,5 @@ SUBSYSTEM_DEF(adjacent_air) name = "Atmos Adjacency" - flags = SS_BACKGROUND runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME wait = 10 priority = FIRE_PRIORITY_ATMOS_ADJACENCY diff --git a/code/controllers/subsystem/air.dm b/code/controllers/subsystem/air.dm index c9cf98be5f6..da411204096 100644 --- a/code/controllers/subsystem/air.dm +++ b/code/controllers/subsystem/air.dm @@ -3,7 +3,6 @@ SUBSYSTEM_DEF(air) init_order = INIT_ORDER_AIR priority = FIRE_PRIORITY_AIR wait = 0.5 SECONDS - flags = SS_BACKGROUND runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME var/cached_cost = 0 diff --git a/code/controllers/subsystem/augury.dm b/code/controllers/subsystem/augury.dm index 53c86004a6a..6dab6a46cfa 100644 --- a/code/controllers/subsystem/augury.dm +++ b/code/controllers/subsystem/augury.dm @@ -1,6 +1,6 @@ SUBSYSTEM_DEF(augury) name = "Augury" - flags = SS_NO_INIT + flags = SS_NO_INIT|SS_BACKGROUND runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME var/list/watchers = list() diff --git a/code/controllers/subsystem/dcs.dm b/code/controllers/subsystem/dcs.dm index 104c3eef31d..67231a0955c 100644 --- a/code/controllers/subsystem/dcs.dm +++ b/code/controllers/subsystem/dcs.dm @@ -23,12 +23,12 @@ PROCESSING_SUBSYSTEM_DEF(dcs) return . = elements_by_type[element_id] = new eletype -/**** - * Generates an id for bespoke elements when given the argument list - * Generating the id here is a bit complex because we need to support named arguments - * Named arguments can appear in any order and we need them to appear after ordered arguments - * We assume that no one will pass in a named argument with a value of null - **/ +/** + * Generates an id for bespoke elements when given the argument list + * Generating the id here is a bit complex because we need to support named arguments + * Named arguments can appear in any order and we need them to appear after ordered arguments + * We assume that no one will pass in a named argument with a value of null + **/ /datum/controller/subsystem/processing/dcs/proc/GetIdFromArguments(list/arguments) var/datum/element/eletype = arguments[1] var/list/fullid = list("[eletype]") diff --git a/code/controllers/subsystem/disease.dm b/code/controllers/subsystem/disease.dm index 4fe5533e161..332a518b848 100644 --- a/code/controllers/subsystem/disease.dm +++ b/code/controllers/subsystem/disease.dm @@ -1,6 +1,6 @@ SUBSYSTEM_DEF(disease) name = "Disease" - flags = SS_NO_FIRE + flags = SS_NO_FIRE | SS_BACKGROUND var/list/active_diseases = list() //List of Active disease in all mobs; purely for quick referencing. var/list/diseases diff --git a/code/controllers/subsystem/fire_burning.dm b/code/controllers/subsystem/fire_burning.dm index a5e903221a8..efaff62630c 100644 --- a/code/controllers/subsystem/fire_burning.dm +++ b/code/controllers/subsystem/fire_burning.dm @@ -1,7 +1,7 @@ SUBSYSTEM_DEF(fire_burning) name = "Fire Burning" priority = FIRE_PRIOTITY_BURNING - flags = SS_NO_INIT|SS_BACKGROUND + flags = SS_NO_INIT runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME var/list/currentrun = list() diff --git a/code/controllers/subsystem/pai.dm b/code/controllers/subsystem/pai.dm index c563e2a7452..552ea48d3aa 100644 --- a/code/controllers/subsystem/pai.dm +++ b/code/controllers/subsystem/pai.dm @@ -1,7 +1,7 @@ SUBSYSTEM_DEF(pai) name = "pAI" - flags = SS_NO_INIT|SS_NO_FIRE + flags = SS_NO_INIT|SS_NO_FIRE|SS_BACKGROUND var/list/candidates = list() var/ghost_spam = FALSE diff --git a/code/controllers/subsystem/processing/fields.dm b/code/controllers/subsystem/processing/fields.dm index a4c58b883a8..10fb4da0f73 100644 --- a/code/controllers/subsystem/processing/fields.dm +++ b/code/controllers/subsystem/processing/fields.dm @@ -2,5 +2,5 @@ PROCESSING_SUBSYSTEM_DEF(fields) name = "Fields" wait = 2 priority = FIRE_PRIORITY_FIELDS - flags = SS_KEEP_TIMING | SS_NO_INIT + flags = SS_KEEP_TIMING | SS_NO_INIT | SS_BACKGROUND runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME diff --git a/code/controllers/subsystem/processing/processing.dm b/code/controllers/subsystem/processing/processing.dm index 058645da495..dc1c3a317f3 100644 --- a/code/controllers/subsystem/processing/processing.dm +++ b/code/controllers/subsystem/processing/processing.dm @@ -3,7 +3,7 @@ SUBSYSTEM_DEF(processing) name = "Processing" priority = FIRE_PRIORITY_PROCESS - flags = SS_BACKGROUND|SS_POST_FIRE_TIMING|SS_NO_INIT + flags = SS_POST_FIRE_TIMING|SS_NO_INIT wait = 1 SECONDS var/stat_tag = "P" //Used for logging diff --git a/code/controllers/subsystem/processing/wet_floors.dm b/code/controllers/subsystem/processing/wet_floors.dm index 4bda8b54536..fbabdb830aa 100644 --- a/code/controllers/subsystem/processing/wet_floors.dm +++ b/code/controllers/subsystem/processing/wet_floors.dm @@ -3,5 +3,6 @@ PROCESSING_SUBSYSTEM_DEF(wet_floors) priority = FIRE_PRIORITY_WET_FLOORS wait = 10 stat_tag = "WFP" //Used for logging + flags = SS_BACKGROUND var/temperature_coeff = 2 var/time_ratio = 1.5 SECONDS