1
0
forked from 0ad/0ad

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:
Nicolas Auvray 2017-07-14 10:09:32 +00:00
parent 812750ddd7
commit d56692d9e1
6 changed files with 101 additions and 63 deletions

View File

@ -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

View File

@ -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;

View File

@ -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:

View File

@ -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);

View File

@ -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.

View File

@ -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);
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();
}
};