Atmos init speedup, saves 4 seconds (#69697)

* Micro optimizes ssair's turf init, saving 2 seconds

Most of this is making existing operations do more legwork, or cheaper.
I did add cycle checking to ONLY init turf linking, which required
creating a new proc.
Did some horrible horrible things in said proc to save like 0.8 seconds.
I think it was worth it.
This commit is contained in:
LemonInTheDark
2022-09-06 02:53:46 -07:00
committed by GitHub
parent 259c72a68a
commit dff635b7f6
18 changed files with 165 additions and 100 deletions
+45 -7
View File
@@ -36,6 +36,9 @@ SUBSYSTEM_DEF(air)
var/list/gas_reactions = list()
var/list/atmos_gen
var/list/planetary = list() //Lets cache static planetary mixes
/// List of gas string -> canonical gas mixture
var/list/strings_to_mix = list()
//Special functions lists
var/list/turf/active_super_conductivity = list()
@@ -521,7 +524,7 @@ SUBSYSTEM_DEF(air)
/datum/controller/subsystem/air/proc/setup_allturfs()
var/list/turfs_to_init = block(locate(1, 1, 1), locate(world.maxx, world.maxy, world.maxz))
var/list/active_turfs = src.active_turfs
var/times_fired = ++src.times_fired
times_fired++
// Clear active turfs - faster than removing every single turf in the world
// one-by-one, and Initalize_Atmos only ever adds `src` back in.
@@ -531,13 +534,16 @@ SUBSYSTEM_DEF(air)
active.remove_atom_colour(TEMPORARY_COLOUR_PRIORITY, COLOR_VIBRANT_LIME)
#endif
active_turfs.Cut()
var/time = 0
for(var/thing in turfs_to_init)
var/turf/T = thing
if (T.blocks_air)
for(var/turf/T as anything in turfs_to_init)
if (!T.init_air)
continue
T.Initalize_Atmos(times_fired)
CHECK_TICK
// We pass the tick as the current step so if we sleep the step changes
// This way we can make setting up adjacent turfs O(n) rather then O(n^2)
T.Initalize_Atmos(time)
if(CHECK_TICK)
time++
if(active_turfs.len)
var/starting_ats = active_turfs.len
@@ -561,8 +567,8 @@ SUBSYSTEM_DEF(air)
active_turfs += new_turfs_to_check
turfs_to_check = new_turfs_to_check
while (turfs_to_check.len)
var/ending_ats = active_turfs.len
for(var/thing in excited_groups)
var/datum/excited_group/EG = thing
@@ -663,6 +669,38 @@ GLOBAL_LIST_EMPTY(colored_images)
var/datum/atmosphere/atmostype = T
atmos_gen[initial(atmostype.id)] = new atmostype
/// Takes a gas string, returns the matching mutable gas_mixture
/datum/controller/subsystem/air/proc/parse_gas_string(gas_string)
var/datum/gas_mixture/cached = strings_to_mix[gas_string]
if(cached)
if(istype(cached, /datum/gas_mixture/immutable))
return cached
return cached.copy()
var/datum/gas_mixture/canonical_mix = new()
// We set here so any future key changes don't fuck us
strings_to_mix[gas_string] = canonical_mix
gas_string = preprocess_gas_string(gas_string)
var/list/gases = canonical_mix.gases
var/list/gas = params2list(gas_string)
if(gas["TEMP"])
canonical_mix.temperature = text2num(gas["TEMP"])
canonical_mix.temperature_archived = canonical_mix.temperature
gas -= "TEMP"
else // if we do not have a temp in the new gas mix lets assume room temp.
canonical_mix.temperature = T20C
for(var/id in gas)
var/path = id
if(!ispath(path))
path = gas_id2path(path) //a lot of these strings can't have embedded expressions (especially for mappers), so support for IDs needs to stick around
ADD_GAS(path, gases)
gases[path][MOLES] = text2num(gas[id])
if(istype(canonical_mix, /datum/gas_mixture/immutable))
return canonical_mix
return canonical_mix.copy()
/datum/controller/subsystem/air/proc/preprocess_gas_string(gas_string)
if(!atmos_gen)
generate_atmos()
@@ -38,8 +38,7 @@
update_mob()
playsound(src, 'sound/effects/spray2.ogg', 100, TRUE)
var/list/connected_turfs = detect_room(origin = get_turf(src), max_size = fix_range)
var/datum/gas_mixture/base_mix = new
base_mix.parse_gas_string(OPENTURF_DEFAULT_ATMOS)
var/datum/gas_mixture/base_mix = SSair.parse_gas_string(OPENTURF_DEFAULT_ATMOS)
for(var/turf/open/turf_fix in connected_turfs)
if(turf_fix.blocks_air)
continue
+1
View File
@@ -4,6 +4,7 @@
opacity = TRUE
density = TRUE
blocks_air = TRUE
init_air = FALSE
rad_insulation = RAD_MEDIUM_INSULATION
pass_flags_self = PASSCLOSEDTURF
+9 -7
View File
@@ -160,21 +160,23 @@
/turf/open/indestructible/airblock
icon_state = "bluespace"
blocks_air = TRUE
init_air = FALSE
baseturfs = /turf/open/indestructible/airblock
/turf/open/Initalize_Atmos(times_fired)
/turf/open/Initalize_Atmos(time)
excited = FALSE
update_visuals()
current_cycle = times_fired
immediate_calculate_adjacent_turfs()
for(var/i in atmos_adjacent_turfs)
var/turf/open/enemy_tile = i
var/datum/gas_mixture/enemy_air = enemy_tile.return_air()
if(!excited && air.compare(enemy_air))
current_cycle = time
init_immediate_calculate_adjacent_turfs()
for(var/turf/open/enemy_tile as anything in atmos_adjacent_turfs)
if(air.compare(enemy_tile.return_air()))
//testing("Active turf found. Return value of compare(): [is_active]")
excited = TRUE
SSair.active_turfs += src
// No sense continuing to iterate
return
/turf/open/GetHeatCapacity()
. = air.heat_capacity()
+2 -3
View File
@@ -14,6 +14,8 @@
var/destination_y
var/static/datum/gas_mixture/immutable/space/space_gas = new
// We do NOT want atmos adjacent turfs
init_air = FALSE
run_later = TRUE
plane = PLANE_SPACE
layer = SPACE_LAYER
@@ -75,9 +77,6 @@
var/turf/T = locate(destination_x, destination_y, destination_z)
user.forceMove(T)
/turf/open/space/Initalize_Atmos(times_fired)
return
/turf/open/space/TakeTemperature(temp)
/turf/open/space/RemoveLattice()
+5 -1
View File
@@ -28,6 +28,9 @@ GLOBAL_LIST_EMPTY(station_turfs)
var/max_fire_temperature_sustained = 0
var/blocks_air = FALSE
// If this turf should initialize atmos adjacent turfs or not
// Optimization, not for setting outside of initialize
var/init_air = TRUE
var/list/image/blueprint_data //for the station blueprints, images of objects eg: pipes
@@ -167,7 +170,8 @@ GLOBAL_LIST_EMPTY(station_turfs)
return INITIALIZE_HINT_NORMAL
/turf/proc/Initalize_Atmos(times_fired)
/// Initializes our adjacent turfs. If you want to avoid this, do not override it, instead set init_air to FALSE
/turf/proc/Initalize_Atmos(time)
CALCULATE_ADJACENT_TURFS(src, NORMAL_TURF)
/turf/Destroy(force)
+1 -2
View File
@@ -11,11 +11,10 @@
var/range=input("Enter range:","Num",2) as num
message_admins("[key_name_admin(usr)] fixed air with range [range] in area [T.loc.name]")
usr.log_message("fixed air with range [range] in area [T.loc.name]", LOG_ADMIN)
var/datum/gas_mixture/GM = new
for(var/turf/open/F in range(range,T))
if(F.blocks_air)
//skip walls
continue
GM.parse_gas_string(F.initial_gas_mix)
var/datum/gas_mixture/GM = SSair.parse_gas_string(F.initial_gas_mix)
F.copy_air(GM)
F.update_visuals()
@@ -53,27 +53,81 @@
/atom/movable/proc/block_superconductivity() // objects that block air and don't let superconductivity act
return FALSE
/turf/proc/immediate_calculate_adjacent_turfs()
/// This proc is a more deeply optimized version of immediate_calculate_adjacent_turfs
/// It contains dumbshit, and also stuff I just can't do at runtime
/// If you're not editing behavior, just read that proc. It's less bad
/turf/proc/init_immediate_calculate_adjacent_turfs()
//Basic optimization, if we can't share why bother asking other people ya feel?
// You know it's gonna be stupid when they include a unit test in the atmos code
// Yes, inlining the string concat does save 0.1 seconds
#ifdef UNIT_TESTS
ASSERT(UP == 16)
ASSERT(DOWN == 32)
#endif
LAZYINITLIST(src.atmos_adjacent_turfs)
var/list/atmos_adjacent_turfs = src.atmos_adjacent_turfs
var/canpass = CANATMOSPASS(src, src, FALSE)
// I am essentially inlineing two get_dir_multizs here, because they're way too slow on their own. I'm sorry brother
var/list/z_traits = SSmapping.multiz_levels[z]
for(var/direction in GLOB.cardinals_multiz)
// Yes this is a reimplementation of get_step_mutliz. It's faster tho. fuck you
// Oh also yes UP and DOWN do just point to +1 and -1 and not z offsets
// Multiz is shitcode welcome home
var/turf/current_turf = (direction & (UP|DOWN)) ? \
(direction & UP) ? \
(z_traits["16"]) ? \
(get_step(locate(x, y, z + 1), NONE)) : \
(null) : \
(z_traits["32"]) ? \
(get_step(locate(x, y, z - 1), NONE)) : \
(null) : \
(get_step(src, direction))
if(!isopenturf(current_turf)) // not interested in you brother
continue
// The assumption is that ONLY DURING INIT if two tiles have the same cycle, there's no way canpass(a->b) will be different then canpass(b->a), so this is faster
// Saves like 1.2 seconds
if(current_turf.current_cycle >= current_cycle)
continue
//Can you and me form a deeper relationship, or is this just a passing wind
// (direction & (UP | DOWN)) is just "is this vertical" by the by
if(canpass && CANATMOSPASS(current_turf, src, (direction & (UP|DOWN))) && !(blocks_air || current_turf.blocks_air))
LAZYINITLIST(current_turf.atmos_adjacent_turfs)
atmos_adjacent_turfs[current_turf] = TRUE
current_turf.atmos_adjacent_turfs[src] = TRUE
else
atmos_adjacent_turfs -= current_turf
if (current_turf.atmos_adjacent_turfs)
current_turf.atmos_adjacent_turfs -= src
UNSETEMPTY(current_turf.atmos_adjacent_turfs)
SEND_SIGNAL(current_turf, COMSIG_TURF_CALCULATED_ADJACENT_ATMOS)
UNSETEMPTY(atmos_adjacent_turfs)
src.atmos_adjacent_turfs = atmos_adjacent_turfs
SEND_SIGNAL(src, COMSIG_TURF_CALCULATED_ADJACENT_ATMOS)
/turf/proc/immediate_calculate_adjacent_turfs()
LAZYINITLIST(src.atmos_adjacent_turfs)
var/list/atmos_adjacent_turfs = src.atmos_adjacent_turfs
var/canpass = CANATMOSPASS(src, src, FALSE)
for(var/direction in GLOB.cardinals_multiz)
var/turf/current_turf = get_step_multiz(src, direction)
if(!isopenturf(current_turf)) // not interested in you brother
continue
//Can you and me form a deeper relationship, or is this just a passing wind
// (direction & (UP | DOWN)) is just "is this vertical" by the by
if(canpass && CANATMOSPASS(current_turf, src, (direction & (UP|DOWN))) && !(blocks_air || current_turf.blocks_air))
LAZYINITLIST(atmos_adjacent_turfs)
LAZYINITLIST(current_turf.atmos_adjacent_turfs)
atmos_adjacent_turfs[current_turf] = TRUE
current_turf.atmos_adjacent_turfs[src] = TRUE
else
if (atmos_adjacent_turfs)
atmos_adjacent_turfs -= current_turf
atmos_adjacent_turfs -= current_turf
if (current_turf.atmos_adjacent_turfs)
current_turf.atmos_adjacent_turfs -= src
UNSETEMPTY(current_turf.atmos_adjacent_turfs)
SEND_SIGNAL(current_turf, COMSIG_TURF_CALCULATED_ADJACENT_ATMOS)
UNSETEMPTY(atmos_adjacent_turfs)
src.atmos_adjacent_turfs = atmos_adjacent_turfs
SEND_SIGNAL(src, COMSIG_TURF_CALCULATED_ADJACENT_ATMOS)
@@ -153,8 +207,7 @@
if(!text || !air)
return
var/datum/gas_mixture/turf_mixture = new
turf_mixture.parse_gas_string(text)
var/datum/gas_mixture/turf_mixture = SSair.parse_gas_string(text)
air.merge(turf_mixture)
archive()
@@ -56,8 +56,7 @@
/turf/open/Initialize(mapload)
if(!blocks_air)
air = new
air.copy_from_turf(src)
air = create_gas_mixture()
if(planetary_atmos)
if(!SSair.planetary[initial_gas_mix])
var/datum/gas_mixture/immutable/planetary/mix = new
@@ -75,6 +74,18 @@
/////////////////GAS MIXTURE PROCS///////////////////
///Copies all gas info from the turf into a new gas_mixture, along with our temperature
///Returns the created gas_mixture
/turf/proc/create_gas_mixture()
var/datum/gas_mixture/mix = SSair.parse_gas_string(initial_gas_mix)
//acounts for changes in temperature
var/turf/parent = parent_type
if(temperature != initial(temperature) || temperature != initial(parent.temperature))
mix.temperature = temperature
return mix
/turf/open/assume_air(datum/gas_mixture/giver) //use this for machines to adjust air
if(!giver)
return FALSE
@@ -100,8 +111,7 @@
/turf/return_air()
RETURN_TYPE(/datum/gas_mixture)
var/datum/gas_mixture/copied_mixture = new
copied_mixture.copy_from_turf(src)
var/datum/gas_mixture/copied_mixture = create_gas_mixture()
return copied_mixture
/turf/open/return_air()
@@ -288,7 +288,7 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache())
return copy
///Copies variables from sample, moles multiplicated by partial
///Returns: 1 if we are mutable, 0 otherwise
///Returns: TRUE if we are mutable, FALSE otherwise
/datum/gas_mixture/proc/copy_from(datum/gas_mixture/sample, partial = 1)
var/list/cached_gases = gases //accessing datum vars is slower than proc vars
var/list/sample_gases = sample.gases
@@ -301,42 +301,8 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache())
ASSERT_GAS(id,src)
cached_gases[id][MOLES] = sample_gases[id][MOLES] * partial
return 1
///Copies all gas info from the turf into the gas list along with temperature
///Returns: TRUE if we are mutable, FALSE otherwise
/datum/gas_mixture/proc/copy_from_turf(turf/model)
parse_gas_string(model.initial_gas_mix)
//acounts for changes in temperature
var/turf/model_parent = model.parent_type
if(model.temperature != initial(model.temperature) || model.temperature != initial(model_parent.temperature))
temperature = model.temperature
return TRUE
///Copies variables from a particularly formatted string.
///Returns: 1 if we are mutable, 0 otherwise
/datum/gas_mixture/proc/parse_gas_string(gas_string)
gas_string = SSair.preprocess_gas_string(gas_string)
var/list/gases = src.gases
var/list/gas = params2list(gas_string)
if(gas["TEMP"])
temperature = text2num(gas["TEMP"])
temperature_archived = temperature
gas -= "TEMP"
else // if we do not have a temp in the new gas mix lets assume room temp.
temperature = T20C
gases.Cut()
for(var/id in gas)
var/path = id
if(!ispath(path))
path = gas_id2path(path) //a lot of these strings can't have embedded expressions (especially for mappers), so support for IDs needs to stick around
ADD_GAS(path, gases)
gases[path][MOLES] = text2num(gas[id])
return 1
/// Performs air sharing calculations between two gas_mixtures
/// share() is communitive, which means A.share(B) needs to be the same as B.share(A)
/// If we don't retain this, we will get negative moles. Don't do it
@@ -459,25 +425,22 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache())
/datum/gas_mixture/proc/compare(datum/gas_mixture/sample)
var/list/sample_gases = sample.gases //accessing datum vars is slower than proc vars
var/list/cached_gases = gases
var/moles_sum = 0
for(var/id in cached_gases | sample_gases) // compare gases from either mixture
var/gas_moles = cached_gases[id]
gas_moles = gas_moles ? gas_moles[MOLES] : 0
var/sample_moles = sample_gases[id]
sample_moles = sample_moles ? sample_moles[MOLES] : 0
var/delta = abs(gas_moles - sample_moles)
if(delta > MINIMUM_MOLES_DELTA_TO_MOVE && \
delta > gas_moles * MINIMUM_AIR_RATIO_TO_MOVE)
return id
// Yes this is actually fast. I too hate it here
var/gas_moles = cached_gases[id]?[MOLES] || 0
var/sample_moles = sample_gases[id]?[MOLES] || 0
// Brief explanation. We are much more likely to not pass this first check then pass the first and fail the second
// Because of this, double calculating the delta is FASTER then inserting it into a var
if(abs(gas_moles - sample_moles) > MINIMUM_MOLES_DELTA_TO_MOVE)
if(abs(gas_moles - sample_moles) > gas_moles * MINIMUM_AIR_RATIO_TO_MOVE)
return id
// similarly, we will rarely get cut off, so this is cheaper then doing it later
moles_sum += gas_moles
var/our_moles
TOTAL_MOLES(cached_gases, our_moles)
if(our_moles > MINIMUM_MOLES_DELTA_TO_MOVE) //Don't consider temp if there's not enough mols
var/temp = temperature
var/sample_temp = sample.temperature
var/temperature_delta = abs(temp - sample_temp)
if(temperature_delta > MINIMUM_TEMPERATURE_DELTA_TO_SUSPEND)
if(moles_sum > MINIMUM_MOLES_DELTA_TO_MOVE) //Don't consider temp if there's not enough mols
if(abs(temperature - sample.temperature) > MINIMUM_TEMPERATURE_DELTA_TO_SUSPEND)
return "temp"
return ""
@@ -34,12 +34,6 @@
/datum/gas_mixture/immutable/copy_from()
return FALSE //we're immutable.
/datum/gas_mixture/immutable/copy_from_turf()
return FALSE //we're immutable.
/datum/gas_mixture/immutable/parse_gas_string()
return FALSE //we're immutable.
/datum/gas_mixture/immutable/temperature_share(datum/gas_mixture/sharer, conduction_coefficient, sharer_temperature, sharer_heat_capacity)
. = ..()
temperature = initial_temperature
@@ -294,7 +294,7 @@
* * obj/machinery/atmospherics/target - the machinery we want to connect to
*/
/obj/machinery/atmospherics/proc/check_connectable_color(obj/machinery/atmospherics/target)
if(lowertext(target.pipe_color) == lowertext(pipe_color) || ((target.pipe_flags | pipe_flags) & PIPING_ALL_COLORS) || lowertext(target.pipe_color) == lowertext(COLOR_VERY_LIGHT_GRAY) || lowertext(pipe_color) == lowertext(COLOR_VERY_LIGHT_GRAY))
if(target.pipe_color == pipe_color || ((target.pipe_flags | pipe_flags) & PIPING_ALL_COLORS) || target.pipe_color == COLOR_VERY_LIGHT_GRAY || pipe_color == COLOR_VERY_LIGHT_GRAY)
return TRUE
return FALSE
@@ -467,8 +467,7 @@
/obj/machinery/atmospherics/on_construction(obj_color, set_layer = PIPING_LAYER_DEFAULT)
if(can_unwrench)
add_atom_colour(obj_color, FIXED_COLOUR_PRIORITY)
pipe_color = obj_color
update_name()
set_pipe_color(obj_color)
set_piping_layer(set_layer)
atmos_init()
var/list/nodes = pipeline_expansion()
@@ -588,3 +587,8 @@
*/
/obj/machinery/atmospherics/proc/paint(paint_color)
return FALSE
/// Setter for pipe color, so we can ensure it's all uniform and save cpu time
/obj/machinery/atmospherics/proc/set_pipe_color(pipe_colour)
src.pipe_color = uppertext(pipe_colour)
update_name()
@@ -242,7 +242,7 @@
/obj/machinery/atmospherics/components/paint(paint_color)
if(paintable)
add_atom_colour(paint_color, FIXED_COLOUR_PRIORITY)
pipe_color = paint_color
set_pipe_color(paint_color)
update_node_icon()
return paintable
@@ -197,7 +197,7 @@
if(!panel_open)
return
color_index = (color_index >= GLOB.pipe_paint_colors.len) ? (color_index = 1) : (color_index = 1 + color_index)
pipe_color = GLOB.pipe_paint_colors[GLOB.pipe_paint_colors[color_index]]
set_pipe_color(GLOB.pipe_paint_colors[GLOB.pipe_paint_colors[color_index]])
visible_message("<span class='notice'>You set [src]'s pipe color to [GLOB.pipe_color_name[pipe_color]].")
update_appearance()
return TOOL_ACT_TOOLTYPE_SUCCESS
@@ -124,7 +124,7 @@
/obj/machinery/atmospherics/pipe/paint(paint_color)
if(paintable)
add_atom_colour(paint_color, FIXED_COLOUR_PRIORITY)
pipe_color = paint_color
set_pipe_color(pipe_color)
update_node_icon()
return paintable
+1
View File
@@ -9,6 +9,7 @@
opacity = TRUE
density = TRUE
blocks_air = TRUE
init_air = FALSE
always_lit = TRUE
bullet_bounce_sound = null
turf_flags = NOJAUNT
@@ -14,7 +14,7 @@
SSair.remove_from_active(T)
for(var/turf/open/T in map)
if(T.air)
T.air.copy_from_turf(T)
T.air = T.create_gas_mixture()
SSair.add_to_active(T, TRUE)
/datum/map_generator_module/bottom_layer/massdelete
+2 -4
View File
@@ -25,8 +25,7 @@
var/turf/open/to_fill = run_loc_floor_bottom_left
//Prep the floor
to_fill.initial_gas_mix = OPENTURF_DEFAULT_ATMOS
to_fill.air = new
to_fill.air.copy_from_turf(to_fill)
to_fill.air = to_fill.create_gas_mixture()
lab_rat.breathe()
@@ -65,8 +64,7 @@
var/turf/open/to_fill = run_loc_floor_bottom_left
//Prep the floor
to_fill.initial_gas_mix = LAVALAND_DEFAULT_ATMOS
to_fill.air = new
to_fill.air.copy_from_turf(to_fill)
to_fill.air = to_fill.create_gas_mixture()
lab_rat.breathe()