From 277906c65708b0263fe05438b6da45609b16bff0 Mon Sep 17 00:00:00 2001 From: teinarss Date: Thu, 1 Aug 2019 20:02:42 +0200 Subject: [PATCH] Fixes on blocking logic --- OpenRA.Game/Player.cs | 4 +- OpenRA.Game/Traits/TraitsInterfaces.cs | 2 +- OpenRA.Game/World.cs | 9 +- OpenRA.Mods.Cnc/Traits/Mine.cs | 10 +- OpenRA.Mods.Common/Traits/Crates/Crate.cs | 9 +- OpenRA.Mods.Common/Traits/Crushable.cs | 18 +- OpenRA.Mods.Common/Traits/World/ActorMap.cs | 18 +- OpenRA.Mods.Common/Traits/World/Locomotor.cs | 183 +++++++++---------- OpenRA.Mods.Common/TraitsInterfaces.cs | 2 +- 9 files changed, 121 insertions(+), 134 deletions(-) diff --git a/OpenRA.Game/Player.cs b/OpenRA.Game/Player.cs index 4b49194700..0095e0b07a 100644 --- a/OpenRA.Game/Player.cs +++ b/OpenRA.Game/Player.cs @@ -76,8 +76,8 @@ namespace OpenRA // Each player is identified with a unique bit in the set // Cache masks for the player's index and ally/enemy player indices for performance. public LongBitSet PlayerMask; - public LongBitSet AllyMask = default(LongBitSet); - public LongBitSet EnemyMask = default(LongBitSet); + public LongBitSet AlliedPlayersMask = default(LongBitSet); + public LongBitSet EnemyPlayersMask = default(LongBitSet); public bool UnlockedRenderPlayer { diff --git a/OpenRA.Game/Traits/TraitsInterfaces.cs b/OpenRA.Game/Traits/TraitsInterfaces.cs index 860357c891..fb8846f63c 100644 --- a/OpenRA.Game/Traits/TraitsInterfaces.cs +++ b/OpenRA.Game/Traits/TraitsInterfaces.cs @@ -260,7 +260,7 @@ namespace OpenRA.Traits WDist LargestActorRadius { get; } WDist LargestBlockingActorRadius { get; } - event Action> CellsUpdated; + event Action CellUpdated; } [RequireExplicitImplementation] diff --git a/OpenRA.Game/World.cs b/OpenRA.Game/World.cs index 47bb2e647b..d5e18a2a90 100644 --- a/OpenRA.Game/World.cs +++ b/OpenRA.Game/World.cs @@ -43,7 +43,8 @@ namespace OpenRA public readonly MersenneTwister SharedRandom; public readonly MersenneTwister LocalRandom; public readonly IModelCache ModelCache; - public LongBitSet AllPlayerMask = default(LongBitSet); + public LongBitSet AllPlayersMask = default(LongBitSet); + public readonly LongBitSet NoPlayersMask = default(LongBitSet); public Player[] Players = new Player[0]; @@ -208,7 +209,7 @@ namespace OpenRA foreach (var p in Players) { if (!p.Spectating) - AllPlayerMask = AllPlayerMask.Union(p.PlayerMask); + AllPlayersMask = AllPlayersMask.Union(p.PlayerMask); foreach (var q in Players) { @@ -244,10 +245,10 @@ namespace OpenRA { case Stance.Enemy: case Stance.Neutral: - p.EnemyMask = p.EnemyMask.Union(bitSet); + p.EnemyPlayersMask = p.EnemyPlayersMask.Union(bitSet); break; case Stance.Ally: - p.AllyMask = p.AllyMask.Union(bitSet); + p.AlliedPlayersMask = p.AlliedPlayersMask.Union(bitSet); break; } } diff --git a/OpenRA.Mods.Cnc/Traits/Mine.cs b/OpenRA.Mods.Cnc/Traits/Mine.cs index b0b16c8546..c373317925 100644 --- a/OpenRA.Mods.Cnc/Traits/Mine.cs +++ b/OpenRA.Mods.Cnc/Traits/Mine.cs @@ -59,11 +59,13 @@ namespace OpenRA.Mods.Cnc.Traits return info.CrushClasses.Overlaps(crushClasses); } - bool ICrushable.TryCalculatePlayerBlocking(Actor self, BitSet crushClasses, out LongBitSet blocking) + LongBitSet ICrushable.CrushableBy(Actor self, BitSet crushClasses) { - // Fall back to the slow path - blocking = default(LongBitSet); - return false; + if (!info.CrushClasses.Overlaps(crushClasses)) + return self.World.NoPlayersMask; + + // Friendly units should move around! + return info.BlockFriendly ? self.Owner.EnemyPlayersMask : self.World.AllPlayersMask; } } diff --git a/OpenRA.Mods.Common/Traits/Crates/Crate.cs b/OpenRA.Mods.Common/Traits/Crates/Crate.cs index aa29c880a8..8dc8c9e7cb 100644 --- a/OpenRA.Mods.Common/Traits/Crates/Crate.cs +++ b/OpenRA.Mods.Common/Traits/Crates/Crate.cs @@ -225,14 +225,9 @@ namespace OpenRA.Mods.Common.Traits return self.IsAtGroundLevel() && crushClasses.Contains(info.CrushClass); } - bool ICrushable.TryCalculatePlayerBlocking(Actor self, BitSet crushClasses, out LongBitSet blocking) + LongBitSet ICrushable.CrushableBy(Actor self, BitSet crushClasses) { - if (self.IsAtGroundLevel() && crushClasses.Contains(info.CrushClass)) - blocking = default(LongBitSet); - else - blocking = self.World.AllPlayerMask; - - return true; + return self.IsAtGroundLevel() && crushClasses.Contains(info.CrushClass) ? self.World.AllPlayersMask : self.World.NoPlayersMask; } void INotifyAddedToWorld.AddedToWorld(Actor self) diff --git a/OpenRA.Mods.Common/Traits/Crushable.cs b/OpenRA.Mods.Common/Traits/Crushable.cs index bf1c7d5680..02e57e1054 100644 --- a/OpenRA.Mods.Common/Traits/Crushable.cs +++ b/OpenRA.Mods.Common/Traits/Crushable.cs @@ -65,6 +65,14 @@ namespace OpenRA.Mods.Common.Traits return CrushableInner(crushClasses, crusher.Owner); } + LongBitSet ICrushable.CrushableBy(Actor self, BitSet crushClasses) + { + if (IsTraitDisabled || !self.IsAtGroundLevel() || !Info.CrushClasses.Overlaps(crushClasses)) + return self.World.NoPlayersMask; + + return Info.CrushedByFriendlies ? self.World.AllPlayersMask : self.Owner.EnemyPlayersMask; + } + bool CrushableInner(BitSet crushClasses, Player crushOwner) { if (IsTraitDisabled) @@ -79,15 +87,5 @@ namespace OpenRA.Mods.Common.Traits return Info.CrushClasses.Overlaps(crushClasses); } - - bool ICrushable.TryCalculatePlayerBlocking(Actor self, BitSet crushClasses, out LongBitSet blocking) - { - if (IsTraitDisabled || !self.IsAtGroundLevel() || !Info.CrushClasses.Overlaps(crushClasses)) - blocking = self.World.AllPlayerMask; - else - blocking = Info.CrushedByFriendlies ? default(LongBitSet) : self.Owner.AllyMask; - - return true; - } } } diff --git a/OpenRA.Mods.Common/Traits/World/ActorMap.cs b/OpenRA.Mods.Common/Traits/World/ActorMap.cs index 0611e93979..3a6f01783d 100644 --- a/OpenRA.Mods.Common/Traits/World/ActorMap.cs +++ b/OpenRA.Mods.Common/Traits/World/ActorMap.cs @@ -173,7 +173,7 @@ namespace OpenRA.Mods.Common.Traits readonly CellLayer influence; readonly Dictionary> customInfluence = new Dictionary>(); public readonly Dictionary CustomMovementLayers = new Dictionary(); - public event Action> CellsUpdated; + public event Action CellUpdated; readonly Bin[] bins; readonly int rows, cols; @@ -182,7 +182,6 @@ namespace OpenRA.Mods.Common.Traits readonly HashSet addActorPosition = new HashSet(); readonly HashSet removeActorPosition = new HashSet(); readonly Predicate actorShouldBeRemoved; - readonly HashSet updatedCells = new HashSet(); public WDist LargestActorRadius { get; private set; } public WDist LargestBlockingActorRadius { get; private set; } @@ -372,7 +371,8 @@ namespace OpenRA.Mods.Common.Traits foreach (var t in triggers) t.Dirty = true; - updatedCells.Add(c.First); + if (CellUpdated != null) + CellUpdated(c.First); } } @@ -394,7 +394,8 @@ namespace OpenRA.Mods.Common.Traits foreach (var t in triggers) t.Dirty = true; - updatedCells.Add(c.First); + if (CellUpdated != null) + CellUpdated(c.First); } } @@ -442,15 +443,6 @@ namespace OpenRA.Mods.Common.Traits foreach (var t in proximityTriggers) t.Value.Tick(this); - - self.World.AddFrameEndTask(s => - { - if (CellsUpdated != null) - { - CellsUpdated(updatedCells); - updatedCells.Clear(); - } - }); } public int AddCellTrigger(CPos[] cells, Action onEntry, Action onExit) diff --git a/OpenRA.Mods.Common/Traits/World/Locomotor.cs b/OpenRA.Mods.Common/Traits/World/Locomotor.cs index 61b9f7005c..e0b3b78533 100644 --- a/OpenRA.Mods.Common/Traits/World/Locomotor.cs +++ b/OpenRA.Mods.Common/Traits/World/Locomotor.cs @@ -29,12 +29,13 @@ namespace OpenRA.Mods.Common.Traits } [Flags] - public enum CellBlocking : byte + public enum CellFlag : byte { - Empty = 0, + HasFreeSpace = 0, HasActor = 1, - FreeSubCell = 2, - Crushable = 4 + HasMovingActor = 2, + HasCrushableActor = 4, + HasTemporaryBlocker = 8 } public static class LocomoterExts @@ -45,10 +46,10 @@ namespace OpenRA.Mods.Common.Traits return (c & cellCondition) == cellCondition; } - public static bool HasCellBlocking(this CellBlocking c, CellBlocking cellBlocking) + public static bool HasCellFlag(this CellFlag c, CellFlag cellFlag) { // PERF: Enum.HasFlag is slower and requires allocations. - return (c & cellBlocking) == cellBlocking; + return (c & cellFlag) == cellFlag; } public static bool HasMovementType(this MovementType m, MovementType movementType) @@ -203,35 +204,25 @@ namespace OpenRA.Mods.Common.Traits { struct CellCache { - public readonly CellBlocking CellBlocking; - public readonly short Cost; public readonly LongBitSet Blocking; + public readonly LongBitSet Crushable; + public readonly CellFlag CellFlag; - public CellCache(short cost, LongBitSet blocking, CellBlocking cellBlocking) + public CellCache(LongBitSet blocking, CellFlag cellFlag, LongBitSet crushable = default(LongBitSet)) { - Cost = cost; Blocking = blocking; - CellBlocking = cellBlocking; - } - - public CellCache WithCost(short cost) - { - return new CellCache(cost, Blocking, CellBlocking); - } - - public CellCache WithBlocking(LongBitSet blocking, CellBlocking cellBlocking) - { - return new CellCache(Cost, blocking, cellBlocking); + Crushable = crushable; + CellFlag = cellFlag; } } public readonly LocomotorInfo Info; - CellLayer pathabilityCache; - LongBitSet allPlayerMask = default(LongBitSet); + CellLayer cellsCost; + CellLayer blockingCache; LocomotorInfo.TerrainInfo[] terrainInfos; World world; - readonly HashSet updatedTerrainCells = new HashSet(); + readonly HashSet dirtyCells = new HashSet(); IActorMap actorMap; bool sharesCell; @@ -247,7 +238,7 @@ namespace OpenRA.Mods.Common.Traits if (!world.Map.Contains(cell)) return short.MaxValue; - return pathabilityCache[cell].Cost; + return cellsCost[cell]; } public short MovementCostToEnterCell(Actor actor, CPos destNode, Actor ignoreActor, CellConditions check) @@ -255,51 +246,51 @@ namespace OpenRA.Mods.Common.Traits if (!world.Map.Contains(destNode)) return short.MaxValue; - var cellCache = pathabilityCache[destNode]; + var cellCost = cellsCost[destNode]; - if (cellCache.Cost == short.MaxValue || - !CanMoveFreelyInto(actor, ignoreActor, destNode, check, cellCache)) + if (cellCost == short.MaxValue || + !CanMoveFreelyInto(actor, destNode, ignoreActor, check)) return short.MaxValue; - return cellCache.Cost; + return cellCost; } // Determines whether the actor is blocked by other Actors public bool CanMoveFreelyInto(Actor actor, CPos cell, Actor ignoreActor, CellConditions check) { - return CanMoveFreelyInto(actor, ignoreActor, cell, check, pathabilityCache[cell]); - } - - bool CanMoveFreelyInto(Actor actor, Actor ignoreActor, CPos cell, CellConditions check, CellCache cellCache) - { - var cellBlocking = cellCache.CellBlocking; - var blocking = cellCache.Blocking; + var cellCache = GetCache(cell); + var cellFlag = cellCache.CellFlag; if (!check.HasCellCondition(CellConditions.TransientActors)) return true; + // No actor in the cell or free SubCell. + if (cellFlag == CellFlag.HasFreeSpace) + return true; + // If actor is null we're just checking what would happen theoretically. // In such a scenario - we'll just assume any other actor in the cell will block us by default. // If we have a real actor, we can then perform the extra checks that allow us to avoid being blocked. if (actor == null) - return true; - - // No actor in cell - if (cellBlocking == CellBlocking.Empty) - return true; - - // We are blocked - if (ignoreActor == null && blocking.Overlaps(actor.Owner.PlayerMask)) return false; - if (cellBlocking.HasCellBlocking(CellBlocking.Crushable)) + // All actors that may be in the cell can be crushed. + if (cellCache.Crushable.Overlaps(actor.Owner.PlayerMask)) return true; - if (sharesCell && cellBlocking.HasCellBlocking(CellBlocking.FreeSubCell)) - return true; + // Cache doesn't account for ignored actors - these must use the slow path + if (ignoreActor == null) + { + // We are blocked by another actor in the cell. + if (cellCache.Blocking.Overlaps(actor.Owner.PlayerMask)) + return false; + + if (check == CellConditions.BlockedByMovers && cellFlag < CellFlag.HasCrushableActor) + return false; + } foreach (var otherActor in world.ActorMap.GetActorsAt(cell)) - if (IsBlockedBy(actor, otherActor, ignoreActor, check)) + if (IsBlockedBy(actor, otherActor, ignoreActor, check, cellFlag)) return false; return true; @@ -307,12 +298,13 @@ namespace OpenRA.Mods.Common.Traits public SubCell GetAvailableSubCell(Actor self, CPos cell, SubCell preferredSubCell = SubCell.Any, Actor ignoreActor = null, CellConditions check = CellConditions.All) { - if (MovementCostForCell(cell) == short.MaxValue) + var cost = cellsCost[cell]; + if (cost == short.MaxValue) return SubCell.Invalid; if (check.HasCellCondition(CellConditions.TransientActors)) { - Func checkTransient = otherActor => IsBlockedBy(self, otherActor, ignoreActor, check); + Func checkTransient = otherActor => IsBlockedBy(self, otherActor, ignoreActor, check, blockingCache[cell].CellFlag); if (!sharesCell) return world.ActorMap.AnyActorsAt(cell, SubCell.FullCell, checkTransient) ? SubCell.Invalid : SubCell.FullCell; @@ -326,19 +318,18 @@ namespace OpenRA.Mods.Common.Traits return world.ActorMap.FreeSubCell(cell, preferredSubCell); } - bool IsBlockedBy(Actor self, Actor otherActor, Actor ignoreActor, CellConditions check) + bool IsBlockedBy(Actor self, Actor otherActor, Actor ignoreActor, CellConditions check, CellFlag cellFlag) { if (otherActor == ignoreActor) return false; // If the check allows: we are not blocked by allied units moving in our direction. - if (!check.HasCellCondition(CellConditions.BlockedByMovers) && + if (!check.HasCellCondition(CellConditions.BlockedByMovers) && cellFlag.HasCellFlag(CellFlag.HasMovingActor) && self.Owner.Stances[otherActor.Owner] == Stance.Ally && IsMovingInMyDirection(self, otherActor)) return false; - // PERF: Only perform ITemporaryBlocker trait look-up if mod/map rules contain any actors that are temporary blockers - if (self.World.RulesContainTemporaryBlocker) + if (cellFlag.HasCellFlag(CellFlag.HasTemporaryBlocker)) { // If there is a temporary blocker in our path, but we can remove it, we are not blocked. var temporaryBlocker = otherActor.TraitOrDefault(); @@ -346,6 +337,9 @@ namespace OpenRA.Mods.Common.Traits return false; } + if (!cellFlag.HasCellFlag(CellFlag.HasCrushableActor)) + return true; + // If we cannot crush the other actor in our way, we are blocked. if (Info.Crushes.IsEmpty) return true; @@ -382,29 +376,34 @@ namespace OpenRA.Mods.Common.Traits world = w; var map = w.Map; actorMap = w.ActorMap; - actorMap.CellsUpdated += CellsUpdated; - pathabilityCache = new CellLayer(map); + actorMap.CellUpdated += CellUpdated; + blockingCache = new CellLayer(map); + cellsCost = new CellLayer(map); + terrainInfos = Info.TilesetTerrainInfo[map.Rules.TileSet]; - allPlayerMask = world.AllPlayerMask; - foreach (var cell in map.AllCells) UpdateCellCost(cell); - map.CustomTerrain.CellEntryChanged += MapCellEntryChanged; - map.Tiles.CellEntryChanged += MapCellEntryChanged; + map.CustomTerrain.CellEntryChanged += UpdateCellCost; + map.Tiles.CellEntryChanged += UpdateCellCost; } - void CellsUpdated(IEnumerable cells) + CellCache GetCache(CPos cell) { - foreach (var cell in cells) + if (dirtyCells.Contains(cell)) + { UpdateCellBlocking(cell); + dirtyCells.Remove(cell); + } - foreach (var cell in updatedTerrainCells) - UpdateCellCost(cell); + return blockingCache[cell]; + } - updatedTerrainCells.Clear(); + void CellUpdated(CPos cell) + { + dirtyCells.Add(cell); } void UpdateCellCost(CPos cell) @@ -418,66 +417,66 @@ namespace OpenRA.Mods.Common.Traits if (index != byte.MaxValue) cost = terrainInfos[index].Cost; - var current = pathabilityCache[cell]; - pathabilityCache[cell] = current.WithCost(cost); + cellsCost[cell] = cost; } void UpdateCellBlocking(CPos cell) { using (new PerfSample("locomotor_cache")) { - var current = pathabilityCache[cell]; - var actors = actorMap.GetActorsAt(cell); if (!actors.Any()) { - pathabilityCache[cell] = current.WithBlocking(default(LongBitSet), CellBlocking.Empty); + blockingCache[cell] = new CellCache(default(LongBitSet), CellFlag.HasFreeSpace); return; } - var cellBlocking = CellBlocking.HasActor; if (sharesCell && actorMap.HasFreeSubCell(cell)) { - pathabilityCache[cell] = current.WithBlocking(default(LongBitSet), cellBlocking | CellBlocking.FreeSubCell); + blockingCache[cell] = new CellCache(default(LongBitSet), CellFlag.HasFreeSpace); return; } - var cellBits = default(LongBitSet); + var cellFlag = CellFlag.HasActor; + var cellBlockedPlayers = default(LongBitSet); + var cellCrushablePlayers = world.AllPlayersMask; foreach (var actor in actors) { - var actorBits = default(LongBitSet); + var actorBlocksPlayers = world.AllPlayersMask; var crushables = actor.TraitsImplementing(); var mobile = actor.OccupiesSpace as Mobile; - var isMoving = mobile != null && mobile.CurrentMovementTypes.HasFlag(MovementType.Horizontal); + var isMoving = mobile != null && mobile.CurrentMovementTypes.HasMovementType(MovementType.Horizontal); if (crushables.Any()) { - LongBitSet crushingBits; + cellFlag |= CellFlag.HasCrushableActor; foreach (var crushable in crushables) - if (crushable.TryCalculatePlayerBlocking(actor, Info.Crushes, out crushingBits)) - { - actorBits = actorBits.Union(crushingBits); - cellBlocking |= CellBlocking.Crushable; - } - - if (isMoving) - actorBits = actorBits.Except(actor.Owner.AllyMask); + cellCrushablePlayers = cellCrushablePlayers.Intersect(crushable.CrushableBy(actor, Info.Crushes)); } else - actorBits = isMoving ? actor.Owner.EnemyMask : allPlayerMask; + cellCrushablePlayers = world.NoPlayersMask; - cellBits = cellBits.Union(actorBits); + if (isMoving) + { + actorBlocksPlayers = actorBlocksPlayers.Except(actor.Owner.AlliedPlayersMask); + cellFlag |= CellFlag.HasMovingActor; + } + + // PERF: Only perform ITemporaryBlocker trait look-up if mod/map rules contain any actors that are temporary blockers + if (world.RulesContainTemporaryBlocker) + { + // If there is a temporary blocker in this cell. + if (actor.TraitOrDefault() != null) + cellFlag |= CellFlag.HasTemporaryBlocker; + } + + cellBlockedPlayers = cellBlockedPlayers.Union(actorBlocksPlayers); } - pathabilityCache[cell] = current.WithBlocking(cellBits, cellBlocking); + blockingCache[cell] = new CellCache(cellBlockedPlayers, cellFlag, cellCrushablePlayers); } } - - void MapCellEntryChanged(CPos cell) - { - updatedTerrainCells.Add(cell); - } } } diff --git a/OpenRA.Mods.Common/TraitsInterfaces.cs b/OpenRA.Mods.Common/TraitsInterfaces.cs index 78a99c65fa..f771b93ee1 100644 --- a/OpenRA.Mods.Common/TraitsInterfaces.cs +++ b/OpenRA.Mods.Common/TraitsInterfaces.cs @@ -90,7 +90,7 @@ namespace OpenRA.Mods.Common.Traits public interface ICrushable { bool CrushableBy(Actor self, Actor crusher, BitSet crushClasses); - bool TryCalculatePlayerBlocking(Actor self, BitSet crushClasses, out LongBitSet blocking); + LongBitSet CrushableBy(Actor self, BitSet crushClasses); } [RequireExplicitImplementation]