1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-01-24 03:47:18 +02:00

Nullkiller: tbb and hero chain calculation optimization and parallel cpathfinder initialization

This commit is contained in:
Andrii Danylchenko 2020-09-02 20:16:24 +03:00 committed by Andrii Danylchenko
parent 3480f17a68
commit fb3cda666f
14 changed files with 339 additions and 248 deletions

View File

@ -189,7 +189,7 @@ bool CDistanceSorter::operator()(const CGObjectInstance * lhs, const CGObjectIns
const CGPathNode * ln = ai->myCb->getPathsInfo(hero)->getPathInfo(lhs->visitablePos());
const CGPathNode * rn = ai->myCb->getPathsInfo(hero)->getPathInfo(rhs->visitablePos());
return ln->cost < rn->cost;
return ln->getCost() < rn->getCost();
}
bool isSafeToVisit(HeroPtr h, const CCreatureSet * heroArmy, uint64_t dangerStrength)

View File

@ -137,6 +137,8 @@ else()
target_link_libraries(VCAI PRIVATE fl-static vcmi)
endif()
target_link_libraries(VCAI PRIVATE TBB::tbb)
vcmi_set_output_dir(VCAI "AI")
set_target_properties(VCAI PROPERTIES ${PCH_PROPERTIES})

View File

@ -44,19 +44,19 @@ struct armyStructure
armyStructure evaluateArmyStructure(const CArmedInstance * army)
{
ui64 totalStrenght = army->getArmyStrength();
double walkersStrenght = 0;
double flyersStrenght = 0;
double shootersStrenght = 0;
ui64 totalStrength = army->getArmyStrength();
double walkersStrength = 0;
double flyersStrength = 0;
double shootersStrength = 0;
ui32 maxSpeed = 0;
static const CSelector selectorSHOOTER = Selector::type(Bonus::SHOOTER);
static const CSelector selectorSHOOTER = Selector::type()(Bonus::SHOOTER);
static const std::string keySHOOTER = "type_"+std::to_string((int32_t)Bonus::SHOOTER);
static const CSelector selectorFLYING = Selector::type(Bonus::FLYING);
static const CSelector selectorFLYING = Selector::type()(Bonus::FLYING);
static const std::string keyFLYING = "type_"+std::to_string((int32_t)Bonus::FLYING);
static const CSelector selectorSTACKS_SPEED = Selector::type(Bonus::STACKS_SPEED);
static const CSelector selectorSTACKS_SPEED = Selector::type()(Bonus::STACKS_SPEED);
static const std::string keySTACKS_SPEED = "type_"+std::to_string((int32_t)Bonus::STACKS_SPEED);
for(auto s : army->Slots())
@ -65,23 +65,23 @@ armyStructure evaluateArmyStructure(const CArmedInstance * army)
const CCreature * creature = s.second->type;
if(creature->hasBonus(selectorSHOOTER, keySHOOTER))
{
shootersStrenght += s.second->getPower();
shootersStrength += s.second->getPower();
walker = false;
}
if(creature->hasBonus(selectorFLYING, keyFLYING))
{
flyersStrenght += s.second->getPower();
flyersStrength += s.second->getPower();
walker = false;
}
if(walker)
walkersStrenght += s.second->getPower();
walkersStrength += s.second->getPower();
vstd::amax(maxSpeed, creature->valOfBonuses(selectorSTACKS_SPEED, keySTACKS_SPEED));
}
armyStructure as;
as.walkers = walkersStrenght / totalStrenght;
as.shooters = shootersStrenght / totalStrenght;
as.flyers = flyersStrenght / totalStrenght;
as.walkers = static_cast<float>(walkersStrength / totalStrength);
as.shooters = static_cast<float>(shootersStrength / totalStrength);
as.flyers = static_cast<float>(flyersStrength / totalStrength);
as.maxSpeed = maxSpeed;
assert(as.walkers || as.flyers || as.shooters);
return as;

View File

@ -262,4 +262,4 @@ void Nullkiller::makeTurn()
return;
}
}
}
}

View File

@ -9,6 +9,8 @@
*/
#pragma once
#include <boost/asio.hpp>
#include "PriorityEvaluator.h"
#include "FuzzyHelper.h"
#include "AIMemory.h"

View File

@ -148,7 +148,7 @@ TGoalVec GatherArmy::getAllPossibleSubgoals()
{
auto dwelling = dynamic_cast<const CGDwelling *>(obj);
ui32 val = std::min<ui32>(value, ai->ah->howManyReinforcementsCanBuy(hero.get(), dwelling));
ui32 val = std::min((ui32)value, (ui32)ai->ah->howManyReinforcementsCanBuy(hero.get(), dwelling));
if(val)
{

View File

@ -95,11 +95,11 @@ TSubgoal Win::whatToDoToAchieve()
{
auto towns = cb->getTownsInfo();
towns.erase(boost::remove_if(towns,
[](const CGTownInstance * t) -> bool
[](const CGTownInstance * t) -> bool
{
return vstd::contains(t->forbiddenBuildings, BuildingID::GRAIL);
}),
towns.end());
towns.end());
boost::sort(towns, CDistanceSorter(h.get()));
if(towns.size())
{

View File

@ -8,6 +8,7 @@
*
*/
#include "StdInc.h"
#include <tbb/tbb.h>
#include "AINodeStorage.h"
#include "Actions/TownPortalAction.h"
#include "../Goals/Goals.h"
@ -19,6 +20,8 @@
#include "../../../lib/PathfinderUtil.h"
#include "../../../lib/CPlayerState.h"
using namespace tbb;
std::shared_ptr<boost::multi_array<AIPathNode, 5>> AISharedStorage::shared;
std::set<int3> commitedTiles;
std::set<int3> commitedTilesInitial;
@ -61,50 +64,56 @@ void AINodeStorage::initialize(const PathfinderOptions & options, const CGameSta
return;
//TODO: fix this code duplication with NodeStorage::initialize, problem is to keep `resetTile` inline
int3 pos;
const PlayerColor player = playerID;
const PlayerColor fowPlayer = ai->playerID;
const int3 sizes = gs->getMapSize();
const auto & fow = static_cast<const CGameInfoCallback *>(gs)->getPlayerTeam(fowPlayer)->fogOfWarMap;
const int3 sizes = gs->getMapSize();
//make 200% sure that these are loop invariants (also a bit shorter code), let compiler do the rest(loop unswitching)
const bool useFlying = options.useFlying;
const bool useWaterWalking = options.useWaterWalking;
for(pos.x=0; pos.x < sizes.x; ++pos.x)
parallel_for(blocked_range<size_t>(0, sizes.x), [&](const blocked_range<size_t>& r)
{
for(pos.y=0; pos.y < sizes.y; ++pos.y)
//make 200% sure that these are loop invariants (also a bit shorter code), let compiler do the rest(loop unswitching)
const bool useFlying = options.useFlying;
const bool useWaterWalking = options.useWaterWalking;
const PlayerColor player = playerID;
int3 pos;
for(pos.x = r.begin(); pos.x != r.end(); ++pos.x)
{
for(pos.z=0; pos.z < sizes.z; ++pos.z)
for(pos.y = 0; pos.y < sizes.y; ++pos.y)
{
const TerrainTile * tile = &gs->map->getTile(pos);
switch(tile->terType)
for(pos.z = 0; pos.z < sizes.z; ++pos.z)
{
case ETerrainType::ROCK:
break;
const TerrainTile * tile = &gs->map->getTile(pos);
switch(tile->terType)
{
case ETerrainType::ROCK:
break;
case ETerrainType::WATER:
resetTile(pos, ELayer::SAIL, PathfinderUtil::evaluateAccessibility<ELayer::SAIL>(pos, tile, fow, player, gs));
if(useFlying)
resetTile(pos, ELayer::AIR, PathfinderUtil::evaluateAccessibility<ELayer::AIR>(pos, tile, fow, player, gs));
if(useWaterWalking)
resetTile(pos, ELayer::WATER, PathfinderUtil::evaluateAccessibility<ELayer::WATER>(pos, tile, fow, player, gs));
break;
case ETerrainType::WATER:
resetTile(pos, ELayer::SAIL, PathfinderUtil::evaluateAccessibility<ELayer::SAIL>(pos, tile, fow, player, gs));
if(useFlying)
resetTile(pos, ELayer::AIR, PathfinderUtil::evaluateAccessibility<ELayer::AIR>(pos, tile, fow, player, gs));
if(useWaterWalking)
resetTile(pos, ELayer::WATER, PathfinderUtil::evaluateAccessibility<ELayer::WATER>(pos, tile, fow, player, gs));
break;
default:
resetTile(pos, ELayer::LAND, PathfinderUtil::evaluateAccessibility<ELayer::LAND>(pos, tile, fow, player, gs));
if(useFlying)
resetTile(pos, ELayer::AIR, PathfinderUtil::evaluateAccessibility<ELayer::AIR>(pos, tile, fow, player, gs));
break;
default:
resetTile(pos, ELayer::LAND, PathfinderUtil::evaluateAccessibility<ELayer::LAND>(pos, tile, fow, player, gs));
if(useFlying)
resetTile(pos, ELayer::AIR, PathfinderUtil::evaluateAccessibility<ELayer::AIR>(pos, tile, fow, player, gs));
break;
}
}
}
}
}
});
}
void AINodeStorage::clear()
{
CCreature::DisableChildLinkage = true;
actors.clear();
CCreature::DisableChildLinkage = false;
heroChainPass = EHeroChainPass::INITIAL;
heroChainTurn = 0;
heroChainMaxTurns = 1;
@ -161,7 +170,7 @@ boost::optional<AIPathNode *> AINodeStorage::getOrCreateNode(
std::vector<CGPathNode *> AINodeStorage::getInitialNodes()
{
if(heroChainPass)
{
{
calculateTownPortalTeleportations(heroChain);
return heroChain;
@ -184,7 +193,7 @@ std::vector<CGPathNode *> AINodeStorage::getInitialNodes()
initialNode->turns = actor->initialTurn;
initialNode->moveRemains = actor->initialMovement;
initialNode->danger = 0;
initialNode->cost = actor->initialTurn;
initialNode->setCost(actor->initialTurn);
initialNode->action = CGPathNode::ENodeAction::NORMAL;
if(actor->isMovable)
@ -205,7 +214,7 @@ std::vector<CGPathNode *> AINodeStorage::getInitialNodes()
void AINodeStorage::resetTile(const int3 & coord, EPathfindingLayer layer, CGPathNode::EAccessibility accessibility)
{
for(AIPathNode & heroNode : nodes.get(coord, layer))
{
{
heroNode.actor = nullptr;
heroNode.danger = 0;
heroNode.manaCost = 0;
@ -246,7 +255,7 @@ void AINodeStorage::commit(
float cost) const
{
destination->action = action;
destination->cost = cost;
destination->setCost(cost);
destination->moveRemains = movementLeft;
destination->turns = turn;
destination->armyLoss = source->armyLoss;
@ -360,61 +369,133 @@ bool AINodeStorage::calculateHeroChainFinal()
}
});
}
return heroChain.size();
}
struct DelayedWork
{
AIPathNode * carrier;
AIPathNode * other;
DelayedWork()
{
}
DelayedWork(AIPathNode * carrier, AIPathNode * other) : carrier(carrier), other(other)
{
}
};
class HeroChainCalculationTask
{
private:
AISharedStorage & nodes;
AINodeStorage & storage;
std::vector<AIPathNode *> existingChains;
std::vector<ExchangeCandidate> newChains;
uint64_t chainMask;
int heroChainTurn;
std::vector<CGPathNode *> heroChain;
const std::vector<int3> & tiles;
public:
HeroChainCalculationTask(
AINodeStorage & storage, AISharedStorage & nodes, const std::vector<int3> & tiles, uint64_t chainMask, int heroChainTurn)
:existingChains(), newChains(), nodes(nodes), storage(storage), chainMask(chainMask), heroChainTurn(heroChainTurn), heroChain(), tiles(tiles)
{
existingChains.reserve(NUM_CHAINS);
newChains.reserve(NUM_CHAINS);
}
void execute(const blocked_range<size_t>& r)
{
for(int i = r.begin(); i != r.end(); i++)
{
auto & pos = tiles[i];
for(auto layer : phisycalLayers)
{
auto chains = nodes.get(pos, layer);
// fast cut inactive nodes
if(chains[0].blocked())
continue;
existingChains.clear();
newChains.clear();
for(AIPathNode & node : chains)
{
if(node.turns <= heroChainTurn && node.action != CGPathNode::ENodeAction::UNKNOWN)
existingChains.push_back(&node);
}
std::random_shuffle(existingChains.begin(), existingChains.end());
for(AIPathNode * node : existingChains)
{
if(node->actor->isMovable)
{
calculateHeroChain(node, existingChains, newChains);
}
}
cleanupInefectiveChains(newChains);
addHeroChain(newChains);
}
}
}
void calculateHeroChain(
AIPathNode * srcNode,
const std::vector<AIPathNode *> & variants,
std::vector<ExchangeCandidate> & result);
void calculateHeroChain(
AIPathNode * carrier,
AIPathNode * other,
std::vector<ExchangeCandidate> & result);
void cleanupInefectiveChains(std::vector<ExchangeCandidate> & result) const;
void addHeroChain(const std::vector<ExchangeCandidate> & result);
ExchangeCandidate calculateExchange(
ChainActor * exchangeActor,
AIPathNode * carrierParentNode,
AIPathNode * otherParentNode) const;
void flushResult(std::vector<CGPathNode *> & result)
{
vstd::concatenate(result, heroChain);
}
};
bool AINodeStorage::calculateHeroChain()
{
heroChainPass = EHeroChainPass::CHAIN;
heroChain.resize(0);
heroChain.clear();
std::vector<AIPathNode *> existingChains;
std::vector<ExchangeCandidate> newChains;
std::vector<int3> data(commitedTiles.begin(), commitedTiles.end());
existingChains.reserve(NUM_CHAINS);
newChains.reserve(NUM_CHAINS);
CCreature::DisableChildLinkage = true;
for(auto & pos : commitedTiles)
{
for(auto layer : phisycalLayers)
{
auto chains = nodes.get(pos, layer);
auto r = blocked_range<size_t>(0, data.size());
HeroChainCalculationTask task(*this, nodes, data, chainMask, heroChainTurn);
// fast cut inactive nodes
if(chains[0].blocked())
continue;
task.execute(r);
task.flushResult(heroChain);
existingChains.clear();
newChains.clear();
for(AIPathNode & node : chains)
{
if(node.turns <= heroChainTurn && node.action != CGPathNode::ENodeAction::UNKNOWN)
existingChains.push_back(&node);
}
for(AIPathNode * node : existingChains)
{
if(node->actor->isMovable)
{
calculateHeroChain(node, existingChains, newChains);
}
}
cleanupInefectiveChains(newChains);
addHeroChain(newChains);
}
}
CCreature::DisableChildLinkage = false;
commitedTiles.clear();
return heroChain.size();
return !heroChain.empty();
}
bool AINodeStorage::selectFirstActor()
{
if(!actors.size())
if(actors.empty())
return false;
auto strongest = *vstd::maxElementByFun(actors, [](std::shared_ptr<ChainActor> actor) -> uint64_t
@ -454,6 +535,9 @@ bool AINodeStorage::selectNextActor()
if(nextActor != actors.end())
{
if(nextActor->get()->armyValue < 1000)
return false;
chainMask = nextActor->get()->chainMask;
commitedTiles = commitedTilesInitial;
@ -463,22 +547,36 @@ bool AINodeStorage::selectNextActor()
return false;
}
void AINodeStorage::cleanupInefectiveChains(std::vector<ExchangeCandidate> & result) const
void HeroChainCalculationTask::cleanupInefectiveChains(std::vector<ExchangeCandidate> & result) const
{
vstd::erase_if(result, [&](const ExchangeCandidate & chainInfo) -> bool
{
auto pos = chainInfo.coord;
auto chains = nodes.get(pos, EPathfindingLayer::LAND);
auto isNotEffective = storage.hasBetterChain(chainInfo.carrierParent, &chainInfo, chains)
|| storage.hasBetterChain(chainInfo.carrierParent, &chainInfo, result);
return hasBetterChain(chainInfo.carrierParent, &chainInfo, chains)
|| hasBetterChain(chainInfo.carrierParent, &chainInfo, result);
#if PATHFINDER_TRACE_LEVEL >= 2
if(isNotEffective)
{
logAi->trace(
"Skip exchange %s[%x] -> %s[%x] at %s is ineficient",
chainInfo.otherParent->actor->toString(),
chainInfo.otherParent->actor->chainMask,
chainInfo.carrierParent->actor->toString(),
chainInfo.carrierParent->actor->chainMask,
chainInfo.carrierParent->coord.toString());
}
#endif
return isNotEffective;
});
}
void AINodeStorage::calculateHeroChain(
void HeroChainCalculationTask::calculateHeroChain(
AIPathNode * srcNode,
const std::vector<AIPathNode *> & variants,
std::vector<ExchangeCandidate> & result) const
std::vector<ExchangeCandidate> & result)
{
for(AIPathNode * node : variants)
{
@ -531,16 +629,15 @@ void AINodeStorage::calculateHeroChain(
}
}
void AINodeStorage::calculateHeroChain(
void HeroChainCalculationTask::calculateHeroChain(
AIPathNode * carrier,
AIPathNode * other,
std::vector<ExchangeCandidate> & result) const
std::vector<ExchangeCandidate> & result)
{
if(carrier->armyLoss < carrier->actor->armyValue
&& (carrier->action != CGPathNode::BATTLE || (carrier->actor->allowBattle && carrier->specialAction))
&& carrier->action != CGPathNode::BLOCKING_VISIT
&& (other->armyLoss == 0 || other->armyLoss < other->actor->armyValue)
&& carrier->actor->canExchange(other->actor))
&& (other->armyLoss == 0 || other->armyLoss < other->actor->armyValue))
{
#if PATHFINDER_TRACE_LEVEL >= 2
logAi->trace(
@ -566,20 +663,20 @@ void AINodeStorage::calculateHeroChain(
}
}
auto newActor = carrier->actor->exchange(other->actor);
auto newActor = carrier->actor->tryExchange(other->actor);
result.push_back(calculateExchange(newActor, carrier, other));
if(newActor) result.push_back(calculateExchange(newActor, carrier, other));
}
}
void AINodeStorage::addHeroChain(const std::vector<ExchangeCandidate> & result)
void HeroChainCalculationTask::addHeroChain(const std::vector<ExchangeCandidate> & result)
{
for(const ExchangeCandidate & chainInfo : result)
{
auto carrier = chainInfo.carrierParent;
auto newActor = chainInfo.actor;
auto other = chainInfo.otherParent;
auto chainNodeOptional = getOrCreateNode(carrier->coord, carrier->layer, newActor);
auto chainNodeOptional = storage.getOrCreateNode(carrier->coord, carrier->layer, newActor);
if(!chainNodeOptional)
{
@ -594,24 +691,34 @@ void AINodeStorage::addHeroChain(const std::vector<ExchangeCandidate> & result)
if(exchangeNode->action != CGPathNode::ENodeAction::UNKNOWN)
{
#if PATHFINDER_TRACE_LEVEL >= 2
logAi->trace("Exchange at %s node is already in use. Blocked.", carrier->coord.toString());
logAi->trace(
"Skip exchange %s[%x] -> %s[%x] at %s because node is in use",
other->actor->toString(),
other->actor->chainMask,
carrier->actor->toString(),
carrier->actor->chainMask,
carrier->coord.toString());
#endif
continue;
}
if(exchangeNode->turns != 0xFF && exchangeNode->cost < chainInfo.cost)
if(exchangeNode->turns != 0xFF && exchangeNode->getCost() < chainInfo.getCost())
{
#if PATHFINDER_TRACE_LEVEL >= 2
logAi->trace(
"Exchange at %s is is not effective enough. %f < %f",
exchangeNode->coord.toString(),
exchangeNode->getCost(),
"Skip exchange %s[%x] -> %s[%x] at %s because not effective enough. %f < %f",
other->actor->toString(),
other->actor->chainMask,
carrier->actor->toString(),
carrier->actor->chainMask,
carrier->coord.toString(),
exchangeNode->getCost(),
chainInfo.getCost());
#endif
continue;
}
commit(exchangeNode, carrier, carrier->action, chainInfo.turns, chainInfo.moveRemains, chainInfo.cost);
storage.commit(exchangeNode, carrier, carrier->action, chainInfo.turns, chainInfo.moveRemains, chainInfo.getCost());
if(carrier->specialAction || carrier->chainOther)
{
@ -644,7 +751,7 @@ void AINodeStorage::addHeroChain(const std::vector<ExchangeCandidate> & result)
}
}
ExchangeCandidate AINodeStorage::calculateExchange(
ExchangeCandidate HeroChainCalculationTask::calculateExchange(
ChainActor * exchangeActor,
AIPathNode * carrierParentNode,
AIPathNode * otherParentNode) const
@ -658,7 +765,7 @@ ExchangeCandidate AINodeStorage::calculateExchange(
candidate.actor = exchangeActor;
candidate.armyLoss = carrierParentNode->armyLoss + otherParentNode->armyLoss;
candidate.turns = carrierParentNode->turns;
candidate.cost = carrierParentNode->cost + otherParentNode->cost / 1000.0;
candidate.setCost(carrierParentNode->getCost() + otherParentNode->getCost() / 1000.0);
candidate.moveRemains = carrierParentNode->moveRemains;
if(carrierParentNode->turns < otherParentNode->turns)
@ -668,7 +775,7 @@ ExchangeCandidate AINodeStorage::calculateExchange(
+ carrierParentNode->moveRemains / (float)moveRemains;
candidate.turns = otherParentNode->turns;
candidate.cost += waitingCost;
candidate.setCost(candidate.getCost() + waitingCost);
candidate.moveRemains = moveRemains;
}
@ -873,7 +980,7 @@ struct TowmPortalFinder
continue;
}
if(!bestNode || bestNode->cost > node->cost)
if(!bestNode || bestNode->getCost() > node->getCost())
bestNode = node;
}
@ -895,9 +1002,9 @@ struct TowmPortalFinder
AIPathNode * node = nodeOptional.get();
float movementCost = (float)movementNeeded / (float)hero->maxMovePoints(EPathfindingLayer::LAND);
movementCost += bestNode->cost;
movementCost += bestNode->getCost();
if(node->action == CGPathNode::UNKNOWN || node->cost > movementCost)
if(node->action == CGPathNode::UNKNOWN || node->getCost() > movementCost)
{
nodeStorage->commit(
node,
@ -1009,7 +1116,7 @@ bool AINodeStorage::hasBetterChain(
if(node.danger <= candidateNode->danger && candidateNode->actor == node.actor->battleActor)
{
if(node.cost < candidateNode->cost)
if(node.getCost() < candidateNode->getCost())
{
#if PATHFINDER_TRACE_LEVEL >= 2
logAi->trace(
@ -1033,7 +1140,7 @@ bool AINodeStorage::hasBetterChain(
auto candidateArmyValue = candidateActor->armyValue - candidateNode->armyLoss;
if(nodeArmyValue > candidateArmyValue
&& node.cost <= candidateNode->cost)
&& node.getCost() <= candidateNode->getCost())
{
#if PATHFINDER_TRACE_LEVEL >= 2
logAi->trace(
@ -1052,10 +1159,10 @@ bool AINodeStorage::hasBetterChain(
{
if(nodeArmyValue == candidateArmyValue
&& nodeActor->heroFightingStrength >= candidateActor->heroFightingStrength
&& node.cost <= candidateNode->cost)
&& node.getCost() <= candidateNode->getCost())
{
if(nodeActor->heroFightingStrength == candidateActor->heroFightingStrength
&& node.cost == candidateNode->cost
&& node.getCost() == candidateNode->getCost()
&& &node < candidateNode)
{
continue;
@ -1141,7 +1248,8 @@ void AINodeStorage::fillChainInfo(const AIPathNode * node, AIPath & path, int pa
//if(node->actor->hero->visitablePos() != node->coord)
{
AIPathNodeInfo pathNode;
pathNode.cost = node->cost;
pathNode.cost = node->getCost();
pathNode.targetHero = node->actor->hero;
pathNode.chainMask = node->actor->chainMask;
pathNode.specialAction = node->specialAction;

View File

@ -23,6 +23,14 @@
#include "Actions/SpecialAction.h"
#include "Actors.h"
namespace AIPathfinding
{
const int BUCKET_COUNT = 11;
const int BUCKET_SIZE = GameConstants::MAX_HEROES_PER_PLAYER;
const int NUM_CHAINS = BUCKET_COUNT * BUCKET_SIZE;
const int THREAD_COUNT = 8;
}
struct AIPathNode : public CGPathNode
{
uint64_t danger;
@ -228,28 +236,14 @@ public:
return (uint64_t)(armyValue * ratio * ratio * ratio);
}
private:
STRONG_INLINE
void resetTile(const int3 & tile, EPathfindingLayer layer, CGPathNode::EAccessibility accessibility);
STRONG_INLINE int getBucket(const ChainActor * actor) const
{
return ((uintptr_t)actor * 395) % AIPathfinding::BUCKET_COUNT;
}
void calculateHeroChain(
AIPathNode * srcNode,
const std::vector<AIPathNode *> & variants,
std::vector<ExchangeCandidate> & result) const;
void calculateHeroChain(
AIPathNode * carrier,
AIPathNode * other,
std::vector<ExchangeCandidate> & result) const;
void cleanupInefectiveChains(std::vector<ExchangeCandidate> & result) const;
void addHeroChain(const std::vector<ExchangeCandidate> & result);
void calculateTownPortalTeleportations(std::vector<CGPathNode *> & neighbours);
void fillChainInfo(const AIPathNode * node, AIPath & path, int parentIndex) const;
ExchangeCandidate calculateExchange(
ChainActor * exchangeActor,
AIPathNode * carrierParentNode,
AIPathNode * otherParentNode) const;
};

View File

@ -114,7 +114,7 @@ namespace AIPathfinding
source->manaCost);
#endif
return hero->mana >= source->manaCost + getManaCost(hero);
return hero->mana >= (si32)(source->manaCost + getManaCost(hero));
}
std::string SummonBoatAction::toString() const

View File

@ -145,14 +145,11 @@ void HeroActor::setupSpecialActors()
}
}
ChainActor * ChainActor::exchange(const ChainActor * specialActor, const ChainActor * other) const
ChainActor * ChainActor::tryExchange(const ChainActor * specialActor, const ChainActor * other) const
{
return baseActor->exchange(specialActor, other);
}
if(!isMovable) return nullptr;
bool ChainActor::canExchange(const ChainActor * other) const
{
return isMovable && baseActor->canExchange(other);
return baseActor->tryExchange(specialActor, other);
}
namespace vstd
@ -172,71 +169,12 @@ namespace vstd
}
}
bool HeroActor::canExchange(const ChainActor * other) const
{
return exchangeMap->canExchange(other);
}
bool HeroExchangeMap::canExchange(const ChainActor * other)
{
return vstd::getOrCompute(canExchangeCache, other, [&](bool & result) {
result = (actor->chainMask & other->chainMask) == 0;
if(result)
{
TResources resources = ai->cb->getResourceAmount();
if(!resources.canAfford(actor->armyCost + other->armyCost))
{
result = false;
#if PATHFINDER_TRACE_LEVEL >= 2
logAi->trace(
"Can not afford exchange because of total cost %s but we have %s",
(actor->armyCost + other->armyCost).toString(),
resources.toString());
#endif
return;
}
TResources availableResources = resources - actor->armyCost - other->armyCost;
auto upgradeInfo = ai->armyManager->calculateCreateresUpgrade(
actor->creatureSet,
other->getActorObject(),
availableResources);
uint64_t reinforcment = upgradeInfo.upgradeValue;
if(other->creatureSet->Slots().size())
reinforcment += ai->armyManager->howManyReinforcementsCanGet(actor->hero, actor->creatureSet, other->creatureSet);
auto obj = other->getActorObject();
if(obj && obj->ID == Obj::TOWN)
{
reinforcment += ai->armyManager->howManyReinforcementsCanBuy(
actor->creatureSet,
ai->cb->getTown(obj->id),
availableResources - upgradeInfo.upgradeCost);
}
#if PATHFINDER_TRACE_LEVEL >= 2
logAi->trace(
"Exchange %s->%s reinforcement: %d, %f%%",
actor->toString(),
other->toString(),
reinforcment,
100.0f * reinforcment / actor->armyValue);
#endif
result = reinforcment > actor->armyValue / 10 || reinforcment > 1000;
}
});
}
ChainActor * HeroActor::exchange(const ChainActor * specialActor, const ChainActor * other) const
ChainActor * HeroActor::tryExchange(const ChainActor * specialActor, const ChainActor * other) const
{
const ChainActor * otherBase = other->baseActor;
HeroActor * result = exchangeMap->exchange(otherBase);
HeroActor * result = exchangeMap->tryExchange(otherBase);
if(!result) return nullptr;
if(specialActor == this)
return result;
@ -250,7 +188,7 @@ ChainActor * HeroActor::exchange(const ChainActor * specialActor, const ChainAct
}
HeroExchangeMap::HeroExchangeMap(const HeroActor * actor, const Nullkiller * ai)
:actor(actor), ai(ai)
:actor(actor), ai(ai), sync()
{
}
@ -258,6 +196,8 @@ HeroExchangeMap::~HeroExchangeMap()
{
for(auto & exchange : exchangeMap)
{
if(!exchange.second) continue;
delete exchange.second->creatureSet;
delete exchange.second;
}
@ -265,44 +205,91 @@ HeroExchangeMap::~HeroExchangeMap()
exchangeMap.clear();
}
HeroActor * HeroExchangeMap::exchange(const ChainActor * other)
HeroActor * HeroExchangeMap::tryExchange(const ChainActor * other)
{
HeroActor * result;
auto position = exchangeMap.find(other);
if(vstd::contains(exchangeMap, other))
result = exchangeMap.at(other);
else
if(position != exchangeMap.end())
{
TResources availableResources = ai->cb->getResourceAmount() - actor->armyCost - other->armyCost;
HeroExchangeArmy * upgradedInitialArmy = tryUpgrade(actor->creatureSet, other->getActorObject(), availableResources);
HeroExchangeArmy * newArmy;
if(other->creatureSet->Slots().size())
{
if(upgradedInitialArmy)
{
newArmy = pickBestCreatures(upgradedInitialArmy, other->creatureSet);
newArmy->armyCost = upgradedInitialArmy->armyCost;
newArmy->requireBuyArmy = upgradedInitialArmy->requireBuyArmy;
return position->second;
}
delete upgradedInitialArmy;
}
else
{
newArmy = pickBestCreatures(actor->creatureSet, other->creatureSet);
}
auto inserted = exchangeMap.insert(std::pair<const ChainActor *, HeroActor *>(other, nullptr));
if(!inserted.second)
{
return inserted.first->second; // already inserted
}
position = inserted.first;
auto differentMasks = (actor->chainMask & other->chainMask) == 0;
if(!differentMasks) return nullptr;
TResources resources = ai->cb->getResourceAmount();
if(!resources.canAfford(actor->armyCost + other->armyCost))
{
#if PATHFINDER_TRACE_LEVEL >= 2
logAi->trace(
"Can not afford exchange because of total cost %s but we have %s",
(actor->armyCost + other->armyCost).toString(),
resources.toString());
#endif
return nullptr;
}
TResources availableResources = resources - actor->armyCost - other->armyCost;
HeroExchangeArmy * upgradedInitialArmy = tryUpgrade(actor->creatureSet, other->getActorObject(), availableResources);
HeroExchangeArmy * newArmy;
if(other->creatureSet->Slots().size())
{
if(upgradedInitialArmy)
{
newArmy = pickBestCreatures(upgradedInitialArmy, other->creatureSet);
newArmy->armyCost = upgradedInitialArmy->armyCost;
newArmy->requireBuyArmy = upgradedInitialArmy->requireBuyArmy;
delete upgradedInitialArmy;
}
else
{
newArmy = upgradedInitialArmy;
newArmy = pickBestCreatures(actor->creatureSet, other->creatureSet);
}
result = new HeroActor(actor, other, newArmy, ai);
result->armyCost += newArmy->armyCost;
exchangeMap[other] = result;
}
else
{
newArmy = upgradedInitialArmy;
}
return result;
if(!newArmy) return nullptr;
auto reinforcement = newArmy->getArmyStrength() - actor->creatureSet->getArmyStrength();
#if PATHFINDER_TRACE_LEVEL >= 2
logAi->trace(
"Exchange %s->%s reinforcement: %d, %f%%",
actor->toString(),
other->toString(),
reinforcement,
100.0f * reinforcement / actor->armyValue);
#endif
if(reinforcement <= actor->armyValue / 10 && reinforcement < 1000)
{
delete newArmy;
return nullptr;
}
HeroActor * exchanged = new HeroActor(actor, other, newArmy, ai);
exchanged->armyCost += newArmy->armyCost;
position->second = exchanged;
return exchanged;
}
HeroExchangeArmy * HeroExchangeMap::tryUpgrade(

View File

@ -65,14 +65,13 @@ public:
ChainActor(){}
virtual bool canExchange(const ChainActor * other) const;
virtual std::string toString() const;
ChainActor * exchange(const ChainActor * other) const { return exchange(this, other); }
ChainActor * tryExchange(const ChainActor * other) const { return tryExchange(this, other); }
void setBaseActor(HeroActor * base);
virtual const CGObjectInstance * getActorObject() const { return hero; }
protected:
virtual ChainActor * exchange(const ChainActor * specialActor, const ChainActor * other) const;
virtual ChainActor * tryExchange(const ChainActor * specialActor, const ChainActor * other) const;
};
class HeroExchangeMap
@ -80,15 +79,14 @@ class HeroExchangeMap
private:
const HeroActor * actor;
std::map<const ChainActor *, HeroActor *> exchangeMap;
std::map<const ChainActor *, bool> canExchangeCache;
const Nullkiller * ai;
boost::shared_mutex sync;
public:
HeroExchangeMap(const HeroActor * actor, const Nullkiller * ai);
~HeroExchangeMap();
HeroActor * exchange(const ChainActor * other);
bool canExchange(const ChainActor * other);
HeroActor * tryExchange(const ChainActor * other);
private:
HeroExchangeArmy * pickBestCreatures(const CCreatureSet * army1, const CCreatureSet * army2) const;
@ -113,10 +111,8 @@ public:
HeroActor(const CGHeroInstance * hero, HeroRole heroRole, uint64_t chainMask, const Nullkiller * ai);
HeroActor(const ChainActor * carrier, const ChainActor * other, const HeroExchangeArmy * army, const Nullkiller * ai);
virtual bool canExchange(const ChainActor * other) const override;
protected:
virtual ChainActor * exchange(const ChainActor * specialActor, const ChainActor * other) const override;
virtual ChainActor * tryExchange(const ChainActor * specialActor, const ChainActor * other) const override;
};
class ObjectActor : public ChainActor

View File

@ -143,7 +143,7 @@ namespace AIPathfinding
destination.node->layer,
destinationNode->actor->resourceActor);
if(!questNode || questNode.get()->cost < destination.cost)
if(!questNode || questNode.get()->getCost() < destination.cost)
{
return false;
}

View File

@ -502,7 +502,7 @@ void VCAI::init(std::shared_ptr<CCallback> CB)
myCb->unlockGsWhenWaiting = true;
nullkiller->init(CB, playerID);
retrieveVisitableObjs();
}
@ -764,6 +764,7 @@ void VCAI::makeTurn()
}
catch (boost::thread_interrupted & e)
{
(void)e;
logAi->debug("Making turn thread has been interrupted. We'll end without calling endTurn.");
return;
}
@ -1195,7 +1196,7 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h)
logAi->error("Hero %s cannot reach %s.", h->name, dst.toString());
return true;
}
int i = path.nodes.size() - 1;
int i = (int)path.nodes.size() - 1;
auto getObj = [&](int3 coord, bool ignoreHero)
{
@ -1391,12 +1392,12 @@ void VCAI::tryRealize(Goals::Trade & g) //trade
int toGive, toGet;
m->getOffer(res, g.resID, toGive, toGet, EMarketMode::RESOURCE_RESOURCE);
toGive = toGive * (it->resVal / toGive); //round down
toGive = static_cast<int>(toGive * (it->resVal / toGive)); //round down
//TODO trade only as much as needed
if (toGive) //don't try to sell 0 resources
{
cb->trade(obj, EMarketMode::RESOURCE_RESOURCE, res, g.resID, toGive);
accquiredResources = toGet * (it->resVal / toGive);
accquiredResources = static_cast<int>(toGet * (it->resVal / toGive));
logAi->debug("Traded %d of %s for %d of %s at %s", toGive, res, accquiredResources, g.resID, obj->getObjectName());
}
if (cb->getResourceAmount((Res::ERes)g.resID) >= g.value)
@ -1482,6 +1483,7 @@ void VCAI::finish()
{
//we want to lock to avoid multiple threads from calling makingTurn->join() at same time
boost::lock_guard<boost::mutex> multipleCleanupGuard(turnInterruptionMutex);
if(makingTurn)
{
makingTurn->interrupt();
@ -1617,7 +1619,7 @@ void AIStatus::removeQuery(QueryID ID)
int AIStatus::getQueriesCount()
{
boost::unique_lock<boost::mutex> lock(mx);
return remainingQueries.size();
return static_cast<int>(remainingQueries.size());
}
void AIStatus::startedTurn()