Skip to content

Commit

Permalink
Changed the core of KmodPathFinder to use a single block of memory.
Browse files Browse the repository at this point in the history
node_map is now a multi_array<FAStarNode, 2>, and the path finder no
longer uses any shared_ptr.

This change will (usually) use a bit more memory than the previous
implementation, but I expect it to be significantly faster, because
there will be fewer memory lookups and far fewer allocations and
deallocations.

Initial testing suggests this makes the path finder around 25% faster,
but that it is still only a tiny speed boost overall. Apparently I
forgot that the path finding is no longer a large part of the total CPU
time.
  • Loading branch information
karadoc committed Oct 7, 2014
1 parent e0cb8b8 commit c8a3e19
Show file tree
Hide file tree
Showing 2 changed files with 110 additions and 67 deletions.
160 changes: 100 additions & 60 deletions CvGameCoreDLL/KmodPathFinder.cpp
Expand Up @@ -39,15 +39,47 @@ int KmodPathFinder::MinimumStepCost(int BaseMoves)
return std::max(1, std::min(admissible_base_weight, BaseMoves * admissible_scaled_weight));
}

bool KmodPathFinder::OpenList_sortPred::operator()(const boost::shared_ptr<FAStarNode> &left, const boost::shared_ptr<FAStarNode> &right)
bool KmodPathFinder::OpenList_sortPred::operator()(const FAStarNode* &left, const FAStarNode* &right)
{
return left->m_iTotalCost < right->m_iTotalCost;
}

//
KmodPathFinder::KmodPathFinder() :
node_map(boost::extents[0][0]),
end_node(0)
{
// Unfortunately, the pathfinder is constructed before the map width and height are determined.

// Ideally the pathfinder would be initialised with a given CvMap, and then not refer to any global objects.
// But that is not easy to do with the current code-base. Instead I'll just check the pathfinder settings
// at particular times to make sure the map hasn't changed.

}

bool KmodPathFinder::ValidateNodeMap()
{
if (!GC.getGameINLINE().isFinalInitialized())
return false;

if (node_map.shape()[0] != GC.getMapINLINE().getGridWidthINLINE() || node_map.shape()[1] != GC.getMapINLINE().getGridHeightINLINE())
{
node_map.resize(boost::extents[GC.getMapINLINE().getGridWidthINLINE()][GC.getMapINLINE().getGridHeightINLINE()]);
end_node = NULL;
}
return true;
}

bool KmodPathFinder::GeneratePath(int x1, int y1, int x2, int y2)
{
PROFILE_FUNC();
end_node.reset();

FASSERT_BOUNDS(0, (int)node_map.shape()[0] , x1, "GeneratePath");
FASSERT_BOUNDS(0, (int)node_map.shape()[1], y1, "GeneratePath");
FASSERT_BOUNDS(0, (int)node_map.shape()[0] , x2, "GeneratePath");
FASSERT_BOUNDS(0, (int)node_map.shape()[1], y2, "GeneratePath");

end_node = NULL;

if (!settings.pGroup)
return false;
Expand All @@ -73,39 +105,29 @@ bool KmodPathFinder::GeneratePath(int x1, int y1, int x2, int y2)
dest_x = x2;
dest_y = y2;

int iPlotNum = GC.getMapINLINE().plotNumINLINE(x1, y1);
FAssert(iPlotNum >= 0 && iPlotNum < GC.getMapINLINE().numPlotsINLINE());

NodeMap_t::iterator it = node_map.find(iPlotNum);
if (it != node_map.end())
if (node_map[x1][y1].m_bOnStack)
{
int iMoves = (settings.iFlags & MOVE_MAX_MOVES) ? settings.pGroup->maxMoves() : settings.pGroup->movesLeft();
if (iMoves != it->second->m_iData1)
if (iMoves != node_map[x1][y1].m_iData1)
{
Reset();
it = node_map.end();
FAssert(!node_map[x1][y1].m_bOnStack);
}
// Note: This condition isn't actually enough to catch all signifiant changes.
// We really need to check max moves /and/ moves left /and/ base moves.
// but I don't feel like doing all that at the moment.
}

if (node_map.empty())
if (!node_map[x1][y1].m_bOnStack)
{
FAssert(it == node_map.end());
AddStartNode();
bRecalcHeuristics = true;
}
//else (not else. maybe start == dest)
{
// check if the end plot is already mapped.
int iEndPlot = GC.getMapINLINE().plotNumINLINE(x2, y2);
FAssert(iEndPlot >= 0 && iEndPlot < GC.getMapINLINE().numPlotsINLINE());

NodeMap_t::iterator end_it = node_map.find(iEndPlot);

if (end_it != node_map.end())
end_node = end_it->second;
if (node_map[x2][y2].m_bOnStack)
end_node = &node_map[x2][y2];
}

if (bRecalcHeuristics)
Expand Down Expand Up @@ -148,7 +170,7 @@ CvPlot* KmodPathFinder::GetPathFirstPlot() const
if (!end_node)
return NULL;

FAStarNode* node = end_node.get();
FAStarNode* node = end_node;

if (!node->m_pParent)
return GC.getMapINLINE().plotSorenINLINE(node->m_iX, node->m_iY);
Expand All @@ -165,7 +187,7 @@ CvPlot* KmodPathFinder::GetPathEndTurnPlot() const
{
FAssert(end_node);

FAStarNode* node = end_node.get();
FAStarNode* node = end_node;

FAssert(!node || node->m_iData2 == 1 || node->m_pParent);

Expand All @@ -179,6 +201,14 @@ CvPlot* KmodPathFinder::GetPathEndTurnPlot() const

void KmodPathFinder::SetSettings(const CvPathSettings& new_settings)
{
// whenever settings are changed, check that we have the right map size.
if (!ValidateNodeMap())
{
FAssertMsg(false, "Failed to validate node map for pathfinder.");
settings.pGroup = NULL;
return;
}

// some flags are not relevant to pathfinder. We should try to strip those out to avoid unnecessary resets.
int relevant_flags = ~(MOVE_DIRECT_ATTACK | MOVE_SINGLE_ATTACK | MOVE_NO_ATTACK); // any bar these

Expand Down Expand Up @@ -222,24 +252,32 @@ void KmodPathFinder::SetSettings(const CvPathSettings& new_settings)

void KmodPathFinder::Reset()
{
node_map.clear();
memset(node_map.data(), 0, sizeof(NodeMap_t::element)*node_map.num_elements());
open_list.clear();
// start & dest don't matter.
end_node = NULL;
// settings is set separately.
}

void KmodPathFinder::AddStartNode()
{
FASSERT_BOUNDS(0, (int)node_map.shape()[0], start_x, "KmodPathFinder::AddStartNode");
FASSERT_BOUNDS(0, (int)node_map.shape()[1], start_y, "KmodPathFinder::AddStartNode");

// add initial node.
boost::shared_ptr<FAStarNode> start_node(new FAStarNode);
FAStarNode* start_node = &node_map[start_x][start_y];
start_node->m_iX = start_x;
start_node->m_iY = start_y;
pathAdd(0, start_node.get(), ASNC_INITIALADD, &settings, 0);
pathAdd(0, start_node, ASNC_INITIALADD, &settings, 0);
start_node->m_iKnownCost = 0;

node_map[GC.getMapINLINE().plotNumINLINE(start_x, start_y)] = start_node;
open_list.push_back(start_node);
start_node->m_eFAStarListType = FASTARLIST_OPEN;

// Note: I'd like to use NO_FASTARLIST as a signal that the node is uninitialised, but unfortunately
// the default value for m_eFAStarListType is FASTARLIST_OPEN, because FASTARLIST_OPEN == 0.
// Hence KmodPathFinder uses m_bOnStack to mean the node is connected. m_eFAStarListType is initialised manually.

start_node->m_eFAStarListType = FASTARLIST_OPEN; // This means nothing. But maybe one day I'll use it.
start_node->m_bOnStack = true; // This means the node is connected and ready to be used.
}

void KmodPathFinder::RecalculateHeuristics()
Expand Down Expand Up @@ -273,14 +311,14 @@ bool KmodPathFinder::ProcessNode()
if (best_it == open_list.end())
return false;

boost::shared_ptr<FAStarNode> parent_node = (*best_it);
FAStarNode* parent_node = (*best_it);

// erase the node from the open_list.
// Note: this needs to be done before pushing new entries, otherwise the iterator will be invalid.
open_list.erase(best_it);
parent_node->m_eFAStarListType = FASTARLIST_CLOSED;

FAssert(node_map[GC.getMapINLINE().plotNumINLINE(parent_node->m_iX, parent_node->m_iY)] == parent_node);
FAssert(&node_map[parent_node->m_iX][parent_node->m_iY] == parent_node);

// open a new node for each direction coming off the chosen node.
for (int i = 0; i < NUM_DIRECTION_TYPES; i++)
Expand All @@ -295,54 +333,62 @@ bool KmodPathFinder::ProcessNode()
if (parent_node->m_pParent && parent_node->m_pParent->m_iX == x && parent_node->m_pParent->m_iY == y)
continue; // no need to backtrack.

int iPlotNum = GC.getMapINLINE().plotNumINLINE(x, y);
FAssert(iPlotNum >= 0 && iPlotNum < GC.getMapINLINE().numPlotsINLINE());

NodeMap_t::iterator it = node_map.find(iPlotNum);
bool bNewNode = it == node_map.end();
//int iPlotNum = GC.getMapINLINE().plotNumINLINE(x, y);
//FAssert(iPlotNum >= 0 && iPlotNum < GC.getMapINLINE().numPlotsINLINE());

boost::shared_ptr<FAStarNode> child_node;
FAStarNode* child_node = &node_map[x][y];
bool bNewNode = !child_node->m_bOnStack;

if (bNewNode)
{
child_node.reset(new FAStarNode);
child_node->m_iX = x;
child_node->m_iY = y;
pathAdd(parent_node.get(), child_node.get(), ASNC_NEWADD, &settings, 0);
pathAdd(parent_node, child_node, ASNC_NEWADD, &settings, 0);

child_node->m_iKnownCost = MAX_INT;
child_node->m_iHeuristicCost = settings.iHeuristicWeight * pathHeuristic(x, y, dest_x, dest_y);
// total will be set when the parent is set.
if (pathValid_join(parent_node.get(), child_node.get(), settings.pGroup , settings.iFlags))
if (pathValid_join(parent_node, child_node, settings.pGroup , settings.iFlags))
{
node_map[iPlotNum] = child_node;
// This path to the new node is valid. So we need to fill in the data.
child_node->m_iKnownCost = MAX_INT;
child_node->m_iHeuristicCost = settings.iHeuristicWeight * pathHeuristic(x, y, dest_x, dest_y);
// total cost will be set when the parent is set.

child_node->m_bOnStack = true;

if (pathValid_source(child_node, settings.pGroup , settings.iFlags))
{
open_list.push_back(child_node);
child_node->m_eFAStarListType = FASTARLIST_OPEN;
}
else
{
child_node->m_eFAStarListType = FASTARLIST_CLOSED; // This node is a dead end.
}
}
else
{
// can't get to the plot from here.
child_node.reset();
child_node = NULL;
}
}
else
{
if (pathValid_join(parent_node.get(), it->second.get(), settings.pGroup , settings.iFlags))
child_node = it->second;
if (!pathValid_join(parent_node, child_node, settings.pGroup , settings.iFlags))
child_node = NULL;
}

if (!child_node)
if (child_node == NULL)
continue;

FAssert(child_node->m_iX == x && child_node->m_iY == y);
FAssert(node_map[iPlotNum] == child_node);

if (iPlotNum == GC.getMapINLINE().plotNumINLINE(dest_x, dest_y))
if (x == dest_x && y == dest_y)
end_node = child_node; // We've found our destination - but we still need to finish our calculations

if (parent_node->m_iKnownCost < child_node->m_iKnownCost)
{
int iNewCost = parent_node->m_iKnownCost
+ pathCost(parent_node.get(), child_node.get(), 666, &settings, 0);
int iNewCost = parent_node->m_iKnownCost + pathCost(parent_node, child_node, 666, &settings, 0);
FAssert(iNewCost > 0);

if (iNewCost < child_node->m_iKnownCost)
{
int cost_delta = iNewCost - child_node->m_iKnownCost; // new minus old. Negative value.
Expand All @@ -360,7 +406,7 @@ bool KmodPathFinder::ProcessNode()
int temp = x_parent->m_iNumChildren;
for (int j = 0; j < x_parent->m_iNumChildren; j++)
{
if (x_parent->m_apChildren[j] == child_node.get())
if (x_parent->m_apChildren[j] == child_node)
{
// found it.
for (j++ ; j < x_parent->m_iNumChildren; j++)
Expand All @@ -372,26 +418,20 @@ bool KmodPathFinder::ProcessNode()
}
FAssert(x_parent->m_iNumChildren == temp -1);
// recalculate movement points
pathAdd(parent_node.get(), child_node.get(), ASNC_PARENTADD_UP, &settings, 0);
pathAdd(parent_node, child_node, ASNC_PARENTADD_UP, &settings, 0);
}

// add child to the list of the new parent
FAssert(parent_node->m_iNumChildren < NUM_DIRECTION_TYPES);
parent_node->m_apChildren[parent_node->m_iNumChildren] = child_node.get();
parent_node->m_apChildren[parent_node->m_iNumChildren] = child_node;
parent_node->m_iNumChildren++;
child_node->m_pParent = parent_node.get();
child_node->m_pParent = parent_node;

// update the new (reduced) costs for all the grandchildren.
FAssert(child_node->m_iNumChildren == 0 || !bNewNode);
ForwardPropagate(child_node.get(), cost_delta);
ForwardPropagate(child_node, cost_delta);

FAssert(child_node->m_iKnownCost > parent_node->m_iKnownCost);

if (child_node->m_eFAStarListType == NO_FASTARLIST && pathValid_source(child_node.get(), settings.pGroup , settings.iFlags))
{
open_list.push_back(child_node);
child_node->m_eFAStarListType = FASTARLIST_OPEN;
}
}
}
// else parent has higher cost. So there must already be a faster route to the child.
Expand Down
17 changes: 10 additions & 7 deletions CvGameCoreDLL/KmodPathFinder.h
@@ -1,7 +1,6 @@
#pragma once

#include <hash_map>
#include <boost/shared_ptr.hpp>
#include <boost/multi_array.hpp>
#include <vector>

struct CvPathSettings
Expand All @@ -22,9 +21,13 @@ class KmodPathFinder
static void InitHeuristicWeights();
static int MinimumStepCost(int BaseMoves);

KmodPathFinder();

bool ValidateNodeMap(); // Called when SetSettings is used.

bool GeneratePath(int x1, int y1, int x2, int y2);
bool GeneratePath(const CvPlot* pToPlot); // just a wrapper for convenience
FAStarNode* GetEndNode() const { FAssert(end_node); return end_node.get(); } // Note: the returned pointer becomes invalid if the pathfinder is destroyed.
FAStarNode* GetEndNode() const { FAssert(end_node); return end_node; } // Note: the returned pointer becomes invalid if the pathfinder is destroyed.
bool IsPathComplete() const { return end_node; }
int GetPathTurns() const;
int GetFinalMoves() const;
Expand All @@ -39,20 +42,20 @@ class KmodPathFinder
void RecalculateHeuristics();
bool ProcessNode();
void ForwardPropagate(FAStarNode* head, int cost_delta);
typedef stdext::hash_map<int, boost::shared_ptr<FAStarNode> > NodeMap_t;
typedef std::vector<boost::shared_ptr<FAStarNode> > OpenList_t;
typedef boost::multi_array<FAStarNode, 2> NodeMap_t;
typedef std::vector<FAStarNode*> OpenList_t;

struct OpenList_sortPred
{
bool operator()(const boost::shared_ptr<FAStarNode> &left, const boost::shared_ptr<FAStarNode> &right);
bool operator()(const FAStarNode* &left, const FAStarNode* &right);
};

NodeMap_t node_map;
OpenList_t open_list;

int dest_x, dest_y;
int start_x, start_y;
boost::shared_ptr<FAStarNode> end_node;
FAStarNode* end_node;
CvPathSettings settings;

static int admissible_scaled_weight;
Expand Down

0 comments on commit c8a3e19

Please sign in to comment.