|
| 1 | +/** |
| 2 | + * Spatial gridmap, cell tracking |
| 3 | + * |
| 4 | + * This datum exists to make the large, repeated "everything in some range" pattern faster |
| 5 | + * Rather then just refreshing against everything, we track all the cells in range of the passed in "window" |
| 6 | + * This lets us do entered/left logic, and make ordinarially quite expensive logic much cheaper |
| 7 | + * |
| 8 | + * Note: This system should not be used for things who have strict requirements about what is NOT in their processed entries |
| 9 | + * It should instead only be used for logic that only really cares about limiting how much gets "entered" in any one call |
| 10 | + * Because we apply this limitation, we can do things to make our code much less prone to unneeded work |
| 11 | + */ |
| 12 | +/datum/cell_tracker |
| 13 | + var/list/datum/spatial_grid_cell/member_cells = list() |
| 14 | + // Inner window |
| 15 | + // If a cell is inside this space, it will be entered into our membership list |
| 16 | + /// The height (y radius) of our inner window |
| 17 | + var/inner_window_x_radius |
| 18 | + /// The width (x radius) of our inner window |
| 19 | + var/inner_window_y_radius |
| 20 | + |
| 21 | + // Outer window |
| 22 | + // If a cell is outside this space, it will be removed from our memebership list |
| 23 | + // This effectively applies a grace window, to prevent moving back and forth across a border line causing issues |
| 24 | + /// The height (y radius) of our outer window |
| 25 | + var/outer_window_x_radius |
| 26 | + /// The width (x radius) of our outer window |
| 27 | + var/outer_window_y_radius |
| 28 | + |
| 29 | +/// Accepts a width and height to use for this tracker |
| 30 | +/// Also accepts the ratio to use between inner and outer window. Optional, defaults to 2 |
| 31 | +/datum/cell_tracker/New(width, height, inner_outer_ratio) |
| 32 | + set_bounds(width, height, inner_outer_ratio) |
| 33 | + return ..() |
| 34 | + |
| 35 | +/datum/cell_tracker/Destroy(force) |
| 36 | + stack_trace("Attempted to delete a cell tracker. They don't hold any refs outside of cells, what are you doing") |
| 37 | + if(!force) |
| 38 | + return QDEL_HINT_LETMELIVE |
| 39 | + member_cells.Cut() |
| 40 | + return ..() |
| 41 | + |
| 42 | +/// Takes a width and height, and uses them to set the inner window, and interpolate the outer window |
| 43 | +/datum/cell_tracker/proc/set_bounds(width = 0, height = 0, ratio = 2) |
| 44 | + // We want to store these as radii, rather then width and height, since that's convineient for spatial grid code |
| 45 | + var/x_radius = CEILING(width, 2) |
| 46 | + var/y_radius = CEILING(height, 2) |
| 47 | + inner_window_x_radius = x_radius |
| 48 | + inner_window_y_radius = y_radius |
| 49 | + |
| 50 | + outer_window_x_radius = x_radius * ratio |
| 51 | + outer_window_y_radius = y_radius * ratio |
| 52 | + |
| 53 | +/// Returns a list of newly and formerly joined spatial grid managed objects of type [type] in the form list(new, old) |
| 54 | +/// Takes the center of our window as input |
| 55 | +/datum/cell_tracker/proc/recalculate_type_members(turf/center, type) |
| 56 | + var/list/new_and_old = recalculate_cells(center) |
| 57 | + |
| 58 | + var/list/new_members = list() |
| 59 | + var/list/former_members = list() |
| 60 | + /// Pull out all the new and old memebers we want |
| 61 | + switch(type) |
| 62 | + if(SPATIAL_GRID_CONTENTS_TYPE_CLIENTS) |
| 63 | + for(var/datum/spatial_grid_cell/cell as anything in new_and_old[1]) |
| 64 | + new_members += cell.client_contents |
| 65 | + for(var/datum/spatial_grid_cell/cell as anything in new_and_old[2]) |
| 66 | + former_members += cell.client_contents |
| 67 | + if(SPATIAL_GRID_CONTENTS_TYPE_HEARING) |
| 68 | + for(var/datum/spatial_grid_cell/cell as anything in new_and_old[1]) |
| 69 | + new_members += cell.hearing_contents |
| 70 | + for(var/datum/spatial_grid_cell/cell as anything in new_and_old[2]) |
| 71 | + former_members += cell.hearing_contents |
| 72 | + if(SPATIAL_GRID_CONTENTS_TYPE_ATMOS) |
| 73 | + for(var/datum/spatial_grid_cell/cell as anything in new_and_old[1]) |
| 74 | + new_members += cell.atmos_contents |
| 75 | + for(var/datum/spatial_grid_cell/cell as anything in new_and_old[2]) |
| 76 | + former_members += cell.atmos_contents |
| 77 | + |
| 78 | + return list(new_members, former_members) |
| 79 | + |
| 80 | +/// Recalculates our member list, returns a list in the form list(new members, old members) for reaction |
| 81 | +/// Accepts the turf to use as our "center" |
| 82 | +/datum/cell_tracker/proc/recalculate_cells(turf/center) |
| 83 | + if(!center) |
| 84 | + CRASH("/datum/cell_tracker had an invalid location on refresh, ya done fucked") |
| 85 | + // This is a mild waste of cpu time. Consider optimizing by adding a new helper function to get just the space between two bounds |
| 86 | + // Assuming it ever becomes a real problem |
| 87 | + var/list/datum/spatial_grid_cell/inner_window = SSspatial_grid.get_cells_in_bounds(center, inner_window_x_radius, inner_window_y_radius) |
| 88 | + var/list/datum/spatial_grid_cell/outer_window = SSspatial_grid.get_cells_in_bounds(center, outer_window_x_radius, outer_window_y_radius) |
| 89 | + |
| 90 | + var/list/datum/spatial_grid_cell/new_cells = inner_window - member_cells |
| 91 | + // The outer window may contain cells we don't actually have, so we do it like this |
| 92 | + var/list/datum/spatial_grid_cell/old_cells = member_cells - outer_window |
| 93 | + |
| 94 | + // This whole thing is a naive implementation, |
| 95 | + // if it turns out to be expensive because of all the list operations I'll look closer at it |
| 96 | + member_cells -= old_cells |
| 97 | + member_cells += new_cells |
| 98 | + |
| 99 | + return list(new_cells, old_cells) |
0 commit comments