From 4b824ca3b6247c4463e46abd26e3beeccb084b70 Mon Sep 17 00:00:00 2001 From: Jacob Ledbetter Date: Tue, 21 Jul 2026 10:34:10 -0600 Subject: [PATCH 1/5] perf(view): Cache only latest terrain location request --- Core/GameEngine/Include/Common/STLTypedefs.h | 5 --- .../Include/W3DDevice/GameClient/W3DView.h | 6 ++- .../Source/W3DDevice/GameClient/W3DView.cpp | 38 +++++++------------ 3 files changed, 17 insertions(+), 32 deletions(-) diff --git a/Core/GameEngine/Include/Common/STLTypedefs.h b/Core/GameEngine/Include/Common/STLTypedefs.h index 68a195c45a8..7276a459c3f 100644 --- a/Core/GameEngine/Include/Common/STLTypedefs.h +++ b/Core/GameEngine/Include/Common/STLTypedefs.h @@ -94,11 +94,6 @@ typedef std::vector::iterator ObjectIDVectorIterator; typedef std::vector VecCoord3D; typedef VecCoord3D::iterator VecCoord3DIt; -// Used for cursor->3D position request caching in the heightmap -typedef std::pair PosRequest; -typedef std::vector VecPosRequests; -typedef std::vector::iterator VecPosRequestsIt; - // Used to cache off names of objects for faster lookup typedef std::pair NamedRequest; typedef std::vector VecNamedRequests; diff --git a/Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DView.h b/Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DView.h index 6bbe42054ad..3f528b4c790 100644 --- a/Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DView.h +++ b/Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DView.h @@ -277,8 +277,10 @@ class W3DView : public View, public SubsystemInterface Bool m_freezeTimeForCameraMovement; Int m_timeMultiplier; ///< Time speedup multiplier. - Bool m_cameraHasMovedSinceRequest; ///< If true, throw out all saved locations - VecPosRequests m_locationRequests; ///< These are cached. New requests are added here + // TheSuperHackers @performance Retain only the latest screen-to-terrain result in the cache. + Bool m_locationRequestValid; ///< Whether the cached screen-to-terrain result can be reused. + ICoord2D m_locationRequestScreen; ///< Screen coordinate of the cached request. + Coord3D m_locationRequestWorld; ///< Cached terrain intersection for the screen coordinate. Coord3D m_previousLookAtPosition; Coord2D m_scrollAmount; ///< scroll speed diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp index 376a5297ed3..1d61145adf5 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp @@ -99,7 +99,6 @@ // 30 fps Real TheW3DFrameLengthInMsec = MSEC_PER_LOGICFRAME_REAL; // default is 33msec/frame == 30fps. but we may change it depending on sys config. -static const Int MAX_REQUEST_CACHE_SIZE = 40; // Any size larger than 10, or examine code below for changes. jkmcd. static const Real DRAWABLE_OVERSCAN = 75.0f; ///< 3D world coords of how much to overscan in the 3D screen region constexpr const Real NearZ = MAP_XY_FACTOR; ///< Set the near to MAP_XY_FACTOR. Improves z buffer resolution. @@ -174,9 +173,7 @@ W3DView::W3DView() m_shakeIntensity = 0.0f; m_FXPitch = 1.0f; m_freezeTimeForCameraMovement = false; - m_cameraHasMovedSinceRequest = true; - m_locationRequests.clear(); - m_locationRequests.reserve(MAX_REQUEST_CACHE_SIZE + 10); // This prevents the vector from ever re-allocating + m_locationRequestValid = false; //Enhancements from CNC3 WST 4/15/2003. JSC Integrated 5/20/03. m_scriptedState = 0; @@ -829,7 +826,7 @@ void W3DView::updateCameraClipPlanes(const Matrix3D &transform) //------------------------------------------------------------------------------------------------- void W3DView::setCameraTransform(const Matrix3D &transform) { - m_cameraHasMovedSinceRequest = true; + m_locationRequestValid = false; #if defined(RTS_DEBUG) m_3DCamera->Set_View_Plane( m_FOV, -1 ); @@ -2539,22 +2536,12 @@ Bool W3DView::screenToTerrain( const ICoord2D *screen, Coord3D *world ) if( screen == nullptr || world == nullptr || TheTerrainRenderObject == nullptr ) return false; - if (m_cameraHasMovedSinceRequest) { - m_locationRequests.clear(); - m_cameraHasMovedSinceRequest = false; - } - - if (m_locationRequests.size() > MAX_REQUEST_CACHE_SIZE) { - m_locationRequests.erase(m_locationRequests.begin(), m_locationRequests.begin() + 10); - } - - // We insert them at the end for speed (no copies needed), but using the principle of locality, we should - // start searching where we most recently inserted - for (int i = m_locationRequests.size() - 1; i >= 0; --i) { - if (m_locationRequests[i].first.x == screen->x && m_locationRequests[i].first.y == screen->y) { - (*world) = m_locationRequests[i].second; - return true; - } + // TheSuperHackers @performance Cache only the latest screen-to-terrain result instead of retaining roughly 40 entries. + if (m_locationRequestValid && + m_locationRequestScreen.x == screen->x && m_locationRequestScreen.y == screen->y) + { + *world = m_locationRequestWorld; + return true; } Vector3 rayStart,rayEnd; @@ -2591,10 +2578,9 @@ Bool W3DView::screenToTerrain( const ICoord2D *screen, Coord3D *world ) world->y = intersection.Y; world->z = intersection.Z; - PosRequest req; - req.first = (*screen); - req.second = (*world); - m_locationRequests.push_back(req); // Insert this request at the end, requires no extra copies + m_locationRequestScreen = *screen; + m_locationRequestWorld = *world; + m_locationRequestValid = true; return true; } @@ -3754,6 +3740,8 @@ bool W3DView::getDesiredTerrainDrawSize(ICoord2D &dimensions) const void W3DView::updateTerrain() { DEBUG_ASSERTCRASH(TheTerrainRenderObject != nullptr, ("TheTerrainRenderObject is null")); + // TheSuperHackers @bugfix Terrain render updates invalidate the cached terrain intersection. + m_locationRequestValid = false; ICoord2D drawSize; From dcc8d4097b368961e5bf9d7fec9526a4c6e106e2 Mon Sep 17 00:00:00 2001 From: Jacob Ledbetter Date: Tue, 21 Jul 2026 10:34:42 -0600 Subject: [PATCH 2/5] bugfix(w3d): Invalidate terrain cache on view changes --- .../Source/W3DDevice/GameClient/W3DView.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp index 1d61145adf5..a52e0e8302f 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp @@ -206,6 +206,8 @@ void W3DView::setHeight(Int height) { // extend View functionality View::setHeight(height); + // TheSuperHackers @bugfix Viewport projection changes invalidate the cached terrain intersection. + m_locationRequestValid = false; Vector2 vMin,vMax; m_3DCamera->Set_Aspect_Ratio((Real)getWidth()/(Real)height); @@ -226,6 +228,8 @@ void W3DView::setWidth(Int width) { // extend View functionality View::setWidth(width); + // TheSuperHackers @bugfix Viewport projection changes invalidate the cached terrain intersection. + m_locationRequestValid = false; Vector2 vMin,vMax; m_3DCamera->Set_Aspect_Ratio((Real)width/(Real)getHeight()); @@ -1464,6 +1468,8 @@ void W3DView::update() Matrix3D camXForm; camXForm.Look_At(camtran,objPos,0); m_3DCamera->Set_Transform(camXForm); + // TheSuperHackers @bugfix This direct camera transform bypasses setCameraTransform. + m_locationRequestValid = false; } } } @@ -1680,8 +1686,9 @@ void W3DView::update() } #ifdef DO_SEISMIC_SIMULATIONS - // Give the terrain a chance to refresh animating (Seismic) regions, if any. - TheTerrainVisual->updateSeismicSimulations(); + // TheSuperHackers @bugfix Seismic terrain deformation invalidates the cached terrain intersection. + m_locationRequestValid = false; + TheTerrainVisual->updateSeismicSimulations(); #endif Region3D axisAlignedRegion; @@ -2536,6 +2543,11 @@ Bool W3DView::screenToTerrain( const ICoord2D *screen, Coord3D *world ) if( screen == nullptr || world == nullptr || TheTerrainRenderObject == nullptr ) return false; + // TheSuperHackers @bugfix Reject cached intersections while a terrain render update is pending. + if (TheTerrainRenderObject->doesNeedFullUpdate()) { + m_locationRequestValid = false; + } + // TheSuperHackers @performance Cache only the latest screen-to-terrain result instead of retaining roughly 40 entries. if (m_locationRequestValid && m_locationRequestScreen.x == screen->x && m_locationRequestScreen.y == screen->y) From 48930dbea7c82f69e7002034488992110bf1b342 Mon Sep 17 00:00:00 2001 From: Jacob Ledbetter Date: Tue, 21 Jul 2026 10:35:40 -0600 Subject: [PATCH 3/5] bugfix(w3d): Invalidate terrain cache on bridge changes --- .../Include/W3DDevice/GameClient/W3DView.h | 1 + .../Source/W3DDevice/GameClient/W3DView.cpp | 8 ++++++++ .../Code/GameEngine/Include/GameLogic/TerrainLogic.h | 3 +++ .../GameEngine/Source/GameLogic/Map/TerrainLogic.cpp | 9 +++++++++ .../Code/GameEngine/Include/GameLogic/TerrainLogic.h | 3 +++ .../GameEngine/Source/GameLogic/Map/TerrainLogic.cpp | 9 +++++++++ 6 files changed, 33 insertions(+) diff --git a/Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DView.h b/Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DView.h index 3f528b4c790..1ce740233da 100644 --- a/Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DView.h +++ b/Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DView.h @@ -281,6 +281,7 @@ class W3DView : public View, public SubsystemInterface Bool m_locationRequestValid; ///< Whether the cached screen-to-terrain result can be reused. ICoord2D m_locationRequestScreen; ///< Screen coordinate of the cached request. Coord3D m_locationRequestWorld; ///< Cached terrain intersection for the screen coordinate. + UnsignedInt m_locationRequestBridgeChangeCounter; ///< Bridge-change counter recorded with the cached result. Coord3D m_previousLookAtPosition; Coord2D m_scrollAmount; ///< scroll speed diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp index a52e0e8302f..3bb7efe36dd 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp @@ -174,6 +174,7 @@ W3DView::W3DView() m_FXPitch = 1.0f; m_freezeTimeForCameraMovement = false; m_locationRequestValid = false; + m_locationRequestBridgeChangeCounter = 0; //Enhancements from CNC3 WST 4/15/2003. JSC Integrated 5/20/03. m_scriptedState = 0; @@ -2548,6 +2549,12 @@ Bool W3DView::screenToTerrain( const ICoord2D *screen, Coord3D *world ) m_locationRequestValid = false; } + const UnsignedInt bridgeChangeCounter = TheTerrainLogic ? TheTerrainLogic->getBridgeChangeCounter() : 0; + // TheSuperHackers @bugfix Use a persistent bridge-change counter so cached intersections remain invalidated after the change frame. + if (m_locationRequestBridgeChangeCounter != bridgeChangeCounter) { + m_locationRequestValid = false; + } + // TheSuperHackers @performance Cache only the latest screen-to-terrain result instead of retaining roughly 40 entries. if (m_locationRequestValid && m_locationRequestScreen.x == screen->x && m_locationRequestScreen.y == screen->y) @@ -2592,6 +2599,7 @@ Bool W3DView::screenToTerrain( const ICoord2D *screen, Coord3D *world ) m_locationRequestScreen = *screen; m_locationRequestWorld = *world; + m_locationRequestBridgeChangeCounter = bridgeChangeCounter; m_locationRequestValid = true; return true; diff --git a/Generals/Code/GameEngine/Include/GameLogic/TerrainLogic.h b/Generals/Code/GameEngine/Include/GameLogic/TerrainLogic.h index ad1db103499..6a9dc69efe2 100644 --- a/Generals/Code/GameEngine/Include/GameLogic/TerrainLogic.h +++ b/Generals/Code/GameEngine/Include/GameLogic/TerrainLogic.h @@ -295,6 +295,7 @@ class TerrainLogic : public Snapshot, virtual void updateBridgeDamageStates(); ///< Updates bridge's damage info. Bool anyBridgesDamageStatesChanged() {return m_bridgeDamageStatesChanged; } ///< Bridge damage states updated. + UnsignedInt getBridgeChangeCounter() const { return m_bridgeChangeCounter; } ///< Bridge-change counter used for cache invalidation. Bool isBridgeRepaired(const Object *bridge); ///< Is bridge repaired? Bool isBridgeBroken(const Object *bridge); ///< Is bridge Broken? void getBridgeAttackPoints(const Object *bridge, TBridgeAttackInfo *info); ///< Get bridge attack points. @@ -347,6 +348,8 @@ class TerrainLogic : public Snapshot, Bridge *m_bridgeListHead; Bool m_bridgeDamageStatesChanged; + // TheSuperHackers @bugfix Track bridge changes beyond the lifetime of the original per-frame damage-state flag. + UnsignedInt m_bridgeChangeCounter; AsciiString m_filenameString; ///< filename for terrain data diff --git a/Generals/Code/GameEngine/Source/GameLogic/Map/TerrainLogic.cpp b/Generals/Code/GameEngine/Source/GameLogic/Map/TerrainLogic.cpp index d5eff4130f6..1f0827d04a4 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Map/TerrainLogic.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Map/TerrainLogic.cpp @@ -977,6 +977,7 @@ TerrainLogic::TerrainLogic() m_bridgeListHead = nullptr; m_mapData = nullptr; m_bridgeDamageStatesChanged = FALSE; + m_bridgeChangeCounter = 0; m_mapDX = 0; m_mapDY = 0; @@ -1529,6 +1530,7 @@ PathfindLayerEnum TerrainLogic::alignOnTerrain( Real angle, const Coord3D& pos, //------------------------------------------------------------------------------------------------- void TerrainLogic::addBridgeToLogic(BridgeInfo *pInfo, Dict *props, AsciiString bridgeTemplateName) { + ++m_bridgeChangeCounter; Bridge *pBridge = newInstance(Bridge)(*pInfo, props, bridgeTemplateName); pBridge->setNext(m_bridgeListHead); m_bridgeListHead = pBridge; @@ -1542,6 +1544,7 @@ void TerrainLogic::addBridgeToLogic(BridgeInfo *pInfo, Dict *props, AsciiString //------------------------------------------------------------------------------------------------- void TerrainLogic::addLandmarkBridgeToLogic(Object *bridgeObj) { + ++m_bridgeChangeCounter; Bridge *pBridge = newInstance(Bridge)(bridgeObj); pBridge->setNext(m_bridgeListHead); @@ -1863,6 +1866,7 @@ void TerrainLogic::updateBridgeDamageStates() pBridge = pBridge->getNext(); } m_bridgeDamageStatesChanged = true; + ++m_bridgeChangeCounter; } //------------------------------------------------------------------------------------------------- @@ -1980,6 +1984,10 @@ Drawable *TerrainLogic::pickBridge(const Vector3 &from, const Vector3 &to, Vecto //------------------------------------------------------------------------------------------------- void TerrainLogic::deleteBridges() { + if (m_bridgeListHead) { + ++m_bridgeChangeCounter; + } + Bridge *pNext = nullptr; Bridge *pBridge; // Traverse all waypoints. @@ -2000,6 +2008,7 @@ void TerrainLogic::deleteBridge( Bridge *bridge ) // sanity if( bridge == nullptr ) return; + ++m_bridgeChangeCounter; // check for removing the head if( m_bridgeListHead == bridge ) diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/TerrainLogic.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/TerrainLogic.h index 90dda7f2411..3b3f3d0a987 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/TerrainLogic.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/TerrainLogic.h @@ -295,6 +295,7 @@ class TerrainLogic : public Snapshot, virtual void updateBridgeDamageStates(); ///< Updates bridge's damage info. Bool anyBridgesDamageStatesChanged() {return m_bridgeDamageStatesChanged; } ///< Bridge damage states updated. + UnsignedInt getBridgeChangeCounter() const { return m_bridgeChangeCounter; } ///< Bridge-change counter used for cache invalidation. Bool isBridgeRepaired(const Object *bridge); ///< Is bridge repaired? Bool isBridgeBroken(const Object *bridge); ///< Is bridge Broken? void getBridgeAttackPoints(const Object *bridge, TBridgeAttackInfo *info); ///< Get bridge attack points. @@ -348,6 +349,8 @@ class TerrainLogic : public Snapshot, Bridge *m_bridgeListHead; Bool m_bridgeDamageStatesChanged; + // TheSuperHackers @bugfix Track bridge changes beyond the lifetime of the original per-frame damage-state flag. + UnsignedInt m_bridgeChangeCounter; AsciiString m_filenameString; ///< filename for terrain data diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Map/TerrainLogic.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Map/TerrainLogic.cpp index db2aa8d2200..9c2a61ad1c7 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Map/TerrainLogic.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Map/TerrainLogic.cpp @@ -977,6 +977,7 @@ TerrainLogic::TerrainLogic() m_bridgeListHead = nullptr; m_mapData = nullptr; m_bridgeDamageStatesChanged = FALSE; + m_bridgeChangeCounter = 0; m_mapDX = 0; m_mapDY = 0; @@ -1529,6 +1530,7 @@ PathfindLayerEnum TerrainLogic::alignOnTerrain( Real angle, const Coord3D& pos, //------------------------------------------------------------------------------------------------- void TerrainLogic::addBridgeToLogic(BridgeInfo *pInfo, Dict *props, AsciiString bridgeTemplateName) { + ++m_bridgeChangeCounter; Bridge *pBridge = newInstance(Bridge)(*pInfo, props, bridgeTemplateName); pBridge->setNext(m_bridgeListHead); m_bridgeListHead = pBridge; @@ -1542,6 +1544,7 @@ void TerrainLogic::addBridgeToLogic(BridgeInfo *pInfo, Dict *props, AsciiString //------------------------------------------------------------------------------------------------- void TerrainLogic::addLandmarkBridgeToLogic(Object *bridgeObj) { + ++m_bridgeChangeCounter; Bridge *pBridge = newInstance(Bridge)(bridgeObj); pBridge->setNext(m_bridgeListHead); @@ -1863,6 +1866,7 @@ void TerrainLogic::updateBridgeDamageStates() pBridge = pBridge->getNext(); } m_bridgeDamageStatesChanged = true; + ++m_bridgeChangeCounter; } //------------------------------------------------------------------------------------------------- @@ -1980,6 +1984,10 @@ Drawable *TerrainLogic::pickBridge(const Vector3 &from, const Vector3 &to, Vecto //------------------------------------------------------------------------------------------------- void TerrainLogic::deleteBridges() { + if (m_bridgeListHead) { + ++m_bridgeChangeCounter; + } + Bridge *pNext = nullptr; Bridge *pBridge; // Traverse all waypoints. @@ -2000,6 +2008,7 @@ void TerrainLogic::deleteBridge( Bridge *bridge ) // sanity if( bridge == nullptr ) return; + ++m_bridgeChangeCounter; // check for removing the head if( m_bridgeListHead == bridge ) From b8a233e1633b4590e2d7cfd1bda5a8831c644fc5 Mon Sep 17 00:00:00 2001 From: Jacob Ledbetter Date: Wed, 22 Jul 2026 12:22:48 -0600 Subject: [PATCH 4/5] refactor(view): Invalidate terrain cache on change events --- Core/GameEngine/Include/GameClient/View.h | 2 ++ .../W3DDevice/GameClient/BaseHeightMap.h | 2 ++ .../Include/W3DDevice/GameClient/W3DView.h | 11 +++++----- .../W3DDevice/GameClient/BaseHeightMap.cpp | 14 +++++++++--- .../Source/W3DDevice/GameClient/HeightMap.cpp | 4 ++-- .../Source/W3DDevice/GameClient/W3DView.cpp | 20 ----------------- .../Include/GameLogic/TerrainLogic.h | 3 --- .../Source/GameLogic/Map/TerrainLogic.cpp | 22 ++++++++++++++----- .../Include/GameLogic/TerrainLogic.h | 3 --- .../Source/GameLogic/Map/TerrainLogic.cpp | 22 ++++++++++++++----- 10 files changed, 55 insertions(+), 48 deletions(-) diff --git a/Core/GameEngine/Include/GameClient/View.h b/Core/GameEngine/Include/GameClient/View.h index ccc33949f64..d60010da63c 100644 --- a/Core/GameEngine/Include/GameClient/View.h +++ b/Core/GameEngine/Include/GameClient/View.h @@ -137,6 +137,8 @@ class View : public Snapshot virtual void getOrigin( Int *x, Int *y) { *x=m_originX; *y=m_originY;} ///< Return location of top-left view corner on display virtual void forceRedraw() = 0; + virtual void onHeightMapChanged() {} + virtual void onBridgeChanged() {} virtual void lookAt( const Coord3D *o ); ///< Center the view on the given coordinate virtual void initHeightForMap() {}; ///< Init the camera height for the map at the current position. diff --git a/Core/GameEngineDevice/Include/W3DDevice/GameClient/BaseHeightMap.h b/Core/GameEngineDevice/Include/W3DDevice/GameClient/BaseHeightMap.h index 119acd38e3f..6feed84e471 100644 --- a/Core/GameEngineDevice/Include/W3DDevice/GameClient/BaseHeightMap.h +++ b/Core/GameEngineDevice/Include/W3DDevice/GameClient/BaseHeightMap.h @@ -229,6 +229,8 @@ class BaseHeightMapRenderObjClass : public RenderObjClass, public DX8_CleanupHoo virtual int updateBlock(Int x0, Int y0, Int x1, Int y1, WorldHeightMap *pMap, RefRenderObjListIterator *pLightsIterator) = 0; protected: + void scheduleFullUpdate(); + // snapshot methods virtual void crc( Xfer *xfer ) override; virtual void xfer( Xfer *xfer ) override; diff --git a/Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DView.h b/Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DView.h index 1ce740233da..5c2728d4ac2 100644 --- a/Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DView.h +++ b/Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DView.h @@ -167,6 +167,8 @@ class W3DView : public View, public SubsystemInterface virtual void scrollBy( const Coord2D *delta ) override; ///< Shift the view by the given delta virtual void forceRedraw() override; + virtual void onHeightMapChanged() override { m_locationRequestValid = false; } + virtual void onBridgeChanged() override { m_locationRequestValid = false; } virtual Bool isDoingScriptedCamera() override; virtual void stopDoingScriptedCamera() override; @@ -277,11 +279,10 @@ class W3DView : public View, public SubsystemInterface Bool m_freezeTimeForCameraMovement; Int m_timeMultiplier; ///< Time speedup multiplier. - // TheSuperHackers @performance Retain only the latest screen-to-terrain result in the cache. - Bool m_locationRequestValid; ///< Whether the cached screen-to-terrain result can be reused. - ICoord2D m_locationRequestScreen; ///< Screen coordinate of the cached request. - Coord3D m_locationRequestWorld; ///< Cached terrain intersection for the screen coordinate. - UnsignedInt m_locationRequestBridgeChangeCounter; ///< Bridge-change counter recorded with the cached result. + // TheSuperHackers @performance Cache only the latest screen-to-terrain result. + Bool m_locationRequestValid; + ICoord2D m_locationRequestScreen; + Coord3D m_locationRequestWorld; Coord3D m_previousLookAtPosition; Coord2D m_scrollAmount; ///< scroll speed diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/BaseHeightMap.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/BaseHeightMap.cpp index 4cb1514c396..397d5eee749 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/BaseHeightMap.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/BaseHeightMap.cpp @@ -312,6 +312,14 @@ BaseHeightMapRenderObjClass::BaseHeightMapRenderObjClass() DX8Wrapper::SetCleanupHook(this); } +void BaseHeightMapRenderObjClass::scheduleFullUpdate() +{ + m_needFullUpdate = true; + if (TheTacticalView) { + TheTacticalView->onHeightMapChanged(); + } +} + void BaseHeightMapRenderObjClass::setTextureLOD(Int lod) { if (m_treeBuffer) @@ -454,7 +462,7 @@ void BaseHeightMapRenderObjClass::ReAcquireResources() { this->initHeightData(m_x,m_y,m_map, nullptr); // Tell lights to update next time through. - m_needFullUpdate = true; + scheduleFullUpdate(); } if (m_treeBuffer) { @@ -1823,7 +1831,7 @@ Int BaseHeightMapRenderObjClass::initHeightData(Int x, Int y, WorldHeightMap *pM } Set_Force_Visible(TRUE); //terrain is always visible. - m_needFullUpdate = true; + scheduleFullUpdate(); m_scorchesInBuffer = 0; m_curNumScorchVertices=0; @@ -2352,7 +2360,7 @@ void BaseHeightMapRenderObjClass::removeTerrainBibDrawable(DrawableID id) void BaseHeightMapRenderObjClass::staticLightingChanged() { // Cause the terrain to get updated with new lighting. - m_needFullUpdate = true; + scheduleFullUpdate(); // Cause the scorches to get updated with new lighting. m_scorchesInBuffer = 0; // If we just allocated the buffers, we got no scorches in the buffer. diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/HeightMap.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/HeightMap.cpp index 4987a8071c1..eddcf160ffa 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/HeightMap.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/HeightMap.cpp @@ -1200,7 +1200,7 @@ void HeightMapRenderObjClass::setTerrainDrawSize(Int width, Int height) //delete m_shroud; //m_shroud = nullptr; initHeightData(m_map->getDrawWidth(), m_map->getDrawHeight(), m_map, nullptr, FALSE); - m_needFullUpdate = true; + scheduleFullUpdate(); } @@ -1267,7 +1267,7 @@ Int HeightMapRenderObjClass::initHeightData(Int x, Int y, WorldHeightMap *pMap, m_originX = 0; m_originY = 0; - m_needFullUpdate = true; + scheduleFullUpdate(); // If the size changed, we need to allocate. Bool needToAllocate = (x != m_x || y != m_y); diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp index 3bb7efe36dd..aa209873168 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp @@ -174,7 +174,6 @@ W3DView::W3DView() m_FXPitch = 1.0f; m_freezeTimeForCameraMovement = false; m_locationRequestValid = false; - m_locationRequestBridgeChangeCounter = 0; //Enhancements from CNC3 WST 4/15/2003. JSC Integrated 5/20/03. m_scriptedState = 0; @@ -207,7 +206,6 @@ void W3DView::setHeight(Int height) { // extend View functionality View::setHeight(height); - // TheSuperHackers @bugfix Viewport projection changes invalidate the cached terrain intersection. m_locationRequestValid = false; Vector2 vMin,vMax; @@ -229,7 +227,6 @@ void W3DView::setWidth(Int width) { // extend View functionality View::setWidth(width); - // TheSuperHackers @bugfix Viewport projection changes invalidate the cached terrain intersection. m_locationRequestValid = false; Vector2 vMin,vMax; @@ -1469,7 +1466,6 @@ void W3DView::update() Matrix3D camXForm; camXForm.Look_At(camtran,objPos,0); m_3DCamera->Set_Transform(camXForm); - // TheSuperHackers @bugfix This direct camera transform bypasses setCameraTransform. m_locationRequestValid = false; } } @@ -1687,7 +1683,6 @@ void W3DView::update() } #ifdef DO_SEISMIC_SIMULATIONS - // TheSuperHackers @bugfix Seismic terrain deformation invalidates the cached terrain intersection. m_locationRequestValid = false; TheTerrainVisual->updateSeismicSimulations(); #endif @@ -2544,18 +2539,6 @@ Bool W3DView::screenToTerrain( const ICoord2D *screen, Coord3D *world ) if( screen == nullptr || world == nullptr || TheTerrainRenderObject == nullptr ) return false; - // TheSuperHackers @bugfix Reject cached intersections while a terrain render update is pending. - if (TheTerrainRenderObject->doesNeedFullUpdate()) { - m_locationRequestValid = false; - } - - const UnsignedInt bridgeChangeCounter = TheTerrainLogic ? TheTerrainLogic->getBridgeChangeCounter() : 0; - // TheSuperHackers @bugfix Use a persistent bridge-change counter so cached intersections remain invalidated after the change frame. - if (m_locationRequestBridgeChangeCounter != bridgeChangeCounter) { - m_locationRequestValid = false; - } - - // TheSuperHackers @performance Cache only the latest screen-to-terrain result instead of retaining roughly 40 entries. if (m_locationRequestValid && m_locationRequestScreen.x == screen->x && m_locationRequestScreen.y == screen->y) { @@ -2599,7 +2582,6 @@ Bool W3DView::screenToTerrain( const ICoord2D *screen, Coord3D *world ) m_locationRequestScreen = *screen; m_locationRequestWorld = *world; - m_locationRequestBridgeChangeCounter = bridgeChangeCounter; m_locationRequestValid = true; return true; @@ -3760,8 +3742,6 @@ bool W3DView::getDesiredTerrainDrawSize(ICoord2D &dimensions) const void W3DView::updateTerrain() { DEBUG_ASSERTCRASH(TheTerrainRenderObject != nullptr, ("TheTerrainRenderObject is null")); - // TheSuperHackers @bugfix Terrain render updates invalidate the cached terrain intersection. - m_locationRequestValid = false; ICoord2D drawSize; diff --git a/Generals/Code/GameEngine/Include/GameLogic/TerrainLogic.h b/Generals/Code/GameEngine/Include/GameLogic/TerrainLogic.h index 6a9dc69efe2..ad1db103499 100644 --- a/Generals/Code/GameEngine/Include/GameLogic/TerrainLogic.h +++ b/Generals/Code/GameEngine/Include/GameLogic/TerrainLogic.h @@ -295,7 +295,6 @@ class TerrainLogic : public Snapshot, virtual void updateBridgeDamageStates(); ///< Updates bridge's damage info. Bool anyBridgesDamageStatesChanged() {return m_bridgeDamageStatesChanged; } ///< Bridge damage states updated. - UnsignedInt getBridgeChangeCounter() const { return m_bridgeChangeCounter; } ///< Bridge-change counter used for cache invalidation. Bool isBridgeRepaired(const Object *bridge); ///< Is bridge repaired? Bool isBridgeBroken(const Object *bridge); ///< Is bridge Broken? void getBridgeAttackPoints(const Object *bridge, TBridgeAttackInfo *info); ///< Get bridge attack points. @@ -348,8 +347,6 @@ class TerrainLogic : public Snapshot, Bridge *m_bridgeListHead; Bool m_bridgeDamageStatesChanged; - // TheSuperHackers @bugfix Track bridge changes beyond the lifetime of the original per-frame damage-state flag. - UnsignedInt m_bridgeChangeCounter; AsciiString m_filenameString; ///< filename for terrain data diff --git a/Generals/Code/GameEngine/Source/GameLogic/Map/TerrainLogic.cpp b/Generals/Code/GameEngine/Source/GameLogic/Map/TerrainLogic.cpp index 1f0827d04a4..0bd97135ed1 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Map/TerrainLogic.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Map/TerrainLogic.cpp @@ -40,6 +40,7 @@ #include "Common/Xfer.h" #include "GameClient/TerrainVisual.h" +#include "GameClient/View.h" #include "GameLogic/AI.h" #include "GameLogic/AIPathfind.h" @@ -977,7 +978,6 @@ TerrainLogic::TerrainLogic() m_bridgeListHead = nullptr; m_mapData = nullptr; m_bridgeDamageStatesChanged = FALSE; - m_bridgeChangeCounter = 0; m_mapDX = 0; m_mapDY = 0; @@ -1530,7 +1530,9 @@ PathfindLayerEnum TerrainLogic::alignOnTerrain( Real angle, const Coord3D& pos, //------------------------------------------------------------------------------------------------- void TerrainLogic::addBridgeToLogic(BridgeInfo *pInfo, Dict *props, AsciiString bridgeTemplateName) { - ++m_bridgeChangeCounter; + if (TheTacticalView) { + TheTacticalView->onBridgeChanged(); + } Bridge *pBridge = newInstance(Bridge)(*pInfo, props, bridgeTemplateName); pBridge->setNext(m_bridgeListHead); m_bridgeListHead = pBridge; @@ -1544,7 +1546,9 @@ void TerrainLogic::addBridgeToLogic(BridgeInfo *pInfo, Dict *props, AsciiString //------------------------------------------------------------------------------------------------- void TerrainLogic::addLandmarkBridgeToLogic(Object *bridgeObj) { - ++m_bridgeChangeCounter; + if (TheTacticalView) { + TheTacticalView->onBridgeChanged(); + } Bridge *pBridge = newInstance(Bridge)(bridgeObj); pBridge->setNext(m_bridgeListHead); @@ -1866,7 +1870,9 @@ void TerrainLogic::updateBridgeDamageStates() pBridge = pBridge->getNext(); } m_bridgeDamageStatesChanged = true; - ++m_bridgeChangeCounter; + if (TheTacticalView) { + TheTacticalView->onBridgeChanged(); + } } //------------------------------------------------------------------------------------------------- @@ -1985,7 +1991,9 @@ Drawable *TerrainLogic::pickBridge(const Vector3 &from, const Vector3 &to, Vecto void TerrainLogic::deleteBridges() { if (m_bridgeListHead) { - ++m_bridgeChangeCounter; + if (TheTacticalView) { + TheTacticalView->onBridgeChanged(); + } } Bridge *pNext = nullptr; @@ -2008,7 +2016,9 @@ void TerrainLogic::deleteBridge( Bridge *bridge ) // sanity if( bridge == nullptr ) return; - ++m_bridgeChangeCounter; + if (TheTacticalView) { + TheTacticalView->onBridgeChanged(); + } // check for removing the head if( m_bridgeListHead == bridge ) diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/TerrainLogic.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/TerrainLogic.h index 3b3f3d0a987..90dda7f2411 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/TerrainLogic.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/TerrainLogic.h @@ -295,7 +295,6 @@ class TerrainLogic : public Snapshot, virtual void updateBridgeDamageStates(); ///< Updates bridge's damage info. Bool anyBridgesDamageStatesChanged() {return m_bridgeDamageStatesChanged; } ///< Bridge damage states updated. - UnsignedInt getBridgeChangeCounter() const { return m_bridgeChangeCounter; } ///< Bridge-change counter used for cache invalidation. Bool isBridgeRepaired(const Object *bridge); ///< Is bridge repaired? Bool isBridgeBroken(const Object *bridge); ///< Is bridge Broken? void getBridgeAttackPoints(const Object *bridge, TBridgeAttackInfo *info); ///< Get bridge attack points. @@ -349,8 +348,6 @@ class TerrainLogic : public Snapshot, Bridge *m_bridgeListHead; Bool m_bridgeDamageStatesChanged; - // TheSuperHackers @bugfix Track bridge changes beyond the lifetime of the original per-frame damage-state flag. - UnsignedInt m_bridgeChangeCounter; AsciiString m_filenameString; ///< filename for terrain data diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Map/TerrainLogic.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Map/TerrainLogic.cpp index 9c2a61ad1c7..a324ce658d9 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Map/TerrainLogic.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Map/TerrainLogic.cpp @@ -40,6 +40,7 @@ #include "Common/Xfer.h" #include "GameClient/TerrainVisual.h" +#include "GameClient/View.h" #include "GameLogic/AI.h" #include "GameLogic/AIPathfind.h" @@ -977,7 +978,6 @@ TerrainLogic::TerrainLogic() m_bridgeListHead = nullptr; m_mapData = nullptr; m_bridgeDamageStatesChanged = FALSE; - m_bridgeChangeCounter = 0; m_mapDX = 0; m_mapDY = 0; @@ -1530,7 +1530,9 @@ PathfindLayerEnum TerrainLogic::alignOnTerrain( Real angle, const Coord3D& pos, //------------------------------------------------------------------------------------------------- void TerrainLogic::addBridgeToLogic(BridgeInfo *pInfo, Dict *props, AsciiString bridgeTemplateName) { - ++m_bridgeChangeCounter; + if (TheTacticalView) { + TheTacticalView->onBridgeChanged(); + } Bridge *pBridge = newInstance(Bridge)(*pInfo, props, bridgeTemplateName); pBridge->setNext(m_bridgeListHead); m_bridgeListHead = pBridge; @@ -1544,7 +1546,9 @@ void TerrainLogic::addBridgeToLogic(BridgeInfo *pInfo, Dict *props, AsciiString //------------------------------------------------------------------------------------------------- void TerrainLogic::addLandmarkBridgeToLogic(Object *bridgeObj) { - ++m_bridgeChangeCounter; + if (TheTacticalView) { + TheTacticalView->onBridgeChanged(); + } Bridge *pBridge = newInstance(Bridge)(bridgeObj); pBridge->setNext(m_bridgeListHead); @@ -1866,7 +1870,9 @@ void TerrainLogic::updateBridgeDamageStates() pBridge = pBridge->getNext(); } m_bridgeDamageStatesChanged = true; - ++m_bridgeChangeCounter; + if (TheTacticalView) { + TheTacticalView->onBridgeChanged(); + } } //------------------------------------------------------------------------------------------------- @@ -1985,7 +1991,9 @@ Drawable *TerrainLogic::pickBridge(const Vector3 &from, const Vector3 &to, Vecto void TerrainLogic::deleteBridges() { if (m_bridgeListHead) { - ++m_bridgeChangeCounter; + if (TheTacticalView) { + TheTacticalView->onBridgeChanged(); + } } Bridge *pNext = nullptr; @@ -2008,7 +2016,9 @@ void TerrainLogic::deleteBridge( Bridge *bridge ) // sanity if( bridge == nullptr ) return; - ++m_bridgeChangeCounter; + if (TheTacticalView) { + TheTacticalView->onBridgeChanged(); + } // check for removing the head if( m_bridgeListHead == bridge ) From b089b42da1877a3d2278916816d765cd15aaf641 Mon Sep 17 00:00:00 2001 From: Jacob Ledbetter Date: Thu, 23 Jul 2026 14:06:08 -0600 Subject: [PATCH 5/5] refactor(view): Refine screen-to-terrain cache invalidation --- .../Include/W3DDevice/GameClient/W3DView.h | 10 +++---- .../W3DDevice/GameClient/W3DTerrainVisual.cpp | 5 ++++ .../Source/W3DDevice/GameClient/W3DView.cpp | 23 +++++++-------- .../Source/GameLogic/Map/TerrainLogic.cpp | 29 +++++++++---------- .../Source/GameLogic/Map/TerrainLogic.cpp | 29 +++++++++---------- 5 files changed, 49 insertions(+), 47 deletions(-) diff --git a/Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DView.h b/Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DView.h index 5c2728d4ac2..87178aed370 100644 --- a/Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DView.h +++ b/Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DView.h @@ -167,8 +167,8 @@ class W3DView : public View, public SubsystemInterface virtual void scrollBy( const Coord2D *delta ) override; ///< Shift the view by the given delta virtual void forceRedraw() override; - virtual void onHeightMapChanged() override { m_locationRequestValid = false; } - virtual void onBridgeChanged() override { m_locationRequestValid = false; } + virtual void onHeightMapChanged() override { m_lastScreenToTerrainValid = false; } + virtual void onBridgeChanged() override { m_lastScreenToTerrainValid = false; } virtual Bool isDoingScriptedCamera() override; virtual void stopDoingScriptedCamera() override; @@ -280,9 +280,9 @@ class W3DView : public View, public SubsystemInterface Int m_timeMultiplier; ///< Time speedup multiplier. // TheSuperHackers @performance Cache only the latest screen-to-terrain result. - Bool m_locationRequestValid; - ICoord2D m_locationRequestScreen; - Coord3D m_locationRequestWorld; + Bool m_lastScreenToTerrainValid; + ICoord2D m_lastScreenToTerrainScreen; + Coord3D m_lastScreenToTerrainWorld; Coord3D m_previousLookAtPosition; Coord2D m_scrollAmount; ///< scroll speed diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DTerrainVisual.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DTerrainVisual.cpp index 963cd2b8183..ad5d6386a98 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DTerrainVisual.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DTerrainVisual.cpp @@ -42,6 +42,7 @@ #include "GameClient/Drawable.h" #include "GameClient/ClientRandomValue.h" +#include "GameClient/View.h" #include "GameLogic/Object.h" #include "GameLogic/GameLogic.h" @@ -494,6 +495,10 @@ void W3DTerrainVisual::updateSeismicSimulations() } + if (TheTacticalView) { + TheTacticalView->onHeightMapChanged(); + } + } } diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp index aa209873168..e65724084c5 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp @@ -173,7 +173,7 @@ W3DView::W3DView() m_shakeIntensity = 0.0f; m_FXPitch = 1.0f; m_freezeTimeForCameraMovement = false; - m_locationRequestValid = false; + m_lastScreenToTerrainValid = false; //Enhancements from CNC3 WST 4/15/2003. JSC Integrated 5/20/03. m_scriptedState = 0; @@ -206,7 +206,6 @@ void W3DView::setHeight(Int height) { // extend View functionality View::setHeight(height); - m_locationRequestValid = false; Vector2 vMin,vMax; m_3DCamera->Set_Aspect_Ratio((Real)getWidth()/(Real)height); @@ -218,6 +217,7 @@ void W3DView::setHeight(Int height) // showing or hiding the control bar will change the viewable area. m_cameraAreaConstraintsValid = false; m_recalcCamera = true; + m_lastScreenToTerrainValid = false; } //------------------------------------------------------------------------------------------------- @@ -227,7 +227,6 @@ void W3DView::setWidth(Int width) { // extend View functionality View::setWidth(width); - m_locationRequestValid = false; Vector2 vMin,vMax; m_3DCamera->Set_Aspect_Ratio((Real)width/(Real)getHeight()); @@ -241,6 +240,7 @@ void W3DView::setWidth(Int width) m_cameraAreaConstraintsValid = false; m_recalcCamera = true; + m_lastScreenToTerrainValid = false; } //------------------------------------------------------------------------------------------------- @@ -828,7 +828,7 @@ void W3DView::updateCameraClipPlanes(const Matrix3D &transform) //------------------------------------------------------------------------------------------------- void W3DView::setCameraTransform(const Matrix3D &transform) { - m_locationRequestValid = false; + m_lastScreenToTerrainValid = false; #if defined(RTS_DEBUG) m_3DCamera->Set_View_Plane( m_FOV, -1 ); @@ -1466,7 +1466,7 @@ void W3DView::update() Matrix3D camXForm; camXForm.Look_At(camtran,objPos,0); m_3DCamera->Set_Transform(camXForm); - m_locationRequestValid = false; + m_lastScreenToTerrainValid = false; } } } @@ -1683,7 +1683,6 @@ void W3DView::update() } #ifdef DO_SEISMIC_SIMULATIONS - m_locationRequestValid = false; TheTerrainVisual->updateSeismicSimulations(); #endif @@ -2539,10 +2538,10 @@ Bool W3DView::screenToTerrain( const ICoord2D *screen, Coord3D *world ) if( screen == nullptr || world == nullptr || TheTerrainRenderObject == nullptr ) return false; - if (m_locationRequestValid && - m_locationRequestScreen.x == screen->x && m_locationRequestScreen.y == screen->y) + if (m_lastScreenToTerrainValid && + m_lastScreenToTerrainScreen.x == screen->x && m_lastScreenToTerrainScreen.y == screen->y) { - *world = m_locationRequestWorld; + *world = m_lastScreenToTerrainWorld; return true; } @@ -2580,9 +2579,9 @@ Bool W3DView::screenToTerrain( const ICoord2D *screen, Coord3D *world ) world->y = intersection.Y; world->z = intersection.Z; - m_locationRequestScreen = *screen; - m_locationRequestWorld = *world; - m_locationRequestValid = true; + m_lastScreenToTerrainScreen = *screen; + m_lastScreenToTerrainWorld = *world; + m_lastScreenToTerrainValid = true; return true; } diff --git a/Generals/Code/GameEngine/Source/GameLogic/Map/TerrainLogic.cpp b/Generals/Code/GameEngine/Source/GameLogic/Map/TerrainLogic.cpp index 0bd97135ed1..00386047267 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Map/TerrainLogic.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Map/TerrainLogic.cpp @@ -1530,15 +1530,15 @@ PathfindLayerEnum TerrainLogic::alignOnTerrain( Real angle, const Coord3D& pos, //------------------------------------------------------------------------------------------------- void TerrainLogic::addBridgeToLogic(BridgeInfo *pInfo, Dict *props, AsciiString bridgeTemplateName) { - if (TheTacticalView) { - TheTacticalView->onBridgeChanged(); - } Bridge *pBridge = newInstance(Bridge)(*pInfo, props, bridgeTemplateName); pBridge->setNext(m_bridgeListHead); m_bridgeListHead = pBridge; PathfindLayerEnum layer = TheAI->pathfinder()->addBridge(pBridge); pBridge->setLayer(layer); + if (TheTacticalView) { + TheTacticalView->onBridgeChanged(); + } } //------------------------------------------------------------------------------------------------- @@ -1546,16 +1546,15 @@ void TerrainLogic::addBridgeToLogic(BridgeInfo *pInfo, Dict *props, AsciiString //------------------------------------------------------------------------------------------------- void TerrainLogic::addLandmarkBridgeToLogic(Object *bridgeObj) { - if (TheTacticalView) { - TheTacticalView->onBridgeChanged(); - } - Bridge *pBridge = newInstance(Bridge)(bridgeObj); pBridge->setNext(m_bridgeListHead); m_bridgeListHead = pBridge; PathfindLayerEnum layer = TheAI->pathfinder()->addBridge(pBridge); pBridge->setLayer(layer); + if (TheTacticalView) { + TheTacticalView->onBridgeChanged(); + } } //------------------------------------------------------------------------------------------------- @@ -1990,11 +1989,7 @@ Drawable *TerrainLogic::pickBridge(const Vector3 &from, const Vector3 &to, Vecto //------------------------------------------------------------------------------------------------- void TerrainLogic::deleteBridges() { - if (m_bridgeListHead) { - if (TheTacticalView) { - TheTacticalView->onBridgeChanged(); - } - } + Bool bridgesChanged = m_bridgeListHead != nullptr; Bridge *pNext = nullptr; Bridge *pBridge; @@ -2005,6 +2000,10 @@ void TerrainLogic::deleteBridges() deleteInstance(pBridge); } m_bridgeListHead = nullptr; + + if (bridgesChanged && TheTacticalView) { + TheTacticalView->onBridgeChanged(); + } } //------------------------------------------------------------------------------------------------- @@ -2016,9 +2015,6 @@ void TerrainLogic::deleteBridge( Bridge *bridge ) // sanity if( bridge == nullptr ) return; - if (TheTacticalView) { - TheTacticalView->onBridgeChanged(); - } // check for removing the head if( m_bridgeListHead == bridge ) @@ -2063,6 +2059,9 @@ void TerrainLogic::deleteBridge( Bridge *bridge ) // delete the bridge in question deleteInstance(bridge); + if (TheTacticalView) { + TheTacticalView->onBridgeChanged(); + } } //------------------------------------------------------------------------------------------------- diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Map/TerrainLogic.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Map/TerrainLogic.cpp index a324ce658d9..5a3d16f86ae 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Map/TerrainLogic.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Map/TerrainLogic.cpp @@ -1530,15 +1530,15 @@ PathfindLayerEnum TerrainLogic::alignOnTerrain( Real angle, const Coord3D& pos, //------------------------------------------------------------------------------------------------- void TerrainLogic::addBridgeToLogic(BridgeInfo *pInfo, Dict *props, AsciiString bridgeTemplateName) { - if (TheTacticalView) { - TheTacticalView->onBridgeChanged(); - } Bridge *pBridge = newInstance(Bridge)(*pInfo, props, bridgeTemplateName); pBridge->setNext(m_bridgeListHead); m_bridgeListHead = pBridge; PathfindLayerEnum layer = TheAI->pathfinder()->addBridge(pBridge); pBridge->setLayer(layer); + if (TheTacticalView) { + TheTacticalView->onBridgeChanged(); + } } //------------------------------------------------------------------------------------------------- @@ -1546,16 +1546,15 @@ void TerrainLogic::addBridgeToLogic(BridgeInfo *pInfo, Dict *props, AsciiString //------------------------------------------------------------------------------------------------- void TerrainLogic::addLandmarkBridgeToLogic(Object *bridgeObj) { - if (TheTacticalView) { - TheTacticalView->onBridgeChanged(); - } - Bridge *pBridge = newInstance(Bridge)(bridgeObj); pBridge->setNext(m_bridgeListHead); m_bridgeListHead = pBridge; PathfindLayerEnum layer = TheAI->pathfinder()->addBridge(pBridge); pBridge->setLayer(layer); + if (TheTacticalView) { + TheTacticalView->onBridgeChanged(); + } } //------------------------------------------------------------------------------------------------- @@ -1990,11 +1989,7 @@ Drawable *TerrainLogic::pickBridge(const Vector3 &from, const Vector3 &to, Vecto //------------------------------------------------------------------------------------------------- void TerrainLogic::deleteBridges() { - if (m_bridgeListHead) { - if (TheTacticalView) { - TheTacticalView->onBridgeChanged(); - } - } + Bool bridgesChanged = m_bridgeListHead != nullptr; Bridge *pNext = nullptr; Bridge *pBridge; @@ -2005,6 +2000,10 @@ void TerrainLogic::deleteBridges() deleteInstance(pBridge); } m_bridgeListHead = nullptr; + + if (bridgesChanged && TheTacticalView) { + TheTacticalView->onBridgeChanged(); + } } //------------------------------------------------------------------------------------------------- @@ -2016,9 +2015,6 @@ void TerrainLogic::deleteBridge( Bridge *bridge ) // sanity if( bridge == nullptr ) return; - if (TheTacticalView) { - TheTacticalView->onBridgeChanged(); - } // check for removing the head if( m_bridgeListHead == bridge ) @@ -2063,6 +2059,9 @@ void TerrainLogic::deleteBridge( Bridge *bridge ) // delete the bridge in question deleteInstance(bridge); + if (TheTacticalView) { + TheTacticalView->onBridgeChanged(); + } } //-------------------------------------------------------------------------------------------------