diff --git a/AI/BattleAI/StackWithBonuses.cpp b/AI/BattleAI/StackWithBonuses.cpp index 149eb33f5..a634e6835 100644 --- a/AI/BattleAI/StackWithBonuses.cpp +++ b/AI/BattleAI/StackWithBonuses.cpp @@ -170,7 +170,7 @@ TConstBonusListPtr StackWithBonuses::getAllBonuses(const CSelector & selector, c return ret; } -int64_t StackWithBonuses::getTreeVersion() const +int32_t StackWithBonuses::getTreeVersion() const { auto result = owner->getTreeVersion(); @@ -485,7 +485,7 @@ BattleLayout HypotheticBattle::getLayout() const return subject->getBattle()->getLayout(); } -int64_t HypotheticBattle::getTreeVersion() const +int32_t HypotheticBattle::getTreeVersion() const { return getBonusBearer()->getTreeVersion() + bonusTreeVersion; } diff --git a/AI/BattleAI/StackWithBonuses.h b/AI/BattleAI/StackWithBonuses.h index 3a34cc761..bc0bc8672 100644 --- a/AI/BattleAI/StackWithBonuses.h +++ b/AI/BattleAI/StackWithBonuses.h @@ -93,7 +93,7 @@ public: TConstBonusListPtr getAllBonuses(const CSelector & selector, const CSelector & limit, const std::string & cachingStr = "") const override; - int64_t getTreeVersion() const override; + int32_t getTreeVersion() const override; void addUnitBonus(const std::vector<Bonus> & bonus); void updateUnitBonus(const std::vector<Bonus> & bonus); @@ -162,7 +162,7 @@ public: int3 getLocation() const override; BattleLayout getLayout() const override; - int64_t getTreeVersion() const; + int32_t getTreeVersion() const; void makeWait(const battle::Unit * activeStack); diff --git a/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp b/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp index f22c46c0d..17f64f324 100644 --- a/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp +++ b/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp @@ -20,7 +20,9 @@ void BuildAnalyzer::updateTownDwellings(TownDevelopmentInfo & developmentInfo) { for(int level = 0; level < developmentInfo.town->getTown()->creatures.size(); level++) { +#if NKAI_TRACE_LEVEL >= 1 logAi->trace("Checking dwelling level %d", level); +#endif std::vector<BuildingID> dwellingsInTown; 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)) 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()); - +#endif developmentInfos.push_back(TownDevelopmentInfo(town)); TownDevelopmentInfo & developmentInfo = developmentInfos.back(); @@ -161,10 +163,10 @@ void BuildAnalyzer::update() } armyCost += developmentInfo.armyCost; +#if NKAI_TRACE_LEVEL >= 1 for(auto bi : developmentInfo.toBuild) - { logAi->trace("Building preferences %s", bi.toString()); - } +#endif } 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); +#if NKAI_TRACE_LEVEL >= 1 logAi->trace("Gold pressure: %f", goldPressure); +#endif } void BuildAnalyzer::reset() @@ -268,12 +272,15 @@ BuildingInfo BuildAnalyzer::getBuildingOrPrerequisite( 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()); +#endif } else if(missingBuildings[0] != toBuild) { +#if NKAI_TRACE_LEVEL >= 1 logAi->trace("cant build %d. Need %d", toBuild.getNum(), missingBuildings[0].num); - +#endif BuildingInfo prerequisite = getBuildingOrPrerequisite(town, missingBuildings[0], excludeDwellingDependencies); prerequisite.buildCostWithPrerequisites += info.buildCost; @@ -298,19 +305,24 @@ BuildingInfo BuildAnalyzer::getBuildingOrPrerequisite( } else { +#if NKAI_TRACE_LEVEL >= 1 logAi->trace("Cant build. The building requires itself as prerequisite"); - +#endif return info; } } else { +#if NKAI_TRACE_LEVEL >= 1 logAi->trace("Cant build. Reason: %d", static_cast<int>(canBuild)); +#endif } } else { +#if NKAI_TRACE_LEVEL >= 1 logAi->trace("Dwelling %d exists", toBuild.getNum()); +#endif info.exists = true; } diff --git a/AI/Nullkiller/Behaviors/DefenceBehavior.cpp b/AI/Nullkiller/Behaviors/DefenceBehavior.cpp index a76008523..2363acd62 100644 --- a/AI/Nullkiller/Behaviors/DefenceBehavior.cpp +++ b/AI/Nullkiller/Behaviors/DefenceBehavior.cpp @@ -151,7 +151,9 @@ bool handleGarrisonHeroFromPreviousTurn(const CGTownInstance * town, Goals::TGoa 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()); +#endif auto threatNode = ai->dangerHitMap->getObjectThreat(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 NKAI_TRACE_LEVEL >= 1 logAi->trace("No threat found for town %s", town->getNameTranslated()); - +#endif return; } @@ -173,7 +176,9 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta if(reinforcement) { +#if NKAI_TRACE_LEVEL >= 1 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))); } @@ -181,13 +186,14 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta for(auto & threat : threats) { +#if NKAI_TRACE_LEVEL >= 1 logAi->trace( "Town %s has threat %lld in %s turns, hero: %s", town->getNameTranslated(), threat.danger, std::to_string(threat.turn), threat.hero ? threat.hero->getNameTranslated() : std::string("<no hero>")); - +#endif handleCounterAttack(town, threat, threatNode.maximumDanger, ai, tasks); if(isThreatUnderControl(town, threat, ai, paths)) @@ -199,7 +205,9 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta if(paths.empty()) { +#if NKAI_TRACE_LEVEL >= 1 logAi->trace("No ways to defend town %s", town->getNameTranslated()); +#endif continue; } diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index fe08da693..0dde01d14 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -1201,7 +1201,9 @@ public: if (bi.id == BuildingID::MARKETPLACE || bi.dailyIncome[EGameResID::WOOD] > 0) 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()); +#endif if(bi.creatureID != CreatureID::NONE) { diff --git a/lib/CArtHandler.cpp b/lib/CArtHandler.cpp index 0232109c5..a7ee512fb 100644 --- a/lib/CArtHandler.cpp +++ b/lib/CArtHandler.cpp @@ -703,8 +703,9 @@ void CArtHandler::afterLoadFinalization() assert(bonus->source == BonusSource::ARTIFACT); bonus->sid = BonusSourceID(art->id); } + art->nodeHasChanged(); } - CBonusSystemNode::treeHasChanged(); + } CArtifactInstance * CArtifactSet::getArt(const ArtifactPosition & pos, bool excludeLocked) const diff --git a/lib/CCreatureSet.cpp b/lib/CCreatureSet.cpp index 46fa481cf..99b1bc14f 100644 --- a/lib/CCreatureSet.cpp +++ b/lib/CCreatureSet.cpp @@ -670,14 +670,18 @@ void CCreatureSet::serializeJson(JsonSerializeFormat & handler, const std::strin } } -CStackInstance::CStackInstance() - : armyObj(_armyObj) +CStackInstance::CStackInstance(bool isHypothetic) + : CBonusSystemNode(isHypothetic), + armyObj(_armyObj), + nativeTerrain(this, Selector::type()(BonusType::TERRAIN_NATIVE)), + initiative(this, Selector::type()(BonusType::STACKS_SPEED)) + { init(); } -CStackInstance::CStackInstance(const CreatureID & id, TQuantity Count, bool isHypothetic): - CBonusSystemNode(isHypothetic), armyObj(_armyObj) +CStackInstance::CStackInstance(const CreatureID & id, TQuantity Count, bool isHypothetic) + : CStackInstance(false) { init(); setType(id); @@ -685,7 +689,7 @@ CStackInstance::CStackInstance(const CreatureID & id, TQuantity Count, bool isHy } CStackInstance::CStackInstance(const CCreature *cre, TQuantity Count, bool isHypothetic) - : CBonusSystemNode(isHypothetic), armyObj(_armyObj) + : CStackInstance(false) { init(); setType(cre); @@ -834,6 +838,22 @@ PlayerColor CStackInstance::getOwner() const 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() { const CArmedInstance *armyBackup = _armyObj; diff --git a/lib/CCreatureSet.h b/lib/CCreatureSet.h index e6b80d4d2..7e13e3f15 100644 --- a/lib/CCreatureSet.h +++ b/lib/CCreatureSet.h @@ -10,6 +10,7 @@ #pragma once #include "bonuses/Bonus.h" +#include "bonuses/BonusCache.h" #include "bonuses/CBonusSystemNode.h" #include "serializer/Serializeable.h" #include "GameConstants.h" @@ -71,6 +72,9 @@ public: class DLL_LINKAGE CStackInstance : public CBonusSystemNode, public CStackBasicDescriptor, public CArtifactSet, public ACreature { + BonusValueCache nativeTerrain; + BonusValueCache initiative; + protected: 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 std::string getName() const; //plural or singular virtual void init(); - CStackInstance(); + CStackInstance(bool isHypothetic = false); CStackInstance(const CreatureID & id, TQuantity count, bool isHypothetic = false); CStackInstance(const CCreature *cre, TQuantity count, bool isHypothetic = false); virtual ~CStackInstance() = default; @@ -135,6 +139,9 @@ public: std::string nodeName() const override; //from CBonusSystemnode void deserializationFix(); PlayerColor getOwner() const override; + + int32_t getInitiative(int turn = 0) const final; + TerrainId getNativeTerrain() const final; }; class DLL_LINKAGE CCommanderInstance : public CStackInstance diff --git a/lib/battle/BattleInfo.cpp b/lib/battle/BattleInfo.cpp index 95670694a..add6bbc53 100644 --- a/lib/battle/BattleInfo.cpp +++ b/lib/battle/BattleInfo.cpp @@ -693,7 +693,7 @@ void BattleInfo::moveUnit(uint32_t id, BattleHex destination) sta->position = destination; //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 - CBonusSystemNode::treeHasChanged(); + nodeHasChanged(); } 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); } } - CBonusSystemNode::treeHasChanged(); + sta->nodeHasChanged(); } } diff --git a/lib/battle/CUnitState.cpp b/lib/battle/CUnitState.cpp index d357bed30..9403c2692 100644 --- a/lib/battle/CUnitState.cpp +++ b/lib/battle/CUnitState.cpp @@ -953,7 +953,7 @@ TConstBonusListPtr CUnitStateDetached::getAllBonuses(const CSelector & selector, return bonus->getAllBonuses(selector, limit, cachingStr); } -int64_t CUnitStateDetached::getTreeVersion() const +int32_t CUnitStateDetached::getTreeVersion() const { return bonus->getTreeVersion(); } diff --git a/lib/battle/CUnitState.h b/lib/battle/CUnitState.h index 110773023..e67d55016 100644 --- a/lib/battle/CUnitState.h +++ b/lib/battle/CUnitState.h @@ -279,7 +279,7 @@ public: 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; BattleSide unitSide() const override; diff --git a/lib/bonuses/BonusCache.h b/lib/bonuses/BonusCache.h index 34a07a304..1bf6b14c1 100644 --- a/lib/bonuses/BonusCache.h +++ b/lib/bonuses/BonusCache.h @@ -32,8 +32,8 @@ protected: struct BonusCacheEntry { - std::atomic<int64_t> version = 0; - std::atomic<int64_t> value = 0; + std::atomic<int32_t> version = 0; + std::atomic<int32_t> value = 0; BonusCacheEntry() = default; BonusCacheEntry(const BonusCacheEntry & other) @@ -152,7 +152,7 @@ private: class PrimarySkillsCache { 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; void update() const; @@ -166,7 +166,7 @@ public: class MagicSchoolMasteryCache { 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; void update() const; @@ -184,7 +184,7 @@ class BonusCachePerTurn : public BonusCacheBase const CSelector selector; mutable TConstBonusListPtr bonusList; mutable std::mutex bonusListMutex; - mutable std::atomic<int64_t> bonusListVersion = 0; + mutable std::atomic<int32_t> bonusListVersion = 0; mutable std::array<BonusCacheEntry, cachedTurns> cache; const BonusCacheMode mode; diff --git a/lib/bonuses/BonusList.cpp b/lib/bonuses/BonusList.cpp index 5e4048573..09cde5566 100644 --- a/lib/bonuses/BonusList.cpp +++ b/lib/bonuses/BonusList.cpp @@ -14,36 +14,6 @@ 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() { 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) { bonuses.push_back(x); - changed(); } BonusList::TInternalContainer::iterator BonusList::erase(const int position) { - changed(); return bonuses.erase(bonuses.begin() + position); } void BonusList::clear() { bonuses.clear(); - changed(); } 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()) return false; bonuses.erase(itr); - changed(); return true; } void BonusList::resize(BonusList::TInternalContainer::size_type sz, const std::shared_ptr<Bonus> & c) { bonuses.resize(sz, c); - changed(); } void BonusList::insert(BonusList::TInternalContainer::iterator position, BonusList::TInternalContainer::size_type n, const std::shared_ptr<Bonus> & x) { bonuses.insert(position, n, x); - changed(); } DLL_LINKAGE std::ostream & operator<<(std::ostream &out, const BonusList &bonusList) diff --git a/lib/bonuses/BonusList.h b/lib/bonuses/BonusList.h index cc266494f..d50e7c9a1 100644 --- a/lib/bonuses/BonusList.h +++ b/lib/bonuses/BonusList.h @@ -21,8 +21,6 @@ public: private: TInternalContainer bonuses; - bool belongsToTree; - void changed() const; public: using const_reference = TInternalContainer::const_reference; @@ -31,10 +29,7 @@ public: using const_iterator = TInternalContainer::const_iterator; using iterator = TInternalContainer::iterator; - BonusList(bool BelongsToTree = false); - BonusList(const BonusList &bonusList); - BonusList(BonusList && other) noexcept; - BonusList& operator=(const BonusList &bonusList); + BonusList() = default; // wrapper functions of the STL vector container TInternalContainer::size_type size() const { return bonuses.size(); } diff --git a/lib/bonuses/CBonusSystemNode.cpp b/lib/bonuses/CBonusSystemNode.cpp index d155edcb8..9f18a1cb5 100644 --- a/lib/bonuses/CBonusSystemNode.cpp +++ b/lib/bonuses/CBonusSystemNode.cpp @@ -17,8 +17,7 @@ VCMI_LIB_NAMESPACE_BEGIN -std::atomic<int64_t> CBonusSystemNode::treeChanged(1); -constexpr bool CBonusSystemNode::cachingEnabled = true; +constexpr bool cachingEnabled = true; 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 { - 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 // 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; @@ -114,7 +113,7 @@ TConstBonusListPtr CBonusSystemNode::getAllBonuses(const CSelector &selector, co //Perform bonus selection 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 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 // cache all bonus objects. Selector objects doesn't matter. 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. cachedBonuses.getBonuses(*ret, selector, limit); @@ -140,7 +139,7 @@ TConstBonusListPtr CBonusSystemNode::getAllBonuses(const CSelector &selector, co getAllBonusesRec(allBonuses, Selector::all); limitBonuses(allBonuses, cachedBonuses); cachedBonuses.stackBonuses(); - cachedLast = treeChanged; + cachedLast = nodeChanged; cachedBonuses.getBonuses(*ret, selector, limit); } } @@ -155,7 +154,7 @@ TConstBonusListPtr CBonusSystemNode::getAllBonuses(const CSelector &selector, co accessor->second.first = cachedLast; } else - cachedRequests.emplace(cachingStr, std::pair<int64_t, TBonusListPtr>{ cachedLast, ret }); + cachedRequests.emplace(cachingStr, std::pair<int32_t, TBonusListPtr>{ cachedLast, ret }); } return ret; @@ -187,8 +186,6 @@ std::shared_ptr<Bonus> CBonusSystemNode::getUpdatedBonus(const std::shared_ptr<B } CBonusSystemNode::CBonusSystemNode(bool isHypotetic): - bonuses(true), - exportedBonuses(true), nodeType(UNKNOWN), cachedLast(0), isHypotheticNode(isHypotetic) @@ -196,8 +193,6 @@ CBonusSystemNode::CBonusSystemNode(bool isHypotetic): } CBonusSystemNode::CBonusSystemNode(ENodeTypes NodeType): - bonuses(true), - exportedBonuses(true), nodeType(NodeType), cachedLast(0), isHypotheticNode(false) @@ -227,10 +222,11 @@ void CBonusSystemNode::attachTo(CBonusSystemNode & parent) if(!parent.actsAsBonusSourceOnly()) 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) @@ -244,7 +240,7 @@ void CBonusSystemNode::attachToSource(const CBonusSystemNode & parent) parent.newRedDescendant(*this); } - CBonusSystemNode::treeHasChanged(); + nodeHasChanged(); } void CBonusSystemNode::detachFrom(CBonusSystemNode & parent) @@ -271,9 +267,15 @@ void CBonusSystemNode::detachFrom(CBonusSystemNode & parent) 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); } - CBonusSystemNode::treeHasChanged(); + nodeHasChanged(); } void CBonusSystemNode::removeBonusesRecursive(const CSelector & s) @@ -333,7 +335,6 @@ void CBonusSystemNode::addNewBonus(const std::shared_ptr<Bonus>& b) assert(!vstd::contains(exportedBonuses, b)); exportedBonuses.push_back(b); exportBonus(b); - CBonusSystemNode::treeHasChanged(); } void CBonusSystemNode::accumulateBonus(const std::shared_ptr<Bonus>& b) @@ -349,10 +350,14 @@ void CBonusSystemNode::removeBonus(const std::shared_ptr<Bonus>& b) { exportedBonuses -= b; if(b->propagator) + { unpropagateBonus(b); + } else + { bonuses -= b; - CBonusSystemNode::treeHasChanged(); + nodeHasChanged(); + } } void CBonusSystemNode::removeBonuses(const CSelector & selector) @@ -385,6 +390,7 @@ void CBonusSystemNode::propagateBonus(const std::shared_ptr<Bonus> & b, const CB : b; bonuses.push_back(propagated); logBonus->trace("#$# %s #propagated to# %s", propagated->Description(nullptr), nodeName()); + nodeHasChanged(); } TNodes lchildren; @@ -402,11 +408,11 @@ void CBonusSystemNode::unpropagateBonus(const std::shared_ptr<Bonus> & b) else 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) { - treeHasChanged(); + nodeHasChanged(); return true; } return false; @@ -419,23 +425,6 @@ void CBonusSystemNode::unpropagateBonus(const std::shared_ptr<Bonus> & 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() { while(!parentsToPropagate.empty()) @@ -558,11 +547,14 @@ void CBonusSystemNode::getRedAncestors(TCNodes &out) const void CBonusSystemNode::exportBonus(const std::shared_ptr<Bonus> & b) { if(b->propagator) + { propagateBonus(b, *this); + } else + { bonuses.push_back(b); - - CBonusSystemNode::treeHasChanged(); + nodeHasChanged(); + } } 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 diff --git a/lib/bonuses/CBonusSystemNode.h b/lib/bonuses/CBonusSystemNode.h index 5baad0e96..c1503aa81 100644 --- a/lib/bonuses/CBonusSystemNode.h +++ b/lib/bonuses/CBonusSystemNode.h @@ -56,15 +56,16 @@ private: ENodeTypes nodeType; bool isHypotheticNode; - static const bool cachingEnabled; mutable BonusList cachedBonuses; - mutable int64_t cachedLast; - static std::atomic<int64_t> treeChanged; + mutable int32_t cachedLast; + 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. // This string needs to be unique, that's why it has to be set in the following manner: // [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 std::shared_mutex sync; @@ -79,8 +80,6 @@ private: 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 unpropagateBonus(const std::shared_ptr<Bonus> & b); bool actsAsBonusSourceOnly() const; @@ -136,9 +135,9 @@ public: void setNodeType(CBonusSystemNode::ENodeTypes type); const TCNodesVector & getParentNodes() const; - static void treeHasChanged(); + void nodeHasChanged(); - int64_t getTreeVersion() const override; + int32_t getTreeVersion() const override; virtual PlayerColor getOwner() const { diff --git a/lib/bonuses/IBonusBearer.h b/lib/bonuses/IBonusBearer.h index b27ca7f0d..a4d4fb25d 100644 --- a/lib/bonuses/IBonusBearer.h +++ b/lib/bonuses/IBonusBearer.h @@ -41,7 +41,7 @@ public: TConstBonusListPtr getBonusesOfType(BonusType type) const; TConstBonusListPtr getBonusesOfType(BonusType type, BonusSubtypeID subtype) const; - virtual int64_t getTreeVersion() const = 0; + virtual int32_t getTreeVersion() const = 0; }; VCMI_LIB_NAMESPACE_END diff --git a/lib/mapObjects/CArmedInstance.cpp b/lib/mapObjects/CArmedInstance.cpp index eddaf2d95..3e3decf79 100644 --- a/lib/mapObjects/CArmedInstance.cpp +++ b/lib/mapObjects/CArmedInstance.cpp @@ -115,7 +115,7 @@ void CArmedInstance::updateMoraleBonusFromArmy() b->description = bonusDescription; - CBonusSystemNode::treeHasChanged(); + nodeHasChanged(); //-1 modifier for any Undead unit in army auto undeadModifier = getExportedBonusList().getFirst(Selector::source(BonusSource::ARMY, BonusCustomSource::undeadMoraleDebuff)); diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index 7027cd9dc..7a52492f4 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -1513,7 +1513,7 @@ void CGHeroInstance::setPrimarySkill(PrimarySkill primarySkill, si64 value, ui8 { skill->val += static_cast<si32>(value); } - CBonusSystemNode::treeHasChanged(); + nodeHasChanged(); } 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 - treeHasChanged(); + nodeHasChanged(); } void CGHeroInstance::levelUpAutomatically(vstd::RNG & rand) diff --git a/lib/mapObjects/CGTownInstance.cpp b/lib/mapObjects/CGTownInstance.cpp index 5bf021a54..038c7cf24 100644 --- a/lib/mapObjects/CGTownInstance.cpp +++ b/lib/mapObjects/CGTownInstance.cpp @@ -730,7 +730,7 @@ void CGTownInstance::updateMoraleBonusFromArmy() if (garrisonHero) { b->val = 0; - CBonusSystemNode::treeHasChanged(); + nodeHasChanged(); } else CArmedInstance::updateMoraleBonusFromArmy(); diff --git a/lib/networkPacks/NetPacksLib.cpp b/lib/networkPacks/NetPacksLib.cpp index 4bfc8c80e..44ce5dc63 100644 --- a/lib/networkPacks/NetPacksLib.cpp +++ b/lib/networkPacks/NetPacksLib.cpp @@ -901,6 +901,7 @@ void SetCommanderProperty::applyGs(CGameState *gs) break; case EXPERIENCE: commander->giveStackExp(amount); //TODO: allow setting exp for stacks via netpacks + commander->nodeHasChanged(); break; } } @@ -1708,7 +1709,9 @@ void RebalanceStacks::applyGs(CGameState *gs) } } - CBonusSystemNode::treeHasChanged(); + srcObj->nodeHasChanged(); + if (srcObj != dstObj) + dstObj->nodeHasChanged(); } void BulkRebalanceStacks::applyGs(CGameState *gs) @@ -2147,10 +2150,16 @@ void BattleResultAccepted::applyGs(CGameState *gs) if(gs->getSettings().getBoolean(EGameSettings::MODULE_STACK_EXPERIENCE)) { if(heroResult[BattleSide::ATTACKER].army) + { heroResult[BattleSide::ATTACKER].army->giveStackExp(heroResult[BattleSide::ATTACKER].exp); + heroResult[BattleSide::ATTACKER].army->nodeHasChanged(); + } if(heroResult[BattleSide::DEFENDER].army) + { 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) diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 839636f1d..25e609187 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -378,7 +378,6 @@ void CGameHandler::giveExperience(const CGHeroInstance * hero, TExpType amountTo scp.which = SetCommanderProperty::EXPERIENCE; scp.amount = amountToGain; sendAndApply(scp); - CBonusSystemNode::treeHasChanged(); } expGiven(hero); diff --git a/test/mock/mock_BonusBearer.cpp b/test/mock/mock_BonusBearer.cpp index 2496fb74b..c97c0873f 100644 --- a/test/mock/mock_BonusBearer.cpp +++ b/test/mock/mock_BonusBearer.cpp @@ -38,7 +38,7 @@ TConstBonusListPtr BonusBearerMock::getAllBonuses(const CSelector & selector, co return ret; } -int64_t BonusBearerMock::getTreeVersion() const +int32_t BonusBearerMock::getTreeVersion() const { return treeVersion; } diff --git a/test/mock/mock_BonusBearer.h b/test/mock/mock_BonusBearer.h index 0808bd4ce..c4406c9e9 100644 --- a/test/mock/mock_BonusBearer.h +++ b/test/mock/mock_BonusBearer.h @@ -25,10 +25,10 @@ public: 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: mutable BonusList bonuses; - mutable int64_t cachedLast; + mutable int32_t cachedLast; int32_t treeVersion; }; diff --git a/test/mock/mock_battle_Unit.h b/test/mock/mock_battle_Unit.h index ffe99b877..da6366adb 100644 --- a/test/mock/mock_battle_Unit.h +++ b/test/mock/mock_battle_Unit.h @@ -16,7 +16,7 @@ class UnitMock : public battle::Unit { public: 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_METHOD2(getSpellSchoolLevel, int32_t(const spells::Spell *, SpellSchool *));