Compare commits

..

2 Commits

Author SHA1 Message Date
1710eb974d TOSAsync 2025-03-11 19:37:36 +06:00
e190c79d00 Пересмотр асинхронностей 2025-03-11 14:55:43 +06:00
88 changed files with 4239 additions and 23139 deletions

3
.gitignore vendored
View File

@@ -14,6 +14,3 @@
/imgui.ini /imgui.ini
/data /data
/gmon.out /gmon.out
/log.raw
/Cache

3
.gitmodules vendored
View File

@@ -1,3 +0,0 @@
[submodule "Libs/boost"]
path = Libs/boost
url = https://github.com/boostorg/boost.git

View File

@@ -1,62 +1,34 @@
cmake_minimum_required(VERSION 3.13) cmake_minimum_required(VERSION 3.13)
option(BUILD_CLIENT "Build the client" ON) option(BUILD_CLIENT "Build the client" TRUE)
option(USE_LIBURING "Build with liburing support" ON)
set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF) 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") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdata-sections -ffunction-sections -DGLM_FORCE_DEPTH_ZERO_TO_ONE")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--gc-sections") # -rdynamic set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--gc-sections") # -rdynamic
# gprof # gprof
# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pg") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pg")
# set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pg") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pg")
# sanitizer # sanitizer
# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer") # set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address")
# set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address") # set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address")
# 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 -fsanitize=address -fno-omit-frame-pointer") # 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")
# set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=undefined -fno-sanitize-recover=all -fsanitize=float-divide-by-zero -fsanitize=float-cast-overflow -fno-sanitize=null -fno-sanitize=alignment -fsanitize=address")
# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address,undefined -fno-omit-frame-pointer -fno-sanitize-recover=all") project (LuaVox VERSION 0.0 DESCRIPTION "LuaVox Description")
# set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address,undefined") add_executable(${PROJECT_NAME})
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_20)
project(LuaVox VERSION 0.0 DESCRIPTION "LuaVox Description") file(GLOB_RECURSE SOURCES RELATIVE ${PROJECT_SOURCE_DIR} "Src/*.cpp")
target_sources(${PROJECT_NAME} PRIVATE ${SOURCES})
add_library(luavox_common INTERFACE) target_include_directories(${PROJECT_NAME} PUBLIC "${PROJECT_SOURCE_DIR}/Src")
target_compile_features(luavox_common INTERFACE cxx_std_23)
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
# target_compile_options(luavox_common INTERFACE -fsanitize=address,undefined -fno-omit-frame-pointer -fno-sanitize-recover=all)
# target_link_options(luavox_common INTERFACE -fsanitize=address,undefined)
# set(ENV{ASAN_OPTIONS} detect_leaks=0)
endif()
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()
if(USE_LIBURING)
find_package(PkgConfig REQUIRED)
pkg_check_modules(LIBURING liburing>=2.0 IMPORTED_TARGET)
if(LIBURING_FOUND)
message(STATUS "liburing found, enabling io_uring support")
target_compile_definitions(luavox_common INTERFACE LUAVOX_HAVE_LIBURING)
target_link_libraries(luavox_common INTERFACE PkgConfig::LIBURING)
else()
message(FATAL_ERROR "liburing >= 2.0 not found but USE_LIBURING is ON")
endif()
else()
message(STATUS "liburing support is disabled")
endif()
include(FetchContent) include(FetchContent)
@@ -72,55 +44,19 @@ set(Boost_USE_STATIC_LIBS ON)
set(BOOST_INCLUDE_LIBRARIES asio thread json) set(BOOST_INCLUDE_LIBRARIES asio thread json)
set(BOOST_ENABLE_CMAKE ON) set(BOOST_ENABLE_CMAKE ON)
set(BOOST_IOSTREAMS_ENABLE_ZLIB ON)
set(BOOST_INCLUDE_LIBRARIES asio thread json iostreams interprocess timer circular_buffer lockfree stacktrace uuid serialization nowide)
FetchContent_Declare( FetchContent_Declare(
Boost Boost
GIT_REPOSITORY https://github.com/boostorg/boost.git URL https://github.com/boostorg/boost/releases/download/boost-1.87.0/boost-1.87.0-cmake.7z
GIT_TAG boost-1.87.0 USES_TERMINAL_DOWNLOAD TRUE
GIT_PROGRESS true DOWNLOAD_NO_EXTRACT FALSE
USES_TERMINAL_DOWNLOAD true
) )
FetchContent_MakeAvailable(Boost) FetchContent_MakeAvailable(Boost)
target_link_libraries(luavox_common INTERFACE Boost::asio Boost::thread Boost::json Boost::iostreams Boost::interprocess Boost::timer Boost::circular_buffer Boost::lockfree Boost::stacktrace Boost::uuid Boost::serialization Boost::nowide) target_link_libraries(${PROJECT_NAME} PUBLIC Boost::asio Boost::thread Boost::json)
# glm # glm
# find_package(glm REQUIRED) # find_package(glm REQUIRED)
# target_include_directories(${PROJECT_NAME} PUBLIC ${GLM_INCLUDE_DIR}) # target_include_directories(${PROJECT_NAME} PUBLIC ${GLM_INCLUDE_DIR})
# target_link_libraries(${PROJECT_NAME} PUBLIC ${GLM_LIBRARY}) # target_link_libraries(${PROJECT_NAME} PUBLIC ${GLM_LIBRARY})
FetchContent_Declare(
luajit
GIT_REPOSITORY https://luajit.org/git/luajit.git
GIT_TAG v2.1
GIT_PROGRESS true
USES_TERMINAL_DOWNLOAD true
)
FetchContent_MakeAvailable(luajit)
set(LUAJIT_DIR ${luajit_SOURCE_DIR})
set(LUAJIT_ENABLE_LUA52COMPAT ON)
FetchContent_Declare(
lua_cmake
GIT_REPOSITORY https://github.com/zhaozg/luajit-cmake.git
GIT_TAG 300c0b3f472be2be158f5b2e6385579ba5c6c0f9
GIT_PROGRESS true
USES_TERMINAL_DOWNLOAD true
)
FetchContent_MakeAvailable(lua_cmake)
target_link_libraries(luavox_common INTERFACE luajit::header luajit::lib)
target_include_directories(luavox_common INTERFACE ${lua_cmake_BINARY_DIR})
FetchContent_Declare(
sol2
GIT_REPOSITORY https://github.com/ThePhD/sol2.git
GIT_TAG v3.5.0
GIT_PROGRESS true
USES_TERMINAL_DOWNLOAD true
)
FetchContent_MakeAvailable(sol2)
target_link_libraries(luavox_common INTERFACE sol2::sol2)
FetchContent_Declare( FetchContent_Declare(
glm glm
@@ -128,112 +64,70 @@ FetchContent_Declare(
GIT_TAG 1.0.1 GIT_TAG 1.0.1
) )
FetchContent_MakeAvailable(glm) FetchContent_MakeAvailable(glm)
target_link_libraries(luavox_common INTERFACE glm) target_link_libraries(${PROJECT_NAME} PUBLIC glm)
find_package(ICU REQUIRED COMPONENTS i18n uc) find_package(ICU REQUIRED COMPONENTS i18n uc)
target_include_directories(luavox_common INTERFACE ${ICU_INCLUDE_DIR}) target_include_directories(${PROJECT_NAME} PUBLIC ${ICU_INCLUDE_DIR})
target_link_libraries(luavox_common INTERFACE ${ICU_LIBRARIES}) target_link_libraries(${PROJECT_NAME} PUBLIC ${ICU_LIBRARIES})
find_package(OpenSSL REQUIRED) find_package(OpenSSL REQUIRED)
target_include_directories(luavox_common INTERFACE ${OPENSSL_INCLUDE_DIR}) target_include_directories(${PROJECT_NAME} PUBLIC ${OPENSSL_INCLUDE_DIR})
target_link_libraries(luavox_common INTERFACE ${OPENSSL_LIBRARIES}) target_link_libraries(${PROJECT_NAME} PUBLIC ${OPENSSL_LIBRARIES})
# JPEG # JPEG
find_package(JPEG REQUIRED) find_package(JPEG REQUIRED)
target_include_directories(luavox_common INTERFACE ${JPEG_INCLUDE_DIRS}) target_include_directories(${PROJECT_NAME} PUBLIC ${JPEG_INCLUDE_DIRS})
target_link_libraries(luavox_common INTERFACE JPEG::JPEG) target_link_libraries(${PROJECT_NAME} PUBLIC JPEG::JPEG)
# PNG # PNG
find_package(PNG REQUIRED) find_package(PNG REQUIRED)
target_include_directories(luavox_common INTERFACE ${PNG_INCLUDE_DIRS}) target_include_directories(${PROJECT_NAME} PUBLIC ${PNG_INCLUDE_DIRS})
target_link_libraries(luavox_common INTERFACE PNG::PNG) target_link_libraries(${PROJECT_NAME} PUBLIC PNG::PNG)
# PNG++ # PNG++
target_include_directories(luavox_common INTERFACE "${PROJECT_SOURCE_DIR}/Libs/png++") target_include_directories(${PROJECT_NAME} PUBLIC "${PROJECT_SOURCE_DIR}/Libs/png++")
# 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)
FetchContent_Declare(
RectangleBinPack
GIT_REPOSITORY https://github.com/juj/RectangleBinPack.git
GIT_TAG 83e7e1132d93777e3732dfaae26b0f3703be2036
)
FetchContent_MakeAvailable(RectangleBinPack)
target_link_libraries(luavox_common INTERFACE RectangleBinPack)
# Static Assets
find_package(Python3 REQUIRED)
set(ASSETS_DIR "${PROJECT_SOURCE_DIR}/assets")
file(GLOB_RECURSE ASSETS_LIST RELATIVE "${ASSETS_DIR}" "${ASSETS_DIR}/*.*")
set(ASSETS_O "${CMAKE_CURRENT_BINARY_DIR}/assets.o")
set(ASSETS_LD_O "${CMAKE_CURRENT_BINARY_DIR}/assets_ld.o")
set(RESOURCES_CPP "${CMAKE_CURRENT_BINARY_DIR}/resources.cpp")
set(ASSETS_ABS)
foreach(asset IN LISTS ASSETS_LIST)
list(APPEND ASSETS_ABS "${ASSETS_DIR}/${asset}")
endforeach()
add_custom_command(
OUTPUT ${ASSETS_O} ${RESOURCES_CPP}
DEPENDS ${ASSETS_ABS}
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/Src/assets.py
"${RESOURCES_CPP}"
${ASSETS_LIST}
COMMAND ${CMAKE_COMMAND} -E chdir "${CMAKE_CURRENT_SOURCE_DIR}/assets" ld -r -b binary -o "${ASSETS_LD_O}" ${ASSETS_LIST}
COMMAND ${CMAKE_OBJCOPY}
-O elf64-x86-64
--rename-section .data=.rodata,alloc,load,readonly,data,contents
${ASSETS_LD_O}
${ASSETS_O}
COMMENT "Embedding assets: generating resources.cpp and assets.o"
VERBATIM
)
set_source_files_properties(${RESOURCES_CPP} PROPERTIES 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(luavox_common INTERFACE assets)
# GLFW3
if(BUILD_CLIENT) if(BUILD_CLIENT)
add_executable(luavox_client) find_package(glfw3 3)
# Common if(TARGET glfw)
target_link_libraries(luavox_client PUBLIC luavox_common) target_include_directories(${PROJECT_NAME} PUBLIC ${GLFW_INCLUDE_DIRS})
else()
# Исходники FetchContent_Declare(
file(GLOB_RECURSE SOURCES RELATIVE ${PROJECT_SOURCE_DIR} "Src/*.cpp") glfw
target_sources(luavox_client PRIVATE ${SOURCES}) GIT_REPOSITORY https://github.com/glfw/glfw.git
target_include_directories(luavox_client PUBLIC "${PROJECT_SOURCE_DIR}/Src") GIT_TAG 3.4
)
# GLFW3 FetchContent_MakeAvailable(glfw)
FetchContent_Declare( endif()
glfw target_link_libraries(${PROJECT_NAME} PUBLIC 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() 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/")
# Static Assets
file(GLOB_RECURSE ASSETS RELATIVE "${PROJECT_SOURCE_DIR}/assets" "assets/*.*")
add_custom_command(OUTPUT assets.o resources.cpp INPUT ${ASSETS}
COMMAND cd ${CMAKE_CURRENT_BINARY_DIR} && ${CMAKE_CURRENT_SOURCE_DIR}/Src/assets.py ${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)
add_library(assets STATIC resources.cpp assets.o)
SET_TARGET_PROPERTIES(assets PROPERTIES LINKER_LANGUAGE C)
target_link_libraries(${PROJECT_NAME} PUBLIC assets uring)

Submodule Libs/boost deleted from c89e626766

View File

@@ -10,9 +10,6 @@
namespace LV::Client { namespace LV::Client {
using EntityId_t = uint16_t;
using FuncEntityId_t = uint16_t;
struct GlobalTime { struct GlobalTime {
uint32_t Seconds : 22 = 0, Sub : 10 = 0; uint32_t Seconds : 22 = 0, Sub : 10 = 0;
@@ -33,22 +30,32 @@ struct GlobalTime {
} }
}; };
struct VoxelCube {
DefVoxelId_c VoxelId;
Pos::Local256_u Left, Size;
};
struct Node {
DefNodeId_c NodeId;
uint8_t Rotate : 6;
};
// 16 метров ребро // 16 метров ребро
// 256 вокселей ребро // 256 вокселей ребро
struct Chunk { struct Chunk {
// Кубы вокселей в чанке // Кубы вокселей в чанке
std::vector<VoxelCube> Voxels; std::vector<VoxelCube> Voxels;
// Ноды // Ноды
std::array<Node, 16*16*16> Nodes; std::unordered_map<Pos::Local16_u, Node> Nodes;
// Ограничения прохождения света, идущего от солнца (от верха карты до верхней плоскости чанка) // Ограничения прохождения света, идущего от солнца (от верха карты до верхней плоскости чанка)
// LightPrism Lights[16][16]; LightPrism Lights[16][16];
}; };
class Entity { class Entity {
public: public:
// PosQuat // PosQuat
WorldId_t WorldId; DefWorldId_c WorldId;
// PortalId LastUsedPortal; DefPortalId_c LastUsedPortal;
Pos::Object Pos; Pos::Object Pos;
glm::quat Quat; glm::quat Quat;
static constexpr uint16_t HP_BS = 4096, HP_BS_Bit = 12; static constexpr uint16_t HP_BS = 4096, HP_BS_Bit = 12;
@@ -63,46 +70,37 @@ public:
/* Интерфейс рендера текущего подключения к серверу */ /* Интерфейс рендера текущего подключения к серверу */
class IRenderSession { class IRenderSession {
public: public:
// Объект уведомления об изменениях virtual void onDefTexture(TextureId_c id, std::vector<std::byte> &&info) = 0;
struct TickSyncData { virtual void onDefTextureLost(const std::vector<TextureId_c> &&lost) = 0;
// Новые или изменённые используемые теперь двоичные ресурсы virtual void onDefModel(ModelId_c id, std::vector<std::byte> &&info) = 0;
std::unordered_map<EnumAssets, std::vector<ResourceId>> Assets_ChangeOrAdd; virtual void onDefModelLost(const std::vector<ModelId_c> &&lost) = 0;
// Более не используемые ресурсы
std::unordered_map<EnumAssets, std::vector<ResourceId>> Assets_Lost;
// Новые или изменённые профили контента virtual void onDefWorldUpdates(const std::vector<DefWorldId_c> &updates) = 0;
std::unordered_map<EnumDefContent, std::vector<ResourceId>> Profiles_ChangeOrAdd; virtual void onDefVoxelUpdates(const std::vector<DefVoxelId_c> &updates) = 0;
// Более не используемые профили virtual void onDefNodeUpdates(const std::vector<DefNodeId_c> &updates) = 0;
std::unordered_map<EnumDefContent, std::vector<ResourceId>> Profiles_Lost; virtual void onDefPortalUpdates(const std::vector<DefPortalId_c> &updates) = 0;
virtual void onDefEntityUpdates(const std::vector<DefEntityId_c> &updates) = 0;
// Новые или изменённые чанки
std::unordered_map<WorldId_t, std::vector<Pos::GlobalChunk>> Chunks_ChangeOrAdd;
// Более не отслеживаемые регионы
std::unordered_map<WorldId_t, std::vector<Pos::GlobalRegion>> Chunks_Lost;
};
public:
// Серверная сессия собирается обработать данные такток сервера (изменение профилей, ресурсов, прочих игровых данных)
virtual void prepareTickSync() = 0;
// Началась стадия изменения данных IServerSession, все должны приостановить работу
virtual void pushStageTickSync() = 0;
// После изменения внутренних данных IServerSession, IRenderSession уведомляется об изменениях
virtual void tickSync(const TickSyncData& data) = 0;
// Сообщаем об изменившихся чанках
virtual void onChunksChange(WorldId_c worldId, const std::unordered_set<Pos::GlobalChunk> &changeOrAddList, const std::unordered_set<Pos::GlobalChunk> &remove) = 0;
// Установить позицию для камеры // Установить позицию для камеры
virtual void setCameraPos(WorldId_t worldId, Pos::Object pos, glm::quat quat) = 0; virtual void setCameraPos(WorldId_c worldId, Pos::Object pos, glm::quat quat) = 0;
virtual ~IRenderSession(); virtual ~IRenderSession();
}; };
struct Region { struct Region {
std::array<Chunk, 4*4*4> Chunks; std::unordered_map<Pos::Local16_u, Chunk> Chunks;
}; };
struct World { struct World {
std::vector<EntityId_c> Entitys;
std::unordered_map<Pos::GlobalRegion::Key, Region> Regions;
}; };
struct DefWorldInfo { struct DefWorldInfo {
}; };
@@ -112,16 +110,11 @@ struct DefPortalInfo {
}; };
struct DefEntityInfo { struct DefEntityInfo {
};
struct DefFuncEntityInfo {
}; };
struct WorldInfo { struct WorldInfo {
std::vector<EntityId_t> Entitys;
std::vector<FuncEntityId_t> FuncEntitys;
std::unordered_map<Pos::GlobalRegion, Region> Regions;
}; };
struct VoxelInfo { struct VoxelInfo {
@@ -137,73 +130,31 @@ struct PortalInfo {
}; };
struct EntityInfo { struct EntityInfo {
DefEntityId DefId = 0;
WorldId_t WorldId = 0;
Pos::Object Pos = Pos::Object(0);
glm::quat Quat = glm::quat(1.f, 0.f, 0.f, 0.f);
};
struct FuncEntityInfo {
}; };
struct DefItemInfo { /* Интерфейс обработчика сессии с сервером */
};
struct DefVoxel_t {};
struct DefNode_t {
AssetsNodestate NodestateId = 0;
AssetsTexture TexId = 0;
};
struct AssetEntry {
EnumAssets Type;
ResourceId Id;
std::string Domain, Key;
Resource Res;
Hash_t Hash = {};
std::vector<uint8_t> Dependencies;
};
/*
Интерфейс обработчика сессии с сервером.
Данный здесь меняются только меж вызовами
IRenderSession::pushStageTickSync
и
IRenderSession::tickSync
*/
class IServerSession { class IServerSession {
public: public:
// Включить логирование входящих сетевых пакетов на клиенте.
bool DebugLogPackets = false;
// Используемые двоичные ресурсы
std::unordered_map<EnumAssets, std::unordered_map<ResourceId, AssetEntry>> Assets;
// Используемые профили контента
struct { struct {
std::unordered_map<DefVoxelId, DefVoxel_t> DefVoxel; std::unordered_map<DefWorldId_c, DefWorldInfo> DefWorlds;
std::unordered_map<DefNodeId, DefNode_t> DefNode; std::unordered_map<DefVoxelId_c, VoxelInfo> DefVoxels;
std::unordered_map<DefWorldId, DefWorldInfo> DefWorld; std::unordered_map<DefNodeId_c, NodeInfo> DefNodes;
std::unordered_map<DefPortalId, DefPortalInfo> DefPortal; std::unordered_map<DefPortalId_c, DefPortalInfo> DefPortals;
std::unordered_map<DefEntityId, DefEntityInfo> DefEntity; std::unordered_map<DefEntityId_c, DefEntityInfo> DefEntityes;
std::unordered_map<DefItemId, DefItemInfo> DefItem;
} Profiles; std::unordered_map<WorldId_c, WorldInfo> Worlds;
std::unordered_map<PortalId_c, PortalInfo> Portals;
std::unordered_map<EntityId_c, EntityInfo> Entityes;
} Registry;
// Видимый контент
struct { struct {
std::unordered_map<WorldId_t, WorldInfo> Worlds; std::unordered_map<WorldId_c, World> Worlds;
// std::unordered_map<PortalId_t, PortalInfo> Portals; } External;
std::unordered_map<EntityId_t, EntityInfo> Entityes;
} Content;
virtual ~IServerSession(); virtual ~IServerSession();
// Обновление сессии с сервером, может начатся стадия IRenderSession::tickSync virtual void atFreeDrawTime(GlobalTime gTime, float dTime) = 0;
virtual void update(GlobalTime gTime, float dTime) = 0;
}; };
@@ -215,7 +166,7 @@ public:
} CursorMode = EnumCursorMoveMode::Default; } CursorMode = EnumCursorMoveMode::Default;
enum struct EnumCursorBtn { enum struct EnumCursorBtn {
Left, Right, Middle, One, Two Left, Middle, Right, One, Two
}; };
public: public:
@@ -239,4 +190,4 @@ public:
virtual ~ISurfaceEventListener(); virtual ~ISurfaceEventListener();
}; };
} }

View File

@@ -1,511 +0,0 @@
#include "AssetsCacheManager.hpp"
#include "Common/Abstract.hpp"
#include "sqlite3.h"
#include <chrono>
#include <cstddef>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <optional>
#include <thread>
#include <utility>
namespace LV::Client {
AssetsCacheManager::AssetsCacheManager(boost::asio::io_context &ioc, const fs::path &cachePath,
size_t maxCacheDirectorySize, size_t maxLifeTime)
: IAsyncDestructible(ioc), CachePath(cachePath)
{
{
auto lock = Changes.lock();
lock->MaxCacheDatabaseSize = maxCacheDirectorySize;
lock->MaxLifeTime = maxLifeTime;
lock->MaxChange = true;
}
if(!fs::exists(PathFiles)) {
LOG.debug() << "Директория для хранения кеша отсутствует, создаём новую '" << CachePath << '\'';
fs::create_directories(PathFiles);
}
LOG.debug() << "Открываем базу данных кеша... (инициализация sqlite3)";
{
int errc = sqlite3_open_v2(PathDatabase.c_str(), &DB, SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE, nullptr);
if(errc)
MAKE_ERROR("Не удалось открыть базу данных " << PathDatabase.c_str() << ": " << sqlite3_errmsg(DB));
const char* sql = R"(
CREATE TABLE IF NOT EXISTS disk_cache(
sha256 BLOB(32) NOT NULL, --
last_used INT NOT NULL, -- unix timestamp
size INT NOT NULL, -- file size
UNIQUE (sha256));
CREATE INDEX IF NOT EXISTS idx__disk_cache__sha256 ON disk_cache(sha256);
CREATE TABLE IF NOT EXISTS inline_cache(
sha256 BLOB(32) NOT NULL, --
last_used INT NOT NULL, -- unix timestamp
data BLOB NOT NULL, -- file data
UNIQUE (sha256));
CREATE INDEX IF NOT EXISTS idx__inline_cache__sha256 ON inline_cache(sha256);
)";
errc = sqlite3_exec(DB, sql, nullptr, nullptr, nullptr);
if(errc != SQLITE_OK) {
MAKE_ERROR("Не удалось подготовить таблицы: " << sqlite3_errmsg(DB));
}
sql = R"(
INSERT OR REPLACE INTO disk_cache (sha256, last_used, size)
VALUES (?, ?, ?);
)";
if(sqlite3_prepare_v2(DB, sql, -1, &STMT_DISK_INSERT, nullptr) != SQLITE_OK) {
MAKE_ERROR("Не удалось подготовить запрос STMT_DISK_INSERT: " << sqlite3_errmsg(DB));
}
sql = R"(
UPDATE disk_cache SET last_used = ? WHERE sha256 = ?;
)";
if(sqlite3_prepare_v2(DB, sql, -1, &STMT_DISK_UPDATE_TIME, nullptr) != SQLITE_OK) {
MAKE_ERROR("Не удалось подготовить запрос STMT_DISK_UPDATE_TIME: " << sqlite3_errmsg(DB));
}
sql = R"(
DELETE FROM disk_cache WHERE sha256=?;
)";
if(sqlite3_prepare_v2(DB, sql, -1, &STMT_DISK_REMOVE, nullptr) != SQLITE_OK) {
MAKE_ERROR("Не удалось подготовить запрос STMT_DISK_REMOVE: " << sqlite3_errmsg(DB));
}
sql = R"(
SELECT 1 FROM disk_cache where sha256=?;
)";
if(sqlite3_prepare_v2(DB, sql, -1, &STMT_DISK_CONTAINS, nullptr) != SQLITE_OK) {
MAKE_ERROR("Не удалось подготовить запрос STMT_DISK_CONTAINS: " << sqlite3_errmsg(DB));
}
sql = R"(
SELECT SUM(size) FROM disk_cache;
)";
if(sqlite3_prepare_v2(DB, sql, -1, &STMT_DISK_SUM, nullptr) != SQLITE_OK) {
MAKE_ERROR("Не удалось подготовить запрос STMT_DISK_SUM: " << sqlite3_errmsg(DB));
}
sql = R"(
SELECT COUNT(*) FROM disk_cache;
)";
if(sqlite3_prepare_v2(DB, sql, -1, &STMT_DISK_COUNT, nullptr) != SQLITE_OK) {
MAKE_ERROR("Не удалось подготовить запрос STMT_DISK_COUNT: " << sqlite3_errmsg(DB));
}
sql = R"(
SELECT sha256, size FROM disk_cache ORDER BY last_used ASC;
)";
if(sqlite3_prepare_v2(DB, sql, -1, &STMT_DISK_OLDEST, nullptr) != SQLITE_OK) {
MAKE_ERROR("Не удалось подготовить запрос STMT_DISK_OLDEST: " << sqlite3_errmsg(DB));
}
sql = R"(
INSERT OR REPLACE INTO inline_cache (sha256, last_used, data)
VALUES (?, ?, ?);
)";
if(sqlite3_prepare_v2(DB, sql, -1, &STMT_INLINE_INSERT, nullptr) != SQLITE_OK) {
MAKE_ERROR("Не удалось подготовить запрос STMT_INLINE_INSERT: " << sqlite3_errmsg(DB));
}
sql = R"(
SELECT data FROM inline_cache where sha256=?;
)";
if(sqlite3_prepare_v2(DB, sql, -1, &STMT_INLINE_GET, nullptr) != SQLITE_OK) {
MAKE_ERROR("Не удалось подготовить запрос STMT_INLINE_GET: " << sqlite3_errmsg(DB));
}
sql = R"(
UPDATE inline_cache SET last_used = ? WHERE sha256 = ?;
)";
if(sqlite3_prepare_v2(DB, sql, -1, &STMT_INLINE_UPDATE_TIME, nullptr) != SQLITE_OK) {
MAKE_ERROR("Не удалось подготовить запрос STMT_INLINE_UPDATE_TIME: " << sqlite3_errmsg(DB));
}
sql = R"(
SELECT SUM(LENGTH(data)) from inline_cache;
)";
if(sqlite3_prepare_v2(DB, sql, -1, &STMT_INLINE_SUM, nullptr) != SQLITE_OK) {
MAKE_ERROR("Не удалось подготовить запрос STMT_INLINE_SUM: " << sqlite3_errmsg(DB));
}
sql = R"(
SELECT COUNT(*) FROM inline_cache;
)";
if(sqlite3_prepare_v2(DB, sql, -1, &STMT_INLINE_COUNT, nullptr) != SQLITE_OK) {
MAKE_ERROR("Не удалось подготовить запрос STMT_INLINE_COUNT: " << sqlite3_errmsg(DB));
}
sql = R"(
DELETE FROM inline_cache WHERE sha256=?;
)";
if(sqlite3_prepare_v2(DB, sql, -1, &STMT_INLINE_REMOVE, nullptr) != SQLITE_OK) {
MAKE_ERROR("Не удалось подготовить запрос STMT_INLINE_REMOVE: " << sqlite3_errmsg(DB));
}
sql = R"(
SELECT sha256, LENGTH(data) FROM inline_cache ORDER BY last_used ASC;
)";
if(sqlite3_prepare_v2(DB, sql, -1, &STMT_INLINE_OLDEST, nullptr) != SQLITE_OK) {
MAKE_ERROR("Не удалось подготовить запрос STMT_INLINE_OLDEST: " << sqlite3_errmsg(DB));
}
}
LOG.debug() << "Успешно, запускаем поток обработки";
OffThread = std::thread(&AssetsCacheManager::readWriteThread, this, AUC.use());
LOG.info() << "Инициализировано хранилище кеша: " << CachePath.c_str();
}
AssetsCacheManager::~AssetsCacheManager() {
for(sqlite3_stmt* stmt : {
STMT_DISK_INSERT, STMT_DISK_UPDATE_TIME, STMT_DISK_REMOVE, STMT_DISK_CONTAINS,
STMT_DISK_SUM, STMT_DISK_COUNT, STMT_DISK_OLDEST, STMT_INLINE_INSERT,
STMT_INLINE_GET, STMT_INLINE_UPDATE_TIME, STMT_INLINE_SUM,
STMT_INLINE_COUNT, STMT_INLINE_REMOVE, STMT_INLINE_OLDEST
}) {
if(stmt)
sqlite3_finalize(stmt);
}
if(DB)
sqlite3_close(DB);
OffThread.join();
LOG.info() << "Хранилище кеша закрыто";
}
coro<> AssetsCacheManager::asyncDestructor() {
NeedShutdown = true;
co_await IAsyncDestructible::asyncDestructor();
}
void AssetsCacheManager::readWriteThread(AsyncUseControl::Lock lock) {
try {
[[maybe_unused]] size_t maxCacheDatabaseSize = 0;
[[maybe_unused]] size_t maxLifeTime = 0;
bool databaseSizeKnown = false;
while(!NeedShutdown || !WriteQueue.get_read().empty()) {
// Получить новые данные
if(Changes.get_read().MaxChange) {
auto lock = Changes.lock();
maxCacheDatabaseSize = lock->MaxCacheDatabaseSize;
maxLifeTime = lock->MaxLifeTime;
lock->MaxChange = false;
}
if(Changes.get_read().FullRecheck) {
std::move_only_function<void(std::string)> onRecheckEnd;
{
auto lock = Changes.lock();
onRecheckEnd = std::move(*lock->OnRecheckEnd);
lock->FullRecheck = false;
}
LOG.info() << "Начата проверка консистентности кеша ассетов";
LOG.info() << "Завершена проверка консистентности кеша ассетов";
}
// Чтение
if(!ReadQueue.get_read().empty()) {
Hash_t hash;
{
auto lock = ReadQueue.lock();
hash = lock->front();
lock->pop();
}
bool finded = false;
// Поищем в малой базе
sqlite3_bind_blob(STMT_INLINE_GET, 1, (const void*) hash.data(), 32, SQLITE_STATIC);
int errc = sqlite3_step(STMT_INLINE_GET);
if(errc == SQLITE_ROW) {
// Есть запись
const uint8_t *data = (const uint8_t*) sqlite3_column_blob(STMT_INLINE_GET, 0);
int size = sqlite3_column_bytes(STMT_INLINE_GET, 0);
Resource res(data, size);
finded = true;
ReadyQueue.lock()->emplace_back(hash, res);
} else if(errc != SQLITE_DONE) {
sqlite3_reset(STMT_INLINE_GET);
MAKE_ERROR("Не удалось выполнить подготовленный запрос STMT_INLINE_GET: " << sqlite3_errmsg(DB));
}
sqlite3_reset(STMT_INLINE_GET);
if(finded) {
sqlite3_bind_blob(STMT_INLINE_UPDATE_TIME, 1, (const void*) hash.data(), 32, SQLITE_STATIC);
sqlite3_bind_int(STMT_INLINE_UPDATE_TIME, 2, time(nullptr));
if(sqlite3_step(STMT_INLINE_UPDATE_TIME) != SQLITE_DONE) {
sqlite3_reset(STMT_INLINE_UPDATE_TIME);
MAKE_ERROR("Не удалось выполнить подготовленный запрос STMT_INLINE_UPDATE_TIME: " << sqlite3_errmsg(DB));
}
sqlite3_reset(STMT_INLINE_UPDATE_TIME);
}
if(!finded) {
// Поищем на диске
sqlite3_bind_blob(STMT_DISK_CONTAINS, 1, (const void*) hash.data(), 32, SQLITE_STATIC);
errc = sqlite3_step(STMT_DISK_CONTAINS);
if(errc == SQLITE_ROW) {
// Есть запись
std::string hashKey;
{
std::stringstream ss;
ss << std::hex << std::setfill('0') << std::setw(2);
for (int i = 0; i < 32; ++i)
ss << static_cast<int>(hash[i]);
hashKey = ss.str();
}
finded = true;
ReadyQueue.lock()->emplace_back(hash, PathFiles / hashKey.substr(0, 2) / hashKey.substr(2));
} else if(errc != SQLITE_DONE) {
sqlite3_reset(STMT_DISK_CONTAINS);
MAKE_ERROR("Не удалось выполнить подготовленный запрос STMT_DISK_CONTAINS: " << sqlite3_errmsg(DB));
}
sqlite3_reset(STMT_DISK_CONTAINS);
if(finded) {
sqlite3_bind_int(STMT_DISK_UPDATE_TIME, 1, time(nullptr));
sqlite3_bind_blob(STMT_DISK_UPDATE_TIME, 2, (const void*) hash.data(), 32, SQLITE_STATIC);
if(sqlite3_step(STMT_DISK_UPDATE_TIME) != SQLITE_DONE) {
sqlite3_reset(STMT_DISK_UPDATE_TIME);
MAKE_ERROR("Не удалось выполнить подготовленный запрос STMT_DISK_UPDATE_TIME: " << sqlite3_errmsg(DB));
}
sqlite3_reset(STMT_DISK_UPDATE_TIME);
}
}
if(!finded) {
// Не нашли
ReadyQueue.lock()->emplace_back(hash, std::nullopt);
}
continue;
}
// Запись
if(!WriteQueue.get_read().empty()) {
Resource res;
{
auto lock = WriteQueue.lock();
res = lock->front();
lock->pop();
}
if(!databaseSizeKnown) {
size_t diskSize = 0;
size_t inlineSize = 0;
int errc = sqlite3_step(STMT_DISK_SUM);
if(errc == SQLITE_ROW) {
if(sqlite3_column_type(STMT_DISK_SUM, 0) != SQLITE_NULL)
diskSize = static_cast<size_t>(sqlite3_column_int64(STMT_DISK_SUM, 0));
} else if(errc != SQLITE_DONE) {
sqlite3_reset(STMT_DISK_SUM);
MAKE_ERROR("Не удалось выполнить подготовленный запрос STMT_DISK_SUM: " << sqlite3_errmsg(DB));
}
sqlite3_reset(STMT_DISK_SUM);
errc = sqlite3_step(STMT_INLINE_SUM);
if(errc == SQLITE_ROW) {
if(sqlite3_column_type(STMT_INLINE_SUM, 0) != SQLITE_NULL)
inlineSize = static_cast<size_t>(sqlite3_column_int64(STMT_INLINE_SUM, 0));
} else if(errc != SQLITE_DONE) {
sqlite3_reset(STMT_INLINE_SUM);
MAKE_ERROR("Не удалось выполнить подготовленный запрос STMT_INLINE_SUM: " << sqlite3_errmsg(DB));
}
sqlite3_reset(STMT_INLINE_SUM);
DatabaseSize = diskSize + inlineSize;
databaseSizeKnown = true;
}
if(maxCacheDatabaseSize > 0 && DatabaseSize + res.size() > maxCacheDatabaseSize) {
size_t bytesToFree = DatabaseSize + res.size() - maxCacheDatabaseSize;
sqlite3_reset(STMT_DISK_OLDEST);
int errc = SQLITE_ROW;
while(bytesToFree > 0 && (errc = sqlite3_step(STMT_DISK_OLDEST)) == SQLITE_ROW) {
const void* data = sqlite3_column_blob(STMT_DISK_OLDEST, 0);
int dataSize = sqlite3_column_bytes(STMT_DISK_OLDEST, 0);
if(data && dataSize == 32) {
Hash_t hash;
std::memcpy(hash.data(), data, 32);
size_t entrySize = static_cast<size_t>(sqlite3_column_int64(STMT_DISK_OLDEST, 1));
std::string hashKey = hashToString(hash);
fs::path end = PathFiles / hashKey.substr(0, 2) / hashKey.substr(2);
std::error_code ec;
fs::remove(end, ec);
sqlite3_bind_blob(STMT_DISK_REMOVE, 1, (const void*) hash.data(), 32, SQLITE_STATIC);
if(sqlite3_step(STMT_DISK_REMOVE) != SQLITE_DONE) {
sqlite3_reset(STMT_DISK_REMOVE);
MAKE_ERROR("Не удалось выполнить подготовленный запрос STMT_DISK_REMOVE: " << sqlite3_errmsg(DB));
}
sqlite3_reset(STMT_DISK_REMOVE);
if(DatabaseSize >= entrySize)
DatabaseSize -= entrySize;
else
DatabaseSize = 0;
if(bytesToFree > entrySize)
bytesToFree -= entrySize;
else
bytesToFree = 0;
}
}
if(errc != SQLITE_DONE && errc != SQLITE_ROW) {
sqlite3_reset(STMT_DISK_OLDEST);
MAKE_ERROR("Не удалось выполнить подготовленный запрос STMT_DISK_OLDEST: " << sqlite3_errmsg(DB));
}
sqlite3_reset(STMT_DISK_OLDEST);
sqlite3_reset(STMT_INLINE_OLDEST);
errc = SQLITE_ROW;
while(bytesToFree > 0 && (errc = sqlite3_step(STMT_INLINE_OLDEST)) == SQLITE_ROW) {
const void* data = sqlite3_column_blob(STMT_INLINE_OLDEST, 0);
int dataSize = sqlite3_column_bytes(STMT_INLINE_OLDEST, 0);
if(data && dataSize == 32) {
Hash_t hash;
std::memcpy(hash.data(), data, 32);
size_t entrySize = static_cast<size_t>(sqlite3_column_int64(STMT_INLINE_OLDEST, 1));
sqlite3_bind_blob(STMT_INLINE_REMOVE, 1, (const void*) hash.data(), 32, SQLITE_STATIC);
if(sqlite3_step(STMT_INLINE_REMOVE) != SQLITE_DONE) {
sqlite3_reset(STMT_INLINE_REMOVE);
MAKE_ERROR("Не удалось выполнить подготовленный запрос STMT_INLINE_REMOVE: " << sqlite3_errmsg(DB));
}
sqlite3_reset(STMT_INLINE_REMOVE);
if(DatabaseSize >= entrySize)
DatabaseSize -= entrySize;
else
DatabaseSize = 0;
if(bytesToFree > entrySize)
bytesToFree -= entrySize;
else
bytesToFree = 0;
}
}
if(errc != SQLITE_DONE && errc != SQLITE_ROW) {
sqlite3_reset(STMT_INLINE_OLDEST);
MAKE_ERROR("Не удалось выполнить подготовленный запрос STMT_INLINE_OLDEST: " << sqlite3_errmsg(DB));
}
sqlite3_reset(STMT_INLINE_OLDEST);
}
if(res.size() <= SMALL_RESOURCE) {
Hash_t hash = res.hash();
LOG.debug() << "Сохраняем ресурс " << hashToString(hash);
try {
sqlite3_bind_blob(STMT_INLINE_INSERT, 1, (const void*) hash.data(), 32, SQLITE_STATIC);
sqlite3_bind_int(STMT_INLINE_INSERT, 2, time(nullptr));
sqlite3_bind_blob(STMT_INLINE_INSERT, 3, res.data(), res.size(), SQLITE_STATIC);
if(sqlite3_step(STMT_INLINE_INSERT) != SQLITE_DONE) {
sqlite3_reset(STMT_INLINE_INSERT);
MAKE_ERROR("Не удалось выполнить подготовленный запрос STMT_INLINE_INSERT: " << sqlite3_errmsg(DB));
}
sqlite3_reset(STMT_INLINE_INSERT);
DatabaseSize += res.size();
} catch(const std::exception& exc) {
LOG.error() << "Произошла ошибка при сохранении " << hashToString(hash);
throw;
}
} else {
std::string hashKey;
{
std::stringstream ss;
ss << std::hex << std::setfill('0') << std::setw(2);
for (int i = 0; i < 32; ++i)
ss << static_cast<int>(res.hash()[i]);
hashKey = ss.str();
}
fs::path end = PathFiles / hashKey.substr(0, 2) / hashKey.substr(2);
fs::create_directories(end.parent_path());
std::ofstream fd(end, std::ios::binary);
fd.write((const char*) res.data(), res.size());
if(fd.fail())
MAKE_ERROR("Ошибка записи в файл: " << end.string());
fd.close();
Hash_t hash = res.hash();
sqlite3_bind_blob(STMT_DISK_INSERT, 1, (const void*) hash.data(), 32, SQLITE_STATIC);
sqlite3_bind_int(STMT_DISK_INSERT, 2, time(nullptr));
sqlite3_bind_int(STMT_DISK_INSERT, 3, res.size());
if(sqlite3_step(STMT_DISK_INSERT) != SQLITE_DONE) {
sqlite3_reset(STMT_DISK_INSERT);
MAKE_ERROR("Не удалось выполнить подготовленный запрос STMT_DISK_INSERT: " << sqlite3_errmsg(DB));
}
sqlite3_reset(STMT_DISK_INSERT);
DatabaseSize += res.size();
}
continue;
}
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
} catch(const std::exception& exc) {
LOG.warn() << "Ошибка в работе потока:\n" << exc.what();
IssuedAnError = true;
}
}
std::string AssetsCacheManager::hashToString(const Hash_t& hash) {
std::stringstream ss;
ss << std::hex << std::setfill('0');
for (const auto& byte : hash)
ss << std::setw(2) << static_cast<int>(byte);
return ss.str();
}
}

View File

@@ -1,196 +0,0 @@
#pragma once
#include "Common/Abstract.hpp"
#include <cassert>
#include <functional>
#include <memory>
#include <optional>
#include <queue>
#include <string>
#include <sqlite3.h>
#include <TOSLib.hpp>
#include <TOSAsync.hpp>
#include <filesystem>
#include <string_view>
#include <thread>
#include <vector>
namespace LV::Client {
using namespace TOS;
namespace fs = std::filesystem;
// NOT ThreadSafe
class CacheDatabase {
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_OLD = nullptr,
*STMT_TO_FREE = nullptr,
*STMT_COUNT = nullptr;
public:
CacheDatabase(const fs::path &cachePath);
~CacheDatabase();
CacheDatabase(const CacheDatabase&) = delete;
CacheDatabase(CacheDatabase&&) = delete;
CacheDatabase& operator=(const CacheDatabase&) = delete;
CacheDatabase& operator=(CacheDatabase&&) = delete;
/*
Выдаёт размер занимаемый всем хранимым кешем
*/
size_t getCacheSize();
// TODO: добавить ограничения на количество файлов
/*
Создаёт линейный массив в котором подряд указаны все хэш суммы в бинарном виде и возвращает их количество
*/
// std::pair<std::string, size_t> getAllHash();
/*
Обновляет время использования кеша
*/
void updateTimeFor(Hash_t hash);
/*
Добавляет запись
*/
void insert(Hash_t hash, size_t size);
/*
Выдаёт хэши на удаление по размеру в сумме больше bytesToFree.
Сначала удаляется старьё, потом по приоритету дата использования + размер
*/
std::vector<Hash_t> findExcessHashes(size_t bytesToFree, int timeBefore);
/*
Удаление записи
*/
void remove(Hash_t hash);
static std::string hashToString(Hash_t hash);
static int hexCharToInt(char c);
static Hash_t stringToHash(const std::string_view view);
};
/*
Менеджер кеша ресурсов по хэшу.
Интерфейс однопоточный, обработка файлов в отдельном потоке.
*/
class AssetsCacheManager : public IAsyncDestructible {
public:
using Ptr = std::shared_ptr<AssetsCacheManager>;
public:
virtual ~AssetsCacheManager();
static std::shared_ptr<AssetsCacheManager> Create(asio::io_context &ioc, const fs::path& cachePath,
size_t maxCacheDirectorySize = 8*1024*1024*1024ULL, size_t maxLifeTime = 7*24*60*60) {
return createShared(ioc, new AssetsCacheManager(ioc, cachePath, maxCacheDirectorySize, maxLifeTime));
}
// Добавить новый полученный с сервера ресурс
void pushResources(std::vector<Resource> resources) {
WriteQueue.lock()->push_range(resources);
}
// Добавить задачи на чтение по хэшу
void pushReads(std::vector<Hash_t> hashes) {
ReadQueue.lock()->push_range(hashes);
}
// Получить считанные данные по хэшу
std::vector<std::pair<Hash_t, std::optional<Resource>>> pullReads() {
return std::move(*ReadyQueue.lock());
}
// Размер всего хранимого кеша
size_t getCacheSize() {
return DatabaseSize;
}
// Обновить параметры хранилища кеша
void updateParams(size_t maxLifeTime, size_t maxCacheDirectorySize) {
auto lock = Changes.lock();
lock->MaxLifeTime = maxLifeTime;
lock->MaxCacheDatabaseSize = maxCacheDirectorySize;
lock->MaxChange = true;
}
// Запуск процедуры проверки хешей всего хранимого кеша
void runFullDatabaseRecheck(std::move_only_function<void(std::string result)>&& func) {
auto lock = Changes.lock();
lock->OnRecheckEnd = std::move(func);
lock->FullRecheck = true;
}
bool hasError() {
return IssuedAnError;
}
private:
Logger LOG = "Client>ResourceHandler";
const fs::path
CachePath,
PathDatabase = CachePath / "db.sqlite3",
PathFiles = CachePath / "blobs";
static constexpr size_t SMALL_RESOURCE = 1 << 21;
sqlite3 *DB = nullptr; // База хранения кеша меньше 2мб и информации о кеше на диске
sqlite3_stmt
*STMT_DISK_INSERT = nullptr, // Вставка записи о хеше
*STMT_DISK_UPDATE_TIME = nullptr, // Обновить дату последнего использования
*STMT_DISK_REMOVE = nullptr, // Удалить хеш
*STMT_DISK_CONTAINS = nullptr, // Проверка наличия хеша
*STMT_DISK_SUM = nullptr, // Вычисляет занятое место на диске
*STMT_DISK_COUNT = nullptr, // Возвращает количество записей
*STMT_DISK_OLDEST = nullptr, // Самые старые записи на диске
*STMT_INLINE_INSERT = nullptr, // Вставка ресурса
*STMT_INLINE_GET = nullptr, // Поиск ресурса по хешу
*STMT_INLINE_UPDATE_TIME = nullptr, // Обновить дату последнего использования
*STMT_INLINE_SUM = nullptr, // Размер внутреннего хранилища
*STMT_INLINE_COUNT = nullptr, // Возвращает количество записей
*STMT_INLINE_REMOVE = nullptr, // Удалить ресурс
*STMT_INLINE_OLDEST = nullptr; // Самые старые записи в базе
// Полный размер данных на диске (насколько известно)
volatile size_t DatabaseSize = 0;
// Очередь задач на чтение
TOS::SpinlockObject<std::queue<Hash_t>> ReadQueue;
// Очередь на запись ресурсов
TOS::SpinlockObject<std::queue<Resource>> WriteQueue;
// Очередь на выдачу результатов чтения
TOS::SpinlockObject<std::vector<std::pair<Hash_t, std::optional<Resource>>>> ReadyQueue;
struct Changes_t {
size_t MaxCacheDatabaseSize, MaxLifeTime;
volatile bool MaxChange = false;
std::optional<std::move_only_function<void(std::string)>> OnRecheckEnd;
volatile bool FullRecheck = false;
};
TOS::SpinlockObject<Changes_t> Changes;
bool NeedShutdown = false, IssuedAnError = false;
std::thread OffThread;
virtual coro<> asyncDestructor();
AssetsCacheManager(boost::asio::io_context &ioc, const fs::path &cachePath,
size_t maxCacheDatabaseSize, size_t maxLifeTime);
void readWriteThread(AsyncUseControl::Lock lock);
std::string hashToString(const Hash_t& hash);
};
}

View File

@@ -1,484 +0,0 @@
#include "Client/AssetsHeaderCodec.hpp"
#include <cstring>
#include <unordered_set>
#include "TOSLib.hpp"
namespace LV::Client::AssetsHeaderCodec {
namespace {
struct ParsedModelHeader {
std::vector<ResourceId> ModelDeps;
std::vector<std::vector<uint8_t>> TexturePipelines;
std::vector<ResourceId> TextureDeps;
};
std::optional<std::vector<ResourceId>> parseNodestateHeaderBytes(const std::vector<uint8_t>& header) {
if(header.empty() || header.size() % sizeof(ResourceId) != 0)
return std::nullopt;
const size_t count = header.size() / sizeof(ResourceId);
std::vector<ResourceId> deps;
deps.resize(count);
for(size_t i = 0; i < count; ++i) {
ResourceId raw = 0;
std::memcpy(&raw, header.data() + i * sizeof(ResourceId), sizeof(ResourceId));
deps[i] = raw;
}
return deps;
}
struct PipelineRemapResult {
bool Ok = true;
std::string Error;
};
PipelineRemapResult remapTexturePipelineIds(std::vector<uint8_t>& code,
const std::function<uint32_t(uint32_t)>& mapId)
{
struct Range {
size_t Start = 0;
size_t End = 0;
};
enum class SrcKind : uint8_t { TexId = 0, Sub = 1 };
enum class Op : uint8_t {
End = 0,
Base_Tex = 1,
Base_Fill = 2,
Base_Anim = 3,
Resize = 10,
Transform = 11,
Opacity = 12,
NoAlpha = 13,
MakeAlpha = 14,
Invert = 15,
Brighten = 16,
Contrast = 17,
Multiply = 18,
Screen = 19,
Colorize = 20,
Anim = 21,
Overlay = 30,
Mask = 31,
LowPart = 32,
Combine = 40
};
struct SrcMeta {
SrcKind Kind = SrcKind::TexId;
uint32_t TexId = 0;
uint32_t Off = 0;
uint32_t Len = 0;
size_t TexIdOffset = 0;
};
const size_t size = code.size();
std::vector<Range> visited;
auto read8 = [&](size_t& ip, uint8_t& out)->bool{
if(ip >= size)
return false;
out = code[ip++];
return true;
};
auto read16 = [&](size_t& ip, uint16_t& out)->bool{
if(ip + 1 >= size)
return false;
out = uint16_t(code[ip]) | (uint16_t(code[ip + 1]) << 8);
ip += 2;
return true;
};
auto read24 = [&](size_t& ip, uint32_t& out)->bool{
if(ip + 2 >= size)
return false;
out = uint32_t(code[ip])
| (uint32_t(code[ip + 1]) << 8)
| (uint32_t(code[ip + 2]) << 16);
ip += 3;
return true;
};
auto read32 = [&](size_t& ip, uint32_t& out)->bool{
if(ip + 3 >= size)
return false;
out = uint32_t(code[ip])
| (uint32_t(code[ip + 1]) << 8)
| (uint32_t(code[ip + 2]) << 16)
| (uint32_t(code[ip + 3]) << 24);
ip += 4;
return true;
};
auto readSrc = [&](size_t& ip, SrcMeta& out)->bool{
uint8_t kind = 0;
if(!read8(ip, kind))
return false;
out.Kind = static_cast<SrcKind>(kind);
if(out.Kind == SrcKind::TexId) {
out.TexIdOffset = ip;
return read24(ip, out.TexId);
}
if(out.Kind == SrcKind::Sub) {
return read24(ip, out.Off) && read24(ip, out.Len);
}
return false;
};
auto patchTexId = [&](const SrcMeta& src)->PipelineRemapResult{
if(src.Kind != SrcKind::TexId)
return {};
uint32_t newId = mapId(src.TexId);
if(newId >= (1u << 24))
return {false, "TexId exceeds u24 range"};
if(src.TexIdOffset + 2 >= code.size())
return {false, "TexId patch outside pipeline"};
code[src.TexIdOffset + 0] = uint8_t(newId & 0xFFu);
code[src.TexIdOffset + 1] = uint8_t((newId >> 8) & 0xFFu);
code[src.TexIdOffset + 2] = uint8_t((newId >> 16) & 0xFFu);
return {};
};
std::function<bool(size_t, size_t)> scan;
scan = [&](size_t start, size_t end) -> bool {
if(start >= end || end > size)
return true;
for(const auto& range : visited) {
if(range.Start == start && range.End == end)
return true;
}
visited.push_back(Range{start, end});
size_t ip = start;
while(ip < end) {
uint8_t opByte = 0;
if(!read8(ip, opByte))
return false;
Op op = static_cast<Op>(opByte);
switch(op) {
case Op::End:
return true;
case Op::Base_Tex: {
SrcMeta src{};
if(!readSrc(ip, src))
return false;
PipelineRemapResult r = patchTexId(src);
if(!r.Ok)
return false;
if(src.Kind == SrcKind::Sub) {
size_t subStart = src.Off;
size_t subEnd = subStart + src.Len;
if(!scan(subStart, subEnd))
return false;
}
} break;
case Op::Base_Fill: {
uint16_t tmp16 = 0;
uint32_t tmp32 = 0;
if(!read16(ip, tmp16)) return false;
if(!read16(ip, tmp16)) return false;
if(!read32(ip, tmp32)) return false;
} break;
case Op::Base_Anim: {
SrcMeta src{};
if(!readSrc(ip, src)) return false;
PipelineRemapResult r = patchTexId(src);
if(!r.Ok) return false;
uint16_t frameW = 0;
uint16_t frameH = 0;
uint16_t frameCount = 0;
uint16_t fpsQ = 0;
uint8_t flags = 0;
if(!read16(ip, frameW)) return false;
if(!read16(ip, frameH)) return false;
if(!read16(ip, frameCount)) return false;
if(!read16(ip, fpsQ)) return false;
if(!read8(ip, flags)) return false;
(void)frameW; (void)frameH; (void)frameCount; (void)fpsQ; (void)flags;
if(src.Kind == SrcKind::Sub) {
size_t subStart = src.Off;
size_t subEnd = subStart + src.Len;
if(!scan(subStart, subEnd)) return false;
}
} break;
case Op::Resize: {
uint16_t tmp16 = 0;
if(!read16(ip, tmp16)) return false;
if(!read16(ip, tmp16)) return false;
} break;
case Op::Transform:
case Op::Opacity:
case Op::Invert:
if(!read8(ip, opByte)) return false;
break;
case Op::NoAlpha:
case Op::Brighten:
break;
case Op::MakeAlpha:
if(ip + 2 >= size) return false;
ip += 3;
break;
case Op::Contrast:
if(ip + 1 >= size) return false;
ip += 2;
break;
case Op::Multiply:
case Op::Screen: {
uint32_t tmp32 = 0;
if(!read32(ip, tmp32)) return false;
} break;
case Op::Colorize: {
uint32_t tmp32 = 0;
if(!read32(ip, tmp32)) return false;
if(!read8(ip, opByte)) return false;
} break;
case Op::Anim: {
uint16_t frameW = 0;
uint16_t frameH = 0;
uint16_t frameCount = 0;
uint16_t fpsQ = 0;
uint8_t flags = 0;
if(!read16(ip, frameW)) return false;
if(!read16(ip, frameH)) return false;
if(!read16(ip, frameCount)) return false;
if(!read16(ip, fpsQ)) return false;
if(!read8(ip, flags)) return false;
(void)frameW; (void)frameH; (void)frameCount; (void)fpsQ; (void)flags;
} break;
case Op::Overlay:
case Op::Mask: {
SrcMeta src{};
if(!readSrc(ip, src)) return false;
PipelineRemapResult r = patchTexId(src);
if(!r.Ok) return false;
if(src.Kind == SrcKind::Sub) {
size_t subStart = src.Off;
size_t subEnd = subStart + src.Len;
if(!scan(subStart, subEnd)) return false;
}
} break;
case Op::LowPart: {
if(!read8(ip, opByte)) return false;
SrcMeta src{};
if(!readSrc(ip, src)) return false;
PipelineRemapResult r = patchTexId(src);
if(!r.Ok) return false;
if(src.Kind == SrcKind::Sub) {
size_t subStart = src.Off;
size_t subEnd = subStart + src.Len;
if(!scan(subStart, subEnd)) return false;
}
} break;
case Op::Combine: {
uint16_t w = 0, h = 0, n = 0;
if(!read16(ip, w)) return false;
if(!read16(ip, h)) return false;
if(!read16(ip, n)) return false;
for(uint16_t i = 0; i < n; ++i) {
uint16_t tmp16 = 0;
if(!read16(ip, tmp16)) return false;
if(!read16(ip, tmp16)) return false;
SrcMeta src{};
if(!readSrc(ip, src)) return false;
PipelineRemapResult r = patchTexId(src);
if(!r.Ok) return false;
if(src.Kind == SrcKind::Sub) {
size_t subStart = src.Off;
size_t subEnd = subStart + src.Len;
if(!scan(subStart, subEnd)) return false;
}
}
(void)w; (void)h;
} break;
default:
return false;
}
}
return true;
};
if(!scan(0, size))
return {false, "Invalid texture pipeline bytecode"};
return {};
}
std::vector<uint32_t> collectTexturePipelineIds(const std::vector<uint8_t>& code) {
std::vector<uint32_t> out;
std::unordered_set<uint32_t> seen;
auto addId = [&](uint32_t id) {
if(seen.insert(id).second)
out.push_back(id);
};
std::vector<uint8_t> copy = code;
auto result = remapTexturePipelineIds(copy, [&](uint32_t id) {
addId(id);
return id;
});
if(!result.Ok)
return {};
return out;
}
std::optional<ParsedModelHeader> parseModelHeaderBytes(const std::vector<uint8_t>& header) {
if(header.empty())
return std::nullopt;
ParsedModelHeader result;
try {
TOS::ByteBuffer buffer(header.size(), header.data());
auto reader = buffer.reader();
uint16_t modelCount = reader.readUInt16();
result.ModelDeps.reserve(modelCount);
for(uint16_t i = 0; i < modelCount; ++i)
result.ModelDeps.push_back(reader.readUInt32());
uint16_t texCount = reader.readUInt16();
result.TexturePipelines.reserve(texCount);
for(uint16_t i = 0; i < texCount; ++i) {
uint32_t size32 = reader.readUInt32();
TOS::ByteBuffer pipe;
reader.readBuffer(pipe);
if(pipe.size() != size32)
return std::nullopt;
result.TexturePipelines.emplace_back(pipe.begin(), pipe.end());
}
std::unordered_set<ResourceId> seen;
for(const auto& pipe : result.TexturePipelines) {
for(uint32_t id : collectTexturePipelineIds(pipe)) {
if(seen.insert(id).second)
result.TextureDeps.push_back(id);
}
}
} catch(const std::exception&) {
return std::nullopt;
}
return result;
}
} // namespace
std::optional<ParsedHeader> parseHeader(EnumAssets type, const std::vector<uint8_t>& header) {
if(header.empty())
return std::nullopt;
ParsedHeader result;
result.Type = type;
if(type == EnumAssets::Nodestate) {
auto deps = parseNodestateHeaderBytes(header);
if(!deps)
return std::nullopt;
result.ModelDeps = std::move(*deps);
return result;
}
if(type == EnumAssets::Model) {
auto parsed = parseModelHeaderBytes(header);
if(!parsed)
return std::nullopt;
result.ModelDeps = std::move(parsed->ModelDeps);
result.TexturePipelines = std::move(parsed->TexturePipelines);
result.TextureDeps = std::move(parsed->TextureDeps);
return result;
}
return std::nullopt;
}
std::vector<uint8_t> rebindHeader(EnumAssets type, const std::vector<uint8_t>& header,
const MapIdFn& mapModelId, const MapIdFn& mapTextureId, const WarnFn& warn)
{
if(header.empty())
return {};
if(type == EnumAssets::Nodestate) {
if(header.size() % sizeof(ResourceId) != 0)
return header;
std::vector<uint8_t> out(header.size());
const size_t count = header.size() / sizeof(ResourceId);
for(size_t i = 0; i < count; ++i) {
ResourceId raw = 0;
std::memcpy(&raw, header.data() + i * sizeof(ResourceId), sizeof(ResourceId));
ResourceId mapped = mapModelId(raw);
std::memcpy(out.data() + i * sizeof(ResourceId), &mapped, sizeof(ResourceId));
}
return out;
}
if(type == EnumAssets::Model) {
try {
TOS::ByteBuffer buffer(header.size(), header.data());
auto reader = buffer.reader();
uint16_t modelCount = reader.readUInt16();
std::vector<ResourceId> models;
models.reserve(modelCount);
for(uint16_t i = 0; i < modelCount; ++i) {
ResourceId id = reader.readUInt32();
models.push_back(mapModelId(id));
}
uint16_t texCount = reader.readUInt16();
std::vector<std::vector<uint8_t>> pipelines;
pipelines.reserve(texCount);
for(uint16_t i = 0; i < texCount; ++i) {
uint32_t size32 = reader.readUInt32();
TOS::ByteBuffer pipe;
reader.readBuffer(pipe);
if(pipe.size() != size32) {
warn("Pipeline size mismatch");
}
std::vector<uint8_t> code(pipe.begin(), pipe.end());
auto result = remapTexturePipelineIds(code, [&](uint32_t id) {
return mapTextureId(static_cast<ResourceId>(id));
});
if(!result.Ok) {
warn(result.Error);
}
pipelines.emplace_back(std::move(code));
}
TOS::ByteBuffer::Writer wr;
wr << uint16_t(models.size());
for(ResourceId id : models)
wr << id;
wr << uint16_t(pipelines.size());
for(const auto& pipe : pipelines) {
wr << uint32_t(pipe.size());
TOS::ByteBuffer pipeBuff(pipe.begin(), pipe.end());
wr << pipeBuff;
}
TOS::ByteBuffer out = wr.complite();
return std::vector<uint8_t>(out.begin(), out.end());
} catch(const std::exception&) {
warn("Failed to rebind model header");
return header;
}
}
return header;
}
} // namespace LV::Client::AssetsHeaderCodec

View File

@@ -1,27 +0,0 @@
#pragma once
#include <cstdint>
#include <functional>
#include <optional>
#include <string>
#include <vector>
#include "Common/Abstract.hpp"
namespace LV::Client::AssetsHeaderCodec {
struct ParsedHeader {
EnumAssets Type{};
std::vector<ResourceId> ModelDeps;
std::vector<ResourceId> TextureDeps;
std::vector<std::vector<uint8_t>> TexturePipelines;
};
using MapIdFn = std::function<ResourceId(ResourceId)>;
using WarnFn = std::function<void(const std::string&)>;
std::optional<ParsedHeader> parseHeader(EnumAssets type, const std::vector<uint8_t>& header);
std::vector<uint8_t> rebindHeader(EnumAssets type, const std::vector<uint8_t>& header,
const MapIdFn& mapModelId, const MapIdFn& mapTextureId, const WarnFn& warn);
} // namespace LV::Client::AssetsHeaderCodec

View File

@@ -1,767 +0,0 @@
#include "AssetsManager.hpp"
#include <algorithm>
#include <cassert>
#include <fstream>
#include "Common/TexturePipelineProgram.hpp"
namespace LV::Client {
namespace {
static const char* assetTypeName(EnumAssets type) {
switch(type) {
case EnumAssets::Nodestate: return "nodestate";
case EnumAssets::Model: return "model";
case EnumAssets::Texture: return "texture";
case EnumAssets::Particle: return "particle";
case EnumAssets::Animation: return "animation";
case EnumAssets::Sound: return "sound";
case EnumAssets::Font: return "font";
default: return "unknown";
}
}
static const char* enumAssetsToDirectory(LV::EnumAssets value) {
switch(value) {
case LV::EnumAssets::Nodestate: return "nodestate";
case LV::EnumAssets::Particle: return "particle";
case LV::EnumAssets::Animation: return "animation";
case LV::EnumAssets::Model: return "model";
case LV::EnumAssets::Texture: return "texture";
case LV::EnumAssets::Sound: return "sound";
case LV::EnumAssets::Font: return "font";
default:
break;
}
assert(!"Unknown asset type");
return "";
}
static std::u8string readFileBytes(const fs::path& path) {
std::ifstream file(path, std::ios::binary);
if(!file)
throw std::runtime_error("Не удалось открыть файл: " + path.string());
file.seekg(0, std::ios::end);
std::streamoff size = file.tellg();
if(size < 0)
size = 0;
file.seekg(0, std::ios::beg);
std::u8string data;
data.resize(static_cast<size_t>(size));
if(size > 0) {
file.read(reinterpret_cast<char*>(data.data()), size);
if(!file)
throw std::runtime_error("Не удалось прочитать файл: " + path.string());
}
return data;
}
static std::u8string readOptionalMeta(const fs::path& path) {
fs::path metaPath = path;
metaPath += ".meta";
if(!fs::exists(metaPath) || !fs::is_regular_file(metaPath))
return {};
return readFileBytes(metaPath);
}
} // namespace
AssetsManager::AssetsManager(asio::io_context& ioc, const fs::path& cachePath,
size_t maxCacheDirectorySize, size_t maxLifeTime)
: Cache(AssetsCacheManager::Create(ioc, cachePath, maxCacheDirectorySize, maxLifeTime))
{
for(size_t i = 0; i < static_cast<size_t>(AssetType::MAX_ENUM); ++i)
Types[i].NextLocalId = 1;
initSources();
}
void AssetsManager::initSources() {
using SourceResult = AssetsManager::SourceResult;
using SourceStatus = AssetsManager::SourceStatus;
using SourceReady = AssetsManager::SourceReady;
using ResourceKey = AssetsManager::ResourceKey;
using PackResource = AssetsManager::PackResource;
class PackSource final : public IResourceSource {
public:
explicit PackSource(AssetsManager* manager) : Manager(manager) {}
SourceResult tryGet(const ResourceKey& key) override {
std::optional<PackResource> pack = Manager->findPackResource(key.Type, key.Domain, key.Key);
if(pack && pack->Hash == key.Hash)
return {SourceStatus::Hit, pack->Res, 0};
return {SourceStatus::Miss, std::nullopt, 0};
}
void collectReady(std::vector<SourceReady>&) override {}
bool isAsync() const override {
return false;
}
void startPending(std::vector<Hash_t>) override {}
private:
AssetsManager* Manager = nullptr;
};
class MemorySource final : public IResourceSource {
public:
explicit MemorySource(AssetsManager* manager) : Manager(manager) {}
SourceResult tryGet(const ResourceKey& key) override {
auto iter = Manager->MemoryResourcesByHash.find(key.Hash);
if(iter == Manager->MemoryResourcesByHash.end())
return {SourceStatus::Miss, std::nullopt, 0};
return {SourceStatus::Hit, iter->second, 0};
}
void collectReady(std::vector<SourceReady>&) override {}
bool isAsync() const override {
return false;
}
void startPending(std::vector<Hash_t>) override {}
private:
AssetsManager* Manager = nullptr;
};
class CacheSource final : public IResourceSource {
public:
CacheSource(AssetsManager* manager, size_t sourceIndex)
: Manager(manager), SourceIndex(sourceIndex) {}
SourceResult tryGet(const ResourceKey&) override {
return {SourceStatus::Pending, std::nullopt, SourceIndex};
}
void collectReady(std::vector<SourceReady>& out) override {
std::vector<std::pair<Hash_t, std::optional<Resource>>> cached = Manager->Cache->pullReads();
out.reserve(out.size() + cached.size());
for(auto& [hash, res] : cached)
out.push_back(SourceReady{hash, res, SourceIndex});
}
bool isAsync() const override {
return true;
}
void startPending(std::vector<Hash_t> hashes) override {
if(!hashes.empty())
Manager->Cache->pushReads(std::move(hashes));
}
private:
AssetsManager* Manager = nullptr;
size_t SourceIndex = 0;
};
Sources.clear();
PackSourceIndex = Sources.size();
Sources.push_back(SourceEntry{std::make_unique<PackSource>(this), 0});
MemorySourceIndex = Sources.size();
Sources.push_back(SourceEntry{std::make_unique<MemorySource>(this), 0});
CacheSourceIndex = Sources.size();
Sources.push_back(SourceEntry{std::make_unique<CacheSource>(this, CacheSourceIndex), 0});
}
void AssetsManager::collectReadyFromSources() {
std::vector<SourceReady> ready;
for(auto& entry : Sources)
entry.Source->collectReady(ready);
for(SourceReady& item : ready) {
auto iter = PendingReadsByHash.find(item.Hash);
if(iter == PendingReadsByHash.end())
continue;
if(item.Value)
registerSourceHit(item.Hash, item.SourceIndex);
for(ResourceKey& key : iter->second) {
if(item.SourceIndex == CacheSourceIndex) {
if(item.Value) {
LOG.debug() << "Cache hit type=" << assetTypeName(key.Type)
<< " id=" << key.Id
<< " key=" << key.Domain << ':' << key.Key
<< " hash=" << int(item.Hash[0]) << '.'
<< int(item.Hash[1]) << '.'
<< int(item.Hash[2]) << '.'
<< int(item.Hash[3])
<< " size=" << item.Value->size();
} else {
LOG.debug() << "Cache miss type=" << assetTypeName(key.Type)
<< " id=" << key.Id
<< " key=" << key.Domain << ':' << key.Key
<< " hash=" << int(item.Hash[0]) << '.'
<< int(item.Hash[1]) << '.'
<< int(item.Hash[2]) << '.'
<< int(item.Hash[3]);
}
}
ReadyReads.emplace_back(std::move(key), item.Value);
}
PendingReadsByHash.erase(iter);
}
}
AssetsManager::SourceResult AssetsManager::querySources(const ResourceKey& key) {
auto cacheIter = SourceCacheByHash.find(key.Hash);
if(cacheIter != SourceCacheByHash.end()) {
const size_t cachedIndex = cacheIter->second.SourceIndex;
if(cachedIndex < Sources.size()
&& cacheIter->second.Generation == Sources[cachedIndex].Generation)
{
SourceResult cached = Sources[cachedIndex].Source->tryGet(key);
cached.SourceIndex = cachedIndex;
if(cached.Status != SourceStatus::Miss)
return cached;
}
SourceCacheByHash.erase(cacheIter);
}
SourceResult pending;
pending.Status = SourceStatus::Miss;
for(size_t i = 0; i < Sources.size(); ++i) {
SourceResult res = Sources[i].Source->tryGet(key);
res.SourceIndex = i;
if(res.Status == SourceStatus::Hit) {
registerSourceHit(key.Hash, i);
return res;
}
if(res.Status == SourceStatus::Pending && pending.Status == SourceStatus::Miss)
pending = res;
}
return pending;
}
void AssetsManager::registerSourceHit(const Hash_t& hash, size_t sourceIndex) {
if(sourceIndex >= Sources.size())
return;
if(Sources[sourceIndex].Source->isAsync())
return;
SourceCacheByHash[hash] = SourceCacheEntry{
.SourceIndex = sourceIndex,
.Generation = Sources[sourceIndex].Generation
};
}
void AssetsManager::invalidateSourceCache(size_t sourceIndex) {
if(sourceIndex >= Sources.size())
return;
Sources[sourceIndex].Generation++;
for(auto iter = SourceCacheByHash.begin(); iter != SourceCacheByHash.end(); ) {
if(iter->second.SourceIndex == sourceIndex)
iter = SourceCacheByHash.erase(iter);
else
++iter;
}
}
void AssetsManager::invalidateAllSourceCache() {
for(auto& entry : Sources)
entry.Generation++;
SourceCacheByHash.clear();
}
void AssetsManager::tickSources() {
collectReadyFromSources();
}
AssetsManager::PackReloadResult AssetsManager::reloadPacks(const PackRegister& reg) {
PackReloadResult result;
std::array<PackTable, static_cast<size_t>(AssetType::MAX_ENUM)> oldPacks;
for(size_t type = 0; type < static_cast<size_t>(AssetType::MAX_ENUM); ++type) {
oldPacks[type] = Types[type].PackResources;
Types[type].PackResources.clear();
}
for(const fs::path& instance : reg.Packs) {
try {
if(fs::is_regular_file(instance)) {
LOG.warn() << "Архивы ресурспаков пока не поддерживаются: " << instance.string();
continue;
}
if(!fs::is_directory(instance)) {
LOG.warn() << "Неизвестный тип ресурспака: " << instance.string();
continue;
}
fs::path assetsRoot = instance;
fs::path assetsCandidate = instance / "assets";
if(fs::exists(assetsCandidate) && fs::is_directory(assetsCandidate))
assetsRoot = assetsCandidate;
for(auto begin = fs::directory_iterator(assetsRoot), end = fs::directory_iterator(); begin != end; ++begin) {
if(!begin->is_directory())
continue;
fs::path domainPath = begin->path();
std::string domain = domainPath.filename().string();
for(size_t type = 0; type < static_cast<size_t>(AssetType::MAX_ENUM); ++type) {
AssetType assetType = static_cast<AssetType>(type);
fs::path assetPath = domainPath / enumAssetsToDirectory(assetType);
if(!fs::exists(assetPath) || !fs::is_directory(assetPath))
continue;
auto& typeTable = Types[type].PackResources[domain];
for(auto fbegin = fs::recursive_directory_iterator(assetPath),
fend = fs::recursive_directory_iterator();
fbegin != fend; ++fbegin) {
if(fbegin->is_directory())
continue;
fs::path file = fbegin->path();
if(assetType == AssetType::Texture && file.extension() == ".meta")
continue;
std::string key = fs::relative(file, assetPath).generic_string();
if(typeTable.contains(key))
continue;
PackResource entry;
entry.Type = assetType;
entry.Domain = domain;
entry.Key = key;
entry.LocalId = getOrCreateLocalId(assetType, entry.Domain, entry.Key);
try {
if(assetType == AssetType::Nodestate) {
std::u8string data = readFileBytes(file);
std::string_view view(reinterpret_cast<const char*>(data.data()), data.size());
js::object obj = js::parse(view).as_object();
HeadlessNodeState hns;
auto modelResolver = [&](std::string_view model) -> AssetsModel {
auto [mDomain, mKey] = parseDomainKey(model, entry.Domain);
return getOrCreateLocalId(AssetType::Model, mDomain, mKey);
};
entry.Header = hns.parse(obj, modelResolver);
std::u8string compiled = hns.dump();
entry.Res = Resource(std::move(compiled));
entry.Hash = entry.Res.hash();
} else if(assetType == AssetType::Model) {
const std::string ext = file.extension().string();
if(ext == ".json") {
std::u8string data = readFileBytes(file);
std::string_view view(reinterpret_cast<const char*>(data.data()), data.size());
js::object obj = js::parse(view).as_object();
HeadlessModel hm;
auto modelResolver = [&](std::string_view model) -> AssetsModel {
auto [mDomain, mKey] = parseDomainKey(model, entry.Domain);
return getOrCreateLocalId(AssetType::Model, mDomain, mKey);
};
auto normalizeTexturePipelineSrc = [](std::string_view src) -> std::string {
std::string out(src);
auto isSpace = [](unsigned char c) { return c == ' ' || c == '\t' || c == '\n' || c == '\r'; };
size_t start = 0;
while(start < out.size() && isSpace(static_cast<unsigned char>(out[start])))
++start;
if(out.compare(start, 3, "tex") != 0) {
std::string pref = "tex ";
pref += out.substr(start);
return pref;
}
return out;
};
auto textureResolver = [&](std::string_view textureSrc) -> std::vector<uint8_t> {
TexturePipelineProgram tpp;
if(!tpp.compile(normalizeTexturePipelineSrc(textureSrc)))
return {};
auto textureIdResolver = [&](std::string_view name) -> std::optional<uint32_t> {
auto [tDomain, tKey] = parseDomainKey(name, entry.Domain);
return getOrCreateLocalId(AssetType::Texture, tDomain, tKey);
};
if(!tpp.link(textureIdResolver))
return {};
return tpp.toBytes();
};
entry.Header = hm.parse(obj, modelResolver, textureResolver);
std::u8string compiled = hm.dump();
entry.Res = Resource(std::move(compiled));
entry.Hash = entry.Res.hash();
} else {
LOG.warn() << "Не поддерживаемый формат модели: " << file.string();
continue;
}
} else if(assetType == AssetType::Texture) {
std::u8string data = readFileBytes(file);
entry.Res = Resource(std::move(data));
entry.Hash = entry.Res.hash();
entry.Header = readOptionalMeta(file);
} else {
std::u8string data = readFileBytes(file);
entry.Res = Resource(std::move(data));
entry.Hash = entry.Res.hash();
}
} catch(const std::exception& exc) {
LOG.warn() << "Ошибка загрузки ресурса " << file.string() << ": " << exc.what();
continue;
}
typeTable.emplace(entry.Key, entry);
}
}
}
} catch(const std::exception& exc) {
LOG.warn() << "Ошибка загрузки ресурспака " << instance.string() << ": " << exc.what();
}
}
for(size_t type = 0; type < static_cast<size_t>(AssetType::MAX_ENUM); ++type) {
for(const auto& [domain, keyTable] : Types[type].PackResources) {
for(const auto& [key, res] : keyTable) {
bool changed = true;
auto oldDomain = oldPacks[type].find(domain);
if(oldDomain != oldPacks[type].end()) {
auto oldKey = oldDomain->second.find(key);
if(oldKey != oldDomain->second.end()) {
changed = oldKey->second.Hash != res.Hash;
}
}
if(changed)
result.ChangeOrAdd[type].push_back(res.LocalId);
}
}
for(const auto& [domain, keyTable] : oldPacks[type]) {
for(const auto& [key, res] : keyTable) {
auto newDomain = Types[type].PackResources.find(domain);
bool lost = true;
if(newDomain != Types[type].PackResources.end()) {
if(newDomain->second.contains(key))
lost = false;
}
if(lost)
result.Lost[type].push_back(res.LocalId);
}
}
}
invalidateAllSourceCache();
return result;
}
AssetsManager::BindResult AssetsManager::bindServerResource(AssetType type, AssetId serverId,
std::string domain, std::string key, const Hash_t& hash, std::vector<uint8_t> header)
{
BindResult result;
AssetId localFromDK = getOrCreateLocalId(type, domain, key);
auto& map = Types[static_cast<size_t>(type)].ServerToLocal;
AssetId localFromServer = 0;
if(serverId < map.size())
localFromServer = map[serverId];
if(localFromServer != 0)
unionLocalIds(type, localFromServer, localFromDK, &result.ReboundFrom);
AssetId localId = resolveLocalIdMutable(type, localFromDK);
if(serverId >= map.size())
map.resize(serverId + 1, 0);
map[serverId] = localId;
auto& infoList = Types[static_cast<size_t>(type)].BindInfos;
if(localId >= infoList.size())
infoList.resize(localId + 1);
bool hadBinding = infoList[localId].has_value();
bool changed = !hadBinding || infoList[localId]->Hash != hash || infoList[localId]->Header != header;
infoList[localId] = BindInfo{
.Type = type,
.LocalId = localId,
.Domain = std::move(domain),
.Key = std::move(key),
.Hash = hash,
.Header = std::move(header)
};
result.LocalId = localId;
result.Changed = changed;
result.NewBinding = !hadBinding;
return result;
}
std::optional<AssetsManager::AssetId> AssetsManager::unbindServerResource(AssetType type, AssetId serverId) {
auto& map = Types[static_cast<size_t>(type)].ServerToLocal;
if(serverId >= map.size())
return std::nullopt;
AssetId localId = map[serverId];
map[serverId] = 0;
if(localId == 0)
return std::nullopt;
return resolveLocalIdMutable(type, localId);
}
void AssetsManager::clearServerBindings() {
for(auto& typeData : Types) {
typeData.ServerToLocal.clear();
typeData.BindInfos.clear();
}
}
const AssetsManager::BindInfo* AssetsManager::getBind(AssetType type, AssetId localId) const {
localId = resolveLocalId(type, localId);
const auto& table = Types[static_cast<size_t>(type)].BindInfos;
if(localId >= table.size())
return nullptr;
if(!table[localId])
return nullptr;
return &*table[localId];
}
std::vector<uint8_t> AssetsManager::rebindHeader(AssetType type, const std::vector<uint8_t>& header, bool serverIds) {
auto mapModelId = [&](AssetId id) -> AssetId {
if(serverIds) {
auto localId = getLocalIdFromServer(AssetType::Model, id);
if(!localId) {
assert(!"Missing server bind for model id");
MAKE_ERROR("Нет бинда сервера для модели id=" << id);
}
return *localId;
}
return resolveLocalIdMutable(AssetType::Model, id);
};
auto mapTextureId = [&](AssetId id) -> AssetId {
if(serverIds) {
auto localId = getLocalIdFromServer(AssetType::Texture, id);
if(!localId) {
assert(!"Missing server bind for texture id");
MAKE_ERROR("Нет бинда сервера для текстуры id=" << id);
}
return *localId;
}
return resolveLocalIdMutable(AssetType::Texture, id);
};
auto warn = [&](const std::string& msg) {
LOG.warn() << msg;
};
return AssetsHeaderCodec::rebindHeader(type, header, mapModelId, mapTextureId, warn);
}
std::optional<AssetsManager::ParsedHeader> AssetsManager::parseHeader(AssetType type, const std::vector<uint8_t>& header) {
return AssetsHeaderCodec::parseHeader(type, header);
}
void AssetsManager::pushResources(std::vector<Resource> resources) {
for(const Resource& res : resources) {
Hash_t hash = res.hash();
MemoryResourcesByHash[hash] = res;
SourceCacheByHash.erase(hash);
registerSourceHit(hash, MemorySourceIndex);
auto iter = PendingReadsByHash.find(hash);
if(iter != PendingReadsByHash.end()) {
for(ResourceKey& key : iter->second)
ReadyReads.emplace_back(std::move(key), res);
PendingReadsByHash.erase(iter);
}
}
Cache->pushResources(std::move(resources));
}
void AssetsManager::pushReads(std::vector<ResourceKey> reads) {
std::unordered_map<size_t, std::vector<Hash_t>> pendingBySource;
for(ResourceKey& key : reads) {
SourceResult res = querySources(key);
if(res.Status == SourceStatus::Hit) {
if(res.SourceIndex == PackSourceIndex && res.Value) {
LOG.debug() << "Pack hit type=" << assetTypeName(key.Type)
<< " id=" << key.Id
<< " key=" << key.Domain << ':' << key.Key
<< " hash=" << int(key.Hash[0]) << '.'
<< int(key.Hash[1]) << '.'
<< int(key.Hash[2]) << '.'
<< int(key.Hash[3])
<< " size=" << res.Value->size();
}
ReadyReads.emplace_back(std::move(key), res.Value);
continue;
}
if(res.Status == SourceStatus::Pending) {
auto& list = PendingReadsByHash[key.Hash];
bool isFirst = list.empty();
list.push_back(std::move(key));
if(isFirst)
pendingBySource[res.SourceIndex].push_back(list.front().Hash);
continue;
}
ReadyReads.emplace_back(std::move(key), std::nullopt);
}
for(auto& [sourceIndex, hashes] : pendingBySource) {
if(sourceIndex < Sources.size())
Sources[sourceIndex].Source->startPending(std::move(hashes));
}
}
std::vector<std::pair<AssetsManager::ResourceKey, std::optional<Resource>>> AssetsManager::pullReads() {
tickSources();
std::vector<std::pair<ResourceKey, std::optional<Resource>>> out;
out.reserve(ReadyReads.size());
for(auto& entry : ReadyReads)
out.emplace_back(std::move(entry));
ReadyReads.clear();
return out;
}
AssetsManager::AssetId AssetsManager::getOrCreateLocalId(AssetType type, std::string_view domain, std::string_view key) {
auto& table = Types[static_cast<size_t>(type)].DKToLocal;
auto iterDomain = table.find(domain);
if(iterDomain == table.end()) {
iterDomain = table.emplace(
std::string(domain),
std::unordered_map<std::string, AssetId, detail::TSVHash, detail::TSVEq>{}
).first;
}
auto& keyTable = iterDomain->second;
auto iterKey = keyTable.find(key);
if(iterKey != keyTable.end()) {
iterKey->second = resolveLocalIdMutable(type, iterKey->second);
return iterKey->second;
}
AssetId id = allocateLocalId(type);
keyTable.emplace(std::string(key), id);
auto& dk = Types[static_cast<size_t>(type)].LocalToDK;
if(id >= dk.size())
dk.resize(id + 1);
dk[id] = DomainKey{std::string(domain), std::string(key), true};
return id;
}
std::optional<AssetsManager::AssetId> AssetsManager::getLocalIdFromServer(AssetType type, AssetId serverId) const {
const auto& map = Types[static_cast<size_t>(type)].ServerToLocal;
if(serverId >= map.size())
return std::nullopt;
AssetId local = map[serverId];
if(local == 0)
return std::nullopt;
return resolveLocalId(type, local);
}
AssetsManager::AssetId AssetsManager::resolveLocalId(AssetType type, AssetId localId) const {
if(localId == 0)
return 0;
const auto& parents = Types[static_cast<size_t>(type)].LocalParent;
if(localId >= parents.size())
return localId;
AssetId cur = localId;
while(cur < parents.size() && parents[cur] != cur && parents[cur] != 0)
cur = parents[cur];
return cur;
}
AssetsManager::AssetId AssetsManager::allocateLocalId(AssetType type) {
auto& next = Types[static_cast<size_t>(type)].NextLocalId;
AssetId id = next++;
auto& parents = Types[static_cast<size_t>(type)].LocalParent;
if(id >= parents.size())
parents.resize(id + 1, 0);
parents[id] = id;
auto& dk = Types[static_cast<size_t>(type)].LocalToDK;
if(id >= dk.size())
dk.resize(id + 1);
return id;
}
AssetsManager::AssetId AssetsManager::resolveLocalIdMutable(AssetType type, AssetId localId) {
if(localId == 0)
return 0;
auto& parents = Types[static_cast<size_t>(type)].LocalParent;
if(localId >= parents.size())
return localId;
AssetId root = localId;
while(root < parents.size() && parents[root] != root && parents[root] != 0)
root = parents[root];
if(root == localId)
return root;
AssetId cur = localId;
while(cur < parents.size() && parents[cur] != root && parents[cur] != 0) {
AssetId next = parents[cur];
parents[cur] = root;
cur = next;
}
return root;
}
void AssetsManager::unionLocalIds(AssetType type, AssetId fromId, AssetId toId, std::optional<AssetId>* reboundFrom) {
AssetId fromRoot = resolveLocalIdMutable(type, fromId);
AssetId toRoot = resolveLocalIdMutable(type, toId);
if(fromRoot == 0 || toRoot == 0 || fromRoot == toRoot)
return;
auto& parents = Types[static_cast<size_t>(type)].LocalParent;
if(fromRoot >= parents.size() || toRoot >= parents.size())
return;
parents[fromRoot] = toRoot;
if(reboundFrom)
*reboundFrom = fromRoot;
auto& dk = Types[static_cast<size_t>(type)].LocalToDK;
if(fromRoot < dk.size()) {
const DomainKey& fromDK = dk[fromRoot];
if(fromDK.Known) {
if(toRoot >= dk.size())
dk.resize(toRoot + 1);
DomainKey& toDK = dk[toRoot];
if(!toDK.Known) {
toDK = fromDK;
Types[static_cast<size_t>(type)].DKToLocal[toDK.Domain][toDK.Key] = toRoot;
} else if(toDK.Domain != fromDK.Domain || toDK.Key != fromDK.Key) {
LOG.warn() << "Конфликт домен/ключ при ребинде: "
<< fromDK.Domain << ':' << fromDK.Key << " vs "
<< toDK.Domain << ':' << toDK.Key;
}
}
}
auto& binds = Types[static_cast<size_t>(type)].BindInfos;
if(fromRoot < binds.size()) {
if(toRoot >= binds.size())
binds.resize(toRoot + 1);
if(!binds[toRoot] && binds[fromRoot])
binds[toRoot] = std::move(binds[fromRoot]);
}
}
std::optional<AssetsManager::PackResource> AssetsManager::findPackResource(AssetType type,
std::string_view domain, std::string_view key) const
{
const auto& typeTable = Types[static_cast<size_t>(type)].PackResources;
auto iterDomain = typeTable.find(domain);
if(iterDomain == typeTable.end())
return std::nullopt;
auto iterKey = iterDomain->second.find(key);
if(iterKey == iterDomain->second.end())
return std::nullopt;
return iterKey->second;
}
} // namespace LV::Client

View File

@@ -1,293 +0,0 @@
#pragma once
#include <array>
#include <cstddef>
#include <cstdint>
#include <filesystem>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <unordered_map>
#include <utility>
#include <vector>
#include "Client/AssetsCacheManager.hpp"
#include "Client/AssetsHeaderCodec.hpp"
#include "Common/Abstract.hpp"
#include "TOSLib.hpp"
namespace LV::Client {
namespace fs = std::filesystem;
class AssetsManager {
public:
using Ptr = std::shared_ptr<AssetsManager>;
using AssetType = EnumAssets;
using AssetId = ResourceId;
// Ключ запроса ресурса (идентификация + хеш для поиска источника).
struct ResourceKey {
// Хеш ресурса, используемый для поиска в источниках и кэше.
Hash_t Hash{};
// Тип ресурса (модель, текстура и т.д.).
AssetType Type{};
// Домен ресурса.
std::string Domain;
// Ключ ресурса внутри домена.
std::string Key;
// Идентификатор ресурса на стороне клиента/локальный.
AssetId Id = 0;
};
// Информация о биндинге серверного ресурса на локальный id.
struct BindInfo {
// Тип ресурса.
AssetType Type{};
// Локальный идентификатор.
AssetId LocalId = 0;
// Домен ресурса.
std::string Domain;
// Ключ ресурса.
std::string Key;
// Хеш ресурса.
Hash_t Hash{};
// Бинарный заголовок с зависимостями.
std::vector<uint8_t> Header;
};
// Результат биндинга ресурса сервера.
struct BindResult {
// Итоговый локальный идентификатор.
AssetId LocalId = 0;
// Признак изменения бинда (хеш/заголовок).
bool Changed = false;
// Признак новой привязки.
bool NewBinding = false;
// Идентификатор, от которого произошёл ребинд (если был).
std::optional<AssetId> ReboundFrom;
};
// Регистрация набора ресурспаков.
struct PackRegister {
// Пути до паков (директории/архивы).
std::vector<fs::path> Packs;
};
// Ресурс, собранный из пака.
struct PackResource {
// Тип ресурса.
AssetType Type{};
// Локальный идентификатор.
AssetId LocalId = 0;
// Домен ресурса.
std::string Domain;
// Ключ ресурса.
std::string Key;
// Тело ресурса.
Resource Res;
// Хеш ресурса.
Hash_t Hash{};
// Заголовок ресурса (например, зависимости).
std::u8string Header;
};
// Результат пересканирования паков.
struct PackReloadResult {
// Добавленные/изменённые ресурсы по типам.
std::array<std::vector<AssetId>, static_cast<size_t>(AssetType::MAX_ENUM)> ChangeOrAdd;
// Потерянные ресурсы по типам.
std::array<std::vector<AssetId>, static_cast<size_t>(AssetType::MAX_ENUM)> Lost;
};
using ParsedHeader = AssetsHeaderCodec::ParsedHeader;
// Фабрика с настройкой лимитов кэша.
static Ptr Create(asio::io_context& ioc, const fs::path& cachePath,
size_t maxCacheDirectorySize = 8 * 1024 * 1024 * 1024ULL,
size_t maxLifeTime = 7 * 24 * 60 * 60) {
return Ptr(new AssetsManager(ioc, cachePath, maxCacheDirectorySize, maxLifeTime));
}
// Пересканировать ресурспаки и вернуть изменившиеся/утраченные ресурсы.
PackReloadResult reloadPacks(const PackRegister& reg);
// Связать серверный ресурс с локальным id и записать метаданные.
BindResult bindServerResource(AssetType type, AssetId serverId, std::string domain, std::string key,
const Hash_t& hash, std::vector<uint8_t> header);
// Отвязать серверный id и вернуть актуальный локальный id (если был).
std::optional<AssetId> unbindServerResource(AssetType type, AssetId serverId);
// Сбросить все серверные бинды.
void clearServerBindings();
// Получить данные бинда по локальному id.
const BindInfo* getBind(AssetType type, AssetId localId) const;
// Перебиндить хедер, заменив id зависимостей.
std::vector<uint8_t> rebindHeader(AssetType type, const std::vector<uint8_t>& header, bool serverIds = true);
// Распарсить хедер ресурса.
static std::optional<ParsedHeader> parseHeader(AssetType type, const std::vector<uint8_t>& header);
// Протолкнуть новые ресурсы в память и кэш.
void pushResources(std::vector<Resource> resources);
// Поставить запросы чтения ресурсов.
void pushReads(std::vector<ResourceKey> reads);
// Получить готовые результаты чтения.
std::vector<std::pair<ResourceKey, std::optional<Resource>>> pullReads();
// Продвинуть асинхронные источники (кэш).
void tickSources();
// Получить или создать локальный id по домену/ключу.
AssetId getOrCreateLocalId(AssetType type, std::string_view domain, std::string_view key);
// Получить локальный id по серверному id (если есть).
std::optional<AssetId> getLocalIdFromServer(AssetType type, AssetId serverId) const;
private:
// Связка домен/ключ для локального id.
struct DomainKey {
// Домен ресурса.
std::string Domain;
// Ключ ресурса.
std::string Key;
// Признак валидности записи.
bool Known = false;
};
using IdTable = std::unordered_map<
std::string,
std::unordered_map<std::string, AssetId, detail::TSVHash, detail::TSVEq>,
detail::TSVHash,
detail::TSVEq>;
using PackTable = std::unordered_map<
std::string,
std::unordered_map<std::string, PackResource, detail::TSVHash, detail::TSVEq>,
detail::TSVHash,
detail::TSVEq>;
struct PerType {
// Таблица домен/ключ -> локальный id.
IdTable DKToLocal;
// Таблица локальный id -> домен/ключ.
std::vector<DomainKey> LocalToDK;
// Union-Find родительские ссылки для ребиндов.
std::vector<AssetId> LocalParent;
// Таблица серверный id -> локальный id.
std::vector<AssetId> ServerToLocal;
// Бинды с сервером по локальному id.
std::vector<std::optional<BindInfo>> BindInfos;
// Ресурсы, собранные из паков.
PackTable PackResources;
// Следующий локальный id.
AssetId NextLocalId = 1;
};
enum class SourceStatus {
Hit,
Miss,
Pending
};
struct SourceResult {
// Статус ответа источника.
SourceStatus Status = SourceStatus::Miss;
// Значение ресурса, если найден.
std::optional<Resource> Value;
// Индекс источника.
size_t SourceIndex = 0;
};
struct SourceReady {
// Хеш готового ресурса.
Hash_t Hash{};
// Значение ресурса, если найден.
std::optional<Resource> Value;
// Индекс источника.
size_t SourceIndex = 0;
};
class IResourceSource {
public:
virtual ~IResourceSource() = default;
// Попытка получить ресурс синхронно.
virtual SourceResult tryGet(const ResourceKey& key) = 0;
// Забрать готовые результаты асинхронных запросов.
virtual void collectReady(std::vector<SourceReady>& out) = 0;
// Признак асинхронности источника.
virtual bool isAsync() const = 0;
// Запустить асинхронные запросы по хешам.
virtual void startPending(std::vector<Hash_t> hashes) = 0;
};
struct SourceEntry {
// Экземпляр источника.
std::unique_ptr<IResourceSource> Source;
// Поколение для инвалидирования кэша.
size_t Generation = 0;
};
struct SourceCacheEntry {
// Индекс источника, где был найден хеш.
size_t SourceIndex = 0;
// Поколение источника на момент кэширования.
size_t Generation = 0;
};
// Конструктор с зависимостью от io_context и кэш-пути.
AssetsManager(asio::io_context& ioc, const fs::path& cachePath,
size_t maxCacheDirectorySize, size_t maxLifeTime);
// Инициализация списка источников.
void initSources();
// Забрать готовые результаты из источников.
void collectReadyFromSources();
// Запросить ресурс в источниках, с учётом кэша.
SourceResult querySources(const ResourceKey& key);
// Запомнить успешный источник для хеша.
void registerSourceHit(const Hash_t& hash, size_t sourceIndex);
// Инвалидировать кэш по конкретному источнику.
void invalidateSourceCache(size_t sourceIndex);
// Инвалидировать весь кэш источников.
void invalidateAllSourceCache();
// Выделить новый локальный id.
AssetId allocateLocalId(AssetType type);
// Получить корневой локальный id с компрессией пути.
AssetId resolveLocalIdMutable(AssetType type, AssetId localId);
// Получить корневой локальный id без мутаций.
AssetId resolveLocalId(AssetType type, AssetId localId) const;
// Объединить два локальных id в один.
void unionLocalIds(AssetType type, AssetId fromId, AssetId toId, std::optional<AssetId>* reboundFrom);
// Найти ресурс в паке по домену/ключу.
std::optional<PackResource> findPackResource(AssetType type, std::string_view domain, std::string_view key) const;
// Логгер подсистемы.
Logger LOG = "Client>AssetsManager";
// Менеджер файлового кэша.
AssetsCacheManager::Ptr Cache;
// Таблицы данных по каждому типу ресурсов.
std::array<PerType, static_cast<size_t>(AssetType::MAX_ENUM)> Types;
// Список источников ресурсов.
std::vector<SourceEntry> Sources;
// Кэш попаданий по хешу.
std::unordered_map<Hash_t, SourceCacheEntry> SourceCacheByHash;
// Индекс источника паков.
size_t PackSourceIndex = 0;
// Индекс памяти (RAM) как источника.
size_t MemorySourceIndex = 0;
// Индекс файлового кэша.
size_t CacheSourceIndex = 0;
// Ресурсы в памяти по хешу.
std::unordered_map<Hash_t, Resource> MemoryResourcesByHash;
// Ожидающие запросы, сгруппированные по хешу.
std::unordered_map<Hash_t, std::vector<ResourceKey>> PendingReadsByHash;
// Готовые ответы на чтение.
std::vector<std::pair<ResourceKey, std::optional<Resource>>> ReadyReads;
};
} // namespace LV::Client

View File

@@ -1,119 +0,0 @@
// https://gist.github.com/podgorskiy/e698d18879588ada9014768e3e82a644
#include <glm/matrix.hpp>
class Frustum
{
public:
Frustum() {}
// m = ProjectionMatrix * ViewMatrix
Frustum(glm::mat4 m);
// http://iquilezles.org/www/articles/frustumcorrect/frustumcorrect.htm
bool IsBoxVisible(const glm::vec3& minp, const glm::vec3& maxp) const;
private:
enum Planes
{
Left = 0,
Right,
Bottom,
Top,
Near,
Far,
Count,
Combinations = Count * (Count - 1) / 2
};
template<Planes i, Planes j>
struct ij2k
{
enum { k = i * (9 - i) / 2 + j - 1 };
};
template<Planes a, Planes b, Planes c>
glm::vec3 intersection(const glm::vec3* crosses) const;
glm::vec4 m_planes[Count];
glm::vec3 m_points[8];
};
inline Frustum::Frustum(glm::mat4 m)
{
m = glm::transpose(m);
m_planes[Left] = m[3] + m[0];
m_planes[Right] = m[3] - m[0];
m_planes[Bottom] = m[3] + m[1];
m_planes[Top] = m[3] - m[1];
m_planes[Near] = m[3] + m[2];
m_planes[Far] = m[3] - m[2];
glm::vec3 crosses[Combinations] = {
glm::cross(glm::vec3(m_planes[Left]), glm::vec3(m_planes[Right])),
glm::cross(glm::vec3(m_planes[Left]), glm::vec3(m_planes[Bottom])),
glm::cross(glm::vec3(m_planes[Left]), glm::vec3(m_planes[Top])),
glm::cross(glm::vec3(m_planes[Left]), glm::vec3(m_planes[Near])),
glm::cross(glm::vec3(m_planes[Left]), glm::vec3(m_planes[Far])),
glm::cross(glm::vec3(m_planes[Right]), glm::vec3(m_planes[Bottom])),
glm::cross(glm::vec3(m_planes[Right]), glm::vec3(m_planes[Top])),
glm::cross(glm::vec3(m_planes[Right]), glm::vec3(m_planes[Near])),
glm::cross(glm::vec3(m_planes[Right]), glm::vec3(m_planes[Far])),
glm::cross(glm::vec3(m_planes[Bottom]), glm::vec3(m_planes[Top])),
glm::cross(glm::vec3(m_planes[Bottom]), glm::vec3(m_planes[Near])),
glm::cross(glm::vec3(m_planes[Bottom]), glm::vec3(m_planes[Far])),
glm::cross(glm::vec3(m_planes[Top]), glm::vec3(m_planes[Near])),
glm::cross(glm::vec3(m_planes[Top]), glm::vec3(m_planes[Far])),
glm::cross(glm::vec3(m_planes[Near]), glm::vec3(m_planes[Far]))
};
m_points[0] = intersection<Left, Bottom, Near>(crosses);
m_points[1] = intersection<Left, Top, Near>(crosses);
m_points[2] = intersection<Right, Bottom, Near>(crosses);
m_points[3] = intersection<Right, Top, Near>(crosses);
m_points[4] = intersection<Left, Bottom, Far>(crosses);
m_points[5] = intersection<Left, Top, Far>(crosses);
m_points[6] = intersection<Right, Bottom, Far>(crosses);
m_points[7] = intersection<Right, Top, Far>(crosses);
}
// http://iquilezles.org/www/articles/frustumcorrect/frustumcorrect.htm
inline bool Frustum::IsBoxVisible(const glm::vec3& minp, const glm::vec3& maxp) const
{
// check box outside/inside of frustum
for (int i = 0; i < Count; i++)
{
if ((glm::dot(m_planes[i], glm::vec4(minp.x, minp.y, minp.z, 1.0f)) < 0.0) &&
(glm::dot(m_planes[i], glm::vec4(maxp.x, minp.y, minp.z, 1.0f)) < 0.0) &&
(glm::dot(m_planes[i], glm::vec4(minp.x, maxp.y, minp.z, 1.0f)) < 0.0) &&
(glm::dot(m_planes[i], glm::vec4(maxp.x, maxp.y, minp.z, 1.0f)) < 0.0) &&
(glm::dot(m_planes[i], glm::vec4(minp.x, minp.y, maxp.z, 1.0f)) < 0.0) &&
(glm::dot(m_planes[i], glm::vec4(maxp.x, minp.y, maxp.z, 1.0f)) < 0.0) &&
(glm::dot(m_planes[i], glm::vec4(minp.x, maxp.y, maxp.z, 1.0f)) < 0.0) &&
(glm::dot(m_planes[i], glm::vec4(maxp.x, maxp.y, maxp.z, 1.0f)) < 0.0))
{
return false;
}
}
// check frustum outside/inside box
int out;
out = 0; for (int i = 0; i<8; i++) out += ((m_points[i].x > maxp.x) ? 1 : 0); if (out == 8) return false;
out = 0; for (int i = 0; i<8; i++) out += ((m_points[i].x < minp.x) ? 1 : 0); if (out == 8) return false;
out = 0; for (int i = 0; i<8; i++) out += ((m_points[i].y > maxp.y) ? 1 : 0); if (out == 8) return false;
out = 0; for (int i = 0; i<8; i++) out += ((m_points[i].y < minp.y) ? 1 : 0); if (out == 8) return false;
out = 0; for (int i = 0; i<8; i++) out += ((m_points[i].z > maxp.z) ? 1 : 0); if (out == 8) return false;
out = 0; for (int i = 0; i<8; i++) out += ((m_points[i].z < minp.z) ? 1 : 0); if (out == 8) return false;
return true;
}
template<Frustum::Planes a, Frustum::Planes b, Frustum::Planes c>
inline glm::vec3 Frustum::intersection(const glm::vec3* crosses) const
{
float D = glm::dot(glm::vec3(m_planes[a]), crosses[ij2k<b, c>::k]);
glm::vec3 res = glm::mat3(crosses[ij2k<b, c>::k], -crosses[ij2k<a, c>::k], crosses[ij2k<a, b>::k]) *
glm::vec3(m_planes[a].w, m_planes[b].w, m_planes[c].w);
return res * (-1.0f / D);
}

File diff suppressed because it is too large Load Diff

View File

@@ -6,170 +6,35 @@
#include "Common/Lockable.hpp" #include "Common/Lockable.hpp"
#include "Common/Net.hpp" #include "Common/Net.hpp"
#include "Common/Packets.hpp" #include "Common/Packets.hpp"
#include "TOSAsync.hpp"
#include <TOSLib.hpp> #include <TOSLib.hpp>
#include <boost/asio/io_context.hpp> #include <boost/asio/io_context.hpp>
#include <filesystem>
#include <memory> #include <memory>
#include <boost/lockfree/spsc_queue.hpp> #include <boost/lockfree/spsc_queue.hpp>
#include <Client/AssetsManager.hpp>
#include <queue>
#include <unordered_map>
namespace LV::Client { namespace LV::Client {
class ServerSession : public IAsyncDestructible, public IServerSession, public ISurfaceEventListener { struct ParsedPacket {
public: ToClient::L1 Level1;
using Ptr = std::shared_ptr<ServerSession>; uint8_t Level2;
public: ParsedPacket(ToClient::L1 l1, uint8_t l2)
static Ptr Create(asio::io_context &ioc, std::unique_ptr<Net::AsyncSocket> &&socket) { : Level1(l1), Level2(l2)
return createShared(ioc, new ServerSession(ioc, std::move(socket))); {}
} virtual ~ParsedPacket();
};
virtual ~ServerSession();
// Авторизоваться или (зарегистрироваться и авторизоваться) или зарегистрироваться
static coro<> asyncAuthorizeWithServer(tcp::socket &socket, const std::string username, const std::string token, int a_ar_r, std::function<void(const std::string&)> onProgress = nullptr);
// Начать игровой протокол в авторизированном сокете
static coro<std::unique_ptr<Net::AsyncSocket>> asyncInitGameProtocol(asio::io_context &ioc, tcp::socket &&socket, std::function<void(const std::string&)> onProgress = nullptr);
void shutdown(EnumDisconnect type);
void requestModsReload();
bool isConnected() {
return Socket->isAlive() && IsConnected;
}
uint64_t getVisibleCompressedChunksBytes() const {
return VisibleChunkCompressedBytes;
}
// ISurfaceEventListener
virtual void onResize(uint32_t width, uint32_t height) override;
virtual void onChangeFocusState(bool isFocused) override;
virtual void onCursorPosChange(int32_t width, int32_t height) override;
virtual void onCursorMove(float xMove, float yMove) override;
virtual void onCursorBtn(EnumCursorBtn btn, bool state) override;
virtual void onKeyboardBtn(int btn, int state) override;
virtual void onJoystick() override;
// IServerSession
virtual void update(GlobalTime gTime, float dTime) override;
void setRenderSession(IRenderSession* session);
private:
TOS::Logger LOG = "ServerSession";
class ServerSession : public IServerSession, public ISurfaceEventListener {
asio::io_context &IOC;
std::unique_ptr<Net::AsyncSocket> Socket; std::unique_ptr<Net::AsyncSocket> Socket;
IRenderSession *RS = nullptr; IRenderSession *RS = nullptr;
DestroyLock UseLock;
// Обработчик кеша ресурсов сервера
AssetsManager::Ptr AM;
static constexpr uint64_t TIME_BEFORE_UNLOAD_RESOURCE = 180;
struct {
// Существующие привязки ресурсов
std::unordered_set<ResourceId> ExistBinds[(int) EnumAssets::MAX_ENUM];
// Недавно использованные ресурсы, пока хранятся здесь в течении TIME_BEFORE_UNLOAD_RESOURCE секунд
std::unordered_map<std::string, std::pair<AssetEntry, uint64_t>> NotInUse[(int) EnumAssets::MAX_ENUM];
} MyAssets;
struct AssetLoadingEntry {
EnumAssets Type;
ResourceId Id;
std::string Domain;
std::string Key;
};
struct AssetLoading {
std::vector<AssetLoadingEntry> Entries;
std::u8string Data;
size_t Offset = 0;
};
struct AssetBindEntry {
EnumAssets Type;
ResourceId Id;
std::string Domain, Key;
Hash_t Hash;
std::vector<uint8_t> Header;
};
std::array<std::vector<std::pair<std::string, std::string>>, (int) EnumAssets::MAX_ENUM> ServerIdToDK;
std::array<ResourceId, (int) EnumAssets::MAX_ENUM> NextServerId = {};
struct TickData {
std::vector<std::pair<DefVoxelId, void*>> Profile_Voxel_AddOrChange;
std::vector<DefVoxelId> Profile_Voxel_Lost;
std::vector<std::pair<DefNodeId, DefNode_t>> Profile_Node_AddOrChange;
std::vector<DefNodeId> Profile_Node_Lost;
std::vector<std::pair<DefWorldId, void*>> Profile_World_AddOrChange;
std::vector<DefWorldId> Profile_World_Lost;
std::vector<std::pair<DefPortalId, void*>> Profile_Portal_AddOrChange;
std::vector<DefPortalId> Profile_Portal_Lost;
std::vector<std::pair<DefEntityId, DefEntityInfo>> Profile_Entity_AddOrChange;
std::vector<DefEntityId> Profile_Entity_Lost;
std::vector<std::pair<DefItemId, void*>> Profile_Item_AddOrChange;
std::vector<DefItemId> Profile_Item_Lost;
std::vector<std::pair<WorldId_t, void*>> Worlds_AddOrChange;
std::vector<WorldId_t> Worlds_Lost;
std::unordered_map<WorldId_t, std::unordered_map<Pos::GlobalChunk, std::u8string>> Chunks_AddOrChange_Voxel;
std::unordered_map<WorldId_t, std::unordered_map<Pos::GlobalChunk, std::u8string>> Chunks_AddOrChange_Node;
std::unordered_map<WorldId_t, std::vector<Pos::GlobalRegion>> Regions_Lost;
std::vector<std::pair<EntityId_t, EntityInfo>> Entity_AddOrChange;
std::vector<EntityId_t> Entity_Lost;
};
struct ChunkCompressedSize {
uint32_t Voxels = 0;
uint32_t Nodes = 0;
};
struct AssetsBindsChange {
// Новые привязки ресурсов
std::vector<AssetBindEntry> Binds;
// Потерянные из видимости ресурсы
std::vector<ResourceId> Lost[(int) EnumAssets::MAX_ENUM];
};
struct {
// Сюда обращается ветка, обрабатывающая сокет; run()
// Получение ресурсов с сервера
std::unordered_map<Hash_t, AssetLoading> AssetsLoading;
// Накопление данных за такт сервера
TickData ThisTickEntry;
// Сюда обращается ветка обновления IServerSession, накапливая данные до SyncTick
// Ресурсы, ожидающие либо кеш, либо сервер; используются для сопоставления hash->domain/key
std::unordered_map<std::string, std::vector<std::pair<std::string, Hash_t>>> ResourceWait[(int) EnumAssets::MAX_ENUM];
// Полученные изменения связок в ожидании стадии синхронизации такта
std::vector<AssetsBindsChange> Binds;
// Подгруженные или принятые меж тактами ресурсы
std::vector<AssetEntry> LoadedResources;
// Список ресурсов на которые уже был отправлен запрос на загрузку ресурса
std::vector<Hash_t> AlreadyLoading;
// Обменный пункт
// Полученные ресурсы с сервера
TOS::SpinlockObject<std::vector<AssetEntry>> LoadedAssets;
// Изменения в наблюдаемых ресурсах
TOS::SpinlockObject<std::vector<AssetsBindsChange>> AssetsBinds;
// Пакеты обновлений игрового мира
TOS::SpinlockObject<std::vector<TickData>> TickSequence;
} AsyncContext;
bool IsConnected = true, IsGoingShutdown = false; bool IsConnected = true, IsGoingShutdown = false;
TOS::Logger LOG = "ServerSession";
boost::lockfree::spsc_queue<ParsedPacket*> NetInputPackets;
// PYR - поворот камеры по осям xyz в радианах, PYR_Offset для сглаживание поворота // PYR - поворот камеры по осям xyz в радианах, PYR_Offset для сглаживание поворота
glm::vec3 PYR = glm::vec3(0), PYR_Offset = glm::vec3(0); glm::vec3 PYR = glm::vec3(0), PYR_Offset = glm::vec3(0);
double PYR_At = 0; double PYR_At = 0;
@@ -189,33 +54,54 @@ private:
GlobalTime LastSendPYR_POS; GlobalTime LastSendPYR_POS;
std::unordered_map<WorldId_t, std::unordered_map<Pos::GlobalChunk, ChunkCompressedSize>> VisibleChunkCompressed; public:
uint64_t VisibleChunkCompressedBytes = 0; // Нужен сокет, на котором только что был согласован игровой протокол (asyncInitGameProtocol)
ServerSession(asio::io_context &ioc, std::unique_ptr<Net::AsyncSocket> &&socket, IRenderSession *rs = nullptr)
: IOC(ioc), Socket(std::move(socket)), RS(rs), NetInputPackets(1024)
{
assert(Socket.get());
asio::co_spawn(IOC, run(), asio::detached);
}
// Приём данных с сокета virtual ~ServerSession();
coro<> run(AsyncUseControl::Lock);
// Авторизоваться или (зарегистрироваться и авторизоваться) или зарегистрироваться
static coro<> asyncAuthorizeWithServer(tcp::socket &socket, const std::string username, const std::string token, int a_ar_r, std::function<void(const std::string&)> onProgress = nullptr);
// Начать игровой протокол в авторизированном сокете
static coro<std::unique_ptr<Net::AsyncSocket>> asyncInitGameProtocol(asio::io_context &ioc, tcp::socket &&socket, std::function<void(const std::string&)> onProgress = nullptr);
void shutdown(EnumDisconnect type);
bool isConnected() {
return Socket->isAlive() && IsConnected;
}
void waitShutdown() {
UseLock.wait_no_use();
}
// ISurfaceEventListener
virtual void onResize(uint32_t width, uint32_t height) override;
virtual void onChangeFocusState(bool isFocused) override;
virtual void onCursorPosChange(int32_t width, int32_t height) override;
virtual void onCursorMove(float xMove, float yMove) override;
virtual void onCursorBtn(EnumCursorBtn btn, bool state) override;
virtual void onKeyboardBtn(int btn, int state) override;
virtual void onJoystick() override;
virtual void atFreeDrawTime(GlobalTime gTime, float dTime) override;
private:
coro<> run();
void protocolError(); void protocolError();
coro<> readPacket(Net::AsyncSocket &sock); coro<> readPacket(Net::AsyncSocket &sock);
coro<> rP_Disconnect(Net::AsyncSocket &sock); coro<> rP_System(Net::AsyncSocket &sock);
coro<> rP_AssetsBindDK(Net::AsyncSocket &sock); coro<> rP_Resource(Net::AsyncSocket &sock);
coro<> rP_AssetsBindHH(Net::AsyncSocket &sock); coro<> rP_Definition(Net::AsyncSocket &sock);
coro<> rP_AssetsInitSend(Net::AsyncSocket &sock); coro<> rP_Content(Net::AsyncSocket &sock);
coro<> rP_AssetsNextSend(Net::AsyncSocket &sock);
coro<> rP_DefinitionsUpdate(Net::AsyncSocket &sock);
coro<> rP_ChunkVoxels(Net::AsyncSocket &sock);
coro<> rP_ChunkNodes(Net::AsyncSocket &sock);
coro<> rP_ChunkLightPrism(Net::AsyncSocket &sock);
coro<> rP_RemoveRegion(Net::AsyncSocket &sock);
coro<> rP_Tick(Net::AsyncSocket &sock);
coro<> rP_TestLinkCameraToEntity(Net::AsyncSocket &sock);
coro<> rP_TestUnlinkCamera(Net::AsyncSocket &sock);
// Нужен сокет, на котором только что был согласован игровой протокол (asyncInitGameProtocol)
ServerSession(asio::io_context &ioc, std::unique_ptr<Net::AsyncSocket> &&socket);
virtual coro<> asyncDestructor() override;
void resetResourceSyncState();
}; };
} }

View File

@@ -1,52 +0,0 @@
#pragma once
#include <cstdint>
#include <cstring>
/*
Воксели рендерятся точками, которые распаковываются в квадратные плоскости
В чанке по оси 256 вокселей, и 257 позиций вершин (включая дальнюю границу чанка)
9 бит на позицию *3 оси = 27 бит
Указание материала 16 бит
*/
namespace LV::Client::VK {
struct VoxelVertexPoint {
uint32_t
FX : 9, FY : 9, FZ : 9, // Позиция
Place : 3, // Положение распространения xz, xy, zy, и обратные
N1 : 1, // Не занято
LS : 1, // Масштаб карты освещения (1м/16 или 1м)
TX : 8, TY : 8, // Размер+1
VoxMtl : 16, // Материал вокселя DefVoxelId_t
LU : 14, LV : 14, // Позиция на карте освещения
N2 : 2; // Не занято
};
/*
Максимальный размер меша 14^3 м от центра ноды
Координатное пространство то же, что и у вокселей + 8 позиций с двух сторон
Рисуется полигонами
В будущем - хранить данные освещения в отдельных буферах. Основные данные пусть спокойно индексируются
*/
struct NodeVertexStatic {
uint32_t
FX : 11, FY : 11, N1 : 10, // Позиция, 64 позиции на метр, +3.5м запас
FZ : 11, // Позиция
LS : 1, // Масштаб карты освещения (1м/16 или 1м)
Tex : 18, // Текстура
N2 : 2, // Не занято
TU : 16, TV : 16; // UV на текстуре
bool operator==(const NodeVertexStatic& other) const {
return std::memcmp(this, &other, sizeof(*this)) == 0;
}
bool operator<=>(const NodeVertexStatic&) const = default;
};
}

View File

@@ -1,307 +0,0 @@
#include "PipelinedTextureAtlas.hpp"
PipelinedTextureAtlas::PipelinedTextureAtlas(TextureAtlas&& tk)
: Super(std::move(tk)) {}
PipelinedTextureAtlas::AtlasTextureId PipelinedTextureAtlas::getByPipeline(const HashedPipeline& pipeline) {
auto iter = _PipeToTexId.find(pipeline);
if (iter == _PipeToTexId.end()) {
AtlasTextureId atlasTexId = Super.registerTexture();
_PipeToTexId.insert({pipeline, atlasTexId});
_ChangedPipelines.push_back(pipeline);
for (uint32_t texId : pipeline.getDependencedTextures()) {
_AddictedTextures[texId].push_back(pipeline);
}
{
std::vector<TexturePipelineProgram::AnimSpec> animMeta =
TexturePipelineProgram::extractAnimationSpecs(pipeline._Pipeline.data(), pipeline._Pipeline.size());
if (!animMeta.empty()) {
AnimatedPipelineState entry;
entry.Specs.reserve(animMeta.size());
for (const auto& spec : animMeta) {
detail::AnimSpec16 outSpec{};
outSpec.TexId = spec.HasTexId ? spec.TexId : TextureAtlas::kOverflowId;
outSpec.FrameW = spec.FrameW;
outSpec.FrameH = spec.FrameH;
outSpec.FrameCount = spec.FrameCount;
outSpec.FpsQ = spec.FpsQ;
outSpec.Flags = spec.Flags;
entry.Specs.push_back(outSpec);
}
entry.LastFrames.resize(entry.Specs.size(), std::numeric_limits<uint32_t>::max());
entry.Smooth = false;
for (const auto& spec : entry.Specs) {
if (spec.Flags & detail::AnimSmooth) {
entry.Smooth = true;
break;
}
}
_AnimatedPipelines.emplace(pipeline, std::move(entry));
}
}
return atlasTexId;
}
return iter->second;
}
void PipelinedTextureAtlas::freeByPipeline(const HashedPipeline& pipeline) {
auto iter = _PipeToTexId.find(pipeline);
if (iter == _PipeToTexId.end()) {
return;
}
for (uint32_t texId : pipeline.getDependencedTextures()) {
auto iterAT = _AddictedTextures.find(texId);
assert(iterAT != _AddictedTextures.end());
auto iterATSub = std::find(iterAT->second.begin(), iterAT->second.end(), pipeline);
assert(iterATSub != iterAT->second.end());
iterAT->second.erase(iterATSub);
}
Super.removeTexture(iter->second);
_AtlasCpuTextures.erase(iter->second);
_PipeToTexId.erase(iter);
_AnimatedPipelines.erase(pipeline);
}
void PipelinedTextureAtlas::updateTexture(uint32_t texId, const StoredTexture& texture) {
_ResToTexture[texId] = texture;
_ChangedTextures.push_back(texId);
}
void PipelinedTextureAtlas::updateTexture(uint32_t texId, StoredTexture&& texture) {
_ResToTexture[texId] = std::move(texture);
_ChangedTextures.push_back(texId);
}
void PipelinedTextureAtlas::freeTexture(uint32_t texId) {
auto iter = _ResToTexture.find(texId);
if (iter != _ResToTexture.end()) {
_ResToTexture.erase(iter);
}
}
bool PipelinedTextureAtlas::getHostTexture(TextureId texId, HostTextureView& out) const {
auto fill = [&](const StoredTexture& tex) -> bool {
if (tex._Pixels.empty() || tex._Widht == 0 || tex._Height == 0) {
return false;
}
out.width = tex._Widht;
out.height = tex._Height;
out.rowPitchBytes = static_cast<uint32_t>(tex._Widht) * 4u;
out.pixelsRGBA8 = reinterpret_cast<const uint8_t*>(tex._Pixels.data());
return true;
};
auto it = _ResToTexture.find(texId);
if (it != _ResToTexture.end() && fill(it->second)) {
return true;
}
auto itAtlas = _AtlasCpuTextures.find(texId);
if (itAtlas != _AtlasCpuTextures.end() && fill(itAtlas->second)) {
return true;
}
return false;
}
StoredTexture PipelinedTextureAtlas::_generatePipelineTexture(const HashedPipeline& pipeline) {
std::vector<detail::Word> words(pipeline._Pipeline.begin(), pipeline._Pipeline.end());
if (words.empty()) {
if (auto tex = tryCopyFirstDependencyTexture(pipeline)) {
return *tex;
}
return makeSolidColorTexture(0xFFFF00FFu);
}
TexturePipelineProgram program;
program.fromBytes(std::move(words));
TexturePipelineProgram::OwnedTexture baked;
auto provider = [this](uint32_t texId) -> std::optional<Texture> {
auto iter = _ResToTexture.find(texId);
if (iter == _ResToTexture.end()) {
return std::nullopt;
}
const StoredTexture& stored = iter->second;
if (stored._Pixels.empty() || stored._Widht == 0 || stored._Height == 0) {
return std::nullopt;
}
Texture tex{};
tex.Width = stored._Widht;
tex.Height = stored._Height;
tex.Pixels = stored._Pixels.data();
return tex;
};
if (!program.bake(provider, baked, _AnimTimeSeconds, nullptr)) {
if (auto tex = tryCopyFirstDependencyTexture(pipeline)) {
return *tex;
}
return makeSolidColorTexture(0xFFFF00FFu);
}
const uint32_t width = baked.Width;
const uint32_t height = baked.Height;
if (width == 0 || height == 0 ||
width > std::numeric_limits<uint16_t>::max() ||
height > std::numeric_limits<uint16_t>::max() ||
baked.Pixels.size() != static_cast<size_t>(width) * static_cast<size_t>(height)) {
if (auto tex = tryCopyFirstDependencyTexture(pipeline)) {
return *tex;
}
return makeSolidColorTexture(0xFFFF00FFu);
}
return StoredTexture(static_cast<uint16_t>(width),
static_cast<uint16_t>(height),
std::move(baked.Pixels));
}
void PipelinedTextureAtlas::flushNewPipelines() {
std::vector<uint32_t> changedTextures = std::move(_ChangedTextures);
_ChangedTextures.clear();
std::sort(changedTextures.begin(), changedTextures.end());
changedTextures.erase(std::unique(changedTextures.begin(), changedTextures.end()), changedTextures.end());
std::vector<HashedPipeline> changedPipelineTextures;
for (uint32_t texId : changedTextures) {
auto iter = _AddictedTextures.find(texId);
if (iter == _AddictedTextures.end()) {
continue;
}
changedPipelineTextures.append_range(iter->second);
}
changedPipelineTextures.append_range(std::move(_ChangedPipelines));
_ChangedPipelines.clear();
changedTextures.clear();
std::sort(changedPipelineTextures.begin(), changedPipelineTextures.end());
changedPipelineTextures.erase(std::unique(changedPipelineTextures.begin(), changedPipelineTextures.end()),
changedPipelineTextures.end());
for (const HashedPipeline& pipeline : changedPipelineTextures) {
auto iterPTTI = _PipeToTexId.find(pipeline);
assert(iterPTTI != _PipeToTexId.end());
StoredTexture texture = _generatePipelineTexture(pipeline);
AtlasTextureId atlasTexId = iterPTTI->second;
auto& stored = _AtlasCpuTextures[atlasTexId];
stored = std::move(texture);
if (!stored._Pixels.empty()) {
// Смена порядка пикселей
for (uint32_t& pixel : stored._Pixels) {
union {
struct { uint8_t r, g, b, a; } color;
uint32_t data;
};
data = pixel;
std::swap(color.r, color.b);
pixel = data;
}
Super.setTextureData(atlasTexId,
stored._Widht,
stored._Height,
stored._Pixels.data(),
stored._Widht * 4u);
}
}
}
TextureAtlas::DescriptorOut PipelinedTextureAtlas::flushUploadsAndBarriers(VkCommandBuffer cmdBuffer) {
return Super.flushUploadsAndBarriers(cmdBuffer);
}
void PipelinedTextureAtlas::notifyGpuFinished() {
Super.notifyGpuFinished();
}
bool PipelinedTextureAtlas::updateAnimatedPipelines(double timeSeconds) {
_AnimTimeSeconds = timeSeconds;
if (_AnimatedPipelines.empty()) {
return false;
}
bool changed = false;
for (auto& [pipeline, entry] : _AnimatedPipelines) {
if (entry.Specs.empty()) {
continue;
}
if (entry.Smooth) {
_ChangedPipelines.push_back(pipeline);
changed = true;
continue;
}
if (entry.LastFrames.size() != entry.Specs.size())
entry.LastFrames.assign(entry.Specs.size(), std::numeric_limits<uint32_t>::max());
bool pipelineChanged = false;
for (size_t i = 0; i < entry.Specs.size(); ++i) {
const auto& spec = entry.Specs[i];
uint32_t fpsQ = spec.FpsQ ? spec.FpsQ : TexturePipelineProgram::DefaultAnimFpsQ;
double fps = double(fpsQ) / 256.0;
double frameTime = timeSeconds * fps;
if (frameTime < 0.0)
frameTime = 0.0;
uint32_t frameCount = spec.FrameCount;
// Авторасчёт количества кадров
if (frameCount == 0) {
auto iterTex = _ResToTexture.find(spec.TexId);
if (iterTex != _ResToTexture.end()) {
uint32_t fw = spec.FrameW ? spec.FrameW : iterTex->second._Widht;
uint32_t fh = spec.FrameH ? spec.FrameH : iterTex->second._Widht;
if (fw > 0 && fh > 0) {
if (spec.Flags & detail::AnimHorizontal)
frameCount = iterTex->second._Widht / fw;
else
frameCount = iterTex->second._Height / fh;
}
}
}
if (frameCount == 0)
frameCount = 1;
uint32_t frameIndex = frameCount ? (uint32_t(frameTime) % frameCount) : 0u;
if (entry.LastFrames[i] != frameIndex) {
entry.LastFrames[i] = frameIndex;
pipelineChanged = true;
}
}
if (pipelineChanged) {
_ChangedPipelines.push_back(pipeline);
changed = true;
}
}
return changed;
}
std::optional<StoredTexture> PipelinedTextureAtlas::tryCopyFirstDependencyTexture(const HashedPipeline& pipeline) const {
auto deps = pipeline.getDependencedTextures();
if (!deps.empty()) {
auto iter = _ResToTexture.find(deps.front());
if (iter != _ResToTexture.end()) {
return iter->second;
}
}
return std::nullopt;
}
StoredTexture PipelinedTextureAtlas::makeSolidColorTexture(uint32_t rgba) {
return StoredTexture(1, 1, std::vector<uint32_t>{rgba});
}

View File

@@ -1,490 +0,0 @@
#pragma once
#include "TextureAtlas.hpp"
#include "TexturePipelineProgram.hpp"
#include <algorithm>
#include <cassert>
#include <cstdlib>
#include <cstring>
#include <limits>
#include <optional>
#include <unordered_map>
#include <utility>
#include <vector>
#include "boost/container/small_vector.hpp"
using TextureId = uint32_t;
namespace detail {
using Word = TexturePipelineProgram::Word;
enum class Op16 : Word {
End = 0,
Base_Tex = 1,
Base_Fill = 2,
Base_Anim = 3,
Resize = 10,
Transform = 11,
Opacity = 12,
NoAlpha = 13,
MakeAlpha = 14,
Invert = 15,
Brighten = 16,
Contrast = 17,
Multiply = 18,
Screen = 19,
Colorize = 20,
Anim = 21,
Overlay = 30,
Mask = 31,
LowPart = 32,
Combine = 40
};
enum class SrcKind16 : Word { TexId = 0, Sub = 1 };
struct SrcRef16 {
SrcKind16 kind{SrcKind16::TexId};
uint32_t TexId = 0;
uint32_t Off = 0;
uint32_t Len = 0;
};
enum AnimFlags16 : Word {
AnimSmooth = 1 << 0,
AnimHorizontal = 1 << 1
};
struct AnimSpec16 {
uint32_t TexId = 0;
uint16_t FrameW = 0;
uint16_t FrameH = 0;
uint16_t FrameCount = 0;
uint16_t FpsQ = 0;
uint16_t Flags = 0;
};
inline void addUniqueDep(boost::container::small_vector<uint32_t, 8>& deps, uint32_t id) {
if (id == TextureAtlas::kOverflowId) {
return;
}
if (std::find(deps.begin(), deps.end(), id) == deps.end()) {
deps.push_back(id);
}
}
inline bool read16(const std::vector<Word>& words, size_t end, size_t& ip, uint16_t& out) {
if (ip + 1 >= end) {
return false;
}
out = uint16_t(words[ip]) | (uint16_t(words[ip + 1]) << 8);
ip += 2;
return true;
}
inline bool read24(const std::vector<Word>& words, size_t end, size_t& ip, uint32_t& out) {
if (ip + 2 >= end) {
return false;
}
out = uint32_t(words[ip]) |
(uint32_t(words[ip + 1]) << 8) |
(uint32_t(words[ip + 2]) << 16);
ip += 3;
return true;
}
inline bool read32(const std::vector<Word>& words, size_t end, size_t& ip, uint32_t& out) {
if (ip + 3 >= end) {
return false;
}
out = uint32_t(words[ip]) |
(uint32_t(words[ip + 1]) << 8) |
(uint32_t(words[ip + 2]) << 16) |
(uint32_t(words[ip + 3]) << 24);
ip += 4;
return true;
}
inline bool readSrc(const std::vector<Word>& words, size_t end, size_t& ip, SrcRef16& out) {
if (ip >= end) {
return false;
}
out.kind = static_cast<SrcKind16>(words[ip++]);
if (out.kind == SrcKind16::TexId) {
return read24(words, end, ip, out.TexId);
}
if (out.kind == SrcKind16::Sub) {
return read24(words, end, ip, out.Off) && read24(words, end, ip, out.Len);
}
return false;
}
inline void extractPipelineDependencies(const std::vector<Word>& words,
size_t start,
size_t end,
boost::container::small_vector<uint32_t, 8>& deps,
std::vector<std::pair<size_t, size_t>>& visited) {
if (start >= end || end > words.size()) {
return;
}
const std::pair<size_t, size_t> key{start, end};
if (std::find(visited.begin(), visited.end(), key) != visited.end()) {
return;
}
visited.push_back(key);
size_t ip = start;
auto need = [&](size_t n) { return ip + n <= end; };
auto handleSrc = [&](const SrcRef16& src) {
if (src.kind == SrcKind16::TexId) {
addUniqueDep(deps, src.TexId);
return;
}
if (src.kind == SrcKind16::Sub) {
size_t subStart = static_cast<size_t>(src.Off);
size_t subEnd = subStart + static_cast<size_t>(src.Len);
if (subStart < subEnd && subEnd <= words.size()) {
extractPipelineDependencies(words, subStart, subEnd, deps, visited);
}
}
};
while (ip < end) {
if (!need(1)) break;
Op16 op = static_cast<Op16>(words[ip++]);
switch (op) {
case Op16::End:
return;
case Op16::Base_Tex: {
SrcRef16 src{};
if (!readSrc(words, end, ip, src)) return;
handleSrc(src);
} break;
case Op16::Base_Anim: {
SrcRef16 src{};
if (!readSrc(words, end, ip, src)) return;
handleSrc(src);
uint16_t tmp16 = 0;
uint8_t tmp8 = 0;
if (!read16(words, end, ip, tmp16)) return;
if (!read16(words, end, ip, tmp16)) return;
if (!read16(words, end, ip, tmp16)) return;
if (!read16(words, end, ip, tmp16)) return;
if (!need(1)) return;
tmp8 = words[ip++];
(void)tmp8;
} break;
case Op16::Base_Fill: {
uint16_t tmp16 = 0;
uint32_t tmp32 = 0;
if (!read16(words, end, ip, tmp16)) return;
if (!read16(words, end, ip, tmp16)) return;
if (!read32(words, end, ip, tmp32)) return;
} break;
case Op16::Overlay:
case Op16::Mask: {
SrcRef16 src{};
if (!readSrc(words, end, ip, src)) return;
handleSrc(src);
} break;
case Op16::LowPart: {
if (!need(1)) return;
ip += 1; // percent
SrcRef16 src{};
if (!readSrc(words, end, ip, src)) return;
handleSrc(src);
} break;
case Op16::Resize: {
uint16_t tmp16 = 0;
if (!read16(words, end, ip, tmp16)) return;
if (!read16(words, end, ip, tmp16)) return;
} break;
case Op16::Transform:
case Op16::Opacity:
if (!need(1)) return;
ip += 1;
break;
case Op16::NoAlpha:
case Op16::Brighten:
break;
case Op16::MakeAlpha:
if (!need(3)) return;
ip += 3;
break;
case Op16::Invert:
if (!need(1)) return;
ip += 1;
break;
case Op16::Contrast:
if (!need(2)) return;
ip += 2;
break;
case Op16::Multiply:
case Op16::Screen: {
uint32_t tmp32 = 0;
if (!read32(words, end, ip, tmp32)) return;
} break;
case Op16::Colorize: {
uint32_t tmp32 = 0;
if (!read32(words, end, ip, tmp32)) return;
if (!need(1)) return;
ip += 1;
} break;
case Op16::Anim: {
uint16_t tmp16 = 0;
if (!read16(words, end, ip, tmp16)) return;
if (!read16(words, end, ip, tmp16)) return;
if (!read16(words, end, ip, tmp16)) return;
if (!read16(words, end, ip, tmp16)) return;
if (!need(1)) return;
ip += 1;
} break;
case Op16::Combine: {
uint16_t w = 0, h = 0, n = 0;
if (!read16(words, end, ip, w)) return;
if (!read16(words, end, ip, h)) return;
if (!read16(words, end, ip, n)) return;
for (uint32_t i = 0; i < n; ++i) {
uint16_t tmp16 = 0;
if (!read16(words, end, ip, tmp16)) return; // x
if (!read16(words, end, ip, tmp16)) return; // y
SrcRef16 src{};
if (!readSrc(words, end, ip, src)) return;
handleSrc(src);
}
(void)w; (void)h;
} break;
default:
return;
}
}
}
inline boost::container::small_vector<uint32_t, 8> extractPipelineDependencies(const std::vector<Word>& words) {
boost::container::small_vector<uint32_t, 8> deps;
std::vector<std::pair<size_t, size_t>> visited;
extractPipelineDependencies(words, 0, words.size(), deps, visited);
return deps;
}
inline boost::container::small_vector<uint32_t, 8> extractPipelineDependencies(const boost::container::small_vector<Word, 32>& words) {
boost::container::small_vector<uint32_t, 8> deps;
std::vector<std::pair<size_t, size_t>> visited;
std::vector<Word> copy(words.begin(), words.end());
extractPipelineDependencies(copy, 0, copy.size(), deps, visited);
return deps;
}
} // namespace detail
// Структура нехешированного пайплайна
struct Pipeline {
std::vector<detail::Word> _Pipeline;
Pipeline() = default;
explicit Pipeline(const TexturePipelineProgram& program)
: _Pipeline(program.words().begin(), program.words().end())
{
}
Pipeline(TextureId texId) {
_Pipeline = {
static_cast<detail::Word>(detail::Op16::Base_Tex),
static_cast<detail::Word>(detail::SrcKind16::TexId),
static_cast<detail::Word>(texId & 0xFFu),
static_cast<detail::Word>((texId >> 8) & 0xFFu),
static_cast<detail::Word>((texId >> 16) & 0xFFu),
static_cast<detail::Word>(detail::Op16::End)
};
}
};
// Структура хешированного текстурного пайплайна
struct HashedPipeline {
// Предвычисленный хеш
std::size_t _Hash;
boost::container::small_vector<detail::Word, 32> _Pipeline;
HashedPipeline() = default;
HashedPipeline(const Pipeline& pipeline) noexcept
: _Pipeline(pipeline._Pipeline.begin(), pipeline._Pipeline.end())
{
reComputeHash();
}
// Перевычисляет хеш
void reComputeHash() noexcept {
std::size_t hash = 14695981039346656037ull;
constexpr std::size_t prime = 1099511628211ull;
for(detail::Word w : _Pipeline) {
hash ^= static_cast<uint8_t>(w);
hash *= prime;
}
_Hash = hash;
}
// Выдаёт список зависимых текстур, на основе которых строится эта
boost::container::small_vector<uint32_t, 8> getDependencedTextures() const {
return detail::extractPipelineDependencies(_Pipeline);
}
bool operator==(const HashedPipeline& obj) const noexcept {
return _Hash == obj._Hash && _Pipeline == obj._Pipeline;
}
bool operator<(const HashedPipeline& obj) const noexcept {
return _Hash < obj._Hash || (_Hash == obj._Hash && _Pipeline < obj._Pipeline);
}
};
struct StoredTexture {
uint16_t _Widht = 0;
uint16_t _Height = 0;
std::vector<uint32_t> _Pixels;
StoredTexture() = default;
StoredTexture(uint16_t w, uint16_t h, std::vector<uint32_t> pixels)
: _Widht(w), _Height(h), _Pixels(std::move(pixels))
{
}
};
// Пайплайновый текстурный атлас
class PipelinedTextureAtlas {
public:
using AtlasTextureId = uint32_t;
struct HostTextureView {
uint32_t width = 0;
uint32_t height = 0;
uint32_t rowPitchBytes = 0;
const uint8_t* pixelsRGBA8 = nullptr;
};
private:
// Функтор хеша
struct HashedPipelineKeyHash {
std::size_t operator()(const HashedPipeline& k) const noexcept {
return k._Hash;
}
};
// Функтор равенства
struct HashedPipelineKeyEqual {
bool operator()(const HashedPipeline& a, const HashedPipeline& b) const noexcept {
return a._Pipeline == b._Pipeline;
}
};
// Текстурный атлас
TextureAtlas Super;
// Пустой пайплайн (указывающий на одну текстуру) ссылается на простой идентификатор (ResToAtlas)
std::unordered_map<HashedPipeline, AtlasTextureId, HashedPipelineKeyHash, HashedPipelineKeyEqual> _PipeToTexId;
// Загруженные текстуры
std::unordered_map<TextureId, StoredTexture> _ResToTexture;
std::unordered_map<AtlasTextureId, StoredTexture> _AtlasCpuTextures;
// Список зависимых пайплайнов от текстур (при изменении текстуры, нужно перерисовать пайплайны)
std::unordered_map<TextureId, boost::container::small_vector<HashedPipeline, 8>> _AddictedTextures;
// Изменённые простые текстуры (для последующего массового обновление пайплайнов)
std::vector<uint32_t> _ChangedTextures;
// Необходимые к созданию/обновлению пайплайны
std::vector<HashedPipeline> _ChangedPipelines;
struct AnimatedPipelineState {
std::vector<detail::AnimSpec16> Specs;
std::vector<uint32_t> LastFrames;
bool Smooth = false;
};
std::unordered_map<HashedPipeline, AnimatedPipelineState, HashedPipelineKeyHash, HashedPipelineKeyEqual> _AnimatedPipelines;
double _AnimTimeSeconds = 0.0;
public:
PipelinedTextureAtlas(TextureAtlas&& tk);
uint32_t atlasSide() const {
return Super.atlasSide();
}
uint32_t atlasLayers() const {
return Super.atlasLayers();
}
uint32_t AtlasSide() const {
return atlasSide();
}
uint32_t AtlasLayers() const {
return atlasLayers();
}
uint32_t maxLayers() const {
return Super.maxLayers();
}
uint32_t maxTextureId() const {
return Super.maxTextureId();
}
TextureAtlas::TextureId reservedOverflowId() const {
return Super.reservedOverflowId();
}
TextureAtlas::TextureId reservedLayerId(uint32_t layer) const {
return Super.reservedLayerId(layer);
}
void requestLayerCount(uint32_t layers) {
Super.requestLayerCount(layers);
}
// Должны всегда бронировать идентификатор, либо отдавать kOverflowId. При этом запись tex+pipeline остаётся
// Выдаёт стабильный идентификатор, привязанный к пайплайну
AtlasTextureId getByPipeline(const HashedPipeline& pipeline);
// Уведомить что текстура+pipeline более не используются (идентификатор будет освобождён)
// Освобождать можно при потере ресурсов
void freeByPipeline(const HashedPipeline& pipeline);
void updateTexture(uint32_t texId, const StoredTexture& texture);
void updateTexture(uint32_t texId, StoredTexture&& texture);
void freeTexture(uint32_t texId);
bool getHostTexture(TextureId texId, HostTextureView& out) const;
// Генерация текстуры пайплайна
StoredTexture _generatePipelineTexture(const HashedPipeline& pipeline);
// Обновляет пайплайны по необходимости
void flushNewPipelines();
TextureAtlas::DescriptorOut flushUploadsAndBarriers(VkCommandBuffer cmdBuffer);
void notifyGpuFinished();
bool updateAnimatedPipelines(double timeSeconds);
private:
std::optional<StoredTexture> tryCopyFirstDependencyTexture(const HashedPipeline& pipeline) const;
static StoredTexture makeSolidColorTexture(uint32_t rgba);
};

View File

@@ -1,169 +0,0 @@
#pragma once
#include <vulkan/vulkan.h>
#include <cstdint>
#include <optional>
#include <stdexcept>
#include <utility>
/*
Межкадровый промежуточный буфер.
Для модели рендера Один за одним.
После окончания рендера кадра считается синхронизированным
и может заполняться по новой.
*/
class SharedStagingBuffer {
public:
static constexpr VkDeviceSize kDefaultSize = 64ull * 1024ull * 1024ull;
SharedStagingBuffer(VkDevice device,
VkPhysicalDevice physicalDevice,
VkDeviceSize sizeBytes = kDefaultSize)
: device_(device),
physicalDevice_(physicalDevice),
size_(sizeBytes) {
if (!device_ || !physicalDevice_) {
throw std::runtime_error("SharedStagingBuffer: null device/physicalDevice");
}
if (size_ == 0) {
throw std::runtime_error("SharedStagingBuffer: size must be > 0");
}
VkBufferCreateInfo bi{
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.size = size_,
.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.queueFamilyIndexCount = 0,
.pQueueFamilyIndices = nullptr
};
if (vkCreateBuffer(device_, &bi, nullptr, &buffer_) != VK_SUCCESS) {
throw std::runtime_error("SharedStagingBuffer: vkCreateBuffer failed");
}
VkMemoryRequirements mr{};
vkGetBufferMemoryRequirements(device_, buffer_, &mr);
VkMemoryAllocateInfo ai{};
ai.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
ai.allocationSize = mr.size;
ai.memoryTypeIndex = FindMemoryType_(mr.memoryTypeBits,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
if (vkAllocateMemory(device_, &ai, nullptr, &memory_) != VK_SUCCESS) {
vkDestroyBuffer(device_, buffer_, nullptr);
buffer_ = VK_NULL_HANDLE;
throw std::runtime_error("SharedStagingBuffer: vkAllocateMemory failed");
}
vkBindBufferMemory(device_, buffer_, memory_, 0);
if (vkMapMemory(device_, memory_, 0, VK_WHOLE_SIZE, 0, &mapped_) != VK_SUCCESS) {
vkFreeMemory(device_, memory_, nullptr);
vkDestroyBuffer(device_, buffer_, nullptr);
buffer_ = VK_NULL_HANDLE;
memory_ = VK_NULL_HANDLE;
throw std::runtime_error("SharedStagingBuffer: vkMapMemory failed");
}
}
~SharedStagingBuffer() { Destroy_(); }
SharedStagingBuffer(const SharedStagingBuffer&) = delete;
SharedStagingBuffer& operator=(const SharedStagingBuffer&) = delete;
SharedStagingBuffer(SharedStagingBuffer&& other) noexcept {
*this = std::move(other);
}
SharedStagingBuffer& operator=(SharedStagingBuffer&& other) noexcept {
if (this != &other) {
Destroy_();
device_ = other.device_;
physicalDevice_ = other.physicalDevice_;
buffer_ = other.buffer_;
memory_ = other.memory_;
mapped_ = other.mapped_;
size_ = other.size_;
offset_ = other.offset_;
other.device_ = VK_NULL_HANDLE;
other.physicalDevice_ = VK_NULL_HANDLE;
other.buffer_ = VK_NULL_HANDLE;
other.memory_ = VK_NULL_HANDLE;
other.mapped_ = nullptr;
other.size_ = 0;
other.offset_ = 0;
}
return *this;
}
VkBuffer Buffer() const { return buffer_; }
void* Mapped() const { return mapped_; }
VkDeviceSize Size() const { return size_; }
std::optional<VkDeviceSize> Allocate(VkDeviceSize bytes, VkDeviceSize alignment) {
VkDeviceSize off = Align_(offset_, alignment);
if (off + bytes > size_) {
return std::nullopt;
}
offset_ = off + bytes;
return off;
}
void Reset() { offset_ = 0; }
private:
uint32_t FindMemoryType_(uint32_t typeBits, VkMemoryPropertyFlags properties) const {
VkPhysicalDeviceMemoryProperties mp{};
vkGetPhysicalDeviceMemoryProperties(physicalDevice_, &mp);
for (uint32_t i = 0; i < mp.memoryTypeCount; ++i) {
if ((typeBits & (1u << i)) &&
(mp.memoryTypes[i].propertyFlags & properties) == properties) {
return i;
}
}
throw std::runtime_error("SharedStagingBuffer: no suitable memory type");
}
static VkDeviceSize Align_(VkDeviceSize value, VkDeviceSize alignment) {
if (alignment == 0) return value;
return (value + alignment - 1) & ~(alignment - 1);
}
void Destroy_() {
if (device_ == VK_NULL_HANDLE) {
return;
}
if (mapped_) {
vkUnmapMemory(device_, memory_);
mapped_ = nullptr;
}
if (buffer_) {
vkDestroyBuffer(device_, buffer_, nullptr);
buffer_ = VK_NULL_HANDLE;
}
if (memory_) {
vkFreeMemory(device_, memory_, nullptr);
memory_ = VK_NULL_HANDLE;
}
size_ = 0;
offset_ = 0;
device_ = VK_NULL_HANDLE;
physicalDevice_ = VK_NULL_HANDLE;
}
VkDevice device_ = VK_NULL_HANDLE;
VkPhysicalDevice physicalDevice_ = VK_NULL_HANDLE;
VkBuffer buffer_ = VK_NULL_HANDLE;
VkDeviceMemory memory_ = VK_NULL_HANDLE;
void* mapped_ = nullptr;
VkDeviceSize size_ = 0;
VkDeviceSize offset_ = 0;
};

View File

@@ -1,485 +0,0 @@
#include "TextureAtlas.hpp"
TextureAtlas::TextureAtlas(VkDevice device,
VkPhysicalDevice physicalDevice,
const Config& cfg,
EventCallback cb,
std::shared_ptr<SharedStagingBuffer> staging)
: Device_(device),
Phys_(physicalDevice),
Cfg_(cfg),
OnEvent_(std::move(cb)),
Staging_(std::move(staging)) {
if(!Device_ || !Phys_) {
throw std::runtime_error("TextureAtlas: device/physicalDevice == null");
}
_validateConfigOrThrow();
VkPhysicalDeviceProperties props{};
vkGetPhysicalDeviceProperties(Phys_, &props);
CopyOffsetAlignment_ = std::max<VkDeviceSize>(4, props.limits.optimalBufferCopyOffsetAlignment);
if(!Staging_) {
Staging_ = std::make_shared<SharedStagingBuffer>(Device_, Phys_, kStagingSizeBytes);
}
_validateStagingCapacityOrThrow();
_createEntriesBufferOrThrow();
_createAtlasOrThrow(Cfg_.InitialSide, 1);
EntriesCpu_.resize(Cfg_.MaxTextureId);
std::memset(EntriesCpu_.data(), 0, EntriesCpu_.size() * sizeof(Entry));
_initReservedEntries();
EntriesDirty_ = true;
Slots_.resize(Cfg_.MaxTextureId);
FreeIds_.reserve(Cfg_.MaxTextureId);
PendingInQueue_.assign(Cfg_.MaxTextureId, false);
NextId_ = _allocatableStart();
if(Cfg_.ExternalSampler != VK_NULL_HANDLE) {
Sampler_ = Cfg_.ExternalSampler;
OwnsSampler_ = false;
} else {
_createSamplerOrThrow();
OwnsSampler_ = true;
}
_rebuildPackersFromPlacements();
Alive_ = true;
}
TextureAtlas::~TextureAtlas() { _shutdownNoThrow(); }
TextureAtlas::TextureAtlas(TextureAtlas&& other) noexcept {
_moveFrom(std::move(other));
}
TextureAtlas& TextureAtlas::operator=(TextureAtlas&& other) noexcept {
if(this != &other) {
_shutdownNoThrow();
_moveFrom(std::move(other));
}
return *this;
}
void TextureAtlas::shutdown() {
_ensureAliveOrThrow();
_shutdownNoThrow();
}
TextureAtlas::TextureId TextureAtlas::registerTexture() {
_ensureAliveOrThrow();
TextureId id = kOverflowId;
if(NextId_ < _allocatableStart()) {
NextId_ = _allocatableStart();
}
while(!FreeIds_.empty() && isReservedId(FreeIds_.back())) {
FreeIds_.pop_back();
}
if(!FreeIds_.empty()) {
id = FreeIds_.back();
FreeIds_.pop_back();
} else if(NextId_ < _allocatableLimit()) {
id = NextId_++;
} else {
return reservedOverflowId();
}
Slot& s = Slots_[id];
s = Slot{};
s.InUse = true;
s.StateValue = State::REGISTERED;
s.Generation = 1;
_setEntryInvalid(id, /*diagPending*/false, /*diagTooLarge*/false);
EntriesDirty_ = true;
return id;
}
void TextureAtlas::setTextureData(TextureId id,
uint32_t w,
uint32_t h,
const void* pixelsRGBA8,
uint32_t rowPitchBytes) {
_ensureAliveOrThrow();
if(isInvalidId(id)) return;
_ensureRegisteredIdOrThrow(id);
if(w == 0 || h == 0) {
throw _inputError("setTextureData: w/h must be > 0");
}
if(w > Cfg_.MaxTextureSize || h > Cfg_.MaxTextureSize) {
_handleTooLarge(id);
throw _inputError("setTextureData: texture is TOO_LARGE (>2048)");
}
if(!pixelsRGBA8) {
throw _inputError("setTextureData: pixelsRGBA8 == null");
}
if(rowPitchBytes == 0) {
rowPitchBytes = w * 4;
}
if(rowPitchBytes < w * 4) {
throw _inputError("setTextureData: rowPitchBytes < w*4");
}
Slot& s = Slots_[id];
const bool sizeChanged = (s.HasCpuData && (s.W != w || s.H != h));
if(sizeChanged) {
_freePlacement(id);
_setEntryInvalid(id, /*diagPending*/true, /*diagTooLarge*/false);
EntriesDirty_ = true;
}
s.W = w;
s.H = h;
s.CpuPixels = static_cast<const uint8_t*>(pixelsRGBA8);
s.CpuRowPitchBytes = rowPitchBytes;
s.HasCpuData = true;
s.StateValue = State::PENDING_UPLOAD;
s.Generation++;
if(!sizeChanged && s.HasPlacement && s.StateWasValid) {
// keep entry valid
} else if(!s.HasPlacement) {
_setEntryInvalid(id, /*diagPending*/true, /*diagTooLarge*/false);
EntriesDirty_ = true;
}
_enqueuePending(id);
if(Repack_.Active && Repack_.Plan.count(id) != 0) {
_enqueueRepackPending(id);
}
}
void TextureAtlas::clearTextureData(TextureId id) {
_ensureAliveOrThrow();
if(isInvalidId(id)) return;
_ensureRegisteredIdOrThrow(id);
Slot& s = Slots_[id];
s.CpuPixels = nullptr;
s.CpuRowPitchBytes = 0;
s.HasCpuData = false;
_freePlacement(id);
s.StateValue = State::REGISTERED;
s.StateWasValid = false;
_removeFromPending(id);
_removeFromRepackPending(id);
_setEntryInvalid(id, /*diagPending*/false, /*diagTooLarge*/false);
EntriesDirty_ = true;
}
void TextureAtlas::removeTexture(TextureId id) {
_ensureAliveOrThrow();
if(isInvalidId(id)) return;
_ensureRegisteredIdOrThrow(id);
Slot& s = Slots_[id];
clearTextureData(id);
s.InUse = false;
s.StateValue = State::REMOVED;
FreeIds_.push_back(id);
_setEntryInvalid(id, /*diagPending*/false, /*diagTooLarge*/false);
EntriesDirty_ = true;
}
void TextureAtlas::requestFullRepack(RepackMode mode) {
_ensureAliveOrThrow();
Repack_.Requested = true;
Repack_.Mode = mode;
}
TextureAtlas::DescriptorOut TextureAtlas::flushUploadsAndBarriers(VkCommandBuffer cmdBuffer) {
_ensureAliveOrThrow();
if(cmdBuffer == VK_NULL_HANDLE) {
throw _inputError("flushUploadsAndBarriers: cmdBuffer == null");
}
if(Repack_.SwapReady) {
_swapToRepackedAtlas();
}
if(Repack_.Requested && !Repack_.Active) {
_startRepackIfPossible();
}
_processPendingLayerGrow(cmdBuffer);
bool willTouchEntries = EntriesDirty_;
auto collectQueue = [this](std::deque<TextureId>& queue,
std::vector<bool>& inQueue,
std::vector<TextureId>& out) {
while (!queue.empty()) {
TextureId id = queue.front();
queue.pop_front();
if(isInvalidId(id) || id >= inQueue.size()) {
continue;
}
if(!inQueue[id]) {
continue;
}
inQueue[id] = false;
out.push_back(id);
}
};
std::vector<TextureId> pendingNow;
pendingNow.reserve(Pending_.size());
collectQueue(Pending_, PendingInQueue_, pendingNow);
std::vector<TextureId> repackPending;
if(Repack_.Active) {
if(Repack_.InPending.empty()) {
Repack_.InPending.assign(Cfg_.MaxTextureId, false);
}
collectQueue(Repack_.Pending, Repack_.InPending, repackPending);
}
auto processPlacement = [&](TextureId id, Slot& s) -> bool {
if(s.HasPlacement) return true;
const uint32_t wP = s.W + 2u * Cfg_.PaddingPx;
const uint32_t hP = s.H + 2u * Cfg_.PaddingPx;
if(!_tryPlaceWithGrow(id, wP, hP, cmdBuffer)) {
return false;
}
willTouchEntries = true;
return true;
};
bool outOfSpace = false;
for(TextureId id : pendingNow) {
if(isInvalidId(id)) continue;
if(id >= Slots_.size()) continue;
Slot& s = Slots_[id];
if(!s.InUse || !s.HasCpuData) continue;
if(!processPlacement(id, s)) {
outOfSpace = true;
_enqueuePending(id);
}
}
if(outOfSpace) {
_emitEventOncePerFlush(AtlasEvent::AtlasOutOfSpace);
}
bool anyAtlasWrites = false;
bool anyRepackWrites = false;
auto uploadTextureIntoAtlas = [&](Slot& s,
const Placement& pp,
ImageRes& targetAtlas,
bool isRepackTarget) {
const uint32_t wP = pp.WP;
const uint32_t hP = pp.HP;
const VkDeviceSize bytes = static_cast<VkDeviceSize>(wP) * hP * 4u;
auto stagingOff = Staging_->Allocate(bytes, CopyOffsetAlignment_);
if(!stagingOff) {
_emitEventOncePerFlush(AtlasEvent::StagingOverflow);
return false;
}
uint8_t* dst = static_cast<uint8_t*>(Staging_->Mapped()) + *stagingOff;
if(!s.CpuPixels) {
return false;
}
_writePaddedRGBA8(dst, wP * 4u, s.W, s.H, Cfg_.PaddingPx,
s.CpuPixels, s.CpuRowPitchBytes);
_ensureImageLayoutForTransferDst(cmdBuffer, targetAtlas,
isRepackTarget ? anyRepackWrites : anyAtlasWrites);
VkBufferImageCopy region{};
region.bufferOffset = *stagingOff;
region.bufferRowLength = wP;
region.bufferImageHeight = hP;
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
region.imageSubresource.mipLevel = 0;
region.imageSubresource.baseArrayLayer = pp.Layer;
region.imageSubresource.layerCount = 1;
region.imageOffset = { static_cast<int32_t>(pp.X),
static_cast<int32_t>(pp.Y), 0 };
region.imageExtent = { wP, hP, 1 };
vkCmdCopyBufferToImage(cmdBuffer, Staging_->Buffer(), targetAtlas.Image,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region);
return true;
};
for(TextureId id : pendingNow) {
if(isInvalidId(id)) continue;
Slot& s = Slots_[id];
if(!s.InUse || !s.HasCpuData || !s.HasPlacement) continue;
if(!uploadTextureIntoAtlas(s, s.Place, Atlas_, false)) {
_enqueuePending(id);
continue;
}
s.StateValue = State::VALID;
s.StateWasValid = true;
_setEntryValid(id);
EntriesDirty_ = true;
}
if(Repack_.Active) {
for(TextureId id : repackPending) {
if(Repack_.Plan.count(id) == 0) continue;
Slot& s = Slots_[id];
if(!s.InUse || !s.HasCpuData) continue;
const PlannedPlacement& pp = Repack_.Plan[id];
Placement place{pp.X, pp.Y, pp.WP, pp.HP, pp.Layer};
if(!uploadTextureIntoAtlas(s, place, Repack_.Atlas, true)) {
_enqueueRepackPending(id);
continue;
}
Repack_.WroteSomethingThisFlush = true;
}
}
if(willTouchEntries || EntriesDirty_) {
const VkDeviceSize entriesBytes = static_cast<VkDeviceSize>(EntriesCpu_.size()) * sizeof(Entry);
auto off = Staging_->Allocate(entriesBytes, CopyOffsetAlignment_);
if(!off) {
_emitEventOncePerFlush(AtlasEvent::StagingOverflow);
} else {
std::memcpy(static_cast<uint8_t*>(Staging_->Mapped()) + *off,
EntriesCpu_.data(),
static_cast<size_t>(entriesBytes));
VkBufferCopy c{};
c.srcOffset = *off;
c.dstOffset = 0;
c.size = entriesBytes;
vkCmdCopyBuffer(cmdBuffer, Staging_->Buffer(), Entries_.Buffer, 1, &c);
VkBufferMemoryBarrier b{};
b.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER;
b.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
b.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
b.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
b.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
b.buffer = Entries_.Buffer;
b.offset = 0;
b.size = VK_WHOLE_SIZE;
vkCmdPipelineBarrier(cmdBuffer,
VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_VERTEX_SHADER_BIT |
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
0, 0, nullptr, 1, &b, 0, nullptr);
EntriesDirty_ = false;
}
}
if(anyAtlasWrites) {
_transitionImage(cmdBuffer, Atlas_,
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
VK_ACCESS_TRANSFER_WRITE_BIT,
VK_ACCESS_SHADER_READ_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT);
} else if(Atlas_.Layout == VK_IMAGE_LAYOUT_UNDEFINED) {
_transitionImage(cmdBuffer, Atlas_,
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
0, VK_ACCESS_SHADER_READ_BIT,
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT);
}
if(anyRepackWrites) {
_transitionImage(cmdBuffer, Repack_.Atlas,
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
VK_ACCESS_TRANSFER_WRITE_BIT,
VK_ACCESS_SHADER_READ_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT);
}
if(Repack_.Active) {
if(Repack_.Pending.empty()) {
Repack_.WaitingGpuForReady = true;
}
Repack_.WroteSomethingThisFlush = false;
}
return _buildDescriptorOut();
}
void TextureAtlas::notifyGpuFinished() {
_ensureAliveOrThrow();
for(auto& img : DeferredImages_) {
_destroyImage(img);
}
DeferredImages_.clear();
if(Staging_) {
Staging_->Reset();
}
FlushEventMask_ = 0;
if(Repack_.Active && Repack_.WaitingGpuForReady && Repack_.Pending.empty()) {
Repack_.SwapReady = true;
Repack_.WaitingGpuForReady = false;
}
}
void TextureAtlas::_moveFrom(TextureAtlas&& other) noexcept {
Device_ = other.Device_;
Phys_ = other.Phys_;
Cfg_ = other.Cfg_;
OnEvent_ = std::move(other.OnEvent_);
Alive_ = other.Alive_;
CopyOffsetAlignment_ = other.CopyOffsetAlignment_;
Staging_ = std::move(other.Staging_);
Entries_ = other.Entries_;
Atlas_ = other.Atlas_;
Sampler_ = other.Sampler_;
OwnsSampler_ = other.OwnsSampler_;
EntriesCpu_ = std::move(other.EntriesCpu_);
EntriesDirty_ = other.EntriesDirty_;
Slots_ = std::move(other.Slots_);
FreeIds_ = std::move(other.FreeIds_);
NextId_ = other.NextId_;
Pending_ = std::move(other.Pending_);
PendingInQueue_ = std::move(other.PendingInQueue_);
Packers_ = std::move(other.Packers_);
DeferredImages_ = std::move(other.DeferredImages_);
FlushEventMask_ = other.FlushEventMask_;
GrewThisFlush_ = other.GrewThisFlush_;
Repack_ = std::move(other.Repack_);
other.Device_ = VK_NULL_HANDLE;
other.Phys_ = VK_NULL_HANDLE;
other.OnEvent_ = {};
other.Alive_ = false;
other.CopyOffsetAlignment_ = 0;
other.Staging_.reset();
other.Entries_ = {};
other.Atlas_ = {};
other.Sampler_ = VK_NULL_HANDLE;
other.OwnsSampler_ = false;
other.EntriesCpu_.clear();
other.EntriesDirty_ = false;
other.Slots_.clear();
other.FreeIds_.clear();
other.NextId_ = 0;
other.Pending_.clear();
other.PendingInQueue_.clear();
other.Packers_.clear();
other.DeferredImages_.clear();
other.FlushEventMask_ = 0;
other.GrewThisFlush_ = false;
other.Repack_ = RepackState{};
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,3 +0,0 @@
#pragma once
#include "Common/TexturePipelineProgram.hpp"

View File

@@ -1,364 +0,0 @@
#pragma once
#include "Vulkan.hpp"
#include "Client/Vulkan/AtlasPipeline/SharedStagingBuffer.hpp"
#include <algorithm>
#include <bitset>
#include <cstring>
#include <memory>
#include <optional>
#include <queue>
#include <vector>
#include <vulkan/vulkan_core.h>
namespace LV::Client::VK {
inline std::weak_ptr<SharedStagingBuffer>& globalVertexStaging() {
static std::weak_ptr<SharedStagingBuffer> staging;
return staging;
}
inline std::shared_ptr<SharedStagingBuffer> getOrCreateVertexStaging(Vulkan* inst) {
auto& staging = globalVertexStaging();
std::shared_ptr<SharedStagingBuffer> shared = staging.lock();
if(!shared) {
shared = std::make_shared<SharedStagingBuffer>(
inst->Graphics.Device,
inst->Graphics.PhysicalDevice
);
staging = shared;
}
return shared;
}
inline void resetVertexStaging() {
auto& staging = globalVertexStaging();
if(auto shared = staging.lock())
shared->Reset();
}
/*
Память на устройстве выделяется пулами
Для массивов вершин память выделяется блоками по PerBlock вершин в каждом
Размер пулла sizeof(Vertex)*PerBlock*PerPool
Получаемые вершины сначала пишутся в общий буфер, потом передаются на устройство
*/
// Нужна реализация индексного буфера
template<typename Vertex, uint16_t PerBlock = 1 << 10, uint16_t PerPool = 1 << 12, bool IsIndex = false>
class VertexPool {
static constexpr size_t HC_Buffer_Size = size_t(PerBlock)*size_t(PerPool);
Vulkan *Inst;
// Память, доступная для обмена с устройством
std::shared_ptr<SharedStagingBuffer> Staging;
VkDeviceSize CopyOffsetAlignment = 4;
struct Pool {
// Память на устройстве
Buffer DeviceBuff;
// Свободные блоки
std::bitset<PerPool> Allocation;
Pool(Vulkan* inst)
: DeviceBuff(inst,
sizeof(Vertex)*size_t(PerBlock)*size_t(PerPool)+4 /* Для vkCmdFillBuffer */,
(IsIndex ? VK_BUFFER_USAGE_INDEX_BUFFER_BIT : VK_BUFFER_USAGE_VERTEX_BUFFER_BIT) | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)
{
Allocation.set();
}
};
std::vector<Pool> Pools;
struct Task {
std::vector<Vertex> Data;
uint8_t PoolId; // Куда потом направить
uint16_t BlockId; // И в какой блок
};
/*
Перед следующим обновлением буфер общения заполняется с начала и до конца
Если место закончится, будет дослано в следующем обновлении
*/
std::queue<Task> TasksWait, TasksPostponed;
private:
void pushData(std::vector<Vertex>&& data, uint8_t poolId, uint16_t blockId) {
TasksWait.push({std::move(data), poolId, blockId});
}
public:
VertexPool(Vulkan* inst)
: Inst(inst)
{
Pools.reserve(16);
Staging = getOrCreateVertexStaging(inst);
VkPhysicalDeviceProperties props{};
vkGetPhysicalDeviceProperties(inst->Graphics.PhysicalDevice, &props);
CopyOffsetAlignment = std::max<VkDeviceSize>(4, props.limits.optimalBufferCopyOffsetAlignment);
}
~VertexPool() {
}
struct Pointer {
uint32_t PoolId : 8, BlockId : 16, VertexCount = 0;
operator bool() const { return VertexCount; }
};
/*
Переносит вершины на устройство, заранее передаёт указатель на область в памяти
Надеемся что к следующему кадру данные будут переданы
*/
Pointer pushVertexs(std::vector<Vertex>&& data) {
if(data.empty())
return {0, 0, 0};
// Необходимое количество блоков
uint16_t blocks = (data.size()+PerBlock-1) / PerBlock;
assert(blocks <= PerPool);
// Нужно найти пулл в котором будет свободно blocks количество блоков или создать новый
for(size_t iterPool = 0; iterPool < Pools.size(); iterPool++) {
Pool &pool = Pools[iterPool];
size_t pos = pool.Allocation._Find_first();
if(pos == PerPool)
continue;
while(true) {
int countEmpty = 1;
for(size_t pos2 = pos+1; pos2 < PerPool && pool.Allocation.test(pos2) && countEmpty < blocks; pos2++, countEmpty++);
if(countEmpty == blocks) {
for(int block = 0; block < blocks; block++) {
pool.Allocation.reset(pos+block);
}
size_t count = data.size();
pushData(std::move(data), iterPool, pos);
return Pointer(iterPool, pos, count);
}
pos += countEmpty;
if(pos >= PerPool)
break;
pos = pool.Allocation._Find_next(pos+countEmpty);
}
}
// Не нашлось подходящего пула, создаём новый
assert(Pools.size() < 256);
Pools.emplace_back(Inst);
Pool &last = Pools.back();
// vkCmdFillBuffer(nullptr, last.DeviceBuff, 0, last.DeviceBuff.getSize() & ~0x3, 0);
for(int block = 0; block < blocks; block++)
last.Allocation.reset(block);
size_t count = data.size();
pushData(std::move(data), Pools.size()-1, 0);
return Pointer(Pools.size()-1, 0, count);
}
/*
Освобождает указатель
*/
void dropVertexs(const Pointer &pointer) {
if(!pointer)
return;
assert(pointer.PoolId < Pools.size());
assert(pointer.BlockId < PerPool);
Pool &pool = Pools[pointer.PoolId];
int blocks = (pointer.VertexCount+PerBlock-1) / PerBlock;
for(int indexBlock = 0; indexBlock < blocks; indexBlock++) {
assert(!pool.Allocation.test(pointer.BlockId+indexBlock));
pool.Allocation.set(pointer.BlockId+indexBlock);
}
}
void dropVertexs(Pointer &pointer) {
dropVertexs(const_cast<const Pointer&>(pointer));
pointer.VertexCount = 0;
}
/*
Перевыделяет память под новые данные
*/
void relocate(Pointer& pointer, std::vector<Vertex>&& data) {
if(data.empty()) {
if(!pointer)
return;
else {
dropVertexs(pointer);
pointer.VertexCount = 0;
}
} else if(!pointer) {
pointer = pushVertexs(std::move(data));
} else {
int needBlocks = (data.size()+PerBlock-1) / PerBlock;
if((pointer.VertexCount+PerBlock-1) / PerBlock == needBlocks) {
pointer.VertexCount = data.size();
pushData(std::move(data), pointer.PoolId, pointer.BlockId);
} else {
dropVertexs(pointer);
pointer = pushVertexs(std::move(data));
}
}
}
/*
Транслирует локальный указатель в буффер и позицию вершины в нём
*/
std::pair<VkBuffer, int> map(const Pointer pointer) {
assert(pointer.PoolId < Pools.size());
assert(pointer.BlockId < PerPool);
return {Pools[pointer.PoolId].DeviceBuff.getBuffer(), pointer.BlockId*PerBlock};
}
/*
Должно вызываться после приёма всех данных, до начала рендера в командном буфере
*/
void flushUploadsAndBarriers(VkCommandBuffer commandBuffer) {
if(TasksWait.empty())
return;
struct CopyTask {
VkBuffer DstBuffer;
VkDeviceSize SrcOffset;
VkDeviceSize DstOffset;
VkDeviceSize Size;
uint8_t PoolId;
};
std::vector<CopyTask> copies;
copies.reserve(TasksWait.size());
std::vector<uint8_t> touchedPools(Pools.size(), 0);
while(!TasksWait.empty()) {
Task task = std::move(TasksWait.front());
TasksWait.pop();
VkDeviceSize bytes = task.Data.size()*sizeof(Vertex);
std::optional<VkDeviceSize> stagingOffset = Staging->Allocate(bytes, CopyOffsetAlignment);
if(!stagingOffset) {
TasksPostponed.push(std::move(task));
while(!TasksWait.empty()) {
TasksPostponed.push(std::move(TasksWait.front()));
TasksWait.pop();
}
break;
}
std::memcpy(static_cast<uint8_t*>(Staging->Mapped()) + *stagingOffset,
task.Data.data(), bytes);
copies.push_back({
Pools[task.PoolId].DeviceBuff.getBuffer(),
*stagingOffset,
task.BlockId*sizeof(Vertex)*size_t(PerBlock),
bytes,
task.PoolId
});
touchedPools[task.PoolId] = 1;
}
if(copies.empty())
return;
VkBufferMemoryBarrier stagingBarrier = {
VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
nullptr,
VK_ACCESS_HOST_WRITE_BIT,
VK_ACCESS_TRANSFER_READ_BIT,
VK_QUEUE_FAMILY_IGNORED,
VK_QUEUE_FAMILY_IGNORED,
Staging->Buffer(),
0,
Staging->Size()
};
vkCmdPipelineBarrier(
commandBuffer,
VK_PIPELINE_STAGE_HOST_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT,
0,
0, nullptr,
1, &stagingBarrier,
0, nullptr
);
for(const CopyTask& copy : copies) {
VkBufferCopy copyRegion {
copy.SrcOffset,
copy.DstOffset,
copy.Size
};
assert(copyRegion.dstOffset+copyRegion.size <= Pools[copy.PoolId].DeviceBuff.getSize());
vkCmdCopyBuffer(commandBuffer, Staging->Buffer(), copy.DstBuffer, 1, &copyRegion);
}
std::vector<VkBufferMemoryBarrier> dstBarriers;
dstBarriers.reserve(Pools.size());
for(size_t poolId = 0; poolId < Pools.size(); poolId++) {
if(!touchedPools[poolId])
continue;
VkBufferMemoryBarrier barrier = {
VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
nullptr,
VK_ACCESS_TRANSFER_WRITE_BIT,
IsIndex ? VK_ACCESS_INDEX_READ_BIT : VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT,
VK_QUEUE_FAMILY_IGNORED,
VK_QUEUE_FAMILY_IGNORED,
Pools[poolId].DeviceBuff.getBuffer(),
0,
Pools[poolId].DeviceBuff.getSize()
};
dstBarriers.push_back(barrier);
}
if(!dstBarriers.empty()) {
vkCmdPipelineBarrier(
commandBuffer,
VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_VERTEX_INPUT_BIT,
0,
0, nullptr,
static_cast<uint32_t>(dstBarriers.size()),
dstBarriers.data(),
0, nullptr
);
}
}
void notifyGpuFinished() {
std::queue<Task> postponed = std::move(TasksPostponed);
while(!postponed.empty()) {
TasksWait.push(std::move(postponed.front()));
postponed.pop();
}
}
};
template<typename Type, uint16_t PerBlock = 1 << 10, uint16_t PerPool = 1 << 12>
using IndexPool = VertexPool<Type, PerBlock, PerPool, true>;
}

View File

@@ -1,5 +1,4 @@
#include <boost/asio/io_context.hpp> #include <boost/asio/io_context.hpp>
#include <chrono>
#include <filesystem> #include <filesystem>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
@@ -8,7 +7,6 @@
#include "Client/ServerSession.hpp" #include "Client/ServerSession.hpp"
#include "Common/Async.hpp" #include "Common/Async.hpp"
#include "Common/Net.hpp" #include "Common/Net.hpp"
#include "TOSLib.hpp"
#include "assets.hpp" #include "assets.hpp"
#include "imgui.h" #include "imgui.h"
#include <GLFW/glfw3.h> #include <GLFW/glfw3.h>
@@ -34,27 +32,12 @@ extern void LoadSymbolsVulkan(TOS::DynamicLibrary &library);
namespace LV::Client::VK { namespace LV::Client::VK {
static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(
VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
VkDebugUtilsMessageTypeFlagsEXT messageType,
const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
void* pUserData)
{
std::cerr << "Validation Layer: " << pCallbackData->pMessage << std::endl;
if("Copying old device 0 into new device 0" == std::string(pCallbackData->pMessage)) {
return VK_FALSE;
}
return VK_FALSE;
}
struct ServerObj { struct ServerObj {
Server::GameServer GS; Server::GameServer GS;
Net::SocketServer LS; Net::SocketServer LS;
ServerObj(asio::io_context &ioc) ServerObj(asio::io_context &ioc)
: GS(ioc, "worlds/test/"), LS(ioc, [&](tcp::socket sock) -> coro<> { co_await GS.pushSocketConnect(std::move(sock)); }, 7890) : GS(ioc, ""), LS(ioc, [&](tcp::socket sock) -> coro<> { co_await GS.pushSocketConnect(std::move(sock)); }, 7890)
{ {
} }
}; };
@@ -90,7 +73,7 @@ ByteBuffer loadPNG(std::istream &&read, int &width, int &height, bool &hasAlpha,
} }
Vulkan::Vulkan(asio::io_context &ioc) Vulkan::Vulkan(asio::io_context &ioc)
: AsyncObject(ioc), GuardLock(ioc.get_executor()) : IOC(ioc), GuardLock(ioc.get_executor())
{ {
Screen.Width = 1920/2; Screen.Width = 1920/2;
Screen.Height = 1080/2; Screen.Height = 1080/2;
@@ -110,16 +93,6 @@ Vulkan::Vulkan(asio::io_context &ioc)
LOG.error() << "Vulkan::run: " << exc.what(); LOG.error() << "Vulkan::run: " << exc.what();
} }
try {
if(Graphics.Window)
glfwSetWindowAttrib(Graphics.Window, GLFW_VISIBLE, false);
} catch(...) {}
try {
if(Game.RSession)
Game.RSession->pushStage(EnumRenderStage::Shutdown);
} catch(...) {}
try { Game.RSession = nullptr; } catch(const std::exception &exc) { try { Game.RSession = nullptr; } catch(const std::exception &exc) {
LOG.error() << "Game.RSession = nullptr: " << exc.what(); LOG.error() << "Game.RSession = nullptr: " << exc.what();
} }
@@ -159,31 +132,12 @@ void Vulkan::run()
NeedShutdown = false; NeedShutdown = false;
Graphics.ThisThread = std::this_thread::get_id(); Graphics.ThisThread = std::this_thread::get_id();
VkSemaphoreCreateInfo semaphoreCreateInfo = { VkSemaphoreCreateInfo semaphoreCreateInfo = { VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, nullptr, 0 };
VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, VkSemaphore SemaphoreImageAcquired, SemaphoreDrawComplete;
nullptr,
0
};
VkSemaphore SemaphoreImageAcquired[4], SemaphoreDrawComplete[4]; vkAssert(!vkCreateSemaphore(Graphics.Device, &semaphoreCreateInfo, NULL, &SemaphoreImageAcquired));
int semNext = 0; vkAssert(!vkCreateSemaphore(Graphics.Device, &semaphoreCreateInfo, NULL, &SemaphoreDrawComplete));
for(int iter = 0; iter < 4; iter++) {
vkAssert(!vkCreateSemaphore(Graphics.Device, &semaphoreCreateInfo, nullptr, &SemaphoreImageAcquired[iter]));
vkAssert(!vkCreateSemaphore(Graphics.Device, &semaphoreCreateInfo, nullptr, &SemaphoreDrawComplete[iter]));
}
VkFence drawEndFence = VK_NULL_HANDLE;
{
VkFenceCreateInfo info = {
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
.pNext = nullptr,
.flags = 0
};
vkCreateFence(Graphics.Device, &info, nullptr, &drawEndFence);
}
double prevTime = glfwGetTime(); double prevTime = glfwGetTime();
while(!NeedShutdown) while(!NeedShutdown)
@@ -192,8 +146,6 @@ void Vulkan::run()
prevTime += dTime; prevTime += dTime;
Screen.State = DrawState::Begin; Screen.State = DrawState::Begin;
/// TODO: Нужно синхронизировать с vkQueue
{ {
std::lock_guard lock(Screen.BeforeDrawMtx); std::lock_guard lock(Screen.BeforeDrawMtx);
while(!Screen.BeforeDraw.empty()) while(!Screen.BeforeDraw.empty())
@@ -203,44 +155,12 @@ void Vulkan::run()
} }
} }
if(Game.Выйти) {
Game.Выйти = false;
try {
if(Game.Session)
Game.Session->setRenderSession(nullptr);
if(Game.RSession)
Game.RSession = nullptr;
} catch(const std::exception &exc) {
LOG.error() << "Game.RSession->shutdown: " << exc.what();
}
try {
if(Game.Session)
Game.Session->shutdown(EnumDisconnect::ByInterface);
} catch(const std::exception &exc) {
LOG.error() << "Game.Session->shutdown: " << exc.what();
}
}
if(!NeedShutdown && glfwWindowShouldClose(Graphics.Window)) { if(!NeedShutdown && glfwWindowShouldClose(Graphics.Window)) {
NeedShutdown = true; NeedShutdown = true;
try {
if(Game.Session)
Game.Session->setRenderSession(nullptr);
if(Game.RSession)
Game.RSession = nullptr;
} catch(const std::exception &exc) {
LOG.error() << "Game.RSession->shutdown: " << exc.what();
}
try { try {
if(Game.Session) if(Game.Session)
Game.Session->shutdown(EnumDisconnect::ByInterface); Game.Session->shutdown(EnumDisconnect::ByInterface);
Game.Session = nullptr;
} catch(const std::exception &exc) { } catch(const std::exception &exc) {
LOG.error() << "Game.Session->shutdown: " << exc.what(); LOG.error() << "Game.Session->shutdown: " << exc.what();
} }
@@ -251,8 +171,6 @@ void Vulkan::run()
} catch(const std::exception &exc) { } catch(const std::exception &exc) {
LOG.error() << "Game.Server->GS.shutdown: " << exc.what(); LOG.error() << "Game.Server->GS.shutdown: " << exc.what();
} }
continue;
} }
if(Game.Session) { if(Game.Session) {
@@ -275,26 +193,25 @@ void Vulkan::run()
// if(CallBeforeDraw) // if(CallBeforeDraw)
// CallBeforeDraw(this); // CallBeforeDraw(this);
if(Game.RSession) {
Game.RSession->beforeDraw();
}
glfwPollEvents(); glfwPollEvents();
VkResult err; VkResult err;
semNext = ++semNext % 4; err = vkAcquireNextImageKHR(Graphics.Device, Graphics.Swapchain, 1000000000ULL/20, SemaphoreImageAcquired, (VkFence) 0, &Graphics.DrawBufferCurrent);
err = vkAcquireNextImageKHR(Graphics.Device, Graphics.Swapchain, 1000000000ULL/20, SemaphoreImageAcquired[semNext], (VkFence) 0, &Graphics.DrawBufferCurrent);
GlobalTime gTime = glfwGetTime(); GlobalTime gTime = glfwGetTime();
if(err == VK_ERROR_OUT_OF_DATE_KHR) if (err == VK_ERROR_OUT_OF_DATE_KHR)
{ {
if(Game.RSession)
Game.RSession->pushStage(EnumRenderStage::WorldUpdate);
freeSwapchains(); freeSwapchains();
buildSwapchains(); buildSwapchains();
continue; continue;
// } else if (err == VK_SUBOPTIMAL_KHR) } else if (err == VK_SUBOPTIMAL_KHR)
// { {
// LOGGER.debug() << "VK_SUBOPTIMAL_KHR Pre"; LOGGER.debug() << "VK_SUBOPTIMAL_KHR Pre";
// continue; } else if(err == VK_SUCCESS) {
} else if(err == VK_SUBOPTIMAL_KHR || err == VK_SUCCESS) {
Screen.State = DrawState::Drawing; Screen.State = DrawState::Drawing;
//Готовим инструкции рисовки //Готовим инструкции рисовки
@@ -303,17 +220,13 @@ void Vulkan::run()
{ {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
.pNext = nullptr, .pNext = nullptr,
.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, .flags = 0,
.pInheritanceInfo = nullptr .pInheritanceInfo = nullptr
}; };
vkAssert(!vkBeginCommandBuffer(Graphics.CommandBufferRender, &cmd_buf_info)); vkAssert(!vkBeginCommandBuffer(Graphics.CommandBufferRender, &cmd_buf_info));
} }
if(Game.RSession) {
Game.RSession->beforeDraw(double(gTime));
}
{ {
VkImageMemoryBarrier image_memory_barrier = VkImageMemoryBarrier image_memory_barrier =
{ {
@@ -532,15 +445,10 @@ void Vulkan::run()
// vkCmdBeginRenderPass(Graphics.CommandBufferRender, &rp_begin, VK_SUBPASS_CONTENTS_INLINE); // vkCmdBeginRenderPass(Graphics.CommandBufferRender, &rp_begin, VK_SUBPASS_CONTENTS_INLINE);
// } // }
if(Game.RSession)
Game.RSession->pushStage(EnumRenderStage::Render);
#ifdef HAS_IMGUI #ifdef HAS_IMGUI
{ ImGui_ImplVulkan_NewFrame();
auto lockQueue = Graphics.DeviceQueueGraphic.lock();
ImGui_ImplVulkan_NewFrame();
lockQueue.unlock();
}
ImGui_ImplGlfw_NewFrame(); ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame(); ImGui::NewFrame();
@@ -585,25 +493,16 @@ void Vulkan::run()
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
.pNext = nullptr, .pNext = nullptr,
.waitSemaphoreCount = 1, .waitSemaphoreCount = 1,
.pWaitSemaphores = &SemaphoreImageAcquired[semNext], .pWaitSemaphores = &SemaphoreImageAcquired,
.pWaitDstStageMask = &pipe_stage_flags, .pWaitDstStageMask = &pipe_stage_flags,
.commandBufferCount = 1, .commandBufferCount = 1,
.pCommandBuffers = &Graphics.CommandBufferRender, .pCommandBuffers = &Graphics.CommandBufferRender,
.signalSemaphoreCount = 1, .signalSemaphoreCount = 1,
.pSignalSemaphores = &SemaphoreDrawComplete[semNext] .pSignalSemaphores = &SemaphoreDrawComplete
}; };
// Отправляем команды рендера в очередь //Рисуем, когда получим картинку
{ vkAssert(!vkQueueSubmit(Graphics.DeviceQueueGraphic, 1, &submit_info, nullFence));
auto lockQueue = Graphics.DeviceQueueGraphic.lock();
vkAssert(!vkQueueSubmit(*lockQueue, 1, &submit_info, drawEndFence));
}
// Насильно ожидаем завершения рендера кадра
vkWaitForFences(Graphics.Device, 1, &drawEndFence, true, -1);
vkResetFences(Graphics.Device, 1, &drawEndFence);
if(Game.RSession)
Game.RSession->onGpuFinished();
} }
{ {
@@ -612,18 +511,14 @@ void Vulkan::run()
.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
.pNext = NULL, .pNext = NULL,
.waitSemaphoreCount = 1, .waitSemaphoreCount = 1,
.pWaitSemaphores = &SemaphoreDrawComplete[semNext], .pWaitSemaphores = &SemaphoreDrawComplete,
.swapchainCount = 1, .swapchainCount = 1,
.pSwapchains = &Graphics.Swapchain, .pSwapchains = &Graphics.Swapchain,
.pImageIndices = &Graphics.DrawBufferCurrent .pImageIndices = &Graphics.DrawBufferCurrent
}; };
// Передадим фрейм, когда рендер будет завершён // Завершаем картинку
{ err = vkQueuePresentKHR(Graphics.DeviceQueueGraphic, &present);
auto lockQueue = Graphics.DeviceQueueGraphic.lock();
err = vkQueuePresentKHR(*lockQueue, &present);
}
if (err == VK_ERROR_OUT_OF_DATE_KHR) if (err == VK_ERROR_OUT_OF_DATE_KHR)
{ {
freeSwapchains(); freeSwapchains();
@@ -637,20 +532,17 @@ void Vulkan::run()
vkAssert(err == VK_TIMEOUT); vkAssert(err == VK_TIMEOUT);
if(Game.Session) { if(Game.Session) {
if(Game.RSession) Game.Session->atFreeDrawTime(gTime, dTime);
Game.RSession->pushStage(EnumRenderStage::WorldUpdate);
Game.Session->update(gTime, dTime);
} }
vkAssert(!vkQueueWaitIdle(Graphics.DeviceQueueGraphic));
vkDeviceWaitIdle(Graphics.Device);
Screen.State = DrawState::End; Screen.State = DrawState::End;
} }
for(int iter = 0; iter < 4; iter++) { vkDestroySemaphore(Graphics.Device, SemaphoreImageAcquired, nullptr);
vkDestroySemaphore(Graphics.Device, SemaphoreImageAcquired[iter], nullptr); vkDestroySemaphore(Graphics.Device, SemaphoreDrawComplete, nullptr);
vkDestroySemaphore(Graphics.Device, SemaphoreDrawComplete[iter], nullptr);
}
vkDestroyFence(Graphics.Device, drawEndFence, nullptr);
} }
void Vulkan::glfwCallbackError(int error, const char *description) void Vulkan::glfwCallbackError(int error, const char *description)
@@ -676,6 +568,8 @@ uint32_t Vulkan::memoryTypeFromProperties(uint32_t bitsOfAcceptableTypes, VkFlag
void Vulkan::freeSwapchains() void Vulkan::freeSwapchains()
{ {
//vkDeviceWaitIdle(Screen.Device);
if(Graphics.Instance && Graphics.Device) if(Graphics.Instance && Graphics.Device)
{ {
std::vector<VkImageView> oldViews; std::vector<VkImageView> oldViews;
@@ -832,8 +726,8 @@ void Vulkan::buildSwapchains()
.imageColorSpace = Graphics.SurfaceColorSpace, .imageColorSpace = Graphics.SurfaceColorSpace,
.imageExtent = swapchainExtent, .imageExtent = swapchainExtent,
.imageArrayLayers = 1, .imageArrayLayers = 1,
.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT
// | VK_IMAGE_USAGE_TRANSFER_DST_BIT, | VK_IMAGE_USAGE_TRANSFER_DST_BIT,
.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE, .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE,
.queueFamilyIndexCount = 0, .queueFamilyIndexCount = 0,
.pQueueFamilyIndices = nullptr, .pQueueFamilyIndices = nullptr,
@@ -1285,11 +1179,7 @@ void Vulkan::checkLibrary()
uint32_t count = -1; uint32_t count = -1;
VkResult res; VkResult res;
VkResult errc = vkEnumeratePhysicalDevices(localInstance.getInstance(), &count, nullptr); vkAssert(!vkEnumeratePhysicalDevices(localInstance.getInstance(), &count, nullptr));
if(errc != VK_SUCCESS && errc != VK_INCOMPLETE) {
error << "vkEnumeratePhysicalDevices не смог обработать запрос (ошибка драйвера?)\n";
goto onError;
}
if(!count) if(!count)
{ {
@@ -1568,7 +1458,6 @@ void Vulkan::initNextSettings()
freeSwapchains(); freeSwapchains();
} }
bool hasVK_EXT_debug_utils = false;
if(!Graphics.Instance) if(!Graphics.Instance)
{ {
std::vector<std::string_view> knownDebugLayers = std::vector<std::string_view> knownDebugLayers =
@@ -1585,7 +1474,7 @@ void Vulkan::initNextSettings()
"VK_LAYER_LUNARG_monitor" "VK_LAYER_LUNARG_monitor"
}; };
if(!SettingsNext.Debug || getenv("no_vk_debug")) if(!SettingsNext.Debug)
knownDebugLayers.clear(); knownDebugLayers.clear();
std::vector<vkInstanceLayer> enableDebugLayers; std::vector<vkInstanceLayer> enableDebugLayers;
@@ -1598,38 +1487,7 @@ void Vulkan::initNextSettings()
break; break;
} }
std::vector<vkInstanceExtension> enableExtension; Graphics.Instance.emplace(this, enableDebugLayers);
if(SettingsNext.Debug)
for(const vkInstanceExtension &ext : Graphics.InstanceExtensions) {
if(ext.ExtensionName == "VK_EXT_debug_utils") {
enableExtension.push_back(ext);
hasVK_EXT_debug_utils = true;
break;
}
}
Graphics.Instance.emplace(this, enableDebugLayers, enableExtension);
}
if(hasVK_EXT_debug_utils) {
VkDebugUtilsMessengerCreateInfoEXT createInfo = {
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT,
.pNext = nullptr,
.flags = 0,
.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT,
.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT,
.pfnUserCallback = &debugCallback,
.pUserData = nullptr
};
VkDebugUtilsMessengerEXT debugMessenger;
PFN_vkCreateDebugUtilsMessengerEXT myvkCreateDebugUtilsMessengerEXT = reinterpret_cast<PFN_vkCreateDebugUtilsMessengerEXT>(vkGetInstanceProcAddr(Graphics.Instance->getInstance(), "vkCreateDebugUtilsMessengerEXT"));
myvkCreateDebugUtilsMessengerEXT(Graphics.Instance->getInstance(), &createInfo, nullptr, &debugMessenger);
LOG.debug() << "Добавлен обработчик логов";
} }
if(!Graphics.Surface) if(!Graphics.Surface)
@@ -1699,8 +1557,8 @@ void Vulkan::initNextSettings()
features.features.geometryShader = true; features.features.geometryShader = true;
feat11.uniformAndStorageBuffer16BitAccess = false; feat11.uniformAndStorageBuffer16BitAccess = true;
feat11.storageBuffer16BitAccess = false; feat11.storageBuffer16BitAccess = true;
VkDeviceCreateInfo infoDevice = VkDeviceCreateInfo infoDevice =
{ {
@@ -1717,8 +1575,7 @@ void Vulkan::initNextSettings()
}; };
vkAssert(!vkCreateDevice(Graphics.PhysicalDevice, &infoDevice, nullptr, &Graphics.Device)); vkAssert(!vkCreateDevice(Graphics.PhysicalDevice, &infoDevice, nullptr, &Graphics.Device));
auto lockQueue = Graphics.DeviceQueueGraphic.lock(); vkGetDeviceQueue(Graphics.Device, SettingsNext.QueueGraphics, 0, &Graphics.DeviceQueueGraphic);
vkGetDeviceQueue(Graphics.Device, SettingsNext.QueueGraphics, 0, &*lockQueue);
} }
// Определяемся с форматом экранного буфера // Определяемся с форматом экранного буфера
@@ -1768,7 +1625,7 @@ void Vulkan::initNextSettings()
{ {
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
.pNext = nullptr, .pNext = nullptr,
.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT | VK_COMMAND_POOL_CREATE_TRANSIENT_BIT, .flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
.queueFamilyIndex = SettingsNext.QueueGraphics .queueFamilyIndex = SettingsNext.QueueGraphics
}; };
@@ -1945,14 +1802,13 @@ void Vulkan::initNextSettings()
ImGui_ImplGlfw_InitForVulkan(Graphics.Window, true); ImGui_ImplGlfw_InitForVulkan(Graphics.Window, true);
auto lockQueue = Graphics.DeviceQueueGraphic.lock();
ImGui_ImplVulkan_InitInfo ImGuiInfo = ImGui_ImplVulkan_InitInfo ImGuiInfo =
{ {
.Instance = Graphics.Instance->getInstance(), .Instance = Graphics.Instance->getInstance(),
.PhysicalDevice = Graphics.PhysicalDevice, .PhysicalDevice = Graphics.PhysicalDevice,
.Device = Graphics.Device, .Device = Graphics.Device,
.QueueFamily = SettingsNext.QueueGraphics, .QueueFamily = SettingsNext.QueueGraphics,
.Queue = *lockQueue, .Queue = Graphics.DeviceQueueGraphic,
.DescriptorPool = Graphics.ImGuiDescPool, .DescriptorPool = Graphics.ImGuiDescPool,
.RenderPass = Graphics.RenderPass, .RenderPass = Graphics.RenderPass,
.MinImageCount = Graphics.DrawBufferCount, .MinImageCount = Graphics.DrawBufferCount,
@@ -1968,7 +1824,6 @@ void Vulkan::initNextSettings()
}; };
ImGui_ImplVulkan_Init(&ImGuiInfo); ImGui_ImplVulkan_Init(&ImGuiInfo);
lockQueue.unlock();
// ImFontConfig fontConfig; // ImFontConfig fontConfig;
// fontConfig.MergeMode = false; // fontConfig.MergeMode = false;
@@ -2072,7 +1927,7 @@ void Vulkan::deInitVulkan()
Graphics.RenderPass = nullptr; Graphics.RenderPass = nullptr;
Graphics.Device = nullptr; Graphics.Device = nullptr;
Graphics.PhysicalDevice = nullptr; Graphics.PhysicalDevice = nullptr;
*Graphics.DeviceQueueGraphic.lock() = nullptr; Graphics.DeviceQueueGraphic = nullptr;
Graphics.Surface = nullptr; Graphics.Surface = nullptr;
Graphics.Instance.reset(); Graphics.Instance.reset();
} }
@@ -2096,10 +1951,8 @@ void Vulkan::flushCommandBufferData()
.pSignalSemaphores = nullptr .pSignalSemaphores = nullptr
}; };
auto lockQueue = Graphics.DeviceQueueGraphic.lock(); vkAssert(!vkQueueSubmit(Graphics.DeviceQueueGraphic, 1, &submit_info, nullFence));
vkAssert(!vkQueueSubmit(*lockQueue, 1, &submit_info, nullFence)); vkAssert(!vkQueueWaitIdle(Graphics.DeviceQueueGraphic));
vkAssert(!vkQueueWaitIdle(*lockQueue));
lockQueue.unlock();
VkCommandBufferBeginInfo infoCmdBuffer = VkCommandBufferBeginInfo infoCmdBuffer =
{ {
@@ -2217,23 +2070,12 @@ void Vulkan::gui_MainMenu() {
ImGui::InputText("Username", ConnectionProgress.Username, sizeof(ConnectionProgress.Username)); ImGui::InputText("Username", ConnectionProgress.Username, sizeof(ConnectionProgress.Username));
ImGui::InputText("Password", ConnectionProgress.Password, sizeof(ConnectionProgress.Password), ImGuiInputTextFlags_Password); ImGui::InputText("Password", ConnectionProgress.Password, sizeof(ConnectionProgress.Password), ImGuiInputTextFlags_Password);
static bool flag = true;
if(!flag) {
flag = true;
Game.Server = std::make_unique<ServerObj>(IOC);
ConnectionProgress.Progress = "Сервер запущен на порту " + std::to_string(Game.Server->LS.getPort());
ConnectionProgress.InProgress = true;
ConnectionProgress.Cancel = false;
ConnectionProgress.Progress.clear();
co_spawn(ConnectionProgress.connect(IOC));
}
if(!ConnectionProgress.InProgress && !ConnectionProgress.Socket) { if(!ConnectionProgress.InProgress && !ConnectionProgress.Socket) {
if(ImGui::Button("Подключиться")) { if(ImGui::Button("Подключиться")) {
ConnectionProgress.InProgress = true; ConnectionProgress.InProgress = true;
ConnectionProgress.Cancel = false; ConnectionProgress.Cancel = false;
ConnectionProgress.Progress.clear(); ConnectionProgress.Progress.clear();
co_spawn(ConnectionProgress.connect(IOC)); asio::co_spawn(IOC, ConnectionProgress.connect(IOC), asio::detached);
} }
if(!Game.Server) { if(!Game.Server) {
@@ -2268,9 +2110,10 @@ void Vulkan::gui_MainMenu() {
if(ConnectionProgress.Socket) { if(ConnectionProgress.Socket) {
std::unique_ptr<Net::AsyncSocket> sock = std::move(ConnectionProgress.Socket); std::unique_ptr<Net::AsyncSocket> sock = std::move(ConnectionProgress.Socket);
Game.Session = ServerSession::Create(IOC, std::move(sock)); Game.RSession = std::make_unique<VulkanRenderSession>();
Game.RSession = std::make_unique<VulkanRenderSession>(this, Game.Session.get()); *this << Game.RSession;
Game.Session->setRenderSession(Game.RSession.get()); Game.Session = std::make_unique<ServerSession>(IOC, std::move(sock), Game.RSession.get());
Game.RSession->setServerSession(Game.Session.get());
Game.ImGuiInterfaces.push_back(&Vulkan::gui_ConnectedToServer); Game.ImGuiInterfaces.push_back(&Vulkan::gui_ConnectedToServer);
} }
@@ -2279,42 +2122,10 @@ void Vulkan::gui_MainMenu() {
} }
void Vulkan::gui_ConnectedToServer() { void Vulkan::gui_ConnectedToServer() {
if(Game.Session && Game.RSession) { if(Game.Session) {
if(ImGui::Begin("MainMenu", nullptr, ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove))
{
ImGui::Text("fps: %2.2f World: %u Pos: %i %i %i Region: %i %i %i",
ImGui::GetIO().Framerate, Game.RSession->WI,
(int) Game.RSession->PlayerPos.x, (int) Game.RSession->PlayerPos.y, (int) Game.RSession->PlayerPos.z,
(int) Game.RSession->PlayerPos.x >> 6, (int) Game.RSession->PlayerPos.y >> 6, (int) Game.RSession->PlayerPos.z >> 6
);
double chunksKb = double(Game.Session->getVisibleCompressedChunksBytes()) / 1024.0;
ImGui::Text("chunks compressed: %.1f KB", chunksKb);
ImGui::Checkbox("Логи сетевых пакетов", &Game.Session->DebugLogPackets);
if(ImGui::Button("Delimeter"))
LOG.debug();
if(ImGui::Button("Перезагрузить моды")) {
Game.Session->requestModsReload();
}
if(ImGui::Button("Выйти")) {
Game.Выйти = true;
Game.ImGuiInterfaces.pop_back();
}
ImGui::End();
if(Game.Выйти)
return;
}
if(Game.Session->isConnected()) if(Game.Session->isConnected())
return; return;
Game.RSession->pushStage(EnumRenderStage::Shutdown);
Game.RSession = nullptr; Game.RSession = nullptr;
Game.Session = nullptr; Game.Session = nullptr;
Game.ImGuiInterfaces.pop_back(); Game.ImGuiInterfaces.pop_back();
@@ -2824,7 +2635,6 @@ Buffer::Buffer(Vulkan *instance, VkDeviceSize bufferSize, VkBufferUsageFlags usa
vkAllocateMemory(instance->Graphics.Device, &memAlloc, nullptr, &Memory); vkAllocateMemory(instance->Graphics.Device, &memAlloc, nullptr, &Memory);
if(res) if(res)
MAKE_ERROR("VkHandler: ошибка выделения памяти на устройстве"); MAKE_ERROR("VkHandler: ошибка выделения памяти на устройстве");
assert(Memory);
vkBindBufferMemory(instance->Graphics.Device, Buff, Memory, 0); vkBindBufferMemory(instance->Graphics.Device, Buff, Memory, 0);
} }
@@ -2853,16 +2663,12 @@ Buffer::Buffer(Buffer &&obj)
obj.Instance = nullptr; obj.Instance = nullptr;
} }
Buffer& Buffer::operator=(Buffer &&obj) { Buffer& Buffer::operator=(Buffer &&obj)
if(this == &obj) {
return *this;
std::swap(Instance, obj.Instance); std::swap(Instance, obj.Instance);
std::swap(Buff, obj.Buff); std::swap(Buff, obj.Buff);
std::swap(Memory, obj.Memory); std::swap(Memory, obj.Memory);
std::swap(Size, obj.Size); std::swap(Size, obj.Size);
obj.Instance = nullptr;
return *this; return *this;
} }
@@ -2922,22 +2728,13 @@ CommandBuffer::CommandBuffer(Vulkan *instance)
}; };
vkAssert(!vkBeginCommandBuffer(Buffer, &infoCmdBuffer)); vkAssert(!vkBeginCommandBuffer(Buffer, &infoCmdBuffer));
const VkFenceCreateInfo info = {
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
.pNext = nullptr,
.flags = 0
};
vkAssert(!vkCreateFence(instance->Graphics.Device, &info, nullptr, &Fence));
} }
CommandBuffer::~CommandBuffer() CommandBuffer::~CommandBuffer()
{ {
if(Buffer && Instance && Instance->Graphics.Device) if(Buffer && Instance && Instance->Graphics.Device)
{ {
auto lockQueue = Instance->Graphics.DeviceQueueGraphic.lock(); if(Instance->Graphics.DeviceQueueGraphic)
if(*lockQueue)
{ {
//vkAssert(!vkEndCommandBuffer(Buffer)); //vkAssert(!vkEndCommandBuffer(Buffer));
vkEndCommandBuffer(Buffer); vkEndCommandBuffer(Buffer);
@@ -2957,33 +2754,25 @@ CommandBuffer::~CommandBuffer()
}; };
//vkAssert(!vkQueueSubmit(Instance->Graphics.DeviceQueueGraphic, 1, &submit_info, nullFence)); //vkAssert(!vkQueueSubmit(Instance->Graphics.DeviceQueueGraphic, 1, &submit_info, nullFence));
vkQueueSubmit(*lockQueue, 1, &submit_info, Fence); vkQueueSubmit(Instance->Graphics.DeviceQueueGraphic, 1, &submit_info, nullFence);
lockQueue.unlock();
//vkAssert(!vkQueueWaitIdle(Instance->Graphics.DeviceQueueGraphic)); //vkAssert(!vkQueueWaitIdle(Instance->Graphics.DeviceQueueGraphic));
//vkQueueWaitIdle(Instance->Graphics.DeviceQueueGraphic); vkQueueWaitIdle(Instance->Graphics.DeviceQueueGraphic);
vkWaitForFences(Instance->Graphics.Device, 1, &Fence, VK_TRUE, UINT64_MAX);
vkResetFences(Instance->Graphics.Device, 1, &Fence);
auto toExecute = std::move(AfterExecute); auto toExecute = std::move(AfterExecute);
for(auto &iter : toExecute) for(auto &iter : toExecute)
try { iter(); } catch(const std::exception &exc) { Logger("CommandBuffer").error() << exc.what(); } try { iter(); } catch(const std::exception &exc) { Logger("CommandBuffer").error() << exc.what(); }
} else }
lockQueue.unlock();
vkFreeCommandBuffers(Instance->Graphics.Device, OffthreadPool ? OffthreadPool : Instance->Graphics.Pool, 1, &Buffer); vkFreeCommandBuffers(Instance->Graphics.Device, OffthreadPool ? OffthreadPool : Instance->Graphics.Pool, 1, &Buffer);
if(OffthreadPool) if(OffthreadPool)
vkDestroyCommandPool(Instance->Graphics.Device, OffthreadPool, nullptr); vkDestroyCommandPool(Instance->Graphics.Device, OffthreadPool, nullptr);
if(Fence)
vkDestroyFence(Instance->Graphics.Device, Fence, nullptr);
} }
} }
void CommandBuffer::execute() void CommandBuffer::execute()
{ {
auto lockQueue = Instance->Graphics.DeviceQueueGraphic.lock(); vkAssert(Instance->Graphics.DeviceQueueGraphic);
vkAssert(*lockQueue);
vkAssert(!vkEndCommandBuffer(Buffer)); vkAssert(!vkEndCommandBuffer(Buffer));
const VkCommandBuffer cmd_bufs[] = { Buffer }; const VkCommandBuffer cmd_bufs[] = { Buffer };
@@ -3001,16 +2790,8 @@ void CommandBuffer::execute()
.pSignalSemaphores = nullptr .pSignalSemaphores = nullptr
}; };
// vkAssert(!vkQueueSubmit(Instance->Graphics.DeviceQueueGraphic, 1, &submit_info, nullFence)); vkAssert(!vkQueueSubmit(Instance->Graphics.DeviceQueueGraphic, 1, &submit_info, nullFence));
// vkAssert(!vkQueueWaitIdle(Instance->Graphics.DeviceQueueGraphic)); vkAssert(!vkQueueWaitIdle(Instance->Graphics.DeviceQueueGraphic));
vkAssert(!vkQueueSubmit(*lockQueue, 1, &submit_info, Fence));
lockQueue.unlock();
//vkAssert(!vkQueueWaitIdle(Instance->Graphics.DeviceQueueGraphic));
//vkQueueWaitIdle(Instance->Graphics.DeviceQueueGraphic);
vkAssert(!vkWaitForFences(Instance->Graphics.Device, 1, &Fence, VK_TRUE, UINT64_MAX));
vkAssert(!vkResetFences(Instance->Graphics.Device, 1, &Fence));
VkCommandBufferBeginInfo infoCmdBuffer = VkCommandBufferBeginInfo infoCmdBuffer =
{ {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
@@ -3719,8 +3500,6 @@ void DynamicImage::changeData(int32_t x, int32_t y, uint16_t width, uint16_t hei
// Выполняем все команды // Выполняем все команды
buffer.execute(); buffer.execute();
Time::sleep3(50);
// Удаляем не нужную картинку // Удаляем не нужную картинку
vkDestroyImage(Instance->Graphics.Device, tempImage, nullptr); vkDestroyImage(Instance->Graphics.Device, tempImage, nullptr);
vkFreeMemory(Instance->Graphics.Device, tempMemory, nullptr); vkFreeMemory(Instance->Graphics.Device, tempMemory, nullptr);
@@ -4049,7 +3828,7 @@ void AtlasImage::atlasChangeTextureData(uint16_t id, const uint32_t *rgba)
InfoSubTexture *info = const_cast<InfoSubTexture*>(atlasGetTextureInfo(id)); InfoSubTexture *info = const_cast<InfoSubTexture*>(atlasGetTextureInfo(id));
auto iter = CachedData.find(id); auto iter = CachedData.find(id);
// Если есть данные в кеше, то меняем их // Если есть данные в кэше, то меняем их
if(iter != CachedData.end()) if(iter != CachedData.end())
{ {
if(iter->second.size() == 0) if(iter->second.size() == 0)

View File

@@ -1,10 +1,5 @@
#pragma once #pragma once
// Cmake
// #define GLM_FORCE_DEPTH_ZERO_TO_ONE
#include <glm/ext.hpp>
static_assert(GLM_CONFIG_CLIP_CONTROL == GLM_CLIP_CONTROL_RH_ZO);
#include "Client/ServerSession.hpp" #include "Client/ServerSession.hpp"
#include "Common/Async.hpp" #include "Common/Async.hpp"
#include <TOSLib.hpp> #include <TOSLib.hpp>
@@ -24,12 +19,14 @@ static_assert(GLM_CONFIG_CLIP_CONTROL == GLM_CLIP_CONTROL_RH_ZO);
#include "freetype/freetype.h" #include "freetype/freetype.h"
#include <vulkan/vulkan_core.h> #include <vulkan/vulkan_core.h>
#include <glm/ext.hpp>
#include <map> #include <map>
#define TOS_VULKAN_NO_VIDEO #define TOS_VULKAN_NO_VIDEO
#include <vulkan/vulkan.h> #include <vulkan/vulkan.h>
#define GLFW_INCLUDE_NONE #define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h> #include <GLFW/glfw3.h>
static_assert(GLM_CONFIG_CLIP_CONTROL == GLM_CLIP_CONTROL_RH_ZO);
#define IMGUI_ENABLE_STB_TEXTEDIT_UNICODE #define IMGUI_ENABLE_STB_TEXTEDIT_UNICODE
@@ -74,9 +71,10 @@ class Buffer;
Vulkan.reInit(); Vulkan.reInit();
*/ */
class Vulkan : public AsyncObject { class Vulkan {
private: private:
Logger LOG = "Vulkan"; Logger LOG = "Vulkan";
asio::io_context &IOC;
struct vkInstanceLayer { struct vkInstanceLayer {
std::string LayerName = "nullptr", Description = "nullptr"; std::string LayerName = "nullptr", Description = "nullptr";
@@ -178,7 +176,7 @@ private:
asio::executor_work_guard<asio::io_context::executor_type> GuardLock; asio::executor_work_guard<asio::io_context::executor_type> GuardLock;
public: public:
struct GraphicsObj_t { struct {
std::vector<vkInstanceLayer> InstanceLayers; std::vector<vkInstanceLayer> InstanceLayers;
std::vector<vkInstanceExtension> InstanceExtensions; std::vector<vkInstanceExtension> InstanceExtensions;
std::vector<std::string> GLFWExtensions; std::vector<std::string> GLFWExtensions;
@@ -191,7 +189,7 @@ public:
VkPhysicalDevice PhysicalDevice = nullptr; VkPhysicalDevice PhysicalDevice = nullptr;
VkPhysicalDeviceMemoryProperties DeviceMemoryProperties = {0}; VkPhysicalDeviceMemoryProperties DeviceMemoryProperties = {0};
VkDevice Device = nullptr; VkDevice Device = nullptr;
SpinlockObject<VkQueue> DeviceQueueGraphic; VkQueue DeviceQueueGraphic = VK_NULL_HANDLE;
VkFormat SurfaceFormat = VK_FORMAT_B8G8R8A8_UNORM; VkFormat SurfaceFormat = VK_FORMAT_B8G8R8A8_UNORM;
VkColorSpaceKHR SurfaceColorSpace = VK_COLOR_SPACE_MAX_ENUM_KHR; VkColorSpaceKHR SurfaceColorSpace = VK_COLOR_SPACE_MAX_ENUM_KHR;
@@ -226,10 +224,6 @@ public:
// Идентификатор потока графики // Идентификатор потока графики
std::thread::id ThisThread = std::this_thread::get_id(); std::thread::id ThisThread = std::this_thread::get_id();
GraphicsObj_t()
: DeviceQueueGraphic(VK_NULL_HANDLE)
{}
} Graphics; } Graphics;
enum struct DrawState { enum struct DrawState {
@@ -250,8 +244,7 @@ public:
DestroyLock UseLock; DestroyLock UseLock;
std::thread MainThread; std::thread MainThread;
std::shared_ptr<VulkanRenderSession> RSession; std::shared_ptr<VulkanRenderSession> RSession;
ServerSession::Ptr Session; std::unique_ptr<ServerSession> Session;
bool Выйти = false;
std::list<void (Vulkan::*)()> ImGuiInterfaces; std::list<void (Vulkan::*)()> ImGuiInterfaces;
std::unique_ptr<ServerObj> Server; std::unique_ptr<ServerObj> Server;
@@ -542,7 +535,6 @@ public:
class CommandBuffer { class CommandBuffer {
VkCommandBuffer Buffer = VK_NULL_HANDLE; VkCommandBuffer Buffer = VK_NULL_HANDLE;
Vulkan *Instance; Vulkan *Instance;
VkFence Fence = VK_NULL_HANDLE;
std::vector<std::function<void()>> AfterExecute; std::vector<std::function<void()>> AfterExecute;
VkCommandPool OffthreadPool = VK_NULL_HANDLE; VkCommandPool OffthreadPool = VK_NULL_HANDLE;
@@ -1027,25 +1019,5 @@ public:
virtual ~PipelineVGF(); virtual ~PipelineVGF();
}; };
enum class EnumRenderStage {
// Постройка буфера команд на рисовку
// В этот период не должно быть изменений в таблице,
// хранящей указатели на данные для рендера ChunkMesh
// Можно работать с миром
// Здесь нужно дождаться завершения работы с ChunkMesh
ComposingCommandBuffer,
// В этот период можно менять ChunkMesh
// Можно работать с миром
Render,
// В этот период нельзя работать с миром
// Можно менять ChunkMesh
// Здесь нужно дождаться завершения работы с миром, только в
// этом этапе могут приходить события изменения чанков и определений
WorldUpdate,
Shutdown
};
} /* namespace TOS::Navie::VK */ } /* namespace TOS::Navie::VK */

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,578 +0,0 @@
#include "AssetsPreloader.hpp"
#include <atomic>
#include <fstream>
#include <unordered_set>
#include <utility>
namespace LV {
static TOS::Logger LOG = "AssetsPreloader";
static ResourceFile readFileBytes(const fs::path& path) {
std::ifstream file(path, std::ios::binary);
if(!file)
throw std::runtime_error("Не удалось открыть файл: " + path.string());
file.seekg(0, std::ios::end);
std::streamoff size = file.tellg();
if(size < 0)
size = 0;
file.seekg(0, std::ios::beg);
ResourceFile out;
out.Data.resize(static_cast<size_t>(size));
if(size > 0) {
file.read(reinterpret_cast<char*>(out.Data.data()), size);
if (!file)
throw std::runtime_error("Не удалось прочитать файл: " + path.string());
}
out.calcHash();
return out;
}
static std::u8string readOptionalMeta(const fs::path& path) {
fs::path metaPath = path;
metaPath += ".meta";
if(!fs::exists(metaPath) || !fs::is_regular_file(metaPath))
return {};
ResourceFile meta = readFileBytes(metaPath);
return std::move(meta.Data);
}
AssetsPreloader::AssetsPreloader() {
std::fill(NextId.begin(), NextId.end(), 1);
std::fill(LastSendId.begin(), LastSendId.end(), 1);
}
AssetsPreloader::Out_reloadResources AssetsPreloader::reloadResources(const AssetsRegister& instances, ReloadStatus* status) {
bool expected = false;
assert(_Reloading.compare_exchange_strong(expected, true) && "Двойной вызов reloadResources");
struct ReloadGuard {
std::atomic<bool>& Flag;
~ReloadGuard() { Flag.exchange(false); }
} guard{_Reloading};
try {
ReloadStatus secondStatus;
return _reloadResources(instances, status ? *status : secondStatus);
} catch(const std::exception& exc) {
LOG.error() << exc.what();
assert(!"reloadResources: здесь не должно быть ошибок");
std::unreachable();
} catch(...) {
assert(!"reloadResources: здесь не должно быть ошибок");
std::unreachable();
}
}
AssetsPreloader::Out_reloadResources AssetsPreloader::_reloadResources(const AssetsRegister& instances, ReloadStatus& status) {
Out_reloadResources result;
// 1) Поиск всех ресурсов и построение конечной карты ресурсов (timestamps, path, name, size)
// Карта найденных ресурсов
std::array<
std::unordered_map<
std::string, // Domain
std::unordered_map<
std::string,
ResourceFindInfo,
detail::TSVHash,
detail::TSVEq
>,
detail::TSVHash,
detail::TSVEq
>,
static_cast<size_t>(AssetType::MAX_ENUM)
> resourcesFirstStage;
for (const fs::path& instance : instances.Assets) {
try {
if (fs::is_regular_file(instance)) {
// Может архив
/// TODO: пока не поддерживается
} else if (fs::is_directory(instance)) {
// Директория
fs::path assetsRoot = instance;
fs::path assetsCandidate = instance / "assets";
if (fs::exists(assetsCandidate) && fs::is_directory(assetsCandidate))
assetsRoot = assetsCandidate;
// Директория assets существует, перебираем домены в ней
for(auto begin = fs::directory_iterator(assetsRoot), end = fs::directory_iterator(); begin != end; begin++) {
if(!begin->is_directory())
continue;
fs::path domainPath = begin->path();
std::string domain = domainPath.filename().string();
// Перебираем по типу ресурса
for(size_t type = 0; type < static_cast<size_t>(AssetType::MAX_ENUM); ++type) {
AssetType assetType = static_cast<AssetType>(type);
fs::path assetPath = domainPath / EnumAssetsToDirectory(assetType);
if (!fs::exists(assetPath) || !fs::is_directory(assetPath))
continue;
std::unordered_map<
std::string, // Key
ResourceFindInfo, // ResourceInfo,
detail::TSVHash,
detail::TSVEq
>& firstStage = resourcesFirstStage[static_cast<size_t>(assetType)][domain];
// Исследуем все ресурсы одного типа
for (auto begin = fs::recursive_directory_iterator(assetPath), end = fs::recursive_directory_iterator(); begin != end; begin++) {
if (begin->is_directory())
continue;
fs::path file = begin->path();
if (assetType == AssetType::Texture && file.extension() == ".meta")
continue;
std::string key = fs::relative(file, assetPath).generic_string();
if (firstStage.contains(key))
continue;
fs::file_time_type timestamp = fs::last_write_time(file);
if (assetType == AssetType::Texture) {
fs::path metaPath = file;
metaPath += ".meta";
if (fs::exists(metaPath) && fs::is_regular_file(metaPath)) {
auto metaTime = fs::last_write_time(metaPath);
if (metaTime > timestamp)
timestamp = metaTime;
}
}
// Работаем с ресурсом
firstStage[key] = ResourceFindInfo{
.Path = file,
.Timestamp = timestamp
};
}
}
}
} else {
throw std::runtime_error("Неизвестный тип инстанса медиаресурсов");
}
} catch (const std::exception& exc) {
/// TODO: Логгировать в статусе
}
}
auto resolveModelInfo = [&](std::string_view domain, std::string_view key) -> const ResourceFindInfo* {
auto& table = resourcesFirstStage[static_cast<size_t>(AssetType::Model)];
auto iterDomain = table.find(std::string(domain));
if(iterDomain == table.end())
return nullptr;
auto iterKey = iterDomain->second.find(std::string(key));
if(iterKey == iterDomain->second.end())
return nullptr;
return &iterKey->second;
};
std::function<std::optional<js::object>(std::string_view, std::string_view, std::unordered_set<std::string>&)> loadModelProfile;
loadModelProfile = [&](std::string_view domain, std::string_view key, std::unordered_set<std::string>& visiting)
-> std::optional<js::object>
{
std::string fullKey = std::string(domain) + ':' + std::string(key);
if(!visiting.insert(fullKey).second) {
LOG.warn() << "Model parent cycle: " << fullKey;
return std::nullopt;
}
const ResourceFindInfo* info = resolveModelInfo(domain, key);
if(!info) {
LOG.warn() << "Model file not found for parent: " << fullKey;
visiting.erase(fullKey);
return std::nullopt;
}
ResourceFile file = readFileBytes(info->Path);
std::string_view view(reinterpret_cast<const char*>(file.Data.data()), file.Data.size());
js::object obj = js::parse(view).as_object();
if(auto parentVal = obj.if_contains("parent")) {
if(parentVal->is_string()) {
std::string parentStr = std::string(parentVal->as_string());
auto [pDomain, pKeyRaw] = parseDomainKey(parentStr, domain);
fs::path pKeyPath = fs::path(pKeyRaw);
if(pKeyPath.extension().empty())
pKeyPath += ".json";
std::string pKey = pKeyPath.generic_string();
std::optional<js::object> parent = loadModelProfile(pDomain, pKey, visiting);
if(parent) {
auto mergeFieldIfMissing = [&](const char* field) {
if(!obj.contains(field) && parent->contains(field))
obj[field] = parent->at(field);
};
mergeFieldIfMissing("cuboids");
mergeFieldIfMissing("sub_models");
mergeFieldIfMissing("gui_light");
mergeFieldIfMissing("ambient_occlusion");
if(auto parentTextures = parent->if_contains("textures"); parentTextures && parentTextures->is_object()) {
if(auto childTextures = obj.if_contains("textures"); childTextures && childTextures->is_object()) {
auto& childObj = childTextures->as_object();
const auto& parentObj = parentTextures->as_object();
for(const auto& [tkey, tval] : parentObj) {
if(!childObj.contains(tkey))
childObj.emplace(tkey, tval);
}
} else if(!obj.contains("textures")) {
obj["textures"] = parentTextures->as_object();
}
}
if(auto parentDisplay = parent->if_contains("display"); parentDisplay && parentDisplay->is_object()) {
if(auto childDisplay = obj.if_contains("display"); childDisplay && childDisplay->is_object()) {
auto& childObj = childDisplay->as_object();
const auto& parentObj = parentDisplay->as_object();
for(const auto& [dkey, dval] : parentObj) {
if(!childObj.contains(dkey))
childObj.emplace(dkey, dval);
}
} else if(!obj.contains("display")) {
obj["display"] = parentDisplay->as_object();
}
}
}
}
}
visiting.erase(fullKey);
return obj;
};
// Функция парсинга ресурсов
auto buildResource = [&](AssetType type, std::string_view domain, std::string_view key, const ResourceFindInfo& info) -> PendingResource {
PendingResource out;
out.Key = key;
out.Timestamp = info.Timestamp;
std::function<uint32_t(const std::string_view)> modelResolver
= [&](const std::string_view model) -> uint32_t
{
auto [mDomain, mKey] = parseDomainKey(model, domain);
return getId(AssetType::Model, mDomain, mKey);
};
std::function<std::optional<uint32_t>(std::string_view)> textureIdResolver
= [&](std::string_view texture) -> std::optional<uint32_t>
{
auto [mDomain, mKey] = parseDomainKey(texture, domain);
return getId(AssetType::Texture, mDomain, mKey);
};
auto normalizeTexturePipelineSrc = [](std::string_view src) -> std::string {
std::string out(src);
auto isSpace = [](unsigned char c) { return c == ' ' || c == '\t' || c == '\n' || c == '\r'; };
size_t start = 0;
while(start < out.size() && isSpace(static_cast<unsigned char>(out[start])))
++start;
if(out.compare(start, 3, "tex") != 0) {
std::string pref = "tex ";
pref += out.substr(start);
return pref;
}
return out;
};
std::function<std::vector<uint8_t>(const std::string_view)> textureResolver
= [&](const std::string_view texturePipelineSrc) -> std::vector<uint8_t>
{
TexturePipelineProgram tpp;
bool flag = tpp.compile(normalizeTexturePipelineSrc(texturePipelineSrc));
if(!flag)
return {};
tpp.link(textureIdResolver);
return tpp.toBytes();
};
if (type == AssetType::Nodestate) {
ResourceFile file = readFileBytes(info.Path);
std::string_view view(reinterpret_cast<const char*>(file.Data.data()), file.Data.size());
js::object obj = js::parse(view).as_object();
HeadlessNodeState hns;
out.Header = hns.parse(obj, modelResolver);
out.Resource = std::make_shared<std::u8string>(hns.dump());
out.Hash = sha2::sha256((const uint8_t*) out.Resource->data(), out.Resource->size());
} else if (type == AssetType::Model) {
const std::string ext = info.Path.extension().string();
if (ext == ".json") {
std::unordered_set<std::string> visiting;
std::optional<js::object> objOpt = loadModelProfile(domain, key, visiting);
if(!objOpt) {
LOG.warn() << "Не удалось загрузить модель: " << info.Path.string();
throw std::runtime_error("Model profile load failed");
}
js::object obj = std::move(*objOpt);
HeadlessModel hm;
out.Header = hm.parse(obj, modelResolver, textureResolver);
std::u8string compiled = hm.dump();
if(hm.Cuboids.empty()) {
static std::atomic<uint32_t> debugEmptyModelLogCount = 0;
uint32_t idx = debugEmptyModelLogCount.fetch_add(1);
if(idx < 128) {
LOG.warn() << "Model compiled with empty cuboids: "
<< domain << ':' << key
<< " file=" << info.Path.string()
<< " size=" << compiled.size();
}
}
out.Resource = std::make_shared<std::u8string>(std::move(compiled));
out.Hash = sha2::sha256((const uint8_t*) out.Resource->data(), out.Resource->size());
// } else if (ext == ".gltf" || ext == ".glb") {
// /// TODO: добавить поддержку gltf
// ResourceFile file = readFileBytes(info.Path);
// out.Resource = std::make_shared<std::vector<uint8_t>>(std::move(file.Data));
// out.Hash = file.Hash;
} else {
throw std::runtime_error("Не поддерживаемый формат модели: " + info.Path.string());
}
} else if (type == AssetType::Texture) {
ResourceFile file = readFileBytes(info.Path);
out.Resource = std::make_shared<std::u8string>(std::move(file.Data));
out.Hash = file.Hash;
out.Header = readOptionalMeta(info.Path);
} else {
ResourceFile file = readFileBytes(info.Path);
out.Resource = std::make_shared<std::u8string>(std::move(file.Data));
out.Hash = file.Hash;
}
out.Id = getId(type, domain, key);
return out;
};
// 2) Обрабатываться будут только изменённые (новый timestamp) или новые ресурсы
// Определяем каких ресурсов не стало
for(size_t type = 0; type < static_cast<size_t>(AssetType::MAX_ENUM); ++type) {
auto& tableResourcesFirstStage = resourcesFirstStage[type];
for(const auto& [id, resource] : MediaResources[type]) {
if(tableResourcesFirstStage.empty()) {
result.Lost[type][resource.Domain].push_back(resource.Key);
continue;
}
auto iterDomain = tableResourcesFirstStage.find(resource.Domain);
if(iterDomain == tableResourcesFirstStage.end()) {
result.Lost[type][resource.Domain].push_back(resource.Key);
continue;
}
if(!iterDomain->second.contains(resource.Key)) {
result.Lost[type][resource.Domain].push_back(resource.Key);
}
}
}
// Определение новых или изменённых ресурсов
for(size_t type = 0; type < static_cast<size_t>(AssetType::MAX_ENUM); ++type) {
for(const auto& [domain, table] : resourcesFirstStage[type]) {
auto iterTableDomain = DKToId[type].find(domain);
if(iterTableDomain == DKToId[type].end()) {
// Домен неизвестен движку, все ресурсы в нём новые
for(const auto& [key, info] : table) {
PendingResource resource = buildResource(static_cast<AssetType>(type), domain, key, info);
result.NewOrChange[type][domain].push_back(std::move(resource));
}
} else {
for(const auto& [key, info] : table) {
bool needsUpdate = true;
if(auto iterKey = iterTableDomain->second.find(key); iterKey != iterTableDomain->second.end()) {
// Идентификатор найден
auto iterRes = MediaResources[type].find(iterKey->second);
// Если нашли ресурс по идентификатору и время изменения не поменялось, то он не новый и не изменился
if(iterRes != MediaResources[type].end() && iterRes->second.Timestamp == info.Timestamp)
needsUpdate = false;
}
if(!needsUpdate)
continue;
PendingResource resource = buildResource(static_cast<AssetType>(type), domain, key, info);
result.NewOrChange[(int) type][domain].push_back(std::move(resource));
}
}
}
}
return result;
}
AssetsPreloader::Out_applyResourceChange AssetsPreloader::applyResourceChange(const Out_reloadResources& orr) {
Out_applyResourceChange result;
// Удаляем ресурсы
/*
Удаляются только ресурсы, при этом за ними остаётся бронь на идентификатор
Уже скомпилированные зависимости к ресурсам не будут
перекомпилироваться для смены идентификатора.
Если нужный ресурс появится, то привязка останется.
Новые клиенты не получат ресурс которого нет,
но он может использоваться
*/
for(size_t type = 0; type < static_cast<size_t>(AssetType::MAX_ENUM); type++) {
for(const auto& [domain, keys] : orr.Lost[type]) {
auto iterDomain = DKToId[type].find(domain);
// Если уже было решено, что ресурсы были, и стали потерянными, то так и должно быть
assert(iterDomain != DKToId[type].end());
for(const auto& key : keys) {
auto iterKey = iterDomain->second.find(key);
// Ресурс был и должен быть
assert(iterKey != iterDomain->second.end());
uint32_t id = iterKey->second;
auto& resType = MediaResources[type];
auto iterRes = resType.find(id);
if(iterRes == resType.end())
continue;
// Ресурс был потерян
result.Lost[type].push_back(id);
// Hash более нам неизвестен
HashToId.erase(iterRes->second.Hash);
// Затираем ресурс
resType.erase(iterRes);
}
}
}
// Добавляем
for(int type = 0; type < (int) AssetType::MAX_ENUM; type++) {
auto& typeTable = DKToId[type];
for(const auto& [domain, resources] : orr.NewOrChange[type]) {
auto& domainTable = typeTable[domain];
for(const PendingResource& pending : resources) {
MediaResource resource {
.Domain = domain,
.Key = std::move(pending.Key),
.Timestamp = pending.Timestamp,
.Resource = std::move(pending.Resource),
.Hash = pending.Hash,
.Header = std::move(pending.Header)
};
auto& table = MediaResources[type];
// Нужно затереть старую ссылку хеша на данный ресурс
if(auto iter = table.find(pending.Id); iter != table.end())
HashToId.erase(iter->second.Hash);
// Добавили ресурс
table[pending.Id] = resource;
// Связали с хешем
HashToId[resource.Hash] = {static_cast<AssetType>(type), pending.Id};
// Осведомили о новом/изменённом ресурсе
result.NewOrChange[type].emplace_back(pending.Id, resource.Hash, std::move(resource.Header));
}
}
// Не должно быть ресурсов, которые были помечены как потерянные
#ifndef NDEBUG
std::unordered_set<uint32_t> changed;
for(const auto& [id, _, _2] : result.NewOrChange[type])
changed.insert(id);
auto& lost = result.Lost[type];
for(auto iter : lost)
assert(!changed.contains(iter));
#endif
}
return result;
}
AssetsPreloader::Out_bakeId AssetsPreloader::bakeIdTables() {
#ifndef NDEBUG
assert(!DKToIdInBakingMode);
DKToIdInBakingMode = true;
struct _tempStruct {
AssetsPreloader* handler;
~_tempStruct() { handler->DKToIdInBakingMode = false; }
} _lock{this};
#endif
Out_bakeId result;
for(size_t type = 0; type < static_cast<size_t>(AssetType::MAX_ENUM); ++type) {
// домен+ключ -> id
{
auto lock = NewDKToId[type].lock();
auto& dkToId = DKToId[type];
for(auto& [domain, keys] : *lock) {
// Если домен не существует, просто воткнёт новые ключи
auto [iterDomain, inserted] = dkToId.try_emplace(domain, std::move(keys));
if(!inserted) {
// Домен уже существует, сливаем новые ключи
iterDomain->second.merge(keys);
}
}
lock->clear();
}
// id -> домен+ключ
{
auto lock = NewIdToDK[type].lock();
auto& idToDK = IdToDK[type];
result.IdToDK[type] = std::move(*lock);
lock->clear();
idToDK.append_range(result.IdToDK[type]);
// result.LastSendId[type] = LastSendId[type];
LastSendId[type] = NextId[type];
}
}
return result;
}
AssetsPreloader::Out_fullSync AssetsPreloader::collectFullSync() const {
Out_fullSync out;
for(size_t type = 0; type < static_cast<size_t>(AssetType::MAX_ENUM); ++type) {
out.IdToDK[type] = IdToDK[type];
}
for(size_t type = 0; type < static_cast<size_t>(AssetType::MAX_ENUM); ++type) {
for(const auto& [id, resource] : MediaResources[type]) {
out.HashHeaders[type].push_back(BindHashHeaderInfo{
.Id = id,
.Hash = resource.Hash,
.Header = resource.Header
});
out.Resources.emplace_back(
static_cast<AssetType>(type),
id,
&resource
);
}
}
return out;
}
std::tuple<AssetsNodestate, std::vector<AssetsModel>, std::vector<AssetsTexture>>
AssetsPreloader::getNodeDependency(const std::string& domain, const std::string& key) {
(void)domain;
(void)key;
return {0, {}, {}};
}
}

View File

@@ -1,410 +0,0 @@
#pragma once
#include <algorithm>
#include <array>
#include <cstdint>
#include <cstring>
#include <filesystem>
#include <functional>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <tuple>
#include <unordered_map>
#include <utility>
#include <vector>
#include "Common/TexturePipelineProgram.hpp"
#include "Common/Abstract.hpp"
#include "Common/Async.hpp"
#include "TOSAsync.hpp"
#include "TOSLib.hpp"
#include "sha2.hpp"
/*
Класс отвечает за отслеживание изменений и подгрузки медиаресурсов в указанных директориях.
Медиаресурсы, собранные из папки assets или зарегистрированные модами.
Хранит все данные в оперативной памяти.
*/
static constexpr const char* EnumAssetsToDirectory(LV::EnumAssets value) {
switch(value) {
case LV::EnumAssets::Nodestate: return "nodestate";
case LV::EnumAssets::Particle: return "particle";
case LV::EnumAssets::Animation: return "animation";
case LV::EnumAssets::Model: return "model";
case LV::EnumAssets::Texture: return "texture";
case LV::EnumAssets::Sound: return "sound";
case LV::EnumAssets::Font: return "font";
default:
break;
}
assert(!"Неизвестный тип медиаресурса");
return "";
}
namespace LV {
namespace fs = std::filesystem;
using AssetType = EnumAssets;
struct ResourceFile {
using Hash_t = sha2::sha256_hash; // boost::uuids::detail::sha1::digest_type;
Hash_t Hash;
std::u8string Data;
void calcHash() {
Hash = sha2::sha256((const uint8_t*) Data.data(), Data.size());
}
};
class AssetsPreloader {
public:
using Ptr = std::shared_ptr<AssetsPreloader>;
using IdTable =
std::unordered_map<
std::string, // Domain
std::unordered_map<
std::string, // Key
uint32_t, // ResourceId
detail::TSVHash,
detail::TSVEq
>,
detail::TSVHash,
detail::TSVEq
>;
//
/*
Ресурс имеет бинарную часть, из который вырезаны все зависимости.
Вторая часть это заголовок, которые всегда динамично передаётся с сервера.
В заголовке хранятся зависимости от ресурсов.
*/
struct MediaResource {
std::string Domain, Key;
fs::file_time_type Timestamp;
// Обезличенный ресурс
std::shared_ptr<std::u8string> Resource;
// Хэш ресурса
ResourceFile::Hash_t Hash;
// Скомпилированный заголовок
std::u8string Header;
};
struct PendingResource {
uint32_t Id;
std::string Key;
fs::file_time_type Timestamp;
// Обезличенный ресурс
std::shared_ptr<std::u8string> Resource;
// Его хеш
ResourceFile::Hash_t Hash;
// Заголовок
std::u8string Header;
};
struct BindDomainKeyInfo {
std::string Domain;
std::string Key;
};
struct BindHashHeaderInfo {
ResourceId Id;
Hash_t Hash;
std::u8string Header;
};
struct Out_reloadResources {
std::unordered_map<std::string, std::vector<PendingResource>> NewOrChange[(int) AssetType::MAX_ENUM];
std::unordered_map<std::string, std::vector<std::string>> Lost[(int) AssetType::MAX_ENUM];
};
struct Out_applyResourceChange {
std::array<
std::vector<AssetsPreloader::BindHashHeaderInfo>,
static_cast<size_t>(AssetType::MAX_ENUM)
> NewOrChange;
std::array<
std::vector<ResourceId>,
static_cast<size_t>(AssetType::MAX_ENUM)
> Lost;
};
struct Out_bakeId {
// Новые привязки
std::array<
std::vector<BindDomainKeyInfo>,
static_cast<size_t>(AssetType::MAX_ENUM)
> IdToDK;
};
struct Out_fullSync {
std::array<
std::vector<BindDomainKeyInfo>,
static_cast<size_t>(AssetType::MAX_ENUM)
> IdToDK;
std::array<
std::vector<BindHashHeaderInfo>,
static_cast<size_t>(AssetType::MAX_ENUM)
> HashHeaders;
std::vector<std::tuple<AssetType, ResourceId, const MediaResource*>> Resources;
};
struct ReloadStatus {
/// TODO: callback'и для обновления статусов
/// TODO: многоуровневый статус std::vector<std::string>. Этапы/Шаги/Объекты
};
struct AssetsRegister {
/*
Пути до активных папок assets, соответствую порядку загруженным модам.
От последнего мода к первому.
Тот файл, что был загружен раньше и будет использоваться
*/
std::vector<fs::path> Assets;
/*
У этих ресурсов приоритет выше, если их удастся получить,
то использоваться будут именно они
Domain -> {key + data}
*/
std::array<
std::unordered_map<
std::string,
std::unordered_map<std::string, void*>
>,
static_cast<size_t>(AssetType::MAX_ENUM)
> Custom;
};
public:
AssetsPreloader();
~AssetsPreloader() = default;
AssetsPreloader(const AssetsPreloader&) = delete;
AssetsPreloader(AssetsPreloader&&) = delete;
AssetsPreloader& operator=(const AssetsPreloader&) = delete;
AssetsPreloader& operator=(AssetsPreloader&&) = delete;
/*
Перепроверка изменений ресурсов по дате изменения, пересчёт хешей.
Обнаруженные изменения должны быть отправлены всем клиентам.
Ресурсы будут обработаны в подходящий формат и сохранены в кеше.
Используется в GameServer.
! Одновременно можно работать только один такой вызов.
! Бронирует идентификаторы используя getId();
instances -> пути к директории с assets или архивы с assets внутри. От низшего приоритета к высшему.
status -> обратный отклик о процессе обновления ресурсов.
ReloadStatus <- новые и потерянные ресурсы.
*/
Out_reloadResources reloadResources(const AssetsRegister& instances, ReloadStatus* status = nullptr);
/*
Применяет расчитанные изменения.
Out_applyResourceChange <- Нужно отправить клиентам новые привязки ресурсов
id -> hash+header
*/
Out_applyResourceChange applyResourceChange(const Out_reloadResources& orr);
/*
Выдаёт идентификатор ресурса.
Многопоточно.
Иногда нужно вызывать bakeIdTables чтобы оптимизировать таблицы
идентификаторов. При этом никто не должен использовать getId
*/
ResourceId getId(AssetType type, std::string_view domain, std::string_view key);
/*
Оптимизирует таблицы идентификаторов.
Нельзя использовать пока есть вероятность что кто-то использует getId().
Такжке нельзя при выполнении reloadResources().
Out_bakeId <- Нужно отправить подключенным клиентам новые привязки id -> домен+ключ
*/
Out_bakeId bakeIdTables();
// Выдаёт полный список привязок и ресурсов для новых клиентов.
Out_fullSync collectFullSync() const;
/*
Выдаёт пакет со всеми текущими привязками id -> домен+ключ.
Используется при подключении новых клиентов.
*/
void makeGlobalLinkagePacket() {
/// TODO: Собрать пакет с IdToDK и сжать его домены и ключи и id -> hash+header
// Тот же пакет для обновления идентификаторов
std::unreachable();
}
// Выдаёт ресурс по идентификатору
const MediaResource* getResource(AssetType type, uint32_t id) const;
// Выдаёт ресурс по хешу
std::optional<std::tuple<AssetType, uint32_t, const MediaResource*>> getResource(const ResourceFile::Hash_t& hash);
// Выдаёт зависимости к ресурсам профиля ноды
std::tuple<AssetsNodestate, std::vector<AssetsModel>, std::vector<AssetsTexture>>
getNodeDependency(const std::string& domain, const std::string& key);
private:
struct ResourceFindInfo {
// Путь к архиву (если есть), и путь до ресурса
fs::path ArchivePath, Path;
// Время изменения файла
fs::file_time_type Timestamp;
};
struct HashHasher {
std::size_t operator()(const ResourceFile::Hash_t& hash) const noexcept {
std::size_t v = 14695981039346656037ULL;
for (const auto& byte : hash) {
v ^= static_cast<std::size_t>(byte);
v *= 1099511628211ULL;
}
return v;
}
};
// Текущее состояние reloadResources
std::atomic<bool> _Reloading = false;
// Если идентификатор не найден в асинхронной таблице, переходим к работе с синхронной
ResourceId _getIdNew(AssetType type, std::string_view domain, std::string_view key);
Out_reloadResources _reloadResources(const AssetsRegister& instances, ReloadStatus& status);
#ifndef NDEBUG
// Для контроля за режимом слияния ключей
bool DKToIdInBakingMode = false;
#endif
/*
Многопоточная таблица идентификаторов. Новые идентификаторы выделяются в NewDKToId,
и далее вливаются в основную таблицу при вызове bakeIdTables()
*/
std::array<IdTable, static_cast<size_t>(AssetType::MAX_ENUM)> DKToId;
/*
Многопоточная таблица обратного резолва.
Идентификатор -> домен+ключ
*/
std::array<std::vector<BindDomainKeyInfo>, static_cast<size_t>(AssetType::MAX_ENUM)> IdToDK;
/*
Таблица в которой выделяются новые идентификаторы, которых не нашлось в DKToId.
Данный объект одновременно может работать только с одним потоком.
*/
std::array<TOS::SpinlockObject<IdTable>, static_cast<size_t>(AssetType::MAX_ENUM)> NewDKToId;
/*
Конец поля идентификаторов, известный клиентам.
Если NextId продвинулся дальше, нужно уведомить клиентов о новых привязках.
*/
std::array<ResourceId, static_cast<size_t>(AssetType::MAX_ENUM)> LastSendId;
/*
Списки в которых пишутся новые привязки. Начала спиской исходят из LastSendId.
Id + LastSendId -> домен+ключ
*/
std::array<TOS::SpinlockObject<std::vector<BindDomainKeyInfo>>, static_cast<size_t>(AssetType::MAX_ENUM)> NewIdToDK;
// Загруженные ресурсы
std::array<std::unordered_map<ResourceId, MediaResource>, static_cast<size_t>(AssetType::MAX_ENUM)> MediaResources;
// Hash -> ресурс
std::unordered_map<ResourceFile::Hash_t, std::pair<AssetType, ResourceId>, HashHasher> HashToId;
// Для последовательного выделения идентификаторов
std::array<ResourceId, static_cast<size_t>(AssetType::MAX_ENUM)> NextId;
};
inline ResourceId AssetsPreloader::getId(AssetType type, std::string_view domain, std::string_view key) {
#ifndef NDEBUG
assert(!DKToIdInBakingMode);
#endif
const auto& typeTable = DKToId[static_cast<size_t>(type)];
auto domainTable = typeTable.find(domain);
#ifndef NDEBUG
assert(!DKToIdInBakingMode);
#endif
if(domainTable == typeTable.end())
return _getIdNew(type, domain, key);
auto keyTable = domainTable->second.find(key);
if (keyTable == domainTable->second.end())
return _getIdNew(type, domain, key);
return keyTable->second;
return 0;
}
inline ResourceId AssetsPreloader::_getIdNew(AssetType type, std::string_view domain, std::string_view key) {
auto lock = NewDKToId[static_cast<size_t>(type)].lock();
auto iterDomainNewTable = lock->find(domain);
if(iterDomainNewTable == lock->end()) {
iterDomainNewTable = lock->emplace_hint(
iterDomainNewTable,
(std::string) domain,
std::unordered_map<std::string, uint32_t, detail::TSVHash, detail::TSVEq>{}
);
}
auto& domainNewTable = iterDomainNewTable->second;
if(auto iter = domainNewTable.find(key); iter != domainNewTable.end())
return iter->second;
uint32_t id = domainNewTable[(std::string) key] = NextId[static_cast<size_t>(type)]++;
auto lock2 = NewIdToDK[static_cast<size_t>(type)].lock();
lock.unlock();
lock2->emplace_back((std::string) domain, (std::string) key);
return id;
}
inline const AssetsPreloader::MediaResource* AssetsPreloader::getResource(AssetType type, uint32_t id) const {
auto& iterType = MediaResources[static_cast<size_t>(type)];
auto iterRes = iterType.find(id);
if(iterRes == iterType.end())
return nullptr;
return &iterRes->second;
}
inline std::optional<std::tuple<AssetType, uint32_t, const AssetsPreloader::MediaResource*>>
AssetsPreloader::getResource(const ResourceFile::Hash_t& hash)
{
auto iter = HashToId.find(hash);
if(iter == HashToId.end())
return std::nullopt;
auto [type, id] = iter->second;
const MediaResource* res = getResource(type, id);
if(!res) {
HashToId.erase(iter);
return std::nullopt;
}
if(res->Hash != hash) {
HashToId.erase(iter);
return std::nullopt;
}
return std::tuple<AssetType, uint32_t, const MediaResource*>{type, id, res};
}
}

View File

@@ -1,14 +1,16 @@
#pragma once #pragma once
#include "TOSLib.hpp"
#include "boost/asio/async_result.hpp"
#include "boost/asio/deadline_timer.hpp"
#include "boost/date_time/posix_time/posix_time_duration.hpp"
#include <atomic>
#include <boost/asio.hpp> #include <boost/asio.hpp>
#include <boost/asio/any_io_executor.hpp> #include <boost/asio/impl/execution_context.hpp>
#include <boost/asio/deadline_timer.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/this_coro.hpp>
#include <boost/asio/use_awaitable.hpp>
#include <boost/date_time/posix_time/ptime.hpp> #include <boost/date_time/posix_time/ptime.hpp>
#include <boost/thread.hpp> #include <boost/thread.hpp>
#include <boost/asio/experimental/awaitable_operators.hpp> #include <boost/asio/experimental/awaitable_operators.hpp>
#include <boost/lockfree/queue.hpp>
namespace LV { namespace LV {
@@ -20,152 +22,4 @@ using tcp = asio::ip::tcp;
template<typename T = void> template<typename T = void>
using coro = asio::awaitable<T>; using coro = asio::awaitable<T>;
class AsyncObject {
protected:
asio::io_context &IOC;
asio::deadline_timer WorkDeadline;
public:
AsyncObject(asio::io_context &ioc)
: IOC(ioc), WorkDeadline(ioc, boost::posix_time::pos_infin)
{
}
inline asio::io_context& EXEC()
{
return IOC;
}
protected:
template<typename Coroutine>
void co_spawn(Coroutine &&coroutine) {
asio::co_spawn(IOC, WorkDeadline.async_wait(asio::use_awaitable) || std::move(coroutine), asio::detached);
}
};
template<typename ValueType>
class AsyncAtomic : public AsyncObject {
protected:
asio::deadline_timer Deadline;
ValueType Value;
boost::mutex Mtx;
public:
AsyncAtomic(asio::io_context &ioc, ValueType &&value)
: AsyncObject(ioc), Deadline(ioc), Value(std::move(value))
{
}
AsyncAtomic& operator=(ValueType &&value) {
boost::unique_lock lock(Mtx);
Value = std::move(value);
Deadline.expires_from_now(boost::posix_time::pos_infin);
return *this;
}
operator ValueType() const {
return Value;
}
ValueType operator*() const {
return Value;
}
AsyncAtomic& operator++() {
boost::unique_lock lock(Mtx);
Value--;
Deadline.expires_from_now(boost::posix_time::pos_infin);
return *this;
}
AsyncAtomic& operator--() {
boost::unique_lock lock(Mtx);
Value--;
Deadline.expires_from_now(boost::posix_time::pos_infin);
return *this;
}
AsyncAtomic& operator+=(ValueType value) {
boost::unique_lock lock(Mtx);
Value += value;
Deadline.expires_from_now(boost::posix_time::pos_infin);
return *this;
}
AsyncAtomic& operator-=(ValueType value) {
boost::unique_lock lock(Mtx);
Value -= value;
Deadline.expires_from_now(boost::posix_time::pos_infin);
return *this;
}
void wait(ValueType oldValue) {
while(true) {
if(oldValue != Value)
return;
boost::unique_lock lock(Mtx);
if(oldValue != Value)
return;
std::atomic_bool flag = false;
Deadline.async_wait([&](boost::system::error_code errc) { flag.store(true); flag.notify_all(); });
lock.unlock();
flag.wait(false);
}
}
void await(ValueType needValue) {
while(true) {
if(needValue == Value)
return;
boost::unique_lock lock(Mtx);
if(needValue == Value)
return;
std::atomic_bool flag = false;
Deadline.async_wait([&](boost::system::error_code errc) { flag.store(true); flag.notify_all(); });
lock.unlock();
flag.wait(false);
}
}
coro<> async_wait(ValueType oldValue) {
while(true) {
if(oldValue != Value)
co_return;
boost::unique_lock lock(Mtx);
if(oldValue != Value)
co_return;
auto coroutine = Deadline.async_wait();
lock.unlock();
try { co_await std::move(coroutine); } catch(...) {}
}
}
coro<> async_await(ValueType needValue) {
while(true) {
if(needValue == Value)
co_return;
boost::unique_lock lock(Mtx);
if(needValue == Value)
co_return;
auto coroutine = Deadline.async_wait();
lock.unlock();
try { co_await std::move(coroutine); } catch(...) {}
}
}
};
} }

View File

@@ -1,6 +1,5 @@
#pragma once #include "Common/Abstract.hpp"
#include <algorithm> #include <iostream>
namespace LV { namespace LV {
@@ -9,7 +8,7 @@ bool calcBoxToBoxCollide(const VecType vec1_min, const VecType vec1_max,
const VecType vec2_min, const VecType vec2_max, bool axis[VecType::length()] = nullptr const VecType vec2_min, const VecType vec2_max, bool axis[VecType::length()] = nullptr
) { ) {
using ValType = VecType::Type; using ValType = VecType::value_type;
ValType max_delta = 0; ValType max_delta = 0;
ValType result = 0; ValType result = 0;

View File

@@ -17,7 +17,7 @@ bool SocketServer::isStopped() {
coro<void> SocketServer::run(std::function<coro<>(tcp::socket)> onConnect) { coro<void> SocketServer::run(std::function<coro<>(tcp::socket)> onConnect) {
while(true) { // TODO: ловить ошибки на async_accept while(true) { // TODO: ловить ошибки на async_accept
try { try {
co_spawn(onConnect(co_await Acceptor.async_accept())); asio::co_spawn(IOC, onConnect(co_await Acceptor.async_accept()), asio::detached);
} catch(const std::exception &exc) { } catch(const std::exception &exc) {
if(const boost::system::system_error *errc = dynamic_cast<const boost::system::system_error*>(&exc); if(const boost::system::system_error *errc = dynamic_cast<const boost::system::system_error*>(&exc);
errc && (errc->code() == asio::error::operation_aborted || errc->code() == asio::error::bad_descriptor)) errc && (errc->code() == asio::error::operation_aborted || errc->code() == asio::error::bad_descriptor))
@@ -31,21 +31,13 @@ AsyncSocket::~AsyncSocket() {
if(SendPackets.Context) if(SendPackets.Context)
SendPackets.Context->NeedShutdown = true; SendPackets.Context->NeedShutdown = true;
{ boost::unique_lock lock(SendPackets.Mtx);
boost::lock_guard lock(SendPackets.Mtx);
SendPackets.SenderGuard.cancel();
WorkDeadline.cancel();
}
if(Socket.is_open()) if(Socket.is_open())
try { Socket.close(); } catch(...) {} try { Socket.close(); } catch(...) {}
} }
void AsyncSocket::pushPackets(std::vector<Packet> *simplePackets, std::vector<SmartPacket> *smartPackets) { void AsyncSocket::pushPackets(std::vector<Packet> *simplePackets, std::vector<SmartPacket> *smartPackets) {
if(simplePackets->empty() && (!smartPackets || smartPackets->empty()))
return;
boost::unique_lock lock(SendPackets.Mtx); boost::unique_lock lock(SendPackets.Mtx);
if(Socket.is_open() if(Socket.is_open()
@@ -58,6 +50,8 @@ void AsyncSocket::pushPackets(std::vector<Packet> *simplePackets, std::vector<Sm
// TODO: std::cout << "Передоз пакетами, сокет закрыт" << std::endl; // TODO: std::cout << "Передоз пакетами, сокет закрыт" << std::endl;
} }
bool wasPackets = SendPackets.SimpleBuffer.size() || SendPackets.SmartBuffer.size();
if(!Socket.is_open()) { if(!Socket.is_open()) {
if(simplePackets) if(simplePackets)
simplePackets->clear(); simplePackets->clear();
@@ -89,8 +83,7 @@ void AsyncSocket::pushPackets(std::vector<Packet> *simplePackets, std::vector<Sm
SendPackets.SizeInQueue += addedSize; SendPackets.SizeInQueue += addedSize;
if(SendPackets.WaitForSemaphore) { if(!wasPackets) {
SendPackets.WaitForSemaphore = false;
SendPackets.Semaphore.cancel(); SendPackets.Semaphore.cancel();
SendPackets.Semaphore.expires_at(boost::posix_time::pos_infin); SendPackets.Semaphore.expires_at(boost::posix_time::pos_infin);
} }
@@ -127,10 +120,18 @@ coro<> AsyncSocket::read(std::byte *data, uint32_t size) {
void AsyncSocket::closeRead() { void AsyncSocket::closeRead() {
if(Socket.is_open() && !ReadShutdowned) { if(Socket.is_open() && !ReadShutdowned) {
ReadShutdowned = true; ReadShutdowned = true;
// TODO:
try { Socket.shutdown(boost::asio::socket_base::shutdown_receive); } catch(...) {} try { Socket.shutdown(boost::asio::socket_base::shutdown_receive); } catch(...) {}
} }
} }
void AsyncSocket::close() {
if(Socket.is_open()) {
Socket.close();
ReadShutdowned = true;
}
}
coro<> AsyncSocket::waitForSend() { coro<> AsyncSocket::waitForSend() {
asio::deadline_timer waiter(IOC); asio::deadline_timer waiter(IOC);
@@ -150,12 +151,19 @@ coro<> AsyncSocket::runSender(std::shared_ptr<AsyncContext> context) {
while(!context->NeedShutdown) { while(!context->NeedShutdown) {
{ {
boost::unique_lock lock(SendPackets.Mtx); boost::unique_lock lock(SendPackets.Mtx);
if(SendPackets.SimpleBuffer.empty() && SendPackets.SmartBuffer.empty()) {
SendPackets.WaitForSemaphore = true;
auto coroutine = SendPackets.Semaphore.async_wait();
lock.unlock();
try { co_await std::move(coroutine); } catch(...) {} if(context->NeedShutdown) {
break;
}
if(SendPackets.SimpleBuffer.empty() && SendPackets.SmartBuffer.empty()) {
auto coroutine = SendPackets.Semaphore.async_wait();
if(SendPackets.SimpleBuffer.empty() && SendPackets.SmartBuffer.empty()) {
lock.unlock();
try { co_await std::move(coroutine); } catch(...) {}
}
continue; continue;
} else { } else {
for(int cycle = 0; cycle < 2; cycle++, NextBuffer++) { for(int cycle = 0; cycle < 2; cycle++, NextBuffer++) {

View File

@@ -1,32 +1,30 @@
#pragma once #pragma once
// TODO: Всё это надо переписать
#include "MemoryPool.hpp" #include "MemoryPool.hpp"
#include "Async.hpp" #include "Async.hpp"
#include "TOSLib.hpp" #include "TOSLib.hpp"
#include "boost/asio/io_context.hpp"
#include <boost/asio.hpp> #include <boost/asio.hpp>
#include <boost/asio/buffer.hpp> #include <boost/asio/buffer.hpp>
#include <boost/asio/write.hpp> #include <boost/asio/write.hpp>
#include <boost/thread.hpp> #include <boost/thread.hpp>
#include <boost/circular_buffer.hpp> #include <boost/circular_buffer.hpp>
#include <type_traits>
namespace LV::Net { namespace LV::Net {
class SocketServer : public AsyncObject { class SocketServer {
protected: protected:
asio::io_context &IOC;
tcp::acceptor Acceptor; tcp::acceptor Acceptor;
public: public:
SocketServer(asio::io_context &ioc, std::function<coro<>(tcp::socket)> &&onConnect, uint16_t port = 0) SocketServer(asio::io_context &ioc, std::function<coro<>(tcp::socket)> &&onConnect, uint16_t port = 0)
: AsyncObject(ioc), Acceptor(ioc, tcp::endpoint(tcp::v4(), port)) : IOC(ioc), Acceptor(ioc, tcp::endpoint(tcp::v4(), port))
{ {
assert(onConnect); assert(onConnect);
co_spawn(run(std::move(onConnect))); asio::co_spawn(IOC, run(std::move(onConnect)), asio::detached);
} }
bool isStopped(); bool isStopped();
@@ -39,10 +37,10 @@ protected:
}; };
#if defined(__BYTE_ORDER) && __BYTE_ORDER == __LITTLE_ENDIAN #if defined(__BYTE_ORDER) && __BYTE_ORDER == __LITTLE_ENDIAN
template <typename T, std::enable_if_t<std::is_floating_point_v<T> or std::is_integral_v<T>, int> = 0> template <typename T, std::enable_if_t<std::is_integral_v<T>, int> = 0>
static inline T swapEndian(const T &u) { return u; } static inline T swapEndian(const T &u) { return u; }
#else #else
template <typename T, std::enable_if_t<std::is_floating_point_v<T> or std::is_integral_v<T>, int> = 0> template <typename T, std::enable_if_t<std::is_integral_v<T>, int> = 0>
static inline T swapEndian(const T &u) { static inline T swapEndian(const T &u) {
if constexpr (sizeof(T) == 1) { if constexpr (sizeof(T) == 1) {
return u; return u;
@@ -64,7 +62,7 @@ protected:
using NetPool = BoostPool<12, 14>; using NetPool = BoostPool<12, 14>;
class Packet { class Packet {
static constexpr size_t MAX_PACKET_SIZE = 1 << 24; static constexpr size_t MAX_PACKET_SIZE = 1 << 16;
uint16_t Size = 0; uint16_t Size = 0;
std::vector<NetPool::PagePtr> Pages; std::vector<NetPool::PagePtr> Pages;
@@ -108,7 +106,7 @@ protected:
return *this; return *this;
} }
template<typename T, std::enable_if_t<std::is_integral_v<T> || std::is_floating_point_v<T>, int> = 0> template<typename T, std::enable_if_t<std::is_integral_v<T>, int> = 0>
inline Packet& write(T u) { inline Packet& write(T u) {
u = swapEndian(u); u = swapEndian(u);
write((const std::byte*) &u, sizeof(u)); write((const std::byte*) &u, sizeof(u));
@@ -129,7 +127,7 @@ protected:
inline uint16_t size() const { return Size; } inline uint16_t size() const { return Size; }
inline const std::vector<NetPool::PagePtr>& getPages() const { return Pages; } inline const std::vector<NetPool::PagePtr>& getPages() const { return Pages; }
template<typename T, std::enable_if_t<std::is_floating_point_v<T> || std::is_integral_v<T> or std::is_convertible_v<T, std::string_view>, int> = 0> template<typename T, std::enable_if_t<std::is_integral_v<T> or std::is_convertible_v<T, std::string_view>, int> = 0>
inline Packet& operator<<(const T &value) { inline Packet& operator<<(const T &value) {
if constexpr (std::is_convertible_v<T, std::string_view>) if constexpr (std::is_convertible_v<T, std::string_view>)
return write((std::string_view) value); return write((std::string_view) value);
@@ -146,7 +144,7 @@ protected:
Size = 0; Size = 0;
} }
Packet& complite(std::u8string &out) { Packet& complite(std::vector<std::byte> &out) {
out.resize(Size); out.resize(Size);
for(size_t pos = 0; pos < Size; pos += NetPool::PageSize) { for(size_t pos = 0; pos < Size; pos += NetPool::PageSize) {
@@ -157,8 +155,8 @@ protected:
return *this; return *this;
} }
std::u8string complite() { std::vector<std::byte> complite() {
std::u8string out; std::vector<std::byte> out;
complite(out); complite(out);
return out; return out;
} }
@@ -174,81 +172,23 @@ protected:
} }
}; };
class LinearReader {
public:
LinearReader(const std::u8string& input, size_t pos = 0)
: Pos(pos), Input(input)
{}
LinearReader(const std::u8string_view input, size_t pos = 0)
: Pos(pos), Input(input)
{}
LinearReader(const LinearReader&) = delete;
LinearReader(LinearReader&&) = delete;
LinearReader& operator=(const LinearReader&) = delete;
LinearReader& operator=(LinearReader&&) = delete;
void read(std::byte *data, uint32_t size) {
if(Input.size()-Pos < size)
MAKE_ERROR("Недостаточно данных");
std::copy((const std::byte*) Input.data()+Pos, (const std::byte*) Input.data()+Pos+size, data);
Pos += size;
}
template<typename T, std::enable_if_t<std::is_floating_point_v<T> || std::is_integral_v<T> or std::is_same_v<T, std::string>, int> = 0>
T read() {
if constexpr(std::is_floating_point_v<T> || std::is_integral_v<T>) {
T value;
read((std::byte*) &value, sizeof(value));
return swapEndian(value);
} else {
uint16_t size = read<uint16_t>();
T value(size, ' ');
read((std::byte*) value.data(), size);
return value;
}
}
template<typename T>
LinearReader& read(T& val) {
val = read<T>();
return *this;
}
template<typename T>
LinearReader& operator>>(T& val) {
val = read<T>();
return *this;
}
void checkUnreaded() {
if(Pos != Input.size())
MAKE_ERROR("Остались не использованные данные");
}
private:
size_t Pos = 0;
const std::u8string_view Input;
};
class SmartPacket : public Packet { class SmartPacket : public Packet {
public: public:
std::function<bool()> IsStillRelevant; std::function<bool()> IsStillRelevant;
std::function<std::optional<SmartPacket>()> OnSend; std::function<std::optional<SmartPacket>()> OnSend;
}; };
class AsyncSocket : public AsyncObject { class AsyncSocket {
asio::io_context &IOC;
NetPool::Array<32> RecvBuffer, SendBuffer; NetPool::Array<32> RecvBuffer, SendBuffer;
size_t RecvPos = 0, RecvSize = 0, SendSize = 0; size_t RecvPos = 0, RecvSize = 0, SendSize = 0;
bool ReadShutdowned = false; bool ReadShutdowned = false;
tcp::socket Socket; tcp::socket Socket;
static constexpr uint32_t static constexpr uint32_t
MAX_SIMPLE_PACKETS = 16384, MAX_SIMPLE_PACKETS = 8192,
MAX_SMART_PACKETS = MAX_SIMPLE_PACKETS/4, MAX_SMART_PACKETS = MAX_SIMPLE_PACKETS/4,
MAX_PACKETS_SIZE_IN_WAIT = 1 << 26; MAX_PACKETS_SIZE_IN_WAIT = 1 << 24;
struct AsyncContext { struct AsyncContext {
volatile bool NeedShutdown = false, RunSendShutdowned = false; volatile bool NeedShutdown = false, RunSendShutdowned = false;
@@ -257,32 +197,31 @@ protected:
struct SendPacketsObj { struct SendPacketsObj {
boost::mutex Mtx; boost::mutex Mtx;
bool WaitForSemaphore = false; asio::deadline_timer Semaphore;
asio::deadline_timer Semaphore, SenderGuard;
boost::circular_buffer_space_optimized<Packet> SimpleBuffer; boost::circular_buffer_space_optimized<Packet> SimpleBuffer;
boost::circular_buffer_space_optimized<SmartPacket> SmartBuffer; boost::circular_buffer_space_optimized<SmartPacket> SmartBuffer;
size_t SizeInQueue = 0; size_t SizeInQueue = 0;
std::shared_ptr<AsyncContext> Context; std::shared_ptr<AsyncContext> Context;
SendPacketsObj(asio::io_context &ioc) SendPacketsObj(asio::io_context &ioc)
: Semaphore(ioc, boost::posix_time::pos_infin), SenderGuard(ioc, boost::posix_time::pos_infin) : Semaphore(ioc, boost::posix_time::pos_infin)
{} {}
} SendPackets; } SendPackets;
public: public:
AsyncSocket(asio::io_context &ioc, tcp::socket &&socket) AsyncSocket(asio::io_context &ioc, tcp::socket &&socket)
: AsyncObject(ioc), Socket(std::move(socket)), SendPackets(ioc) : IOC(ioc), Socket(std::move(socket)), SendPackets(ioc)
{ {
SendPackets.SimpleBuffer.set_capacity(512); SendPackets.SimpleBuffer.set_capacity(512);
SendPackets.SmartBuffer.set_capacity(SendPackets.SimpleBuffer.capacity()/4); SendPackets.SmartBuffer.set_capacity(SendPackets.SimpleBuffer.capacity()/4);
SendPackets.Context = std::make_shared<AsyncContext>(); SendPackets.Context = std::make_shared<AsyncContext>();
boost::asio::socket_base::linger optionLinger(true, 4); // После закрытия сокета оставшиеся данные будут доставлены boost::asio::socket_base::linger optionLinger(true, 4); // После закрытия сокета оставшиеся данные будут доставлены
Socket.set_option(optionLinger); Socket.set_option(optionLinger);
boost::asio::ip::tcp::no_delay optionNoDelay(true); // Отключает попытки объединить данные в крупные пакеты boost::asio::ip::tcp::no_delay optionNoDelay(true); // Отключает попытки объёденить данные в крупные пакеты
Socket.set_option(optionNoDelay); Socket.set_option(optionNoDelay);
co_spawn(runSender(SendPackets.Context)); asio::co_spawn(IOC, runSender(SendPackets.Context), asio::detached);
} }
~AsyncSocket(); ~AsyncSocket();
@@ -300,10 +239,11 @@ protected:
coro<> read(std::byte *data, uint32_t size); coro<> read(std::byte *data, uint32_t size);
void closeRead(); void closeRead();
void close();
template<typename T, std::enable_if_t<std::is_floating_point_v<T> or std::is_integral_v<T> or std::is_same_v<T, std::string>, int> = 0> template<typename T, std::enable_if_t<std::is_integral_v<T> or std::is_same_v<T, std::string>, int> = 0>
coro<T> read() { coro<T> read() {
if constexpr(std::is_floating_point_v<T> or std::is_integral_v<T>) { if constexpr(std::is_integral_v<T>) {
T value; T value;
co_await read((std::byte*) &value, sizeof(value)); co_await read((std::byte*) &value, sizeof(value));
co_return swapEndian(value); co_return swapEndian(value);
@@ -321,9 +261,9 @@ protected:
co_await asio::async_read(socket, asio::mutable_buffer(data, size)); co_await asio::async_read(socket, asio::mutable_buffer(data, size));
} }
template<typename T, std::enable_if_t<std::is_floating_point_v<T> or std::is_integral_v<T> or std::is_same_v<T, std::string>, int> = 0> template<typename T, std::enable_if_t<std::is_integral_v<T> or std::is_same_v<T, std::string>, int> = 0>
static inline coro<T> read(tcp::socket &socket) { static inline coro<T> read(tcp::socket &socket) {
if constexpr(std::is_floating_point_v<T> or std::is_integral_v<T>) { if constexpr(std::is_integral_v<T>) {
T value; T value;
co_await read(socket, (std::byte*) &value, sizeof(value)); co_await read(socket, (std::byte*) &value, sizeof(value));
co_return swapEndian(value); co_return swapEndian(value);
@@ -338,7 +278,7 @@ protected:
co_await asio::async_write(socket, asio::const_buffer(data, size)); co_await asio::async_write(socket, asio::const_buffer(data, size));
} }
template<typename T, std::enable_if_t<std::is_floating_point_v<T> or std::is_integral_v<T>, int> = 0> template<typename T, std::enable_if_t<std::is_integral_v<T>, int> = 0>
static inline coro<> write(tcp::socket &socket, T u) { static inline coro<> write(tcp::socket &socket, T u) {
u = swapEndian(u); u = swapEndian(u);
co_await write(socket, (const std::byte*) &u, sizeof(u)); co_await write(socket, (const std::byte*) &u, sizeof(u));

View File

@@ -24,45 +24,36 @@ struct PacketQuat {
z = (quat.z+1)/2*0x3ff, z = (quat.z+1)/2*0x3ff,
w = (quat.w+1)/2*0x3ff; w = (quat.w+1)/2*0x3ff;
uint64_t value = 0; for(uint8_t &val : Data)
val = 0;
value |= x & 0x3ff; *(uint16_t*) Data |= x;
value |= uint64_t(y & 0x3ff) << 10; *(uint16_t*) (Data+1) |= y << 2;
value |= uint64_t(z & 0x3ff) << 20; *(uint16_t*) (Data+2) |= z << 4;
value |= uint64_t(w & 0x3ff) << 30; *(uint16_t*) (Data+3) |= w << 6;
for(int iter = 0; iter < 5; iter++)
Data[iter] = (value >> (iter*8)) & 0xff;
} }
glm::quat toQuat() const { glm::quat toQuat() const {
uint64_t value = 0; const uint64_t &data = (const uint64_t&) *Data;
for(int iter = 0; iter < 5; iter++)
value |= uint64_t(Data[iter]) << (iter*8);
uint16_t uint16_t
x = value & 0x3ff, x = data & 0x3ff,
y = (value >> 10) & 0x3ff, y = (data >> 10) & 0x3ff,
z = (value >> 20) & 0x3ff, z = (data >> 20) & 0x3ff,
w = (value >> 30) & 0x3ff; w = (data >> 30) & 0x3ff;
float fx = (float(x)/0x3ff)*2-1; float fx = (float(x)/0x3ff)*2-1;
float fy = (float(y)/0x3ff)*2-1; float fy = (float(y)/0x3ff)*2-1;
float fz = (float(z)/0x3ff)*2-1; float fz = (float(z)/0x3ff)*2-1;
float fw = (float(w)/0x3ff)*2-1; float fw = (float(w)/0x3ff)*2-1;
return glm::quat(fw, fx, fy, fz); return glm::quat(fx, fy, fz, fw);
} }
}; };
/* /*
uint8_t+uint8_t uint8_t+uint8_t
0 - Системное 0 - Системное
0 - 0 - Новая позиция камеры WorldId_c+ObjectPos+PacketQuat
1 -
2 - Новая позиция камеры WorldId_c+ObjectPos+PacketQuat
3 - Изменение блока
*/ */
@@ -75,34 +66,111 @@ enum struct L1 : uint8_t {
enum struct L2System : uint8_t { enum struct L2System : uint8_t {
InitEnd, InitEnd,
Disconnect, Disconnect,
Test_CAM_PYR_POS, Test_CAM_PYR_POS
BlockChange,
ResourceRequest,
ReloadMods
}; };
} }
enum struct ToClient : uint8_t { namespace ToClient {
Init, // Первый пакет от сервера
Disconnect, // Отключаем клиента
AssetsBindDK, // Привязка AssetsId к домен+ключ /*
AssetsBindHH, // Привязка AssetsId к hash+header uint8_t+uint8_t
AssetsInitSend, // Начало отправки запрошенного клиентом ресурса 0 - Системное
AssetsNextSend, // Продолжение отправки ресурса 0 - Инициализация WorldId_c+ObjectPos
1 - Отключение от сервера String(Причина)
2 - Привязка камеры к сущности EntityId_c
3 - Отвязка камеры
1 - Оповещение о доступном ресурсе
0 - Текстура TextureId_c+Hash
1 - Освобождение текстуры TextureId_c
2 - Звук SoundId_c+Hash
3 - Освобождение звука SoundId_c
4 - Модель ModelId_c+Hash
5 - Освобождение модели ModelId_c
253 - Инициирование передачи ресурса StreamId+ResType+ResId+Size+Hash
254 - Передача чанка данных StreamId+Size+Data
255 - Передача отменена StreamId
2 - Новые определения
0 - Мир DefWorldId_c+определение
1 - Освобождение мира DefWorldId_c
2 - Воксель DefVoxelId_c+определение
3 - Освобождение вокселя DefVoxelId_c
4 - Нода DefNodeId_c+определение
5 - Освобождение ноды DefNodeId_c
6 - Портал DefPortalId_c+определение
7 - Освобождение портала DefPortalId_c
8 - Сущность DefEntityId_c+определение
9 - Освобождение сущности DefEntityId_c
3 - Новый контент
0 - Мир, новый/изменён WorldId_c+...
1 - Мир/Удалён WorldId_c
2 - Портал, новый/изменён PortalId_c+...
3 - Портал/Удалён PortalId_c
4 - Сущность, новый/изменён EntityId_c+...
5 - Сущность/Удалёна EntityId_c
6 - Чанк/Воксели WorldId_c+GlobalChunk+...
7 - Чанк/Ноды WorldId_c+GlobalChunk+...
8 - Чанк/Призмы освещения WorldId_c+GlobalChunk+...
9 - Чанк/Удалён WorldId_c+GlobalChunk
DefinitionsUpdate, // Обновление и потеря профилей контента (воксели, ноды, сущности, миры, ...)
ChunkVoxels, // Обновление вокселей чанка
ChunkNodes, // Обновление нод чанка
ChunkLightPrism, //
RemoveRegion, // Удаление региона из зоны видимости
Tick, // Новые или потерянные игровые объекты (миры, сущности), динамичные данные такта (положение сущностей) */
TestLinkCameraToEntity, // Привязываем камеру к сущности // Первый уровень
TestUnlinkCamera, // Отвязываем от сущности enum struct L1 : uint8_t {
System,
Resource,
Definition,
Content
};
// Второй уровень
enum struct L2System : uint8_t {
Init,
Disconnect,
LinkCameraToEntity,
UnlinkCamera
};
enum struct L2Resource : uint8_t {
Texture,
FreeTexture,
Sound,
FreeSound,
Model,
FreeModel,
InitResSend = 253,
ChunkSend,
SendCanceled
};
enum struct L2Definition : uint8_t {
World,
FreeWorld,
Voxel,
FreeVoxel,
Node,
FreeNode,
Portal,
FreePortal,
Entity,
FreeEntity
};
enum struct L2Content : uint8_t {
World,
RemoveWorld,
Portal,
RemovePortal,
Entity,
RemoveEntity,
ChunkVoxels,
ChunkNodes,
ChunkLightPrism,
RemoveChunk
}; };
} }
}

File diff suppressed because it is too large Load Diff

422
Src/Common/async_mutex.hpp Normal file
View File

@@ -0,0 +1,422 @@
// SPDX-FileCopyrightText: 2023 Daniel Vrátil <daniel.vratil@gendigital.com>
// SPDX-FileCopyrightText: 2023 Martin Beran <martin.beran@gendigital.com>
//
// SPDX-License-Identifier: BSL-1.0
#pragma once
#include <boost/asio/associated_executor.hpp>
#include <boost/asio/awaitable.hpp>
#include <boost/asio/async_result.hpp>
#include <boost/asio/post.hpp>
#include <boost/asio/use_awaitable.hpp>
#include <atomic>
#define ASIO_NS boost::asio
namespace avast::asio {
class async_mutex_lock;
class async_mutex;
/** \internal **/
namespace detail {
/**
* \brief Represents a suspended coroutine that is awaiting lock acquisition.
**/
struct locked_waiter {
/**
* \brief Constructs a new locked_waiter.
* \param next_waiter Pointer to the waiter to prepend this locked_waiter to.
**/
explicit locked_waiter(locked_waiter *next_waiter): next(next_waiter) {}
locked_waiter(locked_waiter &&) = delete;
locked_waiter(const locked_waiter &) = delete;
locked_waiter &operator=(locked_waiter &&) = delete;
locked_waiter &operator=(const locked_waiter &) = delete;
/**
* \brief Destructor.
**/
virtual ~locked_waiter() = default;
/**
* \brief Completes the pending asynchronous operation.
*
* Resumes the currently suspended coroutine with the acquired lock.
**/
virtual void completion() = 0;
/**
* The waiters are held in a linked list. This is a pointer to the next member of the list.
**/
locked_waiter *next = nullptr;
};
/**
* \brief Locked waiter that used `async_mutex::async_lock()` to acquire the lock.
**/
template <typename Token>
struct async_locked_waiter final: public locked_waiter {
/**
* \brief Constructs a new async_locked_waiter.
* \param mutex A mutex that the waiter is trying to acquire a lock for.
* \param next_waiter Pointer to the head of the waiters linked list to prepend this waiter to.
* \param token The complention token to call when the asynchronous operation is completed.
**/
async_locked_waiter([[maybe_unused]] async_mutex *mutex, locked_waiter *next_waiter, Token &&token):
locked_waiter(next_waiter), m_token(std::move(token)) {}
void completion() override {
auto executor = ASIO_NS::get_associated_executor(m_token);
ASIO_NS::post(std::move(executor), [token = std::move(m_token)]() mutable { token(); });
}
private:
Token m_token; //!< The completion token to invoke when the lock is acquired.
};
/**
* \brief Locked waiter that used `async_mutex::async_scoped_lock()` to acquire the lock.
**/
template <typename Token>
struct scoped_async_locked_waiter final: public locked_waiter {
/**
* \brief Constructs a new scoped_async_locked_waiter.
* \param mutex A mutex that the waiter is trying to acquire a lock for.
* \param next_waiter Pointer to the head of the waiters linked list to prepend this waiter to.
* \param token The complention token to call when the asynchronous operation is completed.
**/
scoped_async_locked_waiter(async_mutex *mutex, locked_waiter *next_waiter, Token &&token):
locked_waiter(next_waiter), m_mutex(mutex), m_token(std::move(token)) {}
void completion() override;
private:
async_mutex *m_mutex; //!< The mutex whose lock is being awaited.
Token m_token; //!< The completion token to invoke when the lock is acquired.
};
/**
* \brief An initiator for asio::async_initiate().
**/
template <template <typename Token> typename Waiter>
class async_lock_initiator_base {
public:
/**
* Constructs a new initiator for an operation on the given mutex.
*
* \param mutex A mutex on which the asynchronous lock operation is being initiated.
**/
explicit async_lock_initiator_base(async_mutex *mutex): m_mutex(mutex) {}
/**
* \brief Invoked by boost asio when the asynchronous operation is initiated.
*
* \param handler A completion handler (a callable) to be called when the asynchronous operation
* has completed (in our case, the lock has been acquired).
* \tparam Handler A callable with signature void(T) where T is the type of the object that will be
* returned as a result of `co_await`ing the operation. In our case that's either
* `void` for `async_lock()` or `async_mutex_lock` for `async_scoped_lock()`.
**/
template <typename Handler>
void operator()(Handler &&handler);
protected:
async_mutex *m_mutex; //!< The mutex whose lock is being awaited.
};
/**
* \brief Initiator for the async_lock() operation.
**/
using initiate_async_lock = async_lock_initiator_base<async_locked_waiter>;
/**
* \brief Initiator for the async_scoped_lock() operation.
**/
using initiate_scoped_async_lock = async_lock_initiator_base<scoped_async_locked_waiter>;
} // namespace detail
/** \endinternal **/
/**
* \brief A basic mutex that can acquire lock asynchronously using asio coroutines.
**/
class async_mutex {
public:
/**
* \brief Constructs a new unlocked mutex.
**/
async_mutex() noexcept = default;
async_mutex(const async_mutex &) = delete;
async_mutex(async_mutex &&) = delete;
async_mutex &operator=(const async_mutex &) = delete;
async_mutex &operator=(async_mutex &&) = delete;
/**
* \brief Destroys the mutex.
*
* \warning Destroying a mutex in locked state is undefined.
**/
~async_mutex() {
[[maybe_unused]] const auto state = m_state.load(std::memory_order_relaxed);
assert(state == not_locked || state == locked_no_waiters);
assert(m_waiters == nullptr);
}
/**
* \brief Attempts to acquire lock without blocking.
*
* \return Returns `true` when the lock has been acquired, `false` when the
* lock is already held by someone else.
* **/
[[nodiscard]] bool try_lock() noexcept {
auto old_state = not_locked;
return m_state.compare_exchange_strong(old_state, locked_no_waiters, std::memory_order_acquire,
std::memory_order_relaxed);
}
/**
* \brief Asynchronously acquires as lock.
*
* When the returned awaitable is `co_await`ed it initiates the process
* of acquiring a lock. The awaiter is suspended. Once the lock is acquired
* (which can be immediately if nothing else holds the lock currently) the
* awaiter is resumed and is now holding the lock.
*
* It's awaiter's responsibility to release the lock by calling `unlock()`.
*
* \param token A completion token (`asio::use_awaitable`).
* \tparam LockToken Type of the complention token.
* \return An awaitable which will initiate the async operation when `co_await`ed.
* The result of `co_await`ing the awaitable is void.
**/
#ifdef DOXYGEN
template <typename LockToken>
ASIO_NS::awaitable<> async_lock(LockToken &&token);
#else
template <ASIO_NS::completion_token_for<void()> LockToken>
[[nodiscard]] auto async_lock(LockToken &&token) {
return ASIO_NS::async_initiate<LockToken, void()>(detail::initiate_async_lock(this), token);
}
#endif
/**
* \brief Asynchronously acquires a lock and returns a scoped lock helper.
*
* Behaves exactly as `async_lock()`, except that the result of `co_await`ing the
* returned awaitable is a scoped lock object, which will automatically release the
* lock when destroyed.
*
* \param token A completion token (`asio::use_awaitable`).
* \tparam LockToken Type of the completion token.
* \returns An awaitable which will initiate the async operation when `co_await`ed.
* The result of `co_await`ing the awaitable is `async_mutex_lock` holding
* the acquired lock.
**/
#ifdef DOXYGEN
template <typename LockToken>
ASIO_NS::awaitable<async_mutex_lock> async_scoped_lock(LockToken &&token);
#else
template <ASIO_NS::completion_token_for<void(async_mutex_lock)> LockToken>
[[nodiscard]] auto async_scoped_lock(LockToken &&token) {
return ASIO_NS::async_initiate<LockToken, void(async_mutex_lock)>(detail::initiate_scoped_async_lock(this),
token);
}
#endif
/**
* \brief Releases the lock.
*
* \warning Unlocking and already unlocked mutex is undefined.
**/
void unlock() {
assert(m_state.load(std::memory_order_relaxed) != not_locked);
auto *waiters_head = m_waiters;
if (waiters_head == nullptr) {
auto old_state = locked_no_waiters;
// If old state was locked_no_waiters then transitions to not_locked and returns true,
// otherwise do nothing and returns false.
const bool released_lock = m_state.compare_exchange_strong(old_state, not_locked, std::memory_order_release,
std::memory_order_relaxed);
if (released_lock) {
return;
}
// At least one new waiter. Acquire the list of new waiters atomically
old_state = m_state.exchange(locked_no_waiters, std::memory_order_acquire);
assert(old_state != locked_no_waiters && old_state != not_locked);
// Transfer the list to m_waiters, reversing the list in the process
// so that the head of the list is the first waiter to be resumed
// NOLINTNEXTLINE(performance-no-int-to-ptr)
auto *next = reinterpret_cast<detail::locked_waiter *>(old_state);
do {
auto *temp = next->next;
next->next = waiters_head;
waiters_head = next;
next = temp;
} while (next != nullptr);
}
assert(waiters_head != nullptr);
m_waiters = waiters_head->next;
// Complete the async operation.
waiters_head->completion();
delete waiters_head;
}
private:
template <template <typename Token> typename Waiter>
friend class detail::async_lock_initiator_base;
/**
* \brief Indicates that the mutex is not locked.
**/
static constexpr std::uintptr_t not_locked = 1;
/**
* \brief Indicates that the mutex is locked, but no-one else is attempting to acquire the lock at the moment.
**/
static constexpr std::uintptr_t locked_no_waiters = 0;
/**
* \brief Holds the current state of the lock.
*
* The state can be `not_locked`, `locked_no_waiters` or a pointer to the head of a linked list
* of new waiters (waiters who have attempted to acquire the lock since the last call to unlock().
**/
std::atomic<std::uintptr_t> m_state = {not_locked};
/**
* \brief Linked list of known locked waiters.
**/
detail::locked_waiter *m_waiters = nullptr;
};
/**
* \brief A RAII-style lock for async_mutex which automatically unlocks the mutex when destroyed.
**/
class async_mutex_lock {
public:
using mutex_type = async_mutex;
/**
* Constructs a new async_mutex_lock without any associated mutex.
**/
explicit async_mutex_lock() noexcept = default;
/**
* Constructs a new async_mutex_lock, taking ownership of the \c mutex.
*
* \param mutex Locked mutex to be unlocked when this objectis destroyed.
*
* \warning The \c mutex must be in a locked state.
**/
explicit async_mutex_lock(mutex_type &mutex, std::adopt_lock_t) noexcept: m_mutex(&mutex) {}
/**
* \brief Initializes the lock with contents of other. Leaves other with no associated mutex.
* \param other The moved-from object.
**/
async_mutex_lock(async_mutex_lock &&other) noexcept { swap(other); }
/**
* \brief Move assignment operator.
* Replaces the current mutex with those of \c other using move semantics.
* If \c *this already has an associated mutex, the mutex is unlocked.
*
* \param other The moved-from object.
* \returns *this.
*/
async_mutex_lock &operator=(async_mutex_lock &&other) noexcept {
if (m_mutex != nullptr) {
m_mutex->unlock();
}
m_mutex = std::exchange(other.m_mutex, nullptr);
return *this;
}
/**
* \brief Copy constructor (deleted).
**/
async_mutex_lock(const async_mutex_lock &) = delete;
/**
* \brief Copy assignment operator (deleted).
**/
async_mutex_lock &operator=(const async_mutex_lock &) = delete;
~async_mutex_lock() {
if (m_mutex != nullptr) {
m_mutex->unlock();
}
}
bool owns_lock() const noexcept { return m_mutex != nullptr; }
mutex_type *mutex() const noexcept { return m_mutex; }
/**
* \brief Swaps state with \c other.
* \param other the lock to swap state with.
**/
void swap(async_mutex_lock &other) noexcept { std::swap(m_mutex, other.m_mutex); }
private:
mutex_type *m_mutex = nullptr; //!< The locked mutex being held by the scoped mutex lock.
};
/** \internal **/
namespace detail {
template <typename Token>
void scoped_async_locked_waiter<Token>::completion() {
auto executor = ASIO_NS::get_associated_executor(m_token);
ASIO_NS::post(std::move(executor), [token = std::move(m_token), mutex = m_mutex]() mutable {
token(async_mutex_lock{*mutex, std::adopt_lock});
});
}
template <template <typename Token> typename Waiter>
template <typename Handler>
void async_lock_initiator_base<Waiter>::operator()(Handler &&handler) {
auto old_state = m_mutex->m_state.load(std::memory_order_acquire);
std::unique_ptr<Waiter<Handler>> waiter;
while (true) {
if (old_state == async_mutex::not_locked) {
if (m_mutex->m_state.compare_exchange_weak(old_state, async_mutex::locked_no_waiters,
std::memory_order_acquire, std::memory_order_relaxed))
{
// Lock acquired, resume the awaiter stright away
if (waiter) {
waiter->next = nullptr;
waiter->completion();
} else {
Waiter(m_mutex, nullptr, std::forward<Handler>(handler)).completion();
}
return;
}
} else {
if (!waiter) {
// NOLINTNEXTLINE(performance-no-int-to-ptr)
auto *next_waiter = reinterpret_cast<locked_waiter *>(old_state);
waiter.reset(new Waiter(m_mutex, next_waiter, std::forward<Handler>(handler)));
} else {
// NOLINTNEXTLINE(performance-no-int-to-ptr)
waiter->next = reinterpret_cast<locked_waiter *>(old_state);
}
if (m_mutex->m_state.compare_exchange_weak(old_state, reinterpret_cast<std::uintptr_t>(waiter.get()),
std::memory_order_release, std::memory_order_relaxed))
{
waiter.release();
return;
}
}
}
}
} // namespace detail
/** \endinternal **/
} // namespace avast::asio

View File

@@ -1,29 +0,0 @@
#include "Abstract.hpp"
#include <csignal>
namespace LV::Server {
Entity::Entity(DefEntityId defId)
: DefId(defId)
{
ABBOX = {Pos::Object_t::BS, Pos::Object_t::BS, Pos::Object_t::BS};
WorldId = 0;
Pos = Pos::Object(0);
Speed = Pos::Object(0);
Acceleration = Pos::Object(0);
Quat = glm::quat(1.f, 0.f, 0.f, 0.f);
InRegionPos = Pos::GlobalRegion(0);
}
}
namespace std {
template <>
struct hash<LV::Server::ServerObjectPos> {
std::size_t operator()(const LV::Server::ServerObjectPos& obj) const {
return std::hash<uint32_t>()(obj.WorldId) ^ std::hash<LV::Pos::Object>()(obj.ObjectPos);
}
};
}

View File

@@ -1,54 +1,62 @@
#pragma once #pragma once
#include "TOSLib.hpp"
#include <algorithm>
#include <bitset>
#include <cctype>
#include <cstdint> #include <cstdint>
#include <Common/Abstract.hpp> #include <Common/Abstract.hpp>
#include <Common/Collide.hpp> #include <Common/Collide.hpp>
#include <sha2.hpp> #include <boost/uuid/detail/sha1.hpp>
#include <sol/sol.hpp>
#include <string>
#include <unordered_map>
#include <boost/json.hpp>
#include <variant>
namespace LV::Server { namespace LV::Server {
namespace js = boost::json; using ResourceId_t = uint32_t;
// Двоичные данные
using BinTextureId_t = ResourceId_t;
using BinSoundId_t = ResourceId_t;
using BinModelId_t = ResourceId_t;
// Игровые определения
using DefWorldId_t = ResourceId_t;
using DefVoxelId_t = ResourceId_t;
using DefNodeId_t = ResourceId_t;
using DefPortalId_t = ResourceId_t;
using DefEntityId_t = ResourceId_t;
// Контент, основанный на игровых определениях
using WorldId_t = ResourceId_t;
// В одном регионе может быть максимум 2^16 сущностей. Клиенту адресуются сущности в формате <мир>+<позиция региона>+<uint16_t> // В одном регионе может быть максимум 2^16 сущностей. Клиенту адресуются сущности в формате <мир>+<позиция региона>+<uint16_t>
// И если сущность перешла из одного региона в другой, идентификатор сущности на стороне клиента сохраняется // И если сущность перешла из одного региона в другой, идентификатор сущности на стороне клиента сохраняется
using RegionEntityId_t = uint16_t; using LocalEntityId_t = uint16_t;
using ClientEntityId_t = RegionEntityId_t; using GlobalEntityId_t = std::tuple<WorldId_t, Pos::GlobalRegion, LocalEntityId_t>;
using ServerEntityId_t = std::tuple<WorldId_t, Pos::GlobalRegion, RegionEntityId_t>; using PortalId_t = uint16_t;
using RegionFuncEntityId_t = uint16_t;
using ClientFuncEntityId_t = RegionFuncEntityId_t;
using ServerFuncEntityId_t = std::tuple<WorldId_t, Pos::GlobalRegion, RegionFuncEntityId_t>;
using MediaStreamId_t = uint16_t; using MediaStreamId_t = uint16_t;
using ContentBridgeId_t = ResourceId; using ContentBridgeId_t = uint16_t;
using PlayerId_t = ResourceId; using PlayerId_t = uint32_t;
using DefGeneratorId_t = ResourceId;
/* /*
Сервер загружает информацию о локальных текстурах Сервер загружает информацию о локальных текстурах
Синхронизация часто используемых текстур?
Пересмотр списка текстур? Пересмотр списка текстур?
Динамичные текстуры? Динамичные текстуры?
*/ */
struct ResourceFile { struct ResourceFile {
using Hash_t = sha2::sha256_hash; // boost::uuids::detail::sha1::digest_type; using Hash_t = boost::uuids::detail::sha1::digest_type;
Hash_t Hash; Hash_t Hash;
std::vector<std::byte> Data; std::vector<std::byte> Data;
void calcHash() { void calcHash() {
Hash = sha2::sha256((const uint8_t*) Data.data(), Data.size()); boost::uuids::detail::sha1 hash;
hash.process_bytes(Data.data(), Data.size());
hash.get_digest(Hash);
} }
}; };
@@ -56,43 +64,37 @@ struct ServerTime {
uint32_t Seconds : 24, Sub : 8; uint32_t Seconds : 24, Sub : 8;
}; };
struct VoxelCube_Region { struct VoxelCube {
union { DefVoxelId_t VoxelId;
struct { Pos::Local256_u Left, Right;
DefVoxelId VoxelId : 24, Meta : 8;
};
DefVoxelId Data = 0; auto operator<=>(const VoxelCube&) const = default;
};
Pos::bvec1024u Left, Right; // TODO: заменить на позицию и размер
auto operator<=>(const VoxelCube_Region& other) const {
if (auto cmp = Left <=> other.Left; cmp != 0)
return cmp;
if (auto cmp = Right <=> other.Right; cmp != 0)
return cmp;
return Data <=> other.Data;
}
bool operator==(const VoxelCube_Region& other) const {
return Left == other.Left && Right == other.Right && Data == other.Data;
}
}; };
struct VoxelCube_Region {
Pos::Local4096_u Left, Right;
DefVoxelId_t VoxelId;
auto operator<=>(const VoxelCube_Region&) const = default;
};
struct Node {
DefNodeId_t NodeId;
uint8_t Rotate : 6;
};
struct AABB { struct AABB {
Pos::Object VecMin, VecMax; Pos::Object VecMin, VecMax;
void sortMinMax() { void sortMinMax() {
Pos::Object::Type left, right; Pos::Object::value_type left, right;
for(int iter = 0; iter < 3; iter++) { for(int iter = 0; iter < 3; iter++) {
left = std::min(VecMin[iter], VecMax[iter]); left = std::min(VecMin[iter], VecMax[iter]);
right = std::max(VecMin[iter], VecMax[iter]); right = std::max(VecMin[iter], VecMax[iter]);
VecMin.set(iter, left); VecMin[iter] = left;
VecMax.set(iter, right); VecMax[iter] = right;
} }
} }
@@ -115,26 +117,22 @@ struct LocalAABB {
struct CollisionAABB : public AABB { struct CollisionAABB : public AABB {
enum struct EnumType { enum struct EnumType {
Voxel, Node, Entity, FuncEntity, Barrier, Portal, Another Voxel, Node, Entity, Barrier, Portal, Another
} Type; } Type;
union { union {
struct { struct {
RegionEntityId_t Index; LocalEntityId_t Index;
} Entity; } Entity;
struct { struct {
RegionFuncEntityId_t Index; Pos::Local16_u Pos;
} FuncEntity;
struct {
Pos::bvec4u Chunk;
Pos::bvec16u Pos;
} Node; } Node;
struct { struct {
Pos::bvec4u Chunk; Pos::Local16_u Chunk;
uint32_t Index; uint32_t Index;
DefVoxelId_t Id;
} Voxel; } Voxel;
struct { struct {
@@ -153,63 +151,15 @@ struct CollisionAABB : public AABB {
bool Skip = false; bool Skip = false;
}; };
/*
Указать модель, текстуры и поворот по конкретным осям.
Может быть вариативность моделей относительно одного условия (случайность в зависимости от координат?)
Допускается активация нескольких условий одновременно
условия snowy=false
"snowy=false": [{"model": "node/grass_node"}, {"model": "node/grass_node", transformations: ["y=90", "x=67"]}] <- модель будет выбрана случайно
или
: [{models: [], weight: 1}, {}] <- в models можно перечислить сразу несколько моделей, и они будут использоваться одновременно
или
"": {"model": "node/grass", weight <вес влияющий на шанс отображения именно этой модели>}
или просто
"model": "node/grass_node"
В условия добавить простые проверки !><=&|()
в задании параметров модели использовать формулы с применением состояний
uvlock ? https://minecraft.wiki/w/Blockstates_definition/format
*/
// Скомпилированный профиль ноды
struct DefNode_t {
// Зарегистрированные состояния (мета)
// Подгружается с файла assets/<modid>/nodestate/node/nodeId.json
// std::variant<DefNodestates_t, std::vector<ModelTransform>> StatesRouter;
// Параметры рендера
struct {
bool HasHalfTransparency = false;
} Render;
// Параметры коллизии
struct {
enum class EnumCollisionType {
None, ByRender,
};
std::variant<EnumCollisionType> CollisionType = EnumCollisionType::None;
} Collision;
// События
struct {
} Events;
// Если нода умная, то для неё будет создаваться дополнительный более активный объект
std::optional<sol::protected_function> NodeAdvancementFactory;
};
class Entity { class Entity {
DefEntityId DefId; DefEntityId_t DefId;
public: public:
LocalAABB ABBOX; LocalAABB ABBOX;
// PosQuat // PosQuat
DefWorldId WorldId; DefWorldId_t WorldId;
Pos::Object Pos, Speed, Acceleration; Pos::Object Pos, Speed, Acceleration;
glm::quat Quat; glm::quat Quat;
static constexpr uint16_t HP_BS = 4096, HP_BS_Bit = 12; static constexpr uint16_t HP_BS = 4096, HP_BS_Bit = 12;
@@ -229,15 +179,16 @@ public:
IsRemoved = false; IsRemoved = false;
public: public:
Entity(DefEntityId defId); Entity(DefEntityId_t defId);
AABB aabbAtPos() { AABB aabbAtPos() {
return {Pos-Pos::Object(ABBOX.x/2, ABBOX.y/2, ABBOX.z/2), Pos+Pos::Object(ABBOX.x/2, ABBOX.y/2, ABBOX.z/2)}; return {Pos-Pos::Object(ABBOX.x/2, ABBOX.y/2, ABBOX.z/2), Pos+Pos::Object(ABBOX.x/2, ABBOX.y/2, ABBOX.z/2)};
} }
DefEntityId getDefId() const { return DefId; } DefEntityId_t getDefId() const { return DefId; }
}; };
template<typename Vec> template<typename Vec>
struct VoxelCuboidsFuncs { struct VoxelCuboidsFuncs {
@@ -246,9 +197,9 @@ struct VoxelCuboidsFuncs {
if (a.VoxelId != b.VoxelId) return false; if (a.VoxelId != b.VoxelId) return false;
// Проверяем, что кубы смежны по одной из осей // Проверяем, что кубы смежны по одной из осей
bool xAdjacent = (a.Right.x == b.Left.x) && (a.Left.y == b.Left.y) && (a.Right.z == b.Right.z) && (a.Left.z == b.Left.z) && (a.Right.z == b.Right.z); bool xAdjacent = (a.Right.X == b.Left.X) && (a.Left.Y == b.Left.Y) && (a.Right.Y == b.Right.Y) && (a.Left.Z == b.Left.Z) && (a.Right.Z == b.Right.Z);
bool yAdjacent = (a.Right.y == b.Left.y) && (a.Left.x == b.Left.x) && (a.Right.x == b.Right.x) && (a.Left.z == b.Left.z) && (a.Right.z == b.Right.z); bool yAdjacent = (a.Right.Y == b.Left.Y) && (a.Left.X == b.Left.X) && (a.Right.X == b.Right.X) && (a.Left.Z == b.Left.Z) && (a.Right.Z == b.Right.Z);
bool zAdjacent = (a.Right.z == b.Left.z) && (a.Left.x == b.Left.x) && (a.Right.x == b.Right.x) && (a.Left.y == b.Left.y) && (a.Right.y == b.Right.y); bool zAdjacent = (a.Right.Z == b.Left.Z) && (a.Left.X == b.Left.X) && (a.Right.X == b.Right.X) && (a.Left.Y == b.Left.Y) && (a.Right.Y == b.Right.Y);
return xAdjacent || yAdjacent || zAdjacent; return xAdjacent || yAdjacent || zAdjacent;
} }
@@ -258,13 +209,13 @@ struct VoxelCuboidsFuncs {
merged.VoxelId = a.VoxelId; merged.VoxelId = a.VoxelId;
// Объединяем кубы по минимальным и максимальным координатам // Объединяем кубы по минимальным и максимальным координатам
merged.Left.x = std::min(a.Left.x, b.Left.x); merged.Left.X = std::min(a.Left.X, b.Left.X);
merged.Left.y = std::min(a.Left.y, b.Left.y); merged.Left.Y = std::min(a.Left.Y, b.Left.Y);
merged.Left.z = std::min(a.Left.z, b.Left.z); merged.Left.Z = std::min(a.Left.Z, b.Left.Z);
merged.Right.x = std::max(a.Right.x, b.Right.x); merged.Right.X = std::max(a.Right.X, b.Right.X);
merged.Right.y = std::max(a.Right.y, b.Right.y); merged.Right.Y = std::max(a.Right.Y, b.Right.Y);
merged.Right.z = std::max(a.Right.z, b.Right.z); merged.Right.Z = std::max(a.Right.Z, b.Right.Z);
return merged; return merged;
} }
@@ -358,47 +309,53 @@ struct VoxelCuboidsFuncs {
} }
}; };
inline void convertRegionVoxelsToChunks(const std::vector<VoxelCube_Region>& regions, std::unordered_map<Pos::bvec4u, std::vector<VoxelCube>> &chunks) { inline void convertRegionVoxelsToChunks(const std::vector<VoxelCube_Region>& regions, std::vector<VoxelCube> *chunks) {
for (const auto& region : regions) { for (const auto& region : regions) {
int minX = region.Left.x >> 8; int minX = region.Left.X >> 8;
int minY = region.Left.y >> 8; int minY = region.Left.Y >> 8;
int minZ = region.Left.z >> 8; int minZ = region.Left.Z >> 8;
int maxX = region.Right.x >> 8; int maxX = region.Right.X >> 8;
int maxY = region.Right.y >> 8; int maxY = region.Right.Y >> 8;
int maxZ = region.Right.z >> 8; int maxZ = region.Right.Z >> 8;
for (int x = minX; x <= maxX; ++x) { for (int x = minX; x <= maxX; ++x) {
for (int y = minY; y <= maxY; ++y) { for (int y = minY; y <= maxY; ++y) {
for (int z = minZ; z <= maxZ; ++z) { for (int z = minZ; z <= maxZ; ++z) {
Pos::bvec256u left { Pos::Local256_u left {
static_cast<uint8_t>(std::max<uint16_t>((x << 8), region.Left.x) - (x << 8)), static_cast<uint8_t>(std::max<uint16_t>((x << 8), region.Left.X) - (x << 8)),
static_cast<uint8_t>(std::max<uint16_t>((y << 8), region.Left.y) - (y << 8)), static_cast<uint8_t>(std::max<uint16_t>((y << 8), region.Left.Y) - (y << 8)),
static_cast<uint8_t>(std::max<uint16_t>((z << 8), region.Left.z) - (z << 8)) static_cast<uint8_t>(std::max<uint16_t>((z << 8), region.Left.Z) - (z << 8))
}; };
Pos::bvec256u right { Pos::Local256_u right {
static_cast<uint8_t>(std::min<uint16_t>(((x+1) << 8)-1, region.Right.x) - (x << 8)), static_cast<uint8_t>(std::min<uint16_t>(((x+1) << 8)-1, region.Right.X) - (x << 8)),
static_cast<uint8_t>(std::min<uint16_t>(((y+1) << 8)-1, region.Right.y) - (y << 8)), static_cast<uint8_t>(std::min<uint16_t>(((y+1) << 8)-1, region.Right.Y) - (y << 8)),
static_cast<uint8_t>(std::min<uint16_t>(((z+1) << 8)-1, region.Right.z) - (z << 8)) static_cast<uint8_t>(std::min<uint16_t>(((z+1) << 8)-1, region.Right.Z) - (z << 8))
}; };
chunks[Pos::bvec4u(x, y, z)].push_back({ int chunkIndex = z * 16 * 16 + y * 16 + x;
region.VoxelId, region.Meta, left, right chunks[chunkIndex].emplace_back(region.VoxelId, left, right);
});
} }
} }
} }
} }
} }
inline void convertChunkVoxelsToRegion(const std::unordered_map<Pos::bvec4u, std::vector<VoxelCube>> &chunks, std::vector<VoxelCube_Region> &regions) { inline void convertChunkVoxelsToRegion(const std::vector<VoxelCube> *chunks, std::vector<VoxelCube_Region> &regions) {
for(const auto& [pos, voxels] : chunks) { for (int x = 0; x < 16; ++x) {
Pos::bvec1024u left = pos << 8; for (int y = 0; y < 16; ++y) {
for (const auto& cube : voxels) { for (int z = 0; z < 16; ++z) {
regions.push_back({ int chunkIndex = z * 16 * 16 + y * 16 + x;
cube.VoxelId, cube.Meta,
Pos::bvec1024u(left.x+cube.Pos.x, left.y+cube.Pos.y, left.z+cube.Pos.z), Pos::Local4096_u left(x << 8, y << 8, z << 8);
Pos::bvec1024u(left.x+cube.Pos.x+cube.Size.x, left.y+cube.Pos.y+cube.Size.y, left.z+cube.Pos.z+cube.Size.z)
}); for (const auto& cube : chunks[chunkIndex]) {
regions.emplace_back(
Pos::Local4096_u(left.X+cube.Left.X, left.Y+cube.Left.Y, left.Z+cube.Left.Z),
Pos::Local4096_u(left.X+cube.Right.X, left.Y+cube.Right.Y, left.Z+cube.Right.Z),
cube.VoxelId
);
}
}
} }
} }
@@ -406,105 +363,4 @@ inline void convertChunkVoxelsToRegion(const std::unordered_map<Pos::bvec4u, std
regions = VoxelCuboidsFuncs<VoxelCube_Region>::optimizeVoxelRegions(regions); regions = VoxelCuboidsFuncs<VoxelCube_Region>::optimizeVoxelRegions(regions);
} }
struct ServerObjectPos {
WorldId_t WorldId;
Pos::Object ObjectPos;
};
/*
Разница между информацией о наблюдаемых регионах
*/
struct ContentViewInfo_Diff {
// Изменения на уровне миров (увиден или потерян)
std::vector<WorldId_t> WorldsNew, WorldsLost;
// Изменения на уровне регионов
std::unordered_map<WorldId_t, std::vector<Pos::GlobalRegion>> RegionsNew, RegionsLost;
bool empty() const {
return WorldsNew.empty() && WorldsLost.empty() && RegionsNew.empty() && RegionsLost.empty();
}
};
/*
То, какие регионы наблюдает игрок
*/
struct ContentViewInfo {
// std::vector<Pos::GlobalRegion> - сортированный и с уникальными значениями
std::unordered_map<WorldId_t, std::vector<Pos::GlobalRegion>> Regions;
// Что изменилось относительно obj
// Перерасчёт должен проводится при смещении игрока или ContentBridge за границу региона
ContentViewInfo_Diff diffWith(const ContentViewInfo& obj) const {
ContentViewInfo_Diff out;
// Проверяем новые миры и регионы
for(const auto& [key, regions] : Regions) {
auto iterWorld = obj.Regions.find(key);
if(iterWorld == obj.Regions.end()) {
out.WorldsNew.push_back(key);
out.RegionsNew[key] = regions;
} else {
auto &vec = out.RegionsNew[key];
vec.reserve(8*8);
std::set_difference(
regions.begin(), regions.end(),
iterWorld->second.begin(), iterWorld->second.end(),
std::back_inserter(vec)
);
}
}
// Проверяем потерянные миры и регионы
for(const auto& [key, regions] : obj.Regions) {
auto iterWorld = Regions.find(key);
if(iterWorld == Regions.end()) {
out.WorldsLost.push_back(key);
out.RegionsLost[key] = regions;
} else {
auto &vec = out.RegionsLost[key];
vec.reserve(8*8);
std::set_difference(
regions.begin(), regions.end(),
iterWorld->second.begin(), iterWorld->second.end(),
std::back_inserter(vec)
);
}
}
// shrink_to_feet
for(auto& [_, regions] : out.RegionsNew)
regions.shrink_to_fit();
for(auto& [_, regions] : out.RegionsLost)
regions.shrink_to_fit();
return out;
}
};
/*
Мост контента, для отслеживания событий из удалённых точек
По типу портала, через который можно видеть контент на расстоянии
*/
struct ContentBridge {
/*
false -> Из точки Left видно контент в точки Right
true -> Контент виден в обе стороны
*/
bool IsTwoWay = false;
WorldId_t LeftWorld;
Pos::GlobalRegion LeftPos;
WorldId_t RightWorld;
Pos::GlobalRegion RightPos;
};
struct ContentViewCircle {
WorldId_t WorldId;
Pos::GlobalRegion Pos;
// Радиус в регионах в квадрате
int16_t Range;
};
} }

View File

@@ -0,0 +1,122 @@
#define BOOST_ASIO_HAS_IO_URING 1
#include "BinaryResourceManager.hpp"
#include <memory>
#include <boost/system.hpp>
#include <boost/asio.hpp>
#include <boost/asio/stream_file.hpp>
#include <TOSLib.hpp>
namespace LV::Server {
BinaryResourceManager::BinaryResourceManager(asio::io_context &ioc,
std::shared_ptr<ResourceFile> zeroResource)
: IOC(ioc), ZeroResource(std::move(zeroResource))
{
}
BinaryResourceManager::~BinaryResourceManager() {
}
void BinaryResourceManager::recheckResources() {
}
ResourceId_t BinaryResourceManager::mapUriToId(const std::string &uri) {
UriParse parse = parseUri(uri);
if(parse.Protocol != "assets")
MAKE_ERROR("Неизвестный протокол ресурса '" << parse.Protocol << "'. Полный путь: " << parse.Orig);
return getResource_Assets(parse.Path);
}
void BinaryResourceManager::needResourceResponse(const std::vector<ResourceId_t> &resources) {
UpdatedResources.lock_write()->insert(resources.end(), resources.begin(), resources.end());
}
void BinaryResourceManager::update(float dtime) {
if(UpdatedResources.no_lock_readable().empty())
return;
auto lock = UpdatedResources.lock_write();
for(ResourceId_t resId : *lock) {
std::shared_ptr<ResourceFile> &objRes = PreparedInformation[resId];
if(objRes)
continue;
auto iter = ResourcesInfo.find(resId);
if(iter == ResourcesInfo.end()) {
objRes = ZeroResource;
} else {
objRes = iter->second->Loaded;
}
}
}
BinaryResourceManager::UriParse BinaryResourceManager::parseUri(const std::string &uri) {
size_t pos = uri.find("://");
if(pos == std::string::npos)
return {uri, "assets", uri};
else
return {uri, uri.substr(0, pos), uri.substr(pos+3)};
}
ResourceId_t BinaryResourceManager::getResource_Assets(std::string path) {
size_t pos = path.find("/");
if(pos == std::string::npos)
MAKE_ERROR("Не действительный путь assets: '" << path << "'");
std::string domain = path.substr(0, pos);
std::string inDomainPath = path.substr(pos+1);
ResourceId_t &resId = KnownResource[path];
if(!resId)
resId = NextId++;
std::shared_ptr<Resource> &res = ResourcesInfo[resId];
if(!res) {
res = std::make_shared<Resource>();
res->Loaded = ZeroResource;
auto iter = Domains.find("domain");
if(iter == Domains.end()) {
UpdatedResources.lock_write()->push_back(resId);
} else {
res->IsLoading = true;
asio::co_spawn(IOC, checkResource_Assets(resId, iter->second / inDomainPath, res), asio::detached);
}
}
return resId;
}
coro<> BinaryResourceManager::checkResource_Assets(ResourceId_t id, fs::path path, std::shared_ptr<Resource> res) {
try {
asio::stream_file fd(IOC, path, asio::stream_file::flags::read_only);
if(fd.size() > 1024*1024*16)
MAKE_ERROR("Превышен лимит размера файла: " << fd.size() << " > " << 1024*1024*16);
std::shared_ptr<ResourceFile> file = std::make_shared<ResourceFile>();
file->Data.resize(fd.size());
co_await asio::async_read(fd, asio::mutable_buffer(file->Data.data(), file->Data.size()));
file->calcHash();
res->LastError.clear();
} catch(const std::exception &exc) {
res->LastError = exc.what();
res->IsLoading = false;
if(const boost::system::system_error *errc = dynamic_cast<const boost::system::system_error*>(&exc); errc && errc->code() == asio::error::operation_aborted)
co_return;
}
res->IsLoading = false;
UpdatedResources.lock_write()->push_back(id);
}
}

View File

@@ -0,0 +1,82 @@
#pragma once
#include "Common/Lockable.hpp"
#include "Server/RemoteClient.hpp"
#include <functional>
#include <optional>
#include <string>
#include <unordered_map>
#include <filesystem>
#include <vector>
#include <Common/Async.hpp>
#include "Abstract.hpp"
#include "boost/asio/io_context.hpp"
namespace LV::Server {
namespace fs = std::filesystem;
class BinaryResourceManager {
asio::io_context &IOC;
public:
private:
struct Resource {
// Файл загруженный на диск
std::shared_ptr<ResourceFile> Loaded;
// Источник
std::string Uri;
bool IsLoading = false;
std::string LastError;
};
struct UriParse {
std::string Orig, Protocol, Path;
};
// Нулевой ресурс
std::shared_ptr<ResourceFile> ZeroResource;
// Домены поиска ресурсов
std::unordered_map<std::string, fs::path> Domains;
// Известные ресурсы
std::map<std::string, ResourceId_t> KnownResource;
std::map<ResourceId_t, std::shared_ptr<Resource>> ResourcesInfo;
// Последовательная регистрация ресурсов
ResourceId_t NextId = 1;
// Накапливаем идентификаторы готовых ресурсов
Lockable<std::vector<ResourceId_t>> UpdatedResources;
// Подготовленая таблица оповещения об изменениях ресурсов
// Должна забираться сервером и отчищаться
std::unordered_map<ResourceId_t, std::shared_ptr<ResourceFile>> PreparedInformation;
public:
// Если ресурс будет обновлён или загружен будет вызвано onResourceUpdate
BinaryResourceManager(asio::io_context &ioc, std::shared_ptr<ResourceFile> zeroResource);
virtual ~BinaryResourceManager();
// Перепроверка изменений ресурсов
void recheckResources();
// Домен мода -> путь к папке с ресурсами
void setAssetsDomain(std::unordered_map<std::string, fs::path> &&domains) { Domains = std::move(domains); }
// Идентификатор ресурса по его uri
ResourceId_t mapUriToId(const std::string &uri);
// Запросить ресурсы через onResourceUpdate
void needResourceResponse(const std::vector<ResourceId_t> &resources);
// Серверный такт
void update(float dtime);
bool hasPreparedInformation() { return !PreparedInformation.empty(); }
std::unordered_map<ResourceId_t, std::shared_ptr<ResourceFile>> takePreparedInformation() {
return std::move(PreparedInformation);
}
protected:
UriParse parseUri(const std::string &uri);
ResourceId_t getResource_Assets(std::string path);
coro<> checkResource_Assets(ResourceId_t id, fs::path path, std::shared_ptr<Resource> res);
};
}

View File

@@ -0,0 +1,236 @@
#include "ContentEventController.hpp"
#include "Common/Abstract.hpp"
#include "RemoteClient.hpp"
#include "Server/Abstract.hpp"
#include "World.hpp"
namespace LV::Server {
ContentEventController::ContentEventController(RemoteClient_ptr &&remote)
: Remote(std::move(remote))
{
}
uint16_t ContentEventController::getViewRangeActive() const {
return 16;
}
uint16_t ContentEventController::getViewRangeBackground() const {
return 0;
}
ServerObjectPos ContentEventController::getLastPos() const {
return {0, Remote->CameraPos};
}
ServerObjectPos ContentEventController::getPos() const {
return {0, Remote->CameraPos};
}
void ContentEventController::checkContentViewChanges() {
// Очистка уже не наблюдаемых чанков
for(const auto &[worldId, regions] : ContentView_LostView.View) {
for(const auto &[regionPos, chunks] : regions) {
size_t bitPos = chunks._Find_first();
while(bitPos != chunks.size()) {
Pos::Local16_u chunkPosLocal;
chunkPosLocal = bitPos;
Pos::GlobalChunk chunkPos = regionPos.toChunk(chunkPosLocal);
Remote->prepareChunkRemove(worldId, chunkPos);
bitPos = chunks._Find_next(bitPos);
}
}
}
// Очистка миров
for(WorldId_t worldId : ContentView_LostView.Worlds) {
Remote->prepareWorldRemove(worldId);
}
}
void ContentEventController::onWorldUpdate(WorldId_t worldId, World *worldObj)
{
auto pWorld = ContentViewState.find(worldId);
if(pWorld == ContentViewState.end())
return;
Remote->prepareWorldUpdate(worldId, worldObj);
}
void ContentEventController::onChunksUpdate_Voxels(WorldId_t worldId, Pos::GlobalRegion regionPos,
const std::unordered_map<Pos::Local16_u, const std::vector<VoxelCube>*> &chunks)
{
auto pWorld = ContentViewState.find(worldId);
if(pWorld == ContentViewState.end())
return;
auto pRegion = pWorld->second.find(regionPos);
if(pRegion == pWorld->second.end())
return;
const std::bitset<4096> &chunkBitset = pRegion->second;
for(auto pChunk : chunks) {
if(!chunkBitset.test(pChunk.first))
continue;
Pos::GlobalChunk chunkPos = regionPos.toChunk(pChunk.first);
Remote->prepareChunkUpdate_Voxels(worldId, chunkPos, *pChunk.second);
}
}
void ContentEventController::onChunksUpdate_Nodes(WorldId_t worldId, Pos::GlobalRegion regionPos,
const std::unordered_map<Pos::Local16_u, const std::unordered_map<Pos::Local16_u, Node>*> &chunks)
{
auto pWorld = ContentViewState.find(worldId);
if(pWorld == ContentViewState.end())
return;
auto pRegion = pWorld->second.find(regionPos);
if(pRegion == pWorld->second.end())
return;
const std::bitset<4096> &chunkBitset = pRegion->second;
for(auto pChunk : chunks) {
if(!chunkBitset.test(pChunk.first))
continue;
Pos::GlobalChunk chunkPos = regionPos.toChunk(pChunk.first);
Remote->prepareChunkUpdate_Nodes(worldId, chunkPos, *pChunk.second);
}
}
void ContentEventController::onChunksUpdate_LightPrism(WorldId_t worldId, Pos::GlobalRegion regionPos,
const std::unordered_map<Pos::Local16_u, const LightPrism*> &chunks)
{
auto pWorld = ContentViewState.find(worldId);
if(pWorld == ContentViewState.end())
return;
auto pRegion = pWorld->second.find(regionPos);
if(pRegion == pWorld->second.end())
return;
const std::bitset<4096> &chunkBitset = pRegion->second;
for(auto pChunk : chunks) {
if(!chunkBitset.test(pChunk.first))
continue;
Pos::GlobalChunk chunkPos = regionPos.toChunk(pChunk.first);
Remote->prepareChunkUpdate_LightPrism(worldId, chunkPos, pChunk.second);
}
}
void ContentEventController::onEntityEnterLost(WorldId_t worldId, Pos::GlobalRegion regionPos,
const std::unordered_set<LocalEntityId_t> &enter, const std::unordered_set<LocalEntityId_t> &lost)
{
auto pWorld = Subscribed.Entities.find(worldId);
if(pWorld == Subscribed.Entities.end()) {
// pWorld = std::get<0>(Subscribed.Entities.emplace(std::make_pair(worldId, decltype(Subscribed.Entities)::value_type())));
Subscribed.Entities[worldId];
pWorld = Subscribed.Entities.find(worldId);
}
auto pRegion = pWorld->second.find(regionPos);
if(pRegion == pWorld->second.end()) {
// pRegion = std::get<0>(pWorld->second.emplace(std::make_pair(worldId, decltype(pWorld->second)::value_type())));
pWorld->second[regionPos];
pRegion = pWorld->second.find(regionPos);
}
std::unordered_set<LocalEntityId_t> &entityesId = pRegion->second;
for(LocalEntityId_t eId : lost) {
entityesId.erase(eId);
}
entityesId.insert(enter.begin(), enter.end());
if(entityesId.empty()) {
pWorld->second.erase(pRegion);
if(pWorld->second.empty())
Subscribed.Entities.erase(pWorld);
}
// Сообщить Remote
for(LocalEntityId_t eId : lost) {
Remote->prepareEntityRemove({worldId, regionPos, eId});
}
}
void ContentEventController::onEntitySwap(WorldId_t lastWorldId, Pos::GlobalRegion lastRegionPos,
LocalEntityId_t lastId, WorldId_t newWorldId, Pos::GlobalRegion newRegionPos, LocalEntityId_t newId)
{
// Проверим отслеживается ли эта сущность нами
auto lpWorld = Subscribed.Entities.find(lastWorldId);
if(lpWorld == Subscribed.Entities.end())
// Исходный мир нами не отслеживается
return;
auto lpRegion = lpWorld->second.find(lastRegionPos);
if(lpRegion == lpWorld->second.end())
// Исходный регион нами не отслеживается
return;
auto lpceId = lpRegion->second.find(lastId);
if(lpceId == lpRegion->second.end())
// Сущность нами не отслеживается
return;
// Проверим отслеживается ли регион, в который будет перемещена сущность
auto npWorld = Subscribed.Entities.find(newWorldId);
if(npWorld != Subscribed.Entities.end()) {
auto npRegion = npWorld->second.find(newRegionPos);
if(npRegion != npWorld->second.end()) {
// Следующий регион отслеживается, перекинем сущность
lpRegion->second.erase(lpceId);
npRegion->second.insert(newId);
Remote->prepareEntitySwap({lastWorldId, lastRegionPos, lastId}, {newWorldId, newRegionPos, newId});
goto entitySwaped;
}
}
Remote->prepareEntityRemove({lastWorldId, lastRegionPos, lastId});
entitySwaped:
return;
}
void ContentEventController::onEntityUpdates(WorldId_t worldId, Pos::GlobalRegion regionPos,
const std::vector<Entity> &entities)
{
auto lpWorld = Subscribed.Entities.find(worldId);
if(lpWorld == Subscribed.Entities.end())
// Исходный мир нами не отслеживается
return;
auto lpRegion = lpWorld->second.find(regionPos);
if(lpRegion == lpWorld->second.end())
// Исходный регион нами не отслеживается
return;
for(size_t eId = 0; eId < entities.size(); eId++) {
if(!lpRegion->second.contains(eId))
continue;
Remote->prepareEntityUpdate({worldId, regionPos, eId}, &entities[eId]);
}
}
void ContentEventController::onPortalEnterLost(const std::vector<void*> &enter, const std::vector<void*> &lost)
{
}
void ContentEventController::onPortalUpdates(const std::vector<void*> &portals)
{
}
}

View File

@@ -1,135 +0,0 @@
#include "ContentEventController.hpp"
#include "Common/Abstract.hpp"
#include "RemoteClient.hpp"
#include "Server/Abstract.hpp"
#include "World.hpp"
#include "glm/ext/quaternion_geometric.hpp"
#include <algorithm>
namespace LV::Server {
ContentEventController::ContentEventController(std::unique_ptr<RemoteClient> &&remote)
: Remote(std::move(remote))
{
LastPos = Pos = {0, Remote->CameraPos};
}
uint16_t ContentEventController::getViewRangeBackground() const {
return 0;
}
ServerObjectPos ContentEventController::getLastPos() const {
return LastPos;
}
ServerObjectPos ContentEventController::getPos() const {
return Pos;
}
void ContentEventController::removeUnobservable(const ContentViewInfo_Diff& diff) {
for(const auto& [worldId, regions] : diff.RegionsLost) {
for(const Pos::GlobalRegion region : regions)
Remote->prepareRegionRemove(worldId, region);
}
for(const WorldId_t worldId : diff.WorldsLost)
Remote->prepareWorldRemove(worldId);
}
void ContentEventController::onWorldUpdate(WorldId_t worldId, World *worldObj)
{
auto pWorld = ContentViewState.Regions.find(worldId);
if(pWorld == ContentViewState.Regions.end())
return;
Remote->prepareWorldUpdate(worldId, worldObj);
}
void ContentEventController::onEntityEnterLost(WorldId_t worldId, Pos::GlobalRegion regionPos,
const std::unordered_set<RegionEntityId_t> &enter, const std::unordered_set<RegionEntityId_t> &lost)
{
// Сообщить Remote
for(RegionEntityId_t eId : lost) {
Remote->prepareEntityRemove({worldId, regionPos, eId});
}
}
void ContentEventController::onEntitySwap(ServerEntityId_t prevId, ServerEntityId_t newId)
{
{
auto pWorld = ContentViewState.Regions.find(std::get<0>(prevId));
assert(pWorld != ContentViewState.Regions.end());
assert(std::binary_search(pWorld->second.begin(), pWorld->second.end(), std::get<1>(prevId)));
}
{
auto npWorld = ContentViewState.Regions.find(std::get<0>(newId));
assert(npWorld != ContentViewState.Regions.end());
assert(std::binary_search(npWorld->second.begin(), npWorld->second.end(), std::get<1>(prevId)));
}
Remote->prepareEntitySwap(prevId, newId);
}
void ContentEventController::onEntityUpdates(WorldId_t worldId, Pos::GlobalRegion regionPos,
const std::unordered_map<RegionEntityId_t, Entity*> &entities)
{
auto pWorld = ContentViewState.Regions.find(worldId);
if(pWorld == ContentViewState.Regions.end())
// Исходный мир нами не отслеживается
return;
if(!std::binary_search(pWorld->second.begin(), pWorld->second.end(), regionPos))
// Исходный регион нами не отслеживается
return;
for(const auto& [id, entity] : entities) {
Remote->prepareEntityUpdate({worldId, regionPos, id}, entity);
}
}
void ContentEventController::onPortalEnterLost(const std::vector<void*> &enter, const std::vector<void*> &lost)
{
}
void ContentEventController::onPortalUpdates(const std::vector<void*> &portals)
{
}
void ContentEventController::onUpdate() {
LastPos = Pos;
Pos.ObjectPos = Remote->CameraPos;
Pos::GlobalRegion r1 = LastPos.ObjectPos >> 12 >> 4 >> 2;
Pos::GlobalRegion r2 = Pos.ObjectPos >> 12 >> 4 >> 2;
if(r1 != r2) {
CrossedBorder = true;
}
if(!Remote->Actions.get_read().empty()) {
auto lock = Remote->Actions.lock();
while(!lock->empty()) {
uint8_t action = lock->front();
lock->pop();
glm::quat q = Remote->CameraQuat.toQuat();
glm::vec4 v = glm::mat4(q)*glm::vec4(0, 0, -6, 1);
Pos::GlobalNode pos = (Pos::GlobalNode) (glm::vec3) v;
pos += Pos.ObjectPos >> Pos::Object_t::BS_Bit;
if(action == 0) {
// Break
Break.push(pos);
} else if(action == 1) {
// Build
Build.push(pos);
}
}
}
}
}

View File

@@ -2,12 +2,10 @@
#include <Common/Abstract.hpp> #include <Common/Abstract.hpp>
#include "Abstract.hpp" #include "Abstract.hpp"
#include "TOSLib.hpp"
#include <algorithm>
#include <bitset> #include <bitset>
#include <map> #include <map>
#include <memory> #include <memory>
#include <queue> #include <optional>
#include <unordered_map> #include <unordered_map>
#include <unordered_set> #include <unordered_set>
#include <vector> #include <vector>
@@ -16,44 +14,225 @@
namespace LV::Server { namespace LV::Server {
class RemoteClient; class RemoteClient;
using RemoteClient_ptr = std::unique_ptr<RemoteClient, std::function<void(RemoteClient*)>>;
class GameServer; class GameServer;
class World; class World;
struct ServerObjectPos {
WorldId_t WorldId;
Pos::Object ObjectPos;
};
/*
Сфера в которой отслеживаются события игроком
*/
struct ContentViewCircle {
WorldId_t WorldId;
// Позиция в чанках
glm::i16vec3 Pos;
// (Единица равна размеру чанка) в квадрате
int32_t Range;
inline int32_t sqrDistance(Pos::GlobalRegion regionPos) const {
glm::i32vec3 vec = {Pos.x-((regionPos.X << 4) | 0b1000), Pos.y-((regionPos.Y << 4) | 0b1000), Pos.z-((regionPos.Z << 4) | 0b1000)};
return vec.x*vec.x+vec.y*vec.y+vec.z*vec.z;
};
inline int32_t sqrDistance(Pos::GlobalChunk chunkPos) const {
glm::i32vec3 vec = {Pos.x-chunkPos.X, Pos.y-chunkPos.Y, Pos.z-chunkPos.Z};
return vec.x*vec.x+vec.y*vec.y+vec.z*vec.z;
};
inline int64_t sqrDistance(Pos::Object objectPos) const {
glm::i32vec3 vec = {Pos.x-(objectPos.x >> 20), Pos.y-(objectPos.y >> 20), Pos.z-(objectPos.z >> 20)};
return vec.x*vec.x+vec.y*vec.y+vec.z*vec.z;
};
bool isIn(Pos::GlobalRegion regionPos) const {
return sqrDistance(regionPos) < Range+192; // (8×sqrt(3))^2
}
bool isIn(Pos::GlobalChunk chunkPos) const {
return sqrDistance(chunkPos) < Range+3; // (1×sqrt(3))^2
}
bool isIn(Pos::Object objectPos, int32_t size = 0) const {
return sqrDistance(objectPos) < Range+3+size;
}
};
// Регион -> чанки попавшие под обозрение Pos::Local16_u
using ContentViewWorld = std::map<Pos::GlobalRegion, std::bitset<4096>>; // 1 - чанк виден, 0 - не виден
struct ContentViewGlobal_DiffInfo;
struct ContentViewGlobal : public std::map<WorldId_t, ContentViewWorld> {
// Вычисляет половинную разницу между текущей и предыдущей области видимости
// Возвращает области, которые появились по отношению к old, чтобы получить области потерянные из виду поменять местами *this и old
ContentViewGlobal_DiffInfo calcDiffWith(const ContentViewGlobal &old) const;
};
struct ContentViewGlobal_DiffInfo {
// Новые увиденные чанки
ContentViewGlobal View;
// Регионы
std::unordered_map<WorldId_t, std::vector<Pos::GlobalRegion>> Regions;
// Миры
std::vector<WorldId_t> Worlds;
bool empty() const {
return View.empty() && Regions.empty() && Worlds.empty();
}
};
inline ContentViewGlobal_DiffInfo ContentViewGlobal::calcDiffWith(const ContentViewGlobal &old) const {
ContentViewGlobal_DiffInfo newView;
// Рассматриваем разницу меж мирами
for(const auto &[newWorldId, newWorldView] : *this) {
auto oldWorldIter = old.find(newWorldId);
if(oldWorldIter == old.end()) { // В старом состоянии нет мира
newView.View[newWorldId] = newWorldView;
newView.Worlds.push_back(newWorldId);
auto &newRegions = newView.Regions[newWorldId];
for(const auto &[regionPos, _] : newWorldView)
newRegions.push_back(regionPos);
} else {
const std::map<Pos::GlobalRegion, std::bitset<4096>> &newRegions = newWorldView;
const std::map<Pos::GlobalRegion, std::bitset<4096>> &oldRegions = oldWorldIter->second;
std::map<Pos::GlobalRegion, std::bitset<4096>> *diffRegions = nullptr;
// Рассматриваем разницу меж регионами
for(const auto &[newRegionPos, newRegionBitField] : newRegions) {
auto oldRegionIter = oldRegions.find(newRegionPos);
if(oldRegionIter == oldRegions.end()) { // В старой описи мира нет региона
if(!diffRegions)
diffRegions = &newView.View[newWorldId];
(*diffRegions)[newRegionPos] = newRegionBitField;
newView.Regions[newWorldId].push_back(newRegionPos);
} else {
const std::bitset<4096> &oldChunks = oldRegionIter->second;
std::bitset<4096> chunks = (~oldChunks) & newRegionBitField; // Останется поле с новыми чанками
if(chunks._Find_first() != chunks.size()) {
// Есть новые чанки
if(!diffRegions)
diffRegions = &newView.View[newWorldId];
(*diffRegions)[newRegionPos] = chunks;
}
}
}
}
}
return newView;
}
/*
Мост контента, для отслеживания событий из удалённх точек
По типу портала, через который можно видеть контент на расстоянии
*/
struct ContentBridge {
/*
false -> Из точки From видно контент из точки To
true -> Контент виден в обе стороны
*/
bool IsTwoWay = false;
WorldId_t LeftWorld;
// Позиция в чанках
glm::i16vec3 LeftPos;
WorldId_t RightWorld;
// Позиция в чанках
glm::i16vec3 RightPos;
};
/* Игрок */ /* Игрок */
class ContentEventController { class ContentEventController {
public: private:
std::queue<Pos::GlobalNode> Build, Break;
struct SubscribedObj {
// Используется регионами
std::vector<PortalId_t> Portals;
std::unordered_map<WorldId_t, std::unordered_map<Pos::GlobalRegion, std::unordered_set<LocalEntityId_t>>> Entities;
} Subscribed;
public: public:
ContentEventController(std::unique_ptr<RemoteClient>&& remote); // Управляется сервером
RemoteClient_ptr Remote;
// Регионы сюда заглядывают
// Каждый такт значения изменений обновляются GameServer'ом
// Объявленная в чанках территория точно отслеживается (активная зона)
ContentViewGlobal ContentViewState;
ContentViewGlobal_DiffInfo ContentView_NewView, ContentView_LostView;
// Измеряется в чанках в регионах (активная зона) // size_t CVCHash = 0; // Хэш для std::vector<ContentViewCircle>
static constexpr uint16_t getViewRangeActive() { return 2; } // std::unordered_map<WorldId_t, std::vector<Pos::GlobalRegion>> SubscribedRegions;
public:
ContentEventController(RemoteClient_ptr &&remote);
// Измеряется в чанках в радиусе (активная зона)
uint16_t getViewRangeActive() const;
// Измеряется в чанках в радиусе (Декоративная зона) + getViewRangeActive() // Измеряется в чанках в радиусе (Декоративная зона) + getViewRangeActive()
uint16_t getViewRangeBackground() const; uint16_t getViewRangeBackground() const;
ServerObjectPos getLastPos() const; ServerObjectPos getLastPos() const;
ServerObjectPos getPos() const; ServerObjectPos getPos() const;
// Очищает более не наблюдаемые чанки и миры // Проверка на необходимость подгрузки новых определений миров
void removeUnobservable(const ContentViewInfo_Diff& diff); // и очистка клиента от не наблюдаемых данных
void checkContentViewChanges();
// Здесь приходят частично фильтрованные события // Здесь приходят частично фильтрованные события
// Фильтровать не отслеживаемые миры // Фильтровать не отслеживаемые миры
void onWorldUpdate(WorldId_t worldId, World *worldObj); void onWorldUpdate(WorldId_t worldId, World *worldObj);
void onEntityEnterLost(WorldId_t worldId, Pos::GlobalRegion regionPos, const std::unordered_set<RegionEntityId_t> &enter, const std::unordered_set<RegionEntityId_t> &lost); // Нужно фильтровать неотслеживаемые чанки
void onEntitySwap(ServerEntityId_t prevId, ServerEntityId_t newId); void onChunksUpdate_Voxels(WorldId_t worldId, Pos::GlobalRegion regionPos, const std::unordered_map<Pos::Local16_u, const std::vector<VoxelCube>*> &chunks);
void onEntityUpdates(WorldId_t worldId, Pos::GlobalRegion regionPos, const std::unordered_map<RegionEntityId_t, Entity*> &entities); void onChunksUpdate_Nodes(WorldId_t worldId, Pos::GlobalRegion regionPos, const std::unordered_map<Pos::Local16_u, const std::unordered_map<Pos::Local16_u, Node>*> &chunks);
void onChunksUpdate_LightPrism(WorldId_t worldId, Pos::GlobalRegion regionPos, const std::unordered_map<Pos::Local16_u, const LightPrism*> &chunks);
void onEntityEnterLost(WorldId_t worldId, Pos::GlobalRegion regionPos, const std::unordered_set<LocalEntityId_t> &enter, const std::unordered_set<LocalEntityId_t> &lost);
void onEntitySwap(WorldId_t lastWorldId, Pos::GlobalRegion lastRegionPos, LocalEntityId_t lastId, WorldId_t newWorldId, Pos::GlobalRegion newRegionPos, LocalEntityId_t newId);
void onEntityUpdates(WorldId_t worldId, Pos::GlobalRegion regionPos, const std::vector<Entity> &entities);
void onPortalEnterLost(const std::vector<void*> &enter, const std::vector<void*> &lost); void onPortalEnterLost(const std::vector<void*> &enter, const std::vector<void*> &lost);
void onPortalUpdates(const std::vector<void*> &portals); void onPortalUpdates(const std::vector<void*> &portals);
inline const SubscribedObj& getSubscribed() { return Subscribed; }; inline const SubscribedObj& getSubscribed() { return Subscribed; };
void onUpdate();
}; };
} }
namespace std {
template <>
struct hash<LV::Server::ServerObjectPos> {
std::size_t operator()(const LV::Server::ServerObjectPos& obj) const {
return std::hash<uint32_t>()(obj.WorldId) ^ std::hash<int32_t>()(obj.ObjectPos.x) ^ std::hash<int32_t>()(obj.ObjectPos.y) ^ std::hash<int32_t>()(obj.ObjectPos.z);
}
};
template <>
struct hash<LV::Server::ContentViewCircle> {
size_t operator()(const LV::Server::ContentViewCircle& obj) const noexcept {
// Используем стандартную функцию хеширования для uint32_t, glm::i16vec3 и int32_t
auto worldIdHash = std::hash<uint32_t>{}(obj.WorldId) << 32;
auto posHash =
std::hash<int16_t>{}(obj.Pos.x) ^
(std::hash<int16_t>{}(obj.Pos.y) << 16) ^
(std::hash<int16_t>{}(obj.Pos.z) << 32);
auto rangeHash = std::hash<int32_t>{}(obj.Range);
return worldIdHash ^
posHash ^
(~rangeHash << 32);
}
};
}

View File

@@ -1,178 +0,0 @@
#include "ContentManager.hpp"
#include "Common/Abstract.hpp"
#include <algorithm>
namespace LV::Server {
ContentManager::ContentManager(AssetsPreloader& am)
: AM(am)
{
std::fill(std::begin(NextId), std::end(NextId), 1);
}
ContentManager::~ContentManager() = default;
void ContentManager::registerBase_Node(ResourceId id, const std::string& domain, const std::string& key, const sol::table& profile) {
std::optional<DefNode>& node = getEntry_Node(id);
if(!node)
node.emplace();
DefNode& def = *node;
def.Domain = domain;
def.Key = key;
{
std::optional<std::variant<std::string, sol::table>> parent = profile.get<std::optional<std::variant<std::string, sol::table>>>("parent");
if(parent) {
if(const sol::table* table = std::get_if<sol::table>(&*parent)) {
// result = createNodeProfileByLua(*table);
} else if(const std::string* key = std::get_if<std::string>(&*parent)) {
auto regResult = TOS::Str::match(*key, "(?:([\\w\\d_]+):)?([\\w\\d_]+)");
if(!regResult)
MAKE_ERROR("Недействительный ключ в определении parent");
std::string realKey;
if(!regResult->at(1)) {
realKey = *key;
} else {
realKey = "core:" + *regResult->at(2);
}
DefNodeId parentId;
// {
// auto& list = Content.ContentKeyToId[(int) EnumDefContent::Node];
// auto iter = list.find(realKey);
// if(iter == list.end())
// MAKE_ERROR("Идентификатор parent не найден");
// parentId = iter->second;
// }
// result = Content.ContentIdToDef_Node.at(parentId);
}
}
}
{
std::optional<sol::table> nodestate = profile.get<std::optional<sol::table>>("nodestate");
}
{
std::optional<sol::table> render = profile.get<std::optional<sol::table>>("render");
}
{
std::optional<sol::table> collision = profile.get<std::optional<sol::table>>("collision");
}
{
std::optional<sol::table> events = profile.get<std::optional<sol::table>>("events");
}
// result.NodeAdvancementFactory = profile["node_advancement_factory"];
}
void ContentManager::registerBase_World(ResourceId id, const std::string& domain, const std::string& key, const sol::table& profile) {
std::optional<DefWorld>& world = getEntry_World(id);
if(!world)
world.emplace();
}
void ContentManager::registerBase_Entity(ResourceId id, const std::string& domain, const std::string& key, const sol::table& profile) {
std::optional<DefEntity>& entity = getEntry_Entity(id);
if(!entity)
entity.emplace();
DefEntity& def = *entity;
def.Domain = domain;
def.Key = key;
}
void ContentManager::registerBase(EnumDefContent type, const std::string& domain, const std::string& key, const sol::table& profile)
{
ResourceId id = getId(type, domain, key);
ProfileChanges[(int) type].push_back(id);
if(type == EnumDefContent::Node)
registerBase_Node(id, domain, key, profile);
else if(type == EnumDefContent::World)
registerBase_World(id, domain, key, profile);
else if(type == EnumDefContent::Entity)
registerBase_Entity(id, domain, key, profile);
else
MAKE_ERROR("Не реализовано");
}
void ContentManager::unRegisterBase(EnumDefContent type, const std::string& domain, const std::string& key)
{
ResourceId id = getId(type, domain, key);
ProfileChanges[(int) type].push_back(id);
}
void ContentManager::registerModifier(EnumDefContent type, const std::string& mod, const std::string& domain, const std::string& key, const sol::table& profile)
{
ResourceId id = getId(type, domain, key);
ProfileChanges[(int) type].push_back(id);
}
void ContentManager::unRegisterModifier(EnumDefContent type, const std::string& mod, const std::string& domain, const std::string& key)
{
ResourceId id = getId(type, domain, key);
ProfileChanges[(int) type].push_back(id);
}
void ContentManager::markAllProfilesDirty(EnumDefContent type) {
const auto &table = ContentKeyToId[(int) type];
for(const auto& domainPair : table) {
for(const auto& keyPair : domainPair.second) {
ProfileChanges[(int) type].push_back(keyPair.second);
}
}
}
std::vector<ResourceId> ContentManager::collectProfileIds(EnumDefContent type) const {
std::vector<ResourceId> ids;
const auto &table = ContentKeyToId[(int) type];
for(const auto& domainPair : table) {
for(const auto& keyPair : domainPair.second) {
ids.push_back(keyPair.second);
}
}
std::sort(ids.begin(), ids.end());
auto last = std::unique(ids.begin(), ids.end());
ids.erase(last, ids.end());
return ids;
}
ContentManager::Out_buildEndProfiles ContentManager::buildEndProfiles() {
Out_buildEndProfiles result;
for(int type = 0; type < (int) EnumDefContent::MAX_ENUM; type++) {
auto& keys = ProfileChanges[type];
std::sort(keys.begin(), keys.end());
auto iterErase = std::unique(keys.begin(), keys.end());
keys.erase(iterErase, keys.end());
}
for(ResourceId id : ProfileChanges[(int) EnumDefContent::Node]) {
std::optional<DefNode>& node = getEntry_Node(id);
if(!node) {
continue;
}
auto [nodestateId, assetsModel, assetsTexture]
= AM.getNodeDependency(node->Domain, node->Key);
node->NodestateId = nodestateId;
node->ModelDeps = std::move(assetsModel);
node->TextureDeps = std::move(assetsTexture);
}
return result;
}
}

View File

@@ -1,221 +0,0 @@
#pragma once
#include "Common/Abstract.hpp"
#include "Server/Abstract.hpp"
#include "Common/AssetsPreloader.hpp"
#include <sol/table.hpp>
#include <unordered_map>
#include <unordered_set>
namespace LV::Server {
struct DefVoxel_Base { };
struct DefNode_Base { };
struct DefWorld_Base { };
struct DefPortal_Base { };
struct DefEntity_Base { };
struct DefItem_Base { };
struct DefVoxel_Mod { };
struct DefNode_Mod { };
struct DefWorld_Mod { };
struct DefPortal_Mod { };
struct DefEntity_Mod { };
struct DefItem_Mod { };
struct ResourceBase {
std::string Domain, Key;
};
struct DefVoxel : public ResourceBase { };
struct DefNode : public ResourceBase {
AssetsNodestate NodestateId;
std::vector<AssetsModel> ModelDeps;
std::vector<AssetsTexture> TextureDeps;
};
struct DefWorld : public ResourceBase { };
struct DefPortal : public ResourceBase { };
struct DefEntity : public ResourceBase { };
struct DefItem : public ResourceBase { };
class ContentManager {
template<typename T>
struct TableEntry {
static constexpr size_t ChunkSize = 4096;
std::array<std::optional<T>, ChunkSize> Entries;
};
// Следующие идентификаторы регистрации контента
ResourceId NextId[(int) EnumDefContent::MAX_ENUM] = {};
// Домен -> {ключ -> идентификатор}
std::unordered_map<std::string, std::unordered_map<std::string, ResourceId>> ContentKeyToId[(int) EnumDefContent::MAX_ENUM];
// Профили зарегистрированные модами
std::vector<std::unique_ptr<TableEntry<DefVoxel>>> Profiles_Base_Voxel;
std::vector<std::unique_ptr<TableEntry<DefNode>>> Profiles_Base_Node;
std::vector<std::unique_ptr<TableEntry<DefWorld>>> Profiles_Base_World;
std::vector<std::unique_ptr<TableEntry<DefPortal>>> Profiles_Base_Portal;
std::vector<std::unique_ptr<TableEntry<DefEntity>>> Profiles_Base_Entity;
std::vector<std::unique_ptr<TableEntry<DefItem>>> Profiles_Base_Item;
// Изменения, накладываемые на профили
// Идентификатор [домен мода модификатора, модификатор]
std::unordered_map<ResourceId, std::vector<std::tuple<std::string, DefVoxel_Mod>>> Profiles_Mod_Voxel;
std::unordered_map<ResourceId, std::vector<std::tuple<std::string, DefNode_Mod>>> Profiles_Mod_Node;
std::unordered_map<ResourceId, std::vector<std::tuple<std::string, DefWorld_Mod>>> Profiles_Mod_World;
std::unordered_map<ResourceId, std::vector<std::tuple<std::string, DefPortal_Mod>>> Profiles_Mod_Portal;
std::unordered_map<ResourceId, std::vector<std::tuple<std::string, DefEntity_Mod>>> Profiles_Mod_Entity;
std::unordered_map<ResourceId, std::vector<std::tuple<std::string, DefItem_Mod>>> Profiles_Mod_Item;
// Затронутые профили в процессе регистраций
// По ним будут пересобраны профили
std::vector<ResourceId> ProfileChanges[(int) EnumDefContent::MAX_ENUM];
// Конечные профили контента
std::vector<std::unique_ptr<TableEntry<DefVoxel>>> Profiles_Voxel;
std::vector<std::unique_ptr<TableEntry<DefNode>>> Profiles_Node;
std::vector<std::unique_ptr<TableEntry<DefWorld>>> Profiles_World;
std::vector<std::unique_ptr<TableEntry<DefPortal>>> Profiles_Portal;
std::vector<std::unique_ptr<TableEntry<DefEntity>>> Profiles_Entity;
std::vector<std::unique_ptr<TableEntry<DefItem>>> Profiles_Item;
std::optional<DefVoxel>& getEntry_Voxel(ResourceId resId) { return Profiles_Voxel[resId / TableEntry<DefVoxel>::ChunkSize]->Entries[resId % TableEntry<DefVoxel>::ChunkSize];}
std::optional<DefNode>& getEntry_Node(ResourceId resId) { return Profiles_Node[resId / TableEntry<DefVoxel>::ChunkSize]->Entries[resId % TableEntry<DefVoxel>::ChunkSize];}
std::optional<DefWorld>& getEntry_World(ResourceId resId) { return Profiles_World[resId / TableEntry<DefVoxel>::ChunkSize]->Entries[resId % TableEntry<DefVoxel>::ChunkSize];}
std::optional<DefPortal>& getEntry_Portal(ResourceId resId) { return Profiles_Portal[resId / TableEntry<DefVoxel>::ChunkSize]->Entries[resId % TableEntry<DefVoxel>::ChunkSize];}
std::optional<DefEntity>& getEntry_Entity(ResourceId resId) { return Profiles_Entity[resId / TableEntry<DefVoxel>::ChunkSize]->Entries[resId % TableEntry<DefVoxel>::ChunkSize];}
std::optional<DefItem>& getEntry_Item(ResourceId resId) { return Profiles_Item[resId / TableEntry<DefVoxel>::ChunkSize]->Entries[resId % TableEntry<DefVoxel>::ChunkSize];}
ResourceId getId(EnumDefContent type, const std::string& domain, const std::string& key) {
if(auto iterCKTI = ContentKeyToId[(int) type].find(domain); iterCKTI != ContentKeyToId[(int) type].end()) {
if(auto iterKey = iterCKTI->second.find(key); iterKey != iterCKTI->second.end()) {
return iterKey->second;
}
}
ResourceId resId = NextId[(int) type]++;
ContentKeyToId[(int) type][domain][key] = resId;
switch(type) {
case EnumDefContent::Voxel:
if(resId >= Profiles_Voxel.size()*TableEntry<DefVoxel>::ChunkSize)
Profiles_Voxel.push_back(std::make_unique<TableEntry<DefVoxel>>());
break;
case EnumDefContent::Node:
if(resId >= Profiles_Node.size()*TableEntry<DefNode>::ChunkSize)
Profiles_Node.push_back(std::make_unique<TableEntry<DefNode>>());
break;
case EnumDefContent::World:
if(resId >= Profiles_World.size()*TableEntry<DefWorld>::ChunkSize)
Profiles_World.push_back(std::make_unique<TableEntry<DefWorld>>());
break;
case EnumDefContent::Portal:
if(resId >= Profiles_Portal.size()*TableEntry<DefPortal>::ChunkSize)
Profiles_Portal.push_back(std::make_unique<TableEntry<DefPortal>>());
break;
case EnumDefContent::Entity:
if(resId >= Profiles_Entity.size()*TableEntry<DefEntity>::ChunkSize)
Profiles_Entity.push_back(std::make_unique<TableEntry<DefEntity>>());
break;
case EnumDefContent::Item:
if(resId >= Profiles_Item.size()*TableEntry<DefItem>::ChunkSize)
Profiles_Item.push_back(std::make_unique<TableEntry<DefItem>>());
break;
default:
std::unreachable();
}
return resId;
}
void registerBase_Node(ResourceId id, const std::string& domain, const std::string& key, const sol::table& profile);
void registerBase_World(ResourceId id, const std::string& domain, const std::string& key, const sol::table& profile);
void registerBase_Entity(ResourceId id, const std::string& domain, const std::string& key, const sol::table& profile);
public:
ContentManager(AssetsPreloader &am);
~ContentManager();
// Регистрирует определение контента
void registerBase(EnumDefContent type, const std::string& domain, const std::string& key, const sol::table& profile);
void unRegisterBase(EnumDefContent type, const std::string& domain, const std::string& key);
// Регистрация модификатора предмета модом
void registerModifier(EnumDefContent type, const std::string& mod, const std::string& domain, const std::string& key, const sol::table& profile);
void unRegisterModifier(EnumDefContent type, const std::string& mod, const std::string& domain, const std::string& key);
// Пометить все профили типа как изменённые (например, после перезагрузки ассетов)
void markAllProfilesDirty(EnumDefContent type);
// Список всех зарегистрированных профилей выбранного типа
std::vector<ResourceId> collectProfileIds(EnumDefContent type) const;
// Компилирует изменённые профили
struct Out_buildEndProfiles {
std::vector<ResourceId> ChangedProfiles[(int) EnumDefContent::MAX_ENUM];
};
Out_buildEndProfiles buildEndProfiles();
std::optional<DefVoxel*> getProfile_Voxel(ResourceId id) {
assert(id < Profiles_Voxel.size()*TableEntry<DefVoxel>::ChunkSize);
auto& value = Profiles_Voxel[id / TableEntry<DefVoxel>::ChunkSize]->Entries[id % TableEntry<DefVoxel>::ChunkSize];
if(value)
return {&*value};
else
return std::nullopt;
}
std::optional<DefNode*> getProfile_Node(ResourceId id) {
assert(id < Profiles_Node.size()*TableEntry<DefNode>::ChunkSize);
auto& value = Profiles_Node[id / TableEntry<DefNode>::ChunkSize]->Entries[id % TableEntry<DefNode>::ChunkSize];
if(value)
return {&*value};
else
return std::nullopt;
}
std::optional<DefWorld*> getProfile_World(ResourceId id) {
assert(id < Profiles_World.size()*TableEntry<DefWorld>::ChunkSize);
auto& value = Profiles_World[id / TableEntry<DefWorld>::ChunkSize]->Entries[id % TableEntry<DefWorld>::ChunkSize];
if(value)
return {&*value};
else
return std::nullopt;
}
std::optional<DefPortal*> getProfile_Portal(ResourceId id) {
assert(id < Profiles_Portal.size()*TableEntry<DefPortal>::ChunkSize);
auto& value = Profiles_Portal[id / TableEntry<DefPortal>::ChunkSize]->Entries[id % TableEntry<DefPortal>::ChunkSize];
if(value)
return {&*value};
else
return std::nullopt;
}
std::optional<DefEntity*> getProfile_Entity(ResourceId id) {
assert(id < Profiles_Entity.size()*TableEntry<DefEntity>::ChunkSize);
auto& value = Profiles_Entity[id / TableEntry<DefEntity>::ChunkSize]->Entries[id % TableEntry<DefEntity>::ChunkSize];
if(value)
return {&*value};
else
return std::nullopt;
}
std::optional<DefItem*> getProfile_Item(ResourceId id) {
assert(id < Profiles_Item.size()*TableEntry<DefItem>::ChunkSize);
auto& value = Profiles_Item[id / TableEntry<DefItem>::ChunkSize]->Entries[id % TableEntry<DefItem>::ChunkSize];
if(value)
return {&*value};
else
return std::nullopt;
}
ResourceId getContentId(EnumDefContent type, const std::string& domain, const std::string& key) {
return getId(type, domain, key);
}
private:
TOS::Logger LOG = "Server>ContentManager";
AssetsPreloader& AM;
};
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,30 +1,21 @@
#pragma once #pragma once
#include "Common/AssetsPreloader.hpp"
#define SOL_EXCEPTIONS_SAFE_PROPAGATION 1
#include <Common/Net.hpp> #include <Common/Net.hpp>
#include <Common/Lockable.hpp> #include <Common/Lockable.hpp>
#include <atomic>
#include <boost/asio/any_io_executor.hpp> #include <boost/asio/any_io_executor.hpp>
#include <boost/asio/io_context.hpp> #include <boost/asio/io_context.hpp>
#include <condition_variable>
#include <filesystem> #include <filesystem>
#include "Common/Abstract.hpp"
#include "RemoteClient.hpp" #include "RemoteClient.hpp"
#include "Server/Abstract.hpp" #include "Server/Abstract.hpp"
#include <TOSLib.hpp> #include <TOSLib.hpp>
#include <functional>
#include <memory> #include <memory>
#include <queue>
#include <set> #include <set>
#include <sol/forward.hpp>
#include <sol/state.hpp>
#include <thread> #include <thread>
#include <unordered_map> #include <unordered_map>
#include "ContentEventController.hpp"
#include "WorldDefManager.hpp" #include "WorldDefManager.hpp"
#include "ContentManager.hpp" #include "BinaryResourceManager.hpp"
#include "World.hpp" #include "World.hpp"
#include "SaveBackend.hpp" #include "SaveBackend.hpp"
@@ -32,33 +23,16 @@
namespace LV::Server { namespace LV::Server {
struct ModRequest {
std::string Id;
std::array<uint32_t, 4> MinVersion, MaxVersion;
};
struct ModInfo {
std::string Id, Name, Description, Author;
std::vector<std::string> AlternativeIds;
std::array<uint32_t, 4> Version;
std::vector<ModRequest> Dependencies, Optional;
float LoadPriority;
fs::path Path;
bool HasLiveReload;
std::string dump() const;
};
namespace fs = std::filesystem; namespace fs = std::filesystem;
class GameServer : public AsyncObject { class GameServer {
asio::io_context &IOC;
TOS::Logger LOG = "GameServer"; TOS::Logger LOG = "GameServer";
DestroyLock UseLock; DestroyLock UseLock;
std::thread RunThread; std::thread RunThread;
bool IsAlive = true, IsGoingShutdown = false; bool IsAlive = true, IsGoingShutdown = false;
std::string ShutdownReason; std::string ShutdownReason;
std::atomic<bool> ModsReloadRequested = false;
static constexpr float static constexpr float
PerTickDuration = 1/30.f, // Минимальная и стартовая длина такта PerTickDuration = 1/30.f, // Минимальная и стартовая длина такта
PerTickAdjustment = 1/60.f; // Подгонка длительности такта в случае провисаний PerTickAdjustment = 1/60.f; // Подгонка длительности такта в случае провисаний
@@ -68,25 +42,36 @@ class GameServer : public AsyncObject {
struct { struct {
Lockable<std::set<std::string>> ConnectedPlayersSet; Lockable<std::set<std::string>> ConnectedPlayersSet;
Lockable<std::list<std::shared_ptr<RemoteClient>>> NewConnectedPlayers; Lockable<std::list<RemoteClient_ptr>> NewConnectedPlayers;
} External; } External;
struct ContentObj { struct ContentObj {
public: public:
AssetsPreloader AM; // WorldDefManager WorldDM;
ContentManager CM; // VoxelDefManager VoxelDM;
// NodeDefManager NodeDM;
BinaryResourceManager TextureM;
BinaryResourceManager ModelM;
BinaryResourceManager SoundM;
// Если контент был перерегистрирован (исключая двоичные ресурсы), то профили будут повторно разосланы ContentObj(asio::io_context &ioc,
ResourceRequest OnContentChanges; std::shared_ptr<ResourceFile> zeroTexture,
std::shared_ptr<ResourceFile> zeroModel,
std::shared_ptr<ResourceFile> zeroSound)
: TextureM(ioc, zeroTexture),
ModelM(ioc, zeroModel),
SoundM(ioc, zeroSound)
{
}
ContentObj(asio::io_context&)
: AM(), CM(AM)
{}
} Content; } Content;
struct { struct {
std::vector<std::shared_ptr<RemoteClient>> RemoteClients; std::vector<std::unique_ptr<ContentEventController>> CECs;
// Индекс игрока, у которого в следующем такте будет пересмотрен ContentEventController->ContentViewCircles
uint16_t CEC_NextRebuildViewCircles = 0, CEC_NextCheckRegions = 0;
ServerTime AfterStartTime = {0, 0}; ServerTime AfterStartTime = {0, 0};
} Game; } Game;
@@ -98,9 +83,9 @@ class GameServer : public AsyncObject {
// depth ограничивает глубину входа в ContentBridges // depth ограничивает глубину входа в ContentBridges
std::vector<ContentViewCircle> accumulateContentViewCircles(ContentViewCircle circle, int depth = 2); std::vector<ContentViewCircle> accumulateContentViewCircles(ContentViewCircle circle, int depth = 2);
// Вынести в отдельный поток // Вынести в отдельный поток
static ContentViewInfo makeContentViewInfo(const std::vector<ContentViewCircle> &views); static ContentViewGlobal makeContentViewGlobal(const std::vector<ContentViewCircle> &views);
ContentViewInfo makeContentViewInfo(ContentViewCircle circle, int depth = 2) { ContentViewGlobal makeContentViewGlobal(ContentViewCircle circle, int depth = 2) {
return makeContentViewInfo(accumulateContentViewCircles(circle, depth)); return makeContentViewGlobal(accumulateContentViewCircles(circle, depth));
} }
// std::unordered_map<WorldId_t, std::vector<ContentViewCircle>> remapCVCsByWorld(const std::vector<ContentViewCircle> &list); // std::unordered_map<WorldId_t, std::vector<ContentViewCircle>> remapCVCsByWorld(const std::vector<ContentViewCircle> &list);
@@ -112,14 +97,9 @@ class GameServer : public AsyncObject {
/* /*
Регистрация миров по строке Регистрация миров по строке
*/ */
/*
*/
private: private:
void _accumulateContentViewCircles(ContentViewCircle circle, int depth); void _accumulateContentViewCircles(ContentViewCircle circle, int depth);
} Expanse; } Expanse;
@@ -131,147 +111,17 @@ class GameServer : public AsyncObject {
std::unique_ptr<IModStorageSaveBackend> ModStorage; std::unique_ptr<IModStorageSaveBackend> ModStorage;
} SaveBackend; } SaveBackend;
/*
Обязательно между тактами
Конвертация ресурсов игры, их хранение в кеше и загрузка в память для отправки клиентам
io_uring или последовательное чтение
Исполнение асинхронного луа
Пул для постоянной работы и синхронизации времени с главным потоком
Сжатие/расжатие регионов в базе
Локальный поток должен собирать ключи профилей для базы
Остальное внутри базы
*/
/*
Отправка изменений чанков клиентам
После окончания такта пул копирует изменённые чанки
- синхронизация сбора в stepDatabaseSync -
сжимает их и отправляет клиентам
- синхронизация в начале stepPlayerProceed -
^ к этому моменту все данные должны быть отправлены в RemoteClient
*/
struct BackingChunkPressure_t {
TOS::Logger LOG = "BackingChunkPressure";
volatile bool NeedShutdown = false;
std::vector<std::thread> Threads;
std::mutex Mutex;
volatile int RunCollect = 0, RunCompress = 0, Iteration = 0;
std::condition_variable Symaphore;
std::unordered_map<WorldId_t, std::unique_ptr<World>> *Worlds;
void startCollectChanges() {
std::lock_guard<std::mutex> lock(Mutex);
RunCollect = Threads.size();
RunCompress = Threads.size();
Iteration += 1;
assert(RunCollect != 0);
Symaphore.notify_all();
}
void endCollectChanges() {
std::unique_lock<std::mutex> lock(Mutex);
Symaphore.wait(lock, [&](){ return RunCollect == 0 || NeedShutdown; });
}
void endWithResults() {
std::unique_lock<std::mutex> lock(Mutex);
Symaphore.wait(lock, [&](){ return RunCompress == 0 || NeedShutdown; });
}
void stop() {
{
std::unique_lock<std::mutex> lock(Mutex);
NeedShutdown = true;
Symaphore.notify_all();
}
for(std::thread& thread : Threads)
thread.join();
}
/* __attribute__((optimize("O3"))) */ void run(int id);
} BackingChunkPressure;
/*
Генератор шума
*/
struct BackingNoiseGenerator_t {
struct NoiseKey {
WorldId_t WId;
Pos::GlobalRegion RegionPos;
};
TOS::Logger LOG = "BackingNoiseGenerator";
bool NeedShutdown = false;
std::vector<std::thread> Threads;
TOS::SpinlockObject<std::queue<NoiseKey>> Input;
TOS::SpinlockObject<std::vector<std::pair<NoiseKey, std::array<float, 64*64*64>>>> Output;
void stop() {
NeedShutdown = true;
for(std::thread& thread : Threads)
thread.join();
}
void run(int id);
std::vector<std::pair<NoiseKey, std::array<float, 64*64*64>>>
tickSync(std::unordered_map<WorldId_t, std::vector<Pos::GlobalRegion>> &&input) {
{
auto lock = Input.lock();
for(auto& [worldId, region] : input) {
for(auto& regionPos : region) {
lock->push({worldId, regionPos});
}
}
}
auto lock = Output.lock();
std::vector<std::pair<NoiseKey, std::array<float, 64*64*64>>> out = std::move(*lock);
lock->reserve(8000);
return std::move(out);
}
} BackingNoiseGenerator;
/*
Обработчик асинронного луа
*/
struct BackingAsyncLua_t {
TOS::Logger LOG = "BackingAsyncLua";
bool NeedShutdown = false;
std::vector<std::thread> Threads;
TOS::SpinlockObject<std::queue<std::pair<BackingNoiseGenerator_t::NoiseKey, std::array<float, 64*64*64>>>> NoiseIn;
TOS::SpinlockObject<std::vector<std::pair<BackingNoiseGenerator_t::NoiseKey, World::RegionIn>>> RegionOut;
void stop() {
NeedShutdown = true;
for(std::thread& thread : Threads)
thread.join();
}
void run(int id);
} BackingAsyncLua;
sol::state LuaMainState;
std::vector<ModInfo> LoadedMods;
std::vector<std::pair<std::string, sol::table>> ModInstances;
// Идентификатор текущегго мода, находящевося в обработке
std::string CurrentModId;
AssetsPreloader::AssetsRegister AssetsInit;
DefEntityId PlayerEntityDefId = 0;
public: public:
GameServer(asio::io_context &ioc, fs::path worldPath); GameServer(asio::io_context &ioc, fs::path worldPath)
: IOC(ioc),
Content(ioc, nullptr, nullptr, nullptr)
{
init(worldPath);
}
virtual ~GameServer(); virtual ~GameServer();
void shutdown(const std::string reason) { void shutdown(const std::string reason) {
if(ShutdownReason.empty()) if(ShutdownReason.empty())
ShutdownReason = reason; ShutdownReason = reason;
@@ -285,7 +135,6 @@ public:
void waitShutdown() { void waitShutdown() {
UseLock.wait_no_use(); UseLock.wait_no_use();
} }
void requestModsReload();
// Подключение tcp сокета // Подключение tcp сокета
coro<> pushSocketConnect(tcp::socket socket); coro<> pushSocketConnect(tcp::socket socket);
@@ -294,75 +143,37 @@ public:
// Инициализация игрового протокола для сокета (onSocketAuthorized() может передать сокет в onSocketGame()) // Инициализация игрового протокола для сокета (onSocketAuthorized() может передать сокет в onSocketGame())
coro<> pushSocketGameProtocol(tcp::socket socket, const std::string username); coro<> pushSocketGameProtocol(tcp::socket socket, const std::string username);
/* Загрузит, сгенерирует или просто выдаст регион из мира, который должен существовать */
Region* forceGetRegion(WorldId_t worldId, Pos::GlobalRegion pos);
private: private:
void init(fs::path worldPath); void init(fs::path worldPath);
void prerun(); void prerun();
void run(); void run();
void initLuaAssets(); void stepContent();
void initLuaPre();
void initLua();
void initLuaPost();
/* /*
Подключение/отключение игроков Дождаться и получить необходимые данные с бд или диска
Получить несрочные данные
*/ */
void stepSyncWithAsync();
void stepConnections(); void stepPlayers();
void stepWorlds();
/* /*
Переинициализация модов, если требуется Пересмотр наблюдаемых зон (чанки, регионы, миры)
Добавить требуемые регионы в список на предзагрузку с приоритетом
TODO: нужен механизм асинхронной загрузки регионов с бд
В начале следующего такта обязательное дожидание прогрузки активной зоны
и
оповещение миров об изменениях в наблюдаемых регионах
*/ */
void stepViewContent();
void stepModInitializations(); void stepSendPlayersPackets();
void reloadMods(); void stepLoadRegions();
void stepGlobal();
/* void stepSave();
Пересчёт зон видимости игроков, если необходимо void save();
Выгрузить более не используемые регионы
Сохранение регионов
Создание списка регионов необходимых для загрузки (бд автоматически будет предзагружать)
<Синхронизация с модулем сохранений>
Очередь загрузки, выгрузка регионов и получение загруженных из бд регионов
Получить список регионов отсутствующих в сохранении и требующих генерации
Подпись на загруженные регионы (отправить полностью на клиент)
*/
IWorldSaveBackend::TickSyncInfo_Out stepDatabaseSync();
/*
Синхронизация с генератором карт (отправка запросов на генерацию и получение шума для обработки модами)
Обработка модами сырых регионов полученных с бд
Синхронизация с потоками модов
*/
void stepGeneratorAndLuaAsync(IWorldSaveBackend::TickSyncInfo_Out db);
/*
Пакеты игроков получает асинхронный поток в RemoteClient
Остаётся только обработать распаршенные пакеты
*/
void stepPlayerProceed();
/*
Физика
*/
void stepWorldPhysic();
/*
Глобальный такт
*/
void stepGlobalStep();
/*
Обработка запросов двоичных ресурсов и определений
Отправка пакетов игрокам
Запуск задачи ChunksChanges
*/
void stepSyncContent();
}; };
} }

View File

@@ -1,12 +1 @@
#pragma once #pragma once
namespace LV::Server {
class NodeDefManager {
public:
};
}

File diff suppressed because it is too large Load Diff

View File

@@ -3,23 +3,25 @@
#include <TOSLib.hpp> #include <TOSLib.hpp>
#include <Common/Lockable.hpp> #include <Common/Lockable.hpp>
#include <Common/Net.hpp> #include <Common/Net.hpp>
#include <Common/Async.hpp>
#include "Abstract.hpp" #include "Abstract.hpp"
#include "Common/Packets.hpp" #include "Common/Packets.hpp"
#include "Server/ContentManager.hpp" #include "Server/ContentEventController.hpp"
#include "TOSAsync.hpp"
#include "boost/asio/detached.hpp"
#include "boost/asio/io_context.hpp"
#include "boost/asio/use_awaitable.hpp"
#include <Common/Abstract.hpp> #include <Common/Abstract.hpp>
#include <bitset> #include <bitset>
#include <initializer_list> #include <initializer_list>
#include <optional> #include <memory>
#include <queue> #include <set>
#include <string>
#include <type_traits> #include <type_traits>
#include <unordered_map> #include <unordered_map>
#include <unordered_set>
namespace LV::Server { namespace LV::Server {
class World;
class GameServer;
template<typename ServerKey, typename ClientKey, std::enable_if_t<sizeof(ServerKey) >= sizeof(ClientKey), int> = 0> template<typename ServerKey, typename ClientKey, std::enable_if_t<sizeof(ServerKey) >= sizeof(ClientKey), int> = 0>
class CSChunkedMapper { class CSChunkedMapper {
std::unordered_map<uint32_t, std::tuple<std::bitset<64>, std::array<ServerKey, 64>>> Chunks; std::unordered_map<uint32_t, std::tuple<std::bitset<64>, std::array<ServerKey, 64>>> Chunks;
@@ -105,7 +107,7 @@ public:
// Соотнести идентификатор на стороне клиента с идентификатором на стороне сервера // Соотнести идентификатор на стороне клиента с идентификатором на стороне сервера
ServerKey toServer(ClientKey ckey) { ServerKey toServer(ClientKey ckey) {
return CSmapper.toServer(ckey); return CSmapper.toServer(ckey);
} }
// Удаляет серверный идентификатор, освобождая идентификатор клиента // Удаляет серверный идентификатор, освобождая идентификатор клиента
ClientKey erase(ServerKey skey) { ClientKey erase(ServerKey skey) {
@@ -139,159 +141,163 @@ public:
состоянии для клиента и шаблоны, которые клиенту уже не нужны. состоянии для клиента и шаблоны, которые клиенту уже не нужны.
Соответствующие менеджеры ресурсов будут следить за изменениями Соответствующие менеджеры ресурсов будут следить за изменениями
этих ресурсов и переотправлять их клиенту этих ресурсов и переотправлять их клиенту
Информация о двоичных ресурсах будет получена сразу же при их запросе.
Действительная отправка ресурсов будет только по запросу клиента.
*/ */
struct ResourceRequest { struct ResourceRequest {
std::vector<Hash_t> Hashes; std::vector<BinTextureId_t> NewTextures;
std::vector<BinModelId_t> NewModels;
std::vector<BinSoundId_t> NewSounds;
void merge(const ResourceRequest &obj) { std::vector<DefWorldId_t> NewWorlds;
Hashes.insert(Hashes.end(), obj.Hashes.begin(), obj.Hashes.end()); std::vector<DefVoxelId_t> NewVoxels;
std::vector<DefNodeId_t> NewNodes;
std::vector<DefPortalId_t> NewPortals;
std::vector<DefEntityId_t> NewEntityes;
void insert(const ResourceRequest &obj) {
NewTextures.insert(NewTextures.end(), obj.NewTextures.begin(), obj.NewTextures.end());
NewModels.insert(NewModels.end(), obj.NewModels.begin(), obj.NewModels.end());
NewSounds.insert(NewSounds.end(), obj.NewSounds.begin(), obj.NewSounds.end());
NewWorlds.insert(NewWorlds.end(), obj.NewWorlds.begin(), obj.NewWorlds.end());
NewVoxels.insert(NewVoxels.end(), obj.NewVoxels.begin(), obj.NewVoxels.end());
NewNodes.insert(NewNodes.end(), obj.NewNodes.begin(), obj.NewNodes.end());
NewPortals.insert(NewPortals.end(), obj.NewPortals.begin(), obj.NewPortals.end());
NewEntityes.insert(NewEntityes.end(), obj.NewEntityes.begin(), obj.NewEntityes.end());
} }
void uniq() { void uniq() {
std::sort(Hashes.begin(), Hashes.end()); for(std::vector<ResourceId_t> *vec : {&NewTextures, &NewModels, &NewSounds, &NewWorlds, &NewVoxels, &NewNodes, &NewPortals, &NewEntityes}) {
auto last = std::unique(Hashes.begin(), Hashes.end()); std::sort(vec->begin(), vec->end());
Hashes.erase(last, Hashes.end()); auto last = std::unique(vec->begin(), vec->end());
vec->erase(last, vec->end());
}
} }
}; };
struct AssetBinaryInfo { using EntityKey = std::tuple<WorldId_c, Pos::GlobalRegion>;
Resource Data;
Hash_t Hash;
};
// using EntityKey = std::tuple<WorldId_c, Pos::GlobalRegion>;
class RemoteClient;
using RemoteClient_ptr = std::unique_ptr<RemoteClient, std::function<void(RemoteClient*)>>;
/* /*
Обработчик сокета клиента. Обработчик сокета клиента.
Подписывает клиента на отслеживание необходимых ресурсов Подписывает клиента на отслеживание необходимых ресурсов
на основе передаваемых клиенту данных на основе передаваемых клиенту данных
*/ */
class RemoteClient { class RemoteClient : public TOS::IAsyncDestructible {
TOS::Logger LOG; TOS::Logger LOG;
DestroyLock UseLock;
Net::AsyncSocket Socket; Net::AsyncSocket Socket;
bool IsConnected = true, IsGoingShutdown = false; bool IsConnected = true, IsGoingShutdown = false;
struct NetworkAndResource_t { struct ResUsesObj {
// Смена идентификаторов сервера на клиентские // Счётчики использования базовых ресурсов высшими объектами
SCSKeyRemapper<ServerEntityId_t, ClientEntityId_t> ReMapEntities; std::map<BinTextureId_t, uint32_t> BinTexture;
// Накопленные чанки для отправки std::map<BinSoundId_t, uint32_t> BinSound;
std::unordered_map<
WorldId_t, // Миры
std::unordered_map<
Pos::GlobalRegion, // Регионы
std::pair<
std::unordered_map< // Воксели
Pos::bvec4u, // Чанки
std::u8string
>,
std::unordered_map< // Ноды
Pos::bvec4u, // Чанки
std::u8string
>
>
>
> ChunksToSend;
// Запрос информации об ассетах и профилях контента // Может использовать текстуры
ResourceRequest NextRequest; std::map<BinModelId_t, uint32_t> BinModel;
// Запрошенные клиентом ресурсы
/// TODO: здесь может быть засор
std::vector<Hash_t> ClientRequested;
Net::Packet NextPacket; // Будут использовать в своих определениях текстуры, звуки, модели
std::vector<Net::Packet> SimplePackets; std::map<DefWorldId_t, uint32_t> DefWorld;
void checkPacketBorder(uint16_t size) { std::map<DefVoxelId_t, uint32_t> DefVoxel;
if(64000-NextPacket.size() < size || (NextPacket.size() != 0 && size == 0)) { std::map<DefNodeId_t, uint32_t> DefNode;
SimplePackets.push_back(std::move(NextPacket)); std::map<DefPortalId_t, uint32_t> DefPortal;
} std::map<DefEntityId_t, uint32_t> DefEntity;
}
void prepareChunkUpdate_Voxels(
WorldId_t worldId,
Pos::GlobalRegion regionPos,
Pos::bvec4u chunkPos,
const std::u8string& compressed_voxels
) {
ChunksToSend[worldId][regionPos].first[chunkPos] = compressed_voxels;
}
void prepareChunkUpdate_Nodes( // Переписываемый контент
WorldId_t worldId,
Pos::GlobalRegion regionPos,
Pos::bvec4u chunkPos,
const std::u8string& compressed_nodes
) {
ChunksToSend[worldId][regionPos].second[chunkPos] = compressed_nodes;
}
void flushChunksToPackets(); // Сущности используют текстуры, звуки, модели
struct EntityResourceUse {
DefEntityId_t DefId;
void prepareEntitiesRemove(const std::vector<ServerEntityId_t>& entityId); std::unordered_set<BinTextureId_t> Textures;
void prepareRegionsRemove(WorldId_t worldId, std::vector<Pos::GlobalRegion> regionPoses); std::unordered_set<BinSoundId_t> Sounds;
void prepareWorldRemove(WorldId_t worldId); std::unordered_set<BinModelId_t> Models;
void prepareEntitiesUpdate(const std::vector<std::tuple<ServerEntityId_t, const Entity*>>& entities); };
void prepareEntitiesUpdate_Dynamic(const std::vector<std::tuple<ServerEntityId_t, const Entity*>>& entities);
void prepareEntitySwap(ServerEntityId_t prevEntityId, ServerEntityId_t nextEntityId); std::map<GlobalEntityId_t, EntityResourceUse> Entity;
void prepareWorldUpdate(WorldId_t worldId, World* world);
}; // Чанки используют воксели, ноды
std::map<std::tuple<WorldId_t, Pos::GlobalChunk>, std::unordered_set<DefVoxelId_t>> ChunkVoxels;
std::map<std::tuple<WorldId_t, Pos::GlobalChunk>, std::unordered_set<DefNodeId_t>> ChunkNodes;
// Миры
struct WorldResourceUse {
DefWorldId_t DefId;
std::unordered_set<BinTextureId_t> Textures;
std::unordered_set<BinSoundId_t> Sounds;
std::unordered_set<BinModelId_t> Models;
};
std::map<WorldId_t, WorldResourceUse> Worlds;
// Порталы
struct PortalResourceUse {
DefPortalId_t DefId;
std::unordered_set<BinTextureId_t> Textures;
std::unordered_set<BinSoundId_t> Sounds;
std::unordered_set<BinModelId_t> Models;
};
std::map<PortalId_t, PortalResourceUse> Portals;
} ResUses;
struct { struct {
/* SCSKeyRemapper<BinTextureId_t, TextureId_c> BinTextures;
К концу такта собираются необходимые идентификаторы ресурсов SCSKeyRemapper<BinSoundId_t, SoundId_c> BinSounds;
В конце такта сервер забирает запросы и возвращает информацию SCSKeyRemapper<BinModelId_t, ModelId_c> BinModels;
о ресурсах. Отправляем связку Идентификатор + домен:ключ
+ хеш. Если у клиента не окажется этого ресурса, он может его запросить
*/
// Ресурсы, отправленные на клиент в этой сессии SCSKeyRemapper<DefWorldId_t, DefWorldId_c> DefWorlds;
std::vector<Hash_t> OnClient; SCSKeyRemapper<DefVoxelId_t, DefVoxelId_c> DefVoxels;
// Отправляемые на клиент ресурсы SCSKeyRemapper<DefNodeId_t, DefNodeId_c> DefNodes;
// Ресурс, количество отправленных байт SCSKeyRemapper<DefPortalId_t, DefPortalId_c> DefPortals;
std::vector<std::tuple<Resource, size_t>> ToSend; SCSKeyRemapper<DefEntityId_t, DefEntityId_c> DefEntityes;
// Пакет с ресурсами
std::vector<Net::Packet> AssetsPackets;
Net::Packet AssetsPacket;
} AssetsInWork;
TOS::SpinlockObject<NetworkAndResource_t> NetworkAndResource; SCSKeyRemapper<WorldId_t, WorldId_c> Worlds;
SCSKeyRemapper<PortalId_t, PortalId_c> Portals;
SCSKeyRemapper<GlobalEntityId_t, EntityId_c> Entityes;
} ResRemap;
Net::Packet NextPacket;
ResourceRequest NextRequest;
std::vector<Net::Packet> SimplePackets;
TOS::WaitableCoro RunCoro;
public: public:
const std::string Username; const std::string Username;
Pos::Object CameraPos = {0, 0, 0}; Pos::Object CameraPos = {0, 0, 0};
Pos::Object LastPos = CameraPos;
ToServer::PacketQuat CameraQuat = {0}; ToServer::PacketQuat CameraQuat = {0};
TOS::SpinlockObject<std::queue<uint8_t>> Actions;
ResourceId RecievedAssets[(int) EnumAssets::MAX_ENUM] = {0};
// Регионы, наблюдаемые клиентом private:
ContentViewInfo ContentViewState;
// Если игрок пересекал границы региона (для перерасчёта ContentViewState) RemoteClient(asio::io_context &ioc, tcp::socket socket, const std::string username)
bool CrossedRegion = true; : IAsyncDestructible(ioc), LOG("RemoteClient " + username), Socket(ioc, std::move(socket)),
std::queue<Pos::GlobalNode> Build, Break; Username(username), RunCoro(ioc)
std::optional<ServerEntityId_t> PlayerEntity; {
RunCoro.co_spawn(run());
}
virtual coro<> asyncDestructor() override;
coro<> run();
public: public:
RemoteClient(asio::io_context &ioc, tcp::socket socket, const std::string username, GameServer* server) static RemoteClient_ptr Create(asio::io_context &ioc, tcp::socket socket, const std::string username) {
: LOG("RemoteClient " + username), Socket(ioc, std::move(socket)), Username(username), Server(server) return createUnique<>(ioc, new RemoteClient(ioc, std::move(socket), username));
{} }
~RemoteClient(); virtual ~RemoteClient();
coro<> run();
void shutdown(EnumDisconnect type, const std::string reason); void shutdown(EnumDisconnect type, const std::string reason);
bool isConnected() { return IsConnected; } bool isConnected() { return IsConnected; }
void setPlayerEntity(ServerEntityId_t id) { PlayerEntity = id; }
std::optional<ServerEntityId_t> getPlayerEntity() const { return PlayerEntity; }
void clearPlayerEntity() { PlayerEntity.reset(); }
void pushPackets(std::vector<Net::Packet> *simplePackets, std::vector<Net::SmartPacket> *smartPackets = nullptr) { void pushPackets(std::vector<Net::Packet> *simplePackets, std::vector<Net::SmartPacket> *smartPackets = nullptr) {
if(IsGoingShutdown) if(IsGoingShutdown)
@@ -300,118 +306,59 @@ public:
Socket.pushPackets(simplePackets, smartPackets); Socket.pushPackets(simplePackets, smartPackets);
} }
// Возвращает список точек наблюдений клиентом с радиусом в регионах // Функции подготавливают пакеты к отправке
std::vector<std::tuple<WorldId_t, Pos::Object, uint8_t>> getViewPoints(); // Отслеживаемое игроком использование контента
// Maybe?
// Текущий список вокселей, определения нод, которые больше не используются в чанке, и определения нод, которые теперь используются
//void prepareChunkUpdate_Voxels(WorldId_t worldId, Pos::GlobalChunk chunkPos, const std::vector<VoxelCube> &voxels, const std::vector<DefVoxelId_t> &noLongerInUseDefs, const std::vector<DefVoxelId_t> &nowUsed);
void prepareChunkUpdate_Voxels(WorldId_t worldId, Pos::GlobalChunk chunkPos, const std::vector<VoxelCube> &voxels);
void prepareChunkUpdate_Nodes(WorldId_t worldId, Pos::GlobalChunk chunkPos, const std::unordered_map<Pos::Local16_u, Node> &nodes);
void prepareChunkUpdate_LightPrism(WorldId_t worldId, Pos::GlobalChunk chunkPos, const LightPrism *lights);
void prepareChunkRemove(WorldId_t worldId, Pos::GlobalChunk chunkPos);
/* void prepareEntitySwap(GlobalEntityId_t prevEntityId, GlobalEntityId_t nextEntityId);
Сервер собирает изменения миров, сжимает их и раздаёт на отправку игрокам void prepareEntityUpdate(GlobalEntityId_t entityId, const Entity *entity);
*/ void prepareEntityRemove(GlobalEntityId_t entityId);
// Все функции prepare потокобезопасные void prepareWorldUpdate(WorldId_t worldId, World* world);
// maybe используются в BackingChunkPressure_t в GameServer в пуле потоков. void prepareWorldRemove(WorldId_t worldId);
// если возвращает false, то блокировка сейчас находится у другого потока
// и запрос не был обработан.
// Создаёт пакет отправки вокселей чанка void preparePortalUpdate(PortalId_t portalId, void* portal);
void prepareChunkUpdate_Voxels( void preparePortalRemove(PortalId_t portalId);
WorldId_t worldId,
Pos::GlobalRegion regionPos,
Pos::bvec4u chunkPos,
const std::u8string& compressed_voxels
) {
NetworkAndResource.lock()->prepareChunkUpdate_Voxels(worldId, regionPos, chunkPos, compressed_voxels);
}
// Создаёт пакет отправки нод чанка
void prepareChunkUpdate_Nodes(
WorldId_t worldId,
Pos::GlobalRegion regionPos,
Pos::bvec4u chunkPos,
const std::u8string& compressed_nodes
) {
NetworkAndResource.lock()->prepareChunkUpdate_Nodes(worldId, regionPos, chunkPos, compressed_nodes);
}
// Клиент перестал наблюдать за сущностями
void prepareEntitiesRemove(const std::vector<ServerEntityId_t>& entityId) { NetworkAndResource.lock()->prepareEntitiesRemove(entityId); }
// Регион удалён из зоны видимости
void prepareRegionsRemove(WorldId_t worldId, std::vector<Pos::GlobalRegion> regionPoses) { NetworkAndResource.lock()->prepareRegionsRemove(worldId, regionPoses); }
// Мир удалён из зоны видимости
void prepareWorldRemove(WorldId_t worldId) { NetworkAndResource.lock()->prepareWorldRemove(worldId); }
// В зоне видимости добавилась новая сущность или она изменилась
void prepareEntitiesUpdate(const std::vector<std::tuple<ServerEntityId_t, const Entity*>>& entities) { NetworkAndResource.lock()->prepareEntitiesUpdate(entities); }
void prepareEntitiesUpdate_Dynamic(const std::vector<std::tuple<ServerEntityId_t, const Entity*>>& entities) { NetworkAndResource.lock()->prepareEntitiesUpdate_Dynamic(entities); }
// Наблюдаемая сущность пересекла границы региона, у неё изменился серверный идентификатор
void prepareEntitySwap(ServerEntityId_t prevEntityId, ServerEntityId_t nextEntityId) { NetworkAndResource.lock()->prepareEntitySwap(prevEntityId, nextEntityId); }
// Мир появился в зоне видимости или изменился
void prepareWorldUpdate(WorldId_t worldId, World* world) { NetworkAndResource.lock()->prepareWorldUpdate(worldId, world); }
// В зоне видимости добавился порта или он изменился
// void preparePortalUpdate(PortalId_t portalId, void* portal);
// Клиент перестал наблюдать за порталом
// void preparePortalRemove(PortalId_t portalId);
// Прочие моменты // Прочие моменты
void prepareCameraSetEntity(ServerEntityId_t entityId); void prepareCameraSetEntity(GlobalEntityId_t entityId);
// Отправка подготовленных пакетов // Отправка подготовленных пакетов
ResourceRequest pushPreparedPackets(); ResourceRequest pushPreparedPackets();
// Создаёт пакет для всех игроков с оповещением о новых идентификаторах (id -> domain+key) // Сообщить о ресурсах
static Net::Packet makePacket_informateAssets_DK( // Сюда приходят все обновления ресурсов движка
const std::array< // Глобально их можно запросить в выдаче pushPreparedPackets()
std::vector<AssetsPreloader::BindDomainKeyInfo>,
static_cast<size_t>(EnumAssets::MAX_ENUM)
>& dkVector
);
// Создаёт пакет для всех игроков с оповещением об изменении файлов ресурсов (id -> hash+header) // Двоичные файлы
static Net::Packet makePacket_informateAssets_HH( void informateDefTexture(const std::unordered_map<BinTextureId_t, std::shared_ptr<ResourceFile>> &textures);
const std::array< void informateDefSound(const std::unordered_map<BinSoundId_t, std::shared_ptr<ResourceFile>> &sounds);
std::vector<AssetsPreloader::BindHashHeaderInfo>, void informateDefModel(const std::unordered_map<BinModelId_t, std::shared_ptr<ResourceFile>> &models);
static_cast<size_t>(EnumAssets::MAX_ENUM)
>& hhVector,
const std::array<
std::vector<ResourceId>,
static_cast<size_t>(EnumAssets::MAX_ENUM)
>& lost
);
// Оповещение о двоичных ресурсах (стриминг по запросу) // Игровые определения
void informateBinaryAssets( void informateDefWorld(const std::unordered_map<DefWorldId_t, World*> &worlds);
const std::vector<AssetBinaryInfo>& resources void informateDefVoxel(const std::unordered_map<DefVoxelId_t, void*> &voxels);
); void informateDefNode(const std::unordered_map<DefNodeId_t, void*> &nodes);
void informateDefEntityes(const std::unordered_map<DefEntityId_t, void*> &entityes);
// Создаёт пакет об обновлении игровых профилей void informateDefPortals(const std::unordered_map<DefPortalId_t, void*> &portals);
static std::vector<Net::Packet> makePackets_sendDefContentUpdate(
std::array<
std::vector<
std::pair<
ResourceId, // Идентификатор профиля
std::u8string // Двоичный формат профиля
>
>,
static_cast<size_t>(EnumDefContent::MAX_ENUM)
> newOrUpdate, // Новые или изменённые
std::array<
std::vector<ResourceId>,
static_cast<size_t>(EnumDefContent::MAX_ENUM)
> lost, // Потерянные профили
std::array<
std::vector<std::pair<std::string, std::string>>,
static_cast<size_t>(EnumDefContent::MAX_ENUM)
> idToDK // Новые привязки
);
void onUpdate();
private: private:
GameServer* Server = nullptr; void checkPacketBorder(uint16_t size);
void protocolError(); void protocolError();
coro<> readPacket(Net::AsyncSocket &sock); coro<> readPacket(Net::AsyncSocket &sock);
coro<> rP_System(Net::AsyncSocket &sock); coro<> rP_System(Net::AsyncSocket &sock);
void incrementBinary(std::unordered_set<BinTextureId_t> &textures, std::unordered_set<BinSoundId_t> &sounds,
std::unordered_set<BinModelId_t> &models);
void decrementBinary(std::unordered_set<BinTextureId_t> &textures, std::unordered_set<BinSoundId_t> &sounds,
std::unordered_set<BinModelId_t> &models);
}; };
} }

View File

@@ -2,7 +2,6 @@
#include "Abstract.hpp" #include "Abstract.hpp"
#include "Common/Abstract.hpp" #include "Common/Abstract.hpp"
#include "Common/Async.hpp"
#include <boost/json.hpp> #include <boost/json.hpp>
#include <boost/json/object.hpp> #include <boost/json/object.hpp>
#include <memory> #include <memory>
@@ -11,59 +10,31 @@
namespace LV::Server { namespace LV::Server {
/* struct SB_Region {
Обменная единица мира
*/
struct SB_Region_In {
// Список вокселей всех чанков
std::unordered_map<Pos::bvec4u, VoxelCube> Voxels;
// Привязка вокселей к ключу профиля
std::vector<std::pair<DefVoxelId, std::string>> VoxelsMap;
// Ноды всех чанков
std::array<std::array<Node, 16*16*16>, 4*4*4> Nodes;
// Привязка нод к ключу профиля
std::vector<std::pair<DefNodeId, std::string>> NodeMap;
// Сущности
std::vector<Entity> Entityes;
// Привязка идентификатора к ключу профиля
std::vector<std::pair<DefEntityId, std::string>> EntityMap;
};
struct DB_Region_Out {
std::vector<VoxelCube_Region> Voxels; std::vector<VoxelCube_Region> Voxels;
std::array<std::array<Node, 16*16*16>, 4*4*4> Nodes; std::unordered_map<DefVoxelId_t, std::string> VoxelsMap;
std::unordered_map<Pos::Local16_u, Node> Nodes;
std::unordered_map<DefNodeId_t, std::string> NodeMap;
std::vector<Entity> Entityes; std::vector<Entity> Entityes;
std::unordered_map<DefEntityId_t, std::string> EntityMap;
std::vector<std::string> VoxelIdToKey, NodeIdToKey, EntityToKey;
}; };
class IWorldSaveBackend { class IWorldSaveBackend {
public: public:
virtual ~IWorldSaveBackend(); virtual ~IWorldSaveBackend();
struct TickSyncInfo_In { // Может ли использоваться параллельно
// Для загрузки и более не используемые (регионы автоматически подгружаются по списку загруженных) virtual bool isAsync() { return false; };
std::unordered_map<WorldId_t, std::vector<Pos::GlobalRegion>> Load, Unload; // Существует ли регион
// Регионы для сохранения virtual bool isExist(std::string worldId, Pos::GlobalRegion regionPos) = 0;
std::unordered_map<WorldId_t, std::vector<std::pair<Pos::GlobalRegion, SB_Region_In>>> ToSave; // Загрузить регион
}; virtual void load(std::string worldId, Pos::GlobalRegion regionPos, SB_Region *data) = 0;
// Сохранить регион
struct TickSyncInfo_Out { virtual void save(std::string worldId, Pos::GlobalRegion regionPos, const SB_Region *data) = 0;
std::unordered_map<WorldId_t, std::vector<Pos::GlobalRegion>> NotExisten; // Удалить регион
std::unordered_map<WorldId_t, std::vector<std::pair<Pos::GlobalRegion, DB_Region_Out>>> LoadedRegions; virtual void remove(std::string worldId, Pos::GlobalRegion regionPos) = 0;
}; // Удалить мир
virtual void remove(std::string worldId) = 0;
/*
Обмен данными раз в такт
Хотим списки на загрузку регионов
Отдаём уже загруженные регионы и список отсутствующих в базе регионов
*/
virtual TickSyncInfo_Out tickSync(TickSyncInfo_In &&data) = 0;
/*
Устанавливает радиус вокруг прогруженного региона для предзагрузки регионов
*/
virtual void changePreloadDistance(uint8_t value) = 0;
}; };
struct SB_Player { struct SB_Player {
@@ -74,6 +45,8 @@ class IPlayerSaveBackend {
public: public:
virtual ~IPlayerSaveBackend(); virtual ~IPlayerSaveBackend();
// Может ли использоваться параллельно
virtual bool isAsync() { return false; };
// Существует ли игрок // Существует ли игрок
virtual bool isExist(PlayerId_t playerId) = 0; virtual bool isExist(PlayerId_t playerId) = 0;
// Загрузить игрока // Загрузить игрока
@@ -93,30 +66,34 @@ class IAuthSaveBackend {
public: public:
virtual ~IAuthSaveBackend(); virtual ~IAuthSaveBackend();
// Может ли использоваться параллельно
virtual bool isAsync() { return false; };
// Существует ли игрок // Существует ли игрок
virtual coro<bool> isExist(std::string username) = 0; virtual bool isExist(std::string playerId) = 0;
// Переименовать игрока // Переименовать игрока
virtual coro<> rename(std::string prevUsername, std::string newUsername) = 0; virtual void rename(std::string fromPlayerId, std::string toPlayerId) = 0;
// Загрузить игрока (если есть, вернёт true) // Загрузить игрока
virtual coro<bool> load(std::string username, SB_Auth &data) = 0; virtual void load(std::string playerId, SB_Auth *data) = 0;
// Сохранить игрока // Сохранить игрока
virtual coro<> save(std::string username, const SB_Auth &data) = 0; virtual void save(std::string playerId, const SB_Auth *data) = 0;
// Удалить игрока // Удалить игрока
virtual coro<> remove(std::string username) = 0; virtual void remove(std::string playerId) = 0;
}; };
class IModStorageSaveBackend { class IModStorageSaveBackend {
public: public:
virtual ~IModStorageSaveBackend(); virtual ~IModStorageSaveBackend();
// // Загрузить запись // Может ли использоваться параллельно
// virtual void load(std::string domain, std::string key, std::string *data) = 0; virtual bool isAsync() { return false; };
// // Сохранить запись // Загрузить запись
// virtual void save(std::string domain, std::string key, const std::string *data) = 0; virtual void load(std::string domain, std::string key, std::string *data) = 0;
// // Удалить запись // Сохранить запись
// virtual void remove(std::string domain, std::string key) = 0; virtual void save(std::string domain, std::string key, const std::string *data) = 0;
// // Удалить домен // Удалить запись
// virtual void remove(std::string domain) = 0; virtual void remove(std::string domain, std::string key) = 0;
// Удалить домен
virtual void remove(std::string domain) = 0;
}; };
class ISaveBackendProvider { class ISaveBackendProvider {

View File

@@ -22,7 +22,7 @@ class WSB_Filesystem : public IWorldSaveBackend {
public: public:
WSB_Filesystem(const boost::json::object &data) { WSB_Filesystem(const boost::json::object &data) {
Dir = (std::string) data.at("path").as_string(); Dir = (std::string) data.at("Path").as_string();
} }
virtual ~WSB_Filesystem() { virtual ~WSB_Filesystem() {
@@ -30,80 +30,84 @@ public:
} }
fs::path getPath(std::string worldId, Pos::GlobalRegion regionPos) { fs::path getPath(std::string worldId, Pos::GlobalRegion regionPos) {
return Dir / worldId / std::to_string(regionPos.x) / std::to_string(regionPos.y) / std::to_string(regionPos.z); return Dir / worldId / std::to_string(regionPos.X) / std::to_string(regionPos.Y) / std::to_string(regionPos.Z);
} }
virtual TickSyncInfo_Out tickSync(TickSyncInfo_In &&data) override { virtual bool isAsync() { return false; };
TickSyncInfo_Out out;
out.NotExisten = std::move(data.Load); virtual bool isExist(std::string worldId, Pos::GlobalRegion regionPos) {
return out; return fs::exists(getPath(worldId, regionPos));
} }
virtual void changePreloadDistance(uint8_t value) override { virtual void load(std::string worldId, Pos::GlobalRegion regionPos, SB_Region *data) {
std::ifstream fd(getPath(worldId, regionPos));
js::object jobj = js::parse(fd).as_object();
{
js::array &jaVoxels = jobj.at("Voxels").as_array();
for(js::value &jvVoxel : jaVoxels) {
js::object &joVoxel = jvVoxel.as_object();
VoxelCube_Region cube;
cube.VoxelId = joVoxel.at("Material").as_uint64();
cube.Left.X = joVoxel.at("LeftX").as_uint64();
cube.Left.Y = joVoxel.at("LeftY").as_uint64();
cube.Left.Z = joVoxel.at("LeftZ").as_uint64();
cube.Right.X = joVoxel.at("RightX").as_uint64();
cube.Right.Y = joVoxel.at("RightY").as_uint64();
cube.Right.Z = joVoxel.at("RightZ").as_uint64();
data->Voxels.push_back(cube);
}
}
{
js::object &joVoxelMap = jobj.at("VoxelsMap").as_object();
for(js::key_value_pair &jkvp : joVoxelMap) {
data->VoxelsMap[std::stoul(jkvp.key())] = jkvp.value().as_string();
}
}
} }
// virtual void load(std::string worldId, Pos::GlobalRegion regionPos, SB_Region *data) { virtual void save(std::string worldId, Pos::GlobalRegion regionPos, const SB_Region *data) {
// std::ifstream fd(getPath(worldId, regionPos)); js::object jobj;
// js::object jobj = js::parse(fd).as_object();
// { {
// js::array &jaVoxels = jobj.at("Voxels").as_array(); js::array jaVoxels;
// for(js::value &jvVoxel : jaVoxels) { for(const VoxelCube_Region &cube : data->Voxels) {
// js::object &joVoxel = jvVoxel.as_object(); js::object joVoxel;
// VoxelCube_Region cube; joVoxel["Material"] = cube.VoxelId;
// cube.Data = joVoxel.at("Data").as_uint64(); joVoxel["LeftX"] = cube.Left.X;
// cube.Left.x = joVoxel.at("LeftX").as_uint64(); joVoxel["LeftY"] = cube.Left.Y;
// cube.Left.y = joVoxel.at("LeftY").as_uint64(); joVoxel["LeftZ"] = cube.Left.Z;
// cube.Left.z = joVoxel.at("LeftZ").as_uint64(); joVoxel["RightX"] = cube.Right.X;
// cube.Right.x = joVoxel.at("RightX").as_uint64(); joVoxel["RightY"] = cube.Right.Y;
// cube.Right.y = joVoxel.at("RightY").as_uint64(); joVoxel["RightZ"] = cube.Right.Z;
// cube.Right.z = joVoxel.at("RightZ").as_uint64(); jaVoxels.push_back(std::move(joVoxel));
// data->Voxels.push_back(cube); }
// }
// }
// { jobj["Voxels"] = std::move(jaVoxels);
// js::object &joVoxelMap = jobj.at("VoxelsMap").as_object(); }
// for(js::key_value_pair &jkvp : joVoxelMap) {
// data->VoxelsMap[std::stoul(jkvp.key())] = jkvp.value().as_string();
// }
// }
// }
// virtual void save(std::string worldId, Pos::GlobalRegion regionPos, const SB_Region *data) { {
// js::object jobj; js::object joVoxelMap;
for(const auto &pair : data->VoxelsMap) {
joVoxelMap[std::to_string(pair.first)] = pair.second;
}
// { jobj["VoxelsMap"] = std::move(joVoxelMap);
// js::array jaVoxels; }
// for(const VoxelCube_Region &cube : data->Voxels) {
// js::object joVoxel;
// joVoxel["Data"] = cube.Data;
// joVoxel["LeftX"] = cube.Left.x;
// joVoxel["LeftY"] = cube.Left.y;
// joVoxel["LeftZ"] = cube.Left.z;
// joVoxel["RightX"] = cube.Right.x;
// joVoxel["RightY"] = cube.Right.y;
// joVoxel["RightZ"] = cube.Right.z;
// jaVoxels.push_back(std::move(joVoxel));
// }
// jobj["Voxels"] = std::move(jaVoxels); fs::create_directories(getPath(worldId, regionPos).parent_path());
// } std::ofstream fd(getPath(worldId, regionPos));
fd << js::serialize(jobj);
}
// { virtual void remove(std::string worldId, Pos::GlobalRegion regionPos) {
// js::object joVoxelMap; fs::remove(getPath(worldId, regionPos));
// for(const auto &pair : data->VoxelsMap) { }
// joVoxelMap[std::to_string(pair.first)] = pair.second;
// }
// jobj["VoxelsMap"] = std::move(joVoxelMap); virtual void remove(std::string worldId) {
// } fs::remove_all(Dir / worldId);
}
// fs::create_directories(getPath(worldId, regionPos).parent_path());
// std::ofstream fd(getPath(worldId, regionPos));
// fd << js::serialize(jobj);
// }
}; };
class PSB_Filesystem : public IPlayerSaveBackend { class PSB_Filesystem : public IPlayerSaveBackend {
@@ -111,7 +115,7 @@ class PSB_Filesystem : public IPlayerSaveBackend {
public: public:
PSB_Filesystem(const boost::json::object &data) { PSB_Filesystem(const boost::json::object &data) {
Dir = (std::string) data.at("path").as_string(); Dir = (std::string) data.at("Path").as_string();
} }
virtual ~PSB_Filesystem() { virtual ~PSB_Filesystem() {
@@ -146,7 +150,7 @@ class ASB_Filesystem : public IAuthSaveBackend {
public: public:
ASB_Filesystem(const boost::json::object &data) { ASB_Filesystem(const boost::json::object &data) {
Dir = (std::string) data.at("path").as_string(); Dir = (std::string) data.at("Path").as_string();
} }
virtual ~ASB_Filesystem() { virtual ~ASB_Filesystem() {
@@ -159,39 +163,35 @@ public:
virtual bool isAsync() { return false; }; virtual bool isAsync() { return false; };
virtual coro<bool> isExist(std::string useranme) override { virtual bool isExist(std::string playerId) {
co_return fs::exists(getPath(useranme)); return fs::exists(getPath(playerId));
} }
virtual coro<> rename(std::string prevUsername, std::string newUsername) override { virtual void rename(std::string fromPlayerId, std::string toPlayerId) {
fs::rename(getPath(prevUsername), getPath(newUsername)); fs::rename(getPath(fromPlayerId), getPath(toPlayerId));
co_return;
} }
virtual coro<bool> load(std::string useranme, SB_Auth& data) override { virtual void load(std::string playerId, SB_Auth *data) {
std::ifstream fd(getPath(useranme)); std::ifstream fd(getPath(playerId));
js::object jobj = js::parse(fd).as_object(); js::object jobj = js::parse(fd).as_object();
data.Id = jobj.at("Id").as_uint64(); data->Id = jobj.at("Id").as_uint64();
data.PasswordHash = jobj.at("PasswordHash").as_string(); data->PasswordHash = jobj.at("PasswordHash").as_string();
co_return true;
} }
virtual coro<> save(std::string playerId, const SB_Auth& data) override { virtual void save(std::string playerId, const SB_Auth *data) {
js::object jobj; js::object jobj;
jobj["Id"] = data.Id; jobj["Id"] = data->Id;
jobj["PasswordHash"] = data.PasswordHash; jobj["PasswordHash"] = data->PasswordHash;
fs::create_directories(getPath(playerId).parent_path()); fs::create_directories(getPath(playerId).parent_path());
std::ofstream fd(getPath(playerId)); std::ofstream fd(getPath(playerId));
fd << js::serialize(jobj); fd << js::serialize(jobj);
co_return;
} }
virtual coro<> remove(std::string username) override { virtual void remove(std::string playerId) {
fs::remove(getPath(username)); fs::remove(getPath(playerId));
co_return;
} }
}; };
@@ -200,7 +200,7 @@ class MSSB_Filesystem : public IModStorageSaveBackend {
public: public:
MSSB_Filesystem(const boost::json::object &data) { MSSB_Filesystem(const boost::json::object &data) {
Dir = (std::string) data.at("path").as_string(); Dir = (std::string) data.at("Path").as_string();
} }
virtual ~MSSB_Filesystem() { virtual ~MSSB_Filesystem() {

View File

@@ -1,12 +1,10 @@
#include "World.hpp" #include "World.hpp"
#include "TOSLib.hpp"
#include <memory>
namespace LV::Server { namespace LV::Server {
World::World(DefWorldId defId) World::World(DefWorldId_t defId)
: DefId(defId) : DefId(defId)
{ {
@@ -16,77 +14,29 @@ World::~World() {
} }
std::vector<Pos::GlobalRegion> World::onRemoteClient_RegionsEnter(WorldId_t worldId, std::shared_ptr<RemoteClient> cec, const std::vector<Pos::GlobalRegion>& enter) { void World::onUpdate(GameServer *server, float dtime) {
std::vector<Pos::GlobalRegion> out;
for(const Pos::GlobalRegion &pos : enter) {
auto iterRegion = Regions.find(pos);
if(iterRegion == Regions.end()) {
out.push_back(pos);
continue;
}
auto &region = *iterRegion->second;
region.RMs.push_back(cec);
region.NewRMs.push_back(cec);
// Отправить клиенту информацию о чанках и сущностях
std::unordered_map<Pos::bvec4u, const std::vector<VoxelCube>*> voxels;
std::unordered_map<Pos::bvec4u, const Node*> nodes;
for(auto& [key, value] : region.Voxels) {
voxels[key] = &value;
}
for(int z = 0; z < 4; z++)
for(int y = 0; y < 4; y++)
for(int x = 0; x < 4; x++) {
nodes[Pos::bvec4u(x, y, z)] = region.Nodes[Pos::bvec4u(x, y, z).pack()].data();
}
if(!region.Entityes.empty()) {
std::vector<std::tuple<ServerEntityId_t, const Entity*>> updates;
updates.reserve(region.Entityes.size());
for(size_t iter = 0; iter < region.Entityes.size(); iter++) {
const Entity& entity = region.Entityes[iter];
if(entity.IsRemoved)
continue;
ServerEntityId_t entityId = {worldId, pos, static_cast<RegionEntityId_t>(iter)};
updates.emplace_back(entityId, &entity);
}
if(!updates.empty())
cec->prepareEntitiesUpdate(updates);
}
}
return out;
} }
void World::onRemoteClient_RegionsLost(WorldId_t worldId, std::shared_ptr<RemoteClient> cec, const std::vector<Pos::GlobalRegion> &lost) { void World::onCEC_RegionsEnter(ContentEventController *cec, const std::vector<Pos::GlobalRegion> &enter) {
for(const Pos::GlobalRegion &pos : enter) {
std::unique_ptr<Region> &region = Regions[pos];
if(!region) {
region = std::make_unique<Region>();
NeedToLoad.push_back(pos);
}
region->CECs.push_back(cec);
}
}
void World::onCEC_RegionsLost(ContentEventController *cec, const std::vector<Pos::GlobalRegion> &lost) {
for(const Pos::GlobalRegion &pos : lost) { for(const Pos::GlobalRegion &pos : lost) {
auto region = Regions.find(pos); auto region = Regions.find(pos);
if(region == Regions.end()) if(region == Regions.end())
continue; continue;
if(!region->second->Entityes.empty()) { std::vector<ContentEventController*> &CECs = region->second->CECs;
std::vector<ServerEntityId_t> removed;
removed.reserve(region->second->Entityes.size());
for(size_t iter = 0; iter < region->second->Entityes.size(); iter++) {
const Entity& entity = region->second->Entityes[iter];
if(entity.IsRemoved)
continue;
removed.emplace_back(worldId, pos, static_cast<RegionEntityId_t>(iter));
}
if(!removed.empty())
cec->prepareEntitiesRemove(removed);
}
std::vector<std::shared_ptr<RemoteClient>> &CECs = region->second->RMs;
for(size_t iter = 0; iter < CECs.size(); iter++) { for(size_t iter = 0; iter < CECs.size(); iter++) {
if(CECs[iter] == cec) { if(CECs[iter] == cec) {
CECs.erase(CECs.begin()+iter); CECs.erase(CECs.begin()+iter);
@@ -96,21 +46,4 @@ void World::onRemoteClient_RegionsLost(WorldId_t worldId, std::shared_ptr<Remote
} }
} }
World::SaveUnloadInfo World::onStepDatabaseSync() { }
return {};
}
void World::pushRegions(std::vector<std::pair<Pos::GlobalRegion, RegionIn>> regions) {
for(auto& [key, value] : regions) {
Region &region = *(Regions[key] = std::make_unique<Region>());
region.Voxels = std::move(value.Voxels);
region.Nodes = value.Nodes;
region.Entityes = std::move(value.Entityes);
}
}
void World::onUpdate(GameServer *server, float dtime) {
}
}

View File

@@ -2,7 +2,7 @@
#include "Common/Abstract.hpp" #include "Common/Abstract.hpp"
#include "Server/Abstract.hpp" #include "Server/Abstract.hpp"
#include "Server/RemoteClient.hpp" #include "Server/ContentEventController.hpp"
#include "Server/SaveBackend.hpp" #include "Server/SaveBackend.hpp"
#include <memory> #include <memory>
#include <unordered_map> #include <unordered_map>
@@ -15,26 +15,37 @@ class GameServer;
class Region { class Region {
public: public:
uint64_t IsChunkChanged_Voxels = 0; uint64_t IsChunkChanged_Voxels[64] = {0};
uint64_t IsChunkChanged_Nodes = 0; uint64_t IsChunkChanged_Nodes[64] = {0};
bool IsChanged = false; // Изменён ли был регион, относительно последнего сохранения bool IsChanged = false; // Изменён ли был регион, относительно последнего сохранения
std::unordered_map<Pos::bvec4u, std::vector<VoxelCube>> Voxels; // cx cy cz
std::vector<VoxelCube> Voxels[16][16][16];
// x y cx cy cz // x y cx cy cz
//LightPrism Lights[16][16][4][4][4]; LightPrism Lights[16][16][16][16][16];
std::unordered_map<Pos::Local16_u, Node> Nodes[16][16][16];
std::array<std::array<Node, 16*16*16>, 4*4*4> Nodes;
std::vector<Entity> Entityes; std::vector<Entity> Entityes;
std::vector<std::shared_ptr<RemoteClient>> RMs, NewRMs; std::vector<ContentEventController*> CECs;
// Используется для прорежения количества проверок на наблюдаемые чанки и сущности
// В одно обновление региона - проверка одного наблюдателя
uint16_t CEC_NextChunkAndEntityesViewCheck = 0;
bool IsLoaded = false;
float LastSaveTime = 0; float LastSaveTime = 0;
void getCollideBoxes(Pos::GlobalRegion rPos, AABB aabb, std::vector<CollisionAABB> &boxes) { void getCollideBoxes(Pos::GlobalRegion rPos, AABB aabb, std::vector<CollisionAABB> &boxes) {
// Абсолютная позиция начала региона // Абсолютная позиция начала региона
Pos::Object raPos = Pos::Object(rPos) << Pos::Object_t::BS_Bit; Pos::Object raPos(rPos.X, rPos.Y, rPos.Z);
raPos <<= Pos::Object_t::BS_Bit;
// Бокс региона // Бокс региона
AABB regionAABB(raPos, raPos+Pos::Object(Pos::Object_t::BS*64)); AABB regionAABB(raPos, raPos+Pos::Object(Pos::Object_t::BS*256));
// Если регион не загружен, то он весь непроходим
if(!IsLoaded) {
boxes.emplace_back(regionAABB);
return;
}
// Собираем коробки сущностей // Собираем коробки сущностей
for(size_t iter = 0; iter < Entityes.size(); iter++) { for(size_t iter = 0; iter < Entityes.size(); iter++) {
@@ -54,48 +65,46 @@ public:
// Собираем коробки вокселей // Собираем коробки вокселей
if(aabb.isCollideWith(regionAABB)) { if(aabb.isCollideWith(regionAABB)) {
// Определяем с какими чанками есть пересечения
glm::ivec3 beg, end; glm::ivec3 beg, end;
for(int axis = 0; axis < 3; axis++) for(int axis = 0; axis < 3; axis++)
beg[axis] = std::max(aabb.VecMin[axis], regionAABB.VecMin[axis]) >> 12 >> 4; beg[axis] = std::max(aabb.VecMin[axis], regionAABB.VecMin[axis]) >> 16;
for(int axis = 0; axis < 3; axis++) for(int axis = 0; axis < 3; axis++)
end[axis] = (std::min(aabb.VecMax[axis], regionAABB.VecMax[axis])+0xffff) >> 12 >> 4; end[axis] = (std::min(aabb.VecMax[axis], regionAABB.VecMax[axis])+0xffff) >> 16;
for(; beg.z <= end.z; beg.z++) for(; beg.z <= end.z; beg.z++)
for(; beg.y <= end.y; beg.y++) for(; beg.y <= end.y; beg.y++)
for(; beg.x <= end.x; beg.x++) { for(; beg.x <= end.x; beg.x++) {
auto iterVoxels = Voxels.find(Pos::bvec4u(beg)); std::vector<VoxelCube> &voxels = Voxels[beg.x][beg.y][beg.z];
if(iterVoxels == Voxels.end() && iterVoxels->second.empty()) if(voxels.empty())
continue; continue;
auto &voxels = iterVoxels->second;
CollisionAABB aabbInfo = CollisionAABB(regionAABB); CollisionAABB aabbInfo = CollisionAABB(regionAABB);
for(int axis = 0; axis < 3; axis++) for(int axis = 0; axis < 3; axis++)
aabbInfo.VecMin.set(axis, aabbInfo.VecMin[axis] | beg[axis] << 16); aabbInfo.VecMin[axis] |= beg[axis] << 16;
for(size_t iter = 0; iter < voxels.size(); iter++) { for(size_t iter = 0; iter < voxels.size(); iter++) {
VoxelCube &cube = voxels[iter]; VoxelCube &cube = voxels[iter];
for(int axis = 0; axis < 3; axis++) for(int axis = 0; axis < 3; axis++)
aabbInfo.VecMin.set(axis, aabbInfo.VecMin[axis] & ~0xff00); aabbInfo.VecMin[axis] &= ~0xff00;
aabbInfo.VecMax = aabbInfo.VecMin; aabbInfo.VecMax = aabbInfo.VecMin;
aabbInfo.VecMin.x |= int(cube.Pos.x) << 6; aabbInfo.VecMin.x |= int(cube.Left.X) << 8;
aabbInfo.VecMin.y |= int(cube.Pos.y) << 6; aabbInfo.VecMin.y |= int(cube.Left.Y) << 8;
aabbInfo.VecMin.z |= int(cube.Pos.z) << 6; aabbInfo.VecMin.z |= int(cube.Left.Z) << 8;
aabbInfo.VecMax.x |= int(cube.Pos.x+cube.Size.x+1) << 6; aabbInfo.VecMax.x |= int(cube.Right.X) << 8;
aabbInfo.VecMax.y |= int(cube.Pos.y+cube.Size.y+1) << 6; aabbInfo.VecMax.y |= int(cube.Right.Y) << 8;
aabbInfo.VecMax.z |= int(cube.Pos.z+cube.Size.z+1) << 6; aabbInfo.VecMax.z |= int(cube.Right.Z) << 8;
if(aabb.isCollideWith(aabbInfo)) { if(aabb.isCollideWith(aabbInfo)) {
aabbInfo = { aabbInfo = {
.Type = CollisionAABB::EnumType::Voxel, .Type = CollisionAABB::EnumType::Voxel,
.Voxel = { .Voxel = {
.Chunk = Pos::bvec4u(beg.x, beg.y, beg.z), .Chunk = Pos::Local16_u(beg.x, beg.y, beg.z),
.Index = static_cast<uint32_t>(iter), .Index = static_cast<uint32_t>(iter),
.Id = cube.VoxelId
} }
}; };
@@ -109,7 +118,7 @@ public:
} }
RegionEntityId_t pushEntity(Entity &entity) { LocalEntityId_t pushEntity(Entity &entity) {
for(size_t iter = 0; iter < Entityes.size(); iter++) { for(size_t iter = 0; iter < Entityes.size(); iter++) {
Entity &obj = Entityes[iter]; Entity &obj = Entityes[iter];
@@ -126,54 +135,42 @@ public:
return Entityes.size()-1; return Entityes.size()-1;
} }
// В регионе не осталось места return LocalEntityId_t(-1);
return RegionEntityId_t(-1); }
void load(SB_Region *data) {
convertRegionVoxelsToChunks(data->Voxels, (std::vector<VoxelCube>*) Voxels);
}
void save(SB_Region *data) {
data->Voxels.clear();
convertChunkVoxelsToRegion((const std::vector<VoxelCube>*) Voxels, data->Voxels);
} }
}; };
class World { class World {
DefWorldId DefId; DefWorldId_t DefId;
public: public:
std::vector<Pos::GlobalRegion> NeedToLoad;
std::unordered_map<Pos::GlobalRegion, std::unique_ptr<Region>> Regions; std::unordered_map<Pos::GlobalRegion, std::unique_ptr<Region>> Regions;
public: public:
World(DefWorldId defId); World(DefWorldId_t defId);
~World(); ~World();
/* /*
Подписывает игрока на отслеживаемые им регионы Обновить регионы
Возвращает список не загруженных регионов, на которые соответственно игрока не получилось подписать
При подписи происходит отправка всех чанков и сущностей региона
*/
std::vector<Pos::GlobalRegion> onRemoteClient_RegionsEnter(WorldId_t worldId, std::shared_ptr<RemoteClient> cec, const std::vector<Pos::GlobalRegion> &enter);
void onRemoteClient_RegionsLost(WorldId_t worldId, std::shared_ptr<RemoteClient> cec, const std::vector<Pos::GlobalRegion>& lost);
struct SaveUnloadInfo {
std::vector<Pos::GlobalRegion> ToUnload;
std::vector<std::pair<Pos::GlobalRegion, SB_Region_In>> ToSave;
};
SaveUnloadInfo onStepDatabaseSync();
struct RegionIn {
std::unordered_map<Pos::bvec4u, std::vector<VoxelCube>> Voxels;
std::array<std::array<Node, 16*16*16>, 4*4*4> Nodes;
std::vector<Entity> Entityes;
};
void pushRegions(std::vector<std::pair<Pos::GlobalRegion, RegionIn>>);
/*
Проверка использования регионов,
*/ */
void onUpdate(GameServer *server, float dtime); void onUpdate(GameServer *server, float dtime);
/* // Игрок начал отслеживать регионы
void onCEC_RegionsEnter(ContentEventController *cec, const std::vector<Pos::GlobalRegion> &enter);
void onCEC_RegionsLost(ContentEventController *cec, const std::vector<Pos::GlobalRegion> &lost);
*/ DefWorldId_t getDefId() const { return DefId; }
DefWorldId getDefId() const { return DefId; }
}; };
} }

View File

@@ -1,61 +1,59 @@
#pragma once #pragma once
#include "TOSLib.hpp" #include "TOSLib.hpp"
#include <functional> #include "boost/asio/associated_cancellation_slot.hpp"
#include "boost/asio/associated_executor.hpp"
#include "boost/asio/deadline_timer.hpp"
#include "boost/asio/io_context.hpp"
#include "boost/system/detail/error_code.hpp" #include "boost/system/detail/error_code.hpp"
#include "boost/system/system_error.hpp"
#include <boost/asio.hpp> #include <boost/asio.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/experimental/awaitable_operators.hpp> #include <boost/asio/experimental/awaitable_operators.hpp>
#include <exception> #include <exception>
#include <memory> #include <memory>
#include <type_traits> #include <type_traits>
#include <list>
namespace TOS { namespace TOS {
using namespace boost::asio::experimental::awaitable_operators; using namespace boost::asio::experimental::awaitable_operators;
namespace asio = boost::asio;
template<typename T = void> template<typename T = void>
using coro = boost::asio::awaitable<T>; using coro = boost::asio::awaitable<T>;
namespace asio = boost::asio;
class AsyncSemaphore // class AsyncSemaphore
{ // {
boost::asio::deadline_timer Deadline; // boost::asio::deadline_timer Deadline;
std::atomic<uint8_t> Lock = 0; // std::atomic<uint8_t> Lock = 0;
public: // public:
AsyncSemaphore( // AsyncSemaphore(boost::asio::io_context& ioc)
boost::asio::io_context& ioc) // : Deadline(ioc, boost::posix_time::ptime(boost::posix_time::pos_infin))
: Deadline(ioc, boost::posix_time::ptime(boost::posix_time::pos_infin)) // {}
{}
boost::asio::awaitable<void> async_wait() { // coro<> async_wait() {
try { // try {
co_await Deadline.async_wait(boost::asio::use_awaitable); // co_await Deadline.async_wait(boost::asio::use_awaitable);
} catch(boost::system::system_error code) { // } catch(boost::system::system_error code) {
if(code.code() != boost::system::errc::operation_canceled) // if(code.code() != boost::system::errc::operation_canceled)
throw; // throw;
} // }
co_await boost::asio::this_coro::throw_if_cancelled(); // co_await asio::this_coro::throw_if_cancelled();
} // }
boost::asio::awaitable<void> async_wait(std::function<bool()> predicate) { // coro<> async_wait(std::function<bool()> predicate) {
while(!predicate()) // while(!predicate())
co_await async_wait(); // co_await async_wait();
} // }
void notify_one() { // void notify_one() {
Deadline.cancel_one(); // Deadline.cancel_one();
} // }
void notify_all() {
Deadline.cancel();
}
};
// void notify_all() {
// Deadline.cancel();
// }
// };
/* /*
Многие могут уведомлять одного Многие могут уведомлять одного
@@ -74,13 +72,14 @@ public:
} }
void wait() { void wait() {
try { Timer.wait(); } catch(...) {} Timer.wait();
Timer.expires_at(boost::posix_time::ptime(boost::posix_time::pos_infin)); Timer.expires_at(boost::posix_time::ptime(boost::posix_time::pos_infin));
} }
coro<> async_wait() { coro<> async_wait() {
try { co_await Timer.async_wait(); } catch(...) {} try { co_await Timer.async_wait(); } catch(...) {}
} }
}; };
class WaitableCoro { class WaitableCoro {
@@ -93,10 +92,11 @@ public:
: IOC(ioc) : IOC(ioc)
{} {}
void co_spawn(coro<> token) { template<typename Token>
void co_spawn(Token token) {
Symaphore = std::make_shared<MultipleToOne_AsyncSymaphore>(IOC); Symaphore = std::make_shared<MultipleToOne_AsyncSymaphore>(IOC);
asio::co_spawn(IOC, [token = std::move(token), symaphore = Symaphore]() -> coro<> { asio::co_spawn(IOC, [token = std::move(token), symaphore = Symaphore]() -> coro<> {
try { co_await std::move(const_cast<coro<>&>(token)); } catch(...) {} co_await std::move(token);
symaphore->notify(); symaphore->notify();
}, asio::detached); }, asio::detached);
} }
@@ -110,115 +110,18 @@ public:
} }
}; };
class AsyncUseControl {
public:
class Lock {
AsyncUseControl *AUC;
public:
Lock(AsyncUseControl *auc)
: AUC(auc)
{}
Lock()
: AUC(nullptr)
{}
~Lock() {
if(AUC)
unlock();
}
Lock(const Lock&) = delete;
Lock(Lock&& obj)
: AUC(obj.AUC)
{
obj.AUC = nullptr;
}
Lock& operator=(const Lock&) = delete;
Lock& operator=(Lock&& obj) {
if(&obj == this)
return *this;
if(AUC)
unlock();
AUC = obj.AUC;
obj.AUC = nullptr;
return *this;
}
void unlock() {
assert(AUC);
if(--AUC->Uses == 0 && AUC->OnNoUse) {
asio::post(AUC->IOC, std::move(AUC->OnNoUse));
}
AUC = nullptr;
}
};
private:
asio::io_context &IOC;
std::move_only_function<void()> OnNoUse;
std::atomic_int Uses = 0;
public:
AsyncUseControl(asio::io_context &ioc)
: IOC(ioc)
{
}
template<BOOST_ASIO_COMPLETION_TOKEN_FOR(void()) Token = asio::default_completion_token_t<asio::io_context>>
auto wait(Token&& token = asio::default_completion_token_t<asio::io_context>()) {
auto initiation = [this](auto&& token) {
int value;
do {
value = Uses.exchange(-1);
} while(value == -1);
OnNoUse = std::move(token);
if(value == 0)
OnNoUse();
Uses.exchange(value);
};
return asio::async_initiate<Token, void()>(initiation, token);
}
Lock use() {
int value;
do {
value = Uses.exchange(-1);
} while(value == -1);
if(OnNoUse)
throw boost::system::system_error(asio::error::operation_aborted, "OnNoUse");
Uses.exchange(++value);
return Lock(this);
}
};
/* /*
Используется, чтобы вместо уничтожения объекта в умной ссылке, вызвать корутину с co_await asyncDestructor() Используется, чтобы вместо уничтожения объекта в умной ссылке, вызвать корутину с co_await asyncDestructor()
*/ */
class IAsyncDestructible : public std::enable_shared_from_this<IAsyncDestructible> { class IAsyncDestructible : public std::enable_shared_from_this<IAsyncDestructible> {
protected: protected:
asio::io_context &IOC; asio::io_context &IOC;
AsyncUseControl AUC;
virtual coro<> asyncDestructor() { co_await AUC.wait(); } virtual coro<> asyncDestructor() { co_return; }
public: public:
IAsyncDestructible(asio::io_context &ioc) IAsyncDestructible(asio::io_context &ioc)
: IOC(ioc), AUC(ioc) : IOC(ioc)
{} {}
virtual ~IAsyncDestructible() {} virtual ~IAsyncDestructible() {}
@@ -227,13 +130,12 @@ protected:
template<typename T, typename = typename std::is_same<IAsyncDestructible, T>> template<typename T, typename = typename std::is_same<IAsyncDestructible, T>>
static std::shared_ptr<T> createShared(asio::io_context &ioc, T *ptr) static std::shared_ptr<T> createShared(asio::io_context &ioc, T *ptr)
{ {
return std::shared_ptr<T>(ptr, [&ioc](T *ptr) { return std::shared_ptr<T>(ptr, [&ioc = ioc](T *ptr) {
boost::asio::co_spawn(ioc, boost::asio::co_spawn(ioc, [](IAsyncDestructible *ptr) -> coro<> {
[ptr, &ioc]() mutable -> coro<> { try { co_await ptr->asyncDestructor(); } catch(...) { }
try { co_await dynamic_cast<IAsyncDestructible*>(ptr)->asyncDestructor(); } catch(...) { } delete ptr;
asio::post(ioc, [ptr](){ delete ptr; }); co_return;
}, } (ptr), boost::asio::detached);
boost::asio::detached);
}); });
} }
@@ -241,25 +143,23 @@ protected:
static coro<std::shared_ptr<T>> createShared(T *ptr) static coro<std::shared_ptr<T>> createShared(T *ptr)
{ {
co_return std::shared_ptr<T>(ptr, [ioc = asio::get_associated_executor(co_await asio::this_coro::executor)](T *ptr) { co_return std::shared_ptr<T>(ptr, [ioc = asio::get_associated_executor(co_await asio::this_coro::executor)](T *ptr) {
boost::asio::co_spawn(ioc, boost::asio::co_spawn(ioc, [](IAsyncDestructible *ptr) -> coro<> {
[ptr, &ioc]() mutable -> coro<> { try { co_await ptr->asyncDestructor(); } catch(...) { }
try { co_await dynamic_cast<IAsyncDestructible*>(ptr)->asyncDestructor(); } catch(...) { } delete ptr;
asio::post(ioc, [ptr](){ delete ptr; }); co_return;
}, } (ptr), boost::asio::detached);
boost::asio::detached);
}); });
} }
template<typename T, typename = typename std::is_same<IAsyncDestructible, T>> template<typename T, typename = typename std::is_same<IAsyncDestructible, T>>
static std::unique_ptr<T, std::function<void(T*)>> createUnique(asio::io_context &ioc, T *ptr) static std::unique_ptr<T, std::function<void(T*)>> createUnique(asio::io_context &ioc, T *ptr)
{ {
return std::unique_ptr<T, std::function<void(T*)>>(ptr, [&ioc](T *ptr) { return std::unique_ptr<T, std::function<void(T*)>>(ptr, [&ioc = ioc](T *ptr) {
boost::asio::co_spawn(ioc, boost::asio::co_spawn(ioc, [](IAsyncDestructible *ptr) -> coro<> {
[ptr, &ioc]() mutable -> coro<> { try { co_await ptr->asyncDestructor(); } catch(...) { }
try { co_await dynamic_cast<IAsyncDestructible*>(ptr)->asyncDestructor(); } catch(...) { } delete ptr;
asio::post(ioc, [ptr](){ delete ptr; }); co_return;
}, } (ptr), boost::asio::detached);
boost::asio::detached);
}); });
} }
@@ -267,99 +167,13 @@ protected:
static coro<std::unique_ptr<T, std::function<void(T*)>>> createUnique(T *ptr) static coro<std::unique_ptr<T, std::function<void(T*)>>> createUnique(T *ptr)
{ {
co_return std::unique_ptr<T, std::function<void(T*)>>(ptr, [ioc = asio::get_associated_executor(co_await asio::this_coro::executor)](T *ptr) { co_return std::unique_ptr<T, std::function<void(T*)>>(ptr, [ioc = asio::get_associated_executor(co_await asio::this_coro::executor)](T *ptr) {
boost::asio::co_spawn(ioc, boost::asio::co_spawn(ioc, [](IAsyncDestructible *ptr) -> coro<> {
[ptr, &ioc]() mutable -> coro<> { try { co_await ptr->asyncDestructor(); } catch(...) { }
try { co_await dynamic_cast<IAsyncDestructible*>(ptr)->asyncDestructor(); } catch(...) { } delete ptr;
asio::post(ioc, [ptr](){ delete ptr; }); co_return;
}, } (ptr), boost::asio::detached);
boost::asio::detached);
}); });
} }
}; };
template<typename T>
class AsyncMutexObject {
public:
class Lock {
public:
Lock(AsyncMutexObject* obj)
: Obj(obj)
{}
Lock(const Lock& other) = delete;
Lock(Lock&& other)
: Obj(other.Obj)
{
other.Obj = nullptr;
}
~Lock() {
if(Obj)
unlock();
}
Lock& operator=(const Lock& other) = delete;
Lock& operator=(Lock& other) {
if(&other == this)
return *this;
if(Obj)
unlock();
Obj = other.Obj;
other.Obj = nullptr;
}
T& get() const { assert(Obj); return Obj->value; }
T* operator->() const { assert(Obj); return &Obj->value; }
T& operator*() const { assert(Obj); return Obj->value; }
void unlock() {
assert(Obj);
typename SpinlockObject<Context>::Lock ctx = Obj->Ctx.lock();
if(ctx->Chain.empty()) {
ctx->InExecution = false;
} else {
auto token = std::move(ctx->Chain.front());
ctx->Chain.pop_front();
ctx.unlock();
token(Lock(Obj));
}
Obj = nullptr;
}
private:
AsyncMutexObject *Obj;
};
private:
struct Context {
std::list<std::move_only_function<void(Lock)>> Chain;
bool InExecution = false;
};
SpinlockObject<Context> Ctx;
T value;
public:
template<BOOST_ASIO_COMPLETION_TOKEN_FOR(void(Lock)) Token = asio::default_completion_token_t<asio::io_context::executor_type>>
auto lock(Token&& token = Token()) {
auto initiation = [this](auto&& token) mutable {
typename SpinlockObject<Context>::Lock ctx = Ctx.lock();
if(ctx->InExecution) {
ctx->Chain.emplace_back(std::move(token));
} else {
ctx->InExecution = true;
ctx.unlock();
token(Lock(this));
}
};
return boost::asio::async_initiate<Token, void(Lock)>(std::move(initiation), token);
}
};
} }

View File

@@ -4,8 +4,6 @@
#include <chrono> #include <chrono>
#include <cstring> #include <cstring>
#include <filesystem> #include <filesystem>
#include <mutex>
#include <shared_mutex>
#include <string> #include <string>
#include <sstream> #include <sstream>
#include <thread> #include <thread>
@@ -13,233 +11,10 @@
#define _USE_MATH_DEFINES #define _USE_MATH_DEFINES
#include <cmath> #include <cmath>
#include <vector> #include <vector>
#include <assert.h>
namespace TOS { namespace TOS {
template<typename T>
class MutexObject {
public:
template<typename... Args>
explicit MutexObject(Args&&... args)
: value(std::forward<Args>(args)...) {}
class SharedLock {
public:
SharedLock(MutexObject* obj, std::shared_lock<std::shared_mutex> lock)
: obj(obj), lock(std::move(lock)) {}
const T& get() const { return obj->value; }
const T& operator*() const { return obj->value; }
const T* operator->() const { return &obj->value; }
void unlock() { lock.unlock(); }
operator bool() const {
return lock.owns_lock();
}
private:
MutexObject* obj;
std::shared_lock<std::shared_mutex> lock;
};
class ExclusiveLock {
public:
ExclusiveLock(MutexObject* obj, std::unique_lock<std::shared_mutex> lock)
: obj(obj), lock(std::move(lock)) {}
T& get() const { return obj->value; }
T& operator*() const { return obj->value; }
T* operator->() const { return &obj->value; }
void unlock() { lock.unlock(); }
operator bool() const {
return lock.owns_lock();
}
private:
MutexObject* obj;
std::unique_lock<std::shared_mutex> lock;
};
SharedLock shared_lock() {
return SharedLock(this, std::shared_lock(mutex));
}
SharedLock shared_lock(const std::try_to_lock_t& tag) {
return SharedLock(this, std::shared_lock(mutex, tag));
}
SharedLock shared_lock(const std::adopt_lock_t& tag) {
return SharedLock(this, std::shared_lock(mutex, tag));
}
SharedLock shared_lock(const std::defer_lock_t& tag) {
return SharedLock(this, std::shared_lock(mutex, tag));
}
ExclusiveLock exclusive_lock() {
return ExclusiveLock(this, std::unique_lock(mutex));
}
ExclusiveLock exclusive_lock(const std::try_to_lock_t& tag) {
return ExclusiveLock(this, std::unique_lock(mutex, tag));
}
ExclusiveLock exclusive_lock(const std::adopt_lock_t& tag) {
return ExclusiveLock(this, std::unique_lock(mutex, tag));
}
ExclusiveLock exclusive_lock(const std::defer_lock_t& tag) {
return ExclusiveLock(this, std::unique_lock(mutex, tag));
}
private:
T value;
mutable std::shared_mutex mutex;
};
template<typename T>
class SpinlockObject {
public:
template<typename... Args>
explicit SpinlockObject(Args&&... args)
: Value(std::forward<Args>(args)...) {}
class Lock {
public:
Lock(SpinlockObject* obj, std::atomic_flag& flag, bool locked = false)
: Obj(obj), Flag(&flag)
{
if(obj && !locked)
while(flag.test_and_set(std::memory_order_acquire));
}
~Lock() {
if(Obj)
Flag->clear(std::memory_order_release);
}
Lock(const Lock&) = delete;
Lock(Lock&& obj)
: Obj(obj.Obj), Flag(obj.Flag)
{
obj.Obj = nullptr;
}
Lock& operator=(const Lock&) = delete;
Lock& operator=(Lock&& obj) {
if(this == &obj)
return *this;
if(Obj)
unlock();
Obj = obj.Obj;
obj.Obj = nullptr;
Flag = obj.Flag;
return *this;
}
T& get() const { assert(Obj); return Obj->Value; }
T* operator->() const { assert(Obj); return &Obj->Value; }
T& operator*() const { assert(Obj); return Obj->Value; }
operator bool() const {
return Obj;
}
void unlock() { assert(Obj); Obj = nullptr; Flag->clear(std::memory_order_release);}
private:
SpinlockObject *Obj;
std::atomic_flag *Flag;
};
Lock lock() {
return Lock(this, Flag);
}
Lock tryLock() {
if(Flag.test_and_set(std::memory_order_acquire))
return Lock(nullptr, Flag);
else
return Lock(this, Flag, true);
}
const T& get_read() { return Value; }
private:
T Value;
std::atomic_flag Flag = ATOMIC_FLAG_INIT;
};
class Spinlock {
public:
Spinlock() {}
class Lock {
public:
Lock(Spinlock* obj, std::atomic_flag& flag, bool locked = false)
: Obj(obj), Flag(&flag)
{
if(obj && !locked)
while(flag.test_and_set(std::memory_order_acquire));
}
~Lock() {
if(Obj)
Flag->clear(std::memory_order_release);
}
Lock(const Lock&) = delete;
Lock(Lock&& obj)
: Obj(obj.Obj), Flag(obj.Flag)
{
obj.Obj = nullptr;
}
Lock& operator=(const Lock&) = delete;
Lock& operator=(Lock&& obj) {
if(this == &obj)
return *this;
if(Obj)
unlock();
Obj = obj.Obj;
obj.Obj = nullptr;
Flag = obj.Flag;
return *this;
}
void unlock() { assert(Obj); Obj = nullptr; Flag->clear(std::memory_order_release);}
private:
Spinlock *Obj;
std::atomic_flag *Flag;
};
Lock lock() {
return Lock(this, Flag);
}
Lock tryLock() {
if(Flag.test_and_set(std::memory_order_acquire))
return Lock(nullptr, Flag);
else
return Lock(this, Flag, true);
}
private:
std::atomic_flag Flag = ATOMIC_FLAG_INIT;
};
#if __BYTE_ORDER == __LITTLE_ENDIAN #if __BYTE_ORDER == __LITTLE_ENDIAN
template <typename T> template <typename T>
static inline T swapEndian(const T &u) { return u; } static inline T swapEndian(const T &u) { return u; }

View File

@@ -11,17 +11,17 @@ namespace fs = std::filesystem;
namespace LV { namespace LV {
iResource::iResource() = default; Resource::Resource() = default;
iResource::~iResource() = default; Resource::~Resource() = default;
static std::mutex iResourceCacheMtx; static std::mutex ResourceCacheMtx;
static std::unordered_map<std::string, std::weak_ptr<iResource>> iResourceCache; static std::unordered_map<std::string, std::weak_ptr<Resource>> ResourceCache;
class FS_iResource : public iResource { class FS_Resource : public Resource {
boost::scoped_array<uint8_t> Array; boost::scoped_array<uint8_t> Array;
public: public:
FS_iResource(const std::filesystem::path &path) FS_Resource(const std::filesystem::path &path)
{ {
std::ifstream fd(path); std::ifstream fd(path);
@@ -36,18 +36,18 @@ public:
Data = Array.get(); Data = Array.get();
} }
virtual ~FS_iResource() = default; virtual ~FS_Resource() = default;
}; };
std::shared_ptr<iResource> getResource(const std::string &path) { std::shared_ptr<Resource> getResource(const std::string &path) {
std::unique_lock<std::mutex> lock(iResourceCacheMtx); std::unique_lock<std::mutex> lock(ResourceCacheMtx);
if(auto iter = iResourceCache.find(path); iter != iResourceCache.end()) { if(auto iter = ResourceCache.find(path); iter != ResourceCache.end()) {
std::shared_ptr<iResource> iResource = iter->second.lock(); std::shared_ptr<Resource> resource = iter->second.lock();
if(!iResource) { if(!resource) {
iResourceCache.erase(iter); ResourceCache.erase(iter);
} else { } else {
return iResource; return resource;
} }
} }
@@ -55,15 +55,15 @@ std::shared_ptr<iResource> getResource(const std::string &path) {
fs_path /= path; fs_path /= path;
if(fs::exists(fs_path)) { if(fs::exists(fs_path)) {
std::shared_ptr<iResource> iResource = std::make_shared<FS_iResource>(fs_path); std::shared_ptr<Resource> resource = std::make_shared<FS_Resource>(fs_path);
iResourceCache.emplace(path, iResource); ResourceCache.emplace(path, resource);
TOS::Logger("iResources").debug() << "Ресурс " << fs_path << " найден в фс"; TOS::Logger("Resources").debug() << "Ресурс " << fs_path << " найден в фс";
return iResource; return resource;
} }
if(auto iter = _binary_assets_symbols.find(path); iter != _binary_assets_symbols.end()) { if(auto iter = _binary_assets_symbols.find(path); iter != _binary_assets_symbols.end()) {
TOS::Logger("iResources").debug() << "Ресурс " << fs_path << " is inlined"; TOS::Logger("Resources").debug() << "Ресурс " << fs_path << " is inlined";
return std::make_shared<iResource>((const uint8_t*) std::get<0>(iter->second), std::get<1>(iter->second)-std::get<0>(iter->second)); return std::make_shared<Resource>((const uint8_t*) std::get<0>(iter->second), std::get<1>(iter->second)-std::get<0>(iter->second));
} }
MAKE_ERROR("Ресурс " << path << " не найден"); MAKE_ERROR("Ресурс " << path << " не найден");

View File

@@ -22,24 +22,24 @@ struct iBinaryStream : detail::membuf {
}; };
class iResource { class Resource {
protected: protected:
const uint8_t* Data; const uint8_t* Data;
size_t Size; size_t Size;
public: public:
iResource(); Resource();
iResource(const uint8_t* data, size_t size) Resource(const uint8_t* data, size_t size)
: Data(data), Size(size) : Data(data), Size(size)
{} {}
virtual ~iResource(); virtual ~Resource();
iResource(const iResource&) = delete; Resource(const Resource&) = delete;
iResource(iResource&&) = delete; Resource(Resource&&) = delete;
iResource& operator=(const iResource&) = delete; Resource& operator=(const Resource&) = delete;
iResource& operator=(iResource&&) = delete; Resource& operator=(Resource&&) = delete;
const uint8_t* getData() const { return Data; } const uint8_t* getData() const { return Data; }
size_t getSize() const { return Size; } size_t getSize() const { return Size; }
@@ -49,6 +49,6 @@ public:
}; };
std::shared_ptr<iResource> getResource(const std::string &path); std::shared_ptr<Resource> getResource(const std::string &path);
} }

View File

@@ -2,28 +2,22 @@
import sys import sys
import re import re
if len(sys.argv) < 3: output_file = "resources.cpp"
print("Usage: assets.py <output_cpp> <file1> [file2 ...]") with open(output_file, "w") as f:
sys.exit(1)
output_cpp = sys.argv[1]
symbols = sys.argv[2:]
with open(output_cpp, "w") as f:
f.write("#include <unordered_map>\n#include <string>\n#include <tuple>\n\nextern \"C\" {\n") f.write("#include <unordered_map>\n#include <string>\n#include <tuple>\n\nextern \"C\" {\n")
for symbol in symbols: for symbol in sys.argv[1:]:
var_name = "_binary_" + re.sub('[^a-zA-Z0-9]', '_', symbol) var_name = "_binary_" + re.sub('[^a-zA-Z0-9]', '_', symbol)
f.write(f"\textern const char {var_name}_start[];\n\textern const char {var_name}_end[];\n") f.write(f"\textern const char {var_name}_start[];\n\textern const char {var_name}_end[];\n")
f.write("}\n\n") f.write("}")
f.write("std::unordered_map<std::string, std::tuple<const char*, const char*>> _binary_assets_symbols = {\n") f.write("\n\nstd::unordered_map<std::string, std::tuple<const char*, const char*>> _binary_assets_symbols = {\n")
for symbol in symbols: for symbol in sys.argv[1:]:
var_name = "_binary_" + re.sub('[^a-zA-Z0-9]', '_', symbol) var_name = "_binary_" + re.sub('[^a-zA-Z0-9]', '_', symbol)
f.write(f"\t{{\"{symbol}\", {{(const char*) &{var_name}_start, (const char*) &{var_name}_end}}}},\n") f.write(f"\t{{\"{symbol}\", {{(const char*) &{var_name}_start, (const char*) &{var_name}_end}}}},\n")
f.write("};\n") f.write("};\n")
print(f"File {output_cpp} is generated.") print(f"File {output_file} is generated.")

View File

@@ -1,31 +1,19 @@
#include "Common/Abstract.hpp"
#include "boost/asio/awaitable.hpp"
#include <chrono>
#include <filesystem>
#include <iostream> #include <iostream>
#include <boost/asio.hpp> #include <boost/asio.hpp>
#include <Client/Vulkan/Vulkan.hpp> #include <Client/Vulkan/Vulkan.hpp>
#include <thread> #include <Common/Async.hpp>
#include <Common/async_mutex.hpp>
namespace LV { namespace LV {
/*
База ресурсов на стороне клиента
Протокол получения ресурсов, удаления, потом -> регулировки размера
*/
using namespace TOS; using namespace TOS;
int main() { int main() {
// LuaVox // LuaVox
asio::io_context ioc; asio::io_context ioc;
Logger LOG = "main";
LV::Client::VK::Vulkan vkInst(ioc);
ioc.run(); ioc.run();
return 0; return 0;

View File

@@ -1,499 +0,0 @@
// Copyright (c) 2018 Martyn Afford
// Licensed under the MIT licence
#ifndef SHA2_HPP
#define SHA2_HPP
#include <array>
#include <cstdint>
#include <cstring>
namespace sha2 {
template <size_t N>
using hash_array = std::array<uint8_t, N>;
using sha224_hash = hash_array<28>;
using sha256_hash = hash_array<32>;
using sha384_hash = hash_array<48>;
using sha512_hash = hash_array<64>;
// SHA-2 uses big-endian integers.
inline void
write_u32(uint8_t* dest, uint32_t x)
{
*dest++ = (x >> 24) & 0xff;
*dest++ = (x >> 16) & 0xff;
*dest++ = (x >> 8) & 0xff;
*dest++ = (x >> 0) & 0xff;
}
inline void
write_u64(uint8_t* dest, uint64_t x)
{
*dest++ = (x >> 56) & 0xff;
*dest++ = (x >> 48) & 0xff;
*dest++ = (x >> 40) & 0xff;
*dest++ = (x >> 32) & 0xff;
*dest++ = (x >> 24) & 0xff;
*dest++ = (x >> 16) & 0xff;
*dest++ = (x >> 8) & 0xff;
*dest++ = (x >> 0) & 0xff;
}
inline uint32_t
read_u32(const uint8_t* src)
{
return static_cast<uint32_t>((src[0] << 24) | (src[1] << 16) |
(src[2] << 8) | src[3]);
}
inline uint64_t
read_u64(const uint8_t* src)
{
uint64_t upper = read_u32(src);
uint64_t lower = read_u32(src + 4);
return ((upper & 0xffffffff) << 32) | (lower & 0xffffffff);
}
// A compiler-recognised implementation of rotate right that avoids the
// undefined behaviour caused by shifting by the number of bits of the left-hand
// type. See John Regehr's article https://blog.regehr.org/archives/1063
inline uint32_t
ror(uint32_t x, uint32_t n)
{
return (x >> n) | (x << (-n & 31));
}
inline uint64_t
ror(uint64_t x, uint64_t n)
{
return (x >> n) | (x << (-n & 63));
}
// Utility function to truncate larger hashes. Assumes appropriate hash types
// (i.e., hash_array<N>) for type T.
template <typename T, size_t N>
inline T
truncate(const hash_array<N>& hash)
{
T result;
memcpy(result.data(), hash.data(), sizeof(result));
return result;
}
// Both sha256_impl and sha512_impl are used by sha224/sha256 and
// sha384/sha512 respectively, avoiding duplication as only the initial hash
// values (s) and output hash length change.
inline sha256_hash
sha256_impl(const uint32_t* s, const uint8_t* data, uint64_t length)
{
static_assert(sizeof(uint32_t) == 4, "sizeof(uint32_t) must be 4");
static_assert(sizeof(uint64_t) == 8, "sizeof(uint64_t) must be 8");
constexpr size_t chunk_bytes = 64;
const uint64_t bit_length = length * 8;
uint32_t hash[8] = {s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7]};
constexpr uint32_t k[64] = {
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1,
0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,
0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147,
0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a,
0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2};
auto chunk = [&hash, &k](const uint8_t* chunk_data) {
uint32_t w[64] = {0};
for (int i = 0; i != 16; ++i) {
w[i] = read_u32(&chunk_data[i * 4]);
}
for (int i = 16; i != 64; ++i) {
auto w15 = w[i - 15];
auto w2 = w[i - 2];
auto s0 = ror(w15, 7) ^ ror(w15, 18) ^ (w15 >> 3);
auto s1 = ror(w2, 17) ^ ror(w2, 19) ^ (w2 >> 10);
w[i] = w[i - 16] + s0 + w[i - 7] + s1;
}
auto a = hash[0];
auto b = hash[1];
auto c = hash[2];
auto d = hash[3];
auto e = hash[4];
auto f = hash[5];
auto g = hash[6];
auto h = hash[7];
for (int i = 0; i != 64; ++i) {
auto s1 = ror(e, 6) ^ ror(e, 11) ^ ror(e, 25);
auto ch = (e & f) ^ (~e & g);
auto temp1 = h + s1 + ch + k[i] + w[i];
auto s0 = ror(a, 2) ^ ror(a, 13) ^ ror(a, 22);
auto maj = (a & b) ^ (a & c) ^ (b & c);
auto temp2 = s0 + maj;
h = g;
g = f;
f = e;
e = d + temp1;
d = c;
c = b;
b = a;
a = temp1 + temp2;
}
hash[0] += a;
hash[1] += b;
hash[2] += c;
hash[3] += d;
hash[4] += e;
hash[5] += f;
hash[6] += g;
hash[7] += h;
};
while (length >= chunk_bytes) {
chunk(data);
data += chunk_bytes;
length -= chunk_bytes;
}
{
std::array<uint8_t, chunk_bytes> buf;
memcpy(buf.data(), data, length);
auto i = length;
buf[i++] = 0x80;
if (i > chunk_bytes - 8) {
while (i < chunk_bytes) {
buf[i++] = 0;
}
chunk(buf.data());
i = 0;
}
while (i < chunk_bytes - 8) {
buf[i++] = 0;
}
write_u64(&buf[i], bit_length);
chunk(buf.data());
}
sha256_hash result;
for (uint8_t i = 0; i != 8; ++i) {
write_u32(&result[i * 4], hash[i]);
}
return result;
}
inline sha512_hash
sha512_impl(const uint64_t* s, const uint8_t* data, uint64_t length)
{
static_assert(sizeof(uint32_t) == 4, "sizeof(uint32_t) must be 4");
static_assert(sizeof(uint64_t) == 8, "sizeof(uint64_t) must be 8");
constexpr size_t chunk_bytes = 128;
const uint64_t bit_length_low = length << 3;
const uint64_t bit_length_high = length >> (64 - 3);
uint64_t hash[8] = {s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7]};
constexpr uint64_t k[80] = {
0x428a2f98d728ae22, 0x7137449123ef65cd, 0xb5c0fbcfec4d3b2f,
0xe9b5dba58189dbbc, 0x3956c25bf348b538, 0x59f111f1b605d019,
0x923f82a4af194f9b, 0xab1c5ed5da6d8118, 0xd807aa98a3030242,
0x12835b0145706fbe, 0x243185be4ee4b28c, 0x550c7dc3d5ffb4e2,
0x72be5d74f27b896f, 0x80deb1fe3b1696b1, 0x9bdc06a725c71235,
0xc19bf174cf692694, 0xe49b69c19ef14ad2, 0xefbe4786384f25e3,
0x0fc19dc68b8cd5b5, 0x240ca1cc77ac9c65, 0x2de92c6f592b0275,
0x4a7484aa6ea6e483, 0x5cb0a9dcbd41fbd4, 0x76f988da831153b5,
0x983e5152ee66dfab, 0xa831c66d2db43210, 0xb00327c898fb213f,
0xbf597fc7beef0ee4, 0xc6e00bf33da88fc2, 0xd5a79147930aa725,
0x06ca6351e003826f, 0x142929670a0e6e70, 0x27b70a8546d22ffc,
0x2e1b21385c26c926, 0x4d2c6dfc5ac42aed, 0x53380d139d95b3df,
0x650a73548baf63de, 0x766a0abb3c77b2a8, 0x81c2c92e47edaee6,
0x92722c851482353b, 0xa2bfe8a14cf10364, 0xa81a664bbc423001,
0xc24b8b70d0f89791, 0xc76c51a30654be30, 0xd192e819d6ef5218,
0xd69906245565a910, 0xf40e35855771202a, 0x106aa07032bbd1b8,
0x19a4c116b8d2d0c8, 0x1e376c085141ab53, 0x2748774cdf8eeb99,
0x34b0bcb5e19b48a8, 0x391c0cb3c5c95a63, 0x4ed8aa4ae3418acb,
0x5b9cca4f7763e373, 0x682e6ff3d6b2b8a3, 0x748f82ee5defb2fc,
0x78a5636f43172f60, 0x84c87814a1f0ab72, 0x8cc702081a6439ec,
0x90befffa23631e28, 0xa4506cebde82bde9, 0xbef9a3f7b2c67915,
0xc67178f2e372532b, 0xca273eceea26619c, 0xd186b8c721c0c207,
0xeada7dd6cde0eb1e, 0xf57d4f7fee6ed178, 0x06f067aa72176fba,
0x0a637dc5a2c898a6, 0x113f9804bef90dae, 0x1b710b35131c471b,
0x28db77f523047d84, 0x32caab7b40c72493, 0x3c9ebe0a15c9bebc,
0x431d67c49c100d4c, 0x4cc5d4becb3e42b6, 0x597f299cfc657e2a,
0x5fcb6fab3ad6faec, 0x6c44198c4a475817};
auto chunk = [&hash, &k](const uint8_t* chunk_data) {
uint64_t w[80] = {0};
for (int i = 0; i != 16; ++i) {
w[i] = read_u64(&chunk_data[i * 8]);
}
for (int i = 16; i != 80; ++i) {
auto w15 = w[i - 15];
auto w2 = w[i - 2];
auto s0 = ror(w15, 1) ^ ror(w15, 8) ^ (w15 >> 7);
auto s1 = ror(w2, 19) ^ ror(w2, 61) ^ (w2 >> 6);
w[i] = w[i - 16] + s0 + w[i - 7] + s1;
}
auto a = hash[0];
auto b = hash[1];
auto c = hash[2];
auto d = hash[3];
auto e = hash[4];
auto f = hash[5];
auto g = hash[6];
auto h = hash[7];
for (int i = 0; i != 80; ++i) {
auto s1 = ror(e, 14) ^ ror(e, 18) ^ ror(e, 41);
auto ch = (e & f) ^ (~e & g);
auto temp1 = h + s1 + ch + k[i] + w[i];
auto s0 = ror(a, 28) ^ ror(a, 34) ^ ror(a, 39);
auto maj = (a & b) ^ (a & c) ^ (b & c);
auto temp2 = s0 + maj;
h = g;
g = f;
f = e;
e = d + temp1;
d = c;
c = b;
b = a;
a = temp1 + temp2;
}
hash[0] += a;
hash[1] += b;
hash[2] += c;
hash[3] += d;
hash[4] += e;
hash[5] += f;
hash[6] += g;
hash[7] += h;
};
while (length >= chunk_bytes) {
chunk(data);
data += chunk_bytes;
length -= chunk_bytes;
}
{
std::array<uint8_t, chunk_bytes> buf;
memcpy(buf.data(), data, length);
auto i = length;
buf[i++] = 0x80;
if (i > chunk_bytes - 16) {
while (i < chunk_bytes) {
buf[i++] = 0;
}
chunk(buf.data());
i = 0;
}
while (i < chunk_bytes - 16) {
buf[i++] = 0;
}
write_u64(&buf[i + 0], bit_length_high);
write_u64(&buf[i + 8], bit_length_low);
chunk(buf.data());
}
sha512_hash result;
for (uint8_t i = 0; i != 8; ++i) {
write_u64(&result[i * 8], hash[i]);
}
return result;
}
inline sha224_hash
sha224(const uint8_t* data, uint64_t length)
{
// Second 32 bits of the fractional parts of the square roots of the ninth
// through sixteenth primes 23..53
const uint32_t initial_hash_values[8] = {0xc1059ed8,
0x367cd507,
0x3070dd17,
0xf70e5939,
0xffc00b31,
0x68581511,
0x64f98fa7,
0xbefa4fa4};
auto hash = sha256_impl(initial_hash_values, data, length);
return truncate<sha224_hash>(hash);
}
inline sha256_hash
sha256(const uint8_t* data, uint64_t length)
{
// First 32 bits of the fractional parts of the square roots of the first
// eight primes 2..19:
const uint32_t initial_hash_values[8] = {0x6a09e667,
0xbb67ae85,
0x3c6ef372,
0xa54ff53a,
0x510e527f,
0x9b05688c,
0x1f83d9ab,
0x5be0cd19};
return sha256_impl(initial_hash_values, data, length);
}
inline sha384_hash
sha384(const uint8_t* data, uint64_t length)
{
const uint64_t initial_hash_values[8] = {0xcbbb9d5dc1059ed8,
0x629a292a367cd507,
0x9159015a3070dd17,
0x152fecd8f70e5939,
0x67332667ffc00b31,
0x8eb44a8768581511,
0xdb0c2e0d64f98fa7,
0x47b5481dbefa4fa4};
auto hash = sha512_impl(initial_hash_values, data, length);
return truncate<sha384_hash>(hash);
}
inline sha512_hash
sha512(const uint8_t* data, uint64_t length)
{
const uint64_t initial_hash_values[8] = {0x6a09e667f3bcc908,
0xbb67ae8584caa73b,
0x3c6ef372fe94f82b,
0xa54ff53a5f1d36f1,
0x510e527fade682d1,
0x9b05688c2b3e6c1f,
0x1f83d9abfb41bd6b,
0x5be0cd19137e2179};
return sha512_impl(initial_hash_values, data, length);
}
// SHA-512/t is a truncated version of SHA-512, where the result is truncated
// to t bits (in this implementation, t must be a multiple of eight). The two
// primariy variants of this are SHA-512/224 and SHA-512/256, both of which are
// provided through explicit functions (sha512_224 and sha512_256) below this
// function. On 64-bit platforms, SHA-512, and correspondingly SHA-512/t,
// should give a significant performance improvement over SHA-224 and SHA-256
// due to the doubled block size.
template <int bits>
inline hash_array<bits / 8>
sha512_t(const uint8_t* data, uint64_t length)
{
static_assert(bits % 8 == 0, "Bits must be a multiple of 8 (i.e., bytes).");
static_assert(0 < bits && bits <= 512, "Bits must be between 8 and 512");
static_assert(bits != 384, "NIST explicitly denies 384 bits, use SHA-384.");
const uint64_t modified_initial_hash_values[8] = {
0x6a09e667f3bcc908 ^ 0xa5a5a5a5a5a5a5a5,
0xbb67ae8584caa73b ^ 0xa5a5a5a5a5a5a5a5,
0x3c6ef372fe94f82b ^ 0xa5a5a5a5a5a5a5a5,
0xa54ff53a5f1d36f1 ^ 0xa5a5a5a5a5a5a5a5,
0x510e527fade682d1 ^ 0xa5a5a5a5a5a5a5a5,
0x9b05688c2b3e6c1f ^ 0xa5a5a5a5a5a5a5a5,
0x1f83d9abfb41bd6b ^ 0xa5a5a5a5a5a5a5a5,
0x5be0cd19137e2179 ^ 0xa5a5a5a5a5a5a5a5};
// The SHA-512/t generation function uses a modified SHA-512 on the string
// "SHA-512/t" where t is the number of bits. The modified SHA-512 operates
// like the original but uses different initial hash values, as seen above.
// The hash is then used for the initial hash values sent to the original
// SHA-512. The sha512_224 and sha512_256 functions have this precalculated.
constexpr int buf_size = 12;
uint8_t buf[buf_size];
auto buf_ptr = reinterpret_cast<char*>(buf);
auto len = snprintf(buf_ptr, buf_size, "SHA-512/%d", bits);
auto ulen = static_cast<uint64_t>(len);
auto initial8 = sha512_impl(modified_initial_hash_values, buf, ulen);
// To read the hash bytes back into 64-bit integers, we must convert back
// from big-endian.
uint64_t initial64[8];
for (uint8_t i = 0; i != 8; ++i) {
initial64[i] = read_u64(&initial8[i * 8]);
}
// Once the initial hash is computed, use regular SHA-512 and copy the
// appropriate number of bytes.
auto hash = sha512_impl(initial64, data, length);
return truncate<hash_array<bits / 8>>(hash);
}
// It is preferable to use either sha512_224 or sha512_256 in place of
// sha512_t<224> or sha512_t<256> for better performance (as the initial
// hashes are precalculated), for slightly less syntactic noise and for
// consistency with the other functions.
inline sha224_hash
sha512_224(const uint8_t* data, uint64_t length)
{
// Precalculated initial hash (The hash of "SHA-512/224" using the modified
// SHA-512 generation function, described above in sha512_t).
const uint64_t initial_hash_values[8] = {0x8c3d37c819544da2,
0x73e1996689dcd4d6,
0x1dfab7ae32ff9c82,
0x679dd514582f9fcf,
0x0f6d2b697bd44da8,
0x77e36f7304c48942,
0x3f9d85a86a1d36c8,
0x1112e6ad91d692a1};
auto hash = sha512_impl(initial_hash_values, data, length);
return truncate<sha224_hash>(hash);
}
inline sha256_hash
sha512_256(const uint8_t* data, uint64_t length)
{
// Precalculated initial hash (The hash of "SHA-512/256" using the modified
// SHA-512 generation function, described above in sha512_t).
const uint64_t initial_hash_values[8] = {0x22312194fc2bf72c,
0x9f555fa3c84c64c2,
0x2393b86b6f53b151,
0x963877195940eabd,
0x96283ee2a88effe3,
0xbe5e1e2553863992,
0x2b0199fc2c85b8aa,
0x0eb72ddc81c52ca2};
auto hash = sha512_impl(initial_hash_values, data, length);
return truncate<sha256_hash>(hash);
}
} // sha2 namespace
#endif /* SHA2_HPP */

8
Work/imgui.ini Normal file
View File

@@ -0,0 +1,8 @@
[Window][Debug##Default]
Pos=0,0
Size=400,400
[Window][MainMenu]
Pos=0,0
Size=960,540

View File

View File

@@ -1,30 +1,26 @@
#version 460 #version 450
layout (triangles) in; layout (triangles) in;
layout (triangle_strip, max_vertices = 3) out; layout (triangle_strip, max_vertices = 3) out;
layout(location = 0) in GeometryObj { layout(location = 0) in GeometryObj {
vec3 GeoPos; // Реальная позиция в мире vec3 GeoPos; // Реальная позиция в мире
flat uint Texture; // Текстура uint Texture; // Текстура
vec2 UV; vec2 UV;
} Geometry[]; } Geometry[];
layout(location = 0) out FragmentObj { layout(location = 0) out FragmentObj {
vec3 GeoPos; // Реальная позиция в мире vec3 GeoPos; // Реальная позиция в мире
vec3 Normal; uint Texture; // Текстура
flat uint Texture; // Текстура
vec2 UV; vec2 UV;
} Fragment; } Fragment;
void main() { void main() {
vec3 normal = normalize(cross(Geometry[1].GeoPos-Geometry[0].GeoPos, Geometry[2].GeoPos-Geometry[0].GeoPos));
for(int iter = 0; iter < 3; iter++) { for(int iter = 0; iter < 3; iter++) {
gl_Position = gl_in[iter].gl_Position; gl_Position = gl_in[iter].gl_Position;
Fragment.GeoPos = Geometry[iter].GeoPos; Fragment.GeoPos = Geometry[iter].GeoPos;
Fragment.Texture = Geometry[iter].Texture; Fragment.Texture = Geometry[iter].Texture;
Fragment.UV = Geometry[iter].UV; Fragment.UV = Geometry[iter].UV;
Fragment.Normal = normal;
EmitVertex(); EmitVertex();
} }

Binary file not shown.

View File

@@ -1,10 +1,10 @@
#version 460 #version 450
layout(location = 0) in uvec3 Vertex; layout(location = 0) in uvec4 Vertex;
layout(location = 0) out GeometryObj { layout(location = 0) out GeometryObj {
vec3 GeoPos; // Реальная позиция в мире vec3 GeoPos; // Реальная позиция в мире
flat uint Texture; // Текстура uint Texture; // Текстура
vec2 UV; vec2 UV;
} Geometry; } Geometry;
@@ -16,29 +16,25 @@ layout(push_constant) uniform UniformBufferObject {
// struct NodeVertexStatic { // struct NodeVertexStatic {
// uint32_t // uint32_t
// FX : 11, FY : 11, N1 : 10, // Позиция, 64 позиции на метр, +3.5м запас // FX : 9, FY : 9, FZ : 9, // Позиция -112 ~ 369 / 16
// FZ : 11, // Позиция // N1 : 4, // Не занято
// LS : 1, // Масштаб карты освещения (1м/16 или 1м) // LS : 1, // Масштаб карты освещения (1м/16 или 1м)
// Tex : 18, // Текстура // Tex : 18, // Текстура
// N2 : 2, // Не занято // N2 : 14, // Не занято
// TU : 16, TV : 16; // UV на текстуре // TU : 16, TV : 16; // UV на текстуре
// }; // };
void main() void main()
{ {
uint fx = Vertex.x & 0x7ffu;
uint fy = (Vertex.x >> 11) & 0x7ffu;
uint fz = Vertex.y & 0x7ffu;
vec4 baseVec = ubo.model*vec4( vec4 baseVec = ubo.model*vec4(
float(fx) / 64.f - 3.5f, float(Vertex.x & 0x1ff) / 16.f - 7,
float(fy) / 64.f - 3.5f, float((Vertex.x >> 9) & 0x1ff) / 16.f - 7,
float(fz) / 64.f - 3.5f, float((Vertex.x >> 18) & 0x1ff) / 16.f - 7,
1 1
); );
Geometry.GeoPos = baseVec.xyz; Geometry.GeoPos = baseVec.xyz;
Geometry.Texture = (Vertex.y >> 12) & 0x3ffffu; Geometry.Texture = Vertex.y & 0x3ffff;
Geometry.UV = vec2( Geometry.UV = vec2(
float(Vertex.z & 0xffff) / pow(2, 16), float(Vertex.z & 0xffff) / pow(2, 16),
float((Vertex.z >> 16) & 0xffff) / pow(2, 16) float((Vertex.z >> 16) & 0xffff) / pow(2, 16)

Binary file not shown.

View File

@@ -1,64 +1,74 @@
#version 460 #version 450
layout(early_fragment_tests) in;
layout(location = 0) in FragmentObj { layout(location = 0) in FragmentObj {
vec3 GeoPos; // Реальная позиция в мире vec3 GeoPos; // Реальная позиция в мире
vec3 Normal; uint Texture; // Текстура
flat uint Texture; // Текстура
vec2 UV; vec2 UV;
} Fragment; } Fragment;
layout(location = 0) out vec4 Frame; layout(location = 0) out vec4 Frame;
struct AtlasEntry { struct InfoSubTexture {
vec4 UVMinMax; uint Flags; // 1 isExist
uint Layer; uint PosXY, WidthHeight;
uint Flags;
uint _Pad0; uint AnimationFrames_AnimationTimePerFrame;
uint _Pad1;
}; };
const uint ATLAS_ENTRY_VALID = 1u; uniform layout(set = 0, binding = 0) sampler2D MainAtlas;
uniform layout(set = 0, binding = 0) sampler2DArray MainAtlas;
layout(set = 0, binding = 1) readonly buffer MainAtlasLayoutObj { layout(set = 0, binding = 1) readonly buffer MainAtlasLayoutObj {
AtlasEntry Entries[]; uint SubsCount;
uint Counter;
uint WidthHeight;
InfoSubTexture SubTextures[];
} MainAtlasLayout; } MainAtlasLayout;
uniform layout(set = 1, binding = 0) sampler2DArray LightMap; uniform layout(set = 1, binding = 0) sampler2D LightMap;
layout(set = 1, binding = 1) readonly buffer LightMapLayoutObj { layout(set = 1, binding = 1) readonly buffer LightMapLayoutObj {
vec3 Color; vec3 Color;
} LightMapLayout; } LightMapLayout;
vec4 atlasColor(uint texId, vec2 uv) vec4 atlasColor(uint texId, vec2 uv)
{ {
AtlasEntry entry = MainAtlasLayout.Entries[texId]; uint flags = (texId & 0xffff0000) >> 16;
if((entry.Flags & ATLAS_ENTRY_VALID) == 0u) texId &= 0xffff;
return vec4(((int(gl_FragCoord.x / 128) + int(gl_FragCoord.y / 128)) % 2) * vec3(1, 0, 1), 1); vec4 color = vec4(uv, 0, 1);
vec2 baseUV = vec2(uv.x, 1.0f - uv.y); if((flags & (2 | 4)) > 0)
vec2 atlasUV = mix(entry.UVMinMax.xy, entry.UVMinMax.zw, baseUV); {
atlasUV = clamp(atlasUV, entry.UVMinMax.xy, entry.UVMinMax.zw); if((flags & 2) > 0)
return texture(MainAtlas, vec3(atlasUV, entry.Layer)); color = vec4(1, 1, 1, 1);
} else if((flags & 4) > 0)
{
vec3 blendOverlay(vec3 base, vec3 blend) { color = vec4(1);
vec3 result; }
for (int i = 0; i < 3; ++i) {
if (base[i] <= 0.5)
result[i] = 2.0 * base[i] * blend[i];
else
result[i] = 1.0 - 2.0 * (1.0 - base[i]) * (1.0 - blend[i]);
} }
return result; else if(texId >= uint(MainAtlasLayout.SubsCount))
return vec4(((int(gl_FragCoord.x / 128) + int(gl_FragCoord.y / 128)) % 2 ) * vec3(0, 1, 1), 1);
else {
InfoSubTexture texInfo = MainAtlasLayout.SubTextures[texId];
if(texInfo.Flags == 0)
return vec4(((int(gl_FragCoord.x / 128) + int(gl_FragCoord.y / 128)) % 2 ) * vec3(1, 0, 1), 1);
uint posX = texInfo.PosXY & 0xffff;
uint posY = (texInfo.PosXY >> 16) & 0xffff;
uint width = texInfo.WidthHeight & 0xffff;
uint height = (texInfo.WidthHeight >> 16) & 0xffff;
uint awidth = MainAtlasLayout.WidthHeight & 0xffff;
uint aheight = (MainAtlasLayout.WidthHeight >> 16) & 0xffff;
if((flags & 1) > 0)
color = texture(MainAtlas, vec2((posX+0.5f+uv.x*(width-1))/awidth, (posY+0.5f+(1-uv.y)*(height-1))/aheight));
else
color = texture(MainAtlas, vec2((posX+uv.x*width)/awidth, (posY+(1-uv.y)*height)/aheight));
}
return color;
} }
void main() { void main() {
Frame = atlasColor(Fragment.Texture, Fragment.UV); Frame = atlasColor(Fragment.Texture, Fragment.UV);
Frame.xyz *= max(0.2f, dot(Fragment.Normal, normalize(vec3(0.5, 1, 0.8)))); }
// Frame = vec4(blendOverlay(vec3(Frame), vec3(Fragment.GeoPos/64.f)), Frame.w);
if(Frame.w == 0)
discard;
}

View File

@@ -1,27 +1,16 @@
#version 460 #version 450
layout(location = 0) in FragmentObj { layout(location = 0) in FragmentObj {
vec3 GeoPos; // Реальная позиция в мире vec3 GeoPos; // Реальная позиция в мире
vec3 Normal; uint Texture; // Текстура
flat uint Texture; // Текстура
vec2 UV; vec2 UV;
} Fragment; } Fragment;
layout(location = 0) out vec4 Frame; layout(location = 0) out vec4 Frame;
struct AtlasEntry {
vec4 UVMinMax;
uint Layer;
uint Flags;
uint _Pad0;
uint _Pad1;
};
const uint ATLAS_ENTRY_VALID = 1u;
uniform layout(set = 0, binding = 0) sampler2DArray MainAtlas; uniform layout(set = 0, binding = 0) sampler2DArray MainAtlas;
layout(set = 0, binding = 1) readonly buffer MainAtlasLayoutObj { layout(set = 0, binding = 1) readonly buffer MainAtlasLayoutObj {
AtlasEntry Entries[]; vec3 Color;
} MainAtlasLayout; } MainAtlasLayout;
uniform layout(set = 1, binding = 0) sampler2DArray LightMap; uniform layout(set = 1, binding = 0) sampler2DArray LightMap;
@@ -29,22 +18,6 @@ layout(set = 1, binding = 1) readonly buffer LightMapLayoutObj {
vec3 Color; vec3 Color;
} LightMapLayout; } LightMapLayout;
vec4 atlasColor(uint texId, vec2 uv)
{
AtlasEntry entry = MainAtlasLayout.Entries[texId];
if((entry.Flags & ATLAS_ENTRY_VALID) == 0u)
return vec4(((int(gl_FragCoord.x / 128) + int(gl_FragCoord.y / 128)) % 2) * vec3(1, 0, 1), 1);
vec2 baseUV = vec2(uv.x, 1.0f - uv.y);
vec2 atlasUV = mix(entry.UVMinMax.xy, entry.UVMinMax.zw, baseUV);
atlasUV = clamp(atlasUV, entry.UVMinMax.xy, entry.UVMinMax.zw);
return texture(MainAtlas, vec3(atlasUV, entry.Layer));
}
void main() { void main() {
Frame = atlasColor(Fragment.Texture, Fragment.UV); Frame = vec4(1);
Frame.xyz *= max(0.2f, dot(Fragment.Normal, normalize(vec3(0.5, 1, 0.8)))); }
if(Frame.w == 0)
discard;
}

View File

@@ -9,22 +9,23 @@ layout(location = 0) in FragmentObj {
layout(location = 0) out vec4 Frame; layout(location = 0) out vec4 Frame;
struct AtlasEntry { struct InfoSubTexture {
vec4 UVMinMax; uint Flags; // 1 isExist
uint Layer; uint PosXY, WidthHeight;
uint Flags;
uint _Pad0; uint AnimationFrames_AnimationTimePerFrame;
uint _Pad1;
}; };
const uint ATLAS_ENTRY_VALID = 1u; uniform layout(set = 0, binding = 0) sampler2D MainAtlas;
uniform layout(set = 0, binding = 0) sampler2DArray MainAtlas;
layout(set = 0, binding = 1) readonly buffer MainAtlasLayoutObj { layout(set = 0, binding = 1) readonly buffer MainAtlasLayoutObj {
AtlasEntry Entries[]; uint SubsCount;
uint Counter;
uint WidthHeight;
InfoSubTexture SubTextures[];
} MainAtlasLayout; } MainAtlasLayout;
uniform layout(set = 1, binding = 0) sampler2DArray LightMap; uniform layout(set = 1, binding = 0) sampler2D LightMap;
layout(set = 1, binding = 1) readonly buffer LightMapLayoutObj { layout(set = 1, binding = 1) readonly buffer LightMapLayoutObj {
vec3 Color; vec3 Color;
} LightMapLayout; } LightMapLayout;
@@ -34,14 +35,42 @@ vec4 atlasColor(uint texId, vec2 uv)
{ {
uv = mod(uv, 1); uv = mod(uv, 1);
AtlasEntry entry = MainAtlasLayout.Entries[texId]; uint flags = (texId & 0xffff0000) >> 16;
if((entry.Flags & ATLAS_ENTRY_VALID) == 0u) texId &= 0xffff;
return vec4(((int(gl_FragCoord.x / 128) + int(gl_FragCoord.y / 128)) % 2) * vec3(1, 0, 1), 1); vec4 color = vec4(uv, 0, 1);
if((flags & (2 | 4)) > 0)
{
if((flags & 2) > 0)
color = vec4(1, 1, 1, 1);
else if((flags & 4) > 0)
{
color = vec4(1);
}
}
else if(texId >= uint(MainAtlasLayout.SubsCount))
return vec4(((int(gl_FragCoord.x / 128) + int(gl_FragCoord.y / 128)) % 2 ) * vec3(0, 1, 1), 1);
else {
InfoSubTexture texInfo = MainAtlasLayout.SubTextures[texId];
if(texInfo.Flags == 0)
return vec4(((int(gl_FragCoord.x / 128) + int(gl_FragCoord.y / 128)) % 2 ) * vec3(1, 0, 1), 1);
vec2 baseUV = vec2(uv.x, 1.0f - uv.y); uint posX = texInfo.PosXY & 0xffff;
vec2 atlasUV = mix(entry.UVMinMax.xy, entry.UVMinMax.zw, baseUV); uint posY = (texInfo.PosXY >> 16) & 0xffff;
atlasUV = clamp(atlasUV, entry.UVMinMax.xy, entry.UVMinMax.zw); uint width = texInfo.WidthHeight & 0xffff;
return texture(MainAtlas, vec3(atlasUV, entry.Layer)); uint height = (texInfo.WidthHeight >> 16) & 0xffff;
uint awidth = MainAtlasLayout.WidthHeight & 0xffff;
uint aheight = (MainAtlasLayout.WidthHeight >> 16) & 0xffff;
if((flags & 1) > 0)
color = texture(MainAtlas, vec2((posX+0.5f+uv.x*(width-1))/awidth, (posY+0.5f+(1-uv.y)*(height-1))/aheight));
else
color = texture(MainAtlas, vec2((posX+uv.x*width)/awidth, (posY+(1-uv.y)*height)/aheight));
}
return color;
} }
void main() { void main() {

View File

@@ -9,19 +9,9 @@ layout(location = 0) in Fragment {
layout(location = 0) out vec4 Frame; layout(location = 0) out vec4 Frame;
struct AtlasEntry {
vec4 UVMinMax;
uint Layer;
uint Flags;
uint _Pad0;
uint _Pad1;
};
const uint ATLAS_ENTRY_VALID = 1u;
uniform layout(set = 0, binding = 0) sampler2DArray MainAtlas; uniform layout(set = 0, binding = 0) sampler2DArray MainAtlas;
layout(set = 0, binding = 1) readonly buffer MainAtlasLayoutObj { layout(set = 0, binding = 1) readonly buffer MainAtlasLayoutObj {
AtlasEntry Entries[]; vec3 Color;
} MainAtlasLayout; } MainAtlasLayout;
uniform layout(set = 1, binding = 0) sampler2DArray LightMap; uniform layout(set = 1, binding = 0) sampler2DArray LightMap;
@@ -29,39 +19,6 @@ layout(set = 1, binding = 1) readonly buffer LightMapLayoutObj {
vec3 Color; vec3 Color;
} LightMapLayout; } LightMapLayout;
vec4 atlasColor(uint texId, vec2 uv)
{
uv = mod(uv, 1);
AtlasEntry entry = MainAtlasLayout.Entries[texId];
if((entry.Flags & ATLAS_ENTRY_VALID) == 0u)
return vec4(((int(gl_FragCoord.x / 128) + int(gl_FragCoord.y / 128)) % 2) * vec3(1, 0, 1), 1);
vec2 baseUV = vec2(uv.x, 1.0f - uv.y);
vec2 atlasUV = mix(entry.UVMinMax.xy, entry.UVMinMax.zw, baseUV);
atlasUV = clamp(atlasUV, entry.UVMinMax.xy, entry.UVMinMax.zw);
return texture(MainAtlas, vec3(atlasUV, entry.Layer));
}
void main() { void main() {
vec2 uv; Frame = vec4(1);
switch(fragment.Place) {
case 0:
uv = fragment.GeoPos.xz; break;
case 1:
uv = fragment.GeoPos.xy; break;
case 2:
uv = fragment.GeoPos.zy; break;
case 3:
uv = fragment.GeoPos.xz*vec2(-1, -1); break;
case 4:
uv = fragment.GeoPos.xy*vec2(-1, 1); break;
case 5:
uv = fragment.GeoPos.zy*vec2(-1, 1); break;
default:
uv = vec2(0);
}
Frame = atlasColor(fragment.VoxMTL, uv);
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 138 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

@@ -1,161 +0,0 @@
# Определение ресурсов (assets)
Документ описывает формат файлов ресурсов и правила их адресации на стороне сервера.
Описание основано на загрузчиках из `Src/Server/AssetsManager.hpp` и связанных структурах
подготовки (`PreparedNodeState`, `PreparedModel`, `PreparedGLTF`).
## Общая схема
- Ресурсы берутся из списка папок `AssetsRegister::Assets` (от последнего мода к первому).
Первый найденный ресурс по пути имеет приоритет.
- Переопределения через `AssetsRegister::Custom` имеют более высокий приоритет.
- Адрес ресурса состоит из `domain` и `key`.
`domain` — имя папки в assets, `key` — относительный путь внутри папки типа ресурса.
- Обработанные ресурсы сохраняются в `server_cache/assets`.
## Дерево папок
```
assets/
<domain>/
nodestate/ *.json
model/ *.json | *.gltf | *.glb
texture/ *.png | *.jpg (jpeg)
particle/ (загрузка из файлов пока не реализована)
animation/ (загрузка из файлов пока не реализована)
sound/ (загрузка из файлов пока не реализована)
font/ (загрузка из файлов пока не реализована)
```
Пример: `assets/core/nodestate/stone.json` имеет `domain=core`, `key=stone.json`.
При обращении к nodestate из логики нод используется ключ без суффикса `.json`
(сервер дописывает расширение автоматически).
## Nodestate (JSON)
Файл nodestate — это JSON-объект, где ключи — условия, а значения — описание модели
или список вариантов моделей.
### Условия
Условие — строковое выражение. Поддерживаются:
- числа, `true`, `false`
- переменные: `state` или `state:value` (двоеточие — часть имени)
- операторы: `+ - * / %`, `!`, `&`, `|`, `< <= > >= == !=`
- скобки
Пустая строка условия трактуется как `true`.
### Формат варианта модели
Объект варианта:
- `model`: строка `domain:key` **или** массив объектов моделей
- `weight`: число (вес при случайном выборе), по умолчанию `1`
- `uvlock`: bool (используется для векторных моделей; для одиночной модели игнорируется)
- `transformations`: массив строк `"key=value"` для трансформаций
Если `model` — строка, это одиночная модель.
Если `model` — массив, это векторная модель: набор объектов вида:
```
{ "model": "domain:key", "uvlock": false, "transformations": ["x=0", "ry=1.57"] }
```
Для векторной модели также могут задаваться `uvlock` и `transformations` на верхнем уровне
(они применяются к группе).
Трансформации поддерживают ключи:
`x`, `y`, `z`, `rx`, `ry`, `rz` (сдвиг и поворот).
Домен в строке `domain:key` можно опустить — тогда используется домен файла nodestate.
### Пример
```json
{
"": { "model": "core:stone" },
"variant == 1": [
{ "model": "core:stone_alt", "weight": 2 },
{ "model": "core:stone_alt_2", "weight": 1, "transformations": ["ry=1.57"] }
],
"facing:north": {
"model": [
{ "model": "core:stone", "transformations": ["ry=3.14"] },
{ "model": "core:stone_detail", "transformations": ["x=0.5"] }
],
"uvlock": true
}
}
```
## Model (JSON)
Формат описывает геометрию и текстуры.
### Верхний уровень
- `gui_light`: строка (сейчас используется только `default`)
- `ambient_occlusion`: bool
- `display`: объект с наборами `rotation`/`translation`/`scale` (все — массивы из 3 чисел)
- `textures`: объект `name -> string` (ссылка на текстуру или pipeline)
- `cuboids`: массив геометрических блоков
- `sub_models`: массив подмоделей
### Текстуры
В `textures` значение:
- либо строка `domain:key` (прямая ссылка на текстуру),
- либо pipeline-строка, начинающаяся с `tex` (компилируется `TexturePipelineProgram`).
Если домен не указан, используется домен файла модели.
### Cuboids
Элемент `cuboids`:
- `shade`: bool (по умолчанию `true`)
- `from`: `[x, y, z]`
- `to`: `[x, y, z]`
- `faces`: объект граней (`down|up|north|south|west|east`)
- `transformations`: массив `"key=value"` (ключи как у nodestate)
Грань (`faces.<name>`) может содержать:
- `uv`: `[u0, v0, u1, v1]`
- `texture`: строка (ключ из `textures`)
- `cullface`: `down|up|north|south|west|east`
- `tintindex`: int
- `rotation`: int16
### Sub-models
`sub_models` допускает:
- строку `domain:key`
- объект `{ "model": "domain:key", "scene": 0 }`
- объект `{ "path": "domain:key", "scene": 0 }`
Поле `scene` опционально.
### Пример
```json
{
"ambient_occlusion": true,
"textures": {
"all": "core:stone"
},
"cuboids": [
{
"from": [0, 0, 0],
"to": [16, 16, 16],
"faces": {
"north": { "uv": [0, 0, 16, 16], "texture": "#all" }
}
}
],
"sub_models": [
"core:stone_detail",
{ "model": "core:stone_variant", "scene": 1 }
]
}
```
## Model (glTF / GLB)
Файлы моделей могут быть:
- `.gltf` (JSON glTF)
- `.glb` (binary glTF)
Оба формата конвертируются в `PreparedGLTF`.
## Texture
Поддерживаются только PNG и JPEG.
Формат определяется по сигнатуре файла.
## Прочие типы ресурсов
Для `particle`, `animation`, `sound`, `font` загрузка из файловой системы
в серверном загрузчике пока не реализована (`std::unreachable()`), но возможна
регистрация из Lua через `path` (сырые бинарные данные).

View File

@@ -1,66 +0,0 @@
# AssetsManager
Документ описывает реализацию `AssetsManager` на стороне клиента.
## Назначение
`AssetsManager` объединяет в одном объекте:
- таблицы привязок ресурсов (domain/key -> localId, serverId -> localId);
- загрузку и хранение ресурсов из ресурспаков;
- систему источников данных (packs/memory/cache) с асинхронной выдачей;
- перестройку заголовков ресурсов под локальные идентификаторы.
## Основные структуры данных
- `PerType` — набор таблиц на каждый тип ресурса:
- `DKToLocal` и `LocalToDK` — двунаправленное отображение domain/key <-> localId.
- `LocalParent` — union-find для ребиндинга локальных id.
- `ServerToLocal` и `BindInfos` — привязки серверных id и их метаданные.
- `PackResources` — набор ресурсов, собранных из паков.
- `Sources` — список источников ресурсов (pack, memory, cache).
- `SourceCacheByHash` — кэш успешного источника для хеша.
- `PendingReadsByHash` и `ReadyReads` — очередь ожидания и готовые ответы.
## Источники ресурсов
Источники реализованы через интерфейс `IResourceSource`:
- pack source (sync) — ищет ресурсы в `PackResources`.
- memory source (sync) — ищет в `MemoryResourcesByHash`.
- cache source (async) — делает чтения через `AssetsCacheManager`.
Алгоритм поиска:
1) Сначала проверяется `SourceCacheByHash` (если не протух по поколению).
2) Источники опрашиваются по порядку, первый `Hit` возвращается сразу.
3) Если источник вернул `Pending`, запрос попадает в ожидание.
4) `tickSources()` опрашивает асинхронные источники и переводит ответы в `ReadyReads`.
## Привязка идентификаторов
- `getOrCreateLocalId()` создаёт локальный id для domain/key.
- `bindServerResource()` связывает serverId с localId и записывает `BindInfo`.
- `unionLocalIds()` объединяет локальные id при конфликте, используя union-find.
## Ресурспаки
`reloadPacks()` сканирует директории, собирает ресурсы в `PackResources`,
а затем возвращает список изменений и потерь по типам.
Важно: ключи ресурсов всегда хранятся с разделителем `/`.
Для нормализации пути используется `fs::path::generic_string()`.
## Заголовки
- `rebindHeader()` заменяет id зависимостей в заголовках ресурса.
- `parseHeader()` парсит заголовок без модификаций.
## Поток данных чтения
1) `pushReads()` принимает список `ResourceKey` и пытается получить ресурс.
2) `pullReads()` возвращает готовые ответы, включая промахи.
3) `pushResources()` добавляет ресурсы в память и прокидывает их в кэш.
## Ограничения
- Класс не предназначен для внешнего многопоточного использования.
- Политика приоритета ресурсов в паке фиксированная: первый найденный ключ побеждает.
- Коллизии хешей не обрабатываются отдельно.

View File

@@ -1,57 +0,0 @@
# Resources and asset transfer
This document describes how binary resources are discovered, cached, and transferred from server to client.
## Resource types
- Binary resources are grouped by EnumAssets (nodestate, particle, animation, model, texture, sound, font).
- Each resource is addressed by a domain + key pair and also identified by a SHA-256 hash (Hash_t).
- The hash is the canonical payload identity: if the hash matches, the content is identical.
## High-level flow
1) Server tracks resource usage per client.
2) Server announces bindings (type + id + domain:key + hash) to the client.
3) Client checks local packs and cache; if missing, client requests by hash.
4) Server streams requested resources in chunks.
5) Client assembles the payload, stores it in cache, and marks the resource as loaded.
## Server side
- Resource usage counters are maintained in `RemoteClient::NetworkAndResource_t::ResUses`.
- When content references change, `incrementAssets` and `decrementAssets` update counters.
- `ResourceRequest` is built from per-client usage and sent to `GameServer::stepSyncContent`.
- `GameServer::stepSyncContent` resolves resource data via `AssetsManager` and calls `RemoteClient::informateAssets`.
- `RemoteClient::informateAssets`:
- Sends bind notifications when a hash for an id changes or is new to the client.
- Queues full resources only if the client explicitly requested the hash.
- Streaming happens in `RemoteClient::onUpdate`:
- `InitResSend` announces the total size + hash + type/id + domain/key.
- `ChunkSend` transmits raw payload slices until complete.
## Client side
- `ServerSession::rP_Resource` handles bind/lost/init/chunk packets.
- `Bind` and `Lost` update the in-memory bindings queue.
- `InitResSend` creates an `AssetLoading` entry keyed by hash.
- `ChunkSend` appends bytes to the loading entry; when finished, the asset is enqueued for cache write.
- The update loop (`ServerSession::update`) pulls assets from `AssetsManager`:
- If cache miss: sends `ResourceRequest` with the hash.
- If cache hit: directly marks the resource as loaded.
## Client cache (`AssetsManager`)
- Reads check, in order:
1) Resource packs on disk (assets directories)
2) Inline sqlite cache (small resources)
3) File-based cache (large resources)
- Writes store small resources in sqlite and larger ones as files under `Cache/blobs/`.
- The cache also tracks last-used timestamps for eviction policies.
## Packet types (Resources)
- `Bind`: server -> client mapping of (type, id, domain, key, hash).
- `Lost`: server -> client removing mapping of (type, id).
- `InitResSend`: server -> client resource stream header.
- `ChunkSend`: server -> client resource stream payload.
- `ResourceRequest`: client -> server, list of hashes that are missing.
## Common failure modes
- Missing bind update: client will never request the hash.
- Missing cache entry or stale `AlreadyLoading`: client stops requesting resources it still needs.
- Interrupted streaming: asset stays in `AssetsLoading` without being finalized.