1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-03-23 21:29:13 +02:00

Merge pull request #5261 from IvanSavenko/bonus_optimize

Bonus system optimization - per-node invalidation
This commit is contained in:
Ivan Savenko 2025-01-15 14:42:36 +02:00 committed by GitHub
commit ce1350b3a4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 156 additions and 135 deletions

View File

@ -170,7 +170,7 @@ TConstBonusListPtr StackWithBonuses::getAllBonuses(const CSelector & selector, c
return ret; return ret;
} }
int64_t StackWithBonuses::getTreeVersion() const int32_t StackWithBonuses::getTreeVersion() const
{ {
auto result = owner->getTreeVersion(); auto result = owner->getTreeVersion();
@ -485,7 +485,7 @@ BattleLayout HypotheticBattle::getLayout() const
return subject->getBattle()->getLayout(); return subject->getBattle()->getLayout();
} }
int64_t HypotheticBattle::getTreeVersion() const int32_t HypotheticBattle::getTreeVersion() const
{ {
return getBonusBearer()->getTreeVersion() + bonusTreeVersion; return getBonusBearer()->getTreeVersion() + bonusTreeVersion;
} }

View File

@ -93,7 +93,7 @@ public:
TConstBonusListPtr getAllBonuses(const CSelector & selector, const CSelector & limit, TConstBonusListPtr getAllBonuses(const CSelector & selector, const CSelector & limit,
const std::string & cachingStr = "") const override; const std::string & cachingStr = "") const override;
int64_t getTreeVersion() const override; int32_t getTreeVersion() const override;
void addUnitBonus(const std::vector<Bonus> & bonus); void addUnitBonus(const std::vector<Bonus> & bonus);
void updateUnitBonus(const std::vector<Bonus> & bonus); void updateUnitBonus(const std::vector<Bonus> & bonus);
@ -162,7 +162,7 @@ public:
int3 getLocation() const override; int3 getLocation() const override;
BattleLayout getLayout() const override; BattleLayout getLayout() const override;
int64_t getTreeVersion() const; int32_t getTreeVersion() const;
void makeWait(const battle::Unit * activeStack); void makeWait(const battle::Unit * activeStack);

View File

@ -20,7 +20,9 @@ void BuildAnalyzer::updateTownDwellings(TownDevelopmentInfo & developmentInfo)
{ {
for(int level = 0; level < developmentInfo.town->getTown()->creatures.size(); level++) for(int level = 0; level < developmentInfo.town->getTown()->creatures.size(); level++)
{ {
#if NKAI_TRACE_LEVEL >= 1
logAi->trace("Checking dwelling level %d", level); logAi->trace("Checking dwelling level %d", level);
#endif
std::vector<BuildingID> dwellingsInTown; std::vector<BuildingID> dwellingsInTown;
for(BuildingID buildID = BuildingID::getDwellingFromLevel(level, 0); buildID.hasValue(); BuildingID::advanceDwelling(buildID)) for(BuildingID buildID = BuildingID::getDwellingFromLevel(level, 0); buildID.hasValue(); BuildingID::advanceDwelling(buildID))
@ -143,9 +145,9 @@ void BuildAnalyzer::update()
{ {
if(town->built >= cb->getSettings().getInteger(EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP)) if(town->built >= cb->getSettings().getInteger(EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP))
continue; // Not much point in trying anything - can't built in this town anymore today continue; // Not much point in trying anything - can't built in this town anymore today
#if NKAI_TRACE_LEVEL >= 1
logAi->trace("Checking town %s", town->getNameTranslated()); logAi->trace("Checking town %s", town->getNameTranslated());
#endif
developmentInfos.push_back(TownDevelopmentInfo(town)); developmentInfos.push_back(TownDevelopmentInfo(town));
TownDevelopmentInfo & developmentInfo = developmentInfos.back(); TownDevelopmentInfo & developmentInfo = developmentInfos.back();
@ -161,10 +163,10 @@ void BuildAnalyzer::update()
} }
armyCost += developmentInfo.armyCost; armyCost += developmentInfo.armyCost;
#if NKAI_TRACE_LEVEL >= 1
for(auto bi : developmentInfo.toBuild) for(auto bi : developmentInfo.toBuild)
{
logAi->trace("Building preferences %s", bi.toString()); logAi->trace("Building preferences %s", bi.toString());
} #endif
} }
std::sort(developmentInfos.begin(), developmentInfos.end(), [](const TownDevelopmentInfo & t1, const TownDevelopmentInfo & t2) -> bool std::sort(developmentInfos.begin(), developmentInfos.end(), [](const TownDevelopmentInfo & t1, const TownDevelopmentInfo & t2) -> bool
@ -179,7 +181,9 @@ void BuildAnalyzer::update()
goldPressure = (ai->getLockedResources()[EGameResID::GOLD] + (float)armyCost[EGameResID::GOLD] + economyDevelopmentCost) / (1 + 2 * ai->getFreeGold() + (float)dailyIncome[EGameResID::GOLD] * 7.0f); goldPressure = (ai->getLockedResources()[EGameResID::GOLD] + (float)armyCost[EGameResID::GOLD] + economyDevelopmentCost) / (1 + 2 * ai->getFreeGold() + (float)dailyIncome[EGameResID::GOLD] * 7.0f);
#if NKAI_TRACE_LEVEL >= 1
logAi->trace("Gold pressure: %f", goldPressure); logAi->trace("Gold pressure: %f", goldPressure);
#endif
} }
void BuildAnalyzer::reset() void BuildAnalyzer::reset()
@ -268,12 +272,15 @@ BuildingInfo BuildAnalyzer::getBuildingOrPrerequisite(
if(vstd::contains_if(missingBuildings, otherDwelling)) if(vstd::contains_if(missingBuildings, otherDwelling))
{ {
#if NKAI_TRACE_LEVEL >= 1
logAi->trace("cant build %d. Need other dwelling %d", toBuild.getNum(), missingBuildings.front().getNum()); logAi->trace("cant build %d. Need other dwelling %d", toBuild.getNum(), missingBuildings.front().getNum());
#endif
} }
else if(missingBuildings[0] != toBuild) else if(missingBuildings[0] != toBuild)
{ {
#if NKAI_TRACE_LEVEL >= 1
logAi->trace("cant build %d. Need %d", toBuild.getNum(), missingBuildings[0].num); logAi->trace("cant build %d. Need %d", toBuild.getNum(), missingBuildings[0].num);
#endif
BuildingInfo prerequisite = getBuildingOrPrerequisite(town, missingBuildings[0], excludeDwellingDependencies); BuildingInfo prerequisite = getBuildingOrPrerequisite(town, missingBuildings[0], excludeDwellingDependencies);
prerequisite.buildCostWithPrerequisites += info.buildCost; prerequisite.buildCostWithPrerequisites += info.buildCost;
@ -298,19 +305,24 @@ BuildingInfo BuildAnalyzer::getBuildingOrPrerequisite(
} }
else else
{ {
#if NKAI_TRACE_LEVEL >= 1
logAi->trace("Cant build. The building requires itself as prerequisite"); logAi->trace("Cant build. The building requires itself as prerequisite");
#endif
return info; return info;
} }
} }
else else
{ {
#if NKAI_TRACE_LEVEL >= 1
logAi->trace("Cant build. Reason: %d", static_cast<int>(canBuild)); logAi->trace("Cant build. Reason: %d", static_cast<int>(canBuild));
#endif
} }
} }
else else
{ {
#if NKAI_TRACE_LEVEL >= 1
logAi->trace("Dwelling %d exists", toBuild.getNum()); logAi->trace("Dwelling %d exists", toBuild.getNum());
#endif
info.exists = true; info.exists = true;
} }

View File

@ -151,7 +151,9 @@ bool handleGarrisonHeroFromPreviousTurn(const CGTownInstance * town, Goals::TGoa
void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInstance * town, const Nullkiller * ai) const void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInstance * town, const Nullkiller * ai) const
{ {
#if NKAI_TRACE_LEVEL >= 1
logAi->trace("Evaluating defence for %s", town->getNameTranslated()); logAi->trace("Evaluating defence for %s", town->getNameTranslated());
#endif
auto threatNode = ai->dangerHitMap->getObjectThreat(town); auto threatNode = ai->dangerHitMap->getObjectThreat(town);
std::vector<HitMapInfo> threats = ai->dangerHitMap->getTownThreats(town); std::vector<HitMapInfo> threats = ai->dangerHitMap->getTownThreats(town);
@ -164,8 +166,9 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
} }
if(!threatNode.fastestDanger.hero) if(!threatNode.fastestDanger.hero)
{ {
#if NKAI_TRACE_LEVEL >= 1
logAi->trace("No threat found for town %s", town->getNameTranslated()); logAi->trace("No threat found for town %s", town->getNameTranslated());
#endif
return; return;
} }
@ -173,7 +176,9 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
if(reinforcement) if(reinforcement)
{ {
#if NKAI_TRACE_LEVEL >= 1
logAi->trace("Town %s can buy defence army %lld", town->getNameTranslated(), reinforcement); logAi->trace("Town %s can buy defence army %lld", town->getNameTranslated(), reinforcement);
#endif
tasks.push_back(Goals::sptr(Goals::BuyArmy(town, reinforcement).setpriority(0.5f))); tasks.push_back(Goals::sptr(Goals::BuyArmy(town, reinforcement).setpriority(0.5f)));
} }
@ -181,13 +186,14 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
for(auto & threat : threats) for(auto & threat : threats)
{ {
#if NKAI_TRACE_LEVEL >= 1
logAi->trace( logAi->trace(
"Town %s has threat %lld in %s turns, hero: %s", "Town %s has threat %lld in %s turns, hero: %s",
town->getNameTranslated(), town->getNameTranslated(),
threat.danger, threat.danger,
std::to_string(threat.turn), std::to_string(threat.turn),
threat.hero ? threat.hero->getNameTranslated() : std::string("<no hero>")); threat.hero ? threat.hero->getNameTranslated() : std::string("<no hero>"));
#endif
handleCounterAttack(town, threat, threatNode.maximumDanger, ai, tasks); handleCounterAttack(town, threat, threatNode.maximumDanger, ai, tasks);
if(isThreatUnderControl(town, threat, ai, paths)) if(isThreatUnderControl(town, threat, ai, paths))
@ -199,7 +205,9 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
if(paths.empty()) if(paths.empty())
{ {
#if NKAI_TRACE_LEVEL >= 1
logAi->trace("No ways to defend town %s", town->getNameTranslated()); logAi->trace("No ways to defend town %s", town->getNameTranslated());
#endif
continue; continue;
} }

View File

@ -1201,7 +1201,9 @@ public:
if (bi.id == BuildingID::MARKETPLACE || bi.dailyIncome[EGameResID::WOOD] > 0) if (bi.id == BuildingID::MARKETPLACE || bi.dailyIncome[EGameResID::WOOD] > 0)
evaluationContext.isTradeBuilding = true; evaluationContext.isTradeBuilding = true;
#if NKAI_TRACE_LEVEL >= 1
logAi->trace("Building costs for %s : %s MarketValue: %d",bi.toString(), evaluationContext.buildingCost.toString(), evaluationContext.buildingCost.marketValue()); logAi->trace("Building costs for %s : %s MarketValue: %d",bi.toString(), evaluationContext.buildingCost.toString(), evaluationContext.buildingCost.marketValue());
#endif
if(bi.creatureID != CreatureID::NONE) if(bi.creatureID != CreatureID::NONE)
{ {

View File

@ -703,8 +703,9 @@ void CArtHandler::afterLoadFinalization()
assert(bonus->source == BonusSource::ARTIFACT); assert(bonus->source == BonusSource::ARTIFACT);
bonus->sid = BonusSourceID(art->id); bonus->sid = BonusSourceID(art->id);
} }
art->nodeHasChanged();
} }
CBonusSystemNode::treeHasChanged();
} }
CArtifactInstance * CArtifactSet::getArt(const ArtifactPosition & pos, bool excludeLocked) const CArtifactInstance * CArtifactSet::getArt(const ArtifactPosition & pos, bool excludeLocked) const

View File

@ -670,14 +670,18 @@ void CCreatureSet::serializeJson(JsonSerializeFormat & handler, const std::strin
} }
} }
CStackInstance::CStackInstance() CStackInstance::CStackInstance(bool isHypothetic)
: armyObj(_armyObj) : CBonusSystemNode(isHypothetic),
armyObj(_armyObj),
nativeTerrain(this, Selector::type()(BonusType::TERRAIN_NATIVE)),
initiative(this, Selector::type()(BonusType::STACKS_SPEED))
{ {
init(); init();
} }
CStackInstance::CStackInstance(const CreatureID & id, TQuantity Count, bool isHypothetic): CStackInstance::CStackInstance(const CreatureID & id, TQuantity Count, bool isHypothetic)
CBonusSystemNode(isHypothetic), armyObj(_armyObj) : CStackInstance(false)
{ {
init(); init();
setType(id); setType(id);
@ -685,7 +689,7 @@ CStackInstance::CStackInstance(const CreatureID & id, TQuantity Count, bool isHy
} }
CStackInstance::CStackInstance(const CCreature *cre, TQuantity Count, bool isHypothetic) CStackInstance::CStackInstance(const CCreature *cre, TQuantity Count, bool isHypothetic)
: CBonusSystemNode(isHypothetic), armyObj(_armyObj) : CStackInstance(false)
{ {
init(); init();
setType(cre); setType(cre);
@ -834,6 +838,22 @@ PlayerColor CStackInstance::getOwner() const
return _armyObj ? _armyObj->getOwner() : PlayerColor::NEUTRAL; return _armyObj ? _armyObj->getOwner() : PlayerColor::NEUTRAL;
} }
int32_t CStackInstance::getInitiative(int turn) const
{
if (turn == 0)
return initiative.getValue();
return ACreature::getInitiative(turn);
}
TerrainId CStackInstance::getNativeTerrain() const
{
if (nativeTerrain.hasBonus())
return TerrainId::ANY_TERRAIN;
return getFactionID().toEntity(VLC)->getNativeTerrain();
}
void CStackInstance::deserializationFix() void CStackInstance::deserializationFix()
{ {
const CArmedInstance *armyBackup = _armyObj; const CArmedInstance *armyBackup = _armyObj;

View File

@ -10,6 +10,7 @@
#pragma once #pragma once
#include "bonuses/Bonus.h" #include "bonuses/Bonus.h"
#include "bonuses/BonusCache.h"
#include "bonuses/CBonusSystemNode.h" #include "bonuses/CBonusSystemNode.h"
#include "serializer/Serializeable.h" #include "serializer/Serializeable.h"
#include "GameConstants.h" #include "GameConstants.h"
@ -71,6 +72,9 @@ public:
class DLL_LINKAGE CStackInstance : public CBonusSystemNode, public CStackBasicDescriptor, public CArtifactSet, public ACreature class DLL_LINKAGE CStackInstance : public CBonusSystemNode, public CStackBasicDescriptor, public CArtifactSet, public ACreature
{ {
BonusValueCache nativeTerrain;
BonusValueCache initiative;
protected: protected:
const CArmedInstance *_armyObj; //stack must be part of some army, army must be part of some object const CArmedInstance *_armyObj; //stack must be part of some army, army must be part of some object
@ -119,7 +123,7 @@ public:
CreatureID getCreatureID() const; //-1 if not available CreatureID getCreatureID() const; //-1 if not available
std::string getName() const; //plural or singular std::string getName() const; //plural or singular
virtual void init(); virtual void init();
CStackInstance(); CStackInstance(bool isHypothetic = false);
CStackInstance(const CreatureID & id, TQuantity count, bool isHypothetic = false); CStackInstance(const CreatureID & id, TQuantity count, bool isHypothetic = false);
CStackInstance(const CCreature *cre, TQuantity count, bool isHypothetic = false); CStackInstance(const CCreature *cre, TQuantity count, bool isHypothetic = false);
virtual ~CStackInstance() = default; virtual ~CStackInstance() = default;
@ -135,6 +139,9 @@ public:
std::string nodeName() const override; //from CBonusSystemnode std::string nodeName() const override; //from CBonusSystemnode
void deserializationFix(); void deserializationFix();
PlayerColor getOwner() const override; PlayerColor getOwner() const override;
int32_t getInitiative(int turn = 0) const final;
TerrainId getNativeTerrain() const final;
}; };
class DLL_LINKAGE CCommanderInstance : public CStackInstance class DLL_LINKAGE CCommanderInstance : public CStackInstance

View File

@ -693,7 +693,7 @@ void BattleInfo::moveUnit(uint32_t id, BattleHex destination)
sta->position = destination; sta->position = destination;
//Bonuses can be limited by unit placement, so, change tree version //Bonuses can be limited by unit placement, so, change tree version
//to force updating a bonus. TODO: update version only when such bonuses are present //to force updating a bonus. TODO: update version only when such bonuses are present
CBonusSystemNode::treeHasChanged(); nodeHasChanged();
} }
void BattleInfo::setUnitState(uint32_t id, const JsonNode & data, int64_t healthDelta) void BattleInfo::setUnitState(uint32_t id, const JsonNode & data, int64_t healthDelta)
@ -890,7 +890,7 @@ void BattleInfo::addOrUpdateUnitBonus(CStack * sta, const Bonus & value, bool fo
stackBonus->turnsRemain = std::max(stackBonus->turnsRemain, value.turnsRemain); stackBonus->turnsRemain = std::max(stackBonus->turnsRemain, value.turnsRemain);
} }
} }
CBonusSystemNode::treeHasChanged(); sta->nodeHasChanged();
} }
} }

View File

@ -953,7 +953,7 @@ TConstBonusListPtr CUnitStateDetached::getAllBonuses(const CSelector & selector,
return bonus->getAllBonuses(selector, limit, cachingStr); return bonus->getAllBonuses(selector, limit, cachingStr);
} }
int64_t CUnitStateDetached::getTreeVersion() const int32_t CUnitStateDetached::getTreeVersion() const
{ {
return bonus->getTreeVersion(); return bonus->getTreeVersion();
} }

View File

@ -279,7 +279,7 @@ public:
TConstBonusListPtr getAllBonuses(const CSelector & selector, const CSelector & limit, const std::string & cachingStr = "") const override; TConstBonusListPtr getAllBonuses(const CSelector & selector, const CSelector & limit, const std::string & cachingStr = "") const override;
int64_t getTreeVersion() const override; int32_t getTreeVersion() const override;
uint32_t unitId() const override; uint32_t unitId() const override;
BattleSide unitSide() const override; BattleSide unitSide() const override;

View File

@ -32,8 +32,8 @@ protected:
struct BonusCacheEntry struct BonusCacheEntry
{ {
std::atomic<int64_t> version = 0; std::atomic<int32_t> version = 0;
std::atomic<int64_t> value = 0; std::atomic<int32_t> value = 0;
BonusCacheEntry() = default; BonusCacheEntry() = default;
BonusCacheEntry(const BonusCacheEntry & other) BonusCacheEntry(const BonusCacheEntry & other)
@ -152,7 +152,7 @@ private:
class PrimarySkillsCache class PrimarySkillsCache
{ {
const IBonusBearer * target; const IBonusBearer * target;
mutable std::atomic<int64_t> version = 0; mutable std::atomic<int32_t> version = 0;
mutable std::array<std::atomic<int32_t>, 4> skills; mutable std::array<std::atomic<int32_t>, 4> skills;
void update() const; void update() const;
@ -166,7 +166,7 @@ public:
class MagicSchoolMasteryCache class MagicSchoolMasteryCache
{ {
const IBonusBearer * target; const IBonusBearer * target;
mutable std::atomic<int64_t> version = 0; mutable std::atomic<int32_t> version = 0;
mutable std::array<std::atomic<int32_t>, 4+1> schools; mutable std::array<std::atomic<int32_t>, 4+1> schools;
void update() const; void update() const;
@ -184,7 +184,7 @@ class BonusCachePerTurn : public BonusCacheBase
const CSelector selector; const CSelector selector;
mutable TConstBonusListPtr bonusList; mutable TConstBonusListPtr bonusList;
mutable std::mutex bonusListMutex; mutable std::mutex bonusListMutex;
mutable std::atomic<int64_t> bonusListVersion = 0; mutable std::atomic<int32_t> bonusListVersion = 0;
mutable std::array<BonusCacheEntry, cachedTurns> cache; mutable std::array<BonusCacheEntry, cachedTurns> cache;
const BonusCacheMode mode; const BonusCacheMode mode;

View File

@ -14,36 +14,6 @@
VCMI_LIB_NAMESPACE_BEGIN VCMI_LIB_NAMESPACE_BEGIN
BonusList::BonusList(bool BelongsToTree) : belongsToTree(BelongsToTree)
{
}
BonusList::BonusList(const BonusList & bonusList): belongsToTree(false)
{
bonuses.resize(bonusList.size());
std::copy(bonusList.begin(), bonusList.end(), bonuses.begin());
}
BonusList::BonusList(BonusList && other) noexcept: belongsToTree(false)
{
std::swap(belongsToTree, other.belongsToTree);
std::swap(bonuses, other.bonuses);
}
BonusList& BonusList::operator=(const BonusList &bonusList)
{
bonuses.resize(bonusList.size());
std::copy(bonusList.begin(), bonusList.end(), bonuses.begin());
belongsToTree = false;
return *this;
}
void BonusList::changed() const
{
if(belongsToTree)
CBonusSystemNode::treeHasChanged();
}
void BonusList::stackBonuses() void BonusList::stackBonuses()
{ {
boost::sort(bonuses, [](const std::shared_ptr<Bonus> & b1, const std::shared_ptr<Bonus> & b2) -> bool boost::sort(bonuses, [](const std::shared_ptr<Bonus> & b1, const std::shared_ptr<Bonus> & b2) -> bool
@ -228,19 +198,16 @@ JsonNode BonusList::toJsonNode() const
void BonusList::push_back(const std::shared_ptr<Bonus> & x) void BonusList::push_back(const std::shared_ptr<Bonus> & x)
{ {
bonuses.push_back(x); bonuses.push_back(x);
changed();
} }
BonusList::TInternalContainer::iterator BonusList::erase(const int position) BonusList::TInternalContainer::iterator BonusList::erase(const int position)
{ {
changed();
return bonuses.erase(bonuses.begin() + position); return bonuses.erase(bonuses.begin() + position);
} }
void BonusList::clear() void BonusList::clear()
{ {
bonuses.clear(); bonuses.clear();
changed();
} }
std::vector<BonusList *>::size_type BonusList::operator-=(const std::shared_ptr<Bonus> & i) std::vector<BonusList *>::size_type BonusList::operator-=(const std::shared_ptr<Bonus> & i)
@ -249,20 +216,17 @@ std::vector<BonusList *>::size_type BonusList::operator-=(const std::shared_ptr<
if(itr == bonuses.end()) if(itr == bonuses.end())
return false; return false;
bonuses.erase(itr); bonuses.erase(itr);
changed();
return true; return true;
} }
void BonusList::resize(BonusList::TInternalContainer::size_type sz, const std::shared_ptr<Bonus> & c) void BonusList::resize(BonusList::TInternalContainer::size_type sz, const std::shared_ptr<Bonus> & c)
{ {
bonuses.resize(sz, c); bonuses.resize(sz, c);
changed();
} }
void BonusList::insert(BonusList::TInternalContainer::iterator position, BonusList::TInternalContainer::size_type n, const std::shared_ptr<Bonus> & x) void BonusList::insert(BonusList::TInternalContainer::iterator position, BonusList::TInternalContainer::size_type n, const std::shared_ptr<Bonus> & x)
{ {
bonuses.insert(position, n, x); bonuses.insert(position, n, x);
changed();
} }
DLL_LINKAGE std::ostream & operator<<(std::ostream &out, const BonusList &bonusList) DLL_LINKAGE std::ostream & operator<<(std::ostream &out, const BonusList &bonusList)

View File

@ -21,8 +21,6 @@ public:
private: private:
TInternalContainer bonuses; TInternalContainer bonuses;
bool belongsToTree;
void changed() const;
public: public:
using const_reference = TInternalContainer::const_reference; using const_reference = TInternalContainer::const_reference;
@ -31,10 +29,7 @@ public:
using const_iterator = TInternalContainer::const_iterator; using const_iterator = TInternalContainer::const_iterator;
using iterator = TInternalContainer::iterator; using iterator = TInternalContainer::iterator;
BonusList(bool BelongsToTree = false); BonusList() = default;
BonusList(const BonusList &bonusList);
BonusList(BonusList && other) noexcept;
BonusList& operator=(const BonusList &bonusList);
// wrapper functions of the STL vector container // wrapper functions of the STL vector container
TInternalContainer::size_type size() const { return bonuses.size(); } TInternalContainer::size_type size() const { return bonuses.size(); }

View File

@ -17,8 +17,7 @@
VCMI_LIB_NAMESPACE_BEGIN VCMI_LIB_NAMESPACE_BEGIN
std::atomic<int64_t> CBonusSystemNode::treeChanged(1); constexpr bool cachingEnabled = true;
constexpr bool CBonusSystemNode::cachingEnabled = true;
std::shared_ptr<Bonus> CBonusSystemNode::getLocalBonus(const CSelector & selector) std::shared_ptr<Bonus> CBonusSystemNode::getLocalBonus(const CSelector & selector)
{ {
@ -97,11 +96,11 @@ void CBonusSystemNode::getAllBonusesRec(BonusList &out, const CSelector & select
TConstBonusListPtr CBonusSystemNode::getAllBonuses(const CSelector &selector, const CSelector &limit, const std::string &cachingStr) const TConstBonusListPtr CBonusSystemNode::getAllBonuses(const CSelector &selector, const CSelector &limit, const std::string &cachingStr) const
{ {
if (CBonusSystemNode::cachingEnabled) if (cachingEnabled)
{ {
// If a bonus system request comes with a caching string then look up in the map if there are any // If a bonus system request comes with a caching string then look up in the map if there are any
// pre-calculated bonus results. Limiters can't be cached so they have to be calculated. // pre-calculated bonus results. Limiters can't be cached so they have to be calculated.
if (cachedLast == treeChanged && !cachingStr.empty()) if (cachedLast == nodeChanged && !cachingStr.empty())
{ {
RequestsMap::const_accessor accessor; RequestsMap::const_accessor accessor;
@ -114,7 +113,7 @@ TConstBonusListPtr CBonusSystemNode::getAllBonuses(const CSelector &selector, co
//Perform bonus selection //Perform bonus selection
auto ret = std::make_shared<BonusList>(); auto ret = std::make_shared<BonusList>();
if (cachedLast == treeChanged) if (cachedLast == nodeChanged)
{ {
// Cached bonuses are up-to-date - use shared/read access and compute results // Cached bonuses are up-to-date - use shared/read access and compute results
std::shared_lock lock(sync); std::shared_lock lock(sync);
@ -125,7 +124,7 @@ TConstBonusListPtr CBonusSystemNode::getAllBonuses(const CSelector &selector, co
// If the bonus system tree changes(state of a single node or the relations to each other) then // If the bonus system tree changes(state of a single node or the relations to each other) then
// cache all bonus objects. Selector objects doesn't matter. // cache all bonus objects. Selector objects doesn't matter.
std::lock_guard lock(sync); std::lock_guard lock(sync);
if (cachedLast == treeChanged) if (cachedLast == nodeChanged)
{ {
// While our thread was waiting, another one have updated bonus tree. Use cached bonuses. // While our thread was waiting, another one have updated bonus tree. Use cached bonuses.
cachedBonuses.getBonuses(*ret, selector, limit); cachedBonuses.getBonuses(*ret, selector, limit);
@ -140,7 +139,7 @@ TConstBonusListPtr CBonusSystemNode::getAllBonuses(const CSelector &selector, co
getAllBonusesRec(allBonuses, Selector::all); getAllBonusesRec(allBonuses, Selector::all);
limitBonuses(allBonuses, cachedBonuses); limitBonuses(allBonuses, cachedBonuses);
cachedBonuses.stackBonuses(); cachedBonuses.stackBonuses();
cachedLast = treeChanged; cachedLast = nodeChanged;
cachedBonuses.getBonuses(*ret, selector, limit); cachedBonuses.getBonuses(*ret, selector, limit);
} }
} }
@ -155,7 +154,7 @@ TConstBonusListPtr CBonusSystemNode::getAllBonuses(const CSelector &selector, co
accessor->second.first = cachedLast; accessor->second.first = cachedLast;
} }
else else
cachedRequests.emplace(cachingStr, std::pair<int64_t, TBonusListPtr>{ cachedLast, ret }); cachedRequests.emplace(cachingStr, std::pair<int32_t, TBonusListPtr>{ cachedLast, ret });
} }
return ret; return ret;
@ -187,8 +186,6 @@ std::shared_ptr<Bonus> CBonusSystemNode::getUpdatedBonus(const std::shared_ptr<B
} }
CBonusSystemNode::CBonusSystemNode(bool isHypotetic): CBonusSystemNode::CBonusSystemNode(bool isHypotetic):
bonuses(true),
exportedBonuses(true),
nodeType(UNKNOWN), nodeType(UNKNOWN),
cachedLast(0), cachedLast(0),
isHypotheticNode(isHypotetic) isHypotheticNode(isHypotetic)
@ -196,8 +193,6 @@ CBonusSystemNode::CBonusSystemNode(bool isHypotetic):
} }
CBonusSystemNode::CBonusSystemNode(ENodeTypes NodeType): CBonusSystemNode::CBonusSystemNode(ENodeTypes NodeType):
bonuses(true),
exportedBonuses(true),
nodeType(NodeType), nodeType(NodeType),
cachedLast(0), cachedLast(0),
isHypotheticNode(false) isHypotheticNode(false)
@ -227,10 +222,11 @@ void CBonusSystemNode::attachTo(CBonusSystemNode & parent)
if(!parent.actsAsBonusSourceOnly()) if(!parent.actsAsBonusSourceOnly())
newRedDescendant(parent); newRedDescendant(parent);
parent.newChildAttached(*this); assert(!vstd::contains(parent.children, this));
parent.children.push_back(this);
} }
CBonusSystemNode::treeHasChanged(); nodeHasChanged();
} }
void CBonusSystemNode::attachToSource(const CBonusSystemNode & parent) void CBonusSystemNode::attachToSource(const CBonusSystemNode & parent)
@ -244,7 +240,7 @@ void CBonusSystemNode::attachToSource(const CBonusSystemNode & parent)
parent.newRedDescendant(*this); parent.newRedDescendant(*this);
} }
CBonusSystemNode::treeHasChanged(); nodeHasChanged();
} }
void CBonusSystemNode::detachFrom(CBonusSystemNode & parent) void CBonusSystemNode::detachFrom(CBonusSystemNode & parent)
@ -271,9 +267,15 @@ void CBonusSystemNode::detachFrom(CBonusSystemNode & parent)
if(!isHypothetic()) if(!isHypothetic())
{ {
parent.childDetached(*this); if(vstd::contains(parent.children, this))
parent.children -= this;
else
{
logBonus->error("Error on Detach. Node %s (nodeType=%d) is not a child of %s (nodeType=%d)"
, nodeShortInfo(), nodeType, parent.nodeShortInfo(), parent.nodeType);
} }
CBonusSystemNode::treeHasChanged(); }
nodeHasChanged();
} }
@ -297,7 +299,7 @@ void CBonusSystemNode::detachFromSource(const CBonusSystemNode & parent)
, nodeShortInfo(), nodeType, parent.nodeShortInfo(), parent.nodeType); , nodeShortInfo(), nodeType, parent.nodeShortInfo(), parent.nodeType);
} }
CBonusSystemNode::treeHasChanged(); nodeHasChanged();
} }
void CBonusSystemNode::removeBonusesRecursive(const CSelector & s) void CBonusSystemNode::removeBonusesRecursive(const CSelector & s)
@ -333,7 +335,6 @@ void CBonusSystemNode::addNewBonus(const std::shared_ptr<Bonus>& b)
assert(!vstd::contains(exportedBonuses, b)); assert(!vstd::contains(exportedBonuses, b));
exportedBonuses.push_back(b); exportedBonuses.push_back(b);
exportBonus(b); exportBonus(b);
CBonusSystemNode::treeHasChanged();
} }
void CBonusSystemNode::accumulateBonus(const std::shared_ptr<Bonus>& b) void CBonusSystemNode::accumulateBonus(const std::shared_ptr<Bonus>& b)
@ -349,10 +350,14 @@ void CBonusSystemNode::removeBonus(const std::shared_ptr<Bonus>& b)
{ {
exportedBonuses -= b; exportedBonuses -= b;
if(b->propagator) if(b->propagator)
{
unpropagateBonus(b); unpropagateBonus(b);
}
else else
{
bonuses -= b; bonuses -= b;
CBonusSystemNode::treeHasChanged(); nodeHasChanged();
}
} }
void CBonusSystemNode::removeBonuses(const CSelector & selector) void CBonusSystemNode::removeBonuses(const CSelector & selector)
@ -385,6 +390,7 @@ void CBonusSystemNode::propagateBonus(const std::shared_ptr<Bonus> & b, const CB
: b; : b;
bonuses.push_back(propagated); bonuses.push_back(propagated);
logBonus->trace("#$# %s #propagated to# %s", propagated->Description(nullptr), nodeName()); logBonus->trace("#$# %s #propagated to# %s", propagated->Description(nullptr), nodeName());
nodeHasChanged();
} }
TNodes lchildren; TNodes lchildren;
@ -402,11 +408,11 @@ void CBonusSystemNode::unpropagateBonus(const std::shared_ptr<Bonus> & b)
else else
logBonus->warn("Attempt to remove #$# %s, which is not propagated to %s", b->Description(nullptr), nodeName()); logBonus->warn("Attempt to remove #$# %s, which is not propagated to %s", b->Description(nullptr), nodeName());
bonuses.remove_if([b](const auto & bonus) bonuses.remove_if([this, b](const auto & bonus)
{ {
if (bonus->propagationUpdater && bonus->propagationUpdater == b->propagationUpdater) if (bonus->propagationUpdater && bonus->propagationUpdater == b->propagationUpdater)
{ {
treeHasChanged(); nodeHasChanged();
return true; return true;
} }
return false; return false;
@ -419,23 +425,6 @@ void CBonusSystemNode::unpropagateBonus(const std::shared_ptr<Bonus> & b)
pname->unpropagateBonus(b); pname->unpropagateBonus(b);
} }
void CBonusSystemNode::newChildAttached(CBonusSystemNode & child)
{
assert(!vstd::contains(children, &child));
children.push_back(&child);
}
void CBonusSystemNode::childDetached(CBonusSystemNode & child)
{
if(vstd::contains(children, &child))
children -= &child;
else
{
logBonus->error("Error on Detach. Node %s (nodeType=%d) is not a child of %s (nodeType=%d)"
, child.nodeShortInfo(), child.nodeType, nodeShortInfo(), nodeType);
}
}
void CBonusSystemNode::detachFromAll() void CBonusSystemNode::detachFromAll()
{ {
while(!parentsToPropagate.empty()) while(!parentsToPropagate.empty())
@ -558,11 +547,14 @@ void CBonusSystemNode::getRedAncestors(TCNodes &out) const
void CBonusSystemNode::exportBonus(const std::shared_ptr<Bonus> & b) void CBonusSystemNode::exportBonus(const std::shared_ptr<Bonus> & b)
{ {
if(b->propagator) if(b->propagator)
{
propagateBonus(b, *this); propagateBonus(b, *this);
}
else else
{
bonuses.push_back(b); bonuses.push_back(b);
nodeHasChanged();
CBonusSystemNode::treeHasChanged(); }
} }
void CBonusSystemNode::exportBonuses() void CBonusSystemNode::exportBonuses()
@ -631,14 +623,27 @@ void CBonusSystemNode::limitBonuses(const BonusList &allBonuses, BonusList &out)
} }
} }
void CBonusSystemNode::treeHasChanged() void CBonusSystemNode::nodeHasChanged()
{ {
treeChanged++; static std::atomic<int32_t> globalCounter = 1;
invalidateChildrenNodes(++globalCounter);
} }
int64_t CBonusSystemNode::getTreeVersion() const void CBonusSystemNode::invalidateChildrenNodes(int32_t changeCounter)
{ {
return treeChanged; if (nodeChanged == changeCounter)
return;
nodeChanged = changeCounter;
for(CBonusSystemNode * child : children)
child->invalidateChildrenNodes(changeCounter);
}
int32_t CBonusSystemNode::getTreeVersion() const
{
return nodeChanged;
} }
VCMI_LIB_NAMESPACE_END VCMI_LIB_NAMESPACE_END

View File

@ -56,15 +56,16 @@ private:
ENodeTypes nodeType; ENodeTypes nodeType;
bool isHypotheticNode; bool isHypotheticNode;
static const bool cachingEnabled;
mutable BonusList cachedBonuses; mutable BonusList cachedBonuses;
mutable int64_t cachedLast; mutable int32_t cachedLast;
static std::atomic<int64_t> treeChanged; std::atomic<int32_t> nodeChanged;
void invalidateChildrenNodes(int32_t changeCounter);
// Setting a value to cachingStr before getting any bonuses caches the result for later requests. // Setting a value to cachingStr before getting any bonuses caches the result for later requests.
// This string needs to be unique, that's why it has to be set in the following manner: // This string needs to be unique, that's why it has to be set in the following manner:
// [property key]_[value] => only for selector // [property key]_[value] => only for selector
using RequestsMap = tbb::concurrent_hash_map<std::string, std::pair<int64_t, TBonusListPtr>, HashStringCompare>; using RequestsMap = tbb::concurrent_hash_map<std::string, std::pair<int32_t, TBonusListPtr>, HashStringCompare>;
mutable RequestsMap cachedRequests; mutable RequestsMap cachedRequests;
mutable std::shared_mutex sync; mutable std::shared_mutex sync;
@ -79,8 +80,6 @@ private:
void getAllParents(TCNodes & out) const; void getAllParents(TCNodes & out) const;
void newChildAttached(CBonusSystemNode & child);
void childDetached(CBonusSystemNode & child);
void propagateBonus(const std::shared_ptr<Bonus> & b, const CBonusSystemNode & source); void propagateBonus(const std::shared_ptr<Bonus> & b, const CBonusSystemNode & source);
void unpropagateBonus(const std::shared_ptr<Bonus> & b); void unpropagateBonus(const std::shared_ptr<Bonus> & b);
bool actsAsBonusSourceOnly() const; bool actsAsBonusSourceOnly() const;
@ -136,9 +135,9 @@ public:
void setNodeType(CBonusSystemNode::ENodeTypes type); void setNodeType(CBonusSystemNode::ENodeTypes type);
const TCNodesVector & getParentNodes() const; const TCNodesVector & getParentNodes() const;
static void treeHasChanged(); void nodeHasChanged();
int64_t getTreeVersion() const override; int32_t getTreeVersion() const override;
virtual PlayerColor getOwner() const virtual PlayerColor getOwner() const
{ {

View File

@ -41,7 +41,7 @@ public:
TConstBonusListPtr getBonusesOfType(BonusType type) const; TConstBonusListPtr getBonusesOfType(BonusType type) const;
TConstBonusListPtr getBonusesOfType(BonusType type, BonusSubtypeID subtype) const; TConstBonusListPtr getBonusesOfType(BonusType type, BonusSubtypeID subtype) const;
virtual int64_t getTreeVersion() const = 0; virtual int32_t getTreeVersion() const = 0;
}; };
VCMI_LIB_NAMESPACE_END VCMI_LIB_NAMESPACE_END

View File

@ -115,7 +115,7 @@ void CArmedInstance::updateMoraleBonusFromArmy()
b->description = bonusDescription; b->description = bonusDescription;
CBonusSystemNode::treeHasChanged(); nodeHasChanged();
//-1 modifier for any Undead unit in army //-1 modifier for any Undead unit in army
auto undeadModifier = getExportedBonusList().getFirst(Selector::source(BonusSource::ARMY, BonusCustomSource::undeadMoraleDebuff)); auto undeadModifier = getExportedBonusList().getFirst(Selector::source(BonusSource::ARMY, BonusCustomSource::undeadMoraleDebuff));

View File

@ -1513,7 +1513,7 @@ void CGHeroInstance::setPrimarySkill(PrimarySkill primarySkill, si64 value, ui8
{ {
skill->val += static_cast<si32>(value); skill->val += static_cast<si32>(value);
} }
CBonusSystemNode::treeHasChanged(); nodeHasChanged();
} }
else if(primarySkill == PrimarySkill::EXPERIENCE) else if(primarySkill == PrimarySkill::EXPERIENCE)
{ {
@ -1550,7 +1550,7 @@ void CGHeroInstance::levelUp(const std::vector<SecondarySkill> & skills)
} }
//update specialty and other bonuses that scale with level //update specialty and other bonuses that scale with level
treeHasChanged(); nodeHasChanged();
} }
void CGHeroInstance::levelUpAutomatically(vstd::RNG & rand) void CGHeroInstance::levelUpAutomatically(vstd::RNG & rand)

View File

@ -730,7 +730,7 @@ void CGTownInstance::updateMoraleBonusFromArmy()
if (garrisonHero) if (garrisonHero)
{ {
b->val = 0; b->val = 0;
CBonusSystemNode::treeHasChanged(); nodeHasChanged();
} }
else else
CArmedInstance::updateMoraleBonusFromArmy(); CArmedInstance::updateMoraleBonusFromArmy();

View File

@ -901,6 +901,7 @@ void SetCommanderProperty::applyGs(CGameState *gs)
break; break;
case EXPERIENCE: case EXPERIENCE:
commander->giveStackExp(amount); //TODO: allow setting exp for stacks via netpacks commander->giveStackExp(amount); //TODO: allow setting exp for stacks via netpacks
commander->nodeHasChanged();
break; break;
} }
} }
@ -1708,7 +1709,9 @@ void RebalanceStacks::applyGs(CGameState *gs)
} }
} }
CBonusSystemNode::treeHasChanged(); srcObj->nodeHasChanged();
if (srcObj != dstObj)
dstObj->nodeHasChanged();
} }
void BulkRebalanceStacks::applyGs(CGameState *gs) void BulkRebalanceStacks::applyGs(CGameState *gs)
@ -2147,10 +2150,16 @@ void BattleResultAccepted::applyGs(CGameState *gs)
if(gs->getSettings().getBoolean(EGameSettings::MODULE_STACK_EXPERIENCE)) if(gs->getSettings().getBoolean(EGameSettings::MODULE_STACK_EXPERIENCE))
{ {
if(heroResult[BattleSide::ATTACKER].army) if(heroResult[BattleSide::ATTACKER].army)
{
heroResult[BattleSide::ATTACKER].army->giveStackExp(heroResult[BattleSide::ATTACKER].exp); heroResult[BattleSide::ATTACKER].army->giveStackExp(heroResult[BattleSide::ATTACKER].exp);
heroResult[BattleSide::ATTACKER].army->nodeHasChanged();
}
if(heroResult[BattleSide::DEFENDER].army) if(heroResult[BattleSide::DEFENDER].army)
{
heroResult[BattleSide::DEFENDER].army->giveStackExp(heroResult[BattleSide::DEFENDER].exp); heroResult[BattleSide::DEFENDER].army->giveStackExp(heroResult[BattleSide::DEFENDER].exp);
CBonusSystemNode::treeHasChanged(); heroResult[BattleSide::DEFENDER].army->nodeHasChanged();
}
} }
auto currentBattle = boost::range::find_if(gs->currentBattles, [&](const auto & battle) auto currentBattle = boost::range::find_if(gs->currentBattles, [&](const auto & battle)

View File

@ -378,7 +378,6 @@ void CGameHandler::giveExperience(const CGHeroInstance * hero, TExpType amountTo
scp.which = SetCommanderProperty::EXPERIENCE; scp.which = SetCommanderProperty::EXPERIENCE;
scp.amount = amountToGain; scp.amount = amountToGain;
sendAndApply(scp); sendAndApply(scp);
CBonusSystemNode::treeHasChanged();
} }
expGiven(hero); expGiven(hero);

View File

@ -38,7 +38,7 @@ TConstBonusListPtr BonusBearerMock::getAllBonuses(const CSelector & selector, co
return ret; return ret;
} }
int64_t BonusBearerMock::getTreeVersion() const int32_t BonusBearerMock::getTreeVersion() const
{ {
return treeVersion; return treeVersion;
} }

View File

@ -25,10 +25,10 @@ public:
TConstBonusListPtr getAllBonuses(const CSelector & selector, const CSelector & limit, const std::string & cachingStr = "") const override; TConstBonusListPtr getAllBonuses(const CSelector & selector, const CSelector & limit, const std::string & cachingStr = "") const override;
int64_t getTreeVersion() const override; int32_t getTreeVersion() const override;
private: private:
mutable BonusList bonuses; mutable BonusList bonuses;
mutable int64_t cachedLast; mutable int32_t cachedLast;
int32_t treeVersion; int32_t treeVersion;
}; };

View File

@ -16,7 +16,7 @@ class UnitMock : public battle::Unit
{ {
public: public:
MOCK_CONST_METHOD3(getAllBonuses, TConstBonusListPtr(const CSelector &, const CSelector &, const std::string &)); MOCK_CONST_METHOD3(getAllBonuses, TConstBonusListPtr(const CSelector &, const CSelector &, const std::string &));
MOCK_CONST_METHOD0(getTreeVersion, int64_t()); MOCK_CONST_METHOD0(getTreeVersion, int32_t());
MOCK_CONST_METHOD0(getCasterUnitId, int32_t()); MOCK_CONST_METHOD0(getCasterUnitId, int32_t());
MOCK_CONST_METHOD2(getSpellSchoolLevel, int32_t(const spells::Spell *, SpellSchool *)); MOCK_CONST_METHOD2(getSpellSchoolLevel, int32_t(const spells::Spell *, SpellSchool *));