Compare commits
2 Commits
57d6e816fc
...
mr_s
| Author | SHA1 | Date | |
|---|---|---|---|
| 1710eb974d | |||
| e190c79d00 |
3
.gitignore
vendored
@@ -14,6 +14,3 @@
|
|||||||
/imgui.ini
|
/imgui.ini
|
||||||
/data
|
/data
|
||||||
/gmon.out
|
/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
|
|
||||||
232
CMakeLists.txt
@@ -1,62 +1,34 @@
|
|||||||
cmake_minimum_required(VERSION 3.13)
|
cmake_minimum_required(VERSION 3.13)
|
||||||
|
|
||||||
option(BUILD_CLIENT "Build the client" ON)
|
option(BUILD_CLIENT "Build the client" TRUE)
|
||||||
option(USE_LIBURING "Build with liburing support" ON)
|
|
||||||
|
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 23)
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||||
|
add_compile_options(-fcoroutines)
|
||||||
|
|
||||||
|
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdata-sections -ffunction-sections -DGLM_FORCE_DEPTH_ZERO_TO_ONE")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdata-sections -ffunction-sections -DGLM_FORCE_DEPTH_ZERO_TO_ONE")
|
||||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--gc-sections") # -rdynamic
|
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--gc-sections") # -rdynamic
|
||||||
|
|
||||||
# gprof
|
# gprof
|
||||||
# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pg")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pg")
|
||||||
# set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pg")
|
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pg")
|
||||||
|
|
||||||
# sanitizer
|
# sanitizer
|
||||||
# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer")
|
# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address")
|
||||||
# set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address")
|
# set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address")
|
||||||
|
|
||||||
# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined -fno-sanitize-recover=all -fsanitize=float-divide-by-zero -fsanitize=float-cast-overflow -fno-sanitize=null -fno-sanitize=alignment -fsanitize=address -fno-omit-frame-pointer")
|
# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined -fno-sanitize-recover=all -fsanitize=float-divide-by-zero -fsanitize=float-cast-overflow -fno-sanitize=null -fno-sanitize=alignment")
|
||||||
# set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=undefined -fno-sanitize-recover=all -fsanitize=float-divide-by-zero -fsanitize=float-cast-overflow -fno-sanitize=null -fno-sanitize=alignment -fsanitize=address")
|
|
||||||
|
|
||||||
# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address,undefined -fno-omit-frame-pointer -fno-sanitize-recover=all")
|
project (LuaVox VERSION 0.0 DESCRIPTION "LuaVox Description")
|
||||||
# set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address,undefined")
|
add_executable(${PROJECT_NAME})
|
||||||
|
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_20)
|
||||||
|
|
||||||
project(LuaVox VERSION 0.0 DESCRIPTION "LuaVox Description")
|
file(GLOB_RECURSE SOURCES RELATIVE ${PROJECT_SOURCE_DIR} "Src/*.cpp")
|
||||||
|
target_sources(${PROJECT_NAME} PRIVATE ${SOURCES})
|
||||||
add_library(luavox_common INTERFACE)
|
target_include_directories(${PROJECT_NAME} PUBLIC "${PROJECT_SOURCE_DIR}/Src")
|
||||||
target_compile_features(luavox_common INTERFACE cxx_std_23)
|
|
||||||
|
|
||||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
|
||||||
target_compile_options(luavox_common INTERFACE -fsanitize=address,undefined -fno-omit-frame-pointer -fno-sanitize-recover=all)
|
|
||||||
target_link_options(luavox_common INTERFACE -fsanitize=address,undefined)
|
|
||||||
set(ENV{ASAN_OPTIONS} detect_leaks=0)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU")
|
|
||||||
target_compile_options(luavox_common INTERFACE -fcoroutines)
|
|
||||||
elseif ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
|
|
||||||
target_compile_options(luavox_common INTERFACE -fcoroutine)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(USE_LIBURING)
|
|
||||||
find_package(PkgConfig REQUIRED)
|
|
||||||
pkg_check_modules(LIBURING liburing>=2.0 IMPORTED_TARGET)
|
|
||||||
|
|
||||||
if(LIBURING_FOUND)
|
|
||||||
message(STATUS "liburing found, enabling io_uring support")
|
|
||||||
target_compile_definitions(luavox_common INTERFACE LUAVOX_HAVE_LIBURING)
|
|
||||||
target_link_libraries(luavox_common INTERFACE PkgConfig::LIBURING)
|
|
||||||
else()
|
|
||||||
message(FATAL_ERROR "liburing >= 2.0 not found but USE_LIBURING is ON")
|
|
||||||
endif()
|
|
||||||
else()
|
|
||||||
message(STATUS "liburing support is disabled")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
include(FetchContent)
|
include(FetchContent)
|
||||||
|
|
||||||
@@ -72,55 +44,19 @@ set(Boost_USE_STATIC_LIBS ON)
|
|||||||
|
|
||||||
set(BOOST_INCLUDE_LIBRARIES asio thread json)
|
set(BOOST_INCLUDE_LIBRARIES asio thread json)
|
||||||
set(BOOST_ENABLE_CMAKE ON)
|
set(BOOST_ENABLE_CMAKE ON)
|
||||||
set(BOOST_IOSTREAMS_ENABLE_ZLIB ON)
|
|
||||||
set(BOOST_INCLUDE_LIBRARIES asio thread json iostreams interprocess timer circular_buffer lockfree stacktrace uuid serialization nowide)
|
|
||||||
FetchContent_Declare(
|
FetchContent_Declare(
|
||||||
Boost
|
Boost
|
||||||
GIT_REPOSITORY https://github.com/boostorg/boost.git
|
URL https://github.com/boostorg/boost/releases/download/boost-1.87.0/boost-1.87.0-cmake.7z
|
||||||
GIT_TAG boost-1.87.0
|
USES_TERMINAL_DOWNLOAD TRUE
|
||||||
GIT_PROGRESS true
|
DOWNLOAD_NO_EXTRACT FALSE
|
||||||
USES_TERMINAL_DOWNLOAD true
|
|
||||||
)
|
)
|
||||||
FetchContent_MakeAvailable(Boost)
|
FetchContent_MakeAvailable(Boost)
|
||||||
target_link_libraries(luavox_common INTERFACE Boost::asio Boost::thread Boost::json Boost::iostreams Boost::interprocess Boost::timer Boost::circular_buffer Boost::lockfree Boost::stacktrace Boost::uuid Boost::serialization Boost::nowide)
|
target_link_libraries(${PROJECT_NAME} PUBLIC Boost::asio Boost::thread Boost::json)
|
||||||
|
|
||||||
# glm
|
# glm
|
||||||
# find_package(glm REQUIRED)
|
# find_package(glm REQUIRED)
|
||||||
# target_include_directories(${PROJECT_NAME} PUBLIC ${GLM_INCLUDE_DIR})
|
# target_include_directories(${PROJECT_NAME} PUBLIC ${GLM_INCLUDE_DIR})
|
||||||
# target_link_libraries(${PROJECT_NAME} PUBLIC ${GLM_LIBRARY})
|
# target_link_libraries(${PROJECT_NAME} PUBLIC ${GLM_LIBRARY})
|
||||||
FetchContent_Declare(
|
|
||||||
luajit
|
|
||||||
GIT_REPOSITORY https://luajit.org/git/luajit.git
|
|
||||||
GIT_TAG v2.1
|
|
||||||
GIT_PROGRESS true
|
|
||||||
USES_TERMINAL_DOWNLOAD true
|
|
||||||
)
|
|
||||||
FetchContent_MakeAvailable(luajit)
|
|
||||||
|
|
||||||
set(LUAJIT_DIR ${luajit_SOURCE_DIR})
|
|
||||||
set(LUAJIT_ENABLE_LUA52COMPAT ON)
|
|
||||||
FetchContent_Declare(
|
|
||||||
lua_cmake
|
|
||||||
GIT_REPOSITORY https://github.com/zhaozg/luajit-cmake.git
|
|
||||||
GIT_TAG 300c0b3f472be2be158f5b2e6385579ba5c6c0f9
|
|
||||||
GIT_PROGRESS true
|
|
||||||
USES_TERMINAL_DOWNLOAD true
|
|
||||||
)
|
|
||||||
FetchContent_MakeAvailable(lua_cmake)
|
|
||||||
|
|
||||||
target_link_libraries(luavox_common INTERFACE luajit::header luajit::lib)
|
|
||||||
target_include_directories(luavox_common INTERFACE ${lua_cmake_BINARY_DIR})
|
|
||||||
|
|
||||||
FetchContent_Declare(
|
|
||||||
sol2
|
|
||||||
GIT_REPOSITORY https://github.com/ThePhD/sol2.git
|
|
||||||
GIT_TAG v3.5.0
|
|
||||||
GIT_PROGRESS true
|
|
||||||
USES_TERMINAL_DOWNLOAD true
|
|
||||||
)
|
|
||||||
FetchContent_MakeAvailable(sol2)
|
|
||||||
target_link_libraries(luavox_common INTERFACE sol2::sol2)
|
|
||||||
|
|
||||||
|
|
||||||
FetchContent_Declare(
|
FetchContent_Declare(
|
||||||
glm
|
glm
|
||||||
@@ -128,104 +64,70 @@ FetchContent_Declare(
|
|||||||
GIT_TAG 1.0.1
|
GIT_TAG 1.0.1
|
||||||
)
|
)
|
||||||
FetchContent_MakeAvailable(glm)
|
FetchContent_MakeAvailable(glm)
|
||||||
target_link_libraries(luavox_common INTERFACE glm)
|
target_link_libraries(${PROJECT_NAME} PUBLIC glm)
|
||||||
|
|
||||||
find_package(ICU REQUIRED COMPONENTS i18n uc)
|
find_package(ICU REQUIRED COMPONENTS i18n uc)
|
||||||
target_include_directories(luavox_common INTERFACE ${ICU_INCLUDE_DIR})
|
target_include_directories(${PROJECT_NAME} PUBLIC ${ICU_INCLUDE_DIR})
|
||||||
target_link_libraries(luavox_common INTERFACE ${ICU_LIBRARIES})
|
target_link_libraries(${PROJECT_NAME} PUBLIC ${ICU_LIBRARIES})
|
||||||
|
|
||||||
find_package(OpenSSL REQUIRED)
|
find_package(OpenSSL REQUIRED)
|
||||||
target_include_directories(luavox_common INTERFACE ${OPENSSL_INCLUDE_DIR})
|
target_include_directories(${PROJECT_NAME} PUBLIC ${OPENSSL_INCLUDE_DIR})
|
||||||
target_link_libraries(luavox_common INTERFACE ${OPENSSL_LIBRARIES})
|
target_link_libraries(${PROJECT_NAME} PUBLIC ${OPENSSL_LIBRARIES})
|
||||||
|
|
||||||
# JPEG
|
# JPEG
|
||||||
find_package(JPEG REQUIRED)
|
find_package(JPEG REQUIRED)
|
||||||
target_include_directories(luavox_common INTERFACE ${JPEG_INCLUDE_DIRS})
|
target_include_directories(${PROJECT_NAME} PUBLIC ${JPEG_INCLUDE_DIRS})
|
||||||
target_link_libraries(luavox_common INTERFACE JPEG::JPEG)
|
target_link_libraries(${PROJECT_NAME} PUBLIC JPEG::JPEG)
|
||||||
|
|
||||||
# PNG
|
# PNG
|
||||||
find_package(PNG REQUIRED)
|
find_package(PNG REQUIRED)
|
||||||
target_include_directories(luavox_common INTERFACE ${PNG_INCLUDE_DIRS})
|
target_include_directories(${PROJECT_NAME} PUBLIC ${PNG_INCLUDE_DIRS})
|
||||||
target_link_libraries(luavox_common INTERFACE PNG::PNG)
|
target_link_libraries(${PROJECT_NAME} PUBLIC PNG::PNG)
|
||||||
|
|
||||||
# PNG++
|
# PNG++
|
||||||
target_include_directories(luavox_common INTERFACE "${PROJECT_SOURCE_DIR}/Libs/png++")
|
target_include_directories(${PROJECT_NAME} PUBLIC "${PROJECT_SOURCE_DIR}/Libs/png++")
|
||||||
|
|
||||||
# sqlite3
|
|
||||||
FetchContent_Declare(sqlite3 GIT_REPOSITORY https://github.com/sjinks/sqlite3-cmake GIT_TAG v3.49.1)
|
|
||||||
FetchContent_MakeAvailable(sqlite3)
|
|
||||||
target_link_libraries(luavox_common INTERFACE SQLite::SQLite3)
|
|
||||||
|
|
||||||
# Static Assets
|
|
||||||
find_package(Python3 REQUIRED)
|
|
||||||
set(ASSETS_DIR "${PROJECT_SOURCE_DIR}/assets")
|
|
||||||
file(GLOB_RECURSE ASSETS_LIST RELATIVE "${ASSETS_DIR}" "${ASSETS_DIR}/*.*")
|
|
||||||
|
|
||||||
set(ASSETS_O "${CMAKE_CURRENT_BINARY_DIR}/assets.o")
|
|
||||||
set(ASSETS_LD_O "${CMAKE_CURRENT_BINARY_DIR}/assets_ld.o")
|
|
||||||
set(RESOURCES_CPP "${CMAKE_CURRENT_BINARY_DIR}/resources.cpp")
|
|
||||||
|
|
||||||
set(ASSETS_ABS)
|
|
||||||
foreach(asset IN LISTS ASSETS_LIST)
|
|
||||||
list(APPEND ASSETS_ABS "${ASSETS_DIR}/${asset}")
|
|
||||||
endforeach()
|
|
||||||
|
|
||||||
add_custom_command(
|
|
||||||
OUTPUT ${ASSETS_O} ${RESOURCES_CPP}
|
|
||||||
DEPENDS ${ASSETS_ABS}
|
|
||||||
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/Src/assets.py
|
|
||||||
"${RESOURCES_CPP}"
|
|
||||||
${ASSETS_LIST}
|
|
||||||
COMMAND ${CMAKE_COMMAND} -E chdir "${CMAKE_CURRENT_SOURCE_DIR}/assets" ld -r -b binary -o "${ASSETS_LD_O}" ${ASSETS_LIST}
|
|
||||||
COMMAND ${CMAKE_OBJCOPY}
|
|
||||||
-O elf64-x86-64
|
|
||||||
--rename-section .data=.rodata,alloc,load,readonly,data,contents
|
|
||||||
${ASSETS_LD_O}
|
|
||||||
${ASSETS_O}
|
|
||||||
COMMENT "Embedding assets: generating resources.cpp and assets.o"
|
|
||||||
VERBATIM
|
|
||||||
)
|
|
||||||
|
|
||||||
set_source_files_properties(${RESOURCES_CPP} PROPERTIES GENERATED true)
|
|
||||||
set_source_files_properties(${ASSETS_O} PROPERTIES EXTERNAL_OBJECT true GENERATED true)
|
|
||||||
add_library(assets STATIC ${RESOURCES_CPP} ${ASSETS_O})
|
|
||||||
set_target_properties(assets PROPERTIES LINKER_LANGUAGE C)
|
|
||||||
target_link_libraries(luavox_common INTERFACE assets)
|
|
||||||
|
|
||||||
|
# GLFW3
|
||||||
if(BUILD_CLIENT)
|
if(BUILD_CLIENT)
|
||||||
add_executable(luavox_client)
|
find_package(glfw3 3)
|
||||||
|
|
||||||
# Common
|
if(TARGET glfw)
|
||||||
target_link_libraries(luavox_client PUBLIC luavox_common)
|
target_include_directories(${PROJECT_NAME} PUBLIC ${GLFW_INCLUDE_DIRS})
|
||||||
|
else()
|
||||||
# Исходники
|
FetchContent_Declare(
|
||||||
file(GLOB_RECURSE SOURCES RELATIVE ${PROJECT_SOURCE_DIR} "Src/*.cpp")
|
glfw
|
||||||
target_sources(luavox_client PRIVATE ${SOURCES})
|
GIT_REPOSITORY https://github.com/glfw/glfw.git
|
||||||
target_include_directories(luavox_client PUBLIC "${PROJECT_SOURCE_DIR}/Src")
|
GIT_TAG 3.4
|
||||||
|
)
|
||||||
# GLFW3
|
FetchContent_MakeAvailable(glfw)
|
||||||
FetchContent_Declare(
|
endif()
|
||||||
glfw
|
target_link_libraries(${PROJECT_NAME} PUBLIC glfw)
|
||||||
GIT_REPOSITORY https://github.com/glfw/glfw.git
|
|
||||||
GIT_TAG 3.4
|
|
||||||
)
|
|
||||||
FetchContent_MakeAvailable(glfw)
|
|
||||||
target_link_libraries(luavox_client PUBLIC glfw)
|
|
||||||
|
|
||||||
# FreeType
|
|
||||||
find_package(Freetype REQUIRED)
|
|
||||||
# FetchContent_Declare(
|
|
||||||
# freetype
|
|
||||||
# GIT_REPOSITORY https://github.com/freetype/freetype.git
|
|
||||||
# GIT_TAG freetype
|
|
||||||
# )
|
|
||||||
# FetchContent_MakeAvailable(freetype)
|
|
||||||
target_include_directories(luavox_client PUBLIC ${freetype_INCLUDE_DIRS})
|
|
||||||
target_link_libraries(luavox_client PUBLIC Freetype::Freetype)
|
|
||||||
|
|
||||||
# ImGui
|
|
||||||
file(GLOB SOURCES "${PROJECT_SOURCE_DIR}/Libs/imgui/*.cpp")
|
|
||||||
target_sources(luavox_client PRIVATE ${SOURCES} "${PROJECT_SOURCE_DIR}/Libs/imgui/backends/imgui_impl_glfw.cpp" "${PROJECT_SOURCE_DIR}/Libs/imgui/backends/imgui_impl_vulkan.cpp")
|
|
||||||
target_include_directories(luavox_client PUBLIC "${PROJECT_SOURCE_DIR}/Libs/imgui/")
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# FreeType
|
||||||
|
find_package(Freetype REQUIRED)
|
||||||
|
# FetchContent_Declare(
|
||||||
|
# freetype
|
||||||
|
# GIT_REPOSITORY https://github.com/freetype/freetype.git
|
||||||
|
# GIT_TAG freetype
|
||||||
|
# )
|
||||||
|
# FetchContent_MakeAvailable(freetype)
|
||||||
|
target_include_directories(${PROJECT_NAME} PUBLIC ${freetype_INCLUDE_DIRS})
|
||||||
|
target_link_libraries(${PROJECT_NAME} PUBLIC Freetype::Freetype)
|
||||||
|
|
||||||
|
# ImGui
|
||||||
|
file(GLOB SOURCES "${PROJECT_SOURCE_DIR}/Libs/imgui/*.cpp")
|
||||||
|
target_sources(${PROJECT_NAME} PRIVATE ${SOURCES} "${PROJECT_SOURCE_DIR}/Libs/imgui/backends/imgui_impl_glfw.cpp" "${PROJECT_SOURCE_DIR}/Libs/imgui/backends/imgui_impl_vulkan.cpp")
|
||||||
|
target_include_directories(${PROJECT_NAME} PUBLIC "${PROJECT_SOURCE_DIR}/Libs/imgui/")
|
||||||
|
|
||||||
|
# Static Assets
|
||||||
|
file(GLOB_RECURSE ASSETS RELATIVE "${PROJECT_SOURCE_DIR}/assets" "assets/*.*")
|
||||||
|
add_custom_command(OUTPUT assets.o resources.cpp INPUT ${ASSETS}
|
||||||
|
COMMAND cd ${CMAKE_CURRENT_BINARY_DIR} && ${CMAKE_CURRENT_SOURCE_DIR}/Src/assets.py ${ASSETS}
|
||||||
|
COMMAND cd "${CMAKE_CURRENT_SOURCE_DIR}/assets" && ld -r -b binary -o '${CMAKE_CURRENT_BINARY_DIR}/assets.o' ${ASSETS}
|
||||||
|
COMMAND objcopy --rename-section .data=.rodata,alloc,load,readonly,data,contents ${CMAKE_CURRENT_BINARY_DIR}/assets.o ${CMAKE_CURRENT_BINARY_DIR}/assets.o)
|
||||||
|
|
||||||
|
SET_SOURCE_FILES_PROPERTIES(assets.o PROPERTIES EXTERNAL_OBJECT true GENERATED true)
|
||||||
|
add_library(assets STATIC resources.cpp assets.o)
|
||||||
|
SET_TARGET_PROPERTIES(assets PROPERTIES LINKER_LANGUAGE C)
|
||||||
|
target_link_libraries(${PROJECT_NAME} PUBLIC assets uring)
|
||||||
|
|||||||
@@ -10,9 +10,6 @@
|
|||||||
|
|
||||||
namespace LV::Client {
|
namespace LV::Client {
|
||||||
|
|
||||||
using EntityId_t = uint16_t;
|
|
||||||
using FuncEntityId_t = uint16_t;
|
|
||||||
|
|
||||||
struct GlobalTime {
|
struct GlobalTime {
|
||||||
uint32_t Seconds : 22 = 0, Sub : 10 = 0;
|
uint32_t Seconds : 22 = 0, Sub : 10 = 0;
|
||||||
|
|
||||||
@@ -33,22 +30,32 @@ struct GlobalTime {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct VoxelCube {
|
||||||
|
DefVoxelId_c VoxelId;
|
||||||
|
Pos::Local256_u Left, Size;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Node {
|
||||||
|
DefNodeId_c NodeId;
|
||||||
|
uint8_t Rotate : 6;
|
||||||
|
};
|
||||||
|
|
||||||
// 16 метров ребро
|
// 16 метров ребро
|
||||||
// 256 вокселей ребро
|
// 256 вокселей ребро
|
||||||
struct Chunk {
|
struct Chunk {
|
||||||
// Кубы вокселей в чанке
|
// Кубы вокселей в чанке
|
||||||
std::vector<VoxelCube> Voxels;
|
std::vector<VoxelCube> Voxels;
|
||||||
// Ноды
|
// Ноды
|
||||||
std::array<Node, 16*16*16> Nodes;
|
std::unordered_map<Pos::Local16_u, Node> Nodes;
|
||||||
// Ограничения прохождения света, идущего от солнца (от верха карты до верхней плоскости чанка)
|
// Ограничения прохождения света, идущего от солнца (от верха карты до верхней плоскости чанка)
|
||||||
// LightPrism Lights[16][16];
|
LightPrism Lights[16][16];
|
||||||
};
|
};
|
||||||
|
|
||||||
class Entity {
|
class Entity {
|
||||||
public:
|
public:
|
||||||
// PosQuat
|
// PosQuat
|
||||||
WorldId_t WorldId;
|
DefWorldId_c WorldId;
|
||||||
// PortalId LastUsedPortal;
|
DefPortalId_c LastUsedPortal;
|
||||||
Pos::Object Pos;
|
Pos::Object Pos;
|
||||||
glm::quat Quat;
|
glm::quat Quat;
|
||||||
static constexpr uint16_t HP_BS = 4096, HP_BS_Bit = 12;
|
static constexpr uint16_t HP_BS = 4096, HP_BS_Bit = 12;
|
||||||
@@ -63,28 +70,37 @@ public:
|
|||||||
/* Интерфейс рендера текущего подключения к серверу */
|
/* Интерфейс рендера текущего подключения к серверу */
|
||||||
class IRenderSession {
|
class IRenderSession {
|
||||||
public:
|
public:
|
||||||
// Подгрузка двоичных ресурсов
|
virtual void onDefTexture(TextureId_c id, std::vector<std::byte> &&info) = 0;
|
||||||
virtual void onBinaryResourceAdd(std::vector<Hash_t>) = 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;
|
||||||
|
|
||||||
virtual void onContentDefinesAdd(std::unordered_map<EnumDefContent, std::vector<ResourceId>>) = 0;
|
virtual void onDefWorldUpdates(const std::vector<DefWorldId_c> &updates) = 0;
|
||||||
virtual void onContentDefinesLost(std::unordered_map<EnumDefContent, std::vector<ResourceId>>) = 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_t worldId, const std::unordered_set<Pos::GlobalChunk> &changeOrAddList, const std::unordered_set<Pos::GlobalRegion> &remove) = 0;
|
virtual void onChunksChange(WorldId_c worldId, const std::unordered_set<Pos::GlobalChunk> &changeOrAddList, const std::unordered_set<Pos::GlobalChunk> &remove) = 0;
|
||||||
// Установить позицию для камеры
|
// Установить позицию для камеры
|
||||||
virtual void setCameraPos(WorldId_t worldId, Pos::Object pos, glm::quat quat) = 0;
|
virtual void setCameraPos(WorldId_c worldId, Pos::Object pos, glm::quat quat) = 0;
|
||||||
|
|
||||||
virtual ~IRenderSession();
|
virtual ~IRenderSession();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
struct Region {
|
struct Region {
|
||||||
std::array<Chunk, 4*4*4> Chunks;
|
std::unordered_map<Pos::Local16_u, Chunk> Chunks;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
struct World {
|
struct World {
|
||||||
|
std::vector<EntityId_c> Entitys;
|
||||||
|
std::unordered_map<Pos::GlobalRegion::Key, Region> Regions;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
struct DefWorldInfo {
|
struct DefWorldInfo {
|
||||||
|
|
||||||
};
|
};
|
||||||
@@ -97,14 +113,8 @@ struct DefEntityInfo {
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DefFuncEntityInfo {
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
struct WorldInfo {
|
struct WorldInfo {
|
||||||
std::vector<EntityId_t> Entitys;
|
|
||||||
std::vector<FuncEntityId_t> FuncEntitys;
|
|
||||||
std::unordered_map<Pos::GlobalRegion, Region> Regions;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct VoxelInfo {
|
struct VoxelInfo {
|
||||||
@@ -123,48 +133,24 @@ struct EntityInfo {
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct FuncEntityInfo {
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
struct DefItemInfo {
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
struct DefVoxel_t {};
|
|
||||||
struct DefNode_t {};
|
|
||||||
|
|
||||||
/* Интерфейс обработчика сессии с сервером */
|
/* Интерфейс обработчика сессии с сервером */
|
||||||
class IServerSession {
|
class IServerSession {
|
||||||
struct ArrayHasher {
|
|
||||||
std::size_t operator()(const Hash_t& a) const {
|
|
||||||
std::size_t h = 0;
|
|
||||||
for (auto e : a)
|
|
||||||
h ^= std::hash<int>{}(e) + 0x9e3779b9 + (h << 6) + (h >> 2);
|
|
||||||
|
|
||||||
return h;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
struct {
|
struct {
|
||||||
std::unordered_map<Hash_t, BinaryResource, ArrayHasher> Resources;
|
std::unordered_map<DefWorldId_c, DefWorldInfo> DefWorlds;
|
||||||
} Binary;
|
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;
|
||||||
|
|
||||||
struct {
|
std::unordered_map<WorldId_c, WorldInfo> Worlds;
|
||||||
std::unordered_map<DefVoxelId, DefVoxel_t> DefVoxel;
|
std::unordered_map<PortalId_c, PortalInfo> Portals;
|
||||||
std::unordered_map<DefNodeId, DefNode_t> DefNode;
|
std::unordered_map<EntityId_c, EntityInfo> Entityes;
|
||||||
std::unordered_map<DefWorldId, DefWorldInfo> DefWorld;
|
|
||||||
std::unordered_map<DefPortalId, DefPortalInfo> DefPortal;
|
|
||||||
std::unordered_map<DefEntityId, DefEntityInfo> DefEntity;
|
|
||||||
std::unordered_map<DefItemId, DefItemInfo> DefItem;
|
|
||||||
} Registry;
|
} Registry;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
std::unordered_map<WorldId_t, WorldInfo> Worlds;
|
std::unordered_map<WorldId_c, World> Worlds;
|
||||||
// std::unordered_map<PortalId_t, PortalInfo> Portals;
|
} External;
|
||||||
std::unordered_map<EntityId_t, EntityInfo> Entityes;
|
|
||||||
} Data;
|
|
||||||
|
|
||||||
virtual ~IServerSession();
|
virtual ~IServerSession();
|
||||||
|
|
||||||
@@ -180,7 +166,7 @@ public:
|
|||||||
} CursorMode = EnumCursorMoveMode::Default;
|
} CursorMode = EnumCursorMoveMode::Default;
|
||||||
|
|
||||||
enum struct EnumCursorBtn {
|
enum struct EnumCursorBtn {
|
||||||
Left, Right, Middle, One, Two
|
Left, Middle, Right, One, Two
|
||||||
};
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|||||||
@@ -1,387 +0,0 @@
|
|||||||
#include "AssetsManager.hpp"
|
|
||||||
#include "Common/Abstract.hpp"
|
|
||||||
#include "sqlite3.h"
|
|
||||||
#include <cstddef>
|
|
||||||
#include <filesystem>
|
|
||||||
#include <fstream>
|
|
||||||
#include <optional>
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
|
|
||||||
namespace LV::Client {
|
|
||||||
|
|
||||||
|
|
||||||
AssetsManager::AssetsManager(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"(
|
|
||||||
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 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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG.debug() << "Успешно, запускаем поток обработки";
|
|
||||||
OffThread = std::thread(&AssetsManager::readWriteThread, this, AUC.use());
|
|
||||||
LOG.info() << "Инициализировано хранилище кеша: " << CachePath.c_str();
|
|
||||||
}
|
|
||||||
|
|
||||||
AssetsManager::~AssetsManager() {
|
|
||||||
for(sqlite3_stmt* stmt : {
|
|
||||||
STMT_DISK_INSERT, STMT_DISK_UPDATE_TIME, STMT_DISK_REMOVE, STMT_DISK_CONTAINS,
|
|
||||||
STMT_DISK_SUM, STMT_DISK_COUNT, STMT_INLINE_INSERT, STMT_INLINE_GET,
|
|
||||||
STMT_INLINE_UPDATE_TIME, STMT_INLINE_SUM, STMT_INLINE_COUNT
|
|
||||||
})
|
|
||||||
if(stmt)
|
|
||||||
sqlite3_finalize(stmt);
|
|
||||||
|
|
||||||
if(DB)
|
|
||||||
sqlite3_close(DB);
|
|
||||||
}
|
|
||||||
|
|
||||||
coro<> AssetsManager::asyncDestructor() {
|
|
||||||
assert(NeedShutdown); // Должен быть вызван нормальный shutdown
|
|
||||||
co_await IAsyncDestructible::asyncDestructor();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetsManager::readWriteThread(AsyncUseControl::Lock lock) {
|
|
||||||
try {
|
|
||||||
std::vector<fs::path> assets;
|
|
||||||
size_t maxCacheDatabaseSize, maxLifeTime;
|
|
||||||
|
|
||||||
while(!NeedShutdown && !WriteQueue.get_read().empty()) {
|
|
||||||
// Получить новые данные
|
|
||||||
if(Changes.get_read().AssetsChange) {
|
|
||||||
auto lock = Changes.lock();
|
|
||||||
assets = std::move(lock->Assets);
|
|
||||||
lock->AssetsChange = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
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()) {
|
|
||||||
ResourceKey rk;
|
|
||||||
|
|
||||||
{
|
|
||||||
auto lock = ReadQueue.lock();
|
|
||||||
rk = lock->front();
|
|
||||||
lock->pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool finded = false;
|
|
||||||
// Сначала пробежимся по ресурспакам
|
|
||||||
{
|
|
||||||
std::string_view type;
|
|
||||||
|
|
||||||
switch(rk.Type) {
|
|
||||||
case EnumAssets::Nodestate: type = "nodestate"; break;
|
|
||||||
case EnumAssets::Particle: type = "particle"; break;
|
|
||||||
case EnumAssets::Animation: type = "animation"; break;
|
|
||||||
case EnumAssets::Model: type = "model"; break;
|
|
||||||
case EnumAssets::Texture: type = "texture"; break;
|
|
||||||
case EnumAssets::Sound: type = "sound"; break;
|
|
||||||
case EnumAssets::Font: type = "font"; break;
|
|
||||||
default:
|
|
||||||
std::unreachable();
|
|
||||||
}
|
|
||||||
|
|
||||||
for(const fs::path& path : assets) {
|
|
||||||
fs::path end = path / rk.Domain / type / rk.Key;
|
|
||||||
|
|
||||||
if(!fs::exists(end))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Нашли
|
|
||||||
finded = true;
|
|
||||||
Resource res = Resource(end).convertToMem();
|
|
||||||
ReadyQueue.lock()->emplace_back(rk.Hash, res);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!finded) {
|
|
||||||
// Поищем в малой базе
|
|
||||||
sqlite3_bind_blob(STMT_INLINE_GET, 1, (const void*) rk.Hash.data(), 32, SQLITE_STATIC);
|
|
||||||
int errc = sqlite3_step(STMT_INLINE_GET);
|
|
||||||
if(errc == SQLITE_ROW) {
|
|
||||||
// Есть запись
|
|
||||||
const uint8_t *hash = (const uint8_t*) sqlite3_column_blob(STMT_INLINE_GET, 0);
|
|
||||||
int size = sqlite3_column_bytes(STMT_INLINE_GET, 0);
|
|
||||||
Resource res(hash, size);
|
|
||||||
finded = true;
|
|
||||||
ReadyQueue.lock()->emplace_back(rk.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*) rk.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*) rk.Hash.data(), 32, SQLITE_STATIC);
|
|
||||||
int 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>(rk.Hash[i]);
|
|
||||||
|
|
||||||
hashKey = ss.str();
|
|
||||||
}
|
|
||||||
|
|
||||||
finded = true;
|
|
||||||
ReadyQueue.lock()->emplace_back(rk.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_blob(STMT_DISK_CONTAINS, 1, (const void*) rk.Hash.data(), 32, SQLITE_STATIC);
|
|
||||||
sqlite3_bind_int(STMT_DISK_CONTAINS, 2, time(nullptr));
|
|
||||||
if(sqlite3_step(STMT_DISK_CONTAINS) != SQLITE_DONE) {
|
|
||||||
sqlite3_reset(STMT_DISK_CONTAINS);
|
|
||||||
MAKE_ERROR("Не удалось выполнить подготовленный запрос STMT_DISK_CONTAINS: " << sqlite3_errmsg(DB));
|
|
||||||
}
|
|
||||||
|
|
||||||
sqlite3_reset(STMT_DISK_CONTAINS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!finded) {
|
|
||||||
// Не нашли
|
|
||||||
ReadyQueue.lock()->emplace_back(rk.Hash, std::nullopt);
|
|
||||||
}
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Запись
|
|
||||||
if(!WriteQueue.get_read().empty()) {
|
|
||||||
Resource res;
|
|
||||||
|
|
||||||
{
|
|
||||||
auto lock = WriteQueue.lock();
|
|
||||||
res = lock->front();
|
|
||||||
lock->pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: добавить вычистку места при нехватке
|
|
||||||
|
|
||||||
if(res.size() <= SMALL_RESOURCE) {
|
|
||||||
sqlite3_bind_blob(STMT_INLINE_INSERT, 1, (const void*) res.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);
|
|
||||||
} 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);
|
|
||||||
std::ofstream fd(end, std::ios::binary);
|
|
||||||
fd.write((const char*) res.data(), res.size());
|
|
||||||
|
|
||||||
if(fd.fail())
|
|
||||||
MAKE_ERROR("Ошибка записи в файл: " << end.string());
|
|
||||||
|
|
||||||
fd.close();
|
|
||||||
|
|
||||||
sqlite3_bind_blob(STMT_DISK_INSERT, 1, (const void*) res.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);
|
|
||||||
}
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch(const std::exception& exc) {
|
|
||||||
LOG.warn() << "Ошибка в работе потока: " << exc.what();
|
|
||||||
IssuedAnError = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,219 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "Common/Abstract.hpp"
|
|
||||||
#include <cassert>
|
|
||||||
#include <functional>
|
|
||||||
#include <memory>
|
|
||||||
#include <queue>
|
|
||||||
#include <string>
|
|
||||||
#include <sqlite3.h>
|
|
||||||
#include <TOSLib.hpp>
|
|
||||||
#include <TOSAsync.hpp>
|
|
||||||
#include <filesystem>
|
|
||||||
#include <string_view>
|
|
||||||
#include <thread>
|
|
||||||
|
|
||||||
|
|
||||||
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 AssetsManager : public IAsyncDestructible {
|
|
||||||
public:
|
|
||||||
using Ptr = std::shared_ptr<AssetsManager>;
|
|
||||||
|
|
||||||
struct ResourceKey {
|
|
||||||
Hash_t Hash;
|
|
||||||
EnumAssets Type;
|
|
||||||
std::string Domain, Key;
|
|
||||||
};
|
|
||||||
|
|
||||||
public:
|
|
||||||
virtual ~AssetsManager();
|
|
||||||
static std::shared_ptr<AssetsManager> 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 AssetsManager(ioc, cachePath, maxCacheDirectorySize, maxLifeTime));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Добавить новый полученный с сервера ресурс
|
|
||||||
void pushResources(std::vector<Resource> resources) {
|
|
||||||
WriteQueue.lock()->push_range(resources);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Добавить задачи на чтение
|
|
||||||
void pushReads(std::vector<ResourceKey> keys) {
|
|
||||||
ReadQueue.lock()->push_range(keys);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Получить считанные данные
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Установка путей до папок assets
|
|
||||||
void setResourcePacks(std::vector<fs::path> packsAssets) {
|
|
||||||
auto lock = Changes.lock();
|
|
||||||
lock->Assets = std::move(packsAssets);
|
|
||||||
lock->AssetsChange = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Запуск процедуры проверки хешей всего хранимого кеша
|
|
||||||
void runFullDatabaseRecheck(std::move_only_function<void(std::string result)>&& func) {
|
|
||||||
auto lock = Changes.lock();
|
|
||||||
lock->OnRecheckEnd = std::move(func);
|
|
||||||
lock->FullRecheck = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Уведомление о завершении работы
|
|
||||||
void prepareShutdown() {
|
|
||||||
NeedShutdown = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// После этого вызова уже нельзя будет обращатся ко внешним ресурсам
|
|
||||||
void shutdown() {
|
|
||||||
assert(NeedShutdown);
|
|
||||||
OffThread.join();
|
|
||||||
}
|
|
||||||
|
|
||||||
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_INLINE_INSERT = nullptr, // Вставка ресурса
|
|
||||||
*STMT_INLINE_GET = nullptr, // Поиск ресурса по хешу
|
|
||||||
*STMT_INLINE_UPDATE_TIME = nullptr, // Обновить дату последнего использования
|
|
||||||
*STMT_INLINE_SUM = nullptr, // Размер внутреннего хранилища
|
|
||||||
*STMT_INLINE_COUNT = nullptr; // Возвращает количество записей
|
|
||||||
|
|
||||||
// Полный размер данных на диске (насколько известно)
|
|
||||||
volatile size_t DatabaseSize = 0;
|
|
||||||
|
|
||||||
// Очередь задач на чтение
|
|
||||||
TOS::SpinlockObject<std::queue<ResourceKey>> ReadQueue;
|
|
||||||
// Очередь на запись ресурсов
|
|
||||||
TOS::SpinlockObject<std::queue<Resource>> WriteQueue;
|
|
||||||
// Очередь на выдачу результатов чтения
|
|
||||||
TOS::SpinlockObject<std::vector<std::pair<Hash_t, std::optional<Resource>>>> ReadyQueue;
|
|
||||||
|
|
||||||
struct Changes_t {
|
|
||||||
std::vector<fs::path> Assets;
|
|
||||||
volatile bool AssetsChange = false;
|
|
||||||
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();
|
|
||||||
AssetsManager(boost::asio::io_context &ioc, const fs::path &cachePath,
|
|
||||||
size_t maxCacheDatabaseSize, size_t maxLifeTime);
|
|
||||||
|
|
||||||
void readWriteThread(AsyncUseControl::Lock lock);
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,6 @@
|
|||||||
#include "Common/Abstract.hpp"
|
#include "Common/Abstract.hpp"
|
||||||
#include "Common/Net.hpp"
|
#include "Common/Net.hpp"
|
||||||
#include "TOSLib.hpp"
|
#include "TOSLib.hpp"
|
||||||
#include "glm/ext/quaternion_geometric.hpp"
|
|
||||||
#include <GLFW/glfw3.h>
|
#include <GLFW/glfw3.h>
|
||||||
#include <boost/asio/deadline_timer.hpp>
|
#include <boost/asio/deadline_timer.hpp>
|
||||||
#include <boost/asio/this_coro.hpp>
|
#include <boost/asio/this_coro.hpp>
|
||||||
@@ -21,86 +20,27 @@ namespace LV::Client {
|
|||||||
ParsedPacket::~ParsedPacket() = default;
|
ParsedPacket::~ParsedPacket() = default;
|
||||||
|
|
||||||
struct PP_Content_ChunkVoxels : public ParsedPacket {
|
struct PP_Content_ChunkVoxels : public ParsedPacket {
|
||||||
WorldId_t Id;
|
WorldId_c Id;
|
||||||
Pos::GlobalChunk Pos;
|
Pos::GlobalChunk Pos;
|
||||||
std::vector<VoxelCube> Cubes;
|
std::vector<VoxelCube> Cubes;
|
||||||
|
|
||||||
PP_Content_ChunkVoxels(WorldId_t id, Pos::GlobalChunk pos, std::vector<VoxelCube> &&cubes)
|
PP_Content_ChunkVoxels(ToClient::L1 l1, uint8_t l2, WorldId_c id, Pos::GlobalChunk pos, std::vector<VoxelCube> &&cubes)
|
||||||
: ParsedPacket(ToClient::L1::Content, (uint8_t) ToClient::L2Content::ChunkVoxels), Id(id), Pos(pos), Cubes(std::move(cubes))
|
: ParsedPacket(l1, l2), Id(id), Pos(pos), Cubes(std::move(cubes))
|
||||||
{}
|
{}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PP_Content_ChunkNodes : public ParsedPacket {
|
struct PP_Content_ChunkRemove : public ParsedPacket {
|
||||||
WorldId_t Id;
|
WorldId_c Id;
|
||||||
Pos::GlobalChunk Pos;
|
Pos::GlobalChunk Pos;
|
||||||
std::array<Node, 16*16*16> Nodes;
|
|
||||||
|
|
||||||
PP_Content_ChunkNodes(WorldId_t id, Pos::GlobalChunk pos)
|
PP_Content_ChunkRemove(ToClient::L1 l1, uint8_t l2, WorldId_c id, Pos::GlobalChunk pos)
|
||||||
: ParsedPacket(ToClient::L1::Content, (uint8_t) ToClient::L2Content::ChunkNodes), Id(id), Pos(pos)
|
: ParsedPacket(l1, l2), Id(id), Pos(pos)
|
||||||
{
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct PP_Content_RegionRemove : public ParsedPacket {
|
|
||||||
WorldId_t Id;
|
|
||||||
Pos::GlobalRegion Pos;
|
|
||||||
|
|
||||||
PP_Content_RegionRemove(WorldId_t id, Pos::GlobalRegion pos)
|
|
||||||
: ParsedPacket(ToClient::L1::Content, (uint8_t) ToClient::L2Content::RemoveRegion), Id(id), Pos(pos)
|
|
||||||
{}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct PP_Definition_Voxel : public ParsedPacket {
|
|
||||||
DefVoxelId Id;
|
|
||||||
DefVoxel_t Def;
|
|
||||||
|
|
||||||
PP_Definition_Voxel(DefVoxelId id, DefVoxel_t def)
|
|
||||||
: ParsedPacket(ToClient::L1::Definition, (uint8_t) ToClient::L2Definition::Voxel),
|
|
||||||
Id(id), Def(def)
|
|
||||||
{}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct PP_Definition_FreeVoxel : public ParsedPacket {
|
|
||||||
DefVoxelId Id;
|
|
||||||
|
|
||||||
PP_Definition_FreeVoxel(DefVoxelId id)
|
|
||||||
: ParsedPacket(ToClient::L1::Definition, (uint8_t) ToClient::L2Definition::FreeVoxel),
|
|
||||||
Id(id)
|
|
||||||
{}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct PP_Definition_Node : public ParsedPacket {
|
|
||||||
DefNodeId Id;
|
|
||||||
DefNode_t Def;
|
|
||||||
|
|
||||||
PP_Definition_Node(DefNodeId id, DefNode_t def)
|
|
||||||
: ParsedPacket(ToClient::L1::Definition, (uint8_t) ToClient::L2Definition::Node),
|
|
||||||
Id(id), Def(def)
|
|
||||||
{}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct PP_Definition_FreeNode : public ParsedPacket {
|
|
||||||
DefNodeId Id;
|
|
||||||
|
|
||||||
PP_Definition_FreeNode(DefNodeId id)
|
|
||||||
: ParsedPacket(ToClient::L1::Definition, (uint8_t) ToClient::L2Definition::FreeNode),
|
|
||||||
Id(id)
|
|
||||||
{}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct PP_Resource_InitResSend : public ParsedPacket {
|
|
||||||
Hash_t Hash;
|
|
||||||
BinaryResource Resource;
|
|
||||||
|
|
||||||
PP_Resource_InitResSend(Hash_t hash, BinaryResource res)
|
|
||||||
: ParsedPacket(ToClient::L1::Resource, (uint8_t) ToClient::L2Resource::InitResSend), Hash(hash), Resource(res)
|
|
||||||
{}
|
{}
|
||||||
};
|
};
|
||||||
|
|
||||||
using namespace TOS;
|
using namespace TOS;
|
||||||
|
|
||||||
ServerSession::~ServerSession() {
|
ServerSession::~ServerSession() {
|
||||||
WorkDeadline.cancel();
|
|
||||||
UseLock.wait_no_use();
|
UseLock.wait_no_use();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,7 +137,6 @@ coro<std::unique_ptr<Net::AsyncSocket>> ServerSession::asyncInitGameProtocol(asi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
co_return std::make_unique<Net::AsyncSocket>(ioc, std::move(socket));
|
co_return std::make_unique<Net::AsyncSocket>(ioc, std::move(socket));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,8 +182,8 @@ void ServerSession::onCursorMove(float xMove, float yMove) {
|
|||||||
|
|
||||||
static constexpr float PI = glm::pi<float>(), PI2 = PI*2, PI_HALF = PI/2, PI_DEG = PI/180;
|
static constexpr float PI = glm::pi<float>(), PI2 = PI*2, PI_HALF = PI/2, PI_DEG = PI/180;
|
||||||
|
|
||||||
deltaPYR.x = std::clamp(PYR.x - yMove*PI_DEG, -PI_HALF+PI_DEG, PI_HALF-PI_DEG)-PYR.x;
|
deltaPYR.x = std::clamp(PYR.x + yMove*PI_DEG, -PI_HALF+PI_DEG, PI_HALF-PI_DEG)-PYR.x;
|
||||||
deltaPYR.y = std::fmod(PYR.y - xMove*PI_DEG, PI2)-PYR.y;
|
deltaPYR.y = std::fmod(PYR.y + xMove*PI_DEG, PI2)-PYR.y;
|
||||||
deltaPYR.z = 0;
|
deltaPYR.z = 0;
|
||||||
|
|
||||||
double gTime = GTime;
|
double gTime = GTime;
|
||||||
@@ -256,26 +195,7 @@ void ServerSession::onCursorMove(float xMove, float yMove) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ServerSession::onCursorBtn(ISurfaceEventListener::EnumCursorBtn btn, bool state) {
|
void ServerSession::onCursorBtn(ISurfaceEventListener::EnumCursorBtn btn, bool state) {
|
||||||
if(!state)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if(btn == EnumCursorBtn::Left) {
|
|
||||||
Net::Packet packet;
|
|
||||||
|
|
||||||
packet << (uint8_t) ToServer::L1::System
|
|
||||||
<< (uint8_t) ToServer::L2System::BlockChange
|
|
||||||
<< uint8_t(0);
|
|
||||||
|
|
||||||
Socket->pushPacket(std::move(packet));
|
|
||||||
} else if(btn == EnumCursorBtn::Right) {
|
|
||||||
Net::Packet packet;
|
|
||||||
|
|
||||||
packet << (uint8_t) ToServer::L1::System
|
|
||||||
<< (uint8_t) ToServer::L2System::BlockChange
|
|
||||||
<< uint8_t(1);
|
|
||||||
|
|
||||||
Socket->pushPacket(std::move(packet));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ServerSession::onKeyboardBtn(int btn, int state) {
|
void ServerSession::onKeyboardBtn(int btn, int state) {
|
||||||
@@ -322,89 +242,42 @@ void ServerSession::atFreeDrawTime(GlobalTime gTime, float dTime) {
|
|||||||
if(Keys.CTRL)
|
if(Keys.CTRL)
|
||||||
mltpl *= 16;
|
mltpl *= 16;
|
||||||
|
|
||||||
Speed += glm::vec3(rot*glm::vec4(0, 0, -1, 1)*float(Keys.W))*mltpl;
|
Speed += glm::vec3(rot*glm::vec4(0, 0, 1, 1)*float(Keys.W))*mltpl;
|
||||||
Speed += glm::vec3(rot*glm::vec4(-1, 0, 0, 1)*float(Keys.A))*mltpl;
|
Speed += glm::vec3(rot*glm::vec4(-1, 0, 0, 1)*float(Keys.A))*mltpl;
|
||||||
Speed += glm::vec3(rot*glm::vec4(0, 0, 1, 1)*float(Keys.S))*mltpl;
|
Speed += glm::vec3(rot*glm::vec4(0, 0, -1, 1)*float(Keys.S))*mltpl;
|
||||||
Speed += glm::vec3(rot*glm::vec4(1, 0, 0, 1)*float(Keys.D))*mltpl;
|
Speed += glm::vec3(rot*glm::vec4(1, 0, 0, 1)*float(Keys.D))*mltpl;
|
||||||
Speed += glm::vec3(0, -1, 0)*float(Keys.SHIFT)*mltpl;
|
Speed += glm::vec3(0, -1, 0)*float(Keys.SHIFT)*mltpl;
|
||||||
Speed += glm::vec3(0, 1, 0)*float(Keys.SPACE)*mltpl;
|
Speed += glm::vec3(0, 1, 0)*float(Keys.SPACE)*mltpl;
|
||||||
|
|
||||||
{
|
{
|
||||||
std::unordered_map<WorldId_t, std::tuple<std::unordered_set<Pos::GlobalChunk>, std::unordered_set<Pos::GlobalRegion>>> changeOrAddList_removeList;
|
std::unordered_map<WorldId_c, std::tuple<std::unordered_set<Pos::GlobalChunk>, std::unordered_set<Pos::GlobalChunk>>> changeOrAddList_removeList;
|
||||||
std::unordered_map<EnumDefContent, std::vector<ResourceId>> onContentDefinesAdd;
|
|
||||||
std::unordered_map<EnumDefContent, std::vector<ResourceId>> onContentDefinesLost;
|
|
||||||
|
|
||||||
// Пакеты
|
// Пакеты
|
||||||
ParsedPacket *pack;
|
ParsedPacket *pack;
|
||||||
while(NetInputPackets.pop(pack)) {
|
while(NetInputPackets.pop(pack)) {
|
||||||
if(pack->Level1 == ToClient::L1::Definition) {
|
if(pack->Level1 == ToClient::L1::Content) {
|
||||||
ToClient::L2Resource l2 = ToClient::L2Resource(pack->Level2);
|
|
||||||
if(l2 == ToClient::L2Resource::InitResSend) {
|
|
||||||
PP_Resource_InitResSend &p = *dynamic_cast<PP_Resource_InitResSend*>(pack);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if(pack->Level1 == ToClient::L1::Definition) {
|
|
||||||
ToClient::L2Definition l2 = ToClient::L2Definition(pack->Level2);
|
|
||||||
|
|
||||||
if(l2 == ToClient::L2Definition::Voxel) {
|
|
||||||
PP_Definition_Voxel &p = *dynamic_cast<PP_Definition_Voxel*>(pack);
|
|
||||||
Registry.DefVoxel[p.Id] = p.Def;
|
|
||||||
onContentDefinesAdd[EnumDefContent::Voxel].push_back(p.Id);
|
|
||||||
} else if(l2 == ToClient::L2Definition::FreeVoxel) {
|
|
||||||
PP_Definition_FreeVoxel &p = *dynamic_cast<PP_Definition_FreeVoxel*>(pack);
|
|
||||||
{
|
|
||||||
auto iter = Registry.DefVoxel.find(p.Id);
|
|
||||||
if(iter != Registry.DefVoxel.end())
|
|
||||||
Registry.DefVoxel.erase(iter);
|
|
||||||
}
|
|
||||||
onContentDefinesLost[EnumDefContent::Voxel].push_back(p.Id);
|
|
||||||
} else if(l2 == ToClient::L2Definition::Node) {
|
|
||||||
PP_Definition_Node &p = *dynamic_cast<PP_Definition_Node*>(pack);
|
|
||||||
Registry.DefNode[p.Id] = p.Def;
|
|
||||||
onContentDefinesAdd[EnumDefContent::Node].push_back(p.Id);
|
|
||||||
} else if(l2 == ToClient::L2Definition::FreeNode) {
|
|
||||||
PP_Definition_FreeNode &p = *dynamic_cast<PP_Definition_FreeNode*>(pack);
|
|
||||||
{
|
|
||||||
auto iter = Registry.DefNode.find(p.Id);
|
|
||||||
if(iter != Registry.DefNode.end())
|
|
||||||
Registry.DefNode.erase(iter);
|
|
||||||
}
|
|
||||||
onContentDefinesLost[EnumDefContent::Node].push_back(p.Id);
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if(pack->Level1 == ToClient::L1::Content) {
|
|
||||||
ToClient::L2Content l2 = ToClient::L2Content(pack->Level2);
|
ToClient::L2Content l2 = ToClient::L2Content(pack->Level2);
|
||||||
if(l2 == ToClient::L2Content::ChunkVoxels) {
|
if(l2 == ToClient::L2Content::ChunkVoxels) {
|
||||||
PP_Content_ChunkVoxels &p = *dynamic_cast<PP_Content_ChunkVoxels*>(pack);
|
PP_Content_ChunkVoxels &p = *dynamic_cast<PP_Content_ChunkVoxels*>(pack);
|
||||||
Pos::GlobalRegion rPos = p.Pos >> 2;
|
Pos::GlobalRegion rPos(p.Pos.X >> 4, p.Pos.Y >> 4, p.Pos.Z >> 4);
|
||||||
Pos::bvec4u cPos = p.Pos & 0x3;
|
Pos::Local16_u cPos(p.Pos.X & 0xf, p.Pos.Y & 0xf, p.Pos.Z & 0xf);
|
||||||
|
|
||||||
Data.Worlds[p.Id].Regions[rPos].Chunks[cPos.pack()].Voxels = std::move(p.Cubes);
|
External.Worlds[p.Id].Regions[rPos].Chunks[cPos].Voxels = std::move(p.Cubes);
|
||||||
|
|
||||||
auto &pair = changeOrAddList_removeList[p.Id];
|
auto &pair = changeOrAddList_removeList[p.Id];
|
||||||
std::get<0>(pair).insert(p.Pos);
|
std::get<0>(pair).insert(p.Pos);
|
||||||
} else if(l2 == ToClient::L2Content::ChunkNodes) {
|
} else if(l2 == ToClient::L2Content::RemoveChunk) {
|
||||||
PP_Content_ChunkNodes &p = *dynamic_cast<PP_Content_ChunkNodes*>(pack);
|
PP_Content_ChunkRemove &p = *dynamic_cast<PP_Content_ChunkRemove*>(pack);
|
||||||
Pos::GlobalRegion rPos = p.Pos >> 2;
|
|
||||||
Pos::bvec4u cPos = p.Pos & 0x3;
|
Pos::GlobalRegion rPos(p.Pos.X >> 4, p.Pos.Y >> 4, p.Pos.Z >> 4);
|
||||||
|
Pos::Local16_u cPos(p.Pos.X & 0xf, p.Pos.Y & 0xf, p.Pos.Z & 0xf);
|
||||||
|
auto &obj = External.Worlds[p.Id].Regions[rPos].Chunks;
|
||||||
|
auto iter = obj.find(cPos);
|
||||||
|
if(iter != obj.end())
|
||||||
|
obj.erase(iter);
|
||||||
|
|
||||||
Node *nodes = (Node*) Data.Worlds[p.Id].Regions[rPos].Chunks[cPos.pack()].Nodes.data();
|
|
||||||
std::copy(p.Nodes.begin(), p.Nodes.end(), nodes);
|
|
||||||
|
|
||||||
auto &pair = changeOrAddList_removeList[p.Id];
|
auto &pair = changeOrAddList_removeList[p.Id];
|
||||||
std::get<0>(pair).insert(p.Pos);
|
std::get<1>(pair).insert(p.Pos);
|
||||||
} else if(l2 == ToClient::L2Content::RemoveRegion) {
|
|
||||||
PP_Content_RegionRemove &p = *dynamic_cast<PP_Content_RegionRemove*>(pack);
|
|
||||||
|
|
||||||
auto ®ions = Data.Worlds[p.Id].Regions;
|
|
||||||
auto obj = regions.find(p.Pos);
|
|
||||||
if(obj != regions.end()) {
|
|
||||||
regions.erase(obj);
|
|
||||||
|
|
||||||
auto &pair = changeOrAddList_removeList[p.Id];
|
|
||||||
std::get<1>(pair).insert(p.Pos);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -414,25 +287,11 @@ void ServerSession::atFreeDrawTime(GlobalTime gTime, float dTime) {
|
|||||||
if(RS && !changeOrAddList_removeList.empty()) {
|
if(RS && !changeOrAddList_removeList.empty()) {
|
||||||
for(auto &pair : changeOrAddList_removeList) {
|
for(auto &pair : changeOrAddList_removeList) {
|
||||||
// Если случится что чанк был изменён и удалён, то исключаем его обновления
|
// Если случится что чанк был изменён и удалён, то исключаем его обновления
|
||||||
for(Pos::GlobalRegion removed : std::get<1>(pair.second)) {
|
for(Pos::GlobalChunk removed : std::get<1>(pair.second))
|
||||||
Pos::GlobalChunk pos = removed << 2;
|
std::get<0>(pair.second).erase(removed);
|
||||||
for(int z = 0; z < 4; z++)
|
|
||||||
for(int y = 0; y < 4; y++)
|
|
||||||
for(int x = 0; x < 4; x++) {
|
|
||||||
std::get<0>(pair.second).erase(pos+Pos::GlobalChunk(x, y, z));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RS->onChunksChange(pair.first, std::get<0>(pair.second), std::get<1>(pair.second));
|
RS->onChunksChange(pair.first, std::get<0>(pair.second), std::get<1>(pair.second));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!onContentDefinesAdd.empty()) {
|
|
||||||
RS->onContentDefinesAdd(std::move(onContentDefinesAdd));
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!onContentDefinesLost.empty()) {
|
|
||||||
RS->onContentDefinesLost(std::move(onContentDefinesLost));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -442,10 +301,7 @@ void ServerSession::atFreeDrawTime(GlobalTime gTime, float dTime) {
|
|||||||
|
|
||||||
glm::quat quat =
|
glm::quat quat =
|
||||||
glm::angleAxis(PYR.x-deltaTime*PYR_Offset.x, glm::vec3(1.f, 0.f, 0.f))
|
glm::angleAxis(PYR.x-deltaTime*PYR_Offset.x, glm::vec3(1.f, 0.f, 0.f))
|
||||||
*
|
* glm::angleAxis(PYR.y-deltaTime*PYR_Offset.y, glm::vec3(0.f, -1.f, 0.f));
|
||||||
glm::angleAxis(PYR.y-deltaTime*PYR_Offset.y, glm::vec3(0.f, -1.f, 0.f));
|
|
||||||
|
|
||||||
quat = glm::normalize(quat);
|
|
||||||
|
|
||||||
if(RS)
|
if(RS)
|
||||||
RS->setCameraPos(0, Pos, quat);
|
RS->setCameraPos(0, Pos, quat);
|
||||||
@@ -457,7 +313,7 @@ void ServerSession::atFreeDrawTime(GlobalTime gTime, float dTime) {
|
|||||||
LastSendPYR_POS = gTime;
|
LastSendPYR_POS = gTime;
|
||||||
Net::Packet packet;
|
Net::Packet packet;
|
||||||
ToServer::PacketQuat q;
|
ToServer::PacketQuat q;
|
||||||
q.fromQuat(glm::inverse(quat));
|
q.fromQuat(quat);
|
||||||
|
|
||||||
packet << (uint8_t) ToServer::L1::System
|
packet << (uint8_t) ToServer::L1::System
|
||||||
<< (uint8_t) ToServer::L2System::Test_CAM_PYR_POS
|
<< (uint8_t) ToServer::L2System::Test_CAM_PYR_POS
|
||||||
@@ -548,48 +404,32 @@ coro<> ServerSession::rP_Resource(Net::AsyncSocket &sock) {
|
|||||||
uint8_t second = co_await sock.read<uint8_t>();
|
uint8_t second = co_await sock.read<uint8_t>();
|
||||||
|
|
||||||
switch((ToClient::L2Resource) second) {
|
switch((ToClient::L2Resource) second) {
|
||||||
case ToClient::L2Resource::Bind:
|
case ToClient::L2Resource::Texture:
|
||||||
{
|
|
||||||
uint32_t count = co_await sock.read<uint32_t>();
|
co_return;
|
||||||
for(size_t iter = 0; iter < count; iter++) {
|
case ToClient::L2Resource::FreeTexture:
|
||||||
uint8_t type = co_await sock.read<uint8_t>();
|
|
||||||
uint32_t id = co_await sock.read<uint32_t>();
|
co_return;
|
||||||
Hash_t hash;
|
case ToClient::L2Resource::Sound:
|
||||||
co_await sock.read((std::byte*) hash.data(), hash.size());
|
|
||||||
}
|
co_return;
|
||||||
}
|
case ToClient::L2Resource::FreeSound:
|
||||||
case ToClient::L2Resource::Lost:
|
|
||||||
{
|
co_return;
|
||||||
uint32_t count = co_await sock.read<uint32_t>();
|
case ToClient::L2Resource::Model:
|
||||||
for(size_t iter = 0; iter < count; iter++) {
|
|
||||||
uint8_t type = co_await sock.read<uint8_t>();
|
co_return;
|
||||||
uint32_t id = co_await sock.read<uint32_t>();
|
case ToClient::L2Resource::FreeModel:
|
||||||
}
|
|
||||||
}
|
co_return;
|
||||||
case ToClient::L2Resource::InitResSend:
|
case ToClient::L2Resource::InitResSend:
|
||||||
{
|
|
||||||
uint32_t size = co_await sock.read<uint32_t>();
|
|
||||||
Hash_t hash;
|
|
||||||
co_await sock.read((std::byte*) hash.data(), hash.size());
|
|
||||||
|
|
||||||
uint32_t chunkSize = co_await sock.read<uint32_t>();
|
|
||||||
assert(chunkSize < std::pow(2, 26));
|
|
||||||
|
|
||||||
std::u8string data(size, '\0');
|
|
||||||
|
|
||||||
co_await sock.read((std::byte*) data.data(), data.size());
|
|
||||||
|
|
||||||
PP_Resource_InitResSend *packet = new PP_Resource_InitResSend(
|
|
||||||
hash,
|
|
||||||
std::make_shared<std::u8string>(std::move(data))
|
|
||||||
);
|
|
||||||
|
|
||||||
while(!NetInputPackets.push(packet));
|
|
||||||
|
|
||||||
co_return;
|
co_return;
|
||||||
}
|
|
||||||
case ToClient::L2Resource::ChunkSend:
|
case ToClient::L2Resource::ChunkSend:
|
||||||
|
|
||||||
|
co_return;
|
||||||
|
case ToClient::L2Resource::SendCanceled:
|
||||||
|
|
||||||
co_return;
|
co_return;
|
||||||
default:
|
default:
|
||||||
protocolError();
|
protocolError();
|
||||||
@@ -601,58 +441,31 @@ coro<> ServerSession::rP_Definition(Net::AsyncSocket &sock) {
|
|||||||
|
|
||||||
switch((ToClient::L2Definition) second) {
|
switch((ToClient::L2Definition) second) {
|
||||||
case ToClient::L2Definition::World: {
|
case ToClient::L2Definition::World: {
|
||||||
DefWorldId cdId = co_await sock.read<DefWorldId>();
|
DefWorldId_c cdId = co_await sock.read<DefWorldId_c>();
|
||||||
|
|
||||||
co_return;
|
co_return;
|
||||||
}
|
}
|
||||||
case ToClient::L2Definition::FreeWorld: {
|
case ToClient::L2Definition::FreeWorld: {
|
||||||
DefWorldId cdId = co_await sock.read<DefWorldId>();
|
DefWorldId_c cdId = co_await sock.read<DefWorldId_c>();
|
||||||
|
|
||||||
co_return;
|
co_return;
|
||||||
}
|
}
|
||||||
case ToClient::L2Definition::Voxel: {
|
case ToClient::L2Definition::Voxel: {
|
||||||
DefVoxelId cdId = co_await sock.read<DefVoxelId>();
|
DefVoxelId_c cdId = co_await sock.read<DefVoxelId_c>();
|
||||||
|
|
||||||
co_return;
|
co_return;
|
||||||
}
|
}
|
||||||
case ToClient::L2Definition::FreeVoxel: {
|
case ToClient::L2Definition::FreeVoxel: {
|
||||||
DefVoxelId cdId = co_await sock.read<DefVoxelId>();
|
DefVoxelId_c cdId = co_await sock.read<DefVoxelId_c>();
|
||||||
|
|
||||||
co_return;
|
co_return;
|
||||||
}
|
}
|
||||||
case ToClient::L2Definition::Node:
|
case ToClient::L2Definition::Node:
|
||||||
{
|
|
||||||
// DefNodeId id;
|
|
||||||
// DefNode_t def;
|
|
||||||
// id = co_await sock.read<DefNodeId>();
|
|
||||||
// def.DrawType = (DefNode_t::EnumDrawType) co_await sock.read<uint8_t>();
|
|
||||||
// for(int iter = 0; iter < 6; iter++) {
|
|
||||||
// auto &pl = def.Texs[iter].Pipeline;
|
|
||||||
// pl.resize(co_await sock.read<uint16_t>());
|
|
||||||
// co_await sock.read((std::byte*) pl.data(), pl.size());
|
|
||||||
// }
|
|
||||||
|
|
||||||
// PP_Definition_Node *packet = new PP_Definition_Node(
|
|
||||||
// id,
|
|
||||||
// def
|
|
||||||
// );
|
|
||||||
|
|
||||||
// while(!NetInputPackets.push(packet));
|
|
||||||
|
|
||||||
// co_return;
|
|
||||||
}
|
|
||||||
case ToClient::L2Definition::FreeNode:
|
|
||||||
{
|
|
||||||
DefNodeId id = co_await sock.read<DefNodeId>();
|
|
||||||
|
|
||||||
PP_Definition_FreeNode *packet = new PP_Definition_FreeNode(
|
|
||||||
id
|
|
||||||
);
|
|
||||||
|
|
||||||
while(!NetInputPackets.push(packet));
|
|
||||||
|
|
||||||
co_return;
|
co_return;
|
||||||
}
|
case ToClient::L2Definition::FreeNode:
|
||||||
|
|
||||||
|
co_return;
|
||||||
case ToClient::L2Definition::Portal:
|
case ToClient::L2Definition::Portal:
|
||||||
|
|
||||||
co_return;
|
co_return;
|
||||||
@@ -694,19 +507,34 @@ coro<> ServerSession::rP_Content(Net::AsyncSocket &sock) {
|
|||||||
co_return;
|
co_return;
|
||||||
case ToClient::L2Content::ChunkVoxels:
|
case ToClient::L2Content::ChunkVoxels:
|
||||||
{
|
{
|
||||||
WorldId_t wcId = co_await sock.read<WorldId_t>();
|
WorldId_c wcId = co_await sock.read<WorldId_c>();
|
||||||
Pos::GlobalChunk pos;
|
Pos::GlobalChunk::Key posKey = co_await sock.read<Pos::GlobalChunk::Key>();
|
||||||
pos.unpack(co_await sock.read<Pos::GlobalChunk::Pack>());
|
Pos::GlobalChunk pos = *(Pos::GlobalChunk*) &posKey;
|
||||||
|
|
||||||
uint32_t compressedSize = co_await sock.read<uint32_t>();
|
std::vector<VoxelCube> cubes(co_await sock.read<uint16_t>());
|
||||||
assert(compressedSize <= std::pow(2, 24));
|
uint16_t debugCubesCount = cubes.size();
|
||||||
std::u8string compressed(compressedSize, '\0');
|
if(debugCubesCount > 1) {
|
||||||
co_await sock.read((std::byte*) compressed.data(), compressedSize);
|
int g = 0;
|
||||||
|
g++;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(size_t iter = 0; iter < cubes.size(); iter++) {
|
||||||
|
VoxelCube &cube = cubes[iter];
|
||||||
|
cube.VoxelId = co_await sock.read<uint16_t>();
|
||||||
|
cube.Left.X = co_await sock.read<uint8_t>();
|
||||||
|
cube.Left.Y = co_await sock.read<uint8_t>();
|
||||||
|
cube.Left.Z = co_await sock.read<uint8_t>();
|
||||||
|
cube.Size.X = co_await sock.read<uint8_t>();
|
||||||
|
cube.Size.Y = co_await sock.read<uint8_t>();
|
||||||
|
cube.Size.Z = co_await sock.read<uint8_t>();
|
||||||
|
}
|
||||||
|
|
||||||
PP_Content_ChunkVoxels *packet = new PP_Content_ChunkVoxels(
|
PP_Content_ChunkVoxels *packet = new PP_Content_ChunkVoxels(
|
||||||
|
ToClient::L1::Content,
|
||||||
|
(uint8_t) ToClient::L2Content::ChunkVoxels,
|
||||||
wcId,
|
wcId,
|
||||||
pos,
|
pos,
|
||||||
unCompressVoxels(compressed) // TODO: вынести в отдельный поток
|
std::move(cubes)
|
||||||
);
|
);
|
||||||
|
|
||||||
while(!NetInputPackets.push(packet));
|
while(!NetInputPackets.push(packet));
|
||||||
@@ -715,36 +543,19 @@ coro<> ServerSession::rP_Content(Net::AsyncSocket &sock) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case ToClient::L2Content::ChunkNodes:
|
case ToClient::L2Content::ChunkNodes:
|
||||||
{
|
|
||||||
WorldId_t wcId = co_await sock.read<WorldId_t>();
|
|
||||||
Pos::GlobalChunk pos;
|
|
||||||
pos.unpack(co_await sock.read<Pos::GlobalChunk::Pack>());
|
|
||||||
|
|
||||||
uint32_t compressedSize = co_await sock.read<uint32_t>();
|
|
||||||
assert(compressedSize <= std::pow(2, 24));
|
|
||||||
std::u8string compressed(compressedSize, '\0');
|
|
||||||
co_await sock.read((std::byte*) compressed.data(), compressedSize);
|
|
||||||
|
|
||||||
PP_Content_ChunkNodes *packet = new PP_Content_ChunkNodes(
|
|
||||||
wcId,
|
|
||||||
pos
|
|
||||||
);
|
|
||||||
|
|
||||||
unCompressNodes(compressed, (Node*) packet->Nodes.data()); // TODO: вынести в отдельный поток
|
|
||||||
|
|
||||||
while(!NetInputPackets.push(packet));
|
|
||||||
|
|
||||||
co_return;
|
co_return;
|
||||||
}
|
|
||||||
case ToClient::L2Content::ChunkLightPrism:
|
case ToClient::L2Content::ChunkLightPrism:
|
||||||
|
|
||||||
co_return;
|
co_return;
|
||||||
case ToClient::L2Content::RemoveRegion: {
|
case ToClient::L2Content::RemoveChunk: {
|
||||||
WorldId_t wcId = co_await sock.read<WorldId_t>();
|
WorldId_c wcId = co_await sock.read<uint8_t>();
|
||||||
Pos::GlobalRegion pos;
|
Pos::GlobalChunk::Key posKey = co_await sock.read<Pos::GlobalChunk::Key>();
|
||||||
pos.unpack(co_await sock.read<Pos::GlobalRegion::Pack>());
|
Pos::GlobalChunk pos = *(Pos::GlobalChunk*) &posKey;
|
||||||
|
|
||||||
PP_Content_RegionRemove *packet = new PP_Content_RegionRemove(
|
PP_Content_ChunkRemove *packet = new PP_Content_ChunkRemove(
|
||||||
|
ToClient::L1::Content,
|
||||||
|
(uint8_t) ToClient::L2Content::RemoveChunk,
|
||||||
wcId,
|
wcId,
|
||||||
pos
|
pos
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -8,10 +8,8 @@
|
|||||||
#include "Common/Packets.hpp"
|
#include "Common/Packets.hpp"
|
||||||
#include <TOSLib.hpp>
|
#include <TOSLib.hpp>
|
||||||
#include <boost/asio/io_context.hpp>
|
#include <boost/asio/io_context.hpp>
|
||||||
#include <filesystem>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <boost/lockfree/spsc_queue.hpp>
|
#include <boost/lockfree/spsc_queue.hpp>
|
||||||
#include <Client/ResourceCache.hpp>
|
|
||||||
|
|
||||||
|
|
||||||
namespace LV::Client {
|
namespace LV::Client {
|
||||||
@@ -26,13 +24,10 @@ struct ParsedPacket {
|
|||||||
virtual ~ParsedPacket();
|
virtual ~ParsedPacket();
|
||||||
};
|
};
|
||||||
|
|
||||||
class ServerSession : public AsyncObject, public IServerSession, public ISurfaceEventListener {
|
class ServerSession : public IServerSession, public ISurfaceEventListener {
|
||||||
|
asio::io_context &IOC;
|
||||||
std::unique_ptr<Net::AsyncSocket> Socket;
|
std::unique_ptr<Net::AsyncSocket> Socket;
|
||||||
IRenderSession *RS = nullptr;
|
IRenderSession *RS = nullptr;
|
||||||
|
|
||||||
// Обработчик кеша ресурсов сервера
|
|
||||||
CacheHandler::Ptr CHDB;
|
|
||||||
|
|
||||||
DestroyLock UseLock;
|
DestroyLock UseLock;
|
||||||
bool IsConnected = true, IsGoingShutdown = false;
|
bool IsConnected = true, IsGoingShutdown = false;
|
||||||
|
|
||||||
@@ -62,26 +57,10 @@ class ServerSession : public AsyncObject, public IServerSession, public ISurface
|
|||||||
public:
|
public:
|
||||||
// Нужен сокет, на котором только что был согласован игровой протокол (asyncInitGameProtocol)
|
// Нужен сокет, на котором только что был согласован игровой протокол (asyncInitGameProtocol)
|
||||||
ServerSession(asio::io_context &ioc, std::unique_ptr<Net::AsyncSocket> &&socket, IRenderSession *rs = nullptr)
|
ServerSession(asio::io_context &ioc, std::unique_ptr<Net::AsyncSocket> &&socket, IRenderSession *rs = nullptr)
|
||||||
: AsyncObject(ioc), Socket(std::move(socket)), RS(rs), NetInputPackets(1024)
|
: IOC(ioc), Socket(std::move(socket)), RS(rs), NetInputPackets(1024)
|
||||||
{
|
{
|
||||||
assert(Socket.get());
|
assert(Socket.get());
|
||||||
|
asio::co_spawn(IOC, run(), asio::detached);
|
||||||
try {
|
|
||||||
fs::create_directories("Cache");
|
|
||||||
CHDB = CacheHandlerBasic::Create(ioc, "Cache");
|
|
||||||
|
|
||||||
// Отправка информации о загруженном кеше
|
|
||||||
// TODO: добавить оптимизацию для подключения клиента к внутреннему серверу
|
|
||||||
auto [data, count] = CHDB->getAll();
|
|
||||||
Net::Packet packet;
|
|
||||||
packet << uint32_t(count);
|
|
||||||
packet.write((const std::byte*) data.data(), data.size());
|
|
||||||
Socket->pushPacket(std::move(packet));
|
|
||||||
} catch(const std::exception &exc) {
|
|
||||||
MAKE_ERROR("Ошибка инициализации обработчика кеша ресурсов сервера:\n" << exc.what());
|
|
||||||
}
|
|
||||||
|
|
||||||
co_spawn(run());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual ~ServerSession();
|
virtual ~ServerSession();
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
/*
|
|
||||||
Воксели рендерятся точками, которые распаковываются в квадратные плоскости
|
|
||||||
|
|
||||||
В чанке по оси 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 : 9, FY : 9, FZ : 9, // Позиция -15 -120 ~ 240 360 +15 / 16
|
|
||||||
N1 : 4, // Не занято
|
|
||||||
LS : 1, // Масштаб карты освещения (1м/16 или 1м)
|
|
||||||
Tex : 18, // Текстура
|
|
||||||
N2 : 14, // Не занято
|
|
||||||
TU : 16, TV : 16; // UV на текстуре
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,331 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "Vulkan.hpp"
|
|
||||||
#include <bitset>
|
|
||||||
#include <vulkan/vulkan_core.h>
|
|
||||||
|
|
||||||
|
|
||||||
namespace LV::Client::VK {
|
|
||||||
|
|
||||||
/*
|
|
||||||
Память на устройстве выделяется пулами
|
|
||||||
Для массивов вершин память выделяется блоками по PerBlock вершин в каждом
|
|
||||||
Размер пулла sizeof(Vertex)*PerBlock*PerPool
|
|
||||||
|
|
||||||
Получаемые вершины сначала пишутся в общий буфер, потом передаются на устройство
|
|
||||||
*/
|
|
||||||
// Нужна реализация индексного буфера
|
|
||||||
template<typename Vertex, uint16_t PerBlock = 1 << 10, uint16_t PerPool = 1 << 12>
|
|
||||||
class VertexPool {
|
|
||||||
static constexpr size_t HC_Buffer_Size = size_t(PerBlock)*size_t(PerPool);
|
|
||||||
|
|
||||||
Vulkan *Inst;
|
|
||||||
|
|
||||||
// Память, доступная для обмена с устройством
|
|
||||||
Buffer HostCoherent;
|
|
||||||
Vertex *HCPtr = nullptr;
|
|
||||||
VkFence Fence = nullptr;
|
|
||||||
size_t WritePos = 0;
|
|
||||||
|
|
||||||
struct Pool {
|
|
||||||
// Память на устройстве
|
|
||||||
Buffer DeviceBuff;
|
|
||||||
// Свободные блоки
|
|
||||||
std::bitset<PerPool> Allocation;
|
|
||||||
|
|
||||||
Pool(Vulkan* inst)
|
|
||||||
: DeviceBuff(inst,
|
|
||||||
sizeof(Vertex)*size_t(PerBlock)*size_t(PerPool)+4 /* Для vkCmdFillBuffer */,
|
|
||||||
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;
|
|
||||||
size_t Pos = -1; // Если данные уже записаны, то будет указана позиция в буфере общения
|
|
||||||
uint8_t PoolId; // Куда потом направить
|
|
||||||
uint16_t BlockId; // И в какой блок
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
Перед следующим обновлением буфер общения заполняется с начала и до конца
|
|
||||||
Если место закончится, будет дослано в следующем обновлении
|
|
||||||
*/
|
|
||||||
std::queue<Task> TasksWait, TasksPostponed;
|
|
||||||
|
|
||||||
|
|
||||||
private:
|
|
||||||
void pushData(std::vector<Vertex>&& data, uint8_t poolId, uint16_t blockId) {
|
|
||||||
if(HC_Buffer_Size-WritePos >= data.size()) {
|
|
||||||
// Пишем в общий буфер, TasksWait
|
|
||||||
Vertex *ptr = HCPtr+WritePos;
|
|
||||||
std::copy(data.begin(), data.end(), ptr);
|
|
||||||
size_t count = data.size();
|
|
||||||
TasksWait.push({std::move(data), WritePos, poolId, blockId});
|
|
||||||
WritePos += count;
|
|
||||||
} else {
|
|
||||||
// Отложим запись на следующий такт
|
|
||||||
TasksPostponed.push(Task(std::move(data), -1, poolId, blockId));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
VertexPool(Vulkan* inst)
|
|
||||||
: Inst(inst),
|
|
||||||
HostCoherent(inst,
|
|
||||||
sizeof(Vertex)*HC_Buffer_Size+4 /* Для vkCmdFillBuffer */,
|
|
||||||
VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
|
|
||||||
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)
|
|
||||||
{
|
|
||||||
Pools.reserve(16);
|
|
||||||
HCPtr = (Vertex*) HostCoherent.mapMemory();
|
|
||||||
|
|
||||||
const VkFenceCreateInfo info = {
|
|
||||||
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
|
|
||||||
.pNext = nullptr,
|
|
||||||
.flags = 0
|
|
||||||
};
|
|
||||||
|
|
||||||
vkAssert(!vkCreateFence(inst->Graphics.Device, &info, nullptr, &Fence));
|
|
||||||
}
|
|
||||||
|
|
||||||
~VertexPool() {
|
|
||||||
if(HCPtr)
|
|
||||||
HostCoherent.unMapMemory();
|
|
||||||
|
|
||||||
if(Fence) {
|
|
||||||
vkDestroyFence(Inst->Graphics.Device, Fence, nullptr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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 update(VkCommandPool commandPool) {
|
|
||||||
if(TasksWait.empty())
|
|
||||||
return;
|
|
||||||
|
|
||||||
assert(WritePos);
|
|
||||||
|
|
||||||
VkCommandBufferAllocateInfo allocInfo {
|
|
||||||
VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
|
|
||||||
nullptr,
|
|
||||||
commandPool,
|
|
||||||
VK_COMMAND_BUFFER_LEVEL_PRIMARY,
|
|
||||||
1
|
|
||||||
};
|
|
||||||
|
|
||||||
VkCommandBuffer commandBuffer;
|
|
||||||
vkAllocateCommandBuffers(Inst->Graphics.Device, &allocInfo, &commandBuffer);
|
|
||||||
|
|
||||||
VkCommandBufferBeginInfo beginInfo {
|
|
||||||
VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
|
|
||||||
nullptr,
|
|
||||||
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
|
|
||||||
nullptr
|
|
||||||
};
|
|
||||||
|
|
||||||
vkBeginCommandBuffer(commandBuffer, &beginInfo);
|
|
||||||
|
|
||||||
VkBufferMemoryBarrier barrier = {
|
|
||||||
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,
|
|
||||||
HostCoherent.getBuffer(),
|
|
||||||
0,
|
|
||||||
WritePos*sizeof(Vertex)
|
|
||||||
};
|
|
||||||
|
|
||||||
vkCmdPipelineBarrier(
|
|
||||||
commandBuffer,
|
|
||||||
VK_PIPELINE_STAGE_HOST_BIT,
|
|
||||||
VK_PIPELINE_STAGE_TRANSFER_BIT,
|
|
||||||
0,
|
|
||||||
0, nullptr,
|
|
||||||
1, &barrier,
|
|
||||||
0, nullptr
|
|
||||||
);
|
|
||||||
|
|
||||||
while(!TasksWait.empty()) {
|
|
||||||
Task& task = TasksWait.front();
|
|
||||||
|
|
||||||
VkBufferCopy copyRegion {
|
|
||||||
task.Pos*sizeof(Vertex),
|
|
||||||
task.BlockId*sizeof(Vertex)*size_t(PerBlock),
|
|
||||||
task.Data.size()*sizeof(Vertex)
|
|
||||||
};
|
|
||||||
|
|
||||||
assert(copyRegion.dstOffset+copyRegion.size < sizeof(Vertex)*PerBlock*PerPool);
|
|
||||||
|
|
||||||
vkCmdCopyBuffer(commandBuffer, HostCoherent.getBuffer(), Pools[task.PoolId].DeviceBuff.getBuffer(),
|
|
||||||
1, ©Region);
|
|
||||||
|
|
||||||
TasksWait.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
vkEndCommandBuffer(commandBuffer);
|
|
||||||
|
|
||||||
VkSubmitInfo submitInfo {
|
|
||||||
VK_STRUCTURE_TYPE_SUBMIT_INFO,
|
|
||||||
nullptr,
|
|
||||||
0, nullptr,
|
|
||||||
nullptr,
|
|
||||||
1,
|
|
||||||
&commandBuffer,
|
|
||||||
0,
|
|
||||||
nullptr
|
|
||||||
};
|
|
||||||
{
|
|
||||||
auto lockQueue = Inst->Graphics.DeviceQueueGraphic.lock();
|
|
||||||
vkAssert(!vkQueueSubmit(*lockQueue, 1, &submitInfo, Fence));
|
|
||||||
}
|
|
||||||
vkAssert(!vkWaitForFences(Inst->Graphics.Device, 1, &Fence, VK_TRUE, UINT64_MAX));
|
|
||||||
vkAssert(!vkResetFences(Inst->Graphics.Device, 1, &Fence));
|
|
||||||
vkFreeCommandBuffers(Inst->Graphics.Device, commandPool, 1, &commandBuffer);
|
|
||||||
|
|
||||||
std::queue<Task> postponed = std::move(TasksPostponed);
|
|
||||||
WritePos = 0;
|
|
||||||
|
|
||||||
while(!postponed.empty()) {
|
|
||||||
Task& task = postponed.front();
|
|
||||||
pushData(std::move(task.Data), task.PoolId, task.BlockId);
|
|
||||||
postponed.pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -7,7 +7,6 @@
|
|||||||
#include "Client/ServerSession.hpp"
|
#include "Client/ServerSession.hpp"
|
||||||
#include "Common/Async.hpp"
|
#include "Common/Async.hpp"
|
||||||
#include "Common/Net.hpp"
|
#include "Common/Net.hpp"
|
||||||
#include "TOSLib.hpp"
|
|
||||||
#include "assets.hpp"
|
#include "assets.hpp"
|
||||||
#include "imgui.h"
|
#include "imgui.h"
|
||||||
#include <GLFW/glfw3.h>
|
#include <GLFW/glfw3.h>
|
||||||
@@ -33,27 +32,12 @@ extern void LoadSymbolsVulkan(TOS::DynamicLibrary &library);
|
|||||||
|
|
||||||
namespace LV::Client::VK {
|
namespace LV::Client::VK {
|
||||||
|
|
||||||
static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(
|
|
||||||
VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
|
|
||||||
VkDebugUtilsMessageTypeFlagsEXT messageType,
|
|
||||||
const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
|
|
||||||
void* pUserData)
|
|
||||||
{
|
|
||||||
std::cerr << "Validation Layer: " << pCallbackData->pMessage << std::endl;
|
|
||||||
|
|
||||||
if("Copying old device 0 into new device 0" == std::string(pCallbackData->pMessage)) {
|
|
||||||
return VK_FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
return VK_FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ServerObj {
|
struct ServerObj {
|
||||||
Server::GameServer GS;
|
Server::GameServer GS;
|
||||||
Net::SocketServer LS;
|
Net::SocketServer LS;
|
||||||
|
|
||||||
ServerObj(asio::io_context &ioc)
|
ServerObj(asio::io_context &ioc)
|
||||||
: GS(ioc, "worlds/test/"), LS(ioc, [&](tcp::socket sock) -> coro<> { co_await GS.pushSocketConnect(std::move(sock)); }, 7890)
|
: GS(ioc, ""), LS(ioc, [&](tcp::socket sock) -> coro<> { co_await GS.pushSocketConnect(std::move(sock)); }, 7890)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -89,7 +73,7 @@ ByteBuffer loadPNG(std::istream &&read, int &width, int &height, bool &hasAlpha,
|
|||||||
}
|
}
|
||||||
|
|
||||||
Vulkan::Vulkan(asio::io_context &ioc)
|
Vulkan::Vulkan(asio::io_context &ioc)
|
||||||
: AsyncObject(ioc), GuardLock(ioc.get_executor())
|
: IOC(ioc), GuardLock(ioc.get_executor())
|
||||||
{
|
{
|
||||||
Screen.Width = 1920/2;
|
Screen.Width = 1920/2;
|
||||||
Screen.Height = 1080/2;
|
Screen.Height = 1080/2;
|
||||||
@@ -109,16 +93,6 @@ Vulkan::Vulkan(asio::io_context &ioc)
|
|||||||
LOG.error() << "Vulkan::run: " << exc.what();
|
LOG.error() << "Vulkan::run: " << exc.what();
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
if(Graphics.Window)
|
|
||||||
glfwSetWindowAttrib(Graphics.Window, GLFW_VISIBLE, false);
|
|
||||||
} catch(...) {}
|
|
||||||
|
|
||||||
try {
|
|
||||||
if(Game.RSession)
|
|
||||||
Game.RSession->pushStage(EnumRenderStage::Shutdown);
|
|
||||||
} catch(...) {}
|
|
||||||
|
|
||||||
try { Game.RSession = nullptr; } catch(const std::exception &exc) {
|
try { Game.RSession = nullptr; } catch(const std::exception &exc) {
|
||||||
LOG.error() << "Game.RSession = nullptr: " << exc.what();
|
LOG.error() << "Game.RSession = nullptr: " << exc.what();
|
||||||
}
|
}
|
||||||
@@ -168,14 +142,10 @@ void Vulkan::run()
|
|||||||
double prevTime = glfwGetTime();
|
double prevTime = glfwGetTime();
|
||||||
while(!NeedShutdown)
|
while(!NeedShutdown)
|
||||||
{
|
{
|
||||||
|
|
||||||
float dTime = glfwGetTime()-prevTime;
|
float dTime = glfwGetTime()-prevTime;
|
||||||
prevTime += dTime;
|
prevTime += dTime;
|
||||||
|
|
||||||
Screen.State = DrawState::Begin;
|
Screen.State = DrawState::Begin;
|
||||||
if(Game.RSession)
|
|
||||||
Game.RSession->pushStage(EnumRenderStage::ComposingCommandBuffer);
|
|
||||||
|
|
||||||
{
|
{
|
||||||
std::lock_guard lock(Screen.BeforeDrawMtx);
|
std::lock_guard lock(Screen.BeforeDrawMtx);
|
||||||
while(!Screen.BeforeDraw.empty())
|
while(!Screen.BeforeDraw.empty())
|
||||||
@@ -241,7 +211,6 @@ void Vulkan::run()
|
|||||||
} else if (err == VK_SUBOPTIMAL_KHR)
|
} else if (err == VK_SUBOPTIMAL_KHR)
|
||||||
{
|
{
|
||||||
LOGGER.debug() << "VK_SUBOPTIMAL_KHR Pre";
|
LOGGER.debug() << "VK_SUBOPTIMAL_KHR Pre";
|
||||||
continue;
|
|
||||||
} else if(err == VK_SUCCESS) {
|
} else if(err == VK_SUCCESS) {
|
||||||
|
|
||||||
Screen.State = DrawState::Drawing;
|
Screen.State = DrawState::Drawing;
|
||||||
@@ -251,7 +220,7 @@ void Vulkan::run()
|
|||||||
{
|
{
|
||||||
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
|
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
|
||||||
.pNext = nullptr,
|
.pNext = nullptr,
|
||||||
.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
|
.flags = 0,
|
||||||
.pInheritanceInfo = nullptr
|
.pInheritanceInfo = nullptr
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -476,15 +445,10 @@ void Vulkan::run()
|
|||||||
// vkCmdBeginRenderPass(Graphics.CommandBufferRender, &rp_begin, VK_SUBPASS_CONTENTS_INLINE);
|
// vkCmdBeginRenderPass(Graphics.CommandBufferRender, &rp_begin, VK_SUBPASS_CONTENTS_INLINE);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
if(Game.RSession)
|
|
||||||
Game.RSession->pushStage(EnumRenderStage::Render);
|
|
||||||
|
|
||||||
#ifdef HAS_IMGUI
|
#ifdef HAS_IMGUI
|
||||||
{
|
ImGui_ImplVulkan_NewFrame();
|
||||||
auto lockQueue = Graphics.DeviceQueueGraphic.lock();
|
|
||||||
ImGui_ImplVulkan_NewFrame();
|
|
||||||
lockQueue.unlock();
|
|
||||||
}
|
|
||||||
ImGui_ImplGlfw_NewFrame();
|
ImGui_ImplGlfw_NewFrame();
|
||||||
ImGui::NewFrame();
|
ImGui::NewFrame();
|
||||||
|
|
||||||
@@ -538,10 +502,7 @@ void Vulkan::run()
|
|||||||
};
|
};
|
||||||
|
|
||||||
//Рисуем, когда получим картинку
|
//Рисуем, когда получим картинку
|
||||||
{
|
vkAssert(!vkQueueSubmit(Graphics.DeviceQueueGraphic, 1, &submit_info, nullFence));
|
||||||
auto lockQueue = Graphics.DeviceQueueGraphic.lock();
|
|
||||||
vkAssert(!vkQueueSubmit(*lockQueue, 1, &submit_info, nullFence));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -557,11 +518,7 @@ void Vulkan::run()
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Завершаем картинку
|
// Завершаем картинку
|
||||||
{
|
err = vkQueuePresentKHR(Graphics.DeviceQueueGraphic, &present);
|
||||||
auto lockQueue = Graphics.DeviceQueueGraphic.lock();
|
|
||||||
err = vkQueuePresentKHR(*lockQueue, &present);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (err == VK_ERROR_OUT_OF_DATE_KHR)
|
if (err == VK_ERROR_OUT_OF_DATE_KHR)
|
||||||
{
|
{
|
||||||
freeSwapchains();
|
freeSwapchains();
|
||||||
@@ -575,20 +532,12 @@ void Vulkan::run()
|
|||||||
vkAssert(err == VK_TIMEOUT);
|
vkAssert(err == VK_TIMEOUT);
|
||||||
|
|
||||||
if(Game.Session) {
|
if(Game.Session) {
|
||||||
if(Game.RSession)
|
|
||||||
Game.RSession->pushStage(EnumRenderStage::WorldUpdate);
|
|
||||||
|
|
||||||
Game.Session->atFreeDrawTime(gTime, dTime);
|
Game.Session->atFreeDrawTime(gTime, dTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
// vkAssert(!vkQueueWaitIdle(Graphics.DeviceQueueGraphic));
|
vkAssert(!vkQueueWaitIdle(Graphics.DeviceQueueGraphic));
|
||||||
|
|
||||||
{
|
vkDeviceWaitIdle(Graphics.Device);
|
||||||
// Эту хрень надо убрать
|
|
||||||
auto lockQueue = Graphics.DeviceQueueGraphic.lock();
|
|
||||||
vkDeviceWaitIdle(Graphics.Device);
|
|
||||||
lockQueue.unlock();
|
|
||||||
}
|
|
||||||
Screen.State = DrawState::End;
|
Screen.State = DrawState::End;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1230,11 +1179,7 @@ void Vulkan::checkLibrary()
|
|||||||
uint32_t count = -1;
|
uint32_t count = -1;
|
||||||
VkResult res;
|
VkResult res;
|
||||||
|
|
||||||
VkResult errc = vkEnumeratePhysicalDevices(localInstance.getInstance(), &count, nullptr);
|
vkAssert(!vkEnumeratePhysicalDevices(localInstance.getInstance(), &count, nullptr));
|
||||||
if(errc != VK_SUCCESS && errc != VK_INCOMPLETE) {
|
|
||||||
error << "vkEnumeratePhysicalDevices не смог обработать запрос (ошибка драйвера?)\n";
|
|
||||||
goto onError;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!count)
|
if(!count)
|
||||||
{
|
{
|
||||||
@@ -1513,7 +1458,6 @@ void Vulkan::initNextSettings()
|
|||||||
freeSwapchains();
|
freeSwapchains();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool hasVK_EXT_debug_utils = false;
|
|
||||||
if(!Graphics.Instance)
|
if(!Graphics.Instance)
|
||||||
{
|
{
|
||||||
std::vector<std::string_view> knownDebugLayers =
|
std::vector<std::string_view> knownDebugLayers =
|
||||||
@@ -1543,38 +1487,7 @@ void Vulkan::initNextSettings()
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<vkInstanceExtension> enableExtension;
|
Graphics.Instance.emplace(this, enableDebugLayers);
|
||||||
if(SettingsNext.Debug)
|
|
||||||
for(const vkInstanceExtension &ext : Graphics.InstanceExtensions) {
|
|
||||||
if(ext.ExtensionName == "VK_EXT_debug_utils") {
|
|
||||||
enableExtension.push_back(ext);
|
|
||||||
hasVK_EXT_debug_utils = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Graphics.Instance.emplace(this, enableDebugLayers, enableExtension);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(hasVK_EXT_debug_utils) {
|
|
||||||
VkDebugUtilsMessengerCreateInfoEXT createInfo = {
|
|
||||||
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT,
|
|
||||||
.pNext = nullptr,
|
|
||||||
.flags = 0,
|
|
||||||
.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT |
|
|
||||||
VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT |
|
|
||||||
VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT,
|
|
||||||
.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT |
|
|
||||||
VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT |
|
|
||||||
VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT,
|
|
||||||
.pfnUserCallback = &debugCallback,
|
|
||||||
.pUserData = nullptr
|
|
||||||
};
|
|
||||||
|
|
||||||
VkDebugUtilsMessengerEXT debugMessenger;
|
|
||||||
PFN_vkCreateDebugUtilsMessengerEXT myvkCreateDebugUtilsMessengerEXT = reinterpret_cast<PFN_vkCreateDebugUtilsMessengerEXT>(vkGetInstanceProcAddr(Graphics.Instance->getInstance(), "vkCreateDebugUtilsMessengerEXT"));
|
|
||||||
myvkCreateDebugUtilsMessengerEXT(Graphics.Instance->getInstance(), &createInfo, nullptr, &debugMessenger);
|
|
||||||
LOG.debug() << "Добавлен обработчик логов";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!Graphics.Surface)
|
if(!Graphics.Surface)
|
||||||
@@ -1644,8 +1557,8 @@ void Vulkan::initNextSettings()
|
|||||||
|
|
||||||
features.features.geometryShader = true;
|
features.features.geometryShader = true;
|
||||||
|
|
||||||
feat11.uniformAndStorageBuffer16BitAccess = false;
|
feat11.uniformAndStorageBuffer16BitAccess = true;
|
||||||
feat11.storageBuffer16BitAccess = false;
|
feat11.storageBuffer16BitAccess = true;
|
||||||
|
|
||||||
VkDeviceCreateInfo infoDevice =
|
VkDeviceCreateInfo infoDevice =
|
||||||
{
|
{
|
||||||
@@ -1662,8 +1575,7 @@ void Vulkan::initNextSettings()
|
|||||||
};
|
};
|
||||||
|
|
||||||
vkAssert(!vkCreateDevice(Graphics.PhysicalDevice, &infoDevice, nullptr, &Graphics.Device));
|
vkAssert(!vkCreateDevice(Graphics.PhysicalDevice, &infoDevice, nullptr, &Graphics.Device));
|
||||||
auto lockQueue = Graphics.DeviceQueueGraphic.lock();
|
vkGetDeviceQueue(Graphics.Device, SettingsNext.QueueGraphics, 0, &Graphics.DeviceQueueGraphic);
|
||||||
vkGetDeviceQueue(Graphics.Device, SettingsNext.QueueGraphics, 0, &*lockQueue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Определяемся с форматом экранного буфера
|
// Определяемся с форматом экранного буфера
|
||||||
@@ -1713,7 +1625,7 @@ void Vulkan::initNextSettings()
|
|||||||
{
|
{
|
||||||
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
|
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
|
||||||
.pNext = nullptr,
|
.pNext = nullptr,
|
||||||
.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT | VK_COMMAND_POOL_CREATE_TRANSIENT_BIT,
|
.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
|
||||||
.queueFamilyIndex = SettingsNext.QueueGraphics
|
.queueFamilyIndex = SettingsNext.QueueGraphics
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1890,14 +1802,13 @@ void Vulkan::initNextSettings()
|
|||||||
|
|
||||||
ImGui_ImplGlfw_InitForVulkan(Graphics.Window, true);
|
ImGui_ImplGlfw_InitForVulkan(Graphics.Window, true);
|
||||||
|
|
||||||
auto lockQueue = Graphics.DeviceQueueGraphic.lock();
|
|
||||||
ImGui_ImplVulkan_InitInfo ImGuiInfo =
|
ImGui_ImplVulkan_InitInfo ImGuiInfo =
|
||||||
{
|
{
|
||||||
.Instance = Graphics.Instance->getInstance(),
|
.Instance = Graphics.Instance->getInstance(),
|
||||||
.PhysicalDevice = Graphics.PhysicalDevice,
|
.PhysicalDevice = Graphics.PhysicalDevice,
|
||||||
.Device = Graphics.Device,
|
.Device = Graphics.Device,
|
||||||
.QueueFamily = SettingsNext.QueueGraphics,
|
.QueueFamily = SettingsNext.QueueGraphics,
|
||||||
.Queue = *lockQueue,
|
.Queue = Graphics.DeviceQueueGraphic,
|
||||||
.DescriptorPool = Graphics.ImGuiDescPool,
|
.DescriptorPool = Graphics.ImGuiDescPool,
|
||||||
.RenderPass = Graphics.RenderPass,
|
.RenderPass = Graphics.RenderPass,
|
||||||
.MinImageCount = Graphics.DrawBufferCount,
|
.MinImageCount = Graphics.DrawBufferCount,
|
||||||
@@ -1913,7 +1824,6 @@ void Vulkan::initNextSettings()
|
|||||||
};
|
};
|
||||||
|
|
||||||
ImGui_ImplVulkan_Init(&ImGuiInfo);
|
ImGui_ImplVulkan_Init(&ImGuiInfo);
|
||||||
lockQueue.unlock();
|
|
||||||
|
|
||||||
// ImFontConfig fontConfig;
|
// ImFontConfig fontConfig;
|
||||||
// fontConfig.MergeMode = false;
|
// fontConfig.MergeMode = false;
|
||||||
@@ -2017,7 +1927,7 @@ void Vulkan::deInitVulkan()
|
|||||||
Graphics.RenderPass = nullptr;
|
Graphics.RenderPass = nullptr;
|
||||||
Graphics.Device = nullptr;
|
Graphics.Device = nullptr;
|
||||||
Graphics.PhysicalDevice = nullptr;
|
Graphics.PhysicalDevice = nullptr;
|
||||||
*Graphics.DeviceQueueGraphic.lock() = nullptr;
|
Graphics.DeviceQueueGraphic = nullptr;
|
||||||
Graphics.Surface = nullptr;
|
Graphics.Surface = nullptr;
|
||||||
Graphics.Instance.reset();
|
Graphics.Instance.reset();
|
||||||
}
|
}
|
||||||
@@ -2041,10 +1951,8 @@ void Vulkan::flushCommandBufferData()
|
|||||||
.pSignalSemaphores = nullptr
|
.pSignalSemaphores = nullptr
|
||||||
};
|
};
|
||||||
|
|
||||||
auto lockQueue = Graphics.DeviceQueueGraphic.lock();
|
vkAssert(!vkQueueSubmit(Graphics.DeviceQueueGraphic, 1, &submit_info, nullFence));
|
||||||
vkAssert(!vkQueueSubmit(*lockQueue, 1, &submit_info, nullFence));
|
vkAssert(!vkQueueWaitIdle(Graphics.DeviceQueueGraphic));
|
||||||
vkAssert(!vkQueueWaitIdle(*lockQueue));
|
|
||||||
lockQueue.unlock();
|
|
||||||
|
|
||||||
VkCommandBufferBeginInfo infoCmdBuffer =
|
VkCommandBufferBeginInfo infoCmdBuffer =
|
||||||
{
|
{
|
||||||
@@ -2162,23 +2070,12 @@ void Vulkan::gui_MainMenu() {
|
|||||||
ImGui::InputText("Username", ConnectionProgress.Username, sizeof(ConnectionProgress.Username));
|
ImGui::InputText("Username", ConnectionProgress.Username, sizeof(ConnectionProgress.Username));
|
||||||
ImGui::InputText("Password", ConnectionProgress.Password, sizeof(ConnectionProgress.Password), ImGuiInputTextFlags_Password);
|
ImGui::InputText("Password", ConnectionProgress.Password, sizeof(ConnectionProgress.Password), ImGuiInputTextFlags_Password);
|
||||||
|
|
||||||
static bool flag = true;
|
|
||||||
if(!flag) {
|
|
||||||
flag = true;
|
|
||||||
Game.Server = std::make_unique<ServerObj>(IOC);
|
|
||||||
ConnectionProgress.Progress = "Сервер запущен на порту " + std::to_string(Game.Server->LS.getPort());
|
|
||||||
ConnectionProgress.InProgress = true;
|
|
||||||
ConnectionProgress.Cancel = false;
|
|
||||||
ConnectionProgress.Progress.clear();
|
|
||||||
co_spawn(ConnectionProgress.connect(IOC));
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!ConnectionProgress.InProgress && !ConnectionProgress.Socket) {
|
if(!ConnectionProgress.InProgress && !ConnectionProgress.Socket) {
|
||||||
if(ImGui::Button("Подключиться")) {
|
if(ImGui::Button("Подключиться")) {
|
||||||
ConnectionProgress.InProgress = true;
|
ConnectionProgress.InProgress = true;
|
||||||
ConnectionProgress.Cancel = false;
|
ConnectionProgress.Cancel = false;
|
||||||
ConnectionProgress.Progress.clear();
|
ConnectionProgress.Progress.clear();
|
||||||
co_spawn(ConnectionProgress.connect(IOC));
|
asio::co_spawn(IOC, ConnectionProgress.connect(IOC), asio::detached);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!Game.Server) {
|
if(!Game.Server) {
|
||||||
@@ -2226,37 +2123,9 @@ void Vulkan::gui_MainMenu() {
|
|||||||
|
|
||||||
void Vulkan::gui_ConnectedToServer() {
|
void Vulkan::gui_ConnectedToServer() {
|
||||||
if(Game.Session) {
|
if(Game.Session) {
|
||||||
if(ImGui::Begin("MainMenu", nullptr, ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove))
|
|
||||||
{
|
|
||||||
std::string text = std::to_string(ImGui::GetIO().Framerate);
|
|
||||||
ImGui::Text("%s", text.c_str());
|
|
||||||
if(ImGui::Button("Выйти")) {
|
|
||||||
try {
|
|
||||||
if(Game.Session)
|
|
||||||
Game.Session->shutdown(EnumDisconnect::ByInterface);
|
|
||||||
} catch(const std::exception &exc) {
|
|
||||||
LOG.error() << "Game.Session->shutdown: " << exc.what();
|
|
||||||
}
|
|
||||||
|
|
||||||
Game.RSession->pushStage(EnumRenderStage::Shutdown);
|
|
||||||
Game.RSession = nullptr;
|
|
||||||
Game.Session = nullptr;
|
|
||||||
Game.ImGuiInterfaces.pop_back();
|
|
||||||
int mode = glfwGetInputMode(Graphics.Window, GLFW_CURSOR);
|
|
||||||
if(mode != GLFW_CURSOR_NORMAL)
|
|
||||||
glfwSetInputMode(Graphics.Window, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::End();
|
|
||||||
|
|
||||||
if(!Game.RSession)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(Game.Session->isConnected())
|
if(Game.Session->isConnected())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Game.RSession->pushStage(EnumRenderStage::Shutdown);
|
|
||||||
Game.RSession = nullptr;
|
Game.RSession = nullptr;
|
||||||
Game.Session = nullptr;
|
Game.Session = nullptr;
|
||||||
Game.ImGuiInterfaces.pop_back();
|
Game.ImGuiInterfaces.pop_back();
|
||||||
@@ -2766,7 +2635,6 @@ Buffer::Buffer(Vulkan *instance, VkDeviceSize bufferSize, VkBufferUsageFlags usa
|
|||||||
vkAllocateMemory(instance->Graphics.Device, &memAlloc, nullptr, &Memory);
|
vkAllocateMemory(instance->Graphics.Device, &memAlloc, nullptr, &Memory);
|
||||||
if(res)
|
if(res)
|
||||||
MAKE_ERROR("VkHandler: ошибка выделения памяти на устройстве");
|
MAKE_ERROR("VkHandler: ошибка выделения памяти на устройстве");
|
||||||
assert(Memory);
|
|
||||||
|
|
||||||
vkBindBufferMemory(instance->Graphics.Device, Buff, Memory, 0);
|
vkBindBufferMemory(instance->Graphics.Device, Buff, Memory, 0);
|
||||||
}
|
}
|
||||||
@@ -2795,16 +2663,12 @@ Buffer::Buffer(Buffer &&obj)
|
|||||||
obj.Instance = nullptr;
|
obj.Instance = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
Buffer& Buffer::operator=(Buffer &&obj) {
|
Buffer& Buffer::operator=(Buffer &&obj)
|
||||||
if(this == &obj)
|
{
|
||||||
return *this;
|
|
||||||
|
|
||||||
std::swap(Instance, obj.Instance);
|
std::swap(Instance, obj.Instance);
|
||||||
std::swap(Buff, obj.Buff);
|
std::swap(Buff, obj.Buff);
|
||||||
std::swap(Memory, obj.Memory);
|
std::swap(Memory, obj.Memory);
|
||||||
std::swap(Size, obj.Size);
|
std::swap(Size, obj.Size);
|
||||||
obj.Instance = nullptr;
|
|
||||||
|
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2864,22 +2728,13 @@ CommandBuffer::CommandBuffer(Vulkan *instance)
|
|||||||
};
|
};
|
||||||
|
|
||||||
vkAssert(!vkBeginCommandBuffer(Buffer, &infoCmdBuffer));
|
vkAssert(!vkBeginCommandBuffer(Buffer, &infoCmdBuffer));
|
||||||
|
|
||||||
const VkFenceCreateInfo info = {
|
|
||||||
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
|
|
||||||
.pNext = nullptr,
|
|
||||||
.flags = 0
|
|
||||||
};
|
|
||||||
|
|
||||||
vkAssert(!vkCreateFence(instance->Graphics.Device, &info, nullptr, &Fence));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CommandBuffer::~CommandBuffer()
|
CommandBuffer::~CommandBuffer()
|
||||||
{
|
{
|
||||||
if(Buffer && Instance && Instance->Graphics.Device)
|
if(Buffer && Instance && Instance->Graphics.Device)
|
||||||
{
|
{
|
||||||
auto lockQueue = Instance->Graphics.DeviceQueueGraphic.lock();
|
if(Instance->Graphics.DeviceQueueGraphic)
|
||||||
if(*lockQueue)
|
|
||||||
{
|
{
|
||||||
//vkAssert(!vkEndCommandBuffer(Buffer));
|
//vkAssert(!vkEndCommandBuffer(Buffer));
|
||||||
vkEndCommandBuffer(Buffer);
|
vkEndCommandBuffer(Buffer);
|
||||||
@@ -2899,33 +2754,25 @@ CommandBuffer::~CommandBuffer()
|
|||||||
};
|
};
|
||||||
|
|
||||||
//vkAssert(!vkQueueSubmit(Instance->Graphics.DeviceQueueGraphic, 1, &submit_info, nullFence));
|
//vkAssert(!vkQueueSubmit(Instance->Graphics.DeviceQueueGraphic, 1, &submit_info, nullFence));
|
||||||
vkQueueSubmit(*lockQueue, 1, &submit_info, Fence);
|
vkQueueSubmit(Instance->Graphics.DeviceQueueGraphic, 1, &submit_info, nullFence);
|
||||||
lockQueue.unlock();
|
|
||||||
//vkAssert(!vkQueueWaitIdle(Instance->Graphics.DeviceQueueGraphic));
|
//vkAssert(!vkQueueWaitIdle(Instance->Graphics.DeviceQueueGraphic));
|
||||||
//vkQueueWaitIdle(Instance->Graphics.DeviceQueueGraphic);
|
vkQueueWaitIdle(Instance->Graphics.DeviceQueueGraphic);
|
||||||
vkWaitForFences(Instance->Graphics.Device, 1, &Fence, VK_TRUE, UINT64_MAX);
|
|
||||||
vkResetFences(Instance->Graphics.Device, 1, &Fence);
|
|
||||||
|
|
||||||
auto toExecute = std::move(AfterExecute);
|
auto toExecute = std::move(AfterExecute);
|
||||||
for(auto &iter : toExecute)
|
for(auto &iter : toExecute)
|
||||||
try { iter(); } catch(const std::exception &exc) { Logger("CommandBuffer").error() << exc.what(); }
|
try { iter(); } catch(const std::exception &exc) { Logger("CommandBuffer").error() << exc.what(); }
|
||||||
} else
|
}
|
||||||
lockQueue.unlock();
|
|
||||||
|
|
||||||
vkFreeCommandBuffers(Instance->Graphics.Device, OffthreadPool ? OffthreadPool : Instance->Graphics.Pool, 1, &Buffer);
|
vkFreeCommandBuffers(Instance->Graphics.Device, OffthreadPool ? OffthreadPool : Instance->Graphics.Pool, 1, &Buffer);
|
||||||
|
|
||||||
if(OffthreadPool)
|
if(OffthreadPool)
|
||||||
vkDestroyCommandPool(Instance->Graphics.Device, OffthreadPool, nullptr);
|
vkDestroyCommandPool(Instance->Graphics.Device, OffthreadPool, nullptr);
|
||||||
|
|
||||||
if(Fence)
|
|
||||||
vkDestroyFence(Instance->Graphics.Device, Fence, nullptr);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CommandBuffer::execute()
|
void CommandBuffer::execute()
|
||||||
{
|
{
|
||||||
auto lockQueue = Instance->Graphics.DeviceQueueGraphic.lock();
|
vkAssert(Instance->Graphics.DeviceQueueGraphic);
|
||||||
vkAssert(*lockQueue);
|
|
||||||
vkAssert(!vkEndCommandBuffer(Buffer));
|
vkAssert(!vkEndCommandBuffer(Buffer));
|
||||||
|
|
||||||
const VkCommandBuffer cmd_bufs[] = { Buffer };
|
const VkCommandBuffer cmd_bufs[] = { Buffer };
|
||||||
@@ -2943,16 +2790,8 @@ void CommandBuffer::execute()
|
|||||||
.pSignalSemaphores = nullptr
|
.pSignalSemaphores = nullptr
|
||||||
};
|
};
|
||||||
|
|
||||||
// vkAssert(!vkQueueSubmit(Instance->Graphics.DeviceQueueGraphic, 1, &submit_info, nullFence));
|
vkAssert(!vkQueueSubmit(Instance->Graphics.DeviceQueueGraphic, 1, &submit_info, nullFence));
|
||||||
// vkAssert(!vkQueueWaitIdle(Instance->Graphics.DeviceQueueGraphic));
|
vkAssert(!vkQueueWaitIdle(Instance->Graphics.DeviceQueueGraphic));
|
||||||
|
|
||||||
vkAssert(!vkQueueSubmit(*lockQueue, 1, &submit_info, Fence));
|
|
||||||
lockQueue.unlock();
|
|
||||||
//vkAssert(!vkQueueWaitIdle(Instance->Graphics.DeviceQueueGraphic));
|
|
||||||
//vkQueueWaitIdle(Instance->Graphics.DeviceQueueGraphic);
|
|
||||||
vkAssert(!vkWaitForFences(Instance->Graphics.Device, 1, &Fence, VK_TRUE, UINT64_MAX));
|
|
||||||
vkAssert(!vkResetFences(Instance->Graphics.Device, 1, &Fence));
|
|
||||||
|
|
||||||
VkCommandBufferBeginInfo infoCmdBuffer =
|
VkCommandBufferBeginInfo infoCmdBuffer =
|
||||||
{
|
{
|
||||||
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
|
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
|
||||||
@@ -3661,8 +3500,6 @@ void DynamicImage::changeData(int32_t x, int32_t y, uint16_t width, uint16_t hei
|
|||||||
// Выполняем все команды
|
// Выполняем все команды
|
||||||
buffer.execute();
|
buffer.execute();
|
||||||
|
|
||||||
Time::sleep3(50);
|
|
||||||
|
|
||||||
// Удаляем не нужную картинку
|
// Удаляем не нужную картинку
|
||||||
vkDestroyImage(Instance->Graphics.Device, tempImage, nullptr);
|
vkDestroyImage(Instance->Graphics.Device, tempImage, nullptr);
|
||||||
vkFreeMemory(Instance->Graphics.Device, tempMemory, nullptr);
|
vkFreeMemory(Instance->Graphics.Device, tempMemory, nullptr);
|
||||||
@@ -3991,7 +3828,7 @@ void AtlasImage::atlasChangeTextureData(uint16_t id, const uint32_t *rgba)
|
|||||||
InfoSubTexture *info = const_cast<InfoSubTexture*>(atlasGetTextureInfo(id));
|
InfoSubTexture *info = const_cast<InfoSubTexture*>(atlasGetTextureInfo(id));
|
||||||
|
|
||||||
auto iter = CachedData.find(id);
|
auto iter = CachedData.find(id);
|
||||||
// Если есть данные в кеше, то меняем их
|
// Если есть данные в кэше, то меняем их
|
||||||
if(iter != CachedData.end())
|
if(iter != CachedData.end())
|
||||||
{
|
{
|
||||||
if(iter->second.size() == 0)
|
if(iter->second.size() == 0)
|
||||||
|
|||||||
@@ -1,10 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
// Cmake
|
|
||||||
// #define GLM_FORCE_DEPTH_ZERO_TO_ONE
|
|
||||||
#include <glm/ext.hpp>
|
|
||||||
static_assert(GLM_CONFIG_CLIP_CONTROL == GLM_CLIP_CONTROL_RH_ZO);
|
|
||||||
|
|
||||||
#include "Client/ServerSession.hpp"
|
#include "Client/ServerSession.hpp"
|
||||||
#include "Common/Async.hpp"
|
#include "Common/Async.hpp"
|
||||||
#include <TOSLib.hpp>
|
#include <TOSLib.hpp>
|
||||||
@@ -24,12 +19,14 @@ static_assert(GLM_CONFIG_CLIP_CONTROL == GLM_CLIP_CONTROL_RH_ZO);
|
|||||||
#include "freetype/freetype.h"
|
#include "freetype/freetype.h"
|
||||||
|
|
||||||
#include <vulkan/vulkan_core.h>
|
#include <vulkan/vulkan_core.h>
|
||||||
|
#include <glm/ext.hpp>
|
||||||
#include <map>
|
#include <map>
|
||||||
#define TOS_VULKAN_NO_VIDEO
|
#define TOS_VULKAN_NO_VIDEO
|
||||||
#include <vulkan/vulkan.h>
|
#include <vulkan/vulkan.h>
|
||||||
#define GLFW_INCLUDE_NONE
|
#define GLFW_INCLUDE_NONE
|
||||||
#include <GLFW/glfw3.h>
|
#include <GLFW/glfw3.h>
|
||||||
|
|
||||||
|
static_assert(GLM_CONFIG_CLIP_CONTROL == GLM_CLIP_CONTROL_RH_ZO);
|
||||||
|
|
||||||
#define IMGUI_ENABLE_STB_TEXTEDIT_UNICODE
|
#define IMGUI_ENABLE_STB_TEXTEDIT_UNICODE
|
||||||
|
|
||||||
@@ -74,9 +71,10 @@ class Buffer;
|
|||||||
Vulkan.reInit();
|
Vulkan.reInit();
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class Vulkan : public AsyncObject {
|
class Vulkan {
|
||||||
private:
|
private:
|
||||||
Logger LOG = "Vulkan";
|
Logger LOG = "Vulkan";
|
||||||
|
asio::io_context &IOC;
|
||||||
|
|
||||||
struct vkInstanceLayer {
|
struct vkInstanceLayer {
|
||||||
std::string LayerName = "nullptr", Description = "nullptr";
|
std::string LayerName = "nullptr", Description = "nullptr";
|
||||||
@@ -178,7 +176,7 @@ private:
|
|||||||
asio::executor_work_guard<asio::io_context::executor_type> GuardLock;
|
asio::executor_work_guard<asio::io_context::executor_type> GuardLock;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
struct GraphicsObj_t {
|
struct {
|
||||||
std::vector<vkInstanceLayer> InstanceLayers;
|
std::vector<vkInstanceLayer> InstanceLayers;
|
||||||
std::vector<vkInstanceExtension> InstanceExtensions;
|
std::vector<vkInstanceExtension> InstanceExtensions;
|
||||||
std::vector<std::string> GLFWExtensions;
|
std::vector<std::string> GLFWExtensions;
|
||||||
@@ -191,7 +189,7 @@ public:
|
|||||||
VkPhysicalDevice PhysicalDevice = nullptr;
|
VkPhysicalDevice PhysicalDevice = nullptr;
|
||||||
VkPhysicalDeviceMemoryProperties DeviceMemoryProperties = {0};
|
VkPhysicalDeviceMemoryProperties DeviceMemoryProperties = {0};
|
||||||
VkDevice Device = nullptr;
|
VkDevice Device = nullptr;
|
||||||
SpinlockObject<VkQueue> DeviceQueueGraphic;
|
VkQueue DeviceQueueGraphic = VK_NULL_HANDLE;
|
||||||
VkFormat SurfaceFormat = VK_FORMAT_B8G8R8A8_UNORM;
|
VkFormat SurfaceFormat = VK_FORMAT_B8G8R8A8_UNORM;
|
||||||
VkColorSpaceKHR SurfaceColorSpace = VK_COLOR_SPACE_MAX_ENUM_KHR;
|
VkColorSpaceKHR SurfaceColorSpace = VK_COLOR_SPACE_MAX_ENUM_KHR;
|
||||||
|
|
||||||
@@ -226,10 +224,6 @@ public:
|
|||||||
|
|
||||||
// Идентификатор потока графики
|
// Идентификатор потока графики
|
||||||
std::thread::id ThisThread = std::this_thread::get_id();
|
std::thread::id ThisThread = std::this_thread::get_id();
|
||||||
|
|
||||||
GraphicsObj_t()
|
|
||||||
: DeviceQueueGraphic(VK_NULL_HANDLE)
|
|
||||||
{}
|
|
||||||
} Graphics;
|
} Graphics;
|
||||||
|
|
||||||
enum struct DrawState {
|
enum struct DrawState {
|
||||||
@@ -541,7 +535,6 @@ public:
|
|||||||
class CommandBuffer {
|
class CommandBuffer {
|
||||||
VkCommandBuffer Buffer = VK_NULL_HANDLE;
|
VkCommandBuffer Buffer = VK_NULL_HANDLE;
|
||||||
Vulkan *Instance;
|
Vulkan *Instance;
|
||||||
VkFence Fence = VK_NULL_HANDLE;
|
|
||||||
std::vector<std::function<void()>> AfterExecute;
|
std::vector<std::function<void()>> AfterExecute;
|
||||||
|
|
||||||
VkCommandPool OffthreadPool = VK_NULL_HANDLE;
|
VkCommandPool OffthreadPool = VK_NULL_HANDLE;
|
||||||
@@ -1026,25 +1019,5 @@ public:
|
|||||||
virtual ~PipelineVGF();
|
virtual ~PipelineVGF();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
enum class EnumRenderStage {
|
|
||||||
// Постройка буфера команд на рисовку
|
|
||||||
// В этот период не должно быть изменений в таблице,
|
|
||||||
// хранящей указатели на данные для рендера ChunkMesh
|
|
||||||
// Можно работать с миром
|
|
||||||
// Здесь нужно дождаться завершения работы с ChunkMesh
|
|
||||||
ComposingCommandBuffer,
|
|
||||||
// В этот период можно менять ChunkMesh
|
|
||||||
// Можно работать с миром
|
|
||||||
Render,
|
|
||||||
// В этот период нельзя работать с миром
|
|
||||||
// Можно менять ChunkMesh
|
|
||||||
// Здесь нужно дождаться завершения работы с миром, только в
|
|
||||||
// этом этапе могут приходить события изменения чанков и определений
|
|
||||||
WorldUpdate,
|
|
||||||
|
|
||||||
Shutdown
|
|
||||||
};
|
|
||||||
|
|
||||||
} /* namespace TOS::Navie::VK */
|
} /* namespace TOS::Navie::VK */
|
||||||
|
|
||||||
|
|||||||
@@ -5,16 +5,9 @@
|
|||||||
#include <Client/Vulkan/Vulkan.hpp>
|
#include <Client/Vulkan/Vulkan.hpp>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <queue>
|
|
||||||
#include <thread>
|
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <unordered_set>
|
|
||||||
#include <vulkan/vulkan_core.h>
|
#include <vulkan/vulkan_core.h>
|
||||||
#include "Abstract.hpp"
|
|
||||||
#include "TOSLib.hpp"
|
|
||||||
#include "VertexPool.hpp"
|
|
||||||
#include "glm/fwd.hpp"
|
|
||||||
#include "../FrustumCull.h"
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
У движка есть один текстурный атлас VK_IMAGE_VIEW_TYPE_2D_ARRAY(RGBA_UINT) и к нему Storage с инфой о положении текстур
|
У движка есть один текстурный атлас VK_IMAGE_VIEW_TYPE_2D_ARRAY(RGBA_UINT) и к нему Storage с инфой о положении текстур
|
||||||
@@ -35,8 +28,6 @@
|
|||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
namespace LV::Client::VK {
|
namespace LV::Client::VK {
|
||||||
|
|
||||||
struct WorldPCO {
|
struct WorldPCO {
|
||||||
@@ -46,240 +37,60 @@ struct WorldPCO {
|
|||||||
static_assert(sizeof(WorldPCO) == 128);
|
static_assert(sizeof(WorldPCO) == 128);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Модуль, рисующий то, что предоставляет IServerSession
|
Воксели рендерятся точками, которые распаковываются в квадратные плоскости
|
||||||
|
|
||||||
|
В чанке по оси 256 вокселей, и 257 позиций вершин (включая дальнюю границу чанка)
|
||||||
|
9 бит на позицию *3 оси = 27 бит
|
||||||
|
Указание материала 16 бит
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
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 : 9, FY : 9, FZ : 9, // Позиция -112 ~ 369 / 16
|
||||||
|
N1 : 4, // Не занято
|
||||||
|
LS : 1, // Масштаб карты освещения (1м/16 или 1м)
|
||||||
|
Tex : 18, // Текстура
|
||||||
|
N2 : 14, // Не занято
|
||||||
|
TU : 16, TV : 16; // UV на текстуре
|
||||||
|
};
|
||||||
|
|
||||||
class VulkanRenderSession : public IRenderSession, public IVulkanDependent {
|
class VulkanRenderSession : public IRenderSession, public IVulkanDependent {
|
||||||
VK::Vulkan *VkInst = nullptr;
|
VK::Vulkan *VkInst = nullptr;
|
||||||
// Доступ к миру на стороне клиента
|
// Доступ к миру на стороне клиента
|
||||||
IServerSession *ServerSession = nullptr;
|
IServerSession *ServerSession = nullptr;
|
||||||
|
|
||||||
// Положение камеры
|
// Положение камеры
|
||||||
WorldId_t WorldId;
|
WorldId_c WorldId;
|
||||||
Pos::Object Pos;
|
Pos::Object Pos;
|
||||||
/*
|
|
||||||
Графический конвейер оперирует числами с плавающей запятой
|
|
||||||
Для сохранения точности матрица модели хранит смещения близкие к нулю (X64Delta)
|
|
||||||
глобальные смещения на уровне региона исключаются из смещения ещё при задании матрицы модели
|
|
||||||
|
|
||||||
X64Offset = позиция игрока на уровне регионов
|
|
||||||
X64Delta = позиция игрока в рамках региона
|
|
||||||
|
|
||||||
Внутри графического конвейера будут числа приблежённые к 0
|
|
||||||
*/
|
|
||||||
// Смещение дочерних объекто на стороне хоста перед рендером
|
|
||||||
Pos::Object X64Offset;
|
|
||||||
glm::vec3 X64Offset_f, X64Delta; // Смещение мира относительно игрока в матрице вида (0 -> 64)
|
|
||||||
glm::quat Quat;
|
glm::quat Quat;
|
||||||
|
|
||||||
/*
|
|
||||||
Поток, занимающийся генерацией меша на основе нод и вокселей
|
|
||||||
Требует доступ к профилям в ServerSession (ServerSession должен быть заблокирован только на чтение)
|
|
||||||
Также доступ к идентификаторам текстур в VulkanRenderSession (только на чтение)
|
|
||||||
Должен оповещаться об изменениях профилей и событий чанков
|
|
||||||
Удалённые мешы хранятся в памяти N количество кадров
|
|
||||||
*/
|
|
||||||
struct ThreadVertexObj_t {
|
|
||||||
// Сессия будет выдана позже
|
|
||||||
// Предполагается что события будут только после того как сессия будет установлена,
|
|
||||||
// соответственно никто не попытаеся сюда обратится без событий
|
|
||||||
IServerSession *SSession = nullptr;
|
|
||||||
Vulkan *VkInst;
|
|
||||||
VkCommandPool CMDPool = nullptr;
|
|
||||||
|
|
||||||
// Здесь не хватает стадии работы с текстурами
|
|
||||||
struct StateObj_t {
|
|
||||||
EnumRenderStage Stage = EnumRenderStage::Render;
|
|
||||||
volatile bool ChunkMesh_IsUse = false, ServerSession_InUse = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
SpinlockObject<StateObj_t> State;
|
|
||||||
|
|
||||||
struct ChunkObj_t {
|
|
||||||
// Сортированный список уникальных значений
|
|
||||||
std::vector<DefVoxelId> VoxelDefines;
|
|
||||||
VertexPool<VoxelVertexPoint>::Pointer VoxelPointer;
|
|
||||||
std::vector<DefNodeId> NodeDefines;
|
|
||||||
VertexPool<NodeVertexStatic>::Pointer NodePointer;
|
|
||||||
};
|
|
||||||
|
|
||||||
ThreadVertexObj_t(Vulkan* vkInst)
|
|
||||||
: VkInst(vkInst),
|
|
||||||
VertexPool_Voxels(vkInst),
|
|
||||||
VertexPool_Nodes(vkInst),
|
|
||||||
Thread(&ThreadVertexObj_t::run, this)
|
|
||||||
{
|
|
||||||
const VkCommandPoolCreateInfo infoCmdPool =
|
|
||||||
{
|
|
||||||
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
|
|
||||||
.pNext = nullptr,
|
|
||||||
.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
|
|
||||||
.queueFamilyIndex = VkInst->getSettings().QueueGraphics
|
|
||||||
};
|
|
||||||
|
|
||||||
vkAssert(!vkCreateCommandPool(VkInst->Graphics.Device, &infoCmdPool, nullptr, &CMDPool));
|
|
||||||
}
|
|
||||||
|
|
||||||
~ThreadVertexObj_t() {
|
|
||||||
assert(!Thread.joinable());
|
|
||||||
|
|
||||||
if(CMDPool)
|
|
||||||
vkDestroyCommandPool(VkInst->Graphics.Device, CMDPool, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Сюда входят добавленные/изменённые/удалённые определения нод и вокселей
|
|
||||||
// Чтобы перерисовать чанки, связанные с ними
|
|
||||||
void onContentDefinesChange(const std::vector<DefVoxelId>& voxels, const std::vector<DefNodeId>& nodes) {
|
|
||||||
ChangedDefines_Voxel.insert(ChangedDefines_Voxel.end(), voxels.begin(), voxels.end());
|
|
||||||
ChangedDefines_Node.insert(ChangedDefines_Node.end(), nodes.begin(), nodes.end());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Изменение/удаление чанков
|
|
||||||
void onContentChunkChange(const std::unordered_map<WorldId_t, std::vector<Pos::GlobalChunk>>& chunkChanges, const std::unordered_map<WorldId_t, std::vector<Pos::GlobalRegion>>& regionRemove) {
|
|
||||||
for(auto& [worldId, chunks] : chunkChanges) {
|
|
||||||
auto &list = ChangedContent_Chunk[worldId];
|
|
||||||
list.insert(list.end(), chunks.begin(), chunks.end());
|
|
||||||
}
|
|
||||||
|
|
||||||
for(auto& [worldId, regions] : regionRemove) {
|
|
||||||
auto &list = ChangedContent_RegionRemove[worldId];
|
|
||||||
list.insert(list.end(), regions.begin(), regions.end());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Синхронизация потока рендера мира
|
|
||||||
void pushStage(EnumRenderStage stage) {
|
|
||||||
auto lock = State.lock();
|
|
||||||
|
|
||||||
if(lock->Stage == EnumRenderStage::Shutdown)
|
|
||||||
MAKE_ERROR("Остановка из-за ошибки ThreadVertex");
|
|
||||||
|
|
||||||
assert(lock->Stage != stage);
|
|
||||||
|
|
||||||
lock->Stage = stage;
|
|
||||||
|
|
||||||
if(stage == EnumRenderStage::ComposingCommandBuffer) {
|
|
||||||
if(lock->ChunkMesh_IsUse) {
|
|
||||||
lock.unlock();
|
|
||||||
while(State.get_read().ChunkMesh_IsUse);
|
|
||||||
} else
|
|
||||||
lock.unlock();
|
|
||||||
} else if(stage == EnumRenderStage::WorldUpdate) {
|
|
||||||
if(lock->ServerSession_InUse) {
|
|
||||||
lock.unlock();
|
|
||||||
while(State.get_read().ServerSession_InUse);
|
|
||||||
} else
|
|
||||||
lock.unlock();
|
|
||||||
} else if(stage == EnumRenderStage::Shutdown) {
|
|
||||||
if(lock->ServerSession_InUse || lock->ChunkMesh_IsUse) {
|
|
||||||
lock.unlock();
|
|
||||||
while(State.get_read().ServerSession_InUse);
|
|
||||||
while(State.get_read().ChunkMesh_IsUse);
|
|
||||||
} else
|
|
||||||
lock.unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
std::pair<
|
|
||||||
std::vector<std::tuple<Pos::GlobalChunk, std::pair<VkBuffer, int>, uint32_t>>,
|
|
||||||
std::vector<std::tuple<Pos::GlobalChunk, std::pair<VkBuffer, int>, uint32_t>>
|
|
||||||
> getChunksForRender(WorldId_t worldId, Pos::Object pos, uint8_t distance, glm::mat4 projView, Pos::GlobalRegion x64offset) {
|
|
||||||
Pos::GlobalRegion center = pos >> Pos::Object_t::BS_Bit >> 4 >> 2;
|
|
||||||
|
|
||||||
std::vector<std::tuple<Pos::GlobalChunk, std::pair<VkBuffer, int>, uint32_t>> vertexVoxels;
|
|
||||||
std::vector<std::tuple<Pos::GlobalChunk, std::pair<VkBuffer, int>, uint32_t>> vertexNodes;
|
|
||||||
|
|
||||||
auto iterWorld = ChunkMesh.find(worldId);
|
|
||||||
if(iterWorld == ChunkMesh.end())
|
|
||||||
return {};
|
|
||||||
|
|
||||||
Frustum fr(projView);
|
|
||||||
|
|
||||||
for(int z = -distance; z <= distance; z++) {
|
|
||||||
for(int y = -distance; y <= distance; y++) {
|
|
||||||
for(int x = -distance; x <= distance; x++) {
|
|
||||||
Pos::GlobalRegion region = center + Pos::GlobalRegion(x, y, z);
|
|
||||||
glm::vec3 begin = glm::vec3(region - x64offset) * 64.f;
|
|
||||||
glm::vec3 end = begin + glm::vec3(64.f);
|
|
||||||
|
|
||||||
if(!fr.IsBoxVisible(begin, end))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
auto iterRegion = iterWorld->second.find(region);
|
|
||||||
if(iterRegion == iterWorld->second.end())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
Pos::GlobalChunk local = Pos::GlobalChunk(region) << 2;
|
|
||||||
|
|
||||||
for(size_t index = 0; index < iterRegion->second.size(); index++) {
|
|
||||||
Pos::bvec4u localPos;
|
|
||||||
localPos.unpack(index);
|
|
||||||
|
|
||||||
glm::vec3 chunkPos = begin+glm::vec3(localPos)*16.f;
|
|
||||||
if(!fr.IsBoxVisible(chunkPos, chunkPos+glm::vec3(16)))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
auto &chunk = iterRegion->second[index];
|
|
||||||
|
|
||||||
if(chunk.VoxelPointer)
|
|
||||||
vertexVoxels.emplace_back(local+Pos::GlobalChunk(localPos), VertexPool_Voxels.map(chunk.VoxelPointer), chunk.VoxelPointer.VertexCount);
|
|
||||||
if(chunk.NodePointer)
|
|
||||||
vertexNodes.emplace_back(local+Pos::GlobalChunk(localPos), VertexPool_Nodes.map(chunk.NodePointer), chunk.NodePointer.VertexCount);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return std::pair{vertexVoxels, vertexNodes};
|
|
||||||
}
|
|
||||||
|
|
||||||
void join() {
|
|
||||||
Thread.join();
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
// Буферы для хранения вершин
|
|
||||||
VertexPool<VoxelVertexPoint> VertexPool_Voxels;
|
|
||||||
VertexPool<NodeVertexStatic> VertexPool_Nodes;
|
|
||||||
// Списки изменённых определений
|
|
||||||
std::vector<DefVoxelId> ChangedDefines_Voxel;
|
|
||||||
std::vector<DefNodeId> ChangedDefines_Node;
|
|
||||||
// Список чанков на перерисовку
|
|
||||||
std::unordered_map<WorldId_t, std::vector<Pos::GlobalChunk>> ChangedContent_Chunk;
|
|
||||||
std::unordered_map<WorldId_t, std::vector<Pos::GlobalRegion>> ChangedContent_RegionRemove;
|
|
||||||
// Меши
|
|
||||||
std::unordered_map<WorldId_t,
|
|
||||||
std::unordered_map<Pos::GlobalRegion, std::array<ChunkObj_t, 4*4*4>>
|
|
||||||
> ChunkMesh;
|
|
||||||
// Внешний поток
|
|
||||||
std::thread Thread;
|
|
||||||
|
|
||||||
void run();
|
|
||||||
};
|
|
||||||
|
|
||||||
struct VulkanContext {
|
struct VulkanContext {
|
||||||
VK::Vulkan *VkInst;
|
|
||||||
AtlasImage MainTest, LightDummy;
|
AtlasImage MainTest, LightDummy;
|
||||||
Buffer TestQuad;
|
Buffer TestQuad;
|
||||||
std::optional<Buffer> TestVoxel;
|
std::optional<Buffer> TestVoxel;
|
||||||
|
|
||||||
ThreadVertexObj_t ThreadVertexObj;
|
VulkanContext(Vulkan *vkInst)
|
||||||
|
: MainTest(vkInst), LightDummy(vkInst),
|
||||||
VulkanContext(Vulkan* vkInst)
|
TestQuad(vkInst, sizeof(NodeVertexStatic)*6)
|
||||||
: VkInst(vkInst),
|
|
||||||
MainTest(vkInst), LightDummy(vkInst),
|
|
||||||
TestQuad(vkInst, sizeof(NodeVertexStatic)*6*3*2),
|
|
||||||
ThreadVertexObj(vkInst)
|
|
||||||
{}
|
{}
|
||||||
|
|
||||||
~VulkanContext() {
|
|
||||||
}
|
|
||||||
|
|
||||||
void onUpdate();
|
|
||||||
|
|
||||||
void setServerSession(IServerSession* ssession) {
|
|
||||||
ThreadVertexObj.SSession = ssession;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
std::shared_ptr<VulkanContext> VKCTX;
|
std::shared_ptr<VulkanContext> VKCTX;
|
||||||
@@ -308,7 +119,7 @@ class VulkanRenderSession : public IRenderSession, public IVulkanDependent {
|
|||||||
|
|
||||||
// Для отрисовки вокселей
|
// Для отрисовки вокселей
|
||||||
std::shared_ptr<ShaderModule> VoxelShaderVertex, VoxelShaderGeometry, VoxelShaderFragmentOpaque, VoxelShaderFragmentTransparent;
|
std::shared_ptr<ShaderModule> VoxelShaderVertex, VoxelShaderGeometry, VoxelShaderFragmentOpaque, VoxelShaderFragmentTransparent;
|
||||||
VkPipeline
|
VkPipeline
|
||||||
VoxelOpaquePipeline = VK_NULL_HANDLE, // Альфа канал может быть либо 255, либо 0
|
VoxelOpaquePipeline = VK_NULL_HANDLE, // Альфа канал может быть либо 255, либо 0
|
||||||
VoxelTransparentPipeline = VK_NULL_HANDLE; // Допускается полупрозрачность и смешивание
|
VoxelTransparentPipeline = VK_NULL_HANDLE; // Допускается полупрозрачность и смешивание
|
||||||
|
|
||||||
@@ -318,9 +129,11 @@ class VulkanRenderSession : public IRenderSession, public IVulkanDependent {
|
|||||||
NodeStaticOpaquePipeline = VK_NULL_HANDLE,
|
NodeStaticOpaquePipeline = VK_NULL_HANDLE,
|
||||||
NodeStaticTransparentPipeline = VK_NULL_HANDLE;
|
NodeStaticTransparentPipeline = VK_NULL_HANDLE;
|
||||||
|
|
||||||
std::map<AssetsTexture, uint16_t> ServerToAtlas;
|
|
||||||
|
std::map<TextureId_c, uint16_t> ServerToAtlas;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
|
std::unordered_map<WorldId_c, std::unordered_map<Pos::GlobalChunk, std::unique_ptr<Buffer>>> ChunkVoxelMesh;
|
||||||
} External;
|
} External;
|
||||||
|
|
||||||
virtual void free(Vulkan *instance) override;
|
virtual void free(Vulkan *instance) override;
|
||||||
@@ -335,16 +148,22 @@ public:
|
|||||||
|
|
||||||
void setServerSession(IServerSession *serverSession) {
|
void setServerSession(IServerSession *serverSession) {
|
||||||
ServerSession = serverSession;
|
ServerSession = serverSession;
|
||||||
if(VKCTX)
|
|
||||||
VKCTX->setServerSession(serverSession);
|
|
||||||
assert(serverSession);
|
assert(serverSession);
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void onBinaryResourceAdd(std::vector<Hash_t>) override;
|
virtual void onDefTexture(TextureId_c id, std::vector<std::byte> &&info) override;
|
||||||
virtual void onContentDefinesAdd(std::unordered_map<EnumDefContent, std::vector<ResourceId>>) override;
|
virtual void onDefTextureLost(const std::vector<TextureId_c> &&lost) override;
|
||||||
virtual void onContentDefinesLost(std::unordered_map<EnumDefContent, std::vector<ResourceId>>) override;
|
virtual void onDefModel(ModelId_c id, std::vector<std::byte> &&info) override;
|
||||||
virtual void onChunksChange(WorldId_t worldId, const std::unordered_set<Pos::GlobalChunk>& changeOrAddList, const std::unordered_set<Pos::GlobalRegion>& remove) override;
|
virtual void onDefModelLost(const std::vector<ModelId_c> &&lost) override;
|
||||||
virtual void setCameraPos(WorldId_t worldId, Pos::Object pos, glm::quat quat) override;
|
|
||||||
|
virtual void onDefWorldUpdates(const std::vector<DefWorldId_c> &updates) override;
|
||||||
|
virtual void onDefVoxelUpdates(const std::vector<DefVoxelId_c> &updates) override;
|
||||||
|
virtual void onDefNodeUpdates(const std::vector<DefNodeId_c> &updates) override;
|
||||||
|
virtual void onDefPortalUpdates(const std::vector<DefPortalId_c> &updates) override;
|
||||||
|
virtual void onDefEntityUpdates(const std::vector<DefEntityId_c> &updates) override;
|
||||||
|
|
||||||
|
virtual void onChunksChange(WorldId_c worldId, const std::unordered_set<Pos::GlobalChunk> &changeOrAddList, const std::unordered_set<Pos::GlobalChunk> &remove) override;
|
||||||
|
virtual void setCameraPos(WorldId_c worldId, Pos::Object pos, glm::quat quat) override;
|
||||||
|
|
||||||
glm::mat4 calcViewMatrix(glm::quat quat, glm::vec3 camOffset = glm::vec3(0)) {
|
glm::mat4 calcViewMatrix(glm::quat quat, glm::vec3 camOffset = glm::vec3(0)) {
|
||||||
return glm::translate(glm::mat4(quat), camOffset);
|
return glm::translate(glm::mat4(quat), camOffset);
|
||||||
@@ -352,10 +171,8 @@ public:
|
|||||||
|
|
||||||
void beforeDraw();
|
void beforeDraw();
|
||||||
void drawWorld(GlobalTime gTime, float dTime, VkCommandBuffer drawCmd);
|
void drawWorld(GlobalTime gTime, float dTime, VkCommandBuffer drawCmd);
|
||||||
void pushStage(EnumRenderStage stage);
|
|
||||||
|
|
||||||
static std::vector<VoxelVertexPoint> generateMeshForVoxelChunks(const std::vector<VoxelCube>& cubes);
|
static std::vector<VoxelVertexPoint> generateMeshForVoxelChunks(const std::vector<VoxelCube> cubes);
|
||||||
static std::vector<NodeVertexStatic> generateMeshForNodeChunks(const Node* nodes);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void updateDescriptor_MainAtlas();
|
void updateDescriptor_MainAtlas();
|
||||||
|
|||||||
@@ -1,727 +1,171 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "TOSLib.hpp"
|
|
||||||
#include "boost/json/array.hpp"
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <glm/ext.hpp>
|
#include <glm/ext.hpp>
|
||||||
#include <memory>
|
|
||||||
#include <sol/forward.hpp>
|
|
||||||
#include <type_traits>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <vector>
|
|
||||||
#include <boost/json.hpp>
|
|
||||||
#include <boost/container/small_vector.hpp>
|
|
||||||
|
|
||||||
|
|
||||||
namespace LV {
|
namespace LV {
|
||||||
|
|
||||||
namespace js = boost::json;
|
|
||||||
|
|
||||||
namespace Pos {
|
namespace Pos {
|
||||||
|
|
||||||
template<typename T, size_t BitsPerComponent>
|
struct Local4_u {
|
||||||
class BitVec3 {
|
uint8_t X : 2, Y : 2, Z : 2;
|
||||||
static_assert(std::is_integral_v<T>, "T must be an integral type");
|
|
||||||
static_assert(BitsPerComponent > 0, "Bits per component must be at least 1");
|
|
||||||
static constexpr size_t N = 3;
|
|
||||||
|
|
||||||
static constexpr auto getType() {
|
using Key = uint8_t;
|
||||||
constexpr size_t bits = N*BitsPerComponent;
|
operator Key() const {
|
||||||
if constexpr(bits <= 8)
|
return Key(X) | (Key(Y) << 2) | (Key(Z) << 4);
|
||||||
return uint8_t(0);
|
};
|
||||||
else if constexpr(bits <= 16)
|
|
||||||
return uint16_t(0);
|
|
||||||
else if constexpr(bits <= 32)
|
|
||||||
return uint32_t(0);
|
|
||||||
else if constexpr(bits <= 64)
|
|
||||||
return uint64_t(0);
|
|
||||||
else {
|
|
||||||
static_assert("Нет подходящего хранилища");
|
|
||||||
return uint8_t(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public:
|
|
||||||
using Pack = decltype(getType());
|
|
||||||
using Type = T;
|
|
||||||
using value_type = Type;
|
|
||||||
|
|
||||||
T x : BitsPerComponent, y : BitsPerComponent, z : BitsPerComponent;
|
|
||||||
|
|
||||||
public:
|
|
||||||
BitVec3() = default;
|
|
||||||
BitVec3(T value)
|
|
||||||
: x(value), y(value), z(value)
|
|
||||||
{}
|
|
||||||
BitVec3(const T x, const T y, const T z)
|
|
||||||
: x(x), y(y), z(z)
|
|
||||||
{}
|
|
||||||
template<typename vT, glm::qualifier vQ>
|
|
||||||
BitVec3(const glm::vec<3, vT, vQ> vec)
|
|
||||||
: x(vec.x), y(vec.y), z(vec.z)
|
|
||||||
{}
|
|
||||||
BitVec3(const BitVec3&) = default;
|
|
||||||
BitVec3(BitVec3&&) = default;
|
|
||||||
|
|
||||||
BitVec3& operator=(const BitVec3&) = default;
|
|
||||||
BitVec3& operator=(BitVec3&&) = default;
|
|
||||||
|
|
||||||
void set(size_t index, const T value) {
|
|
||||||
assert(index < N);
|
|
||||||
if(index == 0)
|
|
||||||
x = value;
|
|
||||||
else if(index == 1)
|
|
||||||
y = value;
|
|
||||||
else if(index == 2)
|
|
||||||
z = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
const T get(size_t index) const {
|
|
||||||
assert(index < N);
|
|
||||||
if(index == 0)
|
|
||||||
return x;
|
|
||||||
else if(index == 1)
|
|
||||||
return y;
|
|
||||||
else if(index == 2)
|
|
||||||
return z;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const T operator[](size_t index) const {
|
|
||||||
return get(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
Pack pack() const requires (N*BitsPerComponent <= 64) {
|
|
||||||
Pack out = 0;
|
|
||||||
using U = std::make_unsigned_t<T>;
|
|
||||||
|
|
||||||
for(size_t iter = 0; iter < N; iter++) {
|
|
||||||
out |= Pack(U(get(iter)) & U((Pack(1) << BitsPerComponent)-1)) << BitsPerComponent*iter;
|
|
||||||
}
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
BitVec3 unpack(const Pack pack) requires (N*BitsPerComponent <= 64) {
|
|
||||||
using U = std::make_unsigned_t<T>;
|
|
||||||
|
|
||||||
for(size_t iter = 0; iter < N; iter++) {
|
|
||||||
set(iter, T(U((pack >> BitsPerComponent*iter) & U((Pack(1) << BitsPerComponent)-1))));
|
|
||||||
}
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto operator<=>(const BitVec3&) const = default;
|
|
||||||
|
|
||||||
template<typename T2, size_t BitsPerComponent2>
|
|
||||||
operator BitVec3<T2, BitsPerComponent2>() const {
|
|
||||||
BitVec3<T2, BitsPerComponent2> out;
|
|
||||||
for(size_t iter = 0; iter < N; iter++) {
|
|
||||||
out.set(iter, T2(std::make_unsigned_t<T2>(std::make_unsigned_t<T>(get(iter)))));
|
|
||||||
}
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
template<typename vT, glm::qualifier vQ>
|
|
||||||
operator glm::vec<3, vT, vQ>() const {
|
|
||||||
return {x, y, z};
|
|
||||||
}
|
|
||||||
|
|
||||||
BitVec3 operator+(const BitVec3 &other) const {
|
|
||||||
BitVec3 out;
|
|
||||||
|
|
||||||
for(size_t iter = 0; iter < N; iter++)
|
|
||||||
out.set(iter, get(iter) + other[iter]);
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
BitVec3& operator+=(const BitVec3 &other) {
|
|
||||||
for(size_t iter = 0; iter < N; iter++)
|
|
||||||
set(iter, get(iter) + other[iter]);
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
BitVec3 operator+(const T value) const {
|
|
||||||
BitVec3 out;
|
|
||||||
|
|
||||||
for(size_t iter = 0; iter < N; iter++)
|
|
||||||
out.set(iter, get(iter) + value);
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
BitVec3& operator+=(const T value) {
|
|
||||||
for(size_t iter = 0; iter < N; iter++)
|
|
||||||
set(iter, get(iter) + value);
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
BitVec3 operator-(const BitVec3 &other) const {
|
|
||||||
BitVec3 out;
|
|
||||||
|
|
||||||
for(size_t iter = 0; iter < N; iter++)
|
|
||||||
out.set(iter, get(iter) - other[iter]);
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
BitVec3& operator-=(const BitVec3 &other) {
|
|
||||||
for(size_t iter = 0; iter < N; iter++)
|
|
||||||
set(iter, get(iter) - other[iter]);
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
BitVec3 operator-(const T value) const {
|
|
||||||
BitVec3 out;
|
|
||||||
|
|
||||||
for(size_t iter = 0; iter < N; iter++)
|
|
||||||
out.set(iter, get(iter) - value);
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
BitVec3& operator-=(const T value) {
|
|
||||||
for(size_t iter = 0; iter < N; iter++)
|
|
||||||
set(iter, get(iter) - value);
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
BitVec3 operator*(const BitVec3 &other) const {
|
|
||||||
BitVec3 out;
|
|
||||||
|
|
||||||
for(size_t iter = 0; iter < N; iter++)
|
|
||||||
out.set(iter, get(iter) * other[iter]);
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
BitVec3& operator*=(const BitVec3 &other) {
|
|
||||||
for(size_t iter = 0; iter < N; iter++)
|
|
||||||
set(iter, get(iter) * other[iter]);
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
BitVec3 operator*(const T value) const {
|
|
||||||
BitVec3 out;
|
|
||||||
|
|
||||||
for(size_t iter = 0; iter < N; iter++)
|
|
||||||
out.set(iter, get(iter) * value);
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
BitVec3& operator*=(const T value) {
|
|
||||||
for(size_t iter = 0; iter < N; iter++)
|
|
||||||
set(iter, get(iter) * value);
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
BitVec3 operator/(const BitVec3 &other) const {
|
|
||||||
BitVec3 out;
|
|
||||||
|
|
||||||
for(size_t iter = 0; iter < N; iter++)
|
|
||||||
out.set(iter, get(iter) / other[iter]);
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
BitVec3& operator/=(const BitVec3 &other) {
|
|
||||||
for(size_t iter = 0; iter < N; iter++)
|
|
||||||
set(iter, get(iter) / other[iter]);
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
BitVec3 operator/(const T value) const {
|
|
||||||
BitVec3 out;
|
|
||||||
|
|
||||||
for(size_t iter = 0; iter < N; iter++)
|
|
||||||
out.set(iter, get(iter) / value);
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
BitVec3& operator/=(const T value) {
|
|
||||||
for(size_t iter = 0; iter < N; iter++)
|
|
||||||
set(iter, get(iter) / value);
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
BitVec3 operator>>(const auto offset) const {
|
|
||||||
BitVec3 out;
|
|
||||||
|
|
||||||
for(size_t iter = 0; iter < N; iter++)
|
|
||||||
out.set(iter, get(iter) >> offset);
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
BitVec3& operator>>=(const auto offset) {
|
|
||||||
for(size_t iter = 0; iter < N; iter++)
|
|
||||||
set(iter, get(iter) >> offset);
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
BitVec3 operator<<(const auto offset) const {
|
|
||||||
BitVec3 out;
|
|
||||||
|
|
||||||
for(size_t iter = 0; iter < N; iter++)
|
|
||||||
out.set(iter, get(iter) << offset);
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
BitVec3& operator<<=(const auto offset) {
|
|
||||||
for(size_t iter = 0; iter < N; iter++)
|
|
||||||
set(iter, get(iter) << offset);
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
BitVec3 operator|(const BitVec3 other) const {
|
|
||||||
BitVec3 out;
|
|
||||||
|
|
||||||
for(size_t iter = 0; iter < N; iter++)
|
|
||||||
out.set(iter, get(iter) | other[iter]);
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
BitVec3& operator|=(const BitVec3 other) {
|
|
||||||
for(size_t iter = 0; iter < N; iter++)
|
|
||||||
set(iter, get(iter) | other[iter]);
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
BitVec3 operator|(const T value) const {
|
|
||||||
BitVec3 out;
|
|
||||||
|
|
||||||
for(size_t iter = 0; iter < N; iter++)
|
|
||||||
out.set(iter, get(iter) | value);
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
BitVec3& operator|=(const T value) {
|
|
||||||
for(size_t iter = 0; iter < N; iter++)
|
|
||||||
set(iter, get(iter) | value);
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
BitVec3 operator&(const BitVec3 other) const {
|
|
||||||
BitVec3 out;
|
|
||||||
|
|
||||||
for(size_t iter = 0; iter < N; iter++)
|
|
||||||
out.set(iter, get(iter) & other[iter]);
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
BitVec3& operator&=(const BitVec3 other) {
|
|
||||||
for(size_t iter = 0; iter < N; iter++)
|
|
||||||
set(iter, get(iter) & other[iter]);
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
BitVec3 operator&(const T value) const {
|
|
||||||
BitVec3 out;
|
|
||||||
|
|
||||||
for(size_t iter = 0; iter < N; iter++)
|
|
||||||
out.set(iter, get(iter) & value);
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
BitVec3& operator&=(const T value) {
|
|
||||||
for(size_t iter = 0; iter < N; iter++)
|
|
||||||
set(iter, get(iter) & value);
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
static constexpr size_t length() { return N; }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
using bvec4i = BitVec3<int8_t, 2>;
|
struct Local16_u {
|
||||||
using bvec4u = BitVec3<uint8_t, 2>;
|
uint8_t X : 4, Y : 4, Z : 4;
|
||||||
using bvec16i = BitVec3<int8_t, 4>;
|
|
||||||
using bvec16u = BitVec3<uint8_t, 4>;
|
|
||||||
using bvec64i = BitVec3<int8_t, 6>;
|
|
||||||
using bvec64u = BitVec3<uint8_t, 6>;
|
|
||||||
using bvec256i = BitVec3<int8_t, 8>;
|
|
||||||
using bvec256u = BitVec3<uint8_t, 8>;
|
|
||||||
using bvec1024i = BitVec3<int16_t, 10>;
|
|
||||||
using bvec1024u = BitVec3<uint16_t, 10>;
|
|
||||||
using bvec4096i = BitVec3<int16_t, 12>;
|
|
||||||
using bvec4096u = BitVec3<uint16_t, 12>;
|
|
||||||
|
|
||||||
using GlobalVoxel = BitVec3<int32_t, 24>;
|
using Key = uint16_t;
|
||||||
using GlobalNode = BitVec3<int32_t, 20>;
|
operator Key() const {
|
||||||
using GlobalChunk = BitVec3<int16_t, 16>;
|
return Key(X) | (Key(Y) << 4) | (Key(Z) << 8);
|
||||||
using GlobalRegion = BitVec3<int16_t, 14>;
|
};
|
||||||
using Object = BitVec3<int32_t, 32>;
|
|
||||||
|
Local16_u& operator=(const Key &key) {
|
||||||
|
X = key & 0xf;
|
||||||
|
Y = (key >> 4) & 0xf;
|
||||||
|
Z = (key >> 8) & 0xf;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Local4_u left() const { return Local4_u{uint8_t(uint16_t(X) >> 2), uint8_t(uint16_t(Y) >> 2), uint8_t(uint16_t(Z) >> 2)}; }
|
||||||
|
Local4_u right() const { return Local4_u{uint8_t(uint16_t(X) & 0b11), uint8_t(uint16_t(Y) & 0b11), uint8_t(uint16_t(Z) & 0b11)}; }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Local16 {
|
||||||
|
int8_t X : 4, Y : 4, Z : 4;
|
||||||
|
|
||||||
|
using Key = uint16_t;
|
||||||
|
operator Key() const {
|
||||||
|
return Key(uint8_t(X)) | (Key(uint8_t(Y) << 4)) | (Key(uint8_t(Z)) << 8);
|
||||||
|
};
|
||||||
|
|
||||||
|
Local4_u left() const { return Local4_u{uint8_t(uint16_t(X) >> 2), uint8_t(uint16_t(Y) >> 2), uint8_t(uint16_t(Z) >> 2)}; }
|
||||||
|
Local4_u right() const { return Local4_u{uint8_t(uint16_t(X) & 0b11), uint8_t(uint16_t(Y) & 0b11), uint8_t(uint16_t(Z) & 0b11)}; }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Local256 {
|
||||||
|
int8_t X : 8, Y : 8, Z : 8;
|
||||||
|
|
||||||
|
auto operator<=>(const Local256&) const = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Local256_u {
|
||||||
|
uint8_t X : 8, Y : 8, Z : 8;
|
||||||
|
|
||||||
|
auto operator<=>(const Local256_u&) const = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Local4096 {
|
||||||
|
int16_t X : 12, Y : 12, Z : 12;
|
||||||
|
|
||||||
|
auto operator<=>(const Local4096&) const = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Local4096_u {
|
||||||
|
uint16_t X : 12, Y : 12, Z : 12;
|
||||||
|
|
||||||
|
auto operator<=>(const Local4096_u&) const = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GlobalVoxel {
|
||||||
|
int32_t X : 24, Y : 24, Z : 24;
|
||||||
|
|
||||||
|
auto operator<=>(const GlobalVoxel&) const = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GlobalNode {
|
||||||
|
int32_t X : 20, Y : 20, Z : 20;
|
||||||
|
|
||||||
|
using Key = uint64_t;
|
||||||
|
operator Key() const {
|
||||||
|
return Key(uint32_t(X)) | (Key(uint32_t(Y) << 20)) | (Key(uint32_t(Z)) << 40);
|
||||||
|
};
|
||||||
|
|
||||||
|
auto operator<=>(const GlobalNode&) const = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GlobalChunk {
|
||||||
|
int16_t X, Y, Z;
|
||||||
|
|
||||||
|
using Key = uint64_t;
|
||||||
|
operator Key() const {
|
||||||
|
return Key(uint16_t(X)) | (Key(uint16_t(Y)) << 16) | (Key(uint16_t(Z)) << 32);
|
||||||
|
};
|
||||||
|
|
||||||
|
operator glm::i16vec3() const {
|
||||||
|
return {X, Y, Z};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto operator<=>(const GlobalChunk&) const = default;
|
||||||
|
|
||||||
|
Local16_u toLocal() const {
|
||||||
|
return Local16_u(X & 0xf, Y & 0xf, Z & 0xf);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GlobalRegion {
|
||||||
|
int16_t X : 12, Y : 12, Z : 12;
|
||||||
|
|
||||||
|
using Key = uint64_t;
|
||||||
|
operator Key() const {
|
||||||
|
return Key(uint16_t(X)) | (Key(uint16_t(Y) << 12)) | (Key(uint16_t(Z)) << 24);
|
||||||
|
};
|
||||||
|
|
||||||
|
auto operator<=>(const GlobalRegion&) const = default;
|
||||||
|
|
||||||
|
void fromChunk(const GlobalChunk &posChunk) {
|
||||||
|
X = posChunk.X >> 4;
|
||||||
|
Y = posChunk.Y >> 4;
|
||||||
|
Z = posChunk.Z >> 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
GlobalChunk toChunk() const {
|
||||||
|
return GlobalChunk(uint16_t(X) << 4, uint16_t(Y) << 4, uint16_t(Z) << 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
GlobalChunk toChunk(const Pos::Local16_u &posLocal) const {
|
||||||
|
return GlobalChunk(
|
||||||
|
(uint16_t(X) << 4) | posLocal.X,
|
||||||
|
(uint16_t(Y) << 4) | posLocal.Y,
|
||||||
|
(uint16_t(Z) << 4) | posLocal.Z
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
using Object = glm::i32vec3;
|
||||||
|
|
||||||
struct Object_t {
|
struct Object_t {
|
||||||
// Позиции объектов целочисленные, BS единиц это один метр
|
// Позиции объектов целочисленные, BS единиц это один метр
|
||||||
static constexpr int32_t BS = 4096, BS_Bit = 12;
|
static constexpr int32_t BS = 4096, BS_Bit = 12;
|
||||||
|
|
||||||
static glm::vec3 asFloatVec(const Object &obj) { return glm::vec3(float(obj[0])/float(BS), float(obj[1])/float(BS), float(obj[2])/float(BS)); }
|
static glm::vec3 asFloatVec(Object &obj) { return glm::vec3(float(obj.x)/float(BS), float(obj.y)/float(BS), float(obj.z)/float(BS)); }
|
||||||
static GlobalNode asNodePos(const Object &obj) { return (GlobalNode) (obj >> BS_Bit); }
|
static GlobalNode asNodePos(Object &obj) { return GlobalNode(obj.x >> BS_Bit, obj.y >> BS_Bit, obj.z >> BS_Bit); }
|
||||||
static GlobalChunk asChunkPos(const Object &obj) { return (GlobalChunk) (obj >> BS_Bit >> 4); }
|
static GlobalChunk asChunkPos(Object &obj) { return GlobalChunk(obj.x >> BS_Bit >> 4, obj.y >> BS_Bit >> 4, obj.z >> BS_Bit >> 4); }
|
||||||
static GlobalRegion asRegionsPos(const Object &obj) { return (GlobalRegion) (obj >> BS_Bit >> 6); }
|
static GlobalChunk asRegionsPos(Object &obj) { return GlobalChunk(obj.x >> BS_Bit >> 8, obj.y >> BS_Bit >> 8, obj.z >> BS_Bit >> 8); }
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct LightPrism {
|
||||||
using ResourceId = uint32_t;
|
uint8_t R : 2, G : 2, B : 2;
|
||||||
|
|
||||||
/*
|
|
||||||
Объекты, собранные из папки assets или зарегистрированные модами.
|
|
||||||
Клиент получает полную информацию о таких объектах и при надобности
|
|
||||||
запрашивает получение файла.
|
|
||||||
Id -> Key + SHA256
|
|
||||||
|
|
||||||
Если объекты удаляются, то сторона клиента об этом не уведомляется
|
|
||||||
*/
|
|
||||||
enum class EnumAssets {
|
|
||||||
Nodestate, Particle, Animation, Model, Texture, Sound, Font, MAX_ENUM
|
|
||||||
};
|
};
|
||||||
|
|
||||||
using AssetsNodestate = ResourceId;
|
// Идентификаторы на стороне клиента
|
||||||
using AssetsParticle = ResourceId;
|
using TextureId_c = uint16_t;
|
||||||
using AssetsAnimation = ResourceId;
|
using SoundId_c = uint16_t;
|
||||||
using AssetsModel = ResourceId;
|
using ModelId_c = uint16_t;
|
||||||
using AssetsTexture = ResourceId;
|
|
||||||
using AssetsSound = ResourceId;
|
|
||||||
using AssetsFont = ResourceId;
|
|
||||||
|
|
||||||
using BinaryResource = std::shared_ptr<const std::u8string>;
|
using DefWorldId_c = uint8_t;
|
||||||
|
using DefVoxelId_c = uint16_t;
|
||||||
/*
|
using DefNodeId_c = uint16_t;
|
||||||
Определения контента, доставляются клиентам сразу
|
using DefPortalId_c = uint8_t;
|
||||||
*/
|
using WorldId_c = uint8_t;
|
||||||
enum class EnumDefContent {
|
using PortalId_c = uint8_t;
|
||||||
Voxel, Node, World, Portal, Entity, Item, MAX_ENUM
|
using DefEntityId_c = uint16_t;
|
||||||
};
|
using EntityId_c = uint16_t;
|
||||||
|
|
||||||
using DefVoxelId = ResourceId;
|
|
||||||
using DefNodeId = ResourceId;
|
|
||||||
using DefWorldId = ResourceId;
|
|
||||||
using DefPortalId = ResourceId;
|
|
||||||
using DefEntityId = ResourceId;
|
|
||||||
using DefItemId = ResourceId;
|
|
||||||
|
|
||||||
/*
|
|
||||||
Контент, основанный на определениях.
|
|
||||||
Отдельные свойства могут менятся в самих объектах
|
|
||||||
*/
|
|
||||||
|
|
||||||
using WorldId_t = ResourceId;
|
|
||||||
|
|
||||||
// struct LightPrism {
|
|
||||||
// uint8_t R : 2, G : 2, B : 2;
|
|
||||||
// };
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
struct VoxelCube {
|
|
||||||
union {
|
|
||||||
struct {
|
|
||||||
DefVoxelId VoxelId : 24, Meta : 8;
|
|
||||||
};
|
|
||||||
|
|
||||||
DefVoxelId Data = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
Pos::bvec256u Pos, Size; // Размер+1, 0 это единичный размер
|
|
||||||
|
|
||||||
auto operator<=>(const VoxelCube& other) const {
|
|
||||||
if (auto cmp = Pos <=> other.Pos; cmp != 0)
|
|
||||||
return cmp;
|
|
||||||
|
|
||||||
if (auto cmp = Size <=> other.Size; cmp != 0)
|
|
||||||
return cmp;
|
|
||||||
|
|
||||||
return Data <=> other.Data;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool operator==(const VoxelCube& other) const {
|
|
||||||
return Pos == other.Pos && Size == other.Size && Data == other.Data;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct CompressedVoxels {
|
|
||||||
std::u8string Compressed;
|
|
||||||
// Уникальный сортированный список идентификаторов вокселей
|
|
||||||
std::vector<DefVoxelId> Defines;
|
|
||||||
};
|
|
||||||
|
|
||||||
CompressedVoxels compressVoxels(const std::vector<VoxelCube>& voxels, bool fast = true);
|
|
||||||
std::vector<VoxelCube> unCompressVoxels(const std::u8string& compressed);
|
|
||||||
|
|
||||||
struct Node {
|
|
||||||
union {
|
|
||||||
struct {
|
|
||||||
DefNodeId NodeId : 24, Meta : 8;
|
|
||||||
};
|
|
||||||
DefNodeId Data;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
struct CompressedNodes {
|
|
||||||
std::u8string Compressed;
|
|
||||||
// Уникальный сортированный список идентификаторов нод
|
|
||||||
std::vector<DefNodeId> Defines;
|
|
||||||
};
|
|
||||||
|
|
||||||
CompressedNodes compressNodes(const Node* nodes, bool fast = true);
|
|
||||||
void unCompressNodes(const std::u8string& compressed, Node* ptr);
|
|
||||||
|
|
||||||
std::u8string compressLinear(const std::u8string& data);
|
|
||||||
std::u8string unCompressLinear(const std::u8string& data);
|
|
||||||
|
|
||||||
enum struct TexturePipelineCMD : uint8_t {
|
|
||||||
Texture, // Указание текстуры
|
|
||||||
Combine, // Комбинирование
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
struct NodestateEntry {
|
|
||||||
std::string Name;
|
|
||||||
int Variability = 0; // Количество возможный значений состояния
|
|
||||||
std::vector<std::string> ValueNames; // Имена состояний, если имеются
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
Хранит распаршенное определение состояний нод.
|
|
||||||
Не привязано ни к какому окружению.
|
|
||||||
*/
|
|
||||||
struct PreparedNodeState {
|
|
||||||
enum class Op {
|
|
||||||
Add, Sub, Mul, Div, Mod,
|
|
||||||
LT, LE, GT, GE, EQ, NE,
|
|
||||||
And, Or,
|
|
||||||
Pos, Neg, Not
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Node {
|
|
||||||
struct Num { int32_t v; };
|
|
||||||
struct Var { std::string name; };
|
|
||||||
struct Unary { Op op; uint16_t rhs; };
|
|
||||||
struct Binary { Op op; uint16_t lhs, rhs; };
|
|
||||||
std::variant<Num, Var, Unary, Binary> v;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Transformation {
|
|
||||||
enum EnumTransform {
|
|
||||||
MoveX, MoveY, MoveZ,
|
|
||||||
RotateX, RotateY, RotateZ,
|
|
||||||
MAX_ENUM
|
|
||||||
} Op;
|
|
||||||
|
|
||||||
float Value;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Model {
|
|
||||||
uint16_t Id;
|
|
||||||
bool UVLock = false;
|
|
||||||
std::vector<Transformation> Transforms;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct VectorModel {
|
|
||||||
std::vector<Model> Models;
|
|
||||||
bool UVLock = false;
|
|
||||||
// Может добавить возможность использовать переменную рандома в трансформациях?
|
|
||||||
std::vector<Transformation> Transforms;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Локальный идентификатор в именной ресурс
|
|
||||||
std::vector<std::pair<std::string, std::string>> ResourceToLocalId;
|
|
||||||
// Ноды выражений
|
|
||||||
std::vector<Node> Nodes;
|
|
||||||
// Условия -> вариации модели + веса
|
|
||||||
boost::container::small_vector<
|
|
||||||
std::pair<uint16_t,
|
|
||||||
boost::container::small_vector<
|
|
||||||
std::pair<float, std::variant<Model, VectorModel>>,
|
|
||||||
1
|
|
||||||
>
|
|
||||||
>
|
|
||||||
, 1> Routes;
|
|
||||||
|
|
||||||
PreparedNodeState(const std::string_view modid, const js::object& profile);
|
|
||||||
PreparedNodeState(const std::string_view modid, const sol::table& profile);
|
|
||||||
PreparedNodeState(const std::u8string& data);
|
|
||||||
|
|
||||||
PreparedNodeState() = default;
|
|
||||||
PreparedNodeState(const PreparedNodeState&) = default;
|
|
||||||
PreparedNodeState(PreparedNodeState&&) = default;
|
|
||||||
|
|
||||||
PreparedNodeState& operator=(const PreparedNodeState&) = default;
|
|
||||||
PreparedNodeState& operator=(PreparedNodeState&&) = default;
|
|
||||||
|
|
||||||
// Пишет в сжатый двоичный формат
|
|
||||||
std::u8string dump() const;
|
|
||||||
|
|
||||||
bool hasVariability() const {
|
|
||||||
return HasVariability;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool HasVariability = false;
|
|
||||||
|
|
||||||
uint16_t parseCondition(const std::string_view condition);
|
|
||||||
std::pair<float, std::variant<Model, VectorModel>> parseModel(const std::string_view modid, const js::object& obj);
|
|
||||||
std::vector<Transformation> parseTransormations(const js::array& arr);
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
Парсит json модель
|
|
||||||
*/
|
|
||||||
struct PreparedModel {
|
|
||||||
enum class EnumGuiLight {
|
|
||||||
Default
|
|
||||||
};
|
|
||||||
|
|
||||||
std::optional<EnumGuiLight> GuiLight = EnumGuiLight::Default;
|
|
||||||
std::optional<bool> AmbientOcclusion = false;
|
|
||||||
|
|
||||||
struct FullTransformation {
|
|
||||||
glm::vec3
|
|
||||||
Rotation = glm::vec3(0),
|
|
||||||
Translation = glm::vec3(0),
|
|
||||||
Scale = glm::vec3(1);
|
|
||||||
};
|
|
||||||
|
|
||||||
std::unordered_map<std::string, FullTransformation> Display;
|
|
||||||
std::unordered_map<std::string, std::pair<std::string, std::string>> Textures;
|
|
||||||
|
|
||||||
struct Cuboid {
|
|
||||||
bool Shade;
|
|
||||||
glm::vec3 From, To;
|
|
||||||
|
|
||||||
enum class EnumFace {
|
|
||||||
Down, Up, North, South, West, East
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Face {
|
|
||||||
glm::vec4 UV;
|
|
||||||
std::string Texture;
|
|
||||||
std::optional<EnumFace> Cullface;
|
|
||||||
int TintIndex = -1;
|
|
||||||
int16_t Rotation = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::unordered_map<EnumFace, Face> Faces;
|
|
||||||
|
|
||||||
struct Transformation {
|
|
||||||
enum EnumTransform {
|
|
||||||
MoveX, MoveY, MoveZ,
|
|
||||||
RotateX, RotateY, RotateZ,
|
|
||||||
MAX_ENUM
|
|
||||||
} Op;
|
|
||||||
|
|
||||||
float Value;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::vector<Transformation> Transformations;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::vector<Cuboid> Cuboids;
|
|
||||||
|
|
||||||
struct SubModel {
|
|
||||||
std::string Domain, Key;
|
|
||||||
std::optional<uint16_t> Scene;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::vector<SubModel> SubModels;
|
|
||||||
|
|
||||||
// Json
|
|
||||||
PreparedModel(const std::string_view modid, const js::object& profile);
|
|
||||||
PreparedModel(const std::string_view modid, const sol::table& profile);
|
|
||||||
PreparedModel(const std::u8string& data);
|
|
||||||
|
|
||||||
PreparedModel() = default;
|
|
||||||
PreparedModel(const PreparedModel&) = default;
|
|
||||||
PreparedModel(PreparedModel&&) = default;
|
|
||||||
|
|
||||||
PreparedModel& operator=(const PreparedModel&) = default;
|
|
||||||
PreparedModel& operator=(PreparedModel&&) = default;
|
|
||||||
|
|
||||||
// Пишет в сжатый двоичный формат
|
|
||||||
std::u8string dump() const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool load(const std::u8string& data) noexcept;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct TexturePipeline {
|
|
||||||
std::vector<AssetsTexture> BinTextures;
|
|
||||||
std::u8string Pipeline;
|
|
||||||
};
|
|
||||||
|
|
||||||
using Hash_t = std::array<uint8_t, 32>;
|
|
||||||
|
|
||||||
inline std::pair<std::string, std::string> parseDomainKey(const std::string& value, const std::string_view defaultDomain = "core") {
|
|
||||||
auto regResult = TOS::Str::match(value, "(?:([\\w\\d_]+):)?([\\w\\d_]+)");
|
|
||||||
if(!regResult)
|
|
||||||
MAKE_ERROR("Недействительный домен:ключ");
|
|
||||||
|
|
||||||
if(regResult->at(1)) {
|
|
||||||
return std::pair<std::string, std::string>{*regResult->at(1), *regResult->at(2)};
|
|
||||||
} else {
|
|
||||||
return std::pair<std::string, std::string>{defaultDomain, *regResult->at(2)};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
struct Resource {
|
|
||||||
private:
|
|
||||||
struct InlineMMap;
|
|
||||||
struct InlinePtr;
|
|
||||||
|
|
||||||
std::shared_ptr<std::variant<InlineMMap, InlinePtr>> In;
|
|
||||||
|
|
||||||
public:
|
|
||||||
Resource() = default;
|
|
||||||
Resource(std::filesystem::path path);
|
|
||||||
Resource(const uint8_t* data, size_t size);
|
|
||||||
Resource(const std::u8string& data);
|
|
||||||
Resource(std::u8string&& data);
|
|
||||||
|
|
||||||
Resource(const Resource&) = default;
|
|
||||||
Resource(Resource&&) = default;
|
|
||||||
Resource& operator=(const Resource&) = default;
|
|
||||||
Resource& operator=(Resource&&) = default;
|
|
||||||
auto operator<=>(const Resource&) const;
|
|
||||||
|
|
||||||
const std::byte* data() const;
|
|
||||||
size_t size() const;
|
|
||||||
Hash_t hash() const;
|
|
||||||
|
|
||||||
Resource convertToMem() const;
|
|
||||||
|
|
||||||
operator bool() const {
|
|
||||||
return (bool) In;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -730,35 +174,12 @@ public:
|
|||||||
|
|
||||||
namespace std {
|
namespace std {
|
||||||
|
|
||||||
template<typename T, size_t BitsPerComponent>
|
#define hash_for_pos(type) template <> struct hash<LV::Pos::type> { std::size_t operator()(const LV::Pos::type& obj) const { return std::hash<LV::Pos::type::Key>()((LV::Pos::type::Key) obj); } };
|
||||||
struct hash<LV::Pos::BitVec3<T, BitsPerComponent>> {
|
hash_for_pos(Local4_u)
|
||||||
std::size_t operator()(const LV::Pos::BitVec3<T, BitsPerComponent>& obj) const {
|
hash_for_pos(Local16_u)
|
||||||
std::size_t result = 0;
|
hash_for_pos(Local16)
|
||||||
constexpr std::size_t seed = 0x9E3779B9;
|
hash_for_pos(GlobalChunk)
|
||||||
|
hash_for_pos(GlobalRegion)
|
||||||
for (size_t i = 0; i < 3; ++i) {
|
#undef hash_for_pos
|
||||||
T value = obj[i];
|
|
||||||
|
|
||||||
std::hash<T> hasher;
|
|
||||||
std::size_t h = hasher(value);
|
|
||||||
|
|
||||||
result ^= h + seed + (result << 6) + (result >> 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
template <>
|
|
||||||
struct hash<LV::Hash_t> {
|
|
||||||
std::size_t operator()(const LV::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;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "TOSLib.hpp"
|
||||||
|
#include "boost/asio/async_result.hpp"
|
||||||
|
#include "boost/asio/deadline_timer.hpp"
|
||||||
|
#include "boost/date_time/posix_time/posix_time_duration.hpp"
|
||||||
|
#include <atomic>
|
||||||
#include <boost/asio.hpp>
|
#include <boost/asio.hpp>
|
||||||
#include <boost/asio/any_io_executor.hpp>
|
#include <boost/asio/impl/execution_context.hpp>
|
||||||
#include <boost/asio/deadline_timer.hpp>
|
|
||||||
#include <boost/asio/io_context.hpp>
|
|
||||||
#include <boost/asio/this_coro.hpp>
|
|
||||||
#include <boost/asio/use_awaitable.hpp>
|
|
||||||
#include <boost/date_time/posix_time/ptime.hpp>
|
#include <boost/date_time/posix_time/ptime.hpp>
|
||||||
#include <boost/thread.hpp>
|
#include <boost/thread.hpp>
|
||||||
#include <boost/asio/experimental/awaitable_operators.hpp>
|
#include <boost/asio/experimental/awaitable_operators.hpp>
|
||||||
|
#include <boost/lockfree/queue.hpp>
|
||||||
|
|
||||||
|
|
||||||
namespace LV {
|
namespace LV {
|
||||||
@@ -20,152 +22,4 @@ using tcp = asio::ip::tcp;
|
|||||||
template<typename T = void>
|
template<typename T = void>
|
||||||
using coro = asio::awaitable<T>;
|
using coro = asio::awaitable<T>;
|
||||||
|
|
||||||
|
|
||||||
class AsyncObject {
|
|
||||||
protected:
|
|
||||||
asio::io_context &IOC;
|
|
||||||
asio::deadline_timer WorkDeadline;
|
|
||||||
|
|
||||||
public:
|
|
||||||
AsyncObject(asio::io_context &ioc)
|
|
||||||
: IOC(ioc), WorkDeadline(ioc, boost::posix_time::pos_infin)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
inline asio::io_context& EXEC()
|
|
||||||
{
|
|
||||||
return IOC;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
|
||||||
template<typename Coroutine>
|
|
||||||
void co_spawn(Coroutine &&coroutine) {
|
|
||||||
asio::co_spawn(IOC, WorkDeadline.async_wait(asio::use_awaitable) || std::move(coroutine), asio::detached);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
template<typename ValueType>
|
|
||||||
class AsyncAtomic : public AsyncObject {
|
|
||||||
protected:
|
|
||||||
asio::deadline_timer Deadline;
|
|
||||||
ValueType Value;
|
|
||||||
boost::mutex Mtx;
|
|
||||||
|
|
||||||
public:
|
|
||||||
AsyncAtomic(asio::io_context &ioc, ValueType &&value)
|
|
||||||
: AsyncObject(ioc), Deadline(ioc), Value(std::move(value))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncAtomic& operator=(ValueType &&value) {
|
|
||||||
boost::unique_lock lock(Mtx);
|
|
||||||
Value = std::move(value);
|
|
||||||
Deadline.expires_from_now(boost::posix_time::pos_infin);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
operator ValueType() const {
|
|
||||||
return Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
ValueType operator*() const {
|
|
||||||
return Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncAtomic& operator++() {
|
|
||||||
boost::unique_lock lock(Mtx);
|
|
||||||
Value--;
|
|
||||||
Deadline.expires_from_now(boost::posix_time::pos_infin);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncAtomic& operator--() {
|
|
||||||
boost::unique_lock lock(Mtx);
|
|
||||||
Value--;
|
|
||||||
Deadline.expires_from_now(boost::posix_time::pos_infin);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncAtomic& operator+=(ValueType value) {
|
|
||||||
boost::unique_lock lock(Mtx);
|
|
||||||
Value += value;
|
|
||||||
Deadline.expires_from_now(boost::posix_time::pos_infin);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncAtomic& operator-=(ValueType value) {
|
|
||||||
boost::unique_lock lock(Mtx);
|
|
||||||
Value -= value;
|
|
||||||
Deadline.expires_from_now(boost::posix_time::pos_infin);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
void wait(ValueType oldValue) {
|
|
||||||
while(true) {
|
|
||||||
if(oldValue != Value)
|
|
||||||
return;
|
|
||||||
|
|
||||||
boost::unique_lock lock(Mtx);
|
|
||||||
|
|
||||||
if(oldValue != Value)
|
|
||||||
return;
|
|
||||||
|
|
||||||
std::atomic_bool flag = false;
|
|
||||||
Deadline.async_wait([&](boost::system::error_code errc) { flag.store(true); flag.notify_all(); });
|
|
||||||
lock.unlock();
|
|
||||||
flag.wait(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void await(ValueType needValue) {
|
|
||||||
while(true) {
|
|
||||||
if(needValue == Value)
|
|
||||||
return;
|
|
||||||
|
|
||||||
boost::unique_lock lock(Mtx);
|
|
||||||
|
|
||||||
if(needValue == Value)
|
|
||||||
return;
|
|
||||||
|
|
||||||
std::atomic_bool flag = false;
|
|
||||||
Deadline.async_wait([&](boost::system::error_code errc) { flag.store(true); flag.notify_all(); });
|
|
||||||
lock.unlock();
|
|
||||||
flag.wait(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
coro<> async_wait(ValueType oldValue) {
|
|
||||||
while(true) {
|
|
||||||
if(oldValue != Value)
|
|
||||||
co_return;
|
|
||||||
|
|
||||||
boost::unique_lock lock(Mtx);
|
|
||||||
|
|
||||||
if(oldValue != Value)
|
|
||||||
co_return;
|
|
||||||
|
|
||||||
auto coroutine = Deadline.async_wait();
|
|
||||||
lock.unlock();
|
|
||||||
try { co_await std::move(coroutine); } catch(...) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
coro<> async_await(ValueType needValue) {
|
|
||||||
while(true) {
|
|
||||||
if(needValue == Value)
|
|
||||||
co_return;
|
|
||||||
|
|
||||||
boost::unique_lock lock(Mtx);
|
|
||||||
|
|
||||||
if(needValue == Value)
|
|
||||||
co_return;
|
|
||||||
|
|
||||||
auto coroutine = Deadline.async_wait();
|
|
||||||
lock.unlock();
|
|
||||||
try { co_await std::move(coroutine); } catch(...) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
#pragma once
|
#include "Common/Abstract.hpp"
|
||||||
#include <algorithm>
|
#include <iostream>
|
||||||
|
|
||||||
|
|
||||||
namespace LV {
|
namespace LV {
|
||||||
|
|
||||||
@@ -9,7 +8,7 @@ bool calcBoxToBoxCollide(const VecType vec1_min, const VecType vec1_max,
|
|||||||
const VecType vec2_min, const VecType vec2_max, bool axis[VecType::length()] = nullptr
|
const VecType vec2_min, const VecType vec2_max, bool axis[VecType::length()] = nullptr
|
||||||
) {
|
) {
|
||||||
|
|
||||||
using ValType = VecType::Type;
|
using ValType = VecType::value_type;
|
||||||
|
|
||||||
ValType max_delta = 0;
|
ValType max_delta = 0;
|
||||||
ValType result = 0;
|
ValType result = 0;
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ bool SocketServer::isStopped() {
|
|||||||
coro<void> SocketServer::run(std::function<coro<>(tcp::socket)> onConnect) {
|
coro<void> SocketServer::run(std::function<coro<>(tcp::socket)> onConnect) {
|
||||||
while(true) { // TODO: ловить ошибки на async_accept
|
while(true) { // TODO: ловить ошибки на async_accept
|
||||||
try {
|
try {
|
||||||
co_spawn(onConnect(co_await Acceptor.async_accept()));
|
asio::co_spawn(IOC, onConnect(co_await Acceptor.async_accept()), asio::detached);
|
||||||
} catch(const std::exception &exc) {
|
} catch(const std::exception &exc) {
|
||||||
if(const boost::system::system_error *errc = dynamic_cast<const boost::system::system_error*>(&exc);
|
if(const boost::system::system_error *errc = dynamic_cast<const boost::system::system_error*>(&exc);
|
||||||
errc && (errc->code() == asio::error::operation_aborted || errc->code() == asio::error::bad_descriptor))
|
errc && (errc->code() == asio::error::operation_aborted || errc->code() == asio::error::bad_descriptor))
|
||||||
@@ -31,21 +31,13 @@ AsyncSocket::~AsyncSocket() {
|
|||||||
if(SendPackets.Context)
|
if(SendPackets.Context)
|
||||||
SendPackets.Context->NeedShutdown = true;
|
SendPackets.Context->NeedShutdown = true;
|
||||||
|
|
||||||
{
|
boost::unique_lock lock(SendPackets.Mtx);
|
||||||
boost::lock_guard lock(SendPackets.Mtx);
|
|
||||||
|
|
||||||
SendPackets.SenderGuard.cancel();
|
|
||||||
WorkDeadline.cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
if(Socket.is_open())
|
if(Socket.is_open())
|
||||||
try { Socket.close(); } catch(...) {}
|
try { Socket.close(); } catch(...) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AsyncSocket::pushPackets(std::vector<Packet> *simplePackets, std::vector<SmartPacket> *smartPackets) {
|
void AsyncSocket::pushPackets(std::vector<Packet> *simplePackets, std::vector<SmartPacket> *smartPackets) {
|
||||||
if(simplePackets->empty() && (!smartPackets || smartPackets->empty()))
|
|
||||||
return;
|
|
||||||
|
|
||||||
boost::unique_lock lock(SendPackets.Mtx);
|
boost::unique_lock lock(SendPackets.Mtx);
|
||||||
|
|
||||||
if(Socket.is_open()
|
if(Socket.is_open()
|
||||||
@@ -58,6 +50,8 @@ void AsyncSocket::pushPackets(std::vector<Packet> *simplePackets, std::vector<Sm
|
|||||||
// TODO: std::cout << "Передоз пакетами, сокет закрыт" << std::endl;
|
// TODO: std::cout << "Передоз пакетами, сокет закрыт" << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool wasPackets = SendPackets.SimpleBuffer.size() || SendPackets.SmartBuffer.size();
|
||||||
|
|
||||||
if(!Socket.is_open()) {
|
if(!Socket.is_open()) {
|
||||||
if(simplePackets)
|
if(simplePackets)
|
||||||
simplePackets->clear();
|
simplePackets->clear();
|
||||||
@@ -89,8 +83,7 @@ void AsyncSocket::pushPackets(std::vector<Packet> *simplePackets, std::vector<Sm
|
|||||||
|
|
||||||
SendPackets.SizeInQueue += addedSize;
|
SendPackets.SizeInQueue += addedSize;
|
||||||
|
|
||||||
if(SendPackets.WaitForSemaphore) {
|
if(!wasPackets) {
|
||||||
SendPackets.WaitForSemaphore = false;
|
|
||||||
SendPackets.Semaphore.cancel();
|
SendPackets.Semaphore.cancel();
|
||||||
SendPackets.Semaphore.expires_at(boost::posix_time::pos_infin);
|
SendPackets.Semaphore.expires_at(boost::posix_time::pos_infin);
|
||||||
}
|
}
|
||||||
@@ -127,10 +120,18 @@ coro<> AsyncSocket::read(std::byte *data, uint32_t size) {
|
|||||||
void AsyncSocket::closeRead() {
|
void AsyncSocket::closeRead() {
|
||||||
if(Socket.is_open() && !ReadShutdowned) {
|
if(Socket.is_open() && !ReadShutdowned) {
|
||||||
ReadShutdowned = true;
|
ReadShutdowned = true;
|
||||||
|
// TODO:
|
||||||
try { Socket.shutdown(boost::asio::socket_base::shutdown_receive); } catch(...) {}
|
try { Socket.shutdown(boost::asio::socket_base::shutdown_receive); } catch(...) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AsyncSocket::close() {
|
||||||
|
if(Socket.is_open()) {
|
||||||
|
Socket.close();
|
||||||
|
ReadShutdowned = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
coro<> AsyncSocket::waitForSend() {
|
coro<> AsyncSocket::waitForSend() {
|
||||||
asio::deadline_timer waiter(IOC);
|
asio::deadline_timer waiter(IOC);
|
||||||
|
|
||||||
@@ -150,12 +151,19 @@ coro<> AsyncSocket::runSender(std::shared_ptr<AsyncContext> context) {
|
|||||||
while(!context->NeedShutdown) {
|
while(!context->NeedShutdown) {
|
||||||
{
|
{
|
||||||
boost::unique_lock lock(SendPackets.Mtx);
|
boost::unique_lock lock(SendPackets.Mtx);
|
||||||
if(SendPackets.SimpleBuffer.empty() && SendPackets.SmartBuffer.empty()) {
|
|
||||||
SendPackets.WaitForSemaphore = true;
|
|
||||||
auto coroutine = SendPackets.Semaphore.async_wait();
|
|
||||||
lock.unlock();
|
|
||||||
|
|
||||||
try { co_await std::move(coroutine); } catch(...) {}
|
if(context->NeedShutdown) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(SendPackets.SimpleBuffer.empty() && SendPackets.SmartBuffer.empty()) {
|
||||||
|
auto coroutine = SendPackets.Semaphore.async_wait();
|
||||||
|
|
||||||
|
if(SendPackets.SimpleBuffer.empty() && SendPackets.SmartBuffer.empty()) {
|
||||||
|
lock.unlock();
|
||||||
|
try { co_await std::move(coroutine); } catch(...) {}
|
||||||
|
}
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
for(int cycle = 0; cycle < 2; cycle++, NextBuffer++) {
|
for(int cycle = 0; cycle < 2; cycle++, NextBuffer++) {
|
||||||
|
|||||||
@@ -1,32 +1,30 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
// TODO: Всё это надо переписать
|
|
||||||
|
|
||||||
|
|
||||||
#include "MemoryPool.hpp"
|
#include "MemoryPool.hpp"
|
||||||
#include "Async.hpp"
|
#include "Async.hpp"
|
||||||
#include "TOSLib.hpp"
|
#include "TOSLib.hpp"
|
||||||
|
#include "boost/asio/io_context.hpp"
|
||||||
|
|
||||||
#include <boost/asio.hpp>
|
#include <boost/asio.hpp>
|
||||||
#include <boost/asio/buffer.hpp>
|
#include <boost/asio/buffer.hpp>
|
||||||
#include <boost/asio/write.hpp>
|
#include <boost/asio/write.hpp>
|
||||||
#include <boost/thread.hpp>
|
#include <boost/thread.hpp>
|
||||||
#include <boost/circular_buffer.hpp>
|
#include <boost/circular_buffer.hpp>
|
||||||
#include <type_traits>
|
|
||||||
|
|
||||||
namespace LV::Net {
|
namespace LV::Net {
|
||||||
|
|
||||||
class SocketServer : public AsyncObject {
|
class SocketServer {
|
||||||
protected:
|
protected:
|
||||||
|
asio::io_context &IOC;
|
||||||
tcp::acceptor Acceptor;
|
tcp::acceptor Acceptor;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
SocketServer(asio::io_context &ioc, std::function<coro<>(tcp::socket)> &&onConnect, uint16_t port = 0)
|
SocketServer(asio::io_context &ioc, std::function<coro<>(tcp::socket)> &&onConnect, uint16_t port = 0)
|
||||||
: AsyncObject(ioc), Acceptor(ioc, tcp::endpoint(tcp::v4(), port))
|
: IOC(ioc), Acceptor(ioc, tcp::endpoint(tcp::v4(), port))
|
||||||
{
|
{
|
||||||
assert(onConnect);
|
assert(onConnect);
|
||||||
|
|
||||||
co_spawn(run(std::move(onConnect)));
|
asio::co_spawn(IOC, run(std::move(onConnect)), asio::detached);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isStopped();
|
bool isStopped();
|
||||||
@@ -39,10 +37,10 @@ protected:
|
|||||||
};
|
};
|
||||||
|
|
||||||
#if defined(__BYTE_ORDER) && __BYTE_ORDER == __LITTLE_ENDIAN
|
#if defined(__BYTE_ORDER) && __BYTE_ORDER == __LITTLE_ENDIAN
|
||||||
template <typename T, std::enable_if_t<std::is_floating_point_v<T> or std::is_integral_v<T>, int> = 0>
|
template <typename T, std::enable_if_t<std::is_integral_v<T>, int> = 0>
|
||||||
static inline T swapEndian(const T &u) { return u; }
|
static inline T swapEndian(const T &u) { return u; }
|
||||||
#else
|
#else
|
||||||
template <typename T, std::enable_if_t<std::is_floating_point_v<T> or std::is_integral_v<T>, int> = 0>
|
template <typename T, std::enable_if_t<std::is_integral_v<T>, int> = 0>
|
||||||
static inline T swapEndian(const T &u) {
|
static inline T swapEndian(const T &u) {
|
||||||
if constexpr (sizeof(T) == 1) {
|
if constexpr (sizeof(T) == 1) {
|
||||||
return u;
|
return u;
|
||||||
@@ -64,7 +62,7 @@ protected:
|
|||||||
using NetPool = BoostPool<12, 14>;
|
using NetPool = BoostPool<12, 14>;
|
||||||
|
|
||||||
class Packet {
|
class Packet {
|
||||||
static constexpr size_t MAX_PACKET_SIZE = 1 << 24;
|
static constexpr size_t MAX_PACKET_SIZE = 1 << 16;
|
||||||
uint16_t Size = 0;
|
uint16_t Size = 0;
|
||||||
std::vector<NetPool::PagePtr> Pages;
|
std::vector<NetPool::PagePtr> Pages;
|
||||||
|
|
||||||
@@ -108,7 +106,7 @@ protected:
|
|||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T, std::enable_if_t<std::is_integral_v<T> || std::is_floating_point_v<T>, int> = 0>
|
template<typename T, std::enable_if_t<std::is_integral_v<T>, int> = 0>
|
||||||
inline Packet& write(T u) {
|
inline Packet& write(T u) {
|
||||||
u = swapEndian(u);
|
u = swapEndian(u);
|
||||||
write((const std::byte*) &u, sizeof(u));
|
write((const std::byte*) &u, sizeof(u));
|
||||||
@@ -129,7 +127,7 @@ protected:
|
|||||||
inline uint16_t size() const { return Size; }
|
inline uint16_t size() const { return Size; }
|
||||||
inline const std::vector<NetPool::PagePtr>& getPages() const { return Pages; }
|
inline const std::vector<NetPool::PagePtr>& getPages() const { return Pages; }
|
||||||
|
|
||||||
template<typename T, std::enable_if_t<std::is_floating_point_v<T> || std::is_integral_v<T> or std::is_convertible_v<T, std::string_view>, int> = 0>
|
template<typename T, std::enable_if_t<std::is_integral_v<T> or std::is_convertible_v<T, std::string_view>, int> = 0>
|
||||||
inline Packet& operator<<(const T &value) {
|
inline Packet& operator<<(const T &value) {
|
||||||
if constexpr (std::is_convertible_v<T, std::string_view>)
|
if constexpr (std::is_convertible_v<T, std::string_view>)
|
||||||
return write((std::string_view) value);
|
return write((std::string_view) value);
|
||||||
@@ -146,7 +144,7 @@ protected:
|
|||||||
Size = 0;
|
Size = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
Packet& complite(std::u8string &out) {
|
Packet& complite(std::vector<std::byte> &out) {
|
||||||
out.resize(Size);
|
out.resize(Size);
|
||||||
|
|
||||||
for(size_t pos = 0; pos < Size; pos += NetPool::PageSize) {
|
for(size_t pos = 0; pos < Size; pos += NetPool::PageSize) {
|
||||||
@@ -157,8 +155,8 @@ protected:
|
|||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::u8string complite() {
|
std::vector<std::byte> complite() {
|
||||||
std::u8string out;
|
std::vector<std::byte> out;
|
||||||
complite(out);
|
complite(out);
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
@@ -174,77 +172,23 @@ protected:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class LinearReader {
|
|
||||||
public:
|
|
||||||
LinearReader(const std::u8string& 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& Input;
|
|
||||||
};
|
|
||||||
|
|
||||||
class SmartPacket : public Packet {
|
class SmartPacket : public Packet {
|
||||||
public:
|
public:
|
||||||
std::function<bool()> IsStillRelevant;
|
std::function<bool()> IsStillRelevant;
|
||||||
std::function<std::optional<SmartPacket>()> OnSend;
|
std::function<std::optional<SmartPacket>()> OnSend;
|
||||||
};
|
};
|
||||||
|
|
||||||
class AsyncSocket : public AsyncObject {
|
class AsyncSocket {
|
||||||
|
asio::io_context &IOC;
|
||||||
NetPool::Array<32> RecvBuffer, SendBuffer;
|
NetPool::Array<32> RecvBuffer, SendBuffer;
|
||||||
size_t RecvPos = 0, RecvSize = 0, SendSize = 0;
|
size_t RecvPos = 0, RecvSize = 0, SendSize = 0;
|
||||||
bool ReadShutdowned = false;
|
bool ReadShutdowned = false;
|
||||||
tcp::socket Socket;
|
tcp::socket Socket;
|
||||||
|
|
||||||
static constexpr uint32_t
|
static constexpr uint32_t
|
||||||
MAX_SIMPLE_PACKETS = 16384,
|
MAX_SIMPLE_PACKETS = 8192,
|
||||||
MAX_SMART_PACKETS = MAX_SIMPLE_PACKETS/4,
|
MAX_SMART_PACKETS = MAX_SIMPLE_PACKETS/4,
|
||||||
MAX_PACKETS_SIZE_IN_WAIT = 1 << 26;
|
MAX_PACKETS_SIZE_IN_WAIT = 1 << 24;
|
||||||
|
|
||||||
struct AsyncContext {
|
struct AsyncContext {
|
||||||
volatile bool NeedShutdown = false, RunSendShutdowned = false;
|
volatile bool NeedShutdown = false, RunSendShutdowned = false;
|
||||||
@@ -253,32 +197,31 @@ protected:
|
|||||||
|
|
||||||
struct SendPacketsObj {
|
struct SendPacketsObj {
|
||||||
boost::mutex Mtx;
|
boost::mutex Mtx;
|
||||||
bool WaitForSemaphore = false;
|
asio::deadline_timer Semaphore;
|
||||||
asio::deadline_timer Semaphore, SenderGuard;
|
|
||||||
boost::circular_buffer_space_optimized<Packet> SimpleBuffer;
|
boost::circular_buffer_space_optimized<Packet> SimpleBuffer;
|
||||||
boost::circular_buffer_space_optimized<SmartPacket> SmartBuffer;
|
boost::circular_buffer_space_optimized<SmartPacket> SmartBuffer;
|
||||||
size_t SizeInQueue = 0;
|
size_t SizeInQueue = 0;
|
||||||
std::shared_ptr<AsyncContext> Context;
|
std::shared_ptr<AsyncContext> Context;
|
||||||
|
|
||||||
SendPacketsObj(asio::io_context &ioc)
|
SendPacketsObj(asio::io_context &ioc)
|
||||||
: Semaphore(ioc, boost::posix_time::pos_infin), SenderGuard(ioc, boost::posix_time::pos_infin)
|
: Semaphore(ioc, boost::posix_time::pos_infin)
|
||||||
{}
|
{}
|
||||||
} SendPackets;
|
} SendPackets;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
AsyncSocket(asio::io_context &ioc, tcp::socket &&socket)
|
AsyncSocket(asio::io_context &ioc, tcp::socket &&socket)
|
||||||
: AsyncObject(ioc), Socket(std::move(socket)), SendPackets(ioc)
|
: IOC(ioc), Socket(std::move(socket)), SendPackets(ioc)
|
||||||
{
|
{
|
||||||
SendPackets.SimpleBuffer.set_capacity(512);
|
SendPackets.SimpleBuffer.set_capacity(512);
|
||||||
SendPackets.SmartBuffer.set_capacity(SendPackets.SimpleBuffer.capacity()/4);
|
SendPackets.SmartBuffer.set_capacity(SendPackets.SimpleBuffer.capacity()/4);
|
||||||
SendPackets.Context = std::make_shared<AsyncContext>();
|
SendPackets.Context = std::make_shared<AsyncContext>();
|
||||||
|
|
||||||
boost::asio::socket_base::linger optionLinger(true, 4); // После закрытия сокета оставшиеся данные будут доставлены
|
boost::asio::socket_base::linger optionLinger(true, 4); // После закрытия сокета оставшиеся данные будут доставлены
|
||||||
Socket.set_option(optionLinger);
|
Socket.set_option(optionLinger);
|
||||||
boost::asio::ip::tcp::no_delay optionNoDelay(true); // Отключает попытки объединить данные в крупные пакеты
|
boost::asio::ip::tcp::no_delay optionNoDelay(true); // Отключает попытки объёденить данные в крупные пакеты
|
||||||
Socket.set_option(optionNoDelay);
|
Socket.set_option(optionNoDelay);
|
||||||
|
|
||||||
co_spawn(runSender(SendPackets.Context));
|
asio::co_spawn(IOC, runSender(SendPackets.Context), asio::detached);
|
||||||
}
|
}
|
||||||
|
|
||||||
~AsyncSocket();
|
~AsyncSocket();
|
||||||
@@ -296,10 +239,11 @@ protected:
|
|||||||
|
|
||||||
coro<> read(std::byte *data, uint32_t size);
|
coro<> read(std::byte *data, uint32_t size);
|
||||||
void closeRead();
|
void closeRead();
|
||||||
|
void close();
|
||||||
|
|
||||||
template<typename T, std::enable_if_t<std::is_floating_point_v<T> or std::is_integral_v<T> or std::is_same_v<T, std::string>, int> = 0>
|
template<typename T, std::enable_if_t<std::is_integral_v<T> or std::is_same_v<T, std::string>, int> = 0>
|
||||||
coro<T> read() {
|
coro<T> read() {
|
||||||
if constexpr(std::is_floating_point_v<T> or std::is_integral_v<T>) {
|
if constexpr(std::is_integral_v<T>) {
|
||||||
T value;
|
T value;
|
||||||
co_await read((std::byte*) &value, sizeof(value));
|
co_await read((std::byte*) &value, sizeof(value));
|
||||||
co_return swapEndian(value);
|
co_return swapEndian(value);
|
||||||
@@ -317,9 +261,9 @@ protected:
|
|||||||
co_await asio::async_read(socket, asio::mutable_buffer(data, size));
|
co_await asio::async_read(socket, asio::mutable_buffer(data, size));
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T, std::enable_if_t<std::is_floating_point_v<T> or std::is_integral_v<T> or std::is_same_v<T, std::string>, int> = 0>
|
template<typename T, std::enable_if_t<std::is_integral_v<T> or std::is_same_v<T, std::string>, int> = 0>
|
||||||
static inline coro<T> read(tcp::socket &socket) {
|
static inline coro<T> read(tcp::socket &socket) {
|
||||||
if constexpr(std::is_floating_point_v<T> or std::is_integral_v<T>) {
|
if constexpr(std::is_integral_v<T>) {
|
||||||
T value;
|
T value;
|
||||||
co_await read(socket, (std::byte*) &value, sizeof(value));
|
co_await read(socket, (std::byte*) &value, sizeof(value));
|
||||||
co_return swapEndian(value);
|
co_return swapEndian(value);
|
||||||
@@ -334,7 +278,7 @@ protected:
|
|||||||
co_await asio::async_write(socket, asio::const_buffer(data, size));
|
co_await asio::async_write(socket, asio::const_buffer(data, size));
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T, std::enable_if_t<std::is_floating_point_v<T> or std::is_integral_v<T>, int> = 0>
|
template<typename T, std::enable_if_t<std::is_integral_v<T>, int> = 0>
|
||||||
static inline coro<> write(tcp::socket &socket, T u) {
|
static inline coro<> write(tcp::socket &socket, T u) {
|
||||||
u = swapEndian(u);
|
u = swapEndian(u);
|
||||||
co_await write(socket, (const std::byte*) &u, sizeof(u));
|
co_await write(socket, (const std::byte*) &u, sizeof(u));
|
||||||
|
|||||||
@@ -24,45 +24,36 @@ struct PacketQuat {
|
|||||||
z = (quat.z+1)/2*0x3ff,
|
z = (quat.z+1)/2*0x3ff,
|
||||||
w = (quat.w+1)/2*0x3ff;
|
w = (quat.w+1)/2*0x3ff;
|
||||||
|
|
||||||
uint64_t value = 0;
|
for(uint8_t &val : Data)
|
||||||
|
val = 0;
|
||||||
|
|
||||||
value |= x & 0x3ff;
|
*(uint16_t*) Data |= x;
|
||||||
value |= uint64_t(y & 0x3ff) << 10;
|
*(uint16_t*) (Data+1) |= y << 2;
|
||||||
value |= uint64_t(z & 0x3ff) << 20;
|
*(uint16_t*) (Data+2) |= z << 4;
|
||||||
value |= uint64_t(w & 0x3ff) << 30;
|
*(uint16_t*) (Data+3) |= w << 6;
|
||||||
|
|
||||||
for(int iter = 0; iter < 5; iter++)
|
|
||||||
Data[iter] = (value >> (iter*8)) & 0xff;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
glm::quat toQuat() const {
|
glm::quat toQuat() const {
|
||||||
uint64_t value = 0;
|
const uint64_t &data = (const uint64_t&) *Data;
|
||||||
|
|
||||||
for(int iter = 0; iter < 5; iter++)
|
|
||||||
value |= uint64_t(Data[iter]) << (iter*8);
|
|
||||||
|
|
||||||
uint16_t
|
uint16_t
|
||||||
x = value & 0x3ff,
|
x = data & 0x3ff,
|
||||||
y = (value >> 10) & 0x3ff,
|
y = (data >> 10) & 0x3ff,
|
||||||
z = (value >> 20) & 0x3ff,
|
z = (data >> 20) & 0x3ff,
|
||||||
w = (value >> 30) & 0x3ff;
|
w = (data >> 30) & 0x3ff;
|
||||||
|
|
||||||
float fx = (float(x)/0x3ff)*2-1;
|
float fx = (float(x)/0x3ff)*2-1;
|
||||||
float fy = (float(y)/0x3ff)*2-1;
|
float fy = (float(y)/0x3ff)*2-1;
|
||||||
float fz = (float(z)/0x3ff)*2-1;
|
float fz = (float(z)/0x3ff)*2-1;
|
||||||
float fw = (float(w)/0x3ff)*2-1;
|
float fw = (float(w)/0x3ff)*2-1;
|
||||||
|
|
||||||
return glm::quat(fw, fx, fy, fz);
|
return glm::quat(fx, fy, fz, fw);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
uint8_t+uint8_t
|
uint8_t+uint8_t
|
||||||
0 - Системное
|
0 - Системное
|
||||||
0 -
|
0 - Новая позиция камеры WorldId_c+ObjectPos+PacketQuat
|
||||||
1 -
|
|
||||||
2 - Новая позиция камеры WorldId_c+ObjectPos+PacketQuat
|
|
||||||
3 - Изменение блока
|
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -75,8 +66,7 @@ enum struct L1 : uint8_t {
|
|||||||
enum struct L2System : uint8_t {
|
enum struct L2System : uint8_t {
|
||||||
InitEnd,
|
InitEnd,
|
||||||
Disconnect,
|
Disconnect,
|
||||||
Test_CAM_PYR_POS,
|
Test_CAM_PYR_POS
|
||||||
BlockChange
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -144,10 +134,15 @@ enum struct L2System : uint8_t {
|
|||||||
};
|
};
|
||||||
|
|
||||||
enum struct L2Resource : uint8_t {
|
enum struct L2Resource : uint8_t {
|
||||||
Bind, // Привязка идентификаторов ресурсов к хешам
|
Texture,
|
||||||
Lost,
|
FreeTexture,
|
||||||
|
Sound,
|
||||||
|
FreeSound,
|
||||||
|
Model,
|
||||||
|
FreeModel,
|
||||||
InitResSend = 253,
|
InitResSend = 253,
|
||||||
ChunkSend
|
ChunkSend,
|
||||||
|
SendCanceled
|
||||||
};
|
};
|
||||||
|
|
||||||
enum struct L2Definition : uint8_t {
|
enum struct L2Definition : uint8_t {
|
||||||
@@ -160,11 +155,7 @@ enum struct L2Definition : uint8_t {
|
|||||||
Portal,
|
Portal,
|
||||||
FreePortal,
|
FreePortal,
|
||||||
Entity,
|
Entity,
|
||||||
FreeEntity,
|
FreeEntity
|
||||||
FuncEntity,
|
|
||||||
FreeFuncEntity,
|
|
||||||
Item,
|
|
||||||
FreeItem
|
|
||||||
};
|
};
|
||||||
|
|
||||||
enum struct L2Content : uint8_t {
|
enum struct L2Content : uint8_t {
|
||||||
@@ -177,7 +168,7 @@ enum struct L2Content : uint8_t {
|
|||||||
ChunkVoxels,
|
ChunkVoxels,
|
||||||
ChunkNodes,
|
ChunkNodes,
|
||||||
ChunkLightPrism,
|
ChunkLightPrism,
|
||||||
RemoveRegion
|
RemoveChunk
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
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,17 +0,0 @@
|
|||||||
#include "Abstract.hpp"
|
|
||||||
#include <csignal>
|
|
||||||
|
|
||||||
|
|
||||||
namespace LV::Server {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
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,54 +1,62 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "TOSLib.hpp"
|
|
||||||
#include <algorithm>
|
|
||||||
#include <bitset>
|
|
||||||
#include <cctype>
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <Common/Abstract.hpp>
|
#include <Common/Abstract.hpp>
|
||||||
#include <Common/Collide.hpp>
|
#include <Common/Collide.hpp>
|
||||||
#include <sha2.hpp>
|
#include <boost/uuid/detail/sha1.hpp>
|
||||||
#include <sol/sol.hpp>
|
|
||||||
#include <string>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <boost/json.hpp>
|
|
||||||
#include <variant>
|
|
||||||
|
|
||||||
|
|
||||||
namespace LV::Server {
|
namespace LV::Server {
|
||||||
|
|
||||||
namespace js = boost::json;
|
using ResourceId_t = uint32_t;
|
||||||
|
|
||||||
|
// Двоичные данные
|
||||||
|
using BinTextureId_t = ResourceId_t;
|
||||||
|
using BinSoundId_t = ResourceId_t;
|
||||||
|
using BinModelId_t = ResourceId_t;
|
||||||
|
|
||||||
|
// Игровые определения
|
||||||
|
using DefWorldId_t = ResourceId_t;
|
||||||
|
using DefVoxelId_t = ResourceId_t;
|
||||||
|
using DefNodeId_t = ResourceId_t;
|
||||||
|
using DefPortalId_t = ResourceId_t;
|
||||||
|
using DefEntityId_t = ResourceId_t;
|
||||||
|
|
||||||
|
|
||||||
|
// Контент, основанный на игровых определениях
|
||||||
|
using WorldId_t = ResourceId_t;
|
||||||
|
|
||||||
// В одном регионе может быть максимум 2^16 сущностей. Клиенту адресуются сущности в формате <мир>+<позиция региона>+<uint16_t>
|
// В одном регионе может быть максимум 2^16 сущностей. Клиенту адресуются сущности в формате <мир>+<позиция региона>+<uint16_t>
|
||||||
// И если сущность перешла из одного региона в другой, идентификатор сущности на стороне клиента сохраняется
|
// И если сущность перешла из одного региона в другой, идентификатор сущности на стороне клиента сохраняется
|
||||||
using RegionEntityId_t = uint16_t;
|
using LocalEntityId_t = uint16_t;
|
||||||
using ClientEntityId_t = RegionEntityId_t;
|
using GlobalEntityId_t = std::tuple<WorldId_t, Pos::GlobalRegion, LocalEntityId_t>;
|
||||||
using ServerEntityId_t = std::tuple<WorldId_t, Pos::GlobalRegion, RegionEntityId_t>;
|
using PortalId_t = uint16_t;
|
||||||
using RegionFuncEntityId_t = uint16_t;
|
|
||||||
using ClientFuncEntityId_t = RegionFuncEntityId_t;
|
|
||||||
using ServerFuncEntityId_t = std::tuple<WorldId_t, Pos::GlobalRegion, RegionFuncEntityId_t>;
|
|
||||||
|
|
||||||
using MediaStreamId_t = uint16_t;
|
using MediaStreamId_t = uint16_t;
|
||||||
using ContentBridgeId_t = ResourceId;
|
using ContentBridgeId_t = uint16_t;
|
||||||
using PlayerId_t = ResourceId;
|
using PlayerId_t = uint32_t;
|
||||||
using DefGeneratorId_t = ResourceId;
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Сервер загружает информацию о локальных текстурах
|
Сервер загружает информацию о локальных текстурах
|
||||||
|
Синхронизация часто используемых текстур?
|
||||||
Пересмотр списка текстур?
|
Пересмотр списка текстур?
|
||||||
Динамичные текстуры?
|
Динамичные текстуры?
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
struct ResourceFile {
|
struct ResourceFile {
|
||||||
using Hash_t = sha2::sha256_hash; // boost::uuids::detail::sha1::digest_type;
|
using Hash_t = boost::uuids::detail::sha1::digest_type;
|
||||||
|
|
||||||
Hash_t Hash;
|
Hash_t Hash;
|
||||||
std::vector<std::byte> Data;
|
std::vector<std::byte> Data;
|
||||||
|
|
||||||
void calcHash() {
|
void calcHash() {
|
||||||
Hash = sha2::sha256((const uint8_t*) Data.data(), Data.size());
|
boost::uuids::detail::sha1 hash;
|
||||||
|
hash.process_bytes(Data.data(), Data.size());
|
||||||
|
hash.get_digest(Hash);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -56,43 +64,37 @@ struct ServerTime {
|
|||||||
uint32_t Seconds : 24, Sub : 8;
|
uint32_t Seconds : 24, Sub : 8;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct VoxelCube_Region {
|
struct VoxelCube {
|
||||||
union {
|
DefVoxelId_t VoxelId;
|
||||||
struct {
|
Pos::Local256_u Left, Right;
|
||||||
DefVoxelId VoxelId : 24, Meta : 8;
|
|
||||||
};
|
|
||||||
|
|
||||||
DefVoxelId Data = 0;
|
auto operator<=>(const VoxelCube&) const = default;
|
||||||
};
|
|
||||||
|
|
||||||
Pos::bvec1024u Left, Right; // TODO: заменить на позицию и размер
|
|
||||||
|
|
||||||
auto operator<=>(const VoxelCube_Region& other) const {
|
|
||||||
if (auto cmp = Left <=> other.Left; cmp != 0)
|
|
||||||
return cmp;
|
|
||||||
|
|
||||||
if (auto cmp = Right <=> other.Right; cmp != 0)
|
|
||||||
return cmp;
|
|
||||||
|
|
||||||
return Data <=> other.Data;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool operator==(const VoxelCube_Region& other) const {
|
|
||||||
return Left == other.Left && Right == other.Right && Data == other.Data;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct VoxelCube_Region {
|
||||||
|
Pos::Local4096_u Left, Right;
|
||||||
|
DefVoxelId_t VoxelId;
|
||||||
|
|
||||||
|
auto operator<=>(const VoxelCube_Region&) const = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Node {
|
||||||
|
DefNodeId_t NodeId;
|
||||||
|
uint8_t Rotate : 6;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
struct AABB {
|
struct AABB {
|
||||||
Pos::Object VecMin, VecMax;
|
Pos::Object VecMin, VecMax;
|
||||||
|
|
||||||
void sortMinMax() {
|
void sortMinMax() {
|
||||||
Pos::Object::Type left, right;
|
Pos::Object::value_type left, right;
|
||||||
|
|
||||||
for(int iter = 0; iter < 3; iter++) {
|
for(int iter = 0; iter < 3; iter++) {
|
||||||
left = std::min(VecMin[iter], VecMax[iter]);
|
left = std::min(VecMin[iter], VecMax[iter]);
|
||||||
right = std::max(VecMin[iter], VecMax[iter]);
|
right = std::max(VecMin[iter], VecMax[iter]);
|
||||||
VecMin.set(iter, left);
|
VecMin[iter] = left;
|
||||||
VecMax.set(iter, right);
|
VecMax[iter] = right;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,26 +117,22 @@ struct LocalAABB {
|
|||||||
|
|
||||||
struct CollisionAABB : public AABB {
|
struct CollisionAABB : public AABB {
|
||||||
enum struct EnumType {
|
enum struct EnumType {
|
||||||
Voxel, Node, Entity, FuncEntity, Barrier, Portal, Another
|
Voxel, Node, Entity, Barrier, Portal, Another
|
||||||
} Type;
|
} Type;
|
||||||
|
|
||||||
union {
|
union {
|
||||||
struct {
|
struct {
|
||||||
RegionEntityId_t Index;
|
LocalEntityId_t Index;
|
||||||
} Entity;
|
} Entity;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
RegionFuncEntityId_t Index;
|
Pos::Local16_u Pos;
|
||||||
} FuncEntity;
|
|
||||||
|
|
||||||
struct {
|
|
||||||
Pos::bvec4u Chunk;
|
|
||||||
Pos::bvec16u Pos;
|
|
||||||
} Node;
|
} Node;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
Pos::bvec4u Chunk;
|
Pos::Local16_u Chunk;
|
||||||
uint32_t Index;
|
uint32_t Index;
|
||||||
|
DefVoxelId_t Id;
|
||||||
} Voxel;
|
} Voxel;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
@@ -153,63 +151,15 @@ struct CollisionAABB : public AABB {
|
|||||||
bool Skip = false;
|
bool Skip = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
|
||||||
Указать модель, текстуры и поворот по конкретным осям.
|
|
||||||
Может быть вариативность моделей относительно одного условия (случайность в зависимости от координат?)
|
|
||||||
Допускается активация нескольких условий одновременно
|
|
||||||
|
|
||||||
условия snowy=false
|
|
||||||
|
|
||||||
"snowy=false": [{"model": "node/grass_node"}, {"model": "node/grass_node", transformations: ["y=90", "x=67"]}] <- модель будет выбрана случайно
|
|
||||||
или
|
|
||||||
: [{models: [], weight: 1}, {}] <- в models можно перечислить сразу несколько моделей, и они будут использоваться одновременно
|
|
||||||
или
|
|
||||||
"": {"model": "node/grass", weight <вес влияющий на шанс отображения именно этой модели>}
|
|
||||||
или просто
|
|
||||||
"model": "node/grass_node"
|
|
||||||
В условия добавить простые проверки !><=&|()
|
|
||||||
в задании параметров модели использовать формулы с применением состояний
|
|
||||||
|
|
||||||
uvlock ? https://minecraft.wiki/w/Blockstates_definition/format
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Скомпилированный профиль ноды
|
|
||||||
struct DefNode_t {
|
|
||||||
// Зарегистрированные состояния (мета)
|
|
||||||
// Подгружается с файла assets/<modid>/nodestate/node/nodeId.json
|
|
||||||
// std::variant<DefNodestates_t, std::vector<ModelTransform>> StatesRouter;
|
|
||||||
|
|
||||||
// Параметры рендера
|
|
||||||
struct {
|
|
||||||
bool HasHalfTransparency = false;
|
|
||||||
} Render;
|
|
||||||
|
|
||||||
// Параметры коллизии
|
|
||||||
struct {
|
|
||||||
enum class EnumCollisionType {
|
|
||||||
None, ByRender,
|
|
||||||
};
|
|
||||||
|
|
||||||
std::variant<EnumCollisionType> CollisionType = EnumCollisionType::None;
|
|
||||||
} Collision;
|
|
||||||
|
|
||||||
// События
|
|
||||||
struct {
|
|
||||||
|
|
||||||
} Events;
|
|
||||||
|
|
||||||
// Если нода умная, то для неё будет создаваться дополнительный более активный объект
|
|
||||||
std::optional<sol::protected_function> NodeAdvancementFactory;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Entity {
|
class Entity {
|
||||||
DefEntityId DefId;
|
DefEntityId_t DefId;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
LocalAABB ABBOX;
|
LocalAABB ABBOX;
|
||||||
|
|
||||||
// PosQuat
|
// PosQuat
|
||||||
DefWorldId WorldId;
|
DefWorldId_t WorldId;
|
||||||
Pos::Object Pos, Speed, Acceleration;
|
Pos::Object Pos, Speed, Acceleration;
|
||||||
glm::quat Quat;
|
glm::quat Quat;
|
||||||
static constexpr uint16_t HP_BS = 4096, HP_BS_Bit = 12;
|
static constexpr uint16_t HP_BS = 4096, HP_BS_Bit = 12;
|
||||||
@@ -229,15 +179,16 @@ public:
|
|||||||
IsRemoved = false;
|
IsRemoved = false;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Entity(DefEntityId defId);
|
Entity(DefEntityId_t defId);
|
||||||
|
|
||||||
AABB aabbAtPos() {
|
AABB aabbAtPos() {
|
||||||
return {Pos-Pos::Object(ABBOX.x/2, ABBOX.y/2, ABBOX.z/2), Pos+Pos::Object(ABBOX.x/2, ABBOX.y/2, ABBOX.z/2)};
|
return {Pos-Pos::Object(ABBOX.x/2, ABBOX.y/2, ABBOX.z/2), Pos+Pos::Object(ABBOX.x/2, ABBOX.y/2, ABBOX.z/2)};
|
||||||
}
|
}
|
||||||
|
|
||||||
DefEntityId getDefId() const { return DefId; }
|
DefEntityId_t getDefId() const { return DefId; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
template<typename Vec>
|
template<typename Vec>
|
||||||
struct VoxelCuboidsFuncs {
|
struct VoxelCuboidsFuncs {
|
||||||
|
|
||||||
@@ -246,9 +197,9 @@ struct VoxelCuboidsFuncs {
|
|||||||
if (a.VoxelId != b.VoxelId) return false;
|
if (a.VoxelId != b.VoxelId) return false;
|
||||||
|
|
||||||
// Проверяем, что кубы смежны по одной из осей
|
// Проверяем, что кубы смежны по одной из осей
|
||||||
bool xAdjacent = (a.Right.x == b.Left.x) && (a.Left.y == b.Left.y) && (a.Right.z == b.Right.z) && (a.Left.z == b.Left.z) && (a.Right.z == b.Right.z);
|
bool xAdjacent = (a.Right.X == b.Left.X) && (a.Left.Y == b.Left.Y) && (a.Right.Y == b.Right.Y) && (a.Left.Z == b.Left.Z) && (a.Right.Z == b.Right.Z);
|
||||||
bool yAdjacent = (a.Right.y == b.Left.y) && (a.Left.x == b.Left.x) && (a.Right.x == b.Right.x) && (a.Left.z == b.Left.z) && (a.Right.z == b.Right.z);
|
bool yAdjacent = (a.Right.Y == b.Left.Y) && (a.Left.X == b.Left.X) && (a.Right.X == b.Right.X) && (a.Left.Z == b.Left.Z) && (a.Right.Z == b.Right.Z);
|
||||||
bool zAdjacent = (a.Right.z == b.Left.z) && (a.Left.x == b.Left.x) && (a.Right.x == b.Right.x) && (a.Left.y == b.Left.y) && (a.Right.y == b.Right.y);
|
bool zAdjacent = (a.Right.Z == b.Left.Z) && (a.Left.X == b.Left.X) && (a.Right.X == b.Right.X) && (a.Left.Y == b.Left.Y) && (a.Right.Y == b.Right.Y);
|
||||||
|
|
||||||
return xAdjacent || yAdjacent || zAdjacent;
|
return xAdjacent || yAdjacent || zAdjacent;
|
||||||
}
|
}
|
||||||
@@ -258,13 +209,13 @@ struct VoxelCuboidsFuncs {
|
|||||||
merged.VoxelId = a.VoxelId;
|
merged.VoxelId = a.VoxelId;
|
||||||
|
|
||||||
// Объединяем кубы по минимальным и максимальным координатам
|
// Объединяем кубы по минимальным и максимальным координатам
|
||||||
merged.Left.x = std::min(a.Left.x, b.Left.x);
|
merged.Left.X = std::min(a.Left.X, b.Left.X);
|
||||||
merged.Left.y = std::min(a.Left.y, b.Left.y);
|
merged.Left.Y = std::min(a.Left.Y, b.Left.Y);
|
||||||
merged.Left.z = std::min(a.Left.z, b.Left.z);
|
merged.Left.Z = std::min(a.Left.Z, b.Left.Z);
|
||||||
|
|
||||||
merged.Right.x = std::max(a.Right.x, b.Right.x);
|
merged.Right.X = std::max(a.Right.X, b.Right.X);
|
||||||
merged.Right.y = std::max(a.Right.y, b.Right.y);
|
merged.Right.Y = std::max(a.Right.Y, b.Right.Y);
|
||||||
merged.Right.z = std::max(a.Right.z, b.Right.z);
|
merged.Right.Z = std::max(a.Right.Z, b.Right.Z);
|
||||||
|
|
||||||
return merged;
|
return merged;
|
||||||
}
|
}
|
||||||
@@ -358,47 +309,53 @@ struct VoxelCuboidsFuncs {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
inline void convertRegionVoxelsToChunks(const std::vector<VoxelCube_Region>& regions, std::unordered_map<Pos::bvec4u, std::vector<VoxelCube>> &chunks) {
|
inline void convertRegionVoxelsToChunks(const std::vector<VoxelCube_Region>& regions, std::vector<VoxelCube> *chunks) {
|
||||||
for (const auto& region : regions) {
|
for (const auto& region : regions) {
|
||||||
int minX = region.Left.x >> 8;
|
int minX = region.Left.X >> 8;
|
||||||
int minY = region.Left.y >> 8;
|
int minY = region.Left.Y >> 8;
|
||||||
int minZ = region.Left.z >> 8;
|
int minZ = region.Left.Z >> 8;
|
||||||
int maxX = region.Right.x >> 8;
|
int maxX = region.Right.X >> 8;
|
||||||
int maxY = region.Right.y >> 8;
|
int maxY = region.Right.Y >> 8;
|
||||||
int maxZ = region.Right.z >> 8;
|
int maxZ = region.Right.Z >> 8;
|
||||||
|
|
||||||
for (int x = minX; x <= maxX; ++x) {
|
for (int x = minX; x <= maxX; ++x) {
|
||||||
for (int y = minY; y <= maxY; ++y) {
|
for (int y = minY; y <= maxY; ++y) {
|
||||||
for (int z = minZ; z <= maxZ; ++z) {
|
for (int z = minZ; z <= maxZ; ++z) {
|
||||||
Pos::bvec256u left {
|
Pos::Local256_u left {
|
||||||
static_cast<uint8_t>(std::max<uint16_t>((x << 8), region.Left.x) - (x << 8)),
|
static_cast<uint8_t>(std::max<uint16_t>((x << 8), region.Left.X) - (x << 8)),
|
||||||
static_cast<uint8_t>(std::max<uint16_t>((y << 8), region.Left.y) - (y << 8)),
|
static_cast<uint8_t>(std::max<uint16_t>((y << 8), region.Left.Y) - (y << 8)),
|
||||||
static_cast<uint8_t>(std::max<uint16_t>((z << 8), region.Left.z) - (z << 8))
|
static_cast<uint8_t>(std::max<uint16_t>((z << 8), region.Left.Z) - (z << 8))
|
||||||
};
|
};
|
||||||
Pos::bvec256u right {
|
Pos::Local256_u right {
|
||||||
static_cast<uint8_t>(std::min<uint16_t>(((x+1) << 8)-1, region.Right.x) - (x << 8)),
|
static_cast<uint8_t>(std::min<uint16_t>(((x+1) << 8)-1, region.Right.X) - (x << 8)),
|
||||||
static_cast<uint8_t>(std::min<uint16_t>(((y+1) << 8)-1, region.Right.y) - (y << 8)),
|
static_cast<uint8_t>(std::min<uint16_t>(((y+1) << 8)-1, region.Right.Y) - (y << 8)),
|
||||||
static_cast<uint8_t>(std::min<uint16_t>(((z+1) << 8)-1, region.Right.z) - (z << 8))
|
static_cast<uint8_t>(std::min<uint16_t>(((z+1) << 8)-1, region.Right.Z) - (z << 8))
|
||||||
};
|
};
|
||||||
|
|
||||||
chunks[Pos::bvec4u(x, y, z)].push_back({
|
int chunkIndex = z * 16 * 16 + y * 16 + x;
|
||||||
region.VoxelId, region.Meta, left, right
|
chunks[chunkIndex].emplace_back(region.VoxelId, left, right);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void convertChunkVoxelsToRegion(const std::unordered_map<Pos::bvec4u, std::vector<VoxelCube>> &chunks, std::vector<VoxelCube_Region> ®ions) {
|
inline void convertChunkVoxelsToRegion(const std::vector<VoxelCube> *chunks, std::vector<VoxelCube_Region> ®ions) {
|
||||||
for(const auto& [pos, voxels] : chunks) {
|
for (int x = 0; x < 16; ++x) {
|
||||||
Pos::bvec1024u left = pos << 8;
|
for (int y = 0; y < 16; ++y) {
|
||||||
for (const auto& cube : voxels) {
|
for (int z = 0; z < 16; ++z) {
|
||||||
regions.push_back({
|
int chunkIndex = z * 16 * 16 + y * 16 + x;
|
||||||
cube.VoxelId, cube.Meta,
|
|
||||||
Pos::bvec1024u(left.x+cube.Pos.x, left.y+cube.Pos.y, left.z+cube.Pos.z),
|
Pos::Local4096_u left(x << 8, y << 8, z << 8);
|
||||||
Pos::bvec1024u(left.x+cube.Pos.x+cube.Size.x, left.y+cube.Pos.y+cube.Size.y, left.z+cube.Pos.z+cube.Size.z)
|
|
||||||
});
|
for (const auto& cube : chunks[chunkIndex]) {
|
||||||
|
regions.emplace_back(
|
||||||
|
Pos::Local4096_u(left.X+cube.Left.X, left.Y+cube.Left.Y, left.Z+cube.Left.Z),
|
||||||
|
Pos::Local4096_u(left.X+cube.Right.X, left.Y+cube.Right.Y, left.Z+cube.Right.Z),
|
||||||
|
cube.VoxelId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -406,105 +363,4 @@ inline void convertChunkVoxelsToRegion(const std::unordered_map<Pos::bvec4u, std
|
|||||||
regions = VoxelCuboidsFuncs<VoxelCube_Region>::optimizeVoxelRegions(regions);
|
regions = VoxelCuboidsFuncs<VoxelCube_Region>::optimizeVoxelRegions(regions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
struct ServerObjectPos {
|
|
||||||
WorldId_t WorldId;
|
|
||||||
Pos::Object ObjectPos;
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
Разница между информацией о наблюдаемых регионах
|
|
||||||
*/
|
|
||||||
struct ContentViewInfo_Diff {
|
|
||||||
// Изменения на уровне миров (увиден или потерян)
|
|
||||||
std::vector<WorldId_t> WorldsNew, WorldsLost;
|
|
||||||
// Изменения на уровне регионов
|
|
||||||
std::unordered_map<WorldId_t, std::vector<Pos::GlobalRegion>> RegionsNew, RegionsLost;
|
|
||||||
|
|
||||||
bool empty() const {
|
|
||||||
return WorldsNew.empty() && WorldsLost.empty() && RegionsNew.empty() && RegionsLost.empty();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
То, какие регионы наблюдает игрок
|
|
||||||
*/
|
|
||||||
struct ContentViewInfo {
|
|
||||||
// std::vector<Pos::GlobalRegion> - сортированный и с уникальными значениями
|
|
||||||
std::unordered_map<WorldId_t, std::vector<Pos::GlobalRegion>> Regions;
|
|
||||||
|
|
||||||
// Что изменилось относительно obj
|
|
||||||
// Перерасчёт должен проводится при смещении игрока или ContentBridge за границу региона
|
|
||||||
ContentViewInfo_Diff diffWith(const ContentViewInfo& obj) const {
|
|
||||||
ContentViewInfo_Diff out;
|
|
||||||
|
|
||||||
// Проверяем новые миры и регионы
|
|
||||||
for(const auto& [key, regions] : Regions) {
|
|
||||||
auto iterWorld = obj.Regions.find(key);
|
|
||||||
|
|
||||||
if(iterWorld == obj.Regions.end()) {
|
|
||||||
out.WorldsNew.push_back(key);
|
|
||||||
out.RegionsNew[key] = regions;
|
|
||||||
} else {
|
|
||||||
auto &vec = out.RegionsNew[key];
|
|
||||||
vec.reserve(8*8);
|
|
||||||
std::set_difference(
|
|
||||||
regions.begin(), regions.end(),
|
|
||||||
iterWorld->second.begin(), iterWorld->second.end(),
|
|
||||||
std::back_inserter(vec)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Проверяем потерянные миры и регионы
|
|
||||||
for(const auto& [key, regions] : obj.Regions) {
|
|
||||||
auto iterWorld = Regions.find(key);
|
|
||||||
|
|
||||||
if(iterWorld == Regions.end()) {
|
|
||||||
out.WorldsLost.push_back(key);
|
|
||||||
out.RegionsLost[key] = regions;
|
|
||||||
} else {
|
|
||||||
auto &vec = out.RegionsLost[key];
|
|
||||||
vec.reserve(8*8);
|
|
||||||
std::set_difference(
|
|
||||||
regions.begin(), regions.end(),
|
|
||||||
iterWorld->second.begin(), iterWorld->second.end(),
|
|
||||||
std::back_inserter(vec)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// shrink_to_feet
|
|
||||||
for(auto& [_, regions] : out.RegionsNew)
|
|
||||||
regions.shrink_to_fit();
|
|
||||||
for(auto& [_, regions] : out.RegionsLost)
|
|
||||||
regions.shrink_to_fit();
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
Мост контента, для отслеживания событий из удалённых точек
|
|
||||||
По типу портала, через который можно видеть контент на расстоянии
|
|
||||||
*/
|
|
||||||
struct ContentBridge {
|
|
||||||
/*
|
|
||||||
false -> Из точки Left видно контент в точки Right
|
|
||||||
true -> Контент виден в обе стороны
|
|
||||||
*/
|
|
||||||
bool IsTwoWay = false;
|
|
||||||
WorldId_t LeftWorld;
|
|
||||||
Pos::GlobalRegion LeftPos;
|
|
||||||
WorldId_t RightWorld;
|
|
||||||
Pos::GlobalRegion RightPos;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ContentViewCircle {
|
|
||||||
WorldId_t WorldId;
|
|
||||||
Pos::GlobalRegion Pos;
|
|
||||||
// Радиус в регионах в квадрате
|
|
||||||
int16_t Range;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,490 +0,0 @@
|
|||||||
#include "AssetsManager.hpp"
|
|
||||||
#include "Common/Abstract.hpp"
|
|
||||||
#include "boost/json.hpp"
|
|
||||||
#include "png++/rgb_pixel.hpp"
|
|
||||||
#include <exception>
|
|
||||||
#include <filesystem>
|
|
||||||
#include <png.h>
|
|
||||||
#include <pngconf.h>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <unordered_set>
|
|
||||||
#include <utility>
|
|
||||||
#include "sol/sol.hpp"
|
|
||||||
|
|
||||||
|
|
||||||
namespace LV::Server {
|
|
||||||
|
|
||||||
PreparedModelCollision::PreparedModelCollision(const PreparedModel& model) {
|
|
||||||
Cuboids.reserve(model.Cuboids.size());
|
|
||||||
|
|
||||||
for(const PreparedModel::Cuboid& cuboid : model.Cuboids) {
|
|
||||||
Cuboid result;
|
|
||||||
result.From = cuboid.From;
|
|
||||||
result.To = cuboid.To;
|
|
||||||
result.Faces = 0;
|
|
||||||
|
|
||||||
for(const auto& [key, _] : cuboid.Faces)
|
|
||||||
result.Faces |= (1 << int(key));
|
|
||||||
|
|
||||||
result.Transformations = cuboid.Transformations;
|
|
||||||
}
|
|
||||||
|
|
||||||
SubModels = model.SubModels;
|
|
||||||
}
|
|
||||||
|
|
||||||
PreparedModelCollision::PreparedModelCollision(const std::string& domain, const js::object& glTF) {
|
|
||||||
// gltf
|
|
||||||
|
|
||||||
// Сцена по умолчанию
|
|
||||||
// Сцены -> Ноды
|
|
||||||
// Ноды -> Ноды, меши, матрицы, translation, rotation
|
|
||||||
// Меши -> Примитивы
|
|
||||||
// Примитивы -> Материал, вершинные данные
|
|
||||||
// Материалы -> текстуры
|
|
||||||
// Текстуры
|
|
||||||
// Буферы
|
|
||||||
}
|
|
||||||
|
|
||||||
PreparedModelCollision::PreparedModelCollision(const std::string& domain, Resource glb) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetsManager::loadResourceFromFile(EnumAssets type, ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const {
|
|
||||||
switch(type) {
|
|
||||||
case EnumAssets::Nodestate: loadResourceFromFile_Nodestate (out, domain, key, path); return;
|
|
||||||
case EnumAssets::Particle: loadResourceFromFile_Particle (out, domain, key, path); return;
|
|
||||||
case EnumAssets::Animation: loadResourceFromFile_Animation (out, domain, key, path); return;
|
|
||||||
case EnumAssets::Model: loadResourceFromFile_Model (out, domain, key, path); return;
|
|
||||||
case EnumAssets::Texture: loadResourceFromFile_Texture (out, domain, key, path); return;
|
|
||||||
case EnumAssets::Sound: loadResourceFromFile_Sound (out, domain, key, path); return;
|
|
||||||
case EnumAssets::Font: loadResourceFromFile_Font (out, domain, key, path); return;
|
|
||||||
default:
|
|
||||||
std::unreachable();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetsManager::loadResourceFromLua(EnumAssets type, ResourceChangeObj& out, const std::string& domain, const std::string& key, const sol::table& profile) const {
|
|
||||||
switch(type) {
|
|
||||||
case EnumAssets::Nodestate: loadResourceFromLua_Nodestate(out, domain, key, profile); return;
|
|
||||||
case EnumAssets::Particle: loadResourceFromLua_Particle(out, domain, key, profile); return;
|
|
||||||
case EnumAssets::Animation: loadResourceFromLua_Animation(out, domain, key, profile); return;
|
|
||||||
case EnumAssets::Model: loadResourceFromLua_Model(out, domain, key, profile); return;
|
|
||||||
case EnumAssets::Texture: loadResourceFromLua_Texture(out, domain, key, profile); return;
|
|
||||||
case EnumAssets::Sound: loadResourceFromLua_Sound(out, domain, key, profile); return;
|
|
||||||
case EnumAssets::Font: loadResourceFromLua_Font(out, domain, key, profile); return;
|
|
||||||
default:
|
|
||||||
std::unreachable();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetsManager::loadResourceFromFile_Nodestate(ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const {
|
|
||||||
Resource res(path);
|
|
||||||
js::object obj = js::parse(std::string_view((const char*) res.data(), res.size())).as_object();
|
|
||||||
PreparedNodeState pns(domain, obj);
|
|
||||||
std::u8string data = pns.dump();
|
|
||||||
Resource result((const uint8_t*) data.data(), data.size());
|
|
||||||
out.Nodestates[domain].emplace_back(key, std::move(pns));
|
|
||||||
out.NewOrChange[(int) EnumAssets::Nodestate][domain].emplace_back(key, result, fs::last_write_time(path));
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetsManager::loadResourceFromFile_Particle(ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const {
|
|
||||||
std::unreachable();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetsManager::loadResourceFromFile_Animation(ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const {
|
|
||||||
std::unreachable();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetsManager::loadResourceFromFile_Model(ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const {
|
|
||||||
/*
|
|
||||||
json, glTF, glB
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Либо это внутренний формат, либо glTF
|
|
||||||
|
|
||||||
Resource res(path);
|
|
||||||
std::filesystem::file_time_type ftt = fs::last_write_time(path);
|
|
||||||
|
|
||||||
if(path.extension() == "json") {
|
|
||||||
js::object obj = js::parse(std::string_view((const char*) res.data(), res.size())).as_object();
|
|
||||||
PreparedModel pm(domain, obj);
|
|
||||||
PreparedModelCollision pmc(pm);
|
|
||||||
std::u8string data = pm.dump();
|
|
||||||
out.Models[domain].emplace_back(key, pmc);
|
|
||||||
out.NewOrChange[(int) EnumAssets::Model][domain].emplace_back(key, Resource((const uint8_t*) data.data(), data.size()), ftt);
|
|
||||||
} else if(path.extension() == "gltf") {
|
|
||||||
js::object obj = js::parse(std::string_view((const char*) res.data(), res.size())).as_object();
|
|
||||||
PreparedModelCollision pmc(domain, obj);
|
|
||||||
out.Models[domain].emplace_back(key, pmc);
|
|
||||||
out.NewOrChange[(int) EnumAssets::Model][domain].emplace_back(key, res, ftt);
|
|
||||||
} else if(path.extension() == "glb") {
|
|
||||||
PreparedModelCollision pmc(domain, res);
|
|
||||||
out.Models[domain].emplace_back(key, pmc);
|
|
||||||
out.NewOrChange[(int) EnumAssets::Model][domain].emplace_back(key, res, ftt);
|
|
||||||
} else {
|
|
||||||
MAKE_ERROR("Не поддерживаемый формат файла");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetsManager::loadResourceFromFile_Texture(ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const {
|
|
||||||
Resource res(path);
|
|
||||||
|
|
||||||
if(res.size() < 8)
|
|
||||||
MAKE_ERROR("Файл не является текстурой png или jpeg (недостаточный размер файла)");
|
|
||||||
|
|
||||||
if(png_check_sig(reinterpret_cast<png_bytep>((unsigned char*) res.data()), 8)) {
|
|
||||||
// Это png
|
|
||||||
fs::file_time_type lwt = fs::last_write_time(path);
|
|
||||||
out.NewOrChange[(int) EnumAssets::Texture][domain].emplace_back(key, res, lwt);
|
|
||||||
return;
|
|
||||||
} else if((int) res.data()[0] == 0xFF && (int) res.data()[1] == 0xD8) {
|
|
||||||
// Это jpeg
|
|
||||||
fs::file_time_type lwt = fs::last_write_time(path);
|
|
||||||
out.NewOrChange[(int) EnumAssets::Texture][domain].emplace_back(key, res, lwt);
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
MAKE_ERROR("Файл не является текстурой png или jpeg");
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unreachable();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetsManager::loadResourceFromFile_Sound(ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const {
|
|
||||||
std::unreachable();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetsManager::loadResourceFromFile_Font(ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const {
|
|
||||||
std::unreachable();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetsManager::loadResourceFromLua_Nodestate(ResourceChangeObj& out, const std::string& domain, const std::string& key, const sol::table& profile) const {
|
|
||||||
if(std::optional<std::string> path = profile.get<std::optional<std::string>>("path")) {
|
|
||||||
out.NewOrChange[(int) EnumAssets::Nodestate][domain].emplace_back(key, Resource(*path), fs::file_time_type::min());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unreachable();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetsManager::loadResourceFromLua_Particle(ResourceChangeObj& out, const std::string& domain, const std::string& key, const sol::table& profile) const {
|
|
||||||
if(std::optional<std::string> path = profile.get<std::optional<std::string>>("path")) {
|
|
||||||
out.NewOrChange[(int) EnumAssets::Particle][domain].emplace_back(key, Resource(*path), fs::file_time_type::min());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unreachable();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetsManager::loadResourceFromLua_Animation(ResourceChangeObj& out, const std::string& domain, const std::string& key, const sol::table& profile) const {
|
|
||||||
if(std::optional<std::string> path = profile.get<std::optional<std::string>>("path")) {
|
|
||||||
out.NewOrChange[(int) EnumAssets::Animation][domain].emplace_back(key, Resource(*path), fs::file_time_type::min());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unreachable();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetsManager::loadResourceFromLua_Model(ResourceChangeObj& out, const std::string& domain, const std::string& key, const sol::table& profile) const {
|
|
||||||
if(std::optional<std::string> path = profile.get<std::optional<std::string>>("path")) {
|
|
||||||
out.NewOrChange[(int) EnumAssets::Model][domain].emplace_back(key, Resource(*path), fs::file_time_type::min());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unreachable();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetsManager::loadResourceFromLua_Texture(ResourceChangeObj& out, const std::string& domain, const std::string& key, const sol::table& profile) const {
|
|
||||||
if(std::optional<std::string> path = profile.get<std::optional<std::string>>("path")) {
|
|
||||||
out.NewOrChange[(int) EnumAssets::Texture][domain].emplace_back(key, Resource(*path), fs::file_time_type::min());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unreachable();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetsManager::loadResourceFromLua_Sound(ResourceChangeObj& out, const std::string& domain, const std::string& key, const sol::table& profile) const {
|
|
||||||
if(std::optional<std::string> path = profile.get<std::optional<std::string>>("path")) {
|
|
||||||
out.NewOrChange[(int) EnumAssets::Sound][domain].emplace_back(key, Resource(*path), fs::file_time_type::min());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unreachable();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetsManager::loadResourceFromLua_Font(ResourceChangeObj& out, const std::string& domain, const std::string& key, const sol::table& profile) const {
|
|
||||||
if(std::optional<std::string> path = profile.get<std::optional<std::string>>("path")) {
|
|
||||||
out.NewOrChange[(int) EnumAssets::Font][domain].emplace_back(key, Resource(*path), fs::file_time_type::min());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unreachable();
|
|
||||||
}
|
|
||||||
|
|
||||||
AssetsManager::AssetsManager(asio::io_context& ioc)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
AssetsManager::~AssetsManager() = default;
|
|
||||||
|
|
||||||
std::tuple<ResourceId, std::optional<AssetsManager::DataEntry>&> AssetsManager::Local::nextId(EnumAssets type) {
|
|
||||||
auto& table = Table[(int) type];
|
|
||||||
ResourceId id = -1;
|
|
||||||
std::optional<DataEntry> *data = nullptr;
|
|
||||||
|
|
||||||
for(size_t index = 0; index < table.size(); index++) {
|
|
||||||
auto& entry = *table[index];
|
|
||||||
|
|
||||||
if(entry.IsFull)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
uint32_t pos = entry.Empty._Find_first();
|
|
||||||
entry.Empty.reset(pos);
|
|
||||||
|
|
||||||
if(entry.Empty._Find_first() == entry.Empty.size())
|
|
||||||
entry.IsFull = true;
|
|
||||||
|
|
||||||
id = index*TableEntry<DataEntry>::ChunkSize + pos;
|
|
||||||
data = &entry.Entries[pos];
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!data) {
|
|
||||||
table.emplace_back(std::make_unique<TableEntry<DataEntry>>());
|
|
||||||
id = (table.size()-1)*TableEntry<DataEntry>::ChunkSize;
|
|
||||||
data = &table.back()->Entries[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
return {id, *data};
|
|
||||||
}
|
|
||||||
|
|
||||||
AssetsManager::ResourceChangeObj AssetsManager::recheckResources(const AssetsRegister& info) {
|
|
||||||
ResourceChangeObj result;
|
|
||||||
|
|
||||||
// Найти пропавшие ресурсы
|
|
||||||
for(int type = 0; type < (int) EnumAssets::MAX_ENUM; type++) {
|
|
||||||
auto lock = LocalObj.lock();
|
|
||||||
for(auto& [domain, resources] : lock->KeyToId[type]) {
|
|
||||||
for(auto& [key, id] : resources) {
|
|
||||||
if(!lock->Table[type][id / TableEntry<DataEntry>::ChunkSize]->Entries[id % TableEntry<DataEntry>::ChunkSize])
|
|
||||||
continue;
|
|
||||||
|
|
||||||
bool exists = false;
|
|
||||||
|
|
||||||
for(const fs::path& path : info.Assets) {
|
|
||||||
fs::path file = path / domain;
|
|
||||||
|
|
||||||
switch ((EnumAssets) type) {
|
|
||||||
case EnumAssets::Nodestate: file /= "nodestate"; break;
|
|
||||||
case EnumAssets::Particle: file /= "particle"; break;
|
|
||||||
case EnumAssets::Animation: file /= "animation"; break;
|
|
||||||
case EnumAssets::Model: file /= "model"; break;
|
|
||||||
case EnumAssets::Texture: file /= "texture"; break;
|
|
||||||
case EnumAssets::Sound: file /= "sound"; break;
|
|
||||||
case EnumAssets::Font: file /= "font"; break;
|
|
||||||
default:
|
|
||||||
std::unreachable();
|
|
||||||
}
|
|
||||||
|
|
||||||
file /= key;
|
|
||||||
|
|
||||||
if(fs::exists(file) && !fs::is_directory(file)) {
|
|
||||||
exists = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(exists) continue;
|
|
||||||
|
|
||||||
auto iterDomain = info.Custom[type].find(domain);
|
|
||||||
if(iterDomain == info.Custom[type].end()) {
|
|
||||||
result.Lost[type][domain].push_back(key);
|
|
||||||
} else {
|
|
||||||
auto iterData = iterDomain->second.find(key);
|
|
||||||
if(iterData == iterDomain->second.end()) {
|
|
||||||
result.Lost[type][domain].push_back(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Если ресурс уже был найден более приоритетными директориями, то пропускаем его
|
|
||||||
std::unordered_map<std::string, std::unordered_set<std::string>> findedResources[(int) EnumAssets::MAX_ENUM];
|
|
||||||
|
|
||||||
// Найти новые или изменённые ресурсы
|
|
||||||
for(int type = 0; type < (int) EnumAssets::MAX_ENUM; type++) {
|
|
||||||
for(auto& [domain, resources] : info.Custom[type]) {
|
|
||||||
auto lock = LocalObj.lock();
|
|
||||||
const auto& keyToId = lock->KeyToId[type];
|
|
||||||
auto iterDomain = keyToId.find(domain);
|
|
||||||
auto& findList = findedResources[type][domain];
|
|
||||||
|
|
||||||
if(iterDomain == keyToId.end()) {
|
|
||||||
// Ресурсы данного домена неизвестны
|
|
||||||
auto& domainList = result.NewOrChange[type][domain];
|
|
||||||
for(auto& [key, id] : resources) {
|
|
||||||
// Подобрать идентификатор
|
|
||||||
// TODO: реализовать регистрации ресурсов из lua
|
|
||||||
domainList.emplace_back(key, Resource("assets/null"), fs::file_time_type::min());
|
|
||||||
findList.insert(key);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for(auto& [key, id] : resources) {
|
|
||||||
if(findList.contains(key))
|
|
||||||
// Ресурс уже был найден в вышестоящей директории
|
|
||||||
continue;
|
|
||||||
else if(iterDomain->second.contains(key)) {
|
|
||||||
// Ресурс уже есть, TODO: нужно проверить его изменение
|
|
||||||
loadResourceFromFile((EnumAssets) type, result, domain, key, "assets/null");
|
|
||||||
} else {
|
|
||||||
// Ресурс не был известен
|
|
||||||
loadResourceFromFile((EnumAssets) type, result, domain, key, "assets/null");
|
|
||||||
}
|
|
||||||
|
|
||||||
findList.insert(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for(const fs::path& path : info.Assets) {
|
|
||||||
if(!fs::exists(path))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
for(auto begin = fs::directory_iterator(path), end = fs::directory_iterator(); begin != end; begin++) {
|
|
||||||
if(!begin->is_directory())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
fs::path domainPath = begin->path();
|
|
||||||
std::string domain = domainPath.filename();
|
|
||||||
|
|
||||||
for(int type = 0; type < (int) EnumAssets::MAX_ENUM; type++) {
|
|
||||||
fs::path resourcesPath = domainPath;
|
|
||||||
|
|
||||||
switch ((EnumAssets) type) {
|
|
||||||
case EnumAssets::Nodestate: resourcesPath /= "nodestate"; break;
|
|
||||||
case EnumAssets::Particle: resourcesPath /= "particle"; break;
|
|
||||||
case EnumAssets::Animation: resourcesPath /= "animation"; break;
|
|
||||||
case EnumAssets::Model: resourcesPath /= "model"; break;
|
|
||||||
case EnumAssets::Texture: resourcesPath /= "texture"; break;
|
|
||||||
case EnumAssets::Sound: resourcesPath /= "sound"; break;
|
|
||||||
case EnumAssets::Font: resourcesPath /= "font"; break;
|
|
||||||
default:
|
|
||||||
std::unreachable();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto& findList = findedResources[type][domain];
|
|
||||||
auto lock = LocalObj.lock();
|
|
||||||
auto iterDomain = lock->KeyToId[type].find(domain);
|
|
||||||
|
|
||||||
if(!fs::exists(resourcesPath) || !fs::is_directory(resourcesPath))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Рекурсивно загрузить ресурсы внутри папки resourcesPath
|
|
||||||
for(auto begin = fs::recursive_directory_iterator(resourcesPath), end = fs::recursive_directory_iterator(); begin != end; begin++) {
|
|
||||||
if(begin->is_directory())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
fs::path file = begin->path();
|
|
||||||
std::string key = fs::relative(begin->path(), resourcesPath).string();
|
|
||||||
if(findList.contains(key))
|
|
||||||
// Ресурс уже был найден в вышестоящей директории
|
|
||||||
continue;
|
|
||||||
else if(iterDomain != lock->KeyToId[type].end() && iterDomain->second.contains(key)) {
|
|
||||||
// Ресурс уже есть, TODO: нужно проверить его изменение
|
|
||||||
ResourceId id = iterDomain->second.at(key);
|
|
||||||
DataEntry& entry = *lock->Table[type][id / TableEntry<DataEntry>::ChunkSize]->Entries[id % TableEntry<DataEntry>::ChunkSize];
|
|
||||||
|
|
||||||
fs::file_time_type lwt = fs::last_write_time(file);
|
|
||||||
if(lwt != entry.FileChangeTime)
|
|
||||||
// Будем считать что ресурс изменился
|
|
||||||
loadResourceFromFile((EnumAssets) type, result, domain, key, file);
|
|
||||||
} else {
|
|
||||||
// Ресурс не был известен
|
|
||||||
loadResourceFromFile((EnumAssets) type, result, domain, key, file);
|
|
||||||
}
|
|
||||||
|
|
||||||
findList.insert(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
AssetsManager::Out_applyResourceChange AssetsManager::applyResourceChange(const ResourceChangeObj& orr) {
|
|
||||||
// Потерянные и обновлённые идентификаторы
|
|
||||||
Out_applyResourceChange result;
|
|
||||||
|
|
||||||
// Удаляем ресурсы
|
|
||||||
/*
|
|
||||||
Удаляются только ресурсы, при этом за ними остаётся бронь на идентификатор
|
|
||||||
Уже скомпилированные зависимости к ресурсам не будут
|
|
||||||
перекомпилироваться для смены идентификатора. Если нужный ресурс
|
|
||||||
появится, то привязка останется. Новые клиенты не получат ресурс
|
|
||||||
которого нет, но он может использоваться
|
|
||||||
*/
|
|
||||||
for(int type = 0; type < (int) EnumAssets::MAX_ENUM; type++) {
|
|
||||||
for(auto& [domain, resources] : orr.Lost[type]) {
|
|
||||||
auto lock = LocalObj.lock();
|
|
||||||
auto& keyToIdDomain = lock->KeyToId[type].at(domain);
|
|
||||||
|
|
||||||
for(const std::string& key : resources) {
|
|
||||||
auto iter = keyToIdDomain.find(key);
|
|
||||||
assert(iter != keyToIdDomain.end());
|
|
||||||
|
|
||||||
ResourceId resId = iter->second;
|
|
||||||
// keyToIdDomain.erase(iter);
|
|
||||||
// lost[type].push_back(resId);
|
|
||||||
|
|
||||||
uint32_t localId = resId % TableEntry<DataEntry>::ChunkSize;
|
|
||||||
auto& chunk = lock->Table[type][resId / TableEntry<DataEntry>::ChunkSize];
|
|
||||||
// chunk->IsFull = false;
|
|
||||||
// chunk->Empty.set(localId);
|
|
||||||
chunk->Entries[localId].reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Добавляем
|
|
||||||
for(int type = 0; type < (int) EnumAssets::MAX_ENUM; type++) {
|
|
||||||
for(auto& [domain, resources] : orr.NewOrChange[type]) {
|
|
||||||
auto lock = LocalObj.lock();
|
|
||||||
auto& keyToIdDomain = lock->KeyToId[type][domain];
|
|
||||||
|
|
||||||
for(auto& [key, resource, lwt] : resources) {
|
|
||||||
ResourceId id = -1;
|
|
||||||
std::optional<DataEntry>* data = nullptr;
|
|
||||||
|
|
||||||
if(auto iterId = keyToIdDomain.find(key); iterId != keyToIdDomain.end()) {
|
|
||||||
id = iterId->second;
|
|
||||||
data = &lock->Table[(int) type][id / TableEntry<DataEntry>::ChunkSize]->Entries[id % TableEntry<DataEntry>::ChunkSize];
|
|
||||||
} else {
|
|
||||||
auto [_id, _data] = lock->nextId((EnumAssets) type);
|
|
||||||
id = _id;
|
|
||||||
data = &_data;
|
|
||||||
}
|
|
||||||
|
|
||||||
result.NewOrChange[type].push_back({id, resource});
|
|
||||||
keyToIdDomain[key] = id;
|
|
||||||
|
|
||||||
data->emplace(lwt, resource, domain, key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Удалённые идентификаторы не считаются удалёнными, если были изменены
|
|
||||||
std::unordered_set<ResourceId> noc;
|
|
||||||
for(auto& [id, _] : result.NewOrChange[type])
|
|
||||||
noc.insert(id);
|
|
||||||
|
|
||||||
std::unordered_set<ResourceId> l(result.Lost[type].begin(), result.Lost[type].end());
|
|
||||||
result.Lost[type].clear();
|
|
||||||
std::set_difference(l.begin(), l.end(), noc.begin(), noc.end(), std::back_inserter(result.Lost[type]));
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,194 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "Common/Abstract.hpp"
|
|
||||||
#include "TOSLib.hpp"
|
|
||||||
#include "Common/Net.hpp"
|
|
||||||
#include "sha2.hpp"
|
|
||||||
#include <bitset>
|
|
||||||
#include <filesystem>
|
|
||||||
#include <optional>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <variant>
|
|
||||||
|
|
||||||
|
|
||||||
namespace LV::Server {
|
|
||||||
|
|
||||||
namespace fs = std::filesystem;
|
|
||||||
|
|
||||||
/*
|
|
||||||
Используется для расчёта коллизии,
|
|
||||||
если это необходимо.
|
|
||||||
|
|
||||||
glTF конвертируется в кубы
|
|
||||||
*/
|
|
||||||
struct PreparedModelCollision {
|
|
||||||
struct Cuboid {
|
|
||||||
glm::vec3 From, To;
|
|
||||||
uint8_t Faces;
|
|
||||||
|
|
||||||
std::vector<PreparedModel::Cuboid::Transformation> Transformations;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::vector<Cuboid> Cuboids;
|
|
||||||
std::vector<PreparedModel::SubModel> SubModels;
|
|
||||||
|
|
||||||
PreparedModelCollision(const PreparedModel& model);
|
|
||||||
PreparedModelCollision(const std::string& domain, const js::object& glTF);
|
|
||||||
PreparedModelCollision(const std::string& domain, Resource glb);
|
|
||||||
|
|
||||||
PreparedModelCollision() = default;
|
|
||||||
PreparedModelCollision(const PreparedModelCollision&) = default;
|
|
||||||
PreparedModelCollision(PreparedModelCollision&&) = default;
|
|
||||||
|
|
||||||
PreparedModelCollision& operator=(const PreparedModelCollision&) = default;
|
|
||||||
PreparedModelCollision& operator=(PreparedModelCollision&&) = default;
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
Работает с ресурсами из папок assets.
|
|
||||||
Использует папку server_cache/assets для хранения
|
|
||||||
преобразованных ресурсов
|
|
||||||
*/
|
|
||||||
class AssetsManager {
|
|
||||||
public:
|
|
||||||
struct ResourceChangeObj {
|
|
||||||
// Потерянные ресурсы
|
|
||||||
std::unordered_map<std::string, std::vector<std::string>> Lost[(int) EnumAssets::MAX_ENUM];
|
|
||||||
// Домен и ключ ресурса
|
|
||||||
std::unordered_map<std::string, std::vector<std::tuple<std::string, Resource, fs::file_time_type>>> NewOrChange[(int) EnumAssets::MAX_ENUM];
|
|
||||||
|
|
||||||
|
|
||||||
std::unordered_map<std::string, std::vector<std::pair<std::string, PreparedNodeState>>> Nodestates;
|
|
||||||
std::unordered_map<std::string, std::vector<std::pair<std::string, PreparedModelCollision>>> Models;
|
|
||||||
};
|
|
||||||
|
|
||||||
private:
|
|
||||||
// Данные об отслеживаемых файлах
|
|
||||||
struct DataEntry {
|
|
||||||
// Время последнего изменения файла
|
|
||||||
fs::file_time_type FileChangeTime;
|
|
||||||
Resource Res;
|
|
||||||
std::string Domain, Key;
|
|
||||||
};
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
struct TableEntry {
|
|
||||||
static constexpr size_t ChunkSize = 4096;
|
|
||||||
bool IsFull = false;
|
|
||||||
std::bitset<ChunkSize> Empty;
|
|
||||||
std::array<std::optional<DataEntry>, ChunkSize> Entries;
|
|
||||||
|
|
||||||
TableEntry() {
|
|
||||||
Empty.set();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Local {
|
|
||||||
// Связь ресурсов по идентификаторам
|
|
||||||
std::vector<std::unique_ptr<TableEntry<DataEntry>>> Table[(int) EnumAssets::MAX_ENUM];
|
|
||||||
|
|
||||||
// Распаршенные ресурсы, для использования сервером
|
|
||||||
std::vector<std::unique_ptr<TableEntry<PreparedNodeState>>> Table_NodeState;
|
|
||||||
std::vector<std::unique_ptr<TableEntry<PreparedModelCollision>>> Table_Model;
|
|
||||||
|
|
||||||
// Связь домены -> {ключ -> идентификатор}
|
|
||||||
std::unordered_map<std::string, std::unordered_map<std::string, ResourceId>> KeyToId[(int) EnumAssets::MAX_ENUM];
|
|
||||||
|
|
||||||
std::tuple<ResourceId, std::optional<DataEntry>&> nextId(EnumAssets type);
|
|
||||||
};
|
|
||||||
|
|
||||||
TOS::SpinlockObject<Local> LocalObj;
|
|
||||||
|
|
||||||
/*
|
|
||||||
Загрузка ресурса с файла. При необходимости приводится
|
|
||||||
к внутреннему формату и сохраняется в кеше
|
|
||||||
*/
|
|
||||||
void loadResourceFromFile (EnumAssets type, ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const;
|
|
||||||
void loadResourceFromLua (EnumAssets type, ResourceChangeObj& out, const std::string& domain, const std::string& key, const sol::table& profile) const;
|
|
||||||
|
|
||||||
void loadResourceFromFile_Nodestate (ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const;
|
|
||||||
void loadResourceFromFile_Particle (ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const;
|
|
||||||
void loadResourceFromFile_Animation (ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const;
|
|
||||||
void loadResourceFromFile_Model (ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const;
|
|
||||||
void loadResourceFromFile_Texture (ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const;
|
|
||||||
void loadResourceFromFile_Sound (ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const;
|
|
||||||
void loadResourceFromFile_Font (ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const;
|
|
||||||
|
|
||||||
void loadResourceFromLua_Nodestate (ResourceChangeObj& out, const std::string& domain, const std::string& key, const sol::table& profile) const;
|
|
||||||
void loadResourceFromLua_Particle (ResourceChangeObj& out, const std::string& domain, const std::string& key, const sol::table& profile) const;
|
|
||||||
void loadResourceFromLua_Animation (ResourceChangeObj& out, const std::string& domain, const std::string& key, const sol::table& profile) const;
|
|
||||||
void loadResourceFromLua_Model (ResourceChangeObj& out, const std::string& domain, const std::string& key, const sol::table& profile) const;
|
|
||||||
void loadResourceFromLua_Texture (ResourceChangeObj& out, const std::string& domain, const std::string& key, const sol::table& profile) const;
|
|
||||||
void loadResourceFromLua_Sound (ResourceChangeObj& out, const std::string& domain, const std::string& key, const sol::table& profile) const;
|
|
||||||
void loadResourceFromLua_Font (ResourceChangeObj& out, const std::string& domain, const std::string& key, const sol::table& profile) const;
|
|
||||||
|
|
||||||
public:
|
|
||||||
AssetsManager(asio::io_context& ioc);
|
|
||||||
~AssetsManager();
|
|
||||||
|
|
||||||
/*
|
|
||||||
Перепроверка изменений ресурсов по дате изменения, пересчёт хешей.
|
|
||||||
Обнаруженные изменения должны быть отправлены всем клиентам.
|
|
||||||
Ресурсы будут обработаны в подходящий формат и сохранены в кеше.
|
|
||||||
Одновременно может выполнятся только одна такая функция
|
|
||||||
Используется в GameServer
|
|
||||||
*/
|
|
||||||
|
|
||||||
struct AssetsRegister {
|
|
||||||
/*
|
|
||||||
Пути до активных папок assets, соответствую порядку загруженным модам.
|
|
||||||
От последнего мода к первому.
|
|
||||||
Тот файл, что был загружен раньше и будет использоваться
|
|
||||||
*/
|
|
||||||
std::vector<fs::path> Assets;
|
|
||||||
/*
|
|
||||||
У этих ресурсов приоритет выше, если их удастся получить,
|
|
||||||
то использоваться будут именно они
|
|
||||||
Domain -> {key + data}
|
|
||||||
*/
|
|
||||||
std::unordered_map<std::string, std::unordered_map<std::string, void*>> Custom[(int) EnumAssets::MAX_ENUM];
|
|
||||||
};
|
|
||||||
|
|
||||||
ResourceChangeObj recheckResources(const AssetsRegister&);
|
|
||||||
|
|
||||||
/*
|
|
||||||
Применяет расчитанные изменения.
|
|
||||||
Раздаёт идентификаторы ресурсам и записывает их в таблицу
|
|
||||||
*/
|
|
||||||
struct Out_applyResourceChange {
|
|
||||||
std::vector<ResourceId> Lost[(int) EnumAssets::MAX_ENUM];
|
|
||||||
std::vector<std::pair<ResourceId, Resource>> NewOrChange[(int) EnumAssets::MAX_ENUM];
|
|
||||||
};
|
|
||||||
|
|
||||||
Out_applyResourceChange applyResourceChange(const ResourceChangeObj& orr);
|
|
||||||
|
|
||||||
/*
|
|
||||||
Выдаёт идентификатор ресурса, даже если он не существует или был удалён.
|
|
||||||
resource должен содержать домен и путь
|
|
||||||
*/
|
|
||||||
ResourceId getId(EnumAssets type, const std::string& domain, const std::string& key) {
|
|
||||||
auto lock = LocalObj.lock();
|
|
||||||
auto& keyToId = lock->KeyToId[(int) type];
|
|
||||||
if(auto iterKTI = keyToId.find(domain); iterKTI != keyToId.end()) {
|
|
||||||
if(auto iterKey = iterKTI->second.find(key); iterKey != iterKTI->second.end()) {
|
|
||||||
return iterKey->second;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto [id, entry] = lock->nextId(type);
|
|
||||||
keyToId[domain][key] = id;
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<std::tuple<Resource, const std::string&, const std::string&>> getResource(EnumAssets type, ResourceId id) {
|
|
||||||
auto lock = LocalObj.lock();
|
|
||||||
assert(id < lock->Table[(int) type].size()*TableEntry<DataEntry>::ChunkSize);
|
|
||||||
auto& value = lock->Table[(int) type][id / TableEntry<DataEntry>::ChunkSize]->Entries[id % TableEntry<DataEntry>::ChunkSize];
|
|
||||||
if(value)
|
|
||||||
return {{value->Res, value->Domain, value->Key}};
|
|
||||||
else
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
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 <Common/Abstract.hpp>
|
||||||
#include "Abstract.hpp"
|
#include "Abstract.hpp"
|
||||||
#include "TOSLib.hpp"
|
|
||||||
#include <algorithm>
|
|
||||||
#include <bitset>
|
#include <bitset>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <queue>
|
#include <optional>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@@ -16,44 +14,225 @@
|
|||||||
namespace LV::Server {
|
namespace LV::Server {
|
||||||
|
|
||||||
class RemoteClient;
|
class RemoteClient;
|
||||||
|
using RemoteClient_ptr = std::unique_ptr<RemoteClient, std::function<void(RemoteClient*)>>;
|
||||||
class GameServer;
|
class GameServer;
|
||||||
class World;
|
class World;
|
||||||
|
|
||||||
|
|
||||||
|
struct ServerObjectPos {
|
||||||
|
WorldId_t WorldId;
|
||||||
|
Pos::Object ObjectPos;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Сфера в которой отслеживаются события игроком
|
||||||
|
*/
|
||||||
|
struct ContentViewCircle {
|
||||||
|
WorldId_t WorldId;
|
||||||
|
// Позиция в чанках
|
||||||
|
glm::i16vec3 Pos;
|
||||||
|
// (Единица равна размеру чанка) в квадрате
|
||||||
|
int32_t Range;
|
||||||
|
|
||||||
|
inline int32_t sqrDistance(Pos::GlobalRegion regionPos) const {
|
||||||
|
glm::i32vec3 vec = {Pos.x-((regionPos.X << 4) | 0b1000), Pos.y-((regionPos.Y << 4) | 0b1000), Pos.z-((regionPos.Z << 4) | 0b1000)};
|
||||||
|
return vec.x*vec.x+vec.y*vec.y+vec.z*vec.z;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline int32_t sqrDistance(Pos::GlobalChunk chunkPos) const {
|
||||||
|
glm::i32vec3 vec = {Pos.x-chunkPos.X, Pos.y-chunkPos.Y, Pos.z-chunkPos.Z};
|
||||||
|
return vec.x*vec.x+vec.y*vec.y+vec.z*vec.z;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline int64_t sqrDistance(Pos::Object objectPos) const {
|
||||||
|
glm::i32vec3 vec = {Pos.x-(objectPos.x >> 20), Pos.y-(objectPos.y >> 20), Pos.z-(objectPos.z >> 20)};
|
||||||
|
return vec.x*vec.x+vec.y*vec.y+vec.z*vec.z;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool isIn(Pos::GlobalRegion regionPos) const {
|
||||||
|
return sqrDistance(regionPos) < Range+192; // (8×sqrt(3))^2
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isIn(Pos::GlobalChunk chunkPos) const {
|
||||||
|
return sqrDistance(chunkPos) < Range+3; // (1×sqrt(3))^2
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isIn(Pos::Object objectPos, int32_t size = 0) const {
|
||||||
|
return sqrDistance(objectPos) < Range+3+size;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Регион -> чанки попавшие под обозрение Pos::Local16_u
|
||||||
|
using ContentViewWorld = std::map<Pos::GlobalRegion, std::bitset<4096>>; // 1 - чанк виден, 0 - не виден
|
||||||
|
|
||||||
|
struct ContentViewGlobal_DiffInfo;
|
||||||
|
|
||||||
|
struct ContentViewGlobal : public std::map<WorldId_t, ContentViewWorld> {
|
||||||
|
// Вычисляет половинную разницу между текущей и предыдущей области видимости
|
||||||
|
// Возвращает области, которые появились по отношению к old, чтобы получить области потерянные из виду поменять местами *this и old
|
||||||
|
ContentViewGlobal_DiffInfo calcDiffWith(const ContentViewGlobal &old) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ContentViewGlobal_DiffInfo {
|
||||||
|
// Новые увиденные чанки
|
||||||
|
ContentViewGlobal View;
|
||||||
|
// Регионы
|
||||||
|
std::unordered_map<WorldId_t, std::vector<Pos::GlobalRegion>> Regions;
|
||||||
|
// Миры
|
||||||
|
std::vector<WorldId_t> Worlds;
|
||||||
|
|
||||||
|
bool empty() const {
|
||||||
|
return View.empty() && Regions.empty() && Worlds.empty();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
inline ContentViewGlobal_DiffInfo ContentViewGlobal::calcDiffWith(const ContentViewGlobal &old) const {
|
||||||
|
ContentViewGlobal_DiffInfo newView;
|
||||||
|
|
||||||
|
// Рассматриваем разницу меж мирами
|
||||||
|
for(const auto &[newWorldId, newWorldView] : *this) {
|
||||||
|
auto oldWorldIter = old.find(newWorldId);
|
||||||
|
if(oldWorldIter == old.end()) { // В старом состоянии нет мира
|
||||||
|
newView.View[newWorldId] = newWorldView;
|
||||||
|
newView.Worlds.push_back(newWorldId);
|
||||||
|
auto &newRegions = newView.Regions[newWorldId];
|
||||||
|
for(const auto &[regionPos, _] : newWorldView)
|
||||||
|
newRegions.push_back(regionPos);
|
||||||
|
} else {
|
||||||
|
const std::map<Pos::GlobalRegion, std::bitset<4096>> &newRegions = newWorldView;
|
||||||
|
const std::map<Pos::GlobalRegion, std::bitset<4096>> &oldRegions = oldWorldIter->second;
|
||||||
|
std::map<Pos::GlobalRegion, std::bitset<4096>> *diffRegions = nullptr;
|
||||||
|
|
||||||
|
// Рассматриваем разницу меж регионами
|
||||||
|
for(const auto &[newRegionPos, newRegionBitField] : newRegions) {
|
||||||
|
auto oldRegionIter = oldRegions.find(newRegionPos);
|
||||||
|
if(oldRegionIter == oldRegions.end()) { // В старой описи мира нет региона
|
||||||
|
if(!diffRegions)
|
||||||
|
diffRegions = &newView.View[newWorldId];
|
||||||
|
|
||||||
|
(*diffRegions)[newRegionPos] = newRegionBitField;
|
||||||
|
newView.Regions[newWorldId].push_back(newRegionPos);
|
||||||
|
} else {
|
||||||
|
const std::bitset<4096> &oldChunks = oldRegionIter->second;
|
||||||
|
std::bitset<4096> chunks = (~oldChunks) & newRegionBitField; // Останется поле с новыми чанками
|
||||||
|
if(chunks._Find_first() != chunks.size()) {
|
||||||
|
// Есть новые чанки
|
||||||
|
if(!diffRegions)
|
||||||
|
diffRegions = &newView.View[newWorldId];
|
||||||
|
|
||||||
|
(*diffRegions)[newRegionPos] = chunks;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return newView;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Мост контента, для отслеживания событий из удалённх точек
|
||||||
|
По типу портала, через который можно видеть контент на расстоянии
|
||||||
|
*/
|
||||||
|
struct ContentBridge {
|
||||||
|
/*
|
||||||
|
false -> Из точки From видно контент из точки To
|
||||||
|
true -> Контент виден в обе стороны
|
||||||
|
*/
|
||||||
|
bool IsTwoWay = false;
|
||||||
|
WorldId_t LeftWorld;
|
||||||
|
// Позиция в чанках
|
||||||
|
glm::i16vec3 LeftPos;
|
||||||
|
WorldId_t RightWorld;
|
||||||
|
// Позиция в чанках
|
||||||
|
glm::i16vec3 RightPos;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/* Игрок */
|
/* Игрок */
|
||||||
class ContentEventController {
|
class ContentEventController {
|
||||||
public:
|
private:
|
||||||
std::queue<Pos::GlobalNode> Build, Break;
|
|
||||||
|
struct SubscribedObj {
|
||||||
|
// Используется регионами
|
||||||
|
std::vector<PortalId_t> Portals;
|
||||||
|
std::unordered_map<WorldId_t, std::unordered_map<Pos::GlobalRegion, std::unordered_set<LocalEntityId_t>>> Entities;
|
||||||
|
} Subscribed;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ContentEventController(std::unique_ptr<RemoteClient>&& remote);
|
// Управляется сервером
|
||||||
|
RemoteClient_ptr Remote;
|
||||||
|
// Регионы сюда заглядывают
|
||||||
|
// Каждый такт значения изменений обновляются GameServer'ом
|
||||||
|
// Объявленная в чанках территория точно отслеживается (активная зона)
|
||||||
|
ContentViewGlobal ContentViewState;
|
||||||
|
ContentViewGlobal_DiffInfo ContentView_NewView, ContentView_LostView;
|
||||||
|
|
||||||
// Измеряется в чанках в регионах (активная зона)
|
// size_t CVCHash = 0; // Хэш для std::vector<ContentViewCircle>
|
||||||
static constexpr uint16_t getViewRangeActive() { return 2; }
|
// std::unordered_map<WorldId_t, std::vector<Pos::GlobalRegion>> SubscribedRegions;
|
||||||
|
|
||||||
|
public:
|
||||||
|
ContentEventController(RemoteClient_ptr &&remote);
|
||||||
|
|
||||||
|
// Измеряется в чанках в радиусе (активная зона)
|
||||||
|
uint16_t getViewRangeActive() const;
|
||||||
// Измеряется в чанках в радиусе (Декоративная зона) + getViewRangeActive()
|
// Измеряется в чанках в радиусе (Декоративная зона) + getViewRangeActive()
|
||||||
uint16_t getViewRangeBackground() const;
|
uint16_t getViewRangeBackground() const;
|
||||||
ServerObjectPos getLastPos() const;
|
ServerObjectPos getLastPos() const;
|
||||||
ServerObjectPos getPos() const;
|
ServerObjectPos getPos() const;
|
||||||
|
|
||||||
// Очищает более не наблюдаемые чанки и миры
|
// Проверка на необходимость подгрузки новых определений миров
|
||||||
void removeUnobservable(const ContentViewInfo_Diff& diff);
|
// и очистка клиента от не наблюдаемых данных
|
||||||
|
void checkContentViewChanges();
|
||||||
// Здесь приходят частично фильтрованные события
|
// Здесь приходят частично фильтрованные события
|
||||||
// Фильтровать не отслеживаемые миры
|
// Фильтровать не отслеживаемые миры
|
||||||
void onWorldUpdate(WorldId_t worldId, World *worldObj);
|
void onWorldUpdate(WorldId_t worldId, World *worldObj);
|
||||||
|
|
||||||
void onEntityEnterLost(WorldId_t worldId, Pos::GlobalRegion regionPos, const std::unordered_set<RegionEntityId_t> &enter, const std::unordered_set<RegionEntityId_t> &lost);
|
// Нужно фильтровать неотслеживаемые чанки
|
||||||
void onEntitySwap(ServerEntityId_t prevId, ServerEntityId_t newId);
|
void onChunksUpdate_Voxels(WorldId_t worldId, Pos::GlobalRegion regionPos, const std::unordered_map<Pos::Local16_u, const std::vector<VoxelCube>*> &chunks);
|
||||||
void onEntityUpdates(WorldId_t worldId, Pos::GlobalRegion regionPos, const std::unordered_map<RegionEntityId_t, Entity*> &entities);
|
void onChunksUpdate_Nodes(WorldId_t worldId, Pos::GlobalRegion regionPos, const std::unordered_map<Pos::Local16_u, const std::unordered_map<Pos::Local16_u, Node>*> &chunks);
|
||||||
|
void onChunksUpdate_LightPrism(WorldId_t worldId, Pos::GlobalRegion regionPos, const std::unordered_map<Pos::Local16_u, const LightPrism*> &chunks);
|
||||||
|
|
||||||
|
void onEntityEnterLost(WorldId_t worldId, Pos::GlobalRegion regionPos, const std::unordered_set<LocalEntityId_t> &enter, const std::unordered_set<LocalEntityId_t> &lost);
|
||||||
|
void onEntitySwap(WorldId_t lastWorldId, Pos::GlobalRegion lastRegionPos, LocalEntityId_t lastId, WorldId_t newWorldId, Pos::GlobalRegion newRegionPos, LocalEntityId_t newId);
|
||||||
|
void onEntityUpdates(WorldId_t worldId, Pos::GlobalRegion regionPos, const std::vector<Entity> &entities);
|
||||||
|
|
||||||
void onPortalEnterLost(const std::vector<void*> &enter, const std::vector<void*> &lost);
|
void onPortalEnterLost(const std::vector<void*> &enter, const std::vector<void*> &lost);
|
||||||
void onPortalUpdates(const std::vector<void*> &portals);
|
void onPortalUpdates(const std::vector<void*> &portals);
|
||||||
|
|
||||||
inline const SubscribedObj& getSubscribed() { return Subscribed; };
|
inline const SubscribedObj& getSubscribed() { return Subscribed; };
|
||||||
|
|
||||||
void onUpdate();
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace std {
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct hash<LV::Server::ServerObjectPos> {
|
||||||
|
std::size_t operator()(const LV::Server::ServerObjectPos& obj) const {
|
||||||
|
return std::hash<uint32_t>()(obj.WorldId) ^ std::hash<int32_t>()(obj.ObjectPos.x) ^ std::hash<int32_t>()(obj.ObjectPos.y) ^ std::hash<int32_t>()(obj.ObjectPos.z);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct hash<LV::Server::ContentViewCircle> {
|
||||||
|
size_t operator()(const LV::Server::ContentViewCircle& obj) const noexcept {
|
||||||
|
// Используем стандартную функцию хеширования для uint32_t, glm::i16vec3 и int32_t
|
||||||
|
auto worldIdHash = std::hash<uint32_t>{}(obj.WorldId) << 32;
|
||||||
|
auto posHash =
|
||||||
|
std::hash<int16_t>{}(obj.Pos.x) ^
|
||||||
|
(std::hash<int16_t>{}(obj.Pos.y) << 16) ^
|
||||||
|
(std::hash<int16_t>{}(obj.Pos.z) << 32);
|
||||||
|
auto rangeHash = std::hash<int32_t>{}(obj.Range);
|
||||||
|
|
||||||
|
return worldIdHash ^
|
||||||
|
posHash ^
|
||||||
|
(~rangeHash << 32);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,127 +0,0 @@
|
|||||||
#include "ContentManager.hpp"
|
|
||||||
#include "Common/Abstract.hpp"
|
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
namespace LV::Server {
|
|
||||||
|
|
||||||
ContentManager::ContentManager(asio::io_context& ioc) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
ContentManager::~ContentManager() = default;
|
|
||||||
|
|
||||||
void ContentManager::registerBase_Node(ResourceId id, const sol::table& profile) {
|
|
||||||
std::optional<DefNode>& node = getEntry_Node(id);
|
|
||||||
if(!node)
|
|
||||||
node.emplace();
|
|
||||||
|
|
||||||
DefNode& def = *node;
|
|
||||||
|
|
||||||
{
|
|
||||||
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 sol::table& profile) {
|
|
||||||
std::optional<DefWorld>& world = getEntry_World(id);
|
|
||||||
if(!world)
|
|
||||||
world.emplace();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ContentManager::registerBase(EnumDefContent type, const std::string& domain, const std::string& key, const sol::table& profile)
|
|
||||||
{
|
|
||||||
ResourceId id = getId(type, domain, key);
|
|
||||||
ProfileChanges[(int) type].push_back(id);
|
|
||||||
|
|
||||||
if(type == EnumDefContent::Node)
|
|
||||||
registerBase_Node(id, profile);
|
|
||||||
else if(type == EnumDefContent::World)
|
|
||||||
registerBase_World(id, 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
ContentManager::Out_buildEndProfiles ContentManager::buildEndProfiles() {
|
|
||||||
Out_buildEndProfiles result;
|
|
||||||
|
|
||||||
for(int type = 0; type < (int) EnumDefContent::MAX_ENUM; type++) {
|
|
||||||
auto& keys = ProfileChanges[type];
|
|
||||||
std::sort(keys.begin(), keys.end());
|
|
||||||
auto iterErase = std::unique(keys.begin(), keys.end());
|
|
||||||
keys.erase(iterErase, keys.end());
|
|
||||||
}
|
|
||||||
|
|
||||||
for(ResourceId id : ProfileChanges[(int) EnumDefContent::Voxel]) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,204 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "Common/Abstract.hpp"
|
|
||||||
#include "Server/Abstract.hpp"
|
|
||||||
#include "Server/AssetsManager.hpp"
|
|
||||||
#include <sol/table.hpp>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <unordered_set>
|
|
||||||
|
|
||||||
|
|
||||||
namespace LV::Server {
|
|
||||||
|
|
||||||
struct DefVoxel_Base { };
|
|
||||||
struct DefNode_Base { };
|
|
||||||
struct DefWorld_Base { };
|
|
||||||
struct DefPortal_Base { };
|
|
||||||
struct DefEntity_Base { };
|
|
||||||
struct DefItem_Base { };
|
|
||||||
|
|
||||||
struct DefVoxel_Mod { };
|
|
||||||
struct DefNode_Mod { };
|
|
||||||
struct DefWorld_Mod { };
|
|
||||||
struct DefPortal_Mod { };
|
|
||||||
struct DefEntity_Mod { };
|
|
||||||
struct DefItem_Mod { };
|
|
||||||
|
|
||||||
struct ResourceBase {
|
|
||||||
std::string Domain, Key;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct DefVoxel : public ResourceBase { };
|
|
||||||
struct DefNode : public ResourceBase { };
|
|
||||||
struct DefWorld : public ResourceBase { };
|
|
||||||
struct DefPortal : public ResourceBase { };
|
|
||||||
struct DefEntity : public ResourceBase { };
|
|
||||||
struct DefItem : public ResourceBase { };
|
|
||||||
|
|
||||||
class ContentManager {
|
|
||||||
template<typename T>
|
|
||||||
struct TableEntry {
|
|
||||||
static constexpr size_t ChunkSize = 4096;
|
|
||||||
std::array<std::optional<T>, ChunkSize> Entries;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// Следующие идентификаторы регистрации контента
|
|
||||||
ResourceId NextId[(int) EnumDefContent::MAX_ENUM] = {0};
|
|
||||||
// Домен -> {ключ -> идентификатор}
|
|
||||||
std::unordered_map<std::string, std::unordered_map<std::string, ResourceId>> ContentKeyToId[(int) EnumDefContent::MAX_ENUM];
|
|
||||||
|
|
||||||
// Профили зарегистрированные модами
|
|
||||||
std::vector<std::unique_ptr<TableEntry<DefVoxel>>> Profiles_Base_Voxel;
|
|
||||||
std::vector<std::unique_ptr<TableEntry<DefNode>>> Profiles_Base_Node;
|
|
||||||
std::vector<std::unique_ptr<TableEntry<DefWorld>>> Profiles_Base_World;
|
|
||||||
std::vector<std::unique_ptr<TableEntry<DefPortal>>> Profiles_Base_Portal;
|
|
||||||
std::vector<std::unique_ptr<TableEntry<DefEntity>>> Profiles_Base_Entity;
|
|
||||||
std::vector<std::unique_ptr<TableEntry<DefItem>>> Profiles_Base_Item;
|
|
||||||
|
|
||||||
// Изменения, накладываемые на профили
|
|
||||||
// Идентификатор [домен мода модификатора, модификатор]
|
|
||||||
std::unordered_map<ResourceId, std::vector<std::tuple<std::string, DefVoxel_Mod>>> Profiles_Mod_Voxel;
|
|
||||||
std::unordered_map<ResourceId, std::vector<std::tuple<std::string, DefNode_Mod>>> Profiles_Mod_Node;
|
|
||||||
std::unordered_map<ResourceId, std::vector<std::tuple<std::string, DefWorld_Mod>>> Profiles_Mod_World;
|
|
||||||
std::unordered_map<ResourceId, std::vector<std::tuple<std::string, DefPortal_Mod>>> Profiles_Mod_Portal;
|
|
||||||
std::unordered_map<ResourceId, std::vector<std::tuple<std::string, DefEntity_Mod>>> Profiles_Mod_Entity;
|
|
||||||
std::unordered_map<ResourceId, std::vector<std::tuple<std::string, DefItem_Mod>>> Profiles_Mod_Item;
|
|
||||||
|
|
||||||
// Затронутые профили в процессе регистраций
|
|
||||||
// По ним будут пересобраны профили
|
|
||||||
std::vector<ResourceId> ProfileChanges[(int) EnumDefContent::MAX_ENUM];
|
|
||||||
|
|
||||||
// Конечные профили контента
|
|
||||||
std::vector<std::unique_ptr<TableEntry<DefVoxel>>> Profiles_Voxel;
|
|
||||||
std::vector<std::unique_ptr<TableEntry<DefNode>>> Profiles_Node;
|
|
||||||
std::vector<std::unique_ptr<TableEntry<DefWorld>>> Profiles_World;
|
|
||||||
std::vector<std::unique_ptr<TableEntry<DefPortal>>> Profiles_Portal;
|
|
||||||
std::vector<std::unique_ptr<TableEntry<DefEntity>>> Profiles_Entity;
|
|
||||||
std::vector<std::unique_ptr<TableEntry<DefItem>>> Profiles_Item;
|
|
||||||
|
|
||||||
std::optional<DefVoxel>& getEntry_Voxel(ResourceId resId) { return Profiles_Voxel[resId / TableEntry<DefVoxel>::ChunkSize]->Entries[resId % TableEntry<DefVoxel>::ChunkSize];}
|
|
||||||
std::optional<DefNode>& getEntry_Node(ResourceId resId) { return Profiles_Node[resId / TableEntry<DefVoxel>::ChunkSize]->Entries[resId % TableEntry<DefVoxel>::ChunkSize];}
|
|
||||||
std::optional<DefWorld>& getEntry_World(ResourceId resId) { return Profiles_World[resId / TableEntry<DefVoxel>::ChunkSize]->Entries[resId % TableEntry<DefVoxel>::ChunkSize];}
|
|
||||||
std::optional<DefPortal>& getEntry_Portal(ResourceId resId) { return Profiles_Portal[resId / TableEntry<DefVoxel>::ChunkSize]->Entries[resId % TableEntry<DefVoxel>::ChunkSize];}
|
|
||||||
std::optional<DefEntity>& getEntry_Entity(ResourceId resId) { return Profiles_Entity[resId / TableEntry<DefVoxel>::ChunkSize]->Entries[resId % TableEntry<DefVoxel>::ChunkSize];}
|
|
||||||
std::optional<DefItem>& getEntry_Item(ResourceId resId) { return Profiles_Item[resId / TableEntry<DefVoxel>::ChunkSize]->Entries[resId % TableEntry<DefVoxel>::ChunkSize];}
|
|
||||||
|
|
||||||
ResourceId getId(EnumDefContent type, const std::string& domain, const std::string& key) {
|
|
||||||
if(auto iterCKTI = ContentKeyToId[(int) type].find(domain); iterCKTI != ContentKeyToId[(int) type].end()) {
|
|
||||||
if(auto iterKey = iterCKTI->second.find(key); iterKey != iterCKTI->second.end()) {
|
|
||||||
return iterKey->second;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ResourceId resId = NextId[(int) type]++;
|
|
||||||
ContentKeyToId[(int) type][domain][key] = resId;
|
|
||||||
|
|
||||||
switch(type) {
|
|
||||||
case EnumDefContent::Voxel:
|
|
||||||
if(resId >= Profiles_Voxel.size()*TableEntry<DefVoxel>::ChunkSize)
|
|
||||||
Profiles_Voxel.push_back(std::make_unique<TableEntry<DefVoxel>>());
|
|
||||||
break;
|
|
||||||
case EnumDefContent::Node:
|
|
||||||
if(resId >= Profiles_Node.size()*TableEntry<DefNode>::ChunkSize)
|
|
||||||
Profiles_Node.push_back(std::make_unique<TableEntry<DefNode>>());
|
|
||||||
break;
|
|
||||||
case EnumDefContent::World:
|
|
||||||
if(resId >= Profiles_World.size()*TableEntry<DefWorld>::ChunkSize)
|
|
||||||
Profiles_World.push_back(std::make_unique<TableEntry<DefWorld>>());
|
|
||||||
break;
|
|
||||||
case EnumDefContent::Portal:
|
|
||||||
if(resId >= Profiles_Portal.size()*TableEntry<DefPortal>::ChunkSize)
|
|
||||||
Profiles_Portal.push_back(std::make_unique<TableEntry<DefPortal>>());
|
|
||||||
break;
|
|
||||||
case EnumDefContent::Entity:
|
|
||||||
if(resId >= Profiles_Entity.size()*TableEntry<DefEntity>::ChunkSize)
|
|
||||||
Profiles_Entity.push_back(std::make_unique<TableEntry<DefEntity>>());
|
|
||||||
break;
|
|
||||||
case EnumDefContent::Item:
|
|
||||||
if(resId >= Profiles_Item.size()*TableEntry<DefItem>::ChunkSize)
|
|
||||||
Profiles_Item.push_back(std::make_unique<TableEntry<DefItem>>());
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
std::unreachable();
|
|
||||||
}
|
|
||||||
|
|
||||||
return resId;
|
|
||||||
}
|
|
||||||
|
|
||||||
void registerBase_Node(ResourceId id, const sol::table& profile);
|
|
||||||
void registerBase_World(ResourceId id, const sol::table& profile);
|
|
||||||
|
|
||||||
public:
|
|
||||||
ContentManager(asio::io_context& ioc);
|
|
||||||
~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);
|
|
||||||
// Компилирует изменённые профили
|
|
||||||
struct Out_buildEndProfiles {
|
|
||||||
std::vector<ResourceId> ChangedProfiles[(int) EnumDefContent::MAX_ENUM];
|
|
||||||
};
|
|
||||||
|
|
||||||
Out_buildEndProfiles buildEndProfiles();
|
|
||||||
|
|
||||||
std::optional<DefVoxel*> getProfile_Voxel(ResourceId id) {
|
|
||||||
assert(id < Profiles_Voxel.size()*TableEntry<DefVoxel>::ChunkSize);
|
|
||||||
auto& value = Profiles_Voxel[id / TableEntry<DefVoxel>::ChunkSize]->Entries[id % TableEntry<DefVoxel>::ChunkSize];
|
|
||||||
if(value)
|
|
||||||
return {&*value};
|
|
||||||
else
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<DefNode*> getProfile_Node(ResourceId id) {
|
|
||||||
assert(id < Profiles_Node.size()*TableEntry<DefNode>::ChunkSize);
|
|
||||||
auto& value = Profiles_Node[id / TableEntry<DefNode>::ChunkSize]->Entries[id % TableEntry<DefNode>::ChunkSize];
|
|
||||||
if(value)
|
|
||||||
return {&*value};
|
|
||||||
else
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<DefWorld*> getProfile_World(ResourceId id) {
|
|
||||||
assert(id < Profiles_World.size()*TableEntry<DefWorld>::ChunkSize);
|
|
||||||
auto& value = Profiles_World[id / TableEntry<DefWorld>::ChunkSize]->Entries[id % TableEntry<DefWorld>::ChunkSize];
|
|
||||||
if(value)
|
|
||||||
return {&*value};
|
|
||||||
else
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<DefPortal*> getProfile_Portal(ResourceId id) {
|
|
||||||
assert(id < Profiles_Portal.size()*TableEntry<DefPortal>::ChunkSize);
|
|
||||||
auto& value = Profiles_Portal[id / TableEntry<DefPortal>::ChunkSize]->Entries[id % TableEntry<DefPortal>::ChunkSize];
|
|
||||||
if(value)
|
|
||||||
return {&*value};
|
|
||||||
else
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<DefEntity*> getProfile_Entity(ResourceId id) {
|
|
||||||
assert(id < Profiles_Entity.size()*TableEntry<DefEntity>::ChunkSize);
|
|
||||||
auto& value = Profiles_Entity[id / TableEntry<DefEntity>::ChunkSize]->Entries[id % TableEntry<DefEntity>::ChunkSize];
|
|
||||||
if(value)
|
|
||||||
return {&*value};
|
|
||||||
else
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<DefItem*> getProfile_Item(ResourceId id) {
|
|
||||||
assert(id < Profiles_Item.size()*TableEntry<DefItem>::ChunkSize);
|
|
||||||
auto& value = Profiles_Item[id / TableEntry<DefItem>::ChunkSize]->Entries[id % TableEntry<DefItem>::ChunkSize];
|
|
||||||
if(value)
|
|
||||||
return {&*value};
|
|
||||||
else
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,30 +1,21 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Server/AssetsManager.hpp"
|
|
||||||
#define SOL_EXCEPTIONS_SAFE_PROPAGATION 1
|
|
||||||
|
|
||||||
#include <Common/Net.hpp>
|
#include <Common/Net.hpp>
|
||||||
#include <Common/Lockable.hpp>
|
#include <Common/Lockable.hpp>
|
||||||
#include <boost/asio/any_io_executor.hpp>
|
#include <boost/asio/any_io_executor.hpp>
|
||||||
#include <boost/asio/io_context.hpp>
|
#include <boost/asio/io_context.hpp>
|
||||||
#include <condition_variable>
|
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include "Common/Abstract.hpp"
|
|
||||||
#include "RemoteClient.hpp"
|
#include "RemoteClient.hpp"
|
||||||
#include "Server/Abstract.hpp"
|
#include "Server/Abstract.hpp"
|
||||||
#include <TOSLib.hpp>
|
#include <TOSLib.hpp>
|
||||||
#include <functional>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <queue>
|
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <sol/forward.hpp>
|
|
||||||
#include <sol/state.hpp>
|
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
#include "ContentEventController.hpp"
|
||||||
|
|
||||||
#include "WorldDefManager.hpp"
|
#include "WorldDefManager.hpp"
|
||||||
#include "AssetsManager.hpp"
|
#include "BinaryResourceManager.hpp"
|
||||||
#include "ContentManager.hpp"
|
|
||||||
#include "World.hpp"
|
#include "World.hpp"
|
||||||
|
|
||||||
#include "SaveBackend.hpp"
|
#include "SaveBackend.hpp"
|
||||||
@@ -32,26 +23,10 @@
|
|||||||
|
|
||||||
namespace LV::Server {
|
namespace LV::Server {
|
||||||
|
|
||||||
struct ModRequest {
|
|
||||||
std::string Id;
|
|
||||||
std::array<uint32_t, 4> MinVersion, MaxVersion;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ModInfo {
|
|
||||||
std::string Id, Name, Description, Author;
|
|
||||||
std::vector<std::string> AlternativeIds;
|
|
||||||
std::array<uint32_t, 4> Version;
|
|
||||||
std::vector<ModRequest> Dependencies, Optional;
|
|
||||||
float LoadPriority;
|
|
||||||
fs::path Path;
|
|
||||||
bool HasLiveReload;
|
|
||||||
|
|
||||||
std::string dump() const;
|
|
||||||
};
|
|
||||||
|
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
class GameServer : public AsyncObject {
|
class GameServer {
|
||||||
|
asio::io_context &IOC;
|
||||||
TOS::Logger LOG = "GameServer";
|
TOS::Logger LOG = "GameServer";
|
||||||
DestroyLock UseLock;
|
DestroyLock UseLock;
|
||||||
std::thread RunThread;
|
std::thread RunThread;
|
||||||
@@ -67,25 +42,36 @@ class GameServer : public AsyncObject {
|
|||||||
|
|
||||||
struct {
|
struct {
|
||||||
Lockable<std::set<std::string>> ConnectedPlayersSet;
|
Lockable<std::set<std::string>> ConnectedPlayersSet;
|
||||||
Lockable<std::list<std::shared_ptr<RemoteClient>>> NewConnectedPlayers;
|
Lockable<std::list<RemoteClient_ptr>> NewConnectedPlayers;
|
||||||
|
|
||||||
} External;
|
} External;
|
||||||
|
|
||||||
struct ContentObj {
|
struct ContentObj {
|
||||||
public:
|
public:
|
||||||
AssetsManager AM;
|
// WorldDefManager WorldDM;
|
||||||
ContentManager CM;
|
// VoxelDefManager VoxelDM;
|
||||||
|
// NodeDefManager NodeDM;
|
||||||
|
BinaryResourceManager TextureM;
|
||||||
|
BinaryResourceManager ModelM;
|
||||||
|
BinaryResourceManager SoundM;
|
||||||
|
|
||||||
// Если контент был перерегистрирован (исключая двоичные ресурсы), то профили будут повторно разосланы
|
ContentObj(asio::io_context &ioc,
|
||||||
ResourceRequest OnContentChanges;
|
std::shared_ptr<ResourceFile> zeroTexture,
|
||||||
|
std::shared_ptr<ResourceFile> zeroModel,
|
||||||
|
std::shared_ptr<ResourceFile> zeroSound)
|
||||||
|
: TextureM(ioc, zeroTexture),
|
||||||
|
ModelM(ioc, zeroModel),
|
||||||
|
SoundM(ioc, zeroSound)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
ContentObj(asio::io_context& ioc)
|
|
||||||
: AM(ioc), CM(ioc)
|
|
||||||
{}
|
|
||||||
} Content;
|
} Content;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
std::vector<std::shared_ptr<RemoteClient>> RemoteClients;
|
std::vector<std::unique_ptr<ContentEventController>> CECs;
|
||||||
|
// Индекс игрока, у которого в следующем такте будет пересмотрен ContentEventController->ContentViewCircles
|
||||||
|
uint16_t CEC_NextRebuildViewCircles = 0, CEC_NextCheckRegions = 0;
|
||||||
ServerTime AfterStartTime = {0, 0};
|
ServerTime AfterStartTime = {0, 0};
|
||||||
|
|
||||||
} Game;
|
} Game;
|
||||||
@@ -97,9 +83,9 @@ class GameServer : public AsyncObject {
|
|||||||
// depth ограничивает глубину входа в ContentBridges
|
// depth ограничивает глубину входа в ContentBridges
|
||||||
std::vector<ContentViewCircle> accumulateContentViewCircles(ContentViewCircle circle, int depth = 2);
|
std::vector<ContentViewCircle> accumulateContentViewCircles(ContentViewCircle circle, int depth = 2);
|
||||||
// Вынести в отдельный поток
|
// Вынести в отдельный поток
|
||||||
static ContentViewInfo makeContentViewInfo(const std::vector<ContentViewCircle> &views);
|
static ContentViewGlobal makeContentViewGlobal(const std::vector<ContentViewCircle> &views);
|
||||||
ContentViewInfo makeContentViewInfo(ContentViewCircle circle, int depth = 2) {
|
ContentViewGlobal makeContentViewGlobal(ContentViewCircle circle, int depth = 2) {
|
||||||
return makeContentViewInfo(accumulateContentViewCircles(circle, depth));
|
return makeContentViewGlobal(accumulateContentViewCircles(circle, depth));
|
||||||
}
|
}
|
||||||
|
|
||||||
// std::unordered_map<WorldId_t, std::vector<ContentViewCircle>> remapCVCsByWorld(const std::vector<ContentViewCircle> &list);
|
// std::unordered_map<WorldId_t, std::vector<ContentViewCircle>> remapCVCsByWorld(const std::vector<ContentViewCircle> &list);
|
||||||
@@ -111,14 +97,9 @@ class GameServer : public AsyncObject {
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
Регистрация миров по строке
|
Регистрация миров по строке
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void _accumulateContentViewCircles(ContentViewCircle circle, int depth);
|
void _accumulateContentViewCircles(ContentViewCircle circle, int depth);
|
||||||
} Expanse;
|
} Expanse;
|
||||||
@@ -130,146 +111,17 @@ class GameServer : public AsyncObject {
|
|||||||
std::unique_ptr<IModStorageSaveBackend> ModStorage;
|
std::unique_ptr<IModStorageSaveBackend> ModStorage;
|
||||||
} SaveBackend;
|
} SaveBackend;
|
||||||
|
|
||||||
/*
|
|
||||||
Обязательно между тактами
|
|
||||||
|
|
||||||
Конвертация ресурсов игры, их хранение в кеше и загрузка в память для отправки клиентам
|
|
||||||
io_uring или последовательное чтение
|
|
||||||
|
|
||||||
Исполнение асинхронного луа
|
|
||||||
Пул для постоянной работы и синхронизации времени с главным потоком
|
|
||||||
|
|
||||||
Сжатие/расжатие регионов в базе
|
|
||||||
Локальный поток должен собирать ключи профилей для базы
|
|
||||||
Остальное внутри базы
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
Отправка изменений чанков клиентам
|
|
||||||
|
|
||||||
После окончания такта пул копирует изменённые чанки
|
|
||||||
- синхронизация сбора в stepDatabaseSync -
|
|
||||||
сжимает их и отправляет клиентам
|
|
||||||
- синхронизация в начале stepPlayerProceed -
|
|
||||||
^ к этому моменту все данные должны быть отправлены в RemoteClient
|
|
||||||
*/
|
|
||||||
struct BackingChunkPressure_t {
|
|
||||||
TOS::Logger LOG = "BackingChunkPressure";
|
|
||||||
volatile bool NeedShutdown = false;
|
|
||||||
std::vector<std::thread> Threads;
|
|
||||||
std::mutex Mutex;
|
|
||||||
volatile int RunCollect = 0, RunCompress = 0, Iteration = 0;
|
|
||||||
std::condition_variable Symaphore;
|
|
||||||
std::unordered_map<WorldId_t, std::unique_ptr<World>> *Worlds;
|
|
||||||
|
|
||||||
void startCollectChanges() {
|
|
||||||
std::lock_guard<std::mutex> lock(Mutex);
|
|
||||||
RunCollect = Threads.size();
|
|
||||||
RunCompress = Threads.size();
|
|
||||||
Iteration += 1;
|
|
||||||
assert(RunCollect != 0);
|
|
||||||
Symaphore.notify_all();
|
|
||||||
}
|
|
||||||
|
|
||||||
void endCollectChanges() {
|
|
||||||
std::unique_lock<std::mutex> lock(Mutex);
|
|
||||||
Symaphore.wait(lock, [&](){ return RunCollect == 0 || NeedShutdown; });
|
|
||||||
}
|
|
||||||
|
|
||||||
void endWithResults() {
|
|
||||||
std::unique_lock<std::mutex> lock(Mutex);
|
|
||||||
Symaphore.wait(lock, [&](){ return RunCompress == 0 || NeedShutdown; });
|
|
||||||
}
|
|
||||||
|
|
||||||
void stop() {
|
|
||||||
{
|
|
||||||
std::unique_lock<std::mutex> lock(Mutex);
|
|
||||||
NeedShutdown = true;
|
|
||||||
Symaphore.notify_all();
|
|
||||||
}
|
|
||||||
|
|
||||||
for(std::thread& thread : Threads)
|
|
||||||
thread.join();
|
|
||||||
}
|
|
||||||
|
|
||||||
__attribute__((optimize("O3"))) void run(int id);
|
|
||||||
} BackingChunkPressure;
|
|
||||||
|
|
||||||
/*
|
|
||||||
Генератор шума
|
|
||||||
*/
|
|
||||||
struct BackingNoiseGenerator_t {
|
|
||||||
struct NoiseKey {
|
|
||||||
WorldId_t WId;
|
|
||||||
Pos::GlobalRegion RegionPos;
|
|
||||||
};
|
|
||||||
|
|
||||||
TOS::Logger LOG = "BackingNoiseGenerator";
|
|
||||||
bool NeedShutdown = false;
|
|
||||||
std::vector<std::thread> Threads;
|
|
||||||
TOS::SpinlockObject<std::queue<NoiseKey>> Input;
|
|
||||||
TOS::SpinlockObject<std::vector<std::pair<NoiseKey, std::array<float, 64*64*64>>>> Output;
|
|
||||||
|
|
||||||
void stop() {
|
|
||||||
NeedShutdown = true;
|
|
||||||
|
|
||||||
for(std::thread& thread : Threads)
|
|
||||||
thread.join();
|
|
||||||
}
|
|
||||||
|
|
||||||
void run(int id);
|
|
||||||
|
|
||||||
std::vector<std::pair<NoiseKey, std::array<float, 64*64*64>>>
|
|
||||||
tickSync(std::unordered_map<WorldId_t, std::vector<Pos::GlobalRegion>> &&input) {
|
|
||||||
{
|
|
||||||
auto lock = Input.lock();
|
|
||||||
|
|
||||||
for(auto& [worldId, region] : input) {
|
|
||||||
for(auto& regionPos : region) {
|
|
||||||
lock->push({worldId, regionPos});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto lock = Output.lock();
|
|
||||||
std::vector<std::pair<NoiseKey, std::array<float, 64*64*64>>> out = std::move(*lock);
|
|
||||||
lock->reserve(8000);
|
|
||||||
|
|
||||||
return std::move(out);
|
|
||||||
}
|
|
||||||
} BackingNoiseGenerator;
|
|
||||||
|
|
||||||
/*
|
|
||||||
Обработчик асинронного луа
|
|
||||||
*/
|
|
||||||
struct BackingAsyncLua_t {
|
|
||||||
TOS::Logger LOG = "BackingAsyncLua";
|
|
||||||
bool NeedShutdown = false;
|
|
||||||
std::vector<std::thread> Threads;
|
|
||||||
TOS::SpinlockObject<std::queue<std::pair<BackingNoiseGenerator_t::NoiseKey, std::array<float, 64*64*64>>>> NoiseIn;
|
|
||||||
TOS::SpinlockObject<std::vector<std::pair<BackingNoiseGenerator_t::NoiseKey, World::RegionIn>>> RegionOut;
|
|
||||||
|
|
||||||
void stop() {
|
|
||||||
NeedShutdown = true;
|
|
||||||
|
|
||||||
for(std::thread& thread : Threads)
|
|
||||||
thread.join();
|
|
||||||
}
|
|
||||||
|
|
||||||
void run(int id);
|
|
||||||
} BackingAsyncLua;
|
|
||||||
|
|
||||||
sol::state LuaMainState;
|
|
||||||
std::vector<ModInfo> LoadedMods;
|
|
||||||
std::vector<std::pair<std::string, sol::table>> ModInstances;
|
|
||||||
// Идентификатор текущегго мода, находящевося в обработке
|
|
||||||
std::string CurrentModId;
|
|
||||||
AssetsManager::AssetsRegister AssetsInit;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
GameServer(asio::io_context &ioc, fs::path worldPath);
|
GameServer(asio::io_context &ioc, fs::path worldPath)
|
||||||
|
: IOC(ioc),
|
||||||
|
Content(ioc, nullptr, nullptr, nullptr)
|
||||||
|
{
|
||||||
|
init(worldPath);
|
||||||
|
}
|
||||||
|
|
||||||
virtual ~GameServer();
|
virtual ~GameServer();
|
||||||
|
|
||||||
|
|
||||||
void shutdown(const std::string reason) {
|
void shutdown(const std::string reason) {
|
||||||
if(ShutdownReason.empty())
|
if(ShutdownReason.empty())
|
||||||
ShutdownReason = reason;
|
ShutdownReason = reason;
|
||||||
@@ -291,77 +143,37 @@ public:
|
|||||||
// Инициализация игрового протокола для сокета (onSocketAuthorized() может передать сокет в onSocketGame())
|
// Инициализация игрового протокола для сокета (onSocketAuthorized() может передать сокет в onSocketGame())
|
||||||
coro<> pushSocketGameProtocol(tcp::socket socket, const std::string username);
|
coro<> pushSocketGameProtocol(tcp::socket socket, const std::string username);
|
||||||
|
|
||||||
TexturePipeline buildTexturePipeline(const std::string& pipeline);
|
/* Загрузит, сгенерирует или просто выдаст регион из мира, который должен существовать */
|
||||||
std::string deBuildTexturePipeline(const TexturePipeline& pipeline);
|
Region* forceGetRegion(WorldId_t worldId, Pos::GlobalRegion pos);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void init(fs::path worldPath);
|
void init(fs::path worldPath);
|
||||||
void prerun();
|
void prerun();
|
||||||
void run();
|
void run();
|
||||||
|
|
||||||
void initLuaAssets();
|
void stepContent();
|
||||||
void initLuaPre();
|
|
||||||
void initLua();
|
|
||||||
void initLuaPost();
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Подключение/отключение игроков
|
Дождаться и получить необходимые данные с бд или диска
|
||||||
|
Получить несрочные данные
|
||||||
*/
|
*/
|
||||||
|
void stepSyncWithAsync();
|
||||||
void stepConnections();
|
void stepPlayers();
|
||||||
|
void stepWorlds();
|
||||||
/*
|
/*
|
||||||
Переинициализация модов, если требуется
|
Пересмотр наблюдаемых зон (чанки, регионы, миры)
|
||||||
|
Добавить требуемые регионы в список на предзагрузку с приоритетом
|
||||||
|
TODO: нужен механизм асинхронной загрузки регионов с бд
|
||||||
|
|
||||||
|
В начале следующего такта обязательное дожидание прогрузки активной зоны
|
||||||
|
и
|
||||||
|
оповещение миров об изменениях в наблюдаемых регионах
|
||||||
*/
|
*/
|
||||||
|
void stepViewContent();
|
||||||
void stepModInitializations();
|
void stepSendPlayersPackets();
|
||||||
|
void stepLoadRegions();
|
||||||
/*
|
void stepGlobal();
|
||||||
Пересчёт зон видимости игроков, если необходимо
|
void stepSave();
|
||||||
Выгрузить более не используемые регионы
|
void save();
|
||||||
Сохранение регионов
|
|
||||||
Создание списка регионов необходимых для загрузки (бд автоматически будет предзагружать)
|
|
||||||
<Синхронизация с модулем сохранений>
|
|
||||||
Очередь загрузки, выгрузка регионов и получение загруженных из бд регионов
|
|
||||||
Получить список регионов отсутствующих в сохранении и требующих генерации
|
|
||||||
Подпись на загруженные регионы (отправить полностью на клиент)
|
|
||||||
*/
|
|
||||||
|
|
||||||
IWorldSaveBackend::TickSyncInfo_Out stepDatabaseSync();
|
|
||||||
|
|
||||||
/*
|
|
||||||
Синхронизация с генератором карт (отправка запросов на генерацию и получение шума для обработки модами)
|
|
||||||
Обработка модами сырых регионов полученных с бд
|
|
||||||
Синхронизация с потоками модов
|
|
||||||
*/
|
|
||||||
|
|
||||||
void stepGeneratorAndLuaAsync(IWorldSaveBackend::TickSyncInfo_Out db);
|
|
||||||
|
|
||||||
/*
|
|
||||||
Пакеты игроков получает асинхронный поток в RemoteClient
|
|
||||||
Остаётся только обработать распаршенные пакеты
|
|
||||||
*/
|
|
||||||
|
|
||||||
void stepPlayerProceed();
|
|
||||||
|
|
||||||
/*
|
|
||||||
Физика
|
|
||||||
*/
|
|
||||||
|
|
||||||
void stepWorldPhysic();
|
|
||||||
|
|
||||||
/*
|
|
||||||
Глобальный такт
|
|
||||||
*/
|
|
||||||
|
|
||||||
void stepGlobalStep();
|
|
||||||
|
|
||||||
/*
|
|
||||||
Обработка запросов двоичных ресурсов и определений
|
|
||||||
Отправка пакетов игрокам
|
|
||||||
Запуск задачи ChunksChanges
|
|
||||||
*/
|
|
||||||
void stepSyncContent();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,12 +1 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
|
||||||
namespace LV::Server {
|
|
||||||
|
|
||||||
class NodeDefManager {
|
|
||||||
public:
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -3,21 +3,25 @@
|
|||||||
#include <TOSLib.hpp>
|
#include <TOSLib.hpp>
|
||||||
#include <Common/Lockable.hpp>
|
#include <Common/Lockable.hpp>
|
||||||
#include <Common/Net.hpp>
|
#include <Common/Net.hpp>
|
||||||
|
#include <Common/Async.hpp>
|
||||||
#include "Abstract.hpp"
|
#include "Abstract.hpp"
|
||||||
#include "Common/Packets.hpp"
|
#include "Common/Packets.hpp"
|
||||||
#include "Server/AssetsManager.hpp"
|
#include "Server/ContentEventController.hpp"
|
||||||
#include "Server/ContentManager.hpp"
|
#include "TOSAsync.hpp"
|
||||||
|
#include "boost/asio/detached.hpp"
|
||||||
|
#include "boost/asio/io_context.hpp"
|
||||||
|
#include "boost/asio/use_awaitable.hpp"
|
||||||
#include <Common/Abstract.hpp>
|
#include <Common/Abstract.hpp>
|
||||||
#include <bitset>
|
#include <bitset>
|
||||||
#include <initializer_list>
|
#include <initializer_list>
|
||||||
#include <queue>
|
#include <memory>
|
||||||
|
#include <set>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
namespace LV::Server {
|
namespace LV::Server {
|
||||||
|
|
||||||
class World;
|
|
||||||
|
|
||||||
template<typename ServerKey, typename ClientKey, std::enable_if_t<sizeof(ServerKey) >= sizeof(ClientKey), int> = 0>
|
template<typename ServerKey, typename ClientKey, std::enable_if_t<sizeof(ServerKey) >= sizeof(ClientKey), int> = 0>
|
||||||
class CSChunkedMapper {
|
class CSChunkedMapper {
|
||||||
std::unordered_map<uint32_t, std::tuple<std::bitset<64>, std::array<ServerKey, 64>>> Chunks;
|
std::unordered_map<uint32_t, std::tuple<std::bitset<64>, std::array<ServerKey, 64>>> Chunks;
|
||||||
@@ -103,7 +107,7 @@ public:
|
|||||||
// Соотнести идентификатор на стороне клиента с идентификатором на стороне сервера
|
// Соотнести идентификатор на стороне клиента с идентификатором на стороне сервера
|
||||||
ServerKey toServer(ClientKey ckey) {
|
ServerKey toServer(ClientKey ckey) {
|
||||||
return CSmapper.toServer(ckey);
|
return CSmapper.toServer(ckey);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Удаляет серверный идентификатор, освобождая идентификатор клиента
|
// Удаляет серверный идентификатор, освобождая идентификатор клиента
|
||||||
ClientKey erase(ServerKey skey) {
|
ClientKey erase(ServerKey skey) {
|
||||||
@@ -137,208 +141,161 @@ public:
|
|||||||
состоянии для клиента и шаблоны, которые клиенту уже не нужны.
|
состоянии для клиента и шаблоны, которые клиенту уже не нужны.
|
||||||
Соответствующие менеджеры ресурсов будут следить за изменениями
|
Соответствующие менеджеры ресурсов будут следить за изменениями
|
||||||
этих ресурсов и переотправлять их клиенту
|
этих ресурсов и переотправлять их клиенту
|
||||||
|
|
||||||
Информация о двоичных ресурсах будет получена сразу же при их запросе.
|
|
||||||
Действительная отправка ресурсов будет только по запросу клиента.
|
|
||||||
|
|
||||||
*/
|
*/
|
||||||
struct ResourceRequest {
|
struct ResourceRequest {
|
||||||
std::vector<Hash_t> Hashes;
|
std::vector<BinTextureId_t> NewTextures;
|
||||||
std::vector<ResourceId> AssetsInfo[(int) EnumAssets::MAX_ENUM];
|
std::vector<BinModelId_t> NewModels;
|
||||||
|
std::vector<BinSoundId_t> NewSounds;
|
||||||
|
|
||||||
std::vector<DefVoxelId> Voxel;
|
std::vector<DefWorldId_t> NewWorlds;
|
||||||
std::vector<DefNodeId> Node;
|
std::vector<DefVoxelId_t> NewVoxels;
|
||||||
std::vector<DefWorldId> World;
|
std::vector<DefNodeId_t> NewNodes;
|
||||||
std::vector<DefPortalId> Portal;
|
std::vector<DefPortalId_t> NewPortals;
|
||||||
std::vector<DefEntityId> Entity;
|
std::vector<DefEntityId_t> NewEntityes;
|
||||||
std::vector<DefItemId> Item;
|
|
||||||
|
|
||||||
void insert(const ResourceRequest &obj) {
|
void insert(const ResourceRequest &obj) {
|
||||||
Hashes.insert(Hashes.end(), obj.Hashes.begin(), obj.Hashes.end());
|
NewTextures.insert(NewTextures.end(), obj.NewTextures.begin(), obj.NewTextures.end());
|
||||||
for(int iter = 0; iter < (int) EnumAssets::MAX_ENUM; iter++)
|
NewModels.insert(NewModels.end(), obj.NewModels.begin(), obj.NewModels.end());
|
||||||
AssetsInfo[iter].insert(AssetsInfo[iter].end(), obj.AssetsInfo[iter].begin(), obj.AssetsInfo[iter].end());
|
NewSounds.insert(NewSounds.end(), obj.NewSounds.begin(), obj.NewSounds.end());
|
||||||
|
|
||||||
Voxel.insert(Voxel.end(), obj.Voxel.begin(), obj.Voxel.end());
|
NewWorlds.insert(NewWorlds.end(), obj.NewWorlds.begin(), obj.NewWorlds.end());
|
||||||
Node.insert(Node.end(), obj.Node.begin(), obj.Node.end());
|
NewVoxels.insert(NewVoxels.end(), obj.NewVoxels.begin(), obj.NewVoxels.end());
|
||||||
World.insert(World.end(), obj.World.begin(), obj.World.end());
|
NewNodes.insert(NewNodes.end(), obj.NewNodes.begin(), obj.NewNodes.end());
|
||||||
Portal.insert(Portal.end(), obj.Portal.begin(), obj.Portal.end());
|
NewPortals.insert(NewPortals.end(), obj.NewPortals.begin(), obj.NewPortals.end());
|
||||||
Entity.insert(Entity.end(), obj.Entity.begin(), obj.Entity.end());
|
NewEntityes.insert(NewEntityes.end(), obj.NewEntityes.begin(), obj.NewEntityes.end());
|
||||||
Item.insert(Item.end(), obj.Item.begin(), obj.Item.end());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void uniq() {
|
void uniq() {
|
||||||
for(std::vector<ResourceId> *vec : {&Voxel, &Node, &World,
|
for(std::vector<ResourceId_t> *vec : {&NewTextures, &NewModels, &NewSounds, &NewWorlds, &NewVoxels, &NewNodes, &NewPortals, &NewEntityes}) {
|
||||||
&Portal, &Entity, &Item
|
|
||||||
})
|
|
||||||
{
|
|
||||||
std::sort(vec->begin(), vec->end());
|
std::sort(vec->begin(), vec->end());
|
||||||
auto last = std::unique(vec->begin(), vec->end());
|
auto last = std::unique(vec->begin(), vec->end());
|
||||||
vec->erase(last, vec->end());
|
vec->erase(last, vec->end());
|
||||||
}
|
}
|
||||||
|
|
||||||
for(int type = 0; type < (int) EnumAssets::MAX_ENUM; type++)
|
|
||||||
{
|
|
||||||
std::sort(AssetsInfo[type].begin(), AssetsInfo[type].end());
|
|
||||||
auto last = std::unique(AssetsInfo[type].begin(), AssetsInfo[type].end());
|
|
||||||
AssetsInfo[type].erase(last, AssetsInfo[type].end());
|
|
||||||
}
|
|
||||||
|
|
||||||
std::sort(Hashes.begin(), Hashes.end());
|
|
||||||
auto last = std::unique(Hashes.begin(), Hashes.end());
|
|
||||||
Hashes.erase(last, Hashes.end());
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 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;
|
TOS::Logger LOG;
|
||||||
DestroyLock UseLock;
|
|
||||||
Net::AsyncSocket Socket;
|
Net::AsyncSocket Socket;
|
||||||
bool IsConnected = true, IsGoingShutdown = false;
|
bool IsConnected = true, IsGoingShutdown = false;
|
||||||
|
|
||||||
struct NetworkAndResource_t {
|
struct ResUsesObj {
|
||||||
struct ResUses_t {
|
// Счётчики использования базовых ресурсов высшими объектами
|
||||||
// Счётчики использования двоичных кэшируемых ресурсов + хэш привязанный к идентификатору
|
std::map<BinTextureId_t, uint32_t> BinTexture;
|
||||||
// Хэш используется для того, чтобы исключить повторные объявления неизменившихся ресурсов
|
std::map<BinSoundId_t, uint32_t> BinSound;
|
||||||
std::map<ResourceId, std::pair<uint32_t, Hash_t>> AssetsUse[(int) EnumAssets::MAX_ENUM];
|
|
||||||
|
|
||||||
// Зависимость профилей контента от профилей ресурсов
|
// Может использовать текстуры
|
||||||
// Нужно чтобы пересчитать зависимости к профилям ресурсов
|
std::map<BinModelId_t, uint32_t> BinModel;
|
||||||
struct RefAssets_t {
|
|
||||||
std::vector<ResourceId> Resources[(int) EnumAssets::MAX_ENUM];
|
|
||||||
};
|
|
||||||
|
|
||||||
std::map<DefVoxelId, RefAssets_t> RefDefVoxel;
|
// Будут использовать в своих определениях текстуры, звуки, модели
|
||||||
std::map<DefNodeId, RefAssets_t> RefDefNode;
|
std::map<DefWorldId_t, uint32_t> DefWorld;
|
||||||
std::map<WorldId_t, RefAssets_t> RefDefWorld;
|
std::map<DefVoxelId_t, uint32_t> DefVoxel;
|
||||||
std::map<DefPortalId, RefAssets_t> RefDefPortal;
|
std::map<DefNodeId_t, uint32_t> DefNode;
|
||||||
std::map<DefEntityId, RefAssets_t> RefDefEntity;
|
std::map<DefPortalId_t, uint32_t> DefPortal;
|
||||||
std::map<DefItemId, RefAssets_t> RefDefItem;
|
std::map<DefEntityId_t, uint32_t> DefEntity;
|
||||||
|
|
||||||
// Счётчики использование профилей контента
|
|
||||||
std::map<DefVoxelId, uint32_t> DefVoxel; // Один чанк, одно использование
|
|
||||||
std::map<DefNodeId, uint32_t> DefNode;
|
|
||||||
std::map<DefWorldId, uint32_t> DefWorld;
|
|
||||||
std::map<DefPortalId, uint32_t> DefPortal;
|
|
||||||
std::map<DefEntityId, uint32_t> DefEntity;
|
|
||||||
std::map<DefItemId, uint32_t> DefItem; // При передаче инвентарей?
|
|
||||||
|
|
||||||
|
|
||||||
// Зависимость наблюдаемых чанков от профилей нод и вокселей
|
// Переписываемый контент
|
||||||
struct ChunkRef {
|
|
||||||
// Отсортированные списки уникальных вокселей
|
|
||||||
std::vector<DefVoxelId> Voxel;
|
|
||||||
std::vector<DefNodeId> Node;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::map<WorldId_t, std::map<Pos::GlobalRegion, std::array<ChunkRef, 4*4*4>>> RefChunk;
|
|
||||||
|
|
||||||
// Модификационные зависимости экземпляров профилей контента
|
// Сущности используют текстуры, звуки, модели
|
||||||
// У сущностей в мире могут дополнительно изменятся свойства, переписывая их профиль
|
struct EntityResourceUse {
|
||||||
struct RefWorld_t {
|
DefEntityId_t DefId;
|
||||||
DefWorldId Profile;
|
|
||||||
RefAssets_t Assets;
|
|
||||||
};
|
|
||||||
std::map<WorldId_t, RefWorld_t> RefWorld;
|
|
||||||
struct RefPortal_t {
|
|
||||||
DefPortalId Profile;
|
|
||||||
RefAssets_t Assets;
|
|
||||||
};
|
|
||||||
// std::map<PortalId, RefPortal_t> RefPortal;
|
|
||||||
struct RefEntity_t {
|
|
||||||
DefEntityId Profile;
|
|
||||||
RefAssets_t Assets;
|
|
||||||
};
|
|
||||||
std::map<ServerEntityId_t, RefEntity_t> RefEntity;
|
|
||||||
} ResUses;
|
|
||||||
|
|
||||||
// Смена идентификаторов сервера на клиентские
|
std::unordered_set<BinTextureId_t> Textures;
|
||||||
SCSKeyRemapper<ServerEntityId_t, ClientEntityId_t> ReMapEntities;
|
std::unordered_set<BinSoundId_t> Sounds;
|
||||||
|
std::unordered_set<BinModelId_t> Models;
|
||||||
|
};
|
||||||
|
|
||||||
// Запрос информации об ассетах и профилях контента
|
std::map<GlobalEntityId_t, EntityResourceUse> Entity;
|
||||||
ResourceRequest NextRequest;
|
|
||||||
|
|
||||||
void incrementAssets(const ResUses_t::RefAssets_t& bin);
|
// Чанки используют воксели, ноды
|
||||||
void decrementAssets(ResUses_t::RefAssets_t&& bin);
|
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;
|
||||||
|
|
||||||
Net::Packet NextPacket;
|
// Миры
|
||||||
std::vector<Net::Packet> SimplePackets;
|
struct WorldResourceUse {
|
||||||
void checkPacketBorder(uint16_t size) {
|
DefWorldId_t DefId;
|
||||||
if(64000-NextPacket.size() < size || (NextPacket.size() != 0 && size == 0)) {
|
|
||||||
SimplePackets.push_back(std::move(NextPacket));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void prepareChunkUpdate_Voxels(WorldId_t worldId, Pos::GlobalChunk chunkPos, const std::u8string& compressed_voxels,
|
std::unordered_set<BinTextureId_t> Textures;
|
||||||
const std::vector<DefVoxelId>& uniq_sorted_defines);
|
std::unordered_set<BinSoundId_t> Sounds;
|
||||||
void prepareChunkUpdate_Nodes(WorldId_t worldId, Pos::GlobalChunk chunkPos, const std::u8string& compressed_nodes,
|
std::unordered_set<BinModelId_t> Models;
|
||||||
const std::vector<DefNodeId>& uniq_sorted_defines);
|
};
|
||||||
void prepareEntitiesRemove(const std::vector<ServerEntityId_t>& entityId);
|
|
||||||
void prepareRegionsRemove(WorldId_t worldId, std::vector<Pos::GlobalRegion> regionPoses);
|
std::map<WorldId_t, WorldResourceUse> Worlds;
|
||||||
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);
|
struct PortalResourceUse {
|
||||||
void prepareWorldUpdate(WorldId_t worldId, World* world);
|
DefPortalId_t DefId;
|
||||||
void informateDefVoxel(const std::vector<std::pair<DefVoxelId, DefVoxel*>>& voxels);
|
|
||||||
void informateDefNode(const std::vector<std::pair<DefNodeId, DefNode*>>& nodes);
|
std::unordered_set<BinTextureId_t> Textures;
|
||||||
void informateDefWorld(const std::vector<std::pair<DefWorldId, DefWorld*>>& worlds);
|
std::unordered_set<BinSoundId_t> Sounds;
|
||||||
void informateDefPortal(const std::vector<std::pair<DefPortalId, DefPortal*>>& portals);
|
std::unordered_set<BinModelId_t> Models;
|
||||||
void informateDefEntity(const std::vector<std::pair<DefEntityId, DefEntity*>>& entityes);
|
};
|
||||||
void informateDefItem(const std::vector<std::pair<DefItemId, DefItem*>>& items);
|
|
||||||
};
|
std::map<PortalId_t, PortalResourceUse> Portals;
|
||||||
|
|
||||||
|
} ResUses;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
/*
|
SCSKeyRemapper<BinTextureId_t, TextureId_c> BinTextures;
|
||||||
К концу такта собираются необходимые идентификаторы ресурсов
|
SCSKeyRemapper<BinSoundId_t, SoundId_c> BinSounds;
|
||||||
В конце такта сервер забирает запросы и возвращает информацию
|
SCSKeyRemapper<BinModelId_t, ModelId_c> BinModels;
|
||||||
о ресурсах. Отправляем связку Идентификатор + домен:ключ
|
|
||||||
+ хеш. Если у клиента не окажется этого ресурса, он может его запросить
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Ресурсы, отправленные на клиент в этой сессии и запрошенные клиентом
|
SCSKeyRemapper<DefWorldId_t, DefWorldId_c> DefWorlds;
|
||||||
/// TODO: ClientRequested здесь может быть засор
|
SCSKeyRemapper<DefVoxelId_t, DefVoxelId_c> DefVoxels;
|
||||||
std::vector<Hash_t> OnClient, ClientRequested;
|
SCSKeyRemapper<DefNodeId_t, DefNodeId_c> DefNodes;
|
||||||
// Отправляемые на клиент ресурсы
|
SCSKeyRemapper<DefPortalId_t, DefPortalId_c> DefPortals;
|
||||||
// Тип, домен, ключ, идентификатор, ресурс, количество отправленных байт
|
SCSKeyRemapper<DefEntityId_t, DefEntityId_c> DefEntityes;
|
||||||
std::vector<std::tuple<EnumAssets, std::string, std::string, ResourceId, Resource, size_t>> ToSend;
|
|
||||||
} AssetsInWork;
|
|
||||||
|
|
||||||
TOS::SpinlockObject<NetworkAndResource_t> NetworkAndResource;
|
SCSKeyRemapper<WorldId_t, WorldId_c> Worlds;
|
||||||
|
SCSKeyRemapper<PortalId_t, PortalId_c> Portals;
|
||||||
|
SCSKeyRemapper<GlobalEntityId_t, EntityId_c> Entityes;
|
||||||
|
} ResRemap;
|
||||||
|
|
||||||
|
Net::Packet NextPacket;
|
||||||
|
ResourceRequest NextRequest;
|
||||||
|
std::vector<Net::Packet> SimplePackets;
|
||||||
|
|
||||||
|
TOS::WaitableCoro RunCoro;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
const std::string Username;
|
const std::string Username;
|
||||||
Pos::Object CameraPos = {0, 0, 0};
|
Pos::Object CameraPos = {0, 0, 0};
|
||||||
Pos::Object LastPos = CameraPos;
|
|
||||||
ToServer::PacketQuat CameraQuat = {0};
|
ToServer::PacketQuat CameraQuat = {0};
|
||||||
TOS::SpinlockObject<std::queue<uint8_t>> Actions;
|
|
||||||
ResourceId RecievedAssets[(int) EnumAssets::MAX_ENUM] = {0};
|
|
||||||
|
|
||||||
// Регионы, наблюдаемые клиентом
|
private:
|
||||||
ContentViewInfo ContentViewState;
|
|
||||||
// Если игрок пересекал границы региона (для перерасчёта ContentViewState)
|
RemoteClient(asio::io_context &ioc, tcp::socket socket, const std::string username)
|
||||||
bool CrossedRegion = true;
|
: IAsyncDestructible(ioc), LOG("RemoteClient " + username), Socket(ioc, std::move(socket)),
|
||||||
std::queue<Pos::GlobalNode> Build, Break;
|
Username(username), RunCoro(ioc)
|
||||||
|
{
|
||||||
|
RunCoro.co_spawn(run());
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual coro<> asyncDestructor() override;
|
||||||
|
coro<> run();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
RemoteClient(asio::io_context &ioc, tcp::socket socket, const std::string username, std::vector<ResourceFile::Hash_t> &&client_cache)
|
static RemoteClient_ptr Create(asio::io_context &ioc, tcp::socket socket, const std::string username) {
|
||||||
: LOG("RemoteClient " + username), Socket(ioc, std::move(socket)), Username(username)
|
return createUnique<>(ioc, new RemoteClient(ioc, std::move(socket), username));
|
||||||
{}
|
}
|
||||||
|
|
||||||
~RemoteClient();
|
virtual ~RemoteClient();
|
||||||
|
|
||||||
coro<> run();
|
|
||||||
void shutdown(EnumDisconnect type, const std::string reason);
|
void shutdown(EnumDisconnect type, const std::string reason);
|
||||||
bool isConnected() { return IsConnected; }
|
bool isConnected() { return IsConnected; }
|
||||||
|
|
||||||
@@ -349,65 +306,28 @@ public:
|
|||||||
Socket.pushPackets(simplePackets, smartPackets);
|
Socket.pushPackets(simplePackets, smartPackets);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Возвращает список точек наблюдений клиентом с радиусом в регионах
|
// Функции подготавливают пакеты к отправке
|
||||||
std::vector<std::tuple<WorldId_t, Pos::Object, uint8_t>> getViewPoints();
|
// Отслеживаемое игроком использование контента
|
||||||
|
// Maybe?
|
||||||
|
// Текущий список вокселей, определения нод, которые больше не используются в чанке, и определения нод, которые теперь используются
|
||||||
|
//void prepareChunkUpdate_Voxels(WorldId_t worldId, Pos::GlobalChunk chunkPos, const std::vector<VoxelCube> &voxels, const std::vector<DefVoxelId_t> &noLongerInUseDefs, const std::vector<DefVoxelId_t> &nowUsed);
|
||||||
|
void prepareChunkUpdate_Voxels(WorldId_t worldId, Pos::GlobalChunk chunkPos, const std::vector<VoxelCube> &voxels);
|
||||||
|
void prepareChunkUpdate_Nodes(WorldId_t worldId, Pos::GlobalChunk chunkPos, const std::unordered_map<Pos::Local16_u, Node> &nodes);
|
||||||
|
void prepareChunkUpdate_LightPrism(WorldId_t worldId, Pos::GlobalChunk chunkPos, const LightPrism *lights);
|
||||||
|
void prepareChunkRemove(WorldId_t worldId, Pos::GlobalChunk chunkPos);
|
||||||
|
|
||||||
/*
|
void prepareEntitySwap(GlobalEntityId_t prevEntityId, GlobalEntityId_t nextEntityId);
|
||||||
Сервер собирает изменения миров, сжимает их и раздаёт на отправку игрокам
|
void prepareEntityUpdate(GlobalEntityId_t entityId, const Entity *entity);
|
||||||
*/
|
void prepareEntityRemove(GlobalEntityId_t entityId);
|
||||||
|
|
||||||
// Все функции prepare потокобезопасные
|
void prepareWorldUpdate(WorldId_t worldId, World* world);
|
||||||
// maybe используются в BackingChunkPressure_t в GameServer в пуле потоков.
|
void prepareWorldRemove(WorldId_t worldId);
|
||||||
// если возвращает false, то блокировка сейчас находится у другого потока
|
|
||||||
// и запрос не был обработан.
|
|
||||||
|
|
||||||
// В зоне видимости добавился чанк или изменились его воксели
|
void preparePortalUpdate(PortalId_t portalId, void* portal);
|
||||||
bool maybe_prepareChunkUpdate_Voxels(WorldId_t worldId, Pos::GlobalChunk chunkPos, const std::u8string& compressed_voxels,
|
void preparePortalRemove(PortalId_t portalId);
|
||||||
const std::vector<DefVoxelId>& uniq_sorted_defines)
|
|
||||||
{
|
|
||||||
auto lock = NetworkAndResource.tryLock();
|
|
||||||
if(!lock)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
lock->prepareChunkUpdate_Voxels(worldId, chunkPos, compressed_voxels, uniq_sorted_defines);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// В зоне видимости добавился чанк или изменились его ноды
|
|
||||||
bool maybe_prepareChunkUpdate_Nodes(WorldId_t worldId, Pos::GlobalChunk chunkPos, const std::u8string& compressed_nodes,
|
|
||||||
const std::vector<DefNodeId>& uniq_sorted_defines)
|
|
||||||
{
|
|
||||||
auto lock = NetworkAndResource.tryLock();
|
|
||||||
if(!lock)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
lock->prepareChunkUpdate_Nodes(worldId, chunkPos, compressed_nodes, uniq_sorted_defines);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// void prepareChunkUpdate_LightPrism(WorldId_t worldId, Pos::GlobalChunk chunkPos, const LightPrism *lights);
|
|
||||||
|
|
||||||
// Клиент перестал наблюдать за сущностью
|
|
||||||
void prepareEntitiesRemove(const std::vector<ServerEntityId_t>& entityId) { NetworkAndResource.lock()->prepareEntitiesRemove(entityId); }
|
|
||||||
// Регион удалён из зоны видимости
|
|
||||||
void prepareRegionsRemove(WorldId_t worldId, std::vector<Pos::GlobalRegion> regionPoses) { NetworkAndResource.lock()->prepareRegionsRemove(worldId, regionPoses); }
|
|
||||||
// Мир удалён из зоны видимости
|
|
||||||
void prepareWorldRemove(WorldId_t worldId) { NetworkAndResource.lock()->prepareWorldRemove(worldId); }
|
|
||||||
|
|
||||||
// В зоне видимости добавилась новая сущность или она изменилась
|
|
||||||
void prepareEntitiesUpdate(const std::vector<std::tuple<ServerEntityId_t, const Entity*>>& entities) { NetworkAndResource.lock()->prepareEntitiesUpdate(entities); }
|
|
||||||
void prepareEntitiesUpdate_Dynamic(const std::vector<std::tuple<ServerEntityId_t, const Entity*>>& entities) { NetworkAndResource.lock()->prepareEntitiesUpdate_Dynamic(entities); }
|
|
||||||
// Наблюдаемая сущность пересекла границы региона, у неё изменился серверный идентификатор
|
|
||||||
void prepareEntitySwap(ServerEntityId_t prevEntityId, ServerEntityId_t nextEntityId) { NetworkAndResource.lock()->prepareEntitySwap(prevEntityId, nextEntityId); }
|
|
||||||
// Мир появился в зоне видимости или изменился
|
|
||||||
void prepareWorldUpdate(WorldId_t worldId, World* world) { NetworkAndResource.lock()->prepareWorldUpdate(worldId, world); }
|
|
||||||
|
|
||||||
// В зоне видимости добавился порта или он изменился
|
|
||||||
// void preparePortalUpdate(PortalId_t portalId, void* portal);
|
|
||||||
// Клиент перестал наблюдать за порталом
|
|
||||||
// void preparePortalRemove(PortalId_t portalId);
|
|
||||||
|
|
||||||
// Прочие моменты
|
// Прочие моменты
|
||||||
void prepareCameraSetEntity(ServerEntityId_t entityId);
|
void prepareCameraSetEntity(GlobalEntityId_t entityId);
|
||||||
|
|
||||||
// Отправка подготовленных пакетов
|
// Отправка подготовленных пакетов
|
||||||
ResourceRequest pushPreparedPackets();
|
ResourceRequest pushPreparedPackets();
|
||||||
@@ -416,30 +336,28 @@ public:
|
|||||||
// Сюда приходят все обновления ресурсов движка
|
// Сюда приходят все обновления ресурсов движка
|
||||||
// Глобально их можно запросить в выдаче pushPreparedPackets()
|
// Глобально их можно запросить в выдаче pushPreparedPackets()
|
||||||
|
|
||||||
// Оповещение о запрошенных (и не только) ассетах
|
// Двоичные файлы
|
||||||
void informateAssets(const std::vector<std::tuple<EnumAssets, ResourceId, const std::string, const std::string, Resource>>& resources);
|
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 informateDefVoxel(const std::vector<std::pair<DefVoxelId, DefVoxel*>>& voxels) { NetworkAndResource.lock()->informateDefVoxel(voxels); }
|
void informateDefWorld(const std::unordered_map<DefWorldId_t, World*> &worlds);
|
||||||
void informateDefNode(const std::vector<std::pair<DefNodeId, DefNode*>>& nodes) { NetworkAndResource.lock()->informateDefNode(nodes); }
|
void informateDefVoxel(const std::unordered_map<DefVoxelId_t, void*> &voxels);
|
||||||
void informateDefWorld(const std::vector<std::pair<DefWorldId, DefWorld*>>& worlds) { NetworkAndResource.lock()->informateDefWorld(worlds); }
|
void informateDefNode(const std::unordered_map<DefNodeId_t, void*> &nodes);
|
||||||
void informateDefPortal(const std::vector<std::pair<DefPortalId, DefPortal*>>& portals) { NetworkAndResource.lock()->informateDefPortal(portals); }
|
void informateDefEntityes(const std::unordered_map<DefEntityId_t, void*> &entityes);
|
||||||
void informateDefEntity(const std::vector<std::pair<DefEntityId, DefEntity*>>& entityes) { NetworkAndResource.lock()->informateDefEntity(entityes); }
|
void informateDefPortals(const std::unordered_map<DefPortalId_t, void*> &portals);
|
||||||
void informateDefItem(const std::vector<std::pair<DefItemId, DefItem*>>& items) { NetworkAndResource.lock()->informateDefItem(items); }
|
|
||||||
|
|
||||||
void onUpdate();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void checkPacketBorder(uint16_t size);
|
||||||
void protocolError();
|
void protocolError();
|
||||||
coro<> readPacket(Net::AsyncSocket &sock);
|
coro<> readPacket(Net::AsyncSocket &sock);
|
||||||
coro<> rP_System(Net::AsyncSocket &sock);
|
coro<> rP_System(Net::AsyncSocket &sock);
|
||||||
|
|
||||||
// void incrementProfile(const std::vector<TextureId_t> &textures, const std::vector<ModelId_t> &model,
|
void incrementBinary(std::unordered_set<BinTextureId_t> &textures, std::unordered_set<BinSoundId_t> &sounds,
|
||||||
// const std::vector<SoundId_t> &sounds, const std::vector<FontId_t> &font
|
std::unordered_set<BinModelId_t> &models);
|
||||||
// );
|
void decrementBinary(std::unordered_set<BinTextureId_t> &textures, std::unordered_set<BinSoundId_t> &sounds,
|
||||||
// void decrementProfile(std::vector<TextureId_t> &&textures, std::vector<ModelId_t> &&model,
|
std::unordered_set<BinModelId_t> &models);
|
||||||
// std::vector<SoundId_t> &&sounds, std::vector<FontId_t> &&font
|
|
||||||
// );
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
#include "Abstract.hpp"
|
#include "Abstract.hpp"
|
||||||
#include "Common/Abstract.hpp"
|
#include "Common/Abstract.hpp"
|
||||||
#include "Common/Async.hpp"
|
|
||||||
#include <boost/json.hpp>
|
#include <boost/json.hpp>
|
||||||
#include <boost/json/object.hpp>
|
#include <boost/json/object.hpp>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
@@ -11,59 +10,31 @@
|
|||||||
|
|
||||||
namespace LV::Server {
|
namespace LV::Server {
|
||||||
|
|
||||||
/*
|
struct SB_Region {
|
||||||
Обменная единица мира
|
|
||||||
*/
|
|
||||||
struct SB_Region_In {
|
|
||||||
// Список вокселей всех чанков
|
|
||||||
std::unordered_map<Pos::bvec4u, VoxelCube> Voxels;
|
|
||||||
// Привязка вокселей к ключу профиля
|
|
||||||
std::vector<std::pair<DefVoxelId, std::string>> VoxelsMap;
|
|
||||||
// Ноды всех чанков
|
|
||||||
std::array<std::array<Node, 16*16*16>, 4*4*4> Nodes;
|
|
||||||
// Привязка нод к ключу профиля
|
|
||||||
std::vector<std::pair<DefNodeId, std::string>> NodeMap;
|
|
||||||
// Сущности
|
|
||||||
std::vector<Entity> Entityes;
|
|
||||||
// Привязка идентификатора к ключу профиля
|
|
||||||
std::vector<std::pair<DefEntityId, std::string>> EntityMap;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct DB_Region_Out {
|
|
||||||
std::vector<VoxelCube_Region> Voxels;
|
std::vector<VoxelCube_Region> Voxels;
|
||||||
std::array<std::array<Node, 16*16*16>, 4*4*4> Nodes;
|
std::unordered_map<DefVoxelId_t, std::string> VoxelsMap;
|
||||||
|
std::unordered_map<Pos::Local16_u, Node> Nodes;
|
||||||
|
std::unordered_map<DefNodeId_t, std::string> NodeMap;
|
||||||
std::vector<Entity> Entityes;
|
std::vector<Entity> Entityes;
|
||||||
|
std::unordered_map<DefEntityId_t, std::string> EntityMap;
|
||||||
std::vector<std::string> VoxelIdToKey, NodeIdToKey, EntityToKey;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class IWorldSaveBackend {
|
class IWorldSaveBackend {
|
||||||
public:
|
public:
|
||||||
virtual ~IWorldSaveBackend();
|
virtual ~IWorldSaveBackend();
|
||||||
|
|
||||||
struct TickSyncInfo_In {
|
// Может ли использоваться параллельно
|
||||||
// Для загрузки и более не используемые (регионы автоматически подгружаются по списку загруженных)
|
virtual bool isAsync() { return false; };
|
||||||
std::unordered_map<WorldId_t, std::vector<Pos::GlobalRegion>> Load, Unload;
|
// Существует ли регион
|
||||||
// Регионы для сохранения
|
virtual bool isExist(std::string worldId, Pos::GlobalRegion regionPos) = 0;
|
||||||
std::unordered_map<WorldId_t, std::vector<std::pair<Pos::GlobalRegion, SB_Region_In>>> ToSave;
|
// Загрузить регион
|
||||||
};
|
virtual void load(std::string worldId, Pos::GlobalRegion regionPos, SB_Region *data) = 0;
|
||||||
|
// Сохранить регион
|
||||||
struct TickSyncInfo_Out {
|
virtual void save(std::string worldId, Pos::GlobalRegion regionPos, const SB_Region *data) = 0;
|
||||||
std::unordered_map<WorldId_t, std::vector<Pos::GlobalRegion>> NotExisten;
|
// Удалить регион
|
||||||
std::unordered_map<WorldId_t, std::vector<std::pair<Pos::GlobalRegion, DB_Region_Out>>> LoadedRegions;
|
virtual void remove(std::string worldId, Pos::GlobalRegion regionPos) = 0;
|
||||||
};
|
// Удалить мир
|
||||||
|
virtual void remove(std::string worldId) = 0;
|
||||||
/*
|
|
||||||
Обмен данными раз в такт
|
|
||||||
Хотим списки на загрузку регионов
|
|
||||||
Отдаём уже загруженные регионы и список отсутствующих в базе регионов
|
|
||||||
*/
|
|
||||||
virtual TickSyncInfo_Out tickSync(TickSyncInfo_In &&data) = 0;
|
|
||||||
|
|
||||||
/*
|
|
||||||
Устанавливает радиус вокруг прогруженного региона для предзагрузки регионов
|
|
||||||
*/
|
|
||||||
virtual void changePreloadDistance(uint8_t value) = 0;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SB_Player {
|
struct SB_Player {
|
||||||
@@ -74,6 +45,8 @@ class IPlayerSaveBackend {
|
|||||||
public:
|
public:
|
||||||
virtual ~IPlayerSaveBackend();
|
virtual ~IPlayerSaveBackend();
|
||||||
|
|
||||||
|
// Может ли использоваться параллельно
|
||||||
|
virtual bool isAsync() { return false; };
|
||||||
// Существует ли игрок
|
// Существует ли игрок
|
||||||
virtual bool isExist(PlayerId_t playerId) = 0;
|
virtual bool isExist(PlayerId_t playerId) = 0;
|
||||||
// Загрузить игрока
|
// Загрузить игрока
|
||||||
@@ -93,30 +66,34 @@ class IAuthSaveBackend {
|
|||||||
public:
|
public:
|
||||||
virtual ~IAuthSaveBackend();
|
virtual ~IAuthSaveBackend();
|
||||||
|
|
||||||
|
// Может ли использоваться параллельно
|
||||||
|
virtual bool isAsync() { return false; };
|
||||||
// Существует ли игрок
|
// Существует ли игрок
|
||||||
virtual coro<bool> isExist(std::string username) = 0;
|
virtual bool isExist(std::string playerId) = 0;
|
||||||
// Переименовать игрока
|
// Переименовать игрока
|
||||||
virtual coro<> rename(std::string prevUsername, std::string newUsername) = 0;
|
virtual void rename(std::string fromPlayerId, std::string toPlayerId) = 0;
|
||||||
// Загрузить игрока (если есть, вернёт true)
|
// Загрузить игрока
|
||||||
virtual coro<bool> load(std::string username, SB_Auth &data) = 0;
|
virtual void load(std::string playerId, SB_Auth *data) = 0;
|
||||||
// Сохранить игрока
|
// Сохранить игрока
|
||||||
virtual coro<> save(std::string username, const SB_Auth &data) = 0;
|
virtual void save(std::string playerId, const SB_Auth *data) = 0;
|
||||||
// Удалить игрока
|
// Удалить игрока
|
||||||
virtual coro<> remove(std::string username) = 0;
|
virtual void remove(std::string playerId) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
class IModStorageSaveBackend {
|
class IModStorageSaveBackend {
|
||||||
public:
|
public:
|
||||||
virtual ~IModStorageSaveBackend();
|
virtual ~IModStorageSaveBackend();
|
||||||
|
|
||||||
// // Загрузить запись
|
// Может ли использоваться параллельно
|
||||||
// virtual void load(std::string domain, std::string key, std::string *data) = 0;
|
virtual bool isAsync() { return false; };
|
||||||
// // Сохранить запись
|
// Загрузить запись
|
||||||
// virtual void save(std::string domain, std::string key, const std::string *data) = 0;
|
virtual void load(std::string domain, std::string key, std::string *data) = 0;
|
||||||
// // Удалить запись
|
// Сохранить запись
|
||||||
// virtual void remove(std::string domain, std::string key) = 0;
|
virtual void save(std::string domain, std::string key, const std::string *data) = 0;
|
||||||
// // Удалить домен
|
// Удалить запись
|
||||||
// virtual void remove(std::string domain) = 0;
|
virtual void remove(std::string domain, std::string key) = 0;
|
||||||
|
// Удалить домен
|
||||||
|
virtual void remove(std::string domain) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
class ISaveBackendProvider {
|
class ISaveBackendProvider {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class WSB_Filesystem : public IWorldSaveBackend {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
WSB_Filesystem(const boost::json::object &data) {
|
WSB_Filesystem(const boost::json::object &data) {
|
||||||
Dir = (std::string) data.at("path").as_string();
|
Dir = (std::string) data.at("Path").as_string();
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual ~WSB_Filesystem() {
|
virtual ~WSB_Filesystem() {
|
||||||
@@ -30,80 +30,84 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
fs::path getPath(std::string worldId, Pos::GlobalRegion regionPos) {
|
fs::path getPath(std::string worldId, Pos::GlobalRegion regionPos) {
|
||||||
return Dir / worldId / std::to_string(regionPos.x) / std::to_string(regionPos.y) / std::to_string(regionPos.z);
|
return Dir / worldId / std::to_string(regionPos.X) / std::to_string(regionPos.Y) / std::to_string(regionPos.Z);
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual TickSyncInfo_Out tickSync(TickSyncInfo_In &&data) override {
|
virtual bool isAsync() { return false; };
|
||||||
TickSyncInfo_Out out;
|
|
||||||
out.NotExisten = std::move(data.Load);
|
virtual bool isExist(std::string worldId, Pos::GlobalRegion regionPos) {
|
||||||
return out;
|
return fs::exists(getPath(worldId, regionPos));
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void changePreloadDistance(uint8_t value) override {
|
virtual void load(std::string worldId, Pos::GlobalRegion regionPos, SB_Region *data) {
|
||||||
|
std::ifstream fd(getPath(worldId, regionPos));
|
||||||
|
js::object jobj = js::parse(fd).as_object();
|
||||||
|
|
||||||
|
{
|
||||||
|
js::array &jaVoxels = jobj.at("Voxels").as_array();
|
||||||
|
for(js::value &jvVoxel : jaVoxels) {
|
||||||
|
js::object &joVoxel = jvVoxel.as_object();
|
||||||
|
VoxelCube_Region cube;
|
||||||
|
cube.VoxelId = joVoxel.at("Material").as_uint64();
|
||||||
|
cube.Left.X = joVoxel.at("LeftX").as_uint64();
|
||||||
|
cube.Left.Y = joVoxel.at("LeftY").as_uint64();
|
||||||
|
cube.Left.Z = joVoxel.at("LeftZ").as_uint64();
|
||||||
|
cube.Right.X = joVoxel.at("RightX").as_uint64();
|
||||||
|
cube.Right.Y = joVoxel.at("RightY").as_uint64();
|
||||||
|
cube.Right.Z = joVoxel.at("RightZ").as_uint64();
|
||||||
|
data->Voxels.push_back(cube);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
js::object &joVoxelMap = jobj.at("VoxelsMap").as_object();
|
||||||
|
for(js::key_value_pair &jkvp : joVoxelMap) {
|
||||||
|
data->VoxelsMap[std::stoul(jkvp.key())] = jkvp.value().as_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// virtual void load(std::string worldId, Pos::GlobalRegion regionPos, SB_Region *data) {
|
virtual void save(std::string worldId, Pos::GlobalRegion regionPos, const SB_Region *data) {
|
||||||
// std::ifstream fd(getPath(worldId, regionPos));
|
js::object jobj;
|
||||||
// js::object jobj = js::parse(fd).as_object();
|
|
||||||
|
|
||||||
// {
|
{
|
||||||
// js::array &jaVoxels = jobj.at("Voxels").as_array();
|
js::array jaVoxels;
|
||||||
// for(js::value &jvVoxel : jaVoxels) {
|
for(const VoxelCube_Region &cube : data->Voxels) {
|
||||||
// js::object &joVoxel = jvVoxel.as_object();
|
js::object joVoxel;
|
||||||
// VoxelCube_Region cube;
|
joVoxel["Material"] = cube.VoxelId;
|
||||||
// cube.Data = joVoxel.at("Data").as_uint64();
|
joVoxel["LeftX"] = cube.Left.X;
|
||||||
// cube.Left.x = joVoxel.at("LeftX").as_uint64();
|
joVoxel["LeftY"] = cube.Left.Y;
|
||||||
// cube.Left.y = joVoxel.at("LeftY").as_uint64();
|
joVoxel["LeftZ"] = cube.Left.Z;
|
||||||
// cube.Left.z = joVoxel.at("LeftZ").as_uint64();
|
joVoxel["RightX"] = cube.Right.X;
|
||||||
// cube.Right.x = joVoxel.at("RightX").as_uint64();
|
joVoxel["RightY"] = cube.Right.Y;
|
||||||
// cube.Right.y = joVoxel.at("RightY").as_uint64();
|
joVoxel["RightZ"] = cube.Right.Z;
|
||||||
// cube.Right.z = joVoxel.at("RightZ").as_uint64();
|
jaVoxels.push_back(std::move(joVoxel));
|
||||||
// data->Voxels.push_back(cube);
|
}
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// {
|
jobj["Voxels"] = std::move(jaVoxels);
|
||||||
// js::object &joVoxelMap = jobj.at("VoxelsMap").as_object();
|
}
|
||||||
// for(js::key_value_pair &jkvp : joVoxelMap) {
|
|
||||||
// data->VoxelsMap[std::stoul(jkvp.key())] = jkvp.value().as_string();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// virtual void save(std::string worldId, Pos::GlobalRegion regionPos, const SB_Region *data) {
|
{
|
||||||
// js::object jobj;
|
js::object joVoxelMap;
|
||||||
|
for(const auto &pair : data->VoxelsMap) {
|
||||||
|
joVoxelMap[std::to_string(pair.first)] = pair.second;
|
||||||
|
}
|
||||||
|
|
||||||
// {
|
jobj["VoxelsMap"] = std::move(joVoxelMap);
|
||||||
// js::array jaVoxels;
|
}
|
||||||
// for(const VoxelCube_Region &cube : data->Voxels) {
|
|
||||||
// js::object joVoxel;
|
|
||||||
// joVoxel["Data"] = cube.Data;
|
|
||||||
// joVoxel["LeftX"] = cube.Left.x;
|
|
||||||
// joVoxel["LeftY"] = cube.Left.y;
|
|
||||||
// joVoxel["LeftZ"] = cube.Left.z;
|
|
||||||
// joVoxel["RightX"] = cube.Right.x;
|
|
||||||
// joVoxel["RightY"] = cube.Right.y;
|
|
||||||
// joVoxel["RightZ"] = cube.Right.z;
|
|
||||||
// jaVoxels.push_back(std::move(joVoxel));
|
|
||||||
// }
|
|
||||||
|
|
||||||
// jobj["Voxels"] = std::move(jaVoxels);
|
fs::create_directories(getPath(worldId, regionPos).parent_path());
|
||||||
// }
|
std::ofstream fd(getPath(worldId, regionPos));
|
||||||
|
fd << js::serialize(jobj);
|
||||||
|
}
|
||||||
|
|
||||||
// {
|
virtual void remove(std::string worldId, Pos::GlobalRegion regionPos) {
|
||||||
// js::object joVoxelMap;
|
fs::remove(getPath(worldId, regionPos));
|
||||||
// for(const auto &pair : data->VoxelsMap) {
|
}
|
||||||
// joVoxelMap[std::to_string(pair.first)] = pair.second;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// jobj["VoxelsMap"] = std::move(joVoxelMap);
|
virtual void remove(std::string worldId) {
|
||||||
// }
|
fs::remove_all(Dir / worldId);
|
||||||
|
}
|
||||||
// fs::create_directories(getPath(worldId, regionPos).parent_path());
|
|
||||||
// std::ofstream fd(getPath(worldId, regionPos));
|
|
||||||
// fd << js::serialize(jobj);
|
|
||||||
// }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class PSB_Filesystem : public IPlayerSaveBackend {
|
class PSB_Filesystem : public IPlayerSaveBackend {
|
||||||
@@ -111,7 +115,7 @@ class PSB_Filesystem : public IPlayerSaveBackend {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
PSB_Filesystem(const boost::json::object &data) {
|
PSB_Filesystem(const boost::json::object &data) {
|
||||||
Dir = (std::string) data.at("path").as_string();
|
Dir = (std::string) data.at("Path").as_string();
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual ~PSB_Filesystem() {
|
virtual ~PSB_Filesystem() {
|
||||||
@@ -146,7 +150,7 @@ class ASB_Filesystem : public IAuthSaveBackend {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
ASB_Filesystem(const boost::json::object &data) {
|
ASB_Filesystem(const boost::json::object &data) {
|
||||||
Dir = (std::string) data.at("path").as_string();
|
Dir = (std::string) data.at("Path").as_string();
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual ~ASB_Filesystem() {
|
virtual ~ASB_Filesystem() {
|
||||||
@@ -159,39 +163,35 @@ public:
|
|||||||
|
|
||||||
virtual bool isAsync() { return false; };
|
virtual bool isAsync() { return false; };
|
||||||
|
|
||||||
virtual coro<bool> isExist(std::string useranme) override {
|
virtual bool isExist(std::string playerId) {
|
||||||
co_return fs::exists(getPath(useranme));
|
return fs::exists(getPath(playerId));
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual coro<> rename(std::string prevUsername, std::string newUsername) override {
|
virtual void rename(std::string fromPlayerId, std::string toPlayerId) {
|
||||||
fs::rename(getPath(prevUsername), getPath(newUsername));
|
fs::rename(getPath(fromPlayerId), getPath(toPlayerId));
|
||||||
co_return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual coro<bool> load(std::string useranme, SB_Auth& data) override {
|
virtual void load(std::string playerId, SB_Auth *data) {
|
||||||
std::ifstream fd(getPath(useranme));
|
std::ifstream fd(getPath(playerId));
|
||||||
js::object jobj = js::parse(fd).as_object();
|
js::object jobj = js::parse(fd).as_object();
|
||||||
|
|
||||||
data.Id = jobj.at("Id").as_uint64();
|
data->Id = jobj.at("Id").as_uint64();
|
||||||
data.PasswordHash = jobj.at("PasswordHash").as_string();
|
data->PasswordHash = jobj.at("PasswordHash").as_string();
|
||||||
co_return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual coro<> save(std::string playerId, const SB_Auth& data) override {
|
virtual void save(std::string playerId, const SB_Auth *data) {
|
||||||
js::object jobj;
|
js::object jobj;
|
||||||
|
|
||||||
jobj["Id"] = data.Id;
|
jobj["Id"] = data->Id;
|
||||||
jobj["PasswordHash"] = data.PasswordHash;
|
jobj["PasswordHash"] = data->PasswordHash;
|
||||||
|
|
||||||
fs::create_directories(getPath(playerId).parent_path());
|
fs::create_directories(getPath(playerId).parent_path());
|
||||||
std::ofstream fd(getPath(playerId));
|
std::ofstream fd(getPath(playerId));
|
||||||
fd << js::serialize(jobj);
|
fd << js::serialize(jobj);
|
||||||
co_return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual coro<> remove(std::string username) override {
|
virtual void remove(std::string playerId) {
|
||||||
fs::remove(getPath(username));
|
fs::remove(getPath(playerId));
|
||||||
co_return;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -200,7 +200,7 @@ class MSSB_Filesystem : public IModStorageSaveBackend {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
MSSB_Filesystem(const boost::json::object &data) {
|
MSSB_Filesystem(const boost::json::object &data) {
|
||||||
Dir = (std::string) data.at("path").as_string();
|
Dir = (std::string) data.at("Path").as_string();
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual ~MSSB_Filesystem() {
|
virtual ~MSSB_Filesystem() {
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
#include "World.hpp"
|
#include "World.hpp"
|
||||||
#include "TOSLib.hpp"
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
|
|
||||||
namespace LV::Server {
|
namespace LV::Server {
|
||||||
|
|
||||||
|
|
||||||
World::World(DefWorldId defId)
|
World::World(DefWorldId_t defId)
|
||||||
: DefId(defId)
|
: DefId(defId)
|
||||||
{
|
{
|
||||||
|
|
||||||
@@ -16,46 +14,29 @@ World::~World() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<Pos::GlobalRegion> World::onRemoteClient_RegionsEnter(std::shared_ptr<RemoteClient> cec, const std::vector<Pos::GlobalRegion>& enter) {
|
void World::onUpdate(GameServer *server, float dtime) {
|
||||||
std::vector<Pos::GlobalRegion> out;
|
|
||||||
|
|
||||||
for(const Pos::GlobalRegion &pos : enter) {
|
|
||||||
auto iterRegion = Regions.find(pos);
|
|
||||||
if(iterRegion == Regions.end()) {
|
|
||||||
out.push_back(pos);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto ®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();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void World::onRemoteClient_RegionsLost(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) {
|
for(const Pos::GlobalRegion &pos : lost) {
|
||||||
auto region = Regions.find(pos);
|
auto region = Regions.find(pos);
|
||||||
if(region == Regions.end())
|
if(region == Regions.end())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
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++) {
|
for(size_t iter = 0; iter < CECs.size(); iter++) {
|
||||||
if(CECs[iter] == cec) {
|
if(CECs[iter] == cec) {
|
||||||
CECs.erase(CECs.begin()+iter);
|
CECs.erase(CECs.begin()+iter);
|
||||||
@@ -65,20 +46,4 @@ void World::onRemoteClient_RegionsLost(std::shared_ptr<RemoteClient> cec, const
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
World::SaveUnloadInfo World::onStepDatabaseSync() {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void World::onUpdate(GameServer *server, float dtime) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
#include "Common/Abstract.hpp"
|
#include "Common/Abstract.hpp"
|
||||||
#include "Server/Abstract.hpp"
|
#include "Server/Abstract.hpp"
|
||||||
#include "Server/RemoteClient.hpp"
|
#include "Server/ContentEventController.hpp"
|
||||||
#include "Server/SaveBackend.hpp"
|
#include "Server/SaveBackend.hpp"
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
@@ -15,26 +15,37 @@ class GameServer;
|
|||||||
|
|
||||||
class Region {
|
class Region {
|
||||||
public:
|
public:
|
||||||
uint64_t IsChunkChanged_Voxels = 0;
|
uint64_t IsChunkChanged_Voxels[64] = {0};
|
||||||
uint64_t IsChunkChanged_Nodes = 0;
|
uint64_t IsChunkChanged_Nodes[64] = {0};
|
||||||
bool IsChanged = false; // Изменён ли был регион, относительно последнего сохранения
|
bool IsChanged = false; // Изменён ли был регион, относительно последнего сохранения
|
||||||
std::unordered_map<Pos::bvec4u, std::vector<VoxelCube>> Voxels;
|
// cx cy cz
|
||||||
|
std::vector<VoxelCube> Voxels[16][16][16];
|
||||||
// x y cx cy cz
|
// x y cx cy cz
|
||||||
//LightPrism Lights[16][16][4][4][4];
|
LightPrism Lights[16][16][16][16][16];
|
||||||
|
std::unordered_map<Pos::Local16_u, Node> Nodes[16][16][16];
|
||||||
std::array<std::array<Node, 16*16*16>, 4*4*4> Nodes;
|
|
||||||
|
|
||||||
std::vector<Entity> Entityes;
|
std::vector<Entity> Entityes;
|
||||||
std::vector<std::shared_ptr<RemoteClient>> RMs, NewRMs;
|
std::vector<ContentEventController*> CECs;
|
||||||
|
// Используется для прорежения количества проверок на наблюдаемые чанки и сущности
|
||||||
|
// В одно обновление региона - проверка одного наблюдателя
|
||||||
|
uint16_t CEC_NextChunkAndEntityesViewCheck = 0;
|
||||||
|
|
||||||
|
bool IsLoaded = false;
|
||||||
float LastSaveTime = 0;
|
float LastSaveTime = 0;
|
||||||
|
|
||||||
void getCollideBoxes(Pos::GlobalRegion rPos, AABB aabb, std::vector<CollisionAABB> &boxes) {
|
void getCollideBoxes(Pos::GlobalRegion rPos, AABB aabb, std::vector<CollisionAABB> &boxes) {
|
||||||
// Абсолютная позиция начала региона
|
// Абсолютная позиция начала региона
|
||||||
Pos::Object raPos = Pos::Object(rPos) << Pos::Object_t::BS_Bit;
|
Pos::Object raPos(rPos.X, rPos.Y, rPos.Z);
|
||||||
|
raPos <<= Pos::Object_t::BS_Bit;
|
||||||
|
|
||||||
// Бокс региона
|
// Бокс региона
|
||||||
AABB regionAABB(raPos, raPos+Pos::Object(Pos::Object_t::BS*64));
|
AABB regionAABB(raPos, raPos+Pos::Object(Pos::Object_t::BS*256));
|
||||||
|
|
||||||
|
// Если регион не загружен, то он весь непроходим
|
||||||
|
if(!IsLoaded) {
|
||||||
|
boxes.emplace_back(regionAABB);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Собираем коробки сущностей
|
// Собираем коробки сущностей
|
||||||
for(size_t iter = 0; iter < Entityes.size(); iter++) {
|
for(size_t iter = 0; iter < Entityes.size(); iter++) {
|
||||||
@@ -54,48 +65,46 @@ public:
|
|||||||
|
|
||||||
// Собираем коробки вокселей
|
// Собираем коробки вокселей
|
||||||
if(aabb.isCollideWith(regionAABB)) {
|
if(aabb.isCollideWith(regionAABB)) {
|
||||||
// Определяем с какими чанками есть пересечения
|
|
||||||
glm::ivec3 beg, end;
|
glm::ivec3 beg, end;
|
||||||
for(int axis = 0; axis < 3; axis++)
|
for(int axis = 0; axis < 3; axis++)
|
||||||
beg[axis] = std::max(aabb.VecMin[axis], regionAABB.VecMin[axis]) >> 12 >> 4;
|
beg[axis] = std::max(aabb.VecMin[axis], regionAABB.VecMin[axis]) >> 16;
|
||||||
for(int axis = 0; axis < 3; axis++)
|
for(int axis = 0; axis < 3; axis++)
|
||||||
end[axis] = (std::min(aabb.VecMax[axis], regionAABB.VecMax[axis])+0xffff) >> 12 >> 4;
|
end[axis] = (std::min(aabb.VecMax[axis], regionAABB.VecMax[axis])+0xffff) >> 16;
|
||||||
|
|
||||||
for(; beg.z <= end.z; beg.z++)
|
for(; beg.z <= end.z; beg.z++)
|
||||||
for(; beg.y <= end.y; beg.y++)
|
for(; beg.y <= end.y; beg.y++)
|
||||||
for(; beg.x <= end.x; beg.x++) {
|
for(; beg.x <= end.x; beg.x++) {
|
||||||
auto iterVoxels = Voxels.find(Pos::bvec4u(beg));
|
std::vector<VoxelCube> &voxels = Voxels[beg.x][beg.y][beg.z];
|
||||||
|
|
||||||
if(iterVoxels == Voxels.end() && iterVoxels->second.empty())
|
if(voxels.empty())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
auto &voxels = iterVoxels->second;
|
|
||||||
|
|
||||||
CollisionAABB aabbInfo = CollisionAABB(regionAABB);
|
CollisionAABB aabbInfo = CollisionAABB(regionAABB);
|
||||||
for(int axis = 0; axis < 3; axis++)
|
for(int axis = 0; axis < 3; axis++)
|
||||||
aabbInfo.VecMin.set(axis, aabbInfo.VecMin[axis] | beg[axis] << 16);
|
aabbInfo.VecMin[axis] |= beg[axis] << 16;
|
||||||
|
|
||||||
for(size_t iter = 0; iter < voxels.size(); iter++) {
|
for(size_t iter = 0; iter < voxels.size(); iter++) {
|
||||||
VoxelCube &cube = voxels[iter];
|
VoxelCube &cube = voxels[iter];
|
||||||
|
|
||||||
for(int axis = 0; axis < 3; axis++)
|
for(int axis = 0; axis < 3; axis++)
|
||||||
aabbInfo.VecMin.set(axis, aabbInfo.VecMin[axis] & ~0xff00);
|
aabbInfo.VecMin[axis] &= ~0xff00;
|
||||||
aabbInfo.VecMax = aabbInfo.VecMin;
|
aabbInfo.VecMax = aabbInfo.VecMin;
|
||||||
|
|
||||||
aabbInfo.VecMin.x |= int(cube.Pos.x) << 6;
|
aabbInfo.VecMin.x |= int(cube.Left.X) << 8;
|
||||||
aabbInfo.VecMin.y |= int(cube.Pos.y) << 6;
|
aabbInfo.VecMin.y |= int(cube.Left.Y) << 8;
|
||||||
aabbInfo.VecMin.z |= int(cube.Pos.z) << 6;
|
aabbInfo.VecMin.z |= int(cube.Left.Z) << 8;
|
||||||
|
|
||||||
aabbInfo.VecMax.x |= int(cube.Pos.x+cube.Size.x+1) << 6;
|
aabbInfo.VecMax.x |= int(cube.Right.X) << 8;
|
||||||
aabbInfo.VecMax.y |= int(cube.Pos.y+cube.Size.y+1) << 6;
|
aabbInfo.VecMax.y |= int(cube.Right.Y) << 8;
|
||||||
aabbInfo.VecMax.z |= int(cube.Pos.z+cube.Size.z+1) << 6;
|
aabbInfo.VecMax.z |= int(cube.Right.Z) << 8;
|
||||||
|
|
||||||
if(aabb.isCollideWith(aabbInfo)) {
|
if(aabb.isCollideWith(aabbInfo)) {
|
||||||
aabbInfo = {
|
aabbInfo = {
|
||||||
.Type = CollisionAABB::EnumType::Voxel,
|
.Type = CollisionAABB::EnumType::Voxel,
|
||||||
.Voxel = {
|
.Voxel = {
|
||||||
.Chunk = Pos::bvec4u(beg.x, beg.y, beg.z),
|
.Chunk = Pos::Local16_u(beg.x, beg.y, beg.z),
|
||||||
.Index = static_cast<uint32_t>(iter),
|
.Index = static_cast<uint32_t>(iter),
|
||||||
|
.Id = cube.VoxelId
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -109,7 +118,7 @@ public:
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RegionEntityId_t pushEntity(Entity &entity) {
|
LocalEntityId_t pushEntity(Entity &entity) {
|
||||||
for(size_t iter = 0; iter < Entityes.size(); iter++) {
|
for(size_t iter = 0; iter < Entityes.size(); iter++) {
|
||||||
Entity &obj = Entityes[iter];
|
Entity &obj = Entityes[iter];
|
||||||
|
|
||||||
@@ -126,52 +135,40 @@ public:
|
|||||||
return Entityes.size()-1;
|
return Entityes.size()-1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// В регионе не осталось места
|
return LocalEntityId_t(-1);
|
||||||
return RegionEntityId_t(-1);
|
}
|
||||||
|
|
||||||
|
void load(SB_Region *data) {
|
||||||
|
convertRegionVoxelsToChunks(data->Voxels, (std::vector<VoxelCube>*) Voxels);
|
||||||
|
}
|
||||||
|
|
||||||
|
void save(SB_Region *data) {
|
||||||
|
data->Voxels.clear();
|
||||||
|
convertChunkVoxelsToRegion((const std::vector<VoxelCube>*) Voxels, data->Voxels);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class World {
|
class World {
|
||||||
DefWorldId DefId;
|
DefWorldId_t DefId;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
std::vector<Pos::GlobalRegion> NeedToLoad;
|
||||||
std::unordered_map<Pos::GlobalRegion, std::unique_ptr<Region>> Regions;
|
std::unordered_map<Pos::GlobalRegion, std::unique_ptr<Region>> Regions;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
World(DefWorldId defId);
|
World(DefWorldId_t defId);
|
||||||
~World();
|
~World();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Подписывает игрока на отслеживаемые им регионы
|
Обновить регионы
|
||||||
Возвращает список не загруженных регионов, на которые соответственно игрока не получилось подписать
|
|
||||||
При подписи происходит отправка всех чанков и сущностей региона
|
|
||||||
*/
|
|
||||||
std::vector<Pos::GlobalRegion> onRemoteClient_RegionsEnter(std::shared_ptr<RemoteClient> cec, const std::vector<Pos::GlobalRegion> &enter);
|
|
||||||
void onRemoteClient_RegionsLost(std::shared_ptr<RemoteClient> cec, const std::vector<Pos::GlobalRegion>& lost);
|
|
||||||
struct SaveUnloadInfo {
|
|
||||||
std::vector<Pos::GlobalRegion> ToUnload;
|
|
||||||
std::vector<std::pair<Pos::GlobalRegion, SB_Region_In>> ToSave;
|
|
||||||
};
|
|
||||||
SaveUnloadInfo onStepDatabaseSync();
|
|
||||||
|
|
||||||
struct RegionIn {
|
|
||||||
std::unordered_map<Pos::bvec4u, std::vector<VoxelCube>> Voxels;
|
|
||||||
std::array<std::array<Node, 16*16*16>, 4*4*4> Nodes;
|
|
||||||
std::vector<Entity> Entityes;
|
|
||||||
};
|
|
||||||
void pushRegions(std::vector<std::pair<Pos::GlobalRegion, RegionIn>>);
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
Проверка использования регионов,
|
|
||||||
*/
|
*/
|
||||||
void onUpdate(GameServer *server, float dtime);
|
void onUpdate(GameServer *server, float dtime);
|
||||||
|
|
||||||
/*
|
// Игрок начал отслеживать регионы
|
||||||
|
void onCEC_RegionsEnter(ContentEventController *cec, const std::vector<Pos::GlobalRegion> &enter);
|
||||||
|
void onCEC_RegionsLost(ContentEventController *cec, const std::vector<Pos::GlobalRegion> &lost);
|
||||||
|
|
||||||
*/
|
DefWorldId_t getDefId() const { return DefId; }
|
||||||
|
|
||||||
DefWorldId getDefId() const { return DefId; }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
266
Src/TOSAsync.hpp
@@ -1,61 +1,59 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "TOSLib.hpp"
|
#include "TOSLib.hpp"
|
||||||
#include <functional>
|
#include "boost/asio/associated_cancellation_slot.hpp"
|
||||||
|
#include "boost/asio/associated_executor.hpp"
|
||||||
|
#include "boost/asio/deadline_timer.hpp"
|
||||||
|
#include "boost/asio/io_context.hpp"
|
||||||
#include "boost/system/detail/error_code.hpp"
|
#include "boost/system/detail/error_code.hpp"
|
||||||
#include "boost/system/system_error.hpp"
|
|
||||||
#include <boost/asio.hpp>
|
#include <boost/asio.hpp>
|
||||||
|
#include <boost/asio/detached.hpp>
|
||||||
#include <boost/asio/experimental/awaitable_operators.hpp>
|
#include <boost/asio/experimental/awaitable_operators.hpp>
|
||||||
#include <exception>
|
#include <exception>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <list>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
namespace TOS {
|
namespace TOS {
|
||||||
|
|
||||||
using namespace boost::asio::experimental::awaitable_operators;
|
using namespace boost::asio::experimental::awaitable_operators;
|
||||||
|
namespace asio = boost::asio;
|
||||||
template<typename T = void>
|
template<typename T = void>
|
||||||
using coro = boost::asio::awaitable<T>;
|
using coro = boost::asio::awaitable<T>;
|
||||||
namespace asio = boost::asio;
|
|
||||||
|
|
||||||
class AsyncSemaphore
|
// class AsyncSemaphore
|
||||||
{
|
// {
|
||||||
boost::asio::deadline_timer Deadline;
|
// boost::asio::deadline_timer Deadline;
|
||||||
std::atomic<uint8_t> Lock = 0;
|
// std::atomic<uint8_t> Lock = 0;
|
||||||
|
|
||||||
public:
|
// public:
|
||||||
AsyncSemaphore(
|
// AsyncSemaphore(boost::asio::io_context& ioc)
|
||||||
boost::asio::io_context& ioc)
|
// : Deadline(ioc, boost::posix_time::ptime(boost::posix_time::pos_infin))
|
||||||
: Deadline(ioc, boost::posix_time::ptime(boost::posix_time::pos_infin))
|
// {}
|
||||||
{}
|
|
||||||
|
|
||||||
boost::asio::awaitable<void> async_wait() {
|
// coro<> async_wait() {
|
||||||
try {
|
// try {
|
||||||
co_await Deadline.async_wait(boost::asio::use_awaitable);
|
// co_await Deadline.async_wait(boost::asio::use_awaitable);
|
||||||
} catch(boost::system::system_error code) {
|
// } catch(boost::system::system_error code) {
|
||||||
if(code.code() != boost::system::errc::operation_canceled)
|
// if(code.code() != boost::system::errc::operation_canceled)
|
||||||
throw;
|
// throw;
|
||||||
}
|
// }
|
||||||
|
|
||||||
co_await boost::asio::this_coro::throw_if_cancelled();
|
// co_await asio::this_coro::throw_if_cancelled();
|
||||||
}
|
// }
|
||||||
|
|
||||||
boost::asio::awaitable<void> async_wait(std::function<bool()> predicate) {
|
// coro<> async_wait(std::function<bool()> predicate) {
|
||||||
while(!predicate())
|
// while(!predicate())
|
||||||
co_await async_wait();
|
// co_await async_wait();
|
||||||
}
|
// }
|
||||||
|
|
||||||
void notify_one() {
|
// void notify_one() {
|
||||||
Deadline.cancel_one();
|
// Deadline.cancel_one();
|
||||||
}
|
// }
|
||||||
|
|
||||||
void notify_all() {
|
|
||||||
Deadline.cancel();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
// void notify_all() {
|
||||||
|
// Deadline.cancel();
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Многие могут уведомлять одного
|
Многие могут уведомлять одного
|
||||||
@@ -74,13 +72,14 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void wait() {
|
void wait() {
|
||||||
try { Timer.wait(); } catch(...) {}
|
Timer.wait();
|
||||||
Timer.expires_at(boost::posix_time::ptime(boost::posix_time::pos_infin));
|
Timer.expires_at(boost::posix_time::ptime(boost::posix_time::pos_infin));
|
||||||
}
|
}
|
||||||
|
|
||||||
coro<> async_wait() {
|
coro<> async_wait() {
|
||||||
try { co_await Timer.async_wait(); } catch(...) {}
|
try { co_await Timer.async_wait(); } catch(...) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class WaitableCoro {
|
class WaitableCoro {
|
||||||
@@ -93,10 +92,11 @@ public:
|
|||||||
: IOC(ioc)
|
: IOC(ioc)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
void co_spawn(coro<> token) {
|
template<typename Token>
|
||||||
|
void co_spawn(Token token) {
|
||||||
Symaphore = std::make_shared<MultipleToOne_AsyncSymaphore>(IOC);
|
Symaphore = std::make_shared<MultipleToOne_AsyncSymaphore>(IOC);
|
||||||
asio::co_spawn(IOC, [token = std::move(token), symaphore = Symaphore]() -> coro<> {
|
asio::co_spawn(IOC, [token = std::move(token), symaphore = Symaphore]() -> coro<> {
|
||||||
try { co_await std::move(const_cast<coro<>&>(token)); } catch(...) {}
|
co_await std::move(token);
|
||||||
symaphore->notify();
|
symaphore->notify();
|
||||||
}, asio::detached);
|
}, asio::detached);
|
||||||
}
|
}
|
||||||
@@ -110,115 +110,18 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class AsyncUseControl {
|
|
||||||
public:
|
|
||||||
class Lock {
|
|
||||||
AsyncUseControl *AUC;
|
|
||||||
|
|
||||||
public:
|
|
||||||
Lock(AsyncUseControl *auc)
|
|
||||||
: AUC(auc)
|
|
||||||
{}
|
|
||||||
|
|
||||||
Lock()
|
|
||||||
: AUC(nullptr)
|
|
||||||
{}
|
|
||||||
|
|
||||||
~Lock() {
|
|
||||||
if(AUC)
|
|
||||||
unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
Lock(const Lock&) = delete;
|
|
||||||
Lock(Lock&& obj)
|
|
||||||
: AUC(obj.AUC)
|
|
||||||
{
|
|
||||||
obj.AUC = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
Lock& operator=(const Lock&) = delete;
|
|
||||||
Lock& operator=(Lock&& obj) {
|
|
||||||
if(&obj == this)
|
|
||||||
return *this;
|
|
||||||
|
|
||||||
if(AUC)
|
|
||||||
unlock();
|
|
||||||
|
|
||||||
AUC = obj.AUC;
|
|
||||||
obj.AUC = nullptr;
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
void unlock() {
|
|
||||||
assert(AUC);
|
|
||||||
|
|
||||||
if(--AUC->Uses == 0 && AUC->OnNoUse) {
|
|
||||||
asio::post(AUC->IOC, std::move(AUC->OnNoUse));
|
|
||||||
}
|
|
||||||
|
|
||||||
AUC = nullptr;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private:
|
|
||||||
asio::io_context &IOC;
|
|
||||||
std::move_only_function<void()> OnNoUse;
|
|
||||||
std::atomic_int Uses = 0;
|
|
||||||
|
|
||||||
public:
|
|
||||||
AsyncUseControl(asio::io_context &ioc)
|
|
||||||
: IOC(ioc)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
template<BOOST_ASIO_COMPLETION_TOKEN_FOR(void()) Token = asio::default_completion_token_t<asio::io_context>>
|
|
||||||
auto wait(Token&& token = asio::default_completion_token_t<asio::io_context>()) {
|
|
||||||
auto initiation = [this](auto&& token) {
|
|
||||||
int value;
|
|
||||||
do {
|
|
||||||
value = Uses.exchange(-1);
|
|
||||||
} while(value == -1);
|
|
||||||
|
|
||||||
OnNoUse = std::move(token);
|
|
||||||
|
|
||||||
if(value == 0)
|
|
||||||
OnNoUse();
|
|
||||||
|
|
||||||
Uses.exchange(value);
|
|
||||||
};
|
|
||||||
|
|
||||||
return asio::async_initiate<Token, void()>(initiation, token);
|
|
||||||
}
|
|
||||||
|
|
||||||
Lock use() {
|
|
||||||
int value;
|
|
||||||
do {
|
|
||||||
value = Uses.exchange(-1);
|
|
||||||
} while(value == -1);
|
|
||||||
|
|
||||||
if(OnNoUse)
|
|
||||||
throw boost::system::system_error(asio::error::operation_aborted, "OnNoUse");
|
|
||||||
|
|
||||||
Uses.exchange(++value);
|
|
||||||
return Lock(this);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Используется, чтобы вместо уничтожения объекта в умной ссылке, вызвать корутину с co_await asyncDestructor()
|
Используется, чтобы вместо уничтожения объекта в умной ссылке, вызвать корутину с co_await asyncDestructor()
|
||||||
*/
|
*/
|
||||||
class IAsyncDestructible : public std::enable_shared_from_this<IAsyncDestructible> {
|
class IAsyncDestructible : public std::enable_shared_from_this<IAsyncDestructible> {
|
||||||
protected:
|
protected:
|
||||||
asio::io_context &IOC;
|
asio::io_context &IOC;
|
||||||
AsyncUseControl AUC;
|
|
||||||
|
|
||||||
virtual coro<> asyncDestructor() { co_await AUC.wait(); }
|
virtual coro<> asyncDestructor() { co_return; }
|
||||||
|
|
||||||
public:
|
public:
|
||||||
IAsyncDestructible(asio::io_context &ioc)
|
IAsyncDestructible(asio::io_context &ioc)
|
||||||
: IOC(ioc), AUC(ioc)
|
: IOC(ioc)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
virtual ~IAsyncDestructible() {}
|
virtual ~IAsyncDestructible() {}
|
||||||
@@ -228,7 +131,7 @@ protected:
|
|||||||
static std::shared_ptr<T> createShared(asio::io_context &ioc, T *ptr)
|
static std::shared_ptr<T> createShared(asio::io_context &ioc, T *ptr)
|
||||||
{
|
{
|
||||||
return std::shared_ptr<T>(ptr, [&ioc = ioc](T *ptr) {
|
return std::shared_ptr<T>(ptr, [&ioc = ioc](T *ptr) {
|
||||||
boost::asio::co_spawn(ioc, [&ioc = ioc](IAsyncDestructible *ptr) -> coro<> {
|
boost::asio::co_spawn(ioc, [](IAsyncDestructible *ptr) -> coro<> {
|
||||||
try { co_await ptr->asyncDestructor(); } catch(...) { }
|
try { co_await ptr->asyncDestructor(); } catch(...) { }
|
||||||
delete ptr;
|
delete ptr;
|
||||||
co_return;
|
co_return;
|
||||||
@@ -273,89 +176,4 @@ protected:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
}
|
||||||
225
Src/TOSLib.hpp
@@ -4,8 +4,6 @@
|
|||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <mutex>
|
|
||||||
#include <shared_mutex>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
@@ -13,233 +11,10 @@
|
|||||||
#define _USE_MATH_DEFINES
|
#define _USE_MATH_DEFINES
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <assert.h>
|
|
||||||
|
|
||||||
namespace TOS {
|
namespace TOS {
|
||||||
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
class MutexObject {
|
|
||||||
public:
|
|
||||||
template<typename... Args>
|
|
||||||
explicit MutexObject(Args&&... args)
|
|
||||||
: value(std::forward<Args>(args)...) {}
|
|
||||||
|
|
||||||
class SharedLock {
|
|
||||||
public:
|
|
||||||
SharedLock(MutexObject* obj, std::shared_lock<std::shared_mutex> lock)
|
|
||||||
: obj(obj), lock(std::move(lock)) {}
|
|
||||||
|
|
||||||
const T& get() const { return obj->value; }
|
|
||||||
const T& operator*() const { return obj->value; }
|
|
||||||
const T* operator->() const { return &obj->value; }
|
|
||||||
|
|
||||||
void unlock() { lock.unlock(); }
|
|
||||||
|
|
||||||
operator bool() const {
|
|
||||||
return lock.owns_lock();
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
MutexObject* obj;
|
|
||||||
std::shared_lock<std::shared_mutex> lock;
|
|
||||||
};
|
|
||||||
|
|
||||||
class ExclusiveLock {
|
|
||||||
public:
|
|
||||||
ExclusiveLock(MutexObject* obj, std::unique_lock<std::shared_mutex> lock)
|
|
||||||
: obj(obj), lock(std::move(lock)) {}
|
|
||||||
|
|
||||||
T& get() const { return obj->value; }
|
|
||||||
T& operator*() const { return obj->value; }
|
|
||||||
T* operator->() const { return &obj->value; }
|
|
||||||
|
|
||||||
void unlock() { lock.unlock(); }
|
|
||||||
|
|
||||||
operator bool() const {
|
|
||||||
return lock.owns_lock();
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
MutexObject* obj;
|
|
||||||
std::unique_lock<std::shared_mutex> lock;
|
|
||||||
};
|
|
||||||
|
|
||||||
SharedLock shared_lock() {
|
|
||||||
return SharedLock(this, std::shared_lock(mutex));
|
|
||||||
}
|
|
||||||
|
|
||||||
SharedLock shared_lock(const std::try_to_lock_t& tag) {
|
|
||||||
return SharedLock(this, std::shared_lock(mutex, tag));
|
|
||||||
}
|
|
||||||
|
|
||||||
SharedLock shared_lock(const std::adopt_lock_t& tag) {
|
|
||||||
return SharedLock(this, std::shared_lock(mutex, tag));
|
|
||||||
}
|
|
||||||
|
|
||||||
SharedLock shared_lock(const std::defer_lock_t& tag) {
|
|
||||||
return SharedLock(this, std::shared_lock(mutex, tag));
|
|
||||||
}
|
|
||||||
|
|
||||||
ExclusiveLock exclusive_lock() {
|
|
||||||
return ExclusiveLock(this, std::unique_lock(mutex));
|
|
||||||
}
|
|
||||||
|
|
||||||
ExclusiveLock exclusive_lock(const std::try_to_lock_t& tag) {
|
|
||||||
return ExclusiveLock(this, std::unique_lock(mutex, tag));
|
|
||||||
}
|
|
||||||
|
|
||||||
ExclusiveLock exclusive_lock(const std::adopt_lock_t& tag) {
|
|
||||||
return ExclusiveLock(this, std::unique_lock(mutex, tag));
|
|
||||||
}
|
|
||||||
|
|
||||||
ExclusiveLock exclusive_lock(const std::defer_lock_t& tag) {
|
|
||||||
return ExclusiveLock(this, std::unique_lock(mutex, tag));
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
T value;
|
|
||||||
mutable std::shared_mutex mutex;
|
|
||||||
};
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
class SpinlockObject {
|
|
||||||
public:
|
|
||||||
template<typename... Args>
|
|
||||||
explicit SpinlockObject(Args&&... args)
|
|
||||||
: Value(std::forward<Args>(args)...) {}
|
|
||||||
|
|
||||||
class Lock {
|
|
||||||
public:
|
|
||||||
Lock(SpinlockObject* obj, std::atomic_flag& flag, bool locked = false)
|
|
||||||
: Obj(obj), Flag(&flag)
|
|
||||||
{
|
|
||||||
if(obj && !locked)
|
|
||||||
while(flag.test_and_set(std::memory_order_acquire));
|
|
||||||
}
|
|
||||||
|
|
||||||
~Lock() {
|
|
||||||
if(Obj)
|
|
||||||
Flag->clear(std::memory_order_release);
|
|
||||||
}
|
|
||||||
|
|
||||||
Lock(const Lock&) = delete;
|
|
||||||
Lock(Lock&& obj)
|
|
||||||
: Obj(obj.Obj), Flag(obj.Flag)
|
|
||||||
{
|
|
||||||
obj.Obj = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
Lock& operator=(const Lock&) = delete;
|
|
||||||
Lock& operator=(Lock&& obj) {
|
|
||||||
if(this == &obj)
|
|
||||||
return *this;
|
|
||||||
|
|
||||||
if(Obj)
|
|
||||||
unlock();
|
|
||||||
|
|
||||||
Obj = obj.Obj;
|
|
||||||
obj.Obj = nullptr;
|
|
||||||
Flag = obj.Flag;
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
T& get() const { assert(Obj); return Obj->Value; }
|
|
||||||
T* operator->() const { assert(Obj); return &Obj->Value; }
|
|
||||||
T& operator*() const { assert(Obj); return Obj->Value; }
|
|
||||||
|
|
||||||
operator bool() const {
|
|
||||||
return Obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
void unlock() { assert(Obj); Obj = nullptr; Flag->clear(std::memory_order_release);}
|
|
||||||
|
|
||||||
private:
|
|
||||||
SpinlockObject *Obj;
|
|
||||||
std::atomic_flag *Flag;
|
|
||||||
};
|
|
||||||
|
|
||||||
Lock lock() {
|
|
||||||
return Lock(this, Flag);
|
|
||||||
}
|
|
||||||
|
|
||||||
Lock tryLock() {
|
|
||||||
if(Flag.test_and_set(std::memory_order_acquire))
|
|
||||||
return Lock(nullptr, Flag);
|
|
||||||
else
|
|
||||||
return Lock(this, Flag, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
const T& get_read() { return Value; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
T Value;
|
|
||||||
std::atomic_flag Flag = ATOMIC_FLAG_INIT;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Spinlock {
|
|
||||||
public:
|
|
||||||
Spinlock() {}
|
|
||||||
|
|
||||||
class Lock {
|
|
||||||
public:
|
|
||||||
Lock(Spinlock* obj, std::atomic_flag& flag, bool locked = false)
|
|
||||||
: Obj(obj), Flag(&flag)
|
|
||||||
{
|
|
||||||
if(obj && !locked)
|
|
||||||
while(flag.test_and_set(std::memory_order_acquire));
|
|
||||||
}
|
|
||||||
|
|
||||||
~Lock() {
|
|
||||||
if(Obj)
|
|
||||||
Flag->clear(std::memory_order_release);
|
|
||||||
}
|
|
||||||
|
|
||||||
Lock(const Lock&) = delete;
|
|
||||||
Lock(Lock&& obj)
|
|
||||||
: Obj(obj.Obj), Flag(obj.Flag)
|
|
||||||
{
|
|
||||||
obj.Obj = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
Lock& operator=(const Lock&) = delete;
|
|
||||||
Lock& operator=(Lock&& obj) {
|
|
||||||
if(this == &obj)
|
|
||||||
return *this;
|
|
||||||
|
|
||||||
if(Obj)
|
|
||||||
unlock();
|
|
||||||
|
|
||||||
Obj = obj.Obj;
|
|
||||||
obj.Obj = nullptr;
|
|
||||||
Flag = obj.Flag;
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
void unlock() { assert(Obj); Obj = nullptr; Flag->clear(std::memory_order_release);}
|
|
||||||
|
|
||||||
private:
|
|
||||||
Spinlock *Obj;
|
|
||||||
std::atomic_flag *Flag;
|
|
||||||
};
|
|
||||||
|
|
||||||
Lock lock() {
|
|
||||||
return Lock(this, Flag);
|
|
||||||
}
|
|
||||||
|
|
||||||
Lock tryLock() {
|
|
||||||
if(Flag.test_and_set(std::memory_order_acquire))
|
|
||||||
return Lock(nullptr, Flag);
|
|
||||||
else
|
|
||||||
return Lock(this, Flag, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::atomic_flag Flag = ATOMIC_FLAG_INIT;
|
|
||||||
};
|
|
||||||
|
|
||||||
#if __BYTE_ORDER == __LITTLE_ENDIAN
|
#if __BYTE_ORDER == __LITTLE_ENDIAN
|
||||||
template <typename T>
|
template <typename T>
|
||||||
static inline T swapEndian(const T &u) { return u; }
|
static inline T swapEndian(const T &u) { return u; }
|
||||||
|
|||||||
@@ -2,28 +2,22 @@
|
|||||||
import sys
|
import sys
|
||||||
import re
|
import re
|
||||||
|
|
||||||
if len(sys.argv) < 3:
|
output_file = "resources.cpp"
|
||||||
print("Usage: assets.py <output_cpp> <file1> [file2 ...]")
|
with open(output_file, "w") as f:
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
output_cpp = sys.argv[1]
|
|
||||||
symbols = sys.argv[2:]
|
|
||||||
|
|
||||||
with open(output_cpp, "w") as f:
|
|
||||||
f.write("#include <unordered_map>\n#include <string>\n#include <tuple>\n\nextern \"C\" {\n")
|
f.write("#include <unordered_map>\n#include <string>\n#include <tuple>\n\nextern \"C\" {\n")
|
||||||
|
|
||||||
for symbol in symbols:
|
for symbol in sys.argv[1:]:
|
||||||
var_name = "_binary_" + re.sub('[^a-zA-Z0-9]', '_', symbol)
|
var_name = "_binary_" + re.sub('[^a-zA-Z0-9]', '_', symbol)
|
||||||
f.write(f"\textern const char {var_name}_start[];\n\textern const char {var_name}_end[];\n")
|
f.write(f"\textern const char {var_name}_start[];\n\textern const char {var_name}_end[];\n")
|
||||||
|
|
||||||
f.write("}\n\n")
|
f.write("}")
|
||||||
|
|
||||||
f.write("std::unordered_map<std::string, std::tuple<const char*, const char*>> _binary_assets_symbols = {\n")
|
f.write("\n\nstd::unordered_map<std::string, std::tuple<const char*, const char*>> _binary_assets_symbols = {\n")
|
||||||
|
|
||||||
for symbol in symbols:
|
for symbol in sys.argv[1:]:
|
||||||
var_name = "_binary_" + re.sub('[^a-zA-Z0-9]', '_', symbol)
|
var_name = "_binary_" + re.sub('[^a-zA-Z0-9]', '_', symbol)
|
||||||
f.write(f"\t{{\"{symbol}\", {{(const char*) &{var_name}_start, (const char*) &{var_name}_end}}}},\n")
|
f.write(f"\t{{\"{symbol}\", {{(const char*) &{var_name}_start, (const char*) &{var_name}_end}}}},\n")
|
||||||
|
|
||||||
f.write("};\n")
|
f.write("};\n")
|
||||||
|
|
||||||
print(f"File {output_cpp} is generated.")
|
print(f"File {output_file} is generated.")
|
||||||
|
|||||||
19
Src/main.cpp
@@ -1,30 +1,19 @@
|
|||||||
#include "Common/Abstract.hpp"
|
|
||||||
#include <filesystem>
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <boost/asio.hpp>
|
#include <boost/asio.hpp>
|
||||||
#include <Client/Vulkan/Vulkan.hpp>
|
#include <Client/Vulkan/Vulkan.hpp>
|
||||||
|
#include <Common/Async.hpp>
|
||||||
#include <Client/ResourceCache.hpp>
|
#include <Common/async_mutex.hpp>
|
||||||
|
|
||||||
namespace LV {
|
namespace LV {
|
||||||
|
|
||||||
/*
|
|
||||||
База ресурсов на стороне клиента
|
|
||||||
Протокол получения ресурсов, удаления, потом -> регулировки размера
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
using namespace TOS;
|
using namespace TOS;
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
|
|
||||||
// LuaVox
|
// LuaVox
|
||||||
asio::io_context ioc;
|
asio::io_context ioc;
|
||||||
Logger LOG = "main";
|
|
||||||
|
|
||||||
LV::Client::VK::Vulkan vkInst(ioc);
|
|
||||||
|
|
||||||
|
|
||||||
ioc.run();
|
ioc.run();
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
@@ -39,6 +28,4 @@ int main() {
|
|||||||
|
|
||||||
std::cout << "Hello world!" << std::endl;
|
std::cout << "Hello world!" << std::endl;
|
||||||
return LV::main();
|
return LV::main();
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|||||||
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 */
|
|
||||||
@@ -5,26 +5,22 @@ layout (triangle_strip, max_vertices = 3) out;
|
|||||||
|
|
||||||
layout(location = 0) in GeometryObj {
|
layout(location = 0) in GeometryObj {
|
||||||
vec3 GeoPos; // Реальная позиция в мире
|
vec3 GeoPos; // Реальная позиция в мире
|
||||||
flat uint Texture; // Текстура
|
uint Texture; // Текстура
|
||||||
vec2 UV;
|
vec2 UV;
|
||||||
} Geometry[];
|
} Geometry[];
|
||||||
|
|
||||||
layout(location = 0) out FragmentObj {
|
layout(location = 0) out FragmentObj {
|
||||||
vec3 GeoPos; // Реальная позиция в мире
|
vec3 GeoPos; // Реальная позиция в мире
|
||||||
vec3 Normal;
|
uint Texture; // Текстура
|
||||||
flat uint Texture; // Текстура
|
|
||||||
vec2 UV;
|
vec2 UV;
|
||||||
} Fragment;
|
} Fragment;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
vec3 normal = normalize(cross(Geometry[1].GeoPos-Geometry[0].GeoPos, Geometry[2].GeoPos-Geometry[0].GeoPos));
|
|
||||||
|
|
||||||
for(int iter = 0; iter < 3; iter++) {
|
for(int iter = 0; iter < 3; iter++) {
|
||||||
gl_Position = gl_in[iter].gl_Position;
|
gl_Position = gl_in[iter].gl_Position;
|
||||||
Fragment.GeoPos = Geometry[iter].GeoPos;
|
Fragment.GeoPos = Geometry[iter].GeoPos;
|
||||||
Fragment.Texture = Geometry[iter].Texture;
|
Fragment.Texture = Geometry[iter].Texture;
|
||||||
Fragment.UV = Geometry[iter].UV;
|
Fragment.UV = Geometry[iter].UV;
|
||||||
Fragment.Normal = normal;
|
|
||||||
EmitVertex();
|
EmitVertex();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
#version 450
|
#version 450
|
||||||
|
|
||||||
layout(location = 0) in uvec3 Vertex;
|
layout(location = 0) in uvec4 Vertex;
|
||||||
|
|
||||||
layout(location = 0) out GeometryObj {
|
layout(location = 0) out GeometryObj {
|
||||||
vec3 GeoPos; // Реальная позиция в мире
|
vec3 GeoPos; // Реальная позиция в мире
|
||||||
flat uint Texture; // Текстура
|
uint Texture; // Текстура
|
||||||
vec2 UV;
|
vec2 UV;
|
||||||
} Geometry;
|
} Geometry;
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ layout(push_constant) uniform UniformBufferObject {
|
|||||||
|
|
||||||
// struct NodeVertexStatic {
|
// struct NodeVertexStatic {
|
||||||
// uint32_t
|
// uint32_t
|
||||||
// FX : 9, FY : 9, FZ : 9, // Позиция 15 -120 ~ 240 360 15 / 16
|
// FX : 9, FY : 9, FZ : 9, // Позиция -112 ~ 369 / 16
|
||||||
// N1 : 4, // Не занято
|
// N1 : 4, // Не занято
|
||||||
// LS : 1, // Масштаб карты освещения (1м/16 или 1м)
|
// LS : 1, // Масштаб карты освещения (1м/16 или 1м)
|
||||||
// Tex : 18, // Текстура
|
// Tex : 18, // Текстура
|
||||||
@@ -27,9 +27,9 @@ layout(push_constant) uniform UniformBufferObject {
|
|||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
vec4 baseVec = ubo.model*vec4(
|
vec4 baseVec = ubo.model*vec4(
|
||||||
float(Vertex.x & 0x1ff) / 16.f - 135/16.f,
|
float(Vertex.x & 0x1ff) / 16.f - 7,
|
||||||
float((Vertex.x >> 9) & 0x1ff) / 16.f - 135/16.f,
|
float((Vertex.x >> 9) & 0x1ff) / 16.f - 7,
|
||||||
float((Vertex.x >> 18) & 0x1ff) / 16.f - 135/16.f,
|
float((Vertex.x >> 18) & 0x1ff) / 16.f - 7,
|
||||||
1
|
1
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,7 @@
|
|||||||
|
|
||||||
layout(location = 0) in FragmentObj {
|
layout(location = 0) in FragmentObj {
|
||||||
vec3 GeoPos; // Реальная позиция в мире
|
vec3 GeoPos; // Реальная позиция в мире
|
||||||
vec3 Normal;
|
uint Texture; // Текстура
|
||||||
flat uint Texture; // Текстура
|
|
||||||
vec2 UV;
|
vec2 UV;
|
||||||
} Fragment;
|
} Fragment;
|
||||||
|
|
||||||
@@ -70,21 +69,6 @@ vec4 atlasColor(uint texId, vec2 uv)
|
|||||||
return color;
|
return color;
|
||||||
}
|
}
|
||||||
|
|
||||||
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]);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
Frame = atlasColor(Fragment.Texture, Fragment.UV);
|
Frame = atlasColor(Fragment.Texture, Fragment.UV);
|
||||||
Frame.xyz *= max(0.2f, dot(Fragment.Normal, normalize(vec3(0.5, 1, 0.8))));
|
}
|
||||||
// Frame = vec4(blendOverlay(vec3(Frame), vec3(Fragment.GeoPos/64.f)), Frame.w);
|
|
||||||
if(Frame.w == 0)
|
|
||||||
discard;
|
|
||||||
}
|
|
||||||
@@ -2,8 +2,7 @@
|
|||||||
|
|
||||||
layout(location = 0) in FragmentObj {
|
layout(location = 0) in FragmentObj {
|
||||||
vec3 GeoPos; // Реальная позиция в мире
|
vec3 GeoPos; // Реальная позиция в мире
|
||||||
vec3 Normal;
|
uint Texture; // Текстура
|
||||||
flat uint Texture; // Текстура
|
|
||||||
vec2 UV;
|
vec2 UV;
|
||||||
} Fragment;
|
} Fragment;
|
||||||
|
|
||||||
|
|||||||
|
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 |