mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-01-21 09:23:51 +00:00
Merge pull request #2708 from elsid/detournavigator_limit_update_frequency
Limit frequency for navmesh updates
This commit is contained in:
commit
a23ab48251
7 changed files with 132 additions and 12 deletions
|
@ -73,6 +73,7 @@ namespace
|
|||
mSettings.mTrianglesPerChunk = 256;
|
||||
mSettings.mMaxPolys = 4096;
|
||||
mSettings.mMaxTilesNumber = 512;
|
||||
mSettings.mMinUpdateInterval = std::chrono::milliseconds(50);
|
||||
mNavigator.reset(new NavigatorImpl(mSettings));
|
||||
}
|
||||
};
|
||||
|
@ -766,4 +767,41 @@ namespace
|
|||
Vec3fEq(215, -215, 1.8782813549041748046875)
|
||||
)) << 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";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -89,6 +89,9 @@ namespace DetourNavigator
|
|||
job.mChangeType = changedTile.second;
|
||||
job.mDistanceToPlayer = getManhattanDistance(changedTile.first, playerTile);
|
||||
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));
|
||||
}
|
||||
|
@ -137,6 +140,8 @@ namespace DetourNavigator
|
|||
if (!processed)
|
||||
repost(std::move(*job));
|
||||
}
|
||||
else
|
||||
cleanupLastUpdates();
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
|
@ -176,6 +181,7 @@ namespace DetourNavigator
|
|||
const auto locked = navMeshCacheItem->lockConst();
|
||||
Log(Debug::Debug) << std::fixed << std::setprecision(2) <<
|
||||
"Cache updated for agent=(" << job.mAgentHalfExtents << ")" <<
|
||||
" tile=" << job.mChangedTile <<
|
||||
" status=" << status <<
|
||||
" generation=" << locked->getGeneration() <<
|
||||
" revision=" << locked->getNavMeshRevision() <<
|
||||
|
@ -195,12 +201,15 @@ namespace DetourNavigator
|
|||
|
||||
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))
|
||||
{
|
||||
mFirstStart.lock()->reset();
|
||||
if (getTotalThreadJobsUnsafe() == 0)
|
||||
if (mJobs.empty() && getTotalThreadJobsUnsafe() == 0)
|
||||
mDone.notify_all();
|
||||
return boost::none;
|
||||
}
|
||||
|
@ -209,29 +218,40 @@ namespace DetourNavigator
|
|||
<< threadQueue.mJobs.size() << " thread jobs by thread=" << std::this_thread::get_id();
|
||||
|
||||
auto job = threadQueue.mJobs.empty()
|
||||
? getJob(mJobs, mPushed)
|
||||
: getJob(threadQueue.mJobs, threadQueue.mPushed);
|
||||
? getJob(mJobs, mPushed, true)
|
||||
: 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)
|
||||
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();
|
||||
|
||||
if (changeLastUpdate && job.mChangeType == ChangeType::update)
|
||||
mLastUpdates[job.mAgentHalfExtents][job.mChangedTile] = now;
|
||||
|
||||
const auto it = pushed.find(job.mAgentHalfExtents);
|
||||
it->second.erase(job.mChangedTile);
|
||||
if (it->second.empty())
|
||||
pushed.erase(it);
|
||||
|
||||
return job;
|
||||
return {std::move(job)};
|
||||
}
|
||||
|
||||
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),
|
||||
[] (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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,6 +32,21 @@ namespace DetourNavigator
|
|||
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
|
||||
{
|
||||
public:
|
||||
|
@ -56,10 +71,11 @@ namespace DetourNavigator
|
|||
ChangeType mChangeType;
|
||||
int mDistanceToPlayer;
|
||||
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)
|
||||
|
@ -93,6 +109,7 @@ namespace DetourNavigator
|
|||
Misc::ScopeGuarded<boost::optional<std::chrono::steady_clock::time_point>> mFirstStart;
|
||||
NavMeshTilesCache mNavMeshTilesCache;
|
||||
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::vector<std::thread> mThreads;
|
||||
|
||||
|
@ -102,7 +119,7 @@ namespace DetourNavigator
|
|||
|
||||
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);
|
||||
|
||||
|
@ -117,6 +134,8 @@ namespace DetourNavigator
|
|||
void unlockTile(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile);
|
||||
|
||||
inline std::size_t getTotalThreadJobsUnsafe() const;
|
||||
|
||||
void cleanupLastUpdates();
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -40,6 +40,7 @@ namespace DetourNavigator
|
|||
navigatorSettings.mNavMeshPathPrefix = ::Settings::Manager::getString("nav mesh path prefix", "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.mMinUpdateInterval = std::chrono::milliseconds(::Settings::Manager::getInt("min update interval ms", "Navigator"));
|
||||
|
||||
return navigatorSettings;
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include <boost/optional.hpp>
|
||||
|
||||
#include <string>
|
||||
#include <chrono>
|
||||
|
||||
namespace DetourNavigator
|
||||
{
|
||||
|
@ -38,6 +39,7 @@ namespace DetourNavigator
|
|||
std::size_t mTrianglesPerChunk = 0;
|
||||
std::string mRecastMeshPathPrefix;
|
||||
std::string mNavMeshPathPrefix;
|
||||
std::chrono::milliseconds mMinUpdateInterval;
|
||||
};
|
||||
|
||||
boost::optional<Settings> makeSettingsFromSettingsManager();
|
||||
|
|
|
@ -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.
|
||||
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
|
||||
********************
|
||||
|
||||
|
|
|
@ -776,6 +776,9 @@ enable recast mesh render = false
|
|||
# Max number of navmesh tiles (value >= 0)
|
||||
max tiles number = 512
|
||||
|
||||
# Min time duration for the same tile update in milliseconds (value >= 0)
|
||||
min update interval ms = 250
|
||||
|
||||
[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…
Reference in a new issue