To prepare them for documentation generation.
Also added descriptions to SpriteSequence implementations and their properties.
Also made a few code style fixes.
Two different issues were causing a path search to not explore cells in order of the cheapest estimated route first. This meant the search could sometimes miss a cheaper route and return a suboptimal path.
- PriorityQueue had a bug which would cause it to not check some elements when restoring the heap property of its internal data structure. Failing to do this would invalidate the heap property, meaning it would not longer return the items in correct priority order. Additional tests ensure this is covered.
- When a path search encountered the same cell again with a lower cost, it would not update the priority queue with the new cost. This meant the cell was not explored early enough as it was in the queue with its original, higher cost. Exploring other paths might close off surrounding cells, preventing the cell with the lower cost from progressing. Instead we now add a duplicate with the lower cost to ensure it gets explored at the right time. We remove the duplicate with the higher cost in CanExpand by checking for already Closed cells.
Prior to ef44c31a72eab61a597cf539ee4b138e94b254fe, Locomotor would be earlier in the trait initialization sequence than SpawnStartingUnits. After this commit, the initialization sequence was perturbed and SpawnStartingUnits would initialize first. When SpawnStartingUnits would query CanEnterCell this would generate a null reference as Locomotor had not yet initialized.
SpawnStartingUnitsInfo is made to initialize NotBefore LocomotorInfo to enforce the required trait ordering.
The following error occurs on a map where the radar activation sound plays:
Exception has occurred: CLR/System.ArgumentException
An exception of type 'System.ArgumentException' occurred in System.Private.CoreLib.dll but was not handled in user code: 'Property set method not found.'
at System.Reflection.RuntimePropertyInfo.SetValue(Object obj, Object value, BindingFlags invokeAttr, Binder binder, Object[] index, CultureInfo culture)
at System.Reflection.RuntimePropertyInfo.SetValue(Object obj, Object value, Object[] index)
at OpenRA.FieldLoader.LoadField(Object target, String key, String value) in /home/jason/git/OpenRA/OpenRA.Game/FieldLoader.cs:line 609
at OpenRA.WidgetLoader.LoadWidget(WidgetArgs args, Widget parent, MiniYamlNode node) in /home/jason/git/OpenRA/OpenRA.Game/Widgets/WidgetLoader.cs:line 60
at OpenRA.WidgetLoader.LoadWidget(WidgetArgs args, Widget parent, MiniYamlNode node) in /home/jason/git/OpenRA/OpenRA.Game/Widgets/WidgetLoader.cs:line 67
at OpenRA.WidgetLoader.LoadWidget(WidgetArgs args, Widget parent, MiniYamlNode node) in /home/jason/git/OpenRA/OpenRA.Game/Widgets/WidgetLoader.cs:line 67
at OpenRA.WidgetLoader.LoadWidget(WidgetArgs args, Widget parent, MiniYamlNode node) in /home/jason/git/OpenRA/OpenRA.Game/Widgets/WidgetLoader.cs:line 67
at OpenRA.WidgetLoader.LoadWidget(WidgetArgs args, Widget parent, String w) in /home/jason/git/OpenRA/OpenRA.Game/Widgets/WidgetLoader.cs:line 43
at OpenRA.Game.LoadWidget(World world, String id, Widget parent, WidgetArgs args) in /home/jason/git/OpenRA/OpenRA.Game/Game.cs:line 160
at OpenRA.Mods.Common.Widgets.Logic.LoadIngamePlayerOrObserverUILogic..ctor(Widget widget, World world) in /home/jason/git/OpenRA/OpenRA.Mods.Common/Widgets/Logic/Ingame/LoadIngamePlayerOrObserverUILogic.cs:line 34
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".
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.
This helps improve the safety of code the uses reflection when methods may get renamed, and helps navigating code as the nameof will show up when searching for references to members.