Merge branch 'navmesh_wait' into 'master'

Wait until navmesh is generated within given distance around player (#5500)

Closes #5500

See merge request OpenMW/openmw!819
pull/3088/head
psi29a 4 years ago
commit fd89582e0c

@ -143,6 +143,7 @@
Feature #5456: Basic collada animation support
Feature #5457: Realistic diagonal movement
Feature #5486: Fixes trainers to choose their training skills based on their base skill points
Feature #5500: Prepare enough navmesh tiles before scene loading ends
Feature #5511: Add in game option to toggle HRTF support in OpenMW
Feature #5519: Code Patch tab in launcher
Feature #5524: Resume failed script execution after reload

@ -618,6 +618,8 @@ namespace MWWorld
if (changeEvent)
mCellChanged = true;
mNavigator.wait(*loadingListener);
}
void Scene::testExteriorCells()
@ -845,6 +847,8 @@ namespace MWWorld
MWBase::Environment::get().getWindowManager()->fadeScreenIn(0.5);
MWBase::Environment::get().getWindowManager()->changeCell(mCurrentCell);
mNavigator.wait(*loadingListener);
}
void Scene::changeToExteriorCell (const ESM::Position& position, bool adjustPlayerPos, bool changeEvent)

@ -3,6 +3,7 @@
#include <components/detournavigator/navigatorimpl.hpp>
#include <components/detournavigator/exceptions.hpp>
#include <components/misc/rng.hpp>
#include <components/loadinglistener/loadinglistener.hpp>
#include <BulletCollision/CollisionShapes/btHeightfieldTerrainShape.h>
#include <BulletCollision/CollisionShapes/btBoxShape.h>
@ -35,6 +36,7 @@ namespace
std::back_insert_iterator<std::deque<osg::Vec3f>> mOut;
float mStepSize;
AreaCosts mAreaCosts;
Loading::Listener mListener;
DetourNavigatorNavigatorTest()
: mPlayerPosition(0, 0, 0)
@ -64,6 +66,7 @@ namespace
mSettings.mRegionMergeSize = 20;
mSettings.mRegionMinSize = 8;
mSettings.mTileSize = 64;
mSettings.mWaitUntilMinDistanceToPlayer = std::numeric_limits<int>::max();
mSettings.mAsyncNavMeshUpdaterThreads = 1;
mSettings.mMaxNavMeshTilesCacheSize = 1024 * 1024;
mSettings.mMaxPolygonPathSize = 1024;
@ -124,7 +127,7 @@ namespace
mNavigator->addAgent(mAgentHalfExtents);
mNavigator->addObject(ObjectId(&shape), shape, btTransform::getIdentity());
mNavigator->update(mPlayerPosition);
mNavigator->wait();
mNavigator->wait(mListener);
EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mOut), Status::Success);
@ -174,7 +177,7 @@ namespace
mNavigator->addAgent(mAgentHalfExtents);
mNavigator->addObject(ObjectId(&heightfieldShape), heightfieldShape, btTransform::getIdentity());
mNavigator->update(mPlayerPosition);
mNavigator->wait();
mNavigator->wait(mListener);
EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mOut), Status::Success);
@ -206,7 +209,7 @@ namespace
mNavigator->addObject(ObjectId(&compoundShape), compoundShape, btTransform::getIdentity());
mNavigator->update(mPlayerPosition);
mNavigator->wait();
mNavigator->wait(mListener);
mPath.clear();
mOut = std::back_inserter(mPath);
@ -259,7 +262,7 @@ namespace
mNavigator->addObject(ObjectId(&heightfieldShape), heightfieldShape, btTransform::getIdentity());
mNavigator->addObject(ObjectId(&compoundShape), compoundShape, btTransform::getIdentity());
mNavigator->update(mPlayerPosition);
mNavigator->wait();
mNavigator->wait(mListener);
EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mOut), Status::Success);
@ -293,7 +296,7 @@ namespace
mNavigator->updateObject(ObjectId(&compoundShape), compoundShape, btTransform::getIdentity());
mNavigator->update(mPlayerPosition);
mNavigator->wait();
mNavigator->wait(mListener);
mPath.clear();
mOut = std::back_inserter(mPath);
@ -352,7 +355,7 @@ namespace
mNavigator->addObject(ObjectId(&shape), shape, btTransform::getIdentity());
mNavigator->addObject(ObjectId(&shape2), shape2, btTransform::getIdentity());
mNavigator->update(mPlayerPosition);
mNavigator->wait();
mNavigator->wait(mListener);
EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mOut), Status::Success);
@ -408,7 +411,7 @@ namespace
mNavigator->addAgent(mAgentHalfExtents);
mNavigator->addObject(ObjectId(&shape), ObjectShapes {shape, &shapeAvoid}, btTransform::getIdentity());
mNavigator->update(mPlayerPosition);
mNavigator->wait();
mNavigator->wait(mListener);
EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mOut), Status::Success);
@ -456,7 +459,7 @@ namespace
mNavigator->addWater(osg::Vec2i(0, 0), 128 * 4, 300, btTransform::getIdentity());
mNavigator->addObject(ObjectId(&shape), shape, btTransform::getIdentity());
mNavigator->update(mPlayerPosition);
mNavigator->wait();
mNavigator->wait(mListener);
mStart.x() = 0;
mStart.z() = 300;
@ -504,7 +507,7 @@ namespace
mNavigator->addWater(osg::Vec2i(0, 0), 128 * 4, -25, btTransform::getIdentity());
mNavigator->addObject(ObjectId(&shape), shape, btTransform::getIdentity());
mNavigator->update(mPlayerPosition);
mNavigator->wait();
mNavigator->wait(mListener);
mStart.x() = 0;
mEnd.x() = 0;
@ -551,7 +554,7 @@ namespace
mNavigator->addObject(ObjectId(&shape), shape, btTransform::getIdentity());
mNavigator->addWater(osg::Vec2i(0, 0), std::numeric_limits<int>::max(), -25, btTransform::getIdentity());
mNavigator->update(mPlayerPosition);
mNavigator->wait();
mNavigator->wait(mListener);
mStart.x() = 0;
mEnd.x() = 0;
@ -598,7 +601,7 @@ namespace
mNavigator->addWater(osg::Vec2i(0, 0), 128 * 4, -25, btTransform::getIdentity());
mNavigator->addObject(ObjectId(&shape), shape, btTransform::getIdentity());
mNavigator->update(mPlayerPosition);
mNavigator->wait();
mNavigator->wait(mListener);
mStart.x() = 0;
mEnd.x() = 0;
@ -642,15 +645,15 @@ namespace
mNavigator->addAgent(mAgentHalfExtents);
mNavigator->addObject(ObjectId(&shape), shape, btTransform::getIdentity());
mNavigator->update(mPlayerPosition);
mNavigator->wait();
mNavigator->wait(mListener);
mNavigator->removeObject(ObjectId(&shape));
mNavigator->update(mPlayerPosition);
mNavigator->wait();
mNavigator->wait(mListener);
mNavigator->addObject(ObjectId(&shape), shape, btTransform::getIdentity());
mNavigator->update(mPlayerPosition);
mNavigator->wait();
mNavigator->wait(mListener);
EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mOut), Status::Success);
@ -696,7 +699,7 @@ namespace
mNavigator->addAgent(mAgentHalfExtents);
mNavigator->addObject(ObjectId(&shape), shape, btTransform::getIdentity());
mNavigator->update(mPlayerPosition);
mNavigator->wait();
mNavigator->wait(mListener);
Misc::Rng::init(42);
@ -745,7 +748,7 @@ namespace
}
mNavigator->update(mPlayerPosition);
mNavigator->wait();
mNavigator->wait(mListener);
EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mOut), Status::Success);
@ -788,7 +791,7 @@ namespace
mNavigator->addObject(ObjectId(&shapes[i]), shapes[i], transform);
}
mNavigator->update(mPlayerPosition);
mNavigator->wait();
mNavigator->wait(mListener);
const auto start = std::chrono::steady_clock::now();
for (std::size_t i = 0; i < shapes.size(); ++i)
@ -797,7 +800,7 @@ namespace
mNavigator->updateObject(ObjectId(&shapes[i]), shapes[i], transform);
}
mNavigator->update(mPlayerPosition);
mNavigator->wait();
mNavigator->wait(mListener);
for (std::size_t i = 0; i < shapes.size(); ++i)
{
@ -805,7 +808,7 @@ namespace
mNavigator->updateObject(ObjectId(&shapes[i]), shapes[i], transform);
}
mNavigator->update(mPlayerPosition);
mNavigator->wait();
mNavigator->wait(mListener);
const auto duration = std::chrono::steady_clock::now() - start;
@ -828,7 +831,7 @@ namespace
mNavigator->addAgent(mAgentHalfExtents);
mNavigator->addObject(ObjectId(&shape), shape, btTransform::getIdentity());
mNavigator->update(mPlayerPosition);
mNavigator->wait();
mNavigator->wait(mListener);
const auto result = mNavigator->raycast(mAgentHalfExtents, mStart, mEnd, Flag_walk);
@ -859,7 +862,7 @@ namespace
mNavigator->addObject(ObjectId(&boderBoxShape), boderBoxShape,
btTransform(btMatrix3x3::getIdentity(), oscillatingBoxShapePosition + btVector3(0, 0, 200)));
mNavigator->update(mPlayerPosition);
mNavigator->wait();
mNavigator->wait(mListener);
const auto navMeshes = mNavigator->getNavMeshes();
ASSERT_EQ(navMeshes.size(), 1);
@ -875,7 +878,7 @@ namespace
oscillatingBoxShapePosition);
mNavigator->updateObject(ObjectId(&oscillatingBoxShape), oscillatingBoxShape, transform);
mNavigator->update(mPlayerPosition);
mNavigator->wait();
mNavigator->wait(mListener);
}
ASSERT_EQ(navMeshes.size(), 1);

@ -6,6 +6,7 @@
#include <components/debug/debuglog.hpp>
#include <components/misc/thread.hpp>
#include <components/loadinglistener/loadinglistener.hpp>
#include <osg/Stats>
@ -20,6 +21,16 @@ namespace
{
return std::abs(lhs.x() - rhs.x()) + std::abs(lhs.y() - rhs.y());
}
int getMinDistanceTo(const TilePosition& position, int maxDistance,
const std::map<osg::Vec3f, std::set<TilePosition>>& tilesPerHalfExtents)
{
int result = maxDistance;
for (const auto& [halfExtents, tiles] : tilesPerHalfExtents)
for (const TilePosition& tile : tiles)
result = std::min(result, getManhattanDistance(position, tile));
return result;
}
}
namespace DetourNavigator
@ -111,13 +122,61 @@ namespace DetourNavigator
mHasJob.notify_all();
}
void AsyncNavMeshUpdater::wait()
void AsyncNavMeshUpdater::wait(Loading::Listener& listener)
{
if (mSettings.get().mWaitUntilMinDistanceToPlayer == 0)
return;
listener.setLabel("Building navigation mesh");
const std::size_t initialJobsLeft = getTotalJobs();
std::size_t maxProgress = initialJobsLeft + mThreads.size();
listener.setProgressRange(maxProgress);
const int minDistanceToPlayer = waitUntilJobsDone(initialJobsLeft, maxProgress, listener);
if (minDistanceToPlayer < mSettings.get().mWaitUntilMinDistanceToPlayer)
{
mProcessingTiles.wait(mProcessed, [] (const auto& v) { return v.empty(); });
listener.setProgress(maxProgress);
}
}
int AsyncNavMeshUpdater::waitUntilJobsDone(const std::size_t initialJobsLeft, std::size_t& maxProgress, Loading::Listener& listener)
{
std::size_t prevJobsLeft = initialJobsLeft;
std::size_t jobsDone = 0;
std::size_t jobsLeft = 0;
const int maxDistanceToPlayer = mSettings.get().mWaitUntilMinDistanceToPlayer;
const TilePosition playerPosition = *mPlayerTile.lockConst();
int minDistanceToPlayer = 0;
const auto isDone = [&]
{
jobsLeft = mJobs.size() + getTotalThreadJobsUnsafe();
if (jobsLeft == 0)
{
minDistanceToPlayer = 0;
return true;
}
minDistanceToPlayer = getMinDistanceTo(playerPosition, maxDistanceToPlayer, mPushed);
for (const auto& [threadId, queue] : mThreadsQueues)
minDistanceToPlayer = getMinDistanceTo(playerPosition, minDistanceToPlayer, queue.mPushed);
return minDistanceToPlayer >= maxDistanceToPlayer;
};
std::unique_lock<std::mutex> lock(mMutex);
while (!mDone.wait_for(lock, std::chrono::milliseconds(250), isDone))
{
std::unique_lock<std::mutex> lock(mMutex);
mDone.wait(lock, [&] { return mJobs.empty() && getTotalThreadJobsUnsafe() == 0; });
if (maxProgress < jobsLeft)
{
maxProgress = jobsLeft + mThreads.size();
listener.setProgressRange(maxProgress);
listener.setProgress(jobsDone);
}
else if (jobsLeft < prevJobsLeft)
{
const std::size_t newJobsDone = prevJobsLeft - jobsLeft;
jobsDone += newJobsDone;
prevJobsLeft = jobsLeft;
listener.increaseProgress(newJobsDone);
}
}
mProcessingTiles.wait(mProcessed, [] (const auto& v) { return v.empty(); });
return minDistanceToPlayer;
}
void AsyncNavMeshUpdater::reportStats(unsigned int frameNumber, osg::Stats& stats) const
@ -381,6 +440,12 @@ namespace DetourNavigator
mProcessed.notify_all();
}
std::size_t AsyncNavMeshUpdater::getTotalJobs() const
{
const std::scoped_lock lock(mMutex);
return mJobs.size() + getTotalThreadJobsUnsafe();
}
std::size_t AsyncNavMeshUpdater::getTotalThreadJobsUnsafe() const
{
return std::accumulate(mThreadsQueues.begin(), mThreadsQueues.end(), std::size_t(0),

@ -20,6 +20,11 @@
class dtNavMesh;
namespace Loading
{
class Listener;
}
namespace DetourNavigator
{
enum class ChangeType
@ -55,7 +60,7 @@ namespace DetourNavigator
void post(const osg::Vec3f& agentHalfExtents, const SharedNavMeshCacheItem& mNavMeshCacheItem,
const TilePosition& playerTile, const std::map<TilePosition, ChangeType>& changedTiles);
void wait();
void wait(Loading::Listener& listener);
void reportStats(unsigned int frameNumber, osg::Stats& stats) const;
@ -131,9 +136,13 @@ namespace DetourNavigator
void unlockTile(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile);
inline std::size_t getTotalJobs() const;
inline std::size_t getTotalThreadJobsUnsafe() const;
void cleanupLastUpdates();
int waitUntilJobsDone(const std::size_t initialJobsLeft, std::size_t& maxJobsLeft, Loading::Listener& listener);
};
}

@ -14,6 +14,11 @@ namespace ESM
struct Pathgrid;
}
namespace Loading
{
class Listener;
}
namespace DetourNavigator
{
struct ObjectShapes
@ -162,7 +167,7 @@ namespace DetourNavigator
/**
* @brief wait locks thread until all tiles are updated from last update call.
*/
virtual void wait() = 0;
virtual void wait(Loading::Listener& listener) = 0;
/**
* @brief findPath fills output iterator with points of scene surfaces to be used for actor to walk through.

@ -153,9 +153,9 @@ namespace DetourNavigator
mUpdatesEnabled = enabled;
}
void NavigatorImpl::wait()
void NavigatorImpl::wait(Loading::Listener& listener)
{
mNavMeshManager.wait();
mNavMeshManager.wait(listener);
}
SharedNavMeshCacheItem NavigatorImpl::getNavMesh(const osg::Vec3f& agentHalfExtents) const

@ -48,7 +48,7 @@ namespace DetourNavigator
void setUpdatesEnabled(bool enabled) override;
void wait() override;
void wait(Loading::Listener& listener) override;
SharedNavMeshCacheItem getNavMesh(const osg::Vec3f& agentHalfExtents) const override;

@ -3,6 +3,11 @@
#include "navigator.hpp"
namespace Loading
{
class Listener;
}
namespace DetourNavigator
{
class NavigatorStub final : public Navigator
@ -68,7 +73,7 @@ namespace DetourNavigator
void setUpdatesEnabled(bool /*enabled*/) override {}
void wait() override {}
void wait(Loading::Listener& /*listener*/) override {}
SharedNavMeshCacheItem getNavMesh(const osg::Vec3f& /*agentHalfExtents*/) const override
{

@ -190,9 +190,9 @@ namespace DetourNavigator
" recastMeshManagerRevision=" << lastRevision;
}
void NavMeshManager::wait()
void NavMeshManager::wait(Loading::Listener& listener)
{
mAsyncNavMeshUpdater.wait();
mAsyncNavMeshUpdater.wait(listener);
}
SharedNavMeshCacheItem NavMeshManager::getNavMesh(const osg::Vec3f& agentHalfExtents) const

@ -45,7 +45,7 @@ namespace DetourNavigator
void update(osg::Vec3f playerPosition, const osg::Vec3f& agentHalfExtents);
void wait();
void wait(Loading::Listener& listener);
SharedNavMeshCacheItem getNavMesh(const osg::Vec3f& agentHalfExtents) const;

@ -29,6 +29,7 @@ namespace DetourNavigator
navigatorSettings.mRegionMergeSize = ::Settings::Manager::getInt("region merge size", "Navigator");
navigatorSettings.mRegionMinSize = ::Settings::Manager::getInt("region min size", "Navigator");
navigatorSettings.mTileSize = ::Settings::Manager::getInt("tile size", "Navigator");
navigatorSettings.mWaitUntilMinDistanceToPlayer = ::Settings::Manager::getInt("wait until min distance to player", "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"));

@ -31,6 +31,7 @@ namespace DetourNavigator
int mRegionMergeSize = 0;
int mRegionMinSize = 0;
int mTileSize = 0;
int mWaitUntilMinDistanceToPlayer = 0;
std::size_t mAsyncNavMeshUpdaterThreads = 0;
std::size_t mMaxNavMeshTilesCacheSize = 0;
std::size_t mMaxPolygonPathSize = 0;

@ -75,7 +75,7 @@ namespace Misc
return Locked<T>(mMutex, mValue);
}
Locked<const T> lockConst()
Locked<const T> lockConst() const
{
return Locked<const T>(mMutex, mValue);
}
@ -88,7 +88,7 @@ namespace Misc
}
private:
std::mutex mMutex;
mutable std::mutex mMutex;
T mValue;
};
}

@ -42,6 +42,18 @@ Increasing this value may decrease performance.
This condition is always true: ``max tiles number * max polygons per tile <= 4194304``.
It's a limitation of `Recastnavigation <https://github.com/recastnavigation/recastnavigation>`_ library.
wait until min distance to player
------------------------------
:Type: integer
:Range: >= 0
:Default: 5
Distance in navmesh tiles around the player to keep loading screen until navigation mesh is generated.
Allows to complete cell loading only when minimal navigation mesh area is generated to correctly find path for actors
nearby the player. Increasing this value will keep loading screen longer but will slightly increase nav mesh generation
speed on systems bound by CPU. Zero means no waiting.
Advanced settings
*****************

@ -908,6 +908,10 @@ max tiles number = 512
# Min time duration for the same tile update in milliseconds (value >= 0)
min update interval ms = 250
# Keep loading screen until navmesh is generated around the player for all tiles within manhattan distance (value >= 0).
# Distance is measured in the number of tiles and can be only an integer value.
wait until min distance to player = 5
[Shadows]
# Enable or disable shadows. Bear in mind that this will force OpenMW to use shaders as if "[Shaders]/force shaders" was set to true.

Loading…
Cancel
Save