mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-01-07 07:22:56 +00:00
## About The Pull Request
As the title says.
`init_order` is no more, subsystems ordering now depends on their
declared dependencies.
Subsystems can now declare which other subsystems need to init before
them using a list and the subsystem's typepath
I.e.
```dm
dependencies = list(
/datum/controller/subsystem/atoms,
/datum/controller/subsystem/mapping
)
```
The reverse can also be done, if a subsystem must initialize after your
own:
```dm
dependents = list(
/datum/controller/subsystem/atoms
)
```
Cyclical dependencies are not allowed and will throw an error on
initialization if one is found.
There's also a debug tool to visualize the dependency graph, although
it's a bit basic:

Subsystem load ordering can still be controlled using `init_stage`, some
subsystems use this in cases where they must initialize first or last
regardless of dependencies. An error will be thrown if a subsystem has
an `init_stage` before one of their dependencies.
## Why It's Good For The Game
Makes dealing with subsystem dependencies easier, and reduces the chance
of making a dependency error when needing to shift around subsystem
inits.
## Changelog
🆑
refactor: Refactored subsystem initialization
/🆑
207 lines
9.7 KiB
Plaintext
207 lines
9.7 KiB
Plaintext
/// Queues and manages JPS pathfinding steps
|
|
SUBSYSTEM_DEF(pathfinder)
|
|
name = "Pathfinder"
|
|
priority = FIRE_PRIORITY_PATHFINDING
|
|
wait = 0.5
|
|
/// List of pathfind datums we are currently trying to process
|
|
var/list/datum/pathfind/active_pathing = list()
|
|
/// List of pathfind datums being ACTIVELY processed. exists to make subsystem stats readable
|
|
var/list/datum/pathfind/currentrun = list()
|
|
/// List of uncheccked source_to_map entries
|
|
var/list/currentmaps = list()
|
|
/// Assoc list of target turf -> list(/datum/path_map) centered on the turf
|
|
var/list/source_to_maps = list()
|
|
var/static/space_type_cache
|
|
|
|
/datum/controller/subsystem/pathfinder/Initialize()
|
|
space_type_cache = typecacheof(/turf/open/space)
|
|
return SS_INIT_SUCCESS
|
|
|
|
/datum/controller/subsystem/pathfinder/stat_entry(msg)
|
|
msg = "P:[length(active_pathing)]"
|
|
return ..()
|
|
|
|
// This is another one of those subsystems (hey lighting) in which one "Run" means fully processing a queue
|
|
// We'll use a copy for this just to be nice to people reading the mc panel
|
|
/datum/controller/subsystem/pathfinder/fire(resumed)
|
|
if(!resumed)
|
|
src.currentrun = active_pathing.Copy()
|
|
src.currentmaps = deep_copy_list(source_to_maps)
|
|
|
|
// Dies of sonic speed from caching datum var reads
|
|
var/list/currentrun = src.currentrun
|
|
while(length(currentrun))
|
|
var/datum/pathfind/path = currentrun[length(currentrun)]
|
|
if(!path.search_step()) // Something's wrong
|
|
path.early_exit()
|
|
currentrun.len--
|
|
continue
|
|
if(MC_TICK_CHECK)
|
|
return
|
|
path.finished()
|
|
// Next please
|
|
currentrun.len--
|
|
|
|
// Go over our existing pathmaps, clear out the ones we aren't using
|
|
var/list/currentmaps = src.currentmaps
|
|
var/oldest_time = world.time - MAP_REUSE_SLOWEST
|
|
while(length(currentmaps))
|
|
var/turf/source = currentmaps[length(currentmaps)]
|
|
var/list/datum/path_map/owned_maps = currentmaps[source]
|
|
for(var/datum/path_map/map as anything in owned_maps)
|
|
if(map.creation_time < oldest_time && !map.building)
|
|
source_to_maps[source] -= map
|
|
owned_maps.len--
|
|
if(MC_TICK_CHECK)
|
|
return
|
|
if(!length(source_to_maps[source]))
|
|
source_to_maps -= source
|
|
|
|
currentmaps.len--
|
|
|
|
/// Initiates a pathfind. Returns true if we're good, FALSE if something's failed
|
|
/datum/controller/subsystem/pathfinder/proc/pathfind(atom/movable/requester, atom/end, max_distance = 30, mintargetdist, access = list(), simulated_only = TRUE, turf/exclude, skip_first = TRUE, diagonal_handling = DIAGONAL_REMOVE_CLUNKY, list/datum/callback/on_finish)
|
|
var/datum/pathfind/jps/path = new()
|
|
path.setup(requester, access, max_distance, simulated_only, exclude, on_finish, end, mintargetdist, skip_first, diagonal_handling)
|
|
if(path.start())
|
|
active_pathing += path
|
|
return TRUE
|
|
return FALSE
|
|
|
|
/// Initiates a swarmed pathfind. Returns TRUE if we're good, FALSE if something's failed
|
|
/// If a valid pathmap exists for the TARGET turf we'll use that, otherwise we have to build a new one
|
|
/datum/controller/subsystem/pathfinder/proc/swarmed_pathfind(atom/movable/requester, atom/end, max_distance = 30, mintargetdist = 0, age = MAP_REUSE_INSTANT, access = list(), simulated_only = TRUE, turf/exclude, skip_first = TRUE, list/datum/callback/on_finish)
|
|
var/turf/target = get_turf(end)
|
|
var/datum/can_pass_info/pass_info = new(requester, access)
|
|
// If there's a map we can use already, use it
|
|
var/datum/path_map/valid_map = get_valid_map(pass_info, target, simulated_only, exclude, age, include_building = TRUE)
|
|
if(valid_map && valid_map.expand(max_distance))
|
|
path_map_passalong(on_finish, get_turf(requester), mintargetdist, skip_first, valid_map)
|
|
return TRUE
|
|
|
|
// Otherwise we're gonna make a new one, and turn it into a path for the callbacks passed into us
|
|
var/list/datum/callback/pass_in = list()
|
|
pass_in += CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(path_map_passalong), on_finish, get_turf(requester), mintargetdist, skip_first)
|
|
// And to allow subsequent calls to reuse the same map, we'll put a placeholder in the cache, and fill it up when the pathing finishes
|
|
var/datum/path_map/empty = new()
|
|
empty.pass_info = new(requester, access)
|
|
empty.start = target
|
|
empty.pass_space = simulated_only
|
|
empty.avoid = exclude
|
|
empty.building = TRUE
|
|
path_map_cache(target, empty)
|
|
pass_in += CALLBACK(src, PROC_REF(path_map_fill), target, empty)
|
|
if(!SSpathfinder.can_pass_build_map(pass_info, target, max_distance, simulated_only, exclude, pass_in))
|
|
return FALSE
|
|
return TRUE
|
|
|
|
/// We generate a path for the passed in callbacks, and then pipe it over
|
|
/proc/path_map_passalong(list/datum/callback/return_callbacks, turf/target, mintargetdist = 0, skip_first = TRUE, datum/path_map/hand_back)
|
|
var/list/requested_path
|
|
if(istype(hand_back, /datum/path_map))
|
|
requested_path = hand_back.get_path_from(target, skip_first, mintargetdist)
|
|
for(var/datum/callback/return_callback as anything in return_callbacks)
|
|
return_callback.Invoke(requested_path)
|
|
|
|
/// Caches the passed in path_map, allowing for reuse in future
|
|
/datum/controller/subsystem/pathfinder/proc/path_map_cache(turf/target, datum/path_map/hand_back)
|
|
// Cache our path_map
|
|
if(!target || !hand_back)
|
|
return
|
|
source_to_maps[target] += list(hand_back)
|
|
|
|
/datum/controller/subsystem/pathfinder/proc/path_map_fill(turf/target, datum/path_map/fill_into, datum/path_map/hand_back)
|
|
fill_into.building = FALSE
|
|
if(!fill_into.compare_against(hand_back))
|
|
source_to_maps[target] -= fill_into
|
|
return
|
|
fill_into.copy_from(hand_back)
|
|
fill_into.creation_time = hand_back.creation_time
|
|
// If we aren't in the source list anymore don't go trying to clear it out yeah?
|
|
if(!source_to_maps[target] || !(fill_into in source_to_maps[target]))
|
|
return
|
|
// Let's remove anything we're better than
|
|
for(var/datum/path_map/same_target as anything in source_to_maps[target])
|
|
if(fill_into == same_target || !same_target.compare_against(hand_back))
|
|
continue
|
|
// If it's still being made it'll be fresher then us
|
|
if(same_target.building)
|
|
continue
|
|
// We assume that we are fresher, and that's all we care about
|
|
// If it's being expanded it'll get updated when that finishes, then clear when all the refs drop
|
|
source_to_maps[target] -= same_target
|
|
|
|
/// Initiates a SSSP run. Returns true if we're good, FALSE if something's failed
|
|
/datum/controller/subsystem/pathfinder/proc/build_map(atom/movable/requester, turf/source, max_distance = 30, access = list(), simulated_only = TRUE, turf/exclude, list/datum/callback/on_finish)
|
|
var/datum/pathfind/sssp/path = new()
|
|
path.setup(requester, access, source, max_distance, simulated_only, exclude, on_finish)
|
|
if(path.start())
|
|
active_pathing += path
|
|
return TRUE
|
|
return FALSE
|
|
|
|
/// Initiates a SSSP run from a pass_info datum. Returns true if we're good, FALSE if something's failed
|
|
/datum/controller/subsystem/pathfinder/proc/can_pass_build_map(datum/can_pass_info/pass_info, turf/source, max_distance = 30, simulated_only = TRUE, turf/exclude, list/datum/callback/on_finish)
|
|
var/datum/pathfind/sssp/path = new()
|
|
path.setup_from_canpass(pass_info, source, max_distance, simulated_only, exclude, on_finish)
|
|
if(path.start())
|
|
active_pathing += path
|
|
return TRUE
|
|
return FALSE
|
|
|
|
/// Begins to handle a pathfinding run based off the input /datum/pathfind datum
|
|
/// You should not use this, it exists to allow for shenanigans. You do not know how to do shenanigans
|
|
/datum/controller/subsystem/pathfinder/proc/run_pathfind(datum/pathfind/run)
|
|
active_pathing += run
|
|
return TRUE
|
|
|
|
/// Takes a set of pathfind info, returns the first valid pathmap that would work if one exists
|
|
/// Optionally takes a max age to accept (defaults to 0 seconds) and a minimum acceptable range
|
|
/// If include_building is true and we can only find a building path, we'll use that instead. tho we will wait for it to finish first
|
|
/datum/controller/subsystem/pathfinder/proc/get_valid_map(datum/can_pass_info/pass_info, turf/target, simulated_only = TRUE, turf/exclude, age = MAP_REUSE_INSTANT, min_range = -INFINITY, include_building = FALSE)
|
|
// Walk all the maps that match our requester's turf OR our target's
|
|
// Then hold onto em. If their cache time is short we can reuse/expand them, if not we'll have to make a new one
|
|
var/oldest_time = world.time - age
|
|
/// Backup return value used if no finished pathmaps are found
|
|
var/datum/path_map/constructing
|
|
for(var/datum/path_map/shared_source as anything in source_to_maps[target])
|
|
if(!shared_source.compare_against_args(pass_info, target, simulated_only, exclude))
|
|
continue
|
|
var/max_dist = 0
|
|
if(shared_source.distances.len)
|
|
max_dist = shared_source.distances[shared_source.distances.len]
|
|
if(max_dist < min_range)
|
|
continue
|
|
if(oldest_time > shared_source.creation_time && !shared_source.building)
|
|
continue
|
|
if(shared_source.building)
|
|
if(include_building)
|
|
constructing = constructing || shared_source
|
|
continue
|
|
|
|
return shared_source
|
|
if(constructing)
|
|
UNTIL(constructing.building == FALSE)
|
|
return constructing
|
|
return null
|
|
|
|
/// Takes a set of pathfind info, returns all valid pathmaps that would work
|
|
/// Takes an optional minimum range arg
|
|
/datum/controller/subsystem/pathfinder/proc/get_valid_maps(datum/can_pass_info/pass_info, turf/target, simulated_only = TRUE, turf/exclude, age = MAP_REUSE_INSTANT, min_range = -INFINITY, include_building = FALSE)
|
|
// Walk all the maps that match our requester's turf OR our target's
|
|
// Then hold onto em. If their cache time is short we can reuse/expand them, if not we'll have to make a new one
|
|
var/list/valid_maps = list()
|
|
var/oldest_time = world.time - age
|
|
for(var/datum/path_map/shared_source as anything in source_to_maps[target])
|
|
if(shared_source.compare_against_args(pass_info, target, simulated_only, exclude))
|
|
continue
|
|
var/max_dist = shared_source.distances[shared_source.distances.len]
|
|
if(max_dist < min_range)
|
|
continue
|
|
if(oldest_time > shared_source.creation_time)
|
|
continue
|
|
if(!include_building && shared_source.building)
|
|
continue
|
|
valid_maps += shared_source
|
|
return valid_maps
|