diff --git a/apps/openmw/mwrender/navmesh.cpp b/apps/openmw/mwrender/navmesh.cpp index 23f4a8d58..331f506ab 100644 --- a/apps/openmw/mwrender/navmesh.cpp +++ b/apps/openmw/mwrender/navmesh.cpp @@ -31,7 +31,7 @@ namespace MWRender return mEnabled; } - void NavMesh::update(const DetourNavigator::SharedNavMesh& sharedNavMesh, const std::size_t id, + void NavMesh::update(const dtNavMesh& navMesh, const std::size_t id, const std::size_t generation, const std::size_t revision, const DetourNavigator::Settings& settings) { if (!mEnabled || (mId == id && mGeneration >= generation && mRevision >= revision)) @@ -42,7 +42,7 @@ namespace MWRender mRevision = revision; if (mGroup) mRootNode->removeChild(mGroup); - mGroup = SceneUtil::createNavMeshGroup(*sharedNavMesh.lock(), settings); + mGroup = SceneUtil::createNavMeshGroup(navMesh, settings); if (mGroup) { mGroup->setNodeMask(Mask_Debug); diff --git a/apps/openmw/mwrender/navmesh.hpp b/apps/openmw/mwrender/navmesh.hpp index 34e71a70c..29205ca27 100644 --- a/apps/openmw/mwrender/navmesh.hpp +++ b/apps/openmw/mwrender/navmesh.hpp @@ -21,9 +21,8 @@ namespace MWRender bool toggle(); - void update(const DetourNavigator::SharedNavMesh& sharedNavMesh, - const std::size_t number, const std::size_t generation, const std::size_t revision, - const DetourNavigator::Settings& settings); + void update(const dtNavMesh& navMesh, const std::size_t number, const std::size_t generation, + const std::size_t revision, const DetourNavigator::Settings& settings); void reset(); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index bc540d7e6..4d79fd3de 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -612,8 +612,9 @@ namespace MWRender { try { - mNavMesh->update(it->second->mValue, mNavMeshNumber, it->second->mGeneration, - it->second->mNavMeshRevision, mNavigator.getSettings()); + const auto locked = it->second.lockConst(); + mNavMesh->update(locked->getValue(), mNavMeshNumber, locked->getGeneration(), + locked->getNavMeshRevision(), mNavigator.getSettings()); } catch (const std::exception& e) { diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index c45d06c41..93babf120 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -216,6 +216,7 @@ namespace MWWorld navigatorSettings.mRegionMinSize = Settings::Manager::getInt("region min size", "Navigator"); navigatorSettings.mTileSize = Settings::Manager::getInt("tile size", "Navigator"); navigatorSettings.mAsyncNavMeshUpdaterThreads = static_cast(Settings::Manager::getInt("async nav mesh updater threads", "Navigator")); + navigatorSettings.mMaxNavMeshTilesCacheSize = static_cast(Settings::Manager::getInt("max nav mesh tiles cache size", "Navigator")); navigatorSettings.mMaxPolygonPathSize = static_cast(Settings::Manager::getInt("max polygon path size", "Navigator")); navigatorSettings.mMaxSmoothPathSize = static_cast(Settings::Manager::getInt("max smooth path size", "Navigator")); navigatorSettings.mTrianglesPerChunk = static_cast(Settings::Manager::getInt("triangles per chunk", "Navigator")); diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index b86f4b812..acb33e1fa 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -23,6 +23,7 @@ if (GTEST_FOUND AND GMOCK_FOUND) detournavigator/recastmeshbuilder.cpp detournavigator/gettilespositions.cpp detournavigator/recastmeshobject.cpp + detournavigator/navmeshtilescache.cpp ) source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) diff --git a/apps/openmw_test_suite/detournavigator/navigator.cpp b/apps/openmw_test_suite/detournavigator/navigator.cpp index 1b42b6d05..7926e208d 100644 --- a/apps/openmw_test_suite/detournavigator/navigator.cpp +++ b/apps/openmw_test_suite/detournavigator/navigator.cpp @@ -56,6 +56,7 @@ namespace mSettings.mRegionMinSize = 8; mSettings.mTileSize = 64; mSettings.mAsyncNavMeshUpdaterThreads = 1; + mSettings.mMaxNavMeshTilesCacheSize = 1024 * 1024; mSettings.mMaxPolygonPathSize = 1024; mSettings.mMaxSmoothPathSize = 1024; mSettings.mTrianglesPerChunk = 256; @@ -602,4 +603,58 @@ namespace osg::Vec3f(0, -215, -94.753631591796875), })) << mPath; } + + TEST_F(DetourNavigatorNavigatorTest, update_remove_and_update_then_find_path_should_return_path) + { + const std::array heightfieldData {{ + 0, 0, 0, 0, 0, + 0, -25, -25, -25, -25, + 0, -25, -100, -100, -100, + 0, -25, -100, -100, -100, + 0, -25, -100, -100, -100, + }}; + btHeightfieldTerrainShape shape(5, 5, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false); + shape.setLocalScaling(btVector3(128, 128, 1)); + + mNavigator->addAgent(mAgentHalfExtents); + mNavigator->addObject(ObjectId(&shape), shape, btTransform::getIdentity()); + mNavigator->update(mPlayerPosition); + mNavigator->wait(); + + mNavigator->removeObject(ObjectId(&shape)); + mNavigator->update(mPlayerPosition); + mNavigator->wait(); + + mNavigator->addObject(ObjectId(&shape), shape, btTransform::getIdentity()); + mNavigator->update(mPlayerPosition); + mNavigator->wait(); + + mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, Flag_walk, mOut); + + EXPECT_EQ(mPath, std::deque({ + osg::Vec3f(-215, 215, 1.85963428020477294921875), + osg::Vec3f(-194.9653167724609375, 194.9653167724609375, -6.5760211944580078125), + osg::Vec3f(-174.930633544921875, 174.930633544921875, -15.01167774200439453125), + osg::Vec3f(-154.8959503173828125, 154.8959503173828125, -23.4473323822021484375), + osg::Vec3f(-134.86126708984375, 134.86126708984375, -31.8829898834228515625), + osg::Vec3f(-114.82657623291015625, 114.82657623291015625, -40.3186492919921875), + osg::Vec3f(-94.7918853759765625, 94.7918853759765625, -47.39907073974609375), + osg::Vec3f(-74.75719451904296875, 74.75719451904296875, -53.7258148193359375), + osg::Vec3f(-54.722499847412109375, 54.722499847412109375, -60.052555084228515625), + osg::Vec3f(-34.68780517578125, 34.68780517578125, -66.37929534912109375), + osg::Vec3f(-14.6531162261962890625, 14.6531162261962890625, -72.70604705810546875), + osg::Vec3f(5.3815765380859375, -5.3815765380859375, -75.35065460205078125), + osg::Vec3f(25.41626739501953125, -25.41626739501953125, -67.96945953369140625), + osg::Vec3f(45.450958251953125, -45.450958251953125, -60.58824920654296875), + osg::Vec3f(65.48564910888671875, -65.48564910888671875, -53.20705413818359375), + osg::Vec3f(85.5203399658203125, -85.5203399658203125, -45.825855255126953125), + osg::Vec3f(105.55503082275390625, -105.55503082275390625, -38.44464874267578125), + osg::Vec3f(125.5897216796875, -125.5897216796875, -31.063449859619140625), + osg::Vec3f(145.6244049072265625, -145.6244049072265625, -23.6822509765625), + osg::Vec3f(165.659088134765625, -165.659088134765625, -16.3010540008544921875), + osg::Vec3f(185.6937713623046875, -185.6937713623046875, -8.91985416412353515625), + osg::Vec3f(205.7284698486328125, -205.7284698486328125, -1.53864824771881103515625), + osg::Vec3f(215, -215, 1.877177715301513671875), + })) << mPath; + } } diff --git a/apps/openmw_test_suite/detournavigator/navmeshtilescache.cpp b/apps/openmw_test_suite/detournavigator/navmeshtilescache.cpp new file mode 100644 index 000000000..93fc5fbaf --- /dev/null +++ b/apps/openmw_test_suite/detournavigator/navmeshtilescache.cpp @@ -0,0 +1,312 @@ +#include "operators.hpp" + +#include +#include +#include + +#include + +#include + +namespace DetourNavigator +{ + static inline bool operator ==(const NavMeshDataRef& lhs, const NavMeshDataRef& rhs) + { + return std::make_pair(lhs.mValue, lhs.mSize) == std::make_pair(rhs.mValue, rhs.mSize); + } +} + +namespace +{ + using namespace testing; + using namespace DetourNavigator; + + struct DetourNavigatorNavMeshTilesCacheTest : Test + { + const osg::Vec3f mAgentHalfExtents {1, 2, 3}; + const TilePosition mTilePosition {0, 0}; + const std::vector mIndices {{0, 1, 2}}; + const std::vector mVertices {{0, 0, 0, 1, 0, 0, 1, 1, 0}}; + const std::vector mAreaTypes {1, AreaType_ground}; + const std::vector mWater {}; + const std::size_t mTrianglesPerChunk {1}; + const RecastMesh mRecastMesh {mIndices, mVertices, mAreaTypes, mWater, mTrianglesPerChunk}; + const std::vector mOffMeshConnections {}; + unsigned char* const mData = reinterpret_cast(dtAlloc(1, DT_ALLOC_PERM)); + NavMeshData mNavMeshData {mData, 1}; + }; + + TEST_F(DetourNavigatorNavMeshTilesCacheTest, get_for_empty_cache_should_return_empty_value) + { + const std::size_t maxSize = 0; + NavMeshTilesCache cache(maxSize); + + EXPECT_FALSE(cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections)); + } + + TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_for_not_enought_cache_size_should_return_empty_value) + { + const std::size_t maxSize = 0; + NavMeshTilesCache cache(maxSize); + + EXPECT_FALSE(cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, + std::move(mNavMeshData))); + } + + TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_should_return_cached_value) + { + const std::size_t maxSize = 1; + NavMeshTilesCache cache(maxSize); + + const auto result = cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, + std::move(mNavMeshData)); + EXPECT_EQ(result.get(), (NavMeshDataRef {mData, 1})); + } + + TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_existing_element_should_throw_exception) + { + const std::size_t maxSize = 2; + NavMeshTilesCache cache(maxSize); + const auto anotherData = reinterpret_cast(dtAlloc(1, DT_ALLOC_PERM)); + NavMeshData anotherNavMeshData {anotherData, 1}; + + cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData)); + EXPECT_THROW( + cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(anotherNavMeshData)), + InvalidArgument + ); + } + + TEST_F(DetourNavigatorNavMeshTilesCacheTest, get_should_return_cached_value) + { + const std::size_t maxSize = 1; + NavMeshTilesCache cache(maxSize); + + cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData)); + const auto result = cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections); + ASSERT_TRUE(result); + EXPECT_EQ(result.get(), (NavMeshDataRef {mData, 1})); + } + + TEST_F(DetourNavigatorNavMeshTilesCacheTest, get_for_cache_miss_by_agent_half_extents_should_return_empty_value) + { + const std::size_t maxSize = 1; + NavMeshTilesCache cache(maxSize); + const osg::Vec3f unexsistentAgentHalfExtents {1, 1, 1}; + + cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData)); + EXPECT_FALSE(cache.get(unexsistentAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections)); + } + + TEST_F(DetourNavigatorNavMeshTilesCacheTest, get_for_cache_miss_by_tile_position_should_return_empty_value) + { + const std::size_t maxSize = 1; + NavMeshTilesCache cache(maxSize); + const TilePosition unexistentTilePosition {1, 1}; + + cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData)); + EXPECT_FALSE(cache.get(mAgentHalfExtents, unexistentTilePosition, mRecastMesh, mOffMeshConnections)); + } + + TEST_F(DetourNavigatorNavMeshTilesCacheTest, get_for_cache_miss_by_recast_mesh_should_return_empty_value) + { + const std::size_t maxSize = 1; + NavMeshTilesCache cache(maxSize); + const std::vector water {1, RecastMesh::Water {1, btTransform::getIdentity()}}; + const RecastMesh unexistentRecastMesh {mIndices, mVertices, mAreaTypes, water, mTrianglesPerChunk}; + + cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData)); + EXPECT_FALSE(cache.get(mAgentHalfExtents, mTilePosition, unexistentRecastMesh, mOffMeshConnections)); + } + + TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_should_replace_unused_value) + { + const std::size_t maxSize = 1; + NavMeshTilesCache cache(maxSize); + + const std::vector water {1, RecastMesh::Water {1, btTransform::getIdentity()}}; + const RecastMesh anotherRecastMesh {mIndices, mVertices, mAreaTypes, water, mTrianglesPerChunk}; + const auto anotherData = reinterpret_cast(dtAlloc(1, DT_ALLOC_PERM)); + NavMeshData anotherNavMeshData {anotherData, 1}; + + cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData)); + const auto result = cache.set(mAgentHalfExtents, mTilePosition, anotherRecastMesh, mOffMeshConnections, + std::move(anotherNavMeshData)); + ASSERT_TRUE(result); + EXPECT_EQ(result.get(), (NavMeshDataRef {anotherData, 1})); + EXPECT_FALSE(cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections)); + } + + TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_should_not_replace_used_value) + { + const std::size_t maxSize = 1; + NavMeshTilesCache cache(maxSize); + + const std::vector water {1, RecastMesh::Water {1, btTransform::getIdentity()}}; + const RecastMesh anotherRecastMesh {mIndices, mVertices, mAreaTypes, water, mTrianglesPerChunk}; + const auto anotherData = reinterpret_cast(dtAlloc(1, DT_ALLOC_PERM)); + NavMeshData anotherNavMeshData {anotherData, 1}; + + const auto value = cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, + std::move(mNavMeshData)); + EXPECT_FALSE(cache.set(mAgentHalfExtents, mTilePosition, anotherRecastMesh, mOffMeshConnections, + std::move(anotherNavMeshData))); + } + + TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_should_replace_unused_least_recently_set_value) + { + const std::size_t maxSize = 2; + NavMeshTilesCache cache(maxSize); + + const std::vector leastRecentlySetWater {1, RecastMesh::Water {1, btTransform::getIdentity()}}; + const RecastMesh leastRecentlySetRecastMesh {mIndices, mVertices, mAreaTypes, leastRecentlySetWater, + mTrianglesPerChunk}; + const auto leastRecentlySetData = reinterpret_cast(dtAlloc(1, DT_ALLOC_PERM)); + NavMeshData leastRecentlySetNavMeshData {leastRecentlySetData, 1}; + + const std::vector mostRecentlySetWater {1, RecastMesh::Water {2, btTransform::getIdentity()}}; + const RecastMesh mostRecentlySetRecastMesh {mIndices, mVertices, mAreaTypes, mostRecentlySetWater, + mTrianglesPerChunk}; + const auto mostRecentlySetData = reinterpret_cast(dtAlloc(1, DT_ALLOC_PERM)); + NavMeshData mostRecentlySetNavMeshData {mostRecentlySetData, 1}; + + ASSERT_TRUE(cache.set(mAgentHalfExtents, mTilePosition, leastRecentlySetRecastMesh, mOffMeshConnections, + std::move(leastRecentlySetNavMeshData))); + ASSERT_TRUE(cache.set(mAgentHalfExtents, mTilePosition, mostRecentlySetRecastMesh, mOffMeshConnections, + std::move(mostRecentlySetNavMeshData))); + + const auto result = cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, + std::move(mNavMeshData)); + EXPECT_EQ(result.get(), (NavMeshDataRef {mData, 1})); + + EXPECT_FALSE(cache.get(mAgentHalfExtents, mTilePosition, leastRecentlySetRecastMesh, mOffMeshConnections)); + EXPECT_TRUE(cache.get(mAgentHalfExtents, mTilePosition, mostRecentlySetRecastMesh, mOffMeshConnections)); + } + + TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_should_replace_unused_least_recently_used_value) + { + const std::size_t maxSize = 2; + NavMeshTilesCache cache(maxSize); + + const std::vector leastRecentlyUsedWater {1, RecastMesh::Water {1, btTransform::getIdentity()}}; + const RecastMesh leastRecentlyUsedRecastMesh {mIndices, mVertices, mAreaTypes, leastRecentlyUsedWater, + mTrianglesPerChunk}; + const auto leastRecentlyUsedData = reinterpret_cast(dtAlloc(1, DT_ALLOC_PERM)); + NavMeshData leastRecentlyUsedNavMeshData {leastRecentlyUsedData, 1}; + + const std::vector mostRecentlyUsedWater {1, RecastMesh::Water {2, btTransform::getIdentity()}}; + const RecastMesh mostRecentlyUsedRecastMesh {mIndices, mVertices, mAreaTypes, mostRecentlyUsedWater, + mTrianglesPerChunk}; + const auto mostRecentlyUsedData = reinterpret_cast(dtAlloc(1, DT_ALLOC_PERM)); + NavMeshData mostRecentlyUsedNavMeshData {mostRecentlyUsedData, 1}; + + cache.set(mAgentHalfExtents, mTilePosition, leastRecentlyUsedRecastMesh, mOffMeshConnections, + std::move(leastRecentlyUsedNavMeshData)); + cache.set(mAgentHalfExtents, mTilePosition, mostRecentlyUsedRecastMesh, mOffMeshConnections, + std::move(mostRecentlyUsedNavMeshData)); + + { + const auto value = cache.get(mAgentHalfExtents, mTilePosition, leastRecentlyUsedRecastMesh, mOffMeshConnections); + ASSERT_TRUE(value); + ASSERT_EQ(value.get(), (NavMeshDataRef {leastRecentlyUsedData, 1})); + } + + { + const auto value = cache.get(mAgentHalfExtents, mTilePosition, mostRecentlyUsedRecastMesh, mOffMeshConnections); + ASSERT_TRUE(value); + ASSERT_EQ(value.get(), (NavMeshDataRef {mostRecentlyUsedData, 1})); + } + + const auto result = cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, + std::move(mNavMeshData)); + EXPECT_EQ(result.get(), (NavMeshDataRef {mData, 1})); + + EXPECT_FALSE(cache.get(mAgentHalfExtents, mTilePosition, leastRecentlyUsedRecastMesh, mOffMeshConnections)); + EXPECT_TRUE(cache.get(mAgentHalfExtents, mTilePosition, mostRecentlyUsedRecastMesh, mOffMeshConnections)); + } + + TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_should_not_replace_unused_least_recently_used_value_when_item_does_not_not_fit_cache_max_size) + { + const std::size_t maxSize = 1; + NavMeshTilesCache cache(maxSize); + + const std::vector water {1, RecastMesh::Water {1, btTransform::getIdentity()}}; + const RecastMesh tooLargeRecastMesh {mIndices, mVertices, mAreaTypes, water, mTrianglesPerChunk}; + const auto tooLargeData = reinterpret_cast(dtAlloc(2, DT_ALLOC_PERM)); + NavMeshData tooLargeNavMeshData {tooLargeData, 2}; + + cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData)); + EXPECT_FALSE(cache.set(mAgentHalfExtents, mTilePosition, tooLargeRecastMesh, mOffMeshConnections, + std::move(tooLargeNavMeshData))); + EXPECT_TRUE(cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections)); + } + + TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_should_not_replace_unused_least_recently_used_value_when_item_does_not_not_fit_size_of_unused_items) + { + const std::size_t maxSize = 2; + NavMeshTilesCache cache(maxSize); + + const std::vector anotherWater {1, RecastMesh::Water {1, btTransform::getIdentity()}}; + const RecastMesh anotherRecastMesh {mIndices, mVertices, mAreaTypes, anotherWater, mTrianglesPerChunk}; + const auto anotherData = reinterpret_cast(dtAlloc(1, DT_ALLOC_PERM)); + NavMeshData anotherNavMeshData {anotherData, 1}; + + const std::vector tooLargeWater {1, RecastMesh::Water {2, btTransform::getIdentity()}}; + const RecastMesh tooLargeRecastMesh {mIndices, mVertices, mAreaTypes, tooLargeWater, mTrianglesPerChunk}; + const auto tooLargeData = reinterpret_cast(dtAlloc(2, DT_ALLOC_PERM)); + NavMeshData tooLargeNavMeshData {tooLargeData, 2}; + + const auto value = cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, + std::move(mNavMeshData)); + ASSERT_TRUE(value); + ASSERT_TRUE(cache.set(mAgentHalfExtents, mTilePosition, anotherRecastMesh, mOffMeshConnections, + std::move(anotherNavMeshData))); + EXPECT_FALSE(cache.set(mAgentHalfExtents, mTilePosition, tooLargeRecastMesh, mOffMeshConnections, + std::move(tooLargeNavMeshData))); + EXPECT_TRUE(cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections)); + EXPECT_TRUE(cache.get(mAgentHalfExtents, mTilePosition, anotherRecastMesh, mOffMeshConnections)); + } + + TEST_F(DetourNavigatorNavMeshTilesCacheTest, release_used_after_set_then_used_by_get_item_should_left_this_item_available) + { + const std::size_t maxSize = 1; + NavMeshTilesCache cache(maxSize); + + const std::vector water {1, RecastMesh::Water {1, btTransform::getIdentity()}}; + const RecastMesh anotherRecastMesh {mIndices, mVertices, mAreaTypes, water, mTrianglesPerChunk}; + const auto anotherData = reinterpret_cast(dtAlloc(1, DT_ALLOC_PERM)); + NavMeshData anotherNavMeshData {anotherData, 1}; + + const auto firstCopy = cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData)); + ASSERT_TRUE(firstCopy); + { + const auto secondCopy = cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections); + ASSERT_TRUE(secondCopy); + } + EXPECT_FALSE(cache.set(mAgentHalfExtents, mTilePosition, anotherRecastMesh, mOffMeshConnections, + std::move(anotherNavMeshData))); + EXPECT_TRUE(cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections)); + } + + TEST_F(DetourNavigatorNavMeshTilesCacheTest, release_twice_used_item_should_left_this_item_available) + { + const std::size_t maxSize = 1; + NavMeshTilesCache cache(maxSize); + + const std::vector water {1, RecastMesh::Water {1, btTransform::getIdentity()}}; + const RecastMesh anotherRecastMesh {mIndices, mVertices, mAreaTypes, water, mTrianglesPerChunk}; + const auto anotherData = reinterpret_cast(dtAlloc(1, DT_ALLOC_PERM)); + NavMeshData anotherNavMeshData {anotherData, 1}; + + cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData)); + const auto firstCopy = cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections); + ASSERT_TRUE(firstCopy); + { + const auto secondCopy = cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections); + ASSERT_TRUE(secondCopy); + } + EXPECT_FALSE(cache.set(mAgentHalfExtents, mTilePosition, anotherRecastMesh, mOffMeshConnections, + std::move(anotherNavMeshData))); + EXPECT_TRUE(cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections)); + } +} diff --git a/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp b/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp index ffef6114c..1bf1163c7 100644 --- a/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp +++ b/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp @@ -56,6 +56,7 @@ namespace btTriangleMesh mesh; mesh.addTriangle(btVector3(-1, -1, 0), btVector3(-1, 1, 0), btVector3(1, -1, 0)); btBvhTriangleMeshShape shape(&mesh, true); + RecastMeshBuilder builder(mSettings, mBounds); builder.addObject(static_cast(shape), btTransform::getIdentity(), AreaType_ground); const auto recastMesh = builder.create(); diff --git a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp index ceb035db8..de1a6a784 100644 --- a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp +++ b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp @@ -268,7 +268,8 @@ namespace value.target = Nif::ControlledPtr(nullptr); } - void copy(const btTransform& src, Nif::Transformation& dst) { + void copy(const btTransform& src, Nif::Transformation& dst) + { dst.pos = osg::Vec3f(src.getOrigin().x(), src.getOrigin().y(), src.getOrigin().z()); for (int row = 0; row < 3; ++row) for (int column = 0; column < 3; ++column) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index cf45d3212..e70784f4b 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -170,6 +170,7 @@ add_component_dir(detournavigator recastmesh tilecachedrecastmeshmanager recastmeshobject + navmeshtilescache ) set (ESM_UI ${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index a61412a2a..c8930e6b9 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -52,6 +52,7 @@ namespace DetourNavigator , mRecastMeshManager(recastMeshManager) , mOffMeshConnectionsManager(offMeshConnectionsManager) , mShouldStop() + , mNavMeshTilesCache(settings.mMaxNavMeshTilesCacheSize) { for (std::size_t i = 0; i < mSettings.get().mAsyncNavMeshUpdaterThreads; ++i) mThreads.emplace_back([&] { process(); }); @@ -69,7 +70,7 @@ namespace DetourNavigator } void AsyncNavMeshUpdater::post(const osg::Vec3f& agentHalfExtents, - const std::shared_ptr& navMeshCacheItem, const TilePosition& playerTile, + const SharedNavMeshCacheItem& navMeshCacheItem, const TilePosition& playerTile, const std::map& changedTiles) { *mPlayerTile.lock() = playerTile; @@ -129,7 +130,7 @@ namespace DetourNavigator const auto offMeshConnections = mOffMeshConnectionsManager.get().get(job.mChangedTile); const auto status = updateNavMesh(job.mAgentHalfExtents, recastMesh.get(), job.mChangedTile, playerTile, - offMeshConnections, mSettings, *job.mNavMeshCacheItem); + offMeshConnections, mSettings, job.mNavMeshCacheItem, mNavMeshTilesCache); const auto finish = std::chrono::steady_clock::now(); @@ -137,9 +138,10 @@ namespace DetourNavigator using FloatMs = std::chrono::duration; + const auto locked = job.mNavMeshCacheItem.lockConst(); log("cache updated for agent=", job.mAgentHalfExtents, " status=", status, - " generation=", job.mNavMeshCacheItem->mGeneration, - " revision=", job.mNavMeshCacheItem->mNavMeshRevision, + " generation=", locked->getGeneration(), + " revision=", locked->getNavMeshRevision(), " time=", std::chrono::duration_cast(finish - start).count(), "ms", " total_time=", std::chrono::duration_cast(finish - firstStart).count(), "ms"); } @@ -151,6 +153,7 @@ namespace DetourNavigator mHasJob.wait_for(lock, std::chrono::milliseconds(10)); if (mJobs.empty()) { + mFirstStart.lock()->reset(); mDone.notify_all(); return boost::none; } @@ -183,7 +186,7 @@ namespace DetourNavigator writeToFile(*recastMesh, mSettings.get().mRecastMeshPathPrefix + std::to_string(job.mChangedTile.x()) + "_" + std::to_string(job.mChangedTile.y()) + "_", recastMeshRevision); if (mSettings.get().mEnableWriteNavMeshToFile) - writeToFile(*job.mNavMeshCacheItem->mValue.lock(), mSettings.get().mNavMeshPathPrefix, navMeshRevision); + writeToFile(job.mNavMeshCacheItem.lockConst()->getValue(), mSettings.get().mNavMeshPathPrefix, navMeshRevision); } std::chrono::steady_clock::time_point AsyncNavMeshUpdater::setFirstStart(const std::chrono::steady_clock::time_point& value) diff --git a/components/detournavigator/asyncnavmeshupdater.hpp b/components/detournavigator/asyncnavmeshupdater.hpp index 28d1952a7..c9d7b1940 100644 --- a/components/detournavigator/asyncnavmeshupdater.hpp +++ b/components/detournavigator/asyncnavmeshupdater.hpp @@ -5,6 +5,7 @@ #include "offmeshconnectionsmanager.hpp" #include "tilecachedrecastmeshmanager.hpp" #include "tileposition.hpp" +#include "navmeshtilescache.hpp" #include @@ -37,7 +38,7 @@ namespace DetourNavigator OffMeshConnectionsManager& offMeshConnectionsManager); ~AsyncNavMeshUpdater(); - void post(const osg::Vec3f& agentHalfExtents, const std::shared_ptr& mNavMeshCacheItem, + void post(const osg::Vec3f& agentHalfExtents, const SharedNavMeshCacheItem& mNavMeshCacheItem, const TilePosition& playerTile, const std::map& changedTiles); void wait(); @@ -46,7 +47,7 @@ namespace DetourNavigator struct Job { osg::Vec3f mAgentHalfExtents; - std::shared_ptr mNavMeshCacheItem; + SharedNavMeshCacheItem mNavMeshCacheItem; TilePosition mChangedTile; std::tuple mPriority; @@ -69,6 +70,7 @@ namespace DetourNavigator std::map> mPushed; Misc::ScopeGuarded mPlayerTile; Misc::ScopeGuarded> mFirstStart; + NavMeshTilesCache mNavMeshTilesCache; std::vector mThreads; void process() throw(); diff --git a/components/detournavigator/makenavmesh.cpp b/components/detournavigator/makenavmesh.cpp index 07f222504..cf6827b64 100644 --- a/components/detournavigator/makenavmesh.cpp +++ b/components/detournavigator/makenavmesh.cpp @@ -9,6 +9,7 @@ #include "sharednavmesh.hpp" #include "settingsutils.hpp" #include "flags.hpp" +#include "navmeshtilescache.hpp" #include #include @@ -23,6 +24,8 @@ namespace { using namespace DetourNavigator; + static const int doNotTransferOwnership = 0; + void initPolyMeshDetail(rcPolyMeshDetail& value) { value.meshes = nullptr; @@ -42,29 +45,6 @@ namespace using PolyMeshDetailStackPtr = std::unique_ptr; - struct NavMeshDataValueDeleter - { - void operator ()(unsigned char* value) const - { - dtFree(value); - } - }; - - using NavMeshDataValue = std::unique_ptr; - - struct NavMeshData - { - NavMeshDataValue mValue; - int mSize; - - NavMeshData() = default; - - NavMeshData(unsigned char* value, int size) - : mValue(value) - , mSize(size) - {} - }; - osg::Vec3f makeOsgVec3f(const btVector3& value) { return osg::Vec3f(value.x(), value.y(), value.z()); @@ -388,7 +368,7 @@ namespace DetourNavigator UpdateNavMeshStatus updateNavMesh(const osg::Vec3f& agentHalfExtents, const RecastMesh* recastMesh, const TilePosition& changedTile, const TilePosition& playerTile, const std::vector& offMeshConnections, const Settings& settings, - NavMeshCacheItem& navMeshCacheItem) + const SharedNavMeshCacheItem& navMeshCacheItem, NavMeshTilesCache& navMeshTilesCache) { log("update NavMesh with mutiple tiles:", " agentHeight=", std::setprecision(std::numeric_limits::max_exponent10), @@ -401,17 +381,19 @@ namespace DetourNavigator " playerTile=", playerTile, " changedTileDistance=", getDistance(changedTile, playerTile)); - auto& navMesh = navMeshCacheItem.mValue; - const auto& params = *navMesh.lock()->getParams(); + const auto params = *navMeshCacheItem.lockConst()->getValue().getParams(); const osg::Vec3f origin(params.orig[0], params.orig[1], params.orig[2]); const auto x = changedTile.x(); const auto y = changedTile.y(); const auto removeTile = [&] { - const auto locked = navMesh.lock(); - const auto removed = dtStatusSucceed(locked->removeTile(locked->getTileRefAt(x, y, 0), nullptr, nullptr)); - navMeshCacheItem.mNavMeshRevision += removed; + const auto locked = navMeshCacheItem.lock(); + auto& navMesh = locked->getValue(); + const auto tileRef = navMesh.getTileRefAt(x, y, 0); + const auto removed = dtStatusSucceed(navMesh.removeTile(tileRef, nullptr, nullptr)); + if (removed) + locked->removeUsedTile(changedTile); return makeUpdateNavMeshStatus(removed, false); }; @@ -437,42 +419,82 @@ namespace DetourNavigator return removeTile(); } - const auto maxTiles = navMesh.lock()->getParams()->maxTiles; - if (!shouldAddTile(changedTile, playerTile, maxTiles)) + if (!shouldAddTile(changedTile, playerTile, params.maxTiles)) { log("ignore add tile: too far from player"); return removeTile(); } - const auto tileBounds = makeTileBounds(settings, changedTile); - const osg::Vec3f tileBorderMin(tileBounds.mMin.x(), boundsMin.y() - 1, tileBounds.mMin.y()); - const osg::Vec3f tileBorderMax(tileBounds.mMax.x(), boundsMax.y() + 1, tileBounds.mMax.y()); + auto cachedNavMeshData = navMeshTilesCache.get(agentHalfExtents, changedTile, *recastMesh, offMeshConnections); - auto navMeshData = makeNavMeshTileData(agentHalfExtents, *recastMesh, offMeshConnections, x, y, - tileBorderMin, tileBorderMax, settings); - - if (!navMeshData.mValue) + if (!cachedNavMeshData) { - log("ignore add tile: NavMeshData is null"); - return removeTile(); + const auto tileBounds = makeTileBounds(settings, changedTile); + const osg::Vec3f tileBorderMin(tileBounds.mMin.x(), boundsMin.y() - 1, tileBounds.mMin.y()); + const osg::Vec3f tileBorderMax(tileBounds.mMax.x(), boundsMax.y() + 1, tileBounds.mMax.y()); + + auto navMeshData = makeNavMeshTileData(agentHalfExtents, *recastMesh, offMeshConnections, x, y, + tileBorderMin, tileBorderMax, settings); + + if (!navMeshData.mValue) + { + log("ignore add tile: NavMeshData is null"); + return removeTile(); + } + + try + { + cachedNavMeshData = navMeshTilesCache.set(agentHalfExtents, changedTile, *recastMesh, + offMeshConnections, std::move(navMeshData)); + } + catch (const InvalidArgument&) + { + cachedNavMeshData = navMeshTilesCache.get(agentHalfExtents, changedTile, *recastMesh, + offMeshConnections); + } + + if (!cachedNavMeshData) + { + log("cache overflow"); + + const auto locked = navMeshCacheItem.lock(); + auto& navMesh = locked->getValue(); + const auto tileRef = navMesh.getTileRefAt(x, y, 0); + const auto removed = dtStatusSucceed(navMesh.removeTile(tileRef, nullptr, nullptr)); + const auto addStatus = navMesh.addTile(navMeshData.mValue.get(), navMeshData.mSize, + doNotTransferOwnership, 0, 0); + + if (dtStatusSucceed(addStatus)) + { + locked->setUsedTile(changedTile, std::move(navMeshData)); + return makeUpdateNavMeshStatus(removed, true); + } + else + { + if (removed) + locked->removeUsedTile(changedTile); + log("failed to add tile with status=", WriteDtStatus {addStatus}); + return makeUpdateNavMeshStatus(removed, false); + } + } } - dtStatus addStatus; - bool removed; - { - const auto locked = navMesh.lock(); - removed = dtStatusSucceed(locked->removeTile(locked->getTileRefAt(x, y, 0), nullptr, nullptr)); - addStatus = locked->addTile(navMeshData.mValue.get(), navMeshData.mSize, DT_TILE_FREE_DATA, 0, 0); - } + const auto locked = navMeshCacheItem.lock(); + auto& navMesh = locked->getValue(); + const auto tileRef = navMesh.getTileRefAt(x, y, 0); + const auto removed = dtStatusSucceed(navMesh.removeTile(tileRef, nullptr, nullptr)); + const auto addStatus = navMesh.addTile(cachedNavMeshData.get().mValue, cachedNavMeshData.get().mSize, + doNotTransferOwnership, 0, 0); if (dtStatusSucceed(addStatus)) { - ++navMeshCacheItem.mNavMeshRevision; - navMeshData.mValue.release(); + locked->setUsedTile(changedTile, std::move(cachedNavMeshData)); return makeUpdateNavMeshStatus(removed, true); } else { + if (removed) + locked->removeUsedTile(changedTile); log("failed to add tile with status=", WriteDtStatus {addStatus}); return makeUpdateNavMeshStatus(removed, false); } diff --git a/components/detournavigator/makenavmesh.hpp b/components/detournavigator/makenavmesh.hpp index 963e0daa8..55d3e261c 100644 --- a/components/detournavigator/makenavmesh.hpp +++ b/components/detournavigator/makenavmesh.hpp @@ -7,6 +7,7 @@ #include "tileposition.hpp" #include "tilebounds.hpp" #include "sharednavmesh.hpp" +#include "navmeshtilescache.hpp" #include @@ -48,7 +49,7 @@ namespace DetourNavigator UpdateNavMeshStatus updateNavMesh(const osg::Vec3f& agentHalfExtents, const RecastMesh* recastMesh, const TilePosition& changedTile, const TilePosition& playerTile, const std::vector& offMeshConnections, const Settings& settings, - NavMeshCacheItem& navMeshCacheItem); + const SharedNavMeshCacheItem& navMeshCacheItem, NavMeshTilesCache& navMeshTilesCache); } #endif diff --git a/components/detournavigator/navigator.cpp b/components/detournavigator/navigator.cpp index d185ad02f..73537ff6f 100644 --- a/components/detournavigator/navigator.cpp +++ b/components/detournavigator/navigator.cpp @@ -122,7 +122,7 @@ namespace DetourNavigator mNavMeshManager.wait(); } - std::map> Navigator::getNavMeshes() const + std::map Navigator::getNavMeshes() const { return mNavMeshManager.getNavMeshes(); } diff --git a/components/detournavigator/navigator.hpp b/components/detournavigator/navigator.hpp index 9d7723b47..351e0f9f8 100644 --- a/components/detournavigator/navigator.hpp +++ b/components/detournavigator/navigator.hpp @@ -176,7 +176,7 @@ namespace DetourNavigator "out is not an OutputIterator" ); const auto navMesh = mNavMeshManager.getNavMesh(agentHalfExtents); - return findSmoothPath(*navMesh.lock(), toNavMeshCoordinates(mSettings, agentHalfExtents), + return findSmoothPath(navMesh.lock()->getValue(), toNavMeshCoordinates(mSettings, agentHalfExtents), toNavMeshCoordinates(mSettings, start), toNavMeshCoordinates(mSettings, end), includeFlags, mSettings, out); } @@ -185,7 +185,7 @@ namespace DetourNavigator * @brief getNavMeshes returns all current navmeshes * @return map of agent half extents to navmesh */ - std::map> getNavMeshes() const; + std::map getNavMeshes() const; const Settings& getSettings() const; diff --git a/components/detournavigator/navmeshcacheitem.hpp b/components/detournavigator/navmeshcacheitem.hpp index 537f6df5d..e64b9d138 100644 --- a/components/detournavigator/navmeshcacheitem.hpp +++ b/components/detournavigator/navmeshcacheitem.hpp @@ -2,20 +2,69 @@ #define OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHCACHEITEM_H #include "sharednavmesh.hpp" +#include "tileposition.hpp" +#include "navmeshtilescache.hpp" -#include +#include + +#include namespace DetourNavigator { - struct NavMeshCacheItem + class NavMeshCacheItem { - SharedNavMesh mValue; - std::size_t mGeneration; - std::atomic_size_t mNavMeshRevision; - + public: NavMeshCacheItem(const NavMeshPtr& value, std::size_t generation) - : mValue(value), mGeneration(generation), mNavMeshRevision(0) {} + : mValue(value), mGeneration(generation), mNavMeshRevision(0) + { + } + + const dtNavMesh& getValue() const + { + return *mValue; + } + + dtNavMesh& getValue() + { + return *mValue; + } + + std::size_t getGeneration() const + { + return mGeneration; + } + + std::size_t getNavMeshRevision() const + { + return mNavMeshRevision; + } + + void setUsedTile(const TilePosition& tilePosition, NavMeshTilesCache::Value value) + { + mUsedTiles[tilePosition] = std::make_pair(std::move(value), NavMeshData()); + ++mNavMeshRevision; + } + + void setUsedTile(const TilePosition& tilePosition, NavMeshData value) + { + mUsedTiles[tilePosition] = std::make_pair(NavMeshTilesCache::Value(), std::move(value)); + ++mNavMeshRevision; + } + + void removeUsedTile(const TilePosition& tilePosition) + { + mUsedTiles.erase(tilePosition); + ++mNavMeshRevision; + } + + private: + NavMeshPtr mValue; + std::size_t mGeneration; + std::size_t mNavMeshRevision; + std::map> mUsedTiles; }; + + using SharedNavMeshCacheItem = Misc::SharedGuarded; } #endif diff --git a/components/detournavigator/navmeshdata.hpp b/components/detournavigator/navmeshdata.hpp new file mode 100644 index 000000000..8ce79614b --- /dev/null +++ b/components/detournavigator/navmeshdata.hpp @@ -0,0 +1,35 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHDATA_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHDATA_H + +#include + +#include +#include + +namespace DetourNavigator +{ + struct NavMeshDataValueDeleter + { + void operator ()(unsigned char* value) const + { + dtFree(value); + } + }; + + using NavMeshDataValue = std::unique_ptr; + + struct NavMeshData + { + NavMeshDataValue mValue; + int mSize; + + NavMeshData() = default; + + NavMeshData(unsigned char* value, int size) + : mValue(value) + , mSize(size) + {} + }; +} + +#endif diff --git a/components/detournavigator/navmeshmanager.cpp b/components/detournavigator/navmeshmanager.cpp index 559aedebb..9afe105d9 100644 --- a/components/detournavigator/navmeshmanager.cpp +++ b/components/detournavigator/navmeshmanager.cpp @@ -136,11 +136,12 @@ namespace DetourNavigator const auto& cached = getCached(agentHalfExtents); const auto changedTiles = mChangedTiles.find(agentHalfExtents); { - const auto locked = cached->mValue.lock(); + const auto locked = cached.lock(); + const auto& navMesh = locked->getValue(); if (changedTiles != mChangedTiles.end()) { for (const auto& tile : changedTiles->second) - if (locked->getTileAt(tile.first.x(), tile.first.y(), 0)) + if (navMesh.getTileAt(tile.first.x(), tile.first.y(), 0)) { auto tileToPost = tilesToPost.find(tile.first); if (tileToPost == tilesToPost.end()) @@ -153,13 +154,13 @@ namespace DetourNavigator if (changedTiles->second.empty()) mChangedTiles.erase(changedTiles); } - const auto maxTiles = locked->getParams()->maxTiles; + const auto maxTiles = navMesh.getParams()->maxTiles; mRecastMeshManager.forEachTilePosition([&] (const TilePosition& tile) { if (tilesToPost.count(tile)) return; const auto shouldAdd = shouldAddTile(tile, playerTile, maxTiles); - const auto presentInNavMesh = bool(locked->getTileAt(tile.x(), tile.y(), 0)); + const auto presentInNavMesh = bool(navMesh.getTileAt(tile.x(), tile.y(), 0)); if (shouldAdd && !presentInNavMesh) tilesToPost.insert(std::make_pair(tile, ChangeType::add)); else if (!shouldAdd && presentInNavMesh) @@ -169,8 +170,7 @@ namespace DetourNavigator mAsyncNavMeshUpdater.post(agentHalfExtents, cached, playerTile, tilesToPost); log("cache update posted for agent=", agentHalfExtents, " playerTile=", lastPlayerTile->second, - " recastMeshManagerRevision=", lastRevision, - " changedTiles=", changedTiles->second.size()); + " recastMeshManagerRevision=", lastRevision); } void NavMeshManager::wait() @@ -178,12 +178,12 @@ namespace DetourNavigator mAsyncNavMeshUpdater.wait(); } - SharedNavMesh NavMeshManager::getNavMesh(const osg::Vec3f& agentHalfExtents) const + SharedNavMeshCacheItem NavMeshManager::getNavMesh(const osg::Vec3f& agentHalfExtents) const { - return getCached(agentHalfExtents)->mValue; + return getCached(agentHalfExtents); } - std::map> NavMeshManager::getNavMeshes() const + std::map NavMeshManager::getNavMeshes() const { return mCache; } @@ -209,19 +209,16 @@ namespace DetourNavigator { for (const auto& cached : mCache) { - if (cached.second) - { - auto& tiles = mChangedTiles[cached.first]; - auto tile = tiles.find(tilePosition); - if (tile == tiles.end()) - tiles.insert(std::make_pair(tilePosition, changeType)); - else - tile->second = addChangeType(tile->second, changeType); - } + auto& tiles = mChangedTiles[cached.first]; + auto tile = tiles.find(tilePosition); + if (tile == tiles.end()) + tiles.insert(std::make_pair(tilePosition, changeType)); + else + tile->second = addChangeType(tile->second, changeType); } } - const std::shared_ptr& NavMeshManager::getCached(const osg::Vec3f& agentHalfExtents) const + const SharedNavMeshCacheItem& NavMeshManager::getCached(const osg::Vec3f& agentHalfExtents) const { const auto cached = mCache.find(agentHalfExtents); if (cached != mCache.end()) diff --git a/components/detournavigator/navmeshmanager.hpp b/components/detournavigator/navmeshmanager.hpp index 86d9ea5d4..f44cdd251 100644 --- a/components/detournavigator/navmeshmanager.hpp +++ b/components/detournavigator/navmeshmanager.hpp @@ -46,17 +46,17 @@ namespace DetourNavigator void wait(); - SharedNavMesh getNavMesh(const osg::Vec3f& agentHalfExtents) const; + SharedNavMeshCacheItem getNavMesh(const osg::Vec3f& agentHalfExtents) const; - std::map> getNavMeshes() const; + std::map getNavMeshes() const; private: const Settings& mSettings; TileCachedRecastMeshManager mRecastMeshManager; OffMeshConnectionsManager mOffMeshConnectionsManager; - std::map> mCache; - std::map> mChangedTiles; AsyncNavMeshUpdater mAsyncNavMeshUpdater; + std::map mCache; + std::map> mChangedTiles; std::size_t mGenerationCounter = 0; std::map mPlayerTile; std::map mLastRecastMeshManagerRevision; @@ -67,7 +67,7 @@ namespace DetourNavigator void addChangedTile(const TilePosition& tilePosition, const ChangeType changeType); - const std::shared_ptr& getCached(const osg::Vec3f& agentHalfExtents) const; + const SharedNavMeshCacheItem& getCached(const osg::Vec3f& agentHalfExtents) const; }; } diff --git a/components/detournavigator/navmeshtilescache.cpp b/components/detournavigator/navmeshtilescache.cpp new file mode 100644 index 000000000..ff24b86f4 --- /dev/null +++ b/components/detournavigator/navmeshtilescache.cpp @@ -0,0 +1,158 @@ +#include "navmeshtilescache.hpp" +#include "exceptions.hpp" + +namespace DetourNavigator +{ + namespace + { + inline std::string makeNavMeshKey(const RecastMesh& recastMesh, + const std::vector& offMeshConnections) + { + std::string result; + result.reserve( + recastMesh.getIndices().size() * sizeof(int) + + recastMesh.getVertices().size() * sizeof(float) + + recastMesh.getAreaTypes().size() * sizeof(AreaType) + + recastMesh.getWater().size() * sizeof(RecastMesh::Water) + + offMeshConnections.size() * sizeof(OffMeshConnection) + ); + std::copy( + reinterpret_cast(recastMesh.getIndices().data()), + reinterpret_cast(recastMesh.getIndices().data() + recastMesh.getIndices().size()), + std::back_inserter(result) + ); + std::copy( + reinterpret_cast(recastMesh.getVertices().data()), + reinterpret_cast(recastMesh.getVertices().data() + recastMesh.getVertices().size()), + std::back_inserter(result) + ); + std::copy( + reinterpret_cast(recastMesh.getAreaTypes().data()), + reinterpret_cast(recastMesh.getAreaTypes().data() + recastMesh.getAreaTypes().size()), + std::back_inserter(result) + ); + std::copy( + reinterpret_cast(recastMesh.getWater().data()), + reinterpret_cast(recastMesh.getWater().data() + recastMesh.getWater().size()), + std::back_inserter(result) + ); + std::copy( + reinterpret_cast(offMeshConnections.data()), + reinterpret_cast(offMeshConnections.data() + offMeshConnections.size()), + std::back_inserter(result) + ); + return result; + } + } + + NavMeshTilesCache::NavMeshTilesCache(const std::size_t maxNavMeshDataSize) + : mMaxNavMeshDataSize(maxNavMeshDataSize), mUsedNavMeshDataSize(0), mFreeNavMeshDataSize(0) {} + + NavMeshTilesCache::Value NavMeshTilesCache::get(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile, + const RecastMesh& recastMesh, const std::vector& offMeshConnections) + { + const std::lock_guard lock(mMutex); + + const auto agentValues = mValues.find(agentHalfExtents); + if (agentValues == mValues.end()) + return Value(); + + const auto tileValues = agentValues->second.find(changedTile); + if (tileValues == agentValues->second.end()) + return Value(); + + const auto tile = tileValues->second.find(makeNavMeshKey(recastMesh, offMeshConnections)); + if (tile == tileValues->second.end()) + return Value(); + + acquireItemUnsafe(tile->second); + + return Value(*this, tile->second); + } + + NavMeshTilesCache::Value NavMeshTilesCache::set(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile, + const RecastMesh& recastMesh, const std::vector& offMeshConnections, + NavMeshData value) + { + const auto navMeshSize = static_cast(value.mSize); + + const std::lock_guard lock(mMutex); + + if (navMeshSize > mMaxNavMeshDataSize) + return Value(); + + if (navMeshSize > mFreeNavMeshDataSize + (mMaxNavMeshDataSize - mUsedNavMeshDataSize)) + return Value(); + + while (!mFreeItems.empty() && mUsedNavMeshDataSize + navMeshSize > mMaxNavMeshDataSize) + removeLeastRecentlyUsed(); + + const auto navMeshKey = makeNavMeshKey(recastMesh, offMeshConnections); + const auto iterator = mFreeItems.emplace(mFreeItems.end(), agentHalfExtents, changedTile, navMeshKey); + const auto emplaced = mValues[agentHalfExtents][changedTile].emplace(navMeshKey, iterator); + + if (!emplaced.second) + { + mFreeItems.erase(iterator); + throw InvalidArgument("Set existing cache value"); + } + + iterator->mNavMeshData = std::move(value); + mUsedNavMeshDataSize += navMeshSize; + mFreeNavMeshDataSize += navMeshSize; + + acquireItemUnsafe(iterator); + + return Value(*this, iterator); + } + + void NavMeshTilesCache::removeLeastRecentlyUsed() + { + const auto& item = mFreeItems.back(); + + const auto agentValues = mValues.find(item.mAgentHalfExtents); + if (agentValues == mValues.end()) + return; + + const auto tileValues = agentValues->second.find(item.mChangedTile); + if (tileValues == agentValues->second.end()) + return; + + const auto value = tileValues->second.find(item.mNavMeshKey); + if (value == tileValues->second.end()) + return; + + mUsedNavMeshDataSize -= static_cast(item.mNavMeshData.mSize); + mFreeItems.pop_back(); + + tileValues->second.erase(value); + if (!tileValues->second.empty()) + return; + + agentValues->second.erase(tileValues); + if (!agentValues->second.empty()) + return; + + mValues.erase(agentValues); + } + + void NavMeshTilesCache::acquireItemUnsafe(ItemIterator iterator) + { + if (++iterator->mUseCount > 1) + return; + + mBusyItems.splice(mBusyItems.end(), mFreeItems, iterator); + mFreeNavMeshDataSize -= static_cast(iterator->mNavMeshData.mSize); + } + + void NavMeshTilesCache::releaseItem(ItemIterator iterator) + { + if (--iterator->mUseCount > 0) + return; + + const std::lock_guard lock(mMutex); + + mFreeItems.splice(mFreeItems.begin(), mBusyItems, iterator); + mFreeNavMeshDataSize += static_cast(iterator->mNavMeshData.mSize); + } +} diff --git a/components/detournavigator/navmeshtilescache.hpp b/components/detournavigator/navmeshtilescache.hpp new file mode 100644 index 000000000..2ecd37bf4 --- /dev/null +++ b/components/detournavigator/navmeshtilescache.hpp @@ -0,0 +1,127 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHTILESCACHE_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHTILESCACHE_H + +#include "offmeshconnection.hpp" +#include "navmeshdata.hpp" +#include "recastmesh.hpp" +#include "tileposition.hpp" + +#include +#include +#include +#include + +namespace DetourNavigator +{ + struct NavMeshDataRef + { + unsigned char* mValue; + int mSize; + }; + + class NavMeshTilesCache + { + public: + struct Item + { + std::atomic mUseCount; + osg::Vec3f mAgentHalfExtents; + TilePosition mChangedTile; + std::string mNavMeshKey; + NavMeshData mNavMeshData; + + Item(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile, std::string navMeshKey) + : mUseCount(0) + , mAgentHalfExtents(agentHalfExtents) + , mChangedTile(changedTile) + , mNavMeshKey(std::move(navMeshKey)) + {} + }; + + using ItemIterator = std::list::iterator; + + class Value + { + public: + Value() + : mOwner(nullptr), mIterator() {} + + Value(NavMeshTilesCache& owner, ItemIterator iterator) + : mOwner(&owner), mIterator(iterator) + { + } + + Value(const Value& other) = delete; + + Value(Value&& other) + : mOwner(other.mOwner), mIterator(other.mIterator) + { + other.mIterator = ItemIterator(); + } + + ~Value() + { + if (mIterator != ItemIterator()) + mOwner->releaseItem(mIterator); + } + + Value& operator =(const Value& other) = delete; + + Value& operator =(Value&& other) + { + if (mIterator == other.mIterator) + return *this; + + if (mIterator != ItemIterator()) + mOwner->releaseItem(mIterator); + + mOwner = other.mOwner; + mIterator = other.mIterator; + + other.mIterator = ItemIterator(); + + return *this; + } + + NavMeshDataRef get() const + { + return NavMeshDataRef {mIterator->mNavMeshData.mValue.get(), mIterator->mNavMeshData.mSize}; + } + + operator bool() const + { + return mIterator != ItemIterator(); + } + + private: + NavMeshTilesCache* mOwner; + ItemIterator mIterator; + }; + + NavMeshTilesCache(const std::size_t maxNavMeshDataSize); + + Value get(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile, + const RecastMesh& recastMesh, const std::vector& offMeshConnections); + + Value set(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile, + const RecastMesh& recastMesh, const std::vector& offMeshConnections, + NavMeshData value); + + private: + std::mutex mMutex; + std::size_t mMaxNavMeshDataSize; + std::size_t mUsedNavMeshDataSize; + std::size_t mFreeNavMeshDataSize; + std::list mBusyItems; + std::list mFreeItems; + std::map>> mValues; + + void removeLeastRecentlyUsed(); + + void acquireItemUnsafe(ItemIterator iterator); + + void releaseItem(ItemIterator iterator); + }; +} + +#endif diff --git a/components/detournavigator/objectid.hpp b/components/detournavigator/objectid.hpp index cbb933ba0..3b56924b1 100644 --- a/components/detournavigator/objectid.hpp +++ b/components/detournavigator/objectid.hpp @@ -10,7 +10,7 @@ namespace DetourNavigator { public: template - explicit ObjectId(const T* value) throw() + explicit ObjectId(const T value) throw() : mValue(reinterpret_cast(value)) { } diff --git a/components/detournavigator/offmeshconnection.hpp b/components/detournavigator/offmeshconnection.hpp new file mode 100644 index 000000000..60e8ecbbb --- /dev/null +++ b/components/detournavigator/offmeshconnection.hpp @@ -0,0 +1,15 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_OFFMESHCONNECTION_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_OFFMESHCONNECTION_H + +#include + +namespace DetourNavigator +{ + struct OffMeshConnection + { + osg::Vec3f mStart; + osg::Vec3f mEnd; + }; +} + +#endif diff --git a/components/detournavigator/offmeshconnectionsmanager.hpp b/components/detournavigator/offmeshconnectionsmanager.hpp index 212f821c9..30d7976ae 100644 --- a/components/detournavigator/offmeshconnectionsmanager.hpp +++ b/components/detournavigator/offmeshconnectionsmanager.hpp @@ -5,6 +5,7 @@ #include "settingsutils.hpp" #include "tileposition.hpp" #include "objectid.hpp" +#include "offmeshconnection.hpp" #include @@ -20,12 +21,6 @@ namespace DetourNavigator { - struct OffMeshConnection - { - osg::Vec3f mStart; - osg::Vec3f mEnd; - }; - class OffMeshConnectionsManager { public: diff --git a/components/detournavigator/recastmesh.cpp b/components/detournavigator/recastmesh.cpp index b4897d14a..50a3493f7 100644 --- a/components/detournavigator/recastmesh.cpp +++ b/components/detournavigator/recastmesh.cpp @@ -1,18 +1,17 @@ #include "recastmesh.hpp" -#include "settings.hpp" #include "exceptions.hpp" #include namespace DetourNavigator { - RecastMesh::RecastMesh(std::vector indices, std::vector vertices, - std::vector areaTypes, std::vector water, const Settings& settings) + RecastMesh::RecastMesh(std::vector indices, std::vector vertices, std::vector areaTypes, + std::vector water, const std::size_t trianglesPerChunk) : mIndices(std::move(indices)) , mVertices(std::move(vertices)) , mAreaTypes(std::move(areaTypes)) , mWater(std::move(water)) - , mChunkyTriMesh(mVertices, mIndices, mAreaTypes, settings.mTrianglesPerChunk) + , mChunkyTriMesh(mVertices, mIndices, mAreaTypes, trianglesPerChunk) { if (getTrianglesCount() != mAreaTypes.size()) throw InvalidArgument("number of flags doesn't match number of triangles: triangles=" diff --git a/components/detournavigator/recastmesh.hpp b/components/detournavigator/recastmesh.hpp index 12dc73e48..b3e158aff 100644 --- a/components/detournavigator/recastmesh.hpp +++ b/components/detournavigator/recastmesh.hpp @@ -5,6 +5,7 @@ #include "chunkytrimesh.hpp" #include +#include #include #include @@ -13,8 +14,6 @@ namespace DetourNavigator { - struct Settings; - class RecastMesh { public: @@ -24,8 +23,8 @@ namespace DetourNavigator btTransform mTransform; }; - RecastMesh(std::vector indices, std::vector vertices, - std::vector areaTypes, std::vector water, const Settings& settings); + RecastMesh(std::vector indices, std::vector vertices, std::vector areaTypes, + std::vector water, const std::size_t trianglesPerChunk); const std::vector& getIndices() const { diff --git a/components/detournavigator/recastmeshbuilder.cpp b/components/detournavigator/recastmeshbuilder.cpp index 3e87aa34b..8ba3309ce 100644 --- a/components/detournavigator/recastmeshbuilder.cpp +++ b/components/detournavigator/recastmeshbuilder.cpp @@ -119,7 +119,7 @@ namespace DetourNavigator std::shared_ptr RecastMeshBuilder::create() const { - return std::make_shared(mIndices, mVertices, mAreaTypes, mWater, mSettings); + return std::make_shared(mIndices, mVertices, mAreaTypes, mWater, mSettings.get().mTrianglesPerChunk); } void RecastMeshBuilder::reset() diff --git a/components/detournavigator/recastmeshbuilder.hpp b/components/detournavigator/recastmeshbuilder.hpp index af0565cab..2f9d0373d 100644 --- a/components/detournavigator/recastmeshbuilder.hpp +++ b/components/detournavigator/recastmeshbuilder.hpp @@ -6,9 +6,6 @@ #include -#include -#include - class btBoxShape; class btCollisionShape; class btCompoundShape; @@ -18,6 +15,8 @@ class btTriangleCallback; namespace DetourNavigator { + struct Settings; + class RecastMeshBuilder { public: diff --git a/components/detournavigator/recastmeshmanager.cpp b/components/detournavigator/recastmeshmanager.cpp index f9f81de96..1c3a72b59 100644 --- a/components/detournavigator/recastmeshmanager.cpp +++ b/components/detournavigator/recastmeshmanager.cpp @@ -14,8 +14,12 @@ namespace DetourNavigator bool RecastMeshManager::addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform, const AreaType areaType) { - if (!mObjects.emplace(id, RecastMeshObject(shape, transform, areaType)).second) + const auto iterator = mObjectsOrder.emplace(mObjectsOrder.end(), RecastMeshObject(shape, transform, areaType)); + if (!mObjects.emplace(id, iterator).second) + { + mObjectsOrder.erase(iterator); return false; + } mShouldRebuild = true; return mShouldRebuild; } @@ -25,7 +29,7 @@ namespace DetourNavigator const auto object = mObjects.find(id); if (object == mObjects.end()) return false; - if (!object->second.update(transform, areaType)) + if (!object->second->update(transform, areaType)) return false; mShouldRebuild = true; return mShouldRebuild; @@ -36,7 +40,8 @@ namespace DetourNavigator const auto object = mObjects.find(id); if (object == mObjects.end()) return boost::none; - const RemovedRecastMeshObject result {object->second.getShape(), object->second.getTransform()}; + const RemovedRecastMeshObject result {object->second->getShape(), object->second->getTransform()}; + mObjectsOrder.erase(object->second); mObjects.erase(object); mShouldRebuild = true; return result; @@ -45,8 +50,12 @@ namespace DetourNavigator bool RecastMeshManager::addWater(const osg::Vec2i& cellPosition, const int cellSize, const btTransform& transform) { - if (!mWater.insert(std::make_pair(cellPosition, Water {cellSize, transform})).second) + const auto iterator = mWaterOrder.emplace(mWaterOrder.end(), Water {cellSize, transform}); + if (!mWater.emplace(cellPosition, iterator).second) + { + mWaterOrder.erase(iterator); return false; + } mShouldRebuild = true; return true; } @@ -57,7 +66,8 @@ namespace DetourNavigator if (water == mWater.end()) return boost::none; mShouldRebuild = true; - const auto result = water->second; + const auto result = *water->second; + mWaterOrder.erase(water->second); mWater.erase(water); return result; } @@ -78,10 +88,10 @@ namespace DetourNavigator if (!mShouldRebuild) return; mMeshBuilder.reset(); - for (const auto& v : mWater) - mMeshBuilder.addWater(v.second.mCellSize, v.second.mTransform); - for (const auto& v : mObjects) - mMeshBuilder.addObject(v.second.getShape(), v.second.getTransform(), v.second.getAreaType()); + for (const auto& v : mWaterOrder) + mMeshBuilder.addWater(v.mCellSize, v.mTransform); + for (const auto& v : mObjectsOrder) + mMeshBuilder.addObject(v.getShape(), v.getTransform(), v.getAreaType()); mShouldRebuild = false; } } diff --git a/components/detournavigator/recastmeshmanager.hpp b/components/detournavigator/recastmeshmanager.hpp index be0fc3581..f8602e514 100644 --- a/components/detournavigator/recastmeshmanager.hpp +++ b/components/detournavigator/recastmeshmanager.hpp @@ -13,6 +13,7 @@ #include #include +#include class btCollisionShape; @@ -53,8 +54,10 @@ namespace DetourNavigator private: bool mShouldRebuild; RecastMeshBuilder mMeshBuilder; - std::unordered_map mObjects; - std::map mWater; + std::list mObjectsOrder; + std::unordered_map::iterator> mObjects; + std::list mWaterOrder; + std::map::iterator> mWater; void rebuild(); }; diff --git a/components/detournavigator/recastmeshobject.cpp b/components/detournavigator/recastmeshobject.cpp index 1aac9fd81..acaf398c1 100644 --- a/components/detournavigator/recastmeshobject.cpp +++ b/components/detournavigator/recastmeshobject.cpp @@ -4,6 +4,7 @@ #include +#include #include namespace DetourNavigator diff --git a/components/detournavigator/settings.hpp b/components/detournavigator/settings.hpp index 9f6ca97be..f2eb2be24 100644 --- a/components/detournavigator/settings.hpp +++ b/components/detournavigator/settings.hpp @@ -28,6 +28,7 @@ namespace DetourNavigator int mRegionMinSize; int mTileSize; std::size_t mAsyncNavMeshUpdaterThreads; + std::size_t mMaxNavMeshTilesCacheSize; std::size_t mMaxPolygonPathSize; std::size_t mMaxSmoothPathSize; std::size_t mTrianglesPerChunk; diff --git a/components/detournavigator/sharednavmesh.hpp b/components/detournavigator/sharednavmesh.hpp index 8045434a1..beb0a3c41 100644 --- a/components/detournavigator/sharednavmesh.hpp +++ b/components/detournavigator/sharednavmesh.hpp @@ -1,9 +1,6 @@ #ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_SHAREDNAVMESH_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_SHAREDNAVMESH_H -#include - -#include #include class dtNavMesh; @@ -11,7 +8,6 @@ class dtNavMesh; namespace DetourNavigator { using NavMeshPtr = std::shared_ptr; - using SharedNavMesh = Misc::SharedGuarded; } #endif diff --git a/components/misc/guarded.hpp b/components/misc/guarded.hpp index 841b9b812..db619569a 100644 --- a/components/misc/guarded.hpp +++ b/components/misc/guarded.hpp @@ -48,6 +48,21 @@ namespace Misc : mValue(std::move(value)) {} + template + ScopeGuarded(Args&& ... args) + : mValue(std::forward(args) ...) + {} + + ScopeGuarded(const ScopeGuarded& other) + : mMutex() + , mValue(other.lock().get()) + {} + + ScopeGuarded(ScopeGuarded&& other) + : mMutex() + , mValue(std::move(other.lock().get())) + {} + Locked lock() { return Locked(mMutex, mValue); diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 991e9fff5..9c1c92695 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -127,7 +127,9 @@ osg::ref_ptr BulletNifLoader::load(const Nif::File& nif) { btTransform trans; trans.setIdentity(); - mCompoundShape->addChildShape(trans, new Resource::TriangleMeshShape(mStaticMesh.get(), true)); + std::unique_ptr child(new Resource::TriangleMeshShape(mStaticMesh.get(), true)); + mCompoundShape->addChildShape(trans, child.get()); + child.release(); mStaticMesh.release(); } mShape->mCollisionShape = mCompoundShape.release(); @@ -139,7 +141,10 @@ osg::ref_ptr BulletNifLoader::load(const Nif::File& nif) } if (mAvoidStaticMesh) - mShape->mAvoidCollisionShape = new Resource::TriangleMeshShape(mAvoidStaticMesh.release(), false); + { + mShape->mAvoidCollisionShape = new Resource::TriangleMeshShape(mAvoidStaticMesh.get(), false); + mAvoidStaticMesh.release(); + } return mShape; } diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 29333682e..a76eaf1d6 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -588,6 +588,9 @@ region min size = 8 # Number of background threads to update nav mesh (value >= 1) async nav mesh updater threads = 1 +# Maximum total cached size of all nav mesh tiles in bytes (value >= 0) +max nav mesh tiles cache size = 268435456 + # Maximum size of path over polygons (value > 0) max polygon path size = 1024