1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-11-06 09:09:40 +02:00

Merge pull request #5496 from IvanSavenko/std_thread

Replace boost::thread with std::thread
This commit is contained in:
Ivan Savenko
2025-03-08 16:03:55 +02:00
committed by GitHub
48 changed files with 323 additions and 373 deletions

View File

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

View File

@@ -32,6 +32,8 @@
#include "AIGateway.h" #include "AIGateway.h"
#include "Goals/Goals.h" #include "Goals/Goals.h"
static tbb::task_arena executeActionAsyncArena;
namespace NKAI namespace NKAI
{ {
@@ -68,10 +70,10 @@ struct SetGlobalState
AIGateway::AIGateway() AIGateway::AIGateway()
{ {
LOG_TRACE(logAi); LOG_TRACE(logAi);
makingTurn = nullptr;
destinationTeleport = ObjectInstanceID(); destinationTeleport = ObjectInstanceID();
destinationTeleportPos = int3(-1); destinationTeleportPos = int3(-1);
nullkiller.reset(new Nullkiller()); nullkiller.reset(new Nullkiller());
asyncTasks = std::make_unique<tbb::task_group>();
} }
AIGateway::~AIGateway() AIGateway::~AIGateway()
@@ -163,7 +165,7 @@ void AIGateway::showTavernWindow(const CGObjectInstance * object, const CGHeroIn
NET_EVENT_HANDLER; NET_EVENT_HANDLER;
status.addQuery(queryID, "TavernWindow"); status.addQuery(queryID, "TavernWindow");
requestActionASAP([this, queryID](){ answerQuery(queryID, 0); }); executeActionAsync("showTavernWindow", [this, queryID](){ answerQuery(queryID, 0); });
} }
void AIGateway::showThievesGuildWindow(const CGObjectInstance * obj) 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()); logAi->debug("AIGateway: Player %d (%s) lost. It's me. What a disappointment! :(", player, player.toString());
} }
// some whitespace to flush stream nullkiller->makingTurnInterrupption.interruptThread();
logAi->debug(std::string(200, ' '));
finish();
} }
} }
@@ -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)); 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 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"); status.addQuery(queryID, "RecruitmentDialog");
requestActionASAP([this, dwelling, dst, queryID](){ executeActionAsync("showRecruitmentDialog", [this, dwelling, dst, queryID](){
recruitCreatures(dwelling, dst); recruitCreatures(dwelling, dst);
answerQuery(queryID, 0); answerQuery(queryID, 0);
}); });
@@ -457,7 +456,7 @@ void AIGateway::showUniversityWindow(const IMarket * market, const CGHeroInstanc
NET_EVENT_HANDLER; NET_EVENT_HANDLER;
status.addQuery(queryID, "UniversityWindow"); status.addQuery(queryID, "UniversityWindow");
requestActionASAP([this, queryID](){ answerQuery(queryID, 0); }); executeActionAsync("showUniversityWindow", [this, queryID](){ answerQuery(queryID, 0); });
} }
void AIGateway::heroManaPointsChanged(const CGHeroInstance * hero) void AIGateway::heroManaPointsChanged(const CGHeroInstance * hero)
@@ -533,7 +532,7 @@ void AIGateway::showMarketWindow(const IMarket * market, const CGHeroInstance *
NET_EVENT_HANDLER; NET_EVENT_HANDLER;
status.addQuery(queryID, "MarketWindow"); 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) void AIGateway::showWorldViewEx(const std::vector<ObjectPosInfo> & objectPositions, bool showTerrain)
@@ -589,9 +588,17 @@ void AIGateway::yourTurn(QueryID queryID)
NET_EVENT_HANDLER; NET_EVENT_HANDLER;
nullkiller->invalidatePathfinderData(); nullkiller->invalidatePathfinderData();
status.addQuery(queryID, "YourTurn"); status.addQuery(queryID, "YourTurn");
requestActionASAP([this, queryID](){ answerQuery(queryID, 0); }); executeActionAsync("yourTurn", [this, queryID](){ answerQuery(queryID, 0); });
status.startedTurn(); 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) 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)); status.addQuery(queryID, boost::str(boost::format("Hero %s got level %d") % hero->getNameTranslated() % hero->level));
HeroPtr hPtr = hero; HeroPtr hPtr = hero;
requestActionASAP([this, hPtr, skills, queryID]() executeActionAsync("heroGotLevel", [this, hPtr, skills, queryID]()
{ {
int sel = 0; int sel = 0;
@@ -624,7 +631,7 @@ void AIGateway::commanderGotLevel(const CCommanderInstance * commander, std::vec
LOG_TRACE_PARAMS(logAi, "queryID '%i'", queryID); LOG_TRACE_PARAMS(logAi, "queryID '%i'", queryID);
NET_EVENT_HANDLER; 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)); 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) 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) if(!selection && cancel)
{ {
requestActionASAP([this, hero, target, askID]() executeActionAsync("showBlockingDialog", [this, hero, target, askID]()
{ {
//yes&no -> always answer yes, we are a brave AI :) //yes&no -> always answer yes, we are a brave AI :)
bool answer = true; bool answer = true;
@@ -687,7 +694,7 @@ void AIGateway::showBlockingDialog(const std::string & text, const std::vector<C
return; return;
} }
requestActionASAP([this, selection, components, hero, askID]() executeActionAsync("showBlockingDialog", [this, selection, components, hero, askID]()
{ {
int sel = 0; 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); 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)); status.addQuery(queryID, boost::str(boost::format("Garrison dialog with %s and %s") % s1 % s2));
//you can't request action from action-response thread //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()) 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; NET_EVENT_HANDLER;
status.addQuery(askID, "Map object select query"); 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) bool AIGateway::makePossibleUpgrades(const CArmedInstance * obj)
@@ -831,13 +838,13 @@ bool AIGateway::makePossibleUpgrades(const CArmedInstance * obj)
void AIGateway::makeTurn() void AIGateway::makeTurn()
{ {
setThreadName("AIGateway::makeTurn");
MAKING_TURN; MAKING_TURN;
auto day = cb->getDate(Date::DAY); auto day = cb->getDate(Date::DAY);
logAi->info("Player %d (%s) starting turn, day %d", playerID, playerID.toString(), day); logAi->info("Player %d (%s) starting turn, day %d", playerID, playerID.toString(), day);
std::shared_lock gsLock(CGameState::mutex); std::shared_lock gsLock(CGameState::mutex);
setThreadName("AIGateway::makeTurn");
if(nullkiller->isOpenMap()) if(nullkiller->isOpenMap())
{ {
@@ -871,19 +878,26 @@ void AIGateway::makeTurn()
} }
#if NKAI_TRACE_LEVEL == 0 #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."); logAi->debug("Making turn thread has been interrupted. We'll end without calling endTurn.");
return; return;
} }
catch (std::exception & e) catch (const std::exception & e)
{ {
logAi->debug("Making turn thread has caught an exception: %s", e.what()); logAi->debug("Making turn thread has caught an exception: %s", e.what());
} }
#endif #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) 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"); status.addQuery(queryID, "Confirm battle query");
requestActionASAP([this, queryID]() executeActionAsync("battleEnd", [this, queryID]()
{ {
answerQuery(queryID, 0); answerQuery(queryID, 0);
}); });
} }
} }
@@ -1581,28 +1595,28 @@ void AIGateway::buildArmyIn(const CGTownInstance * t)
void AIGateway::finish() void AIGateway::finish()
{ {
//we want to lock to avoid multiple threads from calling makingTurn->join() at same time nullkiller->makingTurnInterrupption.interruptThread();
std::lock_guard<std::mutex> multipleCleanupGuard(turnInterruptionMutex);
if(makingTurn) if (asyncTasks)
{ {
makingTurn->interrupt(); asyncTasks->wait();
makingTurn->join(); asyncTasks.reset();
makingTurn.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); SET_GLOBAL_STATE(this);
std::shared_lock gsLock(CGameState::mutex); std::shared_lock gsLock(CGameState::mutex);
whatToDo(); whatToDo();
}); });
executeActionAsyncArena.enqueue([this](){asyncTasks->wait();});
newThread.detach();
} }
void AIGateway::lostHero(HeroPtr h) void AIGateway::lostHero(HeroPtr h)

View File

@@ -22,6 +22,9 @@
#include "Pathfinding/AIPathfinder.h" #include "Pathfinding/AIPathfinder.h"
#include "Engine/Nullkiller.h" #include "Engine/Nullkiller.h"
#include <tbb/task_group.h>
#include <tbb/task_arena.h>
namespace NKAI namespace NKAI
{ {
@@ -67,23 +70,11 @@ public:
ObjectInstanceID destinationTeleport; ObjectInstanceID destinationTeleport;
int3 destinationTeleportPos; int3 destinationTeleportPos;
std::vector<ObjectInstanceID> teleportChannelProbingList; //list of teleport channel exits that not visible and need to be (re-)explored 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; AIStatus status;
std::string battlename; std::string battlename;
std::shared_ptr<CCallback> myCb; std::shared_ptr<CCallback> myCb;
std::unique_ptr<boost::thread> makingTurn; std::unique_ptr<tbb::task_group> asyncTasks;
private:
std::mutex turnInterruptionMutex;
public: public:
ObjectInstanceID selectedObject; ObjectInstanceID selectedObject;
@@ -91,7 +82,7 @@ public:
std::unique_ptr<Nullkiller> nullkiller; std::unique_ptr<Nullkiller> nullkiller;
AIGateway(); AIGateway();
virtual ~AIGateway(); ~AIGateway();
//TODO: extract to appropriate goals //TODO: extract to appropriate goals
void tryRealize(Goals::DigAtTile & g); void tryRealize(Goals::DigAtTile & g);
@@ -190,7 +181,7 @@ public:
void requestSent(const CPackForServer * pack, int requestID) override; void requestSent(const CPackForServer * pack, int requestID) override;
void answerQuery(QueryID queryID, int selection); void answerQuery(QueryID queryID, int selection);
//special function that can be called ONLY from game events handling thread and will send request ASAP //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);
}; };
} }

View File

@@ -62,7 +62,7 @@ void BuildAnalyzer::updateOtherBuildings(TownDevelopmentInfo & developmentInfo)
{BuildingID::MAGES_GUILD_3, BuildingID::MAGES_GUILD_5} {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_1});
otherBuildings.push_back({BuildingID::HORDE_2}); otherBuildings.push_back({BuildingID::HORDE_2});

View File

@@ -124,7 +124,7 @@ void DangerHitMapAnalyzer::updateHitMap()
ai->pathfinder->updatePaths(pair.second, ps); 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) pforeachTilePaths(mapSize, ai, [&](const int3 & pos, const std::vector<AIPath> & paths)
{ {

View File

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

View File

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

View File

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

View File

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

View File

@@ -13,6 +13,8 @@
#include "../../../CCallback.h" #include "../../../CCallback.h"
#include "../../../lib/mapping/CMapDefines.h" #include "../../../lib/mapping/CMapDefines.h"
#include <tbb/task_group.h>
std::vector<std::shared_ptr<AINodeStorage>> AIPathfinder::storagePool; std::vector<std::shared_ptr<AINodeStorage>> AIPathfinder::storagePool;
std::map<HeroPtr, std::shared_ptr<AINodeStorage>> AIPathfinder::storageMap; std::map<HeroPtr, std::shared_ptr<AINodeStorage>> AIPathfinder::storageMap;
@@ -60,7 +62,7 @@ void AIPathfinder::updatePaths(std::vector<HeroPtr> heroes)
cb->calculatePaths(config); cb->calculatePaths(config);
}; };
std::vector<CThreadHelper::Task> calculationTasks; tbb::task_group calculationTasks;
for(HeroPtr hero : heroes) 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); 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( calculationTasks.wait();
boost::thread::hardware_concurrency(),
(uint32_t)calculationTasks.size());
if(threadsCount <= 1)
{
for(auto task : calculationTasks)
{
task();
}
}
else
{
CThreadHelper helper(&calculationTasks, threadsCount);
helper.run();
}
} }
std::shared_ptr<const AINodeStorage> AIPathfinder::getStorage(const HeroPtr & hero) const std::shared_ptr<const AINodeStorage> AIPathfinder::getStorage(const HeroPtr & hero) const

View File

@@ -15,6 +15,7 @@
#include "Goals/Goals.h" #include "Goals/Goals.h"
#include "../../lib/ArtifactUtils.h" #include "../../lib/ArtifactUtils.h"
#include "../../lib/CThreadHelper.h"
#include "../../lib/UnlockGuard.h" #include "../../lib/UnlockGuard.h"
#include "../../lib/StartInfo.h" #include "../../lib/StartInfo.h"
#include "../../lib/mapObjects/MapObjects.h" #include "../../lib/mapObjects/MapObjects.h"
@@ -36,6 +37,8 @@
#include "AIhelper.h" #include "AIhelper.h"
static tbb::task_arena executeActionAsyncArena;
extern FuzzyHelper * fh; extern FuzzyHelper * fh;
const double SAFE_ATTACK_CONSTANT = 1.5; const double SAFE_ATTACK_CONSTANT = 1.5;
@@ -75,7 +78,7 @@ struct SetGlobalState
VCAI::VCAI() VCAI::VCAI()
{ {
LOG_TRACE(logAi); LOG_TRACE(logAi);
makingTurn = nullptr; asyncTasks = std::make_unique<tbb::task_group>();
destinationTeleport = ObjectInstanceID(); destinationTeleport = ObjectInstanceID();
destinationTeleportPos = int3(-1); destinationTeleportPos = int3(-1);
@@ -174,7 +177,7 @@ void VCAI::showTavernWindow(const CGObjectInstance * object, const CGHeroInstanc
NET_EVENT_HANDLER; NET_EVENT_HANDLER;
status.addQuery(queryID, "TavernWindow"); status.addQuery(queryID, "TavernWindow");
requestActionASAP([this, queryID](){ answerQuery(queryID, 0); }); executeActionAsync("showTavernWindow", [this, queryID](){ answerQuery(queryID, 0); });
} }
void VCAI::showThievesGuildWindow(const CGObjectInstance * obj) 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()); 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)); 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 goalpriority1 = 0;
float goalpriority2 = 0; float goalpriority2 = 0;
@@ -370,7 +373,7 @@ void VCAI::showRecruitmentDialog(const CGDwelling * dwelling, const CArmedInstan
NET_EVENT_HANDLER; NET_EVENT_HANDLER;
status.addQuery(queryID, "RecruitmentDialog"); status.addQuery(queryID, "RecruitmentDialog");
requestActionASAP([this, dwelling, dst, queryID](){ executeActionAsync("showRecruitmentDialog", [this, dwelling, dst, queryID](){
recruitCreatures(dwelling, dst); recruitCreatures(dwelling, dst);
checkHeroArmy(dynamic_cast<const CGHeroInstance*>(dst)); checkHeroArmy(dynamic_cast<const CGHeroInstance*>(dst));
answerQuery(queryID, 0); answerQuery(queryID, 0);
@@ -469,7 +472,7 @@ void VCAI::showHillFortWindow(const CGObjectInstance * object, const CGHeroInsta
LOG_TRACE(logAi); LOG_TRACE(logAi);
NET_EVENT_HANDLER; NET_EVENT_HANDLER;
requestActionASAP([=]() executeActionAsync("showHillFortWindow", [visitor]()
{ {
makePossibleUpgrades(visitor); makePossibleUpgrades(visitor);
}); });
@@ -532,7 +535,7 @@ void VCAI::showUniversityWindow(const IMarket * market, const CGHeroInstance * v
NET_EVENT_HANDLER; NET_EVENT_HANDLER;
status.addQuery(queryID, "UniversityWindow"); status.addQuery(queryID, "UniversityWindow");
requestActionASAP([this, queryID](){ answerQuery(queryID, 0); }); executeActionAsync("showUniversityWindow", [this, queryID](){ answerQuery(queryID, 0); });
} }
void VCAI::heroManaPointsChanged(const CGHeroInstance * hero) void VCAI::heroManaPointsChanged(const CGHeroInstance * hero)
@@ -600,7 +603,7 @@ void VCAI::showMarketWindow(const IMarket * market, const CGHeroInstance * visit
NET_EVENT_HANDLER; NET_EVENT_HANDLER;
status.addQuery(queryID, "MarketWindow"); 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) 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); LOG_TRACE_PARAMS(logAi, "queryID '%i'", queryID);
NET_EVENT_HANDLER; NET_EVENT_HANDLER;
status.addQuery(queryID, "YourTurn"); status.addQuery(queryID, "YourTurn");
requestActionASAP([this, queryID](){ answerQuery(queryID, 0); }); executeActionAsync("yourTurn", [this, queryID](){ answerQuery(queryID, 0); });
status.startedTurn(); 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) 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); LOG_TRACE_PARAMS(logAi, "queryID '%i'", queryID);
NET_EVENT_HANDLER; NET_EVENT_HANDLER;
status.addQuery(queryID, boost::str(boost::format("Hero %s got level %d") % hero->getNameTranslated() % hero->level)); 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) 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); LOG_TRACE_PARAMS(logAi, "queryID '%i'", queryID);
NET_EVENT_HANDLER; 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)); 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) 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 :) if(!selection && cancel) //yes&no -> always answer yes, we are a brave AI :)
sel = 1; sel = 1;
requestActionASAP([this, askID, sel]() executeActionAsync("showBlockingDialog", [this, askID, sel]()
{ {
answerQuery(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); 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)); status.addQuery(queryID, boost::str(boost::format("Garrison dialog with %s and %s") % s1 % s2));
//you can't request action from action-response thread //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()) if(removableUnits && !cb->getStartInfo()->isRestorationOfErathiaCampaign())
pickBestCreatures(down, up); pickBestCreatures(down, up);
@@ -756,7 +766,7 @@ void VCAI::showMapObjectSelectDialog(QueryID askID, const Component & icon, cons
{ {
NET_EVENT_HANDLER; NET_EVENT_HANDLER;
status.addQuery(askID, "Map object select query"); 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) void makePossibleUpgrades(const CArmedInstance * obj)
@@ -845,9 +855,8 @@ void VCAI::makeTurn()
logAi->info("Hero %s has %d MP left", h->getNameTranslated(), h->movementPointsRemaining()); 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."); logAi->debug("Making turn thread has been interrupted. We'll end without calling endTurn.");
return; return;
} }
@@ -998,17 +1007,16 @@ void VCAI::mainLoop()
try try
{ {
boost::this_thread::interruption_point(); makingTurnInterrupption.interruptionPoint();
goalToRealize->accept(this); //visitor pattern 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); logAi->debug("Player %d: Making turn thread received an interruption!", playerID);
throw; //rethrow, we want to truly end this thread throw; //rethrow, we want to truly end this thread
} }
catch (goalFulfilledException & e) catch (const goalFulfilledException & e)
{ {
//the sub-goal was completed successfully //the sub-goal was completed successfully
completeGoal(e.goal); completeGoal(e.goal);
@@ -1018,7 +1026,7 @@ void VCAI::mainLoop()
// remove abstract visit tile if we completed the elementar one // remove abstract visit tile if we completed the elementar one
vstd::erase_if_present(goalsToAdd, goalToRealize); 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("Failed to realize subgoal of type %s, I will stop.", goalToRealize->name());
logAi->debug("The error message was: %s", e.what()); logAi->debug("The error message was: %s", e.what());
@@ -2364,24 +2372,23 @@ void VCAI::striveToGoal(Goals::TSubgoal basicGoal)
try try
{ {
boost::this_thread::interruption_point(); makingTurnInterrupption.interruptionPoint();
elementarGoal->accept(this); //visitor pattern 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); logAi->debug("Player %d: Making turn thread received an interruption!", playerID);
throw; //rethrow, we want to truly end this thread throw; //rethrow, we want to truly end this thread
} }
catch (goalFulfilledException & e) catch (const goalFulfilledException & e)
{ {
//the sub-goal was completed successfully //the sub-goal was completed successfully
completeGoal(e.goal); completeGoal(e.goal);
//local goal was also completed //local goal was also completed
completeGoal(elementarGoal); 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("Failed to realize subgoal of type %s, I will stop.", elementarGoal->name());
logAi->debug("The error message was: %s", e.what()); 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 int maxGoals = searchDepth; //preventing deadlock for mutually dependent goals
while (maxGoals) while (maxGoals)
{ {
boost::this_thread::interruption_point(); makingTurnInterrupption.interruptionPoint();
goal = goal->whatToDoToAchieve(); //may throw if decomposition fails goal = goal->whatToDoToAchieve(); //may throw if decomposition fails
--maxGoals; --maxGoals;
@@ -2490,27 +2497,30 @@ void VCAI::recruitHero(const CGTownInstance * t, bool throwing)
void VCAI::finish() void VCAI::finish()
{ {
//we want to lock to avoid multiple threads from calling makingTurn->join() at same time makingTurnInterrupption.interruptThread();
std::lock_guard<std::mutex> multipleCleanupGuard(turnInterruptionMutex);
if(makingTurn) if (asyncTasks)
{ {
makingTurn->interrupt(); asyncTasks->wait();
makingTurn->join(); asyncTasks.reset();
makingTurn.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); SET_GLOBAL_STATE(this);
std::shared_lock gsLock(CGameState::mutex); std::shared_lock gsLock(CGameState::mutex);
whatToDo(); whatToDo();
}); });
executeActionAsyncArena.enqueue([this](){asyncTasks->wait();});
newThread.detach();
} }
void VCAI::lostHero(HeroPtr h) void VCAI::lostHero(HeroPtr h)

View File

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

View File

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

View File

@@ -462,7 +462,7 @@ endif()
# Finding packages # # 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) if(ENABLE_INNOEXTRACT)
list(APPEND BOOST_COMPONENTS iostreams) list(APPEND BOOST_COMPONENTS iostreams)
endif() endif()

View File

@@ -138,6 +138,7 @@ static_assert(sizeof(bool) == 1, "Bool needs to be 1 byte in size.");
#include <shared_mutex> #include <shared_mutex>
#include <sstream> #include <sstream>
#include <string> #include <string>
#include <thread>
#include <unordered_map> #include <unordered_map>
#include <unordered_set> #include <unordered_set>
#include <utility> #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/current_function.hpp>
#include <boost/container/small_vector.hpp> #include <boost/container/small_vector.hpp>
#include <boost/container/static_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.hpp>
#include <boost/filesystem/fstream.hpp> #include <boost/filesystem/fstream.hpp>
#include <boost/filesystem/path.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/filtered.hpp>
#include <boost/range/adaptor/reversed.hpp> #include <boost/range/adaptor/reversed.hpp>
#include <boost/range/algorithm.hpp> #include <boost/range/algorithm.hpp>
#include <boost/thread/thread_only.hpp>
#ifndef M_PI #ifndef M_PI
# define M_PI 3.14159265358979323846 # define M_PI 3.14159265358979323846

View File

@@ -58,7 +58,7 @@ bool ArtifactsUIController::askToAssemble(const CGHeroInstance * hero, const Art
auto assemblyPossibilities = ArtifactUtils::assemblyPossibilities(hero, art->getTypeId(), onlyEquipped); auto assemblyPossibilities = ArtifactUtils::assemblyPossibilities(hero, art->getTypeId(), onlyEquipped);
if(!assemblyPossibilities.empty()) 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); std::scoped_lock askLock(askAssembleArtifactMutex);
for(const auto combinedArt : assemblyPossibilities) for(const auto combinedArt : assemblyPossibilities)

View File

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

View File

@@ -1437,7 +1437,7 @@ void CPlayerInterface::centerView (int3 pos, int focusTime)
{ {
IgnoreEvents ignore(*this); IgnoreEvents ignore(*this);
auto unlockInterface = vstd::makeUnlockGuard(ENGINE->interfaceMutex); 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(); ENGINE->cursor().show();
@@ -1789,7 +1789,7 @@ void CPlayerInterface::waitForAllDialogs()
while(!dialogs.empty()) while(!dialogs.empty())
{ {
auto unlockInterface = vstd::makeUnlockGuard(ENGINE->interfaceMutex); 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(); waitWhileDialog();
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -751,7 +751,7 @@ void BattleInterface::requestAutofightingAIToTakeAction()
// FIXME: unsafe // FIXME: unsafe
// Run task in separate thread to avoid UI lock while AI is making turn (which might take some time) // 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 // 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"); setThreadName("autofightingAI");
localCurInt->autofightingAI->activeStack(localBattleID, activeStack); localCurInt->autofightingAI->activeStack(localBattleID, activeStack);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -37,7 +37,7 @@
#include "../../lib/mapObjects/CGTownInstance.h" #include "../../lib/mapObjects/CGTownInstance.h"
#include "../../lib/mapObjects/CQuest.h" #include "../../lib/mapObjects/CQuest.h"
#include "../../lib/mapObjects/MiscObjects.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) 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)
{ {

View File

@@ -318,7 +318,7 @@ int main(int argc, char * argv[])
#ifndef VCMI_NO_THREADED_LOAD #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 //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"); setThreadName("initialize");
init(); init();
@@ -368,13 +368,13 @@ int main(int argc, char * argv[])
{ {
session["testmap"].String() = vm["testmap"].as<std::string>(); session["testmap"].String() = vm["testmap"].as<std::string>();
session["onlyai"].Bool() = true; 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")) else if(vm.count("testsave"))
{ {
session["testsave"].String() = vm["testsave"].as<std::string>(); session["testsave"].String() = vm["testsave"].as<std::string>();
session["onlyai"].Bool() = true; 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()) else if (!settings["session"]["headless"].Bool())
{ {
@@ -397,9 +397,9 @@ int main(int argc, char * argv[])
else else
{ {
while(!headlessQuit) 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(); quitApplication();
} }
@@ -448,7 +448,6 @@ static void mainLoop()
} }
GAME.reset(); GAME.reset();
GAME->mainmenu().reset();
if(!settings["session"]["headless"].Bool()) if(!settings["session"]["headless"].Bool())
{ {

View File

@@ -101,13 +101,15 @@ If you're creating new project part, place `VCMI_LIB_USING_NAMESPACE` in its `St
## Artificial Intelligence ## 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 ### 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 ## 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 - NullkillerAI parallelizes a lot of its tasks using TBB methods, mostly parallel_for
- Random map generator actively uses thread pool provided by TBB - Random map generator actively uses thread pool provided by TBB
- Client performs image upscaling in background thread to avoid visible freezes - 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 ## 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 - 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. - 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.

View File

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

View File

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

View File

@@ -702,6 +702,7 @@ set(lib_MAIN_HEADERS
CCreatureSet.h CCreatureSet.h
CGameInfoCallback.h CGameInfoCallback.h
CGameInterface.h CGameInterface.h
ConditionalWait.h
ConstTransitivePtr.h ConstTransitivePtr.h
Color.h Color.h
CPlayerState.h CPlayerState.h
@@ -757,7 +758,7 @@ endif()
set_target_properties(vcmi PROPERTIES COMPILE_DEFINITIONS "VCMI_DLL=1") set_target_properties(vcmi PROPERTIES COMPILE_DEFINITIONS "VCMI_DLL=1")
target_link_libraries(vcmi PUBLIC target_link_libraries(vcmi PUBLIC
minizip::minizip ZLIB::ZLIB TBB::tbb 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) if(ENABLE_STATIC_LIBS AND ENABLE_CLIENT)

View File

@@ -35,7 +35,7 @@ void CRandomGenerator::resetSeed()
{ {
logRng->trace("CRandomGenerator::resetSeed"); logRng->trace("CRandomGenerator::resetSeed");
boost::hash<std::string> stringHash; 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))); setSeed(static_cast<int>(threadIdHash * std::time(nullptr)));
} }

View File

@@ -19,42 +19,10 @@
#include <sys/prctl.h> #include <sys/prctl.h>
#endif #endif
#include <tbb/task_arena.h>
VCMI_LIB_NAMESPACE_BEGIN VCMI_LIB_NAMESPACE_BEGIN
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]();
}
}
static thread_local std::string threadNameForLogging; static thread_local std::string threadNameForLogging;
std::string getThreadName() std::string getThreadName()
@@ -62,7 +30,12 @@ std::string getThreadName()
if (!threadNameForLogging.empty()) if (!threadNameForLogging.empty())
return threadNameForLogging; 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) void setThreadNameLoggingOnly(const std::string &name)

View File

@@ -11,80 +11,6 @@
VCMI_LIB_NAMESPACE_BEGIN VCMI_LIB_NAMESPACE_BEGIN
///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;
void processTasks();
};
template<typename Payload>
class ThreadPool
{
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()
{
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
}
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)
{
while(true)
{
size_t pom;
{
std::unique_lock<std::mutex> lock(rtinm);
if((pom = currentTask) >= amount)
break;
else
++currentTask;
}
(*tasks)[pom](payload);
}
}
};
/// Sets thread name that will be used for both logs and debugger (if supported) /// 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 /// 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); void DLL_LINKAGE setThreadName(const std::string &name);
@@ -95,4 +21,17 @@ 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 /// 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(); std::string DLL_LINKAGE getThreadName();
class DLL_LINKAGE ScopedThreadName : boost::noncopyable
{
public:
ScopedThreadName(const std::string & name)
{
setThreadName(name);
}
~ScopedThreadName()
{
setThreadName({});
}
};
VCMI_LIB_NAMESPACE_END VCMI_LIB_NAMESPACE_END

View File

@@ -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 class ConditionalWait
{ {
bool isBusyValue = false; bool isBusyValue = false;

View File

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

View File

@@ -10,6 +10,10 @@
#pragma once #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 VCMI_LIB_NAMESPACE_BEGIN
class CLogger; class CLogger;

View File

@@ -102,7 +102,7 @@ Graphics::Graphics()
tasks += std::bind(&Graphics::loadErmuToPicture,this); tasks += std::bind(&Graphics::loadErmuToPicture,this);
tasks += std::bind(&Graphics::initializeImageLists,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(); th.run();
#else #else
loadPaletteAndColors(); loadPaletteAndColors();

View File

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