1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-08-15 20:03:15 +02:00

Nullkiller: small optimization of AIPathfinder for big maps

This commit is contained in:
Andrii Danylchenko
2021-05-16 14:56:13 +03:00
committed by Andrii Danylchenko
parent 07b6b0605c
commit 5bfe71c8f3
13 changed files with 115 additions and 58 deletions

View File

@@ -351,4 +351,11 @@ bool isWeeklyRevisitable(const CGObjectInstance * obj)
return (dynamic_cast<const CGKeys *>(obj))->wasMyColorVisited(ai->playerID); //FIXME: they could be revisited sooner than in a week
}
return false;
}
uint64_t timeElapsed(boost::chrono::time_point<boost::chrono::steady_clock> start)
{
auto end = boost::chrono::high_resolution_clock::now();
return boost::chrono::duration_cast<boost::chrono::milliseconds>(end - start).count();
}

View File

@@ -182,6 +182,8 @@ bool compareHeroStrength(HeroPtr h1, HeroPtr h2);
bool compareArmyStrength(const CArmedInstance * a1, const CArmedInstance * a2);
bool compareArtifacts(const CArtifactInstance * a1, const CArtifactInstance * a2);
uint64_t timeElapsed(boost::chrono::time_point<boost::chrono::steady_clock> start);
class CDistanceSorter
{
const CGHeroInstance * hero;

View File

@@ -16,14 +16,17 @@ void DangerHitMapAnalyzer::updateHitMap()
if(upToDate)
return;
logAi->trace("Update danger hitmap");
upToDate = true;
auto start = boost::chrono::high_resolution_clock::now();
auto cb = ai->cb.get();
auto mapSize = ai->cb->getMapSize();
hitMap.resize(boost::extents[mapSize.x][mapSize.y][mapSize.z]);
enemyHeroAccessibleObjects.clear();
std::map<PlayerColor, std::vector<const CGHeroInstance *>> heroes;
std::map<PlayerColor, std::map<const CGHeroInstance *, HeroRole>> heroes;
for(const CGObjectInstance * obj : ai->memory->visitableObjs)
{
@@ -31,7 +34,7 @@ void DangerHitMapAnalyzer::updateHitMap()
{
auto hero = dynamic_cast<const CGHeroInstance *>(obj);
heroes[hero->tempOwner].push_back(hero);
heroes[hero->tempOwner][hero] = HeroRole::MAIN;
}
}
@@ -83,6 +86,8 @@ void DangerHitMapAnalyzer::updateHitMap()
}
});
}
logAi->trace("Danger hit map updated in %ld", timeElapsed(start));
}
uint64_t DangerHitMapAnalyzer::enemyCanKillOurHeroesAlongThePath(const AIPath & path) const

View File

@@ -172,6 +172,8 @@ bool ObjectClusterizer::shouldVisitObject(const CGObjectInstance * obj) const
void ObjectClusterizer::clusterize()
{
auto start = boost::chrono::high_resolution_clock::now();
nearObjects.reset();
farObjects.reset();
blockedObjects.clear();
@@ -276,4 +278,6 @@ void ObjectClusterizer::clusterize()
}
#endif
}
logAi->trace("Clusterization complete in %ld", timeElapsed(start));
}

View File

@@ -62,6 +62,7 @@ Goals::TTask Nullkiller::choseBestTask(Goals::TSubgoal behavior) const
Goals::TGoalVec goals[MAX_DEPTH + 1];
Goals::TTaskVec tasks;
std::map<Goals::TSubgoal, Goals::TSubgoal> decompositionMap;
auto start = boost::chrono::high_resolution_clock::now();
goals[0] = {behavior};
@@ -127,14 +128,19 @@ Goals::TTask Nullkiller::choseBestTask(Goals::TSubgoal behavior) const
if(tasks.empty())
{
logAi->debug("Behavior %s found no tasks", behavior->toString());
logAi->debug("Behavior %s found no tasks. Time taken %ld", behavior->toString(), timeElapsed(start));
return Goals::taskptr(Goals::Invalid());
}
auto task = choseBestTask(tasks);
logAi->debug("Behavior %s returns %s, priority %f", behavior->toString(), task->toString(), task->priority);
logAi->debug(
"Behavior %s returns %s, priority %f. Time taken %ld",
behavior->toString(),
task->toString(),
task->priority,
timeElapsed(start));
return task;
}
@@ -148,26 +154,34 @@ void Nullkiller::resetAiState()
void Nullkiller::updateAiState()
{
auto start = boost::chrono::high_resolution_clock::now();
activeHero = nullptr;
memory->removeInvisibleObjects(cb.get());
dangerHitMap->updateHitMap();
auto activeHeroes = cb->getHeroesInfo();
heroManager->update();
logAi->trace("Updating paths");
vstd::erase_if(activeHeroes, [this](const CGHeroInstance * hero) -> bool
std::map<const CGHeroInstance *, HeroRole> activeHeroes;
for(auto hero : cb->getHeroesInfo())
{
auto lockedReason = getHeroLockedReason(hero);
if(getHeroLockedReason(hero) == HeroLockedReason::DEFENCE)
continue;
return lockedReason == HeroLockedReason::DEFENCE;
});
activeHeroes[hero] = heroManager->getHeroRole(hero);
}
pathfinder->updatePaths(activeHeroes, true);
heroManager->update();
armyManager->update();
objectClusterizer->clusterize();
buildAnalyzer->update();
logAi->debug("AI state updated in %ld", timeElapsed(start));
}
bool Nullkiller::isHeroLocked(const CGHeroInstance * hero) const

View File

@@ -640,13 +640,6 @@ EvaluationContext PriorityEvaluator::buildEvaluationContext(Goals::TSubgoal goal
return context;
}
/// distance
/// nearest hero?
/// gold income
/// army income
/// hero strength - hero skills
/// danger
/// importance
float PriorityEvaluator::evaluate(Goals::TSubgoal task)
{
auto evaluationContext = buildEvaluationContext(task);
@@ -677,7 +670,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task)
fearVariable->setValue(evaluationContext.enemyHeroDangerRatio);
engine->process();
//engine.process(VISIT_TILE); //TODO: Process only Visit_Tile
result = value->getValue();
}
catch(fl::Exception & fe)

View File

@@ -614,22 +614,45 @@ const std::set<const CGHeroInstance *> AINodeStorage::getAllHeroes() const
return heroes;
}
void AINodeStorage::setHeroes(std::vector<const CGHeroInstance *> heroes)
bool AINodeStorage::isDistanceLimitReached(const PathNodeInfo & source, CDestinationNodeInfo & destination) const
{
if(heroChainPass == EHeroChainPass::CHAIN && destination.node->turns > heroChainTurn)
{
return true;
}
auto aiNode = getAINode(destination.node);
if(heroChainPass == EHeroChainPass::FINAL)
{
if(aiNode->actor->heroRole == HeroRole::SCOUT && destination.node->turns > 3)
return true;
}
else if(heroChainPass == EHeroChainPass::INITIAL)
{
if(aiNode->actor->heroRole == HeroRole::SCOUT && destination.node->turns > 5)
return true;
}
return false;
}
void AINodeStorage::setHeroes(std::map<const CGHeroInstance *, HeroRole> heroes)
{
playerID = ai->playerID;
for(auto & hero : heroes)
{
uint64_t mask = 1 << actors.size();
auto actor = std::make_shared<HeroActor>(hero, mask, ai);
auto actor = std::make_shared<HeroActor>(hero.first, hero.second, mask, ai);
if(hero->tempOwner != ai->playerID)
if(actor->hero->tempOwner != ai->playerID)
{
bool onLand = !actor->hero->boat;
actor->initialMovement = actor->hero->maxMovePoints(onLand);
}
playerID = hero->tempOwner;
playerID = actor->hero->tempOwner;
actors.push_back(actor);
}
@@ -926,7 +949,7 @@ bool AINodeStorage::hasBetterChain(
}
}
if(candidateActor->chainMask != node.actor->chainMask)
if(candidateActor->chainMask != node.actor->chainMask && heroChainPass == EHeroChainPass::CHAIN)
continue;
auto nodeActor = node.actor;
@@ -949,29 +972,32 @@ bool AINodeStorage::hasBetterChain(
return true;
}
/*if(nodeArmyValue == candidateArmyValue
&& nodeActor->heroFightingStrength >= candidateActor->heroFightingStrength
&& node.cost <= candidateNode->cost)
if(heroChainPass == EHeroChainPass::FINAL)
{
if(nodeActor->heroFightingStrength == candidateActor->heroFightingStrength
&& node.cost == candidateNode->cost
&& &node < candidateNode)
if(nodeArmyValue == candidateArmyValue
&& nodeActor->heroFightingStrength >= candidateActor->heroFightingStrength
&& node.cost <= candidateNode->cost)
{
continue;
}
if(nodeActor->heroFightingStrength == candidateActor->heroFightingStrength
&& node.cost == candidateNode->cost
&& &node < candidateNode)
{
continue;
}
#if AI_TRACE_LEVEL >= 2
logAi->trace(
"Block ineficient move because of stronger hero %s->%s, hero: %s[%X], army %lld, mp diff: %i",
source->coord.toString(),
candidateNode->coord.toString(),
candidateNode->actor->hero->name,
candidateNode->actor->chainMask,
candidateNode->actor->armyValue,
node.moveRemains - candidateNode->moveRemains);
logAi->trace(
"Block ineficient move because of stronger hero %s->%s, hero: %s[%X], army %lld, mp diff: %i",
source->coord.toString(),
candidateNode->coord.toString(),
candidateNode->actor->hero->name,
candidateNode->actor->chainMask,
candidateNode->actor->armyValue,
node.moveRemains - candidateNode->moveRemains);
#endif
return true;
}*/
return true;
}
}
}
return false;
@@ -1170,7 +1196,7 @@ std::string AIPath::toString() const
{
std::stringstream str;
str << targetHero->name << "[" << std::hex << chainMask << std::dec << "]" << ": ";
str << targetHero->name << "[" << std::hex << chainMask << std::dec << "]" << ", turn " << (int)(turn()) << ": ";
for(auto node : nodes)
str << node.targetHero->name << "[" << std::hex << node.chainMask << std::dec << "]" << "->" << node.coord.toString() << "; ";

View File

@@ -11,7 +11,7 @@
#pragma once
#define PATHFINDER_TRACE_LEVEL 0
#define AI_TRACE_LEVEL 1
#define AI_TRACE_LEVEL 0
#include "../../../lib/CPathfinder.h"
#include "../../../lib/mapObjects/CGHeroInstance.h"
@@ -162,10 +162,7 @@ public:
return hasBetterChain(source, destination);
}
bool isDistanceLimitReached(const PathNodeInfo & source, CDestinationNodeInfo & destination) const
{
return heroChainPass == EHeroChainPass::CHAIN && destination.node->turns > heroChainTurn;
}
bool isDistanceLimitReached(const PathNodeInfo & source, CDestinationNodeInfo & destination) const;
template<class NodeRange>
bool hasBetterChain(
@@ -176,7 +173,7 @@ public:
boost::optional<AIPathNode *> getOrCreateNode(const int3 & coord, const EPathfindingLayer layer, const ChainActor * actor);
std::vector<AIPath> getChainInfo(const int3 & pos, bool isOnLand) const;
bool isTileAccessible(const HeroPtr & hero, const int3 & pos, const EPathfindingLayer layer) const;
void setHeroes(std::vector<const CGHeroInstance *> heroes);
void setHeroes(std::map<const CGHeroInstance *, HeroRole> heroes);
void setTownsAndDwellings(
const std::vector<const CGTownInstance *> & towns,
const std::set<const CGObjectInstance *> & visitableObjs);

View File

@@ -42,13 +42,14 @@ std::vector<AIPath> AIPathfinder::getPathInfo(const int3 & tile) const
return storage->getChainInfo(tile, !tileInfo->isWater());
}
void AIPathfinder::updatePaths(std::vector<const CGHeroInstance *> heroes, bool useHeroChain)
void AIPathfinder::updatePaths(std::map<const CGHeroInstance *, HeroRole> heroes, bool useHeroChain)
{
if(!storage)
{
storage.reset(new AINodeStorage(ai, cb->getMapSize()));
}
auto start = boost::chrono::high_resolution_clock::now();
logAi->debug("Recalculate all paths");
int pass = 0;
@@ -89,4 +90,6 @@ void AIPathfinder::updatePaths(std::vector<const CGHeroInstance *> heroes, bool
cb->calculatePaths(config);
}
} while(storage->increaseHeroChainTurnLimit());
logAi->trace("Recalculated paths in %ld", timeElapsed(start));
}

View File

@@ -26,6 +26,6 @@ public:
AIPathfinder(CPlayerSpecificInfoCallback * cb, Nullkiller * ai);
std::vector<AIPath> getPathInfo(const int3 & tile) const;
bool isTileAccessible(const HeroPtr & hero, const int3 & tile) const;
void updatePaths(std::vector<const CGHeroInstance *> heroes, bool useHeroChain = false);
void updatePaths(std::map<const CGHeroInstance *, HeroRole> heroes, bool useHeroChain = false);
void init();
};

View File

@@ -22,8 +22,8 @@ bool HeroExchangeArmy::needsLastStack() const
return true;
}
ChainActor::ChainActor(const CGHeroInstance * hero, uint64_t chainMask)
:hero(hero), isMovable(true), chainMask(chainMask), creatureSet(hero),
ChainActor::ChainActor(const CGHeroInstance * hero, HeroRole heroRole, uint64_t chainMask)
:hero(hero), heroRole(heroRole), isMovable(true), chainMask(chainMask), creatureSet(hero),
baseActor(this), carrierParent(nullptr), otherParent(nullptr), actorExchangeCount(1), armyCost()
{
initialPosition = hero->visitablePos();
@@ -35,7 +35,7 @@ ChainActor::ChainActor(const CGHeroInstance * hero, uint64_t chainMask)
}
ChainActor::ChainActor(const ChainActor * carrier, const ChainActor * other, const CCreatureSet * heroArmy)
:hero(carrier->hero), isMovable(true), creatureSet(heroArmy), chainMask(carrier->chainMask | other->chainMask),
:hero(carrier->hero), heroRole(carrier->heroRole), isMovable(true), creatureSet(heroArmy), chainMask(carrier->chainMask | other->chainMask),
baseActor(this), carrierParent(carrier), otherParent(other), heroFightingStrength(carrier->heroFightingStrength),
actorExchangeCount(carrier->actorExchangeCount + other->actorExchangeCount), armyCost(carrier->armyCost + other->armyCost)
{
@@ -43,7 +43,7 @@ ChainActor::ChainActor(const ChainActor * carrier, const ChainActor * other, con
}
ChainActor::ChainActor(const CGObjectInstance * obj, const CCreatureSet * creatureSet, uint64_t chainMask, int initialTurn)
:hero(nullptr), isMovable(false), creatureSet(creatureSet), chainMask(chainMask),
:hero(nullptr), heroRole(HeroRole::MAIN), isMovable(false), creatureSet(creatureSet), chainMask(chainMask),
baseActor(this), carrierParent(nullptr), otherParent(nullptr), initialTurn(initialTurn), initialMovement(0),
heroFightingStrength(0), actorExchangeCount(1), armyCost()
{
@@ -72,8 +72,8 @@ std::string ObjectActor::toString() const
return object->getObjectName() + " at " + object->visitablePos().toString();
}
HeroActor::HeroActor(const CGHeroInstance * hero, uint64_t chainMask, const Nullkiller * ai)
:ChainActor(hero, chainMask)
HeroActor::HeroActor(const CGHeroInstance * hero, HeroRole heroRole, uint64_t chainMask, const Nullkiller * ai)
:ChainActor(hero, heroRole, chainMask)
{
exchangeMap = new HeroExchangeMap(this, ai);
setupSpecialActors();
@@ -94,6 +94,7 @@ void ChainActor::setBaseActor(HeroActor * base)
{
baseActor = base;
hero = base->hero;
heroRole = base->heroRole;
layer = base->layer;
initialMovement = base->initialMovement;
initialTurn = base->initialTurn;

View File

@@ -27,7 +27,7 @@ public:
class ChainActor
{
protected:
ChainActor(const CGHeroInstance * hero, uint64_t chainMask);
ChainActor(const CGHeroInstance * hero, HeroRole heroRole, uint64_t chainMask);
ChainActor(const ChainActor * carrier, const ChainActor * other, const CCreatureSet * heroArmy);
ChainActor(const CGObjectInstance * obj, const CCreatureSet * army, uint64_t chainMask, int initialTurn);
@@ -38,6 +38,7 @@ public:
bool allowBattle;
bool allowSpellCast;
const CGHeroInstance * hero;
HeroRole heroRole;
const CCreatureSet * creatureSet;
const ChainActor * battleActor;
const ChainActor * castActor;
@@ -105,7 +106,7 @@ public:
std::shared_ptr<SpecialAction> exchangeAction;
// chain flags, can be combined meaning hero exchange and so on
HeroActor(const CGHeroInstance * hero, uint64_t chainMask, const Nullkiller * ai);
HeroActor(const CGHeroInstance * hero, HeroRole heroRole, uint64_t chainMask, const Nullkiller * ai);
HeroActor(const ChainActor * carrier, const ChainActor * other, const CCreatureSet * army, const Nullkiller * ai);
virtual bool canExchange(const ChainActor * other) const override;

View File

@@ -38,7 +38,11 @@ namespace AIPathfinding
auto blocker = getBlockingReason(source, destination, pathfinderConfig, pathfinderHelper);
if(blocker == BlockingReason::NONE)
{
destination.blocked = nodeStorage->isDistanceLimitReached(source, destination);
return;
}
auto destGuardians = cb->getGuardingCreatures(destination.coord);
bool allowBypass = false;