mirror of
https://github.com/OpenMW/openmw.git
synced 2025-01-29 22:45:36 +00:00
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.
This commit is contained in:
parent
9e0451c714
commit
96eb8d7be9
23 changed files with 977 additions and 314 deletions
|
@ -43,6 +43,7 @@ if (GTEST_FOUND AND GMOCK_FOUND)
|
|||
detournavigator/tilecachedrecastmeshmanager.cpp
|
||||
detournavigator/navmeshdb.cpp
|
||||
detournavigator/serialization.cpp
|
||||
detournavigator/asyncnavmeshupdater.cpp
|
||||
|
||||
serialization/binaryreader.cpp
|
||||
serialization/binarywriter.cpp
|
||||
|
|
201
apps/openmw_test_suite/detournavigator/asyncnavmeshupdater.cpp
Normal file
201
apps/openmw_test_suite/detournavigator/asyncnavmeshupdater.cpp
Normal file
|
@ -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 "settings.hpp"
|
||||
|
||||
#include <components/detournavigator/navigatorimpl.hpp>
|
||||
#include <components/detournavigator/exceptions.hpp>
|
||||
|
@ -32,10 +33,11 @@ namespace
|
|||
{
|
||||
using namespace testing;
|
||||
using namespace DetourNavigator;
|
||||
using namespace DetourNavigator::Tests;
|
||||
|
||||
struct DetourNavigatorNavigatorTest : Test
|
||||
{
|
||||
Settings mSettings;
|
||||
Settings mSettings = makeSettings();
|
||||
std::unique_ptr<Navigator> mNavigator;
|
||||
const osg::Vec3f mPlayerPosition;
|
||||
const std::string mWorldspace;
|
||||
|
@ -62,34 +64,6 @@ namespace
|
|||
, mOut(mPath)
|
||||
, 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:")));
|
||||
}
|
||||
};
|
||||
|
@ -1013,7 +987,7 @@ namespace
|
|||
mNavigator->update(mPlayerPosition);
|
||||
mNavigator->wait(mListener, WaitConditionType::allJobsDone);
|
||||
|
||||
const Version expectedVersion {1, 1};
|
||||
const Version expectedVersion {1, 4};
|
||||
|
||||
const auto navMeshes = mNavigator->getNavMeshes();
|
||||
ASSERT_EQ(navMeshes.size(), 1);
|
||||
|
|
|
@ -80,51 +80,9 @@ namespace
|
|||
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)
|
||||
{
|
||||
auto result = std::make_unique<PreparedNavMeshData>();
|
||||
result->mUserId = value.mUserId;
|
||||
result->mCellHeight = value.mCellHeight;
|
||||
result->mCellSize = value.mCellSize;
|
||||
clone(value.mPolyMesh, result->mPolyMesh);
|
||||
clone(value.mPolyMeshDetail, result->mPolyMeshDetail);
|
||||
return result;
|
||||
return std::make_unique<PreparedNavMeshData>(value);
|
||||
}
|
||||
|
||||
Mesh makeMesh()
|
||||
|
|
50
apps/openmw_test_suite/detournavigator/settings.hpp
Normal file
50
apps/openmw_test_suite/detournavigator/settings.hpp
Normal file
|
@ -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 "settings.hpp"
|
||||
#include "version.hpp"
|
||||
#include "serialization.hpp"
|
||||
#include "navmeshdbutils.hpp"
|
||||
#include "dbrefgeometryobject.hpp"
|
||||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
#include <components/misc/thread.hpp>
|
||||
|
@ -15,70 +18,102 @@
|
|||
#include <algorithm>
|
||||
#include <numeric>
|
||||
#include <set>
|
||||
|
||||
namespace
|
||||
{
|
||||
using DetourNavigator::ChangeType;
|
||||
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 getMinDistanceTo(const TilePosition& position, int maxDistance,
|
||||
const std::set<std::tuple<osg::Vec3f, TilePosition>>& pushedTiles,
|
||||
const std::set<std::tuple<osg::Vec3f, TilePosition>>& presentTiles)
|
||||
{
|
||||
int result = maxDistance;
|
||||
for (const auto& [halfExtents, tile] : pushedTiles)
|
||||
if (presentTiles.find(std::tie(halfExtents, tile)) == presentTiles.end())
|
||||
result = std::min(result, getManhattanDistance(position, tile));
|
||||
return result;
|
||||
}
|
||||
|
||||
UpdateType getUpdateType(ChangeType changeType) noexcept
|
||||
{
|
||||
if (changeType == ChangeType::update)
|
||||
return UpdateType::Temporary;
|
||||
return UpdateType::Persistent;
|
||||
}
|
||||
|
||||
auto getPriority(const Job& job) noexcept
|
||||
{
|
||||
return std::make_tuple(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 getAgentAndTile(const Job& job) noexcept
|
||||
{
|
||||
return std::make_tuple(job.mAgentHalfExtents, job.mChangedTile);
|
||||
}
|
||||
}
|
||||
#include <type_traits>
|
||||
|
||||
namespace DetourNavigator
|
||||
{
|
||||
namespace
|
||||
{
|
||||
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,
|
||||
const std::set<std::tuple<osg::Vec3f, TilePosition>>& pushedTiles,
|
||||
const std::set<std::tuple<osg::Vec3f, TilePosition>>& presentTiles)
|
||||
{
|
||||
int result = maxDistance;
|
||||
for (const auto& [halfExtents, tile] : pushedTiles)
|
||||
if (presentTiles.find(std::tie(halfExtents, tile)) == presentTiles.end())
|
||||
result = std::min(result, getManhattanDistance(position, tile));
|
||||
return result;
|
||||
}
|
||||
|
||||
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.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);
|
||||
}
|
||||
}
|
||||
|
||||
Job::Job(const osg::Vec3f& agentHalfExtents, std::weak_ptr<GuardedNavMeshCacheItem> navMeshCacheItem,
|
||||
std::string_view worldspace, const TilePosition& changedTile, ChangeType changeType, int distanceToPlayer,
|
||||
std::chrono::steady_clock::time_point processTime)
|
||||
: mAgentHalfExtents(agentHalfExtents)
|
||||
: mId(getNextJobId())
|
||||
, mAgentHalfExtents(agentHalfExtents)
|
||||
, mNavMeshCacheItem(std::move(navMeshCacheItem))
|
||||
, mWorldspace(worldspace)
|
||||
, mChangedTile(changedTile)
|
||||
|
@ -94,9 +129,9 @@ namespace DetourNavigator
|
|||
: mSettings(settings)
|
||||
, mRecastMeshManager(recastMeshManager)
|
||||
, mOffMeshConnectionsManager(offMeshConnectionsManager)
|
||||
, mDb(std::move(db))
|
||||
, mShouldStop()
|
||||
, mNavMeshTilesCache(settings.mMaxNavMeshTilesCacheSize)
|
||||
, mDbWorker(makeDbWorker(*this, std::move(db), mSettings))
|
||||
{
|
||||
for (std::size_t i = 0; i < mSettings.get().mAsyncNavMeshUpdaterThreads; ++i)
|
||||
mThreads.emplace_back([&] { process(); });
|
||||
|
@ -105,6 +140,8 @@ namespace DetourNavigator
|
|||
AsyncNavMeshUpdater::~AsyncNavMeshUpdater()
|
||||
{
|
||||
mShouldStop = true;
|
||||
if (mDbWorker != nullptr)
|
||||
mDbWorker->stop();
|
||||
std::unique_lock<std::mutex> lock(mMutex);
|
||||
mWaiting.clear();
|
||||
mHasJob.notify_all();
|
||||
|
@ -128,18 +165,12 @@ namespace DetourNavigator
|
|||
return;
|
||||
|
||||
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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
updateJobs(mWaiting, playerTile, maxTiles);
|
||||
|
||||
for (const auto& [changedTile, changeType] : changedTiles)
|
||||
{
|
||||
|
@ -152,6 +183,9 @@ namespace DetourNavigator
|
|||
const JobIt it = mJobs.emplace(mJobs.end(), agentHalfExtents, navMeshCacheItem, worldspace,
|
||||
changedTile, changeType, getManhattanDistance(changedTile, playerTile), processTime);
|
||||
|
||||
Log(Debug::Debug) << "Post job " << it->mId << " for agent=(" << it->mAgentHalfExtents << ")"
|
||||
<< " changedTile=(" << it->mChangedTile << ")";
|
||||
|
||||
if (playerTileChanged)
|
||||
mWaiting.push_back(it);
|
||||
else
|
||||
|
@ -166,6 +200,11 @@ namespace DetourNavigator
|
|||
|
||||
if (!mWaiting.empty())
|
||||
mHasJob.notify_all();
|
||||
|
||||
lock.unlock();
|
||||
|
||||
if (playerTileChanged && mDbWorker != nullptr)
|
||||
mDbWorker->updateJobs(playerTile, maxTiles);
|
||||
}
|
||||
|
||||
void AsyncNavMeshUpdater::wait(Loading::Listener& listener, WaitConditionType waitConditionType)
|
||||
|
@ -243,25 +282,40 @@ namespace DetourNavigator
|
|||
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;
|
||||
std::size_t waiting = 0;
|
||||
std::size_t pushed = 0;
|
||||
|
||||
Stats result;
|
||||
{
|
||||
const std::lock_guard<std::mutex> lock(mMutex);
|
||||
jobs = mJobs.size();
|
||||
waiting = mWaiting.size();
|
||||
pushed = mPushed.size();
|
||||
result.mJobs = mJobs.size();
|
||||
result.mWaiting = mWaiting.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;
|
||||
}
|
||||
|
||||
void reportStats(const AsyncNavMeshUpdater::Stats& 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 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", 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);
|
||||
}
|
||||
|
||||
stats.setAttribute(frameNumber, "NavMesh Jobs", jobs);
|
||||
stats.setAttribute(frameNumber, "NavMesh Waiting", waiting);
|
||||
stats.setAttribute(frameNumber, "NavMesh Pushed", pushed);
|
||||
stats.setAttribute(frameNumber, "NavMesh Processing", mProcessingTiles.lockConst()->size());
|
||||
|
||||
mNavMeshTilesCache.reportStats(frameNumber, stats);
|
||||
reportStats(stats.mCache, frameNumber, out);
|
||||
}
|
||||
|
||||
void AsyncNavMeshUpdater::process() noexcept
|
||||
|
@ -274,12 +328,26 @@ namespace DetourNavigator
|
|||
{
|
||||
if (JobIt job = getNextJob(); job != mJobs.end())
|
||||
{
|
||||
const auto processed = processJob(*job);
|
||||
unlockTile(job->mAgentHalfExtents, job->mChangedTile);
|
||||
if (processed)
|
||||
removeJob(job);
|
||||
else
|
||||
repost(job);
|
||||
const JobStatus status = processJob(*job);
|
||||
Log(Debug::Debug) << "Processed job " << job->mId << " with status=" << status;
|
||||
switch (status)
|
||||
{
|
||||
case JobStatus::Done:
|
||||
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
|
||||
cleanupLastUpdates();
|
||||
|
@ -292,34 +360,156 @@ namespace DetourNavigator
|
|||
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 << ")"
|
||||
" by thread=" << std::this_thread::get_id();
|
||||
|
||||
const auto start = std::chrono::steady_clock::now();
|
||||
Log(Debug::Debug) << "Processing job " << job.mId << " by thread=" << std::this_thread::get_id();
|
||||
|
||||
const auto navMeshCacheItem = job.mNavMeshCacheItem.lock();
|
||||
|
||||
if (!navMeshCacheItem)
|
||||
return true;
|
||||
return JobStatus::Done;
|
||||
|
||||
const auto recastMesh = mRecastMeshManager.get().getMesh(job.mWorldspace, job.mChangedTile);
|
||||
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 status = updateNavMesh(job.mAgentHalfExtents, recastMesh.get(), job.mWorldspace, job.mChangedTile,
|
||||
playerTile, offMeshConnections, mSettings, navMeshCacheItem, mNavMeshTilesCache,
|
||||
getUpdateType(job.mChangeType), mDb, mNextShapeId);
|
||||
const UpdateNavMeshStatus status = navMeshCacheItem.lock()->updateTile(job.mChangedTile, std::move(cachedNavMeshData),
|
||||
makeNavMeshTileData(*preparedNavMeshDataPtr, offMeshConnections, job.mAgentHalfExtents, job.mChangedTile, mSettings.get().mRecast));
|
||||
|
||||
if (recastMesh != nullptr)
|
||||
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)
|
||||
{
|
||||
const Version navMeshVersion = navMeshCacheItem->lockConst()->getVersion();
|
||||
mRecastMeshManager.get().reportNavMeshChange(job.mChangedTile,
|
||||
Version {recastMesh->getGeneration(), recastMesh->getRevision()},
|
||||
navMeshVersion);
|
||||
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 (preparedNavMeshData == nullptr)
|
||||
{
|
||||
Log(Debug::Debug) << "Null navmesh data for job " << job.mId;
|
||||
navMeshCacheItem.lock()->markAsEmpty(job.mChangedTile);
|
||||
return JobStatus::Done;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
const std::scoped_lock lock(mMutex);
|
||||
|
@ -331,23 +521,9 @@ namespace DetourNavigator
|
|||
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();
|
||||
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);
|
||||
return isSuccess(status) ? JobStatus::Done : JobStatus::Fail;
|
||||
}
|
||||
|
||||
JobIt AsyncNavMeshUpdater::getNextJob()
|
||||
|
@ -376,8 +552,12 @@ namespace DetourNavigator
|
|||
|
||||
mWaiting.pop_front();
|
||||
|
||||
if (job->mRecastMesh != nullptr)
|
||||
return job;
|
||||
|
||||
if (!lockTile(job->mAgentHalfExtents, job->mChangedTile))
|
||||
{
|
||||
Log(Debug::Debug) << "Failed to lock tile by " << job->mId;
|
||||
++job->mTryNumber;
|
||||
insertPrioritizedJob(job, mWaiting);
|
||||
return mJobs.end();
|
||||
|
@ -415,6 +595,8 @@ namespace DetourNavigator
|
|||
|
||||
void AsyncNavMeshUpdater::repost(JobIt job)
|
||||
{
|
||||
unlockTile(job->mAgentHalfExtents, job->mChangedTile);
|
||||
|
||||
if (mShouldStop || job->mTryNumber > 2)
|
||||
return;
|
||||
|
||||
|
@ -433,17 +615,15 @@ namespace DetourNavigator
|
|||
|
||||
bool AsyncNavMeshUpdater::lockTile(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile)
|
||||
{
|
||||
if (mSettings.get().mAsyncNavMeshUpdaterThreads <= 1)
|
||||
return true;
|
||||
Log(Debug::Debug) << "Locking tile agent=(" << agentHalfExtents << ") changedTile=(" << changedTile << ")";
|
||||
return mProcessingTiles.lock()->emplace(agentHalfExtents, changedTile).second;
|
||||
}
|
||||
|
||||
void AsyncNavMeshUpdater::unlockTile(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile)
|
||||
{
|
||||
if (mSettings.get().mAsyncNavMeshUpdaterThreads <= 1)
|
||||
return;
|
||||
auto locked = mProcessingTiles.lock();
|
||||
locked->erase(std::tie(agentHalfExtents, changedTile));
|
||||
Log(Debug::Debug) << "Unlocked tile agent=(" << agentHalfExtents << ") changedTile=(" << changedTile << ")";
|
||||
if (locked->empty())
|
||||
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)
|
||||
{
|
||||
Log(Debug::Debug) << "Removing job " << job->mId << " by thread=" << std::this_thread::get_id();
|
||||
const std::lock_guard lock(mMutex);
|
||||
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 <tuple>
|
||||
#include <list>
|
||||
#include <optional>
|
||||
|
||||
class dtNavMesh;
|
||||
|
||||
|
@ -54,8 +55,15 @@ namespace DetourNavigator
|
|||
return stream << "ChangeType::" << static_cast<int>(value);
|
||||
}
|
||||
|
||||
enum class JobState
|
||||
{
|
||||
Initial,
|
||||
WithDbResult,
|
||||
};
|
||||
|
||||
struct Job
|
||||
{
|
||||
const std::size_t mId;
|
||||
const osg::Vec3f mAgentHalfExtents;
|
||||
const std::weak_ptr<GuardedNavMeshCacheItem> mNavMeshCacheItem;
|
||||
const std::string mWorldspace;
|
||||
|
@ -65,6 +73,11 @@ namespace DetourNavigator
|
|||
ChangeType mChangeType;
|
||||
int mDistanceToPlayer;
|
||||
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,
|
||||
std::string_view worldspace, const TilePosition& changedTile, ChangeType changeType, int distanceToPlayer,
|
||||
|
@ -73,27 +86,124 @@ namespace DetourNavigator
|
|||
|
||||
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
|
||||
{
|
||||
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,
|
||||
OffMeshConnectionsManager& offMeshConnectionsManager, std::unique_ptr<NavMeshDb>&& db);
|
||||
~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 std::map<TilePosition, ChangeType>& changedTiles);
|
||||
|
||||
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:
|
||||
std::reference_wrapper<const Settings> mSettings;
|
||||
std::reference_wrapper<TileCachedRecastMeshManager> mRecastMeshManager;
|
||||
std::reference_wrapper<OffMeshConnectionsManager> mOffMeshConnectionsManager;
|
||||
Misc::ScopeGuarded<std::unique_ptr<NavMeshDb>> mDb;
|
||||
ShapeId mNextShapeId {1};
|
||||
std::atomic_bool mShouldStop;
|
||||
mutable std::mutex mMutex;
|
||||
std::condition_variable mHasJob;
|
||||
|
@ -108,15 +218,22 @@ namespace DetourNavigator
|
|||
std::map<std::tuple<osg::Vec3f, TilePosition>, std::chrono::steady_clock::time_point> mLastUpdates;
|
||||
std::set<std::tuple<osg::Vec3f, TilePosition>> mPresentTiles;
|
||||
std::vector<std::thread> mThreads;
|
||||
std::unique_ptr<DbWorker> mDbWorker;
|
||||
std::atomic_size_t mDbGetTileHits {0};
|
||||
|
||||
void process() noexcept;
|
||||
|
||||
bool processJob(const Job& job);
|
||||
JobStatus processJob(Job& job);
|
||||
|
||||
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 getNextJob();
|
||||
|
||||
JobIt getJob(std::deque<JobIt>& jobs, bool changeLastUpdate);
|
||||
|
||||
void postThreadJob(JobIt job, std::deque<JobIt>& queue);
|
||||
|
||||
void writeDebugFiles(const Job& job, const RecastMesh* recastMesh) const;
|
||||
|
@ -134,9 +251,9 @@ namespace DetourNavigator
|
|||
int waitUntilJobsDoneForNotPresentTiles(const std::size_t initialJobsLeft, std::size_t& maxJobsLeft, Loading::Listener& listener);
|
||||
|
||||
void waitUntilAllJobsDone();
|
||||
|
||||
inline void removeJob(JobIt job);
|
||||
};
|
||||
|
||||
void reportStats(const AsyncNavMeshUpdater::Stats& stats, unsigned int frameNumber, osg::Stats& out);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -544,89 +544,4 @@ namespace DetourNavigator
|
|||
|
||||
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);
|
||||
|
||||
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
|
||||
|
|
|
@ -50,7 +50,8 @@ namespace DetourNavigator
|
|||
{
|
||||
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);
|
||||
if (dtStatusSucceed(addStatus))
|
||||
{
|
||||
|
@ -82,7 +83,8 @@ namespace DetourNavigator
|
|||
|
||||
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)
|
||||
{
|
||||
mUsedTiles.erase(position);
|
||||
|
@ -90,4 +92,21 @@ namespace DetourNavigator
|
|||
}
|
||||
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 <ostream>
|
||||
#include <set>
|
||||
|
||||
struct dtMeshTile;
|
||||
|
||||
|
@ -147,6 +148,10 @@ namespace DetourNavigator
|
|||
|
||||
UpdateNavMeshStatus removeTile(const TilePosition& position);
|
||||
|
||||
UpdateNavMeshStatus markAsEmpty(const TilePosition& position);
|
||||
|
||||
bool isEmptyTile(const TilePosition& position) const;
|
||||
|
||||
template <class Function>
|
||||
void forEachUsedTile(Function&& function) const
|
||||
{
|
||||
|
@ -166,6 +171,7 @@ namespace DetourNavigator
|
|||
NavMeshPtr mImpl;
|
||||
Version mVersion;
|
||||
std::map<TilePosition, Tile> mUsedTiles;
|
||||
std::set<TilePosition> mEmptyTiles;
|
||||
};
|
||||
|
||||
using GuardedNavMeshCacheItem = Misc::ScopeGuarded<NavMeshCacheItem>;
|
||||
|
|
|
@ -211,7 +211,7 @@ namespace DetourNavigator
|
|||
const auto shouldAdd = shouldAddTile(tile, playerTile, maxTiles);
|
||||
const auto presentInNavMesh = bool(navMesh.getTileAt(tile.x(), tile.y(), 0));
|
||||
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)
|
||||
tilesToPost.insert(std::make_pair(tile, ChangeType::mixed));
|
||||
else
|
||||
|
@ -243,7 +243,7 @@ namespace DetourNavigator
|
|||
|
||||
void NavMeshManager::reportStats(unsigned int frameNumber, osg::Stats& stats) const
|
||||
{
|
||||
mAsyncNavMeshUpdater.reportStats(frameNumber, stats);
|
||||
DetourNavigator::reportStats(mAsyncNavMeshUpdater.getStats(), frameNumber, stats);
|
||||
}
|
||||
|
||||
RecastMeshTiles NavMeshManager::getRecastMeshTiles() const
|
||||
|
|
|
@ -79,12 +79,11 @@ namespace DetourNavigator
|
|||
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", stats.mNavMeshCacheSize);
|
||||
out.setAttribute(frameNumber, "NavMesh UsedTiles", stats.mUsedNavMeshTiles);
|
||||
out.setAttribute(frameNumber, "NavMesh CachedTiles", stats.mCachedNavMeshTiles);
|
||||
out.setAttribute(frameNumber, "NavMesh CacheSize", static_cast<double>(stats.mNavMeshCacheSize));
|
||||
out.setAttribute(frameNumber, "NavMesh UsedTiles", static_cast<double>(stats.mUsedNavMeshTiles));
|
||||
out.setAttribute(frameNumber, "NavMesh CachedTiles", static_cast<double>(stats.mCachedNavMeshTiles));
|
||||
if (stats.mGetCount > 0)
|
||||
out.setAttribute(frameNumber, "NavMesh CacheHitRate", static_cast<double>(stats.mHitCount) / stats.mGetCount * 100.0);
|
||||
}
|
||||
|
|
|
@ -144,8 +144,6 @@ namespace DetourNavigator
|
|||
|
||||
Stats getStats() const;
|
||||
|
||||
void reportStats(unsigned int frameNumber, osg::Stats& stats) const;
|
||||
|
||||
private:
|
||||
mutable std::mutex mMutex;
|
||||
std::size_t mMaxNavMeshDataSize;
|
||||
|
@ -163,6 +161,8 @@ namespace DetourNavigator
|
|||
|
||||
void releaseItem(ItemIterator iterator);
|
||||
};
|
||||
|
||||
void reportStats(const NavMeshTilesCache::Stats& stats, unsigned int frameNumber, osg::Stats& out);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
#include <Recast.h>
|
||||
|
||||
#include <cstring>
|
||||
|
||||
namespace
|
||||
{
|
||||
void initPolyMeshDetail(rcPolyMeshDetail& value) noexcept
|
||||
|
@ -24,6 +26,15 @@ namespace DetourNavigator
|
|||
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
|
||||
{
|
||||
freePolyMeshDetail(mPolyMeshDetail);
|
||||
|
|
|
@ -18,7 +18,7 @@ namespace DetourNavigator
|
|||
rcPolyMeshDetail mPolyMeshDetail;
|
||||
|
||||
PreparedNavMeshData() noexcept;
|
||||
PreparedNavMeshData(const PreparedNavMeshData&) = delete;
|
||||
PreparedNavMeshData(const PreparedNavMeshData& other);
|
||||
|
||||
~PreparedNavMeshData() noexcept;
|
||||
|
||||
|
|
|
@ -46,4 +46,35 @@ namespace DetourNavigator
|
|||
rcFree(value.verts);
|
||||
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
|
||||
|
||||
#include <Recast.h>
|
||||
#include <RecastAlloc.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <type_traits>
|
||||
|
@ -62,6 +63,10 @@ namespace DetourNavigator
|
|||
void permRecastAlloc(rcPolyMeshDetail& value);
|
||||
|
||||
void freePolyMeshDetail(rcPolyMeshDetail& value) noexcept;
|
||||
|
||||
void copyPolyMesh(const rcPolyMesh& src, rcPolyMesh& dst);
|
||||
|
||||
void copyPolyMeshDetail(const rcPolyMeshDetail& src, rcPolyMeshDetail& dst);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -63,6 +63,7 @@ namespace DetourNavigator
|
|||
result.mMinUpdateInterval = std::chrono::milliseconds(::Settings::Manager::getInt("min update interval ms", "Navigator"));
|
||||
result.mNavMeshVersion = ::Settings::Manager::getInt("nav mesh version", "Navigator");
|
||||
result.mEnableNavMeshDiskCache = ::Settings::Manager::getBool("enable nav mesh disk cache", "Navigator");
|
||||
result.mWriteToNavMeshDb = ::Settings::Manager::getBool("write to navmeshdb", "Navigator");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -40,6 +40,7 @@ namespace DetourNavigator
|
|||
bool mEnableRecastMeshFileNameRevision = false;
|
||||
bool mEnableNavMeshFileNameRevision = false;
|
||||
bool mEnableNavMeshDiskCache = false;
|
||||
bool mWriteToNavMeshDb = false;
|
||||
RecastSettings mRecast;
|
||||
DetourSettings mDetour;
|
||||
int mWaitUntilMinDistanceToPlayer = 0;
|
||||
|
|
|
@ -393,6 +393,8 @@ void StatsHandler::setUpScene(osgViewer::ViewerBase *viewer)
|
|||
"NavMesh Waiting",
|
||||
"NavMesh Pushed",
|
||||
"NavMesh Processing",
|
||||
"NavMesh DbJobs",
|
||||
"NavMesh DbCacheHitRate",
|
||||
"NavMesh CacheSize",
|
||||
"NavMesh UsedTiles",
|
||||
"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 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
|
||||
*****************
|
||||
|
||||
|
|
|
@ -937,6 +937,9 @@ nav mesh version = 1
|
|||
# Use navigation mesh cache stored on disk (true, false)
|
||||
enable nav mesh disk cache = true
|
||||
|
||||
# Cache navigation mesh tiles to disk (true, false)
|
||||
write to navmeshdb = 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…
Reference in a new issue