1
0
mirror of https://gitlab.com/OpenMW/openmw.git synced 2025-02-07 03:40:15 +00:00

Update same navmesh tile with limited frequency

This commit is contained in:
elsid 2020-02-20 15:05:50 -08:00
parent 8c0674490d
commit b150d681a9
No known key found for this signature in database
GPG Key ID: B845CB9FEE18AB40
7 changed files with 132 additions and 12 deletions

View File

@ -73,6 +73,7 @@ namespace
mSettings.mTrianglesPerChunk = 256; mSettings.mTrianglesPerChunk = 256;
mSettings.mMaxPolys = 4096; mSettings.mMaxPolys = 4096;
mSettings.mMaxTilesNumber = 512; mSettings.mMaxTilesNumber = 512;
mSettings.mMinUpdateInterval = std::chrono::milliseconds(50);
mNavigator.reset(new NavigatorImpl(mSettings)); mNavigator.reset(new NavigatorImpl(mSettings));
} }
}; };
@ -766,4 +767,41 @@ namespace
Vec3fEq(215, -215, 1.8782813549041748046875) Vec3fEq(215, -215, 1.8782813549041748046875)
)) << mPath; )) << mPath;
} }
TEST_F(DetourNavigatorNavigatorTest, update_changed_multiple_times_object_should_delay_navmesh_change)
{
const std::vector<btBoxShape> shapes(100, btVector3(64, 64, 64));
mNavigator->addAgent(mAgentHalfExtents);
for (std::size_t i = 0; i < shapes.size(); ++i)
{
const btTransform transform(btMatrix3x3::getIdentity(), btVector3(i * 32, i * 32, i * 32));
mNavigator->addObject(ObjectId(&shapes[i]), shapes[i], transform);
}
mNavigator->update(mPlayerPosition);
mNavigator->wait();
const auto start = std::chrono::steady_clock::now();
for (std::size_t i = 0; i < shapes.size(); ++i)
{
const btTransform transform(btMatrix3x3::getIdentity(), btVector3(i * 32 + 1, i * 32 + 1, i * 32 + 1));
mNavigator->updateObject(ObjectId(&shapes[i]), shapes[i], transform);
}
mNavigator->update(mPlayerPosition);
mNavigator->wait();
for (std::size_t i = 0; i < shapes.size(); ++i)
{
const btTransform transform(btMatrix3x3::getIdentity(), btVector3(i * 32 + 2, i * 32 + 2, i * 32 + 2));
mNavigator->updateObject(ObjectId(&shapes[i]), shapes[i], transform);
}
mNavigator->update(mPlayerPosition);
mNavigator->wait();
const auto duration = std::chrono::steady_clock::now() - start;
EXPECT_GT(duration, mSettings.mMinUpdateInterval)
<< std::chrono::duration_cast<std::chrono::duration<float, std::milli>>(duration).count() << " ms";
}
} }

View File

@ -89,6 +89,9 @@ namespace DetourNavigator
job.mChangeType = changedTile.second; job.mChangeType = changedTile.second;
job.mDistanceToPlayer = getManhattanDistance(changedTile.first, playerTile); job.mDistanceToPlayer = getManhattanDistance(changedTile.first, playerTile);
job.mDistanceToOrigin = getManhattanDistance(changedTile.first, TilePosition {0, 0}); job.mDistanceToOrigin = getManhattanDistance(changedTile.first, TilePosition {0, 0});
job.mProcessTime = job.mChangeType == ChangeType::update
? mLastUpdates[job.mAgentHalfExtents][job.mChangedTile] + mSettings.get().mMinUpdateInterval
: std::chrono::steady_clock::time_point();
mJobs.push(std::move(job)); mJobs.push(std::move(job));
} }
@ -137,6 +140,8 @@ namespace DetourNavigator
if (!processed) if (!processed)
repost(std::move(*job)); repost(std::move(*job));
} }
else
cleanupLastUpdates();
} }
catch (const std::exception& e) catch (const std::exception& e)
{ {
@ -176,6 +181,7 @@ namespace DetourNavigator
const auto locked = navMeshCacheItem->lockConst(); const auto locked = navMeshCacheItem->lockConst();
Log(Debug::Debug) << std::fixed << std::setprecision(2) << Log(Debug::Debug) << std::fixed << std::setprecision(2) <<
"Cache updated for agent=(" << job.mAgentHalfExtents << ")" << "Cache updated for agent=(" << job.mAgentHalfExtents << ")" <<
" tile=" << job.mChangedTile <<
" status=" << status << " status=" << status <<
" generation=" << locked->getGeneration() << " generation=" << locked->getGeneration() <<
" revision=" << locked->getNavMeshRevision() << " revision=" << locked->getNavMeshRevision() <<
@ -195,12 +201,15 @@ namespace DetourNavigator
while (true) while (true)
{ {
const auto hasJob = [&] { return !mJobs.empty() || !threadQueue.mJobs.empty(); }; const auto hasJob = [&] {
return (!mJobs.empty() && mJobs.top().mProcessTime <= std::chrono::steady_clock::now())
|| !threadQueue.mJobs.empty();
};
if (!mHasJob.wait_for(lock, std::chrono::milliseconds(10), hasJob)) if (!mHasJob.wait_for(lock, std::chrono::milliseconds(10), hasJob))
{ {
mFirstStart.lock()->reset(); mFirstStart.lock()->reset();
if (getTotalThreadJobsUnsafe() == 0) if (mJobs.empty() && getTotalThreadJobsUnsafe() == 0)
mDone.notify_all(); mDone.notify_all();
return boost::none; return boost::none;
} }
@ -209,29 +218,40 @@ namespace DetourNavigator
<< threadQueue.mJobs.size() << " thread jobs by thread=" << std::this_thread::get_id(); << threadQueue.mJobs.size() << " thread jobs by thread=" << std::this_thread::get_id();
auto job = threadQueue.mJobs.empty() auto job = threadQueue.mJobs.empty()
? getJob(mJobs, mPushed) ? getJob(mJobs, mPushed, true)
: getJob(threadQueue.mJobs, threadQueue.mPushed); : getJob(threadQueue.mJobs, threadQueue.mPushed, false);
const auto owner = lockTile(job.mAgentHalfExtents, job.mChangedTile); if (!job)
continue;
const auto owner = lockTile(job->mAgentHalfExtents, job->mChangedTile);
if (owner == threadId) if (owner == threadId)
return job; return job;
postThreadJob(std::move(job), mThreadsQueues[owner]); postThreadJob(std::move(*job), mThreadsQueues[owner]);
} }
} }
AsyncNavMeshUpdater::Job AsyncNavMeshUpdater::getJob(Jobs& jobs, Pushed& pushed) boost::optional<AsyncNavMeshUpdater::Job> AsyncNavMeshUpdater::getJob(Jobs& jobs, Pushed& pushed, bool changeLastUpdate)
{ {
auto job = jobs.top(); const auto now = std::chrono::steady_clock::now();
if (jobs.top().mProcessTime > now)
return {};
Job job = std::move(jobs.top());
jobs.pop(); jobs.pop();
if (changeLastUpdate && job.mChangeType == ChangeType::update)
mLastUpdates[job.mAgentHalfExtents][job.mChangedTile] = now;
const auto it = pushed.find(job.mAgentHalfExtents); const auto it = pushed.find(job.mAgentHalfExtents);
it->second.erase(job.mChangedTile); it->second.erase(job.mChangedTile);
if (it->second.empty()) if (it->second.empty())
pushed.erase(it); pushed.erase(it);
return job; return {std::move(job)};
} }
void AsyncNavMeshUpdater::writeDebugFiles(const Job& job, const RecastMesh* recastMesh) const void AsyncNavMeshUpdater::writeDebugFiles(const Job& job, const RecastMesh* recastMesh) const
@ -344,4 +364,27 @@ namespace DetourNavigator
return std::accumulate(mThreadsQueues.begin(), mThreadsQueues.end(), std::size_t(0), return std::accumulate(mThreadsQueues.begin(), mThreadsQueues.end(), std::size_t(0),
[] (auto r, const auto& v) { return r + v.second.mJobs.size(); }); [] (auto r, const auto& v) { return r + v.second.mJobs.size(); });
} }
void AsyncNavMeshUpdater::cleanupLastUpdates()
{
const auto now = std::chrono::steady_clock::now();
const std::lock_guard<std::mutex> lock(mMutex);
for (auto agent = mLastUpdates.begin(); agent != mLastUpdates.end();)
{
for (auto tile = agent->second.begin(); tile != agent->second.end();)
{
if (now - tile->second > mSettings.get().mMinUpdateInterval)
tile = agent->second.erase(tile);
else
++tile;
}
if (agent->second.empty())
agent = mLastUpdates.erase(agent);
else
++agent;
}
}
} }

View File

@ -32,6 +32,21 @@ namespace DetourNavigator
update = 3, update = 3,
}; };
inline std::ostream& operator <<(std::ostream& stream, ChangeType value)
{
switch (value) {
case ChangeType::remove:
return stream << "ChangeType::remove";
case ChangeType::mixed:
return stream << "ChangeType::mixed";
case ChangeType::add:
return stream << "ChangeType::add";
case ChangeType::update:
return stream << "ChangeType::update";
}
return stream << "ChangeType::" << static_cast<int>(value);
}
class AsyncNavMeshUpdater class AsyncNavMeshUpdater
{ {
public: public:
@ -56,10 +71,11 @@ namespace DetourNavigator
ChangeType mChangeType; ChangeType mChangeType;
int mDistanceToPlayer; int mDistanceToPlayer;
int mDistanceToOrigin; int mDistanceToOrigin;
std::chrono::steady_clock::time_point mProcessTime;
std::tuple<unsigned, ChangeType, int, int> getPriority() const std::tuple<std::chrono::steady_clock::time_point, unsigned, ChangeType, int, int> getPriority() const
{ {
return std::make_tuple(mTryNumber, mChangeType, mDistanceToPlayer, mDistanceToOrigin); return std::make_tuple(mProcessTime, mTryNumber, mChangeType, mDistanceToPlayer, mDistanceToOrigin);
} }
friend inline bool operator <(const Job& lhs, const Job& rhs) friend inline bool operator <(const Job& lhs, const Job& rhs)
@ -93,6 +109,7 @@ namespace DetourNavigator
Misc::ScopeGuarded<boost::optional<std::chrono::steady_clock::time_point>> mFirstStart; Misc::ScopeGuarded<boost::optional<std::chrono::steady_clock::time_point>> mFirstStart;
NavMeshTilesCache mNavMeshTilesCache; NavMeshTilesCache mNavMeshTilesCache;
Misc::ScopeGuarded<std::map<osg::Vec3f, std::map<TilePosition, std::thread::id>>> mProcessingTiles; Misc::ScopeGuarded<std::map<osg::Vec3f, std::map<TilePosition, std::thread::id>>> mProcessingTiles;
std::map<osg::Vec3f, std::map<TilePosition, std::chrono::steady_clock::time_point>> mLastUpdates;
std::map<std::thread::id, Queue> mThreadsQueues; std::map<std::thread::id, Queue> mThreadsQueues;
std::vector<std::thread> mThreads; std::vector<std::thread> mThreads;
@ -102,7 +119,7 @@ namespace DetourNavigator
boost::optional<Job> getNextJob(); boost::optional<Job> getNextJob();
static Job getJob(Jobs& jobs, Pushed& pushed); boost::optional<Job> getJob(Jobs& jobs, Pushed& pushed, bool changeLastUpdate);
void postThreadJob(Job&& job, Queue& queue); void postThreadJob(Job&& job, Queue& queue);
@ -117,6 +134,8 @@ namespace DetourNavigator
void unlockTile(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile); void unlockTile(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile);
inline std::size_t getTotalThreadJobsUnsafe() const; inline std::size_t getTotalThreadJobsUnsafe() const;
void cleanupLastUpdates();
}; };
} }

View File

@ -40,6 +40,7 @@ namespace DetourNavigator
navigatorSettings.mNavMeshPathPrefix = ::Settings::Manager::getString("nav mesh path prefix", "Navigator"); navigatorSettings.mNavMeshPathPrefix = ::Settings::Manager::getString("nav mesh path prefix", "Navigator");
navigatorSettings.mEnableRecastMeshFileNameRevision = ::Settings::Manager::getBool("enable recast mesh file name revision", "Navigator"); navigatorSettings.mEnableRecastMeshFileNameRevision = ::Settings::Manager::getBool("enable recast mesh file name revision", "Navigator");
navigatorSettings.mEnableNavMeshFileNameRevision = ::Settings::Manager::getBool("enable nav mesh file name revision", "Navigator"); navigatorSettings.mEnableNavMeshFileNameRevision = ::Settings::Manager::getBool("enable nav mesh file name revision", "Navigator");
navigatorSettings.mMinUpdateInterval = std::chrono::milliseconds(::Settings::Manager::getInt("min update interval ms", "Navigator"));
return navigatorSettings; return navigatorSettings;
} }

View File

@ -4,6 +4,7 @@
#include <boost/optional.hpp> #include <boost/optional.hpp>
#include <string> #include <string>
#include <chrono>
namespace DetourNavigator namespace DetourNavigator
{ {
@ -38,6 +39,7 @@ namespace DetourNavigator
std::size_t mTrianglesPerChunk = 0; std::size_t mTrianglesPerChunk = 0;
std::string mRecastMeshPathPrefix; std::string mRecastMeshPathPrefix;
std::string mNavMeshPathPrefix; std::string mNavMeshPathPrefix;
std::chrono::milliseconds mMinUpdateInterval;
}; };
boost::optional<Settings> makeSettingsFromSettingsManager(); boost::optional<Settings> makeSettingsFromSettingsManager();

View File

@ -74,6 +74,20 @@ Game will not eat all memory at once.
Memory will be consumed in approximately linear dependency from number of nav mesh updates. Memory will be consumed in approximately linear dependency from number of nav mesh updates.
But only for new locations or already dropped from cache. But only for new locations or already dropped from cache.
min update interval ms
----------------
:Type: integer
:Range: >= 0
:Default: 250
Minimum time duration required to pass before next navmesh update for the same tile in milliseconds.
Only tiles affected where objects are transformed.
Next update for tile with added or removed object will not be delayed.
Visible ingame effect is navmesh update around opening or closing door.
Primary usage is for rotating signs like in Seyda Neen at Arrille's Tradehouse entrance.
Decreasing this value may increase CPU usage by background threads.
Developer's settings Developer's settings
******************** ********************

View File

@ -776,6 +776,9 @@ enable recast mesh render = false
# Max number of navmesh tiles (value >= 0) # Max number of navmesh tiles (value >= 0)
max tiles number = 512 max tiles number = 512
# Min time duration for the same tile update in milliseconds (value >= 0)
min update interval ms = 250
[Shadows] [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. # Enable or disable shadows. Bear in mind that this will force OpenMW to use shaders as if "[Shaders]/force shaders" was set to true.