From bc9849d57fa32cc8f18ac83b6000e35bb78e723e Mon Sep 17 00:00:00 2001 From: Contrabang <91113370+Contrabang@users.noreply.github.com> Date: Sat, 25 Jan 2025 10:38:23 -0700 Subject: [PATCH 1/9] kills off heaps --- .../space_management/heap_space_level.dm | 62 ------------------- .../space_management/zlevel_manager.dm | 39 ------------ 2 files changed, 101 deletions(-) delete mode 100644 code/modules/space_management/heap_space_level.dm diff --git a/code/modules/space_management/heap_space_level.dm b/code/modules/space_management/heap_space_level.dm deleted file mode 100644 index 666bfa9fa821..000000000000 --- a/code/modules/space_management/heap_space_level.dm +++ /dev/null @@ -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() diff --git a/code/modules/space_management/zlevel_manager.dm b/code/modules/space_management/zlevel_manager.dm index d2364652ee6a..4887257493c9 100644 --- a/code/modules/space_management/zlevel_manager.dm +++ b/code/modules/space_management/zlevel_manager.dm @@ -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) From 2882f2c72009183fd4e86e62bc24a8d43f335fbc Mon Sep 17 00:00:00 2001 From: Contrabang <91113370+Contrabang@users.noreply.github.com> Date: Sat, 25 Jan 2025 11:18:22 -0700 Subject: [PATCH 2/9] turf reservations --- _maps/__MAP_DEFINES.dm | 2 + code/__DEFINES/_globals.dm | 18 ++ code/__DEFINES/flags.dm | 4 + code/__DEFINES/shuttle_defines.dm | 2 + code/__DEFINES/turfs.dm | 7 + code/_globalvars/lists/objects.dm | 2 +- .../subsystem/non_firing/SSmapping.dm | 93 +++++++- code/game/area/misc_areas.dm | 8 + code/modules/awaymissions/cordon.dm | 32 +++ .../space_management/turf_reservation.dm | 202 ++++++++++++++++++ icons/turf/areas.dmi | Bin 33447 -> 34397 bytes icons/turf/walls.dmi | Bin 5860 -> 10516 bytes paradise.dme | 7 +- 13 files changed, 372 insertions(+), 5 deletions(-) create mode 100644 code/modules/awaymissions/cordon.dm create mode 100644 code/modules/space_management/turf_reservation.dm diff --git a/_maps/__MAP_DEFINES.dm b/_maps/__MAP_DEFINES.dm index 8f55a0aea114..a275d07a744c 100644 --- a/_maps/__MAP_DEFINES.dm +++ b/_maps/__MAP_DEFINES.dm @@ -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 diff --git a/code/__DEFINES/_globals.dm b/code/__DEFINES/_globals.dm index 083abd4108ef..f80661688148 100644 --- a/code/__DEFINES/_globals.dm +++ b/code/__DEFINES/_globals.dm @@ -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) diff --git a/code/__DEFINES/flags.dm b/code/__DEFINES/flags.dm index 08c5087b4b09..1e6e72c2da79 100644 --- a/code/__DEFINES/flags.dm +++ b/code/__DEFINES/flags.dm @@ -149,6 +149,10 @@ #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. +/// 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 diff --git a/code/__DEFINES/shuttle_defines.dm b/code/__DEFINES/shuttle_defines.dm index 43470086ce22..a656e661a9dd 100644 --- a/code/__DEFINES/shuttle_defines.dm +++ b/code/__DEFINES/shuttle_defines.dm @@ -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 diff --git a/code/__DEFINES/turfs.dm b/code/__DEFINES/turfs.dm index f383b4d5540c..f3149bb42aaa 100644 --- a/code/__DEFINES/turfs.dm +++ b/code/__DEFINES/turfs.dm @@ -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)) diff --git a/code/_globalvars/lists/objects.dm b/code/_globalvars/lists/objects.dm index f918fe47277f..5448b02c2e9a 100644 --- a/code/_globalvars/lists/objects.dm +++ b/code/_globalvars/lists/objects.dm @@ -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(rcd_list) //list of Rapid Construction Devices. diff --git a/code/controllers/subsystem/non_firing/SSmapping.dm b/code/controllers/subsystem/non_firing/SSmapping.dm index a236dabf7f7a..ae435958e019 100644 --- a/code/controllers/subsystem/non_firing/SSmapping.dm +++ b/code/controllers/subsystem/non_firing/SSmapping.dm @@ -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() // Setup the Z-level linkage GLOB.space_manager.do_transition_setup() @@ -347,3 +358,83 @@ SUBSYSTEM_DEF(mapping) /datum/controller/subsystem/mapping/Recover() flags |= SS_NO_INIT + +/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) + 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/datum/space_level/newReserved = add_reservation_zlevel() + initialize_reserved_level(newReserved.zpos) + if(reserve.reserve(width, height, newReserved.zpos)) + return reserve + qdel(reserve) + +/datum/controller/subsystem/mapping/proc/add_reservation_zlevel() + num_of_res_levels++ + return GLOB.space_manager.add_new_zlevel("Transit/Reserved #[num_of_res_levels]", traits = list(Z_FLAG_RESERVED, BLOCK_TELEPORT, IMPEDES_MAGIC)) + +///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. + 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, world.maxx - SHUTTLE_TRANSIT_BORDER, world.maxy - SHUTTLE_TRANSIT_BORDER, z) + for(var/turf/T as anything in block) + // No need to empty() these, because they just got created and are already /turf/open/space/basic. + T.flags |= UNUSED_RESERVATION_TURF + T.blocks_air = TRUE + 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)) + + unused_turfs["[z]"] = block + clearing_reserved_turfs = FALSE + +/datum/controller/subsystem/mapping/fire(resumed) + // Cache for sonic speed + var/list/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]"]) + unused_turfs["[reserving_turf.z]"] |= reserving_turf + var/area/old_area = reserving_turf.loc + // LISTASSERTLEN(old_area.turfs_to_uncontain_by_zlevel, reserving_turf.z, list()) + // old_area.turfs_to_uncontain_by_zlevel[reserving_turf.z] += reserving_turf + reserving_turf.flags = UNUSED_RESERVATION_TURF + // reservation turfs are not allowed to interact with atmos at all + reserving_turf.blocks_air = TRUE + + world_contents += reserving_turf + // LISTASSERTLEN(world_turf_contents_by_z, reserving_turf.z, list()) + // world_turf_contents_by_z[reserving_turf.z] += reserving_turf + packet.len-- + packetlen = length(packet) + + index++ + lists_to_reserve.Cut(1, index) diff --git a/code/game/area/misc_areas.dm b/code/game/area/misc_areas.dm index e9bd5f40a6a4..d1e2b01583c7 100644 --- a/code/game/area/misc_areas.dm +++ b/code/game/area/misc_areas.dm @@ -63,3 +63,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 diff --git a/code/modules/awaymissions/cordon.dm b/code/modules/awaymissions/cordon.dm new file mode 100644 index 000000000000..4d44766ac668 --- /dev/null +++ b/code/modules/awaymissions/cordon.dm @@ -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 diff --git a/code/modules/space_management/turf_reservation.dm b/code/modules/space_management/turf_reservation.dm new file mode 100644 index 000000000000..22765d22a4a3 --- /dev/null +++ b/code/modules/space_management/turf_reservation.dm @@ -0,0 +1,202 @@ +//Yes, they can only be rectangular. +//Yes, I'm sorry. +/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 + + /// List of the bottom left turfs. Indexed by what their z index for this reservation is + var/list/bottom_left_turfs = list() + + /// List of the top right turfs. Indexed by what their z index for this reservation is + var/list/top_right_turfs = list() + + /// The turf type the reservation is initially made with + var/turf_type = /turf/space + + /// Do we override baseturfs with turf_type? + var/turf_type_is_baseturf = TRUE + + ///Distance away from the cordon where we can put a "sort-cordon" and run some extra code (see make_repel). 0 makes nothing happen + var/pre_cordon_distance = 0 + +/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_turfs.Cut() + top_right_turfs.Cut() + + 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 + + for(var/turf/reserved_turf as anything in release_turfs) + + // immediately disconnect from atmos + reserved_turf.blocks_air = TRUE + // CALCULATE_ADJACENT_TURFS(reserved_turf, KILL_EXCITED) + + // Makes the linter happy, even tho we don't await this + SSmapping.reserve_turfs(release_turfs) + // INVOKE_ASYNC(SSmapping, TYPE_PROC_REF(/datum/controller/subsystem/mapping, reserve_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(!(cordon_turf.flags & UNUSED_RESERVATION_TURF)) + return FALSE + cordon_turfs |= possible_turfs + + if(pre_cordon_distance) + var/turf/offset_turf = locate(bottom_left.x + pre_cordon_distance, bottom_left.y + pre_cordon_distance, bottom_left.z) + var/list/to_add = CORNER_OUTLINE(offset_turf, width - pre_cordon_distance * 2, height - pre_cordon_distance * 2) //we step-by-stop move inwards from the outer cordon + for(var/turf/turf_being_added as anything in to_add) + pre_cordon_turfs |= turf_being_added //add one by one so we can filter out duplicates + + 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 + // var/area/old_area = cordon_turf.loc + + // LISTASSERTLEN(old_area.turfs_to_uncontain_by_zlevel, cordon_turf.z, list()) + // LISTASSERTLEN(cordon_area.turfs_by_zlevel, cordon_turf.z, list()) + // old_area.turfs_to_uncontain_by_zlevel[cordon_turf.z] += cordon_turf + // cordon_area.turfs_by_zlevel[cordon_turf.z] += cordon_turf + // cordon_area.contents += cordon_turf + + // Its no longer unused, but its also not "used" + cordon_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 + + // //swap the area with the pre-cordoning area + // for(var/turf/pre_cordon_turf as anything in pre_cordon_turfs) + // make_repel(pre_cordon_turf) + +/// 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/currently_inspecting_turf + var/turf/other_corner + var/list/turf/final_turfs = list() + var/passing = FALSE + for(var/i in avail) + CHECK_TICK + currently_inspecting_turf = i + if(!(currently_inspecting_turf.flags & UNUSED_RESERVATION_TURF)) + continue + if(currently_inspecting_turf.x + width > world.maxx || currently_inspecting_turf.y + height > world.maxy) + continue + other_corner = locate(currently_inspecting_turf.x + width - 1, currently_inspecting_turf.y + height - 1, currently_inspecting_turf.z) + if(!(other_corner.flags & UNUSED_RESERVATION_TURF)) + continue + final_turfs = block(currently_inspecting_turf, other_corner) + if(!final_turfs) + continue + passing = TRUE + for(var/I in final_turfs) + var/turf/checking = I + if(!(checking.flags & UNUSED_RESERVATION_TURF)) + passing = FALSE + break + if(passing) // found a potentially valid area, now try to calculate its cordon + passing = calculate_cordon_turfs(currently_inspecting_turf, other_corner) + if(!passing) + continue + break + if(!passing || !istype(currently_inspecting_turf) || !istype(other_corner)) + return FALSE + for(var/i in final_turfs) + var/turf/T = i + reserved_turfs |= T + SSmapping.unused_turfs["[T.z]"] -= T + SSmapping.used_turfs[T] = src + T.flags = (T.flags | RESERVATION_TURF) & ~UNUSED_RESERVATION_TURF + T.empty(turf_type) + + bottom_left_turfs += currently_inspecting_turf + top_right_turfs += other_corner + return TRUE + +/datum/turf_reservation/proc/reserve(width, height, z_reservation) + + var/failed_reservation = FALSE + if(!_reserve_area(width, height, z_reservation)) + failed_reservation = TRUE + break + + if(failed_reservation) + Release() + return FALSE + + generate_cordon() + return TRUE + +/// Calculates the effective bounds information for the given turf. Returns a list of the information, or null if not applicable. +/datum/turf_reservation/proc/calculate_turf_bounds_information(turf/target) + var/turf/bottom_left = bottom_left_turfs[z_idx] + var/turf/top_right = top_right_turfs[z_idx] + var/bl_x = bottom_left.x + var/bl_y = bottom_left.y + var/tr_x = top_right.x + var/tr_y = top_right.y + + if(target.x < bl_x) + continue + + if(target.y < bl_y) + continue + + if(target.x > tr_x) + continue + + if(target.y > tr_y) + continue + + var/list/return_information = list() + return_information["z_idx"] = z_idx + return_information["offset_x"] = target.x - bl_x + return_information["offset_y"] = target.y - bl_y + return return_information + +/// Schedules a group of turfs to be handed back to the reservation system's control +/datum/controller/subsystem/mapping/proc/reserve_turfs(list/turfs) + lists_to_reserve += list(turfs) diff --git a/icons/turf/areas.dmi b/icons/turf/areas.dmi index d27c76b49de6544ee3a12011252a4bb4170aa1b7..69aca13c76e936317389f6cd5af68e8f1989cf7c 100755 GIT binary patch literal 34397 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 D;qMU( literal 33447 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�rF{Q(nd%2&qEiJQdgB5YFbz<=B~ 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 diff --git a/icons/turf/walls.dmi b/icons/turf/walls.dmi index d8d4d8fdd7901b202feba17073e21b02b4b1e0bf..fb71d66c556ca0bcdca0b3b4f88ef28cabbb01c6 100644 GIT binary patch literal 10516 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 diff --git a/paradise.dme b/paradise.dme index 94f084b0272a..2e85095448db 100644 --- a/paradise.dme +++ b/paradise.dme @@ -453,8 +453,8 @@ #include "code\datums\components\footstep.dm" #include "code\datums\components\forces_doors_open.dm" #include "code\datums\components\fullauto.dm" -#include "code\datums\components\jetpack_component.dm" #include "code\datums\components\ghost_direct_control.dm" +#include "code\datums\components\jetpack_component.dm" #include "code\datums\components\label.dm" #include "code\datums\components\largeobjecttransparency.dm" #include "code\datums\components\material_container.dm" @@ -1531,8 +1531,8 @@ #include "code\modules\antagonists\_common\antag_hud.dm" #include "code\modules\antagonists\_common\antag_spawner.dm" #include "code\modules\antagonists\_common\antag_team.dm" -#include "code\modules\antagonists\abductor\team_abductor.dm" #include "code\modules\antagonists\abductor\datum_abductor.dm" +#include "code\modules\antagonists\abductor\team_abductor.dm" #include "code\modules\antagonists\antag_org\antag_org_datum.dm" #include "code\modules\antagonists\antag_org\antag_org_syndicate.dm" #include "code\modules\antagonists\changeling\changeling_power.dm" @@ -1692,6 +1692,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" @@ -2874,12 +2875,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" From a04f889378573b27d2dd83a5a7aa9c1ff82ac9be Mon Sep 17 00:00:00 2001 From: Contrabang <91113370+Contrabang@users.noreply.github.com> Date: Sat, 25 Jan 2025 13:50:10 -0700 Subject: [PATCH 3/9] lazy loading map templates --- code/__DEFINES/flags.dm | 4 +- code/_globalvars/bitfields.dm | 5 + .../subsystem/non_firing/SSmapping.dm | 55 ++++++-- code/game/turfs/turf.dm | 7 +- code/modules/admin/admin_verbs.dm | 1 + .../admin/verbs/map_template_loadverb.dm | 20 +++ code/modules/space_management/level_traits.dm | 2 + .../space_management/turf_reservation.dm | 120 +++++++++--------- 8 files changed, 137 insertions(+), 77 deletions(-) diff --git a/code/__DEFINES/flags.dm b/code/__DEFINES/flags.dm index 1e6e72c2da79..63fddee37456 100644 --- a/code/__DEFINES/flags.dm +++ b/code/__DEFINES/flags.dm @@ -144,11 +144,13 @@ #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 diff --git a/code/_globalvars/bitfields.dm b/code/_globalvars/bitfields.dm index e165eabdb705..3f1de6a67fde 100644 --- a/code/_globalvars/bitfields.dm +++ b/code/_globalvars/bitfields.dm @@ -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, diff --git a/code/controllers/subsystem/non_firing/SSmapping.dm b/code/controllers/subsystem/non_firing/SSmapping.dm index ae435958e019..f5fc006499d4 100644 --- a/code/controllers/subsystem/non_firing/SSmapping.dm +++ b/code/controllers/subsystem/non_firing/SSmapping.dm @@ -119,7 +119,7 @@ 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() + // 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() @@ -357,7 +357,12 @@ 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) @@ -366,40 +371,42 @@ SUBSYSTEM_DEF(mapping) /// 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/datum/space_level/newReserved = add_reservation_zlevel() - initialize_reserved_level(newReserved.zpos) - if(reserve.reserve(width, height, newReserved.zpos)) + 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++ - return GLOB.space_manager.add_new_zlevel("Transit/Reserved #[num_of_res_levels]", traits = list(Z_FLAG_RESERVED, BLOCK_TELEPORT, IMPEDES_MAGIC)) + . = GLOB.space_manager.add_new_zlevel("Transit/Reserved #[num_of_res_levels]", traits = list(Z_FLAG_RESERVED, BLOCK_TELEPORT, IMPEDES_MAGIC)) + initialize_reserved_level(.) ///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, world.maxx - SHUTTLE_TRANSIT_BORDER, world.maxy - SHUTTLE_TRANSIT_BORDER, z) + 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/open/space/basic. - T.flags |= UNUSED_RESERVATION_TURF + // No need to empty() these, because they just got created and are already /turf/space. + T.turf_flags |= UNUSED_RESERVATION_TURF T.blocks_air = TRUE 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)) + SSatoms.InitializeAtoms(block(1, 1, world.maxx, world.maxy, z), FALSE) unused_turfs["[z]"] = block clearing_reserved_turfs = FALSE @@ -423,10 +430,10 @@ SUBSYSTEM_DEF(mapping) reserving_turf.empty(/turf/space) LAZYINITLIST(unused_turfs["[reserving_turf.z]"]) unused_turfs["[reserving_turf.z]"] |= reserving_turf - var/area/old_area = reserving_turf.loc + // var/area/old_area = reserving_turf.loc // LISTASSERTLEN(old_area.turfs_to_uncontain_by_zlevel, reserving_turf.z, list()) // old_area.turfs_to_uncontain_by_zlevel[reserving_turf.z] += reserving_turf - reserving_turf.flags = UNUSED_RESERVATION_TURF + reserving_turf.turf_flags = UNUSED_RESERVATION_TURF // reservation turfs are not allowed to interact with atmos at all reserving_turf.blocks_air = TRUE @@ -438,3 +445,27 @@ SUBSYSTEM_DEF(mapping) 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 diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm index b2c02b58364d..b27a906a9720 100644 --- a/code/game/turfs/turf.dm +++ b/code/game/turfs/turf.dm @@ -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) diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index f12dd40466da..b5a6fd1d5fcd 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -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, diff --git a/code/modules/admin/verbs/map_template_loadverb.dm b/code/modules/admin/verbs/map_template_loadverb.dm index 80d46e18550d..c05060131fcf 100644 --- a/code/modules/admin/verbs/map_template_loadverb.dm +++ b/code/modules/admin/verbs/map_template_loadverb.dm @@ -56,3 +56,23 @@ message_admins("[key_name_admin(usr)] has uploaded a map template ([map]). Took [stop_watch(timer)]s.") 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("[key_name_admin(usr)] is lazyloading the map template ([template.name]).") + var/datum/turf_reservation/reserve = SSmapping.lazy_load_template(template) + if(!istype(reserve)) + message_admins("Lazyloading [template.name] failed! You should report this as a bug.") + return + message_admins("[key_name_admin(usr)] has lazyloaded the map template ([template.name]) at [ADMIN_JMP(reserve.bottom_left_turf)]") + diff --git a/code/modules/space_management/level_traits.dm b/code/modules/space_management/level_traits.dm index 0634d9299902..aa02bdaa44c1 100644 --- a/code/modules/space_management/level_traits.dm +++ b/code/modules/space_management/level_traits.dm @@ -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) diff --git a/code/modules/space_management/turf_reservation.dm b/code/modules/space_management/turf_reservation.dm index 22765d22a4a3..fbad0cbb7c68 100644 --- a/code/modules/space_management/turf_reservation.dm +++ b/code/modules/space_management/turf_reservation.dm @@ -16,20 +16,20 @@ /// The height of the reservation var/height = 0 - /// List of the bottom left turfs. Indexed by what their z index for this reservation is - var/list/bottom_left_turfs = list() + /// Bottom left turf of the reservation + var/turf/bottom_left_turf - /// List of the top right turfs. Indexed by what their z index for this reservation is - var/list/top_right_turfs = list() + /// 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 /// Do we override baseturfs with turf_type? - var/turf_type_is_baseturf = TRUE + // var/turf_type_is_baseturf = TRUE - ///Distance away from the cordon where we can put a "sort-cordon" and run some extra code (see make_repel). 0 makes nothing happen - var/pre_cordon_distance = 0 + // ///Distance away from the cordon where we can put a "sort-cordon" and run some extra code (see make_repel). 0 makes nothing happen + // var/pre_cordon_distance = 0 /datum/turf_reservation/New() LAZYADD(SSmapping.turf_reservations, src) @@ -40,8 +40,8 @@ return ..() /datum/turf_reservation/proc/Release() - bottom_left_turfs.Cut() - top_right_turfs.Cut() + bottom_left_turf = null + top_right_turf = null var/list/reserved_copy = reserved_turfs.Copy() SSmapping.used_turfs -= reserved_turfs @@ -72,32 +72,32 @@ // if they're our cordon turfs, accept them possible_turfs -= cordon_turfs for(var/turf/cordon_turf as anything in possible_turfs) - if(!(cordon_turf.flags & UNUSED_RESERVATION_TURF)) + if(!(cordon_turf.turf_flags & UNUSED_RESERVATION_TURF)) return FALSE cordon_turfs |= possible_turfs - if(pre_cordon_distance) - var/turf/offset_turf = locate(bottom_left.x + pre_cordon_distance, bottom_left.y + pre_cordon_distance, bottom_left.z) - var/list/to_add = CORNER_OUTLINE(offset_turf, width - pre_cordon_distance * 2, height - pre_cordon_distance * 2) //we step-by-stop move inwards from the outer cordon - for(var/turf/turf_being_added as anything in to_add) - pre_cordon_turfs |= turf_being_added //add one by one so we can filter out duplicates + // if(pre_cordon_distance) + // var/turf/offset_turf = locate(bottom_left.x + pre_cordon_distance, bottom_left.y + pre_cordon_distance, bottom_left.z) + // var/list/to_add = CORNER_OUTLINE(offset_turf, width - pre_cordon_distance * 2, height - pre_cordon_distance * 2) //we step-by-stop move inwards from the outer cordon + // for(var/turf/turf_being_added as anything in to_add) + // pre_cordon_turfs |= turf_being_added //add one by one so we can filter out duplicates 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 + var/area/cordon/cordon_area = GLOB.all_unique_areas[/area/cordon] || new /area/cordon // var/area/old_area = cordon_turf.loc // LISTASSERTLEN(old_area.turfs_to_uncontain_by_zlevel, cordon_turf.z, list()) // LISTASSERTLEN(cordon_area.turfs_by_zlevel, cordon_turf.z, list()) // old_area.turfs_to_uncontain_by_zlevel[cordon_turf.z] += cordon_turf // cordon_area.turfs_by_zlevel[cordon_turf.z] += cordon_turf - // cordon_area.contents += cordon_turf + cordon_area.contents += cordon_turf // Its no longer unused, but its also not "used" - cordon_turf.flags &= ~UNUSED_RESERVATION_TURF + 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 @@ -114,88 +114,84 @@ if(width > world.maxx || height > world.maxy || width < 1 || height < 1) return FALSE var/list/avail = SSmapping.unused_turfs["[zlevel]"] - var/turf/currently_inspecting_turf - var/turf/other_corner + var/turf/bottom_left + var/turf/top_right var/list/turf/final_turfs = list() var/passing = FALSE for(var/i in avail) CHECK_TICK - currently_inspecting_turf = i - if(!(currently_inspecting_turf.flags & UNUSED_RESERVATION_TURF)) + bottom_left = i + if(!(bottom_left.turf_flags & UNUSED_RESERVATION_TURF)) continue - if(currently_inspecting_turf.x + width > world.maxx || currently_inspecting_turf.y + height > world.maxy) + if(bottom_left.x + width > world.maxx || bottom_left.y + height > world.maxy) continue - other_corner = locate(currently_inspecting_turf.x + width - 1, currently_inspecting_turf.y + height - 1, currently_inspecting_turf.z) - if(!(other_corner.flags & UNUSED_RESERVATION_TURF)) + 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(currently_inspecting_turf, other_corner) + 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.flags & UNUSED_RESERVATION_TURF)) + 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(currently_inspecting_turf, other_corner) + passing = calculate_cordon_turfs(bottom_left, top_right) if(!passing) continue break - if(!passing || !istype(currently_inspecting_turf) || !istype(other_corner)) + if(!passing || !istype(bottom_left) || !istype(top_right)) return FALSE for(var/i in final_turfs) var/turf/T = i reserved_turfs |= T SSmapping.unused_turfs["[T.z]"] -= T SSmapping.used_turfs[T] = src - T.flags = (T.flags | RESERVATION_TURF) & ~UNUSED_RESERVATION_TURF + T.turf_flags = (T.turf_flags | RESERVATION_TURF) & ~UNUSED_RESERVATION_TURF T.empty(turf_type) - bottom_left_turfs += currently_inspecting_turf - top_right_turfs += other_corner + bottom_left_turf = bottom_left + top_right_turf = top_right return TRUE /datum/turf_reservation/proc/reserve(width, height, z_reservation) - var/failed_reservation = FALSE if(!_reserve_area(width, height, z_reservation)) - failed_reservation = TRUE - break - - if(failed_reservation) + log_debug("Failed turf reservation: releasing") Release() return FALSE + log_debug("Turf reservation successful, generating cordon") generate_cordon() return TRUE /// Calculates the effective bounds information for the given turf. Returns a list of the information, or null if not applicable. -/datum/turf_reservation/proc/calculate_turf_bounds_information(turf/target) - var/turf/bottom_left = bottom_left_turfs[z_idx] - var/turf/top_right = top_right_turfs[z_idx] - var/bl_x = bottom_left.x - var/bl_y = bottom_left.y - var/tr_x = top_right.x - var/tr_y = top_right.y - - if(target.x < bl_x) - continue - - if(target.y < bl_y) - continue - - if(target.x > tr_x) - continue - - if(target.y > tr_y) - continue - - var/list/return_information = list() - return_information["z_idx"] = z_idx - return_information["offset_x"] = target.x - bl_x - return_information["offset_y"] = target.y - bl_y - return return_information +// /datum/turf_reservation/proc/calculate_turf_bounds_information(turf/target) +// var/turf/bottom_left = bottom_left_turfs[z_idx] +// var/turf/top_right = top_right_turfs[z_idx] +// var/bl_x = bottom_left.x +// var/bl_y = bottom_left.y +// var/tr_x = top_right.x +// var/tr_y = top_right.y + +// if(target.x < bl_x) +// return + +// if(target.y < bl_y) +// return + +// if(target.x > tr_x) +// return + +// if(target.y > tr_y) +// return + +// var/list/return_information = list() +// return_information["offset_x"] = target.x - bl_x +// return_information["offset_y"] = target.y - bl_y +// return return_information /// Schedules a group of turfs to be handed back to the reservation system's control /datum/controller/subsystem/mapping/proc/reserve_turfs(list/turfs) From 2dea63c347eb7fcfc902d1c4286decc6309ff4ee Mon Sep 17 00:00:00 2001 From: Contrabang <91113370+Contrabang@users.noreply.github.com> Date: Sat, 25 Jan 2025 13:58:36 -0700 Subject: [PATCH 4/9] atmos + a comment --- code/controllers/subsystem/non_firing/SSmapping.dm | 12 ++++++++++-- code/modules/space_management/turf_reservation.dm | 3 +++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/code/controllers/subsystem/non_firing/SSmapping.dm b/code/controllers/subsystem/non_firing/SSmapping.dm index f5fc006499d4..f7873e2ddd4b 100644 --- a/code/controllers/subsystem/non_firing/SSmapping.dm +++ b/code/controllers/subsystem/non_firing/SSmapping.dm @@ -386,6 +386,13 @@ SUBSYSTEM_DEF(mapping) num_of_res_levels++ . = 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 + SSidlenpcpool.idle_mobs_by_zlevel.len = . + SSmobs.clients_by_zlevel.len = . + SSmobs.dead_players_by_zlevel.len = . ///Sets up a z level as reserved ///This is not for wiping reserved levels, use wipe_reservations() for that. @@ -413,7 +420,7 @@ SUBSYSTEM_DEF(mapping) /datum/controller/subsystem/mapping/fire(resumed) // Cache for sonic speed - var/list/unused_turfs = src.unused_turfs + 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 @@ -429,7 +436,8 @@ SUBSYSTEM_DEF(mapping) var/turf/reserving_turf = packet[packetlen] reserving_turf.empty(/turf/space) LAZYINITLIST(unused_turfs["[reserving_turf.z]"]) - unused_turfs["[reserving_turf.z]"] |= reserving_turf + if(!(reserving_turf in unused_turfs["[reserving_turf.z]"])) + unused_turfs["[reserving_turf.z]"].Insert(1, reserving_turf) // var/area/old_area = reserving_turf.loc // LISTASSERTLEN(old_area.turfs_to_uncontain_by_zlevel, reserving_turf.z, list()) // old_area.turfs_to_uncontain_by_zlevel[reserving_turf.z] += reserving_turf diff --git a/code/modules/space_management/turf_reservation.dm b/code/modules/space_management/turf_reservation.dm index fbad0cbb7c68..e7e4324cbaa9 100644 --- a/code/modules/space_management/turf_reservation.dm +++ b/code/modules/space_management/turf_reservation.dm @@ -72,6 +72,8 @@ // 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 @@ -150,6 +152,7 @@ SSmapping.unused_turfs["[T.z]"] -= T SSmapping.used_turfs[T] = src T.turf_flags = (T.turf_flags | RESERVATION_TURF) & ~UNUSED_RESERVATION_TURF + T.blocks_air = FALSE // Experimental atmos on this z-level T.empty(turf_type) bottom_left_turf = bottom_left From e3abf84fc9d83459a046c9be20125e7ad61506af Mon Sep 17 00:00:00 2001 From: Contrabang <91113370+Contrabang@users.noreply.github.com> Date: Sat, 25 Jan 2025 14:41:28 -0700 Subject: [PATCH 5/9] port LISTASSERTLEN and rename a proc --- code/__HELPERS/lists.dm | 13 ++++++++ .../subsystem/non_firing/SSmapping.dm | 7 ++-- .../space_management/turf_reservation.dm | 32 ++----------------- 3 files changed, 20 insertions(+), 32 deletions(-) diff --git a/code/__HELPERS/lists.dm b/code/__HELPERS/lists.dm index 47947cb245f1..223e34fa6e6d 100644 --- a/code/__HELPERS/lists.dm +++ b/code/__HELPERS/lists.dm @@ -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) diff --git a/code/controllers/subsystem/non_firing/SSmapping.dm b/code/controllers/subsystem/non_firing/SSmapping.dm index f7873e2ddd4b..aec7fbbcaf58 100644 --- a/code/controllers/subsystem/non_firing/SSmapping.dm +++ b/code/controllers/subsystem/non_firing/SSmapping.dm @@ -384,15 +384,16 @@ SUBSYSTEM_DEF(mapping) /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 - SSidlenpcpool.idle_mobs_by_zlevel.len = . - SSmobs.clients_by_zlevel.len = . - SSmobs.dead_players_by_zlevel.len = . + 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. diff --git a/code/modules/space_management/turf_reservation.dm b/code/modules/space_management/turf_reservation.dm index e7e4324cbaa9..1f59e5bf4038 100644 --- a/code/modules/space_management/turf_reservation.dm +++ b/code/modules/space_management/turf_reservation.dm @@ -60,8 +60,8 @@ // CALCULATE_ADJACENT_TURFS(reserved_turf, KILL_EXCITED) // Makes the linter happy, even tho we don't await this - SSmapping.reserve_turfs(release_turfs) - // INVOKE_ASYNC(SSmapping, TYPE_PROC_REF(/datum/controller/subsystem/mapping, reserve_turfs), release_turfs) + SSmapping.unreserve_turfs(release_turfs) + // INVOKE_ASYNC(SSmapping, TYPE_PROC_REF(/datum/controller/subsystem/mapping, 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) @@ -170,32 +170,6 @@ generate_cordon() return TRUE -/// Calculates the effective bounds information for the given turf. Returns a list of the information, or null if not applicable. -// /datum/turf_reservation/proc/calculate_turf_bounds_information(turf/target) -// var/turf/bottom_left = bottom_left_turfs[z_idx] -// var/turf/top_right = top_right_turfs[z_idx] -// var/bl_x = bottom_left.x -// var/bl_y = bottom_left.y -// var/tr_x = top_right.x -// var/tr_y = top_right.y - -// if(target.x < bl_x) -// return - -// if(target.y < bl_y) -// return - -// if(target.x > tr_x) -// return - -// if(target.y > tr_y) -// return - -// var/list/return_information = list() -// return_information["offset_x"] = target.x - bl_x -// return_information["offset_y"] = target.y - bl_y -// return return_information - /// Schedules a group of turfs to be handed back to the reservation system's control -/datum/controller/subsystem/mapping/proc/reserve_turfs(list/turfs) +/datum/controller/subsystem/mapping/proc/unreserve_turfs(list/turfs) lists_to_reserve += list(turfs) From 47128b71bdee26091cc48f5eba649aa4788d4fed Mon Sep 17 00:00:00 2001 From: Contrabang <91113370+Contrabang@users.noreply.github.com> Date: Sat, 25 Jan 2025 14:48:51 -0700 Subject: [PATCH 6/9] CI happy --- code/__HELPERS/lists.dm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/code/__HELPERS/lists.dm b/code/__HELPERS/lists.dm index 223e34fa6e6d..8ff538f3d37f 100644 --- a/code/__HELPERS/lists.dm +++ b/code/__HELPERS/lists.dm @@ -742,12 +742,12 @@ ///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) { \ + 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) { \ + 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; \ } \ } \ From 6f850db0495cc4b39ef8c9223d00bcf28d4209fb Mon Sep 17 00:00:00 2001 From: Contrabang <91113370+Contrabang@users.noreply.github.com> Date: Sat, 25 Jan 2025 16:43:15 -0700 Subject: [PATCH 7/9] yes --- code/controllers/subsystem/non_firing/SSmapping.dm | 5 +++++ code/modules/space_management/turf_reservation.dm | 10 ++++------ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/code/controllers/subsystem/non_firing/SSmapping.dm b/code/controllers/subsystem/non_firing/SSmapping.dm index aec7fbbcaf58..41083df298e8 100644 --- a/code/controllers/subsystem/non_firing/SSmapping.dm +++ b/code/controllers/subsystem/non_firing/SSmapping.dm @@ -477,4 +477,9 @@ SUBSYSTEM_DEF(mapping) return template.load(reservation.bottom_left_turf) + reservation.post_load() 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) diff --git a/code/modules/space_management/turf_reservation.dm b/code/modules/space_management/turf_reservation.dm index 1f59e5bf4038..d96547fcffe1 100644 --- a/code/modules/space_management/turf_reservation.dm +++ b/code/modules/space_management/turf_reservation.dm @@ -146,13 +146,11 @@ break if(!passing || !istype(bottom_left) || !istype(top_right)) return FALSE - for(var/i in final_turfs) - var/turf/T = i + 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.blocks_air = FALSE // Experimental atmos on this z-level T.empty(turf_type) bottom_left_turf = bottom_left @@ -170,6 +168,6 @@ generate_cordon() return TRUE -/// 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) +// /datum/turf_reservation/proc/post_load() +// for(var/turf/T as anything in reserved_turfs) +// T.blocks_air = initial(T.blocks_air) // Experimental atmos on this z-level From 8d9cf3f7aaae97cc42f653b152a0e6fbc6684b5d Mon Sep 17 00:00:00 2001 From: Contrabang <91113370+Contrabang@users.noreply.github.com> Date: Sat, 25 Jan 2025 17:48:57 -0700 Subject: [PATCH 8/9] oops --- code/controllers/subsystem/non_firing/SSmapping.dm | 1 - 1 file changed, 1 deletion(-) diff --git a/code/controllers/subsystem/non_firing/SSmapping.dm b/code/controllers/subsystem/non_firing/SSmapping.dm index 41083df298e8..07d868bf1535 100644 --- a/code/controllers/subsystem/non_firing/SSmapping.dm +++ b/code/controllers/subsystem/non_firing/SSmapping.dm @@ -477,7 +477,6 @@ SUBSYSTEM_DEF(mapping) return template.load(reservation.bottom_left_turf) - reservation.post_load() return reservation /// Schedules a group of turfs to be handed back to the reservation system's control From effb1247c29b33aebc4785aa90d7718e5c2d5974 Mon Sep 17 00:00:00 2001 From: Contrabang <91113370+Contrabang@users.noreply.github.com> Date: Thu, 6 Feb 2025 13:40:23 -0700 Subject: [PATCH 9/9] removes blocks air and adds comments --- .../subsystem/non_firing/SSmapping.dm | 8 ---- .../space_management/turf_reservation.dm | 48 ++++++------------- 2 files changed, 14 insertions(+), 42 deletions(-) diff --git a/code/controllers/subsystem/non_firing/SSmapping.dm b/code/controllers/subsystem/non_firing/SSmapping.dm index 07d868bf1535..6cf89d96c4a8 100644 --- a/code/controllers/subsystem/non_firing/SSmapping.dm +++ b/code/controllers/subsystem/non_firing/SSmapping.dm @@ -409,7 +409,6 @@ SUBSYSTEM_DEF(mapping) 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 - T.blocks_air = TRUE CHECK_TICK // Gotta create these suckers if we've not done so already @@ -439,16 +438,9 @@ SUBSYSTEM_DEF(mapping) LAZYINITLIST(unused_turfs["[reserving_turf.z]"]) if(!(reserving_turf in unused_turfs["[reserving_turf.z]"])) unused_turfs["[reserving_turf.z]"].Insert(1, reserving_turf) - // var/area/old_area = reserving_turf.loc - // LISTASSERTLEN(old_area.turfs_to_uncontain_by_zlevel, reserving_turf.z, list()) - // old_area.turfs_to_uncontain_by_zlevel[reserving_turf.z] += reserving_turf reserving_turf.turf_flags = UNUSED_RESERVATION_TURF - // reservation turfs are not allowed to interact with atmos at all - reserving_turf.blocks_air = TRUE world_contents += reserving_turf - // LISTASSERTLEN(world_turf_contents_by_z, reserving_turf.z, list()) - // world_turf_contents_by_z[reserving_turf.z] += reserving_turf packet.len-- packetlen = length(packet) diff --git a/code/modules/space_management/turf_reservation.dm b/code/modules/space_management/turf_reservation.dm index d96547fcffe1..febe4b34bba4 100644 --- a/code/modules/space_management/turf_reservation.dm +++ b/code/modules/space_management/turf_reservation.dm @@ -1,5 +1,17 @@ -//Yes, they can only be rectangular. -//Yes, I'm sorry. +/* + * 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() @@ -25,12 +37,6 @@ /// The turf type the reservation is initially made with var/turf_type = /turf/space - /// Do we override baseturfs with turf_type? - // var/turf_type_is_baseturf = TRUE - - // ///Distance away from the cordon where we can put a "sort-cordon" and run some extra code (see make_repel). 0 makes nothing happen - // var/pre_cordon_distance = 0 - /datum/turf_reservation/New() LAZYADD(SSmapping.turf_reservations, src) @@ -53,15 +59,7 @@ var/release_turfs = reserved_copy + cordon_copy - for(var/turf/reserved_turf as anything in release_turfs) - - // immediately disconnect from atmos - reserved_turf.blocks_air = TRUE - // CALCULATE_ADJACENT_TURFS(reserved_turf, KILL_EXCITED) - - // Makes the linter happy, even tho we don't await this SSmapping.unreserve_turfs(release_turfs) - // INVOKE_ASYNC(SSmapping, TYPE_PROC_REF(/datum/controller/subsystem/mapping, 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) @@ -78,24 +76,13 @@ return FALSE cordon_turfs |= possible_turfs - // if(pre_cordon_distance) - // var/turf/offset_turf = locate(bottom_left.x + pre_cordon_distance, bottom_left.y + pre_cordon_distance, bottom_left.z) - // var/list/to_add = CORNER_OUTLINE(offset_turf, width - pre_cordon_distance * 2, height - pre_cordon_distance * 2) //we step-by-stop move inwards from the outer cordon - // for(var/turf/turf_being_added as anything in to_add) - // pre_cordon_turfs |= turf_being_added //add one by one so we can filter out duplicates - 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 - // var/area/old_area = cordon_turf.loc - // LISTASSERTLEN(old_area.turfs_to_uncontain_by_zlevel, cordon_turf.z, list()) - // LISTASSERTLEN(cordon_area.turfs_by_zlevel, cordon_turf.z, list()) - // old_area.turfs_to_uncontain_by_zlevel[cordon_turf.z] += cordon_turf - // cordon_area.turfs_by_zlevel[cordon_turf.z] += cordon_turf cordon_area.contents += cordon_turf // Its no longer unused, but its also not "used" @@ -105,10 +92,6 @@ // still gets linked to us though SSmapping.used_turfs[cordon_turf] = src - // //swap the area with the pre-cordoning area - // for(var/turf/pre_cordon_turf as anything in pre_cordon_turfs) - // make_repel(pre_cordon_turf) - /// Internal proc which handles reserving the area for the reservation. /datum/turf_reservation/proc/_reserve_area(width, height, zlevel) src.width = width @@ -168,6 +151,3 @@ generate_cordon() return TRUE -// /datum/turf_reservation/proc/post_load() -// for(var/turf/T as anything in reserved_turfs) -// T.blocks_air = initial(T.blocks_air) // Experimental atmos on this z-level