#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_ASYNCNAVMESHUPDATER_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_ASYNCNAVMESHUPDATER_H #include "agentbounds.hpp" #include "changetype.hpp" #include "guardednavmeshcacheitem.hpp" #include "navmeshcacheitem.hpp" #include "navmeshdb.hpp" #include "navmeshtilescache.hpp" #include "offmeshconnectionsmanager.hpp" #include "sharednavmeshcacheitem.hpp" #include "stats.hpp" #include "tilecachedrecastmeshmanager.hpp" #include "tileposition.hpp" #include "waitconditiontype.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include class dtNavMesh; namespace Loading { class Listener; } namespace DetourNavigator { enum class JobState { Initial, WithDbResult, }; struct Job { const std::size_t mId; const AgentBounds mAgentBounds; const std::weak_ptr mNavMeshCacheItem; const ESM::RefId mWorldspace; const TilePosition mChangedTile; std::chrono::steady_clock::time_point mProcessTime; ChangeType mChangeType; JobState mState = JobState::Initial; std::vector mInput; std::shared_ptr mRecastMesh; std::optional mCachedTileData; std::unique_ptr mGeneratedNavMeshData; Job(const AgentBounds& agentBounds, std::weak_ptr navMeshCacheItem, ESM::RefId worldspace, const TilePosition& changedTile, ChangeType changeType, std::chrono::steady_clock::time_point processTime); }; using JobIt = std::list::iterator; class SpatialJobQueue { public: std::size_t size() const { return mSize; } void clear(); void push(JobIt job); std::optional pop(TilePosition playerTile); void update(TilePosition playerTile, int maxTiles, std::vector& removing); private: using IndexPoint = boost::geometry::model::point; using UpdatingMap = std::map>; using IndexValue = std::pair; std::size_t mSize = 0; UpdatingMap mValues; boost::geometry::index::rtree> mIndex; }; class JobQueue { public: JobQueueStats getStats() const { return JobQueueStats{ .mRemoving = mRemoving.size(), .mUpdating = mUpdating.size(), .mDelayed = mDelayed.size(), }; } bool hasJob(std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now()) const; void clear(); void push(JobIt job, std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now()); std::optional pop( TilePosition playerTile, std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now()); void update(TilePosition playerTile, int maxTiles, std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now()); private: std::vector mRemoving; SpatialJobQueue mUpdating; std::deque mDelayed; }; enum class JobStatus { Done, Fail, MemoryCacheMiss, }; std::ostream& operator<<(std::ostream& stream, JobStatus value); class DbJobQueue { public: void push(JobIt job); std::optional pop(); void update(TilePosition playerTile); void stop(); DbJobQueueStats getStats() const; private: mutable std::mutex mMutex; std::condition_variable mHasJob; SpatialJobQueue mReading; std::deque mWriting; TilePosition mPlayerTile; bool mShouldStop = false; }; class AsyncNavMeshUpdater; class DbWorker { public: DbWorker(AsyncNavMeshUpdater& updater, std::unique_ptr&& db, TileVersion version, const RecastSettings& recastSettings, bool writeToDb); ~DbWorker(); DbWorkerStats getStats() const; void enqueueJob(JobIt job); void update(TilePosition playerTile) { mQueue.update(playerTile); } void stop(); private: AsyncNavMeshUpdater& mUpdater; const RecastSettings& mRecastSettings; const std::unique_ptr mDb; const TileVersion mVersion; bool mWriteToDb; TileId mNextTileId; ShapeId mNextShapeId; DbJobQueue mQueue; std::atomic_bool mShouldStop{ false }; std::atomic_size_t mGetTileCount{ 0 }; std::thread mThread; inline void run() noexcept; inline void processJob(JobIt job); inline void processReadingJob(JobIt job); inline void processWritingJob(JobIt job); }; class AsyncNavMeshUpdater { public: AsyncNavMeshUpdater(const Settings& settings, TileCachedRecastMeshManager& recastMeshManager, OffMeshConnectionsManager& offMeshConnectionsManager, std::unique_ptr&& db); ~AsyncNavMeshUpdater(); void post(const AgentBounds& agentBounds, const SharedNavMeshCacheItem& navMeshCacheItem, const TilePosition& playerTile, ESM::RefId worldspace, const std::map& changedTiles); void wait(WaitConditionType waitConditionType, Loading::Listener* listener); void stop(); AsyncNavMeshUpdaterStats getStats() const; void enqueueJob(JobIt job); void removeJob(JobIt job); private: std::reference_wrapper mSettings; std::reference_wrapper mRecastMeshManager; std::reference_wrapper mOffMeshConnectionsManager; std::atomic_bool mShouldStop; mutable std::mutex mMutex; std::condition_variable mHasJob; std::condition_variable mDone; std::condition_variable mProcessed; std::list mJobs; JobQueue mWaiting; std::set> mPushed; Misc::ScopeGuarded mPlayerTile; NavMeshTilesCache mNavMeshTilesCache; Misc::ScopeGuarded>> mProcessingTiles; std::map, std::chrono::steady_clock::time_point> mLastUpdates; std::set> mPresentTiles; std::vector mThreads; std::unique_ptr mDbWorker; std::atomic_size_t mDbGetTileHits{ 0 }; void process() noexcept; JobStatus processJob(Job& job); inline JobStatus processInitialJob(Job& job, GuardedNavMeshCacheItem& navMeshCacheItem); inline JobStatus processJobWithDbResult(Job& job, GuardedNavMeshCacheItem& navMeshCacheItem); inline JobStatus handleUpdateNavMeshStatus(UpdateNavMeshStatus status, const Job& job, const GuardedNavMeshCacheItem& navMeshCacheItem, const RecastMesh& recastMesh); JobIt getNextJob(); void postThreadJob(JobIt job, std::deque& queue); void writeDebugFiles(const Job& job, const RecastMesh* recastMesh) const; bool lockTile(std::size_t jobId, const AgentBounds& agentBounds, const TilePosition& changedTile); void unlockTile(std::size_t jobId, const AgentBounds& agentBounds, const TilePosition& changedTile); inline std::size_t getTotalJobs() const; void cleanupLastUpdates(); inline void waitUntilJobsDoneForNotPresentTiles(Loading::Listener* listener); inline void waitUntilAllJobsDone(); }; } #endif