#include "asyncnavmeshupdater.hpp" #include "debug.hpp" #include "makenavmesh.hpp" #include "settings.hpp" #include #include namespace { using DetourNavigator::TilePosition; int getDistance(const TilePosition& lhs, const TilePosition& rhs) { return std::abs(lhs.x() - rhs.x()) + std::abs(lhs.y() - rhs.y()); } std::pair makePriority(const TilePosition& changedTile, const TilePosition& playerTile) { return std::make_pair(getDistance(changedTile, playerTile), getDistance(changedTile, TilePosition {0, 0})); } } namespace DetourNavigator { AsyncNavMeshUpdater::AsyncNavMeshUpdater(const Settings& settings) : mSettings(std::cref(settings)) , mShouldStop() , mThread([&] { process(); }) { } AsyncNavMeshUpdater::~AsyncNavMeshUpdater() { mShouldStop = true; std::unique_lock lock(mMutex); mJobs = decltype(mJobs)(); mHasJob.notify_all(); lock.unlock(); mThread.join(); } void AsyncNavMeshUpdater::post(const osg::Vec3f& agentHalfExtents, const std::shared_ptr& recastMesh, const std::shared_ptr& navMeshCacheItem, const TilePosition& playerTile, const std::set& changedTiles) { const std::lock_guard lock(mMutex); for (const auto& changedTile : changedTiles) { mJobs.push(Job {agentHalfExtents, recastMesh, navMeshCacheItem, changedTile, makePriority(changedTile, playerTile)}); } mHasJob.notify_all(); } void AsyncNavMeshUpdater::wait() { std::unique_lock lock(mMutex); mDone.wait(lock, [&] { return mJobs.empty(); }); } void AsyncNavMeshUpdater::process() throw() { log("start process jobs"); while (!mShouldStop) { try { if (const auto job = getNextJob()) processJob(*job); } catch (const std::exception& e) { DetourNavigator::log("AsyncNavMeshUpdater::process exception: ", e.what()); ::Log(Debug::Error) << "Exception while process navmesh updated job: " << e.what(); } } log("stop process jobs"); } void AsyncNavMeshUpdater::processJob(const Job& job) { log("process job for agent=", job.mAgentHalfExtents); const auto start = std::chrono::steady_clock::now(); updateNavMesh(job.mAgentHalfExtents, *job.mRecastMesh, job.mChangedTile, mSettings, job.mNavMeshCacheItem->mValue); const auto finish = std::chrono::steady_clock::now(); writeDebugFiles(job); using FloatMs = std::chrono::duration; log("cache updated for agent=", job.mAgentHalfExtents, " time=", std::chrono::duration_cast(finish - start).count(), "ms"); } boost::optional AsyncNavMeshUpdater::getNextJob() { std::unique_lock lock(mMutex); if (mJobs.empty()) mHasJob.wait_for(lock, std::chrono::milliseconds(10)); if (mJobs.empty()) { mDone.notify_all(); return boost::none; } log("got ", mJobs.size(), " jobs"); const auto job = mJobs.top(); mJobs.pop(); return job; } void AsyncNavMeshUpdater::writeDebugFiles(const Job& job) const { std::string revision; if (mSettings.get().mEnableWriteNavMeshToFile || mSettings.get().mEnableWriteRecastMeshToFile) revision = std::to_string((std::chrono::steady_clock::now() - std::chrono::steady_clock::time_point()).count()); if (mSettings.get().mEnableWriteRecastMeshToFile) writeToFile(*job.mRecastMesh, mSettings.get().mRecastMeshPathPrefix, revision); if (mSettings.get().mEnableWriteNavMeshToFile) writeToFile(*job.mNavMeshCacheItem->mValue.lock(), mSettings.get().mNavMeshPathPrefix, revision); } }