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:
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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});
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
4
Global.h
4
Global.h
@@ -138,6 +138,7 @@ static_assert(sizeof(bool) == 1, "Bool needs to be 1 byte in size.");
|
||||
#include <shared_mutex>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
@@ -172,8 +173,6 @@ static_assert(sizeof(bool) == 1, "Bool needs to be 1 byte in size.");
|
||||
#include <boost/current_function.hpp>
|
||||
#include <boost/container/small_vector.hpp>
|
||||
#include <boost/container/static_vector.hpp>
|
||||
#include <boost/date_time/posix_time/posix_time_types.hpp>
|
||||
#include <boost/date_time/posix_time/time_formatters.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/filesystem/fstream.hpp>
|
||||
#include <boost/filesystem/path.hpp>
|
||||
@@ -188,7 +187,6 @@ static_assert(sizeof(bool) == 1, "Bool needs to be 1 byte in size.");
|
||||
#include <boost/range/adaptor/filtered.hpp>
|
||||
#include <boost/range/adaptor/reversed.hpp>
|
||||
#include <boost/range/algorithm.hpp>
|
||||
#include <boost/thread/thread_only.hpp>
|
||||
|
||||
#ifndef M_PI
|
||||
# define M_PI 3.14159265358979323846
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -405,7 +405,6 @@ set(vcmiclientcommon_HEADERS
|
||||
Client.h
|
||||
ClientCommandManager.h
|
||||
ClientNetPackVisitors.h
|
||||
ConditionalWait.h
|
||||
HeroMovementController.h
|
||||
GameChatHandler.h
|
||||
LobbyClientNetPackVisitors.h
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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!");
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "IMapRendererObserver.h"
|
||||
#include "../ConditionalWait.h"
|
||||
#include "../../lib/ConditionalWait.h"
|
||||
#include "../../lib/Point.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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())
|
||||
{
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user