Files
Bubberstation/code/datums/lazy_template.dm
LemonInTheDarkandGitHub 818008bca0 Fixes process_cell runtimes during lazyloading (#96367)
## About The Pull Request

I normally really don't like doing this but because this is so in the
weeds this is an alt pr.
Alt of #96366, I really don't want to add overhead to process_cell().

The issue here is when we request a reservation the turfs are empty()'d
which clears them out and places them in SSair's adjacent_turf recalc
queue (which will eventually cause an activation to clear out excited
groups).

We then immediately start maploading, which creates new, uninitialized
turfs (that remain in the queue because turf refs are based off position
instead of the actual datum). These unintialized turfs are able to
interact with the other, yet to be new()'d over turfs in the recalc
queue. This causes runtimes.

Later, while we initialize the turfs we loaded, we also have
interactions between initialized and uninitialized turfs, which also
causes runtimes.

The solution here is to prevent atmos processing in a loading template.
I do that here by first clearing out all relevant turfs from
atmos_adjacent_turf lists and deactivating them. We also have to clear
them from the recalc queue to prevent "fixing" this problem.

Then, after initialize is complete, we requeue them for recalcing, to
restore them to a workable state.

The underlying problem here is the logic of maploading is kind of
designed to run before everything else has initialized. It's something
we can dodge, but it will also show up in things that start processing
on initialize and expect their turf to immediately be ready for them. S
a bit of a mess.

Of note, it's technically possible for a turf to be initialized,
activate, and then attempt to process with an unitialized neighbor with
this solution. My gut fix for this would be using blocks_air to prevent
adjacent turfs from being recalculated until after InitializeAtoms is
finished, but that breaks turf/open/Initialize where we setup
gasmixtures, and I don't want to duplicate that code.

So we'll just bite the small risk of runtimes, they won't actually break
anything after all just make a bit of noise.
## Why It's Good For The Game

Closes #89649
This adds about 110ms of cost to loading the nukie station on my machine
(I used line by line macros around the two for loops), which is
significant but not huge, and it's cost that can be safely CHECK_TICK'd.
I think the cost of actually loading outweighs that significantly enough
(6.6s on my machine) that it's fine.

I prefer this solution to the alternative of adding guard checks to
process_cell because I do not want to add overhead to atmos code, and
also I don't like the idea that we cannot reliably prevent this (shuttle
code handles it effectively using blocks_air, as an example). Also, it
doesn't really resolve the uninitialized bit of this problem, which
isn't... good. Technically changes the math on share() too)
2026-06-08 03:36:44 +02:00

157 lines
5.4 KiB
Plaintext

/**
* Datum used to designate certain areas that do not need to exist nor be loaded at world start
* but do want to be loaded under certain circumstances. Use this for stuff like the nukie base or wizden, aka stuff that only matters when their antag is rolled.
*/
/datum/lazy_template
/// If this is true each load will increment an index keyed to the type and it will load [map_name]_[index]
var/list/datum/turf_reservation/reservations = list()
var/uses_multiple_allocations = FALSE
/// Key to identify this template - used in caching
var/key
/// Directory of maps to prefix to the filename
var/map_dir = "_maps/templates/lazy_templates"
/// The filename (without extension) of the map to load
var/map_name
/// place_on_top: Whether to use /turf/proc/PlaceOnTop rather than /turf/proc/ChangeTurf
var/place_on_top = FALSE
/// type of turf reservation
var/turf_reservation_type = /datum/turf_reservation
/datum/lazy_template/New()
reservations = list()
..()
/datum/lazy_template/Destroy(force)
if(!force)
stack_trace("Something is trying to delete [type]")
return QDEL_HINT_LETMELIVE
QDEL_LIST(reservations)
GLOB.lazy_templates -= key
return ..()
/**
* Does the grunt work of loading the template.
*/
/datum/lazy_template/proc/lazy_load()
RETURN_TYPE(/turf)
// This is a static assosciative list that is used to ensure maps that have variations are correctly varied when spawned
// I want to make it to where you can make a range and it'll randomly pick'n'take from the available versions at random
// But that can be done later when I have the time
var/static/list/multiple_allocation_hash = list()
var/load_path = "[map_dir]/[map_name].dmm"
if(uses_multiple_allocations)
var/times = multiple_allocation_hash[key] || 0
times += 1
multiple_allocation_hash[key] = times
load_path = "[map_dir]/[map_name]_[times].dmm"
if(!load_path || !fexists(load_path))
CRASH("lazy template [type] has an invalid load_path: '[load_path]', check directory and map name!")
var/datum/parsed_map/parsed_template = load_map(
file(load_path),
measure_only = TRUE,
)
if(isnull(parsed_template.parsed_bounds))
CRASH("Failed to cache lazy template for loading: '[key]'")
var/width = parsed_template.parsed_bounds[MAP_MAXX] - parsed_template.parsed_bounds[MAP_MINX] + 1
var/height = parsed_template.parsed_bounds[MAP_MAXY] - parsed_template.parsed_bounds[MAP_MINY] + 1
var/datum/turf_reservation/reservation = SSmapping.request_turf_block_reservation(
width,
height,
parsed_template.parsed_bounds[MAP_MAXZ],
reservation_type = turf_reservation_type,
)
if(!reservation)
CRASH("Failed to reserve a block for lazy template: '[key]'")
// lists kept for overall loading
var/list/loaded_atom_movables = list()
var/list/loaded_turfs = list()
var/list/loaded_areas = list()
var/list/obj/structure/cable/loaded_cables = list()
var/list/obj/machinery/atmospherics/loaded_atmospherics = list()
for(var/z_idx in parsed_template.parsed_bounds[MAP_MAXZ] to 1 step -1)
var/turf/bottom_left = reservation.bottom_left_turfs[z_idx]
var/turf/top_right = reservation.top_right_turfs[z_idx]
// Make our turfs dead to atmos
// Cache for sonic speed
var/list/to_rebuild = SSair.adjacent_rebuild
for(var/turf/contained_turf as anything in block(bottom_left, top_right))
SSair.remove_from_active(contained_turf)
to_rebuild -= contained_turf
for(var/turf/sub_turf as anything in contained_turf.atmos_adjacent_turfs)
sub_turf.atmos_adjacent_turfs?.Remove(contained_turf)
contained_turf.atmos_adjacent_turfs?.Cut()
CHECK_TICK
load_map(
file(load_path),
bottom_left.x,
bottom_left.y,
bottom_left.z,
z_upper = z_idx,
z_lower = z_idx,
place_on_top = place_on_top,
)
for(var/turf/turf as anything in block(bottom_left, top_right))
loaded_turfs += turf
loaded_areas |= get_area(turf)
// atoms can actually be in the contents of two or more turfs based on its icon/bound size
// see https://www.byond.com/docs/ref/index.html#/atom/var/contents
for(var/thing in (turf.get_all_contents() - turf))
if(istype(thing, /obj/structure/cable))
loaded_cables += thing
else if(istype(thing, /obj/machinery/atmospherics))
loaded_atmospherics += thing
loaded_atom_movables |= thing
SSatoms.InitializeAtoms(loaded_areas + loaded_atom_movables + loaded_turfs)
for(var/turf/turf as anything in loaded_turfs)
CALCULATE_ADJACENT_TURFS(turf, NORMAL_TURF)
CHECK_TICK
SSlighting.setup_static_lighting_if_needed(loaded_turfs)
SSmachines.setup_template_powernets(loaded_cables)
SSair.setup_template_machinery(loaded_atmospherics)
SEND_SIGNAL(src, COMSIG_LAZY_TEMPLATE_LOADED, loaded_atom_movables, loaded_turfs, loaded_areas)
reservations += reservation
return reservation
/datum/lazy_template/nukie_elevator
key = LAZY_TEMPLATE_KEY_NUKIEELEVATOR
map_name = "nukie_elevator"
/datum/lazy_template/nukie_base
key = LAZY_TEMPLATE_KEY_NUKIEBASE
map_name = "nukie_base"
/datum/lazy_template/wizard_dem
key = LAZY_TEMPLATE_KEY_WIZARDDEN
map_name = "wizard_den"
/datum/lazy_template/ninja_holding_facility
key = LAZY_TEMPLATE_KEY_NINJA_HOLDING_FACILITY
map_name = "ninja_den"
/datum/lazy_template/abductor_ship
key = LAZY_TEMPLATE_KEY_ABDUCTOR_SHIPS
map_name = "abductor_ships"
/datum/lazy_template/heretic_sacrifice_room
key = LAZY_TEMPLATE_KEY_HERETIC_SACRIFICE
map_name = "heretic_sacrifice"
/datum/lazy_template/voidwalker_void
key = LAZY_TEMPLATE_KEY_VOIDWALKER_VOID
map_name = "voidwalker_void"