From dd9d600ef917947a1d0401497f386e22a9d1b4dd Mon Sep 17 00:00:00 2001 From: RoosterDragon Date: Fri, 8 Oct 2021 21:11:14 +0100 Subject: [PATCH] Change GetCustomMovementLayers to expose an array, not a dictionary. As there are few custom movement layers, using an array is good for improving lookup speed. Additionally, we can simplify some code by reserving index 0 of the array for the ground layer. Code that needs to maintain a state for the ground layer and every custom movement layer can now maintain a flat array of state using index 0 for the ground layer, and the the ICustomMovementLayer.Index for the custom movement layer. This removes a lot of ternary statements checking for the ground layer special case. --- OpenRA.Mods.Common/Pathfinder/PathGraph.cs | 47 +++++----- OpenRA.Mods.Common/Traits/Mobile.cs | 10 +-- OpenRA.Mods.Common/Traits/World/ActorMap.cs | 92 ++++++++++++-------- OpenRA.Mods.Common/Traits/World/Locomotor.cs | 42 ++++----- 4 files changed, 105 insertions(+), 86 deletions(-) diff --git a/OpenRA.Mods.Common/Pathfinder/PathGraph.cs b/OpenRA.Mods.Common/Pathfinder/PathGraph.cs index b7ff6cd9d8..200297be7c 100644 --- a/OpenRA.Mods.Common/Pathfinder/PathGraph.cs +++ b/OpenRA.Mods.Common/Pathfinder/PathGraph.cs @@ -88,22 +88,23 @@ namespace OpenRA.Mods.Common.Pathfinder readonly Locomotor locomotor; readonly CellInfoLayerPool.PooledCellInfoLayer pooledLayer; readonly bool checkTerrainHeight; - CellLayer groundInfo; - - readonly Dictionary Info)> customLayerInfo = - new Dictionary)>(); + readonly CellLayer[] cellInfoForLayer; public PathGraph(CellInfoLayerPool layerPool, Locomotor locomotor, Actor actor, World world, BlockedByActor check) { - pooledLayer = layerPool.Get(); - groundInfo = pooledLayer.GetLayer(); - var locomotorInfo = locomotor.Info; this.locomotor = locomotor; + // As we support a search over the whole map area, + // use the pool to grab the CellInfos we need to track the graph state. + // This allows us to avoid the cost of allocating large arrays constantly. // PERF: Avoid LINQ - foreach (var cml in world.GetCustomMovementLayers().Values) - if (cml.EnabledForLocomotor(locomotorInfo)) - customLayerInfo[cml.Index] = (cml, pooledLayer.GetLayer()); + var cmls = world.GetCustomMovementLayers(); + pooledLayer = layerPool.Get(); + cellInfoForLayer = new CellLayer[cmls.Length]; + cellInfoForLayer[0] = pooledLayer.GetLayer(); + foreach (var cml in cmls) + if (cml != null && cml.EnabledForLocomotor(locomotor.Info)) + cellInfoForLayer[cml.Index] = pooledLayer.GetLayer(); World = world; Actor = actor; @@ -132,8 +133,8 @@ namespace OpenRA.Mods.Common.Pathfinder public List GetConnections(CPos position) { - var posLayer = position.Layer; - var info = posLayer == 0 ? groundInfo : customLayerInfo[posLayer].Info; + var layer = position.Layer; + var info = cellInfoForLayer[layer]; var previousPos = info[position].PreviousPos; var dx = position.X - previousPos.X; @@ -141,7 +142,7 @@ namespace OpenRA.Mods.Common.Pathfinder var index = dy * 3 + dx + 4; var directions = DirectedNeighbors[index]; - var validNeighbors = new List(directions.Length + (posLayer == 0 ? customLayerInfo.Count : 1)); + var validNeighbors = new List(directions.Length + (layer == 0 ? cellInfoForLayer.Length : 1)); for (var i = 0; i < directions.Length; i++) { var dir = directions[i]; @@ -153,12 +154,16 @@ namespace OpenRA.Mods.Common.Pathfinder validNeighbors.Add(new GraphConnection(neighbor, pathCost)); } - if (posLayer == 0) + var cmls = World.GetCustomMovementLayers(); + if (layer == 0) { - foreach (var cli in customLayerInfo.Values) + foreach (var cml in cmls) { - var layerPosition = new CPos(position.X, position.Y, cli.Layer.Index); - var entryCost = cli.Layer.EntryMovementCost(locomotor.Info, layerPosition); + if (cml == null || !cml.EnabledForLocomotor(locomotor.Info)) + continue; + + var layerPosition = new CPos(position.X, position.Y, cml.Index); + var entryCost = cml.EntryMovementCost(locomotor.Info, layerPosition); if (entryCost != MovementCostForUnreachableCell) validNeighbors.Add(new GraphConnection(layerPosition, entryCost)); } @@ -166,7 +171,7 @@ namespace OpenRA.Mods.Common.Pathfinder else { var layerPosition = new CPos(position.X, position.Y, 0); - var exitCost = customLayerInfo[posLayer].Layer.ExitMovementCost(locomotor.Info, layerPosition); + var exitCost = cmls[layer].ExitMovementCost(locomotor.Info, layerPosition); if (exitCost != MovementCostForUnreachableCell) validNeighbors.Add(new GraphConnection(layerPosition, exitCost)); } @@ -226,14 +231,12 @@ namespace OpenRA.Mods.Common.Pathfinder public CellInfo this[CPos pos] { - get => (pos.Layer == 0 ? groundInfo : customLayerInfo[pos.Layer].Info)[pos]; - set => (pos.Layer == 0 ? groundInfo : customLayerInfo[pos.Layer].Info)[pos] = value; + get => cellInfoForLayer[pos.Layer][pos]; + set => cellInfoForLayer[pos.Layer][pos] = value; } public void Dispose() { - groundInfo = null; - customLayerInfo.Clear(); pooledLayer.Dispose(); } } diff --git a/OpenRA.Mods.Common/Traits/Mobile.cs b/OpenRA.Mods.Common/Traits/Mobile.cs index 0ffaa093cf..063d9ed881 100644 --- a/OpenRA.Mods.Common/Traits/Mobile.cs +++ b/OpenRA.Mods.Common/Traits/Mobile.cs @@ -428,10 +428,8 @@ namespace OpenRA.Mods.Common.Traits if (ToCell.Layer == 0) return true; - if (self.World.GetCustomMovementLayers().TryGetValue(ToCell.Layer, out var layer)) - return layer.InteractsWithDefaultLayer; - - return true; + var layer = self.World.GetCustomMovementLayers()[ToCell.Layer]; + return layer == null || layer.InteractsWithDefaultLayer; } #endregion @@ -862,9 +860,7 @@ namespace OpenRA.Mods.Common.Traits return; } - var cml = self.World.WorldActor.TraitsImplementing() - .First(l => l.Index == self.Location.Layer); - + var cml = self.World.GetCustomMovementLayers()[self.Location.Layer]; if (!cml.ReturnToGroundLayerOnIdle) return; diff --git a/OpenRA.Mods.Common/Traits/World/ActorMap.cs b/OpenRA.Mods.Common/Traits/World/ActorMap.cs index ea1b9c6973..60bc35c08c 100644 --- a/OpenRA.Mods.Common/Traits/World/ActorMap.cs +++ b/OpenRA.Mods.Common/Traits/World/ActorMap.cs @@ -171,9 +171,10 @@ namespace OpenRA.Mods.Common.Traits readonly Dictionary proximityTriggers = new Dictionary(); int nextTriggerId; - readonly CellLayer influence; - readonly Dictionary> customInfluence = new Dictionary>(); - public readonly Dictionary CustomMovementLayers = new Dictionary(); + CellLayer[] influence; + + // Index 0 is kept null as layer 0 is used for the ground layer. + public ICustomMovementLayer[] CustomMovementLayers = new ICustomMovementLayer[] { null }; public event Action CellUpdated; readonly Bin[] bins; readonly int rows, cols; @@ -191,7 +192,7 @@ namespace OpenRA.Mods.Common.Traits { this.info = info; map = world.Map; - influence = new CellLayer(world.Map); + influence = new[] { new CellLayer(world.Map) }; cols = CellCoordToBinIndex(world.Map.MapSize.X) + 1; rows = CellCoordToBinIndex(world.Map.MapSize.Y) + 1; @@ -210,10 +211,18 @@ namespace OpenRA.Mods.Common.Traits void INotifyCreated.Created(Actor self) { - foreach (var cml in self.TraitsImplementing()) + var cmls = self.TraitsImplementing().ToList(); + if (cmls.Count == 0) + return; + + var length = cmls.Max(cml => cml.Index) + 1; + Array.Resize(ref CustomMovementLayers, length); + Array.Resize(ref influence, length); + + foreach (var cml in cmls) { CustomMovementLayers[cml.Index] = cml; - customInfluence.Add(cml.Index, new CellLayer(self.World.Map)); + influence[cml.Index] = new CellLayer(self.World.Map); } } @@ -259,21 +268,21 @@ namespace OpenRA.Mods.Common.Traits { // PERF: Custom enumerator for efficiency - using `yield` is slower. var uv = a.ToMPos(map); - if (!influence.Contains(uv)) + var layer = influence[a.Layer]; + if (!layer.Contains(uv)) return Enumerable.Empty(); - var layer = a.Layer == 0 ? influence : customInfluence[a.Layer]; return new ActorsAtEnumerable(layer[uv]); } public IEnumerable GetActorsAt(CPos a, SubCell sub) { var uv = a.ToMPos(map); - if (!influence.Contains(uv)) + var layer = influence[a.Layer]; + if (!layer.Contains(uv)) yield break; var always = sub == SubCell.FullCell || sub == SubCell.Any; - var layer = a.Layer == 0 ? influence : customInfluence[a.Layer]; for (var i = layer[uv]; i != null; i = i.Next) if (!i.Actor.Disposed && (i.SubCell == sub || i.SubCell == SubCell.FullCell || always)) yield return i.Actor; @@ -287,17 +296,18 @@ namespace OpenRA.Mods.Common.Traits public SubCell FreeSubCell(CPos cell, SubCell preferredSubCell = SubCell.Any, bool checkTransient = true) { var uv = cell.ToMPos(map); - if (!influence.Contains(uv)) + var layer = influence[cell.Layer]; + if (!layer.Contains(uv)) return preferredSubCell != SubCell.Any ? preferredSubCell : SubCell.First; - if (preferredSubCell != SubCell.Any && !AnyActorsAt(uv, cell, preferredSubCell, checkTransient)) + if (preferredSubCell != SubCell.Any && !AnyActorsAt(uv, cell, layer, preferredSubCell, checkTransient)) return preferredSubCell; - if (!AnyActorsAt(uv, cell.Layer)) + if (!AnyActorsAt(uv, layer)) return map.Grid.DefaultSubCell; for (var i = (int)SubCell.First; i < map.Grid.SubCellOffsets.Length; i++) - if (i != (int)preferredSubCell && !AnyActorsAt(uv, cell, (SubCell)i, checkTransient)) + if (i != (int)preferredSubCell && !AnyActorsAt(uv, cell, layer, (SubCell)i, checkTransient)) return (SubCell)i; return SubCell.Invalid; @@ -306,26 +316,26 @@ namespace OpenRA.Mods.Common.Traits public SubCell FreeSubCell(CPos cell, SubCell preferredSubCell, Func checkIfBlocker) { var uv = cell.ToMPos(map); - if (!influence.Contains(uv)) + var layer = influence[cell.Layer]; + if (!layer.Contains(uv)) return preferredSubCell != SubCell.Any ? preferredSubCell : SubCell.First; - if (preferredSubCell != SubCell.Any && !AnyActorsAt(uv, cell, preferredSubCell, checkIfBlocker)) + if (preferredSubCell != SubCell.Any && !AnyActorsAt(uv, layer, preferredSubCell, checkIfBlocker)) return preferredSubCell; - if (!AnyActorsAt(uv, cell.Layer)) + if (!AnyActorsAt(uv, layer)) return map.Grid.DefaultSubCell; for (var i = (byte)SubCell.First; i < map.Grid.SubCellOffsets.Length; i++) - if (i != (byte)preferredSubCell && !AnyActorsAt(uv, cell, (SubCell)i, checkIfBlocker)) + if (i != (byte)preferredSubCell && !AnyActorsAt(uv, layer, (SubCell)i, checkIfBlocker)) return (SubCell)i; return SubCell.Invalid; } // NOTE: pos required to be in map bounds - bool AnyActorsAt(MPos uv, int layerIndex) + bool AnyActorsAt(MPos uv, CellLayer layer) { - var layer = layerIndex == 0 ? influence : customInfluence[layerIndex]; return layer[uv] != null; } @@ -333,17 +343,17 @@ namespace OpenRA.Mods.Common.Traits public bool AnyActorsAt(CPos a) { var uv = a.ToMPos(map); - if (!influence.Contains(uv)) + var layer = influence[a.Layer]; + if (!layer.Contains(uv)) return false; - return AnyActorsAt(uv, a.Layer); + return AnyActorsAt(uv, layer); } // NOTE: pos required to be in map bounds - bool AnyActorsAt(MPos uv, CPos a, SubCell sub, bool checkTransient) + bool AnyActorsAt(MPos uv, CPos a, CellLayer layer, SubCell sub, bool checkTransient) { var always = sub == SubCell.FullCell || sub == SubCell.Any; - var layer = a.Layer == 0 ? influence : customInfluence[a.Layer]; for (var i = layer[uv]; i != null; i = i.Next) { if (always || i.SubCell == sub || i.SubCell == SubCell.FullCell) @@ -364,17 +374,17 @@ namespace OpenRA.Mods.Common.Traits public bool AnyActorsAt(CPos a, SubCell sub, bool checkTransient = true) { var uv = a.ToMPos(map); - if (!influence.Contains(uv)) + var layer = influence[a.Layer]; + if (!layer.Contains(uv)) return false; - return AnyActorsAt(uv, a, sub, checkTransient); + return AnyActorsAt(uv, a, layer, sub, checkTransient); } // NOTE: can not check aircraft - bool AnyActorsAt(MPos uv, CPos a, SubCell sub, Func withCondition) + bool AnyActorsAt(MPos uv, CellLayer layer, SubCell sub, Func withCondition) { var always = sub == SubCell.FullCell || sub == SubCell.Any; - var layer = a.Layer == 0 ? influence : customInfluence[a.Layer]; for (var i = layer[uv]; i != null; i = i.Next) if ((always || i.SubCell == sub || i.SubCell == SubCell.FullCell) && !i.Actor.Disposed && withCondition(i.Actor)) return true; @@ -386,10 +396,11 @@ namespace OpenRA.Mods.Common.Traits public bool AnyActorsAt(CPos a, SubCell sub, Func withCondition) { var uv = a.ToMPos(map); - if (!influence.Contains(uv)) + var layer = influence[a.Layer]; + if (!layer.Contains(uv)) return false; - return AnyActorsAt(uv, a, sub, withCondition); + return AnyActorsAt(uv, layer, sub, withCondition); } public void AddInfluence(Actor self, IOccupySpace ios) @@ -397,10 +408,10 @@ namespace OpenRA.Mods.Common.Traits foreach (var c in ios.OccupiedCells()) { var uv = c.Cell.ToMPos(map); - if (!influence.Contains(uv)) + var layer = influence[c.Cell.Layer]; + if (!layer.Contains(uv)) continue; - var layer = c.Cell.Layer == 0 ? influence : customInfluence[c.Cell.Layer]; layer[uv] = new InfluenceNode { Next = layer[uv], SubCell = c.SubCell, Actor = self }; if (cellTriggerInfluence.TryGetValue(c.Cell, out var triggers)) @@ -416,10 +427,10 @@ namespace OpenRA.Mods.Common.Traits foreach (var c in ios.OccupiedCells()) { var uv = c.Cell.ToMPos(map); - if (!influence.Contains(uv)) + var layer = influence[c.Cell.Layer]; + if (!layer.Contains(uv)) continue; - var layer = c.Cell.Layer == 0 ? influence : customInfluence[c.Cell.Layer]; var temp = layer[uv]; RemoveInfluenceInner(ref temp, self); layer[uv] = temp; @@ -496,9 +507,10 @@ namespace OpenRA.Mods.Common.Traits var t = new CellTrigger(cells, onEntry, onExit); cellTriggers.Add(id, t); + var layer = influence[0]; foreach (var c in cells) { - if (!influence.Contains(c)) + if (!layer.Contains(c)) continue; if (!cellTriggerInfluence.ContainsKey(c)) @@ -649,7 +661,15 @@ namespace OpenRA.Mods.Common.Traits public static class ActorMapWorldExts { - public static Dictionary GetCustomMovementLayers(this World world) + /// + /// Returns an array of custom movement layers. + /// The of a layer is used to index into this array. + /// This array may contain null entries for layers which are not present in the world. + /// This array is guaranteed to have a length of at least one. Index 0 is always null. + /// Index 0 is kept null as layer 0 is used for the ground layer, consumers can combine + /// the ground layer and custom layers into a single array for easy indexing. + /// + public static ICustomMovementLayer[] GetCustomMovementLayers(this World world) { return ((ActorMap)world.ActorMap).CustomMovementLayers; } diff --git a/OpenRA.Mods.Common/Traits/World/Locomotor.cs b/OpenRA.Mods.Common/Traits/World/Locomotor.cs index cb5e4665e3..be665623f0 100644 --- a/OpenRA.Mods.Common/Traits/World/Locomotor.cs +++ b/OpenRA.Mods.Common/Traits/World/Locomotor.cs @@ -144,18 +144,16 @@ namespace OpenRA.Mods.Common.Traits public readonly LocomotorInfo Info; public readonly uint MovementClass; - CellLayer cellsCost; - CellLayer blockingCache; - - readonly Dictionary> customLayerCellsCost = new Dictionary>(); - readonly Dictionary> customLayerBlockingCache = new Dictionary>(); readonly LocomotorInfo.TerrainInfo[] terrainInfos; readonly World world; readonly HashSet dirtyCells = new HashSet(); + readonly bool sharesCell; + + CellLayer[] cellsCost; + CellLayer[] blockingCache; IActorMap actorMap; - bool sharesCell; public Locomotor(Actor self, LocomotorInfo info) { @@ -177,7 +175,7 @@ namespace OpenRA.Mods.Common.Traits if (!world.Map.Contains(cell)) return PathGraph.MovementCostForUnreachableCell; - return cell.Layer == 0 ? cellsCost[cell] : customLayerCellsCost[cell.Layer][cell]; + return cellsCost[cell.Layer][cell]; } public int MovementSpeedForCell(CPos cell) @@ -193,7 +191,7 @@ namespace OpenRA.Mods.Common.Traits if (!world.Map.Contains(destNode)) return PathGraph.MovementCostForUnreachableCell; - var cellCost = destNode.Layer == 0 ? cellsCost[destNode] : customLayerCellsCost[destNode.Layer][destNode]; + var cellCost = cellsCost[destNode.Layer][destNode]; if (cellCost == PathGraph.MovementCostForUnreachableCell || !CanMoveFreelyInto(actor, destNode, check, ignoreActor)) @@ -359,8 +357,8 @@ namespace OpenRA.Mods.Common.Traits actorMap = w.ActorMap; actorMap.CellUpdated += CellUpdated; - blockingCache = new CellLayer(map); - cellsCost = new CellLayer(map); + cellsCost = new[] { new CellLayer(map) }; + blockingCache = new[] { new CellLayer(map) }; foreach (var cell in map.AllCells) UpdateCellCost(cell); @@ -371,12 +369,17 @@ namespace OpenRA.Mods.Common.Traits // This section needs to run after WorldLoaded() because we need to be sure that all types of ICustomMovementLayer have been initialized. w.AddFrameEndTask(_ => { - var customMovementLayers = w.WorldActor.TraitsImplementing(); - foreach (var cml in customMovementLayers) + var cmls = world.GetCustomMovementLayers(); + Array.Resize(ref cellsCost, cmls.Length); + Array.Resize(ref blockingCache, cmls.Length); + foreach (var cml in cmls) { + if (cml == null) + continue; + var cellLayer = new CellLayer(map); - customLayerCellsCost[cml.Index] = cellLayer; - customLayerBlockingCache[cml.Index] = new CellLayer(map); + cellsCost[cml.Index] = cellLayer; + blockingCache[cml.Index] = new CellLayer(map); foreach (var cell in map.AllCells) { @@ -395,13 +398,10 @@ namespace OpenRA.Mods.Common.Traits CellCache GetCache(CPos cell) { - if (dirtyCells.Contains(cell)) - { + if (dirtyCells.Remove(cell)) UpdateCellBlocking(cell); - dirtyCells.Remove(cell); - } - var cache = cell.Layer == 0 ? blockingCache : customLayerBlockingCache[cell.Layer]; + var cache = blockingCache[cell.Layer]; return cache[cell]; } @@ -422,7 +422,7 @@ namespace OpenRA.Mods.Common.Traits if (index != byte.MaxValue) cost = terrainInfos[index].Cost; - var cache = cell.Layer == 0 ? cellsCost : customLayerCellsCost[cell.Layer]; + var cache = cellsCost[cell.Layer]; cache[cell] = cost; } @@ -431,7 +431,7 @@ namespace OpenRA.Mods.Common.Traits { using (new PerfSample("locomotor_cache")) { - var cache = cell.Layer == 0 ? blockingCache : customLayerBlockingCache[cell.Layer]; + var cache = blockingCache[cell.Layer]; var actors = actorMap.GetActorsAt(cell); var cellFlag = CellFlag.HasFreeSpace;