diff --git a/include/vcmi/Creature.h b/include/vcmi/Creature.h index a1334652d..ad74e0cab 100644 --- a/include/vcmi/Creature.h +++ b/include/vcmi/Creature.h @@ -23,8 +23,8 @@ class DLL_LINKAGE ACreature: public AFactionMember { public: bool isLiving() const; //non-undead, non-non living or alive - ui32 getMovementRange(int turn) const; //get speed (in moving tiles) of creature with all modificators - ui32 getMovementRange() const; //get speed (in moving tiles) of creature with all modificators + virtual ui32 getMovementRange(int turn) const; //get speed (in moving tiles) of creature with all modificators + virtual ui32 getMovementRange() const; //get speed (in moving tiles) of creature with all modificators virtual ui32 getMaxHealth() const; //get max HP of stack with all modifiers }; diff --git a/lib/battle/CUnitState.cpp b/lib/battle/CUnitState.cpp index 53beb4aac..b09fea2e6 100644 --- a/lib/battle/CUnitState.cpp +++ b/lib/battle/CUnitState.cpp @@ -333,6 +333,8 @@ CUnitState::CUnitState(): counterAttacks(this), health(this), shots(this), + stackSpeedPerTurn(this, Selector::type()(BonusType::STACKS_SPEED), BonusCacheMode::VALUE), + immobilizedPerTurn(this, Selector::type()(BonusType::SIEGE_WEAPON).Or(Selector::type()(BonusType::BIND_EFFECT)), BonusCacheMode::PRESENCE), bonusCache(this, generateBonusSelectors()), cloneLifetimeMarker(this, Selector::type()(BonusType::NONE).And(Selector::source(BonusSource::SPELL_EFFECT, BonusSourceID(SpellID(SpellID::CLONE)))), "CUnitState::cloneLifetimeMarker"), cloneID(-1) @@ -573,11 +575,20 @@ void CUnitState::setPosition(BattleHex hex) int32_t CUnitState::getInitiative(int turn) const { - if (turn == 0) - return valOfBonuses(BonusType::STACKS_SPEED); + return stackSpeedPerTurn.getValue(turn); +} - std::string cachingStr = "type_STACKS_SPEED_turns_" + std::to_string(turn); - return valOfBonuses(Selector::type()(BonusType::STACKS_SPEED).And(Selector::turns(turn)), cachingStr); +ui32 CUnitState::getMovementRange(int turn) const +{ + if (immobilizedPerTurn.getValue(0) != 0) + return 0; + + return stackSpeedPerTurn.getValue(0); +} + +ui32 CUnitState::getMovementRange() const +{ + return getMovementRange(0); } uint8_t CUnitState::getRangedFullDamageDistance() const diff --git a/lib/battle/CUnitState.h b/lib/battle/CUnitState.h index f43474209..9c1a5649c 100644 --- a/lib/battle/CUnitState.h +++ b/lib/battle/CUnitState.h @@ -248,6 +248,9 @@ public: uint8_t getRangedFullDamageDistance() const; uint8_t getShootingRangeDistance() const; + ui32 getMovementRange(int turn) const override; + ui32 getMovementRange() const override; + bool canMove(int turn = 0) const override; bool defended(int turn = 0) const override; bool moved(int turn = 0) const override; @@ -293,6 +296,8 @@ private: const IUnitEnvironment * env; + BonusCachePerTurn immobilizedPerTurn; + BonusCachePerTurn stackSpeedPerTurn; UnitBonusValuesProxy bonusCache; CCheckProxy cloneLifetimeMarker; diff --git a/lib/bonuses/BonusCache.cpp b/lib/bonuses/BonusCache.cpp index 9dd594efb..cb4863c4c 100644 --- a/lib/bonuses/BonusCache.cpp +++ b/lib/bonuses/BonusCache.cpp @@ -79,3 +79,59 @@ const std::array, 4> & PrimarySkillsCache::getSkills() cons update(); return skills; } + +int BonusCachePerTurn::getValueUncached(int turns) const +{ + std::lock_guard lock(bonusListMutex); + + int nodeTreeVersion = target->getTreeVersion(); + + if (bonusListVersion != nodeTreeVersion) + { + bonusList = target->getBonuses(selector); + bonusListVersion = nodeTreeVersion; + } + + if (mode == BonusCacheMode::VALUE) + { + if (turns != 0) + return bonusList->valOfBonuses(Selector::turns(turns)); + else + return bonusList->totalValue(); + } + else + { + if (turns != 0) + return bonusList->getFirst(Selector::turns(turns)) != nullptr; + else + return !bonusList->empty(); + } +} + +int BonusCachePerTurn::getValue(int turns) const +{ + int nodeTreeVersion = target->getTreeVersion(); + + if (turns < cachedTurns) + { + auto & entry = cache[turns]; + if (entry.version == nodeTreeVersion) + { + // best case: value is in cache and up-to-date + return entry.value; + } + else + { + // else - compute value and update it in the cache + int newValue = getValueUncached(turns); + entry.value = newValue; + entry.version = nodeTreeVersion; + return newValue; + } + } + else + { + // non-cacheable value - compute and return (should be 0 / close to 0 calls) + return getValueUncached(turns); + } +} diff --git a/lib/bonuses/BonusCache.h b/lib/bonuses/BonusCache.h index 29ac26d39..8f247b8f8 100644 --- a/lib/bonuses/BonusCache.h +++ b/lib/bonuses/BonusCache.h @@ -12,12 +12,18 @@ #include "BonusSelector.h" +enum class BonusCacheMode +{ + VALUE, // total value of bonus will be cached + PRESENCE, // presence of bonus will be cached +}; + /// Internal base class with no own cache class BonusCacheBase { +protected: const IBonusBearer * target; -protected: explicit BonusCacheBase(const IBonusBearer * target): target(target) {} @@ -79,3 +85,26 @@ public: const std::array, 4> & getSkills() const; }; + +/// Cache that tracks values for different values of bonus duration +class BonusCachePerTurn : public BonusCacheBase +{ + static constexpr int cachedTurns = 8; + + const CSelector selector; + mutable TConstBonusListPtr bonusList; + mutable std::mutex bonusListMutex; + mutable std::atomic bonusListVersion = 0; + mutable std::array cache; + const BonusCacheMode mode; + + int getValueUncached(int turns) const; +public: + BonusCachePerTurn(const IBonusBearer * target, const CSelector & selector, BonusCacheMode mode) + : BonusCacheBase(target) + , selector(selector) + , mode(mode) + {} + + int getValue(int turns) const; +};