Browse Source

Merge pull request #5496 from IvanSavenko/std_thread

Replace boost::thread with std::thread
Ivan Savenko 9 months ago
parent
commit
4a6a8f9496
48 changed files with 319 additions and 369 deletions
  1. 25 32
      AI/BattleAI/BattleAI.cpp
  2. 53 39
      AI/Nullkiller/AIGateway.cpp
  3. 6 15
      AI/Nullkiller/AIGateway.h
  4. 1 1
      AI/Nullkiller/Analyzers/BuildAnalyzer.cpp
  5. 1 1
      AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp
  6. 7 7
      AI/Nullkiller/Engine/Nullkiller.cpp
  7. 3 0
      AI/Nullkiller/Engine/Nullkiller.h
  8. 4 4
      AI/Nullkiller/Pathfinding/AIPathfinder.cpp
  9. 0 1
      AI/StupidAI/StupidAI.cpp
  10. 5 19
      AI/VCAI/Pathfinding/AIPathfinder.cpp
  11. 52 42
      AI/VCAI/VCAI.cpp
  12. 8 5
      AI/VCAI/VCAI.h
  13. 1 3
      CCallback.cpp
  14. 1 1
      CMakeLists.txt
  15. 1 3
      Global.h
  16. 1 1
      client/ArtifactsUIController.cpp
  17. 0 1
      client/CMakeLists.txt
  18. 2 2
      client/CPlayerInterface.cpp
  19. 19 20
      client/CServerHandler.cpp
  20. 1 1
      client/CServerHandler.h
  21. 14 13
      client/Client.cpp
  22. 18 3
      client/Client.h
  23. 1 1
      client/HeroMovementController.cpp
  24. 1 1
      client/NetPacksClient.cpp
  25. 1 1
      client/ServerRunner.cpp
  26. 1 1
      client/ServerRunner.h
  27. 1 1
      client/adventureMap/CInGameConsole.cpp
  28. 1 1
      client/battle/BattleInterface.cpp
  29. 1 1
      client/battle/BattleInterface.h
  30. 6 6
      client/gui/FramerateManager.cpp
  31. 1 1
      client/gui/FramerateManager.h
  32. 1 1
      client/mapView/MapViewController.h
  33. 1 1
      client/windows/CTutorialWindow.cpp
  34. 1 1
      client/windows/GUIClasses.cpp
  35. 1 1
      client/windows/InfoWindows.cpp
  36. 5 6
      clientapp/EntryPoint.cpp
  37. 7 7
      docs/developers/Code_Structure.md
  38. 7 6
      lib/CConsoleHandler.cpp
  39. 3 1
      lib/CConsoleHandler.h
  40. 2 1
      lib/CMakeLists.txt
  41. 1 1
      lib/CRandomGenerator.cpp
  42. 8 35
      lib/CThreadHelper.cpp
  43. 12 73
      lib/CThreadHelper.h
  44. 24 0
      lib/ConditionalWait.h
  45. 0 4
      lib/logging/CLogger.cpp
  46. 4 0
      lib/logging/CLogger.h
  47. 1 1
      mapeditor/graphics.cpp
  48. 4 2
      server/CVCMIServer.cpp

+ 25 - 32
AI/BattleAI/BattleAI.cpp

@@ -143,49 +143,42 @@ void CBattleAI::activeStack(const BattleID & battleID, const CStack * stack )
 
 	auto start = std::chrono::high_resolution_clock::now();
 
-	try
+	if(stack->creatureId() == CreatureID::CATAPULT)
 	{
-		if(stack->creatureId() == CreatureID::CATAPULT)
-		{
-			cb->battleMakeUnitAction(battleID, useCatapult(battleID, stack));
-			return;
-		}
-		if(stack->hasBonusOfType(BonusType::SIEGE_WEAPON) && stack->hasBonusOfType(BonusType::HEALER))
-		{
-			cb->battleMakeUnitAction(battleID, useHealingTent(battleID, stack));
-			return;
-		}
+		cb->battleMakeUnitAction(battleID, useCatapult(battleID, stack));
+		return;
+	}
+	if(stack->hasBonusOfType(BonusType::SIEGE_WEAPON) && stack->hasBonusOfType(BonusType::HEALER))
+	{
+		cb->battleMakeUnitAction(battleID, useHealingTent(battleID, stack));
+		return;
+	}
 
 #if BATTLE_TRACE_LEVEL>=1
-		logAi->trace("Build evaluator and targets");
+	logAi->trace("Build evaluator and targets");
 #endif
 
-		BattleEvaluator evaluator(
-			env, cb, stack, playerID, battleID, side, 
-			getStrengthRatio(cb->getBattle(battleID), side),
-			getSimulationTurnsCount(env->game()->getStartInfo()));
+	BattleEvaluator evaluator(
+		env, cb, stack, playerID, battleID, side,
+		getStrengthRatio(cb->getBattle(battleID), side),
+		getSimulationTurnsCount(env->game()->getStartInfo()));
 
-		result = evaluator.selectStackAction(stack);
+	result = evaluator.selectStackAction(stack);
 
-		if(autobattlePreferences.enableSpellsUsage && evaluator.canCastSpell())
-		{
-			auto spelCasted = evaluator.attemptCastingSpell(stack);
-
-			if(spelCasted)
-				return;
-		}
-
-		logAi->trace("Spellcast attempt completed in %lld", timeElapsed(start));
+	if(autobattlePreferences.enableSpellsUsage && evaluator.canCastSpell())
+	{
+		auto spelCasted = evaluator.attemptCastingSpell(stack);
 
-		if(auto action = considerFleeingOrSurrendering(battleID))
-		{
-			cb->battleMakeUnitAction(battleID, *action);
+		if(spelCasted)
 			return;
-		}
 	}
-	catch(boost::thread_interrupted &)
+
+	logAi->trace("Spellcast attempt completed in %lld", timeElapsed(start));
+
+	if(auto action = considerFleeingOrSurrendering(battleID))
 	{
-		throw;
+		cb->battleMakeUnitAction(battleID, *action);
+		return;
 	}
 
 	if(result.actionType == EActionType::DEFEND)

+ 53 - 39
AI/Nullkiller/AIGateway.cpp

@@ -32,6 +32,8 @@
 #include "AIGateway.h"
 #include "Goals/Goals.h"
 
+static tbb::task_arena executeActionAsyncArena;
+
 namespace NKAI
 {
 
@@ -68,10 +70,10 @@ struct SetGlobalState
 AIGateway::AIGateway()
 {
 	LOG_TRACE(logAi);
-	makingTurn = nullptr;
 	destinationTeleport = ObjectInstanceID();
 	destinationTeleportPos = int3(-1);
 	nullkiller.reset(new Nullkiller());
+	asyncTasks = std::make_unique<tbb::task_group>();
 }
 
 AIGateway::~AIGateway()
@@ -163,7 +165,7 @@ void AIGateway::showTavernWindow(const CGObjectInstance * object, const CGHeroIn
 	NET_EVENT_HANDLER;
 
 	status.addQuery(queryID, "TavernWindow");
-	requestActionASAP([this, queryID](){ answerQuery(queryID, 0); });
+	executeActionAsync("showTavernWindow", [this, queryID](){ answerQuery(queryID, 0); });
 }
 
 void AIGateway::showThievesGuildWindow(const CGObjectInstance * obj)
@@ -216,10 +218,7 @@ void AIGateway::gameOver(PlayerColor player, const EVictoryLossCheckResult & vic
 			logAi->debug("AIGateway: Player %d (%s) lost. It's me. What a disappointment! :(", player, player.toString());
 		}
 
-		// some whitespace to flush stream
-		logAi->debug(std::string(200, ' '));
-
-		finish();
+		nullkiller->makingTurnInterrupption.interruptThread();
 	}
 }
 
@@ -299,7 +298,7 @@ void AIGateway::heroExchangeStarted(ObjectInstanceID hero1, ObjectInstanceID her
 
 	status.addQuery(query, boost::str(boost::format("Exchange between heroes %s (%d) and %s (%d)") % firstHero->getNameTranslated() % firstHero->tempOwner % secondHero->getNameTranslated() % secondHero->tempOwner));
 
-	requestActionASAP([this, firstHero, secondHero, query]()
+	executeActionAsync("heroExchangeStarted", [this, firstHero, secondHero, query]()
 	{
 		auto transferFrom2to1 = [this](const CGHeroInstance * h1, const CGHeroInstance * h2) -> void
 		{
@@ -338,7 +337,7 @@ void AIGateway::showRecruitmentDialog(const CGDwelling * dwelling, const CArmedI
 
 	status.addQuery(queryID, "RecruitmentDialog");
 
-	requestActionASAP([this, dwelling, dst, queryID](){
+	executeActionAsync("showRecruitmentDialog", [this, dwelling, dst, queryID](){
 		recruitCreatures(dwelling, dst);
 		answerQuery(queryID, 0);
 	});
@@ -457,7 +456,7 @@ void AIGateway::showUniversityWindow(const IMarket * market, const CGHeroInstanc
 	NET_EVENT_HANDLER;
 
 	status.addQuery(queryID, "UniversityWindow");
-	requestActionASAP([this, queryID](){ answerQuery(queryID, 0); });
+	executeActionAsync("showUniversityWindow", [this, queryID](){ answerQuery(queryID, 0); });
 }
 
 void AIGateway::heroManaPointsChanged(const CGHeroInstance * hero)
@@ -533,7 +532,7 @@ void AIGateway::showMarketWindow(const IMarket * market, const CGHeroInstance *
 	NET_EVENT_HANDLER;
 
 	status.addQuery(queryID, "MarketWindow");
-	requestActionASAP([this, queryID](){ answerQuery(queryID, 0); });
+	executeActionAsync("showMarketWindow", [this, queryID](){ answerQuery(queryID, 0); });
 }
 
 void AIGateway::showWorldViewEx(const std::vector<ObjectPosInfo> & objectPositions, bool showTerrain)
@@ -589,9 +588,17 @@ void AIGateway::yourTurn(QueryID queryID)
 	NET_EVENT_HANDLER;
 	nullkiller->invalidatePathfinderData();
 	status.addQuery(queryID, "YourTurn");
-	requestActionASAP([this, queryID](){ answerQuery(queryID, 0); });
+	executeActionAsync("yourTurn", [this, queryID](){ answerQuery(queryID, 0); });
 	status.startedTurn();
-	makingTurn = std::make_unique<boost::thread>(&AIGateway::makeTurn, this);
+
+	nullkiller->makingTurnInterrupption.reset();
+
+	asyncTasks->run([this]()
+	{
+		ScopedThreadName guard("NKAI::makingTurn");
+		makeTurn();
+	});
+	executeActionAsyncArena.enqueue([this](){asyncTasks->wait();});
 }
 
 void AIGateway::heroGotLevel(const CGHeroInstance * hero, PrimarySkill pskill, std::vector<SecondarySkill> & skills, QueryID queryID)
@@ -602,7 +609,7 @@ void AIGateway::heroGotLevel(const CGHeroInstance * hero, PrimarySkill pskill, s
 	status.addQuery(queryID, boost::str(boost::format("Hero %s got level %d") % hero->getNameTranslated() % hero->level));
 	HeroPtr hPtr = hero;
 
-	requestActionASAP([this, hPtr, skills, queryID]()
+	executeActionAsync("heroGotLevel", [this, hPtr, skills, queryID]()
 	{ 
 		int sel = 0;
 
@@ -624,7 +631,7 @@ void AIGateway::commanderGotLevel(const CCommanderInstance * commander, std::vec
 	LOG_TRACE_PARAMS(logAi, "queryID '%i'", queryID);
 	NET_EVENT_HANDLER;
 	status.addQuery(queryID, boost::str(boost::format("Commander %s of %s got level %d") % commander->name % commander->armyObj->nodeName() % (int)commander->level));
-	requestActionASAP([this, queryID](){ answerQuery(queryID, 0); });
+	executeActionAsync("commanderGotLevel", [this, queryID](){ answerQuery(queryID, 0); });
 }
 
 void AIGateway::showBlockingDialog(const std::string & text, const std::vector<Component> & components, QueryID askID, const int soundID, bool selection, bool cancel, bool safeToAutoaccept)
@@ -639,7 +646,7 @@ void AIGateway::showBlockingDialog(const std::string & text, const std::vector<C
 
 	if(!selection && cancel)
 	{
-		requestActionASAP([this, hero, target, askID]()
+		executeActionAsync("showBlockingDialog", [this, hero, target, askID]()
 		{
 			//yes&no -> always answer yes, we are a brave AI :)
 			bool answer = true;
@@ -687,7 +694,7 @@ void AIGateway::showBlockingDialog(const std::string & text, const std::vector<C
 		return;
 	}
 
-	requestActionASAP([this, selection, components, hero, askID]()
+	executeActionAsync("showBlockingDialog", [this, selection, components, hero, askID]()
 	{
 		int sel = 0;
 
@@ -749,7 +756,7 @@ void AIGateway::showTeleportDialog(const CGHeroInstance * hero, TeleportChannelI
 		}
 	}
 
-	requestActionASAP([this, askID, chosenExit]()
+	executeActionAsync("showTeleportDialog", [this, askID, chosenExit]()
 	{
 		answerQuery(askID, chosenExit);
 	});
@@ -766,7 +773,7 @@ void AIGateway::showGarrisonDialog(const CArmedInstance * up, const CGHeroInstan
 	status.addQuery(queryID, boost::str(boost::format("Garrison dialog with %s and %s") % s1 % s2));
 
 	//you can't request action from action-response thread
-	requestActionASAP([this, up, down, removableUnits, queryID]()
+	executeActionAsync("showGarrisonDialog", [this, up, down, removableUnits, queryID]()
 	{
 		if(removableUnits && up->tempOwner == down->tempOwner && nullkiller->settings->isGarrisonTroopsUsageAllowed() && !cb->getStartInfo()->isRestorationOfErathiaCampaign())
 		{
@@ -781,7 +788,7 @@ void AIGateway::showMapObjectSelectDialog(QueryID askID, const Component & icon,
 {
 	NET_EVENT_HANDLER;
 	status.addQuery(askID, "Map object select query");
-	requestActionASAP([this, askID](){ answerQuery(askID, selectedObject.getNum()); });
+	executeActionAsync("showMapObjectSelectDialog", [this, askID](){ answerQuery(askID, selectedObject.getNum()); });
 }
 
 bool AIGateway::makePossibleUpgrades(const CArmedInstance * obj)
@@ -831,13 +838,13 @@ bool AIGateway::makePossibleUpgrades(const CArmedInstance * obj)
 
 void AIGateway::makeTurn()
 {
+	setThreadName("AIGateway::makeTurn");
 	MAKING_TURN;
 
 	auto day = cb->getDate(Date::DAY);
 	logAi->info("Player %d (%s) starting turn, day %d", playerID, playerID.toString(), day);
 
 	std::shared_lock gsLock(CGameState::mutex);
-	setThreadName("AIGateway::makeTurn");
 
 	if(nullkiller->isOpenMap())
 	{
@@ -871,19 +878,26 @@ void AIGateway::makeTurn()
 		}
 #if NKAI_TRACE_LEVEL == 0
 	}
-	catch (boost::thread_interrupted & e)
+	catch (const TerminationRequestedException &)
 	{
-	(void)e;
 		logAi->debug("Making turn thread has been interrupted. We'll end without calling endTurn.");
 		return;
 	}
-	catch (std::exception & e)
+	catch (const std::exception & e)
 	{
 		logAi->debug("Making turn thread has caught an exception: %s", e.what());
 	}
 #endif
 
-	endTurn();
+	try
+	{
+		endTurn();
+	}
+	catch (const TerminationRequestedException &)
+	{
+		logAi->debug("Making turn thread has been interrupted. We'll end without calling endTurn.");
+		return;
+	}
 }
 
 void AIGateway::performObjectInteraction(const CGObjectInstance * obj, HeroPtr h)
@@ -1209,10 +1223,10 @@ void AIGateway::battleEnd(const BattleID & battleID, const BattleResult * br, Qu
 	{
 		status.addQuery(queryID, "Confirm battle query");
 
-		requestActionASAP([this, queryID]()
-			{
-				answerQuery(queryID, 0);
-			});
+		executeActionAsync("battleEnd", [this, queryID]()
+		{
+			answerQuery(queryID, 0);
+		});
 	}
 }
 
@@ -1581,28 +1595,28 @@ void AIGateway::buildArmyIn(const CGTownInstance * t)
 
 void AIGateway::finish()
 {
-	//we want to lock to avoid multiple threads from calling makingTurn->join() at same time
-	std::lock_guard<std::mutex> multipleCleanupGuard(turnInterruptionMutex);
+	nullkiller->makingTurnInterrupption.interruptThread();
 
-	if(makingTurn)
+	if (asyncTasks)
 	{
-		makingTurn->interrupt();
-		makingTurn->join();
-		makingTurn.reset();
+		asyncTasks->wait();
+		asyncTasks.reset();
 	}
 }
 
-void AIGateway::requestActionASAP(std::function<void()> whatToDo)
+void AIGateway::executeActionAsync(const std::string & description, const std::function<void()> & whatToDo)
 {
-	boost::thread newThread([this, whatToDo]()
+	if (!asyncTasks)
+		throw std::runtime_error("Attempt to execute task on shut down AI state!");
+
+	asyncTasks->run([this, description, whatToDo]()
 	{
-		setThreadName("AIGateway::requestActionASAP::whatToDo");
+		ScopedThreadName guard("NKAI::" + description);
 		SET_GLOBAL_STATE(this);
 		std::shared_lock gsLock(CGameState::mutex);
 		whatToDo();
 	});
-
-	newThread.detach();
+	executeActionAsyncArena.enqueue([this](){asyncTasks->wait();});
 }
 
 void AIGateway::lostHero(HeroPtr h)

+ 6 - 15
AI/Nullkiller/AIGateway.h

@@ -22,6 +22,9 @@
 #include "Pathfinding/AIPathfinder.h"
 #include "Engine/Nullkiller.h"
 
+#include <tbb/task_group.h>
+#include <tbb/task_arena.h>
+
 namespace NKAI
 {
 
@@ -67,23 +70,11 @@ public:
 	ObjectInstanceID destinationTeleport;
 	int3 destinationTeleportPos;
 	std::vector<ObjectInstanceID> teleportChannelProbingList; //list of teleport channel exits that not visible and need to be (re-)explored
-	//std::vector<const CGObjectInstance *> visitedThisWeek; //only OPWs
-
-	//std::set<HeroPtr> invalidPathHeroes; //FIXME, just a workaround
-	//std::map<HeroPtr, Goals::TSubgoal> lockedHeroes; //TODO: allow non-elementar objectives
-	//std::map<HeroPtr, std::set<const CGObjectInstance *>> reservedHeroesMap; //objects reserved by specific heroes
-	//std::set<HeroPtr> heroesUnableToExplore; //these heroes will not be polled for exploration in current state of game
-
-	//sets are faster to search, also do not contain duplicates
-	//std::set<const CGObjectInstance *> reservedObjs; //to be visited by specific hero
-	//std::map<HeroPtr, std::set<HeroPtr>> visitedHeroes; //visited this turn //FIXME: this is just bug workaround
 
 	AIStatus status;
 	std::string battlename;
 	std::shared_ptr<CCallback> myCb;
-	std::unique_ptr<boost::thread> makingTurn;
-private:
-	std::mutex turnInterruptionMutex;
+	std::unique_ptr<tbb::task_group> asyncTasks;
 
 public:
 	ObjectInstanceID selectedObject;
@@ -91,7 +82,7 @@ public:
 	std::unique_ptr<Nullkiller> nullkiller;
 
 	AIGateway();
-	virtual ~AIGateway();
+	~AIGateway();
 
 	//TODO: extract to appropriate goals
 	void tryRealize(Goals::DigAtTile & g);
@@ -190,7 +181,7 @@ public:
 	void requestSent(const CPackForServer * pack, int requestID) override;
 	void answerQuery(QueryID queryID, int selection);
 	//special function that can be called ONLY from game events handling thread and will send request ASAP
-	void requestActionASAP(std::function<void()> whatToDo);
+	void executeActionAsync(const std::string & description, const std::function<void()> & whatToDo);
 };
 
 }

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

@@ -62,7 +62,7 @@ void BuildAnalyzer::updateOtherBuildings(TownDevelopmentInfo & developmentInfo)
 		{BuildingID::MAGES_GUILD_3, BuildingID::MAGES_GUILD_5}
 	};
 
-	if(developmentInfo.existingDwellings.size() >= 2 && ai->cb->getDate(Date::DAY_OF_WEEK) > boost::date_time::Friday)
+	if(developmentInfo.existingDwellings.size() >= 2 && ai->cb->getDate(Date::DAY_OF_WEEK) > 4)
 	{
 		otherBuildings.push_back({BuildingID::HORDE_1});
 		otherBuildings.push_back({BuildingID::HORDE_2});

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

@@ -124,7 +124,7 @@ void DangerHitMapAnalyzer::updateHitMap()
 
 		ai->pathfinder->updatePaths(pair.second, ps);
 
-		boost::this_thread::interruption_point();
+		ai->makingTurnInterrupption.interruptionPoint();
 
 		pforeachTilePaths(mapSize, ai, [&](const int3 & pos, const std::vector<AIPath> & paths)
 		{

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

@@ -218,7 +218,7 @@ Goals::TTaskVec Nullkiller::buildPlan(TGoalVec & tasks, int priorityTier) const
 
 void Nullkiller::decompose(Goals::TGoalVec & result, Goals::TSubgoal behavior, int decompositionMaxDepth) const
 {
-	boost::this_thread::interruption_point();
+	makingTurnInterrupption.interruptionPoint();
 
 	logAi->debug("Checking behavior %s", behavior->toString());
 
@@ -226,7 +226,7 @@ void Nullkiller::decompose(Goals::TGoalVec & result, Goals::TSubgoal behavior, i
 	
 	decomposer->decompose(result, behavior, decompositionMaxDepth);
 
-	boost::this_thread::interruption_point();
+	makingTurnInterrupption.interruptionPoint();
 
 	logAi->debug(
 		"Behavior %s. Time taken %ld",
@@ -259,7 +259,7 @@ void Nullkiller::invalidatePathfinderData()
 
 void Nullkiller::updateAiState(int pass, bool fast)
 {
-	boost::this_thread::interruption_point();
+	makingTurnInterrupption.interruptionPoint();
 
 	std::unique_lock lockGuard(aiStateMutex);
 
@@ -281,7 +281,7 @@ void Nullkiller::updateAiState(int pass, bool fast)
 		dangerHitMap->updateHitMap();
 		dangerHitMap->calculateTileOwners();
 
-		boost::this_thread::interruption_point();
+		makingTurnInterrupption.interruptionPoint();
 
 		heroManager->update();
 		logAi->trace("Updating paths");
@@ -310,7 +310,7 @@ void Nullkiller::updateAiState(int pass, bool fast)
 			cfg.scoutTurnDistanceLimit =settings->getScoutHeroTurnDistanceLimit();
 		}
 
-		boost::this_thread::interruption_point();
+		makingTurnInterrupption.interruptionPoint();
 
 		pathfinder->updatePaths(activeHeroes, cfg);
 
@@ -322,7 +322,7 @@ void Nullkiller::updateAiState(int pass, bool fast)
 				scanDepth == ScanDepth::ALL_FULL ? 255 : 3);
 		}
 
-		boost::this_thread::interruption_point();
+		makingTurnInterrupption.interruptionPoint();
 
 		objectClusterizer->clusterize();
 
@@ -601,7 +601,7 @@ bool Nullkiller::executeTask(Goals::TTask task)
 	auto start = std::chrono::high_resolution_clock::now();
 	std::string taskDescr = task->toString();
 
-	boost::this_thread::interruption_point();
+	makingTurnInterrupption.interruptionPoint();
 	logAi->debug("Trying to realize %s (value %2.3f)", taskDescr, task->priority);
 
 	try

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

@@ -21,6 +21,8 @@
 #include "../Analyzers/ObjectClusterizer.h"
 #include "../Helpers/ArmyFormation.h"
 
+#include "../../../lib/ConditionalWait.h"
+
 VCMI_LIB_NAMESPACE_BEGIN
 
 class PathfinderCache;
@@ -106,6 +108,7 @@ public:
 	PlayerColor playerID;
 	std::shared_ptr<CCallback> cb;
 	std::mutex aiStateMutex;
+	mutable ThreadInterruption makingTurnInterrupption;
 
 	Nullkiller();
 	~Nullkiller();

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

@@ -117,11 +117,11 @@ void AIPathfinder::updatePaths(const std::map<const CGHeroInstance *, HeroRole>
 
 		do
 		{
-			boost::this_thread::interruption_point();
+			ai->makingTurnInterrupption.interruptionPoint();
 
 			while(storage->calculateHeroChain())
 			{
-				boost::this_thread::interruption_point();
+				ai->makingTurnInterrupption.interruptionPoint();
 
 				logAi->trace("Recalculate paths pass %d", pass++);
 				cb->calculatePaths(config);
@@ -130,11 +130,11 @@ void AIPathfinder::updatePaths(const std::map<const CGHeroInstance *, HeroRole>
 			logAi->trace("Select next actor");
 		} while(storage->selectNextActor());
 
-		boost::this_thread::interruption_point();
+		ai->makingTurnInterrupption.interruptionPoint();
 
 		if(storage->calculateHeroChainFinal())
 		{
-			boost::this_thread::interruption_point();
+			ai->makingTurnInterrupption.interruptionPoint();
 
 			logAi->trace("Recalculate paths pass final");
 			cb->calculatePaths(config);

+ 0 - 1
AI/StupidAI/StupidAI.cpp

@@ -124,7 +124,6 @@ void CStupidAI::yourTacticPhase(const BattleID & battleID, int distance)
 
 void CStupidAI::activeStack(const BattleID & battleID, const CStack * stack)
 {
-	//boost::this_thread::sleep_for(boost::chrono::seconds(2));
 	print("activeStack called for " + stack->nodeName());
 	ReachabilityInfo dists = cb->getBattle(battleID)->getReachability(stack);
 	std::vector<EnemyInfo> enemiesShootable;

+ 5 - 19
AI/VCAI/Pathfinding/AIPathfinder.cpp

@@ -13,6 +13,8 @@
 #include "../../../CCallback.h"
 #include "../../../lib/mapping/CMapDefines.h"
 
+#include <tbb/task_group.h>
+
 std::vector<std::shared_ptr<AINodeStorage>> AIPathfinder::storagePool;
 std::map<HeroPtr, std::shared_ptr<AINodeStorage>> AIPathfinder::storageMap;
 
@@ -60,7 +62,7 @@ void AIPathfinder::updatePaths(std::vector<HeroPtr> heroes)
 		cb->calculatePaths(config);
 	};
 
-	std::vector<CThreadHelper::Task> calculationTasks;
+	tbb::task_group calculationTasks;
 
 	for(HeroPtr hero : heroes)
 	{
@@ -81,26 +83,10 @@ void AIPathfinder::updatePaths(std::vector<HeroPtr> heroes)
 
 		auto config = std::make_shared<AIPathfinding::AIPathfinderConfig>(cb, ai, nodeStorage);
 
-		calculationTasks.push_back(std::bind(calculatePaths, hero.get(), config));
+		calculationTasks.run(std::bind(calculatePaths, hero.get(), config));
 	}
 
-	int threadsCount = std::min(
-		boost::thread::hardware_concurrency(),
-		(uint32_t)calculationTasks.size());
-
-	if(threadsCount <= 1)
-	{
-		for(auto task : calculationTasks)
-		{
-			task();
-		}
-	}
-	else
-	{
-		CThreadHelper helper(&calculationTasks, threadsCount);
-
-		helper.run();
-	}
+	calculationTasks.wait();
 }
 
 std::shared_ptr<const AINodeStorage> AIPathfinder::getStorage(const HeroPtr & hero) const

+ 52 - 42
AI/VCAI/VCAI.cpp

@@ -15,6 +15,7 @@
 #include "Goals/Goals.h"
 
 #include "../../lib/ArtifactUtils.h"
+#include "../../lib/CThreadHelper.h"
 #include "../../lib/UnlockGuard.h"
 #include "../../lib/StartInfo.h"
 #include "../../lib/mapObjects/MapObjects.h"
@@ -36,6 +37,8 @@
 
 #include "AIhelper.h"
 
+static tbb::task_arena executeActionAsyncArena;
+
 extern FuzzyHelper * fh;
 
 const double SAFE_ATTACK_CONSTANT = 1.5;
@@ -75,7 +78,7 @@ struct SetGlobalState
 VCAI::VCAI()
 {
 	LOG_TRACE(logAi);
-	makingTurn = nullptr;
+	asyncTasks = std::make_unique<tbb::task_group>();
 	destinationTeleport = ObjectInstanceID();
 	destinationTeleportPos = int3(-1);
 
@@ -174,7 +177,7 @@ void VCAI::showTavernWindow(const CGObjectInstance * object, const CGHeroInstanc
 	NET_EVENT_HANDLER;
 
 	status.addQuery(queryID, "TavernWindow");
-	requestActionASAP([this, queryID](){ answerQuery(queryID, 0); });
+	executeActionAsync("showTavernWindow", [this, queryID](){ answerQuery(queryID, 0); });
 }
 
 void VCAI::showThievesGuildWindow(const CGObjectInstance * obj)
@@ -223,7 +226,7 @@ void VCAI::gameOver(PlayerColor player, const EVictoryLossCheckResult & victoryL
 			logAi->debug("VCAI: Player %d (%s) lost. It's me. What a disappointment! :(", player, player.toString());
 		}
 
-		finish();
+		makingTurnInterrupption.interruptThread();
 	}
 }
 
@@ -311,7 +314,7 @@ void VCAI::heroExchangeStarted(ObjectInstanceID hero1, ObjectInstanceID hero2, Q
 
 	status.addQuery(query, boost::str(boost::format("Exchange between heroes %s (%d) and %s (%d)") % firstHero->getNameTranslated() % firstHero->tempOwner % secondHero->getNameTranslated() % secondHero->tempOwner));
 
-	requestActionASAP([this, firstHero, secondHero, query]()
+	executeActionAsync("heroExchangeStarted", [this, firstHero, secondHero, query]()
 	{
 		float goalpriority1 = 0;
 		float goalpriority2 = 0;
@@ -370,7 +373,7 @@ void VCAI::showRecruitmentDialog(const CGDwelling * dwelling, const CArmedInstan
 	NET_EVENT_HANDLER;
 
 	status.addQuery(queryID, "RecruitmentDialog");
-	requestActionASAP([this, dwelling, dst, queryID](){
+	executeActionAsync("showRecruitmentDialog", [this, dwelling, dst, queryID](){
 		recruitCreatures(dwelling, dst);
 		checkHeroArmy(dynamic_cast<const CGHeroInstance*>(dst));
 		answerQuery(queryID, 0);
@@ -469,7 +472,7 @@ void VCAI::showHillFortWindow(const CGObjectInstance * object, const CGHeroInsta
 	LOG_TRACE(logAi);
 	NET_EVENT_HANDLER;
 
-	requestActionASAP([=]()
+	executeActionAsync("showHillFortWindow", [visitor]()
 	{
 		makePossibleUpgrades(visitor);
 	});
@@ -532,7 +535,7 @@ void VCAI::showUniversityWindow(const IMarket * market, const CGHeroInstance * v
 	NET_EVENT_HANDLER;
 
 	status.addQuery(queryID, "UniversityWindow");
-	requestActionASAP([this, queryID](){ answerQuery(queryID, 0); });
+	executeActionAsync("showUniversityWindow", [this, queryID](){ answerQuery(queryID, 0); });
 }
 
 void VCAI::heroManaPointsChanged(const CGHeroInstance * hero)
@@ -600,7 +603,7 @@ void VCAI::showMarketWindow(const IMarket * market, const CGHeroInstance * visit
 	NET_EVENT_HANDLER;
 
 	status.addQuery(queryID, "MarketWindow");
-	requestActionASAP([this, queryID](){ answerQuery(queryID, 0); });
+	executeActionAsync("showMarketWindow", [this, queryID](){ answerQuery(queryID, 0); });
 }
 
 void VCAI::showWorldViewEx(const std::vector<ObjectPosInfo> & objectPositions, bool showTerrain)
@@ -646,9 +649,16 @@ void VCAI::yourTurn(QueryID queryID)
 	LOG_TRACE_PARAMS(logAi, "queryID '%i'", queryID);
 	NET_EVENT_HANDLER;
 	status.addQuery(queryID, "YourTurn");
-	requestActionASAP([this, queryID](){ answerQuery(queryID, 0); });
+	executeActionAsync("yourTurn", [this, queryID](){ answerQuery(queryID, 0); });
 	status.startedTurn();
-	makingTurn = std::make_unique<boost::thread>(&VCAI::makeTurn, this);
+
+	makingTurnInterrupption.reset();
+	asyncTasks->run([this]()
+	{
+		ScopedThreadName guard("VCAI::makingTurn");
+		makeTurn();
+	});
+	executeActionAsyncArena.enqueue([this](){asyncTasks->wait();});
 }
 
 void VCAI::heroGotLevel(const CGHeroInstance * hero, PrimarySkill pskill, std::vector<SecondarySkill> & skills, QueryID queryID)
@@ -656,7 +666,7 @@ void VCAI::heroGotLevel(const CGHeroInstance * hero, PrimarySkill pskill, std::v
 	LOG_TRACE_PARAMS(logAi, "queryID '%i'", queryID);
 	NET_EVENT_HANDLER;
 	status.addQuery(queryID, boost::str(boost::format("Hero %s got level %d") % hero->getNameTranslated() % hero->level));
-	requestActionASAP([this, queryID](){ answerQuery(queryID, 0); });
+	executeActionAsync("heroGotLevel", [this, queryID](){ answerQuery(queryID, 0); });
 }
 
 void VCAI::commanderGotLevel(const CCommanderInstance * commander, std::vector<ui32> skills, QueryID queryID)
@@ -664,7 +674,7 @@ void VCAI::commanderGotLevel(const CCommanderInstance * commander, std::vector<u
 	LOG_TRACE_PARAMS(logAi, "queryID '%i'", queryID);
 	NET_EVENT_HANDLER;
 	status.addQuery(queryID, boost::str(boost::format("Commander %s of %s got level %d") % commander->name % commander->armyObj->nodeName() % (int)commander->level));
-	requestActionASAP([this, queryID](){ answerQuery(queryID, 0); });
+	executeActionAsync("commanderGotLevel", [this, queryID](){ answerQuery(queryID, 0); });
 }
 
 void VCAI::showBlockingDialog(const std::string & text, const std::vector<Component> & components, QueryID askID, const int soundID, bool selection, bool cancel, bool safeToAutoaccept)
@@ -681,7 +691,7 @@ void VCAI::showBlockingDialog(const std::string & text, const std::vector<Compon
 	if(!selection && cancel) //yes&no -> always answer yes, we are a brave AI :)
 		sel = 1;
 
-	requestActionASAP([this, askID, sel]()
+	executeActionAsync("showBlockingDialog", [this, askID, sel]()
 	{
 		answerQuery(askID, sel);
 	});
@@ -726,7 +736,7 @@ void VCAI::showTeleportDialog(const CGHeroInstance * hero, TeleportChannelID cha
 		}
 	}
 
-	requestActionASAP([this, askID, chosenExit]()
+	executeActionAsync("showTeleportDialog", [this, askID, chosenExit]()
 	{
 		answerQuery(askID, chosenExit);
 	});
@@ -743,7 +753,7 @@ void VCAI::showGarrisonDialog(const CArmedInstance * up, const CGHeroInstance *
 	status.addQuery(queryID, boost::str(boost::format("Garrison dialog with %s and %s") % s1 % s2));
 
 	//you can't request action from action-response thread
-	requestActionASAP([this, down, up, removableUnits, queryID]()
+	executeActionAsync("showGarrisonDialog", [this, down, up, removableUnits, queryID]()
 	{
 		if(removableUnits && !cb->getStartInfo()->isRestorationOfErathiaCampaign())
 			pickBestCreatures(down, up);
@@ -756,7 +766,7 @@ void VCAI::showMapObjectSelectDialog(QueryID askID, const Component & icon, cons
 {
 	NET_EVENT_HANDLER;
 	status.addQuery(askID, "Map object select query");
-	requestActionASAP([this, askID](){ answerQuery(askID, selectedObject.getNum()); });
+	executeActionAsync("showMapObjectSelectDialog", [this, askID](){ answerQuery(askID, selectedObject.getNum()); });
 }
 
 void makePossibleUpgrades(const CArmedInstance * obj)
@@ -845,9 +855,8 @@ void VCAI::makeTurn()
 				logAi->info("Hero %s has %d MP left", h->getNameTranslated(), h->movementPointsRemaining());
 		}
 	}
-	catch (boost::thread_interrupted & e)
+	catch (const TerminationRequestedException &)
 	{
-	(void)e;
 		logAi->debug("Making turn thread has been interrupted. We'll end without calling endTurn.");
 		return;
 	}
@@ -998,17 +1007,16 @@ void VCAI::mainLoop()
 
 			try
 			{
-				boost::this_thread::interruption_point();
+				makingTurnInterrupption.interruptionPoint();
 				goalToRealize->accept(this); //visitor pattern
-				boost::this_thread::interruption_point();
+				makingTurnInterrupption.interruptionPoint();
 			}
-			catch (boost::thread_interrupted & e)
+			catch (const TerminationRequestedException &)
 			{
-				(void)e;
 				logAi->debug("Player %d: Making turn thread received an interruption!", playerID);
 				throw; //rethrow, we want to truly end this thread
 			}
-			catch (goalFulfilledException & e)
+			catch (const goalFulfilledException & e)
 			{
 				//the sub-goal was completed successfully
 				completeGoal(e.goal);
@@ -1018,7 +1026,7 @@ void VCAI::mainLoop()
 				// remove abstract visit tile if we completed the elementar one
 				vstd::erase_if_present(goalsToAdd, goalToRealize);
 			}
-			catch (std::exception & e)
+			catch (const std::exception & e)
 			{
 				logAi->debug("Failed to realize subgoal of type %s, I will stop.", goalToRealize->name());
 				logAi->debug("The error message was: %s", e.what());
@@ -2364,24 +2372,23 @@ void VCAI::striveToGoal(Goals::TSubgoal basicGoal)
 
 		try
 		{
-			boost::this_thread::interruption_point();
+			makingTurnInterrupption.interruptionPoint();
 			elementarGoal->accept(this); //visitor pattern
-			boost::this_thread::interruption_point();
+			makingTurnInterrupption.interruptionPoint();
 		}
-		catch (boost::thread_interrupted & e)
+		catch (const TerminationRequestedException &)
 		{
-			(void)e;
 			logAi->debug("Player %d: Making turn thread received an interruption!", playerID);
 			throw; //rethrow, we want to truly end this thread
 		}
-		catch (goalFulfilledException & e)
+		catch (const goalFulfilledException & e)
 		{
 			//the sub-goal was completed successfully
 			completeGoal(e.goal);
 			//local goal was also completed
 			completeGoal(elementarGoal);
 		}
-		catch (std::exception & e)
+		catch (const std::exception & e)
 		{
 			logAi->debug("Failed to realize subgoal of type %s, I will stop.", elementarGoal->name());
 			logAi->debug("The error message was: %s", e.what());
@@ -2409,7 +2416,7 @@ Goals::TSubgoal VCAI::decomposeGoal(Goals::TSubgoal ultimateGoal)
 	int maxGoals = searchDepth; //preventing deadlock for mutually dependent goals
 	while (maxGoals)
 	{
-		boost::this_thread::interruption_point();
+		makingTurnInterrupption.interruptionPoint();
 
 		goal = goal->whatToDoToAchieve(); //may throw if decomposition fails
 		--maxGoals;
@@ -2490,27 +2497,30 @@ void VCAI::recruitHero(const CGTownInstance * t, bool throwing)
 
 void VCAI::finish()
 {
-	//we want to lock to avoid multiple threads from calling makingTurn->join() at same time
-	std::lock_guard<std::mutex> multipleCleanupGuard(turnInterruptionMutex);
-	if(makingTurn)
+	makingTurnInterrupption.interruptThread();
+
+	if (asyncTasks)
 	{
-		makingTurn->interrupt();
-		makingTurn->join();
-		makingTurn.reset();
+		asyncTasks->wait();
+		asyncTasks.reset();
 	}
 }
 
-void VCAI::requestActionASAP(std::function<void()> whatToDo)
+void VCAI::executeActionAsync(const std::string & description, const std::function<void()> & whatToDo)
 {
-	boost::thread newThread([this, whatToDo]()
+
+
+	if (!asyncTasks)
+		throw std::runtime_error("Attempt to execute task on shut down AI state!");
+
+	asyncTasks->run([this, description, whatToDo]()
 	{
-		setThreadName("VCAI::requestActionASAP::whatToDo");
+		ScopedThreadName guard("VCAI::" + description);
 		SET_GLOBAL_STATE(this);
 		std::shared_lock gsLock(CGameState::mutex);
 		whatToDo();
 	});
-
-	newThread.detach();
+	executeActionAsyncArena.enqueue([this](){asyncTasks->wait();});
 }
 
 void VCAI::lostHero(HeroPtr h)

+ 8 - 5
AI/VCAI/VCAI.h

@@ -14,7 +14,7 @@
 #include "../../lib/AI_Base.h"
 #include "../../CCallback.h"
 
-#include "../../lib/CThreadHelper.h"
+#include "../../lib/ConditionalWait.h"
 
 #include "../../lib/GameConstants.h"
 #include "../../lib/GameLibrary.h"
@@ -23,6 +23,9 @@
 #include "../../lib/spells/CSpellHandler.h"
 #include "Pathfinding/AIPathfinder.h"
 
+#include <tbb/task_group.h>
+#include <tbb/task_arena.h>
+
 VCMI_LIB_NAMESPACE_BEGIN
 
 struct QuestInfo;
@@ -105,9 +108,9 @@ public:
 
 	std::shared_ptr<CCallback> myCb;
 
-	std::unique_ptr<boost::thread> makingTurn;
-private:
-	std::mutex turnInterruptionMutex;
+	std::unique_ptr<tbb::task_group> asyncTasks;
+	ThreadInterruption makingTurnInterrupption;
+
 public:
 	ObjectInstanceID selectedObject;
 
@@ -262,7 +265,7 @@ public:
 	void requestSent(const CPackForServer * pack, int requestID) override;
 	void answerQuery(QueryID queryID, int selection);
 	//special function that can be called ONLY from game events handling thread and will send request ASAP
-	void requestActionASAP(std::function<void()> whatToDo);
+	void executeActionAsync(const std::string & description, const std::function<void()> & whatToDo);
 };
 
 class cannotFulfillGoalException : public std::exception

+ 1 - 3
CCallback.cpp

@@ -260,10 +260,8 @@ int CBattleCallback::sendRequest(const CPackForServer & request)
 	{
 		logGlobal->trace("We'll wait till request %d is answered.\n", requestID);
 		auto gsUnlocker = vstd::makeUnlockSharedGuardIf(CGameState::mutex, unlockGsWhenWaiting);
-		CClient::waitingRequest.waitWhileContains(requestID);
+		cl->waitingRequest.waitWhileContains(requestID);
 	}
-
-	boost::this_thread::interruption_point();
 	return requestID;
 }
 

+ 1 - 1
CMakeLists.txt

@@ -462,7 +462,7 @@ endif()
 #        Finding packages                  #
 ############################################
 
-set(BOOST_COMPONENTS date_time filesystem locale program_options system thread)
+set(BOOST_COMPONENTS date_time filesystem locale program_options system)
 if(ENABLE_INNOEXTRACT)
 	list(APPEND BOOST_COMPONENTS iostreams)
 endif()

+ 1 - 3
Global.h

@@ -138,6 +138,7 @@ static_assert(sizeof(bool) == 1, "Bool needs to be 1 byte in size.");
 #include <shared_mutex>
 #include <sstream>
 #include <string>
+#include <thread>
 #include <unordered_map>
 #include <unordered_set>
 #include <utility>
@@ -172,8 +173,6 @@ static_assert(sizeof(bool) == 1, "Bool needs to be 1 byte in size.");
 #include <boost/current_function.hpp>
 #include <boost/container/small_vector.hpp>
 #include <boost/container/static_vector.hpp>
-#include <boost/date_time/posix_time/posix_time_types.hpp>
-#include <boost/date_time/posix_time/time_formatters.hpp>
 #include <boost/filesystem.hpp>
 #include <boost/filesystem/fstream.hpp>
 #include <boost/filesystem/path.hpp>
@@ -188,7 +187,6 @@ static_assert(sizeof(bool) == 1, "Bool needs to be 1 byte in size.");
 #include <boost/range/adaptor/filtered.hpp>
 #include <boost/range/adaptor/reversed.hpp>
 #include <boost/range/algorithm.hpp>
-#include <boost/thread/thread_only.hpp>
 
 #ifndef M_PI
 #  define M_PI 3.14159265358979323846

+ 1 - 1
client/ArtifactsUIController.cpp

@@ -58,7 +58,7 @@ bool ArtifactsUIController::askToAssemble(const CGHeroInstance * hero, const Art
 	auto assemblyPossibilities = ArtifactUtils::assemblyPossibilities(hero, art->getTypeId(), onlyEquipped);
 	if(!assemblyPossibilities.empty())
 	{
-		auto askThread = new boost::thread([this, hero, art, slot, assemblyPossibilities, checkIgnored]() -> void
+		auto askThread = new std::thread([this, hero, art, slot, assemblyPossibilities, checkIgnored]() -> void
 			{
 				std::scoped_lock askLock(askAssembleArtifactMutex);
 				for(const auto combinedArt : assemblyPossibilities)

+ 0 - 1
client/CMakeLists.txt

@@ -405,7 +405,6 @@ set(vcmiclientcommon_HEADERS
 	Client.h
 	ClientCommandManager.h
 	ClientNetPackVisitors.h
-	ConditionalWait.h
 	HeroMovementController.h
 	GameChatHandler.h
 	LobbyClientNetPackVisitors.h

+ 2 - 2
client/CPlayerInterface.cpp

@@ -1437,7 +1437,7 @@ void CPlayerInterface::centerView (int3 pos, int focusTime)
 		{
 			IgnoreEvents ignore(*this);
 			auto unlockInterface = vstd::makeUnlockGuard(ENGINE->interfaceMutex);
-			boost::this_thread::sleep_for(boost::chrono::milliseconds(focusTime));
+			std::this_thread::sleep_for(std::chrono::milliseconds(focusTime));
 		}
 	}
 	ENGINE->cursor().show();
@@ -1789,7 +1789,7 @@ void CPlayerInterface::waitForAllDialogs()
 	while(!dialogs.empty())
 	{
 		auto unlockInterface = vstd::makeUnlockGuard(ENGINE->interfaceMutex);
-		boost::this_thread::sleep_for(boost::chrono::milliseconds(5));
+		std::this_thread::sleep_for(std::chrono::milliseconds(5));
 	}
 	waitWhileDialog();
 }

+ 19 - 20
client/CServerHandler.cpp

@@ -33,7 +33,7 @@
 
 #include "../lib/CConfigHandler.h"
 #include "../lib/texts/CGeneralTextHandler.h"
-#include "ConditionalWait.h"
+#include "../lib/ConditionalWait.h"
 #include "../lib/CThreadHelper.h"
 #include "../lib/StartInfo.h"
 #include "../lib/TurnTimerInfo.h"
@@ -64,20 +64,14 @@ CServerHandler::~CServerHandler()
 	if (serverRunner)
 		serverRunner->shutdown();
 	networkHandler->stop();
-	try
-	{
-		if (serverRunner)
-			serverRunner->wait();
-		serverRunner.reset();
-		{
-			auto unlockInterface = vstd::makeUnlockGuard(ENGINE->interfaceMutex);
-			threadNetwork.join();
-		}
-	}
-	catch (const std::runtime_error & e)
+
+	if (serverRunner)
+		serverRunner->wait();
+	serverRunner.reset();
+	if (threadNetwork.joinable())
 	{
-		logGlobal->error("Failed to shut down network thread! Reason: %s", e.what());
-		assert(0);
+		auto unlockInterface = vstd::makeUnlockGuard(ENGINE->interfaceMutex);
+		threadNetwork.join();
 	}
 }
 
@@ -86,6 +80,8 @@ void CServerHandler::endNetwork()
 	if (client)
 		client->endNetwork();
 	networkHandler->stop();
+
+	if (threadNetwork.joinable())
 	{
 		auto unlockInterface = vstd::makeUnlockGuard(ENGINE->interfaceMutex);
 		threadNetwork.join();
@@ -110,8 +106,8 @@ CServerHandler::CServerHandler()
 
 void CServerHandler::threadRunNetwork()
 {
-	logGlobal->info("Starting network thread");
 	setThreadName("runNetwork");
+	logGlobal->info("Starting network thread");
 	try {
 		networkHandler->run();
 	}
@@ -657,6 +653,8 @@ void CServerHandler::showHighScoresAndEndGameplay(PlayerColor player, bool victo
 
 void CServerHandler::endGameplay()
 {
+	client->finishGameplay();
+
 	// Game is ending
 	// Tell the network thread to reach a stable state
 	sendClientDisconnecting();
@@ -675,6 +673,7 @@ void CServerHandler::endGameplay()
 
 void CServerHandler::restartGameplay()
 {
+	client->finishGameplay();
 	client->endGame();
 	client.reset();
 
@@ -792,20 +791,20 @@ void CServerHandler::debugStartTest(std::string filename, bool save)
 	else
 		startLocalServerAndConnect(false);
 
-	boost::this_thread::sleep_for(boost::chrono::milliseconds(100));
+	std::this_thread::sleep_for(std::chrono::milliseconds(100));
 
 	while(!settings["session"]["headless"].Bool() && !ENGINE->windows().topWindow<CLobbyScreen>())
-		boost::this_thread::sleep_for(boost::chrono::milliseconds(50));
+		std::this_thread::sleep_for(std::chrono::milliseconds(50));
 
 	while(!mi || mapInfo->fileURI != mi->fileURI)
 	{
 		setMapInfo(mapInfo);
-		boost::this_thread::sleep_for(boost::chrono::milliseconds(50));
+		std::this_thread::sleep_for(std::chrono::milliseconds(50));
 	}
 	// "Click" on color to remove us from it
 	setPlayer(myFirstColor());
 	while(myFirstColor() != PlayerColor::CANNOT_DETERMINE)
-		boost::this_thread::sleep_for(boost::chrono::milliseconds(50));
+		std::this_thread::sleep_for(std::chrono::milliseconds(50));
 
 	while(true)
 	{
@@ -818,7 +817,7 @@ void CServerHandler::debugStartTest(std::string filename, bool save)
 		{
 
 		}
-		boost::this_thread::sleep_for(boost::chrono::milliseconds(50));
+		std::this_thread::sleep_for(std::chrono::milliseconds(50));
 	}
 }
 

+ 1 - 1
client/CServerHandler.h

@@ -103,7 +103,7 @@ class CServerHandler final : public IServerAPI, public LobbyInfo, public INetwor
 	std::shared_ptr<CMapInfo> mapToStart;
 	std::vector<std::string> localPlayerNames;
 
-	boost::thread threadNetwork;
+	std::thread threadNetwork;
 
 	std::atomic<EClientState> state;
 

+ 14 - 13
client/Client.cpp

@@ -46,8 +46,6 @@
 #include "lib/CAndroidVMHelper.h"
 #endif
 
-ThreadSafeVector<int> CClient::waitingRequest;
-
 CPlayerEnvironment::CPlayerEnvironment(PlayerColor player_, CClient * cl_, std::shared_ptr<CCallback> mainCallback_)
 	: player(player_),
 	cl(cl_),
@@ -181,25 +179,28 @@ void CClient::endNetwork()
 	}
 }
 
-void CClient::endGame()
+void CClient::finishGameplay()
 {
-#if SCRIPTING_ENABLED
-	clientScripts.reset();
-#endif
+	waitingRequest.requestTermination();
 
 	//suggest interfaces to finish their stuff (AI should interrupt any bg working threads)
 	for(auto & i : playerint)
 		i.second->finish();
+}
 
-	{
-		logNetwork->info("Ending current game!");
-		removeGUI();
+void CClient::endGame()
+{
+#if SCRIPTING_ENABLED
+	clientScripts.reset();
+#endif
 
-		GAME->setMapInstance(nullptr);
-		vstd::clear_pointer(gs);
+	logNetwork->info("Ending current game!");
+	removeGUI();
 
-		logNetwork->info("Deleted mapHandler and gameState.");
-	}
+	GAME->setMapInstance(nullptr);
+	vstd::clear_pointer(gs);
+
+	logNetwork->info("Deleted mapHandler and gameState.");
 
 	CPlayerInterface::battleInt.reset();
 	playerint.clear();

+ 18 - 3
client/Client.h

@@ -13,6 +13,8 @@
 #include <vcmi/Environment.h>
 
 #include "../lib/IGameCallback.h"
+#include "../lib/ConditionalWait.h"
+
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -43,8 +45,6 @@ class CCallback;
 class CClient;
 class CBaseForCLApply;
 
-namespace boost { class thread; }
-
 template<typename T>
 class ThreadSafeVector
 {
@@ -52,8 +52,15 @@ class ThreadSafeVector
 	std::vector<T> items;
 	std::mutex mx;
 	std::condition_variable cond;
+	std::atomic<bool> isTerminating = false;
 
 public:
+	void requestTermination()
+	{
+		isTerminating = true;
+		clear();
+	}
+
 	void clear()
 	{
 		TLock lock(mx);
@@ -63,6 +70,8 @@ public:
 
 	void pushBack(const T & item)
 	{
+		assert(!isTerminating);
+
 		TLock lock(mx);
 		items.push_back(item);
 		cond.notify_all();
@@ -73,10 +82,15 @@ public:
 		TLock lock(mx);
 		while(vstd::contains(items, item))
 			cond.wait(lock);
+
+		if (isTerminating)
+			throw TerminationRequestedException();
 	}
 
 	bool tryRemovingElement(const T & item) //returns false if element was not present
 	{
+		assert(!isTerminating);
+
 		TLock lock(mx);
 		auto itr = vstd::find(items, item);
 		if(itr == items.end()) //not in container
@@ -130,6 +144,7 @@ public:
 
 	void save(const std::string & fname);
 	void endNetwork();
+	void finishGameplay();
 	void endGame();
 
 	void initMapHandler();
@@ -140,7 +155,7 @@ public:
 	void installNewPlayerInterface(std::shared_ptr<CGameInterface> gameInterface, PlayerColor color, bool battlecb = false);
 	void installNewBattleInterface(std::shared_ptr<CBattleGameInterface> battleInterface, PlayerColor color, bool needCallback = true);
 
-	static ThreadSafeVector<int> waitingRequest; //FIXME: make this normal field (need to join all threads before client destruction)
+	ThreadSafeVector<int> waitingRequest;
 
 	void handlePack(CPackForClient & pack); //applies the given pack and deletes it
 	int sendRequest(const CPackForServer & request, PlayerColor player); //returns ID given to that request

+ 1 - 1
client/HeroMovementController.cpp

@@ -22,7 +22,7 @@
 
 #include "../CCallback.h"
 
-#include "ConditionalWait.h"
+#include "../lib/ConditionalWait.h"
 #include "../lib/CConfigHandler.h"
 #include "../lib/CRandomGenerator.h"
 #include "../lib/pathfinder/CGPathNode.h"

+ 1 - 1
client/NetPacksClient.cpp

@@ -882,7 +882,7 @@ void ApplyClientNetPackVisitor::visitEndAction(EndAction & pack)
 void ApplyClientNetPackVisitor::visitPackageApplied(PackageApplied & pack)
 {
 	callInterfaceIfPresent(cl, pack.player, &IGameEventsReceiver::requestRealized, &pack);
-	if(!CClient::waitingRequest.tryRemovingElement(pack.requestID))
+	if(!cl.waitingRequest.tryRemovingElement(pack.requestID))
 		logNetwork->warn("Surprising server message! PackageApplied for unknown requestID!");
 }
 

+ 1 - 1
client/ServerRunner.cpp

@@ -49,7 +49,7 @@ void ServerThreadRunner::start(bool listenForConnections, bool connectToLobby, s
 
 	std::promise<uint16_t> promise;
 
-	threadRunLocalServer = boost::thread([this, connectToLobby, listenForConnections, &promise]{
+	threadRunLocalServer = std::thread([this, connectToLobby, listenForConnections, &promise]{
 		setThreadName("runServer");
 		uint16_t port = server->prepare(connectToLobby, listenForConnections);
 		promise.set_value(port);

+ 1 - 1
client/ServerRunner.h

@@ -36,7 +36,7 @@ public:
 class ServerThreadRunner final : public IServerRunner, boost::noncopyable
 {
 	std::unique_ptr<CVCMIServer> server;
-	boost::thread threadRunLocalServer;
+	std::thread threadRunLocalServer;
 	uint16_t serverPort = 0;
 	bool lobbyMode = false;
 

+ 1 - 1
client/adventureMap/CInGameConsole.cpp

@@ -300,7 +300,7 @@ void CInGameConsole::endEnteringText(bool processEnteredText)
 				commandController.processCommand(txt.substr(1), true);
 			};
 
-			boost::thread clientCommandThread(threadFunction);
+			std::thread clientCommandThread(threadFunction);
 			clientCommandThread.detach();
 		}
 		else

+ 1 - 1
client/battle/BattleInterface.cpp

@@ -751,7 +751,7 @@ void BattleInterface::requestAutofightingAIToTakeAction()
 			// FIXME: unsafe
 			// Run task in separate thread to avoid UI lock while AI is making turn (which might take some time)
 			// HOWEVER this thread won't atttempt to lock game state, potentially leading to races
-			boost::thread aiThread([localBattleID = battleID, localCurInt = curInt, activeStack]()
+			std::thread aiThread([localBattleID = battleID, localCurInt = curInt, activeStack]()
 			{
 				setThreadName("autofightingAI");
 				localCurInt->autofightingAI->activeStack(localBattleID, activeStack);

+ 1 - 1
client/battle/BattleInterface.h

@@ -13,7 +13,7 @@
 #include "../lib/battle/BattleHex.h"
 #include "../gui/CIntObject.h"
 #include "../../lib/spells/CSpellHandler.h" //CSpell::TAnimation
-#include "../ConditionalWait.h"
+#include "../../lib/ConditionalWait.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 

+ 6 - 6
client/gui/FramerateManager.cpp

@@ -15,7 +15,7 @@
 #include <SDL_video.h>
 
 FramerateManager::FramerateManager(int targetFrameRate)
-	: targetFrameTime(Duration(boost::chrono::seconds(1)) / targetFrameRate)
+	: targetFrameTime(Duration(std::chrono::seconds(1)) / targetFrameRate)
 	, lastFrameIndex(0)
 	, lastFrameTimes({})
 	, lastTimePoint(Clock::now())
@@ -31,15 +31,15 @@ void FramerateManager::framerateDelay()
 	if(!vsyncEnabled && timeSpentBusy < targetFrameTime)
 	{
 		// if FPS is higher than it should be, then wait some time
-		boost::this_thread::sleep_for(targetFrameTime - timeSpentBusy);
+		std::this_thread::sleep_for(targetFrameTime - timeSpentBusy);
 	}
 
 	// compute actual timeElapsed taking into account actual sleep interval
 	// limit it to 100 ms to avoid breaking animation in case of huge lag (e.g. triggered breakpoint)
 	TimePoint currentTicks = Clock::now();
 	Duration timeElapsed = currentTicks - lastTimePoint;
-	if(timeElapsed > boost::chrono::milliseconds(100))
-		timeElapsed = boost::chrono::milliseconds(100);
+	if(timeElapsed > std::chrono::milliseconds(100))
+		timeElapsed = std::chrono::milliseconds(100);
 
 	lastTimePoint = currentTicks;
 	lastFrameIndex = (lastFrameIndex + 1) % lastFrameTimes.size();
@@ -48,7 +48,7 @@ void FramerateManager::framerateDelay()
 
 ui32 FramerateManager::getElapsedMilliseconds() const
 {
-	return lastFrameTimes[lastFrameIndex] / boost::chrono::milliseconds(1);
+	return lastFrameTimes[lastFrameIndex] / std::chrono::milliseconds(1);
 }
 
 ui32 FramerateManager::getFramerate() const
@@ -59,5 +59,5 @@ ui32 FramerateManager::getFramerate() const
 	if(actualFrameTime == actualFrameTime.zero())
 		return 0;
 
-	return std::round(boost::chrono::duration<double>(1) / actualFrameTime);
+	return std::round(std::chrono::duration<double>(1) / actualFrameTime);
 };

+ 1 - 1
client/gui/FramerateManager.h

@@ -12,7 +12,7 @@
 /// Framerate manager controls current game frame rate by constantly trying to reach targeted frame rate
 class FramerateManager
 {
-	using Clock = boost::chrono::high_resolution_clock;
+	using Clock = std::chrono::steady_clock;
 	using TimePoint = Clock::time_point;
 	using Duration = Clock::duration;
 

+ 1 - 1
client/mapView/MapViewController.h

@@ -10,7 +10,7 @@
 #pragma once
 
 #include "IMapRendererObserver.h"
-#include "../ConditionalWait.h"
+#include "../../lib/ConditionalWait.h"
 #include "../../lib/Point.h"
 
 VCMI_LIB_NAMESPACE_BEGIN

+ 1 - 1
client/windows/CTutorialWindow.cpp

@@ -12,7 +12,7 @@
 
 #include "../eventsSDL/InputHandler.h"
 #include "../../lib/CConfigHandler.h"
-#include "../ConditionalWait.h"
+#include "../../lib/ConditionalWait.h"
 #include "../../lib/texts/CGeneralTextHandler.h"
 #include "../../lib/GameLibrary.h"
 #include "../CPlayerInterface.h"

+ 1 - 1
client/windows/GUIClasses.cpp

@@ -57,7 +57,7 @@
 #include "../lib/texts/CGeneralTextHandler.h"
 #include "../lib/texts/TextOperations.h"
 #include "../lib/IGameSettings.h"
-#include "ConditionalWait.h"
+#include "../lib/ConditionalWait.h"
 #include "../lib/CConfigHandler.h"
 #include "../lib/CRandomGenerator.h"
 #include "../lib/CSkillHandler.h"

+ 1 - 1
client/windows/InfoWindows.cpp

@@ -37,7 +37,7 @@
 #include "../../lib/mapObjects/CGTownInstance.h"
 #include "../../lib/mapObjects/CQuest.h"
 #include "../../lib/mapObjects/MiscObjects.h"
-#include "../ConditionalWait.h"
+#include "../../lib/ConditionalWait.h"
 
 CSelWindow::CSelWindow( const std::string & Text, PlayerColor player, int charperline, const std::vector<std::shared_ptr<CSelectableComponent>> & comps, const std::vector<std::pair<AnimationPath, CFunctionList<void()>>> & Buttons, QueryID askID)
 {

+ 5 - 6
clientapp/EntryPoint.cpp

@@ -318,7 +318,7 @@ int main(int argc, char * argv[])
 	
 #ifndef VCMI_NO_THREADED_LOAD
 	//we can properly play intro only in the main thread, so we have to move loading to the separate thread
-	boost::thread loading([]()
+	std::thread loading([]()
 	{
 		setThreadName("initialize");
 		init();
@@ -368,13 +368,13 @@ int main(int argc, char * argv[])
 	{
 		session["testmap"].String() = vm["testmap"].as<std::string>();
 		session["onlyai"].Bool() = true;
-		boost::thread(&CServerHandler::debugStartTest, &GAME->server(), session["testmap"].String(), false);
+		GAME->server().debugStartTest(session["testmap"].String(), false);
 	}
 	else if(vm.count("testsave"))
 	{
 		session["testsave"].String() = vm["testsave"].as<std::string>();
 		session["onlyai"].Bool() = true;
-		boost::thread(&CServerHandler::debugStartTest, &GAME->server(), session["testsave"].String(), true);
+		GAME->server().debugStartTest(session["testsave"].String(), true);
 	}
 	else if (!settings["session"]["headless"].Bool())
 	{
@@ -397,9 +397,9 @@ int main(int argc, char * argv[])
 	else
 	{
 		while(!headlessQuit)
-			boost::this_thread::sleep_for(boost::chrono::milliseconds(200));
+			std::this_thread::sleep_for(std::chrono::milliseconds(200));
 
-		boost::this_thread::sleep_for(boost::chrono::milliseconds(500));
+		std::this_thread::sleep_for(std::chrono::milliseconds(500));
 
 		quitApplication();
 	}
@@ -448,7 +448,6 @@ static void mainLoop()
 	}
 
 	GAME.reset();
-	GAME->mainmenu().reset();
 
 	if(!settings["session"]["headless"].Bool())
 	{

+ 7 - 7
docs/developers/Code_Structure.md

@@ -101,13 +101,15 @@ If you're creating new project part, place `VCMI_LIB_USING_NAMESPACE` in its `St
 
 ## Artificial Intelligence
 
-### StupidAI
+### Combat AI
 
-Stupid AI is recent and used battle AI.
+- Battle AI is recent and used combat AI.
+- Stupid AI is old and deprecated version of combat AI
 
 ### Adventure AI
 
-VCAI module is currently developed agent-based system driven by goals and heroes.
+- NullkillerAI module is currently developed agent-based system driven by goals and heroes.
+- VCAI is old and deprecated version of adventure map AI
 
 ## Threading Model
 
@@ -131,13 +133,11 @@ Here is list of threads including their name that can be seen in logging or in d
 - NullkillerAI parallelizes a lot of its tasks using TBB methods, mostly parallel_for
 - Random map generator actively uses thread pool provided by TBB
 - Client performs image upscaling in background thread to avoid visible freezes
+- AI main task (`NKAI::makeTurn`). This TBB task is created whenever AI stars a new turn, and ends when AI ends its turn. Majority of AI event processing is done in this thread, however some actions are either offloaded entirely as tbb task, or parallelized using methods like parallel_for.
+- AI helper tasks (`NKAI::<various>`). Adventure AI creates such tasks whenever it receives event that requires processing without locking network thread that initiated the call.
 
 ## Short-living threads
 
-- AI thread (`AIGateway::makeTurn`). Adventure AI creates its thread whenever it stars a new turn, and terminates it when turn ends. Majority of AI event processing is done in this thread, however some actions are either offloaded entirely as tbb task, or parallelized using methods like parallel_for.
-
-- AI helper thread (`AIGateway::doActionASAP`). Adventure AI creates such thread whenever it receives event that requires processing without locking network thread that initiated the call.
-
 - Autocombat initiation thread (`autofightingAI`). Combat AI usually runs on network thread, as reaction on unit taking turn netpack event. However initial activation of AI when player presses hotkey or button is done in input processing (`MainGUI`) thread. To avoid freeze when AI selects its first action, this action is done on a temporary thread
 
 - Initializition thread (`initialize`). On game start, to avoid delay in game loading, most of game library initialization is done in separate thread while main thread is playing intro movies.

+ 7 - 6
lib/CConsoleHandler.cpp

@@ -243,7 +243,7 @@ int CConsoleHandler::run()
 
 	while ( std::cin.good() )
 	{
-#ifndef VCMI_WINDOWS
+#ifndef _MSC_VER
 		//check if we have some unreaded symbols
 		if (std::cin.rdbuf()->in_avail())
 		{
@@ -252,9 +252,10 @@ int CConsoleHandler::run()
 					cb(buffer, false);
 		}
 		else
-			boost::this_thread::sleep_for(boost::chrono::milliseconds(100));
+			std::this_thread::sleep_for(std::chrono::milliseconds(100));
 
-		boost::this_thread::interruption_point();
+		if (shutdownPending)
+			return -1;
 #else
 		std::getline(std::cin, buffer);
 		if ( cb )
@@ -305,8 +306,8 @@ void CConsoleHandler::end()
 {
 	if (thread.joinable())
 	{
-#ifndef VCMI_WINDOWS
-		thread.interrupt();
+#ifndef _MSC_VER
+		shutdownPending = true;
 #else
 		TerminateThread(thread.native_handle(),0);
 #endif
@@ -316,7 +317,7 @@ void CConsoleHandler::end()
 
 void CConsoleHandler::start()
 {
-	thread = boost::thread(std::bind(&CConsoleHandler::run, this));
+	thread = std::thread(std::bind(&CConsoleHandler::run, this));
 }
 
 VCMI_LIB_NAMESPACE_END

+ 3 - 1
lib/CConsoleHandler.h

@@ -94,9 +94,11 @@ private:
 	//function to be called when message is received - string: message, bool: whether call was made from in-game console
 	std::function<void(const std::string &, bool)> cb;
 
+	std::atomic<bool> shutdownPending = false;
+
 	std::mutex smx;
 
-	boost::thread thread;
+	std::thread thread;
 };
 
 VCMI_LIB_NAMESPACE_END

+ 2 - 1
lib/CMakeLists.txt

@@ -702,6 +702,7 @@ set(lib_MAIN_HEADERS
 	CCreatureSet.h
 	CGameInfoCallback.h
 	CGameInterface.h
+	ConditionalWait.h
 	ConstTransitivePtr.h
 	Color.h
 	CPlayerState.h
@@ -757,7 +758,7 @@ endif()
 set_target_properties(vcmi PROPERTIES COMPILE_DEFINITIONS "VCMI_DLL=1")
 target_link_libraries(vcmi PUBLIC
 	minizip::minizip ZLIB::ZLIB TBB::tbb
-	${SYSTEM_LIBS} Boost::boost Boost::thread Boost::filesystem Boost::program_options Boost::locale Boost::date_time
+	${SYSTEM_LIBS} Boost::boost Boost::filesystem Boost::program_options Boost::locale Boost::date_time
 )
 
 if(ENABLE_STATIC_LIBS AND ENABLE_CLIENT)

+ 1 - 1
lib/CRandomGenerator.cpp

@@ -35,7 +35,7 @@ void CRandomGenerator::resetSeed()
 {
 	logRng->trace("CRandomGenerator::resetSeed");
 	boost::hash<std::string> stringHash;
-	auto threadIdHash = stringHash(boost::lexical_cast<std::string>(boost::this_thread::get_id()));
+	auto threadIdHash = stringHash(boost::lexical_cast<std::string>(std::this_thread::get_id()));
 	setSeed(static_cast<int>(threadIdHash * std::time(nullptr)));
 }
 

+ 8 - 35
lib/CThreadHelper.cpp

@@ -19,41 +19,9 @@
 	#include <sys/prctl.h>
 #endif
 
-VCMI_LIB_NAMESPACE_BEGIN
+#include <tbb/task_arena.h>
 
-CThreadHelper::CThreadHelper(std::vector<std::function<void()>> * Tasks, int Threads):
-	currentTask(0),
-	amount(static_cast<int>(Tasks->size())),
-	tasks(Tasks),
-	threads(Threads)
-{
-}
-void CThreadHelper::run()
-{
-	std::vector<boost::thread> group;
-	for(int i=0;i<threads;i++)
-		group.emplace_back(std::bind(&CThreadHelper::processTasks,this));
-
-	for (auto & thread : group)
-		thread.join();
-
-	//thread group deletes threads, do not free manually
-}
-void CThreadHelper::processTasks()
-{
-	while(true)
-	{
-		int pom;
-		{
-			std::unique_lock<std::mutex> lock(rtinm);
-			if((pom = currentTask) >= amount)
-				break;
-			else
-				++currentTask;
-		}
-		(*tasks)[pom]();
-	}
-}
+VCMI_LIB_NAMESPACE_BEGIN
 
 static thread_local std::string threadNameForLogging;
 
@@ -62,7 +30,12 @@ std::string getThreadName()
 	if (!threadNameForLogging.empty())
 		return threadNameForLogging;
 
-	return boost::lexical_cast<std::string>(boost::this_thread::get_id());
+	int tbbIndex = tbb::this_task_arena::current_thread_index();
+
+	if (tbbIndex < 0)
+		return boost::lexical_cast<std::string>(std::this_thread::get_id());
+	else
+		return "TBB worker " + boost::lexical_cast<std::string>(tbbIndex);
 }
 
 void setThreadNameLoggingOnly(const std::string &name)

+ 12 - 73
lib/CThreadHelper.h

@@ -11,88 +11,27 @@
 
 VCMI_LIB_NAMESPACE_BEGIN
 
+/// Sets thread name that will be used for both logs and debugger (if supported)
+/// WARNING: on Unix-like systems this method should not be used for main thread since it will also change name of the process
+void DLL_LINKAGE setThreadName(const std::string &name);
 
-///DEPRECATED
-/// Can assign CPU work to other threads/cores
-class DLL_LINKAGE CThreadHelper
-{
-public:
-	using Task = std::function<void()>;
-	CThreadHelper(std::vector<std::function<void()> > *Tasks, int Threads);
-	void run();
-private:
-	std::mutex rtinm;
-	int currentTask;
-	int amount;
-	int threads;
-	std::vector<Task> *tasks;
-
+/// Sets thread name for use in logging only
+void DLL_LINKAGE setThreadNameLoggingOnly(const std::string &name);
 
-	void processTasks();
-};
+/// Returns human-readable thread name that was set before, or string form of system-provided thread ID if no human-readable name was set
+std::string DLL_LINKAGE getThreadName();
 
-template<typename Payload>
-class ThreadPool
+class DLL_LINKAGE ScopedThreadName : boost::noncopyable
 {
 public:
-	using Task = std::function<void(std::shared_ptr<Payload>)>;
-	using Tasks = std::vector<Task>;
-
-	ThreadPool(Tasks * tasks_, std::vector<std::shared_ptr<Payload>> context_)
-		: currentTask(0),
-		amount(tasks_->size()),
-		threads(context_.size()),
-		tasks(tasks_),
-		context(context_)
-	{}
-
-	void run()
+	ScopedThreadName(const std::string & name)
 	{
-		std::vector<boost::thread> group;
-		for(size_t i=0; i<threads; i++)
-		{
-			std::shared_ptr<Payload> payload = context.at(i);
-			group.emplace_back(std::bind(&ThreadPool::processTasks, this, payload));
-		}
-
-		for (auto & thread : group)
-			thread.join();
-
-		//thread group deletes threads, do not free manually
+		setThreadName(name);
 	}
-private:
-	std::mutex rtinm;
-	size_t currentTask;
-	size_t amount;
-	size_t threads;
-	Tasks * tasks;
-	std::vector<std::shared_ptr<Payload>> context;
-
-	void processTasks(std::shared_ptr<Payload> payload)
+	~ScopedThreadName()
 	{
-		while(true)
-		{
-			size_t pom;
-			{
-				std::unique_lock<std::mutex> lock(rtinm);
-				if((pom = currentTask) >= amount)
-					break;
-				else
-					++currentTask;
-			}
-			(*tasks)[pom](payload);
-		}
+		setThreadName({});
 	}
 };
 
-/// Sets thread name that will be used for both logs and debugger (if supported)
-/// WARNING: on Unix-like systems this method should not be used for main thread since it will also change name of the process
-void DLL_LINKAGE setThreadName(const std::string &name);
-
-/// Sets thread name for use in logging only
-void DLL_LINKAGE setThreadNameLoggingOnly(const std::string &name);
-
-/// Returns human-readable thread name that was set before, or string form of system-provided thread ID if no human-readable name was set
-std::string DLL_LINKAGE getThreadName();
-
 VCMI_LIB_NAMESPACE_END

+ 24 - 0
client/ConditionalWait.h → lib/ConditionalWait.h

@@ -24,6 +24,30 @@ public:
 	}
 };
 
+class ThreadInterruption
+{
+	std::atomic<bool> interruptionRequested = false;
+
+public:
+	void interruptionPoint()
+	{
+		bool result = interruptionRequested.exchange(false);
+
+		if (result)
+			throw TerminationRequestedException();
+	}
+
+	void interruptThread()
+	{
+		interruptionRequested.store(true);
+	}
+
+	void reset()
+	{
+		interruptionRequested.store(false);
+	}
+};
+
 class ConditionalWait
 {
 	bool isBusyValue = false;

+ 0 - 4
lib/logging/CLogger.cpp

@@ -112,10 +112,6 @@ CLogger * CLogger::getLogger(const CLoggerDomain & domain)
 			logger->setLevel(ELogLevel::TRACE);
 		}
 		CLogManager::get().addLogger(logger);
-		if (logGlobal != nullptr)
-		{
-			logGlobal->debug("Created logger %s", domain.getName());
-		}
 	}
 	return logger;
 }

+ 4 - 0
lib/logging/CLogger.h

@@ -10,6 +10,10 @@
 #pragma once
 
 
+#include <boost/date_time/posix_time/posix_time_types.hpp>
+#include <boost/date_time/posix_time/time_formatters.hpp>
+
+
 VCMI_LIB_NAMESPACE_BEGIN
 
 class CLogger;

+ 1 - 1
mapeditor/graphics.cpp

@@ -102,7 +102,7 @@ Graphics::Graphics()
 	tasks += std::bind(&Graphics::loadErmuToPicture,this);
 	tasks += std::bind(&Graphics::initializeImageLists,this);
 	
-	CThreadHelper th(&tasks,std::max((ui32)1,boost::thread::hardware_concurrency()));
+	CThreadHelper th(&tasks,std::max((ui32)1,std::thread::hardware_concurrency()));
 	th.run();
 #else
 	loadPaletteAndColors();

+ 4 - 2
server/CVCMIServer.cpp

@@ -16,6 +16,7 @@
 #include "processors/PlayerMessageProcessor.h"
 
 #include "../lib/CPlayerState.h"
+#include "../lib/CThreadHelper.h"
 #include "../lib/campaign/CampaignState.h"
 #include "../lib/entities/hero/CHeroHandler.h"
 #include "../lib/entities/hero/CHeroClass.h"
@@ -231,8 +232,9 @@ bool CVCMIServer::prepareToStartGame()
 	if (lobbyProcessor)
 		lobbyProcessor->sendGameStarted();
 
-	auto progressTrackingThread = boost::thread([this, &progressTracking]()
+	auto progressTrackingThread = std::thread([this, &progressTracking]()
 	{
+		setThreadName("progressTrackingThread");
 		auto currentProgress = std::numeric_limits<Load::Type>::max();
 
 		while(!progressTracking.finished())
@@ -245,7 +247,7 @@ bool CVCMIServer::prepareToStartGame()
 				loadProgress.progress = currentProgress;
 				announcePack(loadProgress);
 			}
-			boost::this_thread::sleep(boost::posix_time::milliseconds(50));
+			std::this_thread::sleep_for(std::chrono::milliseconds(50));
 		}
 	});