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
@@ -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)