Various Delta Time Fixes (#21871)

Organs were being given ~3 seconds of DT per ~2 seconds of real time,
because they were being hit by Process() TWICE, once during processing
for the "Set of all objects", and again during processing for the "Set
of all mobs". No species was hit harder than this but IPCs, who suffered
from having wildly inconsistent temperature mechanics that seemingly
never matched the expected numbers.

I have actually tested this PR to verify that it works. I even went the
extra mile to more extensively test IPC temperature mechanics to find a
separate bug unrelated to DTs that was causing suit coolers to just
straight up not work at all (space suit or otherwise) on a majority of
all IPC subspecies. I then tested the numbers in the PR for IPC cooling
to verify that things work as intended.

<img width="969" height="623" alt="image"
src="https://github.com/user-attachments/assets/01c3aca7-b436-4e52-86f7-aed847dcba5e"
/>

I also tweaked all the subsystems to have a small performance
improvement.

---------

Signed-off-by: VMSolidus <evilexecutive@gmail.com>
This commit is contained in:
VMSolidus
2026-02-27 01:28:20 +00:00
committed by GitHub
parent 8e602afb32
commit 1423fcd04e
19 changed files with 75 additions and 74 deletions
+4 -2
View File
@@ -151,6 +151,7 @@ SUBSYSTEM_DEF(machinery)
if (!resumed)
queue = pipenets.Copy()
var/datum/pipe_network/network
var/seconds_per_tick = wait * 0.1
for (var/i = queue.len to 1 step -1)
network = queue[i]
if (QDELETED(network))
@@ -158,7 +159,7 @@ SUBSYSTEM_DEF(machinery)
network.datum_flags &= ~DF_ISPROCESSING
pipenets -= network
continue
network.process(wait * 0.1)
network.process(seconds_per_tick)
if (no_mc_tick)
CHECK_TICK
else if (MC_TICK_CHECK)
@@ -169,6 +170,7 @@ SUBSYSTEM_DEF(machinery)
if (!resumed)
queue = processing.Copy()
var/obj/machinery/machine
var/seconds_per_tick = wait * 0.1
for (var/i = queue.len to 1 step -1)
machine = queue[i]
@@ -196,7 +198,7 @@ SUBSYSTEM_DEF(machinery)
continue
//process_all was moved here because of calls overhead for no benefits
if((machine.processing_flags & MACHINERY_PROCESS_SELF))
if(machine.process(wait * 0.1) == PROCESS_KILL)
if(machine.process(seconds_per_tick) == PROCESS_KILL)
STOP_PROCESSING_MACHINE(machine, MACHINERY_PROCESS_SELF)
processing -= machine
if (no_mc_tick)
+2 -2
View File
@@ -80,14 +80,14 @@ SUBSYSTEM_DEF(mobs)
//of course, if we haven't resumed, this comparison would be useless, hence we skip it
var/list/currentrun = resumed ? (src.currentrun &= (GLOB.mob_list + processing)) : src.currentrun
var/seconds_per_tick = (1 SECONDS) / wait
var/seconds_per_tick = wait * 0.1
while(length(currentrun))
var/datum/thing = currentrun[length(currentrun)]
currentrun.len--
if(!ismob(thing))
if(!QDELETED(thing))
if(thing.process(wait, times_fired) == PROCESS_KILL)
if(thing.process(seconds_per_tick, times_fired) == PROCESS_KILL)
stop_processing(thing)
else
processing -= thing
@@ -39,22 +39,22 @@ SUBSYSTEM_DEF(movement)
/datum/controller/subsystem/movement/fire(resumed)
if(!resumed)
canonical_time = world.time
var/seconds_per_tick = wait * 0.1
for(var/list/bucket_info as anything in sorted_buckets)
var/time = bucket_info[MOVEMENT_BUCKET_TIME]
if(time > canonical_time || MC_TICK_CHECK)
return
pour_bucket(bucket_info)
pour_bucket(bucket_info, seconds_per_tick)
/// Processes a bucket of movement loops (This should only ever be called by fire(), it exists to prevent runtime fuckery)
/datum/controller/subsystem/movement/proc/pour_bucket(list/bucket_info)
/datum/controller/subsystem/movement/proc/pour_bucket(list/bucket_info, seconds_per_tick)
var/list/processing = bucket_info[MOVEMENT_BUCKET_LIST] // Cache for lookup speed
while(processing.len)
var/datum/move_loop/loop = processing[processing.len]
processing.len--
// No longer queued since we just got removed from the loop
loop.queued_time = null
loop.process() //This shouldn't get nulls, if it does, runtime
loop.process(seconds_per_tick) //This shouldn't get nulls, if it does, runtime
if(!QDELETED(loop) && loop.status & MOVELOOP_STATUS_QUEUED) //Re-Insert the loop
loop.status &= ~MOVELOOP_STATUS_QUEUED
loop.timer = world.time + loop.delay
+2 -1
View File
@@ -106,12 +106,13 @@ SUBSYSTEM_DEF(plants)
processing = old
var/list/queue = current
var/seconds_per_tick = wait * 0.1
while (queue.len)
var/obj/effect/plant/P = queue[queue.len]
queue.len--
if (!QDELETED(P))
P.process()
P.process(seconds_per_tick)
if (MC_TICK_CHECK)
return
@@ -12,12 +12,13 @@ PROCESSING_SUBSYSTEM_DEF(obj_tab_items)
currentrun = processing.Copy()
//cache for sanic speed (lists are references anyways)
var/list/current_run = currentrun
var/seconds_per_tick = wait * 0.1
while(current_run.len)
var/datum/thing = current_run[current_run.len]
if(QDELETED(thing))
processing -= thing
else if(thing.process(wait * 0.1) == PROCESS_KILL)
else if(thing.process(seconds_per_tick) == PROCESS_KILL)
// fully stop so that a future START_PROCESSING will work
STOP_PROCESSING(src, thing)
if (MC_TICK_CHECK)
@@ -19,13 +19,13 @@ SUBSYSTEM_DEF(processing)
currentrun = processing.Copy()
//cache for sanic speed (lists are references anyways)
var/list/current_run = currentrun
var/seconds_per_tick = wait * 0.1
while(current_run.len)
var/datum/thing = current_run[current_run.len]
current_run.len--
if(QDELETED(thing))
processing -= thing
else if(thing.process(wait * 0.1) == PROCESS_KILL)
else if(thing.process(seconds_per_tick) == PROCESS_KILL)
// fully stop so that a future START_PROCESSING will work
STOP_PROCESSING(src, thing)
if (MC_TICK_CHECK)
@@ -65,12 +65,13 @@ PROCESSING_SUBSYSTEM_DEF(tgui)
src.current_run = all_uis.Copy()
// Cache for sanic speed (lists are references anyways)
var/list/current_run = src.current_run
var/seconds_per_tick = wait * 0.1
while(current_run.len)
var/datum/tgui/ui = current_run[current_run.len]
current_run.len--
// TODO: Move user/src_object check to process()
if(ui?.user && ui.src_object)
ui.process(wait * 0.1)
ui.process(seconds_per_tick)
else
ui.close(0)
if(MC_TICK_CHECK)
@@ -212,10 +213,11 @@ PROCESSING_SUBSYSTEM_DEF(tgui)
if(!LAZYLEN(src_object?.open_uis))
return 0
var/count = 0
var/seconds_per_tick = wait * 0.1
for(var/datum/tgui/ui in src_object.open_uis)
// Check if UI is valid.
if(ui?.src_object && ui.user && ui.src_object.ui_host(ui.user))
INVOKE_ASYNC(ui, TYPE_PROC_REF(/datum/tgui, process), wait * 0.1, TRUE)
INVOKE_ASYNC(ui, TYPE_PROC_REF(/datum/tgui, process), seconds_per_tick, TRUE)
count++
return count
@@ -270,9 +272,10 @@ PROCESSING_SUBSYSTEM_DEF(tgui)
var/count = 0
if(length(user?.tgui_open_uis) == 0)
return count
var/seconds_per_tick = wait * 0.1
for(var/datum/tgui/ui in user.tgui_open_uis)
if(isnull(src_object) || ui.src_object == src_object)
ui.process(wait * 0.1, force = 1)
ui.process(seconds_per_tick, force = 1)
count++
return count
+2 -2
View File
@@ -5,8 +5,8 @@ SUBSYSTEM_DEF(turf_fire)
flags = SS_NO_INIT
var/list/fires = list()
/datum/controller/subsystem/turf_fire/fire()
var/seconds_per_tick = (wait * 0.1) // Equivalent to wait / (1 SECOND) but micro-optimized
/datum/controller/subsystem/turf_fire/fire(resumed)
var/seconds_per_tick = wait * 0.1
for(var/obj/turf_fire/fire as anything in fires)
fire.process(seconds_per_tick)
if(MC_TICK_CHECK)