1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-01-12 02:28:11 +02:00

Merge pull request #4220 from IvanSavenko/ai_optimize

[1.5.4] AI optimizations
This commit is contained in:
Ivan Savenko 2024-07-05 15:45:49 +03:00 committed by GitHub
commit 11a3da3f4f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 140 additions and 119 deletions

View File

@ -649,7 +649,7 @@ void AIGateway::showBlockingDialog(const std::string & text, const std::vector<C
auto ratio = static_cast<float>(danger) / hero->getTotalStrength(); auto ratio = static_cast<float>(danger) / hero->getTotalStrength();
answer = topObj->id == goalObjectID; // no if we do not aim to visit this object answer = topObj->id == goalObjectID; // no if we do not aim to visit this object
logAi->trace("Query hook: %s(%s) by %s danger ratio %f", target.toString(), topObj->getObjectName(), hero.name, ratio); logAi->trace("Query hook: %s(%s) by %s danger ratio %f", target.toString(), topObj->getObjectName(), hero.name(), ratio);
if(cb->getObj(goalObjectID, false)) if(cb->getObj(goalObjectID, false))
{ {
@ -1574,7 +1574,7 @@ void AIGateway::requestActionASAP(std::function<void()> whatToDo)
void AIGateway::lostHero(HeroPtr h) void AIGateway::lostHero(HeroPtr h)
{ {
logAi->debug("I lost my hero %s. It's best to forget and move on.", h.name); logAi->debug("I lost my hero %s. It's best to forget and move on.", h.name());
} }
void AIGateway::answerQuery(QueryID queryID, int selection) void AIGateway::answerQuery(QueryID queryID, int selection)

View File

@ -67,7 +67,6 @@ HeroPtr::HeroPtr(const CGHeroInstance * H)
} }
h = H; h = H;
name = h->getNameTranslated();
hid = H->id; hid = H->id;
// infosCount[ai->playerID][hid]++; // infosCount[ai->playerID][hid]++;
} }
@ -89,6 +88,14 @@ bool HeroPtr::operator<(const HeroPtr & rhs) const
return hid < rhs.hid; return hid < rhs.hid;
} }
std::string HeroPtr::name() const
{
if (h)
return h->getNameTextID();
else
return "<NO HERO>";
}
const CGHeroInstance * HeroPtr::get(bool doWeExpectNull) const const CGHeroInstance * HeroPtr::get(bool doWeExpectNull) const
{ {
return get(cb, doWeExpectNull); return get(cb, doWeExpectNull);

View File

@ -87,8 +87,7 @@ struct DLL_EXPORT HeroPtr
ObjectInstanceID hid; ObjectInstanceID hid;
public: public:
std::string name; std::string name() const;
HeroPtr(); HeroPtr();
HeroPtr(const CGHeroInstance * H); HeroPtr(const CGHeroInstance * H);
@ -117,7 +116,6 @@ public:
{ {
handler & h; handler & h;
handler & hid; handler & hid;
handler & name;
} }
}; };

View File

@ -176,7 +176,7 @@ int HeroManager::selectBestSkill(const HeroPtr & hero, const std::vector<Seconda
logAi->trace( logAi->trace(
"Hero %s is proposed to learn %d with score %f", "Hero %s is proposed to learn %d with score %f",
hero.name, hero.name(),
skills[i].toEnum(), skills[i].toEnum(),
score); score);
} }
@ -204,6 +204,7 @@ float HeroManager::getFightingStrengthCached(const CGHeroInstance * hero) const
{ {
auto cached = knownFightingStrength.find(hero->id); auto cached = knownFightingStrength.find(hero->id);
//FIXME: fallback to hero->getFightingStrength() is VERY slow on higher difficulties (no object graph? map reveal?)
return cached != knownFightingStrength.end() ? cached->second : hero->getFightingStrength(); return cached != knownFightingStrength.end() ? cached->second : hero->getFightingStrength();
} }

View File

@ -212,7 +212,7 @@ void CaptureObjectsBehavior::decomposeObjects(
vstd::concatenate(tasksLocal, getVisitGoals(paths, nullkiller, objToVisit, specificObjects)); vstd::concatenate(tasksLocal, getVisitGoals(paths, nullkiller, objToVisit, specificObjects));
} }
std::lock_guard<std::mutex> lock(sync); std::lock_guard<std::mutex> lock(sync); // FIXME: consider using tbb::parallel_reduce instead to avoid mutex overhead
vstd::concatenate(result, tasksLocal); vstd::concatenate(result, tasksLocal);
}); });
} }

View File

@ -31,7 +31,7 @@ namespace Goals
{ {
objid = obj->id.getNum(); objid = obj->id.getNum();
tile = obj->visitablePos(); tile = obj->visitablePos();
name = obj->getObjectName(); name = obj->typeName;
} }
bool operator==(const CaptureObject & other) const override; bool operator==(const CaptureObject & other) const override;

View File

@ -26,7 +26,7 @@ ExecuteHeroChain::ExecuteHeroChain(const AIPath & path, const CGObjectInstance *
if(obj) if(obj)
{ {
objid = obj->id.getNum(); objid = obj->id.getNum();
targetName = obj->getObjectName() + tile.toString(); targetName = obj->typeName + tile.toString();
} }
else else
{ {
@ -106,7 +106,7 @@ void ExecuteHeroChain::accept(AIGateway * ai)
if(!heroPtr.validAndSet()) if(!heroPtr.validAndSet())
{ {
logAi->error("Hero %s was lost. Exit hero chain.", heroPtr.name); logAi->error("Hero %s was lost. Exit hero chain.", heroPtr.name());
return; return;
} }
@ -143,7 +143,7 @@ void ExecuteHeroChain::accept(AIGateway * ai)
if(!heroPtr.validAndSet()) if(!heroPtr.validAndSet())
{ {
logAi->error("Hero %s was lost trying to execute special action. Exit hero chain.", heroPtr.name); logAi->error("Hero %s was lost trying to execute special action. Exit hero chain.", heroPtr.name());
return; return;
} }
@ -204,7 +204,7 @@ void ExecuteHeroChain::accept(AIGateway * ai)
{ {
if(!heroPtr.validAndSet()) if(!heroPtr.validAndSet())
{ {
logAi->error("Hero %s was lost. Exit hero chain.", heroPtr.name); logAi->error("Hero %s was lost. Exit hero chain.", heroPtr.name());
return; return;
} }
@ -250,7 +250,7 @@ void ExecuteHeroChain::accept(AIGateway * ai)
{ {
if(!heroPtr.validAndSet()) if(!heroPtr.validAndSet())
{ {
logAi->debug("Hero %s was killed while attempting to reach %s", heroPtr.name, node->coord.toString()); logAi->debug("Hero %s was killed while attempting to reach %s", heroPtr.name(), node->coord.toString());
return; return;
} }

View File

@ -70,11 +70,8 @@ bool ExplorationHelper::scanMap()
int3 mapSize = cbp->getMapSize(); int3 mapSize = cbp->getMapSize();
int perimeter = 2 * sightRadius * (mapSize.x + mapSize.y); int perimeter = 2 * sightRadius * (mapSize.x + mapSize.y);
std::vector<int3> from; std::vector<int3> edgeTiles;
std::vector<int3> to; edgeTiles.reserve(perimeter);
from.reserve(perimeter);
to.reserve(perimeter);
foreach_tile_pos([&](const int3 & pos) foreach_tile_pos([&](const int3 & pos)
{ {
@ -91,13 +88,13 @@ bool ExplorationHelper::scanMap()
}); });
if(hasInvisibleNeighbor) if(hasInvisibleNeighbor)
from.push_back(pos); edgeTiles.push_back(pos);
} }
}); });
logAi->debug("Exploration scan visible area perimeter for hero %s", hero->getNameTranslated()); logAi->debug("Exploration scan visible area perimeter for hero %s", hero->getNameTranslated());
for(const int3 & tile : from) for(const int3 & tile : edgeTiles)
{ {
scanTile(tile); scanTile(tile);
} }
@ -108,19 +105,36 @@ bool ExplorationHelper::scanMap()
} }
allowDeadEndCancellation = false; allowDeadEndCancellation = false;
for(int i = 0; i < sightRadius; i++)
{
getVisibleNeighbours(from, to);
vstd::concatenate(from, to);
vstd::removeDuplicates(from);
}
logAi->debug("Exploration scan all possible tiles for hero %s", hero->getNameTranslated()); logAi->debug("Exploration scan all possible tiles for hero %s", hero->getNameTranslated());
for(const int3 & tile : from) boost::multi_array<ui8, 3> potentialTiles = *ts->fogOfWarMap;
std::vector<int3> tilesToExploreFrom = edgeTiles;
// WARNING: POTENTIAL BUG
// AI attempts to move to any tile within sight radius to reveal some new tiles
// however sight radius is circular, while this method assumes square radius
// standing on the edge of a square will NOT reveal tile in opposite corner
for(int i = 0; i < sightRadius; i++)
{ {
scanTile(tile); std::vector<int3> newTilesToExploreFrom;
for(const int3 & tile : tilesToExploreFrom)
{
foreach_neighbour(cbp, tile, [&](CCallback * cbp, int3 neighbour)
{
if(potentialTiles[neighbour.z][neighbour.x][neighbour.y])
{
newTilesToExploreFrom.push_back(neighbour);
potentialTiles[neighbour.z][neighbour.x][neighbour.y] = false;
}
});
}
for(const int3 & tile : newTilesToExploreFrom)
{
scanTile(tile);
}
std::swap(tilesToExploreFrom, newTilesToExploreFrom);
} }
return !bestGoal->invalid(); return !bestGoal->invalid();
@ -172,20 +186,6 @@ void ExplorationHelper::scanTile(const int3 & tile)
} }
} }
void ExplorationHelper::getVisibleNeighbours(const std::vector<int3> & tiles, std::vector<int3> & out) const
{
for(const int3 & tile : tiles)
{
foreach_neighbour(cbp, tile, [&](CCallback * cbp, int3 neighbour)
{
if((*(ts->fogOfWarMap))[neighbour.z][neighbour.x][neighbour.y])
{
out.push_back(neighbour);
}
});
}
}
int ExplorationHelper::howManyTilesWillBeDiscovered(const int3 & pos) const int ExplorationHelper::howManyTilesWillBeDiscovered(const int3 & pos) const
{ {
int ret = 0; int ret = 0;

View File

@ -46,7 +46,6 @@ public:
private: private:
void scanTile(const int3 & tile); void scanTile(const int3 & tile);
bool hasReachableNeighbor(const int3 & pos) const; bool hasReachableNeighbor(const int3 & pos) const;
void getVisibleNeighbours(const std::vector<int3> & tiles, std::vector<int3> & out) const;
}; };
} }

View File

@ -320,11 +320,9 @@ void AINodeStorage::calculateNeighbours(
const PathfinderConfig * pathfinderConfig, const PathfinderConfig * pathfinderConfig,
const CPathfinderHelper * pathfinderHelper) const CPathfinderHelper * pathfinderHelper)
{ {
std::vector<int3> accessibleNeighbourTiles; NeighbourTilesVector accessibleNeighbourTiles;
result.clear(); result.clear();
accessibleNeighbourTiles.reserve(8);
pathfinderHelper->calculateNeighbourTiles(accessibleNeighbourTiles, source); pathfinderHelper->calculateNeighbourTiles(accessibleNeighbourTiles, source);
const AIPathNode * srcNode = getAINode(source.node); const AIPathNode * srcNode = getAINode(source.node);

View File

@ -23,6 +23,8 @@ constexpr int NKAI_GRAPH_TRACE_LEVEL = 0;
#include "Actions/SpecialAction.h" #include "Actions/SpecialAction.h"
#include "Actors.h" #include "Actors.h"
#include <boost/container/small_vector.hpp>
namespace NKAI namespace NKAI
{ {
namespace AIPathfinding namespace AIPathfinding
@ -85,7 +87,9 @@ struct AIPathNodeInfo
struct AIPath struct AIPath
{ {
std::vector<AIPathNodeInfo> nodes; using NodesVector = boost::container::small_vector<AIPathNodeInfo, 16>;
NodesVector nodes;
uint64_t targetObjectDanger; uint64_t targetObjectDanger;
uint64_t armyLoss; uint64_t armyLoss;
uint64_t targetObjectArmyLoss; uint64_t targetObjectArmyLoss;

View File

@ -141,7 +141,8 @@ namespace AIPathfinding
{ {
SpellID summonBoat = SpellID::SUMMON_BOAT; SpellID summonBoat = SpellID::SUMMON_BOAT;
return hero->getSpellCost(summonBoat.toSpell()); // FIXME: this should be hero->getSpellCost, however currently queries to bonus system are too slow
return summonBoat.toSpell()->getCost(0);
} }
} }

View File

@ -118,7 +118,7 @@ void GraphPaths::calculatePaths(const CGHeroInstance * targetHero, const Nullkil
targetNode.specialAction = compositeAction; targetNode.specialAction = compositeAction;
auto targetGraphNode = graph.getNode(target); const auto & targetGraphNode = graph.getNode(target);
if(targetGraphNode.objID.hasValue()) if(targetGraphNode.objID.hasValue())
{ {

View File

@ -162,10 +162,9 @@ void AINodeStorage::calculateNeighbours(
const PathfinderConfig * pathfinderConfig, const PathfinderConfig * pathfinderConfig,
const CPathfinderHelper * pathfinderHelper) const CPathfinderHelper * pathfinderHelper)
{ {
std::vector<int3> accessibleNeighbourTiles; NeighbourTilesVector accessibleNeighbourTiles;
result.clear(); result.clear();
accessibleNeighbourTiles.reserve(8);
pathfinderHelper->calculateNeighbourTiles(accessibleNeighbourTiles, source); pathfinderHelper->calculateNeighbourTiles(accessibleNeighbourTiles, source);

View File

@ -85,6 +85,9 @@ void BonusList::stackBonuses()
int BonusList::totalValue() const int BonusList::totalValue() const
{ {
if (bonuses.empty())
return 0;
struct BonusCollection struct BonusCollection
{ {
int base = 0; int base = 0;
@ -96,63 +99,65 @@ int BonusList::totalValue() const
int indepMax = std::numeric_limits<int>::min(); int indepMax = std::numeric_limits<int>::min();
}; };
auto percent = [](int64_t base, int64_t percent) -> int { auto percent = [](int base, int percent) -> int {
return static_cast<int>(std::clamp<int64_t>((base * (100 + percent)) / 100, std::numeric_limits<int>::min(), std::numeric_limits<int>::max())); return (static_cast<int64_t>(base) * (100 + percent)) / 100;
}; };
std::array <BonusCollection, vstd::to_underlying(BonusSource::NUM_BONUS_SOURCE)> sources = {};
BonusCollection any; BonusCollection accumulated;
bool hasIndepMax = false; bool hasIndepMax = false;
bool hasIndepMin = false; bool hasIndepMin = false;
std::array<int, vstd::to_underlying(BonusSource::NUM_BONUS_SOURCE)> percentToSource = {};
for(const auto & b : bonuses) for(const auto & b : bonuses)
{ {
switch(b->valType) switch(b->valType)
{ {
case BonusValueType::BASE_NUMBER:
sources[vstd::to_underlying(b->source)].base += b->val;
break;
case BonusValueType::PERCENT_TO_ALL:
sources[vstd::to_underlying(b->source)].percentToAll += b->val;
break;
case BonusValueType::PERCENT_TO_BASE:
sources[vstd::to_underlying(b->source)].percentToBase += b->val;
break;
case BonusValueType::PERCENT_TO_SOURCE: case BonusValueType::PERCENT_TO_SOURCE:
sources[vstd::to_underlying(b->source)].percentToSource += b->val; percentToSource[vstd::to_underlying(b->source)] += b->val;
break; break;
case BonusValueType::PERCENT_TO_TARGET_TYPE: case BonusValueType::PERCENT_TO_TARGET_TYPE:
sources[vstd::to_underlying(b->targetSourceType)].percentToSource += b->val; percentToSource[vstd::to_underlying(b->targetSourceType)] += b->val;
break;
case BonusValueType::ADDITIVE_VALUE:
sources[vstd::to_underlying(b->source)].additive += b->val;
break;
case BonusValueType::INDEPENDENT_MAX:
hasIndepMax = true;
vstd::amax(sources[vstd::to_underlying(b->source)].indepMax, b->val);
break;
case BonusValueType::INDEPENDENT_MIN:
hasIndepMin = true;
vstd::amin(sources[vstd::to_underlying(b->source)].indepMin, b->val);
break; break;
} }
} }
for(const auto & src : sources)
{
any.base += percent(src.base, src.percentToSource);
any.percentToBase += percent(src.percentToBase, src.percentToSource);
any.percentToAll += percent(src.percentToAll, src.percentToSource);
any.additive += percent(src.additive, src.percentToSource);
if(hasIndepMin)
vstd::amin(any.indepMin, percent(src.indepMin, src.percentToSource));
if(hasIndepMax)
vstd::amax(any.indepMax, percent(src.indepMax, src.percentToSource));
}
any.base = percent(any.base, any.percentToBase);
any.base += any.additive;
auto valFirst = percent(any.base ,any.percentToAll);
if(hasIndepMin && hasIndepMax && any.indepMin < any.indepMax) for(const auto & b : bonuses)
any.indepMax = any.indepMin; {
int sourceIndex = vstd::to_underlying(b->source);
int valModified = percent(b->val, percentToSource[sourceIndex]);
switch(b->valType)
{
case BonusValueType::BASE_NUMBER:
accumulated.base += valModified;
break;
case BonusValueType::PERCENT_TO_ALL:
accumulated.percentToAll += valModified;
break;
case BonusValueType::PERCENT_TO_BASE:
accumulated.percentToBase += valModified;
break;
case BonusValueType::ADDITIVE_VALUE:
accumulated.additive += valModified;
break;
case BonusValueType::INDEPENDENT_MAX:
hasIndepMax = true;
vstd::amax(accumulated.indepMax, valModified);
break;
case BonusValueType::INDEPENDENT_MIN:
hasIndepMin = true;
vstd::amin(accumulated.indepMin, valModified);
break;
}
}
accumulated.base = percent(accumulated.base, accumulated.percentToBase);
accumulated.base += accumulated.additive;
auto valFirst = percent(accumulated.base ,accumulated.percentToAll);
if(hasIndepMin && hasIndepMax && accumulated.indepMin < accumulated.indepMax)
accumulated.indepMax = accumulated.indepMin;
const int notIndepBonuses = static_cast<int>(std::count_if(bonuses.cbegin(), bonuses.cend(), [](const std::shared_ptr<Bonus>& b) const int notIndepBonuses = static_cast<int>(std::count_if(bonuses.cbegin(), bonuses.cend(), [](const std::shared_ptr<Bonus>& b)
{ {
@ -160,9 +165,9 @@ int BonusList::totalValue() const
})); }));
if(notIndepBonuses) if(notIndepBonuses)
return std::clamp(valFirst, any.indepMax, any.indepMin); return std::clamp(valFirst, accumulated.indepMax, accumulated.indepMin);
return hasIndepMin ? any.indepMin : hasIndepMax ? any.indepMax : 0; return hasIndepMin ? accumulated.indepMin : hasIndepMax ? accumulated.indepMax : 0;
} }
std::shared_ptr<Bonus> BonusList::getFirst(const CSelector &select) std::shared_ptr<Bonus> BonusList::getFirst(const CSelector &select)

View File

@ -157,7 +157,6 @@ void CLogger::log(ELogLevel::ELogLevel level, const boost::format & fmt) const
ELogLevel::ELogLevel CLogger::getLevel() const ELogLevel::ELogLevel CLogger::getLevel() const
{ {
TLockGuard _(mx);
return level; return level;
} }

View File

@ -1231,7 +1231,7 @@ void RemoveObject::applyGs(CGameState *gs)
gs->map->instanceNames.erase(obj->instanceName); gs->map->instanceNames.erase(obj->instanceName);
gs->map->objects[objectID.getNum()].dellNull(); gs->map->objects[objectID.getNum()].dellNull();
gs->map->calculateGuardingGreaturePositions(); gs->map->calculateGuardingGreaturePositions();//FIXME: excessive, update only affected tiles
} }
static int getDir(const int3 & src, const int3 & dst) static int getDir(const int3 & src, const int3 & dst)

View File

@ -49,7 +49,7 @@ bool CPathfinderHelper::canMoveFromNode(const PathNodeInfo & source) const
return true; return true;
} }
void CPathfinderHelper::calculateNeighbourTiles(std::vector<int3> & result, const PathNodeInfo & source) const void CPathfinderHelper::calculateNeighbourTiles(NeighbourTilesVector & result, const PathNodeInfo & source) const
{ {
result.clear(); result.clear();
@ -239,9 +239,9 @@ void CPathfinder::calculatePaths()
logAi->trace("CPathfinder finished with %s iterations", std::to_string(counter)); logAi->trace("CPathfinder finished with %s iterations", std::to_string(counter));
} }
std::vector<int3> CPathfinderHelper::getAllowedTeleportChannelExits(const TeleportChannelID & channelID) const TeleporterTilesVector CPathfinderHelper::getAllowedTeleportChannelExits(const TeleportChannelID & channelID) const
{ {
std::vector<int3> allowedExits; TeleporterTilesVector allowedExits;
for(const auto & objId : getTeleportChannelExits(channelID, hero->tempOwner)) for(const auto & objId : getTeleportChannelExits(channelID, hero->tempOwner))
{ {
@ -262,9 +262,9 @@ std::vector<int3> CPathfinderHelper::getAllowedTeleportChannelExits(const Telepo
return allowedExits; return allowedExits;
} }
std::vector<int3> CPathfinderHelper::getCastleGates(const PathNodeInfo & source) const TeleporterTilesVector CPathfinderHelper::getCastleGates(const PathNodeInfo & source) const
{ {
std::vector<int3> allowedExits; TeleporterTilesVector allowedExits;
auto towns = getPlayerState(hero->tempOwner)->towns; auto towns = getPlayerState(hero->tempOwner)->towns;
for(const auto & town : towns) for(const auto & town : towns)
@ -279,9 +279,9 @@ std::vector<int3> CPathfinderHelper::getCastleGates(const PathNodeInfo & source)
return allowedExits; return allowedExits;
} }
std::vector<int3> CPathfinderHelper::getTeleportExits(const PathNodeInfo & source) const TeleporterTilesVector CPathfinderHelper::getTeleportExits(const PathNodeInfo & source) const
{ {
std::vector<int3> teleportationExits; TeleporterTilesVector teleportationExits;
const auto * objTeleport = dynamic_cast<const CGTeleport *>(source.nodeObject); const auto * objTeleport = dynamic_cast<const CGTeleport *>(source.nodeObject);
if(isAllowedTeleportEntrance(objTeleport)) if(isAllowedTeleportEntrance(objTeleport))
@ -578,7 +578,7 @@ int CPathfinderHelper::getMaxMovePoints(const EPathfindingLayer & layer) const
void CPathfinderHelper::getNeighbours( void CPathfinderHelper::getNeighbours(
const TerrainTile & srcTile, const TerrainTile & srcTile,
const int3 & srcCoord, const int3 & srcCoord,
std::vector<int3> & vec, NeighbourTilesVector & vec,
const boost::logic::tribool & onLand, const boost::logic::tribool & onLand,
const bool limitCoastSailing) const const bool limitCoastSailing) const
{ {
@ -702,8 +702,8 @@ int CPathfinderHelper::getMovementCost(
constexpr auto maxCostOfOneStep = static_cast<int>(175 * M_SQRT2); // diagonal move on Swamp - 247 MP constexpr auto maxCostOfOneStep = static_cast<int>(175 * M_SQRT2); // diagonal move on Swamp - 247 MP
if(checkLast && left > 0 && left <= maxCostOfOneStep) //it might be the last tile - if no further move possible we take all move points if(checkLast && left > 0 && left <= maxCostOfOneStep) //it might be the last tile - if no further move possible we take all move points
{ {
std::vector<int3> vec; NeighbourTilesVector vec;
vec.reserve(8); //optimization
getNeighbours(*dt, dst, vec, ct->terType->isLand(), true); getNeighbours(*dt, dst, vec, ct->terType->isLand(), true);
for(const auto & elem : vec) for(const auto & elem : vec)
{ {

View File

@ -13,12 +13,23 @@
#include "../IGameCallback.h" #include "../IGameCallback.h"
#include "../bonuses/BonusEnum.h" #include "../bonuses/BonusEnum.h"
#include <boost/container/static_vector.hpp>
#include <boost/container/small_vector.hpp>
VCMI_LIB_NAMESPACE_BEGIN VCMI_LIB_NAMESPACE_BEGIN
class CGWhirlpool; class CGWhirlpool;
struct TurnInfo; struct TurnInfo;
struct PathfinderOptions; struct PathfinderOptions;
// Optimized storage - tile can have 0-8 neighbour tiles
// static_vector uses fixed, preallocated storage (capacity) and dynamic size
// this avoid dynamic allocations on huge number of neighbour list queries
using NeighbourTilesVector = boost::container::static_vector<int3, 8>;
// Optimized storage to minimize dynamic allocations - most of teleporters have only one exit, but some may have more (premade maps, castle gates)
using TeleporterTilesVector = boost::container::small_vector<int3, 4>;
class DLL_LINKAGE CPathfinder class DLL_LINKAGE CPathfinder
{ {
public: public:
@ -87,22 +98,22 @@ public:
bool hasBonusOfType(BonusType type) const; bool hasBonusOfType(BonusType type) const;
int getMaxMovePoints(const EPathfindingLayer & layer) const; int getMaxMovePoints(const EPathfindingLayer & layer) const;
std::vector<int3> getCastleGates(const PathNodeInfo & source) const; TeleporterTilesVector getCastleGates(const PathNodeInfo & source) const;
bool isAllowedTeleportEntrance(const CGTeleport * obj) const; bool isAllowedTeleportEntrance(const CGTeleport * obj) const;
std::vector<int3> getAllowedTeleportChannelExits(const TeleportChannelID & channelID) const; TeleporterTilesVector getAllowedTeleportChannelExits(const TeleportChannelID & channelID) const;
bool addTeleportTwoWay(const CGTeleport * obj) const; bool addTeleportTwoWay(const CGTeleport * obj) const;
bool addTeleportOneWay(const CGTeleport * obj) const; bool addTeleportOneWay(const CGTeleport * obj) const;
bool addTeleportOneWayRandom(const CGTeleport * obj) const; bool addTeleportOneWayRandom(const CGTeleport * obj) const;
bool addTeleportWhirlpool(const CGWhirlpool * obj) const; bool addTeleportWhirlpool(const CGWhirlpool * obj) const;
bool canMoveBetween(const int3 & a, const int3 & b) const; //checks only for visitable objects that may make moving between tiles impossible, not other conditions (like tiles itself accessibility) bool canMoveBetween(const int3 & a, const int3 & b) const; //checks only for visitable objects that may make moving between tiles impossible, not other conditions (like tiles itself accessibility)
void calculateNeighbourTiles(std::vector<int3> & result, const PathNodeInfo & source) const; void calculateNeighbourTiles(NeighbourTilesVector & result, const PathNodeInfo & source) const;
std::vector<int3> getTeleportExits(const PathNodeInfo & source) const; TeleporterTilesVector getTeleportExits(const PathNodeInfo & source) const;
void getNeighbours( void getNeighbours(
const TerrainTile & srcTile, const TerrainTile & srcTile,
const int3 & srcCoord, const int3 & srcCoord,
std::vector<int3> & vec, NeighbourTilesVector & vec,
const boost::logic::tribool & onLand, const boost::logic::tribool & onLand,
const bool limitCoastSailing) const; const bool limitCoastSailing) const;

View File

@ -40,7 +40,7 @@ void NodeStorage::initialize(const PathfinderOptions & options, const CGameState
{ {
for(pos.y=0; pos.y < sizes.y; ++pos.y) for(pos.y=0; pos.y < sizes.y; ++pos.y)
{ {
const TerrainTile tile = gs->map->getTile(pos); const TerrainTile & tile = gs->map->getTile(pos);
if(tile.terType->isWater()) if(tile.terType->isWater())
{ {
resetTile(pos, ELayer::SAIL, PathfinderUtil::evaluateAccessibility<ELayer::SAIL>(pos, tile, fow, player, gs)); resetTile(pos, ELayer::SAIL, PathfinderUtil::evaluateAccessibility<ELayer::SAIL>(pos, tile, fow, player, gs));
@ -67,10 +67,9 @@ void NodeStorage::calculateNeighbours(
const PathfinderConfig * pathfinderConfig, const PathfinderConfig * pathfinderConfig,
const CPathfinderHelper * pathfinderHelper) const CPathfinderHelper * pathfinderHelper)
{ {
std::vector<int3> accessibleNeighbourTiles; NeighbourTilesVector accessibleNeighbourTiles;
result.clear(); result.clear();
accessibleNeighbourTiles.reserve(8);
pathfinderHelper->calculateNeighbourTiles(accessibleNeighbourTiles, source); pathfinderHelper->calculateNeighbourTiles(accessibleNeighbourTiles, source);