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,8 +143,6 @@ void CBattleAI::activeStack(const BattleID & battleID, const CStack * stack )
auto start = std::chrono::high_resolution_clock::now();
try
{
if(stack->creatureId() == CreatureID::CATAPULT)
{
cb->battleMakeUnitAction(battleID, useCatapult(battleID, stack));
@@ -182,11 +180,6 @@ void CBattleAI::activeStack(const BattleID & battleID, const CStack * stack )
cb->battleMakeUnitAction(battleID, *action);
return;
}
}
catch(boost::thread_interrupted &)
{
throw;
}
if(result.actionType == EActionType::DEFEND)
{

View File

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

View File

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

View File

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

View File

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

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

View File

@@ -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();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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

View File

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

View File

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

View File

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

View File

@@ -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"

View File

@@ -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"

View File

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

View File

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

View File

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

View File

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

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
std::function<void(const std::string &, bool)> cb;
std::atomic<bool> shutdownPending = false;
std::mutex smx;
boost::thread thread;
std::thread thread;
};
VCMI_LIB_NAMESPACE_END

View File

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

View File

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

View File

@@ -19,42 +19,10 @@
#include <sys/prctl.h>
#endif
#include <tbb/task_arena.h>
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;
std::string getThreadName()
@@ -62,7 +30,12 @@ std::string getThreadName()
if (!threadNameForLogging.empty())
return threadNameForLogging;
return boost::lexical_cast<std::string>(boost::this_thread::get_id());
int tbbIndex = tbb::this_task_arena::current_thread_index();
if (tbbIndex < 0)
return boost::lexical_cast<std::string>(std::this_thread::get_id());
else
return "TBB worker " + boost::lexical_cast<std::string>(tbbIndex);
}
void setThreadNameLoggingOnly(const std::string &name)

View File

@@ -11,80 +11,6 @@
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)
/// 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);
@@ -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
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

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

View File

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

View File

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

View File

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

View File

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