diff --git a/code/datums/helper_datums/map_template.dm b/code/datums/helper_datums/map_template.dm
index c1b9312b734..03fe8602281 100644
--- a/code/datums/helper_datums/map_template.dm
+++ b/code/datums/helper_datums/map_template.dm
@@ -23,7 +23,7 @@
height = bounds[MAP_MAXY]
return bounds
-/datum/map_template/proc/load(turf/T, centered = 0, delay_init = 0)
+/datum/map_template/proc/load(turf/T, centered = 0)
var/turf/placement = T
var/min_x = placement.x
var/min_y = placement.y
@@ -43,7 +43,13 @@
// 1 bigger, to update the turf smoothing
var/turf/ST_bot_left = locate(max(1, min_x-1), max(1, min_y-1), placement.z)
var/turf/ST_top_right = locate(min(world.maxx, max_x+1), min(world.maxy, max_y+1), placement.z)
- var/list/bounds = maploader.load_map(get_file(), min_x, min_y, placement.z, cropMap = 1, delay_init = delay_init)
+ // This is to place a freeze on initialization until the map's done loading
+ // otherwise atmos and stuff will start running mid-load
+ // This system will metaphorically snap in half (not postpone init everywhere)
+ // if given a multi-z template
+ // it might need to be adapted for that when that time comes
+ zlevels.add_dirt(placement.z)
+ var/list/bounds = maploader.load_map(get_file(), min_x, min_y, placement.z, cropMap = 1)
if(!bounds)
return 0
if(bot_left == null || top_right == null)
@@ -55,9 +61,11 @@
log_debug("One of the smoothing corners is bust")
else
log_debug("Tile smoothing from ([ST_bot_left.x],[ST_bot_left.y]) to ([ST_top_right.x],[ST_top_right.y])")
+
late_setup_level(
block(bot_left, top_right),
block(ST_bot_left, ST_top_right))
+ zlevels.remove_dirt(placement.z)
log_game("[name] loaded at [min_x],[min_y],[placement.z]")
return 1
diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm
index 4a422cf6d1b..f4cb6220cc4 100644
--- a/code/game/atoms_movable.dm
+++ b/code/game/atoms_movable.dm
@@ -23,12 +23,11 @@
/atom/movable/New()
. = ..()
areaMaster = get_area_master(src)
- var/turf/T = get_turf(src)
// If you're wondering what goofery this is, this is for things that need the environment
// around them set up - like `air_update_turf` and the like
- if(auto_init && (ticker && ticker.current_state == GAME_STATE_PLAYING) && !(T && zlevels.is_zlevel_dirty(T.z)))
- initialize()
+ if(auto_init && (ticker && ticker.current_state == GAME_STATE_PLAYING))
+ attempt_init()
/atom/movable/Destroy()
for(var/atom/movable/AM in contents)
@@ -40,6 +39,16 @@
pulledby = null
return ..()
+// used to provide a good interface for the init delay system to step in
+// and we don't need to call `get_turf` until the game's started
+// at which point object creations are a fair toss more seldom
+/atom/movable/proc/attempt_init()
+ var/turf/T = get_turf(src)
+ if (T && zlevels.is_zlevel_dirty(T.z))
+ zlevels.postpone_init(T.z, src)
+ else
+ initialize()
+
/atom/movable/proc/initialize()
return
diff --git a/code/modules/admin/verbs/map_template_loadverb.dm b/code/modules/admin/verbs/map_template_loadverb.dm
index f37ad73a1dc..f447810d860 100644
--- a/code/modules/admin/verbs/map_template_loadverb.dm
+++ b/code/modules/admin/verbs/map_template_loadverb.dm
@@ -19,10 +19,6 @@
to_chat(usr, "Map is too large to fit in bounds. Map's dimensions: ([template.width], [template.height])")
return
- var/delay_init = 0
- if(alert(usr,"Delay initialization? (Needed for large atmos-containing maps)","Delay","Yes","No") == "Yes")
- delay_init = 1
-
var/list/preview = list()
for(var/S in template.get_affected_turfs(T,centered = TRUE))
preview += image('icons/turf/overlays.dmi',S,"greenOverlay")
@@ -30,7 +26,7 @@
if(alert(usr,"Confirm location.","Template Confirm","Yes","No") == "Yes")
var/timer = start_watch()
message_admins("[key_name_admin(usr)] has started to place the map template ([template.name]) at (JMP)")
- if(template.load(T, centered = TRUE, delay_init = delay_init))
+ if(template.load(T, centered = TRUE))
message_admins("[key_name_admin(usr)] has placed a map template ([template.name]) at (JMP). Took [stop_watch(timer)]s.")
else
to_chat(usr, "Failed to place map")
diff --git a/code/modules/awaymissions/map_rng.dm b/code/modules/awaymissions/map_rng.dm
index 0f8ca13b7a6..00df28d2160 100644
--- a/code/modules/awaymissions/map_rng.dm
+++ b/code/modules/awaymissions/map_rng.dm
@@ -10,7 +10,6 @@
var/datum/map_template/template = null
var/centered = 1
var/loaded = 0
- var/delay_init = 0
/obj/effect/landmark/map_loader/New(turf/loc, tname)
..()
@@ -28,13 +27,13 @@
/obj/effect/landmark/map_loader/proc/load(datum/map_template/t)
if(!t)
return
- if(loaded)
+ if(loaded) // I wanna be super sure this loads only once
return
loaded = 1
var/turf/pos = get_turf(src)
// Hop to nullspace so we don't get re-initialized by the map we're loading
loc = null
- t.load(pos, centered = centered, delay_init = delay_init)
+ t.load(pos, centered = centered)
t.loaded++
qdel(src)
diff --git a/code/modules/awaymissions/maploader/reader.dm b/code/modules/awaymissions/maploader/reader.dm
index 2ef9d79d994..d174822cdd0 100644
--- a/code/modules/awaymissions/maploader/reader.dm
+++ b/code/modules/awaymissions/maploader/reader.dm
@@ -31,12 +31,13 @@ var/global/dmm_suite/preloader/_preloader = new
* If `measureOnly` is set, then no atoms will be created, and all this will do
* is return the bounds after parsing the file
*
- * If delay_init is set, then all initializations while the map is loading will
- * be deferred until later
- * You'll need it for large initializations like loading the cyberiad, where
- * glass and similar can cause atmos to initialize before it's ready
+ * If you need to freeze init while you're working, you can use the spacial allocator's
+ * "add_dirt" and "remove_dirt" which will put initializations on hold until you say
+ * the word. This is important for loading large maps such as the cyberiad, where
+ * atmos will attempt to start before it's ready, causing runtimes galore if init is
+ * allowed to romp unchecked.
*/
-/dmm_suite/load_map(dmm_file as file, x_offset as num, y_offset as num, z_offset as num, cropMap as num, measureOnly as num, delay_init as num)
+/dmm_suite/load_map(dmm_file as file, x_offset as num, y_offset as num, z_offset as num, cropMap as num, measureOnly as num)
var/tfile = dmm_file//the map file we're creating
var/fname = "Lambda"
if(isfile(tfile))
@@ -57,8 +58,6 @@ var/global/dmm_suite/preloader/_preloader = new
var/dmm_suite/loaded_map/LM = new
- if(measureOnly)
- delay_init = 0
// This try-catch is used as a budget "Finally" clause, as the dirt count
// needs to be reset
var/watch = start_watch()
@@ -91,9 +90,6 @@ var/global/dmm_suite/preloader/_preloader = new
var/xcrd
var/ycrd = text2num(dmmRegex.group[4]) + y_offset - 1
var/zcrd = text2num(dmmRegex.group[5]) + z_offset - 1
- if(!measureOnly && delay_init)
- zlevels.add_dirt(zcrd)
- LM.touched_z_levels |= zcrd
if(zcrd > world.maxz)
if(cropMap)
@@ -161,15 +157,9 @@ var/global/dmm_suite/preloader/_preloader = new
CHECK_TICK
catch(var/exception/e)
_preloader.reset()
- if(delay_init)
- for(var/i in LM.touched_z_levels)
- zlevels.remove_dirt(i)
throw e
- if(delay_init)
- _preloader.reset()
- for(var/i in LM.touched_z_levels)
- zlevels.remove_dirt(i)
+ _preloader.reset()
log_debug("Loaded map in [stop_watch(watch)]s.")
qdel(LM)
if(bounds[MAP_MINX] == 1.#INF) // Shouldn't need to check every item
@@ -465,7 +455,6 @@ var/global/dmm_suite/preloader/_preloader = new
// a new area type for each new ruin
/dmm_suite/loaded_map
parent_type = /datum
- var/list/touched_z_levels = list()
var/list/area_list = list()
var/index = 1 // To store the state of the regex
diff --git a/code/modules/awaymissions/zlevel.dm b/code/modules/awaymissions/zlevel.dm
index 1b1fd4d7b86..7c06aa07535 100644
--- a/code/modules/awaymissions/zlevel.dm
+++ b/code/modules/awaymissions/zlevel.dm
@@ -1,5 +1,7 @@
var/global/list/potentialRandomZlevels = generateMapList(filename = "config/away_mission_config.txt")
+// Call this before you remove the last dirt on a z level - that way, all objects
+// will have proper atmos and other important enviro things
/proc/late_setup_level(turfs, smoothTurfs)
var/total_timer = start_watch()
var/subtimer = start_watch()
@@ -12,14 +14,10 @@ var/global/list/potentialRandomZlevels = generateMapList(filename = "config/away
log_debug("\tTook [stop_watch(subtimer)]s")
subtimer = start_watch()
- log_debug("Initializing objects and lighting")
+ log_debug("Initializing lighting")
for(var/turf/T in turfs)
if(T.dynamic_lighting)
T.lighting_build_overlays()
- for(var/obj/structure/cable/PC in T)
- makepowernet_for(PC)
- for(var/atom/movable/AM in T)
- AM.initialize()
log_debug("\tTook [stop_watch(subtimer)]s")
subtimer = start_watch()
@@ -35,14 +33,6 @@ var/global/list/potentialRandomZlevels = generateMapList(filename = "config/away
var/turf/simulated/mineral/MT = T
MT.add_edges()
log_debug("\tTook [stop_watch(subtimer)]s")
-
- log_debug("Initializing pipenets")
- // We do this once we're finished initializing since otherwise the pipes
- // won't have finished connecting
- for(var/turf/T in turfs)
- for(var/obj/machinery/atmospherics/machine in T)
- machine.build_network()
- log_debug("\tTook [stop_watch(subtimer)]s")
log_debug("Late setup finished - took [stop_watch(total_timer)]s")
/proc/createRandomZlevel()
@@ -56,8 +46,11 @@ var/global/list/potentialRandomZlevels = generateMapList(filename = "config/away
var/map = pick(potentialRandomZlevels)
var/file = file(map)
if(isfile(file))
- maploader.load_map(file)
- late_setup_level(block(locate(1, 1, world.maxz), locate(world.maxx, world.maxy, world.maxz)))
+ var/zlev = zlevels.add_new_zlevel()
+ zlevels.add_dirt(zlev)
+ maploader.load_map(file, z_offset = zlev)
+ late_setup_level(block(locate(1, 1, zlev), locate(world.maxx, world.maxy, zlev)))
+ zlevels.remove_dirt(zlev)
log_to_dd(" Away mission loaded: [map]")
//map_transition_config.Add(AWAY_MISSION_LIST)
@@ -85,8 +78,11 @@ var/global/list/potentialRandomZlevels = generateMapList(filename = "config/away
var/file = file(map)
if(isfile(file))
log_startup_progress("Loading away mission: [map]")
- maploader.load_map(file)
- late_setup_level(block(locate(1, 1, world.maxz), locate(world.maxx, world.maxy, world.maxz)))
+ var/zlev = zlevels.add_new_zlevel()
+ zlevels.add_dirt(zlev)
+ maploader.load_map(file, z_offset = zlev)
+ late_setup_level(block(locate(1, 1, zlev), locate(world.maxx, world.maxy, zlev)))
+ zlevels.remove_dirt(zlev)
log_to_dd(" Away mission loaded: [map]")
//map_transition_config.Add(AWAY_MISSION_LIST)
diff --git a/code/modules/spacial_allocator/zlevel.dm b/code/modules/spacial_allocator/zlevel.dm
index 45be707c15a..6fd3f75d53f 100644
--- a/code/modules/spacial_allocator/zlevel.dm
+++ b/code/modules/spacial_allocator/zlevel.dm
@@ -2,6 +2,48 @@
var/flags = 0
var/dirt_count = 0
var/zpos
+ var/list/init_list = list()
+ var/list/pipes = list()
+ var/list/cables = list()
/datum/zlevel/New(z)
zpos = z
+
+/datum/zlevel/proc/resume_init()
+ if(dirt_count > 0)
+ throw EXCEPTION("Init told to resume when z-level still dirty. Z level: '[zpos]'")
+ log_debug("Releasing freeze on z-level '[zpos]'!")
+ log_debug("Beginning initialization!")
+ var/watch = start_watch()
+ for(var/schmoo in init_list)
+ var/atom/movable/AM = schmoo
+ if(AM) // to catch stuff like the nuke disk that no longer exists
+ AM.initialize()
+ if(istype(AM, /obj/machinery/atmospherics))
+ pipes.Add(AM)
+ else if(istype(AM, /obj/structure/cable))
+ cables.Add(AM)
+ log_debug("Primary initialization finished in [stop_watch(watch)]s.")
+ init_list.Cut()
+ do_pipes()
+ do_cables()
+
+/datum/zlevel/proc/do_pipes()
+ var/watch = start_watch()
+ log_debug("Building pipenets on z-level '[zpos]'!")
+ for(var/schmoo in pipes)
+ var/obj/machinery/atmospherics/machine = schmoo
+ if(machine)
+ machine.build_network()
+ pipes.Cut()
+ log_debug("Took [stop_watch(watch)]s")
+
+/datum/zlevel/proc/do_cables()
+ var/watch = start_watch()
+ log_debug("Building powernets on z-level '[zpos]'!")
+ for(var/schmoo in cables)
+ var/obj/structure/cable/C = schmoo
+ if(C)
+ makepowernet_for(C)
+ cables.Cut()
+ log_debug("Took [stop_watch(watch)]s")
diff --git a/code/modules/spacial_allocator/zlevel_manager.dm b/code/modules/spacial_allocator/zlevel_manager.dm
index 7eb10d551e8..b2540f874e9 100644
--- a/code/modules/spacial_allocator/zlevel_manager.dm
+++ b/code/modules/spacial_allocator/zlevel_manager.dm
@@ -12,6 +12,15 @@ var/global/datum/zlev_manager/zlevels = new
z_list[i] = new /datum/zlevel(i)
+
+/datum/zlev_manager/proc/get_zlev(z)
+ if(z < 1)
+ throw EXCEPTION("Non-positive z level given!")
+ else if (z > z_list.len)
+ throw EXCEPTION("Untracked z level: '[z]'")
+ else
+ return z_list[z]
+
// For when you need the z-level to be at a certain point
/datum/zlev_manager/proc/increase_max_zlevel_to(new_maxz)
if(world.maxz>=new_maxz)
@@ -53,19 +62,28 @@ var/global/datum/zlev_manager/zlevels = new
// Returns whether the given z level has a freeze on initialization
/datum/zlev_manager/proc/is_zlevel_dirty(z)
- if(z > z_list.len)
- return 0 // It's not dirty if it's not being tracked
- var/datum/zlevel/our_z = z_list[z]
+ var/datum/zlevel/our_z = get_zlev(z)
return (our_z.dirt_count > 0)
// Increases the dirt count on a z level
/datum/zlev_manager/proc/add_dirt(z)
- var/datum/zlevel/our_z = z_list[z]
+ var/datum/zlevel/our_z = get_zlev(z)
+ if(our_z.dirt_count == 0)
+ log_debug("Placing an init freeze on z-level '[our_z.zpos]'!")
our_z.dirt_count++
// Decreases the dirt count on a z level
/datum/zlev_manager/proc/remove_dirt(z)
- var/datum/zlevel/our_z = z_list[z]
+ var/datum/zlevel/our_z = get_zlev(z)
our_z.dirt_count--
+ if(our_z.dirt_count == 0)
+ our_z.resume_init()
+ if(our_z.dirt_count < 0)
+ log_debug("WARNING: Imbalanced dirt removal")
+ our_z.dirt_count = 0
+
+/datum/zlev_manager/proc/postpone_init(z, thing)
+ var/datum/zlevel/our_z = get_zlev(z)
+ our_z.init_list.Add(thing)