Builds logic that manages turfs contained inside an area (#70966)

## About The Pull Request

Area contents isn't a real list, instead it involves filtering
everything in world
This is slow, and something we should have better support for.

So instead, lets manage a list of turfs inside our area. This is simple,
since we already move turfs by area contents anyway

This should speed up the uses I've found, and opens us up to using this
pattern more often, which should make dev work easier.

By nature this is a tad fragile, so I've added a unit test to double
check my work

Rather then instantly removing turfs from the contained_turfs list, we
enter them into a list of turfs to pull out, later.
Then we just use a getter for contained_turfs rather then a var read

This means we don't need to generate a lot of usage off removing turf by
turf from space, and can instead do it only when we need to

I've added a subsystem to manage this process as well, to ensure we
don't get any out of memory errors. It goes entry by entry, ensuring we
get no overtime.
This allows me to keep things like space clean, while keeping high
amounts of usage on a sepearate subsystem when convienient

As a part of this goal of keeping space's churn as low as possible, I've
setup code to ensure we do not add turfs to areas during a z level
increment adjacent mapload. this saves a LOT of time, but is a tad
messy

I've expanded where we use contained_turfs, including into some cases
that filter for objects in areas. need to see if this is sane or not.

Builds sortedAreas on demand, caching until we mark the cache as
violated

It's faster, and it also has the same behavior

I'm not posting speed changes cause frankly they're gonna be a bit
scattered and I'm scared to.
@Mothblocks if you'd like I can look into it. I think it'll pay for
itself just off `reg_in_areas_in_z` (I looked into it. it's really hard
to tell, sometimes it's a bit slower (0.7), sometimes it's 2 seconds
(0.5 if you use the old master figure) faster. life is pain.)

## Why It's Good For The Game

Less stupid, more flexible, more speed

Co-authored-by: san7890 <the@san7890.com>
This commit is contained in:
LemonInTheDark
2022-11-04 20:13:54 -07:00
committed by GitHub
co-authored by san7890
parent 1ed6876e37
commit 5b4ba051a0
40 changed files with 285 additions and 128 deletions
+27 -18
View File
@@ -50,10 +50,10 @@
/// - `no_changeturf`: When true, [/turf/proc/AfterChange] won't be called on loaded turfs
/// - `x_lower`, `x_upper`, `y_lower`, `y_upper`: Coordinates (relative to the map) to crop to (Optional).
/// - `placeOnTop`: Whether to use [/turf/proc/PlaceOnTop] rather than [/turf/proc/ChangeTurf] (Optional).
/proc/load_map(dmm_file as file, x_offset as num, y_offset as num, z_offset as num, cropMap as num, measureOnly as num, no_changeturf as num, x_lower = -INFINITY as num, x_upper = INFINITY as num, y_lower = -INFINITY as num, y_upper = INFINITY as num, placeOnTop = FALSE as num)
/proc/load_map(dmm_file as file, x_offset as num, y_offset as num, z_offset as num, cropMap as num, measureOnly as num, no_changeturf as num, x_lower = -INFINITY as num, x_upper = INFINITY as num, y_lower = -INFINITY as num, y_upper = INFINITY as num, placeOnTop = FALSE as num, new_z)
var/datum/parsed_map/parsed = new(dmm_file, x_lower, x_upper, y_lower, y_upper, measureOnly)
if(parsed.bounds && !measureOnly)
parsed.load(x_offset, y_offset, z_offset, cropMap, no_changeturf, x_lower, x_upper, y_lower, y_upper, placeOnTop)
parsed.load(x_offset, y_offset, z_offset, cropMap, no_changeturf, x_lower, x_upper, y_lower, y_upper, placeOnTop, new_z = new_z)
return parsed
/// Parse a map, possibly cropping it.
@@ -157,10 +157,10 @@
src.key_len = key_len
/// Load the parsed map into the world. See [/proc/load_map] for arguments.
/datum/parsed_map/proc/load(x_offset, y_offset, z_offset, cropMap, no_changeturf, x_lower, x_upper, y_lower, y_upper, placeOnTop, whitelist = FALSE)
/datum/parsed_map/proc/load(x_offset, y_offset, z_offset, cropMap, no_changeturf, x_lower, x_upper, y_lower, y_upper, placeOnTop, whitelist = FALSE, new_z)
//How I wish for RAII
Master.StartLoadingMap()
. = _load_impl(x_offset, y_offset, z_offset, cropMap, no_changeturf, x_lower, x_upper, y_lower, y_upper, placeOnTop)
. = _load_impl(x_offset, y_offset, z_offset, cropMap, no_changeturf, x_lower, x_upper, y_lower, y_upper, placeOnTop, new_z)
Master.StopLoadingMap()
@@ -171,7 +171,7 @@
SSatoms.map_loader_begin(); \
}
// Do not call except via load() above.
/datum/parsed_map/proc/_load_impl(x_offset = 1, y_offset = 1, z_offset = world.maxz + 1, cropMap = FALSE, no_changeturf = FALSE, x_lower = -INFINITY, x_upper = INFINITY, y_lower = -INFINITY, y_upper = INFINITY, placeOnTop = FALSE)
/datum/parsed_map/proc/_load_impl(x_offset = 1, y_offset = 1, z_offset = world.maxz + 1, cropMap = FALSE, no_changeturf = FALSE, x_lower = -INFINITY, x_upper = INFINITY, y_lower = -INFINITY, y_upper = INFINITY, placeOnTop = FALSE, new_z = FALSE)
PRIVATE_PROC(TRUE)
var/list/modelCache = build_cache(no_changeturf)
var/space_key = modelCache[SPACE_KEY]
@@ -288,7 +288,7 @@
if(!cache)
SSatoms.map_loader_stop()
CRASH("Undefined model key in DMM: [line]")
build_coordinate(cache, locate(true_xcrd, ycrd, zcrd), no_afterchange, placeOnTop)
build_coordinate(cache, locate(true_xcrd, ycrd, zcrd), no_afterchange, placeOnTop, new_z)
// only bother with bounds that actually exist
if(!first_found)
@@ -320,7 +320,7 @@
if(!cache)
SSatoms.map_loader_stop()
CRASH("Undefined model key in DMM: [model_key]")
build_coordinate(cache, locate(xcrd, ycrd, zcrd), no_afterchange, placeOnTop)
build_coordinate(cache, locate(xcrd, ycrd, zcrd), no_afterchange, placeOnTop, new_z)
// only bother with bounds that actually exist
if(!first_found)
@@ -347,6 +347,10 @@
//we do this after we load everything in. if we don't; we'll have weird atmos bugs regarding atmos adjacent turfs
T.AfterChange(CHANGETURF_IGNORE_AIR)
if(new_z)
for(var/z_index in bounds[MAP_MINZ] to bounds[MAP_MAXZ])
SSmapping.build_area_turfs(z_index)
if(has_expanded_world_maxx || has_expanded_world_maxy)
SEND_GLOBAL_SIGNAL(COMSIG_GLOB_EXPANDED_WORLD_BOUNDS, has_expanded_world_maxx, has_expanded_world_maxy)
@@ -437,7 +441,7 @@ GLOBAL_LIST_EMPTY(map_model_default)
.[model_key] = list(members, members_attributes)
/datum/parsed_map/proc/build_coordinate(list/model, turf/crds, no_changeturf as num, placeOnTop as num)
/datum/parsed_map/proc/build_coordinate(list/model, turf/crds, no_changeturf as num, placeOnTop as num, new_z)
// If we don't have a turf, nothing we will do next will actually acomplish anything, so just go back
// Note, this would actually drop area vvs in the tile, but like, why tho
if(!crds)
@@ -458,29 +462,34 @@ GLOBAL_LIST_EMPTY(map_model_default)
//The next part of the code assumes there's ALWAYS an /area AND a /turf on a given tile
//first instance the /area and remove it from the members list
index = members.len
var/atom/instance
if(members[index] != /area/template_noop)
var/area/area_instance
if(members_attributes[index] != default_list)
world.preloader_setup(members_attributes[index], members[index])//preloader for assigning set variables on atom creation
instance = loaded_areas[members[index]]
if(!instance)
area_instance = loaded_areas[members[index]]
if(!area_instance)
var/area_type = members[index]
// If this parsed map doesn't have that area already, we check the global cache
instance = GLOB.areas_by_type[area_type]
area_instance = GLOB.areas_by_type[area_type]
// If the global list DOESN'T have this area it's either not a unique area, or it just hasn't been created yet
if (!instance)
instance = new area_type(null)
if(!instance)
if (!area_instance)
area_instance = new area_type(null)
if(!area_instance)
CRASH("[area_type] failed to be new'd, what'd you do?")
loaded_areas[area_type] = instance
loaded_areas[area_type] = area_instance
instance.contents.Add(crds)
if(!new_z)
var/area/old_area = crds.loc
old_area.turfs_to_uncontain += crds
area_instance.contained_turfs.Add(crds)
area_instance.contents.Add(crds)
if(GLOB.use_preloader)
world.preloader_load(instance)
world.preloader_load(area_instance)
// Index right before /area is /turf
index--
var/atom/instance
//then instance the /turf
//NOTE: this used to place any turfs before the last "underneath" it using .appearance and underlays
//We don't actually use this, and all it did was cost cpu, so we don't do this anymore