From 9470d14151c9c2bb03957f0a9973c2bb417b859a Mon Sep 17 00:00:00 2001 From: DrSocalkwe3n Date: Thu, 26 Jun 2025 11:20:31 +0600 Subject: [PATCH] =?UTF-8?q?Cmake=20=D0=B8=20ClientCache?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitmodules | 3 + CMakeLists.txt | 119 ++++++++------ Libs/boost | 1 + Src/Client/ResourceCache.cpp | 7 + Src/Client/ResourceCache.hpp | 311 +++++++++++++++++++++++++++++++++++ Src/Server/GameServer.hpp | 1 + Src/main.cpp | 15 +- 7 files changed, 404 insertions(+), 53 deletions(-) create mode 100644 .gitmodules create mode 160000 Libs/boost create mode 100644 Src/Client/ResourceCache.cpp create mode 100644 Src/Client/ResourceCache.hpp diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..45f5441 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "Libs/boost"] + path = Libs/boost + url = https://github.com/boostorg/boost.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 1ab235b..a782013 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,6 @@ option(BUILD_CLIENT "Build the client" TRUE) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) -add_compile_options(-fcoroutines) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdata-sections -ffunction-sections -DGLM_FORCE_DEPTH_ZERO_TO_ONE") @@ -23,12 +22,15 @@ set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pg") # set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined -fno-sanitize-recover=all -fsanitize=float-divide-by-zero -fsanitize=float-cast-overflow -fno-sanitize=null -fno-sanitize=alignment") project (LuaVox VERSION 0.0 DESCRIPTION "LuaVox Description") -add_executable(${PROJECT_NAME}) -target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_20) -file(GLOB_RECURSE SOURCES RELATIVE ${PROJECT_SOURCE_DIR} "Src/*.cpp") -target_sources(${PROJECT_NAME} PRIVATE ${SOURCES}) -target_include_directories(${PROJECT_NAME} PUBLIC "${PROJECT_SOURCE_DIR}/Src") +add_library(luavox_common INTERFACE) +target_compile_features(luavox_common INTERFACE cxx_std_20) + +if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") + target_compile_options(luavox_common INTERFACE -fcoroutines) +elseif ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") + target_compile_options(luavox_common INTERFACE -fcoroutine) +endif() include(FetchContent) @@ -51,7 +53,7 @@ FetchContent_Declare( DOWNLOAD_NO_EXTRACT FALSE ) FetchContent_MakeAvailable(Boost) -target_link_libraries(${PROJECT_NAME} PUBLIC Boost::asio Boost::thread Boost::json) +target_link_libraries(luavox_common INTERFACE Boost::asio Boost::thread Boost::json) # glm # find_package(glm REQUIRED) @@ -64,61 +66,33 @@ FetchContent_Declare( GIT_TAG 1.0.1 ) FetchContent_MakeAvailable(glm) -target_link_libraries(${PROJECT_NAME} PUBLIC glm) +target_link_libraries(luavox_common INTERFACE glm) find_package(ICU REQUIRED COMPONENTS i18n uc) -target_include_directories(${PROJECT_NAME} PUBLIC ${ICU_INCLUDE_DIR}) -target_link_libraries(${PROJECT_NAME} PUBLIC ${ICU_LIBRARIES}) +target_include_directories(luavox_common INTERFACE ${ICU_INCLUDE_DIR}) +target_link_libraries(luavox_common INTERFACE ${ICU_LIBRARIES}) find_package(OpenSSL REQUIRED) -target_include_directories(${PROJECT_NAME} PUBLIC ${OPENSSL_INCLUDE_DIR}) -target_link_libraries(${PROJECT_NAME} PUBLIC ${OPENSSL_LIBRARIES}) +target_include_directories(luavox_common INTERFACE ${OPENSSL_INCLUDE_DIR}) +target_link_libraries(luavox_common INTERFACE ${OPENSSL_LIBRARIES}) # JPEG find_package(JPEG REQUIRED) -target_include_directories(${PROJECT_NAME} PUBLIC ${JPEG_INCLUDE_DIRS}) -target_link_libraries(${PROJECT_NAME} PUBLIC JPEG::JPEG) +target_include_directories(luavox_common INTERFACE ${JPEG_INCLUDE_DIRS}) +target_link_libraries(luavox_common INTERFACE JPEG::JPEG) # PNG find_package(PNG REQUIRED) -target_include_directories(${PROJECT_NAME} PUBLIC ${PNG_INCLUDE_DIRS}) -target_link_libraries(${PROJECT_NAME} PUBLIC PNG::PNG) +target_include_directories(luavox_common INTERFACE ${PNG_INCLUDE_DIRS}) +target_link_libraries(luavox_common INTERFACE PNG::PNG) # PNG++ -target_include_directories(${PROJECT_NAME} PUBLIC "${PROJECT_SOURCE_DIR}/Libs/png++") +target_include_directories(luavox_common INTERFACE "${PROJECT_SOURCE_DIR}/Libs/png++") -# GLFW3 -if(BUILD_CLIENT) - find_package(glfw3 3) - - if(TARGET glfw) - target_include_directories(${PROJECT_NAME} PUBLIC ${GLFW_INCLUDE_DIRS}) - else() - FetchContent_Declare( - glfw - GIT_REPOSITORY https://github.com/glfw/glfw.git - GIT_TAG 3.4 - ) - FetchContent_MakeAvailable(glfw) - endif() - target_link_libraries(${PROJECT_NAME} PUBLIC glfw) -endif() - -# FreeType -find_package(Freetype REQUIRED) -# FetchContent_Declare( -# freetype -# GIT_REPOSITORY https://github.com/freetype/freetype.git -# GIT_TAG freetype -# ) -# FetchContent_MakeAvailable(freetype) -target_include_directories(${PROJECT_NAME} PUBLIC ${freetype_INCLUDE_DIRS}) -target_link_libraries(${PROJECT_NAME} PUBLIC Freetype::Freetype) - -# ImGui -file(GLOB SOURCES "${PROJECT_SOURCE_DIR}/Libs/imgui/*.cpp") -target_sources(${PROJECT_NAME} PRIVATE ${SOURCES} "${PROJECT_SOURCE_DIR}/Libs/imgui/backends/imgui_impl_glfw.cpp" "${PROJECT_SOURCE_DIR}/Libs/imgui/backends/imgui_impl_vulkan.cpp") -target_include_directories(${PROJECT_NAME} PUBLIC "${PROJECT_SOURCE_DIR}/Libs/imgui/") +# sqlite3 +FetchContent_Declare(sqlite3 GIT_REPOSITORY https://github.com/sjinks/sqlite3-cmake GIT_TAG v3.49.1) +FetchContent_MakeAvailable(sqlite3) +target_link_libraries(luavox_common INTERFACE SQLite::SQLite3) # Static Assets file(GLOB_RECURSE ASSETS RELATIVE "${PROJECT_SOURCE_DIR}/assets" "assets/*.*") @@ -127,7 +101,48 @@ add_custom_command(OUTPUT assets.o resources.cpp INPUT ${ASSETS} COMMAND cd "${CMAKE_CURRENT_SOURCE_DIR}/assets" && ld -r -b binary -o '${CMAKE_CURRENT_BINARY_DIR}/assets.o' ${ASSETS} COMMAND objcopy --rename-section .data=.rodata,alloc,load,readonly,data,contents ${CMAKE_CURRENT_BINARY_DIR}/assets.o ${CMAKE_CURRENT_BINARY_DIR}/assets.o) -SET_SOURCE_FILES_PROPERTIES(assets.o PROPERTIES EXTERNAL_OBJECT true GENERATED true) +set_source_files_properties(assets.o PROPERTIES EXTERNAL_OBJECT true GENERATED true) add_library(assets STATIC resources.cpp assets.o) -SET_TARGET_PROPERTIES(assets PROPERTIES LINKER_LANGUAGE C) -target_link_libraries(${PROJECT_NAME} PUBLIC assets uring) +set_target_properties(assets PROPERTIES LINKER_LANGUAGE C) +target_link_libraries(luavox_common INTERFACE assets) + +# uring +target_link_libraries(luavox_common INTERFACE uring) + +if(BUILD_CLIENT) + add_executable(luavox_client) + + # Common + target_link_libraries(luavox_client PUBLIC luavox_common) + + # Исходники + file(GLOB_RECURSE SOURCES RELATIVE ${PROJECT_SOURCE_DIR} "Src/*.cpp") + target_sources(luavox_client PRIVATE ${SOURCES}) + target_include_directories(luavox_client PUBLIC "${PROJECT_SOURCE_DIR}/Src") + + # GLFW3 + FetchContent_Declare( + glfw + GIT_REPOSITORY https://github.com/glfw/glfw.git + GIT_TAG 3.4 + ) + FetchContent_MakeAvailable(glfw) + target_link_libraries(luavox_client PUBLIC glfw) + + # FreeType + find_package(Freetype REQUIRED) + # FetchContent_Declare( + # freetype + # GIT_REPOSITORY https://github.com/freetype/freetype.git + # GIT_TAG freetype + # ) + # FetchContent_MakeAvailable(freetype) + target_include_directories(luavox_client PUBLIC ${freetype_INCLUDE_DIRS}) + target_link_libraries(luavox_client PUBLIC Freetype::Freetype) + + # ImGui + file(GLOB SOURCES "${PROJECT_SOURCE_DIR}/Libs/imgui/*.cpp") + target_sources(luavox_client PRIVATE ${SOURCES} "${PROJECT_SOURCE_DIR}/Libs/imgui/backends/imgui_impl_glfw.cpp" "${PROJECT_SOURCE_DIR}/Libs/imgui/backends/imgui_impl_vulkan.cpp") + target_include_directories(luavox_client PUBLIC "${PROJECT_SOURCE_DIR}/Libs/imgui/") +endif() + diff --git a/Libs/boost b/Libs/boost new file mode 160000 index 0000000..c89e626 --- /dev/null +++ b/Libs/boost @@ -0,0 +1 @@ +Subproject commit c89e6267665516192015a9e40955e154466f4f68 diff --git a/Src/Client/ResourceCache.cpp b/Src/Client/ResourceCache.cpp new file mode 100644 index 0000000..fd87461 --- /dev/null +++ b/Src/Client/ResourceCache.cpp @@ -0,0 +1,7 @@ +#include "ResourceCache.hpp" + + +namespace LV::Client { + + +} \ No newline at end of file diff --git a/Src/Client/ResourceCache.hpp b/Src/Client/ResourceCache.hpp new file mode 100644 index 0000000..7a40497 --- /dev/null +++ b/Src/Client/ResourceCache.hpp @@ -0,0 +1,311 @@ +#include +#include +#include +#include +#include +#include +#include + + +namespace LV::Client { + +namespace fs = std::filesystem; + +// NOT ThreadSafe +class ResourceCacheHandler { + const fs::path Path; + + sqlite3 *DB = nullptr; + sqlite3_stmt *STMT_INSERT = nullptr, + *STMT_UPDATE_TIME = nullptr, + *STMT_REMOVE = nullptr, + *STMT_ALL_HASH = nullptr, + *STMT_SUM = nullptr, + *STMT_TO_FREE = nullptr, + *STMT_COUNT = nullptr; + + size_t Size = -1; + +public: + ResourceCacheHandler(const std::string_view cache_path) + : Path(cache_path) + { + int errc = sqlite3_open_v2((Path / "db.sqlite3").c_str(), &DB, SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE, nullptr); + if(errc) { + MAKE_ERROR("Не удалось открыть базу данных " << (Path / "db.sqlite3").c_str() << ": " << sqlite3_errmsg(DB)); + } + + const char* sql = R"( + CREATE TABLE IF NOT EXISTS files( + sha256 BLOB(32) NOT NULL, -- + last_used INT NOT NULL, -- unix timestamp + size INT NOT NULL, -- file size + UNIQUE (sha256)); + )"; + + errc = sqlite3_exec(DB, sql, nullptr, nullptr, nullptr); + if(errc != SQLITE_OK) { + MAKE_ERROR("Не удалось подготовить таблицу базы: " << sqlite3_errmsg(DB)); + } + + sql = R"( + INSERT OR REPLACE INTO files (sha256, last_used, size) + VALUES (?, ?, ?); + )"; + + if(sqlite3_prepare_v2(DB, sql, -1, &STMT_INSERT, nullptr) != SQLITE_OK) { + MAKE_ERROR("Не удалось подготовить запрос STMT_INSERT: " << sqlite3_errmsg(DB)); + } + + sql = R"( + UPDATE files SET last_used = ? WHERE sha256 = ?; + )"; + + if(sqlite3_prepare_v2(DB, sql, -1, &STMT_UPDATE_TIME, nullptr) != SQLITE_OK) { + MAKE_ERROR("Не удалось подготовить запрос STMT_UPDATE_TIME: " << sqlite3_errmsg(DB)); + } + + sql = R"( + DELETE FROM files WHERE sha256=?; + )"; + + if(sqlite3_prepare_v2(DB, sql, -1, &STMT_REMOVE, nullptr) != SQLITE_OK) { + MAKE_ERROR("Не удалось подготовить запрос STMT_REMOVE: " << sqlite3_errmsg(DB)); + } + + sql = R"( + SELECT sha256 FROM files; + )"; + + if(sqlite3_prepare_v2(DB, sql, -1, &STMT_ALL_HASH, nullptr) != SQLITE_OK) { + MAKE_ERROR("Не удалось подготовить запрос STMT_ALL_HASH: " << sqlite3_errmsg(DB)); + } + + sql = R"( + SELECT SUM(size) FROM files; + )"; + + if(sqlite3_prepare_v2(DB, sql, -1, &STMT_SUM, nullptr) != SQLITE_OK) { + MAKE_ERROR("Не удалось подготовить запрос STMT_SUM: " << sqlite3_errmsg(DB)); + } + + sql = R"( + SELECT sha256 + FROM files + WHERE last_used < ? + ORDER BY last_used ASC, size ASC + LIMIT ( + SELECT COUNT(*) FROM ( + SELECT SUM(size) OVER (ORDER BY last_used ASC, size ASC ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS running_total + FROM files + WHERE last_used < ? + ORDER BY last_used ASC, size ASC + ) sub + WHERE running_total <= ? + ); + )"; + + if(sqlite3_prepare_v2(DB, sql, -1, &STMT_TO_FREE, nullptr) != SQLITE_OK) { + MAKE_ERROR("Не удалось подготовить запрос STMT_TO_FREE: " << sqlite3_errmsg(DB)); + } + + sql = R"( + SELECT COUNT(*) FROM files; + )"; + + if(sqlite3_prepare_v2(DB, sql, -1, &STMT_COUNT, nullptr) != SQLITE_OK) { + MAKE_ERROR("Не удалось подготовить запрос STMT_COUNT: " << sqlite3_errmsg(DB)); + } + } + + ~ResourceCacheHandler() { + for(sqlite3_stmt* stmt : {STMT_INSERT, STMT_UPDATE_TIME, STMT_REMOVE, STMT_ALL_HASH, STMT_SUM, STMT_TO_FREE, STMT_COUNT}) + if(stmt) + sqlite3_finalize(stmt); + + if(DB) + sqlite3_close(DB); + } + + ResourceCacheHandler(const ResourceCacheHandler&) = delete; + ResourceCacheHandler(ResourceCacheHandler&&) = delete; + ResourceCacheHandler& operator=(const ResourceCacheHandler&) = delete; + ResourceCacheHandler& operator=(ResourceCacheHandler&&) = delete; + + /* + Выдаёт размер занимаемый всем хранимым кешем + */ + size_t getCacheSize() { + if(Size == -1) { + if(sqlite3_step(STMT_SUM) != SQLITE_ROW) { + sqlite3_reset(STMT_SUM); + MAKE_ERROR("Не удалось выполнить подготовленный запрос STMT_SUM: " << sqlite3_errmsg(DB)); + } + + Size = sqlite3_column_int(STMT_SUM, 0); + sqlite3_reset(STMT_SUM); + } + + return Size; + } + + // TODO: добавить ограничения на количество файлов + + /* + Создаёт линейный массив в котором подряд указаны все хэш суммы в бинарном виде и возвращает их количество + */ + std::pair getAllHash() { + if(sqlite3_step(STMT_COUNT) != SQLITE_ROW) { + sqlite3_reset(STMT_COUNT); + MAKE_ERROR("Не удалось выполнить подготовленный запрос STMT_COUNT: " << sqlite3_errmsg(DB)); + } + + size_t count = sqlite3_column_int(STMT_COUNT, 0); + sqlite3_reset(STMT_COUNT); + + std::string out; + out.reserve(32*count); + + int errc; + size_t readed = 0; + while(true) { + errc = sqlite3_step(STMT_ALL_HASH); + if(errc == SQLITE_DONE) + break; + else if(errc != SQLITE_ROW) { + sqlite3_reset(STMT_ALL_HASH); + MAKE_ERROR("Не удалось выполнить подготовленный запрос STMT_ALL_HASH: " << sqlite3_errmsg(DB)); + } + + const char *hash = (const char*) sqlite3_column_blob(STMT_ALL_HASH, 0); + readed++; + out += std::string_view(hash, hash+32); + } + + sqlite3_reset(STMT_ALL_HASH); + return {out, readed}; + } + + using HASH = std::array; + + /* + Обновляет время использования кеша + */ + void updateTimeFor(HASH hash) { + sqlite3_bind_blob(STMT_UPDATE_TIME, 0, (const void*) hash.data(), 32, SQLITE_STATIC); + sqlite3_bind_int(STMT_UPDATE_TIME, 1, time(nullptr)); + if(sqlite3_step(STMT_UPDATE_TIME) != SQLITE_OK) { + sqlite3_reset(STMT_UPDATE_TIME); + MAKE_ERROR("Не удалось выполнить подготовленный запрос STMT_UPDATE_TIME: " << sqlite3_errmsg(DB)); + } + + sqlite3_reset(STMT_UPDATE_TIME); + } + + /* + Добавляет запись + */ + void insert(HASH hash, size_t size) { + assert(size < (size_t(1) << 31)-1 && size > 0); + + sqlite3_bind_blob(STMT_INSERT, 0, (const void*) hash.data(), 32, SQLITE_STATIC); + sqlite3_bind_int(STMT_INSERT, 1, (int) size); + sqlite3_bind_int(STMT_INSERT, 2, time(nullptr)); + if(sqlite3_step(STMT_INSERT) != SQLITE_OK) { + sqlite3_reset(STMT_INSERT); + MAKE_ERROR("Не удалось выполнить подготовленный запрос STMT_INSERT: " << sqlite3_errmsg(DB)); + } + + sqlite3_reset(STMT_INSERT); + } + + /* + Выдаёт хэши на удаление по размеру в сумме больше bytesToFree. В приоритете старые, потом мелкие + */ + std::vector findExcessHashes(size_t bytesToFree, int timeBefore = time(nullptr)-604800) { + sqlite3_bind_int(STMT_TO_FREE, 0, timeBefore); + sqlite3_bind_int(STMT_TO_FREE, 1, timeBefore); + sqlite3_bind_int(STMT_TO_FREE, 2, (int) bytesToFree); + + std::vector out; + while(true) { + int errc = sqlite3_step(STMT_TO_FREE); + if(errc == SQLITE_DONE) + break; + else if(errc != SQLITE_ROW) { + sqlite3_reset(STMT_TO_FREE); + MAKE_ERROR("Не удалось выполнить подготовленный запрос STMT_TO_FREE: " << sqlite3_errmsg(DB)); + } + + const uint8_t *hash = (const uint8_t*) sqlite3_column_blob(STMT_TO_FREE, 0); + HASH obj; + for(int iter = 0; iter < 32; iter++) + obj[iter] = hash[iter]; + + out.push_back(obj); + } + + sqlite3_reset(STMT_TO_FREE); + return out; + } + + /* + Удаление записи + */ + void remove(HASH hash) { + sqlite3_bind_blob(STMT_REMOVE, 0, (const void*) hash.data(), 32, SQLITE_STATIC); + if(sqlite3_step(STMT_REMOVE) != SQLITE_OK) { + sqlite3_reset(STMT_REMOVE); + MAKE_ERROR("Не удалось выполнить подготовленный запрос STMT_REMOVE: " << sqlite3_errmsg(DB)); + } + + sqlite3_reset(STMT_REMOVE); + } + + static std::string hashToString(HASH hash) { + std::string text; + text.reserve(64); + + for(int iter = 0; iter < 32; iter++) { + int val = hash[31-iter] & 0xf; + if(val > 9) + text += 'a'+val-10; + else + text += '0'+val; + + val = (hash[31-iter] >> 4) & 0xf; + if(val > 9) + text += 'a'+val-10; + else + text += '0'+val; + } + + return text; + } + + static int hexCharToInt(char c) { + if (c >= '0' && c <= '9') return c - '0'; + if (c >= 'a' && c <= 'f') return c - 'a' + 10; + throw std::invalid_argument("Invalid hexadecimal character"); + } + + static HASH stringToHash(const std::string_view view) { + if (view.size() != 64) + throw std::invalid_argument("Hex string must be exactly 64 characters long"); + + HASH hash; + + for (size_t i = 0; i < 32; ++i) { + size_t offset = 62 - i * 2; + int high = hexCharToInt(view[offset]); + int low = hexCharToInt(view[offset + 1]); + hash[i] = (high << 4) | low; + } + + return hash; + } +}; + + + +} \ No newline at end of file diff --git a/Src/Server/GameServer.hpp b/Src/Server/GameServer.hpp index 39c7427..c0e737f 100644 --- a/Src/Server/GameServer.hpp +++ b/Src/Server/GameServer.hpp @@ -19,6 +19,7 @@ #include "World.hpp" #include "SaveBackend.hpp" +#include "boost/asio/ip/address.hpp" namespace LV::Server { diff --git a/Src/main.cpp b/Src/main.cpp index 5453df3..7db35bd 100644 --- a/Src/main.cpp +++ b/Src/main.cpp @@ -2,8 +2,17 @@ #include #include +#include + namespace LV { +/* + База ресурсов на стороне клиента + Протокол получения ресурсов, удаления, потом -> регулировки размера + +*/ + + using namespace TOS; int main() { @@ -25,5 +34,9 @@ int main() { TOS::Logger::addLogFile(".*", TOS::EnumLogType::All, "log.raw"); std::cout << "Hello world!" << std::endl; - return LV::main(); + //return LV::main(); + + LV::Client::ResourceCacheHandler handler("cache"); + + return 0; }