1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-08-13 19:54:17 +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 * ln = ai->myCb->getPathsInfo(hero)->getPathInfo(lhs->visitablePos());
const CGPathNode * rn = ai->myCb->getPathsInfo(hero)->getPathInfo(rhs->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) 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) target_link_libraries(VCAI PRIVATE fl-static vcmi)
endif() endif()
target_link_libraries(VCAI PRIVATE TBB::tbb)
vcmi_set_output_dir(VCAI "AI") vcmi_set_output_dir(VCAI "AI")
set_target_properties(VCAI PROPERTIES ${PCH_PROPERTIES}) set_target_properties(VCAI PROPERTIES ${PCH_PROPERTIES})

View File

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

View File

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

View File

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

View File

@@ -148,7 +148,7 @@ TGoalVec GatherArmy::getAllPossibleSubgoals()
{ {
auto dwelling = dynamic_cast<const CGDwelling *>(obj); 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) if(val)
{ {

View File

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

View File

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

View File

@@ -23,6 +23,14 @@
#include "Actions/SpecialAction.h" #include "Actions/SpecialAction.h"
#include "Actors.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 struct AIPathNode : public CGPathNode
{ {
uint64_t danger; uint64_t danger;
@@ -228,28 +236,14 @@ public:
return (uint64_t)(armyValue * ratio * ratio * ratio); return (uint64_t)(armyValue * ratio * ratio * ratio);
} }
private:
STRONG_INLINE STRONG_INLINE
void resetTile(const int3 & tile, EPathfindingLayer layer, CGPathNode::EAccessibility accessibility); 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 calculateTownPortalTeleportations(std::vector<CGPathNode *> & neighbours);
void fillChainInfo(const AIPathNode * node, AIPath & path, int parentIndex) const; 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); source->manaCost);
#endif #endif
return hero->mana >= source->manaCost + getManaCost(hero); return hero->mana >= (si32)(source->manaCost + getManaCost(hero));
} }
std::string SummonBoatAction::toString() const 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 baseActor->tryExchange(specialActor, other);
{
return isMovable && baseActor->canExchange(other);
} }
namespace vstd namespace vstd
@@ -172,71 +169,12 @@ namespace vstd
} }
} }
bool HeroActor::canExchange(const ChainActor * other) const ChainActor * HeroActor::tryExchange(const ChainActor * specialActor, 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
{ {
const ChainActor * otherBase = other->baseActor; const ChainActor * otherBase = other->baseActor;
HeroActor * result = exchangeMap->exchange(otherBase); HeroActor * result = exchangeMap->tryExchange(otherBase);
if(!result) return nullptr;
if(specialActor == this) if(specialActor == this)
return result; return result;
@@ -250,7 +188,7 @@ ChainActor * HeroActor::exchange(const ChainActor * specialActor, const ChainAct
} }
HeroExchangeMap::HeroExchangeMap(const HeroActor * actor, const Nullkiller * ai) 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) for(auto & exchange : exchangeMap)
{ {
if(!exchange.second) continue;
delete exchange.second->creatureSet; delete exchange.second->creatureSet;
delete exchange.second; delete exchange.second;
} }
@@ -265,44 +205,91 @@ HeroExchangeMap::~HeroExchangeMap()
exchangeMap.clear(); 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)) if(position != exchangeMap.end())
result = exchangeMap.at(other);
else
{ {
TResources availableResources = ai->cb->getResourceAmount() - actor->armyCost - other->armyCost; return position->second;
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; auto inserted = exchangeMap.insert(std::pair<const ChainActor *, HeroActor *>(other, nullptr));
}
else if(!inserted.second)
{ {
newArmy = pickBestCreatures(actor->creatureSet, other->creatureSet); 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 else
{ {
newArmy = upgradedInitialArmy; newArmy = pickBestCreatures(actor->creatureSet, other->creatureSet);
} }
}
result = new HeroActor(actor, other, newArmy, ai); else
result->armyCost += newArmy->armyCost; {
exchangeMap[other] = result; 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( HeroExchangeArmy * HeroExchangeMap::tryUpgrade(

View File

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

View File

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

View File

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