mirror of
https://github.com/ParadiseSS13/Paradise.git
synced 2026-07-18 02:24:11 +01:00
StonedMC
This commit ports the StonedMC from /tg/station, intended to replace the Process Scheduler from goon. Currently, they exist simultaneously, as it's very difficult to port our 22 processes to the SMC all at once. Instead, we can make them work together until everything is converted over at a later point, and then take the old PS out back and put a couple of rounds into it's deformed skull. Primary benefits of this new process controller include: Other people that can actually maintain it, unlike the PS, pre-world-init initialization for subsystems, ease of ports from /tg/station13, and potential performance improvement (to be seen).
This commit is contained in:
@@ -34,8 +34,6 @@
|
||||
/**
|
||||
* Config vars
|
||||
*/
|
||||
// Process name
|
||||
var/name
|
||||
|
||||
// Process schedule interval
|
||||
// This controls how often the process would run under ideal conditions.
|
||||
@@ -364,7 +362,7 @@
|
||||
var/highestRunTime = round(highest_run_time, 0.001)
|
||||
var/deferTime = round(cpu_defer_count / 10 * world.tick_lag, 0.01)
|
||||
if(!statclick)
|
||||
statclick = new (src)
|
||||
statclick = new /obj/effect/statclick/debug(src)
|
||||
stat("[name]", statclick.update("T#[getTicks()] | AR [averageRunTime] | LR [lastRunTime] | HR [highestRunTime] | D [deferTime]"))
|
||||
|
||||
/datum/controller/process/proc/catchException(var/exception/e, var/thrower)
|
||||
|
||||
@@ -232,7 +232,7 @@ var/global/datum/controller/processScheduler/processScheduler
|
||||
stat("Processes", "Scheduler not running")
|
||||
return
|
||||
if(!statclick)
|
||||
statclick = new (src)
|
||||
statclick = new /obj/effect/statclick/debug(src)
|
||||
stat("Processes", statclick.update("[processes.len] (R [running.len] / Q [queued.len] / I [idle.len])"))
|
||||
for(var/datum/controller/process/p in processes)
|
||||
p.statProcess()
|
||||
|
||||
@@ -189,6 +189,16 @@
|
||||
|
||||
var/disable_karma = 0 // Disable all karma functions and unlock karma jobs by default
|
||||
|
||||
// StonedMC
|
||||
var/tick_limit_mc_init = TICK_LIMIT_MC_INIT_DEFAULT //SSinitialization throttling
|
||||
|
||||
// Highpop tickrates
|
||||
var/base_mc_tick_rate = 1
|
||||
var/high_pop_mc_tick_rate = 1.1
|
||||
|
||||
var/high_pop_mc_mode_amount = 65
|
||||
var/disable_high_pop_mc_mode_amount = 60
|
||||
|
||||
/datum/configuration/New()
|
||||
var/list/L = subtypesof(/datum/game_mode)
|
||||
for(var/T in L)
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
/datum/controller
|
||||
var/name
|
||||
// The object used for the clickable stat() button.
|
||||
var/obj/effect/statclick/statclick
|
||||
|
||||
/datum/controller/proc/Initialize()
|
||||
|
||||
//cleanup actions
|
||||
/datum/controller/proc/Shutdown()
|
||||
|
||||
//when we enter dmm_suite.load_map
|
||||
/datum/controller/proc/StartLoadingMap()
|
||||
|
||||
//when we exit dmm_suite.load_map
|
||||
/datum/controller/proc/StopLoadingMap()
|
||||
|
||||
/datum/controller/proc/Recover()
|
||||
|
||||
/datum/controller/proc/stat_entry()
|
||||
@@ -1,39 +1,97 @@
|
||||
var/global/datum/controller/failsafe/failsafe
|
||||
var/global/datum/controller/failsafe/Failsafe
|
||||
|
||||
/datum/controller/failsafe // This thing pretty much just keeps poking the controllers.
|
||||
processing_interval = 100 // Poke the controllers every 10 seconds.
|
||||
|
||||
/*
|
||||
* Controller alert level.
|
||||
* For every poke that fails this is raised by 1.
|
||||
* When it reaches 5 the MC is replaced with a new one
|
||||
* (effectively killing any controller process() and starting a new one).
|
||||
*/
|
||||
/datum/controller/failsafe // This thing pretty much just keeps poking the master controller
|
||||
name = "Failsafe"
|
||||
|
||||
// master
|
||||
var/masterControllerIteration = 0
|
||||
var/masterControllerAlertLevel = 0
|
||||
// The length of time to check on the MC (in deciseconds).
|
||||
// Set to 0 to disable.
|
||||
var/processing_interval = 20
|
||||
// The alert level. For every failed poke, we drop a DEFCON level. Once we hit DEFCON 1, restart the MC.
|
||||
var/defcon = 5
|
||||
//the world.time of the last check, so the mc can restart US if we hang.
|
||||
// (Real friends look out for *eachother*)
|
||||
var/lasttick = 0
|
||||
|
||||
// Track the MC iteration to make sure its still on track.
|
||||
var/master_iteration = 0
|
||||
var/running = TRUE
|
||||
|
||||
/datum/controller/failsafe/New()
|
||||
. = ..()
|
||||
// Highlander-style: there can only be one! Kill off the old and replace it with the new.
|
||||
if(Failsafe != src)
|
||||
if(istype(Failsafe))
|
||||
qdel(Failsafe)
|
||||
Failsafe = src
|
||||
Initialize()
|
||||
|
||||
// There can be only one failsafe. Out with the old in with the new (that way we can restart the Failsafe by spawning a new one).
|
||||
if(failsafe != src)
|
||||
if(istype(failsafe))
|
||||
recover()
|
||||
qdel(failsafe)
|
||||
/datum/controller/failsafe/Initialize()
|
||||
set waitfor = 0
|
||||
Failsafe.Loop()
|
||||
if(!qdeleted(src))
|
||||
qdel(src) //when Loop() returns, we delete ourselves and let the mc recreate us
|
||||
|
||||
failsafe = src
|
||||
/datum/controller/failsafe/Destroy()
|
||||
running = FALSE
|
||||
..()
|
||||
return QDEL_HINT_HARDDEL_NOW
|
||||
|
||||
//failsafe.process()
|
||||
/datum/controller/failsafe/proc/Loop()
|
||||
while(running)
|
||||
lasttick = world.time
|
||||
if(!Master)
|
||||
// Replace the missing Master! This should never, ever happen.
|
||||
new /datum/controller/master()
|
||||
// Only poke it if overrides are not in effect.
|
||||
if(processing_interval > 0)
|
||||
if(Master.processing && Master.iteration)
|
||||
// Check if processing is done yet.
|
||||
if(Master.iteration == master_iteration)
|
||||
switch(defcon)
|
||||
if(4,5)
|
||||
--defcon
|
||||
if(3)
|
||||
message_admins("<span class='adminnotice'>Notice: DEFCON [defcon_pretty()]. The Master Controller has not fired in the last [(5-defcon) * processing_interval] ticks.</span>")
|
||||
--defcon
|
||||
if(2)
|
||||
to_chat(admins, "<span class='boldannounce'>Warning: DEFCON [defcon_pretty()]. The Master Controller has not fired in the last [(5-defcon) * processing_interval] ticks. Automatic restart in [processing_interval] ticks.</span>")
|
||||
--defcon
|
||||
if(1)
|
||||
|
||||
/datum/controller/failsafe/proc/process()
|
||||
processing = 1
|
||||
to_chat(admins, "<span class='boldannounce'>Warning: DEFCON [defcon_pretty()]. The Master Controller has still not fired within the last [(5-defcon) * processing_interval] ticks. Killing and restarting...</span>")
|
||||
--defcon
|
||||
var/rtn = Recreate_MC()
|
||||
if(rtn > 0)
|
||||
defcon = 4
|
||||
master_iteration = 0
|
||||
to_chat(admins, "<span class='adminnotice'>MC restarted successfully</span>")
|
||||
else if(rtn < 0)
|
||||
log_game("FailSafe: Could not restart MC, runtime encountered. Entering defcon 0")
|
||||
to_chat(admins, "<span class='boldannounce'>ERROR: DEFCON [defcon_pretty()]. Could not restart MC, runtime encountered. I will silently keep retrying.</span>")
|
||||
//if the return number was 0, it just means the mc was restarted too recently, and it just needs some time before we try again
|
||||
//no need to handle that specially when defcon 0 can handle it
|
||||
if(0) //DEFCON 0! (mc failed to restart)
|
||||
var/rtn = Recreate_MC()
|
||||
if(rtn > 0)
|
||||
defcon = 4
|
||||
master_iteration = 0
|
||||
to_chat(admins, "<span class='adminnotice'>MC restarted successfully</span>")
|
||||
else
|
||||
defcon = min(defcon + 1,5)
|
||||
master_iteration = Master.iteration
|
||||
if (defcon <= 1)
|
||||
sleep(processing_interval*2)
|
||||
else
|
||||
sleep(processing_interval)
|
||||
else
|
||||
defcon = 5
|
||||
sleep(initial(processing_interval))
|
||||
|
||||
spawn(0)
|
||||
set background = BACKGROUND_ENABLED
|
||||
/datum/controller/failsafe/proc/defcon_pretty()
|
||||
return defcon
|
||||
|
||||
while(1) // More efficient than recursivly calling ourself over and over. background = 1 ensures we do not trigger an infinite loop.
|
||||
iteration++
|
||||
/datum/controller/failsafe/stat_entry()
|
||||
if(!statclick)
|
||||
statclick = new/obj/effect/statclick/debug(src, "Initializing...")
|
||||
|
||||
sleep(processing_interval)
|
||||
stat("Failsafe Controller:", statclick.update("Defcon: [defcon_pretty()] (Interval: [Failsafe.processing_interval] | Iteration: [Failsafe.master_iteration])"))
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
GLOBAL_REAL(GLOB, /datum/controller/global_vars)
|
||||
|
||||
/datum/controller/global_vars
|
||||
name = "Global Variables"
|
||||
|
||||
var/list/gvars_datum_protected_varlist
|
||||
var/list/gvars_datum_in_built_vars
|
||||
var/list/gvars_datum_init_order
|
||||
|
||||
/datum/controller/global_vars/New()
|
||||
if(GLOB)
|
||||
CRASH("Multiple instances of global variable controller created")
|
||||
GLOB = src
|
||||
|
||||
var/datum/controller/exclude_these = new
|
||||
gvars_datum_in_built_vars = exclude_these.vars + list("gvars_datum_protected_varlist", "gvars_datum_in_built_vars", "gvars_datum_init_order")
|
||||
qdel(exclude_these)
|
||||
|
||||
log_to_dd("[vars.len - gvars_datum_in_built_vars.len] global variables")
|
||||
|
||||
Initialize()
|
||||
|
||||
/datum/controller/global_vars/Destroy(force)
|
||||
stack_trace("Some fucker qdel'd the global holder!")
|
||||
if(!force)
|
||||
return QDEL_HINT_LETMELIVE
|
||||
|
||||
QDEL_NULL(statclick)
|
||||
gvars_datum_protected_varlist.Cut()
|
||||
gvars_datum_in_built_vars.Cut()
|
||||
|
||||
GLOB = null
|
||||
|
||||
return ..()
|
||||
|
||||
/datum/controller/global_vars/stat_entry()
|
||||
if(!statclick)
|
||||
statclick = new/obj/effect/statclick/debug(src, "Initializing...")
|
||||
|
||||
stat("Globals:", statclick.update("Edit"))
|
||||
|
||||
/datum/controller/global_vars/can_vv_get(var_name)
|
||||
if(gvars_datum_protected_varlist[var_name])
|
||||
return FALSE
|
||||
return ..()
|
||||
|
||||
/datum/controller/global_vars/vv_edit_var(var_name, var_value)
|
||||
if(gvars_datum_protected_varlist[var_name])
|
||||
return FALSE
|
||||
return ..()
|
||||
|
||||
/datum/controller/global_vars/Initialize()
|
||||
gvars_datum_init_order = list()
|
||||
gvars_datum_protected_varlist = list("gvars_datum_protected_varlist" = TRUE)
|
||||
var/list/global_procs = typesof(/datum/controller/global_vars/proc)
|
||||
var/expected_len = vars.len - gvars_datum_in_built_vars.len
|
||||
if(global_procs.len != expected_len)
|
||||
warning("Unable to detect all global initialization procs! Expected [expected_len] got [global_procs.len]!")
|
||||
if(global_procs.len)
|
||||
var/list/expected_global_procs = vars - gvars_datum_in_built_vars
|
||||
for(var/I in global_procs)
|
||||
expected_global_procs -= replacetext("[I]", "InitGlobal", "")
|
||||
log_to_dd("Missing procs: [expected_global_procs.Join(", ")]")
|
||||
for(var/I in global_procs)
|
||||
var/start_tick = world.time
|
||||
call(src, I)()
|
||||
var/end_tick = world.time
|
||||
if(end_tick - start_tick)
|
||||
warning("Global [replacetext("[I]", "InitGlobal", "")] slept during initialization!")
|
||||
@@ -0,0 +1,611 @@
|
||||
/**
|
||||
* StonedMC
|
||||
*
|
||||
* Designed to properly split up a given tick among subsystems
|
||||
* Note: if you read parts of this code and think "why is it doing it that way"
|
||||
* Odds are, there is a reason
|
||||
*
|
||||
**/
|
||||
|
||||
//This is the ABSOLUTE ONLY THING that should init globally like this
|
||||
GLOBAL_REAL(Master, /datum/controller/master) = new
|
||||
|
||||
//THIS IS THE INIT ORDER
|
||||
//Master -> SSPreInit -> GLOB -> world -> config -> SSInit -> Failsafe
|
||||
//GOT IT MEMORIZED?
|
||||
|
||||
/datum/controller/master
|
||||
name = "Master"
|
||||
|
||||
// Are we processing (higher values increase the processing delay by n ticks)
|
||||
var/processing = TRUE
|
||||
// How many times have we ran
|
||||
var/iteration = 0
|
||||
|
||||
// world.time of last fire, for tracking lag outside of the mc
|
||||
var/last_run
|
||||
|
||||
// List of subsystems to process().
|
||||
var/list/subsystems
|
||||
|
||||
// Vars for keeping track of tick drift.
|
||||
var/init_timeofday
|
||||
var/init_time
|
||||
var/tickdrift = 0
|
||||
|
||||
var/sleep_delta = 1
|
||||
|
||||
var/make_runtime = 0
|
||||
|
||||
var/initializations_finished_with_no_players_logged_in //I wonder what this could be?
|
||||
|
||||
// The type of the last subsystem to be process()'d.
|
||||
var/last_type_processed
|
||||
|
||||
var/datum/controller/subsystem/queue_head //Start of queue linked list
|
||||
var/datum/controller/subsystem/queue_tail //End of queue linked list (used for appending to the list)
|
||||
var/queue_priority_count = 0 //Running total so that we don't have to loop thru the queue each run to split up the tick
|
||||
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
|
||||
var/sleep_offline_after_initializations = TRUE
|
||||
|
||||
var/static/restart_clear = 0
|
||||
var/static/restart_timeout = 0
|
||||
var/static/restart_count = 0
|
||||
|
||||
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
|
||||
var/static/current_ticklimit = TICK_LIMIT_RUNNING
|
||||
|
||||
/datum/controller/master/New()
|
||||
makeDatumRefLists()
|
||||
load_configuration()
|
||||
// Highlander-style: there can only be one! Kill off the old and replace it with the new.
|
||||
|
||||
if(!random_seed)
|
||||
random_seed = rand(1, 1e9)
|
||||
rand_seed(random_seed)
|
||||
|
||||
var/list/_subsystems = list()
|
||||
subsystems = _subsystems
|
||||
if (Master != src)
|
||||
if (istype(Master))
|
||||
Recover()
|
||||
qdel(Master)
|
||||
else
|
||||
var/list/subsytem_types = subtypesof(/datum/controller/subsystem)
|
||||
sortTim(subsytem_types, /proc/cmp_subsystem_init)
|
||||
for(var/I in subsytem_types)
|
||||
_subsystems += new I
|
||||
Master = src
|
||||
|
||||
if(!GLOB)
|
||||
new /datum/controller/global_vars
|
||||
|
||||
/datum/controller/master/Destroy()
|
||||
..()
|
||||
// Tell qdel() to Del() this object.
|
||||
return QDEL_HINT_HARDDEL_NOW
|
||||
|
||||
/datum/controller/master/Shutdown()
|
||||
processing = FALSE
|
||||
sortTim(subsystems, /proc/cmp_subsystem_init)
|
||||
reverseRange(subsystems)
|
||||
for(var/datum/controller/subsystem/ss in subsystems)
|
||||
log_to_dd("Shutting down [ss.name] subsystem...")
|
||||
ss.Shutdown()
|
||||
log_to_dd("Shutdown complete")
|
||||
|
||||
// Returns 1 if we created a new mc, 0 if we couldn't due to a recent restart,
|
||||
// -1 if we encountered a runtime trying to recreate it
|
||||
/proc/Recreate_MC()
|
||||
. = -1 //so if we runtime, things know we failed
|
||||
if (world.time < Master.restart_timeout)
|
||||
return 0
|
||||
if (world.time < Master.restart_clear)
|
||||
Master.restart_count *= 0.5
|
||||
|
||||
var/delay = 50 * ++Master.restart_count
|
||||
Master.restart_timeout = world.time + delay
|
||||
Master.restart_clear = world.time + (delay * 2)
|
||||
Master.processing = FALSE //stop ticking this one
|
||||
try
|
||||
new/datum/controller/master()
|
||||
catch
|
||||
return -1
|
||||
return 1
|
||||
|
||||
|
||||
/datum/controller/master/Recover()
|
||||
var/msg = "## DEBUG: [time2text(world.timeofday)] MC restarted. Reports:\n"
|
||||
for (var/varname in Master.vars)
|
||||
switch (varname)
|
||||
if("name", "tag", "bestF", "type", "parent_type", "vars", "statclick") // Built-in junk.
|
||||
continue
|
||||
else
|
||||
var/varval = Master.vars[varname]
|
||||
if (istype(varval, /datum)) // Check if it has a type var.
|
||||
var/datum/D = varval
|
||||
msg += "\t [varname] = [D]([D.type])\n"
|
||||
else
|
||||
msg += "\t [varname] = [varval]\n"
|
||||
log_to_dd(msg)
|
||||
|
||||
var/datum/controller/subsystem/BadBoy = Master.last_type_processed
|
||||
var/FireHim = FALSE
|
||||
if(istype(BadBoy))
|
||||
msg = null
|
||||
LAZYINITLIST(BadBoy.failure_strikes)
|
||||
switch(++BadBoy.failure_strikes[BadBoy.type])
|
||||
if(2)
|
||||
msg = "The [BadBoy.name] subsystem was the last to fire for 2 controller restarts. It will be recovered now and disabled if it happens again."
|
||||
FireHim = TRUE
|
||||
if(3)
|
||||
msg = "The [BadBoy.name] subsystem seems to be destabilizing the MC and will be offlined."
|
||||
BadBoy.flags |= SS_NO_FIRE
|
||||
if(msg)
|
||||
to_chat(admins, "<span class='boldannounce'>[msg]</span>")
|
||||
log_to_dd(msg)
|
||||
|
||||
if (istype(Master.subsystems))
|
||||
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, "<span class='boldannounce'>The Master Controller is having some issues, we will need to re-initialize EVERYTHING</span>")
|
||||
Initialize(20, TRUE)
|
||||
|
||||
|
||||
// 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)
|
||||
set waitfor = 0
|
||||
|
||||
if(delay)
|
||||
sleep(delay)
|
||||
|
||||
if(init_sss)
|
||||
init_subtypes(/datum/controller/subsystem, subsystems)
|
||||
|
||||
to_chat(world, "<span class='boldannounce'>Initializing subsystems...</span>")
|
||||
|
||||
// Sort subsystems by init_order, so they initialize in the correct order.
|
||||
sortTim(subsystems, /proc/cmp_subsystem_init)
|
||||
|
||||
var/start_timeofday = REALTIMEOFDAY
|
||||
// Initialize subsystems.
|
||||
current_ticklimit = config.tick_limit_mc_init
|
||||
for (var/datum/controller/subsystem/SS in subsystems)
|
||||
if (SS.flags & SS_NO_INIT)
|
||||
continue
|
||||
SS.Initialize(REALTIMEOFDAY)
|
||||
CHECK_TICK
|
||||
current_ticklimit = TICK_LIMIT_RUNNING
|
||||
var/time = (REALTIMEOFDAY - start_timeofday) / 10
|
||||
|
||||
var/msg = "Initializations complete within [time] second[time == 1 ? "" : "s"]!"
|
||||
to_chat(world, "<span class='boldannounce'>[msg]</span>")
|
||||
log_to_dd(msg)
|
||||
|
||||
if (!current_runlevel)
|
||||
SetRunLevel(1)
|
||||
|
||||
// Sort subsystems by display setting for easy access.
|
||||
sortTim(subsystems, /proc/cmp_subsystem_display)
|
||||
// Set world options.
|
||||
if(sleep_offline_after_initializations)
|
||||
world.sleep_offline = TRUE
|
||||
// world.fps = CONFIG_GET(number/fps) // TIGER TODO
|
||||
world.tick_lag = config.Ticklag
|
||||
var/initialized_tod = REALTIMEOFDAY
|
||||
sleep(1)
|
||||
initializations_finished_with_no_players_logged_in = initialized_tod < REALTIMEOFDAY - 10
|
||||
// Loop.
|
||||
Master.StartProcessing(0)
|
||||
|
||||
/datum/controller/master/proc/SetRunLevel(new_runlevel)
|
||||
var/old_runlevel = current_runlevel
|
||||
if(isnull(old_runlevel))
|
||||
old_runlevel = "NULL"
|
||||
|
||||
testing("MC: Runlevel changed from [old_runlevel] to [new_runlevel]")
|
||||
current_runlevel = log(2, new_runlevel) + 1
|
||||
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)
|
||||
set waitfor = 0
|
||||
if(delay)
|
||||
sleep(delay)
|
||||
testing("Master starting processing")
|
||||
var/rtn = Loop()
|
||||
if (rtn > 0 || processing < 0)
|
||||
return //this was suppose to happen.
|
||||
//loop ended, restart the mc
|
||||
log_game("MC crashed or runtimed, restarting")
|
||||
message_admins("MC crashed or runtimed, restarting")
|
||||
var/rtn2 = Recreate_MC()
|
||||
if (rtn2 <= 0)
|
||||
log_game("Failed to recreate MC (Error code: [rtn2]), it's up to the failsafe now")
|
||||
message_admins("Failed to recreate MC (Error code: [rtn2]), it's up to the failsafe now")
|
||||
Failsafe.defcon = 2
|
||||
|
||||
// Main loop.
|
||||
/datum/controller/master/proc/Loop()
|
||||
. = -1
|
||||
//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
|
||||
|
||||
//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/runlevel_sorted_subsystems = list(list(), list(), list(), list(), list(), list(), list(), list()) //ensure we always have as many runlevels as we need to operate with no subsystems (8 currently)
|
||||
var/timer = world.time
|
||||
for (var/thing in subsystems)
|
||||
var/datum/controller/subsystem/SS = thing
|
||||
if (SS.flags & SS_NO_FIRE)
|
||||
continue
|
||||
SS.queued_time = 0
|
||||
SS.queue_next = null
|
||||
SS.queue_prev = null
|
||||
SS.state = SS_IDLE
|
||||
if (SS.flags & SS_TICKER)
|
||||
tickersubsystems += SS
|
||||
timer += world.tick_lag * rand(1, 5)
|
||||
SS.next_fire = timer
|
||||
continue
|
||||
|
||||
var/ss_runlevels = SS.runlevels
|
||||
var/added_to_any = FALSE
|
||||
for(var/I in 1 to GLOB.bitflags.len)
|
||||
if(ss_runlevels & GLOB.bitflags[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)
|
||||
for(var/I in runlevel_sorted_subsystems)
|
||||
sortTim(runlevel_sorted_subsystems, /proc/cmp_subsystem_priority)
|
||||
I += tickersubsystems
|
||||
|
||||
var/cached_runlevel = current_runlevel
|
||||
var/list/current_runlevel_subsystems = runlevel_sorted_subsystems[cached_runlevel]
|
||||
|
||||
init_timeofday = REALTIMEOFDAY
|
||||
init_time = world.time
|
||||
|
||||
iteration = 1
|
||||
var/error_level = 0
|
||||
var/sleep_delta = 1
|
||||
var/list/subsystems_to_check
|
||||
//the actual loop.
|
||||
|
||||
while (1)
|
||||
tickdrift = max(0, MC_AVERAGE_FAST(tickdrift, (((REALTIMEOFDAY - init_timeofday) - (world.time - init_time)) / world.tick_lag)))
|
||||
var/starting_tick_usage = TICK_USAGE
|
||||
if (processing <= 0)
|
||||
current_ticklimit = TICK_LIMIT_RUNNING
|
||||
sleep(10)
|
||||
continue
|
||||
|
||||
//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)
|
||||
if (starting_tick_usage > TICK_LIMIT_MC) //if there isn't enough time to bother doing anything this tick, sleep a bit.
|
||||
sleep_delta *= 2
|
||||
current_ticklimit = TICK_LIMIT_RUNNING * 0.5
|
||||
sleep(world.tick_lag * (processing * sleep_delta))
|
||||
continue
|
||||
|
||||
//Byond resumed us late. assume it might have to do the same next tick
|
||||
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
|
||||
|
||||
if (!Failsafe || (Failsafe.processing_interval > 0 && (Failsafe.lasttick+(Failsafe.processing_interval*5)) < world.time))
|
||||
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
|
||||
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, runlevel_sorted_subsystems))
|
||||
log_to_dd("MC: SoftReset() failed, crashing")
|
||||
return
|
||||
if (!error_level)
|
||||
iteration++
|
||||
error_level++
|
||||
current_ticklimit = TICK_LIMIT_RUNNING
|
||||
sleep(10)
|
||||
continue
|
||||
|
||||
if (queue_head)
|
||||
if (RunQueue() <= 0)
|
||||
if (!SoftReset(tickersubsystems, runlevel_sorted_subsystems))
|
||||
log_to_dd("MC: SoftReset() failed, crashing")
|
||||
return
|
||||
if (!error_level)
|
||||
iteration++
|
||||
error_level++
|
||||
current_ticklimit = TICK_LIMIT_RUNNING
|
||||
sleep(10)
|
||||
continue
|
||||
error_level--
|
||||
if (!queue_head) //reset the counts if the queue is empty, in the off chance they get out of sync
|
||||
queue_priority_count = 0
|
||||
queue_priority_count_bg = 0
|
||||
|
||||
iteration++
|
||||
last_run = world.time
|
||||
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))
|
||||
|
||||
|
||||
|
||||
|
||||
// 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
|
||||
|
||||
for (var/thing in subsystemstocheck)
|
||||
if (!thing)
|
||||
subsystemstocheck -= thing
|
||||
SS = thing
|
||||
if (SS.state != SS_IDLE)
|
||||
continue
|
||||
if (SS.can_fire <= 0)
|
||||
continue
|
||||
if (SS.next_fire > world.time)
|
||||
continue
|
||||
SS_flags = SS.flags
|
||||
if (SS_flags & SS_NO_FIRE)
|
||||
subsystemstocheck -= SS
|
||||
continue
|
||||
if (!(SS_flags & SS_TICKER) && (SS_flags & SS_KEEP_TIMING) && SS.last_fire + (SS.wait * 0.75) > world.time)
|
||||
continue
|
||||
SS.enqueue()
|
||||
. = 1
|
||||
|
||||
|
||||
// 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/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
|
||||
|
||||
//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
|
||||
|
||||
//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)
|
||||
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 ((queue_node_flags & SS_BACKGROUND) && !bg_calc)
|
||||
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
|
||||
|
||||
|
||||
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 (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++
|
||||
|
||||
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
|
||||
|
||||
. = 1
|
||||
|
||||
//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_to_dd("MC: SoftReset called, resetting MC queue state.")
|
||||
if (!istype(subsystems) || !istype(ticker_SS) || !istype(runlevel_SS))
|
||||
log_to_dd("MC: SoftReset: Bad list contents: '[subsystems]' '[ticker_SS]' '[runlevel_SS]'")
|
||||
return
|
||||
var/subsystemstocheck = subsystems + ticker_SS
|
||||
for(var/I in runlevel_SS)
|
||||
subsystemstocheck |= I
|
||||
|
||||
for (var/thing in subsystemstocheck)
|
||||
var/datum/controller/subsystem/SS = thing
|
||||
if (!SS || !istype(SS))
|
||||
//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)
|
||||
for(var/I in runlevel_SS)
|
||||
I -= list(SS)
|
||||
log_to_dd("MC: SoftReset: Found bad entry in subsystem list, '[SS]'")
|
||||
continue
|
||||
if (SS.queue_next && !istype(SS.queue_next))
|
||||
log_to_dd("MC: SoftReset: Found bad data in subsystem queue, queue_next = '[SS.queue_next]'")
|
||||
SS.queue_next = null
|
||||
if (SS.queue_prev && !istype(SS.queue_prev))
|
||||
log_to_dd("MC: SoftReset: Found bad data in subsystem queue, queue_prev = '[SS.queue_prev]'")
|
||||
SS.queue_prev = null
|
||||
SS.queued_priority = 0
|
||||
SS.queued_time = 0
|
||||
SS.state = SS_IDLE
|
||||
if (queue_head && !istype(queue_head))
|
||||
log_to_dd("MC: SoftReset: Found bad data in subsystem queue, queue_head = '[queue_head]'")
|
||||
queue_head = null
|
||||
if (queue_tail && !istype(queue_tail))
|
||||
log_to_dd("MC: SoftReset: Found bad data in subsystem queue, queue_tail = '[queue_tail]'")
|
||||
queue_tail = null
|
||||
queue_priority_count = 0
|
||||
queue_priority_count_bg = 0
|
||||
log_to_dd("MC: SoftReset: Finished.")
|
||||
. = 1
|
||||
|
||||
|
||||
|
||||
/datum/controller/master/stat_entry()
|
||||
if(!statclick)
|
||||
statclick = new/obj/effect/statclick/debug(src, "Initializing...")
|
||||
|
||||
stat("Byond:", "(FPS:[world.fps]) (TickCount:[world.time/world.tick_lag]) (TickDrift:[round(Master.tickdrift,1)]([round((Master.tickdrift/(world.time/world.tick_lag))*100,0.1)]%))")
|
||||
stat("Master Controller:", statclick.update("(TickRate:[Master.processing]) (Iteration:[Master.iteration])"))
|
||||
|
||||
/datum/controller/master/StartLoadingMap()
|
||||
//disallow more than one map to load at once, multithreading it will just cause race conditions
|
||||
while(map_loading)
|
||||
stoplag()
|
||||
for(var/S in subsystems)
|
||||
var/datum/controller/subsystem/SS = S
|
||||
SS.StartLoadingMap()
|
||||
map_loading = TRUE
|
||||
|
||||
/datum/controller/master/StopLoadingMap(bounds = null)
|
||||
map_loading = FALSE
|
||||
for(var/S in subsystems)
|
||||
var/datum/controller/subsystem/SS = S
|
||||
SS.StopLoadingMap()
|
||||
|
||||
|
||||
/datum/controller/master/proc/UpdateTickRate()
|
||||
if (!processing)
|
||||
return
|
||||
var/client_count = length(clients)
|
||||
if (client_count < config.disable_high_pop_mc_mode_amount)
|
||||
processing = config.base_mc_tick_rate
|
||||
else if (client_count > config.high_pop_mc_mode_amount)
|
||||
processing = config.high_pop_mc_tick_rate
|
||||
@@ -1,6 +1,6 @@
|
||||
//simplified MC that is designed to fail when procs 'break'. When it fails it's just replaced with a new one.
|
||||
//It ensures master_controller.process() is never doubled up by killing the MC (hence terminating any of its sleeping procs)
|
||||
//WIP, needs lots of work still
|
||||
// old deprecated rusted piece of shit MC
|
||||
// All this does now is misc. world init stuff because too lazy to put it somewhere else
|
||||
// It used to run all of the repeating processes controlling the game but now the SMC and Process Scheduler do that
|
||||
|
||||
var/global/datum/controller/game_controller/master_controller //Set in world.New()
|
||||
|
||||
@@ -10,16 +10,6 @@ var/global/last_tick_duration = 0
|
||||
var/global/air_processing_killed = 0
|
||||
var/global/pipe_processing_killed = 0
|
||||
|
||||
/datum/controller
|
||||
var/processing = 0
|
||||
var/iteration = 0
|
||||
var/processing_interval = 0
|
||||
|
||||
// Dummy object to let us click it to debug while in the stat panel
|
||||
var/obj/effect/statclick/debug/statclick
|
||||
|
||||
/datum/controller/proc/recover() // If we are replacing an existing controller (due to a crash) we attempt to preserve as much as we can.
|
||||
|
||||
/datum/controller/game_controller
|
||||
var/list/shuttle_list // For debugging and VV
|
||||
|
||||
@@ -46,8 +36,6 @@ var/global/pipe_processing_killed = 0
|
||||
return QDEL_HINT_HARDDEL_NOW
|
||||
|
||||
/datum/controller/game_controller/proc/setup()
|
||||
world.tick_lag = config.Ticklag
|
||||
|
||||
preloadTemplates()
|
||||
if(!config.disable_away_missions)
|
||||
createRandomZlevel()
|
||||
|
||||
@@ -0,0 +1,215 @@
|
||||
|
||||
/datum/controller/subsystem
|
||||
// Metadata; you should define these.
|
||||
name = "fire coderbus" //name of the subsystem
|
||||
var/init_order = INIT_ORDER_DEFAULT //order of initialization. Higher numbers are initialized first, lower numbers later. Use defines in __DEFINES/subsystems.dm for easy understanding of order.
|
||||
var/wait = 20 //time to wait (in deciseconds) between each call to fire(). Must be a positive integer.
|
||||
var/priority = FIRE_PRIORITY_DEFAULT //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/initialized = FALSE //set to TRUE after it has been initialized, will obviously never be set if the subsystem doesn't initialize
|
||||
|
||||
//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
|
||||
var/can_fire = TRUE
|
||||
|
||||
// Bookkeeping variables; probably shouldn't mess with these.
|
||||
var/last_fire = 0 //last world.time we called fire()
|
||||
var/next_fire = 0 //scheduled world.time for next fire()
|
||||
var/cost = 0 //average time to execute
|
||||
var/tick_usage = 0 //average tick usage
|
||||
var/tick_overrun = 0 //average tick overrun
|
||||
var/state = SS_IDLE //tracks the current state of the ss, running, paused, etc.
|
||||
var/paused_ticks = 0 //ticks this ss is taking to run right now.
|
||||
var/paused_tick_usage //total tick_usage of all of our runs while pausing this run
|
||||
var/ticks = 1 //how many ticks does this ss take to run on avg.
|
||||
var/times_fired = 0 //number of times we have called fire()
|
||||
var/queued_time = 0 //time we entered the queue, (for timing and priority reasons)
|
||||
var/queued_priority //we keep a running total to make the math easier, if priority changes mid-fire that would break our running total, so we store it here
|
||||
//linked list stuff for the queue
|
||||
var/datum/controller/subsystem/queue_next
|
||||
var/datum/controller/subsystem/queue_prev
|
||||
|
||||
var/runlevels = RUNLEVELS_DEFAULT //points of the game at which the SS can fire
|
||||
|
||||
var/static/list/failure_strikes //How many times we suspect a subsystem type has crashed the MC, 3 strikes and you're out!
|
||||
|
||||
//Do not override
|
||||
///datum/controller/subsystem/New()
|
||||
|
||||
// Used to initialize the subsystem BEFORE the map has loaded
|
||||
// Called AFTER Recover if that is called
|
||||
// Prefer to use Initialize if possible
|
||||
/datum/controller/subsystem/proc/PreInit()
|
||||
return
|
||||
|
||||
//This is used so the mc knows when the subsystem sleeps. do not override.
|
||||
/datum/controller/subsystem/proc/ignite(resumed = 0)
|
||||
set waitfor = 0
|
||||
. = SS_SLEEPING
|
||||
fire(resumed)
|
||||
. = state
|
||||
if (state == SS_SLEEPING)
|
||||
state = SS_IDLE
|
||||
if (state == SS_PAUSING)
|
||||
var/QT = queued_time
|
||||
enqueue()
|
||||
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.
|
||||
/datum/controller/subsystem/proc/fire(resumed = 0)
|
||||
flags |= SS_NO_FIRE
|
||||
throw EXCEPTION("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.")
|
||||
|
||||
/datum/controller/subsystem/Destroy()
|
||||
dequeue()
|
||||
can_fire = 0
|
||||
flags |= SS_NO_FIRE
|
||||
Master.subsystems -= src
|
||||
return ..()
|
||||
|
||||
//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
|
||||
var/datum/controller/subsystem/queue_node
|
||||
var/queue_node_priority
|
||||
var/queue_node_flags
|
||||
|
||||
for (queue_node = Master.queue_head; queue_node; queue_node = queue_node.queue_next)
|
||||
queue_node_priority = queue_node.queued_priority
|
||||
queue_node_flags = queue_node.flags
|
||||
|
||||
if (queue_node_flags & SS_TICKER)
|
||||
if (!(SS_flags & SS_TICKER))
|
||||
continue
|
||||
if (queue_node_priority < SS_priority)
|
||||
break
|
||||
|
||||
else if (queue_node_flags & SS_BACKGROUND)
|
||||
if (!(SS_flags & SS_BACKGROUND))
|
||||
break
|
||||
if (queue_node_priority < SS_priority)
|
||||
break
|
||||
|
||||
else
|
||||
if (SS_flags & SS_BACKGROUND)
|
||||
continue
|
||||
if (SS_flags & SS_TICKER)
|
||||
break
|
||||
if (queue_node_priority < SS_priority)
|
||||
break
|
||||
|
||||
queued_time = world.time
|
||||
queued_priority = SS_priority
|
||||
state = SS_QUEUED
|
||||
if (SS_flags & SS_BACKGROUND) //update our running total
|
||||
Master.queue_priority_count_bg += SS_priority
|
||||
else
|
||||
Master.queue_priority_count += SS_priority
|
||||
|
||||
queue_next = queue_node
|
||||
if (!queue_node)//we stopped at the end, add to tail
|
||||
queue_prev = Master.queue_tail
|
||||
if (Master.queue_tail)
|
||||
Master.queue_tail.queue_next = src
|
||||
else //empty queue, we also need to set the head
|
||||
Master.queue_head = src
|
||||
Master.queue_tail = src
|
||||
|
||||
else if (queue_node == Master.queue_head)//insert at start of list
|
||||
Master.queue_head.queue_prev = src
|
||||
Master.queue_head = src
|
||||
queue_prev = null
|
||||
else
|
||||
queue_node.queue_prev.queue_next = src
|
||||
queue_prev = queue_node.queue_prev
|
||||
queue_node.queue_prev = src
|
||||
|
||||
|
||||
/datum/controller/subsystem/proc/dequeue()
|
||||
if (queue_next)
|
||||
queue_next.queue_prev = queue_prev
|
||||
if (queue_prev)
|
||||
queue_prev.queue_next = queue_next
|
||||
if (src == Master.queue_tail)
|
||||
Master.queue_tail = queue_prev
|
||||
if (src == Master.queue_head)
|
||||
Master.queue_head = queue_next
|
||||
queued_time = 0
|
||||
if (state == SS_QUEUED)
|
||||
state = SS_IDLE
|
||||
|
||||
|
||||
/datum/controller/subsystem/proc/pause()
|
||||
. = 1
|
||||
switch(state)
|
||||
if(SS_RUNNING)
|
||||
state = SS_PAUSED
|
||||
if(SS_SLEEPING)
|
||||
state = SS_PAUSING
|
||||
|
||||
|
||||
//used to initialize the subsystem AFTER the map has loaded
|
||||
/datum/controller/subsystem/Initialize(start_timeofday)
|
||||
initialized = TRUE
|
||||
var/time = (REALTIMEOFDAY - start_timeofday) / 10
|
||||
var/msg = "Initialized [name] subsystem within [time] second[time == 1 ? "" : "s"]!"
|
||||
to_chat(world, "<span class='boldannounce'>[msg]</span>")
|
||||
log_to_dd(msg)
|
||||
return time
|
||||
|
||||
//hook for printing stats to the "MC" statuspanel for admins to see performance and related stats etc.
|
||||
/datum/controller/subsystem/stat_entry(msg)
|
||||
if(!statclick)
|
||||
statclick = new/obj/effect/statclick/debug(src, "Initializing...")
|
||||
|
||||
if(can_fire && !(SS_NO_FIRE & flags))
|
||||
msg = "[round(cost,1)]ms|[round(tick_usage,1)]%([round(tick_overrun,1)]%)|[round(ticks,0.1)]\t[msg]"
|
||||
else
|
||||
msg = "OFFLINE\t[msg]"
|
||||
|
||||
var/title = name
|
||||
if (can_fire)
|
||||
title = "\[[state_letter()]][title]"
|
||||
|
||||
stat(title, statclick.update(msg))
|
||||
|
||||
/datum/controller/subsystem/proc/state_letter()
|
||||
switch (state)
|
||||
if (SS_RUNNING)
|
||||
. = "R"
|
||||
if (SS_QUEUED)
|
||||
. = "Q"
|
||||
if (SS_PAUSED, SS_PAUSING)
|
||||
. = "P"
|
||||
if (SS_SLEEPING)
|
||||
. = "S"
|
||||
if (SS_IDLE)
|
||||
. = " "
|
||||
|
||||
//could be used to postpone a costly subsystem for (default one) var/cycles, cycles
|
||||
//for instance, during cpu intensive operations like explosions
|
||||
/datum/controller/subsystem/proc/postpone(cycles = 1)
|
||||
if(next_fire - world.time < wait)
|
||||
next_fire += (wait*cycles)
|
||||
|
||||
//usually called via datum/controller/subsystem/New() when replacing a subsystem (i.e. due to a recurring crash)
|
||||
//should attempt to salvage what it can from the old instance of subsystem
|
||||
/datum/controller/subsystem/Recover()
|
||||
|
||||
/datum/controller/subsystem/vv_edit_var(var_name, var_value)
|
||||
switch (var_name)
|
||||
if ("can_fire")
|
||||
//this is so the subsystem doesn't rapid fire to make up missed ticks causing more lag
|
||||
if (var_value)
|
||||
next_fire = world.time + wait
|
||||
if ("queued_priority") //editing this breaks things.
|
||||
return 0
|
||||
. = ..()
|
||||
@@ -17,7 +17,10 @@
|
||||
message_admins("Admin [key_name_admin(usr)] has restarted the [controller] controller.")
|
||||
return
|
||||
|
||||
/client/proc/debug_controller(controller in list("Master","failsafe","Ticker","Air","Jobs","Sun","Radio","Configuration","pAI", "Cameras","Garbage", "Transfer Controller","Event","Alarm","Scheduler","Nano","Vote","Diseases","Fires","Mob","NPC AI","Shuttle","Timer","Weather","Space","Mob Hunt Server"))
|
||||
/client/proc/debug_controller(controller in list("Master",
|
||||
"failsafe","Scheduler","StonedMaster","Ticker","Air","Jobs","Sun","Radio","Configuration","pAI",
|
||||
"Cameras","Garbage", "Transfer Controller","Event","Alarm","Nano","Vote","Fires",
|
||||
"Mob","NPC AI","Shuttle","Timer","Weather","Space","Mob Hunt Server"))
|
||||
set category = "Debug"
|
||||
set name = "Debug Controller"
|
||||
set desc = "Debug the various periodic loop controllers for the game (be careful!)"
|
||||
@@ -28,8 +31,14 @@
|
||||
debug_variables(master_controller)
|
||||
feedback_add_details("admin_verb","DMC")
|
||||
if("failsafe")
|
||||
debug_variables(failsafe)
|
||||
debug_variables(Failsafe)
|
||||
feedback_add_details("admin_verb", "dfailsafe")
|
||||
if("Scheduler")
|
||||
debug_variables(processScheduler)
|
||||
feedback_add_details("admin_verb","DprocessScheduler")
|
||||
if("StonedMaster")
|
||||
debug_variables(Master)
|
||||
feedback_add_details("admin_verb","Dsmc")
|
||||
if("Ticker")
|
||||
debug_variables(ticker)
|
||||
feedback_add_details("admin_verb","DTicker")
|
||||
@@ -63,9 +72,6 @@
|
||||
if("Garbage")
|
||||
debug_variables(garbageCollector)
|
||||
feedback_add_details("admin_verb","DGarbage")
|
||||
if("Scheduler")
|
||||
debug_variables(processScheduler)
|
||||
feedback_add_details("admin_verb","DprocessScheduler")
|
||||
if("Nano")
|
||||
debug_variables(nanomanager)
|
||||
feedback_add_details("admin_verb","DNano")
|
||||
|
||||
Reference in New Issue
Block a user