bot: implemented asynchronous pathfinding

nav: floyd-warshall matrices and practice updates are done asynchronously by now
add: yb_threadpool_workers cvar, that controls number of worker threads bot will use
add: cv_autovacate_keep_slots, the amount of slots to keep by auto vacate
aim: enemy prediction is now done asynchronously by now
bot: minor fixes and refactoring, including analyze suspend mistake (ref #441)

note: the master builds are now NOT production ready, please test before installing on real servers!
This commit is contained in:
jeefo 2023-05-06 20:14:03 +03:00
commit a616f25b1a
No known key found for this signature in database
GPG key ID: 927BCA0779BEA8ED
30 changed files with 743 additions and 421 deletions

View file

@ -146,6 +146,9 @@ public:
// initialize levels
void levelInitialize (edict_t *entities, int max);
// shutdown levels
void levelShutdown ();
// display world line
void drawLine (edict_t *ent, const Vector &start, const Vector &end, int width, int noise, const Color &color, int brightness, int speed, int life, DrawLine type = DrawLine::Simple);
@ -688,8 +691,7 @@ public:
int close (DLSYM_HANDLE module) {
if (m_self.handle () == module) {
disable ();
return m_dlclose (module);
return m_dlclose (module);
}
return m_dlclose (module);
}

View file

@ -153,7 +153,6 @@ private:
int m_lastJumpNode {};
int m_findWPIndex {};
int m_facingAtIndex {};
int m_highestDamage[kGameTeamNum] {};
int m_autoSaveCount {};
float m_timeJumpStarted {};
@ -265,14 +264,6 @@ public:
return m_paths.length () / 2;
}
int getHighestDamageForTeam (int team) const {
return cr::max (1, m_highestDamage[team]);
}
void setHighestDamageForTeam (int team, int value) {
m_highestDamage[team] = value;
}
StringRef getAuthor () const {
return m_graphAuthor;
}

View file

@ -63,7 +63,7 @@ private:
float m_maintainTime {}; // time to maintain bot creation
float m_quotaMaintainTime {}; // time to maintain bot quota
float m_grenadeUpdateTime {}; // time to update active grenades
float m_entityUpdateTime {}; // time to update intresting entities
float m_entityUpdateTime {}; // time to update interesting entities
float m_plantSearchUpdateTime {}; // time to update for searching planted bomb
float m_lastChatTime {}; // global chat time timestamp
float m_timeBombPlanted {}; // time the bomb were planted
@ -81,7 +81,7 @@ private:
bool m_roundOver {};
Array <edict_t *> m_activeGrenades {}; // holds currently active grenades on the map
Array <edict_t *> m_intrestingEntities {}; // holds currently intresting entities on the map
Array <edict_t *> m_interestingEntities {}; // holds currently interesting entities on the map
Deque <String> m_saveBotNames {}; // bots names that persist upon changelevel
Deque <BotRequest> m_addRequests {}; // bot creation tab
@ -143,7 +143,7 @@ public:
void initFilters ();
void resetFilters ();
void updateActiveGrenade ();
void updateIntrestingEntities ();
void updateInterestingEntities ();
void captureChatRadio (const char *cmd, const char *arg, edict_t *ent);
void notifyBombDefuse ();
void execGameEntity (edict_t *ent);
@ -160,16 +160,16 @@ public:
return m_activeGrenades;
}
const Array <edict_t *> &getIntrestingEntities () {
return m_intrestingEntities;
const Array <edict_t *> &getInterestingEntities () {
return m_interestingEntities;
}
bool hasActiveGrenades () const {
return !m_activeGrenades.empty ();
}
bool hasIntrestingEntities () const {
return !m_intrestingEntities.empty ();
bool hasInterestingEntities () const {
return !m_interestingEntities.empty ();
}
bool checkTeamEco (int team) const {
@ -302,5 +302,31 @@ public:
}
};
// bot async worker wrapper
class BotThreadWorker final : public Singleton <BotThreadWorker> {
private:
ThreadPool m_botWorker;
public:
BotThreadWorker () = default;
~BotThreadWorker () = default;
public:
void shutdown ();
void startup (int workers);
public:
template <typename F> void enqueue (F &&fn) {
if (m_botWorker.threadCount () == 0) {
fn (); // no threads, no fun, just run task in current thread
return;
}
m_botWorker.enqueue (cr::forward <F> (fn));
}
};
// explose global
CR_EXPOSE_GLOBAL_SINGLETON (BotManager, bots);
// expose async worker
CR_EXPOSE_GLOBAL_SINGLETON (BotThreadWorker, worker);

View file

@ -84,7 +84,7 @@ public:
};
// A* algorithm for bots
class AStarAlgo final {
class AStarAlgo final : public DenyCopying {
public:
using HeuristicFn = Heuristic::Func;
@ -108,7 +108,7 @@ private:
Array <int> m_smoothedPath;
private:
// cleares the currently built route
// clears the currently built route
void clearRoute ();
// can the node can be skipped?
@ -118,6 +118,10 @@ private:
void postSmooth (NodeAdderFn onAddedNode);
public:
explicit AStarAlgo (const int length) {
init (length);
}
AStarAlgo () = default;
~AStarAlgo () = default;
@ -180,6 +184,9 @@ public:
private:
// create floyd matrics
void syncRebuild ();
// async rebuild
void rebuild ();
public:
@ -201,6 +208,9 @@ public:
// dijkstra shortest path algorithm
class DijkstraAlgo final {
private:
mutable Mutex m_cs {};
private:
using Route = Twin <int, int>;
@ -215,11 +225,6 @@ public:
DijkstraAlgo () = default;
~DijkstraAlgo () = default;
private:
// reset pathfinder state to defaults
void resetState ();
public:
// initialize dijkstra with valid path length
void init (const int length);
@ -236,7 +241,6 @@ class PathPlanner : public Singleton <PathPlanner> {
private:
UniquePtr <DijkstraAlgo> m_dijkstra;
UniquePtr <FloydWarshallAlgo> m_floyd;
UniquePtr <AStarAlgo > m_astar;
bool m_memoryLimitHit {};
public:
@ -261,17 +265,15 @@ public:
return m_floyd.get ();
}
// get the floyd algo
decltype (auto) getAStar () {
return m_astar.get ();
}
public:
// do the pathfinding
bool find (int srcIndex, int destIndex, NodeAdderFn onAddedNode, int *pathDistance = nullptr);
// distance between two nodes with pathfinder
int dist (int srcIndex, int destIndex);
float dist (int srcIndex, int destIndex);
// get the precise distanace regardless of cvar
float preciseDistance (int srcIndex, int destIndex);
};
CR_EXPOSE_GLOBAL_SINGLETON (PathPlanner, planner);

View file

@ -80,17 +80,22 @@ public:
DangerSaveRestore (const DangerStorage &ds, const PracticeData &pd) : danger (ds), data (pd) {}
};
private:
HashMap <DangerStorage, PracticeData> m_data {};
int32_t m_teamHighestDamage[kGameTeamNum] {};
// avoid concurrent access to practice
mutable Mutex m_damageUpdateLock {};
public:
BotPractice () = default;
~BotPractice () = default;
private:
inline bool exists (int32_t team, int32_t start, int32_t goal) const {
bool exists (int32_t team, int32_t start, int32_t goal) const {
return m_data.exists ({ start, goal, team });
}
void syncUpdate ();
public:
int32_t getIndex (int32_t team, int32_t start, int32_t goal);
@ -102,17 +107,21 @@ public:
int32_t getDamage (int32_t team, int32_t start, int32_t goal);
void setDamage (int32_t team, int32_t start, int32_t goal, int32_t value);
// interlocked get damage
float plannerGetDamage (int32_t team, int32_t start, int32_t goal, bool addTeamHighestDamage);
public:
void update ();
void load ();
void save ();
public:
int32_t getHighestDamageForTeam (int32_t team) const {
return cr::max (1, m_teamHighestDamage[team]);
template <typename U = int32_t> U getHighestDamageForTeam (int32_t team) const {
return static_cast <U> (cr::max (1, m_teamHighestDamage[team]));
}
void setHighestDamageForTeam (int32_t team, int32_t value) {
MutexScopedLock lock (m_damageUpdateLock);
m_teamHighestDamage[team] = value;
}
};

View file

@ -8,6 +8,9 @@
#pragma once
class BotSupport final : public Singleton <BotSupport> {
private:
mutable Mutex m_cs {};
private:
bool m_needToSendWelcome {};
float m_welcomeReceiveTime {};
@ -78,6 +81,9 @@ public:
// generates ping bitmask for SVC_PINGS message
int getPingBitmask (edict_t *ent, int loss, int ping);
// calculate our own pings for all the players
void syncCalculatePings ();
// calculate our own pings for all the players
void calculatePings ();

View file

@ -663,6 +663,10 @@ class Bot final {
public:
friend class BotManager;
private:
mutable Mutex m_pathFindLock {};
mutable Mutex m_predictLock {};
private:
uint32_t m_states {}; // sensing bitstates
uint32_t m_collideMoves[kMaxCollideMoves] {}; // sorted array of movements
@ -689,8 +693,10 @@ private:
int m_tryOpenDoor {}; // attempt's to open the door
int m_liftState {}; // state of lift handling
int m_radioSelect {}; // radio entry
int m_lastPredictIndex {}; // last predicted index
int m_lastPredictIndex { kInvalidNodeIndex }; // last predicted path index
int m_lastPredictLength {}; // last predicted path length
float m_headedTime {};
float m_prevTime {}; // time previously checked movement speed
float m_heavyTimestamp {}; // is it time to execute heavy-weight functions
@ -747,6 +753,7 @@ private:
bool m_moveToGoal {}; // bot currently moving to goal??
bool m_isStuck {}; // bot is stuck
bool m_isStale {}; // bot is leaving server
bool m_isReloading {}; // bot is reloading a gun
bool m_forceRadio {}; // should bot use radio anyway?
bool m_defendedBomb {}; // defend action issued
@ -773,6 +780,7 @@ private:
FindPath m_pathType {}; // which pathfinder to use
uint8_t m_enemyParts {}; // visibility flags
TraceResult m_lastTrace[TraceChannel::Num] {}; // last trace result
UniquePtr <class AStarAlgo> m_planner;
edict_t *m_pickupItem {}; // pointer to entity of item to use/pickup
edict_t *m_itemIgnore {}; // pointer to entity to ignore for pickup
@ -888,7 +896,6 @@ private:
void doPlayerAvoidance (const Vector &normal);
void selectCampButtons (int index);
void markStale ();
void instantChatter (int type);
void update ();
void runMovement ();
@ -920,6 +927,7 @@ private:
void findShortestPath (int srcIndex, int destIndex);
void calculateFrustum ();
void findPath (int srcIndex, int destIndex, FindPath pathType = FindPath::Fast);
void syncFindPath (int srcIndex, int destIndex, FindPath pathType);
void debugMsgInternal (const char *str);
void frame ();
void resetCollision ();
@ -933,6 +941,7 @@ private:
void decideFollowUser ();
void attackMovement ();
void findValidNode ();
void setPathOrigin ();
void fireWeapons ();
void selectWeapons (float distance, int index, int id, int choosen);
void focusEnemy ();
@ -940,6 +949,9 @@ private:
void selectSecondary ();
void selectWeaponById (int id);
void selectWeaponByIndex (int index);
void refreshEnemyPredict ();
void syncUpdatePredictedIndex ();
void updatePredictedIndex ();
void completeTask ();
void executeTasks ();
@ -1094,11 +1106,13 @@ public:
bool m_hasNVG {}; // does bot has nightvision goggles
bool m_usesNVG {}; // does nightvision goggles turned on
bool m_hasC4 {}; // does bot has c4 bomb
bool m_hasHostage {}; // does bot owns some hostages
bool m_hasProgressBar {}; // has progress bar on a HUD
bool m_jumpReady {}; // is double jump ready
bool m_canChooseAimDirection {}; // can choose aiming direction
bool m_isEnemyReachable {}; // direct line to enemy
bool m_kickedByRotation {}; // is bot kicked due to rotation ?
bool m_kickMeFromServer {}; // kick the bot off the server?
edict_t *m_doubleJumpEntity {}; // pointer to entity that request double jump
edict_t *m_radioEntity {}; // pointer to entity issuing a radio command
@ -1121,7 +1135,11 @@ public:
public:
Bot (edict_t *bot, int difficulty, int personality, int team, int skin);
~Bot () = default;
// need to wait until all threads will finish it's work before terminating bot object
~Bot () {
MutexScopedLock lock (m_pathFindLock);
}
public:
void logic (); /// the things that can be executed while skipping frames
@ -1154,7 +1172,7 @@ public:
void resetDoubleJump ();
void startDoubleJump (edict_t *ent);
void sendBotToOrigin (const Vector &origin);
void markStale ();
bool hasHostage ();
bool usesRifle ();
bool usesPistol ();