Skip to content

Commit

Permalink
MC: subsystem time dilation tracking, scheduling tweaks (Citadel-Stat…
Browse files Browse the repository at this point in the history
  • Loading branch information
silicons authored Jul 28, 2024
1 parent 8dba008 commit c007c1a
Show file tree
Hide file tree
Showing 16 changed files with 174 additions and 113 deletions.
6 changes: 6 additions & 0 deletions code/__DEFINES/MC.dm
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,16 @@
/// (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)
///
/// * Ticker is its own priority bucket. The highest one. Be careful.
/// * Ticker disables tick overrun punishment.
#define SS_TICKER (1<<4)

/** 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.
///
/// * This will only keep timing past the last 10 seconds, it will not attempt to catch the subsystem up without bounds.
/// * This disables tick overrun punishment.
#define SS_KEEP_TIMING (1<<5)

/** Calculate its next fire after its fired. */
Expand Down
59 changes: 30 additions & 29 deletions code/__DEFINES/controllers/_subsystems.dm
Original file line number Diff line number Diff line change
Expand Up @@ -155,14 +155,15 @@ DEFINE_BITFIELD(runlevels, list(
// Any ../subsystem/.. is here unless it doesn't have SS_BACKGROUND in subsystem_flags!
// This means by default, ../subsystem/processing/.. is here!

#define FIRE_PRIORITY_RADIATION 10 //! laggy as hell, bottom barrel until optimizations are done.
#define FIRE_PRIORITY_GARBAGE 15
#define FIRE_PRIORITY_CHARACTERS 25
#define FIRE_PRIORITY_PARALLAX 30
#define FIRE_PRIORITY_AIR 35
#define FIRE_PRIORITY_PROCESS 45
// DEFAULT PRIORITY IS HERE
#define FIRE_PRIORITY_PLANETS 75
#define FIRE_PRIORITY_RADIATION 10 //! laggy as hell, bottom barrel until optimizations are done.
#define FIRE_PRIORITY_GARBAGE 15
#define FIRE_PRIORITY_CHARACTERS 20
#define FIRE_PRIORITY_PARALLAX 20
#define FIRE_PRIORITY_AIR 25
#define FIRE_PRIORITY_ASSET_LOADING 25
#define FIRE_PRIORITY_PLANETS 25
#define FIRE_PRIORITY_PROCESS 50
// DEFAULT PRIORITY IS HERE (50)

//? Normal Subsystems - Above background, below ticker
// Any ../subsystem/.. without SS_TICKER or SS_BACKGROUND in subsystem_flags is here!
Expand All @@ -176,37 +177,37 @@ DEFINE_BITFIELD(runlevels, list(
#define FIRE_PRIORITY_SERVER_MAINT 10
#define FIRE_PRIORITY_ZMIMIC 10
#define FIRE_PRIORITY_ALARMS 20
#define FIRE_PRIORITY_AIRFLOW 20
#define FIRE_PRIORITY_SPACEDRIFT 25
#define FIRE_PRIORITY_AIRFLOW 30
#define FIRE_PRIORITY_OBJ 40
// DEFAULT PRIORITY IS HERE
// DEFAULT PRIORITY IS HERE (50)
#define FIRE_PRIORITY_LIGHTING 50
#define FIRE_PRIORITY_INSTRUMENTS 90
#define FIRE_PRIORITY_ASSET_LOADING 100
#define FIRE_PRIORITY_MACHINES 100
#define FIRE_PRIORITY_AI_MOVEMENT 150
#define FIRE_PRIORITY_AI_SCHEDULING 150
#define FIRE_PRIORITY_AI_HOLDERS 150
#define FIRE_PRIORITY_NANO 150
#define FIRE_PRIORITY_AI 200
#define FIRE_PRIORITY_TGUI 200
#define FIRE_PRIORITY_PROJECTILES 200
#define FIRE_PRIORITY_THROWING 200
#define FIRE_PRIORITY_STATPANELS 400
#define FIRE_PRIORITY_OVERLAYS 500
#define FIRE_PRIORITY_SMOOTHING 500
#define FIRE_PRIORITY_CHAT 500
#define FIRE_PRIORITY_INPUT 1000
#define FIRE_PRIORITY_INSTRUMENTS 50
#define FIRE_PRIORITY_MACHINES 50
#define FIRE_PRIORITY_AI 65
#define FIRE_PRIORITY_AI_HOLDERS 65
#define FIRE_PRIORITY_AI_MOVEMENT 75
#define FIRE_PRIORITY_AI_SCHEDULING 75
#define FIRE_PRIORITY_NANO 80
#define FIRE_PRIORITY_TGUI 80
#define FIRE_PRIORITY_OVERMAP_PHYSICS 90
#define FIRE_PRIORITY_PROJECTILES 90
#define FIRE_PRIORITY_THROWING 90
#define FIRE_PRIORITY_STATPANELS 100
#define FIRE_PRIORITY_OVERLAYS 100
#define FIRE_PRIORITY_SMOOTHING 100
#define FIRE_PRIORITY_CHAT 100
#define FIRE_PRIORITY_INPUT 100

//? Ticker Subsystems - Highest priority
// Any subsystem flagged with SS_TICKER is here!
// Do not unnecessarily set your subsystem as TICKER.
// Is your feature as important as movement, chat, or timers?
// Probably not! Go to normal bracket instead!

// DEFAULT PRIORITY IS HERE
#define FIRE_PRIORITY_DPC 700
#define FIRE_PRIORITY_TIMER 700
// DEFAULT PRIORITY IS HERE (50)
#define FIRE_PRIORITY_DPC 100
#define FIRE_PRIORITY_TIMER 100

//? Special

Expand Down
105 changes: 55 additions & 50 deletions code/controllers/master.dm
Original file line number Diff line number Diff line change
Expand Up @@ -52,18 +52,6 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
/// The type of the last subsystem to be process()'d.
var/last_type_processed

/// Start of queue linked list.
var/datum/controller/subsystem/queue_head

/// End of queue linked list (used for appending to the list).
var/datum/controller/subsystem/queue_tail

/// Running total so that we don't have to loop thru the queue each run to split up the tick.
var/queue_priority_count = 0

/// Total background subsystems in the queue.
var/queue_priority_count_bg = 0

/// For scheduling different subsystems for different stages of the round.
var/current_runlevel

Expand All @@ -75,13 +63,24 @@ GLOBAL_REAL(Master, /datum/controller/master) = new

var/static/random_seed

//* Processing Variables *//

/// total fire_priority of all non-background subsystems in the queue
var/queue_priority_count = 0
/// total fire_priority of all background subsystems in the queue
var/queue_priority_count_bg = 0

/// Start of queue linked list.
var/datum/controller/subsystem/queue_head
/// End of queue linked list (used for appending to the list).
var/datum/controller/subsystem/queue_tail

/**
* 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


/datum/controller/master/New()
//# 1. load configs
if(!config_legacy)
Expand Down Expand Up @@ -302,7 +301,6 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
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.
*/
Expand Down Expand Up @@ -349,6 +347,10 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
SS.queue_next = null
SS.queue_prev = null
SS.state = SS_IDLE

// Set precomputed variables
SS.recompute_wait_dt()

if (SS.subsystem_flags & SS_TICKER)
SStickersubsystems += SS
// Timer subsystems aren't allowed to bunch up, so we offset them a bit.
Expand Down Expand Up @@ -390,7 +392,6 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
iteration = 1
var/error_level = 0
var/sleep_delta = 1
var/list/subsystems_to_check

//# The actual loop.
while (1)
Expand Down Expand Up @@ -433,28 +434,24 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
new/datum/controller/failsafe() // (re)Start the failsafe.

//# Now do the actual stuff.
if (!queue_head || !(iteration % 3))
var/checking_runlevel = current_runlevel
if(cached_runlevel != checking_runlevel)
// Resechedule subsystems.
var/list/old_subsystems = current_runlevel_subsystems
cached_runlevel = checking_runlevel
current_runlevel_subsystems = runlevel_sorted_subsystems[cached_runlevel]

// Now we'll go through all the subsystems we want to offset and give them a next_fire.
for(var/datum/controller/subsystem/SS as anything in current_runlevel_subsystems)
// We only want to offset it if it's new and also behind.
if(SS.next_fire > world.time || (SS in old_subsystems))
continue

SS.next_fire = world.time + world.tick_lag * rand(0, DS2TICKS(min(SS.wait, 2 SECONDS)))
//* **Experimental**: Check every tick.
if(cached_runlevel != current_runlevel)
// Resechedule subsystems.
var/list/old_subsystems = current_runlevel_subsystems
cached_runlevel = current_runlevel
current_runlevel_subsystems = runlevel_sorted_subsystems[cached_runlevel]

// Now we'll go through all the subsystems we want to offset and give them a next_fire.
for(var/datum/controller/subsystem/SS as anything in current_runlevel_subsystems)
// We only want to offset it if it's new and also behind.
if(SS.next_fire > world.time || (SS in old_subsystems))
continue

subsystems_to_check = current_runlevel_subsystems
SS.next_fire = world.time + world.tick_lag * rand(0, DS2TICKS(min(SS.wait, 2 SECONDS)))

else
subsystems_to_check = SStickersubsystems

if (CheckQueue(subsystems_to_check) <= 0)
//* **Experimental**: Check every queue, every tick.
if (CheckQueue(current_runlevel_subsystems) <= 0 || CheckQueue(SStickersubsystems) <= 0)
if (!SoftReset(SStickersubsystems, runlevel_sorted_subsystems))
log_world("MC: SoftReset() failed, crashing")
return
Expand Down Expand Up @@ -550,7 +547,9 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
var/ran = TRUE // This is right.
var/ran_non_SSticker = FALSE
var/bg_calc // Have we swtiched current_tick_budget to background mode yet?
var/tick_usage

// the % of tick used by the current running subsystem
var/queue_node_tick_usage

/**
* Keep running while we have stuff to run and we haven't gone over a tick
Expand Down Expand Up @@ -609,63 +608,69 @@ GLOBAL_REAL(Master, /datum/controller/master) = new

queue_node.state = SS_RUNNING

tick_usage = TICK_USAGE
// ignite / fire the head node
queue_node_tick_usage = TICK_USAGE
var/state = queue_node.ignite(queue_node_paused)
tick_usage = TICK_USAGE - tick_usage
queue_node_tick_usage = TICK_USAGE - queue_node_tick_usage

if (state == SS_RUNNING)
state = SS_IDLE

current_tick_budget -= queue_node_priority


if (tick_usage < 0)
tick_usage = 0
if (queue_node_tick_usage < 0)
queue_node_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.tick_overrun = max(0, MC_AVG_FAST_UP_SLOW_DOWN(queue_node.tick_overrun, queue_node_tick_usage-tick_precentage))
queue_node.state = state

// if it paused mid-run, track that
if (state == SS_PAUSED)
queue_node.paused_ticks++
queue_node.paused_tick_usage += tick_usage
queue_node.paused_tick_usage += queue_node_tick_usage
queue_node = queue_node.queue_next
continue

// it did not pause; this is a complete run

queue_node.ticks = MC_AVERAGE(queue_node.ticks, queue_node.paused_ticks)
tick_usage += queue_node.paused_tick_usage
queue_node_tick_usage += queue_node.paused_tick_usage

queue_node.tick_usage = MC_AVERAGE_FAST(queue_node.tick_usage, tick_usage)
queue_node.tick_usage = MC_AVERAGE_FAST(queue_node.tick_usage, queue_node_tick_usage)

queue_node.cost = MC_AVERAGE_FAST(queue_node.cost, TICK_DELTA_TO_MS(tick_usage))
queue_node.cost = MC_AVERAGE_FAST(queue_node.cost, TICK_DELTA_TO_MS(queue_node_tick_usage))
queue_node.paused_ticks = 0
queue_node.paused_tick_usage = 0

if (queue_node_flags & SS_BACKGROUND) // 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++

// schedule next run
if (queue_node_flags & SS_TICKER)
// ticker: run this many ticks after always
queue_node.next_fire = world.time + (world.tick_lag * queue_node.wait)

else if (queue_node_flags & SS_POST_FIRE_TIMING)
// post fire timing: fire this much wait after current time, with tick overrun punishment
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

// keep timing: fire this much wait after *the last time we should have fired*, without tick overrun punishment
// **experimental**: do not keep timing past last 10 seconds, if something is running behind that much don't permanently accelerate it.
queue_node.next_fire = max(world.time - 10 SECONDS, queue_node.next_fire + queue_node.wait)
else
// normal: fire this much wait after when we were queued, with tick overrun punishment
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()

// move to next
queue_node = queue_node.queue_next

. = TRUE
Expand Down
Loading

0 comments on commit c007c1a

Please sign in to comment.