[MIRROR] Ventcrawling improvements, performance and visual [MDB IGNORE] (#13461)

* Ventcrawling improvements, performance and visual (#66709)

* Initial pipecrawl work

Ok so pipecrawl images were updating EVERY TIME YOU MOVED
This was not good mojo

What I've done here is twofold
First, I ensured pipecrawl updates only when the net changes. This
breaks the current implementation, but I intend on fixing that

Second, I moved our method of getting pipes to the spatial grid
This isn't that great at the moment, but I intend on adding support for
tracking entered/exited cells, which should make this much better

* Much faster pipecrawling processing, niceties

Adds a concept called a cell tracker datum.
It manages a list of cells a passed in bound is "inside", and when
queried will return a list of new cells, and old cells.

Because we only really care about maintaining an absolute window of
"CELLS WE ARE IN" and less about always removing cells we're not in, we
can manage a second window to prevent moving up and down on a cell line
causing a ton of updates.

Uses this concept to optimize pipecrawling significantly, from 3ms per
call before to roughly 0.03ms per call.

Also moves pipecrawl images to their own plane, so they don't overlap ui
elements

* Pipecrawling effects niceties, direction help

You can now move in more then one direction when pipecrawling
This works as expected, if you hold up and left, move up for a while,
and come to a fork, you'll go left

Added some effects to pipecrawling. It'll darken the lighting plane
slightly, so you get a nice effect instead of just fullbright.
Also added a color matrix and drop shadow to the pipe images, this way
they stand out a bit more.

You now glide between pipe moves, rather then moving instantly. This
doesn't effect your actual move rate, but it no longer feels jittery
with say, 60fps

* Bounds

* Fixes runtimes, cache something somethign sonic speed

* Reworks how being interested in the spatial grid is tracked

Rather then checking multiple variables on the atom to consider, we
instead check for the existence of a string key.

This key is used by a list on the spatial grid subsystem to retrive a
cached list of all of the atoms "types"

Doing this requires doing a bit of extra work in
important_recursive_contents code, but it allows us to separate being a
part of the spatial grid from using important recursive contents, which
is very nice.

As a consequence, I've had to unroll some lazylist macros in important
recursive contents logic. It's not ""that"" bad but it's not great
either.

Oh and this adds a slight cost to enter/exit cell, but it's minimal.
Basically, rather then checking for different features of a grid member,
we just iterate the list their string key points to. Very handy

So there's an added cost of a list copy and all, but we save the
headache of more types technically increasing the cost of
addition/removal.

I also made adding/removing from the grid into one "pulbic" proc rather then two
different ones for each operation, because it was starting to get silly

* waaa waa it doesn't compile

* chord -> coord

* Ensures important_recursive_contents is actually emptied on removal

* Removes soul

* Kyler's review

Co-authored-by: Kylerace <kylerlumpkin1@ gmail.com>

* Kyler's review 2

Co-authored-by: Kylerace <kylerlumpkin1@ gmail.com>

* Kyler's review 3

Moves some procs around, improves some documentation, catches a few
small issues

Co-authored-by: Kylerace <kylerlumpkin1@ gmail.com>

* Ventcrawling improvements, performance and visual

Co-authored-by: LemonInTheDark <58055496+LemonInTheDark@users.noreply.github.com>
Co-authored-by: Kylerace <kylerlumpkin1@ gmail.com>
This commit is contained in:
SkyratBot
2022-05-09 15:06:38 +01:00
committed by GitHub
co-authored by Kylerace LemonInTheDark
parent a5fbfaccbe
commit edfaf90ebf
13 changed files with 535 additions and 148 deletions
+226 -58
View File
@@ -26,6 +26,8 @@
var/list/hearing_contents
///every client possessed mob inside this cell
var/list/client_contents
///every atmos machine inside this cell
var/list/atmos_contents
/datum/spatial_grid_cell/New(cell_x, cell_y, cell_z)
. = ..()
@@ -40,6 +42,7 @@
stack_trace("SSspatial_grid.dummy_list had something inserted into it at some point! this is a problem as it is supposed to stay empty")
hearing_contents = dummy_list
client_contents = dummy_list
atmos_contents = dummy_list
/datum/spatial_grid_cell/Destroy(force, ...)
if(force)//the response to someone trying to qdel this is a right proper fuck you
@@ -63,8 +66,16 @@
* currently this system is only designed for searching for relatively uncommon things, small subsets of /atom/movable.
* dont add stupid shit to the cells please, keep the information that the cells store to things that need to be searched for often
*
* as of right now this system operates on a subset of the important_recursive_contents list for atom/movable, specifically
* [RECURSIVE_CONTENTS_HEARING_SENSITIVE] and [RECURSIVE_CONTENTS_CLIENT_MOBS] because both are those are both 1. important and 2. commonly searched for
* The system currently implements two different "classes" of spatial type
*
* The first exists to support important_recursive_contents.
* So if a client is inside a locker and the locker crosses a boundary, you'll still get a signal from the spatial grid.
* These types are [SPATIAL_GRID_CONTENTS_TYPE_HEARING] and [SPATIAL_GRID_CONTENTS_TYPE_CLIENTS]
*
* The second pattern is more paired down, and supports more wide use.
* Rather then the object and anything the object is in being sensitive, it's limited to just the object itself
* Currently only [SPATIAL_GRID_CONTENTS_TYPE_ATMOS] uses this pattern. This is because it's far more common, and so worth optimizing
*
*/
SUBSYSTEM_DEF(spatial_grid)
can_fire = FALSE
@@ -74,7 +85,11 @@ SUBSYSTEM_DEF(spatial_grid)
///list of the spatial_grid_cell datums per z level, arranged in the order of y index then x index
var/list/grids_by_z_level = list()
///everything that spawns before us is added to this list until we initialize
var/list/waiting_to_add_by_type = list(RECURSIVE_CONTENTS_HEARING_SENSITIVE = list(), RECURSIVE_CONTENTS_CLIENT_MOBS = list())
var/list/waiting_to_add_by_type = list(SPATIAL_GRID_CONTENTS_TYPE_HEARING = list(), SPATIAL_GRID_CONTENTS_TYPE_CLIENTS = list(), SPATIAL_GRID_CONTENTS_TYPE_ATMOS = list())
///associative list of the form: movable.spatial_grid_key (string) -> inner list of spatial grid types for that key.
///inner lists contain contents channel types such as SPATIAL_GRID_CONTENTS_TYPE_HEARING etc.
///we use this to make adding to a cell static cost, and to save on memory
var/list/spatial_grid_categories = list()
var/cells_on_x_axis = 0
var/cells_on_y_axis = 0
@@ -237,10 +252,10 @@ SUBSYSTEM_DEF(spatial_grid)
old_cell_that_needs_updating.cell_y = cell_row_for_expanded_y_axis
///the left or bottom side index of a box composed of spatial grid cells with the given actual center x or y coordinate
#define BOUNDING_BOX_MIN(center_coord) max(ROUND_UP((center_coord - range) / SPATIAL_GRID_CELLSIZE), 1)
#define BOUNDING_BOX_MIN(center_coord) max(GET_SPATIAL_INDEX(center_coord - range), 1)
///the right or upper side index of a box composed of spatial grid cells with the given center x or y coordinate.
///outputted value cant exceed the number of cells on that axis
#define BOUNDING_BOX_MAX(center_coord, axis_size) min(ROUND_UP((center_coord + range) / SPATIAL_GRID_CELLSIZE), axis_size)
#define BOUNDING_BOX_MAX(center_coord, axis_size) min(GET_SPATIAL_INDEX(center_coord + range), axis_size)
/**
* https://en.wikipedia.org/wiki/Range_searching#Orthogonal_range_searching
@@ -283,6 +298,11 @@ SUBSYSTEM_DEF(spatial_grid)
. += grid_level[row][x_index].hearing_contents
if(SPATIAL_GRID_CONTENTS_TYPE_ATMOS)
for(var/row in BOUNDING_BOX_MIN(center_y) to BOUNDING_BOX_MAX(center_y, cells_on_y_axis))
for(var/x_index in BOUNDING_BOX_MIN(center_x) to BOUNDING_BOX_MAX(center_x, cells_on_x_axis))
. += grid_level[row][x_index].atmos_contents
return .
///get the grid cell encomapassing targets coordinates
@@ -291,10 +311,14 @@ SUBSYSTEM_DEF(spatial_grid)
if(!target_turf)
return
return grids_by_z_level[target_turf.z][ROUND_UP(target_turf.y / SPATIAL_GRID_CELLSIZE)][ROUND_UP(target_turf.x / SPATIAL_GRID_CELLSIZE)]
return grids_by_z_level[target_turf.z][GET_SPATIAL_INDEX(target_turf.y)][GET_SPATIAL_INDEX(target_turf.x)]
///get all grid cells intersecting the bounding box around center with sides of length 2 * range
/datum/controller/subsystem/spatial_grid/proc/get_cells_in_range(atom/center, range)
return get_cells_in_bounds(center, range, range)
///get all grid cells intersecting the bounding box around center with sides of length (2 * range_x, 2 * range_y)
/datum/controller/subsystem/spatial_grid/proc/get_cells_in_bounds(atom/center, range_x, range_y)
var/turf/center_turf = get_turf(center)
var/center_x = center_turf.x
@@ -303,12 +327,12 @@ SUBSYSTEM_DEF(spatial_grid)
var/list/intersecting_grid_cells = list()
//the minimum x and y cell indexes to test
var/min_x = max(ROUND_UP((center_x - range) / SPATIAL_GRID_CELLSIZE), 1)
var/min_y = max(ROUND_UP((center_y - range) / SPATIAL_GRID_CELLSIZE), 1)//calculating these indices only takes around 2 microseconds
var/min_x = max(GET_SPATIAL_INDEX(center_x - range_x), 1)
var/min_y = max(GET_SPATIAL_INDEX(center_y - range_y), 1)//calculating these indices only takes around 2 microseconds
//the maximum x and y cell indexes to test
var/max_x = min(ROUND_UP((center_x + range) / SPATIAL_GRID_CELLSIZE), cells_on_x_axis)
var/max_y = min(ROUND_UP((center_y + range) / SPATIAL_GRID_CELLSIZE), cells_on_y_axis)
var/max_x = min(GET_SPATIAL_INDEX(center_x + range_x), cells_on_x_axis)
var/max_y = min(GET_SPATIAL_INDEX(center_y + range_y), cells_on_y_axis)
var/list/grid_level = grids_by_z_level[center_turf.z]
@@ -320,7 +344,59 @@ SUBSYSTEM_DEF(spatial_grid)
return intersecting_grid_cells
///find the spatial map cell that target belongs to, then add target's important_recusive_contents to it.
/// Adds grid awareness to the passed in atom, of the passed in type
/// Basically, when this atom moves between grids, it wants to have enter/exit cell called on it
/datum/controller/subsystem/spatial_grid/proc/add_grid_awareness(atom/movable/add_to, type)
// We need to ensure we have a new list reference, to build our new key out of
var/list/current_list = spatial_grid_categories[add_to.spatial_grid_key]
if(current_list)
current_list = current_list.Copy()
else
current_list = list()
// Now we do a binary insert, to ensure it's sorted (don't wanna overcache)
BINARY_INSERT_DEFINE(type, current_list, SORT_VAR_NO_TYPE, type, SORT_COMPARE_DIRECTLY, COMPARE_KEY)
update_grid_awareness(add_to, current_list)
/// Removes grid awareness from the passed in atom, of the passed in type
/datum/controller/subsystem/spatial_grid/proc/remove_grid_awareness(atom/movable/remove_from, type)
// We need to ensure we have a new list reference, to build our new key out of
var/list/current_list = spatial_grid_categories[remove_from.spatial_grid_key]
if(current_list)
current_list = current_list.Copy()
else
current_list = list()
current_list -= type
update_grid_awareness(remove_from, current_list)
/// Alerts the atom's current cell that it wishes to be treated as a member
/// This functionally amounts to "hey, I was recently made aware by [add_grid_awareness], please insert me into my current cell"
/datum/controller/subsystem/spatial_grid/proc/add_grid_membership(atom/movable/add_to, turf/target_turf, type)
if(!target_turf)
return
if(initialized)
add_single_type(add_to, target_turf, type)
else //SSspatial_grid isnt init'd yet, add ourselves to the queue
enter_pre_init_queue(add_to, type)
/// Removes grid membership from the passed in atom, of the passed in type
/datum/controller/subsystem/spatial_grid/proc/remove_grid_membership(atom/movable/remove_from, turf/target_turf, type)
if(!target_turf)
return
if(initialized)
remove_single_type(remove_from, target_turf, type)
else //SSspatial_grid isnt init'd yet, remove ourselves from the queue
remove_from_pre_init_queue(remove_from, type)
/// Updates the string that atoms hold that stores their grid awareness
/// We will use it to key into their spatial grid categories later
/datum/controller/subsystem/spatial_grid/proc/update_grid_awareness(atom/movable/update, list/new_list)
// We locally store a stringified version of the list, to prevent people trying to mutate it
update.spatial_grid_key = new_list.Join("-")
// Ensure the global representation is cached
if(!spatial_grid_categories[update.spatial_grid_key])
spatial_grid_categories[update.spatial_grid_key] = new_list
///find the spatial map cell that target belongs to, then add the target to it, as its type prefers.
///make sure to provide the turf new_target is "in"
/datum/controller/subsystem/spatial_grid/proc/enter_cell(atom/movable/new_target, turf/target_turf)
if(!initialized)
@@ -328,27 +404,62 @@ SUBSYSTEM_DEF(spatial_grid)
if(QDELETED(new_target))
CRASH("qdeleted or null target trying to enter the spatial grid!")
if(!target_turf || !new_target.important_recursive_contents)
CRASH("null turf loc or a new_target without important_recursive_contents trying to enter the spatial grid!")
if(!target_turf || !new_target.spatial_grid_key)
CRASH("null turf loc or a new_target that doesn't support it trying to enter the spatial grid!")
var/x_index = ROUND_UP(target_turf.x / SPATIAL_GRID_CELLSIZE)
var/y_index = ROUND_UP(target_turf.y / SPATIAL_GRID_CELLSIZE)
var/x_index = GET_SPATIAL_INDEX(target_turf.x)
var/y_index = GET_SPATIAL_INDEX(target_turf.y)
var/z_index = target_turf.z
var/datum/spatial_grid_cell/intersecting_cell = grids_by_z_level[z_index][y_index][x_index]
for(var/type in spatial_grid_categories[new_target.spatial_grid_key])
switch(type)
if(SPATIAL_GRID_CONTENTS_TYPE_CLIENTS)
var/list/new_target_contents = new_target.important_recursive_contents //cache for sanic speeds (lists are references anyways)
GRID_CELL_SET(intersecting_cell.client_contents, new_target_contents[SPATIAL_GRID_CONTENTS_TYPE_CLIENTS])
SEND_SIGNAL(intersecting_cell, SPATIAL_GRID_CELL_ENTERED(SPATIAL_GRID_CONTENTS_TYPE_CLIENTS), new_target_contents[SPATIAL_GRID_CONTENTS_TYPE_CLIENTS])
if(new_target.important_recursive_contents[SPATIAL_GRID_CONTENTS_TYPE_CLIENTS])
GRID_CELL_SET(intersecting_cell.client_contents, new_target.important_recursive_contents[SPATIAL_GRID_CONTENTS_TYPE_CLIENTS])
if(SPATIAL_GRID_CONTENTS_TYPE_HEARING)
var/list/new_target_contents = new_target.important_recursive_contents
GRID_CELL_SET(intersecting_cell.hearing_contents, new_target.important_recursive_contents[SPATIAL_GRID_CONTENTS_TYPE_HEARING])
SEND_SIGNAL(intersecting_cell, SPATIAL_GRID_CELL_ENTERED(SPATIAL_GRID_CONTENTS_TYPE_HEARING), new_target_contents[SPATIAL_GRID_CONTENTS_TYPE_HEARING])
SEND_SIGNAL(intersecting_cell, SPATIAL_GRID_CELL_ENTERED(SPATIAL_GRID_CONTENTS_TYPE_CLIENTS), new_target.important_recursive_contents[SPATIAL_GRID_CONTENTS_TYPE_CLIENTS])
if(SPATIAL_GRID_CONTENTS_TYPE_ATMOS)
GRID_CELL_SET(intersecting_cell.atmos_contents, new_target)
SEND_SIGNAL(intersecting_cell, SPATIAL_GRID_CELL_ENTERED(SPATIAL_GRID_CONTENTS_TYPE_ATMOS), new_target)
if(new_target.important_recursive_contents[SPATIAL_GRID_CONTENTS_TYPE_HEARING])
GRID_CELL_SET(intersecting_cell.hearing_contents, new_target.important_recursive_contents[SPATIAL_GRID_CONTENTS_TYPE_HEARING])
///acts like enter_cell() but only adds the target to a specified type of grid cell contents list
/datum/controller/subsystem/spatial_grid/proc/add_single_type(atom/movable/new_target, turf/target_turf, exclusive_type)
if(!initialized)
return
if(QDELETED(new_target))
CRASH("qdeleted or null target trying to enter the spatial grid!")
SEND_SIGNAL(intersecting_cell, SPATIAL_GRID_CELL_ENTERED(SPATIAL_GRID_CONTENTS_TYPE_HEARING), new_target.important_recursive_contents[SPATIAL_GRID_CONTENTS_TYPE_HEARING])
if(!target_turf || !(exclusive_type in spatial_grid_categories[new_target.spatial_grid_key]))
CRASH("null turf loc or a new_target that doesn't support it trying to enter the spatial grid as a [exclusive_type]!")
var/x_index = GET_SPATIAL_INDEX(target_turf.x)
var/y_index = GET_SPATIAL_INDEX(target_turf.y)
var/z_index = target_turf.z
var/datum/spatial_grid_cell/intersecting_cell = grids_by_z_level[z_index][y_index][x_index]
switch(exclusive_type)
if(SPATIAL_GRID_CONTENTS_TYPE_CLIENTS)
var/list/new_target_contents = new_target.important_recursive_contents //cache for sanic speeds (lists are references anyways)
GRID_CELL_SET(intersecting_cell.client_contents, new_target_contents[SPATIAL_GRID_CONTENTS_TYPE_CLIENTS])
SEND_SIGNAL(intersecting_cell, SPATIAL_GRID_CELL_ENTERED(SPATIAL_GRID_CONTENTS_TYPE_CLIENTS), new_target_contents[SPATIAL_GRID_CONTENTS_TYPE_CLIENTS])
if(SPATIAL_GRID_CONTENTS_TYPE_HEARING)
var/list/new_target_contents = new_target.important_recursive_contents
GRID_CELL_SET(intersecting_cell.hearing_contents, new_target.important_recursive_contents[SPATIAL_GRID_CONTENTS_TYPE_HEARING])
SEND_SIGNAL(intersecting_cell, SPATIAL_GRID_CELL_ENTERED(SPATIAL_GRID_CONTENTS_TYPE_HEARING), new_target_contents[SPATIAL_GRID_CONTENTS_TYPE_HEARING])
if(SPATIAL_GRID_CONTENTS_TYPE_ATMOS)
GRID_CELL_SET(intersecting_cell.atmos_contents, new_target)
SEND_SIGNAL(intersecting_cell, SPATIAL_GRID_CELL_ENTERED(SPATIAL_GRID_CONTENTS_TYPE_ATMOS), new_target)
/**
* find the spatial map cell that target used to belong to, then subtract target's important_recusive_contents from it.
* find the spatial map cell that target used to belong to, then remove the target (and sometimes it's important_recusive_contents) from it.
* make sure to provide the turf old_target used to be "in"
*
* * old_target - the thing we want to remove from the spatial grid cell
@@ -358,56 +469,66 @@ SUBSYSTEM_DEF(spatial_grid)
/datum/controller/subsystem/spatial_grid/proc/exit_cell(atom/movable/old_target, turf/target_turf, exclusive_type)
if(!initialized)
return
if(!target_turf || !old_target?.important_recursive_contents)
stack_trace("/datum/controller/subsystem/spatial_grid/proc/exit_cell() was given null arguments or a new_target without important_recursive_contents!")
if(!target_turf || !old_target.spatial_grid_key)
stack_trace("/datum/controller/subsystem/spatial_grid/proc/exit_cell() was given null arguments or a old_target that doesn't use the spatial grid!")
return FALSE
var/x_index = ROUND_UP(target_turf.x / SPATIAL_GRID_CELLSIZE)
var/y_index = ROUND_UP(target_turf.y / SPATIAL_GRID_CELLSIZE)
var/x_index = GET_SPATIAL_INDEX(target_turf.x)
var/y_index = GET_SPATIAL_INDEX(target_turf.y)
var/z_index = target_turf.z
var/datum/spatial_grid_cell/intersecting_cell = grids_by_z_level[z_index][y_index][x_index]
var/list/old_target_contents = old_target.important_recursive_contents //cache for sanic speeds (lists are references anyways)
for(var/type in spatial_grid_categories[old_target.spatial_grid_key])
switch(type)
if(SPATIAL_GRID_CONTENTS_TYPE_CLIENTS)
var/list/old_target_contents = old_target.important_recursive_contents //cache for sanic speeds (lists are references anyways)
GRID_CELL_REMOVE(intersecting_cell.client_contents, old_target_contents[SPATIAL_GRID_CONTENTS_TYPE_CLIENTS])
SEND_SIGNAL(intersecting_cell, SPATIAL_GRID_CELL_EXITED(SPATIAL_GRID_CONTENTS_TYPE_CLIENTS), old_target_contents[SPATIAL_GRID_CONTENTS_TYPE_CLIENTS])
if(old_target_contents[SPATIAL_GRID_CONTENTS_TYPE_CLIENTS])
GRID_CELL_REMOVE(intersecting_cell.client_contents, old_target_contents[SPATIAL_GRID_CONTENTS_TYPE_CLIENTS])
if(SPATIAL_GRID_CONTENTS_TYPE_HEARING)
var/list/old_target_contents = old_target.important_recursive_contents //cache for sanic speeds (lists are references anyways)
GRID_CELL_REMOVE(intersecting_cell.hearing_contents, old_target_contents[SPATIAL_GRID_CONTENTS_TYPE_HEARING])
SEND_SIGNAL(intersecting_cell, SPATIAL_GRID_CELL_EXITED(SPATIAL_GRID_CONTENTS_TYPE_HEARING), old_target_contents[SPATIAL_GRID_CONTENTS_TYPE_HEARING])
SEND_SIGNAL(intersecting_cell, SPATIAL_GRID_CELL_EXITED(SPATIAL_GRID_CONTENTS_TYPE_CLIENTS), old_target_contents[SPATIAL_GRID_CONTENTS_TYPE_CLIENTS])
if(old_target_contents[SPATIAL_GRID_CONTENTS_TYPE_HEARING])
GRID_CELL_REMOVE(intersecting_cell.hearing_contents, old_target_contents[SPATIAL_GRID_CONTENTS_TYPE_HEARING])
SEND_SIGNAL(intersecting_cell, SPATIAL_GRID_CELL_EXITED(SPATIAL_GRID_CONTENTS_TYPE_HEARING), old_target_contents[SPATIAL_GRID_CONTENTS_TYPE_HEARING])
if(SPATIAL_GRID_CONTENTS_TYPE_ATMOS)
GRID_CELL_REMOVE(intersecting_cell.atmos_contents, old_target)
SEND_SIGNAL(intersecting_cell, SPATIAL_GRID_CELL_EXITED(SPATIAL_GRID_CONTENTS_TYPE_ATMOS), old_target)
return TRUE
///acts like exit_cell() but only removes the target from the specified type of grid cell contents list
/datum/controller/subsystem/spatial_grid/proc/remove_single_contents_type(atom/movable/old_target, turf/target_turf, exclusive_type)
if(!target_turf || !old_target?.important_recursive_contents || !exclusive_type)
stack_trace("/datum/controller/subsystem/spatial_grid/proc/remove_single_contents_type() was given null arguments or a new_target without important_recursive_contents!")
/datum/controller/subsystem/spatial_grid/proc/remove_single_type(atom/movable/old_target, turf/target_turf, exclusive_type)
if(!target_turf || !exclusive_type || !old_target.spatial_grid_key)
stack_trace("/datum/controller/subsystem/spatial_grid/proc/remove_single_type() was given null arguments or an old_target that doesn't use the spatial grid!")
return FALSE
if(!old_target.important_recursive_contents[exclusive_type])
if(!(exclusive_type in spatial_grid_categories[old_target.spatial_grid_key]))
return FALSE
var/x_index = ROUND_UP(target_turf.x / SPATIAL_GRID_CELLSIZE)
var/y_index = ROUND_UP(target_turf.y / SPATIAL_GRID_CELLSIZE)
var/x_index = GET_SPATIAL_INDEX(target_turf.x)
var/y_index = GET_SPATIAL_INDEX(target_turf.y)
var/z_index = target_turf.z
var/datum/spatial_grid_cell/intersecting_cell = grids_by_z_level[z_index][y_index][x_index]
switch(exclusive_type)
if(SPATIAL_GRID_CONTENTS_TYPE_CLIENTS)
GRID_CELL_REMOVE(intersecting_cell.client_contents, old_target.important_recursive_contents[SPATIAL_GRID_CONTENTS_TYPE_CLIENTS])
var/list/old_target_contents = old_target.important_recursive_contents //cache for sanic speeds (lists are references anyways)
GRID_CELL_REMOVE(intersecting_cell.client_contents, old_target_contents[SPATIAL_GRID_CONTENTS_TYPE_CLIENTS])
SEND_SIGNAL(intersecting_cell, SPATIAL_GRID_CELL_EXITED(exclusive_type), old_target_contents[SPATIAL_GRID_CONTENTS_TYPE_CLIENTS])
if(SPATIAL_GRID_CONTENTS_TYPE_HEARING)
GRID_CELL_REMOVE(intersecting_cell.hearing_contents, old_target.important_recursive_contents[SPATIAL_GRID_CONTENTS_TYPE_HEARING])
var/list/old_target_contents = old_target.important_recursive_contents
GRID_CELL_REMOVE(intersecting_cell.hearing_contents, old_target_contents[SPATIAL_GRID_CONTENTS_TYPE_HEARING])
SEND_SIGNAL(intersecting_cell, SPATIAL_GRID_CELL_EXITED(exclusive_type), old_target_contents[SPATIAL_GRID_CONTENTS_TYPE_HEARING])
SEND_SIGNAL(intersecting_cell, SPATIAL_GRID_CELL_EXITED(exclusive_type), old_target)
if(SPATIAL_GRID_CONTENTS_TYPE_ATMOS)
GRID_CELL_REMOVE(intersecting_cell.atmos_contents, old_target)
SEND_SIGNAL(intersecting_cell, SPATIAL_GRID_CELL_EXITED(exclusive_type), old_target)
return TRUE
///find the cell this movable is associated with and removes it from all lists
/datum/controller/subsystem/spatial_grid/proc/force_remove_from_cell(atom/movable/to_remove, datum/spatial_grid_cell/input_cell)
if(!initialized)
@@ -422,6 +543,7 @@ SUBSYSTEM_DEF(spatial_grid)
GRID_CELL_REMOVE(input_cell.client_contents, to_remove)
GRID_CELL_REMOVE(input_cell.hearing_contents, to_remove)
GRID_CELL_REMOVE(input_cell.atmos_contents, to_remove)
///if shit goes south, this will find hanging references for qdeleting movables inside the spatial grid
/datum/controller/subsystem/spatial_grid/proc/find_hanging_cell_refs_for_movable(atom/movable/to_remove, remove_from_cells = TRUE)
@@ -441,7 +563,7 @@ SUBSYSTEM_DEF(spatial_grid)
for(var/list/z_level_grid as anything in grids_by_z_level)
for(var/list/cell_row as anything in z_level_grid)
for(var/datum/spatial_grid_cell/cell as anything in cell_row)
if(to_remove in (cell.hearing_contents | cell.client_contents))
if(to_remove in (cell.hearing_contents | cell.client_contents | cell.atmos_contents))
containing_cells += cell
if(remove_from_cells)
force_remove_from_cell(to_remove, cell)
@@ -466,30 +588,43 @@ SUBSYSTEM_DEF(spatial_grid)
/atom/proc/find_grid_statistics_for_z_level(insert_clients = 0)
var/raw_clients = 0
var/raw_hearables = 0
var/raw_atmos = 0
var/cells_with_clients = 0
var/cells_with_hearables = 0
var/cells_with_atmos = 0
var/list/client_list = list()
var/list/hearable_list = list()
var/list/atmos_list = list()
var/total_cells = (world.maxx / SPATIAL_GRID_CELLSIZE) ** 2
var/x_cell_count = world.maxx / SPATIAL_GRID_CELLSIZE
var/y_cell_count = world.maxy / SPATIAL_GRID_CELLSIZE
var/total_cells = x_cell_count ** 2
var/average_clients_per_cell = 0
var/average_hearables_per_cell = 0
var/average_atmos_mech_per_call = 0
var/hearable_min_x = (world.maxx / SPATIAL_GRID_CELLSIZE)
var/hearable_min_x = x_cell_count
var/hearable_max_x = 1
var/hearable_min_y = (world.maxy / SPATIAL_GRID_CELLSIZE)
var/hearable_min_y = y_cell_count
var/hearable_max_y = 1
var/client_min_x = (world.maxx / SPATIAL_GRID_CELLSIZE)
var/client_min_x = x_cell_count
var/client_max_x = 1
var/client_min_y = (world.maxy / SPATIAL_GRID_CELLSIZE)
var/client_min_y = y_cell_count
var/client_max_y = 1
var/atmos_min_x = x_cell_count
var/atmos_max_x = 1
var/atmos_min_y = y_cell_count
var/atmos_max_y = 1
var/list/inserted_clients = list()
if(insert_clients)
@@ -513,9 +648,11 @@ SUBSYSTEM_DEF(spatial_grid)
for(var/datum/spatial_grid_cell/cell as anything in all_z_level_cells)
var/client_length = length(cell.client_contents)
var/hearable_length = length(cell.hearing_contents)
var/atmos_length = length(cell.atmos_contents)
raw_clients += client_length
raw_hearables += hearable_length
raw_atmos += atmos_length
if(client_length)
cells_with_clients++
@@ -551,11 +688,30 @@ SUBSYSTEM_DEF(spatial_grid)
if(cell.cell_y > hearable_max_y)
hearable_max_y = cell.cell_y
if(raw_atmos)
cells_with_atmos++
atmos_list += cell.atmos_contents
if(cell.cell_x < atmos_min_x)
atmos_min_x = cell.cell_x
if(cell.cell_x > atmos_max_x)
atmos_max_x = cell.cell_x
if(cell.cell_y < atmos_min_y)
atmos_min_y = cell.cell_y
if(cell.cell_y > atmos_max_y)
atmos_max_y = cell.cell_y
var/total_client_distance = 0
var/total_hearable_distance = 0
var/total_atmos_distance = 0
var/average_client_distance = 0
var/average_hearable_distance = 0
var/average_atmos_distance = 0
for(var/hearable in hearable_list)//n^2 btw
for(var/other_hearable in hearable_list)
@@ -569,24 +725,36 @@ SUBSYSTEM_DEF(spatial_grid)
continue
total_client_distance += get_dist(client, other_client)
for(var/atmos in atmos_list)//n^2 btw
for(var/other_atmos in atmos_list)
if(atmos == other_atmos)
continue
total_atmos_distance += get_dist(atmos, other_atmos)
if(length(hearable_list))
average_hearable_distance = total_hearable_distance / length(hearable_list)
if(length(client_list))
average_client_distance = total_client_distance / length(client_list)
if(length(atmos_list))
average_atmos_distance = total_atmos_distance / length(atmos_list)
average_clients_per_cell = raw_clients / total_cells
average_hearables_per_cell = raw_hearables / total_cells
average_atmos_mech_per_call = raw_atmos / total_cells
for(var/mob/inserted_client as anything in inserted_clients)
qdel(inserted_client)
message_admins("on z level [z] there are [raw_clients] clients ([insert_clients] of whom are fakes inserted to random station turfs) \
and [raw_hearables] hearables. all of whom are inside the bounding box given by \
clients: ([client_min_x], [client_min_y]) x ([client_max_x], [client_max_y]) \
and hearables: ([hearable_min_x], [hearable_min_y]) x ([hearable_max_x], [hearable_max_y]) \
on average there are [average_clients_per_cell] clients per cell and [average_hearables_per_cell] hearables per cell. \
[cells_with_clients] cells have clients and [cells_with_hearables] have hearables, \
the average client distance is: [average_client_distance] and the average hearable_distance is [average_hearable_distance].")
message_admins("on z level [z] there are [raw_clients] clients ([insert_clients] of whom are fakes inserted to random station turfs)\
, [raw_hearables] hearables, and [raw_atmos] atmos machines. all of whom are inside the bounding box given by \
clients: ([client_min_x], [client_min_y]) x ([client_max_x], [client_max_y]), \
hearables: ([hearable_min_x], [hearable_min_y]) x ([hearable_max_x], [hearable_max_y]) \
and atmos machines: ([atmos_min_x], [atmos_min_y]) x ([atmos_max_x], [atmos_max_y]), \
on average there are [average_clients_per_cell] clients per cell, [average_hearables_per_cell] hearables per cell, \
and [average_atmos_mech_per_call] per cell, \
[cells_with_clients] cells have clients, [cells_with_hearables] have hearables, and [cells_with_atmos] have atmos machines \
the average client distance is: [average_client_distance], the average hearable_distance is [average_hearable_distance], \
and the average atmos distance is [average_atmos_distance] ")
#undef GRID_CELL_ADD
#undef GRID_CELL_REMOVE