Forráskód Böngészése

Nullkiller: small optimization of AIPathfinder for big maps

Andrii Danylchenko 4 éve
szülő
commit
5bfe71c8f3

+ 7 - 0
AI/Nullkiller/AIUtility.cpp

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

+ 2 - 0
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<boost::chrono::steady_clock> start);
+
 class CDistanceSorter
 {
 	const CGHeroInstance * hero;

+ 7 - 2
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<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

+ 4 - 0
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));
 }

+ 22 - 8
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<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");
+
+	std::map<const CGHeroInstance *, HeroRole> activeHeroes;
 
-	vstd::erase_if(activeHeroes, [this](const CGHeroInstance * hero) -> bool
+	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

+ 1 - 8
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)

+ 50 - 24
AI/Nullkiller/Pathfinding/AINodeStorage.cpp

@@ -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() << "; ";

+ 3 - 6
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<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);

+ 4 - 1
AI/Nullkiller/Pathfinding/AIPathfinder.cpp

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

+ 1 - 1
AI/Nullkiller/Pathfinding/AIPathfinder.h

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

+ 7 - 6
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;

+ 3 - 2
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<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;

+ 4 - 0
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;