At a bare minimum, entities need to be able to travel from one chunk's heightmap to another. Ideally, they will also be able to find floating islands, buildings and other structures, and vehicles they could reach as well. This results in a combinatorial explosion of possible edge types. I do not want to use dynamic dispatch in this case, because the possible kinds of edges will be known at compile time, and dynamic dispatch would completely eliminate the compiler's ability to inline and optimize edge filters. Also, matching on an enum would force all possible combinations to be handled, while a trait might hide unhandled cases.
Proposal
Rather than just matching on all possible pairs of edge targets, I believe a sort of manual jump table would be possible. Something like this:
// Could be 2 separate structs, but genericness ensures both are kept in sync.
struct EdgeFilters<H, S, F, V> {
heightmap: H,
structure: S,
floating_island: F,
vehicle: V,
}
// Would probably want to make a macro for this:
type EdgeFilter = EdgeFilters<
EdgeFilters<fn(&HeightmapNode, &HeightmapNode) -> bool, fn(&HeightmapNode, &StructureNode) -> bool, /*...*/>,
EdgeFilters<fn(&StructureNode, &HeightmapNode) -> bool, fn(&StructureNode, &StrutureNode) -> bool, /*...*/>,
/*...*/,
EdgeFilters<fn(&VehicleNode, &HeightmapNode) -> bool, /*...*/, fn(&VehicleNode, &VehicleNode) -> bool>,
>;
This would also allow built-in filter functions like
fn all(a: &impl Node, b: &impl Node) -> bool { true }
fn none(a: &impl Node, b: &impl Node) -> bool { false }
// usage:
// ...
heightmap: all,
// ...
to be re-used.
Then the only match statement would need to be in the nav graph traversal code. All custom filters would simply fill out the matrix of function pointers, being forced to handle every case with the correct type parameters.
Of course having to manually specify at least 16 function names for every single AI agent would be tedious. Struct update syntax would help a lot, but I'm not sure I would want to allow ..default()
, because then we're back to being able to forget to handle specific cases. Perhaps constants could be defined instead, like:
pub const ABSOLUTELY_ALL_EDGES: EdgeFilter = EdgeFilters::</*...*/> { heightmaps: ALL_EDGES, /*...*/ };
This would allow terse definitions, while making it clear what the "default" behavior will be. Of course I'm not even sure if bool
is what will be returned. This probably needs to be used to handle costs as well.