Fixes exceptions in AiReachableSystem on round restart.

This commit is contained in:
Pieter-Jan Briers
2020-07-17 11:17:42 +02:00
parent 53a2392edc
commit 11ad47303a

View File

@@ -43,42 +43,42 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Accessible
[Dependency] private IGameTiming _gameTiming;
#pragma warning restore 649
private PathfindingSystem _pathfindingSystem;
/// <summary>
/// Queued region updates
/// </summary>
private HashSet<PathfindingChunk> _queuedUpdates = new HashSet<PathfindingChunk>();
// Oh god the nesting. Shouldn't need to go beyond this
/// <summary>
/// The corresponding regions for each PathfindingChunk.
/// Regions are groups of nodes with the same profile (for pathfinding purposes)
/// i.e. same collision, not-space, same access, etc.
/// </summary>
private Dictionary<GridId, Dictionary<PathfindingChunk, HashSet<PathfindingRegion>>> _regions =
private Dictionary<GridId, Dictionary<PathfindingChunk, HashSet<PathfindingRegion>>> _regions =
new Dictionary<GridId, Dictionary<PathfindingChunk, HashSet<PathfindingRegion>>>();
/// <summary>
/// Minimum time for the cached reachable regions to be stored
/// </summary>
private const float MinCacheTime = 1.0f;
// Cache what regions are accessible from this region. Cached per ReachableArgs
// so multiple entities in the same region with the same args should all be able to share their reachable lookup
// Also need to store when we cached it to know if it's stale if the chunks have updated
// TODO: There's probably a more memory-efficient way to cache this
// Then again, there's likely also a more memory-efficient way to implement regions.
// Also, didn't use a dictionary because there didn't seem to be a clean way to do the lookup
// Plus this way we can check if everything is equal except for vision so an entity with a lower vision radius can use an entity with a higher vision radius' cached result
private Dictionary<ReachableArgs, Dictionary<PathfindingRegion, (TimeSpan CacheTime, HashSet<PathfindingRegion> Regions)>> _cachedAccessible =
private Dictionary<ReachableArgs, Dictionary<PathfindingRegion, (TimeSpan CacheTime, HashSet<PathfindingRegion> Regions)>> _cachedAccessible =
new Dictionary<ReachableArgs, Dictionary<PathfindingRegion, (TimeSpan, HashSet<PathfindingRegion>)>>();
#if DEBUG
private int _runningCacheIdx = 0;
#endif
public override void Initialize()
{
_pathfindingSystem = Get<PathfindingSystem>();
@@ -86,6 +86,12 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Accessible
#if DEBUG
SubscribeLocalEvent<PlayerAttachSystemMessage>(SendDebugMessage);
#endif
_mapmanager.OnGridRemoved += GridRemoved;
}
private void GridRemoved(GridId gridId)
{
_regions.Remove(gridId);
}
public override void Update(float frameTime)
@@ -165,7 +171,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Accessible
targetNode = node;
}
}
return CanAccess(entity, targetNode);
}
@@ -175,7 +181,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Accessible
{
return false;
}
var entityTile = _mapmanager.GetGrid(entity.Transform.GridID).GetTileRef(entity.Transform.GridPosition);
var entityNode = _pathfindingSystem.GetNode(entityTile);
var entityRegion = GetRegion(entityNode);
@@ -207,7 +213,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Accessible
{
return new HashSet<PathfindingRegion>();
}
var cachedArgs = GetCachedArgs(reachableArgs);
(TimeSpan CacheTime, HashSet<PathfindingRegion> Regions) cached;
@@ -239,7 +245,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Accessible
private ReachableArgs GetCachedArgs(ReachableArgs accessibleArgs)
{
ReachableArgs foundArgs = null;
foreach (var (cachedAccessible, _) in _cachedAccessible)
{
if (Equals(cachedAccessible.Access, accessibleArgs.Access) &&
@@ -269,7 +275,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Accessible
_cachedAccessible.Add(accessibleArgs, new Dictionary<PathfindingRegion, (TimeSpan, HashSet<PathfindingRegion>)>());
return false;
}
if (!cachedArgs.TryGetValue(region, out var regionCache))
{
return false;
@@ -287,7 +293,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Accessible
foreach (var accessibleRegion in regionCache.Regions)
{
if (checkedAccess.Contains(accessibleRegion)) continue;
// Any applicable chunk has been invalidated OR one of our neighbors has been invalidated (i.e. new connections)
// TODO: Could look at storing the TimeSpan directly on the region so our neighbor can tell us straight-up
if (accessibleRegion.ParentChunk.LastUpdate > regionCache.CacheTime)
@@ -336,7 +342,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Accessible
{
continue;
}
// Distance is an approximation here so we'll be generous with it
// TODO: Could do better; the fewer nodes the better it is.
if (!neighbor.RegionTraversable(reachableArgs) ||
@@ -345,12 +351,12 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Accessible
closedSet.Add(neighbor);
continue;
}
openSet.Enqueue(neighbor);
accessible.Add(neighbor);
}
}
return (_gameTiming.CurTime, accessible);
}
@@ -370,7 +376,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Accessible
{
var directionNode = node.GetNeighbor(direction);
if (directionNode == null) continue;
var directionRegion = GetRegion(directionNode);
if (directionRegion == null || directionRegion == region) continue;
@@ -403,7 +409,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Accessible
// On the other hand, you might need O(n) lookups on regions for each chunk, though it's probably not too bad with smaller chunk sizes?
// Someone smarter than me will know better
var parentChunk = node.ParentChunk;
// No guarantee the node even has a region yet (if we're doing neighbor lookups)
if (!_regions[parentChunk.GridId].TryGetValue(parentChunk, out var regions))
{
@@ -460,10 +466,10 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Accessible
PathfindingRegion bottomRegion;
// We'll check if our left or down neighbors are already in a region and join them
// Is left node valid to connect to
if (leftNeighbor != null &&
existingRegions.TryGetValue(leftNeighbor, out leftRegion) &&
existingRegions.TryGetValue(leftNeighbor, out leftRegion) &&
!leftRegion.IsDoor)
{
// We'll try and connect the left node's region to the bottom region if they're separate (yay merge)
@@ -483,8 +489,8 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Accessible
}
//Is bottom node valid to connect to
if (bottomNeighbor != null &&
existingRegions.TryGetValue(bottomNeighbor, out bottomRegion) &&
if (bottomNeighbor != null &&
existingRegions.TryGetValue(bottomNeighbor, out bottomRegion) &&
!bottomRegion.IsDoor)
{
bottomRegion.Add(node);
@@ -492,13 +498,13 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Accessible
UpdateRegionEdge(bottomRegion, node);
return bottomRegion;
}
// If we can't join an existing region then we'll make our own
var newRegion = new PathfindingRegion(node, new HashSet<PathfindingNode> {node}, node.AccessReaders.Count > 0);
_regions[parentChunk.GridId][parentChunk].Add(newRegion);
existingRegions.Add(node, newRegion);
UpdateRegionEdge(newRegion, node);
return newRegion;
}
@@ -524,7 +530,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Accessible
UpdateRegionEdge(target, node);
}
}
/// <summary>
/// Generate all of the regions within a chunk
/// </summary>
@@ -564,7 +570,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Accessible
{
continue;
}
chunkRegions.Add(region);
}
}
@@ -587,8 +593,13 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Accessible
var debugResult = new Dictionary<int, Dictionary<int, List<Vector2>>>();
var chunkIdx = 0;
var regionIdx = 0;
foreach (var (_, regions) in _regions[gridId])
if (!_regions.TryGetValue(gridId, out var dict))
{
return;
}
foreach (var (_, regions) in dict)
{
var debugRegions = new Dictionary<int, List<Vector2>>();
debugResult.Add(chunkIdx, debugRegions);
@@ -622,15 +633,15 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Accessible
{
var grid = _mapmanager.GetGrid(gridId);
var debugResult = new Dictionary<int, List<Vector2>>();
foreach (var region in regions)
{
debugResult.Add(_runningCacheIdx, new List<Vector2>());
foreach (var node in region.Nodes)
{
var nodeVector = grid.GridTileToLocal(node.TileRef.GridIndices).ToMapPos(_mapmanager);
debugResult[_runningCacheIdx].Add(nodeVector);
}
@@ -641,4 +652,4 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Accessible
}
#endif
}
}
}