Update same navmesh tile with limited frequency

pull/2708/head
elsid 4 years ago
parent 8c0674490d
commit b150d681a9
No known key found for this signature in database
GPG Key ID: B845CB9FEE18AB40

@ -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…
Cancel
Save