Ticket #930: pathfinder-incremental-grid-update.patch
File pathfinder-incremental-grid-update.patch, 13.8 KB (added by , 13 years ago) |
---|
-
source/simulation2/helpers/Grid.h
19 19 #define INCLUDED_GRID 20 20 21 21 #include <cstring> 22 #include <vector> 22 23 23 24 #ifdef NDEBUG 24 25 #define GRID_BOUNDS_DEBUG 0 … … 27 28 #endif 28 29 29 30 /** 31 * Can be used to observe modifications of a Grid 32 */ 33 class GridObserver { 34 public: 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 66 private: 67 std::vector< std::pair<u16, u16> > m_Modified; 68 bool m_Reseted; 69 }; 70 71 /** 30 72 * Basic 2D array, intended for storing tile data, plus support for lazy updates 31 73 * by ICmpObstructionManager. 32 74 * @c T must be a POD type that can be initialised with 0s. … … 35 77 class Grid 36 78 { 37 79 public: 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) 39 81 { 40 82 } 41 83 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) 43 85 { 44 86 if (m_W || m_H) 45 87 m_Data = new T[m_W * m_H]; … … 65 107 } 66 108 else 67 109 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 68 111 } 69 112 return *this; 70 113 } … … 76 119 77 120 void reset() 78 121 { 79 if (m_Data) 122 if (m_Data) { 80 123 memset(m_Data, 0, m_W*m_H*sizeof(T)); 124 if (m_Observer) 125 m_Observer->reset(); 126 } 81 127 } 82 128 83 129 void set(int i, int j, const T& value) … … 86 132 ENSURE(0 <= i && i < m_W && 0 <= j && j < m_H); 87 133 #endif 88 134 m_Data[j*m_W + i] = value; 135 if (m_Observer) 136 m_Observer->modifiy(i, j); 89 137 } 90 138 91 139 T& get(int i, int j) const … … 100 148 T* m_Data; 101 149 102 150 size_t m_DirtyID; // if this is < the id maintained by ICmpObstructionManager then it needs to be updated 151 GridObserver* m_Observer; 103 152 }; 104 153 105 154 /** -
source/simulation2/components/CCmpPathfinder.cpp
35 35 #include "simulation2/components/ICmpWaterManager.h" 36 36 #include "simulation2/serialization/SerializeTemplates.h" 37 37 38 #include "lib/timer.h" 39 TIMER_ADD_CLIENT(tc_PathfinderUpdateGrid); 40 38 41 // Default cost to move a single tile is a fairly arbitrary number, which should be big 39 42 // enough to be precise when multiplied/divided and small enough to never overflow when 40 43 // summing the cost of a whole path. … … 328 331 329 332 CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY); 330 333 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 331 338 bool obstructionsDirty = cmpObstructionManager->Rasterise(*m_ObstructionGrid); 339 m_ObstructionGrid->m_Observer = NULL; 332 340 333 341 if (obstructionsDirty && !m_TerrainDirty) 334 342 { 335 343 PROFILE("UpdateGrid obstructions"); 344 TIMER_ACCRUE(tc_PathfinderUpdateGrid); 336 345 337 346 // Obstructions changed - we need to recompute passability 338 347 // Since terrain hasn't changed we only need to update the obstruction bits … … 343 352 // then TILE_OUTOFBOUNDS will change and we can't use this fast path, but 344 353 // currently it'll just set obstructionsDirty and we won't notice 345 354 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) 349 357 { 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 350 383 TerrainTile& t = m_Grid->get(i, j); 351 384 352 385 u8 obstruct = m_ObstructionGrid->get(i, j); … … 360 393 t |= 2; 361 394 else 362 395 t &= (TerrainTile)~2; 396 397 ++it; 363 398 } 364 399 } 365 400 … … 368 403 else if (obstructionsDirty || m_TerrainDirty) 369 404 { 370 405 PROFILE("UpdateGrid full"); 406 TIMER_ACCRUE(tc_PathfinderUpdateGrid); 371 407 372 408 // Obstructions or terrain changed - we need to recompute passability 373 409 // TODO: only bother recomputing the region that has actually changed -
source/simulation2/components/CCmpObstructionManager.cpp
33 33 #include "ps/Profile.h" 34 34 #include "renderer/Scene.h" 35 35 36 #include "lib/timer.h" 37 TIMER_ADD_CLIENT(tc_Rasterize); 38 36 39 // Externally, tags are opaque non-zero positive integers. 37 40 // Internally, they are tagged (by shape) indexes into shape lists. 38 41 // idx must be non-zero. … … 67 70 ICmpObstructionManager::flags_t flags; 68 71 }; 69 72 73 template <class S> 74 class RemoveHistory { 75 public: 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 103 private: 104 std::map<size_t, S> m_RemovedShapes; 105 size_t m_HistoryBase; 106 }; 107 70 108 /** 71 109 * Serialization helper template for UnitShape 72 110 */ … … 149 187 m_StaticShapeNext = 1; 150 188 151 189 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); 152 192 153 193 m_PassabilityCircular = false; 154 194 … … 250 290 UnitShape shape = { ent, x, z, r, flags, group }; 251 291 u32 id = m_UnitShapeNext++; 252 292 m_UnitShapes[id] = shape; 253 MakeDirtyUnit( flags);293 MakeDirtyUnit(shape, false); 254 294 255 295 m_UnitSubdivision.Add(id, CFixedVector2D(x - r, z - r), CFixedVector2D(x + r, z + r)); 256 296 … … 267 307 StaticShape shape = { ent, x, z, u, v, w/2, h/2, flags }; 268 308 u32 id = m_StaticShapeNext++; 269 309 m_StaticShapes[id] = shape; 270 MakeDirtyStatic( flags);310 MakeDirtyStatic(shape, false); 271 311 272 312 CFixedVector2D center(x, z); 273 313 CFixedVector2D bbHalfSize = Geometry::GetHalfBoundingBox(u, v, CFixedVector2D(w/2, h/2)); … … 302 342 if (TAG_IS_UNIT(tag)) 303 343 { 304 344 UnitShape& shape = m_UnitShapes[TAG_TO_INDEX(tag)]; 345 346 MakeDirtyUnit(shape, true); 305 347 306 348 m_UnitSubdivision.Move(TAG_TO_INDEX(tag), 307 349 CFixedVector2D(shape.x - shape.r, shape.z - shape.r), … … 311 353 312 354 shape.x = x; 313 355 shape.z = z; 314 315 MakeDirtyUnit(shape.flags);316 356 } 317 357 else 318 358 { … … 323 363 324 364 StaticShape& shape = m_StaticShapes[TAG_TO_INDEX(tag)]; 325 365 366 MakeDirtyStatic(shape, true); 367 326 368 CFixedVector2D fromBbHalfSize = Geometry::GetHalfBoundingBox(shape.u, shape.v, CFixedVector2D(shape.hw, shape.hh)); 327 369 CFixedVector2D toBbHalfSize = Geometry::GetHalfBoundingBox(u, v, CFixedVector2D(shape.hw, shape.hh)); 328 370 m_StaticSubdivision.Move(TAG_TO_INDEX(tag), … … 335 377 shape.z = z; 336 378 shape.u = u; 337 379 shape.v = v; 338 339 MakeDirtyStatic(shape.flags);340 380 } 341 381 } 342 382 … … 378 418 CFixedVector2D(shape.x - shape.r, shape.z - shape.r), 379 419 CFixedVector2D(shape.x + shape.r, shape.z + shape.r)); 380 420 381 MakeDirtyUnit(shape .flags);421 MakeDirtyUnit(shape, true); 382 422 m_UnitShapes.erase(TAG_TO_INDEX(tag)); 383 423 } 384 424 else … … 389 429 CFixedVector2D bbHalfSize = Geometry::GetHalfBoundingBox(shape.u, shape.v, CFixedVector2D(shape.hw, shape.hh)); 390 430 m_StaticSubdivision.Remove(TAG_TO_INDEX(tag), center - bbHalfSize, center + bbHalfSize); 391 431 392 MakeDirtyStatic(shape .flags);432 MakeDirtyStatic(shape, true); 393 433 m_StaticShapes.erase(TAG_TO_INDEX(tag)); 394 434 } 395 435 } … … 444 484 // if a grid has a lower DirtyID then it needs to be updated. 445 485 446 486 size_t m_DirtyID; 487 RemoveHistory<StaticShape> m_StaticRemoved; 488 RemoveHistory<UnitShape> m_UnitRemoved; 447 489 490 448 491 /** 449 492 * Mark all previous Rasterise()d grids as dirty, and the debug display. 450 493 * Call this when the world bounds have changed. … … 452 495 void MakeDirtyAll() 453 496 { 454 497 ++m_DirtyID; 498 m_StaticRemoved.reset(m_DirtyID); 499 m_UnitRemoved.reset(m_DirtyID); 455 500 m_DebugOverlayDirty = true; 456 501 } 457 502 … … 468 513 * Mark all previous Rasterise()d grids as dirty, if they depend on this shape. 469 514 * Call this when a static shape has changed. 470 515 */ 471 void MakeDirtyStatic( flags_t flags)516 void MakeDirtyStatic(StaticShape const & shape, bool removed) 472 517 { 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); 474 521 ++m_DirtyID; 522 } 475 523 476 524 m_DebugOverlayDirty = true; 477 525 } … … 480 528 * Mark all previous Rasterise()d grids as dirty, if they depend on this shape. 481 529 * Call this when a unit shape has changed. 482 530 */ 483 void MakeDirtyUnit( flags_t flags)531 void MakeDirtyUnit(UnitShape const & shape, bool removed) 484 532 { 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); 486 536 ++m_DirtyID; 537 } 487 538 488 539 m_DebugOverlayDirty = true; 489 540 } … … 709 760 return false; 710 761 711 762 PROFILE("Rasterise"); 763 TIMER_ACCRUE(tc_Rasterize); 712 764 713 grid.m_DirtyID = m_DirtyID;714 715 // TODO: this is all hopelessly inefficient716 // What we should perhaps do is have some kind of quadtree storing Shapes so it's717 // quick to invalidate and update small numbers of tiles718 719 grid.reset();720 721 765 // For tile-based pathfinding: 722 766 // Since we only count tiles whose centers are inside the square, 723 767 // we maybe want to expand the square a bit so we're less likely to think there's … … 733 777 // so we need to expand by at least 1/sqrt(2) of a tile 734 778 entity_pos_t expandFoundation = (entity_pos_t::FromInt(CELL_SIZE) * 3) / 4; 735 779 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 736 841 for (std::map<u32, StaticShape>::iterator it = m_StaticShapes.begin(); it != m_StaticShapes.end(); ++it) 737 842 { 738 843 CFixedVector2D center(it->second.x, it->second.z);