瀏覽代碼

Nullkiller: parallel object clusterization, stabilization

Andrii Danylchenko 4 年之前
父節點
當前提交
9a203b8af9

+ 41 - 10
AI/Nullkiller/AIUtility.cpp

@@ -115,6 +115,32 @@ const CGHeroInstance * HeroPtr::get(bool doWeExpectNull) const
 	return h;
 	return h;
 }
 }
 
 
+const CGHeroInstance * HeroPtr::get(CCallback * cb, bool doWeExpectNull) const
+{
+	//TODO? check if these all assertions every time we get info about hero affect efficiency
+	//
+	//behave terribly when attempting unauthorized access to hero that is not ours (or was lost)
+	assert(doWeExpectNull || h);
+
+	if(h)
+	{
+		auto obj = cb->getObj(hid);
+		//const bool owned = obj && obj->tempOwner == ai->playerID;
+
+		if(doWeExpectNull && !obj)
+		{
+			return nullptr;
+		}
+		else
+		{
+			assert(obj);
+			//assert(owned);
+		}
+	}
+
+	return h;
+}
+
 const CGHeroInstance * HeroPtr::operator->() const
 const CGHeroInstance * HeroPtr::operator->() const
 {
 {
 	return get();
 	return get();
@@ -251,6 +277,11 @@ bool canBeEmbarkmentPoint(const TerrainTile * t, bool fromWater)
 	return false;
 	return false;
 }
 }
 
 
+bool isObjectPassable(const Nullkiller * ai, const CGObjectInstance * obj)
+{
+	return isObjectPassable(obj, ai->playerID, ai->cb->getPlayerRelations(obj->tempOwner, ai->playerID));
+}
+
 bool isObjectPassable(const CGObjectInstance * obj)
 bool isObjectPassable(const CGObjectInstance * obj)
 {
 {
 	return isObjectPassable(obj, ai->playerID, cb->getPlayerRelations(obj->tempOwner, ai->playerID));
 	return isObjectPassable(obj, ai->playerID, cb->getPlayerRelations(obj->tempOwner, ai->playerID));
@@ -355,7 +386,7 @@ uint64_t timeElapsed(boost::chrono::time_point<boost::chrono::steady_clock> star
 }
 }
 
 
 // todo: move to obj manager
 // todo: move to obj manager
-bool shouldVisit(const CGHeroInstance * h, const CGObjectInstance * obj)
+bool shouldVisit(const Nullkiller * ai, const CGHeroInstance * h, const CGObjectInstance * obj)
 {
 {
 	switch(obj->ID)
 	switch(obj->ID)
 	{
 	{
@@ -364,7 +395,7 @@ bool shouldVisit(const CGHeroInstance * h, const CGObjectInstance * obj)
 		return obj->tempOwner != h->tempOwner; //do not visit our towns at random
 		return obj->tempOwner != h->tempOwner; //do not visit our towns at random
 	case Obj::BORDER_GATE:
 	case Obj::BORDER_GATE:
 	{
 	{
-		for(auto q : ai->myCb->getMyQuests())
+		for(auto q : ai->cb->getMyQuests())
 		{
 		{
 			if(q.obj == obj)
 			if(q.obj == obj)
 			{
 			{
@@ -378,7 +409,7 @@ bool shouldVisit(const CGHeroInstance * h, const CGObjectInstance * obj)
 	case Obj::SEER_HUT:
 	case Obj::SEER_HUT:
 	case Obj::QUEST_GUARD:
 	case Obj::QUEST_GUARD:
 	{
 	{
-		for(auto q : ai->myCb->getMyQuests())
+		for(auto q : ai->cb->getMyQuests())
 		{
 		{
 			if(q.obj == obj)
 			if(q.obj == obj)
 			{
 			{
@@ -403,7 +434,7 @@ bool shouldVisit(const CGHeroInstance * h, const CGObjectInstance * obj)
 			{
 			{
 				if(level.first
 				if(level.first
 					&& h->getSlotFor(CreatureID(c)) != SlotID()
 					&& h->getSlotFor(CreatureID(c)) != SlotID()
-					&& cb->getResourceAmount().canAfford(c.toCreature()->cost))
+					&& ai->cb->getResourceAmount().canAfford(c.toCreature()->cost))
 				{
 				{
 					return true;
 					return true;
 				}
 				}
@@ -429,7 +460,7 @@ bool shouldVisit(const CGHeroInstance * h, const CGObjectInstance * obj)
 	case Obj::SCHOOL_OF_MAGIC:
 	case Obj::SCHOOL_OF_MAGIC:
 	case Obj::SCHOOL_OF_WAR:
 	case Obj::SCHOOL_OF_WAR:
 	{
 	{
-		if(cb->getResourceAmount(Res::GOLD) < 1000)
+		if(ai->cb->getResourceAmount(Res::GOLD) < 1000)
 			return false;
 			return false;
 		break;
 		break;
 	}
 	}
@@ -439,10 +470,10 @@ bool shouldVisit(const CGHeroInstance * h, const CGObjectInstance * obj)
 		break;
 		break;
 	case Obj::TREE_OF_KNOWLEDGE:
 	case Obj::TREE_OF_KNOWLEDGE:
 	{
 	{
-		if(ai->nullkiller->heroManager->getHeroRole(h) == HeroRole::SCOUT)
+		if(ai->heroManager->getHeroRole(h) == HeroRole::SCOUT)
 			return false;
 			return false;
 
 
-		TResources myRes = cb->getResourceAmount();
+		TResources myRes = ai->cb->getResourceAmount();
 		if(myRes[Res::GOLD] < 2000 || myRes[Res::GEMS] < 10)
 		if(myRes[Res::GOLD] < 2000 || myRes[Res::GEMS] < 10)
 			return false;
 			return false;
 		break;
 		break;
@@ -450,14 +481,14 @@ bool shouldVisit(const CGHeroInstance * h, const CGObjectInstance * obj)
 	case Obj::MAGIC_WELL:
 	case Obj::MAGIC_WELL:
 		return h->mana < h->manaLimit();
 		return h->mana < h->manaLimit();
 	case Obj::PRISON:
 	case Obj::PRISON:
-		return ai->myCb->getHeroesInfo().size() < VLC->modh->settings.MAX_HEROES_ON_MAP_PER_PLAYER;
+		return ai->cb->getHeroesInfo().size() < VLC->modh->settings.MAX_HEROES_ON_MAP_PER_PLAYER;
 	case Obj::TAVERN:
 	case Obj::TAVERN:
 	{
 	{
 		//TODO: make AI actually recruit heroes
 		//TODO: make AI actually recruit heroes
 		//TODO: only on request
 		//TODO: only on request
-		if(ai->myCb->getHeroesInfo().size() >= VLC->modh->settings.MAX_HEROES_ON_MAP_PER_PLAYER)
+		if(ai->cb->getHeroesInfo().size() >= VLC->modh->settings.MAX_HEROES_ON_MAP_PER_PLAYER)
 			return false;
 			return false;
-		else if(cb->getResourceAmount(Res::GOLD) < GameConstants::HERO_GOLD_COST)
+		else if(ai->cb->getResourceAmount(Res::GOLD) < GameConstants::HERO_GOLD_COST)
 			return false;
 			return false;
 		break;
 		break;
 	}
 	}

+ 99 - 1
AI/Nullkiller/AIUtility.h

@@ -18,8 +18,12 @@
 #include "../../lib/mapObjects/CObjectHandler.h"
 #include "../../lib/mapObjects/CObjectHandler.h"
 #include "../../lib/mapObjects/CGHeroInstance.h"
 #include "../../lib/mapObjects/CGHeroInstance.h"
 #include "../../lib/CPathfinder.h"
 #include "../../lib/CPathfinder.h"
+#include <tbb/tbb.h>
+
+using namespace tbb;
 
 
 class CCallback;
 class CCallback;
+class Nullkiller;
 struct creInfo;
 struct creInfo;
 
 
 typedef const int3 & crint3;
 typedef const int3 & crint3;
@@ -72,6 +76,7 @@ public:
 	}
 	}
 
 
 	const CGHeroInstance * get(bool doWeExpectNull = false) const;
 	const CGHeroInstance * get(bool doWeExpectNull = false) const;
+	const CGHeroInstance * get(CCallback * cb, bool doWeExpectNull = false) const;
 	bool validAndSet() const;
 	bool validAndSet() const;
 
 
 
 
@@ -168,6 +173,7 @@ void foreach_neighbour(CCallback * cbp, const int3 & pos, std::function<void(CCa
 
 
 bool canBeEmbarkmentPoint(const TerrainTile * t, bool fromWater);
 bool canBeEmbarkmentPoint(const TerrainTile * t, bool fromWater);
 bool isObjectPassable(const CGObjectInstance * obj);
 bool isObjectPassable(const CGObjectInstance * obj);
+bool isObjectPassable(const Nullkiller * ai, const CGObjectInstance * obj);
 bool isObjectPassable(const CGObjectInstance * obj, PlayerColor playerColor, PlayerRelations::PlayerRelations objectRelations);
 bool isObjectPassable(const CGObjectInstance * obj, PlayerColor playerColor, PlayerRelations::PlayerRelations objectRelations);
 bool isBlockVisitObj(const int3 & pos);
 bool isBlockVisitObj(const int3 & pos);
 
 
@@ -184,7 +190,27 @@ bool compareArtifacts(const CArtifactInstance * a1, const CArtifactInstance * a2
 uint64_t timeElapsed(boost::chrono::time_point<boost::chrono::steady_clock> start);
 uint64_t timeElapsed(boost::chrono::time_point<boost::chrono::steady_clock> start);
 
 
 // todo: move to obj manager
 // todo: move to obj manager
-bool shouldVisit(const CGHeroInstance * h, const CGObjectInstance * obj);
+bool shouldVisit(const Nullkiller * ai, const CGHeroInstance * h, const CGObjectInstance * obj);
+
+template<typename TFunc>
+void pforeachTilePos(crint3 mapSize, TFunc fn)
+{
+	parallel_for(blocked_range<size_t>(0, mapSize.x), [&](const blocked_range<size_t>& r)
+	{
+		int3 pos;
+
+		for(pos.x = r.begin(); pos.x != r.end(); ++pos.x)
+		{
+			for(pos.y = 0; pos.y < mapSize.y; ++pos.y)
+			{
+				for(pos.z = 0; pos.z < mapSize.z; ++pos.z)
+				{
+					fn(pos);
+				}
+			}
+		}
+	});
+}
 
 
 class CDistanceSorter
 class CDistanceSorter
 {
 {
@@ -197,3 +223,75 @@ public:
 	}
 	}
 	bool operator()(const CGObjectInstance * lhs, const CGObjectInstance * rhs) const;
 	bool operator()(const CGObjectInstance * lhs, const CGObjectInstance * rhs) const;
 };
 };
+
+template <class T>
+class SharedPool
+{
+public:
+	struct External_Deleter
+	{
+		explicit External_Deleter(std::weak_ptr<SharedPool<T>* > pool)
+			: pool(pool)
+		{
+		}
+
+		void operator()(T * ptr)
+		{
+			std::unique_ptr<T> uptr(ptr);
+
+			if(auto pool_ptr = pool.lock())
+			{
+				(*pool_ptr.get())->add(std::move(uptr));
+			}
+		}
+
+	private:
+		std::weak_ptr<SharedPool<T>* > pool;
+	};
+
+public:
+	using ptr_type = std::unique_ptr<T, External_Deleter>;
+
+	SharedPool(std::function<std::unique_ptr<T>()> elementFactory)
+		: elementFactory(elementFactory), pool(), sync(), instance_tracker(new SharedPool<T>*(this))
+	{}
+	
+	void add(std::unique_ptr<T> t)
+	{
+		std::lock_guard<std::mutex> lock(sync);
+		pool.push_back(std::move(t));
+	}
+
+	ptr_type acquire()
+	{
+		std::lock_guard<std::mutex> lock(sync);
+		bool poolIsEmpty = pool.empty();
+		T * element = poolIsEmpty
+			? elementFactory().release()
+			: pool.back().release();
+
+		ptr_type tmp(
+			element,
+			External_Deleter(std::weak_ptr<SharedPool<T>*>(instance_tracker)));
+
+		if(!poolIsEmpty) pool.pop_back();
+
+		return std::move(tmp);
+	}
+
+	bool empty() const
+	{
+		return pool.empty();
+	}
+
+	size_t size() const
+	{
+		return pool.size();
+	}
+
+private:
+	std::vector<std::unique_ptr<T>> pool;
+	std::function<std::unique_ptr<T>()> elementFactory;
+	std::shared_ptr<SharedPool<T> *> instance_tracker;
+	std::mutex sync;
+};

+ 8 - 10
AI/Nullkiller/Analyzers/BuildAnalyzer.cpp

@@ -12,8 +12,6 @@
 #include "../../../lib/mapping/CMap.h" //for victory conditions
 #include "../../../lib/mapping/CMap.h" //for victory conditions
 #include "../Engine/Nullkiller.h"
 #include "../Engine/Nullkiller.h"
 
 
-extern boost::thread_specific_ptr<CCallback> cb;
-
 void BuildAnalyzer::updateTownDwellings(TownDevelopmentInfo & developmentInfo)
 void BuildAnalyzer::updateTownDwellings(TownDevelopmentInfo & developmentInfo)
 {
 {
 	auto townInfo = developmentInfo.town->town;
 	auto townInfo = developmentInfo.town->town;
@@ -71,7 +69,7 @@ void BuildAnalyzer::updateOtherBuildings(TownDevelopmentInfo & developmentInfo)
 		{BuildingID::TOWN_HALL, BuildingID::CITY_HALL, BuildingID::CAPITOL}
 		{BuildingID::TOWN_HALL, BuildingID::CITY_HALL, BuildingID::CAPITOL}
 	};
 	};
 
 
-	if(developmentInfo.existingDwellings.size() >= 2 && cb->getDate(Date::DAY_OF_WEEK) > boost::date_time::Friday)
+	if(developmentInfo.existingDwellings.size() >= 2 && ai->cb->getDate(Date::DAY_OF_WEEK) > boost::date_time::Friday)
 	{
 	{
 		otherBuildings.push_back({BuildingID::CITADEL, BuildingID::CASTLE});
 		otherBuildings.push_back({BuildingID::CITADEL, BuildingID::CASTLE});
 	}
 	}
@@ -99,7 +97,7 @@ int32_t convertToGold(const TResources & res)
 
 
 TResources BuildAnalyzer::getResourcesRequiredNow() const
 TResources BuildAnalyzer::getResourcesRequiredNow() const
 {
 {
-	auto resourcesAvailable = cb->getResourceAmount();
+	auto resourcesAvailable = ai->cb->getResourceAmount();
 	auto result = requiredResources - resourcesAvailable;
 	auto result = requiredResources - resourcesAvailable;
 
 
 	result.positive();
 	result.positive();
@@ -109,7 +107,7 @@ TResources BuildAnalyzer::getResourcesRequiredNow() const
 
 
 TResources BuildAnalyzer::getTotalResourcesRequired() const
 TResources BuildAnalyzer::getTotalResourcesRequired() const
 {
 {
-	auto resourcesAvailable = cb->getResourceAmount();
+	auto resourcesAvailable = ai->cb->getResourceAmount();
 	auto result = totalDevelopmentCost - resourcesAvailable;
 	auto result = totalDevelopmentCost - resourcesAvailable;
 
 
 	result.positive();
 	result.positive();
@@ -125,7 +123,7 @@ void BuildAnalyzer::update()
 
 
 	reset();
 	reset();
 
 
-	auto towns = cb->getTownsInfo();
+	auto towns = ai->cb->getTownsInfo();
 
 
 	for(const CGTownInstance* town : towns)
 	for(const CGTownInstance* town : towns)
 	{
 	{
@@ -159,7 +157,7 @@ void BuildAnalyzer::update()
 
 
 	updateDailyIncome();
 	updateDailyIncome();
 
 
-	goldPreasure = (float)armyCost[Res::GOLD] / (1 + cb->getResourceAmount(Res::GOLD) + (float)dailyIncome[Res::GOLD] * 7.0f);
+	goldPreasure = (float)armyCost[Res::GOLD] / (1 + ai->cb->getResourceAmount(Res::GOLD) + (float)dailyIncome[Res::GOLD] * 7.0f);
 
 
 	logAi->trace("Gold preasure: %f", goldPreasure);
 	logAi->trace("Gold preasure: %f", goldPreasure);
 }
 }
@@ -203,7 +201,7 @@ BuildingInfo BuildAnalyzer::getBuildingOrPrerequisite(
 
 
 	if(!town->hasBuilt(building))
 	if(!town->hasBuilt(building))
 	{
 	{
-		auto canBuild = cb->canBuildStructure(town, building);
+		auto canBuild = ai->cb->canBuildStructure(town, building);
 
 
 		if(canBuild == EBuildingState::ALLOWED)
 		if(canBuild == EBuildingState::ALLOWED)
 		{
 		{
@@ -262,8 +260,8 @@ BuildingInfo BuildAnalyzer::getBuildingOrPrerequisite(
 
 
 void BuildAnalyzer::updateDailyIncome()
 void BuildAnalyzer::updateDailyIncome()
 {
 {
-	auto objects = cb->getMyObjects();
-	auto towns = cb->getTownsInfo();
+	auto objects = ai->cb->getMyObjects();
+	auto towns = ai->cb->getTownsInfo();
 	
 	
 	dailyIncome = TResources();
 	dailyIncome = TResources();
 
 

+ 1 - 1
AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp

@@ -51,7 +51,7 @@ void DangerHitMapAnalyzer::updateHitMap()
 
 
 		boost::this_thread::interruption_point();
 		boost::this_thread::interruption_point();
 
 
-		foreach_tile_pos([&](const int3 & pos)
+		pforeachTilePos(mapSize, [&](const int3 & pos)
 		{
 		{
 			for(AIPath & path : ai->pathfinder->getPathInfo(pos))
 			for(AIPath & path : ai->pathfinder->getPathInfo(pos))
 			{
 			{

+ 94 - 88
AI/Nullkiller/Analyzers/ObjectClusterizer.cpp

@@ -16,14 +16,16 @@
 
 
 void ObjectCluster::addObject(const CGObjectInstance * obj, const AIPath & path, float priority)
 void ObjectCluster::addObject(const CGObjectInstance * obj, const AIPath & path, float priority)
 {
 {
-	auto & info = objects[obj];
+	ClusterObjects::accessor info;
 
 
-	if(info.priority < priority)
+	objects.insert(info, ClusterObjects::value_type(obj, ClusterObjectInfo()));
+
+	if(info->second.priority < priority)
 	{
 	{
-		info.priority = priority;
-		info.movementCost = path.movementCost() - path.firstNode().cost;
-		info.danger = path.targetObjectDanger;
-		info.turn = path.turn();
+		info->second.priority = priority;
+		info->second.movementCost = path.movementCost() - path.firstNode().cost;
+		info->second.danger = path.targetObjectDanger;
+		info->second.turn = path.turn();
 	}
 	}
 }
 }
 
 
@@ -125,7 +127,7 @@ const CGObjectInstance * ObjectClusterizer::getBlocker(const AIPath & path) cons
 			|| blocker->ID == Obj::BORDER_GATE
 			|| blocker->ID == Obj::BORDER_GATE
 			|| blocker->ID == Obj::SHIPYARD)
 			|| blocker->ID == Obj::SHIPYARD)
 		{
 		{
-			if(!isObjectPassable(blocker))
+			if(!isObjectPassable(ai, blocker))
 				return blocker;
 				return blocker;
 		}
 		}
 
 
@@ -206,130 +208,134 @@ void ObjectClusterizer::clusterize()
 
 
 	logAi->debug("Begin object clusterization");
 	logAi->debug("Begin object clusterization");
 
 
-	for(const CGObjectInstance * obj : ai->memory->visitableObjs)
+	std::vector<const CGObjectInstance *> objs(
+		ai->memory->visitableObjs.begin(),
+		ai->memory->visitableObjs.end());
+
+	parallel_for(blocked_range<size_t>(0, objs.size()), [&](const blocked_range<size_t> & r)
 	{
 	{
-		if(!shouldVisitObject(obj))
-			continue;
+		auto priorityEvaluator = ai->priorityEvaluators->acquire();
+
+		for(int i = r.begin(); i != r.end(); i++)
+		{
+			auto obj = objs[i];
+
+			if(!shouldVisitObject(obj))
+				return;
 
 
 #if AI_TRACE_LEVEL >= 2
 #if AI_TRACE_LEVEL >= 2
-		logAi->trace("Check object %s%s.", obj->getObjectName(), obj->visitablePos().toString());
+			logAi->trace("Check object %s%s.", obj->getObjectName(), obj->visitablePos().toString());
 #endif
 #endif
 
 
-		auto paths = ai->pathfinder->getPathInfo(obj->visitablePos());
+			auto paths = ai->pathfinder->getPathInfo(obj->visitablePos());
 
 
-		if(paths.empty())
-		{
+			if(paths.empty())
+			{
 #if AI_TRACE_LEVEL >= 2
 #if AI_TRACE_LEVEL >= 2
-			logAi->trace("No paths found.");
+				logAi->trace("No paths found.");
 #endif
 #endif
-			continue;
-		}
+				continue;
+			}
 
 
-		std::sort(paths.begin(), paths.end(), [](const AIPath & p1, const AIPath & p2) -> bool
-		{
-			return p1.movementCost() < p2.movementCost();
-		});
+			std::sort(paths.begin(), paths.end(), [](const AIPath & p1, const AIPath & p2) -> bool
+			{
+				return p1.movementCost() < p2.movementCost();
+			});
 
 
-		if(vstd::contains(ignoreObjects, obj->ID))
-		{
-			farObjects.addObject(obj, paths.front(), 0);
+			if(vstd::contains(ignoreObjects, obj->ID))
+			{
+				farObjects.addObject(obj, paths.front(), 0);
 
 
 #if AI_TRACE_LEVEL >= 2
 #if AI_TRACE_LEVEL >= 2
-			logAi->trace("Object ignored. Moved to far objects with path %s", paths.front().toString());
+				logAi->trace("Object ignored. Moved to far objects with path %s", paths.front().toString());
 #endif
 #endif
 
 
-			continue;
-		}
-		
-		std::set<const CGHeroInstance *> heroesProcessed;
+				continue;
+			}
 
 
-		for(auto & path : paths)
-		{
-#if AI_TRACE_LEVEL >= 2
-			logAi->trace("Checking path %s", path.toString());
-#endif
+			std::set<const CGHeroInstance *> heroesProcessed;
 
 
-			if(!shouldVisit(path.targetHero, obj))
+			for(auto & path : paths)
 			{
 			{
 #if AI_TRACE_LEVEL >= 2
 #if AI_TRACE_LEVEL >= 2
-				logAi->trace("Hero %s does not need to visit %s", path.targetHero->name, obj->getObjectName());
+				logAi->trace("Checking path %s", path.toString());
 #endif
 #endif
-				continue;
-			}
 
 
-			if(path.nodes.size() > 1)
-			{
-				auto blocker = getBlocker(path);
+				if(!shouldVisit(ai, path.targetHero, obj))
+				{
+#if AI_TRACE_LEVEL >= 2
+					logAi->trace("Hero %s does not need to visit %s", path.targetHero->name, obj->getObjectName());
+#endif
+					continue;
+				}
 
 
-				if(blocker)
+				if(path.nodes.size() > 1)
 				{
 				{
-					if(vstd::contains(heroesProcessed, path.targetHero))
-					{
-	#if AI_TRACE_LEVEL >= 2
-						logAi->trace("Hero %s is already processed.", path.targetHero->name);
-	#endif
-						continue;
-					}
+					auto blocker = getBlocker(path);
 
 
-					heroesProcessed.insert(path.targetHero);
+					if(blocker)
+					{
+						if(vstd::contains(heroesProcessed, path.targetHero))
+						{
+#if AI_TRACE_LEVEL >= 2
+							logAi->trace("Hero %s is already processed.", path.targetHero->name);
+#endif
+							continue;
+						}
 
 
-					auto cluster = blockedObjects[blocker];
+						heroesProcessed.insert(path.targetHero);
 
 
-					if(!cluster)
-					{
-						cluster.reset(new ObjectCluster(blocker));
-						blockedObjects[blocker] = cluster;
-					}
+						float priority = priorityEvaluator->evaluate(Goals::sptr(Goals::ExecuteHeroChain(path, obj)));
 
 
-					float priority = ai->priorityEvaluator->evaluate(Goals::sptr(Goals::ExecuteHeroChain(path, obj)));
+						if(priority < MIN_PRIORITY)
+							continue;
 
 
-					if(priority < MIN_PRIORITY)
-						continue;
+						ClusterMap::accessor cluster;
+						blockedObjects.insert(
+							cluster,
+							ClusterMap::value_type(blocker, std::make_shared<ObjectCluster>(blocker)));
 
 
-					cluster->addObject(obj, path, priority);
+						cluster->second->addObject(obj, path, priority);
 
 
 #if AI_TRACE_LEVEL >= 2
 #if AI_TRACE_LEVEL >= 2
-					logAi->trace("Path added to cluster %s%s", blocker->getObjectName(), blocker->visitablePos().toString());
+						logAi->trace("Path added to cluster %s%s", blocker->getObjectName(), blocker->visitablePos().toString());
 #endif
 #endif
-					continue;
+						continue;
+					}
 				}
 				}
-			}
-			
-			heroesProcessed.insert(path.targetHero);
 
 
-			float priority = ai->priorityEvaluator->evaluate(Goals::sptr(Goals::ExecuteHeroChain(path, obj)));
+				heroesProcessed.insert(path.targetHero);
 
 
-			if(priority < MIN_PRIORITY)
-				continue;
-			
-			bool interestingObject = path.turn() <= 2 || priority > 0.5f;
+				float priority = priorityEvaluator->evaluate(Goals::sptr(Goals::ExecuteHeroChain(path, obj)));
 
 
-			if(interestingObject)
-			{
-				nearObjects.addObject(obj, path, priority);
-			}
-			else
-			{
-				farObjects.addObject(obj, path, priority);
-			}
+				if(priority < MIN_PRIORITY)
+					continue;
+
+				bool interestingObject = path.turn() <= 2 || priority > 0.5f;
+
+				if(interestingObject)
+				{
+					nearObjects.addObject(obj, path, priority);
+				}
+				else
+				{
+					farObjects.addObject(obj, path, priority);
+				}
 
 
 #if AI_TRACE_LEVEL >= 2
 #if AI_TRACE_LEVEL >= 2
-			logAi->trace("Path %s added to %s objects. Turn: %d, priority: %f",
-				path.toString(),
-				interestingObject ? "near" : "far",
-				path.turn(),
-				priority);
+				logAi->trace("Path %s added to %s objects. Turn: %d, priority: %f",
+					path.toString(),
+					interestingObject ? "near" : "far",
+					path.turn(),
+					priority);
 #endif
 #endif
+			}
 		}
 		}
-	}
-
-	vstd::erase_if(blockedObjects, [](std::pair<const CGObjectInstance *, std::shared_ptr<ObjectCluster>> pair) -> bool
-	{
-		return pair.second->objects.empty();
 	});
 	});
 
 
 	logAi->trace("Near objects count: %i", nearObjects.objects.size());
 	logAi->trace("Near objects count: %i", nearObjects.objects.size());
 	logAi->trace("Far objects count: %i", farObjects.objects.size());
 	logAi->trace("Far objects count: %i", farObjects.objects.size());
+
 	for(auto pair : blockedObjects)
 	for(auto pair : blockedObjects)
 	{
 	{
 		logAi->trace("Cluster %s %s count: %i", pair.first->getObjectName(), pair.first->visitablePos().toString(), pair.second->objects.size());
 		logAi->trace("Cluster %s %s count: %i", pair.first->getObjectName(), pair.first->visitablePos().toString(), pair.second->objects.size());

+ 6 - 2
AI/Nullkiller/Analyzers/ObjectClusterizer.h

@@ -19,10 +19,12 @@ struct ClusterObjectInfo
 	uint8_t turn;
 	uint8_t turn;
 };
 };
 
 
+typedef tbb::concurrent_hash_map<const CGObjectInstance *, ClusterObjectInfo> ClusterObjects;
+
 struct ObjectCluster
 struct ObjectCluster
 {
 {
 public:
 public:
-	std::map<const CGObjectInstance *, ClusterObjectInfo> objects;
+	ClusterObjects objects;
 	const CGObjectInstance * blocker;
 	const CGObjectInstance * blocker;
 
 
 	void reset()
 	void reset()
@@ -45,12 +47,14 @@ public:
 	const CGObjectInstance * calculateCenter() const;
 	const CGObjectInstance * calculateCenter() const;
 };
 };
 
 
+typedef tbb::concurrent_hash_map<const CGObjectInstance *, std::shared_ptr<ObjectCluster>> ClusterMap;
+
 class ObjectClusterizer
 class ObjectClusterizer
 {
 {
 private:
 private:
 	ObjectCluster nearObjects;
 	ObjectCluster nearObjects;
 	ObjectCluster farObjects;
 	ObjectCluster farObjects;
-	std::map<const CGObjectInstance *, std::shared_ptr<ObjectCluster>> blockedObjects;
+	ClusterMap blockedObjects;
 	const Nullkiller * ai;
 	const Nullkiller * ai;
 
 
 public:
 public:

+ 1 - 1
AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp

@@ -71,7 +71,7 @@ Goals::TGoalVec CaptureObjectsBehavior::getVisitGoals(const std::vector<AIPath>
 			continue;
 			continue;
 		}
 		}
 
 
-		if(objToVisit && !shouldVisit(path.targetHero, objToVisit))
+		if(objToVisit && !shouldVisit(ai->nullkiller.get(), path.targetHero, objToVisit))
 			continue;
 			continue;
 
 
 		auto hero = path.targetHero;
 		auto hero = path.targetHero;

+ 22 - 8
AI/Nullkiller/Engine/Nullkiller.cpp

@@ -43,6 +43,13 @@ void Nullkiller::init(std::shared_ptr<CCallback> cb, PlayerColor playerID)
 	this->playerID = playerID;
 	this->playerID = playerID;
 
 
 	priorityEvaluator.reset(new PriorityEvaluator(this));
 	priorityEvaluator.reset(new PriorityEvaluator(this));
+	priorityEvaluators.reset(
+		new SharedPool<PriorityEvaluator>(
+			[&]()->std::unique_ptr<PriorityEvaluator>
+			{
+				return std::make_unique<PriorityEvaluator>(this);
+			}));
+
 	dangerHitMap.reset(new DangerHitMapAnalyzer(this));
 	dangerHitMap.reset(new DangerHitMapAnalyzer(this));
 	buildAnalyzer.reset(new BuildAnalyzer(this));
 	buildAnalyzer.reset(new BuildAnalyzer(this));
 	objectClusterizer.reset(new ObjectClusterizer(this));
 	objectClusterizer.reset(new ObjectClusterizer(this));
@@ -224,16 +231,23 @@ void Nullkiller::makeTurn()
 		HeroPtr hero = bestTask->getHero();
 		HeroPtr hero = bestTask->getHero();
 
 
 		if(bestTask->priority < NEXT_SCAN_MIN_PRIORITY
 		if(bestTask->priority < NEXT_SCAN_MIN_PRIORITY
-			&& hero.validAndSet()
-			&& heroManager->getHeroRole(hero) == HeroRole::MAIN
 			&& scanDepth != ScanDepth::FULL)
 			&& scanDepth != ScanDepth::FULL)
 		{
 		{
-			logAi->trace(
-				"Goal %s has too low priority %f so increasing scan depth",
-				bestTask->toString(),
-				bestTask->priority);
-			scanDepth = (ScanDepth)((int)scanDepth + 1);
-			continue;
+			HeroRole heroRole = HeroRole::MAIN;
+
+			if(hero.validAndSet())
+				heroRole = heroManager->getHeroRole(hero);
+
+			if(heroRole == HeroRole::MAIN)
+			{
+				logAi->trace(
+					"Goal %s has too low priority %f so increasing scan depth",
+					bestTask->toString(),
+					bestTask->priority);
+				scanDepth = (ScanDepth)((int)scanDepth + 1);
+
+				continue;
+			}
 		}
 		}
 
 
 		if(bestTask->priority < MIN_PRIORITY)
 		if(bestTask->priority < MIN_PRIORITY)

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

@@ -9,8 +9,6 @@
 */
 */
 #pragma once
 #pragma once
 
 
-#include <boost/asio.hpp>
-
 #include "PriorityEvaluator.h"
 #include "PriorityEvaluator.h"
 #include "FuzzyHelper.h"
 #include "FuzzyHelper.h"
 #include "AIMemory.h"
 #include "AIMemory.h"
@@ -58,6 +56,7 @@ public:
 	std::unique_ptr<BuildAnalyzer> buildAnalyzer;
 	std::unique_ptr<BuildAnalyzer> buildAnalyzer;
 	std::unique_ptr<ObjectClusterizer> objectClusterizer;
 	std::unique_ptr<ObjectClusterizer> objectClusterizer;
 	std::unique_ptr<PriorityEvaluator> priorityEvaluator;
 	std::unique_ptr<PriorityEvaluator> priorityEvaluator;
+	std::unique_ptr<SharedPool<PriorityEvaluator>> priorityEvaluators;
 	std::unique_ptr<AIPathfinder> pathfinder;
 	std::unique_ptr<AIPathfinder> pathfinder;
 	std::unique_ptr<HeroManager> heroManager;
 	std::unique_ptr<HeroManager> heroManager;
 	std::unique_ptr<ArmyManager> armyManager;
 	std::unique_ptr<ArmyManager> armyManager;

+ 28 - 20
AI/Nullkiller/Engine/PriorityEvaluator.cpp

@@ -29,8 +29,6 @@
 #define MIN_AI_STRENGHT (0.5f) //lower when combat AI gets smarter
 #define MIN_AI_STRENGHT (0.5f) //lower when combat AI gets smarter
 #define UNGUARDED_OBJECT (100.0f) //we consider unguarded objects 100 times weaker than us
 #define UNGUARDED_OBJECT (100.0f) //we consider unguarded objects 100 times weaker than us
 
 
-extern boost::thread_specific_ptr<CCallback> cb;
-
 EvaluationContext::EvaluationContext(const Nullkiller * ai)
 EvaluationContext::EvaluationContext(const Nullkiller * ai)
 	: movementCost(0.0),
 	: movementCost(0.0),
 	manaCost(0),
 	manaCost(0),
@@ -78,7 +76,7 @@ void PriorityEvaluator::initVisitTile()
 	value = engine->getOutputVariable("Value");
 	value = engine->getOutputVariable("Value");
 }
 }
 
 
-int32_t estimateTownIncome(const CGObjectInstance * target, const CGHeroInstance * hero)
+int32_t estimateTownIncome(CCallback * cb, const CGObjectInstance * target, const CGHeroInstance * hero)
 {
 {
 	auto relations = cb->getPlayerRelations(hero->tempOwner, target->tempOwner);
 	auto relations = cb->getPlayerRelations(hero->tempOwner, target->tempOwner);
 
 
@@ -116,7 +114,7 @@ uint64_t getCreatureBankArmyReward(const CGObjectInstance * target, const CGHero
 	return result;
 	return result;
 }
 }
 
 
-uint64_t getDwellingScore(const CGObjectInstance * target, bool checkGold)
+uint64_t getDwellingScore(CCallback * cb, const CGObjectInstance * target, bool checkGold)
 {
 {
 	auto dwelling = dynamic_cast<const CGDwelling *>(target);
 	auto dwelling = dynamic_cast<const CGDwelling *>(target);
 	uint64_t score = 0;
 	uint64_t score = 0;
@@ -207,14 +205,14 @@ uint64_t RewardEvaluator::getArmyReward(
 	case Obj::TOWN:
 	case Obj::TOWN:
 		return target->tempOwner == PlayerColor::NEUTRAL ? 1000 : 10000;
 		return target->tempOwner == PlayerColor::NEUTRAL ? 1000 : 10000;
 	case Obj::HILL_FORT:
 	case Obj::HILL_FORT:
-		return ai->armyManager->calculateCreateresUpgrade(army, target, cb->getResourceAmount()).upgradeValue;
+		return ai->armyManager->calculateCreateresUpgrade(army, target, ai->cb->getResourceAmount()).upgradeValue;
 	case Obj::CREATURE_BANK:
 	case Obj::CREATURE_BANK:
 		return getCreatureBankArmyReward(target, hero);
 		return getCreatureBankArmyReward(target, hero);
 	case Obj::CREATURE_GENERATOR1:
 	case Obj::CREATURE_GENERATOR1:
 	case Obj::CREATURE_GENERATOR2:
 	case Obj::CREATURE_GENERATOR2:
 	case Obj::CREATURE_GENERATOR3:
 	case Obj::CREATURE_GENERATOR3:
 	case Obj::CREATURE_GENERATOR4:
 	case Obj::CREATURE_GENERATOR4:
-		return getDwellingScore(target, checkGold);
+		return getDwellingScore(ai->cb.get(), target, checkGold);
 	case Obj::CRYPT:
 	case Obj::CRYPT:
 	case Obj::SHIPWRECK:
 	case Obj::SHIPWRECK:
 	case Obj::SHIPWRECK_SURVIVOR:
 	case Obj::SHIPWRECK_SURVIVOR:
@@ -225,7 +223,7 @@ uint64_t RewardEvaluator::getArmyReward(
 	case Obj::DRAGON_UTOPIA:
 	case Obj::DRAGON_UTOPIA:
 		return 10000;
 		return 10000;
 	case Obj::HERO:
 	case Obj::HERO:
-		return cb->getPlayerRelations(target->tempOwner, ai->playerID) == PlayerRelations::ENEMIES
+		return ai->cb->getPlayerRelations(target->tempOwner, ai->playerID) == PlayerRelations::ENEMIES
 			? enemyArmyEliminationRewardRatio * dynamic_cast<const CGHeroInstance *>(target)->getArmyStrength()
 			? enemyArmyEliminationRewardRatio * dynamic_cast<const CGHeroInstance *>(target)->getArmyStrength()
 			: 0;
 			: 0;
 	default:
 	default:
@@ -241,7 +239,7 @@ int RewardEvaluator::getGoldCost(const CGObjectInstance * target, const CGHeroIn
 	switch(target->ID)
 	switch(target->ID)
 	{
 	{
 	case Obj::HILL_FORT:
 	case Obj::HILL_FORT:
-		return ai->armyManager->calculateCreateresUpgrade(army, target, cb->getResourceAmount()).upgradeCost[Res::GOLD];
+		return ai->armyManager->calculateCreateresUpgrade(army, target, ai->cb->getResourceAmount()).upgradeCost[Res::GOLD];
 	case Obj::SCHOOL_OF_MAGIC:
 	case Obj::SCHOOL_OF_MAGIC:
 	case Obj::SCHOOL_OF_WAR:
 	case Obj::SCHOOL_OF_WAR:
 		return 1000;
 		return 1000;
@@ -325,7 +323,7 @@ float RewardEvaluator::getStrategicalValue(const CGObjectInstance * target) cons
 			: 0.5f;
 			: 0.5f;
 
 
 	case Obj::HERO:
 	case Obj::HERO:
-		return cb->getPlayerRelations(target->tempOwner, ai->playerID) == PlayerRelations::ENEMIES
+		return ai->cb->getPlayerRelations(target->tempOwner, ai->playerID) == PlayerRelations::ENEMIES
 			? getEnemyHeroStrategicalValue(dynamic_cast<const CGHeroInstance *>(target))
 			? getEnemyHeroStrategicalValue(dynamic_cast<const CGHeroInstance *>(target))
 			: 0;
 			: 0;
 
 
@@ -380,7 +378,7 @@ float RewardEvaluator::getSkillReward(const CGObjectInstance * target, const CGH
 	case Obj::WITCH_HUT:
 	case Obj::WITCH_HUT:
 		return evaluateWitchHutSkillScore(dynamic_cast<const CGWitchHut *>(target), hero, role);
 		return evaluateWitchHutSkillScore(dynamic_cast<const CGWitchHut *>(target), hero, role);
 	case Obj::HERO:
 	case Obj::HERO:
-		return cb->getPlayerRelations(target->tempOwner, ai->playerID) == PlayerRelations::ENEMIES
+		return ai->cb->getPlayerRelations(target->tempOwner, ai->playerID) == PlayerRelations::ENEMIES
 			? enemyHeroEliminationSkillRewardRatio * dynamic_cast<const CGHeroInstance *>(target)->level
 			? enemyHeroEliminationSkillRewardRatio * dynamic_cast<const CGHeroInstance *>(target)->level
 			: 0;
 			: 0;
 	default:
 	default:
@@ -438,7 +436,7 @@ int32_t RewardEvaluator::getGoldReward(const CGObjectInstance * target, const CG
 	case Obj::WATER_WHEEL:
 	case Obj::WATER_WHEEL:
 		return 1000;
 		return 1000;
 	case Obj::TOWN:
 	case Obj::TOWN:
-		return dailyIncomeMultiplier * estimateTownIncome(target, hero);
+		return dailyIncomeMultiplier * estimateTownIncome(ai->cb.get(), target, hero);
 	case Obj::MINE:
 	case Obj::MINE:
 	case Obj::ABANDONED_MINE:
 	case Obj::ABANDONED_MINE:
 		return dailyIncomeMultiplier * (isGold ? 1000 : 75);
 		return dailyIncomeMultiplier * (isGold ? 1000 : 75);
@@ -459,7 +457,7 @@ int32_t RewardEvaluator::getGoldReward(const CGObjectInstance * target, const CG
 	case Obj::SEA_CHEST:
 	case Obj::SEA_CHEST:
 		return 1500;
 		return 1500;
 	case Obj::HERO:
 	case Obj::HERO:
-		return cb->getPlayerRelations(target->tempOwner, ai->playerID) == PlayerRelations::ENEMIES
+		return ai->cb->getPlayerRelations(target->tempOwner, ai->playerID) == PlayerRelations::ENEMIES
 			? heroEliminationBonus + enemyArmyEliminationGoldRewardRatio * getArmyCost(dynamic_cast<const CGHeroInstance *>(target))
 			? heroEliminationBonus + enemyArmyEliminationGoldRewardRatio * getArmyCost(dynamic_cast<const CGHeroInstance *>(target))
 			: 0;
 			: 0;
 	default:
 	default:
@@ -550,7 +548,12 @@ public:
 
 
 class ExecuteHeroChainEvaluationContextBuilder : public IEvaluationContextBuilder
 class ExecuteHeroChainEvaluationContextBuilder : public IEvaluationContextBuilder
 {
 {
+private:
+	const Nullkiller * ai;
+
 public:
 public:
+	ExecuteHeroChainEvaluationContextBuilder(const Nullkiller * ai) : ai(ai) {}
+
 	virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
 	virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
 	{
 	{
 		if(task->goalType != Goals::EXECUTE_HERO_CHAIN)
 		if(task->goalType != Goals::EXECUTE_HERO_CHAIN)
@@ -578,14 +581,14 @@ public:
 		}
 		}
 
 
 		auto heroPtr = task->hero;
 		auto heroPtr = task->hero;
-		auto day = cb->getDate(Date::DAY);
-		auto hero = heroPtr.get();
+		auto day = ai->cb->getDate(Date::DAY);
+		auto hero = heroPtr.get(ai->cb.get());
 		bool checkGold = evaluationContext.danger == 0;
 		bool checkGold = evaluationContext.danger == 0;
 		auto army = path.heroArmy;
 		auto army = path.heroArmy;
 
 
-		const CGObjectInstance * target = cb->getObj((ObjectInstanceID)task->objid, false);
+		const CGObjectInstance * target = ai->cb->getObj((ObjectInstanceID)task->objid, false);
 
 
-		if (target && cb->getPlayerRelations(target->tempOwner, hero->tempOwner) == PlayerRelations::ENEMIES)
+		if (target && ai->cb->getPlayerRelations(target->tempOwner, hero->tempOwner) == PlayerRelations::ENEMIES)
 		{
 		{
 			evaluationContext.goldReward += evaluationContext.evaluator.getGoldReward(target, hero);
 			evaluationContext.goldReward += evaluationContext.evaluator.getGoldReward(target, hero);
 			evaluationContext.armyReward += evaluationContext.evaluator.getArmyReward(target, hero, army, checkGold);
 			evaluationContext.armyReward += evaluationContext.evaluator.getArmyReward(target, hero, army, checkGold);
@@ -603,7 +606,12 @@ public:
 
 
 class ClusterEvaluationContextBuilder : public IEvaluationContextBuilder
 class ClusterEvaluationContextBuilder : public IEvaluationContextBuilder
 {
 {
+private:
+	const Nullkiller * ai;
+
 public:
 public:
+	ClusterEvaluationContextBuilder(const Nullkiller * ai) : ai(ai) {}
+
 	virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
 	virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
 	{
 	{
 		if(task->goalType != Goals::UNLOCK_CLUSTER)
 		if(task->goalType != Goals::UNLOCK_CLUSTER)
@@ -627,7 +635,7 @@ public:
 		for(auto objInfo : objects)
 		for(auto objInfo : objects)
 		{
 		{
 			auto target = objInfo.first;
 			auto target = objInfo.first;
-			auto day = cb->getDate(Date::DAY);
+			auto day = ai->cb->getDate(Date::DAY);
 			bool checkGold = objInfo.second.danger == 0;
 			bool checkGold = objInfo.second.danger == 0;
 			auto army = hero;
 			auto army = hero;
 
 
@@ -709,9 +717,9 @@ PriorityEvaluator::PriorityEvaluator(const Nullkiller * ai)
 	:ai(ai)
 	:ai(ai)
 {
 {
 	initVisitTile();
 	initVisitTile();
-	evaluationContextBuilders.push_back(std::make_shared<ExecuteHeroChainEvaluationContextBuilder>());
+	evaluationContextBuilders.push_back(std::make_shared<ExecuteHeroChainEvaluationContextBuilder>(ai));
 	evaluationContextBuilders.push_back(std::make_shared<BuildThisEvaluationContextBuilder>());
 	evaluationContextBuilders.push_back(std::make_shared<BuildThisEvaluationContextBuilder>());
-	evaluationContextBuilders.push_back(std::make_shared<ClusterEvaluationContextBuilder>());
+	evaluationContextBuilders.push_back(std::make_shared<ClusterEvaluationContextBuilder>(ai));
 	evaluationContextBuilders.push_back(std::make_shared<HeroExchangeEvaluator>());
 	evaluationContextBuilders.push_back(std::make_shared<HeroExchangeEvaluator>());
 	evaluationContextBuilders.push_back(std::make_shared<ArmyUpgradeEvaluator>());
 	evaluationContextBuilders.push_back(std::make_shared<ArmyUpgradeEvaluator>());
 	evaluationContextBuilders.push_back(std::make_shared<DefendTownEvaluator>());
 	evaluationContextBuilders.push_back(std::make_shared<DefendTownEvaluator>());
@@ -769,7 +777,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task)
 		closestHeroRatioVariable->setValue(evaluationContext.closestWayRatio);
 		closestHeroRatioVariable->setValue(evaluationContext.closestWayRatio);
 		strategicalValueVariable->setValue(evaluationContext.strategicalValue);
 		strategicalValueVariable->setValue(evaluationContext.strategicalValue);
 		goldPreasureVariable->setValue(ai->buildAnalyzer->getGoldPreasure());
 		goldPreasureVariable->setValue(ai->buildAnalyzer->getGoldPreasure());
-		goldCostVariable->setValue(evaluationContext.goldCost / ((float)cb->getResourceAmount(Res::GOLD) + (float)ai->buildAnalyzer->getDailyIncome()[Res::GOLD] + 1.0f));
+		goldCostVariable->setValue(evaluationContext.goldCost / ((float)ai->cb->getResourceAmount(Res::GOLD) + (float)ai->buildAnalyzer->getDailyIncome()[Res::GOLD] + 1.0f));
 		turnVariable->setValue(evaluationContext.turn);
 		turnVariable->setValue(evaluationContext.turn);
 		fearVariable->setValue(evaluationContext.enemyHeroDangerRatio);
 		fearVariable->setValue(evaluationContext.enemyHeroDangerRatio);
 
 

+ 2 - 5
AI/Nullkiller/Pathfinding/AINodeStorage.cpp

@@ -8,7 +8,6 @@
 *
 *
 */
 */
 #include "StdInc.h"
 #include "StdInc.h"
-#include <tbb/tbb.h>
 #include "AINodeStorage.h"
 #include "AINodeStorage.h"
 #include "Actions/TownPortalAction.h"
 #include "Actions/TownPortalAction.h"
 #include "../Goals/Goals.h"
 #include "../Goals/Goals.h"
@@ -20,8 +19,6 @@
 #include "../../../lib/PathfinderUtil.h"
 #include "../../../lib/PathfinderUtil.h"
 #include "../../../lib/CPlayerState.h"
 #include "../../../lib/CPlayerState.h"
 
 
-using namespace tbb;
-
 std::shared_ptr<boost::multi_array<AIPathNode, 5>> AISharedStorage::shared;
 std::shared_ptr<boost::multi_array<AIPathNode, 5>> AISharedStorage::shared;
 std::set<int3> commitedTiles;
 std::set<int3> commitedTiles;
 std::set<int3> commitedTilesInitial;
 std::set<int3> commitedTilesInitial;
@@ -504,7 +501,7 @@ bool AINodeStorage::calculateHeroChain()
 	{
 	{
 		std::mutex resultMutex;
 		std::mutex resultMutex;
 
 
-	std::random_shuffle(data.begin(), data.end());
+		std::random_shuffle(data.begin(), data.end());
 
 
 		parallel_for(blocked_range<size_t>(0, data.size()), [&](const blocked_range<size_t>& r)
 		parallel_for(blocked_range<size_t>(0, data.size()), [&](const blocked_range<size_t>& r)
 		{
 		{
@@ -526,7 +523,7 @@ bool AINodeStorage::calculateHeroChain()
 		HeroChainCalculationTask task(*this, nodes, data, chainMask, heroChainTurn);
 		HeroChainCalculationTask task(*this, nodes, data, chainMask, heroChainTurn);
 
 
 		task.execute(r);
 		task.execute(r);
-		task.flushResult(heroChain);\
+		task.flushResult(heroChain);
 	}
 	}
 
 
 	CCreature::DisableChildLinkage = false;
 	CCreature::DisableChildLinkage = false;

+ 3 - 2
AI/Nullkiller/Pathfinding/Actions/BoatActions.h

@@ -51,10 +51,11 @@ namespace AIPathfinding
 	{
 	{
 	private:
 	private:
 		const IShipyard * shipyard;
 		const IShipyard * shipyard;
+		const CPlayerSpecificInfoCallback * cb;
 
 
 	public:
 	public:
-		BuildBoatAction(const IShipyard * shipyard)
-			: shipyard(shipyard)
+		BuildBoatAction(const CPlayerSpecificInfoCallback * cb, const IShipyard * shipyard)
+			: cb(cb), shipyard(shipyard)
 		{
 		{
 		}
 		}
 
 

+ 12 - 1
AI/Nullkiller/Pathfinding/Actors.cpp

@@ -196,15 +196,26 @@ HeroExchangeMap::HeroExchangeMap(const HeroActor * actor, const Nullkiller * ai)
 
 
 HeroExchangeMap::~HeroExchangeMap()
 HeroExchangeMap::~HeroExchangeMap()
 {
 {
+	CCreature::DisableChildLinkage = true;
+
 	for(auto & exchange : exchangeMap)
 	for(auto & exchange : exchangeMap)
 	{
 	{
 		if(!exchange.second) continue;
 		if(!exchange.second) continue;
 
 
 		delete exchange.second->creatureSet;
 		delete exchange.second->creatureSet;
+	}
+
+	CCreature::DisableChildLinkage = false;
+
+	for(auto & exchange : exchangeMap)
+	{
+		if(!exchange.second) continue;
+
 		delete exchange.second;
 		delete exchange.second;
 	}
 	}
 
 
 	exchangeMap.clear();
 	exchangeMap.clear();
+
 }
 }
 
 
 ExchangeResult HeroExchangeMap::tryExchangeNoLock(const ChainActor * other)
 ExchangeResult HeroExchangeMap::tryExchangeNoLock(const ChainActor * other)
@@ -270,7 +281,7 @@ ExchangeResult HeroExchangeMap::tryExchangeNoLock(const ChainActor * other)
 		}
 		}
 
 
 	if(other->isMovable && other->armyValue <= actor->armyValue / 10 && other->armyValue < MIN_ARMY_STRENGTH_FOR_CHAIN)
 	if(other->isMovable && other->armyValue <= actor->armyValue / 10 && other->armyValue < MIN_ARMY_STRENGTH_FOR_CHAIN)
-		return nullptr;
+		return result;
 
 
 	TResources availableResources = resources - actor->armyCost - other->armyCost;
 	TResources availableResources = resources - actor->armyCost - other->armyCost;
 	HeroExchangeArmy * upgradedInitialArmy = tryUpgrade(actor->creatureSet, other->getActorObject(), availableResources);
 	HeroExchangeArmy * upgradedInitialArmy = tryUpgrade(actor->creatureSet, other->getActorObject(), availableResources);

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

@@ -69,7 +69,7 @@ namespace AIPathfinding
 			if(shipyard->shipyardStatus() == IShipyard::GOOD)
 			if(shipyard->shipyardStatus() == IShipyard::GOOD)
 			{
 			{
 				int3 boatLocation = shipyard->bestLocation();
 				int3 boatLocation = shipyard->bestLocation();
-				virtualBoats[boatLocation] = std::make_shared<BuildBoatAction>(shipyard);
+				virtualBoats[boatLocation] = std::make_shared<BuildBoatAction>(cb, shipyard);
 				logAi->debug("Virtual boat added at %s", boatLocation.toString());
 				logAi->debug("Virtual boat added at %s", boatLocation.toString());
 			}
 			}
 		}
 		}