1
0
mirror of https://gitlab.com/OpenMW/openmw.git synced 2025-03-03 04:13:24 +00:00

Cache navmesh tiles

Use LRU modification to hold currently used items. Use RecastMesh binary
data for item key.

Store original pointer of btCollisionShape in user pointer to make available
it as an identifier within all duplicates. Use pointer to heights data array
for btHeightfieldTerrainShape.
This commit is contained in:
elsid 2018-10-01 01:33:25 +03:00
parent 69b5834c64
commit ed73d130f9
No known key found for this signature in database
GPG Key ID: B845CB9FEE18AB40
37 changed files with 942 additions and 135 deletions

View File

@ -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);

View File

@ -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();

View File

@ -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)
{

View File

@ -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<std::size_t>(Settings::Manager::getInt("async nav mesh updater threads", "Navigator"));
navigatorSettings.mMaxNavMeshTilesCacheSize = static_cast<std::size_t>(Settings::Manager::getInt("max nav mesh tiles cache size", "Navigator"));
navigatorSettings.mMaxPolygonPathSize = static_cast<std::size_t>(Settings::Manager::getInt("max polygon path size", "Navigator"));
navigatorSettings.mMaxSmoothPathSize = static_cast<std::size_t>(Settings::Manager::getInt("max smooth path size", "Navigator"));
navigatorSettings.mTrianglesPerChunk = static_cast<std::size_t>(Settings::Manager::getInt("triangles per chunk", "Navigator"));

View File

@ -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})

View File

@ -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<btScalar, 5 * 5> 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>({
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;
}
}

View File

@ -0,0 +1,312 @@
#include "operators.hpp"
#include <components/detournavigator/navmeshtilescache.hpp>
#include <components/detournavigator/exceptions.hpp>
#include <components/detournavigator/recastmesh.hpp>
#include <LinearMath/btTransform.h>
#include <gtest/gtest.h>
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<int> mIndices {{0, 1, 2}};
const std::vector<float> mVertices {{0, 0, 0, 1, 0, 0, 1, 1, 0}};
const std::vector<AreaType> mAreaTypes {1, AreaType_ground};
const std::vector<RecastMesh::Water> mWater {};
const std::size_t mTrianglesPerChunk {1};
const RecastMesh mRecastMesh {mIndices, mVertices, mAreaTypes, mWater, mTrianglesPerChunk};
const std::vector<OffMeshConnection> mOffMeshConnections {};
unsigned char* const mData = reinterpret_cast<unsigned char*>(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<unsigned char*>(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<RecastMesh::Water> 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<RecastMesh::Water> water {1, RecastMesh::Water {1, btTransform::getIdentity()}};
const RecastMesh anotherRecastMesh {mIndices, mVertices, mAreaTypes, water, mTrianglesPerChunk};
const auto anotherData = reinterpret_cast<unsigned char*>(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<RecastMesh::Water> water {1, RecastMesh::Water {1, btTransform::getIdentity()}};
const RecastMesh anotherRecastMesh {mIndices, mVertices, mAreaTypes, water, mTrianglesPerChunk};
const auto anotherData = reinterpret_cast<unsigned char*>(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<RecastMesh::Water> leastRecentlySetWater {1, RecastMesh::Water {1, btTransform::getIdentity()}};
const RecastMesh leastRecentlySetRecastMesh {mIndices, mVertices, mAreaTypes, leastRecentlySetWater,
mTrianglesPerChunk};
const auto leastRecentlySetData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM));
NavMeshData leastRecentlySetNavMeshData {leastRecentlySetData, 1};
const std::vector<RecastMesh::Water> mostRecentlySetWater {1, RecastMesh::Water {2, btTransform::getIdentity()}};
const RecastMesh mostRecentlySetRecastMesh {mIndices, mVertices, mAreaTypes, mostRecentlySetWater,
mTrianglesPerChunk};
const auto mostRecentlySetData = reinterpret_cast<unsigned char*>(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<RecastMesh::Water> leastRecentlyUsedWater {1, RecastMesh::Water {1, btTransform::getIdentity()}};
const RecastMesh leastRecentlyUsedRecastMesh {mIndices, mVertices, mAreaTypes, leastRecentlyUsedWater,
mTrianglesPerChunk};
const auto leastRecentlyUsedData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM));
NavMeshData leastRecentlyUsedNavMeshData {leastRecentlyUsedData, 1};
const std::vector<RecastMesh::Water> mostRecentlyUsedWater {1, RecastMesh::Water {2, btTransform::getIdentity()}};
const RecastMesh mostRecentlyUsedRecastMesh {mIndices, mVertices, mAreaTypes, mostRecentlyUsedWater,
mTrianglesPerChunk};
const auto mostRecentlyUsedData = reinterpret_cast<unsigned char*>(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<RecastMesh::Water> water {1, RecastMesh::Water {1, btTransform::getIdentity()}};
const RecastMesh tooLargeRecastMesh {mIndices, mVertices, mAreaTypes, water, mTrianglesPerChunk};
const auto tooLargeData = reinterpret_cast<unsigned char*>(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<RecastMesh::Water> anotherWater {1, RecastMesh::Water {1, btTransform::getIdentity()}};
const RecastMesh anotherRecastMesh {mIndices, mVertices, mAreaTypes, anotherWater, mTrianglesPerChunk};
const auto anotherData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM));
NavMeshData anotherNavMeshData {anotherData, 1};
const std::vector<RecastMesh::Water> tooLargeWater {1, RecastMesh::Water {2, btTransform::getIdentity()}};
const RecastMesh tooLargeRecastMesh {mIndices, mVertices, mAreaTypes, tooLargeWater, mTrianglesPerChunk};
const auto tooLargeData = reinterpret_cast<unsigned char*>(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<RecastMesh::Water> water {1, RecastMesh::Water {1, btTransform::getIdentity()}};
const RecastMesh anotherRecastMesh {mIndices, mVertices, mAreaTypes, water, mTrianglesPerChunk};
const auto anotherData = reinterpret_cast<unsigned char*>(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<RecastMesh::Water> water {1, RecastMesh::Water {1, btTransform::getIdentity()}};
const RecastMesh anotherRecastMesh {mIndices, mVertices, mAreaTypes, water, mTrianglesPerChunk};
const auto anotherData = reinterpret_cast<unsigned char*>(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));
}
}

View File

@ -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<const btCollisionShape&>(shape), btTransform::getIdentity(), AreaType_ground);
const auto recastMesh = builder.create();

View File

@ -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)

View File

@ -170,6 +170,7 @@ add_component_dir(detournavigator
recastmesh
tilecachedrecastmeshmanager
recastmeshobject
navmeshtilescache
)
set (ESM_UI ${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui

View File

@ -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>& navMeshCacheItem, const TilePosition& playerTile,
const SharedNavMeshCacheItem& navMeshCacheItem, const TilePosition& playerTile,
const std::map<TilePosition, ChangeType>& 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<float, std::milli>;
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<FloatMs>(finish - start).count(), "ms",
" total_time=", std::chrono::duration_cast<FloatMs>(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)

View File

@ -5,6 +5,7 @@
#include "offmeshconnectionsmanager.hpp"
#include "tilecachedrecastmeshmanager.hpp"
#include "tileposition.hpp"
#include "navmeshtilescache.hpp"
#include <osg/Vec3f>
@ -37,7 +38,7 @@ namespace DetourNavigator
OffMeshConnectionsManager& offMeshConnectionsManager);
~AsyncNavMeshUpdater();
void post(const osg::Vec3f& agentHalfExtents, const std::shared_ptr<NavMeshCacheItem>& mNavMeshCacheItem,
void post(const osg::Vec3f& agentHalfExtents, const SharedNavMeshCacheItem& mNavMeshCacheItem,
const TilePosition& playerTile, const std::map<TilePosition, ChangeType>& changedTiles);
void wait();
@ -46,7 +47,7 @@ namespace DetourNavigator
struct Job
{
osg::Vec3f mAgentHalfExtents;
std::shared_ptr<NavMeshCacheItem> mNavMeshCacheItem;
SharedNavMeshCacheItem mNavMeshCacheItem;
TilePosition mChangedTile;
std::tuple<ChangeType, int, int> mPriority;
@ -69,6 +70,7 @@ namespace DetourNavigator
std::map<osg::Vec3f, std::set<TilePosition>> mPushed;
Misc::ScopeGuarded<TilePosition> mPlayerTile;
Misc::ScopeGuarded<boost::optional<std::chrono::steady_clock::time_point>> mFirstStart;
NavMeshTilesCache mNavMeshTilesCache;
std::vector<std::thread> mThreads;
void process() throw();

View File

@ -9,6 +9,7 @@
#include "sharednavmesh.hpp"
#include "settingsutils.hpp"
#include "flags.hpp"
#include "navmeshtilescache.hpp"
#include <DetourNavMesh.h>
#include <DetourNavMeshBuilder.h>
@ -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<rcPolyMeshDetail, PolyMeshDetailStackDeleter>;
struct NavMeshDataValueDeleter
{
void operator ()(unsigned char* value) const
{
dtFree(value);
}
};
using NavMeshDataValue = std::unique_ptr<unsigned char, NavMeshDataValueDeleter>;
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<OffMeshConnection>& offMeshConnections, const Settings& settings,
NavMeshCacheItem& navMeshCacheItem)
const SharedNavMeshCacheItem& navMeshCacheItem, NavMeshTilesCache& navMeshTilesCache)
{
log("update NavMesh with mutiple tiles:",
" agentHeight=", std::setprecision(std::numeric_limits<float>::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);
}

View File

@ -7,6 +7,7 @@
#include "tileposition.hpp"
#include "tilebounds.hpp"
#include "sharednavmesh.hpp"
#include "navmeshtilescache.hpp"
#include <osg/Vec3f>
@ -48,7 +49,7 @@ namespace DetourNavigator
UpdateNavMeshStatus updateNavMesh(const osg::Vec3f& agentHalfExtents, const RecastMesh* recastMesh,
const TilePosition& changedTile, const TilePosition& playerTile,
const std::vector<OffMeshConnection>& offMeshConnections, const Settings& settings,
NavMeshCacheItem& navMeshCacheItem);
const SharedNavMeshCacheItem& navMeshCacheItem, NavMeshTilesCache& navMeshTilesCache);
}
#endif

View File

@ -122,7 +122,7 @@ namespace DetourNavigator
mNavMeshManager.wait();
}
std::map<osg::Vec3f, std::shared_ptr<NavMeshCacheItem>> Navigator::getNavMeshes() const
std::map<osg::Vec3f, SharedNavMeshCacheItem> Navigator::getNavMeshes() const
{
return mNavMeshManager.getNavMeshes();
}

View File

@ -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<osg::Vec3f, std::shared_ptr<NavMeshCacheItem>> getNavMeshes() const;
std::map<osg::Vec3f, SharedNavMeshCacheItem> getNavMeshes() const;
const Settings& getSettings() const;

View File

@ -2,20 +2,69 @@
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHCACHEITEM_H
#include "sharednavmesh.hpp"
#include "tileposition.hpp"
#include "navmeshtilescache.hpp"
#include <atomic>
#include <components/misc/guarded.hpp>
#include <map>
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<TilePosition, std::pair<NavMeshTilesCache::Value, NavMeshData>> mUsedTiles;
};
using SharedNavMeshCacheItem = Misc::SharedGuarded<NavMeshCacheItem>;
}
#endif

View File

@ -0,0 +1,35 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHDATA_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHDATA_H
#include <DetourAlloc.h>
#include <algorithm>
#include <memory>
namespace DetourNavigator
{
struct NavMeshDataValueDeleter
{
void operator ()(unsigned char* value) const
{
dtFree(value);
}
};
using NavMeshDataValue = std::unique_ptr<unsigned char, NavMeshDataValueDeleter>;
struct NavMeshData
{
NavMeshDataValue mValue;
int mSize;
NavMeshData() = default;
NavMeshData(unsigned char* value, int size)
: mValue(value)
, mSize(size)
{}
};
}
#endif

View File

@ -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<osg::Vec3f, std::shared_ptr<NavMeshCacheItem>> NavMeshManager::getNavMeshes() const
std::map<osg::Vec3f, SharedNavMeshCacheItem> 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<NavMeshCacheItem>& 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())

View File

@ -46,17 +46,17 @@ namespace DetourNavigator
void wait();
SharedNavMesh getNavMesh(const osg::Vec3f& agentHalfExtents) const;
SharedNavMeshCacheItem getNavMesh(const osg::Vec3f& agentHalfExtents) const;
std::map<osg::Vec3f, std::shared_ptr<NavMeshCacheItem>> getNavMeshes() const;
std::map<osg::Vec3f, SharedNavMeshCacheItem> getNavMeshes() const;
private:
const Settings& mSettings;
TileCachedRecastMeshManager mRecastMeshManager;
OffMeshConnectionsManager mOffMeshConnectionsManager;
std::map<osg::Vec3f, std::shared_ptr<NavMeshCacheItem>> mCache;
std::map<osg::Vec3f, std::map<TilePosition, ChangeType>> mChangedTiles;
AsyncNavMeshUpdater mAsyncNavMeshUpdater;
std::map<osg::Vec3f, SharedNavMeshCacheItem> mCache;
std::map<osg::Vec3f, std::map<TilePosition, ChangeType>> mChangedTiles;
std::size_t mGenerationCounter = 0;
std::map<osg::Vec3f, TilePosition> mPlayerTile;
std::map<osg::Vec3f, std::size_t> mLastRecastMeshManagerRevision;
@ -67,7 +67,7 @@ namespace DetourNavigator
void addChangedTile(const TilePosition& tilePosition, const ChangeType changeType);
const std::shared_ptr<NavMeshCacheItem>& getCached(const osg::Vec3f& agentHalfExtents) const;
const SharedNavMeshCacheItem& getCached(const osg::Vec3f& agentHalfExtents) const;
};
}

View File

@ -0,0 +1,158 @@
#include "navmeshtilescache.hpp"
#include "exceptions.hpp"
namespace DetourNavigator
{
namespace
{
inline std::string makeNavMeshKey(const RecastMesh& recastMesh,
const std::vector<OffMeshConnection>& 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<const char*>(recastMesh.getIndices().data()),
reinterpret_cast<const char*>(recastMesh.getIndices().data() + recastMesh.getIndices().size()),
std::back_inserter(result)
);
std::copy(
reinterpret_cast<const char*>(recastMesh.getVertices().data()),
reinterpret_cast<const char*>(recastMesh.getVertices().data() + recastMesh.getVertices().size()),
std::back_inserter(result)
);
std::copy(
reinterpret_cast<const char*>(recastMesh.getAreaTypes().data()),
reinterpret_cast<const char*>(recastMesh.getAreaTypes().data() + recastMesh.getAreaTypes().size()),
std::back_inserter(result)
);
std::copy(
reinterpret_cast<const char*>(recastMesh.getWater().data()),
reinterpret_cast<const char*>(recastMesh.getWater().data() + recastMesh.getWater().size()),
std::back_inserter(result)
);
std::copy(
reinterpret_cast<const char*>(offMeshConnections.data()),
reinterpret_cast<const char*>(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<OffMeshConnection>& offMeshConnections)
{
const std::lock_guard<std::mutex> 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<OffMeshConnection>& offMeshConnections,
NavMeshData value)
{
const auto navMeshSize = static_cast<std::size_t>(value.mSize);
const std::lock_guard<std::mutex> 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<std::size_t>(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<std::size_t>(iterator->mNavMeshData.mSize);
}
void NavMeshTilesCache::releaseItem(ItemIterator iterator)
{
if (--iterator->mUseCount > 0)
return;
const std::lock_guard<std::mutex> lock(mMutex);
mFreeItems.splice(mFreeItems.begin(), mBusyItems, iterator);
mFreeNavMeshDataSize += static_cast<std::size_t>(iterator->mNavMeshData.mSize);
}
}

View File

@ -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 <atomic>
#include <map>
#include <list>
#include <mutex>
namespace DetourNavigator
{
struct NavMeshDataRef
{
unsigned char* mValue;
int mSize;
};
class NavMeshTilesCache
{
public:
struct Item
{
std::atomic<std::int64_t> 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<Item>::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<OffMeshConnection>& offMeshConnections);
Value set(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile,
const RecastMesh& recastMesh, const std::vector<OffMeshConnection>& offMeshConnections,
NavMeshData value);
private:
std::mutex mMutex;
std::size_t mMaxNavMeshDataSize;
std::size_t mUsedNavMeshDataSize;
std::size_t mFreeNavMeshDataSize;
std::list<Item> mBusyItems;
std::list<Item> mFreeItems;
std::map<osg::Vec3f, std::map<TilePosition, std::map<std::string, ItemIterator>>> mValues;
void removeLeastRecentlyUsed();
void acquireItemUnsafe(ItemIterator iterator);
void releaseItem(ItemIterator iterator);
};
}
#endif

View File

@ -10,7 +10,7 @@ namespace DetourNavigator
{
public:
template <class T>
explicit ObjectId(const T* value) throw()
explicit ObjectId(const T value) throw()
: mValue(reinterpret_cast<std::size_t>(value))
{
}

View File

@ -0,0 +1,15 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_OFFMESHCONNECTION_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_OFFMESHCONNECTION_H
#include <osg/Vec3f>
namespace DetourNavigator
{
struct OffMeshConnection
{
osg::Vec3f mStart;
osg::Vec3f mEnd;
};
}
#endif

View File

@ -5,6 +5,7 @@
#include "settingsutils.hpp"
#include "tileposition.hpp"
#include "objectid.hpp"
#include "offmeshconnection.hpp"
#include <components/misc/guarded.hpp>
@ -20,12 +21,6 @@
namespace DetourNavigator
{
struct OffMeshConnection
{
osg::Vec3f mStart;
osg::Vec3f mEnd;
};
class OffMeshConnectionsManager
{
public:

View File

@ -1,18 +1,17 @@
#include "recastmesh.hpp"
#include "settings.hpp"
#include "exceptions.hpp"
#include <Recast.h>
namespace DetourNavigator
{
RecastMesh::RecastMesh(std::vector<int> indices, std::vector<float> vertices,
std::vector<AreaType> areaTypes, std::vector<Water> water, const Settings& settings)
RecastMesh::RecastMesh(std::vector<int> indices, std::vector<float> vertices, std::vector<AreaType> areaTypes,
std::vector<Water> 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="

View File

@ -5,6 +5,7 @@
#include "chunkytrimesh.hpp"
#include <memory>
#include <string>
#include <vector>
#include <osg/Vec3f>
@ -13,8 +14,6 @@
namespace DetourNavigator
{
struct Settings;
class RecastMesh
{
public:
@ -24,8 +23,8 @@ namespace DetourNavigator
btTransform mTransform;
};
RecastMesh(std::vector<int> indices, std::vector<float> vertices,
std::vector<AreaType> areaTypes, std::vector<Water> water, const Settings& settings);
RecastMesh(std::vector<int> indices, std::vector<float> vertices, std::vector<AreaType> areaTypes,
std::vector<Water> water, const std::size_t trianglesPerChunk);
const std::vector<int>& getIndices() const
{

View File

@ -119,7 +119,7 @@ namespace DetourNavigator
std::shared_ptr<RecastMesh> RecastMeshBuilder::create() const
{
return std::make_shared<RecastMesh>(mIndices, mVertices, mAreaTypes, mWater, mSettings);
return std::make_shared<RecastMesh>(mIndices, mVertices, mAreaTypes, mWater, mSettings.get().mTrianglesPerChunk);
}
void RecastMeshBuilder::reset()

View File

@ -6,9 +6,6 @@
#include <LinearMath/btTransform.h>
#include <osg/Vec2f>
#include <osg/Vec2i>
class btBoxShape;
class btCollisionShape;
class btCompoundShape;
@ -18,6 +15,8 @@ class btTriangleCallback;
namespace DetourNavigator
{
struct Settings;
class RecastMeshBuilder
{
public:

View File

@ -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;
}
}

View File

@ -13,6 +13,7 @@
#include <map>
#include <unordered_map>
#include <list>
class btCollisionShape;
@ -53,8 +54,10 @@ namespace DetourNavigator
private:
bool mShouldRebuild;
RecastMeshBuilder mMeshBuilder;
std::unordered_map<ObjectId, RecastMeshObject> mObjects;
std::map<osg::Vec2i, Water> mWater;
std::list<RecastMeshObject> mObjectsOrder;
std::unordered_map<ObjectId, std::list<RecastMeshObject>::iterator> mObjects;
std::list<Water> mWaterOrder;
std::map<osg::Vec2i, std::list<Water>::iterator> mWater;
void rebuild();
};

View File

@ -4,6 +4,7 @@
#include <BulletCollision/CollisionShapes/btCompoundShape.h>
#include <cassert>
#include <numeric>
namespace DetourNavigator

View File

@ -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;

View File

@ -1,9 +1,6 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_SHAREDNAVMESH_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_SHAREDNAVMESH_H
#include <components/misc/guarded.hpp>
#include <mutex>
#include <memory>
class dtNavMesh;
@ -11,7 +8,6 @@ class dtNavMesh;
namespace DetourNavigator
{
using NavMeshPtr = std::shared_ptr<dtNavMesh>;
using SharedNavMesh = Misc::SharedGuarded<dtNavMesh>;
}
#endif

View File

@ -48,6 +48,21 @@ namespace Misc
: mValue(std::move(value))
{}
template <class ... Args>
ScopeGuarded(Args&& ... args)
: mValue(std::forward<Args>(args) ...)
{}
ScopeGuarded(const ScopeGuarded& other)
: mMutex()
, mValue(other.lock().get())
{}
ScopeGuarded(ScopeGuarded&& other)
: mMutex()
, mValue(std::move(other.lock().get()))
{}
Locked<T> lock()
{
return Locked<T>(mMutex, mValue);

View File

@ -127,7 +127,9 @@ osg::ref_ptr<Resource::BulletShape> BulletNifLoader::load(const Nif::File& nif)
{
btTransform trans;
trans.setIdentity();
mCompoundShape->addChildShape(trans, new Resource::TriangleMeshShape(mStaticMesh.get(), true));
std::unique_ptr<btCollisionShape> 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<Resource::BulletShape> 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;
}

View File

@ -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