mirror of
https://github.com/vcmi/vcmi.git
synced 2025-11-06 09:09:40 +02:00
VCAI performance improvements
* Node graph initialization optimized. * Fixed "Unathorized obstacle access". * Pathfinding tracing disabled with ifdef. * Misc bonus calculation optimizations. * Removed timestamp from log lines. Date formatting eats too much CPU. * Paths for all heroes in Client are now cached
This commit is contained in:
@@ -14,16 +14,61 @@
|
||||
#include "../../../lib/mapping/CMap.h"
|
||||
#include "../../../lib/mapObjects/MapObjects.h"
|
||||
|
||||
#include "../../../lib/PathfinderUtil.h"
|
||||
#include "../../../lib/CPlayerState.h"
|
||||
extern boost::thread_specific_ptr<CCallback> cb;
|
||||
|
||||
|
||||
AINodeStorage::AINodeStorage(const int3 & Sizes)
|
||||
: sizes(Sizes)
|
||||
{
|
||||
nodes.resize(boost::extents[sizes.x][sizes.y][sizes.z][EPathfindingLayer::NUM_LAYERS][NUM_CHAINS]);
|
||||
}
|
||||
|
||||
AINodeStorage::~AINodeStorage()
|
||||
AINodeStorage::~AINodeStorage() = default;
|
||||
|
||||
void AINodeStorage::initialize(const PathfinderOptions & options, const CGameState * gs, const CGHeroInstance * hero)
|
||||
{
|
||||
//TODO: fix this code duplication with NodeStorage::initialize, problem is to keep `resetTile` inline
|
||||
|
||||
int3 pos;
|
||||
const int3 sizes = gs->getMapSize();
|
||||
const auto & fow = static_cast<const CGameInfoCallback *>(gs)->getPlayerTeam(hero->tempOwner)->fogOfWarMap;
|
||||
const PlayerColor player = hero->tempOwner;
|
||||
|
||||
//make 200% sure that these are loop invariants (also a bit shorter code), let compiler do the rest(loop unswitching)
|
||||
const bool useFlying = options.useFlying;
|
||||
const bool useWaterWalking = options.useWaterWalking;
|
||||
|
||||
for(pos.x=0; pos.x < sizes.x; ++pos.x)
|
||||
{
|
||||
for(pos.y=0; pos.y < sizes.y; ++pos.y)
|
||||
{
|
||||
for(pos.z=0; pos.z < sizes.z; ++pos.z)
|
||||
{
|
||||
const TerrainTile * tile = &gs->map->getTile(pos);
|
||||
switch(tile->terType)
|
||||
{
|
||||
case ETerrainType::ROCK:
|
||||
break;
|
||||
|
||||
case ETerrainType::WATER:
|
||||
resetTile(pos, ELayer::SAIL, PathfinderUtil::evaluateAccessibility<ELayer::SAIL>(pos, tile, fow, player, gs));
|
||||
if(useFlying)
|
||||
resetTile(pos, ELayer::AIR, PathfinderUtil::evaluateAccessibility<ELayer::AIR>(pos, tile, fow, player, gs));
|
||||
if(useWaterWalking)
|
||||
resetTile(pos, ELayer::WATER, PathfinderUtil::evaluateAccessibility<ELayer::WATER>(pos, tile, fow, player, gs));
|
||||
break;
|
||||
|
||||
default:
|
||||
resetTile(pos, ELayer::LAND, PathfinderUtil::evaluateAccessibility<ELayer::LAND>(pos, tile, fow, player, gs));
|
||||
if(useFlying)
|
||||
resetTile(pos, ELayer::AIR, PathfinderUtil::evaluateAccessibility<ELayer::AIR>(pos, tile, fow, player, gs));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const AIPathNode * AINodeStorage::getAINode(const CGPathNode * node) const
|
||||
@@ -97,7 +142,8 @@ void AINodeStorage::commit(CDestinationNodeInfo & destination, const PathNodeInf
|
||||
{
|
||||
const AIPathNode * srcNode = getAINode(source.node);
|
||||
|
||||
updateAINode(destination.node, [&](AIPathNode * dstNode) {
|
||||
updateAINode(destination.node, [&](AIPathNode * dstNode)
|
||||
{
|
||||
dstNode->moveRemains = destination.movementLeft;
|
||||
dstNode->turns = destination.turn;
|
||||
dstNode->danger = srcNode->danger;
|
||||
@@ -172,18 +218,18 @@ std::vector<CGPathNode *> AINodeStorage::calculateTeleportations(
|
||||
|
||||
if(source.isNodeObjectVisitable())
|
||||
{
|
||||
auto accessibleExits = pathfinderHelper->getTeleportExits(source);
|
||||
auto srcNode = getAINode(source.node);
|
||||
auto accessibleExits = pathfinderHelper->getTeleportExits(source);
|
||||
auto srcNode = getAINode(source.node);
|
||||
|
||||
for(auto & neighbour : accessibleExits)
|
||||
{
|
||||
auto node = getOrCreateNode(neighbour, source.node->layer, srcNode->chainMask);
|
||||
for(auto & neighbour : accessibleExits)
|
||||
{
|
||||
auto node = getOrCreateNode(neighbour, source.node->layer, srcNode->chainMask);
|
||||
|
||||
if(!node)
|
||||
continue;
|
||||
if(!node)
|
||||
continue;
|
||||
|
||||
neighbours.push_back(node.get());
|
||||
}
|
||||
neighbours.push_back(node.get());
|
||||
}
|
||||
}
|
||||
|
||||
if(hero->getPosition(false) == source.coord)
|
||||
@@ -287,7 +333,6 @@ bool AINodeStorage::hasBetterChain(const PathNodeInfo & source, CDestinationNode
|
||||
destinationNode->chainMask,
|
||||
node.moveRemains - destinationNode->moveRemains);
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ public:
|
||||
|
||||
virtual void applyOnDestination(
|
||||
HeroPtr hero,
|
||||
CDestinationNodeInfo & destination,
|
||||
CDestinationNodeInfo & destination,
|
||||
const PathNodeInfo & source,
|
||||
AIPathNode * dstMode,
|
||||
const AIPathNode * srcNode) const
|
||||
@@ -76,6 +76,9 @@ private:
|
||||
boost::multi_array<AIPathNode, 5> nodes;
|
||||
const CGHeroInstance * hero;
|
||||
|
||||
STRONG_INLINE
|
||||
void resetTile(const int3 & tile, EPathfindingLayer layer, CGPathNode::EAccessibility accessibility);
|
||||
|
||||
public:
|
||||
/// more than 1 chain layer allows us to have more than 1 path to each tile so we can chose more optimal one.
|
||||
static const int NUM_CHAINS = 3;
|
||||
@@ -89,8 +92,9 @@ public:
|
||||
AINodeStorage(const int3 & sizes);
|
||||
~AINodeStorage();
|
||||
|
||||
void initialize(const PathfinderOptions & options, const CGameState * gs, const CGHeroInstance * hero) override;
|
||||
|
||||
virtual CGPathNode * getInitialNode() override;
|
||||
virtual void resetTile(const int3 & tile, EPathfindingLayer layer, CGPathNode::EAccessibility accessibility) override;
|
||||
|
||||
virtual std::vector<CGPathNode *> calculateNeighbours(
|
||||
const PathNodeInfo & source,
|
||||
|
||||
@@ -76,12 +76,14 @@ namespace AIPathfinding
|
||||
|
||||
bool isAffordableBy(HeroPtr hero, const AIPathNode * source) const
|
||||
{
|
||||
#ifdef VCMI_TRACE_PATHFINDER
|
||||
logAi->trace(
|
||||
"Hero %s has %d mana and needed %d and already spent %d",
|
||||
hero->name,
|
||||
hero->mana,
|
||||
getManaCost(hero),
|
||||
source->manaCost);
|
||||
#endif
|
||||
|
||||
return hero->mana >= source->manaCost + getManaCost(hero);
|
||||
}
|
||||
@@ -148,7 +150,9 @@ namespace AIPathfinding
|
||||
|
||||
if(virtualBoat && tryEmbarkVirtualBoat(destination, source, virtualBoat))
|
||||
{
|
||||
#ifdef VCMI_TRACE_PATHFINDER
|
||||
logAi->trace("Embarking to virtual boat while moving %s -> %s!", source.coord.toString(), destination.coord.toString());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -242,15 +246,17 @@ namespace AIPathfinding
|
||||
}
|
||||
else
|
||||
{
|
||||
#ifdef VCMI_TRACE_PATHFINDER
|
||||
logAi->trace(
|
||||
"Special transition node already allocated. Blocked moving %s -> %s",
|
||||
source.coord.toString(),
|
||||
destination.coord.toString());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logAi->trace(
|
||||
logAi->debug(
|
||||
"Can not allocate special transition node while moving %s -> %s",
|
||||
source.coord.toString(),
|
||||
destination.coord.toString());
|
||||
@@ -330,10 +336,12 @@ namespace AIPathfinding
|
||||
auto guardsAlreadyBypassed = destGuardians.empty() && srcGuardians.size();
|
||||
if(guardsAlreadyBypassed && nodeStorage->isBattleNode(source.node))
|
||||
{
|
||||
//logAi->trace(
|
||||
// "Bypass guard at destination while moving %s -> %s",
|
||||
// source.coord.toString(),
|
||||
// destination.coord.toString());
|
||||
#ifdef VCMI_TRACE_PATHFINDER
|
||||
logAi->trace(
|
||||
"Bypass guard at destination while moving %s -> %s",
|
||||
source.coord.toString(),
|
||||
destination.coord.toString());
|
||||
#endif
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -346,10 +354,12 @@ namespace AIPathfinding
|
||||
|
||||
if(!battleNodeOptional)
|
||||
{
|
||||
//logAi->trace(
|
||||
// "Can not allocate battle node while moving %s -> %s",
|
||||
// source.coord.toString(),
|
||||
// destination.coord.toString());
|
||||
#ifdef VCMI_TRACE_PATHFINDER
|
||||
logAi->trace(
|
||||
"Can not allocate battle node while moving %s -> %s",
|
||||
source.coord.toString(),
|
||||
destination.coord.toString());
|
||||
#endif
|
||||
|
||||
destination.blocked = true;
|
||||
|
||||
@@ -360,11 +370,12 @@ namespace AIPathfinding
|
||||
|
||||
if(battleNode->locked)
|
||||
{
|
||||
//logAi->trace(
|
||||
// "Block bypass guard at destination while moving %s -> %s",
|
||||
// source.coord.toString(),
|
||||
// destination.coord.toString());
|
||||
|
||||
#ifdef VCMI_TRACE_PATHFINDER
|
||||
logAi->trace(
|
||||
"Block bypass guard at destination while moving %s -> %s",
|
||||
source.coord.toString(),
|
||||
destination.coord.toString());
|
||||
#endif
|
||||
destination.blocked = true;
|
||||
|
||||
return;
|
||||
@@ -382,13 +393,13 @@ namespace AIPathfinding
|
||||
}
|
||||
|
||||
battleNode->specialAction = std::make_shared<BattleAction>(destination.coord);
|
||||
|
||||
//logAi->trace(
|
||||
// "Begin bypass guard at destination with danger %s while moving %s -> %s",
|
||||
// std::to_string(danger),
|
||||
// source.coord.toString(),
|
||||
// destination.coord.toString());
|
||||
|
||||
#ifdef VCMI_TRACE_PATHFINDER
|
||||
logAi->trace(
|
||||
"Begin bypass guard at destination with danger %s while moving %s -> %s",
|
||||
std::to_string(danger),
|
||||
source.coord.toString(),
|
||||
destination.coord.toString());
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -428,11 +439,12 @@ namespace AIPathfinding
|
||||
|
||||
if(blocker == BlockingReason::SOURCE_GUARDED && nodeStorage->isBattleNode(source.node))
|
||||
{
|
||||
//logAi->trace(
|
||||
// "Bypass src guard while moving from %s to %s",
|
||||
// source.coord.toString(),
|
||||
// destination.coord.toString());
|
||||
|
||||
#ifdef VCMI_TRACE_PATHFINDER
|
||||
logAi->trace(
|
||||
"Bypass src guard while moving from %s to %s",
|
||||
source.coord.toString(),
|
||||
destination.coord.toString());
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -462,12 +474,12 @@ namespace AIPathfinding
|
||||
{
|
||||
// we can not directly bypass objects, we need to interact with them first
|
||||
destination.node->theNodeBefore = source.node;
|
||||
|
||||
//logAi->trace(
|
||||
// "Link src node %s to destination node %s while bypassing visitable obj",
|
||||
// source.coord.toString(),
|
||||
// destination.coord.toString());
|
||||
|
||||
#ifdef VCMI_TRACE_PATHFINDER
|
||||
logAi->trace(
|
||||
"Link src node %s to destination node %s while bypassing visitable obj",
|
||||
source.coord.toString(),
|
||||
destination.coord.toString());
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -124,14 +124,16 @@ Goals::TGoalVec PathfindingManager::findPath(
|
||||
|
||||
std::vector<AIPath> chainInfo = pathfinder->getPathInfo(hero, dest);
|
||||
|
||||
#ifdef VCMI_TRACE_PATHFINDER
|
||||
logAi->trace("Trying to find a way for %s to visit tile %s", hero->name, dest.toString());
|
||||
#endif
|
||||
|
||||
for(auto path : chainInfo)
|
||||
{
|
||||
int3 firstTileToGet = path.firstTileToGet();
|
||||
|
||||
#ifdef VCMI_TRACE_PATHFINDER
|
||||
logAi->trace("Path found size=%i, first tile=%s", path.nodes.size(), firstTileToGet.toString());
|
||||
|
||||
#endif
|
||||
if(firstTileToGet.valid() && ai->isTileNotReserved(hero.get(), firstTileToGet))
|
||||
{
|
||||
danger = path.getTotalDanger(hero);
|
||||
@@ -158,9 +160,9 @@ Goals::TGoalVec PathfindingManager::findPath(
|
||||
solution->evaluationContext.danger = danger;
|
||||
|
||||
solution->evaluationContext.movementCost += path.movementCost();
|
||||
|
||||
#ifdef VCMI_TRACE_PATHFINDER
|
||||
logAi->trace("It's safe for %s to visit tile %s with danger %s, goal %s", hero->name, dest.toString(), std::to_string(danger), solution->name());
|
||||
|
||||
#endif
|
||||
result.push_back(solution);
|
||||
|
||||
continue;
|
||||
@@ -178,7 +180,9 @@ Goals::TGoalVec PathfindingManager::findPath(
|
||||
if(allowGatherArmy && danger > 0)
|
||||
{
|
||||
//we need to get army in order to conquer that place
|
||||
#ifdef VCMI_TRACE_PATHFINDER
|
||||
logAi->trace("Gather army for %s, value=%s", hero->name, std::to_string(danger));
|
||||
#endif
|
||||
result.push_back(sptr(Goals::GatherArmy(danger * SAFE_ATTACK_CONSTANT).sethero(hero).setisAbstract(true)));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user