Compare commits

...

104 Commits

Author SHA1 Message Date
6dd1f93221 Отправка идентификаторов при подключении клиентов 2026-01-04 20:47:48 +06:00
01ea7eee74 ocdex-5.2: Отправка чанков на клиент 2026-01-04 20:23:44 +06:00
dbebf50552 codex-5.2: логгирование передачи ресурсов 2026-01-04 20:18:02 +06:00
51cc68e1b2 codex-5.2: Обработчик ресурсов на стороне клиента 2026-01-04 20:11:14 +06:00
7c54f429ba Изменение кеша ресурсов на клиенте 2026-01-04 18:48:29 +06:00
abe7b987c4 Восстановление архитектуры на стороне сервера 2026-01-04 15:49:40 +06:00
83c4628995 Переработка интерфейса предоставления данных клиентам 2026-01-04 13:36:52 +06:00
2759073bb3 Подстройки кодекса 2026-01-03 22:17:39 +06:00
2540439bf0 Переработка загрузчика ресурсов игры (assets) 2026-01-03 22:16:52 +06:00
d9e40b4e80 На стороне сервера теперь используется AssetsPreloader.hpp 2026-01-03 13:11:20 +06:00
776e9bfaca Доработка пайплайн машины (требуется пересмотр технологии) 2026-01-03 00:41:09 +06:00
f56b46f669 codex-5.2: синхронизация ресурсов модов, частичная перезагрузка модов 2026-01-01 15:12:27 +06:00
4aa7c6f41a codex-5.2: ресурсы 2026-01-01 02:13:01 +06:00
d47a5cc090 codex5.2: исправил ошибки в работе с ресурсами на стороне клиента 2025-12-31 16:49:01 +06:00
347844251e Правка для работы после предыдущего коммита 2025-12-31 16:39:44 +06:00
b1bc2c8e74 Фиг знает какие были изменения 2025-12-31 15:02:09 +06:00
9cfae9c807 * 2025-09-14 10:07:23 +06:00
55700c6939 * 2025-09-13 17:15:53 +06:00
f55a598199 * 2025-09-11 09:37:47 +06:00
4bdbbdbe2f * 2025-09-10 21:09:37 +06:00
e74e623c0b * 2025-09-10 09:51:17 +06:00
d646061c32 Индексный буфер 2025-09-06 23:11:47 +06:00
6034bc94fe * 2025-09-06 20:39:46 +06:00
a1b84053d4 Ресурсы 2025-09-02 16:41:55 +06:00
4eef3ca211 Отладка получения ресурсов 2025-09-02 13:03:39 +06:00
05570b0844 * 2025-09-01 18:43:23 +06:00
95fc3c7e74 Nodestate 2025-09-01 13:39:38 +06:00
2dd3ea60d7 Состояния нод на стороне сервера 2025-08-31 20:22:59 +06:00
f745f58a31 Новая система построения вершин чанков 2025-08-31 01:26:08 +06:00
0fda466d3f * 2025-08-30 21:52:19 +06:00
eb9701e762 * 2025-08-30 14:25:20 +06:00
1bf897b3d1 * 2025-08-30 03:12:18 +06:00
5b02fec75e * 2025-08-29 17:21:03 +06:00
28b7e8fe91 * 2025-08-28 17:16:21 +06:00
8bf84f9138 Исправлен баг с пропаданием территории на клиенте 2025-08-28 15:51:00 +06:00
3b18037bb5 * 2025-08-27 23:00:50 +06:00
bd1dec04f2 Поправка порядка рендера чанков 2025-08-27 22:54:47 +06:00
388b59e9bf * 2025-08-27 17:22:51 +06:00
d60405cd18 Продолжение передачи ресурсов на клиент 2025-08-27 14:44:56 +06:00
cfbbfa286a Передача ресурсов клиенту 2025-08-27 00:26:11 +06:00
57d6e816fc * 2025-08-26 17:34:05 +06:00
bdb6395351 * 2025-08-26 17:33:58 +06:00
44925edc9a * 2025-08-25 21:56:23 +06:00
36346c9798 * 2025-08-25 17:13:47 +06:00
c06683cd57 * 2025-08-25 10:58:21 +06:00
c624c1ad0b * 2025-08-22 17:49:49 +06:00
d02f747ca0 * 2025-08-21 17:45:45 +06:00
825080ca21 * 2025-08-20 23:45:34 +06:00
d0d925de05 * 2025-08-20 17:53:35 +06:00
e8e3667fa9 * 2025-08-20 12:00:25 +06:00
2cf37f4e0a * 2025-08-19 17:12:45 +06:00
2f62ffd59c * 2025-08-18 11:44:57 +06:00
a59905f5b1 * 2025-08-16 22:18:05 +06:00
4b05246e29 * 2025-08-16 18:35:58 +06:00
c9b9d4e296 * 2025-08-16 16:06:05 +06:00
e21394c4f1 Эта версия компилится 2025-08-15 17:35:13 +06:00
4d6355298c * 2025-08-15 11:31:48 +06:00
bf458a28c1 * 2025-08-14 17:52:24 +06:00
1273516f62 * 2025-08-13 21:26:08 +06:00
4c7e2c8e72 * 2025-08-13 11:49:56 +06:00
c1b16949fa * 2025-08-12 17:41:36 +06:00
e54f04f62e * 2025-08-12 01:58:08 +06:00
9556e1affd * 2025-08-11 17:30:19 +06:00
efcf8daf7e * 2025-08-11 00:17:32 +06:00
e5c6eb874a * 2025-08-08 18:08:30 +06:00
9140497b3b Изучаю парсинг математических выражений 2025-08-08 15:58:41 +06:00
e3b7a636bf * 2025-08-06 18:03:33 +06:00
93a202e736 * 2025-08-06 14:57:59 +06:00
e5cfdd3787 * 2025-08-05 18:07:41 +06:00
c1df4a21f3 * 2025-08-04 17:42:12 +06:00
31ee024d2f Пишу парсеры для конифгов мира и модов 2025-08-04 15:46:15 +06:00
24c133b2f7 Парсер файлов модов 2025-08-02 18:28:09 +06:00
a8b6647fac modJson 2025-07-29 11:26:06 +06:00
fbe48124a6 Кооректировка подключения Lua 2025-07-28 17:26:03 +06:00
a53c2be381 Подключен LuaJit2.1 2025-07-28 12:30:11 +06:00
3e3b66a415 Загрузчик двоичных ресурсов на сервере (Alpha) 2025-07-27 17:58:44 +06:00
9e0c6c5220 Синхронизация графической очереди 2025-07-26 01:55:11 +06:00
824b7f2f80 Добавлен Frustum Culling, требующий отладки 2025-07-25 19:22:15 +06:00
2933465f84 Начало отделение генерации мешей чанков в отдельном потоке 2025-07-25 14:59:45 +06:00
67476f5908 Сначала надо сделать расчёт мешей в отдельном потоке 2025-07-24 18:10:55 +06:00
bd31b57d7d На этом коммите всё компилируется, дальше разработка профилей нод 2025-07-24 17:44:05 +06:00
cfec93957d * 2025-07-14 09:50:26 +06:00
9c64b893cf * 2025-07-12 03:27:20 +06:00
dbe2e2c33c * 2025-07-11 16:17:16 +06:00
8f3af0c687 * 2025-07-10 22:19:13 +06:00
62959a36b2 * 2025-07-10 17:06:53 +06:00
c0989a53ea * 2025-07-09 23:02:19 +06:00
2eb48d12a8 Буферы вершин 2025-07-09 18:30:54 +06:00
c4532ac903 * 2025-07-09 07:51:08 +06:00
4d23b69ecd * 2025-07-08 16:19:39 +06:00
b85c242b53 * 2025-07-08 10:01:50 +06:00
8fe8057d9c * 2025-07-07 18:42:52 +06:00
Your Name
9bc18b5396 * 2025-07-07 12:46:33 +06:00
876d0e053e Переработка доставки вокселей и нод в чанках 2025-07-06 11:43:01 +06:00
cd3e615ad3 * 2025-07-03 19:05:34 +06:00
565f2318fb * 2025-07-01 17:47:35 +06:00
97ed969a6c stepDatabaseSync stepGeneratorAndLuaAsync 2025-06-30 23:30:34 +06:00
efc6dc0692 * 2025-06-30 18:03:56 +06:00
6fd0b3e9d5 Переделка ресурсов и этапов обновения 2025-06-30 08:19:51 +06:00
cfc80660dd Сейчас оно компилируется, пересмотр расчёта зон наблюдения 2025-06-29 11:55:46 +06:00
e083510525 Реформа ресурсов 2025-06-27 18:44:53 +06:00
4743583831 ClientCache 2025-06-27 12:20:46 +06:00
ecc77544b2 ResourceCache 2025-06-26 15:45:05 +06:00
9470d14151 Cmake и ClientCache 2025-06-26 11:20:31 +06:00
110 changed files with 23230 additions and 3652 deletions

3
.gitignore vendored
View File

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

3
.gitmodules vendored Normal file
View File

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

View File

@@ -1,34 +1,62 @@
cmake_minimum_required(VERSION 3.13) cmake_minimum_required(VERSION 3.13)
option(BUILD_CLIENT "Build the client" TRUE) option(BUILD_CLIENT "Build the client" ON)
option(USE_LIBURING "Build with liburing support" ON)
set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD 23)
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") # set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer")
# 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") # set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined -fno-sanitize-recover=all -fsanitize=float-divide-by-zero -fsanitize=float-cast-overflow -fno-sanitize=null -fno-sanitize=alignment -fsanitize=address -fno-omit-frame-pointer")
# set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=undefined -fno-sanitize-recover=all -fsanitize=float-divide-by-zero -fsanitize=float-cast-overflow -fno-sanitize=null -fno-sanitize=alignment -fsanitize=address")
project (LuaVox VERSION 0.0 DESCRIPTION "LuaVox Description") # set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address,undefined -fno-omit-frame-pointer -fno-sanitize-recover=all")
add_executable(${PROJECT_NAME}) # set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address,undefined")
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_20)
file(GLOB_RECURSE SOURCES RELATIVE ${PROJECT_SOURCE_DIR} "Src/*.cpp") project(LuaVox VERSION 0.0 DESCRIPTION "LuaVox Description")
target_sources(${PROJECT_NAME} PRIVATE ${SOURCES})
target_include_directories(${PROJECT_NAME} PUBLIC "${PROJECT_SOURCE_DIR}/Src") add_library(luavox_common INTERFACE)
target_compile_features(luavox_common INTERFACE cxx_std_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)
@@ -44,19 +72,55 @@ 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
URL https://github.com/boostorg/boost/releases/download/boost-1.87.0/boost-1.87.0-cmake.7z GIT_REPOSITORY https://github.com/boostorg/boost.git
USES_TERMINAL_DOWNLOAD TRUE GIT_TAG boost-1.87.0
DOWNLOAD_NO_EXTRACT FALSE GIT_PROGRESS true
USES_TERMINAL_DOWNLOAD true
) )
FetchContent_MakeAvailable(Boost) FetchContent_MakeAvailable(Boost)
target_link_libraries(${PROJECT_NAME} PUBLIC Boost::asio Boost::thread Boost::json) target_link_libraries(luavox_common INTERFACE Boost::asio Boost::thread Boost::json Boost::iostreams Boost::interprocess Boost::timer Boost::circular_buffer Boost::lockfree Boost::stacktrace Boost::uuid Boost::serialization Boost::nowide)
# 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
@@ -64,70 +128,112 @@ FetchContent_Declare(
GIT_TAG 1.0.1 GIT_TAG 1.0.1
) )
FetchContent_MakeAvailable(glm) FetchContent_MakeAvailable(glm)
target_link_libraries(${PROJECT_NAME} PUBLIC glm) target_link_libraries(luavox_common INTERFACE glm)
find_package(ICU REQUIRED COMPONENTS i18n uc) find_package(ICU REQUIRED COMPONENTS i18n uc)
target_include_directories(${PROJECT_NAME} PUBLIC ${ICU_INCLUDE_DIR}) target_include_directories(luavox_common INTERFACE ${ICU_INCLUDE_DIR})
target_link_libraries(${PROJECT_NAME} PUBLIC ${ICU_LIBRARIES}) target_link_libraries(luavox_common INTERFACE ${ICU_LIBRARIES})
find_package(OpenSSL REQUIRED) find_package(OpenSSL REQUIRED)
target_include_directories(${PROJECT_NAME} PUBLIC ${OPENSSL_INCLUDE_DIR}) target_include_directories(luavox_common INTERFACE ${OPENSSL_INCLUDE_DIR})
target_link_libraries(${PROJECT_NAME} PUBLIC ${OPENSSL_LIBRARIES}) target_link_libraries(luavox_common INTERFACE ${OPENSSL_LIBRARIES})
# JPEG # JPEG
find_package(JPEG REQUIRED) find_package(JPEG REQUIRED)
target_include_directories(${PROJECT_NAME} PUBLIC ${JPEG_INCLUDE_DIRS}) target_include_directories(luavox_common INTERFACE ${JPEG_INCLUDE_DIRS})
target_link_libraries(${PROJECT_NAME} PUBLIC JPEG::JPEG) target_link_libraries(luavox_common INTERFACE JPEG::JPEG)
# PNG # PNG
find_package(PNG REQUIRED) find_package(PNG REQUIRED)
target_include_directories(${PROJECT_NAME} PUBLIC ${PNG_INCLUDE_DIRS}) target_include_directories(luavox_common INTERFACE ${PNG_INCLUDE_DIRS})
target_link_libraries(${PROJECT_NAME} PUBLIC PNG::PNG) target_link_libraries(luavox_common INTERFACE PNG::PNG)
# PNG++ # PNG++
target_include_directories(${PROJECT_NAME} PUBLIC "${PROJECT_SOURCE_DIR}/Libs/png++") target_include_directories(luavox_common INTERFACE "${PROJECT_SOURCE_DIR}/Libs/png++")
# GLFW3 # sqlite3
if(BUILD_CLIENT) FetchContent_Declare(sqlite3 GIT_REPOSITORY https://github.com/sjinks/sqlite3-cmake GIT_TAG v3.49.1)
find_package(glfw3 3) FetchContent_MakeAvailable(sqlite3)
target_link_libraries(luavox_common INTERFACE SQLite::SQLite3)
if(TARGET glfw) FetchContent_Declare(
target_include_directories(${PROJECT_NAME} PUBLIC ${GLFW_INCLUDE_DIRS}) RectangleBinPack
else() GIT_REPOSITORY https://github.com/juj/RectangleBinPack.git
FetchContent_Declare( GIT_TAG 83e7e1132d93777e3732dfaae26b0f3703be2036
glfw )
GIT_REPOSITORY https://github.com/glfw/glfw.git FetchContent_MakeAvailable(RectangleBinPack)
GIT_TAG 3.4 target_link_libraries(luavox_common INTERFACE RectangleBinPack)
)
FetchContent_MakeAvailable(glfw)
endif()
target_link_libraries(${PROJECT_NAME} PUBLIC glfw)
endif()
# FreeType
find_package(Freetype REQUIRED)
# FetchContent_Declare(
# freetype
# GIT_REPOSITORY https://github.com/freetype/freetype.git
# GIT_TAG freetype
# )
# FetchContent_MakeAvailable(freetype)
target_include_directories(${PROJECT_NAME} PUBLIC ${freetype_INCLUDE_DIRS})
target_link_libraries(${PROJECT_NAME} PUBLIC Freetype::Freetype)
# ImGui
file(GLOB SOURCES "${PROJECT_SOURCE_DIR}/Libs/imgui/*.cpp")
target_sources(${PROJECT_NAME} PRIVATE ${SOURCES} "${PROJECT_SOURCE_DIR}/Libs/imgui/backends/imgui_impl_glfw.cpp" "${PROJECT_SOURCE_DIR}/Libs/imgui/backends/imgui_impl_vulkan.cpp")
target_include_directories(${PROJECT_NAME} PUBLIC "${PROJECT_SOURCE_DIR}/Libs/imgui/")
# Static Assets # Static Assets
file(GLOB_RECURSE ASSETS RELATIVE "${PROJECT_SOURCE_DIR}/assets" "assets/*.*") find_package(Python3 REQUIRED)
add_custom_command(OUTPUT assets.o resources.cpp INPUT ${ASSETS} set(ASSETS_DIR "${PROJECT_SOURCE_DIR}/assets")
COMMAND cd ${CMAKE_CURRENT_BINARY_DIR} && ${CMAKE_CURRENT_SOURCE_DIR}/Src/assets.py ${ASSETS} file(GLOB_RECURSE ASSETS_LIST RELATIVE "${ASSETS_DIR}" "${ASSETS_DIR}/*.*")
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(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)
if(BUILD_CLIENT)
add_executable(luavox_client)
# Common
target_link_libraries(luavox_client PUBLIC luavox_common)
# Исходники
file(GLOB_RECURSE SOURCES RELATIVE ${PROJECT_SOURCE_DIR} "Src/*.cpp")
target_sources(luavox_client PRIVATE ${SOURCES})
target_include_directories(luavox_client PUBLIC "${PROJECT_SOURCE_DIR}/Src")
# GLFW3
FetchContent_Declare(
glfw
GIT_REPOSITORY https://github.com/glfw/glfw.git
GIT_TAG 3.4
)
FetchContent_MakeAvailable(glfw)
target_link_libraries(luavox_client PUBLIC glfw)
# FreeType
find_package(Freetype REQUIRED)
# FetchContent_Declare(
# freetype
# GIT_REPOSITORY https://github.com/freetype/freetype.git
# GIT_TAG freetype
# )
# FetchContent_MakeAvailable(freetype)
target_include_directories(luavox_client PUBLIC ${freetype_INCLUDE_DIRS})
target_link_libraries(luavox_client PUBLIC Freetype::Freetype)
# ImGui
file(GLOB SOURCES "${PROJECT_SOURCE_DIR}/Libs/imgui/*.cpp")
target_sources(luavox_client PRIVATE ${SOURCES} "${PROJECT_SOURCE_DIR}/Libs/imgui/backends/imgui_impl_glfw.cpp" "${PROJECT_SOURCE_DIR}/Libs/imgui/backends/imgui_impl_vulkan.cpp")
target_include_directories(luavox_client PUBLIC "${PROJECT_SOURCE_DIR}/Libs/imgui/")
endif()
SET_SOURCE_FILES_PROPERTIES(assets.o PROPERTIES EXTERNAL_OBJECT true GENERATED true)
add_library(assets STATIC resources.cpp assets.o)
SET_TARGET_PROPERTIES(assets PROPERTIES LINKER_LANGUAGE C)
target_link_libraries(${PROJECT_NAME} PUBLIC assets uring)

1
Libs/boost Submodule

Submodule Libs/boost added at c89e626766

View File

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

View File

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

View File

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

1021
Src/Client/AssetsManager.cpp Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,152 @@
#pragma once
#include <array>
#include <cstddef>
#include <cstdint>
#include <filesystem>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <unordered_map>
#include <utility>
#include <vector>
#include "Client/AssetsCacheManager.hpp"
#include "Common/Abstract.hpp"
#include "TOSLib.hpp"
namespace LV::Client {
namespace fs = std::filesystem;
class AssetsManager {
public:
using Ptr = std::shared_ptr<AssetsManager>;
using AssetType = EnumAssets;
using AssetId = ResourceId;
struct ResourceKey {
Hash_t Hash{};
AssetType Type{};
std::string Domain;
std::string Key;
AssetId Id = 0;
};
struct BindInfo {
AssetType Type{};
AssetId LocalId = 0;
std::string Domain;
std::string Key;
Hash_t Hash{};
std::vector<uint8_t> Header;
};
struct BindResult {
AssetId LocalId = 0;
bool Changed = false;
bool NewBinding = false;
std::optional<AssetId> ReboundFrom;
};
struct PackRegister {
std::vector<fs::path> Packs;
};
struct PackResource {
AssetType Type{};
AssetId LocalId = 0;
std::string Domain;
std::string Key;
Resource Res;
Hash_t Hash{};
std::u8string Header;
};
struct PackReloadResult {
std::array<std::vector<AssetId>, static_cast<size_t>(AssetType::MAX_ENUM)> ChangeOrAdd;
std::array<std::vector<AssetId>, static_cast<size_t>(AssetType::MAX_ENUM)> Lost;
};
struct ParsedHeader {
AssetType Type{};
std::vector<AssetId> ModelDeps;
std::vector<AssetId> TextureDeps;
std::vector<std::vector<uint8_t>> TexturePipelines;
};
static Ptr Create(asio::io_context& ioc, const fs::path& cachePath,
size_t maxCacheDirectorySize = 8 * 1024 * 1024 * 1024ULL,
size_t maxLifeTime = 7 * 24 * 60 * 60) {
return Ptr(new AssetsManager(ioc, cachePath, maxCacheDirectorySize, maxLifeTime));
}
PackReloadResult reloadPacks(const PackRegister& reg);
BindResult bindServerResource(AssetType type, AssetId serverId, std::string domain, std::string key,
const Hash_t& hash, std::vector<uint8_t> header);
std::optional<AssetId> unbindServerResource(AssetType type, AssetId serverId);
void clearServerBindings();
const BindInfo* getBind(AssetType type, AssetId localId) const;
std::vector<uint8_t> rebindHeader(AssetType type, const std::vector<uint8_t>& header, bool serverIds = true);
static std::optional<ParsedHeader> parseHeader(AssetType type, const std::vector<uint8_t>& header);
void pushResources(std::vector<Resource> resources) {
Cache->pushResources(std::move(resources));
}
void pushReads(std::vector<ResourceKey> reads);
std::vector<std::pair<ResourceKey, std::optional<Resource>>> pullReads();
AssetId getOrCreateLocalId(AssetType type, std::string_view domain, std::string_view key);
AssetId getOrCreateLocalFromServer(AssetType type, AssetId serverId);
std::optional<AssetId> getLocalIdFromServer(AssetType type, AssetId serverId) const;
private:
struct DomainKey {
std::string Domain;
std::string Key;
bool Known = false;
};
using IdTable = std::unordered_map<
std::string,
std::unordered_map<std::string, AssetId, detail::TSVHash, detail::TSVEq>,
detail::TSVHash,
detail::TSVEq>;
using PackTable = std::unordered_map<
std::string,
std::unordered_map<std::string, PackResource, detail::TSVHash, detail::TSVEq>,
detail::TSVHash,
detail::TSVEq>;
AssetsManager(asio::io_context& ioc, const fs::path& cachePath,
size_t maxCacheDirectorySize, size_t maxLifeTime);
AssetId allocateLocalId(AssetType type);
AssetId ensureServerLocalId(AssetType type, AssetId serverId);
AssetId resolveLocalIdMutable(AssetType type, AssetId localId);
AssetId resolveLocalId(AssetType type, AssetId localId) const;
void unionLocalIds(AssetType type, AssetId fromId, AssetId toId, std::optional<AssetId>* reboundFrom);
std::optional<PackResource> findPackResource(AssetType type, std::string_view domain, std::string_view key) const;
Logger LOG = "Client>AssetsManager";
AssetsCacheManager::Ptr Cache;
std::array<IdTable, static_cast<size_t>(AssetType::MAX_ENUM)> DKToLocal;
std::array<std::vector<DomainKey>, static_cast<size_t>(AssetType::MAX_ENUM)> LocalToDK;
std::array<std::vector<AssetId>, static_cast<size_t>(AssetType::MAX_ENUM)> LocalParent;
std::array<std::vector<AssetId>, static_cast<size_t>(AssetType::MAX_ENUM)> ServerToLocal;
std::array<std::vector<std::optional<BindInfo>>, static_cast<size_t>(AssetType::MAX_ENUM)> BindInfos;
std::array<PackTable, static_cast<size_t>(AssetType::MAX_ENUM)> PackResources;
std::array<AssetId, static_cast<size_t>(AssetType::MAX_ENUM)> NextLocalId{};
std::unordered_map<Hash_t, std::vector<ResourceKey>> PendingReadsByHash;
std::vector<std::pair<ResourceKey, std::optional<Resource>>> ReadyReads;
};
} // namespace LV::Client

119
Src/Client/FrustumCull.h Normal file
View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@@ -1,4 +1,5 @@
#include <boost/asio/io_context.hpp> #include <boost/asio/io_context.hpp>
#include <chrono>
#include <filesystem> #include <filesystem>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
@@ -7,6 +8,7 @@
#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>
@@ -32,12 +34,27 @@ 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, ""), LS(ioc, [&](tcp::socket sock) -> coro<> { co_await GS.pushSocketConnect(std::move(sock)); }, 7890) : GS(ioc, "worlds/test/"), LS(ioc, [&](tcp::socket sock) -> coro<> { co_await GS.pushSocketConnect(std::move(sock)); }, 7890)
{ {
} }
}; };
@@ -93,6 +110,16 @@ 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();
} }
@@ -132,12 +159,31 @@ void Vulkan::run()
NeedShutdown = false; NeedShutdown = false;
Graphics.ThisThread = std::this_thread::get_id(); Graphics.ThisThread = std::this_thread::get_id();
VkSemaphoreCreateInfo semaphoreCreateInfo = { VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, nullptr, 0 }; VkSemaphoreCreateInfo semaphoreCreateInfo = {
VkSemaphore SemaphoreImageAcquired, SemaphoreDrawComplete; VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
nullptr,
0
};
vkAssert(!vkCreateSemaphore(Graphics.Device, &semaphoreCreateInfo, NULL, &SemaphoreImageAcquired)); VkSemaphore SemaphoreImageAcquired[4], SemaphoreDrawComplete[4];
vkAssert(!vkCreateSemaphore(Graphics.Device, &semaphoreCreateInfo, NULL, &SemaphoreDrawComplete)); int semNext = 0;
for(int iter = 0; iter < 4; iter++) {
vkAssert(!vkCreateSemaphore(Graphics.Device, &semaphoreCreateInfo, nullptr, &SemaphoreImageAcquired[iter]));
vkAssert(!vkCreateSemaphore(Graphics.Device, &semaphoreCreateInfo, nullptr, &SemaphoreDrawComplete[iter]));
}
VkFence drawEndFence = VK_NULL_HANDLE;
{
VkFenceCreateInfo info = {
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
.pNext = nullptr,
.flags = 0
};
vkCreateFence(Graphics.Device, &info, nullptr, &drawEndFence);
}
double prevTime = glfwGetTime(); double prevTime = glfwGetTime();
while(!NeedShutdown) while(!NeedShutdown)
@@ -146,6 +192,8 @@ void Vulkan::run()
prevTime += dTime; prevTime += dTime;
Screen.State = DrawState::Begin; Screen.State = DrawState::Begin;
/// TODO: Нужно синхронизировать с vkQueue
{ {
std::lock_guard lock(Screen.BeforeDrawMtx); std::lock_guard lock(Screen.BeforeDrawMtx);
while(!Screen.BeforeDraw.empty()) while(!Screen.BeforeDraw.empty())
@@ -155,12 +203,44 @@ void Vulkan::run()
} }
} }
if(Game.Выйти) {
Game.Выйти = false;
try {
if(Game.Session)
Game.Session->setRenderSession(nullptr);
if(Game.RSession)
Game.RSession = nullptr;
} catch(const std::exception &exc) {
LOG.error() << "Game.RSession->shutdown: " << exc.what();
}
try {
if(Game.Session)
Game.Session->shutdown(EnumDisconnect::ByInterface);
} catch(const std::exception &exc) {
LOG.error() << "Game.Session->shutdown: " << exc.what();
}
}
if(!NeedShutdown && glfwWindowShouldClose(Graphics.Window)) { if(!NeedShutdown && glfwWindowShouldClose(Graphics.Window)) {
NeedShutdown = true; NeedShutdown = true;
try {
if(Game.Session)
Game.Session->setRenderSession(nullptr);
if(Game.RSession)
Game.RSession = nullptr;
} catch(const std::exception &exc) {
LOG.error() << "Game.RSession->shutdown: " << exc.what();
}
try { try {
if(Game.Session) if(Game.Session)
Game.Session->shutdown(EnumDisconnect::ByInterface); Game.Session->shutdown(EnumDisconnect::ByInterface);
Game.Session = nullptr;
} catch(const std::exception &exc) { } catch(const std::exception &exc) {
LOG.error() << "Game.Session->shutdown: " << exc.what(); LOG.error() << "Game.Session->shutdown: " << exc.what();
} }
@@ -171,6 +251,8 @@ void Vulkan::run()
} catch(const std::exception &exc) { } catch(const std::exception &exc) {
LOG.error() << "Game.Server->GS.shutdown: " << exc.what(); LOG.error() << "Game.Server->GS.shutdown: " << exc.what();
} }
continue;
} }
if(Game.Session) { if(Game.Session) {
@@ -193,25 +275,26 @@ void Vulkan::run()
// if(CallBeforeDraw) // if(CallBeforeDraw)
// CallBeforeDraw(this); // CallBeforeDraw(this);
if(Game.RSession) {
Game.RSession->beforeDraw();
}
glfwPollEvents(); glfwPollEvents();
VkResult err; VkResult err;
err = vkAcquireNextImageKHR(Graphics.Device, Graphics.Swapchain, 1000000000ULL/20, SemaphoreImageAcquired, (VkFence) 0, &Graphics.DrawBufferCurrent); semNext = ++semNext % 4;
err = vkAcquireNextImageKHR(Graphics.Device, Graphics.Swapchain, 1000000000ULL/20, SemaphoreImageAcquired[semNext], (VkFence) 0, &Graphics.DrawBufferCurrent);
GlobalTime gTime = glfwGetTime(); GlobalTime gTime = glfwGetTime();
if (err == VK_ERROR_OUT_OF_DATE_KHR) if(err == VK_ERROR_OUT_OF_DATE_KHR)
{ {
if(Game.RSession)
Game.RSession->pushStage(EnumRenderStage::WorldUpdate);
freeSwapchains(); freeSwapchains();
buildSwapchains(); buildSwapchains();
continue; continue;
} else if (err == VK_SUBOPTIMAL_KHR) // } else if (err == VK_SUBOPTIMAL_KHR)
{ // {
LOGGER.debug() << "VK_SUBOPTIMAL_KHR Pre"; // LOGGER.debug() << "VK_SUBOPTIMAL_KHR Pre";
} else if(err == VK_SUCCESS) { // continue;
} else if(err == VK_SUBOPTIMAL_KHR || err == VK_SUCCESS) {
Screen.State = DrawState::Drawing; Screen.State = DrawState::Drawing;
//Готовим инструкции рисовки //Готовим инструкции рисовки
@@ -220,13 +303,17 @@ void Vulkan::run()
{ {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
.pNext = nullptr, .pNext = nullptr,
.flags = 0, .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
.pInheritanceInfo = nullptr .pInheritanceInfo = nullptr
}; };
vkAssert(!vkBeginCommandBuffer(Graphics.CommandBufferRender, &cmd_buf_info)); vkAssert(!vkBeginCommandBuffer(Graphics.CommandBufferRender, &cmd_buf_info));
} }
if(Game.RSession) {
Game.RSession->beforeDraw(double(gTime));
}
{ {
VkImageMemoryBarrier image_memory_barrier = VkImageMemoryBarrier image_memory_barrier =
{ {
@@ -445,10 +532,15 @@ 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();
@@ -493,16 +585,25 @@ void Vulkan::run()
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
.pNext = nullptr, .pNext = nullptr,
.waitSemaphoreCount = 1, .waitSemaphoreCount = 1,
.pWaitSemaphores = &SemaphoreImageAcquired, .pWaitSemaphores = &SemaphoreImageAcquired[semNext],
.pWaitDstStageMask = &pipe_stage_flags, .pWaitDstStageMask = &pipe_stage_flags,
.commandBufferCount = 1, .commandBufferCount = 1,
.pCommandBuffers = &Graphics.CommandBufferRender, .pCommandBuffers = &Graphics.CommandBufferRender,
.signalSemaphoreCount = 1, .signalSemaphoreCount = 1,
.pSignalSemaphores = &SemaphoreDrawComplete .pSignalSemaphores = &SemaphoreDrawComplete[semNext]
}; };
//Рисуем, когда получим картинку // Отправляем команды рендера в очередь
vkAssert(!vkQueueSubmit(Graphics.DeviceQueueGraphic, 1, &submit_info, nullFence)); {
auto lockQueue = Graphics.DeviceQueueGraphic.lock();
vkAssert(!vkQueueSubmit(*lockQueue, 1, &submit_info, drawEndFence));
}
// Насильно ожидаем завершения рендера кадра
vkWaitForFences(Graphics.Device, 1, &drawEndFence, true, -1);
vkResetFences(Graphics.Device, 1, &drawEndFence);
if(Game.RSession)
Game.RSession->onGpuFinished();
} }
{ {
@@ -511,14 +612,18 @@ void Vulkan::run()
.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
.pNext = NULL, .pNext = NULL,
.waitSemaphoreCount = 1, .waitSemaphoreCount = 1,
.pWaitSemaphores = &SemaphoreDrawComplete, .pWaitSemaphores = &SemaphoreDrawComplete[semNext],
.swapchainCount = 1, .swapchainCount = 1,
.pSwapchains = &Graphics.Swapchain, .pSwapchains = &Graphics.Swapchain,
.pImageIndices = &Graphics.DrawBufferCurrent .pImageIndices = &Graphics.DrawBufferCurrent
}; };
// Завершаем картинку // Передадим фрейм, когда рендер будет завершён
err = vkQueuePresentKHR(Graphics.DeviceQueueGraphic, &present); {
auto lockQueue = Graphics.DeviceQueueGraphic.lock();
err = vkQueuePresentKHR(*lockQueue, &present);
}
if (err == VK_ERROR_OUT_OF_DATE_KHR) if (err == VK_ERROR_OUT_OF_DATE_KHR)
{ {
freeSwapchains(); freeSwapchains();
@@ -532,17 +637,20 @@ void Vulkan::run()
vkAssert(err == VK_TIMEOUT); vkAssert(err == VK_TIMEOUT);
if(Game.Session) { if(Game.Session) {
Game.Session->atFreeDrawTime(gTime, dTime); if(Game.RSession)
Game.RSession->pushStage(EnumRenderStage::WorldUpdate);
Game.Session->update(gTime, dTime);
} }
vkAssert(!vkQueueWaitIdle(Graphics.DeviceQueueGraphic));
vkDeviceWaitIdle(Graphics.Device);
Screen.State = DrawState::End; Screen.State = DrawState::End;
} }
vkDestroySemaphore(Graphics.Device, SemaphoreImageAcquired, nullptr); for(int iter = 0; iter < 4; iter++) {
vkDestroySemaphore(Graphics.Device, SemaphoreDrawComplete, nullptr); vkDestroySemaphore(Graphics.Device, SemaphoreImageAcquired[iter], nullptr);
vkDestroySemaphore(Graphics.Device, SemaphoreDrawComplete[iter], nullptr);
}
vkDestroyFence(Graphics.Device, drawEndFence, nullptr);
} }
void Vulkan::glfwCallbackError(int error, const char *description) void Vulkan::glfwCallbackError(int error, const char *description)
@@ -568,8 +676,6 @@ uint32_t Vulkan::memoryTypeFromProperties(uint32_t bitsOfAcceptableTypes, VkFlag
void Vulkan::freeSwapchains() void Vulkan::freeSwapchains()
{ {
//vkDeviceWaitIdle(Screen.Device);
if(Graphics.Instance && Graphics.Device) if(Graphics.Instance && Graphics.Device)
{ {
std::vector<VkImageView> oldViews; std::vector<VkImageView> oldViews;
@@ -726,8 +832,8 @@ void Vulkan::buildSwapchains()
.imageColorSpace = Graphics.SurfaceColorSpace, .imageColorSpace = Graphics.SurfaceColorSpace,
.imageExtent = swapchainExtent, .imageExtent = swapchainExtent,
.imageArrayLayers = 1, .imageArrayLayers = 1,
.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
| VK_IMAGE_USAGE_TRANSFER_DST_BIT, // | VK_IMAGE_USAGE_TRANSFER_DST_BIT,
.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE, .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE,
.queueFamilyIndexCount = 0, .queueFamilyIndexCount = 0,
.pQueueFamilyIndices = nullptr, .pQueueFamilyIndices = nullptr,
@@ -1179,7 +1285,11 @@ void Vulkan::checkLibrary()
uint32_t count = -1; uint32_t count = -1;
VkResult res; VkResult res;
vkAssert(!vkEnumeratePhysicalDevices(localInstance.getInstance(), &count, nullptr)); VkResult errc = vkEnumeratePhysicalDevices(localInstance.getInstance(), &count, nullptr);
if(errc != VK_SUCCESS && errc != VK_INCOMPLETE) {
error << "vkEnumeratePhysicalDevices не смог обработать запрос (ошибка драйвера?)\n";
goto onError;
}
if(!count) if(!count)
{ {
@@ -1458,6 +1568,7 @@ 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 =
@@ -1474,7 +1585,7 @@ void Vulkan::initNextSettings()
"VK_LAYER_LUNARG_monitor" "VK_LAYER_LUNARG_monitor"
}; };
if(!SettingsNext.Debug) if(!SettingsNext.Debug || getenv("no_vk_debug"))
knownDebugLayers.clear(); knownDebugLayers.clear();
std::vector<vkInstanceLayer> enableDebugLayers; std::vector<vkInstanceLayer> enableDebugLayers;
@@ -1487,7 +1598,38 @@ void Vulkan::initNextSettings()
break; break;
} }
Graphics.Instance.emplace(this, enableDebugLayers); std::vector<vkInstanceExtension> enableExtension;
if(SettingsNext.Debug)
for(const vkInstanceExtension &ext : Graphics.InstanceExtensions) {
if(ext.ExtensionName == "VK_EXT_debug_utils") {
enableExtension.push_back(ext);
hasVK_EXT_debug_utils = true;
break;
}
}
Graphics.Instance.emplace(this, enableDebugLayers, enableExtension);
}
if(hasVK_EXT_debug_utils) {
VkDebugUtilsMessengerCreateInfoEXT createInfo = {
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT,
.pNext = nullptr,
.flags = 0,
.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT,
.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT,
.pfnUserCallback = &debugCallback,
.pUserData = nullptr
};
VkDebugUtilsMessengerEXT debugMessenger;
PFN_vkCreateDebugUtilsMessengerEXT myvkCreateDebugUtilsMessengerEXT = reinterpret_cast<PFN_vkCreateDebugUtilsMessengerEXT>(vkGetInstanceProcAddr(Graphics.Instance->getInstance(), "vkCreateDebugUtilsMessengerEXT"));
myvkCreateDebugUtilsMessengerEXT(Graphics.Instance->getInstance(), &createInfo, nullptr, &debugMessenger);
LOG.debug() << "Добавлен обработчик логов";
} }
if(!Graphics.Surface) if(!Graphics.Surface)
@@ -1557,8 +1699,8 @@ void Vulkan::initNextSettings()
features.features.geometryShader = true; features.features.geometryShader = true;
feat11.uniformAndStorageBuffer16BitAccess = true; feat11.uniformAndStorageBuffer16BitAccess = false;
feat11.storageBuffer16BitAccess = true; feat11.storageBuffer16BitAccess = false;
VkDeviceCreateInfo infoDevice = VkDeviceCreateInfo infoDevice =
{ {
@@ -1575,7 +1717,8 @@ void Vulkan::initNextSettings()
}; };
vkAssert(!vkCreateDevice(Graphics.PhysicalDevice, &infoDevice, nullptr, &Graphics.Device)); vkAssert(!vkCreateDevice(Graphics.PhysicalDevice, &infoDevice, nullptr, &Graphics.Device));
vkGetDeviceQueue(Graphics.Device, SettingsNext.QueueGraphics, 0, &Graphics.DeviceQueueGraphic); auto lockQueue = Graphics.DeviceQueueGraphic.lock();
vkGetDeviceQueue(Graphics.Device, SettingsNext.QueueGraphics, 0, &*lockQueue);
} }
// Определяемся с форматом экранного буфера // Определяемся с форматом экранного буфера
@@ -1625,7 +1768,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, .flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT | VK_COMMAND_POOL_CREATE_TRANSIENT_BIT,
.queueFamilyIndex = SettingsNext.QueueGraphics .queueFamilyIndex = SettingsNext.QueueGraphics
}; };
@@ -1802,13 +1945,14 @@ 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 = Graphics.DeviceQueueGraphic, .Queue = *lockQueue,
.DescriptorPool = Graphics.ImGuiDescPool, .DescriptorPool = Graphics.ImGuiDescPool,
.RenderPass = Graphics.RenderPass, .RenderPass = Graphics.RenderPass,
.MinImageCount = Graphics.DrawBufferCount, .MinImageCount = Graphics.DrawBufferCount,
@@ -1824,6 +1968,7 @@ void Vulkan::initNextSettings()
}; };
ImGui_ImplVulkan_Init(&ImGuiInfo); ImGui_ImplVulkan_Init(&ImGuiInfo);
lockQueue.unlock();
// ImFontConfig fontConfig; // ImFontConfig fontConfig;
// fontConfig.MergeMode = false; // fontConfig.MergeMode = false;
@@ -1927,7 +2072,7 @@ void Vulkan::deInitVulkan()
Graphics.RenderPass = nullptr; Graphics.RenderPass = nullptr;
Graphics.Device = nullptr; Graphics.Device = nullptr;
Graphics.PhysicalDevice = nullptr; Graphics.PhysicalDevice = nullptr;
Graphics.DeviceQueueGraphic = nullptr; *Graphics.DeviceQueueGraphic.lock() = nullptr;
Graphics.Surface = nullptr; Graphics.Surface = nullptr;
Graphics.Instance.reset(); Graphics.Instance.reset();
} }
@@ -1951,8 +2096,10 @@ void Vulkan::flushCommandBufferData()
.pSignalSemaphores = nullptr .pSignalSemaphores = nullptr
}; };
vkAssert(!vkQueueSubmit(Graphics.DeviceQueueGraphic, 1, &submit_info, nullFence)); auto lockQueue = Graphics.DeviceQueueGraphic.lock();
vkAssert(!vkQueueWaitIdle(Graphics.DeviceQueueGraphic)); vkAssert(!vkQueueSubmit(*lockQueue, 1, &submit_info, nullFence));
vkAssert(!vkQueueWaitIdle(*lockQueue));
lockQueue.unlock();
VkCommandBufferBeginInfo infoCmdBuffer = VkCommandBufferBeginInfo infoCmdBuffer =
{ {
@@ -2070,6 +2217,17 @@ 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;
@@ -2110,10 +2268,9 @@ void Vulkan::gui_MainMenu() {
if(ConnectionProgress.Socket) { if(ConnectionProgress.Socket) {
std::unique_ptr<Net::AsyncSocket> sock = std::move(ConnectionProgress.Socket); std::unique_ptr<Net::AsyncSocket> sock = std::move(ConnectionProgress.Socket);
Game.RSession = std::make_unique<VulkanRenderSession>(); Game.Session = ServerSession::Create(IOC, std::move(sock));
*this << Game.RSession; Game.RSession = std::make_unique<VulkanRenderSession>(this, Game.Session.get());
Game.Session = std::make_unique<ServerSession>(IOC, std::move(sock), Game.RSession.get()); Game.Session->setRenderSession(Game.RSession.get());
Game.RSession->setServerSession(Game.Session.get());
Game.ImGuiInterfaces.push_back(&Vulkan::gui_ConnectedToServer); Game.ImGuiInterfaces.push_back(&Vulkan::gui_ConnectedToServer);
} }
@@ -2122,10 +2279,40 @@ void Vulkan::gui_MainMenu() {
} }
void Vulkan::gui_ConnectedToServer() { void Vulkan::gui_ConnectedToServer() {
if(Game.Session) { if(Game.Session && Game.RSession) {
if(ImGui::Begin("MainMenu", nullptr, ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove))
{
ImGui::Text("fps: %2.2f World: %u Pos: %i %i %i Region: %i %i %i",
ImGui::GetIO().Framerate, Game.RSession->WI,
(int) Game.RSession->PlayerPos.x, (int) Game.RSession->PlayerPos.y, (int) Game.RSession->PlayerPos.z,
(int) Game.RSession->PlayerPos.x >> 6, (int) Game.RSession->PlayerPos.y >> 6, (int) Game.RSession->PlayerPos.z >> 6
);
double chunksKb = double(Game.Session->getVisibleCompressedChunksBytes()) / 1024.0;
ImGui::Text("chunks compressed: %.1f KB", chunksKb);
if(ImGui::Button("Delimeter"))
LOG.debug();
if(ImGui::Button("Перезагрузить моды")) {
Game.Session->requestModsReload();
}
if(ImGui::Button("Выйти")) {
Game.Выйти = true;
Game.ImGuiInterfaces.pop_back();
}
ImGui::End();
if(Game.Выйти)
return;
}
if(Game.Session->isConnected()) if(Game.Session->isConnected())
return; return;
Game.RSession->pushStage(EnumRenderStage::Shutdown);
Game.RSession = nullptr; Game.RSession = nullptr;
Game.Session = nullptr; Game.Session = nullptr;
Game.ImGuiInterfaces.pop_back(); Game.ImGuiInterfaces.pop_back();
@@ -2635,6 +2822,7 @@ 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);
} }
@@ -2663,12 +2851,16 @@ 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;
} }
@@ -2728,13 +2920,22 @@ 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)
{ {
if(Instance->Graphics.DeviceQueueGraphic) auto lockQueue = Instance->Graphics.DeviceQueueGraphic.lock();
if(*lockQueue)
{ {
//vkAssert(!vkEndCommandBuffer(Buffer)); //vkAssert(!vkEndCommandBuffer(Buffer));
vkEndCommandBuffer(Buffer); vkEndCommandBuffer(Buffer);
@@ -2754,25 +2955,33 @@ CommandBuffer::~CommandBuffer()
}; };
//vkAssert(!vkQueueSubmit(Instance->Graphics.DeviceQueueGraphic, 1, &submit_info, nullFence)); //vkAssert(!vkQueueSubmit(Instance->Graphics.DeviceQueueGraphic, 1, &submit_info, nullFence));
vkQueueSubmit(Instance->Graphics.DeviceQueueGraphic, 1, &submit_info, nullFence); vkQueueSubmit(*lockQueue, 1, &submit_info, Fence);
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()
{ {
vkAssert(Instance->Graphics.DeviceQueueGraphic); auto lockQueue = Instance->Graphics.DeviceQueueGraphic.lock();
vkAssert(*lockQueue);
vkAssert(!vkEndCommandBuffer(Buffer)); vkAssert(!vkEndCommandBuffer(Buffer));
const VkCommandBuffer cmd_bufs[] = { Buffer }; const VkCommandBuffer cmd_bufs[] = { Buffer };
@@ -2790,8 +2999,16 @@ 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,
@@ -3500,6 +3717,8 @@ 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);
@@ -3828,7 +4047,7 @@ void AtlasImage::atlasChangeTextureData(uint16_t id, const uint32_t *rgba)
InfoSubTexture *info = const_cast<InfoSubTexture*>(atlasGetTextureInfo(id)); InfoSubTexture *info = const_cast<InfoSubTexture*>(atlasGetTextureInfo(id));
auto iter = CachedData.find(id); auto iter = CachedData.find(id);
// Если есть данные в кэше, то меняем их // Если есть данные в кеше, то меняем их
if(iter != CachedData.end()) if(iter != CachedData.end())
{ {
if(iter->second.size() == 0) if(iter->second.size() == 0)

View File

@@ -1,5 +1,10 @@
#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>
@@ -19,14 +24,12 @@
#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
@@ -175,7 +178,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 { struct GraphicsObj_t {
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;
@@ -188,7 +191,7 @@ public:
VkPhysicalDevice PhysicalDevice = nullptr; VkPhysicalDevice PhysicalDevice = nullptr;
VkPhysicalDeviceMemoryProperties DeviceMemoryProperties = {0}; VkPhysicalDeviceMemoryProperties DeviceMemoryProperties = {0};
VkDevice Device = nullptr; VkDevice Device = nullptr;
VkQueue DeviceQueueGraphic = VK_NULL_HANDLE; SpinlockObject<VkQueue> DeviceQueueGraphic;
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;
@@ -223,6 +226,10 @@ 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 {
@@ -243,7 +250,8 @@ public:
DestroyLock UseLock; DestroyLock UseLock;
std::thread MainThread; std::thread MainThread;
std::shared_ptr<VulkanRenderSession> RSession; std::shared_ptr<VulkanRenderSession> RSession;
std::unique_ptr<ServerSession> Session; ServerSession::Ptr Session;
bool Выйти = false;
std::list<void (Vulkan::*)()> ImGuiInterfaces; std::list<void (Vulkan::*)()> ImGuiInterfaces;
std::unique_ptr<ServerObj> Server; std::unique_ptr<ServerObj> Server;
@@ -534,6 +542,7 @@ 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;
@@ -1018,5 +1027,25 @@ public:
virtual ~PipelineVGF(); virtual ~PipelineVGF();
}; };
enum class EnumRenderStage {
// Постройка буфера команд на рисовку
// В этот период не должно быть изменений в таблице,
// хранящей указатели на данные для рендера ChunkMesh
// Можно работать с миром
// Здесь нужно дождаться завершения работы с ChunkMesh
ComposingCommandBuffer,
// В этот период можно менять ChunkMesh
// Можно работать с миром
Render,
// В этот период нельзя работать с миром
// Можно менять ChunkMesh
// Здесь нужно дождаться завершения работы с миром, только в
// этом этапе могут приходить события изменения чанков и определений
WorldUpdate,
Shutdown
};
} /* namespace TOS::Navie::VK */ } /* namespace TOS::Navie::VK */

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

2106
Src/Common/Abstract.cpp Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@@ -1,5 +1,6 @@
#include "Common/Abstract.hpp" #pragma once
#include <iostream> #include <algorithm>
namespace LV { namespace LV {
@@ -8,7 +9,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::value_type; using ValType = VecType::Type;
ValType max_delta = 0; ValType max_delta = 0;
ValType result = 0; ValType result = 0;

View File

@@ -43,6 +43,9 @@ AsyncSocket::~AsyncSocket() {
} }
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()

View File

@@ -1,16 +1,18 @@
#pragma once #pragma once
// TODO: Всё это надо переписать
#include "MemoryPool.hpp" #include "MemoryPool.hpp"
#include "Async.hpp" #include "Async.hpp"
#include "TOSLib.hpp" #include "TOSLib.hpp"
#include <atomic>
#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 <condition_variable> #include <type_traits>
namespace LV::Net { namespace LV::Net {
@@ -37,10 +39,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_integral_v<T>, int> = 0> template <typename T, std::enable_if_t<std::is_floating_point_v<T> or 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_integral_v<T>, int> = 0> template <typename T, std::enable_if_t<std::is_floating_point_v<T> or 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;
@@ -62,7 +64,7 @@ protected:
using NetPool = BoostPool<12, 14>; using NetPool = BoostPool<12, 14>;
class Packet { class Packet {
static constexpr size_t MAX_PACKET_SIZE = 1 << 16; static constexpr size_t MAX_PACKET_SIZE = 1 << 24;
uint16_t Size = 0; uint16_t Size = 0;
std::vector<NetPool::PagePtr> Pages; std::vector<NetPool::PagePtr> Pages;
@@ -106,7 +108,7 @@ protected:
return *this; return *this;
} }
template<typename T, std::enable_if_t<std::is_integral_v<T>, int> = 0> template<typename T, std::enable_if_t<std::is_integral_v<T> || std::is_floating_point_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));
@@ -127,7 +129,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_integral_v<T> or std::is_convertible_v<T, std::string_view>, int> = 0> 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>
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);
@@ -144,7 +146,7 @@ protected:
Size = 0; Size = 0;
} }
Packet& complite(std::vector<std::byte> &out) { Packet& complite(std::u8string &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) {
@@ -155,8 +157,8 @@ protected:
return *this; return *this;
} }
std::vector<std::byte> complite() { std::u8string complite() {
std::vector<std::byte> out; std::u8string out;
complite(out); complite(out);
return out; return out;
} }
@@ -172,6 +174,65 @@ protected:
} }
}; };
class LinearReader {
public:
LinearReader(const std::u8string& input, size_t pos = 0)
: Pos(pos), Input(input)
{}
LinearReader(const std::u8string_view input, size_t pos = 0)
: Pos(pos), Input(input)
{}
LinearReader(const LinearReader&) = delete;
LinearReader(LinearReader&&) = delete;
LinearReader& operator=(const LinearReader&) = delete;
LinearReader& operator=(LinearReader&&) = delete;
void read(std::byte *data, uint32_t size) {
if(Input.size()-Pos < size)
MAKE_ERROR("Недостаточно данных");
std::copy((const std::byte*) Input.data()+Pos, (const std::byte*) Input.data()+Pos+size, data);
Pos += size;
}
template<typename T, std::enable_if_t<std::is_floating_point_v<T> || std::is_integral_v<T> or std::is_same_v<T, std::string>, int> = 0>
T read() {
if constexpr(std::is_floating_point_v<T> || std::is_integral_v<T>) {
T value;
read((std::byte*) &value, sizeof(value));
return swapEndian(value);
} else {
uint16_t size = read<uint16_t>();
T value(size, ' ');
read((std::byte*) value.data(), size);
return value;
}
}
template<typename T>
LinearReader& read(T& val) {
val = read<T>();
return *this;
}
template<typename T>
LinearReader& operator>>(T& val) {
val = read<T>();
return *this;
}
void checkUnreaded() {
if(Pos != Input.size())
MAKE_ERROR("Остались не использованные данные");
}
private:
size_t Pos = 0;
const std::u8string_view Input;
};
class SmartPacket : public Packet { class SmartPacket : public Packet {
public: public:
std::function<bool()> IsStillRelevant; std::function<bool()> IsStillRelevant;
@@ -185,9 +246,9 @@ protected:
tcp::socket Socket; tcp::socket Socket;
static constexpr uint32_t static constexpr uint32_t
MAX_SIMPLE_PACKETS = 8192, MAX_SIMPLE_PACKETS = 16384,
MAX_SMART_PACKETS = MAX_SIMPLE_PACKETS/4, MAX_SMART_PACKETS = MAX_SIMPLE_PACKETS/4,
MAX_PACKETS_SIZE_IN_WAIT = 1 << 24; MAX_PACKETS_SIZE_IN_WAIT = 1 << 26;
struct AsyncContext { struct AsyncContext {
volatile bool NeedShutdown = false, RunSendShutdowned = false; volatile bool NeedShutdown = false, RunSendShutdowned = false;
@@ -218,7 +279,7 @@ protected:
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)); co_spawn(runSender(SendPackets.Context));
@@ -240,9 +301,9 @@ protected:
coro<> read(std::byte *data, uint32_t size); coro<> read(std::byte *data, uint32_t size);
void closeRead(); void closeRead();
template<typename T, std::enable_if_t<std::is_integral_v<T> or std::is_same_v<T, std::string>, int> = 0> 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>
coro<T> read() { coro<T> read() {
if constexpr(std::is_integral_v<T>) { if constexpr(std::is_floating_point_v<T> or 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);
@@ -260,9 +321,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_integral_v<T> or std::is_same_v<T, std::string>, int> = 0> 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>
static inline coro<T> read(tcp::socket &socket) { static inline coro<T> read(tcp::socket &socket) {
if constexpr(std::is_integral_v<T>) { if constexpr(std::is_floating_point_v<T> or 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);
@@ -277,7 +338,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_integral_v<T>, int> = 0> template<typename T, std::enable_if_t<std::is_floating_point_v<T> or std::is_integral_v<T>, int> = 0>
static inline coro<> write(tcp::socket &socket, T u) { static inline coro<> write(tcp::socket &socket, T u) {
u = swapEndian(u); u = swapEndian(u);
co_await write(socket, (const std::byte*) &u, sizeof(u)); co_await write(socket, (const std::byte*) &u, sizeof(u));

View File

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

File diff suppressed because it is too large Load Diff

29
Src/Server/Abstract.cpp Normal file
View File

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

View File

@@ -1,62 +1,54 @@
#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 <boost/uuid/detail/sha1.hpp> #include <sha2.hpp>
#include <sol/sol.hpp>
#include <string>
#include <unordered_map>
#include <boost/json.hpp>
#include <variant>
namespace LV::Server { namespace LV::Server {
using ResourceId_t = uint32_t; namespace js = boost::json;
// Двоичные данные
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 LocalEntityId_t = uint16_t; using RegionEntityId_t = uint16_t;
using GlobalEntityId_t = std::tuple<WorldId_t, Pos::GlobalRegion, LocalEntityId_t>; using ClientEntityId_t = RegionEntityId_t;
using PortalId_t = uint16_t; using ServerEntityId_t = std::tuple<WorldId_t, Pos::GlobalRegion, RegionEntityId_t>;
using RegionFuncEntityId_t = uint16_t;
using ClientFuncEntityId_t = RegionFuncEntityId_t;
using ServerFuncEntityId_t = std::tuple<WorldId_t, Pos::GlobalRegion, RegionFuncEntityId_t>;
using MediaStreamId_t = uint16_t; using MediaStreamId_t = uint16_t;
using ContentBridgeId_t = uint16_t; using ContentBridgeId_t = ResourceId;
using PlayerId_t = uint32_t; using PlayerId_t = ResourceId;
using DefGeneratorId_t = ResourceId;
/* /*
Сервер загружает информацию о локальных текстурах Сервер загружает информацию о локальных текстурах
Синхронизация часто используемых текстур?
Пересмотр списка текстур? Пересмотр списка текстур?
Динамичные текстуры? Динамичные текстуры?
*/ */
struct ResourceFile { struct ResourceFile {
using Hash_t = boost::uuids::detail::sha1::digest_type; using Hash_t = sha2::sha256_hash; // 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() {
boost::uuids::detail::sha1 hash; Hash = sha2::sha256((const uint8_t*) Data.data(), Data.size());
hash.process_bytes(Data.data(), Data.size());
hash.get_digest(Hash);
} }
}; };
@@ -64,37 +56,43 @@ struct ServerTime {
uint32_t Seconds : 24, Sub : 8; uint32_t Seconds : 24, Sub : 8;
}; };
struct VoxelCube {
DefVoxelId_t VoxelId;
Pos::Local256_u Left, Right;
auto operator<=>(const VoxelCube&) const = default;
};
struct VoxelCube_Region { struct VoxelCube_Region {
Pos::Local4096_u Left, Right; union {
DefVoxelId_t VoxelId; struct {
DefVoxelId VoxelId : 24, Meta : 8;
};
auto operator<=>(const VoxelCube_Region&) const = default; DefVoxelId Data = 0;
};
Pos::bvec1024u Left, Right; // TODO: заменить на позицию и размер
auto operator<=>(const VoxelCube_Region& other) const {
if (auto cmp = Left <=> other.Left; cmp != 0)
return cmp;
if (auto cmp = Right <=> other.Right; cmp != 0)
return cmp;
return Data <=> other.Data;
}
bool operator==(const VoxelCube_Region& other) const {
return Left == other.Left && Right == other.Right && Data == other.Data;
}
}; };
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::value_type left, right; Pos::Object::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[iter] = left; VecMin.set(iter, left);
VecMax[iter] = right; VecMax.set(iter, right);
} }
} }
@@ -117,22 +115,26 @@ struct LocalAABB {
struct CollisionAABB : public AABB { struct CollisionAABB : public AABB {
enum struct EnumType { enum struct EnumType {
Voxel, Node, Entity, Barrier, Portal, Another Voxel, Node, Entity, FuncEntity, Barrier, Portal, Another
} Type; } Type;
union { union {
struct { struct {
LocalEntityId_t Index; RegionEntityId_t Index;
} Entity; } Entity;
struct { struct {
Pos::Local16_u Pos; RegionFuncEntityId_t Index;
} FuncEntity;
struct {
Pos::bvec4u Chunk;
Pos::bvec16u Pos;
} Node; } Node;
struct { struct {
Pos::Local16_u Chunk; Pos::bvec4u Chunk;
uint32_t Index; uint32_t Index;
DefVoxelId_t Id;
} Voxel; } Voxel;
struct { struct {
@@ -151,15 +153,63 @@ 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_t DefId; DefEntityId DefId;
public: public:
LocalAABB ABBOX; LocalAABB ABBOX;
// PosQuat // PosQuat
DefWorldId_t WorldId; DefWorldId 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;
@@ -179,16 +229,15 @@ public:
IsRemoved = false; IsRemoved = false;
public: public:
Entity(DefEntityId_t defId); Entity(DefEntityId 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_t getDefId() const { return DefId; } DefEntityId getDefId() const { return DefId; }
}; };
template<typename Vec> template<typename Vec>
struct VoxelCuboidsFuncs { struct VoxelCuboidsFuncs {
@@ -197,9 +246,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.Y == b.Right.Y) && (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.z == b.Right.z) && (a.Left.z == b.Left.z) && (a.Right.z == b.Right.z);
bool yAdjacent = (a.Right.Y == b.Left.Y) && (a.Left.X == b.Left.X) && (a.Right.X == b.Right.X) && (a.Left.Z == b.Left.Z) && (a.Right.Z == b.Right.Z); bool 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;
} }
@@ -209,13 +258,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;
} }
@@ -309,53 +358,47 @@ struct VoxelCuboidsFuncs {
} }
}; };
inline void convertRegionVoxelsToChunks(const std::vector<VoxelCube_Region>& regions, std::vector<VoxelCube> *chunks) { inline void convertRegionVoxelsToChunks(const std::vector<VoxelCube_Region>& regions, std::unordered_map<Pos::bvec4u, 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::Local256_u left { Pos::bvec256u left {
static_cast<uint8_t>(std::max<uint16_t>((x << 8), region.Left.X) - (x << 8)), static_cast<uint8_t>(std::max<uint16_t>((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::Local256_u right { Pos::bvec256u right {
static_cast<uint8_t>(std::min<uint16_t>(((x+1) << 8)-1, region.Right.X) - (x << 8)), static_cast<uint8_t>(std::min<uint16_t>(((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))
}; };
int chunkIndex = z * 16 * 16 + y * 16 + x; chunks[Pos::bvec4u(x, y, z)].push_back({
chunks[chunkIndex].emplace_back(region.VoxelId, left, right); region.VoxelId, region.Meta, left, right
});
} }
} }
} }
} }
} }
inline void convertChunkVoxelsToRegion(const std::vector<VoxelCube> *chunks, std::vector<VoxelCube_Region> &regions) { inline void convertChunkVoxelsToRegion(const std::unordered_map<Pos::bvec4u, std::vector<VoxelCube>> &chunks, std::vector<VoxelCube_Region> &regions) {
for (int x = 0; x < 16; ++x) { for(const auto& [pos, voxels] : chunks) {
for (int y = 0; y < 16; ++y) { Pos::bvec1024u left = pos << 8;
for (int z = 0; z < 16; ++z) { for (const auto& cube : voxels) {
int chunkIndex = z * 16 * 16 + y * 16 + x; regions.push_back({
cube.VoxelId, cube.Meta,
Pos::Local4096_u left(x << 8, y << 8, z << 8); Pos::bvec1024u(left.x+cube.Pos.x, left.y+cube.Pos.y, left.z+cube.Pos.z),
Pos::bvec1024u(left.x+cube.Pos.x+cube.Size.x, left.y+cube.Pos.y+cube.Size.y, left.z+cube.Pos.z+cube.Size.z)
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
);
}
}
} }
} }
@@ -363,4 +406,105 @@ inline void convertChunkVoxelsToRegion(const std::vector<VoxelCube> *chunks, std
regions = VoxelCuboidsFuncs<VoxelCube_Region>::optimizeVoxelRegions(regions); regions = VoxelCuboidsFuncs<VoxelCube_Region>::optimizeVoxelRegions(regions);
} }
struct ServerObjectPos {
WorldId_t WorldId;
Pos::Object ObjectPos;
};
/*
Разница между информацией о наблюдаемых регионах
*/
struct ContentViewInfo_Diff {
// Изменения на уровне миров (увиден или потерян)
std::vector<WorldId_t> WorldsNew, WorldsLost;
// Изменения на уровне регионов
std::unordered_map<WorldId_t, std::vector<Pos::GlobalRegion>> RegionsNew, RegionsLost;
bool empty() const {
return WorldsNew.empty() && WorldsLost.empty() && RegionsNew.empty() && RegionsLost.empty();
}
};
/*
То, какие регионы наблюдает игрок
*/
struct ContentViewInfo {
// std::vector<Pos::GlobalRegion> - сортированный и с уникальными значениями
std::unordered_map<WorldId_t, std::vector<Pos::GlobalRegion>> Regions;
// Что изменилось относительно obj
// Перерасчёт должен проводится при смещении игрока или ContentBridge за границу региона
ContentViewInfo_Diff diffWith(const ContentViewInfo& obj) const {
ContentViewInfo_Diff out;
// Проверяем новые миры и регионы
for(const auto& [key, regions] : Regions) {
auto iterWorld = obj.Regions.find(key);
if(iterWorld == obj.Regions.end()) {
out.WorldsNew.push_back(key);
out.RegionsNew[key] = regions;
} else {
auto &vec = out.RegionsNew[key];
vec.reserve(8*8);
std::set_difference(
regions.begin(), regions.end(),
iterWorld->second.begin(), iterWorld->second.end(),
std::back_inserter(vec)
);
}
}
// Проверяем потерянные миры и регионы
for(const auto& [key, regions] : obj.Regions) {
auto iterWorld = Regions.find(key);
if(iterWorld == Regions.end()) {
out.WorldsLost.push_back(key);
out.RegionsLost[key] = regions;
} else {
auto &vec = out.RegionsLost[key];
vec.reserve(8*8);
std::set_difference(
regions.begin(), regions.end(),
iterWorld->second.begin(), iterWorld->second.end(),
std::back_inserter(vec)
);
}
}
// shrink_to_feet
for(auto& [_, regions] : out.RegionsNew)
regions.shrink_to_fit();
for(auto& [_, regions] : out.RegionsLost)
regions.shrink_to_fit();
return out;
}
};
/*
Мост контента, для отслеживания событий из удалённых точек
По типу портала, через который можно видеть контент на расстоянии
*/
struct ContentBridge {
/*
false -> Из точки Left видно контент в точки Right
true -> Контент виден в обе стороны
*/
bool IsTwoWay = false;
WorldId_t LeftWorld;
Pos::GlobalRegion LeftPos;
WorldId_t RightWorld;
Pos::GlobalRegion RightPos;
};
struct ContentViewCircle {
WorldId_t WorldId;
Pos::GlobalRegion Pos;
// Радиус в регионах в квадрате
int16_t Range;
};
} }

View File

@@ -1,122 +0,0 @@
#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)
: AsyncObject(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;
co_spawn(checkResource_Assets(resId, iter->second / inDomainPath, res));
}
}
return resId;
}
coro<> BinaryResourceManager::checkResource_Assets(ResourceId_t id, fs::path path, std::shared_ptr<Resource> res) {
try {
asio::stream_file fd(IOC, path, asio::stream_file::flags::read_only);
if(fd.size() > 1024*1024*16)
MAKE_ERROR("Превышен лимит размера файла: " << fd.size() << " > " << 1024*1024*16);
std::shared_ptr<ResourceFile> file = std::make_shared<ResourceFile>();
file->Data.resize(fd.size());
co_await asio::async_read(fd, asio::mutable_buffer(file->Data.data(), file->Data.size()));
file->calcHash();
res->LastError.clear();
} catch(const std::exception &exc) {
res->LastError = exc.what();
res->IsLoading = false;
if(const boost::system::system_error *errc = dynamic_cast<const boost::system::system_error*>(&exc); errc && errc->code() == asio::error::operation_aborted)
co_return;
}
res->IsLoading = false;
UpdatedResources.lock_write()->push_back(id);
}
}

View File

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

View File

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

View File

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

View File

@@ -2,10 +2,12 @@
#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 <optional> #include <queue>
#include <unordered_map> #include <unordered_map>
#include <unordered_set> #include <unordered_set>
#include <vector> #include <vector>
@@ -18,220 +20,40 @@ 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 {
private: public:
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);
std::unique_ptr<RemoteClient> Remote;
// Регионы сюда заглядывают
// Каждый такт значения изменений обновляются GameServer'ом
// Объявленная в чанках территория точно отслеживается (активная зона)
ContentViewGlobal ContentViewState;
ContentViewGlobal_DiffInfo ContentView_NewView, ContentView_LostView;
// size_t CVCHash = 0; // Хэш для std::vector<ContentViewCircle> // Измеряется в чанках в регионах (активная зона)
// std::unordered_map<WorldId_t, std::vector<Pos::GlobalRegion>> SubscribedRegions; static constexpr uint16_t getViewRangeActive() { return 2; }
public:
ContentEventController(std::unique_ptr<RemoteClient> &&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 onChunksUpdate_Voxels(WorldId_t worldId, Pos::GlobalRegion regionPos, const std::unordered_map<Pos::Local16_u, const std::vector<VoxelCube>*> &chunks); void onEntitySwap(ServerEntityId_t prevId, ServerEntityId_t newId);
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 onEntityUpdates(WorldId_t worldId, Pos::GlobalRegion regionPos, const std::unordered_map<RegionEntityId_t, Entity*> &entities);
void onChunksUpdate_LightPrism(WorldId_t worldId, Pos::GlobalRegion regionPos, const std::unordered_map<Pos::Local16_u, const LightPrism*> &chunks);
void onEntityEnterLost(WorldId_t worldId, Pos::GlobalRegion regionPos, const std::unordered_set<LocalEntityId_t> &enter, const std::unordered_set<LocalEntityId_t> &lost);
void onEntitySwap(WorldId_t lastWorldId, Pos::GlobalRegion lastRegionPos, LocalEntityId_t lastId, WorldId_t newWorldId, Pos::GlobalRegion newRegionPos, LocalEntityId_t newId);
void onEntityUpdates(WorldId_t worldId, Pos::GlobalRegion regionPos, const std::vector<Entity> &entities);
void onPortalEnterLost(const std::vector<void*> &enter, const std::vector<void*> &lost); void onPortalEnterLost(const std::vector<void*> &enter, const std::vector<void*> &lost);
void onPortalUpdates(const std::vector<void*> &portals); void onPortalUpdates(const std::vector<void*> &portals);
inline const SubscribedObj& getSubscribed() { return Subscribed; }; inline const SubscribedObj& getSubscribed() { return Subscribed; };
}; void onUpdate();
};
} }
namespace std {
template <>
struct hash<LV::Server::ServerObjectPos> {
std::size_t operator()(const LV::Server::ServerObjectPos& obj) const {
return std::hash<uint32_t>()(obj.WorldId) ^ std::hash<int32_t>()(obj.ObjectPos.x) ^ std::hash<int32_t>()(obj.ObjectPos.y) ^ std::hash<int32_t>()(obj.ObjectPos.z);
}
};
template <>
struct hash<LV::Server::ContentViewCircle> {
size_t operator()(const LV::Server::ContentViewCircle& obj) const noexcept {
// Используем стандартную функцию хеширования для uint32_t, glm::i16vec3 и int32_t
auto worldIdHash = std::hash<uint32_t>{}(obj.WorldId) << 32;
auto posHash =
std::hash<int16_t>{}(obj.Pos.x) ^
(std::hash<int16_t>{}(obj.Pos.y) << 16) ^
(std::hash<int16_t>{}(obj.Pos.z) << 32);
auto rangeHash = std::hash<int32_t>{}(obj.Range);
return worldIdHash ^
posHash ^
(~rangeHash << 32);
}
};
}

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -2,6 +2,7 @@
#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>
@@ -10,31 +11,59 @@
namespace LV::Server { namespace LV::Server {
struct SB_Region { /*
std::vector<VoxelCube_Region> Voxels; Обменная единица мира
std::unordered_map<DefVoxelId_t, std::string> VoxelsMap; */
std::unordered_map<Pos::Local16_u, Node> Nodes; struct SB_Region_In {
std::unordered_map<DefNodeId_t, std::string> NodeMap; // Список вокселей всех чанков
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<Entity> Entityes;
std::unordered_map<DefEntityId_t, std::string> EntityMap; // Привязка идентификатора к ключу профиля
std::vector<std::pair<DefEntityId, std::string>> EntityMap;
};
struct DB_Region_Out {
std::vector<VoxelCube_Region> Voxels;
std::array<std::array<Node, 16*16*16>, 4*4*4> Nodes;
std::vector<Entity> Entityes;
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; };
// Сохранить регион
virtual void save(std::string worldId, Pos::GlobalRegion regionPos, const SB_Region *data) = 0; struct TickSyncInfo_Out {
// Удалить регион std::unordered_map<WorldId_t, std::vector<Pos::GlobalRegion>> NotExisten;
virtual void remove(std::string worldId, Pos::GlobalRegion regionPos) = 0; std::unordered_map<WorldId_t, std::vector<std::pair<Pos::GlobalRegion, DB_Region_Out>>> LoadedRegions;
// Удалить мир };
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 {
@@ -45,8 +74,6 @@ 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;
// Загрузить игрока // Загрузить игрока
@@ -66,34 +93,30 @@ class IAuthSaveBackend {
public: public:
virtual ~IAuthSaveBackend(); virtual ~IAuthSaveBackend();
// Может ли использоваться параллельно
virtual bool isAsync() { return false; };
// Существует ли игрок // Существует ли игрок
virtual bool isExist(std::string playerId) = 0; virtual coro<bool> isExist(std::string username) = 0;
// Переименовать игрока // Переименовать игрока
virtual void rename(std::string fromPlayerId, std::string toPlayerId) = 0; virtual coro<> rename(std::string prevUsername, std::string newUsername) = 0;
// Загрузить игрока // Загрузить игрока (если есть, вернёт true)
virtual void load(std::string playerId, SB_Auth *data) = 0; virtual coro<bool> load(std::string username, SB_Auth &data) = 0;
// Сохранить игрока // Сохранить игрока
virtual void save(std::string playerId, const SB_Auth *data) = 0; virtual coro<> save(std::string username, const SB_Auth &data) = 0;
// Удалить игрока // Удалить игрока
virtual void remove(std::string playerId) = 0; virtual coro<> remove(std::string username) = 0;
}; };
class IModStorageSaveBackend { class IModStorageSaveBackend {
public: public:
virtual ~IModStorageSaveBackend(); virtual ~IModStorageSaveBackend();
// Может ли использоваться параллельно // // Загрузить запись
virtual bool isAsync() { return false; }; // virtual void load(std::string domain, std::string key, std::string *data) = 0;
// Загрузить запись // // Сохранить запись
virtual void load(std::string domain, std::string key, std::string *data) = 0; // virtual void save(std::string domain, std::string key, const std::string *data) = 0;
// Сохранить запись // // Удалить запись
virtual void save(std::string domain, std::string key, const std::string *data) = 0; // virtual void remove(std::string domain, std::string key) = 0;
// Удалить запись // // Удалить домен
virtual void remove(std::string domain, std::string key) = 0; // virtual void remove(std::string domain) = 0;
// Удалить домен
virtual void remove(std::string domain) = 0;
}; };
class ISaveBackendProvider { class ISaveBackendProvider {

View File

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

View File

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

View File

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

View File

@@ -1,20 +1,24 @@
#pragma once #pragma once
#include "boost/asio/awaitable.hpp" #include "TOSLib.hpp"
#include "boost/asio/co_spawn.hpp" #include <functional>
#include "boost/asio/deadline_timer.hpp" #include "boost/system/detail/error_code.hpp"
#include "boost/asio/detached.hpp" #include "boost/system/system_error.hpp"
#include "boost/asio/io_context.hpp" #include <boost/asio.hpp>
#include "boost/asio/use_awaitable.hpp"
#include <boost/asio/experimental/awaitable_operators.hpp> #include <boost/asio/experimental/awaitable_operators.hpp>
#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;
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
{ {
@@ -52,44 +56,309 @@ public:
} }
}; };
class IAsyncDestructible : public std::enable_shared_from_this<IAsyncDestructible> {
protected:
boost::asio::any_io_executor IOC;
boost::asio::deadline_timer DestructLine;
virtual coro<> asyncDestructor() { DestructLine.cancel(); co_return; } /*
Многие могут уведомлять одного
Ждёт события. После доставки уведомления ждёт повторно
*/
class MultipleToOne_AsyncSymaphore {
asio::deadline_timer Timer;
public: public:
IAsyncDestructible(boost::asio::any_io_executor ioc) MultipleToOne_AsyncSymaphore(asio::io_context &ioc)
: IOC(ioc), DestructLine(ioc, boost::posix_time::ptime(boost::posix_time::pos_infin)) : Timer(ioc, boost::posix_time::ptime(boost::posix_time::pos_infin))
{}
void notify() {
Timer.cancel();
}
void wait() {
try { Timer.wait(); } catch(...) {}
Timer.expires_at(boost::posix_time::ptime(boost::posix_time::pos_infin));
}
coro<> async_wait() {
try { co_await Timer.async_wait(); } catch(...) {}
}
};
class WaitableCoro {
asio::io_context &IOC;
std::shared_ptr<MultipleToOne_AsyncSymaphore> Symaphore;
std::exception_ptr LastException;
public:
WaitableCoro(asio::io_context &ioc)
: IOC(ioc)
{}
void co_spawn(coro<> token) {
Symaphore = std::make_shared<MultipleToOne_AsyncSymaphore>(IOC);
asio::co_spawn(IOC, [token = std::move(token), symaphore = Symaphore]() -> coro<> {
try { co_await std::move(const_cast<coro<>&>(token)); } catch(...) {}
symaphore->notify();
}, asio::detached);
}
void wait() {
Symaphore->wait();
}
coro<> async_wait() {
return Symaphore->async_wait();
}
};
class AsyncUseControl {
public:
class Lock {
AsyncUseControl *AUC;
public:
Lock(AsyncUseControl *auc)
: AUC(auc)
{}
Lock()
: AUC(nullptr)
{}
~Lock() {
if(AUC)
unlock();
}
Lock(const Lock&) = delete;
Lock(Lock&& obj)
: AUC(obj.AUC)
{
obj.AUC = nullptr;
}
Lock& operator=(const Lock&) = delete;
Lock& operator=(Lock&& obj) {
if(&obj == this)
return *this;
if(AUC)
unlock();
AUC = obj.AUC;
obj.AUC = nullptr;
return *this;
}
void unlock() {
assert(AUC);
if(--AUC->Uses == 0 && AUC->OnNoUse) {
asio::post(AUC->IOC, std::move(AUC->OnNoUse));
}
AUC = nullptr;
}
};
private:
asio::io_context &IOC;
std::move_only_function<void()> OnNoUse;
std::atomic_int Uses = 0;
public:
AsyncUseControl(asio::io_context &ioc)
: IOC(ioc)
{
}
template<BOOST_ASIO_COMPLETION_TOKEN_FOR(void()) Token = asio::default_completion_token_t<asio::io_context>>
auto wait(Token&& token = asio::default_completion_token_t<asio::io_context>()) {
auto initiation = [this](auto&& token) {
int value;
do {
value = Uses.exchange(-1);
} while(value == -1);
OnNoUse = std::move(token);
if(value == 0)
OnNoUse();
Uses.exchange(value);
};
return asio::async_initiate<Token, void()>(initiation, token);
}
Lock use() {
int value;
do {
value = Uses.exchange(-1);
} while(value == -1);
if(OnNoUse)
throw boost::system::system_error(asio::error::operation_aborted, "OnNoUse");
Uses.exchange(++value);
return Lock(this);
}
};
/*
Используется, чтобы вместо уничтожения объекта в умной ссылке, вызвать корутину с co_await asyncDestructor()
*/
class IAsyncDestructible : public std::enable_shared_from_this<IAsyncDestructible> {
protected:
asio::io_context &IOC;
AsyncUseControl AUC;
virtual coro<> asyncDestructor() { co_await AUC.wait(); }
public:
IAsyncDestructible(asio::io_context &ioc)
: IOC(ioc), AUC(ioc)
{} {}
virtual ~IAsyncDestructible() {} virtual ~IAsyncDestructible() {}
coro<std::variant<std::monostate, std::monostate>> cancelable(coro<> &&c) { return std::move(c) || DestructLine.async_wait(boost::asio::use_awaitable); } protected:
template<typename T, typename = typename std::is_same<IAsyncDestructible, T>> template<typename T, typename = typename std::is_same<IAsyncDestructible, T>>
static std::shared_ptr<T> createShared(boost::asio::any_io_executor ioc, T *ptr) static std::shared_ptr<T> createShared(asio::io_context &ioc, T *ptr)
{ {
return std::shared_ptr<T>(ptr, [ioc = std::move(ioc)](IAsyncDestructible *ptr) { return std::shared_ptr<T>(ptr, [&ioc](T *ptr) {
boost::asio::co_spawn(ioc, [](IAsyncDestructible *ptr) -> coro<> { boost::asio::co_spawn(ioc,
try { co_await ptr->asyncDestructor(); } catch(...) { } [ptr, &ioc]() mutable -> coro<> {
delete ptr; try { co_await dynamic_cast<IAsyncDestructible*>(ptr)->asyncDestructor(); } catch(...) { }
co_return; asio::post(ioc, [ptr](){ delete ptr; });
} (ptr), boost::asio::detached); },
boost::asio::detached);
}); });
} }
template<typename T, typename ...Args, typename = typename std::is_same<IAsyncDestructible, T>> template<typename T, typename = typename std::is_same<IAsyncDestructible, T>>
static std::shared_ptr<T> makeShared(boost::asio::any_io_executor ioc, Args&& ... args) static coro<std::shared_ptr<T>> createShared(T *ptr)
{ {
std::shared_ptr<T>(new T(ioc, std::forward<Args>(args)..., [ioc = std::move(ioc)](IAsyncDestructible *ptr) { co_return std::shared_ptr<T>(ptr, [ioc = asio::get_associated_executor(co_await asio::this_coro::executor)](T *ptr) {
boost::asio::co_spawn(ioc, [](IAsyncDestructible *ptr) -> coro<> { boost::asio::co_spawn(ioc,
try { co_await ptr->asyncDestructor(); } catch(...) { } [ptr, &ioc]() mutable -> coro<> {
delete ptr; try { co_await dynamic_cast<IAsyncDestructible*>(ptr)->asyncDestructor(); } catch(...) { }
co_return; asio::post(ioc, [ptr](){ delete ptr; });
} (ptr), boost::asio::detached); },
})); boost::asio::detached);
});
}
template<typename T, typename = typename std::is_same<IAsyncDestructible, T>>
static std::unique_ptr<T, std::function<void(T*)>> createUnique(asio::io_context &ioc, T *ptr)
{
return std::unique_ptr<T, std::function<void(T*)>>(ptr, [&ioc](T *ptr) {
boost::asio::co_spawn(ioc,
[ptr, &ioc]() mutable -> coro<> {
try { co_await dynamic_cast<IAsyncDestructible*>(ptr)->asyncDestructor(); } catch(...) { }
asio::post(ioc, [ptr](){ delete ptr; });
},
boost::asio::detached);
});
}
template<typename T, typename = typename std::is_same<IAsyncDestructible, T>>
static coro<std::unique_ptr<T, std::function<void(T*)>>> createUnique(T *ptr)
{
co_return std::unique_ptr<T, std::function<void(T*)>>(ptr, [ioc = asio::get_associated_executor(co_await asio::this_coro::executor)](T *ptr) {
boost::asio::co_spawn(ioc,
[ptr, &ioc]() mutable -> coro<> {
try { co_await dynamic_cast<IAsyncDestructible*>(ptr)->asyncDestructor(); } catch(...) { }
asio::post(ioc, [ptr](){ delete ptr; });
},
boost::asio::detached);
});
}
};
template<typename T>
class AsyncMutexObject {
public:
class Lock {
public:
Lock(AsyncMutexObject* obj)
: Obj(obj)
{}
Lock(const Lock& other) = delete;
Lock(Lock&& other)
: Obj(other.Obj)
{
other.Obj = nullptr;
}
~Lock() {
if(Obj)
unlock();
}
Lock& operator=(const Lock& other) = delete;
Lock& operator=(Lock& other) {
if(&other == this)
return *this;
if(Obj)
unlock();
Obj = other.Obj;
other.Obj = nullptr;
}
T& get() const { assert(Obj); return Obj->value; }
T* operator->() const { assert(Obj); return &Obj->value; }
T& operator*() const { assert(Obj); return Obj->value; }
void unlock() {
assert(Obj);
typename SpinlockObject<Context>::Lock ctx = Obj->Ctx.lock();
if(ctx->Chain.empty()) {
ctx->InExecution = false;
} else {
auto token = std::move(ctx->Chain.front());
ctx->Chain.pop_front();
ctx.unlock();
token(Lock(Obj));
}
Obj = nullptr;
}
private:
AsyncMutexObject *Obj;
};
private:
struct Context {
std::list<std::move_only_function<void(Lock)>> Chain;
bool InExecution = false;
};
SpinlockObject<Context> Ctx;
T value;
public:
template<BOOST_ASIO_COMPLETION_TOKEN_FOR(void(Lock)) Token = asio::default_completion_token_t<asio::io_context::executor_type>>
auto lock(Token&& token = Token()) {
auto initiation = [this](auto&& token) mutable {
typename SpinlockObject<Context>::Lock ctx = Ctx.lock();
if(ctx->InExecution) {
ctx->Chain.emplace_back(std::move(token));
} else {
ctx->InExecution = true;
ctx.unlock();
token(Lock(this));
}
};
return boost::asio::async_initiate<Token, void(Lock)>(std::move(initiation), token);
} }
}; };

View File

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

View File

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

View File

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

View File

@@ -2,22 +2,28 @@
import sys import sys
import re import re
output_file = "resources.cpp" if len(sys.argv) < 3:
with open(output_file, "w") as f: print("Usage: assets.py <output_cpp> <file1> [file2 ...]")
sys.exit(1)
output_cpp = sys.argv[1]
symbols = sys.argv[2:]
with open(output_cpp, "w") as f:
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 sys.argv[1:]: for symbol in symbols:
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("}") f.write("}\n\n")
f.write("\n\nstd::unordered_map<std::string, std::tuple<const char*, const char*>> _binary_assets_symbols = {\n") f.write("std::unordered_map<std::string, std::tuple<const char*, const char*>> _binary_assets_symbols = {\n")
for symbol in sys.argv[1:]: for symbol in symbols:
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_file} is generated.") print(f"File {output_cpp} is generated.")

View File

@@ -1,17 +1,31 @@
#include "Common/Abstract.hpp"
#include "boost/asio/awaitable.hpp"
#include <chrono>
#include <filesystem>
#include <iostream> #include <iostream>
#include <boost/asio.hpp> #include <boost/asio.hpp>
#include <Client/Vulkan/Vulkan.hpp> #include <Client/Vulkan/Vulkan.hpp>
#include <thread>
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); LV::Client::VK::Vulkan vkInst(ioc);
ioc.run(); ioc.run();
return 0; return 0;

499
Src/sha2.hpp Normal file
View File

@@ -0,0 +1,499 @@
// 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 */

View File

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

0
assets/null Normal file
View File

View File

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

Binary file not shown.

View File

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

Binary file not shown.

View File

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

View File

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

View File

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

View File

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

BIN
assets/textures/0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

BIN
assets/textures/frame.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

161
docs/assets_definitions.md Normal file
View File

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

57
docs/resources.md Normal file
View File

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

View File

@@ -0,0 +1,81 @@
{
"textures": {
"default": "acacia_planks.png"
},
"cuboids": [
{
"from": [
-0.5,
-0.5,
-0.5
],
"to": [
0.5,
0.5,
0.5
],
"faces": {
"down": {
"uv": [
0,
0,
1,
1
],
"texture": "default",
"cullface": "down"
},
"up": {
"uv": [
0,
0,
1,
1
],
"texture": "default",
"cullface": "up"
},
"north": {
"uv": [
0,
0,
1,
1
],
"texture": "default",
"cullface": "north"
},
"south": {
"uv": [
0,
0,
1,
1
],
"texture": "default",
"cullface": "south"
},
"west": {
"uv": [
0,
0,
1,
1
],
"texture": "default",
"cullface": "west"
},
"east": {
"uv": [
0,
0,
1,
1
],
"texture": "default",
"cullface": "east"
}
}
}
]
}

View File

@@ -0,0 +1,81 @@
{
"textures": {
"default": "frame.png"
},
"cuboids": [
{
"from": [
-0.5,
-0.5,
-0.5
],
"to": [
0.5,
0.5,
0.5
],
"faces": {
"down": {
"uv": [
0,
0,
1,
1
],
"texture": "default",
"cullface": "down"
},
"up": {
"uv": [
0,
0,
1,
1
],
"texture": "default",
"cullface": "up"
},
"north": {
"uv": [
0,
0,
1,
1
],
"texture": "default",
"cullface": "north"
},
"south": {
"uv": [
0,
0,
1,
1
],
"texture": "default",
"cullface": "south"
},
"west": {
"uv": [
0,
0,
1,
1
],
"texture": "default",
"cullface": "west"
},
"east": {
"uv": [
0,
0,
1,
1
],
"texture": "default",
"cullface": "east"
}
}
}
]
}

View File

@@ -0,0 +1,81 @@
{
"textures": {
"default": "grass.png"
},
"cuboids": [
{
"from": [
-0.5,
-0.5,
-0.5
],
"to": [
0.5,
0.5,
0.5
],
"faces": {
"down": {
"uv": [
0,
0,
1,
1
],
"texture": "default",
"cullface": "down"
},
"up": {
"uv": [
0,
0,
1,
1
],
"texture": "default",
"cullface": "up"
},
"north": {
"uv": [
0,
0,
1,
1
],
"texture": "default",
"cullface": "north"
},
"south": {
"uv": [
0,
0,
1,
1
],
"texture": "default",
"cullface": "south"
},
"west": {
"uv": [
0,
0,
1,
1
],
"texture": "default",
"cullface": "west"
},
"east": {
"uv": [
0,
0,
1,
1
],
"texture": "default",
"cullface": "east"
}
}
}
]
}

View File

@@ -0,0 +1,81 @@
{
"textures": {
"default": "jungle_planks.png"
},
"cuboids": [
{
"from": [
-0.5,
-0.5,
-0.5
],
"to": [
0.5,
0.5,
0.5
],
"faces": {
"down": {
"uv": [
0,
0,
1,
1
],
"texture": "default",
"cullface": "down"
},
"up": {
"uv": [
0,
0,
1,
1
],
"texture": "default",
"cullface": "up"
},
"north": {
"uv": [
0,
0,
1,
1
],
"texture": "default",
"cullface": "north"
},
"south": {
"uv": [
0,
0,
1,
1
],
"texture": "default",
"cullface": "south"
},
"west": {
"uv": [
0,
0,
1,
1
],
"texture": "default",
"cullface": "west"
},
"east": {
"uv": [
0,
0,
1,
1
],
"texture": "default",
"cullface": "east"
}
}
}
]
}

View File

@@ -0,0 +1,81 @@
{
"textures": {
"default": "oak_planks.png"
},
"cuboids": [
{
"from": [
-0.5,
-0.5,
-0.5
],
"to": [
0.5,
0.5,
0.5
],
"faces": {
"down": {
"uv": [
0,
0,
1,
1
],
"texture": "default",
"cullface": "down"
},
"up": {
"uv": [
0,
0,
1,
1
],
"texture": "default",
"cullface": "up"
},
"north": {
"uv": [
0,
0,
1,
1
],
"texture": "default",
"cullface": "north"
},
"south": {
"uv": [
0,
0,
1,
1
],
"texture": "default",
"cullface": "south"
},
"west": {
"uv": [
0,
0,
1,
1
],
"texture": "default",
"cullface": "west"
},
"east": {
"uv": [
0,
0,
1,
1
],
"texture": "default",
"cullface": "east"
}
}
}
]
}

View File

@@ -0,0 +1,81 @@
{
"textures": {
"default": "tropical_rainforest_wood.png"
},
"cuboids": [
{
"from": [
-0.5,
-0.5,
-0.5
],
"to": [
0.5,
0.5,
0.5
],
"faces": {
"down": {
"uv": [
0,
0,
1,
1
],
"texture": "default",
"cullface": "down"
},
"up": {
"uv": [
0,
0,
1,
1
],
"texture": "default",
"cullface": "up"
},
"north": {
"uv": [
0,
0,
1,
1
],
"texture": "default",
"cullface": "north"
},
"south": {
"uv": [
0,
0,
1,
1
],
"texture": "default",
"cullface": "south"
},
"west": {
"uv": [
0,
0,
1,
1
],
"texture": "default",
"cullface": "west"
},
"east": {
"uv": [
0,
0,
1,
1
],
"texture": "default",
"cullface": "east"
}
}
}
]
}

View File

@@ -0,0 +1,81 @@
{
"textures": {
"default": "willow_wood.png"
},
"cuboids": [
{
"from": [
-0.5,
-0.5,
-0.5
],
"to": [
0.5,
0.5,
0.5
],
"faces": {
"down": {
"uv": [
0,
0,
1,
1
],
"texture": "default",
"cullface": "down"
},
"up": {
"uv": [
0,
0,
1,
1
],
"texture": "default",
"cullface": "up"
},
"north": {
"uv": [
0,
0,
1,
1
],
"texture": "default",
"cullface": "north"
},
"south": {
"uv": [
0,
0,
1,
1
],
"texture": "default",
"cullface": "south"
},
"west": {
"uv": [
0,
0,
1,
1
],
"texture": "default",
"cullface": "west"
},
"east": {
"uv": [
0,
0,
1,
1
],
"texture": "default",
"cullface": "east"
}
}
}
]
}

View File

@@ -0,0 +1,81 @@
{
"textures": {
"default": "xnether_blue_wood.png"
},
"cuboids": [
{
"from": [
-0.5,
-0.5,
-0.5
],
"to": [
0.5,
0.5,
0.5
],
"faces": {
"down": {
"uv": [
0,
0,
1,
1
],
"texture": "default",
"cullface": "down"
},
"up": {
"uv": [
0,
0,
1,
1
],
"texture": "default",
"cullface": "up"
},
"north": {
"uv": [
0,
0,
1,
1
],
"texture": "default",
"cullface": "north"
},
"south": {
"uv": [
0,
0,
1,
1
],
"texture": "default",
"cullface": "south"
},
"west": {
"uv": [
0,
0,
1,
1
],
"texture": "default",
"cullface": "west"
},
"east": {
"uv": [
0,
0,
1,
1
],
"texture": "default",
"cullface": "east"
}
}
}
]
}

View File

@@ -0,0 +1,81 @@
{
"textures": {
"default": "xnether_purple_wood.png"
},
"cuboids": [
{
"from": [
-0.5,
-0.5,
-0.5
],
"to": [
0.5,
0.5,
0.5
],
"faces": {
"down": {
"uv": [
0,
0,
1,
1
],
"texture": "default",
"cullface": "down"
},
"up": {
"uv": [
0,
0,
1,
1
],
"texture": "default",
"cullface": "up"
},
"north": {
"uv": [
0,
0,
1,
1
],
"texture": "default",
"cullface": "north"
},
"south": {
"uv": [
0,
0,
1,
1
],
"texture": "default",
"cullface": "south"
},
"west": {
"uv": [
0,
0,
1,
1
],
"texture": "default",
"cullface": "west"
},
"east": {
"uv": [
0,
0,
1,
1
],
"texture": "default",
"cullface": "east"
}
}
}
]
}

View File

@@ -0,0 +1,14 @@
{
"meta==0": {
"model": "node/grass.json"
},
"meta==1": {
"model": "node/oak_planks.json"
},
"meta==2": {
"model": "node/jungle_planks.json"
},
"meta==3": {
"model": "node/acacia_planks.json"
}
}

View File

@@ -0,0 +1,14 @@
{
"meta==0": {
"model": "node/tropical_rainforest_wood.json"
},
"meta==1": {
"model": "node/willow_wood.json"
},
"meta==2": {
"model": "node/xnether_blue_wood.json"
},
"meta==3": {
"model": "node/xnether_purple_wood.json"
}
}

View File

@@ -0,0 +1,14 @@
{
"meta==0": {
"model": "node/frame.json"
},
"meta==1": {
"model": "node/grass.json"
},
"meta==2": {
"model": "node/oak_planks.json"
},
"meta==3": {
"model": "node/acacia_planks.json"
}
}

View File

@@ -0,0 +1,14 @@
{
"meta==0": {
"model": "node/jungle_planks.json"
},
"meta==1": {
"model": "node/tropical_rainforest_wood.json"
},
"meta==2": {
"model": "node/willow_wood.json"
},
"meta==3": {
"model": "node/xnether_blue_wood.json"
}
}

View File

@@ -0,0 +1,14 @@
{
"meta==0": {
"model": "node/oak_planks.json"
},
"meta==1": {
"model": "node/jungle_planks.json"
},
"meta==2": {
"model": "node/acacia_planks.json"
},
"meta==3": {
"model": "node/willow_wood.json"
}
}

View File

@@ -0,0 +1,14 @@
{
"meta==0": {
"model": "node/grass.json"
},
"meta==1": {
"model": "node/frame.json"
},
"meta==2": {
"model": "node/xnether_purple_wood.json"
},
"meta==3": {
"model": "node/tropical_rainforest_wood.json"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Some files were not shown because too many files have changed in this diff Show More