Ticket #930: pathfinder-incremental-grid-update.patch

File pathfinder-incremental-grid-update.patch, 13.8 KB (added by Sylvain Gadrat, 13 years ago)

First try to implement incremental updates, diff from the revision r10023

  • source/simulation2/helpers/Grid.h

     
    1919#define INCLUDED_GRID
    2020
    2121#include <cstring>
     22#include <vector>
    2223
    2324#ifdef NDEBUG
    2425#define GRID_BOUNDS_DEBUG 0
     
    2728#endif
    2829
    2930/**
     31 * Can be used to observe modifications of a Grid
     32 */
     33class GridObserver {
     34public:
     35    typedef std::vector< std::pair<u16, u16> >::const_iterator const_iterator;
     36
     37    // Methods modifying the observer itself
     38    GridObserver() : m_Reseted(false) {
     39        m_Modified.reserve(3000); //FIXME 3000 ? Needs some estimation.
     40    }
     41    void clear() {
     42        m_Modified.clear();
     43        m_Reseted = false;
     44    }
     45
     46    // Methods indicating changes on the observed grid
     47    void modifiy(u16 i, u16 j) {
     48        //TODO Need check to avoid doubles
     49        m_Modified.push_back(std::make_pair(i, j));
     50    }
     51    void reset() {
     52        m_Reseted = true;
     53    }
     54
     55    // Methods to read observed modifications
     56    bool reseted() const {
     57        return m_Reseted;
     58    }
     59    const_iterator begin() const {
     60        return m_Modified.begin();
     61    }
     62    const_iterator end() const {
     63        return m_Modified.end();
     64    }
     65
     66private:
     67    std::vector< std::pair<u16, u16> > m_Modified;
     68    bool m_Reseted;
     69};
     70
     71/**
    3072 * Basic 2D array, intended for storing tile data, plus support for lazy updates
    3173 * by ICmpObstructionManager.
    3274 * @c T must be a POD type that can be initialised with 0s.
     
    3577class Grid
    3678{
    3779public:
    38     Grid() : m_W(0), m_H(0), m_Data(NULL), m_DirtyID(0)
     80    Grid() : m_W(0), m_H(0), m_Data(NULL), m_DirtyID(0), m_Observer(NULL)
    3981    {
    4082    }
    4183
    42     Grid(u16 w, u16 h) : m_W(w), m_H(h), m_Data(NULL), m_DirtyID(0)
     84    Grid(u16 w, u16 h) : m_W(w), m_H(h), m_Data(NULL), m_DirtyID(0), m_Observer(NULL)
    4385    {
    4486        if (m_W || m_H)
    4587            m_Data = new T[m_W * m_H];
     
    65107            }
    66108            else
    67109                m_Data = NULL;
     110            m_Observer = NULL; //TODO determine if we have to keep the old observer (and call m_Observer->reset()), share the observer with g or set it to NULL
    68111        }
    69112        return *this;
    70113    }
     
    76119
    77120    void reset()
    78121    {
    79         if (m_Data)
     122        if (m_Data) {
    80123            memset(m_Data, 0, m_W*m_H*sizeof(T));
     124            if (m_Observer)
     125                m_Observer->reset();
     126        }
    81127    }
    82128
    83129    void set(int i, int j, const T& value)
     
    86132        ENSURE(0 <= i && i < m_W && 0 <= j && j < m_H);
    87133#endif
    88134        m_Data[j*m_W + i] = value;
     135        if (m_Observer)
     136            m_Observer->modifiy(i, j);
    89137    }
    90138
    91139    T& get(int i, int j) const
     
    100148    T* m_Data;
    101149
    102150    size_t m_DirtyID; // if this is < the id maintained by ICmpObstructionManager then it needs to be updated
     151    GridObserver* m_Observer;
    103152};
    104153
    105154/**
  • source/simulation2/components/CCmpPathfinder.cpp

     
    3535#include "simulation2/components/ICmpWaterManager.h"
    3636#include "simulation2/serialization/SerializeTemplates.h"
    3737
     38#include "lib/timer.h"
     39TIMER_ADD_CLIENT(tc_PathfinderUpdateGrid);
     40
    3841// Default cost to move a single tile is a fairly arbitrary number, which should be big
    3942// enough to be precise when multiplied/divided and small enough to never overflow when
    4043// summing the cost of a whole path.
     
    328331
    329332    CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY);
    330333
     334    GridObserver obstructionChanges;
     335    m_ObstructionGrid->m_Observer = &obstructionChanges;
     336    //FIXME Hope for no throw, with throws next calls to m_ObstructionGrid::set() and reset()
     337    //      will try to use the observer which no more exists
    331338    bool obstructionsDirty = cmpObstructionManager->Rasterise(*m_ObstructionGrid);
     339    m_ObstructionGrid->m_Observer = NULL;
    332340
    333341    if (obstructionsDirty && !m_TerrainDirty)
    334342    {
    335343        PROFILE("UpdateGrid obstructions");
     344        TIMER_ACCRUE(tc_PathfinderUpdateGrid);
    336345
    337346        // Obstructions changed - we need to recompute passability
    338347        // Since terrain hasn't changed we only need to update the obstruction bits
     
    343352        // then TILE_OUTOFBOUNDS will change and we can't use this fast path, but
    344353        // currently it'll just set obstructionsDirty and we won't notice
    345354
    346         for (u16 j = 0; j < m_MapSize; ++j)
    347         {
    348             for (u16 i = 0; i < m_MapSize; ++i)
     355        if (obstructionChanges.reseted()) {
     356            for (u16 j = 0; j < m_MapSize; ++j)
    349357            {
     358                for (u16 i = 0; i < m_MapSize; ++i)
     359                {
     360                    TerrainTile& t = m_Grid->get(i, j);
     361
     362                    u8 obstruct = m_ObstructionGrid->get(i, j);
     363
     364                    if (obstruct & ICmpObstructionManager::TILE_OBSTRUCTED_PATHFINDING)
     365                        t |= 1;
     366                    else
     367                        t &= ~1;
     368
     369                    if (obstruct & ICmpObstructionManager::TILE_OBSTRUCTED_FOUNDATION)
     370                        t |= 2;
     371                    else
     372                        t &= ~2;
     373                }
     374            }
     375        }else {
     376            GridObserver::const_iterator it  = obstructionChanges.begin();
     377            GridObserver::const_iterator end = obstructionChanges.end();
     378            while (it != end) {
     379                u16 i = it->first;
     380                u16 j = it->second;
     381
     382                //FIXME ugly copy/paste
    350383                TerrainTile& t = m_Grid->get(i, j);
    351384
    352385                u8 obstruct = m_ObstructionGrid->get(i, j);
     
    360393                    t |= 2;
    361394                else
    362395                    t &= (TerrainTile)~2;
     396
     397                ++it;
    363398            }
    364399        }
    365400
     
    368403    else if (obstructionsDirty || m_TerrainDirty)
    369404    {
    370405        PROFILE("UpdateGrid full");
     406        TIMER_ACCRUE(tc_PathfinderUpdateGrid);
    371407
    372408        // Obstructions or terrain changed - we need to recompute passability
    373409        // TODO: only bother recomputing the region that has actually changed
  • source/simulation2/components/CCmpObstructionManager.cpp

     
    3333#include "ps/Profile.h"
    3434#include "renderer/Scene.h"
    3535
     36#include "lib/timer.h"
     37TIMER_ADD_CLIENT(tc_Rasterize);
     38
    3639// Externally, tags are opaque non-zero positive integers.
    3740// Internally, they are tagged (by shape) indexes into shape lists.
    3841// idx must be non-zero.
     
    6770    ICmpObstructionManager::flags_t flags;
    6871};
    6972
     73template <class S>
     74class RemoveHistory {
     75public:
     76    typedef typename std::map<size_t, S>::const_iterator const_iterator;
     77
     78    RemoveHistory(size_t baseId = 1)
     79    : m_HistoryBase(baseId)
     80    {
     81    }
     82    bool isInHistory(size_t id) const {
     83        return id >= m_HistoryBase;
     84    }
     85    void add(size_t id, S const & e) {
     86        m_RemovedShapes.insert(std::make_pair(id, e));
     87        if (m_RemovedShapes.size() > 10) {
     88            m_HistoryBase = m_RemovedShapes.begin()->first + 1;
     89            m_RemovedShapes.erase(m_RemovedShapes.begin());
     90        }
     91    }
     92    void reset(size_t id) {
     93        m_RemovedShapes.clear();
     94        m_HistoryBase = id;
     95    }
     96    const_iterator end() const {
     97        return m_RemovedShapes.end();
     98    }
     99    const_iterator get(size_t id) const {
     100        return m_RemovedShapes.lower_bound(id);
     101    }
     102
     103private:
     104    std::map<size_t, S> m_RemovedShapes;
     105    size_t m_HistoryBase;
     106};
     107
    70108/**
    71109 * Serialization helper template for UnitShape
    72110 */
     
    149187        m_StaticShapeNext = 1;
    150188
    151189        m_DirtyID = 1; // init to 1 so default-initialised grids are considered dirty
     190        m_StaticRemoved.reset(m_DirtyID);
     191        m_UnitRemoved.reset(m_DirtyID);
    152192
    153193        m_PassabilityCircular = false;
    154194
     
    250290        UnitShape shape = { ent, x, z, r, flags, group };
    251291        u32 id = m_UnitShapeNext++;
    252292        m_UnitShapes[id] = shape;
    253         MakeDirtyUnit(flags);
     293        MakeDirtyUnit(shape, false);
    254294
    255295        m_UnitSubdivision.Add(id, CFixedVector2D(x - r, z - r), CFixedVector2D(x + r, z + r));
    256296
     
    267307        StaticShape shape = { ent, x, z, u, v, w/2, h/2, flags };
    268308        u32 id = m_StaticShapeNext++;
    269309        m_StaticShapes[id] = shape;
    270         MakeDirtyStatic(flags);
     310        MakeDirtyStatic(shape, false);
    271311
    272312        CFixedVector2D center(x, z);
    273313        CFixedVector2D bbHalfSize = Geometry::GetHalfBoundingBox(u, v, CFixedVector2D(w/2, h/2));
     
    302342        if (TAG_IS_UNIT(tag))
    303343        {
    304344            UnitShape& shape = m_UnitShapes[TAG_TO_INDEX(tag)];
     345           
     346            MakeDirtyUnit(shape, true);
    305347
    306348            m_UnitSubdivision.Move(TAG_TO_INDEX(tag),
    307349                CFixedVector2D(shape.x - shape.r, shape.z - shape.r),
     
    311353
    312354            shape.x = x;
    313355            shape.z = z;
    314 
    315             MakeDirtyUnit(shape.flags);
    316356        }
    317357        else
    318358        {
     
    323363
    324364            StaticShape& shape = m_StaticShapes[TAG_TO_INDEX(tag)];
    325365
     366            MakeDirtyStatic(shape, true);
     367
    326368            CFixedVector2D fromBbHalfSize = Geometry::GetHalfBoundingBox(shape.u, shape.v, CFixedVector2D(shape.hw, shape.hh));
    327369            CFixedVector2D toBbHalfSize = Geometry::GetHalfBoundingBox(u, v, CFixedVector2D(shape.hw, shape.hh));
    328370            m_StaticSubdivision.Move(TAG_TO_INDEX(tag),
     
    335377            shape.z = z;
    336378            shape.u = u;
    337379            shape.v = v;
    338 
    339             MakeDirtyStatic(shape.flags);
    340380        }
    341381    }
    342382
     
    378418                CFixedVector2D(shape.x - shape.r, shape.z - shape.r),
    379419                CFixedVector2D(shape.x + shape.r, shape.z + shape.r));
    380420
    381             MakeDirtyUnit(shape.flags);
     421            MakeDirtyUnit(shape, true);
    382422            m_UnitShapes.erase(TAG_TO_INDEX(tag));
    383423        }
    384424        else
     
    389429            CFixedVector2D bbHalfSize = Geometry::GetHalfBoundingBox(shape.u, shape.v, CFixedVector2D(shape.hw, shape.hh));
    390430            m_StaticSubdivision.Remove(TAG_TO_INDEX(tag), center - bbHalfSize, center + bbHalfSize);
    391431
    392             MakeDirtyStatic(shape.flags);
     432            MakeDirtyStatic(shape, true);
    393433            m_StaticShapes.erase(TAG_TO_INDEX(tag));
    394434        }
    395435    }
     
    444484    // if a grid has a lower DirtyID then it needs to be updated.
    445485
    446486    size_t m_DirtyID;
     487    RemoveHistory<StaticShape> m_StaticRemoved;
     488    RemoveHistory<UnitShape> m_UnitRemoved;
    447489
     490
    448491    /**
    449492     * Mark all previous Rasterise()d grids as dirty, and the debug display.
    450493     * Call this when the world bounds have changed.
     
    452495    void MakeDirtyAll()
    453496    {
    454497        ++m_DirtyID;
     498        m_StaticRemoved.reset(m_DirtyID);
     499        m_UnitRemoved.reset(m_DirtyID);
    455500        m_DebugOverlayDirty = true;
    456501    }
    457502
     
    468513     * Mark all previous Rasterise()d grids as dirty, if they depend on this shape.
    469514     * Call this when a static shape has changed.
    470515     */
    471     void MakeDirtyStatic(flags_t flags)
     516    void MakeDirtyStatic(StaticShape const & shape, bool removed)
    472517    {
    473         if (flags & (FLAG_BLOCK_PATHFINDING|FLAG_BLOCK_FOUNDATION))
     518        if (shape.flags & (FLAG_BLOCK_PATHFINDING|FLAG_BLOCK_FOUNDATION)) {
     519            if (removed)
     520                m_StaticRemoved.add(m_DirtyID, shape);
    474521            ++m_DirtyID;
     522        }
    475523
    476524        m_DebugOverlayDirty = true;
    477525    }
     
    480528     * Mark all previous Rasterise()d grids as dirty, if they depend on this shape.
    481529     * Call this when a unit shape has changed.
    482530     */
    483     void MakeDirtyUnit(flags_t flags)
     531    void MakeDirtyUnit(UnitShape const & shape, bool removed)
    484532    {
    485         if (flags & (FLAG_BLOCK_PATHFINDING|FLAG_BLOCK_FOUNDATION))
     533        if (shape.flags & (FLAG_BLOCK_PATHFINDING|FLAG_BLOCK_FOUNDATION)) {
     534            if (removed)
     535                m_UnitRemoved.add(m_DirtyID, shape);
    486536            ++m_DirtyID;
     537        }
    487538
    488539        m_DebugOverlayDirty = true;
    489540    }
     
    709760        return false;
    710761
    711762    PROFILE("Rasterise");
     763    TIMER_ACCRUE(tc_Rasterize);
    712764
    713     grid.m_DirtyID = m_DirtyID;
    714 
    715     // TODO: this is all hopelessly inefficient
    716     // What we should perhaps do is have some kind of quadtree storing Shapes so it's
    717     // quick to invalidate and update small numbers of tiles
    718 
    719     grid.reset();
    720 
    721765    // For tile-based pathfinding:
    722766    // Since we only count tiles whose centers are inside the square,
    723767    // we maybe want to expand the square a bit so we're less likely to think there's
     
    733777    // so we need to expand by at least 1/sqrt(2) of a tile
    734778    entity_pos_t expandFoundation = (entity_pos_t::FromInt(CELL_SIZE) * 3) / 4;
    735779
     780    entity_pos_t maxExpand = std::max(expandPathfinding, expandFoundation);
     781
     782    if (!m_StaticRemoved.isInHistory(grid.m_DirtyID) || !m_UnitRemoved.isInHistory(grid.m_DirtyID)) {
     783        // Some pieces of history are missing, we need to reset the entire grid
     784        grid.reset();
     785    }else {
     786        // We'll try to reset only needed tiles according to history
     787
     788        // Reset tiles under removed static shapes
     789        RemoveHistory<StaticShape>::const_iterator srmit = m_StaticRemoved.get(grid.m_DirtyID);
     790        RemoveHistory<StaticShape>::const_iterator srmend = m_StaticRemoved.end();
     791        while (srmit != srmend) {
     792            CFixedVector2D center(srmit->second.x, srmit->second.z);
     793
     794            if ((srmit->second.flags & FLAG_BLOCK_PATHFINDING) || (srmit->second.flags & FLAG_BLOCK_FOUNDATION))
     795            {
     796                CFixedVector2D halfSize(srmit->second.hw + maxExpand, srmit->second.hh + maxExpand);
     797                CFixedVector2D halfBound = Geometry::GetHalfBoundingBox(srmit->second.u, srmit->second.v, halfSize);
     798
     799                u16 i0, j0, i1, j1;
     800                NearestTile(center.X - halfBound.X, center.Y - halfBound.Y, i0, j0, grid.m_W, grid.m_H);
     801                NearestTile(center.X + halfBound.X, center.Y + halfBound.Y, i1, j1, grid.m_W, grid.m_H);
     802                for (u16 j = j0; j <= j1; ++j)
     803                {
     804                    for (u16 i = i0; i <= i1; ++i)
     805                    {
     806                        entity_pos_t x, z;
     807                        TileCenter(i, j, x, z);
     808                        if (Geometry::PointIsInSquare(CFixedVector2D(x, z) - center, srmit->second.u, srmit->second.v, halfSize))
     809                            grid.set(i, j, 0);
     810                    }
     811                }
     812            }
     813
     814            ++srmit;
     815        }
     816
     817        // Reset tiles under removed unit shapes
     818        RemoveHistory<UnitShape>::const_iterator urmit = m_UnitRemoved.get(grid.m_DirtyID);
     819        RemoveHistory<UnitShape>::const_iterator urmend = m_UnitRemoved.end();
     820        while (urmit != urmend) {
     821            CFixedVector2D center(urmit->second.x, urmit->second.z);
     822
     823            if ((urmit->second.flags & FLAG_BLOCK_PATHFINDING) || (urmit->second.flags & FLAG_BLOCK_FOUNDATION))
     824            {
     825                entity_pos_t r = urmit->second.r + maxExpand;
     826
     827                u16 i0, j0, i1, j1;
     828                NearestTile(center.X - r, center.Y - r, i0, j0, grid.m_W, grid.m_H);
     829                NearestTile(center.X + r, center.Y + r, i1, j1, grid.m_W, grid.m_H);
     830                for (u16 j = j0; j <= j1; ++j)
     831                    for (u16 i = i0; i <= i1; ++i)
     832                        grid.set(i, j, 0);
     833            }
     834
     835            ++urmit;
     836        }
     837    }
     838
     839    grid.m_DirtyID = m_DirtyID;
     840
    736841    for (std::map<u32, StaticShape>::iterator it = m_StaticShapes.begin(); it != m_StaticShapes.end(); ++it)
    737842    {
    738843        CFixedVector2D center(it->second.x, it->second.z);