Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions Core/GameEngine/Include/GameLogic/AIPathfind.h
Original file line number Diff line number Diff line change
Expand Up @@ -254,16 +254,23 @@ class PathfindCellList
friend class PathfindCell;

public:
PathfindCellList() : m_head(nullptr) {}
PathfindCellList() : m_head(nullptr), m_tail(nullptr) {}

void reset(PathfindCell* newHead = nullptr) { m_head = newHead; }
#if RETAIL_COMPATIBLE_PATHFINDING
void reset(PathfindCell* newHead = nullptr) { m_head = newHead; m_tail = nullptr; }
#else
void reset() { m_head = nullptr; m_tail = nullptr; }
#endif

PathfindCell* getHead() const { return m_head; }

Bool empty() const { return m_head == nullptr; }

Bool canReverseSort(PathfindCell& currentCell) const;

private:
PathfindCell* m_head;
PathfindCell* m_tail;
};

/**
Expand Down Expand Up @@ -334,6 +341,9 @@ class PathfindCell
// Forward insertion sort, in ascending cost order
void forwardInsertionSort(PathfindCellList& list);

// Reverse insertion sort, in ascending cost order
void reverseInsertionSort(PathfindCellList& list);

/// put self on "open" list in ascending cost order
void putOnSortedOpenList( PathfindCellList &list );

Expand All @@ -353,6 +363,7 @@ class PathfindCell
static Int releaseOpenList( PathfindCellList &list );

inline PathfindCell *getNextOpen() {return m_info->m_nextOpen?m_info->m_nextOpen->m_cell: nullptr;}
inline PathfindCell *getPrevOpen() {return m_info->m_prevOpen?m_info->m_prevOpen->m_cell: nullptr;}

inline UnsignedShort getXIndex() const {return m_info->m_pos.x;}
inline UnsignedShort getYIndex() const {return m_info->m_pos.y;}
Expand All @@ -365,6 +376,8 @@ class PathfindCell
inline UnsignedInt getCostSoFar() const {return m_info->m_costSoFar;}
inline UnsignedInt getTotalCost() const {return m_info->m_totalCost;}

inline UnsignedInt getTotalCostDifference(PathfindCell& other) const;

inline void setCostSoFar(UnsignedInt cost) { if( m_info ) m_info->m_costSoFar = cost;}
inline void setTotalCost(UnsignedInt cost) { if( m_info ) m_info->m_totalCost = cost;}

Expand Down
92 changes: 88 additions & 4 deletions Core/GameEngine/Source/GameLogic/AI/AIPathfind.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1235,6 +1235,16 @@ void PathfindCellInfo::releaseACellInfo(PathfindCellInfo *theInfo)

//-----------------------------------------------------------------------------------

Bool PathfindCellList::canReverseSort(PathfindCell& currentCell) const
{
if (m_head && m_tail)
return m_head->getTotalCostDifference(currentCell) > m_tail->getTotalCostDifference(currentCell);

return false;
}

//-----------------------------------------------------------------------------------

/**
* Constructor
*/
Expand Down Expand Up @@ -1338,6 +1348,18 @@ inline void PathfindCell::setBlockedByAlly(Bool blocked)
#endif
}

/**
* Determine absolute total path cost difference between two cells.
* Returns UINT_MAX if used with an uninitialised cell, so will be sorted as maximally dissimilar.
*/
inline UnsignedInt PathfindCell::getTotalCostDifference(PathfindCell& other) const
{
if (m_info && other.m_info)
return abs((Int)m_info->m_totalCost - (Int)other.m_info->m_totalCost);

return UINT_MAX;
}

/**
* Set the parent pointer.
*/
Expand Down Expand Up @@ -1734,7 +1756,7 @@ void PathfindCell::forwardInsertionSortRetailCompatible(PathfindCellList& list)
}
#endif

// Forward insertion sort, returns early if the list is being initialised or we are prepending the list
// Forward insertion sort, returns early if the list is being initialized or we are prepending the list
void PathfindCell::forwardInsertionSort(PathfindCellList& list)
{
DEBUG_ASSERTCRASH(m_info, ("Has to have info."));
Expand All @@ -1748,6 +1770,7 @@ void PathfindCell::forwardInsertionSort(PathfindCellList& list)
m_info->m_prevOpen = nullptr;
m_info->m_nextOpen = nullptr;
list.m_head = this;
list.m_tail = this;
return;
}

Expand All @@ -1771,10 +1794,60 @@ void PathfindCell::forwardInsertionSort(PathfindCellList& list)
if (current->m_info->m_nextOpen != nullptr) {
current->m_info->m_nextOpen->m_prevOpen = this->m_info;
}
else {
list.m_tail = this;
}

current->m_info->m_nextOpen = this->m_info;
m_info->m_prevOpen = current->m_info;
}

// Reverse insertion sort, returns early if the list is being initialized or we are appending the list
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could add TheSuperHackers @performance here or elsewhere suitable and explain how this gives performance and how much.

void PathfindCell::reverseInsertionSort(PathfindCellList& list)
{
DEBUG_ASSERTCRASH(m_info, ("Has to have info."));
DEBUG_ASSERTCRASH(m_info->m_closed == FALSE && m_info->m_open == FALSE, ("Serious error - Invalid flags. jba"));

// mark the new cell as being on the open list
m_info->m_open = true;
m_info->m_closed = false;

if (list.m_tail == nullptr) {
m_info->m_prevOpen = nullptr;
m_info->m_nextOpen = nullptr;
list.m_tail = this;
list.m_head = this;
return;
}

// If the node needs inserting after the current list tail
if (m_info->m_totalCost >= list.m_tail->m_info->m_totalCost) {
m_info->m_prevOpen = list.m_tail->m_info;
list.m_tail->m_info->m_nextOpen = this->m_info;
m_info->m_nextOpen = nullptr;
list.m_tail = this;
return;
}

// Traverse the list to find correct position
PathfindCell* current = list.m_tail;
while (current->m_info->m_prevOpen && current->m_info->m_prevOpen->m_totalCost > m_info->m_totalCost) {
current = current->getPrevOpen();
}

// Insert the new node in the correct position
m_info->m_prevOpen = current->m_info->m_prevOpen;
if (current->m_info->m_prevOpen != nullptr) {
current->m_info->m_prevOpen->m_nextOpen = this->m_info;
}
else {
list.m_head = this;
}

current->m_info->m_prevOpen = this->m_info;
m_info->m_nextOpen = current->m_info;
}

/// put self on "open" list in ascending cost order, return new list
void PathfindCell::putOnSortedOpenList( PathfindCellList &list )
{
Expand All @@ -1785,7 +1858,15 @@ void PathfindCell::putOnSortedOpenList( PathfindCellList &list )
}
#endif

forwardInsertionSort(list);
// TheSuperHackers @performance Mauller 20/03/2026 Implement reverse insertion sorting.
// Long and complex paths often append PathfindCell's, with high total path costs, to the open list.
// Appending and reverse traversal allow faster insertion of these cells, reducing pathfinding overhead by 50 - 66%.
if (list.canReverseSort(*this)) {
reverseInsertionSort(list);
}
else {
forwardInsertionSort(list);
}
}

/// remove self from "open" list
Expand All @@ -1795,6 +1876,9 @@ void PathfindCell::removeFromOpenList( PathfindCellList &list )
DEBUG_ASSERTCRASH(m_info->m_closed==FALSE && m_info->m_open==TRUE, ("Serious error - Invalid flags. jba"));
if (m_info->m_nextOpen)
m_info->m_nextOpen->m_prevOpen = m_info->m_prevOpen;
else {
list.m_tail = getPrevOpen();
}

if (m_info->m_prevOpen)
m_info->m_prevOpen->m_nextOpen = m_info->m_nextOpen;
Expand Down Expand Up @@ -1832,7 +1916,7 @@ Int PathfindCell::releaseOpenList( PathfindCellList &list )
if (curInfo->m_nextOpen) {
list.m_head = curInfo->m_nextOpen->m_cell;
} else {
list.m_head = nullptr;
list.reset();
}
DEBUG_ASSERTCRASH(cur == curInfo->m_cell, ("Bad backpointer in PathfindCellInfo"));
curInfo->m_nextOpen = nullptr;
Expand Down Expand Up @@ -1867,7 +1951,7 @@ Int PathfindCell::releaseClosedList( PathfindCellList &list )
if (curInfo->m_nextOpen) {
list.m_head = curInfo->m_nextOpen->m_cell;
} else {
list.m_head = nullptr;
list.reset();
}
DEBUG_ASSERTCRASH(cur == curInfo->m_cell, ("Bad backpointer in PathfindCellInfo"));
curInfo->m_nextOpen = nullptr;
Expand Down
Loading