Compare commits
33 Commits
d47a5cc090
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 0b8326e278 | |||
| 07ccd4dd68 | |||
| da673b0965 | |||
| 3fb06080db | |||
| affdc75ebd | |||
| 49c4d77c59 | |||
| 16a0fa5f7a | |||
| a29e772f35 | |||
| 5135aa30a7 | |||
| 523f9725c0 | |||
| c13ad06ba9 | |||
| 83530a6c15 | |||
| b61cc9fb03 | |||
| 7224499d14 | |||
| ad4b0d593a | |||
| d3add82c55 | |||
| 2b2be796e9 | |||
| 6c7a6df8f6 | |||
| 8ce820569a | |||
| 5904fe6853 | |||
| 6dd1f93221 | |||
| 01ea7eee74 | |||
| dbebf50552 | |||
| 51cc68e1b2 | |||
| 7c54f429ba | |||
| abe7b987c4 | |||
| 83c4628995 | |||
| 2759073bb3 | |||
| 2540439bf0 | |||
| d9e40b4e80 | |||
| 776e9bfaca | |||
| f56b46f669 | |||
| 4aa7c6f41a |
@@ -84,6 +84,16 @@ FetchContent_Declare(
|
|||||||
FetchContent_MakeAvailable(Boost)
|
FetchContent_MakeAvailable(Boost)
|
||||||
target_link_libraries(luavox_common INTERFACE Boost::asio Boost::thread Boost::json Boost::iostreams Boost::interprocess Boost::timer Boost::circular_buffer Boost::lockfree Boost::stacktrace Boost::uuid Boost::serialization Boost::nowide)
|
target_link_libraries(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)
|
||||||
|
|
||||||
# glm
|
# glm
|
||||||
# find_package(glm REQUIRED)
|
# find_package(glm REQUIRED)
|
||||||
# target_include_directories(${PROJECT_NAME} PUBLIC ${GLM_INCLUDE_DIR})
|
# target_include_directories(${PROJECT_NAME} PUBLIC ${GLM_INCLUDE_DIR})
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "Common/Net.hpp"
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <functional>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
|
#include <variant>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <Common/Abstract.hpp>
|
#include <Common/Abstract.hpp>
|
||||||
|
|
||||||
@@ -60,15 +64,40 @@ public:
|
|||||||
// states
|
// 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 {
|
class IRenderSession {
|
||||||
public:
|
public:
|
||||||
// Объект уведомления об изменениях
|
// Объект уведомления об изменениях
|
||||||
struct TickSyncData {
|
struct TickSyncData {
|
||||||
// Новые или изменённые используемые теперь двоичные ресурсы
|
// Изменения в ассетах.
|
||||||
std::unordered_map<EnumAssets, std::vector<ResourceId>> Assets_ChangeOrAdd;
|
std::vector<AssetsModelUpdate> AssetsModels;
|
||||||
// Более не используемые ресурсы
|
std::vector<AssetsNodestateUpdate> AssetsNodestates;
|
||||||
std::unordered_map<EnumAssets, std::vector<ResourceId>> Assets_Lost;
|
std::vector<AssetsTextureUpdate> AssetsTextures;
|
||||||
|
|
||||||
// Новые или изменённые профили контента
|
// Новые или изменённые профили контента
|
||||||
std::unordered_map<EnumDefContent, std::vector<ResourceId>> Profiles_ChangeOrAdd;
|
std::unordered_map<EnumDefContent, std::vector<ResourceId>> Profiles_ChangeOrAdd;
|
||||||
@@ -87,7 +116,7 @@ public:
|
|||||||
// Началась стадия изменения данных IServerSession, все должны приостановить работу
|
// Началась стадия изменения данных IServerSession, все должны приостановить работу
|
||||||
virtual void pushStageTickSync() = 0;
|
virtual void pushStageTickSync() = 0;
|
||||||
// После изменения внутренних данных IServerSession, IRenderSession уведомляется об изменениях
|
// После изменения внутренних данных IServerSession, IRenderSession уведомляется об изменениях
|
||||||
virtual void tickSync(const TickSyncData& data) = 0;
|
virtual void tickSync(TickSyncData& data) = 0;
|
||||||
|
|
||||||
// Установить позицию для камеры
|
// Установить позицию для камеры
|
||||||
virtual void setCameraPos(WorldId_t worldId, Pos::Object pos, glm::quat quat) = 0;
|
virtual void setCameraPos(WorldId_t worldId, Pos::Object pos, glm::quat quat) = 0;
|
||||||
@@ -112,7 +141,6 @@ struct DefPortalInfo {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct DefEntityInfo {
|
struct DefEntityInfo {
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DefFuncEntityInfo {
|
struct DefFuncEntityInfo {
|
||||||
@@ -125,42 +153,107 @@ struct WorldInfo {
|
|||||||
std::unordered_map<Pos::GlobalRegion, Region> Regions;
|
std::unordered_map<Pos::GlobalRegion, Region> Regions;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct VoxelInfo {
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
struct NodeInfo {
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
struct PortalInfo {
|
struct PortalInfo {
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct EntityInfo {
|
struct EntityInfo {
|
||||||
|
DefEntityId DefId = 0;
|
||||||
|
WorldId_t WorldId = 0;
|
||||||
|
Pos::Object Pos = Pos::Object(0);
|
||||||
|
glm::quat Quat = glm::quat(1.f, 0.f, 0.f, 0.f);
|
||||||
};
|
};
|
||||||
|
|
||||||
struct FuncEntityInfo {
|
/*
|
||||||
|
Конструируются с серверными идентификаторами
|
||||||
|
*/
|
||||||
|
|
||||||
|
struct DefVoxel {
|
||||||
|
DefVoxel() = default;
|
||||||
|
DefVoxel(const std::u8string_view view) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void reBind(const std::function<ResourceId(EnumAssets, ResourceId)>& am) {
|
||||||
|
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DefItemInfo {
|
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 DefVoxel_t {};
|
struct DefWorld {
|
||||||
struct DefNode_t {
|
DefWorld() = default;
|
||||||
AssetsNodestate NodestateId = 0;
|
DefWorld(const std::u8string_view view) {
|
||||||
AssetsTexture TexId = 0;
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
struct AssetEntry {
|
||||||
EnumAssets Type;
|
ResourceId Id = 0;
|
||||||
ResourceId Id;
|
std::string Domain;
|
||||||
std::string Domain, Key;
|
std::string Key;
|
||||||
Resource Res;
|
|
||||||
|
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;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -173,17 +266,17 @@ struct AssetEntry {
|
|||||||
*/
|
*/
|
||||||
class IServerSession {
|
class IServerSession {
|
||||||
public:
|
public:
|
||||||
// Используемые двоичные ресурсы
|
// Включить логирование входящих сетевых пакетов на клиенте.
|
||||||
std::unordered_map<EnumAssets, std::unordered_map<ResourceId, AssetEntry>> Assets;
|
bool DebugLogPackets = false;
|
||||||
|
|
||||||
// Используемые профили контента
|
// Используемые профили контента
|
||||||
struct {
|
struct {
|
||||||
std::unordered_map<DefVoxelId, DefVoxel_t> DefVoxel;
|
std::unordered_map<DefVoxelId, DefVoxel> DefVoxels;
|
||||||
std::unordered_map<DefNodeId, DefNode_t> DefNode;
|
std::unordered_map<DefNodeId, DefNode> DefNodes;
|
||||||
std::unordered_map<DefWorldId, DefWorldInfo> DefWorld;
|
std::unordered_map<DefWorldId, DefWorld> DefWorlds;
|
||||||
std::unordered_map<DefPortalId, DefPortalInfo> DefPortal;
|
std::unordered_map<DefPortalId, DefPortal> DefPortals;
|
||||||
std::unordered_map<DefEntityId, DefEntityInfo> DefEntity;
|
std::unordered_map<DefEntityId, DefEntity> DefEntitys;
|
||||||
std::unordered_map<DefItemId, DefItemInfo> DefItem;
|
std::unordered_map<DefItemId, DefItem> DefItems;
|
||||||
} Profiles;
|
} Profiles;
|
||||||
|
|
||||||
// Видимый контент
|
// Видимый контент
|
||||||
@@ -232,4 +325,4 @@ public:
|
|||||||
virtual ~ISurfaceEventListener();
|
virtual ~ISurfaceEventListener();
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
#include "AssetsManager.hpp"
|
#include "AssetsCacheManager.hpp"
|
||||||
#include "Common/Abstract.hpp"
|
#include "Common/Abstract.hpp"
|
||||||
#include "sqlite3.h"
|
#include "sqlite3.h"
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
#include <cstring>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
@@ -13,7 +14,7 @@
|
|||||||
namespace LV::Client {
|
namespace LV::Client {
|
||||||
|
|
||||||
|
|
||||||
AssetsManager::AssetsManager(boost::asio::io_context &ioc, const fs::path &cachePath,
|
AssetsCacheManager::AssetsCacheManager(boost::asio::io_context &ioc, const fs::path &cachePath,
|
||||||
size_t maxCacheDirectorySize, size_t maxLifeTime)
|
size_t maxCacheDirectorySize, size_t maxLifeTime)
|
||||||
: IAsyncDestructible(ioc), CachePath(cachePath)
|
: IAsyncDestructible(ioc), CachePath(cachePath)
|
||||||
{
|
{
|
||||||
@@ -106,6 +107,14 @@ AssetsManager::AssetsManager(boost::asio::io_context &ioc, const fs::path &cache
|
|||||||
MAKE_ERROR("Не удалось подготовить запрос STMT_DISK_COUNT: " << sqlite3_errmsg(DB));
|
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"(
|
sql = R"(
|
||||||
INSERT OR REPLACE INTO inline_cache (sha256, last_used, data)
|
INSERT OR REPLACE INTO inline_cache (sha256, last_used, data)
|
||||||
VALUES (?, ?, ?);
|
VALUES (?, ?, ?);
|
||||||
@@ -147,18 +156,35 @@ AssetsManager::AssetsManager(boost::asio::io_context &ioc, const fs::path &cache
|
|||||||
if(sqlite3_prepare_v2(DB, sql, -1, &STMT_INLINE_COUNT, nullptr) != SQLITE_OK) {
|
if(sqlite3_prepare_v2(DB, sql, -1, &STMT_INLINE_COUNT, nullptr) != SQLITE_OK) {
|
||||||
MAKE_ERROR("Не удалось подготовить запрос STMT_INLINE_COUNT: " << sqlite3_errmsg(DB));
|
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() << "Успешно, запускаем поток обработки";
|
LOG.debug() << "Успешно, запускаем поток обработки";
|
||||||
OffThread = std::thread(&AssetsManager::readWriteThread, this, AUC.use());
|
OffThread = std::thread(&AssetsCacheManager::readWriteThread, this, AUC.use());
|
||||||
LOG.info() << "Инициализировано хранилище кеша: " << CachePath.c_str();
|
LOG.info() << "Инициализировано хранилище кеша: " << CachePath.c_str();
|
||||||
}
|
}
|
||||||
|
|
||||||
AssetsManager::~AssetsManager() {
|
AssetsCacheManager::~AssetsCacheManager() {
|
||||||
for(sqlite3_stmt* stmt : {
|
for(sqlite3_stmt* stmt : {
|
||||||
STMT_DISK_INSERT, STMT_DISK_UPDATE_TIME, STMT_DISK_REMOVE, STMT_DISK_CONTAINS,
|
STMT_DISK_INSERT, STMT_DISK_UPDATE_TIME, STMT_DISK_REMOVE, STMT_DISK_CONTAINS,
|
||||||
STMT_DISK_SUM, STMT_DISK_COUNT, STMT_INLINE_INSERT, STMT_INLINE_GET,
|
STMT_DISK_SUM, STMT_DISK_COUNT, STMT_DISK_OLDEST, STMT_INLINE_INSERT,
|
||||||
STMT_INLINE_UPDATE_TIME, STMT_INLINE_SUM, STMT_INLINE_COUNT
|
STMT_INLINE_GET, STMT_INLINE_UPDATE_TIME, STMT_INLINE_SUM,
|
||||||
|
STMT_INLINE_COUNT, STMT_INLINE_REMOVE, STMT_INLINE_OLDEST
|
||||||
}) {
|
}) {
|
||||||
if(stmt)
|
if(stmt)
|
||||||
sqlite3_finalize(stmt);
|
sqlite3_finalize(stmt);
|
||||||
@@ -172,24 +198,19 @@ AssetsManager::~AssetsManager() {
|
|||||||
LOG.info() << "Хранилище кеша закрыто";
|
LOG.info() << "Хранилище кеша закрыто";
|
||||||
}
|
}
|
||||||
|
|
||||||
coro<> AssetsManager::asyncDestructor() {
|
coro<> AssetsCacheManager::asyncDestructor() {
|
||||||
NeedShutdown = true;
|
NeedShutdown = true;
|
||||||
co_await IAsyncDestructible::asyncDestructor();
|
co_await IAsyncDestructible::asyncDestructor();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AssetsManager::readWriteThread(AsyncUseControl::Lock lock) {
|
void AssetsCacheManager::readWriteThread(AsyncUseControl::Lock lock) {
|
||||||
try {
|
try {
|
||||||
std::vector<fs::path> assets;
|
[[maybe_unused]] size_t maxCacheDatabaseSize = 0;
|
||||||
size_t maxCacheDatabaseSize, maxLifeTime;
|
[[maybe_unused]] size_t maxLifeTime = 0;
|
||||||
|
bool databaseSizeKnown = false;
|
||||||
|
|
||||||
while(!NeedShutdown || !WriteQueue.get_read().empty()) {
|
while(!NeedShutdown || !WriteQueue.get_read().empty()) {
|
||||||
// Получить новые данные
|
// Получить новые данные
|
||||||
if(Changes.get_read().AssetsChange) {
|
|
||||||
auto lock = Changes.lock();
|
|
||||||
assets = std::move(lock->Assets);
|
|
||||||
lock->AssetsChange = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(Changes.get_read().MaxChange) {
|
if(Changes.get_read().MaxChange) {
|
||||||
auto lock = Changes.lock();
|
auto lock = Changes.lock();
|
||||||
maxCacheDatabaseSize = lock->MaxCacheDatabaseSize;
|
maxCacheDatabaseSize = lock->MaxCacheDatabaseSize;
|
||||||
@@ -215,79 +236,47 @@ void AssetsManager::readWriteThread(AsyncUseControl::Lock lock) {
|
|||||||
|
|
||||||
// Чтение
|
// Чтение
|
||||||
if(!ReadQueue.get_read().empty()) {
|
if(!ReadQueue.get_read().empty()) {
|
||||||
ResourceKey rk;
|
Hash_t hash;
|
||||||
|
|
||||||
{
|
{
|
||||||
auto lock = ReadQueue.lock();
|
auto lock = ReadQueue.lock();
|
||||||
rk = lock->front();
|
hash = lock->front();
|
||||||
lock->pop();
|
lock->pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool finded = false;
|
bool finded = false;
|
||||||
// Сначала пробежимся по ресурспакам
|
// Поищем в малой базе
|
||||||
{
|
sqlite3_bind_blob(STMT_INLINE_GET, 1, (const void*) hash.data(), 32, SQLITE_STATIC);
|
||||||
std::string_view type;
|
int errc = sqlite3_step(STMT_INLINE_GET);
|
||||||
|
if(errc == SQLITE_ROW) {
|
||||||
switch(rk.Type) {
|
// Есть запись
|
||||||
case EnumAssets::Nodestate: type = "nodestate"; break;
|
const uint8_t *data = (const uint8_t*) sqlite3_column_blob(STMT_INLINE_GET, 0);
|
||||||
case EnumAssets::Particle: type = "particle"; break;
|
int size = sqlite3_column_bytes(STMT_INLINE_GET, 0);
|
||||||
case EnumAssets::Animation: type = "animation"; break;
|
Resource res(data, size);
|
||||||
case EnumAssets::Model: type = "model"; break;
|
finded = true;
|
||||||
case EnumAssets::Texture: type = "texture"; break;
|
ReadyQueue.lock()->emplace_back(hash, res);
|
||||||
case EnumAssets::Sound: type = "sound"; break;
|
} else if(errc != SQLITE_DONE) {
|
||||||
case EnumAssets::Font: type = "font"; break;
|
sqlite3_reset(STMT_INLINE_GET);
|
||||||
default:
|
MAKE_ERROR("Не удалось выполнить подготовленный запрос STMT_INLINE_GET: " << sqlite3_errmsg(DB));
|
||||||
std::unreachable();
|
|
||||||
}
|
|
||||||
|
|
||||||
for(const fs::path& path : assets) {
|
|
||||||
fs::path end = path / rk.Domain / type / rk.Key;
|
|
||||||
|
|
||||||
if(!fs::exists(end))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Нашли
|
|
||||||
finded = true;
|
|
||||||
Resource res = Resource(end).convertToMem();
|
|
||||||
ReadyQueue.lock()->emplace_back(rk, res);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!finded) {
|
sqlite3_reset(STMT_INLINE_GET);
|
||||||
// Поищем в малой базе
|
|
||||||
sqlite3_bind_blob(STMT_INLINE_GET, 1, (const void*) rk.Hash.data(), 32, SQLITE_STATIC);
|
|
||||||
int errc = sqlite3_step(STMT_INLINE_GET);
|
|
||||||
if(errc == SQLITE_ROW) {
|
|
||||||
// Есть запись
|
|
||||||
const uint8_t *hash = (const uint8_t*) sqlite3_column_blob(STMT_INLINE_GET, 0);
|
|
||||||
int size = sqlite3_column_bytes(STMT_INLINE_GET, 0);
|
|
||||||
Resource res(hash, size);
|
|
||||||
finded = true;
|
|
||||||
ReadyQueue.lock()->emplace_back(rk, res);
|
|
||||||
} else if(errc != SQLITE_DONE) {
|
|
||||||
sqlite3_reset(STMT_INLINE_GET);
|
|
||||||
MAKE_ERROR("Не удалось выполнить подготовленный запрос STMT_INLINE_GET: " << sqlite3_errmsg(DB));
|
|
||||||
}
|
|
||||||
|
|
||||||
sqlite3_reset(STMT_INLINE_GET);
|
|
||||||
|
|
||||||
if(finded) {
|
|
||||||
sqlite3_bind_blob(STMT_INLINE_UPDATE_TIME, 1, (const void*) rk.Hash.data(), 32, SQLITE_STATIC);
|
|
||||||
sqlite3_bind_int(STMT_INLINE_UPDATE_TIME, 2, time(nullptr));
|
|
||||||
if(sqlite3_step(STMT_INLINE_UPDATE_TIME) != SQLITE_DONE) {
|
|
||||||
sqlite3_reset(STMT_INLINE_UPDATE_TIME);
|
|
||||||
MAKE_ERROR("Не удалось выполнить подготовленный запрос STMT_INLINE_UPDATE_TIME: " << sqlite3_errmsg(DB));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
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);
|
sqlite3_reset(STMT_INLINE_UPDATE_TIME);
|
||||||
|
MAKE_ERROR("Не удалось выполнить подготовленный запрос STMT_INLINE_UPDATE_TIME: " << sqlite3_errmsg(DB));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sqlite3_reset(STMT_INLINE_UPDATE_TIME);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!finded) {
|
if(!finded) {
|
||||||
// Поищем на диске
|
// Поищем на диске
|
||||||
sqlite3_bind_blob(STMT_DISK_CONTAINS, 1, (const void*) rk.Hash.data(), 32, SQLITE_STATIC);
|
sqlite3_bind_blob(STMT_DISK_CONTAINS, 1, (const void*) hash.data(), 32, SQLITE_STATIC);
|
||||||
int errc = sqlite3_step(STMT_DISK_CONTAINS);
|
errc = sqlite3_step(STMT_DISK_CONTAINS);
|
||||||
if(errc == SQLITE_ROW) {
|
if(errc == SQLITE_ROW) {
|
||||||
// Есть запись
|
// Есть запись
|
||||||
std::string hashKey;
|
std::string hashKey;
|
||||||
@@ -295,13 +284,13 @@ void AssetsManager::readWriteThread(AsyncUseControl::Lock lock) {
|
|||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << std::hex << std::setfill('0') << std::setw(2);
|
ss << std::hex << std::setfill('0') << std::setw(2);
|
||||||
for (int i = 0; i < 32; ++i)
|
for (int i = 0; i < 32; ++i)
|
||||||
ss << static_cast<int>(rk.Hash[i]);
|
ss << static_cast<int>(hash[i]);
|
||||||
|
|
||||||
hashKey = ss.str();
|
hashKey = ss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
finded = true;
|
finded = true;
|
||||||
ReadyQueue.lock()->emplace_back(rk, PathFiles / hashKey.substr(0, 2) / hashKey.substr(2));
|
ReadyQueue.lock()->emplace_back(hash, PathFiles / hashKey.substr(0, 2) / hashKey.substr(2));
|
||||||
} else if(errc != SQLITE_DONE) {
|
} else if(errc != SQLITE_DONE) {
|
||||||
sqlite3_reset(STMT_DISK_CONTAINS);
|
sqlite3_reset(STMT_DISK_CONTAINS);
|
||||||
MAKE_ERROR("Не удалось выполнить подготовленный запрос STMT_DISK_CONTAINS: " << sqlite3_errmsg(DB));
|
MAKE_ERROR("Не удалось выполнить подготовленный запрос STMT_DISK_CONTAINS: " << sqlite3_errmsg(DB));
|
||||||
@@ -311,7 +300,7 @@ void AssetsManager::readWriteThread(AsyncUseControl::Lock lock) {
|
|||||||
|
|
||||||
if(finded) {
|
if(finded) {
|
||||||
sqlite3_bind_int(STMT_DISK_UPDATE_TIME, 1, time(nullptr));
|
sqlite3_bind_int(STMT_DISK_UPDATE_TIME, 1, time(nullptr));
|
||||||
sqlite3_bind_blob(STMT_DISK_UPDATE_TIME, 2, (const void*) rk.Hash.data(), 32, SQLITE_STATIC);
|
sqlite3_bind_blob(STMT_DISK_UPDATE_TIME, 2, (const void*) hash.data(), 32, SQLITE_STATIC);
|
||||||
if(sqlite3_step(STMT_DISK_UPDATE_TIME) != SQLITE_DONE) {
|
if(sqlite3_step(STMT_DISK_UPDATE_TIME) != SQLITE_DONE) {
|
||||||
sqlite3_reset(STMT_DISK_UPDATE_TIME);
|
sqlite3_reset(STMT_DISK_UPDATE_TIME);
|
||||||
MAKE_ERROR("Не удалось выполнить подготовленный запрос STMT_DISK_UPDATE_TIME: " << sqlite3_errmsg(DB));
|
MAKE_ERROR("Не удалось выполнить подготовленный запрос STMT_DISK_UPDATE_TIME: " << sqlite3_errmsg(DB));
|
||||||
@@ -323,7 +312,7 @@ void AssetsManager::readWriteThread(AsyncUseControl::Lock lock) {
|
|||||||
|
|
||||||
if(!finded) {
|
if(!finded) {
|
||||||
// Не нашли
|
// Не нашли
|
||||||
ReadyQueue.lock()->emplace_back(rk, std::nullopt);
|
ReadyQueue.lock()->emplace_back(hash, std::nullopt);
|
||||||
}
|
}
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
@@ -339,7 +328,111 @@ void AssetsManager::readWriteThread(AsyncUseControl::Lock lock) {
|
|||||||
lock->pop();
|
lock->pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: добавить вычистку места при нехватке
|
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) {
|
if(res.size() <= SMALL_RESOURCE) {
|
||||||
Hash_t hash = res.hash();
|
Hash_t hash = res.hash();
|
||||||
@@ -355,6 +448,7 @@ void AssetsManager::readWriteThread(AsyncUseControl::Lock lock) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sqlite3_reset(STMT_INLINE_INSERT);
|
sqlite3_reset(STMT_INLINE_INSERT);
|
||||||
|
DatabaseSize += res.size();
|
||||||
} catch(const std::exception& exc) {
|
} catch(const std::exception& exc) {
|
||||||
LOG.error() << "Произошла ошибка при сохранении " << hashToString(hash);
|
LOG.error() << "Произошла ошибка при сохранении " << hashToString(hash);
|
||||||
throw;
|
throw;
|
||||||
@@ -391,6 +485,7 @@ void AssetsManager::readWriteThread(AsyncUseControl::Lock lock) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sqlite3_reset(STMT_DISK_INSERT);
|
sqlite3_reset(STMT_DISK_INSERT);
|
||||||
|
DatabaseSize += res.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
@@ -404,7 +499,7 @@ void AssetsManager::readWriteThread(AsyncUseControl::Lock lock) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string AssetsManager::hashToString(const Hash_t& hash) {
|
std::string AssetsCacheManager::hashToString(const Hash_t& hash) {
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << std::hex << std::setfill('0');
|
ss << std::hex << std::setfill('0');
|
||||||
for (const auto& byte : hash)
|
for (const auto& byte : hash)
|
||||||
196
Src/Client/AssetsCacheManager.hpp
Normal file
196
Src/Client/AssetsCacheManager.hpp
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Common/Abstract.hpp"
|
||||||
|
#include <cassert>
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
#include <queue>
|
||||||
|
#include <string>
|
||||||
|
#include <sqlite3.h>
|
||||||
|
#include <TOSLib.hpp>
|
||||||
|
#include <TOSAsync.hpp>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <string_view>
|
||||||
|
#include <thread>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
|
||||||
|
namespace LV::Client {
|
||||||
|
|
||||||
|
using namespace TOS;
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
// NOT ThreadSafe
|
||||||
|
class CacheDatabase {
|
||||||
|
const fs::path Path;
|
||||||
|
|
||||||
|
sqlite3 *DB = nullptr;
|
||||||
|
sqlite3_stmt *STMT_INSERT = nullptr,
|
||||||
|
*STMT_UPDATE_TIME = nullptr,
|
||||||
|
*STMT_REMOVE = nullptr,
|
||||||
|
*STMT_ALL_HASH = nullptr,
|
||||||
|
*STMT_SUM = nullptr,
|
||||||
|
*STMT_OLD = nullptr,
|
||||||
|
*STMT_TO_FREE = nullptr,
|
||||||
|
*STMT_COUNT = nullptr;
|
||||||
|
|
||||||
|
public:
|
||||||
|
CacheDatabase(const fs::path &cachePath);
|
||||||
|
~CacheDatabase();
|
||||||
|
|
||||||
|
CacheDatabase(const CacheDatabase&) = delete;
|
||||||
|
CacheDatabase(CacheDatabase&&) = delete;
|
||||||
|
CacheDatabase& operator=(const CacheDatabase&) = delete;
|
||||||
|
CacheDatabase& operator=(CacheDatabase&&) = delete;
|
||||||
|
|
||||||
|
/*
|
||||||
|
Выдаёт размер занимаемый всем хранимым кешем
|
||||||
|
*/
|
||||||
|
size_t getCacheSize();
|
||||||
|
|
||||||
|
// TODO: добавить ограничения на количество файлов
|
||||||
|
|
||||||
|
/*
|
||||||
|
Создаёт линейный массив в котором подряд указаны все хэш суммы в бинарном виде и возвращает их количество
|
||||||
|
*/
|
||||||
|
// std::pair<std::string, size_t> getAllHash();
|
||||||
|
|
||||||
|
/*
|
||||||
|
Обновляет время использования кеша
|
||||||
|
*/
|
||||||
|
void updateTimeFor(Hash_t hash);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Добавляет запись
|
||||||
|
*/
|
||||||
|
void insert(Hash_t hash, size_t size);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Выдаёт хэши на удаление по размеру в сумме больше bytesToFree.
|
||||||
|
Сначала удаляется старьё, потом по приоритету дата использования + размер
|
||||||
|
*/
|
||||||
|
std::vector<Hash_t> findExcessHashes(size_t bytesToFree, int timeBefore);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Удаление записи
|
||||||
|
*/
|
||||||
|
void remove(Hash_t hash);
|
||||||
|
|
||||||
|
static std::string hashToString(Hash_t hash);
|
||||||
|
static int hexCharToInt(char c);
|
||||||
|
static Hash_t stringToHash(const std::string_view view);
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
Менеджер кеша ресурсов по хэшу.
|
||||||
|
Интерфейс однопоточный, обработка файлов в отдельном потоке.
|
||||||
|
*/
|
||||||
|
class AssetsCacheManager : public IAsyncDestructible {
|
||||||
|
public:
|
||||||
|
using Ptr = std::shared_ptr<AssetsCacheManager>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
virtual ~AssetsCacheManager();
|
||||||
|
static std::shared_ptr<AssetsCacheManager> Create(asio::io_context &ioc, const fs::path& cachePath,
|
||||||
|
size_t maxCacheDirectorySize = 8*1024*1024*1024ULL, size_t maxLifeTime = 7*24*60*60) {
|
||||||
|
return createShared(ioc, new AssetsCacheManager(ioc, cachePath, maxCacheDirectorySize, maxLifeTime));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Добавить новый полученный с сервера ресурс
|
||||||
|
void pushResources(std::vector<Resource> resources) {
|
||||||
|
WriteQueue.lock()->push_range(resources);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Добавить задачи на чтение по хэшу
|
||||||
|
void pushReads(std::vector<Hash_t> hashes) {
|
||||||
|
ReadQueue.lock()->push_range(hashes);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Получить считанные данные по хэшу
|
||||||
|
std::vector<std::pair<Hash_t, std::optional<Resource>>> pullReads() {
|
||||||
|
return std::move(*ReadyQueue.lock());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Размер всего хранимого кеша
|
||||||
|
size_t getCacheSize() {
|
||||||
|
return DatabaseSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обновить параметры хранилища кеша
|
||||||
|
void updateParams(size_t maxLifeTime, size_t maxCacheDirectorySize) {
|
||||||
|
auto lock = Changes.lock();
|
||||||
|
lock->MaxLifeTime = maxLifeTime;
|
||||||
|
lock->MaxCacheDatabaseSize = maxCacheDirectorySize;
|
||||||
|
lock->MaxChange = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Запуск процедуры проверки хешей всего хранимого кеша
|
||||||
|
void runFullDatabaseRecheck(std::move_only_function<void(std::string result)>&& func) {
|
||||||
|
auto lock = Changes.lock();
|
||||||
|
lock->OnRecheckEnd = std::move(func);
|
||||||
|
lock->FullRecheck = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hasError() {
|
||||||
|
return IssuedAnError;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Logger LOG = "Client>ResourceHandler";
|
||||||
|
const fs::path
|
||||||
|
CachePath,
|
||||||
|
PathDatabase = CachePath / "db.sqlite3",
|
||||||
|
PathFiles = CachePath / "blobs";
|
||||||
|
static constexpr size_t SMALL_RESOURCE = 1 << 21;
|
||||||
|
|
||||||
|
sqlite3 *DB = nullptr; // База хранения кеша меньше 2мб и информации о кеше на диске
|
||||||
|
sqlite3_stmt
|
||||||
|
*STMT_DISK_INSERT = nullptr, // Вставка записи о хеше
|
||||||
|
*STMT_DISK_UPDATE_TIME = nullptr, // Обновить дату последнего использования
|
||||||
|
*STMT_DISK_REMOVE = nullptr, // Удалить хеш
|
||||||
|
*STMT_DISK_CONTAINS = nullptr, // Проверка наличия хеша
|
||||||
|
*STMT_DISK_SUM = nullptr, // Вычисляет занятое место на диске
|
||||||
|
*STMT_DISK_COUNT = nullptr, // Возвращает количество записей
|
||||||
|
*STMT_DISK_OLDEST = nullptr, // Самые старые записи на диске
|
||||||
|
|
||||||
|
*STMT_INLINE_INSERT = nullptr, // Вставка ресурса
|
||||||
|
*STMT_INLINE_GET = nullptr, // Поиск ресурса по хешу
|
||||||
|
*STMT_INLINE_UPDATE_TIME = nullptr, // Обновить дату последнего использования
|
||||||
|
*STMT_INLINE_SUM = nullptr, // Размер внутреннего хранилища
|
||||||
|
*STMT_INLINE_COUNT = nullptr, // Возвращает количество записей
|
||||||
|
*STMT_INLINE_REMOVE = nullptr, // Удалить ресурс
|
||||||
|
*STMT_INLINE_OLDEST = nullptr; // Самые старые записи в базе
|
||||||
|
|
||||||
|
// Полный размер данных на диске (насколько известно)
|
||||||
|
volatile size_t DatabaseSize = 0;
|
||||||
|
|
||||||
|
// Очередь задач на чтение
|
||||||
|
TOS::SpinlockObject<std::queue<Hash_t>> ReadQueue;
|
||||||
|
// Очередь на запись ресурсов
|
||||||
|
TOS::SpinlockObject<std::queue<Resource>> WriteQueue;
|
||||||
|
// Очередь на выдачу результатов чтения
|
||||||
|
TOS::SpinlockObject<std::vector<std::pair<Hash_t, std::optional<Resource>>>> ReadyQueue;
|
||||||
|
|
||||||
|
struct Changes_t {
|
||||||
|
size_t MaxCacheDatabaseSize, MaxLifeTime;
|
||||||
|
volatile bool MaxChange = false;
|
||||||
|
std::optional<std::move_only_function<void(std::string)>> OnRecheckEnd;
|
||||||
|
volatile bool FullRecheck = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
TOS::SpinlockObject<Changes_t> Changes;
|
||||||
|
|
||||||
|
bool NeedShutdown = false, IssuedAnError = false;
|
||||||
|
std::thread OffThread;
|
||||||
|
|
||||||
|
|
||||||
|
virtual coro<> asyncDestructor();
|
||||||
|
AssetsCacheManager(boost::asio::io_context &ioc, const fs::path &cachePath,
|
||||||
|
size_t maxCacheDatabaseSize, size_t maxLifeTime);
|
||||||
|
|
||||||
|
void readWriteThread(AsyncUseControl::Lock lock);
|
||||||
|
std::string hashToString(const Hash_t& hash);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
484
Src/Client/AssetsHeaderCodec.cpp
Normal file
484
Src/Client/AssetsHeaderCodec.cpp
Normal file
@@ -0,0 +1,484 @@
|
|||||||
|
#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
|
||||||
27
Src/Client/AssetsHeaderCodec.hpp
Normal file
27
Src/Client/AssetsHeaderCodec.hpp
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
#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
|
||||||
767
Src/Client/AssetsManager.cpp_
Normal file
767
Src/Client/AssetsManager.cpp_
Normal file
@@ -0,0 +1,767 @@
|
|||||||
|
#include "AssetsManager.hpp"
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cassert>
|
||||||
|
#include <fstream>
|
||||||
|
#include "Common/TexturePipelineProgram.hpp"
|
||||||
|
|
||||||
|
namespace LV::Client {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
static const char* assetTypeName(EnumAssets type) {
|
||||||
|
switch(type) {
|
||||||
|
case EnumAssets::Nodestate: return "nodestate";
|
||||||
|
case EnumAssets::Model: return "model";
|
||||||
|
case EnumAssets::Texture: return "texture";
|
||||||
|
case EnumAssets::Particle: return "particle";
|
||||||
|
case EnumAssets::Animation: return "animation";
|
||||||
|
case EnumAssets::Sound: return "sound";
|
||||||
|
case EnumAssets::Font: return "font";
|
||||||
|
default: return "unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char* enumAssetsToDirectory(LV::EnumAssets value) {
|
||||||
|
switch(value) {
|
||||||
|
case LV::EnumAssets::Nodestate: return "nodestate";
|
||||||
|
case LV::EnumAssets::Particle: return "particle";
|
||||||
|
case LV::EnumAssets::Animation: return "animation";
|
||||||
|
case LV::EnumAssets::Model: return "model";
|
||||||
|
case LV::EnumAssets::Texture: return "texture";
|
||||||
|
case LV::EnumAssets::Sound: return "sound";
|
||||||
|
case LV::EnumAssets::Font: return "font";
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(!"Unknown asset type");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::u8string readFileBytes(const fs::path& path) {
|
||||||
|
std::ifstream file(path, std::ios::binary);
|
||||||
|
if(!file)
|
||||||
|
throw std::runtime_error("Не удалось открыть файл: " + path.string());
|
||||||
|
|
||||||
|
file.seekg(0, std::ios::end);
|
||||||
|
std::streamoff size = file.tellg();
|
||||||
|
if(size < 0)
|
||||||
|
size = 0;
|
||||||
|
file.seekg(0, std::ios::beg);
|
||||||
|
|
||||||
|
std::u8string data;
|
||||||
|
data.resize(static_cast<size_t>(size));
|
||||||
|
if(size > 0) {
|
||||||
|
file.read(reinterpret_cast<char*>(data.data()), size);
|
||||||
|
if(!file)
|
||||||
|
throw std::runtime_error("Не удалось прочитать файл: " + path.string());
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::u8string readOptionalMeta(const fs::path& path) {
|
||||||
|
fs::path metaPath = path;
|
||||||
|
metaPath += ".meta";
|
||||||
|
if(!fs::exists(metaPath) || !fs::is_regular_file(metaPath))
|
||||||
|
return {};
|
||||||
|
|
||||||
|
return readFileBytes(metaPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
AssetsManager::AssetsManager(asio::io_context& ioc, const fs::path& cachePath,
|
||||||
|
size_t maxCacheDirectorySize, size_t maxLifeTime)
|
||||||
|
: Cache(AssetsCacheManager::Create(ioc, cachePath, maxCacheDirectorySize, maxLifeTime))
|
||||||
|
{
|
||||||
|
for(size_t i = 0; i < static_cast<size_t>(AssetType::MAX_ENUM); ++i)
|
||||||
|
Types[i].NextLocalId = 1;
|
||||||
|
initSources();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AssetsManager::initSources() {
|
||||||
|
using SourceResult = AssetsManager::SourceResult;
|
||||||
|
using SourceStatus = AssetsManager::SourceStatus;
|
||||||
|
using SourceReady = AssetsManager::SourceReady;
|
||||||
|
using ResourceKey = AssetsManager::ResourceKey;
|
||||||
|
using PackResource = AssetsManager::PackResource;
|
||||||
|
|
||||||
|
class PackSource final : public IResourceSource {
|
||||||
|
public:
|
||||||
|
explicit PackSource(AssetsManager* manager) : Manager(manager) {}
|
||||||
|
|
||||||
|
SourceResult tryGet(const ResourceKey& key) override {
|
||||||
|
std::optional<PackResource> pack = Manager->findPackResource(key.Type, key.Domain, key.Key);
|
||||||
|
if(pack && pack->Hash == key.Hash)
|
||||||
|
return {SourceStatus::Hit, pack->Res, 0};
|
||||||
|
return {SourceStatus::Miss, std::nullopt, 0};
|
||||||
|
}
|
||||||
|
|
||||||
|
void collectReady(std::vector<SourceReady>&) override {}
|
||||||
|
|
||||||
|
bool isAsync() const override {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void startPending(std::vector<Hash_t>) override {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
AssetsManager* Manager = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
class MemorySource final : public IResourceSource {
|
||||||
|
public:
|
||||||
|
explicit MemorySource(AssetsManager* manager) : Manager(manager) {}
|
||||||
|
|
||||||
|
SourceResult tryGet(const ResourceKey& key) override {
|
||||||
|
auto iter = Manager->MemoryResourcesByHash.find(key.Hash);
|
||||||
|
if(iter == Manager->MemoryResourcesByHash.end())
|
||||||
|
return {SourceStatus::Miss, std::nullopt, 0};
|
||||||
|
return {SourceStatus::Hit, iter->second, 0};
|
||||||
|
}
|
||||||
|
|
||||||
|
void collectReady(std::vector<SourceReady>&) override {}
|
||||||
|
|
||||||
|
bool isAsync() const override {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void startPending(std::vector<Hash_t>) override {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
AssetsManager* Manager = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CacheSource final : public IResourceSource {
|
||||||
|
public:
|
||||||
|
CacheSource(AssetsManager* manager, size_t sourceIndex)
|
||||||
|
: Manager(manager), SourceIndex(sourceIndex) {}
|
||||||
|
|
||||||
|
SourceResult tryGet(const ResourceKey&) override {
|
||||||
|
return {SourceStatus::Pending, std::nullopt, SourceIndex};
|
||||||
|
}
|
||||||
|
|
||||||
|
void collectReady(std::vector<SourceReady>& out) override {
|
||||||
|
std::vector<std::pair<Hash_t, std::optional<Resource>>> cached = Manager->Cache->pullReads();
|
||||||
|
out.reserve(out.size() + cached.size());
|
||||||
|
for(auto& [hash, res] : cached)
|
||||||
|
out.push_back(SourceReady{hash, res, SourceIndex});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isAsync() const override {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void startPending(std::vector<Hash_t> hashes) override {
|
||||||
|
if(!hashes.empty())
|
||||||
|
Manager->Cache->pushReads(std::move(hashes));
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
AssetsManager* Manager = nullptr;
|
||||||
|
size_t SourceIndex = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
Sources.clear();
|
||||||
|
PackSourceIndex = Sources.size();
|
||||||
|
Sources.push_back(SourceEntry{std::make_unique<PackSource>(this), 0});
|
||||||
|
MemorySourceIndex = Sources.size();
|
||||||
|
Sources.push_back(SourceEntry{std::make_unique<MemorySource>(this), 0});
|
||||||
|
CacheSourceIndex = Sources.size();
|
||||||
|
Sources.push_back(SourceEntry{std::make_unique<CacheSource>(this, CacheSourceIndex), 0});
|
||||||
|
}
|
||||||
|
|
||||||
|
void AssetsManager::collectReadyFromSources() {
|
||||||
|
std::vector<SourceReady> ready;
|
||||||
|
for(auto& entry : Sources)
|
||||||
|
entry.Source->collectReady(ready);
|
||||||
|
|
||||||
|
for(SourceReady& item : ready) {
|
||||||
|
auto iter = PendingReadsByHash.find(item.Hash);
|
||||||
|
if(iter == PendingReadsByHash.end())
|
||||||
|
continue;
|
||||||
|
if(item.Value)
|
||||||
|
registerSourceHit(item.Hash, item.SourceIndex);
|
||||||
|
for(ResourceKey& key : iter->second) {
|
||||||
|
if(item.SourceIndex == CacheSourceIndex) {
|
||||||
|
if(item.Value) {
|
||||||
|
LOG.debug() << "Cache hit type=" << assetTypeName(key.Type)
|
||||||
|
<< " id=" << key.Id
|
||||||
|
<< " key=" << key.Domain << ':' << key.Key
|
||||||
|
<< " hash=" << int(item.Hash[0]) << '.'
|
||||||
|
<< int(item.Hash[1]) << '.'
|
||||||
|
<< int(item.Hash[2]) << '.'
|
||||||
|
<< int(item.Hash[3])
|
||||||
|
<< " size=" << item.Value->size();
|
||||||
|
} else {
|
||||||
|
LOG.debug() << "Cache miss type=" << assetTypeName(key.Type)
|
||||||
|
<< " id=" << key.Id
|
||||||
|
<< " key=" << key.Domain << ':' << key.Key
|
||||||
|
<< " hash=" << int(item.Hash[0]) << '.'
|
||||||
|
<< int(item.Hash[1]) << '.'
|
||||||
|
<< int(item.Hash[2]) << '.'
|
||||||
|
<< int(item.Hash[3]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ReadyReads.emplace_back(std::move(key), item.Value);
|
||||||
|
}
|
||||||
|
PendingReadsByHash.erase(iter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AssetsManager::SourceResult AssetsManager::querySources(const ResourceKey& key) {
|
||||||
|
auto cacheIter = SourceCacheByHash.find(key.Hash);
|
||||||
|
if(cacheIter != SourceCacheByHash.end()) {
|
||||||
|
const size_t cachedIndex = cacheIter->second.SourceIndex;
|
||||||
|
if(cachedIndex < Sources.size()
|
||||||
|
&& cacheIter->second.Generation == Sources[cachedIndex].Generation)
|
||||||
|
{
|
||||||
|
SourceResult cached = Sources[cachedIndex].Source->tryGet(key);
|
||||||
|
cached.SourceIndex = cachedIndex;
|
||||||
|
if(cached.Status != SourceStatus::Miss)
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
SourceCacheByHash.erase(cacheIter);
|
||||||
|
}
|
||||||
|
|
||||||
|
SourceResult pending;
|
||||||
|
pending.Status = SourceStatus::Miss;
|
||||||
|
for(size_t i = 0; i < Sources.size(); ++i) {
|
||||||
|
SourceResult res = Sources[i].Source->tryGet(key);
|
||||||
|
res.SourceIndex = i;
|
||||||
|
if(res.Status == SourceStatus::Hit) {
|
||||||
|
registerSourceHit(key.Hash, i);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
if(res.Status == SourceStatus::Pending && pending.Status == SourceStatus::Miss)
|
||||||
|
pending = res;
|
||||||
|
}
|
||||||
|
|
||||||
|
return pending;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AssetsManager::registerSourceHit(const Hash_t& hash, size_t sourceIndex) {
|
||||||
|
if(sourceIndex >= Sources.size())
|
||||||
|
return;
|
||||||
|
if(Sources[sourceIndex].Source->isAsync())
|
||||||
|
return;
|
||||||
|
SourceCacheByHash[hash] = SourceCacheEntry{
|
||||||
|
.SourceIndex = sourceIndex,
|
||||||
|
.Generation = Sources[sourceIndex].Generation
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void AssetsManager::invalidateSourceCache(size_t sourceIndex) {
|
||||||
|
if(sourceIndex >= Sources.size())
|
||||||
|
return;
|
||||||
|
Sources[sourceIndex].Generation++;
|
||||||
|
for(auto iter = SourceCacheByHash.begin(); iter != SourceCacheByHash.end(); ) {
|
||||||
|
if(iter->second.SourceIndex == sourceIndex)
|
||||||
|
iter = SourceCacheByHash.erase(iter);
|
||||||
|
else
|
||||||
|
++iter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AssetsManager::invalidateAllSourceCache() {
|
||||||
|
for(auto& entry : Sources)
|
||||||
|
entry.Generation++;
|
||||||
|
SourceCacheByHash.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AssetsManager::tickSources() {
|
||||||
|
collectReadyFromSources();
|
||||||
|
}
|
||||||
|
|
||||||
|
AssetsManager::PackReloadResult AssetsManager::reloadPacks(const PackRegister& reg) {
|
||||||
|
PackReloadResult result;
|
||||||
|
std::array<PackTable, static_cast<size_t>(AssetType::MAX_ENUM)> oldPacks;
|
||||||
|
for(size_t type = 0; type < static_cast<size_t>(AssetType::MAX_ENUM); ++type) {
|
||||||
|
oldPacks[type] = Types[type].PackResources;
|
||||||
|
Types[type].PackResources.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
for(const fs::path& instance : reg.Packs) {
|
||||||
|
try {
|
||||||
|
if(fs::is_regular_file(instance)) {
|
||||||
|
LOG.warn() << "Архивы ресурспаков пока не поддерживаются: " << instance.string();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(!fs::is_directory(instance)) {
|
||||||
|
LOG.warn() << "Неизвестный тип ресурспака: " << instance.string();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
fs::path assetsRoot = instance;
|
||||||
|
fs::path assetsCandidate = instance / "assets";
|
||||||
|
if(fs::exists(assetsCandidate) && fs::is_directory(assetsCandidate))
|
||||||
|
assetsRoot = assetsCandidate;
|
||||||
|
|
||||||
|
for(auto begin = fs::directory_iterator(assetsRoot), end = fs::directory_iterator(); begin != end; ++begin) {
|
||||||
|
if(!begin->is_directory())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
fs::path domainPath = begin->path();
|
||||||
|
std::string domain = domainPath.filename().string();
|
||||||
|
|
||||||
|
for(size_t type = 0; type < static_cast<size_t>(AssetType::MAX_ENUM); ++type) {
|
||||||
|
AssetType assetType = static_cast<AssetType>(type);
|
||||||
|
fs::path assetPath = domainPath / enumAssetsToDirectory(assetType);
|
||||||
|
if(!fs::exists(assetPath) || !fs::is_directory(assetPath))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto& typeTable = Types[type].PackResources[domain];
|
||||||
|
for(auto fbegin = fs::recursive_directory_iterator(assetPath),
|
||||||
|
fend = fs::recursive_directory_iterator();
|
||||||
|
fbegin != fend; ++fbegin) {
|
||||||
|
if(fbegin->is_directory())
|
||||||
|
continue;
|
||||||
|
fs::path file = fbegin->path();
|
||||||
|
if(assetType == AssetType::Texture && file.extension() == ".meta")
|
||||||
|
continue;
|
||||||
|
|
||||||
|
std::string key = fs::relative(file, assetPath).generic_string();
|
||||||
|
if(typeTable.contains(key))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
PackResource entry;
|
||||||
|
entry.Type = assetType;
|
||||||
|
entry.Domain = domain;
|
||||||
|
entry.Key = key;
|
||||||
|
entry.LocalId = getOrCreateLocalId(assetType, entry.Domain, entry.Key);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if(assetType == AssetType::Nodestate) {
|
||||||
|
std::u8string data = readFileBytes(file);
|
||||||
|
std::string_view view(reinterpret_cast<const char*>(data.data()), data.size());
|
||||||
|
js::object obj = js::parse(view).as_object();
|
||||||
|
|
||||||
|
HeadlessNodeState hns;
|
||||||
|
auto modelResolver = [&](std::string_view model) -> AssetsModel {
|
||||||
|
auto [mDomain, mKey] = parseDomainKey(model, entry.Domain);
|
||||||
|
return getOrCreateLocalId(AssetType::Model, mDomain, mKey);
|
||||||
|
};
|
||||||
|
|
||||||
|
entry.Header = hns.parse(obj, modelResolver);
|
||||||
|
std::u8string compiled = hns.dump();
|
||||||
|
entry.Res = Resource(std::move(compiled));
|
||||||
|
entry.Hash = entry.Res.hash();
|
||||||
|
} else if(assetType == AssetType::Model) {
|
||||||
|
const std::string ext = file.extension().string();
|
||||||
|
if(ext == ".json") {
|
||||||
|
std::u8string data = readFileBytes(file);
|
||||||
|
std::string_view view(reinterpret_cast<const char*>(data.data()), data.size());
|
||||||
|
js::object obj = js::parse(view).as_object();
|
||||||
|
|
||||||
|
HeadlessModel hm;
|
||||||
|
auto modelResolver = [&](std::string_view model) -> AssetsModel {
|
||||||
|
auto [mDomain, mKey] = parseDomainKey(model, entry.Domain);
|
||||||
|
return getOrCreateLocalId(AssetType::Model, mDomain, mKey);
|
||||||
|
};
|
||||||
|
auto normalizeTexturePipelineSrc = [](std::string_view src) -> std::string {
|
||||||
|
std::string out(src);
|
||||||
|
auto isSpace = [](unsigned char c) { return c == ' ' || c == '\t' || c == '\n' || c == '\r'; };
|
||||||
|
size_t start = 0;
|
||||||
|
while(start < out.size() && isSpace(static_cast<unsigned char>(out[start])))
|
||||||
|
++start;
|
||||||
|
if(out.compare(start, 3, "tex") != 0) {
|
||||||
|
std::string pref = "tex ";
|
||||||
|
pref += out.substr(start);
|
||||||
|
return pref;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto textureResolver = [&](std::string_view textureSrc) -> std::vector<uint8_t> {
|
||||||
|
TexturePipelineProgram tpp;
|
||||||
|
if(!tpp.compile(normalizeTexturePipelineSrc(textureSrc)))
|
||||||
|
return {};
|
||||||
|
auto textureIdResolver = [&](std::string_view name) -> std::optional<uint32_t> {
|
||||||
|
auto [tDomain, tKey] = parseDomainKey(name, entry.Domain);
|
||||||
|
return getOrCreateLocalId(AssetType::Texture, tDomain, tKey);
|
||||||
|
};
|
||||||
|
if(!tpp.link(textureIdResolver))
|
||||||
|
return {};
|
||||||
|
return tpp.toBytes();
|
||||||
|
};
|
||||||
|
|
||||||
|
entry.Header = hm.parse(obj, modelResolver, textureResolver);
|
||||||
|
std::u8string compiled = hm.dump();
|
||||||
|
entry.Res = Resource(std::move(compiled));
|
||||||
|
entry.Hash = entry.Res.hash();
|
||||||
|
} else {
|
||||||
|
LOG.warn() << "Не поддерживаемый формат модели: " << file.string();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else if(assetType == AssetType::Texture) {
|
||||||
|
std::u8string data = readFileBytes(file);
|
||||||
|
entry.Res = Resource(std::move(data));
|
||||||
|
entry.Hash = entry.Res.hash();
|
||||||
|
entry.Header = readOptionalMeta(file);
|
||||||
|
} else {
|
||||||
|
std::u8string data = readFileBytes(file);
|
||||||
|
entry.Res = Resource(std::move(data));
|
||||||
|
entry.Hash = entry.Res.hash();
|
||||||
|
}
|
||||||
|
} catch(const std::exception& exc) {
|
||||||
|
LOG.warn() << "Ошибка загрузки ресурса " << file.string() << ": " << exc.what();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
typeTable.emplace(entry.Key, entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch(const std::exception& exc) {
|
||||||
|
LOG.warn() << "Ошибка загрузки ресурспака " << instance.string() << ": " << exc.what();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(size_t type = 0; type < static_cast<size_t>(AssetType::MAX_ENUM); ++type) {
|
||||||
|
for(const auto& [domain, keyTable] : Types[type].PackResources) {
|
||||||
|
for(const auto& [key, res] : keyTable) {
|
||||||
|
bool changed = true;
|
||||||
|
auto oldDomain = oldPacks[type].find(domain);
|
||||||
|
if(oldDomain != oldPacks[type].end()) {
|
||||||
|
auto oldKey = oldDomain->second.find(key);
|
||||||
|
if(oldKey != oldDomain->second.end()) {
|
||||||
|
changed = oldKey->second.Hash != res.Hash;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(changed)
|
||||||
|
result.ChangeOrAdd[type].push_back(res.LocalId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(const auto& [domain, keyTable] : oldPacks[type]) {
|
||||||
|
for(const auto& [key, res] : keyTable) {
|
||||||
|
auto newDomain = Types[type].PackResources.find(domain);
|
||||||
|
bool lost = true;
|
||||||
|
if(newDomain != Types[type].PackResources.end()) {
|
||||||
|
if(newDomain->second.contains(key))
|
||||||
|
lost = false;
|
||||||
|
}
|
||||||
|
if(lost)
|
||||||
|
result.Lost[type].push_back(res.LocalId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
invalidateAllSourceCache();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
AssetsManager::BindResult AssetsManager::bindServerResource(AssetType type, AssetId serverId,
|
||||||
|
std::string domain, std::string key, const Hash_t& hash, std::vector<uint8_t> header)
|
||||||
|
{
|
||||||
|
BindResult result;
|
||||||
|
AssetId localFromDK = getOrCreateLocalId(type, domain, key);
|
||||||
|
auto& map = Types[static_cast<size_t>(type)].ServerToLocal;
|
||||||
|
AssetId localFromServer = 0;
|
||||||
|
if(serverId < map.size())
|
||||||
|
localFromServer = map[serverId];
|
||||||
|
|
||||||
|
if(localFromServer != 0)
|
||||||
|
unionLocalIds(type, localFromServer, localFromDK, &result.ReboundFrom);
|
||||||
|
AssetId localId = resolveLocalIdMutable(type, localFromDK);
|
||||||
|
|
||||||
|
if(serverId >= map.size())
|
||||||
|
map.resize(serverId + 1, 0);
|
||||||
|
map[serverId] = localId;
|
||||||
|
|
||||||
|
auto& infoList = Types[static_cast<size_t>(type)].BindInfos;
|
||||||
|
if(localId >= infoList.size())
|
||||||
|
infoList.resize(localId + 1);
|
||||||
|
|
||||||
|
bool hadBinding = infoList[localId].has_value();
|
||||||
|
bool changed = !hadBinding || infoList[localId]->Hash != hash || infoList[localId]->Header != header;
|
||||||
|
|
||||||
|
infoList[localId] = BindInfo{
|
||||||
|
.Type = type,
|
||||||
|
.LocalId = localId,
|
||||||
|
.Domain = std::move(domain),
|
||||||
|
.Key = std::move(key),
|
||||||
|
.Hash = hash,
|
||||||
|
.Header = std::move(header)
|
||||||
|
};
|
||||||
|
|
||||||
|
result.LocalId = localId;
|
||||||
|
result.Changed = changed;
|
||||||
|
result.NewBinding = !hadBinding;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<AssetsManager::AssetId> AssetsManager::unbindServerResource(AssetType type, AssetId serverId) {
|
||||||
|
auto& map = Types[static_cast<size_t>(type)].ServerToLocal;
|
||||||
|
if(serverId >= map.size())
|
||||||
|
return std::nullopt;
|
||||||
|
AssetId localId = map[serverId];
|
||||||
|
map[serverId] = 0;
|
||||||
|
if(localId == 0)
|
||||||
|
return std::nullopt;
|
||||||
|
return resolveLocalIdMutable(type, localId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AssetsManager::clearServerBindings() {
|
||||||
|
for(auto& typeData : Types) {
|
||||||
|
typeData.ServerToLocal.clear();
|
||||||
|
typeData.BindInfos.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const AssetsManager::BindInfo* AssetsManager::getBind(AssetType type, AssetId localId) const {
|
||||||
|
localId = resolveLocalId(type, localId);
|
||||||
|
const auto& table = Types[static_cast<size_t>(type)].BindInfos;
|
||||||
|
if(localId >= table.size())
|
||||||
|
return nullptr;
|
||||||
|
if(!table[localId])
|
||||||
|
return nullptr;
|
||||||
|
return &*table[localId];
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> AssetsManager::rebindHeader(AssetType type, const std::vector<uint8_t>& header, bool serverIds) {
|
||||||
|
auto mapModelId = [&](AssetId id) -> AssetId {
|
||||||
|
if(serverIds) {
|
||||||
|
auto localId = getLocalIdFromServer(AssetType::Model, id);
|
||||||
|
if(!localId) {
|
||||||
|
assert(!"Missing server bind for model id");
|
||||||
|
MAKE_ERROR("Нет бинда сервера для модели id=" << id);
|
||||||
|
}
|
||||||
|
return *localId;
|
||||||
|
}
|
||||||
|
return resolveLocalIdMutable(AssetType::Model, id);
|
||||||
|
};
|
||||||
|
|
||||||
|
auto mapTextureId = [&](AssetId id) -> AssetId {
|
||||||
|
if(serverIds) {
|
||||||
|
auto localId = getLocalIdFromServer(AssetType::Texture, id);
|
||||||
|
if(!localId) {
|
||||||
|
assert(!"Missing server bind for texture id");
|
||||||
|
MAKE_ERROR("Нет бинда сервера для текстуры id=" << id);
|
||||||
|
}
|
||||||
|
return *localId;
|
||||||
|
}
|
||||||
|
return resolveLocalIdMutable(AssetType::Texture, id);
|
||||||
|
};
|
||||||
|
|
||||||
|
auto warn = [&](const std::string& msg) {
|
||||||
|
LOG.warn() << msg;
|
||||||
|
};
|
||||||
|
|
||||||
|
return AssetsHeaderCodec::rebindHeader(type, header, mapModelId, mapTextureId, warn);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<AssetsManager::ParsedHeader> AssetsManager::parseHeader(AssetType type, const std::vector<uint8_t>& header) {
|
||||||
|
return AssetsHeaderCodec::parseHeader(type, header);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AssetsManager::pushResources(std::vector<Resource> resources) {
|
||||||
|
for(const Resource& res : resources) {
|
||||||
|
Hash_t hash = res.hash();
|
||||||
|
MemoryResourcesByHash[hash] = res;
|
||||||
|
SourceCacheByHash.erase(hash);
|
||||||
|
registerSourceHit(hash, MemorySourceIndex);
|
||||||
|
|
||||||
|
auto iter = PendingReadsByHash.find(hash);
|
||||||
|
if(iter != PendingReadsByHash.end()) {
|
||||||
|
for(ResourceKey& key : iter->second)
|
||||||
|
ReadyReads.emplace_back(std::move(key), res);
|
||||||
|
PendingReadsByHash.erase(iter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Cache->pushResources(std::move(resources));
|
||||||
|
}
|
||||||
|
|
||||||
|
void AssetsManager::pushReads(std::vector<ResourceKey> reads) {
|
||||||
|
std::unordered_map<size_t, std::vector<Hash_t>> pendingBySource;
|
||||||
|
|
||||||
|
for(ResourceKey& key : reads) {
|
||||||
|
SourceResult res = querySources(key);
|
||||||
|
if(res.Status == SourceStatus::Hit) {
|
||||||
|
if(res.SourceIndex == PackSourceIndex && res.Value) {
|
||||||
|
LOG.debug() << "Pack hit type=" << assetTypeName(key.Type)
|
||||||
|
<< " id=" << key.Id
|
||||||
|
<< " key=" << key.Domain << ':' << key.Key
|
||||||
|
<< " hash=" << int(key.Hash[0]) << '.'
|
||||||
|
<< int(key.Hash[1]) << '.'
|
||||||
|
<< int(key.Hash[2]) << '.'
|
||||||
|
<< int(key.Hash[3])
|
||||||
|
<< " size=" << res.Value->size();
|
||||||
|
}
|
||||||
|
ReadyReads.emplace_back(std::move(key), res.Value);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(res.Status == SourceStatus::Pending) {
|
||||||
|
auto& list = PendingReadsByHash[key.Hash];
|
||||||
|
bool isFirst = list.empty();
|
||||||
|
list.push_back(std::move(key));
|
||||||
|
if(isFirst)
|
||||||
|
pendingBySource[res.SourceIndex].push_back(list.front().Hash);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ReadyReads.emplace_back(std::move(key), std::nullopt);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(auto& [sourceIndex, hashes] : pendingBySource) {
|
||||||
|
if(sourceIndex < Sources.size())
|
||||||
|
Sources[sourceIndex].Source->startPending(std::move(hashes));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::pair<AssetsManager::ResourceKey, std::optional<Resource>>> AssetsManager::pullReads() {
|
||||||
|
tickSources();
|
||||||
|
|
||||||
|
std::vector<std::pair<ResourceKey, std::optional<Resource>>> out;
|
||||||
|
out.reserve(ReadyReads.size());
|
||||||
|
|
||||||
|
for(auto& entry : ReadyReads)
|
||||||
|
out.emplace_back(std::move(entry));
|
||||||
|
ReadyReads.clear();
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
AssetsManager::AssetId AssetsManager::getOrCreateLocalId(AssetType type, std::string_view domain, std::string_view key) {
|
||||||
|
auto& table = Types[static_cast<size_t>(type)].DKToLocal;
|
||||||
|
auto iterDomain = table.find(domain);
|
||||||
|
if(iterDomain == table.end()) {
|
||||||
|
iterDomain = table.emplace(
|
||||||
|
std::string(domain),
|
||||||
|
std::unordered_map<std::string, AssetId, detail::TSVHash, detail::TSVEq>{}
|
||||||
|
).first;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& keyTable = iterDomain->second;
|
||||||
|
auto iterKey = keyTable.find(key);
|
||||||
|
if(iterKey != keyTable.end()) {
|
||||||
|
iterKey->second = resolveLocalIdMutable(type, iterKey->second);
|
||||||
|
return iterKey->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
AssetId id = allocateLocalId(type);
|
||||||
|
keyTable.emplace(std::string(key), id);
|
||||||
|
|
||||||
|
auto& dk = Types[static_cast<size_t>(type)].LocalToDK;
|
||||||
|
if(id >= dk.size())
|
||||||
|
dk.resize(id + 1);
|
||||||
|
dk[id] = DomainKey{std::string(domain), std::string(key), true};
|
||||||
|
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<AssetsManager::AssetId> AssetsManager::getLocalIdFromServer(AssetType type, AssetId serverId) const {
|
||||||
|
const auto& map = Types[static_cast<size_t>(type)].ServerToLocal;
|
||||||
|
if(serverId >= map.size())
|
||||||
|
return std::nullopt;
|
||||||
|
AssetId local = map[serverId];
|
||||||
|
if(local == 0)
|
||||||
|
return std::nullopt;
|
||||||
|
return resolveLocalId(type, local);
|
||||||
|
}
|
||||||
|
|
||||||
|
AssetsManager::AssetId AssetsManager::resolveLocalId(AssetType type, AssetId localId) const {
|
||||||
|
if(localId == 0)
|
||||||
|
return 0;
|
||||||
|
const auto& parents = Types[static_cast<size_t>(type)].LocalParent;
|
||||||
|
if(localId >= parents.size())
|
||||||
|
return localId;
|
||||||
|
AssetId cur = localId;
|
||||||
|
while(cur < parents.size() && parents[cur] != cur && parents[cur] != 0)
|
||||||
|
cur = parents[cur];
|
||||||
|
return cur;
|
||||||
|
}
|
||||||
|
|
||||||
|
AssetsManager::AssetId AssetsManager::allocateLocalId(AssetType type) {
|
||||||
|
auto& next = Types[static_cast<size_t>(type)].NextLocalId;
|
||||||
|
AssetId id = next++;
|
||||||
|
|
||||||
|
auto& parents = Types[static_cast<size_t>(type)].LocalParent;
|
||||||
|
if(id >= parents.size())
|
||||||
|
parents.resize(id + 1, 0);
|
||||||
|
parents[id] = id;
|
||||||
|
|
||||||
|
auto& dk = Types[static_cast<size_t>(type)].LocalToDK;
|
||||||
|
if(id >= dk.size())
|
||||||
|
dk.resize(id + 1);
|
||||||
|
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
AssetsManager::AssetId AssetsManager::resolveLocalIdMutable(AssetType type, AssetId localId) {
|
||||||
|
if(localId == 0)
|
||||||
|
return 0;
|
||||||
|
auto& parents = Types[static_cast<size_t>(type)].LocalParent;
|
||||||
|
if(localId >= parents.size())
|
||||||
|
return localId;
|
||||||
|
AssetId root = localId;
|
||||||
|
while(root < parents.size() && parents[root] != root && parents[root] != 0)
|
||||||
|
root = parents[root];
|
||||||
|
if(root == localId)
|
||||||
|
return root;
|
||||||
|
AssetId cur = localId;
|
||||||
|
while(cur < parents.size() && parents[cur] != root && parents[cur] != 0) {
|
||||||
|
AssetId next = parents[cur];
|
||||||
|
parents[cur] = root;
|
||||||
|
cur = next;
|
||||||
|
}
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AssetsManager::unionLocalIds(AssetType type, AssetId fromId, AssetId toId, std::optional<AssetId>* reboundFrom) {
|
||||||
|
AssetId fromRoot = resolveLocalIdMutable(type, fromId);
|
||||||
|
AssetId toRoot = resolveLocalIdMutable(type, toId);
|
||||||
|
if(fromRoot == 0 || toRoot == 0 || fromRoot == toRoot)
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto& parents = Types[static_cast<size_t>(type)].LocalParent;
|
||||||
|
if(fromRoot >= parents.size() || toRoot >= parents.size())
|
||||||
|
return;
|
||||||
|
|
||||||
|
parents[fromRoot] = toRoot;
|
||||||
|
if(reboundFrom)
|
||||||
|
*reboundFrom = fromRoot;
|
||||||
|
|
||||||
|
auto& dk = Types[static_cast<size_t>(type)].LocalToDK;
|
||||||
|
if(fromRoot < dk.size()) {
|
||||||
|
const DomainKey& fromDK = dk[fromRoot];
|
||||||
|
if(fromDK.Known) {
|
||||||
|
if(toRoot >= dk.size())
|
||||||
|
dk.resize(toRoot + 1);
|
||||||
|
DomainKey& toDK = dk[toRoot];
|
||||||
|
if(!toDK.Known) {
|
||||||
|
toDK = fromDK;
|
||||||
|
Types[static_cast<size_t>(type)].DKToLocal[toDK.Domain][toDK.Key] = toRoot;
|
||||||
|
} else if(toDK.Domain != fromDK.Domain || toDK.Key != fromDK.Key) {
|
||||||
|
LOG.warn() << "Конфликт домен/ключ при ребинде: "
|
||||||
|
<< fromDK.Domain << ':' << fromDK.Key << " vs "
|
||||||
|
<< toDK.Domain << ':' << toDK.Key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& binds = Types[static_cast<size_t>(type)].BindInfos;
|
||||||
|
if(fromRoot < binds.size()) {
|
||||||
|
if(toRoot >= binds.size())
|
||||||
|
binds.resize(toRoot + 1);
|
||||||
|
if(!binds[toRoot] && binds[fromRoot])
|
||||||
|
binds[toRoot] = std::move(binds[fromRoot]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<AssetsManager::PackResource> AssetsManager::findPackResource(AssetType type,
|
||||||
|
std::string_view domain, std::string_view key) const
|
||||||
|
{
|
||||||
|
const auto& typeTable = Types[static_cast<size_t>(type)].PackResources;
|
||||||
|
auto iterDomain = typeTable.find(domain);
|
||||||
|
if(iterDomain == typeTable.end())
|
||||||
|
return std::nullopt;
|
||||||
|
auto iterKey = iterDomain->second.find(key);
|
||||||
|
if(iterKey == iterDomain->second.end())
|
||||||
|
return std::nullopt;
|
||||||
|
return iterKey->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace LV::Client
|
||||||
@@ -1,210 +1,638 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Common/Abstract.hpp"
|
#include <algorithm>
|
||||||
#include <cassert>
|
#include <array>
|
||||||
#include <functional>
|
#include <cstddef>
|
||||||
#include <memory>
|
#include <cstdint>
|
||||||
#include <queue>
|
|
||||||
#include <string>
|
|
||||||
#include <sqlite3.h>
|
|
||||||
#include <TOSLib.hpp>
|
|
||||||
#include <TOSAsync.hpp>
|
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <thread>
|
#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 LV::Client {
|
||||||
|
|
||||||
using namespace TOS;
|
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
// NOT ThreadSafe
|
class AssetsManager : public IdProvider<EnumAssets> {
|
||||||
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:
|
public:
|
||||||
CacheDatabase(const fs::path &cachePath);
|
struct ResourceUpdates {
|
||||||
~CacheDatabase();
|
|
||||||
|
|
||||||
CacheDatabase(const CacheDatabase&) = delete;
|
std::vector<AssetsModelUpdate> Models;
|
||||||
CacheDatabase(CacheDatabase&&) = delete;
|
std::vector<AssetsNodestateUpdate> Nodestates;
|
||||||
CacheDatabase& operator=(const CacheDatabase&) = delete;
|
std::vector<AssetsTextureUpdate> Textures;
|
||||||
CacheDatabase& operator=(CacheDatabase&&) = delete;
|
std::vector<AssetsBinaryUpdate> Particles;
|
||||||
|
std::vector<AssetsBinaryUpdate> Animations;
|
||||||
/*
|
std::vector<AssetsBinaryUpdate> Sounds;
|
||||||
Выдаёт размер занимаемый всем хранимым кешем
|
std::vector<AssetsBinaryUpdate> Fonts;
|
||||||
*/
|
|
||||||
size_t getCacheSize();
|
|
||||||
|
|
||||||
// TODO: добавить ограничения на количество файлов
|
|
||||||
|
|
||||||
/*
|
|
||||||
Создаёт линейный массив в котором подряд указаны все хэш суммы в бинарном виде и возвращает их количество
|
|
||||||
*/
|
|
||||||
// std::pair<std::string, size_t> getAllHash();
|
|
||||||
|
|
||||||
/*
|
|
||||||
Обновляет время использования кеша
|
|
||||||
*/
|
|
||||||
void updateTimeFor(Hash_t hash);
|
|
||||||
|
|
||||||
/*
|
|
||||||
Добавляет запись
|
|
||||||
*/
|
|
||||||
void insert(Hash_t hash, size_t size);
|
|
||||||
|
|
||||||
/*
|
|
||||||
Выдаёт хэши на удаление по размеру в сумме больше bytesToFree.
|
|
||||||
Сначала удаляется старьё, потом по приоритету дата использования + размер
|
|
||||||
*/
|
|
||||||
std::vector<Hash_t> findExcessHashes(size_t bytesToFree, int timeBefore);
|
|
||||||
|
|
||||||
/*
|
|
||||||
Удаление записи
|
|
||||||
*/
|
|
||||||
void remove(Hash_t hash);
|
|
||||||
|
|
||||||
static std::string hashToString(Hash_t hash);
|
|
||||||
static int hexCharToInt(char c);
|
|
||||||
static Hash_t stringToHash(const std::string_view view);
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
Менеджер предоставления ресурсов. Управляет ресурс паками
|
|
||||||
и хранением кешированных ресурсов с сервера.
|
|
||||||
Интерфейс однопоточный.
|
|
||||||
|
|
||||||
Обработка файлов в отдельном потоке.
|
|
||||||
*/
|
|
||||||
class AssetsManager : public IAsyncDestructible {
|
|
||||||
public:
|
|
||||||
using Ptr = std::shared_ptr<AssetsManager>;
|
|
||||||
|
|
||||||
struct ResourceKey {
|
|
||||||
Hash_t Hash;
|
|
||||||
EnumAssets Type;
|
|
||||||
std::string Domain, Key;
|
|
||||||
ResourceId Id;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
virtual ~AssetsManager();
|
AssetsManager(asio::io_context& ioc, fs::path cachePath)
|
||||||
static std::shared_ptr<AssetsManager> Create(asio::io_context &ioc, const fs::path& cachePath,
|
: Cache(AssetsCacheManager::Create(ioc, cachePath))
|
||||||
size_t maxCacheDirectorySize = 8*1024*1024*1024ULL, size_t maxLifeTime = 7*24*60*60) {
|
{
|
||||||
return createShared(ioc, new AssetsManager(ioc, cachePath, maxCacheDirectorySize, maxLifeTime));
|
for(size_t type = 0; type < static_cast<size_t>(EnumAssets::MAX_ENUM); type++) {
|
||||||
|
ServerToClientMap[type].push_back(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Добавить новый полученный с сервера ресурс
|
// Ручные обновления
|
||||||
void pushResources(std::vector<Resource> resources) {
|
struct Out_checkAndPrepareResourcesUpdate {
|
||||||
WriteQueue.lock()->push_range(resources);
|
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 {
|
||||||
void pushReads(std::vector<ResourceKey> keys) {
|
|
||||||
ReadQueue.lock()->push_range(keys);
|
};
|
||||||
|
|
||||||
|
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
|
||||||
std::vector<std::pair<ResourceKey, std::optional<Resource>>> pullReads() {
|
// Новые привязки ассетов к Домен+Ключ.
|
||||||
return std::move(*ReadyQueue.lock());
|
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.
|
||||||
size_t getCacheSize() {
|
void pushAssetsBindHH(
|
||||||
return DatabaseSize;
|
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 updateParams(size_t maxLifeTime, size_t maxCacheDirectorySize) {
|
void pushNewResources(
|
||||||
auto lock = Changes.lock();
|
std::vector<std::tuple<ResourceFile::Hash_t, std::u8string>> &&resources
|
||||||
lock->MaxLifeTime = maxLifeTime;
|
) {
|
||||||
lock->MaxCacheDatabaseSize = maxCacheDirectorySize;
|
std::unordered_map<ResourceFile::Hash_t, std::u8string> files;
|
||||||
lock->MaxChange = true;
|
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));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Установка путей до папок assets
|
// Для запроса отсутствующих ресурсов с сервера на клиент.
|
||||||
void setResourcePacks(std::vector<fs::path> packsAssets) {
|
std::vector<ResourceFile::Hash_t> pullNeededResources() {
|
||||||
auto lock = Changes.lock();
|
return std::move(NeedToRequestFromServer);
|
||||||
lock->Assets = std::move(packsAssets);
|
}
|
||||||
lock->AssetsChange = true;
|
|
||||||
|
// Получить изменённые ресурсы (для передачи другим модулям).
|
||||||
|
ResourceUpdates pullResourceUpdates() {
|
||||||
|
return std::move(RU);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Запуск процедуры проверки хешей всего хранимого кеша
|
ResourceId reBind(EnumAssets type, ResourceId server) {
|
||||||
void runFullDatabaseRecheck(std::move_only_function<void(std::string result)>&& func) {
|
return ServerToClientMap[static_cast<size_t>(type)].at(server);
|
||||||
auto lock = Changes.lock();
|
|
||||||
lock->OnRecheckEnd = std::move(func);
|
|
||||||
lock->FullRecheck = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool hasError() {
|
void tick() {
|
||||||
return IssuedAnError;
|
// Проверим кеш
|
||||||
|
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:
|
private:
|
||||||
Logger LOG = "Client>ResourceHandler";
|
Logger LOG = "Client>AssetsManager";
|
||||||
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
|
AssetsPreloader
|
||||||
*STMT_DISK_INSERT = nullptr, // Вставка записи о хеше
|
// В приоритете ищутся ресурсы из ресурспаков по Domain+Key.
|
||||||
*STMT_DISK_UPDATE_TIME = nullptr, // Обновить дату последнего использования
|
ResourcePacks,
|
||||||
*STMT_DISK_REMOVE = nullptr, // Удалить хеш
|
/*
|
||||||
*STMT_DISK_CONTAINS = nullptr, // Проверка наличия хеша
|
Дополнительные источники ресурсов.
|
||||||
*STMT_DISK_SUM = nullptr, // Вычисляет занятое место на диске
|
Используется для поиска ресурса по хешу от сервера (может стоит тот же мод с совпадающими ресурсами),
|
||||||
*STMT_DISK_COUNT = nullptr, // Возвращает количество записей
|
или для временной подгрузки ресурса по Domain+Key пока ресурс не был получен с сервера.
|
||||||
|
*/
|
||||||
*STMT_INLINE_INSERT = nullptr, // Вставка ресурса
|
ExtraSource;
|
||||||
*STMT_INLINE_GET = nullptr, // Поиск ресурса по хешу
|
|
||||||
*STMT_INLINE_UPDATE_TIME = nullptr, // Обновить дату последнего использования
|
|
||||||
*STMT_INLINE_SUM = nullptr, // Размер внутреннего хранилища
|
|
||||||
*STMT_INLINE_COUNT = nullptr; // Возвращает количество записей
|
|
||||||
|
|
||||||
// Полный размер данных на диске (насколько известно)
|
// Менеджер файлового кэша.
|
||||||
volatile size_t DatabaseSize = 0;
|
AssetsCacheManager::Ptr Cache;
|
||||||
|
|
||||||
// Очередь задач на чтение
|
// Указатели на доступные ресурсы
|
||||||
TOS::SpinlockObject<std::queue<ResourceKey>> ReadQueue;
|
std::unordered_map<ResourceFile::Hash_t, std::vector<fs::path>> HashToPath;
|
||||||
// Очередь на запись ресурсов
|
|
||||||
TOS::SpinlockObject<std::queue<Resource>> WriteQueue;
|
|
||||||
// Очередь на выдачу результатов чтения
|
|
||||||
TOS::SpinlockObject<std::vector<std::pair<ResourceKey, std::optional<Resource>>>> ReadyQueue;
|
|
||||||
|
|
||||||
struct Changes_t {
|
// Таблица релинковки ассетов с идентификаторов сервера на клиентские.
|
||||||
std::vector<fs::path> Assets;
|
std::array<
|
||||||
volatile bool AssetsChange = false;
|
std::vector<ResourceId>,
|
||||||
size_t MaxCacheDatabaseSize, MaxLifeTime;
|
static_cast<size_t>(EnumAssets::MAX_ENUM)
|
||||||
volatile bool MaxChange = false;
|
> ServerToClientMap;
|
||||||
std::optional<std::move_only_function<void(std::string)>> OnRecheckEnd;
|
|
||||||
volatile bool FullRecheck = false;
|
// Таблица серверных привязок HH (id клиентские)
|
||||||
};
|
std::array<
|
||||||
|
std::vector<std::tuple<ResourceFile::Hash_t, ResourceHeader>>,
|
||||||
|
static_cast<size_t>(EnumAssets::MAX_ENUM)
|
||||||
|
> ServerIdToHH;
|
||||||
|
|
||||||
TOS::SpinlockObject<Changes_t> Changes;
|
// Ресурсы в ожидании данных по хешу для обновления (с диска, кеша, сервера).
|
||||||
|
std::array<
|
||||||
|
std::vector<ResourceId>,
|
||||||
|
static_cast<size_t>(EnumAssets::MAX_ENUM)
|
||||||
|
> PendingUpdateFromAsync;
|
||||||
|
|
||||||
bool NeedShutdown = false, IssuedAnError = false;
|
// Хеши, для которых где-то висит задача на загрузку.
|
||||||
std::thread OffThread;
|
std::unordered_set<ResourceFile::Hash_t> WaitingHashes;
|
||||||
|
|
||||||
|
// Хеши, которые необходимо запросить с сервера.
|
||||||
|
std::vector<ResourceFile::Hash_t> NeedToRequestFromServer;
|
||||||
|
|
||||||
virtual coro<> asyncDestructor();
|
// Ресурсы, которые нужно считать с диска
|
||||||
AssetsManager(boost::asio::io_context &ioc, const fs::path &cachePath,
|
std::vector<std::tuple<ResourceFile::Hash_t, fs::path>> NeedToReadFromDisk;
|
||||||
size_t maxCacheDatabaseSize, size_t maxLifeTime);
|
|
||||||
|
|
||||||
void readWriteThread(AsyncUseControl::Lock lock);
|
// Обновлённые ресурсы
|
||||||
std::string hashToString(const Hash_t& hash);
|
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
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -36,11 +36,16 @@ public:
|
|||||||
static coro<std::unique_ptr<Net::AsyncSocket>> asyncInitGameProtocol(asio::io_context &ioc, tcp::socket &&socket, 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 shutdown(EnumDisconnect type);
|
||||||
|
void requestModsReload();
|
||||||
|
|
||||||
bool isConnected() {
|
bool isConnected() {
|
||||||
return Socket->isAlive() && IsConnected;
|
return Socket->isAlive() && IsConnected;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint64_t getVisibleCompressedChunksBytes() const {
|
||||||
|
return VisibleChunkCompressedBytes;
|
||||||
|
}
|
||||||
|
|
||||||
// ISurfaceEventListener
|
// ISurfaceEventListener
|
||||||
|
|
||||||
virtual void onResize(uint32_t width, uint32_t height) override;
|
virtual void onResize(uint32_t width, uint32_t height) override;
|
||||||
@@ -64,22 +69,26 @@ private:
|
|||||||
IRenderSession *RS = nullptr;
|
IRenderSession *RS = nullptr;
|
||||||
|
|
||||||
// Обработчик кеша ресурсов сервера
|
// Обработчик кеша ресурсов сервера
|
||||||
AssetsManager::Ptr AM;
|
AssetsManager AM;
|
||||||
|
|
||||||
static constexpr uint64_t TIME_BEFORE_UNLOAD_RESOURCE = 180;
|
static constexpr uint64_t TIME_BEFORE_UNLOAD_RESOURCE = 180;
|
||||||
struct {
|
struct {
|
||||||
// Существующие привязки ресурсов
|
// Существующие привязки ресурсов
|
||||||
std::unordered_set<ResourceId> ExistBinds[(int) EnumAssets::MAX_ENUM];
|
// std::unordered_set<ResourceId> ExistBinds[(int) EnumAssets::MAX_ENUM];
|
||||||
// Недавно использованные ресурсы, пока хранятся здесь в течении TIME_BEFORE_UNLOAD_RESOURCE секунд
|
// Недавно использованные ресурсы, пока хранятся здесь в течении TIME_BEFORE_UNLOAD_RESOURCE секунд
|
||||||
std::unordered_map<std::string, std::pair<AssetEntry, uint64_t>> NotInUse[(int) EnumAssets::MAX_ENUM];
|
// std::unordered_map<std::string, std::pair<AssetEntry, uint64_t>> NotInUse[(int) EnumAssets::MAX_ENUM];
|
||||||
} MyAssets;
|
} MyAssets;
|
||||||
|
|
||||||
struct AssetLoading {
|
struct AssetLoadingEntry {
|
||||||
EnumAssets Type;
|
EnumAssets Type;
|
||||||
ResourceId Id;
|
ResourceId Id;
|
||||||
std::string Domain, Key;
|
std::string Domain;
|
||||||
|
std::string Key;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AssetLoading {
|
||||||
std::u8string Data;
|
std::u8string Data;
|
||||||
size_t Offset;
|
size_t Offset = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct AssetBindEntry {
|
struct AssetBindEntry {
|
||||||
@@ -87,20 +96,44 @@ private:
|
|||||||
ResourceId Id;
|
ResourceId Id;
|
||||||
std::string Domain, Key;
|
std::string Domain, Key;
|
||||||
Hash_t Hash;
|
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 {
|
struct TickData {
|
||||||
std::vector<std::pair<DefVoxelId, void*>> Profile_Voxel_AddOrChange;
|
// Полученные изменения привязок 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<DefVoxelId> Profile_Voxel_Lost;
|
||||||
std::vector<std::pair<DefNodeId, DefNode_t>> Profile_Node_AddOrChange;
|
std::vector<std::pair<DefNodeId, DefNode>> Profile_Node_AddOrChange;
|
||||||
std::vector<DefNodeId> Profile_Node_Lost;
|
std::vector<DefNodeId> Profile_Node_Lost;
|
||||||
std::vector<std::pair<DefWorldId, void*>> Profile_World_AddOrChange;
|
std::vector<std::pair<DefWorldId, DefWorld>> Profile_World_AddOrChange;
|
||||||
std::vector<DefWorldId> Profile_World_Lost;
|
std::vector<DefWorldId> Profile_World_Lost;
|
||||||
std::vector<std::pair<DefPortalId, void*>> Profile_Portal_AddOrChange;
|
std::vector<std::pair<DefPortalId, DefPortal>> Profile_Portal_AddOrChange;
|
||||||
std::vector<DefPortalId> Profile_Portal_Lost;
|
std::vector<DefPortalId> Profile_Portal_Lost;
|
||||||
std::vector<std::pair<DefEntityId, void*>> Profile_Entity_AddOrChange;
|
std::vector<std::pair<DefEntityId, DefEntity>> Profile_Entity_AddOrChange;
|
||||||
std::vector<DefEntityId> Profile_Entity_Lost;
|
std::vector<DefEntityId> Profile_Entity_Lost;
|
||||||
std::vector<std::pair<DefItemId, void*>> Profile_Item_AddOrChange;
|
std::vector<std::pair<DefItemId, DefItem>> Profile_Item_AddOrChange;
|
||||||
std::vector<DefItemId> Profile_Item_Lost;
|
std::vector<DefItemId> Profile_Item_Lost;
|
||||||
|
|
||||||
std::vector<std::pair<WorldId_t, void*>> Worlds_AddOrChange;
|
std::vector<std::pair<WorldId_t, void*>> Worlds_AddOrChange;
|
||||||
@@ -109,13 +142,13 @@ private:
|
|||||||
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_Voxel;
|
||||||
std::unordered_map<WorldId_t, std::unordered_map<Pos::GlobalChunk, std::u8string>> Chunks_AddOrChange_Node;
|
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::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 AssetsBindsChange {
|
struct ChunkCompressedSize {
|
||||||
// Новые привязки ресурсов
|
uint32_t Voxels = 0;
|
||||||
std::vector<AssetBindEntry> Binds;
|
uint32_t Nodes = 0;
|
||||||
// Потерянные из видимости ресурсы
|
|
||||||
std::vector<ResourceId> Lost[(int) EnumAssets::MAX_ENUM];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
@@ -125,22 +158,7 @@ private:
|
|||||||
// Накопление данных за такт сервера
|
// Накопление данных за такт сервера
|
||||||
TickData ThisTickEntry;
|
TickData ThisTickEntry;
|
||||||
|
|
||||||
// Сюда обращается ветка обновления IServerSession, накапливая данные до SyncTick
|
|
||||||
// Ресурсы, ожидающие ответа от менеджера кеша
|
|
||||||
std::unordered_map<std::string, std::vector<std::pair<std::string, Hash_t>>> ResourceWait[(int) EnumAssets::MAX_ENUM];
|
|
||||||
// Полученные изменения связок в ожидании стадии синхронизации такта
|
|
||||||
std::vector<AssetsBindsChange> Binds;
|
|
||||||
// Подгруженные или принятые меж тактами ресурсы
|
|
||||||
std::vector<AssetEntry> LoadedResources;
|
|
||||||
// Список ресурсов на которые уже был отправлен запрос на загрузку ресурса
|
|
||||||
std::vector<Hash_t> AlreadyLoading;
|
|
||||||
|
|
||||||
|
|
||||||
// Обменный пункт
|
// Обменный пункт
|
||||||
// Полученные ресурсы с сервера
|
|
||||||
TOS::SpinlockObject<std::vector<AssetEntry>> LoadedAssets;
|
|
||||||
// Изменения в наблюдаемых ресурсах
|
|
||||||
TOS::SpinlockObject<std::vector<AssetsBindsChange>> AssetsBinds;
|
|
||||||
// Пакеты обновлений игрового мира
|
// Пакеты обновлений игрового мира
|
||||||
TOS::SpinlockObject<std::vector<TickData>> TickSequence;
|
TOS::SpinlockObject<std::vector<TickData>> TickSequence;
|
||||||
} AsyncContext;
|
} AsyncContext;
|
||||||
@@ -168,20 +186,34 @@ private:
|
|||||||
|
|
||||||
GlobalTime LastSendPYR_POS;
|
GlobalTime LastSendPYR_POS;
|
||||||
|
|
||||||
|
std::unordered_map<WorldId_t, std::unordered_map<Pos::GlobalChunk, ChunkCompressedSize>> VisibleChunkCompressed;
|
||||||
|
uint64_t VisibleChunkCompressedBytes = 0;
|
||||||
|
|
||||||
// Приём данных с сокета
|
// Приём данных с сокета
|
||||||
coro<> run(AsyncUseControl::Lock);
|
coro<> run(AsyncUseControl::Lock);
|
||||||
void protocolError();
|
void protocolError();
|
||||||
coro<> readPacket(Net::AsyncSocket &sock);
|
coro<> readPacket(Net::AsyncSocket &sock);
|
||||||
coro<> rP_System(Net::AsyncSocket &sock);
|
coro<> rP_Disconnect(Net::AsyncSocket &sock);
|
||||||
coro<> rP_Resource(Net::AsyncSocket &sock);
|
coro<> rP_AssetsBindDK(Net::AsyncSocket &sock);
|
||||||
coro<> rP_Definition(Net::AsyncSocket &sock);
|
coro<> rP_AssetsBindHH(Net::AsyncSocket &sock);
|
||||||
coro<> rP_Content(Net::AsyncSocket &sock);
|
coro<> rP_AssetsInitSend(Net::AsyncSocket &sock);
|
||||||
|
coro<> rP_AssetsNextSend(Net::AsyncSocket &sock);
|
||||||
|
coro<> rP_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)
|
// Нужен сокет, на котором только что был согласован игровой протокол (asyncInitGameProtocol)
|
||||||
ServerSession(asio::io_context &ioc, std::unique_ptr<Net::AsyncSocket> &&socket);
|
ServerSession(asio::io_context &ioc, std::unique_ptr<Net::AsyncSocket> &&socket);
|
||||||
|
|
||||||
virtual coro<> asyncDestructor() override;
|
virtual coro<> asyncDestructor() override;
|
||||||
|
void resetResourceSyncState();
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,12 +35,12 @@ struct VoxelVertexPoint {
|
|||||||
|
|
||||||
struct NodeVertexStatic {
|
struct NodeVertexStatic {
|
||||||
uint32_t
|
uint32_t
|
||||||
FX : 9, FY : 9, FZ : 9, // Позиция -224 ~ 288; 64 позиций в одной ноде, 7.5 метров в ряд
|
FX : 11, FY : 11, N1 : 10, // Позиция, 64 позиции на метр, +3.5м запас
|
||||||
N1 : 4, // Не занято
|
FZ : 11, // Позиция
|
||||||
LS : 1, // Масштаб карты освещения (1м/16 или 1м)
|
LS : 1, // Масштаб карты освещения (1м/16 или 1м)
|
||||||
Tex : 18, // Текстура
|
Tex : 18, // Текстура
|
||||||
N2 : 14, // Не занято
|
N2 : 2, // Не занято
|
||||||
TU : 16, TV : 16; // UV на текстуре
|
TU : 16, TV : 16; // UV на текстуре
|
||||||
|
|
||||||
bool operator==(const NodeVertexStatic& other) const {
|
bool operator==(const NodeVertexStatic& other) const {
|
||||||
return std::memcmp(this, &other, sizeof(*this)) == 0;
|
return std::memcmp(this, &other, sizeof(*this)) == 0;
|
||||||
@@ -49,4 +49,4 @@ struct NodeVertexStatic {
|
|||||||
bool operator<=>(const NodeVertexStatic&) const = default;
|
bool operator<=>(const NodeVertexStatic&) const = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
307
Src/Client/Vulkan/AtlasPipeline/PipelinedTextureAtlas.cpp
Normal file
307
Src/Client/Vulkan/AtlasPipeline/PipelinedTextureAtlas.cpp
Normal file
@@ -0,0 +1,307 @@
|
|||||||
|
#include "PipelinedTextureAtlas.hpp"
|
||||||
|
|
||||||
|
PipelinedTextureAtlas::PipelinedTextureAtlas(TextureAtlas&& tk)
|
||||||
|
: Super(std::move(tk)) {}
|
||||||
|
|
||||||
|
PipelinedTextureAtlas::AtlasTextureId PipelinedTextureAtlas::getByPipeline(const HashedPipeline& pipeline) {
|
||||||
|
auto iter = _PipeToTexId.find(pipeline);
|
||||||
|
if (iter == _PipeToTexId.end()) {
|
||||||
|
AtlasTextureId atlasTexId = Super.registerTexture();
|
||||||
|
_PipeToTexId.insert({pipeline, atlasTexId});
|
||||||
|
_ChangedPipelines.push_back(pipeline);
|
||||||
|
|
||||||
|
for (uint32_t texId : pipeline.getDependencedTextures()) {
|
||||||
|
_AddictedTextures[texId].push_back(pipeline);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::vector<TexturePipelineProgram::AnimSpec> animMeta =
|
||||||
|
TexturePipelineProgram::extractAnimationSpecs(pipeline._Pipeline.data(), pipeline._Pipeline.size());
|
||||||
|
if (!animMeta.empty()) {
|
||||||
|
AnimatedPipelineState entry;
|
||||||
|
entry.Specs.reserve(animMeta.size());
|
||||||
|
for (const auto& spec : animMeta) {
|
||||||
|
detail::AnimSpec16 outSpec{};
|
||||||
|
outSpec.TexId = spec.HasTexId ? spec.TexId : TextureAtlas::kOverflowId;
|
||||||
|
outSpec.FrameW = spec.FrameW;
|
||||||
|
outSpec.FrameH = spec.FrameH;
|
||||||
|
outSpec.FrameCount = spec.FrameCount;
|
||||||
|
outSpec.FpsQ = spec.FpsQ;
|
||||||
|
outSpec.Flags = spec.Flags;
|
||||||
|
entry.Specs.push_back(outSpec);
|
||||||
|
}
|
||||||
|
entry.LastFrames.resize(entry.Specs.size(), std::numeric_limits<uint32_t>::max());
|
||||||
|
entry.Smooth = false;
|
||||||
|
for (const auto& spec : entry.Specs) {
|
||||||
|
if (spec.Flags & detail::AnimSmooth) {
|
||||||
|
entry.Smooth = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_AnimatedPipelines.emplace(pipeline, std::move(entry));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return atlasTexId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return iter->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PipelinedTextureAtlas::freeByPipeline(const HashedPipeline& pipeline) {
|
||||||
|
auto iter = _PipeToTexId.find(pipeline);
|
||||||
|
if (iter == _PipeToTexId.end()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint32_t texId : pipeline.getDependencedTextures()) {
|
||||||
|
auto iterAT = _AddictedTextures.find(texId);
|
||||||
|
assert(iterAT != _AddictedTextures.end());
|
||||||
|
auto iterATSub = std::find(iterAT->second.begin(), iterAT->second.end(), pipeline);
|
||||||
|
assert(iterATSub != iterAT->second.end());
|
||||||
|
iterAT->second.erase(iterATSub);
|
||||||
|
}
|
||||||
|
|
||||||
|
Super.removeTexture(iter->second);
|
||||||
|
_AtlasCpuTextures.erase(iter->second);
|
||||||
|
_PipeToTexId.erase(iter);
|
||||||
|
_AnimatedPipelines.erase(pipeline);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PipelinedTextureAtlas::updateTexture(uint32_t texId, const StoredTexture& texture) {
|
||||||
|
_ResToTexture[texId] = texture;
|
||||||
|
_ChangedTextures.push_back(texId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PipelinedTextureAtlas::updateTexture(uint32_t texId, StoredTexture&& texture) {
|
||||||
|
_ResToTexture[texId] = std::move(texture);
|
||||||
|
_ChangedTextures.push_back(texId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PipelinedTextureAtlas::freeTexture(uint32_t texId) {
|
||||||
|
auto iter = _ResToTexture.find(texId);
|
||||||
|
if (iter != _ResToTexture.end()) {
|
||||||
|
_ResToTexture.erase(iter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PipelinedTextureAtlas::getHostTexture(TextureId texId, HostTextureView& out) const {
|
||||||
|
auto fill = [&](const StoredTexture& tex) -> bool {
|
||||||
|
if (tex._Pixels.empty() || tex._Widht == 0 || tex._Height == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out.width = tex._Widht;
|
||||||
|
out.height = tex._Height;
|
||||||
|
out.rowPitchBytes = static_cast<uint32_t>(tex._Widht) * 4u;
|
||||||
|
out.pixelsRGBA8 = reinterpret_cast<const uint8_t*>(tex._Pixels.data());
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto it = _ResToTexture.find(texId);
|
||||||
|
if (it != _ResToTexture.end() && fill(it->second)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto itAtlas = _AtlasCpuTextures.find(texId);
|
||||||
|
if (itAtlas != _AtlasCpuTextures.end() && fill(itAtlas->second)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
StoredTexture PipelinedTextureAtlas::_generatePipelineTexture(const HashedPipeline& pipeline) {
|
||||||
|
std::vector<detail::Word> words(pipeline._Pipeline.begin(), pipeline._Pipeline.end());
|
||||||
|
if (words.empty()) {
|
||||||
|
if (auto tex = tryCopyFirstDependencyTexture(pipeline)) {
|
||||||
|
return *tex;
|
||||||
|
}
|
||||||
|
return makeSolidColorTexture(0xFFFF00FFu);
|
||||||
|
}
|
||||||
|
|
||||||
|
TexturePipelineProgram program;
|
||||||
|
program.fromBytes(std::move(words));
|
||||||
|
|
||||||
|
TexturePipelineProgram::OwnedTexture baked;
|
||||||
|
auto provider = [this](uint32_t texId) -> std::optional<Texture> {
|
||||||
|
auto iter = _ResToTexture.find(texId);
|
||||||
|
if (iter == _ResToTexture.end()) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
const StoredTexture& stored = iter->second;
|
||||||
|
if (stored._Pixels.empty() || stored._Widht == 0 || stored._Height == 0) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
Texture tex{};
|
||||||
|
tex.Width = stored._Widht;
|
||||||
|
tex.Height = stored._Height;
|
||||||
|
tex.Pixels = stored._Pixels.data();
|
||||||
|
return tex;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!program.bake(provider, baked, _AnimTimeSeconds, nullptr)) {
|
||||||
|
if (auto tex = tryCopyFirstDependencyTexture(pipeline)) {
|
||||||
|
return *tex;
|
||||||
|
}
|
||||||
|
return makeSolidColorTexture(0xFFFF00FFu);
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint32_t width = baked.Width;
|
||||||
|
const uint32_t height = baked.Height;
|
||||||
|
if (width == 0 || height == 0 ||
|
||||||
|
width > std::numeric_limits<uint16_t>::max() ||
|
||||||
|
height > std::numeric_limits<uint16_t>::max() ||
|
||||||
|
baked.Pixels.size() != static_cast<size_t>(width) * static_cast<size_t>(height)) {
|
||||||
|
if (auto tex = tryCopyFirstDependencyTexture(pipeline)) {
|
||||||
|
return *tex;
|
||||||
|
}
|
||||||
|
return makeSolidColorTexture(0xFFFF00FFu);
|
||||||
|
}
|
||||||
|
|
||||||
|
return StoredTexture(static_cast<uint16_t>(width),
|
||||||
|
static_cast<uint16_t>(height),
|
||||||
|
std::move(baked.Pixels));
|
||||||
|
}
|
||||||
|
|
||||||
|
void PipelinedTextureAtlas::flushNewPipelines() {
|
||||||
|
std::vector<uint32_t> changedTextures = std::move(_ChangedTextures);
|
||||||
|
_ChangedTextures.clear();
|
||||||
|
|
||||||
|
std::sort(changedTextures.begin(), changedTextures.end());
|
||||||
|
changedTextures.erase(std::unique(changedTextures.begin(), changedTextures.end()), changedTextures.end());
|
||||||
|
|
||||||
|
std::vector<HashedPipeline> changedPipelineTextures;
|
||||||
|
for (uint32_t texId : changedTextures) {
|
||||||
|
auto iter = _AddictedTextures.find(texId);
|
||||||
|
if (iter == _AddictedTextures.end()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
changedPipelineTextures.append_range(iter->second);
|
||||||
|
}
|
||||||
|
|
||||||
|
changedPipelineTextures.append_range(std::move(_ChangedPipelines));
|
||||||
|
_ChangedPipelines.clear();
|
||||||
|
changedTextures.clear();
|
||||||
|
|
||||||
|
std::sort(changedPipelineTextures.begin(), changedPipelineTextures.end());
|
||||||
|
changedPipelineTextures.erase(std::unique(changedPipelineTextures.begin(), changedPipelineTextures.end()),
|
||||||
|
changedPipelineTextures.end());
|
||||||
|
|
||||||
|
for (const HashedPipeline& pipeline : changedPipelineTextures) {
|
||||||
|
auto iterPTTI = _PipeToTexId.find(pipeline);
|
||||||
|
assert(iterPTTI != _PipeToTexId.end());
|
||||||
|
|
||||||
|
StoredTexture texture = _generatePipelineTexture(pipeline);
|
||||||
|
AtlasTextureId atlasTexId = iterPTTI->second;
|
||||||
|
auto& stored = _AtlasCpuTextures[atlasTexId];
|
||||||
|
stored = std::move(texture);
|
||||||
|
if (!stored._Pixels.empty()) {
|
||||||
|
// Смена порядка пикселей
|
||||||
|
for (uint32_t& pixel : stored._Pixels) {
|
||||||
|
union {
|
||||||
|
struct { uint8_t r, g, b, a; } color;
|
||||||
|
uint32_t data;
|
||||||
|
};
|
||||||
|
|
||||||
|
data = pixel;
|
||||||
|
std::swap(color.r, color.b);
|
||||||
|
pixel = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
Super.setTextureData(atlasTexId,
|
||||||
|
stored._Widht,
|
||||||
|
stored._Height,
|
||||||
|
stored._Pixels.data(),
|
||||||
|
stored._Widht * 4u);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TextureAtlas::DescriptorOut PipelinedTextureAtlas::flushUploadsAndBarriers(VkCommandBuffer cmdBuffer) {
|
||||||
|
return Super.flushUploadsAndBarriers(cmdBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PipelinedTextureAtlas::notifyGpuFinished() {
|
||||||
|
Super.notifyGpuFinished();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PipelinedTextureAtlas::updateAnimatedPipelines(double timeSeconds) {
|
||||||
|
_AnimTimeSeconds = timeSeconds;
|
||||||
|
if (_AnimatedPipelines.empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool changed = false;
|
||||||
|
for (auto& [pipeline, entry] : _AnimatedPipelines) {
|
||||||
|
if (entry.Specs.empty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.Smooth) {
|
||||||
|
_ChangedPipelines.push_back(pipeline);
|
||||||
|
changed = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.LastFrames.size() != entry.Specs.size())
|
||||||
|
entry.LastFrames.assign(entry.Specs.size(), std::numeric_limits<uint32_t>::max());
|
||||||
|
|
||||||
|
bool pipelineChanged = false;
|
||||||
|
for (size_t i = 0; i < entry.Specs.size(); ++i) {
|
||||||
|
const auto& spec = entry.Specs[i];
|
||||||
|
|
||||||
|
uint32_t fpsQ = spec.FpsQ ? spec.FpsQ : TexturePipelineProgram::DefaultAnimFpsQ;
|
||||||
|
double fps = double(fpsQ) / 256.0;
|
||||||
|
double frameTime = timeSeconds * fps;
|
||||||
|
if (frameTime < 0.0)
|
||||||
|
frameTime = 0.0;
|
||||||
|
|
||||||
|
uint32_t frameCount = spec.FrameCount;
|
||||||
|
// Авторасчёт количества кадров
|
||||||
|
if (frameCount == 0) {
|
||||||
|
auto iterTex = _ResToTexture.find(spec.TexId);
|
||||||
|
if (iterTex != _ResToTexture.end()) {
|
||||||
|
uint32_t fw = spec.FrameW ? spec.FrameW : iterTex->second._Widht;
|
||||||
|
uint32_t fh = spec.FrameH ? spec.FrameH : iterTex->second._Widht;
|
||||||
|
if (fw > 0 && fh > 0) {
|
||||||
|
if (spec.Flags & detail::AnimHorizontal)
|
||||||
|
frameCount = iterTex->second._Widht / fw;
|
||||||
|
else
|
||||||
|
frameCount = iterTex->second._Height / fh;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frameCount == 0)
|
||||||
|
frameCount = 1;
|
||||||
|
|
||||||
|
uint32_t frameIndex = frameCount ? (uint32_t(frameTime) % frameCount) : 0u;
|
||||||
|
if (entry.LastFrames[i] != frameIndex) {
|
||||||
|
entry.LastFrames[i] = frameIndex;
|
||||||
|
pipelineChanged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pipelineChanged) {
|
||||||
|
_ChangedPipelines.push_back(pipeline);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<StoredTexture> PipelinedTextureAtlas::tryCopyFirstDependencyTexture(const HashedPipeline& pipeline) const {
|
||||||
|
auto deps = pipeline.getDependencedTextures();
|
||||||
|
if (!deps.empty()) {
|
||||||
|
auto iter = _ResToTexture.find(deps.front());
|
||||||
|
if (iter != _ResToTexture.end()) {
|
||||||
|
return iter->second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
StoredTexture PipelinedTextureAtlas::makeSolidColorTexture(uint32_t rgba) {
|
||||||
|
return StoredTexture(1, 1, std::vector<uint32_t>{rgba});
|
||||||
|
}
|
||||||
490
Src/Client/Vulkan/AtlasPipeline/PipelinedTextureAtlas.hpp
Normal file
490
Src/Client/Vulkan/AtlasPipeline/PipelinedTextureAtlas.hpp
Normal file
@@ -0,0 +1,490 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "TextureAtlas.hpp"
|
||||||
|
#include "TexturePipelineProgram.hpp"
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cassert>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
#include <limits>
|
||||||
|
#include <optional>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
#include "boost/container/small_vector.hpp"
|
||||||
|
|
||||||
|
using TextureId = uint32_t;
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
using Word = TexturePipelineProgram::Word;
|
||||||
|
|
||||||
|
enum class Op16 : Word {
|
||||||
|
End = 0,
|
||||||
|
Base_Tex = 1,
|
||||||
|
Base_Fill = 2,
|
||||||
|
Base_Anim = 3,
|
||||||
|
Resize = 10,
|
||||||
|
Transform = 11,
|
||||||
|
Opacity = 12,
|
||||||
|
NoAlpha = 13,
|
||||||
|
MakeAlpha = 14,
|
||||||
|
Invert = 15,
|
||||||
|
Brighten = 16,
|
||||||
|
Contrast = 17,
|
||||||
|
Multiply = 18,
|
||||||
|
Screen = 19,
|
||||||
|
Colorize = 20,
|
||||||
|
Anim = 21,
|
||||||
|
Overlay = 30,
|
||||||
|
Mask = 31,
|
||||||
|
LowPart = 32,
|
||||||
|
Combine = 40
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class SrcKind16 : Word { TexId = 0, Sub = 1 };
|
||||||
|
|
||||||
|
struct SrcRef16 {
|
||||||
|
SrcKind16 kind{SrcKind16::TexId};
|
||||||
|
uint32_t TexId = 0;
|
||||||
|
uint32_t Off = 0;
|
||||||
|
uint32_t Len = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum AnimFlags16 : Word {
|
||||||
|
AnimSmooth = 1 << 0,
|
||||||
|
AnimHorizontal = 1 << 1
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AnimSpec16 {
|
||||||
|
uint32_t TexId = 0;
|
||||||
|
uint16_t FrameW = 0;
|
||||||
|
uint16_t FrameH = 0;
|
||||||
|
uint16_t FrameCount = 0;
|
||||||
|
uint16_t FpsQ = 0;
|
||||||
|
uint16_t Flags = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline void addUniqueDep(boost::container::small_vector<uint32_t, 8>& deps, uint32_t id) {
|
||||||
|
if (id == TextureAtlas::kOverflowId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (std::find(deps.begin(), deps.end(), id) == deps.end()) {
|
||||||
|
deps.push_back(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool read16(const std::vector<Word>& words, size_t end, size_t& ip, uint16_t& out) {
|
||||||
|
if (ip + 1 >= end) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out = uint16_t(words[ip]) | (uint16_t(words[ip + 1]) << 8);
|
||||||
|
ip += 2;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool read24(const std::vector<Word>& words, size_t end, size_t& ip, uint32_t& out) {
|
||||||
|
if (ip + 2 >= end) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out = uint32_t(words[ip]) |
|
||||||
|
(uint32_t(words[ip + 1]) << 8) |
|
||||||
|
(uint32_t(words[ip + 2]) << 16);
|
||||||
|
ip += 3;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool read32(const std::vector<Word>& words, size_t end, size_t& ip, uint32_t& out) {
|
||||||
|
if (ip + 3 >= end) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out = uint32_t(words[ip]) |
|
||||||
|
(uint32_t(words[ip + 1]) << 8) |
|
||||||
|
(uint32_t(words[ip + 2]) << 16) |
|
||||||
|
(uint32_t(words[ip + 3]) << 24);
|
||||||
|
ip += 4;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool readSrc(const std::vector<Word>& words, size_t end, size_t& ip, SrcRef16& out) {
|
||||||
|
if (ip >= end) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out.kind = static_cast<SrcKind16>(words[ip++]);
|
||||||
|
if (out.kind == SrcKind16::TexId) {
|
||||||
|
return read24(words, end, ip, out.TexId);
|
||||||
|
}
|
||||||
|
if (out.kind == SrcKind16::Sub) {
|
||||||
|
return read24(words, end, ip, out.Off) && read24(words, end, ip, out.Len);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void extractPipelineDependencies(const std::vector<Word>& words,
|
||||||
|
size_t start,
|
||||||
|
size_t end,
|
||||||
|
boost::container::small_vector<uint32_t, 8>& deps,
|
||||||
|
std::vector<std::pair<size_t, size_t>>& visited) {
|
||||||
|
if (start >= end || end > words.size()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const std::pair<size_t, size_t> key{start, end};
|
||||||
|
if (std::find(visited.begin(), visited.end(), key) != visited.end()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
visited.push_back(key);
|
||||||
|
|
||||||
|
size_t ip = start;
|
||||||
|
auto need = [&](size_t n) { return ip + n <= end; };
|
||||||
|
auto handleSrc = [&](const SrcRef16& src) {
|
||||||
|
if (src.kind == SrcKind16::TexId) {
|
||||||
|
addUniqueDep(deps, src.TexId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (src.kind == SrcKind16::Sub) {
|
||||||
|
size_t subStart = static_cast<size_t>(src.Off);
|
||||||
|
size_t subEnd = subStart + static_cast<size_t>(src.Len);
|
||||||
|
if (subStart < subEnd && subEnd <= words.size()) {
|
||||||
|
extractPipelineDependencies(words, subStart, subEnd, deps, visited);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
while (ip < end) {
|
||||||
|
if (!need(1)) break;
|
||||||
|
Op16 op = static_cast<Op16>(words[ip++]);
|
||||||
|
switch (op) {
|
||||||
|
case Op16::End:
|
||||||
|
return;
|
||||||
|
|
||||||
|
case Op16::Base_Tex: {
|
||||||
|
SrcRef16 src{};
|
||||||
|
if (!readSrc(words, end, ip, src)) return;
|
||||||
|
handleSrc(src);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case Op16::Base_Anim: {
|
||||||
|
SrcRef16 src{};
|
||||||
|
if (!readSrc(words, end, ip, src)) return;
|
||||||
|
handleSrc(src);
|
||||||
|
uint16_t tmp16 = 0;
|
||||||
|
uint8_t tmp8 = 0;
|
||||||
|
if (!read16(words, end, ip, tmp16)) return;
|
||||||
|
if (!read16(words, end, ip, tmp16)) return;
|
||||||
|
if (!read16(words, end, ip, tmp16)) return;
|
||||||
|
if (!read16(words, end, ip, tmp16)) return;
|
||||||
|
if (!need(1)) return;
|
||||||
|
tmp8 = words[ip++];
|
||||||
|
(void)tmp8;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case Op16::Base_Fill: {
|
||||||
|
uint16_t tmp16 = 0;
|
||||||
|
uint32_t tmp32 = 0;
|
||||||
|
if (!read16(words, end, ip, tmp16)) return;
|
||||||
|
if (!read16(words, end, ip, tmp16)) return;
|
||||||
|
if (!read32(words, end, ip, tmp32)) return;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case Op16::Overlay:
|
||||||
|
case Op16::Mask: {
|
||||||
|
SrcRef16 src{};
|
||||||
|
if (!readSrc(words, end, ip, src)) return;
|
||||||
|
handleSrc(src);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case Op16::LowPart: {
|
||||||
|
if (!need(1)) return;
|
||||||
|
ip += 1; // percent
|
||||||
|
SrcRef16 src{};
|
||||||
|
if (!readSrc(words, end, ip, src)) return;
|
||||||
|
handleSrc(src);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case Op16::Resize: {
|
||||||
|
uint16_t tmp16 = 0;
|
||||||
|
if (!read16(words, end, ip, tmp16)) return;
|
||||||
|
if (!read16(words, end, ip, tmp16)) return;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case Op16::Transform:
|
||||||
|
case Op16::Opacity:
|
||||||
|
if (!need(1)) return;
|
||||||
|
ip += 1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Op16::NoAlpha:
|
||||||
|
case Op16::Brighten:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Op16::MakeAlpha:
|
||||||
|
if (!need(3)) return;
|
||||||
|
ip += 3;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Op16::Invert:
|
||||||
|
if (!need(1)) return;
|
||||||
|
ip += 1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Op16::Contrast:
|
||||||
|
if (!need(2)) return;
|
||||||
|
ip += 2;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Op16::Multiply:
|
||||||
|
case Op16::Screen: {
|
||||||
|
uint32_t tmp32 = 0;
|
||||||
|
if (!read32(words, end, ip, tmp32)) return;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case Op16::Colorize: {
|
||||||
|
uint32_t tmp32 = 0;
|
||||||
|
if (!read32(words, end, ip, tmp32)) return;
|
||||||
|
if (!need(1)) return;
|
||||||
|
ip += 1;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case Op16::Anim: {
|
||||||
|
uint16_t tmp16 = 0;
|
||||||
|
if (!read16(words, end, ip, tmp16)) return;
|
||||||
|
if (!read16(words, end, ip, tmp16)) return;
|
||||||
|
if (!read16(words, end, ip, tmp16)) return;
|
||||||
|
if (!read16(words, end, ip, tmp16)) return;
|
||||||
|
if (!need(1)) return;
|
||||||
|
ip += 1;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case Op16::Combine: {
|
||||||
|
uint16_t w = 0, h = 0, n = 0;
|
||||||
|
if (!read16(words, end, ip, w)) return;
|
||||||
|
if (!read16(words, end, ip, h)) return;
|
||||||
|
if (!read16(words, end, ip, n)) return;
|
||||||
|
for (uint32_t i = 0; i < n; ++i) {
|
||||||
|
uint16_t tmp16 = 0;
|
||||||
|
if (!read16(words, end, ip, tmp16)) return; // x
|
||||||
|
if (!read16(words, end, ip, tmp16)) return; // y
|
||||||
|
SrcRef16 src{};
|
||||||
|
if (!readSrc(words, end, ip, src)) return;
|
||||||
|
handleSrc(src);
|
||||||
|
}
|
||||||
|
(void)w; (void)h;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline boost::container::small_vector<uint32_t, 8> extractPipelineDependencies(const std::vector<Word>& words) {
|
||||||
|
boost::container::small_vector<uint32_t, 8> deps;
|
||||||
|
std::vector<std::pair<size_t, size_t>> visited;
|
||||||
|
extractPipelineDependencies(words, 0, words.size(), deps, visited);
|
||||||
|
return deps;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline boost::container::small_vector<uint32_t, 8> extractPipelineDependencies(const boost::container::small_vector<Word, 32>& words) {
|
||||||
|
boost::container::small_vector<uint32_t, 8> deps;
|
||||||
|
std::vector<std::pair<size_t, size_t>> visited;
|
||||||
|
std::vector<Word> copy(words.begin(), words.end());
|
||||||
|
extractPipelineDependencies(copy, 0, copy.size(), deps, visited);
|
||||||
|
return deps;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
// Структура нехешированного пайплайна
|
||||||
|
struct Pipeline {
|
||||||
|
std::vector<detail::Word> _Pipeline;
|
||||||
|
|
||||||
|
Pipeline() = default;
|
||||||
|
|
||||||
|
explicit Pipeline(const TexturePipelineProgram& program)
|
||||||
|
: _Pipeline(program.words().begin(), program.words().end())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Pipeline(TextureId texId) {
|
||||||
|
_Pipeline = {
|
||||||
|
static_cast<detail::Word>(detail::Op16::Base_Tex),
|
||||||
|
static_cast<detail::Word>(detail::SrcKind16::TexId),
|
||||||
|
static_cast<detail::Word>(texId & 0xFFu),
|
||||||
|
static_cast<detail::Word>((texId >> 8) & 0xFFu),
|
||||||
|
static_cast<detail::Word>((texId >> 16) & 0xFFu),
|
||||||
|
static_cast<detail::Word>(detail::Op16::End)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Структура хешированного текстурного пайплайна
|
||||||
|
struct HashedPipeline {
|
||||||
|
// Предвычисленный хеш
|
||||||
|
std::size_t _Hash;
|
||||||
|
boost::container::small_vector<detail::Word, 32> _Pipeline;
|
||||||
|
|
||||||
|
HashedPipeline() = default;
|
||||||
|
HashedPipeline(const Pipeline& pipeline) noexcept
|
||||||
|
: _Pipeline(pipeline._Pipeline.begin(), pipeline._Pipeline.end())
|
||||||
|
{
|
||||||
|
reComputeHash();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Перевычисляет хеш
|
||||||
|
void reComputeHash() noexcept {
|
||||||
|
std::size_t hash = 14695981039346656037ull;
|
||||||
|
constexpr std::size_t prime = 1099511628211ull;
|
||||||
|
|
||||||
|
for(detail::Word w : _Pipeline) {
|
||||||
|
hash ^= static_cast<uint8_t>(w);
|
||||||
|
hash *= prime;
|
||||||
|
}
|
||||||
|
|
||||||
|
_Hash = hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Выдаёт список зависимых текстур, на основе которых строится эта
|
||||||
|
boost::container::small_vector<uint32_t, 8> getDependencedTextures() const {
|
||||||
|
return detail::extractPipelineDependencies(_Pipeline);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const HashedPipeline& obj) const noexcept {
|
||||||
|
return _Hash == obj._Hash && _Pipeline == obj._Pipeline;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator<(const HashedPipeline& obj) const noexcept {
|
||||||
|
return _Hash < obj._Hash || (_Hash == obj._Hash && _Pipeline < obj._Pipeline);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct StoredTexture {
|
||||||
|
uint16_t _Widht = 0;
|
||||||
|
uint16_t _Height = 0;
|
||||||
|
std::vector<uint32_t> _Pixels;
|
||||||
|
|
||||||
|
StoredTexture() = default;
|
||||||
|
StoredTexture(uint16_t w, uint16_t h, std::vector<uint32_t> pixels)
|
||||||
|
: _Widht(w), _Height(h), _Pixels(std::move(pixels))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// Пайплайновый текстурный атлас
|
||||||
|
class PipelinedTextureAtlas {
|
||||||
|
public:
|
||||||
|
using AtlasTextureId = uint32_t;
|
||||||
|
struct HostTextureView {
|
||||||
|
uint32_t width = 0;
|
||||||
|
uint32_t height = 0;
|
||||||
|
uint32_t rowPitchBytes = 0;
|
||||||
|
const uint8_t* pixelsRGBA8 = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Функтор хеша
|
||||||
|
struct HashedPipelineKeyHash {
|
||||||
|
std::size_t operator()(const HashedPipeline& k) const noexcept {
|
||||||
|
return k._Hash;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Функтор равенства
|
||||||
|
struct HashedPipelineKeyEqual {
|
||||||
|
bool operator()(const HashedPipeline& a, const HashedPipeline& b) const noexcept {
|
||||||
|
return a._Pipeline == b._Pipeline;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Текстурный атлас
|
||||||
|
TextureAtlas Super;
|
||||||
|
// Пустой пайплайн (указывающий на одну текстуру) ссылается на простой идентификатор (ResToAtlas)
|
||||||
|
std::unordered_map<HashedPipeline, AtlasTextureId, HashedPipelineKeyHash, HashedPipelineKeyEqual> _PipeToTexId;
|
||||||
|
// Загруженные текстуры
|
||||||
|
std::unordered_map<TextureId, StoredTexture> _ResToTexture;
|
||||||
|
std::unordered_map<AtlasTextureId, StoredTexture> _AtlasCpuTextures;
|
||||||
|
// Список зависимых пайплайнов от текстур (при изменении текстуры, нужно перерисовать пайплайны)
|
||||||
|
std::unordered_map<TextureId, boost::container::small_vector<HashedPipeline, 8>> _AddictedTextures;
|
||||||
|
// Изменённые простые текстуры (для последующего массового обновление пайплайнов)
|
||||||
|
std::vector<uint32_t> _ChangedTextures;
|
||||||
|
// Необходимые к созданию/обновлению пайплайны
|
||||||
|
std::vector<HashedPipeline> _ChangedPipelines;
|
||||||
|
struct AnimatedPipelineState {
|
||||||
|
std::vector<detail::AnimSpec16> Specs;
|
||||||
|
std::vector<uint32_t> LastFrames;
|
||||||
|
bool Smooth = false;
|
||||||
|
};
|
||||||
|
std::unordered_map<HashedPipeline, AnimatedPipelineState, HashedPipelineKeyHash, HashedPipelineKeyEqual> _AnimatedPipelines;
|
||||||
|
double _AnimTimeSeconds = 0.0;
|
||||||
|
|
||||||
|
public:
|
||||||
|
PipelinedTextureAtlas(TextureAtlas&& tk);
|
||||||
|
|
||||||
|
uint32_t atlasSide() const {
|
||||||
|
return Super.atlasSide();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t atlasLayers() const {
|
||||||
|
return Super.atlasLayers();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t AtlasSide() const {
|
||||||
|
return atlasSide();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t AtlasLayers() const {
|
||||||
|
return atlasLayers();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t maxLayers() const {
|
||||||
|
return Super.maxLayers();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t maxTextureId() const {
|
||||||
|
return Super.maxTextureId();
|
||||||
|
}
|
||||||
|
|
||||||
|
TextureAtlas::TextureId reservedOverflowId() const {
|
||||||
|
return Super.reservedOverflowId();
|
||||||
|
}
|
||||||
|
|
||||||
|
TextureAtlas::TextureId reservedLayerId(uint32_t layer) const {
|
||||||
|
return Super.reservedLayerId(layer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void requestLayerCount(uint32_t layers) {
|
||||||
|
Super.requestLayerCount(layers);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Должны всегда бронировать идентификатор, либо отдавать kOverflowId. При этом запись tex+pipeline остаётся
|
||||||
|
// Выдаёт стабильный идентификатор, привязанный к пайплайну
|
||||||
|
AtlasTextureId getByPipeline(const HashedPipeline& pipeline);
|
||||||
|
|
||||||
|
// Уведомить что текстура+pipeline более не используются (идентификатор будет освобождён)
|
||||||
|
// Освобождать можно при потере ресурсов
|
||||||
|
void freeByPipeline(const HashedPipeline& pipeline);
|
||||||
|
|
||||||
|
void updateTexture(uint32_t texId, const StoredTexture& texture);
|
||||||
|
void updateTexture(uint32_t texId, StoredTexture&& texture);
|
||||||
|
|
||||||
|
void freeTexture(uint32_t texId);
|
||||||
|
|
||||||
|
bool getHostTexture(TextureId texId, HostTextureView& out) const;
|
||||||
|
|
||||||
|
// Генерация текстуры пайплайна
|
||||||
|
StoredTexture _generatePipelineTexture(const HashedPipeline& pipeline);
|
||||||
|
|
||||||
|
// Обновляет пайплайны по необходимости
|
||||||
|
void flushNewPipelines();
|
||||||
|
|
||||||
|
TextureAtlas::DescriptorOut flushUploadsAndBarriers(VkCommandBuffer cmdBuffer);
|
||||||
|
|
||||||
|
void notifyGpuFinished();
|
||||||
|
|
||||||
|
bool updateAnimatedPipelines(double timeSeconds);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::optional<StoredTexture> tryCopyFirstDependencyTexture(const HashedPipeline& pipeline) const;
|
||||||
|
|
||||||
|
static StoredTexture makeSolidColorTexture(uint32_t rgba);
|
||||||
|
};
|
||||||
169
Src/Client/Vulkan/AtlasPipeline/SharedStagingBuffer.hpp
Normal file
169
Src/Client/Vulkan/AtlasPipeline/SharedStagingBuffer.hpp
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vulkan/vulkan.h>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <optional>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
/*
|
||||||
|
Межкадровый промежуточный буфер.
|
||||||
|
Для модели рендера Один за одним.
|
||||||
|
После окончания рендера кадра считается синхронизированным
|
||||||
|
и может заполняться по новой.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class SharedStagingBuffer {
|
||||||
|
public:
|
||||||
|
static constexpr VkDeviceSize kDefaultSize = 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;
|
||||||
|
};
|
||||||
485
Src/Client/Vulkan/AtlasPipeline/TextureAtlas.cpp
Normal file
485
Src/Client/Vulkan/AtlasPipeline/TextureAtlas.cpp
Normal file
@@ -0,0 +1,485 @@
|
|||||||
|
#include "TextureAtlas.hpp"
|
||||||
|
|
||||||
|
TextureAtlas::TextureAtlas(VkDevice device,
|
||||||
|
VkPhysicalDevice physicalDevice,
|
||||||
|
const Config& cfg,
|
||||||
|
EventCallback cb,
|
||||||
|
std::shared_ptr<SharedStagingBuffer> staging)
|
||||||
|
: Device_(device),
|
||||||
|
Phys_(physicalDevice),
|
||||||
|
Cfg_(cfg),
|
||||||
|
OnEvent_(std::move(cb)),
|
||||||
|
Staging_(std::move(staging)) {
|
||||||
|
if(!Device_ || !Phys_) {
|
||||||
|
throw std::runtime_error("TextureAtlas: device/physicalDevice == null");
|
||||||
|
}
|
||||||
|
_validateConfigOrThrow();
|
||||||
|
|
||||||
|
VkPhysicalDeviceProperties props{};
|
||||||
|
vkGetPhysicalDeviceProperties(Phys_, &props);
|
||||||
|
CopyOffsetAlignment_ = std::max<VkDeviceSize>(4, props.limits.optimalBufferCopyOffsetAlignment);
|
||||||
|
|
||||||
|
if(!Staging_) {
|
||||||
|
Staging_ = std::make_shared<SharedStagingBuffer>(Device_, Phys_, kStagingSizeBytes);
|
||||||
|
}
|
||||||
|
_validateStagingCapacityOrThrow();
|
||||||
|
|
||||||
|
_createEntriesBufferOrThrow();
|
||||||
|
_createAtlasOrThrow(Cfg_.InitialSide, 1);
|
||||||
|
|
||||||
|
EntriesCpu_.resize(Cfg_.MaxTextureId);
|
||||||
|
std::memset(EntriesCpu_.data(), 0, EntriesCpu_.size() * sizeof(Entry));
|
||||||
|
_initReservedEntries();
|
||||||
|
EntriesDirty_ = true;
|
||||||
|
|
||||||
|
Slots_.resize(Cfg_.MaxTextureId);
|
||||||
|
FreeIds_.reserve(Cfg_.MaxTextureId);
|
||||||
|
PendingInQueue_.assign(Cfg_.MaxTextureId, false);
|
||||||
|
NextId_ = _allocatableStart();
|
||||||
|
|
||||||
|
if(Cfg_.ExternalSampler != VK_NULL_HANDLE) {
|
||||||
|
Sampler_ = Cfg_.ExternalSampler;
|
||||||
|
OwnsSampler_ = false;
|
||||||
|
} else {
|
||||||
|
_createSamplerOrThrow();
|
||||||
|
OwnsSampler_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
_rebuildPackersFromPlacements();
|
||||||
|
Alive_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
TextureAtlas::~TextureAtlas() { _shutdownNoThrow(); }
|
||||||
|
|
||||||
|
TextureAtlas::TextureAtlas(TextureAtlas&& other) noexcept {
|
||||||
|
_moveFrom(std::move(other));
|
||||||
|
}
|
||||||
|
|
||||||
|
TextureAtlas& TextureAtlas::operator=(TextureAtlas&& other) noexcept {
|
||||||
|
if(this != &other) {
|
||||||
|
_shutdownNoThrow();
|
||||||
|
_moveFrom(std::move(other));
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextureAtlas::shutdown() {
|
||||||
|
_ensureAliveOrThrow();
|
||||||
|
_shutdownNoThrow();
|
||||||
|
}
|
||||||
|
|
||||||
|
TextureAtlas::TextureId TextureAtlas::registerTexture() {
|
||||||
|
_ensureAliveOrThrow();
|
||||||
|
|
||||||
|
TextureId id = kOverflowId;
|
||||||
|
if(NextId_ < _allocatableStart()) {
|
||||||
|
NextId_ = _allocatableStart();
|
||||||
|
}
|
||||||
|
while(!FreeIds_.empty() && isReservedId(FreeIds_.back())) {
|
||||||
|
FreeIds_.pop_back();
|
||||||
|
}
|
||||||
|
if(!FreeIds_.empty()) {
|
||||||
|
id = FreeIds_.back();
|
||||||
|
FreeIds_.pop_back();
|
||||||
|
} else if(NextId_ < _allocatableLimit()) {
|
||||||
|
id = NextId_++;
|
||||||
|
} else {
|
||||||
|
return reservedOverflowId();
|
||||||
|
}
|
||||||
|
|
||||||
|
Slot& s = Slots_[id];
|
||||||
|
s = Slot{};
|
||||||
|
s.InUse = true;
|
||||||
|
s.StateValue = State::REGISTERED;
|
||||||
|
s.Generation = 1;
|
||||||
|
|
||||||
|
_setEntryInvalid(id, /*diagPending*/false, /*diagTooLarge*/false);
|
||||||
|
EntriesDirty_ = true;
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextureAtlas::setTextureData(TextureId id,
|
||||||
|
uint32_t w,
|
||||||
|
uint32_t h,
|
||||||
|
const void* pixelsRGBA8,
|
||||||
|
uint32_t rowPitchBytes) {
|
||||||
|
_ensureAliveOrThrow();
|
||||||
|
if(isInvalidId(id)) return;
|
||||||
|
_ensureRegisteredIdOrThrow(id);
|
||||||
|
|
||||||
|
if(w == 0 || h == 0) {
|
||||||
|
throw _inputError("setTextureData: w/h must be > 0");
|
||||||
|
}
|
||||||
|
if(w > Cfg_.MaxTextureSize || h > Cfg_.MaxTextureSize) {
|
||||||
|
_handleTooLarge(id);
|
||||||
|
throw _inputError("setTextureData: texture is TOO_LARGE (>2048)");
|
||||||
|
}
|
||||||
|
if(!pixelsRGBA8) {
|
||||||
|
throw _inputError("setTextureData: pixelsRGBA8 == null");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(rowPitchBytes == 0) {
|
||||||
|
rowPitchBytes = w * 4;
|
||||||
|
}
|
||||||
|
if(rowPitchBytes < w * 4) {
|
||||||
|
throw _inputError("setTextureData: rowPitchBytes < w*4");
|
||||||
|
}
|
||||||
|
|
||||||
|
Slot& s = Slots_[id];
|
||||||
|
|
||||||
|
const bool sizeChanged = (s.HasCpuData && (s.W != w || s.H != h));
|
||||||
|
if(sizeChanged) {
|
||||||
|
_freePlacement(id);
|
||||||
|
_setEntryInvalid(id, /*diagPending*/true, /*diagTooLarge*/false);
|
||||||
|
EntriesDirty_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
s.W = w;
|
||||||
|
s.H = h;
|
||||||
|
|
||||||
|
s.CpuPixels = static_cast<const uint8_t*>(pixelsRGBA8);
|
||||||
|
s.CpuRowPitchBytes = rowPitchBytes;
|
||||||
|
s.HasCpuData = true;
|
||||||
|
s.StateValue = State::PENDING_UPLOAD;
|
||||||
|
s.Generation++;
|
||||||
|
|
||||||
|
if(!sizeChanged && s.HasPlacement && s.StateWasValid) {
|
||||||
|
// keep entry valid
|
||||||
|
} else if(!s.HasPlacement) {
|
||||||
|
_setEntryInvalid(id, /*diagPending*/true, /*diagTooLarge*/false);
|
||||||
|
EntriesDirty_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
_enqueuePending(id);
|
||||||
|
|
||||||
|
if(Repack_.Active && Repack_.Plan.count(id) != 0) {
|
||||||
|
_enqueueRepackPending(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextureAtlas::clearTextureData(TextureId id) {
|
||||||
|
_ensureAliveOrThrow();
|
||||||
|
if(isInvalidId(id)) return;
|
||||||
|
_ensureRegisteredIdOrThrow(id);
|
||||||
|
|
||||||
|
Slot& s = Slots_[id];
|
||||||
|
s.CpuPixels = nullptr;
|
||||||
|
s.CpuRowPitchBytes = 0;
|
||||||
|
s.HasCpuData = false;
|
||||||
|
|
||||||
|
_freePlacement(id);
|
||||||
|
s.StateValue = State::REGISTERED;
|
||||||
|
s.StateWasValid = false;
|
||||||
|
|
||||||
|
_removeFromPending(id);
|
||||||
|
_removeFromRepackPending(id);
|
||||||
|
|
||||||
|
_setEntryInvalid(id, /*diagPending*/false, /*diagTooLarge*/false);
|
||||||
|
EntriesDirty_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextureAtlas::removeTexture(TextureId id) {
|
||||||
|
_ensureAliveOrThrow();
|
||||||
|
if(isInvalidId(id)) return;
|
||||||
|
_ensureRegisteredIdOrThrow(id);
|
||||||
|
|
||||||
|
Slot& s = Slots_[id];
|
||||||
|
|
||||||
|
clearTextureData(id);
|
||||||
|
|
||||||
|
s.InUse = false;
|
||||||
|
s.StateValue = State::REMOVED;
|
||||||
|
|
||||||
|
FreeIds_.push_back(id);
|
||||||
|
|
||||||
|
_setEntryInvalid(id, /*diagPending*/false, /*diagTooLarge*/false);
|
||||||
|
EntriesDirty_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextureAtlas::requestFullRepack(RepackMode mode) {
|
||||||
|
_ensureAliveOrThrow();
|
||||||
|
Repack_.Requested = true;
|
||||||
|
Repack_.Mode = mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
TextureAtlas::DescriptorOut TextureAtlas::flushUploadsAndBarriers(VkCommandBuffer cmdBuffer) {
|
||||||
|
_ensureAliveOrThrow();
|
||||||
|
if(cmdBuffer == VK_NULL_HANDLE) {
|
||||||
|
throw _inputError("flushUploadsAndBarriers: cmdBuffer == null");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(Repack_.SwapReady) {
|
||||||
|
_swapToRepackedAtlas();
|
||||||
|
}
|
||||||
|
if(Repack_.Requested && !Repack_.Active) {
|
||||||
|
_startRepackIfPossible();
|
||||||
|
}
|
||||||
|
|
||||||
|
_processPendingLayerGrow(cmdBuffer);
|
||||||
|
|
||||||
|
bool willTouchEntries = EntriesDirty_;
|
||||||
|
|
||||||
|
auto collectQueue = [this](std::deque<TextureId>& queue,
|
||||||
|
std::vector<bool>& inQueue,
|
||||||
|
std::vector<TextureId>& out) {
|
||||||
|
while (!queue.empty()) {
|
||||||
|
TextureId id = queue.front();
|
||||||
|
queue.pop_front();
|
||||||
|
if(isInvalidId(id) || id >= inQueue.size()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(!inQueue[id]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
inQueue[id] = false;
|
||||||
|
out.push_back(id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<TextureId> pendingNow;
|
||||||
|
pendingNow.reserve(Pending_.size());
|
||||||
|
collectQueue(Pending_, PendingInQueue_, pendingNow);
|
||||||
|
|
||||||
|
std::vector<TextureId> repackPending;
|
||||||
|
if(Repack_.Active) {
|
||||||
|
if(Repack_.InPending.empty()) {
|
||||||
|
Repack_.InPending.assign(Cfg_.MaxTextureId, false);
|
||||||
|
}
|
||||||
|
collectQueue(Repack_.Pending, Repack_.InPending, repackPending);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto processPlacement = [&](TextureId id, Slot& s) -> bool {
|
||||||
|
if(s.HasPlacement) return true;
|
||||||
|
const uint32_t wP = s.W + 2u * Cfg_.PaddingPx;
|
||||||
|
const uint32_t hP = s.H + 2u * Cfg_.PaddingPx;
|
||||||
|
if(!_tryPlaceWithGrow(id, wP, hP, cmdBuffer)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
willTouchEntries = true;
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool outOfSpace = false;
|
||||||
|
for(TextureId id : pendingNow) {
|
||||||
|
if(isInvalidId(id)) continue;
|
||||||
|
if(id >= Slots_.size()) continue;
|
||||||
|
Slot& s = Slots_[id];
|
||||||
|
if(!s.InUse || !s.HasCpuData) continue;
|
||||||
|
if(!processPlacement(id, s)) {
|
||||||
|
outOfSpace = true;
|
||||||
|
_enqueuePending(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(outOfSpace) {
|
||||||
|
_emitEventOncePerFlush(AtlasEvent::AtlasOutOfSpace);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool anyAtlasWrites = false;
|
||||||
|
bool anyRepackWrites = false;
|
||||||
|
|
||||||
|
auto uploadTextureIntoAtlas = [&](Slot& s,
|
||||||
|
const Placement& pp,
|
||||||
|
ImageRes& targetAtlas,
|
||||||
|
bool isRepackTarget) {
|
||||||
|
const uint32_t wP = pp.WP;
|
||||||
|
const uint32_t hP = pp.HP;
|
||||||
|
const VkDeviceSize bytes = static_cast<VkDeviceSize>(wP) * hP * 4u;
|
||||||
|
auto stagingOff = Staging_->Allocate(bytes, CopyOffsetAlignment_);
|
||||||
|
if(!stagingOff) {
|
||||||
|
_emitEventOncePerFlush(AtlasEvent::StagingOverflow);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t* dst = static_cast<uint8_t*>(Staging_->Mapped()) + *stagingOff;
|
||||||
|
if(!s.CpuPixels) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_writePaddedRGBA8(dst, wP * 4u, s.W, s.H, Cfg_.PaddingPx,
|
||||||
|
s.CpuPixels, s.CpuRowPitchBytes);
|
||||||
|
|
||||||
|
_ensureImageLayoutForTransferDst(cmdBuffer, targetAtlas,
|
||||||
|
isRepackTarget ? anyRepackWrites : anyAtlasWrites);
|
||||||
|
|
||||||
|
VkBufferImageCopy region{};
|
||||||
|
region.bufferOffset = *stagingOff;
|
||||||
|
region.bufferRowLength = wP;
|
||||||
|
region.bufferImageHeight = hP;
|
||||||
|
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||||
|
region.imageSubresource.mipLevel = 0;
|
||||||
|
region.imageSubresource.baseArrayLayer = pp.Layer;
|
||||||
|
region.imageSubresource.layerCount = 1;
|
||||||
|
region.imageOffset = { static_cast<int32_t>(pp.X),
|
||||||
|
static_cast<int32_t>(pp.Y), 0 };
|
||||||
|
region.imageExtent = { wP, hP, 1 };
|
||||||
|
|
||||||
|
vkCmdCopyBufferToImage(cmdBuffer, Staging_->Buffer(), targetAtlas.Image,
|
||||||
|
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
for(TextureId id : pendingNow) {
|
||||||
|
if(isInvalidId(id)) continue;
|
||||||
|
Slot& s = Slots_[id];
|
||||||
|
if(!s.InUse || !s.HasCpuData || !s.HasPlacement) continue;
|
||||||
|
if(!uploadTextureIntoAtlas(s, s.Place, Atlas_, false)) {
|
||||||
|
_enqueuePending(id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
s.StateValue = State::VALID;
|
||||||
|
s.StateWasValid = true;
|
||||||
|
_setEntryValid(id);
|
||||||
|
EntriesDirty_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(Repack_.Active) {
|
||||||
|
for(TextureId id : repackPending) {
|
||||||
|
if(Repack_.Plan.count(id) == 0) continue;
|
||||||
|
Slot& s = Slots_[id];
|
||||||
|
if(!s.InUse || !s.HasCpuData) continue;
|
||||||
|
const PlannedPlacement& pp = Repack_.Plan[id];
|
||||||
|
Placement place{pp.X, pp.Y, pp.WP, pp.HP, pp.Layer};
|
||||||
|
if(!uploadTextureIntoAtlas(s, place, Repack_.Atlas, true)) {
|
||||||
|
_enqueueRepackPending(id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Repack_.WroteSomethingThisFlush = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(willTouchEntries || EntriesDirty_) {
|
||||||
|
const VkDeviceSize entriesBytes = static_cast<VkDeviceSize>(EntriesCpu_.size()) * sizeof(Entry);
|
||||||
|
auto off = Staging_->Allocate(entriesBytes, CopyOffsetAlignment_);
|
||||||
|
if(!off) {
|
||||||
|
_emitEventOncePerFlush(AtlasEvent::StagingOverflow);
|
||||||
|
} else {
|
||||||
|
std::memcpy(static_cast<uint8_t*>(Staging_->Mapped()) + *off,
|
||||||
|
EntriesCpu_.data(),
|
||||||
|
static_cast<size_t>(entriesBytes));
|
||||||
|
|
||||||
|
VkBufferCopy c{};
|
||||||
|
c.srcOffset = *off;
|
||||||
|
c.dstOffset = 0;
|
||||||
|
c.size = entriesBytes;
|
||||||
|
vkCmdCopyBuffer(cmdBuffer, Staging_->Buffer(), Entries_.Buffer, 1, &c);
|
||||||
|
|
||||||
|
VkBufferMemoryBarrier b{};
|
||||||
|
b.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER;
|
||||||
|
b.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||||
|
b.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
||||||
|
b.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||||
|
b.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||||
|
b.buffer = Entries_.Buffer;
|
||||||
|
b.offset = 0;
|
||||||
|
b.size = VK_WHOLE_SIZE;
|
||||||
|
|
||||||
|
vkCmdPipelineBarrier(cmdBuffer,
|
||||||
|
VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||||
|
VK_PIPELINE_STAGE_VERTEX_SHADER_BIT |
|
||||||
|
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
|
||||||
|
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
||||||
|
0, 0, nullptr, 1, &b, 0, nullptr);
|
||||||
|
EntriesDirty_ = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(anyAtlasWrites) {
|
||||||
|
_transitionImage(cmdBuffer, Atlas_,
|
||||||
|
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
|
||||||
|
VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||||
|
VK_ACCESS_SHADER_READ_BIT,
|
||||||
|
VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||||
|
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT);
|
||||||
|
} else if(Atlas_.Layout == VK_IMAGE_LAYOUT_UNDEFINED) {
|
||||||
|
_transitionImage(cmdBuffer, Atlas_,
|
||||||
|
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
|
||||||
|
0, VK_ACCESS_SHADER_READ_BIT,
|
||||||
|
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
|
||||||
|
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(anyRepackWrites) {
|
||||||
|
_transitionImage(cmdBuffer, Repack_.Atlas,
|
||||||
|
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
|
||||||
|
VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||||
|
VK_ACCESS_SHADER_READ_BIT,
|
||||||
|
VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||||
|
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(Repack_.Active) {
|
||||||
|
if(Repack_.Pending.empty()) {
|
||||||
|
Repack_.WaitingGpuForReady = true;
|
||||||
|
}
|
||||||
|
Repack_.WroteSomethingThisFlush = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _buildDescriptorOut();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextureAtlas::notifyGpuFinished() {
|
||||||
|
_ensureAliveOrThrow();
|
||||||
|
|
||||||
|
for(auto& img : DeferredImages_) {
|
||||||
|
_destroyImage(img);
|
||||||
|
}
|
||||||
|
DeferredImages_.clear();
|
||||||
|
|
||||||
|
if(Staging_) {
|
||||||
|
Staging_->Reset();
|
||||||
|
}
|
||||||
|
FlushEventMask_ = 0;
|
||||||
|
|
||||||
|
if(Repack_.Active && Repack_.WaitingGpuForReady && Repack_.Pending.empty()) {
|
||||||
|
Repack_.SwapReady = true;
|
||||||
|
Repack_.WaitingGpuForReady = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextureAtlas::_moveFrom(TextureAtlas&& other) noexcept {
|
||||||
|
Device_ = other.Device_;
|
||||||
|
Phys_ = other.Phys_;
|
||||||
|
Cfg_ = other.Cfg_;
|
||||||
|
OnEvent_ = std::move(other.OnEvent_);
|
||||||
|
Alive_ = other.Alive_;
|
||||||
|
CopyOffsetAlignment_ = other.CopyOffsetAlignment_;
|
||||||
|
Staging_ = std::move(other.Staging_);
|
||||||
|
Entries_ = other.Entries_;
|
||||||
|
Atlas_ = other.Atlas_;
|
||||||
|
Sampler_ = other.Sampler_;
|
||||||
|
OwnsSampler_ = other.OwnsSampler_;
|
||||||
|
EntriesCpu_ = std::move(other.EntriesCpu_);
|
||||||
|
EntriesDirty_ = other.EntriesDirty_;
|
||||||
|
Slots_ = std::move(other.Slots_);
|
||||||
|
FreeIds_ = std::move(other.FreeIds_);
|
||||||
|
NextId_ = other.NextId_;
|
||||||
|
Pending_ = std::move(other.Pending_);
|
||||||
|
PendingInQueue_ = std::move(other.PendingInQueue_);
|
||||||
|
Packers_ = std::move(other.Packers_);
|
||||||
|
DeferredImages_ = std::move(other.DeferredImages_);
|
||||||
|
FlushEventMask_ = other.FlushEventMask_;
|
||||||
|
GrewThisFlush_ = other.GrewThisFlush_;
|
||||||
|
Repack_ = std::move(other.Repack_);
|
||||||
|
|
||||||
|
other.Device_ = VK_NULL_HANDLE;
|
||||||
|
other.Phys_ = VK_NULL_HANDLE;
|
||||||
|
other.OnEvent_ = {};
|
||||||
|
other.Alive_ = false;
|
||||||
|
other.CopyOffsetAlignment_ = 0;
|
||||||
|
other.Staging_.reset();
|
||||||
|
other.Entries_ = {};
|
||||||
|
other.Atlas_ = {};
|
||||||
|
other.Sampler_ = VK_NULL_HANDLE;
|
||||||
|
other.OwnsSampler_ = false;
|
||||||
|
other.EntriesCpu_.clear();
|
||||||
|
other.EntriesDirty_ = false;
|
||||||
|
other.Slots_.clear();
|
||||||
|
other.FreeIds_.clear();
|
||||||
|
other.NextId_ = 0;
|
||||||
|
other.Pending_.clear();
|
||||||
|
other.PendingInQueue_.clear();
|
||||||
|
other.Packers_.clear();
|
||||||
|
other.DeferredImages_.clear();
|
||||||
|
other.FlushEventMask_ = 0;
|
||||||
|
other.GrewThisFlush_ = false;
|
||||||
|
other.Repack_ = RepackState{};
|
||||||
|
}
|
||||||
1450
Src/Client/Vulkan/AtlasPipeline/TextureAtlas.hpp
Normal file
1450
Src/Client/Vulkan/AtlasPipeline/TextureAtlas.hpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,3 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Common/TexturePipelineProgram.hpp"
|
||||||
@@ -1,12 +1,43 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Vulkan.hpp"
|
#include "Vulkan.hpp"
|
||||||
|
#include "Client/Vulkan/AtlasPipeline/SharedStagingBuffer.hpp"
|
||||||
|
#include <algorithm>
|
||||||
#include <bitset>
|
#include <bitset>
|
||||||
|
#include <cstring>
|
||||||
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
#include <queue>
|
||||||
|
#include <vector>
|
||||||
#include <vulkan/vulkan_core.h>
|
#include <vulkan/vulkan_core.h>
|
||||||
|
|
||||||
|
|
||||||
namespace LV::Client::VK {
|
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 вершин в каждом
|
Для массивов вершин память выделяется блоками по PerBlock вершин в каждом
|
||||||
@@ -22,10 +53,8 @@ class VertexPool {
|
|||||||
Vulkan *Inst;
|
Vulkan *Inst;
|
||||||
|
|
||||||
// Память, доступная для обмена с устройством
|
// Память, доступная для обмена с устройством
|
||||||
Buffer HostCoherent;
|
std::shared_ptr<SharedStagingBuffer> Staging;
|
||||||
Vertex *HCPtr = nullptr;
|
VkDeviceSize CopyOffsetAlignment = 4;
|
||||||
VkFence Fence = nullptr;
|
|
||||||
size_t WritePos = 0;
|
|
||||||
|
|
||||||
struct Pool {
|
struct Pool {
|
||||||
// Память на устройстве
|
// Память на устройстве
|
||||||
@@ -47,7 +76,6 @@ class VertexPool {
|
|||||||
|
|
||||||
struct Task {
|
struct Task {
|
||||||
std::vector<Vertex> Data;
|
std::vector<Vertex> Data;
|
||||||
size_t Pos = -1; // Если данные уже записаны, то будет указана позиция в буфере общения
|
|
||||||
uint8_t PoolId; // Куда потом направить
|
uint8_t PoolId; // Куда потом направить
|
||||||
uint16_t BlockId; // И в какой блок
|
uint16_t BlockId; // И в какой блок
|
||||||
};
|
};
|
||||||
@@ -61,46 +89,21 @@ class VertexPool {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
void pushData(std::vector<Vertex>&& data, uint8_t poolId, uint16_t blockId) {
|
void pushData(std::vector<Vertex>&& data, uint8_t poolId, uint16_t blockId) {
|
||||||
if(HC_Buffer_Size-WritePos >= data.size()) {
|
TasksWait.push({std::move(data), poolId, blockId});
|
||||||
// Пишем в общий буфер, TasksWait
|
|
||||||
Vertex *ptr = HCPtr+WritePos;
|
|
||||||
std::copy(data.begin(), data.end(), ptr);
|
|
||||||
size_t count = data.size();
|
|
||||||
TasksWait.push({std::move(data), WritePos, poolId, blockId});
|
|
||||||
WritePos += count;
|
|
||||||
} else {
|
|
||||||
// Отложим запись на следующий такт
|
|
||||||
TasksPostponed.push(Task(std::move(data), -1, poolId, blockId));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
VertexPool(Vulkan* inst)
|
VertexPool(Vulkan* inst)
|
||||||
: Inst(inst),
|
: Inst(inst)
|
||||||
HostCoherent(inst,
|
|
||||||
sizeof(Vertex)*HC_Buffer_Size+4 /* Для vkCmdFillBuffer */,
|
|
||||||
VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
|
|
||||||
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)
|
|
||||||
{
|
{
|
||||||
Pools.reserve(16);
|
Pools.reserve(16);
|
||||||
HCPtr = (Vertex*) HostCoherent.mapMemory();
|
Staging = getOrCreateVertexStaging(inst);
|
||||||
|
VkPhysicalDeviceProperties props{};
|
||||||
const VkFenceCreateInfo info = {
|
vkGetPhysicalDeviceProperties(inst->Graphics.PhysicalDevice, &props);
|
||||||
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
|
CopyOffsetAlignment = std::max<VkDeviceSize>(4, props.limits.optimalBufferCopyOffsetAlignment);
|
||||||
.pNext = nullptr,
|
|
||||||
.flags = 0
|
|
||||||
};
|
|
||||||
|
|
||||||
vkAssert(!vkCreateFence(inst->Graphics.Device, &info, nullptr, &Fence));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
~VertexPool() {
|
~VertexPool() {
|
||||||
if(HCPtr)
|
|
||||||
HostCoherent.unMapMemory();
|
|
||||||
|
|
||||||
if(Fence) {
|
|
||||||
vkDestroyFence(Inst->Graphics.Device, Fence, nullptr);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -229,44 +232,65 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Должно вызываться после приёма всех данных и перед рендером
|
Должно вызываться после приёма всех данных, до начала рендера в командном буфере
|
||||||
*/
|
*/
|
||||||
void update(VkCommandPool commandPool) {
|
void flushUploadsAndBarriers(VkCommandBuffer commandBuffer) {
|
||||||
if(TasksWait.empty())
|
if(TasksWait.empty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
assert(WritePos);
|
struct CopyTask {
|
||||||
|
VkBuffer DstBuffer;
|
||||||
VkCommandBufferAllocateInfo allocInfo {
|
VkDeviceSize SrcOffset;
|
||||||
VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
|
VkDeviceSize DstOffset;
|
||||||
nullptr,
|
VkDeviceSize Size;
|
||||||
commandPool,
|
uint8_t PoolId;
|
||||||
VK_COMMAND_BUFFER_LEVEL_PRIMARY,
|
|
||||||
1
|
|
||||||
};
|
};
|
||||||
|
|
||||||
VkCommandBuffer commandBuffer;
|
std::vector<CopyTask> copies;
|
||||||
vkAllocateCommandBuffers(Inst->Graphics.Device, &allocInfo, &commandBuffer);
|
copies.reserve(TasksWait.size());
|
||||||
|
std::vector<uint8_t> touchedPools(Pools.size(), 0);
|
||||||
|
|
||||||
VkCommandBufferBeginInfo beginInfo {
|
while(!TasksWait.empty()) {
|
||||||
VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
|
Task task = std::move(TasksWait.front());
|
||||||
nullptr,
|
TasksWait.pop();
|
||||||
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
|
|
||||||
nullptr
|
|
||||||
};
|
|
||||||
|
|
||||||
vkBeginCommandBuffer(commandBuffer, &beginInfo);
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
VkBufferMemoryBarrier barrier = {
|
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,
|
VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
|
||||||
nullptr,
|
nullptr,
|
||||||
VK_ACCESS_HOST_WRITE_BIT,
|
VK_ACCESS_HOST_WRITE_BIT,
|
||||||
VK_ACCESS_TRANSFER_READ_BIT,
|
VK_ACCESS_TRANSFER_READ_BIT,
|
||||||
VK_QUEUE_FAMILY_IGNORED,
|
VK_QUEUE_FAMILY_IGNORED,
|
||||||
VK_QUEUE_FAMILY_IGNORED,
|
VK_QUEUE_FAMILY_IGNORED,
|
||||||
HostCoherent.getBuffer(),
|
Staging->Buffer(),
|
||||||
0,
|
0,
|
||||||
WritePos*sizeof(Vertex)
|
Staging->Size()
|
||||||
};
|
};
|
||||||
|
|
||||||
vkCmdPipelineBarrier(
|
vkCmdPipelineBarrier(
|
||||||
@@ -275,53 +299,60 @@ public:
|
|||||||
VK_PIPELINE_STAGE_TRANSFER_BIT,
|
VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||||
0,
|
0,
|
||||||
0, nullptr,
|
0, nullptr,
|
||||||
1, &barrier,
|
1, &stagingBarrier,
|
||||||
0, nullptr
|
0, nullptr
|
||||||
);
|
);
|
||||||
|
|
||||||
while(!TasksWait.empty()) {
|
for(const CopyTask& copy : copies) {
|
||||||
Task& task = TasksWait.front();
|
|
||||||
|
|
||||||
VkBufferCopy copyRegion {
|
VkBufferCopy copyRegion {
|
||||||
task.Pos*sizeof(Vertex),
|
copy.SrcOffset,
|
||||||
task.BlockId*sizeof(Vertex)*size_t(PerBlock),
|
copy.DstOffset,
|
||||||
task.Data.size()*sizeof(Vertex)
|
copy.Size
|
||||||
};
|
};
|
||||||
|
|
||||||
assert(copyRegion.dstOffset+copyRegion.size < sizeof(Vertex)*PerBlock*PerPool);
|
assert(copyRegion.dstOffset+copyRegion.size <= Pools[copy.PoolId].DeviceBuff.getSize());
|
||||||
|
|
||||||
vkCmdCopyBuffer(commandBuffer, HostCoherent.getBuffer(), Pools[task.PoolId].DeviceBuff.getBuffer(),
|
vkCmdCopyBuffer(commandBuffer, Staging->Buffer(), copy.DstBuffer, 1, ©Region);
|
||||||
1, ©Region);
|
|
||||||
|
|
||||||
TasksWait.pop();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
vkEndCommandBuffer(commandBuffer);
|
std::vector<VkBufferMemoryBarrier> dstBarriers;
|
||||||
|
dstBarriers.reserve(Pools.size());
|
||||||
|
for(size_t poolId = 0; poolId < Pools.size(); poolId++) {
|
||||||
|
if(!touchedPools[poolId])
|
||||||
|
continue;
|
||||||
|
|
||||||
VkSubmitInfo submitInfo {
|
VkBufferMemoryBarrier barrier = {
|
||||||
VK_STRUCTURE_TYPE_SUBMIT_INFO,
|
VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
|
||||||
nullptr,
|
nullptr,
|
||||||
0, nullptr,
|
VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||||
nullptr,
|
IsIndex ? VK_ACCESS_INDEX_READ_BIT : VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT,
|
||||||
1,
|
VK_QUEUE_FAMILY_IGNORED,
|
||||||
&commandBuffer,
|
VK_QUEUE_FAMILY_IGNORED,
|
||||||
0,
|
Pools[poolId].DeviceBuff.getBuffer(),
|
||||||
nullptr
|
0,
|
||||||
};
|
Pools[poolId].DeviceBuff.getSize()
|
||||||
{
|
};
|
||||||
auto lockQueue = Inst->Graphics.DeviceQueueGraphic.lock();
|
dstBarriers.push_back(barrier);
|
||||||
vkAssert(!vkQueueSubmit(*lockQueue, 1, &submitInfo, Fence));
|
|
||||||
}
|
}
|
||||||
vkAssert(!vkWaitForFences(Inst->Graphics.Device, 1, &Fence, VK_TRUE, UINT64_MAX));
|
|
||||||
vkAssert(!vkResetFences(Inst->Graphics.Device, 1, &Fence));
|
|
||||||
vkFreeCommandBuffers(Inst->Graphics.Device, commandPool, 1, &commandBuffer);
|
|
||||||
|
|
||||||
|
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);
|
std::queue<Task> postponed = std::move(TasksPostponed);
|
||||||
WritePos = 0;
|
|
||||||
|
|
||||||
while(!postponed.empty()) {
|
while(!postponed.empty()) {
|
||||||
Task& task = postponed.front();
|
TasksWait.push(std::move(postponed.front()));
|
||||||
pushData(std::move(task.Data), task.PoolId, task.BlockId);
|
|
||||||
postponed.pop();
|
postponed.pop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -330,4 +361,4 @@ public:
|
|||||||
template<typename Type, uint16_t PerBlock = 1 << 10, uint16_t PerPool = 1 << 12>
|
template<typename Type, uint16_t PerBlock = 1 << 10, uint16_t PerPool = 1 << 12>
|
||||||
using IndexPool = VertexPool<Type, PerBlock, PerPool, true>;
|
using IndexPool = VertexPool<Type, PerBlock, PerPool, true>;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
#include <png++/png.hpp>
|
#include <png++/png.hpp>
|
||||||
#include "VulkanRenderSession.hpp"
|
#include "VulkanRenderSession.hpp"
|
||||||
#include <Server/GameServer.hpp>
|
#include <Server/GameServer.hpp>
|
||||||
|
#include <malloc.h>
|
||||||
|
|
||||||
extern void LoadSymbolsVulkan(TOS::DynamicLibrary &library);
|
extern void LoadSymbolsVulkan(TOS::DynamicLibrary &library);
|
||||||
|
|
||||||
@@ -222,6 +223,8 @@ void Vulkan::run()
|
|||||||
} catch(const std::exception &exc) {
|
} catch(const std::exception &exc) {
|
||||||
LOG.error() << "Game.Session->shutdown: " << exc.what();
|
LOG.error() << "Game.Session->shutdown: " << exc.what();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Game.Session = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!NeedShutdown && glfwWindowShouldClose(Graphics.Window)) {
|
if(!NeedShutdown && glfwWindowShouldClose(Graphics.Window)) {
|
||||||
@@ -240,11 +243,12 @@ void Vulkan::run()
|
|||||||
try {
|
try {
|
||||||
if(Game.Session)
|
if(Game.Session)
|
||||||
Game.Session->shutdown(EnumDisconnect::ByInterface);
|
Game.Session->shutdown(EnumDisconnect::ByInterface);
|
||||||
Game.Session = nullptr;
|
|
||||||
} catch(const std::exception &exc) {
|
} catch(const std::exception &exc) {
|
||||||
LOG.error() << "Game.Session->shutdown: " << exc.what();
|
LOG.error() << "Game.Session->shutdown: " << exc.what();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Game.Session = nullptr;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if(Game.Server)
|
if(Game.Server)
|
||||||
Game.Server->GS.shutdown("Завершение работы из-за остановки клиента");
|
Game.Server->GS.shutdown("Завершение работы из-за остановки клиента");
|
||||||
@@ -275,10 +279,6 @@ void Vulkan::run()
|
|||||||
// if(CallBeforeDraw)
|
// if(CallBeforeDraw)
|
||||||
// CallBeforeDraw(this);
|
// CallBeforeDraw(this);
|
||||||
|
|
||||||
if(Game.RSession) {
|
|
||||||
Game.RSession->beforeDraw();
|
|
||||||
}
|
|
||||||
|
|
||||||
glfwPollEvents();
|
glfwPollEvents();
|
||||||
|
|
||||||
VkResult err;
|
VkResult err;
|
||||||
@@ -314,6 +314,10 @@ void Vulkan::run()
|
|||||||
vkAssert(!vkBeginCommandBuffer(Graphics.CommandBufferRender, &cmd_buf_info));
|
vkAssert(!vkBeginCommandBuffer(Graphics.CommandBufferRender, &cmd_buf_info));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(Game.RSession) {
|
||||||
|
Game.RSession->beforeDraw(double(gTime));
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
VkImageMemoryBarrier image_memory_barrier =
|
VkImageMemoryBarrier image_memory_barrier =
|
||||||
{
|
{
|
||||||
@@ -602,6 +606,8 @@ void Vulkan::run()
|
|||||||
// Насильно ожидаем завершения рендера кадра
|
// Насильно ожидаем завершения рендера кадра
|
||||||
vkWaitForFences(Graphics.Device, 1, &drawEndFence, true, -1);
|
vkWaitForFences(Graphics.Device, 1, &drawEndFence, true, -1);
|
||||||
vkResetFences(Graphics.Device, 1, &drawEndFence);
|
vkResetFences(Graphics.Device, 1, &drawEndFence);
|
||||||
|
if(Game.RSession)
|
||||||
|
Game.RSession->onGpuFinished();
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -622,12 +628,6 @@ void Vulkan::run()
|
|||||||
err = vkQueuePresentKHR(*lockQueue, &present);
|
err = vkQueuePresentKHR(*lockQueue, &present);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
|
||||||
auto lockQueue = Graphics.DeviceQueueGraphic.lock();
|
|
||||||
vkDeviceWaitIdle(Graphics.Device);
|
|
||||||
lockQueue.unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (err == VK_ERROR_OUT_OF_DATE_KHR)
|
if (err == VK_ERROR_OUT_OF_DATE_KHR)
|
||||||
{
|
{
|
||||||
freeSwapchains();
|
freeSwapchains();
|
||||||
@@ -649,12 +649,6 @@ void Vulkan::run()
|
|||||||
Screen.State = DrawState::End;
|
Screen.State = DrawState::End;
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
|
||||||
auto lockQueue = Graphics.DeviceQueueGraphic.lock();
|
|
||||||
vkDeviceWaitIdle(Graphics.Device);
|
|
||||||
lockQueue.unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
for(int iter = 0; iter < 4; iter++) {
|
for(int iter = 0; iter < 4; iter++) {
|
||||||
vkDestroySemaphore(Graphics.Device, SemaphoreImageAcquired[iter], nullptr);
|
vkDestroySemaphore(Graphics.Device, SemaphoreImageAcquired[iter], nullptr);
|
||||||
vkDestroySemaphore(Graphics.Device, SemaphoreDrawComplete[iter], nullptr);
|
vkDestroySemaphore(Graphics.Device, SemaphoreDrawComplete[iter], nullptr);
|
||||||
@@ -686,8 +680,6 @@ uint32_t Vulkan::memoryTypeFromProperties(uint32_t bitsOfAcceptableTypes, VkFlag
|
|||||||
|
|
||||||
void Vulkan::freeSwapchains()
|
void Vulkan::freeSwapchains()
|
||||||
{
|
{
|
||||||
//vkDeviceWaitIdle(Screen.Device);
|
|
||||||
|
|
||||||
if(Graphics.Instance && Graphics.Device)
|
if(Graphics.Instance && Graphics.Device)
|
||||||
{
|
{
|
||||||
std::vector<VkImageView> oldViews;
|
std::vector<VkImageView> oldViews;
|
||||||
@@ -2266,6 +2258,10 @@ void Vulkan::gui_MainMenu() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(ImGui::Button("Memory trim")) {
|
||||||
|
malloc_trim(0);
|
||||||
|
}
|
||||||
|
|
||||||
if(ConnectionProgress.InProgress) {
|
if(ConnectionProgress.InProgress) {
|
||||||
if(ImGui::Button("Отмена"))
|
if(ImGui::Button("Отмена"))
|
||||||
ConnectionProgress.Cancel = true;
|
ConnectionProgress.Cancel = true;
|
||||||
@@ -2300,14 +2296,27 @@ void Vulkan::gui_ConnectedToServer() {
|
|||||||
(int) Game.RSession->PlayerPos.x >> 6, (int) Game.RSession->PlayerPos.y >> 6, (int) Game.RSession->PlayerPos.z >> 6
|
(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"))
|
if(ImGui::Button("Delimeter"))
|
||||||
LOG.debug();
|
LOG.debug();
|
||||||
|
|
||||||
|
if(ImGui::Button("Перезагрузить моды")) {
|
||||||
|
Game.Session->requestModsReload();
|
||||||
|
}
|
||||||
|
|
||||||
if(ImGui::Button("Выйти")) {
|
if(ImGui::Button("Выйти")) {
|
||||||
Game.Выйти = true;
|
Game.Выйти = true;
|
||||||
Game.ImGuiInterfaces.pop_back();
|
Game.ImGuiInterfaces.pop_back();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(ImGui::Button("Memory trim")) {
|
||||||
|
malloc_trim(0);
|
||||||
|
}
|
||||||
|
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
|
|
||||||
if(Game.Выйти)
|
if(Game.Выйти)
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ struct DeviceId {
|
|||||||
struct Settings {
|
struct Settings {
|
||||||
DeviceId DeviceMain;
|
DeviceId DeviceMain;
|
||||||
uint32_t QueueGraphics = -1, QueueSurface = -1;
|
uint32_t QueueGraphics = -1, QueueSurface = -1;
|
||||||
bool Debug = true;
|
bool Debug = false;
|
||||||
|
|
||||||
bool isValid()
|
bool isValid()
|
||||||
{
|
{
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,5 @@
|
|||||||
#include "Abstract.hpp"
|
#include "Abstract.hpp"
|
||||||
|
#include "Common/TexturePipelineProgram.hpp"
|
||||||
#include "Common/Net.hpp"
|
#include "Common/Net.hpp"
|
||||||
#include "TOSLib.hpp"
|
#include "TOSLib.hpp"
|
||||||
#include <boost/interprocess/file_mapping.hpp>
|
#include <boost/interprocess/file_mapping.hpp>
|
||||||
@@ -6,6 +7,8 @@
|
|||||||
#include "boost/json.hpp"
|
#include "boost/json.hpp"
|
||||||
#include "sha2.hpp"
|
#include "sha2.hpp"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <cctype>
|
||||||
|
#include <cstring>
|
||||||
#include <boost/iostreams/filtering_streambuf.hpp>
|
#include <boost/iostreams/filtering_streambuf.hpp>
|
||||||
#include <boost/iostreams/copy.hpp>
|
#include <boost/iostreams/copy.hpp>
|
||||||
#include <boost/iostreams/filter/zlib.hpp>
|
#include <boost/iostreams/filter/zlib.hpp>
|
||||||
@@ -15,6 +18,8 @@
|
|||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <unordered_set>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
|
|
||||||
@@ -22,8 +27,7 @@ namespace LV {
|
|||||||
|
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
std::u8string compressVoxels_byte(const std::vector<VoxelCube>& voxels) {
|
||||||
CompressedVoxels compressVoxels_byte(const std::vector<VoxelCube>& voxels) {
|
|
||||||
std::u8string compressed;
|
std::u8string compressed;
|
||||||
std::vector<DefVoxelId> defines;
|
std::vector<DefVoxelId> defines;
|
||||||
DefVoxelId maxValue = 0;
|
DefVoxelId maxValue = 0;
|
||||||
@@ -116,10 +120,10 @@ CompressedVoxels compressVoxels_byte(const std::vector<VoxelCube>& voxels) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {compressLinear(compressed), defines};
|
return compressLinear(compressed);
|
||||||
}
|
}
|
||||||
|
|
||||||
CompressedVoxels compressVoxels_bit(const std::vector<VoxelCube>& voxels) {
|
std::u8string compressVoxels_bit(const std::vector<VoxelCube>& voxels) {
|
||||||
std::vector<DefVoxelId> profile;
|
std::vector<DefVoxelId> profile;
|
||||||
std::vector<DefVoxelId> one_byte[7];
|
std::vector<DefVoxelId> one_byte[7];
|
||||||
|
|
||||||
@@ -262,10 +266,10 @@ CompressedVoxels compressVoxels_bit(const std::vector<VoxelCube>& voxels) {
|
|||||||
for(size_t iter = 0; iter < buff.size(); iter++)
|
for(size_t iter = 0; iter < buff.size(); iter++)
|
||||||
compressed[iter / 8] |= (buff[iter] << (iter % 8));
|
compressed[iter / 8] |= (buff[iter] << (iter % 8));
|
||||||
|
|
||||||
return {compressLinear(compressed), profile};
|
return compressLinear(compressed);
|
||||||
}
|
}
|
||||||
|
|
||||||
CompressedVoxels compressVoxels(const std::vector<VoxelCube>& voxels, bool fast) {
|
std::u8string compressVoxels(const std::vector<VoxelCube>& voxels, bool fast) {
|
||||||
if(fast)
|
if(fast)
|
||||||
return compressVoxels_byte(voxels);
|
return compressVoxels_byte(voxels);
|
||||||
else
|
else
|
||||||
@@ -649,24 +653,8 @@ CompressedNodes compressNodes_bit(const Node* nodes) {
|
|||||||
return {compressLinear(compressed), profiles};
|
return {compressLinear(compressed), profiles};
|
||||||
}
|
}
|
||||||
|
|
||||||
CompressedNodes compressNodes(const Node* nodes, bool fast) {
|
std::u8string compressNodes(const Node* nodes, bool fast) {
|
||||||
std::u8string data(16*16*16*sizeof(Node), '\0');
|
return compressLinear(std::u8string_view((const char8_t*) nodes, 16*16*16*sizeof(Node)));
|
||||||
const char8_t *ptr = (const char8_t*) nodes;
|
|
||||||
std::copy(ptr, ptr+16*16*16*4, data.data());
|
|
||||||
|
|
||||||
std::vector<DefNodeId> node(16*16*16);
|
|
||||||
for(int iter = 0; iter < 16*16*16; iter++) {
|
|
||||||
node[iter] = nodes[iter].NodeId;
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
std::sort(node.begin(), node.end());
|
|
||||||
auto last = std::unique(node.begin(), node.end());
|
|
||||||
node.erase(last, node.end());
|
|
||||||
node.shrink_to_fit();
|
|
||||||
}
|
|
||||||
|
|
||||||
return {compressLinear(data), std::move(node)};
|
|
||||||
|
|
||||||
// if(fast)
|
// if(fast)
|
||||||
// return compressNodes_byte(nodes);
|
// return compressNodes_byte(nodes);
|
||||||
@@ -795,7 +783,7 @@ void unCompressNodes_bit(const std::u8string& compressed, Node* ptr) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void unCompressNodes(const std::u8string& compressed, Node* ptr) {
|
void unCompressNodes(std::u8string_view compressed, Node* ptr) {
|
||||||
const std::u8string& next = unCompressLinear(compressed);
|
const std::u8string& next = unCompressLinear(compressed);
|
||||||
const Node *lPtr = (const Node*) next.data();
|
const Node *lPtr = (const Node*) next.data();
|
||||||
std::copy(lPtr, lPtr+16*16*16, ptr);
|
std::copy(lPtr, lPtr+16*16*16, ptr);
|
||||||
@@ -806,7 +794,7 @@ void unCompressNodes(const std::u8string& compressed, Node* ptr) {
|
|||||||
// return unCompressNodes_bit(next, ptr);
|
// return unCompressNodes_bit(next, ptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::u8string compressLinear(const std::u8string& data) {
|
std::u8string compressLinear(std::u8string_view data) {
|
||||||
std::stringstream in;
|
std::stringstream in;
|
||||||
in.write((const char*) data.data(), data.size());
|
in.write((const char*) data.data(), data.size());
|
||||||
|
|
||||||
@@ -821,7 +809,7 @@ std::u8string compressLinear(const std::u8string& data) {
|
|||||||
return *(std::u8string*) &outString;
|
return *(std::u8string*) &outString;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::u8string unCompressLinear(const std::u8string& data) {
|
std::u8string unCompressLinear(std::u8string_view data) {
|
||||||
std::stringstream in;
|
std::stringstream in;
|
||||||
in.write((const char*) data.data(), data.size());
|
in.write((const char*) data.data(), data.size());
|
||||||
|
|
||||||
@@ -836,7 +824,123 @@ std::u8string unCompressLinear(const std::u8string& data) {
|
|||||||
return *(std::u8string*) &outString;
|
return *(std::u8string*) &outString;
|
||||||
}
|
}
|
||||||
|
|
||||||
PreparedNodeState::PreparedNodeState(const std::string_view modid, const js::object& profile) {
|
Hash_t ResourceFile::calcHash(const char8_t* data, size_t size) {
|
||||||
|
return sha2::sha256((const uint8_t*) data, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t HeadlessNodeState::Header::addModel(AssetsModel id) {
|
||||||
|
auto iter = std::find(Models.begin(), Models.end(), id);
|
||||||
|
if(iter == Models.end()) {
|
||||||
|
Models.push_back(id);
|
||||||
|
return Models.size() - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return iter - Models.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
void HeadlessNodeState::Header::load(std::u8string_view data) {
|
||||||
|
Models.clear();
|
||||||
|
if(data.empty())
|
||||||
|
return;
|
||||||
|
if(data.size() % sizeof(AssetsModel) != 0)
|
||||||
|
MAKE_ERROR("Invalid nodestate header size");
|
||||||
|
|
||||||
|
const size_t count = data.size() / sizeof(AssetsModel);
|
||||||
|
Models.resize(count);
|
||||||
|
std::memcpy(Models.data(), data.data(), data.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
ResourceHeader HeadlessNodeState::Header::dump() const {
|
||||||
|
ResourceHeader rh;
|
||||||
|
rh.reserve(Models.size() * sizeof(AssetsModel));
|
||||||
|
|
||||||
|
for(AssetsModel id : Models) {
|
||||||
|
rh += std::u8string_view((const char8_t*) &id, sizeof(AssetsModel));
|
||||||
|
}
|
||||||
|
|
||||||
|
return rh;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t HeadlessModel::Header::addModel(AssetsModel id) {
|
||||||
|
auto iter = std::find(Models.begin(), Models.end(), id);
|
||||||
|
if(iter == Models.end()) {
|
||||||
|
Models.push_back(id);
|
||||||
|
return Models.size() - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return iter - Models.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t HeadlessModel::Header::addTexturePipeline(std::vector<uint8_t> pipeline) {
|
||||||
|
TexturePipelines.push_back(std::move(pipeline));
|
||||||
|
return TexturePipelines.size() - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HeadlessModel::Header::load(std::u8string_view data) {
|
||||||
|
Models.clear();
|
||||||
|
TexturePipelines.clear();
|
||||||
|
if(data.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
TOS::ByteBuffer buffer(data.size(), reinterpret_cast<const uint8_t*>(data.data()));
|
||||||
|
auto reader = buffer.reader();
|
||||||
|
|
||||||
|
uint16_t modelCount = reader.readUInt16();
|
||||||
|
Models.reserve(modelCount);
|
||||||
|
for(uint16_t i = 0; i < modelCount; ++i)
|
||||||
|
Models.push_back(reader.readUInt32());
|
||||||
|
|
||||||
|
uint16_t texCount = reader.readUInt16();
|
||||||
|
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)
|
||||||
|
MAKE_ERROR("Invalid model header size");
|
||||||
|
TexturePipelines.emplace_back(pipe.begin(), pipe.end());
|
||||||
|
}
|
||||||
|
} catch(const std::exception&) {
|
||||||
|
MAKE_ERROR("Invalid model header");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ResourceHeader HeadlessModel::Header::dump() const {
|
||||||
|
TOS::ByteBuffer rh;
|
||||||
|
|
||||||
|
{
|
||||||
|
uint32_t fullSize = 0;
|
||||||
|
for(const auto& vector : TexturePipelines)
|
||||||
|
fullSize += vector.size();
|
||||||
|
rh.reserve(2 + Models.size() * sizeof(AssetsModel) + 2 + 4 * TexturePipelines.size() + fullSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
TOS::ByteBuffer::Writer wr;
|
||||||
|
wr << uint16_t(Models.size());
|
||||||
|
for(AssetsModel id : Models)
|
||||||
|
wr << id;
|
||||||
|
|
||||||
|
wr << uint16_t(TexturePipelines.size());
|
||||||
|
for(const auto& pipe : TexturePipelines) {
|
||||||
|
wr << uint32_t(pipe.size());
|
||||||
|
wr << pipe;
|
||||||
|
}
|
||||||
|
|
||||||
|
TOS::ByteBuffer buff = wr.complite();
|
||||||
|
|
||||||
|
return std::u8string((const char8_t*) buff.data(), buff.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
ResourceHeader HeadlessNodeState::parse(const js::object& profile, const std::function<AssetsModel(const std::string_view model)>& modelResolver) {
|
||||||
|
Header header;
|
||||||
|
|
||||||
|
std::function<uint16_t(const std::string_view model)> headerResolver =
|
||||||
|
[&](const std::string_view model) -> uint16_t {
|
||||||
|
AssetsModel id = modelResolver(model);
|
||||||
|
return header.addModel(id);
|
||||||
|
};
|
||||||
|
|
||||||
for(auto& [condition, variability] : profile) {
|
for(auto& [condition, variability] : profile) {
|
||||||
// Распарсить условие
|
// Распарсить условие
|
||||||
uint16_t node = parseCondition(condition);
|
uint16_t node = parseCondition(condition);
|
||||||
@@ -849,39 +953,33 @@ PreparedNodeState::PreparedNodeState(const std::string_view modid, const js::obj
|
|||||||
if(variability.is_array()) {
|
if(variability.is_array()) {
|
||||||
// Варианты условия
|
// Варианты условия
|
||||||
for(const js::value& model : variability.as_array()) {
|
for(const js::value& model : variability.as_array()) {
|
||||||
models.push_back(parseModel(modid, model.as_object()));
|
models.push_back(parseModel(model.as_object(), headerResolver));
|
||||||
}
|
}
|
||||||
|
|
||||||
HasVariability = true;
|
HasVariability = true;
|
||||||
} else if (variability.is_object()) {
|
} else if (variability.is_object()) {
|
||||||
// Один список моделей на условие
|
// Один список моделей на условие
|
||||||
models.push_back(parseModel(modid, variability.as_object()));
|
models.push_back(parseModel(variability.as_object(), headerResolver));
|
||||||
} else {
|
} else {
|
||||||
MAKE_ERROR("Условию должен соответствовать список или объект");
|
MAKE_ERROR("Условию должен соответствовать список или объект");
|
||||||
}
|
}
|
||||||
|
|
||||||
Routes.emplace_back(node, std::move(models));
|
Routes.emplace_back(node, std::move(models));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return header.dump();
|
||||||
}
|
}
|
||||||
|
|
||||||
PreparedNodeState::PreparedNodeState(const std::string_view modid, const sol::table& profile) {
|
ResourceHeader HeadlessNodeState::parse(const sol::table& profile, const std::function<AssetsModel(const std::string_view model)>& modelResolver) {
|
||||||
|
return std::u8string();
|
||||||
}
|
}
|
||||||
|
|
||||||
PreparedNodeState::PreparedNodeState(const std::u8string_view data) {
|
void HeadlessNodeState::load(const std::u8string_view data) {
|
||||||
Net::LinearReader lr(data);
|
Net::LinearReader lr(data);
|
||||||
|
|
||||||
lr.read<uint16_t>();
|
lr.read<uint16_t>();
|
||||||
|
|
||||||
uint16_t size;
|
uint16_t size;
|
||||||
lr >> size;
|
|
||||||
|
|
||||||
LocalToModel.reserve(size);
|
|
||||||
for(int counter = 0; counter < size; counter++) {
|
|
||||||
AssetsModel modelId;
|
|
||||||
lr >> modelId;
|
|
||||||
LocalToModel.push_back(modelId);
|
|
||||||
}
|
|
||||||
|
|
||||||
lr >> size;
|
lr >> size;
|
||||||
Nodes.reserve(size);
|
Nodes.reserve(size);
|
||||||
@@ -948,8 +1046,11 @@ PreparedNodeState::PreparedNodeState(const std::u8string_view data) {
|
|||||||
for(int counter4 = 0; counter4 < transformsSize; counter4++) {
|
for(int counter4 = 0; counter4 < transformsSize; counter4++) {
|
||||||
Transformation tr;
|
Transformation tr;
|
||||||
tr.Op = Transformation::EnumTransform(lr.read<uint8_t>());
|
tr.Op = Transformation::EnumTransform(lr.read<uint8_t>());
|
||||||
|
tr.Value = lr.read<float>();
|
||||||
mod2.Transforms.push_back(tr);
|
mod2.Transforms.push_back(tr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mod.Models.push_back(std::move(mod2));
|
||||||
}
|
}
|
||||||
|
|
||||||
mod.UVLock = lr.read<uint8_t>();
|
mod.UVLock = lr.read<uint8_t>();
|
||||||
@@ -961,6 +1062,7 @@ PreparedNodeState::PreparedNodeState(const std::u8string_view data) {
|
|||||||
for(int counter3 = 0; counter3 < transformsSize; counter3++) {
|
for(int counter3 = 0; counter3 < transformsSize; counter3++) {
|
||||||
Transformation tr;
|
Transformation tr;
|
||||||
tr.Op = Transformation::EnumTransform(lr.read<uint8_t>());
|
tr.Op = Transformation::EnumTransform(lr.read<uint8_t>());
|
||||||
|
tr.Value = lr.read<float>();
|
||||||
mod.Transforms.push_back(tr);
|
mod.Transforms.push_back(tr);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -976,28 +1078,25 @@ PreparedNodeState::PreparedNodeState(const std::u8string_view data) {
|
|||||||
for(int counter3 = 0; counter3 < transformsSize; counter3++) {
|
for(int counter3 = 0; counter3 < transformsSize; counter3++) {
|
||||||
Transformation tr;
|
Transformation tr;
|
||||||
tr.Op = Transformation::EnumTransform(lr.read<uint8_t>());
|
tr.Op = Transformation::EnumTransform(lr.read<uint8_t>());
|
||||||
|
tr.Value = lr.read<float>();
|
||||||
mod.Transforms.push_back(tr);
|
mod.Transforms.push_back(tr);
|
||||||
}
|
}
|
||||||
|
|
||||||
variants.emplace_back(weight, std::move(mod));
|
variants.emplace_back(weight, std::move(mod));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Routes.emplace_back(nodeId, std::move(variants));
|
||||||
}
|
}
|
||||||
|
|
||||||
lr.checkUnreaded();
|
lr.checkUnreaded();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::u8string PreparedNodeState::dump() const {
|
std::u8string HeadlessNodeState::dump() const {
|
||||||
Net::Packet result;
|
Net::Packet result;
|
||||||
|
|
||||||
// ResourceToLocalId
|
const char magic[] = "bn";
|
||||||
assert(LocalToModelKD.size() < (1 << 16));
|
result.write(reinterpret_cast<const std::byte*>(magic), 2);
|
||||||
assert(LocalToModelKD.size() == LocalToModel.size());
|
|
||||||
result << uint16_t(LocalToModel.size());
|
|
||||||
|
|
||||||
for(AssetsModel modelId : LocalToModel) {
|
|
||||||
result << modelId;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Nodes
|
// Nodes
|
||||||
assert(Nodes.size() < (1 << 16));
|
assert(Nodes.size() < (1 << 16));
|
||||||
@@ -1070,7 +1169,7 @@ std::u8string PreparedNodeState::dump() const {
|
|||||||
return result.complite();
|
return result.complite();
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t PreparedNodeState::parseCondition(const std::string_view expression) {
|
uint16_t HeadlessNodeState::parseCondition(const std::string_view expression) {
|
||||||
enum class EnumTokenKind {
|
enum class EnumTokenKind {
|
||||||
LParen, RParen,
|
LParen, RParen,
|
||||||
Plus, Minus, Star, Slash, Percent,
|
Plus, Minus, Star, Slash, Percent,
|
||||||
@@ -1079,6 +1178,10 @@ uint16_t PreparedNodeState::parseCondition(const std::string_view expression) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
std::vector<std::variant<EnumTokenKind, std::string_view, int, uint16_t>> tokens;
|
std::vector<std::variant<EnumTokenKind, std::string_view, int, uint16_t>> tokens;
|
||||||
|
|
||||||
|
if(expression.empty())
|
||||||
|
tokens.push_back(int(1));
|
||||||
|
|
||||||
ssize_t pos = 0;
|
ssize_t pos = 0;
|
||||||
auto skipWS = [&](){ while(pos<expression.size() && std::isspace((unsigned char) expression[pos])) ++pos; };
|
auto skipWS = [&](){ while(pos<expression.size() && std::isspace((unsigned char) expression[pos])) ++pos; };
|
||||||
|
|
||||||
@@ -1088,9 +1191,9 @@ uint16_t PreparedNodeState::parseCondition(const std::string_view expression) {
|
|||||||
char c = expression[pos];
|
char c = expression[pos];
|
||||||
|
|
||||||
// Числа
|
// Числа
|
||||||
if(std::isdigit(c)) {
|
if(std::isdigit(static_cast<unsigned char>(c))) {
|
||||||
ssize_t npos = pos;
|
ssize_t npos = pos;
|
||||||
for(; npos < expression.size() && std::isdigit(expression[npos]); npos++);
|
for(; npos < expression.size() && std::isdigit(static_cast<unsigned char>(expression[npos])); npos++);
|
||||||
int value;
|
int value;
|
||||||
std::string_view value_view = expression.substr(pos, npos-pos);
|
std::string_view value_view = expression.substr(pos, npos-pos);
|
||||||
auto [partial_ptr, partial_ec] = std::from_chars(value_view.data(), value_view.data() + value_view.size(), value);
|
auto [partial_ptr, partial_ec] = std::from_chars(value_view.data(), value_view.data() + value_view.size(), value);
|
||||||
@@ -1102,15 +1205,20 @@ uint16_t PreparedNodeState::parseCondition(const std::string_view expression) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tokens.push_back(value);
|
tokens.push_back(value);
|
||||||
|
pos = npos - 1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Переменные
|
// Переменные
|
||||||
if(std::isalpha(c) || c == ':') {
|
if(std::isalpha(static_cast<unsigned char>(c)) || c == '_' || c == ':') {
|
||||||
ssize_t npos = pos;
|
ssize_t npos = pos;
|
||||||
for(; npos < expression.size() && std::isalpha(expression[npos]); npos++);
|
for(; npos < expression.size(); npos++) {
|
||||||
|
char ch = expression[npos];
|
||||||
|
if(!std::isalnum(static_cast<unsigned char>(ch)) && ch != '_' && ch != ':')
|
||||||
|
break;
|
||||||
|
}
|
||||||
std::string_view value = expression.substr(pos, npos-pos);
|
std::string_view value = expression.substr(pos, npos-pos);
|
||||||
pos += value.size();
|
pos = npos - 1;
|
||||||
if(value == "true")
|
if(value == "true")
|
||||||
tokens.push_back(1);
|
tokens.push_back(1);
|
||||||
else if(value == "false")
|
else if(value == "false")
|
||||||
@@ -1121,7 +1229,7 @@ uint16_t PreparedNodeState::parseCondition(const std::string_view expression) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Двойные операторы
|
// Двойные операторы
|
||||||
if(pos-1 < expression.size()) {
|
if(pos + 1 < expression.size()) {
|
||||||
char n = expression[pos+1];
|
char n = expression[pos+1];
|
||||||
|
|
||||||
if(c == '<' && n == '=') {
|
if(c == '<' && n == '=') {
|
||||||
@@ -1145,21 +1253,22 @@ uint16_t PreparedNodeState::parseCondition(const std::string_view expression) {
|
|||||||
|
|
||||||
// Операторы
|
// Операторы
|
||||||
switch(c) {
|
switch(c) {
|
||||||
case '(': tokens.push_back(EnumTokenKind::LParen);
|
case '(': tokens.push_back(EnumTokenKind::LParen); break;
|
||||||
case ')': tokens.push_back(EnumTokenKind::RParen);
|
case ')': tokens.push_back(EnumTokenKind::RParen); break;
|
||||||
case '+': tokens.push_back(EnumTokenKind::Plus);
|
case '+': tokens.push_back(EnumTokenKind::Plus); break;
|
||||||
case '-': tokens.push_back(EnumTokenKind::Minus);
|
case '-': tokens.push_back(EnumTokenKind::Minus); break;
|
||||||
case '*': tokens.push_back(EnumTokenKind::Star);
|
case '*': tokens.push_back(EnumTokenKind::Star); break;
|
||||||
case '/': tokens.push_back(EnumTokenKind::Slash);
|
case '/': tokens.push_back(EnumTokenKind::Slash); break;
|
||||||
case '%': tokens.push_back(EnumTokenKind::Percent);
|
case '%': tokens.push_back(EnumTokenKind::Percent); break;
|
||||||
case '!': tokens.push_back(EnumTokenKind::Not);
|
case '!': tokens.push_back(EnumTokenKind::Not); break;
|
||||||
case '&': tokens.push_back(EnumTokenKind::And);
|
case '&': tokens.push_back(EnumTokenKind::And); break;
|
||||||
case '|': tokens.push_back(EnumTokenKind::Or);
|
case '|': tokens.push_back(EnumTokenKind::Or); break;
|
||||||
case '<': tokens.push_back(EnumTokenKind::LT);
|
case '<': tokens.push_back(EnumTokenKind::LT); break;
|
||||||
case '>': tokens.push_back(EnumTokenKind::GT);
|
case '>': tokens.push_back(EnumTokenKind::GT); break;
|
||||||
|
default:
|
||||||
|
MAKE_ERROR("Недопустимый символ: " << c);
|
||||||
}
|
}
|
||||||
|
continue;
|
||||||
MAKE_ERROR("Недопустимый символ: " << c);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1344,6 +1453,7 @@ uint16_t PreparedNodeState::parseCondition(const std::string_view expression) {
|
|||||||
bin.rhs = *nodeId;
|
bin.rhs = *nodeId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
node.v = bin;
|
||||||
Nodes.emplace_back(std::move(node));
|
Nodes.emplace_back(std::move(node));
|
||||||
assert(Nodes.size() < std::pow(2, 16)-64);
|
assert(Nodes.size() < std::pow(2, 16)-64);
|
||||||
leftToken = uint16_t(Nodes.size()-1);
|
leftToken = uint16_t(Nodes.size()-1);
|
||||||
@@ -1371,55 +1481,11 @@ uint16_t PreparedNodeState::parseCondition(const std::string_view expression) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return lambdaParse(0);
|
return lambdaParse(0);
|
||||||
|
|
||||||
// std::unordered_map<std::string, int> vars;
|
|
||||||
// std::function<int(uint16_t)> lambdaCalcNode = [&](uint16_t nodeId) -> int {
|
|
||||||
// const Node& node = Nodes[nodeId];
|
|
||||||
// if(const Node::Num* value = std::get_if<Node::Num>(&node.v)) {
|
|
||||||
// return value->v;
|
|
||||||
// } else if(const Node::Var* value = std::get_if<Node::Var>(&node.v)) {
|
|
||||||
// auto iter = vars.find(value->name);
|
|
||||||
// if(iter == vars.end())
|
|
||||||
// MAKE_ERROR("Неопознанное состояние");
|
|
||||||
|
|
||||||
// return iter->second;
|
|
||||||
// } else if(const Node::Unary* value = std::get_if<Node::Unary>(&node.v)) {
|
|
||||||
// int rNodeValue = lambdaCalcNode(value->rhs);
|
|
||||||
// switch(value->op) {
|
|
||||||
// case Op::Not: return !rNodeValue;
|
|
||||||
// case Op::Pos: return +rNodeValue;
|
|
||||||
// case Op::Neg: return -rNodeValue;
|
|
||||||
// default:
|
|
||||||
// std::unreachable();
|
|
||||||
// }
|
|
||||||
// } else if(const Node::Binary* value = std::get_if<Node::Binary>(&node.v)) {
|
|
||||||
// int lNodeValue = lambdaCalcNode(value->lhs);
|
|
||||||
// int rNodeValue = lambdaCalcNode(value->rhs);
|
|
||||||
|
|
||||||
// switch(value->op) {
|
|
||||||
// case Op::Add: return lNodeValue+rNodeValue;
|
|
||||||
// case Op::Sub: return lNodeValue-rNodeValue;
|
|
||||||
// case Op::Mul: return lNodeValue*rNodeValue;
|
|
||||||
// case Op::Div: return lNodeValue/rNodeValue;
|
|
||||||
// case Op::Mod: return lNodeValue%rNodeValue;
|
|
||||||
// case Op::LT: return lNodeValue<rNodeValue;
|
|
||||||
// case Op::LE: return lNodeValue<=rNodeValue;
|
|
||||||
// case Op::GT: return lNodeValue>rNodeValue;
|
|
||||||
// case Op::GE: return lNodeValue>=rNodeValue;
|
|
||||||
// case Op::EQ: return lNodeValue==rNodeValue;
|
|
||||||
// case Op::NE: return lNodeValue!=rNodeValue;
|
|
||||||
// case Op::And: return lNodeValue&&rNodeValue;
|
|
||||||
// case Op::Or: return lNodeValue||rNodeValue;
|
|
||||||
// default:
|
|
||||||
// std::unreachable();
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// std::unreachable();
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair<float, std::variant<PreparedNodeState::Model, PreparedNodeState::VectorModel>> PreparedNodeState::parseModel(const std::string_view modid, const js::object& obj) {
|
std::pair<float, std::variant<HeadlessNodeState::Model, HeadlessNodeState::VectorModel>>
|
||||||
|
HeadlessNodeState::parseModel(const js::object& obj, const std::function<uint16_t(const std::string_view model)>& modelResolver)
|
||||||
|
{
|
||||||
// ModelToLocalId
|
// ModelToLocalId
|
||||||
|
|
||||||
bool uvlock;
|
bool uvlock;
|
||||||
@@ -1444,22 +1510,7 @@ std::pair<float, std::variant<PreparedNodeState::Model, PreparedNodeState::Vecto
|
|||||||
Model result;
|
Model result;
|
||||||
result.UVLock = false;
|
result.UVLock = false;
|
||||||
result.Transforms = std::move(transforms);
|
result.Transforms = std::move(transforms);
|
||||||
|
result.Id = modelResolver(*model_key);
|
||||||
auto [domain, key] = parseDomainKey((std::string) *model_key, modid);
|
|
||||||
|
|
||||||
uint16_t resId = 0;
|
|
||||||
for(auto& [lDomain, lKey] : LocalToModelKD) {
|
|
||||||
if(lDomain == domain && lKey == key)
|
|
||||||
break;
|
|
||||||
|
|
||||||
resId++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(resId == LocalToModelKD.size()) {
|
|
||||||
LocalToModelKD.emplace_back(domain, key);
|
|
||||||
}
|
|
||||||
|
|
||||||
result.Id = resId;
|
|
||||||
|
|
||||||
return {weight, result};
|
return {weight, result};
|
||||||
} else if(model.is_array()) {
|
} else if(model.is_array()) {
|
||||||
@@ -1480,21 +1531,7 @@ std::pair<float, std::variant<PreparedNodeState::Model, PreparedNodeState::Vecto
|
|||||||
subModel.Transforms = parseTransormations(transformations_val->as_array());
|
subModel.Transforms = parseTransormations(transformations_val->as_array());
|
||||||
}
|
}
|
||||||
|
|
||||||
auto [domain, key] = parseDomainKey((std::string) js_obj.at("model").as_string(), modid);
|
subModel.Id = modelResolver((std::string) js_obj.at("model").as_string());
|
||||||
|
|
||||||
uint16_t resId = 0;
|
|
||||||
for(auto& [lDomain, lKey] : LocalToModelKD) {
|
|
||||||
if(lDomain == domain && lKey == key)
|
|
||||||
break;
|
|
||||||
|
|
||||||
resId++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(resId == LocalToModelKD.size()) {
|
|
||||||
LocalToModelKD.emplace_back(domain, key);
|
|
||||||
}
|
|
||||||
|
|
||||||
subModel.Id = resId;
|
|
||||||
result.Models.push_back(std::move(subModel));
|
result.Models.push_back(std::move(subModel));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1504,7 +1541,7 @@ std::pair<float, std::variant<PreparedNodeState::Model, PreparedNodeState::Vecto
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<Transformation> PreparedNodeState::parseTransormations(const js::array& arr) {
|
std::vector<Transformation> HeadlessNodeState::parseTransormations(const js::array& arr) {
|
||||||
std::vector<Transformation> result;
|
std::vector<Transformation> result;
|
||||||
|
|
||||||
for(const js::value& js_value : arr) {
|
for(const js::value& js_value : arr) {
|
||||||
@@ -1543,7 +1580,34 @@ std::vector<Transformation> PreparedNodeState::parseTransormations(const js::arr
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
PreparedModel::PreparedModel(const std::string_view modid, const js::object& profile) {
|
ResourceHeader HeadlessModel::parse(
|
||||||
|
const js::object& profile,
|
||||||
|
const std::function<AssetsModel(const std::string_view model)>& modelResolver,
|
||||||
|
const std::function<std::vector<uint8_t>(const std::string_view texturePipelineSrc)>& textureResolver
|
||||||
|
) {
|
||||||
|
Header header;
|
||||||
|
|
||||||
|
std::function<uint16_t(const std::string_view model)> headerResolverModel =
|
||||||
|
[&](const std::string_view model) -> uint16_t {
|
||||||
|
AssetsModel id = modelResolver(model);
|
||||||
|
return header.addModel(id);
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unordered_map<std::string, uint32_t, detail::TSVHash, detail::TSVEq> textureToLocal;
|
||||||
|
|
||||||
|
std::function<uint16_t(const std::string_view texturePipelineSrc)> headerResolverTexture =
|
||||||
|
[&](const std::string_view texturePipelineSrc) -> uint16_t {
|
||||||
|
auto iter = textureToLocal.find(texturePipelineSrc);
|
||||||
|
if(iter != textureToLocal.end()) {
|
||||||
|
return iter->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t id = header.addTexturePipeline(textureResolver(texturePipelineSrc));
|
||||||
|
textureToLocal[(std::string) texturePipelineSrc] = id;
|
||||||
|
return id;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
if(profile.contains("gui_light")) {
|
if(profile.contains("gui_light")) {
|
||||||
std::string_view gui_light = profile.at("gui_light").as_string();
|
std::string_view gui_light = profile.at("gui_light").as_string();
|
||||||
|
|
||||||
@@ -1596,7 +1660,7 @@ PreparedModel::PreparedModel(const std::string_view modid, const js::object& pro
|
|||||||
const js::object& textures = textures_val->as_object();
|
const js::object& textures = textures_val->as_object();
|
||||||
|
|
||||||
for(const auto& [key, value] : textures) {
|
for(const auto& [key, value] : textures) {
|
||||||
Textures[key] = compileTexturePipeline((std::string) value.as_string(), modid);
|
Textures[key] = headerResolverTexture(value.as_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1743,36 +1807,42 @@ PreparedModel::PreparedModel(const std::string_view modid, const js::object& pro
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(boost::system::result<const js::value&> subModels_val = profile.try_at("sub_models")) {
|
if(boost::system::result<const js::value&> submodels_val = profile.try_at("sub_models")) {
|
||||||
const js::array& subModels = subModels_val->as_array();
|
const js::array& submodels = submodels_val->as_array();
|
||||||
|
SubModels.reserve(submodels.size());
|
||||||
|
|
||||||
for(const js::value& sub_val : subModels) {
|
for(const js::value& value : submodels) {
|
||||||
SubModel result;
|
if(const auto model_key = value.try_as_string()) {
|
||||||
|
SubModels.emplace_back(headerResolverModel(*model_key), std::nullopt);
|
||||||
if(auto path = sub_val.try_as_string()) {
|
|
||||||
auto [domain, key] = parseDomainKey((std::string) path.value(), modid);
|
|
||||||
result.Domain = std::move(domain);
|
|
||||||
result.Key = std::move(key);
|
|
||||||
} else {
|
} else {
|
||||||
const js::object& sub = sub_val.as_object();
|
const js::object& obj = value.as_object();
|
||||||
auto [domain, key] = parseDomainKey((std::string) sub.at("path").as_string(), modid);
|
const std::string model_key_str = (std::string) obj.at("model").as_string();
|
||||||
result.Domain = std::move(domain);
|
|
||||||
result.Key = std::move(key);
|
|
||||||
|
|
||||||
if(boost::system::result<const js::value&> scene_val = profile.try_at("scene"))
|
std::optional<uint16_t> scene;
|
||||||
result.Scene = scene_val->to_number<uint16_t>();
|
if(const auto scene_val = obj.try_at("scene")) {
|
||||||
|
scene = static_cast<uint16_t>(scene_val->to_number<int>());
|
||||||
|
}
|
||||||
|
|
||||||
|
SubModels.emplace_back(headerResolverModel(model_key_str), scene);
|
||||||
}
|
}
|
||||||
|
|
||||||
SubModels.emplace_back(std::move(result));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Заголовок
|
||||||
|
TOS::ByteBuffer rh;
|
||||||
|
|
||||||
|
return header.dump();
|
||||||
}
|
}
|
||||||
|
|
||||||
PreparedModel::PreparedModel(const std::string_view modid, const sol::table& profile) {
|
ResourceHeader HeadlessModel::parse(
|
||||||
|
const sol::table& profile,
|
||||||
|
const std::function<AssetsModel(const std::string_view model)>& modelResolver,
|
||||||
|
const std::function<std::vector<uint8_t>(const std::string_view texturePipelineSrc)>& textureResolver
|
||||||
|
) {
|
||||||
std::unreachable();
|
std::unreachable();
|
||||||
}
|
}
|
||||||
|
|
||||||
PreparedModel::PreparedModel(const std::u8string& data) {
|
void HeadlessModel::load(const std::u8string_view data) {
|
||||||
Net::LinearReader lr(data);
|
Net::LinearReader lr(data);
|
||||||
|
|
||||||
lr.read<uint16_t>();
|
lr.read<uint16_t>();
|
||||||
@@ -1819,17 +1889,10 @@ PreparedModel::PreparedModel(const std::u8string& data) {
|
|||||||
for(int counter = 0; counter < size; counter++) {
|
for(int counter = 0; counter < size; counter++) {
|
||||||
std::string tkey;
|
std::string tkey;
|
||||||
lr >> tkey;
|
lr >> tkey;
|
||||||
TexturePipeline pipe;
|
uint16_t id;
|
||||||
|
lr >> id;
|
||||||
|
|
||||||
uint16_t size;
|
Textures.insert({tkey, id});
|
||||||
lr >> size;
|
|
||||||
pipe.BinTextures.reserve(size);
|
|
||||||
for(int iter = 0; iter < size; iter++)
|
|
||||||
pipe.BinTextures.push_back(lr.read<ResourceId>());
|
|
||||||
|
|
||||||
lr >> (std::string&) pipe.Pipeline;
|
|
||||||
|
|
||||||
CompiledTextures.insert({tkey, std::move(pipe)});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lr >> size;
|
lr >> size;
|
||||||
@@ -1886,7 +1949,7 @@ PreparedModel::PreparedModel(const std::u8string& data) {
|
|||||||
SubModels.reserve(size8);
|
SubModels.reserve(size8);
|
||||||
for(int counter = 0; counter < size8; counter++) {
|
for(int counter = 0; counter < size8; counter++) {
|
||||||
SubModel sub;
|
SubModel sub;
|
||||||
lr >> sub.Domain >> sub.Key;
|
lr >> sub.Id;
|
||||||
uint16_t val = lr.read<uint16_t>();
|
uint16_t val = lr.read<uint16_t>();
|
||||||
if(val != uint16_t(-1)) {
|
if(val != uint16_t(-1)) {
|
||||||
sub.Scene = val;
|
sub.Scene = val;
|
||||||
@@ -1898,7 +1961,7 @@ PreparedModel::PreparedModel(const std::u8string& data) {
|
|||||||
lr.checkUnreaded();
|
lr.checkUnreaded();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::u8string PreparedModel::dump() const {
|
std::u8string HeadlessModel::dump() const {
|
||||||
Net::Packet result;
|
Net::Packet result;
|
||||||
|
|
||||||
result << 'b' << 'm';
|
result << 'b' << 'm';
|
||||||
@@ -1937,19 +2000,9 @@ std::u8string PreparedModel::dump() const {
|
|||||||
assert(Textures.size() < (1 << 16));
|
assert(Textures.size() < (1 << 16));
|
||||||
result << uint16_t(Textures.size());
|
result << uint16_t(Textures.size());
|
||||||
|
|
||||||
assert(CompiledTextures.size() == Textures.size());
|
for(const auto& [tkey, id] : Textures) {
|
||||||
|
|
||||||
for(const auto& [tkey, dk] : CompiledTextures) {
|
|
||||||
assert(tkey.size() < 32);
|
assert(tkey.size() < 32);
|
||||||
result << tkey;
|
result << tkey << id;
|
||||||
|
|
||||||
assert(dk.BinTextures.size() < 512);
|
|
||||||
result << uint16_t(dk.BinTextures.size());
|
|
||||||
for(size_t iter = 0; iter < dk.BinTextures.size(); iter++) {
|
|
||||||
result << dk.BinTextures[iter];
|
|
||||||
}
|
|
||||||
|
|
||||||
result << (const std::string&) dk.Pipeline;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(Cuboids.size() < (1 << 16));
|
assert(Cuboids.size() < (1 << 16));
|
||||||
@@ -1988,10 +2041,7 @@ std::u8string PreparedModel::dump() const {
|
|||||||
assert(SubModels.size() < 256);
|
assert(SubModels.size() < 256);
|
||||||
result << uint8_t(SubModels.size());
|
result << uint8_t(SubModels.size());
|
||||||
for(const SubModel& model : SubModels) {
|
for(const SubModel& model : SubModels) {
|
||||||
assert(model.Domain.size() < 32);
|
result << model.Id;
|
||||||
assert(model.Key.size() < 32);
|
|
||||||
|
|
||||||
result << model.Domain << model.Key;
|
|
||||||
if(model.Scene)
|
if(model.Scene)
|
||||||
result << uint16_t(*model.Scene);
|
result << uint16_t(*model.Scene);
|
||||||
else
|
else
|
||||||
@@ -2001,37 +2051,6 @@ std::u8string PreparedModel::dump() const {
|
|||||||
return result.complite();
|
return result.complite();
|
||||||
}
|
}
|
||||||
|
|
||||||
PreparedGLTF::PreparedGLTF(const std::string_view modid, const js::object& gltf) {
|
|
||||||
// gltf
|
|
||||||
|
|
||||||
// Сцена по умолчанию
|
|
||||||
// Сцены -> Ноды
|
|
||||||
// Ноды -> Ноды, меши, матрицы, translation, rotation
|
|
||||||
// Меши -> Примитивы
|
|
||||||
// Примитивы -> Материал, вершинные данные
|
|
||||||
// Материалы -> текстуры
|
|
||||||
// Текстуры
|
|
||||||
// Буферы
|
|
||||||
}
|
|
||||||
|
|
||||||
PreparedGLTF::PreparedGLTF(const std::string_view modid, Resource glb) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
PreparedGLTF::PreparedGLTF(std::u8string_view data) {
|
|
||||||
|
|
||||||
// lr.checkUnreaded();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
std::u8string PreparedGLTF::dump() const {
|
|
||||||
std::unreachable();
|
|
||||||
}
|
|
||||||
|
|
||||||
void PreparedGLTF::load(std::u8string_view data) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Resource::InlineMMap {
|
struct Resource::InlineMMap {
|
||||||
boost::interprocess::file_mapping MMap;
|
boost::interprocess::file_mapping MMap;
|
||||||
boost::interprocess::mapped_region Region;
|
boost::interprocess::mapped_region Region;
|
||||||
@@ -2100,4 +2119,4 @@ Resource Resource::convertToMem() const {
|
|||||||
|
|
||||||
auto Resource::operator<=>(const Resource&) const = default;
|
auto Resource::operator<=>(const Resource&) const = default;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,45 @@
|
|||||||
|
|
||||||
namespace LV {
|
namespace LV {
|
||||||
|
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
// Позволяет использовать как std::string так и std::string_view в хэш таблицах
|
||||||
|
struct TSVHash {
|
||||||
|
using is_transparent = void;
|
||||||
|
|
||||||
|
size_t operator()(std::string_view sv) const noexcept {
|
||||||
|
return std::hash<std::string_view>{}(sv);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t operator()(const std::string& s) const noexcept {
|
||||||
|
return std::hash<std::string_view>{}(s);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Позволяет использовать как std::string так и std::string_view в хэш таблицах
|
||||||
|
struct TSVEq {
|
||||||
|
using is_transparent = void;
|
||||||
|
|
||||||
|
bool operator()(std::string_view a, std::string_view b) const noexcept {
|
||||||
|
return a == b;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator()(const std::string& a, std::string_view b) const noexcept {
|
||||||
|
return std::string_view(a) == b;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator()(std::string_view a, const std::string& b) const noexcept {
|
||||||
|
return a == std::string_view(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator()(const std::string& a, const std::string& b) const noexcept {
|
||||||
|
return a == b;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
namespace js = boost::json;
|
namespace js = boost::json;
|
||||||
|
|
||||||
namespace Pos {
|
namespace Pos {
|
||||||
@@ -469,13 +508,7 @@ struct VoxelCube {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct CompressedVoxels {
|
std::u8string compressVoxels(const std::vector<VoxelCube>& voxels, bool fast = true);
|
||||||
std::u8string Compressed;
|
|
||||||
// Уникальный сортированный список идентификаторов вокселей
|
|
||||||
std::vector<DefVoxelId> Defines;
|
|
||||||
};
|
|
||||||
|
|
||||||
CompressedVoxels compressVoxels(const std::vector<VoxelCube>& voxels, bool fast = true);
|
|
||||||
std::vector<VoxelCube> unCompressVoxels(const std::u8string& compressed);
|
std::vector<VoxelCube> unCompressVoxels(const std::u8string& compressed);
|
||||||
|
|
||||||
struct Node {
|
struct Node {
|
||||||
@@ -493,11 +526,20 @@ struct CompressedNodes {
|
|||||||
std::vector<DefNodeId> Defines;
|
std::vector<DefNodeId> Defines;
|
||||||
};
|
};
|
||||||
|
|
||||||
CompressedNodes compressNodes(const Node* nodes, bool fast = true);
|
std::u8string compressNodes(const Node* nodes, bool fast = true);
|
||||||
void unCompressNodes(const std::u8string& compressed, Node* ptr);
|
void unCompressNodes(std::u8string_view compressed, Node* ptr);
|
||||||
|
|
||||||
std::u8string compressLinear(const std::u8string& data);
|
std::u8string compressLinear(std::u8string_view data);
|
||||||
std::u8string unCompressLinear(const std::u8string& data);
|
std::u8string unCompressLinear(std::u8string_view data);
|
||||||
|
|
||||||
|
inline std::pair<std::string_view, std::string_view> parseDomainKey(const std::string_view value, const std::string_view defaultDomain = "core") {
|
||||||
|
size_t pos = value.find(':');
|
||||||
|
|
||||||
|
if(pos == std::string_view::npos)
|
||||||
|
return {defaultDomain, value};
|
||||||
|
else
|
||||||
|
return {value.substr(0, pos), value.substr(pos+1)};
|
||||||
|
}
|
||||||
|
|
||||||
inline std::pair<std::string, std::string> parseDomainKey(const std::string& value, const std::string_view defaultDomain = "core") {
|
inline std::pair<std::string, std::string> parseDomainKey(const std::string& value, const std::string_view defaultDomain = "core") {
|
||||||
auto regResult = TOS::Str::match(value, "(?:([\\w\\d_]+):)?([\\w\\d/_.]+)");
|
auto regResult = TOS::Str::match(value, "(?:([\\w\\d_]+):)?([\\w\\d/_.]+)");
|
||||||
@@ -511,35 +553,19 @@ inline std::pair<std::string, std::string> parseDomainKey(const std::string& val
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct PrecompiledTexturePipeline {
|
struct ResourceFile {
|
||||||
// Локальные идентификаторы пайплайна в домен+ключ
|
using Hash_t = std::array<uint8_t, 32>;
|
||||||
std::vector<std::pair<std::string, std::string>> Assets;
|
|
||||||
// Чистый код текстурных преобразований, локальные идентификаторы связаны с Assets
|
|
||||||
std::u8string Pipeline;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct TexturePipeline {
|
Hash_t Hash;
|
||||||
// Разыменованые идентификаторы
|
std::u8string Data;
|
||||||
std::vector<AssetsTexture> BinTextures;
|
|
||||||
// Чистый код текстурных преобразований, локальные идентификаторы связаны с BinTextures
|
|
||||||
std::u8string Pipeline;
|
|
||||||
|
|
||||||
bool operator==(const TexturePipeline& other) const {
|
static Hash_t calcHash(const char8_t* data, size_t size);
|
||||||
return BinTextures == other.BinTextures && Pipeline == other.Pipeline;
|
|
||||||
|
void calcHash() {
|
||||||
|
Hash = calcHash(Data.data(), Data.size());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Компилятор текстурных потоков
|
|
||||||
inline PrecompiledTexturePipeline compileTexturePipeline(const std::string &cmd, const std::string_view defaultDomain = "core") {
|
|
||||||
PrecompiledTexturePipeline result;
|
|
||||||
|
|
||||||
auto [domain, key] = parseDomainKey(cmd, defaultDomain);
|
|
||||||
|
|
||||||
result.Assets.emplace_back(domain, key);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct NodestateEntry {
|
struct NodestateEntry {
|
||||||
std::string Name;
|
std::string Name;
|
||||||
int Variability = 0; // Количество возможный значений состояния
|
int Variability = 0; // Количество возможный значений состояния
|
||||||
@@ -614,11 +640,13 @@ struct NodeStateInfo {
|
|||||||
int Variations = 0;
|
int Variations = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
using ResourceHeader = std::u8string;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Хранит распаршенное определение состояний нод.
|
Хранит распаршенное определение состояний нод.
|
||||||
Не привязано ни к какому окружению.
|
Не привязано ни к какому окружению.
|
||||||
*/
|
*/
|
||||||
struct PreparedNodeState {
|
struct HeadlessNodeState {
|
||||||
enum class Op {
|
enum class Op {
|
||||||
Add, Sub, Mul, Div, Mod,
|
Add, Sub, Mul, Div, Mod,
|
||||||
LT, LE, GT, GE, EQ, NE,
|
LT, LE, GT, GE, EQ, NE,
|
||||||
@@ -647,10 +675,14 @@ struct PreparedNodeState {
|
|||||||
std::vector<Transformation> Transforms;
|
std::vector<Transformation> Transforms;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Локальный идентификатор в именной ресурс
|
struct Header {
|
||||||
std::vector<std::pair<std::string, std::string>> LocalToModelKD;
|
std::vector<AssetsModel> Models;
|
||||||
// Локальный идентификатор в глобальный идентификатор
|
|
||||||
std::vector<AssetsModel> LocalToModel;
|
uint16_t addModel(AssetsModel id);
|
||||||
|
void load(std::u8string_view data);
|
||||||
|
ResourceHeader dump() const;
|
||||||
|
};
|
||||||
|
|
||||||
// Ноды выражений
|
// Ноды выражений
|
||||||
std::vector<Node> Nodes;
|
std::vector<Node> Nodes;
|
||||||
// Условия -> вариации модели + веса
|
// Условия -> вариации модели + веса
|
||||||
@@ -663,18 +695,33 @@ struct PreparedNodeState {
|
|||||||
>
|
>
|
||||||
, 1> Routes;
|
, 1> Routes;
|
||||||
|
|
||||||
PreparedNodeState(const std::string_view modid, const js::object& profile);
|
HeadlessNodeState() = default;
|
||||||
PreparedNodeState(const std::string_view modid, const sol::table& profile);
|
HeadlessNodeState(const HeadlessNodeState&) = default;
|
||||||
PreparedNodeState(const std::u8string_view data);
|
HeadlessNodeState(HeadlessNodeState&&) = default;
|
||||||
|
|
||||||
PreparedNodeState() = default;
|
HeadlessNodeState& operator=(const HeadlessNodeState&) = default;
|
||||||
PreparedNodeState(const PreparedNodeState&) = default;
|
HeadlessNodeState& operator=(HeadlessNodeState&&) = default;
|
||||||
PreparedNodeState(PreparedNodeState&&) = default;
|
|
||||||
|
|
||||||
PreparedNodeState& operator=(const PreparedNodeState&) = default;
|
/*
|
||||||
PreparedNodeState& operator=(PreparedNodeState&&) = default;
|
Парсит json формат с выделением все зависимостей в заголовок.
|
||||||
|
Требуется ресолвер идентификаторов моделей.
|
||||||
|
*/
|
||||||
|
ResourceHeader parse(const js::object& profile, const std::function<AssetsModel(const std::string_view model)>& modelResolver);
|
||||||
|
|
||||||
// Пишет в сжатый двоичный формат
|
/*
|
||||||
|
Парсит lua формат с выделением зависимостей в заголовок.
|
||||||
|
Требуется ресолвер идентификаторов моделей.
|
||||||
|
*/
|
||||||
|
ResourceHeader parse(const sol::table& profile, const std::function<AssetsModel(const std::string_view model)>& modelResolver);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Загружает ресурс из двоичного формата.
|
||||||
|
*/
|
||||||
|
void load(std::u8string_view data);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Транслирует в двоичный формат.
|
||||||
|
*/
|
||||||
std::u8string dump() const;
|
std::u8string dump() const;
|
||||||
|
|
||||||
// Если зависит от случайного распределения по миру
|
// Если зависит от случайного распределения по миру
|
||||||
@@ -713,13 +760,15 @@ struct PreparedNodeState {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
for(const auto& route : Routes)
|
||||||
|
lambda(route.first);
|
||||||
|
|
||||||
std::sort(variables.begin(), variables.end());
|
std::sort(variables.begin(), variables.end());
|
||||||
auto eraseIter = std::unique(variables.begin(), variables.end());
|
auto eraseIter = std::unique(variables.begin(), variables.end());
|
||||||
variables.erase(eraseIter, variables.end());
|
variables.erase(eraseIter, variables.end());
|
||||||
|
|
||||||
bool ok = false;
|
|
||||||
|
|
||||||
for(const std::string_view key : variables) {
|
for(const std::string_view key : variables) {
|
||||||
|
bool ok = false;
|
||||||
if(size_t pos = key.find(':'); pos != std::string::npos) {
|
if(size_t pos = key.find(':'); pos != std::string::npos) {
|
||||||
std::string_view state, value;
|
std::string_view state, value;
|
||||||
state = key.substr(0, pos);
|
state = key.substr(0, pos);
|
||||||
@@ -765,7 +814,6 @@ struct PreparedNodeState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
std::move_only_function<int32_t(uint16_t nodeId)> calcNode;
|
std::move_only_function<int32_t(uint16_t nodeId)> calcNode;
|
||||||
|
|
||||||
calcNode = [&](uint16_t nodeId) -> int32_t {
|
calcNode = [&](uint16_t nodeId) -> int32_t {
|
||||||
@@ -852,7 +900,7 @@ private:
|
|||||||
bool HasVariability = false;
|
bool HasVariability = false;
|
||||||
|
|
||||||
uint16_t parseCondition(const std::string_view condition);
|
uint16_t parseCondition(const std::string_view condition);
|
||||||
std::pair<float, std::variant<Model, VectorModel>> parseModel(const std::string_view modid, const js::object& obj);
|
std::pair<float, std::variant<Model, VectorModel>> parseModel(const js::object& obj, const std::function<uint16_t(const std::string_view model)>& modelResolver);
|
||||||
std::vector<Transformation> parseTransormations(const js::array& arr);
|
std::vector<Transformation> parseTransormations(const js::array& arr);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -864,13 +912,23 @@ enum class EnumFace {
|
|||||||
/*
|
/*
|
||||||
Парсит json модель
|
Парсит json модель
|
||||||
*/
|
*/
|
||||||
struct PreparedModel {
|
struct HeadlessModel {
|
||||||
enum class EnumGuiLight {
|
enum class EnumGuiLight {
|
||||||
Default
|
Default
|
||||||
};
|
};
|
||||||
|
|
||||||
std::optional<EnumGuiLight> GuiLight = EnumGuiLight::Default;
|
std::optional<EnumGuiLight> GuiLight = EnumGuiLight::Default;
|
||||||
std::optional<bool> AmbientOcclusion = false;
|
std::optional<bool> AmbientOcclusion = false;
|
||||||
|
|
||||||
|
struct Header {
|
||||||
|
std::vector<AssetsModel> Models;
|
||||||
|
std::vector<std::vector<uint8_t>> TexturePipelines;
|
||||||
|
|
||||||
|
uint16_t addModel(AssetsModel id);
|
||||||
|
uint16_t addTexturePipeline(std::vector<uint8_t> pipeline);
|
||||||
|
void load(std::u8string_view data);
|
||||||
|
ResourceHeader dump() const;
|
||||||
|
};
|
||||||
|
|
||||||
struct FullTransformation {
|
struct FullTransformation {
|
||||||
glm::vec3
|
glm::vec3
|
||||||
@@ -880,8 +938,7 @@ struct PreparedModel {
|
|||||||
};
|
};
|
||||||
|
|
||||||
std::unordered_map<std::string, FullTransformation> Display;
|
std::unordered_map<std::string, FullTransformation> Display;
|
||||||
std::unordered_map<std::string, PrecompiledTexturePipeline> Textures;
|
std::unordered_map<std::string, uint16_t> Textures;
|
||||||
std::unordered_map<std::string, TexturePipeline> CompiledTextures;
|
|
||||||
|
|
||||||
struct Cuboid {
|
struct Cuboid {
|
||||||
bool Shade;
|
bool Shade;
|
||||||
@@ -903,54 +960,68 @@ struct PreparedModel {
|
|||||||
std::vector<Cuboid> Cuboids;
|
std::vector<Cuboid> Cuboids;
|
||||||
|
|
||||||
struct SubModel {
|
struct SubModel {
|
||||||
std::string Domain, Key;
|
uint16_t Id;
|
||||||
std::optional<uint16_t> Scene;
|
std::optional<uint16_t> Scene;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::vector<SubModel> SubModels;
|
std::vector<SubModel> SubModels;
|
||||||
|
|
||||||
// Json
|
HeadlessModel() = default;
|
||||||
PreparedModel(const std::string_view modid, const js::object& profile);
|
HeadlessModel(const HeadlessModel&) = default;
|
||||||
PreparedModel(const std::string_view modid, const sol::table& profile);
|
HeadlessModel(HeadlessModel&&) = default;
|
||||||
PreparedModel(const std::u8string& data);
|
|
||||||
|
|
||||||
PreparedModel() = default;
|
HeadlessModel& operator=(const HeadlessModel&) = default;
|
||||||
PreparedModel(const PreparedModel&) = default;
|
HeadlessModel& operator=(HeadlessModel&&) = default;
|
||||||
PreparedModel(PreparedModel&&) = default;
|
|
||||||
|
|
||||||
PreparedModel& operator=(const PreparedModel&) = default;
|
/*
|
||||||
PreparedModel& operator=(PreparedModel&&) = default;
|
Парсит json формат с выделением все зависимостей в заголовок.
|
||||||
|
Требуется ресолвер идентификаторов моделей.
|
||||||
|
*/
|
||||||
|
ResourceHeader parse(
|
||||||
|
const js::object& profile,
|
||||||
|
const std::function<AssetsModel(const std::string_view model)>& modelResolver,
|
||||||
|
const std::function<std::vector<uint8_t>(const std::string_view texturePipelineSrc)>& textureResolver
|
||||||
|
);
|
||||||
|
|
||||||
// Пишет в сжатый двоичный формат
|
/*
|
||||||
std::u8string dump() const;
|
Парсит lua формат с выделением зависимостей в заголовок.
|
||||||
|
Требуется ресолвер идентификаторов моделей.
|
||||||
|
*/
|
||||||
|
ResourceHeader parse(
|
||||||
|
const sol::table& profile,
|
||||||
|
const std::function<AssetsModel(const std::string_view model)>& modelResolver,
|
||||||
|
const std::function<std::vector<uint8_t>(const std::string_view texturePipelineSrc)>& textureResolver
|
||||||
|
);
|
||||||
|
|
||||||
private:
|
/*
|
||||||
|
Загружает ресурс из двоичного формата.
|
||||||
|
*/
|
||||||
void load(std::u8string_view data);
|
void load(std::u8string_view data);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Транслирует в двоичный формат.
|
||||||
|
*/
|
||||||
|
std::u8string dump() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PreparedGLTF {
|
struct TexturePipeline {
|
||||||
std::vector<std::string> TextureKey;
|
std::vector<uint32_t> BinTextures;
|
||||||
std::unordered_map<std::string, PrecompiledTexturePipeline> Textures;
|
std::vector<uint8_t> Pipeline;
|
||||||
std::unordered_map<std::string, TexturePipeline> CompiledTextures;
|
|
||||||
std::vector<Vertex> Vertices;
|
|
||||||
|
|
||||||
|
bool operator==(const TexturePipeline& other) const {
|
||||||
|
return BinTextures == other.BinTextures && Pipeline == other.Pipeline;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
PreparedGLTF(const std::string_view modid, const js::object& gltf);
|
struct PreparedNodeState : public HeadlessNodeState {
|
||||||
PreparedGLTF(const std::string_view modid, Resource glb);
|
using HeadlessNodeState::Model;
|
||||||
PreparedGLTF(std::u8string_view data);
|
using HeadlessNodeState::VectorModel;
|
||||||
|
|
||||||
PreparedGLTF() = default;
|
std::vector<AssetsModel> LocalToModel;
|
||||||
PreparedGLTF(const PreparedGLTF&) = default;
|
|
||||||
PreparedGLTF(PreparedGLTF&&) = default;
|
|
||||||
|
|
||||||
PreparedGLTF& operator=(const PreparedGLTF&) = default;
|
PreparedNodeState() = default;
|
||||||
PreparedGLTF& operator=(PreparedGLTF&&) = default;
|
PreparedNodeState(std::u8string_view data) { load(data); }
|
||||||
|
PreparedNodeState(const std::u8string& data) { load(data); }
|
||||||
// Пишет в сжатый двоичный формат
|
|
||||||
std::u8string dump() const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
void load(std::u8string_view data);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
enum struct TexturePipelineCMD : uint8_t {
|
enum struct TexturePipelineCMD : uint8_t {
|
||||||
@@ -1031,16 +1102,17 @@ struct hash<LV::Hash_t> {
|
|||||||
|
|
||||||
template <>
|
template <>
|
||||||
struct hash<LV::TexturePipeline> {
|
struct hash<LV::TexturePipeline> {
|
||||||
std::size_t operator()(const LV::TexturePipeline& tp) const noexcept {
|
std::size_t operator()(const LV::TexturePipeline& pipe) const noexcept {
|
||||||
size_t seed = 0;
|
std::size_t v = 14695981039346656037ULL;
|
||||||
|
for (uint32_t id : pipe.BinTextures) {
|
||||||
for (const auto& tex : tp.BinTextures)
|
v ^= static_cast<std::size_t>(id);
|
||||||
seed ^= std::hash<LV::AssetsTexture>{}(tex) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
|
v *= 1099511628211ULL;
|
||||||
|
}
|
||||||
std::string_view sv(reinterpret_cast<const char*>(tp.Pipeline.data()), tp.Pipeline.size());
|
for (uint8_t byte : pipe.Pipeline) {
|
||||||
seed ^= std::hash<std::string_view>{}(sv) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
|
v ^= static_cast<std::size_t>(byte);
|
||||||
|
v *= 1099511628211ULL;
|
||||||
return seed;
|
}
|
||||||
|
return v;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
399
Src/Common/AssetsPreloader.cpp
Normal file
399
Src/Common/AssetsPreloader.cpp
Normal file
@@ -0,0 +1,399 @@
|
|||||||
|
#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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
293
Src/Common/AssetsPreloader.hpp
Normal file
293
Src/Common/AssetsPreloader.hpp
Normal file
@@ -0,0 +1,293 @@
|
|||||||
|
#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;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
272
Src/Common/IdProvider.hpp
Normal file
272
Src/Common/IdProvider.hpp
Normal file
@@ -0,0 +1,272 @@
|
|||||||
|
#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
|
||||||
483
Src/Common/Net2.cpp
Normal file
483
Src/Common/Net2.cpp
Normal file
@@ -0,0 +1,483 @@
|
|||||||
|
#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
|
||||||
227
Src/Common/Net2.hpp
Normal file
227
Src/Common/Net2.hpp
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
#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
|
||||||
@@ -77,111 +77,33 @@ enum struct L2System : uint8_t {
|
|||||||
Disconnect,
|
Disconnect,
|
||||||
Test_CAM_PYR_POS,
|
Test_CAM_PYR_POS,
|
||||||
BlockChange,
|
BlockChange,
|
||||||
ResourceRequest
|
ResourceRequest,
|
||||||
|
ReloadMods
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace ToClient {
|
enum struct ToClient : uint8_t {
|
||||||
|
Init, // Первый пакет от сервера
|
||||||
|
Disconnect, // Отключаем клиента
|
||||||
|
|
||||||
/*
|
AssetsBindDK, // Привязка AssetsId к домен+ключ
|
||||||
uint8_t+uint8_t
|
AssetsBindHH, // Привязка AssetsId к hash+header
|
||||||
0 - Системное
|
AssetsInitSend, // Начало отправки запрошенного клиентом ресурса
|
||||||
0 - Инициализация WorldId_c+ObjectPos
|
AssetsNextSend, // Продолжение отправки ресурса
|
||||||
1 - Отключение от сервера String(Причина)
|
|
||||||
2 - Привязка камеры к сущности EntityId_c
|
|
||||||
3 - Отвязка камеры
|
|
||||||
1 - Оповещение о доступном ресурсе
|
|
||||||
0 - Текстура TextureId_c+Hash
|
|
||||||
1 - Освобождение текстуры TextureId_c
|
|
||||||
2 - Звук SoundId_c+Hash
|
|
||||||
3 - Освобождение звука SoundId_c
|
|
||||||
4 - Модель ModelId_c+Hash
|
|
||||||
5 - Освобождение модели ModelId_c
|
|
||||||
253 - Инициирование передачи ресурса StreamId+ResType+ResId+Size+Hash
|
|
||||||
254 - Передача чанка данных StreamId+Size+Data
|
|
||||||
255 - Передача отменена StreamId
|
|
||||||
2 - Новые определения
|
|
||||||
0 - Мир DefWorldId_c+определение
|
|
||||||
1 - Освобождение мира DefWorldId_c
|
|
||||||
2 - Воксель DefVoxelId_c+определение
|
|
||||||
3 - Освобождение вокселя DefVoxelId_c
|
|
||||||
4 - Нода DefNodeId_c+определение
|
|
||||||
5 - Освобождение ноды DefNodeId_c
|
|
||||||
6 - Портал DefPortalId_c+определение
|
|
||||||
7 - Освобождение портала DefPortalId_c
|
|
||||||
8 - Сущность DefEntityId_c+определение
|
|
||||||
9 - Освобождение сущности DefEntityId_c
|
|
||||||
3 - Новый контент
|
|
||||||
0 - Мир, новый/изменён WorldId_c+...
|
|
||||||
1 - Мир/Удалён WorldId_c
|
|
||||||
2 - Портал, новый/изменён PortalId_c+...
|
|
||||||
3 - Портал/Удалён PortalId_c
|
|
||||||
4 - Сущность, новый/изменён EntityId_c+...
|
|
||||||
5 - Сущность/Удалёна EntityId_c
|
|
||||||
6 - Чанк/Воксели WorldId_c+GlobalChunk+...
|
|
||||||
7 - Чанк/Ноды WorldId_c+GlobalChunk+...
|
|
||||||
8 - Чанк/Призмы освещения WorldId_c+GlobalChunk+...
|
|
||||||
9 - Чанк/Удалён WorldId_c+GlobalChunk
|
|
||||||
|
|
||||||
|
DefinitionsFull, // Полная информация о профилях контента
|
||||||
|
DefinitionsUpdate, // Обновление и потеря профилей контента (воксели, ноды, сущности, миры, ...)
|
||||||
|
|
||||||
|
ChunkVoxels, // Обновление вокселей чанка
|
||||||
|
ChunkNodes, // Обновление нод чанка
|
||||||
|
ChunkLightPrism, //
|
||||||
|
RemoveRegion, // Удаление региона из зоны видимости
|
||||||
|
|
||||||
*/
|
Tick, // Новые или потерянные игровые объекты (миры, сущности), динамичные данные такта (положение сущностей)
|
||||||
|
|
||||||
// Первый уровень
|
TestLinkCameraToEntity, // Привязываем камеру к сущности
|
||||||
enum struct L1 : uint8_t {
|
TestUnlinkCamera, // Отвязываем от сущности
|
||||||
System,
|
|
||||||
Resource,
|
|
||||||
Definition,
|
|
||||||
Content
|
|
||||||
};
|
|
||||||
|
|
||||||
// Второй уровень
|
|
||||||
enum struct L2System : uint8_t {
|
|
||||||
Init,
|
|
||||||
Disconnect,
|
|
||||||
LinkCameraToEntity,
|
|
||||||
UnlinkCamera,
|
|
||||||
SyncTick
|
|
||||||
};
|
|
||||||
|
|
||||||
enum struct L2Resource : uint8_t {
|
|
||||||
Bind, // Привязка идентификаторов ресурсов к хешам
|
|
||||||
Lost,
|
|
||||||
InitResSend = 253,
|
|
||||||
ChunkSend
|
|
||||||
};
|
|
||||||
|
|
||||||
enum struct L2Definition : uint8_t {
|
|
||||||
World,
|
|
||||||
FreeWorld,
|
|
||||||
Voxel,
|
|
||||||
FreeVoxel,
|
|
||||||
Node,
|
|
||||||
FreeNode,
|
|
||||||
Portal,
|
|
||||||
FreePortal,
|
|
||||||
Entity,
|
|
||||||
FreeEntity,
|
|
||||||
FuncEntity,
|
|
||||||
FreeFuncEntity,
|
|
||||||
Item,
|
|
||||||
FreeItem
|
|
||||||
};
|
|
||||||
|
|
||||||
enum struct L2Content : uint8_t {
|
|
||||||
World,
|
|
||||||
RemoveWorld,
|
|
||||||
Portal,
|
|
||||||
RemovePortal,
|
|
||||||
Entity,
|
|
||||||
RemoveEntity,
|
|
||||||
ChunkVoxels,
|
|
||||||
ChunkNodes,
|
|
||||||
ChunkLightPrism,
|
|
||||||
RemoveRegion
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
1845
Src/Common/TexturePipelineProgram.cpp
Normal file
1845
Src/Common/TexturePipelineProgram.cpp
Normal file
File diff suppressed because it is too large
Load Diff
406
Src/Common/TexturePipelineProgram.hpp
Normal file
406
Src/Common/TexturePipelineProgram.hpp
Normal file
@@ -0,0 +1,406 @@
|
|||||||
|
#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);
|
||||||
|
};
|
||||||
@@ -4,6 +4,18 @@
|
|||||||
|
|
||||||
namespace LV::Server {
|
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 {
|
namespace std {
|
||||||
@@ -14,4 +26,4 @@ struct hash<LV::Server::ServerObjectPos> {
|
|||||||
return std::hash<uint32_t>()(obj.WorldId) ^ std::hash<LV::Pos::Object>()(obj.ObjectPos);
|
return std::hash<uint32_t>()(obj.WorldId) ^ std::hash<LV::Pos::Object>()(obj.ObjectPos);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,24 +34,6 @@ using PlayerId_t = ResourceId;
|
|||||||
using DefGeneratorId_t = ResourceId;
|
using DefGeneratorId_t = ResourceId;
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
Сервер загружает информацию о локальных текстурах
|
|
||||||
Пересмотр списка текстур?
|
|
||||||
Динамичные текстуры?
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
struct ResourceFile {
|
|
||||||
using Hash_t = sha2::sha256_hash; // boost::uuids::detail::sha1::digest_type;
|
|
||||||
|
|
||||||
Hash_t Hash;
|
|
||||||
std::vector<std::byte> Data;
|
|
||||||
|
|
||||||
void calcHash() {
|
|
||||||
Hash = sha2::sha256((const uint8_t*) Data.data(), Data.size());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ServerTime {
|
struct ServerTime {
|
||||||
uint32_t Seconds : 24, Sub : 8;
|
uint32_t Seconds : 24, Sub : 8;
|
||||||
};
|
};
|
||||||
@@ -236,6 +218,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
DefEntityId getDefId() const { return DefId; }
|
DefEntityId getDefId() const { return DefId; }
|
||||||
|
void setDefId(DefEntityId defId) { DefId = defId; }
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename Vec>
|
template<typename Vec>
|
||||||
@@ -507,4 +490,4 @@ struct ContentViewCircle {
|
|||||||
int16_t Range;
|
int16_t Range;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,720 +0,0 @@
|
|||||||
#include "AssetsManager.hpp"
|
|
||||||
#include "Common/Abstract.hpp"
|
|
||||||
#include "boost/json.hpp"
|
|
||||||
#include "png++/rgb_pixel.hpp"
|
|
||||||
#include <algorithm>
|
|
||||||
#include <exception>
|
|
||||||
#include <filesystem>
|
|
||||||
#include <png.h>
|
|
||||||
#include <pngconf.h>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <unordered_set>
|
|
||||||
#include <utility>
|
|
||||||
#include "sol/sol.hpp"
|
|
||||||
|
|
||||||
|
|
||||||
namespace LV::Server {
|
|
||||||
|
|
||||||
PreparedModel::PreparedModel(const std::string& domain, const LV::PreparedModel& model) {
|
|
||||||
Cuboids.reserve(model.Cuboids.size());
|
|
||||||
|
|
||||||
for(auto& [key, cmd] : model.Textures) {
|
|
||||||
for(auto& [domain, key] : cmd.Assets) {
|
|
||||||
TextureDependencies[domain].push_back(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for(auto& sub : model.SubModels) {
|
|
||||||
ModelDependencies[sub.Domain].push_back(sub.Key);
|
|
||||||
}
|
|
||||||
|
|
||||||
// for(const PreparedModel::Cuboid& cuboid : model.Cuboids) {
|
|
||||||
// Cuboid result;
|
|
||||||
// result.From = cuboid.From;
|
|
||||||
// result.To = cuboid.To;
|
|
||||||
// result.Faces = 0;
|
|
||||||
|
|
||||||
// for(const auto& [key, _] : cuboid.Faces)
|
|
||||||
// result.Faces |= (1 << int(key));
|
|
||||||
|
|
||||||
// result.Transformations = cuboid.Transformations;
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
PreparedModel::PreparedModel(const std::string& domain, const PreparedGLTF& glTF) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetsManager::loadResourceFromFile(EnumAssets type, ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const {
|
|
||||||
switch(type) {
|
|
||||||
case EnumAssets::Nodestate: loadResourceFromFile_Nodestate (out, domain, key, path); return;
|
|
||||||
case EnumAssets::Particle: loadResourceFromFile_Particle (out, domain, key, path); return;
|
|
||||||
case EnumAssets::Animation: loadResourceFromFile_Animation (out, domain, key, path); return;
|
|
||||||
case EnumAssets::Model: loadResourceFromFile_Model (out, domain, key, path); return;
|
|
||||||
case EnumAssets::Texture: loadResourceFromFile_Texture (out, domain, key, path); return;
|
|
||||||
case EnumAssets::Sound: loadResourceFromFile_Sound (out, domain, key, path); return;
|
|
||||||
case EnumAssets::Font: loadResourceFromFile_Font (out, domain, key, path); return;
|
|
||||||
default:
|
|
||||||
std::unreachable();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetsManager::loadResourceFromLua(EnumAssets type, ResourceChangeObj& out, const std::string& domain, const std::string& key, const sol::table& profile) const {
|
|
||||||
switch(type) {
|
|
||||||
case EnumAssets::Nodestate: loadResourceFromLua_Nodestate(out, domain, key, profile); return;
|
|
||||||
case EnumAssets::Particle: loadResourceFromLua_Particle(out, domain, key, profile); return;
|
|
||||||
case EnumAssets::Animation: loadResourceFromLua_Animation(out, domain, key, profile); return;
|
|
||||||
case EnumAssets::Model: loadResourceFromLua_Model(out, domain, key, profile); return;
|
|
||||||
case EnumAssets::Texture: loadResourceFromLua_Texture(out, domain, key, profile); return;
|
|
||||||
case EnumAssets::Sound: loadResourceFromLua_Sound(out, domain, key, profile); return;
|
|
||||||
case EnumAssets::Font: loadResourceFromLua_Font(out, domain, key, profile); return;
|
|
||||||
default:
|
|
||||||
std::unreachable();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetsManager::loadResourceFromFile_Nodestate(ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const {
|
|
||||||
Resource res(path);
|
|
||||||
js::object obj = js::parse(std::string_view((const char*) res.data(), res.size())).as_object();
|
|
||||||
PreparedNodeState pns(domain, obj);
|
|
||||||
out.NewOrChange_Nodestates[domain].emplace_back(key, std::move(pns), fs::last_write_time(path));
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetsManager::loadResourceFromFile_Particle(ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const {
|
|
||||||
std::unreachable();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetsManager::loadResourceFromFile_Animation(ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const {
|
|
||||||
std::unreachable();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetsManager::loadResourceFromFile_Model(ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const {
|
|
||||||
/*
|
|
||||||
json, glTF, glB
|
|
||||||
*/
|
|
||||||
|
|
||||||
Resource res(path);
|
|
||||||
std::filesystem::file_time_type ftt = fs::last_write_time(path);
|
|
||||||
auto extension = path.extension();
|
|
||||||
|
|
||||||
if(extension == ".json") {
|
|
||||||
js::object obj = js::parse(std::string_view((const char*) res.data(), res.size())).as_object();
|
|
||||||
LV::PreparedModel pm(domain, obj);
|
|
||||||
out.NewOrChange_Models[domain].emplace_back(key, std::move(pm), ftt);
|
|
||||||
} else if(extension == ".gltf") {
|
|
||||||
js::object obj = js::parse(std::string_view((const char*) res.data(), res.size())).as_object();
|
|
||||||
PreparedGLTF gltf(domain, obj);
|
|
||||||
out.NewOrChange_Models[domain].emplace_back(key, std::move(gltf), ftt);
|
|
||||||
} else if(extension == ".glb") {
|
|
||||||
PreparedGLTF gltf(domain, res);
|
|
||||||
out.NewOrChange_Models[domain].emplace_back(key, std::move(gltf), ftt);
|
|
||||||
} else {
|
|
||||||
MAKE_ERROR("Не поддерживаемый формат файла");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetsManager::loadResourceFromFile_Texture(ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const {
|
|
||||||
Resource res(path);
|
|
||||||
|
|
||||||
if(res.size() < 8)
|
|
||||||
MAKE_ERROR("Файл не является текстурой png или jpeg (недостаточный размер файла)");
|
|
||||||
|
|
||||||
if(png_check_sig(reinterpret_cast<png_bytep>((unsigned char*) res.data()), 8)) {
|
|
||||||
// Это png
|
|
||||||
fs::file_time_type lwt = fs::last_write_time(path);
|
|
||||||
out.NewOrChange[(int) EnumAssets::Texture][domain].emplace_back(key, res, lwt);
|
|
||||||
return;
|
|
||||||
} else if((int) res.data()[0] == 0xFF && (int) res.data()[1] == 0xD8) {
|
|
||||||
// Это jpeg
|
|
||||||
fs::file_time_type lwt = fs::last_write_time(path);
|
|
||||||
out.NewOrChange[(int) EnumAssets::Texture][domain].emplace_back(key, res, lwt);
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
MAKE_ERROR("Файл не является текстурой png или jpeg");
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unreachable();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetsManager::loadResourceFromFile_Sound(ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const {
|
|
||||||
std::unreachable();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetsManager::loadResourceFromFile_Font(ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const {
|
|
||||||
std::unreachable();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetsManager::loadResourceFromLua_Nodestate(ResourceChangeObj& out, const std::string& domain, const std::string& key, const sol::table& profile) const {
|
|
||||||
if(std::optional<std::string> path = profile.get<std::optional<std::string>>("path")) {
|
|
||||||
out.NewOrChange[(int) EnumAssets::Nodestate][domain].emplace_back(key, Resource(*path), fs::file_time_type::min());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unreachable();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetsManager::loadResourceFromLua_Particle(ResourceChangeObj& out, const std::string& domain, const std::string& key, const sol::table& profile) const {
|
|
||||||
if(std::optional<std::string> path = profile.get<std::optional<std::string>>("path")) {
|
|
||||||
out.NewOrChange[(int) EnumAssets::Particle][domain].emplace_back(key, Resource(*path), fs::file_time_type::min());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unreachable();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetsManager::loadResourceFromLua_Animation(ResourceChangeObj& out, const std::string& domain, const std::string& key, const sol::table& profile) const {
|
|
||||||
if(std::optional<std::string> path = profile.get<std::optional<std::string>>("path")) {
|
|
||||||
out.NewOrChange[(int) EnumAssets::Animation][domain].emplace_back(key, Resource(*path), fs::file_time_type::min());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unreachable();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetsManager::loadResourceFromLua_Model(ResourceChangeObj& out, const std::string& domain, const std::string& key, const sol::table& profile) const {
|
|
||||||
if(std::optional<std::string> path = profile.get<std::optional<std::string>>("path")) {
|
|
||||||
out.NewOrChange[(int) EnumAssets::Model][domain].emplace_back(key, Resource(*path), fs::file_time_type::min());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unreachable();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetsManager::loadResourceFromLua_Texture(ResourceChangeObj& out, const std::string& domain, const std::string& key, const sol::table& profile) const {
|
|
||||||
if(std::optional<std::string> path = profile.get<std::optional<std::string>>("path")) {
|
|
||||||
out.NewOrChange[(int) EnumAssets::Texture][domain].emplace_back(key, Resource(*path), fs::file_time_type::min());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unreachable();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetsManager::loadResourceFromLua_Sound(ResourceChangeObj& out, const std::string& domain, const std::string& key, const sol::table& profile) const {
|
|
||||||
if(std::optional<std::string> path = profile.get<std::optional<std::string>>("path")) {
|
|
||||||
out.NewOrChange[(int) EnumAssets::Sound][domain].emplace_back(key, Resource(*path), fs::file_time_type::min());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unreachable();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetsManager::loadResourceFromLua_Font(ResourceChangeObj& out, const std::string& domain, const std::string& key, const sol::table& profile) const {
|
|
||||||
if(std::optional<std::string> path = profile.get<std::optional<std::string>>("path")) {
|
|
||||||
out.NewOrChange[(int) EnumAssets::Font][domain].emplace_back(key, Resource(*path), fs::file_time_type::min());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unreachable();
|
|
||||||
}
|
|
||||||
|
|
||||||
AssetsManager::AssetsManager(asio::io_context& ioc)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
AssetsManager::~AssetsManager() = default;
|
|
||||||
|
|
||||||
std::tuple<ResourceId, std::optional<AssetsManager::DataEntry>&> AssetsManager::Local::nextId(EnumAssets type) {
|
|
||||||
auto& table = Table[(int) type];
|
|
||||||
ResourceId id = -1;
|
|
||||||
std::optional<DataEntry> *data = nullptr;
|
|
||||||
|
|
||||||
for(size_t index = 0; index < table.size(); index++) {
|
|
||||||
auto& entry = *table[index];
|
|
||||||
|
|
||||||
if(entry.IsFull)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
uint32_t pos = entry.Empty._Find_first();
|
|
||||||
entry.Empty.reset(pos);
|
|
||||||
|
|
||||||
if(entry.Empty._Find_next(pos) == entry.Empty.size())
|
|
||||||
entry.IsFull = true;
|
|
||||||
|
|
||||||
id = index*TableEntry<DataEntry>::ChunkSize + pos;
|
|
||||||
data = &entry.Entries[pos];
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!data) {
|
|
||||||
table.emplace_back(std::make_unique<TableEntry<DataEntry>>());
|
|
||||||
id = (table.size()-1)*TableEntry<DataEntry>::ChunkSize;
|
|
||||||
data = &table.back()->Entries[0];
|
|
||||||
table.back()->Empty.reset(0);
|
|
||||||
|
|
||||||
// Расширяем таблицу с ресурсами, если необходимо
|
|
||||||
if(type == EnumAssets::Nodestate)
|
|
||||||
Table_NodeState.emplace_back(std::make_unique<TableEntry<std::vector<AssetsModel>>>());
|
|
||||||
else if(type == EnumAssets::Model)
|
|
||||||
Table_Model.emplace_back(std::make_unique<TableEntry<ModelDependency>>());
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return {id, *data};
|
|
||||||
}
|
|
||||||
|
|
||||||
AssetsManager::ResourceChangeObj AssetsManager::recheckResources(const AssetsRegister& info) {
|
|
||||||
ResourceChangeObj result;
|
|
||||||
|
|
||||||
// Найти пропавшие ресурсы
|
|
||||||
for(int type = 0; type < (int) EnumAssets::MAX_ENUM; type++) {
|
|
||||||
auto lock = LocalObj.lock();
|
|
||||||
for(auto& [domain, resources] : lock->KeyToId[type]) {
|
|
||||||
for(auto& [key, id] : resources) {
|
|
||||||
if(!lock->Table[type][id / TableEntry<DataEntry>::ChunkSize]->Entries[id % TableEntry<DataEntry>::ChunkSize])
|
|
||||||
continue;
|
|
||||||
|
|
||||||
bool exists = false;
|
|
||||||
|
|
||||||
for(const fs::path& path : info.Assets) {
|
|
||||||
fs::path file = path / domain;
|
|
||||||
|
|
||||||
switch ((EnumAssets) type) {
|
|
||||||
case EnumAssets::Nodestate: file /= "nodestate"; break;
|
|
||||||
case EnumAssets::Particle: file /= "particle"; break;
|
|
||||||
case EnumAssets::Animation: file /= "animation"; break;
|
|
||||||
case EnumAssets::Model: file /= "model"; break;
|
|
||||||
case EnumAssets::Texture: file /= "texture"; break;
|
|
||||||
case EnumAssets::Sound: file /= "sound"; break;
|
|
||||||
case EnumAssets::Font: file /= "font"; break;
|
|
||||||
default:
|
|
||||||
std::unreachable();
|
|
||||||
}
|
|
||||||
|
|
||||||
file /= key;
|
|
||||||
|
|
||||||
if(fs::exists(file) && !fs::is_directory(file)) {
|
|
||||||
exists = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(exists) continue;
|
|
||||||
|
|
||||||
auto iterDomain = info.Custom[type].find(domain);
|
|
||||||
if(iterDomain == info.Custom[type].end()) {
|
|
||||||
result.Lost[type][domain].push_back(key);
|
|
||||||
} else {
|
|
||||||
auto iterData = iterDomain->second.find(key);
|
|
||||||
if(iterData == iterDomain->second.end()) {
|
|
||||||
result.Lost[type][domain].push_back(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Если ресурс уже был найден более приоритетными директориями, то пропускаем его
|
|
||||||
std::unordered_map<std::string, std::unordered_set<std::string>> findedResources[(int) EnumAssets::MAX_ENUM];
|
|
||||||
|
|
||||||
// Найти новые или изменённые ресурсы
|
|
||||||
for(int type = 0; type < (int) EnumAssets::MAX_ENUM; type++) {
|
|
||||||
for(auto& [domain, resources] : info.Custom[type]) {
|
|
||||||
auto lock = LocalObj.lock();
|
|
||||||
const auto& keyToId = lock->KeyToId[type];
|
|
||||||
auto iterDomain = keyToId.find(domain);
|
|
||||||
auto& findList = findedResources[type][domain];
|
|
||||||
|
|
||||||
if(iterDomain == keyToId.end()) {
|
|
||||||
// Ресурсы данного домена неизвестны
|
|
||||||
auto& domainList = result.NewOrChange[type][domain];
|
|
||||||
for(auto& [key, id] : resources) {
|
|
||||||
// Подобрать идентификатор
|
|
||||||
// TODO: реализовать регистрации ресурсов из lua
|
|
||||||
domainList.emplace_back(key, Resource("assets/null"), fs::file_time_type::min());
|
|
||||||
findList.insert(key);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for(auto& [key, id] : resources) {
|
|
||||||
if(findList.contains(key))
|
|
||||||
// Ресурс уже был найден в вышестоящей директории
|
|
||||||
continue;
|
|
||||||
else if(iterDomain->second.contains(key)) {
|
|
||||||
// Ресурс уже есть, TODO: нужно проверить его изменение
|
|
||||||
loadResourceFromFile((EnumAssets) type, result, domain, key, "assets/null");
|
|
||||||
} else {
|
|
||||||
// Ресурс не был известен
|
|
||||||
loadResourceFromFile((EnumAssets) type, result, domain, key, "assets/null");
|
|
||||||
}
|
|
||||||
|
|
||||||
findList.insert(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for(const fs::path& path : info.Assets) {
|
|
||||||
if(!fs::exists(path))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
for(auto begin = fs::directory_iterator(path), end = fs::directory_iterator(); begin != end; begin++) {
|
|
||||||
if(!begin->is_directory())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
fs::path domainPath = begin->path();
|
|
||||||
std::string domain = domainPath.filename();
|
|
||||||
|
|
||||||
for(int type = 0; type < (int) EnumAssets::MAX_ENUM; type++) {
|
|
||||||
fs::path resourcesPath = domainPath;
|
|
||||||
|
|
||||||
switch ((EnumAssets) type) {
|
|
||||||
case EnumAssets::Nodestate: resourcesPath /= "nodestate"; break;
|
|
||||||
case EnumAssets::Particle: resourcesPath /= "particle"; break;
|
|
||||||
case EnumAssets::Animation: resourcesPath /= "animation"; break;
|
|
||||||
case EnumAssets::Model: resourcesPath /= "model"; break;
|
|
||||||
case EnumAssets::Texture: resourcesPath /= "texture"; break;
|
|
||||||
case EnumAssets::Sound: resourcesPath /= "sound"; break;
|
|
||||||
case EnumAssets::Font: resourcesPath /= "font"; break;
|
|
||||||
default:
|
|
||||||
std::unreachable();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto& findList = findedResources[type][domain];
|
|
||||||
auto lock = LocalObj.lock();
|
|
||||||
auto iterDomain = lock->KeyToId[type].find(domain);
|
|
||||||
|
|
||||||
if(!fs::exists(resourcesPath) || !fs::is_directory(resourcesPath))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Рекурсивно загрузить ресурсы внутри папки resourcesPath
|
|
||||||
for(auto begin = fs::recursive_directory_iterator(resourcesPath), end = fs::recursive_directory_iterator(); begin != end; begin++) {
|
|
||||||
if(begin->is_directory())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
fs::path file = begin->path();
|
|
||||||
std::string key = fs::relative(begin->path(), resourcesPath).string();
|
|
||||||
if(findList.contains(key))
|
|
||||||
// Ресурс уже был найден в вышестоящей директории
|
|
||||||
continue;
|
|
||||||
else if(iterDomain != lock->KeyToId[type].end() && iterDomain->second.contains(key)) {
|
|
||||||
// Ресурс уже есть, TODO: нужно проверить его изменение
|
|
||||||
ResourceId id = iterDomain->second.at(key);
|
|
||||||
DataEntry& entry = *lock->Table[type][id / TableEntry<DataEntry>::ChunkSize]->Entries[id % TableEntry<DataEntry>::ChunkSize];
|
|
||||||
|
|
||||||
fs::file_time_type lwt = fs::last_write_time(file);
|
|
||||||
if(lwt != entry.FileChangeTime)
|
|
||||||
// Будем считать что ресурс изменился
|
|
||||||
loadResourceFromFile((EnumAssets) type, result, domain, key, file);
|
|
||||||
} else {
|
|
||||||
// Ресурс не был известен
|
|
||||||
loadResourceFromFile((EnumAssets) type, result, domain, key, file);
|
|
||||||
}
|
|
||||||
|
|
||||||
findList.insert(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
AssetsManager::Out_applyResourceChange AssetsManager::applyResourceChange(const ResourceChangeObj& orr) {
|
|
||||||
// Потерянные и обновлённые идентификаторы
|
|
||||||
Out_applyResourceChange result;
|
|
||||||
|
|
||||||
// Удаляем ресурсы
|
|
||||||
/*
|
|
||||||
Удаляются только ресурсы, при этом за ними остаётся бронь на идентификатор
|
|
||||||
Уже скомпилированные зависимости к ресурсам не будут
|
|
||||||
перекомпилироваться для смены идентификатора. Если нужный ресурс
|
|
||||||
появится, то привязка останется. Новые клиенты не получат ресурс
|
|
||||||
которого нет, но он может использоваться
|
|
||||||
*/
|
|
||||||
for(int type = 0; type < (int) EnumAssets::MAX_ENUM; type++) {
|
|
||||||
for(auto& [domain, resources] : orr.Lost[type]) {
|
|
||||||
auto lock = LocalObj.lock();
|
|
||||||
auto& keyToIdDomain = lock->KeyToId[type].at(domain);
|
|
||||||
|
|
||||||
for(const std::string& key : resources) {
|
|
||||||
auto iter = keyToIdDomain.find(key);
|
|
||||||
assert(iter != keyToIdDomain.end());
|
|
||||||
|
|
||||||
ResourceId resId = iter->second;
|
|
||||||
|
|
||||||
if(type == (int) EnumAssets::Nodestate) {
|
|
||||||
if(resId / TableEntry<PreparedNodeState>::ChunkSize < lock->Table_NodeState.size()) {
|
|
||||||
lock->Table_NodeState[resId / TableEntry<PreparedNodeState>::ChunkSize]
|
|
||||||
->Entries[resId % TableEntry<PreparedNodeState>::ChunkSize].reset();
|
|
||||||
}
|
|
||||||
} else if(type == (int) EnumAssets::Model) {
|
|
||||||
if(resId / TableEntry<ModelDependency>::ChunkSize < lock->Table_Model.size()) {
|
|
||||||
lock->Table_Model[resId / TableEntry<ModelDependency>::ChunkSize]
|
|
||||||
->Entries[resId % TableEntry<ModelDependency>::ChunkSize].reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto& chunk = lock->Table[type][resId / TableEntry<DataEntry>::ChunkSize];
|
|
||||||
chunk->Entries[resId % TableEntry<DataEntry>::ChunkSize].reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Добавляем
|
|
||||||
for(int type = 0; type < (int) EnumAssets::MAX_ENUM; type++) {
|
|
||||||
for(auto& [domain, resources] : orr.NewOrChange[type]) {
|
|
||||||
auto lock = LocalObj.lock();
|
|
||||||
auto& keyToIdDomain = lock->KeyToId[type][domain];
|
|
||||||
|
|
||||||
for(auto& [key, resource, lwt] : resources) {
|
|
||||||
ResourceId id = -1;
|
|
||||||
std::optional<DataEntry>* data = nullptr;
|
|
||||||
|
|
||||||
if(auto iterId = keyToIdDomain.find(key); iterId != keyToIdDomain.end()) {
|
|
||||||
id = iterId->second;
|
|
||||||
data = &lock->Table[(int) type][id / TableEntry<DataEntry>::ChunkSize]->Entries[id % TableEntry<DataEntry>::ChunkSize];
|
|
||||||
} else {
|
|
||||||
auto [_id, _data] = lock->nextId((EnumAssets) type);
|
|
||||||
id = _id;
|
|
||||||
data = &_data;
|
|
||||||
}
|
|
||||||
|
|
||||||
result.NewOrChange[type].push_back({id, resource});
|
|
||||||
keyToIdDomain[key] = id;
|
|
||||||
|
|
||||||
data->emplace(lwt, resource, domain, key);
|
|
||||||
|
|
||||||
lock->HashToId[resource.hash()] = {(EnumAssets) type, id};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Удалённые идентификаторы не считаются удалёнными, если были изменены
|
|
||||||
std::unordered_set<ResourceId> noc;
|
|
||||||
for(auto& [id, _] : result.NewOrChange[type])
|
|
||||||
noc.insert(id);
|
|
||||||
|
|
||||||
std::unordered_set<ResourceId> l(result.Lost[type].begin(), result.Lost[type].end());
|
|
||||||
result.Lost[type].clear();
|
|
||||||
std::set_difference(l.begin(), l.end(), noc.begin(), noc.end(), std::back_inserter(result.Lost[type]));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Приёмка новых/изменённых описаний состояний нод
|
|
||||||
if(!orr.NewOrChange_Nodestates.empty())
|
|
||||||
{
|
|
||||||
auto lock = LocalObj.lock();
|
|
||||||
for(auto& [domain, table] : orr.NewOrChange_Nodestates) {
|
|
||||||
for(auto& [key, _nodestate, ftt] : table) {
|
|
||||||
ResourceId resId = lock->getId(EnumAssets::Nodestate, domain, key);
|
|
||||||
std::optional<DataEntry>& data = lock->Table[(int) EnumAssets::Nodestate][resId / TableEntry<DataEntry>::ChunkSize]->Entries[resId % TableEntry<DataEntry>::ChunkSize];
|
|
||||||
PreparedNodeState nodestate = _nodestate;
|
|
||||||
|
|
||||||
// Ресолвим модели
|
|
||||||
for(const auto& [lKey, lDomain] : nodestate.LocalToModelKD) {
|
|
||||||
nodestate.LocalToModel.push_back(lock->getId(EnumAssets::Nodestate, lDomain, lKey));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Сдампим для отправки клиенту (Кеш в пролёте?)
|
|
||||||
Resource res(nodestate.dump());
|
|
||||||
|
|
||||||
// На оповещение
|
|
||||||
result.NewOrChange[(int) EnumAssets::Model].push_back({resId, res});
|
|
||||||
|
|
||||||
// Запись в таблице ресурсов
|
|
||||||
data.emplace(ftt, res, domain, key);
|
|
||||||
lock->HashToId[res.hash()] = {EnumAssets::Nodestate, resId};
|
|
||||||
|
|
||||||
lock->Table_NodeState[resId / TableEntry<DataEntry>::ChunkSize]
|
|
||||||
->Entries[resId % TableEntry<DataEntry>::ChunkSize] = nodestate.LocalToModel;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Приёмка новых/изменённых моделей
|
|
||||||
if(!orr.NewOrChange_Models.empty())
|
|
||||||
{
|
|
||||||
auto lock = LocalObj.lock();
|
|
||||||
for(auto& [domain, table] : orr.NewOrChange_Models) {
|
|
||||||
auto& keyToIdDomain = lock->KeyToId[(int) EnumAssets::Model][domain];
|
|
||||||
|
|
||||||
for(auto& [key, _model, ftt] : table) {
|
|
||||||
ResourceId resId = -1;
|
|
||||||
std::optional<DataEntry>* data = nullptr;
|
|
||||||
|
|
||||||
if(auto iterId = keyToIdDomain.find(key); iterId != keyToIdDomain.end()) {
|
|
||||||
resId = iterId->second;
|
|
||||||
data = &lock->Table[(int) EnumAssets::Model][resId / TableEntry<DataEntry>::ChunkSize]->Entries[resId % TableEntry<DataEntry>::ChunkSize];
|
|
||||||
} else {
|
|
||||||
auto [_id, _data] = lock->nextId((EnumAssets) EnumAssets::Model);
|
|
||||||
resId = _id;
|
|
||||||
data = &_data;
|
|
||||||
}
|
|
||||||
|
|
||||||
keyToIdDomain[key] = resId;
|
|
||||||
|
|
||||||
// Ресолвим текстуры
|
|
||||||
std::variant<LV::PreparedModel, PreparedGLTF> model = _model;
|
|
||||||
std::visit([&lock](auto& val) {
|
|
||||||
for(const auto& [key, pipeline] : val.Textures) {
|
|
||||||
TexturePipeline pipe;
|
|
||||||
pipe.Pipeline = pipeline.Pipeline;
|
|
||||||
|
|
||||||
for(const auto& [domain, key] : pipeline.Assets) {
|
|
||||||
ResourceId texId = lock->getId(EnumAssets::Texture, domain, key);
|
|
||||||
pipe.BinTextures.push_back(texId);
|
|
||||||
}
|
|
||||||
|
|
||||||
val.CompiledTextures[key] = std::move(pipe);
|
|
||||||
}
|
|
||||||
}, model);
|
|
||||||
|
|
||||||
// Сдампим для отправки клиенту (Кеш в пролёте?)
|
|
||||||
std::u8string dump = std::visit<std::u8string>([&lock](auto& val) {
|
|
||||||
return val.dump();
|
|
||||||
}, model);
|
|
||||||
Resource res(std::move(dump));
|
|
||||||
|
|
||||||
// На оповещение
|
|
||||||
result.NewOrChange[(int) EnumAssets::Model].push_back({resId, res});
|
|
||||||
|
|
||||||
// Запись в таблице ресурсов
|
|
||||||
data->emplace(ftt, res, domain, key);
|
|
||||||
|
|
||||||
lock->HashToId[res.hash()] = {EnumAssets::Model, resId};
|
|
||||||
|
|
||||||
// Для нужд сервера, ресолвим зависимости
|
|
||||||
PreparedModel pm = std::visit<PreparedModel>([&domain](auto& val) {
|
|
||||||
return PreparedModel(domain, val);
|
|
||||||
}, model);
|
|
||||||
|
|
||||||
ModelDependency deps;
|
|
||||||
for(auto& [domain2, list] : pm.ModelDependencies) {
|
|
||||||
for(const std::string& key2 : list) {
|
|
||||||
ResourceId subResId = lock->getId(EnumAssets::Model, domain2, key2);
|
|
||||||
deps.ModelDeps.push_back(subResId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for(auto& [domain2, list] : pm.TextureDependencies) {
|
|
||||||
for(const std::string& key2 : list) {
|
|
||||||
ResourceId subResId = lock->getId(EnumAssets::Texture, domain2, key2);
|
|
||||||
deps.TextureDeps.push_back(subResId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lock->Table_Model[resId / TableEntry<DataEntry>::ChunkSize]
|
|
||||||
->Entries[resId % TableEntry<DataEntry>::ChunkSize] = std::move(deps);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Дамп ключей assets
|
|
||||||
{
|
|
||||||
std::stringstream result;
|
|
||||||
|
|
||||||
auto lock = LocalObj.lock();
|
|
||||||
for(int type = 0; type < (int) EnumAssets::MAX_ENUM; type++) {
|
|
||||||
if(type == 0)
|
|
||||||
result << "Nodestate:\n";
|
|
||||||
else if(type == 1)
|
|
||||||
result << "Particle:\n";
|
|
||||||
else if(type == 2)
|
|
||||||
result << "Animation:\n";
|
|
||||||
else if(type == 3)
|
|
||||||
result << "Model:\n";
|
|
||||||
else if(type == 4)
|
|
||||||
result << "Texture:\n";
|
|
||||||
else if(type == 5)
|
|
||||||
result << "Sound:\n";
|
|
||||||
else if(type == 6)
|
|
||||||
result << "Font:\n";
|
|
||||||
|
|
||||||
for(const auto& [domain, list] : lock->KeyToId[type]) {
|
|
||||||
result << "\t" << domain << ":\n";
|
|
||||||
|
|
||||||
for(const auto& [key, id] : list) {
|
|
||||||
result << "\t\t" << key << " = " << id << '\n';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG.debug() << "Дамп ассетов:\n" << result.str();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Вычислить зависимости моделей
|
|
||||||
{
|
|
||||||
// Затираем старые данные
|
|
||||||
auto lock = LocalObj.lock();
|
|
||||||
for(auto& entriesChunk : lock->Table_Model) {
|
|
||||||
for(auto& entry : entriesChunk->Entries) {
|
|
||||||
if(!entry)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
entry->Ready = false;
|
|
||||||
entry->FullSubTextureDeps.clear();
|
|
||||||
entry->FullSubModelDeps.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Вычисляем зависимости
|
|
||||||
std::function<void(AssetsModel resId, ModelDependency&)> calcDeps;
|
|
||||||
calcDeps = [&](AssetsModel resId, ModelDependency& entry) {
|
|
||||||
for(AssetsModel subResId : entry.ModelDeps) {
|
|
||||||
auto& model = lock->Table_Model[subResId / TableEntry<ModelDependency>::ChunkSize]
|
|
||||||
->Entries[subResId % TableEntry<ModelDependency>::ChunkSize];
|
|
||||||
|
|
||||||
if(!model)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if(resId == subResId) {
|
|
||||||
const auto object1 = lock->getResource(EnumAssets::Model, resId);
|
|
||||||
const auto object2 = lock->getResource(EnumAssets::Model, subResId);
|
|
||||||
LOG.warn() << "В моделе " << std::get<1>(*object1) << ':' << std::get<2>(*object1)
|
|
||||||
<< " обнаружена циклическая зависимость с самой собою";
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!model->Ready)
|
|
||||||
calcDeps(subResId, *model);
|
|
||||||
|
|
||||||
if(std::binary_search(model->FullSubModelDeps.begin(), model->FullSubModelDeps.end(), resId)) {
|
|
||||||
// Циклическая зависимость
|
|
||||||
const auto object1 = lock->getResource(EnumAssets::Model, resId);
|
|
||||||
const auto object2 = lock->getResource(EnumAssets::Model, subResId);
|
|
||||||
assert(object1);
|
|
||||||
|
|
||||||
LOG.warn() << "В моделе " << std::get<1>(*object1) << ':' << std::get<2>(*object1)
|
|
||||||
<< " обнаружена циклическая зависимость с " << std::get<1>(*object2) << ':'
|
|
||||||
<< std::get<2>(*object2);
|
|
||||||
} else {
|
|
||||||
entry.FullSubTextureDeps.append_range(model->FullSubTextureDeps);
|
|
||||||
entry.FullSubModelDeps.push_back(subResId);
|
|
||||||
entry.FullSubModelDeps.append_range(model->FullSubModelDeps);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
entry.FullSubTextureDeps.append_range(entry.TextureDeps);
|
|
||||||
{
|
|
||||||
std::sort(entry.FullSubTextureDeps.begin(), entry.FullSubTextureDeps.end());
|
|
||||||
auto eraseIter = std::unique(entry.FullSubTextureDeps.begin(), entry.FullSubTextureDeps.end());
|
|
||||||
entry.FullSubTextureDeps.erase(eraseIter, entry.FullSubTextureDeps.end());
|
|
||||||
entry.FullSubTextureDeps.shrink_to_fit();
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
std::sort(entry.FullSubModelDeps.begin(), entry.FullSubModelDeps.end());
|
|
||||||
auto eraseIter = std::unique(entry.FullSubModelDeps.begin(), entry.FullSubModelDeps.end());
|
|
||||||
entry.FullSubModelDeps.erase(eraseIter, entry.FullSubModelDeps.end());
|
|
||||||
entry.FullSubModelDeps.shrink_to_fit();
|
|
||||||
}
|
|
||||||
|
|
||||||
entry.Ready = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
ssize_t iter = -1;
|
|
||||||
for(auto& entriesChunk : lock->Table_Model) {
|
|
||||||
for(auto& entry : entriesChunk->Entries) {
|
|
||||||
iter++;
|
|
||||||
|
|
||||||
if(!entry || entry->Ready)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Собираем зависимости
|
|
||||||
calcDeps(iter, *entry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,304 +1,129 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Common/Abstract.hpp"
|
#include "Common/Abstract.hpp"
|
||||||
#include "TOSLib.hpp"
|
#include "Common/IdProvider.hpp"
|
||||||
#include "Common/Net.hpp"
|
#include "Common/AssetsPreloader.hpp"
|
||||||
#include "sha2.hpp"
|
|
||||||
#include <bitset>
|
|
||||||
#include <filesystem>
|
|
||||||
#include <optional>
|
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <unordered_set>
|
|
||||||
#include <variant>
|
|
||||||
|
|
||||||
|
|
||||||
namespace LV::Server {
|
namespace LV::Server {
|
||||||
|
|
||||||
namespace fs = std::filesystem;
|
class AssetsManager : public IdProvider<EnumAssets>, protected AssetsPreloader {
|
||||||
|
|
||||||
/*
|
|
||||||
Используется для расчёта коллизии,
|
|
||||||
если это необходимо, а также зависимостей к ассетам.
|
|
||||||
*/
|
|
||||||
struct PreparedModel {
|
|
||||||
// Упрощённая коллизия
|
|
||||||
std::vector<std::pair<glm::vec3, glm::vec3>> Cuboids;
|
|
||||||
// Зависимости от текстур, которые нужно сообщить клиенту
|
|
||||||
std::unordered_map<std::string, std::vector<std::string>> TextureDependencies;
|
|
||||||
// Зависимости от моделей
|
|
||||||
std::unordered_map<std::string, std::vector<std::string>> ModelDependencies;
|
|
||||||
|
|
||||||
PreparedModel(const std::string& domain, const LV::PreparedModel& model);
|
|
||||||
PreparedModel(const std::string& domain, const PreparedGLTF& glTF);
|
|
||||||
|
|
||||||
PreparedModel() = default;
|
|
||||||
PreparedModel(const PreparedModel&) = default;
|
|
||||||
PreparedModel(PreparedModel&&) = default;
|
|
||||||
|
|
||||||
PreparedModel& operator=(const PreparedModel&) = default;
|
|
||||||
PreparedModel& operator=(PreparedModel&&) = default;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ModelDependency {
|
|
||||||
// Прямые зависимости к тестурам и моделям
|
|
||||||
std::vector<AssetsTexture> TextureDeps;
|
|
||||||
std::vector<AssetsModel> ModelDeps;
|
|
||||||
// Коллизия
|
|
||||||
std::vector<std::pair<glm::vec3, glm::vec3>> Cuboids;
|
|
||||||
|
|
||||||
//
|
|
||||||
bool Ready = false;
|
|
||||||
// Полный список зависимостей рекурсивно
|
|
||||||
std::vector<AssetsTexture> FullSubTextureDeps;
|
|
||||||
std::vector<AssetsModel> FullSubModelDeps;
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
Работает с ресурсами из папок assets.
|
|
||||||
Использует папку server_cache/assets для хранения
|
|
||||||
преобразованных ресурсов
|
|
||||||
*/
|
|
||||||
class AssetsManager {
|
|
||||||
public:
|
public:
|
||||||
struct ResourceChangeObj {
|
using BindHashHeaderInfo = AssetsManager::BindHashHeaderInfo;
|
||||||
// Потерянные ресурсы
|
|
||||||
std::unordered_map<std::string, std::vector<std::string>> Lost[(int) EnumAssets::MAX_ENUM];
|
struct Out_checkAndPrepareResourcesUpdate : public AssetsPreloader::Out_checkAndPrepareResourcesUpdate {
|
||||||
// Домен и ключ ресурса
|
Out_checkAndPrepareResourcesUpdate(AssetsPreloader::Out_checkAndPrepareResourcesUpdate&& obj)
|
||||||
std::unordered_map<std::string, std::vector<std::tuple<std::string, Resource, fs::file_time_type>>> NewOrChange[(int) EnumAssets::MAX_ENUM];
|
: AssetsPreloader::Out_checkAndPrepareResourcesUpdate(std::move(obj))
|
||||||
std::unordered_map<std::string, std::vector<std::tuple<std::string, PreparedNodeState, fs::file_time_type>>> NewOrChange_Nodestates;
|
{}
|
||||||
std::unordered_map<std::string, std::vector<std::tuple<std::string, std::variant<
|
|
||||||
LV::PreparedModel,
|
std::unordered_map<ResourceFile::Hash_t, std::u8string> NewHeadless;
|
||||||
PreparedGLTF
|
|
||||||
>, fs::file_time_type>>> NewOrChange_Models;
|
|
||||||
|
|
||||||
// std::unordered_map<std::string, std::vector<std::pair<std::string, PreparedModel>>> Models;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private:
|
Out_checkAndPrepareResourcesUpdate checkAndPrepareResourcesUpdate(
|
||||||
// Данные об отслеживаемых файлах
|
const AssetsRegister& instances,
|
||||||
struct DataEntry {
|
ReloadStatus* status = nullptr
|
||||||
// Время последнего изменения файла
|
) {
|
||||||
fs::file_time_type FileChangeTime;
|
std::unordered_map<ResourceFile::Hash_t, std::u8string> newHeadless;
|
||||||
Resource Res;
|
|
||||||
std::string Domain, Key;
|
|
||||||
};
|
|
||||||
|
|
||||||
template<typename T>
|
Out_checkAndPrepareResourcesUpdate result = AssetsPreloader::checkAndPrepareResourcesUpdate(
|
||||||
struct TableEntry {
|
instances,
|
||||||
static constexpr size_t ChunkSize = 4096;
|
[&](EnumAssets type, std::string_view domain, std::string_view key) { return getId(type, domain, key); },
|
||||||
bool IsFull = false;
|
[&](std::u8string&& resource, ResourceFile::Hash_t hash, fs::path resPath) { newHeadless.emplace(hash, std::move(resource)); },
|
||||||
std::bitset<ChunkSize> Empty;
|
status
|
||||||
std::array<std::optional<T>, ChunkSize> Entries;
|
);
|
||||||
|
|
||||||
TableEntry() {
|
result.NewHeadless = std::move(newHeadless);
|
||||||
Empty.set();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Local {
|
return result;
|
||||||
// Связь ресурсов по идентификаторам
|
|
||||||
std::vector<std::unique_ptr<TableEntry<DataEntry>>> Table[(int) EnumAssets::MAX_ENUM];
|
|
||||||
|
|
||||||
// Распаршенные ресурсы, для использования сервером (сбор зависимостей профиля нод и расчёт коллизии если нужно)
|
|
||||||
// Первичные зависимости Nodestate к моделям
|
|
||||||
std::vector<std::unique_ptr<TableEntry<std::vector<AssetsModel>>>> Table_NodeState;
|
|
||||||
// Упрощённые модели для коллизии
|
|
||||||
std::vector<std::unique_ptr<TableEntry<ModelDependency>>> Table_Model;
|
|
||||||
|
|
||||||
// Связь домены -> {ключ -> идентификатор}
|
|
||||||
std::unordered_map<std::string, std::unordered_map<std::string, ResourceId>> KeyToId[(int) EnumAssets::MAX_ENUM];
|
|
||||||
std::unordered_map<Hash_t, std::tuple<EnumAssets, ResourceId>> HashToId;
|
|
||||||
|
|
||||||
std::tuple<ResourceId, std::optional<DataEntry>&> nextId(EnumAssets type);
|
|
||||||
|
|
||||||
|
|
||||||
ResourceId getId(EnumAssets type, const std::string& domain, const std::string& key) {
|
|
||||||
auto& keyToId = KeyToId[(int) type];
|
|
||||||
if(auto iterKTI = keyToId.find(domain); iterKTI != keyToId.end()) {
|
|
||||||
if(auto iterKey = iterKTI->second.find(key); iterKey != iterKTI->second.end()) {
|
|
||||||
return iterKey->second;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto [id, entry] = nextId(type);
|
|
||||||
keyToId[domain][key] = id;
|
|
||||||
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<std::tuple<Resource, const std::string&, const std::string&>> getResource(EnumAssets type, ResourceId id) {
|
|
||||||
assert(id < Table[(int) type].size()*TableEntry<DataEntry>::ChunkSize);
|
|
||||||
auto& value = Table[(int) type][id / TableEntry<DataEntry>::ChunkSize]->Entries[id % TableEntry<DataEntry>::ChunkSize];
|
|
||||||
if(value)
|
|
||||||
return {{value->Res, value->Domain, value->Key}};
|
|
||||||
else
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<std::tuple<Resource, const std::string&, const std::string&, EnumAssets, ResourceId>> getResource(const Hash_t& hash) {
|
|
||||||
auto iter = HashToId.find(hash);
|
|
||||||
if(iter == HashToId.end())
|
|
||||||
return std::nullopt;
|
|
||||||
|
|
||||||
auto [type, id] = iter->second;
|
|
||||||
std::optional<std::tuple<Resource, const std::string&, const std::string&>> res = getResource(type, id);
|
|
||||||
if(!res) {
|
|
||||||
HashToId.erase(iter);
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(std::get<Resource>(*res).hash() == hash) {
|
|
||||||
auto& [resource, domain, key] = *res;
|
|
||||||
return std::tuple<Resource, const std::string&, const std::string&, EnumAssets, ResourceId>{resource, domain, key, type, id};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
HashToId.erase(iter);
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::optional<std::vector<AssetsModel>>& getResourceNodestate(ResourceId id) {
|
|
||||||
assert(id < Table_NodeState.size()*TableEntry<DataEntry>::ChunkSize);
|
|
||||||
return Table_NodeState[id / TableEntry<DataEntry>::ChunkSize]
|
|
||||||
->Entries[id % TableEntry<DataEntry>::ChunkSize];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const std::optional<ModelDependency>& getResourceModel(ResourceId id) {
|
|
||||||
assert(id < Table_Model.size()*TableEntry<DataEntry>::ChunkSize);
|
|
||||||
return Table_Model[id / TableEntry<DataEntry>::ChunkSize]
|
|
||||||
->Entries[id % TableEntry<DataEntry>::ChunkSize];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
TOS::SpinlockObject<Local> LocalObj;
|
|
||||||
|
|
||||||
/*
|
|
||||||
Загрузка ресурса с файла. При необходимости приводится
|
|
||||||
к внутреннему формату и сохраняется в кеше
|
|
||||||
*/
|
|
||||||
void loadResourceFromFile (EnumAssets type, ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const;
|
|
||||||
void loadResourceFromLua (EnumAssets type, ResourceChangeObj& out, const std::string& domain, const std::string& key, const sol::table& profile) const;
|
|
||||||
|
|
||||||
void loadResourceFromFile_Nodestate (ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const;
|
|
||||||
void loadResourceFromFile_Particle (ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const;
|
|
||||||
void loadResourceFromFile_Animation (ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const;
|
|
||||||
void loadResourceFromFile_Model (ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const;
|
|
||||||
void loadResourceFromFile_Texture (ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const;
|
|
||||||
void loadResourceFromFile_Sound (ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const;
|
|
||||||
void loadResourceFromFile_Font (ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const;
|
|
||||||
|
|
||||||
void loadResourceFromLua_Nodestate (ResourceChangeObj& out, const std::string& domain, const std::string& key, const sol::table& profile) const;
|
|
||||||
void loadResourceFromLua_Particle (ResourceChangeObj& out, const std::string& domain, const std::string& key, const sol::table& profile) const;
|
|
||||||
void loadResourceFromLua_Animation (ResourceChangeObj& out, const std::string& domain, const std::string& key, const sol::table& profile) const;
|
|
||||||
void loadResourceFromLua_Model (ResourceChangeObj& out, const std::string& domain, const std::string& key, const sol::table& profile) const;
|
|
||||||
void loadResourceFromLua_Texture (ResourceChangeObj& out, const std::string& domain, const std::string& key, const sol::table& profile) const;
|
|
||||||
void loadResourceFromLua_Sound (ResourceChangeObj& out, const std::string& domain, const std::string& key, const sol::table& profile) const;
|
|
||||||
void loadResourceFromLua_Font (ResourceChangeObj& out, const std::string& domain, const std::string& key, const sol::table& profile) const;
|
|
||||||
|
|
||||||
public:
|
|
||||||
AssetsManager(asio::io_context& ioc);
|
|
||||||
~AssetsManager();
|
|
||||||
|
|
||||||
/*
|
|
||||||
Перепроверка изменений ресурсов по дате изменения, пересчёт хешей.
|
|
||||||
Обнаруженные изменения должны быть отправлены всем клиентам.
|
|
||||||
Ресурсы будут обработаны в подходящий формат и сохранены в кеше.
|
|
||||||
Одновременно может выполнятся только одна такая функция
|
|
||||||
Используется в GameServer
|
|
||||||
*/
|
|
||||||
|
|
||||||
struct AssetsRegister {
|
|
||||||
/*
|
|
||||||
Пути до активных папок assets, соответствую порядку загруженным модам.
|
|
||||||
От последнего мода к первому.
|
|
||||||
Тот файл, что был загружен раньше и будет использоваться
|
|
||||||
*/
|
|
||||||
std::vector<fs::path> Assets;
|
|
||||||
/*
|
|
||||||
У этих ресурсов приоритет выше, если их удастся получить,
|
|
||||||
то использоваться будут именно они
|
|
||||||
Domain -> {key + data}
|
|
||||||
*/
|
|
||||||
std::unordered_map<std::string, std::unordered_map<std::string, void*>> Custom[(int) EnumAssets::MAX_ENUM];
|
|
||||||
};
|
|
||||||
|
|
||||||
ResourceChangeObj recheckResources(const AssetsRegister&);
|
|
||||||
|
|
||||||
/*
|
|
||||||
Применяет расчитанные изменения.
|
|
||||||
Раздаёт идентификаторы ресурсам и записывает их в таблицу
|
|
||||||
*/
|
|
||||||
struct Out_applyResourceChange {
|
|
||||||
std::vector<ResourceId> Lost[(int) EnumAssets::MAX_ENUM];
|
|
||||||
std::vector<std::pair<ResourceId, Resource>> NewOrChange[(int) EnumAssets::MAX_ENUM];
|
|
||||||
};
|
|
||||||
|
|
||||||
Out_applyResourceChange applyResourceChange(const ResourceChangeObj& orr);
|
|
||||||
|
|
||||||
/*
|
|
||||||
Выдаёт идентификатор ресурса, даже если он не существует или был удалён.
|
|
||||||
resource должен содержать домен и путь
|
|
||||||
*/
|
|
||||||
ResourceId getId(EnumAssets type, const std::string& domain, const std::string& key) {
|
|
||||||
return LocalObj.lock()->getId(type, domain, key);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Выдаёт ресурс по идентификатору
|
struct Out_applyResourcesUpdate : public AssetsPreloader::Out_applyResourcesUpdate {
|
||||||
std::optional<std::tuple<Resource, const std::string&, const std::string&>> getResource(EnumAssets type, ResourceId id) {
|
Out_applyResourcesUpdate(AssetsPreloader::Out_applyResourcesUpdate&& obj)
|
||||||
return LocalObj.lock()->getResource(type, id);
|
: AssetsPreloader::Out_applyResourcesUpdate(std::move(obj))
|
||||||
}
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
// Выдаёт ресурс по хешу
|
Out_applyResourcesUpdate applyResourcesUpdate(Out_checkAndPrepareResourcesUpdate& orr) {
|
||||||
std::optional<std::tuple<Resource, const std::string&, const std::string&, EnumAssets, ResourceId>> getResource(const Hash_t& hash) {
|
Out_applyResourcesUpdate result = AssetsPreloader::applyResourcesUpdate(orr);
|
||||||
return LocalObj.lock()->getResource(hash);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Выдаёт зависимости к ресурсам профиля ноды
|
{
|
||||||
std::tuple<AssetsNodestate, std::vector<AssetsModel>, std::vector<AssetsTexture>>
|
static TOS::Logger LOG = "Server>AssetsManager";
|
||||||
getNodeDependency(const std::string& domain, const std::string& key)
|
|
||||||
{
|
|
||||||
auto lock = LocalObj.lock();
|
|
||||||
AssetsNodestate nodestateId = lock->getId(EnumAssets::Nodestate, domain, key+".json");
|
|
||||||
|
|
||||||
std::vector<AssetsModel> models;
|
for(size_t type = 0; type < static_cast<size_t>(EnumAssets::MAX_ENUM); ++type) {
|
||||||
std::vector<AssetsTexture> textures;
|
if(result.NewOrUpdates[type].empty())
|
||||||
|
|
||||||
if(auto subModelsPtr = lock->getResourceNodestate(nodestateId)) {
|
|
||||||
for(AssetsModel resId : *subModelsPtr) {
|
|
||||||
const auto& subModel = lock->getResourceModel(resId);
|
|
||||||
|
|
||||||
if(!subModel)
|
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
models.push_back(resId);
|
EnumAssets typeEnum = static_cast<EnumAssets>(type);
|
||||||
models.append_range(subModel->FullSubModelDeps);
|
const char* typeName = ::EnumAssetsToDirectory(typeEnum);
|
||||||
textures.append_range(subModel->FullSubTextureDeps);
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
LOG.debug() << "Для ноды " << domain << ':' << key << " отсутствует описание Nodestate";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
for(auto& [hash, data] : orr.NewHeadless) {
|
||||||
std::sort(models.begin(), models.end());
|
Resources.emplace(hash, ResourceHashData{0, std::make_shared<std::u8string>(std::move(data))});
|
||||||
auto eraseIter = std::unique(models.begin(), models.end());
|
|
||||||
models.erase(eraseIter, models.end());
|
|
||||||
models.shrink_to_fit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
for(auto& [hash, pathes] : orr.HashToPathNew) {
|
||||||
std::sort(textures.begin(), textures.end());
|
auto iter = Resources.find(hash);
|
||||||
auto eraseIter = std::unique(textures.begin(), textures.end());
|
assert(iter != Resources.end());
|
||||||
textures.erase(eraseIter, textures.end());
|
iter->second.RefCount += pathes.size();
|
||||||
textures.shrink_to_fit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {nodestateId, std::move(models), std::move(textures)};
|
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:
|
private:
|
||||||
TOS::Logger LOG = "Server>AssetsManager";
|
struct ResourceHashData {
|
||||||
|
size_t RefCount;
|
||||||
|
std::shared_ptr<std::u8string> Data;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unordered_map<
|
||||||
|
ResourceFile::Hash_t,
|
||||||
|
ResourceHashData
|
||||||
|
> Resources;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,35 @@
|
|||||||
#include "ContentManager.hpp"
|
#include "ContentManager.hpp"
|
||||||
#include "Common/Abstract.hpp"
|
#include "Common/Abstract.hpp"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <optional>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
namespace LV::Server {
|
namespace LV::Server {
|
||||||
|
|
||||||
ContentManager::ContentManager(AssetsManager &am)
|
ContentManager::ContentManager(AssetsManager& am)
|
||||||
: AM(am)
|
: AM(am)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ContentManager::~ContentManager() = default;
|
ContentManager::~ContentManager() = default;
|
||||||
|
|
||||||
void ContentManager::registerBase_Node(ResourceId id, const std::string& domain, const std::string& key, const sol::table& profile) {
|
void ContentManager::registerBase_Node(ResourceId id, const std::string& domain, const std::string& key, const sol::table& profile) {
|
||||||
std::optional<DefNode>& node = getEntry_Node(id);
|
std::optional<DefNode_Base>* basePtr;
|
||||||
if(!node)
|
|
||||||
node.emplace();
|
|
||||||
|
|
||||||
DefNode& def = *node;
|
{
|
||||||
def.Domain = domain;
|
size_t entryIndex = id / TableEntry<DefNode_Base>::ChunkSize;
|
||||||
def.Key = key;
|
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");
|
std::optional<std::variant<std::string, sol::table>> parent = profile.get<std::optional<std::variant<std::string, sol::table>>>("parent");
|
||||||
@@ -80,15 +90,27 @@ void ContentManager::registerBase_World(ResourceId id, const std::string& domain
|
|||||||
world.emplace();
|
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)
|
void ContentManager::registerBase(EnumDefContent type, const std::string& domain, const std::string& key, const sol::table& profile)
|
||||||
{
|
{
|
||||||
ResourceId id = getId(type, domain, key);
|
ResourceId id = getId(type, domain, key);
|
||||||
ProfileChanges[(int) type].push_back(id);
|
ProfileChanges[static_cast<size_t>(type)].push_back(id);
|
||||||
|
|
||||||
if(type == EnumDefContent::Node)
|
if(type == EnumDefContent::Node)
|
||||||
registerBase_Node(id, domain, key, profile);
|
registerBase_Node(id, domain, key, profile);
|
||||||
else if(type == EnumDefContent::World)
|
else if(type == EnumDefContent::World)
|
||||||
registerBase_World(id, domain, key, profile);
|
registerBase_World(id, domain, key, profile);
|
||||||
|
else if(type == EnumDefContent::Entity)
|
||||||
|
registerBase_Entity(id, domain, key, profile);
|
||||||
else
|
else
|
||||||
MAKE_ERROR("Не реализовано");
|
MAKE_ERROR("Не реализовано");
|
||||||
}
|
}
|
||||||
@@ -111,31 +133,203 @@ void ContentManager::unRegisterModifier(EnumDefContent type, const std::string&
|
|||||||
ProfileChanges[(int) type].push_back(id);
|
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() {
|
ContentManager::Out_buildEndProfiles ContentManager::buildEndProfiles() {
|
||||||
Out_buildEndProfiles result;
|
Out_buildEndProfiles result;
|
||||||
|
|
||||||
for(int type = 0; type < (int) EnumDefContent::MAX_ENUM; type++) {
|
for(int type = 0; type < (int) EnumDefContent::MAX_ENUM; type++) {
|
||||||
|
std::shared_lock lock(Profiles_Mtx[type]);
|
||||||
auto& keys = ProfileChanges[type];
|
auto& keys = ProfileChanges[type];
|
||||||
std::sort(keys.begin(), keys.end());
|
std::sort(keys.begin(), keys.end());
|
||||||
auto iterErase = std::unique(keys.begin(), keys.end());
|
auto iterErase = std::unique(keys.begin(), keys.end());
|
||||||
keys.erase(iterErase, keys.end());
|
keys.erase(iterErase, keys.end());
|
||||||
}
|
|
||||||
|
|
||||||
for(ResourceId id : ProfileChanges[(int) EnumDefContent::Node]) {
|
switch(type) {
|
||||||
std::optional<DefNode>& node = getEntry_Node(id);
|
case 0: buildEndProfilesByType<DefVoxel, DefVoxel_Mod> (Profiles_Voxel, EnumDefContent::Voxel, Profiles_Base_Voxel, keys, result, Profiles_Mod_Voxel); break;
|
||||||
if(!node) {
|
case 1: buildEndProfilesByType<DefNode, DefNode_Mod> (Profiles_Node, EnumDefContent::Node, Profiles_Base_Node, keys, result, Profiles_Mod_Node); break;
|
||||||
continue;
|
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto [nodestateId, assetsModel, assetsTexture]
|
|
||||||
= AM.getNodeDependency(node->Domain, node->Key);
|
|
||||||
|
|
||||||
node->NodestateId = nodestateId;
|
|
||||||
node->ModelDeps = std::move(assetsModel);
|
|
||||||
node->TextureDeps = std::move(assetsTexture);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
ContentManager::Out_getAllProfiles ContentManager::getAllProfiles() {
|
||||||
|
Out_getAllProfiles result;
|
||||||
|
|
||||||
|
size_t counter;
|
||||||
|
|
||||||
|
{
|
||||||
|
std::shared_lock lock(Profiles_Mtx[static_cast<size_t>(EnumDefContent::Voxel)]);
|
||||||
|
result.ProfilesIds_Voxel.reserve(Profiles_Voxel.size()*TableEntry<DefVoxel>::ChunkSize);
|
||||||
|
counter = 0;
|
||||||
|
for(const auto& entry : Profiles_Voxel)
|
||||||
|
for(const auto& item : entry->Entries) {
|
||||||
|
size_t id = counter++;
|
||||||
|
if(item)
|
||||||
|
result.ProfilesIds_Voxel.push_back(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
result.ProfilesIds_Voxel.shrink_to_fit();
|
||||||
|
result.Profiles_Voxel.reserve(result.ProfilesIds_Voxel.size());
|
||||||
|
for(const auto& entry : Profiles_Voxel)
|
||||||
|
for(const auto& item : entry->Entries)
|
||||||
|
if(item)
|
||||||
|
result.Profiles_Voxel.push_back(&item.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::shared_lock lock(Profiles_Mtx[static_cast<size_t>(EnumDefContent::Node)]);
|
||||||
|
result.ProfilesIds_Node.reserve(Profiles_Node.size()*TableEntry<DefNode>::ChunkSize);
|
||||||
|
counter = 0;
|
||||||
|
for(const auto& entry : Profiles_Node)
|
||||||
|
for(const auto& item : entry->Entries) {
|
||||||
|
size_t id = counter++;
|
||||||
|
if(item)
|
||||||
|
result.ProfilesIds_Node.push_back(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
result.ProfilesIds_Node.shrink_to_fit();
|
||||||
|
result.Profiles_Node.reserve(result.ProfilesIds_Node.size());
|
||||||
|
for(const auto& entry : Profiles_Node)
|
||||||
|
for(const auto& item : entry->Entries)
|
||||||
|
if(item)
|
||||||
|
result.Profiles_Node.push_back(&item.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::shared_lock lock(Profiles_Mtx[static_cast<size_t>(EnumDefContent::World)]);
|
||||||
|
result.ProfilesIds_World.reserve(Profiles_World.size()*TableEntry<DefWorld>::ChunkSize);
|
||||||
|
counter = 0;
|
||||||
|
for(const auto& entry : Profiles_World)
|
||||||
|
for(const auto& item : entry->Entries) {
|
||||||
|
size_t id = counter++;
|
||||||
|
if(item)
|
||||||
|
result.ProfilesIds_World.push_back(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
result.ProfilesIds_World.shrink_to_fit();
|
||||||
|
result.Profiles_World.reserve(result.ProfilesIds_World.size());
|
||||||
|
for(const auto& entry : Profiles_World)
|
||||||
|
for(const auto& item : entry->Entries)
|
||||||
|
if(item)
|
||||||
|
result.Profiles_World.push_back(&item.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::shared_lock lock(Profiles_Mtx[static_cast<size_t>(EnumDefContent::Portal)]);
|
||||||
|
result.ProfilesIds_Portal.reserve(Profiles_Portal.size()*TableEntry<DefPortal>::ChunkSize);
|
||||||
|
counter = 0;
|
||||||
|
for(const auto& entry : Profiles_Portal)
|
||||||
|
for(const auto& item : entry->Entries) {
|
||||||
|
size_t id = counter++;
|
||||||
|
if(item)
|
||||||
|
result.ProfilesIds_Portal.push_back(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
result.ProfilesIds_Portal.shrink_to_fit();
|
||||||
|
result.Profiles_Portal.reserve(result.ProfilesIds_Portal.size());
|
||||||
|
for(const auto& entry : Profiles_Portal)
|
||||||
|
for(const auto& item : entry->Entries)
|
||||||
|
if(item)
|
||||||
|
result.Profiles_Portal.push_back(&item.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::shared_lock lock(Profiles_Mtx[static_cast<size_t>(EnumDefContent::Entity)]);
|
||||||
|
result.ProfilesIds_Entity.reserve(Profiles_Entity.size()*TableEntry<DefEntity>::ChunkSize);
|
||||||
|
counter = 0;
|
||||||
|
for(const auto& entry : Profiles_Entity)
|
||||||
|
for(const auto& item : entry->Entries) {
|
||||||
|
size_t id = counter++;
|
||||||
|
if(item)
|
||||||
|
result.ProfilesIds_Entity.push_back(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
result.ProfilesIds_Entity.shrink_to_fit();
|
||||||
|
result.Profiles_Entity.reserve(result.ProfilesIds_Entity.size());
|
||||||
|
for(const auto& entry : Profiles_Entity)
|
||||||
|
for(const auto& item : entry->Entries)
|
||||||
|
if(item)
|
||||||
|
result.Profiles_Entity.push_back(&item.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::shared_lock lock(Profiles_Mtx[static_cast<size_t>(EnumDefContent::Item)]);
|
||||||
|
result.ProfilesIds_Item.reserve(Profiles_Item.size()*TableEntry<DefItem>::ChunkSize);
|
||||||
|
counter = 0;
|
||||||
|
for(const auto& entry : Profiles_Item)
|
||||||
|
for(const auto& item : entry->Entries) {
|
||||||
|
size_t id = counter++;
|
||||||
|
if(item)
|
||||||
|
result.ProfilesIds_Item.push_back(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
result.ProfilesIds_Item.shrink_to_fit();
|
||||||
|
result.Profiles_Item.reserve(result.ProfilesIds_Item.size());
|
||||||
|
for(const auto& entry : Profiles_Item)
|
||||||
|
for(const auto& item : entry->Entries)
|
||||||
|
if(item)
|
||||||
|
result.Profiles_Item.push_back(&item.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
result.IdToDK = idToDK();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,21 +1,64 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Common/Abstract.hpp"
|
#include "Common/Abstract.hpp"
|
||||||
#include "Server/Abstract.hpp"
|
#include "AssetsManager.hpp"
|
||||||
#include "Server/AssetsManager.hpp"
|
#include "Common/IdProvider.hpp"
|
||||||
|
#include "Common/Net.hpp"
|
||||||
|
#include "TOSLib.hpp"
|
||||||
|
#include <array>
|
||||||
|
#include <mutex>
|
||||||
#include <sol/table.hpp>
|
#include <sol/table.hpp>
|
||||||
|
#include <string_view>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <unordered_set>
|
|
||||||
|
|
||||||
|
|
||||||
namespace LV::Server {
|
namespace LV::Server {
|
||||||
|
|
||||||
struct DefVoxel_Base { };
|
struct ResourceBase {
|
||||||
struct DefNode_Base { };
|
std::string Domain, Key;
|
||||||
struct DefWorld_Base { };
|
};
|
||||||
struct DefPortal_Base { };
|
|
||||||
struct DefEntity_Base { };
|
class ContentManager;
|
||||||
struct DefItem_Base { };
|
|
||||||
|
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 DefVoxel_Mod { };
|
||||||
struct DefNode_Mod { };
|
struct DefNode_Mod { };
|
||||||
@@ -24,41 +67,370 @@ struct DefPortal_Mod { };
|
|||||||
struct DefEntity_Mod { };
|
struct DefEntity_Mod { };
|
||||||
struct DefItem_Mod { };
|
struct DefItem_Mod { };
|
||||||
|
|
||||||
struct ResourceBase {
|
struct DefVoxel_Base {
|
||||||
std::string Domain, Key;
|
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 DefVoxel : public ResourceBase { };
|
struct DefNode_Base {
|
||||||
struct DefNode : public ResourceBase {
|
private:
|
||||||
AssetsNodestate NodestateId;
|
friend ContentManager;
|
||||||
std::vector<AssetsModel> ModelDeps;
|
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 {
|
||||||
std::vector<AssetsTexture> TextureDeps;
|
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 : public ResourceBase { };
|
|
||||||
struct DefPortal : public ResourceBase { };
|
|
||||||
struct DefEntity : public ResourceBase { };
|
|
||||||
struct DefItem : public ResourceBase { };
|
|
||||||
|
|
||||||
class ContentManager {
|
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>
|
template<typename T>
|
||||||
struct TableEntry {
|
struct TableEntry {
|
||||||
static constexpr size_t ChunkSize = 4096;
|
static constexpr size_t ChunkSize = 4096;
|
||||||
std::array<std::optional<T>, ChunkSize> Entries;
|
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>
|
||||||
ResourceId NextId[(int) EnumDefContent::MAX_ENUM] = {0};
|
void buildEndProfilesByType(auto& profiles, auto enumType, auto& base, auto& keys, auto& result, auto& mods);
|
||||||
// Домен -> {ключ -> идентификатор}
|
|
||||||
std::unordered_map<std::string, std::unordered_map<std::string, ResourceId>> ContentKeyToId[(int) EnumDefContent::MAX_ENUM];
|
TOS::Logger LOG = "Server>ContentManager";
|
||||||
|
AssetsManager& AM;
|
||||||
|
|
||||||
// Профили зарегистрированные модами
|
// Профили зарегистрированные модами
|
||||||
std::vector<std::unique_ptr<TableEntry<DefVoxel>>> Profiles_Base_Voxel;
|
std::vector<std::unique_ptr<TableEntry<DefVoxel_Base>>> Profiles_Base_Voxel;
|
||||||
std::vector<std::unique_ptr<TableEntry<DefNode>>> Profiles_Base_Node;
|
std::vector<std::unique_ptr<TableEntry<DefNode_Base>>> Profiles_Base_Node;
|
||||||
std::vector<std::unique_ptr<TableEntry<DefWorld>>> Profiles_Base_World;
|
std::vector<std::unique_ptr<TableEntry<DefWorld_Base>>> Profiles_Base_World;
|
||||||
std::vector<std::unique_ptr<TableEntry<DefPortal>>> Profiles_Base_Portal;
|
std::vector<std::unique_ptr<TableEntry<DefPortal_Base>>> Profiles_Base_Portal;
|
||||||
std::vector<std::unique_ptr<TableEntry<DefEntity>>> Profiles_Base_Entity;
|
std::vector<std::unique_ptr<TableEntry<DefEntity_Base>>> Profiles_Base_Entity;
|
||||||
std::vector<std::unique_ptr<TableEntry<DefItem>>> Profiles_Base_Item;
|
std::vector<std::unique_ptr<TableEntry<DefItem_Base>>> Profiles_Base_Item;
|
||||||
|
|
||||||
// Изменения, накладываемые на профили
|
// Изменения, накладываемые на профили
|
||||||
// Идентификатор [домен мода модификатора, модификатор]
|
// Идентификатор [домен мода модификатора, модификатор]
|
||||||
@@ -71,142 +443,16 @@ class ContentManager {
|
|||||||
|
|
||||||
// Затронутые профили в процессе регистраций
|
// Затронутые профили в процессе регистраций
|
||||||
// По ним будут пересобраны профили
|
// По ним будут пересобраны профили
|
||||||
std::vector<ResourceId> ProfileChanges[(int) EnumDefContent::MAX_ENUM];
|
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<DefVoxel>>> Profiles_Voxel;
|
||||||
std::vector<std::unique_ptr<TableEntry<DefNode>>> Profiles_Node;
|
std::vector<std::unique_ptr<TableEntry<DefNode>>> Profiles_Node;
|
||||||
std::vector<std::unique_ptr<TableEntry<DefWorld>>> Profiles_World;
|
std::vector<std::unique_ptr<TableEntry<DefWorld>>> Profiles_World;
|
||||||
std::vector<std::unique_ptr<TableEntry<DefPortal>>> Profiles_Portal;
|
std::vector<std::unique_ptr<TableEntry<DefPortal>>> Profiles_Portal;
|
||||||
std::vector<std::unique_ptr<TableEntry<DefEntity>>> Profiles_Entity;
|
std::vector<std::unique_ptr<TableEntry<DefEntity>>> Profiles_Entity;
|
||||||
std::vector<std::unique_ptr<TableEntry<DefItem>>> Profiles_Item;
|
std::vector<std::unique_ptr<TableEntry<DefItem>>> Profiles_Item;
|
||||||
|
|
||||||
std::optional<DefVoxel>& getEntry_Voxel(ResourceId resId) { return Profiles_Voxel[resId / TableEntry<DefVoxel>::ChunkSize]->Entries[resId % TableEntry<DefVoxel>::ChunkSize];}
|
|
||||||
std::optional<DefNode>& getEntry_Node(ResourceId resId) { return Profiles_Node[resId / TableEntry<DefVoxel>::ChunkSize]->Entries[resId % TableEntry<DefVoxel>::ChunkSize];}
|
|
||||||
std::optional<DefWorld>& getEntry_World(ResourceId resId) { return Profiles_World[resId / TableEntry<DefVoxel>::ChunkSize]->Entries[resId % TableEntry<DefVoxel>::ChunkSize];}
|
|
||||||
std::optional<DefPortal>& getEntry_Portal(ResourceId resId) { return Profiles_Portal[resId / TableEntry<DefVoxel>::ChunkSize]->Entries[resId % TableEntry<DefVoxel>::ChunkSize];}
|
|
||||||
std::optional<DefEntity>& getEntry_Entity(ResourceId resId) { return Profiles_Entity[resId / TableEntry<DefVoxel>::ChunkSize]->Entries[resId % TableEntry<DefVoxel>::ChunkSize];}
|
|
||||||
std::optional<DefItem>& getEntry_Item(ResourceId resId) { return Profiles_Item[resId / TableEntry<DefVoxel>::ChunkSize]->Entries[resId % TableEntry<DefVoxel>::ChunkSize];}
|
|
||||||
|
|
||||||
ResourceId getId(EnumDefContent type, const std::string& domain, const std::string& key) {
|
|
||||||
if(auto iterCKTI = ContentKeyToId[(int) type].find(domain); iterCKTI != ContentKeyToId[(int) type].end()) {
|
|
||||||
if(auto iterKey = iterCKTI->second.find(key); iterKey != iterCKTI->second.end()) {
|
|
||||||
return iterKey->second;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ResourceId resId = NextId[(int) type]++;
|
|
||||||
ContentKeyToId[(int) type][domain][key] = resId;
|
|
||||||
|
|
||||||
switch(type) {
|
|
||||||
case EnumDefContent::Voxel:
|
|
||||||
if(resId >= Profiles_Voxel.size()*TableEntry<DefVoxel>::ChunkSize)
|
|
||||||
Profiles_Voxel.push_back(std::make_unique<TableEntry<DefVoxel>>());
|
|
||||||
break;
|
|
||||||
case EnumDefContent::Node:
|
|
||||||
if(resId >= Profiles_Node.size()*TableEntry<DefNode>::ChunkSize)
|
|
||||||
Profiles_Node.push_back(std::make_unique<TableEntry<DefNode>>());
|
|
||||||
break;
|
|
||||||
case EnumDefContent::World:
|
|
||||||
if(resId >= Profiles_World.size()*TableEntry<DefWorld>::ChunkSize)
|
|
||||||
Profiles_World.push_back(std::make_unique<TableEntry<DefWorld>>());
|
|
||||||
break;
|
|
||||||
case EnumDefContent::Portal:
|
|
||||||
if(resId >= Profiles_Portal.size()*TableEntry<DefPortal>::ChunkSize)
|
|
||||||
Profiles_Portal.push_back(std::make_unique<TableEntry<DefPortal>>());
|
|
||||||
break;
|
|
||||||
case EnumDefContent::Entity:
|
|
||||||
if(resId >= Profiles_Entity.size()*TableEntry<DefEntity>::ChunkSize)
|
|
||||||
Profiles_Entity.push_back(std::make_unique<TableEntry<DefEntity>>());
|
|
||||||
break;
|
|
||||||
case EnumDefContent::Item:
|
|
||||||
if(resId >= Profiles_Item.size()*TableEntry<DefItem>::ChunkSize)
|
|
||||||
Profiles_Item.push_back(std::make_unique<TableEntry<DefItem>>());
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
std::unreachable();
|
|
||||||
}
|
|
||||||
|
|
||||||
return resId;
|
|
||||||
}
|
|
||||||
|
|
||||||
void registerBase_Node(ResourceId id, const std::string& domain, const std::string& key, const sol::table& profile);
|
|
||||||
void registerBase_World(ResourceId id, const std::string& domain, const std::string& key, const sol::table& profile);
|
|
||||||
|
|
||||||
public:
|
|
||||||
ContentManager(AssetsManager &am);
|
|
||||||
~ContentManager();
|
|
||||||
|
|
||||||
// Регистрирует определение контента
|
|
||||||
void registerBase(EnumDefContent type, const std::string& domain, const std::string& key, const sol::table& profile);
|
|
||||||
void unRegisterBase(EnumDefContent type, const std::string& domain, const std::string& key);
|
|
||||||
// Регистрация модификатора предмета модом
|
|
||||||
void registerModifier(EnumDefContent type, const std::string& mod, const std::string& domain, const std::string& key, const sol::table& profile);
|
|
||||||
void unRegisterModifier(EnumDefContent type, const std::string& mod, const std::string& domain, const std::string& key);
|
|
||||||
// Компилирует изменённые профили
|
|
||||||
struct Out_buildEndProfiles {
|
|
||||||
std::vector<ResourceId> ChangedProfiles[(int) EnumDefContent::MAX_ENUM];
|
|
||||||
};
|
|
||||||
|
|
||||||
Out_buildEndProfiles buildEndProfiles();
|
|
||||||
|
|
||||||
std::optional<DefVoxel*> getProfile_Voxel(ResourceId id) {
|
|
||||||
assert(id < Profiles_Voxel.size()*TableEntry<DefVoxel>::ChunkSize);
|
|
||||||
auto& value = Profiles_Voxel[id / TableEntry<DefVoxel>::ChunkSize]->Entries[id % TableEntry<DefVoxel>::ChunkSize];
|
|
||||||
if(value)
|
|
||||||
return {&*value};
|
|
||||||
else
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<DefNode*> getProfile_Node(ResourceId id) {
|
|
||||||
assert(id < Profiles_Node.size()*TableEntry<DefNode>::ChunkSize);
|
|
||||||
auto& value = Profiles_Node[id / TableEntry<DefNode>::ChunkSize]->Entries[id % TableEntry<DefNode>::ChunkSize];
|
|
||||||
if(value)
|
|
||||||
return {&*value};
|
|
||||||
else
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<DefWorld*> getProfile_World(ResourceId id) {
|
|
||||||
assert(id < Profiles_World.size()*TableEntry<DefWorld>::ChunkSize);
|
|
||||||
auto& value = Profiles_World[id / TableEntry<DefWorld>::ChunkSize]->Entries[id % TableEntry<DefWorld>::ChunkSize];
|
|
||||||
if(value)
|
|
||||||
return {&*value};
|
|
||||||
else
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<DefPortal*> getProfile_Portal(ResourceId id) {
|
|
||||||
assert(id < Profiles_Portal.size()*TableEntry<DefPortal>::ChunkSize);
|
|
||||||
auto& value = Profiles_Portal[id / TableEntry<DefPortal>::ChunkSize]->Entries[id % TableEntry<DefPortal>::ChunkSize];
|
|
||||||
if(value)
|
|
||||||
return {&*value};
|
|
||||||
else
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<DefEntity*> getProfile_Entity(ResourceId id) {
|
|
||||||
assert(id < Profiles_Entity.size()*TableEntry<DefEntity>::ChunkSize);
|
|
||||||
auto& value = Profiles_Entity[id / TableEntry<DefEntity>::ChunkSize]->Entries[id % TableEntry<DefEntity>::ChunkSize];
|
|
||||||
if(value)
|
|
||||||
return {&*value};
|
|
||||||
else
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<DefItem*> getProfile_Item(ResourceId id) {
|
|
||||||
assert(id < Profiles_Item.size()*TableEntry<DefItem>::ChunkSize);
|
|
||||||
auto& value = Profiles_Item[id / TableEntry<DefItem>::ChunkSize]->Entries[id % TableEntry<DefItem>::ChunkSize];
|
|
||||||
if(value)
|
|
||||||
return {&*value};
|
|
||||||
else
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
TOS::Logger LOG = "Server>ContentManager";
|
|
||||||
AssetsManager& AM;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,19 +1,19 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Server/AssetsManager.hpp"
|
#include "Common/AssetsPreloader.hpp"
|
||||||
#define SOL_EXCEPTIONS_SAFE_PROPAGATION 1
|
#define SOL_EXCEPTIONS_SAFE_PROPAGATION 1
|
||||||
|
|
||||||
#include <Common/Net.hpp>
|
#include <Common/Net.hpp>
|
||||||
#include <Common/Lockable.hpp>
|
#include <Common/Lockable.hpp>
|
||||||
|
#include <atomic>
|
||||||
|
#include <barrier>
|
||||||
#include <boost/asio/any_io_executor.hpp>
|
#include <boost/asio/any_io_executor.hpp>
|
||||||
#include <boost/asio/io_context.hpp>
|
#include <boost/asio/io_context.hpp>
|
||||||
#include <condition_variable>
|
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include "Common/Abstract.hpp"
|
#include "Common/Abstract.hpp"
|
||||||
#include "RemoteClient.hpp"
|
#include "RemoteClient.hpp"
|
||||||
#include "Server/Abstract.hpp"
|
#include "Server/Abstract.hpp"
|
||||||
#include <TOSLib.hpp>
|
#include <TOSLib.hpp>
|
||||||
#include <functional>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
#include <set>
|
#include <set>
|
||||||
@@ -23,8 +23,8 @@
|
|||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
#include "WorldDefManager.hpp"
|
#include "WorldDefManager.hpp"
|
||||||
#include "AssetsManager.hpp"
|
|
||||||
#include "ContentManager.hpp"
|
#include "ContentManager.hpp"
|
||||||
|
#include "AssetsManager.hpp"
|
||||||
#include "World.hpp"
|
#include "World.hpp"
|
||||||
|
|
||||||
#include "SaveBackend.hpp"
|
#include "SaveBackend.hpp"
|
||||||
@@ -58,6 +58,7 @@ class GameServer : public AsyncObject {
|
|||||||
|
|
||||||
bool IsAlive = true, IsGoingShutdown = false;
|
bool IsAlive = true, IsGoingShutdown = false;
|
||||||
std::string ShutdownReason;
|
std::string ShutdownReason;
|
||||||
|
std::atomic<bool> ModsReloadRequested = false;
|
||||||
static constexpr float
|
static constexpr float
|
||||||
PerTickDuration = 1/30.f, // Минимальная и стартовая длина такта
|
PerTickDuration = 1/30.f, // Минимальная и стартовая длина такта
|
||||||
PerTickAdjustment = 1/60.f; // Подгонка длительности такта в случае провисаний
|
PerTickAdjustment = 1/60.f; // Подгонка длительности такта в случае провисаний
|
||||||
@@ -79,14 +80,16 @@ class GameServer : public AsyncObject {
|
|||||||
ResourceRequest OnContentChanges;
|
ResourceRequest OnContentChanges;
|
||||||
|
|
||||||
|
|
||||||
ContentObj(asio::io_context& ioc)
|
ContentObj(asio::io_context&)
|
||||||
: AM(ioc), CM(AM)
|
: AM(), CM(AM)
|
||||||
{}
|
{}
|
||||||
} Content;
|
} Content;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
std::vector<std::shared_ptr<RemoteClient>> RemoteClients;
|
std::vector<std::shared_ptr<RemoteClient>> RemoteClients;
|
||||||
ServerTime AfterStartTime = {0, 0};
|
ServerTime AfterStartTime = {0, 0};
|
||||||
|
// Счётчик тактов (увеличивается на 1 каждый тик в GameServer::run)
|
||||||
|
uint32_t Tick = 0;
|
||||||
|
|
||||||
} Game;
|
} Game;
|
||||||
|
|
||||||
@@ -155,38 +158,56 @@ class GameServer : public AsyncObject {
|
|||||||
*/
|
*/
|
||||||
struct BackingChunkPressure_t {
|
struct BackingChunkPressure_t {
|
||||||
TOS::Logger LOG = "BackingChunkPressure";
|
TOS::Logger LOG = "BackingChunkPressure";
|
||||||
volatile bool NeedShutdown = false;
|
std::atomic<bool> NeedShutdown = false;
|
||||||
std::vector<std::thread> Threads;
|
std::vector<std::thread> Threads;
|
||||||
std::mutex Mutex;
|
std::unique_ptr<std::barrier<>> CollectStart;
|
||||||
volatile int RunCollect = 0, RunCompress = 0, Iteration = 0;
|
std::unique_ptr<std::barrier<>> CollectEnd;
|
||||||
std::condition_variable Symaphore;
|
std::unique_ptr<std::barrier<>> CompressEnd;
|
||||||
std::unordered_map<WorldId_t, std::unique_ptr<World>> *Worlds;
|
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() {
|
void startCollectChanges() {
|
||||||
std::lock_guard<std::mutex> lock(Mutex);
|
if(!CollectStart)
|
||||||
RunCollect = Threads.size();
|
return;
|
||||||
RunCompress = Threads.size();
|
HasStarted = true;
|
||||||
Iteration += 1;
|
CollectStart->arrive_and_wait();
|
||||||
assert(RunCollect != 0);
|
|
||||||
Symaphore.notify_all();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void endCollectChanges() {
|
void endCollectChanges() {
|
||||||
std::unique_lock<std::mutex> lock(Mutex);
|
if(!CollectEnd)
|
||||||
Symaphore.wait(lock, [&](){ return RunCollect == 0 || NeedShutdown; });
|
return;
|
||||||
|
if(!HasStarted)
|
||||||
|
return;
|
||||||
|
CollectEnd->arrive_and_wait();
|
||||||
}
|
}
|
||||||
|
|
||||||
void endWithResults() {
|
void endWithResults() {
|
||||||
std::unique_lock<std::mutex> lock(Mutex);
|
if(!CompressEnd)
|
||||||
Symaphore.wait(lock, [&](){ return RunCompress == 0 || NeedShutdown; });
|
return;
|
||||||
|
if(!HasStarted)
|
||||||
|
return;
|
||||||
|
CompressEnd->arrive_and_wait();
|
||||||
}
|
}
|
||||||
|
|
||||||
void stop() {
|
void stop() {
|
||||||
{
|
NeedShutdown.store(true, std::memory_order_release);
|
||||||
std::unique_lock<std::mutex> lock(Mutex);
|
|
||||||
NeedShutdown = true;
|
if(CollectStart)
|
||||||
Symaphore.notify_all();
|
CollectStart->arrive_and_drop();
|
||||||
}
|
if(CollectEnd)
|
||||||
|
CollectEnd->arrive_and_drop();
|
||||||
|
if(CompressEnd)
|
||||||
|
CompressEnd->arrive_and_drop();
|
||||||
|
|
||||||
for(std::thread& thread : Threads)
|
for(std::thread& thread : Threads)
|
||||||
thread.join();
|
thread.join();
|
||||||
@@ -233,7 +254,7 @@ class GameServer : public AsyncObject {
|
|||||||
|
|
||||||
auto lock = Output.lock();
|
auto lock = Output.lock();
|
||||||
std::vector<std::pair<NoiseKey, std::array<float, 64*64*64>>> out = std::move(*lock);
|
std::vector<std::pair<NoiseKey, std::array<float, 64*64*64>>> out = std::move(*lock);
|
||||||
lock->reserve(8000);
|
lock->reserve(25);
|
||||||
|
|
||||||
return std::move(out);
|
return std::move(out);
|
||||||
}
|
}
|
||||||
@@ -248,6 +269,13 @@ class GameServer : public AsyncObject {
|
|||||||
std::vector<std::thread> Threads;
|
std::vector<std::thread> Threads;
|
||||||
TOS::SpinlockObject<std::queue<std::pair<BackingNoiseGenerator_t::NoiseKey, std::array<float, 64*64*64>>>> NoiseIn;
|
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;
|
TOS::SpinlockObject<std::vector<std::pair<BackingNoiseGenerator_t::NoiseKey, World::RegionIn>>> RegionOut;
|
||||||
|
ContentManager &CM;
|
||||||
|
|
||||||
|
BackingAsyncLua_t(ContentManager& cm)
|
||||||
|
: CM(cm)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
void stop() {
|
void stop() {
|
||||||
NeedShutdown = true;
|
NeedShutdown = true;
|
||||||
@@ -264,7 +292,8 @@ class GameServer : public AsyncObject {
|
|||||||
std::vector<std::pair<std::string, sol::table>> ModInstances;
|
std::vector<std::pair<std::string, sol::table>> ModInstances;
|
||||||
// Идентификатор текущегго мода, находящевося в обработке
|
// Идентификатор текущегго мода, находящевося в обработке
|
||||||
std::string CurrentModId;
|
std::string CurrentModId;
|
||||||
AssetsManager::AssetsRegister AssetsInit;
|
AssetsPreloader::AssetsRegister AssetsInit;
|
||||||
|
DefEntityId PlayerEntityDefId = 0;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
GameServer(asio::io_context &ioc, fs::path worldPath);
|
GameServer(asio::io_context &ioc, fs::path worldPath);
|
||||||
@@ -283,6 +312,7 @@ public:
|
|||||||
void waitShutdown() {
|
void waitShutdown() {
|
||||||
UseLock.wait_no_use();
|
UseLock.wait_no_use();
|
||||||
}
|
}
|
||||||
|
void requestModsReload();
|
||||||
|
|
||||||
// Подключение tcp сокета
|
// Подключение tcp сокета
|
||||||
coro<> pushSocketConnect(tcp::socket socket);
|
coro<> pushSocketConnect(tcp::socket socket);
|
||||||
@@ -291,9 +321,6 @@ public:
|
|||||||
// Инициализация игрового протокола для сокета (onSocketAuthorized() может передать сокет в onSocketGame())
|
// Инициализация игрового протокола для сокета (onSocketAuthorized() может передать сокет в onSocketGame())
|
||||||
coro<> pushSocketGameProtocol(tcp::socket socket, const std::string username);
|
coro<> pushSocketGameProtocol(tcp::socket socket, const std::string username);
|
||||||
|
|
||||||
TexturePipeline buildTexturePipeline(const std::string& pipeline);
|
|
||||||
std::string deBuildTexturePipeline(const TexturePipeline& pipeline);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void init(fs::path worldPath);
|
void init(fs::path worldPath);
|
||||||
void prerun();
|
void prerun();
|
||||||
@@ -315,6 +342,7 @@ private:
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
void stepModInitializations();
|
void stepModInitializations();
|
||||||
|
void reloadMods();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Пересчёт зон видимости игроков, если необходимо
|
Пересчёт зон видимости игроков, если необходимо
|
||||||
@@ -364,4 +392,4 @@ private:
|
|||||||
void stepSyncContent();
|
void stepSyncContent();
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -10,13 +10,16 @@
|
|||||||
#include <Common/Abstract.hpp>
|
#include <Common/Abstract.hpp>
|
||||||
#include <bitset>
|
#include <bitset>
|
||||||
#include <initializer_list>
|
#include <initializer_list>
|
||||||
|
#include <optional>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
|
#include <string>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
namespace LV::Server {
|
namespace LV::Server {
|
||||||
|
|
||||||
class World;
|
class World;
|
||||||
|
class GameServer;
|
||||||
|
|
||||||
template<typename ServerKey, typename ClientKey, std::enable_if_t<sizeof(ServerKey) >= sizeof(ClientKey), int> = 0>
|
template<typename ServerKey, typename ClientKey, std::enable_if_t<sizeof(ServerKey) >= sizeof(ClientKey), int> = 0>
|
||||||
class CSChunkedMapper {
|
class CSChunkedMapper {
|
||||||
@@ -140,55 +143,26 @@ public:
|
|||||||
|
|
||||||
Информация о двоичных ресурсах будет получена сразу же при их запросе.
|
Информация о двоичных ресурсах будет получена сразу же при их запросе.
|
||||||
Действительная отправка ресурсов будет только по запросу клиента.
|
Действительная отправка ресурсов будет только по запросу клиента.
|
||||||
|
|
||||||
*/
|
*/
|
||||||
struct ResourceRequest {
|
struct ResourceRequest {
|
||||||
std::vector<Hash_t> Hashes;
|
std::vector<Hash_t> Hashes;
|
||||||
std::vector<ResourceId> AssetsInfo[(int) EnumAssets::MAX_ENUM];
|
|
||||||
|
|
||||||
std::vector<DefVoxelId> Voxel;
|
void merge(const ResourceRequest &obj) {
|
||||||
std::vector<DefNodeId> Node;
|
|
||||||
std::vector<DefWorldId> World;
|
|
||||||
std::vector<DefPortalId> Portal;
|
|
||||||
std::vector<DefEntityId> Entity;
|
|
||||||
std::vector<DefItemId> Item;
|
|
||||||
|
|
||||||
void insert(const ResourceRequest &obj) {
|
|
||||||
Hashes.insert(Hashes.end(), obj.Hashes.begin(), obj.Hashes.end());
|
Hashes.insert(Hashes.end(), obj.Hashes.begin(), obj.Hashes.end());
|
||||||
for(int iter = 0; iter < (int) EnumAssets::MAX_ENUM; iter++)
|
|
||||||
AssetsInfo[iter].insert(AssetsInfo[iter].end(), obj.AssetsInfo[iter].begin(), obj.AssetsInfo[iter].end());
|
|
||||||
|
|
||||||
Voxel.insert(Voxel.end(), obj.Voxel.begin(), obj.Voxel.end());
|
|
||||||
Node.insert(Node.end(), obj.Node.begin(), obj.Node.end());
|
|
||||||
World.insert(World.end(), obj.World.begin(), obj.World.end());
|
|
||||||
Portal.insert(Portal.end(), obj.Portal.begin(), obj.Portal.end());
|
|
||||||
Entity.insert(Entity.end(), obj.Entity.begin(), obj.Entity.end());
|
|
||||||
Item.insert(Item.end(), obj.Item.begin(), obj.Item.end());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void uniq() {
|
void uniq() {
|
||||||
for(std::vector<ResourceId> *vec : {&Voxel, &Node, &World,
|
|
||||||
&Portal, &Entity, &Item
|
|
||||||
})
|
|
||||||
{
|
|
||||||
std::sort(vec->begin(), vec->end());
|
|
||||||
auto last = std::unique(vec->begin(), vec->end());
|
|
||||||
vec->erase(last, vec->end());
|
|
||||||
}
|
|
||||||
|
|
||||||
for(int type = 0; type < (int) EnumAssets::MAX_ENUM; type++)
|
|
||||||
{
|
|
||||||
std::sort(AssetsInfo[type].begin(), AssetsInfo[type].end());
|
|
||||||
auto last = std::unique(AssetsInfo[type].begin(), AssetsInfo[type].end());
|
|
||||||
AssetsInfo[type].erase(last, AssetsInfo[type].end());
|
|
||||||
}
|
|
||||||
|
|
||||||
std::sort(Hashes.begin(), Hashes.end());
|
std::sort(Hashes.begin(), Hashes.end());
|
||||||
auto last = std::unique(Hashes.begin(), Hashes.end());
|
auto last = std::unique(Hashes.begin(), Hashes.end());
|
||||||
Hashes.erase(last, Hashes.end());
|
Hashes.erase(last, Hashes.end());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct AssetBinaryInfo {
|
||||||
|
Resource Data;
|
||||||
|
Hash_t Hash;
|
||||||
|
};
|
||||||
|
|
||||||
// using EntityKey = std::tuple<WorldId_c, Pos::GlobalRegion>;
|
// using EntityKey = std::tuple<WorldId_c, Pos::GlobalRegion>;
|
||||||
|
|
||||||
|
|
||||||
@@ -208,63 +182,25 @@ class RemoteClient {
|
|||||||
bool IsConnected = true, IsGoingShutdown = false;
|
bool IsConnected = true, IsGoingShutdown = false;
|
||||||
|
|
||||||
struct NetworkAndResource_t {
|
struct NetworkAndResource_t {
|
||||||
struct ResUses_t {
|
|
||||||
// Счётчики использования двоичных кэшируемых ресурсов + хэш привязанный к идентификатору
|
|
||||||
// Хэш используется для того, чтобы исключить повторные объявления неизменившихся ресурсов
|
|
||||||
std::map<ResourceId, std::pair<uint32_t, Hash_t>> AssetsUse[(int) EnumAssets::MAX_ENUM];
|
|
||||||
|
|
||||||
// Зависимость профилей контента от профилей ресурсов
|
|
||||||
// Нужно чтобы пересчитать зависимости к профилям ресурсов
|
|
||||||
struct RefAssets_t {
|
|
||||||
std::vector<ResourceId> Resources[(int) EnumAssets::MAX_ENUM];
|
|
||||||
};
|
|
||||||
|
|
||||||
std::map<DefVoxelId, RefAssets_t> RefDefVoxel;
|
|
||||||
std::map<DefNodeId, RefAssets_t> RefDefNode;
|
|
||||||
std::map<WorldId_t, RefAssets_t> RefDefWorld;
|
|
||||||
std::map<DefPortalId, RefAssets_t> RefDefPortal;
|
|
||||||
std::map<DefEntityId, RefAssets_t> RefDefEntity;
|
|
||||||
std::map<DefItemId, RefAssets_t> RefDefItem;
|
|
||||||
|
|
||||||
// Счётчики использование профилей контента
|
|
||||||
std::map<DefVoxelId, uint32_t> DefVoxel; // Один чанк, одно использование
|
|
||||||
std::map<DefNodeId, uint32_t> DefNode;
|
|
||||||
std::map<DefWorldId, uint32_t> DefWorld;
|
|
||||||
std::map<DefPortalId, uint32_t> DefPortal;
|
|
||||||
std::map<DefEntityId, uint32_t> DefEntity;
|
|
||||||
std::map<DefItemId, uint32_t> DefItem; // При передаче инвентарей?
|
|
||||||
|
|
||||||
|
|
||||||
// Зависимость наблюдаемых чанков от профилей нод и вокселей
|
|
||||||
struct ChunkRef {
|
|
||||||
// Отсортированные списки уникальных вокселей
|
|
||||||
std::vector<DefVoxelId> Voxel;
|
|
||||||
std::vector<DefNodeId> Node;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::map<WorldId_t, std::map<Pos::GlobalRegion, std::array<ChunkRef, 4*4*4>>> RefChunk;
|
|
||||||
|
|
||||||
// Модификационные зависимости экземпляров профилей контента
|
|
||||||
// У сущностей в мире могут дополнительно изменятся свойства, переписывая их профиль
|
|
||||||
struct RefWorld_t {
|
|
||||||
DefWorldId Profile;
|
|
||||||
RefAssets_t Assets;
|
|
||||||
};
|
|
||||||
std::map<WorldId_t, RefWorld_t> RefWorld;
|
|
||||||
struct RefPortal_t {
|
|
||||||
DefPortalId Profile;
|
|
||||||
RefAssets_t Assets;
|
|
||||||
};
|
|
||||||
// std::map<PortalId, RefPortal_t> RefPortal;
|
|
||||||
struct RefEntity_t {
|
|
||||||
DefEntityId Profile;
|
|
||||||
RefAssets_t Assets;
|
|
||||||
};
|
|
||||||
std::map<ServerEntityId_t, RefEntity_t> RefEntity;
|
|
||||||
} ResUses;
|
|
||||||
|
|
||||||
// Смена идентификаторов сервера на клиентские
|
// Смена идентификаторов сервера на клиентские
|
||||||
SCSKeyRemapper<ServerEntityId_t, ClientEntityId_t> ReMapEntities;
|
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;
|
||||||
|
|
||||||
// Запрос информации об ассетах и профилях контента
|
// Запрос информации об ассетах и профилях контента
|
||||||
ResourceRequest NextRequest;
|
ResourceRequest NextRequest;
|
||||||
@@ -272,9 +208,6 @@ class RemoteClient {
|
|||||||
/// TODO: здесь может быть засор
|
/// TODO: здесь может быть засор
|
||||||
std::vector<Hash_t> ClientRequested;
|
std::vector<Hash_t> ClientRequested;
|
||||||
|
|
||||||
void incrementAssets(const ResUses_t::RefAssets_t& bin);
|
|
||||||
void decrementAssets(ResUses_t::RefAssets_t&& bin);
|
|
||||||
|
|
||||||
Net::Packet NextPacket;
|
Net::Packet NextPacket;
|
||||||
std::vector<Net::Packet> SimplePackets;
|
std::vector<Net::Packet> SimplePackets;
|
||||||
void checkPacketBorder(uint16_t size) {
|
void checkPacketBorder(uint16_t size) {
|
||||||
@@ -283,10 +216,26 @@ class RemoteClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void prepareChunkUpdate_Voxels(WorldId_t worldId, Pos::GlobalChunk chunkPos, const std::u8string& compressed_voxels,
|
void prepareChunkUpdate_Voxels(
|
||||||
const std::vector<DefVoxelId>& uniq_sorted_defines);
|
WorldId_t worldId,
|
||||||
void prepareChunkUpdate_Nodes(WorldId_t worldId, Pos::GlobalChunk chunkPos, const std::u8string& compressed_nodes,
|
Pos::GlobalRegion regionPos,
|
||||||
const std::vector<DefNodeId>& uniq_sorted_defines);
|
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();
|
||||||
|
|
||||||
void prepareEntitiesRemove(const std::vector<ServerEntityId_t>& entityId);
|
void prepareEntitiesRemove(const std::vector<ServerEntityId_t>& entityId);
|
||||||
void prepareRegionsRemove(WorldId_t worldId, std::vector<Pos::GlobalRegion> regionPoses);
|
void prepareRegionsRemove(WorldId_t worldId, std::vector<Pos::GlobalRegion> regionPoses);
|
||||||
void prepareWorldRemove(WorldId_t worldId);
|
void prepareWorldRemove(WorldId_t worldId);
|
||||||
@@ -294,12 +243,6 @@ class RemoteClient {
|
|||||||
void prepareEntitiesUpdate_Dynamic(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 prepareEntitySwap(ServerEntityId_t prevEntityId, ServerEntityId_t nextEntityId);
|
||||||
void prepareWorldUpdate(WorldId_t worldId, World* world);
|
void prepareWorldUpdate(WorldId_t worldId, World* world);
|
||||||
void informateDefVoxel(const std::vector<std::pair<DefVoxelId, DefVoxel*>>& voxels);
|
|
||||||
void informateDefNode(const std::vector<std::pair<DefNodeId, DefNode*>>& nodes);
|
|
||||||
void informateDefWorld(const std::vector<std::pair<DefWorldId, DefWorld*>>& worlds);
|
|
||||||
void informateDefPortal(const std::vector<std::pair<DefPortalId, DefPortal*>>& portals);
|
|
||||||
void informateDefEntity(const std::vector<std::pair<DefEntityId, DefEntity*>>& entityes);
|
|
||||||
void informateDefItem(const std::vector<std::pair<DefItemId, DefItem*>>& items);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
@@ -313,9 +256,10 @@ class RemoteClient {
|
|||||||
// Ресурсы, отправленные на клиент в этой сессии
|
// Ресурсы, отправленные на клиент в этой сессии
|
||||||
std::vector<Hash_t> OnClient;
|
std::vector<Hash_t> OnClient;
|
||||||
// Отправляемые на клиент ресурсы
|
// Отправляемые на клиент ресурсы
|
||||||
// Тип, домен, ключ, идентификатор, ресурс, количество отправленных байт
|
// Ресурс, количество отправленных байт
|
||||||
std::vector<std::tuple<EnumAssets, std::string, std::string, ResourceId, Resource, size_t>> ToSend;
|
std::vector<std::tuple<ResourceFile::Hash_t, std::shared_ptr<const std::u8string>, size_t>> ToSend;
|
||||||
// Пакет с ресурсами
|
// Пакет с ресурсами
|
||||||
|
std::vector<Net::Packet> AssetsPackets;
|
||||||
Net::Packet AssetsPacket;
|
Net::Packet AssetsPacket;
|
||||||
} AssetsInWork;
|
} AssetsInWork;
|
||||||
|
|
||||||
@@ -333,11 +277,16 @@ public:
|
|||||||
ContentViewInfo ContentViewState;
|
ContentViewInfo ContentViewState;
|
||||||
// Если игрок пересекал границы региона (для перерасчёта ContentViewState)
|
// Если игрок пересекал границы региона (для перерасчёта ContentViewState)
|
||||||
bool CrossedRegion = true;
|
bool CrossedRegion = true;
|
||||||
|
|
||||||
|
// Отложенная выгрузка регионов (гистерезис + задержка)
|
||||||
|
// worldId -> (regionPos -> tick_deadline)
|
||||||
|
std::unordered_map<WorldId_t, std::unordered_map<Pos::GlobalRegion, uint32_t>> PendingRegionUnload;
|
||||||
std::queue<Pos::GlobalNode> Build, Break;
|
std::queue<Pos::GlobalNode> Build, Break;
|
||||||
|
std::optional<ServerEntityId_t> PlayerEntity;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
RemoteClient(asio::io_context &ioc, tcp::socket socket, const std::string username)
|
RemoteClient(asio::io_context &ioc, tcp::socket socket, const std::string username, GameServer* server)
|
||||||
: LOG("RemoteClient " + username), Socket(ioc, std::move(socket)), Username(username)
|
: LOG("RemoteClient " + username), Socket(ioc, std::move(socket)), Username(username), Server(server)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
~RemoteClient();
|
~RemoteClient();
|
||||||
@@ -345,6 +294,9 @@ public:
|
|||||||
coro<> run();
|
coro<> run();
|
||||||
void shutdown(EnumDisconnect type, const std::string reason);
|
void shutdown(EnumDisconnect type, const std::string reason);
|
||||||
bool isConnected() { return IsConnected; }
|
bool isConnected() { return IsConnected; }
|
||||||
|
void setPlayerEntity(ServerEntityId_t id) { PlayerEntity = id; }
|
||||||
|
std::optional<ServerEntityId_t> getPlayerEntity() const { return PlayerEntity; }
|
||||||
|
void clearPlayerEntity() { PlayerEntity.reset(); }
|
||||||
|
|
||||||
void pushPackets(std::vector<Net::Packet> *simplePackets, std::vector<Net::SmartPacket> *smartPackets = nullptr) {
|
void pushPackets(std::vector<Net::Packet> *simplePackets, std::vector<Net::SmartPacket> *smartPackets = nullptr) {
|
||||||
if(IsGoingShutdown)
|
if(IsGoingShutdown)
|
||||||
@@ -365,32 +317,27 @@ public:
|
|||||||
// если возвращает false, то блокировка сейчас находится у другого потока
|
// если возвращает false, то блокировка сейчас находится у другого потока
|
||||||
// и запрос не был обработан.
|
// и запрос не был обработан.
|
||||||
|
|
||||||
// В зоне видимости добавился чанк или изменились его воксели
|
// Создаёт пакет отправки вокселей чанка
|
||||||
bool maybe_prepareChunkUpdate_Voxels(WorldId_t worldId, Pos::GlobalChunk chunkPos, const std::u8string& compressed_voxels,
|
void prepareChunkUpdate_Voxels(
|
||||||
const std::vector<DefVoxelId>& uniq_sorted_defines)
|
WorldId_t worldId,
|
||||||
{
|
Pos::GlobalRegion regionPos,
|
||||||
auto lock = NetworkAndResource.tryLock();
|
Pos::bvec4u chunkPos,
|
||||||
if(!lock)
|
const std::u8string& compressed_voxels
|
||||||
return false;
|
) {
|
||||||
|
NetworkAndResource.lock()->prepareChunkUpdate_Voxels(worldId, regionPos, chunkPos, compressed_voxels);
|
||||||
lock->prepareChunkUpdate_Voxels(worldId, chunkPos, compressed_voxels, uniq_sorted_defines);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// В зоне видимости добавился чанк или изменились его ноды
|
// Создаёт пакет отправки нод чанка
|
||||||
bool maybe_prepareChunkUpdate_Nodes(WorldId_t worldId, Pos::GlobalChunk chunkPos, const std::u8string& compressed_nodes,
|
void prepareChunkUpdate_Nodes(
|
||||||
const std::vector<DefNodeId>& uniq_sorted_defines)
|
WorldId_t worldId,
|
||||||
{
|
Pos::GlobalRegion regionPos,
|
||||||
auto lock = NetworkAndResource.tryLock();
|
Pos::bvec4u chunkPos,
|
||||||
if(!lock)
|
const std::u8string& compressed_nodes
|
||||||
return false;
|
) {
|
||||||
|
NetworkAndResource.lock()->prepareChunkUpdate_Nodes(worldId, regionPos, chunkPos, compressed_nodes);
|
||||||
lock->prepareChunkUpdate_Nodes(worldId, chunkPos, compressed_nodes, uniq_sorted_defines);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
// void prepareChunkUpdate_LightPrism(WorldId_t worldId, Pos::GlobalChunk chunkPos, const LightPrism *lights);
|
|
||||||
|
// Клиент перестал наблюдать за сущностями
|
||||||
// Клиент перестал наблюдать за сущностью
|
|
||||||
void prepareEntitiesRemove(const std::vector<ServerEntityId_t>& entityId) { NetworkAndResource.lock()->prepareEntitiesRemove(entityId); }
|
void 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 prepareRegionsRemove(WorldId_t worldId, std::vector<Pos::GlobalRegion> regionPoses) { NetworkAndResource.lock()->prepareRegionsRemove(worldId, regionPoses); }
|
||||||
@@ -416,35 +363,50 @@ public:
|
|||||||
// Отправка подготовленных пакетов
|
// Отправка подготовленных пакетов
|
||||||
ResourceRequest pushPreparedPackets();
|
ResourceRequest pushPreparedPackets();
|
||||||
|
|
||||||
// Сообщить о ресурсах
|
// Создаёт пакет для всех игроков с оповещением о новых идентификаторах (id -> domain+key)
|
||||||
// Сюда приходят все обновления ресурсов движка
|
static Net::Packet makePacket_informateAssets_DK(
|
||||||
// Глобально их можно запросить в выдаче pushPreparedPackets()
|
const std::array<
|
||||||
|
std::vector<AssetsManager::BindDomainKeyInfo>,
|
||||||
|
static_cast<size_t>(EnumAssets::MAX_ENUM)
|
||||||
|
>& dkVector
|
||||||
|
);
|
||||||
|
|
||||||
// Оповещение о запрошенных (и не только) ассетах
|
// Создаёт пакет для всех игроков с оповещением об изменении файлов ресурсов (id -> hash+header)
|
||||||
void informateAssets(const std::vector<std::tuple<EnumAssets, ResourceId, const std::string, const std::string, Resource>>& resources);
|
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 informateDefVoxel(const std::vector<std::pair<DefVoxelId, DefVoxel*>>& voxels) { NetworkAndResource.lock()->informateDefVoxel(voxels); }
|
void informateBinaryAssets(
|
||||||
void informateDefNode(const std::vector<std::pair<DefNodeId, DefNode*>>& nodes) { NetworkAndResource.lock()->informateDefNode(nodes); }
|
const std::vector<std::tuple<ResourceFile::Hash_t, std::shared_ptr<const std::u8string>>>& resources
|
||||||
void informateDefWorld(const std::vector<std::pair<DefWorldId, DefWorld*>>& worlds) { NetworkAndResource.lock()->informateDefWorld(worlds); }
|
);
|
||||||
void informateDefPortal(const std::vector<std::pair<DefPortalId, DefPortal*>>& portals) { NetworkAndResource.lock()->informateDefPortal(portals); }
|
|
||||||
void informateDefEntity(const std::vector<std::pair<DefEntityId, DefEntity*>>& entityes) { NetworkAndResource.lock()->informateDefEntity(entityes); }
|
|
||||||
void informateDefItem(const std::vector<std::pair<DefItemId, DefItem*>>& items) { NetworkAndResource.lock()->informateDefItem(items); }
|
|
||||||
|
|
||||||
|
|
||||||
|
// Создаёт пакет со всеми данными об игровых профилях
|
||||||
|
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 onUpdate();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
GameServer* Server = nullptr;
|
||||||
void protocolError();
|
void protocolError();
|
||||||
coro<> readPacket(Net::AsyncSocket &sock);
|
coro<> readPacket(Net::AsyncSocket &sock);
|
||||||
coro<> rP_System(Net::AsyncSocket &sock);
|
coro<> rP_System(Net::AsyncSocket &sock);
|
||||||
|
|
||||||
// void incrementProfile(const std::vector<TextureId_t> &textures, const std::vector<ModelId_t> &model,
|
|
||||||
// const std::vector<SoundId_t> &sounds, const std::vector<FontId_t> &font
|
|
||||||
// );
|
|
||||||
// void decrementProfile(std::vector<TextureId_t> &&textures, std::vector<ModelId_t> &&model,
|
|
||||||
// std::vector<SoundId_t> &&sounds, std::vector<FontId_t> &&font
|
|
||||||
// );
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ namespace LV::Server {
|
|||||||
*/
|
*/
|
||||||
struct SB_Region_In {
|
struct SB_Region_In {
|
||||||
// Список вокселей всех чанков
|
// Список вокселей всех чанков
|
||||||
std::unordered_map<Pos::bvec4u, VoxelCube> Voxels;
|
std::unordered_map<Pos::bvec4u, std::vector<VoxelCube>> Voxels;
|
||||||
// Привязка вокселей к ключу профиля
|
// Привязка вокселей к ключу профиля
|
||||||
std::vector<std::pair<DefVoxelId, std::string>> VoxelsMap;
|
std::vector<std::pair<DefVoxelId, std::string>> VoxelsMap;
|
||||||
// Ноды всех чанков
|
// Ноды всех чанков
|
||||||
@@ -132,4 +132,4 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "Filesystem.hpp"
|
#include "Filesystem.hpp"
|
||||||
#include "Server/Abstract.hpp"
|
#include "Server/Abstract.hpp"
|
||||||
#include "Server/SaveBackend.hpp"
|
#include "Server/SaveBackend.hpp"
|
||||||
|
#include "TOSLib.hpp"
|
||||||
#include <boost/json/array.hpp>
|
#include <boost/json/array.hpp>
|
||||||
#include <boost/json/object.hpp>
|
#include <boost/json/object.hpp>
|
||||||
#include <boost/json/parse.hpp>
|
#include <boost/json/parse.hpp>
|
||||||
@@ -11,12 +12,277 @@
|
|||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
namespace LV::Server::SaveBackends {
|
namespace LV::Server::SaveBackends {
|
||||||
|
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
namespace js = boost::json;
|
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 {
|
class WSB_Filesystem : public IWorldSaveBackend {
|
||||||
fs::path Dir;
|
fs::path Dir;
|
||||||
|
|
||||||
@@ -35,7 +301,32 @@ public:
|
|||||||
|
|
||||||
virtual TickSyncInfo_Out tickSync(TickSyncInfo_In &&data) override {
|
virtual TickSyncInfo_Out tickSync(TickSyncInfo_In &&data) override {
|
||||||
TickSyncInfo_Out out;
|
TickSyncInfo_Out out;
|
||||||
out.NotExisten = std::move(data.Load);
|
// Сохранение регионов
|
||||||
|
for(auto& [worldId, regions] : data.ToSave) {
|
||||||
|
for(auto& [regionPos, region] : regions) {
|
||||||
|
writeRegionFile(getPath(std::to_string(worldId), regionPos), region);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Загрузка регионов
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
#include "World.hpp"
|
#include "World.hpp"
|
||||||
|
#include "ContentManager.hpp"
|
||||||
#include "TOSLib.hpp"
|
#include "TOSLib.hpp"
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
|
|
||||||
namespace LV::Server {
|
namespace LV::Server {
|
||||||
@@ -16,7 +18,7 @@ World::~World() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<Pos::GlobalRegion> World::onRemoteClient_RegionsEnter(std::shared_ptr<RemoteClient> cec, const std::vector<Pos::GlobalRegion>& enter) {
|
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;
|
std::vector<Pos::GlobalRegion> out;
|
||||||
|
|
||||||
for(const Pos::GlobalRegion &pos : enter) {
|
for(const Pos::GlobalRegion &pos : enter) {
|
||||||
@@ -43,18 +45,49 @@ std::vector<Pos::GlobalRegion> World::onRemoteClient_RegionsEnter(std::shared_pt
|
|||||||
nodes[Pos::bvec4u(x, y, z)] = region.Nodes[Pos::bvec4u(x, y, z).pack()].data();
|
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;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
void World::onRemoteClient_RegionsLost(std::shared_ptr<RemoteClient> cec, const std::vector<Pos::GlobalRegion> &lost) {
|
void World::onRemoteClient_RegionsLost(WorldId_t worldId, std::shared_ptr<RemoteClient> cec, const std::vector<Pos::GlobalRegion> &lost) {
|
||||||
for(const Pos::GlobalRegion &pos : lost) {
|
for(const Pos::GlobalRegion &pos : lost) {
|
||||||
auto region = Regions.find(pos);
|
auto region = Regions.find(pos);
|
||||||
if(region == Regions.end())
|
if(region == Regions.end())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
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<std::shared_ptr<RemoteClient>> &CECs = region->second->RMs;
|
||||||
for(size_t iter = 0; iter < CECs.size(); iter++) {
|
for(size_t iter = 0; iter < CECs.size(); iter++) {
|
||||||
if(CECs[iter] == cec) {
|
if(CECs[iter] == cec) {
|
||||||
@@ -65,8 +98,94 @@ void World::onRemoteClient_RegionsLost(std::shared_ptr<RemoteClient> cec, const
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
World::SaveUnloadInfo World::onStepDatabaseSync() {
|
World::SaveUnloadInfo World::onStepDatabaseSync(ContentManager& cm, float dtime) {
|
||||||
return {};
|
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) {
|
void World::pushRegions(std::vector<std::pair<Pos::GlobalRegion, RegionIn>> regions) {
|
||||||
@@ -74,6 +193,7 @@ void World::pushRegions(std::vector<std::pair<Pos::GlobalRegion, RegionIn>> regi
|
|||||||
Region ®ion = *(Regions[key] = std::make_unique<Region>());
|
Region ®ion = *(Regions[key] = std::make_unique<Region>());
|
||||||
region.Voxels = std::move(value.Voxels);
|
region.Voxels = std::move(value.Voxels);
|
||||||
region.Nodes = value.Nodes;
|
region.Nodes = value.Nodes;
|
||||||
|
region.Entityes = std::move(value.Entityes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,4 +201,4 @@ void World::onUpdate(GameServer *server, float dtime) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
namespace LV::Server {
|
namespace LV::Server {
|
||||||
|
|
||||||
class GameServer;
|
class GameServer;
|
||||||
|
class ContentManager;
|
||||||
|
|
||||||
class Region {
|
class Region {
|
||||||
public:
|
public:
|
||||||
@@ -146,13 +147,13 @@ public:
|
|||||||
Возвращает список не загруженных регионов, на которые соответственно игрока не получилось подписать
|
Возвращает список не загруженных регионов, на которые соответственно игрока не получилось подписать
|
||||||
При подписи происходит отправка всех чанков и сущностей региона
|
При подписи происходит отправка всех чанков и сущностей региона
|
||||||
*/
|
*/
|
||||||
std::vector<Pos::GlobalRegion> onRemoteClient_RegionsEnter(std::shared_ptr<RemoteClient> cec, const std::vector<Pos::GlobalRegion> &enter);
|
std::vector<Pos::GlobalRegion> onRemoteClient_RegionsEnter(WorldId_t worldId, std::shared_ptr<RemoteClient> cec, const std::vector<Pos::GlobalRegion> &enter);
|
||||||
void onRemoteClient_RegionsLost(std::shared_ptr<RemoteClient> cec, const std::vector<Pos::GlobalRegion>& lost);
|
void onRemoteClient_RegionsLost(WorldId_t worldId, std::shared_ptr<RemoteClient> cec, const std::vector<Pos::GlobalRegion>& lost);
|
||||||
struct SaveUnloadInfo {
|
struct SaveUnloadInfo {
|
||||||
std::vector<Pos::GlobalRegion> ToUnload;
|
std::vector<Pos::GlobalRegion> ToUnload;
|
||||||
std::vector<std::pair<Pos::GlobalRegion, SB_Region_In>> ToSave;
|
std::vector<std::pair<Pos::GlobalRegion, SB_Region_In>> ToSave;
|
||||||
};
|
};
|
||||||
SaveUnloadInfo onStepDatabaseSync();
|
SaveUnloadInfo onStepDatabaseSync(ContentManager& cm, float dtime);
|
||||||
|
|
||||||
struct RegionIn {
|
struct RegionIn {
|
||||||
std::unordered_map<Pos::bvec4u, std::vector<VoxelCube>> Voxels;
|
std::unordered_map<Pos::bvec4u, std::vector<VoxelCube>> Voxels;
|
||||||
@@ -176,4 +177,4 @@ public:
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -338,9 +338,10 @@ class ByteBuffer : public std::vector<uint8_t> {
|
|||||||
if(Index + sizeof(T) > Obj->size())
|
if(Index + sizeof(T) > Obj->size())
|
||||||
throw std::runtime_error("Вышли за пределы буфера");
|
throw std::runtime_error("Вышли за пределы буфера");
|
||||||
|
|
||||||
const uint8_t *ptr = Obj->data()+Index;
|
T value{};
|
||||||
Index += sizeof(T);
|
std::memcpy(&value, Obj->data() + Index, sizeof(T));
|
||||||
return swapEndian(*(const T*) ptr);
|
Index += sizeof(T);
|
||||||
|
return swapEndian(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@@ -362,8 +363,8 @@ class ByteBuffer : public std::vector<uint8_t> {
|
|||||||
inline Reader& operator>>(int64_t &value) { value = readOffset<int64_t>(); return *this; }
|
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>>(uint64_t &value) { value = readOffset<uint64_t>(); return *this; }
|
||||||
inline Reader& operator>>(bool &value) { value = readOffset<uint8_t>(); return *this; }
|
inline Reader& operator>>(bool &value) { value = readOffset<uint8_t>(); return *this; }
|
||||||
inline Reader& operator>>(float &value) { return operator>>(*(uint32_t*) &value); }
|
inline Reader& operator>>(float &value) { uint32_t raw = readOffset<uint32_t>(); std::memcpy(&value, &raw, sizeof(raw)); return *this; }
|
||||||
inline Reader& operator>>(double &value) { return operator>>(*(uint64_t*) &value); }
|
inline Reader& operator>>(double &value) { uint64_t raw = readOffset<uint64_t>(); std::memcpy(&value, &raw, sizeof(raw)); return *this; }
|
||||||
|
|
||||||
inline int8_t readInt8() { int8_t value; this->operator>>(value); return 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; }
|
inline uint8_t readUInt8() { uint8_t value; this->operator>>(value); return value; }
|
||||||
@@ -449,6 +450,17 @@ class ByteBuffer : public std::vector<uint8_t> {
|
|||||||
size_t Index = 0;
|
size_t Index = 0;
|
||||||
uint16_t BlockSize = 256;
|
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)
|
inline uint8_t* checkBorder(size_t count)
|
||||||
{
|
{
|
||||||
@@ -469,17 +481,17 @@ class ByteBuffer : public std::vector<uint8_t> {
|
|||||||
Writer& operator=(const Writer&) = default;
|
Writer& operator=(const Writer&) = default;
|
||||||
Writer& operator=(Writer&&) = default;
|
Writer& operator=(Writer&&) = default;
|
||||||
|
|
||||||
inline Writer& operator<<(const int8_t &value) { *(int8_t*) checkBorder(sizeof(value)) = value; return *this; }
|
inline Writer& operator<<(const int8_t &value) { writeRaw(value); return *this; }
|
||||||
inline Writer& operator<<(const uint8_t &value) { *(uint8_t*) checkBorder(sizeof(value)) = value; return *this; }
|
inline Writer& operator<<(const uint8_t &value) { writeRaw(value); return *this; }
|
||||||
inline Writer& operator<<(const int16_t &value) { *(int16_t*) checkBorder(sizeof(value)) = swapEndian(value); return *this; }
|
inline Writer& operator<<(const int16_t &value) { writeSwapped(value); return *this; }
|
||||||
inline Writer& operator<<(const uint16_t &value) { *(uint16_t*) checkBorder(sizeof(value)) = swapEndian(value); return *this; }
|
inline Writer& operator<<(const uint16_t &value) { writeSwapped(value); return *this; }
|
||||||
inline Writer& operator<<(const int32_t &value) { *(int32_t*) checkBorder(sizeof(value)) = swapEndian(value); return *this; }
|
inline Writer& operator<<(const int32_t &value) { writeSwapped(value); return *this; }
|
||||||
inline Writer& operator<<(const uint32_t &value) { *(uint32_t*) checkBorder(sizeof(value)) = swapEndian(value); return *this; }
|
inline Writer& operator<<(const uint32_t &value) { writeSwapped(value); return *this; }
|
||||||
inline Writer& operator<<(const int64_t &value) { *(int64_t*) checkBorder(sizeof(value)) = swapEndian(value); return *this; }
|
inline Writer& operator<<(const int64_t &value) { writeSwapped(value); return *this; }
|
||||||
inline Writer& operator<<(const uint64_t &value) { *(uint64_t*) checkBorder(sizeof(value)) = swapEndian(value); return *this; }
|
inline Writer& operator<<(const uint64_t &value) { writeSwapped(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 bool &value) { uint8_t temp = value ? 1 : 0; writeRaw(temp); return *this; }
|
||||||
inline Writer& operator<<(const float &value) { *(uint32_t*) checkBorder(sizeof(value)) = swapEndian(*(uint32_t*) &value); 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*) checkBorder(sizeof(value)) = swapEndian(*(uint64_t*) &value); return *this; }
|
inline Writer& operator<<(const double &value) { uint64_t raw; std::memcpy(&raw, &value, sizeof(raw)); writeSwapped(raw); return *this; }
|
||||||
|
|
||||||
inline void writeInt8(const int8_t &value) { this->operator<<(value); }
|
inline void writeInt8(const int8_t &value) { this->operator<<(value); }
|
||||||
inline void writeUInt8(const uint8_t &value) { this->operator<<(value); }
|
inline void writeUInt8(const uint8_t &value) { this->operator<<(value); }
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
#include "Common/Abstract.hpp"
|
#include "Common/Abstract.hpp"
|
||||||
|
#include "boost/asio/awaitable.hpp"
|
||||||
|
#include <chrono>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <boost/asio.hpp>
|
#include <boost/asio.hpp>
|
||||||
#include <Client/Vulkan/Vulkan.hpp>
|
#include <Client/Vulkan/Vulkan.hpp>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
namespace LV {
|
namespace LV {
|
||||||
|
|
||||||
@@ -37,6 +40,4 @@ int main() {
|
|||||||
|
|
||||||
std::cout << "Hello world!" << std::endl;
|
std::cout << "Hello world!" << std::endl;
|
||||||
return LV::main();
|
return LV::main();
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,25 +16,29 @@ layout(push_constant) uniform UniformBufferObject {
|
|||||||
|
|
||||||
// struct NodeVertexStatic {
|
// struct NodeVertexStatic {
|
||||||
// uint32_t
|
// uint32_t
|
||||||
// FX : 9, FY : 9, FZ : 9, // Позиция -224 ~ 288; 64 позиций в одной ноде, 7.5 метров в ряд
|
// FX : 11, FY : 11, N1 : 10, // Позиция, 64 позиции на метр, +3.5м запас
|
||||||
// N1 : 4, // Не занято
|
// FZ : 11, // Позиция
|
||||||
// LS : 1, // Масштаб карты освещения (1м/16 или 1м)
|
// LS : 1, // Масштаб карты освещения (1м/16 или 1м)
|
||||||
// Tex : 18, // Текстура
|
// Tex : 18, // Текстура
|
||||||
// N2 : 14, // Не занято
|
// N2 : 2, // Не занято
|
||||||
// TU : 16, TV : 16; // UV на текстуре
|
// TU : 16, TV : 16; // UV на текстуре
|
||||||
// };
|
// };
|
||||||
|
|
||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
|
uint fx = Vertex.x & 0x7ffu;
|
||||||
|
uint fy = (Vertex.x >> 11) & 0x7ffu;
|
||||||
|
uint fz = Vertex.y & 0x7ffu;
|
||||||
|
|
||||||
vec4 baseVec = ubo.model*vec4(
|
vec4 baseVec = ubo.model*vec4(
|
||||||
float(Vertex.x & 0x1ff) / 64.f - 3.5f,
|
float(fx) / 64.f - 3.5f,
|
||||||
float((Vertex.x >> 9) & 0x1ff) / 64.f - 3.5f,
|
float(fy) / 64.f - 3.5f,
|
||||||
float((Vertex.x >> 18) & 0x1ff) / 64.f - 3.5f,
|
float(fz) / 64.f - 3.5f,
|
||||||
1
|
1
|
||||||
);
|
);
|
||||||
|
|
||||||
Geometry.GeoPos = baseVec.xyz;
|
Geometry.GeoPos = baseVec.xyz;
|
||||||
Geometry.Texture = Vertex.y & 0x3ffff;
|
Geometry.Texture = (Vertex.y >> 12) & 0x3ffffu;
|
||||||
Geometry.UV = vec2(
|
Geometry.UV = vec2(
|
||||||
float(Vertex.z & 0xffff) / pow(2, 16),
|
float(Vertex.z & 0xffff) / pow(2, 16),
|
||||||
float((Vertex.z >> 16) & 0xffff) / pow(2, 16)
|
float((Vertex.z >> 16) & 0xffff) / pow(2, 16)
|
||||||
|
|||||||
Binary file not shown.
@@ -1,6 +1,6 @@
|
|||||||
#version 460
|
#version 460
|
||||||
|
|
||||||
// layout(early_fragment_tests) in;
|
layout(early_fragment_tests) in;
|
||||||
|
|
||||||
layout(location = 0) in FragmentObj {
|
layout(location = 0) in FragmentObj {
|
||||||
vec3 GeoPos; // Реальная позиция в мире
|
vec3 GeoPos; // Реальная позиция в мире
|
||||||
@@ -11,65 +11,36 @@ layout(location = 0) in FragmentObj {
|
|||||||
|
|
||||||
layout(location = 0) out vec4 Frame;
|
layout(location = 0) out vec4 Frame;
|
||||||
|
|
||||||
struct InfoSubTexture {
|
struct AtlasEntry {
|
||||||
uint Flags; // 1 isExist
|
vec4 UVMinMax;
|
||||||
uint PosXY, WidthHeight;
|
uint Layer;
|
||||||
|
uint Flags;
|
||||||
uint AnimationFrames_AnimationTimePerFrame;
|
uint _Pad0;
|
||||||
|
uint _Pad1;
|
||||||
};
|
};
|
||||||
|
|
||||||
uniform layout(set = 0, binding = 0) sampler2D MainAtlas;
|
const uint ATLAS_ENTRY_VALID = 1u;
|
||||||
layout(set = 0, binding = 1) readonly buffer MainAtlasLayoutObj {
|
|
||||||
uint SubsCount;
|
|
||||||
uint Counter;
|
|
||||||
uint WidthHeight;
|
|
||||||
|
|
||||||
InfoSubTexture SubTextures[];
|
uniform layout(set = 0, binding = 0) sampler2DArray MainAtlas;
|
||||||
|
layout(set = 0, binding = 1) readonly buffer MainAtlasLayoutObj {
|
||||||
|
AtlasEntry Entries[];
|
||||||
} MainAtlasLayout;
|
} MainAtlasLayout;
|
||||||
|
|
||||||
uniform layout(set = 1, binding = 0) sampler2D LightMap;
|
uniform layout(set = 1, binding = 0) sampler2DArray LightMap;
|
||||||
layout(set = 1, binding = 1) readonly buffer LightMapLayoutObj {
|
layout(set = 1, binding = 1) readonly buffer LightMapLayoutObj {
|
||||||
vec3 Color;
|
vec3 Color;
|
||||||
} LightMapLayout;
|
} LightMapLayout;
|
||||||
|
|
||||||
vec4 atlasColor(uint texId, vec2 uv)
|
vec4 atlasColor(uint texId, vec2 uv)
|
||||||
{
|
{
|
||||||
uint flags = (texId & 0xffff0000) >> 16;
|
AtlasEntry entry = MainAtlasLayout.Entries[texId];
|
||||||
texId &= 0xffff;
|
if((entry.Flags & ATLAS_ENTRY_VALID) == 0u)
|
||||||
vec4 color = vec4(uv, 0, 1);
|
return vec4(((int(gl_FragCoord.x / 128) + int(gl_FragCoord.y / 128)) % 2) * vec3(1, 0, 1), 1);
|
||||||
|
|
||||||
if((flags & (2 | 4)) > 0)
|
|
||||||
{
|
|
||||||
if((flags & 2) > 0)
|
|
||||||
color = vec4(1, 1, 1, 1);
|
|
||||||
else if((flags & 4) > 0)
|
|
||||||
{
|
|
||||||
color = vec4(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
else if(texId >= uint(MainAtlasLayout.SubsCount))
|
|
||||||
return vec4(((int(gl_FragCoord.x / 128) + int(gl_FragCoord.y / 128)) % 2 ) * vec3(0, 1, 1), 1);
|
|
||||||
else {
|
|
||||||
InfoSubTexture texInfo = MainAtlasLayout.SubTextures[texId];
|
|
||||||
if(texInfo.Flags == 0)
|
|
||||||
return vec4(((int(gl_FragCoord.x / 128) + int(gl_FragCoord.y / 128)) % 2 ) * vec3(1, 0, 1), 1);
|
|
||||||
|
|
||||||
uint posX = texInfo.PosXY & 0xffff;
|
vec2 baseUV = vec2(uv.x, 1.0f - uv.y);
|
||||||
uint posY = (texInfo.PosXY >> 16) & 0xffff;
|
vec2 atlasUV = mix(entry.UVMinMax.xy, entry.UVMinMax.zw, baseUV);
|
||||||
uint width = texInfo.WidthHeight & 0xffff;
|
atlasUV = clamp(atlasUV, entry.UVMinMax.xy, entry.UVMinMax.zw);
|
||||||
uint height = (texInfo.WidthHeight >> 16) & 0xffff;
|
return texture(MainAtlas, vec3(atlasUV, entry.Layer));
|
||||||
uint awidth = MainAtlasLayout.WidthHeight & 0xffff;
|
|
||||||
uint aheight = (MainAtlasLayout.WidthHeight >> 16) & 0xffff;
|
|
||||||
|
|
||||||
if((flags & 1) > 0)
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
vec3 blendOverlay(vec3 base, vec3 blend) {
|
vec3 blendOverlay(vec3 base, vec3 blend) {
|
||||||
@@ -87,6 +58,7 @@ void main() {
|
|||||||
Frame = atlasColor(Fragment.Texture, Fragment.UV);
|
Frame = atlasColor(Fragment.Texture, Fragment.UV);
|
||||||
Frame.xyz *= max(0.2f, dot(Fragment.Normal, normalize(vec3(0.5, 1, 0.8))));
|
Frame.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);
|
// Frame = vec4(blendOverlay(vec3(Frame), vec3(Fragment.GeoPos/64.f)), Frame.w);
|
||||||
|
|
||||||
if(Frame.w == 0)
|
if(Frame.w == 0)
|
||||||
discard;
|
discard;
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
@@ -9,9 +9,19 @@ layout(location = 0) in FragmentObj {
|
|||||||
|
|
||||||
layout(location = 0) out vec4 Frame;
|
layout(location = 0) out vec4 Frame;
|
||||||
|
|
||||||
|
struct AtlasEntry {
|
||||||
|
vec4 UVMinMax;
|
||||||
|
uint Layer;
|
||||||
|
uint Flags;
|
||||||
|
uint _Pad0;
|
||||||
|
uint _Pad1;
|
||||||
|
};
|
||||||
|
|
||||||
|
const uint ATLAS_ENTRY_VALID = 1u;
|
||||||
|
|
||||||
uniform layout(set = 0, binding = 0) sampler2DArray MainAtlas;
|
uniform layout(set = 0, binding = 0) sampler2DArray MainAtlas;
|
||||||
layout(set = 0, binding = 1) readonly buffer MainAtlasLayoutObj {
|
layout(set = 0, binding = 1) readonly buffer MainAtlasLayoutObj {
|
||||||
vec3 Color;
|
AtlasEntry Entries[];
|
||||||
} MainAtlasLayout;
|
} MainAtlasLayout;
|
||||||
|
|
||||||
uniform layout(set = 1, binding = 0) sampler2DArray LightMap;
|
uniform layout(set = 1, binding = 0) sampler2DArray LightMap;
|
||||||
@@ -19,6 +29,22 @@ layout(set = 1, binding = 1) readonly buffer LightMapLayoutObj {
|
|||||||
vec3 Color;
|
vec3 Color;
|
||||||
} LightMapLayout;
|
} LightMapLayout;
|
||||||
|
|
||||||
void main() {
|
vec4 atlasColor(uint texId, vec2 uv)
|
||||||
Frame = vec4(Fragment.GeoPos, 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() {
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
@@ -9,23 +9,22 @@ layout(location = 0) in FragmentObj {
|
|||||||
|
|
||||||
layout(location = 0) out vec4 Frame;
|
layout(location = 0) out vec4 Frame;
|
||||||
|
|
||||||
struct InfoSubTexture {
|
struct AtlasEntry {
|
||||||
uint Flags; // 1 isExist
|
vec4 UVMinMax;
|
||||||
uint PosXY, WidthHeight;
|
uint Layer;
|
||||||
|
uint Flags;
|
||||||
uint AnimationFrames_AnimationTimePerFrame;
|
uint _Pad0;
|
||||||
|
uint _Pad1;
|
||||||
};
|
};
|
||||||
|
|
||||||
uniform layout(set = 0, binding = 0) sampler2D MainAtlas;
|
const uint ATLAS_ENTRY_VALID = 1u;
|
||||||
layout(set = 0, binding = 1) readonly buffer MainAtlasLayoutObj {
|
|
||||||
uint SubsCount;
|
|
||||||
uint Counter;
|
|
||||||
uint WidthHeight;
|
|
||||||
|
|
||||||
InfoSubTexture SubTextures[];
|
uniform layout(set = 0, binding = 0) sampler2DArray MainAtlas;
|
||||||
|
layout(set = 0, binding = 1) readonly buffer MainAtlasLayoutObj {
|
||||||
|
AtlasEntry Entries[];
|
||||||
} MainAtlasLayout;
|
} MainAtlasLayout;
|
||||||
|
|
||||||
uniform layout(set = 1, binding = 0) sampler2D LightMap;
|
uniform layout(set = 1, binding = 0) sampler2DArray LightMap;
|
||||||
layout(set = 1, binding = 1) readonly buffer LightMapLayoutObj {
|
layout(set = 1, binding = 1) readonly buffer LightMapLayoutObj {
|
||||||
vec3 Color;
|
vec3 Color;
|
||||||
} LightMapLayout;
|
} LightMapLayout;
|
||||||
@@ -35,42 +34,14 @@ vec4 atlasColor(uint texId, vec2 uv)
|
|||||||
{
|
{
|
||||||
uv = mod(uv, 1);
|
uv = mod(uv, 1);
|
||||||
|
|
||||||
uint flags = (texId & 0xffff0000) >> 16;
|
AtlasEntry entry = MainAtlasLayout.Entries[texId];
|
||||||
texId &= 0xffff;
|
if((entry.Flags & ATLAS_ENTRY_VALID) == 0u)
|
||||||
vec4 color = vec4(uv, 0, 1);
|
return vec4(((int(gl_FragCoord.x / 128) + int(gl_FragCoord.y / 128)) % 2) * vec3(1, 0, 1), 1);
|
||||||
|
|
||||||
if((flags & (2 | 4)) > 0)
|
|
||||||
{
|
|
||||||
if((flags & 2) > 0)
|
|
||||||
color = vec4(1, 1, 1, 1);
|
|
||||||
else if((flags & 4) > 0)
|
|
||||||
{
|
|
||||||
color = vec4(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
else if(texId >= uint(MainAtlasLayout.SubsCount))
|
|
||||||
return vec4(((int(gl_FragCoord.x / 128) + int(gl_FragCoord.y / 128)) % 2 ) * vec3(0, 1, 1), 1);
|
|
||||||
else {
|
|
||||||
InfoSubTexture texInfo = MainAtlasLayout.SubTextures[texId];
|
|
||||||
if(texInfo.Flags == 0)
|
|
||||||
return vec4(((int(gl_FragCoord.x / 128) + int(gl_FragCoord.y / 128)) % 2 ) * vec3(1, 0, 1), 1);
|
|
||||||
|
|
||||||
uint posX = texInfo.PosXY & 0xffff;
|
vec2 baseUV = vec2(uv.x, 1.0f - uv.y);
|
||||||
uint posY = (texInfo.PosXY >> 16) & 0xffff;
|
vec2 atlasUV = mix(entry.UVMinMax.xy, entry.UVMinMax.zw, baseUV);
|
||||||
uint width = texInfo.WidthHeight & 0xffff;
|
atlasUV = clamp(atlasUV, entry.UVMinMax.xy, entry.UVMinMax.zw);
|
||||||
uint height = (texInfo.WidthHeight >> 16) & 0xffff;
|
return texture(MainAtlas, vec3(atlasUV, entry.Layer));
|
||||||
uint awidth = MainAtlasLayout.WidthHeight & 0xffff;
|
|
||||||
uint aheight = (MainAtlasLayout.WidthHeight >> 16) & 0xffff;
|
|
||||||
|
|
||||||
if((flags & 1) > 0)
|
|
||||||
color = texture(MainAtlas, vec2((posX+0.5f+uv.x*(width-1))/awidth, (posY+0.5f+(1-uv.y)*(height-1))/aheight));
|
|
||||||
else
|
|
||||||
color = texture(MainAtlas, vec2((posX+uv.x*width)/awidth, (posY+(1-uv.y)*height)/aheight));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return color;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
|
|||||||
Binary file not shown.
@@ -9,9 +9,19 @@ layout(location = 0) in Fragment {
|
|||||||
|
|
||||||
layout(location = 0) out vec4 Frame;
|
layout(location = 0) out vec4 Frame;
|
||||||
|
|
||||||
|
struct AtlasEntry {
|
||||||
|
vec4 UVMinMax;
|
||||||
|
uint Layer;
|
||||||
|
uint Flags;
|
||||||
|
uint _Pad0;
|
||||||
|
uint _Pad1;
|
||||||
|
};
|
||||||
|
|
||||||
|
const uint ATLAS_ENTRY_VALID = 1u;
|
||||||
|
|
||||||
uniform layout(set = 0, binding = 0) sampler2DArray MainAtlas;
|
uniform layout(set = 0, binding = 0) sampler2DArray MainAtlas;
|
||||||
layout(set = 0, binding = 1) readonly buffer MainAtlasLayoutObj {
|
layout(set = 0, binding = 1) readonly buffer MainAtlasLayoutObj {
|
||||||
vec3 Color;
|
AtlasEntry Entries[];
|
||||||
} MainAtlasLayout;
|
} MainAtlasLayout;
|
||||||
|
|
||||||
uniform layout(set = 1, binding = 0) sampler2DArray LightMap;
|
uniform layout(set = 1, binding = 0) sampler2DArray LightMap;
|
||||||
@@ -19,6 +29,39 @@ layout(set = 1, binding = 1) readonly buffer LightMapLayoutObj {
|
|||||||
vec3 Color;
|
vec3 Color;
|
||||||
} LightMapLayout;
|
} LightMapLayout;
|
||||||
|
|
||||||
void main() {
|
vec4 atlasColor(uint texId, vec2 uv)
|
||||||
Frame = vec4(1);
|
{
|
||||||
|
uv = mod(uv, 1);
|
||||||
|
|
||||||
|
AtlasEntry entry = MainAtlasLayout.Entries[texId];
|
||||||
|
if((entry.Flags & ATLAS_ENTRY_VALID) == 0u)
|
||||||
|
return vec4(((int(gl_FragCoord.x / 128) + int(gl_FragCoord.y / 128)) % 2) * vec3(1, 0, 1), 1);
|
||||||
|
|
||||||
|
vec2 baseUV = vec2(uv.x, 1.0f - uv.y);
|
||||||
|
vec2 atlasUV = mix(entry.UVMinMax.xy, entry.UVMinMax.zw, baseUV);
|
||||||
|
atlasUV = clamp(atlasUV, entry.UVMinMax.xy, entry.UVMinMax.zw);
|
||||||
|
return texture(MainAtlas, vec3(atlasUV, entry.Layer));
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 uv;
|
||||||
|
|
||||||
|
switch(fragment.Place) {
|
||||||
|
case 0:
|
||||||
|
uv = fragment.GeoPos.xz; break;
|
||||||
|
case 1:
|
||||||
|
uv = fragment.GeoPos.xy; break;
|
||||||
|
case 2:
|
||||||
|
uv = fragment.GeoPos.zy; break;
|
||||||
|
case 3:
|
||||||
|
uv = fragment.GeoPos.xz*vec2(-1, -1); break;
|
||||||
|
case 4:
|
||||||
|
uv = fragment.GeoPos.xy*vec2(-1, 1); break;
|
||||||
|
case 5:
|
||||||
|
uv = fragment.GeoPos.zy*vec2(-1, 1); break;
|
||||||
|
default:
|
||||||
|
uv = vec2(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
Frame = atlasColor(fragment.VoxMTL, uv);
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
161
docs/assets_definitions.md
Normal file
161
docs/assets_definitions.md
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
# Определение ресурсов (assets)
|
||||||
|
|
||||||
|
Документ описывает формат файлов ресурсов и правила их адресации на стороне сервера.
|
||||||
|
Описание основано на загрузчиках из `Src/Server/AssetsManager.hpp` и связанных структурах
|
||||||
|
подготовки (`PreparedNodeState`, `PreparedModel`, `PreparedGLTF`).
|
||||||
|
|
||||||
|
## Общая схема
|
||||||
|
- Ресурсы берутся из списка папок `AssetsRegister::Assets` (от последнего мода к первому).
|
||||||
|
Первый найденный ресурс по пути имеет приоритет.
|
||||||
|
- Переопределения через `AssetsRegister::Custom` имеют более высокий приоритет.
|
||||||
|
- Адрес ресурса состоит из `domain` и `key`.
|
||||||
|
`domain` — имя папки в assets, `key` — относительный путь внутри папки типа ресурса.
|
||||||
|
- Обработанные ресурсы сохраняются в `server_cache/assets`.
|
||||||
|
|
||||||
|
## Дерево папок
|
||||||
|
```
|
||||||
|
assets/
|
||||||
|
<domain>/
|
||||||
|
nodestate/ *.json
|
||||||
|
model/ *.json | *.gltf | *.glb
|
||||||
|
texture/ *.png | *.jpg (jpeg)
|
||||||
|
particle/ (загрузка из файлов пока не реализована)
|
||||||
|
animation/ (загрузка из файлов пока не реализована)
|
||||||
|
sound/ (загрузка из файлов пока не реализована)
|
||||||
|
font/ (загрузка из файлов пока не реализована)
|
||||||
|
```
|
||||||
|
|
||||||
|
Пример: `assets/core/nodestate/stone.json` имеет `domain=core`, `key=stone.json`.
|
||||||
|
При обращении к nodestate из логики нод используется ключ без суффикса `.json`
|
||||||
|
(сервер дописывает расширение автоматически).
|
||||||
|
|
||||||
|
## Nodestate (JSON)
|
||||||
|
Файл nodestate — это JSON-объект, где ключи — условия, а значения — описание модели
|
||||||
|
или список вариантов моделей.
|
||||||
|
|
||||||
|
### Условия
|
||||||
|
Условие — строковое выражение. Поддерживаются:
|
||||||
|
- числа, `true`, `false`
|
||||||
|
- переменные: `state` или `state:value` (двоеточие — часть имени)
|
||||||
|
- операторы: `+ - * / %`, `!`, `&`, `|`, `< <= > >= == !=`
|
||||||
|
- скобки
|
||||||
|
|
||||||
|
Пустая строка условия трактуется как `true`.
|
||||||
|
|
||||||
|
### Формат варианта модели
|
||||||
|
Объект варианта:
|
||||||
|
- `model`: строка `domain:key` **или** массив объектов моделей
|
||||||
|
- `weight`: число (вес при случайном выборе), по умолчанию `1`
|
||||||
|
- `uvlock`: bool (используется для векторных моделей; для одиночной модели игнорируется)
|
||||||
|
- `transformations`: массив строк `"key=value"` для трансформаций
|
||||||
|
|
||||||
|
Если `model` — строка, это одиночная модель.
|
||||||
|
Если `model` — массив, это векторная модель: набор объектов вида:
|
||||||
|
```
|
||||||
|
{ "model": "domain:key", "uvlock": false, "transformations": ["x=0", "ry=1.57"] }
|
||||||
|
```
|
||||||
|
Для векторной модели также могут задаваться `uvlock` и `transformations` на верхнем уровне
|
||||||
|
(они применяются к группе).
|
||||||
|
|
||||||
|
Трансформации поддерживают ключи:
|
||||||
|
`x`, `y`, `z`, `rx`, `ry`, `rz` (сдвиг и поворот).
|
||||||
|
|
||||||
|
Домен в строке `domain:key` можно опустить — тогда используется домен файла nodestate.
|
||||||
|
|
||||||
|
### Пример
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"": { "model": "core:stone" },
|
||||||
|
"variant == 1": [
|
||||||
|
{ "model": "core:stone_alt", "weight": 2 },
|
||||||
|
{ "model": "core:stone_alt_2", "weight": 1, "transformations": ["ry=1.57"] }
|
||||||
|
],
|
||||||
|
"facing:north": {
|
||||||
|
"model": [
|
||||||
|
{ "model": "core:stone", "transformations": ["ry=3.14"] },
|
||||||
|
{ "model": "core:stone_detail", "transformations": ["x=0.5"] }
|
||||||
|
],
|
||||||
|
"uvlock": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Model (JSON)
|
||||||
|
Формат описывает геометрию и текстуры.
|
||||||
|
|
||||||
|
### Верхний уровень
|
||||||
|
- `gui_light`: строка (сейчас используется только `default`)
|
||||||
|
- `ambient_occlusion`: bool
|
||||||
|
- `display`: объект с наборами `rotation`/`translation`/`scale` (все — массивы из 3 чисел)
|
||||||
|
- `textures`: объект `name -> string` (ссылка на текстуру или pipeline)
|
||||||
|
- `cuboids`: массив геометрических блоков
|
||||||
|
- `sub_models`: массив подмоделей
|
||||||
|
|
||||||
|
### Текстуры
|
||||||
|
В `textures` значение:
|
||||||
|
- либо строка `domain:key` (прямая ссылка на текстуру),
|
||||||
|
- либо pipeline-строка, начинающаяся с `tex` (компилируется `TexturePipelineProgram`).
|
||||||
|
|
||||||
|
Если домен не указан, используется домен файла модели.
|
||||||
|
|
||||||
|
### Cuboids
|
||||||
|
Элемент `cuboids`:
|
||||||
|
- `shade`: bool (по умолчанию `true`)
|
||||||
|
- `from`: `[x, y, z]`
|
||||||
|
- `to`: `[x, y, z]`
|
||||||
|
- `faces`: объект граней (`down|up|north|south|west|east`)
|
||||||
|
- `transformations`: массив `"key=value"` (ключи как у nodestate)
|
||||||
|
|
||||||
|
Грань (`faces.<name>`) может содержать:
|
||||||
|
- `uv`: `[u0, v0, u1, v1]`
|
||||||
|
- `texture`: строка (ключ из `textures`)
|
||||||
|
- `cullface`: `down|up|north|south|west|east`
|
||||||
|
- `tintindex`: int
|
||||||
|
- `rotation`: int16
|
||||||
|
|
||||||
|
### Sub-models
|
||||||
|
`sub_models` допускает:
|
||||||
|
- строку `domain:key`
|
||||||
|
- объект `{ "model": "domain:key", "scene": 0 }`
|
||||||
|
- объект `{ "path": "domain:key", "scene": 0 }`
|
||||||
|
|
||||||
|
Поле `scene` опционально.
|
||||||
|
|
||||||
|
### Пример
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ambient_occlusion": true,
|
||||||
|
"textures": {
|
||||||
|
"all": "core:stone"
|
||||||
|
},
|
||||||
|
"cuboids": [
|
||||||
|
{
|
||||||
|
"from": [0, 0, 0],
|
||||||
|
"to": [16, 16, 16],
|
||||||
|
"faces": {
|
||||||
|
"north": { "uv": [0, 0, 16, 16], "texture": "#all" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sub_models": [
|
||||||
|
"core:stone_detail",
|
||||||
|
{ "model": "core:stone_variant", "scene": 1 }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Model (glTF / GLB)
|
||||||
|
Файлы моделей могут быть:
|
||||||
|
- `.gltf` (JSON glTF)
|
||||||
|
- `.glb` (binary glTF)
|
||||||
|
|
||||||
|
Оба формата конвертируются в `PreparedGLTF`.
|
||||||
|
|
||||||
|
## Texture
|
||||||
|
Поддерживаются только PNG и JPEG.
|
||||||
|
Формат определяется по сигнатуре файла.
|
||||||
|
|
||||||
|
## Прочие типы ресурсов
|
||||||
|
Для `particle`, `animation`, `sound`, `font` загрузка из файловой системы
|
||||||
|
в серверном загрузчике пока не реализована (`std::unreachable()`), но возможна
|
||||||
|
регистрация из Lua через `path` (сырые бинарные данные).
|
||||||
66
docs/assets_manager.md
Normal file
66
docs/assets_manager.md
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
# 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()` добавляет ресурсы в память и прокидывает их в кэш.
|
||||||
|
|
||||||
|
## Ограничения
|
||||||
|
|
||||||
|
- Класс не предназначен для внешнего многопоточного использования.
|
||||||
|
- Политика приоритета ресурсов в паке фиксированная: первый найденный ключ побеждает.
|
||||||
|
- Коллизии хешей не обрабатываются отдельно.
|
||||||
105
docs/texture_pipeline.md
Normal file
105
docs/texture_pipeline.md
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
# Текстурные программы (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() )
|
||||||
|
```
|
||||||
Reference in New Issue
Block a user