From be212f495741c2bea03ee1f413db50392b36044b Mon Sep 17 00:00:00 2001 From: Skyaero <21192585+Skyaero42@users.noreply.github.com> Date: Mon, 24 Aug 2026 21:27:43 +0200 Subject: [PATCH 1/3] refactor(pathfind): Optimize appending node to end of the path (#3198) PathNode::appendToList() walks the entire list from the head to find the tail on every call, making repeated appendNode() calls O(n^2) in path length. Path already tracks m_pathTail, so append directly onto it in O(1) instead. Removed PathNode::appendToList() as it is not used anywhere else. --- .../GameEngine/Include/GameLogic/AIPathfind.h | 4 --- .../Source/GameLogic/AI/AIPathfind.cpp | 34 ++++++------------- 2 files changed, 10 insertions(+), 28 deletions(-) diff --git a/Core/GameEngine/Include/GameLogic/AIPathfind.h b/Core/GameEngine/Include/GameLogic/AIPathfind.h index bbb40644f22..2fb18e2fb38 100644 --- a/Core/GameEngine/Include/GameLogic/AIPathfind.h +++ b/Core/GameEngine/Include/GameLogic/AIPathfind.h @@ -106,10 +106,6 @@ class PathNode : public MemoryPoolObject /// given a list, prepend this node, return new list PathNode *prependToList( PathNode *list ); - /// given a list, append this node, return new list. slow implementation. - /// @todo optimize this - PathNode *appendToList( PathNode *list ); - /// given a node, append to this node void append( PathNode *list ); diff --git a/Core/GameEngine/Source/GameLogic/AI/AIPathfind.cpp b/Core/GameEngine/Source/GameLogic/AI/AIPathfind.cpp index 4377e6d2105..7225de86a58 100644 --- a/Core/GameEngine/Source/GameLogic/AI/AIPathfind.cpp +++ b/Core/GameEngine/Source/GameLogic/AI/AIPathfind.cpp @@ -173,28 +173,6 @@ PathNode *PathNode::prependToList( PathNode *list ) return this; } -//----------------------------------------------------------------------------------- -/// given a list, append this node, return new list. slow implementation. -/// @todo optimize this -PathNode *PathNode::appendToList( PathNode *list ) -{ - if (list == nullptr) - { - m_next = nullptr; - m_prev = nullptr; - return this; - } - - PathNode *tail; - for( tail = list; tail->m_next; tail = tail->m_next ) - ; - - tail->m_next = this; - m_prev = tail; - m_next = nullptr; - - return list; -} //----------------------------------------------------------------------------------- /// given a node, append new node to this. @@ -437,9 +415,17 @@ void Path::appendNode( const Coord3D *pos, PathfindLayerEnum layer ) node->setPosition( pos ); node->setLayer(layer); - m_path = node->appendToList( m_path ); + if (!m_path) + { + m_path = node; + m_pathTail = node; - if (m_isOptimized && m_pathTail) + return; + } + + m_pathTail->append(node); + + if (m_isOptimized) { m_pathTail->setNextOptimized(node); } From dac4aa96c510ccfa73550f89682aa58fdb44022b Mon Sep 17 00:00:00 2001 From: Skyaero <21192585+Skyaero42@users.noreply.github.com> Date: Tue, 25 Aug 2026 08:34:32 +0200 Subject: [PATCH 2/3] refactor(pathfind): Take parents cell's position outside of for-loop for optimization (#3198) The parent cell's world position fromPos never changes across the neighbour loop, so compute it once instead. --- .../GameEngine/Source/GameLogic/AI/AIPathfind.cpp | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/Core/GameEngine/Source/GameLogic/AI/AIPathfind.cpp b/Core/GameEngine/Source/GameLogic/AI/AIPathfind.cpp index 7225de86a58..e7caca3a05c 100644 --- a/Core/GameEngine/Source/GameLogic/AI/AIPathfind.cpp +++ b/Core/GameEngine/Source/GameLogic/AI/AIPathfind.cpp @@ -6308,6 +6308,11 @@ Int Pathfinder::examineNeighboringCells(PathfindCell *parentCell, PathfindCell * UnsignedInt newCostSoFar = 0; + Coord3D fromPos; + fromPos.x = parentCell->getXIndex() * PATHFIND_CELL_SIZE_F ; + fromPos.y = parentCell->getYIndex() * PATHFIND_CELL_SIZE_F ; + fromPos.z = TheTerrainLogic->getGroundHeight(fromPos.x , fromPos.y); + for( int i=0; igetXIndex() * PATHFIND_CELL_SIZE_F ; - fromPos.y = parentCell->getYIndex() * PATHFIND_CELL_SIZE_F ; - fromPos.z = TheTerrainLogic->getGroundHeight(fromPos.x , fromPos.y); - Coord3D toPos; toPos.x = newCellCoord.x * PATHFIND_CELL_SIZE_F ; toPos.y = newCellCoord.y * PATHFIND_CELL_SIZE_F ; @@ -6416,11 +6416,6 @@ Int Pathfinder::examineNeighboringCells(PathfindCell *parentCell, PathfindCell * } if (newCell->getType() == PathfindCell::CELL_CLIFF && !newCell->getPinched() ) { - Coord3D fromPos; - fromPos.x = parentCell->getXIndex() * PATHFIND_CELL_SIZE_F ; - fromPos.y = parentCell->getYIndex() * PATHFIND_CELL_SIZE_F ; - fromPos.z = TheTerrainLogic->getGroundHeight(fromPos.x , fromPos.y); - Coord3D toPos; toPos.x = newCellCoord.x * PATHFIND_CELL_SIZE_F ; toPos.y = newCellCoord.y * PATHFIND_CELL_SIZE_F ; From 65c9a506d7a20150710ab1988925b09386732ffb Mon Sep 17 00:00:00 2001 From: Skyaero <21192585+Skyaero42@users.noreply.github.com> Date: Mon, 24 Aug 2026 21:33:13 +0200 Subject: [PATCH 3/3] refactor(pathfind): Remove redundant isCrusher recomputation for optimization (#3198) --- Core/GameEngine/Source/GameLogic/AI/AIPathfind.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Core/GameEngine/Source/GameLogic/AI/AIPathfind.cpp b/Core/GameEngine/Source/GameLogic/AI/AIPathfind.cpp index e7caca3a05c..92991e6ae03 100644 --- a/Core/GameEngine/Source/GameLogic/AI/AIPathfind.cpp +++ b/Core/GameEngine/Source/GameLogic/AI/AIPathfind.cpp @@ -6170,6 +6170,7 @@ struct ExamineCellsStruct const LocomotorSet *theLoco; Bool centerInCell; Bool isHuman; + Bool isCrusher; Int radius; const Object *obj; PathfindCell *goalCell; @@ -6178,10 +6179,9 @@ struct ExamineCellsStruct /*static*/ Int Pathfinder::examineCellsCallback(Pathfinder* pathfinder, PathfindCell* from, PathfindCell* to, Int to_x, Int to_y, void* userData) { ExamineCellsStruct* d = (ExamineCellsStruct*)userData; - Bool isCrusher = d->obj ? d->obj->getCrusherLevel() > 0 : false; if (d->thePathfinder->m_isTunneling) return 1; // abort. if (from && to) { - if (!d->thePathfinder->validMovementPosition( isCrusher, d->theLoco->getValidSurfaces(), to, from )) { + if (!d->thePathfinder->validMovementPosition( d->isCrusher, d->theLoco->getValidSurfaces(), to, from )) { return 1; } if ( (to->getLayer() == LAYER_GROUND) && !d->thePathfinder->m_zoneManager.isPassable(to_x, to_y) ) { @@ -6283,6 +6283,7 @@ Int Pathfinder::examineNeighboringCells(PathfindCell *parentCell, PathfindCell * info.radius = radius; info.obj = obj; info.isHuman = isHuman; + info.isCrusher = isCrusher; info.goalCell = goalCell; ICoord2D start, end; start.x = parentCell->getXIndex();