[MIRROR] Optimizations for reactions [MDB IGNORE] (#25788)

* Optimizations for reactions (#80380)

## About The Pull Request
1. Fixes #80365

The lowers the rounding accuracy for reaction rate from
`CHEMICAL_VOLUME_ROUNDING(0.01)` to
`CHEMICAL_QUANTIZATION_LEVEL(0.0001)`. This stops reactions from
abruptly ending when either their temperature/ph/volume become very low
values.

This also removes a call to `handle_reactions()` after the reaction has
finished so it that it doesn't restart reactions that have failed due to
failed environmental conditions(low ph/temps far below the optimal
value). This not only saves overhead but can also stop potential
infinite reaction loops(where if a reaction fails we try to restart it
again)

2. Simplifies math for temperature handling and stops each react step
from calling `handle_reactions()` when new reagent products are formed
thus making reactions slightly faster. Also ensures early return if
adding/removing reagents failed at any point & other smaller. nitpicks

3. Removes excess call to `update_total()` in each react step making it
even faster

## Changelog
🆑
fix: Reactions whose temps/ph values fall way below their optimal values
no longer restart & prevents infinite loops. Made reaction code slightly
faster
/🆑

* Optimizations for reactions

---------

Co-authored-by: SyncIt21 <110812394+SyncIt21@users.noreply.github.com>
This commit is contained in:
SkyratBot
2023-12-22 22:02:46 -05:00
committed by GitHub
co-authored by SyncIt21
parent 63a443ed30
commit b8ee1409bd
4 changed files with 27 additions and 23 deletions
+23 -17
View File
@@ -168,7 +168,7 @@
step_target_vol = 0
reacted_vol = 0 //Because volumes can be lost mid reactions
for(var/product in reaction.results)
step_target_vol += (multiplier * reaction.results[product])
step_target_vol += multiplier * reaction.results[product]
reacted_vol += holder.get_reagent_amount(product)
target_vol = reacted_vol + step_target_vol
return TRUE
@@ -277,7 +277,7 @@
//Calculate DeltaT (Deviation of T from optimal)
if(!reaction.is_cold_recipe)
if (cached_temp < reaction.optimal_temp && cached_temp >= reaction.required_temp)
delta_t = (((cached_temp - reaction.required_temp) ** reaction.temp_exponent_factor) / ((reaction.optimal_temp - reaction.required_temp) ** reaction.temp_exponent_factor))
delta_t = ((cached_temp - reaction.required_temp) / (reaction.optimal_temp - reaction.required_temp)) ** reaction.temp_exponent_factor
else if (cached_temp >= reaction.optimal_temp)
delta_t = 1
else //too hot
@@ -286,7 +286,7 @@
return
else
if (cached_temp > reaction.optimal_temp && cached_temp <= reaction.required_temp)
delta_t = (((reaction.required_temp - cached_temp) ** reaction.temp_exponent_factor) / ((reaction.required_temp - reaction.optimal_temp) ** reaction.temp_exponent_factor))
delta_t = ((reaction.required_temp - cached_temp) / (reaction.required_temp - reaction.optimal_temp)) ** reaction.temp_exponent_factor
else if (cached_temp <= reaction.optimal_temp)
delta_t = 1
else //Too cold
@@ -312,34 +312,39 @@
purity *= purity_modifier
//Now we calculate how much to add - this is normalised to the rate up limiter
var/delta_chem_factor = (reaction.rate_up_lim * delta_t) * seconds_per_tick//add/remove factor
var/delta_chem_factor = reaction.rate_up_lim * delta_t * seconds_per_tick//add/remove factor
//keep limited
if(delta_chem_factor > step_target_vol)
delta_chem_factor = step_target_vol
//Normalise to multiproducts
delta_chem_factor = round(delta_chem_factor / product_ratio, CHEMICAL_VOLUME_ROUNDING)
delta_chem_factor = round(delta_chem_factor / product_ratio, CHEMICAL_QUANTISATION_LEVEL)
if(delta_chem_factor <= 0)
to_delete = TRUE
return
//Calculate how much product to make and how much reactant to remove factors..
for(var/reagent in reaction.required_reagents)
holder.remove_reagent(reagent, (delta_chem_factor * reaction.required_reagents[reagent]))
var/required_amount
for(var/datum/reagent/requirement as anything in reaction.required_reagents)
required_amount = reaction.required_reagents[requirement]
if(!holder.remove_reagent(requirement, delta_chem_factor * required_amount))
to_delete = TRUE
return
//Apply pH changes
var/pH_adjust
if(reaction.reaction_flags & REACTION_PH_VOL_CONSTANT)
pH_adjust = ((delta_chem_factor * reaction.required_reagents[reagent]) / target_vol) * (reaction.H_ion_release * h_ion_mod)
pH_adjust = ((delta_chem_factor * required_amount) / target_vol) * (reaction.H_ion_release * h_ion_mod)
else //Default adds pH independant of volume
pH_adjust = (delta_chem_factor * reaction.required_reagents[reagent]) * (reaction.H_ion_release * h_ion_mod)
holder.adjust_specific_reagent_ph(reagent, pH_adjust)
pH_adjust = (delta_chem_factor * required_amount) * (reaction.H_ion_release * h_ion_mod)
holder.adjust_specific_reagent_ph(requirement, pH_adjust)
var/step_add
var/total_step_added = 0
for(var/product in reaction.results)
for(var/datum/reagent/product as anything in reaction.results)
//create the products
step_add = delta_chem_factor * reaction.results[product]
//Default handiling
holder.add_reagent(product, step_add, null, cached_temp, purity, override_base_ph = TRUE)
step_add = holder.add_reagent(product, delta_chem_factor * reaction.results[product], null, cached_temp, purity, override_base_ph = TRUE, no_react = TRUE)
if(!step_add)
to_delete = TRUE
return
//Apply pH changes
var/pH_adjust
@@ -348,6 +353,8 @@
else
pH_adjust = step_add * (reaction.H_ion_release * h_ion_mod)
holder.adjust_specific_reagent_ph(product, pH_adjust)
//record amounts created
reacted_vol += step_add
total_step_added += step_add
@@ -377,14 +384,13 @@
//post reaction checks
if(!(check_fail_states(total_step_added)))
to_delete = TRUE
return
//end reactions faster so plumbing is faster
//length is so that plumbing is faster - but it doesn't disable competitive reactions. Basically, competitive reactions will likely reach their step target at the start, so this will disable that. We want to avoid that. But equally, we do want to full stop a holder from reacting asap so plumbing isn't waiting an tick to resolve.
if((step_add >= step_target_vol) && (length(holder.reaction_list == 1)))
if((step_add >= step_target_vol) && (length(holder.reaction_list) == 1))
to_delete = TRUE
holder.update_total()
/*
* Calculates the total sum normalised purity of ALL reagents in a holder
* Currently calculates it irrespective of required reagents at the start, but this should be changed if this is powergamed to required reagents
@@ -643,7 +643,7 @@
reagent_volume = round(reagent.volume, CHEMICAL_QUANTISATION_LEVEL) //round to this many decimal places
//remove very small amounts of reagents
if((reagent_volume <= 0.05 && !is_reacting) || reagent_volume <= CHEMICAL_QUANTISATION_LEVEL)
if(!reagent_volume || (reagent_volume <= 0.05 && !is_reacting))
//end metabolization
if(isliving(my_atom))
if(reagent.metabolizing)
@@ -663,14 +663,14 @@
//compute volume & ph like we would normally
. += reagent_volume
total_ph += (reagent.ph * reagent_volume)
total_ph += reagent.ph * reagent_volume
//reasign rounded value
reagent.volume = reagent_volume
//assign the final values, rounding up can sometimes cause overflow so bring it down
total_volume = min(round(., CHEMICAL_VOLUME_ROUNDING), maximum_volume)
if(!.)
if(!total_volume)
ph = CHEMICAL_NORMAL_PH
else
ph = clamp(total_ph / total_volume, CHEMICAL_MIN_PH, CHEMICAL_MAX_PH)
@@ -32,7 +32,7 @@
//short cut to break when we have found our one exact type
if(type_check == REAGENT_STRICT_TYPE)
break
return total_amount
return round(total_amount, CHEMICAL_VOLUME_ROUNDING)
@@ -236,8 +236,6 @@
is_reacting = FALSE
LAZYNULL(previous_reagent_list) //reset it to 0 - because any change will be different now.
update_total()
if(!QDELING(src))
handle_reactions() //Should be okay without. Each step checks.
/*
* Force stops the current holder/reagents datum from reacting