From 2887dc7ce0e837bf0f3cb6740d02a7c5c45cb6e5 Mon Sep 17 00:00:00 2001 From: MrStonedOne Date: Sun, 13 Mar 2016 07:47:19 -0700 Subject: [PATCH] Removes lag. This setup allows subsystems to pause themselves until next mc fire if they are about to go over a tick, and resume on the next mc tick It also makes it so we prioritize running subsystems we skipped because there wasn't enough time in the current tick to run them based on their avg tick usage. (as well as subsystems paused mid way thru) Adds a macro for adding this to non-mc procs. just drop it in the loop. There is a define version as well for if checks if you want to know if you just slept to avoid lag. This is all inside #if DM_VERSION defines, so once i test that 509 still works to compile/test this should be good to merge --- code/__DEFINES/tick.dm | 9 +++ code/__HELPERS/unsorted.dm | 4 ++ code/controllers/master.dm | 56 ++++++++++++++----- code/controllers/subsystem.dm | 10 +++- code/controllers/subsystem/air.dm | 37 ++++++++---- code/controllers/subsystem/garbage.dm | 18 +++--- code/controllers/subsystem/lighting.dm | 25 +++++---- code/controllers/subsystem/machines.dm | 22 +++++--- code/controllers/subsystem/mobs.dm | 19 +++++-- code/controllers/subsystem/objects.dm | 18 ++++-- code/game/objects/explosion.dm | 5 +- code/modules/power/singularity/singularity.dm | 1 + tgstation.dme | 1 + 13 files changed, 160 insertions(+), 65 deletions(-) create mode 100644 code/__DEFINES/tick.dm 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"