Merge branch 'navmesh_queue' into 'master'

Optimize queue for navmesh async jobs

See merge request OpenMW/openmw!4025
master
psi29a 2 weeks ago
commit b91ff634e4

@ -591,6 +591,12 @@ namespace MWWorld
// Must be cleared before mRendering is destroyed
if (mProjectileManager)
mProjectileManager->clear();
if (Settings::navigator().mWaitForAllJobsOnExit)
{
Log(Debug::Verbose) << "Waiting for all navmesh jobs to be done...";
mNavigator->wait(DetourNavigator::WaitConditionType::allJobsDone, nullptr);
}
}
void World::setRandomSeed(uint32_t seed)

@ -267,7 +267,6 @@ namespace
updater.wait(WaitConditionType::allJobsDone, &mListener);
updater.stop();
const std::set<TilePosition> present{
TilePosition(-2, 0),
TilePosition(-1, -1),
TilePosition(-1, 0),
TilePosition(-1, 1),
@ -278,6 +277,7 @@ namespace
TilePosition(0, 2),
TilePosition(1, -1),
TilePosition(1, 0),
TilePosition(1, 1),
};
for (int x = -5; x <= 5; ++x)
for (int y = -5; y <= 5; ++y)
@ -336,4 +336,273 @@ namespace
EXPECT_EQ(tile->mTileId, 2);
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);
}
}

@ -1055,6 +1055,96 @@ namespace
}
}
TEST_P(DetourNavigatorUpdateTest, update_should_change_covered_area_when_player_moves_without_waiting_for_all)
{
Loading::Listener listener;
Settings settings = makeSettings();
settings.mMaxTilesNumber = 1;
settings.mWaitUntilMinDistanceToPlayer = 1;
NavigatorImpl navigator(settings, nullptr);
const AgentBounds agentBounds{ CollisionShapeType::Aabb, { 29, 29, 66 } };
ASSERT_TRUE(navigator.addAgent(agentBounds));
GetParam()(navigator);
{
auto updateGuard = navigator.makeUpdateGuard();
navigator.update(osg::Vec3f(3000, 3000, 0), updateGuard.get());
}
navigator.wait(WaitConditionType::requiredTilesPresent, &listener);
{
const auto navMesh = navigator.getNavMesh(agentBounds);
ASSERT_NE(navMesh, nullptr);
const TilePosition expectedTile(4, 4);
const auto usedTiles = getUsedTiles(*navMesh->lockConst());
EXPECT_THAT(usedTiles, Contains(expectedTile)) << usedTiles;
}
{
auto updateGuard = navigator.makeUpdateGuard();
navigator.update(osg::Vec3f(6000, 3000, 0), updateGuard.get());
}
navigator.wait(WaitConditionType::requiredTilesPresent, &listener);
{
const auto navMesh = navigator.getNavMesh(agentBounds);
ASSERT_NE(navMesh, nullptr);
const TilePosition expectedTile(8, 4);
const auto usedTiles = getUsedTiles(*navMesh->lockConst());
EXPECT_THAT(usedTiles, Contains(expectedTile)) << usedTiles;
}
}
TEST_P(DetourNavigatorUpdateTest, update_should_change_covered_area_when_player_moves_with_db)
{
Loading::Listener listener;
Settings settings = makeSettings();
settings.mMaxTilesNumber = 1;
settings.mWaitUntilMinDistanceToPlayer = 1;
NavigatorImpl navigator(settings, std::make_unique<NavMeshDb>(":memory:", settings.mMaxDbFileSize));
const AgentBounds agentBounds{ CollisionShapeType::Aabb, { 29, 29, 66 } };
ASSERT_TRUE(navigator.addAgent(agentBounds));
GetParam()(navigator);
{
auto updateGuard = navigator.makeUpdateGuard();
navigator.update(osg::Vec3f(3000, 3000, 0), updateGuard.get());
}
navigator.wait(WaitConditionType::requiredTilesPresent, &listener);
{
const auto navMesh = navigator.getNavMesh(agentBounds);
ASSERT_NE(navMesh, nullptr);
const TilePosition expectedTile(4, 4);
const auto usedTiles = getUsedTiles(*navMesh->lockConst());
EXPECT_THAT(usedTiles, Contains(expectedTile)) << usedTiles;
}
{
auto updateGuard = navigator.makeUpdateGuard();
navigator.update(osg::Vec3f(6000, 3000, 0), updateGuard.get());
}
navigator.wait(WaitConditionType::requiredTilesPresent, &listener);
{
const auto navMesh = navigator.getNavMesh(agentBounds);
ASSERT_NE(navMesh, nullptr);
const TilePosition expectedTile(8, 4);
const auto usedTiles = getUsedTiles(*navMesh->lockConst());
EXPECT_THAT(usedTiles, Contains(expectedTile)) << usedTiles;
}
}
struct AddHeightfieldSurface
{
static constexpr std::size_t sSize = 65;

@ -16,8 +16,9 @@
#include <osg/io_utils>
#include <boost/geometry.hpp>
#include <algorithm>
#include <numeric>
#include <optional>
#include <set>
#include <tuple>
@ -49,40 +50,6 @@ namespace DetourNavigator
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
{
return std::make_tuple(job.mAgentBounds, job.mChangedTile);
@ -97,16 +64,6 @@ namespace DetourNavigator
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()
{
static std::atomic_size_t nextJobId{ 1 };
@ -134,7 +91,7 @@ namespace DetourNavigator
}
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)
: mId(getNextJobId())
, mAgentBounds(agentBounds)
@ -143,11 +100,148 @@ namespace DetourNavigator
, mChangedTile(changedTile)
, mProcessTime(processTime)
, 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,
OffMeshConnectionsManager& offMeshConnectionsManager, std::unique_ptr<NavMeshDb>&& db)
: mSettings(settings)
@ -180,48 +274,47 @@ namespace DetourNavigator
if (!playerTileChanged && changedTiles.empty())
return;
const int maxTiles
= std::min(mSettings.get().mMaxTilesNumber, navMeshCacheItem->lockConst()->getImpl().getParams()->maxTiles);
std::unique_lock lock(mMutex);
if (playerTileChanged)
updateJobs(mWaiting, playerTile, maxTiles);
{
Log(Debug::Debug) << "Player tile has been changed to " << playerTile;
mWaiting.update(playerTile, mSettings.get().mMaxTilesNumber);
}
for (const auto& [changedTile, changeType] : changedTiles)
{
if (mPushed.emplace(agentBounds, changedTile).second)
{
const auto processTime = changeType == ChangeType::update
? mLastUpdates[std::tie(agentBounds, changedTile)] + mSettings.get().mMinUpdateInterval
: std::chrono::steady_clock::time_point();
const JobIt it = mJobs.emplace(mJobs.end(), agentBounds, navMeshCacheItem, worldspace, changedTile,
changeType, getManhattanDistance(changedTile, playerTile), processTime);
const auto processTime = [&, changedTile = changedTile, changeType = changeType] {
if (changeType != ChangeType::update)
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, changeType, processTime);
Log(Debug::Debug) << "Post job " << it->mId << " for agent=(" << it->mAgentBounds << ")"
<< " changedTile=(" << it->mChangedTile << ") "
<< " changedTile=(" << it->mChangedTile << ")"
<< " changeType=" << it->mChangeType;
if (playerTileChanged)
mWaiting.push_back(it);
else
insertPrioritizedJob(it, mWaiting);
mWaiting.push(it);
}
}
if (playerTileChanged)
std::sort(mWaiting.begin(), mWaiting.end(), LessByJobPriority{});
Log(Debug::Debug) << "Posted " << mJobs.size() << " navigator jobs";
if (!mWaiting.empty())
if (mWaiting.hasJob())
mHasJob.notify_all();
lock.unlock();
if (playerTileChanged && mDbWorker != nullptr)
mDbWorker->updateJobs(playerTile, maxTiles);
mDbWorker->update(playerTile);
}
void AsyncNavMeshUpdater::wait(WaitConditionType waitConditionType, Loading::Listener* listener)
@ -313,7 +406,7 @@ namespace DetourNavigator
{
const std::lock_guard<std::mutex> lock(mMutex);
result.mJobs = mJobs.size();
result.mWaiting = mWaiting.size();
result.mWaiting = mWaiting.getStats();
result.mPushed = mPushed.size();
}
result.mProcessing = mProcessingTiles.lockConst()->size();
@ -335,7 +428,8 @@ namespace DetourNavigator
if (JobIt job = getNextJob(); job != mJobs.end())
{
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)
{
case JobStatus::Done:
@ -346,7 +440,8 @@ namespace DetourNavigator
removeJob(job);
break;
case JobStatus::Fail:
repost(job);
unlockTile(job->mId, job->mAgentBounds, job->mChangedTile);
removeJob(job);
break;
case JobStatus::MemoryCacheMiss:
{
@ -368,7 +463,9 @@ namespace DetourNavigator
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();
@ -376,12 +473,11 @@ namespace DetourNavigator
return JobStatus::Done;
const auto playerTile = *mPlayerTile.lockConst();
const int maxTiles
= std::min(mSettings.get().mMaxTilesNumber, navMeshCacheItem->lockConst()->getImpl().getParams()->maxTiles);
if (!shouldAddTile(job.mChangedTile, playerTile, maxTiles))
if (!shouldAddTile(job.mChangedTile, playerTile, mSettings.get().mMaxTilesNumber))
{
Log(Debug::Debug) << "Ignore add tile by job " << job.mId << ": too far from player";
job.mChangeType = ChangeType::remove;
navMeshCacheItem->lock()->removeTile(job.mChangedTile);
return JobStatus::Done;
}
@ -549,9 +645,8 @@ namespace DetourNavigator
bool shouldStop = false;
const auto hasJob = [&] {
shouldStop = mShouldStop;
return shouldStop
|| (!mWaiting.empty() && mWaiting.front()->mProcessTime <= std::chrono::steady_clock::now());
shouldStop = mShouldStop.load();
return shouldStop || mWaiting.hasJob();
};
if (!mHasJob.wait_for(lock, std::chrono::milliseconds(10), hasJob))
@ -564,9 +659,15 @@ namespace DetourNavigator
if (shouldStop)
return mJobs.end();
const JobIt job = mWaiting.front();
const TilePosition playerTile = *mPlayerTile.lockConst();
JobIt job = mJobs.end();
mWaiting.pop_front();
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();
@ -575,9 +676,9 @@ namespace DetourNavigator
if (!lockTile(job->mId, job->mAgentBounds, job->mChangedTile))
{
Log(Debug::Debug) << "Failed to lock tile by job " << job->mId << " try=" << job->mTryNumber;
++job->mTryNumber;
insertPrioritizedJob(job, mWaiting);
Log(Debug::Debug) << "Failed to lock tile by job " << job->mId;
job->mProcessTime = std::chrono::steady_clock::now() + mSettings.get().mMinUpdateInterval;
mWaiting.push(job);
return mJobs.end();
}
@ -613,26 +714,6 @@ namespace DetourNavigator
writeToFile(shared->lockConst()->getImpl(), mSettings.get().mNavMeshPathPrefix, navMeshRevision);
}
void AsyncNavMeshUpdater::repost(JobIt job)
{
unlockTile(job->mId, job->mAgentBounds, job->mChangedTile);
if (mShouldStop || job->mTryNumber > 2)
return;
const std::lock_guard<std::mutex> lock(mMutex);
if (mPushed.emplace(job->mAgentBounds, job->mChangedTile).second)
{
++job->mTryNumber;
insertPrioritizedJob(job, mWaiting);
mHasJob.notify_all();
return;
}
mJobs.erase(job);
}
bool AsyncNavMeshUpdater::lockTile(
std::size_t jobId, const AgentBounds& agentBounds, const TilePosition& changedTile)
{
@ -677,7 +758,7 @@ namespace DetourNavigator
{
Log(Debug::Debug) << "Enqueueing job " << job->mId << " by thread=" << std::this_thread::get_id();
const std::lock_guard lock(mMutex);
insertPrioritizedJob(job, mWaiting);
mWaiting.push(job);
mHasJob.notify_all();
}
@ -691,40 +772,47 @@ namespace DetourNavigator
void DbJobQueue::push(JobIt job)
{
const std::lock_guard lock(mMutex);
insertPrioritizedDbJob(job, mJobs);
if (isWritingDbJob(*job))
++mWritingJobs;
mWriting.push_back(job);
else
++mReadingJobs;
mReading.push(job);
mHasJob.notify_all();
}
std::optional<JobIt> DbJobQueue::pop()
{
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;
const JobIt job = mJobs.front();
mJobs.pop_front();
if (isWritingDbJob(*job))
--mWritingJobs;
else
--mReadingJobs;
if (const std::optional<JobIt> job = mReading.pop(mPlayerTile))
return job;
if (mWriting.empty())
return std::nullopt;
const JobIt job = mWriting.front();
mWriting.pop_front();
return job;
}
void DbJobQueue::update(TilePosition playerTile, int maxTiles)
void DbJobQueue::update(TilePosition playerTile)
{
const std::lock_guard lock(mMutex);
updateJobs(mJobs, playerTile, maxTiles);
std::sort(mJobs.begin(), mJobs.end(), LessByJobDbPriority{});
mPlayerTile = playerTile;
}
void DbJobQueue::stop()
{
const std::lock_guard lock(mMutex);
mJobs.clear();
mReading.clear();
mWriting.clear();
mShouldStop = true;
mHasJob.notify_all();
}
@ -732,7 +820,10 @@ namespace DetourNavigator
DbJobQueueStats DbJobQueue::getStats() const
{
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,
@ -761,8 +852,10 @@ namespace DetourNavigator
DbWorkerStats DbWorker::getStats() const
{
return DbWorkerStats{ .mJobs = mQueue.getStats(),
.mGetTileCount = mGetTileCount.load(std::memory_order_relaxed) };
return DbWorkerStats{
.mJobs = mQueue.getStats(),
.mGetTileCount = mGetTileCount.load(std::memory_order_relaxed),
};
}
void DbWorker::stop()

@ -14,6 +14,9 @@
#include "tileposition.hpp"
#include "waitconditiontype.hpp"
#include <boost/geometry/geometries/point.hpp>
#include <boost/geometry/index/rtree.hpp>
#include <atomic>
#include <chrono>
#include <condition_variable>
@ -49,11 +52,8 @@ namespace DetourNavigator
const std::weak_ptr<GuardedNavMeshCacheItem> mNavMeshCacheItem;
const std::string mWorldspace;
const TilePosition mChangedTile;
const std::chrono::steady_clock::time_point mProcessTime;
unsigned mTryNumber = 0;
std::chrono::steady_clock::time_point mProcessTime;
ChangeType mChangeType;
int mDistanceToPlayer;
const int mDistanceToOrigin;
JobState mState = JobState::Initial;
std::vector<std::byte> mInput;
std::shared_ptr<RecastMesh> mRecastMesh;
@ -61,12 +61,65 @@ namespace DetourNavigator
std::unique_ptr<PreparedNavMeshData> mGeneratedNavMeshData;
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);
};
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
{
Done,
@ -83,7 +136,7 @@ namespace DetourNavigator
std::optional<JobIt> pop();
void update(TilePosition playerTile, int maxTiles);
void update(TilePosition playerTile);
void stop();
@ -92,10 +145,10 @@ namespace DetourNavigator
private:
mutable std::mutex mMutex;
std::condition_variable mHasJob;
std::deque<JobIt> mJobs;
SpatialJobQueue mReading;
std::deque<JobIt> mWriting;
TilePosition mPlayerTile;
bool mShouldStop = false;
std::size_t mWritingJobs = 0;
std::size_t mReadingJobs = 0;
};
class AsyncNavMeshUpdater;
@ -112,7 +165,7 @@ namespace DetourNavigator
void enqueueJob(JobIt job);
void updateJobs(TilePosition playerTile, int maxTiles) { mQueue.update(playerTile, maxTiles); }
void update(TilePosition playerTile) { mQueue.update(playerTile); }
void stop();
@ -169,7 +222,7 @@ namespace DetourNavigator
std::condition_variable mDone;
std::condition_variable mProcessed;
std::list<Job> mJobs;
std::deque<JobIt> mWaiting;
JobQueue mWaiting;
std::set<std::tuple<AgentBounds, TilePosition>> mPushed;
Misc::ScopeGuarded<TilePosition> mPlayerTile;
NavMeshTilesCache mNavMeshTilesCache;
@ -197,8 +250,6 @@ namespace DetourNavigator
void writeDebugFiles(const Job& job, const RecastMesh* recastMesh) const;
void repost(JobIt job);
bool lockTile(std::size_t jobId, const AgentBounds& agentBounds, const TilePosition& changedTile);
void unlockTile(std::size_t jobId, const AgentBounds& agentBounds, const TilePosition& changedTile);

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

@ -79,13 +79,13 @@ namespace DetourNavigator
switch (v)
{
case CollisionShapeType::Aabb:
return s << "AgentShapeType::Aabb";
return s << "CollisionShapeType::Aabb";
case CollisionShapeType::RotatingBox:
return s << "AgentShapeType::RotatingBox";
return s << "CollisionShapeType::RotatingBox";
case CollisionShapeType::Cylinder:
return s << "AgentShapeType::Cylinder";
return s << "CollisionShapeType::Cylinder";
}
return s << "AgentShapeType::" << static_cast<std::underlying_type_t<CollisionShapeType>>(v);
return s << "CollisionShapeType::" << static_cast<std::underlying_type_t<CollisionShapeType>>(v);
}
std::ostream& operator<<(std::ostream& s, const AgentBounds& v)
@ -184,8 +184,6 @@ namespace DetourNavigator
{
case ChangeType::remove:
return stream << "ChangeType::remove";
case ChangeType::mixed:
return stream << "ChangeType::mixed";
case ChangeType::add:
return stream << "ChangeType::add";
case ChangeType::update:

@ -480,15 +480,6 @@ namespace DetourNavigator
return true;
}
template <class T>
unsigned long getMinValuableBitsNumber(const T value)
{
unsigned long power = 0;
while (power < sizeof(T) * 8 && (static_cast<T>(1) << power) < value)
++power;
return power;
}
std::pair<float, float> getBoundsByZ(
const RecastMesh& recastMesh, float agentHalfExtentsZ, const RecastSettings& settings)
{
@ -528,10 +519,7 @@ namespace DetourNavigator
return { minZ, maxZ };
}
}
} // namespace DetourNavigator
namespace DetourNavigator
{
std::unique_ptr<PreparedNavMeshData> prepareNavMeshTileData(const RecastMesh& recastMesh,
std::string_view worldspace, const TilePosition& tilePosition, const AgentBounds& agentBounds,
const RecastSettings& settings)
@ -621,22 +609,12 @@ namespace DetourNavigator
void initEmptyNavMesh(const Settings& settings, dtNavMesh& navMesh)
{
// Max tiles and max polys affect how the tile IDs are caculated.
// There are 22 bits available for identifying a tile and a polygon.
const int polysAndTilesBits = 22;
const auto polysBits = getMinValuableBitsNumber(settings.mDetour.mMaxPolys);
if (polysBits >= polysAndTilesBits)
throw InvalidArgument("Too many polygons per tile");
const auto tilesBits = polysAndTilesBits - polysBits;
dtNavMeshParams params;
std::fill_n(params.orig, 3, 0.0f);
params.tileWidth = settings.mRecast.mTileSize * settings.mRecast.mCellSize;
params.tileHeight = settings.mRecast.mTileSize * settings.mRecast.mCellSize;
params.maxTiles = 1 << tilesBits;
params.maxPolys = 1 << polysBits;
params.maxTiles = settings.mMaxTilesNumber;
params.maxPolys = settings.mDetour.mMaxPolys;
const auto status = navMesh.init(&params);

@ -131,6 +131,15 @@ namespace DetourNavigator
function(position, tile.mVersion, *meshTile);
}
template <class Function>
void forEachTilePosition(Function&& function) const
{
for (const auto& [position, tile] : mUsedTiles)
function(position);
for (const TilePosition& position : mEmptyTiles)
function(position);
}
private:
struct Tile
{

@ -13,8 +13,6 @@
#include <DetourNavMesh.h>
#include <iterator>
namespace
{
/// Safely reset shared_ptr with definite underlying object destrutor call.
@ -179,9 +177,9 @@ namespace DetourNavigator
{
std::map<osg::Vec2i, ChangeType> tilesToPost = changedTiles;
{
const int maxTiles = mSettings.mMaxTilesNumber;
const auto locked = cached->lockConst();
const auto& navMesh = locked->getImpl();
const auto maxTiles = std::min(mSettings.mMaxTilesNumber, navMesh.getParams()->maxTiles);
getTilesPositions(range, [&](const TilePosition& tile) {
if (changedTiles.find(tile) != changedTiles.end())
return;
@ -190,7 +188,11 @@ namespace DetourNavigator
if (shouldAdd && !presentInNavMesh)
tilesToPost.emplace(tile, locked->isEmptyTile(tile) ? ChangeType::update : ChangeType::add);
else if (!shouldAdd && presentInNavMesh)
tilesToPost.emplace(tile, ChangeType::mixed);
tilesToPost.emplace(tile, ChangeType::remove);
});
locked->forEachTilePosition([&](const TilePosition& tile) {
if (!shouldAddTile(tile, playerTile, maxTiles))
tilesToPost.emplace(tile, ChangeType::remove);
});
}
mAsyncNavMeshUpdater.post(agentBounds, cached, playerTile, mWorldspace, tilesToPost);

@ -3,41 +3,81 @@
#include <components/misc/constants.hpp>
#include <components/settings/values.hpp>
#include <algorithm>
#include <stdexcept>
#include <string>
namespace DetourNavigator
{
RecastSettings makeRecastSettingsFromSettingsManager()
namespace
{
RecastSettings result;
result.mBorderSize = ::Settings::navigator().mBorderSize;
result.mCellHeight = ::Settings::navigator().mCellHeight;
result.mCellSize = ::Settings::navigator().mCellSize;
result.mDetailSampleDist = ::Settings::navigator().mDetailSampleDist;
result.mDetailSampleMaxError = ::Settings::navigator().mDetailSampleMaxError;
result.mMaxClimb = Constants::sStepSizeUp;
result.mMaxSimplificationError = ::Settings::navigator().mMaxSimplificationError;
result.mMaxSlope = Constants::sMaxSlope;
result.mRecastScaleFactor = ::Settings::navigator().mRecastScaleFactor;
result.mSwimHeightScale = 0;
result.mMaxEdgeLen = ::Settings::navigator().mMaxEdgeLen;
result.mMaxVertsPerPoly = ::Settings::navigator().mMaxVertsPerPoly;
result.mRegionMergeArea = ::Settings::navigator().mRegionMergeArea;
result.mRegionMinArea = ::Settings::navigator().mRegionMinArea;
result.mTileSize = ::Settings::navigator().mTileSize;
struct NavMeshLimits
{
int mMaxTiles;
int mMaxPolys;
};
return result;
}
template <class T>
unsigned long getMinValuableBitsNumber(const T value)
{
unsigned long power = 0;
while (power < sizeof(T) * 8 && (static_cast<T>(1) << power) < value)
++power;
return power;
}
DetourSettings makeDetourSettingsFromSettingsManager()
{
DetourSettings result;
NavMeshLimits getNavMeshTileLimits(const DetourSettings& settings)
{
// Max tiles and max polys affect how the tile IDs are caculated.
// There are 22 bits available for identifying a tile and a polygon.
constexpr int polysAndTilesBits = 22;
const unsigned long polysBits = getMinValuableBitsNumber(settings.mMaxPolys);
result.mMaxNavMeshQueryNodes = ::Settings::navigator().mMaxNavMeshQueryNodes;
result.mMaxPolys = ::Settings::navigator().mMaxPolygonsPerTile;
result.mMaxPolygonPathSize = ::Settings::navigator().mMaxPolygonPathSize;
result.mMaxSmoothPathSize = ::Settings::navigator().mMaxSmoothPathSize;
if (polysBits >= polysAndTilesBits)
throw std::invalid_argument("Too many polygons per tile: " + std::to_string(settings.mMaxPolys));
return result;
const unsigned long tilesBits = polysAndTilesBits - polysBits;
return NavMeshLimits{
.mMaxTiles = static_cast<int>(1 << tilesBits),
.mMaxPolys = static_cast<int>(1 << polysBits),
};
}
RecastSettings makeRecastSettingsFromSettingsManager()
{
RecastSettings result;
result.mBorderSize = ::Settings::navigator().mBorderSize;
result.mCellHeight = ::Settings::navigator().mCellHeight;
result.mCellSize = ::Settings::navigator().mCellSize;
result.mDetailSampleDist = ::Settings::navigator().mDetailSampleDist;
result.mDetailSampleMaxError = ::Settings::navigator().mDetailSampleMaxError;
result.mMaxClimb = Constants::sStepSizeUp;
result.mMaxSimplificationError = ::Settings::navigator().mMaxSimplificationError;
result.mMaxSlope = Constants::sMaxSlope;
result.mRecastScaleFactor = ::Settings::navigator().mRecastScaleFactor;
result.mSwimHeightScale = 0;
result.mMaxEdgeLen = ::Settings::navigator().mMaxEdgeLen;
result.mMaxVertsPerPoly = ::Settings::navigator().mMaxVertsPerPoly;
result.mRegionMergeArea = ::Settings::navigator().mRegionMergeArea;
result.mRegionMinArea = ::Settings::navigator().mRegionMinArea;
result.mTileSize = ::Settings::navigator().mTileSize;
return result;
}
DetourSettings makeDetourSettingsFromSettingsManager()
{
DetourSettings result;
result.mMaxNavMeshQueryNodes = ::Settings::navigator().mMaxNavMeshQueryNodes;
result.mMaxPolys = ::Settings::navigator().mMaxPolygonsPerTile;
result.mMaxPolygonPathSize = ::Settings::navigator().mMaxPolygonPathSize;
result.mMaxSmoothPathSize = ::Settings::navigator().mMaxSmoothPathSize;
return result;
}
}
Settings makeSettingsFromSettingsManager()
@ -46,7 +86,12 @@ namespace DetourNavigator
result.mRecast = makeRecastSettingsFromSettingsManager();
result.mDetour = makeDetourSettingsFromSettingsManager();
result.mMaxTilesNumber = ::Settings::navigator().mMaxTilesNumber;
const NavMeshLimits limits = getNavMeshTileLimits(result.mDetour);
result.mDetour.mMaxPolys = limits.mMaxPolys;
result.mMaxTilesNumber = std::min(limits.mMaxTiles, ::Settings::navigator().mMaxTilesNumber.get());
result.mWaitUntilMinDistanceToPlayer = ::Settings::navigator().mWaitUntilMinDistanceToPlayer;
result.mAsyncNavMeshUpdaterThreads = ::Settings::navigator().mAsyncNavMeshUpdaterThreads;
result.mMaxNavMeshTilesCacheSize = ::Settings::navigator().mMaxNavMeshTilesCacheSize;

@ -55,10 +55,6 @@ namespace DetourNavigator
inline constexpr std::int64_t navMeshFormatVersion = 2;
RecastSettings makeRecastSettingsFromSettingsManager();
DetourSettings makeDetourSettingsFromSettingsManager();
Settings makeSettingsFromSettingsManager();
}

@ -9,16 +9,18 @@ namespace DetourNavigator
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 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 Processing", static_cast<double>(stats.mProcessing));
if (stats.mDb.has_value())
{
out.setAttribute(
frameNumber, "NavMesh DbJobs Write", static_cast<double>(stats.mDb->mJobs.mWritingJobs));
out.setAttribute(
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 Hit", static_cast<double>(stats.mDbGetTileHits));

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

@ -10,7 +10,6 @@
#include <boost/geometry/geometry.hpp>
#include <algorithm>
#include <limits>
#include <vector>
@ -416,7 +415,7 @@ namespace DetourNavigator
if (tile == mChangedTiles.end())
mChangedTiles.emplace(tilePosition, changeType);
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)

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

@ -63,6 +63,7 @@ namespace Settings
SettingValue<bool> mEnableNavMeshDiskCache{ mIndex, "Navigator", "enable nav mesh disk cache" };
SettingValue<bool> mWriteToNavmeshdb{ mIndex, "Navigator", "write to navmeshdb" };
SettingValue<std::uint64_t> mMaxNavmeshdbFileSize{ mIndex, "Navigator", "max navmeshdb file size" };
SettingValue<bool> mWaitForAllJobsOnExit{ mIndex, "Navigator", "wait for all jobs on exit" };
};
}

@ -245,6 +245,16 @@ Absent pieces usually mean a bug in recast mesh tiles building.
Allows to do in-game debug.
Potentially decreases performance.
wait for all jobs on exit
-------------------------
:Type: boolean
:Range: True/False
:Default: False
Wait until all queued async navmesh jobs are processed before exiting the engine.
Useful when a benchmark generates jobs to write into navmeshdb faster than they are processed.
Expert settings
***************

@ -981,7 +981,7 @@ enable agents paths render = false
enable recast mesh render = false
# Max number of navmesh tiles (value >= 0)
max tiles number = 512
max tiles number = 1024
# Min time duration for the same tile update in milliseconds (value >= 0)
min update interval ms = 250
@ -999,6 +999,9 @@ write to navmeshdb = true
# Approximate maximum file size of navigation mesh cache stored on disk in bytes (value > 0)
max navmeshdb file size = 2147483648
# Wait until all queued async navmesh jobs are processed before exiting the engine (true, false)
wait for all jobs on exit = false
[Shadows]
# Enable or disable shadows. Bear in mind that this will force OpenMW to use shaders as if "[Shaders]/force shaders" was set to true.

Loading…
Cancel
Save