Index: ps/trunk/source/simulation2/components/CCmpAIManager.cpp =================================================================== --- ps/trunk/source/simulation2/components/CCmpAIManager.cpp +++ ps/trunk/source/simulation2/components/CCmpAIManager.cpp @@ -1114,7 +1114,7 @@ CmpPtr cmpPathfinder(GetSystemEntity()); if (cmpPathfinder) { - const GridUpdateInformation& dirtinessInformations = cmpPathfinder->GetDirtinessData(); + const GridUpdateInformation& dirtinessInformations = cmpPathfinder->GetAIPathfinderDirtinessInformation(); if (dirtinessInformations.dirty || m_JustDeserialized) { @@ -1127,6 +1127,8 @@ dirtinessInformations.globallyDirty, dirtinessInformations.dirtinessGrid, m_JustDeserialized, nonPathfindingPassClassMasks, pathfindingPassClassMasks); } + + cmpPathfinder->FlushAIPathfinderDirtinessInformation(); } // Update the territory data Index: ps/trunk/source/simulation2/components/CCmpObstructionManager.cpp =================================================================== --- ps/trunk/source/simulation2/components/CCmpObstructionManager.cpp +++ ps/trunk/source/simulation2/components/CCmpObstructionManager.cpp @@ -160,7 +160,6 @@ m_UpdateInformations.dirty = true; m_UpdateInformations.globallyDirty = true; - m_UpdateInformations.globalRecompute = true; m_PassabilityCircular = false; @@ -501,11 +500,8 @@ 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 @@ { m_UpdateInformations.dirty = true; m_UpdateInformations.globallyDirty = true; - m_UpdateInformations.globalRecompute = true; m_UpdateInformations.dirtinessGrid.reset(); m_DebugOverlayDirty = true; Index: ps/trunk/source/simulation2/components/CCmpPathfinder.cpp =================================================================== --- ps/trunk/source/simulation2/components/CCmpPathfinder.cpp +++ ps/trunk/source/simulation2/components/CCmpPathfinder.cpp @@ -45,8 +45,7 @@ m_Grid = NULL; m_TerrainOnlyGrid = NULL; - m_ObstructionsDirty.Clean(); - m_PreserveUpdateInformations = false; + FlushAIPathfinderDirtinessInformation(); m_NextAsyncTicket = 1; @@ -175,7 +174,6 @@ case MT_ObstructionMapShapeChanged: m_TerrainDirty = true; UpdateGrid(); - m_PreserveUpdateInformations = true; break; case MT_TurnStart: m_SameTurnMovesCount = 0; @@ -245,10 +243,7 @@ const Grid& CCmpPathfinder::GetPassabilityGrid() { if (!m_Grid) - { UpdateGrid(); - m_PreserveUpdateInformations = true; - } return *m_Grid; } @@ -449,11 +444,6 @@ 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 @@ SAFE_DELETE(m_TerrainOnlyGrid); m_TerrainOnlyGrid = new Grid(m_MapSize * Pathfinding::NAVCELLS_PER_TILE, m_MapSize * Pathfinding::NAVCELLS_PER_TILE); - m_ObstructionsDirty = { true, true, true, Grid(m_MapSize * Pathfinding::NAVCELLS_PER_TILE, m_MapSize * Pathfinding::NAVCELLS_PER_TILE) }; + m_DirtinessInformation = { true, true, Grid(m_MapSize * Pathfinding::NAVCELLS_PER_TILE, m_MapSize * Pathfinding::NAVCELLS_PER_TILE) }; + m_AIPathfinderDirtinessInformation = m_DirtinessInformation; m_TerrainDirty = true; } + // 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(m_MapSize * Pathfinding::NAVCELLS_PER_TILE, m_MapSize * Pathfinding::NAVCELLS_PER_TILE)); +#endif + CmpPtr cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY); - cmpObstructionManager->UpdateInformations(m_ObstructionsDirty); + cmpObstructionManager->UpdateInformations(m_DirtinessInformation); - if (!m_ObstructionsDirty.dirty && !m_TerrainDirty) + if (!m_DirtinessInformation.dirty && !m_TerrainDirty) return; // If the terrain has changed, recompute m_Grid @@ -493,39 +491,38 @@ *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 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 @@ TerrainUpdateHelper(false); } -void CCmpPathfinder::TerrainUpdateHelper(bool expandPassability) +void CCmpPathfinder::TerrainUpdateHelper(bool expandPassability/* = true */) { PROFILE3("TerrainUpdateHelper"); @@ -663,11 +660,6 @@ } } -const GridUpdateInformation& CCmpPathfinder::GetDirtinessData() const -{ - return m_ObstructionsDirty; -} - ////////////////////////////////////////////////////////// // Async path requests: Index: ps/trunk/source/simulation2/components/CCmpPathfinder_Common.h =================================================================== --- ps/trunk/source/simulation2/components/CCmpPathfinder_Common.h +++ ps/trunk/source/simulation2/components/CCmpPathfinder_Common.h @@ -151,12 +151,12 @@ Grid* m_Grid; // terrain/passability information Grid* 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 @@ virtual const Grid& GetPassabilityGrid(); - virtual const GridUpdateInformation& GetDirtinessData() const; + virtual const GridUpdateInformation& GetAIPathfinderDirtinessInformation() const + { + return m_AIPathfinderDirtinessInformation; + } + + virtual void FlushAIPathfinderDirtinessInformation() + { + m_AIPathfinderDirtinessInformation.Clean(); + } virtual Grid ComputeShoreGrid(bool expandOnWater = false); Index: ps/trunk/source/simulation2/components/ICmpPathfinder.h =================================================================== --- ps/trunk/source/simulation2/components/ICmpPathfinder.h +++ ps/trunk/source/simulation2/components/ICmpPathfinder.h @@ -78,10 +78,10 @@ virtual const Grid& 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. Index: ps/trunk/source/simulation2/helpers/Grid.h =================================================================== --- ps/trunk/source/simulation2/helpers/Grid.h +++ ps/trunk/source/simulation2/helpers/Grid.h @@ -90,6 +90,19 @@ 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 @@ 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 @@ return m_Data[j*m_W + i]; } + template + bool compare_sizes(const Grid* 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 @@ }; /** - * 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 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 @@ { dirty = false; globallyDirty = false; - globalRecompute = false; dirtinessGrid.reset(); } };