Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Process procs now properly utilize deltatime when implementing rates, timers and probabilities #4090

Open
wants to merge 21 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
8 changes: 4 additions & 4 deletions .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ In a lot of our older code, `process()` is frame dependent. Here's some example
var/health = 100
var/health_loss = 4 //We want to lose 2 health per second, so 4 per SSmobs process

/mob/testmob/process(delta_time) //SSmobs runs once every 2 seconds
/mob/testmob/process(seconds_per_tick) //SSmobs runs once every 2 seconds
health -= health_loss
```

Expand All @@ -274,11 +274,11 @@ How do we solve this? By using delta-time. Delta-time is the amount of seconds y
var/health = 100
var/health_loss = 2 //Health loss every second

/mob/testmob/process(delta_time) //SSmobs runs once every 2 seconds
health -= health_loss * delta_time
/mob/testmob/process(seconds_per_tick) //SSmobs runs once every 2 seconds
health -= health_loss * seconds_per_tick
```

In the above example, we made our health_loss variable a per second value rather than per process. In the actual process() proc we then make use of deltatime. Because SSmobs runs once every 2 seconds. Delta_time would have a value of 2. This means that by doing health_loss * delta_time, you end up with the correct amount of health_loss per process, but if for some reason the SSmobs subsystem gets changed to be faster or slower in a PR, your health_loss variable will work the same.
In the above example, we made our health_loss variable a per second value rather than per process. In the actual process() proc we then make use of seconds_per_tick. Because SSmobs runs once every 2 seconds. seconds_per_tick would have a value of 2. This means that by doing health_loss * seconds_per_tick, you end up with the correct amount of health_loss per process, but if for some reason the SSmobs subsystem gets changed to be faster or slower in a PR, your health_loss variable will work the same.

For example, if SSmobs is set to run once every 4 seconds, it would call process once every 4 seconds and multiply your health_loss var by 4 before subtracting it. Ensuring that your code is frame independent.

Expand Down
6 changes: 3 additions & 3 deletions code/__DEFINES/atmospherics.dm
Original file line number Diff line number Diff line change
Expand Up @@ -157,9 +157,9 @@
/// (kPa) What pressure pumps and powered equipment max out at.
#define MAX_OUTPUT_PRESSURE 4500
/// (L/s) Maximum speed powered equipment can work at.
#define MAX_TRANSFER_RATE 200
/// 10% of an overclocked volume pump leaks into the air
#define VOLUME_PUMP_LEAK_AMOUNT 0.1
#define MAX_TRANSFER_RATE 400
/// How many percent of the contents that an overclocked volume pumps leak into the air
#define VOLUME_PUMP_LEAK_AMOUNT 0.2
//used for device_type vars
#define UNARY 1
#define BINARY 2
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// All signals send the source datum of the signal as the first argument

// /obj/machinery/power/supermatter_crystal signals
/// from /obj/machinery/power/supermatter_crystal/process_atmos(); when the SM delam reaches the point of sounding alarms
/// from /obj/machinery/power/supermatter_crystal/process_atmos(seconds_per_tick); when the SM delam reaches the point of sounding alarms
#define COMSIG_SUPERMATTER_DELAM_START_ALARM "sm_delam_start_alarm"
/// from /obj/machinery/power/supermatter_crystal/process_atmos(); when the SM sounds an audible alarm
/// from /obj/machinery/power/supermatter_crystal/process_atmos(seconds_per_tick); when the SM sounds an audible alarm
#define COMSIG_SUPERMATTER_DELAM_ALARM "sm_delam_alarm"
14 changes: 5 additions & 9 deletions code/__DEFINES/maths.dm
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,11 @@
// Returns the nth root of x.
#define ROOT(n, x) ((x) ** (1 / (n)))

/// Low-pass filter a value to smooth out high frequent peaks. This can be thought of as a moving average filter as well.
/// seconds_per_tick is how many seconds since we last ran this command. RC is the filter constant, high RC means more smoothing
/// See https://en.wikipedia.org/wiki/Low-pass_filter#Simple_infinite_impulse_response_filter for the maths
#define LPFILTER(memory, signal, seconds_per_tick, RC) (seconds_per_tick / (RC + seconds_per_tick)) * signal + (1 - seconds_per_tick / (RC + seconds_per_tick)) * memory

// The quadratic formula. Returns a list with the solutions, or an empty list
// if they are imaginary.
/proc/SolveQuadratic(a, b, c)
Expand Down Expand Up @@ -286,15 +291,6 @@
/// For example, if you want an event to happen with a 10% per second chance, but your proc only runs every 5 seconds, do `if(prob(100*SPT_PROB_RATE(0.1, 5)))`
#define SPT_PROB_RATE(prob_per_second, seconds_per_tick) (1 - (1 - (prob_per_second)) ** (seconds_per_tick))

//make spawners use this -Erika

/// Like SPT_PROB_RATE but easier to use, simply put `if(SPT_PROB(10, 5))`
#define SPT_PROB(prob_per_second_percent, seconds_per_tick) (prob(100*SPT_PROB_RATE((prob_per_second_percent)/100, (seconds_per_tick))))

/// Converts a probability/second chance to probability/delta_time chance
/// For example, if you want an event to happen with a 10% per second chance, but your proc only runs every 5 seconds, do `if(prob(100*DT_PROB_RATE(0.1, 5)))`
#define DT_PROB_RATE(prob_per_second, delta_time) (1 - (1 - (prob_per_second)) ** (delta_time))

/// Like DT_PROB_RATE but easier to use, simply put `if(DT_PROB(10, 5))`
#define DT_PROB(prob_per_second_percent, delta_time) (prob(100*DT_PROB_RATE((prob_per_second_percent)/100, (delta_time))))
// )
3 changes: 2 additions & 1 deletion code/__DEFINES/radiation.dm
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,5 @@ Ask ninjanomnom if they're around

#define RAD_MEASURE_SMOOTHING 5

#define RAD_GRACE_PERIOD 2
#define RAD_GEIGER_RC 4 // RC-constant for the LP filter for geiger counters. See #define LPFILTER for more info.
#define RAD_GEIGER_GRACE_PERIOD 4 // How many seconds after we last detect a radiation pulse until we stop blipping
7 changes: 7 additions & 0 deletions code/__DEFINES/subsystems.dm
Original file line number Diff line number Diff line change
Expand Up @@ -253,3 +253,10 @@
#define VOTE_WINNER_METHOD_WEIGHTED_RANDOM "Weighted Random"
/// There is no winner for this vote.
#define VOTE_WINNER_METHOD_NONE "None"

// Subsystem delta times or tickrates, in seconds. I.e, how many seconds in between each process() call for objects being processed by that subsystem.
// Only use these defines if you want to access some other objects processing seconds_per_tick, otherwise use the seconds_per_tick that is sent as a parameter to process()
#define SSFLUIDS_DT (SSfluids.wait/10)
#define SSMACHINES_DT (SSmachines.wait/10)
#define SSMOBS_DT (SSmobs.wait/10)
#define SSOBJ_DT (SSobj.wait/10)
2 changes: 1 addition & 1 deletion code/_onclick/telekinesis.dm
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@
tk_user = null
return ..()

/obj/item/tk_grab/process()
/obj/item/tk_grab/process(seconds_per_tick)
if(check_if_focusable(focus)) //if somebody grabs your thing, no waiting for them to put it down and hitting them again.
update_appearance()

Expand Down
2 changes: 1 addition & 1 deletion code/controllers/subsystem.dm
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
var/init_order = INIT_ORDER_DEFAULT

/// Time to wait (in deciseconds) between each call to fire(). Must be a positive integer.
var/wait = 20
var/wait = 2 SECONDS

/// 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
Expand Down
31 changes: 16 additions & 15 deletions code/controllers/subsystem/air.dm
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ SUBSYSTEM_DEF(air)
name = "Atmospherics"
init_order = INIT_ORDER_AIR
priority = FIRE_PRIORITY_AIR
wait = 5
wait = 0.5 SECONDS
flags = SS_BACKGROUND
runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME

Expand Down Expand Up @@ -75,6 +75,8 @@ SUBSYSTEM_DEF(air)
// Excited group processing will try to equalize groups with total pressure difference less than this amount.
var/excited_group_pressure_goal = 1

var/lasttick = 0

/datum/controller/subsystem/air/stat_entry(msg)
msg += "C:{"
msg += "HP:[round(cost_highpressure,1)]|"
Expand Down Expand Up @@ -125,6 +127,7 @@ SUBSYSTEM_DEF(air)

/datum/controller/subsystem/air/fire(resumed = 0)
var/timer = TICK_USAGE_REAL
var/seconds_per_tick = wait * 0.1

//Rebuilds can happen at any time, so this needs to be done outside of the normal system
cost_rebuilds = 0
Expand Down Expand Up @@ -177,7 +180,7 @@ SUBSYSTEM_DEF(air)

if(currentpart == SSAIR_PIPENETS || !resumed)
timer = TICK_USAGE_REAL
process_pipenets(resumed)
process_pipenets(seconds_per_tick, resumed)
cost_pipenets = MC_AVERAGE(cost_pipenets, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer))
if(state != SS_RUNNING)
return
Expand All @@ -187,7 +190,7 @@ SUBSYSTEM_DEF(air)
// This is only machinery like filters, mixers that don't interact with air
if(currentpart == SSAIR_ATMOSMACHINERY)
timer = TICK_USAGE_REAL
process_atmos_machinery(resumed)
process_atmos_machinery(seconds_per_tick, resumed)
cost_atmos_machinery = MC_AVERAGE(cost_atmos_machinery, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer))
if(state != SS_RUNNING)
return
Expand All @@ -205,7 +208,7 @@ SUBSYSTEM_DEF(air)

if(currentpart == SSAIR_ATMOSMACHINERY_AIR)
timer = TICK_USAGE_REAL
process_atmos_air_machinery(resumed)
process_atmos_air_machinery(seconds_per_tick, resumed)
cost_atmos_machinery_air = MC_AVERAGE(cost_atmos_machinery_air, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer))
if(state != SS_RUNNING)
return
Expand All @@ -214,7 +217,7 @@ SUBSYSTEM_DEF(air)

if(currentpart == SSAIR_HOTSPOTS)
timer = TICK_USAGE_REAL
process_hotspots(resumed)
process_hotspots(seconds_per_tick, resumed)
cost_hotspots = MC_AVERAGE(cost_hotspots, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer))
if(state != SS_RUNNING)
return
Expand Down Expand Up @@ -292,7 +295,7 @@ SUBSYSTEM_DEF(air)
currentrun -= machine


/datum/controller/subsystem/air/proc/process_pipenets(resumed = 0)
/datum/controller/subsystem/air/proc/process_pipenets(seconds_per_tick, resumed = FALSE)
if (!resumed)
src.currentrun = networks.Copy()
//cache for sanic speed (lists are references anyways)
Expand All @@ -301,7 +304,7 @@ SUBSYSTEM_DEF(air)
var/datum/thing = currentrun[currentrun.len]
currentrun.len--
if(thing)
thing.process()
thing.process(seconds_per_tick)
else
networks.Remove(thing)
if(MC_TICK_CHECK)
Expand Down Expand Up @@ -387,8 +390,7 @@ SUBSYSTEM_DEF(air)
if (MC_TICK_CHECK)
return

/datum/controller/subsystem/air/proc/process_atmos_machinery(resumed = 0)
var/seconds = wait * 0.1
/datum/controller/subsystem/air/proc/process_atmos_machinery(seconds_per_tick, resumed = 0)
if (!resumed)
src.currentrun = atmos_machinery.Copy()
//cache for sanic speed (lists are references anyways)
Expand All @@ -398,13 +400,12 @@ SUBSYSTEM_DEF(air)
currentrun.len--
if(!M)
atmos_machinery -= M
if(M.process_atmos(seconds) == PROCESS_KILL)
if(M.process_atmos(seconds_per_tick) == PROCESS_KILL)
stop_processing_machine(M)
if(MC_TICK_CHECK)
return

/datum/controller/subsystem/air/proc/process_atmos_air_machinery(resumed = 0)
var/seconds = wait * 0.1
/datum/controller/subsystem/air/proc/process_atmos_air_machinery(seconds_per_tick, resumed = 0)
if (!resumed)
src.currentrun = atmos_air_machinery.Copy()
//cache for sanic speed (lists are references anyways)
Expand All @@ -414,14 +415,14 @@ SUBSYSTEM_DEF(air)
currentrun.len--
if(!M)
atmos_air_machinery -= M
if(M.process_atmos(seconds) == PROCESS_KILL)
if(M.process_atmos(seconds_per_tick) == PROCESS_KILL)
stop_processing_machine(M)
if(MC_TICK_CHECK)
return

/datum/controller/subsystem/air/proc/process_turf_heat()

/datum/controller/subsystem/air/proc/process_hotspots(resumed = FALSE)
/datum/controller/subsystem/air/proc/process_hotspots(seconds_per_tick, resumed = FALSE)
if (!resumed)
src.currentrun = hotspots.Copy()
//cache for sanic speed (lists are references anyways)
Expand All @@ -430,7 +431,7 @@ SUBSYSTEM_DEF(air)
var/obj/effect/hotspot/H = currentrun[currentrun.len]
currentrun.len--
if (H)
H.process()
H.process(seconds_per_tick)
else
hotspots -= H
if(MC_TICK_CHECK)
Expand Down
2 changes: 1 addition & 1 deletion code/controllers/subsystem/events.dm
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ SUBSYSTEM_DEF(events)
var/datum/thing = currentrun[currentrun.len]
currentrun.len--
if(thing)
thing.process()
thing.process(wait * 0.1)
else
running.Remove(thing)
if (MC_TICK_CHECK)
Expand Down
3 changes: 2 additions & 1 deletion code/controllers/subsystem/fire_burning.dm
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ SUBSYSTEM_DEF(fire_burning)

//cache for sanic speed (lists are references anyways)
var/list/currentrun = src.currentrun
var/seconds_per_tick = wait * 0.1

while(currentrun.len)
var/obj/O = currentrun[currentrun.len]
Expand All @@ -36,7 +37,7 @@ SUBSYSTEM_DEF(fire_burning)

if(O.resistance_flags & ON_FIRE) //in case an object is extinguished while still in currentrun
if(!(O.resistance_flags & FIRE_PROOF))
O.take_damage(20, BURN, "fire", 0)
O.take_damage(10 * seconds_per_tick, BURN, BURN, 0)
else
O.extinguish()

Expand Down
4 changes: 2 additions & 2 deletions code/controllers/subsystem/machines.dm
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ SUBSYSTEM_DEF(machines)
name = "Machines"
init_order = INIT_ORDER_MACHINES
flags = SS_KEEP_TIMING
wait = 2 SECONDS
var/list/processing = list()
var/list/currentrun = list()
var/list/powernets = list()
Expand Down Expand Up @@ -43,11 +44,10 @@ SUBSYSTEM_DEF(machines)
//cache for sanic speed (lists are references anyways)
var/list/currentrun = src.currentrun

var/seconds = wait * 0.1
while(currentrun.len)
var/obj/machinery/thing = currentrun[currentrun.len]
currentrun.len--
if(QDELETED(thing) || thing.process(seconds) == PROCESS_KILL)
if(QDELETED(thing) || thing.process(wait * 0.1) == PROCESS_KILL)
processing -= thing
if (!QDELETED(thing))
thing.datum_flags &= ~DF_ISPROCESSING
Expand Down
5 changes: 3 additions & 2 deletions code/controllers/subsystem/mobs.dm
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ SUBSYSTEM_DEF(mobs)
priority = FIRE_PRIORITY_MOBS
flags = SS_KEEP_TIMING | SS_NO_INIT
runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME
wait = 2 SECONDS

var/list/currentrun = list()

Expand All @@ -26,18 +27,18 @@ SUBSYSTEM_DEF(mobs)
.["custom"] = cust

/datum/controller/subsystem/mobs/fire(resumed = 0)
var/seconds = wait * 0.1
if (!resumed)
src.currentrun = GLOB.mob_living_list.Copy()

//cache for sanic speed (lists are references anyways)
var/list/currentrun = src.currentrun
var/times_fired = src.times_fired
var/seconds_per_tick = wait / (1 SECONDS)
while(currentrun.len)
var/mob/living/L = currentrun[currentrun.len]
currentrun.len--
if(L)
L.Life(seconds, times_fired)
L.Life(seconds_per_tick, times_fired)
else
GLOB.mob_living_list.Remove(L)
stack_trace("[L] no longer exists in mob_living_list")
Expand Down
1 change: 1 addition & 0 deletions code/controllers/subsystem/moods.dm
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ PROCESSING_SUBSYSTEM_DEF(mood)
name = "Mood"
flags = SS_NO_INIT | SS_BACKGROUND
priority = 20
wait = 1 SECONDS
4 changes: 1 addition & 3 deletions code/controllers/subsystem/processing/fastprocess.dm
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
//Fires five times every second.

PROCESSING_SUBSYSTEM_DEF(fastprocess)
name = "Fast Processing"
wait = 2
wait = 0.2 SECONDS
stat_tag = "FP"
2 changes: 1 addition & 1 deletion code/controllers/subsystem/processing/nanites.dm
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PROCESSING_SUBSYSTEM_DEF(nanites)
name = "Nanites"
flags = SS_BACKGROUND|SS_POST_FIRE_TIMING|SS_NO_INIT
wait = 10
wait = 1 SECONDS

var/list/datum/nanite_cloud_backup/cloud_backups = list()
var/list/mob/living/nanite_monitored_mobs = list()
Expand Down
2 changes: 1 addition & 1 deletion code/controllers/subsystem/processing/obj.dm
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ PROCESSING_SUBSYSTEM_DEF(obj)
name = "Objects"
priority = FIRE_PRIORITY_OBJ
flags = SS_NO_INIT
wait = 20
wait = 2 SECONDS
16 changes: 15 additions & 1 deletion code/controllers/subsystem/processing/processing.dm
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,20 @@ SUBSYSTEM_DEF(processing)
if (MC_TICK_CHECK)
return

/datum/proc/process(delta_time)
/**
* This proc is called on a datum on every "cycle" if it is being processed by a subsystem. The time between each cycle is determined by the subsystem's "wait" setting.
* You can start and stop processing a datum using the START_PROCESSING and STOP_PROCESSING defines.
*
* Since the wait setting of a subsystem can be changed at any time, it is important that any rate-of-change that you implement in this proc is multiplied by the seconds_per_tick that is sent as a parameter,
* Additionally, any "prob" you use in this proc should instead use the SPT_PROB define to make sure that the final probability per second stays the same even if the subsystem's wait is altered.
* Examples where this must be considered:
* - Implementing a cooldown timer, use `mytimer -= seconds_per_tick`, not `mytimer -= 1`. This way, `mytimer` will always have the unit of seconds
* - Damaging a mob, do `L.adjustFireLoss(20 * seconds_per_tick)`, not `L.adjustFireLoss(20)`. This way, the damage per second stays constant even if the wait of the subsystem is changed
* - Probability of something happening, do `if(SPT_PROB(25, seconds_per_tick))`, not `if(prob(25))`. This way, if the subsystem wait is e.g. lowered, there won't be a higher chance of this event happening per second
*
* If you override this do not call parent, as it will return PROCESS_KILL. This is done to prevent objects that dont override process() from staying in the processing list
*/

/datum/proc/process(seconds_per_tick)
set waitfor = 0
return PROCESS_KILL
2 changes: 1 addition & 1 deletion code/controllers/subsystem/processing/quirks.dm
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ PROCESSING_SUBSYSTEM_DEF(quirks)
name = "Quirks"
init_order = INIT_ORDER_QUIRKS
flags = SS_BACKGROUND
wait = 10
runlevels = RUNLEVEL_GAME
wait = 1 SECONDS

var/list/quirks = list() //Assoc. list of all roundstart quirk datum types; "name" = /path/
var/list/quirk_points = list() //Assoc. list of quirk names and their "point cost"; positive numbers are good traits, and negative ones are bad
Expand Down
1 change: 1 addition & 0 deletions code/controllers/subsystem/radiation.dm
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
PROCESSING_SUBSYSTEM_DEF(radiation)
name = "Radiation"
flags = SS_NO_INIT | SS_BACKGROUND
wait = 1 SECONDS

var/list/warned_atoms = list()

Expand Down
2 changes: 1 addition & 1 deletion code/controllers/subsystem/statpanel.dm
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ SUBSYSTEM_DEF(statpanels)

/// Takes a client, attempts to generate object images for it
/// We will update the client with any improvements we make when we're done
/datum/object_window_info/process(delta_time)
/datum/object_window_info/process(seconds_per_tick)
// Cache the datum access for sonic speed
var/list/to_make = atoms_to_imagify
var/list/newly_seen = atoms_to_images
Expand Down
Loading
Loading