From 5bfe71c8f37977a4d35a75750453204ddca5c93f Mon Sep 17 00:00:00 2001 From: Andrii Danylchenko Date: Sun, 16 May 2021 14:56:13 +0300 Subject: [PATCH] Nullkiller: small optimization of AIPathfinder for big maps --- AI/Nullkiller/AIUtility.cpp | 7 ++ AI/Nullkiller/AIUtility.h | 2 + .../Analyzers/DangerHitMapAnalyzer.cpp | 9 ++- AI/Nullkiller/Analyzers/ObjectClusterizer.cpp | 4 + AI/Nullkiller/Engine/Nullkiller.cpp | 30 ++++++-- AI/Nullkiller/Engine/PriorityEvaluator.cpp | 9 +-- AI/Nullkiller/Pathfinding/AINodeStorage.cpp | 74 +++++++++++++------ AI/Nullkiller/Pathfinding/AINodeStorage.h | 9 +-- AI/Nullkiller/Pathfinding/AIPathfinder.cpp | 5 +- AI/Nullkiller/Pathfinding/AIPathfinder.h | 2 +- AI/Nullkiller/Pathfinding/Actors.cpp | 13 ++-- AI/Nullkiller/Pathfinding/Actors.h | 5 +- .../Rules/AIMovementAfterDestinationRule.cpp | 4 + 13 files changed, 115 insertions(+), 58 deletions(-) diff --git a/AI/Nullkiller/AIUtility.cpp b/AI/Nullkiller/AIUtility.cpp index eb218de88..3dbf404a5 100644 --- a/AI/Nullkiller/AIUtility.cpp +++ b/AI/Nullkiller/AIUtility.cpp @@ -351,4 +351,11 @@ bool isWeeklyRevisitable(const CGObjectInstance * obj) return (dynamic_cast(obj))->wasMyColorVisited(ai->playerID); //FIXME: they could be revisited sooner than in a week } return false; +} + +uint64_t timeElapsed(boost::chrono::time_point start) +{ + auto end = boost::chrono::high_resolution_clock::now(); + + return boost::chrono::duration_cast(end - start).count(); } \ No newline at end of file diff --git a/AI/Nullkiller/AIUtility.h b/AI/Nullkiller/AIUtility.h index 1fce47eb7..b9fd8b3e5 100644 --- a/AI/Nullkiller/AIUtility.h +++ b/AI/Nullkiller/AIUtility.h @@ -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 start); + class CDistanceSorter { const CGHeroInstance * hero; diff --git a/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp b/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp index 80da5a846..7eb4e3ce9 100644 --- a/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp +++ b/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp @@ -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> heroes; + std::map> heroes; for(const CGObjectInstance * obj : ai->memory->visitableObjs) { @@ -31,7 +34,7 @@ void DangerHitMapAnalyzer::updateHitMap() { auto hero = dynamic_cast(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 diff --git a/AI/Nullkiller/Analyzers/ObjectClusterizer.cpp b/AI/Nullkiller/Analyzers/ObjectClusterizer.cpp index 5ad6166ad..29810a57d 100644 --- a/AI/Nullkiller/Analyzers/ObjectClusterizer.cpp +++ b/AI/Nullkiller/Analyzers/ObjectClusterizer.cpp @@ -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)); } \ No newline at end of file diff --git a/AI/Nullkiller/Engine/Nullkiller.cpp b/AI/Nullkiller/Engine/Nullkiller.cpp index 1364d2ff3..e082092a4 100644 --- a/AI/Nullkiller/Engine/Nullkiller.cpp +++ b/AI/Nullkiller/Engine/Nullkiller.cpp @@ -62,6 +62,7 @@ Goals::TTask Nullkiller::choseBestTask(Goals::TSubgoal behavior) const Goals::TGoalVec goals[MAX_DEPTH + 1]; Goals::TTaskVec tasks; std::map 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 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 diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index 4061edb40..73a1f18c1 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -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) diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp index f4973eaff..93e795af9 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp @@ -614,22 +614,45 @@ const std::set AINodeStorage::getAllHeroes() const return heroes; } -void AINodeStorage::setHeroes(std::vector 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 heroes) { playerID = ai->playerID; for(auto & hero : heroes) { uint64_t mask = 1 << actors.size(); - auto actor = std::make_shared(hero, mask, ai); + auto actor = std::make_shared(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() << "; "; diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.h b/AI/Nullkiller/Pathfinding/AINodeStorage.h index 11b505042..fe350617f 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.h +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.h @@ -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 bool hasBetterChain( @@ -176,7 +173,7 @@ public: boost::optional getOrCreateNode(const int3 & coord, const EPathfindingLayer layer, const ChainActor * actor); std::vector getChainInfo(const int3 & pos, bool isOnLand) const; bool isTileAccessible(const HeroPtr & hero, const int3 & pos, const EPathfindingLayer layer) const; - void setHeroes(std::vector heroes); + void setHeroes(std::map heroes); void setTownsAndDwellings( const std::vector & towns, const std::set & visitableObjs); diff --git a/AI/Nullkiller/Pathfinding/AIPathfinder.cpp b/AI/Nullkiller/Pathfinding/AIPathfinder.cpp index c4a36f94f..2fb61c0fa 100644 --- a/AI/Nullkiller/Pathfinding/AIPathfinder.cpp +++ b/AI/Nullkiller/Pathfinding/AIPathfinder.cpp @@ -42,13 +42,14 @@ std::vector AIPathfinder::getPathInfo(const int3 & tile) const return storage->getChainInfo(tile, !tileInfo->isWater()); } -void AIPathfinder::updatePaths(std::vector heroes, bool useHeroChain) +void AIPathfinder::updatePaths(std::map 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 heroes, bool cb->calculatePaths(config); } } while(storage->increaseHeroChainTurnLimit()); + + logAi->trace("Recalculated paths in %ld", timeElapsed(start)); } diff --git a/AI/Nullkiller/Pathfinding/AIPathfinder.h b/AI/Nullkiller/Pathfinding/AIPathfinder.h index 1052e1bcd..1bc4edbbc 100644 --- a/AI/Nullkiller/Pathfinding/AIPathfinder.h +++ b/AI/Nullkiller/Pathfinding/AIPathfinder.h @@ -26,6 +26,6 @@ public: AIPathfinder(CPlayerSpecificInfoCallback * cb, Nullkiller * ai); std::vector getPathInfo(const int3 & tile) const; bool isTileAccessible(const HeroPtr & hero, const int3 & tile) const; - void updatePaths(std::vector heroes, bool useHeroChain = false); + void updatePaths(std::map heroes, bool useHeroChain = false); void init(); }; diff --git a/AI/Nullkiller/Pathfinding/Actors.cpp b/AI/Nullkiller/Pathfinding/Actors.cpp index 22687f45d..875b2de9e 100644 --- a/AI/Nullkiller/Pathfinding/Actors.cpp +++ b/AI/Nullkiller/Pathfinding/Actors.cpp @@ -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; diff --git a/AI/Nullkiller/Pathfinding/Actors.h b/AI/Nullkiller/Pathfinding/Actors.h index 75e700969..e0bfd1678 100644 --- a/AI/Nullkiller/Pathfinding/Actors.h +++ b/AI/Nullkiller/Pathfinding/Actors.h @@ -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 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; diff --git a/AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.cpp b/AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.cpp index 1a730be37..c910ad551 100644 --- a/AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.cpp +++ b/AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.cpp @@ -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;