mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-19 22:23:11 +00:00
* Optimization pass focused on foam code (saves about 30% of cpu usage I think) (#76104) ## About The Pull Request Foam is crummy at high load rn, both because it runs on a low priority background subsystem, and because it wastes a bit of time. Let's reduce usage (while speeding up a bunch of other stuff too), and give it more cpu generally. [Optimizes reagent processing somewhat](d409bd4afc) Turns out most of the cost of foam is the reagents it carries, and the varying effects they have I'm doing my best here to optimize them without touching "user space" too much That means doing things like prechecking if we're gonna spawn on top of an existing decal (from glitter, flour, etc), and using that same proc to also avoid spawning on unacceptable turfs (I had to convert inheritance to a bitflag system to make this work, but I think that's ok since we want it imparative anyhow) It's actually nice for code quality too, since it lets me clean up code that was using raw locates and weird var pong. god I wish I had implied types man [Optimizes foam spreading in its most accursed aspect, reagent copying](5cc56a64ad) Holy shit reagent code is a lot. I'm doing a bunch of small things here. istype in init -> typecache, removing procs that are called once and loop over a list we JUST looped over (ph and the caching for reactions in particular) I am mainly trying to optimize copy_to here, since that's what foam spams As a part of this, I removed a pair of update_total and handle_reactions calls that were done on the reagents we are copying FROM I have no god damn idea why you would want to do that, but if anything is relying on the copy proc modifying the source, then that code deserves to break Speaking of, I cleaned up handle_reaction's main filter loop a lot, removed a lot of redundant vars and changed it from a full loop w tracker vars to an early exit pattern This meant using a loop label, which is unfortunate, but this is the fastest method, and it does end up cleaning up the code significantly, Which is nice Oh also I made the required_other var function even if there is no atom attached to the reaction, since I don't see why it wouldn't This last bit is gonna get a bit esoteric so bear with me Failing calls (which are most of them) to handle_reactions are going to be fastest if they need to check as few reactions as possible One reagent in a reaction's required list is marked as the "primary", and thus gets to trigger checking it. We need all the reagents to react anyhow, so we might as well only check if we have one particular one to avoid double checking Anyhow, in order to make most calls the fastest, we want these reactions distributed as evenly as possible across all our reagents. The current way of doing this is just taking the first reagent in the requirements list and using it, which is not ideal Instead of that, lets figure out how many reactions each reagent is in, then divy reactions up based off that and the currently divvied reactions This doubles the reagent index count, and takes the most common reagent, water, from 67 reactions to I think like 22 Does some other general cleaning in reagent code too, etc etc etc [Fixes runtimes from the forced gravity element being applied more then once](941d067611) I feel like this element should take a trait source or something to make them potentially unique, it's too easy to accidentally override one with another [Removes connect_loc usage in atmos_sensitive, replaces it with direct reg/unreg](de1c76029d) I only really used it because I liked the componentization, but it costs like 0.2 seconds off init alone which is really stupid, so let's just do this the very slightly harder way [Micros foam code slightly by inlining a LinkBlockedWithAccess call](744da3694c) This is in the space of like 0.05 seconds kinda save so I can put it back if you'd like, the double loop just felt silly [Changes how foam processes slightly](ee5e633e32) Rather then treating spreading and processing as separate actions, we do both in sync. This makes foam fade faster when spreading, which is good cause the whole spread but unclearing foam thing looks silly. It also avoids the potential bad ending of foam spreading into itself, backwards and forwards. This is better I promise. [Bumps fluid priority closer to heavy eaters, moves it off background](811797f09d) Also fixes a bug where foam would travel under public access airlocks. ## Why It's Good For The Game Saves a lot of cpu just in general, from both init and live. In theory makes foam faster, tho I'd have to test that on live at highpop to see if I've actually succeeded or not. Guess we'll see. * Optimization pass focused on foam code (saves about 30% of cpu usage I think) --------- Co-authored-by: LemonInTheDark <58055496+LemonInTheDark@users.noreply.github.com> Co-authored-by: Bloop <vinylspiders@gmail.com>
252 lines
8.7 KiB
Plaintext
252 lines
8.7 KiB
Plaintext
// Flags indicating what parts of the fluid the subsystem processes.
|
|
/// Indicates that a fluid subsystem processes fluid spreading.
|
|
#define SS_PROCESSES_SPREADING (1<<0)
|
|
/// Indicates that a fluid subsystem processes fluid effects.
|
|
#define SS_PROCESSES_EFFECTS (1<<1)
|
|
|
|
/**
|
|
* # Fluid Subsystem
|
|
*
|
|
* A subsystem that processes the propagation and effects of a particular fluid.
|
|
*
|
|
* Both fluid spread and effect processing are handled through a carousel system.
|
|
* Fluids being spread and fluids being processed are organized into buckets.
|
|
* Each fresh (non-resumed) fire one bucket of each is selected to be processed.
|
|
* These selected buckets are then fully processed.
|
|
* The next fresh fire selects the next bucket in each set for processing.
|
|
* If this would walk off the end of a carousel list we wrap back to the first element.
|
|
* This effectively makes each set a circular list, hence a carousel.
|
|
*/
|
|
SUBSYSTEM_DEF(fluids)
|
|
name = "Fluid"
|
|
wait = 0 // Will be autoset to whatever makes the most sense given the spread and effect waits.
|
|
flags = SS_KEEP_TIMING
|
|
runlevels = RUNLEVEL_GAME|RUNLEVEL_POSTGAME
|
|
priority = FIRE_PRIORITY_FLUIDS
|
|
|
|
// Fluid spread processing:
|
|
/// The amount of time (in deciseconds) before a fluid node is created and when it spreads.
|
|
var/spread_wait = 1 SECONDS
|
|
/// The number of buckets in the spread carousel.
|
|
var/num_spread_buckets
|
|
/// The set of buckets containing fluid nodes to spread.
|
|
var/list/spread_carousel
|
|
/// The index of the spread carousel bucket currently being processed.
|
|
var/spread_bucket_index
|
|
/// The set of fluid nodes we are currently processing spreading for.
|
|
var/list/currently_spreading
|
|
|
|
// Fluid effect processing:
|
|
/// The amount of time (in deciseconds) between effect processing ticks for each fluid node.
|
|
var/effect_wait = 1 SECONDS
|
|
/// The number of buckets in the effect carousel.
|
|
var/num_effect_buckets
|
|
/// The set of buckets containing fluid nodes to process effects for.
|
|
var/list/effect_carousel
|
|
/// The index of the currently processing bucket on the effect carousel.
|
|
var/effect_bucket_index
|
|
/// The set of fluid nodes we are currently processing effects for.
|
|
var/list/currently_processing
|
|
|
|
/datum/controller/subsystem/fluids/Initialize()
|
|
initialize_waits()
|
|
initialize_spread_carousel()
|
|
initialize_effect_carousel()
|
|
return SS_INIT_SUCCESS
|
|
|
|
/**
|
|
* Initializes the subsystem waits.
|
|
*
|
|
* Ensures that the subsystem's fire wait evenly splits the spread and effect waits.
|
|
*/
|
|
/datum/controller/subsystem/fluids/proc/initialize_waits()
|
|
if (spread_wait <= 0)
|
|
WARNING("[src] has the invalid spread wait [spread_wait].")
|
|
spread_wait = 1 SECONDS
|
|
if (effect_wait <= 0)
|
|
WARNING("[src] has the invalid effect wait [effect_wait].")
|
|
spread_wait = 1 SECONDS
|
|
|
|
// Sets the overall wait of the subsystem to evenly divide both the effect and spread waits.
|
|
var/max_wait = Gcd(spread_wait, effect_wait)
|
|
if (max_wait < wait || wait <= 0)
|
|
wait = max_wait
|
|
else
|
|
// If the wait of the subsystem overall is set to a valid value make the actual wait of the subsystem evenly divide that as well.
|
|
// Makes effect bubbling possible with identical spread and effect waits.
|
|
wait = Gcd(wait, max_wait)
|
|
|
|
|
|
/**
|
|
* Initializes the carousel used to process fluid spreading.
|
|
*
|
|
* Synchronizes the spread delta time with the actual target spread tick rate.
|
|
* Builds the carousel buckets used to queue spreads.
|
|
*/
|
|
/datum/controller/subsystem/fluids/proc/initialize_spread_carousel()
|
|
// Make absolutely certain that the spread wait is in sync with the target spread tick rate.
|
|
num_spread_buckets = round(spread_wait / wait)
|
|
spread_wait = wait * num_spread_buckets
|
|
|
|
spread_carousel = list()
|
|
spread_carousel.len = num_spread_buckets
|
|
for(var/i in 1 to num_spread_buckets)
|
|
spread_carousel[i] = list()
|
|
currently_spreading = list()
|
|
spread_bucket_index = 1
|
|
|
|
/**
|
|
* Initializes the carousel used to process fluid effects.
|
|
*
|
|
* Synchronizes the spread delta time with the actual target spread tick rate.
|
|
* Builds the carousel buckets used to bubble processing.
|
|
*/
|
|
/datum/controller/subsystem/fluids/proc/initialize_effect_carousel()
|
|
// Make absolutely certain that the effect wait is in sync with the target effect tick rate.
|
|
num_effect_buckets = round(effect_wait / wait)
|
|
effect_wait = wait * num_effect_buckets
|
|
|
|
effect_carousel = list()
|
|
effect_carousel.len = num_effect_buckets
|
|
for(var/i in 1 to num_effect_buckets)
|
|
effect_carousel[i] = list()
|
|
currently_processing = list()
|
|
effect_bucket_index = 1
|
|
|
|
|
|
/datum/controller/subsystem/fluids/fire(resumed)
|
|
var/seconds_per_tick
|
|
var/cached_bucket_index
|
|
var/list/obj/effect/particle_effect/fluid/currentrun
|
|
// Ok so like I get the lighting style splittick but why are we doing this churn thing
|
|
// It seems like a bad idea for processing to get out of step with spreading
|
|
MC_SPLIT_TICK_INIT(2)
|
|
|
|
MC_SPLIT_TICK // Start processing fluid spread (we take a lot of cpu for ourselves, spreading is more important after all)
|
|
if(!resumed)
|
|
spread_bucket_index = WRAP_UP(spread_bucket_index, num_spread_buckets)
|
|
currently_spreading = spread_carousel[spread_bucket_index]
|
|
spread_carousel[spread_bucket_index] = list() // Reset the bucket so we don't process an _entire station's worth of foam_ spreading every 2 ticks when the foam flood event happens.
|
|
|
|
seconds_per_tick = spread_wait / (1 SECONDS)
|
|
currentrun = currently_spreading
|
|
while(currentrun.len)
|
|
var/obj/effect/particle_effect/fluid/to_spread = currentrun[currentrun.len]
|
|
currentrun.len--
|
|
|
|
if(!QDELETED(to_spread))
|
|
to_spread.spread(seconds_per_tick)
|
|
to_spread.spread_bucket = null
|
|
|
|
if (MC_TICK_CHECK)
|
|
break
|
|
|
|
MC_SPLIT_TICK // Start processing fluid effects:
|
|
if(!resumed)
|
|
effect_bucket_index = WRAP_UP(effect_bucket_index, num_effect_buckets)
|
|
var/list/tmp_list = effect_carousel[effect_bucket_index]
|
|
currently_processing = tmp_list.Copy()
|
|
|
|
seconds_per_tick = effect_wait / (1 SECONDS)
|
|
cached_bucket_index = effect_bucket_index
|
|
currentrun = currently_processing
|
|
while(currentrun.len)
|
|
var/obj/effect/particle_effect/fluid/to_process = currentrun[currentrun.len]
|
|
currentrun.len--
|
|
|
|
if (QDELETED(to_process) || to_process.process(seconds_per_tick) == PROCESS_KILL)
|
|
effect_carousel[cached_bucket_index] -= to_process
|
|
to_process.effect_bucket = null
|
|
to_process.datum_flags &= ~DF_ISPROCESSING
|
|
|
|
if (MC_TICK_CHECK)
|
|
break
|
|
|
|
/**
|
|
* Queues a fluid node to spread later after one full carousel rotation.
|
|
*
|
|
* Arguments:
|
|
* - [node][/obj/effect/particle_effect/fluid]: The node to queue to spread.
|
|
*/
|
|
/datum/controller/subsystem/fluids/proc/queue_spread(obj/effect/particle_effect/fluid/node)
|
|
if (node.spread_bucket)
|
|
return
|
|
|
|
spread_carousel[spread_bucket_index] += node
|
|
node.spread_bucket = spread_bucket_index
|
|
|
|
/**
|
|
* Cancels a queued spread of a fluid node.
|
|
*
|
|
* Arguments:
|
|
* - [node][/obj/effect/particle_effect/fluid]: The node to cancel the spread of.
|
|
*/
|
|
/datum/controller/subsystem/fluids/proc/cancel_spread(obj/effect/particle_effect/fluid/node)
|
|
if(!node.spread_bucket)
|
|
return
|
|
|
|
var/bucket_index = node.spread_bucket
|
|
spread_carousel[bucket_index] -= node
|
|
if (bucket_index == spread_bucket_index)
|
|
currently_spreading -= node
|
|
|
|
node.spread_bucket = null
|
|
|
|
/**
|
|
* Starts processing the effects of a fluid node.
|
|
*
|
|
* The fluid node will next process after one full bucket rotation.
|
|
*
|
|
* Arguments:
|
|
* - [node][/obj/effect/particle_effect/fluid]: The node to start processing.
|
|
*/
|
|
/datum/controller/subsystem/fluids/proc/start_processing(obj/effect/particle_effect/fluid/node)
|
|
if (node.datum_flags & DF_ISPROCESSING || node.effect_bucket)
|
|
return
|
|
|
|
// Edit this value to make all fluids process effects (at the same time|offset by when they started processing| -> offset by a random amount <- )
|
|
var/bucket_index = rand(1, num_effect_buckets)
|
|
effect_carousel[bucket_index] += node
|
|
node.effect_bucket = bucket_index
|
|
node.datum_flags |= DF_ISPROCESSING
|
|
|
|
/**
|
|
* Stops processing the effects of a fluid node.
|
|
*
|
|
* Arguments:
|
|
* - [node][/obj/effect/particle_effect/fluid]: The node to stop processing.
|
|
*/
|
|
/datum/controller/subsystem/fluids/proc/stop_processing(obj/effect/particle_effect/fluid/node)
|
|
if(!(node.datum_flags & DF_ISPROCESSING))
|
|
return
|
|
|
|
var/bucket_index = node.effect_bucket
|
|
if(!bucket_index)
|
|
return
|
|
|
|
effect_carousel[bucket_index] -= node
|
|
if (bucket_index == effect_bucket_index)
|
|
currently_processing -= node
|
|
|
|
node.effect_bucket = null
|
|
node.datum_flags &= ~DF_ISPROCESSING
|
|
|
|
#undef SS_PROCESSES_SPREADING
|
|
#undef SS_PROCESSES_EFFECTS
|
|
|
|
|
|
// Subtypes:
|
|
|
|
/// The subsystem responsible for processing smoke propagation and effects.
|
|
FLUID_SUBSYSTEM_DEF(smoke)
|
|
name = "Smoke"
|
|
spread_wait = 0.1 SECONDS
|
|
effect_wait = 2.0 SECONDS
|
|
|
|
/// The subsystem responsible for processing foam propagation and effects.
|
|
FLUID_SUBSYSTEM_DEF(foam)
|
|
name = "Foam"
|
|
wait = 0.1 SECONDS // Makes effect bubbling work with foam.
|
|
spread_wait = 0.2 SECONDS
|
|
effect_wait = 0.2 SECONDS
|