mirror of
https://github.com/OpenMW/openmw.git
synced 2025-04-01 15:36:40 +00:00
Merge branch 'navmeshdb_max_file_size' into 'master'
Limit max navmeshdb file size (#6649) Closes #6649 See merge request OpenMW/openmw!1716
This commit is contained in:
commit
1f14f97d17
19 changed files with 242 additions and 68 deletions
|
@ -156,8 +156,9 @@ namespace NavMeshTool
|
||||||
settings.load(config);
|
settings.load(config);
|
||||||
|
|
||||||
const osg::Vec3f agentHalfExtents = Settings::Manager::getVector3("default actor pathfind half extents", "Game");
|
const osg::Vec3f agentHalfExtents = Settings::Manager::getVector3("default actor pathfind half extents", "Game");
|
||||||
|
const std::uint64_t maxDbFileSize = static_cast<std::uint64_t>(Settings::Manager::getInt64("max navmeshdb file size", "Navigator"));
|
||||||
|
|
||||||
DetourNavigator::NavMeshDb db((config.getUserDataPath() / "navmesh.db").string());
|
DetourNavigator::NavMeshDb db((config.getUserDataPath() / "navmesh.db").string(), maxDbFileSize);
|
||||||
|
|
||||||
std::vector<ESM::ESMReader> readers(contentFiles.size());
|
std::vector<ESM::ESMReader> readers(contentFiles.size());
|
||||||
EsmLoader::Query query;
|
EsmLoader::Query query;
|
||||||
|
|
|
@ -67,7 +67,7 @@ namespace NavMeshTool
|
||||||
explicit NavMeshTileConsumer(NavMeshDb&& db, bool removeUnusedTiles)
|
explicit NavMeshTileConsumer(NavMeshDb&& db, bool removeUnusedTiles)
|
||||||
: mDb(std::move(db))
|
: mDb(std::move(db))
|
||||||
, mRemoveUnusedTiles(removeUnusedTiles)
|
, mRemoveUnusedTiles(removeUnusedTiles)
|
||||||
, mTransaction(mDb.startTransaction())
|
, mTransaction(mDb.startTransaction(Sqlite3::TransactionMode::Immediate))
|
||||||
, mNextTileId(mDb.getMaxTileId() + 1)
|
, mNextTileId(mDb.getMaxTileId() + 1)
|
||||||
, mNextShapeId(mDb.getMaxShapeId() + 1)
|
, mNextShapeId(mDb.getMaxShapeId() + 1)
|
||||||
{}
|
{}
|
||||||
|
@ -128,14 +128,11 @@ namespace NavMeshTool
|
||||||
void insert(std::string_view worldspace, const TilePosition& tilePosition,
|
void insert(std::string_view worldspace, const TilePosition& tilePosition,
|
||||||
std::int64_t version, const std::vector<std::byte>& input, PreparedNavMeshData& data) override
|
std::int64_t version, const std::vector<std::byte>& input, PreparedNavMeshData& data) override
|
||||||
{
|
{
|
||||||
if (mRemoveUnusedTiles)
|
|
||||||
{
|
|
||||||
std::lock_guard lock(mMutex);
|
|
||||||
mDeleted += static_cast<std::size_t>(mDb.deleteTilesAt(worldspace, tilePosition));
|
|
||||||
}
|
|
||||||
data.mUserId = static_cast<unsigned>(mNextTileId);
|
|
||||||
{
|
{
|
||||||
std::lock_guard lock(mMutex);
|
std::lock_guard lock(mMutex);
|
||||||
|
if (mRemoveUnusedTiles)
|
||||||
|
mDeleted += static_cast<std::size_t>(mDb.deleteTilesAt(worldspace, tilePosition));
|
||||||
|
data.mUserId = static_cast<unsigned>(mNextTileId);
|
||||||
mDb.insertTile(mNextTileId, worldspace, tilePosition, TileVersion {version}, input, serialize(data));
|
mDb.insertTile(mNextTileId, worldspace, tilePosition, TileVersion {version}, input, serialize(data));
|
||||||
++mNextTileId;
|
++mNextTileId;
|
||||||
}
|
}
|
||||||
|
@ -157,25 +154,44 @@ namespace NavMeshTool
|
||||||
report();
|
report();
|
||||||
}
|
}
|
||||||
|
|
||||||
void wait()
|
void cancel() override
|
||||||
{
|
{
|
||||||
constexpr std::size_t tilesPerTransaction = 3000;
|
|
||||||
std::unique_lock lock(mMutex);
|
std::unique_lock lock(mMutex);
|
||||||
while (mProvided < mExpected)
|
mCancelled = true;
|
||||||
|
mHasTile.notify_one();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool wait()
|
||||||
|
{
|
||||||
|
constexpr std::chrono::seconds transactionInterval(1);
|
||||||
|
std::unique_lock lock(mMutex);
|
||||||
|
auto start = std::chrono::steady_clock::now();
|
||||||
|
while (mProvided < mExpected && !mCancelled)
|
||||||
{
|
{
|
||||||
mHasTile.wait(lock);
|
mHasTile.wait(lock);
|
||||||
if (mProvided % tilesPerTransaction == 0)
|
const auto now = std::chrono::steady_clock::now();
|
||||||
|
if (now - start > transactionInterval)
|
||||||
{
|
{
|
||||||
mTransaction.commit();
|
mTransaction.commit();
|
||||||
mTransaction = mDb.startTransaction();
|
mTransaction = mDb.startTransaction(Sqlite3::TransactionMode::Immediate);
|
||||||
|
start = now;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
logGeneratedTiles(mProvided, mExpected);
|
logGeneratedTiles(mProvided, mExpected);
|
||||||
|
return !mCancelled;
|
||||||
}
|
}
|
||||||
|
|
||||||
void commit() { mTransaction.commit(); }
|
void commit()
|
||||||
|
{
|
||||||
|
const std::lock_guard lock(mMutex);
|
||||||
|
mTransaction.commit();
|
||||||
|
}
|
||||||
|
|
||||||
void vacuum() { mDb.vacuum(); }
|
void vacuum()
|
||||||
|
{
|
||||||
|
const std::lock_guard lock(mMutex);
|
||||||
|
mDb.vacuum();
|
||||||
|
}
|
||||||
|
|
||||||
void removeTilesOutsideRange(std::string_view worldspace, const TilesPositionsRange& range)
|
void removeTilesOutsideRange(std::string_view worldspace, const TilesPositionsRange& range)
|
||||||
{
|
{
|
||||||
|
@ -183,7 +199,7 @@ namespace NavMeshTool
|
||||||
mTransaction.commit();
|
mTransaction.commit();
|
||||||
Log(Debug::Info) << "Removing tiles outside processed range for worldspace \"" << worldspace << "\"...";
|
Log(Debug::Info) << "Removing tiles outside processed range for worldspace \"" << worldspace << "\"...";
|
||||||
mDeleted += static_cast<std::size_t>(mDb.deleteTilesOutsideRange(worldspace, range));
|
mDeleted += static_cast<std::size_t>(mDb.deleteTilesOutsideRange(worldspace, range));
|
||||||
mTransaction = mDb.startTransaction();
|
mTransaction = mDb.startTransaction(Sqlite3::TransactionMode::Immediate);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -191,6 +207,7 @@ namespace NavMeshTool
|
||||||
std::atomic_size_t mInserted {0};
|
std::atomic_size_t mInserted {0};
|
||||||
std::atomic_size_t mUpdated {0};
|
std::atomic_size_t mUpdated {0};
|
||||||
std::size_t mDeleted = 0;
|
std::size_t mDeleted = 0;
|
||||||
|
bool mCancelled = false;
|
||||||
mutable std::mutex mMutex;
|
mutable std::mutex mMutex;
|
||||||
NavMeshDb mDb;
|
NavMeshDb mDb;
|
||||||
const bool mRemoveUnusedTiles;
|
const bool mRemoveUnusedTiles;
|
||||||
|
@ -211,7 +228,8 @@ namespace NavMeshTool
|
||||||
}
|
}
|
||||||
|
|
||||||
void generateAllNavMeshTiles(const osg::Vec3f& agentHalfExtents, const Settings& settings,
|
void generateAllNavMeshTiles(const osg::Vec3f& agentHalfExtents, const Settings& settings,
|
||||||
std::size_t threadsNumber, bool removeUnusedTiles, WorldspaceData& data, NavMeshDb&& db)
|
std::size_t threadsNumber, bool removeUnusedTiles, WorldspaceData& data,
|
||||||
|
NavMeshDb&& db)
|
||||||
{
|
{
|
||||||
Log(Debug::Info) << "Generating navmesh tiles by " << threadsNumber << " parallel workers...";
|
Log(Debug::Info) << "Generating navmesh tiles by " << threadsNumber << " parallel workers...";
|
||||||
|
|
||||||
|
@ -253,8 +271,8 @@ namespace NavMeshTool
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
navMeshTileConsumer->wait();
|
if (navMeshTileConsumer->wait())
|
||||||
navMeshTileConsumer->commit();
|
navMeshTileConsumer->commit();
|
||||||
|
|
||||||
const auto inserted = navMeshTileConsumer->getInserted();
|
const auto inserted = navMeshTileConsumer->getInserted();
|
||||||
const auto updated = navMeshTileConsumer->getUpdated();
|
const auto updated = navMeshTileConsumer->getUpdated();
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
@ -19,9 +20,8 @@ namespace
|
||||||
using namespace DetourNavigator;
|
using namespace DetourNavigator;
|
||||||
using namespace DetourNavigator::Tests;
|
using namespace DetourNavigator::Tests;
|
||||||
|
|
||||||
void addHeightFieldPlane(TileCachedRecastMeshManager& recastMeshManager)
|
void addHeightFieldPlane(TileCachedRecastMeshManager& recastMeshManager, const osg::Vec2i cellPosition = osg::Vec2i(0, 0))
|
||||||
{
|
{
|
||||||
const osg::Vec2i cellPosition(0, 0);
|
|
||||||
const int cellSize = 8192;
|
const int cellSize = 8192;
|
||||||
recastMeshManager.addHeightfield(cellPosition, cellSize, HeightfieldPlane {0});
|
recastMeshManager.addHeightfield(cellPosition, cellSize, HeightfieldPlane {0});
|
||||||
}
|
}
|
||||||
|
@ -130,7 +130,7 @@ namespace
|
||||||
mRecastMeshManager.setWorldspace(mWorldspace);
|
mRecastMeshManager.setWorldspace(mWorldspace);
|
||||||
addHeightFieldPlane(mRecastMeshManager);
|
addHeightFieldPlane(mRecastMeshManager);
|
||||||
addObject(mBox, mRecastMeshManager);
|
addObject(mBox, mRecastMeshManager);
|
||||||
auto db = std::make_unique<NavMeshDb>(":memory:");
|
auto db = std::make_unique<NavMeshDb>(":memory:", std::numeric_limits<std::uint64_t>::max());
|
||||||
NavMeshDb* const dbPtr = db.get();
|
NavMeshDb* const dbPtr = db.get();
|
||||||
AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, std::move(db));
|
AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, std::move(db));
|
||||||
const auto navMeshCacheItem = std::make_shared<GuardedNavMeshCacheItem>(makeEmptyNavMesh(mSettings), 1);
|
const auto navMeshCacheItem = std::make_shared<GuardedNavMeshCacheItem>(makeEmptyNavMesh(mSettings), 1);
|
||||||
|
@ -138,6 +138,7 @@ namespace
|
||||||
const std::map<TilePosition, ChangeType> changedTiles {{tilePosition, ChangeType::add}};
|
const std::map<TilePosition, ChangeType> changedTiles {{tilePosition, ChangeType::add}};
|
||||||
updater.post(mAgentHalfExtents, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles);
|
updater.post(mAgentHalfExtents, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles);
|
||||||
updater.wait(mListener, WaitConditionType::allJobsDone);
|
updater.wait(mListener, WaitConditionType::allJobsDone);
|
||||||
|
updater.stop();
|
||||||
const auto recastMesh = mRecastMeshManager.getMesh(mWorldspace, tilePosition);
|
const auto recastMesh = mRecastMeshManager.getMesh(mWorldspace, tilePosition);
|
||||||
ASSERT_NE(recastMesh, nullptr);
|
ASSERT_NE(recastMesh, nullptr);
|
||||||
ShapeId nextShapeId {1};
|
ShapeId nextShapeId {1};
|
||||||
|
@ -154,7 +155,7 @@ namespace
|
||||||
mRecastMeshManager.setWorldspace(mWorldspace);
|
mRecastMeshManager.setWorldspace(mWorldspace);
|
||||||
addHeightFieldPlane(mRecastMeshManager);
|
addHeightFieldPlane(mRecastMeshManager);
|
||||||
addObject(mBox, mRecastMeshManager);
|
addObject(mBox, mRecastMeshManager);
|
||||||
auto db = std::make_unique<NavMeshDb>(":memory:");
|
auto db = std::make_unique<NavMeshDb>(":memory:", std::numeric_limits<std::uint64_t>::max());
|
||||||
NavMeshDb* const dbPtr = db.get();
|
NavMeshDb* const dbPtr = db.get();
|
||||||
mSettings.mWriteToNavMeshDb = false;
|
mSettings.mWriteToNavMeshDb = false;
|
||||||
AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, std::move(db));
|
AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, std::move(db));
|
||||||
|
@ -163,6 +164,7 @@ namespace
|
||||||
const std::map<TilePosition, ChangeType> changedTiles {{tilePosition, ChangeType::add}};
|
const std::map<TilePosition, ChangeType> changedTiles {{tilePosition, ChangeType::add}};
|
||||||
updater.post(mAgentHalfExtents, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles);
|
updater.post(mAgentHalfExtents, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles);
|
||||||
updater.wait(mListener, WaitConditionType::allJobsDone);
|
updater.wait(mListener, WaitConditionType::allJobsDone);
|
||||||
|
updater.stop();
|
||||||
const auto recastMesh = mRecastMeshManager.getMesh(mWorldspace, tilePosition);
|
const auto recastMesh = mRecastMeshManager.getMesh(mWorldspace, tilePosition);
|
||||||
ASSERT_NE(recastMesh, nullptr);
|
ASSERT_NE(recastMesh, nullptr);
|
||||||
ShapeId nextShapeId {1};
|
ShapeId nextShapeId {1};
|
||||||
|
@ -177,7 +179,7 @@ namespace
|
||||||
mRecastMeshManager.setWorldspace(mWorldspace);
|
mRecastMeshManager.setWorldspace(mWorldspace);
|
||||||
addHeightFieldPlane(mRecastMeshManager);
|
addHeightFieldPlane(mRecastMeshManager);
|
||||||
addObject(mBox, mRecastMeshManager);
|
addObject(mBox, mRecastMeshManager);
|
||||||
auto db = std::make_unique<NavMeshDb>(":memory:");
|
auto db = std::make_unique<NavMeshDb>(":memory:", std::numeric_limits<std::uint64_t>::max());
|
||||||
NavMeshDb* const dbPtr = db.get();
|
NavMeshDb* const dbPtr = db.get();
|
||||||
mSettings.mWriteToNavMeshDb = false;
|
mSettings.mWriteToNavMeshDb = false;
|
||||||
AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, std::move(db));
|
AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, std::move(db));
|
||||||
|
@ -186,6 +188,7 @@ namespace
|
||||||
const std::map<TilePosition, ChangeType> changedTiles {{tilePosition, ChangeType::add}};
|
const std::map<TilePosition, ChangeType> changedTiles {{tilePosition, ChangeType::add}};
|
||||||
updater.post(mAgentHalfExtents, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles);
|
updater.post(mAgentHalfExtents, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles);
|
||||||
updater.wait(mListener, WaitConditionType::allJobsDone);
|
updater.wait(mListener, WaitConditionType::allJobsDone);
|
||||||
|
updater.stop();
|
||||||
const auto recastMesh = mRecastMeshManager.getMesh(mWorldspace, tilePosition);
|
const auto recastMesh = mRecastMeshManager.getMesh(mWorldspace, tilePosition);
|
||||||
ASSERT_NE(recastMesh, nullptr);
|
ASSERT_NE(recastMesh, nullptr);
|
||||||
const auto objects = makeDbRefGeometryObjects(recastMesh->getMeshSources(),
|
const auto objects = makeDbRefGeometryObjects(recastMesh->getMeshSources(),
|
||||||
|
@ -198,7 +201,8 @@ namespace
|
||||||
mRecastMeshManager.setWorldspace(mWorldspace);
|
mRecastMeshManager.setWorldspace(mWorldspace);
|
||||||
addHeightFieldPlane(mRecastMeshManager);
|
addHeightFieldPlane(mRecastMeshManager);
|
||||||
mSettings.mMaxNavMeshTilesCacheSize = 0;
|
mSettings.mMaxNavMeshTilesCacheSize = 0;
|
||||||
AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, std::make_unique<NavMeshDb>(":memory:"));
|
AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager,
|
||||||
|
std::make_unique<NavMeshDb>(":memory:", std::numeric_limits<std::uint64_t>::max()));
|
||||||
const auto navMeshCacheItem = std::make_shared<GuardedNavMeshCacheItem>(makeEmptyNavMesh(mSettings), 1);
|
const auto navMeshCacheItem = std::make_shared<GuardedNavMeshCacheItem>(makeEmptyNavMesh(mSettings), 1);
|
||||||
const std::map<TilePosition, ChangeType> changedTiles {{TilePosition {0, 0}, ChangeType::add}};
|
const std::map<TilePosition, ChangeType> changedTiles {{TilePosition {0, 0}, ChangeType::add}};
|
||||||
updater.post(mAgentHalfExtents, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles);
|
updater.post(mAgentHalfExtents, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles);
|
||||||
|
@ -239,4 +243,37 @@ namespace
|
||||||
updater.wait(mListener, WaitConditionType::allJobsDone);
|
updater.wait(mListener, WaitConditionType::allJobsDone);
|
||||||
EXPECT_EQ(navMeshCacheItem->lockConst()->getImpl().getTileRefAt(0, 0, 0), 0);
|
EXPECT_EQ(navMeshCacheItem->lockConst()->getImpl().getTileRefAt(0, 0, 0), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, should_stop_writing_to_db_when_size_limit_is_reached)
|
||||||
|
{
|
||||||
|
mRecastMeshManager.setWorldspace(mWorldspace);
|
||||||
|
for (int x = -1; x <= 1; ++x)
|
||||||
|
for (int y = -1; y <= 1; ++y)
|
||||||
|
addHeightFieldPlane(mRecastMeshManager, osg::Vec2i(x, y));
|
||||||
|
addObject(mBox, mRecastMeshManager);
|
||||||
|
auto db = std::make_unique<NavMeshDb>(":memory:", 4097);
|
||||||
|
NavMeshDb* const dbPtr = db.get();
|
||||||
|
AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, std::move(db));
|
||||||
|
const auto navMeshCacheItem = std::make_shared<GuardedNavMeshCacheItem>(makeEmptyNavMesh(mSettings), 1);
|
||||||
|
std::map<TilePosition, ChangeType> changedTiles;
|
||||||
|
for (int x = -5; x <= 5; ++x)
|
||||||
|
for (int y = -5; y <= 5; ++y)
|
||||||
|
changedTiles.emplace(TilePosition {x, y}, ChangeType::add);
|
||||||
|
updater.post(mAgentHalfExtents, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles);
|
||||||
|
updater.wait(mListener, WaitConditionType::allJobsDone);
|
||||||
|
updater.stop();
|
||||||
|
for (int x = -5; x <= 5; ++x)
|
||||||
|
for (int y = -5; y <= 5; ++y)
|
||||||
|
{
|
||||||
|
const TilePosition tilePosition(x, y);
|
||||||
|
const auto recastMesh = mRecastMeshManager.getMesh(mWorldspace, tilePosition);
|
||||||
|
ASSERT_NE(recastMesh, nullptr);
|
||||||
|
const std::optional<std::vector<DbRefGeometryObject>> objects = makeDbRefGeometryObjects(recastMesh->getMeshSources(),
|
||||||
|
[&] (const MeshSource& v) { return resolveMeshSource(*dbPtr, v); });
|
||||||
|
if (!objects.has_value())
|
||||||
|
continue;
|
||||||
|
EXPECT_FALSE(dbPtr->findTile(mWorldspace, tilePosition, serialize(mSettings.mRecast, *recastMesh, *objects)).has_value())
|
||||||
|
<< tilePosition.x() << " " << tilePosition.y();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <deque>
|
#include <deque>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
MATCHER_P3(Vec3fEq, x, y, z, "")
|
MATCHER_P3(Vec3fEq, x, y, z, "")
|
||||||
{
|
{
|
||||||
|
@ -64,7 +65,7 @@ namespace
|
||||||
, mOut(mPath)
|
, mOut(mPath)
|
||||||
, mStepSize(28.333332061767578125f)
|
, mStepSize(28.333332061767578125f)
|
||||||
{
|
{
|
||||||
mNavigator.reset(new NavigatorImpl(mSettings, std::make_unique<NavMeshDb>(":memory:")));
|
mNavigator.reset(new NavigatorImpl(mSettings, std::make_unique<NavMeshDb>(":memory:", std::numeric_limits<std::uint64_t>::max())));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -831,7 +832,7 @@ namespace
|
||||||
TEST_F(DetourNavigatorNavigatorTest, multiple_threads_should_lock_tiles)
|
TEST_F(DetourNavigatorNavigatorTest, multiple_threads_should_lock_tiles)
|
||||||
{
|
{
|
||||||
mSettings.mAsyncNavMeshUpdaterThreads = 2;
|
mSettings.mAsyncNavMeshUpdaterThreads = 2;
|
||||||
mNavigator.reset(new NavigatorImpl(mSettings, std::make_unique<NavMeshDb>(":memory:")));
|
mNavigator.reset(new NavigatorImpl(mSettings, std::make_unique<NavMeshDb>(":memory:", std::numeric_limits<std::uint64_t>::max())));
|
||||||
|
|
||||||
const std::array<float, 5 * 5> heightfieldData {{
|
const std::array<float, 5 * 5> heightfieldData {{
|
||||||
0, 0, 0, 0, 0,
|
0, 0, 0, 0, 0,
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
|
|
||||||
#include <numeric>
|
#include <numeric>
|
||||||
#include <random>
|
#include <random>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
@ -27,7 +28,7 @@ namespace
|
||||||
|
|
||||||
struct DetourNavigatorNavMeshDbTest : Test
|
struct DetourNavigatorNavMeshDbTest : Test
|
||||||
{
|
{
|
||||||
NavMeshDb mDb {":memory:"};
|
NavMeshDb mDb {":memory:", std::numeric_limits<std::uint64_t>::max()};
|
||||||
std::minstd_rand mRandom;
|
std::minstd_rand mRandom;
|
||||||
|
|
||||||
std::vector<std::byte> generateData()
|
std::vector<std::byte> generateData()
|
||||||
|
@ -166,4 +167,15 @@ namespace
|
||||||
ASSERT_EQ(mDb.findTile(worldspace, TilePosition {x, y}, input).has_value(),
|
ASSERT_EQ(mDb.findTile(worldspace, TilePosition {x, y}, input).has_value(),
|
||||||
-1 <= x && x <= 1 && -1 <= y && y <= 1) << "x=" << x << " y=" << y;
|
-1 <= x && x <= 1 && -1 <= y && y <= 1) << "x=" << x << " y=" << y;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(DetourNavigatorNavMeshDbTest, should_support_file_size_limit)
|
||||||
|
{
|
||||||
|
mDb = NavMeshDb(":memory:", 4096);
|
||||||
|
const auto f = [&]
|
||||||
|
{
|
||||||
|
for (std::int64_t i = 1; i <= 100; ++i)
|
||||||
|
insertTile(TileId {i}, TileVersion {1});
|
||||||
|
};
|
||||||
|
EXPECT_THROW(f(), std::runtime_error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -140,15 +140,7 @@ namespace DetourNavigator
|
||||||
|
|
||||||
AsyncNavMeshUpdater::~AsyncNavMeshUpdater()
|
AsyncNavMeshUpdater::~AsyncNavMeshUpdater()
|
||||||
{
|
{
|
||||||
mShouldStop = true;
|
stop();
|
||||||
if (mDbWorker != nullptr)
|
|
||||||
mDbWorker->stop();
|
|
||||||
std::unique_lock<std::mutex> lock(mMutex);
|
|
||||||
mWaiting.clear();
|
|
||||||
mHasJob.notify_all();
|
|
||||||
lock.unlock();
|
|
||||||
for (auto& thread : mThreads)
|
|
||||||
thread.join();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AsyncNavMeshUpdater::post(const osg::Vec3f& agentHalfExtents, const SharedNavMeshCacheItem& navMeshCacheItem,
|
void AsyncNavMeshUpdater::post(const osg::Vec3f& agentHalfExtents, const SharedNavMeshCacheItem& navMeshCacheItem,
|
||||||
|
@ -235,6 +227,20 @@ namespace DetourNavigator
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AsyncNavMeshUpdater::stop()
|
||||||
|
{
|
||||||
|
mShouldStop = true;
|
||||||
|
if (mDbWorker != nullptr)
|
||||||
|
mDbWorker->stop();
|
||||||
|
std::unique_lock<std::mutex> lock(mMutex);
|
||||||
|
mWaiting.clear();
|
||||||
|
mHasJob.notify_all();
|
||||||
|
lock.unlock();
|
||||||
|
for (auto& thread : mThreads)
|
||||||
|
if (thread.joinable())
|
||||||
|
thread.join();
|
||||||
|
}
|
||||||
|
|
||||||
int AsyncNavMeshUpdater::waitUntilJobsDoneForNotPresentTiles(const std::size_t initialJobsLeft, std::size_t& maxProgress, Loading::Listener& listener)
|
int AsyncNavMeshUpdater::waitUntilJobsDoneForNotPresentTiles(const std::size_t initialJobsLeft, std::size_t& maxProgress, Loading::Listener& listener)
|
||||||
{
|
{
|
||||||
std::size_t prevJobsLeft = initialJobsLeft;
|
std::size_t prevJobsLeft = initialJobsLeft;
|
||||||
|
@ -672,10 +678,10 @@ namespace DetourNavigator
|
||||||
mHasJob.notify_all();
|
mHasJob.notify_all();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<JobIt> DbJobQueue::pop()
|
std::optional<JobIt> DbJobQueue::pop(std::chrono::steady_clock::duration timeout)
|
||||||
{
|
{
|
||||||
std::unique_lock lock(mMutex);
|
std::unique_lock lock(mMutex);
|
||||||
mHasJob.wait(lock, [&] { return mShouldStop || !mJobs.empty(); });
|
mHasJob.wait_for(lock, timeout, [&] { return mShouldStop || !mJobs.empty(); });
|
||||||
if (mJobs.empty())
|
if (mJobs.empty())
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
const JobIt job = mJobs.front();
|
const JobIt job = mJobs.front();
|
||||||
|
@ -720,7 +726,6 @@ namespace DetourNavigator
|
||||||
DbWorker::~DbWorker()
|
DbWorker::~DbWorker()
|
||||||
{
|
{
|
||||||
stop();
|
stop();
|
||||||
mThread.join();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DbWorker::enqueueJob(JobIt job)
|
void DbWorker::enqueueJob(JobIt job)
|
||||||
|
@ -741,23 +746,35 @@ namespace DetourNavigator
|
||||||
{
|
{
|
||||||
mShouldStop = true;
|
mShouldStop = true;
|
||||||
mQueue.stop();
|
mQueue.stop();
|
||||||
|
if (mThread.joinable())
|
||||||
|
mThread.join();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DbWorker::run() noexcept
|
void DbWorker::run() noexcept
|
||||||
{
|
{
|
||||||
constexpr std::size_t writesPerTransaction = 100;
|
constexpr std::chrono::seconds transactionInterval(1);
|
||||||
auto transaction = mDb->startTransaction();
|
auto transaction = mDb->startTransaction(Sqlite3::TransactionMode::Immediate);
|
||||||
|
auto start = std::chrono::steady_clock::now();
|
||||||
while (!mShouldStop)
|
while (!mShouldStop)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (const auto job = mQueue.pop())
|
if (const auto job = mQueue.pop(transactionInterval))
|
||||||
processJob(*job);
|
processJob(*job);
|
||||||
if (mWrites > writesPerTransaction)
|
const auto now = std::chrono::steady_clock::now();
|
||||||
|
if (mHasChanges && now - start > transactionInterval)
|
||||||
{
|
{
|
||||||
mWrites = 0;
|
mHasChanges = false;
|
||||||
transaction.commit();
|
try
|
||||||
transaction = mDb->startTransaction();
|
{
|
||||||
|
transaction.commit();
|
||||||
|
}
|
||||||
|
catch (const std::exception& e)
|
||||||
|
{
|
||||||
|
Log(Debug::Error) << "DbWorker exception on commit: " << e.what();
|
||||||
|
}
|
||||||
|
transaction = mDb->startTransaction(Sqlite3::TransactionMode::Immediate);
|
||||||
|
start = now;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (const std::exception& e)
|
catch (const std::exception& e)
|
||||||
|
@ -765,7 +782,15 @@ namespace DetourNavigator
|
||||||
Log(Debug::Error) << "DbWorker exception: " << e.what();
|
Log(Debug::Error) << "DbWorker exception: " << e.what();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
transaction.commit();
|
if (mHasChanges)
|
||||||
|
try
|
||||||
|
{
|
||||||
|
transaction.commit();
|
||||||
|
}
|
||||||
|
catch (const std::exception& e)
|
||||||
|
{
|
||||||
|
Log(Debug::Error) << "DbWorker exception on final commit: " << e.what();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DbWorker::processJob(JobIt job)
|
void DbWorker::processJob(JobIt job)
|
||||||
|
@ -779,6 +804,11 @@ namespace DetourNavigator
|
||||||
catch (const std::exception& e)
|
catch (const std::exception& e)
|
||||||
{
|
{
|
||||||
Log(Debug::Error) << "DbWorker exception while processing job " << job->mId << ": " << e.what();
|
Log(Debug::Error) << "DbWorker exception while processing job " << job->mId << ": " << e.what();
|
||||||
|
if (std::string_view(e.what()).find("database or disk is full") != std::string_view::npos)
|
||||||
|
{
|
||||||
|
mWriteToDb = false;
|
||||||
|
Log(Debug::Warning) << "Writes to navmeshdb are disabled because file size limit is reached or disk is full";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -807,7 +837,7 @@ namespace DetourNavigator
|
||||||
const auto objects = makeDbRefGeometryObjects(job->mRecastMesh->getMeshSources(),
|
const auto objects = makeDbRefGeometryObjects(job->mRecastMesh->getMeshSources(),
|
||||||
[&] (const MeshSource& v) { return resolveMeshSource(*mDb, v, mNextShapeId); });
|
[&] (const MeshSource& v) { return resolveMeshSource(*mDb, v, mNextShapeId); });
|
||||||
if (shapeId != mNextShapeId)
|
if (shapeId != mNextShapeId)
|
||||||
++mWrites;
|
mHasChanges = true;
|
||||||
job->mInput = serialize(mRecastSettings, *job->mRecastMesh, objects);
|
job->mInput = serialize(mRecastSettings, *job->mRecastMesh, objects);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -826,7 +856,11 @@ namespace DetourNavigator
|
||||||
|
|
||||||
void DbWorker::processWritingJob(JobIt job)
|
void DbWorker::processWritingJob(JobIt job)
|
||||||
{
|
{
|
||||||
++mWrites;
|
if (!mWriteToDb)
|
||||||
|
{
|
||||||
|
Log(Debug::Debug) << "Ignored db write job " << job->mId;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Log(Debug::Debug) << "Processing db write job " << job->mId;
|
Log(Debug::Debug) << "Processing db write job " << job->mId;
|
||||||
|
|
||||||
|
@ -843,6 +877,7 @@ namespace DetourNavigator
|
||||||
Log(Debug::Debug) << "Update db tile by job " << job->mId;
|
Log(Debug::Debug) << "Update db tile by job " << job->mId;
|
||||||
job->mGeneratedNavMeshData->mUserId = cachedTileData->mTileId;
|
job->mGeneratedNavMeshData->mUserId = cachedTileData->mTileId;
|
||||||
mDb->updateTile(cachedTileData->mTileId, mVersion, serialize(*job->mGeneratedNavMeshData));
|
mDb->updateTile(cachedTileData->mTileId, mVersion, serialize(*job->mGeneratedNavMeshData));
|
||||||
|
mHasChanges = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -858,5 +893,6 @@ namespace DetourNavigator
|
||||||
mDb->insertTile(mNextTileId, job->mWorldspace, job->mChangedTile,
|
mDb->insertTile(mNextTileId, job->mWorldspace, job->mChangedTile,
|
||||||
mVersion, job->mInput, serialize(*job->mGeneratedNavMeshData));
|
mVersion, job->mInput, serialize(*job->mGeneratedNavMeshData));
|
||||||
++mNextTileId;
|
++mNextTileId;
|
||||||
|
mHasChanges = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,7 +87,7 @@ namespace DetourNavigator
|
||||||
public:
|
public:
|
||||||
void push(JobIt job);
|
void push(JobIt job);
|
||||||
|
|
||||||
std::optional<JobIt> pop();
|
std::optional<JobIt> pop(std::chrono::steady_clock::duration timeout);
|
||||||
|
|
||||||
void update(TilePosition playerTile, int maxTiles);
|
void update(TilePosition playerTile, int maxTiles);
|
||||||
|
|
||||||
|
@ -131,13 +131,13 @@ namespace DetourNavigator
|
||||||
const RecastSettings& mRecastSettings;
|
const RecastSettings& mRecastSettings;
|
||||||
const std::unique_ptr<NavMeshDb> mDb;
|
const std::unique_ptr<NavMeshDb> mDb;
|
||||||
const TileVersion mVersion;
|
const TileVersion mVersion;
|
||||||
const bool mWriteToDb;
|
bool mWriteToDb;
|
||||||
TileId mNextTileId;
|
TileId mNextTileId;
|
||||||
ShapeId mNextShapeId;
|
ShapeId mNextShapeId;
|
||||||
DbJobQueue mQueue;
|
DbJobQueue mQueue;
|
||||||
std::atomic_bool mShouldStop {false};
|
std::atomic_bool mShouldStop {false};
|
||||||
std::atomic_size_t mGetTileCount {0};
|
std::atomic_size_t mGetTileCount {0};
|
||||||
std::size_t mWrites = 0;
|
bool mHasChanges = false;
|
||||||
std::thread mThread;
|
std::thread mThread;
|
||||||
|
|
||||||
inline void run() noexcept;
|
inline void run() noexcept;
|
||||||
|
@ -173,6 +173,8 @@ namespace DetourNavigator
|
||||||
|
|
||||||
void wait(Loading::Listener& listener, WaitConditionType waitConditionType);
|
void wait(Loading::Listener& listener, WaitConditionType waitConditionType);
|
||||||
|
|
||||||
|
void stop();
|
||||||
|
|
||||||
Stats getStats() const;
|
Stats getStats() const;
|
||||||
|
|
||||||
void enqueueJob(JobIt job);
|
void enqueueJob(JobIt job);
|
||||||
|
|
|
@ -96,6 +96,7 @@ namespace DetourNavigator
|
||||||
{
|
{
|
||||||
Log(Debug::Warning) << "Failed to generate navmesh for worldspace \"" << mWorldspace
|
Log(Debug::Warning) << "Failed to generate navmesh for worldspace \"" << mWorldspace
|
||||||
<< "\" tile " << mTilePosition << ": " << e.what();
|
<< "\" tile " << mTilePosition << ": " << e.what();
|
||||||
|
consumer->cancel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,6 +49,8 @@ namespace DetourNavigator
|
||||||
|
|
||||||
virtual void update(std::string_view worldspace, const TilePosition& tilePosition,
|
virtual void update(std::string_view worldspace, const TilePosition& tilePosition,
|
||||||
std::int64_t tileId, std::int64_t version, PreparedNavMeshData& data) = 0;
|
std::int64_t tileId, std::int64_t version, PreparedNavMeshData& data) = 0;
|
||||||
|
|
||||||
|
virtual void cancel() = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
class GenerateNavMeshTile final : public SceneUtil::WorkItem
|
class GenerateNavMeshTile final : public SceneUtil::WorkItem
|
||||||
|
|
|
@ -16,7 +16,7 @@ namespace DetourNavigator
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
db = std::make_unique<NavMeshDb>(userDataPath + "/navmesh.db");
|
db = std::make_unique<NavMeshDb>(userDataPath + "/navmesh.db", settings.mMaxDbFileSize);
|
||||||
}
|
}
|
||||||
catch (const std::exception& e)
|
catch (const std::exception& e)
|
||||||
{
|
{
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
#include <components/misc/compression.hpp>
|
#include <components/misc/compression.hpp>
|
||||||
#include <components/sqlite3/db.hpp>
|
#include <components/sqlite3/db.hpp>
|
||||||
#include <components/sqlite3/request.hpp>
|
#include <components/sqlite3/request.hpp>
|
||||||
|
#include <components/misc/stringops.hpp>
|
||||||
|
|
||||||
#include <DetourAlloc.h>
|
#include <DetourAlloc.h>
|
||||||
|
|
||||||
|
@ -130,6 +131,27 @@ namespace DetourNavigator
|
||||||
constexpr std::string_view vacuumQuery = R"(
|
constexpr std::string_view vacuumQuery = R"(
|
||||||
VACUUM;
|
VACUUM;
|
||||||
)";
|
)";
|
||||||
|
|
||||||
|
struct GetPageSize
|
||||||
|
{
|
||||||
|
static std::string_view text() noexcept { return "pragma page_size;"; }
|
||||||
|
static void bind(sqlite3&, sqlite3_stmt&) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::uint64_t getPageSize(sqlite3& db)
|
||||||
|
{
|
||||||
|
Sqlite3::Statement<GetPageSize> statement(db);
|
||||||
|
std::uint64_t value = 0;
|
||||||
|
request(db, statement, &value, 1);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setMaxPageCount(sqlite3& db, std::uint64_t value)
|
||||||
|
{
|
||||||
|
const auto query = Misc::StringUtils::format("pragma max_page_count = %lu;", value);
|
||||||
|
if (const int ec = sqlite3_exec(&db, query.c_str(), nullptr, nullptr, nullptr); ec != SQLITE_OK)
|
||||||
|
throw std::runtime_error("Failed set max page count: " + std::string(sqlite3_errmsg(&db)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::ostream& operator<<(std::ostream& stream, ShapeType value)
|
std::ostream& operator<<(std::ostream& stream, ShapeType value)
|
||||||
|
@ -142,7 +164,7 @@ namespace DetourNavigator
|
||||||
return stream << "unknown shape type (" << static_cast<std::underlying_type_t<ShapeType>>(value) << ")";
|
return stream << "unknown shape type (" << static_cast<std::underlying_type_t<ShapeType>>(value) << ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
NavMeshDb::NavMeshDb(std::string_view path)
|
NavMeshDb::NavMeshDb(std::string_view path, std::uint64_t maxFileSize)
|
||||||
: mDb(Sqlite3::makeDb(path, schema))
|
: mDb(Sqlite3::makeDb(path, schema))
|
||||||
, mGetMaxTileId(*mDb, DbQueries::GetMaxTileId {})
|
, mGetMaxTileId(*mDb, DbQueries::GetMaxTileId {})
|
||||||
, mFindTile(*mDb, DbQueries::FindTile {})
|
, mFindTile(*mDb, DbQueries::FindTile {})
|
||||||
|
@ -157,11 +179,13 @@ namespace DetourNavigator
|
||||||
, mInsertShape(*mDb, DbQueries::InsertShape {})
|
, mInsertShape(*mDb, DbQueries::InsertShape {})
|
||||||
, mVacuum(*mDb, DbQueries::Vacuum {})
|
, mVacuum(*mDb, DbQueries::Vacuum {})
|
||||||
{
|
{
|
||||||
|
const auto dbPageSize = getPageSize(*mDb);
|
||||||
|
setMaxPageCount(*mDb, maxFileSize / dbPageSize + static_cast<std::uint64_t>((maxFileSize % dbPageSize) != 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
Sqlite3::Transaction NavMeshDb::startTransaction()
|
Sqlite3::Transaction NavMeshDb::startTransaction(Sqlite3::TransactionMode mode)
|
||||||
{
|
{
|
||||||
return Sqlite3::Transaction(*mDb);
|
return Sqlite3::Transaction(*mDb, mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
TileId NavMeshDb::getMaxTileId()
|
TileId NavMeshDb::getMaxTileId()
|
||||||
|
|
|
@ -141,9 +141,9 @@ namespace DetourNavigator
|
||||||
class NavMeshDb
|
class NavMeshDb
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit NavMeshDb(std::string_view path);
|
explicit NavMeshDb(std::string_view path, std::uint64_t maxFileSize);
|
||||||
|
|
||||||
Sqlite3::Transaction startTransaction();
|
Sqlite3::Transaction startTransaction(Sqlite3::TransactionMode mode = Sqlite3::TransactionMode::Default);
|
||||||
|
|
||||||
TileId getMaxTileId();
|
TileId getMaxTileId();
|
||||||
|
|
||||||
|
|
|
@ -64,6 +64,7 @@ namespace DetourNavigator
|
||||||
result.mNavMeshVersion = ::Settings::Manager::getInt("nav mesh version", "Navigator");
|
result.mNavMeshVersion = ::Settings::Manager::getInt("nav mesh version", "Navigator");
|
||||||
result.mEnableNavMeshDiskCache = ::Settings::Manager::getBool("enable nav mesh disk cache", "Navigator");
|
result.mEnableNavMeshDiskCache = ::Settings::Manager::getBool("enable nav mesh disk cache", "Navigator");
|
||||||
result.mWriteToNavMeshDb = ::Settings::Manager::getBool("write to navmeshdb", "Navigator");
|
result.mWriteToNavMeshDb = ::Settings::Manager::getBool("write to navmeshdb", "Navigator");
|
||||||
|
result.mMaxDbFileSize = static_cast<std::uint64_t>(::Settings::Manager::getInt64("max navmeshdb file size", "Navigator"));
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,6 +51,7 @@ namespace DetourNavigator
|
||||||
std::string mNavMeshPathPrefix;
|
std::string mNavMeshPathPrefix;
|
||||||
std::chrono::milliseconds mMinUpdateInterval;
|
std::chrono::milliseconds mMinUpdateInterval;
|
||||||
std::int64_t mNavMeshVersion = 0;
|
std::int64_t mNavMeshVersion = 0;
|
||||||
|
std::uint64_t mMaxDbFileSize = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
RecastSettings makeRecastSettingsFromSettingsManager();
|
RecastSettings makeRecastSettingsFromSettingsManager();
|
||||||
|
|
|
@ -10,7 +10,7 @@ namespace Sqlite3
|
||||||
{
|
{
|
||||||
void CloseSqlite3::operator()(sqlite3* handle) const noexcept
|
void CloseSqlite3::operator()(sqlite3* handle) const noexcept
|
||||||
{
|
{
|
||||||
sqlite3_close(handle);
|
sqlite3_close_v2(handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
Db makeDb(std::string_view path, const char* schema)
|
Db makeDb(std::string_view path, const char* schema)
|
||||||
|
|
|
@ -9,25 +9,43 @@
|
||||||
|
|
||||||
namespace Sqlite3
|
namespace Sqlite3
|
||||||
{
|
{
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
const char* getBeginStatement(TransactionMode mode)
|
||||||
|
{
|
||||||
|
switch (mode)
|
||||||
|
{
|
||||||
|
case TransactionMode::Default: return "BEGIN";
|
||||||
|
case TransactionMode::Deferred: return "BEGIN DEFERRED";
|
||||||
|
case TransactionMode::Immediate: return "BEGIN IMMEDIATE";
|
||||||
|
case TransactionMode::Exclusive: return "BEGIN EXCLUSIVE";
|
||||||
|
}
|
||||||
|
throw std::logic_error("Invalid transaction mode: " + std::to_string(static_cast<std::underlying_type_t<TransactionMode>>(mode)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Rollback::operator()(sqlite3* db) const
|
void Rollback::operator()(sqlite3* db) const
|
||||||
{
|
{
|
||||||
if (db == nullptr)
|
if (db == nullptr)
|
||||||
return;
|
return;
|
||||||
if (const int ec = sqlite3_exec(db, "ROLLBACK", nullptr, nullptr, nullptr); ec != SQLITE_OK)
|
if (const int ec = sqlite3_exec(db, "ROLLBACK", nullptr, nullptr, nullptr); ec != SQLITE_OK)
|
||||||
Log(Debug::Warning) << "Failed to rollback SQLite3 transaction: " << std::string(sqlite3_errmsg(db));
|
Log(Debug::Debug) << "Failed to rollback SQLite3 transaction: " << sqlite3_errmsg(db) << " (" << ec << ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
Transaction::Transaction(sqlite3& db)
|
Transaction::Transaction(sqlite3& db, TransactionMode mode)
|
||||||
: mDb(&db)
|
: mDb(&db)
|
||||||
{
|
{
|
||||||
if (const int ec = sqlite3_exec(mDb.get(), "BEGIN", nullptr, nullptr, nullptr); ec != SQLITE_OK)
|
if (const int ec = sqlite3_exec(&db, getBeginStatement(mode), nullptr, nullptr, nullptr); ec != SQLITE_OK)
|
||||||
throw std::runtime_error("Failed to start transaction: " + std::string(sqlite3_errmsg(mDb.get())));
|
{
|
||||||
|
(void) mDb.release();
|
||||||
|
throw std::runtime_error("Failed to start transaction: " + std::string(sqlite3_errmsg(&db)) + " (" + std::to_string(ec) + ")");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Transaction::commit()
|
void Transaction::commit()
|
||||||
{
|
{
|
||||||
if (const int ec = sqlite3_exec(mDb.get(), "COMMIT", nullptr, nullptr, nullptr); ec != SQLITE_OK)
|
if (const int ec = sqlite3_exec(mDb.get(), "COMMIT", nullptr, nullptr, nullptr); ec != SQLITE_OK)
|
||||||
throw std::runtime_error("Failed to commit transaction: " + std::string(sqlite3_errmsg(mDb.get())));
|
throw std::runtime_error("Failed to commit transaction: " + std::string(sqlite3_errmsg(mDb.get())) + " (" + std::to_string(ec) + ")");
|
||||||
(void) mDb.release();
|
(void) mDb.release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,10 +12,18 @@ namespace Sqlite3
|
||||||
void operator()(sqlite3* handle) const;
|
void operator()(sqlite3* handle) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class TransactionMode
|
||||||
|
{
|
||||||
|
Default,
|
||||||
|
Deferred,
|
||||||
|
Immediate,
|
||||||
|
Exclusive,
|
||||||
|
};
|
||||||
|
|
||||||
class Transaction
|
class Transaction
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Transaction(sqlite3& db);
|
explicit Transaction(sqlite3& db, TransactionMode mode = TransactionMode::Default);
|
||||||
|
|
||||||
void commit();
|
void commit();
|
||||||
|
|
||||||
|
|
|
@ -70,10 +70,19 @@ write to navmeshdb
|
||||||
|
|
||||||
:Type: boolean
|
:Type: boolean
|
||||||
:Range: True/False
|
:Range: True/False
|
||||||
:Default: False
|
:Default: True
|
||||||
|
|
||||||
If true generated navmesh tiles will be stored into disk cache while game is running.
|
If true generated navmesh tiles will be stored into disk cache while game is running.
|
||||||
|
|
||||||
|
max navmeshdb file size
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
:Type: integer
|
||||||
|
:Range: > 0
|
||||||
|
:Default: 2147483648
|
||||||
|
|
||||||
|
Approximate maximum file size of navigation mesh cache stored on disk in bytes (value > 0).
|
||||||
|
|
||||||
Advanced settings
|
Advanced settings
|
||||||
*****************
|
*****************
|
||||||
|
|
||||||
|
|
|
@ -938,7 +938,10 @@ nav mesh version = 1
|
||||||
enable nav mesh disk cache = true
|
enable nav mesh disk cache = true
|
||||||
|
|
||||||
# Cache navigation mesh tiles to disk (true, false)
|
# Cache navigation mesh tiles to disk (true, false)
|
||||||
write to navmeshdb = false
|
write to navmeshdb = true
|
||||||
|
|
||||||
|
# Approximate maximum file size of navigation mesh cache stored on disk in bytes (value > 0)
|
||||||
|
max navmeshdb file size = 2147483648
|
||||||
|
|
||||||
[Shadows]
|
[Shadows]
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue