Merge pull request #2225 from elsid/retry_async_navmesh_update_job

Support explicit limit of navmesh tiles for scene
pull/2229/head
Bret Curtis 6 years ago committed by GitHub
commit c2a7aa2932
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -61,6 +61,7 @@ namespace
mSettings.mMaxSmoothPathSize = 1024;
mSettings.mTrianglesPerChunk = 256;
mSettings.mMaxPolys = 4096;
mSettings.mMaxTilesNumber = 512;
mNavigator.reset(new NavigatorImpl(mSettings));
}
};

@ -1,4 +1,4 @@
#include "asyncnavmeshupdater.hpp"
#include "asyncnavmeshupdater.hpp"
#include "debug.hpp"
#include "makenavmesh.hpp"
#include "settings.hpp"
@ -14,16 +14,6 @@ namespace
{
return std::abs(lhs.x() - rhs.x()) + std::abs(lhs.y() - rhs.y());
}
std::tuple<ChangeType, int, int> makePriority(const TilePosition& position, const ChangeType changeType,
const TilePosition& playerTile)
{
return std::make_tuple(
changeType,
getManhattanDistance(position, playerTile),
getManhattanDistance(position, TilePosition {0, 0})
);
}
}
namespace DetourNavigator
@ -32,14 +22,18 @@ namespace DetourNavigator
{
switch (value)
{
case UpdateNavMeshStatus::ignore:
case UpdateNavMeshStatus::ignored:
return stream << "ignore";
case UpdateNavMeshStatus::removed:
return stream << "removed";
case UpdateNavMeshStatus::add:
case UpdateNavMeshStatus::added:
return stream << "add";
case UpdateNavMeshStatus::replaced:
return stream << "replaced";
case UpdateNavMeshStatus::failed:
return stream << "failed";
case UpdateNavMeshStatus::lost:
return stream << "lost";
}
return stream << "unknown";
}
@ -81,13 +75,25 @@ namespace DetourNavigator
for (const auto& changedTile : changedTiles)
{
if (mPushed[agentHalfExtents].insert(changedTile.first).second)
mJobs.push(Job {agentHalfExtents, navMeshCacheItem, changedTile.first,
makePriority(changedTile.first, changedTile.second, playerTile)});
{
Job job;
job.mAgentHalfExtents = agentHalfExtents;
job.mNavMeshCacheItem = navMeshCacheItem;
job.mChangedTile = changedTile.first;
job.mTryNumber = 0;
job.mChangeType = changedTile.second;
job.mDistanceToPlayer = getManhattanDistance(changedTile.first, playerTile);
job.mDistanceToOrigin = getManhattanDistance(changedTile.first, TilePosition {0, 0});
mJobs.push(std::move(job));
}
}
log("posted ", mJobs.size(), " jobs");
mHasJob.notify_all();
if (!mJobs.empty())
mHasJob.notify_all();
}
void AsyncNavMeshUpdater::wait()
@ -103,8 +109,9 @@ namespace DetourNavigator
{
try
{
if (const auto job = getNextJob())
processJob(*job);
if (auto job = getNextJob())
if (!processJob(*job))
repost(std::move(*job));
}
catch (const std::exception& e)
{
@ -114,7 +121,7 @@ namespace DetourNavigator
log("stop process jobs");
}
void AsyncNavMeshUpdater::processJob(const Job& job)
bool AsyncNavMeshUpdater::processJob(const Job& job)
{
log("process job for agent=", job.mAgentHalfExtents);
@ -135,12 +142,16 @@ namespace DetourNavigator
using FloatMs = std::chrono::duration<float, std::milli>;
const auto locked = job.mNavMeshCacheItem.lockConst();
log("cache updated for agent=", job.mAgentHalfExtents, " status=", status,
" generation=", locked->getGeneration(),
" revision=", locked->getNavMeshRevision(),
" time=", std::chrono::duration_cast<FloatMs>(finish - start).count(), "ms",
" total_time=", std::chrono::duration_cast<FloatMs>(finish - firstStart).count(), "ms");
{
const auto locked = job.mNavMeshCacheItem.lockConst();
log("cache updated for agent=", job.mAgentHalfExtents, " status=", status,
" generation=", locked->getGeneration(),
" revision=", locked->getNavMeshRevision(),
" time=", std::chrono::duration_cast<FloatMs>(finish - start).count(), "ms",
" total_time=", std::chrono::duration_cast<FloatMs>(finish - firstStart).count(), "ms");
}
return isSuccess(status);
}
boost::optional<AsyncNavMeshUpdater::Job> AsyncNavMeshUpdater::getNextJob()
@ -193,4 +204,19 @@ namespace DetourNavigator
*locked = value;
return *locked.get();
}
void AsyncNavMeshUpdater::repost(Job&& job)
{
if (mShouldStop || job.mTryNumber > 2)
return;
const std::lock_guard<std::mutex> lock(mMutex);
if (mPushed[job.mAgentHalfExtents].insert(job.mChangedTile).second)
{
++job.mTryNumber;
mJobs.push(std::move(job));
mHasJob.notify_all();
}
}
}

@ -50,11 +50,19 @@ namespace DetourNavigator
osg::Vec3f mAgentHalfExtents;
SharedNavMeshCacheItem mNavMeshCacheItem;
TilePosition mChangedTile;
std::tuple<ChangeType, int, int> mPriority;
unsigned mTryNumber;
ChangeType mChangeType;
int mDistanceToPlayer;
int mDistanceToOrigin;
std::tuple<unsigned, ChangeType, int, int> getPriority() const
{
return std::make_tuple(mTryNumber, mChangeType, mDistanceToPlayer, mDistanceToOrigin);
}
friend inline bool operator <(const Job& lhs, const Job& rhs)
{
return lhs.mPriority > rhs.mPriority;
return lhs.getPriority() > rhs.getPriority();
}
};
@ -76,13 +84,15 @@ namespace DetourNavigator
void process() throw();
void processJob(const Job& job);
bool processJob(const Job& job);
boost::optional<Job> getNextJob();
void writeDebugFiles(const Job& job, const RecastMesh* recastMesh) const;
std::chrono::steady_clock::time_point setFirstStart(const std::chrono::steady_clock::time_point& value);
void repost(Job&& job);
};
}

@ -441,17 +441,56 @@ namespace
return NavMeshData(navMeshData, navMeshDataSize);
}
UpdateNavMeshStatus makeUpdateNavMeshStatus(bool removed, bool add)
class UpdateNavMeshStatusBuilder
{
if (removed && add)
return UpdateNavMeshStatus::replaced;
else if (removed)
return UpdateNavMeshStatus::removed;
else if (add)
return UpdateNavMeshStatus::add;
else
return UpdateNavMeshStatus::ignore;
}
public:
UpdateNavMeshStatusBuilder() = default;
UpdateNavMeshStatusBuilder removed(bool value)
{
if (value)
set(UpdateNavMeshStatus::removed);
else
unset(UpdateNavMeshStatus::removed);
return *this;
}
UpdateNavMeshStatusBuilder added(bool value)
{
if (value)
set(UpdateNavMeshStatus::added);
else
unset(UpdateNavMeshStatus::added);
return *this;
}
UpdateNavMeshStatusBuilder failed(bool value)
{
if (value)
set(UpdateNavMeshStatus::failed);
else
unset(UpdateNavMeshStatus::failed);
return *this;
}
UpdateNavMeshStatus getResult() const
{
return mResult;
}
private:
UpdateNavMeshStatus mResult = UpdateNavMeshStatus::ignored;
void set(UpdateNavMeshStatus value)
{
mResult = static_cast<UpdateNavMeshStatus>(static_cast<unsigned>(mResult) | static_cast<unsigned>(value));
}
void unset(UpdateNavMeshStatus value)
{
mResult = static_cast<UpdateNavMeshStatus>(static_cast<unsigned>(mResult) & ~static_cast<unsigned>(value));
}
};
template <class T>
unsigned long getMinValuableBitsNumber(const T value)
@ -461,6 +500,49 @@ namespace
++power;
return power;
}
dtStatus addTile(dtNavMesh& navMesh, const NavMeshData& navMeshData)
{
const dtTileRef lastRef = 0;
dtTileRef* const result = nullptr;
return navMesh.addTile(navMeshData.mValue.get(), navMeshData.mSize,
doNotTransferOwnership, lastRef, result);
}
dtStatus addTile(dtNavMesh& navMesh, const NavMeshTilesCache::Value& cachedNavMeshData)
{
const dtTileRef lastRef = 0;
dtTileRef* const result = nullptr;
return navMesh.addTile(cachedNavMeshData.get().mValue, cachedNavMeshData.get().mSize,
doNotTransferOwnership, lastRef, result);
}
template <class T>
UpdateNavMeshStatus replaceTile(const SharedNavMeshCacheItem& navMeshCacheItem,
const TilePosition& changedTile, T&& navMeshData)
{
const auto locked = navMeshCacheItem.lock();
auto& navMesh = locked->getValue();
const int layer = 0;
const auto tileRef = navMesh.getTileRefAt(changedTile.x(), changedTile.y(), layer);
unsigned char** const data = nullptr;
int* const dataSize = nullptr;
const auto removed = dtStatusSucceed(navMesh.removeTile(tileRef, data, dataSize));
const auto addStatus = addTile(navMesh, navMeshData);
if (dtStatusSucceed(addStatus))
{
locked->setUsedTile(changedTile, std::forward<T>(navMeshData));
return UpdateNavMeshStatusBuilder().added(true).removed(removed).getResult();
}
else
{
if (removed)
locked->removeUsedTile(changedTile);
log("failed to add tile with status=", WriteDtStatus {addStatus});
return UpdateNavMeshStatusBuilder().removed(removed).failed((addStatus & DT_OUT_OF_MEMORY) != 0).getResult();
}
}
}
namespace DetourNavigator
@ -522,7 +604,7 @@ namespace DetourNavigator
const auto removed = dtStatusSucceed(navMesh.removeTile(tileRef, nullptr, nullptr));
if (removed)
locked->removeUsedTile(changedTile);
return makeUpdateNavMeshStatus(removed, false);
return UpdateNavMeshStatusBuilder().removed(removed).getResult();
};
if (!recastMesh)
@ -546,7 +628,7 @@ namespace DetourNavigator
return removeTile();
}
if (!shouldAddTile(changedTile, playerTile, params.maxTiles))
if (!shouldAddTile(changedTile, playerTile, std::min(settings.mMaxTilesNumber, params.maxTiles)))
{
log("ignore add tile: too far from player");
return removeTile();
@ -583,47 +665,10 @@ namespace DetourNavigator
if (!cachedNavMeshData)
{
log("cache overflow");
const auto locked = navMeshCacheItem.lock();
auto& navMesh = locked->getValue();
const auto tileRef = navMesh.getTileRefAt(x, y, 0);
const auto removed = dtStatusSucceed(navMesh.removeTile(tileRef, nullptr, nullptr));
const auto addStatus = navMesh.addTile(navMeshData.mValue.get(), navMeshData.mSize,
doNotTransferOwnership, 0, 0);
if (dtStatusSucceed(addStatus))
{
locked->setUsedTile(changedTile, std::move(navMeshData));
return makeUpdateNavMeshStatus(removed, true);
}
else
{
if (removed)
locked->removeUsedTile(changedTile);
log("failed to add tile with status=", WriteDtStatus {addStatus});
return makeUpdateNavMeshStatus(removed, false);
}
return replaceTile(navMeshCacheItem, changedTile, std::move(navMeshData));
}
}
const auto locked = navMeshCacheItem.lock();
auto& navMesh = locked->getValue();
const auto tileRef = navMesh.getTileRefAt(x, y, 0);
const auto removed = dtStatusSucceed(navMesh.removeTile(tileRef, nullptr, nullptr));
const auto addStatus = navMesh.addTile(cachedNavMeshData.get().mValue, cachedNavMeshData.get().mSize,
doNotTransferOwnership, 0, 0);
if (dtStatusSucceed(addStatus))
{
locked->setUsedTile(changedTile, std::move(cachedNavMeshData));
return makeUpdateNavMeshStatus(removed, true);
}
else
{
if (removed)
locked->removeUsedTile(changedTile);
log("failed to add tile with status=", WriteDtStatus {addStatus});
return makeUpdateNavMeshStatus(removed, false);
}
return replaceTile(navMeshCacheItem, changedTile, std::move(cachedNavMeshData));
}
}

@ -20,14 +20,21 @@ namespace DetourNavigator
class RecastMesh;
struct Settings;
enum class UpdateNavMeshStatus
enum class UpdateNavMeshStatus : unsigned
{
ignore,
removed,
add,
replaced
ignored = 0,
removed = 1 << 0,
added = 1 << 1,
replaced = removed | added,
failed = 1 << 2,
lost = removed | failed,
};
inline bool isSuccess(UpdateNavMeshStatus value)
{
return (static_cast<unsigned>(value) & static_cast<unsigned>(UpdateNavMeshStatus::failed)) == 0;
}
inline float getLength(const osg::Vec2i& value)
{
return std::sqrt(float(osg::square(value.x()) + osg::square(value.y())));
@ -41,7 +48,7 @@ namespace DetourNavigator
inline bool shouldAddTile(const TilePosition& changedTile, const TilePosition& playerTile, int maxTiles)
{
const auto expectedTilesCount = std::ceil(osg::PI * osg::square(getDistance(changedTile, playerTile)));
return expectedTilesCount * 3 <= maxTiles;
return expectedTilesCount <= maxTiles;
}
NavMeshPtr makeEmptyNavMesh(const Settings& settings);

@ -157,7 +157,7 @@ namespace DetourNavigator
if (changedTiles->second.empty())
mChangedTiles.erase(changedTiles);
}
const auto maxTiles = navMesh.getParams()->maxTiles;
const auto maxTiles = std::min(mSettings.mMaxTilesNumber, navMesh.getParams()->maxTiles);
mRecastMeshManager.forEachTilePosition([&] (const TilePosition& tile)
{
if (tilesToPost.count(tile))

@ -62,8 +62,8 @@ namespace DetourNavigator
return Value();
// TODO: use different function to make key to avoid unnecessary std::string allocation
const auto tile = tileValues->second.Map.find(makeNavMeshKey(recastMesh, offMeshConnections));
if (tile == tileValues->second.Map.end())
const auto tile = tileValues->second.mMap.find(makeNavMeshKey(recastMesh, offMeshConnections));
if (tile == tileValues->second.mMap.end())
return Value();
acquireItemUnsafe(tile->second);
@ -96,7 +96,7 @@ namespace DetourNavigator
const auto iterator = mFreeItems.emplace(mFreeItems.end(), agentHalfExtents, changedTile, navMeshKey);
// TODO: use std::string_view or some alternative to avoid navMeshKey copy into both mFreeItems and mValues
const auto emplaced = mValues[agentHalfExtents][changedTile].Map.emplace(navMeshKey, iterator);
const auto emplaced = mValues[agentHalfExtents][changedTile].mMap.emplace(navMeshKey, iterator);
if (!emplaced.second)
{
@ -125,16 +125,16 @@ namespace DetourNavigator
if (tileValues == agentValues->second.end())
return;
const auto value = tileValues->second.Map.find(item.mNavMeshKey);
if (value == tileValues->second.Map.end())
const auto value = tileValues->second.mMap.find(item.mNavMeshKey);
if (value == tileValues->second.mMap.end())
return;
mUsedNavMeshDataSize -= getSize(item);
mFreeNavMeshDataSize -= getSize(item);
mFreeItems.pop_back();
tileValues->second.Map.erase(value);
if (!tileValues->second.Map.empty())
tileValues->second.mMap.erase(value);
if (!tileValues->second.mMap.empty())
return;
agentValues->second.erase(tileValues);

@ -108,7 +108,7 @@ namespace DetourNavigator
struct TileMap
{
std::map<std::string, ItemIterator> Map;
std::map<std::string, ItemIterator> mMap;
};
std::mutex mMutex;

@ -24,6 +24,7 @@ namespace DetourNavigator
navigatorSettings.mMaxEdgeLen = ::Settings::Manager::getInt("max edge len", "Navigator");
navigatorSettings.mMaxNavMeshQueryNodes = ::Settings::Manager::getInt("max nav mesh query nodes", "Navigator");
navigatorSettings.mMaxPolys = ::Settings::Manager::getInt("max polygons per tile", "Navigator");
navigatorSettings.mMaxTilesNumber = ::Settings::Manager::getInt("max tiles number", "Navigator");
navigatorSettings.mMaxVertsPerPoly = ::Settings::Manager::getInt("max verts per poly", "Navigator");
navigatorSettings.mRegionMergeSize = ::Settings::Manager::getInt("region merge size", "Navigator");
navigatorSettings.mRegionMinSize = ::Settings::Manager::getInt("region min size", "Navigator");

@ -26,6 +26,7 @@ namespace DetourNavigator
int mMaxEdgeLen = 0;
int mMaxNavMeshQueryNodes = 0;
int mMaxPolys = 0;
int mMaxTilesNumber = 0;
int mMaxVertsPerPoly = 0;
int mRegionMergeSize = 0;
int mRegionMinSize = 0;

@ -24,6 +24,24 @@ Moving across external world, entering/exiting location produce nav mesh update.
NPC and creatures may not be able to find path before nav mesh is built around them.
Try to disable this if you want to have old fashioned AI which doesn't know where to go when you stand behind that stone and casting a firebolt.
max tiles number
----------------
:Type: integer
:Range: >= 0
:Default: 512
Number of tiles at nav mesh.
Nav mesh covers circle area around player.
This option allows to set an explicit limit for nav mesh size, how many tiles should fit into circle.
If actor is inside this area it able to find path over nav mesh.
Increasing this value may decrease performance.
.. note::
Don't expect infinite nav mesh size increasing.
This condition is always true: ``max tiles number * max polygons per tile <= 4194304``.
It's a limitation of `Recastnavigation <https://github.com/recastnavigation/recastnavigation>`_ library.
Advanced settings
*****************
@ -322,6 +340,12 @@ Maximum number of polygons per nav mesh tile. Maximum number of nav mesh tiles d
this value. 22 bits is a limit to store both tile identifier and polygon identifier (tiles = 2^(22 - log2(polygons))).
See `recastnavigation <https://github.com/recastnavigation/recastnavigation>`_ for more details.
.. Warning::
Lower value may lead to ignored world geometry on nav mesh.
Greater value will reduce number of nav mesh tiles.
This condition is always true: ``max tiles number * max polygons per tile <= 4194304``.
It's a limitation of `Recastnavigation <https://github.com/recastnavigation/recastnavigation>`_ library.
max verts per poly
------------------

@ -670,6 +670,9 @@ enable nav mesh render = false
# Render agents paths (true, false)
enable agents paths render = false
# Max number of navmesh tiles (value >= 0)
max tiles number = 512
[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