diff --git a/code/__DEFINES/tick.dm b/code/__DEFINES/tick.dm new file mode 100644 index 00000000000..5fec424e52c --- /dev/null +++ b/code/__DEFINES/tick.dm @@ -0,0 +1,9 @@ +#if DM_VERSION >= 510 +#define TICK_CHECK ( world.tick_usage > 85 ? pause1tick() : 0 ) +#define CHECK_TICK if (world.tick_usage > 85) pause1tick() +#define MC_TICK_CHECK ( world.tick_usage > 85 ? pause() : 0 ) +#else +#define TICK_CHECK ( 0 ) +#define CHECK_TICK +#define MC_TICK_CHECK ( 0 ) +#endif \ No newline at end of file diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm index c8177b9618b..9a364f15324 100644 --- a/code/__HELPERS/unsorted.dm +++ b/code/__HELPERS/unsorted.dm @@ -1291,3 +1291,7 @@ proc/pick_closest_path(value) //gives us the stack trace from CRASH() without ending the current proc. /proc/stack_trace(msg) CRASH(msg) + +/proc/pause1tick() + . = 1 + sleep world.tick_lag diff --git a/code/controllers/master.dm b/code/controllers/master.dm index fe163960ced..aa25333933b 100644 --- a/code/controllers/master.dm +++ b/code/controllers/master.dm @@ -31,6 +31,9 @@ var/global/datum/controller/master/Master = new() var/subsystem_cost = 0 // The type of the last subsystem to be process()'d. var/last_type_processed +#if DM_VERSION >= 510 + var/list/priority_queue = list() //any time we pause or skip a ss for tick reasons, we run it first next tick +#endif /datum/controller/master/New() // Highlander-style: there can only be one! Kill off the old and replace it with the new. @@ -120,6 +123,7 @@ var/global/datum/controller/master/Master = new() #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)) + /datum/controller/master/process() if(!Failsafe) new/datum/controller/failsafe() // (re)Start the failsafe. @@ -131,27 +135,43 @@ var/global/datum/controller/master/Master = new() SS.next_fire = timer var/start_time - while(1) // More efficient than recursion, 1 to avoid an infinite loop. - if(processing_interval > 0) + while(1) // More efficient than recursion. + if(processing_interval > 0) //are we processing ++iteration - var/startingtick = world.time // Store when we started this iteration. start_time = world.timeofday - + var/list/subsystemstorun = subsystems + var/priorityrunning = 0 //so we know if there are priority queue items +#if DM_VERSION >= 510 + //this is a queue for any SS skipped or paused for tick reasons, to be ran first next tick + if (priority_queue.len) + priorityrunning = priority_queue.len + subsystemstorun = priority_queue | subsystems var/ran_subsystems = 0 - for(var/datum/subsystem/SS in subsystems) + for(var/datum/subsystem/SS in subsystemstorun) #if DM_VERSION >= 510 if (world.tick_usage > 80) #else if(world.cpu >= 100) #endif break - - if(SS.can_fire > 0) - if(SS.next_fire <= world.time && SS.last_fire + (SS.wait * 0.75) <= world.time) // Check if it's time. #if DM_VERSION >= 510 - if (world.tick_usage + SS.tick_usage > 80 && SS.last_fire + (SS.wait*1.25) > world.time) + if (priorityrunning) + if (!priority_queue.len || !(SS in priority_queue)) + priorityrunning = 0 //end of priority queue items + else + priority_queue -= SS +#endif + if(SS.can_fire > 0) + if(priorityrunning || ((SS.next_fire <= world.time) && (SS.last_fire + (SS.wait * 0.75) <= world.time))) +#if DM_VERSION >= 510 + if (!priorityrunning && (world.tick_usage + SS.tick_usage > 75) && (SS.last_fire + (SS.wait*1.25) > world.time)) + priority_queue += SS continue #endif + //we can't reset SS.paused after we fire, incase it pauses again, so we cache it and + // send it to SS.fire() + var/paused = SS.paused + SS.paused = 0 ran_subsystems = 1 timer = world.timeofday last_type_processed = SS.type @@ -159,8 +179,10 @@ var/global/datum/controller/master/Master = new() #if DM_VERSION >= 510 var/tick_usage = world.tick_usage #endif - SS.fire() // Fire the subsystem and record the cost. + SS.fire(paused) // Fire the subsystem #if DM_VERSION >= 510 + if (priorityrunning) + priorityrunning-- var/newusage = max(world.tick_usage - tick_usage, 0) if (newusage < SS.tick_usage) SS.tick_usage = MC_AVERAGE_SLOW(SS.tick_usage,world.tick_usage - tick_usage) @@ -179,12 +201,13 @@ var/global/datum/controller/master/Master = new() SS.wait = Clamp(newwait, SS.dwait_lower, SS.dwait_upper) SS.next_fire = world.time + SS.wait else - SS.next_fire += SS.wait - ++SS.times_fired - // If we caused BYOND to miss a tick, stop processing for a bit... - if(startingtick < world.time || start_time + 1 < world.timeofday) - break + if (!paused) + SS.next_fire += SS.wait + if (!SS.paused) + SS.times_fired++ +#if DM_VERSION < 510 sleep(0) +#endif cost = max(MC_AVERAGE(cost, world.timeofday - start_time), 0) if(ran_subsystems) @@ -214,7 +237,10 @@ var/global/datum/controller/master/Master = new() else sleep(50) + +#undef MC_AVERAGE_FAST #undef MC_AVERAGE +#undef MC_AVERAGE_SLOW /datum/controller/master/proc/stat_entry() if(!statclick) diff --git a/code/controllers/subsystem.dm b/code/controllers/subsystem.dm index 2edfa916c59..d931acdad18 100644 --- a/code/controllers/subsystem.dm +++ b/code/controllers/subsystem.dm @@ -13,6 +13,7 @@ // Defaults are pretty sane for most use cases. // You can change how quickly it starts scaling back with dwait_buffer, // and you can change how much it scales back with dwait_delta. + var/dynamic_wait = 0 //changes the wait based on the amount of time it took to process var/dwait_upper = 20 //longest wait can be under dynamic_wait var/dwait_lower = 5 //shortest wait can be under dynamic_wait @@ -27,6 +28,7 @@ #if DM_VERSION >= 510 var/tick_usage = 0 //average tick usage #endif + var/paused =0 //was this subsystem paused mid fire. var/times_fired = 0 //number of times we have called fire() // The object used for the clickable stat() button. @@ -39,9 +41,15 @@ //fire() seems more suitable. This is the procedure that gets called every 'wait' deciseconds. //fire(), and the procs it calls, SHOULD NOT HAVE ANY SLEEP OPERATIONS in them! //YE BE WARNED! -/datum/subsystem/proc/fire() +/datum/subsystem/proc/fire(resumed = 0) + set waitfor = 0 //this should not be depended upon, this is just to solve issues with sleeps messing up tick tracking can_fire = 0 +/datum/subsystem/proc/pause() + . = 1 + Master.priority_queue += src + paused = 1 + //used to initialize the subsystem AFTER the map has loaded /datum/subsystem/proc/Initialize(start_timeofday, zlevel) var/time = (world.timeofday - start_timeofday) / 10 diff --git a/code/controllers/subsystem/air.dm b/code/controllers/subsystem/air.dm index 045783bb6d7..736352fe007 100644 --- a/code/controllers/subsystem/air.dm +++ b/code/controllers/subsystem/air.dm @@ -20,6 +20,7 @@ var/datum/subsystem/air/SSair var/list/excited_groups = list() var/list/active_turfs = list() + var/list/processing = list() var/list/hotspots = list() var/list/networks = list() var/list/obj/machinery/atmos_machinery = list() @@ -56,19 +57,24 @@ var/datum/subsystem/air/SSair ..() #define MC_AVERAGE(average, current) (0.8*(average) + 0.2*(current)) -/datum/subsystem/air/fire() +/datum/subsystem/air/fire(resumed = 0) + var/timer = world.timeofday - process_pipenets() - cost_pipenets = MC_AVERAGE(cost_pipenets, (world.timeofday - timer)) + //tick paused, that means we already did this bit + if (!resumed) + process_pipenets() + cost_pipenets = MC_AVERAGE(cost_pipenets, (world.timeofday - timer)) - timer = world.timeofday - process_atmos_machinery() - cost_atmos_machinery = MC_AVERAGE(cost_atmos_machinery, (world.timeofday - timer)) + timer = world.timeofday + process_atmos_machinery() + cost_atmos_machinery = MC_AVERAGE(cost_atmos_machinery, (world.timeofday - timer)) - timer = world.timeofday - process_active_turfs() + timer = world.timeofday + + process_active_turfs(resumed) cost_turfs = MC_AVERAGE(cost_turfs, (world.timeofday - timer)) - + if (paused) + return //we paused mid way thru processing turfs due to tick overrun timer = world.timeofday process_excited_groups() cost_groups = MC_AVERAGE(cost_groups, (world.timeofday - timer)) @@ -123,11 +129,18 @@ var/datum/subsystem/air/SSair high_pressure_delta.len = 0 -/datum/subsystem/air/proc/process_active_turfs() +/datum/subsystem/air/proc/process_active_turfs(resumed = 0) //cache for sanic speed var/fire_count = times_fired - for(var/turf/simulated/T in active_turfs) - T.process_cell(fire_count) + if (!resumed) + processing = active_turfs.Copy() + while(processing.len) + var/turf/simulated/T = processing[1] + processing.Cut(1, 2) + if (T) + T.process_cell(fire_count) + if (MC_TICK_CHECK) + return /datum/subsystem/air/proc/remove_from_active(turf/simulated/T) diff --git a/code/controllers/subsystem/garbage.dm b/code/controllers/subsystem/garbage.dm index eecc54ef93d..c840add735c 100644 --- a/code/controllers/subsystem/garbage.dm +++ b/code/controllers/subsystem/garbage.dm @@ -22,7 +22,7 @@ var/datum/subsystem/garbage_collector/SSgarbage var/list/queue = list() // list of refID's of things that should be garbage collected // refID's are associated with the time at which they time out and need to be manually del() // we do this so we aren't constantly locating them and preventing them from being gc'd - + var/list/tobequeued = list() //We store the references of things to be added to the queue seperately so we can spread out GC overhead over a few ticks var/list/didntgc = list() // list of all types that have failed to GC associated with the number of times that's happened. @@ -56,11 +56,13 @@ var/datum/subsystem/garbage_collector/SSgarbage var/time_to_stop = world.timeofday + max_run_time HandleToBeQueued(time_to_stop) HandleQueue(time_to_stop) - + //If you see this proc high on the profile, what you are really seeing is the garbage collection/soft delete overhead in byond. //Don't attempt to optimize, not worth the effort. /datum/subsystem/garbage_collector/proc/HandleToBeQueued(time_to_stop) while(tobequeued.len && world.timeofday < time_to_stop) + if (MC_TICK_CHECK) + break var/ref = tobequeued[1] Queue(ref) tobequeued.Cut(1, 2) @@ -69,8 +71,10 @@ var/datum/subsystem/garbage_collector/SSgarbage delslasttick = 0 gcedlasttick = 0 var/time_to_kill = world.time - collection_timeout // Anything qdel() but not GC'd BEFORE this time needs to be manually del() - + while(queue.len && world.timeofday < time_to_stop) + if (MC_TICK_CHECK) + break var/refID = queue[1] if (!refID) queue.Cut(1, 2) @@ -104,15 +108,15 @@ var/datum/subsystem/garbage_collector/SSgarbage /datum/subsystem/garbage_collector/proc/Queue(datum/A) if (!istype(A) || (!isnull(A.gc_destroyed) && A.gc_destroyed >= 0)) - return + return var/gctime = world.time var/refid = "\ref[A]" - + A.gc_destroyed = gctime - + if (queue[refid]) queue -= refid // Removing any previous references that were GC'd so that the current object will be at the end of the list. - + queue[refid] = gctime /datum/subsystem/garbage_collector/proc/HardQueue(datum/A) diff --git a/code/controllers/subsystem/lighting.dm b/code/controllers/subsystem/lighting.dm index ad117cb80ea..6aebe1f33ed 100644 --- a/code/controllers/subsystem/lighting.dm +++ b/code/controllers/subsystem/lighting.dm @@ -30,20 +30,25 @@ var/datum/subsystem/lighting/SSlighting //effects and then processes every turf in the queue, updating their lighting object's appearance //Any light that returns 1 in check() deletes itself //By using queues we are ensuring we don't perform more updates than are necessary -/datum/subsystem/lighting/fire() - changed_lights_workload = MC_AVERAGE(changed_lights_workload, changed_lights.len) - - for(var/thing in changed_lights) - var/datum/light_source/LS = thing +/datum/subsystem/lighting/fire(resumed = 0) + if (!resumed) + changed_lights_workload = MC_AVERAGE(changed_lights_workload, changed_lights.len) + while (changed_lights.len) + var/datum/light_source/LS = changed_lights[1] + changed_lights.Cut(1, 2) LS.check() - changed_lights.Cut() + if (MC_TICK_CHECK) + return - changed_turfs_workload = MC_AVERAGE(changed_turfs_workload, changed_turfs.len) - for(var/thing in changed_turfs) - var/turf/T = thing + if (!resumed) + changed_turfs_workload = MC_AVERAGE(changed_turfs_workload, changed_turfs.len) + while (changed_turfs.len) + var/turf/T = changed_turfs[1] + changed_turfs.Cut(1, 2) if(T.lighting_changed) T.redraw_lighting() - changed_turfs.Cut() + if (MC_TICK_CHECK) + return //same as above except it attempts to shift ALL turfs in the world regardless of lighting_changed status //Does not loop. Should be run prior to process() being called for the first time. diff --git a/code/controllers/subsystem/machines.dm b/code/controllers/subsystem/machines.dm index f77562a5950..2fd79cb29bd 100644 --- a/code/controllers/subsystem/machines.dm +++ b/code/controllers/subsystem/machines.dm @@ -6,6 +6,7 @@ var/datum/subsystem/machines/SSmachine display = 3 var/list/processing = list() + var/list/currentrun = list() var/list/powernets = list() @@ -33,17 +34,22 @@ var/datum/subsystem/machines/SSmachine ..("M:[processing.len]|PN:[powernets.len]") -/datum/subsystem/machines/fire() - for(var/datum/powernet/Powernet in powernets) - Powernet.reset() //reset the power state. - +/datum/subsystem/machines/fire(resumed = 0) + if (!resumed) + for(var/datum/powernet/Powernet in powernets) + Powernet.reset() //reset the power state. + currentrun = processing.Copy() var/seconds = wait * 0.1 - for(var/thing in processing) - if(thing && (thing:process(seconds) != PROCESS_KILL)) + while(currentrun.len) + var/datum/thing = currentrun[1] + currentrun.Cut(1, 2) + if(thing && thing.process(seconds) != PROCESS_KILL) if(thing:use_power) thing:auto_use_power() //add back the power state - continue - processing.Remove(thing) + else + processing.Remove(thing) + if (MC_TICK_CHECK) + return /datum/subsystem/machines/proc/setup_template_powernets(list/cables) for(var/A in cables) diff --git a/code/controllers/subsystem/mobs.dm b/code/controllers/subsystem/mobs.dm index 24297bde8c9..090af7cff79 100644 --- a/code/controllers/subsystem/mobs.dm +++ b/code/controllers/subsystem/mobs.dm @@ -5,6 +5,7 @@ var/datum/subsystem/mobs/SSmob priority = 4 display = 4 + var/list/currentrun = list() /datum/subsystem/mobs/New() NEW_SS_GLOBAL(SSmob) @@ -14,10 +15,16 @@ var/datum/subsystem/mobs/SSmob ..("P:[mob_list.len]") -/datum/subsystem/mobs/fire() +/datum/subsystem/mobs/fire(resumed = 0) var/seconds = wait * 0.1 - for(var/thing in mob_list) - if(thing) - thing:Life(seconds) - continue - mob_list.Remove(thing) \ No newline at end of file + if (!resumed) + currentrun = mob_list.Copy() + while(currentrun.len) + var/mob/M = currentrun[1] + currentrun.Cut(1, 2) + if(M) + M.Life(seconds) + else + mob_list.Remove(M) + if (MC_TICK_CHECK) + return \ No newline at end of file diff --git a/code/controllers/subsystem/objects.dm b/code/controllers/subsystem/objects.dm index 630e186314e..0bac6bb249b 100644 --- a/code/controllers/subsystem/objects.dm +++ b/code/controllers/subsystem/objects.dm @@ -10,6 +10,7 @@ var/datum/subsystem/objects/SSobj priority = 12 var/list/processing = list() + var/list/currentrun = list() var/list/burning = list() /datum/subsystem/objects/New() @@ -29,12 +30,19 @@ var/datum/subsystem/objects/SSobj ..("P:[processing.len]") -/datum/subsystem/objects/fire() - for(var/thing in SSobj.processing) +/datum/subsystem/objects/fire(resumed = 0) + if (!resumed) + currentrun = processing.Copy() + while(currentrun.len) + var/datum/thing = currentrun[1] + currentrun.Cut(1, 2) if(thing) - thing:process(wait) - continue - SSobj.processing.Remove(thing) + thing.process(wait) + else + SSobj.processing.Remove(thing) + if (MC_TICK_CHECK) + return + for(var/obj/burningobj in SSobj.burning) if(burningobj && (burningobj.burn_state == ON_FIRE)) if(burningobj.burn_world_time < world.time) diff --git a/code/game/objects/explosion.dm b/code/game/objects/explosion.dm index 524e04393a4..f4fd000a505 100644 --- a/code/game/objects/explosion.dm +++ b/code/game/objects/explosion.dm @@ -109,9 +109,12 @@ for(var/obj/effect/blob/B in T) cached_exp_block[T] += B.explosion_block + CHECK_TICK for(var/turf/T in affected_turfs) - + CHECK_TICK + if (!T) + continue var/dist = cheap_hypotenuse(T.x, T.y, x0, y0) if(config.reactionary_explosions) diff --git a/code/modules/power/singularity/singularity.dm b/code/modules/power/singularity/singularity.dm index 7478b5bfe5a..2555f23b35e 100644 --- a/code/modules/power/singularity/singularity.dm +++ b/code/modules/power/singularity/singularity.dm @@ -244,6 +244,7 @@ X.singularity_pull(S, current_size) else if(dist <= consume_range) consume(X) + CHECK_TICK return diff --git a/tgstation.dme b/tgstation.dme index 8a1490418b5..46287b529c8 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -63,6 +63,7 @@ #include "code\__HELPERS\radio.dm" #include "code\__HELPERS\sanitize_values.dm" #include "code\__HELPERS\text.dm" +#include "code\__HELPERS\tick.dm" #include "code\__HELPERS\time.dm" #include "code\__HELPERS\type2type.dm" #include "code\__HELPERS\unsorted.dm"