Ports TG lazy-loading of map templates using turf reservations (#28101)

* kills off heaps

* turf reservations

* lazy loading map templates

* atmos + a comment

* port LISTASSERTLEN and rename a proc

* CI happy

* yes

* oops

* removes blocks air and adds comments
This commit is contained in:
Contrabang
2025-02-07 10:15:08 +00:00
committed by GitHub
parent 659c4bb47e
commit f9f3b4aac6
21 changed files with 407 additions and 108 deletions
+2
View File
@@ -13,6 +13,8 @@
#define STATION_CONTACT "Station Contact"
// A level dedicated to admin use
#define ADMIN_LEVEL "Admin Level"
// For Z-levels dedicated to auto-spawning stuff in
#define Z_FLAG_RESERVED "Reserved"
// A level that can be navigated to by the crew without admin intervention or the emergency shuttle.
#define REACHABLE_BY_CREW "Reachable"
// For away missions - used by some consoles
+18
View File
@@ -16,23 +16,41 @@
#define GLOBAL_PROTECT(X)
#endif
/// Standard BYOND global, seriously do not use without an earthshakingly good reason
#define GLOBAL_REAL_VAR(X) var/global/##X
/// Standard typed BYOND global, seriously do not use without an earthshakingly good reason
#define GLOBAL_REAL(X, Typepath) var/global##Typepath/##X
/// Defines a global var on the controller, do not use
#define GLOBAL_RAW(X) /datum/controller/global_vars/var/global##X
/// Create an untyped global with an initializer expression
#define GLOBAL_VAR_INIT(X, InitValue) GLOBAL_RAW(/##X); GLOBAL_MANAGED(X, InitValue)
/// Create a global const var, do not use
#define GLOBAL_VAR_CONST(X, InitValue) GLOBAL_RAW(/const/##X) = InitValue; GLOBAL_UNMANAGED(X)
/// Create a list global with an initializer expression
#define GLOBAL_LIST_INIT(X, InitValue) GLOBAL_RAW(/list/##X); GLOBAL_MANAGED(X, InitValue)
/// Create a list global that is initialized as an empty list
#define GLOBAL_LIST_EMPTY(X) GLOBAL_LIST_INIT(X, list())
/// Create a typed list global with an initializer expression
#define GLOBAL_LIST_INIT_TYPED(X, Typepath, InitValue) GLOBAL_RAW(/list##Typepath/X); GLOBAL_MANAGED(X, InitValue)
/// Create a typed list global that is initialized as an empty list
#define GLOBAL_LIST_EMPTY_TYPED(X, Typepath) GLOBAL_LIST_INIT_TYPED(X, Typepath, list())
/// Create a typed global with an initializer expression
#define GLOBAL_DATUM_INIT(X, Typepath, InitValue) GLOBAL_RAW(Typepath/##X); GLOBAL_MANAGED(X, InitValue)
/// Create an untyped null global
#define GLOBAL_VAR(X) GLOBAL_RAW(/##X); GLOBAL_UNMANAGED(X)
/// Create a null global list
#define GLOBAL_LIST(X) GLOBAL_RAW(/list/##X); GLOBAL_UNMANAGED(X)
/// Create a typed null global
#define GLOBAL_DATUM(X, Typepath) GLOBAL_RAW(Typepath/##X); GLOBAL_UNMANAGED(X)
+7 -1
View File
@@ -144,12 +144,18 @@
#define PASSTAKE (1<<9)
#define PASSBARRICADE (1<<10)
//turf-only flags
//turf-only flags, under the flags variable
#define BLESSED_TILE (1<<0)
#define NO_LAVA_GEN (1<<1) //Blocks lava rivers being generated on the turf
#define NO_RUINS (1<<2)
#define LAVA_BRIDGE (1<<3) //! This turf has already been reserved for a lavaland bridge placement.
// turf flags, under the turf_flags variable
/// If a turf is an unused reservation turf awaiting assignment
#define UNUSED_RESERVATION_TURF (1<<4)
/// If a turf is a reserved turf
#define RESERVATION_TURF (1<<5)
//ORGAN TYPE FLAGS
#define AFFECT_ROBOTIC_ORGAN 1
#define AFFECT_ORGANIC_ORGAN 2
+2
View File
@@ -14,3 +14,5 @@
#define SHUTTLE_DOCKER_LANDING_CLEAR 1
#define SHUTTLE_DOCKER_BLOCKED_BY_HIDDEN_PORT 2
#define SHUTTLE_DOCKER_BLOCKED 3
#define SHUTTLE_TRANSIT_BORDER 16
+7
View File
@@ -13,3 +13,10 @@
#define MINERAL_PREVENT_DIG 0 //! A mineral turf should not be changed when mined.
#define MINERAL_ALLOW_DIG 1 //! A mineral turf should be dug out when mined.
/// Returns an outline (neighboring turfs) of the given block
#define CORNER_OUTLINE(corner, width, height) ( \
CORNER_BLOCK_OFFSET(corner, width + 2, 1, -1, -1) + \
CORNER_BLOCK_OFFSET(corner, width + 2, 1, -1, height) + \
CORNER_BLOCK_OFFSET(corner, 1, height, -1, 0) + \
CORNER_BLOCK_OFFSET(corner, 1, height, width, 0))
+13
View File
@@ -740,6 +740,19 @@
LAZYINITLIST(lazy_list[key]); \
lazy_list[key] |= value;
///Ensures the length of a list is at least I, prefilling it with V if needed. if V is a proc call, it is repeated for each new index so that list() can just make a new list for each item.
#define LISTASSERTLEN(L, I, V...) \
if(length(L) < I) { \
var/_OLD_LENGTH = length(L); \
L.len = I; \
/* Convert the optional argument to a if check */ \
for(var/_USELESS_VAR in list(V)) { \
for(var/_INDEX_TO_ASSIGN_TO in _OLD_LENGTH+1 to I) { \
L[_INDEX_TO_ASSIGN_TO] = V; \
} \
} \
}
//same, but returns nothing and acts on list in place
/proc/shuffle_inplace(list/L)
if(!L)
+5
View File
@@ -112,6 +112,11 @@ DEFINE_BITFIELD(flags, list(
"NO_SCREENTIPS" = NO_SCREENTIPS,
))
DEFINE_BITFIELD(turf_flags, list(
"UNUSED_RESERVATION_TURF" = UNUSED_RESERVATION_TURF,
"RESERVATION_TURF" = RESERVATION_TURF
))
DEFINE_BITFIELD(flags_2, list(
"SLOWS_WHILE_IN_HAND_2" = SLOWS_WHILE_IN_HAND_2,
"NO_EMP_WIRES_2" = NO_EMP_WIRES_2,
+1 -1
View File
@@ -18,7 +18,7 @@ GLOBAL_LIST_EMPTY(hierophant_walls)
GLOBAL_LIST_EMPTY(pandemics)
GLOBAL_LIST_EMPTY(all_areas)
GLOBAL_LIST_EMPTY(all_unique_areas) // List of all unique areas. AKA areas with there_can_be_many = FALSE
GLOBAL_LIST_EMPTY_TYPED(all_unique_areas, /area) // List of all unique areas. AKA areas with there_can_be_many = FALSE
GLOBAL_LIST_EMPTY(machines)
GLOBAL_LIST_EMPTY(telescreens) /// List of entertainment telescreens connected to the "news" cameranet
GLOBAL_LIST_EMPTY(rcd_list) //list of Rapid Construction Devices.
@@ -1,7 +1,7 @@
SUBSYSTEM_DEF(mapping)
name = "Mapping"
init_order = INIT_ORDER_MAPPING // 9
flags = SS_NO_FIRE
/// What map datum are we using
var/datum/map/map_datum
/// What map will be used next round
@@ -31,6 +31,15 @@ SUBSYSTEM_DEF(mapping)
/// Ruin placement manager for lavaland levels.
var/datum/ruin_placer/lavaland/lavaland_ruins_placer
var/num_of_res_levels = 0
var/clearing_reserved_turfs = FALSE
var/list/datum/turf_reservations //list of turf reservations
var/list/turf/unused_turfs = list() //Not actually unused turfs they're unused but reserved for use for whatever requests them. "[zlevel_of_turf]" = list(turfs)
var/list/used_turfs = list() //list of turf = datum/turf_reservation
/// List of lists of turfs to reserve
var/list/lists_to_reserve = list()
// This has to be here because world/New() uses [station_name()], which looks this datum up
/datum/controller/subsystem/mapping/PreInit()
. = ..()
@@ -109,6 +118,8 @@ SUBSYSTEM_DEF(mapping)
// Makes a blank space level for the sake of randomness
GLOB.space_manager.add_new_zlevel("Empty Area", linkage = CROSSLINKED, traits = empty_z_traits)
// Add a reserved z-level
// add_reservation_zlevel() // CURRENTLY DISABLED, AS NOTHING USES IT. IF YOU WANT TO ADD LAZYLOADING TO ANYTHING, MAKE SURE TO REIMPLEMENT THIS
// Setup the Z-level linkage
GLOB.space_manager.do_transition_setup()
@@ -346,4 +357,120 @@ SUBSYSTEM_DEF(mapping)
SSblackbox.record_feedback("nested tally", "keycard_auths", 1, list("emergency station access", "disabled"))
/datum/controller/subsystem/mapping/Recover()
flags |= SS_NO_INIT
num_of_res_levels = SSmapping.num_of_res_levels
clearing_reserved_turfs = SSmapping.clearing_reserved_turfs
turf_reservations = SSmapping.turf_reservations
unused_turfs = SSmapping.unused_turfs
used_turfs = SSmapping.used_turfs
lists_to_reserve = SSmapping.lists_to_reserve
/datum/controller/subsystem/mapping/proc/get_reservation_from_turf(turf/T)
RETURN_TYPE(/datum/turf_reservation)
return used_turfs[T]
/// Requests a /datum/turf_reservation based on the given width, height.
/datum/controller/subsystem/mapping/proc/request_turf_block_reservation(width, height)
UNTIL(!clearing_reserved_turfs)
log_debug("Reserving [width]x[height] turf reservation")
var/datum/turf_reservation/reserve = new /datum/turf_reservation
for(var/i in levels_by_trait(Z_FLAG_RESERVED))
if(reserve.reserve(width, height, i))
return reserve
//If we didn't return at this point, theres a good chance we ran out of room on the exisiting reserved z levels, so lets try a new one
var/z_level_num = add_reservation_zlevel()
if(reserve.reserve(width, height, z_level_num))
return reserve
qdel(reserve)
/datum/controller/subsystem/mapping/proc/add_reservation_zlevel()
num_of_res_levels++
// . here is the z of the just added z-level
. = GLOB.space_manager.add_new_zlevel("Transit/Reserved #[num_of_res_levels]", traits = list(Z_FLAG_RESERVED, BLOCK_TELEPORT, IMPEDES_MAGIC))
initialize_reserved_level(.)
if(!initialized)
return
if(length(SSidlenpcpool.idle_mobs_by_zlevel) == . || !islist(SSidlenpcpool.idle_mobs_by_zlevel)) // arbitrary chosen from these lists that require the length of the z-levels
return
LISTASSERTLEN(SSidlenpcpool.idle_mobs_by_zlevel, ., list())
LISTASSERTLEN(SSmobs.clients_by_zlevel, ., list())
LISTASSERTLEN(SSmobs.dead_players_by_zlevel, ., list())
///Sets up a z level as reserved
///This is not for wiping reserved levels, use wipe_reservations() for that.
///If this is called after SSatom init, it will call Initialize on all turfs on the passed z, as its name promises
/datum/controller/subsystem/mapping/proc/initialize_reserved_level(z)
UNTIL(!clearing_reserved_turfs) //regardless, lets add a check just in case.
log_debug("Initializing new reserved Z-level")
clearing_reserved_turfs = TRUE //This operation will likely clear any existing reservations, so lets make sure nothing tries to make one while we're doing it.
if(!check_level_trait(z, Z_FLAG_RESERVED))
clearing_reserved_turfs = FALSE
CRASH("Invalid z level prepared for reservations.")
var/block = block(SHUTTLE_TRANSIT_BORDER, SHUTTLE_TRANSIT_BORDER, z, world.maxx - SHUTTLE_TRANSIT_BORDER, world.maxy - SHUTTLE_TRANSIT_BORDER)
for(var/turf/T as anything in block)
// No need to empty() these, because they just got created and are already /turf/space.
T.turf_flags |= UNUSED_RESERVATION_TURF
CHECK_TICK
// Gotta create these suckers if we've not done so already
if(SSatoms.initialized)
SSatoms.InitializeAtoms(block(1, 1, world.maxx, world.maxy, z), FALSE)
unused_turfs["[z]"] = block
clearing_reserved_turfs = FALSE
/datum/controller/subsystem/mapping/fire(resumed)
// Cache for sonic speed
var/list/list/turf/unused_turfs = src.unused_turfs
var/list/world_contents = GLOB.all_unique_areas[world.area].contents
// var/list/world_turf_contents_by_z = GLOB.all_unique_areas[world.area].turfs_by_zlevel
var/list/lists_to_reserve = src.lists_to_reserve
var/index = 0
while(index < length(lists_to_reserve))
var/list/packet = lists_to_reserve[index + 1]
var/packetlen = length(packet)
while(packetlen)
if(MC_TICK_CHECK)
if(index)
lists_to_reserve.Cut(1, index)
return
var/turf/reserving_turf = packet[packetlen]
reserving_turf.empty(/turf/space)
LAZYINITLIST(unused_turfs["[reserving_turf.z]"])
if(!(reserving_turf in unused_turfs["[reserving_turf.z]"]))
unused_turfs["[reserving_turf.z]"].Insert(1, reserving_turf)
reserving_turf.turf_flags = UNUSED_RESERVATION_TURF
world_contents += reserving_turf
packet.len--
packetlen = length(packet)
index++
lists_to_reserve.Cut(1, index)
/**
* Lazy loads a template on a lazy-loaded z-level
* If you want to use this as non-debug, make sure to uncomment add_reservation_zlevel in /datum/controller/subsystem/mapping/Initialize()
*/
/datum/controller/subsystem/mapping/proc/lazy_load_template(datum/map_template/template)
RETURN_TYPE(/datum/turf_reservation)
UNTIL(initialized)
var/static/lazy_loading = FALSE
UNTIL(!lazy_loading)
lazy_loading = TRUE
. = _lazy_load_template(template)
lazy_loading = FALSE
/datum/controller/subsystem/mapping/proc/_lazy_load_template(datum/map_template/template)
PRIVATE_PROC(TRUE)
var/datum/turf_reservation/reservation = request_turf_block_reservation(template.width, template.height)
if(!istype(reservation))
return
template.load(reservation.bottom_left_turf)
return reservation
/// Schedules a group of turfs to be handed back to the reservation system's control
/datum/controller/subsystem/mapping/proc/unreserve_turfs(list/turfs)
lists_to_reserve += list(turfs)
+8
View File
@@ -64,3 +64,11 @@
/area/syndicate_mothership/jail
name = "\improper Syndicate Jail"
/area/cordon
name = "CORDON"
icon_state = "cordon"
requires_power = FALSE
always_unpowered = TRUE
dynamic_lighting = DYNAMIC_LIGHTING_DISABLED
valid_territory = FALSE
+5 -2
View File
@@ -34,7 +34,9 @@
var/blocks_air = FALSE
flags = 0
flags = 0 // TODO, someday move all off the flags here to turf_flags
var/turf_flags = NONE
var/image/obscured //camerachunks
@@ -282,8 +284,9 @@
qdel(src) //Just get the side effects and call Destroy
var/list/old_comp_lookup = comp_lookup?.Copy()
var/list/old_signal_procs = signal_procs?.Copy()
var/carryover_turf_flags = turf_flags & (RESERVATION_TURF|UNUSED_RESERVATION_TURF)
var/turf/W = new path(src)
W.turf_flags |= carryover_turf_flags
if(old_comp_lookup)
LAZYOR(W.comp_lookup, old_comp_lookup)
if(old_signal_procs)
+1
View File
@@ -159,6 +159,7 @@ GLOBAL_LIST_INIT(admin_verbs_debug, list(
/proc/machine_upgrade,
/client/proc/map_template_load,
/client/proc/map_template_upload,
/client/proc/map_template_load_lazy,
/client/proc/view_runtimes,
/client/proc/admin_serialize,
/client/proc/uid_log,
@@ -56,3 +56,23 @@
message_admins("<span class='adminnotice'>[key_name_admin(usr)] has uploaded a map template ([map]). Took [stop_watch(timer)]s.</span>")
else
to_chat(usr, "Map template '[map]' failed to load properly")
/client/proc/map_template_load_lazy()
set category = "Debug"
set name = "Map template - Lazy Load"
if(!check_rights(R_DEBUG))
return
var/map = input(usr, "Choose a Map Template to place on the lazy load map level.","Place Map Template") as null|anything in GLOB.map_templates
if(!map)
return
var/datum/map_template/template = GLOB.map_templates[map]
message_admins("<span class='adminnotice'>[key_name_admin(usr)] is lazyloading the map template ([template.name]).</span>")
var/datum/turf_reservation/reserve = SSmapping.lazy_load_template(template)
if(!istype(reserve))
message_admins("<span class='danger'>Lazyloading [template.name] failed! You should report this as a bug.</span>")
return
message_admins("<span class='adminnotice'>[key_name_admin(usr)] has lazyloaded the map template ([template.name]) at [ADMIN_JMP(reserve.bottom_left_turf)]</span>")
+32
View File
@@ -0,0 +1,32 @@
/// Turf type that appears to be a world border, completely impassable and non-interactable to all physical (alive) entities.
/turf/cordon
name = "cordon"
icon = 'icons/turf/walls.dmi'
icon_state = "cordon"
invisibility = INVISIBILITY_ABSTRACT
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
explosion_block = 50
rad_insulation = RAD_FULL_INSULATION
opacity = TRUE
density = TRUE
blocks_air = TRUE
baseturf = /turf/cordon
// /turf/cordon/rust_heretic_act()
// return FALSE
/turf/cordon/acid_act(acidpwr, acid_volume, acid_id)
return FALSE
/turf/cordon/singularity_act()
return FALSE
/turf/cordon/TerraformTurf(path, list/new_baseturfs, flags)
return
/turf/cordon/bullet_act(obj/item/projectile/hitting_projectile, def_zone, piercing_hit)
SHOULD_CALL_PARENT(FALSE) // Fuck you
return
/turf/cordon/Adjacent(atom/neighbor, atom/target, atom/movable/mover)
return FALSE
@@ -1,62 +0,0 @@
// This represents a level we can carve up as we please, and hand out
// chunks of to whatever requests it
/datum/space_level/heap
name = "Heap level #ERROR"
var/datum/space_chunk/top
linkage = UNAFFECTED
/datum/space_level/heap/New(z, name, transition_type, traits)
..(z, "Heap level #[z]", UNAFFECTED, traits)
top = new(1, 1, zpos, world.maxx, world.maxy)
flags = traits
/datum/space_level/heap/proc/request(width, height)
return top.can_fit_space(width, height)
// Returns a space chunk datum for some nerd to work with - tells them what's safe to write into, and such
/datum/space_level/heap/proc/allocate(width, height)
if(!request(width, height))
return null
var/datum/space_chunk/C = top.get_optimal_chunk(width, height)
if(!C)
return null
C.children.Cut()
if(C.width == width && C.height == height && C.is_empty)
C.set_occupied(TRUE)
return C
// Split the chunk into 4 pieces
var/datum/space_chunk/return_chunk = new(C.x, C.y, C.zpos, width, height)
C.children += return_chunk
C.children += new /datum/space_chunk(C.x+width, C.y, C.zpos, C.width-width, height)
C.children += new /datum/space_chunk(C.x, C.y+height, C.zpos, width, C.height-height)
C.children += new /datum/space_chunk(C.x+width, C.y+height, C.zpos, C.width-width, C.height-height)
for(var/datum/space_chunk/C2 in C.children)
C2.parent = C
return_chunk.set_occupied(TRUE)
return return_chunk
/datum/space_level/heap/proc/free(datum/space_chunk/C)
if(!istype(C))
return
if(C.zpos != zpos)
return
C.set_occupied(FALSE)
for(var/turf/T in block(C.x, C.y, C.zpos, C.x + C.width - 1, C.y + C.height - 1, C.zpos))
for(var/atom/movable/M in T)
if(isobserver(M))
continue
M.loc = null
qdel(M, TRUE)
T.ChangeTurf(T.baseturf)
var/datum/space_chunk/last_empty_parent = C
while(last_empty_parent.parent && last_empty_parent.parent.is_empty)
last_empty_parent = last_empty_parent.parent
// Prevent a self-qdel from killing this proc
src = null
for(var/datum/space_chunk/child in last_empty_parent.children)
qdel(child)
last_empty_parent.children.Cut()
@@ -28,6 +28,8 @@
/proc/level_boosts_signal(z)
return check_level_trait(z, BOOSTS_SIGNAL)
#define is_reserved_level(z) check_level_trait(z, ZTRAIT_RESERVED)
// Used for the nuke disk, or for checking if players survived through xenos
/proc/is_secure_level(z)
var/secure = check_level_trait(z, STATION_LEVEL)
@@ -0,0 +1,153 @@
/*
* Turf reservations are used to reserve specific areas of the reserved z-levels for various purposes, typically for late-loading maps.
* It ensures that reserved turfs are properly managed and can be released when no longer needed.
* Reservations are not automatically released, so they must be manually released when no longer needed.
*
* Usage:
* - To create a new reservation, call the `request_turf_block_reservation(width, height)` method from the mapping subsystem.
* - This will return a new instance of /datum/turf_reservation if the reservation is successful.
*
* Releasing:
* - Call the `Release` method on the /datum/turf_reservation instance to release the reserved turfs and cordon turfs.
* - This will return the used turfs to the mapping subsystem to allow for reuse of the turfs.
*/
/datum/turf_reservation
/// All turfs that we've reserved
var/list/reserved_turfs = list()
/// Turfs around the reservation for cordoning
var/list/cordon_turfs = list()
/// Area of turfs next to the cordon to fill with pre_cordon_area's
var/list/pre_cordon_turfs = list()
/// The width of the reservation
var/width = 0
/// The height of the reservation
var/height = 0
/// Bottom left turf of the reservation
var/turf/bottom_left_turf
/// Top right turf of the reservation
var/turf/top_right_turf
/// The turf type the reservation is initially made with
var/turf_type = /turf/space
/datum/turf_reservation/New()
LAZYADD(SSmapping.turf_reservations, src)
/datum/turf_reservation/Destroy()
Release()
LAZYREMOVE(SSmapping.turf_reservations, src)
return ..()
/datum/turf_reservation/proc/Release()
bottom_left_turf = null
top_right_turf = null
var/list/reserved_copy = reserved_turfs.Copy()
SSmapping.used_turfs -= reserved_turfs
reserved_turfs = list()
var/list/cordon_copy = cordon_turfs.Copy()
SSmapping.used_turfs -= cordon_turfs
cordon_turfs = list()
var/release_turfs = reserved_copy + cordon_copy
SSmapping.unreserve_turfs(release_turfs)
/// Attempts to calaculate and store a list of turfs around the reservation for cordoning. Returns whether a valid cordon was calculated
/datum/turf_reservation/proc/calculate_cordon_turfs(turf/bottom_left, turf/top_right)
if(bottom_left.x < 2 || bottom_left.y < 2 || top_right.x > (world.maxx - 2) || top_right.y > (world.maxy - 2))
return FALSE // no space for a cordon here
var/list/possible_turfs = CORNER_OUTLINE(bottom_left, width, height)
// if they're our cordon turfs, accept them
possible_turfs -= cordon_turfs
for(var/turf/cordon_turf as anything in possible_turfs)
// if we changed this to check reservation turf instead of not unused, we could have overlapping cordons.
// Unfortunately, that means adding logic for cordons not being removed if they have multiple edges and I'm lazy
if(!(cordon_turf.turf_flags & UNUSED_RESERVATION_TURF))
return FALSE
cordon_turfs |= possible_turfs
return TRUE
/// Actually generates the cordon around the reservation, and marking the cordon turfs as reserved
/datum/turf_reservation/proc/generate_cordon()
for(var/turf/cordon_turf as anything in cordon_turfs)
var/area/cordon/cordon_area = GLOB.all_unique_areas[/area/cordon] || new /area/cordon
cordon_area.contents += cordon_turf
// Its no longer unused, but its also not "used"
cordon_turf.turf_flags &= ~UNUSED_RESERVATION_TURF
cordon_turf.empty(/turf/cordon)
SSmapping.unused_turfs["[cordon_turf.z]"] -= cordon_turf
// still gets linked to us though
SSmapping.used_turfs[cordon_turf] = src
/// Internal proc which handles reserving the area for the reservation.
/datum/turf_reservation/proc/_reserve_area(width, height, zlevel)
src.width = width
src.height = height
if(width > world.maxx || height > world.maxy || width < 1 || height < 1)
return FALSE
var/list/avail = SSmapping.unused_turfs["[zlevel]"]
var/turf/bottom_left
var/turf/top_right
var/list/turf/final_turfs = list()
var/passing = FALSE
for(var/i in avail)
CHECK_TICK
bottom_left = i
if(!(bottom_left.turf_flags & UNUSED_RESERVATION_TURF))
continue
if(bottom_left.x + width > world.maxx || bottom_left.y + height > world.maxy)
continue
top_right = locate(bottom_left.x + width - 1, bottom_left.y + height - 1, bottom_left.z)
if(!(top_right.turf_flags & UNUSED_RESERVATION_TURF))
continue
final_turfs = block(bottom_left, top_right)
if(!final_turfs)
continue
passing = TRUE
for(var/I in final_turfs)
var/turf/checking = I
if(!(checking.turf_flags & UNUSED_RESERVATION_TURF))
passing = FALSE
break
if(passing) // found a potentially valid area, now try to calculate its cordon
passing = calculate_cordon_turfs(bottom_left, top_right)
if(!passing)
continue
break
if(!passing || !istype(bottom_left) || !istype(top_right))
return FALSE
for(var/turf/T as anything in final_turfs)
reserved_turfs |= T
SSmapping.unused_turfs["[T.z]"] -= T
SSmapping.used_turfs[T] = src
T.turf_flags = (T.turf_flags | RESERVATION_TURF) & ~UNUSED_RESERVATION_TURF
T.empty(turf_type)
bottom_left_turf = bottom_left
top_right_turf = top_right
return TRUE
/datum/turf_reservation/proc/reserve(width, height, z_reservation)
if(!_reserve_area(width, height, z_reservation))
log_debug("Failed turf reservation: releasing")
Release()
return FALSE
log_debug("Turf reservation successful, generating cordon")
generate_cordon()
return TRUE
@@ -134,42 +134,3 @@ GLOBAL_DATUM_INIT(space_manager, /datum/zlev_manager, new())
z_list.Remove(S)
qdel(S)
world.maxz--
// An internally-used proc used for heap-zlevel management
/datum/zlev_manager/proc/add_new_heap()
world.maxz++
var/our_z = world.maxz
var/datum/space_level/yup = new /datum/space_level/heap(our_z, traits = list(BLOCK_TELEPORT, ADMIN_LEVEL))
z_list["[our_z]"] = yup
return yup
// This is what you can call to allocate a section of space
// Later, I'll add an argument to let you define the flags on the region
/datum/zlev_manager/proc/allocate_space(width, height)
if(width > world.maxx || height > world.maxy)
throw EXCEPTION("Too much space requested! \[[width],[height]\]")
if(!length(heaps))
heaps.len++
heaps[length(heaps)] = add_new_heap()
var/datum/space_level/heap/our_heap
var/weve_got_vacancy = 0
for(our_heap in heaps)
weve_got_vacancy = our_heap.request(width, height)
if(weve_got_vacancy)
break // We're sticking with the present value of `our_heap` - it's got room
// This loop will also run out if no vacancies are found
if(!weve_got_vacancy)
heaps.len++
our_heap = add_new_heap()
heaps[length(heaps)] = our_heap
return our_heap.allocate(width, height)
/datum/zlev_manager/proc/free_space(datum/space_chunk/C)
if(!istype(C))
return
var/datum/space_level/heap/heap = z_list["[C.zpos]"]
if(!istype(heap))
throw EXCEPTION("Attempted to free chunk at invalid z-level ([C.x],[C.y],[C.zpos]) [C.width]x[C.height]")
heap.free(C)
Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 10 KiB

+2 -1
View File
@@ -1694,6 +1694,7 @@
#include "code\modules\atmospherics\machinery\portable\portable_atmospherics.dm"
#include "code\modules\atmospherics\machinery\portable\portable_pump.dm"
#include "code\modules\atmospherics\machinery\portable\scrubber.dm"
#include "code\modules\awaymissions\cordon.dm"
#include "code\modules\awaymissions\mob_spawn.dm"
#include "code\modules\awaymissions\zlevel_helpers.dm"
#include "code\modules\awaymissions\maploader\dmm_suite.dm"
@@ -2876,12 +2877,12 @@
#include "code\modules\shuttle\shuttle_rotate.dm"
#include "code\modules\shuttle\supply.dm"
#include "code\modules\shuttle\syndicate_shuttles.dm"
#include "code\modules\space_management\heap_space_level.dm"
#include "code\modules\space_management\level_check.dm"
#include "code\modules\space_management\level_traits.dm"
#include "code\modules\space_management\space_chunk.dm"
#include "code\modules\space_management\space_level.dm"
#include "code\modules\space_management\space_transition.dm"
#include "code\modules\space_management\turf_reservation.dm"
#include "code\modules\space_management\zlevel_manager.dm"
#include "code\modules\station_goals\bluespace_tap.dm"
#include "code\modules\station_goals\bluespace_tap_events.dm"