diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f8b668f1c..647a8fa569 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 3fdf4503af..23525dea8d 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -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) diff --git a/apps/openmw_test_suite/detournavigator/navigator.cpp b/apps/openmw_test_suite/detournavigator/navigator.cpp index 6192765888..cb8ae41523 100644 --- a/apps/openmw_test_suite/detournavigator/navigator.cpp +++ b/apps/openmw_test_suite/detournavigator/navigator.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -35,6 +36,7 @@ namespace std::back_insert_iterator> 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::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::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); diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index 7248850a6c..ff51e39aad 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -6,6 +6,7 @@ #include #include +#include #include @@ -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>& 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 lock(mMutex); + while (!mDone.wait_for(lock, std::chrono::milliseconds(250), isDone)) { - std::unique_lock 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), diff --git a/components/detournavigator/asyncnavmeshupdater.hpp b/components/detournavigator/asyncnavmeshupdater.hpp index 53e7fd7c14..c28d8f21db 100644 --- a/components/detournavigator/asyncnavmeshupdater.hpp +++ b/components/detournavigator/asyncnavmeshupdater.hpp @@ -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& 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); }; } diff --git a/components/detournavigator/navigator.hpp b/components/detournavigator/navigator.hpp index edf597348f..3ec5e5acc4 100644 --- a/components/detournavigator/navigator.hpp +++ b/components/detournavigator/navigator.hpp @@ -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. diff --git a/components/detournavigator/navigatorimpl.cpp b/components/detournavigator/navigatorimpl.cpp index abfb20ba80..d1e75b864a 100644 --- a/components/detournavigator/navigatorimpl.cpp +++ b/components/detournavigator/navigatorimpl.cpp @@ -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 diff --git a/components/detournavigator/navigatorimpl.hpp b/components/detournavigator/navigatorimpl.hpp index 74fff0dea4..a53c9608de 100644 --- a/components/detournavigator/navigatorimpl.hpp +++ b/components/detournavigator/navigatorimpl.hpp @@ -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; diff --git a/components/detournavigator/navigatorstub.hpp b/components/detournavigator/navigatorstub.hpp index f6892bf1b5..c21db2bf8d 100644 --- a/components/detournavigator/navigatorstub.hpp +++ b/components/detournavigator/navigatorstub.hpp @@ -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 { diff --git a/components/detournavigator/navmeshmanager.cpp b/components/detournavigator/navmeshmanager.cpp index 2692f016ee..71a4def4f7 100644 --- a/components/detournavigator/navmeshmanager.cpp +++ b/components/detournavigator/navmeshmanager.cpp @@ -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 diff --git a/components/detournavigator/navmeshmanager.hpp b/components/detournavigator/navmeshmanager.hpp index f3861f8f27..ce90aafc5d 100644 --- a/components/detournavigator/navmeshmanager.hpp +++ b/components/detournavigator/navmeshmanager.hpp @@ -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; diff --git a/components/detournavigator/settings.cpp b/components/detournavigator/settings.cpp index 0c4d0f838b..ff99fae20c 100644 --- a/components/detournavigator/settings.cpp +++ b/components/detournavigator/settings.cpp @@ -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(::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")); diff --git a/components/detournavigator/settings.hpp b/components/detournavigator/settings.hpp index ece16e35a9..39f5815b8e 100644 --- a/components/detournavigator/settings.hpp +++ b/components/detournavigator/settings.hpp @@ -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; diff --git a/components/misc/guarded.hpp b/components/misc/guarded.hpp index 55a2c670cf..7f1005fc3a 100644 --- a/components/misc/guarded.hpp +++ b/components/misc/guarded.hpp @@ -75,7 +75,7 @@ namespace Misc return Locked(mMutex, mValue); } - Locked lockConst() + Locked lockConst() const { return Locked(mMutex, mValue); } @@ -88,7 +88,7 @@ namespace Misc } private: - std::mutex mMutex; + mutable std::mutex mMutex; T mValue; }; } diff --git a/docs/source/reference/modding/settings/navigator.rst b/docs/source/reference/modding/settings/navigator.rst index b9485c3e95..fee4b2626e 100644 --- a/docs/source/reference/modding/settings/navigator.rst +++ b/docs/source/reference/modding/settings/navigator.rst @@ -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 `_ 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 ***************** diff --git a/files/settings-default.cfg b/files/settings-default.cfg index b4910c9b86..88ca82f416 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -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.