Skip to content

Commit

Permalink
StonedMC, the bastard love child of GoonPS and CarnMC (tgstation#17987)
Browse files Browse the repository at this point in the history
Basically, they key difference between StonedMC and CarnMC is that when multiple ticks want to run at the same byond tick, we divvy up the tick between the subsystems, rather then allow one subsystem to hog it all.

The key difference between StonedMC and GoonPS is that we allow the subsystems to tell us how to divvy up the tick using flags and priority.

The new SS_ flags allows us to select behaviors that used to be piggybacked as side effects of dynamic wait or default but sometimes unneeded behavior.

Dynamic wait is 100% gone, lower priority and SS_BACKGROUND are better more refined ways of doing this when combined with MC_TICK_CHECK

I have by design never looked at the inners of goonPS, so this is all original code but I know it uses two loops because of comments by goon devs on reddit threads, that design didn't make sense before, but when I can tell a SS how much of a byond tick it is allowed to have, knowing how many need to run this tick is helpful I also know a bit more about how it works from piecing together comments in #vgstation.

Detailed list of changes:

Subsystems now have flags, allowing fine grain control over things like rather or not it processes, inits, rather it's wait is how long between runs (post run timing) or how long between starts, and rather or not late fires should cause the next fire to be earlier.

Mc now has two loops One loop handles queuing shit, one loop handles running shit.

MC now splits up tick allotment rather than first come first serve Subsystems can even request a bigger share using higher priorities. (It will even resume subsystems it paused if other subsystems hadn't used as much as it predicted they might need)

Default fps is now 20 This is related enough to the MC and it's a change that's really long since over due

All code oddities are most likely to be necessities to lower overhead on the mc since it runs every tick
  • Loading branch information
MrStonedOne authored and optimumtact committed Jun 16, 2016
1 parent 2ae6b94 commit 235b79f
Show file tree
Hide file tree
Showing 41 changed files with 1,135 additions and 537 deletions.
43 changes: 43 additions & 0 deletions code/__DEFINES/MC.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#define MC_TICK_CHECK ( world.tick_usage > CURRENT_TICKLIMIT ? pause() : 0 )
// Used to smooth out costs to try and avoid oscillation.
#define MC_AVERAGE_FAST(average, current) (0.7 * (average) + 0.3 * (current))
#define MC_AVERAGE(average, current) (0.8 * (average) + 0.2 * (current))
#define MC_AVERAGE_SLOW(average, current) (0.9 * (average) + 0.1 * (current))
#define NEW_SS_GLOBAL(varname) if(varname != src){if(istype(varname)){Recover();qdel(varname);}varname = src;}

//SubSystem flags (Please design any new flags so that the default is off, to make adding flags to subsystems easier)

//subsystem should fire during pre-game lobby.
#define SS_FIRE_IN_LOBBY 1

//subsystem does not initialize.
#define SS_NO_INIT 2

//subsystem does not fire.
// (like can_fire = 0, but keeps it from getting added to the processing subsystems list)
// (Requires a MC restart to change)
#define SS_NO_FIRE 4

//subsystem only runs on spare cpu (after all non-background subsystems have ran that tick)
// SS_BACKGROUND has its own priority bracket
#define SS_BACKGROUND 8

//subsystem does not tick check, and should not run unless there is enough time (or its running behind (unless background))
#define SS_NO_TICK_CHECK 16

//Treat wait as a tick count, not DS, run every wait ticks.
// (also forces it to run first in the tick, above even SS_NO_TICK_CHECK subsystems)
// (implies SS_FIRE_IN_LOBBY because of how it works)
// (overrides SS_BACKGROUND)
// This is designed for basically anything that works as a mini-mc (like SStimer)
#define SS_TICKER 32

//keep the subsystem's timing on point by firing early if it fired late last fire because of lag
// ie: if a 20ds subsystem fires say 5 ds late due to lag or what not, its next fire would be in 15ds, not 20ds.
#define SS_KEEP_TIMING 64

//Calculate its next fire after its fired.
// (IE: if a 5ds wait SS takes 2ds to run, its next fire should be 5ds away, not 3ds like it normally would be)
// This flag overrides SS_KEEP_TIMING
#define SS_POST_FIRE_TIMING 128

6 changes: 6 additions & 0 deletions code/__DEFINES/math.dm
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,9 @@
#define T0C 273.15 // 0degC
#define T20C 293.15 // 20degC
#define TCMB 2.7 // -270.3degC

//"fancy" math for calculating time in ms from tick_usage percentage and the length of ticks
//percent_of_tick_used * (ticklag * 100(to convert to ms)) / 100(percent ratio)
//collapsed to percent_of_tick_used * tick_lag
#define TICK_DELTA_TO_MS(percent_of_tick_used) ((percent_of_tick_used) * world.tick_lag)
#define TICK_USAGE_TO_MS(starting_tickusage) (TICK_DELTA_TO_MS(world.tick_usage-starting_tickusage))
5 changes: 4 additions & 1 deletion code/__DEFINES/misc.dm
Original file line number Diff line number Diff line change
Expand Up @@ -435,7 +435,6 @@ var/global/list/ghost_others_options = list(GHOST_OTHERS_SIMPLE, GHOST_OTHERS_DE
#define FIRST_DIAG_STEP 1
#define SECOND_DIAG_STEP 2


//Slime commands defines
#define SLIME_FRIENDSHIP_FOLLOW 3 //Min friendship to order it to follow
#define SLIME_FRIENDSHIP_STOPEAT 5 //Min friendship to order it to stop eating someone
Expand All @@ -455,3 +454,7 @@ var/global/list/ghost_others_options = list(GHOST_OTHERS_SIMPLE, GHOST_OTHERS_DE
#define SHELTER_DEPLOY_BAD_AREA "bad area"
#define SHELTER_DEPLOY_ANCHORED_OBJECTS "anchored objects"

//debug printing macros
#define debug_world(msg) if (Debug2) world << "DEBUG: [msg]"
#define debug_admins(msg) if (Debug2) admins << "DEBUG: [msg]"
#define debug_world_log(msg) if (Debug2) world.log << "DEBUG: [msg]"
4 changes: 4 additions & 0 deletions code/__DEFINES/qdel.dm
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,7 @@
#define QDEL_HINT_PUTINPOOL 5 //qdel will put this object in the atom pool.
#define QDEL_HINT_FINDREFERENCE 6 //functionally identical to QDEL_HINT_QUEUE if TESTING is not enabled in _compiler_options.dm.
//if TESTING is enabled, qdel will call this object's find_references() verb.
//defines for the gc_destroyed var

#define GC_QUEUED_FOR_QUEUING -1
#define GC_QUEUED_FOR_HARD_DEL -2
12 changes: 6 additions & 6 deletions code/__DEFINES/tick.dm
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#define TICK_LIMIT_RUNNING 90
#define TICK_LIMIT_TO_RUN 85
#define TICK_LIMIT_MC 80
#define TICK_LIMIT_RUNNING 85
#define TICK_LIMIT_TO_RUN 80
#define TICK_LIMIT_MC 84
#define TICK_LIMIT_MC_INIT 100

#define TICK_CHECK ( world.tick_usage > TICK_LIMIT_RUNNING ? stoplag() : 0 )
#define CHECK_TICK if (world.tick_usage > TICK_LIMIT_RUNNING) stoplag()
#define MC_TICK_CHECK ( world.tick_usage > TICK_LIMIT_RUNNING ? pause() : 0 )
#define TICK_CHECK ( world.tick_usage > CURRENT_TICKLIMIT ? stoplag() : 0 )
#define CHECK_TICK if (world.tick_usage > CURRENT_TICKLIMIT) stoplag()
11 changes: 7 additions & 4 deletions code/__HELPERS/cmp.dm
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,16 @@ var/cmp_field = "name"
/proc/cmp_ckey_dsc(client/a, client/b)
return sorttext(a.ckey, b.ckey)

/proc/cmp_subsystem_priority(datum/subsystem/a, datum/subsystem/b)
return b.priority - a.priority
/proc/cmp_subsystem_init(datum/subsystem/a, datum/subsystem/b)
return b.init_order - a.init_order

/proc/cmp_subsystem_display(datum/subsystem/a, datum/subsystem/b)
if(a.display == b.display)
if(a.display_order == b.display_order)
return sorttext(b.name, a.name)
return a.display - b.display
return a.display_order - b.display_order

/proc/cmp_subsystem_priority(datum/subsystem/a, datum/subsystem/b)
return a.priority - b.priority

/proc/cmp_clientcolour_priority(datum/client_colour/A, datum/client_colour/B)
return B.priority - A.priority
3 changes: 2 additions & 1 deletion code/__HELPERS/icon_smoothing.dm
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,8 @@
//SSicon_smooth
/proc/queue_smooth(atom/A)
if(SSicon_smooth)
SSicon_smooth.smooth_queue |= A
SSicon_smooth.smooth_queue[A] = A
SSicon_smooth.can_fire = 1
else
smooth_icon(A)

Expand Down
2 changes: 1 addition & 1 deletion code/controllers/configuration.dm
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
var/del_new_on_log = 1 // del's new players if they log before they spawn in
var/allow_Metadata = 0 // Metadata is supported.
var/popup_admin_pm = 0 //adminPMs to non-admins show in a pop-up 'reply' window when set to 1.
var/fps = 10
var/fps = 20
var/allow_holidays = 0 //toggles whether holiday-specific content should be used

var/hostedby = null
Expand Down
96 changes: 62 additions & 34 deletions code/controllers/failsafe.dm
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,12 @@ var/datum/controller/failsafe/Failsafe

// The length of time to check on the MC (in deciseconds).
// Set to 0 to disable.
var/processing_interval = 100
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 = 0
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
Expand All @@ -24,45 +27,70 @@ var/datum/controller/failsafe/Failsafe
if(istype(Failsafe))
qdel(Failsafe)
Failsafe = src
Failsafe.process()
spawn()
Failsafe.Loop()
qdel(Failsafe) //when Loop() returns, we delete ourselves and let the mc recreate us

/datum/controller/failsafe/process()
spawn(0)
while(1) // More efficient than recursion, 1 to avoid an infinite loop.
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)
// Check if processing is done yet.
if(Master.iteration == master_iteration)
switch(defcon)
if(0 to 2)
++defcon
if(3)
admins << "<span class='boldannounce'>Warning: DEFCON [defcon_pretty()]. The Master Controller has not fired in the last [defcon * processing_interval] ticks. Automatic restart in [processing_interval] ticks.</span>"
/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)
admins << "<span class='adminnotice'>Notice: DEFCON [defcon_pretty()]. The Master Controller has not fired in the last [(5-defcon) * processing_interval] ticks."
--defcon
if(2)
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)

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
if(4)
admins << "<span class='boldannounce'>Warning: DEFCON [defcon_pretty()]. The Master Controller has still not fired within the last [defcon * processing_interval] ticks. Killing and restarting...</span>"
// Replace the old master controller by creating a new one.
new/datum/controller/master()
// Get it rolling again.
Master.process()
defcon = 0
else
defcon = 0
master_iteration = Master.iteration
sleep(processing_interval)
master_iteration = 0
admins << "<span class='adminnotice'>MC restarted successfully</span>"
else if(rtn < 0)
log_game("FailSafe: Could not restart MC, runtime encountered. Entering defcon 0")
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
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
defcon = 0
sleep(initial(processing_interval))
sleep(processing_interval)
else
defcon = 5
sleep(initial(processing_interval))

/datum/controller/failsafe/proc/defcon_pretty()
return 5 - Failsafe.defcon
return defcon

/datum/controller/failsafe/proc/stat_entry()
if(!statclick)
statclick = new/obj/effect/statclick/debug("Initializing...", src)

stat("Failsafe Controller:", statclick.update("Defcon: [Failsafe.defcon_pretty()] (Interval: [Failsafe.processing_interval] | Iteration: [Failsafe.master_iteration])"))
stat("Failsafe Controller:", statclick.update("Defcon: [defcon_pretty()] (Interval: [Failsafe.processing_interval] | Iteration: [Failsafe.master_iteration])"))
Loading

0 comments on commit 235b79f

Please sign in to comment.