mirror of
				https://github.com/OpenMW/openmw.git
				synced 2025-10-31 19:26:38 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			425 lines
		
	
	
	
		
			16 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			425 lines
		
	
	
	
		
			16 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| #include "navmeshdb.hpp"
 | |
| 
 | |
| #include <components/debug/debuglog.hpp>
 | |
| #include <components/misc/compression.hpp>
 | |
| #include <components/misc/strings/format.hpp>
 | |
| #include <components/sqlite3/db.hpp>
 | |
| #include <components/sqlite3/request.hpp>
 | |
| 
 | |
| #include <DetourAlloc.h>
 | |
| 
 | |
| #include <sqlite3.h>
 | |
| 
 | |
| #include <cstddef>
 | |
| #include <string_view>
 | |
| #include <vector>
 | |
| 
 | |
| namespace DetourNavigator
 | |
| {
 | |
|     namespace
 | |
|     {
 | |
|         constexpr const char schema[] = R"(
 | |
|             BEGIN TRANSACTION;
 | |
| 
 | |
|             CREATE TABLE IF NOT EXISTS tiles (
 | |
|                 tile_id INTEGER PRIMARY KEY,
 | |
|                 revision INTEGER NOT NULL DEFAULT 1,
 | |
|                 worldspace TEXT NOT NULL,
 | |
|                 tile_position_x INTEGER NOT NULL,
 | |
|                 tile_position_y INTEGER NOT NULL,
 | |
|                 version INTEGER NOT NULL,
 | |
|                 input BLOB,
 | |
|                 data BLOB
 | |
|             );
 | |
| 
 | |
|             CREATE UNIQUE INDEX IF NOT EXISTS index_unique_tiles_by_worldspace_and_tile_position_and_input
 | |
|                 ON tiles (worldspace, tile_position_x, tile_position_y, input);
 | |
| 
 | |
|             CREATE INDEX IF NOT EXISTS index_tiles_by_worldspace_and_tile_position
 | |
|                 ON tiles (worldspace, tile_position_x, tile_position_y);
 | |
| 
 | |
|             CREATE TABLE IF NOT EXISTS shapes (
 | |
|                 shape_id INTEGER PRIMARY KEY,
 | |
|                 name TEXT NOT NULL,
 | |
|                 type INTEGER NOT NULL,
 | |
|                 hash BLOB NOT NULL
 | |
|             );
 | |
| 
 | |
|             CREATE UNIQUE INDEX IF NOT EXISTS index_unique_shapes_by_name_and_type_and_hash
 | |
|                 ON shapes (name, type, hash);
 | |
| 
 | |
|             COMMIT;
 | |
|         )";
 | |
| 
 | |
|         constexpr std::string_view getMaxTileIdQuery = R"(
 | |
|             SELECT max(tile_id) FROM tiles
 | |
|         )";
 | |
| 
 | |
|         constexpr std::string_view findTileQuery = R"(
 | |
|             SELECT tile_id, version
 | |
|               FROM tiles
 | |
|              WHERE worldspace = :worldspace
 | |
|                AND tile_position_x = :tile_position_x
 | |
|                AND tile_position_y = :tile_position_y
 | |
|                AND input = :input
 | |
|         )";
 | |
| 
 | |
|         constexpr std::string_view getTileDataQuery = R"(
 | |
|             SELECT tile_id, version, data
 | |
|               FROM tiles
 | |
|              WHERE worldspace = :worldspace
 | |
|                AND tile_position_x = :tile_position_x
 | |
|                AND tile_position_y = :tile_position_y
 | |
|                AND input = :input
 | |
|         )";
 | |
| 
 | |
|         constexpr std::string_view insertTileQuery = R"(
 | |
|             INSERT INTO tiles ( tile_id,  worldspace,  version,  tile_position_x,  tile_position_y,  input,  data)
 | |
|                    VALUES     (:tile_id, :worldspace, :version, :tile_position_x, :tile_position_y, :input, :data)
 | |
|         )";
 | |
| 
 | |
|         constexpr std::string_view updateTileQuery = R"(
 | |
|             UPDATE tiles
 | |
|                SET version = :version,
 | |
|                    data = :data,
 | |
|                    revision = revision + 1
 | |
|              WHERE tile_id = :tile_id
 | |
|         )";
 | |
| 
 | |
|         constexpr std::string_view deleteTilesAtQuery = R"(
 | |
|             DELETE FROM tiles
 | |
|              WHERE worldspace = :worldspace
 | |
|                AND tile_position_x = :tile_position_x
 | |
|                AND tile_position_y = :tile_position_y
 | |
|         )";
 | |
| 
 | |
|         constexpr std::string_view deleteTilesAtExceptQuery = R"(
 | |
|             DELETE FROM tiles
 | |
|              WHERE worldspace = :worldspace
 | |
|                AND tile_position_x = :tile_position_x
 | |
|                AND tile_position_y = :tile_position_y
 | |
|                AND tile_id != :exclude_tile_id
 | |
|         )";
 | |
| 
 | |
|         constexpr std::string_view deleteTilesOutsideRangeQuery = R"(
 | |
|             DELETE FROM tiles
 | |
|              WHERE worldspace = :worldspace
 | |
|                AND (   tile_position_x < :begin_tile_position_x
 | |
|                     OR tile_position_y < :begin_tile_position_y
 | |
|                     OR tile_position_x >= :end_tile_position_x
 | |
|                     OR tile_position_y >= :end_tile_position_y
 | |
|                    )
 | |
|         )";
 | |
| 
 | |
|         constexpr std::string_view getMaxShapeIdQuery = R"(
 | |
|             SELECT max(shape_id) FROM shapes
 | |
|         )";
 | |
| 
 | |
|         constexpr std::string_view findShapeIdQuery = R"(
 | |
|             SELECT shape_id
 | |
|               FROM shapes
 | |
|              WHERE name = :name
 | |
|                AND type = :type
 | |
|                AND hash = :hash
 | |
|         )";
 | |
| 
 | |
|         constexpr std::string_view insertShapeQuery = R"(
 | |
|             INSERT INTO shapes ( shape_id,  name,  type,  hash)
 | |
|                    VALUES      (:shape_id, :name, :type, :hash)
 | |
|         )";
 | |
| 
 | |
|         constexpr std::string_view vacuumQuery = R"(
 | |
|             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)
 | |
|     {
 | |
|         switch (value)
 | |
|         {
 | |
|             case ShapeType::Collision:
 | |
|                 return stream << "collision";
 | |
|             case ShapeType::Avoid:
 | |
|                 return stream << "avoid";
 | |
|         }
 | |
|         return stream << "unknown shape type (" << static_cast<std::underlying_type_t<ShapeType>>(value) << ")";
 | |
|     }
 | |
| 
 | |
|     NavMeshDb::NavMeshDb(std::string_view path, std::uint64_t maxFileSize)
 | |
|         : mDb(Sqlite3::makeDb(path, schema))
 | |
|         , mGetMaxTileId(*mDb, DbQueries::GetMaxTileId{})
 | |
|         , mFindTile(*mDb, DbQueries::FindTile{})
 | |
|         , mGetTileData(*mDb, DbQueries::GetTileData{})
 | |
|         , mInsertTile(*mDb, DbQueries::InsertTile{})
 | |
|         , mUpdateTile(*mDb, DbQueries::UpdateTile{})
 | |
|         , mDeleteTilesAt(*mDb, DbQueries::DeleteTilesAt{})
 | |
|         , mDeleteTilesAtExcept(*mDb, DbQueries::DeleteTilesAtExcept{})
 | |
|         , mDeleteTilesOutsideRange(*mDb, DbQueries::DeleteTilesOutsideRange{})
 | |
|         , mGetMaxShapeId(*mDb, DbQueries::GetMaxShapeId{})
 | |
|         , mFindShapeId(*mDb, DbQueries::FindShapeId{})
 | |
|         , mInsertShape(*mDb, DbQueries::InsertShape{})
 | |
|         , mVacuum(*mDb, DbQueries::Vacuum{})
 | |
|     {
 | |
|         const std::uint64_t dbPageSize = getPageSize(*mDb);
 | |
|         if (dbPageSize == 0)
 | |
|             throw std::runtime_error("NavMeshDb page size is zero");
 | |
|         setMaxPageCount(*mDb, maxFileSize / dbPageSize + static_cast<std::uint64_t>((maxFileSize % dbPageSize) != 0));
 | |
|     }
 | |
| 
 | |
|     Sqlite3::Transaction NavMeshDb::startTransaction(Sqlite3::TransactionMode mode)
 | |
|     {
 | |
|         return Sqlite3::Transaction(*mDb, mode);
 | |
|     }
 | |
| 
 | |
|     TileId NavMeshDb::getMaxTileId()
 | |
|     {
 | |
|         TileId tileId{ 0 };
 | |
|         request(*mDb, mGetMaxTileId, &tileId, 1);
 | |
|         return tileId;
 | |
|     }
 | |
| 
 | |
|     std::optional<Tile> NavMeshDb::findTile(
 | |
|         ESM::RefId worldspace, const TilePosition& tilePosition, const std::vector<std::byte>& input)
 | |
|     {
 | |
|         Tile result;
 | |
|         auto row = std::tie(result.mTileId, result.mVersion);
 | |
|         const std::vector<std::byte> compressedInput = Misc::compress(input);
 | |
|         if (&row == request(*mDb, mFindTile, &row, 1, worldspace.serializeText(), tilePosition, compressedInput))
 | |
|             return {};
 | |
|         return result;
 | |
|     }
 | |
| 
 | |
|     std::optional<TileData> NavMeshDb::getTileData(
 | |
|         ESM::RefId worldspace, const TilePosition& tilePosition, const std::vector<std::byte>& input)
 | |
|     {
 | |
|         TileData result;
 | |
|         auto row = std::tie(result.mTileId, result.mVersion, result.mData);
 | |
|         const std::vector<std::byte> compressedInput = Misc::compress(input);
 | |
|         if (&row == request(*mDb, mGetTileData, &row, 1, worldspace.serializeText(), tilePosition, compressedInput))
 | |
|             return {};
 | |
|         result.mData = Misc::decompress(result.mData);
 | |
|         return result;
 | |
|     }
 | |
| 
 | |
|     int NavMeshDb::insertTile(TileId tileId, ESM::RefId worldspace, const TilePosition& tilePosition,
 | |
|         TileVersion version, const std::vector<std::byte>& input, const std::vector<std::byte>& data)
 | |
|     {
 | |
|         const std::vector<std::byte> compressedInput = Misc::compress(input);
 | |
|         const std::vector<std::byte> compressedData = Misc::compress(data);
 | |
|         return execute(*mDb, mInsertTile, tileId, worldspace.serializeText(), tilePosition, version, compressedInput,
 | |
|             compressedData);
 | |
|     }
 | |
| 
 | |
|     int NavMeshDb::updateTile(TileId tileId, TileVersion version, const std::vector<std::byte>& data)
 | |
|     {
 | |
|         const std::vector<std::byte> compressedData = Misc::compress(data);
 | |
|         return execute(*mDb, mUpdateTile, tileId, version, compressedData);
 | |
|     }
 | |
| 
 | |
|     int NavMeshDb::deleteTilesAt(ESM::RefId worldspace, const TilePosition& tilePosition)
 | |
|     {
 | |
|         return execute(*mDb, mDeleteTilesAt, worldspace.serializeText(), tilePosition);
 | |
|     }
 | |
| 
 | |
|     int NavMeshDb::deleteTilesAtExcept(ESM::RefId worldspace, const TilePosition& tilePosition, TileId excludeTileId)
 | |
|     {
 | |
|         return execute(*mDb, mDeleteTilesAtExcept, worldspace.serializeText(), tilePosition, excludeTileId);
 | |
|     }
 | |
| 
 | |
|     int NavMeshDb::deleteTilesOutsideRange(ESM::RefId worldspace, const TilesPositionsRange& range)
 | |
|     {
 | |
|         return execute(*mDb, mDeleteTilesOutsideRange, worldspace.serializeText(), range);
 | |
|     }
 | |
| 
 | |
|     ShapeId NavMeshDb::getMaxShapeId()
 | |
|     {
 | |
|         ShapeId shapeId{ 0 };
 | |
|         request(*mDb, mGetMaxShapeId, &shapeId, 1);
 | |
|         return shapeId;
 | |
|     }
 | |
| 
 | |
|     std::optional<ShapeId> NavMeshDb::findShapeId(std::string_view name, ShapeType type, const Sqlite3::ConstBlob& hash)
 | |
|     {
 | |
|         ShapeId shapeId;
 | |
|         if (&shapeId == request(*mDb, mFindShapeId, &shapeId, 1, name, type, hash))
 | |
|             return {};
 | |
|         return shapeId;
 | |
|     }
 | |
| 
 | |
|     int NavMeshDb::insertShape(ShapeId shapeId, std::string_view name, ShapeType type, const Sqlite3::ConstBlob& hash)
 | |
|     {
 | |
|         return execute(*mDb, mInsertShape, shapeId, name, type, hash);
 | |
|     }
 | |
| 
 | |
|     void NavMeshDb::vacuum()
 | |
|     {
 | |
|         execute(*mDb, mVacuum);
 | |
|     }
 | |
| 
 | |
|     namespace DbQueries
 | |
|     {
 | |
|         std::string_view GetMaxTileId::text() noexcept
 | |
|         {
 | |
|             return getMaxTileIdQuery;
 | |
|         }
 | |
| 
 | |
|         std::string_view FindTile::text() noexcept
 | |
|         {
 | |
|             return findTileQuery;
 | |
|         }
 | |
| 
 | |
|         void FindTile::bind(sqlite3& db, sqlite3_stmt& statement, std::string_view worldspace,
 | |
|             const TilePosition& tilePosition, const std::vector<std::byte>& input)
 | |
|         {
 | |
|             Sqlite3::bindParameter(db, statement, ":worldspace", worldspace);
 | |
|             Sqlite3::bindParameter(db, statement, ":tile_position_x", tilePosition.x());
 | |
|             Sqlite3::bindParameter(db, statement, ":tile_position_y", tilePosition.y());
 | |
|             Sqlite3::bindParameter(db, statement, ":input", input);
 | |
|         }
 | |
| 
 | |
|         std::string_view GetTileData::text() noexcept
 | |
|         {
 | |
|             return getTileDataQuery;
 | |
|         }
 | |
| 
 | |
|         void GetTileData::bind(sqlite3& db, sqlite3_stmt& statement, std::string_view worldspace,
 | |
|             const TilePosition& tilePosition, const std::vector<std::byte>& input)
 | |
|         {
 | |
|             Sqlite3::bindParameter(db, statement, ":worldspace", worldspace);
 | |
|             Sqlite3::bindParameter(db, statement, ":tile_position_x", tilePosition.x());
 | |
|             Sqlite3::bindParameter(db, statement, ":tile_position_y", tilePosition.y());
 | |
|             Sqlite3::bindParameter(db, statement, ":input", input);
 | |
|         }
 | |
| 
 | |
|         std::string_view InsertTile::text() noexcept
 | |
|         {
 | |
|             return insertTileQuery;
 | |
|         }
 | |
| 
 | |
|         void InsertTile::bind(sqlite3& db, sqlite3_stmt& statement, TileId tileId, std::string_view worldspace,
 | |
|             const TilePosition& tilePosition, TileVersion version, const std::vector<std::byte>& input,
 | |
|             const std::vector<std::byte>& data)
 | |
|         {
 | |
|             Sqlite3::bindParameter(db, statement, ":tile_id", tileId);
 | |
|             Sqlite3::bindParameter(db, statement, ":worldspace", worldspace);
 | |
|             Sqlite3::bindParameter(db, statement, ":tile_position_x", tilePosition.x());
 | |
|             Sqlite3::bindParameter(db, statement, ":tile_position_y", tilePosition.y());
 | |
|             Sqlite3::bindParameter(db, statement, ":version", version);
 | |
|             Sqlite3::bindParameter(db, statement, ":input", input);
 | |
|             Sqlite3::bindParameter(db, statement, ":data", data);
 | |
|         }
 | |
| 
 | |
|         std::string_view UpdateTile::text() noexcept
 | |
|         {
 | |
|             return updateTileQuery;
 | |
|         }
 | |
| 
 | |
|         void UpdateTile::bind(sqlite3& db, sqlite3_stmt& statement, TileId tileId, TileVersion version,
 | |
|             const std::vector<std::byte>& data)
 | |
|         {
 | |
|             Sqlite3::bindParameter(db, statement, ":tile_id", tileId);
 | |
|             Sqlite3::bindParameter(db, statement, ":version", version);
 | |
|             Sqlite3::bindParameter(db, statement, ":data", data);
 | |
|         }
 | |
| 
 | |
|         std::string_view DeleteTilesAt::text() noexcept
 | |
|         {
 | |
|             return deleteTilesAtQuery;
 | |
|         }
 | |
| 
 | |
|         void DeleteTilesAt::bind(
 | |
|             sqlite3& db, sqlite3_stmt& statement, std::string_view worldspace, const TilePosition& tilePosition)
 | |
|         {
 | |
|             Sqlite3::bindParameter(db, statement, ":worldspace", worldspace);
 | |
|             Sqlite3::bindParameter(db, statement, ":tile_position_x", tilePosition.x());
 | |
|             Sqlite3::bindParameter(db, statement, ":tile_position_y", tilePosition.y());
 | |
|         }
 | |
| 
 | |
|         std::string_view DeleteTilesAtExcept::text() noexcept
 | |
|         {
 | |
|             return deleteTilesAtExceptQuery;
 | |
|         }
 | |
| 
 | |
|         void DeleteTilesAtExcept::bind(sqlite3& db, sqlite3_stmt& statement, std::string_view worldspace,
 | |
|             const TilePosition& tilePosition, TileId excludeTileId)
 | |
|         {
 | |
|             Sqlite3::bindParameter(db, statement, ":worldspace", worldspace);
 | |
|             Sqlite3::bindParameter(db, statement, ":tile_position_x", tilePosition.x());
 | |
|             Sqlite3::bindParameter(db, statement, ":tile_position_y", tilePosition.y());
 | |
|             Sqlite3::bindParameter(db, statement, ":exclude_tile_id", excludeTileId);
 | |
|         }
 | |
| 
 | |
|         std::string_view DeleteTilesOutsideRange::text() noexcept
 | |
|         {
 | |
|             return deleteTilesOutsideRangeQuery;
 | |
|         }
 | |
| 
 | |
|         void DeleteTilesOutsideRange::bind(
 | |
|             sqlite3& db, sqlite3_stmt& statement, std::string_view worldspace, const TilesPositionsRange& range)
 | |
|         {
 | |
|             Sqlite3::bindParameter(db, statement, ":worldspace", worldspace);
 | |
|             Sqlite3::bindParameter(db, statement, ":begin_tile_position_x", range.mBegin.x());
 | |
|             Sqlite3::bindParameter(db, statement, ":begin_tile_position_y", range.mBegin.y());
 | |
|             Sqlite3::bindParameter(db, statement, ":end_tile_position_x", range.mEnd.x());
 | |
|             Sqlite3::bindParameter(db, statement, ":end_tile_position_y", range.mEnd.y());
 | |
|         }
 | |
| 
 | |
|         std::string_view GetMaxShapeId::text() noexcept
 | |
|         {
 | |
|             return getMaxShapeIdQuery;
 | |
|         }
 | |
| 
 | |
|         std::string_view FindShapeId::text() noexcept
 | |
|         {
 | |
|             return findShapeIdQuery;
 | |
|         }
 | |
| 
 | |
|         void FindShapeId::bind(
 | |
|             sqlite3& db, sqlite3_stmt& statement, std::string_view name, ShapeType type, const Sqlite3::ConstBlob& hash)
 | |
|         {
 | |
|             Sqlite3::bindParameter(db, statement, ":name", name);
 | |
|             Sqlite3::bindParameter(db, statement, ":type", static_cast<int>(type));
 | |
|             Sqlite3::bindParameter(db, statement, ":hash", hash);
 | |
|         }
 | |
| 
 | |
|         std::string_view InsertShape::text() noexcept
 | |
|         {
 | |
|             return insertShapeQuery;
 | |
|         }
 | |
| 
 | |
|         void InsertShape::bind(sqlite3& db, sqlite3_stmt& statement, ShapeId shapeId, std::string_view name,
 | |
|             ShapeType type, const Sqlite3::ConstBlob& hash)
 | |
|         {
 | |
|             Sqlite3::bindParameter(db, statement, ":shape_id", shapeId);
 | |
|             Sqlite3::bindParameter(db, statement, ":name", name);
 | |
|             Sqlite3::bindParameter(db, statement, ":type", static_cast<int>(type));
 | |
|             Sqlite3::bindParameter(db, statement, ":hash", hash);
 | |
|         }
 | |
| 
 | |
|         std::string_view Vacuum::text() noexcept
 | |
|         {
 | |
|             return vacuumQuery;
 | |
|         }
 | |
|     }
 | |
| }
 |