Compare commits

..

2 Commits

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

3
.gitignore vendored
View File

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

3
.gitmodules vendored
View File

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

View File

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

Submodule Libs/boost deleted from c89e626766

View File

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

View File

@@ -1,416 +0,0 @@
#include "AssetsManager.hpp"
#include "Common/Abstract.hpp"
#include "sqlite3.h"
#include <chrono>
#include <cstddef>
#include <filesystem>
#include <fstream>
#include <optional>
#include <thread>
#include <utility>
namespace LV::Client {
AssetsManager::AssetsManager(boost::asio::io_context &ioc, const fs::path &cachePath,
size_t maxCacheDirectorySize, size_t maxLifeTime)
: IAsyncDestructible(ioc), CachePath(cachePath)
{
{
auto lock = Changes.lock();
lock->MaxCacheDatabaseSize = maxCacheDirectorySize;
lock->MaxLifeTime = maxLifeTime;
lock->MaxChange = true;
}
if(!fs::exists(PathFiles)) {
LOG.debug() << "Директория для хранения кеша отсутствует, создаём новую '" << CachePath << '\'';
fs::create_directories(PathFiles);
}
LOG.debug() << "Открываем базу данных кеша... (инициализация sqlite3)";
{
int errc = sqlite3_open_v2(PathDatabase.c_str(), &DB, SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE, nullptr);
if(errc)
MAKE_ERROR("Не удалось открыть базу данных " << PathDatabase.c_str() << ": " << sqlite3_errmsg(DB));
const char* sql = R"(
CREATE TABLE IF NOT EXISTS disk_cache(
sha256 BLOB(32) NOT NULL, --
last_used INT NOT NULL, -- unix timestamp
size INT NOT NULL, -- file size
UNIQUE (sha256));
CREATE INDEX IF NOT EXISTS idx__disk_cache__sha256 ON disk_cache(sha256);
CREATE TABLE IF NOT EXISTS inline_cache(
sha256 BLOB(32) NOT NULL, --
last_used INT NOT NULL, -- unix timestamp
data BLOB NOT NULL, -- file data
UNIQUE (sha256));
CREATE INDEX IF NOT EXISTS idx__inline_cache__sha256 ON inline_cache(sha256);
)";
errc = sqlite3_exec(DB, sql, nullptr, nullptr, nullptr);
if(errc != SQLITE_OK) {
MAKE_ERROR("Не удалось подготовить таблицы: " << sqlite3_errmsg(DB));
}
sql = R"(
INSERT OR REPLACE INTO disk_cache (sha256, last_used, size)
VALUES (?, ?, ?);
)";
if(sqlite3_prepare_v2(DB, sql, -1, &STMT_DISK_INSERT, nullptr) != SQLITE_OK) {
MAKE_ERROR("Не удалось подготовить запрос STMT_DISK_INSERT: " << sqlite3_errmsg(DB));
}
sql = R"(
UPDATE disk_cache SET last_used = ? WHERE sha256 = ?;
)";
if(sqlite3_prepare_v2(DB, sql, -1, &STMT_DISK_UPDATE_TIME, nullptr) != SQLITE_OK) {
MAKE_ERROR("Не удалось подготовить запрос STMT_DISK_UPDATE_TIME: " << sqlite3_errmsg(DB));
}
sql = R"(
DELETE FROM disk_cache WHERE sha256=?;
)";
if(sqlite3_prepare_v2(DB, sql, -1, &STMT_DISK_REMOVE, nullptr) != SQLITE_OK) {
MAKE_ERROR("Не удалось подготовить запрос STMT_DISK_REMOVE: " << sqlite3_errmsg(DB));
}
sql = R"(
SELECT 1 FROM disk_cache where sha256=?;
)";
if(sqlite3_prepare_v2(DB, sql, -1, &STMT_DISK_CONTAINS, nullptr) != SQLITE_OK) {
MAKE_ERROR("Не удалось подготовить запрос STMT_DISK_CONTAINS: " << sqlite3_errmsg(DB));
}
sql = R"(
SELECT SUM(size) FROM disk_cache;
)";
if(sqlite3_prepare_v2(DB, sql, -1, &STMT_DISK_SUM, nullptr) != SQLITE_OK) {
MAKE_ERROR("Не удалось подготовить запрос STMT_DISK_SUM: " << sqlite3_errmsg(DB));
}
sql = R"(
SELECT COUNT(*) FROM disk_cache;
)";
if(sqlite3_prepare_v2(DB, sql, -1, &STMT_DISK_COUNT, nullptr) != SQLITE_OK) {
MAKE_ERROR("Не удалось подготовить запрос STMT_DISK_COUNT: " << sqlite3_errmsg(DB));
}
sql = R"(
INSERT OR REPLACE INTO inline_cache (sha256, last_used, data)
VALUES (?, ?, ?);
)";
if(sqlite3_prepare_v2(DB, sql, -1, &STMT_INLINE_INSERT, nullptr) != SQLITE_OK) {
MAKE_ERROR("Не удалось подготовить запрос STMT_INLINE_INSERT: " << sqlite3_errmsg(DB));
}
sql = R"(
SELECT data 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));
}
}
LOG.debug() << "Успешно, запускаем поток обработки";
OffThread = std::thread(&AssetsManager::readWriteThread, this, AUC.use());
LOG.info() << "Инициализировано хранилище кеша: " << CachePath.c_str();
}
AssetsManager::~AssetsManager() {
for(sqlite3_stmt* stmt : {
STMT_DISK_INSERT, STMT_DISK_UPDATE_TIME, STMT_DISK_REMOVE, STMT_DISK_CONTAINS,
STMT_DISK_SUM, STMT_DISK_COUNT, STMT_INLINE_INSERT, STMT_INLINE_GET,
STMT_INLINE_UPDATE_TIME, STMT_INLINE_SUM, STMT_INLINE_COUNT
}) {
if(stmt)
sqlite3_finalize(stmt);
}
if(DB)
sqlite3_close(DB);
OffThread.join();
LOG.info() << "Хранилище кеша закрыто";
}
coro<> AssetsManager::asyncDestructor() {
NeedShutdown = true;
co_await IAsyncDestructible::asyncDestructor();
}
void AssetsManager::readWriteThread(AsyncUseControl::Lock lock) {
try {
std::vector<fs::path> assets;
size_t maxCacheDatabaseSize, maxLifeTime;
while(!NeedShutdown || !WriteQueue.get_read().empty()) {
// Получить новые данные
if(Changes.get_read().AssetsChange) {
auto lock = Changes.lock();
assets = std::move(lock->Assets);
lock->AssetsChange = false;
}
if(Changes.get_read().MaxChange) {
auto lock = Changes.lock();
maxCacheDatabaseSize = lock->MaxCacheDatabaseSize;
maxLifeTime = lock->MaxLifeTime;
lock->MaxChange = false;
}
if(Changes.get_read().FullRecheck) {
std::move_only_function<void(std::string)> onRecheckEnd;
{
auto lock = Changes.lock();
onRecheckEnd = std::move(*lock->OnRecheckEnd);
lock->FullRecheck = false;
}
LOG.info() << "Начата проверка консистентности кеша ассетов";
LOG.info() << "Завершена проверка консистентности кеша ассетов";
}
// Чтение
if(!ReadQueue.get_read().empty()) {
ResourceKey rk;
{
auto lock = ReadQueue.lock();
rk = lock->front();
lock->pop();
}
bool finded = false;
// Сначала пробежимся по ресурспакам
{
std::string_view type;
switch(rk.Type) {
case EnumAssets::Nodestate: type = "nodestate"; break;
case EnumAssets::Particle: type = "particle"; break;
case EnumAssets::Animation: type = "animation"; break;
case EnumAssets::Model: type = "model"; break;
case EnumAssets::Texture: type = "texture"; break;
case EnumAssets::Sound: type = "sound"; break;
case EnumAssets::Font: type = "font"; break;
default:
std::unreachable();
}
for(const fs::path& path : assets) {
fs::path end = path / rk.Domain / type / rk.Key;
if(!fs::exists(end))
continue;
// Нашли
finded = true;
Resource res = Resource(end).convertToMem();
ReadyQueue.lock()->emplace_back(rk, res);
break;
}
}
if(!finded) {
// Поищем в малой базе
sqlite3_bind_blob(STMT_INLINE_GET, 1, (const void*) rk.Hash.data(), 32, SQLITE_STATIC);
int errc = sqlite3_step(STMT_INLINE_GET);
if(errc == SQLITE_ROW) {
// Есть запись
const uint8_t *hash = (const uint8_t*) sqlite3_column_blob(STMT_INLINE_GET, 0);
int size = sqlite3_column_bytes(STMT_INLINE_GET, 0);
Resource res(hash, size);
finded = true;
ReadyQueue.lock()->emplace_back(rk, res);
} else if(errc != SQLITE_DONE) {
sqlite3_reset(STMT_INLINE_GET);
MAKE_ERROR("Не удалось выполнить подготовленный запрос STMT_INLINE_GET: " << sqlite3_errmsg(DB));
}
sqlite3_reset(STMT_INLINE_GET);
if(finded) {
sqlite3_bind_blob(STMT_INLINE_UPDATE_TIME, 1, (const void*) rk.Hash.data(), 32, SQLITE_STATIC);
sqlite3_bind_int(STMT_INLINE_UPDATE_TIME, 2, time(nullptr));
if(sqlite3_step(STMT_INLINE_UPDATE_TIME) != SQLITE_DONE) {
sqlite3_reset(STMT_INLINE_UPDATE_TIME);
MAKE_ERROR("Не удалось выполнить подготовленный запрос STMT_INLINE_UPDATE_TIME: " << sqlite3_errmsg(DB));
}
sqlite3_reset(STMT_INLINE_UPDATE_TIME);
}
}
if(!finded) {
// Поищем на диске
sqlite3_bind_blob(STMT_DISK_CONTAINS, 1, (const void*) rk.Hash.data(), 32, SQLITE_STATIC);
int errc = sqlite3_step(STMT_DISK_CONTAINS);
if(errc == SQLITE_ROW) {
// Есть запись
std::string hashKey;
{
std::stringstream ss;
ss << std::hex << std::setfill('0') << std::setw(2);
for (int i = 0; i < 32; ++i)
ss << static_cast<int>(rk.Hash[i]);
hashKey = ss.str();
}
finded = true;
ReadyQueue.lock()->emplace_back(rk, 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*) rk.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(rk, std::nullopt);
}
continue;
}
// Запись
if(!WriteQueue.get_read().empty()) {
Resource res;
{
auto lock = WriteQueue.lock();
res = lock->front();
lock->pop();
}
// TODO: добавить вычистку места при нехватке
if(res.size() <= SMALL_RESOURCE) {
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);
} 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);
}
continue;
}
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
} catch(const std::exception& exc) {
LOG.warn() << "Ошибка в работе потока:\n" << exc.what();
IssuedAnError = true;
}
}
std::string AssetsManager::hashToString(const Hash_t& hash) {
std::stringstream ss;
ss << std::hex << std::setfill('0');
for (const auto& byte : hash)
ss << std::setw(2) << static_cast<int>(byte);
return ss.str();
}
}

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -6,150 +6,35 @@
#include "Common/Lockable.hpp"
#include "Common/Net.hpp"
#include "Common/Packets.hpp"
#include "TOSAsync.hpp"
#include <TOSLib.hpp>
#include <boost/asio/io_context.hpp>
#include <filesystem>
#include <memory>
#include <boost/lockfree/spsc_queue.hpp>
#include <Client/AssetsManager.hpp>
#include <queue>
#include <unordered_map>
namespace LV::Client {
class ServerSession : public IAsyncDestructible, public IServerSession, public ISurfaceEventListener {
public:
using Ptr = std::shared_ptr<ServerSession>;
struct ParsedPacket {
ToClient::L1 Level1;
uint8_t Level2;
public:
static Ptr Create(asio::io_context &ioc, std::unique_ptr<Net::AsyncSocket> &&socket) {
return createShared(ioc, new ServerSession(ioc, std::move(socket)));
}
virtual ~ServerSession();
// Авторизоваться или (зарегистрироваться и авторизоваться) или зарегистрироваться
static coro<> asyncAuthorizeWithServer(tcp::socket &socket, const std::string username, const std::string token, int a_ar_r, std::function<void(const std::string&)> onProgress = nullptr);
// Начать игровой протокол в авторизированном сокете
static coro<std::unique_ptr<Net::AsyncSocket>> asyncInitGameProtocol(asio::io_context &ioc, tcp::socket &&socket, std::function<void(const std::string&)> onProgress = nullptr);
void shutdown(EnumDisconnect type);
void requestModsReload();
bool isConnected() {
return Socket->isAlive() && IsConnected;
}
// ISurfaceEventListener
virtual void onResize(uint32_t width, uint32_t height) override;
virtual void onChangeFocusState(bool isFocused) override;
virtual void onCursorPosChange(int32_t width, int32_t height) override;
virtual void onCursorMove(float xMove, float yMove) override;
virtual void onCursorBtn(EnumCursorBtn btn, bool state) override;
virtual void onKeyboardBtn(int btn, int state) override;
virtual void onJoystick() override;
// IServerSession
virtual void update(GlobalTime gTime, float dTime) override;
void setRenderSession(IRenderSession* session);
private:
TOS::Logger LOG = "ServerSession";
ParsedPacket(ToClient::L1 l1, uint8_t l2)
: Level1(l1), Level2(l2)
{}
virtual ~ParsedPacket();
};
class ServerSession : public IServerSession, public ISurfaceEventListener {
asio::io_context &IOC;
std::unique_ptr<Net::AsyncSocket> Socket;
IRenderSession *RS = nullptr;
// Обработчик кеша ресурсов сервера
AssetsManager::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;
};
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, void*>> 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;
};
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;
DestroyLock UseLock;
bool IsConnected = true, IsGoingShutdown = false;
TOS::Logger LOG = "ServerSession";
boost::lockfree::spsc_queue<ParsedPacket*> NetInputPackets;
// PYR - поворот камеры по осям xyz в радианах, PYR_Offset для сглаживание поворота
glm::vec3 PYR = glm::vec3(0), PYR_Offset = glm::vec3(0);
double PYR_At = 0;
@@ -169,21 +54,54 @@ private:
GlobalTime LastSendPYR_POS;
// Приём данных с сокета
coro<> run(AsyncUseControl::Lock);
public:
// Нужен сокет, на котором только что был согласован игровой протокол (asyncInitGameProtocol)
ServerSession(asio::io_context &ioc, std::unique_ptr<Net::AsyncSocket> &&socket, IRenderSession *rs = nullptr)
: IOC(ioc), Socket(std::move(socket)), RS(rs), NetInputPackets(1024)
{
assert(Socket.get());
asio::co_spawn(IOC, run(), asio::detached);
}
virtual ~ServerSession();
// Авторизоваться или (зарегистрироваться и авторизоваться) или зарегистрироваться
static coro<> asyncAuthorizeWithServer(tcp::socket &socket, const std::string username, const std::string token, int a_ar_r, std::function<void(const std::string&)> onProgress = nullptr);
// Начать игровой протокол в авторизированном сокете
static coro<std::unique_ptr<Net::AsyncSocket>> asyncInitGameProtocol(asio::io_context &ioc, tcp::socket &&socket, std::function<void(const std::string&)> onProgress = nullptr);
void shutdown(EnumDisconnect type);
bool isConnected() {
return Socket->isAlive() && IsConnected;
}
void waitShutdown() {
UseLock.wait_no_use();
}
// ISurfaceEventListener
virtual void onResize(uint32_t width, uint32_t height) override;
virtual void onChangeFocusState(bool isFocused) override;
virtual void onCursorPosChange(int32_t width, int32_t height) override;
virtual void onCursorMove(float xMove, float yMove) override;
virtual void onCursorBtn(EnumCursorBtn btn, bool state) override;
virtual void onKeyboardBtn(int btn, int state) override;
virtual void onJoystick() override;
virtual void atFreeDrawTime(GlobalTime gTime, float dTime) override;
private:
coro<> run();
void protocolError();
coro<> readPacket(Net::AsyncSocket &sock);
coro<> rP_System(Net::AsyncSocket &sock);
coro<> rP_Resource(Net::AsyncSocket &sock);
coro<> rP_Definition(Net::AsyncSocket &sock);
coro<> rP_Content(Net::AsyncSocket &sock);
// Нужен сокет, на котором только что был согласован игровой протокол (asyncInitGameProtocol)
ServerSession(asio::io_context &ioc, std::unique_ptr<Net::AsyncSocket> &&socket);
virtual coro<> asyncDestructor() override;
void resetResourceSyncState();
};
}
}

View File

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

View File

@@ -1,198 +0,0 @@
#include "PipelinedTextureAtlas.hpp"
PipelinedTextureAtlas::PipelinedTextureAtlas(TextureAtlas&& tk)
: Super(std::move(tk)) {}
PipelinedTextureAtlas::AtlasTextureId PipelinedTextureAtlas::getByPipeline(const HashedPipeline& pipeline) {
auto iter = _PipeToTexId.find(pipeline);
if (iter == _PipeToTexId.end()) {
AtlasTextureId atlasTexId = Super.registerTexture();
_PipeToTexId.insert({pipeline, atlasTexId});
_ChangedPipelines.push_back(pipeline);
for (uint32_t texId : pipeline.getDependencedTextures()) {
_AddictedTextures[texId].push_back(pipeline);
}
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);
}
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.fromWords(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, 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);
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));
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()) {
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();
}
std::optional<StoredTexture> PipelinedTextureAtlas::tryCopyFirstDependencyTexture(const HashedPipeline& pipeline) const {
auto deps = pipeline.getDependencedTextures();
if (!deps.empty()) {
auto iter = _ResToTexture.find(deps.front());
if (iter != _ResToTexture.end()) {
return iter->second;
}
}
return std::nullopt;
}
StoredTexture PipelinedTextureAtlas::makeSolidColorTexture(uint32_t rgba) {
return StoredTexture(1, 1, std::vector<uint32_t>{rgba});
}

View File

@@ -1,380 +0,0 @@
#pragma once
#include "TextureAtlas.hpp"
#include "TexturePipelineProgram.hpp"
#include <algorithm>
#include <cassert>
#include <cstdlib>
#include <cstring>
#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,
Resize = 10,
Transform = 11,
Opacity = 12,
NoAlpha = 13,
MakeAlpha = 14,
Invert = 15,
Brighten = 16,
Contrast = 17,
Multiply = 18,
Screen = 19,
Colorize = 20,
Overlay = 30,
Mask = 31,
LowPart = 32,
Combine = 40
};
enum class SrcKind16 : Word { TexId = 0, Sub = 1 };
struct SrcRef16 {
SrcKind16 kind{SrcKind16::TexId};
Word a = 0;
Word b = 0;
};
inline uint32_t makeU32(Word lo, Word hi) {
return uint32_t(lo) | (uint32_t(hi) << 16);
}
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 readSrc(const std::vector<Word>& words, size_t end, size_t& ip, SrcRef16& out) {
if (ip + 2 >= end) {
return false;
}
out.kind = static_cast<SrcKind16>(words[ip++]);
out.a = words[ip++];
out.b = words[ip++];
return true;
}
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, makeU32(src.a, src.b));
return;
}
if (src.kind == SrcKind16::Sub) {
size_t subStart = static_cast<size_t>(src.a);
size_t subEnd = subStart + static_cast<size_t>(src.b);
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: {
if (!need(3)) return;
SrcRef16 src{};
if (!readSrc(words, end, ip, src)) return;
handleSrc(src);
} break;
case Op16::Base_Fill:
if (!need(4)) return;
ip += 4;
break;
case Op16::Overlay:
case Op16::Mask: {
if (!need(3)) return;
SrcRef16 src{};
if (!readSrc(words, end, ip, src)) return;
handleSrc(src);
} break;
case Op16::LowPart: {
if (!need(1 + 3)) return;
ip += 1; // percent
SrcRef16 src{};
if (!readSrc(words, end, ip, src)) return;
handleSrc(src);
} break;
case Op16::Resize:
if (!need(2)) return;
ip += 2;
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(2)) return;
ip += 2;
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:
if (!need(2)) return;
ip += 2;
break;
case Op16::Colorize:
if (!need(3)) return;
ip += 3;
break;
case Op16::Combine: {
if (!need(3)) return;
ip += 2; // skip w,h
uint32_t n = words[ip++];
for (uint32_t i = 0; i < n; ++i) {
if (!need(2 + 3)) return;
ip += 2; // x, y
SrcRef16 src{};
if (!readSrc(words, end, ip, src)) return;
handleSrc(src);
}
} 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 & 0xFFFFu),
static_cast<detail::Word>((texId >> 16) & 0xFFFFu),
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 & 0xFF);
hash *= prime;
hash ^= static_cast<uint8_t>((w >> 8) & 0xFF);
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;
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();
}
// Должны всегда бронировать идентификатор, либо отдавать 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();
private:
std::optional<StoredTexture> tryCopyFirstDependencyTexture(const HashedPipeline& pipeline) const;
static StoredTexture makeSolidColorTexture(uint32_t rgba);
};

View File

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

View File

@@ -1,477 +0,0 @@
#include "TextureAtlas.hpp"
TextureAtlas::TextureAtlas(VkDevice device,
VkPhysicalDevice physicalDevice,
const Config& cfg,
EventCallback cb,
std::shared_ptr<SharedStagingBuffer> staging)
: Device_(device),
Phys_(physicalDevice),
Cfg_(cfg),
OnEvent_(std::move(cb)),
Staging_(std::move(staging)) {
if(!Device_ || !Phys_) {
throw std::runtime_error("TextureAtlas: device/physicalDevice == null");
}
_validateConfigOrThrow();
VkPhysicalDeviceProperties props{};
vkGetPhysicalDeviceProperties(Phys_, &props);
CopyOffsetAlignment_ = std::max<VkDeviceSize>(4, props.limits.optimalBufferCopyOffsetAlignment);
if(!Staging_) {
Staging_ = std::make_shared<SharedStagingBuffer>(Device_, Phys_, kStagingSizeBytes);
}
_validateStagingCapacityOrThrow();
_createEntriesBufferOrThrow();
_createAtlasOrThrow(Cfg_.InitialSide, 1);
EntriesCpu_.resize(Cfg_.MaxTextureId);
std::memset(EntriesCpu_.data(), 0, EntriesCpu_.size() * sizeof(Entry));
EntriesDirty_ = true;
Slots_.resize(Cfg_.MaxTextureId);
FreeIds_.reserve(Cfg_.MaxTextureId);
PendingInQueue_.assign(Cfg_.MaxTextureId, false);
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(!FreeIds_.empty()) {
id = FreeIds_.back();
FreeIds_.pop_back();
} else if(NextId_ < Cfg_.MaxTextureId) {
id = NextId_++;
} else {
return kOverflowId;
}
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(id == kOverflowId) 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(id == kOverflowId) 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(id == kOverflowId) 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(id == kOverflowId || 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(id == kOverflowId) 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(id == kOverflowId) 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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

@@ -24,45 +24,36 @@ struct PacketQuat {
z = (quat.z+1)/2*0x3ff,
w = (quat.w+1)/2*0x3ff;
uint64_t value = 0;
for(uint8_t &val : Data)
val = 0;
value |= x & 0x3ff;
value |= uint64_t(y & 0x3ff) << 10;
value |= uint64_t(z & 0x3ff) << 20;
value |= uint64_t(w & 0x3ff) << 30;
for(int iter = 0; iter < 5; iter++)
Data[iter] = (value >> (iter*8)) & 0xff;
*(uint16_t*) Data |= x;
*(uint16_t*) (Data+1) |= y << 2;
*(uint16_t*) (Data+2) |= z << 4;
*(uint16_t*) (Data+3) |= w << 6;
}
glm::quat toQuat() const {
uint64_t value = 0;
for(int iter = 0; iter < 5; iter++)
value |= uint64_t(Data[iter]) << (iter*8);
const uint64_t &data = (const uint64_t&) *Data;
uint16_t
x = value & 0x3ff,
y = (value >> 10) & 0x3ff,
z = (value >> 20) & 0x3ff,
w = (value >> 30) & 0x3ff;
x = data & 0x3ff,
y = (data >> 10) & 0x3ff,
z = (data >> 20) & 0x3ff,
w = (data >> 30) & 0x3ff;
float fx = (float(x)/0x3ff)*2-1;
float fy = (float(y)/0x3ff)*2-1;
float fz = (float(z)/0x3ff)*2-1;
float fw = (float(w)/0x3ff)*2-1;
return glm::quat(fw, fx, fy, fz);
return glm::quat(fx, fy, fz, fw);
}
};
/*
uint8_t+uint8_t
0 - Системное
0 -
1 -
2 - Новая позиция камеры WorldId_c+ObjectPos+PacketQuat
3 - Изменение блока
0 - Новая позиция камеры WorldId_c+ObjectPos+PacketQuat
*/
@@ -75,10 +66,7 @@ enum struct L1 : uint8_t {
enum struct L2System : uint8_t {
InitEnd,
Disconnect,
Test_CAM_PYR_POS,
BlockChange,
ResourceRequest,
ReloadMods
Test_CAM_PYR_POS
};
}
@@ -142,15 +130,19 @@ enum struct L2System : uint8_t {
Init,
Disconnect,
LinkCameraToEntity,
UnlinkCamera,
SyncTick
UnlinkCamera
};
enum struct L2Resource : uint8_t {
Bind, // Привязка идентификаторов ресурсов к хешам
Lost,
Texture,
FreeTexture,
Sound,
FreeSound,
Model,
FreeModel,
InitResSend = 253,
ChunkSend
ChunkSend,
SendCanceled
};
enum struct L2Definition : uint8_t {
@@ -163,11 +155,7 @@ enum struct L2Definition : uint8_t {
Portal,
FreePortal,
Entity,
FreeEntity,
FuncEntity,
FreeFuncEntity,
Item,
FreeItem
FreeEntity
};
enum struct L2Content : uint8_t {
@@ -180,9 +168,9 @@ enum struct L2Content : uint8_t {
ChunkVoxels,
ChunkNodes,
ChunkLightPrism,
RemoveRegion
RemoveChunk
};
}
}
}

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

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

View File

@@ -1,17 +0,0 @@
#include "Abstract.hpp"
#include <csignal>
namespace LV::Server {
}
namespace std {
template <>
struct hash<LV::Server::ServerObjectPos> {
std::size_t operator()(const LV::Server::ServerObjectPos& obj) const {
return std::hash<uint32_t>()(obj.WorldId) ^ std::hash<LV::Pos::Object>()(obj.ObjectPos);
}
};
}

View File

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

View File

@@ -1,737 +0,0 @@
#include "AssetsManager.hpp"
#include "Common/Abstract.hpp"
#include "boost/json.hpp"
#include "png++/rgb_pixel.hpp"
#include <algorithm>
#include <exception>
#include <filesystem>
#include <png.h>
#include <pngconf.h>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include "sol/sol.hpp"
namespace LV::Server {
PreparedModel::PreparedModel(const std::string& domain, const LV::PreparedModel& model) {
Cuboids.reserve(model.Cuboids.size());
for(auto& [key, cmd] : model.Textures) {
for(auto& [domain, key] : cmd.Assets) {
TextureDependencies[domain].push_back(key);
}
}
for(auto& sub : model.SubModels) {
ModelDependencies[sub.Domain].push_back(sub.Key);
}
// for(const PreparedModel::Cuboid& cuboid : model.Cuboids) {
// Cuboid result;
// result.From = cuboid.From;
// result.To = cuboid.To;
// result.Faces = 0;
// for(const auto& [key, _] : cuboid.Faces)
// result.Faces |= (1 << int(key));
// result.Transformations = cuboid.Transformations;
// }
}
PreparedModel::PreparedModel(const std::string& domain, const PreparedGLTF& glTF) {
}
void AssetsManager::loadResourceFromFile(EnumAssets type, ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const {
switch(type) {
case EnumAssets::Nodestate: loadResourceFromFile_Nodestate (out, domain, key, path); return;
case EnumAssets::Particle: loadResourceFromFile_Particle (out, domain, key, path); return;
case EnumAssets::Animation: loadResourceFromFile_Animation (out, domain, key, path); return;
case EnumAssets::Model: loadResourceFromFile_Model (out, domain, key, path); return;
case EnumAssets::Texture: loadResourceFromFile_Texture (out, domain, key, path); return;
case EnumAssets::Sound: loadResourceFromFile_Sound (out, domain, key, path); return;
case EnumAssets::Font: loadResourceFromFile_Font (out, domain, key, path); return;
default:
std::unreachable();
}
}
void AssetsManager::loadResourceFromLua(EnumAssets type, ResourceChangeObj& out, const std::string& domain, const std::string& key, const sol::table& profile) const {
switch(type) {
case EnumAssets::Nodestate: loadResourceFromLua_Nodestate(out, domain, key, profile); return;
case EnumAssets::Particle: loadResourceFromLua_Particle(out, domain, key, profile); return;
case EnumAssets::Animation: loadResourceFromLua_Animation(out, domain, key, profile); return;
case EnumAssets::Model: loadResourceFromLua_Model(out, domain, key, profile); return;
case EnumAssets::Texture: loadResourceFromLua_Texture(out, domain, key, profile); return;
case EnumAssets::Sound: loadResourceFromLua_Sound(out, domain, key, profile); return;
case EnumAssets::Font: loadResourceFromLua_Font(out, domain, key, profile); return;
default:
std::unreachable();
}
}
void AssetsManager::loadResourceFromFile_Nodestate(ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const {
Resource res(path);
js::object obj = js::parse(std::string_view((const char*) res.data(), res.size())).as_object();
PreparedNodeState pns(domain, obj);
out.NewOrChange_Nodestates[domain].emplace_back(key, std::move(pns), fs::last_write_time(path));
}
void AssetsManager::loadResourceFromFile_Particle(ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const {
std::unreachable();
}
void AssetsManager::loadResourceFromFile_Animation(ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const {
std::unreachable();
}
void AssetsManager::loadResourceFromFile_Model(ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const {
/*
json, glTF, glB
*/
Resource res(path);
std::filesystem::file_time_type ftt = fs::last_write_time(path);
auto extension = path.extension();
if(extension == ".json") {
js::object obj = js::parse(std::string_view((const char*) res.data(), res.size())).as_object();
LV::PreparedModel pm(domain, obj);
out.NewOrChange_Models[domain].emplace_back(key, std::move(pm), ftt);
} else if(extension == ".gltf") {
js::object obj = js::parse(std::string_view((const char*) res.data(), res.size())).as_object();
PreparedGLTF gltf(domain, obj);
out.NewOrChange_Models[domain].emplace_back(key, std::move(gltf), ftt);
} else if(extension == ".glb") {
PreparedGLTF gltf(domain, res);
out.NewOrChange_Models[domain].emplace_back(key, std::move(gltf), ftt);
} else {
MAKE_ERROR("Не поддерживаемый формат файла");
}
}
void AssetsManager::loadResourceFromFile_Texture(ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const {
Resource res(path);
if(res.size() < 8)
MAKE_ERROR("Файл не является текстурой png или jpeg (недостаточный размер файла)");
if(png_check_sig(reinterpret_cast<png_bytep>((unsigned char*) res.data()), 8)) {
// Это png
fs::file_time_type lwt = fs::last_write_time(path);
out.NewOrChange[(int) EnumAssets::Texture][domain].emplace_back(key, res, lwt);
return;
} else if((int) res.data()[0] == 0xFF && (int) res.data()[1] == 0xD8) {
// Это jpeg
fs::file_time_type lwt = fs::last_write_time(path);
out.NewOrChange[(int) EnumAssets::Texture][domain].emplace_back(key, res, lwt);
return;
} else {
MAKE_ERROR("Файл не является текстурой png или jpeg");
}
std::unreachable();
}
void AssetsManager::loadResourceFromFile_Sound(ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const {
std::unreachable();
}
void AssetsManager::loadResourceFromFile_Font(ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const {
std::unreachable();
}
void AssetsManager::loadResourceFromLua_Nodestate(ResourceChangeObj& out, const std::string& domain, const std::string& key, const sol::table& profile) const {
if(std::optional<std::string> path = profile.get<std::optional<std::string>>("path")) {
out.NewOrChange[(int) EnumAssets::Nodestate][domain].emplace_back(key, Resource(*path), fs::file_time_type::min());
return;
}
std::unreachable();
}
void AssetsManager::loadResourceFromLua_Particle(ResourceChangeObj& out, const std::string& domain, const std::string& key, const sol::table& profile) const {
if(std::optional<std::string> path = profile.get<std::optional<std::string>>("path")) {
out.NewOrChange[(int) EnumAssets::Particle][domain].emplace_back(key, Resource(*path), fs::file_time_type::min());
return;
}
std::unreachable();
}
void AssetsManager::loadResourceFromLua_Animation(ResourceChangeObj& out, const std::string& domain, const std::string& key, const sol::table& profile) const {
if(std::optional<std::string> path = profile.get<std::optional<std::string>>("path")) {
out.NewOrChange[(int) EnumAssets::Animation][domain].emplace_back(key, Resource(*path), fs::file_time_type::min());
return;
}
std::unreachable();
}
void AssetsManager::loadResourceFromLua_Model(ResourceChangeObj& out, const std::string& domain, const std::string& key, const sol::table& profile) const {
if(std::optional<std::string> path = profile.get<std::optional<std::string>>("path")) {
out.NewOrChange[(int) EnumAssets::Model][domain].emplace_back(key, Resource(*path), fs::file_time_type::min());
return;
}
std::unreachable();
}
void AssetsManager::loadResourceFromLua_Texture(ResourceChangeObj& out, const std::string& domain, const std::string& key, const sol::table& profile) const {
if(std::optional<std::string> path = profile.get<std::optional<std::string>>("path")) {
out.NewOrChange[(int) EnumAssets::Texture][domain].emplace_back(key, Resource(*path), fs::file_time_type::min());
return;
}
std::unreachable();
}
void AssetsManager::loadResourceFromLua_Sound(ResourceChangeObj& out, const std::string& domain, const std::string& key, const sol::table& profile) const {
if(std::optional<std::string> path = profile.get<std::optional<std::string>>("path")) {
out.NewOrChange[(int) EnumAssets::Sound][domain].emplace_back(key, Resource(*path), fs::file_time_type::min());
return;
}
std::unreachable();
}
void AssetsManager::loadResourceFromLua_Font(ResourceChangeObj& out, const std::string& domain, const std::string& key, const sol::table& profile) const {
if(std::optional<std::string> path = profile.get<std::optional<std::string>>("path")) {
out.NewOrChange[(int) EnumAssets::Font][domain].emplace_back(key, Resource(*path), fs::file_time_type::min());
return;
}
std::unreachable();
}
AssetsManager::AssetsManager(asio::io_context& ioc)
{
}
AssetsManager::~AssetsManager() = default;
std::tuple<ResourceId, std::optional<AssetsManager::DataEntry>&> AssetsManager::Local::nextId(EnumAssets type) {
auto& table = Table[(int) type];
ResourceId id = -1;
std::optional<DataEntry> *data = nullptr;
for(size_t index = 0; index < table.size(); index++) {
auto& entry = *table[index];
if(index == 0 && entry.Empty.test(0)) {
entry.Empty.reset(0);
}
if(entry.IsFull)
continue;
uint32_t pos = entry.Empty._Find_first();
if(pos == entry.Empty.size()) {
entry.IsFull = true;
continue;
}
entry.Empty.reset(pos);
if(entry.Empty._Find_next(pos) == entry.Empty.size())
entry.IsFull = true;
id = index*TableEntry<DataEntry>::ChunkSize + pos;
data = &entry.Entries[pos];
break;
}
if(!data) {
table.emplace_back(std::make_unique<TableEntry<DataEntry>>());
auto& entry = *table.back();
if(table.size() == 1 && entry.Empty.test(0)) {
entry.Empty.reset(0);
}
uint32_t pos = entry.Empty._Find_first();
entry.Empty.reset(pos);
if(entry.Empty._Find_next(pos) == entry.Empty.size())
entry.IsFull = true;
id = (table.size()-1)*TableEntry<DataEntry>::ChunkSize + pos;
data = &entry.Entries[pos];
// Расширяем таблицу с ресурсами, если необходимо
if(type == EnumAssets::Nodestate)
Table_NodeState.emplace_back(std::make_unique<TableEntry<std::vector<AssetsModel>>>());
else if(type == EnumAssets::Model)
Table_Model.emplace_back(std::make_unique<TableEntry<ModelDependency>>());
}
return {id, *data};
}
AssetsManager::ResourceChangeObj AssetsManager::recheckResources(const AssetsRegister& info) {
ResourceChangeObj result;
// Найти пропавшие ресурсы
for(int type = 0; type < (int) EnumAssets::MAX_ENUM; type++) {
auto lock = LocalObj.lock();
for(auto& [domain, resources] : lock->KeyToId[type]) {
for(auto& [key, id] : resources) {
if(!lock->Table[type][id / TableEntry<DataEntry>::ChunkSize]->Entries[id % TableEntry<DataEntry>::ChunkSize])
continue;
bool exists = false;
for(const fs::path& path : info.Assets) {
fs::path file = path / domain;
switch ((EnumAssets) type) {
case EnumAssets::Nodestate: file /= "nodestate"; break;
case EnumAssets::Particle: file /= "particle"; break;
case EnumAssets::Animation: file /= "animation"; break;
case EnumAssets::Model: file /= "model"; break;
case EnumAssets::Texture: file /= "texture"; break;
case EnumAssets::Sound: file /= "sound"; break;
case EnumAssets::Font: file /= "font"; break;
default:
std::unreachable();
}
file /= key;
if(fs::exists(file) && !fs::is_directory(file)) {
exists = true;
break;
}
}
if(exists) continue;
auto iterDomain = info.Custom[type].find(domain);
if(iterDomain == info.Custom[type].end()) {
result.Lost[type][domain].push_back(key);
} else {
auto iterData = iterDomain->second.find(key);
if(iterData == iterDomain->second.end()) {
result.Lost[type][domain].push_back(key);
}
}
}
}
}
// Если ресурс уже был найден более приоритетными директориями, то пропускаем его
std::unordered_map<std::string, std::unordered_set<std::string>> findedResources[(int) EnumAssets::MAX_ENUM];
// Найти новые или изменённые ресурсы
for(int type = 0; type < (int) EnumAssets::MAX_ENUM; type++) {
for(auto& [domain, resources] : info.Custom[type]) {
auto lock = LocalObj.lock();
const auto& keyToId = lock->KeyToId[type];
auto iterDomain = keyToId.find(domain);
auto& findList = findedResources[type][domain];
if(iterDomain == keyToId.end()) {
// Ресурсы данного домена неизвестны
auto& domainList = result.NewOrChange[type][domain];
for(auto& [key, id] : resources) {
// Подобрать идентификатор
// TODO: реализовать регистрации ресурсов из lua
domainList.emplace_back(key, Resource("assets/null"), fs::file_time_type::min());
findList.insert(key);
}
} else {
for(auto& [key, id] : resources) {
if(findList.contains(key))
// Ресурс уже был найден в вышестоящей директории
continue;
else if(iterDomain->second.contains(key)) {
// Ресурс уже есть, TODO: нужно проверить его изменение
loadResourceFromFile((EnumAssets) type, result, domain, key, "assets/null");
} else {
// Ресурс не был известен
loadResourceFromFile((EnumAssets) type, result, domain, key, "assets/null");
}
findList.insert(key);
}
}
}
}
for(const fs::path& path : info.Assets) {
if(!fs::exists(path))
continue;
for(auto begin = fs::directory_iterator(path), end = fs::directory_iterator(); begin != end; begin++) {
if(!begin->is_directory())
continue;
fs::path domainPath = begin->path();
std::string domain = domainPath.filename();
for(int type = 0; type < (int) EnumAssets::MAX_ENUM; type++) {
fs::path resourcesPath = domainPath;
switch ((EnumAssets) type) {
case EnumAssets::Nodestate: resourcesPath /= "nodestate"; break;
case EnumAssets::Particle: resourcesPath /= "particle"; break;
case EnumAssets::Animation: resourcesPath /= "animation"; break;
case EnumAssets::Model: resourcesPath /= "model"; break;
case EnumAssets::Texture: resourcesPath /= "texture"; break;
case EnumAssets::Sound: resourcesPath /= "sound"; break;
case EnumAssets::Font: resourcesPath /= "font"; break;
default:
std::unreachable();
}
auto& findList = findedResources[type][domain];
auto lock = LocalObj.lock();
auto iterDomain = lock->KeyToId[type].find(domain);
if(!fs::exists(resourcesPath) || !fs::is_directory(resourcesPath))
continue;
// Рекурсивно загрузить ресурсы внутри папки resourcesPath
for(auto begin = fs::recursive_directory_iterator(resourcesPath), end = fs::recursive_directory_iterator(); begin != end; begin++) {
if(begin->is_directory())
continue;
fs::path file = begin->path();
std::string key = fs::relative(begin->path(), resourcesPath).string();
if(findList.contains(key))
// Ресурс уже был найден в вышестоящей директории
continue;
else if(iterDomain != lock->KeyToId[type].end() && iterDomain->second.contains(key)) {
// Ресурс уже есть, TODO: нужно проверить его изменение
ResourceId id = iterDomain->second.at(key);
DataEntry& entry = *lock->Table[type][id / TableEntry<DataEntry>::ChunkSize]->Entries[id % TableEntry<DataEntry>::ChunkSize];
fs::file_time_type lwt = fs::last_write_time(file);
if(lwt != entry.FileChangeTime)
// Будем считать что ресурс изменился
loadResourceFromFile((EnumAssets) type, result, domain, key, file);
} else {
// Ресурс не был известен
loadResourceFromFile((EnumAssets) type, result, domain, key, file);
}
findList.insert(key);
}
}
}
}
return result;
}
AssetsManager::Out_applyResourceChange AssetsManager::applyResourceChange(const ResourceChangeObj& orr) {
// Потерянные и обновлённые идентификаторы
Out_applyResourceChange result;
// Удаляем ресурсы
/*
Удаляются только ресурсы, при этом за ними остаётся бронь на идентификатор
Уже скомпилированные зависимости к ресурсам не будут
перекомпилироваться для смены идентификатора. Если нужный ресурс
появится, то привязка останется. Новые клиенты не получат ресурс
которого нет, но он может использоваться
*/
for(int type = 0; type < (int) EnumAssets::MAX_ENUM; type++) {
for(auto& [domain, resources] : orr.Lost[type]) {
auto lock = LocalObj.lock();
auto& keyToIdDomain = lock->KeyToId[type].at(domain);
for(const std::string& key : resources) {
auto iter = keyToIdDomain.find(key);
assert(iter != keyToIdDomain.end());
ResourceId resId = iter->second;
if(type == (int) EnumAssets::Nodestate) {
if(resId / TableEntry<PreparedNodeState>::ChunkSize < lock->Table_NodeState.size()) {
lock->Table_NodeState[resId / TableEntry<PreparedNodeState>::ChunkSize]
->Entries[resId % TableEntry<PreparedNodeState>::ChunkSize].reset();
}
} else if(type == (int) EnumAssets::Model) {
if(resId / TableEntry<ModelDependency>::ChunkSize < lock->Table_Model.size()) {
lock->Table_Model[resId / TableEntry<ModelDependency>::ChunkSize]
->Entries[resId % TableEntry<ModelDependency>::ChunkSize].reset();
}
}
auto& chunk = lock->Table[type][resId / TableEntry<DataEntry>::ChunkSize];
chunk->Entries[resId % TableEntry<DataEntry>::ChunkSize].reset();
}
}
}
// Добавляем
for(int type = 0; type < (int) EnumAssets::MAX_ENUM; type++) {
for(auto& [domain, resources] : orr.NewOrChange[type]) {
auto lock = LocalObj.lock();
auto& keyToIdDomain = lock->KeyToId[type][domain];
for(auto& [key, resource, lwt] : resources) {
ResourceId id = -1;
std::optional<DataEntry>* data = nullptr;
if(auto iterId = keyToIdDomain.find(key); iterId != keyToIdDomain.end()) {
id = iterId->second;
data = &lock->Table[(int) type][id / TableEntry<DataEntry>::ChunkSize]->Entries[id % TableEntry<DataEntry>::ChunkSize];
} else {
auto [_id, _data] = lock->nextId((EnumAssets) type);
id = _id;
data = &_data;
}
result.NewOrChange[type].push_back({id, resource});
keyToIdDomain[key] = id;
data->emplace(lwt, resource, domain, key);
lock->HashToId[resource.hash()] = {(EnumAssets) type, id};
}
}
// Удалённые идентификаторы не считаются удалёнными, если были изменены
std::unordered_set<ResourceId> noc;
for(auto& [id, _] : result.NewOrChange[type])
noc.insert(id);
std::unordered_set<ResourceId> l(result.Lost[type].begin(), result.Lost[type].end());
result.Lost[type].clear();
std::set_difference(l.begin(), l.end(), noc.begin(), noc.end(), std::back_inserter(result.Lost[type]));
}
// Приёмка новых/изменённых описаний состояний нод
if(!orr.NewOrChange_Nodestates.empty())
{
auto lock = LocalObj.lock();
for(auto& [domain, table] : orr.NewOrChange_Nodestates) {
for(auto& [key, _nodestate, ftt] : table) {
ResourceId resId = lock->getId(EnumAssets::Nodestate, domain, key);
std::optional<DataEntry>& data = lock->Table[(int) EnumAssets::Nodestate][resId / TableEntry<DataEntry>::ChunkSize]->Entries[resId % TableEntry<DataEntry>::ChunkSize];
PreparedNodeState nodestate = _nodestate;
// Ресолвим модели
for(const auto& [lDomain, lKey] : nodestate.LocalToModelKD) {
nodestate.LocalToModel.push_back(lock->getId(EnumAssets::Model, lDomain, lKey));
}
// Сдампим для отправки клиенту (Кеш в пролёте?)
Resource res(nodestate.dump());
// На оповещение
result.NewOrChange[(int) EnumAssets::Nodestate].push_back({resId, res});
// Запись в таблице ресурсов
data.emplace(ftt, res, domain, key);
lock->HashToId[res.hash()] = {EnumAssets::Nodestate, resId};
lock->Table_NodeState[resId / TableEntry<DataEntry>::ChunkSize]
->Entries[resId % TableEntry<DataEntry>::ChunkSize] = nodestate.LocalToModel;
}
}
}
// Приёмка новых/изменённых моделей
if(!orr.NewOrChange_Models.empty())
{
auto lock = LocalObj.lock();
for(auto& [domain, table] : orr.NewOrChange_Models) {
auto& keyToIdDomain = lock->KeyToId[(int) EnumAssets::Model][domain];
for(auto& [key, _model, ftt] : table) {
ResourceId resId = -1;
std::optional<DataEntry>* data = nullptr;
if(auto iterId = keyToIdDomain.find(key); iterId != keyToIdDomain.end()) {
resId = iterId->second;
data = &lock->Table[(int) EnumAssets::Model][resId / TableEntry<DataEntry>::ChunkSize]->Entries[resId % TableEntry<DataEntry>::ChunkSize];
} else {
auto [_id, _data] = lock->nextId((EnumAssets) EnumAssets::Model);
resId = _id;
data = &_data;
}
keyToIdDomain[key] = resId;
// Ресолвим текстуры
std::variant<LV::PreparedModel, PreparedGLTF> model = _model;
std::visit([&lock](auto& val) {
for(const auto& [key, pipeline] : val.Textures) {
TexturePipeline pipe;
pipe.Pipeline = pipeline.Pipeline;
for(const auto& [domain, key] : pipeline.Assets) {
ResourceId texId = lock->getId(EnumAssets::Texture, domain, key);
pipe.BinTextures.push_back(texId);
}
val.CompiledTextures[key] = std::move(pipe);
}
}, model);
// Сдампим для отправки клиенту (Кеш в пролёте?)
std::u8string dump = std::visit<std::u8string>([&lock](auto& val) {
return val.dump();
}, model);
Resource res(std::move(dump));
// На оповещение
result.NewOrChange[(int) EnumAssets::Model].push_back({resId, res});
// Запись в таблице ресурсов
data->emplace(ftt, res, domain, key);
lock->HashToId[res.hash()] = {EnumAssets::Model, resId};
// Для нужд сервера, ресолвим зависимости
PreparedModel pm = std::visit<PreparedModel>([&domain](auto& val) {
return PreparedModel(domain, val);
}, model);
ModelDependency deps;
for(auto& [domain2, list] : pm.ModelDependencies) {
for(const std::string& key2 : list) {
ResourceId subResId = lock->getId(EnumAssets::Model, domain2, key2);
deps.ModelDeps.push_back(subResId);
}
}
for(auto& [domain2, list] : pm.TextureDependencies) {
for(const std::string& key2 : list) {
ResourceId subResId = lock->getId(EnumAssets::Texture, domain2, key2);
deps.TextureDeps.push_back(subResId);
}
}
lock->Table_Model[resId / TableEntry<DataEntry>::ChunkSize]
->Entries[resId % TableEntry<DataEntry>::ChunkSize] = std::move(deps);
}
}
}
// Дамп ключей assets
{
std::stringstream result;
auto lock = LocalObj.lock();
for(int type = 0; type < (int) EnumAssets::MAX_ENUM; type++) {
if(type == 0)
result << "Nodestate:\n";
else if(type == 1)
result << "Particle:\n";
else if(type == 2)
result << "Animation:\n";
else if(type == 3)
result << "Model:\n";
else if(type == 4)
result << "Texture:\n";
else if(type == 5)
result << "Sound:\n";
else if(type == 6)
result << "Font:\n";
for(const auto& [domain, list] : lock->KeyToId[type]) {
result << "\t" << domain << ":\n";
for(const auto& [key, id] : list) {
result << "\t\t" << key << " = " << id << '\n';
}
}
}
LOG.debug() << "Дамп ассетов:\n" << result.str();
}
// Вычислить зависимости моделей
{
// Затираем старые данные
auto lock = LocalObj.lock();
for(auto& entriesChunk : lock->Table_Model) {
for(auto& entry : entriesChunk->Entries) {
if(!entry)
continue;
entry->Ready = false;
entry->FullSubTextureDeps.clear();
entry->FullSubModelDeps.clear();
}
}
// Вычисляем зависимости
std::function<void(AssetsModel resId, ModelDependency&)> calcDeps;
calcDeps = [&](AssetsModel resId, ModelDependency& entry) {
for(AssetsModel subResId : entry.ModelDeps) {
auto& model = lock->Table_Model[subResId / TableEntry<ModelDependency>::ChunkSize]
->Entries[subResId % TableEntry<ModelDependency>::ChunkSize];
if(!model)
continue;
if(resId == subResId) {
const auto object1 = lock->getResource(EnumAssets::Model, resId);
const auto object2 = lock->getResource(EnumAssets::Model, subResId);
LOG.warn() << "В моделе " << std::get<1>(*object1) << ':' << std::get<2>(*object1)
<< " обнаружена циклическая зависимость с самой собою";
continue;
}
if(!model->Ready)
calcDeps(subResId, *model);
if(std::binary_search(model->FullSubModelDeps.begin(), model->FullSubModelDeps.end(), resId)) {
// Циклическая зависимость
const auto object1 = lock->getResource(EnumAssets::Model, resId);
const auto object2 = lock->getResource(EnumAssets::Model, subResId);
assert(object1);
LOG.warn() << "В моделе " << std::get<1>(*object1) << ':' << std::get<2>(*object1)
<< " обнаружена циклическая зависимость с " << std::get<1>(*object2) << ':'
<< std::get<2>(*object2);
} else {
entry.FullSubTextureDeps.append_range(model->FullSubTextureDeps);
entry.FullSubModelDeps.push_back(subResId);
entry.FullSubModelDeps.append_range(model->FullSubModelDeps);
}
}
entry.FullSubTextureDeps.append_range(entry.TextureDeps);
{
std::sort(entry.FullSubTextureDeps.begin(), entry.FullSubTextureDeps.end());
auto eraseIter = std::unique(entry.FullSubTextureDeps.begin(), entry.FullSubTextureDeps.end());
entry.FullSubTextureDeps.erase(eraseIter, entry.FullSubTextureDeps.end());
entry.FullSubTextureDeps.shrink_to_fit();
}
{
std::sort(entry.FullSubModelDeps.begin(), entry.FullSubModelDeps.end());
auto eraseIter = std::unique(entry.FullSubModelDeps.begin(), entry.FullSubModelDeps.end());
entry.FullSubModelDeps.erase(eraseIter, entry.FullSubModelDeps.end());
entry.FullSubModelDeps.shrink_to_fit();
}
entry.Ready = true;
};
ssize_t iter = -1;
for(auto& entriesChunk : lock->Table_Model) {
for(auto& entry : entriesChunk->Entries) {
iter++;
if(!entry || entry->Ready)
continue;
// Собираем зависимости
calcDeps(iter, *entry);
}
}
}
return result;
}
}

View File

@@ -1,308 +0,0 @@
#pragma once
#include "Common/Abstract.hpp"
#include "TOSLib.hpp"
#include "Common/Net.hpp"
#include "sha2.hpp"
#include <bitset>
#include <filesystem>
#include <optional>
#include <unordered_map>
#include <unordered_set>
#include <variant>
namespace LV::Server {
namespace fs = std::filesystem;
/*
Используется для расчёта коллизии,
если это необходимо, а также зависимостей к ассетам.
*/
struct PreparedModel {
// Упрощённая коллизия
std::vector<std::pair<glm::vec3, glm::vec3>> Cuboids;
// Зависимости от текстур, которые нужно сообщить клиенту
std::unordered_map<std::string, std::vector<std::string>> TextureDependencies;
// Зависимости от моделей
std::unordered_map<std::string, std::vector<std::string>> ModelDependencies;
PreparedModel(const std::string& domain, const LV::PreparedModel& model);
PreparedModel(const std::string& domain, const PreparedGLTF& glTF);
PreparedModel() = default;
PreparedModel(const PreparedModel&) = default;
PreparedModel(PreparedModel&&) = default;
PreparedModel& operator=(const PreparedModel&) = default;
PreparedModel& operator=(PreparedModel&&) = default;
};
struct ModelDependency {
// Прямые зависимости к тестурам и моделям
std::vector<AssetsTexture> TextureDeps;
std::vector<AssetsModel> ModelDeps;
// Коллизия
std::vector<std::pair<glm::vec3, glm::vec3>> Cuboids;
//
bool Ready = false;
// Полный список зависимостей рекурсивно
std::vector<AssetsTexture> FullSubTextureDeps;
std::vector<AssetsModel> FullSubModelDeps;
};
/*
Работает с ресурсами из папок assets.
Использует папку server_cache/assets для хранения
преобразованных ресурсов
*/
class AssetsManager {
public:
struct ResourceChangeObj {
// Потерянные ресурсы
std::unordered_map<std::string, std::vector<std::string>> Lost[(int) EnumAssets::MAX_ENUM];
// Домен и ключ ресурса
std::unordered_map<std::string, std::vector<std::tuple<std::string, Resource, fs::file_time_type>>> NewOrChange[(int) EnumAssets::MAX_ENUM];
std::unordered_map<std::string, std::vector<std::tuple<std::string, PreparedNodeState, fs::file_time_type>>> NewOrChange_Nodestates;
std::unordered_map<std::string, std::vector<std::tuple<std::string, std::variant<
LV::PreparedModel,
PreparedGLTF
>, fs::file_time_type>>> NewOrChange_Models;
// std::unordered_map<std::string, std::vector<std::pair<std::string, PreparedModel>>> Models;
};
private:
// Данные об отслеживаемых файлах
struct DataEntry {
// Время последнего изменения файла
fs::file_time_type FileChangeTime;
Resource Res;
std::string Domain, Key;
};
template<typename T>
struct TableEntry {
static constexpr size_t ChunkSize = 4096;
bool IsFull = false;
std::bitset<ChunkSize> Empty;
std::array<std::optional<T>, ChunkSize> Entries;
TableEntry() {
Empty.set();
}
};
struct Local {
// Связь ресурсов по идентификаторам
std::vector<std::unique_ptr<TableEntry<DataEntry>>> Table[(int) EnumAssets::MAX_ENUM];
// Распаршенные ресурсы, для использования сервером (сбор зависимостей профиля нод и расчёт коллизии если нужно)
// Первичные зависимости Nodestate к моделям
std::vector<std::unique_ptr<TableEntry<std::vector<AssetsModel>>>> Table_NodeState;
// Упрощённые модели для коллизии
std::vector<std::unique_ptr<TableEntry<ModelDependency>>> Table_Model;
// Связь домены -> {ключ -> идентификатор}
std::unordered_map<std::string, std::unordered_map<std::string, ResourceId>> KeyToId[(int) EnumAssets::MAX_ENUM];
std::unordered_map<Hash_t, std::tuple<EnumAssets, ResourceId>> HashToId;
std::tuple<ResourceId, std::optional<DataEntry>&> nextId(EnumAssets type);
ResourceId getId(EnumAssets type, const std::string& domain, const std::string& key) {
auto& keyToId = KeyToId[(int) type];
if(auto iterKTI = keyToId.find(domain); iterKTI != keyToId.end()) {
if(auto iterKey = iterKTI->second.find(key); iterKey != iterKTI->second.end()) {
return iterKey->second;
}
}
auto [id, entry] = nextId(type);
keyToId[domain][key] = id;
return id;
}
std::optional<std::tuple<Resource, const std::string&, const std::string&>> getResource(EnumAssets type, ResourceId id) {
assert(id < Table[(int) type].size()*TableEntry<DataEntry>::ChunkSize);
auto& value = Table[(int) type][id / TableEntry<DataEntry>::ChunkSize]->Entries[id % TableEntry<DataEntry>::ChunkSize];
if(value)
return {{value->Res, value->Domain, value->Key}};
else
return std::nullopt;
}
std::optional<std::tuple<Resource, const std::string&, const std::string&, EnumAssets, ResourceId>> getResource(const Hash_t& hash) {
auto iter = HashToId.find(hash);
if(iter == HashToId.end())
return std::nullopt;
auto [type, id] = iter->second;
std::optional<std::tuple<Resource, const std::string&, const std::string&>> res = getResource(type, id);
if(!res) {
HashToId.erase(iter);
return std::nullopt;
}
if(std::get<Resource>(*res).hash() == hash) {
auto& [resource, domain, key] = *res;
return std::tuple<Resource, const std::string&, const std::string&, EnumAssets, ResourceId>{resource, domain, key, type, id};
}
HashToId.erase(iter);
return std::nullopt;
}
const std::optional<std::vector<AssetsModel>>& getResourceNodestate(ResourceId id) {
assert(id < Table_NodeState.size()*TableEntry<DataEntry>::ChunkSize);
return Table_NodeState[id / TableEntry<DataEntry>::ChunkSize]
->Entries[id % TableEntry<DataEntry>::ChunkSize];
}
const std::optional<ModelDependency>& getResourceModel(ResourceId id) {
assert(id < Table_Model.size()*TableEntry<DataEntry>::ChunkSize);
return Table_Model[id / TableEntry<DataEntry>::ChunkSize]
->Entries[id % TableEntry<DataEntry>::ChunkSize];
}
};
TOS::SpinlockObject<Local> LocalObj;
/*
Загрузка ресурса с файла. При необходимости приводится
к внутреннему формату и сохраняется в кеше
*/
void loadResourceFromFile (EnumAssets type, ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const;
void loadResourceFromLua (EnumAssets type, ResourceChangeObj& out, const std::string& domain, const std::string& key, const sol::table& profile) const;
void loadResourceFromFile_Nodestate (ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const;
void loadResourceFromFile_Particle (ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const;
void loadResourceFromFile_Animation (ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const;
void loadResourceFromFile_Model (ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const;
void loadResourceFromFile_Texture (ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const;
void loadResourceFromFile_Sound (ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const;
void loadResourceFromFile_Font (ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const;
void loadResourceFromLua_Nodestate (ResourceChangeObj& out, const std::string& domain, const std::string& key, const sol::table& profile) const;
void loadResourceFromLua_Particle (ResourceChangeObj& out, const std::string& domain, const std::string& key, const sol::table& profile) const;
void loadResourceFromLua_Animation (ResourceChangeObj& out, const std::string& domain, const std::string& key, const sol::table& profile) const;
void loadResourceFromLua_Model (ResourceChangeObj& out, const std::string& domain, const std::string& key, const sol::table& profile) const;
void loadResourceFromLua_Texture (ResourceChangeObj& out, const std::string& domain, const std::string& key, const sol::table& profile) const;
void loadResourceFromLua_Sound (ResourceChangeObj& out, const std::string& domain, const std::string& key, const sol::table& profile) const;
void loadResourceFromLua_Font (ResourceChangeObj& out, const std::string& domain, const std::string& key, const sol::table& profile) const;
public:
AssetsManager(asio::io_context& ioc);
~AssetsManager();
/*
Перепроверка изменений ресурсов по дате изменения, пересчёт хешей.
Обнаруженные изменения должны быть отправлены всем клиентам.
Ресурсы будут обработаны в подходящий формат и сохранены в кеше.
Одновременно может выполнятся только одна такая функция
Используется в GameServer
*/
struct AssetsRegister {
/*
Пути до активных папок assets, соответствую порядку загруженным модам.
От последнего мода к первому.
Тот файл, что был загружен раньше и будет использоваться
*/
std::vector<fs::path> Assets;
/*
У этих ресурсов приоритет выше, если их удастся получить,
то использоваться будут именно они
Domain -> {key + data}
*/
std::unordered_map<std::string, std::unordered_map<std::string, void*>> Custom[(int) EnumAssets::MAX_ENUM];
};
ResourceChangeObj recheckResources(const AssetsRegister&);
/*
Применяет расчитанные изменения.
Раздаёт идентификаторы ресурсам и записывает их в таблицу
*/
struct Out_applyResourceChange {
std::vector<ResourceId> Lost[(int) EnumAssets::MAX_ENUM];
std::vector<std::pair<ResourceId, Resource>> NewOrChange[(int) EnumAssets::MAX_ENUM];
};
Out_applyResourceChange applyResourceChange(const ResourceChangeObj& orr);
/*
Выдаёт идентификатор ресурса, даже если он не существует или был удалён.
resource должен содержать домен и путь
*/
ResourceId getId(EnumAssets type, const std::string& domain, const std::string& key) {
return LocalObj.lock()->getId(type, domain, key);
}
// Выдаёт ресурс по идентификатору
std::optional<std::tuple<Resource, const std::string&, const std::string&>> getResource(EnumAssets type, ResourceId id) {
return LocalObj.lock()->getResource(type, id);
}
// Выдаёт ресурс по хешу
std::optional<std::tuple<Resource, const std::string&, const std::string&, EnumAssets, ResourceId>> getResource(const Hash_t& hash) {
return LocalObj.lock()->getResource(hash);
}
// Выдаёт зависимости к ресурсам профиля ноды
std::tuple<AssetsNodestate, std::vector<AssetsModel>, std::vector<AssetsTexture>>
getNodeDependency(const std::string& domain, const std::string& key)
{
if(domain == "core" && key == "none") {
return {0, {}, {}};
}
auto lock = LocalObj.lock();
AssetsNodestate nodestateId = lock->getId(EnumAssets::Nodestate, domain, key+".json");
std::vector<AssetsModel> models;
std::vector<AssetsTexture> textures;
if(auto subModelsPtr = lock->getResourceNodestate(nodestateId)) {
for(AssetsModel resId : *subModelsPtr) {
const auto& subModel = lock->getResourceModel(resId);
if(!subModel)
continue;
models.push_back(resId);
models.append_range(subModel->FullSubModelDeps);
textures.append_range(subModel->FullSubTextureDeps);
}
} else {
LOG.debug() << "Для ноды " << domain << ':' << key << " отсутствует описание Nodestate";
}
{
std::sort(models.begin(), models.end());
auto eraseIter = std::unique(models.begin(), models.end());
models.erase(eraseIter, models.end());
models.shrink_to_fit();
}
{
std::sort(textures.begin(), textures.end());
auto eraseIter = std::unique(textures.begin(), textures.end());
textures.erase(eraseIter, textures.end());
textures.shrink_to_fit();
}
return {nodestateId, std::move(models), std::move(textures)};
}
private:
TOS::Logger LOG = "Server>AssetsManager";
};
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,166 +0,0 @@
#include "ContentManager.hpp"
#include "Common/Abstract.hpp"
#include <algorithm>
namespace LV::Server {
ContentManager::ContentManager(AssetsManager &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(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
MAKE_ERROR("Не реализовано");
}
void ContentManager::unRegisterBase(EnumDefContent type, const std::string& domain, const std::string& key)
{
ResourceId id = getId(type, domain, key);
ProfileChanges[(int) type].push_back(id);
}
void ContentManager::registerModifier(EnumDefContent type, const std::string& mod, const std::string& domain, const std::string& key, const sol::table& profile)
{
ResourceId id = getId(type, domain, key);
ProfileChanges[(int) type].push_back(id);
}
void ContentManager::unRegisterModifier(EnumDefContent type, const std::string& mod, const std::string& domain, const std::string& key)
{
ResourceId id = getId(type, domain, key);
ProfileChanges[(int) type].push_back(id);
}
void ContentManager::markAllProfilesDirty(EnumDefContent type) {
const auto &table = ContentKeyToId[(int) type];
for(const auto& domainPair : table) {
for(const auto& keyPair : domainPair.second) {
ProfileChanges[(int) type].push_back(keyPair.second);
}
}
}
std::vector<ResourceId> ContentManager::collectProfileIds(EnumDefContent type) const {
std::vector<ResourceId> ids;
const auto &table = ContentKeyToId[(int) type];
for(const auto& domainPair : table) {
for(const auto& keyPair : domainPair.second) {
ids.push_back(keyPair.second);
}
}
std::sort(ids.begin(), ids.end());
auto last = std::unique(ids.begin(), ids.end());
ids.erase(last, ids.end());
return ids;
}
ContentManager::Out_buildEndProfiles ContentManager::buildEndProfiles() {
Out_buildEndProfiles result;
for(int type = 0; type < (int) EnumDefContent::MAX_ENUM; type++) {
auto& keys = ProfileChanges[type];
std::sort(keys.begin(), keys.end());
auto iterErase = std::unique(keys.begin(), keys.end());
keys.erase(iterErase, keys.end());
}
for(ResourceId id : ProfileChanges[(int) EnumDefContent::Node]) {
std::optional<DefNode>& node = getEntry_Node(id);
if(!node) {
continue;
}
auto [nodestateId, assetsModel, assetsTexture]
= AM.getNodeDependency(node->Domain, node->Key);
node->NodestateId = nodestateId;
node->ModelDeps = std::move(assetsModel);
node->TextureDeps = std::move(assetsTexture);
}
return result;
}
}

View File

@@ -1,216 +0,0 @@
#pragma once
#include "Common/Abstract.hpp"
#include "Server/Abstract.hpp"
#include "Server/AssetsManager.hpp"
#include <sol/table.hpp>
#include <unordered_map>
#include <unordered_set>
namespace LV::Server {
struct DefVoxel_Base { };
struct DefNode_Base { };
struct DefWorld_Base { };
struct DefPortal_Base { };
struct DefEntity_Base { };
struct DefItem_Base { };
struct DefVoxel_Mod { };
struct DefNode_Mod { };
struct DefWorld_Mod { };
struct DefPortal_Mod { };
struct DefEntity_Mod { };
struct DefItem_Mod { };
struct ResourceBase {
std::string Domain, Key;
};
struct DefVoxel : public ResourceBase { };
struct DefNode : public ResourceBase {
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);
public:
ContentManager(AssetsManager &am);
~ContentManager();
// Регистрирует определение контента
void registerBase(EnumDefContent type, const std::string& domain, const std::string& key, const sol::table& profile);
void unRegisterBase(EnumDefContent type, const std::string& domain, const std::string& key);
// Регистрация модификатора предмета модом
void registerModifier(EnumDefContent type, const std::string& mod, const std::string& domain, const std::string& key, const sol::table& profile);
void unRegisterModifier(EnumDefContent type, const std::string& mod, const std::string& domain, const std::string& key);
// Пометить все профили типа как изменённые (например, после перезагрузки ассетов)
void markAllProfilesDirty(EnumDefContent type);
// Список всех зарегистрированных профилей выбранного типа
std::vector<ResourceId> collectProfileIds(EnumDefContent type) const;
// Компилирует изменённые профили
struct Out_buildEndProfiles {
std::vector<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;
}
private:
TOS::Logger LOG = "Server>ContentManager";
AssetsManager& AM;
};
}

File diff suppressed because it is too large Load Diff

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -3,22 +3,25 @@
#include <TOSLib.hpp>
#include <Common/Lockable.hpp>
#include <Common/Net.hpp>
#include <Common/Async.hpp>
#include "Abstract.hpp"
#include "Common/Packets.hpp"
#include "Server/AssetsManager.hpp"
#include "Server/ContentManager.hpp"
#include "Server/ContentEventController.hpp"
#include "TOSAsync.hpp"
#include "boost/asio/detached.hpp"
#include "boost/asio/io_context.hpp"
#include "boost/asio/use_awaitable.hpp"
#include <Common/Abstract.hpp>
#include <bitset>
#include <initializer_list>
#include <queue>
#include <memory>
#include <set>
#include <type_traits>
#include <unordered_map>
#include <unordered_set>
namespace LV::Server {
class World;
class GameServer;
template<typename ServerKey, typename ClientKey, std::enable_if_t<sizeof(ServerKey) >= sizeof(ClientKey), int> = 0>
class CSChunkedMapper {
std::unordered_map<uint32_t, std::tuple<std::bitset<64>, std::array<ServerKey, 64>>> Chunks;
@@ -104,7 +107,7 @@ public:
// Соотнести идентификатор на стороне клиента с идентификатором на стороне сервера
ServerKey toServer(ClientKey ckey) {
return CSmapper.toServer(ckey);
}
}
// Удаляет серверный идентификатор, освобождая идентификатор клиента
ClientKey erase(ServerKey skey) {
@@ -138,213 +141,161 @@ public:
состоянии для клиента и шаблоны, которые клиенту уже не нужны.
Соответствующие менеджеры ресурсов будут следить за изменениями
этих ресурсов и переотправлять их клиенту
Информация о двоичных ресурсах будет получена сразу же при их запросе.
Действительная отправка ресурсов будет только по запросу клиента.
*/
struct ResourceRequest {
std::vector<Hash_t> Hashes;
std::vector<ResourceId> AssetsInfo[(int) EnumAssets::MAX_ENUM];
std::vector<BinTextureId_t> NewTextures;
std::vector<BinModelId_t> NewModels;
std::vector<BinSoundId_t> NewSounds;
std::vector<DefVoxelId> Voxel;
std::vector<DefNodeId> Node;
std::vector<DefWorldId> World;
std::vector<DefPortalId> Portal;
std::vector<DefEntityId> Entity;
std::vector<DefItemId> Item;
std::vector<DefWorldId_t> NewWorlds;
std::vector<DefVoxelId_t> NewVoxels;
std::vector<DefNodeId_t> NewNodes;
std::vector<DefPortalId_t> NewPortals;
std::vector<DefEntityId_t> NewEntityes;
void insert(const ResourceRequest &obj) {
Hashes.insert(Hashes.end(), obj.Hashes.begin(), obj.Hashes.end());
for(int iter = 0; iter < (int) EnumAssets::MAX_ENUM; iter++)
AssetsInfo[iter].insert(AssetsInfo[iter].end(), obj.AssetsInfo[iter].begin(), obj.AssetsInfo[iter].end());
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());
Voxel.insert(Voxel.end(), obj.Voxel.begin(), obj.Voxel.end());
Node.insert(Node.end(), obj.Node.begin(), obj.Node.end());
World.insert(World.end(), obj.World.begin(), obj.World.end());
Portal.insert(Portal.end(), obj.Portal.begin(), obj.Portal.end());
Entity.insert(Entity.end(), obj.Entity.begin(), obj.Entity.end());
Item.insert(Item.end(), obj.Item.begin(), obj.Item.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() {
for(std::vector<ResourceId> *vec : {&Voxel, &Node, &World,
&Portal, &Entity, &Item
})
{
for(std::vector<ResourceId_t> *vec : {&NewTextures, &NewModels, &NewSounds, &NewWorlds, &NewVoxels, &NewNodes, &NewPortals, &NewEntityes}) {
std::sort(vec->begin(), vec->end());
auto last = std::unique(vec->begin(), vec->end());
vec->erase(last, vec->end());
}
for(int type = 0; type < (int) EnumAssets::MAX_ENUM; type++)
{
std::sort(AssetsInfo[type].begin(), AssetsInfo[type].end());
auto last = std::unique(AssetsInfo[type].begin(), AssetsInfo[type].end());
AssetsInfo[type].erase(last, AssetsInfo[type].end());
}
std::sort(Hashes.begin(), Hashes.end());
auto last = std::unique(Hashes.begin(), Hashes.end());
Hashes.erase(last, Hashes.end());
}
};
// using EntityKey = std::tuple<WorldId_c, Pos::GlobalRegion>;
using EntityKey = std::tuple<WorldId_c, Pos::GlobalRegion>;
class RemoteClient;
using RemoteClient_ptr = std::unique_ptr<RemoteClient, std::function<void(RemoteClient*)>>;
/*
Обработчик сокета клиента.
Подписывает клиента на отслеживание необходимых ресурсов
на основе передаваемых клиенту данных
*/
class RemoteClient {
class RemoteClient : public TOS::IAsyncDestructible {
TOS::Logger LOG;
DestroyLock UseLock;
Net::AsyncSocket Socket;
bool IsConnected = true, IsGoingShutdown = false;
struct NetworkAndResource_t {
struct ResUses_t {
// Счётчики использования двоичных кэшируемых ресурсов + хэш привязанный к идентификатору
// Хэш используется для того, чтобы исключить повторные объявления неизменившихся ресурсов
std::map<ResourceId, std::pair<uint32_t, Hash_t>> AssetsUse[(int) EnumAssets::MAX_ENUM];
struct ResUsesObj {
// Счётчики использования базовых ресурсов высшими объектами
std::map<BinTextureId_t, uint32_t> BinTexture;
std::map<BinSoundId_t, uint32_t> BinSound;
// Зависимость профилей контента от профилей ресурсов
// Нужно чтобы пересчитать зависимости к профилям ресурсов
struct RefAssets_t {
std::vector<ResourceId> Resources[(int) EnumAssets::MAX_ENUM];
};
// Может использовать текстуры
std::map<BinModelId_t, uint32_t> BinModel;
std::map<DefVoxelId, RefAssets_t> RefDefVoxel;
std::map<DefNodeId, RefAssets_t> RefDefNode;
std::map<WorldId_t, RefAssets_t> RefDefWorld;
std::map<DefPortalId, RefAssets_t> RefDefPortal;
std::map<DefEntityId, RefAssets_t> RefDefEntity;
std::map<DefItemId, RefAssets_t> RefDefItem;
// Счётчики использование профилей контента
std::map<DefVoxelId, uint32_t> DefVoxel; // Один чанк, одно использование
std::map<DefNodeId, uint32_t> DefNode;
std::map<DefWorldId, uint32_t> DefWorld;
std::map<DefPortalId, uint32_t> DefPortal;
std::map<DefEntityId, uint32_t> DefEntity;
std::map<DefItemId, uint32_t> DefItem; // При передаче инвентарей?
// Будут использовать в своих определениях текстуры, звуки, модели
std::map<DefWorldId_t, uint32_t> DefWorld;
std::map<DefVoxelId_t, uint32_t> DefVoxel;
std::map<DefNodeId_t, uint32_t> DefNode;
std::map<DefPortalId_t, uint32_t> DefPortal;
std::map<DefEntityId_t, uint32_t> DefEntity;
// Зависимость наблюдаемых чанков от профилей нод и вокселей
struct ChunkRef {
// Отсортированные списки уникальных вокселей
std::vector<DefVoxelId> Voxel;
std::vector<DefNodeId> Node;
};
std::map<WorldId_t, std::map<Pos::GlobalRegion, std::array<ChunkRef, 4*4*4>>> RefChunk;
// Переписываемый контент
// Модификационные зависимости экземпляров профилей контента
// У сущностей в мире могут дополнительно изменятся свойства, переписывая их профиль
struct RefWorld_t {
DefWorldId Profile;
RefAssets_t Assets;
};
std::map<WorldId_t, RefWorld_t> RefWorld;
struct RefPortal_t {
DefPortalId Profile;
RefAssets_t Assets;
};
// std::map<PortalId, RefPortal_t> RefPortal;
struct RefEntity_t {
DefEntityId Profile;
RefAssets_t Assets;
};
std::map<ServerEntityId_t, RefEntity_t> RefEntity;
} ResUses;
// Сущности используют текстуры, звуки, модели
struct EntityResourceUse {
DefEntityId_t DefId;
// Смена идентификаторов сервера на клиентские
SCSKeyRemapper<ServerEntityId_t, ClientEntityId_t> ReMapEntities;
std::unordered_set<BinTextureId_t> Textures;
std::unordered_set<BinSoundId_t> Sounds;
std::unordered_set<BinModelId_t> Models;
};
// Запрос информации об ассетах и профилях контента
ResourceRequest NextRequest;
// Запрошенные клиентом ресурсы
/// TODO: здесь может быть засор
std::vector<Hash_t> ClientRequested;
std::map<GlobalEntityId_t, EntityResourceUse> Entity;
void incrementAssets(const ResUses_t::RefAssets_t& bin);
void decrementAssets(ResUses_t::RefAssets_t&& bin);
// Чанки используют воксели, ноды
std::map<std::tuple<WorldId_t, Pos::GlobalChunk>, std::unordered_set<DefVoxelId_t>> ChunkVoxels;
std::map<std::tuple<WorldId_t, Pos::GlobalChunk>, std::unordered_set<DefNodeId_t>> ChunkNodes;
Net::Packet NextPacket;
std::vector<Net::Packet> SimplePackets;
void checkPacketBorder(uint16_t size) {
if(64000-NextPacket.size() < size || (NextPacket.size() != 0 && size == 0)) {
SimplePackets.push_back(std::move(NextPacket));
}
}
// Миры
struct WorldResourceUse {
DefWorldId_t DefId;
void prepareChunkUpdate_Voxels(WorldId_t worldId, Pos::GlobalChunk chunkPos, const std::u8string& compressed_voxels,
const std::vector<DefVoxelId>& uniq_sorted_defines);
void prepareChunkUpdate_Nodes(WorldId_t worldId, Pos::GlobalChunk chunkPos, const std::u8string& compressed_nodes,
const std::vector<DefNodeId>& uniq_sorted_defines);
void prepareEntitiesRemove(const std::vector<ServerEntityId_t>& entityId);
void prepareRegionsRemove(WorldId_t worldId, std::vector<Pos::GlobalRegion> regionPoses);
void prepareWorldRemove(WorldId_t worldId);
void prepareEntitiesUpdate(const std::vector<std::tuple<ServerEntityId_t, const Entity*>>& entities);
void prepareEntitiesUpdate_Dynamic(const std::vector<std::tuple<ServerEntityId_t, const Entity*>>& entities);
void prepareEntitySwap(ServerEntityId_t prevEntityId, ServerEntityId_t nextEntityId);
void prepareWorldUpdate(WorldId_t worldId, World* world);
void informateDefVoxel(const std::vector<std::pair<DefVoxelId, DefVoxel*>>& voxels);
void informateDefNode(const std::vector<std::pair<DefNodeId, DefNode*>>& nodes);
void informateDefWorld(const std::vector<std::pair<DefWorldId, DefWorld*>>& worlds);
void informateDefPortal(const std::vector<std::pair<DefPortalId, DefPortal*>>& portals);
void informateDefEntity(const std::vector<std::pair<DefEntityId, DefEntity*>>& entityes);
void informateDefItem(const std::vector<std::pair<DefItemId, DefItem*>>& items);
};
std::unordered_set<BinTextureId_t> Textures;
std::unordered_set<BinSoundId_t> Sounds;
std::unordered_set<BinModelId_t> Models;
};
std::map<WorldId_t, WorldResourceUse> Worlds;
// Порталы
struct PortalResourceUse {
DefPortalId_t DefId;
std::unordered_set<BinTextureId_t> Textures;
std::unordered_set<BinSoundId_t> Sounds;
std::unordered_set<BinModelId_t> Models;
};
std::map<PortalId_t, PortalResourceUse> Portals;
} ResUses;
struct {
/*
К концу такта собираются необходимые идентификаторы ресурсов
В конце такта сервер забирает запросы и возвращает информацию
о ресурсах. Отправляем связку Идентификатор + домен:ключ
+ хеш. Если у клиента не окажется этого ресурса, он может его запросить
*/
SCSKeyRemapper<BinTextureId_t, TextureId_c> BinTextures;
SCSKeyRemapper<BinSoundId_t, SoundId_c> BinSounds;
SCSKeyRemapper<BinModelId_t, ModelId_c> BinModels;
// Ресурсы, отправленные на клиент в этой сессии
std::vector<Hash_t> OnClient;
// Отправляемые на клиент ресурсы
// Тип, домен, ключ, идентификатор, ресурс, количество отправленных байт
std::vector<std::tuple<EnumAssets, std::string, std::string, ResourceId, Resource, size_t>> ToSend;
// Пакет с ресурсами
std::vector<Net::Packet> AssetsPackets;
Net::Packet AssetsPacket;
} AssetsInWork;
SCSKeyRemapper<DefWorldId_t, DefWorldId_c> DefWorlds;
SCSKeyRemapper<DefVoxelId_t, DefVoxelId_c> DefVoxels;
SCSKeyRemapper<DefNodeId_t, DefNodeId_c> DefNodes;
SCSKeyRemapper<DefPortalId_t, DefPortalId_c> DefPortals;
SCSKeyRemapper<DefEntityId_t, DefEntityId_c> DefEntityes;
TOS::SpinlockObject<NetworkAndResource_t> NetworkAndResource;
SCSKeyRemapper<WorldId_t, WorldId_c> Worlds;
SCSKeyRemapper<PortalId_t, PortalId_c> Portals;
SCSKeyRemapper<GlobalEntityId_t, EntityId_c> Entityes;
} ResRemap;
Net::Packet NextPacket;
ResourceRequest NextRequest;
std::vector<Net::Packet> SimplePackets;
TOS::WaitableCoro RunCoro;
public:
const std::string Username;
Pos::Object CameraPos = {0, 0, 0};
Pos::Object LastPos = CameraPos;
ToServer::PacketQuat CameraQuat = {0};
TOS::SpinlockObject<std::queue<uint8_t>> Actions;
ResourceId RecievedAssets[(int) EnumAssets::MAX_ENUM] = {0};
// Регионы, наблюдаемые клиентом
ContentViewInfo ContentViewState;
// Если игрок пересекал границы региона (для перерасчёта ContentViewState)
bool CrossedRegion = true;
std::queue<Pos::GlobalNode> Build, Break;
private:
RemoteClient(asio::io_context &ioc, tcp::socket socket, const std::string username)
: IAsyncDestructible(ioc), LOG("RemoteClient " + username), Socket(ioc, std::move(socket)),
Username(username), RunCoro(ioc)
{
RunCoro.co_spawn(run());
}
virtual coro<> asyncDestructor() override;
coro<> run();
public:
RemoteClient(asio::io_context &ioc, tcp::socket socket, const std::string username, GameServer* server)
: LOG("RemoteClient " + username), Socket(ioc, std::move(socket)), Username(username), Server(server)
{}
static RemoteClient_ptr Create(asio::io_context &ioc, tcp::socket socket, const std::string username) {
return createUnique<>(ioc, new RemoteClient(ioc, std::move(socket), username));
}
~RemoteClient();
virtual ~RemoteClient();
coro<> run();
void shutdown(EnumDisconnect type, const std::string reason);
bool isConnected() { return IsConnected; }
@@ -355,65 +306,28 @@ public:
Socket.pushPackets(simplePackets, smartPackets);
}
// Возвращает список точек наблюдений клиентом с радиусом в регионах
std::vector<std::tuple<WorldId_t, Pos::Object, uint8_t>> getViewPoints();
// Функции подготавливают пакеты к отправке
// Отслеживаемое игроком использование контента
// Maybe?
// Текущий список вокселей, определения нод, которые больше не используются в чанке, и определения нод, которые теперь используются
//void prepareChunkUpdate_Voxels(WorldId_t worldId, Pos::GlobalChunk chunkPos, const std::vector<VoxelCube> &voxels, const std::vector<DefVoxelId_t> &noLongerInUseDefs, const std::vector<DefVoxelId_t> &nowUsed);
void prepareChunkUpdate_Voxels(WorldId_t worldId, Pos::GlobalChunk chunkPos, const std::vector<VoxelCube> &voxels);
void prepareChunkUpdate_Nodes(WorldId_t worldId, Pos::GlobalChunk chunkPos, const std::unordered_map<Pos::Local16_u, Node> &nodes);
void prepareChunkUpdate_LightPrism(WorldId_t worldId, Pos::GlobalChunk chunkPos, const LightPrism *lights);
void prepareChunkRemove(WorldId_t worldId, Pos::GlobalChunk chunkPos);
/*
Сервер собирает изменения миров, сжимает их и раздаёт на отправку игрокам
*/
void prepareEntitySwap(GlobalEntityId_t prevEntityId, GlobalEntityId_t nextEntityId);
void prepareEntityUpdate(GlobalEntityId_t entityId, const Entity *entity);
void prepareEntityRemove(GlobalEntityId_t entityId);
// Все функции prepare потокобезопасные
// maybe используются в BackingChunkPressure_t в GameServer в пуле потоков.
// если возвращает false, то блокировка сейчас находится у другого потока
// и запрос не был обработан.
void prepareWorldUpdate(WorldId_t worldId, World* world);
void prepareWorldRemove(WorldId_t worldId);
// В зоне видимости добавился чанк или изменились его воксели
bool maybe_prepareChunkUpdate_Voxels(WorldId_t worldId, Pos::GlobalChunk chunkPos, const std::u8string& compressed_voxels,
const std::vector<DefVoxelId>& uniq_sorted_defines)
{
auto lock = NetworkAndResource.tryLock();
if(!lock)
return false;
lock->prepareChunkUpdate_Voxels(worldId, chunkPos, compressed_voxels, uniq_sorted_defines);
return true;
}
// В зоне видимости добавился чанк или изменились его ноды
bool maybe_prepareChunkUpdate_Nodes(WorldId_t worldId, Pos::GlobalChunk chunkPos, const std::u8string& compressed_nodes,
const std::vector<DefNodeId>& uniq_sorted_defines)
{
auto lock = NetworkAndResource.tryLock();
if(!lock)
return false;
lock->prepareChunkUpdate_Nodes(worldId, chunkPos, compressed_nodes, uniq_sorted_defines);
return true;
}
// void prepareChunkUpdate_LightPrism(WorldId_t worldId, Pos::GlobalChunk chunkPos, const LightPrism *lights);
// Клиент перестал наблюдать за сущностью
void prepareEntitiesRemove(const std::vector<ServerEntityId_t>& entityId) { NetworkAndResource.lock()->prepareEntitiesRemove(entityId); }
// Регион удалён из зоны видимости
void prepareRegionsRemove(WorldId_t worldId, std::vector<Pos::GlobalRegion> regionPoses) { NetworkAndResource.lock()->prepareRegionsRemove(worldId, regionPoses); }
// Мир удалён из зоны видимости
void prepareWorldRemove(WorldId_t worldId) { NetworkAndResource.lock()->prepareWorldRemove(worldId); }
// В зоне видимости добавилась новая сущность или она изменилась
void prepareEntitiesUpdate(const std::vector<std::tuple<ServerEntityId_t, const Entity*>>& entities) { NetworkAndResource.lock()->prepareEntitiesUpdate(entities); }
void prepareEntitiesUpdate_Dynamic(const std::vector<std::tuple<ServerEntityId_t, const Entity*>>& entities) { NetworkAndResource.lock()->prepareEntitiesUpdate_Dynamic(entities); }
// Наблюдаемая сущность пересекла границы региона, у неё изменился серверный идентификатор
void prepareEntitySwap(ServerEntityId_t prevEntityId, ServerEntityId_t nextEntityId) { NetworkAndResource.lock()->prepareEntitySwap(prevEntityId, nextEntityId); }
// Мир появился в зоне видимости или изменился
void prepareWorldUpdate(WorldId_t worldId, World* world) { NetworkAndResource.lock()->prepareWorldUpdate(worldId, world); }
// В зоне видимости добавился порта или он изменился
// void preparePortalUpdate(PortalId_t portalId, void* portal);
// Клиент перестал наблюдать за порталом
// void preparePortalRemove(PortalId_t portalId);
void preparePortalUpdate(PortalId_t portalId, void* portal);
void preparePortalRemove(PortalId_t portalId);
// Прочие моменты
void prepareCameraSetEntity(ServerEntityId_t entityId);
void prepareCameraSetEntity(GlobalEntityId_t entityId);
// Отправка подготовленных пакетов
ResourceRequest pushPreparedPackets();
@@ -422,32 +336,29 @@ public:
// Сюда приходят все обновления ресурсов движка
// Глобально их можно запросить в выдаче pushPreparedPackets()
// Оповещение о запрошенных (и не только) ассетах
void informateAssets(const std::vector<std::tuple<EnumAssets, ResourceId, const std::string, const std::string, Resource>>& resources);
// Двоичные файлы
void informateDefTexture(const std::unordered_map<BinTextureId_t, std::shared_ptr<ResourceFile>> &textures);
void informateDefSound(const std::unordered_map<BinSoundId_t, std::shared_ptr<ResourceFile>> &sounds);
void informateDefModel(const std::unordered_map<BinModelId_t, std::shared_ptr<ResourceFile>> &models);
// Игровые определения
void informateDefVoxel(const std::vector<std::pair<DefVoxelId, DefVoxel*>>& voxels) { NetworkAndResource.lock()->informateDefVoxel(voxels); }
void informateDefNode(const std::vector<std::pair<DefNodeId, DefNode*>>& nodes) { NetworkAndResource.lock()->informateDefNode(nodes); }
void informateDefWorld(const std::vector<std::pair<DefWorldId, DefWorld*>>& worlds) { NetworkAndResource.lock()->informateDefWorld(worlds); }
void informateDefPortal(const std::vector<std::pair<DefPortalId, DefPortal*>>& portals) { NetworkAndResource.lock()->informateDefPortal(portals); }
void informateDefEntity(const std::vector<std::pair<DefEntityId, DefEntity*>>& entityes) { NetworkAndResource.lock()->informateDefEntity(entityes); }
void informateDefItem(const std::vector<std::pair<DefItemId, DefItem*>>& items) { NetworkAndResource.lock()->informateDefItem(items); }
void onUpdate();
void informateDefWorld(const std::unordered_map<DefWorldId_t, World*> &worlds);
void informateDefVoxel(const std::unordered_map<DefVoxelId_t, void*> &voxels);
void informateDefNode(const std::unordered_map<DefNodeId_t, void*> &nodes);
void informateDefEntityes(const std::unordered_map<DefEntityId_t, void*> &entityes);
void informateDefPortals(const std::unordered_map<DefPortalId_t, void*> &portals);
private:
GameServer* Server = nullptr;
void checkPacketBorder(uint16_t size);
void protocolError();
coro<> readPacket(Net::AsyncSocket &sock);
coro<> rP_System(Net::AsyncSocket &sock);
// void incrementProfile(const std::vector<TextureId_t> &textures, const std::vector<ModelId_t> &model,
// const std::vector<SoundId_t> &sounds, const std::vector<FontId_t> &font
// );
// void decrementProfile(std::vector<TextureId_t> &&textures, std::vector<ModelId_t> &&model,
// std::vector<SoundId_t> &&sounds, std::vector<FontId_t> &&font
// );
void incrementBinary(std::unordered_set<BinTextureId_t> &textures, std::unordered_set<BinSoundId_t> &sounds,
std::unordered_set<BinModelId_t> &models);
void decrementBinary(std::unordered_set<BinTextureId_t> &textures, std::unordered_set<BinSoundId_t> &sounds,
std::unordered_set<BinModelId_t> &models);
};
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

8
Work/imgui.ini Normal file
View File

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

View File

View File

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

Binary file not shown.

View File

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

Binary file not shown.

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 138 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

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

View File

@@ -1,81 +0,0 @@
{
"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

@@ -1,81 +0,0 @@
{
"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

@@ -1,81 +0,0 @@
{
"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

@@ -1,81 +0,0 @@
{
"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

@@ -1,81 +0,0 @@
{
"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

@@ -1,81 +0,0 @@
{
"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

@@ -1,81 +0,0 @@
{
"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

@@ -1,81 +0,0 @@
{
"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

@@ -1,81 +0,0 @@
{
"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

@@ -1,14 +0,0 @@
{
"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

@@ -1,14 +0,0 @@
{
"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

@@ -1,14 +0,0 @@
{
"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

@@ -1,14 +0,0 @@
{
"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

@@ -1,14 +0,0 @@
{
"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

@@ -1,14 +0,0 @@
{
"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.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 138 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

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