From 8c4ec9ce915d7d6a18157b5ae525276430fb5374 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 | 31 +++++-------------- 2 files changed, 8 insertions(+), 27 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..c8e809b928c 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,7 +415,14 @@ void Path::appendNode( const Coord3D *pos, PathfindLayerEnum layer ) node->setPosition( pos ); node->setLayer(layer); - m_path = node->appendToList( m_path ); + if (m_pathTail) + { + m_pathTail->append(node); + } + else + { + m_path = node->prependToList(nullptr); + } if (m_isOptimized && m_pathTail) { From a95690cbba22c56c1c7aff722f926a5d2a80ead2 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 c8e809b928c..93aba2cf446 100644 --- a/Core/GameEngine/Source/GameLogic/AI/AIPathfind.cpp +++ b/Core/GameEngine/Source/GameLogic/AI/AIPathfind.cpp @@ -6307,6 +6307,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 ; @@ -6415,11 +6415,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 c95c628c0b9b321c2f20d8ba73702adea1327712 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 93aba2cf446..5aa4c97de07 100644 --- a/Core/GameEngine/Source/GameLogic/AI/AIPathfind.cpp +++ b/Core/GameEngine/Source/GameLogic/AI/AIPathfind.cpp @@ -6169,6 +6169,7 @@ struct ExamineCellsStruct const LocomotorSet *theLoco; Bool centerInCell; Bool isHuman; + Bool isCrusher; Int radius; const Object *obj; PathfindCell *goalCell; @@ -6177,10 +6178,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) ) { @@ -6282,6 +6282,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();