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:
parent
8c0674490d
commit
b150d681a9
@ -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";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
@ -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
|
||||||
********************
|
********************
|
||||||
|
|
||||||
|
@ -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.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user