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