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
94 changed files with 4286 additions and 25414 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,65 +44,19 @@ set(Boost_USE_STATIC_LIBS ON)
set(BOOST_INCLUDE_LIBRARIES asio thread json)
set(BOOST_ENABLE_CMAKE ON)
set(BOOST_IOSTREAMS_ENABLE_ZLIB ON)
set(BOOST_INCLUDE_LIBRARIES asio thread json iostreams interprocess timer circular_buffer lockfree stacktrace uuid serialization nowide)
FetchContent_Declare(
Boost
GIT_REPOSITORY https://github.com/boostorg/boost.git
GIT_TAG boost-1.87.0
GIT_PROGRESS true
USES_TERMINAL_DOWNLOAD true
URL https://github.com/boostorg/boost/releases/download/boost-1.87.0/boost-1.87.0-cmake.7z
USES_TERMINAL_DOWNLOAD TRUE
DOWNLOAD_NO_EXTRACT FALSE
)
FetchContent_MakeAvailable(Boost)
target_link_libraries(luavox_common INTERFACE Boost::asio Boost::thread Boost::json Boost::iostreams Boost::interprocess Boost::timer Boost::circular_buffer Boost::lockfree Boost::stacktrace Boost::uuid Boost::serialization Boost::nowide)
# unordered_dense
FetchContent_Declare(
unordered_dense
GIT_REPOSITORY https://github.com/martinus/unordered_dense.git
GIT_TAG v4.8.1
)
FetchContent_MakeAvailable(unordered_dense)
target_link_libraries(luavox_common INTERFACE unordered_dense::unordered_dense)
target_link_libraries(${PROJECT_NAME} PUBLIC Boost::asio Boost::thread Boost::json)
# glm
# find_package(glm REQUIRED)
# target_include_directories(${PROJECT_NAME} PUBLIC ${GLM_INCLUDE_DIR})
# target_link_libraries(${PROJECT_NAME} PUBLIC ${GLM_LIBRARY})
FetchContent_Declare(
luajit
GIT_REPOSITORY https://luajit.org/git/luajit.git
GIT_TAG v2.1
GIT_PROGRESS true
USES_TERMINAL_DOWNLOAD true
)
FetchContent_MakeAvailable(luajit)
set(LUAJIT_DIR ${luajit_SOURCE_DIR})
set(LUAJIT_ENABLE_LUA52COMPAT ON)
FetchContent_Declare(
lua_cmake
GIT_REPOSITORY https://github.com/zhaozg/luajit-cmake.git
GIT_TAG 300c0b3f472be2be158f5b2e6385579ba5c6c0f9
GIT_PROGRESS true
USES_TERMINAL_DOWNLOAD true
)
FetchContent_MakeAvailable(lua_cmake)
target_link_libraries(luavox_common INTERFACE luajit::header luajit::lib)
target_include_directories(luavox_common INTERFACE ${lua_cmake_BINARY_DIR})
FetchContent_Declare(
sol2
GIT_REPOSITORY https://github.com/ThePhD/sol2.git
GIT_TAG v3.5.0
GIT_PROGRESS true
USES_TERMINAL_DOWNLOAD true
)
FetchContent_MakeAvailable(sol2)
target_link_libraries(luavox_common INTERFACE sol2::sol2)
FetchContent_Declare(
glm
@@ -138,112 +64,70 @@ FetchContent_Declare(
GIT_TAG 1.0.1
)
FetchContent_MakeAvailable(glm)
target_link_libraries(luavox_common INTERFACE glm)
target_link_libraries(${PROJECT_NAME} PUBLIC glm)
find_package(ICU REQUIRED COMPONENTS i18n uc)
target_include_directories(luavox_common INTERFACE ${ICU_INCLUDE_DIR})
target_link_libraries(luavox_common INTERFACE ${ICU_LIBRARIES})
target_include_directories(${PROJECT_NAME} PUBLIC ${ICU_INCLUDE_DIR})
target_link_libraries(${PROJECT_NAME} PUBLIC ${ICU_LIBRARIES})
find_package(OpenSSL REQUIRED)
target_include_directories(luavox_common INTERFACE ${OPENSSL_INCLUDE_DIR})
target_link_libraries(luavox_common INTERFACE ${OPENSSL_LIBRARIES})
target_include_directories(${PROJECT_NAME} PUBLIC ${OPENSSL_INCLUDE_DIR})
target_link_libraries(${PROJECT_NAME} PUBLIC ${OPENSSL_LIBRARIES})
# JPEG
find_package(JPEG REQUIRED)
target_include_directories(luavox_common INTERFACE ${JPEG_INCLUDE_DIRS})
target_link_libraries(luavox_common INTERFACE JPEG::JPEG)
target_include_directories(${PROJECT_NAME} PUBLIC ${JPEG_INCLUDE_DIRS})
target_link_libraries(${PROJECT_NAME} PUBLIC JPEG::JPEG)
# PNG
find_package(PNG REQUIRED)
target_include_directories(luavox_common INTERFACE ${PNG_INCLUDE_DIRS})
target_link_libraries(luavox_common INTERFACE PNG::PNG)
target_include_directories(${PROJECT_NAME} PUBLIC ${PNG_INCLUDE_DIRS})
target_link_libraries(${PROJECT_NAME} PUBLIC PNG::PNG)
# PNG++
target_include_directories(luavox_common INTERFACE "${PROJECT_SOURCE_DIR}/Libs/png++")
# sqlite3
FetchContent_Declare(sqlite3 GIT_REPOSITORY https://github.com/sjinks/sqlite3-cmake GIT_TAG v3.49.1)
FetchContent_MakeAvailable(sqlite3)
target_link_libraries(luavox_common INTERFACE SQLite::SQLite3)
FetchContent_Declare(
RectangleBinPack
GIT_REPOSITORY https://github.com/juj/RectangleBinPack.git
GIT_TAG 83e7e1132d93777e3732dfaae26b0f3703be2036
)
FetchContent_MakeAvailable(RectangleBinPack)
target_link_libraries(luavox_common INTERFACE RectangleBinPack)
# Static Assets
find_package(Python3 REQUIRED)
set(ASSETS_DIR "${PROJECT_SOURCE_DIR}/assets")
file(GLOB_RECURSE ASSETS_LIST RELATIVE "${ASSETS_DIR}" "${ASSETS_DIR}/*.*")
set(ASSETS_O "${CMAKE_CURRENT_BINARY_DIR}/assets.o")
set(ASSETS_LD_O "${CMAKE_CURRENT_BINARY_DIR}/assets_ld.o")
set(RESOURCES_CPP "${CMAKE_CURRENT_BINARY_DIR}/resources.cpp")
set(ASSETS_ABS)
foreach(asset IN LISTS ASSETS_LIST)
list(APPEND ASSETS_ABS "${ASSETS_DIR}/${asset}")
endforeach()
add_custom_command(
OUTPUT ${ASSETS_O} ${RESOURCES_CPP}
DEPENDS ${ASSETS_ABS}
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/Src/assets.py
"${RESOURCES_CPP}"
${ASSETS_LIST}
COMMAND ${CMAKE_COMMAND} -E chdir "${CMAKE_CURRENT_SOURCE_DIR}/assets" ld -r -b binary -o "${ASSETS_LD_O}" ${ASSETS_LIST}
COMMAND ${CMAKE_OBJCOPY}
-O elf64-x86-64
--rename-section .data=.rodata,alloc,load,readonly,data,contents
${ASSETS_LD_O}
${ASSETS_O}
COMMENT "Embedding assets: generating resources.cpp and assets.o"
VERBATIM
)
set_source_files_properties(${RESOURCES_CPP} PROPERTIES GENERATED true)
set_source_files_properties(${ASSETS_O} PROPERTIES EXTERNAL_OBJECT true GENERATED true)
add_library(assets STATIC ${RESOURCES_CPP} ${ASSETS_O})
set_target_properties(assets PROPERTIES LINKER_LANGUAGE C)
target_link_libraries(luavox_common INTERFACE assets)
target_include_directories(${PROJECT_NAME} PUBLIC "${PROJECT_SOURCE_DIR}/Libs/png++")
# GLFW3
if(BUILD_CLIENT)
add_executable(luavox_client)
find_package(glfw3 3)
# Common
target_link_libraries(luavox_client PUBLIC luavox_common)
# Исходники
file(GLOB_RECURSE SOURCES RELATIVE ${PROJECT_SOURCE_DIR} "Src/*.cpp")
target_sources(luavox_client PRIVATE ${SOURCES})
target_include_directories(luavox_client PUBLIC "${PROJECT_SOURCE_DIR}/Src")
# GLFW3
if(TARGET glfw)
target_include_directories(${PROJECT_NAME} PUBLIC ${GLFW_INCLUDE_DIRS})
else()
FetchContent_Declare(
glfw
GIT_REPOSITORY https://github.com/glfw/glfw.git
GIT_TAG 3.4
)
FetchContent_MakeAvailable(glfw)
target_link_libraries(luavox_client PUBLIC glfw)
# FreeType
find_package(Freetype REQUIRED)
# FetchContent_Declare(
# freetype
# GIT_REPOSITORY https://github.com/freetype/freetype.git
# GIT_TAG freetype
# )
# FetchContent_MakeAvailable(freetype)
target_include_directories(luavox_client PUBLIC ${freetype_INCLUDE_DIRS})
target_link_libraries(luavox_client PUBLIC Freetype::Freetype)
# ImGui
file(GLOB SOURCES "${PROJECT_SOURCE_DIR}/Libs/imgui/*.cpp")
target_sources(luavox_client PRIVATE ${SOURCES} "${PROJECT_SOURCE_DIR}/Libs/imgui/backends/imgui_impl_glfw.cpp" "${PROJECT_SOURCE_DIR}/Libs/imgui/backends/imgui_impl_vulkan.cpp")
target_include_directories(luavox_client PUBLIC "${PROJECT_SOURCE_DIR}/Libs/imgui/")
endif()
target_link_libraries(${PROJECT_NAME} PUBLIC glfw)
endif()
# FreeType
find_package(Freetype REQUIRED)
# FetchContent_Declare(
# freetype
# GIT_REPOSITORY https://github.com/freetype/freetype.git
# GIT_TAG freetype
# )
# FetchContent_MakeAvailable(freetype)
target_include_directories(${PROJECT_NAME} PUBLIC ${freetype_INCLUDE_DIRS})
target_link_libraries(${PROJECT_NAME} PUBLIC Freetype::Freetype)
# ImGui
file(GLOB SOURCES "${PROJECT_SOURCE_DIR}/Libs/imgui/*.cpp")
target_sources(${PROJECT_NAME} PRIVATE ${SOURCES} "${PROJECT_SOURCE_DIR}/Libs/imgui/backends/imgui_impl_glfw.cpp" "${PROJECT_SOURCE_DIR}/Libs/imgui/backends/imgui_impl_vulkan.cpp")
target_include_directories(${PROJECT_NAME} PUBLIC "${PROJECT_SOURCE_DIR}/Libs/imgui/")
# Static Assets
file(GLOB_RECURSE ASSETS RELATIVE "${PROJECT_SOURCE_DIR}/assets" "assets/*.*")
add_custom_command(OUTPUT assets.o resources.cpp INPUT ${ASSETS}
COMMAND cd ${CMAKE_CURRENT_BINARY_DIR} && ${CMAKE_CURRENT_SOURCE_DIR}/Src/assets.py ${ASSETS}
COMMAND cd "${CMAKE_CURRENT_SOURCE_DIR}/assets" && ld -r -b binary -o '${CMAKE_CURRENT_BINARY_DIR}/assets.o' ${ASSETS}
COMMAND objcopy --rename-section .data=.rodata,alloc,load,readonly,data,contents ${CMAKE_CURRENT_BINARY_DIR}/assets.o ${CMAKE_CURRENT_BINARY_DIR}/assets.o)
SET_SOURCE_FILES_PROPERTIES(assets.o PROPERTIES EXTERNAL_OBJECT true GENERATED true)
add_library(assets STATIC resources.cpp assets.o)
SET_TARGET_PROPERTIES(assets PROPERTIES LINKER_LANGUAGE C)
target_link_libraries(${PROJECT_NAME} PUBLIC assets uring)

Submodule Libs/boost deleted from c89e626766

View File

@@ -1,22 +1,15 @@
#pragma once
#include "Common/Net.hpp"
#include <cstdint>
#include <functional>
#include <string>
#include <string_view>
#include <unordered_map>
#include <unordered_set>
#include <variant>
#include <vector>
#include <Common/Abstract.hpp>
namespace LV::Client {
using EntityId_t = uint16_t;
using FuncEntityId_t = uint16_t;
struct GlobalTime {
uint32_t Seconds : 22 = 0, Sub : 10 = 0;
@@ -37,22 +30,32 @@ struct GlobalTime {
}
};
struct VoxelCube {
DefVoxelId_c VoxelId;
Pos::Local256_u Left, Size;
};
struct Node {
DefNodeId_c NodeId;
uint8_t Rotate : 6;
};
// 16 метров ребро
// 256 вокселей ребро
struct Chunk {
// Кубы вокселей в чанке
std::vector<VoxelCube> Voxels;
// Ноды
std::array<Node, 16*16*16> Nodes;
std::unordered_map<Pos::Local16_u, Node> Nodes;
// Ограничения прохождения света, идущего от солнца (от верха карты до верхней плоскости чанка)
// LightPrism Lights[16][16];
LightPrism Lights[16][16];
};
class Entity {
public:
// PosQuat
WorldId_t WorldId;
// PortalId LastUsedPortal;
DefWorldId_c WorldId;
DefPortalId_c LastUsedPortal;
Pos::Object Pos;
glm::quat Quat;
static constexpr uint16_t HP_BS = 4096, HP_BS_Bit = 12;
@@ -64,74 +67,40 @@ public:
// states
};
struct AssetsModelUpdate {
ResourceId Id = 0;
HeadlessModel Model;
HeadlessModel::Header Header;
};
struct AssetsNodestateUpdate {
ResourceId Id = 0;
HeadlessNodeState Nodestate;
HeadlessNodeState::Header Header;
};
struct AssetsTextureUpdate {
ResourceId Id = 0;
uint16_t Width = 0;
uint16_t Height = 0;
std::vector<uint32_t> Pixels;
ResourceHeader Header;
};
struct AssetsBinaryUpdate {
ResourceId Id = 0;
std::u8string Data;
};
/* Интерфейс рендера текущего подключения к серверу */
class IRenderSession {
public:
// Объект уведомления об изменениях
struct TickSyncData {
// Изменения в ассетах.
std::vector<AssetsModelUpdate> AssetsModels;
std::vector<AssetsNodestateUpdate> AssetsNodestates;
std::vector<AssetsTextureUpdate> AssetsTextures;
virtual void onDefTexture(TextureId_c id, std::vector<std::byte> &&info) = 0;
virtual void onDefTextureLost(const std::vector<TextureId_c> &&lost) = 0;
virtual void onDefModel(ModelId_c id, std::vector<std::byte> &&info) = 0;
virtual void onDefModelLost(const std::vector<ModelId_c> &&lost) = 0;
// Новые или изменённые профили контента
std::unordered_map<EnumDefContent, std::vector<ResourceId>> Profiles_ChangeOrAdd;
// Более не используемые профили
std::unordered_map<EnumDefContent, std::vector<ResourceId>> Profiles_Lost;
// Новые или изменённые чанки
std::unordered_map<WorldId_t, std::vector<Pos::GlobalChunk>> Chunks_ChangeOrAdd;
// Более не отслеживаемые регионы
std::unordered_map<WorldId_t, std::vector<Pos::GlobalRegion>> Chunks_Lost;
};
public:
// Серверная сессия собирается обработать данные такток сервера (изменение профилей, ресурсов, прочих игровых данных)
virtual void prepareTickSync() = 0;
// Началась стадия изменения данных IServerSession, все должны приостановить работу
virtual void pushStageTickSync() = 0;
// После изменения внутренних данных IServerSession, IRenderSession уведомляется об изменениях
virtual void tickSync(TickSyncData& data) = 0;
virtual void onDefWorldUpdates(const std::vector<DefWorldId_c> &updates) = 0;
virtual void onDefVoxelUpdates(const std::vector<DefVoxelId_c> &updates) = 0;
virtual void onDefNodeUpdates(const std::vector<DefNodeId_c> &updates) = 0;
virtual void onDefPortalUpdates(const std::vector<DefPortalId_c> &updates) = 0;
virtual void onDefEntityUpdates(const std::vector<DefEntityId_c> &updates) = 0;
// Сообщаем об изменившихся чанках
virtual void onChunksChange(WorldId_c worldId, const std::unordered_set<Pos::GlobalChunk> &changeOrAddList, const std::unordered_set<Pos::GlobalChunk> &remove) = 0;
// Установить позицию для камеры
virtual void setCameraPos(WorldId_t worldId, Pos::Object pos, glm::quat quat) = 0;
virtual void setCameraPos(WorldId_c worldId, Pos::Object pos, glm::quat quat) = 0;
virtual ~IRenderSession();
};
struct Region {
std::array<Chunk, 4*4*4> Chunks;
std::unordered_map<Pos::Local16_u, Chunk> Chunks;
};
struct World {
std::vector<EntityId_c> Entitys;
std::unordered_map<Pos::GlobalRegion::Key, Region> Regions;
};
struct DefWorldInfo {
};
@@ -141,16 +110,19 @@ struct DefPortalInfo {
};
struct DefEntityInfo {
};
struct DefFuncEntityInfo {
};
struct WorldInfo {
std::vector<EntityId_t> Entitys;
std::vector<FuncEntityId_t> FuncEntitys;
std::unordered_map<Pos::GlobalRegion, Region> Regions;
};
struct VoxelInfo {
};
struct NodeInfo {
};
struct PortalInfo {
@@ -158,138 +130,31 @@ struct PortalInfo {
};
struct EntityInfo {
DefEntityId DefId = 0;
WorldId_t WorldId = 0;
Pos::Object Pos = Pos::Object(0);
glm::quat Quat = glm::quat(1.f, 0.f, 0.f, 0.f);
};
/*
Конструируются с серверными идентификаторами
*/
struct DefVoxel {
DefVoxel() = default;
DefVoxel(const std::u8string_view view) {
}
void reBind(const std::function<ResourceId(EnumAssets, ResourceId)>& am) {
}
};
struct DefNode {
std::variant<AssetsNodestate> RenderStates;
DefNode() = default;
DefNode(const std::u8string_view view) {
Net::LinearReader lr(view);
RenderStates = lr.read<uint32_t>();
}
void reBind(const std::function<ResourceId(EnumAssets, ResourceId)>& am) {
RenderStates = am(EnumAssets::Nodestate, std::get<AssetsNodestate>(RenderStates));
}
};
struct DefWorld {
DefWorld() = default;
DefWorld(const std::u8string_view view) {
}
void reBind(const std::function<ResourceId(EnumAssets, ResourceId)>& am) {
}
};
struct DefPortal {
DefPortal() = default;
DefPortal(const std::u8string_view view) {
}
void reBind(const std::function<ResourceId(EnumAssets, ResourceId)>& am) {
}
};
struct DefEntity {
DefEntity() = default;
DefEntity(const std::u8string_view view) {
}
void reBind(const std::function<ResourceId(EnumAssets, ResourceId)>& am) {
}
};
struct DefItem {
DefItem() = default;
DefItem(const std::u8string_view view) {
}
void reBind(const std::function<ResourceId(EnumAssets, ResourceId)>& am) {
}
};
struct AssetEntry {
ResourceId Id = 0;
std::string Domain;
std::string Key;
HeadlessModel Model;
HeadlessModel::Header ModelHeader;
HeadlessNodeState Nodestate;
HeadlessNodeState::Header NodestateHeader;
uint16_t Width = 0;
uint16_t Height = 0;
std::vector<uint32_t> Pixels;
ResourceHeader Header;
std::u8string Data;
};
/*
Интерфейс обработчика сессии с сервером.
Данный здесь меняются только меж вызовами
IRenderSession::pushStageTickSync
и
IRenderSession::tickSync
*/
/* Интерфейс обработчика сессии с сервером */
class IServerSession {
public:
// Включить логирование входящих сетевых пакетов на клиенте.
bool DebugLogPackets = false;
// Используемые профили контента
struct {
std::unordered_map<DefVoxelId, DefVoxel> DefVoxels;
std::unordered_map<DefNodeId, DefNode> DefNodes;
std::unordered_map<DefWorldId, DefWorld> DefWorlds;
std::unordered_map<DefPortalId, DefPortal> DefPortals;
std::unordered_map<DefEntityId, DefEntity> DefEntitys;
std::unordered_map<DefItemId, DefItem> DefItems;
} Profiles;
std::unordered_map<DefWorldId_c, DefWorldInfo> DefWorlds;
std::unordered_map<DefVoxelId_c, VoxelInfo> DefVoxels;
std::unordered_map<DefNodeId_c, NodeInfo> DefNodes;
std::unordered_map<DefPortalId_c, DefPortalInfo> DefPortals;
std::unordered_map<DefEntityId_c, DefEntityInfo> DefEntityes;
std::unordered_map<WorldId_c, WorldInfo> Worlds;
std::unordered_map<PortalId_c, PortalInfo> Portals;
std::unordered_map<EntityId_c, EntityInfo> Entityes;
} Registry;
// Видимый контент
struct {
std::unordered_map<WorldId_t, WorldInfo> Worlds;
// std::unordered_map<PortalId_t, PortalInfo> Portals;
std::unordered_map<EntityId_t, EntityInfo> Entityes;
} Content;
std::unordered_map<WorldId_c, World> Worlds;
} External;
virtual ~IServerSession();
// Обновление сессии с сервером, может начатся стадия IRenderSession::tickSync
virtual void update(GlobalTime gTime, float dTime) = 0;
virtual void atFreeDrawTime(GlobalTime gTime, float dTime) = 0;
};
@@ -301,7 +166,7 @@ public:
} CursorMode = EnumCursorMoveMode::Default;
enum struct EnumCursorBtn {
Left, Right, Middle, One, Two
Left, Middle, Right, One, Two
};
public:

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,638 +0,0 @@
#pragma once
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <filesystem>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
#include <cstring>
#include "Client/AssetsCacheManager.hpp"
#include "Client/AssetsHeaderCodec.hpp"
#include "Common/Abstract.hpp"
#include "Common/IdProvider.hpp"
#include "Common/AssetsPreloader.hpp"
#include "Common/TexturePipelineProgram.hpp"
#include "TOSLib.hpp"
#include "assets.hpp"
#include "boost/asio/io_context.hpp"
#include "png++/image.hpp"
#include <fstream>
#include "Abstract.hpp"
namespace LV::Client {
namespace fs = std::filesystem;
class AssetsManager : public IdProvider<EnumAssets> {
public:
struct ResourceUpdates {
std::vector<AssetsModelUpdate> Models;
std::vector<AssetsNodestateUpdate> Nodestates;
std::vector<AssetsTextureUpdate> Textures;
std::vector<AssetsBinaryUpdate> Particles;
std::vector<AssetsBinaryUpdate> Animations;
std::vector<AssetsBinaryUpdate> Sounds;
std::vector<AssetsBinaryUpdate> Fonts;
};
public:
AssetsManager(asio::io_context& ioc, fs::path cachePath)
: Cache(AssetsCacheManager::Create(ioc, cachePath))
{
for(size_t type = 0; type < static_cast<size_t>(EnumAssets::MAX_ENUM); type++) {
ServerToClientMap[type].push_back(0);
}
}
// Ручные обновления
struct Out_checkAndPrepareResourcesUpdate {
AssetsPreloader::Out_checkAndPrepareResourcesUpdate RP, ES;
std::unordered_map<ResourceFile::Hash_t, std::u8string> Files;
};
Out_checkAndPrepareResourcesUpdate checkAndPrepareResourcesUpdate(
const std::vector<fs::path>& resourcePacks,
const std::vector<fs::path>& extraSources
) {
Out_checkAndPrepareResourcesUpdate result;
result.RP = ResourcePacks.checkAndPrepareResourcesUpdate(
AssetsPreloader::AssetsRegister{resourcePacks},
[&](EnumAssets type, std::string_view domain, std::string_view key) -> ResourceId {
return getId(type, domain, key);
},
[&](std::u8string&& data, ResourceFile::Hash_t hash, fs::path path) {
result.Files.emplace(hash, std::move(data));
}
);
result.ES = ExtraSource.checkAndPrepareResourcesUpdate(
AssetsPreloader::AssetsRegister{resourcePacks},
[&](EnumAssets type, std::string_view domain, std::string_view key) -> ResourceId {
return getId(type, domain, key);
}
);
return result;
}
struct Out_applyResourcesUpdate {
};
Out_applyResourcesUpdate applyResourcesUpdate(const Out_checkAndPrepareResourcesUpdate& orr) {
Out_applyResourcesUpdate result;
for(size_t type = 0; type < static_cast<size_t>(EnumAssets::MAX_ENUM); ++type) {
for(ResourceId id : orr.RP.LostLinks[type]) {
std::optional<AssetsPreloader::Out_Resource> res = ResourcePacks.getResource((EnumAssets) type, id);
assert(res);
auto hashIter = HashToPath.find(res->Hash);
assert(hashIter != HashToPath.end());
auto& entry = hashIter->second;
auto iter = std::find(entry.begin(), entry.end(), res->Path);
assert(iter != entry.end());
entry.erase(iter);
if(entry.empty())
HashToPath.erase(hashIter);
}
}
ResourcePacks.applyResourcesUpdate(orr.RP);
ExtraSource.applyResourcesUpdate(orr.ES);
std::unordered_set<ResourceFile::Hash_t> needHashes;
for(size_t type = 0; type < static_cast<size_t>(EnumAssets::MAX_ENUM); ++type) {
for(const auto& res : orr.RP.ResourceUpdates[type]) {
// Помечаем ресурс для обновления
PendingUpdateFromAsync[type].push_back(std::get<ResourceId>(res));
HashToPath[std::get<ResourceFile::Hash_t>(res)].push_back(std::get<fs::path>(res));
}
for(ResourceId id : orr.RP.LostLinks[type]) {
// Помечаем ресурс для обновления
PendingUpdateFromAsync[type].push_back(id);
auto& hh = ServerIdToHH[type];
if(id < hh.size())
needHashes.insert(std::get<ResourceFile::Hash_t>(hh[id]));
}
}
{
for(const auto& [hash, data] : orr.Files) {
WaitingHashes.insert(hash);
}
for(const auto& hash : WaitingHashes)
needHashes.erase(hash);
std::vector<std::tuple<ResourceFile::Hash_t, fs::path>> toDisk;
std::vector<ResourceFile::Hash_t> toCache;
// Теперь раскидаем хеши по доступным источникам.
for(const auto& hash : needHashes) {
auto iter = HashToPath.find(hash);
if(iter != HashToPath.end()) {
// Ставим задачу загрузить с диска.
toDisk.emplace_back(hash, iter->second.front());
} else {
// Сделаем запрос в кеш.
toCache.push_back(hash);
}
}
// Запоминаем, что эти ресурсы уже ожидаются.
WaitingHashes.insert_range(needHashes);
// Запрос в кеш (если там не найдётся, то запрос уйдёт на сервер).
if(!toCache.empty())
Cache->pushReads(std::move(toCache));
// Запрос к диску.
if(!toDisk.empty())
NeedToReadFromDisk.append_range(std::move(toDisk));
_onHashLoad(orr.Files);
}
return result;
}
// ServerSession
// Новые привязки ассетов к Домен+Ключ.
void pushAssetsBindDK(
const std::vector<std::string>& domains,
const std::array<
std::vector<std::vector<std::string>>,
static_cast<size_t>(EnumAssets::MAX_ENUM)
>& keys
) {
LOG.debug() << "BindDK domains=" << domains.size();
for(size_t type = 0; type < static_cast<size_t>(EnumAssets::MAX_ENUM); ++type) {
LOG.info() << type;
for(size_t forDomainIter = 0; forDomainIter < keys[type].size(); ++forDomainIter) {
LOG.info() << "\t" << domains[forDomainIter];
for(const std::string& key : keys[type][forDomainIter]) {
uint32_t id = getId((EnumAssets) type, domains[forDomainIter], key);
LOG.info() << "\t\t" << key << " -> " << id;
ServerToClientMap[type].push_back(id);
}
}
}
}
// Новые привязки ассетов к Hash+Header.
void pushAssetsBindHH(
std::array<
std::vector<std::tuple<ResourceId, ResourceFile::Hash_t, ResourceHeader>>,
static_cast<size_t>(EnumAssets::MAX_ENUM)
>&& hash_and_headers
) {
std::unordered_set<ResourceFile::Hash_t> needHashes;
size_t totalBinds = 0;
for(size_t type = 0; type < static_cast<size_t>(EnumAssets::MAX_ENUM); ++type) {
size_t maxSize = 0;
for(auto& [id, hash, header] : hash_and_headers[type]) {
totalBinds++;
assert(id < ServerToClientMap[type].size());
id = ServerToClientMap[type][id];
if(id >= maxSize)
maxSize = id+1;
// Добавляем идентификатор в таблицу ожидающих обновлений.
PendingUpdateFromAsync[type].push_back(id);
// Поискать есть ли ресурс в ресурспаках.
std::optional<AssetsPreloader::Out_Resource> res = ResourcePacks.getResource((EnumAssets) type, id);
if(res) {
needHashes.insert(res->Hash);
} else {
needHashes.insert(hash);
}
}
{
// Уберём повторения в идентификаторах.
auto& vec = PendingUpdateFromAsync[type];
std::sort(vec.begin(), vec.end());
vec.erase(std::unique(vec.begin(), vec.end()), vec.end());
}
if(ServerIdToHH[type].size() < maxSize)
ServerIdToHH[type].resize(maxSize);
for(auto& [id, hash, header] : hash_and_headers[type]) {
ServerIdToHH[type][id] = {hash, std::move(header)};
}
}
if(totalBinds)
LOG.debug() << "BindHH total=" << totalBinds << " wait=" << WaitingHashes.size();
// Нужно убрать хеши, которые уже запрошены
// needHashes ^ WaitingHashes.
for(const auto& hash : WaitingHashes)
needHashes.erase(hash);
std::vector<std::tuple<ResourceFile::Hash_t, fs::path>> toDisk;
std::vector<ResourceFile::Hash_t> toCache;
// Теперь раскидаем хеши по доступным источникам.
for(const auto& hash : needHashes) {
auto iter = HashToPath.find(hash);
if(iter != HashToPath.end()) {
// Ставим задачу загрузить с диска.
toDisk.emplace_back(hash, iter->second.front());
} else {
// Сделаем запрос в кеш.
toCache.push_back(hash);
}
}
// Запоминаем, что эти ресурсы уже ожидаются.
WaitingHashes.insert_range(needHashes);
// Запрос к диску.
if(!toDisk.empty())
NeedToReadFromDisk.append_range(std::move(toDisk));
// Запрос в кеш (если там не найдётся, то запрос уйдёт на сервер).
if(!toCache.empty())
Cache->pushReads(std::move(toCache));
}
// Новые ресурсы, полученные с сервера.
void pushNewResources(
std::vector<std::tuple<ResourceFile::Hash_t, std::u8string>> &&resources
) {
std::unordered_map<ResourceFile::Hash_t, std::u8string> files;
std::vector<Resource> vec;
files.reserve(resources.size());
vec.reserve(resources.size());
for(auto& [hash, res] : resources) {
vec.emplace_back(res);
files.emplace(hash, std::move(res));
}
_onHashLoad(files);
Cache->pushResources(std::move(vec));
}
// Для запроса отсутствующих ресурсов с сервера на клиент.
std::vector<ResourceFile::Hash_t> pullNeededResources() {
return std::move(NeedToRequestFromServer);
}
// Получить изменённые ресурсы (для передачи другим модулям).
ResourceUpdates pullResourceUpdates() {
return std::move(RU);
}
ResourceId reBind(EnumAssets type, ResourceId server) {
return ServerToClientMap[static_cast<size_t>(type)].at(server);
}
void tick() {
// Проверим кеш
std::vector<std::pair<Hash_t, std::optional<Resource>>> resources = Cache->pullReads();
if(!resources.empty()) {
std::unordered_map<ResourceFile::Hash_t, std::u8string> needToProceed;
needToProceed.reserve(resources.size());
for(auto& [hash, res] : resources) {
if(!res)
NeedToRequestFromServer.push_back(hash);
else
needToProceed.emplace(hash, std::u8string{(const char8_t*) res->data(), res->size()});
}
if(!NeedToRequestFromServer.empty())
LOG.debug() << "CacheMiss count=" << NeedToRequestFromServer.size();
if(!needToProceed.empty())
_onHashLoad(needToProceed);
}
/// Читаем с диска TODO: получилась хрень с определением типа, чтобы получать headless ресурс
if(!NeedToReadFromDisk.empty()) {
std::unordered_map<ResourceFile::Hash_t, std::u8string> files;
files.reserve(NeedToReadFromDisk.size());
auto detectTypeDomainKey = [&](const fs::path& path, EnumAssets& typeOut, std::string& domainOut, std::string& keyOut) -> bool {
fs::path cur = path.parent_path();
for(; !cur.empty(); cur = cur.parent_path()) {
std::string name = cur.filename().string();
for(size_t typeIndex = 0; typeIndex < static_cast<size_t>(EnumAssets::MAX_ENUM); ++typeIndex) {
EnumAssets type = static_cast<EnumAssets>(typeIndex);
if(name == ::EnumAssetsToDirectory(type)) {
typeOut = type;
domainOut = cur.parent_path().filename().string();
keyOut = fs::relative(path, cur).generic_string();
return true;
}
}
}
return false;
};
for(const auto& [hash, path] : NeedToReadFromDisk) {
std::u8string data;
std::ifstream file(path, std::ios::binary);
if(file) {
file.seekg(0, std::ios::end);
std::streamoff size = file.tellg();
if(size < 0)
size = 0;
file.seekg(0, std::ios::beg);
data.resize(static_cast<size_t>(size));
if(size > 0) {
file.read(reinterpret_cast<char*>(data.data()), size);
if(!file)
data.clear();
}
} else {
LOG.warn() << "DiskReadFail " << path.string();
}
if(!data.empty()) {
EnumAssets type{};
std::string domain;
std::string key;
if(detectTypeDomainKey(path, type, domain, key)) {
if(type == EnumAssets::Nodestate) {
std::string_view view(reinterpret_cast<const char*>(data.data()), data.size());
js::object obj = js::parse(view).as_object();
HeadlessNodeState hns;
auto modelResolver = [&](const std::string_view model) -> AssetsModel {
auto [mDomain, mKey] = parseDomainKey(model, domain);
return getId(EnumAssets::Model, mDomain, mKey);
};
hns.parse(obj, modelResolver);
data = hns.dump();
} else if(type == EnumAssets::Model) {
std::string_view view(reinterpret_cast<const char*>(data.data()), data.size());
js::object obj = js::parse(view).as_object();
HeadlessModel hm;
auto modelResolver = [&](const std::string_view model) -> AssetsModel {
auto [mDomain, mKey] = parseDomainKey(model, domain);
return getId(EnumAssets::Model, mDomain, mKey);
};
auto textureIdResolver = [&](const std::string_view texture) -> std::optional<uint32_t> {
auto [tDomain, tKey] = parseDomainKey(texture, domain);
return getId(EnumAssets::Texture, tDomain, tKey);
};
auto textureResolver = [&](const std::string_view texturePipelineSrc) -> std::vector<uint8_t> {
TexturePipelineProgram tpp;
if(!tpp.compile(texturePipelineSrc))
return {};
tpp.link(textureIdResolver);
return tpp.toBytes();
};
hm.parse(obj, modelResolver, textureResolver);
data = hm.dump();
}
}
}
files.emplace(hash, std::move(data));
}
NeedToReadFromDisk.clear();
_onHashLoad(files);
}
}
private:
Logger LOG = "Client>AssetsManager";
// Менеджеры учёта дисковых ресурсов
AssetsPreloader
// В приоритете ищутся ресурсы из ресурспаков по Domain+Key.
ResourcePacks,
/*
Дополнительные источники ресурсов.
Используется для поиска ресурса по хешу от сервера (может стоит тот же мод с совпадающими ресурсами),
или для временной подгрузки ресурса по Domain+Key пока ресурс не был получен с сервера.
*/
ExtraSource;
// Менеджер файлового кэша.
AssetsCacheManager::Ptr Cache;
// Указатели на доступные ресурсы
std::unordered_map<ResourceFile::Hash_t, std::vector<fs::path>> HashToPath;
// Таблица релинковки ассетов с идентификаторов сервера на клиентские.
std::array<
std::vector<ResourceId>,
static_cast<size_t>(EnumAssets::MAX_ENUM)
> ServerToClientMap;
// Таблица серверных привязок HH (id клиентские)
std::array<
std::vector<std::tuple<ResourceFile::Hash_t, ResourceHeader>>,
static_cast<size_t>(EnumAssets::MAX_ENUM)
> ServerIdToHH;
// Ресурсы в ожидании данных по хешу для обновления (с диска, кеша, сервера).
std::array<
std::vector<ResourceId>,
static_cast<size_t>(EnumAssets::MAX_ENUM)
> PendingUpdateFromAsync;
// Хеши, для которых где-то висит задача на загрузку.
std::unordered_set<ResourceFile::Hash_t> WaitingHashes;
// Хеши, которые необходимо запросить с сервера.
std::vector<ResourceFile::Hash_t> NeedToRequestFromServer;
// Ресурсы, которые нужно считать с диска
std::vector<std::tuple<ResourceFile::Hash_t, fs::path>> NeedToReadFromDisk;
// Обновлённые ресурсы
ResourceUpdates RU;
// Когда данные были получены с диска, кеша или сервера
void _onHashLoad(const std::unordered_map<ResourceFile::Hash_t, std::u8string>& files) {
const auto& rpLinks = ResourcePacks.getResourceLinks();
const auto& esLinks = ExtraSource.getResourceLinks();
auto mapModelId = [&](ResourceId id) -> ResourceId {
const auto& map = ServerToClientMap[static_cast<size_t>(EnumAssets::Model)];
if(id >= map.size())
return 0;
return map[id];
};
auto mapTextureId = [&](ResourceId id) -> ResourceId {
const auto& map = ServerToClientMap[static_cast<size_t>(EnumAssets::Texture)];
if(id >= map.size())
return 0;
return map[id];
};
auto rebindHeader = [&](EnumAssets type, const ResourceHeader& header) -> ResourceHeader {
if(header.empty())
return {};
std::vector<uint8_t> bytes;
bytes.resize(header.size());
std::memcpy(bytes.data(), header.data(), header.size());
std::vector<uint8_t> rebound = AssetsHeaderCodec::rebindHeader(
type,
bytes,
mapModelId,
mapTextureId,
[](const std::string&) {}
);
return ResourceHeader(reinterpret_cast<const char8_t*>(rebound.data()), rebound.size());
};
for(size_t typeIndex = 0; typeIndex < static_cast<size_t>(EnumAssets::MAX_ENUM); ++typeIndex) {
auto& pending = PendingUpdateFromAsync[typeIndex];
if(pending.empty())
continue;
std::vector<ResourceId> stillPending;
stillPending.reserve(pending.size());
size_t updated = 0;
size_t missingSource = 0;
size_t missingData = 0;
for(ResourceId id : pending) {
ResourceFile::Hash_t hash{};
ResourceHeader header;
bool hasSource = false;
bool localHeader = false;
if(id < rpLinks[typeIndex].size() && rpLinks[typeIndex][id].IsExist) {
hash = rpLinks[typeIndex][id].Hash;
header = rpLinks[typeIndex][id].Header;
hasSource = true;
localHeader = true;
} else if(id < ServerIdToHH[typeIndex].size()) {
std::tie(hash, header) = ServerIdToHH[typeIndex][id];
hasSource = true;
}
if(!hasSource) {
missingSource++;
stillPending.push_back(id);
continue;
}
auto dataIter = files.find(hash);
if(dataIter == files.end()) {
missingData++;
stillPending.push_back(id);
continue;
}
std::string domain = "core";
std::string key;
{
auto d = getDK((EnumAssets) typeIndex, id);
if(d) {
domain = d->Domain;
key = d->Key;
}
}
std::u8string data = dataIter->second;
EnumAssets type = static_cast<EnumAssets>(typeIndex);
ResourceHeader finalHeader = localHeader ? header : rebindHeader(type, header);
if(id == 0)
continue;
if(type == EnumAssets::Nodestate) {
HeadlessNodeState ns;
ns.load(data);
HeadlessNodeState::Header headerParsed;
headerParsed.load(finalHeader);
RU.Nodestates.push_back({id, std::move(ns), std::move(headerParsed)});
updated++;
} else if(type == EnumAssets::Model) {
HeadlessModel hm;
hm.load(data);
HeadlessModel::Header headerParsed;
headerParsed.load(finalHeader);
RU.Models.push_back({id, std::move(hm), std::move(headerParsed)});
updated++;
} else if(type == EnumAssets::Texture) {
AssetsTextureUpdate entry;
entry.Id = id;
entry.Header = std::move(finalHeader);
if(!data.empty()) {
iResource sres(reinterpret_cast<const uint8_t*>(data.data()), data.size());
iBinaryStream stream = sres.makeStream();
png::image<png::rgba_pixel> img(stream.Stream);
entry.Width = static_cast<uint16_t>(img.get_width());
entry.Height = static_cast<uint16_t>(img.get_height());
entry.Pixels.resize(static_cast<size_t>(entry.Width) * entry.Height);
for(uint32_t y = 0; y < entry.Height; ++y) {
const auto& row = img.get_pixbuf().operator[](y);
for(uint32_t x = 0; x < entry.Width; ++x) {
const auto& px = row[x];
uint32_t rgba = (uint32_t(px.alpha) << 24)
| (uint32_t(px.red) << 16)
| (uint32_t(px.green) << 8)
| uint32_t(px.blue);
entry.Pixels[x + y * entry.Width] = rgba;
}
}
}
RU.Textures.push_back(std::move(entry));
updated++;
} else if(type == EnumAssets::Particle) {
RU.Particles.push_back({id, std::move(data)});
updated++;
} else if(type == EnumAssets::Animation) {
RU.Animations.push_back({id, std::move(data)});
updated++;
} else if(type == EnumAssets::Sound) {
RU.Sounds.push_back({id, std::move(data)});
updated++;
} else if(type == EnumAssets::Font) {
RU.Fonts.push_back({id, std::move(data)});
updated++;
}
}
if(updated || missingSource || missingData) {
LOG.debug() << "HashLoad type=" << int(typeIndex)
<< " updated=" << updated
<< " missingSource=" << missingSource
<< " missingData=" << missingData;
}
pending = std::move(stillPending);
}
for(const auto& [hash, res] : files)
WaitingHashes.erase(hash);
}
};
} // namespace LV::Client

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

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
@@ -48,7 +45,7 @@ struct DeviceId {
struct Settings {
DeviceId DeviceMain;
uint32_t QueueGraphics = -1, QueueSurface = -1;
bool Debug = false;
bool Debug = true;
bool isValid()
{
@@ -74,9 +71,10 @@ class Buffer;
Vulkan.reInit();
*/
class Vulkan : public AsyncObject {
class Vulkan {
private:
Logger LOG = "Vulkan";
asio::io_context &IOC;
struct vkInstanceLayer {
std::string LayerName = "nullptr", Description = "nullptr";
@@ -178,7 +176,7 @@ private:
asio::executor_work_guard<asio::io_context::executor_type> GuardLock;
public:
struct GraphicsObj_t {
struct {
std::vector<vkInstanceLayer> InstanceLayers;
std::vector<vkInstanceExtension> InstanceExtensions;
std::vector<std::string> GLFWExtensions;
@@ -191,7 +189,7 @@ public:
VkPhysicalDevice PhysicalDevice = nullptr;
VkPhysicalDeviceMemoryProperties DeviceMemoryProperties = {0};
VkDevice Device = nullptr;
SpinlockObject<VkQueue> DeviceQueueGraphic;
VkQueue DeviceQueueGraphic = VK_NULL_HANDLE;
VkFormat SurfaceFormat = VK_FORMAT_B8G8R8A8_UNORM;
VkColorSpaceKHR SurfaceColorSpace = VK_COLOR_SPACE_MAX_ENUM_KHR;
@@ -226,10 +224,6 @@ public:
// Идентификатор потока графики
std::thread::id ThisThread = std::this_thread::get_id();
GraphicsObj_t()
: DeviceQueueGraphic(VK_NULL_HANDLE)
{}
} Graphics;
enum struct DrawState {
@@ -250,8 +244,7 @@ public:
DestroyLock UseLock;
std::thread MainThread;
std::shared_ptr<VulkanRenderSession> RSession;
ServerSession::Ptr Session;
bool Выйти = false;
std::unique_ptr<ServerSession> Session;
std::list<void (Vulkan::*)()> ImGuiInterfaces;
std::unique_ptr<ServerObj> Server;
@@ -542,7 +535,6 @@ public:
class CommandBuffer {
VkCommandBuffer Buffer = VK_NULL_HANDLE;
Vulkan *Instance;
VkFence Fence = VK_NULL_HANDLE;
std::vector<std::function<void()>> AfterExecute;
VkCommandPool OffthreadPool = VK_NULL_HANDLE;
@@ -1027,25 +1019,5 @@ public:
virtual ~PipelineVGF();
};
enum class EnumRenderStage {
// Постройка буфера команд на рисовку
// В этот период не должно быть изменений в таблице,
// хранящей указатели на данные для рендера ChunkMesh
// Можно работать с миром
// Здесь нужно дождаться завершения работы с ChunkMesh
ComposingCommandBuffer,
// В этот период можно менять ChunkMesh
// Можно работать с миром
Render,
// В этот период нельзя работать с миром
// Можно менять ChunkMesh
// Здесь нужно дождаться завершения работы с миром, только в
// этом этапе могут приходить события изменения чанков и определений
WorldUpdate,
Shutdown
};
} /* namespace TOS::Navie::VK */

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,399 +0,0 @@
#include "AssetsPreloader.hpp"
#include "Common/Abstract.hpp"
#include "Common/TexturePipelineProgram.hpp"
#include "sha2.hpp"
#include <atomic>
#include <filesystem>
#include <fstream>
#include <unordered_set>
#include <utility>
namespace LV {
static TOS::Logger LOG = "AssetsPreloader";
static ResourceFile readFileBytes(const fs::path& path) {
std::ifstream file(path, std::ios::binary);
if(!file)
throw std::runtime_error("Не удалось открыть файл: " + path.string());
file.seekg(0, std::ios::end);
std::streamoff size = file.tellg();
if(size < 0)
size = 0;
file.seekg(0, std::ios::beg);
ResourceFile out;
out.Data.resize(static_cast<size_t>(size));
if(size > 0) {
file.read(reinterpret_cast<char*>(out.Data.data()), size);
if (!file)
throw std::runtime_error("Не удалось прочитать файл: " + path.string());
}
out.calcHash();
return out;
}
static std::u8string readOptionalMeta(const fs::path& path) {
fs::path metaPath = path;
metaPath += ".meta";
if(!fs::exists(metaPath) || !fs::is_regular_file(metaPath))
return {};
ResourceFile meta = readFileBytes(metaPath);
return std::move(meta.Data);
}
AssetsPreloader::AssetsPreloader() {
for(size_t type = 0; type < static_cast<size_t>(EnumAssets::MAX_ENUM); type++) {
ResourceLinks[type].emplace_back(
ResourceFile::Hash_t{0},
ResourceHeader(),
fs::file_time_type(),
fs::path{""},
false
);
}
}
AssetsPreloader::Out_checkAndPrepareResourcesUpdate AssetsPreloader::checkAndPrepareResourcesUpdate(
const AssetsRegister& instances,
const std::function<ResourceId(EnumAssets type, std::string_view domain, std::string_view key)>& idResolver,
const std::function<void(std::u8string&& resource, ResourceFile::Hash_t hash, fs::path resPath)>& onNewResourceParsed,
ReloadStatus* status
) {
assert(idResolver);
#ifndef NDEBUG
bool expected = false;
assert(_Reloading.compare_exchange_strong(expected, true) && "Двойной вызов reloadResources");
struct ReloadGuard {
std::atomic<bool>& Flag;
~ReloadGuard() { Flag.exchange(false); }
} guard{_Reloading};
#endif
try {
ReloadStatus secondStatus;
return _checkAndPrepareResourcesUpdate(instances, idResolver, onNewResourceParsed, status ? *status : secondStatus);
} catch(const std::exception& exc) {
LOG.error() << exc.what();
assert(!"reloadResources: здесь не должно быть ошибок");
std::unreachable();
} catch(...) {
assert(!"reloadResources: здесь не должно быть ошибок");
std::unreachable();
}
}
AssetsPreloader::Out_checkAndPrepareResourcesUpdate AssetsPreloader::_checkAndPrepareResourcesUpdate(
const AssetsRegister& instances,
const std::function<ResourceId(EnumAssets type, std::string_view domain, std::string_view key)>& idResolver,
const std::function<void(std::u8string&& resource, ResourceFile::Hash_t hash, fs::path resPath)>& onNewResourceParsed,
ReloadStatus& status
) {
// 1) Поиск всех ресурсов и построение конечной карты ресурсов (timestamps, path, name, size)
// Карта найденных ресурсов
std::array<
std::unordered_map<
std::string, // Domain
std::unordered_map<
std::string,
ResourceFindInfo,
detail::TSVHash,
detail::TSVEq
>,
detail::TSVHash,
detail::TSVEq
>,
static_cast<size_t>(AssetType::MAX_ENUM)
> resourcesFirstStage;
for(const fs::path& instance : instances.Assets) {
try {
if(fs::is_regular_file(instance)) {
// Может архив
/// TODO: пока не поддерживается
} else if(fs::is_directory(instance)) {
// Директория
fs::path assetsRoot = instance;
fs::path assetsCandidate = instance / "assets";
if (fs::exists(assetsCandidate) && fs::is_directory(assetsCandidate))
assetsRoot = assetsCandidate;
// Директория assets существует, перебираем домены в ней
for(auto begin = fs::directory_iterator(assetsRoot), end = fs::directory_iterator(); begin != end; begin++) {
if(!begin->is_directory())
continue;
fs::path domainPath = begin->path();
std::string domain = domainPath.filename().string();
// Перебираем по типу ресурса
for(size_t type = 0; type < static_cast<size_t>(AssetType::MAX_ENUM); ++type) {
AssetType assetType = static_cast<AssetType>(type);
fs::path assetPath = domainPath / EnumAssetsToDirectory(assetType);
if (!fs::exists(assetPath) || !fs::is_directory(assetPath))
continue;
std::unordered_map<
std::string, // Key
ResourceFindInfo, // ResourceInfo,
detail::TSVHash,
detail::TSVEq
>& firstStage = resourcesFirstStage[static_cast<size_t>(assetType)][domain];
// Исследуем все ресурсы одного типа
for(auto begin = fs::recursive_directory_iterator(assetPath), end = fs::recursive_directory_iterator(); begin != end; begin++) {
if(begin->is_directory())
continue;
fs::path file = begin->path();
if(assetType == AssetType::Texture && file.extension() == ".meta")
continue;
std::string key = fs::relative(file, assetPath).generic_string();
if(firstStage.contains(key))
continue;
fs::file_time_type timestamp = fs::last_write_time(file);
if(assetType == AssetType::Texture) {
fs::path metaPath = file;
metaPath += ".meta";
if (fs::exists(metaPath) && fs::is_regular_file(metaPath)) {
auto metaTime = fs::last_write_time(metaPath);
if (metaTime > timestamp)
timestamp = metaTime;
}
}
// Работаем с ресурсом
firstStage[key] = ResourceFindInfo{
.Path = file,
.Timestamp = timestamp,
.Id = idResolver(assetType, domain, key)
};
}
}
}
} else {
throw std::runtime_error("Неизвестный тип инстанса медиаресурсов");
}
} catch (const std::exception& exc) {
/// TODO: Логгировать в статусе
}
}
// Функция парсинга ресурсов
auto buildResource = [&](AssetType type, std::string_view domain, std::string_view key, const ResourceFindInfo& info) -> PendingResource {
PendingResource out;
out.Key = key;
out.Timestamp = info.Timestamp;
std::function<uint32_t(const std::string_view)> modelResolver
= [&](const std::string_view model) -> uint32_t
{
auto [mDomain, mKey] = parseDomainKey(model, domain);
return idResolver(AssetType::Model, mDomain, mKey);
};
std::function<std::optional<uint32_t>(std::string_view)> textureIdResolver
= [&](std::string_view texture) -> std::optional<uint32_t>
{
auto [mDomain, mKey] = parseDomainKey(texture, domain);
return idResolver(AssetType::Texture, mDomain, mKey);
};
std::function<std::vector<uint8_t>(const std::string_view)> textureResolver
= [&](const std::string_view texturePipelineSrc) -> std::vector<uint8_t>
{
TexturePipelineProgram tpp;
bool flag = tpp.compile(texturePipelineSrc);
if(!flag)
return {};
tpp.link(textureIdResolver);
return tpp.toBytes();
};
if (type == AssetType::Nodestate) {
ResourceFile file = readFileBytes(info.Path);
std::string_view view(reinterpret_cast<const char*>(file.Data.data()), file.Data.size());
js::object obj = js::parse(view).as_object();
HeadlessNodeState hns;
out.Header = hns.parse(obj, modelResolver);
out.Resource = hns.dump();
out.Hash = sha2::sha256((const uint8_t*) out.Resource.data(), out.Resource.size());
} else if (type == AssetType::Model) {
const std::string ext = info.Path.extension().string();
if (ext == ".json") {
ResourceFile file = readFileBytes(info.Path);
std::string_view view(reinterpret_cast<const char*>(file.Data.data()), file.Data.size());
js::object obj = js::parse(view).as_object();
HeadlessModel hm;
out.Header = hm.parse(obj, modelResolver, textureResolver);
out.Resource = hm.dump();
out.Hash = sha2::sha256((const uint8_t*) out.Resource.data(), out.Resource.size());
// } else if (ext == ".gltf" || ext == ".glb") {
// /// TODO: добавить поддержку gltf
// ResourceFile file = readFileBytes(info.Path);
// out.Resource = std::make_shared<std::vector<uint8_t>>(std::move(file.Data));
// out.Hash = file.Hash;
} else {
throw std::runtime_error("Не поддерживаемый формат модели: " + info.Path.string());
}
} else if (type == AssetType::Texture) {
ResourceFile file = readFileBytes(info.Path);
out.Resource = std::move(file.Data);
out.Hash = file.Hash;
out.Header = readOptionalMeta(info.Path);
} else {
ResourceFile file = readFileBytes(info.Path);
out.Resource = std::move(file.Data);
out.Hash = file.Hash;
}
out.Id = idResolver(type, domain, key);
return out;
};
// 2) Определяем какие ресурсы изменились (новый timestamp) или новые ресурсы
Out_checkAndPrepareResourcesUpdate result;
// Собираем идентификаторы, чтобы потом определить какие ресурсы пропали
std::array<
std::unordered_set<ResourceId>,
static_cast<size_t>(EnumAssets::MAX_ENUM)
> uniqueExists;
for(size_t type = 0; type < static_cast<size_t>(AssetType::MAX_ENUM); ++type) {
auto& uniqueExistsTypes = uniqueExists[type];
const auto& resourceLinksTyped = ResourceLinks[type];
result.MaxNewSize[type] = resourceLinksTyped.size();
{
size_t allIds = 0;
for(const auto& [domain, keys] : resourcesFirstStage[type])
allIds += keys.size();
uniqueExistsTypes.reserve(allIds);
}
for(const auto& [domain, keys] : resourcesFirstStage[type]) {
for(const auto& [key, res] : keys) {
uniqueExistsTypes.insert(res.Id);
if(res.Id >= resourceLinksTyped.size() || !resourceLinksTyped[res.Id].IsExist)
{ // Если идентификатора нет в таблице или ресурс не привязан
PendingResource resource = buildResource(static_cast<AssetType>(type), domain, key, res);
if(onNewResourceParsed)
onNewResourceParsed(std::move(resource.Resource), resource.Hash, res.Path);
result.HashToPathNew[resource.Hash].push_back(res.Path);
if(res.Id >= result.MaxNewSize[type])
result.MaxNewSize[type] = res.Id+1;
result.ResourceUpdates[type].emplace_back(res.Id, resource.Hash, std::move(resource.Header), resource.Timestamp, res.Path);
} else if(resourceLinksTyped[res.Id].Path != res.Path
|| resourceLinksTyped[res.Id].LastWrite != res.Timestamp
) { // Если ресурс теперь берётся с другого места или изменилось время изменения файла
const auto& lastResource = resourceLinksTyped[res.Id];
PendingResource resource = buildResource(static_cast<AssetType>(type), domain, key, res);
if(lastResource.Hash != resource.Hash) {
// Хэш изменился
// Сообщаем о новом ресурсе
if(onNewResourceParsed)
onNewResourceParsed(std::move(resource.Resource), resource.Hash, res.Path);
// Старый хэш более не доступен по этому расположению.
result.HashToPathLost[lastResource.Hash].push_back(resourceLinksTyped[res.Id].Path);
// Новый хеш стал доступен по этому расположению.
result.HashToPathNew[resource.Hash].push_back(res.Path);
} else if(resourceLinksTyped[res.Id].Path != res.Path) {
// Изменился конечный путь.
// Хэш более не доступен по этому расположению.
result.HashToPathLost[resource.Hash].push_back(resourceLinksTyped[res.Id].Path);
// Хеш теперь доступен по этому расположению.
result.HashToPathNew[resource.Hash].push_back(res.Path);
} else {
// Ресурс без заголовка никак не изменился.
}
// Чтобы там не поменялось, мог поменятся заголовок. Уведомляем о новой привязке.
result.ResourceUpdates[type].emplace_back(res.Id, resource.Hash, std::move(resource.Header), resource.Timestamp, res.Path);
} else {
// Ресурс не изменился
}
}
}
}
// 3) Определяем какие ресурсы пропали
for(size_t type = 0; type < static_cast<size_t>(AssetType::MAX_ENUM); ++type) {
const auto& resourceLinksTyped = ResourceLinks[type];
size_t counter = 0;
for(const auto& [hash, header, timestamp, path, isExist] : resourceLinksTyped) {
size_t id = counter++;
if(!isExist)
continue;
if(uniqueExists[type].contains(id))
continue;
// Ресурс потерян
// Хэш более не доступен по этому расположению.
result.HashToPathLost[hash].push_back(path);
result.LostLinks[type].push_back(id);
}
}
return result;
}
AssetsPreloader::Out_applyResourcesUpdate AssetsPreloader::applyResourcesUpdate(const Out_checkAndPrepareResourcesUpdate& orr) {
Out_applyResourcesUpdate result;
for(size_t type = 0; type < static_cast<size_t>(EnumAssets::MAX_ENUM); ++type) {
// Затираем потерянные
for(ResourceId id : orr.LostLinks[type]) {
assert(id < ResourceLinks[type].size());
auto& [hash, header, timestamp, path, isExist] = ResourceLinks[type][id];
hash = {0};
header = {};
timestamp = fs::file_time_type();
path.clear();
isExist = false;
result.NewOrUpdates[type].emplace_back(id, hash, header);
}
// Увеличиваем размер, если необходимо
if(orr.MaxNewSize[type] > ResourceLinks[type].size()) {
ResourceLink def{
ResourceFile::Hash_t{0},
ResourceHeader(),
fs::file_time_type(),
fs::path{""},
false
};
ResourceLinks[type].resize(orr.MaxNewSize[type], def);
}
// Обновляем / добавляем
for(auto& [id, hash, header, timestamp, path] : orr.ResourceUpdates[type]) {
ResourceLinks[type][id] = {hash, std::move(header), timestamp, std::move(path), true};
result.NewOrUpdates[type].emplace_back(id, hash, header);
}
}
return result;
}
}

View File

@@ -1,293 +0,0 @@
#pragma once
#include <array>
#include <cstdint>
#include <cstring>
#include <filesystem>
#include <functional>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <tuple>
#include <unordered_map>
#include <vector>
#include "Abstract.hpp"
#include "Common/Abstract.hpp"
/*
Класс отвечает за отслеживание изменений и подгрузки медиаресурсов в указанных директориях.
Медиаресурсы, собранные из папки assets или зарегистрированные модами.
*/
static constexpr const char* EnumAssetsToDirectory(LV::EnumAssets value) {
switch(value) {
case LV::EnumAssets::Nodestate: return "nodestate";
case LV::EnumAssets::Particle: return "particle";
case LV::EnumAssets::Animation: return "animation";
case LV::EnumAssets::Model: return "model";
case LV::EnumAssets::Texture: return "texture";
case LV::EnumAssets::Sound: return "sound";
case LV::EnumAssets::Font: return "font";
default:
break;
}
assert(!"Неизвестный тип медиаресурса");
return "";
}
namespace LV {
namespace fs = std::filesystem;
using AssetType = EnumAssets;
class AssetsPreloader {
public:
using Ptr = std::shared_ptr<AssetsPreloader>;
/*
Ресурс имеет бинарную часть, из который вырезаны все зависимости.
Вторая часть это заголовок, которые всегда динамично передаётся с сервера.
В заголовке хранятся зависимости от ресурсов.
*/
struct MediaResource {
std::string Domain, Key;
fs::file_time_type Timestamp;
// Обезличенный ресурс
std::shared_ptr<std::u8string> Resource;
// Хэш ресурса
ResourceFile::Hash_t Hash;
// Скомпилированный заголовок
std::u8string Header;
};
struct PendingResource {
uint32_t Id;
std::string Key;
fs::file_time_type Timestamp;
// Обезличенный ресурс
std::u8string Resource;
// Его хеш
ResourceFile::Hash_t Hash;
// Заголовок
std::u8string Header;
};
struct ReloadStatus {
/// TODO: callback'и для обновления статусов
/// TODO: многоуровневый статус std::vector<std::string>. Этапы/Шаги/Объекты
};
struct AssetsRegister {
/*
Пути до активных папок assets, соответствую порядку загруженным модам.
От последнего мода к первому.
Тот файл, что был загружен раньше и будет использоваться
*/
std::vector<fs::path> Assets;
/*
У этих ресурсов приоритет выше, если их удастся получить,
то использоваться будут именно они
Domain -> {key + data}
*/
std::array<
std::unordered_map<
std::string,
std::unordered_map<std::string, void*>
>,
static_cast<size_t>(AssetType::MAX_ENUM)
> Custom;
};
public:
AssetsPreloader();
~AssetsPreloader() = default;
AssetsPreloader(const AssetsPreloader&) = delete;
AssetsPreloader(AssetsPreloader&&) = delete;
AssetsPreloader& operator=(const AssetsPreloader&) = delete;
AssetsPreloader& operator=(AssetsPreloader&&) = delete;
/*
Перепроверка изменений ресурсов по дате изменения, пересчёт хешей.
Обнаруженные изменения должны быть отправлены всем клиентам.
Ресурсы будут обработаны в подходящий формат и сохранены в кеше.
Используется в GameServer.
! Одновременно можно работать только один такой вызов.
! Бронирует идентификаторы используя getId();
instances -> пути к директории с assets или архивы с assets внутри. От низшего приоритета к высшему.
idResolver -> функция получения идентификатора по Тип+Домен+Ключ
onNewResourceParsed -> Callback на обработку распаршенных ресурсов без заголовков
(на стороне сервера хранится в другой сущности, на стороне клиента игнорируется).
status -> обратный отклик о процессе обновления ресурсов.
ReloadStatus <- новые и потерянные ресурсы.
*/
struct Out_checkAndPrepareResourcesUpdate {
// Новые связки Id -> Hash + Header + Timestamp + Path (ресурс новый или изменён)
std::array<
std::vector<
std::tuple<
ResourceId, // Ресурс
ResourceFile::Hash_t, // Хэш ресурса на диске
ResourceHeader, // Хедер ресурса (со всеми зависимостями)
fs::file_time_type, // Время изменения ресурса на диске
fs::path // Путь до ресурса
>
>,
static_cast<size_t>(AssetType::MAX_ENUM)
> ResourceUpdates;
// Используется чтобы эффективно увеличить размер таблиц
std::array<
ResourceId,
static_cast<size_t>(AssetType::MAX_ENUM)
> MaxNewSize;
// Потерянные связки Id (ресурс физически потерян)
std::array<
std::vector<ResourceId>,
static_cast<size_t>(AssetType::MAX_ENUM)
> LostLinks;
/*
Новые пути предоставляющие хеш
(по каким путям можно получить ресурс определённого хеша).
*/
std::unordered_map<
ResourceFile::Hash_t,
std::vector<fs::path>
> HashToPathNew;
/*
Потерянные пути, предоставлявшые ресурсы с данным хешем
(пути по которым уже нельзя получить заданных хеш).
*/
std::unordered_map<
ResourceFile::Hash_t,
std::vector<fs::path>
> HashToPathLost;
};
Out_checkAndPrepareResourcesUpdate checkAndPrepareResourcesUpdate(
const AssetsRegister& instances,
const std::function<ResourceId(EnumAssets type, std::string_view domain, std::string_view key)>& idResolver,
const std::function<void(std::u8string&& resource, ResourceFile::Hash_t hash, fs::path resPath)>& onNewResourceParsed = nullptr,
ReloadStatus* status = nullptr
);
/*
Применяет расчитанные изменения.
Out_applyResourceUpdate <- Нужно отправить клиентам новые привязки ресурсов
id -> hash+header
*/
struct BindHashHeaderInfo {
ResourceId Id;
ResourceFile::Hash_t Hash;
ResourceHeader Header;
};
struct Out_applyResourcesUpdate {
std::array<
std::vector<BindHashHeaderInfo>,
static_cast<size_t>(EnumAssets::MAX_ENUM)
> NewOrUpdates;
};
Out_applyResourcesUpdate applyResourcesUpdate(const Out_checkAndPrepareResourcesUpdate& orr);
std::array<
std::vector<BindHashHeaderInfo>,
static_cast<size_t>(EnumAssets::MAX_ENUM)
> collectHashBindings() const
{
std::array<
std::vector<BindHashHeaderInfo>,
static_cast<size_t>(EnumAssets::MAX_ENUM)
> result;
for(size_t type = 0; type < static_cast<size_t>(EnumAssets::MAX_ENUM); ++type) {
result[type].reserve(ResourceLinks[type].size());
ResourceId counter = 0;
for(const auto& [hash, header, _1, _2, _3] : ResourceLinks[type]) {
ResourceId id = counter++;
result[type].emplace_back(id, hash, header);
}
}
return result;
}
const auto& getResourceLinks() const {
return ResourceLinks;
}
struct Out_Resource {
ResourceFile::Hash_t Hash;
fs::path Path;
};
std::optional<Out_Resource> getResource(EnumAssets type, ResourceId id) const {
const auto& rl = ResourceLinks[static_cast<size_t>(type)];
if(id >= rl.size() || !rl[id].IsExist)
return std::nullopt;
return Out_Resource{rl[id].Hash, rl[id].Path};
}
private:
struct ResourceFindInfo {
// Путь к архиву (если есть), и путь до ресурса
fs::path ArchivePath, Path;
// Время изменения файла
fs::file_time_type Timestamp;
// Идентификатор ресурса
ResourceId Id;
};
struct HashHasher {
std::size_t operator()(const ResourceFile::Hash_t& hash) const noexcept {
std::size_t v = 14695981039346656037ULL;
for (const auto& byte : hash) {
v ^= static_cast<std::size_t>(byte);
v *= 1099511628211ULL;
}
return v;
}
};
#ifndef NDEBUG
// Текущее состояние reloadResources
std::atomic<bool> _Reloading = false;
#endif
struct ResourceLink {
ResourceFile::Hash_t Hash; // Хэш ресурса на диске
/// TODO: клиенту не нужны хедеры
ResourceHeader Header; // Хедер ресурса (со всеми зависимостями)
fs::file_time_type LastWrite; // Время изменения ресурса на диске
fs::path Path; // Путь до ресурса
bool IsExist;
};
Out_checkAndPrepareResourcesUpdate _checkAndPrepareResourcesUpdate(
const AssetsRegister& instances,
const std::function<ResourceId(EnumAssets type, std::string_view domain, std::string_view key)>& idResolver,
const std::function<void(std::u8string&& resource, ResourceFile::Hash_t hash, fs::path resPath)>& onNewResourceParsed,
ReloadStatus& status
);
// Привязка Id -> Hash + Header + Timestamp + Path
std::array<
std::vector<ResourceLink>,
static_cast<size_t>(AssetType::MAX_ENUM)
> ResourceLinks;
};
}

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

@@ -1,272 +0,0 @@
#pragma once
#include "Common/Abstract.hpp"
#include <ankerl/unordered_dense.h>
#include <array>
#include <atomic>
#include <cassert>
#include <optional>
#include <shared_mutex>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include <algorithm>
namespace LV {
template<class Enum = EnumAssets, size_t ShardCount = 64>
class IdProvider {
public:
static constexpr size_t MAX_ENUM = static_cast<size_t>(Enum::MAX_ENUM);
struct BindDomainKeyInfo {
std::string Domain, Key;
};
struct BindDomainKeyViewInfo {
std::string_view Domain, Key;
};
struct KeyHash {
using is_transparent = void;
static inline std::size_t h(std::string_view sv) noexcept {
return std::hash<std::string_view>{}(sv);
}
static inline std::size_t mix(std::size_t a, std::size_t b) noexcept {
a ^= b + 0x9e3779b97f4a7c15ULL + (a << 6) + (a >> 2);
return a;
}
std::size_t operator()(const BindDomainKeyInfo& k) const noexcept {
return mix(h(k.Domain), h(k.Key));
}
std::size_t operator()(const BindDomainKeyViewInfo& kv) const noexcept {
return mix(h(kv.Domain), h(kv.Key));
}
};
struct KeyEq {
using is_transparent = void;
bool operator()(const BindDomainKeyInfo& a, const BindDomainKeyInfo& b) const noexcept {
return a.Domain == b.Domain && a.Key == b.Key;
}
bool operator()(const BindDomainKeyInfo& a, const BindDomainKeyViewInfo& b) const noexcept {
return a.Domain == b.Domain && a.Key == b.Key;
}
bool operator()(const BindDomainKeyViewInfo& a, const BindDomainKeyInfo& b) const noexcept {
return a.Domain == b.Domain && a.Key == b.Key;
}
};
public:
explicit IdProvider() {
for(size_t type = 0; type < MAX_ENUM; ++type) {
_NextId[type].store(1, std::memory_order_relaxed);
_Reverse[type].reserve(1024);
IdToDK[type].push_back({"core", "none"});
auto& sh = _shardFor(static_cast<Enum>(type), "core", "none");
std::unique_lock lk(sh.mutex);
sh.map.emplace(BindDomainKeyInfo{"core", "none"}, 0);
// ensure id 0 has a reverse mapping too
_storeReverse(static_cast<Enum>(type), 0, std::string("core"), std::string("none"));
}
}
/*
Находит или выдаёт идентификатор на запрошенный ресурс.
Функция не требует внешней синхронизации.
*/
inline ResourceId getId(Enum type, std::string_view domain, std::string_view key) {
#ifndef NDEBUG
assert(!DKToIdInBakingMode);
#endif
auto& sh = _shardFor(type, domain, key);
// 1) Поиск в режиме для чтения
{
std::shared_lock lk(sh.mutex);
if(auto it = sh.map.find(BindDomainKeyViewInfo{domain, key}); it != sh.map.end()) {
return it->second;
}
}
// 2) Блокируем и повторно ищем запись (может кто уже успел её добавить)
std::unique_lock lk(sh.mutex);
if (auto it = sh.map.find(BindDomainKeyViewInfo{domain, key}); it != sh.map.end()) {
return it->second;
}
// Выделяем идентификатор
ResourceId id = _NextId[static_cast<size_t>(type)].fetch_add(1, std::memory_order_relaxed);
std::string d(domain);
std::string k(key);
sh.map.emplace(BindDomainKeyInfo{d, k}, id);
sh.newlyInserted.push_back(id);
_storeReverse(type, id, std::move(d), std::move(k));
return id;
}
/*
Переносит все новые идентификаторы в основную таблицу.
В этой реализации "основная таблица" уже основная (forward map обновляется сразу),
а bake() собирает только новые привязки (domain,key) по логам вставок и дополняет IdToDK.
Нельзя использовать пока есть вероятность что кто-то использует getId(), если ты хочешь
строгий debug-контроль как раньше. В релизе это не требуется: bake читает только reverse,
а forward не трогает.
*/
std::array<std::vector<BindDomainKeyInfo>, MAX_ENUM> bake() {
#ifndef NDEBUG
assert(!DKToIdInBakingMode);
DKToIdInBakingMode = true;
struct _tempStruct {
IdProvider* handler;
~_tempStruct() { handler->DKToIdInBakingMode = false; }
} _lock{this};
#endif
std::array<std::vector<BindDomainKeyInfo>, MAX_ENUM> result;
for(size_t t = 0; t < MAX_ENUM; ++t) {
auto type = static_cast<Enum>(t);
// 1) собрать новые id из всех шардов
std::vector<ResourceId> new_ids;
_drainNew(type, new_ids);
if(new_ids.empty())
continue;
// 2) превратить id -> (domain,key) через reverse и вернуть наружу
// + дописать в IdToDK[type] в порядке id (по желанию)
std::sort(new_ids.begin(), new_ids.end());
new_ids.erase(std::unique(new_ids.begin(), new_ids.end()), new_ids.end());
result[t].reserve(new_ids.size());
// reverse читаем под shared lock
std::shared_lock rlk(_ReverseMutex[t]);
for(ResourceId id : new_ids) {
const std::size_t idx = static_cast<std::size_t>(id);
if(idx >= _Reverse[t].size()) {
// теоретически не должно случаться (мы пишем reverse до push в log)
continue;
}
const auto& e = _Reverse[t][idx];
result[t].push_back({e.Domain, e.Key});
}
rlk.unlock();
// 3) дописать в IdToDK (для новых клиентов)
IdToDK[t].append_range(result[t]);
}
return result;
}
// id to DK
std::optional<BindDomainKeyInfo> getDK(Enum type, ResourceId id) {
auto& vec = _Reverse[static_cast<size_t>(type)];
auto& mtx = _ReverseMutex[static_cast<size_t>(type)];
std::unique_lock lk(mtx);
if(id >= vec.size())
return std::nullopt;
return vec[id];
}
// Для отправки новым подключенным клиентам
const std::array<std::vector<BindDomainKeyInfo>, MAX_ENUM>& idToDK() const {
return IdToDK;
}
private:
using Map = ankerl::unordered_dense::map<BindDomainKeyInfo, ResourceId, KeyHash, KeyEq>;
struct Shard {
mutable std::shared_mutex mutex;
Map map;
std::vector<ResourceId> newlyInserted;
};
private:
// Кластер таблиц идентификаторов
std::array<
std::array<Shard, ShardCount>, MAX_ENUM
> _Shards;
// Счётчики идентификаторов
std::array<std::atomic<ResourceId>, MAX_ENUM> _NextId;
// Таблица обратных связок (Id to DK)
std::array<std::vector<BindDomainKeyInfo>, MAX_ENUM> _Reverse;
mutable std::array<std::shared_mutex, MAX_ENUM> _ReverseMutex;
#ifndef NDEBUG
bool DKToIdInBakingMode = false;
#endif
// stable "full sync" table for new clients:
std::array<std::vector<BindDomainKeyInfo>, MAX_ENUM> IdToDK;
private:
Shard& _shardFor(Enum type, const std::string_view domain, const std::string_view key) {
const std::size_t idx = KeyHash{}(BindDomainKeyViewInfo{domain, key}) % ShardCount;
return _Shards[static_cast<size_t>(type)][idx];
}
const Shard& _shardFor(Enum type, const std::string_view domain, const std::string_view key) const {
const std::size_t idx = KeyHash{}(BindDomainKeyViewInfo{domain, key}) % ShardCount;
return _Shards[static_cast<size_t>(type)][idx];
}
void _storeReverse(Enum type, ResourceId id, std::string&& domain, std::string&& key) {
auto& vec = _Reverse[static_cast<size_t>(type)];
auto& mtx = _ReverseMutex[static_cast<size_t>(type)];
const std::size_t idx = static_cast<std::size_t>(id);
std::unique_lock lk(mtx);
if(idx >= vec.size())
vec.resize(idx + 1);
vec[idx] = BindDomainKeyInfo{std::move(domain), std::move(key)};
}
void _drainNew(Enum type, std::vector<ResourceId>& out) {
out.clear();
auto& shards = _Shards[static_cast<size_t>(type)];
// Можно добавить reserve по эвристике
for (auto& sh : shards) {
std::unique_lock lk(sh.mutex);
if (sh.newlyInserted.empty()) continue;
const auto old = out.size();
out.resize(old + sh.newlyInserted.size());
std::copy(sh.newlyInserted.begin(), sh.newlyInserted.end(), out.begin() + old);
sh.newlyInserted.clear();
}
}
};
} // namespace LV

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();
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,21 +197,20 @@ protected:
struct SendPacketsObj {
boost::mutex Mtx;
bool WaitForSemaphore = false;
asio::deadline_timer Semaphore, SenderGuard;
asio::deadline_timer Semaphore;
boost::circular_buffer_space_optimized<Packet> SimpleBuffer;
boost::circular_buffer_space_optimized<SmartPacket> SmartBuffer;
size_t SizeInQueue = 0;
std::shared_ptr<AsyncContext> Context;
SendPacketsObj(asio::io_context &ioc)
: Semaphore(ioc, boost::posix_time::pos_infin), SenderGuard(ioc, boost::posix_time::pos_infin)
: Semaphore(ioc, boost::posix_time::pos_infin)
{}
} SendPackets;
public:
AsyncSocket(asio::io_context &ioc, tcp::socket &&socket)
: AsyncObject(ioc), Socket(std::move(socket)), SendPackets(ioc)
: IOC(ioc), Socket(std::move(socket)), SendPackets(ioc)
{
SendPackets.SimpleBuffer.set_capacity(512);
SendPackets.SmartBuffer.set_capacity(SendPackets.SimpleBuffer.capacity()/4);
@@ -279,10 +218,10 @@ protected:
boost::asio::socket_base::linger optionLinger(true, 4); // После закрытия сокета оставшиеся данные будут доставлены
Socket.set_option(optionLinger);
boost::asio::ip::tcp::no_delay optionNoDelay(true); // Отключает попытки объединить данные в крупные пакеты
boost::asio::ip::tcp::no_delay optionNoDelay(true); // Отключает попытки объёденить данные в крупные пакеты
Socket.set_option(optionNoDelay);
co_spawn(runSender(SendPackets.Context));
asio::co_spawn(IOC, runSender(SendPackets.Context), asio::detached);
}
~AsyncSocket();
@@ -300,10 +239,11 @@ protected:
coro<> read(std::byte *data, uint32_t size);
void closeRead();
void close();
template<typename T, std::enable_if_t<std::is_floating_point_v<T> or std::is_integral_v<T> or std::is_same_v<T, std::string>, int> = 0>
template<typename T, std::enable_if_t<std::is_integral_v<T> or std::is_same_v<T, std::string>, int> = 0>
coro<T> read() {
if constexpr(std::is_floating_point_v<T> or std::is_integral_v<T>) {
if constexpr(std::is_integral_v<T>) {
T value;
co_await read((std::byte*) &value, sizeof(value));
co_return swapEndian(value);
@@ -321,9 +261,9 @@ protected:
co_await asio::async_read(socket, asio::mutable_buffer(data, size));
}
template<typename T, std::enable_if_t<std::is_floating_point_v<T> or std::is_integral_v<T> or std::is_same_v<T, std::string>, int> = 0>
template<typename T, std::enable_if_t<std::is_integral_v<T> or std::is_same_v<T, std::string>, int> = 0>
static inline coro<T> read(tcp::socket &socket) {
if constexpr(std::is_floating_point_v<T> or std::is_integral_v<T>) {
if constexpr(std::is_integral_v<T>) {
T value;
co_await read(socket, (std::byte*) &value, sizeof(value));
co_return swapEndian(value);
@@ -338,7 +278,7 @@ protected:
co_await asio::async_write(socket, asio::const_buffer(data, size));
}
template<typename T, std::enable_if_t<std::is_floating_point_v<T> or std::is_integral_v<T>, int> = 0>
template<typename T, std::enable_if_t<std::is_integral_v<T>, int> = 0>
static inline coro<> write(tcp::socket &socket, T u) {
u = swapEndian(u);
co_await write(socket, (const std::byte*) &u, sizeof(u));

View File

@@ -1,483 +0,0 @@
#include "Net2.hpp"
#include <boost/asio/buffer.hpp>
#include <boost/asio/error.hpp>
#include <boost/asio/read.hpp>
#include <boost/asio/write.hpp>
#include <boost/system/system_error.hpp>
#include <algorithm>
#include <tuple>
namespace LV::Net2 {
using namespace TOS;
namespace {
struct HeaderFields {
uint32_t size = 0;
uint16_t type = 0;
Priority priority = Priority::Normal;
FrameFlags flags = FrameFlags::None;
uint32_t streamId = 0;
};
std::array<std::byte, AsyncSocket::kHeaderSize> encodeHeader(const HeaderFields &h) {
std::array<std::byte, AsyncSocket::kHeaderSize> out{};
uint32_t sizeNet = detail::toNetwork(h.size);
uint16_t typeNet = detail::toNetwork(h.type);
uint32_t streamNet = detail::toNetwork(h.streamId);
std::memcpy(out.data(), &sizeNet, sizeof(sizeNet));
std::memcpy(out.data() + 4, &typeNet, sizeof(typeNet));
out[6] = std::byte(static_cast<uint8_t>(h.priority));
out[7] = std::byte(static_cast<uint8_t>(h.flags));
std::memcpy(out.data() + 8, &streamNet, sizeof(streamNet));
return out;
}
HeaderFields decodeHeader(const std::array<std::byte, AsyncSocket::kHeaderSize> &in) {
HeaderFields h{};
std::memcpy(&h.size, in.data(), sizeof(h.size));
std::memcpy(&h.type, in.data() + 4, sizeof(h.type));
h.priority = static_cast<Priority>(std::to_integer<uint8_t>(in[6]));
h.flags = static_cast<FrameFlags>(std::to_integer<uint8_t>(in[7]));
std::memcpy(&h.streamId, in.data() + 8, sizeof(h.streamId));
h.size = detail::fromNetwork(h.size);
h.type = detail::fromNetwork(h.type);
h.streamId = detail::fromNetwork(h.streamId);
return h;
}
} // namespace
PacketWriter& PacketWriter::writeBytes(std::span<const std::byte> data) {
Buffer.insert(Buffer.end(), data.begin(), data.end());
return *this;
}
PacketWriter& PacketWriter::writeString(std::string_view str) {
write<uint32_t>(static_cast<uint32_t>(str.size()));
auto bytes = std::as_bytes(std::span<const char>(str.data(), str.size()));
Buffer.insert(Buffer.end(), bytes.begin(), bytes.end());
return *this;
}
std::vector<std::byte> PacketWriter::release() {
std::vector<std::byte> out = std::move(Buffer);
Buffer.clear();
return out;
}
void PacketWriter::clear() {
Buffer.clear();
}
PacketReader::PacketReader(std::span<const std::byte> data)
: Data(data)
{
}
void PacketReader::readBytes(std::span<std::byte> out) {
require(out.size());
std::memcpy(out.data(), Data.data() + Pos, out.size());
Pos += out.size();
}
std::string PacketReader::readString() {
uint32_t size = read<uint32_t>();
require(size);
std::string out(size, '\0');
std::memcpy(out.data(), Data.data() + Pos, size);
Pos += size;
return out;
}
void PacketReader::require(size_t size) {
if(Data.size() - Pos < size)
MAKE_ERROR("Net2::PacketReader: not enough data");
}
SocketServer::SocketServer(asio::io_context &ioc, std::function<coro<>(tcp::socket)> &&onConnect, uint16_t port)
: AsyncObject(ioc), Acceptor(ioc, tcp::endpoint(tcp::v4(), port))
{
assert(onConnect);
co_spawn(run(std::move(onConnect)));
}
bool SocketServer::isStopped() const {
return !Acceptor.is_open();
}
uint16_t SocketServer::getPort() const {
return Acceptor.local_endpoint().port();
}
coro<void> SocketServer::run(std::function<coro<>(tcp::socket)> onConnect) {
while(true) {
try {
co_spawn(onConnect(co_await Acceptor.async_accept()));
} catch(const std::exception &exc) {
if(const boost::system::system_error *errc = dynamic_cast<const boost::system::system_error*>(&exc);
errc && (errc->code() == asio::error::operation_aborted || errc->code() == asio::error::bad_descriptor))
break;
}
}
}
AsyncSocket::SendQueue::SendQueue(asio::io_context &ioc)
: semaphore(ioc)
{
semaphore.expires_at(std::chrono::steady_clock::time_point::max());
}
bool AsyncSocket::SendQueue::empty() const {
for(const auto &queue : queues) {
if(!queue.empty())
return false;
}
return true;
}
AsyncSocket::AsyncSocket(asio::io_context &ioc, tcp::socket &&socket, Limits limits)
: AsyncObject(ioc), LimitsCfg(limits), Socket(std::move(socket)), Outgoing(ioc)
{
Context = std::make_shared<AsyncContext>();
boost::asio::socket_base::linger optionLinger(true, 4);
Socket.set_option(optionLinger);
boost::asio::ip::tcp::no_delay optionNoDelay(true);
Socket.set_option(optionNoDelay);
co_spawn(sendLoop());
}
AsyncSocket::~AsyncSocket() {
if(Context)
Context->needShutdown.store(true);
{
boost::lock_guard lock(Outgoing.mtx);
Outgoing.semaphore.cancel();
WorkDeadline.cancel();
}
if(Socket.is_open())
try { Socket.close(); } catch(...) {}
}
void AsyncSocket::enqueue(OutgoingMessage &&msg) {
if(msg.payload.size() > LimitsCfg.maxMessageSize) {
setError("Net2::AsyncSocket: message too large");
close();
return;
}
boost::unique_lock lock(Outgoing.mtx);
const size_t msgSize = msg.payload.size();
const size_t lowIndex = static_cast<size_t>(Priority::Low);
if(msg.priority == Priority::Low) {
while(Outgoing.bytesInLow + msgSize > LimitsCfg.maxLowPriorityBytes && !Outgoing.queues[lowIndex].empty()) {
Outgoing.bytesInQueue -= Outgoing.queues[lowIndex].front().payload.size();
Outgoing.bytesInLow -= Outgoing.queues[lowIndex].front().payload.size();
Outgoing.queues[lowIndex].pop_front();
}
if(Outgoing.bytesInLow + msgSize > LimitsCfg.maxLowPriorityBytes) {
return;
}
}
if(Outgoing.bytesInQueue + msgSize > LimitsCfg.maxQueueBytes) {
dropLow(msgSize);
if(Outgoing.bytesInQueue + msgSize > LimitsCfg.maxQueueBytes) {
if(msg.dropIfOverloaded)
return;
setError("Net2::AsyncSocket: send queue overflow");
close();
return;
}
}
const size_t idx = static_cast<size_t>(msg.priority);
Outgoing.bytesInQueue += msgSize;
if(msg.priority == Priority::Low)
Outgoing.bytesInLow += msgSize;
Outgoing.queues[idx].push_back(std::move(msg));
if(Outgoing.waiting) {
Outgoing.waiting = false;
Outgoing.semaphore.cancel();
Outgoing.semaphore.expires_at(std::chrono::steady_clock::time_point::max());
}
}
coro<IncomingMessage> AsyncSocket::readMessage() {
while(true) {
std::array<std::byte, kHeaderSize> headerBytes{};
co_await readExact(headerBytes.data(), headerBytes.size());
HeaderFields header = decodeHeader(headerBytes);
if(header.size > LimitsCfg.maxFrameSize)
MAKE_ERROR("Net2::AsyncSocket: frame too large");
std::vector<std::byte> chunk(header.size);
if(header.size)
co_await readExact(chunk.data(), chunk.size());
if(header.streamId != 0) {
if(Fragments.size() >= LimitsCfg.maxOpenStreams && !Fragments.contains(header.streamId))
MAKE_ERROR("Net2::AsyncSocket: too many open streams");
FragmentState &state = Fragments[header.streamId];
if(state.data.empty()) {
state.type = header.type;
state.priority = header.priority;
}
if(state.data.size() + chunk.size() > LimitsCfg.maxMessageSize)
MAKE_ERROR("Net2::AsyncSocket: reassembled message too large");
state.data.insert(state.data.end(), chunk.begin(), chunk.end());
if(!hasFlag(header.flags, FrameFlags::HasMore)) {
IncomingMessage msg{state.type, state.priority, std::move(state.data)};
Fragments.erase(header.streamId);
co_return msg;
}
continue;
}
if(hasFlag(header.flags, FrameFlags::HasMore))
MAKE_ERROR("Net2::AsyncSocket: stream id missing for fragmented frame");
IncomingMessage msg{header.type, header.priority, std::move(chunk)};
co_return msg;
}
}
coro<> AsyncSocket::readLoop(std::function<coro<>(IncomingMessage&&)> onMessage) {
while(isAlive()) {
IncomingMessage msg = co_await readMessage();
co_await onMessage(std::move(msg));
}
}
void AsyncSocket::closeRead() {
if(Socket.is_open() && !Context->readClosed.exchange(true)) {
try { Socket.shutdown(boost::asio::socket_base::shutdown_receive); } catch(...) {}
}
}
void AsyncSocket::close() {
if(Context)
Context->needShutdown.store(true);
if(Socket.is_open())
try { Socket.close(); } catch(...) {}
}
bool AsyncSocket::isAlive() const {
return Context && !Context->needShutdown.load() && !Context->senderStopped.load() && Socket.is_open();
}
std::string AsyncSocket::getError() const {
boost::lock_guard lock(Context->errorMtx);
return Context->error;
}
coro<> AsyncSocket::sendLoop() {
try {
while(!Context->needShutdown.load()) {
OutgoingMessage msg;
{
boost::unique_lock lock(Outgoing.mtx);
if(Outgoing.empty()) {
Outgoing.waiting = true;
auto coroutine = Outgoing.semaphore.async_wait();
lock.unlock();
try { co_await std::move(coroutine); } catch(...) {}
continue;
}
if(!popNext(msg))
continue;
}
co_await sendMessage(std::move(msg));
}
} catch(const std::exception &exc) {
setError(exc.what());
} catch(...) {
setError("Net2::AsyncSocket: send loop stopped");
}
Context->senderStopped.store(true);
}
coro<> AsyncSocket::sendMessage(OutgoingMessage &&msg) {
const size_t total = msg.payload.size();
if(total <= LimitsCfg.maxFrameSize) {
co_await sendFrame(msg.type, msg.priority, FrameFlags::None, 0, msg.payload);
co_return;
}
if(!msg.allowFragment) {
setError("Net2::AsyncSocket: message requires fragmentation");
close();
co_return;
}
uint32_t streamId = NextStreamId++;
if(streamId == 0)
streamId = NextStreamId++;
size_t offset = 0;
while(offset < total) {
const size_t chunk = std::min(LimitsCfg.maxFrameSize, total - offset);
const bool more = (offset + chunk) < total;
FrameFlags flags = more ? FrameFlags::HasMore : FrameFlags::None;
std::span<const std::byte> view(msg.payload.data() + offset, chunk);
co_await sendFrame(msg.type, msg.priority, flags, streamId, view);
offset += chunk;
}
}
coro<> AsyncSocket::sendFrame(uint16_t type, Priority priority, FrameFlags flags, uint32_t streamId,
std::span<const std::byte> payload) {
HeaderFields header{
.size = static_cast<uint32_t>(payload.size()),
.type = type,
.priority = priority,
.flags = flags,
.streamId = streamId
};
auto headerBytes = encodeHeader(header);
std::array<asio::const_buffer, 2> buffers{
asio::buffer(headerBytes),
asio::buffer(payload.data(), payload.size())
};
if(payload.empty())
co_await asio::async_write(Socket, asio::buffer(headerBytes));
else
co_await asio::async_write(Socket, buffers);
}
coro<> AsyncSocket::readExact(std::byte *data, size_t size) {
if(size == 0)
co_return;
co_await asio::async_read(Socket, asio::buffer(data, size));
}
bool AsyncSocket::popNext(OutgoingMessage &out) {
static constexpr int kWeights[4] = {8, 4, 2, 1};
for(int attempt = 0; attempt < 4; ++attempt) {
const uint8_t idx = static_cast<uint8_t>((Outgoing.nextIndex + attempt) % 4);
auto &queue = Outgoing.queues[idx];
if(queue.empty())
continue;
if(Outgoing.credits[idx] <= 0)
Outgoing.credits[idx] = kWeights[idx];
if(Outgoing.credits[idx] <= 0)
continue;
out = std::move(queue.front());
queue.pop_front();
Outgoing.credits[idx]--;
Outgoing.nextIndex = idx;
const size_t msgSize = out.payload.size();
Outgoing.bytesInQueue -= msgSize;
if(idx == static_cast<uint8_t>(Priority::Low))
Outgoing.bytesInLow -= msgSize;
return true;
}
for(int i = 0; i < 4; ++i)
Outgoing.credits[i] = kWeights[i];
return false;
}
void AsyncSocket::dropLow(size_t needBytes) {
const size_t lowIndex = static_cast<size_t>(Priority::Low);
while(Outgoing.bytesInQueue + needBytes > LimitsCfg.maxQueueBytes && !Outgoing.queues[lowIndex].empty()) {
const size_t size = Outgoing.queues[lowIndex].front().payload.size();
Outgoing.bytesInQueue -= size;
Outgoing.bytesInLow -= size;
Outgoing.queues[lowIndex].pop_front();
}
}
void AsyncSocket::setError(const std::string &msg) {
if(!Context)
return;
boost::lock_guard lock(Context->errorMtx);
Context->error = msg;
}
coro<tcp::socket> asyncConnectTo(const std::string &address,
std::function<void(const std::string&)> onProgress) {
std::string progress;
auto addLog = [&](const std::string &msg) {
progress += '\n';
progress += msg;
if(onProgress)
onProgress('\n' + msg);
};
auto ioc = co_await asio::this_coro::executor;
addLog("Parsing address " + address);
auto re = Str::match(address, "((?:\\[[\\d\\w:]+\\])|(?:[\\d\\.]+))(?:\\:(\\d+))?");
std::vector<std::tuple<tcp::endpoint, std::string>> eps;
if(!re) {
re = Str::match(address, "([-_\\.\\w\\d]+)(?:\\:(\\d+))?");
if(!re)
MAKE_ERROR("Failed to parse address");
tcp::resolver resv{ioc};
tcp::resolver::results_type result;
addLog("Resolving name...");
result = co_await resv.async_resolve(*re->at(1), re->at(2) ? *re->at(2) : "7890");
addLog("Got " + std::to_string(result.size()) + " endpoints");
for(auto iter : result) {
std::string addr = iter.endpoint().address().to_string() + ':' + std::to_string(iter.endpoint().port());
std::string hostname = iter.host_name();
if(hostname == addr)
addLog("ep: " + addr);
else
addLog("ep: " + hostname + " (" + addr + ')');
eps.emplace_back(iter.endpoint(), iter.host_name());
}
} else {
eps.emplace_back(tcp::endpoint{asio::ip::make_address(*re->at(1)),
static_cast<uint16_t>(re->at(2) ? Str::toVal<int>(*re->at(2)) : 7890)},
*re->at(1));
}
for(auto [ep, hostname] : eps) {
addLog("Connecting to " + hostname + " (" + ep.address().to_string() + ':'
+ std::to_string(ep.port()) + ")");
try {
tcp::socket sock{ioc};
co_await sock.async_connect(ep);
addLog("Connected");
co_return sock;
} catch(const std::exception &exc) {
addLog(std::string("Connect failed: ") + exc.what());
}
}
MAKE_ERROR("Unable to connect to server");
}
} // namespace LV::Net2

View File

@@ -1,227 +0,0 @@
#pragma once
#include "Async.hpp"
#include "TOSLib.hpp"
#include <boost/asio.hpp>
#include <boost/thread.hpp>
#include <array>
#include <bit>
#include <atomic>
#include <cassert>
#include <chrono>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <deque>
#include <memory>
#include <optional>
#include <span>
#include <string>
#include <string_view>
#include <type_traits>
#include <unordered_map>
#include <vector>
namespace LV::Net2 {
namespace detail {
constexpr bool kLittleEndian = (std::endian::native == std::endian::little);
template<typename T>
requires std::is_integral_v<T>
inline T toNetwork(T value) {
if constexpr (kLittleEndian && sizeof(T) > 1)
return std::byteswap(value);
return value;
}
template<typename T>
requires std::is_floating_point_v<T>
inline T toNetwork(T value) {
using U = std::conditional_t<sizeof(T) == 4, uint32_t, uint64_t>;
U u = std::bit_cast<U>(value);
u = toNetwork(u);
return std::bit_cast<T>(u);
}
template<typename T>
inline T fromNetwork(T value) {
return toNetwork(value);
}
} // namespace detail
enum class Priority : uint8_t {
Realtime = 0,
High = 1,
Normal = 2,
Low = 3
};
enum class FrameFlags : uint8_t {
None = 0,
HasMore = 1
};
inline FrameFlags operator|(FrameFlags a, FrameFlags b) {
return static_cast<FrameFlags>(static_cast<uint8_t>(a) | static_cast<uint8_t>(b));
}
inline bool hasFlag(FrameFlags value, FrameFlags flag) {
return (static_cast<uint8_t>(value) & static_cast<uint8_t>(flag)) != 0;
}
struct Limits {
size_t maxFrameSize = 1 << 24;
size_t maxMessageSize = 1 << 26;
size_t maxQueueBytes = 1 << 27;
size_t maxLowPriorityBytes = 1 << 26;
size_t maxOpenStreams = 64;
};
struct OutgoingMessage {
uint16_t type = 0;
Priority priority = Priority::Normal;
bool dropIfOverloaded = false;
bool allowFragment = true;
std::vector<std::byte> payload;
};
struct IncomingMessage {
uint16_t type = 0;
Priority priority = Priority::Normal;
std::vector<std::byte> payload;
};
class PacketWriter {
public:
PacketWriter& writeBytes(std::span<const std::byte> data);
template<typename T>
requires (std::is_integral_v<T> || std::is_floating_point_v<T>)
PacketWriter& write(T value) {
T net = detail::toNetwork(value);
std::array<std::byte, sizeof(T)> bytes{};
std::memcpy(bytes.data(), &net, sizeof(T));
Buffer.insert(Buffer.end(), bytes.begin(), bytes.end());
return *this;
}
PacketWriter& writeString(std::string_view str);
const std::vector<std::byte>& data() const { return Buffer; }
std::vector<std::byte> release();
void clear();
private:
std::vector<std::byte> Buffer;
};
class PacketReader {
public:
explicit PacketReader(std::span<const std::byte> data);
template<typename T>
requires (std::is_integral_v<T> || std::is_floating_point_v<T>)
T read() {
require(sizeof(T));
T net{};
std::memcpy(&net, Data.data() + Pos, sizeof(T));
Pos += sizeof(T);
return detail::fromNetwork(net);
}
void readBytes(std::span<std::byte> out);
std::string readString();
bool empty() const { return Pos >= Data.size(); }
size_t remaining() const { return Data.size() - Pos; }
private:
void require(size_t size);
size_t Pos = 0;
std::span<const std::byte> Data;
};
class SocketServer : public AsyncObject {
public:
SocketServer(asio::io_context &ioc, std::function<coro<>(tcp::socket)> &&onConnect, uint16_t port = 0);
bool isStopped() const;
uint16_t getPort() const;
private:
coro<void> run(std::function<coro<>(tcp::socket)> onConnect);
tcp::acceptor Acceptor;
};
class AsyncSocket : public AsyncObject {
public:
static constexpr size_t kHeaderSize = 12;
AsyncSocket(asio::io_context &ioc, tcp::socket &&socket, Limits limits = {});
~AsyncSocket();
void enqueue(OutgoingMessage &&msg);
coro<IncomingMessage> readMessage();
coro<> readLoop(std::function<coro<>(IncomingMessage&&)> onMessage);
void closeRead();
void close();
bool isAlive() const;
std::string getError() const;
private:
struct FragmentState {
uint16_t type = 0;
Priority priority = Priority::Normal;
std::vector<std::byte> data;
};
struct AsyncContext {
std::atomic_bool needShutdown{false};
std::atomic_bool senderStopped{false};
std::atomic_bool readClosed{false};
boost::mutex errorMtx;
std::string error;
};
struct SendQueue {
boost::mutex mtx;
bool waiting = false;
asio::steady_timer semaphore;
std::deque<OutgoingMessage> queues[4];
size_t bytesInQueue = 0;
size_t bytesInLow = 0;
uint8_t nextIndex = 0;
int credits[4] = {8, 4, 2, 1};
explicit SendQueue(asio::io_context &ioc);
bool empty() const;
};
coro<> sendLoop();
coro<> sendMessage(OutgoingMessage &&msg);
coro<> sendFrame(uint16_t type, Priority priority, FrameFlags flags, uint32_t streamId,
std::span<const std::byte> payload);
coro<> readExact(std::byte *data, size_t size);
bool popNext(OutgoingMessage &out);
void dropLow(size_t needBytes);
void setError(const std::string &msg);
Limits LimitsCfg;
tcp::socket Socket;
SendQueue Outgoing;
std::shared_ptr<AsyncContext> Context;
std::unordered_map<uint32_t, FragmentState> Fragments;
uint32_t NextStreamId = 1;
};
coro<tcp::socket> asyncConnectTo(const std::string &address,
std::function<void(const std::string&)> onProgress = nullptr);
} // namespace LV::Net2

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

File diff suppressed because it is too large Load Diff

View File

@@ -1,406 +0,0 @@
#pragma once
#include <cstdint>
#include <vector>
#include <string>
#include <string_view>
#include <optional>
#include <functional>
#include <unordered_map>
#include <algorithm>
#include <cmath>
#include <cstring>
// ========================
// External texture view
// ========================
struct Texture {
uint32_t Width, Height;
const uint32_t* Pixels; // assumed 0xAARRGGBB
};
// ========================
// Bytecode words are uint8_t (1 byte machine word)
// TexId is u24 (3 bytes, little-endian)
// Subprogram refs use off24/len24 in BYTES (<=65535)
// ========================
class TexturePipelineProgram {
public:
using Word = uint8_t;
enum AnimFlags : Word {
AnimSmooth = 1u << 0,
AnimHorizontal = 1u << 1,
AnimGrid = 1u << 2
};
static constexpr uint16_t DefaultAnimFpsQ = uint16_t(8u * 256u);
static constexpr size_t MaxCodeBytes = (1u << 16) + 1u; // 65537
struct OwnedTexture {
uint32_t Width = 0, Height = 0;
std::vector<uint32_t> Pixels;
Texture view() const { return Texture{Width, Height, Pixels.data()}; }
};
using IdResolverFunc = std::function<std::optional<uint32_t>(std::string_view)>;
using TextureProviderFunc = std::function<std::optional<Texture>(uint32_t)>;
// Patch point to 3 consecutive bytes where u24 texId lives (b0,b1,b2)
struct Patch {
size_t ByteIndex0 = 0; // Code_[i], Code_[i+1], Code_[i+2]
std::string Name;
};
bool compile(std::string_view src, std::string* err = nullptr);
bool link(const IdResolverFunc& resolver, std::string* err = nullptr);
bool bake(const TextureProviderFunc& provider, OwnedTexture& out, std::string* err = nullptr) const;
bool bake(const TextureProviderFunc& provider, OwnedTexture& out, double timeSeconds, std::string* err = nullptr) const;
const std::vector<Word>& words() const { return Code_; }
const std::vector<Patch>& patches() const { return Patches_; }
std::vector<uint8_t> toBytes() const { return Code_; }
struct AnimSpec {
uint32_t TexId = 0;
bool HasTexId = false;
uint16_t FrameW = 0;
uint16_t FrameH = 0;
uint16_t FrameCount = 0;
uint16_t FpsQ = 0;
uint16_t Flags = 0;
};
static std::vector<AnimSpec> extractAnimationSpecs(const Word* code, size_t size);
static bool remapTexIds(std::vector<uint8_t>& code, const std::vector<uint32_t>& remap, std::string* err = nullptr);
static std::vector<AnimSpec> extractAnimationSpecs(const std::vector<Word>& code) {
return extractAnimationSpecs(code.data(), code.size());
}
void fromBytes(std::vector<uint8_t> bytes);
private:
// ========================
// Byte helpers (little-endian)
// ========================
static inline uint16_t _rd16(const std::vector<uint8_t>& c, size_t& ip) {
uint16_t v = uint16_t(c[ip]) | (uint16_t(c[ip+1]) << 8);
ip += 2;
return v;
}
static inline uint32_t _rd24(const std::vector<uint8_t>& c, size_t& ip) {
uint32_t v = uint32_t(c[ip]) | (uint32_t(c[ip+1]) << 8) | (uint32_t(c[ip+2]) << 16);
ip += 3;
return v;
}
static inline uint32_t _rd32(const std::vector<uint8_t>& c, size_t& ip) {
uint32_t v = uint32_t(c[ip]) |
(uint32_t(c[ip+1]) << 8) |
(uint32_t(c[ip+2]) << 16) |
(uint32_t(c[ip+3]) << 24);
ip += 4;
return v;
}
static inline void _wr8 (std::vector<uint8_t>& o, uint32_t v){ o.push_back(uint8_t(v & 0xFFu)); }
static inline void _wr16(std::vector<uint8_t>& o, uint32_t v){
o.push_back(uint8_t(v & 0xFFu));
o.push_back(uint8_t((v >> 8) & 0xFFu));
}
static inline void _wr24(std::vector<uint8_t>& o, uint32_t v){
o.push_back(uint8_t(v & 0xFFu));
o.push_back(uint8_t((v >> 8) & 0xFFu));
o.push_back(uint8_t((v >> 16) & 0xFFu));
}
static inline void _wr32(std::vector<uint8_t>& o, uint32_t v){
o.push_back(uint8_t(v & 0xFFu));
o.push_back(uint8_t((v >> 8) & 0xFFu));
o.push_back(uint8_t((v >> 16) & 0xFFu));
o.push_back(uint8_t((v >> 24) & 0xFFu));
}
// ========================
// SrcRef encoding in bytes (variable length)
// kind(1) + payload
// TexId: id24(3) => total 4
// Sub : off16(3) + len16(3) => total 7
// ========================
enum class SrcKind : uint8_t { TexId = 0, Sub = 1 };
struct SrcRef {
SrcKind Kind{};
uint32_t TexId24 = 0; // for TexId
uint16_t Off24 = 0; // for Sub
uint16_t Len24 = 0; // for Sub
};
// ========================
// Opcodes (1 byte)
// ========================
enum class Op : uint8_t {
End = 0,
Base_Tex = 1, // SrcRef(TexId)
Base_Fill = 2, // w16, h16, color32
Base_Anim = 3, // SrcRef(TexId), frameW16, frameH16, frames16, fpsQ16, flags8
Resize = 10, // w16, h16
Transform = 11, // t8
Opacity = 12, // a8
NoAlpha = 13, // -
MakeAlpha = 14, // rgb24 (3 bytes) RR,GG,BB
Invert = 15, // mask8
Brighten = 16, // -
Contrast = 17, // cBias8, bBias8 (bias-127)
Multiply = 18, // color32
Screen = 19, // color32
Colorize = 20, // color32, ratio8
Anim = 21, // frameW16, frameH16, frames16, fpsQ16, flags8
Overlay = 30, // SrcRef (var)
Mask = 31, // SrcRef (var)
LowPart = 32, // percent8, SrcRef (var)
Combine = 40 // w16,h16,n16 then n*(x16,y16,SrcRef) (если понадобится — допишем DSL)
};
// ========================
// Pixel helpers (assume 0xAARRGGBB)
// ========================
static inline uint8_t _a(uint32_t c){ return uint8_t((c >> 24) & 0xFF); }
static inline uint8_t _r(uint32_t c){ return uint8_t((c >> 16) & 0xFF); }
static inline uint8_t _g(uint32_t c){ return uint8_t((c >> 8) & 0xFF); }
static inline uint8_t _b(uint32_t c){ return uint8_t((c >> 0) & 0xFF); }
static inline uint32_t _pack(uint8_t a,uint8_t r,uint8_t g,uint8_t b){
return (uint32_t(a)<<24)|(uint32_t(r)<<16)|(uint32_t(g)<<8)|(uint32_t(b));
}
static inline uint8_t _clampu8(int v){ return uint8_t(std::min(255, std::max(0, v))); }
// ========================
// VM (executes bytes)
// ========================
struct Image {
uint32_t W=0,H=0;
std::vector<uint32_t> Px;
};
class VM {
public:
using TextureProvider = TexturePipelineProgram::TextureProviderFunc;
explicit VM(TextureProvider provider);
bool run(const std::vector<uint8_t>& code, OwnedTexture& out, double timeSeconds, std::string* err);
private:
TextureProvider Provider_;
static bool _bad(std::string* err, const char* msg);
static bool _readSrc(const std::vector<uint8_t>& code, size_t& ip, SrcRef& out, std::string* err);
Image _loadTex(uint32_t id, std::unordered_map<uint32_t, Image>& cache, std::string* err);
Image _loadSub(const std::vector<uint8_t>& code,
uint32_t off, uint32_t len,
std::unordered_map<uint32_t, Image>& texCache,
std::unordered_map<uint64_t, Image>& subCache,
double timeSeconds,
std::string* err);
Image _loadSrc(const std::vector<uint8_t>& code,
const SrcRef& src,
std::unordered_map<uint32_t, Image>& texCache,
std::unordered_map<uint64_t, Image>& subCache,
double timeSeconds,
std::string* err);
// ---- image ops (как в исходнике) ----
static Image _makeSolid(uint32_t w, uint32_t h, uint32_t color);
static Image _resizeNN(const Image& src, uint32_t nw, uint32_t nh);
static Image _resizeNN_ifNeeded(Image img, uint32_t w, uint32_t h);
static Image _cropFrame(const Image& sheet, uint32_t index, uint32_t fw, uint32_t fh, bool horizontal);
static Image _cropFrameGrid(const Image& sheet, uint32_t index, uint32_t fw, uint32_t fh);
static void _lerp(Image& base, const Image& over, double t);
static void _alphaOver(Image& base, const Image& over);
static void _applyMask(Image& base, const Image& mask);
static void _opacity(Image& img, uint8_t mul);
static void _noAlpha(Image& img);
static void _makeAlpha(Image& img, uint32_t rgb24);
static void _invert(Image& img, uint32_t maskBits);
static void _brighten(Image& img);
static void _contrast(Image& img, int c, int br);
static void _multiply(Image& img, uint32_t color);
static void _screen(Image& img, uint32_t color);
static void _colorize(Image& img, uint32_t color, uint8_t ratio);
static void _lowpart(Image& base, const Image& over, uint32_t percent);
static Image _transform(const Image& src, uint32_t t);
};
// ========================
// Minimal DSL Lexer/Parser
// now supports:
// name |> op(...)
// 32x32 "#RRGGBBAA"
// optional prefix:
// tex name |> op(...)
// nested only where op expects a texture arg:
// overlay( tex other |> ... )
// Also supports overlay(other) / mask(other) / lowpart(50, other)
// ========================
enum class TokKind { End, Ident, Number, String, Pipe, Comma, LParen, RParen, Eq, X };
struct Tok {
TokKind Kind = TokKind::End;
std::string Text;
uint32_t U32 = 0;
};
struct Lexer {
std::string_view S;
size_t I=0;
bool HasBuf = false;
Tok Buf;
static bool isAlpha(char c){ return (c>='a'&&c<='z')||(c>='A'&&c<='Z')||c=='_'; }
static bool isNum(char c){ return (c>='0'&&c<='9'); }
static bool isAlnum(char c){ return isAlpha(c)||isNum(c); }
void unread(const Tok& t);
Tok peek();
void skipWs();
Tok next();
};
struct ArgVal {
enum class ValueKind { U32, Str, Ident };
ValueKind Kind = ValueKind::U32;
uint32_t U32 = 0;
std::string S;
};
struct ParsedOp {
std::string Name;
std::vector<ArgVal> Pos;
std::unordered_map<std::string, ArgVal> Named;
};
// ========================
// Compiler state
// ========================
std::string Source_;
std::vector<uint8_t> Code_;
std::vector<Patch> Patches_;
// ---- emit helpers (target = arbitrary out vector) ----
static inline void _emitOp(std::vector<uint8_t>& out, Op op) { _wr8(out, uint8_t(op)); }
static inline void _emitU8(std::vector<uint8_t>& out, uint32_t v){ _wr8(out, v); }
static inline void _emitU16(std::vector<uint8_t>& out, uint32_t v){ _wr16(out, v); }
static inline void _emitU24(std::vector<uint8_t>& out, uint32_t v){ _wr24(out, v); }
static inline void _emitU32(std::vector<uint8_t>& out, uint32_t v){ _wr32(out, v); }
// reserve 3 bytes for u24 texId and register patch (absolute or relative)
struct RelPatch { size_t Rel0; std::string Name; };
static void _emitTexPatchU24(std::vector<uint8_t>& out,
std::vector<Patch>* absPatches,
std::vector<RelPatch>* relPatches,
const std::string& name) {
const size_t idx = out.size();
out.push_back(0); out.push_back(0); out.push_back(0);
if(absPatches) absPatches->push_back(Patch{idx, name});
if(relPatches) relPatches->push_back(RelPatch{idx, name});
}
static void _emitSrcTexName(std::vector<uint8_t>& out,
std::vector<Patch>* absPatches,
std::vector<RelPatch>* relPatches,
const std::string& name) {
_emitU8(out, uint8_t(SrcKind::TexId));
_emitTexPatchU24(out, absPatches, relPatches, name);
}
static void _emitSrcSub(std::vector<uint8_t>& out, uint32_t off24, uint32_t len24) {
_emitU8(out, uint8_t(SrcKind::Sub));
_emitU24(out, off24);
_emitU24(out, len24);
}
// ========================
// Color parsing: #RRGGBB or #RRGGBBAA -> 0xAARRGGBB
// ========================
static bool _parseHexColor(std::string_view s, uint32_t& outARGB);
// ========================
// Parsing entry: full program
// ========================
bool _parseProgram(std::string* err);
// ========================
// Base compilation (optionally after 'tex')
// supports:
// 1) name
// 2) "name(.png/.jpg/.jpeg)" (allowed but normalized)
// 3) anim(...)
// 4) 32x32 "#RRGGBBAA"
// optional: all of the above may be prefixed with 'tex'
// ========================
bool _compileBaseAfterTex(Lexer& lx,
std::vector<uint8_t>& out,
std::vector<Patch>* absPatches,
std::vector<RelPatch>* relPatches,
std::string* err);
bool _compileBaseFromToken(Lexer& lx,
const Tok& a,
std::vector<uint8_t>& out,
std::vector<Patch>* absPatches,
std::vector<RelPatch>* relPatches,
std::string* err);
// ========================
// Args parsing:
// - normal args: (a,b,key=v)
// - OR if first token inside '(' is 'tex' => parse nested program until ')'
// ========================
bool _parseArgListOrTextureExpr(Lexer& lx, ParsedOp& op, std::string* err);
bool _parseArgList(Lexer& lx, ParsedOp& op, std::string* err);
bool _tokToVal(const Tok& t, ArgVal& out, std::string* err);
// ========================
// Subprogram compilation:
// we already consumed 'tex'. Parse base + pipeline until next token is ')'
// DO NOT consume ')'
// ========================
struct PendingSubData {
std::vector<uint8_t> Bytes;
std::vector<RelPatch> RelPatches;
};
bool _compileSubProgramFromAlreadySawTex(Lexer& lx, PendingSubData& outSub, std::string* err);
// pending subprogram associated with ParsedOp pointer (created during parsing)
mutable std::unordered_map<const ParsedOp*, PendingSubData> PendingSub_;
// Append subprogram to `out` and emit SrcRef(Sub, off16, len16), migrating patches properly.
static bool _appendSubprogram(std::vector<uint8_t>& out,
PendingSubData&& sub,
std::vector<Patch>* absPatches,
std::vector<RelPatch>* relPatches,
uint32_t& outOff,
uint32_t& outLen,
std::string* err);
// ========================
// Compile operations into arbitrary `out`
// absPatches != nullptr => patches recorded as absolute for this buffer
// relPatches != nullptr => patches recorded as relative for this buffer
// ========================
bool _compileOpInto(Lexer& lx,
const ParsedOp& op,
std::vector<uint8_t>& out,
std::vector<Patch>* absPatches,
std::vector<RelPatch>* relPatches,
std::string* err);
};

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

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

View File

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

View File

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

View File

@@ -1,129 +0,0 @@
#pragma once
#include "Common/Abstract.hpp"
#include "Common/IdProvider.hpp"
#include "Common/AssetsPreloader.hpp"
#include <unordered_map>
namespace LV::Server {
class AssetsManager : public IdProvider<EnumAssets>, protected AssetsPreloader {
public:
using BindHashHeaderInfo = AssetsManager::BindHashHeaderInfo;
struct Out_checkAndPrepareResourcesUpdate : public AssetsPreloader::Out_checkAndPrepareResourcesUpdate {
Out_checkAndPrepareResourcesUpdate(AssetsPreloader::Out_checkAndPrepareResourcesUpdate&& obj)
: AssetsPreloader::Out_checkAndPrepareResourcesUpdate(std::move(obj))
{}
std::unordered_map<ResourceFile::Hash_t, std::u8string> NewHeadless;
};
Out_checkAndPrepareResourcesUpdate checkAndPrepareResourcesUpdate(
const AssetsRegister& instances,
ReloadStatus* status = nullptr
) {
std::unordered_map<ResourceFile::Hash_t, std::u8string> newHeadless;
Out_checkAndPrepareResourcesUpdate result = AssetsPreloader::checkAndPrepareResourcesUpdate(
instances,
[&](EnumAssets type, std::string_view domain, std::string_view key) { return getId(type, domain, key); },
[&](std::u8string&& resource, ResourceFile::Hash_t hash, fs::path resPath) { newHeadless.emplace(hash, std::move(resource)); },
status
);
result.NewHeadless = std::move(newHeadless);
return result;
}
struct Out_applyResourcesUpdate : public AssetsPreloader::Out_applyResourcesUpdate {
Out_applyResourcesUpdate(AssetsPreloader::Out_applyResourcesUpdate&& obj)
: AssetsPreloader::Out_applyResourcesUpdate(std::move(obj))
{}
};
Out_applyResourcesUpdate applyResourcesUpdate(Out_checkAndPrepareResourcesUpdate& orr) {
Out_applyResourcesUpdate result = AssetsPreloader::applyResourcesUpdate(orr);
{
static TOS::Logger LOG = "Server>AssetsManager";
for(size_t type = 0; type < static_cast<size_t>(EnumAssets::MAX_ENUM); ++type) {
if(result.NewOrUpdates[type].empty())
continue;
EnumAssets typeEnum = static_cast<EnumAssets>(type);
const char* typeName = ::EnumAssetsToDirectory(typeEnum);
for(const auto& bind : result.NewOrUpdates[type]) {
auto dk = getDK(typeEnum, bind.Id);
if(!dk)
continue;
LOG.debug()
<< typeName << ": "
<< dk->Domain << '+' << dk->Key << " -> " << bind.Id;
}
}
}
for(auto& [hash, data] : orr.NewHeadless) {
Resources.emplace(hash, ResourceHashData{0, std::make_shared<std::u8string>(std::move(data))});
}
for(auto& [hash, pathes] : orr.HashToPathNew) {
auto iter = Resources.find(hash);
assert(iter != Resources.end());
iter->second.RefCount += pathes.size();
}
for(auto& [hash, pathes] : orr.HashToPathLost) {
auto iter = Resources.find(hash);
assert(iter != Resources.end());
iter->second.RefCount -= pathes.size();
if(iter->second.RefCount == 0)
Resources.erase(iter);
}
return result;
}
std::vector<std::tuple<ResourceFile::Hash_t, std::shared_ptr<const std::u8string>>>
getResources(const std::vector<ResourceFile::Hash_t>& hashes) const
{
std::vector<std::tuple<ResourceFile::Hash_t, std::shared_ptr<const std::u8string>>> result;
result.reserve(hashes.size());
for(const auto& hash : hashes) {
auto iter = Resources.find(hash);
if(iter == Resources.end())
continue;
result.emplace_back(hash, iter->second.Data);
}
return result;
}
std::array<
std::vector<BindHashHeaderInfo>,
static_cast<size_t>(EnumAssets::MAX_ENUM)
> collectHashBindings() const {
return AssetsPreloader::collectHashBindings();
}
private:
struct ResourceHashData {
size_t RefCount;
std::shared_ptr<std::u8string> Data;
};
std::unordered_map<
ResourceFile::Hash_t,
ResourceHashData
> Resources;
};
}

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,335 +0,0 @@
#include "ContentManager.hpp"
#include "Common/Abstract.hpp"
#include <algorithm>
#include <optional>
#include <utility>
namespace LV::Server {
ContentManager::ContentManager(AssetsManager& am)
: AM(am)
{
}
ContentManager::~ContentManager() = default;
void ContentManager::registerBase_Node(ResourceId id, const std::string& domain, const std::string& key, const sol::table& profile) {
std::optional<DefNode_Base>* basePtr;
{
size_t entryIndex = id / TableEntry<DefNode_Base>::ChunkSize;
size_t entryId = id % TableEntry<DefNode_Base>::ChunkSize;
size_t need = entryIndex+1-Profiles_Base_Node.size();
for(size_t iter = 0; iter < need; iter++) {
Profiles_Base_Node.emplace_back(std::make_unique<TableEntry<DefNode_Base>>());
}
basePtr = &Profiles_Base_Node[entryIndex]->Entries[entryId];
*basePtr = DefNode_Base();
}
DefNode_Base& def = **basePtr;
{
std::optional<std::variant<std::string, sol::table>> parent = profile.get<std::optional<std::variant<std::string, sol::table>>>("parent");
if(parent) {
if(const sol::table* table = std::get_if<sol::table>(&*parent)) {
// result = createNodeProfileByLua(*table);
} else if(const std::string* key = std::get_if<std::string>(&*parent)) {
auto regResult = TOS::Str::match(*key, "(?:([\\w\\d_]+):)?([\\w\\d_]+)");
if(!regResult)
MAKE_ERROR("Недействительный ключ в определении parent");
std::string realKey;
if(!regResult->at(1)) {
realKey = *key;
} else {
realKey = "core:" + *regResult->at(2);
}
DefNodeId parentId;
// {
// auto& list = Content.ContentKeyToId[(int) EnumDefContent::Node];
// auto iter = list.find(realKey);
// if(iter == list.end())
// MAKE_ERROR("Идентификатор parent не найден");
// parentId = iter->second;
// }
// result = Content.ContentIdToDef_Node.at(parentId);
}
}
}
{
std::optional<sol::table> nodestate = profile.get<std::optional<sol::table>>("nodestate");
}
{
std::optional<sol::table> render = profile.get<std::optional<sol::table>>("render");
}
{
std::optional<sol::table> collision = profile.get<std::optional<sol::table>>("collision");
}
{
std::optional<sol::table> events = profile.get<std::optional<sol::table>>("events");
}
// result.NodeAdvancementFactory = profile["node_advancement_factory"];
}
void ContentManager::registerBase_World(ResourceId id, const std::string& domain, const std::string& key, const sol::table& profile) {
std::optional<DefWorld>& world = getEntry_World(id);
if(!world)
world.emplace();
}
void ContentManager::registerBase_Entity(ResourceId id, const std::string& domain, const std::string& key, const sol::table& profile) {
std::optional<DefEntity>& entity = getEntry_Entity(id);
if(!entity)
entity.emplace();
DefEntity& def = *entity;
def.Domain = domain;
def.Key = key;
}
void ContentManager::registerBase(EnumDefContent type, const std::string& domain, const std::string& key, const sol::table& profile)
{
ResourceId id = getId(type, domain, key);
ProfileChanges[static_cast<size_t>(type)].push_back(id);
if(type == EnumDefContent::Node)
registerBase_Node(id, domain, key, profile);
else if(type == EnumDefContent::World)
registerBase_World(id, domain, key, profile);
else if(type == EnumDefContent::Entity)
registerBase_Entity(id, domain, key, profile);
else
MAKE_ERROR("Не реализовано");
}
void ContentManager::unRegisterBase(EnumDefContent type, const std::string& domain, const std::string& key)
{
ResourceId id = getId(type, domain, key);
ProfileChanges[(int) type].push_back(id);
}
void ContentManager::registerModifier(EnumDefContent type, const std::string& mod, const std::string& domain, const std::string& key, const sol::table& profile)
{
ResourceId id = getId(type, domain, key);
ProfileChanges[(int) type].push_back(id);
}
void ContentManager::unRegisterModifier(EnumDefContent type, const std::string& mod, const std::string& domain, const std::string& key)
{
ResourceId id = getId(type, domain, key);
ProfileChanges[(int) type].push_back(id);
}
// void ContentManager::markAllProfilesDirty(EnumDefContent type) {
// const auto &table = this->idToDK()[(int) type];
// size_t counter = 0;
// for(const auto& [domain, key] : table) {
// ProfileChanges[static_cast<size_t>(type)].push_back(counter++);
// }
// }
template<class type, class modType>
void ContentManager::buildEndProfilesByType(auto& profiles, auto enumType, auto& base, auto& keys, auto& result, auto& modsTable) {
// Расширяем таблицу итоговых профилей до нужного количества
if(!keys.empty()) {
size_t need = keys.back() / TableEntry<type>::ChunkSize;
if(need >= profiles.size()) {
profiles.reserve(need);
for(size_t iter = 0; iter <= need-profiles.size(); ++iter)
profiles.emplace_back(std::make_unique<TableEntry<type>>());
}
}
TOS::Logger("CM").debug() << "type: " << static_cast<size_t>(enumType);
// Пересчитываем профили
for(size_t id : keys) {
size_t entryIndex = id / TableEntry<type>::ChunkSize;
size_t subIndex = id % TableEntry<type>::ChunkSize;
if(
entryIndex >= base.size()
|| !base[entryIndex]->Entries[subIndex]
) {
// Базовый профиль не существует
profiles[entryIndex]->Entries[subIndex] = std::nullopt;
// Уведомляем о потере профиля
result.LostProfiles[static_cast<size_t>(enumType)].push_back(id);
} else {
// Собираем конечный профиль
std::vector<std::tuple<std::string, modType>> mods_default, *mods = &mods_default;
auto iter = modsTable.find(id);
if(iter != modsTable.end())
mods = &iter->second;
std::optional<BindDomainKeyInfo> dk = getDK(enumType, id);
assert(dk);
TOS::Logger("CM").debug() << "\t" << dk->Domain << ":" << dk->Key << " -> " << id;
profiles[entryIndex]->Entries[subIndex] = base[entryIndex]->Entries[subIndex]->compile(AM, *this, dk->Domain, dk->Key, *mods);
}
}
}
ContentManager::Out_buildEndProfiles ContentManager::buildEndProfiles() {
Out_buildEndProfiles result;
for(int type = 0; type < (int) EnumDefContent::MAX_ENUM; type++) {
std::shared_lock lock(Profiles_Mtx[type]);
auto& keys = ProfileChanges[type];
std::sort(keys.begin(), keys.end());
auto iterErase = std::unique(keys.begin(), keys.end());
keys.erase(iterErase, keys.end());
switch(type) {
case 0: buildEndProfilesByType<DefVoxel, DefVoxel_Mod> (Profiles_Voxel, EnumDefContent::Voxel, Profiles_Base_Voxel, keys, result, Profiles_Mod_Voxel); break;
case 1: buildEndProfilesByType<DefNode, DefNode_Mod> (Profiles_Node, EnumDefContent::Node, Profiles_Base_Node, keys, result, Profiles_Mod_Node); break;
case 2: buildEndProfilesByType<DefWorld, DefWorld_Mod> (Profiles_World, EnumDefContent::World, Profiles_Base_World, keys, result, Profiles_Mod_World); break;
case 3: buildEndProfilesByType<DefPortal, DefPortal_Mod> (Profiles_Portal, EnumDefContent::Portal, Profiles_Base_Portal, keys, result, Profiles_Mod_Portal); break;
case 4: buildEndProfilesByType<DefEntity, DefEntity_Mod> (Profiles_Entity, EnumDefContent::Entity, Profiles_Base_Entity, keys, result, Profiles_Mod_Entity); break;
case 5: buildEndProfilesByType<DefItem, DefItem_Mod> (Profiles_Item, EnumDefContent::Item, Profiles_Base_Item, keys, result, Profiles_Mod_Item); break;
default: std::unreachable();
}
}
return result;
}
ContentManager::Out_getAllProfiles ContentManager::getAllProfiles() {
Out_getAllProfiles result;
size_t counter;
{
std::shared_lock lock(Profiles_Mtx[static_cast<size_t>(EnumDefContent::Voxel)]);
result.ProfilesIds_Voxel.reserve(Profiles_Voxel.size()*TableEntry<DefVoxel>::ChunkSize);
counter = 0;
for(const auto& entry : Profiles_Voxel)
for(const auto& item : entry->Entries) {
size_t id = counter++;
if(item)
result.ProfilesIds_Voxel.push_back(id);
}
result.ProfilesIds_Voxel.shrink_to_fit();
result.Profiles_Voxel.reserve(result.ProfilesIds_Voxel.size());
for(const auto& entry : Profiles_Voxel)
for(const auto& item : entry->Entries)
if(item)
result.Profiles_Voxel.push_back(&item.value());
}
{
std::shared_lock lock(Profiles_Mtx[static_cast<size_t>(EnumDefContent::Node)]);
result.ProfilesIds_Node.reserve(Profiles_Node.size()*TableEntry<DefNode>::ChunkSize);
counter = 0;
for(const auto& entry : Profiles_Node)
for(const auto& item : entry->Entries) {
size_t id = counter++;
if(item)
result.ProfilesIds_Node.push_back(id);
}
result.ProfilesIds_Node.shrink_to_fit();
result.Profiles_Node.reserve(result.ProfilesIds_Node.size());
for(const auto& entry : Profiles_Node)
for(const auto& item : entry->Entries)
if(item)
result.Profiles_Node.push_back(&item.value());
}
{
std::shared_lock lock(Profiles_Mtx[static_cast<size_t>(EnumDefContent::World)]);
result.ProfilesIds_World.reserve(Profiles_World.size()*TableEntry<DefWorld>::ChunkSize);
counter = 0;
for(const auto& entry : Profiles_World)
for(const auto& item : entry->Entries) {
size_t id = counter++;
if(item)
result.ProfilesIds_World.push_back(id);
}
result.ProfilesIds_World.shrink_to_fit();
result.Profiles_World.reserve(result.ProfilesIds_World.size());
for(const auto& entry : Profiles_World)
for(const auto& item : entry->Entries)
if(item)
result.Profiles_World.push_back(&item.value());
}
{
std::shared_lock lock(Profiles_Mtx[static_cast<size_t>(EnumDefContent::Portal)]);
result.ProfilesIds_Portal.reserve(Profiles_Portal.size()*TableEntry<DefPortal>::ChunkSize);
counter = 0;
for(const auto& entry : Profiles_Portal)
for(const auto& item : entry->Entries) {
size_t id = counter++;
if(item)
result.ProfilesIds_Portal.push_back(id);
}
result.ProfilesIds_Portal.shrink_to_fit();
result.Profiles_Portal.reserve(result.ProfilesIds_Portal.size());
for(const auto& entry : Profiles_Portal)
for(const auto& item : entry->Entries)
if(item)
result.Profiles_Portal.push_back(&item.value());
}
{
std::shared_lock lock(Profiles_Mtx[static_cast<size_t>(EnumDefContent::Entity)]);
result.ProfilesIds_Entity.reserve(Profiles_Entity.size()*TableEntry<DefEntity>::ChunkSize);
counter = 0;
for(const auto& entry : Profiles_Entity)
for(const auto& item : entry->Entries) {
size_t id = counter++;
if(item)
result.ProfilesIds_Entity.push_back(id);
}
result.ProfilesIds_Entity.shrink_to_fit();
result.Profiles_Entity.reserve(result.ProfilesIds_Entity.size());
for(const auto& entry : Profiles_Entity)
for(const auto& item : entry->Entries)
if(item)
result.Profiles_Entity.push_back(&item.value());
}
{
std::shared_lock lock(Profiles_Mtx[static_cast<size_t>(EnumDefContent::Item)]);
result.ProfilesIds_Item.reserve(Profiles_Item.size()*TableEntry<DefItem>::ChunkSize);
counter = 0;
for(const auto& entry : Profiles_Item)
for(const auto& item : entry->Entries) {
size_t id = counter++;
if(item)
result.ProfilesIds_Item.push_back(id);
}
result.ProfilesIds_Item.shrink_to_fit();
result.Profiles_Item.reserve(result.ProfilesIds_Item.size());
for(const auto& entry : Profiles_Item)
for(const auto& item : entry->Entries)
if(item)
result.Profiles_Item.push_back(&item.value());
}
result.IdToDK = idToDK();
return result;
}
}

View File

@@ -1,458 +0,0 @@
#pragma once
#include "Common/Abstract.hpp"
#include "AssetsManager.hpp"
#include "Common/IdProvider.hpp"
#include "Common/Net.hpp"
#include "TOSLib.hpp"
#include <array>
#include <mutex>
#include <sol/table.hpp>
#include <string_view>
#include <unordered_map>
namespace LV::Server {
struct ResourceBase {
std::string Domain, Key;
};
class ContentManager;
struct DefVoxel : public ResourceBase {
std::u8string dumpToClient() const {
return {};
}
};
struct DefNode : public ResourceBase {
AssetsNodestate NodestateId;
std::u8string dumpToClient() const {
auto wr = TOS::ByteBuffer::Writer();
wr << uint32_t(NodestateId);
auto buff = wr.complite();
return (std::u8string) std::u8string_view((const char8_t*) buff.data(), buff.size());
}
};
struct DefWorld : public ResourceBase {
std::u8string dumpToClient() const {
return {};
}
};
struct DefPortal : public ResourceBase {
std::u8string dumpToClient() const {
return {};
}
};
struct DefEntity : public ResourceBase {
std::u8string dumpToClient() const {
return {};
}
};
struct DefItem : public ResourceBase {
std::u8string dumpToClient() const {
return {};
}
};
struct DefVoxel_Mod { };
struct DefNode_Mod { };
struct DefWorld_Mod { };
struct DefPortal_Mod { };
struct DefEntity_Mod { };
struct DefItem_Mod { };
struct DefVoxel_Base {
private:
friend ContentManager;
DefVoxel compile(AssetsManager& am, ContentManager& cm, const std::string_view domain, const std::string_view key, const std::vector<std::tuple<std::string, DefVoxel_Mod>>& mods) const {
return DefVoxel();
}
};
struct DefNode_Base {
private:
friend ContentManager;
DefNode compile(AssetsManager& am, ContentManager& cm, const std::string_view domain, const std::string_view key, const std::vector<std::tuple<std::string, DefNode_Mod>>& mods) const {
DefNode profile;
std::string jsonKey = std::string(key)+".json";
profile.NodestateId = am.getId(EnumAssets::Nodestate, domain, jsonKey);
TOS::Logger("Compile").info() << domain << ' ' << key << " -> " << profile.NodestateId;
return profile;
}
};
struct DefWorld_Base {
private:
friend ContentManager;
DefWorld compile(AssetsManager& am, ContentManager& cm, const std::string_view domain, const std::string_view key, const std::vector<std::tuple<std::string, DefWorld_Mod>>& mods) const {
return DefWorld();
}
};
struct DefPortal_Base {
private:
friend ContentManager;
DefPortal compile(AssetsManager& am, ContentManager& cm, const std::string_view domain, const std::string_view key, const std::vector<std::tuple<std::string, DefPortal_Mod>>& mods) const {
return DefPortal();
}
};
struct DefEntity_Base {
private:
friend ContentManager;
DefEntity compile(AssetsManager& am, ContentManager& cm, const std::string_view domain, const std::string_view key, const std::vector<std::tuple<std::string, DefEntity_Mod>>& mods) const {
return DefEntity();
}
};
struct DefItem_Base {
private:
friend ContentManager;
DefItem compile(AssetsManager& am, ContentManager& cm, const std::string_view domain, const std::string_view key, const std::vector<std::tuple<std::string, DefItem_Mod>>& mods) const {
return DefItem();
}
};
/*
DK to id
id to profile
*/
class ContentManager : public IdProvider<EnumDefContent> {
public:
class LRU {
public:
LRU(ContentManager& cm)
: CM(&cm)
{
}
LRU(const LRU&) = default;
LRU(LRU&&) = default;
LRU& operator=(const LRU&) = default;
LRU& operator=(LRU&&) = default;
ResourceId getId(EnumDefContent type, const std::string_view domain, const std::string_view key) {
auto iter = DKToId[static_cast<size_t>(type)].find(BindDomainKeyViewInfo(domain, key));
if(iter == DKToId[static_cast<size_t>(type)].end()) {
ResourceId id = CM->getId(type, domain, key);
DKToId[static_cast<size_t>(type)].emplace_hint(iter, BindDomainKeyInfo((std::string) domain, (std::string) key), id);
return id;
}
return iter->second;
// switch(type) {
// case EnumDefContent::Voxel:
// case EnumDefContent::Node:
// case EnumDefContent::World:
// case EnumDefContent::Portal:
// case EnumDefContent::Entity:
// case EnumDefContent::Item:
// default:
// std::unreachable();
// }
}
ResourceId getIdVoxel(const std::string_view domain, const std::string_view key) {
return getId(EnumDefContent::Voxel, domain, key);
}
ResourceId getIdNode(const std::string_view domain, const std::string_view key) {
return getId(EnumDefContent::Node, domain, key);
}
ResourceId getIdWorld(const std::string_view domain, const std::string_view key) {
return getId(EnumDefContent::World, domain, key);
}
ResourceId getIdPortal(const std::string_view domain, const std::string_view key) {
return getId(EnumDefContent::Portal, domain, key);
}
ResourceId getIdEntity(const std::string_view domain, const std::string_view key) {
return getId(EnumDefContent::Entity, domain, key);
}
ResourceId getIdItem(const std::string_view domain, const std::string_view key) {
return getId(EnumDefContent::Item, domain, key);
}
private:
ContentManager* CM;
std::array<
ankerl::unordered_dense::map<BindDomainKeyInfo, ResourceId, KeyHash, KeyEq>,
MAX_ENUM
> DKToId;
std::unordered_map<DefVoxelId, std::optional<DefVoxel>*> Profiles_Voxel;
std::unordered_map<DefNodeId, std::optional<DefNode>*> Profiles_Node;
std::unordered_map<DefWorldId, std::optional<DefWorld>*> Profiles_World;
std::unordered_map<DefPortalId, std::optional<DefPortal>*> Profiles_Portal;
std::unordered_map<DefEntityId, std::optional<DefEntity>*> Profiles_Entity;
std::unordered_map<DefItemId, std::optional<DefItem>*> Profiles_Item;
};
public:
ContentManager(AssetsManager &am);
~ContentManager();
// Регистрирует определение контента
void registerBase(EnumDefContent type, const std::string& domain, const std::string& key, const sol::table& profile);
void unRegisterBase(EnumDefContent type, const std::string& domain, const std::string& key);
// Регистрация модификатора предмета модом
void registerModifier(EnumDefContent type, const std::string& mod, const std::string& domain, const std::string& key, const sol::table& profile);
void unRegisterModifier(EnumDefContent type, const std::string& mod, const std::string& domain, const std::string& key);
// Пометить все профили типа как изменённые (например, после перезагрузки ассетов)
// void markAllProfilesDirty(EnumDefContent type);
// Список всех зарегистрированных профилей выбранного типа
std::vector<ResourceId> collectProfileIds(EnumDefContent type) const;
// Компилирует изменённые профили
struct Out_buildEndProfiles {
std::vector<
std::tuple<DefVoxelId, const DefVoxel*>
> ChangedProfiles_Voxel;
std::vector<
std::tuple<DefNodeId, const DefNode*>
> ChangedProfiles_Node;
std::vector<
std::tuple<DefWorldId, const DefWorld*>
> ChangedProfiles_World;
std::vector<
std::tuple<DefPortalId, const DefPortal*>
> ChangedProfiles_Portal;
std::vector<
std::tuple<DefEntityId, const DefEntity*>
> ChangedProfiles_Entity;
std::vector<
std::tuple<DefItemId, const DefItem*>
> ChangedProfiles_Item;
std::array<
std::vector<ResourceId>,
MAX_ENUM
> LostProfiles;
std::array<
std::vector<BindDomainKeyInfo>,
static_cast<size_t>(EnumDefContent::MAX_ENUM)
> IdToDK;
};
// Компилирует конечные профили по базе и модификаторам (предоставляет клиентам изменённые и потерянные)
Out_buildEndProfiles buildEndProfiles();
struct Out_getAllProfiles {
std::vector<DefVoxelId> ProfilesIds_Voxel;
std::vector<const DefVoxel*> Profiles_Voxel;
std::vector<DefNodeId> ProfilesIds_Node;
std::vector<const DefNode*> Profiles_Node;
std::vector<DefWorldId> ProfilesIds_World;
std::vector<const DefWorld*> Profiles_World;
std::vector<DefPortalId> ProfilesIds_Portal;
std::vector<const DefPortal*> Profiles_Portal;
std::vector<DefEntityId> ProfilesIds_Entity;
std::vector<const DefEntity*> Profiles_Entity;
std::vector<DefItemId> ProfilesIds_Item;
std::vector<const DefItem*> Profiles_Item;
std::array<
std::vector<BindDomainKeyInfo>,
static_cast<size_t>(EnumDefContent::MAX_ENUM)
> IdToDK;
};
// Выдаёт все профили (для новых клиентов)
Out_getAllProfiles getAllProfiles();
std::optional<DefVoxel>& getEntry_Voxel(ResourceId resId) {
std::shared_lock mtx(Profiles_Mtx[static_cast<size_t>(EnumDefContent::Voxel)]);
assert(resId / TableEntry<DefVoxel>::ChunkSize <= Profiles_Voxel.size());
return Profiles_Voxel[resId / TableEntry<DefVoxel>::ChunkSize]->Entries[resId % TableEntry<DefVoxel>::ChunkSize];
}
std::optional<DefVoxel>& getEntry_Voxel(const std::string_view domain, const std::string_view key) {
return getEntry_Voxel(getId(EnumDefContent::Voxel, domain, key));
}
std::optional<DefNode>& getEntry_Node(ResourceId resId) {
std::shared_lock mtx(Profiles_Mtx[static_cast<size_t>(EnumDefContent::Node)]);
assert(resId / TableEntry<DefNode>::ChunkSize < Profiles_Node.size());
return Profiles_Node[resId / TableEntry<DefVoxel>::ChunkSize]->Entries[resId % TableEntry<DefVoxel>::ChunkSize];
}
std::optional<DefNode>& getEntry_Node(const std::string_view domain, const std::string_view key) {
return getEntry_Node(getId(EnumDefContent::Node, domain, key));
}
std::optional<DefWorld>& getEntry_World(ResourceId resId) {
std::shared_lock mtx(Profiles_Mtx[static_cast<size_t>(EnumDefContent::World)]);
assert(resId / TableEntry<DefWorld>::ChunkSize < Profiles_World.size());
return Profiles_World[resId / TableEntry<DefVoxel>::ChunkSize]->Entries[resId % TableEntry<DefVoxel>::ChunkSize];
}
std::optional<DefWorld>& getEntry_World(const std::string_view domain, const std::string_view key) {
return getEntry_World(getId(EnumDefContent::World, domain, key));
}
std::optional<DefPortal>& getEntry_Portal(ResourceId resId) {
std::shared_lock mtx(Profiles_Mtx[static_cast<size_t>(EnumDefContent::Portal)]);
assert(resId / TableEntry<DefPortal>::ChunkSize < Profiles_Portal.size());
return Profiles_Portal[resId / TableEntry<DefVoxel>::ChunkSize]->Entries[resId % TableEntry<DefVoxel>::ChunkSize];
}
std::optional<DefPortal>& getEntry_Portal(const std::string_view domain, const std::string_view key) {
return getEntry_Portal(getId(EnumDefContent::Portal, domain, key));
}
std::optional<DefEntity>& getEntry_Entity(ResourceId resId) {
std::shared_lock mtx(Profiles_Mtx[static_cast<size_t>(EnumDefContent::Entity)]);
assert(resId / TableEntry<DefEntity>::ChunkSize < Profiles_Entity.size());
return Profiles_Entity[resId / TableEntry<DefVoxel>::ChunkSize]->Entries[resId % TableEntry<DefVoxel>::ChunkSize];
}
std::optional<DefEntity>& getEntry_Entity(const std::string_view domain, const std::string_view key) {
return getEntry_Entity(getId(EnumDefContent::Entity, domain, key));
}
std::optional<DefItem>& getEntry_Item(ResourceId resId) {
std::shared_lock mtx(Profiles_Mtx[static_cast<size_t>(EnumDefContent::Item)]);
assert(resId / TableEntry<DefItem>::ChunkSize < Profiles_Item.size());
return Profiles_Item[resId / TableEntry<DefVoxel>::ChunkSize]->Entries[resId % TableEntry<DefVoxel>::ChunkSize];
}
std::optional<DefItem>& getEntry_Item(const std::string_view domain, const std::string_view key) {
return getEntry_Item(getId(EnumDefContent::Item, domain, key));
}
ResourceId getId(EnumDefContent type, const std::string_view domain, const std::string_view key) {
ResourceId resId = IdProvider::getId(type, domain, key);
switch(type) {
case EnumDefContent::Voxel:
if(resId >= Profiles_Voxel.size()*TableEntry<DefVoxel>::ChunkSize) {
std::unique_lock mtx(Profiles_Mtx[static_cast<size_t>(EnumDefContent::Voxel)]);
if(resId >= Profiles_Voxel.size()*TableEntry<DefVoxel>::ChunkSize)
Profiles_Voxel.push_back(std::make_unique<TableEntry<DefVoxel>>());
}
break;
case EnumDefContent::Node:
if(resId >= Profiles_Node.size()*TableEntry<DefNode>::ChunkSize) {
std::unique_lock mtx(Profiles_Mtx[static_cast<size_t>(EnumDefContent::Node)]);
if(resId >= Profiles_Node.size()*TableEntry<DefNode>::ChunkSize)
Profiles_Node.push_back(std::make_unique<TableEntry<DefNode>>());
}
break;
case EnumDefContent::World:
if(resId >= Profiles_World.size()*TableEntry<DefWorld>::ChunkSize) {
std::unique_lock mtx(Profiles_Mtx[static_cast<size_t>(EnumDefContent::World)]);
if(resId >= Profiles_World.size()*TableEntry<DefWorld>::ChunkSize)
Profiles_World.push_back(std::make_unique<TableEntry<DefWorld>>());
}
break;
case EnumDefContent::Portal:
if(resId >= Profiles_Portal.size()*TableEntry<DefPortal>::ChunkSize) {
std::unique_lock mtx(Profiles_Mtx[static_cast<size_t>(EnumDefContent::Portal)]);
if(resId >= Profiles_Portal.size()*TableEntry<DefPortal>::ChunkSize)
Profiles_Portal.push_back(std::make_unique<TableEntry<DefPortal>>());
}
break;
case EnumDefContent::Entity:
if(resId >= Profiles_Entity.size()*TableEntry<DefEntity>::ChunkSize) {
std::unique_lock mtx(Profiles_Mtx[static_cast<size_t>(EnumDefContent::Entity)]);
if(resId >= Profiles_Entity.size()*TableEntry<DefEntity>::ChunkSize)
Profiles_Entity.push_back(std::make_unique<TableEntry<DefEntity>>());
}
break;
case EnumDefContent::Item:
if(resId >= Profiles_Item.size()*TableEntry<DefItem>::ChunkSize) {
std::unique_lock mtx(Profiles_Mtx[static_cast<size_t>(EnumDefContent::Item)]);
if(resId >= Profiles_Item.size()*TableEntry<DefItem>::ChunkSize)
Profiles_Item.push_back(std::make_unique<TableEntry<DefItem>>());
}
break;
default:
std::unreachable();
}
return resId;
}
LRU createLRU() {
return {*this};
}
private:
template<typename T>
struct TableEntry {
static constexpr size_t ChunkSize = 4096;
std::array<std::optional<T>, ChunkSize> Entries;
};
void registerBase_Node(ResourceId id, const std::string& domain, const std::string& key, const sol::table& profile);
void registerBase_World(ResourceId id, const std::string& domain, const std::string& key, const sol::table& profile);
void registerBase_Entity(ResourceId id, const std::string& domain, const std::string& key, const sol::table& profile);
template<class type, class modType>
void buildEndProfilesByType(auto& profiles, auto enumType, auto& base, auto& keys, auto& result, auto& mods);
TOS::Logger LOG = "Server>ContentManager";
AssetsManager& AM;
// Профили зарегистрированные модами
std::vector<std::unique_ptr<TableEntry<DefVoxel_Base>>> Profiles_Base_Voxel;
std::vector<std::unique_ptr<TableEntry<DefNode_Base>>> Profiles_Base_Node;
std::vector<std::unique_ptr<TableEntry<DefWorld_Base>>> Profiles_Base_World;
std::vector<std::unique_ptr<TableEntry<DefPortal_Base>>> Profiles_Base_Portal;
std::vector<std::unique_ptr<TableEntry<DefEntity_Base>>> Profiles_Base_Entity;
std::vector<std::unique_ptr<TableEntry<DefItem_Base>>> Profiles_Base_Item;
// Изменения, накладываемые на профили
// Идентификатор [домен мода модификатора, модификатор]
std::unordered_map<ResourceId, std::vector<std::tuple<std::string, DefVoxel_Mod>>> Profiles_Mod_Voxel;
std::unordered_map<ResourceId, std::vector<std::tuple<std::string, DefNode_Mod>>> Profiles_Mod_Node;
std::unordered_map<ResourceId, std::vector<std::tuple<std::string, DefWorld_Mod>>> Profiles_Mod_World;
std::unordered_map<ResourceId, std::vector<std::tuple<std::string, DefPortal_Mod>>> Profiles_Mod_Portal;
std::unordered_map<ResourceId, std::vector<std::tuple<std::string, DefEntity_Mod>>> Profiles_Mod_Entity;
std::unordered_map<ResourceId, std::vector<std::tuple<std::string, DefItem_Mod>>> Profiles_Mod_Item;
// Затронутые профили в процессе регистраций
// По ним будут пересобраны профили
std::vector<ResourceId> ProfileChanges[MAX_ENUM];
// Конечные профили контента
std::array<std::shared_mutex, MAX_ENUM> Profiles_Mtx;
std::vector<std::unique_ptr<TableEntry<DefVoxel>>> Profiles_Voxel;
std::vector<std::unique_ptr<TableEntry<DefNode>>> Profiles_Node;
std::vector<std::unique_ptr<TableEntry<DefWorld>>> Profiles_World;
std::vector<std::unique_ptr<TableEntry<DefPortal>>> Profiles_Portal;
std::vector<std::unique_ptr<TableEntry<DefEntity>>> Profiles_Entity;
std::vector<std::unique_ptr<TableEntry<DefItem>>> Profiles_Item;
};
}

File diff suppressed because it is too large Load Diff

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

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

View File

@@ -1,7 +1,6 @@
#include "Filesystem.hpp"
#include "Server/Abstract.hpp"
#include "Server/SaveBackend.hpp"
#include "TOSLib.hpp"
#include <boost/json/array.hpp>
#include <boost/json/object.hpp>
#include <boost/json/parse.hpp>
@@ -12,283 +11,18 @@
#include <filesystem>
#include <fstream>
#include <string>
#include <vector>
#include <algorithm>
#include <cstring>
namespace LV::Server::SaveBackends {
namespace fs = std::filesystem;
namespace js = boost::json;
namespace {
constexpr uint32_t kRegionVersion = 1;
constexpr size_t kRegionNodeCount = 4 * 4 * 4 * 16 * 16 * 16;
template<typename T>
js::object packIdMap(const std::vector<std::pair<T, std::string>>& map) {
js::object out;
for(const auto& [id, key] : map) {
out[std::to_string(id)] = key;
}
return out;
}
void unpackIdMap(const js::object& obj, std::vector<std::string>& out) {
size_t maxId = 0;
for(const auto& kvp : obj) {
try {
maxId = std::max(maxId, static_cast<size_t>(std::stoul(kvp.key())));
} catch(...) {
continue;
}
}
out.assign(maxId + 1, {});
for(const auto& kvp : obj) {
try {
size_t id = std::stoul(kvp.key());
out[id] = std::string(kvp.value().as_string());
} catch(...) {
continue;
}
}
}
std::string encodeCompressed(const uint8_t* data, size_t size) {
std::u8string compressed = compressLinear(std::u8string_view(reinterpret_cast<const char8_t*>(data), size));
return TOS::Enc::toBase64(reinterpret_cast<const uint8_t*>(compressed.data()), compressed.size());
}
std::u8string decodeCompressed(const std::string& base64) {
if(base64.empty())
return {};
TOS::ByteBuffer buffer = TOS::Enc::fromBase64(base64);
return unCompressLinear(std::u8string_view(reinterpret_cast<const char8_t*>(buffer.data()), buffer.size()));
}
bool writeRegionFile(const fs::path& path, const SB_Region_In& data) {
js::object jobj;
jobj["version"] = kRegionVersion;
{
std::vector<VoxelCube_Region> voxels;
convertChunkVoxelsToRegion(data.Voxels, voxels);
js::object jvoxels;
jvoxels["count"] = static_cast<uint64_t>(voxels.size());
if(!voxels.empty()) {
const uint8_t* raw = reinterpret_cast<const uint8_t*>(voxels.data());
size_t rawSize = sizeof(VoxelCube_Region) * voxels.size();
jvoxels["data"] = encodeCompressed(raw, rawSize);
} else {
jvoxels["data"] = "";
}
jobj["voxels"] = std::move(jvoxels);
jobj["voxels_map"] = packIdMap(data.VoxelsMap);
}
{
js::object jnodes;
const Node* nodePtr = data.Nodes[0].data();
const uint8_t* raw = reinterpret_cast<const uint8_t*>(nodePtr);
size_t rawSize = sizeof(Node) * kRegionNodeCount;
jnodes["data"] = encodeCompressed(raw, rawSize);
jobj["nodes"] = std::move(jnodes);
jobj["nodes_map"] = packIdMap(data.NodeMap);
}
{
js::array ents;
for(const Entity& entity : data.Entityes) {
js::object je;
je["def"] = static_cast<uint64_t>(entity.getDefId());
je["world"] = static_cast<uint64_t>(entity.WorldId);
je["pos"] = js::array{entity.Pos.x, entity.Pos.y, entity.Pos.z};
je["speed"] = js::array{entity.Speed.x, entity.Speed.y, entity.Speed.z};
je["accel"] = js::array{entity.Acceleration.x, entity.Acceleration.y, entity.Acceleration.z};
je["quat"] = js::array{entity.Quat.x, entity.Quat.y, entity.Quat.z, entity.Quat.w};
je["hp"] = static_cast<uint64_t>(entity.HP);
je["abbox"] = js::array{entity.ABBOX.x, entity.ABBOX.y, entity.ABBOX.z};
je["in_region"] = js::array{entity.InRegionPos.x, entity.InRegionPos.y, entity.InRegionPos.z};
js::object tags;
for(const auto& [key, value] : entity.Tags) {
tags[key] = value;
}
je["tags"] = std::move(tags);
ents.push_back(std::move(je));
}
jobj["entities"] = std::move(ents);
jobj["entities_map"] = packIdMap(data.EntityMap);
}
fs::create_directories(path.parent_path());
std::ofstream fd(path, std::ios::binary);
if(!fd)
return false;
fd << js::serialize(jobj);
return true;
}
bool readRegionFile(const fs::path& path, DB_Region_Out& out) {
try {
std::ifstream fd(path, std::ios::binary);
if(!fd)
return false;
out = {};
js::object jobj = js::parse(fd).as_object();
if(auto it = jobj.find("voxels"); it != jobj.end()) {
const js::object& jvoxels = it->value().as_object();
size_t count = 0;
if(auto itCount = jvoxels.find("count"); itCount != jvoxels.end())
count = static_cast<size_t>(itCount->value().to_number<uint64_t>());
std::string base64;
if(auto itData = jvoxels.find("data"); itData != jvoxels.end())
base64 = std::string(itData->value().as_string());
if(count > 0 && !base64.empty()) {
std::u8string raw = decodeCompressed(base64);
if(raw.size() != sizeof(VoxelCube_Region) * count)
return false;
out.Voxels.resize(count);
std::memcpy(out.Voxels.data(), raw.data(), raw.size());
}
}
if(auto it = jobj.find("voxels_map"); it != jobj.end()) {
unpackIdMap(it->value().as_object(), out.VoxelIdToKey);
}
if(auto it = jobj.find("nodes"); it != jobj.end()) {
const js::object& jnodes = it->value().as_object();
std::string base64;
if(auto itData = jnodes.find("data"); itData != jnodes.end())
base64 = std::string(itData->value().as_string());
if(!base64.empty()) {
std::u8string raw = decodeCompressed(base64);
if(raw.size() != sizeof(Node) * kRegionNodeCount)
return false;
std::memcpy(out.Nodes[0].data(), raw.data(), raw.size());
}
}
if(auto it = jobj.find("nodes_map"); it != jobj.end()) {
unpackIdMap(it->value().as_object(), out.NodeIdToKey);
}
if(auto it = jobj.find("entities"); it != jobj.end()) {
const js::array& ents = it->value().as_array();
out.Entityes.reserve(ents.size());
for(const js::value& val : ents) {
const js::object& je = val.as_object();
DefEntityId defId = static_cast<DefEntityId>(je.at("def").to_number<uint64_t>());
Entity entity(defId);
if(auto itWorld = je.find("world"); itWorld != je.end())
entity.WorldId = static_cast<DefWorldId>(itWorld->value().to_number<uint64_t>());
if(auto itPos = je.find("pos"); itPos != je.end()) {
const js::array& arr = itPos->value().as_array();
entity.Pos = Pos::Object(
static_cast<int32_t>(arr.at(0).to_number<int64_t>()),
static_cast<int32_t>(arr.at(1).to_number<int64_t>()),
static_cast<int32_t>(arr.at(2).to_number<int64_t>())
);
}
if(auto itSpeed = je.find("speed"); itSpeed != je.end()) {
const js::array& arr = itSpeed->value().as_array();
entity.Speed = Pos::Object(
static_cast<int32_t>(arr.at(0).to_number<int64_t>()),
static_cast<int32_t>(arr.at(1).to_number<int64_t>()),
static_cast<int32_t>(arr.at(2).to_number<int64_t>())
);
}
if(auto itAccel = je.find("accel"); itAccel != je.end()) {
const js::array& arr = itAccel->value().as_array();
entity.Acceleration = Pos::Object(
static_cast<int32_t>(arr.at(0).to_number<int64_t>()),
static_cast<int32_t>(arr.at(1).to_number<int64_t>()),
static_cast<int32_t>(arr.at(2).to_number<int64_t>())
);
}
if(auto itQuat = je.find("quat"); itQuat != je.end()) {
const js::array& arr = itQuat->value().as_array();
entity.Quat = glm::quat(
static_cast<float>(arr.at(3).to_number<double>()),
static_cast<float>(arr.at(0).to_number<double>()),
static_cast<float>(arr.at(1).to_number<double>()),
static_cast<float>(arr.at(2).to_number<double>())
);
}
if(auto itHp = je.find("hp"); itHp != je.end())
entity.HP = static_cast<uint32_t>(itHp->value().to_number<uint64_t>());
if(auto itAabb = je.find("abbox"); itAabb != je.end()) {
const js::array& arr = itAabb->value().as_array();
entity.ABBOX.x = static_cast<uint64_t>(arr.at(0).to_number<uint64_t>());
entity.ABBOX.y = static_cast<uint64_t>(arr.at(1).to_number<uint64_t>());
entity.ABBOX.z = static_cast<uint64_t>(arr.at(2).to_number<uint64_t>());
}
if(auto itRegion = je.find("in_region"); itRegion != je.end()) {
const js::array& arr = itRegion->value().as_array();
entity.InRegionPos = Pos::GlobalRegion(
static_cast<int16_t>(arr.at(0).to_number<int64_t>()),
static_cast<int16_t>(arr.at(1).to_number<int64_t>()),
static_cast<int16_t>(arr.at(2).to_number<int64_t>())
);
}
if(auto itTags = je.find("tags"); itTags != je.end()) {
const js::object& tags = itTags->value().as_object();
for(const auto& kvp : tags) {
entity.Tags[std::string(kvp.key())] = static_cast<float>(kvp.value().to_number<double>());
}
}
out.Entityes.push_back(std::move(entity));
}
}
if(auto it = jobj.find("entities_map"); it != jobj.end()) {
unpackIdMap(it->value().as_object(), out.EntityToKey);
}
return true;
} catch(const std::exception& exc) {
TOS::Logger("RegionLoader::Filesystem").warn() << "Не удалось загрузить регион " << path << "\n\t" << exc.what();
return false;
}
}
}
class WSB_Filesystem : public IWorldSaveBackend {
fs::path Dir;
public:
WSB_Filesystem(const boost::json::object &data) {
Dir = (std::string) data.at("path").as_string();
Dir = (std::string) data.at("Path").as_string();
}
virtual ~WSB_Filesystem() {
@@ -296,105 +30,84 @@ public:
}
fs::path getPath(std::string worldId, Pos::GlobalRegion regionPos) {
return Dir / worldId / std::to_string(regionPos.x) / std::to_string(regionPos.y) / std::to_string(regionPos.z);
return Dir / worldId / std::to_string(regionPos.X) / std::to_string(regionPos.Y) / std::to_string(regionPos.Z);
}
virtual TickSyncInfo_Out tickSync(TickSyncInfo_In &&data) override {
TickSyncInfo_Out out;
// Сохранение регионов
for(auto& [worldId, regions] : data.ToSave) {
for(auto& [regionPos, region] : regions) {
writeRegionFile(getPath(std::to_string(worldId), regionPos), region);
virtual bool isAsync() { return false; };
virtual bool isExist(std::string worldId, Pos::GlobalRegion regionPos) {
return fs::exists(getPath(worldId, regionPos));
}
virtual void load(std::string worldId, Pos::GlobalRegion regionPos, SB_Region *data) {
std::ifstream fd(getPath(worldId, regionPos));
js::object jobj = js::parse(fd).as_object();
{
js::array &jaVoxels = jobj.at("Voxels").as_array();
for(js::value &jvVoxel : jaVoxels) {
js::object &joVoxel = jvVoxel.as_object();
VoxelCube_Region cube;
cube.VoxelId = joVoxel.at("Material").as_uint64();
cube.Left.X = joVoxel.at("LeftX").as_uint64();
cube.Left.Y = joVoxel.at("LeftY").as_uint64();
cube.Left.Z = joVoxel.at("LeftZ").as_uint64();
cube.Right.X = joVoxel.at("RightX").as_uint64();
cube.Right.Y = joVoxel.at("RightY").as_uint64();
cube.Right.Z = joVoxel.at("RightZ").as_uint64();
data->Voxels.push_back(cube);
}
}
// Загрузка регионов
for(auto& [worldId, regions] : data.Load) {
for(const Pos::GlobalRegion& regionPos : regions) {
const fs::path path = getPath(std::to_string(worldId), regionPos);
if(!fs::exists(path)) {
out.NotExisten[worldId].push_back(regionPos);
continue;
{
js::object &joVoxelMap = jobj.at("VoxelsMap").as_object();
for(js::key_value_pair &jkvp : joVoxelMap) {
data->VoxelsMap[std::stoul(jkvp.key())] = jkvp.value().as_string();
}
DB_Region_Out regionOut;
if(!readRegionFile(path, regionOut)) {
out.NotExisten[worldId].push_back(regionPos);
continue;
}
out.LoadedRegions[worldId].push_back({regionPos, std::move(regionOut)});
}
}
return out;
virtual void save(std::string worldId, Pos::GlobalRegion regionPos, const SB_Region *data) {
js::object jobj;
{
js::array jaVoxels;
for(const VoxelCube_Region &cube : data->Voxels) {
js::object joVoxel;
joVoxel["Material"] = cube.VoxelId;
joVoxel["LeftX"] = cube.Left.X;
joVoxel["LeftY"] = cube.Left.Y;
joVoxel["LeftZ"] = cube.Left.Z;
joVoxel["RightX"] = cube.Right.X;
joVoxel["RightY"] = cube.Right.Y;
joVoxel["RightZ"] = cube.Right.Z;
jaVoxels.push_back(std::move(joVoxel));
}
virtual void changePreloadDistance(uint8_t value) override {
jobj["Voxels"] = std::move(jaVoxels);
}
// virtual void load(std::string worldId, Pos::GlobalRegion regionPos, SB_Region *data) {
// std::ifstream fd(getPath(worldId, regionPos));
// js::object jobj = js::parse(fd).as_object();
{
js::object joVoxelMap;
for(const auto &pair : data->VoxelsMap) {
joVoxelMap[std::to_string(pair.first)] = pair.second;
}
// {
// js::array &jaVoxels = jobj.at("Voxels").as_array();
// for(js::value &jvVoxel : jaVoxels) {
// js::object &joVoxel = jvVoxel.as_object();
// VoxelCube_Region cube;
// cube.Data = joVoxel.at("Data").as_uint64();
// cube.Left.x = joVoxel.at("LeftX").as_uint64();
// cube.Left.y = joVoxel.at("LeftY").as_uint64();
// cube.Left.z = joVoxel.at("LeftZ").as_uint64();
// cube.Right.x = joVoxel.at("RightX").as_uint64();
// cube.Right.y = joVoxel.at("RightY").as_uint64();
// cube.Right.z = joVoxel.at("RightZ").as_uint64();
// data->Voxels.push_back(cube);
// }
// }
jobj["VoxelsMap"] = std::move(joVoxelMap);
}
// {
// js::object &joVoxelMap = jobj.at("VoxelsMap").as_object();
// for(js::key_value_pair &jkvp : joVoxelMap) {
// data->VoxelsMap[std::stoul(jkvp.key())] = jkvp.value().as_string();
// }
// }
// }
fs::create_directories(getPath(worldId, regionPos).parent_path());
std::ofstream fd(getPath(worldId, regionPos));
fd << js::serialize(jobj);
}
// virtual void save(std::string worldId, Pos::GlobalRegion regionPos, const SB_Region *data) {
// js::object jobj;
virtual void remove(std::string worldId, Pos::GlobalRegion regionPos) {
fs::remove(getPath(worldId, regionPos));
}
// {
// js::array jaVoxels;
// for(const VoxelCube_Region &cube : data->Voxels) {
// js::object joVoxel;
// joVoxel["Data"] = cube.Data;
// joVoxel["LeftX"] = cube.Left.x;
// joVoxel["LeftY"] = cube.Left.y;
// joVoxel["LeftZ"] = cube.Left.z;
// joVoxel["RightX"] = cube.Right.x;
// joVoxel["RightY"] = cube.Right.y;
// joVoxel["RightZ"] = cube.Right.z;
// jaVoxels.push_back(std::move(joVoxel));
// }
// jobj["Voxels"] = std::move(jaVoxels);
// }
// {
// js::object joVoxelMap;
// for(const auto &pair : data->VoxelsMap) {
// joVoxelMap[std::to_string(pair.first)] = pair.second;
// }
// jobj["VoxelsMap"] = std::move(joVoxelMap);
// }
// fs::create_directories(getPath(worldId, regionPos).parent_path());
// std::ofstream fd(getPath(worldId, regionPos));
// fd << js::serialize(jobj);
// }
virtual void remove(std::string worldId) {
fs::remove_all(Dir / worldId);
}
};
class PSB_Filesystem : public IPlayerSaveBackend {
@@ -402,7 +115,7 @@ class PSB_Filesystem : public IPlayerSaveBackend {
public:
PSB_Filesystem(const boost::json::object &data) {
Dir = (std::string) data.at("path").as_string();
Dir = (std::string) data.at("Path").as_string();
}
virtual ~PSB_Filesystem() {
@@ -437,7 +150,7 @@ class ASB_Filesystem : public IAuthSaveBackend {
public:
ASB_Filesystem(const boost::json::object &data) {
Dir = (std::string) data.at("path").as_string();
Dir = (std::string) data.at("Path").as_string();
}
virtual ~ASB_Filesystem() {
@@ -450,39 +163,35 @@ public:
virtual bool isAsync() { return false; };
virtual coro<bool> isExist(std::string useranme) override {
co_return fs::exists(getPath(useranme));
virtual bool isExist(std::string playerId) {
return fs::exists(getPath(playerId));
}
virtual coro<> rename(std::string prevUsername, std::string newUsername) override {
fs::rename(getPath(prevUsername), getPath(newUsername));
co_return;
virtual void rename(std::string fromPlayerId, std::string toPlayerId) {
fs::rename(getPath(fromPlayerId), getPath(toPlayerId));
}
virtual coro<bool> load(std::string useranme, SB_Auth& data) override {
std::ifstream fd(getPath(useranme));
virtual void load(std::string playerId, SB_Auth *data) {
std::ifstream fd(getPath(playerId));
js::object jobj = js::parse(fd).as_object();
data.Id = jobj.at("Id").as_uint64();
data.PasswordHash = jobj.at("PasswordHash").as_string();
co_return true;
data->Id = jobj.at("Id").as_uint64();
data->PasswordHash = jobj.at("PasswordHash").as_string();
}
virtual coro<> save(std::string playerId, const SB_Auth& data) override {
virtual void save(std::string playerId, const SB_Auth *data) {
js::object jobj;
jobj["Id"] = data.Id;
jobj["PasswordHash"] = data.PasswordHash;
jobj["Id"] = data->Id;
jobj["PasswordHash"] = data->PasswordHash;
fs::create_directories(getPath(playerId).parent_path());
std::ofstream fd(getPath(playerId));
fd << js::serialize(jobj);
co_return;
}
virtual coro<> remove(std::string username) override {
fs::remove(getPath(username));
co_return;
virtual void remove(std::string playerId) {
fs::remove(getPath(playerId));
}
};
@@ -491,7 +200,7 @@ class MSSB_Filesystem : public IModStorageSaveBackend {
public:
MSSB_Filesystem(const boost::json::object &data) {
Dir = (std::string) data.at("path").as_string();
Dir = (std::string) data.at("Path").as_string();
}
virtual ~MSSB_Filesystem() {

View File

@@ -1,14 +1,10 @@
#include "World.hpp"
#include "ContentManager.hpp"
#include "TOSLib.hpp"
#include <memory>
#include <unordered_set>
namespace LV::Server {
World::World(DefWorldId defId)
World::World(DefWorldId_t defId)
: DefId(defId)
{
@@ -18,77 +14,29 @@ World::~World() {
}
std::vector<Pos::GlobalRegion> World::onRemoteClient_RegionsEnter(WorldId_t worldId, std::shared_ptr<RemoteClient> cec, const std::vector<Pos::GlobalRegion>& enter) {
std::vector<Pos::GlobalRegion> out;
void World::onUpdate(GameServer *server, float dtime) {
for(const Pos::GlobalRegion &pos : enter) {
auto iterRegion = Regions.find(pos);
if(iterRegion == Regions.end()) {
out.push_back(pos);
continue;
}
auto &region = *iterRegion->second;
region.RMs.push_back(cec);
region.NewRMs.push_back(cec);
// Отправить клиенту информацию о чанках и сущностях
std::unordered_map<Pos::bvec4u, const std::vector<VoxelCube>*> voxels;
std::unordered_map<Pos::bvec4u, const Node*> nodes;
for(auto& [key, value] : region.Voxels) {
voxels[key] = &value;
}
for(int z = 0; z < 4; z++)
for(int y = 0; y < 4; y++)
for(int x = 0; x < 4; x++) {
nodes[Pos::bvec4u(x, y, z)] = region.Nodes[Pos::bvec4u(x, y, z).pack()].data();
}
if(!region.Entityes.empty()) {
std::vector<std::tuple<ServerEntityId_t, const Entity*>> updates;
updates.reserve(region.Entityes.size());
for(size_t iter = 0; iter < region.Entityes.size(); iter++) {
const Entity& entity = region.Entityes[iter];
if(entity.IsRemoved)
continue;
ServerEntityId_t entityId = {worldId, pos, static_cast<RegionEntityId_t>(iter)};
updates.emplace_back(entityId, &entity);
}
if(!updates.empty())
cec->prepareEntitiesUpdate(updates);
}
}
return out;
}
void World::onRemoteClient_RegionsLost(WorldId_t worldId, std::shared_ptr<RemoteClient> cec, const std::vector<Pos::GlobalRegion> &lost) {
void World::onCEC_RegionsEnter(ContentEventController *cec, const std::vector<Pos::GlobalRegion> &enter) {
for(const Pos::GlobalRegion &pos : enter) {
std::unique_ptr<Region> &region = Regions[pos];
if(!region) {
region = std::make_unique<Region>();
NeedToLoad.push_back(pos);
}
region->CECs.push_back(cec);
}
}
void World::onCEC_RegionsLost(ContentEventController *cec, const std::vector<Pos::GlobalRegion> &lost) {
for(const Pos::GlobalRegion &pos : lost) {
auto region = Regions.find(pos);
if(region == Regions.end())
continue;
if(!region->second->Entityes.empty()) {
std::vector<ServerEntityId_t> removed;
removed.reserve(region->second->Entityes.size());
for(size_t iter = 0; iter < region->second->Entityes.size(); iter++) {
const Entity& entity = region->second->Entityes[iter];
if(entity.IsRemoved)
continue;
removed.emplace_back(worldId, pos, static_cast<RegionEntityId_t>(iter));
}
if(!removed.empty())
cec->prepareEntitiesRemove(removed);
}
std::vector<std::shared_ptr<RemoteClient>> &CECs = region->second->RMs;
std::vector<ContentEventController*> &CECs = region->second->CECs;
for(size_t iter = 0; iter < CECs.size(); iter++) {
if(CECs[iter] == cec) {
CECs.erase(CECs.begin()+iter);
@@ -98,107 +46,4 @@ void World::onRemoteClient_RegionsLost(WorldId_t worldId, std::shared_ptr<Remote
}
}
World::SaveUnloadInfo World::onStepDatabaseSync(ContentManager& cm, float dtime) {
SaveUnloadInfo out;
constexpr float kSaveDelay = 15.0f;
constexpr float kUnloadDelay = 15.0f;
std::vector<Pos::GlobalRegion> toErase;
toErase.reserve(16);
for(auto& [pos, regionPtr] : Regions) {
Region& region = *regionPtr;
region.LastSaveTime += dtime;
const bool hasChanges = region.IsChanged || region.IsChunkChanged_Voxels || region.IsChunkChanged_Nodes;
const bool needToSave = hasChanges && region.LastSaveTime > kSaveDelay;
const bool needToUnload = region.RMs.empty() && region.LastSaveTime > kUnloadDelay;
if(needToSave || needToUnload) {
SB_Region_In data;
data.Voxels = region.Voxels;
data.Nodes = region.Nodes;
data.Entityes.reserve(region.Entityes.size());
for(const Entity& entity : region.Entityes) {
if(entity.IsRemoved || entity.NeedRemove)
continue;
data.Entityes.push_back(entity);
}
std::unordered_set<DefVoxelId> voxelIds;
for(const auto& [chunkPos, voxels] : region.Voxels) {
(void) chunkPos;
for(const VoxelCube& cube : voxels)
voxelIds.insert(cube.VoxelId);
}
std::unordered_set<DefNodeId> nodeIds;
for(const auto& chunk : region.Nodes) {
for(const Node& node : chunk)
nodeIds.insert(node.NodeId);
}
std::unordered_set<DefEntityId> entityIds;
for(const Entity& entity : data.Entityes)
entityIds.insert(entity.getDefId());
data.VoxelsMap.reserve(voxelIds.size());
for(DefVoxelId id : voxelIds) {
auto dk = cm.getDK(EnumDefContent::Voxel, id);
if(!dk)
continue;
data.VoxelsMap.emplace_back(id, dk->Domain + ":" + dk->Key);
}
data.NodeMap.reserve(nodeIds.size());
for(DefNodeId id : nodeIds) {
auto dk = cm.getDK(EnumDefContent::Node, id);
if(!dk)
continue;
data.NodeMap.emplace_back(id, dk->Domain + ":" + dk->Key);
}
data.EntityMap.reserve(entityIds.size());
for(DefEntityId id : entityIds) {
auto dk = cm.getDK(EnumDefContent::Entity, id);
if(!dk)
continue;
data.EntityMap.emplace_back(id, dk->Domain + ":" + dk->Key);
}
out.ToSave.push_back({pos, std::move(data)});
region.LastSaveTime = 0.0f;
region.IsChanged = false;
}
if(needToUnload) {
out.ToUnload.push_back(pos);
toErase.push_back(pos);
}
}
for(const Pos::GlobalRegion& pos : toErase) {
Regions.erase(pos);
}
return out;
}
void World::pushRegions(std::vector<std::pair<Pos::GlobalRegion, RegionIn>> regions) {
for(auto& [key, value] : regions) {
Region &region = *(Regions[key] = std::make_unique<Region>());
region.Voxels = std::move(value.Voxels);
region.Nodes = value.Nodes;
region.Entityes = std::move(value.Entityes);
}
}
void World::onUpdate(GameServer *server, float dtime) {
}
}

View File

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

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; }
@@ -338,10 +113,9 @@ class ByteBuffer : public std::vector<uint8_t> {
if(Index + sizeof(T) > Obj->size())
throw std::runtime_error("Вышли за пределы буфера");
T value{};
std::memcpy(&value, Obj->data() + Index, sizeof(T));
const uint8_t *ptr = Obj->data()+Index;
Index += sizeof(T);
return swapEndian(value);
return swapEndian(*(const T*) ptr);
}
public:
@@ -363,8 +137,8 @@ class ByteBuffer : public std::vector<uint8_t> {
inline Reader& operator>>(int64_t &value) { value = readOffset<int64_t>(); return *this; }
inline Reader& operator>>(uint64_t &value) { value = readOffset<uint64_t>(); return *this; }
inline Reader& operator>>(bool &value) { value = readOffset<uint8_t>(); return *this; }
inline Reader& operator>>(float &value) { uint32_t raw = readOffset<uint32_t>(); std::memcpy(&value, &raw, sizeof(raw)); return *this; }
inline Reader& operator>>(double &value) { uint64_t raw = readOffset<uint64_t>(); std::memcpy(&value, &raw, sizeof(raw)); return *this; }
inline Reader& operator>>(float &value) { return operator>>(*(uint32_t*) &value); }
inline Reader& operator>>(double &value) { return operator>>(*(uint64_t*) &value); }
inline int8_t readInt8() { int8_t value; this->operator>>(value); return value; }
inline uint8_t readUInt8() { uint8_t value; this->operator>>(value); return value; }
@@ -450,17 +224,6 @@ class ByteBuffer : public std::vector<uint8_t> {
size_t Index = 0;
uint16_t BlockSize = 256;
template<typename T> inline void writeRaw(const T &value)
{
uint8_t *ptr = checkBorder(sizeof(T));
std::memcpy(ptr, &value, sizeof(T));
}
template<typename T> inline void writeSwapped(const T &value)
{
T temp = swapEndian(value);
writeRaw(temp);
}
inline uint8_t* checkBorder(size_t count)
{
@@ -481,17 +244,17 @@ class ByteBuffer : public std::vector<uint8_t> {
Writer& operator=(const Writer&) = default;
Writer& operator=(Writer&&) = default;
inline Writer& operator<<(const int8_t &value) { writeRaw(value); return *this; }
inline Writer& operator<<(const uint8_t &value) { writeRaw(value); return *this; }
inline Writer& operator<<(const int16_t &value) { writeSwapped(value); return *this; }
inline Writer& operator<<(const uint16_t &value) { writeSwapped(value); return *this; }
inline Writer& operator<<(const int32_t &value) { writeSwapped(value); return *this; }
inline Writer& operator<<(const uint32_t &value) { writeSwapped(value); return *this; }
inline Writer& operator<<(const int64_t &value) { writeSwapped(value); return *this; }
inline Writer& operator<<(const uint64_t &value) { writeSwapped(value); return *this; }
inline Writer& operator<<(const bool &value) { uint8_t temp = value ? 1 : 0; writeRaw(temp); return *this; }
inline Writer& operator<<(const float &value) { uint32_t raw; std::memcpy(&raw, &value, sizeof(raw)); writeSwapped(raw); return *this; }
inline Writer& operator<<(const double &value) { uint64_t raw; std::memcpy(&raw, &value, sizeof(raw)); writeSwapped(raw); return *this; }
inline Writer& operator<<(const int8_t &value) { *(int8_t*) checkBorder(sizeof(value)) = value; return *this; }
inline Writer& operator<<(const uint8_t &value) { *(uint8_t*) checkBorder(sizeof(value)) = value; return *this; }
inline Writer& operator<<(const int16_t &value) { *(int16_t*) checkBorder(sizeof(value)) = swapEndian(value); return *this; }
inline Writer& operator<<(const uint16_t &value) { *(uint16_t*) checkBorder(sizeof(value)) = swapEndian(value); return *this; }
inline Writer& operator<<(const int32_t &value) { *(int32_t*) checkBorder(sizeof(value)) = swapEndian(value); return *this; }
inline Writer& operator<<(const uint32_t &value) { *(uint32_t*) checkBorder(sizeof(value)) = swapEndian(value); return *this; }
inline Writer& operator<<(const int64_t &value) { *(int64_t*) checkBorder(sizeof(value)) = swapEndian(value); return *this; }
inline Writer& operator<<(const uint64_t &value) { *(uint64_t*) checkBorder(sizeof(value)) = swapEndian(value); return *this; }
inline Writer& operator<<(const bool &value) { *(uint8_t*) checkBorder(sizeof(value)) = uint8_t(value ? 1 : 0); return *this; }
inline Writer& operator<<(const float &value) { *(uint32_t*) checkBorder(sizeof(value)) = swapEndian(*(uint32_t*) &value); return *this; }
inline Writer& operator<<(const double &value) { *(uint64_t*) checkBorder(sizeof(value)) = swapEndian(*(uint64_t*) &value); return *this; }
inline void writeInt8(const int8_t &value) { this->operator<<(value); }
inline void writeUInt8(const uint8_t &value) { this->operator<<(value); }

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

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, // Позиция
// FX : 9, FY : 9, FZ : 9, // Позиция -112 ~ 369 / 16
// N1 : 4, // Не занято
// LS : 1, // Масштаб карты освещения (1м/16 или 1м)
// Tex : 18, // Текстура
// N2 : 2, // Не занято
// N2 : 14, // Не занято
// TU : 16, TV : 16; // UV на текстуре
// };
void main()
{
uint fx = Vertex.x & 0x7ffu;
uint fy = (Vertex.x >> 11) & 0x7ffu;
uint fz = Vertex.y & 0x7ffu;
vec4 baseVec = ubo.model*vec4(
float(fx) / 64.f - 3.5f,
float(fy) / 64.f - 3.5f,
float(fz) / 64.f - 3.5f,
float(Vertex.x & 0x1ff) / 16.f - 7,
float((Vertex.x >> 9) & 0x1ff) / 16.f - 7,
float((Vertex.x >> 18) & 0x1ff) / 16.f - 7,
1
);
Geometry.GeoPos = baseVec.xyz;
Geometry.Texture = (Vertex.y >> 12) & 0x3ffffu;
Geometry.Texture = Vertex.y & 0x3ffff;
Geometry.UV = vec2(
float(Vertex.z & 0xffff) / pow(2, 16),
float((Vertex.z >> 16) & 0xffff) / pow(2, 16)

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);
uint flags = (texId & 0xffff0000) >> 16;
texId &= 0xffff;
vec4 color = vec4(uv, 0, 1);
vec2 baseUV = vec2(uv.x, 1.0f - uv.y);
vec2 atlasUV = mix(entry.UVMinMax.xy, entry.UVMinMax.zw, baseUV);
atlasUV = clamp(atlasUV, entry.UVMinMax.xy, entry.UVMinMax.zw);
return texture(MainAtlas, vec3(atlasUV, entry.Layer));
}
vec3 blendOverlay(vec3 base, vec3 blend) {
vec3 result;
for (int i = 0; i < 3; ++i) {
if (base[i] <= 0.5)
result[i] = 2.0 * base[i] * blend[i];
else
result[i] = 1.0 - 2.0 * (1.0 - base[i]) * (1.0 - blend[i]);
if((flags & (2 | 4)) > 0)
{
if((flags & 2) > 0)
color = vec4(1, 1, 1, 1);
else if((flags & 4) > 0)
{
color = vec4(1);
}
return result;
}
else if(texId >= uint(MainAtlasLayout.SubsCount))
return vec4(((int(gl_FragCoord.x / 128) + int(gl_FragCoord.y / 128)) % 2 ) * vec3(0, 1, 1), 1);
else {
InfoSubTexture texInfo = MainAtlasLayout.SubTextures[texId];
if(texInfo.Flags == 0)
return vec4(((int(gl_FragCoord.x / 128) + int(gl_FragCoord.y / 128)) % 2 ) * vec3(1, 0, 1), 1);
uint posX = texInfo.PosXY & 0xffff;
uint posY = (texInfo.PosXY >> 16) & 0xffff;
uint width = texInfo.WidthHeight & 0xffff;
uint height = (texInfo.WidthHeight >> 16) & 0xffff;
uint awidth = MainAtlasLayout.WidthHeight & 0xffff;
uint aheight = (MainAtlasLayout.WidthHeight >> 16) & 0xffff;
if((flags & 1) > 0)
color = texture(MainAtlas, vec2((posX+0.5f+uv.x*(width-1))/awidth, (posY+0.5f+(1-uv.y)*(height-1))/aheight));
else
color = texture(MainAtlas, vec2((posX+uv.x*width)/awidth, (posY+(1-uv.y)*height)/aheight));
}
return color;
}
void main() {
Frame = atlasColor(Fragment.Texture, Fragment.UV);
Frame.xyz *= max(0.2f, dot(Fragment.Normal, normalize(vec3(0.5, 1, 0.8))));
// Frame = vec4(blendOverlay(vec3(Frame), vec3(Fragment.GeoPos/64.f)), Frame.w);
if(Frame.w == 0)
discard;
}

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,22 +18,6 @@ layout(set = 1, binding = 1) readonly buffer LightMapLayoutObj {
vec3 Color;
} LightMapLayout;
vec4 atlasColor(uint texId, vec2 uv)
{
AtlasEntry entry = MainAtlasLayout.Entries[texId];
if((entry.Flags & ATLAS_ENTRY_VALID) == 0u)
return vec4(((int(gl_FragCoord.x / 128) + int(gl_FragCoord.y / 128)) % 2) * vec3(1, 0, 1), 1);
vec2 baseUV = vec2(uv.x, 1.0f - uv.y);
vec2 atlasUV = mix(entry.UVMinMax.xy, entry.UVMinMax.zw, baseUV);
atlasUV = clamp(atlasUV, entry.UVMinMax.xy, entry.UVMinMax.zw);
return texture(MainAtlas, vec3(atlasUV, entry.Layer));
}
void main() {
Frame = atlasColor(Fragment.Texture, Fragment.UV);
Frame.xyz *= max(0.2f, dot(Fragment.Normal, normalize(vec3(0.5, 1, 0.8))));
if(Frame.w == 0)
discard;
Frame = vec4(1);
}

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);
vec2 baseUV = vec2(uv.x, 1.0f - uv.y);
vec2 atlasUV = mix(entry.UVMinMax.xy, entry.UVMinMax.zw, baseUV);
atlasUV = clamp(atlasUV, entry.UVMinMax.xy, entry.UVMinMax.zw);
return texture(MainAtlas, vec3(atlasUV, entry.Layer));
if((flags & (2 | 4)) > 0)
{
if((flags & 2) > 0)
color = vec4(1, 1, 1, 1);
else if((flags & 4) > 0)
{
color = vec4(1);
}
}
else if(texId >= uint(MainAtlasLayout.SubsCount))
return vec4(((int(gl_FragCoord.x / 128) + int(gl_FragCoord.y / 128)) % 2 ) * vec3(0, 1, 1), 1);
else {
InfoSubTexture texInfo = MainAtlasLayout.SubTextures[texId];
if(texInfo.Flags == 0)
return vec4(((int(gl_FragCoord.x / 128) + int(gl_FragCoord.y / 128)) % 2 ) * vec3(1, 0, 1), 1);
uint posX = texInfo.PosXY & 0xffff;
uint posY = (texInfo.PosXY >> 16) & 0xffff;
uint width = texInfo.WidthHeight & 0xffff;
uint height = (texInfo.WidthHeight >> 16) & 0xffff;
uint awidth = MainAtlasLayout.WidthHeight & 0xffff;
uint aheight = (MainAtlasLayout.WidthHeight >> 16) & 0xffff;
if((flags & 1) > 0)
color = texture(MainAtlas, vec2((posX+0.5f+uv.x*(width-1))/awidth, (posY+0.5f+(1-uv.y)*(height-1))/aheight));
else
color = texture(MainAtlas, vec2((posX+uv.x*width)/awidth, (posY+(1-uv.y)*height)/aheight));
}
return color;
}
void main() {

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

View File

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

View File

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

View File

@@ -1,105 +0,0 @@
# Текстурные программы (TexturePipelineProgram)
Текстурная программа — это строка, описывающая источник текстуры и цепочку операций
над ней. Такие строки используются в `textures` моделей и компилируются
`TexturePipelineProgram`.
## Общая форма
```
[tex] <base> [|> op(...)]*
```
`tex` в начале необязателен.
## Базовые выражения
- `name` или `"name.png"` — ссылка на текстуру из assets. Расширение .png/.jpg/.jpeg допустимо.
- `anim(...)` — анимация из спрайт-листа (см. ниже).
- `<w>x<h> <#RRGGBB|#RRGGBBAA>` — заливка цветом.
Примеры:
```
stone
tex "core:stone.png"
32x32 "#FF00FF"
```
## Аргументы операций
- Позиционные: `op(1, 2, "str")`
- Именованные: `op(w=16, h=16)`
- Значения: числа (uint32), строки в кавычках, либо идентификаторы.
Цвета задаются `#RRGGBB` или `#RRGGBBAA`.
## Операции пайплайна
Операции без аргументов можно писать без `()`: `brighten` и т.п.
В подвыражениях текстур (см. ниже) операции без аргументов нужно писать со скобками:
`brighten()`.
### Операции, принимающие текстуру
- `overlay(tex)` — наложение с альфой.
- `mask(tex)` — применение альфа-маски.
- `lowpart(percent, tex)` — смешивание нижней части (percent 1..100).
`tex` может быть:
- именем текстуры: `overlay("core:stone")`
- именованным аргументом: `overlay(tex="core:stone")`
- вложенной программой: `overlay( tex stone |> invert("rgb") )`
### Геометрия и альфа
- `resize(w, h)` — ресайз до размеров.
- `transform(t)` — трансформация (значение 0..7).
- `opacity(a)` — прозрачность 0..255.
- `remove_alpha` или `noalpha` — убрать альфа-канал.
- `make_alpha(color)` — сделать альфу по цвету (цвет в `#RRGGBB`).
### Цвет и яркость
- `invert(channels="rgb")` — инверсия каналов (`r`, `g`, `b`, `a`).
- `brighten()` — лёгкое осветление.
- `contrast(value, brightness)` — контраст и яркость (-127..127).
- `multiply(color)` — умножение на цвет.
- `screen(color)` — экранный режим.
- `colorize(color, ratio=255)` — тонирование цветом.
### Анимация
`anim` можно использовать в базе (с указанием текстуры) или в пайплайне
над текущим изображением.
База:
```
anim(tex, frame_w, frame_h, frames, fps, smooth, axis)
```
Пайплайн:
```
... |> anim(frame_w, frame_h, frames, fps, smooth, axis)
```
Именованные аргументы:
- `tex` — имя текстуры (только для базового `anim`).
- `frame_w` или `w`
- `frame_h` или `h`
- `frames` или `count`
- `fps`
- `smooth` (0/1)
- `axis` — режим нарезки:
- `g` или пусто: по сетке (слева направо, сверху вниз)
- `x`/`h`: по горизонтали
- `y`/`v`: по вертикали
Если `frames` не задан, количество кадров вычисляется автоматически:
- сетка: `(sheet.W / frame_w) * (sheet.H / frame_h)`
- ось X/Y: `sheet.W / frame_w` или `sheet.H / frame_h`
Примеры:
```
anim("core:sheet", 16, 16, fps=8) # сетка по умолчанию
anim("core:sheet", 16, 16, axis="x") # по горизонтали
stone |> anim(16, 16, fps=10, smooth=1) # анимировать текущую текстуру
```
## Вложенные текстурные выражения
Некоторые операции принимают текстуру в аргументах. Чтобы передать не только имя,
но и полноценную программу, используйте префикс `tex`:
```
overlay( tex "core:stone" |> resize(16,16) |> brighten() )
```