Move the domain logic involved into a base class named DensePathGraph. The base class contains all the domain logic necessary to traverse a graph including concepts such as custom movement layer.
PathGraph becomes responsible for proving a backing array for the pathfinding information, and is where the pooling logic lives instead, helping split the two concepts out.
The restores the previous behaviour before FindUnitPathToTargetCell was introduced. This prevents callers such as the harvester code crashing when a harvester tries to route home to a refinery, but there are no refineries.
Prior to ef44c31a72eab61a597cf539ee4b138e94b254fe, Locomotor would be earlier in the trait initialization sequence than SpawnMapActors. Locomotor would assume no actors on the map, and register to update blocked cells when new ones were added. When SpawnMapActors created actors, Locomotor was made aware and kept up-to-date.
After this commit, the initialization sequence was perturbed and SpawnMapActors would initialize first. Locomotor would assume no actors on the map and thus be unaware of these starting units, meaning those starting units would not cause blocking, allowing units to pass through them.
There are two possible fixes. SpawnMapActorsInfo can initialize NotBefore<LocomotorInfo>, enforcing that actors are spawned after locomotor is ready. Or we can remove the assumption in Locomotor that the map starts empty, and have it update blocked cells on startup. The latter seems cleaner, so any other traits that may want to spawn actors don't have to be aware sequencing their initialization with the Locomotor trait, instead things would "just work".
The types for Int32 and Boolean are currently replaced with friendly names of int and bool for the docs. Ensure we apply the same handling when these are nullable types, changing the output from Int32? and Boolean? to int? and bool?
Some path searches, using PathSearch, were created directly at the callsite rather than using the pathfinder trait. This means some searches did not not benefit from the performance checks done in the pathfinder trait. It also means the pathfinder trait was not responsible for all pathing done in the game. Fix this with the following changes:
- Create a sensible shape for the IPathFinder interface and promote it to a trait interface, allowing theoretical replacements of the implementation. Ensure none of the concrete classes in OpenRA.Mods.Common.Pathfinder are exposed in the interface to ensure this is possible.
- Update the PathFinder class to implement the interface, and update several callsites manually running pathfinding code to instead call the IPathFinder interface.
- Overall, this allows any implementation of the IPathFinder interface to intercept and control all path searching performed by the game. Previously some searches would not have used it, and no alternate implementations were possible as the existing implementation was hardcoded into the interface shape.
Additionally:
- Move the responsibility of finding paths on completed path searches from pathfinder to path search, which is a more sensible location.
- Clean up the pathfinder pre-search optimizations.
Requires<T> means that trait of type T will be initialized first, and asserts that at least one exists. The new NotBefore<T> means that trait of type T will be initialized first, but allows no traits.
This allows traits to control initialization order for optional dependencies. They want to be initialized second so they can rely on the dependencies having been initialized. But if the dependencies are optional then to not throw if none are present.
We apply this to Locomotor which was previously using AddFrameEndTask to work around trait order initialization. This improves the user experience as the initialization is applied whilst the loading screen is still visible, rather than the game starting and creating jank by performing initialization on the first tick.