Write generated navmesh to navmeshdb

Perform all request to db in a single thread to avoid blocking navmesh
generator threads due to slow write operations.

Write to db navmesh for all changes except update as it done for memory cache.

Batch multiple db operations into a single transaction to speed up writing by
not executing fsync after each insert/update query. All reads are performed in
the same transaction so they see uncommited data.
pull/3225/head
elsid 3 years ago
parent 9e0451c714
commit 96eb8d7be9
No known key found for this signature in database
GPG Key ID: B845CB9FEE18AB40

@ -43,6 +43,7 @@ if (GTEST_FOUND AND GMOCK_FOUND)
detournavigator/tilecachedrecastmeshmanager.cpp detournavigator/tilecachedrecastmeshmanager.cpp
detournavigator/navmeshdb.cpp detournavigator/navmeshdb.cpp
detournavigator/serialization.cpp detournavigator/serialization.cpp
detournavigator/asyncnavmeshupdater.cpp
serialization/binaryreader.cpp serialization/binaryreader.cpp
serialization/binarywriter.cpp serialization/binarywriter.cpp

@ -0,0 +1,201 @@
#include "settings.hpp"
#include <components/detournavigator/asyncnavmeshupdater.hpp>
#include <components/detournavigator/makenavmesh.hpp>
#include <components/detournavigator/serialization.hpp>
#include <components/loadinglistener/loadinglistener.hpp>
#include <components/detournavigator/navmeshdbutils.hpp>
#include <components/detournavigator/dbrefgeometryobject.hpp>
#include <DetourNavMesh.h>
#include <gtest/gtest.h>
#include <map>
namespace
{
using namespace testing;
using namespace DetourNavigator;
using namespace DetourNavigator::Tests;
void addHeightFieldPlane(TileCachedRecastMeshManager& recastMeshManager)
{
const osg::Vec2i cellPosition(0, 0);
const int cellSize = 8192;
recastMeshManager.addHeightfield(cellPosition, cellSize, HeightfieldPlane {0});
}
struct DetourNavigatorAsyncNavMeshUpdaterTest : Test
{
Settings mSettings = makeSettings();
TileCachedRecastMeshManager mRecastMeshManager {mSettings.mRecast};
OffMeshConnectionsManager mOffMeshConnectionsManager {mSettings.mRecast};
const osg::Vec3f mAgentHalfExtents {29, 29, 66};
const TilePosition mPlayerTile {0, 0};
const std::string mWorldspace = "sys::default";
Loading::Listener mListener;
};
TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, for_all_jobs_done_when_empty_wait_should_terminate)
{
AsyncNavMeshUpdater updater {mSettings, mRecastMeshManager, mOffMeshConnectionsManager, nullptr};
updater.wait(mListener, WaitConditionType::allJobsDone);
}
TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, for_required_tiles_present_when_empty_wait_should_terminate)
{
AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, nullptr);
updater.wait(mListener, WaitConditionType::requiredTilesPresent);
}
TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, post_should_generate_navmesh_tile)
{
mRecastMeshManager.setWorldspace(mWorldspace);
addHeightFieldPlane(mRecastMeshManager);
AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, nullptr);
const auto navMeshCacheItem = std::make_shared<GuardedNavMeshCacheItem>(makeEmptyNavMesh(mSettings), 1);
const std::map<TilePosition, ChangeType> changedTiles {{TilePosition {0, 0}, ChangeType::add}};
updater.post(mAgentHalfExtents, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles);
updater.wait(mListener, WaitConditionType::allJobsDone);
EXPECT_NE(navMeshCacheItem->lockConst()->getImpl().getTileRefAt(0, 0, 0), 0);
}
TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, repeated_post_should_lead_to_cache_hit)
{
mRecastMeshManager.setWorldspace(mWorldspace);
addHeightFieldPlane(mRecastMeshManager);
AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, nullptr);
const auto navMeshCacheItem = std::make_shared<GuardedNavMeshCacheItem>(makeEmptyNavMesh(mSettings), 1);
const std::map<TilePosition, ChangeType> changedTiles {{TilePosition {0, 0}, ChangeType::add}};
updater.post(mAgentHalfExtents, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles);
updater.wait(mListener, WaitConditionType::allJobsDone);
{
const auto stats = updater.getStats();
ASSERT_EQ(stats.mCache.mGetCount, 1);
ASSERT_EQ(stats.mCache.mHitCount, 0);
}
updater.post(mAgentHalfExtents, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles);
updater.wait(mListener, WaitConditionType::allJobsDone);
{
const auto stats = updater.getStats();
EXPECT_EQ(stats.mCache.mGetCount, 2);
EXPECT_EQ(stats.mCache.mHitCount, 1);
}
}
TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, post_for_update_change_type_should_not_update_cache)
{
mRecastMeshManager.setWorldspace(mWorldspace);
addHeightFieldPlane(mRecastMeshManager);
AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, nullptr);
const auto navMeshCacheItem = std::make_shared<GuardedNavMeshCacheItem>(makeEmptyNavMesh(mSettings), 1);
const std::map<TilePosition, ChangeType> changedTiles {{TilePosition {0, 0}, ChangeType::update}};
updater.post(mAgentHalfExtents, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles);
updater.wait(mListener, WaitConditionType::allJobsDone);
{
const auto stats = updater.getStats();
ASSERT_EQ(stats.mCache.mGetCount, 1);
ASSERT_EQ(stats.mCache.mHitCount, 0);
}
updater.post(mAgentHalfExtents, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles);
updater.wait(mListener, WaitConditionType::allJobsDone);
{
const auto stats = updater.getStats();
EXPECT_EQ(stats.mCache.mGetCount, 2);
EXPECT_EQ(stats.mCache.mHitCount, 0);
}
}
TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, post_should_write_generated_tile_to_db)
{
mRecastMeshManager.setWorldspace(mWorldspace);
addHeightFieldPlane(mRecastMeshManager);
auto db = std::make_unique<NavMeshDb>(":memory:");
NavMeshDb* const dbPtr = db.get();
AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, std::move(db));
const auto navMeshCacheItem = std::make_shared<GuardedNavMeshCacheItem>(makeEmptyNavMesh(mSettings), 1);
const TilePosition tilePosition {0, 0};
const std::map<TilePosition, ChangeType> changedTiles {{tilePosition, ChangeType::add}};
updater.post(mAgentHalfExtents, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles);
updater.wait(mListener, WaitConditionType::allJobsDone);
const auto recastMesh = mRecastMeshManager.getMesh(mWorldspace, tilePosition);
ASSERT_NE(recastMesh, nullptr);
ShapeId nextShapeId {1};
const std::vector<DbRefGeometryObject> objects = makeDbRefGeometryObjects(recastMesh->getMeshSources(),
[&] (const MeshSource& v) { return resolveMeshSource(*dbPtr, v, nextShapeId); });
const auto tile = dbPtr->findTile(mWorldspace, tilePosition, serialize(mSettings.mRecast, *recastMesh, objects));
ASSERT_TRUE(tile.has_value());
EXPECT_EQ(tile->mTileId, 1);
EXPECT_EQ(tile->mVersion, mSettings.mNavMeshVersion);
}
TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, post_when_writing_to_db_disabled_should_not_write)
{
mRecastMeshManager.setWorldspace(mWorldspace);
addHeightFieldPlane(mRecastMeshManager);
auto db = std::make_unique<NavMeshDb>(":memory:");
NavMeshDb* const dbPtr = db.get();
mSettings.mWriteToNavMeshDb = false;
AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, std::move(db));
const auto navMeshCacheItem = std::make_shared<GuardedNavMeshCacheItem>(makeEmptyNavMesh(mSettings), 1);
const TilePosition tilePosition {0, 0};
const std::map<TilePosition, ChangeType> changedTiles {{tilePosition, ChangeType::add}};
updater.post(mAgentHalfExtents, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles);
updater.wait(mListener, WaitConditionType::allJobsDone);
const auto recastMesh = mRecastMeshManager.getMesh(mWorldspace, tilePosition);
ASSERT_NE(recastMesh, nullptr);
ShapeId nextShapeId {1};
const std::vector<DbRefGeometryObject> objects = makeDbRefGeometryObjects(recastMesh->getMeshSources(),
[&] (const MeshSource& v) { return resolveMeshSource(*dbPtr, v, nextShapeId); });
const auto tile = dbPtr->findTile(mWorldspace, tilePosition, serialize(mSettings.mRecast, *recastMesh, objects));
ASSERT_FALSE(tile.has_value());
}
TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, post_should_read_from_db_on_cache_miss)
{
mRecastMeshManager.setWorldspace(mWorldspace);
addHeightFieldPlane(mRecastMeshManager);
mSettings.mMaxNavMeshTilesCacheSize = 0;
AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, std::make_unique<NavMeshDb>(":memory:"));
const auto navMeshCacheItem = std::make_shared<GuardedNavMeshCacheItem>(makeEmptyNavMesh(mSettings), 1);
const std::map<TilePosition, ChangeType> changedTiles {{TilePosition {0, 0}, ChangeType::add}};
updater.post(mAgentHalfExtents, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles);
updater.wait(mListener, WaitConditionType::allJobsDone);
{
const auto stats = updater.getStats();
ASSERT_EQ(stats.mCache.mGetCount, 1);
ASSERT_EQ(stats.mCache.mHitCount, 0);
ASSERT_TRUE(stats.mDb.has_value());
ASSERT_EQ(stats.mDb->mGetTileCount, 1);
ASSERT_EQ(stats.mDbGetTileHits, 0);
}
updater.post(mAgentHalfExtents, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles);
updater.wait(mListener, WaitConditionType::allJobsDone);
{
const auto stats = updater.getStats();
EXPECT_EQ(stats.mCache.mGetCount, 2);
EXPECT_EQ(stats.mCache.mHitCount, 0);
ASSERT_TRUE(stats.mDb.has_value());
EXPECT_EQ(stats.mDb->mGetTileCount, 2);
EXPECT_EQ(stats.mDbGetTileHits, 1);
}
}
TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, on_changing_player_tile_post_should_remove_tiles_out_of_range)
{
mRecastMeshManager.setWorldspace(mWorldspace);
addHeightFieldPlane(mRecastMeshManager);
AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, nullptr);
const auto navMeshCacheItem = std::make_shared<GuardedNavMeshCacheItem>(makeEmptyNavMesh(mSettings), 1);
const std::map<TilePosition, ChangeType> changedTilesAdd {{TilePosition {0, 0}, ChangeType::add}};
updater.post(mAgentHalfExtents, navMeshCacheItem, mPlayerTile, mWorldspace, changedTilesAdd);
updater.wait(mListener, WaitConditionType::allJobsDone);
ASSERT_NE(navMeshCacheItem->lockConst()->getImpl().getTileRefAt(0, 0, 0), 0);
const std::map<TilePosition, ChangeType> changedTilesRemove {{TilePosition {0, 0}, ChangeType::remove}};
const TilePosition playerTile(100, 100);
updater.post(mAgentHalfExtents, navMeshCacheItem, playerTile, mWorldspace, changedTilesRemove);
updater.wait(mListener, WaitConditionType::allJobsDone);
EXPECT_EQ(navMeshCacheItem->lockConst()->getImpl().getTileRefAt(0, 0, 0), 0);
}
}

@ -1,4 +1,5 @@
#include "operators.hpp" #include "operators.hpp"
#include "settings.hpp"
#include <components/detournavigator/navigatorimpl.hpp> #include <components/detournavigator/navigatorimpl.hpp>
#include <components/detournavigator/exceptions.hpp> #include <components/detournavigator/exceptions.hpp>
@ -32,10 +33,11 @@ namespace
{ {
using namespace testing; using namespace testing;
using namespace DetourNavigator; using namespace DetourNavigator;
using namespace DetourNavigator::Tests;
struct DetourNavigatorNavigatorTest : Test struct DetourNavigatorNavigatorTest : Test
{ {
Settings mSettings; Settings mSettings = makeSettings();
std::unique_ptr<Navigator> mNavigator; std::unique_ptr<Navigator> mNavigator;
const osg::Vec3f mPlayerPosition; const osg::Vec3f mPlayerPosition;
const std::string mWorldspace; const std::string mWorldspace;
@ -62,34 +64,6 @@ namespace
, mOut(mPath) , mOut(mPath)
, mStepSize(28.333332061767578125f) , mStepSize(28.333332061767578125f)
{ {
mSettings.mEnableWriteRecastMeshToFile = false;
mSettings.mEnableWriteNavMeshToFile = false;
mSettings.mEnableRecastMeshFileNameRevision = false;
mSettings.mEnableNavMeshFileNameRevision = false;
mSettings.mRecast.mBorderSize = 16;
mSettings.mRecast.mCellHeight = 0.2f;
mSettings.mRecast.mCellSize = 0.2f;
mSettings.mRecast.mDetailSampleDist = 6;
mSettings.mRecast.mDetailSampleMaxError = 1;
mSettings.mRecast.mMaxClimb = 34;
mSettings.mRecast.mMaxSimplificationError = 1.3f;
mSettings.mRecast.mMaxSlope = 49;
mSettings.mRecast.mRecastScaleFactor = 0.017647058823529415f;
mSettings.mRecast.mSwimHeightScale = 0.89999997615814208984375f;
mSettings.mRecast.mMaxEdgeLen = 12;
mSettings.mDetour.mMaxNavMeshQueryNodes = 2048;
mSettings.mRecast.mMaxVertsPerPoly = 6;
mSettings.mRecast.mRegionMergeArea = 400;
mSettings.mRecast.mRegionMinArea = 64;
mSettings.mRecast.mTileSize = 64;
mSettings.mWaitUntilMinDistanceToPlayer = std::numeric_limits<int>::max();
mSettings.mAsyncNavMeshUpdaterThreads = 1;
mSettings.mMaxNavMeshTilesCacheSize = 1024 * 1024;
mSettings.mDetour.mMaxPolygonPathSize = 1024;
mSettings.mDetour.mMaxSmoothPathSize = 1024;
mSettings.mDetour.mMaxPolys = 4096;
mSettings.mMaxTilesNumber = 512;
mSettings.mMinUpdateInterval = std::chrono::milliseconds(50);
mNavigator.reset(new NavigatorImpl(mSettings, std::make_unique<NavMeshDb>(":memory:"))); mNavigator.reset(new NavigatorImpl(mSettings, std::make_unique<NavMeshDb>(":memory:")));
} }
}; };
@ -1013,7 +987,7 @@ namespace
mNavigator->update(mPlayerPosition); mNavigator->update(mPlayerPosition);
mNavigator->wait(mListener, WaitConditionType::allJobsDone); mNavigator->wait(mListener, WaitConditionType::allJobsDone);
const Version expectedVersion {1, 1}; const Version expectedVersion {1, 4};
const auto navMeshes = mNavigator->getNavMeshes(); const auto navMeshes = mNavigator->getNavMeshes();
ASSERT_EQ(navMeshes.size(), 1); ASSERT_EQ(navMeshes.size(), 1);

@ -80,51 +80,9 @@ namespace
return result; return result;
} }
template <class T>
void clone(const T* src, T*& dst, std::size_t size)
{
dst = static_cast<T*>(permRecastAlloc(static_cast<int>(size) * sizeof(T)));
std::memcpy(dst, src, size * sizeof(T));
}
void clone(const rcPolyMesh& src, rcPolyMesh& dst)
{
dst.nverts = src.nverts;
dst.npolys = src.npolys;
dst.maxpolys = src.maxpolys;
dst.nvp = src.nvp;
rcVcopy(dst.bmin, src.bmin);
rcVcopy(dst.bmax, src.bmax);
dst.cs = src.cs;
dst.ch = src.ch;
dst.borderSize = src.borderSize;
dst.maxEdgeError = src.maxEdgeError;
clone(src.verts, dst.verts, getVertsLength(dst));
clone(src.polys, dst.polys, getPolysLength(dst));
clone(src.regs, dst.regs, getRegsLength(dst));
clone(src.flags, dst.flags, getFlagsLength(dst));
clone(src.areas, dst.areas, getAreasLength(dst));
}
void clone(const rcPolyMeshDetail& src, rcPolyMeshDetail& dst)
{
dst.nmeshes = src.nmeshes;
dst.nverts = src.nverts;
dst.ntris = src.ntris;
clone(src.meshes, dst.meshes, getMeshesLength(dst));
clone(src.verts, dst.verts, getVertsLength(dst));
clone(src.tris, dst.tris, getTrisLength(dst));
}
std::unique_ptr<PreparedNavMeshData> clone(const PreparedNavMeshData& value) std::unique_ptr<PreparedNavMeshData> clone(const PreparedNavMeshData& value)
{ {
auto result = std::make_unique<PreparedNavMeshData>(); return std::make_unique<PreparedNavMeshData>(value);
result->mUserId = value.mUserId;
result->mCellHeight = value.mCellHeight;
result->mCellSize = value.mCellSize;
clone(value.mPolyMesh, result->mPolyMesh);
clone(value.mPolyMeshDetail, result->mPolyMeshDetail);
return result;
} }
Mesh makeMesh() Mesh makeMesh()

@ -0,0 +1,50 @@
#ifndef OPENMW_TEST_SUITE_DETOURNAVIGATOR_SETTINGS_H
#define OPENMW_TEST_SUITE_DETOURNAVIGATOR_SETTINGS_H
#include <components/detournavigator/settings.hpp>
#include <chrono>
#include <limits>
namespace DetourNavigator
{
namespace Tests
{
inline Settings makeSettings()
{
Settings result;
result.mEnableWriteRecastMeshToFile = false;
result.mEnableWriteNavMeshToFile = false;
result.mEnableRecastMeshFileNameRevision = false;
result.mEnableNavMeshFileNameRevision = false;
result.mRecast.mBorderSize = 16;
result.mRecast.mCellHeight = 0.2f;
result.mRecast.mCellSize = 0.2f;
result.mRecast.mDetailSampleDist = 6;
result.mRecast.mDetailSampleMaxError = 1;
result.mRecast.mMaxClimb = 34;
result.mRecast.mMaxSimplificationError = 1.3f;
result.mRecast.mMaxSlope = 49;
result.mRecast.mRecastScaleFactor = 0.017647058823529415f;
result.mRecast.mSwimHeightScale = 0.89999997615814208984375f;
result.mRecast.mMaxEdgeLen = 12;
result.mDetour.mMaxNavMeshQueryNodes = 2048;
result.mRecast.mMaxVertsPerPoly = 6;
result.mRecast.mRegionMergeArea = 400;
result.mRecast.mRegionMinArea = 64;
result.mRecast.mTileSize = 64;
result.mWaitUntilMinDistanceToPlayer = std::numeric_limits<int>::max();
result.mAsyncNavMeshUpdaterThreads = 1;
result.mMaxNavMeshTilesCacheSize = 1024 * 1024;
result.mDetour.mMaxPolygonPathSize = 1024;
result.mDetour.mMaxSmoothPathSize = 1024;
result.mDetour.mMaxPolys = 4096;
result.mMaxTilesNumber = 512;
result.mMinUpdateInterval = std::chrono::milliseconds(50);
result.mWriteToNavMeshDb = true;
return result;
}
}
}
#endif

@ -3,6 +3,9 @@
#include "makenavmesh.hpp" #include "makenavmesh.hpp"
#include "settings.hpp" #include "settings.hpp"
#include "version.hpp" #include "version.hpp"
#include "serialization.hpp"
#include "navmeshdbutils.hpp"
#include "dbrefgeometryobject.hpp"
#include <components/debug/debuglog.hpp> #include <components/debug/debuglog.hpp>
#include <components/misc/thread.hpp> #include <components/misc/thread.hpp>
@ -15,70 +18,102 @@
#include <algorithm> #include <algorithm>
#include <numeric> #include <numeric>
#include <set> #include <set>
#include <type_traits>
namespace namespace DetourNavigator
{ {
using DetourNavigator::ChangeType; namespace
using DetourNavigator::TilePosition;
using DetourNavigator::UpdateType;
using DetourNavigator::ChangeType;
using DetourNavigator::Job;
using DetourNavigator::JobIt;
int getManhattanDistance(const TilePosition& lhs, const TilePosition& rhs)
{ {
return std::abs(lhs.x() - rhs.x()) + std::abs(lhs.y() - rhs.y()); int getManhattanDistance(const TilePosition& lhs, const TilePosition& rhs)
} {
return std::abs(lhs.x() - rhs.x()) + std::abs(lhs.y() - rhs.y());
}
int getMinDistanceTo(const TilePosition& position, int maxDistance, int getMinDistanceTo(const TilePosition& position, int maxDistance,
const std::set<std::tuple<osg::Vec3f, TilePosition>>& pushedTiles, const std::set<std::tuple<osg::Vec3f, TilePosition>>& pushedTiles,
const std::set<std::tuple<osg::Vec3f, TilePosition>>& presentTiles) const std::set<std::tuple<osg::Vec3f, TilePosition>>& presentTiles)
{ {
int result = maxDistance; int result = maxDistance;
for (const auto& [halfExtents, tile] : pushedTiles) for (const auto& [halfExtents, tile] : pushedTiles)
if (presentTiles.find(std::tie(halfExtents, tile)) == presentTiles.end()) if (presentTiles.find(std::tie(halfExtents, tile)) == presentTiles.end())
result = std::min(result, getManhattanDistance(position, tile)); result = std::min(result, getManhattanDistance(position, tile));
return result; return result;
} }
UpdateType getUpdateType(ChangeType changeType) noexcept auto getPriority(const Job& job) noexcept
{ {
if (changeType == ChangeType::update) return std::make_tuple(-static_cast<std::underlying_type_t<JobState>>(job.mState), job.mProcessTime,
return UpdateType::Temporary; job.mChangeType, job.mTryNumber, job.mDistanceToPlayer, job.mDistanceToOrigin);
return UpdateType::Persistent; }
}
auto getPriority(const Job& job) noexcept struct LessByJobPriority
{ {
return std::make_tuple(job.mProcessTime, job.mChangeType, job.mTryNumber, job.mDistanceToPlayer, job.mDistanceToOrigin); bool operator()(JobIt lhs, JobIt rhs) const noexcept
} {
return getPriority(*lhs) < getPriority(*rhs);
}
};
struct LessByJobPriority void insertPrioritizedJob(JobIt job, std::deque<JobIt>& queue)
{
bool operator()(JobIt lhs, JobIt rhs) const noexcept
{ {
return getPriority(*lhs) < getPriority(*rhs); const auto it = std::upper_bound(queue.begin(), queue.end(), job, LessByJobPriority {});
queue.insert(it, job);
} }
};
void insertPrioritizedJob(JobIt job, std::deque<JobIt>& queue) auto getDbPriority(const Job& job) noexcept
{ {
const auto it = std::upper_bound(queue.begin(), queue.end(), job, LessByJobPriority {}); return std::make_tuple(static_cast<std::underlying_type_t<JobState>>(job.mState),
queue.insert(it, job); job.mChangeType, job.mDistanceToPlayer, job.mDistanceToOrigin);
} }
auto getAgentAndTile(const Job& job) noexcept struct LessByJobDbPriority
{ {
return std::make_tuple(job.mAgentHalfExtents, job.mChangedTile); 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.mAgentHalfExtents, job.mChangedTile);
}
std::unique_ptr<DbWorker> makeDbWorker(AsyncNavMeshUpdater& updater, std::unique_ptr<NavMeshDb>&& db, const Settings& settings)
{
if (db == nullptr)
return nullptr;
return std::make_unique<DbWorker>(updater, std::move(db), TileVersion(settings.mNavMeshVersion), settings.mRecast);
}
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};
return nextJobId.fetch_add(1);
}
} }
}
namespace DetourNavigator
{
Job::Job(const osg::Vec3f& agentHalfExtents, std::weak_ptr<GuardedNavMeshCacheItem> navMeshCacheItem, Job::Job(const osg::Vec3f& agentHalfExtents, 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, int distanceToPlayer,
std::chrono::steady_clock::time_point processTime) std::chrono::steady_clock::time_point processTime)
: mAgentHalfExtents(agentHalfExtents) : mId(getNextJobId())
, mAgentHalfExtents(agentHalfExtents)
, mNavMeshCacheItem(std::move(navMeshCacheItem)) , mNavMeshCacheItem(std::move(navMeshCacheItem))
, mWorldspace(worldspace) , mWorldspace(worldspace)
, mChangedTile(changedTile) , mChangedTile(changedTile)
@ -94,9 +129,9 @@ namespace DetourNavigator
: mSettings(settings) : mSettings(settings)
, mRecastMeshManager(recastMeshManager) , mRecastMeshManager(recastMeshManager)
, mOffMeshConnectionsManager(offMeshConnectionsManager) , mOffMeshConnectionsManager(offMeshConnectionsManager)
, mDb(std::move(db))
, mShouldStop() , mShouldStop()
, mNavMeshTilesCache(settings.mMaxNavMeshTilesCacheSize) , mNavMeshTilesCache(settings.mMaxNavMeshTilesCacheSize)
, mDbWorker(makeDbWorker(*this, std::move(db), mSettings))
{ {
for (std::size_t i = 0; i < mSettings.get().mAsyncNavMeshUpdaterThreads; ++i) for (std::size_t i = 0; i < mSettings.get().mAsyncNavMeshUpdaterThreads; ++i)
mThreads.emplace_back([&] { process(); }); mThreads.emplace_back([&] { process(); });
@ -105,6 +140,8 @@ namespace DetourNavigator
AsyncNavMeshUpdater::~AsyncNavMeshUpdater() AsyncNavMeshUpdater::~AsyncNavMeshUpdater()
{ {
mShouldStop = true; mShouldStop = true;
if (mDbWorker != nullptr)
mDbWorker->stop();
std::unique_lock<std::mutex> lock(mMutex); std::unique_lock<std::mutex> lock(mMutex);
mWaiting.clear(); mWaiting.clear();
mHasJob.notify_all(); mHasJob.notify_all();
@ -128,18 +165,12 @@ namespace DetourNavigator
return; return;
const dtNavMeshParams params = *navMeshCacheItem->lockConst()->getImpl().getParams(); const dtNavMeshParams params = *navMeshCacheItem->lockConst()->getImpl().getParams();
const int maxTiles = std::min(mSettings.get().mMaxTilesNumber, params.maxTiles);
const std::lock_guard<std::mutex> lock(mMutex); std::unique_lock lock(mMutex);
if (playerTileChanged) if (playerTileChanged)
{ updateJobs(mWaiting, playerTile, maxTiles);
for (JobIt job : mWaiting)
{
job->mDistanceToPlayer = getManhattanDistance(job->mChangedTile, playerTile);
if (!shouldAddTile(job->mChangedTile, playerTile, std::min(mSettings.get().mMaxTilesNumber, params.maxTiles)))
job->mChangeType = ChangeType::remove;
}
}
for (const auto& [changedTile, changeType] : changedTiles) for (const auto& [changedTile, changeType] : changedTiles)
{ {
@ -152,6 +183,9 @@ namespace DetourNavigator
const JobIt it = mJobs.emplace(mJobs.end(), agentHalfExtents, navMeshCacheItem, worldspace, const JobIt it = mJobs.emplace(mJobs.end(), agentHalfExtents, navMeshCacheItem, worldspace,
changedTile, changeType, getManhattanDistance(changedTile, playerTile), processTime); changedTile, changeType, getManhattanDistance(changedTile, playerTile), processTime);
Log(Debug::Debug) << "Post job " << it->mId << " for agent=(" << it->mAgentHalfExtents << ")"
<< " changedTile=(" << it->mChangedTile << ")";
if (playerTileChanged) if (playerTileChanged)
mWaiting.push_back(it); mWaiting.push_back(it);
else else
@ -166,6 +200,11 @@ namespace DetourNavigator
if (!mWaiting.empty()) if (!mWaiting.empty())
mHasJob.notify_all(); mHasJob.notify_all();
lock.unlock();
if (playerTileChanged && mDbWorker != nullptr)
mDbWorker->updateJobs(playerTile, maxTiles);
} }
void AsyncNavMeshUpdater::wait(Loading::Listener& listener, WaitConditionType waitConditionType) void AsyncNavMeshUpdater::wait(Loading::Listener& listener, WaitConditionType waitConditionType)
@ -243,25 +282,40 @@ namespace DetourNavigator
mProcessingTiles.wait(mProcessed, [] (const auto& v) { return v.empty(); }); mProcessingTiles.wait(mProcessed, [] (const auto& v) { return v.empty(); });
} }
void AsyncNavMeshUpdater::reportStats(unsigned int frameNumber, osg::Stats& stats) const AsyncNavMeshUpdater::Stats AsyncNavMeshUpdater::getStats() const
{ {
std::size_t jobs = 0; Stats result;
std::size_t waiting = 0;
std::size_t pushed = 0;
{ {
const std::lock_guard<std::mutex> lock(mMutex); const std::lock_guard<std::mutex> lock(mMutex);
jobs = mJobs.size(); result.mJobs = mJobs.size();
waiting = mWaiting.size(); result.mWaiting = mWaiting.size();
pushed = mPushed.size(); result.mPushed = mPushed.size();
} }
result.mProcessing = mProcessingTiles.lockConst()->size();
if (mDbWorker != nullptr)
result.mDb = mDbWorker->getStats();
result.mCache = mNavMeshTilesCache.getStats();
result.mDbGetTileHits = mDbGetTileHits.load(std::memory_order_relaxed);
return result;
}
stats.setAttribute(frameNumber, "NavMesh Jobs", jobs); void reportStats(const AsyncNavMeshUpdater::Stats& stats, unsigned int frameNumber, osg::Stats& out)
stats.setAttribute(frameNumber, "NavMesh Waiting", waiting); {
stats.setAttribute(frameNumber, "NavMesh Pushed", pushed); out.setAttribute(frameNumber, "NavMesh Jobs", static_cast<double>(stats.mJobs));
stats.setAttribute(frameNumber, "NavMesh Processing", mProcessingTiles.lockConst()->size()); out.setAttribute(frameNumber, "NavMesh Waiting", static_cast<double>(stats.mWaiting));
out.setAttribute(frameNumber, "NavMesh Pushed", static_cast<double>(stats.mPushed));
out.setAttribute(frameNumber, "NavMesh Processing", static_cast<double>(stats.mProcessing));
mNavMeshTilesCache.reportStats(frameNumber, stats); if (stats.mDb.has_value())
{
out.setAttribute(frameNumber, "NavMesh DbJobs", static_cast<double>(stats.mDb->mJobs));
if (stats.mDb->mGetTileCount > 0)
out.setAttribute(frameNumber, "NavMesh DbCacheHitRate", static_cast<double>(stats.mDbGetTileHits)
/ static_cast<double>(stats.mDb->mGetTileCount) * 100.0);
}
reportStats(stats.mCache, frameNumber, out);
} }
void AsyncNavMeshUpdater::process() noexcept void AsyncNavMeshUpdater::process() noexcept
@ -274,12 +328,26 @@ namespace DetourNavigator
{ {
if (JobIt job = getNextJob(); job != mJobs.end()) if (JobIt job = getNextJob(); job != mJobs.end())
{ {
const auto processed = processJob(*job); const JobStatus status = processJob(*job);
unlockTile(job->mAgentHalfExtents, job->mChangedTile); Log(Debug::Debug) << "Processed job " << job->mId << " with status=" << status;
if (processed) switch (status)
removeJob(job); {
else case JobStatus::Done:
repost(job); unlockTile(job->mAgentHalfExtents, job->mChangedTile);
if (job->mGeneratedNavMeshData != nullptr)
mDbWorker->enqueueJob(job);
else
removeJob(job);
break;
case JobStatus::Fail:
repost(job);
break;
case JobStatus::MemoryCacheMiss:
{
mDbWorker->enqueueJob(job);
break;
}
}
} }
else else
cleanupLastUpdates(); cleanupLastUpdates();
@ -292,34 +360,156 @@ namespace DetourNavigator
Log(Debug::Debug) << "Stop navigator jobs processing by thread=" << std::this_thread::get_id(); Log(Debug::Debug) << "Stop navigator jobs processing by thread=" << std::this_thread::get_id();
} }
bool AsyncNavMeshUpdater::processJob(const Job& job) JobStatus AsyncNavMeshUpdater::processJob(Job& job)
{ {
Log(Debug::Debug) << "Process job for agent=(" << std::fixed << std::setprecision(2) << job.mAgentHalfExtents << ")" Log(Debug::Debug) << "Processing job " << job.mId << " by thread=" << std::this_thread::get_id();
" by thread=" << std::this_thread::get_id();
const auto start = std::chrono::steady_clock::now();
const auto navMeshCacheItem = job.mNavMeshCacheItem.lock(); const auto navMeshCacheItem = job.mNavMeshCacheItem.lock();
if (!navMeshCacheItem) if (!navMeshCacheItem)
return true; return JobStatus::Done;
const auto recastMesh = mRecastMeshManager.get().getMesh(job.mWorldspace, job.mChangedTile);
const auto playerTile = *mPlayerTile.lockConst(); const auto playerTile = *mPlayerTile.lockConst();
const auto params = *navMeshCacheItem->lockConst()->getImpl().getParams();
if (!shouldAddTile(job.mChangedTile, playerTile, std::min(mSettings.get().mMaxTilesNumber, params.maxTiles)))
{
Log(Debug::Debug) << "Ignore add tile by job " << job.mId << ": too far from player";
navMeshCacheItem->lock()->removeTile(job.mChangedTile);
return JobStatus::Done;
}
switch (job.mState)
{
case JobState::Initial:
return processInitialJob(job, *navMeshCacheItem);
case JobState::WithDbResult:
return processJobWithDbResult(job, *navMeshCacheItem);
}
return JobStatus::Done;
}
JobStatus AsyncNavMeshUpdater::processInitialJob(Job& job, GuardedNavMeshCacheItem& navMeshCacheItem)
{
Log(Debug::Debug) << "Processing initial job " << job.mId;
std::shared_ptr<RecastMesh> recastMesh = mRecastMeshManager.get().getMesh(job.mWorldspace, job.mChangedTile);
if (recastMesh == nullptr)
{
Log(Debug::Debug) << "Null recast mesh for job " << job.mId;
navMeshCacheItem.lock()->markAsEmpty(job.mChangedTile);
return JobStatus::Done;
}
if (isEmpty(*recastMesh))
{
Log(Debug::Debug) << "Empty bounds for job " << job.mId;
navMeshCacheItem.lock()->markAsEmpty(job.mChangedTile);
return JobStatus::Done;
}
NavMeshTilesCache::Value cachedNavMeshData = mNavMeshTilesCache.get(job.mAgentHalfExtents, job.mChangedTile, *recastMesh);
std::unique_ptr<PreparedNavMeshData> preparedNavMeshData;
const PreparedNavMeshData* preparedNavMeshDataPtr = nullptr;
if (cachedNavMeshData)
{
preparedNavMeshDataPtr = &cachedNavMeshData.get();
}
else
{
if (job.mChangeType != ChangeType::update && mDbWorker != nullptr)
{
job.mRecastMesh = std::move(recastMesh);
return JobStatus::MemoryCacheMiss;
}
preparedNavMeshData = prepareNavMeshTileData(*recastMesh, job.mChangedTile, job.mAgentHalfExtents, mSettings.get().mRecast);
if (preparedNavMeshData == nullptr)
{
Log(Debug::Debug) << "Null navmesh data for job " << job.mId;
navMeshCacheItem.lock()->markAsEmpty(job.mChangedTile);
return JobStatus::Done;
}
if (job.mChangeType == ChangeType::update)
{
preparedNavMeshDataPtr = preparedNavMeshData.get();
}
else
{
cachedNavMeshData = mNavMeshTilesCache.set(job.mAgentHalfExtents, job.mChangedTile,
*recastMesh, std::move(preparedNavMeshData));
preparedNavMeshDataPtr = cachedNavMeshData ? &cachedNavMeshData.get() : preparedNavMeshData.get();
}
}
const auto offMeshConnections = mOffMeshConnectionsManager.get().get(job.mChangedTile); const auto offMeshConnections = mOffMeshConnectionsManager.get().get(job.mChangedTile);
const auto status = updateNavMesh(job.mAgentHalfExtents, recastMesh.get(), job.mWorldspace, job.mChangedTile, const UpdateNavMeshStatus status = navMeshCacheItem.lock()->updateTile(job.mChangedTile, std::move(cachedNavMeshData),
playerTile, offMeshConnections, mSettings, navMeshCacheItem, mNavMeshTilesCache, makeNavMeshTileData(*preparedNavMeshDataPtr, offMeshConnections, job.mAgentHalfExtents, job.mChangedTile, mSettings.get().mRecast));
getUpdateType(job.mChangeType), mDb, mNextShapeId);
return handleUpdateNavMeshStatus(status, job, navMeshCacheItem, *recastMesh);
}
JobStatus AsyncNavMeshUpdater::processJobWithDbResult(Job& job, GuardedNavMeshCacheItem& navMeshCacheItem)
{
Log(Debug::Debug) << "Processing job with db result " << job.mId;
std::unique_ptr<PreparedNavMeshData> preparedNavMeshData;
bool generatedNavMeshData = false;
if (job.mCachedTileData.has_value() && job.mCachedTileData->mVersion == mSettings.get().mNavMeshVersion)
{
preparedNavMeshData = std::make_unique<PreparedNavMeshData>();
if (deserialize(job.mCachedTileData->mData, *preparedNavMeshData))
++mDbGetTileHits;
else
preparedNavMeshData = nullptr;
}
if (preparedNavMeshData == nullptr)
{
preparedNavMeshData = prepareNavMeshTileData(*job.mRecastMesh, job.mChangedTile, job.mAgentHalfExtents, mSettings.get().mRecast);
generatedNavMeshData = true;
}
if (recastMesh != nullptr) if (preparedNavMeshData == nullptr)
{ {
const Version navMeshVersion = navMeshCacheItem->lockConst()->getVersion(); Log(Debug::Debug) << "Null navmesh data for job " << job.mId;
mRecastMeshManager.get().reportNavMeshChange(job.mChangedTile, navMeshCacheItem.lock()->markAsEmpty(job.mChangedTile);
Version {recastMesh->getGeneration(), recastMesh->getRevision()}, return JobStatus::Done;
navMeshVersion);
} }
auto cachedNavMeshData = mNavMeshTilesCache.set(job.mAgentHalfExtents, job.mChangedTile, *job.mRecastMesh,
std::move(preparedNavMeshData));
const auto offMeshConnections = mOffMeshConnectionsManager.get().get(job.mChangedTile);
const PreparedNavMeshData* preparedNavMeshDataPtr = cachedNavMeshData ? &cachedNavMeshData.get() : preparedNavMeshData.get();
const UpdateNavMeshStatus status = navMeshCacheItem.lock()->updateTile(job.mChangedTile, std::move(cachedNavMeshData),
makeNavMeshTileData(*preparedNavMeshDataPtr, offMeshConnections, job.mAgentHalfExtents, job.mChangedTile, mSettings.get().mRecast));
const JobStatus result = handleUpdateNavMeshStatus(status, job, navMeshCacheItem, *job.mRecastMesh);
if (result == JobStatus::Done && job.mChangeType != ChangeType::update
&& mDbWorker != nullptr && mSettings.get().mWriteToNavMeshDb && generatedNavMeshData)
job.mGeneratedNavMeshData = std::make_unique<PreparedNavMeshData>(*preparedNavMeshDataPtr);
return result;
}
JobStatus AsyncNavMeshUpdater::handleUpdateNavMeshStatus(UpdateNavMeshStatus status,
const Job& job, const GuardedNavMeshCacheItem& navMeshCacheItem, const RecastMesh& recastMesh)
{
const Version navMeshVersion = navMeshCacheItem.lockConst()->getVersion();
mRecastMeshManager.get().reportNavMeshChange(job.mChangedTile,
Version {recastMesh.getGeneration(), recastMesh.getRevision()},
navMeshVersion);
if (status == UpdateNavMeshStatus::removed || status == UpdateNavMeshStatus::lost) if (status == UpdateNavMeshStatus::removed || status == UpdateNavMeshStatus::lost)
{ {
const std::scoped_lock lock(mMutex); const std::scoped_lock lock(mMutex);
@ -331,23 +521,9 @@ namespace DetourNavigator
mPresentTiles.insert(std::make_tuple(job.mAgentHalfExtents, job.mChangedTile)); mPresentTiles.insert(std::make_tuple(job.mAgentHalfExtents, job.mChangedTile));
} }
const auto finish = std::chrono::steady_clock::now(); writeDebugFiles(job, &recastMesh);
writeDebugFiles(job, recastMesh.get());
using FloatMs = std::chrono::duration<float, std::milli>;
const Version version = navMeshCacheItem->lockConst()->getVersion(); return isSuccess(status) ? JobStatus::Done : JobStatus::Fail;
Log(Debug::Debug) << std::fixed << std::setprecision(2) <<
"Cache updated for agent=(" << job.mAgentHalfExtents << ")" <<
" tile=" << job.mChangedTile <<
" status=" << status <<
" generation=" << version.mGeneration <<
" revision=" << version.mRevision <<
" time=" << std::chrono::duration_cast<FloatMs>(finish - start).count() << "ms" <<
" thread=" << std::this_thread::get_id();
return isSuccess(status);
} }
JobIt AsyncNavMeshUpdater::getNextJob() JobIt AsyncNavMeshUpdater::getNextJob()
@ -376,8 +552,12 @@ namespace DetourNavigator
mWaiting.pop_front(); mWaiting.pop_front();
if (job->mRecastMesh != nullptr)
return job;
if (!lockTile(job->mAgentHalfExtents, job->mChangedTile)) if (!lockTile(job->mAgentHalfExtents, job->mChangedTile))
{ {
Log(Debug::Debug) << "Failed to lock tile by " << job->mId;
++job->mTryNumber; ++job->mTryNumber;
insertPrioritizedJob(job, mWaiting); insertPrioritizedJob(job, mWaiting);
return mJobs.end(); return mJobs.end();
@ -415,6 +595,8 @@ namespace DetourNavigator
void AsyncNavMeshUpdater::repost(JobIt job) void AsyncNavMeshUpdater::repost(JobIt job)
{ {
unlockTile(job->mAgentHalfExtents, job->mChangedTile);
if (mShouldStop || job->mTryNumber > 2) if (mShouldStop || job->mTryNumber > 2)
return; return;
@ -433,17 +615,15 @@ namespace DetourNavigator
bool AsyncNavMeshUpdater::lockTile(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile) bool AsyncNavMeshUpdater::lockTile(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile)
{ {
if (mSettings.get().mAsyncNavMeshUpdaterThreads <= 1) Log(Debug::Debug) << "Locking tile agent=(" << agentHalfExtents << ") changedTile=(" << changedTile << ")";
return true;
return mProcessingTiles.lock()->emplace(agentHalfExtents, changedTile).second; return mProcessingTiles.lock()->emplace(agentHalfExtents, changedTile).second;
} }
void AsyncNavMeshUpdater::unlockTile(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile) void AsyncNavMeshUpdater::unlockTile(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile)
{ {
if (mSettings.get().mAsyncNavMeshUpdaterThreads <= 1)
return;
auto locked = mProcessingTiles.lock(); auto locked = mProcessingTiles.lock();
locked->erase(std::tie(agentHalfExtents, changedTile)); locked->erase(std::tie(agentHalfExtents, changedTile));
Log(Debug::Debug) << "Unlocked tile agent=(" << agentHalfExtents << ") changedTile=(" << changedTile << ")";
if (locked->empty()) if (locked->empty())
mProcessed.notify_all(); mProcessed.notify_all();
} }
@ -469,9 +649,201 @@ namespace DetourNavigator
} }
} }
void AsyncNavMeshUpdater::enqueueJob(JobIt job)
{
Log(Debug::Debug) << "Enqueueing job " << job->mId << " by thread=" << std::this_thread::get_id();
const std::lock_guard lock(mMutex);
insertPrioritizedJob(job, mWaiting);
mHasJob.notify_all();
}
void AsyncNavMeshUpdater::removeJob(JobIt job) void AsyncNavMeshUpdater::removeJob(JobIt job)
{ {
Log(Debug::Debug) << "Removing job " << job->mId << " by thread=" << std::this_thread::get_id();
const std::lock_guard lock(mMutex); const std::lock_guard lock(mMutex);
mJobs.erase(job); mJobs.erase(job);
} }
void DbJobQueue::push(JobIt job)
{
const std::lock_guard lock(mMutex);
insertPrioritizedDbJob(job, mJobs);
mHasJob.notify_all();
}
std::optional<JobIt> DbJobQueue::pop()
{
std::unique_lock lock(mMutex);
mHasJob.wait(lock, [&] { return mShouldStop || !mJobs.empty(); });
if (mJobs.empty())
return std::nullopt;
const JobIt job = mJobs.front();
mJobs.pop_front();
return job;
}
void DbJobQueue::update(TilePosition playerTile, int maxTiles)
{
const std::lock_guard lock(mMutex);
updateJobs(mJobs, playerTile, maxTiles);
std::sort(mJobs.begin(), mJobs.end(), LessByJobDbPriority {});
}
void DbJobQueue::stop()
{
const std::lock_guard lock(mMutex);
mJobs.clear();
mShouldStop = true;
mHasJob.notify_all();
}
std::size_t DbJobQueue::size() const
{
const std::lock_guard lock(mMutex);
return mJobs.size();
}
DbWorker::DbWorker(AsyncNavMeshUpdater& updater, std::unique_ptr<NavMeshDb>&& db,
TileVersion version, const RecastSettings& recastSettings)
: mUpdater(updater)
, mRecastSettings(recastSettings)
, mDb(std::move(db))
, mVersion(version)
, mNextTileId(mDb->getMaxTileId() + 1)
, mNextShapeId(mDb->getMaxShapeId() + 1)
, mThread([this] { run(); })
{
}
DbWorker::~DbWorker()
{
stop();
mThread.join();
}
void DbWorker::enqueueJob(JobIt job)
{
Log(Debug::Debug) << "Enqueueing db job " << job->mId << " by thread=" << std::this_thread::get_id();
mQueue.push(job);
}
DbWorker::Stats DbWorker::getStats() const
{
Stats result;
result.mJobs = mQueue.size();
result.mGetTileCount = mGetTileCount.load(std::memory_order::memory_order_relaxed);
return result;
}
void DbWorker::stop()
{
mShouldStop = true;
mQueue.stop();
}
void DbWorker::run() noexcept
{
constexpr std::size_t writesPerTransaction = 100;
auto transaction = mDb->startTransaction();
while (!mShouldStop)
{
try
{
if (const auto job = mQueue.pop())
processJob(*job);
if (mWrites > writesPerTransaction)
{
mWrites = 0;
transaction.commit();
transaction = mDb->startTransaction();
}
}
catch (const std::exception& e)
{
Log(Debug::Error) << "DbWorker exception: " << e.what();
}
}
transaction.commit();
}
void DbWorker::processJob(JobIt job)
{
const auto process = [&] (auto f)
{
try
{
f(job);
}
catch (const std::exception& e)
{
Log(Debug::Error) << "DbWorker exception while processing job " << job->mId << ": " << e.what();
}
};
if (job->mGeneratedNavMeshData != nullptr)
{
process([&] (JobIt job) { processWritingJob(job); });
mUpdater.removeJob(job);
return;
}
process([&] (JobIt job) { processReadingJob(job); });
job->mState = JobState::WithDbResult;
mUpdater.enqueueJob(job);
}
void DbWorker::processReadingJob(JobIt job)
{
Log(Debug::Debug) << "Processing db read job " << job->mId;
if (job->mInput.empty())
{
Log(Debug::Debug) << "Serializing input for job " << job->mId;
const ShapeId shapeId = mNextShapeId;
const std::vector<DbRefGeometryObject> objects = makeDbRefGeometryObjects(job->mRecastMesh->getMeshSources(),
[&] (const MeshSource& v) { return resolveMeshSource(*mDb, v, mNextShapeId); });
if (shapeId != mNextShapeId)
++mWrites;
job->mInput = serialize(mRecastSettings, *job->mRecastMesh, objects);
}
job->mCachedTileData = mDb->getTileData(job->mWorldspace, job->mChangedTile, job->mInput);
++mGetTileCount;
}
void DbWorker::processWritingJob(JobIt job)
{
++mWrites;
Log(Debug::Debug) << "Processing db write job " << job->mId;
if (job->mInput.empty())
{
Log(Debug::Debug) << "Serializing input for job " << job->mId;
const std::vector<DbRefGeometryObject> objects = makeDbRefGeometryObjects(job->mRecastMesh->getMeshSources(),
[&] (const MeshSource& v) { return resolveMeshSource(*mDb, v, mNextShapeId); });
job->mInput = serialize(mRecastSettings, *job->mRecastMesh, objects);
}
if (const auto& cachedTileData = job->mCachedTileData)
{
Log(Debug::Debug) << "Update db tile by job " << job->mId;
job->mGeneratedNavMeshData->mUserId = cachedTileData->mTileId;
mDb->updateTile(cachedTileData->mTileId, mVersion, serialize(*job->mGeneratedNavMeshData));
return;
}
const auto cached = mDb->findTile(job->mWorldspace, job->mChangedTile, job->mInput);
if (cached.has_value() && cached->mVersion == mVersion)
{
Log(Debug::Debug) << "Ignore existing db tile by job " << job->mId;
return;
}
job->mGeneratedNavMeshData->mUserId = mNextTileId;
Log(Debug::Debug) << "Insert db tile by job " << job->mId;
mDb->insertTile(mNextTileId, job->mWorldspace, job->mChangedTile,
mVersion, job->mInput, serialize(*job->mGeneratedNavMeshData));
++mNextTileId.t;
}
} }

@ -21,6 +21,7 @@
#include <thread> #include <thread>
#include <tuple> #include <tuple>
#include <list> #include <list>
#include <optional>
class dtNavMesh; class dtNavMesh;
@ -54,8 +55,15 @@ namespace DetourNavigator
return stream << "ChangeType::" << static_cast<int>(value); return stream << "ChangeType::" << static_cast<int>(value);
} }
enum class JobState
{
Initial,
WithDbResult,
};
struct Job struct Job
{ {
const std::size_t mId;
const osg::Vec3f mAgentHalfExtents; const osg::Vec3f mAgentHalfExtents;
const std::weak_ptr<GuardedNavMeshCacheItem> mNavMeshCacheItem; const std::weak_ptr<GuardedNavMeshCacheItem> mNavMeshCacheItem;
const std::string mWorldspace; const std::string mWorldspace;
@ -65,6 +73,11 @@ namespace DetourNavigator
ChangeType mChangeType; ChangeType mChangeType;
int mDistanceToPlayer; int mDistanceToPlayer;
const int mDistanceToOrigin; const int mDistanceToOrigin;
JobState mState = JobState::Initial;
std::vector<std::byte> mInput;
std::shared_ptr<RecastMesh> mRecastMesh;
std::optional<TileData> mCachedTileData;
std::unique_ptr<PreparedNavMeshData> mGeneratedNavMeshData;
Job(const osg::Vec3f& agentHalfExtents, std::weak_ptr<GuardedNavMeshCacheItem> navMeshCacheItem, Job(const osg::Vec3f& agentHalfExtents, 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, int distanceToPlayer,
@ -73,27 +86,124 @@ namespace DetourNavigator
using JobIt = std::list<Job>::iterator; using JobIt = std::list<Job>::iterator;
enum class JobStatus
{
Done,
Fail,
MemoryCacheMiss,
};
inline std::ostream& operator<<(std::ostream& stream, JobStatus value)
{
switch (value)
{
case JobStatus::Done: return stream << "JobStatus::Done";
case JobStatus::Fail: return stream << "JobStatus::Fail";
case JobStatus::MemoryCacheMiss: return stream << "JobStatus::MemoryCacheMiss";
}
return stream << "JobStatus::" << static_cast<std::underlying_type_t<JobState>>(value);
}
class DbJobQueue
{
public:
void push(JobIt job);
std::optional<JobIt> pop();
void update(TilePosition playerTile, int maxTiles);
void stop();
std::size_t size() const;
private:
mutable std::mutex mMutex;
std::condition_variable mHasJob;
std::deque<JobIt> mJobs;
bool mShouldStop = false;
};
class AsyncNavMeshUpdater;
class DbWorker
{
public:
struct Stats
{
std::size_t mJobs = 0;
std::size_t mGetTileCount = 0;
};
DbWorker(AsyncNavMeshUpdater& updater, std::unique_ptr<NavMeshDb>&& db,
TileVersion version, const RecastSettings& recastSettings);
~DbWorker();
Stats getStats() const;
void enqueueJob(JobIt job);
void updateJobs(TilePosition playerTile, int maxTiles) { mQueue.update(playerTile, maxTiles); }
void stop();
private:
AsyncNavMeshUpdater& mUpdater;
const RecastSettings& mRecastSettings;
const std::unique_ptr<NavMeshDb> mDb;
const TileVersion mVersion;
TileId mNextTileId;
ShapeId mNextShapeId;
DbJobQueue mQueue;
std::atomic_bool mShouldStop {false};
std::atomic_size_t mGetTileCount {0};
std::size_t mWrites = 0;
std::thread mThread;
inline void run() noexcept;
inline void processJob(JobIt job);
inline void processReadingJob(JobIt job);
inline void processWritingJob(JobIt job);
};
class AsyncNavMeshUpdater class AsyncNavMeshUpdater
{ {
public: public:
struct Stats
{
std::size_t mJobs = 0;
std::size_t mWaiting = 0;
std::size_t mPushed = 0;
std::size_t mProcessing = 0;
std::size_t mDbGetTileHits = 0;
std::optional<DbWorker::Stats> mDb;
NavMeshTilesCache::Stats mCache;
};
AsyncNavMeshUpdater(const Settings& settings, TileCachedRecastMeshManager& recastMeshManager, AsyncNavMeshUpdater(const Settings& settings, TileCachedRecastMeshManager& recastMeshManager,
OffMeshConnectionsManager& offMeshConnectionsManager, std::unique_ptr<NavMeshDb>&& db); OffMeshConnectionsManager& offMeshConnectionsManager, std::unique_ptr<NavMeshDb>&& db);
~AsyncNavMeshUpdater(); ~AsyncNavMeshUpdater();
void post(const osg::Vec3f& agentHalfExtents, const SharedNavMeshCacheItem& mNavMeshCacheItem, void post(const osg::Vec3f& agentHalfExtents, const SharedNavMeshCacheItem& navMeshCacheItem,
const TilePosition& playerTile, std::string_view worldspace, const TilePosition& playerTile, std::string_view worldspace,
const std::map<TilePosition, ChangeType>& changedTiles); const std::map<TilePosition, ChangeType>& changedTiles);
void wait(Loading::Listener& listener, WaitConditionType waitConditionType); void wait(Loading::Listener& listener, WaitConditionType waitConditionType);
void reportStats(unsigned int frameNumber, osg::Stats& stats) const; Stats getStats() const;
void enqueueJob(JobIt job);
void removeJob(JobIt job);
private: private:
std::reference_wrapper<const Settings> mSettings; std::reference_wrapper<const Settings> mSettings;
std::reference_wrapper<TileCachedRecastMeshManager> mRecastMeshManager; std::reference_wrapper<TileCachedRecastMeshManager> mRecastMeshManager;
std::reference_wrapper<OffMeshConnectionsManager> mOffMeshConnectionsManager; std::reference_wrapper<OffMeshConnectionsManager> mOffMeshConnectionsManager;
Misc::ScopeGuarded<std::unique_ptr<NavMeshDb>> mDb;
ShapeId mNextShapeId {1};
std::atomic_bool mShouldStop; std::atomic_bool mShouldStop;
mutable std::mutex mMutex; mutable std::mutex mMutex;
std::condition_variable mHasJob; std::condition_variable mHasJob;
@ -108,14 +218,21 @@ namespace DetourNavigator
std::map<std::tuple<osg::Vec3f, TilePosition>, std::chrono::steady_clock::time_point> mLastUpdates; std::map<std::tuple<osg::Vec3f, TilePosition>, std::chrono::steady_clock::time_point> mLastUpdates;
std::set<std::tuple<osg::Vec3f, TilePosition>> mPresentTiles; std::set<std::tuple<osg::Vec3f, TilePosition>> mPresentTiles;
std::vector<std::thread> mThreads; std::vector<std::thread> mThreads;
std::unique_ptr<DbWorker> mDbWorker;
std::atomic_size_t mDbGetTileHits {0};
void process() noexcept; void process() noexcept;
bool processJob(const Job& job); JobStatus processJob(Job& job);
JobIt getNextJob(); inline JobStatus processInitialJob(Job& job, GuardedNavMeshCacheItem& navMeshCacheItem);
inline JobStatus processJobWithDbResult(Job& job, GuardedNavMeshCacheItem& navMeshCacheItem);
inline JobStatus handleUpdateNavMeshStatus(UpdateNavMeshStatus status, const Job& job,
const GuardedNavMeshCacheItem& navMeshCacheItem, const RecastMesh& recastMesh);
JobIt getJob(std::deque<JobIt>& jobs, bool changeLastUpdate); JobIt getNextJob();
void postThreadJob(JobIt job, std::deque<JobIt>& queue); void postThreadJob(JobIt job, std::deque<JobIt>& queue);
@ -134,9 +251,9 @@ namespace DetourNavigator
int waitUntilJobsDoneForNotPresentTiles(const std::size_t initialJobsLeft, std::size_t& maxJobsLeft, Loading::Listener& listener); int waitUntilJobsDoneForNotPresentTiles(const std::size_t initialJobsLeft, std::size_t& maxJobsLeft, Loading::Listener& listener);
void waitUntilAllJobsDone(); void waitUntilAllJobsDone();
inline void removeJob(JobIt job);
}; };
void reportStats(const AsyncNavMeshUpdater::Stats& stats, unsigned int frameNumber, osg::Stats& out);
} }
#endif #endif

@ -544,89 +544,4 @@ namespace DetourNavigator
return navMesh; return navMesh;
} }
UpdateNavMeshStatus updateNavMesh(const osg::Vec3f& agentHalfExtents, const RecastMesh* recastMesh,
const std::string& worldspace, const TilePosition& changedTile, const TilePosition& playerTile,
const std::vector<OffMeshConnection>& offMeshConnections, const Settings& settings,
const SharedNavMeshCacheItem& navMeshCacheItem, NavMeshTilesCache& navMeshTilesCache, UpdateType updateType,
Misc::ScopeGuarded<std::unique_ptr<NavMeshDb>>& db, ShapeId& nextShapeId)
{
Log(Debug::Debug) << std::fixed << std::setprecision(2) <<
"Update NavMesh with multiple tiles:" <<
" agentHeight=" << getHeight(settings.mRecast, agentHalfExtents) <<
" agentMaxClimb=" << getMaxClimb(settings.mRecast) <<
" agentRadius=" << getRadius(settings.mRecast, agentHalfExtents) <<
" changedTile=(" << changedTile << ")" <<
" playerTile=(" << playerTile << ")" <<
" changedTileDistance=" << getDistance(changedTile, playerTile);
if (!recastMesh)
{
Log(Debug::Debug) << "Ignore add tile: recastMesh is null";
return navMeshCacheItem->lock()->removeTile(changedTile);
}
if (isEmpty(*recastMesh))
{
Log(Debug::Debug) << "Ignore add tile: recastMesh is empty";
return navMeshCacheItem->lock()->removeTile(changedTile);
}
const dtNavMeshParams params = *navMeshCacheItem->lockConst()->getImpl().getParams();
if (!shouldAddTile(changedTile, playerTile, std::min(settings.mMaxTilesNumber, params.maxTiles)))
{
Log(Debug::Debug) << "Ignore add tile: too far from player";
return navMeshCacheItem->lock()->removeTile(changedTile);
}
auto cachedNavMeshData = navMeshTilesCache.get(agentHalfExtents, changedTile, *recastMesh);
bool cached = static_cast<bool>(cachedNavMeshData);
if (!cachedNavMeshData)
{
std::optional<TileData> stored;
if (const auto dbLocked = db.lock(); *dbLocked != nullptr)
{
const std::vector<DbRefGeometryObject> objects = makeDbRefGeometryObjects(recastMesh->getMeshSources(),
[&] (const MeshSource& v) { return resolveMeshSource(**dbLocked, v, nextShapeId); });
stored = (*dbLocked)->getTileData(worldspace, changedTile, serialize(settings.mRecast, *recastMesh, objects));
}
std::unique_ptr<PreparedNavMeshData> prepared;
if (stored.has_value() && stored->mVersion == settings.mNavMeshVersion)
{
prepared = std::make_unique<PreparedNavMeshData>();
if (!deserialize(stored->mData, *prepared))
prepared = nullptr;
}
if (prepared == nullptr)
prepared = prepareNavMeshTileData(*recastMesh, changedTile, agentHalfExtents, settings.mRecast);
if (prepared == nullptr)
{
Log(Debug::Debug) << "Ignore add tile: NavMeshData is null";
return navMeshCacheItem->lock()->removeTile(changedTile);
}
if (updateType == UpdateType::Temporary)
return navMeshCacheItem->lock()->updateTile(changedTile, NavMeshTilesCache::Value(),
makeNavMeshTileData(*prepared, offMeshConnections, agentHalfExtents, changedTile, settings.mRecast));
cachedNavMeshData = navMeshTilesCache.set(agentHalfExtents, changedTile, *recastMesh, std::move(prepared));
if (!cachedNavMeshData)
{
Log(Debug::Debug) << "Navigator cache overflow";
return navMeshCacheItem->lock()->updateTile(changedTile, NavMeshTilesCache::Value(),
makeNavMeshTileData(*prepared, offMeshConnections, agentHalfExtents, changedTile, settings.mRecast));
}
}
const auto updateStatus = navMeshCacheItem->lock()->updateTile(changedTile, std::move(cachedNavMeshData),
makeNavMeshTileData(cachedNavMeshData.get(), offMeshConnections, agentHalfExtents, changedTile, settings.mRecast));
return UpdateNavMeshStatusBuilder(updateStatus).cached(cached).getResult();
}
} }

@ -58,18 +58,6 @@ namespace DetourNavigator
const TilePosition& tile, const RecastSettings& settings); const TilePosition& tile, const RecastSettings& settings);
NavMeshPtr makeEmptyNavMesh(const Settings& settings); NavMeshPtr makeEmptyNavMesh(const Settings& settings);
enum class UpdateType
{
Persistent,
Temporary
};
UpdateNavMeshStatus updateNavMesh(const osg::Vec3f& agentHalfExtents, const RecastMesh* recastMesh,
const std::string& worldspace, const TilePosition& changedTile, const TilePosition& playerTile,
const std::vector<OffMeshConnection>& offMeshConnections, const Settings& settings,
const SharedNavMeshCacheItem& navMeshCacheItem, NavMeshTilesCache& navMeshTilesCache, UpdateType updateType,
Misc::ScopeGuarded<std::unique_ptr<NavMeshDb>>& db, ShapeId& nextShapeId);
} }
#endif #endif

@ -50,7 +50,8 @@ namespace DetourNavigator
{ {
return UpdateNavMeshStatus::ignored; return UpdateNavMeshStatus::ignored;
} }
const auto removed = ::removeTile(*mImpl, position); bool removed = ::removeTile(*mImpl, position);
removed = mEmptyTiles.erase(position) > 0 || removed;
const auto addStatus = addTile(*mImpl, navMeshData.mValue.get(), navMeshData.mSize); const auto addStatus = addTile(*mImpl, navMeshData.mValue.get(), navMeshData.mSize);
if (dtStatusSucceed(addStatus)) if (dtStatusSucceed(addStatus))
{ {
@ -82,7 +83,8 @@ namespace DetourNavigator
UpdateNavMeshStatus NavMeshCacheItem::removeTile(const TilePosition& position) UpdateNavMeshStatus NavMeshCacheItem::removeTile(const TilePosition& position)
{ {
const auto removed = ::removeTile(*mImpl, position); bool removed = ::removeTile(*mImpl, position);
removed = mEmptyTiles.erase(position) > 0 || removed;
if (removed) if (removed)
{ {
mUsedTiles.erase(position); mUsedTiles.erase(position);
@ -90,4 +92,21 @@ namespace DetourNavigator
} }
return UpdateNavMeshStatusBuilder().removed(removed).getResult(); return UpdateNavMeshStatusBuilder().removed(removed).getResult();
} }
UpdateNavMeshStatus NavMeshCacheItem::markAsEmpty(const TilePosition& position)
{
bool removed = ::removeTile(*mImpl, position);
removed = mEmptyTiles.insert(position).second || removed;
if (removed)
{
mUsedTiles.erase(position);
++mVersion.mRevision;
}
return UpdateNavMeshStatusBuilder().removed(removed).getResult();
}
bool NavMeshCacheItem::isEmptyTile(const TilePosition& position) const
{
return mEmptyTiles.find(position) != mEmptyTiles.end();
}
} }

@ -12,6 +12,7 @@
#include <map> #include <map>
#include <ostream> #include <ostream>
#include <set>
struct dtMeshTile; struct dtMeshTile;
@ -147,6 +148,10 @@ namespace DetourNavigator
UpdateNavMeshStatus removeTile(const TilePosition& position); UpdateNavMeshStatus removeTile(const TilePosition& position);
UpdateNavMeshStatus markAsEmpty(const TilePosition& position);
bool isEmptyTile(const TilePosition& position) const;
template <class Function> template <class Function>
void forEachUsedTile(Function&& function) const void forEachUsedTile(Function&& function) const
{ {
@ -166,6 +171,7 @@ namespace DetourNavigator
NavMeshPtr mImpl; NavMeshPtr mImpl;
Version mVersion; Version mVersion;
std::map<TilePosition, Tile> mUsedTiles; std::map<TilePosition, Tile> mUsedTiles;
std::set<TilePosition> mEmptyTiles;
}; };
using GuardedNavMeshCacheItem = Misc::ScopeGuarded<NavMeshCacheItem>; using GuardedNavMeshCacheItem = Misc::ScopeGuarded<NavMeshCacheItem>;

@ -211,7 +211,7 @@ namespace DetourNavigator
const auto shouldAdd = shouldAddTile(tile, playerTile, maxTiles); const auto shouldAdd = shouldAddTile(tile, playerTile, maxTiles);
const auto presentInNavMesh = bool(navMesh.getTileAt(tile.x(), tile.y(), 0)); const auto presentInNavMesh = bool(navMesh.getTileAt(tile.x(), tile.y(), 0));
if (shouldAdd && !presentInNavMesh) if (shouldAdd && !presentInNavMesh)
tilesToPost.insert(std::make_pair(tile, ChangeType::add)); tilesToPost.insert(std::make_pair(tile, locked->isEmptyTile(tile) ? ChangeType::update : ChangeType::add));
else if (!shouldAdd && presentInNavMesh) else if (!shouldAdd && presentInNavMesh)
tilesToPost.insert(std::make_pair(tile, ChangeType::mixed)); tilesToPost.insert(std::make_pair(tile, ChangeType::mixed));
else else
@ -243,7 +243,7 @@ namespace DetourNavigator
void NavMeshManager::reportStats(unsigned int frameNumber, osg::Stats& stats) const void NavMeshManager::reportStats(unsigned int frameNumber, osg::Stats& stats) const
{ {
mAsyncNavMeshUpdater.reportStats(frameNumber, stats); DetourNavigator::reportStats(mAsyncNavMeshUpdater.getStats(), frameNumber, stats);
} }
RecastMeshTiles NavMeshManager::getRecastMeshTiles() const RecastMeshTiles NavMeshManager::getRecastMeshTiles() const

@ -79,12 +79,11 @@ namespace DetourNavigator
return result; return result;
} }
void NavMeshTilesCache::reportStats(unsigned int frameNumber, osg::Stats& out) const void reportStats(const NavMeshTilesCache::Stats& stats, unsigned int frameNumber, osg::Stats& out)
{ {
const Stats stats = getStats(); out.setAttribute(frameNumber, "NavMesh CacheSize", static_cast<double>(stats.mNavMeshCacheSize));
out.setAttribute(frameNumber, "NavMesh CacheSize", stats.mNavMeshCacheSize); out.setAttribute(frameNumber, "NavMesh UsedTiles", static_cast<double>(stats.mUsedNavMeshTiles));
out.setAttribute(frameNumber, "NavMesh UsedTiles", stats.mUsedNavMeshTiles); out.setAttribute(frameNumber, "NavMesh CachedTiles", static_cast<double>(stats.mCachedNavMeshTiles));
out.setAttribute(frameNumber, "NavMesh CachedTiles", stats.mCachedNavMeshTiles);
if (stats.mGetCount > 0) if (stats.mGetCount > 0)
out.setAttribute(frameNumber, "NavMesh CacheHitRate", static_cast<double>(stats.mHitCount) / stats.mGetCount * 100.0); out.setAttribute(frameNumber, "NavMesh CacheHitRate", static_cast<double>(stats.mHitCount) / stats.mGetCount * 100.0);
} }

@ -144,8 +144,6 @@ namespace DetourNavigator
Stats getStats() const; Stats getStats() const;
void reportStats(unsigned int frameNumber, osg::Stats& stats) const;
private: private:
mutable std::mutex mMutex; mutable std::mutex mMutex;
std::size_t mMaxNavMeshDataSize; std::size_t mMaxNavMeshDataSize;
@ -163,6 +161,8 @@ namespace DetourNavigator
void releaseItem(ItemIterator iterator); void releaseItem(ItemIterator iterator);
}; };
void reportStats(const NavMeshTilesCache::Stats& stats, unsigned int frameNumber, osg::Stats& out);
} }
#endif #endif

@ -4,6 +4,8 @@
#include <Recast.h> #include <Recast.h>
#include <cstring>
namespace namespace
{ {
void initPolyMeshDetail(rcPolyMeshDetail& value) noexcept void initPolyMeshDetail(rcPolyMeshDetail& value) noexcept
@ -24,6 +26,15 @@ namespace DetourNavigator
initPolyMeshDetail(mPolyMeshDetail); initPolyMeshDetail(mPolyMeshDetail);
} }
PreparedNavMeshData::PreparedNavMeshData(const PreparedNavMeshData& other)
: mUserId(other.mUserId)
, mCellSize(other.mCellSize)
, mCellHeight(other.mCellHeight)
{
copyPolyMesh(other.mPolyMesh, mPolyMesh);
copyPolyMeshDetail(other.mPolyMeshDetail, mPolyMeshDetail);
}
PreparedNavMeshData::~PreparedNavMeshData() noexcept PreparedNavMeshData::~PreparedNavMeshData() noexcept
{ {
freePolyMeshDetail(mPolyMeshDetail); freePolyMeshDetail(mPolyMeshDetail);

@ -18,7 +18,7 @@ namespace DetourNavigator
rcPolyMeshDetail mPolyMeshDetail; rcPolyMeshDetail mPolyMeshDetail;
PreparedNavMeshData() noexcept; PreparedNavMeshData() noexcept;
PreparedNavMeshData(const PreparedNavMeshData&) = delete; PreparedNavMeshData(const PreparedNavMeshData& other);
~PreparedNavMeshData() noexcept; ~PreparedNavMeshData() noexcept;

@ -46,4 +46,35 @@ namespace DetourNavigator
rcFree(value.verts); rcFree(value.verts);
rcFree(value.tris); rcFree(value.tris);
} }
void copyPolyMesh(const rcPolyMesh& src, rcPolyMesh& dst)
{
dst.nverts = src.nverts;
dst.npolys = src.npolys;
dst.maxpolys = src.maxpolys;
dst.nvp = src.nvp;
rcVcopy(dst.bmin, src.bmin);
rcVcopy(dst.bmax, src.bmax);
dst.cs = src.cs;
dst.ch = src.ch;
dst.borderSize = src.borderSize;
dst.maxEdgeError = src.maxEdgeError;
permRecastAlloc(dst);
std::memcpy(dst.verts, src.verts, getVertsLength(src) * sizeof(*dst.verts));
std::memcpy(dst.polys, src.polys, getPolysLength(src) * sizeof(*dst.polys));
std::memcpy(dst.regs, src.regs, getRegsLength(src) * sizeof(*dst.regs));
std::memcpy(dst.flags, src.flags, getFlagsLength(src) * sizeof(*dst.flags));
std::memcpy(dst.areas, src.areas, getAreasLength(src) * sizeof(*dst.areas));
}
void copyPolyMeshDetail(const rcPolyMeshDetail& src, rcPolyMeshDetail& dst)
{
dst.nmeshes = src.nmeshes;
dst.nverts = src.nverts;
dst.ntris = src.ntris;
permRecastAlloc(dst);
std::memcpy(dst.meshes, src.meshes, getMeshesLength(src) * sizeof(*dst.meshes));
std::memcpy(dst.verts, src.verts, getVertsLength(src) * sizeof(*dst.verts));
std::memcpy(dst.tris, src.tris, getTrisLength(src) * sizeof(*dst.tris));
}
} }

@ -2,6 +2,7 @@
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_RECAST_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_RECAST_H
#include <Recast.h> #include <Recast.h>
#include <RecastAlloc.h>
#include <cstddef> #include <cstddef>
#include <type_traits> #include <type_traits>
@ -62,6 +63,10 @@ namespace DetourNavigator
void permRecastAlloc(rcPolyMeshDetail& value); void permRecastAlloc(rcPolyMeshDetail& value);
void freePolyMeshDetail(rcPolyMeshDetail& value) noexcept; void freePolyMeshDetail(rcPolyMeshDetail& value) noexcept;
void copyPolyMesh(const rcPolyMesh& src, rcPolyMesh& dst);
void copyPolyMeshDetail(const rcPolyMeshDetail& src, rcPolyMeshDetail& dst);
} }
#endif #endif

@ -63,6 +63,7 @@ namespace DetourNavigator
result.mMinUpdateInterval = std::chrono::milliseconds(::Settings::Manager::getInt("min update interval ms", "Navigator")); result.mMinUpdateInterval = std::chrono::milliseconds(::Settings::Manager::getInt("min update interval ms", "Navigator"));
result.mNavMeshVersion = ::Settings::Manager::getInt("nav mesh version", "Navigator"); result.mNavMeshVersion = ::Settings::Manager::getInt("nav mesh version", "Navigator");
result.mEnableNavMeshDiskCache = ::Settings::Manager::getBool("enable nav mesh disk cache", "Navigator"); result.mEnableNavMeshDiskCache = ::Settings::Manager::getBool("enable nav mesh disk cache", "Navigator");
result.mWriteToNavMeshDb = ::Settings::Manager::getBool("write to navmeshdb", "Navigator");
return result; return result;
} }

@ -40,6 +40,7 @@ namespace DetourNavigator
bool mEnableRecastMeshFileNameRevision = false; bool mEnableRecastMeshFileNameRevision = false;
bool mEnableNavMeshFileNameRevision = false; bool mEnableNavMeshFileNameRevision = false;
bool mEnableNavMeshDiskCache = false; bool mEnableNavMeshDiskCache = false;
bool mWriteToNavMeshDb = false;
RecastSettings mRecast; RecastSettings mRecast;
DetourSettings mDetour; DetourSettings mDetour;
int mWaitUntilMinDistanceToPlayer = 0; int mWaitUntilMinDistanceToPlayer = 0;

@ -393,6 +393,8 @@ void StatsHandler::setUpScene(osgViewer::ViewerBase *viewer)
"NavMesh Waiting", "NavMesh Waiting",
"NavMesh Pushed", "NavMesh Pushed",
"NavMesh Processing", "NavMesh Processing",
"NavMesh DbJobs",
"NavMesh DbCacheHitRate",
"NavMesh CacheSize", "NavMesh CacheSize",
"NavMesh UsedTiles", "NavMesh UsedTiles",
"NavMesh CachedTiles", "NavMesh CachedTiles",

@ -65,6 +65,15 @@ If true navmesh cache stored on disk will be used in addition to memory cache.
If navmesh tile is not present in memory cache, it will be looked up in the disk cache. If navmesh tile is not present in memory cache, it will be looked up in the disk cache.
If it's not found there it will be generated. If it's not found there it will be generated.
write to navmeshdb
------------------
:Type: boolean
:Range: True/False
:Default: False
If true generated navmesh tiles will be stored into disk cache while game is running.
Advanced settings Advanced settings
***************** *****************

@ -937,6 +937,9 @@ nav mesh version = 1
# Use navigation mesh cache stored on disk (true, false) # Use navigation mesh cache stored on disk (true, false)
enable nav mesh disk cache = true enable nav mesh disk cache = true
# Cache navigation mesh tiles to disk (true, false)
write to navmeshdb = false
[Shadows] [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. # 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