Files
Paradise/code/modules/spatial_grid/cell_tracker.dm
PollardTheDragon 96495fab84 Port /tg/'s spatial grid, Round 2 (#30567)
* port /tg/'s spatial grid.

* fix lint

* fix identical variables

---------

Co-authored-by: warriorstar-orion <orion@snowfrost.garden>
2025-10-19 14:03:08 +00:00

100 lines
4.7 KiB
Plaintext

/**
* Spatial gridmap, cell tracking
*
* This datum exists to make the large, repeated "everything in some range" pattern faster
* Rather then just refreshing against everything, we track all the cells in range of the passed in "window"
* This lets us do entered/left logic, and make ordinarially quite expensive logic much cheaper
*
* Note: This system should not be used for things who have strict requirements about what is NOT in their processed entries
* It should instead only be used for logic that only really cares about limiting how much gets "entered" in any one call
* Because we apply this limitation, we can do things to make our code much less prone to unneeded work
*/
/datum/cell_tracker
var/list/datum/spatial_grid_cell/member_cells = list()
// Inner window
// If a cell is inside this space, it will be entered into our membership list
/// The height (y radius) of our inner window
var/inner_window_x_radius
/// The width (x radius) of our inner window
var/inner_window_y_radius
// Outer window
// If a cell is outside this space, it will be removed from our memebership list
// This effectively applies a grace window, to prevent moving back and forth across a border line causing issues
/// The height (y radius) of our outer window
var/outer_window_x_radius
/// The width (x radius) of our outer window
var/outer_window_y_radius
/// Accepts a width and height to use for this tracker
/// Also accepts the ratio to use between inner and outer window. Optional, defaults to 2
/datum/cell_tracker/New(width, height, inner_outer_ratio)
set_bounds(width, height, inner_outer_ratio)
return ..()
/datum/cell_tracker/Destroy(force)
stack_trace("Attempted to delete a cell tracker. They don't hold any refs outside of cells, what are you doing")
if(!force)
return QDEL_HINT_LETMELIVE
member_cells.Cut()
return ..()
/// Takes a width and height, and uses them to set the inner window, and interpolate the outer window
/datum/cell_tracker/proc/set_bounds(width = 0, height = 0, ratio = 2)
// We want to store these as radii, rather then width and height, since that's convineient for spatial grid code
var/x_radius = CEILING(width, 2)
var/y_radius = CEILING(height, 2)
inner_window_x_radius = x_radius
inner_window_y_radius = y_radius
outer_window_x_radius = x_radius * ratio
outer_window_y_radius = y_radius * ratio
/// Returns a list of newly and formerly joined spatial grid managed objects of type [type] in the form list(new, old)
/// Takes the center of our window as input
/datum/cell_tracker/proc/recalculate_type_members(turf/center, type)
var/list/new_and_old = recalculate_cells(center)
var/list/new_members = list()
var/list/former_members = list()
/// Pull out all the new and old memebers we want
switch(type)
if(SPATIAL_GRID_CONTENTS_TYPE_CLIENTS)
for(var/datum/spatial_grid_cell/cell as anything in new_and_old[1])
new_members += cell.client_contents
for(var/datum/spatial_grid_cell/cell as anything in new_and_old[2])
former_members += cell.client_contents
if(SPATIAL_GRID_CONTENTS_TYPE_HEARING)
for(var/datum/spatial_grid_cell/cell as anything in new_and_old[1])
new_members += cell.hearing_contents
for(var/datum/spatial_grid_cell/cell as anything in new_and_old[2])
former_members += cell.hearing_contents
if(SPATIAL_GRID_CONTENTS_TYPE_ATMOS)
for(var/datum/spatial_grid_cell/cell as anything in new_and_old[1])
new_members += cell.atmos_contents
for(var/datum/spatial_grid_cell/cell as anything in new_and_old[2])
former_members += cell.atmos_contents
return list(new_members, former_members)
/// Recalculates our member list, returns a list in the form list(new members, old members) for reaction
/// Accepts the turf to use as our "center"
/datum/cell_tracker/proc/recalculate_cells(turf/center)
if(!center)
CRASH("/datum/cell_tracker had an invalid location on refresh, ya done fucked")
// This is a mild waste of cpu time. Consider optimizing by adding a new helper function to get just the space between two bounds
// Assuming it ever becomes a real problem
var/list/datum/spatial_grid_cell/inner_window = SSspatial_grid.get_cells_in_bounds(center, inner_window_x_radius, inner_window_y_radius)
var/list/datum/spatial_grid_cell/outer_window = SSspatial_grid.get_cells_in_bounds(center, outer_window_x_radius, outer_window_y_radius)
var/list/datum/spatial_grid_cell/new_cells = inner_window - member_cells
// The outer window may contain cells we don't actually have, so we do it like this
var/list/datum/spatial_grid_cell/old_cells = member_cells - outer_window
// This whole thing is a naive implementation,
// if it turns out to be expensive because of all the list operations I'll look closer at it
member_cells -= old_cells
member_cells += new_cells
return list(new_cells, old_cells)