* sdf

* fsda

* fuck

* fuck2

* toolz

* sdaf

* sdfa

* saf

* sdfa

* sdfa

* sdf

* sdfa

* temp rename

* temp rename

* temp rename

* sdaf

* the pain is immensurable in the land of byond

* the curse of rah

* safd

* sadf

* sadf

* gf

* asf

* fssdfa

* sfd

* sadf

* sfda

* brah

* brah

* it's easier for you to fix this

* ffs

* brah

* brah
This commit is contained in:
Fluffy
2024-01-06 17:03:57 +01:00
committed by GitHub
parent 8c76cfa9ea
commit e867030c2e
331 changed files with 2348 additions and 1643 deletions
-121
View File
@@ -1,121 +0,0 @@
#define FAILSAFE_MSG(msg) admin_notice("<big><em><span class='warning'>FAILSAFE: </span><font color='#ff8800'>[msg]</font></em></big>", R_DEBUG|R_ADMIN|R_DEV)
var/datum/controller/failsafe/Failsafe
/**
* #Failsafe Controller
*
* Pretty much pokes the MC to make sure it's still alive.
*/
/datum/controller/failsafe // This thing pretty much just keeps poking the master controller
name = "Failsafe"
// 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
/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()
/datum/controller/failsafe/Initialize()
set waitfor = 0
Failsafe.Loop()
qdel(Failsafe) //when Loop() returns, we delete ourselves and let the mc recreate us
/datum/controller/failsafe/Destroy()
..()
return QDEL_HINT_HARDDEL_NOW
/datum/controller/failsafe/proc/Loop()
while(1)
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)
FAILSAFE_MSG("Notice: DEFCON [defcon_pretty()]. The Master Controller has not fired in the last [(5-defcon) * processing_interval] ticks.")
--defcon
if(2)
FAILSAFE_MSG("Warning: DEFCON [defcon_pretty()]. The Master Controller has not fired in the last [(5-defcon) * processing_interval] ticks. Automatic restart in [processing_interval] ticks.")
--defcon
if(1)
FAILSAFE_MSG("Warning: DEFCON [defcon_pretty()]. The Master Controller has still not fired within the last [(5-defcon) * processing_interval] ticks. Killing and restarting...")
log_failsafe("MC has not fired within last [(5-defcon) * processing_interval] ticks, killing and restarting.")
--defcon
//Do not restart the MC if we are doing a REFERENCE_TRACKING hard lookup
#if !defined(GC_FAILURE_HARD_LOOKUP)
var/rtn = Recreate_MC()
#else
log_failsafe("MC was not actually recreated, because we're compiled with GC_FAILURE_HARD_LOOKUP.")
FAILSAFE_MSG("MC was not actually recreated, because we're compiled with GC_FAILURE_HARD_LOOKUP.")
var/rtn = TRUE
#endif
if(rtn > 0)
defcon = 4
master_iteration = 0
log_failsafe("MC restarted successfully.")
FAILSAFE_MSG("MC restarted successfully!")
else if(rtn < 0)
log_failsafe("Could not restart MC, runtime encountered. Entering defcon 0!")
FAILSAFE_MSG("ERROR: DEFCON [defcon_pretty()]. Unable to restart MC, runtime encountered. Silently retrying.")
//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)
//Do not restart the MC if we are doing a REFERENCE_TRACKING hard lookup
#if !defined(GC_FAILURE_HARD_LOOKUP)
var/rtn = Recreate_MC()
#else
var/rtn = TRUE
log_failsafe("MC was not actually recreated, because we're compiled with GC_FAILURE_HARD_LOOKUP.")
FAILSAFE_MSG("MC was not actually recreated, because we're compiled with GC_FAILURE_HARD_LOOKUP.")
#endif
if(rtn > 0)
defcon = 4
master_iteration = 0
log_failsafe("MC restarted successfully.")
FAILSAFE_MSG("MC restarted successfully.")
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))
/datum/controller/failsafe/proc/defcon_pretty()
return defcon
/datum/controller/failsafe/stat_entry(msg)
msg = "Defcon: [defcon_pretty()] (Interval: [Failsafe.processing_interval] | Iteration: [Failsafe.master_iteration])"
return msg
#undef FAILSAFE_MSG
-673
View File
@@ -1,673 +0,0 @@
var/datum/controller/master/Master = new()
//current tick limit, assigned by the queue controller before running a subsystem.
//used by check_tick as well so that the procs subsystems call can obey that SS's tick limits
var/CURRENT_TICKLIMIT = TICK_LIMIT_RUNNING
/**
* 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
*/
/datum/controller/master
name = "Master"
// Are we processing (higher values increase the processing delay by n ticks)
var/processing = 1
// 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
/// Most recent init stage to complete init.
var/static/init_stage_completed
// Vars for keeping track of tick drift.
var/init_timeofday
var/init_time
var/tickdrift = 0
var/sleep_delta = 1
/// Only run ticker subsystems for the next n ticks
var/skip_ticks = 0
var/make_runtime = 0
var/initialization_time_taken
var/initializations_finished_with_no_players_logged_in //I wonder what this could be?
var/current_runlevel // scheduling SS for different stages of the round
var/initializing = FALSE
// 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/static/restart_clear = 0
var/static/restart_timeout = 0
var/static/restart_count = 0
/datum/controller/master/New()
// Highlander-style: there can only be one! Kill off the old and replace it with the new.
subsystems = list()
if (Master != src)
if (istype(Master))
Recover()
qdel(Master)
else
init_subtypes(/datum/controller/subsystem, subsystems)
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
for(var/datum/controller/subsystem/ss in subsystems)
ss.Shutdown()
// 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 = 0 //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_subsystem_mastercontroller(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 we are running a REFERENCE_TRACKING with hard lookups, this is expected and we do not want the master controller
//to stop the garbage collector from working
#if !defined(GC_FAILURE_HARD_LOOKUP)
if(3)
msg = "The [BadBoy.name] subsystem seems to be destabilizing the MC and will be offlined."
BadBoy.flags |= SS_NO_FIRE
#endif
if(msg)
admin_notice("<span class='danger'>[msg]</span>", R_DEBUG | R_DEV)
log_subsystem_mastercontroller(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='danger'><big>The Master Controller is having some issues, we will need to re-initialize EVERYTHING</big></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)
world.log << "Initializing subsystems..."
log_subsystem_mastercontroller("Initializing subsystems...")
initializing = TRUE
init_stage_completed = 0
var/mc_started = FALSE
var/start_timeofday = REALTIMEOFDAY
// Initialize subsystems.
var/list/stage_sorted_subsystems = new(INITSTAGE_MAX)
for (var/i in 1 to INITSTAGE_MAX)
stage_sorted_subsystems[i] = list()
// Sort subsystems by init_order, so they initialize in the correct order.
sortTim(subsystems, GLOBAL_PROC_REF(cmp_subsystem_init))
for (var/datum/controller/subsystem/subsystem as anything in subsystems)
var/subsystem_init_stage = subsystem.init_stage
if (!isnum(subsystem_init_stage) || subsystem_init_stage < 1 || subsystem_init_stage > INITSTAGE_MAX || round(subsystem_init_stage) != subsystem_init_stage)
stack_trace("ERROR: MC: subsystem `[subsystem.type]` has invalid init_stage: `[subsystem_init_stage]`. Setting to `[INITSTAGE_MAX]`")
subsystem_init_stage = subsystem.init_stage = INITSTAGE_MAX
stage_sorted_subsystems[subsystem_init_stage] += subsystem
CURRENT_TICKLIMIT = TICK_LIMIT_MC_INIT
for(var/current_init_stage in 1 to INITSTAGE_MAX)
for (var/datum/controller/subsystem/SS in stage_sorted_subsystems[current_init_stage])
if (SS.flags & SS_NO_INIT)
continue
SS.StartInitialize(REALTIMEOFDAY)
CHECK_TICK
init_stage_completed = current_init_stage
if (!mc_started)
mc_started = TRUE
if (!current_runlevel)
SetRunLevel(1) // Intentionally not using the defines here because the MC doesn't care about them
// Loop.
Master.StartProcessing(0)
CURRENT_TICKLIMIT = TICK_LIMIT_RUNNING
var/time = (REALTIMEOFDAY - start_timeofday) / 10
initializing = FALSE
initialization_time_taken = time
var/msg = "Initializations complete within [time] second\s!"
log_subsystem_mastercontroller(msg)
admin_notice(SPAN_DANGER(msg), R_DEBUG)
world.log << msg
SetRunLevel(RUNLEVEL_LOBBY)
// Sort subsystems by display setting for easy access.
sortTim(subsystems, GLOBAL_PROC_REF(cmp_subsystem_display))
// Set world options.
#ifndef UNIT_TEST
world.sleep_offline = 1
#endif
world.TgsInitializationComplete()
world.change_tick_lag(GLOB.config.Ticklag)
var/initialized_tod = REALTIMEOFDAY
sleep(1)
initializations_finished_with_no_players_logged_in = initialized_tod < REALTIMEOFDAY - 10
// Loop.
Master.StartProcessing(10)
/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)
var/started_stage
var/rtn = -2
do
started_stage = init_stage_completed
rtn = Loop(started_stage)
while (rtn == MC_LOOP_RTN_NEWSTAGES && processing > 0 && started_stage < init_stage_completed)
if (rtn >= MC_LOOP_RTN_GRACEFUL_EXIT || 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(init_stage)
. = -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())
var/timer = world.time
for (var/thing in subsystems)
var/datum/controller/subsystem/SS = thing
if (SS.flags & SS_NO_FIRE)
continue
if (SS.init_stage > init_stage)
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(0, 1)
SS.next_fire = timer
continue
var/ss_runlevels = SS.runlevels
var/added_to_any = FALSE
for(var/I in 1 to bitflags.len)
if(ss_runlevels & 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, GLOBAL_PROC_REF(cmp_subsystem_priority))
for(var/level in runlevel_sorted_subsystems)
sortTim(level, GLOBAL_PROC_REF(cmp_subsystem_priority))
level += 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 = world.tick_usage
if (init_stage != init_stage_completed)
return MC_LOOP_RTN_NEWSTAGES
if (processing <= 0)
CURRENT_TICKLIMIT = TICK_LIMIT_RUNNING
sleep(1 SECONDS)
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 (init_stage == INITSTAGE_MAX)
if (starting_tick_usage > TICK_LIMIT_MC)
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
else
sleep_delta = 1
// debug
if (make_runtime)
var/datum/controller/subsystem/SS
SS.can_fire = 0
// Check the failsafe's still alive.
if (!Failsafe || (Failsafe.processing_interval > 0 && (Failsafe.lasttick+(Failsafe.processing_interval*5)) < world.time))
new/datum/controller/failsafe() // (re)Start the failsafe.
if (!skip_ticks)
var/checking_runlevel = current_runlevel
if(cached_runlevel != checking_runlevel)
// reschedule subsystems
var/list/old_subsystems = current_runlevel_subsystems
cached_runlevel = checking_runlevel
current_runlevel_subsystems = runlevel_sorted_subsystems[cached_runlevel]
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)))
subsystems_to_check = current_runlevel_subsystems
else
subsystems_to_check = tickersubsystems
if (CheckQueue(subsystems_to_check) <= 0)
if (!SoftReset(tickersubsystems, runlevel_sorted_subsystems))
log_subsystem_mastercontroller("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_subsystem_mastercontroller("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
if(skip_ticks)
skip_ticks--
src.sleep_delta = MC_AVERAGE_FAST(src.sleep_delta, sleep_delta)
if (init_stage != INITSTAGE_MAX)
CURRENT_TICKLIMIT = TICK_LIMIT_RUNNING * 2
else
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.suspended > 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_KEEP_TIMING)) == 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 && world.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 && world.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 - world.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 - world.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(world.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 = world.tick_usage
var/state = queue_node.ignite(queue_node_paused)
tick_usage = world.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++
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_subsystem_mastercontroller("SoftReset called, resetting MC queue state.")
if (!istype(subsystems) || !istype(ticker_SS) || !istype(runlevel_SS))
log_subsystem_mastercontroller("SoftReset: Bad list contents: '[subsystems]' '[ticker_SS]' '[runlevel_SS]' Crashing!")
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_subsystem_mastercontroller("SoftReset: Found bad entry in subsystem list, '[SS]'")
continue
if (SS.queue_next && !istype(SS.queue_next))
log_subsystem_mastercontroller("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_subsystem_mastercontroller("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_subsystem_mastercontroller("SoftReset: Found bad data in subsystem queue, queue_head = '[queue_head]'")
queue_head = null
if (queue_tail && !istype(queue_tail))
log_subsystem_mastercontroller("SoftReset: Found bad data in subsystem queue, queue_tail = '[queue_tail]'")
queue_tail = null
queue_priority_count = 0
queue_priority_count_bg = 0
log_subsystem_mastercontroller("SoftReset: Finished.")
. = 1
/datum/controller/master/proc/laggy_byond_map_update_incoming()
if(!skip_ticks)
skip_ticks = 1
/datum/controller/master/stat_entry(msg)
msg = "(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)]%)) \
(Internal Tick Usage: [round(MAPTICK_LAST_TICK_USAGE,0.1)]%) (TickRate:[Master.processing]) \
(Iteration:[Master.iteration]) (TickLimit: [round(CURRENT_TICKLIMIT, 0.1)])"
return msg
/datum/controller/master/ExplosionStart()
for (var/thing in subsystems)
var/datum/controller/subsystem/SS = thing
SS.ExplosionStart()
/datum/controller/master/ExplosionEnd()
for (var/thing in subsystems)
var/datum/controller/subsystem/SS = thing
SS.ExplosionEnd()
/world/proc/has_round_started()
if (SSticker.current_state >= GAME_STATE_PLAYING)
return TRUE
return FALSE
/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()
+143 -115
View File
@@ -2,12 +2,21 @@
/datum/controller/subsystem
// Metadata; you should define these.
name = "fire coderbus" //name of the subsystem
var/init_order = SS_INIT_MISC //order of initialization. Higher numbers are initialized first, lower numbers later. Can be decimal and negative values.
var/wait = 20 //time to wait between each call to fire(). Must be a positive integer. In ticks if SS_TICKER, deciseconds otherwise.
var/priority = SS_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 master_controller.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)
/// Name of the subsystem - you must change this
name = "fire coderbus"
/// Order of initialization. Higher numbers are initialized first, lower numbers later. Use or create defines such as [INIT_ORDER_DEFAULT] so we can see the order in one file.
var/init_order = SS_INIT_MISC
/// Time to wait (in deciseconds) between each call to fire(). Must be a positive integer.
var/wait = 20
/// Priority Weight: When mutiple subsystems need to run in the same tick, higher priority subsystems will be given a higher share of the tick before MC_TICK_CHECK triggers a sleep, higher priority subsystems also run before lower priority subsystems
var/priority = FIRE_PRIORITY_DEFAULT
/// [Subsystem Flags][SS_NO_INIT] to control binary behavior. Flags must be set at compile time or before preinit finishes to take full effect. (You can also restart the mc to force them to process again)
var/flags = NONE
/// Which stage does this subsystem init at. Earlier stages can fire while later stages init.
var/init_stage = INITSTAGE_MAIN
@@ -15,41 +24,76 @@
/// This var is set to TRUE after the subsystem has been initialized.
var/initialized = FALSE
/// Levels of the game that the SS can fire. See __defines/subsystem_priority.dm
var/runlevels = RUNLEVELS_DEFAULT
//set to 0 to prevent fire() calls, mostly for admin use.
// use the SS_NO_FIRE flag instead for systems that never fire to keep it from even being added to the list
/// 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 list that is checked every tick
var/can_fire = TRUE
// Similar to can_fire, but intended explicitly for subsystems that are asleep. Using this var instead of can_fire
// allows admins to disable subsystems without them re-enabling themselves.
var/suspended = FALSE
// 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
///Bitmap of what game states can this subsystem fire at. See [RUNLEVELS_DEFAULT] for more details.
var/runlevels = RUNLEVELS_DEFAULT //points of the game at which the SS can fire
// Subsystem startup accounting - these variables cannot be trusted if the subsystem has crashed and been Recover()'d.
var/init_state = SS_INITSTATE_NONE // The current initialization state of this SS - this might be invalid if the subsystem has been Recover()'d.
var/init_time = 0 // How long the subsystem took to initialize, in seconds.
var/init_start = 0 // What timeofday did we start initializing?
var/init_finish // What timeofday did we finish initializing?
/*
* The following variables are managed by the MC and should not be modified directly.
*/
//linked list stuff for the queue
/// Last world.time the subsystem completed a run (as in wasn't paused by [MC_TICK_CHECK])
var/last_fire = 0
/// Scheduled world.time for next fire()
var/next_fire = 0
/// Running average of the amount of milliseconds it takes the subsystem to complete a run (including all resumes but not the time spent paused)
var/cost = 0
/// Running average of the amount of tick usage in percents of a tick it takes the subsystem to complete a run
var/tick_usage = 0
/// Running average of the amount of tick usage (in percents of a game tick) the subsystem has spent past its allocated time without pausing
var/tick_overrun = 0
/// How much of a tick (in percents of a tick) were we allocated last fire.
var/tick_allocation_last = 0
/// How much of a tick (in percents of a tick) do we get allocated by the mc on avg.
var/tick_allocation_avg = 0
/// Tracks the current execution state of the subsystem. Used to handle subsystems that sleep in fire so the mc doesn't run them again while they are sleeping
var/state = SS_IDLE
/// Tracks how many times a subsystem has ever slept in fire().
var/slept_count = 0
/// Tracks how many fires the subsystem has consecutively paused on in the current run
var/paused_ticks = 0
/// Tracks how much of a tick the subsystem has consumed in the current run
var/paused_tick_usage
/// Tracks how many fires the subsystem takes to complete a run on average.
var/ticks = 1
/// Tracks the amount of completed runs for the subsystem
var/times_fired = 0
/// How many fires have we been requested to postpone
var/postponed_fires = 0
/// Time the subsystem entered the queue, (for timing and priority reasons)
var/queued_time = 0
/// Priority at the time the subsystem entered the queue. Needed to avoid changes in priority (by admins and the like) from breaking things.
var/queued_priority
/// How many times we suspect a subsystem type has crashed the MC, 3 strikes and you're out!
var/static/list/failure_strikes
/// Next subsystem in the queue of subsystems to run this tick
var/datum/controller/subsystem/queue_next
/// Previous subsystem in the queue of subsystems to run this tick
var/datum/controller/subsystem/queue_prev
var/static/list/failure_strikes //How many times we suspect a subsystem type has crashed the MC, 3 strikes and you're out! This is an assoc list indexed by type.
//Do not blindly add vars here to the bottom, put it where it goes above
//If your var only has two values, put it in as a flag.
//Do not override
///datum/controller/subsystem/New()
@@ -63,13 +107,20 @@
//This is used so the mc knows when the subsystem sleeps. do not override.
/datum/controller/subsystem/proc/ignite(resumed = 0)
SHOULD_NOT_OVERRIDE(TRUE)
set waitfor = 0
set waitfor = FALSE
. = SS_IDLE
tick_allocation_last = Master.current_ticklimit-(TICK_USAGE)
tick_allocation_avg = MC_AVERAGE(tick_allocation_avg, tick_allocation_last)
. = SS_SLEEPING
fire(resumed)
. = state
if (state == SS_SLEEPING)
slept_count++
state = SS_IDLE
if (state == SS_PAUSING)
slept_count++
var/QT = queued_time
enqueue()
state = SS_PAUSED
@@ -87,13 +138,38 @@
dequeue()
can_fire = 0
flags |= SS_NO_FIRE
Master.subsystems -= src
if (Master)
Master.subsystems -= src
return ..()
return QDEL_HINT_HARDDEL_NOW
//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)
/** Update next_fire for the next run.
* reset_time (bool) - Ignore things that would normally alter the next fire, like tick_overrun, and last_fire. (also resets postpone)
*/
/datum/controller/subsystem/proc/update_nextfire(reset_time = FALSE)
var/queue_node_flags = flags
if (reset_time)
postponed_fires = 0
if (queue_node_flags & SS_TICKER)
next_fire = world.time + (world.tick_lag * wait)
else
next_fire = world.time + wait
return
if (queue_node_flags & SS_TICKER)
next_fire = world.time + (world.tick_lag * wait)
else if (queue_node_flags & SS_POST_FIRE_TIMING)
next_fire = world.time + wait + (world.tick_lag * (tick_overrun/100))
else if (queue_node_flags & SS_KEEP_TIMING)
next_fire += wait
else
next_fire = queued_time + wait + (world.tick_lag * (tick_overrun/100))
///Queue it to run.
/// (we loop thru a linked list until we get to the end or find the right point)
/// (this lets us sort our run order correctly without having to re-sort the entire already sorted list)
/datum/controller/subsystem/proc/enqueue()
var/SS_priority = priority
var/SS_flags = flags
@@ -157,9 +233,9 @@
queue_next.queue_prev = queue_prev
if (queue_prev)
queue_prev.queue_next = queue_next
if (src == Master.queue_tail)
if (Master && (src == Master.queue_tail))
Master.queue_tail = queue_prev
if (src == Master.queue_head)
if (Master && (src == Master.queue_head))
Master.queue_head = queue_next
queued_time = 0
if (state == SS_QUEUED)
@@ -168,66 +244,28 @@
/datum/controller/subsystem/proc/pause()
. = 1
if (state == SS_RUNNING)
state = SS_PAUSED
else if (state == SS_SLEEPING)
state = SS_PAUSING
switch(state)
if(SS_RUNNING)
state = SS_PAUSED
if(SS_SLEEPING)
state = SS_PAUSING
// Do not override - Wrapper for Initialize() so initialization status can be shown for subsystems that do not return parent.
/datum/controller/subsystem/proc/StartInitialize(timeofday)
init_state = SS_INITSTATE_STARTED
init_start = timeofday
. = Initialize(timeofday)
init_finish = REALTIMEOFDAY
if (!init_time)
init_time = (init_finish - init_start) / 10
init_state = SS_INITSTATE_DONE
/// Called after the config has been loaded or reloaded.
/datum/controller/subsystem/proc/OnConfigLoad()
//used to initialize the subsystem AFTER the map has loaded
/datum/controller/subsystem/Initialize(start_timeofday)
var/time = (REALTIMEOFDAY - start_timeofday) / 10
init_time = time
var/msg = "Initialized [name] subsystem within [time] second\s!"
admin_notice(SPAN_DANGER(msg), R_DEBUG)
/**
* Used to initialize the subsystem. This is expected to be overriden by subtypes.
*/
/datum/controller/subsystem/Initialize()
return SS_INIT_NONE
// Do not print to world.log if we're running the unit tests
#if !defined(UNIT_TEST)
world.log << "SS Init: [msg]"
#endif
log_subsystem_init(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(can_fire && !(SS_NO_FIRE & flags) && !Master.initializing)
if(can_fire && !(SS_NO_FIRE & flags) && init_stage <= Master.init_stage_completed)
msg = "[round(cost,1)]ms|[round(tick_usage,1)]%([round(tick_overrun,1)]%)|[round(ticks,0.1)]\t[msg]"
else
msg = "OFFLINE\t[msg]"
return msg
// Generates the message shown before a subsystem during normal MC operation.
/datum/controller/subsystem/proc/stat_entry_run()
if (flags & SS_NO_FIRE)
. = "NO FIRE"
else if (can_fire && !suspended)
. = "[round(cost,1)]ms|[round(tick_usage,1)]%([round(tick_overrun,1)]%)|[round(ticks,0.1)]"
else if (!can_fire)
. = "OFFLINE"
else
. = "SUSPEND"
/datum/controller/subsystem/proc/init_state_letter()
if (flags & SS_NO_INIT)
return
switch (init_state)
if (SS_INITSTATE_NONE)
. = "W"
if (SS_INITSTATE_STARTED)
. = "L"
if (SS_INITSTATE_DONE)
. = "D"
/datum/controller/subsystem/proc/state_letter()
switch (state)
if (SS_RUNNING)
@@ -241,34 +279,24 @@
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
/// Causes the next "cycle" fires to be missed. Effect is accumulative but can reset by calling update_nextfire(reset_time = TRUE)
/datum/controller/subsystem/proc/postpone(cycles = 1)
if(next_fire - world.time < wait)
next_fire += (wait*cycles)
if (can_fire && cycles >= 1)
postponed_fires += 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()
// Admin-disables this subsystem. Will show as OFFLINE in MC panel.
/datum/controller/subsystem/proc/disable()
can_fire = FALSE
/datum/controller/subsystem/vv_edit_var(var_name, var_value)
switch (var_name)
if (NAMEOF(src, can_fire))
//this is so the subsystem doesn't rapid fire to make up missed ticks causing more lag
if (var_value)
update_nextfire(reset_time = TRUE)
if (NAMEOF(src, queued_priority)) //editing this breaks things.
return FALSE
. = ..()
// Admin-enables this subsystem.
/datum/controller/subsystem/proc/enable()
if (!can_fire)
next_fire = world.time + wait
can_fire = TRUE
// Suspends this subsystem. Functionally identical to disable(), but shows SUSPEND in MC panel.
// Preferred over disable() for self-disabling subsystems.
/datum/controller/subsystem/proc/suspend()
suspended = TRUE
// Wakes a suspended subsystem.
/datum/controller/subsystem/proc/wake()
if (suspended)
suspended = FALSE
if (can_fire)
next_fire = world.time + wait
/* Aurora shit */