diff --git a/AI/Nullkiller/AIUtility.cpp b/AI/Nullkiller/AIUtility.cpp index b6ecb376c..b17fd746f 100644 --- a/AI/Nullkiller/AIUtility.cpp +++ b/AI/Nullkiller/AIUtility.cpp @@ -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) diff --git a/AI/Nullkiller/CMakeLists.txt b/AI/Nullkiller/CMakeLists.txt index 498a9999e..0a3fb5348 100644 --- a/AI/Nullkiller/CMakeLists.txt +++ b/AI/Nullkiller/CMakeLists.txt @@ -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}) diff --git a/AI/Nullkiller/Engine/FuzzyEngines.cpp b/AI/Nullkiller/Engine/FuzzyEngines.cpp index 90ed106d8..cd757cc45 100644 --- a/AI/Nullkiller/Engine/FuzzyEngines.cpp +++ b/AI/Nullkiller/Engine/FuzzyEngines.cpp @@ -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(walkersStrength / totalStrength); + as.shooters = static_cast(shootersStrength / totalStrength); + as.flyers = static_cast(flyersStrength / totalStrength); as.maxSpeed = maxSpeed; assert(as.walkers || as.flyers || as.shooters); return as; diff --git a/AI/Nullkiller/Engine/Nullkiller.cpp b/AI/Nullkiller/Engine/Nullkiller.cpp index 89048f110..2afa9084b 100644 --- a/AI/Nullkiller/Engine/Nullkiller.cpp +++ b/AI/Nullkiller/Engine/Nullkiller.cpp @@ -262,4 +262,4 @@ void Nullkiller::makeTurn() return; } } -} +} \ No newline at end of file diff --git a/AI/Nullkiller/Engine/Nullkiller.h b/AI/Nullkiller/Engine/Nullkiller.h index fe2c7768f..8800b6d6c 100644 --- a/AI/Nullkiller/Engine/Nullkiller.h +++ b/AI/Nullkiller/Engine/Nullkiller.h @@ -9,6 +9,8 @@ */ #pragma once +#include + #include "PriorityEvaluator.h" #include "FuzzyHelper.h" #include "AIMemory.h" diff --git a/AI/Nullkiller/Goals/GatherArmy.cpp b/AI/Nullkiller/Goals/GatherArmy.cpp index 826037689..df6620a81 100644 --- a/AI/Nullkiller/Goals/GatherArmy.cpp +++ b/AI/Nullkiller/Goals/GatherArmy.cpp @@ -148,7 +148,7 @@ TGoalVec GatherArmy::getAllPossibleSubgoals() { auto dwelling = dynamic_cast(obj); - ui32 val = std::min(value, ai->ah->howManyReinforcementsCanBuy(hero.get(), dwelling)); + ui32 val = std::min((ui32)value, (ui32)ai->ah->howManyReinforcementsCanBuy(hero.get(), dwelling)); if(val) { diff --git a/AI/Nullkiller/Goals/Win.cpp b/AI/Nullkiller/Goals/Win.cpp index a692ec46a..a0c96135d 100644 --- a/AI/Nullkiller/Goals/Win.cpp +++ b/AI/Nullkiller/Goals/Win.cpp @@ -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()) { diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp index 494cf6dcc..db43a9cd8 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp @@ -8,6 +8,7 @@ * */ #include "StdInc.h" +#include #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> AISharedStorage::shared; std::set commitedTiles; std::set 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(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(0, sizes.x), [&](const blocked_range& 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(pos, tile, fow, player, gs)); - if(useFlying) - resetTile(pos, ELayer::AIR, PathfinderUtil::evaluateAccessibility(pos, tile, fow, player, gs)); - if(useWaterWalking) - resetTile(pos, ELayer::WATER, PathfinderUtil::evaluateAccessibility(pos, tile, fow, player, gs)); - break; + case ETerrainType::WATER: + resetTile(pos, ELayer::SAIL, PathfinderUtil::evaluateAccessibility(pos, tile, fow, player, gs)); + if(useFlying) + resetTile(pos, ELayer::AIR, PathfinderUtil::evaluateAccessibility(pos, tile, fow, player, gs)); + if(useWaterWalking) + resetTile(pos, ELayer::WATER, PathfinderUtil::evaluateAccessibility(pos, tile, fow, player, gs)); + break; - default: - resetTile(pos, ELayer::LAND, PathfinderUtil::evaluateAccessibility(pos, tile, fow, player, gs)); - if(useFlying) - resetTile(pos, ELayer::AIR, PathfinderUtil::evaluateAccessibility(pos, tile, fow, player, gs)); - break; + default: + resetTile(pos, ELayer::LAND, PathfinderUtil::evaluateAccessibility(pos, tile, fow, player, gs)); + if(useFlying) + resetTile(pos, ELayer::AIR, PathfinderUtil::evaluateAccessibility(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 AINodeStorage::getOrCreateNode( std::vector AINodeStorage::getInitialNodes() { if(heroChainPass) - { +{ calculateTownPortalTeleportations(heroChain); return heroChain; @@ -184,7 +193,7 @@ std::vector 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 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 existingChains; + std::vector newChains; + uint64_t chainMask; + int heroChainTurn; + std::vector heroChain; + const std::vector & tiles; + +public: + HeroChainCalculationTask( + AINodeStorage & storage, AISharedStorage & nodes, const std::vector & 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& 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 & variants, + std::vector & result); + + void calculateHeroChain( + AIPathNode * carrier, + AIPathNode * other, + std::vector & result); + + void cleanupInefectiveChains(std::vector & result) const; + void addHeroChain(const std::vector & result); + + ExchangeCandidate calculateExchange( + ChainActor * exchangeActor, + AIPathNode * carrierParentNode, + AIPathNode * otherParentNode) const; + + void flushResult(std::vector & result) + { + vstd::concatenate(result, heroChain); + } +}; + bool AINodeStorage::calculateHeroChain() { heroChainPass = EHeroChainPass::CHAIN; - heroChain.resize(0); + heroChain.clear(); - std::vector existingChains; - std::vector newChains; + std::vector 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(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 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 & result) const +void HeroChainCalculationTask::cleanupInefectiveChains(std::vector & 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 & variants, - std::vector & result) const + std::vector & result) { for(AIPathNode * node : variants) { @@ -531,16 +629,15 @@ void AINodeStorage::calculateHeroChain( } } -void AINodeStorage::calculateHeroChain( +void HeroChainCalculationTask::calculateHeroChain( AIPathNode * carrier, AIPathNode * other, - std::vector & result) const + std::vector & 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 & result) +void HeroChainCalculationTask::addHeroChain(const std::vector & 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 & 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 & 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; diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.h b/AI/Nullkiller/Pathfinding/AINodeStorage.h index 29607e20e..cf9e910cc 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.h +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.h @@ -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 & variants, - std::vector & result) const; - - void calculateHeroChain( - AIPathNode * carrier, - AIPathNode * other, - std::vector & result) const; - - void cleanupInefectiveChains(std::vector & result) const; - void addHeroChain(const std::vector & result); void calculateTownPortalTeleportations(std::vector & neighbours); void fillChainInfo(const AIPathNode * node, AIPath & path, int parentIndex) const; - - ExchangeCandidate calculateExchange( - ChainActor * exchangeActor, - AIPathNode * carrierParentNode, - AIPathNode * otherParentNode) const; }; diff --git a/AI/Nullkiller/Pathfinding/Actions/BoatActions.cpp b/AI/Nullkiller/Pathfinding/Actions/BoatActions.cpp index 81802c7ad..b09c93c95 100644 --- a/AI/Nullkiller/Pathfinding/Actions/BoatActions.cpp +++ b/AI/Nullkiller/Pathfinding/Actions/BoatActions.cpp @@ -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 diff --git a/AI/Nullkiller/Pathfinding/Actors.cpp b/AI/Nullkiller/Pathfinding/Actors.cpp index cfe2a5328..1e239456d 100644 --- a/AI/Nullkiller/Pathfinding/Actors.cpp +++ b/AI/Nullkiller/Pathfinding/Actors.cpp @@ -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(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( diff --git a/AI/Nullkiller/Pathfinding/Actors.h b/AI/Nullkiller/Pathfinding/Actors.h index 513614faf..3701a1d81 100644 --- a/AI/Nullkiller/Pathfinding/Actors.h +++ b/AI/Nullkiller/Pathfinding/Actors.h @@ -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 exchangeMap; - std::map 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 diff --git a/AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.cpp b/AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.cpp index 781eaac12..8ffcf1be7 100644 --- a/AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.cpp +++ b/AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.cpp @@ -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; } diff --git a/AI/Nullkiller/VCAI.cpp b/AI/Nullkiller/VCAI.cpp index b62e041ad..5b2e6798f 100644 --- a/AI/Nullkiller/VCAI.cpp +++ b/AI/Nullkiller/VCAI.cpp @@ -502,7 +502,7 @@ void VCAI::init(std::shared_ptr 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(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(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 multipleCleanupGuard(turnInterruptionMutex); + if(makingTurn) { makingTurn->interrupt(); @@ -1617,7 +1619,7 @@ void AIStatus::removeQuery(QueryID ID) int AIStatus::getQueriesCount() { boost::unique_lock lock(mx); - return remainingQueries.size(); + return static_cast(remainingQueries.size()); } void AIStatus::startedTurn()