Compare commits
2 Commits
0b8326e278
...
mr_s
| Author | SHA1 | Date | |
|---|---|---|---|
| 1710eb974d | |||
| e190c79d00 |
3
.gitignore
vendored
@@ -14,6 +14,3 @@
|
||||
/imgui.ini
|
||||
/data
|
||||
/gmon.out
|
||||
|
||||
/log.raw
|
||||
/Cache
|
||||
|
||||
3
.gitmodules
vendored
@@ -1,3 +0,0 @@
|
||||
[submodule "Libs/boost"]
|
||||
path = Libs/boost
|
||||
url = https://github.com/boostorg/boost.git
|
||||
238
CMakeLists.txt
@@ -1,62 +1,34 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
|
||||
option(BUILD_CLIENT "Build the client" ON)
|
||||
option(USE_LIBURING "Build with liburing support" ON)
|
||||
option(BUILD_CLIENT "Build the client" TRUE)
|
||||
|
||||
|
||||
set(CMAKE_CXX_STANDARD 23)
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
add_compile_options(-fcoroutines)
|
||||
|
||||
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdata-sections -ffunction-sections -DGLM_FORCE_DEPTH_ZERO_TO_ONE")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--gc-sections") # -rdynamic
|
||||
|
||||
# gprof
|
||||
# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pg")
|
||||
# set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pg")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pg")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pg")
|
||||
|
||||
# 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_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_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=undefined -fno-sanitize-recover=all -fsanitize=float-divide-by-zero -fsanitize=float-cast-overflow -fno-sanitize=null -fno-sanitize=alignment")
|
||||
|
||||
# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address,undefined -fno-omit-frame-pointer -fno-sanitize-recover=all")
|
||||
# set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address,undefined")
|
||||
project (LuaVox VERSION 0.0 DESCRIPTION "LuaVox Description")
|
||||
add_executable(${PROJECT_NAME})
|
||||
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_20)
|
||||
|
||||
project(LuaVox VERSION 0.0 DESCRIPTION "LuaVox Description")
|
||||
|
||||
add_library(luavox_common INTERFACE)
|
||||
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()
|
||||
file(GLOB_RECURSE SOURCES RELATIVE ${PROJECT_SOURCE_DIR} "Src/*.cpp")
|
||||
target_sources(${PROJECT_NAME} PRIVATE ${SOURCES})
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC "${PROJECT_SOURCE_DIR}/Src")
|
||||
|
||||
include(FetchContent)
|
||||
|
||||
@@ -72,65 +44,19 @@ set(Boost_USE_STATIC_LIBS ON)
|
||||
|
||||
set(BOOST_INCLUDE_LIBRARIES asio thread json)
|
||||
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(
|
||||
Boost
|
||||
GIT_REPOSITORY https://github.com/boostorg/boost.git
|
||||
GIT_TAG boost-1.87.0
|
||||
GIT_PROGRESS true
|
||||
USES_TERMINAL_DOWNLOAD true
|
||||
URL https://github.com/boostorg/boost/releases/download/boost-1.87.0/boost-1.87.0-cmake.7z
|
||||
USES_TERMINAL_DOWNLOAD TRUE
|
||||
DOWNLOAD_NO_EXTRACT FALSE
|
||||
)
|
||||
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)
|
||||
|
||||
# unordered_dense
|
||||
FetchContent_Declare(
|
||||
unordered_dense
|
||||
GIT_REPOSITORY https://github.com/martinus/unordered_dense.git
|
||||
GIT_TAG v4.8.1
|
||||
)
|
||||
FetchContent_MakeAvailable(unordered_dense)
|
||||
|
||||
target_link_libraries(luavox_common INTERFACE unordered_dense::unordered_dense)
|
||||
target_link_libraries(${PROJECT_NAME} PUBLIC Boost::asio Boost::thread Boost::json)
|
||||
|
||||
# glm
|
||||
# find_package(glm REQUIRED)
|
||||
# target_include_directories(${PROJECT_NAME} PUBLIC ${GLM_INCLUDE_DIR})
|
||||
# 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(
|
||||
glm
|
||||
@@ -138,112 +64,70 @@ FetchContent_Declare(
|
||||
GIT_TAG 1.0.1
|
||||
)
|
||||
FetchContent_MakeAvailable(glm)
|
||||
target_link_libraries(luavox_common INTERFACE glm)
|
||||
target_link_libraries(${PROJECT_NAME} PUBLIC glm)
|
||||
|
||||
find_package(ICU REQUIRED COMPONENTS i18n uc)
|
||||
target_include_directories(luavox_common INTERFACE ${ICU_INCLUDE_DIR})
|
||||
target_link_libraries(luavox_common INTERFACE ${ICU_LIBRARIES})
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC ${ICU_INCLUDE_DIR})
|
||||
target_link_libraries(${PROJECT_NAME} PUBLIC ${ICU_LIBRARIES})
|
||||
|
||||
find_package(OpenSSL REQUIRED)
|
||||
target_include_directories(luavox_common INTERFACE ${OPENSSL_INCLUDE_DIR})
|
||||
target_link_libraries(luavox_common INTERFACE ${OPENSSL_LIBRARIES})
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC ${OPENSSL_INCLUDE_DIR})
|
||||
target_link_libraries(${PROJECT_NAME} PUBLIC ${OPENSSL_LIBRARIES})
|
||||
|
||||
# JPEG
|
||||
find_package(JPEG REQUIRED)
|
||||
target_include_directories(luavox_common INTERFACE ${JPEG_INCLUDE_DIRS})
|
||||
target_link_libraries(luavox_common INTERFACE JPEG::JPEG)
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC ${JPEG_INCLUDE_DIRS})
|
||||
target_link_libraries(${PROJECT_NAME} PUBLIC JPEG::JPEG)
|
||||
|
||||
# PNG
|
||||
find_package(PNG REQUIRED)
|
||||
target_include_directories(luavox_common INTERFACE ${PNG_INCLUDE_DIRS})
|
||||
target_link_libraries(luavox_common INTERFACE PNG::PNG)
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC ${PNG_INCLUDE_DIRS})
|
||||
target_link_libraries(${PROJECT_NAME} PUBLIC PNG::PNG)
|
||||
|
||||
# PNG++
|
||||
target_include_directories(luavox_common INTERFACE "${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)
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC "${PROJECT_SOURCE_DIR}/Libs/png++")
|
||||
|
||||
# GLFW3
|
||||
if(BUILD_CLIENT)
|
||||
add_executable(luavox_client)
|
||||
find_package(glfw3 3)
|
||||
|
||||
# Common
|
||||
target_link_libraries(luavox_client PUBLIC luavox_common)
|
||||
|
||||
# Исходники
|
||||
file(GLOB_RECURSE SOURCES RELATIVE ${PROJECT_SOURCE_DIR} "Src/*.cpp")
|
||||
target_sources(luavox_client PRIVATE ${SOURCES})
|
||||
target_include_directories(luavox_client PUBLIC "${PROJECT_SOURCE_DIR}/Src")
|
||||
|
||||
# GLFW3
|
||||
if(TARGET glfw)
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC ${GLFW_INCLUDE_DIRS})
|
||||
else()
|
||||
FetchContent_Declare(
|
||||
glfw
|
||||
GIT_REPOSITORY https://github.com/glfw/glfw.git
|
||||
GIT_TAG 3.4
|
||||
)
|
||||
FetchContent_MakeAvailable(glfw)
|
||||
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()
|
||||
target_link_libraries(${PROJECT_NAME} PUBLIC glfw)
|
||||
endif()
|
||||
|
||||
# FreeType
|
||||
find_package(Freetype REQUIRED)
|
||||
# FetchContent_Declare(
|
||||
# freetype
|
||||
# GIT_REPOSITORY https://github.com/freetype/freetype.git
|
||||
# GIT_TAG freetype
|
||||
# )
|
||||
# FetchContent_MakeAvailable(freetype)
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC ${freetype_INCLUDE_DIRS})
|
||||
target_link_libraries(${PROJECT_NAME} PUBLIC Freetype::Freetype)
|
||||
|
||||
# ImGui
|
||||
file(GLOB SOURCES "${PROJECT_SOURCE_DIR}/Libs/imgui/*.cpp")
|
||||
target_sources(${PROJECT_NAME} PRIVATE ${SOURCES} "${PROJECT_SOURCE_DIR}/Libs/imgui/backends/imgui_impl_glfw.cpp" "${PROJECT_SOURCE_DIR}/Libs/imgui/backends/imgui_impl_vulkan.cpp")
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC "${PROJECT_SOURCE_DIR}/Libs/imgui/")
|
||||
|
||||
# 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)
|
||||
|
||||
@@ -1,22 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include "Common/Net.hpp"
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
#include <Common/Abstract.hpp>
|
||||
|
||||
|
||||
namespace LV::Client {
|
||||
|
||||
using EntityId_t = uint16_t;
|
||||
using FuncEntityId_t = uint16_t;
|
||||
|
||||
struct GlobalTime {
|
||||
uint32_t Seconds : 22 = 0, Sub : 10 = 0;
|
||||
|
||||
@@ -37,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 метров ребро
|
||||
// 256 вокселей ребро
|
||||
struct Chunk {
|
||||
// Кубы вокселей в чанке
|
||||
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 {
|
||||
public:
|
||||
// PosQuat
|
||||
WorldId_t WorldId;
|
||||
// PortalId LastUsedPortal;
|
||||
DefWorldId_c WorldId;
|
||||
DefPortalId_c LastUsedPortal;
|
||||
Pos::Object Pos;
|
||||
glm::quat Quat;
|
||||
static constexpr uint16_t HP_BS = 4096, HP_BS_Bit = 12;
|
||||
@@ -64,74 +67,40 @@ public:
|
||||
// states
|
||||
};
|
||||
|
||||
struct AssetsModelUpdate {
|
||||
ResourceId Id = 0;
|
||||
HeadlessModel Model;
|
||||
HeadlessModel::Header Header;
|
||||
};
|
||||
|
||||
struct AssetsNodestateUpdate {
|
||||
ResourceId Id = 0;
|
||||
HeadlessNodeState Nodestate;
|
||||
HeadlessNodeState::Header Header;
|
||||
};
|
||||
|
||||
struct AssetsTextureUpdate {
|
||||
ResourceId Id = 0;
|
||||
uint16_t Width = 0;
|
||||
uint16_t Height = 0;
|
||||
std::vector<uint32_t> Pixels;
|
||||
ResourceHeader Header;
|
||||
};
|
||||
|
||||
struct AssetsBinaryUpdate {
|
||||
ResourceId Id = 0;
|
||||
std::u8string Data;
|
||||
};
|
||||
|
||||
/* Интерфейс рендера текущего подключения к серверу */
|
||||
class IRenderSession {
|
||||
public:
|
||||
// Объект уведомления об изменениях
|
||||
struct TickSyncData {
|
||||
// Изменения в ассетах.
|
||||
std::vector<AssetsModelUpdate> AssetsModels;
|
||||
std::vector<AssetsNodestateUpdate> AssetsNodestates;
|
||||
std::vector<AssetsTextureUpdate> AssetsTextures;
|
||||
virtual void onDefTexture(TextureId_c id, std::vector<std::byte> &&info) = 0;
|
||||
virtual void onDefTextureLost(const std::vector<TextureId_c> &&lost) = 0;
|
||||
virtual void onDefModel(ModelId_c id, std::vector<std::byte> &&info) = 0;
|
||||
virtual void onDefModelLost(const std::vector<ModelId_c> &&lost) = 0;
|
||||
|
||||
// Новые или изменённые профили контента
|
||||
std::unordered_map<EnumDefContent, std::vector<ResourceId>> Profiles_ChangeOrAdd;
|
||||
// Более не используемые профили
|
||||
std::unordered_map<EnumDefContent, std::vector<ResourceId>> Profiles_Lost;
|
||||
|
||||
// Новые или изменённые чанки
|
||||
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(TickSyncData& data) = 0;
|
||||
virtual void onDefWorldUpdates(const std::vector<DefWorldId_c> &updates) = 0;
|
||||
virtual void onDefVoxelUpdates(const std::vector<DefVoxelId_c> &updates) = 0;
|
||||
virtual void onDefNodeUpdates(const std::vector<DefNodeId_c> &updates) = 0;
|
||||
virtual void onDefPortalUpdates(const std::vector<DefPortalId_c> &updates) = 0;
|
||||
virtual void onDefEntityUpdates(const std::vector<DefEntityId_c> &updates) = 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();
|
||||
};
|
||||
|
||||
|
||||
struct Region {
|
||||
std::array<Chunk, 4*4*4> Chunks;
|
||||
std::unordered_map<Pos::Local16_u, Chunk> Chunks;
|
||||
};
|
||||
|
||||
|
||||
struct World {
|
||||
|
||||
std::vector<EntityId_c> Entitys;
|
||||
std::unordered_map<Pos::GlobalRegion::Key, Region> Regions;
|
||||
};
|
||||
|
||||
|
||||
struct DefWorldInfo {
|
||||
|
||||
};
|
||||
@@ -141,16 +110,19 @@ struct DefPortalInfo {
|
||||
};
|
||||
|
||||
struct DefEntityInfo {
|
||||
};
|
||||
|
||||
struct DefFuncEntityInfo {
|
||||
|
||||
};
|
||||
|
||||
struct WorldInfo {
|
||||
std::vector<EntityId_t> Entitys;
|
||||
std::vector<FuncEntityId_t> FuncEntitys;
|
||||
std::unordered_map<Pos::GlobalRegion, Region> Regions;
|
||||
|
||||
};
|
||||
|
||||
struct VoxelInfo {
|
||||
|
||||
};
|
||||
|
||||
struct NodeInfo {
|
||||
|
||||
};
|
||||
|
||||
struct PortalInfo {
|
||||
@@ -158,138 +130,31 @@ struct PortalInfo {
|
||||
};
|
||||
|
||||
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 DefVoxel {
|
||||
DefVoxel() = default;
|
||||
DefVoxel(const std::u8string_view view) {
|
||||
|
||||
}
|
||||
|
||||
void reBind(const std::function<ResourceId(EnumAssets, ResourceId)>& am) {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
struct DefNode {
|
||||
std::variant<AssetsNodestate> RenderStates;
|
||||
|
||||
DefNode() = default;
|
||||
DefNode(const std::u8string_view view) {
|
||||
Net::LinearReader lr(view);
|
||||
RenderStates = lr.read<uint32_t>();
|
||||
}
|
||||
|
||||
void reBind(const std::function<ResourceId(EnumAssets, ResourceId)>& am) {
|
||||
RenderStates = am(EnumAssets::Nodestate, std::get<AssetsNodestate>(RenderStates));
|
||||
}
|
||||
};
|
||||
|
||||
struct DefWorld {
|
||||
DefWorld() = default;
|
||||
DefWorld(const std::u8string_view view) {
|
||||
|
||||
}
|
||||
|
||||
void reBind(const std::function<ResourceId(EnumAssets, ResourceId)>& am) {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
struct DefPortal {
|
||||
DefPortal() = default;
|
||||
DefPortal(const std::u8string_view view) {
|
||||
|
||||
}
|
||||
|
||||
void reBind(const std::function<ResourceId(EnumAssets, ResourceId)>& am) {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
struct DefEntity {
|
||||
DefEntity() = default;
|
||||
DefEntity(const std::u8string_view view) {
|
||||
|
||||
}
|
||||
|
||||
void reBind(const std::function<ResourceId(EnumAssets, ResourceId)>& am) {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
struct DefItem {
|
||||
DefItem() = default;
|
||||
DefItem(const std::u8string_view view) {
|
||||
|
||||
}
|
||||
|
||||
void reBind(const std::function<ResourceId(EnumAssets, ResourceId)>& am) {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
struct AssetEntry {
|
||||
ResourceId Id = 0;
|
||||
std::string Domain;
|
||||
std::string Key;
|
||||
|
||||
HeadlessModel Model;
|
||||
HeadlessModel::Header ModelHeader;
|
||||
|
||||
HeadlessNodeState Nodestate;
|
||||
HeadlessNodeState::Header NodestateHeader;
|
||||
|
||||
uint16_t Width = 0;
|
||||
uint16_t Height = 0;
|
||||
std::vector<uint32_t> Pixels;
|
||||
ResourceHeader Header;
|
||||
|
||||
std::u8string Data;
|
||||
};
|
||||
|
||||
/*
|
||||
Интерфейс обработчика сессии с сервером.
|
||||
|
||||
Данный здесь меняются только меж вызовами
|
||||
IRenderSession::pushStageTickSync
|
||||
и
|
||||
IRenderSession::tickSync
|
||||
*/
|
||||
/* Интерфейс обработчика сессии с сервером */
|
||||
class IServerSession {
|
||||
public:
|
||||
// Включить логирование входящих сетевых пакетов на клиенте.
|
||||
bool DebugLogPackets = false;
|
||||
|
||||
// Используемые профили контента
|
||||
struct {
|
||||
std::unordered_map<DefVoxelId, DefVoxel> DefVoxels;
|
||||
std::unordered_map<DefNodeId, DefNode> DefNodes;
|
||||
std::unordered_map<DefWorldId, DefWorld> DefWorlds;
|
||||
std::unordered_map<DefPortalId, DefPortal> DefPortals;
|
||||
std::unordered_map<DefEntityId, DefEntity> DefEntitys;
|
||||
std::unordered_map<DefItemId, DefItem> DefItems;
|
||||
} Profiles;
|
||||
std::unordered_map<DefWorldId_c, DefWorldInfo> DefWorlds;
|
||||
std::unordered_map<DefVoxelId_c, VoxelInfo> DefVoxels;
|
||||
std::unordered_map<DefNodeId_c, NodeInfo> DefNodes;
|
||||
std::unordered_map<DefPortalId_c, DefPortalInfo> DefPortals;
|
||||
std::unordered_map<DefEntityId_c, DefEntityInfo> DefEntityes;
|
||||
|
||||
std::unordered_map<WorldId_c, WorldInfo> Worlds;
|
||||
std::unordered_map<PortalId_c, PortalInfo> Portals;
|
||||
std::unordered_map<EntityId_c, EntityInfo> Entityes;
|
||||
} Registry;
|
||||
|
||||
// Видимый контент
|
||||
struct {
|
||||
std::unordered_map<WorldId_t, WorldInfo> Worlds;
|
||||
// std::unordered_map<PortalId_t, PortalInfo> Portals;
|
||||
std::unordered_map<EntityId_t, EntityInfo> Entityes;
|
||||
} Content;
|
||||
std::unordered_map<WorldId_c, World> Worlds;
|
||||
} External;
|
||||
|
||||
virtual ~IServerSession();
|
||||
|
||||
// Обновление сессии с сервером, может начатся стадия IRenderSession::tickSync
|
||||
virtual void update(GlobalTime gTime, float dTime) = 0;
|
||||
virtual void atFreeDrawTime(GlobalTime gTime, float dTime) = 0;
|
||||
};
|
||||
|
||||
|
||||
@@ -301,7 +166,7 @@ public:
|
||||
} CursorMode = EnumCursorMoveMode::Default;
|
||||
|
||||
enum struct EnumCursorBtn {
|
||||
Left, Right, Middle, One, Two
|
||||
Left, Middle, Right, One, Two
|
||||
};
|
||||
|
||||
public:
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -1,638 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <cstring>
|
||||
#include "Client/AssetsCacheManager.hpp"
|
||||
#include "Client/AssetsHeaderCodec.hpp"
|
||||
#include "Common/Abstract.hpp"
|
||||
#include "Common/IdProvider.hpp"
|
||||
#include "Common/AssetsPreloader.hpp"
|
||||
#include "Common/TexturePipelineProgram.hpp"
|
||||
#include "TOSLib.hpp"
|
||||
#include "assets.hpp"
|
||||
#include "boost/asio/io_context.hpp"
|
||||
#include "png++/image.hpp"
|
||||
#include <fstream>
|
||||
#include "Abstract.hpp"
|
||||
|
||||
namespace LV::Client {
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
class AssetsManager : public IdProvider<EnumAssets> {
|
||||
public:
|
||||
struct ResourceUpdates {
|
||||
|
||||
std::vector<AssetsModelUpdate> Models;
|
||||
std::vector<AssetsNodestateUpdate> Nodestates;
|
||||
std::vector<AssetsTextureUpdate> Textures;
|
||||
std::vector<AssetsBinaryUpdate> Particles;
|
||||
std::vector<AssetsBinaryUpdate> Animations;
|
||||
std::vector<AssetsBinaryUpdate> Sounds;
|
||||
std::vector<AssetsBinaryUpdate> Fonts;
|
||||
};
|
||||
|
||||
public:
|
||||
AssetsManager(asio::io_context& ioc, fs::path cachePath)
|
||||
: Cache(AssetsCacheManager::Create(ioc, cachePath))
|
||||
{
|
||||
for(size_t type = 0; type < static_cast<size_t>(EnumAssets::MAX_ENUM); type++) {
|
||||
ServerToClientMap[type].push_back(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Ручные обновления
|
||||
struct Out_checkAndPrepareResourcesUpdate {
|
||||
AssetsPreloader::Out_checkAndPrepareResourcesUpdate RP, ES;
|
||||
|
||||
std::unordered_map<ResourceFile::Hash_t, std::u8string> Files;
|
||||
};
|
||||
|
||||
Out_checkAndPrepareResourcesUpdate checkAndPrepareResourcesUpdate(
|
||||
const std::vector<fs::path>& resourcePacks,
|
||||
const std::vector<fs::path>& extraSources
|
||||
) {
|
||||
Out_checkAndPrepareResourcesUpdate result;
|
||||
|
||||
result.RP = ResourcePacks.checkAndPrepareResourcesUpdate(
|
||||
AssetsPreloader::AssetsRegister{resourcePacks},
|
||||
[&](EnumAssets type, std::string_view domain, std::string_view key) -> ResourceId {
|
||||
return getId(type, domain, key);
|
||||
},
|
||||
[&](std::u8string&& data, ResourceFile::Hash_t hash, fs::path path) {
|
||||
result.Files.emplace(hash, std::move(data));
|
||||
}
|
||||
);
|
||||
|
||||
result.ES = ExtraSource.checkAndPrepareResourcesUpdate(
|
||||
AssetsPreloader::AssetsRegister{resourcePacks},
|
||||
[&](EnumAssets type, std::string_view domain, std::string_view key) -> ResourceId {
|
||||
return getId(type, domain, key);
|
||||
}
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
struct Out_applyResourcesUpdate {
|
||||
|
||||
};
|
||||
|
||||
Out_applyResourcesUpdate applyResourcesUpdate(const Out_checkAndPrepareResourcesUpdate& orr) {
|
||||
Out_applyResourcesUpdate result;
|
||||
|
||||
for(size_t type = 0; type < static_cast<size_t>(EnumAssets::MAX_ENUM); ++type) {
|
||||
for(ResourceId id : orr.RP.LostLinks[type]) {
|
||||
std::optional<AssetsPreloader::Out_Resource> res = ResourcePacks.getResource((EnumAssets) type, id);
|
||||
assert(res);
|
||||
|
||||
auto hashIter = HashToPath.find(res->Hash);
|
||||
assert(hashIter != HashToPath.end());
|
||||
auto& entry = hashIter->second;
|
||||
auto iter = std::find(entry.begin(), entry.end(), res->Path);
|
||||
assert(iter != entry.end());
|
||||
entry.erase(iter);
|
||||
|
||||
if(entry.empty())
|
||||
HashToPath.erase(hashIter);
|
||||
}
|
||||
}
|
||||
|
||||
ResourcePacks.applyResourcesUpdate(orr.RP);
|
||||
ExtraSource.applyResourcesUpdate(orr.ES);
|
||||
|
||||
std::unordered_set<ResourceFile::Hash_t> needHashes;
|
||||
|
||||
for(size_t type = 0; type < static_cast<size_t>(EnumAssets::MAX_ENUM); ++type) {
|
||||
for(const auto& res : orr.RP.ResourceUpdates[type]) {
|
||||
// Помечаем ресурс для обновления
|
||||
PendingUpdateFromAsync[type].push_back(std::get<ResourceId>(res));
|
||||
HashToPath[std::get<ResourceFile::Hash_t>(res)].push_back(std::get<fs::path>(res));
|
||||
}
|
||||
|
||||
for(ResourceId id : orr.RP.LostLinks[type]) {
|
||||
// Помечаем ресурс для обновления
|
||||
PendingUpdateFromAsync[type].push_back(id);
|
||||
|
||||
auto& hh = ServerIdToHH[type];
|
||||
if(id < hh.size())
|
||||
needHashes.insert(std::get<ResourceFile::Hash_t>(hh[id]));
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
for(const auto& [hash, data] : orr.Files) {
|
||||
WaitingHashes.insert(hash);
|
||||
}
|
||||
|
||||
for(const auto& hash : WaitingHashes)
|
||||
needHashes.erase(hash);
|
||||
|
||||
std::vector<std::tuple<ResourceFile::Hash_t, fs::path>> toDisk;
|
||||
std::vector<ResourceFile::Hash_t> toCache;
|
||||
|
||||
// Теперь раскидаем хеши по доступным источникам.
|
||||
for(const auto& hash : needHashes) {
|
||||
auto iter = HashToPath.find(hash);
|
||||
if(iter != HashToPath.end()) {
|
||||
// Ставим задачу загрузить с диска.
|
||||
toDisk.emplace_back(hash, iter->second.front());
|
||||
} else {
|
||||
// Сделаем запрос в кеш.
|
||||
toCache.push_back(hash);
|
||||
}
|
||||
}
|
||||
|
||||
// Запоминаем, что эти ресурсы уже ожидаются.
|
||||
WaitingHashes.insert_range(needHashes);
|
||||
|
||||
// Запрос в кеш (если там не найдётся, то запрос уйдёт на сервер).
|
||||
if(!toCache.empty())
|
||||
Cache->pushReads(std::move(toCache));
|
||||
|
||||
// Запрос к диску.
|
||||
if(!toDisk.empty())
|
||||
NeedToReadFromDisk.append_range(std::move(toDisk));
|
||||
|
||||
_onHashLoad(orr.Files);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// ServerSession
|
||||
// Новые привязки ассетов к Домен+Ключ.
|
||||
void pushAssetsBindDK(
|
||||
const std::vector<std::string>& domains,
|
||||
const std::array<
|
||||
std::vector<std::vector<std::string>>,
|
||||
static_cast<size_t>(EnumAssets::MAX_ENUM)
|
||||
>& keys
|
||||
) {
|
||||
LOG.debug() << "BindDK domains=" << domains.size();
|
||||
for(size_t type = 0; type < static_cast<size_t>(EnumAssets::MAX_ENUM); ++type) {
|
||||
LOG.info() << type;
|
||||
for(size_t forDomainIter = 0; forDomainIter < keys[type].size(); ++forDomainIter) {
|
||||
LOG.info() << "\t" << domains[forDomainIter];
|
||||
for(const std::string& key : keys[type][forDomainIter]) {
|
||||
uint32_t id = getId((EnumAssets) type, domains[forDomainIter], key);
|
||||
LOG.info() << "\t\t" << key << " -> " << id;
|
||||
ServerToClientMap[type].push_back(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Новые привязки ассетов к Hash+Header.
|
||||
void pushAssetsBindHH(
|
||||
std::array<
|
||||
std::vector<std::tuple<ResourceId, ResourceFile::Hash_t, ResourceHeader>>,
|
||||
static_cast<size_t>(EnumAssets::MAX_ENUM)
|
||||
>&& hash_and_headers
|
||||
) {
|
||||
std::unordered_set<ResourceFile::Hash_t> needHashes;
|
||||
|
||||
size_t totalBinds = 0;
|
||||
for(size_t type = 0; type < static_cast<size_t>(EnumAssets::MAX_ENUM); ++type) {
|
||||
size_t maxSize = 0;
|
||||
|
||||
for(auto& [id, hash, header] : hash_and_headers[type]) {
|
||||
totalBinds++;
|
||||
assert(id < ServerToClientMap[type].size());
|
||||
id = ServerToClientMap[type][id];
|
||||
|
||||
if(id >= maxSize)
|
||||
maxSize = id+1;
|
||||
|
||||
// Добавляем идентификатор в таблицу ожидающих обновлений.
|
||||
PendingUpdateFromAsync[type].push_back(id);
|
||||
|
||||
// Поискать есть ли ресурс в ресурспаках.
|
||||
std::optional<AssetsPreloader::Out_Resource> res = ResourcePacks.getResource((EnumAssets) type, id);
|
||||
if(res) {
|
||||
needHashes.insert(res->Hash);
|
||||
} else {
|
||||
needHashes.insert(hash);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// Уберём повторения в идентификаторах.
|
||||
auto& vec = PendingUpdateFromAsync[type];
|
||||
std::sort(vec.begin(), vec.end());
|
||||
vec.erase(std::unique(vec.begin(), vec.end()), vec.end());
|
||||
}
|
||||
|
||||
if(ServerIdToHH[type].size() < maxSize)
|
||||
ServerIdToHH[type].resize(maxSize);
|
||||
|
||||
for(auto& [id, hash, header] : hash_and_headers[type]) {
|
||||
ServerIdToHH[type][id] = {hash, std::move(header)};
|
||||
}
|
||||
}
|
||||
|
||||
if(totalBinds)
|
||||
LOG.debug() << "BindHH total=" << totalBinds << " wait=" << WaitingHashes.size();
|
||||
|
||||
// Нужно убрать хеши, которые уже запрошены
|
||||
// needHashes ^ WaitingHashes.
|
||||
|
||||
for(const auto& hash : WaitingHashes)
|
||||
needHashes.erase(hash);
|
||||
|
||||
std::vector<std::tuple<ResourceFile::Hash_t, fs::path>> toDisk;
|
||||
std::vector<ResourceFile::Hash_t> toCache;
|
||||
|
||||
// Теперь раскидаем хеши по доступным источникам.
|
||||
for(const auto& hash : needHashes) {
|
||||
auto iter = HashToPath.find(hash);
|
||||
if(iter != HashToPath.end()) {
|
||||
// Ставим задачу загрузить с диска.
|
||||
toDisk.emplace_back(hash, iter->second.front());
|
||||
} else {
|
||||
// Сделаем запрос в кеш.
|
||||
toCache.push_back(hash);
|
||||
}
|
||||
}
|
||||
|
||||
// Запоминаем, что эти ресурсы уже ожидаются.
|
||||
WaitingHashes.insert_range(needHashes);
|
||||
|
||||
// Запрос к диску.
|
||||
if(!toDisk.empty())
|
||||
NeedToReadFromDisk.append_range(std::move(toDisk));
|
||||
|
||||
// Запрос в кеш (если там не найдётся, то запрос уйдёт на сервер).
|
||||
if(!toCache.empty())
|
||||
Cache->pushReads(std::move(toCache));
|
||||
}
|
||||
|
||||
// Новые ресурсы, полученные с сервера.
|
||||
void pushNewResources(
|
||||
std::vector<std::tuple<ResourceFile::Hash_t, std::u8string>> &&resources
|
||||
) {
|
||||
std::unordered_map<ResourceFile::Hash_t, std::u8string> files;
|
||||
std::vector<Resource> vec;
|
||||
files.reserve(resources.size());
|
||||
vec.reserve(resources.size());
|
||||
|
||||
for(auto& [hash, res] : resources) {
|
||||
vec.emplace_back(res);
|
||||
files.emplace(hash, std::move(res));
|
||||
}
|
||||
|
||||
_onHashLoad(files);
|
||||
Cache->pushResources(std::move(vec));
|
||||
}
|
||||
|
||||
// Для запроса отсутствующих ресурсов с сервера на клиент.
|
||||
std::vector<ResourceFile::Hash_t> pullNeededResources() {
|
||||
return std::move(NeedToRequestFromServer);
|
||||
}
|
||||
|
||||
// Получить изменённые ресурсы (для передачи другим модулям).
|
||||
ResourceUpdates pullResourceUpdates() {
|
||||
return std::move(RU);
|
||||
}
|
||||
|
||||
ResourceId reBind(EnumAssets type, ResourceId server) {
|
||||
return ServerToClientMap[static_cast<size_t>(type)].at(server);
|
||||
}
|
||||
|
||||
void tick() {
|
||||
// Проверим кеш
|
||||
std::vector<std::pair<Hash_t, std::optional<Resource>>> resources = Cache->pullReads();
|
||||
if(!resources.empty()) {
|
||||
std::unordered_map<ResourceFile::Hash_t, std::u8string> needToProceed;
|
||||
needToProceed.reserve(resources.size());
|
||||
|
||||
for(auto& [hash, res] : resources) {
|
||||
if(!res)
|
||||
NeedToRequestFromServer.push_back(hash);
|
||||
else
|
||||
needToProceed.emplace(hash, std::u8string{(const char8_t*) res->data(), res->size()});
|
||||
}
|
||||
|
||||
if(!NeedToRequestFromServer.empty())
|
||||
LOG.debug() << "CacheMiss count=" << NeedToRequestFromServer.size();
|
||||
|
||||
if(!needToProceed.empty())
|
||||
_onHashLoad(needToProceed);
|
||||
}
|
||||
|
||||
/// Читаем с диска TODO: получилась хрень с определением типа, чтобы получать headless ресурс
|
||||
if(!NeedToReadFromDisk.empty()) {
|
||||
std::unordered_map<ResourceFile::Hash_t, std::u8string> files;
|
||||
files.reserve(NeedToReadFromDisk.size());
|
||||
|
||||
auto detectTypeDomainKey = [&](const fs::path& path, EnumAssets& typeOut, std::string& domainOut, std::string& keyOut) -> bool {
|
||||
fs::path cur = path.parent_path();
|
||||
for(; !cur.empty(); cur = cur.parent_path()) {
|
||||
std::string name = cur.filename().string();
|
||||
for(size_t typeIndex = 0; typeIndex < static_cast<size_t>(EnumAssets::MAX_ENUM); ++typeIndex) {
|
||||
EnumAssets type = static_cast<EnumAssets>(typeIndex);
|
||||
if(name == ::EnumAssetsToDirectory(type)) {
|
||||
typeOut = type;
|
||||
domainOut = cur.parent_path().filename().string();
|
||||
keyOut = fs::relative(path, cur).generic_string();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
for(const auto& [hash, path] : NeedToReadFromDisk) {
|
||||
std::u8string data;
|
||||
std::ifstream file(path, std::ios::binary);
|
||||
if(file) {
|
||||
file.seekg(0, std::ios::end);
|
||||
std::streamoff size = file.tellg();
|
||||
if(size < 0)
|
||||
size = 0;
|
||||
file.seekg(0, std::ios::beg);
|
||||
data.resize(static_cast<size_t>(size));
|
||||
if(size > 0) {
|
||||
file.read(reinterpret_cast<char*>(data.data()), size);
|
||||
if(!file)
|
||||
data.clear();
|
||||
}
|
||||
} else {
|
||||
LOG.warn() << "DiskReadFail " << path.string();
|
||||
}
|
||||
|
||||
if(!data.empty()) {
|
||||
EnumAssets type{};
|
||||
std::string domain;
|
||||
std::string key;
|
||||
if(detectTypeDomainKey(path, type, domain, key)) {
|
||||
if(type == EnumAssets::Nodestate) {
|
||||
std::string_view view(reinterpret_cast<const char*>(data.data()), data.size());
|
||||
js::object obj = js::parse(view).as_object();
|
||||
HeadlessNodeState hns;
|
||||
auto modelResolver = [&](const std::string_view model) -> AssetsModel {
|
||||
auto [mDomain, mKey] = parseDomainKey(model, domain);
|
||||
return getId(EnumAssets::Model, mDomain, mKey);
|
||||
};
|
||||
hns.parse(obj, modelResolver);
|
||||
data = hns.dump();
|
||||
} else if(type == EnumAssets::Model) {
|
||||
std::string_view view(reinterpret_cast<const char*>(data.data()), data.size());
|
||||
js::object obj = js::parse(view).as_object();
|
||||
HeadlessModel hm;
|
||||
auto modelResolver = [&](const std::string_view model) -> AssetsModel {
|
||||
auto [mDomain, mKey] = parseDomainKey(model, domain);
|
||||
return getId(EnumAssets::Model, mDomain, mKey);
|
||||
};
|
||||
auto textureIdResolver = [&](const std::string_view texture) -> std::optional<uint32_t> {
|
||||
auto [tDomain, tKey] = parseDomainKey(texture, domain);
|
||||
return getId(EnumAssets::Texture, tDomain, tKey);
|
||||
};
|
||||
auto textureResolver = [&](const std::string_view texturePipelineSrc) -> std::vector<uint8_t> {
|
||||
TexturePipelineProgram tpp;
|
||||
if(!tpp.compile(texturePipelineSrc))
|
||||
return {};
|
||||
tpp.link(textureIdResolver);
|
||||
return tpp.toBytes();
|
||||
};
|
||||
hm.parse(obj, modelResolver, textureResolver);
|
||||
data = hm.dump();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
files.emplace(hash, std::move(data));
|
||||
}
|
||||
|
||||
NeedToReadFromDisk.clear();
|
||||
_onHashLoad(files);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
Logger LOG = "Client>AssetsManager";
|
||||
|
||||
// Менеджеры учёта дисковых ресурсов
|
||||
AssetsPreloader
|
||||
// В приоритете ищутся ресурсы из ресурспаков по Domain+Key.
|
||||
ResourcePacks,
|
||||
/*
|
||||
Дополнительные источники ресурсов.
|
||||
Используется для поиска ресурса по хешу от сервера (может стоит тот же мод с совпадающими ресурсами),
|
||||
или для временной подгрузки ресурса по Domain+Key пока ресурс не был получен с сервера.
|
||||
*/
|
||||
ExtraSource;
|
||||
|
||||
// Менеджер файлового кэша.
|
||||
AssetsCacheManager::Ptr Cache;
|
||||
|
||||
// Указатели на доступные ресурсы
|
||||
std::unordered_map<ResourceFile::Hash_t, std::vector<fs::path>> HashToPath;
|
||||
|
||||
// Таблица релинковки ассетов с идентификаторов сервера на клиентские.
|
||||
std::array<
|
||||
std::vector<ResourceId>,
|
||||
static_cast<size_t>(EnumAssets::MAX_ENUM)
|
||||
> ServerToClientMap;
|
||||
|
||||
// Таблица серверных привязок HH (id клиентские)
|
||||
std::array<
|
||||
std::vector<std::tuple<ResourceFile::Hash_t, ResourceHeader>>,
|
||||
static_cast<size_t>(EnumAssets::MAX_ENUM)
|
||||
> ServerIdToHH;
|
||||
|
||||
// Ресурсы в ожидании данных по хешу для обновления (с диска, кеша, сервера).
|
||||
std::array<
|
||||
std::vector<ResourceId>,
|
||||
static_cast<size_t>(EnumAssets::MAX_ENUM)
|
||||
> PendingUpdateFromAsync;
|
||||
|
||||
// Хеши, для которых где-то висит задача на загрузку.
|
||||
std::unordered_set<ResourceFile::Hash_t> WaitingHashes;
|
||||
|
||||
// Хеши, которые необходимо запросить с сервера.
|
||||
std::vector<ResourceFile::Hash_t> NeedToRequestFromServer;
|
||||
|
||||
// Ресурсы, которые нужно считать с диска
|
||||
std::vector<std::tuple<ResourceFile::Hash_t, fs::path>> NeedToReadFromDisk;
|
||||
|
||||
// Обновлённые ресурсы
|
||||
ResourceUpdates RU;
|
||||
|
||||
// Когда данные были получены с диска, кеша или сервера
|
||||
void _onHashLoad(const std::unordered_map<ResourceFile::Hash_t, std::u8string>& files) {
|
||||
const auto& rpLinks = ResourcePacks.getResourceLinks();
|
||||
const auto& esLinks = ExtraSource.getResourceLinks();
|
||||
|
||||
auto mapModelId = [&](ResourceId id) -> ResourceId {
|
||||
const auto& map = ServerToClientMap[static_cast<size_t>(EnumAssets::Model)];
|
||||
if(id >= map.size())
|
||||
return 0;
|
||||
|
||||
return map[id];
|
||||
};
|
||||
auto mapTextureId = [&](ResourceId id) -> ResourceId {
|
||||
const auto& map = ServerToClientMap[static_cast<size_t>(EnumAssets::Texture)];
|
||||
if(id >= map.size())
|
||||
return 0;
|
||||
|
||||
return map[id];
|
||||
};
|
||||
auto rebindHeader = [&](EnumAssets type, const ResourceHeader& header) -> ResourceHeader {
|
||||
if(header.empty())
|
||||
return {};
|
||||
|
||||
std::vector<uint8_t> bytes;
|
||||
bytes.resize(header.size());
|
||||
std::memcpy(bytes.data(), header.data(), header.size());
|
||||
std::vector<uint8_t> rebound = AssetsHeaderCodec::rebindHeader(
|
||||
type,
|
||||
bytes,
|
||||
mapModelId,
|
||||
mapTextureId,
|
||||
[](const std::string&) {}
|
||||
);
|
||||
|
||||
return ResourceHeader(reinterpret_cast<const char8_t*>(rebound.data()), rebound.size());
|
||||
};
|
||||
|
||||
for(size_t typeIndex = 0; typeIndex < static_cast<size_t>(EnumAssets::MAX_ENUM); ++typeIndex) {
|
||||
auto& pending = PendingUpdateFromAsync[typeIndex];
|
||||
if(pending.empty())
|
||||
continue;
|
||||
|
||||
std::vector<ResourceId> stillPending;
|
||||
stillPending.reserve(pending.size());
|
||||
size_t updated = 0;
|
||||
size_t missingSource = 0;
|
||||
size_t missingData = 0;
|
||||
|
||||
for(ResourceId id : pending) {
|
||||
ResourceFile::Hash_t hash{};
|
||||
ResourceHeader header;
|
||||
bool hasSource = false;
|
||||
bool localHeader = false;
|
||||
|
||||
if(id < rpLinks[typeIndex].size() && rpLinks[typeIndex][id].IsExist) {
|
||||
hash = rpLinks[typeIndex][id].Hash;
|
||||
header = rpLinks[typeIndex][id].Header;
|
||||
hasSource = true;
|
||||
localHeader = true;
|
||||
} else if(id < ServerIdToHH[typeIndex].size()) {
|
||||
std::tie(hash, header) = ServerIdToHH[typeIndex][id];
|
||||
hasSource = true;
|
||||
}
|
||||
|
||||
if(!hasSource) {
|
||||
missingSource++;
|
||||
stillPending.push_back(id);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto dataIter = files.find(hash);
|
||||
if(dataIter == files.end()) {
|
||||
missingData++;
|
||||
stillPending.push_back(id);
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string domain = "core";
|
||||
std::string key;
|
||||
{
|
||||
auto d = getDK((EnumAssets) typeIndex, id);
|
||||
if(d) {
|
||||
domain = d->Domain;
|
||||
key = d->Key;
|
||||
}
|
||||
}
|
||||
|
||||
std::u8string data = dataIter->second;
|
||||
EnumAssets type = static_cast<EnumAssets>(typeIndex);
|
||||
ResourceHeader finalHeader = localHeader ? header : rebindHeader(type, header);
|
||||
|
||||
if(id == 0)
|
||||
continue;
|
||||
|
||||
if(type == EnumAssets::Nodestate) {
|
||||
HeadlessNodeState ns;
|
||||
ns.load(data);
|
||||
HeadlessNodeState::Header headerParsed;
|
||||
headerParsed.load(finalHeader);
|
||||
RU.Nodestates.push_back({id, std::move(ns), std::move(headerParsed)});
|
||||
updated++;
|
||||
} else if(type == EnumAssets::Model) {
|
||||
HeadlessModel hm;
|
||||
hm.load(data);
|
||||
HeadlessModel::Header headerParsed;
|
||||
headerParsed.load(finalHeader);
|
||||
RU.Models.push_back({id, std::move(hm), std::move(headerParsed)});
|
||||
updated++;
|
||||
} else if(type == EnumAssets::Texture) {
|
||||
AssetsTextureUpdate entry;
|
||||
entry.Id = id;
|
||||
entry.Header = std::move(finalHeader);
|
||||
if(!data.empty()) {
|
||||
iResource sres(reinterpret_cast<const uint8_t*>(data.data()), data.size());
|
||||
iBinaryStream stream = sres.makeStream();
|
||||
png::image<png::rgba_pixel> img(stream.Stream);
|
||||
entry.Width = static_cast<uint16_t>(img.get_width());
|
||||
entry.Height = static_cast<uint16_t>(img.get_height());
|
||||
entry.Pixels.resize(static_cast<size_t>(entry.Width) * entry.Height);
|
||||
for(uint32_t y = 0; y < entry.Height; ++y) {
|
||||
const auto& row = img.get_pixbuf().operator[](y);
|
||||
for(uint32_t x = 0; x < entry.Width; ++x) {
|
||||
const auto& px = row[x];
|
||||
uint32_t rgba = (uint32_t(px.alpha) << 24)
|
||||
| (uint32_t(px.red) << 16)
|
||||
| (uint32_t(px.green) << 8)
|
||||
| uint32_t(px.blue);
|
||||
entry.Pixels[x + y * entry.Width] = rgba;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RU.Textures.push_back(std::move(entry));
|
||||
updated++;
|
||||
} else if(type == EnumAssets::Particle) {
|
||||
RU.Particles.push_back({id, std::move(data)});
|
||||
updated++;
|
||||
} else if(type == EnumAssets::Animation) {
|
||||
RU.Animations.push_back({id, std::move(data)});
|
||||
updated++;
|
||||
} else if(type == EnumAssets::Sound) {
|
||||
RU.Sounds.push_back({id, std::move(data)});
|
||||
updated++;
|
||||
} else if(type == EnumAssets::Font) {
|
||||
RU.Fonts.push_back({id, std::move(data)});
|
||||
updated++;
|
||||
}
|
||||
}
|
||||
|
||||
if(updated || missingSource || missingData) {
|
||||
LOG.debug() << "HashLoad type=" << int(typeIndex)
|
||||
<< " updated=" << updated
|
||||
<< " missingSource=" << missingSource
|
||||
<< " missingData=" << missingData;
|
||||
}
|
||||
|
||||
pending = std::move(stillPending);
|
||||
}
|
||||
|
||||
for(const auto& [hash, res] : files)
|
||||
WaitingHashes.erase(hash);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace LV::Client
|
||||
@@ -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);
|
||||
}
|
||||
@@ -6,167 +6,35 @@
|
||||
#include "Common/Lockable.hpp"
|
||||
#include "Common/Net.hpp"
|
||||
#include "Common/Packets.hpp"
|
||||
#include "TOSAsync.hpp"
|
||||
#include <TOSLib.hpp>
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <boost/lockfree/spsc_queue.hpp>
|
||||
#include <Client/AssetsManager.hpp>
|
||||
#include <queue>
|
||||
#include <unordered_map>
|
||||
|
||||
|
||||
namespace LV::Client {
|
||||
|
||||
class ServerSession : public IAsyncDestructible, public IServerSession, public ISurfaceEventListener {
|
||||
public:
|
||||
using Ptr = std::shared_ptr<ServerSession>;
|
||||
struct ParsedPacket {
|
||||
ToClient::L1 Level1;
|
||||
uint8_t Level2;
|
||||
|
||||
public:
|
||||
static Ptr Create(asio::io_context &ioc, std::unique_ptr<Net::AsyncSocket> &&socket) {
|
||||
return createShared(ioc, new ServerSession(ioc, std::move(socket)));
|
||||
}
|
||||
|
||||
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";
|
||||
ParsedPacket(ToClient::L1 l1, uint8_t l2)
|
||||
: Level1(l1), Level2(l2)
|
||||
{}
|
||||
virtual ~ParsedPacket();
|
||||
};
|
||||
|
||||
class ServerSession : public IServerSession, public ISurfaceEventListener {
|
||||
asio::io_context &IOC;
|
||||
std::unique_ptr<Net::AsyncSocket> Socket;
|
||||
IRenderSession *RS = nullptr;
|
||||
|
||||
// Обработчик кеша ресурсов сервера
|
||||
AssetsManager 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::u8string Data;
|
||||
size_t Offset = 0;
|
||||
};
|
||||
|
||||
struct AssetBindEntry {
|
||||
EnumAssets Type;
|
||||
ResourceId Id;
|
||||
std::string Domain, Key;
|
||||
Hash_t Hash;
|
||||
std::vector<uint8_t> Header;
|
||||
};
|
||||
|
||||
struct UpdateAssetsBindsDK {
|
||||
std::vector<std::string> Domains;
|
||||
std::array<
|
||||
std::vector<std::vector<std::string>>,
|
||||
static_cast<size_t>(EnumAssets::MAX_ENUM)
|
||||
> Keys;
|
||||
};
|
||||
|
||||
struct UpdateAssetsBindsHH {
|
||||
std::array<
|
||||
std::vector<std::tuple<ResourceId, ResourceFile::Hash_t, ResourceHeader>>,
|
||||
static_cast<size_t>(EnumAssets::MAX_ENUM)
|
||||
> HashAndHeaders;
|
||||
};
|
||||
|
||||
struct TickData {
|
||||
// Полученные изменения привязок Domain+Key
|
||||
std::vector<UpdateAssetsBindsDK> BindsDK;
|
||||
// Полученные изменения привязок Hash+Header
|
||||
std::vector<UpdateAssetsBindsHH> BindsHH;
|
||||
// Потерянные привязываются к hash_t(0)
|
||||
// Полученные с сервера ресурсы
|
||||
std::vector<std::tuple<ResourceFile::Hash_t, std::u8string>> ReceivedAssets;
|
||||
|
||||
std::vector<std::pair<DefVoxelId, DefVoxel>> Profile_Voxel_AddOrChange;
|
||||
std::vector<DefVoxelId> Profile_Voxel_Lost;
|
||||
std::vector<std::pair<DefNodeId, DefNode>> Profile_Node_AddOrChange;
|
||||
std::vector<DefNodeId> Profile_Node_Lost;
|
||||
std::vector<std::pair<DefWorldId, DefWorld>> Profile_World_AddOrChange;
|
||||
std::vector<DefWorldId> Profile_World_Lost;
|
||||
std::vector<std::pair<DefPortalId, DefPortal>> Profile_Portal_AddOrChange;
|
||||
std::vector<DefPortalId> Profile_Portal_Lost;
|
||||
std::vector<std::pair<DefEntityId, DefEntity>> Profile_Entity_AddOrChange;
|
||||
std::vector<DefEntityId> Profile_Entity_Lost;
|
||||
std::vector<std::pair<DefItemId, DefItem>> 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 {
|
||||
// Сюда обращается ветка, обрабатывающая сокет; run()
|
||||
// Получение ресурсов с сервера
|
||||
std::unordered_map<Hash_t, AssetLoading> AssetsLoading;
|
||||
// Накопление данных за такт сервера
|
||||
TickData ThisTickEntry;
|
||||
|
||||
// Обменный пункт
|
||||
// Пакеты обновлений игрового мира
|
||||
TOS::SpinlockObject<std::vector<TickData>> TickSequence;
|
||||
} AsyncContext;
|
||||
|
||||
|
||||
|
||||
DestroyLock UseLock;
|
||||
bool IsConnected = true, IsGoingShutdown = false;
|
||||
|
||||
TOS::Logger LOG = "ServerSession";
|
||||
|
||||
boost::lockfree::spsc_queue<ParsedPacket*> NetInputPackets;
|
||||
|
||||
// PYR - поворот камеры по осям xyz в радианах, PYR_Offset для сглаживание поворота
|
||||
glm::vec3 PYR = glm::vec3(0), PYR_Offset = glm::vec3(0);
|
||||
double PYR_At = 0;
|
||||
@@ -186,34 +54,54 @@ private:
|
||||
|
||||
GlobalTime LastSendPYR_POS;
|
||||
|
||||
std::unordered_map<WorldId_t, std::unordered_map<Pos::GlobalChunk, ChunkCompressedSize>> VisibleChunkCompressed;
|
||||
uint64_t VisibleChunkCompressedBytes = 0;
|
||||
public:
|
||||
// Нужен сокет, на котором только что был согласован игровой протокол (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);
|
||||
}
|
||||
|
||||
// Приём данных с сокета
|
||||
coro<> run(AsyncUseControl::Lock);
|
||||
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);
|
||||
|
||||
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();
|
||||
coro<> readPacket(Net::AsyncSocket &sock);
|
||||
coro<> rP_Disconnect(Net::AsyncSocket &sock);
|
||||
coro<> rP_AssetsBindDK(Net::AsyncSocket &sock);
|
||||
coro<> rP_AssetsBindHH(Net::AsyncSocket &sock);
|
||||
coro<> rP_AssetsInitSend(Net::AsyncSocket &sock);
|
||||
coro<> rP_AssetsNextSend(Net::AsyncSocket &sock);
|
||||
coro<> rP_DefinitionsFull(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();
|
||||
coro<> rP_System(Net::AsyncSocket &sock);
|
||||
coro<> rP_Resource(Net::AsyncSocket &sock);
|
||||
coro<> rP_Definition(Net::AsyncSocket &sock);
|
||||
coro<> rP_Content(Net::AsyncSocket &sock);
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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});
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
@@ -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 = 18ull * 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;
|
||||
};
|
||||
@@ -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, ®ion);
|
||||
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{};
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "Common/TexturePipelineProgram.hpp"
|
||||
@@ -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, ©Region);
|
||||
}
|
||||
|
||||
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>;
|
||||
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
@@ -8,7 +7,6 @@
|
||||
#include "Client/ServerSession.hpp"
|
||||
#include "Common/Async.hpp"
|
||||
#include "Common/Net.hpp"
|
||||
#include "TOSLib.hpp"
|
||||
#include "assets.hpp"
|
||||
#include "imgui.h"
|
||||
#include <GLFW/glfw3.h>
|
||||
@@ -29,33 +27,17 @@
|
||||
#include <png++/png.hpp>
|
||||
#include "VulkanRenderSession.hpp"
|
||||
#include <Server/GameServer.hpp>
|
||||
#include <malloc.h>
|
||||
|
||||
extern void LoadSymbolsVulkan(TOS::DynamicLibrary &library);
|
||||
|
||||
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 {
|
||||
Server::GameServer GS;
|
||||
Net::SocketServer LS;
|
||||
|
||||
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)
|
||||
{
|
||||
}
|
||||
};
|
||||
@@ -91,7 +73,7 @@ ByteBuffer loadPNG(std::istream &&read, int &width, int &height, bool &hasAlpha,
|
||||
}
|
||||
|
||||
Vulkan::Vulkan(asio::io_context &ioc)
|
||||
: AsyncObject(ioc), GuardLock(ioc.get_executor())
|
||||
: IOC(ioc), GuardLock(ioc.get_executor())
|
||||
{
|
||||
Screen.Width = 1920/2;
|
||||
Screen.Height = 1080/2;
|
||||
@@ -111,16 +93,6 @@ Vulkan::Vulkan(asio::io_context &ioc)
|
||||
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) {
|
||||
LOG.error() << "Game.RSession = nullptr: " << exc.what();
|
||||
}
|
||||
@@ -160,31 +132,12 @@ void Vulkan::run()
|
||||
NeedShutdown = false;
|
||||
Graphics.ThisThread = std::this_thread::get_id();
|
||||
|
||||
VkSemaphoreCreateInfo semaphoreCreateInfo = {
|
||||
VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
|
||||
nullptr,
|
||||
0
|
||||
};
|
||||
VkSemaphoreCreateInfo semaphoreCreateInfo = { VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, nullptr, 0 };
|
||||
VkSemaphore SemaphoreImageAcquired, SemaphoreDrawComplete;
|
||||
|
||||
VkSemaphore SemaphoreImageAcquired[4], SemaphoreDrawComplete[4];
|
||||
int semNext = 0;
|
||||
vkAssert(!vkCreateSemaphore(Graphics.Device, &semaphoreCreateInfo, NULL, &SemaphoreImageAcquired));
|
||||
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();
|
||||
while(!NeedShutdown)
|
||||
@@ -193,8 +146,6 @@ void Vulkan::run()
|
||||
prevTime += dTime;
|
||||
|
||||
Screen.State = DrawState::Begin;
|
||||
|
||||
/// TODO: Нужно синхронизировать с vkQueue
|
||||
{
|
||||
std::lock_guard lock(Screen.BeforeDrawMtx);
|
||||
while(!Screen.BeforeDraw.empty())
|
||||
@@ -204,42 +155,9 @@ 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();
|
||||
}
|
||||
|
||||
Game.Session = nullptr;
|
||||
}
|
||||
|
||||
if(!NeedShutdown && glfwWindowShouldClose(Graphics.Window)) {
|
||||
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 {
|
||||
if(Game.Session)
|
||||
Game.Session->shutdown(EnumDisconnect::ByInterface);
|
||||
@@ -247,16 +165,12 @@ void Vulkan::run()
|
||||
LOG.error() << "Game.Session->shutdown: " << exc.what();
|
||||
}
|
||||
|
||||
Game.Session = nullptr;
|
||||
|
||||
try {
|
||||
if(Game.Server)
|
||||
Game.Server->GS.shutdown("Завершение работы из-за остановки клиента");
|
||||
} catch(const std::exception &exc) {
|
||||
LOG.error() << "Game.Server->GS.shutdown: " << exc.what();
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if(Game.Session) {
|
||||
@@ -279,26 +193,25 @@ void Vulkan::run()
|
||||
// if(CallBeforeDraw)
|
||||
// CallBeforeDraw(this);
|
||||
|
||||
if(Game.RSession) {
|
||||
Game.RSession->beforeDraw();
|
||||
}
|
||||
|
||||
glfwPollEvents();
|
||||
|
||||
VkResult err;
|
||||
semNext = ++semNext % 4;
|
||||
err = vkAcquireNextImageKHR(Graphics.Device, Graphics.Swapchain, 1000000000ULL/20, SemaphoreImageAcquired[semNext], (VkFence) 0, &Graphics.DrawBufferCurrent);
|
||||
err = vkAcquireNextImageKHR(Graphics.Device, Graphics.Swapchain, 1000000000ULL/20, SemaphoreImageAcquired, (VkFence) 0, &Graphics.DrawBufferCurrent);
|
||||
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();
|
||||
buildSwapchains();
|
||||
continue;
|
||||
// } else if (err == VK_SUBOPTIMAL_KHR)
|
||||
// {
|
||||
// LOGGER.debug() << "VK_SUBOPTIMAL_KHR Pre";
|
||||
// continue;
|
||||
} else if(err == VK_SUBOPTIMAL_KHR || err == VK_SUCCESS) {
|
||||
} else if (err == VK_SUBOPTIMAL_KHR)
|
||||
{
|
||||
LOGGER.debug() << "VK_SUBOPTIMAL_KHR Pre";
|
||||
} else if(err == VK_SUCCESS) {
|
||||
|
||||
Screen.State = DrawState::Drawing;
|
||||
//Готовим инструкции рисовки
|
||||
@@ -307,17 +220,13 @@ void Vulkan::run()
|
||||
{
|
||||
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
|
||||
.flags = 0,
|
||||
.pInheritanceInfo = nullptr
|
||||
};
|
||||
|
||||
vkAssert(!vkBeginCommandBuffer(Graphics.CommandBufferRender, &cmd_buf_info));
|
||||
}
|
||||
|
||||
if(Game.RSession) {
|
||||
Game.RSession->beforeDraw(double(gTime));
|
||||
}
|
||||
|
||||
{
|
||||
VkImageMemoryBarrier image_memory_barrier =
|
||||
{
|
||||
@@ -536,15 +445,10 @@ void Vulkan::run()
|
||||
// vkCmdBeginRenderPass(Graphics.CommandBufferRender, &rp_begin, VK_SUBPASS_CONTENTS_INLINE);
|
||||
// }
|
||||
|
||||
if(Game.RSession)
|
||||
Game.RSession->pushStage(EnumRenderStage::Render);
|
||||
|
||||
|
||||
#ifdef HAS_IMGUI
|
||||
{
|
||||
auto lockQueue = Graphics.DeviceQueueGraphic.lock();
|
||||
ImGui_ImplVulkan_NewFrame();
|
||||
lockQueue.unlock();
|
||||
}
|
||||
ImGui_ImplGlfw_NewFrame();
|
||||
ImGui::NewFrame();
|
||||
|
||||
@@ -589,25 +493,16 @@ void Vulkan::run()
|
||||
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
|
||||
.pNext = nullptr,
|
||||
.waitSemaphoreCount = 1,
|
||||
.pWaitSemaphores = &SemaphoreImageAcquired[semNext],
|
||||
.pWaitSemaphores = &SemaphoreImageAcquired,
|
||||
.pWaitDstStageMask = &pipe_stage_flags,
|
||||
.commandBufferCount = 1,
|
||||
.pCommandBuffers = &Graphics.CommandBufferRender,
|
||||
.signalSemaphoreCount = 1,
|
||||
.pSignalSemaphores = &SemaphoreDrawComplete[semNext]
|
||||
.pSignalSemaphores = &SemaphoreDrawComplete
|
||||
};
|
||||
|
||||
// Отправляем команды рендера в очередь
|
||||
{
|
||||
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();
|
||||
//Рисуем, когда получим картинку
|
||||
vkAssert(!vkQueueSubmit(Graphics.DeviceQueueGraphic, 1, &submit_info, nullFence));
|
||||
}
|
||||
|
||||
{
|
||||
@@ -616,18 +511,14 @@ void Vulkan::run()
|
||||
.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
|
||||
.pNext = NULL,
|
||||
.waitSemaphoreCount = 1,
|
||||
.pWaitSemaphores = &SemaphoreDrawComplete[semNext],
|
||||
.pWaitSemaphores = &SemaphoreDrawComplete,
|
||||
.swapchainCount = 1,
|
||||
.pSwapchains = &Graphics.Swapchain,
|
||||
.pImageIndices = &Graphics.DrawBufferCurrent
|
||||
};
|
||||
|
||||
// Передадим фрейм, когда рендер будет завершён
|
||||
{
|
||||
auto lockQueue = Graphics.DeviceQueueGraphic.lock();
|
||||
err = vkQueuePresentKHR(*lockQueue, &present);
|
||||
}
|
||||
|
||||
// Завершаем картинку
|
||||
err = vkQueuePresentKHR(Graphics.DeviceQueueGraphic, &present);
|
||||
if (err == VK_ERROR_OUT_OF_DATE_KHR)
|
||||
{
|
||||
freeSwapchains();
|
||||
@@ -641,20 +532,17 @@ void Vulkan::run()
|
||||
vkAssert(err == VK_TIMEOUT);
|
||||
|
||||
if(Game.Session) {
|
||||
if(Game.RSession)
|
||||
Game.RSession->pushStage(EnumRenderStage::WorldUpdate);
|
||||
|
||||
Game.Session->update(gTime, dTime);
|
||||
Game.Session->atFreeDrawTime(gTime, dTime);
|
||||
}
|
||||
|
||||
vkAssert(!vkQueueWaitIdle(Graphics.DeviceQueueGraphic));
|
||||
|
||||
vkDeviceWaitIdle(Graphics.Device);
|
||||
Screen.State = DrawState::End;
|
||||
}
|
||||
|
||||
for(int iter = 0; iter < 4; iter++) {
|
||||
vkDestroySemaphore(Graphics.Device, SemaphoreImageAcquired[iter], nullptr);
|
||||
vkDestroySemaphore(Graphics.Device, SemaphoreDrawComplete[iter], nullptr);
|
||||
}
|
||||
|
||||
vkDestroyFence(Graphics.Device, drawEndFence, nullptr);
|
||||
vkDestroySemaphore(Graphics.Device, SemaphoreImageAcquired, nullptr);
|
||||
vkDestroySemaphore(Graphics.Device, SemaphoreDrawComplete, nullptr);
|
||||
}
|
||||
|
||||
void Vulkan::glfwCallbackError(int error, const char *description)
|
||||
@@ -680,6 +568,8 @@ uint32_t Vulkan::memoryTypeFromProperties(uint32_t bitsOfAcceptableTypes, VkFlag
|
||||
|
||||
void Vulkan::freeSwapchains()
|
||||
{
|
||||
//vkDeviceWaitIdle(Screen.Device);
|
||||
|
||||
if(Graphics.Instance && Graphics.Device)
|
||||
{
|
||||
std::vector<VkImageView> oldViews;
|
||||
@@ -836,8 +726,8 @@ void Vulkan::buildSwapchains()
|
||||
.imageColorSpace = Graphics.SurfaceColorSpace,
|
||||
.imageExtent = swapchainExtent,
|
||||
.imageArrayLayers = 1,
|
||||
.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
|
||||
// | VK_IMAGE_USAGE_TRANSFER_DST_BIT,
|
||||
.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT
|
||||
| VK_IMAGE_USAGE_TRANSFER_DST_BIT,
|
||||
.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE,
|
||||
.queueFamilyIndexCount = 0,
|
||||
.pQueueFamilyIndices = nullptr,
|
||||
@@ -1289,11 +1179,7 @@ void Vulkan::checkLibrary()
|
||||
uint32_t count = -1;
|
||||
VkResult res;
|
||||
|
||||
VkResult errc = vkEnumeratePhysicalDevices(localInstance.getInstance(), &count, nullptr);
|
||||
if(errc != VK_SUCCESS && errc != VK_INCOMPLETE) {
|
||||
error << "vkEnumeratePhysicalDevices не смог обработать запрос (ошибка драйвера?)\n";
|
||||
goto onError;
|
||||
}
|
||||
vkAssert(!vkEnumeratePhysicalDevices(localInstance.getInstance(), &count, nullptr));
|
||||
|
||||
if(!count)
|
||||
{
|
||||
@@ -1572,7 +1458,6 @@ void Vulkan::initNextSettings()
|
||||
freeSwapchains();
|
||||
}
|
||||
|
||||
bool hasVK_EXT_debug_utils = false;
|
||||
if(!Graphics.Instance)
|
||||
{
|
||||
std::vector<std::string_view> knownDebugLayers =
|
||||
@@ -1589,7 +1474,7 @@ void Vulkan::initNextSettings()
|
||||
"VK_LAYER_LUNARG_monitor"
|
||||
};
|
||||
|
||||
if(!SettingsNext.Debug || getenv("no_vk_debug"))
|
||||
if(!SettingsNext.Debug)
|
||||
knownDebugLayers.clear();
|
||||
|
||||
std::vector<vkInstanceLayer> enableDebugLayers;
|
||||
@@ -1602,38 +1487,7 @@ void Vulkan::initNextSettings()
|
||||
break;
|
||||
}
|
||||
|
||||
std::vector<vkInstanceExtension> enableExtension;
|
||||
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() << "Добавлен обработчик логов";
|
||||
Graphics.Instance.emplace(this, enableDebugLayers);
|
||||
}
|
||||
|
||||
if(!Graphics.Surface)
|
||||
@@ -1703,8 +1557,8 @@ void Vulkan::initNextSettings()
|
||||
|
||||
features.features.geometryShader = true;
|
||||
|
||||
feat11.uniformAndStorageBuffer16BitAccess = false;
|
||||
feat11.storageBuffer16BitAccess = false;
|
||||
feat11.uniformAndStorageBuffer16BitAccess = true;
|
||||
feat11.storageBuffer16BitAccess = true;
|
||||
|
||||
VkDeviceCreateInfo infoDevice =
|
||||
{
|
||||
@@ -1721,8 +1575,7 @@ void Vulkan::initNextSettings()
|
||||
};
|
||||
|
||||
vkAssert(!vkCreateDevice(Graphics.PhysicalDevice, &infoDevice, nullptr, &Graphics.Device));
|
||||
auto lockQueue = Graphics.DeviceQueueGraphic.lock();
|
||||
vkGetDeviceQueue(Graphics.Device, SettingsNext.QueueGraphics, 0, &*lockQueue);
|
||||
vkGetDeviceQueue(Graphics.Device, SettingsNext.QueueGraphics, 0, &Graphics.DeviceQueueGraphic);
|
||||
}
|
||||
|
||||
// Определяемся с форматом экранного буфера
|
||||
@@ -1772,7 +1625,7 @@ void Vulkan::initNextSettings()
|
||||
{
|
||||
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
|
||||
.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
|
||||
};
|
||||
|
||||
@@ -1949,14 +1802,13 @@ void Vulkan::initNextSettings()
|
||||
|
||||
ImGui_ImplGlfw_InitForVulkan(Graphics.Window, true);
|
||||
|
||||
auto lockQueue = Graphics.DeviceQueueGraphic.lock();
|
||||
ImGui_ImplVulkan_InitInfo ImGuiInfo =
|
||||
{
|
||||
.Instance = Graphics.Instance->getInstance(),
|
||||
.PhysicalDevice = Graphics.PhysicalDevice,
|
||||
.Device = Graphics.Device,
|
||||
.QueueFamily = SettingsNext.QueueGraphics,
|
||||
.Queue = *lockQueue,
|
||||
.Queue = Graphics.DeviceQueueGraphic,
|
||||
.DescriptorPool = Graphics.ImGuiDescPool,
|
||||
.RenderPass = Graphics.RenderPass,
|
||||
.MinImageCount = Graphics.DrawBufferCount,
|
||||
@@ -1972,7 +1824,6 @@ void Vulkan::initNextSettings()
|
||||
};
|
||||
|
||||
ImGui_ImplVulkan_Init(&ImGuiInfo);
|
||||
lockQueue.unlock();
|
||||
|
||||
// ImFontConfig fontConfig;
|
||||
// fontConfig.MergeMode = false;
|
||||
@@ -2076,7 +1927,7 @@ void Vulkan::deInitVulkan()
|
||||
Graphics.RenderPass = nullptr;
|
||||
Graphics.Device = nullptr;
|
||||
Graphics.PhysicalDevice = nullptr;
|
||||
*Graphics.DeviceQueueGraphic.lock() = nullptr;
|
||||
Graphics.DeviceQueueGraphic = nullptr;
|
||||
Graphics.Surface = nullptr;
|
||||
Graphics.Instance.reset();
|
||||
}
|
||||
@@ -2100,10 +1951,8 @@ void Vulkan::flushCommandBufferData()
|
||||
.pSignalSemaphores = nullptr
|
||||
};
|
||||
|
||||
auto lockQueue = Graphics.DeviceQueueGraphic.lock();
|
||||
vkAssert(!vkQueueSubmit(*lockQueue, 1, &submit_info, nullFence));
|
||||
vkAssert(!vkQueueWaitIdle(*lockQueue));
|
||||
lockQueue.unlock();
|
||||
vkAssert(!vkQueueSubmit(Graphics.DeviceQueueGraphic, 1, &submit_info, nullFence));
|
||||
vkAssert(!vkQueueWaitIdle(Graphics.DeviceQueueGraphic));
|
||||
|
||||
VkCommandBufferBeginInfo infoCmdBuffer =
|
||||
{
|
||||
@@ -2221,23 +2070,12 @@ void Vulkan::gui_MainMenu() {
|
||||
ImGui::InputText("Username", ConnectionProgress.Username, sizeof(ConnectionProgress.Username));
|
||||
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(ImGui::Button("Подключиться")) {
|
||||
ConnectionProgress.InProgress = true;
|
||||
ConnectionProgress.Cancel = false;
|
||||
ConnectionProgress.Progress.clear();
|
||||
co_spawn(ConnectionProgress.connect(IOC));
|
||||
asio::co_spawn(IOC, ConnectionProgress.connect(IOC), asio::detached);
|
||||
}
|
||||
|
||||
if(!Game.Server) {
|
||||
@@ -2258,10 +2096,6 @@ void Vulkan::gui_MainMenu() {
|
||||
}
|
||||
}
|
||||
|
||||
if(ImGui::Button("Memory trim")) {
|
||||
malloc_trim(0);
|
||||
}
|
||||
|
||||
if(ConnectionProgress.InProgress) {
|
||||
if(ImGui::Button("Отмена"))
|
||||
ConnectionProgress.Cancel = true;
|
||||
@@ -2276,9 +2110,10 @@ void Vulkan::gui_MainMenu() {
|
||||
|
||||
if(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>(this, Game.Session.get());
|
||||
Game.Session->setRenderSession(Game.RSession.get());
|
||||
Game.RSession = std::make_unique<VulkanRenderSession>();
|
||||
*this << Game.RSession;
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -2287,46 +2122,10 @@ void Vulkan::gui_MainMenu() {
|
||||
}
|
||||
|
||||
void Vulkan::gui_ConnectedToServer() {
|
||||
if(Game.Session && Game.RSession) {
|
||||
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();
|
||||
}
|
||||
|
||||
if(ImGui::Button("Memory trim")) {
|
||||
malloc_trim(0);
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
|
||||
if(Game.Выйти)
|
||||
return;
|
||||
}
|
||||
|
||||
if(Game.Session) {
|
||||
if(Game.Session->isConnected())
|
||||
return;
|
||||
|
||||
Game.RSession->pushStage(EnumRenderStage::Shutdown);
|
||||
Game.RSession = nullptr;
|
||||
Game.Session = nullptr;
|
||||
Game.ImGuiInterfaces.pop_back();
|
||||
@@ -2836,7 +2635,6 @@ Buffer::Buffer(Vulkan *instance, VkDeviceSize bufferSize, VkBufferUsageFlags usa
|
||||
vkAllocateMemory(instance->Graphics.Device, &memAlloc, nullptr, &Memory);
|
||||
if(res)
|
||||
MAKE_ERROR("VkHandler: ошибка выделения памяти на устройстве");
|
||||
assert(Memory);
|
||||
|
||||
vkBindBufferMemory(instance->Graphics.Device, Buff, Memory, 0);
|
||||
}
|
||||
@@ -2865,16 +2663,12 @@ Buffer::Buffer(Buffer &&obj)
|
||||
obj.Instance = nullptr;
|
||||
}
|
||||
|
||||
Buffer& Buffer::operator=(Buffer &&obj) {
|
||||
if(this == &obj)
|
||||
return *this;
|
||||
|
||||
Buffer& Buffer::operator=(Buffer &&obj)
|
||||
{
|
||||
std::swap(Instance, obj.Instance);
|
||||
std::swap(Buff, obj.Buff);
|
||||
std::swap(Memory, obj.Memory);
|
||||
std::swap(Size, obj.Size);
|
||||
obj.Instance = nullptr;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -2934,22 +2728,13 @@ CommandBuffer::CommandBuffer(Vulkan *instance)
|
||||
};
|
||||
|
||||
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()
|
||||
{
|
||||
if(Buffer && Instance && Instance->Graphics.Device)
|
||||
{
|
||||
auto lockQueue = Instance->Graphics.DeviceQueueGraphic.lock();
|
||||
if(*lockQueue)
|
||||
if(Instance->Graphics.DeviceQueueGraphic)
|
||||
{
|
||||
//vkAssert(!vkEndCommandBuffer(Buffer));
|
||||
vkEndCommandBuffer(Buffer);
|
||||
@@ -2969,33 +2754,25 @@ CommandBuffer::~CommandBuffer()
|
||||
};
|
||||
|
||||
//vkAssert(!vkQueueSubmit(Instance->Graphics.DeviceQueueGraphic, 1, &submit_info, nullFence));
|
||||
vkQueueSubmit(*lockQueue, 1, &submit_info, Fence);
|
||||
lockQueue.unlock();
|
||||
vkQueueSubmit(Instance->Graphics.DeviceQueueGraphic, 1, &submit_info, nullFence);
|
||||
//vkAssert(!vkQueueWaitIdle(Instance->Graphics.DeviceQueueGraphic));
|
||||
//vkQueueWaitIdle(Instance->Graphics.DeviceQueueGraphic);
|
||||
vkWaitForFences(Instance->Graphics.Device, 1, &Fence, VK_TRUE, UINT64_MAX);
|
||||
vkResetFences(Instance->Graphics.Device, 1, &Fence);
|
||||
vkQueueWaitIdle(Instance->Graphics.DeviceQueueGraphic);
|
||||
|
||||
auto toExecute = std::move(AfterExecute);
|
||||
for(auto &iter : toExecute)
|
||||
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);
|
||||
|
||||
if(OffthreadPool)
|
||||
vkDestroyCommandPool(Instance->Graphics.Device, OffthreadPool, nullptr);
|
||||
|
||||
if(Fence)
|
||||
vkDestroyFence(Instance->Graphics.Device, Fence, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void CommandBuffer::execute()
|
||||
{
|
||||
auto lockQueue = Instance->Graphics.DeviceQueueGraphic.lock();
|
||||
vkAssert(*lockQueue);
|
||||
vkAssert(Instance->Graphics.DeviceQueueGraphic);
|
||||
vkAssert(!vkEndCommandBuffer(Buffer));
|
||||
|
||||
const VkCommandBuffer cmd_bufs[] = { Buffer };
|
||||
@@ -3013,16 +2790,8 @@ void CommandBuffer::execute()
|
||||
.pSignalSemaphores = nullptr
|
||||
};
|
||||
|
||||
// vkAssert(!vkQueueSubmit(Instance->Graphics.DeviceQueueGraphic, 1, &submit_info, nullFence));
|
||||
// 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));
|
||||
|
||||
vkAssert(!vkQueueSubmit(Instance->Graphics.DeviceQueueGraphic, 1, &submit_info, nullFence));
|
||||
vkAssert(!vkQueueWaitIdle(Instance->Graphics.DeviceQueueGraphic));
|
||||
VkCommandBufferBeginInfo infoCmdBuffer =
|
||||
{
|
||||
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
|
||||
@@ -3731,8 +3500,6 @@ void DynamicImage::changeData(int32_t x, int32_t y, uint16_t width, uint16_t hei
|
||||
// Выполняем все команды
|
||||
buffer.execute();
|
||||
|
||||
Time::sleep3(50);
|
||||
|
||||
// Удаляем не нужную картинку
|
||||
vkDestroyImage(Instance->Graphics.Device, tempImage, nullptr);
|
||||
vkFreeMemory(Instance->Graphics.Device, tempMemory, nullptr);
|
||||
@@ -4061,7 +3828,7 @@ void AtlasImage::atlasChangeTextureData(uint16_t id, const uint32_t *rgba)
|
||||
InfoSubTexture *info = const_cast<InfoSubTexture*>(atlasGetTextureInfo(id));
|
||||
|
||||
auto iter = CachedData.find(id);
|
||||
// Если есть данные в кеше, то меняем их
|
||||
// Если есть данные в кэше, то меняем их
|
||||
if(iter != CachedData.end())
|
||||
{
|
||||
if(iter->second.size() == 0)
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
#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 "Common/Async.hpp"
|
||||
#include <TOSLib.hpp>
|
||||
@@ -24,12 +19,14 @@ static_assert(GLM_CONFIG_CLIP_CONTROL == GLM_CLIP_CONTROL_RH_ZO);
|
||||
#include "freetype/freetype.h"
|
||||
|
||||
#include <vulkan/vulkan_core.h>
|
||||
#include <glm/ext.hpp>
|
||||
#include <map>
|
||||
#define TOS_VULKAN_NO_VIDEO
|
||||
#include <vulkan/vulkan.h>
|
||||
#define GLFW_INCLUDE_NONE
|
||||
#include <GLFW/glfw3.h>
|
||||
|
||||
static_assert(GLM_CONFIG_CLIP_CONTROL == GLM_CLIP_CONTROL_RH_ZO);
|
||||
|
||||
#define IMGUI_ENABLE_STB_TEXTEDIT_UNICODE
|
||||
|
||||
@@ -48,7 +45,7 @@ struct DeviceId {
|
||||
struct Settings {
|
||||
DeviceId DeviceMain;
|
||||
uint32_t QueueGraphics = -1, QueueSurface = -1;
|
||||
bool Debug = false;
|
||||
bool Debug = true;
|
||||
|
||||
bool isValid()
|
||||
{
|
||||
@@ -74,9 +71,10 @@ class Buffer;
|
||||
Vulkan.reInit();
|
||||
*/
|
||||
|
||||
class Vulkan : public AsyncObject {
|
||||
class Vulkan {
|
||||
private:
|
||||
Logger LOG = "Vulkan";
|
||||
asio::io_context &IOC;
|
||||
|
||||
struct vkInstanceLayer {
|
||||
std::string LayerName = "nullptr", Description = "nullptr";
|
||||
@@ -178,7 +176,7 @@ private:
|
||||
asio::executor_work_guard<asio::io_context::executor_type> GuardLock;
|
||||
|
||||
public:
|
||||
struct GraphicsObj_t {
|
||||
struct {
|
||||
std::vector<vkInstanceLayer> InstanceLayers;
|
||||
std::vector<vkInstanceExtension> InstanceExtensions;
|
||||
std::vector<std::string> GLFWExtensions;
|
||||
@@ -191,7 +189,7 @@ public:
|
||||
VkPhysicalDevice PhysicalDevice = nullptr;
|
||||
VkPhysicalDeviceMemoryProperties DeviceMemoryProperties = {0};
|
||||
VkDevice Device = nullptr;
|
||||
SpinlockObject<VkQueue> DeviceQueueGraphic;
|
||||
VkQueue DeviceQueueGraphic = VK_NULL_HANDLE;
|
||||
VkFormat SurfaceFormat = VK_FORMAT_B8G8R8A8_UNORM;
|
||||
VkColorSpaceKHR SurfaceColorSpace = VK_COLOR_SPACE_MAX_ENUM_KHR;
|
||||
|
||||
@@ -226,10 +224,6 @@ public:
|
||||
|
||||
// Идентификатор потока графики
|
||||
std::thread::id ThisThread = std::this_thread::get_id();
|
||||
|
||||
GraphicsObj_t()
|
||||
: DeviceQueueGraphic(VK_NULL_HANDLE)
|
||||
{}
|
||||
} Graphics;
|
||||
|
||||
enum struct DrawState {
|
||||
@@ -250,8 +244,7 @@ public:
|
||||
DestroyLock UseLock;
|
||||
std::thread MainThread;
|
||||
std::shared_ptr<VulkanRenderSession> RSession;
|
||||
ServerSession::Ptr Session;
|
||||
bool Выйти = false;
|
||||
std::unique_ptr<ServerSession> Session;
|
||||
|
||||
std::list<void (Vulkan::*)()> ImGuiInterfaces;
|
||||
std::unique_ptr<ServerObj> Server;
|
||||
@@ -542,7 +535,6 @@ public:
|
||||
class CommandBuffer {
|
||||
VkCommandBuffer Buffer = VK_NULL_HANDLE;
|
||||
Vulkan *Instance;
|
||||
VkFence Fence = VK_NULL_HANDLE;
|
||||
std::vector<std::function<void()>> AfterExecute;
|
||||
|
||||
VkCommandPool OffthreadPool = VK_NULL_HANDLE;
|
||||
@@ -1027,25 +1019,5 @@ public:
|
||||
virtual ~PipelineVGF();
|
||||
};
|
||||
|
||||
|
||||
enum class EnumRenderStage {
|
||||
// Постройка буфера команд на рисовку
|
||||
// В этот период не должно быть изменений в таблице,
|
||||
// хранящей указатели на данные для рендера ChunkMesh
|
||||
// Можно работать с миром
|
||||
// Здесь нужно дождаться завершения работы с ChunkMesh
|
||||
ComposingCommandBuffer,
|
||||
// В этот период можно менять ChunkMesh
|
||||
// Можно работать с миром
|
||||
Render,
|
||||
// В этот период нельзя работать с миром
|
||||
// Можно менять ChunkMesh
|
||||
// Здесь нужно дождаться завершения работы с миром, только в
|
||||
// этом этапе могут приходить события изменения чанков и определений
|
||||
WorldUpdate,
|
||||
|
||||
Shutdown
|
||||
};
|
||||
|
||||
} /* namespace TOS::Navie::VK */
|
||||
|
||||
|
||||
@@ -1,399 +0,0 @@
|
||||
#include "AssetsPreloader.hpp"
|
||||
#include "Common/Abstract.hpp"
|
||||
#include "Common/TexturePipelineProgram.hpp"
|
||||
#include "sha2.hpp"
|
||||
#include <atomic>
|
||||
#include <filesystem>
|
||||
#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() {
|
||||
for(size_t type = 0; type < static_cast<size_t>(EnumAssets::MAX_ENUM); type++) {
|
||||
ResourceLinks[type].emplace_back(
|
||||
ResourceFile::Hash_t{0},
|
||||
ResourceHeader(),
|
||||
fs::file_time_type(),
|
||||
fs::path{""},
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
AssetsPreloader::Out_checkAndPrepareResourcesUpdate AssetsPreloader::checkAndPrepareResourcesUpdate(
|
||||
const AssetsRegister& instances,
|
||||
const std::function<ResourceId(EnumAssets type, std::string_view domain, std::string_view key)>& idResolver,
|
||||
const std::function<void(std::u8string&& resource, ResourceFile::Hash_t hash, fs::path resPath)>& onNewResourceParsed,
|
||||
ReloadStatus* status
|
||||
) {
|
||||
assert(idResolver);
|
||||
|
||||
#ifndef NDEBUG
|
||||
bool expected = false;
|
||||
assert(_Reloading.compare_exchange_strong(expected, true) && "Двойной вызов reloadResources");
|
||||
struct ReloadGuard {
|
||||
std::atomic<bool>& Flag;
|
||||
~ReloadGuard() { Flag.exchange(false); }
|
||||
} guard{_Reloading};
|
||||
#endif
|
||||
|
||||
try {
|
||||
ReloadStatus secondStatus;
|
||||
return _checkAndPrepareResourcesUpdate(instances, idResolver, onNewResourceParsed, status ? *status : secondStatus);
|
||||
} catch(const std::exception& exc) {
|
||||
LOG.error() << exc.what();
|
||||
assert(!"reloadResources: здесь не должно быть ошибок");
|
||||
std::unreachable();
|
||||
} catch(...) {
|
||||
assert(!"reloadResources: здесь не должно быть ошибок");
|
||||
std::unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
AssetsPreloader::Out_checkAndPrepareResourcesUpdate AssetsPreloader::_checkAndPrepareResourcesUpdate(
|
||||
const AssetsRegister& instances,
|
||||
const std::function<ResourceId(EnumAssets type, std::string_view domain, std::string_view key)>& idResolver,
|
||||
const std::function<void(std::u8string&& resource, ResourceFile::Hash_t hash, fs::path resPath)>& onNewResourceParsed,
|
||||
ReloadStatus& status
|
||||
) {
|
||||
// 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,
|
||||
.Id = idResolver(assetType, domain, key)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw std::runtime_error("Неизвестный тип инстанса медиаресурсов");
|
||||
}
|
||||
} catch (const std::exception& exc) {
|
||||
/// TODO: Логгировать в статусе
|
||||
}
|
||||
}
|
||||
|
||||
// Функция парсинга ресурсов
|
||||
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 idResolver(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 idResolver(AssetType::Texture, mDomain, mKey);
|
||||
};
|
||||
|
||||
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(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 = 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") {
|
||||
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();
|
||||
|
||||
HeadlessModel hm;
|
||||
out.Header = hm.parse(obj, modelResolver, textureResolver);
|
||||
out.Resource = hm.dump();
|
||||
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::move(file.Data);
|
||||
out.Hash = file.Hash;
|
||||
out.Header = readOptionalMeta(info.Path);
|
||||
} else {
|
||||
ResourceFile file = readFileBytes(info.Path);
|
||||
out.Resource = std::move(file.Data);
|
||||
out.Hash = file.Hash;
|
||||
}
|
||||
|
||||
out.Id = idResolver(type, domain, key);
|
||||
|
||||
return out;
|
||||
};
|
||||
|
||||
// 2) Определяем какие ресурсы изменились (новый timestamp) или новые ресурсы
|
||||
Out_checkAndPrepareResourcesUpdate result;
|
||||
|
||||
// Собираем идентификаторы, чтобы потом определить какие ресурсы пропали
|
||||
std::array<
|
||||
std::unordered_set<ResourceId>,
|
||||
static_cast<size_t>(EnumAssets::MAX_ENUM)
|
||||
> uniqueExists;
|
||||
|
||||
for(size_t type = 0; type < static_cast<size_t>(AssetType::MAX_ENUM); ++type) {
|
||||
auto& uniqueExistsTypes = uniqueExists[type];
|
||||
const auto& resourceLinksTyped = ResourceLinks[type];
|
||||
result.MaxNewSize[type] = resourceLinksTyped.size();
|
||||
|
||||
{
|
||||
size_t allIds = 0;
|
||||
for(const auto& [domain, keys] : resourcesFirstStage[type])
|
||||
allIds += keys.size();
|
||||
|
||||
uniqueExistsTypes.reserve(allIds);
|
||||
}
|
||||
|
||||
for(const auto& [domain, keys] : resourcesFirstStage[type]) {
|
||||
for(const auto& [key, res] : keys) {
|
||||
uniqueExistsTypes.insert(res.Id);
|
||||
|
||||
if(res.Id >= resourceLinksTyped.size() || !resourceLinksTyped[res.Id].IsExist)
|
||||
{ // Если идентификатора нет в таблице или ресурс не привязан
|
||||
PendingResource resource = buildResource(static_cast<AssetType>(type), domain, key, res);
|
||||
if(onNewResourceParsed)
|
||||
onNewResourceParsed(std::move(resource.Resource), resource.Hash, res.Path);
|
||||
result.HashToPathNew[resource.Hash].push_back(res.Path);
|
||||
|
||||
if(res.Id >= result.MaxNewSize[type])
|
||||
result.MaxNewSize[type] = res.Id+1;
|
||||
|
||||
result.ResourceUpdates[type].emplace_back(res.Id, resource.Hash, std::move(resource.Header), resource.Timestamp, res.Path);
|
||||
} else if(resourceLinksTyped[res.Id].Path != res.Path
|
||||
|| resourceLinksTyped[res.Id].LastWrite != res.Timestamp
|
||||
) { // Если ресурс теперь берётся с другого места или изменилось время изменения файла
|
||||
const auto& lastResource = resourceLinksTyped[res.Id];
|
||||
PendingResource resource = buildResource(static_cast<AssetType>(type), domain, key, res);
|
||||
|
||||
if(lastResource.Hash != resource.Hash) {
|
||||
// Хэш изменился
|
||||
// Сообщаем о новом ресурсе
|
||||
if(onNewResourceParsed)
|
||||
onNewResourceParsed(std::move(resource.Resource), resource.Hash, res.Path);
|
||||
// Старый хэш более не доступен по этому расположению.
|
||||
result.HashToPathLost[lastResource.Hash].push_back(resourceLinksTyped[res.Id].Path);
|
||||
// Новый хеш стал доступен по этому расположению.
|
||||
result.HashToPathNew[resource.Hash].push_back(res.Path);
|
||||
} else if(resourceLinksTyped[res.Id].Path != res.Path) {
|
||||
// Изменился конечный путь.
|
||||
// Хэш более не доступен по этому расположению.
|
||||
result.HashToPathLost[resource.Hash].push_back(resourceLinksTyped[res.Id].Path);
|
||||
// Хеш теперь доступен по этому расположению.
|
||||
result.HashToPathNew[resource.Hash].push_back(res.Path);
|
||||
} else {
|
||||
// Ресурс без заголовка никак не изменился.
|
||||
}
|
||||
|
||||
// Чтобы там не поменялось, мог поменятся заголовок. Уведомляем о новой привязке.
|
||||
result.ResourceUpdates[type].emplace_back(res.Id, resource.Hash, std::move(resource.Header), resource.Timestamp, res.Path);
|
||||
} else {
|
||||
// Ресурс не изменился
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3) Определяем какие ресурсы пропали
|
||||
for(size_t type = 0; type < static_cast<size_t>(AssetType::MAX_ENUM); ++type) {
|
||||
const auto& resourceLinksTyped = ResourceLinks[type];
|
||||
|
||||
size_t counter = 0;
|
||||
for(const auto& [hash, header, timestamp, path, isExist] : resourceLinksTyped) {
|
||||
size_t id = counter++;
|
||||
if(!isExist)
|
||||
continue;
|
||||
|
||||
if(uniqueExists[type].contains(id))
|
||||
continue;
|
||||
|
||||
// Ресурс потерян
|
||||
// Хэш более не доступен по этому расположению.
|
||||
result.HashToPathLost[hash].push_back(path);
|
||||
result.LostLinks[type].push_back(id);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
AssetsPreloader::Out_applyResourcesUpdate AssetsPreloader::applyResourcesUpdate(const Out_checkAndPrepareResourcesUpdate& orr) {
|
||||
Out_applyResourcesUpdate result;
|
||||
|
||||
for(size_t type = 0; type < static_cast<size_t>(EnumAssets::MAX_ENUM); ++type) {
|
||||
// Затираем потерянные
|
||||
for(ResourceId id : orr.LostLinks[type]) {
|
||||
assert(id < ResourceLinks[type].size());
|
||||
auto& [hash, header, timestamp, path, isExist] = ResourceLinks[type][id];
|
||||
hash = {0};
|
||||
header = {};
|
||||
timestamp = fs::file_time_type();
|
||||
path.clear();
|
||||
isExist = false;
|
||||
|
||||
result.NewOrUpdates[type].emplace_back(id, hash, header);
|
||||
}
|
||||
|
||||
// Увеличиваем размер, если необходимо
|
||||
if(orr.MaxNewSize[type] > ResourceLinks[type].size()) {
|
||||
ResourceLink def{
|
||||
ResourceFile::Hash_t{0},
|
||||
ResourceHeader(),
|
||||
fs::file_time_type(),
|
||||
fs::path{""},
|
||||
false
|
||||
};
|
||||
|
||||
ResourceLinks[type].resize(orr.MaxNewSize[type], def);
|
||||
}
|
||||
|
||||
// Обновляем / добавляем
|
||||
for(auto& [id, hash, header, timestamp, path] : orr.ResourceUpdates[type]) {
|
||||
ResourceLinks[type][id] = {hash, std::move(header), timestamp, std::move(path), true};
|
||||
result.NewOrUpdates[type].emplace_back(id, hash, header);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,293 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#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 <vector>
|
||||
#include "Abstract.hpp"
|
||||
#include "Common/Abstract.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;
|
||||
|
||||
class AssetsPreloader {
|
||||
public:
|
||||
using Ptr = std::shared_ptr<AssetsPreloader>;
|
||||
|
||||
/*
|
||||
Ресурс имеет бинарную часть, из который вырезаны все зависимости.
|
||||
Вторая часть это заголовок, которые всегда динамично передаётся с сервера.
|
||||
В заголовке хранятся зависимости от ресурсов.
|
||||
*/
|
||||
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::u8string Resource;
|
||||
// Его хеш
|
||||
ResourceFile::Hash_t Hash;
|
||||
// Заголовок
|
||||
std::u8string Header;
|
||||
};
|
||||
|
||||
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 внутри. От низшего приоритета к высшему.
|
||||
idResolver -> функция получения идентификатора по Тип+Домен+Ключ
|
||||
onNewResourceParsed -> Callback на обработку распаршенных ресурсов без заголовков
|
||||
(на стороне сервера хранится в другой сущности, на стороне клиента игнорируется).
|
||||
status -> обратный отклик о процессе обновления ресурсов.
|
||||
ReloadStatus <- новые и потерянные ресурсы.
|
||||
*/
|
||||
struct Out_checkAndPrepareResourcesUpdate {
|
||||
// Новые связки Id -> Hash + Header + Timestamp + Path (ресурс новый или изменён)
|
||||
std::array<
|
||||
std::vector<
|
||||
std::tuple<
|
||||
ResourceId, // Ресурс
|
||||
ResourceFile::Hash_t, // Хэш ресурса на диске
|
||||
ResourceHeader, // Хедер ресурса (со всеми зависимостями)
|
||||
fs::file_time_type, // Время изменения ресурса на диске
|
||||
fs::path // Путь до ресурса
|
||||
>
|
||||
>,
|
||||
static_cast<size_t>(AssetType::MAX_ENUM)
|
||||
> ResourceUpdates;
|
||||
|
||||
// Используется чтобы эффективно увеличить размер таблиц
|
||||
std::array<
|
||||
ResourceId,
|
||||
static_cast<size_t>(AssetType::MAX_ENUM)
|
||||
> MaxNewSize;
|
||||
|
||||
// Потерянные связки Id (ресурс физически потерян)
|
||||
std::array<
|
||||
std::vector<ResourceId>,
|
||||
static_cast<size_t>(AssetType::MAX_ENUM)
|
||||
> LostLinks;
|
||||
|
||||
/*
|
||||
Новые пути предоставляющие хеш
|
||||
(по каким путям можно получить ресурс определённого хеша).
|
||||
*/
|
||||
std::unordered_map<
|
||||
ResourceFile::Hash_t,
|
||||
std::vector<fs::path>
|
||||
> HashToPathNew;
|
||||
|
||||
/*
|
||||
Потерянные пути, предоставлявшые ресурсы с данным хешем
|
||||
(пути по которым уже нельзя получить заданных хеш).
|
||||
*/
|
||||
std::unordered_map<
|
||||
ResourceFile::Hash_t,
|
||||
std::vector<fs::path>
|
||||
> HashToPathLost;
|
||||
};
|
||||
|
||||
Out_checkAndPrepareResourcesUpdate checkAndPrepareResourcesUpdate(
|
||||
const AssetsRegister& instances,
|
||||
const std::function<ResourceId(EnumAssets type, std::string_view domain, std::string_view key)>& idResolver,
|
||||
const std::function<void(std::u8string&& resource, ResourceFile::Hash_t hash, fs::path resPath)>& onNewResourceParsed = nullptr,
|
||||
ReloadStatus* status = nullptr
|
||||
);
|
||||
|
||||
/*
|
||||
Применяет расчитанные изменения.
|
||||
|
||||
Out_applyResourceUpdate <- Нужно отправить клиентам новые привязки ресурсов
|
||||
id -> hash+header
|
||||
*/
|
||||
struct BindHashHeaderInfo {
|
||||
ResourceId Id;
|
||||
ResourceFile::Hash_t Hash;
|
||||
ResourceHeader Header;
|
||||
};
|
||||
|
||||
struct Out_applyResourcesUpdate {
|
||||
std::array<
|
||||
std::vector<BindHashHeaderInfo>,
|
||||
static_cast<size_t>(EnumAssets::MAX_ENUM)
|
||||
> NewOrUpdates;
|
||||
};
|
||||
|
||||
Out_applyResourcesUpdate applyResourcesUpdate(const Out_checkAndPrepareResourcesUpdate& orr);
|
||||
|
||||
std::array<
|
||||
std::vector<BindHashHeaderInfo>,
|
||||
static_cast<size_t>(EnumAssets::MAX_ENUM)
|
||||
> collectHashBindings() const
|
||||
{
|
||||
std::array<
|
||||
std::vector<BindHashHeaderInfo>,
|
||||
static_cast<size_t>(EnumAssets::MAX_ENUM)
|
||||
> result;
|
||||
|
||||
for(size_t type = 0; type < static_cast<size_t>(EnumAssets::MAX_ENUM); ++type) {
|
||||
result[type].reserve(ResourceLinks[type].size());
|
||||
|
||||
ResourceId counter = 0;
|
||||
for(const auto& [hash, header, _1, _2, _3] : ResourceLinks[type]) {
|
||||
ResourceId id = counter++;
|
||||
result[type].emplace_back(id, hash, header);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const auto& getResourceLinks() const {
|
||||
return ResourceLinks;
|
||||
}
|
||||
|
||||
struct Out_Resource {
|
||||
ResourceFile::Hash_t Hash;
|
||||
fs::path Path;
|
||||
};
|
||||
|
||||
std::optional<Out_Resource> getResource(EnumAssets type, ResourceId id) const {
|
||||
const auto& rl = ResourceLinks[static_cast<size_t>(type)];
|
||||
if(id >= rl.size() || !rl[id].IsExist)
|
||||
return std::nullopt;
|
||||
|
||||
return Out_Resource{rl[id].Hash, rl[id].Path};
|
||||
}
|
||||
|
||||
private:
|
||||
struct ResourceFindInfo {
|
||||
// Путь к архиву (если есть), и путь до ресурса
|
||||
fs::path ArchivePath, Path;
|
||||
// Время изменения файла
|
||||
fs::file_time_type Timestamp;
|
||||
// Идентификатор ресурса
|
||||
ResourceId Id;
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
#ifndef NDEBUG
|
||||
// Текущее состояние reloadResources
|
||||
std::atomic<bool> _Reloading = false;
|
||||
#endif
|
||||
|
||||
struct ResourceLink {
|
||||
ResourceFile::Hash_t Hash; // Хэш ресурса на диске
|
||||
/// TODO: клиенту не нужны хедеры
|
||||
ResourceHeader Header; // Хедер ресурса (со всеми зависимостями)
|
||||
fs::file_time_type LastWrite; // Время изменения ресурса на диске
|
||||
fs::path Path; // Путь до ресурса
|
||||
bool IsExist;
|
||||
};
|
||||
|
||||
Out_checkAndPrepareResourcesUpdate _checkAndPrepareResourcesUpdate(
|
||||
const AssetsRegister& instances,
|
||||
const std::function<ResourceId(EnumAssets type, std::string_view domain, std::string_view key)>& idResolver,
|
||||
const std::function<void(std::u8string&& resource, ResourceFile::Hash_t hash, fs::path resPath)>& onNewResourceParsed,
|
||||
ReloadStatus& status
|
||||
);
|
||||
|
||||
// Привязка Id -> Hash + Header + Timestamp + Path
|
||||
std::array<
|
||||
std::vector<ResourceLink>,
|
||||
static_cast<size_t>(AssetType::MAX_ENUM)
|
||||
> ResourceLinks;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,14 +1,16 @@
|
||||
#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/any_io_executor.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/asio/impl/execution_context.hpp>
|
||||
#include <boost/date_time/posix_time/ptime.hpp>
|
||||
#include <boost/thread.hpp>
|
||||
#include <boost/asio/experimental/awaitable_operators.hpp>
|
||||
#include <boost/lockfree/queue.hpp>
|
||||
|
||||
|
||||
namespace LV {
|
||||
@@ -20,152 +22,4 @@ using tcp = asio::ip::tcp;
|
||||
template<typename T = void>
|
||||
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(...) {}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
#pragma once
|
||||
#include <algorithm>
|
||||
|
||||
#include "Common/Abstract.hpp"
|
||||
#include <iostream>
|
||||
|
||||
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
|
||||
) {
|
||||
|
||||
using ValType = VecType::Type;
|
||||
using ValType = VecType::value_type;
|
||||
|
||||
ValType max_delta = 0;
|
||||
ValType result = 0;
|
||||
|
||||
@@ -1,272 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "Common/Abstract.hpp"
|
||||
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <optional>
|
||||
#include <shared_mutex>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
namespace LV {
|
||||
|
||||
template<class Enum = EnumAssets, size_t ShardCount = 64>
|
||||
class IdProvider {
|
||||
public:
|
||||
static constexpr size_t MAX_ENUM = static_cast<size_t>(Enum::MAX_ENUM);
|
||||
|
||||
struct BindDomainKeyInfo {
|
||||
std::string Domain, Key;
|
||||
};
|
||||
|
||||
struct BindDomainKeyViewInfo {
|
||||
std::string_view Domain, Key;
|
||||
};
|
||||
|
||||
struct KeyHash {
|
||||
using is_transparent = void;
|
||||
|
||||
static inline std::size_t h(std::string_view sv) noexcept {
|
||||
return std::hash<std::string_view>{}(sv);
|
||||
}
|
||||
|
||||
static inline std::size_t mix(std::size_t a, std::size_t b) noexcept {
|
||||
a ^= b + 0x9e3779b97f4a7c15ULL + (a << 6) + (a >> 2);
|
||||
return a;
|
||||
}
|
||||
|
||||
std::size_t operator()(const BindDomainKeyInfo& k) const noexcept {
|
||||
return mix(h(k.Domain), h(k.Key));
|
||||
}
|
||||
|
||||
std::size_t operator()(const BindDomainKeyViewInfo& kv) const noexcept {
|
||||
return mix(h(kv.Domain), h(kv.Key));
|
||||
}
|
||||
};
|
||||
|
||||
struct KeyEq {
|
||||
using is_transparent = void;
|
||||
|
||||
bool operator()(const BindDomainKeyInfo& a, const BindDomainKeyInfo& b) const noexcept {
|
||||
return a.Domain == b.Domain && a.Key == b.Key;
|
||||
}
|
||||
|
||||
bool operator()(const BindDomainKeyInfo& a, const BindDomainKeyViewInfo& b) const noexcept {
|
||||
return a.Domain == b.Domain && a.Key == b.Key;
|
||||
}
|
||||
|
||||
bool operator()(const BindDomainKeyViewInfo& a, const BindDomainKeyInfo& b) const noexcept {
|
||||
return a.Domain == b.Domain && a.Key == b.Key;
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
explicit IdProvider() {
|
||||
for(size_t type = 0; type < MAX_ENUM; ++type) {
|
||||
_NextId[type].store(1, std::memory_order_relaxed);
|
||||
_Reverse[type].reserve(1024);
|
||||
|
||||
IdToDK[type].push_back({"core", "none"});
|
||||
|
||||
auto& sh = _shardFor(static_cast<Enum>(type), "core", "none");
|
||||
std::unique_lock lk(sh.mutex);
|
||||
sh.map.emplace(BindDomainKeyInfo{"core", "none"}, 0);
|
||||
|
||||
// ensure id 0 has a reverse mapping too
|
||||
_storeReverse(static_cast<Enum>(type), 0, std::string("core"), std::string("none"));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Находит или выдаёт идентификатор на запрошенный ресурс.
|
||||
Функция не требует внешней синхронизации.
|
||||
*/
|
||||
inline ResourceId getId(Enum type, std::string_view domain, std::string_view key) {
|
||||
#ifndef NDEBUG
|
||||
assert(!DKToIdInBakingMode);
|
||||
#endif
|
||||
auto& sh = _shardFor(type, domain, key);
|
||||
|
||||
// 1) Поиск в режиме для чтения
|
||||
{
|
||||
std::shared_lock lk(sh.mutex);
|
||||
if(auto it = sh.map.find(BindDomainKeyViewInfo{domain, key}); it != sh.map.end()) {
|
||||
return it->second;
|
||||
}
|
||||
}
|
||||
|
||||
// 2) Блокируем и повторно ищем запись (может кто уже успел её добавить)
|
||||
std::unique_lock lk(sh.mutex);
|
||||
if (auto it = sh.map.find(BindDomainKeyViewInfo{domain, key}); it != sh.map.end()) {
|
||||
return it->second;
|
||||
}
|
||||
|
||||
// Выделяем идентификатор
|
||||
ResourceId id = _NextId[static_cast<size_t>(type)].fetch_add(1, std::memory_order_relaxed);
|
||||
|
||||
std::string d(domain);
|
||||
std::string k(key);
|
||||
|
||||
sh.map.emplace(BindDomainKeyInfo{d, k}, id);
|
||||
sh.newlyInserted.push_back(id);
|
||||
|
||||
_storeReverse(type, id, std::move(d), std::move(k));
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
/*
|
||||
Переносит все новые идентификаторы в основную таблицу.
|
||||
|
||||
В этой реализации "основная таблица" уже основная (forward map обновляется сразу),
|
||||
а bake() собирает только новые привязки (domain,key) по логам вставок и дополняет IdToDK.
|
||||
|
||||
Нельзя использовать пока есть вероятность что кто-то использует getId(), если ты хочешь
|
||||
строгий debug-контроль как раньше. В релизе это не требуется: bake читает только reverse,
|
||||
а forward не трогает.
|
||||
*/
|
||||
std::array<std::vector<BindDomainKeyInfo>, MAX_ENUM> bake() {
|
||||
#ifndef NDEBUG
|
||||
assert(!DKToIdInBakingMode);
|
||||
DKToIdInBakingMode = true;
|
||||
struct _tempStruct {
|
||||
IdProvider* handler;
|
||||
~_tempStruct() { handler->DKToIdInBakingMode = false; }
|
||||
} _lock{this};
|
||||
#endif
|
||||
|
||||
std::array<std::vector<BindDomainKeyInfo>, MAX_ENUM> result;
|
||||
|
||||
for(size_t t = 0; t < MAX_ENUM; ++t) {
|
||||
auto type = static_cast<Enum>(t);
|
||||
|
||||
// 1) собрать новые id из всех шардов
|
||||
std::vector<ResourceId> new_ids;
|
||||
_drainNew(type, new_ids);
|
||||
|
||||
if(new_ids.empty())
|
||||
continue;
|
||||
|
||||
// 2) превратить id -> (domain,key) через reverse и вернуть наружу
|
||||
// + дописать в IdToDK[type] в порядке id (по желанию)
|
||||
std::sort(new_ids.begin(), new_ids.end());
|
||||
new_ids.erase(std::unique(new_ids.begin(), new_ids.end()), new_ids.end());
|
||||
|
||||
result[t].reserve(new_ids.size());
|
||||
|
||||
// reverse читаем под shared lock
|
||||
std::shared_lock rlk(_ReverseMutex[t]);
|
||||
for(ResourceId id : new_ids) {
|
||||
const std::size_t idx = static_cast<std::size_t>(id);
|
||||
if(idx >= _Reverse[t].size()) {
|
||||
// теоретически не должно случаться (мы пишем reverse до push в log)
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto& e = _Reverse[t][idx];
|
||||
result[t].push_back({e.Domain, e.Key});
|
||||
}
|
||||
|
||||
rlk.unlock();
|
||||
|
||||
// 3) дописать в IdToDK (для новых клиентов)
|
||||
IdToDK[t].append_range(result[t]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// id to DK
|
||||
std::optional<BindDomainKeyInfo> getDK(Enum type, ResourceId id) {
|
||||
auto& vec = _Reverse[static_cast<size_t>(type)];
|
||||
auto& mtx = _ReverseMutex[static_cast<size_t>(type)];
|
||||
|
||||
std::unique_lock lk(mtx);
|
||||
if(id >= vec.size())
|
||||
return std::nullopt;
|
||||
|
||||
return vec[id];
|
||||
}
|
||||
|
||||
// Для отправки новым подключенным клиентам
|
||||
const std::array<std::vector<BindDomainKeyInfo>, MAX_ENUM>& idToDK() const {
|
||||
return IdToDK;
|
||||
}
|
||||
|
||||
private:
|
||||
using Map = ankerl::unordered_dense::map<BindDomainKeyInfo, ResourceId, KeyHash, KeyEq>;
|
||||
|
||||
struct Shard {
|
||||
mutable std::shared_mutex mutex;
|
||||
Map map;
|
||||
std::vector<ResourceId> newlyInserted;
|
||||
};
|
||||
|
||||
private:
|
||||
// Кластер таблиц идентификаторов
|
||||
std::array<
|
||||
std::array<Shard, ShardCount>, MAX_ENUM
|
||||
> _Shards;
|
||||
|
||||
// Счётчики идентификаторов
|
||||
std::array<std::atomic<ResourceId>, MAX_ENUM> _NextId;
|
||||
|
||||
// Таблица обратных связок (Id to DK)
|
||||
std::array<std::vector<BindDomainKeyInfo>, MAX_ENUM> _Reverse;
|
||||
mutable std::array<std::shared_mutex, MAX_ENUM> _ReverseMutex;
|
||||
|
||||
#ifndef NDEBUG
|
||||
bool DKToIdInBakingMode = false;
|
||||
#endif
|
||||
|
||||
// stable "full sync" table for new clients:
|
||||
std::array<std::vector<BindDomainKeyInfo>, MAX_ENUM> IdToDK;
|
||||
|
||||
private:
|
||||
Shard& _shardFor(Enum type, const std::string_view domain, const std::string_view key) {
|
||||
const std::size_t idx = KeyHash{}(BindDomainKeyViewInfo{domain, key}) % ShardCount;
|
||||
return _Shards[static_cast<size_t>(type)][idx];
|
||||
}
|
||||
|
||||
const Shard& _shardFor(Enum type, const std::string_view domain, const std::string_view key) const {
|
||||
const std::size_t idx = KeyHash{}(BindDomainKeyViewInfo{domain, key}) % ShardCount;
|
||||
return _Shards[static_cast<size_t>(type)][idx];
|
||||
}
|
||||
|
||||
void _storeReverse(Enum type, ResourceId id, std::string&& domain, std::string&& key) {
|
||||
auto& vec = _Reverse[static_cast<size_t>(type)];
|
||||
auto& mtx = _ReverseMutex[static_cast<size_t>(type)];
|
||||
const std::size_t idx = static_cast<std::size_t>(id);
|
||||
|
||||
std::unique_lock lk(mtx);
|
||||
if(idx >= vec.size())
|
||||
vec.resize(idx + 1);
|
||||
|
||||
vec[idx] = BindDomainKeyInfo{std::move(domain), std::move(key)};
|
||||
}
|
||||
|
||||
void _drainNew(Enum type, std::vector<ResourceId>& out) {
|
||||
out.clear();
|
||||
auto& shards = _Shards[static_cast<size_t>(type)];
|
||||
|
||||
// Можно добавить reserve по эвристике
|
||||
for (auto& sh : shards) {
|
||||
std::unique_lock lk(sh.mutex);
|
||||
if (sh.newlyInserted.empty()) continue;
|
||||
|
||||
const auto old = out.size();
|
||||
out.resize(old + sh.newlyInserted.size());
|
||||
std::copy(sh.newlyInserted.begin(), sh.newlyInserted.end(), out.begin() + old);
|
||||
sh.newlyInserted.clear();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace LV
|
||||
@@ -17,7 +17,7 @@ bool SocketServer::isStopped() {
|
||||
coro<void> SocketServer::run(std::function<coro<>(tcp::socket)> onConnect) {
|
||||
while(true) { // TODO: ловить ошибки на async_accept
|
||||
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) {
|
||||
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))
|
||||
@@ -31,21 +31,13 @@ AsyncSocket::~AsyncSocket() {
|
||||
if(SendPackets.Context)
|
||||
SendPackets.Context->NeedShutdown = true;
|
||||
|
||||
{
|
||||
boost::lock_guard lock(SendPackets.Mtx);
|
||||
|
||||
SendPackets.SenderGuard.cancel();
|
||||
WorkDeadline.cancel();
|
||||
}
|
||||
boost::unique_lock lock(SendPackets.Mtx);
|
||||
|
||||
if(Socket.is_open())
|
||||
try { Socket.close(); } catch(...) {}
|
||||
}
|
||||
|
||||
void AsyncSocket::pushPackets(std::vector<Packet> *simplePackets, std::vector<SmartPacket> *smartPackets) {
|
||||
if(simplePackets->empty() && (!smartPackets || smartPackets->empty()))
|
||||
return;
|
||||
|
||||
boost::unique_lock lock(SendPackets.Mtx);
|
||||
|
||||
if(Socket.is_open()
|
||||
@@ -58,6 +50,8 @@ void AsyncSocket::pushPackets(std::vector<Packet> *simplePackets, std::vector<Sm
|
||||
// TODO: std::cout << "Передоз пакетами, сокет закрыт" << std::endl;
|
||||
}
|
||||
|
||||
bool wasPackets = SendPackets.SimpleBuffer.size() || SendPackets.SmartBuffer.size();
|
||||
|
||||
if(!Socket.is_open()) {
|
||||
if(simplePackets)
|
||||
simplePackets->clear();
|
||||
@@ -89,8 +83,7 @@ void AsyncSocket::pushPackets(std::vector<Packet> *simplePackets, std::vector<Sm
|
||||
|
||||
SendPackets.SizeInQueue += addedSize;
|
||||
|
||||
if(SendPackets.WaitForSemaphore) {
|
||||
SendPackets.WaitForSemaphore = false;
|
||||
if(!wasPackets) {
|
||||
SendPackets.Semaphore.cancel();
|
||||
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() {
|
||||
if(Socket.is_open() && !ReadShutdowned) {
|
||||
ReadShutdowned = true;
|
||||
// TODO:
|
||||
try { Socket.shutdown(boost::asio::socket_base::shutdown_receive); } catch(...) {}
|
||||
}
|
||||
}
|
||||
|
||||
void AsyncSocket::close() {
|
||||
if(Socket.is_open()) {
|
||||
Socket.close();
|
||||
ReadShutdowned = true;
|
||||
}
|
||||
}
|
||||
|
||||
coro<> AsyncSocket::waitForSend() {
|
||||
asio::deadline_timer waiter(IOC);
|
||||
|
||||
@@ -150,12 +151,19 @@ coro<> AsyncSocket::runSender(std::shared_ptr<AsyncContext> context) {
|
||||
while(!context->NeedShutdown) {
|
||||
{
|
||||
boost::unique_lock lock(SendPackets.Mtx);
|
||||
if(SendPackets.SimpleBuffer.empty() && SendPackets.SmartBuffer.empty()) {
|
||||
SendPackets.WaitForSemaphore = true;
|
||||
auto coroutine = SendPackets.Semaphore.async_wait();
|
||||
lock.unlock();
|
||||
|
||||
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;
|
||||
} else {
|
||||
for(int cycle = 0; cycle < 2; cycle++, NextBuffer++) {
|
||||
|
||||
@@ -1,32 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
// TODO: Всё это надо переписать
|
||||
|
||||
|
||||
#include "MemoryPool.hpp"
|
||||
#include "Async.hpp"
|
||||
#include "TOSLib.hpp"
|
||||
#include "boost/asio/io_context.hpp"
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/asio/buffer.hpp>
|
||||
#include <boost/asio/write.hpp>
|
||||
#include <boost/thread.hpp>
|
||||
#include <boost/circular_buffer.hpp>
|
||||
#include <type_traits>
|
||||
|
||||
namespace LV::Net {
|
||||
|
||||
class SocketServer : public AsyncObject {
|
||||
class SocketServer {
|
||||
protected:
|
||||
asio::io_context &IOC;
|
||||
tcp::acceptor Acceptor;
|
||||
|
||||
public:
|
||||
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);
|
||||
|
||||
co_spawn(run(std::move(onConnect)));
|
||||
asio::co_spawn(IOC, run(std::move(onConnect)), asio::detached);
|
||||
}
|
||||
|
||||
bool isStopped();
|
||||
@@ -39,10 +37,10 @@ protected:
|
||||
};
|
||||
|
||||
#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; }
|
||||
#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) {
|
||||
if constexpr (sizeof(T) == 1) {
|
||||
return u;
|
||||
@@ -64,7 +62,7 @@ protected:
|
||||
using NetPool = BoostPool<12, 14>;
|
||||
|
||||
class Packet {
|
||||
static constexpr size_t MAX_PACKET_SIZE = 1 << 24;
|
||||
static constexpr size_t MAX_PACKET_SIZE = 1 << 16;
|
||||
uint16_t Size = 0;
|
||||
std::vector<NetPool::PagePtr> Pages;
|
||||
|
||||
@@ -108,7 +106,7 @@ protected:
|
||||
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) {
|
||||
u = swapEndian(u);
|
||||
write((const std::byte*) &u, sizeof(u));
|
||||
@@ -129,7 +127,7 @@ protected:
|
||||
inline uint16_t size() const { return Size; }
|
||||
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) {
|
||||
if constexpr (std::is_convertible_v<T, std::string_view>)
|
||||
return write((std::string_view) value);
|
||||
@@ -146,7 +144,7 @@ protected:
|
||||
Size = 0;
|
||||
}
|
||||
|
||||
Packet& complite(std::u8string &out) {
|
||||
Packet& complite(std::vector<std::byte> &out) {
|
||||
out.resize(Size);
|
||||
|
||||
for(size_t pos = 0; pos < Size; pos += NetPool::PageSize) {
|
||||
@@ -157,8 +155,8 @@ protected:
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::u8string complite() {
|
||||
std::u8string out;
|
||||
std::vector<std::byte> complite() {
|
||||
std::vector<std::byte> out;
|
||||
complite(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 {
|
||||
public:
|
||||
std::function<bool()> IsStillRelevant;
|
||||
std::function<std::optional<SmartPacket>()> OnSend;
|
||||
};
|
||||
|
||||
class AsyncSocket : public AsyncObject {
|
||||
class AsyncSocket {
|
||||
asio::io_context &IOC;
|
||||
NetPool::Array<32> RecvBuffer, SendBuffer;
|
||||
size_t RecvPos = 0, RecvSize = 0, SendSize = 0;
|
||||
bool ReadShutdowned = false;
|
||||
tcp::socket Socket;
|
||||
|
||||
static constexpr uint32_t
|
||||
MAX_SIMPLE_PACKETS = 16384,
|
||||
MAX_SIMPLE_PACKETS = 8192,
|
||||
MAX_SMART_PACKETS = MAX_SIMPLE_PACKETS/4,
|
||||
MAX_PACKETS_SIZE_IN_WAIT = 1 << 26;
|
||||
MAX_PACKETS_SIZE_IN_WAIT = 1 << 24;
|
||||
|
||||
struct AsyncContext {
|
||||
volatile bool NeedShutdown = false, RunSendShutdowned = false;
|
||||
@@ -257,21 +197,20 @@ protected:
|
||||
|
||||
struct SendPacketsObj {
|
||||
boost::mutex Mtx;
|
||||
bool WaitForSemaphore = false;
|
||||
asio::deadline_timer Semaphore, SenderGuard;
|
||||
asio::deadline_timer Semaphore;
|
||||
boost::circular_buffer_space_optimized<Packet> SimpleBuffer;
|
||||
boost::circular_buffer_space_optimized<SmartPacket> SmartBuffer;
|
||||
size_t SizeInQueue = 0;
|
||||
std::shared_ptr<AsyncContext> Context;
|
||||
|
||||
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;
|
||||
|
||||
public:
|
||||
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.SmartBuffer.set_capacity(SendPackets.SimpleBuffer.capacity()/4);
|
||||
@@ -279,10 +218,10 @@ protected:
|
||||
|
||||
boost::asio::socket_base::linger optionLinger(true, 4); // После закрытия сокета оставшиеся данные будут доставлены
|
||||
Socket.set_option(optionLinger);
|
||||
boost::asio::ip::tcp::no_delay optionNoDelay(true); // Отключает попытки объединить данные в крупные пакеты
|
||||
boost::asio::ip::tcp::no_delay optionNoDelay(true); // Отключает попытки объёденить данные в крупные пакеты
|
||||
Socket.set_option(optionNoDelay);
|
||||
|
||||
co_spawn(runSender(SendPackets.Context));
|
||||
asio::co_spawn(IOC, runSender(SendPackets.Context), asio::detached);
|
||||
}
|
||||
|
||||
~AsyncSocket();
|
||||
@@ -300,10 +239,11 @@ protected:
|
||||
|
||||
coro<> read(std::byte *data, uint32_t size);
|
||||
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() {
|
||||
if constexpr(std::is_floating_point_v<T> or std::is_integral_v<T>) {
|
||||
if constexpr(std::is_integral_v<T>) {
|
||||
T value;
|
||||
co_await read((std::byte*) &value, sizeof(value));
|
||||
co_return swapEndian(value);
|
||||
@@ -321,9 +261,9 @@ protected:
|
||||
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) {
|
||||
if constexpr(std::is_floating_point_v<T> or std::is_integral_v<T>) {
|
||||
if constexpr(std::is_integral_v<T>) {
|
||||
T value;
|
||||
co_await read(socket, (std::byte*) &value, sizeof(value));
|
||||
co_return swapEndian(value);
|
||||
@@ -338,7 +278,7 @@ protected:
|
||||
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) {
|
||||
u = swapEndian(u);
|
||||
co_await write(socket, (const std::byte*) &u, sizeof(u));
|
||||
|
||||
@@ -1,483 +0,0 @@
|
||||
#include "Net2.hpp"
|
||||
|
||||
#include <boost/asio/buffer.hpp>
|
||||
#include <boost/asio/error.hpp>
|
||||
#include <boost/asio/read.hpp>
|
||||
#include <boost/asio/write.hpp>
|
||||
#include <boost/system/system_error.hpp>
|
||||
#include <algorithm>
|
||||
#include <tuple>
|
||||
|
||||
namespace LV::Net2 {
|
||||
|
||||
using namespace TOS;
|
||||
|
||||
namespace {
|
||||
|
||||
struct HeaderFields {
|
||||
uint32_t size = 0;
|
||||
uint16_t type = 0;
|
||||
Priority priority = Priority::Normal;
|
||||
FrameFlags flags = FrameFlags::None;
|
||||
uint32_t streamId = 0;
|
||||
};
|
||||
|
||||
std::array<std::byte, AsyncSocket::kHeaderSize> encodeHeader(const HeaderFields &h) {
|
||||
std::array<std::byte, AsyncSocket::kHeaderSize> out{};
|
||||
uint32_t sizeNet = detail::toNetwork(h.size);
|
||||
uint16_t typeNet = detail::toNetwork(h.type);
|
||||
uint32_t streamNet = detail::toNetwork(h.streamId);
|
||||
|
||||
std::memcpy(out.data(), &sizeNet, sizeof(sizeNet));
|
||||
std::memcpy(out.data() + 4, &typeNet, sizeof(typeNet));
|
||||
out[6] = std::byte(static_cast<uint8_t>(h.priority));
|
||||
out[7] = std::byte(static_cast<uint8_t>(h.flags));
|
||||
std::memcpy(out.data() + 8, &streamNet, sizeof(streamNet));
|
||||
return out;
|
||||
}
|
||||
|
||||
HeaderFields decodeHeader(const std::array<std::byte, AsyncSocket::kHeaderSize> &in) {
|
||||
HeaderFields h{};
|
||||
std::memcpy(&h.size, in.data(), sizeof(h.size));
|
||||
std::memcpy(&h.type, in.data() + 4, sizeof(h.type));
|
||||
h.priority = static_cast<Priority>(std::to_integer<uint8_t>(in[6]));
|
||||
h.flags = static_cast<FrameFlags>(std::to_integer<uint8_t>(in[7]));
|
||||
std::memcpy(&h.streamId, in.data() + 8, sizeof(h.streamId));
|
||||
|
||||
h.size = detail::fromNetwork(h.size);
|
||||
h.type = detail::fromNetwork(h.type);
|
||||
h.streamId = detail::fromNetwork(h.streamId);
|
||||
return h;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
PacketWriter& PacketWriter::writeBytes(std::span<const std::byte> data) {
|
||||
Buffer.insert(Buffer.end(), data.begin(), data.end());
|
||||
return *this;
|
||||
}
|
||||
|
||||
PacketWriter& PacketWriter::writeString(std::string_view str) {
|
||||
write<uint32_t>(static_cast<uint32_t>(str.size()));
|
||||
auto bytes = std::as_bytes(std::span<const char>(str.data(), str.size()));
|
||||
Buffer.insert(Buffer.end(), bytes.begin(), bytes.end());
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::vector<std::byte> PacketWriter::release() {
|
||||
std::vector<std::byte> out = std::move(Buffer);
|
||||
Buffer.clear();
|
||||
return out;
|
||||
}
|
||||
|
||||
void PacketWriter::clear() {
|
||||
Buffer.clear();
|
||||
}
|
||||
|
||||
PacketReader::PacketReader(std::span<const std::byte> data)
|
||||
: Data(data)
|
||||
{
|
||||
}
|
||||
|
||||
void PacketReader::readBytes(std::span<std::byte> out) {
|
||||
require(out.size());
|
||||
std::memcpy(out.data(), Data.data() + Pos, out.size());
|
||||
Pos += out.size();
|
||||
}
|
||||
|
||||
std::string PacketReader::readString() {
|
||||
uint32_t size = read<uint32_t>();
|
||||
require(size);
|
||||
std::string out(size, '\0');
|
||||
std::memcpy(out.data(), Data.data() + Pos, size);
|
||||
Pos += size;
|
||||
return out;
|
||||
}
|
||||
|
||||
void PacketReader::require(size_t size) {
|
||||
if(Data.size() - Pos < size)
|
||||
MAKE_ERROR("Net2::PacketReader: not enough data");
|
||||
}
|
||||
|
||||
SocketServer::SocketServer(asio::io_context &ioc, std::function<coro<>(tcp::socket)> &&onConnect, uint16_t port)
|
||||
: AsyncObject(ioc), Acceptor(ioc, tcp::endpoint(tcp::v4(), port))
|
||||
{
|
||||
assert(onConnect);
|
||||
co_spawn(run(std::move(onConnect)));
|
||||
}
|
||||
|
||||
bool SocketServer::isStopped() const {
|
||||
return !Acceptor.is_open();
|
||||
}
|
||||
|
||||
uint16_t SocketServer::getPort() const {
|
||||
return Acceptor.local_endpoint().port();
|
||||
}
|
||||
|
||||
coro<void> SocketServer::run(std::function<coro<>(tcp::socket)> onConnect) {
|
||||
while(true) {
|
||||
try {
|
||||
co_spawn(onConnect(co_await Acceptor.async_accept()));
|
||||
} catch(const std::exception &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))
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AsyncSocket::SendQueue::SendQueue(asio::io_context &ioc)
|
||||
: semaphore(ioc)
|
||||
{
|
||||
semaphore.expires_at(std::chrono::steady_clock::time_point::max());
|
||||
}
|
||||
|
||||
bool AsyncSocket::SendQueue::empty() const {
|
||||
for(const auto &queue : queues) {
|
||||
if(!queue.empty())
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
AsyncSocket::AsyncSocket(asio::io_context &ioc, tcp::socket &&socket, Limits limits)
|
||||
: AsyncObject(ioc), LimitsCfg(limits), Socket(std::move(socket)), Outgoing(ioc)
|
||||
{
|
||||
Context = std::make_shared<AsyncContext>();
|
||||
|
||||
boost::asio::socket_base::linger optionLinger(true, 4);
|
||||
Socket.set_option(optionLinger);
|
||||
boost::asio::ip::tcp::no_delay optionNoDelay(true);
|
||||
Socket.set_option(optionNoDelay);
|
||||
|
||||
co_spawn(sendLoop());
|
||||
}
|
||||
|
||||
AsyncSocket::~AsyncSocket() {
|
||||
if(Context)
|
||||
Context->needShutdown.store(true);
|
||||
|
||||
{
|
||||
boost::lock_guard lock(Outgoing.mtx);
|
||||
Outgoing.semaphore.cancel();
|
||||
WorkDeadline.cancel();
|
||||
}
|
||||
|
||||
if(Socket.is_open())
|
||||
try { Socket.close(); } catch(...) {}
|
||||
}
|
||||
|
||||
void AsyncSocket::enqueue(OutgoingMessage &&msg) {
|
||||
if(msg.payload.size() > LimitsCfg.maxMessageSize) {
|
||||
setError("Net2::AsyncSocket: message too large");
|
||||
close();
|
||||
return;
|
||||
}
|
||||
|
||||
boost::unique_lock lock(Outgoing.mtx);
|
||||
const size_t msgSize = msg.payload.size();
|
||||
const size_t lowIndex = static_cast<size_t>(Priority::Low);
|
||||
|
||||
if(msg.priority == Priority::Low) {
|
||||
while(Outgoing.bytesInLow + msgSize > LimitsCfg.maxLowPriorityBytes && !Outgoing.queues[lowIndex].empty()) {
|
||||
Outgoing.bytesInQueue -= Outgoing.queues[lowIndex].front().payload.size();
|
||||
Outgoing.bytesInLow -= Outgoing.queues[lowIndex].front().payload.size();
|
||||
Outgoing.queues[lowIndex].pop_front();
|
||||
}
|
||||
if(Outgoing.bytesInLow + msgSize > LimitsCfg.maxLowPriorityBytes) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(Outgoing.bytesInQueue + msgSize > LimitsCfg.maxQueueBytes) {
|
||||
dropLow(msgSize);
|
||||
if(Outgoing.bytesInQueue + msgSize > LimitsCfg.maxQueueBytes) {
|
||||
if(msg.dropIfOverloaded)
|
||||
return;
|
||||
setError("Net2::AsyncSocket: send queue overflow");
|
||||
close();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const size_t idx = static_cast<size_t>(msg.priority);
|
||||
Outgoing.bytesInQueue += msgSize;
|
||||
if(msg.priority == Priority::Low)
|
||||
Outgoing.bytesInLow += msgSize;
|
||||
Outgoing.queues[idx].push_back(std::move(msg));
|
||||
|
||||
if(Outgoing.waiting) {
|
||||
Outgoing.waiting = false;
|
||||
Outgoing.semaphore.cancel();
|
||||
Outgoing.semaphore.expires_at(std::chrono::steady_clock::time_point::max());
|
||||
}
|
||||
}
|
||||
|
||||
coro<IncomingMessage> AsyncSocket::readMessage() {
|
||||
while(true) {
|
||||
std::array<std::byte, kHeaderSize> headerBytes{};
|
||||
co_await readExact(headerBytes.data(), headerBytes.size());
|
||||
HeaderFields header = decodeHeader(headerBytes);
|
||||
|
||||
if(header.size > LimitsCfg.maxFrameSize)
|
||||
MAKE_ERROR("Net2::AsyncSocket: frame too large");
|
||||
|
||||
std::vector<std::byte> chunk(header.size);
|
||||
if(header.size)
|
||||
co_await readExact(chunk.data(), chunk.size());
|
||||
|
||||
if(header.streamId != 0) {
|
||||
if(Fragments.size() >= LimitsCfg.maxOpenStreams && !Fragments.contains(header.streamId))
|
||||
MAKE_ERROR("Net2::AsyncSocket: too many open streams");
|
||||
|
||||
FragmentState &state = Fragments[header.streamId];
|
||||
if(state.data.empty()) {
|
||||
state.type = header.type;
|
||||
state.priority = header.priority;
|
||||
}
|
||||
|
||||
if(state.data.size() + chunk.size() > LimitsCfg.maxMessageSize)
|
||||
MAKE_ERROR("Net2::AsyncSocket: reassembled message too large");
|
||||
|
||||
state.data.insert(state.data.end(), chunk.begin(), chunk.end());
|
||||
|
||||
if(!hasFlag(header.flags, FrameFlags::HasMore)) {
|
||||
IncomingMessage msg{state.type, state.priority, std::move(state.data)};
|
||||
Fragments.erase(header.streamId);
|
||||
co_return msg;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if(hasFlag(header.flags, FrameFlags::HasMore))
|
||||
MAKE_ERROR("Net2::AsyncSocket: stream id missing for fragmented frame");
|
||||
|
||||
IncomingMessage msg{header.type, header.priority, std::move(chunk)};
|
||||
co_return msg;
|
||||
}
|
||||
}
|
||||
|
||||
coro<> AsyncSocket::readLoop(std::function<coro<>(IncomingMessage&&)> onMessage) {
|
||||
while(isAlive()) {
|
||||
IncomingMessage msg = co_await readMessage();
|
||||
co_await onMessage(std::move(msg));
|
||||
}
|
||||
}
|
||||
|
||||
void AsyncSocket::closeRead() {
|
||||
if(Socket.is_open() && !Context->readClosed.exchange(true)) {
|
||||
try { Socket.shutdown(boost::asio::socket_base::shutdown_receive); } catch(...) {}
|
||||
}
|
||||
}
|
||||
|
||||
void AsyncSocket::close() {
|
||||
if(Context)
|
||||
Context->needShutdown.store(true);
|
||||
if(Socket.is_open())
|
||||
try { Socket.close(); } catch(...) {}
|
||||
}
|
||||
|
||||
bool AsyncSocket::isAlive() const {
|
||||
return Context && !Context->needShutdown.load() && !Context->senderStopped.load() && Socket.is_open();
|
||||
}
|
||||
|
||||
std::string AsyncSocket::getError() const {
|
||||
boost::lock_guard lock(Context->errorMtx);
|
||||
return Context->error;
|
||||
}
|
||||
|
||||
coro<> AsyncSocket::sendLoop() {
|
||||
try {
|
||||
while(!Context->needShutdown.load()) {
|
||||
OutgoingMessage msg;
|
||||
{
|
||||
boost::unique_lock lock(Outgoing.mtx);
|
||||
if(Outgoing.empty()) {
|
||||
Outgoing.waiting = true;
|
||||
auto coroutine = Outgoing.semaphore.async_wait();
|
||||
lock.unlock();
|
||||
try { co_await std::move(coroutine); } catch(...) {}
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!popNext(msg))
|
||||
continue;
|
||||
}
|
||||
|
||||
co_await sendMessage(std::move(msg));
|
||||
}
|
||||
} catch(const std::exception &exc) {
|
||||
setError(exc.what());
|
||||
} catch(...) {
|
||||
setError("Net2::AsyncSocket: send loop stopped");
|
||||
}
|
||||
|
||||
Context->senderStopped.store(true);
|
||||
}
|
||||
|
||||
coro<> AsyncSocket::sendMessage(OutgoingMessage &&msg) {
|
||||
const size_t total = msg.payload.size();
|
||||
if(total <= LimitsCfg.maxFrameSize) {
|
||||
co_await sendFrame(msg.type, msg.priority, FrameFlags::None, 0, msg.payload);
|
||||
co_return;
|
||||
}
|
||||
|
||||
if(!msg.allowFragment) {
|
||||
setError("Net2::AsyncSocket: message requires fragmentation");
|
||||
close();
|
||||
co_return;
|
||||
}
|
||||
|
||||
uint32_t streamId = NextStreamId++;
|
||||
if(streamId == 0)
|
||||
streamId = NextStreamId++;
|
||||
|
||||
size_t offset = 0;
|
||||
while(offset < total) {
|
||||
const size_t chunk = std::min(LimitsCfg.maxFrameSize, total - offset);
|
||||
const bool more = (offset + chunk) < total;
|
||||
FrameFlags flags = more ? FrameFlags::HasMore : FrameFlags::None;
|
||||
std::span<const std::byte> view(msg.payload.data() + offset, chunk);
|
||||
co_await sendFrame(msg.type, msg.priority, flags, streamId, view);
|
||||
offset += chunk;
|
||||
}
|
||||
}
|
||||
|
||||
coro<> AsyncSocket::sendFrame(uint16_t type, Priority priority, FrameFlags flags, uint32_t streamId,
|
||||
std::span<const std::byte> payload) {
|
||||
HeaderFields header{
|
||||
.size = static_cast<uint32_t>(payload.size()),
|
||||
.type = type,
|
||||
.priority = priority,
|
||||
.flags = flags,
|
||||
.streamId = streamId
|
||||
};
|
||||
auto headerBytes = encodeHeader(header);
|
||||
std::array<asio::const_buffer, 2> buffers{
|
||||
asio::buffer(headerBytes),
|
||||
asio::buffer(payload.data(), payload.size())
|
||||
};
|
||||
if(payload.empty())
|
||||
co_await asio::async_write(Socket, asio::buffer(headerBytes));
|
||||
else
|
||||
co_await asio::async_write(Socket, buffers);
|
||||
}
|
||||
|
||||
coro<> AsyncSocket::readExact(std::byte *data, size_t size) {
|
||||
if(size == 0)
|
||||
co_return;
|
||||
co_await asio::async_read(Socket, asio::buffer(data, size));
|
||||
}
|
||||
|
||||
bool AsyncSocket::popNext(OutgoingMessage &out) {
|
||||
static constexpr int kWeights[4] = {8, 4, 2, 1};
|
||||
|
||||
for(int attempt = 0; attempt < 4; ++attempt) {
|
||||
const uint8_t idx = static_cast<uint8_t>((Outgoing.nextIndex + attempt) % 4);
|
||||
auto &queue = Outgoing.queues[idx];
|
||||
if(queue.empty())
|
||||
continue;
|
||||
|
||||
if(Outgoing.credits[idx] <= 0)
|
||||
Outgoing.credits[idx] = kWeights[idx];
|
||||
|
||||
if(Outgoing.credits[idx] <= 0)
|
||||
continue;
|
||||
|
||||
out = std::move(queue.front());
|
||||
queue.pop_front();
|
||||
Outgoing.credits[idx]--;
|
||||
Outgoing.nextIndex = idx;
|
||||
|
||||
const size_t msgSize = out.payload.size();
|
||||
Outgoing.bytesInQueue -= msgSize;
|
||||
if(idx == static_cast<uint8_t>(Priority::Low))
|
||||
Outgoing.bytesInLow -= msgSize;
|
||||
return true;
|
||||
}
|
||||
|
||||
for(int i = 0; i < 4; ++i)
|
||||
Outgoing.credits[i] = kWeights[i];
|
||||
return false;
|
||||
}
|
||||
|
||||
void AsyncSocket::dropLow(size_t needBytes) {
|
||||
const size_t lowIndex = static_cast<size_t>(Priority::Low);
|
||||
while(Outgoing.bytesInQueue + needBytes > LimitsCfg.maxQueueBytes && !Outgoing.queues[lowIndex].empty()) {
|
||||
const size_t size = Outgoing.queues[lowIndex].front().payload.size();
|
||||
Outgoing.bytesInQueue -= size;
|
||||
Outgoing.bytesInLow -= size;
|
||||
Outgoing.queues[lowIndex].pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
void AsyncSocket::setError(const std::string &msg) {
|
||||
if(!Context)
|
||||
return;
|
||||
boost::lock_guard lock(Context->errorMtx);
|
||||
Context->error = msg;
|
||||
}
|
||||
|
||||
coro<tcp::socket> asyncConnectTo(const std::string &address,
|
||||
std::function<void(const std::string&)> onProgress) {
|
||||
std::string progress;
|
||||
auto addLog = [&](const std::string &msg) {
|
||||
progress += '\n';
|
||||
progress += msg;
|
||||
if(onProgress)
|
||||
onProgress('\n' + msg);
|
||||
};
|
||||
|
||||
auto ioc = co_await asio::this_coro::executor;
|
||||
|
||||
addLog("Parsing address " + address);
|
||||
auto re = Str::match(address, "((?:\\[[\\d\\w:]+\\])|(?:[\\d\\.]+))(?:\\:(\\d+))?");
|
||||
|
||||
std::vector<std::tuple<tcp::endpoint, std::string>> eps;
|
||||
|
||||
if(!re) {
|
||||
re = Str::match(address, "([-_\\.\\w\\d]+)(?:\\:(\\d+))?");
|
||||
if(!re)
|
||||
MAKE_ERROR("Failed to parse address");
|
||||
|
||||
tcp::resolver resv{ioc};
|
||||
tcp::resolver::results_type result;
|
||||
|
||||
addLog("Resolving name...");
|
||||
result = co_await resv.async_resolve(*re->at(1), re->at(2) ? *re->at(2) : "7890");
|
||||
|
||||
addLog("Got " + std::to_string(result.size()) + " endpoints");
|
||||
for(auto iter : result) {
|
||||
std::string addr = iter.endpoint().address().to_string() + ':' + std::to_string(iter.endpoint().port());
|
||||
std::string hostname = iter.host_name();
|
||||
if(hostname == addr)
|
||||
addLog("ep: " + addr);
|
||||
else
|
||||
addLog("ep: " + hostname + " (" + addr + ')');
|
||||
|
||||
eps.emplace_back(iter.endpoint(), iter.host_name());
|
||||
}
|
||||
} else {
|
||||
eps.emplace_back(tcp::endpoint{asio::ip::make_address(*re->at(1)),
|
||||
static_cast<uint16_t>(re->at(2) ? Str::toVal<int>(*re->at(2)) : 7890)},
|
||||
*re->at(1));
|
||||
}
|
||||
|
||||
for(auto [ep, hostname] : eps) {
|
||||
addLog("Connecting to " + hostname + " (" + ep.address().to_string() + ':'
|
||||
+ std::to_string(ep.port()) + ")");
|
||||
try {
|
||||
tcp::socket sock{ioc};
|
||||
co_await sock.async_connect(ep);
|
||||
addLog("Connected");
|
||||
co_return sock;
|
||||
} catch(const std::exception &exc) {
|
||||
addLog(std::string("Connect failed: ") + exc.what());
|
||||
}
|
||||
}
|
||||
|
||||
MAKE_ERROR("Unable to connect to server");
|
||||
}
|
||||
|
||||
} // namespace LV::Net2
|
||||
@@ -1,227 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "Async.hpp"
|
||||
#include "TOSLib.hpp"
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/thread.hpp>
|
||||
#include <array>
|
||||
#include <bit>
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace LV::Net2 {
|
||||
|
||||
namespace detail {
|
||||
|
||||
constexpr bool kLittleEndian = (std::endian::native == std::endian::little);
|
||||
|
||||
template<typename T>
|
||||
requires std::is_integral_v<T>
|
||||
inline T toNetwork(T value) {
|
||||
if constexpr (kLittleEndian && sizeof(T) > 1)
|
||||
return std::byteswap(value);
|
||||
return value;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
requires std::is_floating_point_v<T>
|
||||
inline T toNetwork(T value) {
|
||||
using U = std::conditional_t<sizeof(T) == 4, uint32_t, uint64_t>;
|
||||
U u = std::bit_cast<U>(value);
|
||||
u = toNetwork(u);
|
||||
return std::bit_cast<T>(u);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline T fromNetwork(T value) {
|
||||
return toNetwork(value);
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
enum class Priority : uint8_t {
|
||||
Realtime = 0,
|
||||
High = 1,
|
||||
Normal = 2,
|
||||
Low = 3
|
||||
};
|
||||
|
||||
enum class FrameFlags : uint8_t {
|
||||
None = 0,
|
||||
HasMore = 1
|
||||
};
|
||||
|
||||
inline FrameFlags operator|(FrameFlags a, FrameFlags b) {
|
||||
return static_cast<FrameFlags>(static_cast<uint8_t>(a) | static_cast<uint8_t>(b));
|
||||
}
|
||||
|
||||
inline bool hasFlag(FrameFlags value, FrameFlags flag) {
|
||||
return (static_cast<uint8_t>(value) & static_cast<uint8_t>(flag)) != 0;
|
||||
}
|
||||
|
||||
struct Limits {
|
||||
size_t maxFrameSize = 1 << 24;
|
||||
size_t maxMessageSize = 1 << 26;
|
||||
size_t maxQueueBytes = 1 << 27;
|
||||
size_t maxLowPriorityBytes = 1 << 26;
|
||||
size_t maxOpenStreams = 64;
|
||||
};
|
||||
|
||||
struct OutgoingMessage {
|
||||
uint16_t type = 0;
|
||||
Priority priority = Priority::Normal;
|
||||
bool dropIfOverloaded = false;
|
||||
bool allowFragment = true;
|
||||
std::vector<std::byte> payload;
|
||||
};
|
||||
|
||||
struct IncomingMessage {
|
||||
uint16_t type = 0;
|
||||
Priority priority = Priority::Normal;
|
||||
std::vector<std::byte> payload;
|
||||
};
|
||||
|
||||
class PacketWriter {
|
||||
public:
|
||||
PacketWriter& writeBytes(std::span<const std::byte> data);
|
||||
|
||||
template<typename T>
|
||||
requires (std::is_integral_v<T> || std::is_floating_point_v<T>)
|
||||
PacketWriter& write(T value) {
|
||||
T net = detail::toNetwork(value);
|
||||
std::array<std::byte, sizeof(T)> bytes{};
|
||||
std::memcpy(bytes.data(), &net, sizeof(T));
|
||||
Buffer.insert(Buffer.end(), bytes.begin(), bytes.end());
|
||||
return *this;
|
||||
}
|
||||
|
||||
PacketWriter& writeString(std::string_view str);
|
||||
|
||||
const std::vector<std::byte>& data() const { return Buffer; }
|
||||
std::vector<std::byte> release();
|
||||
void clear();
|
||||
|
||||
private:
|
||||
std::vector<std::byte> Buffer;
|
||||
};
|
||||
|
||||
class PacketReader {
|
||||
public:
|
||||
explicit PacketReader(std::span<const std::byte> data);
|
||||
|
||||
template<typename T>
|
||||
requires (std::is_integral_v<T> || std::is_floating_point_v<T>)
|
||||
T read() {
|
||||
require(sizeof(T));
|
||||
T net{};
|
||||
std::memcpy(&net, Data.data() + Pos, sizeof(T));
|
||||
Pos += sizeof(T);
|
||||
return detail::fromNetwork(net);
|
||||
}
|
||||
|
||||
void readBytes(std::span<std::byte> out);
|
||||
std::string readString();
|
||||
bool empty() const { return Pos >= Data.size(); }
|
||||
size_t remaining() const { return Data.size() - Pos; }
|
||||
|
||||
private:
|
||||
void require(size_t size);
|
||||
|
||||
size_t Pos = 0;
|
||||
std::span<const std::byte> Data;
|
||||
};
|
||||
|
||||
class SocketServer : public AsyncObject {
|
||||
public:
|
||||
SocketServer(asio::io_context &ioc, std::function<coro<>(tcp::socket)> &&onConnect, uint16_t port = 0);
|
||||
bool isStopped() const;
|
||||
uint16_t getPort() const;
|
||||
|
||||
private:
|
||||
coro<void> run(std::function<coro<>(tcp::socket)> onConnect);
|
||||
|
||||
tcp::acceptor Acceptor;
|
||||
};
|
||||
|
||||
class AsyncSocket : public AsyncObject {
|
||||
public:
|
||||
static constexpr size_t kHeaderSize = 12;
|
||||
|
||||
AsyncSocket(asio::io_context &ioc, tcp::socket &&socket, Limits limits = {});
|
||||
~AsyncSocket();
|
||||
|
||||
void enqueue(OutgoingMessage &&msg);
|
||||
coro<IncomingMessage> readMessage();
|
||||
coro<> readLoop(std::function<coro<>(IncomingMessage&&)> onMessage);
|
||||
|
||||
void closeRead();
|
||||
void close();
|
||||
bool isAlive() const;
|
||||
std::string getError() const;
|
||||
|
||||
private:
|
||||
struct FragmentState {
|
||||
uint16_t type = 0;
|
||||
Priority priority = Priority::Normal;
|
||||
std::vector<std::byte> data;
|
||||
};
|
||||
|
||||
struct AsyncContext {
|
||||
std::atomic_bool needShutdown{false};
|
||||
std::atomic_bool senderStopped{false};
|
||||
std::atomic_bool readClosed{false};
|
||||
boost::mutex errorMtx;
|
||||
std::string error;
|
||||
};
|
||||
|
||||
struct SendQueue {
|
||||
boost::mutex mtx;
|
||||
bool waiting = false;
|
||||
asio::steady_timer semaphore;
|
||||
std::deque<OutgoingMessage> queues[4];
|
||||
size_t bytesInQueue = 0;
|
||||
size_t bytesInLow = 0;
|
||||
uint8_t nextIndex = 0;
|
||||
int credits[4] = {8, 4, 2, 1};
|
||||
|
||||
explicit SendQueue(asio::io_context &ioc);
|
||||
bool empty() const;
|
||||
};
|
||||
|
||||
coro<> sendLoop();
|
||||
coro<> sendMessage(OutgoingMessage &&msg);
|
||||
coro<> sendFrame(uint16_t type, Priority priority, FrameFlags flags, uint32_t streamId,
|
||||
std::span<const std::byte> payload);
|
||||
|
||||
coro<> readExact(std::byte *data, size_t size);
|
||||
|
||||
bool popNext(OutgoingMessage &out);
|
||||
void dropLow(size_t needBytes);
|
||||
void setError(const std::string &msg);
|
||||
|
||||
Limits LimitsCfg;
|
||||
tcp::socket Socket;
|
||||
SendQueue Outgoing;
|
||||
std::shared_ptr<AsyncContext> Context;
|
||||
std::unordered_map<uint32_t, FragmentState> Fragments;
|
||||
uint32_t NextStreamId = 1;
|
||||
};
|
||||
|
||||
coro<tcp::socket> asyncConnectTo(const std::string &address,
|
||||
std::function<void(const std::string&)> onProgress = nullptr);
|
||||
|
||||
} // namespace LV::Net2
|
||||
@@ -24,45 +24,36 @@ struct PacketQuat {
|
||||
z = (quat.z+1)/2*0x3ff,
|
||||
w = (quat.w+1)/2*0x3ff;
|
||||
|
||||
uint64_t value = 0;
|
||||
for(uint8_t &val : Data)
|
||||
val = 0;
|
||||
|
||||
value |= x & 0x3ff;
|
||||
value |= uint64_t(y & 0x3ff) << 10;
|
||||
value |= uint64_t(z & 0x3ff) << 20;
|
||||
value |= uint64_t(w & 0x3ff) << 30;
|
||||
|
||||
for(int iter = 0; iter < 5; iter++)
|
||||
Data[iter] = (value >> (iter*8)) & 0xff;
|
||||
*(uint16_t*) Data |= x;
|
||||
*(uint16_t*) (Data+1) |= y << 2;
|
||||
*(uint16_t*) (Data+2) |= z << 4;
|
||||
*(uint16_t*) (Data+3) |= w << 6;
|
||||
}
|
||||
|
||||
glm::quat toQuat() const {
|
||||
uint64_t value = 0;
|
||||
|
||||
for(int iter = 0; iter < 5; iter++)
|
||||
value |= uint64_t(Data[iter]) << (iter*8);
|
||||
|
||||
const uint64_t &data = (const uint64_t&) *Data;
|
||||
uint16_t
|
||||
x = value & 0x3ff,
|
||||
y = (value >> 10) & 0x3ff,
|
||||
z = (value >> 20) & 0x3ff,
|
||||
w = (value >> 30) & 0x3ff;
|
||||
x = data & 0x3ff,
|
||||
y = (data >> 10) & 0x3ff,
|
||||
z = (data >> 20) & 0x3ff,
|
||||
w = (data >> 30) & 0x3ff;
|
||||
|
||||
float fx = (float(x)/0x3ff)*2-1;
|
||||
float fy = (float(y)/0x3ff)*2-1;
|
||||
float fz = (float(z)/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
|
||||
0 - Системное
|
||||
0 -
|
||||
1 -
|
||||
2 - Новая позиция камеры WorldId_c+ObjectPos+PacketQuat
|
||||
3 - Изменение блока
|
||||
0 - Новая позиция камеры WorldId_c+ObjectPos+PacketQuat
|
||||
|
||||
*/
|
||||
|
||||
@@ -75,35 +66,111 @@ enum struct L1 : uint8_t {
|
||||
enum struct L2System : uint8_t {
|
||||
InitEnd,
|
||||
Disconnect,
|
||||
Test_CAM_PYR_POS,
|
||||
BlockChange,
|
||||
ResourceRequest,
|
||||
ReloadMods
|
||||
Test_CAM_PYR_POS
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
enum struct ToClient : uint8_t {
|
||||
Init, // Первый пакет от сервера
|
||||
Disconnect, // Отключаем клиента
|
||||
namespace ToClient {
|
||||
|
||||
AssetsBindDK, // Привязка AssetsId к домен+ключ
|
||||
AssetsBindHH, // Привязка AssetsId к hash+header
|
||||
AssetsInitSend, // Начало отправки запрошенного клиентом ресурса
|
||||
AssetsNextSend, // Продолжение отправки ресурса
|
||||
/*
|
||||
uint8_t+uint8_t
|
||||
0 - Системное
|
||||
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
|
||||
|
||||
DefinitionsFull, // Полная информация о профилях контента
|
||||
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
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,406 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <optional>
|
||||
#include <functional>
|
||||
#include <unordered_map>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
|
||||
// ========================
|
||||
// External texture view
|
||||
// ========================
|
||||
struct Texture {
|
||||
uint32_t Width, Height;
|
||||
const uint32_t* Pixels; // assumed 0xAARRGGBB
|
||||
};
|
||||
|
||||
// ========================
|
||||
// Bytecode words are uint8_t (1 byte machine word)
|
||||
// TexId is u24 (3 bytes, little-endian)
|
||||
// Subprogram refs use off24/len24 in BYTES (<=65535)
|
||||
// ========================
|
||||
class TexturePipelineProgram {
|
||||
public:
|
||||
using Word = uint8_t;
|
||||
|
||||
enum AnimFlags : Word {
|
||||
AnimSmooth = 1u << 0,
|
||||
AnimHorizontal = 1u << 1,
|
||||
AnimGrid = 1u << 2
|
||||
};
|
||||
|
||||
static constexpr uint16_t DefaultAnimFpsQ = uint16_t(8u * 256u);
|
||||
static constexpr size_t MaxCodeBytes = (1u << 16) + 1u; // 65537
|
||||
|
||||
struct OwnedTexture {
|
||||
uint32_t Width = 0, Height = 0;
|
||||
std::vector<uint32_t> Pixels;
|
||||
Texture view() const { return Texture{Width, Height, Pixels.data()}; }
|
||||
};
|
||||
|
||||
using IdResolverFunc = std::function<std::optional<uint32_t>(std::string_view)>;
|
||||
using TextureProviderFunc = std::function<std::optional<Texture>(uint32_t)>;
|
||||
|
||||
// Patch point to 3 consecutive bytes where u24 texId lives (b0,b1,b2)
|
||||
struct Patch {
|
||||
size_t ByteIndex0 = 0; // Code_[i], Code_[i+1], Code_[i+2]
|
||||
std::string Name;
|
||||
};
|
||||
|
||||
bool compile(std::string_view src, std::string* err = nullptr);
|
||||
bool link(const IdResolverFunc& resolver, std::string* err = nullptr);
|
||||
bool bake(const TextureProviderFunc& provider, OwnedTexture& out, std::string* err = nullptr) const;
|
||||
bool bake(const TextureProviderFunc& provider, OwnedTexture& out, double timeSeconds, std::string* err = nullptr) const;
|
||||
|
||||
const std::vector<Word>& words() const { return Code_; }
|
||||
const std::vector<Patch>& patches() const { return Patches_; }
|
||||
|
||||
std::vector<uint8_t> toBytes() const { return Code_; }
|
||||
|
||||
struct AnimSpec {
|
||||
uint32_t TexId = 0;
|
||||
bool HasTexId = false;
|
||||
uint16_t FrameW = 0;
|
||||
uint16_t FrameH = 0;
|
||||
uint16_t FrameCount = 0;
|
||||
uint16_t FpsQ = 0;
|
||||
uint16_t Flags = 0;
|
||||
};
|
||||
|
||||
static std::vector<AnimSpec> extractAnimationSpecs(const Word* code, size_t size);
|
||||
static bool remapTexIds(std::vector<uint8_t>& code, const std::vector<uint32_t>& remap, std::string* err = nullptr);
|
||||
|
||||
static std::vector<AnimSpec> extractAnimationSpecs(const std::vector<Word>& code) {
|
||||
return extractAnimationSpecs(code.data(), code.size());
|
||||
}
|
||||
|
||||
void fromBytes(std::vector<uint8_t> bytes);
|
||||
|
||||
private:
|
||||
// ========================
|
||||
// Byte helpers (little-endian)
|
||||
// ========================
|
||||
static inline uint16_t _rd16(const std::vector<uint8_t>& c, size_t& ip) {
|
||||
uint16_t v = uint16_t(c[ip]) | (uint16_t(c[ip+1]) << 8);
|
||||
ip += 2;
|
||||
return v;
|
||||
}
|
||||
static inline uint32_t _rd24(const std::vector<uint8_t>& c, size_t& ip) {
|
||||
uint32_t v = uint32_t(c[ip]) | (uint32_t(c[ip+1]) << 8) | (uint32_t(c[ip+2]) << 16);
|
||||
ip += 3;
|
||||
return v;
|
||||
}
|
||||
static inline uint32_t _rd32(const std::vector<uint8_t>& c, size_t& ip) {
|
||||
uint32_t v = uint32_t(c[ip]) |
|
||||
(uint32_t(c[ip+1]) << 8) |
|
||||
(uint32_t(c[ip+2]) << 16) |
|
||||
(uint32_t(c[ip+3]) << 24);
|
||||
ip += 4;
|
||||
return v;
|
||||
}
|
||||
|
||||
static inline void _wr8 (std::vector<uint8_t>& o, uint32_t v){ o.push_back(uint8_t(v & 0xFFu)); }
|
||||
static inline void _wr16(std::vector<uint8_t>& o, uint32_t v){
|
||||
o.push_back(uint8_t(v & 0xFFu));
|
||||
o.push_back(uint8_t((v >> 8) & 0xFFu));
|
||||
}
|
||||
static inline void _wr24(std::vector<uint8_t>& o, uint32_t v){
|
||||
o.push_back(uint8_t(v & 0xFFu));
|
||||
o.push_back(uint8_t((v >> 8) & 0xFFu));
|
||||
o.push_back(uint8_t((v >> 16) & 0xFFu));
|
||||
}
|
||||
static inline void _wr32(std::vector<uint8_t>& o, uint32_t v){
|
||||
o.push_back(uint8_t(v & 0xFFu));
|
||||
o.push_back(uint8_t((v >> 8) & 0xFFu));
|
||||
o.push_back(uint8_t((v >> 16) & 0xFFu));
|
||||
o.push_back(uint8_t((v >> 24) & 0xFFu));
|
||||
}
|
||||
|
||||
// ========================
|
||||
// SrcRef encoding in bytes (variable length)
|
||||
// kind(1) + payload
|
||||
// TexId: id24(3) => total 4
|
||||
// Sub : off16(3) + len16(3) => total 7
|
||||
// ========================
|
||||
enum class SrcKind : uint8_t { TexId = 0, Sub = 1 };
|
||||
|
||||
struct SrcRef {
|
||||
SrcKind Kind{};
|
||||
uint32_t TexId24 = 0; // for TexId
|
||||
uint16_t Off24 = 0; // for Sub
|
||||
uint16_t Len24 = 0; // for Sub
|
||||
};
|
||||
|
||||
// ========================
|
||||
// Opcodes (1 byte)
|
||||
// ========================
|
||||
enum class Op : uint8_t {
|
||||
End = 0,
|
||||
|
||||
Base_Tex = 1, // SrcRef(TexId)
|
||||
Base_Fill = 2, // w16, h16, color32
|
||||
Base_Anim = 3, // SrcRef(TexId), frameW16, frameH16, frames16, fpsQ16, flags8
|
||||
|
||||
Resize = 10, // w16, h16
|
||||
Transform = 11, // t8
|
||||
Opacity = 12, // a8
|
||||
NoAlpha = 13, // -
|
||||
MakeAlpha = 14, // rgb24 (3 bytes) RR,GG,BB
|
||||
Invert = 15, // mask8
|
||||
Brighten = 16, // -
|
||||
Contrast = 17, // cBias8, bBias8 (bias-127)
|
||||
Multiply = 18, // color32
|
||||
Screen = 19, // color32
|
||||
Colorize = 20, // color32, ratio8
|
||||
Anim = 21, // frameW16, frameH16, frames16, fpsQ16, flags8
|
||||
|
||||
Overlay = 30, // SrcRef (var)
|
||||
Mask = 31, // SrcRef (var)
|
||||
LowPart = 32, // percent8, SrcRef (var)
|
||||
|
||||
Combine = 40 // w16,h16,n16 then n*(x16,y16,SrcRef) (если понадобится — допишем DSL)
|
||||
};
|
||||
|
||||
// ========================
|
||||
// Pixel helpers (assume 0xAARRGGBB)
|
||||
// ========================
|
||||
static inline uint8_t _a(uint32_t c){ return uint8_t((c >> 24) & 0xFF); }
|
||||
static inline uint8_t _r(uint32_t c){ return uint8_t((c >> 16) & 0xFF); }
|
||||
static inline uint8_t _g(uint32_t c){ return uint8_t((c >> 8) & 0xFF); }
|
||||
static inline uint8_t _b(uint32_t c){ return uint8_t((c >> 0) & 0xFF); }
|
||||
static inline uint32_t _pack(uint8_t a,uint8_t r,uint8_t g,uint8_t b){
|
||||
return (uint32_t(a)<<24)|(uint32_t(r)<<16)|(uint32_t(g)<<8)|(uint32_t(b));
|
||||
}
|
||||
static inline uint8_t _clampu8(int v){ return uint8_t(std::min(255, std::max(0, v))); }
|
||||
|
||||
// ========================
|
||||
// VM (executes bytes)
|
||||
// ========================
|
||||
struct Image {
|
||||
uint32_t W=0,H=0;
|
||||
std::vector<uint32_t> Px;
|
||||
};
|
||||
|
||||
class VM {
|
||||
public:
|
||||
using TextureProvider = TexturePipelineProgram::TextureProviderFunc;
|
||||
|
||||
explicit VM(TextureProvider provider);
|
||||
bool run(const std::vector<uint8_t>& code, OwnedTexture& out, double timeSeconds, std::string* err);
|
||||
|
||||
private:
|
||||
TextureProvider Provider_;
|
||||
|
||||
static bool _bad(std::string* err, const char* msg);
|
||||
static bool _readSrc(const std::vector<uint8_t>& code, size_t& ip, SrcRef& out, std::string* err);
|
||||
Image _loadTex(uint32_t id, std::unordered_map<uint32_t, Image>& cache, std::string* err);
|
||||
Image _loadSub(const std::vector<uint8_t>& code,
|
||||
uint32_t off, uint32_t len,
|
||||
std::unordered_map<uint32_t, Image>& texCache,
|
||||
std::unordered_map<uint64_t, Image>& subCache,
|
||||
double timeSeconds,
|
||||
std::string* err);
|
||||
Image _loadSrc(const std::vector<uint8_t>& code,
|
||||
const SrcRef& src,
|
||||
std::unordered_map<uint32_t, Image>& texCache,
|
||||
std::unordered_map<uint64_t, Image>& subCache,
|
||||
double timeSeconds,
|
||||
std::string* err);
|
||||
|
||||
// ---- image ops (как в исходнике) ----
|
||||
static Image _makeSolid(uint32_t w, uint32_t h, uint32_t color);
|
||||
static Image _resizeNN(const Image& src, uint32_t nw, uint32_t nh);
|
||||
static Image _resizeNN_ifNeeded(Image img, uint32_t w, uint32_t h);
|
||||
static Image _cropFrame(const Image& sheet, uint32_t index, uint32_t fw, uint32_t fh, bool horizontal);
|
||||
static Image _cropFrameGrid(const Image& sheet, uint32_t index, uint32_t fw, uint32_t fh);
|
||||
static void _lerp(Image& base, const Image& over, double t);
|
||||
static void _alphaOver(Image& base, const Image& over);
|
||||
static void _applyMask(Image& base, const Image& mask);
|
||||
static void _opacity(Image& img, uint8_t mul);
|
||||
static void _noAlpha(Image& img);
|
||||
static void _makeAlpha(Image& img, uint32_t rgb24);
|
||||
static void _invert(Image& img, uint32_t maskBits);
|
||||
static void _brighten(Image& img);
|
||||
static void _contrast(Image& img, int c, int br);
|
||||
static void _multiply(Image& img, uint32_t color);
|
||||
static void _screen(Image& img, uint32_t color);
|
||||
static void _colorize(Image& img, uint32_t color, uint8_t ratio);
|
||||
static void _lowpart(Image& base, const Image& over, uint32_t percent);
|
||||
static Image _transform(const Image& src, uint32_t t);
|
||||
};
|
||||
|
||||
// ========================
|
||||
// Minimal DSL Lexer/Parser
|
||||
// now supports:
|
||||
// name |> op(...)
|
||||
// 32x32 "#RRGGBBAA"
|
||||
// optional prefix:
|
||||
// tex name |> op(...)
|
||||
// nested only where op expects a texture arg:
|
||||
// overlay( tex other |> ... )
|
||||
// Also supports overlay(other) / mask(other) / lowpart(50, other)
|
||||
// ========================
|
||||
enum class TokKind { End, Ident, Number, String, Pipe, Comma, LParen, RParen, Eq, X };
|
||||
|
||||
struct Tok {
|
||||
TokKind Kind = TokKind::End;
|
||||
std::string Text;
|
||||
uint32_t U32 = 0;
|
||||
};
|
||||
|
||||
struct Lexer {
|
||||
std::string_view S;
|
||||
size_t I=0;
|
||||
|
||||
bool HasBuf = false;
|
||||
Tok Buf;
|
||||
|
||||
static bool isAlpha(char c){ return (c>='a'&&c<='z')||(c>='A'&&c<='Z')||c=='_'; }
|
||||
static bool isNum(char c){ return (c>='0'&&c<='9'); }
|
||||
static bool isAlnum(char c){ return isAlpha(c)||isNum(c); }
|
||||
|
||||
void unread(const Tok& t);
|
||||
Tok peek();
|
||||
void skipWs();
|
||||
Tok next();
|
||||
};
|
||||
|
||||
|
||||
struct ArgVal {
|
||||
enum class ValueKind { U32, Str, Ident };
|
||||
ValueKind Kind = ValueKind::U32;
|
||||
uint32_t U32 = 0;
|
||||
std::string S;
|
||||
};
|
||||
|
||||
struct ParsedOp {
|
||||
std::string Name;
|
||||
std::vector<ArgVal> Pos;
|
||||
std::unordered_map<std::string, ArgVal> Named;
|
||||
};
|
||||
|
||||
// ========================
|
||||
// Compiler state
|
||||
// ========================
|
||||
std::string Source_;
|
||||
std::vector<uint8_t> Code_;
|
||||
std::vector<Patch> Patches_;
|
||||
|
||||
// ---- emit helpers (target = arbitrary out vector) ----
|
||||
static inline void _emitOp(std::vector<uint8_t>& out, Op op) { _wr8(out, uint8_t(op)); }
|
||||
static inline void _emitU8(std::vector<uint8_t>& out, uint32_t v){ _wr8(out, v); }
|
||||
static inline void _emitU16(std::vector<uint8_t>& out, uint32_t v){ _wr16(out, v); }
|
||||
static inline void _emitU24(std::vector<uint8_t>& out, uint32_t v){ _wr24(out, v); }
|
||||
static inline void _emitU32(std::vector<uint8_t>& out, uint32_t v){ _wr32(out, v); }
|
||||
|
||||
// reserve 3 bytes for u24 texId and register patch (absolute or relative)
|
||||
struct RelPatch { size_t Rel0; std::string Name; };
|
||||
|
||||
static void _emitTexPatchU24(std::vector<uint8_t>& out,
|
||||
std::vector<Patch>* absPatches,
|
||||
std::vector<RelPatch>* relPatches,
|
||||
const std::string& name) {
|
||||
const size_t idx = out.size();
|
||||
out.push_back(0); out.push_back(0); out.push_back(0);
|
||||
if(absPatches) absPatches->push_back(Patch{idx, name});
|
||||
if(relPatches) relPatches->push_back(RelPatch{idx, name});
|
||||
}
|
||||
|
||||
static void _emitSrcTexName(std::vector<uint8_t>& out,
|
||||
std::vector<Patch>* absPatches,
|
||||
std::vector<RelPatch>* relPatches,
|
||||
const std::string& name) {
|
||||
_emitU8(out, uint8_t(SrcKind::TexId));
|
||||
_emitTexPatchU24(out, absPatches, relPatches, name);
|
||||
}
|
||||
|
||||
static void _emitSrcSub(std::vector<uint8_t>& out, uint32_t off24, uint32_t len24) {
|
||||
_emitU8(out, uint8_t(SrcKind::Sub));
|
||||
_emitU24(out, off24);
|
||||
_emitU24(out, len24);
|
||||
}
|
||||
|
||||
// ========================
|
||||
// Color parsing: #RRGGBB or #RRGGBBAA -> 0xAARRGGBB
|
||||
// ========================
|
||||
static bool _parseHexColor(std::string_view s, uint32_t& outARGB);
|
||||
|
||||
// ========================
|
||||
// Parsing entry: full program
|
||||
// ========================
|
||||
bool _parseProgram(std::string* err);
|
||||
|
||||
// ========================
|
||||
// Base compilation (optionally after 'tex')
|
||||
// supports:
|
||||
// 1) name
|
||||
// 2) "name(.png/.jpg/.jpeg)" (allowed but normalized)
|
||||
// 3) anim(...)
|
||||
// 4) 32x32 "#RRGGBBAA"
|
||||
// optional: all of the above may be prefixed with 'tex'
|
||||
// ========================
|
||||
bool _compileBaseAfterTex(Lexer& lx,
|
||||
std::vector<uint8_t>& out,
|
||||
std::vector<Patch>* absPatches,
|
||||
std::vector<RelPatch>* relPatches,
|
||||
std::string* err);
|
||||
|
||||
bool _compileBaseFromToken(Lexer& lx,
|
||||
const Tok& a,
|
||||
std::vector<uint8_t>& out,
|
||||
std::vector<Patch>* absPatches,
|
||||
std::vector<RelPatch>* relPatches,
|
||||
std::string* err);
|
||||
|
||||
// ========================
|
||||
// Args parsing:
|
||||
// - normal args: (a,b,key=v)
|
||||
// - OR if first token inside '(' is 'tex' => parse nested program until ')'
|
||||
// ========================
|
||||
bool _parseArgListOrTextureExpr(Lexer& lx, ParsedOp& op, std::string* err);
|
||||
|
||||
bool _parseArgList(Lexer& lx, ParsedOp& op, std::string* err);
|
||||
|
||||
bool _tokToVal(const Tok& t, ArgVal& out, std::string* err);
|
||||
|
||||
// ========================
|
||||
// Subprogram compilation:
|
||||
// we already consumed 'tex'. Parse base + pipeline until next token is ')'
|
||||
// DO NOT consume ')'
|
||||
// ========================
|
||||
struct PendingSubData {
|
||||
std::vector<uint8_t> Bytes;
|
||||
std::vector<RelPatch> RelPatches;
|
||||
};
|
||||
|
||||
bool _compileSubProgramFromAlreadySawTex(Lexer& lx, PendingSubData& outSub, std::string* err);
|
||||
|
||||
// pending subprogram associated with ParsedOp pointer (created during parsing)
|
||||
mutable std::unordered_map<const ParsedOp*, PendingSubData> PendingSub_;
|
||||
|
||||
// Append subprogram to `out` and emit SrcRef(Sub, off16, len16), migrating patches properly.
|
||||
static bool _appendSubprogram(std::vector<uint8_t>& out,
|
||||
PendingSubData&& sub,
|
||||
std::vector<Patch>* absPatches,
|
||||
std::vector<RelPatch>* relPatches,
|
||||
uint32_t& outOff,
|
||||
uint32_t& outLen,
|
||||
std::string* err);
|
||||
|
||||
// ========================
|
||||
// Compile operations into arbitrary `out`
|
||||
// absPatches != nullptr => patches recorded as absolute for this buffer
|
||||
// relPatches != nullptr => patches recorded as relative for this buffer
|
||||
// ========================
|
||||
bool _compileOpInto(Lexer& lx,
|
||||
const ParsedOp& op,
|
||||
std::vector<uint8_t>& out,
|
||||
std::vector<Patch>* absPatches,
|
||||
std::vector<RelPatch>* relPatches,
|
||||
std::string* err);
|
||||
};
|
||||
422
Src/Common/async_mutex.hpp
Normal 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
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,80 +1,100 @@
|
||||
#pragma once
|
||||
|
||||
#include "TOSLib.hpp"
|
||||
#include <algorithm>
|
||||
#include <bitset>
|
||||
#include <cctype>
|
||||
#include <cstdint>
|
||||
#include <Common/Abstract.hpp>
|
||||
#include <Common/Collide.hpp>
|
||||
#include <sha2.hpp>
|
||||
#include <sol/sol.hpp>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <boost/json.hpp>
|
||||
#include <variant>
|
||||
#include <boost/uuid/detail/sha1.hpp>
|
||||
|
||||
|
||||
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>
|
||||
// И если сущность перешла из одного региона в другой, идентификатор сущности на стороне клиента сохраняется
|
||||
using RegionEntityId_t = uint16_t;
|
||||
using ClientEntityId_t = RegionEntityId_t;
|
||||
using ServerEntityId_t = std::tuple<WorldId_t, Pos::GlobalRegion, RegionEntityId_t>;
|
||||
using RegionFuncEntityId_t = uint16_t;
|
||||
using ClientFuncEntityId_t = RegionFuncEntityId_t;
|
||||
using ServerFuncEntityId_t = std::tuple<WorldId_t, Pos::GlobalRegion, RegionFuncEntityId_t>;
|
||||
using LocalEntityId_t = uint16_t;
|
||||
using GlobalEntityId_t = std::tuple<WorldId_t, Pos::GlobalRegion, LocalEntityId_t>;
|
||||
using PortalId_t = uint16_t;
|
||||
|
||||
|
||||
|
||||
using MediaStreamId_t = uint16_t;
|
||||
using ContentBridgeId_t = ResourceId;
|
||||
using PlayerId_t = ResourceId;
|
||||
using DefGeneratorId_t = ResourceId;
|
||||
using ContentBridgeId_t = uint16_t;
|
||||
using PlayerId_t = uint32_t;
|
||||
|
||||
|
||||
/*
|
||||
Сервер загружает информацию о локальных текстурах
|
||||
Синхронизация часто используемых текстур?
|
||||
Пересмотр списка текстур?
|
||||
Динамичные текстуры?
|
||||
|
||||
*/
|
||||
|
||||
struct ResourceFile {
|
||||
using Hash_t = boost::uuids::detail::sha1::digest_type;
|
||||
|
||||
Hash_t Hash;
|
||||
std::vector<std::byte> Data;
|
||||
|
||||
void calcHash() {
|
||||
boost::uuids::detail::sha1 hash;
|
||||
hash.process_bytes(Data.data(), Data.size());
|
||||
hash.get_digest(Hash);
|
||||
}
|
||||
};
|
||||
|
||||
struct ServerTime {
|
||||
uint32_t Seconds : 24, Sub : 8;
|
||||
};
|
||||
|
||||
struct VoxelCube_Region {
|
||||
union {
|
||||
struct {
|
||||
DefVoxelId VoxelId : 24, Meta : 8;
|
||||
};
|
||||
struct VoxelCube {
|
||||
DefVoxelId_t VoxelId;
|
||||
Pos::Local256_u Left, Right;
|
||||
|
||||
DefVoxelId Data = 0;
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
auto operator<=>(const VoxelCube&) const = default;
|
||||
};
|
||||
|
||||
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 {
|
||||
Pos::Object VecMin, VecMax;
|
||||
|
||||
void sortMinMax() {
|
||||
Pos::Object::Type left, right;
|
||||
Pos::Object::value_type left, right;
|
||||
|
||||
for(int iter = 0; iter < 3; iter++) {
|
||||
left = std::min(VecMin[iter], VecMax[iter]);
|
||||
right = std::max(VecMin[iter], VecMax[iter]);
|
||||
VecMin.set(iter, left);
|
||||
VecMax.set(iter, right);
|
||||
VecMin[iter] = left;
|
||||
VecMax[iter] = right;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,26 +117,22 @@ struct LocalAABB {
|
||||
|
||||
struct CollisionAABB : public AABB {
|
||||
enum struct EnumType {
|
||||
Voxel, Node, Entity, FuncEntity, Barrier, Portal, Another
|
||||
Voxel, Node, Entity, Barrier, Portal, Another
|
||||
} Type;
|
||||
|
||||
union {
|
||||
struct {
|
||||
RegionEntityId_t Index;
|
||||
LocalEntityId_t Index;
|
||||
} Entity;
|
||||
|
||||
struct {
|
||||
RegionFuncEntityId_t Index;
|
||||
} FuncEntity;
|
||||
|
||||
struct {
|
||||
Pos::bvec4u Chunk;
|
||||
Pos::bvec16u Pos;
|
||||
Pos::Local16_u Pos;
|
||||
} Node;
|
||||
|
||||
struct {
|
||||
Pos::bvec4u Chunk;
|
||||
Pos::Local16_u Chunk;
|
||||
uint32_t Index;
|
||||
DefVoxelId_t Id;
|
||||
} Voxel;
|
||||
|
||||
struct {
|
||||
@@ -135,63 +151,15 @@ struct CollisionAABB : public AABB {
|
||||
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 {
|
||||
DefEntityId DefId;
|
||||
DefEntityId_t DefId;
|
||||
|
||||
public:
|
||||
LocalAABB ABBOX;
|
||||
|
||||
// PosQuat
|
||||
DefWorldId WorldId;
|
||||
DefWorldId_t WorldId;
|
||||
Pos::Object Pos, Speed, Acceleration;
|
||||
glm::quat Quat;
|
||||
static constexpr uint16_t HP_BS = 4096, HP_BS_Bit = 12;
|
||||
@@ -211,16 +179,16 @@ public:
|
||||
IsRemoved = false;
|
||||
|
||||
public:
|
||||
Entity(DefEntityId defId);
|
||||
Entity(DefEntityId_t defId);
|
||||
|
||||
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)};
|
||||
}
|
||||
|
||||
DefEntityId getDefId() const { return DefId; }
|
||||
void setDefId(DefEntityId defId) { DefId = defId; }
|
||||
DefEntityId_t getDefId() const { return DefId; }
|
||||
};
|
||||
|
||||
|
||||
template<typename Vec>
|
||||
struct VoxelCuboidsFuncs {
|
||||
|
||||
@@ -229,9 +197,9 @@ struct VoxelCuboidsFuncs {
|
||||
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 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 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 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;
|
||||
}
|
||||
@@ -241,13 +209,13 @@ struct VoxelCuboidsFuncs {
|
||||
merged.VoxelId = a.VoxelId;
|
||||
|
||||
// Объединяем кубы по минимальным и максимальным координатам
|
||||
merged.Left.x = std::min(a.Left.x, b.Left.x);
|
||||
merged.Left.y = std::min(a.Left.y, b.Left.y);
|
||||
merged.Left.z = std::min(a.Left.z, b.Left.z);
|
||||
merged.Left.X = std::min(a.Left.X, b.Left.X);
|
||||
merged.Left.Y = std::min(a.Left.Y, b.Left.Y);
|
||||
merged.Left.Z = std::min(a.Left.Z, b.Left.Z);
|
||||
|
||||
merged.Right.x = std::max(a.Right.x, b.Right.x);
|
||||
merged.Right.y = std::max(a.Right.y, b.Right.y);
|
||||
merged.Right.z = std::max(a.Right.z, b.Right.z);
|
||||
merged.Right.X = std::max(a.Right.X, b.Right.X);
|
||||
merged.Right.Y = std::max(a.Right.Y, b.Right.Y);
|
||||
merged.Right.Z = std::max(a.Right.Z, b.Right.Z);
|
||||
|
||||
return merged;
|
||||
}
|
||||
@@ -341,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) {
|
||||
int minX = region.Left.x >> 8;
|
||||
int minY = region.Left.y >> 8;
|
||||
int minZ = region.Left.z >> 8;
|
||||
int maxX = region.Right.x >> 8;
|
||||
int maxY = region.Right.y >> 8;
|
||||
int maxZ = region.Right.z >> 8;
|
||||
int minX = region.Left.X >> 8;
|
||||
int minY = region.Left.Y >> 8;
|
||||
int minZ = region.Left.Z >> 8;
|
||||
int maxX = region.Right.X >> 8;
|
||||
int maxY = region.Right.Y >> 8;
|
||||
int maxZ = region.Right.Z >> 8;
|
||||
|
||||
for (int x = minX; x <= maxX; ++x) {
|
||||
for (int y = minY; y <= maxY; ++y) {
|
||||
for (int z = minZ; z <= maxZ; ++z) {
|
||||
Pos::bvec256u left {
|
||||
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>((z << 8), region.Left.z) - (z << 8))
|
||||
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>((y << 8), region.Left.Y) - (y << 8)),
|
||||
static_cast<uint8_t>(std::max<uint16_t>((z << 8), region.Left.Z) - (z << 8))
|
||||
};
|
||||
Pos::bvec256u 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>(((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))
|
||||
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>(((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))
|
||||
};
|
||||
|
||||
chunks[Pos::bvec4u(x, y, z)].push_back({
|
||||
region.VoxelId, region.Meta, left, right
|
||||
});
|
||||
int chunkIndex = z * 16 * 16 + y * 16 + x;
|
||||
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> ®ions) {
|
||||
for(const auto& [pos, voxels] : chunks) {
|
||||
Pos::bvec1024u left = pos << 8;
|
||||
for (const auto& cube : voxels) {
|
||||
regions.push_back({
|
||||
cube.VoxelId, cube.Meta,
|
||||
Pos::bvec1024u(left.x+cube.Pos.x, left.y+cube.Pos.y, left.z+cube.Pos.z),
|
||||
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)
|
||||
});
|
||||
inline void convertChunkVoxelsToRegion(const std::vector<VoxelCube> *chunks, std::vector<VoxelCube_Region> ®ions) {
|
||||
for (int x = 0; x < 16; ++x) {
|
||||
for (int y = 0; y < 16; ++y) {
|
||||
for (int z = 0; z < 16; ++z) {
|
||||
int chunkIndex = z * 16 * 16 + y * 16 + x;
|
||||
|
||||
Pos::Local4096_u left(x << 8, y << 8, z << 8);
|
||||
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -389,105 +363,4 @@ inline void convertChunkVoxelsToRegion(const std::unordered_map<Pos::bvec4u, std
|
||||
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;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "Common/Abstract.hpp"
|
||||
#include "Common/IdProvider.hpp"
|
||||
#include "Common/AssetsPreloader.hpp"
|
||||
#include <unordered_map>
|
||||
|
||||
namespace LV::Server {
|
||||
|
||||
class AssetsManager : public IdProvider<EnumAssets>, protected AssetsPreloader {
|
||||
public:
|
||||
using BindHashHeaderInfo = AssetsManager::BindHashHeaderInfo;
|
||||
|
||||
struct Out_checkAndPrepareResourcesUpdate : public AssetsPreloader::Out_checkAndPrepareResourcesUpdate {
|
||||
Out_checkAndPrepareResourcesUpdate(AssetsPreloader::Out_checkAndPrepareResourcesUpdate&& obj)
|
||||
: AssetsPreloader::Out_checkAndPrepareResourcesUpdate(std::move(obj))
|
||||
{}
|
||||
|
||||
std::unordered_map<ResourceFile::Hash_t, std::u8string> NewHeadless;
|
||||
};
|
||||
|
||||
Out_checkAndPrepareResourcesUpdate checkAndPrepareResourcesUpdate(
|
||||
const AssetsRegister& instances,
|
||||
ReloadStatus* status = nullptr
|
||||
) {
|
||||
std::unordered_map<ResourceFile::Hash_t, std::u8string> newHeadless;
|
||||
|
||||
Out_checkAndPrepareResourcesUpdate result = AssetsPreloader::checkAndPrepareResourcesUpdate(
|
||||
instances,
|
||||
[&](EnumAssets type, std::string_view domain, std::string_view key) { return getId(type, domain, key); },
|
||||
[&](std::u8string&& resource, ResourceFile::Hash_t hash, fs::path resPath) { newHeadless.emplace(hash, std::move(resource)); },
|
||||
status
|
||||
);
|
||||
|
||||
result.NewHeadless = std::move(newHeadless);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
struct Out_applyResourcesUpdate : public AssetsPreloader::Out_applyResourcesUpdate {
|
||||
Out_applyResourcesUpdate(AssetsPreloader::Out_applyResourcesUpdate&& obj)
|
||||
: AssetsPreloader::Out_applyResourcesUpdate(std::move(obj))
|
||||
{}
|
||||
};
|
||||
|
||||
Out_applyResourcesUpdate applyResourcesUpdate(Out_checkAndPrepareResourcesUpdate& orr) {
|
||||
Out_applyResourcesUpdate result = AssetsPreloader::applyResourcesUpdate(orr);
|
||||
|
||||
{
|
||||
static TOS::Logger LOG = "Server>AssetsManager";
|
||||
|
||||
for(size_t type = 0; type < static_cast<size_t>(EnumAssets::MAX_ENUM); ++type) {
|
||||
if(result.NewOrUpdates[type].empty())
|
||||
continue;
|
||||
|
||||
EnumAssets typeEnum = static_cast<EnumAssets>(type);
|
||||
const char* typeName = ::EnumAssetsToDirectory(typeEnum);
|
||||
|
||||
for(const auto& bind : result.NewOrUpdates[type]) {
|
||||
auto dk = getDK(typeEnum, bind.Id);
|
||||
if(!dk)
|
||||
continue;
|
||||
|
||||
LOG.debug()
|
||||
<< typeName << ": "
|
||||
<< dk->Domain << '+' << dk->Key << " -> " << bind.Id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(auto& [hash, data] : orr.NewHeadless) {
|
||||
Resources.emplace(hash, ResourceHashData{0, std::make_shared<std::u8string>(std::move(data))});
|
||||
}
|
||||
|
||||
for(auto& [hash, pathes] : orr.HashToPathNew) {
|
||||
auto iter = Resources.find(hash);
|
||||
assert(iter != Resources.end());
|
||||
iter->second.RefCount += pathes.size();
|
||||
}
|
||||
|
||||
for(auto& [hash, pathes] : orr.HashToPathLost) {
|
||||
auto iter = Resources.find(hash);
|
||||
assert(iter != Resources.end());
|
||||
iter->second.RefCount -= pathes.size();
|
||||
|
||||
if(iter->second.RefCount == 0)
|
||||
Resources.erase(iter);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<std::tuple<ResourceFile::Hash_t, std::shared_ptr<const std::u8string>>>
|
||||
getResources(const std::vector<ResourceFile::Hash_t>& hashes) const
|
||||
{
|
||||
std::vector<std::tuple<ResourceFile::Hash_t, std::shared_ptr<const std::u8string>>> result;
|
||||
result.reserve(hashes.size());
|
||||
|
||||
for(const auto& hash : hashes) {
|
||||
auto iter = Resources.find(hash);
|
||||
if(iter == Resources.end())
|
||||
continue;
|
||||
|
||||
result.emplace_back(hash, iter->second.Data);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::array<
|
||||
std::vector<BindHashHeaderInfo>,
|
||||
static_cast<size_t>(EnumAssets::MAX_ENUM)
|
||||
> collectHashBindings() const {
|
||||
return AssetsPreloader::collectHashBindings();
|
||||
}
|
||||
|
||||
private:
|
||||
struct ResourceHashData {
|
||||
size_t RefCount;
|
||||
std::shared_ptr<std::u8string> Data;
|
||||
};
|
||||
|
||||
std::unordered_map<
|
||||
ResourceFile::Hash_t,
|
||||
ResourceHashData
|
||||
> Resources;
|
||||
};
|
||||
|
||||
}
|
||||
122
Src/Server/BinaryResourceManager.cpp
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
82
Src/Server/BinaryResourceManager.hpp
Normal 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);
|
||||
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
236
Src/Server/ContentEventController.cpp
Normal 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)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,12 +2,10 @@
|
||||
|
||||
#include <Common/Abstract.hpp>
|
||||
#include "Abstract.hpp"
|
||||
#include "TOSLib.hpp"
|
||||
#include <algorithm>
|
||||
#include <bitset>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <queue>
|
||||
#include <optional>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
@@ -16,44 +14,225 @@
|
||||
namespace LV::Server {
|
||||
|
||||
class RemoteClient;
|
||||
using RemoteClient_ptr = std::unique_ptr<RemoteClient, std::function<void(RemoteClient*)>>;
|
||||
class GameServer;
|
||||
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 {
|
||||
public:
|
||||
std::queue<Pos::GlobalNode> Build, Break;
|
||||
private:
|
||||
|
||||
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:
|
||||
ContentEventController(std::unique_ptr<RemoteClient>&& remote);
|
||||
// Управляется сервером
|
||||
RemoteClient_ptr Remote;
|
||||
// Регионы сюда заглядывают
|
||||
// Каждый такт значения изменений обновляются GameServer'ом
|
||||
// Объявленная в чанках территория точно отслеживается (активная зона)
|
||||
ContentViewGlobal ContentViewState;
|
||||
ContentViewGlobal_DiffInfo ContentView_NewView, ContentView_LostView;
|
||||
|
||||
// Измеряется в чанках в регионах (активная зона)
|
||||
static constexpr uint16_t getViewRangeActive() { return 2; }
|
||||
// size_t CVCHash = 0; // Хэш для std::vector<ContentViewCircle>
|
||||
// std::unordered_map<WorldId_t, std::vector<Pos::GlobalRegion>> SubscribedRegions;
|
||||
|
||||
public:
|
||||
ContentEventController(RemoteClient_ptr &&remote);
|
||||
|
||||
// Измеряется в чанках в радиусе (активная зона)
|
||||
uint16_t getViewRangeActive() const;
|
||||
// Измеряется в чанках в радиусе (Декоративная зона) + getViewRangeActive()
|
||||
uint16_t getViewRangeBackground() const;
|
||||
ServerObjectPos getLastPos() const;
|
||||
ServerObjectPos getPos() const;
|
||||
|
||||
// Очищает более не наблюдаемые чанки и миры
|
||||
void removeUnobservable(const ContentViewInfo_Diff& diff);
|
||||
// Проверка на необходимость подгрузки новых определений миров
|
||||
// и очистка клиента от не наблюдаемых данных
|
||||
void checkContentViewChanges();
|
||||
// Здесь приходят частично фильтрованные события
|
||||
// Фильтровать не отслеживаемые миры
|
||||
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 onEntityUpdates(WorldId_t worldId, Pos::GlobalRegion regionPos, const std::unordered_map<RegionEntityId_t, Entity*> &entities);
|
||||
// Нужно фильтровать неотслеживаемые чанки
|
||||
void onChunksUpdate_Voxels(WorldId_t worldId, Pos::GlobalRegion regionPos, const std::unordered_map<Pos::Local16_u, const std::vector<VoxelCube>*> &chunks);
|
||||
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 onPortalUpdates(const std::vector<void*> &portals);
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,335 +0,0 @@
|
||||
#include "ContentManager.hpp"
|
||||
#include "Common/Abstract.hpp"
|
||||
#include <algorithm>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
|
||||
namespace LV::Server {
|
||||
|
||||
ContentManager::ContentManager(AssetsManager& am)
|
||||
: AM(am)
|
||||
{
|
||||
}
|
||||
|
||||
ContentManager::~ContentManager() = default;
|
||||
|
||||
void ContentManager::registerBase_Node(ResourceId id, const std::string& domain, const std::string& key, const sol::table& profile) {
|
||||
std::optional<DefNode_Base>* basePtr;
|
||||
|
||||
{
|
||||
size_t entryIndex = id / TableEntry<DefNode_Base>::ChunkSize;
|
||||
size_t entryId = id % TableEntry<DefNode_Base>::ChunkSize;
|
||||
|
||||
size_t need = entryIndex+1-Profiles_Base_Node.size();
|
||||
for(size_t iter = 0; iter < need; iter++) {
|
||||
Profiles_Base_Node.emplace_back(std::make_unique<TableEntry<DefNode_Base>>());
|
||||
}
|
||||
|
||||
basePtr = &Profiles_Base_Node[entryIndex]->Entries[entryId];
|
||||
*basePtr = DefNode_Base();
|
||||
}
|
||||
|
||||
DefNode_Base& def = **basePtr;
|
||||
|
||||
{
|
||||
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[static_cast<size_t>(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 = this->idToDK()[(int) type];
|
||||
// size_t counter = 0;
|
||||
// for(const auto& [domain, key] : table) {
|
||||
// ProfileChanges[static_cast<size_t>(type)].push_back(counter++);
|
||||
// }
|
||||
// }
|
||||
|
||||
template<class type, class modType>
|
||||
void ContentManager::buildEndProfilesByType(auto& profiles, auto enumType, auto& base, auto& keys, auto& result, auto& modsTable) {
|
||||
// Расширяем таблицу итоговых профилей до нужного количества
|
||||
if(!keys.empty()) {
|
||||
size_t need = keys.back() / TableEntry<type>::ChunkSize;
|
||||
if(need >= profiles.size()) {
|
||||
profiles.reserve(need);
|
||||
|
||||
for(size_t iter = 0; iter <= need-profiles.size(); ++iter)
|
||||
profiles.emplace_back(std::make_unique<TableEntry<type>>());
|
||||
}
|
||||
}
|
||||
|
||||
TOS::Logger("CM").debug() << "type: " << static_cast<size_t>(enumType);
|
||||
|
||||
// Пересчитываем профили
|
||||
for(size_t id : keys) {
|
||||
size_t entryIndex = id / TableEntry<type>::ChunkSize;
|
||||
size_t subIndex = id % TableEntry<type>::ChunkSize;
|
||||
|
||||
if(
|
||||
entryIndex >= base.size()
|
||||
|| !base[entryIndex]->Entries[subIndex]
|
||||
) {
|
||||
// Базовый профиль не существует
|
||||
profiles[entryIndex]->Entries[subIndex] = std::nullopt;
|
||||
// Уведомляем о потере профиля
|
||||
result.LostProfiles[static_cast<size_t>(enumType)].push_back(id);
|
||||
} else {
|
||||
// Собираем конечный профиль
|
||||
std::vector<std::tuple<std::string, modType>> mods_default, *mods = &mods_default;
|
||||
auto iter = modsTable.find(id);
|
||||
if(iter != modsTable.end())
|
||||
mods = &iter->second;
|
||||
|
||||
std::optional<BindDomainKeyInfo> dk = getDK(enumType, id);
|
||||
assert(dk);
|
||||
TOS::Logger("CM").debug() << "\t" << dk->Domain << ":" << dk->Key << " -> " << id;
|
||||
profiles[entryIndex]->Entries[subIndex] = base[entryIndex]->Entries[subIndex]->compile(AM, *this, dk->Domain, dk->Key, *mods);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ContentManager::Out_buildEndProfiles ContentManager::buildEndProfiles() {
|
||||
Out_buildEndProfiles result;
|
||||
|
||||
for(int type = 0; type < (int) EnumDefContent::MAX_ENUM; type++) {
|
||||
std::shared_lock lock(Profiles_Mtx[type]);
|
||||
auto& keys = ProfileChanges[type];
|
||||
std::sort(keys.begin(), keys.end());
|
||||
auto iterErase = std::unique(keys.begin(), keys.end());
|
||||
keys.erase(iterErase, keys.end());
|
||||
|
||||
switch(type) {
|
||||
case 0: buildEndProfilesByType<DefVoxel, DefVoxel_Mod> (Profiles_Voxel, EnumDefContent::Voxel, Profiles_Base_Voxel, keys, result, Profiles_Mod_Voxel); break;
|
||||
case 1: buildEndProfilesByType<DefNode, DefNode_Mod> (Profiles_Node, EnumDefContent::Node, Profiles_Base_Node, keys, result, Profiles_Mod_Node); break;
|
||||
case 2: buildEndProfilesByType<DefWorld, DefWorld_Mod> (Profiles_World, EnumDefContent::World, Profiles_Base_World, keys, result, Profiles_Mod_World); break;
|
||||
case 3: buildEndProfilesByType<DefPortal, DefPortal_Mod> (Profiles_Portal, EnumDefContent::Portal, Profiles_Base_Portal, keys, result, Profiles_Mod_Portal); break;
|
||||
case 4: buildEndProfilesByType<DefEntity, DefEntity_Mod> (Profiles_Entity, EnumDefContent::Entity, Profiles_Base_Entity, keys, result, Profiles_Mod_Entity); break;
|
||||
case 5: buildEndProfilesByType<DefItem, DefItem_Mod> (Profiles_Item, EnumDefContent::Item, Profiles_Base_Item, keys, result, Profiles_Mod_Item); break;
|
||||
default: std::unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
ContentManager::Out_getAllProfiles ContentManager::getAllProfiles() {
|
||||
Out_getAllProfiles result;
|
||||
|
||||
size_t counter;
|
||||
|
||||
{
|
||||
std::shared_lock lock(Profiles_Mtx[static_cast<size_t>(EnumDefContent::Voxel)]);
|
||||
result.ProfilesIds_Voxel.reserve(Profiles_Voxel.size()*TableEntry<DefVoxel>::ChunkSize);
|
||||
counter = 0;
|
||||
for(const auto& entry : Profiles_Voxel)
|
||||
for(const auto& item : entry->Entries) {
|
||||
size_t id = counter++;
|
||||
if(item)
|
||||
result.ProfilesIds_Voxel.push_back(id);
|
||||
}
|
||||
|
||||
result.ProfilesIds_Voxel.shrink_to_fit();
|
||||
result.Profiles_Voxel.reserve(result.ProfilesIds_Voxel.size());
|
||||
for(const auto& entry : Profiles_Voxel)
|
||||
for(const auto& item : entry->Entries)
|
||||
if(item)
|
||||
result.Profiles_Voxel.push_back(&item.value());
|
||||
}
|
||||
|
||||
{
|
||||
std::shared_lock lock(Profiles_Mtx[static_cast<size_t>(EnumDefContent::Node)]);
|
||||
result.ProfilesIds_Node.reserve(Profiles_Node.size()*TableEntry<DefNode>::ChunkSize);
|
||||
counter = 0;
|
||||
for(const auto& entry : Profiles_Node)
|
||||
for(const auto& item : entry->Entries) {
|
||||
size_t id = counter++;
|
||||
if(item)
|
||||
result.ProfilesIds_Node.push_back(id);
|
||||
}
|
||||
|
||||
result.ProfilesIds_Node.shrink_to_fit();
|
||||
result.Profiles_Node.reserve(result.ProfilesIds_Node.size());
|
||||
for(const auto& entry : Profiles_Node)
|
||||
for(const auto& item : entry->Entries)
|
||||
if(item)
|
||||
result.Profiles_Node.push_back(&item.value());
|
||||
}
|
||||
|
||||
{
|
||||
std::shared_lock lock(Profiles_Mtx[static_cast<size_t>(EnumDefContent::World)]);
|
||||
result.ProfilesIds_World.reserve(Profiles_World.size()*TableEntry<DefWorld>::ChunkSize);
|
||||
counter = 0;
|
||||
for(const auto& entry : Profiles_World)
|
||||
for(const auto& item : entry->Entries) {
|
||||
size_t id = counter++;
|
||||
if(item)
|
||||
result.ProfilesIds_World.push_back(id);
|
||||
}
|
||||
|
||||
result.ProfilesIds_World.shrink_to_fit();
|
||||
result.Profiles_World.reserve(result.ProfilesIds_World.size());
|
||||
for(const auto& entry : Profiles_World)
|
||||
for(const auto& item : entry->Entries)
|
||||
if(item)
|
||||
result.Profiles_World.push_back(&item.value());
|
||||
}
|
||||
|
||||
{
|
||||
std::shared_lock lock(Profiles_Mtx[static_cast<size_t>(EnumDefContent::Portal)]);
|
||||
result.ProfilesIds_Portal.reserve(Profiles_Portal.size()*TableEntry<DefPortal>::ChunkSize);
|
||||
counter = 0;
|
||||
for(const auto& entry : Profiles_Portal)
|
||||
for(const auto& item : entry->Entries) {
|
||||
size_t id = counter++;
|
||||
if(item)
|
||||
result.ProfilesIds_Portal.push_back(id);
|
||||
}
|
||||
|
||||
result.ProfilesIds_Portal.shrink_to_fit();
|
||||
result.Profiles_Portal.reserve(result.ProfilesIds_Portal.size());
|
||||
for(const auto& entry : Profiles_Portal)
|
||||
for(const auto& item : entry->Entries)
|
||||
if(item)
|
||||
result.Profiles_Portal.push_back(&item.value());
|
||||
}
|
||||
|
||||
{
|
||||
std::shared_lock lock(Profiles_Mtx[static_cast<size_t>(EnumDefContent::Entity)]);
|
||||
result.ProfilesIds_Entity.reserve(Profiles_Entity.size()*TableEntry<DefEntity>::ChunkSize);
|
||||
counter = 0;
|
||||
for(const auto& entry : Profiles_Entity)
|
||||
for(const auto& item : entry->Entries) {
|
||||
size_t id = counter++;
|
||||
if(item)
|
||||
result.ProfilesIds_Entity.push_back(id);
|
||||
}
|
||||
|
||||
result.ProfilesIds_Entity.shrink_to_fit();
|
||||
result.Profiles_Entity.reserve(result.ProfilesIds_Entity.size());
|
||||
for(const auto& entry : Profiles_Entity)
|
||||
for(const auto& item : entry->Entries)
|
||||
if(item)
|
||||
result.Profiles_Entity.push_back(&item.value());
|
||||
}
|
||||
|
||||
{
|
||||
std::shared_lock lock(Profiles_Mtx[static_cast<size_t>(EnumDefContent::Item)]);
|
||||
result.ProfilesIds_Item.reserve(Profiles_Item.size()*TableEntry<DefItem>::ChunkSize);
|
||||
counter = 0;
|
||||
for(const auto& entry : Profiles_Item)
|
||||
for(const auto& item : entry->Entries) {
|
||||
size_t id = counter++;
|
||||
if(item)
|
||||
result.ProfilesIds_Item.push_back(id);
|
||||
}
|
||||
|
||||
result.ProfilesIds_Item.shrink_to_fit();
|
||||
result.Profiles_Item.reserve(result.ProfilesIds_Item.size());
|
||||
for(const auto& entry : Profiles_Item)
|
||||
for(const auto& item : entry->Entries)
|
||||
if(item)
|
||||
result.Profiles_Item.push_back(&item.value());
|
||||
}
|
||||
|
||||
result.IdToDK = idToDK();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,458 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "Common/Abstract.hpp"
|
||||
#include "AssetsManager.hpp"
|
||||
#include "Common/IdProvider.hpp"
|
||||
#include "Common/Net.hpp"
|
||||
#include "TOSLib.hpp"
|
||||
#include <array>
|
||||
#include <mutex>
|
||||
#include <sol/table.hpp>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
|
||||
|
||||
namespace LV::Server {
|
||||
|
||||
struct ResourceBase {
|
||||
std::string Domain, Key;
|
||||
};
|
||||
|
||||
class ContentManager;
|
||||
|
||||
struct DefVoxel : public ResourceBase {
|
||||
std::u8string dumpToClient() const {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
struct DefNode : public ResourceBase {
|
||||
AssetsNodestate NodestateId;
|
||||
|
||||
std::u8string dumpToClient() const {
|
||||
auto wr = TOS::ByteBuffer::Writer();
|
||||
wr << uint32_t(NodestateId);
|
||||
auto buff = wr.complite();
|
||||
return (std::u8string) std::u8string_view((const char8_t*) buff.data(), buff.size());
|
||||
}
|
||||
};
|
||||
struct DefWorld : public ResourceBase {
|
||||
std::u8string dumpToClient() const {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
struct DefPortal : public ResourceBase {
|
||||
std::u8string dumpToClient() const {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
struct DefEntity : public ResourceBase {
|
||||
std::u8string dumpToClient() const {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
struct DefItem : public ResourceBase {
|
||||
std::u8string dumpToClient() const {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
struct DefVoxel_Mod { };
|
||||
struct DefNode_Mod { };
|
||||
struct DefWorld_Mod { };
|
||||
struct DefPortal_Mod { };
|
||||
struct DefEntity_Mod { };
|
||||
struct DefItem_Mod { };
|
||||
|
||||
struct DefVoxel_Base {
|
||||
private:
|
||||
friend ContentManager;
|
||||
DefVoxel compile(AssetsManager& am, ContentManager& cm, const std::string_view domain, const std::string_view key, const std::vector<std::tuple<std::string, DefVoxel_Mod>>& mods) const {
|
||||
return DefVoxel();
|
||||
}
|
||||
};
|
||||
|
||||
struct DefNode_Base {
|
||||
private:
|
||||
friend ContentManager;
|
||||
DefNode compile(AssetsManager& am, ContentManager& cm, const std::string_view domain, const std::string_view key, const std::vector<std::tuple<std::string, DefNode_Mod>>& mods) const {
|
||||
DefNode profile;
|
||||
std::string jsonKey = std::string(key)+".json";
|
||||
profile.NodestateId = am.getId(EnumAssets::Nodestate, domain, jsonKey);
|
||||
TOS::Logger("Compile").info() << domain << ' ' << key << " -> " << profile.NodestateId;
|
||||
return profile;
|
||||
}
|
||||
};
|
||||
|
||||
struct DefWorld_Base {
|
||||
private:
|
||||
friend ContentManager;
|
||||
DefWorld compile(AssetsManager& am, ContentManager& cm, const std::string_view domain, const std::string_view key, const std::vector<std::tuple<std::string, DefWorld_Mod>>& mods) const {
|
||||
return DefWorld();
|
||||
}
|
||||
};
|
||||
|
||||
struct DefPortal_Base {
|
||||
private:
|
||||
friend ContentManager;
|
||||
DefPortal compile(AssetsManager& am, ContentManager& cm, const std::string_view domain, const std::string_view key, const std::vector<std::tuple<std::string, DefPortal_Mod>>& mods) const {
|
||||
return DefPortal();
|
||||
}
|
||||
};
|
||||
|
||||
struct DefEntity_Base {
|
||||
private:
|
||||
friend ContentManager;
|
||||
DefEntity compile(AssetsManager& am, ContentManager& cm, const std::string_view domain, const std::string_view key, const std::vector<std::tuple<std::string, DefEntity_Mod>>& mods) const {
|
||||
return DefEntity();
|
||||
}
|
||||
};
|
||||
|
||||
struct DefItem_Base {
|
||||
private:
|
||||
friend ContentManager;
|
||||
DefItem compile(AssetsManager& am, ContentManager& cm, const std::string_view domain, const std::string_view key, const std::vector<std::tuple<std::string, DefItem_Mod>>& mods) const {
|
||||
return DefItem();
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
DK to id
|
||||
id to profile
|
||||
*/
|
||||
|
||||
class ContentManager : public IdProvider<EnumDefContent> {
|
||||
public:
|
||||
class LRU {
|
||||
public:
|
||||
LRU(ContentManager& cm)
|
||||
: CM(&cm)
|
||||
{
|
||||
}
|
||||
|
||||
LRU(const LRU&) = default;
|
||||
LRU(LRU&&) = default;
|
||||
LRU& operator=(const LRU&) = default;
|
||||
LRU& operator=(LRU&&) = default;
|
||||
|
||||
ResourceId getId(EnumDefContent type, const std::string_view domain, const std::string_view key) {
|
||||
auto iter = DKToId[static_cast<size_t>(type)].find(BindDomainKeyViewInfo(domain, key));
|
||||
if(iter == DKToId[static_cast<size_t>(type)].end()) {
|
||||
ResourceId id = CM->getId(type, domain, key);
|
||||
DKToId[static_cast<size_t>(type)].emplace_hint(iter, BindDomainKeyInfo((std::string) domain, (std::string) key), id);
|
||||
return id;
|
||||
}
|
||||
|
||||
return iter->second;
|
||||
|
||||
// switch(type) {
|
||||
// case EnumDefContent::Voxel:
|
||||
|
||||
// case EnumDefContent::Node:
|
||||
// case EnumDefContent::World:
|
||||
// case EnumDefContent::Portal:
|
||||
// case EnumDefContent::Entity:
|
||||
// case EnumDefContent::Item:
|
||||
// default:
|
||||
// std::unreachable();
|
||||
// }
|
||||
}
|
||||
|
||||
ResourceId getIdVoxel(const std::string_view domain, const std::string_view key) {
|
||||
return getId(EnumDefContent::Voxel, domain, key);
|
||||
}
|
||||
|
||||
ResourceId getIdNode(const std::string_view domain, const std::string_view key) {
|
||||
return getId(EnumDefContent::Node, domain, key);
|
||||
}
|
||||
|
||||
ResourceId getIdWorld(const std::string_view domain, const std::string_view key) {
|
||||
return getId(EnumDefContent::World, domain, key);
|
||||
}
|
||||
|
||||
ResourceId getIdPortal(const std::string_view domain, const std::string_view key) {
|
||||
return getId(EnumDefContent::Portal, domain, key);
|
||||
}
|
||||
|
||||
ResourceId getIdEntity(const std::string_view domain, const std::string_view key) {
|
||||
return getId(EnumDefContent::Entity, domain, key);
|
||||
}
|
||||
|
||||
ResourceId getIdItem(const std::string_view domain, const std::string_view key) {
|
||||
return getId(EnumDefContent::Item, domain, key);
|
||||
}
|
||||
|
||||
private:
|
||||
ContentManager* CM;
|
||||
|
||||
std::array<
|
||||
ankerl::unordered_dense::map<BindDomainKeyInfo, ResourceId, KeyHash, KeyEq>,
|
||||
MAX_ENUM
|
||||
> DKToId;
|
||||
|
||||
std::unordered_map<DefVoxelId, std::optional<DefVoxel>*> Profiles_Voxel;
|
||||
std::unordered_map<DefNodeId, std::optional<DefNode>*> Profiles_Node;
|
||||
std::unordered_map<DefWorldId, std::optional<DefWorld>*> Profiles_World;
|
||||
std::unordered_map<DefPortalId, std::optional<DefPortal>*> Profiles_Portal;
|
||||
std::unordered_map<DefEntityId, std::optional<DefEntity>*> Profiles_Entity;
|
||||
std::unordered_map<DefItemId, std::optional<DefItem>*> Profiles_Item;
|
||||
};
|
||||
|
||||
public:
|
||||
ContentManager(AssetsManager &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<
|
||||
std::tuple<DefVoxelId, const DefVoxel*>
|
||||
> ChangedProfiles_Voxel;
|
||||
|
||||
std::vector<
|
||||
std::tuple<DefNodeId, const DefNode*>
|
||||
> ChangedProfiles_Node;
|
||||
|
||||
std::vector<
|
||||
std::tuple<DefWorldId, const DefWorld*>
|
||||
> ChangedProfiles_World;
|
||||
|
||||
std::vector<
|
||||
std::tuple<DefPortalId, const DefPortal*>
|
||||
> ChangedProfiles_Portal;
|
||||
|
||||
std::vector<
|
||||
std::tuple<DefEntityId, const DefEntity*>
|
||||
> ChangedProfiles_Entity;
|
||||
|
||||
std::vector<
|
||||
std::tuple<DefItemId, const DefItem*>
|
||||
> ChangedProfiles_Item;
|
||||
|
||||
std::array<
|
||||
std::vector<ResourceId>,
|
||||
MAX_ENUM
|
||||
> LostProfiles;
|
||||
|
||||
std::array<
|
||||
std::vector<BindDomainKeyInfo>,
|
||||
static_cast<size_t>(EnumDefContent::MAX_ENUM)
|
||||
> IdToDK;
|
||||
};
|
||||
|
||||
// Компилирует конечные профили по базе и модификаторам (предоставляет клиентам изменённые и потерянные)
|
||||
Out_buildEndProfiles buildEndProfiles();
|
||||
|
||||
struct Out_getAllProfiles {
|
||||
std::vector<DefVoxelId> ProfilesIds_Voxel;
|
||||
std::vector<const DefVoxel*> Profiles_Voxel;
|
||||
|
||||
std::vector<DefNodeId> ProfilesIds_Node;
|
||||
std::vector<const DefNode*> Profiles_Node;
|
||||
|
||||
std::vector<DefWorldId> ProfilesIds_World;
|
||||
std::vector<const DefWorld*> Profiles_World;
|
||||
|
||||
std::vector<DefPortalId> ProfilesIds_Portal;
|
||||
std::vector<const DefPortal*> Profiles_Portal;
|
||||
|
||||
std::vector<DefEntityId> ProfilesIds_Entity;
|
||||
std::vector<const DefEntity*> Profiles_Entity;
|
||||
|
||||
std::vector<DefItemId> ProfilesIds_Item;
|
||||
std::vector<const DefItem*> Profiles_Item;
|
||||
|
||||
std::array<
|
||||
std::vector<BindDomainKeyInfo>,
|
||||
static_cast<size_t>(EnumDefContent::MAX_ENUM)
|
||||
> IdToDK;
|
||||
};
|
||||
|
||||
// Выдаёт все профили (для новых клиентов)
|
||||
Out_getAllProfiles getAllProfiles();
|
||||
|
||||
|
||||
std::optional<DefVoxel>& getEntry_Voxel(ResourceId resId) {
|
||||
std::shared_lock mtx(Profiles_Mtx[static_cast<size_t>(EnumDefContent::Voxel)]);
|
||||
|
||||
assert(resId / TableEntry<DefVoxel>::ChunkSize <= Profiles_Voxel.size());
|
||||
return Profiles_Voxel[resId / TableEntry<DefVoxel>::ChunkSize]->Entries[resId % TableEntry<DefVoxel>::ChunkSize];
|
||||
}
|
||||
|
||||
std::optional<DefVoxel>& getEntry_Voxel(const std::string_view domain, const std::string_view key) {
|
||||
return getEntry_Voxel(getId(EnumDefContent::Voxel, domain, key));
|
||||
}
|
||||
|
||||
std::optional<DefNode>& getEntry_Node(ResourceId resId) {
|
||||
std::shared_lock mtx(Profiles_Mtx[static_cast<size_t>(EnumDefContent::Node)]);
|
||||
|
||||
assert(resId / TableEntry<DefNode>::ChunkSize < Profiles_Node.size());
|
||||
return Profiles_Node[resId / TableEntry<DefVoxel>::ChunkSize]->Entries[resId % TableEntry<DefVoxel>::ChunkSize];
|
||||
}
|
||||
|
||||
std::optional<DefNode>& getEntry_Node(const std::string_view domain, const std::string_view key) {
|
||||
return getEntry_Node(getId(EnumDefContent::Node, domain, key));
|
||||
}
|
||||
|
||||
std::optional<DefWorld>& getEntry_World(ResourceId resId) {
|
||||
std::shared_lock mtx(Profiles_Mtx[static_cast<size_t>(EnumDefContent::World)]);
|
||||
|
||||
assert(resId / TableEntry<DefWorld>::ChunkSize < Profiles_World.size());
|
||||
return Profiles_World[resId / TableEntry<DefVoxel>::ChunkSize]->Entries[resId % TableEntry<DefVoxel>::ChunkSize];
|
||||
}
|
||||
|
||||
std::optional<DefWorld>& getEntry_World(const std::string_view domain, const std::string_view key) {
|
||||
return getEntry_World(getId(EnumDefContent::World, domain, key));
|
||||
}
|
||||
|
||||
std::optional<DefPortal>& getEntry_Portal(ResourceId resId) {
|
||||
std::shared_lock mtx(Profiles_Mtx[static_cast<size_t>(EnumDefContent::Portal)]);
|
||||
|
||||
assert(resId / TableEntry<DefPortal>::ChunkSize < Profiles_Portal.size());
|
||||
return Profiles_Portal[resId / TableEntry<DefVoxel>::ChunkSize]->Entries[resId % TableEntry<DefVoxel>::ChunkSize];
|
||||
}
|
||||
|
||||
std::optional<DefPortal>& getEntry_Portal(const std::string_view domain, const std::string_view key) {
|
||||
return getEntry_Portal(getId(EnumDefContent::Portal, domain, key));
|
||||
}
|
||||
|
||||
std::optional<DefEntity>& getEntry_Entity(ResourceId resId) {
|
||||
std::shared_lock mtx(Profiles_Mtx[static_cast<size_t>(EnumDefContent::Entity)]);
|
||||
|
||||
assert(resId / TableEntry<DefEntity>::ChunkSize < Profiles_Entity.size());
|
||||
return Profiles_Entity[resId / TableEntry<DefVoxel>::ChunkSize]->Entries[resId % TableEntry<DefVoxel>::ChunkSize];
|
||||
}
|
||||
|
||||
std::optional<DefEntity>& getEntry_Entity(const std::string_view domain, const std::string_view key) {
|
||||
return getEntry_Entity(getId(EnumDefContent::Entity, domain, key));
|
||||
}
|
||||
|
||||
std::optional<DefItem>& getEntry_Item(ResourceId resId) {
|
||||
std::shared_lock mtx(Profiles_Mtx[static_cast<size_t>(EnumDefContent::Item)]);
|
||||
|
||||
assert(resId / TableEntry<DefItem>::ChunkSize < Profiles_Item.size());
|
||||
return Profiles_Item[resId / TableEntry<DefVoxel>::ChunkSize]->Entries[resId % TableEntry<DefVoxel>::ChunkSize];
|
||||
}
|
||||
|
||||
std::optional<DefItem>& getEntry_Item(const std::string_view domain, const std::string_view key) {
|
||||
return getEntry_Item(getId(EnumDefContent::Item, domain, key));
|
||||
}
|
||||
|
||||
ResourceId getId(EnumDefContent type, const std::string_view domain, const std::string_view key) {
|
||||
ResourceId resId = IdProvider::getId(type, domain, key);
|
||||
|
||||
switch(type) {
|
||||
case EnumDefContent::Voxel:
|
||||
if(resId >= Profiles_Voxel.size()*TableEntry<DefVoxel>::ChunkSize) {
|
||||
std::unique_lock mtx(Profiles_Mtx[static_cast<size_t>(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) {
|
||||
std::unique_lock mtx(Profiles_Mtx[static_cast<size_t>(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) {
|
||||
std::unique_lock mtx(Profiles_Mtx[static_cast<size_t>(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) {
|
||||
std::unique_lock mtx(Profiles_Mtx[static_cast<size_t>(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) {
|
||||
std::unique_lock mtx(Profiles_Mtx[static_cast<size_t>(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) {
|
||||
std::unique_lock mtx(Profiles_Mtx[static_cast<size_t>(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;
|
||||
}
|
||||
|
||||
LRU createLRU() {
|
||||
return {*this};
|
||||
}
|
||||
|
||||
private:
|
||||
template<typename T>
|
||||
struct TableEntry {
|
||||
static constexpr size_t ChunkSize = 4096;
|
||||
std::array<std::optional<T>, ChunkSize> Entries;
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
template<class type, class modType>
|
||||
void buildEndProfilesByType(auto& profiles, auto enumType, auto& base, auto& keys, auto& result, auto& mods);
|
||||
|
||||
TOS::Logger LOG = "Server>ContentManager";
|
||||
AssetsManager& AM;
|
||||
|
||||
// Профили зарегистрированные модами
|
||||
std::vector<std::unique_ptr<TableEntry<DefVoxel_Base>>> Profiles_Base_Voxel;
|
||||
std::vector<std::unique_ptr<TableEntry<DefNode_Base>>> Profiles_Base_Node;
|
||||
std::vector<std::unique_ptr<TableEntry<DefWorld_Base>>> Profiles_Base_World;
|
||||
std::vector<std::unique_ptr<TableEntry<DefPortal_Base>>> Profiles_Base_Portal;
|
||||
std::vector<std::unique_ptr<TableEntry<DefEntity_Base>>> Profiles_Base_Entity;
|
||||
std::vector<std::unique_ptr<TableEntry<DefItem_Base>>> 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[MAX_ENUM];
|
||||
|
||||
// Конечные профили контента
|
||||
std::array<std::shared_mutex, MAX_ENUM> Profiles_Mtx;
|
||||
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;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,30 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "Common/AssetsPreloader.hpp"
|
||||
#define SOL_EXCEPTIONS_SAFE_PROPAGATION 1
|
||||
|
||||
#include <Common/Net.hpp>
|
||||
#include <Common/Lockable.hpp>
|
||||
#include <atomic>
|
||||
#include <barrier>
|
||||
#include <boost/asio/any_io_executor.hpp>
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <filesystem>
|
||||
#include "Common/Abstract.hpp"
|
||||
#include "RemoteClient.hpp"
|
||||
#include "Server/Abstract.hpp"
|
||||
#include <TOSLib.hpp>
|
||||
#include <memory>
|
||||
#include <queue>
|
||||
#include <set>
|
||||
#include <sol/forward.hpp>
|
||||
#include <sol/state.hpp>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include "ContentEventController.hpp"
|
||||
|
||||
#include "WorldDefManager.hpp"
|
||||
#include "ContentManager.hpp"
|
||||
#include "AssetsManager.hpp"
|
||||
#include "BinaryResourceManager.hpp"
|
||||
#include "World.hpp"
|
||||
|
||||
#include "SaveBackend.hpp"
|
||||
@@ -32,33 +23,16 @@
|
||||
|
||||
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;
|
||||
|
||||
class GameServer : public AsyncObject {
|
||||
class GameServer {
|
||||
asio::io_context &IOC;
|
||||
TOS::Logger LOG = "GameServer";
|
||||
DestroyLock UseLock;
|
||||
std::thread RunThread;
|
||||
|
||||
bool IsAlive = true, IsGoingShutdown = false;
|
||||
std::string ShutdownReason;
|
||||
std::atomic<bool> ModsReloadRequested = false;
|
||||
static constexpr float
|
||||
PerTickDuration = 1/30.f, // Минимальная и стартовая длина такта
|
||||
PerTickAdjustment = 1/60.f; // Подгонка длительности такта в случае провисаний
|
||||
@@ -68,28 +42,37 @@ class GameServer : public AsyncObject {
|
||||
|
||||
struct {
|
||||
Lockable<std::set<std::string>> ConnectedPlayersSet;
|
||||
Lockable<std::list<std::shared_ptr<RemoteClient>>> NewConnectedPlayers;
|
||||
Lockable<std::list<RemoteClient_ptr>> NewConnectedPlayers;
|
||||
|
||||
} External;
|
||||
|
||||
struct ContentObj {
|
||||
public:
|
||||
AssetsManager AM;
|
||||
ContentManager CM;
|
||||
// WorldDefManager WorldDM;
|
||||
// VoxelDefManager VoxelDM;
|
||||
// NodeDefManager NodeDM;
|
||||
BinaryResourceManager TextureM;
|
||||
BinaryResourceManager ModelM;
|
||||
BinaryResourceManager SoundM;
|
||||
|
||||
// Если контент был перерегистрирован (исключая двоичные ресурсы), то профили будут повторно разосланы
|
||||
ResourceRequest OnContentChanges;
|
||||
ContentObj(asio::io_context &ioc,
|
||||
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;
|
||||
|
||||
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};
|
||||
// Счётчик тактов (увеличивается на 1 каждый тик в GameServer::run)
|
||||
uint32_t Tick = 0;
|
||||
|
||||
} Game;
|
||||
|
||||
@@ -100,9 +83,9 @@ class GameServer : public AsyncObject {
|
||||
// depth ограничивает глубину входа в ContentBridges
|
||||
std::vector<ContentViewCircle> accumulateContentViewCircles(ContentViewCircle circle, int depth = 2);
|
||||
// Вынести в отдельный поток
|
||||
static ContentViewInfo makeContentViewInfo(const std::vector<ContentViewCircle> &views);
|
||||
ContentViewInfo makeContentViewInfo(ContentViewCircle circle, int depth = 2) {
|
||||
return makeContentViewInfo(accumulateContentViewCircles(circle, depth));
|
||||
static ContentViewGlobal makeContentViewGlobal(const std::vector<ContentViewCircle> &views);
|
||||
ContentViewGlobal makeContentViewGlobal(ContentViewCircle circle, int depth = 2) {
|
||||
return makeContentViewGlobal(accumulateContentViewCircles(circle, depth));
|
||||
}
|
||||
|
||||
// std::unordered_map<WorldId_t, std::vector<ContentViewCircle>> remapCVCsByWorld(const std::vector<ContentViewCircle> &list);
|
||||
@@ -114,14 +97,9 @@ class GameServer : public AsyncObject {
|
||||
|
||||
/*
|
||||
Регистрация миров по строке
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
|
||||
*/
|
||||
|
||||
|
||||
private:
|
||||
void _accumulateContentViewCircles(ContentViewCircle circle, int depth);
|
||||
} Expanse;
|
||||
@@ -133,172 +111,17 @@ class GameServer : public AsyncObject {
|
||||
std::unique_ptr<IModStorageSaveBackend> ModStorage;
|
||||
} SaveBackend;
|
||||
|
||||
/*
|
||||
Обязательно между тактами
|
||||
|
||||
Конвертация ресурсов игры, их хранение в кеше и загрузка в память для отправки клиентам
|
||||
io_uring или последовательное чтение
|
||||
|
||||
Исполнение асинхронного луа
|
||||
Пул для постоянной работы и синхронизации времени с главным потоком
|
||||
|
||||
Сжатие/расжатие регионов в базе
|
||||
Локальный поток должен собирать ключи профилей для базы
|
||||
Остальное внутри базы
|
||||
*/
|
||||
|
||||
/*
|
||||
Отправка изменений чанков клиентам
|
||||
|
||||
После окончания такта пул копирует изменённые чанки
|
||||
- синхронизация сбора в stepDatabaseSync -
|
||||
сжимает их и отправляет клиентам
|
||||
- синхронизация в начале stepPlayerProceed -
|
||||
^ к этому моменту все данные должны быть отправлены в RemoteClient
|
||||
*/
|
||||
struct BackingChunkPressure_t {
|
||||
TOS::Logger LOG = "BackingChunkPressure";
|
||||
std::atomic<bool> NeedShutdown = false;
|
||||
std::vector<std::thread> Threads;
|
||||
std::unique_ptr<std::barrier<>> CollectStart;
|
||||
std::unique_ptr<std::barrier<>> CollectEnd;
|
||||
std::unique_ptr<std::barrier<>> CompressEnd;
|
||||
std::unordered_map<WorldId_t, std::unique_ptr<World>> *Worlds;
|
||||
bool HasStarted = false;
|
||||
|
||||
void init(size_t threadCount) {
|
||||
if(threadCount == 0)
|
||||
return;
|
||||
|
||||
const ptrdiff_t participants = static_cast<ptrdiff_t>(threadCount + 1);
|
||||
CollectStart = std::make_unique<std::barrier<>>(participants);
|
||||
CollectEnd = std::make_unique<std::barrier<>>(participants);
|
||||
CompressEnd = std::make_unique<std::barrier<>>(participants);
|
||||
}
|
||||
|
||||
void startCollectChanges() {
|
||||
if(!CollectStart)
|
||||
return;
|
||||
HasStarted = true;
|
||||
CollectStart->arrive_and_wait();
|
||||
}
|
||||
|
||||
void endCollectChanges() {
|
||||
if(!CollectEnd)
|
||||
return;
|
||||
if(!HasStarted)
|
||||
return;
|
||||
CollectEnd->arrive_and_wait();
|
||||
}
|
||||
|
||||
void endWithResults() {
|
||||
if(!CompressEnd)
|
||||
return;
|
||||
if(!HasStarted)
|
||||
return;
|
||||
CompressEnd->arrive_and_wait();
|
||||
}
|
||||
|
||||
void stop() {
|
||||
NeedShutdown.store(true, std::memory_order_release);
|
||||
|
||||
if(CollectStart)
|
||||
CollectStart->arrive_and_drop();
|
||||
if(CollectEnd)
|
||||
CollectEnd->arrive_and_drop();
|
||||
if(CompressEnd)
|
||||
CompressEnd->arrive_and_drop();
|
||||
|
||||
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(25);
|
||||
|
||||
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;
|
||||
ContentManager &CM;
|
||||
|
||||
BackingAsyncLua_t(ContentManager& cm)
|
||||
: CM(cm)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
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:
|
||||
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();
|
||||
|
||||
|
||||
void shutdown(const std::string reason) {
|
||||
if(ShutdownReason.empty())
|
||||
ShutdownReason = reason;
|
||||
@@ -312,7 +135,6 @@ public:
|
||||
void waitShutdown() {
|
||||
UseLock.wait_no_use();
|
||||
}
|
||||
void requestModsReload();
|
||||
|
||||
// Подключение tcp сокета
|
||||
coro<> pushSocketConnect(tcp::socket socket);
|
||||
@@ -321,75 +143,37 @@ public:
|
||||
// Инициализация игрового протокола для сокета (onSocketAuthorized() может передать сокет в onSocketGame())
|
||||
coro<> pushSocketGameProtocol(tcp::socket socket, const std::string username);
|
||||
|
||||
/* Загрузит, сгенерирует или просто выдаст регион из мира, который должен существовать */
|
||||
Region* forceGetRegion(WorldId_t worldId, Pos::GlobalRegion pos);
|
||||
|
||||
private:
|
||||
void init(fs::path worldPath);
|
||||
void prerun();
|
||||
void run();
|
||||
|
||||
void initLuaAssets();
|
||||
void initLuaPre();
|
||||
void initLua();
|
||||
void initLuaPost();
|
||||
|
||||
void stepContent();
|
||||
/*
|
||||
Подключение/отключение игроков
|
||||
Дождаться и получить необходимые данные с бд или диска
|
||||
Получить несрочные данные
|
||||
*/
|
||||
|
||||
void stepConnections();
|
||||
|
||||
void stepSyncWithAsync();
|
||||
void stepPlayers();
|
||||
void stepWorlds();
|
||||
/*
|
||||
Переинициализация модов, если требуется
|
||||
Пересмотр наблюдаемых зон (чанки, регионы, миры)
|
||||
Добавить требуемые регионы в список на предзагрузку с приоритетом
|
||||
TODO: нужен механизм асинхронной загрузки регионов с бд
|
||||
|
||||
В начале следующего такта обязательное дожидание прогрузки активной зоны
|
||||
и
|
||||
оповещение миров об изменениях в наблюдаемых регионах
|
||||
*/
|
||||
|
||||
void stepModInitializations();
|
||||
void reloadMods();
|
||||
|
||||
/*
|
||||
Пересчёт зон видимости игроков, если необходимо
|
||||
Выгрузить более не используемые регионы
|
||||
Сохранение регионов
|
||||
Создание списка регионов необходимых для загрузки (бд автоматически будет предзагружать)
|
||||
<Синхронизация с модулем сохранений>
|
||||
Очередь загрузки, выгрузка регионов и получение загруженных из бд регионов
|
||||
Получить список регионов отсутствующих в сохранении и требующих генерации
|
||||
Подпись на загруженные регионы (отправить полностью на клиент)
|
||||
*/
|
||||
|
||||
IWorldSaveBackend::TickSyncInfo_Out stepDatabaseSync();
|
||||
|
||||
/*
|
||||
Синхронизация с генератором карт (отправка запросов на генерацию и получение шума для обработки модами)
|
||||
Обработка модами сырых регионов полученных с бд
|
||||
Синхронизация с потоками модов
|
||||
*/
|
||||
|
||||
void stepGeneratorAndLuaAsync(IWorldSaveBackend::TickSyncInfo_Out db);
|
||||
|
||||
/*
|
||||
Пакеты игроков получает асинхронный поток в RemoteClient
|
||||
Остаётся только обработать распаршенные пакеты
|
||||
*/
|
||||
|
||||
void stepPlayerProceed();
|
||||
|
||||
/*
|
||||
Физика
|
||||
*/
|
||||
|
||||
void stepWorldPhysic();
|
||||
|
||||
/*
|
||||
Глобальный такт
|
||||
*/
|
||||
|
||||
void stepGlobalStep();
|
||||
|
||||
/*
|
||||
Обработка запросов двоичных ресурсов и определений
|
||||
Отправка пакетов игрокам
|
||||
Запуск задачи ChunksChanges
|
||||
*/
|
||||
void stepSyncContent();
|
||||
void stepViewContent();
|
||||
void stepSendPlayersPackets();
|
||||
void stepLoadRegions();
|
||||
void stepGlobal();
|
||||
void stepSave();
|
||||
void save();
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,12 +1 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
namespace LV::Server {
|
||||
|
||||
class NodeDefManager {
|
||||
public:
|
||||
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
@@ -3,24 +3,25 @@
|
||||
#include <TOSLib.hpp>
|
||||
#include <Common/Lockable.hpp>
|
||||
#include <Common/Net.hpp>
|
||||
#include <Common/Async.hpp>
|
||||
#include "Abstract.hpp"
|
||||
#include "Common/Packets.hpp"
|
||||
#include "Server/AssetsManager.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 <bitset>
|
||||
#include <initializer_list>
|
||||
#include <optional>
|
||||
#include <queue>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace LV::Server {
|
||||
|
||||
class World;
|
||||
class GameServer;
|
||||
|
||||
template<typename ServerKey, typename ClientKey, std::enable_if_t<sizeof(ServerKey) >= sizeof(ClientKey), int> = 0>
|
||||
class CSChunkedMapper {
|
||||
std::unordered_map<uint32_t, std::tuple<std::bitset<64>, std::array<ServerKey, 64>>> Chunks;
|
||||
@@ -140,163 +141,163 @@ public:
|
||||
состоянии для клиента и шаблоны, которые клиенту уже не нужны.
|
||||
Соответствующие менеджеры ресурсов будут следить за изменениями
|
||||
этих ресурсов и переотправлять их клиенту
|
||||
|
||||
Информация о двоичных ресурсах будет получена сразу же при их запросе.
|
||||
Действительная отправка ресурсов будет только по запросу клиента.
|
||||
*/
|
||||
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) {
|
||||
Hashes.insert(Hashes.end(), obj.Hashes.begin(), obj.Hashes.end());
|
||||
std::vector<DefWorldId_t> NewWorlds;
|
||||
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() {
|
||||
std::sort(Hashes.begin(), Hashes.end());
|
||||
auto last = std::unique(Hashes.begin(), Hashes.end());
|
||||
Hashes.erase(last, Hashes.end());
|
||||
for(std::vector<ResourceId_t> *vec : {&NewTextures, &NewModels, &NewSounds, &NewWorlds, &NewVoxels, &NewNodes, &NewPortals, &NewEntityes}) {
|
||||
std::sort(vec->begin(), vec->end());
|
||||
auto last = std::unique(vec->begin(), vec->end());
|
||||
vec->erase(last, vec->end());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct AssetBinaryInfo {
|
||||
Resource Data;
|
||||
Hash_t Hash;
|
||||
};
|
||||
|
||||
// using EntityKey = std::tuple<WorldId_c, Pos::GlobalRegion>;
|
||||
|
||||
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;
|
||||
DestroyLock UseLock;
|
||||
Net::AsyncSocket Socket;
|
||||
bool IsConnected = true, IsGoingShutdown = false;
|
||||
|
||||
struct NetworkAndResource_t {
|
||||
// Смена идентификаторов сервера на клиентские
|
||||
SCSKeyRemapper<ServerEntityId_t, ClientEntityId_t> ReMapEntities;
|
||||
// Накопленные чанки для отправки
|
||||
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;
|
||||
struct ResUsesObj {
|
||||
// Счётчики использования базовых ресурсов высшими объектами
|
||||
std::map<BinTextureId_t, uint32_t> BinTexture;
|
||||
std::map<BinSoundId_t, uint32_t> BinSound;
|
||||
|
||||
// Запрос информации об ассетах и профилях контента
|
||||
ResourceRequest NextRequest;
|
||||
// Запрошенные клиентом ресурсы
|
||||
/// TODO: здесь может быть засор
|
||||
std::vector<Hash_t> ClientRequested;
|
||||
// Может использовать текстуры
|
||||
std::map<BinModelId_t, uint32_t> BinModel;
|
||||
|
||||
Net::Packet NextPacket;
|
||||
std::vector<Net::Packet> SimplePackets;
|
||||
void checkPacketBorder(uint16_t size) {
|
||||
if(64000-NextPacket.size() < size || (NextPacket.size() != 0 && size == 0)) {
|
||||
SimplePackets.push_back(std::move(NextPacket));
|
||||
}
|
||||
}
|
||||
// Будут использовать в своих определениях текстуры, звуки, модели
|
||||
std::map<DefWorldId_t, uint32_t> DefWorld;
|
||||
std::map<DefVoxelId_t, uint32_t> DefVoxel;
|
||||
std::map<DefNodeId_t, uint32_t> DefNode;
|
||||
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);
|
||||
void prepareRegionsRemove(WorldId_t worldId, std::vector<Pos::GlobalRegion> regionPoses);
|
||||
void prepareWorldRemove(WorldId_t worldId);
|
||||
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);
|
||||
void prepareWorldUpdate(WorldId_t worldId, World* world);
|
||||
std::unordered_set<BinTextureId_t> Textures;
|
||||
std::unordered_set<BinSoundId_t> Sounds;
|
||||
std::unordered_set<BinModelId_t> Models;
|
||||
};
|
||||
|
||||
std::map<GlobalEntityId_t, EntityResourceUse> Entity;
|
||||
|
||||
// Чанки используют воксели, ноды
|
||||
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 {
|
||||
/*
|
||||
К концу такта собираются необходимые идентификаторы ресурсов
|
||||
В конце такта сервер забирает запросы и возвращает информацию
|
||||
о ресурсах. Отправляем связку Идентификатор + домен:ключ
|
||||
+ хеш. Если у клиента не окажется этого ресурса, он может его запросить
|
||||
*/
|
||||
SCSKeyRemapper<BinTextureId_t, TextureId_c> BinTextures;
|
||||
SCSKeyRemapper<BinSoundId_t, SoundId_c> BinSounds;
|
||||
SCSKeyRemapper<BinModelId_t, ModelId_c> BinModels;
|
||||
|
||||
// Ресурсы, отправленные на клиент в этой сессии
|
||||
std::vector<Hash_t> OnClient;
|
||||
// Отправляемые на клиент ресурсы
|
||||
// Ресурс, количество отправленных байт
|
||||
std::vector<std::tuple<ResourceFile::Hash_t, std::shared_ptr<const std::u8string>, size_t>> ToSend;
|
||||
// Пакет с ресурсами
|
||||
std::vector<Net::Packet> AssetsPackets;
|
||||
Net::Packet AssetsPacket;
|
||||
} AssetsInWork;
|
||||
SCSKeyRemapper<DefWorldId_t, DefWorldId_c> DefWorlds;
|
||||
SCSKeyRemapper<DefVoxelId_t, DefVoxelId_c> DefVoxels;
|
||||
SCSKeyRemapper<DefNodeId_t, DefNodeId_c> DefNodes;
|
||||
SCSKeyRemapper<DefPortalId_t, DefPortalId_c> DefPortals;
|
||||
SCSKeyRemapper<DefEntityId_t, DefEntityId_c> DefEntityes;
|
||||
|
||||
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:
|
||||
const std::string Username;
|
||||
Pos::Object CameraPos = {0, 0, 0};
|
||||
Pos::Object LastPos = CameraPos;
|
||||
ToServer::PacketQuat CameraQuat = {0};
|
||||
TOS::SpinlockObject<std::queue<uint8_t>> Actions;
|
||||
ResourceId RecievedAssets[(int) EnumAssets::MAX_ENUM] = {0};
|
||||
|
||||
// Регионы, наблюдаемые клиентом
|
||||
ContentViewInfo ContentViewState;
|
||||
// Если игрок пересекал границы региона (для перерасчёта ContentViewState)
|
||||
bool CrossedRegion = true;
|
||||
private:
|
||||
|
||||
// Отложенная выгрузка регионов (гистерезис + задержка)
|
||||
// worldId -> (regionPos -> tick_deadline)
|
||||
std::unordered_map<WorldId_t, std::unordered_map<Pos::GlobalRegion, uint32_t>> PendingRegionUnload;
|
||||
std::queue<Pos::GlobalNode> Build, Break;
|
||||
std::optional<ServerEntityId_t> PlayerEntity;
|
||||
RemoteClient(asio::io_context &ioc, tcp::socket socket, const std::string username)
|
||||
: IAsyncDestructible(ioc), LOG("RemoteClient " + username), Socket(ioc, std::move(socket)),
|
||||
Username(username), RunCoro(ioc)
|
||||
{
|
||||
RunCoro.co_spawn(run());
|
||||
}
|
||||
|
||||
virtual coro<> asyncDestructor() override;
|
||||
coro<> run();
|
||||
|
||||
public:
|
||||
RemoteClient(asio::io_context &ioc, tcp::socket socket, const std::string username, GameServer* server)
|
||||
: LOG("RemoteClient " + username), Socket(ioc, std::move(socket)), Username(username), Server(server)
|
||||
{}
|
||||
static RemoteClient_ptr Create(asio::io_context &ioc, tcp::socket socket, const std::string username) {
|
||||
return createUnique<>(ioc, new RemoteClient(ioc, std::move(socket), username));
|
||||
}
|
||||
|
||||
~RemoteClient();
|
||||
virtual ~RemoteClient();
|
||||
|
||||
coro<> run();
|
||||
void shutdown(EnumDisconnect type, const std::string reason);
|
||||
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) {
|
||||
if(IsGoingShutdown)
|
||||
@@ -305,107 +306,58 @@ public:
|
||||
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 потокобезопасные
|
||||
// maybe используются в BackingChunkPressure_t в GameServer в пуле потоков.
|
||||
// если возвращает false, то блокировка сейчас находится у другого потока
|
||||
// и запрос не был обработан.
|
||||
void prepareWorldUpdate(WorldId_t worldId, World* world);
|
||||
void prepareWorldRemove(WorldId_t worldId);
|
||||
|
||||
// Создаёт пакет отправки вокселей чанка
|
||||
void prepareChunkUpdate_Voxels(
|
||||
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 preparePortalUpdate(PortalId_t portalId, void* portal);
|
||||
void preparePortalRemove(PortalId_t portalId);
|
||||
|
||||
// Прочие моменты
|
||||
void prepareCameraSetEntity(ServerEntityId_t entityId);
|
||||
void prepareCameraSetEntity(GlobalEntityId_t entityId);
|
||||
|
||||
// Отправка подготовленных пакетов
|
||||
ResourceRequest pushPreparedPackets();
|
||||
|
||||
// Создаёт пакет для всех игроков с оповещением о новых идентификаторах (id -> domain+key)
|
||||
static Net::Packet makePacket_informateAssets_DK(
|
||||
const std::array<
|
||||
std::vector<AssetsManager::BindDomainKeyInfo>,
|
||||
static_cast<size_t>(EnumAssets::MAX_ENUM)
|
||||
>& dkVector
|
||||
);
|
||||
// Сообщить о ресурсах
|
||||
// Сюда приходят все обновления ресурсов движка
|
||||
// Глобально их можно запросить в выдаче pushPreparedPackets()
|
||||
|
||||
// Создаёт пакет для всех игроков с оповещением об изменении файлов ресурсов (id -> hash+header)
|
||||
static Net::Packet makePacket_informateAssets_HH(
|
||||
const std::array<
|
||||
std::vector<AssetsManager::BindHashHeaderInfo>,
|
||||
static_cast<size_t>(EnumAssets::MAX_ENUM)
|
||||
>& hhVector,
|
||||
const std::array<
|
||||
std::vector<ResourceId>,
|
||||
static_cast<size_t>(EnumAssets::MAX_ENUM)
|
||||
>& lost
|
||||
);
|
||||
// Двоичные файлы
|
||||
void informateDefTexture(const std::unordered_map<BinTextureId_t, std::shared_ptr<ResourceFile>> &textures);
|
||||
void informateDefSound(const std::unordered_map<BinSoundId_t, std::shared_ptr<ResourceFile>> &sounds);
|
||||
void informateDefModel(const std::unordered_map<BinModelId_t, std::shared_ptr<ResourceFile>> &models);
|
||||
|
||||
// Оповещение о двоичных ресурсах (стриминг по запросу)
|
||||
void informateBinaryAssets(
|
||||
const std::vector<std::tuple<ResourceFile::Hash_t, std::shared_ptr<const std::u8string>>>& resources
|
||||
);
|
||||
|
||||
|
||||
// Создаёт пакет со всеми данными об игровых профилях
|
||||
static std::vector<Net::Packet> makePackets_informateDefContent_Full(
|
||||
const ContentManager::Out_getAllProfiles& profiles
|
||||
);
|
||||
|
||||
// Создаёт пакет об обновлении игровых профилей
|
||||
static std::vector<Net::Packet> makePackets_informateDefContentUpdate(
|
||||
const ContentManager::Out_buildEndProfiles& profiles
|
||||
);
|
||||
|
||||
void onUpdate();
|
||||
// Игровые определения
|
||||
void informateDefWorld(const std::unordered_map<DefWorldId_t, World*> &worlds);
|
||||
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);
|
||||
|
||||
private:
|
||||
GameServer* Server = nullptr;
|
||||
void checkPacketBorder(uint16_t size);
|
||||
void protocolError();
|
||||
coro<> readPacket(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);
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#include "Abstract.hpp"
|
||||
#include "Common/Abstract.hpp"
|
||||
#include "Common/Async.hpp"
|
||||
#include <boost/json.hpp>
|
||||
#include <boost/json/object.hpp>
|
||||
#include <memory>
|
||||
@@ -11,59 +10,31 @@
|
||||
|
||||
namespace LV::Server {
|
||||
|
||||
/*
|
||||
Обменная единица мира
|
||||
*/
|
||||
struct SB_Region_In {
|
||||
// Список вокселей всех чанков
|
||||
std::unordered_map<Pos::bvec4u, std::vector<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 {
|
||||
struct SB_Region {
|
||||
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<std::string> VoxelIdToKey, NodeIdToKey, EntityToKey;
|
||||
std::unordered_map<DefEntityId_t, std::string> EntityMap;
|
||||
};
|
||||
|
||||
class IWorldSaveBackend {
|
||||
public:
|
||||
virtual ~IWorldSaveBackend();
|
||||
|
||||
struct TickSyncInfo_In {
|
||||
// Для загрузки и более не используемые (регионы автоматически подгружаются по списку загруженных)
|
||||
std::unordered_map<WorldId_t, std::vector<Pos::GlobalRegion>> Load, Unload;
|
||||
// Регионы для сохранения
|
||||
std::unordered_map<WorldId_t, std::vector<std::pair<Pos::GlobalRegion, SB_Region_In>>> ToSave;
|
||||
};
|
||||
|
||||
struct TickSyncInfo_Out {
|
||||
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 TickSyncInfo_Out tickSync(TickSyncInfo_In &&data) = 0;
|
||||
|
||||
/*
|
||||
Устанавливает радиус вокруг прогруженного региона для предзагрузки регионов
|
||||
*/
|
||||
virtual void changePreloadDistance(uint8_t value) = 0;
|
||||
// Может ли использоваться параллельно
|
||||
virtual bool isAsync() { return false; };
|
||||
// Существует ли регион
|
||||
virtual bool isExist(std::string worldId, Pos::GlobalRegion regionPos) = 0;
|
||||
// Загрузить регион
|
||||
virtual void load(std::string worldId, Pos::GlobalRegion regionPos, SB_Region *data) = 0;
|
||||
// Сохранить регион
|
||||
virtual void save(std::string worldId, Pos::GlobalRegion regionPos, const SB_Region *data) = 0;
|
||||
// Удалить регион
|
||||
virtual void remove(std::string worldId, Pos::GlobalRegion regionPos) = 0;
|
||||
// Удалить мир
|
||||
virtual void remove(std::string worldId) = 0;
|
||||
};
|
||||
|
||||
struct SB_Player {
|
||||
@@ -74,6 +45,8 @@ class IPlayerSaveBackend {
|
||||
public:
|
||||
virtual ~IPlayerSaveBackend();
|
||||
|
||||
// Может ли использоваться параллельно
|
||||
virtual bool isAsync() { return false; };
|
||||
// Существует ли игрок
|
||||
virtual bool isExist(PlayerId_t playerId) = 0;
|
||||
// Загрузить игрока
|
||||
@@ -93,30 +66,34 @@ class IAuthSaveBackend {
|
||||
public:
|
||||
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;
|
||||
// Загрузить игрока (если есть, вернёт true)
|
||||
virtual coro<bool> load(std::string username, SB_Auth &data) = 0;
|
||||
virtual void rename(std::string fromPlayerId, std::string toPlayerId) = 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 {
|
||||
public:
|
||||
virtual ~IModStorageSaveBackend();
|
||||
|
||||
// // Загрузить запись
|
||||
// virtual void load(std::string domain, std::string key, std::string *data) = 0;
|
||||
// // Сохранить запись
|
||||
// virtual void save(std::string domain, std::string key, const std::string *data) = 0;
|
||||
// // Удалить запись
|
||||
// virtual void remove(std::string domain, std::string key) = 0;
|
||||
// // Удалить домен
|
||||
// virtual void remove(std::string domain) = 0;
|
||||
// Может ли использоваться параллельно
|
||||
virtual bool isAsync() { return false; };
|
||||
// Загрузить запись
|
||||
virtual void load(std::string domain, std::string key, std::string *data) = 0;
|
||||
// Сохранить запись
|
||||
virtual void save(std::string domain, std::string key, const std::string *data) = 0;
|
||||
// Удалить запись
|
||||
virtual void remove(std::string domain, std::string key) = 0;
|
||||
// Удалить домен
|
||||
virtual void remove(std::string domain) = 0;
|
||||
};
|
||||
|
||||
class ISaveBackendProvider {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "Filesystem.hpp"
|
||||
#include "Server/Abstract.hpp"
|
||||
#include "Server/SaveBackend.hpp"
|
||||
#include "TOSLib.hpp"
|
||||
#include <boost/json/array.hpp>
|
||||
#include <boost/json/object.hpp>
|
||||
#include <boost/json/parse.hpp>
|
||||
@@ -12,283 +11,18 @@
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
namespace LV::Server::SaveBackends {
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
namespace js = boost::json;
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr uint32_t kRegionVersion = 1;
|
||||
constexpr size_t kRegionNodeCount = 4 * 4 * 4 * 16 * 16 * 16;
|
||||
|
||||
template<typename T>
|
||||
js::object packIdMap(const std::vector<std::pair<T, std::string>>& map) {
|
||||
js::object out;
|
||||
for(const auto& [id, key] : map) {
|
||||
out[std::to_string(id)] = key;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
void unpackIdMap(const js::object& obj, std::vector<std::string>& out) {
|
||||
size_t maxId = 0;
|
||||
for(const auto& kvp : obj) {
|
||||
try {
|
||||
maxId = std::max(maxId, static_cast<size_t>(std::stoul(kvp.key())));
|
||||
} catch(...) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
out.assign(maxId + 1, {});
|
||||
|
||||
for(const auto& kvp : obj) {
|
||||
try {
|
||||
size_t id = std::stoul(kvp.key());
|
||||
out[id] = std::string(kvp.value().as_string());
|
||||
} catch(...) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string encodeCompressed(const uint8_t* data, size_t size) {
|
||||
std::u8string compressed = compressLinear(std::u8string_view(reinterpret_cast<const char8_t*>(data), size));
|
||||
return TOS::Enc::toBase64(reinterpret_cast<const uint8_t*>(compressed.data()), compressed.size());
|
||||
}
|
||||
|
||||
std::u8string decodeCompressed(const std::string& base64) {
|
||||
if(base64.empty())
|
||||
return {};
|
||||
|
||||
TOS::ByteBuffer buffer = TOS::Enc::fromBase64(base64);
|
||||
return unCompressLinear(std::u8string_view(reinterpret_cast<const char8_t*>(buffer.data()), buffer.size()));
|
||||
}
|
||||
|
||||
bool writeRegionFile(const fs::path& path, const SB_Region_In& data) {
|
||||
js::object jobj;
|
||||
jobj["version"] = kRegionVersion;
|
||||
|
||||
{
|
||||
std::vector<VoxelCube_Region> voxels;
|
||||
convertChunkVoxelsToRegion(data.Voxels, voxels);
|
||||
|
||||
js::object jvoxels;
|
||||
jvoxels["count"] = static_cast<uint64_t>(voxels.size());
|
||||
if(!voxels.empty()) {
|
||||
const uint8_t* raw = reinterpret_cast<const uint8_t*>(voxels.data());
|
||||
size_t rawSize = sizeof(VoxelCube_Region) * voxels.size();
|
||||
jvoxels["data"] = encodeCompressed(raw, rawSize);
|
||||
} else {
|
||||
jvoxels["data"] = "";
|
||||
}
|
||||
|
||||
jobj["voxels"] = std::move(jvoxels);
|
||||
jobj["voxels_map"] = packIdMap(data.VoxelsMap);
|
||||
}
|
||||
|
||||
{
|
||||
js::object jnodes;
|
||||
const Node* nodePtr = data.Nodes[0].data();
|
||||
const uint8_t* raw = reinterpret_cast<const uint8_t*>(nodePtr);
|
||||
size_t rawSize = sizeof(Node) * kRegionNodeCount;
|
||||
jnodes["data"] = encodeCompressed(raw, rawSize);
|
||||
jobj["nodes"] = std::move(jnodes);
|
||||
jobj["nodes_map"] = packIdMap(data.NodeMap);
|
||||
}
|
||||
|
||||
{
|
||||
js::array ents;
|
||||
for(const Entity& entity : data.Entityes) {
|
||||
js::object je;
|
||||
je["def"] = static_cast<uint64_t>(entity.getDefId());
|
||||
je["world"] = static_cast<uint64_t>(entity.WorldId);
|
||||
je["pos"] = js::array{entity.Pos.x, entity.Pos.y, entity.Pos.z};
|
||||
je["speed"] = js::array{entity.Speed.x, entity.Speed.y, entity.Speed.z};
|
||||
je["accel"] = js::array{entity.Acceleration.x, entity.Acceleration.y, entity.Acceleration.z};
|
||||
je["quat"] = js::array{entity.Quat.x, entity.Quat.y, entity.Quat.z, entity.Quat.w};
|
||||
je["hp"] = static_cast<uint64_t>(entity.HP);
|
||||
je["abbox"] = js::array{entity.ABBOX.x, entity.ABBOX.y, entity.ABBOX.z};
|
||||
je["in_region"] = js::array{entity.InRegionPos.x, entity.InRegionPos.y, entity.InRegionPos.z};
|
||||
|
||||
js::object tags;
|
||||
for(const auto& [key, value] : entity.Tags) {
|
||||
tags[key] = value;
|
||||
}
|
||||
je["tags"] = std::move(tags);
|
||||
|
||||
ents.push_back(std::move(je));
|
||||
}
|
||||
|
||||
jobj["entities"] = std::move(ents);
|
||||
jobj["entities_map"] = packIdMap(data.EntityMap);
|
||||
}
|
||||
|
||||
fs::create_directories(path.parent_path());
|
||||
std::ofstream fd(path, std::ios::binary);
|
||||
if(!fd)
|
||||
return false;
|
||||
|
||||
fd << js::serialize(jobj);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool readRegionFile(const fs::path& path, DB_Region_Out& out) {
|
||||
try {
|
||||
std::ifstream fd(path, std::ios::binary);
|
||||
if(!fd)
|
||||
return false;
|
||||
|
||||
out = {};
|
||||
|
||||
js::object jobj = js::parse(fd).as_object();
|
||||
|
||||
if(auto it = jobj.find("voxels"); it != jobj.end()) {
|
||||
const js::object& jvoxels = it->value().as_object();
|
||||
size_t count = 0;
|
||||
if(auto itCount = jvoxels.find("count"); itCount != jvoxels.end())
|
||||
count = static_cast<size_t>(itCount->value().to_number<uint64_t>());
|
||||
|
||||
std::string base64;
|
||||
if(auto itData = jvoxels.find("data"); itData != jvoxels.end())
|
||||
base64 = std::string(itData->value().as_string());
|
||||
|
||||
if(count > 0 && !base64.empty()) {
|
||||
std::u8string raw = decodeCompressed(base64);
|
||||
if(raw.size() != sizeof(VoxelCube_Region) * count)
|
||||
return false;
|
||||
|
||||
out.Voxels.resize(count);
|
||||
std::memcpy(out.Voxels.data(), raw.data(), raw.size());
|
||||
}
|
||||
}
|
||||
|
||||
if(auto it = jobj.find("voxels_map"); it != jobj.end()) {
|
||||
unpackIdMap(it->value().as_object(), out.VoxelIdToKey);
|
||||
}
|
||||
|
||||
if(auto it = jobj.find("nodes"); it != jobj.end()) {
|
||||
const js::object& jnodes = it->value().as_object();
|
||||
std::string base64;
|
||||
if(auto itData = jnodes.find("data"); itData != jnodes.end())
|
||||
base64 = std::string(itData->value().as_string());
|
||||
|
||||
if(!base64.empty()) {
|
||||
std::u8string raw = decodeCompressed(base64);
|
||||
if(raw.size() != sizeof(Node) * kRegionNodeCount)
|
||||
return false;
|
||||
|
||||
std::memcpy(out.Nodes[0].data(), raw.data(), raw.size());
|
||||
}
|
||||
}
|
||||
|
||||
if(auto it = jobj.find("nodes_map"); it != jobj.end()) {
|
||||
unpackIdMap(it->value().as_object(), out.NodeIdToKey);
|
||||
}
|
||||
|
||||
if(auto it = jobj.find("entities"); it != jobj.end()) {
|
||||
const js::array& ents = it->value().as_array();
|
||||
out.Entityes.reserve(ents.size());
|
||||
|
||||
for(const js::value& val : ents) {
|
||||
const js::object& je = val.as_object();
|
||||
DefEntityId defId = static_cast<DefEntityId>(je.at("def").to_number<uint64_t>());
|
||||
Entity entity(defId);
|
||||
|
||||
if(auto itWorld = je.find("world"); itWorld != je.end())
|
||||
entity.WorldId = static_cast<DefWorldId>(itWorld->value().to_number<uint64_t>());
|
||||
|
||||
if(auto itPos = je.find("pos"); itPos != je.end()) {
|
||||
const js::array& arr = itPos->value().as_array();
|
||||
entity.Pos = Pos::Object(
|
||||
static_cast<int32_t>(arr.at(0).to_number<int64_t>()),
|
||||
static_cast<int32_t>(arr.at(1).to_number<int64_t>()),
|
||||
static_cast<int32_t>(arr.at(2).to_number<int64_t>())
|
||||
);
|
||||
}
|
||||
|
||||
if(auto itSpeed = je.find("speed"); itSpeed != je.end()) {
|
||||
const js::array& arr = itSpeed->value().as_array();
|
||||
entity.Speed = Pos::Object(
|
||||
static_cast<int32_t>(arr.at(0).to_number<int64_t>()),
|
||||
static_cast<int32_t>(arr.at(1).to_number<int64_t>()),
|
||||
static_cast<int32_t>(arr.at(2).to_number<int64_t>())
|
||||
);
|
||||
}
|
||||
|
||||
if(auto itAccel = je.find("accel"); itAccel != je.end()) {
|
||||
const js::array& arr = itAccel->value().as_array();
|
||||
entity.Acceleration = Pos::Object(
|
||||
static_cast<int32_t>(arr.at(0).to_number<int64_t>()),
|
||||
static_cast<int32_t>(arr.at(1).to_number<int64_t>()),
|
||||
static_cast<int32_t>(arr.at(2).to_number<int64_t>())
|
||||
);
|
||||
}
|
||||
|
||||
if(auto itQuat = je.find("quat"); itQuat != je.end()) {
|
||||
const js::array& arr = itQuat->value().as_array();
|
||||
entity.Quat = glm::quat(
|
||||
static_cast<float>(arr.at(3).to_number<double>()),
|
||||
static_cast<float>(arr.at(0).to_number<double>()),
|
||||
static_cast<float>(arr.at(1).to_number<double>()),
|
||||
static_cast<float>(arr.at(2).to_number<double>())
|
||||
);
|
||||
}
|
||||
|
||||
if(auto itHp = je.find("hp"); itHp != je.end())
|
||||
entity.HP = static_cast<uint32_t>(itHp->value().to_number<uint64_t>());
|
||||
|
||||
if(auto itAabb = je.find("abbox"); itAabb != je.end()) {
|
||||
const js::array& arr = itAabb->value().as_array();
|
||||
entity.ABBOX.x = static_cast<uint64_t>(arr.at(0).to_number<uint64_t>());
|
||||
entity.ABBOX.y = static_cast<uint64_t>(arr.at(1).to_number<uint64_t>());
|
||||
entity.ABBOX.z = static_cast<uint64_t>(arr.at(2).to_number<uint64_t>());
|
||||
}
|
||||
|
||||
if(auto itRegion = je.find("in_region"); itRegion != je.end()) {
|
||||
const js::array& arr = itRegion->value().as_array();
|
||||
entity.InRegionPos = Pos::GlobalRegion(
|
||||
static_cast<int16_t>(arr.at(0).to_number<int64_t>()),
|
||||
static_cast<int16_t>(arr.at(1).to_number<int64_t>()),
|
||||
static_cast<int16_t>(arr.at(2).to_number<int64_t>())
|
||||
);
|
||||
}
|
||||
|
||||
if(auto itTags = je.find("tags"); itTags != je.end()) {
|
||||
const js::object& tags = itTags->value().as_object();
|
||||
for(const auto& kvp : tags) {
|
||||
entity.Tags[std::string(kvp.key())] = static_cast<float>(kvp.value().to_number<double>());
|
||||
}
|
||||
}
|
||||
|
||||
out.Entityes.push_back(std::move(entity));
|
||||
}
|
||||
}
|
||||
|
||||
if(auto it = jobj.find("entities_map"); it != jobj.end()) {
|
||||
unpackIdMap(it->value().as_object(), out.EntityToKey);
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch(const std::exception& exc) {
|
||||
TOS::Logger("RegionLoader::Filesystem").warn() << "Не удалось загрузить регион " << path << "\n\t" << exc.what();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class WSB_Filesystem : public IWorldSaveBackend {
|
||||
fs::path Dir;
|
||||
|
||||
public:
|
||||
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() {
|
||||
@@ -296,105 +30,84 @@ public:
|
||||
}
|
||||
|
||||
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 {
|
||||
TickSyncInfo_Out out;
|
||||
// Сохранение регионов
|
||||
for(auto& [worldId, regions] : data.ToSave) {
|
||||
for(auto& [regionPos, region] : regions) {
|
||||
writeRegionFile(getPath(std::to_string(worldId), regionPos), region);
|
||||
virtual bool isAsync() { return false; };
|
||||
|
||||
virtual bool isExist(std::string worldId, Pos::GlobalRegion regionPos) {
|
||||
return fs::exists(getPath(worldId, regionPos));
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// Загрузка регионов
|
||||
for(auto& [worldId, regions] : data.Load) {
|
||||
for(const Pos::GlobalRegion& regionPos : regions) {
|
||||
const fs::path path = getPath(std::to_string(worldId), regionPos);
|
||||
if(!fs::exists(path)) {
|
||||
out.NotExisten[worldId].push_back(regionPos);
|
||||
continue;
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
DB_Region_Out regionOut;
|
||||
if(!readRegionFile(path, regionOut)) {
|
||||
out.NotExisten[worldId].push_back(regionPos);
|
||||
continue;
|
||||
}
|
||||
|
||||
out.LoadedRegions[worldId].push_back({regionPos, std::move(regionOut)});
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
virtual void save(std::string worldId, Pos::GlobalRegion regionPos, const SB_Region *data) {
|
||||
js::object jobj;
|
||||
|
||||
{
|
||||
js::array jaVoxels;
|
||||
for(const VoxelCube_Region &cube : data->Voxels) {
|
||||
js::object joVoxel;
|
||||
joVoxel["Material"] = cube.VoxelId;
|
||||
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));
|
||||
}
|
||||
|
||||
virtual void changePreloadDistance(uint8_t value) override {
|
||||
|
||||
jobj["Voxels"] = std::move(jaVoxels);
|
||||
}
|
||||
|
||||
// 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::object joVoxelMap;
|
||||
for(const auto &pair : data->VoxelsMap) {
|
||||
joVoxelMap[std::to_string(pair.first)] = pair.second;
|
||||
}
|
||||
|
||||
// {
|
||||
// js::array &jaVoxels = jobj.at("Voxels").as_array();
|
||||
// for(js::value &jvVoxel : jaVoxels) {
|
||||
// js::object &joVoxel = jvVoxel.as_object();
|
||||
// VoxelCube_Region cube;
|
||||
// cube.Data = joVoxel.at("Data").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);
|
||||
// }
|
||||
// }
|
||||
jobj["VoxelsMap"] = std::move(joVoxelMap);
|
||||
}
|
||||
|
||||
// {
|
||||
// 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();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
fs::create_directories(getPath(worldId, regionPos).parent_path());
|
||||
std::ofstream fd(getPath(worldId, regionPos));
|
||||
fd << js::serialize(jobj);
|
||||
}
|
||||
|
||||
// virtual void save(std::string worldId, Pos::GlobalRegion regionPos, const SB_Region *data) {
|
||||
// js::object jobj;
|
||||
virtual void remove(std::string worldId, Pos::GlobalRegion regionPos) {
|
||||
fs::remove(getPath(worldId, regionPos));
|
||||
}
|
||||
|
||||
// {
|
||||
// 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);
|
||||
// }
|
||||
|
||||
// {
|
||||
// js::object joVoxelMap;
|
||||
// for(const auto &pair : data->VoxelsMap) {
|
||||
// joVoxelMap[std::to_string(pair.first)] = pair.second;
|
||||
// }
|
||||
|
||||
// jobj["VoxelsMap"] = std::move(joVoxelMap);
|
||||
// }
|
||||
|
||||
// fs::create_directories(getPath(worldId, regionPos).parent_path());
|
||||
// std::ofstream fd(getPath(worldId, regionPos));
|
||||
// fd << js::serialize(jobj);
|
||||
// }
|
||||
virtual void remove(std::string worldId) {
|
||||
fs::remove_all(Dir / worldId);
|
||||
}
|
||||
};
|
||||
|
||||
class PSB_Filesystem : public IPlayerSaveBackend {
|
||||
@@ -402,7 +115,7 @@ class PSB_Filesystem : public IPlayerSaveBackend {
|
||||
|
||||
public:
|
||||
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() {
|
||||
@@ -437,7 +150,7 @@ class ASB_Filesystem : public IAuthSaveBackend {
|
||||
|
||||
public:
|
||||
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() {
|
||||
@@ -450,39 +163,35 @@ public:
|
||||
|
||||
virtual bool isAsync() { return false; };
|
||||
|
||||
virtual coro<bool> isExist(std::string useranme) override {
|
||||
co_return fs::exists(getPath(useranme));
|
||||
virtual bool isExist(std::string playerId) {
|
||||
return fs::exists(getPath(playerId));
|
||||
}
|
||||
|
||||
virtual coro<> rename(std::string prevUsername, std::string newUsername) override {
|
||||
fs::rename(getPath(prevUsername), getPath(newUsername));
|
||||
co_return;
|
||||
virtual void rename(std::string fromPlayerId, std::string toPlayerId) {
|
||||
fs::rename(getPath(fromPlayerId), getPath(toPlayerId));
|
||||
}
|
||||
|
||||
virtual coro<bool> load(std::string useranme, SB_Auth& data) override {
|
||||
std::ifstream fd(getPath(useranme));
|
||||
virtual void load(std::string playerId, SB_Auth *data) {
|
||||
std::ifstream fd(getPath(playerId));
|
||||
js::object jobj = js::parse(fd).as_object();
|
||||
|
||||
data.Id = jobj.at("Id").as_uint64();
|
||||
data.PasswordHash = jobj.at("PasswordHash").as_string();
|
||||
co_return true;
|
||||
data->Id = jobj.at("Id").as_uint64();
|
||||
data->PasswordHash = jobj.at("PasswordHash").as_string();
|
||||
}
|
||||
|
||||
virtual coro<> save(std::string playerId, const SB_Auth& data) override {
|
||||
virtual void save(std::string playerId, const SB_Auth *data) {
|
||||
js::object jobj;
|
||||
|
||||
jobj["Id"] = data.Id;
|
||||
jobj["PasswordHash"] = data.PasswordHash;
|
||||
jobj["Id"] = data->Id;
|
||||
jobj["PasswordHash"] = data->PasswordHash;
|
||||
|
||||
fs::create_directories(getPath(playerId).parent_path());
|
||||
std::ofstream fd(getPath(playerId));
|
||||
fd << js::serialize(jobj);
|
||||
co_return;
|
||||
}
|
||||
|
||||
virtual coro<> remove(std::string username) override {
|
||||
fs::remove(getPath(username));
|
||||
co_return;
|
||||
virtual void remove(std::string playerId) {
|
||||
fs::remove(getPath(playerId));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -491,7 +200,7 @@ class MSSB_Filesystem : public IModStorageSaveBackend {
|
||||
|
||||
public:
|
||||
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() {
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
#include "World.hpp"
|
||||
#include "ContentManager.hpp"
|
||||
#include "TOSLib.hpp"
|
||||
#include <memory>
|
||||
#include <unordered_set>
|
||||
|
||||
|
||||
namespace LV::Server {
|
||||
|
||||
|
||||
World::World(DefWorldId defId)
|
||||
World::World(DefWorldId_t defId)
|
||||
: DefId(defId)
|
||||
{
|
||||
|
||||
@@ -18,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) {
|
||||
std::vector<Pos::GlobalRegion> out;
|
||||
void World::onUpdate(GameServer *server, float dtime) {
|
||||
|
||||
for(const Pos::GlobalRegion &pos : enter) {
|
||||
auto iterRegion = Regions.find(pos);
|
||||
if(iterRegion == Regions.end()) {
|
||||
out.push_back(pos);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto ®ion = *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> ®ion = 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) {
|
||||
auto region = Regions.find(pos);
|
||||
if(region == Regions.end())
|
||||
continue;
|
||||
|
||||
if(!region->second->Entityes.empty()) {
|
||||
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;
|
||||
std::vector<ContentEventController*> &CECs = region->second->CECs;
|
||||
for(size_t iter = 0; iter < CECs.size(); iter++) {
|
||||
if(CECs[iter] == cec) {
|
||||
CECs.erase(CECs.begin()+iter);
|
||||
@@ -98,107 +46,4 @@ void World::onRemoteClient_RegionsLost(WorldId_t worldId, std::shared_ptr<Remote
|
||||
}
|
||||
}
|
||||
|
||||
World::SaveUnloadInfo World::onStepDatabaseSync(ContentManager& cm, float dtime) {
|
||||
SaveUnloadInfo out;
|
||||
|
||||
constexpr float kSaveDelay = 15.0f;
|
||||
constexpr float kUnloadDelay = 15.0f;
|
||||
|
||||
std::vector<Pos::GlobalRegion> toErase;
|
||||
toErase.reserve(16);
|
||||
|
||||
for(auto& [pos, regionPtr] : Regions) {
|
||||
Region& region = *regionPtr;
|
||||
|
||||
region.LastSaveTime += dtime;
|
||||
|
||||
const bool hasChanges = region.IsChanged || region.IsChunkChanged_Voxels || region.IsChunkChanged_Nodes;
|
||||
const bool needToSave = hasChanges && region.LastSaveTime > kSaveDelay;
|
||||
const bool needToUnload = region.RMs.empty() && region.LastSaveTime > kUnloadDelay;
|
||||
|
||||
if(needToSave || needToUnload) {
|
||||
SB_Region_In data;
|
||||
data.Voxels = region.Voxels;
|
||||
data.Nodes = region.Nodes;
|
||||
|
||||
data.Entityes.reserve(region.Entityes.size());
|
||||
for(const Entity& entity : region.Entityes) {
|
||||
if(entity.IsRemoved || entity.NeedRemove)
|
||||
continue;
|
||||
data.Entityes.push_back(entity);
|
||||
}
|
||||
|
||||
std::unordered_set<DefVoxelId> voxelIds;
|
||||
for(const auto& [chunkPos, voxels] : region.Voxels) {
|
||||
(void) chunkPos;
|
||||
for(const VoxelCube& cube : voxels)
|
||||
voxelIds.insert(cube.VoxelId);
|
||||
}
|
||||
|
||||
std::unordered_set<DefNodeId> nodeIds;
|
||||
for(const auto& chunk : region.Nodes) {
|
||||
for(const Node& node : chunk)
|
||||
nodeIds.insert(node.NodeId);
|
||||
}
|
||||
|
||||
std::unordered_set<DefEntityId> entityIds;
|
||||
for(const Entity& entity : data.Entityes)
|
||||
entityIds.insert(entity.getDefId());
|
||||
|
||||
data.VoxelsMap.reserve(voxelIds.size());
|
||||
for(DefVoxelId id : voxelIds) {
|
||||
auto dk = cm.getDK(EnumDefContent::Voxel, id);
|
||||
if(!dk)
|
||||
continue;
|
||||
data.VoxelsMap.emplace_back(id, dk->Domain + ":" + dk->Key);
|
||||
}
|
||||
|
||||
data.NodeMap.reserve(nodeIds.size());
|
||||
for(DefNodeId id : nodeIds) {
|
||||
auto dk = cm.getDK(EnumDefContent::Node, id);
|
||||
if(!dk)
|
||||
continue;
|
||||
data.NodeMap.emplace_back(id, dk->Domain + ":" + dk->Key);
|
||||
}
|
||||
|
||||
data.EntityMap.reserve(entityIds.size());
|
||||
for(DefEntityId id : entityIds) {
|
||||
auto dk = cm.getDK(EnumDefContent::Entity, id);
|
||||
if(!dk)
|
||||
continue;
|
||||
data.EntityMap.emplace_back(id, dk->Domain + ":" + dk->Key);
|
||||
}
|
||||
|
||||
out.ToSave.push_back({pos, std::move(data)});
|
||||
|
||||
region.LastSaveTime = 0.0f;
|
||||
region.IsChanged = false;
|
||||
}
|
||||
|
||||
if(needToUnload) {
|
||||
out.ToUnload.push_back(pos);
|
||||
toErase.push_back(pos);
|
||||
}
|
||||
}
|
||||
|
||||
for(const Pos::GlobalRegion& pos : toErase) {
|
||||
Regions.erase(pos);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
void World::pushRegions(std::vector<std::pair<Pos::GlobalRegion, RegionIn>> regions) {
|
||||
for(auto& [key, value] : regions) {
|
||||
Region ®ion = *(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) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#include "Common/Abstract.hpp"
|
||||
#include "Server/Abstract.hpp"
|
||||
#include "Server/RemoteClient.hpp"
|
||||
#include "Server/ContentEventController.hpp"
|
||||
#include "Server/SaveBackend.hpp"
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
@@ -12,30 +12,40 @@
|
||||
namespace LV::Server {
|
||||
|
||||
class GameServer;
|
||||
class ContentManager;
|
||||
|
||||
class Region {
|
||||
public:
|
||||
uint64_t IsChunkChanged_Voxels = 0;
|
||||
uint64_t IsChunkChanged_Nodes = 0;
|
||||
uint64_t IsChunkChanged_Voxels[64] = {0};
|
||||
uint64_t IsChunkChanged_Nodes[64] = {0};
|
||||
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
|
||||
//LightPrism Lights[16][16][4][4][4];
|
||||
|
||||
std::array<std::array<Node, 16*16*16>, 4*4*4> Nodes;
|
||||
LightPrism Lights[16][16][16][16][16];
|
||||
std::unordered_map<Pos::Local16_u, Node> Nodes[16][16][16];
|
||||
|
||||
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;
|
||||
|
||||
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++) {
|
||||
@@ -55,48 +65,46 @@ public:
|
||||
|
||||
// Собираем коробки вокселей
|
||||
if(aabb.isCollideWith(regionAABB)) {
|
||||
// Определяем с какими чанками есть пересечения
|
||||
glm::ivec3 beg, end;
|
||||
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++)
|
||||
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.y <= end.y; beg.y++)
|
||||
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;
|
||||
|
||||
auto &voxels = iterVoxels->second;
|
||||
|
||||
CollisionAABB aabbInfo = CollisionAABB(regionAABB);
|
||||
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++) {
|
||||
VoxelCube &cube = voxels[iter];
|
||||
|
||||
for(int axis = 0; axis < 3; axis++)
|
||||
aabbInfo.VecMin.set(axis, aabbInfo.VecMin[axis] & ~0xff00);
|
||||
aabbInfo.VecMin[axis] &= ~0xff00;
|
||||
aabbInfo.VecMax = aabbInfo.VecMin;
|
||||
|
||||
aabbInfo.VecMin.x |= int(cube.Pos.x) << 6;
|
||||
aabbInfo.VecMin.y |= int(cube.Pos.y) << 6;
|
||||
aabbInfo.VecMin.z |= int(cube.Pos.z) << 6;
|
||||
aabbInfo.VecMin.x |= int(cube.Left.X) << 8;
|
||||
aabbInfo.VecMin.y |= int(cube.Left.Y) << 8;
|
||||
aabbInfo.VecMin.z |= int(cube.Left.Z) << 8;
|
||||
|
||||
aabbInfo.VecMax.x |= int(cube.Pos.x+cube.Size.x+1) << 6;
|
||||
aabbInfo.VecMax.y |= int(cube.Pos.y+cube.Size.y+1) << 6;
|
||||
aabbInfo.VecMax.z |= int(cube.Pos.z+cube.Size.z+1) << 6;
|
||||
aabbInfo.VecMax.x |= int(cube.Right.X) << 8;
|
||||
aabbInfo.VecMax.y |= int(cube.Right.Y) << 8;
|
||||
aabbInfo.VecMax.z |= int(cube.Right.Z) << 8;
|
||||
|
||||
if(aabb.isCollideWith(aabbInfo)) {
|
||||
aabbInfo = {
|
||||
.Type = CollisionAABB::EnumType::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),
|
||||
.Id = cube.VoxelId
|
||||
}
|
||||
};
|
||||
|
||||
@@ -110,7 +118,7 @@ public:
|
||||
|
||||
}
|
||||
|
||||
RegionEntityId_t pushEntity(Entity &entity) {
|
||||
LocalEntityId_t pushEntity(Entity &entity) {
|
||||
for(size_t iter = 0; iter < Entityes.size(); iter++) {
|
||||
Entity &obj = Entityes[iter];
|
||||
|
||||
@@ -127,52 +135,40 @@ public:
|
||||
return Entityes.size()-1;
|
||||
}
|
||||
|
||||
// В регионе не осталось места
|
||||
return RegionEntityId_t(-1);
|
||||
return LocalEntityId_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 {
|
||||
DefWorldId DefId;
|
||||
DefWorldId_t DefId;
|
||||
|
||||
public:
|
||||
std::vector<Pos::GlobalRegion> NeedToLoad;
|
||||
std::unordered_map<Pos::GlobalRegion, std::unique_ptr<Region>> Regions;
|
||||
|
||||
public:
|
||||
World(DefWorldId defId);
|
||||
World(DefWorldId_t defId);
|
||||
~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(ContentManager& cm, float dtime);
|
||||
|
||||
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 onCEC_RegionsEnter(ContentEventController *cec, const std::vector<Pos::GlobalRegion> &enter);
|
||||
void onCEC_RegionsLost(ContentEventController *cec, const std::vector<Pos::GlobalRegion> &lost);
|
||||
|
||||
*/
|
||||
|
||||
DefWorldId getDefId() const { return DefId; }
|
||||
DefWorldId_t getDefId() const { return DefId; }
|
||||
};
|
||||
|
||||
|
||||
|
||||
312
Src/TOSAsync.hpp
@@ -1,61 +1,59 @@
|
||||
#pragma once
|
||||
|
||||
#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/system_error.hpp"
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/asio/detached.hpp>
|
||||
#include <boost/asio/experimental/awaitable_operators.hpp>
|
||||
#include <exception>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <list>
|
||||
|
||||
|
||||
|
||||
namespace TOS {
|
||||
|
||||
using namespace boost::asio::experimental::awaitable_operators;
|
||||
namespace asio = boost::asio;
|
||||
template<typename T = void>
|
||||
using coro = boost::asio::awaitable<T>;
|
||||
namespace asio = boost::asio;
|
||||
|
||||
class AsyncSemaphore
|
||||
{
|
||||
boost::asio::deadline_timer Deadline;
|
||||
std::atomic<uint8_t> Lock = 0;
|
||||
// class AsyncSemaphore
|
||||
// {
|
||||
// boost::asio::deadline_timer Deadline;
|
||||
// std::atomic<uint8_t> Lock = 0;
|
||||
|
||||
public:
|
||||
AsyncSemaphore(
|
||||
boost::asio::io_context& ioc)
|
||||
: Deadline(ioc, boost::posix_time::ptime(boost::posix_time::pos_infin))
|
||||
{}
|
||||
// public:
|
||||
// AsyncSemaphore(boost::asio::io_context& ioc)
|
||||
// : Deadline(ioc, boost::posix_time::ptime(boost::posix_time::pos_infin))
|
||||
// {}
|
||||
|
||||
boost::asio::awaitable<void> async_wait() {
|
||||
try {
|
||||
co_await Deadline.async_wait(boost::asio::use_awaitable);
|
||||
} catch(boost::system::system_error code) {
|
||||
if(code.code() != boost::system::errc::operation_canceled)
|
||||
throw;
|
||||
}
|
||||
// coro<> async_wait() {
|
||||
// try {
|
||||
// co_await Deadline.async_wait(boost::asio::use_awaitable);
|
||||
// } catch(boost::system::system_error code) {
|
||||
// if(code.code() != boost::system::errc::operation_canceled)
|
||||
// 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) {
|
||||
while(!predicate())
|
||||
co_await async_wait();
|
||||
}
|
||||
// coro<> async_wait(std::function<bool()> predicate) {
|
||||
// while(!predicate())
|
||||
// co_await async_wait();
|
||||
// }
|
||||
|
||||
void notify_one() {
|
||||
Deadline.cancel_one();
|
||||
}
|
||||
|
||||
void notify_all() {
|
||||
Deadline.cancel();
|
||||
}
|
||||
};
|
||||
// void notify_one() {
|
||||
// Deadline.cancel_one();
|
||||
// }
|
||||
|
||||
// void notify_all() {
|
||||
// Deadline.cancel();
|
||||
// }
|
||||
// };
|
||||
|
||||
/*
|
||||
Многие могут уведомлять одного
|
||||
@@ -74,13 +72,14 @@ public:
|
||||
}
|
||||
|
||||
void wait() {
|
||||
try { Timer.wait(); } catch(...) {}
|
||||
Timer.wait();
|
||||
Timer.expires_at(boost::posix_time::ptime(boost::posix_time::pos_infin));
|
||||
}
|
||||
|
||||
coro<> async_wait() {
|
||||
try { co_await Timer.async_wait(); } catch(...) {}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
class WaitableCoro {
|
||||
@@ -93,10 +92,11 @@ public:
|
||||
: IOC(ioc)
|
||||
{}
|
||||
|
||||
void co_spawn(coro<> token) {
|
||||
template<typename Token>
|
||||
void co_spawn(Token token) {
|
||||
Symaphore = std::make_shared<MultipleToOne_AsyncSymaphore>(IOC);
|
||||
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();
|
||||
}, 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()
|
||||
*/
|
||||
class IAsyncDestructible : public std::enable_shared_from_this<IAsyncDestructible> {
|
||||
protected:
|
||||
asio::io_context &IOC;
|
||||
AsyncUseControl AUC;
|
||||
|
||||
virtual coro<> asyncDestructor() { co_await AUC.wait(); }
|
||||
virtual coro<> asyncDestructor() { co_return; }
|
||||
|
||||
public:
|
||||
IAsyncDestructible(asio::io_context &ioc)
|
||||
: IOC(ioc), AUC(ioc)
|
||||
: IOC(ioc)
|
||||
{}
|
||||
|
||||
virtual ~IAsyncDestructible() {}
|
||||
@@ -227,13 +130,12 @@ protected:
|
||||
template<typename T, typename = typename std::is_same<IAsyncDestructible, T>>
|
||||
static std::shared_ptr<T> createShared(asio::io_context &ioc, T *ptr)
|
||||
{
|
||||
return std::shared_ptr<T>(ptr, [&ioc](T *ptr) {
|
||||
boost::asio::co_spawn(ioc,
|
||||
[ptr, &ioc]() mutable -> coro<> {
|
||||
try { co_await dynamic_cast<IAsyncDestructible*>(ptr)->asyncDestructor(); } catch(...) { }
|
||||
asio::post(ioc, [ptr](){ delete ptr; });
|
||||
},
|
||||
boost::asio::detached);
|
||||
return std::shared_ptr<T>(ptr, [&ioc = ioc](T *ptr) {
|
||||
boost::asio::co_spawn(ioc, [](IAsyncDestructible *ptr) -> coro<> {
|
||||
try { co_await ptr->asyncDestructor(); } catch(...) { }
|
||||
delete ptr;
|
||||
co_return;
|
||||
} (ptr), boost::asio::detached);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -241,25 +143,23 @@ protected:
|
||||
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) {
|
||||
boost::asio::co_spawn(ioc,
|
||||
[ptr, &ioc]() mutable -> coro<> {
|
||||
try { co_await dynamic_cast<IAsyncDestructible*>(ptr)->asyncDestructor(); } catch(...) { }
|
||||
asio::post(ioc, [ptr](){ delete ptr; });
|
||||
},
|
||||
boost::asio::detached);
|
||||
boost::asio::co_spawn(ioc, [](IAsyncDestructible *ptr) -> coro<> {
|
||||
try { co_await ptr->asyncDestructor(); } catch(...) { }
|
||||
delete ptr;
|
||||
co_return;
|
||||
} (ptr), boost::asio::detached);
|
||||
});
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
return std::unique_ptr<T, std::function<void(T*)>>(ptr, [&ioc](T *ptr) {
|
||||
boost::asio::co_spawn(ioc,
|
||||
[ptr, &ioc]() mutable -> coro<> {
|
||||
try { co_await dynamic_cast<IAsyncDestructible*>(ptr)->asyncDestructor(); } catch(...) { }
|
||||
asio::post(ioc, [ptr](){ delete ptr; });
|
||||
},
|
||||
boost::asio::detached);
|
||||
return std::unique_ptr<T, std::function<void(T*)>>(ptr, [&ioc = ioc](T *ptr) {
|
||||
boost::asio::co_spawn(ioc, [](IAsyncDestructible *ptr) -> coro<> {
|
||||
try { co_await ptr->asyncDestructor(); } catch(...) { }
|
||||
delete ptr;
|
||||
co_return;
|
||||
} (ptr), boost::asio::detached);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -267,99 +167,13 @@ protected:
|
||||
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) {
|
||||
boost::asio::co_spawn(ioc,
|
||||
[ptr, &ioc]() mutable -> coro<> {
|
||||
try { co_await dynamic_cast<IAsyncDestructible*>(ptr)->asyncDestructor(); } catch(...) { }
|
||||
asio::post(ioc, [ptr](){ delete ptr; });
|
||||
},
|
||||
boost::asio::detached);
|
||||
boost::asio::co_spawn(ioc, [](IAsyncDestructible *ptr) -> coro<> {
|
||||
try { co_await ptr->asyncDestructor(); } catch(...) { }
|
||||
delete ptr;
|
||||
co_return;
|
||||
} (ptr), 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);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
267
Src/TOSLib.hpp
@@ -4,8 +4,6 @@
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <mutex>
|
||||
#include <shared_mutex>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
@@ -13,233 +11,10 @@
|
||||
#define _USE_MATH_DEFINES
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
#include <assert.h>
|
||||
|
||||
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
|
||||
template <typename T>
|
||||
static inline T swapEndian(const T &u) { return u; }
|
||||
@@ -338,10 +113,9 @@ class ByteBuffer : public std::vector<uint8_t> {
|
||||
if(Index + sizeof(T) > Obj->size())
|
||||
throw std::runtime_error("Вышли за пределы буфера");
|
||||
|
||||
T value{};
|
||||
std::memcpy(&value, Obj->data() + Index, sizeof(T));
|
||||
const uint8_t *ptr = Obj->data()+Index;
|
||||
Index += sizeof(T);
|
||||
return swapEndian(value);
|
||||
return swapEndian(*(const T*) ptr);
|
||||
}
|
||||
|
||||
public:
|
||||
@@ -363,8 +137,8 @@ class ByteBuffer : public std::vector<uint8_t> {
|
||||
inline Reader& operator>>(int64_t &value) { value = readOffset<int64_t>(); return *this; }
|
||||
inline Reader& operator>>(uint64_t &value) { value = readOffset<uint64_t>(); return *this; }
|
||||
inline Reader& operator>>(bool &value) { value = readOffset<uint8_t>(); return *this; }
|
||||
inline Reader& operator>>(float &value) { uint32_t raw = readOffset<uint32_t>(); std::memcpy(&value, &raw, sizeof(raw)); return *this; }
|
||||
inline Reader& operator>>(double &value) { uint64_t raw = readOffset<uint64_t>(); std::memcpy(&value, &raw, sizeof(raw)); return *this; }
|
||||
inline Reader& operator>>(float &value) { return operator>>(*(uint32_t*) &value); }
|
||||
inline Reader& operator>>(double &value) { return operator>>(*(uint64_t*) &value); }
|
||||
|
||||
inline int8_t readInt8() { int8_t value; this->operator>>(value); return value; }
|
||||
inline uint8_t readUInt8() { uint8_t value; this->operator>>(value); return value; }
|
||||
@@ -450,17 +224,6 @@ class ByteBuffer : public std::vector<uint8_t> {
|
||||
size_t Index = 0;
|
||||
uint16_t BlockSize = 256;
|
||||
|
||||
template<typename T> inline void writeRaw(const T &value)
|
||||
{
|
||||
uint8_t *ptr = checkBorder(sizeof(T));
|
||||
std::memcpy(ptr, &value, sizeof(T));
|
||||
}
|
||||
|
||||
template<typename T> inline void writeSwapped(const T &value)
|
||||
{
|
||||
T temp = swapEndian(value);
|
||||
writeRaw(temp);
|
||||
}
|
||||
|
||||
inline uint8_t* checkBorder(size_t count)
|
||||
{
|
||||
@@ -481,17 +244,17 @@ class ByteBuffer : public std::vector<uint8_t> {
|
||||
Writer& operator=(const Writer&) = default;
|
||||
Writer& operator=(Writer&&) = default;
|
||||
|
||||
inline Writer& operator<<(const int8_t &value) { writeRaw(value); return *this; }
|
||||
inline Writer& operator<<(const uint8_t &value) { writeRaw(value); return *this; }
|
||||
inline Writer& operator<<(const int16_t &value) { writeSwapped(value); return *this; }
|
||||
inline Writer& operator<<(const uint16_t &value) { writeSwapped(value); return *this; }
|
||||
inline Writer& operator<<(const int32_t &value) { writeSwapped(value); return *this; }
|
||||
inline Writer& operator<<(const uint32_t &value) { writeSwapped(value); return *this; }
|
||||
inline Writer& operator<<(const int64_t &value) { writeSwapped(value); return *this; }
|
||||
inline Writer& operator<<(const uint64_t &value) { writeSwapped(value); return *this; }
|
||||
inline Writer& operator<<(const bool &value) { uint8_t temp = value ? 1 : 0; writeRaw(temp); return *this; }
|
||||
inline Writer& operator<<(const float &value) { uint32_t raw; std::memcpy(&raw, &value, sizeof(raw)); writeSwapped(raw); return *this; }
|
||||
inline Writer& operator<<(const double &value) { uint64_t raw; std::memcpy(&raw, &value, sizeof(raw)); writeSwapped(raw); return *this; }
|
||||
inline Writer& operator<<(const int8_t &value) { *(int8_t*) checkBorder(sizeof(value)) = value; return *this; }
|
||||
inline Writer& operator<<(const uint8_t &value) { *(uint8_t*) checkBorder(sizeof(value)) = value; return *this; }
|
||||
inline Writer& operator<<(const int16_t &value) { *(int16_t*) checkBorder(sizeof(value)) = swapEndian(value); return *this; }
|
||||
inline Writer& operator<<(const uint16_t &value) { *(uint16_t*) checkBorder(sizeof(value)) = swapEndian(value); return *this; }
|
||||
inline Writer& operator<<(const int32_t &value) { *(int32_t*) checkBorder(sizeof(value)) = swapEndian(value); return *this; }
|
||||
inline Writer& operator<<(const uint32_t &value) { *(uint32_t*) checkBorder(sizeof(value)) = swapEndian(value); return *this; }
|
||||
inline Writer& operator<<(const int64_t &value) { *(int64_t*) checkBorder(sizeof(value)) = swapEndian(value); return *this; }
|
||||
inline Writer& operator<<(const uint64_t &value) { *(uint64_t*) checkBorder(sizeof(value)) = swapEndian(value); return *this; }
|
||||
inline Writer& operator<<(const bool &value) { *(uint8_t*) checkBorder(sizeof(value)) = uint8_t(value ? 1 : 0); return *this; }
|
||||
inline Writer& operator<<(const float &value) { *(uint32_t*) checkBorder(sizeof(value)) = swapEndian(*(uint32_t*) &value); return *this; }
|
||||
inline Writer& operator<<(const double &value) { *(uint64_t*) checkBorder(sizeof(value)) = swapEndian(*(uint64_t*) &value); return *this; }
|
||||
|
||||
inline void writeInt8(const int8_t &value) { this->operator<<(value); }
|
||||
inline void writeUInt8(const uint8_t &value) { this->operator<<(value); }
|
||||
|
||||
@@ -11,17 +11,17 @@ namespace fs = std::filesystem;
|
||||
|
||||
namespace LV {
|
||||
|
||||
iResource::iResource() = default;
|
||||
iResource::~iResource() = default;
|
||||
Resource::Resource() = default;
|
||||
Resource::~Resource() = default;
|
||||
|
||||
static std::mutex iResourceCacheMtx;
|
||||
static std::unordered_map<std::string, std::weak_ptr<iResource>> iResourceCache;
|
||||
static std::mutex ResourceCacheMtx;
|
||||
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;
|
||||
|
||||
public:
|
||||
FS_iResource(const std::filesystem::path &path)
|
||||
FS_Resource(const std::filesystem::path &path)
|
||||
{
|
||||
std::ifstream fd(path);
|
||||
|
||||
@@ -36,18 +36,18 @@ public:
|
||||
Data = Array.get();
|
||||
}
|
||||
|
||||
virtual ~FS_iResource() = default;
|
||||
virtual ~FS_Resource() = default;
|
||||
};
|
||||
|
||||
std::shared_ptr<iResource> getResource(const std::string &path) {
|
||||
std::unique_lock<std::mutex> lock(iResourceCacheMtx);
|
||||
std::shared_ptr<Resource> getResource(const std::string &path) {
|
||||
std::unique_lock<std::mutex> lock(ResourceCacheMtx);
|
||||
|
||||
if(auto iter = iResourceCache.find(path); iter != iResourceCache.end()) {
|
||||
std::shared_ptr<iResource> iResource = iter->second.lock();
|
||||
if(!iResource) {
|
||||
iResourceCache.erase(iter);
|
||||
if(auto iter = ResourceCache.find(path); iter != ResourceCache.end()) {
|
||||
std::shared_ptr<Resource> resource = iter->second.lock();
|
||||
if(!resource) {
|
||||
ResourceCache.erase(iter);
|
||||
} else {
|
||||
return iResource;
|
||||
return resource;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,15 +55,15 @@ std::shared_ptr<iResource> getResource(const std::string &path) {
|
||||
fs_path /= path;
|
||||
|
||||
if(fs::exists(fs_path)) {
|
||||
std::shared_ptr<iResource> iResource = std::make_shared<FS_iResource>(fs_path);
|
||||
iResourceCache.emplace(path, iResource);
|
||||
TOS::Logger("iResources").debug() << "Ресурс " << fs_path << " найден в фс";
|
||||
return iResource;
|
||||
std::shared_ptr<Resource> resource = std::make_shared<FS_Resource>(fs_path);
|
||||
ResourceCache.emplace(path, resource);
|
||||
TOS::Logger("Resources").debug() << "Ресурс " << fs_path << " найден в фс";
|
||||
return resource;
|
||||
}
|
||||
|
||||
if(auto iter = _binary_assets_symbols.find(path); iter != _binary_assets_symbols.end()) {
|
||||
TOS::Logger("iResources").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));
|
||||
TOS::Logger("Resources").debug() << "Ресурс " << fs_path << " is inlined";
|
||||
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 << " не найден");
|
||||
|
||||
@@ -22,24 +22,24 @@ struct iBinaryStream : detail::membuf {
|
||||
};
|
||||
|
||||
|
||||
class iResource {
|
||||
class Resource {
|
||||
protected:
|
||||
const uint8_t* Data;
|
||||
size_t Size;
|
||||
|
||||
|
||||
public:
|
||||
iResource();
|
||||
iResource(const uint8_t* data, size_t size)
|
||||
Resource();
|
||||
Resource(const uint8_t* data, size_t size)
|
||||
: Data(data), Size(size)
|
||||
{}
|
||||
|
||||
virtual ~iResource();
|
||||
virtual ~Resource();
|
||||
|
||||
iResource(const iResource&) = delete;
|
||||
iResource(iResource&&) = delete;
|
||||
iResource& operator=(const iResource&) = delete;
|
||||
iResource& operator=(iResource&&) = delete;
|
||||
Resource(const Resource&) = delete;
|
||||
Resource(Resource&&) = delete;
|
||||
Resource& operator=(const Resource&) = delete;
|
||||
Resource& operator=(Resource&&) = delete;
|
||||
|
||||
const uint8_t* getData() const { return Data; }
|
||||
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);
|
||||
|
||||
}
|
||||
@@ -2,28 +2,22 @@
|
||||
import sys
|
||||
import re
|
||||
|
||||
if len(sys.argv) < 3:
|
||||
print("Usage: assets.py <output_cpp> <file1> [file2 ...]")
|
||||
sys.exit(1)
|
||||
|
||||
output_cpp = sys.argv[1]
|
||||
symbols = sys.argv[2:]
|
||||
|
||||
with open(output_cpp, "w") as f:
|
||||
output_file = "resources.cpp"
|
||||
with open(output_file, "w") as f:
|
||||
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)
|
||||
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)
|
||||
f.write(f"\t{{\"{symbol}\", {{(const char*) &{var_name}_start, (const char*) &{var_name}_end}}}},\n")
|
||||
|
||||
f.write("};\n")
|
||||
|
||||
print(f"File {output_cpp} is generated.")
|
||||
print(f"File {output_file} is generated.")
|
||||
|
||||
16
Src/main.cpp
@@ -1,30 +1,18 @@
|
||||
#include "Common/Abstract.hpp"
|
||||
#include "boost/asio/awaitable.hpp"
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <boost/asio.hpp>
|
||||
#include <Client/Vulkan/Vulkan.hpp>
|
||||
#include <thread>
|
||||
#include <Common/Async.hpp>
|
||||
#include <Common/async_mutex.hpp>
|
||||
|
||||
namespace LV {
|
||||
|
||||
/*
|
||||
База ресурсов на стороне клиента
|
||||
Протокол получения ресурсов, удаления, потом -> регулировки размера
|
||||
|
||||
*/
|
||||
|
||||
|
||||
using namespace TOS;
|
||||
|
||||
int main() {
|
||||
|
||||
// LuaVox
|
||||
asio::io_context ioc;
|
||||
Logger LOG = "main";
|
||||
|
||||
LV::Client::VK::Vulkan vkInst(ioc);
|
||||
|
||||
ioc.run();
|
||||
|
||||
|
||||
499
Src/sha2.hpp
@@ -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
@@ -0,0 +1,8 @@
|
||||
[Window][Debug##Default]
|
||||
Pos=0,0
|
||||
Size=400,400
|
||||
|
||||
[Window][MainMenu]
|
||||
Pos=0,0
|
||||
Size=960,540
|
||||
|
||||
@@ -1,30 +1,26 @@
|
||||
#version 460
|
||||
#version 450
|
||||
|
||||
layout (triangles) in;
|
||||
layout (triangle_strip, max_vertices = 3) out;
|
||||
|
||||
layout(location = 0) in GeometryObj {
|
||||
vec3 GeoPos; // Реальная позиция в мире
|
||||
flat uint Texture; // Текстура
|
||||
uint Texture; // Текстура
|
||||
vec2 UV;
|
||||
} Geometry[];
|
||||
|
||||
layout(location = 0) out FragmentObj {
|
||||
vec3 GeoPos; // Реальная позиция в мире
|
||||
vec3 Normal;
|
||||
flat uint Texture; // Текстура
|
||||
uint Texture; // Текстура
|
||||
vec2 UV;
|
||||
} Fragment;
|
||||
|
||||
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++) {
|
||||
gl_Position = gl_in[iter].gl_Position;
|
||||
Fragment.GeoPos = Geometry[iter].GeoPos;
|
||||
Fragment.Texture = Geometry[iter].Texture;
|
||||
Fragment.UV = Geometry[iter].UV;
|
||||
Fragment.Normal = normal;
|
||||
EmitVertex();
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
vec3 GeoPos; // Реальная позиция в мире
|
||||
flat uint Texture; // Текстура
|
||||
uint Texture; // Текстура
|
||||
vec2 UV;
|
||||
} Geometry;
|
||||
|
||||
@@ -16,29 +16,25 @@ layout(push_constant) uniform UniformBufferObject {
|
||||
|
||||
// struct NodeVertexStatic {
|
||||
// uint32_t
|
||||
// FX : 11, FY : 11, N1 : 10, // Позиция, 64 позиции на метр, +3.5м запас
|
||||
// FZ : 11, // Позиция
|
||||
// FX : 9, FY : 9, FZ : 9, // Позиция -112 ~ 369 / 16
|
||||
// N1 : 4, // Не занято
|
||||
// LS : 1, // Масштаб карты освещения (1м/16 или 1м)
|
||||
// Tex : 18, // Текстура
|
||||
// N2 : 2, // Не занято
|
||||
// N2 : 14, // Не занято
|
||||
// TU : 16, TV : 16; // UV на текстуре
|
||||
// };
|
||||
|
||||
void main()
|
||||
{
|
||||
uint fx = Vertex.x & 0x7ffu;
|
||||
uint fy = (Vertex.x >> 11) & 0x7ffu;
|
||||
uint fz = Vertex.y & 0x7ffu;
|
||||
|
||||
vec4 baseVec = ubo.model*vec4(
|
||||
float(fx) / 64.f - 3.5f,
|
||||
float(fy) / 64.f - 3.5f,
|
||||
float(fz) / 64.f - 3.5f,
|
||||
float(Vertex.x & 0x1ff) / 16.f - 7,
|
||||
float((Vertex.x >> 9) & 0x1ff) / 16.f - 7,
|
||||
float((Vertex.x >> 18) & 0x1ff) / 16.f - 7,
|
||||
1
|
||||
);
|
||||
|
||||
Geometry.GeoPos = baseVec.xyz;
|
||||
Geometry.Texture = (Vertex.y >> 12) & 0x3ffffu;
|
||||
Geometry.Texture = Vertex.y & 0x3ffff;
|
||||
Geometry.UV = vec2(
|
||||
float(Vertex.z & 0xffff) / pow(2, 16),
|
||||
float((Vertex.z >> 16) & 0xffff) / pow(2, 16)
|
||||
|
||||
@@ -1,64 +1,74 @@
|
||||
#version 460
|
||||
|
||||
layout(early_fragment_tests) in;
|
||||
#version 450
|
||||
|
||||
layout(location = 0) in FragmentObj {
|
||||
vec3 GeoPos; // Реальная позиция в мире
|
||||
vec3 Normal;
|
||||
flat uint Texture; // Текстура
|
||||
uint Texture; // Текстура
|
||||
vec2 UV;
|
||||
} Fragment;
|
||||
|
||||
layout(location = 0) out vec4 Frame;
|
||||
|
||||
struct AtlasEntry {
|
||||
vec4 UVMinMax;
|
||||
uint Layer;
|
||||
uint Flags;
|
||||
uint _Pad0;
|
||||
uint _Pad1;
|
||||
struct InfoSubTexture {
|
||||
uint Flags; // 1 isExist
|
||||
uint PosXY, WidthHeight;
|
||||
|
||||
uint AnimationFrames_AnimationTimePerFrame;
|
||||
};
|
||||
|
||||
const uint ATLAS_ENTRY_VALID = 1u;
|
||||
|
||||
uniform layout(set = 0, binding = 0) sampler2DArray MainAtlas;
|
||||
uniform layout(set = 0, binding = 0) sampler2D MainAtlas;
|
||||
layout(set = 0, binding = 1) readonly buffer MainAtlasLayoutObj {
|
||||
AtlasEntry Entries[];
|
||||
uint SubsCount;
|
||||
uint Counter;
|
||||
uint WidthHeight;
|
||||
|
||||
InfoSubTexture SubTextures[];
|
||||
} 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 {
|
||||
vec3 Color;
|
||||
} 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);
|
||||
uint flags = (texId & 0xffff0000) >> 16;
|
||||
texId &= 0xffff;
|
||||
vec4 color = vec4(uv, 0, 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));
|
||||
}
|
||||
|
||||
vec3 blendOverlay(vec3 base, vec3 blend) {
|
||||
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]);
|
||||
if((flags & (2 | 4)) > 0)
|
||||
{
|
||||
if((flags & 2) > 0)
|
||||
color = vec4(1, 1, 1, 1);
|
||||
else if((flags & 4) > 0)
|
||||
{
|
||||
color = vec4(1);
|
||||
}
|
||||
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() {
|
||||
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;
|
||||
}
|
||||
@@ -1,27 +1,16 @@
|
||||
#version 460
|
||||
#version 450
|
||||
|
||||
layout(location = 0) in FragmentObj {
|
||||
vec3 GeoPos; // Реальная позиция в мире
|
||||
vec3 Normal;
|
||||
flat uint Texture; // Текстура
|
||||
uint Texture; // Текстура
|
||||
vec2 UV;
|
||||
} Fragment;
|
||||
|
||||
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;
|
||||
layout(set = 0, binding = 1) readonly buffer MainAtlasLayoutObj {
|
||||
AtlasEntry Entries[];
|
||||
vec3 Color;
|
||||
} MainAtlasLayout;
|
||||
|
||||
uniform layout(set = 1, binding = 0) sampler2DArray LightMap;
|
||||
@@ -29,22 +18,6 @@ layout(set = 1, binding = 1) readonly buffer LightMapLayoutObj {
|
||||
vec3 Color;
|
||||
} 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() {
|
||||
Frame = atlasColor(Fragment.Texture, Fragment.UV);
|
||||
Frame.xyz *= max(0.2f, dot(Fragment.Normal, normalize(vec3(0.5, 1, 0.8))));
|
||||
|
||||
if(Frame.w == 0)
|
||||
discard;
|
||||
Frame = vec4(1);
|
||||
}
|
||||
@@ -9,22 +9,23 @@ layout(location = 0) in FragmentObj {
|
||||
|
||||
layout(location = 0) out vec4 Frame;
|
||||
|
||||
struct AtlasEntry {
|
||||
vec4 UVMinMax;
|
||||
uint Layer;
|
||||
uint Flags;
|
||||
uint _Pad0;
|
||||
uint _Pad1;
|
||||
struct InfoSubTexture {
|
||||
uint Flags; // 1 isExist
|
||||
uint PosXY, WidthHeight;
|
||||
|
||||
uint AnimationFrames_AnimationTimePerFrame;
|
||||
};
|
||||
|
||||
const uint ATLAS_ENTRY_VALID = 1u;
|
||||
|
||||
uniform layout(set = 0, binding = 0) sampler2DArray MainAtlas;
|
||||
uniform layout(set = 0, binding = 0) sampler2D MainAtlas;
|
||||
layout(set = 0, binding = 1) readonly buffer MainAtlasLayoutObj {
|
||||
AtlasEntry Entries[];
|
||||
uint SubsCount;
|
||||
uint Counter;
|
||||
uint WidthHeight;
|
||||
|
||||
InfoSubTexture SubTextures[];
|
||||
} 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 {
|
||||
vec3 Color;
|
||||
} LightMapLayout;
|
||||
@@ -34,14 +35,42 @@ 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);
|
||||
uint flags = (texId & 0xffff0000) >> 16;
|
||||
texId &= 0xffff;
|
||||
vec4 color = vec4(uv, 0, 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));
|
||||
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);
|
||||
|
||||
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() {
|
||||
|
||||
@@ -9,19 +9,9 @@ layout(location = 0) in Fragment {
|
||||
|
||||
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;
|
||||
layout(set = 0, binding = 1) readonly buffer MainAtlasLayoutObj {
|
||||
AtlasEntry Entries[];
|
||||
vec3 Color;
|
||||
} MainAtlasLayout;
|
||||
|
||||
uniform layout(set = 1, binding = 0) sampler2DArray LightMap;
|
||||
@@ -29,39 +19,6 @@ layout(set = 1, binding = 1) readonly buffer LightMapLayoutObj {
|
||||
vec3 Color;
|
||||
} 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() {
|
||||
vec2 uv;
|
||||
|
||||
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);
|
||||
Frame = vec4(1);
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 138 B |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 5.8 KiB |
|
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 6.0 KiB |
@@ -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` (сырые бинарные данные).
|
||||
@@ -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()` добавляет ресурсы в память и прокидывает их в кэш.
|
||||
|
||||
## Ограничения
|
||||
|
||||
- Класс не предназначен для внешнего многопоточного использования.
|
||||
- Политика приоритета ресурсов в паке фиксированная: первый найденный ключ побеждает.
|
||||
- Коллизии хешей не обрабатываются отдельно.
|
||||
@@ -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.
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
# Текстурные программы (TexturePipelineProgram)
|
||||
|
||||
Текстурная программа — это строка, описывающая источник текстуры и цепочку операций
|
||||
над ней. Такие строки используются в `textures` моделей и компилируются
|
||||
`TexturePipelineProgram`.
|
||||
|
||||
## Общая форма
|
||||
```
|
||||
[tex] <base> [|> op(...)]*
|
||||
```
|
||||
`tex` в начале необязателен.
|
||||
|
||||
## Базовые выражения
|
||||
- `name` или `"name.png"` — ссылка на текстуру из assets. Расширение .png/.jpg/.jpeg допустимо.
|
||||
- `anim(...)` — анимация из спрайт-листа (см. ниже).
|
||||
- `<w>x<h> <#RRGGBB|#RRGGBBAA>` — заливка цветом.
|
||||
|
||||
Примеры:
|
||||
```
|
||||
stone
|
||||
tex "core:stone.png"
|
||||
32x32 "#FF00FF"
|
||||
```
|
||||
|
||||
## Аргументы операций
|
||||
- Позиционные: `op(1, 2, "str")`
|
||||
- Именованные: `op(w=16, h=16)`
|
||||
- Значения: числа (uint32), строки в кавычках, либо идентификаторы.
|
||||
|
||||
Цвета задаются `#RRGGBB` или `#RRGGBBAA`.
|
||||
|
||||
## Операции пайплайна
|
||||
|
||||
Операции без аргументов можно писать без `()`: `brighten` и т.п.
|
||||
В подвыражениях текстур (см. ниже) операции без аргументов нужно писать со скобками:
|
||||
`brighten()`.
|
||||
|
||||
### Операции, принимающие текстуру
|
||||
- `overlay(tex)` — наложение с альфой.
|
||||
- `mask(tex)` — применение альфа-маски.
|
||||
- `lowpart(percent, tex)` — смешивание нижней части (percent 1..100).
|
||||
|
||||
`tex` может быть:
|
||||
- именем текстуры: `overlay("core:stone")`
|
||||
- именованным аргументом: `overlay(tex="core:stone")`
|
||||
- вложенной программой: `overlay( tex stone |> invert("rgb") )`
|
||||
|
||||
### Геометрия и альфа
|
||||
- `resize(w, h)` — ресайз до размеров.
|
||||
- `transform(t)` — трансформация (значение 0..7).
|
||||
- `opacity(a)` — прозрачность 0..255.
|
||||
- `remove_alpha` или `noalpha` — убрать альфа-канал.
|
||||
- `make_alpha(color)` — сделать альфу по цвету (цвет в `#RRGGBB`).
|
||||
|
||||
### Цвет и яркость
|
||||
- `invert(channels="rgb")` — инверсия каналов (`r`, `g`, `b`, `a`).
|
||||
- `brighten()` — лёгкое осветление.
|
||||
- `contrast(value, brightness)` — контраст и яркость (-127..127).
|
||||
- `multiply(color)` — умножение на цвет.
|
||||
- `screen(color)` — экранный режим.
|
||||
- `colorize(color, ratio=255)` — тонирование цветом.
|
||||
|
||||
### Анимация
|
||||
`anim` можно использовать в базе (с указанием текстуры) или в пайплайне
|
||||
над текущим изображением.
|
||||
|
||||
База:
|
||||
```
|
||||
anim(tex, frame_w, frame_h, frames, fps, smooth, axis)
|
||||
```
|
||||
|
||||
Пайплайн:
|
||||
```
|
||||
... |> anim(frame_w, frame_h, frames, fps, smooth, axis)
|
||||
```
|
||||
|
||||
Именованные аргументы:
|
||||
- `tex` — имя текстуры (только для базового `anim`).
|
||||
- `frame_w` или `w`
|
||||
- `frame_h` или `h`
|
||||
- `frames` или `count`
|
||||
- `fps`
|
||||
- `smooth` (0/1)
|
||||
- `axis` — режим нарезки:
|
||||
- `g` или пусто: по сетке (слева направо, сверху вниз)
|
||||
- `x`/`h`: по горизонтали
|
||||
- `y`/`v`: по вертикали
|
||||
|
||||
Если `frames` не задан, количество кадров вычисляется автоматически:
|
||||
- сетка: `(sheet.W / frame_w) * (sheet.H / frame_h)`
|
||||
- ось X/Y: `sheet.W / frame_w` или `sheet.H / frame_h`
|
||||
|
||||
Примеры:
|
||||
```
|
||||
anim("core:sheet", 16, 16, fps=8) # сетка по умолчанию
|
||||
anim("core:sheet", 16, 16, axis="x") # по горизонтали
|
||||
stone |> anim(16, 16, fps=10, smooth=1) # анимировать текущую текстуру
|
||||
```
|
||||
|
||||
## Вложенные текстурные выражения
|
||||
Некоторые операции принимают текстуру в аргументах. Чтобы передать не только имя,
|
||||
но и полноценную программу, используйте префикс `tex`:
|
||||
```
|
||||
overlay( tex "core:stone" |> resize(16,16) |> brighten() )
|
||||
```
|
||||