Ver código fonte

Nullkiller: tbb and hero chain calculation optimization and parallel cpathfinder initialization

Andrii Danylchenko 5 anos atrás
pai
commit
fb3cda666f

+ 1 - 1
AI/Nullkiller/AIUtility.cpp

@@ -189,7 +189,7 @@ bool CDistanceSorter::operator()(const CGObjectInstance * lhs, const CGObjectIns
 	const CGPathNode * ln = ai->myCb->getPathsInfo(hero)->getPathInfo(lhs->visitablePos());
 	const CGPathNode * rn = ai->myCb->getPathsInfo(hero)->getPathInfo(rhs->visitablePos());
 
-	return ln->cost < rn->cost;
+	return ln->getCost() < rn->getCost();
 }
 
 bool isSafeToVisit(HeroPtr h, const CCreatureSet * heroArmy, uint64_t dangerStrength)

+ 2 - 0
AI/Nullkiller/CMakeLists.txt

@@ -137,6 +137,8 @@ else()
 	target_link_libraries(VCAI PRIVATE fl-static vcmi)
 endif()
 
+target_link_libraries(VCAI PRIVATE TBB::tbb)
+
 vcmi_set_output_dir(VCAI "AI")
 
 set_target_properties(VCAI PROPERTIES ${PCH_PROPERTIES})

+ 13 - 13
AI/Nullkiller/Engine/FuzzyEngines.cpp

@@ -44,19 +44,19 @@ struct armyStructure
 
 armyStructure evaluateArmyStructure(const CArmedInstance * army)
 {
-	ui64 totalStrenght = army->getArmyStrength();
-	double walkersStrenght = 0;
-	double flyersStrenght = 0;
-	double shootersStrenght = 0;
+	ui64 totalStrength = army->getArmyStrength();
+	double walkersStrength = 0;
+	double flyersStrength = 0;
+	double shootersStrength = 0;
 	ui32 maxSpeed = 0;
 
-	static const CSelector selectorSHOOTER = Selector::type(Bonus::SHOOTER);
+	static const CSelector selectorSHOOTER = Selector::type()(Bonus::SHOOTER);
 	static const std::string keySHOOTER = "type_"+std::to_string((int32_t)Bonus::SHOOTER);
 
-	static const CSelector selectorFLYING = Selector::type(Bonus::FLYING);
+	static const CSelector selectorFLYING = Selector::type()(Bonus::FLYING);
 	static const std::string keyFLYING = "type_"+std::to_string((int32_t)Bonus::FLYING);
 
-	static const CSelector selectorSTACKS_SPEED = Selector::type(Bonus::STACKS_SPEED);
+	static const CSelector selectorSTACKS_SPEED = Selector::type()(Bonus::STACKS_SPEED);
 	static const std::string keySTACKS_SPEED = "type_"+std::to_string((int32_t)Bonus::STACKS_SPEED);
 
 	for(auto s : army->Slots())
@@ -65,23 +65,23 @@ armyStructure evaluateArmyStructure(const CArmedInstance * army)
 		const CCreature * creature = s.second->type;
 		if(creature->hasBonus(selectorSHOOTER, keySHOOTER))
 		{
-			shootersStrenght += s.second->getPower();
+			shootersStrength += s.second->getPower();
 			walker = false;
 		}
 		if(creature->hasBonus(selectorFLYING, keyFLYING))
 		{
-			flyersStrenght += s.second->getPower();
+			flyersStrength += s.second->getPower();
 			walker = false;
 		}
 		if(walker)
-			walkersStrenght += s.second->getPower();
+			walkersStrength += s.second->getPower();
 
 		vstd::amax(maxSpeed, creature->valOfBonuses(selectorSTACKS_SPEED, keySTACKS_SPEED));
 	}
 	armyStructure as;
-	as.walkers = walkersStrenght / totalStrenght;
-	as.shooters = shootersStrenght / totalStrenght;
-	as.flyers = flyersStrenght / totalStrenght;
+	as.walkers = static_cast<float>(walkersStrength / totalStrength);
+	as.shooters = static_cast<float>(shootersStrength / totalStrength);
+	as.flyers = static_cast<float>(flyersStrength / totalStrength);
 	as.maxSpeed = maxSpeed;
 	assert(as.walkers || as.flyers || as.shooters);
 	return as;

+ 1 - 1
AI/Nullkiller/Engine/Nullkiller.cpp

@@ -262,4 +262,4 @@ void Nullkiller::makeTurn()
 			return;
 		}
 	}
-}
+}

+ 2 - 0
AI/Nullkiller/Engine/Nullkiller.h

@@ -9,6 +9,8 @@
 */
 #pragma once
 
+#include <boost/asio.hpp>
+
 #include "PriorityEvaluator.h"
 #include "FuzzyHelper.h"
 #include "AIMemory.h"

+ 1 - 1
AI/Nullkiller/Goals/GatherArmy.cpp

@@ -148,7 +148,7 @@ TGoalVec GatherArmy::getAllPossibleSubgoals()
 			{
 				auto dwelling = dynamic_cast<const CGDwelling *>(obj);
 
-				ui32 val = std::min<ui32>(value, ai->ah->howManyReinforcementsCanBuy(hero.get(), dwelling));
+				ui32 val = std::min((ui32)value, (ui32)ai->ah->howManyReinforcementsCanBuy(hero.get(), dwelling));
 
 				if(val)
 				{

+ 2 - 2
AI/Nullkiller/Goals/Win.cpp

@@ -95,11 +95,11 @@ TSubgoal Win::whatToDoToAchieve()
 					{
 						auto towns = cb->getTownsInfo();
 						towns.erase(boost::remove_if(towns,
-									     [](const CGTownInstance * t) -> bool
+										[](const CGTownInstance * t) -> bool
 							{
 								return vstd::contains(t->forbiddenBuildings, BuildingID::GRAIL);
 							}),
-							    towns.end());
+								towns.end());
 						boost::sort(towns, CDistanceSorter(h.get()));
 						if(towns.size())
 						{

+ 200 - 92
AI/Nullkiller/Pathfinding/AINodeStorage.cpp

@@ -8,6 +8,7 @@
 *
 */
 #include "StdInc.h"
+#include <tbb/tbb.h>
 #include "AINodeStorage.h"
 #include "Actions/TownPortalAction.h"
 #include "../Goals/Goals.h"
@@ -19,6 +20,8 @@
 #include "../../../lib/PathfinderUtil.h"
 #include "../../../lib/CPlayerState.h"
 
+using namespace tbb;
+
 std::shared_ptr<boost::multi_array<AIPathNode, 5>> AISharedStorage::shared;
 std::set<int3> commitedTiles;
 std::set<int3> commitedTilesInitial;
@@ -61,50 +64,56 @@ void AINodeStorage::initialize(const PathfinderOptions & options, const CGameSta
 		return;
 
 	//TODO: fix this code duplication with NodeStorage::initialize, problem is to keep `resetTile` inline
-	int3 pos;
-	const PlayerColor player = playerID;
 	const PlayerColor fowPlayer = ai->playerID;
-	const int3 sizes = gs->getMapSize();
 	const auto & fow = static_cast<const CGameInfoCallback *>(gs)->getPlayerTeam(fowPlayer)->fogOfWarMap;
+	const int3 sizes = gs->getMapSize();
 
-	//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)
+	parallel_for(blocked_range<size_t>(0, sizes.x), [&](const blocked_range<size_t>& r)
 	{
-		for(pos.y=0; pos.y < sizes.y; ++pos.y)
+		//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;
+		const PlayerColor player = playerID;
+
+		int3 pos;
+
+		for(pos.x = r.begin(); pos.x != r.end(); ++pos.x)
 		{
-			for(pos.z=0; pos.z < sizes.z; ++pos.z)
+			for(pos.y = 0; pos.y < sizes.y; ++pos.y)
 			{
-				const TerrainTile * tile = &gs->map->getTile(pos);
-				switch(tile->terType)
+				for(pos.z = 0; pos.z < sizes.z; ++pos.z)
 				{
-				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 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;
+					}
 				}
 			}
 		}
-	}
+	});
 }
 
 void AINodeStorage::clear()
 {
+	CCreature::DisableChildLinkage = true;
 	actors.clear();
+	CCreature::DisableChildLinkage = false;
 	heroChainPass = EHeroChainPass::INITIAL;
 	heroChainTurn = 0;
 	heroChainMaxTurns = 1;
@@ -161,7 +170,7 @@ boost::optional<AIPathNode *> AINodeStorage::getOrCreateNode(
 std::vector<CGPathNode *> AINodeStorage::getInitialNodes()
 {
 	if(heroChainPass)
-	{
+{
 		calculateTownPortalTeleportations(heroChain);
 
 		return heroChain;
@@ -184,7 +193,7 @@ std::vector<CGPathNode *> AINodeStorage::getInitialNodes()
 		initialNode->turns = actor->initialTurn;
 		initialNode->moveRemains = actor->initialMovement;
 		initialNode->danger = 0;
-		initialNode->cost = actor->initialTurn;
+		initialNode->setCost(actor->initialTurn);
 		initialNode->action = CGPathNode::ENodeAction::NORMAL;
 
 		if(actor->isMovable)
@@ -205,7 +214,7 @@ std::vector<CGPathNode *> AINodeStorage::getInitialNodes()
 void AINodeStorage::resetTile(const int3 & coord, EPathfindingLayer layer, CGPathNode::EAccessibility accessibility)
 {
 	for(AIPathNode & heroNode : nodes.get(coord, layer))
-	{
+{
 		heroNode.actor = nullptr;
 		heroNode.danger = 0;
 		heroNode.manaCost = 0;
@@ -246,7 +255,7 @@ void AINodeStorage::commit(
 	float cost) const
 {
 	destination->action = action;
-	destination->cost = cost;
+	destination->setCost(cost);
 	destination->moveRemains = movementLeft;
 	destination->turns = turn;
 	destination->armyLoss = source->armyLoss;
@@ -360,61 +369,133 @@ bool AINodeStorage::calculateHeroChainFinal()
 			}
 		});
 	}
-	
+
 	return heroChain.size();
 }
 
-bool AINodeStorage::calculateHeroChain()
+struct DelayedWork
 {
-	heroChainPass = EHeroChainPass::CHAIN;
-	heroChain.resize(0);
+	AIPathNode * carrier;
+	AIPathNode * other;
+
+	DelayedWork()
+	{
+	}
+	
+	DelayedWork(AIPathNode * carrier, AIPathNode * other) : carrier(carrier), other(other)
+	{
+	}
+};
 
+class HeroChainCalculationTask
+{
+private:
+	AISharedStorage & nodes;
+	AINodeStorage & storage;
 	std::vector<AIPathNode *> existingChains;
 	std::vector<ExchangeCandidate> newChains;
+	uint64_t chainMask;
+	int heroChainTurn;
+	std::vector<CGPathNode *> heroChain;
+	const std::vector<int3> & tiles;
+
+public:
+	HeroChainCalculationTask(
+		AINodeStorage & storage, AISharedStorage & nodes, const std::vector<int3> & tiles, uint64_t chainMask, int heroChainTurn)
+		:existingChains(), newChains(), nodes(nodes), storage(storage), chainMask(chainMask), heroChainTurn(heroChainTurn), heroChain(), tiles(tiles)
+	{
+		existingChains.reserve(NUM_CHAINS);
+		newChains.reserve(NUM_CHAINS);
+	}
 
-	existingChains.reserve(NUM_CHAINS);
-	newChains.reserve(NUM_CHAINS);
-
-	for(auto & pos : commitedTiles)
+	void execute(const blocked_range<size_t>& r)
 	{
-		for(auto layer : phisycalLayers)
+		for(int i = r.begin(); i != r.end(); i++)
 		{
-			auto chains = nodes.get(pos, layer);
+			auto & pos = tiles[i];
 
-			// fast cut inactive nodes
-			if(chains[0].blocked())
-				continue;
+			for(auto layer : phisycalLayers)
+			{
+				auto chains = nodes.get(pos, layer);
 
-			existingChains.clear();
-			newChains.clear();
+				// fast cut inactive nodes
+				if(chains[0].blocked())
+					continue;
 
-			for(AIPathNode & node : chains)
-			{
-				if(node.turns <= heroChainTurn && node.action != CGPathNode::ENodeAction::UNKNOWN)
-					existingChains.push_back(&node);
-			}
+				existingChains.clear();
+				newChains.clear();
 
-			for(AIPathNode * node : existingChains)
-			{
-				if(node->actor->isMovable)
+				for(AIPathNode & node : chains)
 				{
-					calculateHeroChain(node, existingChains, newChains);
+					if(node.turns <= heroChainTurn && node.action != CGPathNode::ENodeAction::UNKNOWN)
+						existingChains.push_back(&node);
+				}
+
+				std::random_shuffle(existingChains.begin(), existingChains.end());
+
+				for(AIPathNode * node : existingChains)
+				{
+					if(node->actor->isMovable)
+					{
+						calculateHeroChain(node, existingChains, newChains);
+					}
 				}
-			}
 
-			cleanupInefectiveChains(newChains);
-			addHeroChain(newChains);
+				cleanupInefectiveChains(newChains);
+				addHeroChain(newChains);
+			}
 		}
 	}
 
+	void calculateHeroChain(
+		AIPathNode * srcNode,
+		const std::vector<AIPathNode *> & variants,
+		std::vector<ExchangeCandidate> & result);
+
+	void calculateHeroChain(
+		AIPathNode * carrier,
+		AIPathNode * other,
+		std::vector<ExchangeCandidate> & result);
+
+	void cleanupInefectiveChains(std::vector<ExchangeCandidate> & result) const;
+	void addHeroChain(const std::vector<ExchangeCandidate> & result);
+
+	ExchangeCandidate calculateExchange(
+		ChainActor * exchangeActor,
+		AIPathNode * carrierParentNode,
+		AIPathNode * otherParentNode) const;
+
+	void flushResult(std::vector<CGPathNode *> & result)
+	{
+		vstd::concatenate(result, heroChain);
+	}
+};
+
+bool AINodeStorage::calculateHeroChain()
+{
+	heroChainPass = EHeroChainPass::CHAIN;
+	heroChain.clear();
+
+	std::vector<int3> data(commitedTiles.begin(), commitedTiles.end());
+
+	CCreature::DisableChildLinkage = true;
+
+	auto r = blocked_range<size_t>(0, data.size());
+	HeroChainCalculationTask task(*this, nodes, data, chainMask, heroChainTurn);
+
+	task.execute(r);
+	task.flushResult(heroChain);
+
+	CCreature::DisableChildLinkage = false;
+
 	commitedTiles.clear();
 
-	return heroChain.size();
+	return !heroChain.empty();
 }
 
 bool AINodeStorage::selectFirstActor()
 {
-	if(!actors.size())
+	if(actors.empty())
 		return false;
 
 	auto strongest = *vstd::maxElementByFun(actors, [](std::shared_ptr<ChainActor> actor) -> uint64_t
@@ -454,6 +535,9 @@ bool AINodeStorage::selectNextActor()
 
 	if(nextActor != actors.end())
 	{
+		if(nextActor->get()->armyValue < 1000)
+			return false;
+
 		chainMask = nextActor->get()->chainMask;
 		commitedTiles = commitedTilesInitial;
 
@@ -463,22 +547,36 @@ bool AINodeStorage::selectNextActor()
 	return false;
 }
 
-void AINodeStorage::cleanupInefectiveChains(std::vector<ExchangeCandidate> & result) const
+void HeroChainCalculationTask::cleanupInefectiveChains(std::vector<ExchangeCandidate> & result) const
 {
 	vstd::erase_if(result, [&](const ExchangeCandidate & chainInfo) -> bool
 	{
 		auto pos = chainInfo.coord;
 		auto chains = nodes.get(pos, EPathfindingLayer::LAND);
+		auto isNotEffective = storage.hasBetterChain(chainInfo.carrierParent, &chainInfo, chains)
+			|| storage.hasBetterChain(chainInfo.carrierParent, &chainInfo, result);
 
-		return hasBetterChain(chainInfo.carrierParent, &chainInfo, chains)
-			|| hasBetterChain(chainInfo.carrierParent, &chainInfo, result);
+#if PATHFINDER_TRACE_LEVEL >= 2
+		if(isNotEffective)
+		{
+			logAi->trace(
+				"Skip exchange %s[%x] -> %s[%x] at %s is ineficient",
+				chainInfo.otherParent->actor->toString(), 
+				chainInfo.otherParent->actor->chainMask,
+				chainInfo.carrierParent->actor->toString(),
+				chainInfo.carrierParent->actor->chainMask,
+				chainInfo.carrierParent->coord.toString());
+		}
+#endif
+
+		return isNotEffective;
 	});
 }
 
-void AINodeStorage::calculateHeroChain(
+void HeroChainCalculationTask::calculateHeroChain(
 	AIPathNode * srcNode, 
 	const std::vector<AIPathNode *> & variants, 
-	std::vector<ExchangeCandidate> & result) const
+	std::vector<ExchangeCandidate> & result)
 {
 	for(AIPathNode * node : variants)
 	{
@@ -531,16 +629,15 @@ void AINodeStorage::calculateHeroChain(
 	}
 }
 
-void AINodeStorage::calculateHeroChain(
+void HeroChainCalculationTask::calculateHeroChain(
 	AIPathNode * carrier, 
 	AIPathNode * other, 
-	std::vector<ExchangeCandidate> & result) const
+	std::vector<ExchangeCandidate> & result)
 {	
 	if(carrier->armyLoss < carrier->actor->armyValue
 		&& (carrier->action != CGPathNode::BATTLE || (carrier->actor->allowBattle && carrier->specialAction))
 		&& carrier->action != CGPathNode::BLOCKING_VISIT
-		&& (other->armyLoss == 0 || other->armyLoss < other->actor->armyValue)
-		&& carrier->actor->canExchange(other->actor))
+		&& (other->armyLoss == 0 || other->armyLoss < other->actor->armyValue))
 	{
 #if PATHFINDER_TRACE_LEVEL >= 2
 		logAi->trace(
@@ -566,20 +663,20 @@ void AINodeStorage::calculateHeroChain(
 			}
 		}
 
-		auto newActor = carrier->actor->exchange(other->actor);
+		auto newActor = carrier->actor->tryExchange(other->actor);
 		
-		result.push_back(calculateExchange(newActor, carrier, other));
+		if(newActor) result.push_back(calculateExchange(newActor, carrier, other));
 	}
 }
 
-void AINodeStorage::addHeroChain(const std::vector<ExchangeCandidate> & result)
+void HeroChainCalculationTask::addHeroChain(const std::vector<ExchangeCandidate> & result)
 {
 	for(const ExchangeCandidate & chainInfo : result)
 	{
 		auto carrier = chainInfo.carrierParent;
 		auto newActor = chainInfo.actor;
 		auto other = chainInfo.otherParent;
-		auto chainNodeOptional = getOrCreateNode(carrier->coord, carrier->layer, newActor);
+		auto chainNodeOptional = storage.getOrCreateNode(carrier->coord, carrier->layer, newActor);
 
 		if(!chainNodeOptional)
 		{
@@ -594,24 +691,34 @@ void AINodeStorage::addHeroChain(const std::vector<ExchangeCandidate> & result)
 		if(exchangeNode->action != CGPathNode::ENodeAction::UNKNOWN)
 		{
 #if PATHFINDER_TRACE_LEVEL >= 2
-			logAi->trace("Exchange at %s node is already in use. Blocked.", carrier->coord.toString());
+			logAi->trace(
+				"Skip exchange %s[%x] -> %s[%x] at %s because node is in use",
+				other->actor->toString(),
+				other->actor->chainMask,
+				carrier->actor->toString(),
+				carrier->actor->chainMask,
+				carrier->coord.toString());
 #endif
 			continue;
 		}
 		
-		if(exchangeNode->turns != 0xFF && exchangeNode->cost < chainInfo.cost)
+		if(exchangeNode->turns != 0xFF && exchangeNode->getCost() < chainInfo.getCost())
 		{
 #if PATHFINDER_TRACE_LEVEL >= 2
 			logAi->trace(
-				"Exchange at %s is is not effective enough. %f < %f", 
-				exchangeNode->coord.toString(), 
-				exchangeNode->getCost(), 
+				"Skip exchange %s[%x] -> %s[%x] at %s because not effective enough. %f < %f",
+				other->actor->toString(),
+				other->actor->chainMask,
+				carrier->actor->toString(),
+				carrier->actor->chainMask,
+				carrier->coord.toString(),
+				exchangeNode->getCost(),
 				chainInfo.getCost());
 #endif
 			continue;
 		}
 
-		commit(exchangeNode, carrier, carrier->action, chainInfo.turns, chainInfo.moveRemains, chainInfo.cost);
+		storage.commit(exchangeNode, carrier, carrier->action, chainInfo.turns, chainInfo.moveRemains, chainInfo.getCost());
 
 		if(carrier->specialAction || carrier->chainOther)
 		{
@@ -644,7 +751,7 @@ void AINodeStorage::addHeroChain(const std::vector<ExchangeCandidate> & result)
 	}
 }
 
-ExchangeCandidate AINodeStorage::calculateExchange(
+ExchangeCandidate HeroChainCalculationTask::calculateExchange(
 	ChainActor * exchangeActor, 
 	AIPathNode * carrierParentNode, 
 	AIPathNode * otherParentNode) const
@@ -658,7 +765,7 @@ ExchangeCandidate AINodeStorage::calculateExchange(
 	candidate.actor = exchangeActor;
 	candidate.armyLoss = carrierParentNode->armyLoss + otherParentNode->armyLoss;
 	candidate.turns = carrierParentNode->turns;
-	candidate.cost = carrierParentNode->cost + otherParentNode->cost / 1000.0;
+	candidate.setCost(carrierParentNode->getCost() + otherParentNode->getCost() / 1000.0);
 	candidate.moveRemains = carrierParentNode->moveRemains;
 
 	if(carrierParentNode->turns < otherParentNode->turns)
@@ -668,7 +775,7 @@ ExchangeCandidate AINodeStorage::calculateExchange(
 			+ carrierParentNode->moveRemains / (float)moveRemains;
 
 		candidate.turns = otherParentNode->turns;
-		candidate.cost += waitingCost;
+		candidate.setCost(candidate.getCost() + waitingCost);
 		candidate.moveRemains = moveRemains;
 	}
 
@@ -873,7 +980,7 @@ struct TowmPortalFinder
 					continue;
 			}
 
-			if(!bestNode || bestNode->cost > node->cost)
+			if(!bestNode || bestNode->getCost() > node->getCost())
 				bestNode = node;
 		}
 
@@ -895,9 +1002,9 @@ struct TowmPortalFinder
 		AIPathNode * node = nodeOptional.get();
 		float movementCost = (float)movementNeeded / (float)hero->maxMovePoints(EPathfindingLayer::LAND);
 
-		movementCost += bestNode->cost;
+		movementCost += bestNode->getCost();
 
-		if(node->action == CGPathNode::UNKNOWN || node->cost > movementCost)
+		if(node->action == CGPathNode::UNKNOWN || node->getCost() > movementCost)
 		{
 			nodeStorage->commit(
 				node,
@@ -1009,7 +1116,7 @@ bool AINodeStorage::hasBetterChain(
 
 		if(node.danger <= candidateNode->danger && candidateNode->actor == node.actor->battleActor)
 		{
-			if(node.cost < candidateNode->cost)
+			if(node.getCost() < candidateNode->getCost())
 			{
 #if PATHFINDER_TRACE_LEVEL >= 2
 				logAi->trace(
@@ -1033,7 +1140,7 @@ bool AINodeStorage::hasBetterChain(
 		auto candidateArmyValue = candidateActor->armyValue - candidateNode->armyLoss;
 
 		if(nodeArmyValue > candidateArmyValue
-			&& node.cost <= candidateNode->cost)
+			&& node.getCost() <= candidateNode->getCost())
 		{
 #if PATHFINDER_TRACE_LEVEL >= 2
 			logAi->trace(
@@ -1052,10 +1159,10 @@ bool AINodeStorage::hasBetterChain(
 		{
 			if(nodeArmyValue == candidateArmyValue
 				&& nodeActor->heroFightingStrength >= candidateActor->heroFightingStrength
-				&& node.cost <= candidateNode->cost)
+				&& node.getCost() <= candidateNode->getCost())
 			{
 				if(nodeActor->heroFightingStrength == candidateActor->heroFightingStrength
-					&& node.cost == candidateNode->cost
+					&& node.getCost() == candidateNode->getCost()
 					&& &node < candidateNode)
 				{
 					continue;
@@ -1141,7 +1248,8 @@ void AINodeStorage::fillChainInfo(const AIPathNode * node, AIPath & path, int pa
 		//if(node->actor->hero->visitablePos() != node->coord)
 		{
 			AIPathNodeInfo pathNode;
-			pathNode.cost = node->cost;
+
+			pathNode.cost = node->getCost();
 			pathNode.targetHero = node->actor->hero;
 			pathNode.chainMask = node->actor->chainMask;
 			pathNode.specialAction = node->specialAction;

+ 12 - 18
AI/Nullkiller/Pathfinding/AINodeStorage.h

@@ -23,6 +23,14 @@
 #include "Actions/SpecialAction.h"
 #include "Actors.h"
 
+namespace AIPathfinding
+{
+	const int BUCKET_COUNT = 11;
+	const int BUCKET_SIZE = GameConstants::MAX_HEROES_PER_PLAYER;
+	const int NUM_CHAINS = BUCKET_COUNT * BUCKET_SIZE;
+	const int THREAD_COUNT = 8;
+}
+
 struct AIPathNode : public CGPathNode
 {
 	uint64_t danger;
@@ -228,28 +236,14 @@ public:
 		return (uint64_t)(armyValue * ratio * ratio * ratio);
 	}
 
-private:
 	STRONG_INLINE
 	void resetTile(const int3 & tile, EPathfindingLayer layer, CGPathNode::EAccessibility accessibility);
+	STRONG_INLINE int getBucket(const ChainActor * actor) const
+	{
+		return ((uintptr_t)actor * 395) % AIPathfinding::BUCKET_COUNT;
+	}
 
-	void calculateHeroChain(
-		AIPathNode * srcNode, 
-		const std::vector<AIPathNode *> & variants, 
-		std::vector<ExchangeCandidate> & result) const;
-
-	void calculateHeroChain(
-		AIPathNode * carrier, 
-		AIPathNode * other, 
-		std::vector<ExchangeCandidate> & result) const;
-	
-	void cleanupInefectiveChains(std::vector<ExchangeCandidate> & result) const;
-	void addHeroChain(const std::vector<ExchangeCandidate> & result);
 
 	void calculateTownPortalTeleportations(std::vector<CGPathNode *> & neighbours);
 	void fillChainInfo(const AIPathNode * node, AIPath & path, int parentIndex) const;
-
-	ExchangeCandidate calculateExchange(
-		ChainActor * exchangeActor, 
-		AIPathNode * carrierParentNode, 
-		AIPathNode * otherParentNode) const;
 };

+ 1 - 1
AI/Nullkiller/Pathfinding/Actions/BoatActions.cpp

@@ -114,7 +114,7 @@ namespace AIPathfinding
 			source->manaCost);
 #endif
 
-		return hero->mana >= source->manaCost + getManaCost(hero);
+		return hero->mana >= (si32)(source->manaCost + getManaCost(hero));
 	}
 
 	std::string SummonBoatAction::toString() const

+ 84 - 97
AI/Nullkiller/Pathfinding/Actors.cpp

@@ -145,14 +145,11 @@ void HeroActor::setupSpecialActors()
 	}
 }
 
-ChainActor * ChainActor::exchange(const ChainActor * specialActor, const ChainActor * other) const
+ChainActor * ChainActor::tryExchange(const ChainActor * specialActor, const ChainActor * other) const
 {
-	return baseActor->exchange(specialActor, other);
-}
+	if(!isMovable) return nullptr;
 
-bool ChainActor::canExchange(const ChainActor * other) const
-{
-	return isMovable && baseActor->canExchange(other);
+	return baseActor->tryExchange(specialActor, other);
 }
 
 namespace vstd
@@ -172,71 +169,12 @@ namespace vstd
 	}
 }
 
-bool HeroActor::canExchange(const ChainActor * other) const
-{
-	return exchangeMap->canExchange(other);
-}
-
-bool HeroExchangeMap::canExchange(const ChainActor * other)
-{
-	return vstd::getOrCompute(canExchangeCache, other, [&](bool & result) {
-		result = (actor->chainMask & other->chainMask) == 0;
-
-		if(result)
-		{
-			TResources resources = ai->cb->getResourceAmount();
-
-			if(!resources.canAfford(actor->armyCost + other->armyCost))
-			{
-				result = false;
-#if PATHFINDER_TRACE_LEVEL >= 2
-				logAi->trace(
-					"Can not afford exchange because of total cost %s but we have %s",
-					(actor->armyCost + other->armyCost).toString(),
-					resources.toString());
-#endif
-				return;
-			}
-
-			TResources availableResources = resources - actor->armyCost - other->armyCost;
-
-			auto upgradeInfo = ai->armyManager->calculateCreateresUpgrade(
-				actor->creatureSet, 
-				other->getActorObject(),
-				availableResources);
-
-			uint64_t reinforcment = upgradeInfo.upgradeValue;
-			
-			if(other->creatureSet->Slots().size())
-				reinforcment += ai->armyManager->howManyReinforcementsCanGet(actor->hero, actor->creatureSet, other->creatureSet);
-			
-			auto obj = other->getActorObject();
-			if(obj && obj->ID == Obj::TOWN)
-			{
-				reinforcment += ai->armyManager->howManyReinforcementsCanBuy(
-					actor->creatureSet,
-					ai->cb->getTown(obj->id),
-					availableResources - upgradeInfo.upgradeCost);
-			}
-
-#if PATHFINDER_TRACE_LEVEL >= 2
-			logAi->trace(
-				"Exchange %s->%s reinforcement: %d, %f%%",
-				actor->toString(),
-				other->toString(),
-				reinforcment,
-				100.0f * reinforcment / actor->armyValue);
-#endif
-
-			result = reinforcment > actor->armyValue / 10 || reinforcment > 1000;
-		}
-	});
-}
-
-ChainActor * HeroActor::exchange(const ChainActor * specialActor, const ChainActor * other) const
+ChainActor * HeroActor::tryExchange(const ChainActor * specialActor, const ChainActor * other) const
 {
 	const ChainActor * otherBase = other->baseActor;
-	HeroActor * result = exchangeMap->exchange(otherBase);
+	HeroActor * result = exchangeMap->tryExchange(otherBase);
+
+	if(!result) return nullptr;
 
 	if(specialActor == this)
 		return result;
@@ -250,7 +188,7 @@ ChainActor * HeroActor::exchange(const ChainActor * specialActor, const ChainAct
 }
 
 HeroExchangeMap::HeroExchangeMap(const HeroActor * actor, const Nullkiller * ai)
-	:actor(actor), ai(ai)
+	:actor(actor), ai(ai), sync()
 {
 }
 
@@ -258,6 +196,8 @@ HeroExchangeMap::~HeroExchangeMap()
 {
 	for(auto & exchange : exchangeMap)
 	{
+		if(!exchange.second) continue;
+
 		delete exchange.second->creatureSet;
 		delete exchange.second;
 	}
@@ -265,44 +205,91 @@ HeroExchangeMap::~HeroExchangeMap()
 	exchangeMap.clear();
 }
 
-HeroActor * HeroExchangeMap::exchange(const ChainActor * other)
+HeroActor * HeroExchangeMap::tryExchange(const ChainActor * other)
 {
-	HeroActor * result;
+	auto position = exchangeMap.find(other);
+
+	if(position != exchangeMap.end())
+	{
+		return position->second;
+	}
+
+	auto inserted = exchangeMap.insert(std::pair<const ChainActor *, HeroActor *>(other, nullptr));
 
-	if(vstd::contains(exchangeMap, other))
-		result = exchangeMap.at(other);
-	else 
+	if(!inserted.second)
 	{
-		TResources availableResources = ai->cb->getResourceAmount() - actor->armyCost - other->armyCost;
-		HeroExchangeArmy * upgradedInitialArmy = tryUpgrade(actor->creatureSet, other->getActorObject(), availableResources);
-		HeroExchangeArmy * newArmy;
-		
-		if(other->creatureSet->Slots().size())
+		return inserted.first->second; // already inserted
+	}
+
+	position = inserted.first;
+
+	auto differentMasks = (actor->chainMask & other->chainMask) == 0;
+
+	if(!differentMasks) return nullptr;
+
+	TResources resources = ai->cb->getResourceAmount();
+
+	if(!resources.canAfford(actor->armyCost + other->armyCost))
+	{
+#if PATHFINDER_TRACE_LEVEL >= 2
+		logAi->trace(
+			"Can not afford exchange because of total cost %s but we have %s",
+			(actor->armyCost + other->armyCost).toString(),
+			resources.toString());
+#endif
+		return nullptr;
+	}
+
+	TResources availableResources = resources - actor->armyCost - other->armyCost;
+	HeroExchangeArmy * upgradedInitialArmy = tryUpgrade(actor->creatureSet, other->getActorObject(), availableResources);
+	HeroExchangeArmy * newArmy;
+
+	if(other->creatureSet->Slots().size())
+	{
+		if(upgradedInitialArmy)
 		{
-			if(upgradedInitialArmy)
-			{
-				newArmy = pickBestCreatures(upgradedInitialArmy, other->creatureSet);
-				newArmy->armyCost = upgradedInitialArmy->armyCost;
-				newArmy->requireBuyArmy = upgradedInitialArmy->requireBuyArmy;
-
-				delete upgradedInitialArmy;
-			}
-			else
-			{
-				newArmy = pickBestCreatures(actor->creatureSet, other->creatureSet);
-			}
+			newArmy = pickBestCreatures(upgradedInitialArmy, other->creatureSet);
+			newArmy->armyCost = upgradedInitialArmy->armyCost;
+			newArmy->requireBuyArmy = upgradedInitialArmy->requireBuyArmy;
+
+			delete upgradedInitialArmy;
 		}
 		else
 		{
-			newArmy = upgradedInitialArmy;
+			newArmy = pickBestCreatures(actor->creatureSet, other->creatureSet);
 		}
+	}
+	else
+	{
+		newArmy = upgradedInitialArmy;
+	}
 
-		result = new HeroActor(actor, other, newArmy, ai);
-		result->armyCost += newArmy->armyCost;
-		exchangeMap[other] = result;
+	if(!newArmy) return nullptr;
+
+	auto reinforcement = newArmy->getArmyStrength() - actor->creatureSet->getArmyStrength();
+
+#if PATHFINDER_TRACE_LEVEL >= 2
+	logAi->trace(
+		"Exchange %s->%s reinforcement: %d, %f%%",
+		actor->toString(),
+		other->toString(),
+		reinforcement,
+		100.0f * reinforcement / actor->armyValue);
+#endif
+
+	if(reinforcement <= actor->armyValue / 10 && reinforcement < 1000)
+	{
+		delete newArmy;
+
+		return nullptr;
 	}
 
-	return result;
+	HeroActor * exchanged = new HeroActor(actor, other, newArmy, ai);
+
+	exchanged->armyCost += newArmy->armyCost;
+	position->second = exchanged;
+
+	return exchanged;
 }
 
 HeroExchangeArmy * HeroExchangeMap::tryUpgrade(

+ 5 - 9
AI/Nullkiller/Pathfinding/Actors.h

@@ -65,14 +65,13 @@ public:
 
 	ChainActor(){}
 
-	virtual bool canExchange(const ChainActor * other) const;
 	virtual std::string toString() const;
-	ChainActor * exchange(const ChainActor * other) const { return exchange(this, other); }
+	ChainActor * tryExchange(const ChainActor * other) const { return tryExchange(this, other); }
 	void setBaseActor(HeroActor * base);
 	virtual const CGObjectInstance * getActorObject() const	{ return hero; }
 
 protected:
-	virtual ChainActor * exchange(const ChainActor * specialActor, const ChainActor * other) const;
+	virtual ChainActor * tryExchange(const ChainActor * specialActor, const ChainActor * other) const;
 };
 
 class HeroExchangeMap
@@ -80,15 +79,14 @@ class HeroExchangeMap
 private:
 	const HeroActor * actor;
 	std::map<const ChainActor *, HeroActor *> exchangeMap;
-	std::map<const ChainActor *, bool> canExchangeCache;
 	const Nullkiller * ai;
+	boost::shared_mutex sync;
 
 public:
 	HeroExchangeMap(const HeroActor * actor, const Nullkiller * ai);
 	~HeroExchangeMap();
 
-	HeroActor * exchange(const ChainActor * other);
-	bool canExchange(const ChainActor * other);
+	HeroActor * tryExchange(const ChainActor * other);
 
 private:
 	HeroExchangeArmy * pickBestCreatures(const CCreatureSet * army1, const CCreatureSet * army2) const;
@@ -113,10 +111,8 @@ public:
 	HeroActor(const CGHeroInstance * hero, HeroRole heroRole, uint64_t chainMask, const Nullkiller * ai);
 	HeroActor(const ChainActor * carrier, const ChainActor * other, const HeroExchangeArmy * army, const Nullkiller * ai);
 
-	virtual bool canExchange(const ChainActor * other) const override;
-
 protected:
-	virtual ChainActor * exchange(const ChainActor * specialActor, const ChainActor * other) const override;
+	virtual ChainActor * tryExchange(const ChainActor * specialActor, const ChainActor * other) const override;
 };
 
 class ObjectActor : public ChainActor

+ 1 - 1
AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.cpp

@@ -143,7 +143,7 @@ namespace AIPathfinding
 					destination.node->layer,
 					destinationNode->actor->resourceActor);
 
-				if(!questNode || questNode.get()->cost < destination.cost)
+				if(!questNode || questNode.get()->getCost() < destination.cost)
 				{
 					return false;
 				}

+ 7 - 5
AI/Nullkiller/VCAI.cpp

@@ -502,7 +502,7 @@ void VCAI::init(std::shared_ptr<CCallback> CB)
 	myCb->unlockGsWhenWaiting = true;
 
 	nullkiller->init(CB, playerID);
-
+	
 	retrieveVisitableObjs();
 }
 
@@ -764,6 +764,7 @@ void VCAI::makeTurn()
 	}
 	catch (boost::thread_interrupted & e)
 	{
+	(void)e;
 		logAi->debug("Making turn thread has been interrupted. We'll end without calling endTurn.");
 		return;
 	}
@@ -1195,7 +1196,7 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h)
 			logAi->error("Hero %s cannot reach %s.", h->name, dst.toString());
 			return true;
 		}
-		int i = path.nodes.size() - 1;
+		int i = (int)path.nodes.size() - 1;
 
 		auto getObj = [&](int3 coord, bool ignoreHero)
 		{
@@ -1391,12 +1392,12 @@ void VCAI::tryRealize(Goals::Trade & g) //trade
 
 				int toGive, toGet;
 				m->getOffer(res, g.resID, toGive, toGet, EMarketMode::RESOURCE_RESOURCE);
-				toGive = toGive * (it->resVal / toGive); //round down
+				toGive = static_cast<int>(toGive * (it->resVal / toGive)); //round down
 				//TODO trade only as much as needed
 				if (toGive) //don't try to sell 0 resources
 				{
 					cb->trade(obj, EMarketMode::RESOURCE_RESOURCE, res, g.resID, toGive);
-					accquiredResources = toGet * (it->resVal / toGive);
+					accquiredResources = static_cast<int>(toGet * (it->resVal / toGive));
 					logAi->debug("Traded %d of %s for %d of %s at %s", toGive, res, accquiredResources, g.resID, obj->getObjectName());
 				}
 				if (cb->getResourceAmount((Res::ERes)g.resID) >= g.value)
@@ -1482,6 +1483,7 @@ void VCAI::finish()
 {
 	//we want to lock to avoid multiple threads from calling makingTurn->join() at same time
 	boost::lock_guard<boost::mutex> multipleCleanupGuard(turnInterruptionMutex);
+
 	if(makingTurn)
 	{
 		makingTurn->interrupt();
@@ -1617,7 +1619,7 @@ void AIStatus::removeQuery(QueryID ID)
 int AIStatus::getQueriesCount()
 {
 	boost::unique_lock<boost::mutex> lock(mx);
-	return remainingQueries.size();
+	return static_cast<int>(remainingQueries.size());
 }
 
 void AIStatus::startedTurn()