From 9a203b8af9dd6389f3f9946eaf2c50e930b8f5b5 Mon Sep 17 00:00:00 2001 From: Andrii Danylchenko Date: Sun, 16 May 2021 15:08:56 +0300 Subject: [PATCH] Nullkiller: parallel object clusterization, stabilization --- AI/Nullkiller/AIUtility.cpp | 51 ++++- AI/Nullkiller/AIUtility.h | 100 ++++++++- AI/Nullkiller/Analyzers/BuildAnalyzer.cpp | 18 +- .../Analyzers/DangerHitMapAnalyzer.cpp | 2 +- AI/Nullkiller/Analyzers/ObjectClusterizer.cpp | 208 +++++++++--------- AI/Nullkiller/Analyzers/ObjectClusterizer.h | 8 +- .../Behaviors/CaptureObjectsBehavior.cpp | 2 +- AI/Nullkiller/Engine/Nullkiller.cpp | 30 ++- AI/Nullkiller/Engine/Nullkiller.h | 3 +- AI/Nullkiller/Engine/PriorityEvaluator.cpp | 48 ++-- AI/Nullkiller/Pathfinding/AINodeStorage.cpp | 7 +- .../Pathfinding/Actions/BoatActions.h | 5 +- AI/Nullkiller/Pathfinding/Actors.cpp | 13 +- .../Rules/AILayerTransitionRule.cpp | 2 +- 14 files changed, 332 insertions(+), 165 deletions(-) diff --git a/AI/Nullkiller/AIUtility.cpp b/AI/Nullkiller/AIUtility.cpp index b17fd746f..2a4bcef68 100644 --- a/AI/Nullkiller/AIUtility.cpp +++ b/AI/Nullkiller/AIUtility.cpp @@ -115,6 +115,32 @@ const CGHeroInstance * HeroPtr::get(bool doWeExpectNull) const return h; } +const CGHeroInstance * HeroPtr::get(CCallback * cb, bool doWeExpectNull) const +{ + //TODO? check if these all assertions every time we get info about hero affect efficiency + // + //behave terribly when attempting unauthorized access to hero that is not ours (or was lost) + assert(doWeExpectNull || h); + + if(h) + { + auto obj = cb->getObj(hid); + //const bool owned = obj && obj->tempOwner == ai->playerID; + + if(doWeExpectNull && !obj) + { + return nullptr; + } + else + { + assert(obj); + //assert(owned); + } + } + + return h; +} + const CGHeroInstance * HeroPtr::operator->() const { return get(); @@ -251,6 +277,11 @@ bool canBeEmbarkmentPoint(const TerrainTile * t, bool fromWater) return false; } +bool isObjectPassable(const Nullkiller * ai, const CGObjectInstance * obj) +{ + return isObjectPassable(obj, ai->playerID, ai->cb->getPlayerRelations(obj->tempOwner, ai->playerID)); +} + bool isObjectPassable(const CGObjectInstance * obj) { return isObjectPassable(obj, ai->playerID, cb->getPlayerRelations(obj->tempOwner, ai->playerID)); @@ -355,7 +386,7 @@ uint64_t timeElapsed(boost::chrono::time_point star } // todo: move to obj manager -bool shouldVisit(const CGHeroInstance * h, const CGObjectInstance * obj) +bool shouldVisit(const Nullkiller * ai, const CGHeroInstance * h, const CGObjectInstance * obj) { switch(obj->ID) { @@ -364,7 +395,7 @@ bool shouldVisit(const CGHeroInstance * h, const CGObjectInstance * obj) return obj->tempOwner != h->tempOwner; //do not visit our towns at random case Obj::BORDER_GATE: { - for(auto q : ai->myCb->getMyQuests()) + for(auto q : ai->cb->getMyQuests()) { if(q.obj == obj) { @@ -378,7 +409,7 @@ bool shouldVisit(const CGHeroInstance * h, const CGObjectInstance * obj) case Obj::SEER_HUT: case Obj::QUEST_GUARD: { - for(auto q : ai->myCb->getMyQuests()) + for(auto q : ai->cb->getMyQuests()) { if(q.obj == obj) { @@ -403,7 +434,7 @@ bool shouldVisit(const CGHeroInstance * h, const CGObjectInstance * obj) { if(level.first && h->getSlotFor(CreatureID(c)) != SlotID() - && cb->getResourceAmount().canAfford(c.toCreature()->cost)) + && ai->cb->getResourceAmount().canAfford(c.toCreature()->cost)) { return true; } @@ -429,7 +460,7 @@ bool shouldVisit(const CGHeroInstance * h, const CGObjectInstance * obj) case Obj::SCHOOL_OF_MAGIC: case Obj::SCHOOL_OF_WAR: { - if(cb->getResourceAmount(Res::GOLD) < 1000) + if(ai->cb->getResourceAmount(Res::GOLD) < 1000) return false; break; } @@ -439,10 +470,10 @@ bool shouldVisit(const CGHeroInstance * h, const CGObjectInstance * obj) break; case Obj::TREE_OF_KNOWLEDGE: { - if(ai->nullkiller->heroManager->getHeroRole(h) == HeroRole::SCOUT) + if(ai->heroManager->getHeroRole(h) == HeroRole::SCOUT) return false; - TResources myRes = cb->getResourceAmount(); + TResources myRes = ai->cb->getResourceAmount(); if(myRes[Res::GOLD] < 2000 || myRes[Res::GEMS] < 10) return false; break; @@ -450,14 +481,14 @@ bool shouldVisit(const CGHeroInstance * h, const CGObjectInstance * obj) case Obj::MAGIC_WELL: return h->mana < h->manaLimit(); case Obj::PRISON: - return ai->myCb->getHeroesInfo().size() < VLC->modh->settings.MAX_HEROES_ON_MAP_PER_PLAYER; + return ai->cb->getHeroesInfo().size() < VLC->modh->settings.MAX_HEROES_ON_MAP_PER_PLAYER; case Obj::TAVERN: { //TODO: make AI actually recruit heroes //TODO: only on request - if(ai->myCb->getHeroesInfo().size() >= VLC->modh->settings.MAX_HEROES_ON_MAP_PER_PLAYER) + if(ai->cb->getHeroesInfo().size() >= VLC->modh->settings.MAX_HEROES_ON_MAP_PER_PLAYER) return false; - else if(cb->getResourceAmount(Res::GOLD) < GameConstants::HERO_GOLD_COST) + else if(ai->cb->getResourceAmount(Res::GOLD) < GameConstants::HERO_GOLD_COST) return false; break; } diff --git a/AI/Nullkiller/AIUtility.h b/AI/Nullkiller/AIUtility.h index f1595a2e5..1807f451c 100644 --- a/AI/Nullkiller/AIUtility.h +++ b/AI/Nullkiller/AIUtility.h @@ -18,8 +18,12 @@ #include "../../lib/mapObjects/CObjectHandler.h" #include "../../lib/mapObjects/CGHeroInstance.h" #include "../../lib/CPathfinder.h" +#include + +using namespace tbb; class CCallback; +class Nullkiller; struct creInfo; typedef const int3 & crint3; @@ -72,6 +76,7 @@ public: } const CGHeroInstance * get(bool doWeExpectNull = false) const; + const CGHeroInstance * get(CCallback * cb, bool doWeExpectNull = false) const; bool validAndSet() const; @@ -168,6 +173,7 @@ void foreach_neighbour(CCallback * cbp, const int3 & pos, std::function start); // todo: move to obj manager -bool shouldVisit(const CGHeroInstance * h, const CGObjectInstance * obj); +bool shouldVisit(const Nullkiller * ai, const CGHeroInstance * h, const CGObjectInstance * obj); + +template +void pforeachTilePos(crint3 mapSize, TFunc fn) +{ + parallel_for(blocked_range(0, mapSize.x), [&](const blocked_range& r) + { + int3 pos; + + for(pos.x = r.begin(); pos.x != r.end(); ++pos.x) + { + for(pos.y = 0; pos.y < mapSize.y; ++pos.y) + { + for(pos.z = 0; pos.z < mapSize.z; ++pos.z) + { + fn(pos); + } + } + } + }); +} class CDistanceSorter { @@ -197,3 +223,75 @@ public: } bool operator()(const CGObjectInstance * lhs, const CGObjectInstance * rhs) const; }; + +template +class SharedPool +{ +public: + struct External_Deleter + { + explicit External_Deleter(std::weak_ptr* > pool) + : pool(pool) + { + } + + void operator()(T * ptr) + { + std::unique_ptr uptr(ptr); + + if(auto pool_ptr = pool.lock()) + { + (*pool_ptr.get())->add(std::move(uptr)); + } + } + + private: + std::weak_ptr* > pool; + }; + +public: + using ptr_type = std::unique_ptr; + + SharedPool(std::function()> elementFactory) + : elementFactory(elementFactory), pool(), sync(), instance_tracker(new SharedPool*(this)) + {} + + void add(std::unique_ptr t) + { + std::lock_guard lock(sync); + pool.push_back(std::move(t)); + } + + ptr_type acquire() + { + std::lock_guard lock(sync); + bool poolIsEmpty = pool.empty(); + T * element = poolIsEmpty + ? elementFactory().release() + : pool.back().release(); + + ptr_type tmp( + element, + External_Deleter(std::weak_ptr*>(instance_tracker))); + + if(!poolIsEmpty) pool.pop_back(); + + return std::move(tmp); + } + + bool empty() const + { + return pool.empty(); + } + + size_t size() const + { + return pool.size(); + } + +private: + std::vector> pool; + std::function()> elementFactory; + std::shared_ptr *> instance_tracker; + std::mutex sync; +}; diff --git a/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp b/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp index d7d5d1f1f..14105a48f 100644 --- a/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp +++ b/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp @@ -12,8 +12,6 @@ #include "../../../lib/mapping/CMap.h" //for victory conditions #include "../Engine/Nullkiller.h" -extern boost::thread_specific_ptr cb; - void BuildAnalyzer::updateTownDwellings(TownDevelopmentInfo & developmentInfo) { auto townInfo = developmentInfo.town->town; @@ -71,7 +69,7 @@ void BuildAnalyzer::updateOtherBuildings(TownDevelopmentInfo & developmentInfo) {BuildingID::TOWN_HALL, BuildingID::CITY_HALL, BuildingID::CAPITOL} }; - if(developmentInfo.existingDwellings.size() >= 2 && cb->getDate(Date::DAY_OF_WEEK) > boost::date_time::Friday) + if(developmentInfo.existingDwellings.size() >= 2 && ai->cb->getDate(Date::DAY_OF_WEEK) > boost::date_time::Friday) { otherBuildings.push_back({BuildingID::CITADEL, BuildingID::CASTLE}); } @@ -99,7 +97,7 @@ int32_t convertToGold(const TResources & res) TResources BuildAnalyzer::getResourcesRequiredNow() const { - auto resourcesAvailable = cb->getResourceAmount(); + auto resourcesAvailable = ai->cb->getResourceAmount(); auto result = requiredResources - resourcesAvailable; result.positive(); @@ -109,7 +107,7 @@ TResources BuildAnalyzer::getResourcesRequiredNow() const TResources BuildAnalyzer::getTotalResourcesRequired() const { - auto resourcesAvailable = cb->getResourceAmount(); + auto resourcesAvailable = ai->cb->getResourceAmount(); auto result = totalDevelopmentCost - resourcesAvailable; result.positive(); @@ -125,7 +123,7 @@ void BuildAnalyzer::update() reset(); - auto towns = cb->getTownsInfo(); + auto towns = ai->cb->getTownsInfo(); for(const CGTownInstance* town : towns) { @@ -159,7 +157,7 @@ void BuildAnalyzer::update() updateDailyIncome(); - goldPreasure = (float)armyCost[Res::GOLD] / (1 + cb->getResourceAmount(Res::GOLD) + (float)dailyIncome[Res::GOLD] * 7.0f); + goldPreasure = (float)armyCost[Res::GOLD] / (1 + ai->cb->getResourceAmount(Res::GOLD) + (float)dailyIncome[Res::GOLD] * 7.0f); logAi->trace("Gold preasure: %f", goldPreasure); } @@ -203,7 +201,7 @@ BuildingInfo BuildAnalyzer::getBuildingOrPrerequisite( if(!town->hasBuilt(building)) { - auto canBuild = cb->canBuildStructure(town, building); + auto canBuild = ai->cb->canBuildStructure(town, building); if(canBuild == EBuildingState::ALLOWED) { @@ -262,8 +260,8 @@ BuildingInfo BuildAnalyzer::getBuildingOrPrerequisite( void BuildAnalyzer::updateDailyIncome() { - auto objects = cb->getMyObjects(); - auto towns = cb->getTownsInfo(); + auto objects = ai->cb->getMyObjects(); + auto towns = ai->cb->getTownsInfo(); dailyIncome = TResources(); diff --git a/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp b/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp index 33a39804a..cb15b1914 100644 --- a/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp +++ b/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp @@ -51,7 +51,7 @@ void DangerHitMapAnalyzer::updateHitMap() boost::this_thread::interruption_point(); - foreach_tile_pos([&](const int3 & pos) + pforeachTilePos(mapSize, [&](const int3 & pos) { for(AIPath & path : ai->pathfinder->getPathInfo(pos)) { diff --git a/AI/Nullkiller/Analyzers/ObjectClusterizer.cpp b/AI/Nullkiller/Analyzers/ObjectClusterizer.cpp index 9315932ce..8f7a3e3c4 100644 --- a/AI/Nullkiller/Analyzers/ObjectClusterizer.cpp +++ b/AI/Nullkiller/Analyzers/ObjectClusterizer.cpp @@ -16,14 +16,16 @@ void ObjectCluster::addObject(const CGObjectInstance * obj, const AIPath & path, float priority) { - auto & info = objects[obj]; + ClusterObjects::accessor info; - if(info.priority < priority) + objects.insert(info, ClusterObjects::value_type(obj, ClusterObjectInfo())); + + if(info->second.priority < priority) { - info.priority = priority; - info.movementCost = path.movementCost() - path.firstNode().cost; - info.danger = path.targetObjectDanger; - info.turn = path.turn(); + info->second.priority = priority; + info->second.movementCost = path.movementCost() - path.firstNode().cost; + info->second.danger = path.targetObjectDanger; + info->second.turn = path.turn(); } } @@ -125,7 +127,7 @@ const CGObjectInstance * ObjectClusterizer::getBlocker(const AIPath & path) cons || blocker->ID == Obj::BORDER_GATE || blocker->ID == Obj::SHIPYARD) { - if(!isObjectPassable(blocker)) + if(!isObjectPassable(ai, blocker)) return blocker; } @@ -206,130 +208,134 @@ void ObjectClusterizer::clusterize() logAi->debug("Begin object clusterization"); - for(const CGObjectInstance * obj : ai->memory->visitableObjs) + std::vector objs( + ai->memory->visitableObjs.begin(), + ai->memory->visitableObjs.end()); + + parallel_for(blocked_range(0, objs.size()), [&](const blocked_range & r) { - if(!shouldVisitObject(obj)) - continue; + auto priorityEvaluator = ai->priorityEvaluators->acquire(); + + for(int i = r.begin(); i != r.end(); i++) + { + auto obj = objs[i]; + + if(!shouldVisitObject(obj)) + return; #if AI_TRACE_LEVEL >= 2 - logAi->trace("Check object %s%s.", obj->getObjectName(), obj->visitablePos().toString()); + logAi->trace("Check object %s%s.", obj->getObjectName(), obj->visitablePos().toString()); #endif - auto paths = ai->pathfinder->getPathInfo(obj->visitablePos()); + auto paths = ai->pathfinder->getPathInfo(obj->visitablePos()); - if(paths.empty()) - { -#if AI_TRACE_LEVEL >= 2 - logAi->trace("No paths found."); -#endif - continue; - } - - std::sort(paths.begin(), paths.end(), [](const AIPath & p1, const AIPath & p2) -> bool - { - return p1.movementCost() < p2.movementCost(); - }); - - if(vstd::contains(ignoreObjects, obj->ID)) - { - farObjects.addObject(obj, paths.front(), 0); - -#if AI_TRACE_LEVEL >= 2 - logAi->trace("Object ignored. Moved to far objects with path %s", paths.front().toString()); -#endif - - continue; - } - - std::set heroesProcessed; - - for(auto & path : paths) - { -#if AI_TRACE_LEVEL >= 2 - logAi->trace("Checking path %s", path.toString()); -#endif - - if(!shouldVisit(path.targetHero, obj)) + if(paths.empty()) { #if AI_TRACE_LEVEL >= 2 - logAi->trace("Hero %s does not need to visit %s", path.targetHero->name, obj->getObjectName()); + logAi->trace("No paths found."); #endif continue; } - if(path.nodes.size() > 1) + std::sort(paths.begin(), paths.end(), [](const AIPath & p1, const AIPath & p2) -> bool { - auto blocker = getBlocker(path); + return p1.movementCost() < p2.movementCost(); + }); - if(blocker) - { - if(vstd::contains(heroesProcessed, path.targetHero)) - { - #if AI_TRACE_LEVEL >= 2 - logAi->trace("Hero %s is already processed.", path.targetHero->name); - #endif - continue; - } - - heroesProcessed.insert(path.targetHero); - - auto cluster = blockedObjects[blocker]; - - if(!cluster) - { - cluster.reset(new ObjectCluster(blocker)); - blockedObjects[blocker] = cluster; - } - - float priority = ai->priorityEvaluator->evaluate(Goals::sptr(Goals::ExecuteHeroChain(path, obj))); - - if(priority < MIN_PRIORITY) - continue; - - cluster->addObject(obj, path, priority); + if(vstd::contains(ignoreObjects, obj->ID)) + { + farObjects.addObject(obj, paths.front(), 0); #if AI_TRACE_LEVEL >= 2 - logAi->trace("Path added to cluster %s%s", blocker->getObjectName(), blocker->visitablePos().toString()); + logAi->trace("Object ignored. Moved to far objects with path %s", paths.front().toString()); +#endif + + continue; + } + + std::set heroesProcessed; + + for(auto & path : paths) + { +#if AI_TRACE_LEVEL >= 2 + logAi->trace("Checking path %s", path.toString()); +#endif + + if(!shouldVisit(ai, path.targetHero, obj)) + { +#if AI_TRACE_LEVEL >= 2 + logAi->trace("Hero %s does not need to visit %s", path.targetHero->name, obj->getObjectName()); #endif continue; } - } - - heroesProcessed.insert(path.targetHero); - float priority = ai->priorityEvaluator->evaluate(Goals::sptr(Goals::ExecuteHeroChain(path, obj))); + if(path.nodes.size() > 1) + { + auto blocker = getBlocker(path); - if(priority < MIN_PRIORITY) - continue; - - bool interestingObject = path.turn() <= 2 || priority > 0.5f; + if(blocker) + { + if(vstd::contains(heroesProcessed, path.targetHero)) + { +#if AI_TRACE_LEVEL >= 2 + logAi->trace("Hero %s is already processed.", path.targetHero->name); +#endif + continue; + } - if(interestingObject) - { - nearObjects.addObject(obj, path, priority); - } - else - { - farObjects.addObject(obj, path, priority); - } + heroesProcessed.insert(path.targetHero); + + float priority = priorityEvaluator->evaluate(Goals::sptr(Goals::ExecuteHeroChain(path, obj))); + + if(priority < MIN_PRIORITY) + continue; + + ClusterMap::accessor cluster; + blockedObjects.insert( + cluster, + ClusterMap::value_type(blocker, std::make_shared(blocker))); + + cluster->second->addObject(obj, path, priority); #if AI_TRACE_LEVEL >= 2 - logAi->trace("Path %s added to %s objects. Turn: %d, priority: %f", - path.toString(), - interestingObject ? "near" : "far", - path.turn(), - priority); + logAi->trace("Path added to cluster %s%s", blocker->getObjectName(), blocker->visitablePos().toString()); #endif - } - } + continue; + } + } - vstd::erase_if(blockedObjects, [](std::pair> pair) -> bool - { - return pair.second->objects.empty(); + heroesProcessed.insert(path.targetHero); + + float priority = priorityEvaluator->evaluate(Goals::sptr(Goals::ExecuteHeroChain(path, obj))); + + if(priority < MIN_PRIORITY) + continue; + + bool interestingObject = path.turn() <= 2 || priority > 0.5f; + + if(interestingObject) + { + nearObjects.addObject(obj, path, priority); + } + else + { + farObjects.addObject(obj, path, priority); + } + +#if AI_TRACE_LEVEL >= 2 + logAi->trace("Path %s added to %s objects. Turn: %d, priority: %f", + path.toString(), + interestingObject ? "near" : "far", + path.turn(), + priority); +#endif + } + } }); logAi->trace("Near objects count: %i", nearObjects.objects.size()); logAi->trace("Far objects count: %i", farObjects.objects.size()); + for(auto pair : blockedObjects) { logAi->trace("Cluster %s %s count: %i", pair.first->getObjectName(), pair.first->visitablePos().toString(), pair.second->objects.size()); diff --git a/AI/Nullkiller/Analyzers/ObjectClusterizer.h b/AI/Nullkiller/Analyzers/ObjectClusterizer.h index 47b11cdbb..3972e3084 100644 --- a/AI/Nullkiller/Analyzers/ObjectClusterizer.h +++ b/AI/Nullkiller/Analyzers/ObjectClusterizer.h @@ -19,10 +19,12 @@ struct ClusterObjectInfo uint8_t turn; }; +typedef tbb::concurrent_hash_map ClusterObjects; + struct ObjectCluster { public: - std::map objects; + ClusterObjects objects; const CGObjectInstance * blocker; void reset() @@ -45,12 +47,14 @@ public: const CGObjectInstance * calculateCenter() const; }; +typedef tbb::concurrent_hash_map> ClusterMap; + class ObjectClusterizer { private: ObjectCluster nearObjects; ObjectCluster farObjects; - std::map> blockedObjects; + ClusterMap blockedObjects; const Nullkiller * ai; public: diff --git a/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp b/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp index e49db35b5..05c8b1c9f 100644 --- a/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp +++ b/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp @@ -71,7 +71,7 @@ Goals::TGoalVec CaptureObjectsBehavior::getVisitGoals(const std::vector continue; } - if(objToVisit && !shouldVisit(path.targetHero, objToVisit)) + if(objToVisit && !shouldVisit(ai->nullkiller.get(), path.targetHero, objToVisit)) continue; auto hero = path.targetHero; diff --git a/AI/Nullkiller/Engine/Nullkiller.cpp b/AI/Nullkiller/Engine/Nullkiller.cpp index bc4a657ea..d6e6250c2 100644 --- a/AI/Nullkiller/Engine/Nullkiller.cpp +++ b/AI/Nullkiller/Engine/Nullkiller.cpp @@ -43,6 +43,13 @@ void Nullkiller::init(std::shared_ptr cb, PlayerColor playerID) this->playerID = playerID; priorityEvaluator.reset(new PriorityEvaluator(this)); + priorityEvaluators.reset( + new SharedPool( + [&]()->std::unique_ptr + { + return std::make_unique(this); + })); + dangerHitMap.reset(new DangerHitMapAnalyzer(this)); buildAnalyzer.reset(new BuildAnalyzer(this)); objectClusterizer.reset(new ObjectClusterizer(this)); @@ -224,16 +231,23 @@ void Nullkiller::makeTurn() HeroPtr hero = bestTask->getHero(); if(bestTask->priority < NEXT_SCAN_MIN_PRIORITY - && hero.validAndSet() - && heroManager->getHeroRole(hero) == HeroRole::MAIN && scanDepth != ScanDepth::FULL) { - logAi->trace( - "Goal %s has too low priority %f so increasing scan depth", - bestTask->toString(), - bestTask->priority); - scanDepth = (ScanDepth)((int)scanDepth + 1); - continue; + HeroRole heroRole = HeroRole::MAIN; + + if(hero.validAndSet()) + heroRole = heroManager->getHeroRole(hero); + + if(heroRole == HeroRole::MAIN) + { + logAi->trace( + "Goal %s has too low priority %f so increasing scan depth", + bestTask->toString(), + bestTask->priority); + scanDepth = (ScanDepth)((int)scanDepth + 1); + + continue; + } } if(bestTask->priority < MIN_PRIORITY) diff --git a/AI/Nullkiller/Engine/Nullkiller.h b/AI/Nullkiller/Engine/Nullkiller.h index 8800b6d6c..7d3997a5b 100644 --- a/AI/Nullkiller/Engine/Nullkiller.h +++ b/AI/Nullkiller/Engine/Nullkiller.h @@ -9,8 +9,6 @@ */ #pragma once -#include - #include "PriorityEvaluator.h" #include "FuzzyHelper.h" #include "AIMemory.h" @@ -58,6 +56,7 @@ public: std::unique_ptr buildAnalyzer; std::unique_ptr objectClusterizer; std::unique_ptr priorityEvaluator; + std::unique_ptr> priorityEvaluators; std::unique_ptr pathfinder; std::unique_ptr heroManager; std::unique_ptr armyManager; diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index 747e9d674..47b8ab2ba 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -29,8 +29,6 @@ #define MIN_AI_STRENGHT (0.5f) //lower when combat AI gets smarter #define UNGUARDED_OBJECT (100.0f) //we consider unguarded objects 100 times weaker than us -extern boost::thread_specific_ptr cb; - EvaluationContext::EvaluationContext(const Nullkiller * ai) : movementCost(0.0), manaCost(0), @@ -78,7 +76,7 @@ void PriorityEvaluator::initVisitTile() value = engine->getOutputVariable("Value"); } -int32_t estimateTownIncome(const CGObjectInstance * target, const CGHeroInstance * hero) +int32_t estimateTownIncome(CCallback * cb, const CGObjectInstance * target, const CGHeroInstance * hero) { auto relations = cb->getPlayerRelations(hero->tempOwner, target->tempOwner); @@ -116,7 +114,7 @@ uint64_t getCreatureBankArmyReward(const CGObjectInstance * target, const CGHero return result; } -uint64_t getDwellingScore(const CGObjectInstance * target, bool checkGold) +uint64_t getDwellingScore(CCallback * cb, const CGObjectInstance * target, bool checkGold) { auto dwelling = dynamic_cast(target); uint64_t score = 0; @@ -207,14 +205,14 @@ uint64_t RewardEvaluator::getArmyReward( case Obj::TOWN: return target->tempOwner == PlayerColor::NEUTRAL ? 1000 : 10000; case Obj::HILL_FORT: - return ai->armyManager->calculateCreateresUpgrade(army, target, cb->getResourceAmount()).upgradeValue; + return ai->armyManager->calculateCreateresUpgrade(army, target, ai->cb->getResourceAmount()).upgradeValue; case Obj::CREATURE_BANK: return getCreatureBankArmyReward(target, hero); case Obj::CREATURE_GENERATOR1: case Obj::CREATURE_GENERATOR2: case Obj::CREATURE_GENERATOR3: case Obj::CREATURE_GENERATOR4: - return getDwellingScore(target, checkGold); + return getDwellingScore(ai->cb.get(), target, checkGold); case Obj::CRYPT: case Obj::SHIPWRECK: case Obj::SHIPWRECK_SURVIVOR: @@ -225,7 +223,7 @@ uint64_t RewardEvaluator::getArmyReward( case Obj::DRAGON_UTOPIA: return 10000; case Obj::HERO: - return cb->getPlayerRelations(target->tempOwner, ai->playerID) == PlayerRelations::ENEMIES + return ai->cb->getPlayerRelations(target->tempOwner, ai->playerID) == PlayerRelations::ENEMIES ? enemyArmyEliminationRewardRatio * dynamic_cast(target)->getArmyStrength() : 0; default: @@ -241,7 +239,7 @@ int RewardEvaluator::getGoldCost(const CGObjectInstance * target, const CGHeroIn switch(target->ID) { case Obj::HILL_FORT: - return ai->armyManager->calculateCreateresUpgrade(army, target, cb->getResourceAmount()).upgradeCost[Res::GOLD]; + return ai->armyManager->calculateCreateresUpgrade(army, target, ai->cb->getResourceAmount()).upgradeCost[Res::GOLD]; case Obj::SCHOOL_OF_MAGIC: case Obj::SCHOOL_OF_WAR: return 1000; @@ -325,7 +323,7 @@ float RewardEvaluator::getStrategicalValue(const CGObjectInstance * target) cons : 0.5f; case Obj::HERO: - return cb->getPlayerRelations(target->tempOwner, ai->playerID) == PlayerRelations::ENEMIES + return ai->cb->getPlayerRelations(target->tempOwner, ai->playerID) == PlayerRelations::ENEMIES ? getEnemyHeroStrategicalValue(dynamic_cast(target)) : 0; @@ -380,7 +378,7 @@ float RewardEvaluator::getSkillReward(const CGObjectInstance * target, const CGH case Obj::WITCH_HUT: return evaluateWitchHutSkillScore(dynamic_cast(target), hero, role); case Obj::HERO: - return cb->getPlayerRelations(target->tempOwner, ai->playerID) == PlayerRelations::ENEMIES + return ai->cb->getPlayerRelations(target->tempOwner, ai->playerID) == PlayerRelations::ENEMIES ? enemyHeroEliminationSkillRewardRatio * dynamic_cast(target)->level : 0; default: @@ -438,7 +436,7 @@ int32_t RewardEvaluator::getGoldReward(const CGObjectInstance * target, const CG case Obj::WATER_WHEEL: return 1000; case Obj::TOWN: - return dailyIncomeMultiplier * estimateTownIncome(target, hero); + return dailyIncomeMultiplier * estimateTownIncome(ai->cb.get(), target, hero); case Obj::MINE: case Obj::ABANDONED_MINE: return dailyIncomeMultiplier * (isGold ? 1000 : 75); @@ -459,7 +457,7 @@ int32_t RewardEvaluator::getGoldReward(const CGObjectInstance * target, const CG case Obj::SEA_CHEST: return 1500; case Obj::HERO: - return cb->getPlayerRelations(target->tempOwner, ai->playerID) == PlayerRelations::ENEMIES + return ai->cb->getPlayerRelations(target->tempOwner, ai->playerID) == PlayerRelations::ENEMIES ? heroEliminationBonus + enemyArmyEliminationGoldRewardRatio * getArmyCost(dynamic_cast(target)) : 0; default: @@ -550,7 +548,12 @@ public: class ExecuteHeroChainEvaluationContextBuilder : public IEvaluationContextBuilder { +private: + const Nullkiller * ai; + public: + ExecuteHeroChainEvaluationContextBuilder(const Nullkiller * ai) : ai(ai) {} + virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override { if(task->goalType != Goals::EXECUTE_HERO_CHAIN) @@ -578,14 +581,14 @@ public: } auto heroPtr = task->hero; - auto day = cb->getDate(Date::DAY); - auto hero = heroPtr.get(); + auto day = ai->cb->getDate(Date::DAY); + auto hero = heroPtr.get(ai->cb.get()); bool checkGold = evaluationContext.danger == 0; auto army = path.heroArmy; - const CGObjectInstance * target = cb->getObj((ObjectInstanceID)task->objid, false); + const CGObjectInstance * target = ai->cb->getObj((ObjectInstanceID)task->objid, false); - if (target && cb->getPlayerRelations(target->tempOwner, hero->tempOwner) == PlayerRelations::ENEMIES) + if (target && ai->cb->getPlayerRelations(target->tempOwner, hero->tempOwner) == PlayerRelations::ENEMIES) { evaluationContext.goldReward += evaluationContext.evaluator.getGoldReward(target, hero); evaluationContext.armyReward += evaluationContext.evaluator.getArmyReward(target, hero, army, checkGold); @@ -603,7 +606,12 @@ public: class ClusterEvaluationContextBuilder : public IEvaluationContextBuilder { +private: + const Nullkiller * ai; + public: + ClusterEvaluationContextBuilder(const Nullkiller * ai) : ai(ai) {} + virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override { if(task->goalType != Goals::UNLOCK_CLUSTER) @@ -627,7 +635,7 @@ public: for(auto objInfo : objects) { auto target = objInfo.first; - auto day = cb->getDate(Date::DAY); + auto day = ai->cb->getDate(Date::DAY); bool checkGold = objInfo.second.danger == 0; auto army = hero; @@ -709,9 +717,9 @@ PriorityEvaluator::PriorityEvaluator(const Nullkiller * ai) :ai(ai) { initVisitTile(); - evaluationContextBuilders.push_back(std::make_shared()); + evaluationContextBuilders.push_back(std::make_shared(ai)); evaluationContextBuilders.push_back(std::make_shared()); - evaluationContextBuilders.push_back(std::make_shared()); + evaluationContextBuilders.push_back(std::make_shared(ai)); evaluationContextBuilders.push_back(std::make_shared()); evaluationContextBuilders.push_back(std::make_shared()); evaluationContextBuilders.push_back(std::make_shared()); @@ -769,7 +777,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task) closestHeroRatioVariable->setValue(evaluationContext.closestWayRatio); strategicalValueVariable->setValue(evaluationContext.strategicalValue); goldPreasureVariable->setValue(ai->buildAnalyzer->getGoldPreasure()); - goldCostVariable->setValue(evaluationContext.goldCost / ((float)cb->getResourceAmount(Res::GOLD) + (float)ai->buildAnalyzer->getDailyIncome()[Res::GOLD] + 1.0f)); + goldCostVariable->setValue(evaluationContext.goldCost / ((float)ai->cb->getResourceAmount(Res::GOLD) + (float)ai->buildAnalyzer->getDailyIncome()[Res::GOLD] + 1.0f)); turnVariable->setValue(evaluationContext.turn); fearVariable->setValue(evaluationContext.enemyHeroDangerRatio); diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp index 9d2dbcd92..d35aa6ddd 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp @@ -8,7 +8,6 @@ * */ #include "StdInc.h" -#include #include "AINodeStorage.h" #include "Actions/TownPortalAction.h" #include "../Goals/Goals.h" @@ -20,8 +19,6 @@ #include "../../../lib/PathfinderUtil.h" #include "../../../lib/CPlayerState.h" -using namespace tbb; - std::shared_ptr> AISharedStorage::shared; std::set commitedTiles; std::set commitedTilesInitial; @@ -504,7 +501,7 @@ bool AINodeStorage::calculateHeroChain() { std::mutex resultMutex; - std::random_shuffle(data.begin(), data.end()); + std::random_shuffle(data.begin(), data.end()); parallel_for(blocked_range(0, data.size()), [&](const blocked_range& r) { @@ -526,7 +523,7 @@ bool AINodeStorage::calculateHeroChain() HeroChainCalculationTask task(*this, nodes, data, chainMask, heroChainTurn); task.execute(r); - task.flushResult(heroChain);\ + task.flushResult(heroChain); } CCreature::DisableChildLinkage = false; diff --git a/AI/Nullkiller/Pathfinding/Actions/BoatActions.h b/AI/Nullkiller/Pathfinding/Actions/BoatActions.h index 4e9cf2dfb..0db5ff781 100644 --- a/AI/Nullkiller/Pathfinding/Actions/BoatActions.h +++ b/AI/Nullkiller/Pathfinding/Actions/BoatActions.h @@ -51,10 +51,11 @@ namespace AIPathfinding { private: const IShipyard * shipyard; + const CPlayerSpecificInfoCallback * cb; public: - BuildBoatAction(const IShipyard * shipyard) - : shipyard(shipyard) + BuildBoatAction(const CPlayerSpecificInfoCallback * cb, const IShipyard * shipyard) + : cb(cb), shipyard(shipyard) { } diff --git a/AI/Nullkiller/Pathfinding/Actors.cpp b/AI/Nullkiller/Pathfinding/Actors.cpp index 756b1a0a3..0c277b2f3 100644 --- a/AI/Nullkiller/Pathfinding/Actors.cpp +++ b/AI/Nullkiller/Pathfinding/Actors.cpp @@ -196,15 +196,26 @@ HeroExchangeMap::HeroExchangeMap(const HeroActor * actor, const Nullkiller * ai) HeroExchangeMap::~HeroExchangeMap() { + CCreature::DisableChildLinkage = true; + for(auto & exchange : exchangeMap) { if(!exchange.second) continue; delete exchange.second->creatureSet; + } + + CCreature::DisableChildLinkage = false; + + for(auto & exchange : exchangeMap) + { + if(!exchange.second) continue; + delete exchange.second; } exchangeMap.clear(); + } ExchangeResult HeroExchangeMap::tryExchangeNoLock(const ChainActor * other) @@ -270,7 +281,7 @@ ExchangeResult HeroExchangeMap::tryExchangeNoLock(const ChainActor * other) } if(other->isMovable && other->armyValue <= actor->armyValue / 10 && other->armyValue < MIN_ARMY_STRENGTH_FOR_CHAIN) - return nullptr; + return result; TResources availableResources = resources - actor->armyCost - other->armyCost; HeroExchangeArmy * upgradedInitialArmy = tryUpgrade(actor->creatureSet, other->getActorObject(), availableResources); diff --git a/AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.cpp b/AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.cpp index 1e91f5bc4..7ce26bee3 100644 --- a/AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.cpp +++ b/AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.cpp @@ -69,7 +69,7 @@ namespace AIPathfinding if(shipyard->shipyardStatus() == IShipyard::GOOD) { int3 boatLocation = shipyard->bestLocation(); - virtualBoats[boatLocation] = std::make_shared(shipyard); + virtualBoats[boatLocation] = std::make_shared(cb, shipyard); logAi->debug("Virtual boat added at %s", boatLocation.toString()); } }