2019-05-21 15:22:34 +00:00
|
|
|
#include "asyncnavmeshupdater.hpp"
|
2018-03-13 22:49:08 +00:00
|
|
|
#include "debug.hpp"
|
|
|
|
#include "makenavmesh.hpp"
|
|
|
|
#include "settings.hpp"
|
|
|
|
|
|
|
|
#include <components/debug/debuglog.hpp>
|
|
|
|
|
2019-03-17 17:18:53 +00:00
|
|
|
#include <osg/Stats>
|
|
|
|
|
2018-04-01 17:24:02 +00:00
|
|
|
namespace
|
|
|
|
{
|
2018-07-14 12:05:28 +00:00
|
|
|
using DetourNavigator::ChangeType;
|
2018-04-01 17:24:02 +00:00
|
|
|
using DetourNavigator::TilePosition;
|
|
|
|
|
2018-07-14 12:05:28 +00:00
|
|
|
int getManhattanDistance(const TilePosition& lhs, const TilePosition& rhs)
|
2018-04-01 17:24:02 +00:00
|
|
|
{
|
|
|
|
return std::abs(lhs.x() - rhs.x()) + std::abs(lhs.y() - rhs.y());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-13 22:49:08 +00:00
|
|
|
namespace DetourNavigator
|
|
|
|
{
|
2018-04-21 12:27:47 +00:00
|
|
|
static std::ostream& operator <<(std::ostream& stream, UpdateNavMeshStatus value)
|
|
|
|
{
|
|
|
|
switch (value)
|
|
|
|
{
|
2019-03-08 12:06:47 +00:00
|
|
|
case UpdateNavMeshStatus::ignored:
|
2018-04-21 12:27:47 +00:00
|
|
|
return stream << "ignore";
|
|
|
|
case UpdateNavMeshStatus::removed:
|
|
|
|
return stream << "removed";
|
2019-03-08 12:06:47 +00:00
|
|
|
case UpdateNavMeshStatus::added:
|
2018-04-21 12:27:47 +00:00
|
|
|
return stream << "add";
|
|
|
|
case UpdateNavMeshStatus::replaced:
|
|
|
|
return stream << "replaced";
|
2019-03-08 12:28:32 +00:00
|
|
|
case UpdateNavMeshStatus::failed:
|
|
|
|
return stream << "failed";
|
|
|
|
case UpdateNavMeshStatus::lost:
|
|
|
|
return stream << "lost";
|
2018-04-21 12:27:47 +00:00
|
|
|
}
|
|
|
|
return stream << "unknown";
|
|
|
|
}
|
|
|
|
|
2018-08-26 20:27:38 +00:00
|
|
|
AsyncNavMeshUpdater::AsyncNavMeshUpdater(const Settings& settings, TileCachedRecastMeshManager& recastMeshManager,
|
|
|
|
OffMeshConnectionsManager& offMeshConnectionsManager)
|
2018-04-15 22:07:18 +00:00
|
|
|
: mSettings(settings)
|
|
|
|
, mRecastMeshManager(recastMeshManager)
|
2018-08-26 20:27:38 +00:00
|
|
|
, mOffMeshConnectionsManager(offMeshConnectionsManager)
|
2018-03-13 22:49:08 +00:00
|
|
|
, mShouldStop()
|
2018-09-30 22:33:25 +00:00
|
|
|
, mNavMeshTilesCache(settings.mMaxNavMeshTilesCacheSize)
|
2018-03-13 22:49:08 +00:00
|
|
|
{
|
2018-09-22 14:49:57 +00:00
|
|
|
for (std::size_t i = 0; i < mSettings.get().mAsyncNavMeshUpdaterThreads; ++i)
|
|
|
|
mThreads.emplace_back([&] { process(); });
|
2018-03-13 22:49:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
AsyncNavMeshUpdater::~AsyncNavMeshUpdater()
|
|
|
|
{
|
|
|
|
mShouldStop = true;
|
|
|
|
std::unique_lock<std::mutex> lock(mMutex);
|
2018-04-01 17:24:02 +00:00
|
|
|
mJobs = decltype(mJobs)();
|
2018-03-13 22:49:08 +00:00
|
|
|
mHasJob.notify_all();
|
|
|
|
lock.unlock();
|
2018-09-22 14:49:57 +00:00
|
|
|
for (auto& thread : mThreads)
|
|
|
|
thread.join();
|
2018-03-13 22:49:08 +00:00
|
|
|
}
|
|
|
|
|
2018-04-15 22:07:18 +00:00
|
|
|
void AsyncNavMeshUpdater::post(const osg::Vec3f& agentHalfExtents,
|
2018-09-30 22:33:25 +00:00
|
|
|
const SharedNavMeshCacheItem& navMeshCacheItem, const TilePosition& playerTile,
|
2018-07-14 12:05:28 +00:00
|
|
|
const std::map<TilePosition, ChangeType>& changedTiles)
|
2018-03-13 22:49:08 +00:00
|
|
|
{
|
2018-09-29 19:57:41 +00:00
|
|
|
*mPlayerTile.lock() = playerTile;
|
2018-04-20 23:57:01 +00:00
|
|
|
|
2018-04-04 00:20:48 +00:00
|
|
|
if (changedTiles.empty())
|
|
|
|
return;
|
|
|
|
|
2018-03-13 22:49:08 +00:00
|
|
|
const std::lock_guard<std::mutex> lock(mMutex);
|
2018-04-04 00:20:48 +00:00
|
|
|
|
2018-04-01 17:24:02 +00:00
|
|
|
for (const auto& changedTile : changedTiles)
|
2018-04-20 23:57:01 +00:00
|
|
|
{
|
2018-07-14 12:05:28 +00:00
|
|
|
if (mPushed[agentHalfExtents].insert(changedTile.first).second)
|
2019-03-08 12:23:36 +00:00
|
|
|
{
|
|
|
|
Job job;
|
|
|
|
|
|
|
|
job.mAgentHalfExtents = agentHalfExtents;
|
|
|
|
job.mNavMeshCacheItem = navMeshCacheItem;
|
|
|
|
job.mChangedTile = changedTile.first;
|
2019-03-08 12:28:32 +00:00
|
|
|
job.mTryNumber = 0;
|
2019-03-08 12:23:36 +00:00
|
|
|
job.mChangeType = changedTile.second;
|
|
|
|
job.mDistanceToPlayer = getManhattanDistance(changedTile.first, playerTile);
|
|
|
|
job.mDistanceToOrigin = getManhattanDistance(changedTile.first, TilePosition {0, 0});
|
|
|
|
|
|
|
|
mJobs.push(std::move(job));
|
|
|
|
}
|
2018-04-20 23:57:01 +00:00
|
|
|
}
|
2018-04-04 00:20:48 +00:00
|
|
|
|
2018-11-01 10:08:33 +00:00
|
|
|
Log(Debug::Debug) << "Posted " << mJobs.size() << " navigator jobs";
|
2018-05-26 14:44:25 +00:00
|
|
|
|
2019-03-08 12:27:16 +00:00
|
|
|
if (!mJobs.empty())
|
|
|
|
mHasJob.notify_all();
|
2018-03-13 22:49:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void AsyncNavMeshUpdater::wait()
|
|
|
|
{
|
|
|
|
std::unique_lock<std::mutex> lock(mMutex);
|
|
|
|
mDone.wait(lock, [&] { return mJobs.empty(); });
|
|
|
|
}
|
|
|
|
|
2019-03-17 17:18:53 +00:00
|
|
|
void AsyncNavMeshUpdater::reportStats(unsigned int frameNumber, osg::Stats& stats) const
|
|
|
|
{
|
|
|
|
std::size_t jobs = 0;
|
|
|
|
|
|
|
|
{
|
|
|
|
const std::lock_guard<std::mutex> lock(mMutex);
|
|
|
|
jobs = mJobs.size();
|
|
|
|
}
|
|
|
|
|
|
|
|
stats.setAttribute(frameNumber, "NavMesh UpdateJobs", jobs);
|
|
|
|
|
|
|
|
mNavMeshTilesCache.reportStats(frameNumber, stats);
|
|
|
|
}
|
|
|
|
|
2018-03-13 22:49:08 +00:00
|
|
|
void AsyncNavMeshUpdater::process() throw()
|
|
|
|
{
|
2018-11-01 10:08:33 +00:00
|
|
|
Log(Debug::Debug) << "Start process navigator jobs";
|
2018-03-13 22:49:08 +00:00
|
|
|
while (!mShouldStop)
|
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
2019-03-08 12:28:32 +00:00
|
|
|
if (auto job = getNextJob())
|
2019-04-08 17:14:17 +00:00
|
|
|
{
|
|
|
|
const auto processed = processJob(*job);
|
|
|
|
unlockTile(job->mAgentHalfExtents, job->mChangedTile);
|
|
|
|
if (!processed)
|
2019-03-08 12:28:32 +00:00
|
|
|
repost(std::move(*job));
|
2019-04-08 17:14:17 +00:00
|
|
|
}
|
2018-03-13 22:49:08 +00:00
|
|
|
}
|
|
|
|
catch (const std::exception& e)
|
|
|
|
{
|
2018-11-01 10:08:33 +00:00
|
|
|
Log(Debug::Error) << "AsyncNavMeshUpdater::process exception: ", e.what();
|
2018-03-13 22:49:08 +00:00
|
|
|
}
|
|
|
|
}
|
2018-11-01 10:08:33 +00:00
|
|
|
Log(Debug::Debug) << "Stop navigator jobs processing";
|
2018-03-13 22:49:08 +00:00
|
|
|
}
|
|
|
|
|
2019-03-08 12:28:32 +00:00
|
|
|
bool AsyncNavMeshUpdater::processJob(const Job& job)
|
2018-03-13 22:49:08 +00:00
|
|
|
{
|
2018-11-01 10:08:33 +00:00
|
|
|
Log(Debug::Debug) << "Process job for agent=(" << std::fixed << std::setprecision(2) << job.mAgentHalfExtents << ")";
|
2018-03-13 22:49:08 +00:00
|
|
|
|
|
|
|
const auto start = std::chrono::steady_clock::now();
|
|
|
|
|
2018-09-29 19:57:41 +00:00
|
|
|
const auto firstStart = setFirstStart(start);
|
2018-04-20 22:39:21 +00:00
|
|
|
|
2019-03-10 13:04:44 +00:00
|
|
|
const auto navMeshCacheItem = job.mNavMeshCacheItem.lock();
|
|
|
|
|
|
|
|
if (!navMeshCacheItem)
|
|
|
|
return true;
|
|
|
|
|
2018-04-15 22:07:18 +00:00
|
|
|
const auto recastMesh = mRecastMeshManager.get().getMesh(job.mChangedTile);
|
2018-09-29 19:57:41 +00:00
|
|
|
const auto playerTile = *mPlayerTile.lockConst();
|
2018-08-26 20:27:38 +00:00
|
|
|
const auto offMeshConnections = mOffMeshConnectionsManager.get().get(job.mChangedTile);
|
2018-04-04 00:20:48 +00:00
|
|
|
|
2018-04-20 23:57:01 +00:00
|
|
|
const auto status = updateNavMesh(job.mAgentHalfExtents, recastMesh.get(), job.mChangedTile, playerTile,
|
2019-03-10 13:04:44 +00:00
|
|
|
offMeshConnections, mSettings, navMeshCacheItem, mNavMeshTilesCache);
|
2018-04-07 13:11:23 +00:00
|
|
|
|
2018-03-13 22:49:08 +00:00
|
|
|
const auto finish = std::chrono::steady_clock::now();
|
|
|
|
|
2018-04-20 23:57:01 +00:00
|
|
|
writeDebugFiles(job, recastMesh.get());
|
2018-03-13 22:49:08 +00:00
|
|
|
|
|
|
|
using FloatMs = std::chrono::duration<float, std::milli>;
|
|
|
|
|
2018-11-01 10:08:33 +00:00
|
|
|
const auto locked = navMeshCacheItem->lockConst();
|
|
|
|
Log(Debug::Debug) << std::fixed << std::setprecision(2) <<
|
|
|
|
"Cache updated for agent=(" << job.mAgentHalfExtents << ")" <<
|
|
|
|
" status=" << status <<
|
|
|
|
" generation=" << locked->getGeneration() <<
|
|
|
|
" revision=" << locked->getNavMeshRevision() <<
|
|
|
|
" time=" << std::chrono::duration_cast<FloatMs>(finish - start).count() << "ms" <<
|
|
|
|
" total_time=" << std::chrono::duration_cast<FloatMs>(finish - firstStart).count() << "ms";
|
2019-03-08 12:28:32 +00:00
|
|
|
|
|
|
|
return isSuccess(status);
|
2018-03-13 22:49:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
boost::optional<AsyncNavMeshUpdater::Job> AsyncNavMeshUpdater::getNextJob()
|
|
|
|
{
|
|
|
|
std::unique_lock<std::mutex> lock(mMutex);
|
2019-04-08 17:14:17 +00:00
|
|
|
|
|
|
|
const auto threadId = std::this_thread::get_id();
|
|
|
|
auto& threadQueue = mThreadsQueues[threadId];
|
|
|
|
|
|
|
|
while (true)
|
2018-03-13 22:49:08 +00:00
|
|
|
{
|
2019-04-08 17:14:17 +00:00
|
|
|
const auto hasJob = [&] { return !mJobs.empty() || !threadQueue.mPushed.empty(); };
|
|
|
|
|
|
|
|
if (!mHasJob.wait_for(lock, std::chrono::milliseconds(10), hasJob))
|
|
|
|
{
|
|
|
|
mFirstStart.lock()->reset();
|
|
|
|
mDone.notify_all();
|
|
|
|
return boost::none;
|
|
|
|
}
|
|
|
|
|
|
|
|
Log(Debug::Debug) << "Got " << mJobs.size() << " navigator jobs and "
|
|
|
|
<< threadQueue.mJobs.size() << " thread jobs";
|
|
|
|
|
|
|
|
auto job = threadQueue.mJobs.empty()
|
|
|
|
? getJob(mJobs, mPushed)
|
|
|
|
: getJob(threadQueue.mJobs, threadQueue.mPushed);
|
|
|
|
|
|
|
|
const auto owner = lockTile(job.mAgentHalfExtents, job.mChangedTile);
|
|
|
|
|
|
|
|
if (owner == threadId)
|
|
|
|
return job;
|
|
|
|
|
|
|
|
postThreadJob(std::move(job), mThreadsQueues[owner]);
|
2018-03-13 22:49:08 +00:00
|
|
|
}
|
2019-04-08 17:14:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
AsyncNavMeshUpdater::Job AsyncNavMeshUpdater::getJob(Jobs& jobs, Pushed& pushed)
|
|
|
|
{
|
|
|
|
auto job = jobs.top();
|
|
|
|
jobs.pop();
|
|
|
|
|
|
|
|
const auto it = pushed.find(job.mAgentHalfExtents);
|
|
|
|
it->second.erase(job.mChangedTile);
|
|
|
|
if (it->second.empty())
|
|
|
|
pushed.erase(it);
|
|
|
|
|
2018-03-13 22:49:08 +00:00
|
|
|
return job;
|
|
|
|
}
|
|
|
|
|
2018-04-20 23:57:01 +00:00
|
|
|
void AsyncNavMeshUpdater::writeDebugFiles(const Job& job, const RecastMesh* recastMesh) const
|
2018-03-13 22:49:08 +00:00
|
|
|
{
|
2018-04-01 15:09:43 +00:00
|
|
|
std::string revision;
|
2018-04-02 06:55:12 +00:00
|
|
|
std::string recastMeshRevision;
|
|
|
|
std::string navMeshRevision;
|
|
|
|
if ((mSettings.get().mEnableWriteNavMeshToFile || mSettings.get().mEnableWriteRecastMeshToFile)
|
|
|
|
&& (mSettings.get().mEnableRecastMeshFileNameRevision || mSettings.get().mEnableNavMeshFileNameRevision))
|
|
|
|
{
|
|
|
|
revision = "." + std::to_string((std::chrono::steady_clock::now()
|
2018-04-01 15:09:43 +00:00
|
|
|
- std::chrono::steady_clock::time_point()).count());
|
2018-04-02 06:55:12 +00:00
|
|
|
if (mSettings.get().mEnableRecastMeshFileNameRevision)
|
|
|
|
recastMeshRevision = revision;
|
|
|
|
if (mSettings.get().mEnableNavMeshFileNameRevision)
|
|
|
|
navMeshRevision = revision;
|
|
|
|
}
|
2018-04-20 23:57:01 +00:00
|
|
|
if (recastMesh && mSettings.get().mEnableWriteRecastMeshToFile)
|
2018-05-26 14:44:25 +00:00
|
|
|
writeToFile(*recastMesh, mSettings.get().mRecastMeshPathPrefix + std::to_string(job.mChangedTile.x())
|
|
|
|
+ "_" + std::to_string(job.mChangedTile.y()) + "_", recastMeshRevision);
|
2018-04-01 15:09:43 +00:00
|
|
|
if (mSettings.get().mEnableWriteNavMeshToFile)
|
2019-03-10 13:04:44 +00:00
|
|
|
if (const auto shared = job.mNavMeshCacheItem.lock())
|
2019-03-10 12:48:12 +00:00
|
|
|
writeToFile(shared->lockConst()->getImpl(), mSettings.get().mNavMeshPathPrefix, navMeshRevision);
|
2018-03-13 22:49:08 +00:00
|
|
|
}
|
2018-04-04 00:20:48 +00:00
|
|
|
|
2018-09-29 19:57:41 +00:00
|
|
|
std::chrono::steady_clock::time_point AsyncNavMeshUpdater::setFirstStart(const std::chrono::steady_clock::time_point& value)
|
2018-04-20 23:57:01 +00:00
|
|
|
{
|
2018-09-29 19:57:41 +00:00
|
|
|
const auto locked = mFirstStart.lock();
|
|
|
|
if (!*locked)
|
|
|
|
*locked = value;
|
|
|
|
return *locked.get();
|
2018-04-20 23:57:01 +00:00
|
|
|
}
|
2019-03-08 12:28:32 +00:00
|
|
|
|
|
|
|
void AsyncNavMeshUpdater::repost(Job&& job)
|
|
|
|
{
|
|
|
|
if (mShouldStop || job.mTryNumber > 2)
|
|
|
|
return;
|
|
|
|
|
|
|
|
const std::lock_guard<std::mutex> lock(mMutex);
|
|
|
|
|
|
|
|
if (mPushed[job.mAgentHalfExtents].insert(job.mChangedTile).second)
|
|
|
|
{
|
|
|
|
++job.mTryNumber;
|
|
|
|
mJobs.push(std::move(job));
|
|
|
|
mHasJob.notify_all();
|
|
|
|
}
|
|
|
|
}
|
2019-04-08 17:14:17 +00:00
|
|
|
|
|
|
|
void AsyncNavMeshUpdater::postThreadJob(Job&& job, Queue& queue)
|
|
|
|
{
|
|
|
|
if (queue.mPushed[job.mAgentHalfExtents].insert(job.mChangedTile).second)
|
|
|
|
{
|
|
|
|
queue.mJobs.push(std::move(job));
|
|
|
|
mHasJob.notify_all();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
std::thread::id AsyncNavMeshUpdater::lockTile(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile)
|
|
|
|
{
|
|
|
|
if (mSettings.get().mAsyncNavMeshUpdaterThreads <= 1)
|
|
|
|
return std::this_thread::get_id();
|
|
|
|
|
|
|
|
auto locked = mProcessingTiles.lock();
|
|
|
|
|
|
|
|
auto agent = locked->find(agentHalfExtents);
|
|
|
|
if (agent == locked->end())
|
|
|
|
{
|
|
|
|
const auto threadId = std::this_thread::get_id();
|
|
|
|
locked->emplace(agentHalfExtents, std::map<TilePosition, std::thread::id>({{changedTile, threadId}}));
|
|
|
|
return threadId;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto tile = agent->second.find(changedTile);
|
|
|
|
if (tile == agent->second.end())
|
|
|
|
{
|
|
|
|
const auto threadId = std::this_thread::get_id();
|
|
|
|
agent->second.emplace(changedTile, threadId);
|
|
|
|
return threadId;
|
|
|
|
}
|
|
|
|
|
|
|
|
return tile->second;
|
|
|
|
}
|
|
|
|
|
|
|
|
void AsyncNavMeshUpdater::unlockTile(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile)
|
|
|
|
{
|
|
|
|
if (mSettings.get().mAsyncNavMeshUpdaterThreads <= 1)
|
|
|
|
return;
|
|
|
|
|
|
|
|
auto locked = mProcessingTiles.lock();
|
|
|
|
|
|
|
|
auto agent = locked->find(agentHalfExtents);
|
|
|
|
if (agent == locked->end())
|
|
|
|
return;
|
|
|
|
|
|
|
|
auto tile = agent->second.find(changedTile);
|
|
|
|
if (tile == agent->second.end())
|
|
|
|
return;
|
|
|
|
|
|
|
|
agent->second.erase(tile);
|
|
|
|
|
|
|
|
if (agent->second.empty())
|
|
|
|
locked->erase(agent);
|
|
|
|
}
|
2018-03-13 22:49:08 +00:00
|
|
|
}
|