1
0
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:
AlexVinS
2019-01-15 06:00:00 +03:00
parent 8fee46de7c
commit 4b5910c2f4
25 changed files with 472 additions and 347 deletions

View File

@@ -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;
}
}

View File

@@ -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,

View File

@@ -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;
}

View File

@@ -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)));
}