Rewrite the pathfinder dirtiness information system.
Remove all hacks while keeping most optimizations in memory management. This fixes incomplete grid updates that could cause OOS on rejoin, fixes #4596. Tested by: elexis, ffffffff Reviewers: leper, wraitii Differential Revision: https://code.wildfiregames.com/D675 This was SVN commit r19916.
This commit is contained in:
parent
812750ddd7
commit
d56692d9e1
@ -1114,7 +1114,7 @@ public:
|
||||
CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity());
|
||||
if (cmpPathfinder)
|
||||
{
|
||||
const GridUpdateInformation& dirtinessInformations = cmpPathfinder->GetDirtinessData();
|
||||
const GridUpdateInformation& dirtinessInformations = cmpPathfinder->GetAIPathfinderDirtinessInformation();
|
||||
|
||||
if (dirtinessInformations.dirty || m_JustDeserialized)
|
||||
{
|
||||
@ -1127,6 +1127,8 @@ public:
|
||||
dirtinessInformations.globallyDirty, dirtinessInformations.dirtinessGrid, m_JustDeserialized,
|
||||
nonPathfindingPassClassMasks, pathfindingPassClassMasks);
|
||||
}
|
||||
|
||||
cmpPathfinder->FlushAIPathfinderDirtinessInformation();
|
||||
}
|
||||
|
||||
// Update the territory data
|
||||
|
@ -160,7 +160,6 @@ public:
|
||||
|
||||
m_UpdateInformations.dirty = true;
|
||||
m_UpdateInformations.globallyDirty = true;
|
||||
m_UpdateInformations.globalRecompute = true;
|
||||
|
||||
m_PassabilityCircular = false;
|
||||
|
||||
@ -501,11 +500,8 @@ public:
|
||||
|
||||
virtual void UpdateInformations(GridUpdateInformation& informations)
|
||||
{
|
||||
// If the pathfinder wants to perform a full update, don't change that.
|
||||
if (m_UpdateInformations.dirty && !informations.globalRecompute)
|
||||
informations.swap(m_UpdateInformations);
|
||||
|
||||
m_UpdateInformations.Clean();
|
||||
if (!m_UpdateInformations.dirtinessGrid.blank())
|
||||
informations.MergeAndClear(m_UpdateInformations);
|
||||
}
|
||||
|
||||
private:
|
||||
@ -523,7 +519,6 @@ private:
|
||||
{
|
||||
m_UpdateInformations.dirty = true;
|
||||
m_UpdateInformations.globallyDirty = true;
|
||||
m_UpdateInformations.globalRecompute = true;
|
||||
m_UpdateInformations.dirtinessGrid.reset();
|
||||
|
||||
m_DebugOverlayDirty = true;
|
||||
|
@ -45,8 +45,7 @@ void CCmpPathfinder::Init(const CParamNode& UNUSED(paramNode))
|
||||
m_Grid = NULL;
|
||||
m_TerrainOnlyGrid = NULL;
|
||||
|
||||
m_ObstructionsDirty.Clean();
|
||||
m_PreserveUpdateInformations = false;
|
||||
FlushAIPathfinderDirtinessInformation();
|
||||
|
||||
m_NextAsyncTicket = 1;
|
||||
|
||||
@ -175,7 +174,6 @@ void CCmpPathfinder::HandleMessage(const CMessage& msg, bool UNUSED(global))
|
||||
case MT_ObstructionMapShapeChanged:
|
||||
m_TerrainDirty = true;
|
||||
UpdateGrid();
|
||||
m_PreserveUpdateInformations = true;
|
||||
break;
|
||||
case MT_TurnStart:
|
||||
m_SameTurnMovesCount = 0;
|
||||
@ -245,10 +243,7 @@ const PathfinderPassability* CCmpPathfinder::GetPassabilityFromMask(pass_class_t
|
||||
const Grid<NavcellData>& CCmpPathfinder::GetPassabilityGrid()
|
||||
{
|
||||
if (!m_Grid)
|
||||
{
|
||||
UpdateGrid();
|
||||
m_PreserveUpdateInformations = true;
|
||||
}
|
||||
|
||||
return *m_Grid;
|
||||
}
|
||||
@ -449,11 +444,6 @@ void CCmpPathfinder::UpdateGrid()
|
||||
if (!cmpTerrain)
|
||||
return; // error
|
||||
|
||||
if (!m_PreserveUpdateInformations)
|
||||
m_ObstructionsDirty.Clean();
|
||||
else
|
||||
m_PreserveUpdateInformations = false; // Next time will be a regular update
|
||||
|
||||
u16 terrainSize = cmpTerrain->GetTilesPerSide();
|
||||
if (terrainSize == 0)
|
||||
return;
|
||||
@ -473,15 +463,23 @@ void CCmpPathfinder::UpdateGrid()
|
||||
SAFE_DELETE(m_TerrainOnlyGrid);
|
||||
m_TerrainOnlyGrid = new Grid<NavcellData>(m_MapSize * Pathfinding::NAVCELLS_PER_TILE, m_MapSize * Pathfinding::NAVCELLS_PER_TILE);
|
||||
|
||||
m_ObstructionsDirty = { true, true, true, Grid<u8>(m_MapSize * Pathfinding::NAVCELLS_PER_TILE, m_MapSize * Pathfinding::NAVCELLS_PER_TILE) };
|
||||
m_DirtinessInformation = { true, true, Grid<u8>(m_MapSize * Pathfinding::NAVCELLS_PER_TILE, m_MapSize * Pathfinding::NAVCELLS_PER_TILE) };
|
||||
m_AIPathfinderDirtinessInformation = m_DirtinessInformation;
|
||||
|
||||
m_TerrainDirty = true;
|
||||
}
|
||||
|
||||
CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY);
|
||||
cmpObstructionManager->UpdateInformations(m_ObstructionsDirty);
|
||||
// The grid should be properly initialized and clean. Checking the latter is expensive so do it only for debugging.
|
||||
#ifdef NDEBUG
|
||||
ENSURE(m_DirtinessInformation.dirtinessGrid.compare_sizes(m_Grid));
|
||||
#else
|
||||
ENSURE(m_DirtinessInformation.dirtinessGrid == Grid<u8>(m_MapSize * Pathfinding::NAVCELLS_PER_TILE, m_MapSize * Pathfinding::NAVCELLS_PER_TILE));
|
||||
#endif
|
||||
|
||||
if (!m_ObstructionsDirty.dirty && !m_TerrainDirty)
|
||||
CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY);
|
||||
cmpObstructionManager->UpdateInformations(m_DirtinessInformation);
|
||||
|
||||
if (!m_DirtinessInformation.dirty && !m_TerrainDirty)
|
||||
return;
|
||||
|
||||
// If the terrain has changed, recompute m_Grid
|
||||
@ -493,39 +491,38 @@ void CCmpPathfinder::UpdateGrid()
|
||||
*m_Grid = *m_TerrainOnlyGrid;
|
||||
|
||||
m_TerrainDirty = false;
|
||||
m_ObstructionsDirty.globalRecompute = true;
|
||||
m_ObstructionsDirty.globallyDirty = true;
|
||||
m_DirtinessInformation.globallyDirty = true;
|
||||
}
|
||||
else if (m_ObstructionsDirty.globalRecompute)
|
||||
else if (m_DirtinessInformation.globallyDirty)
|
||||
{
|
||||
ENSURE(m_Grid->m_W == m_TerrainOnlyGrid->m_W && m_Grid->m_H == m_TerrainOnlyGrid->m_H);
|
||||
ENSURE(m_Grid->compare_sizes(m_TerrainOnlyGrid));
|
||||
memcpy(m_Grid->m_Data, m_TerrainOnlyGrid->m_Data, (m_Grid->m_W)*(m_Grid->m_H)*sizeof(NavcellData));
|
||||
|
||||
m_ObstructionsDirty.globallyDirty = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
ENSURE(m_Grid->m_W == m_ObstructionsDirty.dirtinessGrid.m_W && m_Grid->m_H == m_ObstructionsDirty.dirtinessGrid.m_H);
|
||||
ENSURE(m_Grid->m_W == m_TerrainOnlyGrid->m_W && m_Grid->m_H == m_TerrainOnlyGrid->m_H);
|
||||
ENSURE(m_Grid->compare_sizes(m_TerrainOnlyGrid));
|
||||
|
||||
for (u16 j = 0; j < m_ObstructionsDirty.dirtinessGrid.m_H; ++j)
|
||||
for (u16 i = 0; i < m_ObstructionsDirty.dirtinessGrid.m_W; ++i)
|
||||
if (m_ObstructionsDirty.dirtinessGrid.get(i, j) == 1)
|
||||
for (u16 j = 0; j < m_DirtinessInformation.dirtinessGrid.m_H; ++j)
|
||||
for (u16 i = 0; i < m_DirtinessInformation.dirtinessGrid.m_W; ++i)
|
||||
if (m_DirtinessInformation.dirtinessGrid.get(i, j) == 1)
|
||||
m_Grid->set(i, j, m_TerrainOnlyGrid->get(i, j));
|
||||
}
|
||||
|
||||
// Add obstructions onto the grid
|
||||
cmpObstructionManager->Rasterize(*m_Grid, m_PassClasses, m_ObstructionsDirty.globalRecompute);
|
||||
cmpObstructionManager->Rasterize(*m_Grid, m_PassClasses, m_DirtinessInformation.globallyDirty);
|
||||
|
||||
// Update the long-range pathfinder
|
||||
if (m_ObstructionsDirty.globallyDirty)
|
||||
if (m_DirtinessInformation.globallyDirty)
|
||||
{
|
||||
std::map<std::string, pass_class_t> nonPathfindingPassClasses, pathfindingPassClasses;
|
||||
GetPassabilityClasses(nonPathfindingPassClasses, pathfindingPassClasses);
|
||||
m_LongPathfinder.Reload(m_Grid, nonPathfindingPassClasses, pathfindingPassClasses);
|
||||
}
|
||||
else
|
||||
m_LongPathfinder.Update(m_Grid, m_ObstructionsDirty.dirtinessGrid);
|
||||
m_LongPathfinder.Update(m_Grid, m_DirtinessInformation.dirtinessGrid);
|
||||
|
||||
// Remember the necessary updates that the AI pathfinder will have to perform as well
|
||||
m_AIPathfinderDirtinessInformation.MergeAndClear(m_DirtinessInformation);
|
||||
}
|
||||
|
||||
void CCmpPathfinder::MinimalTerrainUpdate()
|
||||
@ -533,7 +530,7 @@ void CCmpPathfinder::MinimalTerrainUpdate()
|
||||
TerrainUpdateHelper(false);
|
||||
}
|
||||
|
||||
void CCmpPathfinder::TerrainUpdateHelper(bool expandPassability)
|
||||
void CCmpPathfinder::TerrainUpdateHelper(bool expandPassability/* = true */)
|
||||
{
|
||||
PROFILE3("TerrainUpdateHelper");
|
||||
|
||||
@ -663,11 +660,6 @@ void CCmpPathfinder::TerrainUpdateHelper(bool expandPassability)
|
||||
}
|
||||
}
|
||||
|
||||
const GridUpdateInformation& CCmpPathfinder::GetDirtinessData() const
|
||||
{
|
||||
return m_ObstructionsDirty;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
|
||||
// Async path requests:
|
||||
|
@ -151,12 +151,12 @@ public:
|
||||
Grid<NavcellData>* m_Grid; // terrain/passability information
|
||||
Grid<NavcellData>* m_TerrainOnlyGrid; // same as m_Grid, but only with terrain, to avoid some recomputations
|
||||
|
||||
// Update data, used for clever updates and then stored for the AI manager
|
||||
GridUpdateInformation m_ObstructionsDirty;
|
||||
// Keep clever updates in memory to avoid memory fragmentation from the grid.
|
||||
// This should be used only in UpdateGrid(), there is no guarantee the data is properly initialized anywhere else.
|
||||
GridUpdateInformation m_DirtinessInformation;
|
||||
// The data from clever updates is stored for the AI manager
|
||||
GridUpdateInformation m_AIPathfinderDirtinessInformation;
|
||||
bool m_TerrainDirty;
|
||||
// When other components request the passability grid and trigger an update,
|
||||
// the following regular update should not clean the dirtiness state.
|
||||
bool m_PreserveUpdateInformations;
|
||||
|
||||
// Interface to the long-range pathfinder.
|
||||
LongPathfinder m_LongPathfinder;
|
||||
@ -233,7 +233,15 @@ public:
|
||||
|
||||
virtual const Grid<NavcellData>& GetPassabilityGrid();
|
||||
|
||||
virtual const GridUpdateInformation& GetDirtinessData() const;
|
||||
virtual const GridUpdateInformation& GetAIPathfinderDirtinessInformation() const
|
||||
{
|
||||
return m_AIPathfinderDirtinessInformation;
|
||||
}
|
||||
|
||||
virtual void FlushAIPathfinderDirtinessInformation()
|
||||
{
|
||||
m_AIPathfinderDirtinessInformation.Clean();
|
||||
}
|
||||
|
||||
virtual Grid<u16> ComputeShoreGrid(bool expandOnWater = false);
|
||||
|
||||
|
@ -78,10 +78,10 @@ public:
|
||||
virtual const Grid<NavcellData>& GetPassabilityGrid() = 0;
|
||||
|
||||
/**
|
||||
* Passes the lazily-stored dirtiness data collected from
|
||||
* the obstruction manager during the previous grid update.
|
||||
* Get the accumulated dirtiness information since the last time the AI accessed and flushed it.
|
||||
*/
|
||||
virtual const GridUpdateInformation& GetDirtinessData() const = 0;
|
||||
virtual const GridUpdateInformation& GetAIPathfinderDirtinessInformation() const = 0;
|
||||
virtual void FlushAIPathfinderDirtinessInformation() = 0;
|
||||
|
||||
/**
|
||||
* Get a grid representing the distance to the shore of the terrain tile.
|
||||
|
@ -90,6 +90,19 @@ public:
|
||||
delete[] m_Data;
|
||||
}
|
||||
|
||||
bool operator==(const Grid& g) const
|
||||
{
|
||||
if (!compare_sizes(&g) || m_DirtyID != g.m_DirtyID)
|
||||
return false;
|
||||
|
||||
return memcmp(m_Data, g.m_Data, m_W*m_H*sizeof(T)) == 0;
|
||||
}
|
||||
|
||||
bool blank() const
|
||||
{
|
||||
return m_W == 0 && m_H == 0;
|
||||
}
|
||||
|
||||
void reset()
|
||||
{
|
||||
if (m_Data)
|
||||
@ -106,6 +119,18 @@ public:
|
||||
m_Data[i] += g.m_Data[i];
|
||||
}
|
||||
|
||||
void bitwise_or(const Grid& g)
|
||||
{
|
||||
if (this == &g)
|
||||
return;
|
||||
|
||||
#if GRID_BOUNDS_DEBUG
|
||||
ENSURE(g.m_W == m_W && g.m_H == m_H);
|
||||
#endif
|
||||
for (int i = 0; i < m_H*m_W; ++i)
|
||||
m_Data[i] |= g.m_Data[i];
|
||||
}
|
||||
|
||||
void set(int i, int j, const T& value)
|
||||
{
|
||||
#if GRID_BOUNDS_DEBUG
|
||||
@ -122,6 +147,12 @@ public:
|
||||
return m_Data[j*m_W + i];
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
bool compare_sizes(const Grid<U>* g) const
|
||||
{
|
||||
return m_W == g->m_W && m_H == g->m_H;
|
||||
}
|
||||
|
||||
u16 m_W, m_H;
|
||||
T* m_Data;
|
||||
|
||||
@ -200,24 +231,35 @@ public:
|
||||
};
|
||||
|
||||
/**
|
||||
* Structure holding grid dirtiness informations, for clever updates
|
||||
*
|
||||
* Note: globallyDirty should be used to know which parts of the grid have been changed after an update,
|
||||
* whereas globalRecompute should be used during the update itself, to avoid unnecessary recomputations.
|
||||
* Structure holding grid dirtiness informations, for clever updates.
|
||||
*/
|
||||
struct GridUpdateInformation
|
||||
{
|
||||
bool dirty;
|
||||
bool globallyDirty;
|
||||
bool globalRecompute;
|
||||
Grid<u8> dirtinessGrid;
|
||||
|
||||
void swap(GridUpdateInformation& b)
|
||||
/**
|
||||
* Update the information with additionnal needed updates, then erase the source of additions.
|
||||
* This can usually be optimized through a careful memory management.
|
||||
*/
|
||||
void MergeAndClear(GridUpdateInformation& b)
|
||||
{
|
||||
std::swap(dirty, b.dirty);
|
||||
std::swap(globallyDirty, b.globallyDirty);
|
||||
std::swap(globalRecompute, b.globalRecompute);
|
||||
dirtinessGrid.swap(b.dirtinessGrid);
|
||||
ENSURE(dirtinessGrid.compare_sizes(&b.dirtinessGrid));
|
||||
|
||||
bool wasDirty = dirty;
|
||||
|
||||
dirty |= b.dirty;
|
||||
globallyDirty |= b.globallyDirty;
|
||||
|
||||
// If the current grid is useless, swap it
|
||||
if (!wasDirty)
|
||||
dirtinessGrid.swap(b.dirtinessGrid);
|
||||
// If the new grid isn't used, don't bother updating it
|
||||
else if (dirty && !globallyDirty)
|
||||
dirtinessGrid.bitwise_or(b.dirtinessGrid);
|
||||
|
||||
b.Clean();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -227,7 +269,6 @@ struct GridUpdateInformation
|
||||
{
|
||||
dirty = false;
|
||||
globallyDirty = false;
|
||||
globalRecompute = false;
|
||||
dirtinessGrid.reset();
|
||||
}
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user