소스 검색

AI now uses std::thread, added custom thread interruption logic

Ivan Savenko 7 달 전
부모
커밋
948abfb04c

+ 11 - 12
AI/Nullkiller/AIGateway.cpp

@@ -216,10 +216,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();
 	}
 }
 
@@ -591,7 +588,12 @@ void AIGateway::yourTurn(QueryID queryID)
 	status.addQuery(queryID, "YourTurn");
 	requestActionASAP([this, queryID](){ answerQuery(queryID, 0); });
 	status.startedTurn();
-	makingTurn = std::make_unique<boost::thread>(&AIGateway::makeTurn, this);
+
+	if (makingTurn && makingTurn->joinable())
+		makingTurn->join(); // leftover from previous turn
+
+	nullkiller->makingTurnInterrupption.reset();
+	makingTurn = std::make_unique<std::thread>(&AIGateway::makeTurn, this);
 }
 
 void AIGateway::heroGotLevel(const CGHeroInstance * hero, PrimarySkill pskill, std::vector<SecondarySkill> & skills, QueryID queryID)
@@ -871,13 +873,12 @@ void AIGateway::makeTurn()
 		}
 #if NKAI_TRACE_LEVEL == 0
 	}
-	catch (boost::thread_interrupted & e)
+	catch (const TerminationRequestedException & e)
 	{
-	(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());
 	}
@@ -1581,12 +1582,10 @@ 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)
 	{
-		makingTurn->interrupt();
 		makingTurn->join();
 		makingTurn.reset();
 	}
@@ -1594,7 +1593,7 @@ void AIGateway::finish()
 
 void AIGateway::requestActionASAP(std::function<void()> whatToDo)
 {
-	boost::thread newThread([this, whatToDo]()
+	std::thread newThread([this, whatToDo]()
 	{
 		setThreadName("AIGateway::requestActionASAP::whatToDo");
 		SET_GLOBAL_STATE(this);

+ 1 - 15
AI/Nullkiller/AIGateway.h

@@ -22,8 +22,6 @@
 #include "Pathfinding/AIPathfinder.h"
 #include "Engine/Nullkiller.h"
 
-#include <boost/thread/thread_only.hpp>
-
 namespace NKAI
 {
 
@@ -69,23 +67,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<std::thread> makingTurn;
 
 public:
 	ObjectInstanceID selectedObject;

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

@@ -15,8 +15,6 @@
 #include "../../../lib/CRandomGenerator.h"
 #include "../../../lib/logging/VisualLogger.h"
 
-#include <boost/thread/thread_only.hpp>
-
 namespace NKAI
 {
 
@@ -126,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 - 6
AI/Nullkiller/Pathfinding/AIPathfinder.cpp

@@ -14,8 +14,6 @@
 #include "../../../lib/mapping/CMap.h"
 #include "../Engine/Nullkiller.h"
 
-#include <boost/thread/thread_only.hpp>
-
 namespace NKAI
 {
 
@@ -119,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);
@@ -132,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);

+ 23 - 21
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"
@@ -223,7 +224,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();
 	}
 }
 
@@ -648,7 +649,12 @@ void VCAI::yourTurn(QueryID queryID)
 	status.addQuery(queryID, "YourTurn");
 	requestActionASAP([this, queryID](){ answerQuery(queryID, 0); });
 	status.startedTurn();
-	makingTurn = std::make_unique<boost::thread>(&VCAI::makeTurn, this);
+
+	if (makingTurn && makingTurn->joinable())
+		makingTurn->join(); // leftover from previous turn
+
+	makingTurnInterrupption.reset();
+	makingTurn = std::make_unique<std::thread>(&VCAI::makeTurn, this);
 }
 
 void VCAI::heroGotLevel(const CGHeroInstance * hero, PrimarySkill pskill, std::vector<SecondarySkill> & skills, QueryID queryID)
@@ -845,9 +851,8 @@ void VCAI::makeTurn()
 				logAi->info("Hero %s has %d MP left", h->getNameTranslated(), h->movementPointsRemaining());
 		}
 	}
-	catch (boost::thread_interrupted & e)
+	catch (const TerminationRequestedException & e)
 	{
-	(void)e;
 		logAi->debug("Making turn thread has been interrupted. We'll end without calling endTurn.");
 		return;
 	}
@@ -998,17 +1003,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 & e)
 			{
-				(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 +1022,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 +2368,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 & e)
 		{
-			(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 +2412,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,11 +2493,10 @@ 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);
+	makingTurnInterrupption.interruptThread();
+
 	if(makingTurn)
 	{
-		makingTurn->interrupt();
 		makingTurn->join();
 		makingTurn.reset();
 	}
@@ -2502,7 +2504,7 @@ void VCAI::finish()
 
 void VCAI::requestActionASAP(std::function<void()> whatToDo)
 {
-	boost::thread newThread([this, whatToDo]()
+	std::thread newThread([this, whatToDo]()
 	{
 		setThreadName("VCAI::requestActionASAP::whatToDo");
 		SET_GLOBAL_STATE(this);

+ 4 - 6
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,8 +23,6 @@
 #include "../../lib/spells/CSpellHandler.h"
 #include "Pathfinding/AIPathfinder.h"
 
-#include <boost/thread/thread_only.hpp>
-
 VCMI_LIB_NAMESPACE_BEGIN
 
 struct QuestInfo;
@@ -107,9 +105,9 @@ public:
 
 	std::shared_ptr<CCallback> myCb;
 
-	std::unique_ptr<boost::thread> makingTurn;
-private:
-	std::mutex turnInterruptionMutex;
+	std::unique_ptr<std::thread> makingTurn;
+	ThreadInterruption makingTurnInterrupption;
+
 public:
 	ObjectInstanceID selectedObject;
 

+ 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

+ 1 - 1
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"

+ 0 - 2
client/Client.h

@@ -43,8 +43,6 @@ class CCallback;
 class CClient;
 class CBaseForCLApply;
 
-namespace boost { class thread; }
-
 template<typename T>
 class ThreadSafeVector
 {

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

+ 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)
 {

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

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