Use R-tree for dynamic priority of navmesh async job

pull/3235/head
elsid 9 months ago
parent 17bd571a65
commit 50f4471750
No known key found for this signature in database
GPG Key ID: 4DE04C198CBA7625

@ -267,7 +267,6 @@ namespace
updater.wait(WaitConditionType::allJobsDone, &mListener); updater.wait(WaitConditionType::allJobsDone, &mListener);
updater.stop(); updater.stop();
const std::set<TilePosition> present{ const std::set<TilePosition> present{
TilePosition(-2, 0),
TilePosition(-1, -1), TilePosition(-1, -1),
TilePosition(-1, 0), TilePosition(-1, 0),
TilePosition(-1, 1), TilePosition(-1, 1),
@ -278,6 +277,7 @@ namespace
TilePosition(0, 2), TilePosition(0, 2),
TilePosition(1, -1), TilePosition(1, -1),
TilePosition(1, 0), TilePosition(1, 0),
TilePosition(1, 1),
}; };
for (int x = -5; x <= 5; ++x) for (int x = -5; x <= 5; ++x)
for (int y = -5; y <= 5; ++y) for (int y = -5; y <= 5; ++y)
@ -336,4 +336,273 @@ namespace
EXPECT_EQ(tile->mTileId, 2); EXPECT_EQ(tile->mTileId, 2);
EXPECT_EQ(tile->mVersion, navMeshFormatVersion); EXPECT_EQ(tile->mVersion, navMeshFormatVersion);
} }
TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, repeated_tile_updates_should_be_delayed)
{
mRecastMeshManager.setWorldspace(mWorldspace, nullptr);
mSettings.mMaxTilesNumber = 9;
mSettings.mMinUpdateInterval = std::chrono::milliseconds(250);
AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, nullptr);
const auto navMeshCacheItem = std::make_shared<GuardedNavMeshCacheItem>(1, mSettings);
std::map<TilePosition, ChangeType> changedTiles;
for (int x = -3; x <= 3; ++x)
for (int y = -3; y <= 3; ++y)
changedTiles.emplace(TilePosition{ x, y }, ChangeType::update);
updater.post(mAgentBounds, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles);
updater.wait(WaitConditionType::allJobsDone, &mListener);
{
const AsyncNavMeshUpdaterStats stats = updater.getStats();
EXPECT_EQ(stats.mJobs, 0);
EXPECT_EQ(stats.mWaiting.mDelayed, 0);
}
updater.post(mAgentBounds, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles);
{
const AsyncNavMeshUpdaterStats stats = updater.getStats();
EXPECT_EQ(stats.mJobs, 49);
EXPECT_EQ(stats.mWaiting.mDelayed, 49);
}
updater.wait(WaitConditionType::allJobsDone, &mListener);
{
const AsyncNavMeshUpdaterStats stats = updater.getStats();
EXPECT_EQ(stats.mJobs, 0);
EXPECT_EQ(stats.mWaiting.mDelayed, 0);
}
}
struct DetourNavigatorSpatialJobQueueTest : Test
{
const AgentBounds mAgentBounds{ CollisionShapeType::Aabb, osg::Vec3f(1, 1, 1) };
const std::shared_ptr<GuardedNavMeshCacheItem> mNavMeshCacheItemPtr;
const std::weak_ptr<GuardedNavMeshCacheItem> mNavMeshCacheItem = mNavMeshCacheItemPtr;
const std::string_view mWorldspace = "worldspace";
const TilePosition mChangedTile{ 0, 0 };
const std::chrono::steady_clock::time_point mProcessTime{};
const TilePosition mPlayerTile{ 0, 0 };
const int mMaxTiles = 9;
};
TEST_F(DetourNavigatorSpatialJobQueueTest, should_store_multiple_jobs_per_tile)
{
std::list<Job> jobs;
SpatialJobQueue queue;
queue.push(jobs.emplace(jobs.end(), mAgentBounds, mNavMeshCacheItem, "worldspace1", mChangedTile,
ChangeType::remove, mProcessTime));
queue.push(jobs.emplace(jobs.end(), mAgentBounds, mNavMeshCacheItem, "worldspace2", mChangedTile,
ChangeType::update, mProcessTime));
ASSERT_EQ(queue.size(), 2);
const auto job1 = queue.pop(mChangedTile);
ASSERT_TRUE(job1.has_value());
EXPECT_EQ((*job1)->mWorldspace, "worldspace1");
const auto job2 = queue.pop(mChangedTile);
ASSERT_TRUE(job2.has_value());
EXPECT_EQ((*job2)->mWorldspace, "worldspace2");
EXPECT_EQ(queue.size(), 0);
}
struct DetourNavigatorJobQueueTest : DetourNavigatorSpatialJobQueueTest
{
};
TEST_F(DetourNavigatorJobQueueTest, pop_should_return_nullptr_from_empty)
{
JobQueue queue;
ASSERT_FALSE(queue.hasJob());
ASSERT_FALSE(queue.pop(mPlayerTile).has_value());
}
TEST_F(DetourNavigatorJobQueueTest, push_on_change_type_remove_should_add_to_removing)
{
const std::chrono::steady_clock::time_point processTime{};
std::list<Job> jobs;
const JobIt job = jobs.emplace(
jobs.end(), mAgentBounds, mNavMeshCacheItem, mWorldspace, mChangedTile, ChangeType::remove, processTime);
JobQueue queue;
queue.push(job);
EXPECT_EQ(queue.getStats().mRemoving, 1);
}
TEST_F(DetourNavigatorJobQueueTest, pop_should_return_last_removing)
{
std::list<Job> jobs;
JobQueue queue;
queue.push(jobs.emplace(jobs.end(), mAgentBounds, mNavMeshCacheItem, mWorldspace, TilePosition(0, 0),
ChangeType::remove, mProcessTime));
queue.push(jobs.emplace(jobs.end(), mAgentBounds, mNavMeshCacheItem, mWorldspace, TilePosition(1, 0),
ChangeType::remove, mProcessTime));
ASSERT_TRUE(queue.hasJob());
const auto job = queue.pop(mPlayerTile);
ASSERT_TRUE(job.has_value());
EXPECT_EQ((*job)->mChangedTile, TilePosition(1, 0));
}
TEST_F(DetourNavigatorJobQueueTest, push_on_change_type_not_remove_should_add_to_updating)
{
std::list<Job> jobs;
const JobIt job = jobs.emplace(
jobs.end(), mAgentBounds, mNavMeshCacheItem, mWorldspace, mChangedTile, ChangeType::update, mProcessTime);
JobQueue queue;
queue.push(job);
EXPECT_EQ(queue.getStats().mUpdating, 1);
}
TEST_F(DetourNavigatorJobQueueTest, pop_should_return_nearest_to_player_tile)
{
std::list<Job> jobs;
JobQueue queue;
queue.push(jobs.emplace(jobs.end(), mAgentBounds, mNavMeshCacheItem, mWorldspace, TilePosition(0, 0),
ChangeType::update, mProcessTime));
queue.push(jobs.emplace(jobs.end(), mAgentBounds, mNavMeshCacheItem, mWorldspace, TilePosition(1, 0),
ChangeType::update, mProcessTime));
ASSERT_TRUE(queue.hasJob());
const auto job = queue.pop(TilePosition(1, 0));
ASSERT_TRUE(job.has_value());
EXPECT_EQ((*job)->mChangedTile, TilePosition(1, 0));
}
TEST_F(DetourNavigatorJobQueueTest, push_on_processing_time_more_than_now_should_add_to_delayed)
{
const std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();
const std::chrono::steady_clock::time_point processTime = now + std::chrono::seconds(1);
std::list<Job> jobs;
const JobIt job = jobs.emplace(
jobs.end(), mAgentBounds, mNavMeshCacheItem, mWorldspace, mChangedTile, ChangeType::update, processTime);
JobQueue queue;
queue.push(job, now);
EXPECT_EQ(queue.getStats().mDelayed, 1);
}
TEST_F(DetourNavigatorJobQueueTest, pop_should_return_when_delayed_job_is_ready)
{
const std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();
const std::chrono::steady_clock::time_point processTime = now + std::chrono::seconds(1);
std::list<Job> jobs;
const JobIt job = jobs.emplace(
jobs.end(), mAgentBounds, mNavMeshCacheItem, mWorldspace, mChangedTile, ChangeType::update, processTime);
JobQueue queue;
queue.push(job, now);
ASSERT_FALSE(queue.hasJob(now));
ASSERT_FALSE(queue.pop(mPlayerTile, now).has_value());
ASSERT_TRUE(queue.hasJob(processTime));
EXPECT_TRUE(queue.pop(mPlayerTile, processTime).has_value());
}
TEST_F(DetourNavigatorJobQueueTest, update_should_move_ready_delayed_to_updating)
{
const std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();
const std::chrono::steady_clock::time_point processTime = now + std::chrono::seconds(1);
std::list<Job> jobs;
const JobIt job = jobs.emplace(
jobs.end(), mAgentBounds, mNavMeshCacheItem, mWorldspace, mChangedTile, ChangeType::update, processTime);
JobQueue queue;
queue.push(job, now);
ASSERT_EQ(queue.getStats().mDelayed, 1);
queue.update(mPlayerTile, mMaxTiles, processTime);
EXPECT_EQ(queue.getStats().mDelayed, 0);
EXPECT_EQ(queue.getStats().mUpdating, 1);
}
TEST_F(DetourNavigatorJobQueueTest, update_should_move_ready_delayed_to_removing_when_out_of_range)
{
const std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();
const std::chrono::steady_clock::time_point processTime = now + std::chrono::seconds(1);
std::list<Job> jobs;
const JobIt job = jobs.emplace(
jobs.end(), mAgentBounds, mNavMeshCacheItem, mWorldspace, mChangedTile, ChangeType::update, processTime);
JobQueue queue;
queue.push(job, now);
ASSERT_EQ(queue.getStats().mDelayed, 1);
queue.update(TilePosition(10, 10), mMaxTiles, processTime);
EXPECT_EQ(queue.getStats().mDelayed, 0);
EXPECT_EQ(queue.getStats().mRemoving, 1);
EXPECT_EQ(job->mChangeType, ChangeType::remove);
}
TEST_F(DetourNavigatorJobQueueTest, update_should_move_updating_to_removing_when_out_of_range)
{
std::list<Job> jobs;
JobQueue queue;
queue.push(jobs.emplace(
jobs.end(), mAgentBounds, mNavMeshCacheItem, mWorldspace, mChangedTile, ChangeType::update, mProcessTime));
queue.push(jobs.emplace(jobs.end(), mAgentBounds, mNavMeshCacheItem, mWorldspace, TilePosition(10, 10),
ChangeType::update, mProcessTime));
ASSERT_EQ(queue.getStats().mUpdating, 2);
queue.update(TilePosition(10, 10), mMaxTiles);
EXPECT_EQ(queue.getStats().mUpdating, 1);
EXPECT_EQ(queue.getStats().mRemoving, 1);
}
TEST_F(DetourNavigatorJobQueueTest, clear_should_remove_all)
{
const std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();
const std::chrono::steady_clock::time_point processTime = now + std::chrono::seconds(1);
std::list<Job> jobs;
const JobIt removing = jobs.emplace(jobs.end(), mAgentBounds, mNavMeshCacheItem, mWorldspace,
TilePosition(0, 0), ChangeType::remove, mProcessTime);
const JobIt updating = jobs.emplace(jobs.end(), mAgentBounds, mNavMeshCacheItem, mWorldspace,
TilePosition(1, 0), ChangeType::update, mProcessTime);
const JobIt delayed = jobs.emplace(jobs.end(), mAgentBounds, mNavMeshCacheItem, mWorldspace, TilePosition(2, 0),
ChangeType::update, processTime);
JobQueue queue;
queue.push(removing);
queue.push(updating);
queue.push(delayed, now);
ASSERT_EQ(queue.getStats().mRemoving, 1);
ASSERT_EQ(queue.getStats().mUpdating, 1);
ASSERT_EQ(queue.getStats().mDelayed, 1);
queue.clear();
EXPECT_EQ(queue.getStats().mRemoving, 0);
EXPECT_EQ(queue.getStats().mUpdating, 0);
EXPECT_EQ(queue.getStats().mDelayed, 0);
}
} }

@ -16,8 +16,9 @@
#include <osg/io_utils> #include <osg/io_utils>
#include <boost/geometry.hpp>
#include <algorithm> #include <algorithm>
#include <numeric>
#include <optional> #include <optional>
#include <set> #include <set>
#include <tuple> #include <tuple>
@ -49,40 +50,6 @@ namespace DetourNavigator
return false; return false;
} }
auto getPriority(const Job& job) noexcept
{
return std::make_tuple(-static_cast<std::underlying_type_t<JobState>>(job.mState), job.mProcessTime,
job.mChangeType, job.mTryNumber, job.mDistanceToPlayer, job.mDistanceToOrigin);
}
struct LessByJobPriority
{
bool operator()(JobIt lhs, JobIt rhs) const noexcept { return getPriority(*lhs) < getPriority(*rhs); }
};
void insertPrioritizedJob(JobIt job, std::deque<JobIt>& queue)
{
const auto it = std::upper_bound(queue.begin(), queue.end(), job, LessByJobPriority{});
queue.insert(it, job);
}
auto getDbPriority(const Job& job) noexcept
{
return std::make_tuple(static_cast<std::underlying_type_t<JobState>>(job.mState), job.mChangeType,
job.mDistanceToPlayer, job.mDistanceToOrigin);
}
struct LessByJobDbPriority
{
bool operator()(JobIt lhs, JobIt rhs) const noexcept { return getDbPriority(*lhs) < getDbPriority(*rhs); }
};
void insertPrioritizedDbJob(JobIt job, std::deque<JobIt>& queue)
{
const auto it = std::upper_bound(queue.begin(), queue.end(), job, LessByJobDbPriority{});
queue.insert(it, job);
}
auto getAgentAndTile(const Job& job) noexcept auto getAgentAndTile(const Job& job) noexcept
{ {
return std::make_tuple(job.mAgentBounds, job.mChangedTile); return std::make_tuple(job.mAgentBounds, job.mChangedTile);
@ -97,16 +64,6 @@ namespace DetourNavigator
settings.mRecast, settings.mWriteToNavMeshDb); settings.mRecast, settings.mWriteToNavMeshDb);
} }
void updateJobs(std::deque<JobIt>& jobs, TilePosition playerTile, int maxTiles)
{
for (JobIt job : jobs)
{
job->mDistanceToPlayer = getManhattanDistance(job->mChangedTile, playerTile);
if (!shouldAddTile(job->mChangedTile, playerTile, maxTiles))
job->mChangeType = ChangeType::remove;
}
}
std::size_t getNextJobId() std::size_t getNextJobId()
{ {
static std::atomic_size_t nextJobId{ 1 }; static std::atomic_size_t nextJobId{ 1 };
@ -134,7 +91,7 @@ namespace DetourNavigator
} }
Job::Job(const AgentBounds& agentBounds, std::weak_ptr<GuardedNavMeshCacheItem> navMeshCacheItem, Job::Job(const AgentBounds& agentBounds, std::weak_ptr<GuardedNavMeshCacheItem> navMeshCacheItem,
std::string_view worldspace, const TilePosition& changedTile, ChangeType changeType, int distanceToPlayer, std::string_view worldspace, const TilePosition& changedTile, ChangeType changeType,
std::chrono::steady_clock::time_point processTime) std::chrono::steady_clock::time_point processTime)
: mId(getNextJobId()) : mId(getNextJobId())
, mAgentBounds(agentBounds) , mAgentBounds(agentBounds)
@ -143,11 +100,148 @@ namespace DetourNavigator
, mChangedTile(changedTile) , mChangedTile(changedTile)
, mProcessTime(processTime) , mProcessTime(processTime)
, mChangeType(changeType) , mChangeType(changeType)
, mDistanceToPlayer(distanceToPlayer)
, mDistanceToOrigin(getManhattanDistance(changedTile, TilePosition{ 0, 0 }))
{ {
} }
void SpatialJobQueue::clear()
{
mValues.clear();
mIndex.clear();
mSize = 0;
}
void SpatialJobQueue::push(JobIt job)
{
auto it = mValues.find(job->mChangedTile);
if (it == mValues.end())
{
it = mValues.emplace_hint(it, job->mChangedTile, std::deque<JobIt>());
mIndex.insert(IndexValue(IndexPoint(job->mChangedTile.x(), job->mChangedTile.y()), it));
}
it->second.push_back(job);
++mSize;
}
std::optional<JobIt> SpatialJobQueue::pop(TilePosition playerTile)
{
const IndexPoint point(playerTile.x(), playerTile.y());
const auto it = mIndex.qbegin(boost::geometry::index::nearest(point, 1));
if (it == mIndex.qend())
return std::nullopt;
const UpdatingMap::iterator mapIt = it->second;
std::deque<JobIt>& tileJobs = mapIt->second;
JobIt result = tileJobs.front();
tileJobs.pop_front();
--mSize;
if (tileJobs.empty())
{
mValues.erase(mapIt);
mIndex.remove(*it);
}
return result;
}
void SpatialJobQueue::update(TilePosition playerTile, int maxTiles, std::vector<JobIt>& removing)
{
for (auto it = mValues.begin(); it != mValues.end();)
{
if (shouldAddTile(it->first, playerTile, maxTiles))
{
++it;
continue;
}
for (JobIt job : it->second)
{
job->mChangeType = ChangeType::remove;
removing.push_back(job);
}
mSize -= it->second.size();
mIndex.remove(IndexValue(IndexPoint(it->first.x(), it->first.y()), it));
it = mValues.erase(it);
}
}
bool JobQueue::hasJob(std::chrono::steady_clock::time_point now) const
{
return !mRemoving.empty() || mUpdating.size() > 0
|| (!mDelayed.empty() && mDelayed.front()->mProcessTime <= now);
}
void JobQueue::clear()
{
mRemoving.clear();
mDelayed.clear();
mUpdating.clear();
}
void JobQueue::push(JobIt job, std::chrono::steady_clock::time_point now)
{
if (job->mProcessTime > now)
{
mDelayed.push_back(job);
return;
}
if (job->mChangeType == ChangeType::remove)
{
mRemoving.push_back(job);
return;
}
mUpdating.push(job);
}
std::optional<JobIt> JobQueue::pop(TilePosition playerTile, std::chrono::steady_clock::time_point now)
{
if (!mRemoving.empty())
{
const JobIt result = mRemoving.back();
mRemoving.pop_back();
return result;
}
if (const std::optional<JobIt> result = mUpdating.pop(playerTile))
return result;
if (mDelayed.empty() || mDelayed.front()->mProcessTime > now)
return std::nullopt;
const JobIt result = mDelayed.front();
mDelayed.pop_front();
return result;
}
void JobQueue::update(TilePosition playerTile, int maxTiles, std::chrono::steady_clock::time_point now)
{
mUpdating.update(playerTile, maxTiles, mRemoving);
while (!mDelayed.empty() && mDelayed.front()->mProcessTime <= now)
{
const JobIt job = mDelayed.front();
mDelayed.pop_front();
if (shouldAddTile(job->mChangedTile, playerTile, maxTiles))
{
mUpdating.push(job);
}
else
{
job->mChangeType = ChangeType::remove;
mRemoving.push_back(job);
}
}
}
AsyncNavMeshUpdater::AsyncNavMeshUpdater(const Settings& settings, TileCachedRecastMeshManager& recastMeshManager, AsyncNavMeshUpdater::AsyncNavMeshUpdater(const Settings& settings, TileCachedRecastMeshManager& recastMeshManager,
OffMeshConnectionsManager& offMeshConnectionsManager, std::unique_ptr<NavMeshDb>&& db) OffMeshConnectionsManager& offMeshConnectionsManager, std::unique_ptr<NavMeshDb>&& db)
: mSettings(settings) : mSettings(settings)
@ -183,42 +277,44 @@ namespace DetourNavigator
std::unique_lock lock(mMutex); std::unique_lock lock(mMutex);
if (playerTileChanged) if (playerTileChanged)
updateJobs(mWaiting, playerTile, mSettings.get().mMaxTilesNumber); {
Log(Debug::Debug) << "Player tile has been changed to " << playerTile;
mWaiting.update(playerTile, mSettings.get().mMaxTilesNumber);
}
for (const auto& [changedTile, changeType] : changedTiles) for (const auto& [changedTile, changeType] : changedTiles)
{ {
if (mPushed.emplace(agentBounds, changedTile).second) if (mPushed.emplace(agentBounds, changedTile).second)
{ {
const auto processTime = changeType == ChangeType::update const auto processTime = [&, changedTile = changedTile, changeType = changeType] {
? mLastUpdates[std::tie(agentBounds, changedTile)] + mSettings.get().mMinUpdateInterval if (changeType != ChangeType::update)
: std::chrono::steady_clock::time_point(); return std::chrono::steady_clock::time_point();
const auto lastUpdate = mLastUpdates.find(std::tie(agentBounds, changedTile));
if (lastUpdate == mLastUpdates.end())
return std::chrono::steady_clock::time_point();
return lastUpdate->second + mSettings.get().mMinUpdateInterval;
}();
const JobIt it = mJobs.emplace(mJobs.end(), agentBounds, navMeshCacheItem, worldspace, changedTile, const JobIt it = mJobs.emplace(
changeType, getManhattanDistance(changedTile, playerTile), processTime); mJobs.end(), agentBounds, navMeshCacheItem, worldspace, changedTile, changeType, processTime);
Log(Debug::Debug) << "Post job " << it->mId << " for agent=(" << it->mAgentBounds << ")" Log(Debug::Debug) << "Post job " << it->mId << " for agent=(" << it->mAgentBounds << ")"
<< " changedTile=(" << it->mChangedTile << ")" << " changedTile=(" << it->mChangedTile << ")"
<< " changeType=" << it->mChangeType; << " changeType=" << it->mChangeType;
if (playerTileChanged) mWaiting.push(it);
mWaiting.push_back(it);
else
insertPrioritizedJob(it, mWaiting);
} }
} }
if (playerTileChanged)
std::sort(mWaiting.begin(), mWaiting.end(), LessByJobPriority{});
Log(Debug::Debug) << "Posted " << mJobs.size() << " navigator jobs"; Log(Debug::Debug) << "Posted " << mJobs.size() << " navigator jobs";
if (!mWaiting.empty()) if (mWaiting.hasJob())
mHasJob.notify_all(); mHasJob.notify_all();
lock.unlock(); lock.unlock();
if (playerTileChanged && mDbWorker != nullptr) if (playerTileChanged && mDbWorker != nullptr)
mDbWorker->updateJobs(playerTile, mSettings.get().mMaxTilesNumber); mDbWorker->update(playerTile);
} }
void AsyncNavMeshUpdater::wait(WaitConditionType waitConditionType, Loading::Listener* listener) void AsyncNavMeshUpdater::wait(WaitConditionType waitConditionType, Loading::Listener* listener)
@ -310,7 +406,7 @@ namespace DetourNavigator
{ {
const std::lock_guard<std::mutex> lock(mMutex); const std::lock_guard<std::mutex> lock(mMutex);
result.mJobs = mJobs.size(); result.mJobs = mJobs.size();
result.mWaiting = mWaiting.size(); result.mWaiting = mWaiting.getStats();
result.mPushed = mPushed.size(); result.mPushed = mPushed.size();
} }
result.mProcessing = mProcessingTiles.lockConst()->size(); result.mProcessing = mProcessingTiles.lockConst()->size();
@ -332,7 +428,8 @@ namespace DetourNavigator
if (JobIt job = getNextJob(); job != mJobs.end()) if (JobIt job = getNextJob(); job != mJobs.end())
{ {
const JobStatus status = processJob(*job); const JobStatus status = processJob(*job);
Log(Debug::Debug) << "Processed job " << job->mId << " with status=" << status; Log(Debug::Debug) << "Processed job " << job->mId << " with status=" << status
<< " changeType=" << job->mChangeType;
switch (status) switch (status)
{ {
case JobStatus::Done: case JobStatus::Done:
@ -366,7 +463,9 @@ namespace DetourNavigator
JobStatus AsyncNavMeshUpdater::processJob(Job& job) JobStatus AsyncNavMeshUpdater::processJob(Job& job)
{ {
Log(Debug::Debug) << "Processing job " << job.mId << " by thread=" << std::this_thread::get_id(); Log(Debug::Debug) << "Processing job " << job.mId << " for agent=(" << job.mAgentBounds << ")"
<< " changedTile=(" << job.mChangedTile << ")"
<< " changeType=" << job.mChangeType << " by thread=" << std::this_thread::get_id();
const auto navMeshCacheItem = job.mNavMeshCacheItem.lock(); const auto navMeshCacheItem = job.mNavMeshCacheItem.lock();
@ -378,6 +477,7 @@ namespace DetourNavigator
if (!shouldAddTile(job.mChangedTile, playerTile, mSettings.get().mMaxTilesNumber)) if (!shouldAddTile(job.mChangedTile, playerTile, mSettings.get().mMaxTilesNumber))
{ {
Log(Debug::Debug) << "Ignore add tile by job " << job.mId << ": too far from player"; Log(Debug::Debug) << "Ignore add tile by job " << job.mId << ": too far from player";
job.mChangeType = ChangeType::remove;
navMeshCacheItem->lock()->removeTile(job.mChangedTile); navMeshCacheItem->lock()->removeTile(job.mChangedTile);
return JobStatus::Done; return JobStatus::Done;
} }
@ -545,9 +645,8 @@ namespace DetourNavigator
bool shouldStop = false; bool shouldStop = false;
const auto hasJob = [&] { const auto hasJob = [&] {
shouldStop = mShouldStop; shouldStop = mShouldStop.load();
return shouldStop return shouldStop || mWaiting.hasJob();
|| (!mWaiting.empty() && mWaiting.front()->mProcessTime <= std::chrono::steady_clock::now());
}; };
if (!mHasJob.wait_for(lock, std::chrono::milliseconds(10), hasJob)) if (!mHasJob.wait_for(lock, std::chrono::milliseconds(10), hasJob))
@ -560,9 +659,15 @@ namespace DetourNavigator
if (shouldStop) if (shouldStop)
return mJobs.end(); return mJobs.end();
const JobIt job = mWaiting.front(); const TilePosition playerTile = *mPlayerTile.lockConst();
mWaiting.pop_front(); JobIt job = mJobs.end();
if (const std::optional<JobIt> nextJob = mWaiting.pop(playerTile))
job = *nextJob;
if (job == mJobs.end())
return job;
Log(Debug::Debug) << "Pop job " << job->mId << " by thread=" << std::this_thread::get_id(); Log(Debug::Debug) << "Pop job " << job->mId << " by thread=" << std::this_thread::get_id();
@ -571,9 +676,9 @@ namespace DetourNavigator
if (!lockTile(job->mId, job->mAgentBounds, job->mChangedTile)) if (!lockTile(job->mId, job->mAgentBounds, job->mChangedTile))
{ {
Log(Debug::Debug) << "Failed to lock tile by job " << job->mId << " try=" << job->mTryNumber; Log(Debug::Debug) << "Failed to lock tile by job " << job->mId;
++job->mTryNumber; job->mProcessTime = std::chrono::steady_clock::now() + mSettings.get().mMinUpdateInterval;
insertPrioritizedJob(job, mWaiting); mWaiting.push(job);
return mJobs.end(); return mJobs.end();
} }
@ -653,7 +758,7 @@ namespace DetourNavigator
{ {
Log(Debug::Debug) << "Enqueueing job " << job->mId << " by thread=" << std::this_thread::get_id(); Log(Debug::Debug) << "Enqueueing job " << job->mId << " by thread=" << std::this_thread::get_id();
const std::lock_guard lock(mMutex); const std::lock_guard lock(mMutex);
insertPrioritizedJob(job, mWaiting); mWaiting.push(job);
mHasJob.notify_all(); mHasJob.notify_all();
} }
@ -667,40 +772,47 @@ namespace DetourNavigator
void DbJobQueue::push(JobIt job) void DbJobQueue::push(JobIt job)
{ {
const std::lock_guard lock(mMutex); const std::lock_guard lock(mMutex);
insertPrioritizedDbJob(job, mJobs);
if (isWritingDbJob(*job)) if (isWritingDbJob(*job))
++mWritingJobs; mWriting.push_back(job);
else else
++mReadingJobs; mReading.push(job);
mHasJob.notify_all(); mHasJob.notify_all();
} }
std::optional<JobIt> DbJobQueue::pop() std::optional<JobIt> DbJobQueue::pop()
{ {
std::unique_lock lock(mMutex); std::unique_lock lock(mMutex);
mHasJob.wait(lock, [&] { return mShouldStop || !mJobs.empty(); });
if (mJobs.empty()) const auto hasJob = [&] { return mShouldStop || mReading.size() > 0 || mWriting.size() > 0; };
mHasJob.wait(lock, hasJob);
if (mShouldStop)
return std::nullopt; return std::nullopt;
const JobIt job = mJobs.front();
mJobs.pop_front(); if (const std::optional<JobIt> job = mReading.pop(mPlayerTile))
if (isWritingDbJob(*job)) return job;
--mWritingJobs;
else if (mWriting.empty())
--mReadingJobs; return std::nullopt;
const JobIt job = mWriting.front();
mWriting.pop_front();
return job; return job;
} }
void DbJobQueue::update(TilePosition playerTile, int maxTiles) void DbJobQueue::update(TilePosition playerTile)
{ {
const std::lock_guard lock(mMutex); const std::lock_guard lock(mMutex);
updateJobs(mJobs, playerTile, maxTiles); mPlayerTile = playerTile;
std::sort(mJobs.begin(), mJobs.end(), LessByJobDbPriority{});
} }
void DbJobQueue::stop() void DbJobQueue::stop()
{ {
const std::lock_guard lock(mMutex); const std::lock_guard lock(mMutex);
mJobs.clear(); mReading.clear();
mWriting.clear();
mShouldStop = true; mShouldStop = true;
mHasJob.notify_all(); mHasJob.notify_all();
} }
@ -708,7 +820,10 @@ namespace DetourNavigator
DbJobQueueStats DbJobQueue::getStats() const DbJobQueueStats DbJobQueue::getStats() const
{ {
const std::lock_guard lock(mMutex); const std::lock_guard lock(mMutex);
return DbJobQueueStats{ .mWritingJobs = mWritingJobs, .mReadingJobs = mReadingJobs }; return DbJobQueueStats{
.mReadingJobs = mReading.size(),
.mWritingJobs = mWriting.size(),
};
} }
DbWorker::DbWorker(AsyncNavMeshUpdater& updater, std::unique_ptr<NavMeshDb>&& db, TileVersion version, DbWorker::DbWorker(AsyncNavMeshUpdater& updater, std::unique_ptr<NavMeshDb>&& db, TileVersion version,
@ -737,8 +852,10 @@ namespace DetourNavigator
DbWorkerStats DbWorker::getStats() const DbWorkerStats DbWorker::getStats() const
{ {
return DbWorkerStats{ .mJobs = mQueue.getStats(), return DbWorkerStats{
.mGetTileCount = mGetTileCount.load(std::memory_order_relaxed) }; .mJobs = mQueue.getStats(),
.mGetTileCount = mGetTileCount.load(std::memory_order_relaxed),
};
} }
void DbWorker::stop() void DbWorker::stop()

@ -14,6 +14,9 @@
#include "tileposition.hpp" #include "tileposition.hpp"
#include "waitconditiontype.hpp" #include "waitconditiontype.hpp"
#include <boost/geometry/geometries/point.hpp>
#include <boost/geometry/index/rtree.hpp>
#include <atomic> #include <atomic>
#include <chrono> #include <chrono>
#include <condition_variable> #include <condition_variable>
@ -49,11 +52,8 @@ namespace DetourNavigator
const std::weak_ptr<GuardedNavMeshCacheItem> mNavMeshCacheItem; const std::weak_ptr<GuardedNavMeshCacheItem> mNavMeshCacheItem;
const std::string mWorldspace; const std::string mWorldspace;
const TilePosition mChangedTile; const TilePosition mChangedTile;
const std::chrono::steady_clock::time_point mProcessTime; std::chrono::steady_clock::time_point mProcessTime;
unsigned mTryNumber = 0;
ChangeType mChangeType; ChangeType mChangeType;
int mDistanceToPlayer;
const int mDistanceToOrigin;
JobState mState = JobState::Initial; JobState mState = JobState::Initial;
std::vector<std::byte> mInput; std::vector<std::byte> mInput;
std::shared_ptr<RecastMesh> mRecastMesh; std::shared_ptr<RecastMesh> mRecastMesh;
@ -61,12 +61,65 @@ namespace DetourNavigator
std::unique_ptr<PreparedNavMeshData> mGeneratedNavMeshData; std::unique_ptr<PreparedNavMeshData> mGeneratedNavMeshData;
Job(const AgentBounds& agentBounds, std::weak_ptr<GuardedNavMeshCacheItem> navMeshCacheItem, Job(const AgentBounds& agentBounds, std::weak_ptr<GuardedNavMeshCacheItem> navMeshCacheItem,
std::string_view worldspace, const TilePosition& changedTile, ChangeType changeType, int distanceToPlayer, std::string_view worldspace, const TilePosition& changedTile, ChangeType changeType,
std::chrono::steady_clock::time_point processTime); std::chrono::steady_clock::time_point processTime);
}; };
using JobIt = std::list<Job>::iterator; using JobIt = std::list<Job>::iterator;
class SpatialJobQueue
{
public:
std::size_t size() const { return mSize; }
void clear();
void push(JobIt job);
std::optional<JobIt> pop(TilePosition playerTile);
void update(TilePosition playerTile, int maxTiles, std::vector<JobIt>& removing);
private:
using IndexPoint = boost::geometry::model::point<int, 2, boost::geometry::cs::cartesian>;
using UpdatingMap = std::map<TilePosition, std::deque<JobIt>>;
using IndexValue = std::pair<IndexPoint, UpdatingMap::iterator>;
std::size_t mSize = 0;
UpdatingMap mValues;
boost::geometry::index::rtree<IndexValue, boost::geometry::index::linear<4>> 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<JobIt> 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<JobIt> mRemoving;
SpatialJobQueue mUpdating;
std::deque<JobIt> mDelayed;
};
enum class JobStatus enum class JobStatus
{ {
Done, Done,
@ -83,7 +136,7 @@ namespace DetourNavigator
std::optional<JobIt> pop(); std::optional<JobIt> pop();
void update(TilePosition playerTile, int maxTiles); void update(TilePosition playerTile);
void stop(); void stop();
@ -92,10 +145,10 @@ namespace DetourNavigator
private: private:
mutable std::mutex mMutex; mutable std::mutex mMutex;
std::condition_variable mHasJob; std::condition_variable mHasJob;
std::deque<JobIt> mJobs; SpatialJobQueue mReading;
std::deque<JobIt> mWriting;
TilePosition mPlayerTile;
bool mShouldStop = false; bool mShouldStop = false;
std::size_t mWritingJobs = 0;
std::size_t mReadingJobs = 0;
}; };
class AsyncNavMeshUpdater; class AsyncNavMeshUpdater;
@ -112,7 +165,7 @@ namespace DetourNavigator
void enqueueJob(JobIt job); void enqueueJob(JobIt job);
void updateJobs(TilePosition playerTile, int maxTiles) { mQueue.update(playerTile, maxTiles); } void update(TilePosition playerTile) { mQueue.update(playerTile); }
void stop(); void stop();
@ -169,7 +222,7 @@ namespace DetourNavigator
std::condition_variable mDone; std::condition_variable mDone;
std::condition_variable mProcessed; std::condition_variable mProcessed;
std::list<Job> mJobs; std::list<Job> mJobs;
std::deque<JobIt> mWaiting; JobQueue mWaiting;
std::set<std::tuple<AgentBounds, TilePosition>> mPushed; std::set<std::tuple<AgentBounds, TilePosition>> mPushed;
Misc::ScopeGuarded<TilePosition> mPlayerTile; Misc::ScopeGuarded<TilePosition> mPlayerTile;
NavMeshTilesCache mNavMeshTilesCache; NavMeshTilesCache mNavMeshTilesCache;

@ -6,15 +6,9 @@ namespace DetourNavigator
enum class ChangeType enum class ChangeType
{ {
remove = 0, remove = 0,
mixed = 1, add = 1,
add = 2, update = 2,
update = 3,
}; };
inline ChangeType addChangeType(const ChangeType current, const ChangeType add)
{
return current == add ? current : ChangeType::mixed;
}
} }
#endif #endif

@ -184,8 +184,6 @@ namespace DetourNavigator
{ {
case ChangeType::remove: case ChangeType::remove:
return stream << "ChangeType::remove"; return stream << "ChangeType::remove";
case ChangeType::mixed:
return stream << "ChangeType::mixed";
case ChangeType::add: case ChangeType::add:
return stream << "ChangeType::add"; return stream << "ChangeType::add";
case ChangeType::update: case ChangeType::update:

@ -188,7 +188,7 @@ namespace DetourNavigator
if (shouldAdd && !presentInNavMesh) if (shouldAdd && !presentInNavMesh)
tilesToPost.emplace(tile, locked->isEmptyTile(tile) ? ChangeType::update : ChangeType::add); tilesToPost.emplace(tile, locked->isEmptyTile(tile) ? ChangeType::update : ChangeType::add);
else if (!shouldAdd && presentInNavMesh) else if (!shouldAdd && presentInNavMesh)
tilesToPost.emplace(tile, ChangeType::mixed); tilesToPost.emplace(tile, ChangeType::remove);
}); });
locked->forEachTilePosition([&](const TilePosition& tile) { locked->forEachTilePosition([&](const TilePosition& tile) {
if (!shouldAddTile(tile, playerTile, maxTiles)) if (!shouldAddTile(tile, playerTile, maxTiles))

@ -9,16 +9,18 @@ namespace DetourNavigator
void reportStats(const AsyncNavMeshUpdaterStats& stats, unsigned int frameNumber, osg::Stats& out) void reportStats(const AsyncNavMeshUpdaterStats& stats, unsigned int frameNumber, osg::Stats& out)
{ {
out.setAttribute(frameNumber, "NavMesh Jobs", static_cast<double>(stats.mJobs)); out.setAttribute(frameNumber, "NavMesh Jobs", static_cast<double>(stats.mJobs));
out.setAttribute(frameNumber, "NavMesh Waiting", static_cast<double>(stats.mWaiting)); out.setAttribute(frameNumber, "NavMesh Removing", static_cast<double>(stats.mWaiting.mRemoving));
out.setAttribute(frameNumber, "NavMesh Updating", static_cast<double>(stats.mWaiting.mUpdating));
out.setAttribute(frameNumber, "NavMesh Delayed", static_cast<double>(stats.mWaiting.mDelayed));
out.setAttribute(frameNumber, "NavMesh Pushed", static_cast<double>(stats.mPushed)); out.setAttribute(frameNumber, "NavMesh Pushed", static_cast<double>(stats.mPushed));
out.setAttribute(frameNumber, "NavMesh Processing", static_cast<double>(stats.mProcessing)); out.setAttribute(frameNumber, "NavMesh Processing", static_cast<double>(stats.mProcessing));
if (stats.mDb.has_value()) if (stats.mDb.has_value())
{ {
out.setAttribute(
frameNumber, "NavMesh DbJobs Write", static_cast<double>(stats.mDb->mJobs.mWritingJobs));
out.setAttribute( out.setAttribute(
frameNumber, "NavMesh DbJobs Read", static_cast<double>(stats.mDb->mJobs.mReadingJobs)); frameNumber, "NavMesh DbJobs Read", static_cast<double>(stats.mDb->mJobs.mReadingJobs));
out.setAttribute(
frameNumber, "NavMesh DbJobs Write", static_cast<double>(stats.mDb->mJobs.mWritingJobs));
out.setAttribute(frameNumber, "NavMesh DbCache Get", static_cast<double>(stats.mDb->mGetTileCount)); out.setAttribute(frameNumber, "NavMesh DbCache Get", static_cast<double>(stats.mDb->mGetTileCount));
out.setAttribute(frameNumber, "NavMesh DbCache Hit", static_cast<double>(stats.mDbGetTileHits)); out.setAttribute(frameNumber, "NavMesh DbCache Hit", static_cast<double>(stats.mDbGetTileHits));

@ -11,10 +11,17 @@ namespace osg
namespace DetourNavigator namespace DetourNavigator
{ {
struct JobQueueStats
{
std::size_t mRemoving = 0;
std::size_t mUpdating = 0;
std::size_t mDelayed = 0;
};
struct DbJobQueueStats struct DbJobQueueStats
{ {
std::size_t mWritingJobs = 0;
std::size_t mReadingJobs = 0; std::size_t mReadingJobs = 0;
std::size_t mWritingJobs = 0;
}; };
struct DbWorkerStats struct DbWorkerStats
@ -35,7 +42,7 @@ namespace DetourNavigator
struct AsyncNavMeshUpdaterStats struct AsyncNavMeshUpdaterStats
{ {
std::size_t mJobs = 0; std::size_t mJobs = 0;
std::size_t mWaiting = 0; JobQueueStats mWaiting;
std::size_t mPushed = 0; std::size_t mPushed = 0;
std::size_t mProcessing = 0; std::size_t mProcessing = 0;
std::size_t mDbGetTileHits = 0; std::size_t mDbGetTileHits = 0;

@ -10,7 +10,6 @@
#include <boost/geometry/geometry.hpp> #include <boost/geometry/geometry.hpp>
#include <algorithm>
#include <limits> #include <limits>
#include <vector> #include <vector>
@ -416,7 +415,7 @@ namespace DetourNavigator
if (tile == mChangedTiles.end()) if (tile == mChangedTiles.end())
mChangedTiles.emplace(tilePosition, changeType); mChangedTiles.emplace(tilePosition, changeType);
else else
tile->second = addChangeType(tile->second, changeType); tile->second = changeType == ChangeType::remove ? changeType : tile->second;
} }
std::map<osg::Vec2i, ChangeType> TileCachedRecastMeshManager::takeChangedTiles(const UpdateGuard* guard) std::map<osg::Vec2i, ChangeType> TileCachedRecastMeshManager::takeChangedTiles(const UpdateGuard* guard)

@ -49,6 +49,8 @@ namespace Resource
std::vector<std::string> generateAllStatNames() std::vector<std::string> generateAllStatNames()
{ {
constexpr std::size_t itemsPerPage = 24;
constexpr std::string_view firstPage[] = { constexpr std::string_view firstPage[] = {
"FrameNumber", "FrameNumber",
"", "",
@ -76,6 +78,8 @@ namespace Resource
"", "",
}; };
static_assert(std::size(firstPage) == itemsPerPage);
constexpr std::string_view caches[] = { constexpr std::string_view caches[] = {
"Node", "Node",
"Shape", "Shape",
@ -100,7 +104,9 @@ namespace Resource
constexpr std::string_view navMesh[] = { constexpr std::string_view navMesh[] = {
"NavMesh Jobs", "NavMesh Jobs",
"NavMesh Waiting", "NavMesh Removing",
"NavMesh Updating",
"NavMesh Delayed",
"NavMesh Pushed", "NavMesh Pushed",
"NavMesh Processing", "NavMesh Processing",
"NavMesh DbJobs Write", "NavMesh DbJobs Write",
@ -129,6 +135,7 @@ namespace Resource
for (std::string_view name : cellPreloader) for (std::string_view name : cellPreloader)
statNames.emplace_back(name); statNames.emplace_back(name);
while (statNames.size() % itemsPerPage != 0)
statNames.emplace_back(); statNames.emplace_back();
for (std::string_view name : navMesh) for (std::string_view name : navMesh)

Loading…
Cancel
Save