Состояния нод на стороне сервера

This commit is contained in:
2025-08-31 20:22:59 +06:00
parent f745f58a31
commit 2dd3ea60d7
5 changed files with 355 additions and 102 deletions

View File

@@ -2,6 +2,7 @@
#include "Common/Abstract.hpp"
#include "boost/json.hpp"
#include "png++/rgb_pixel.hpp"
#include <algorithm>
#include <exception>
#include <filesystem>
#include <png.h>
@@ -14,25 +15,34 @@
namespace LV::Server {
PreparedModelCollision::PreparedModelCollision(const PreparedModel& model) {
PreparedModel::PreparedModel(const std::string& domain, const LV::PreparedModel& model) {
Cuboids.reserve(model.Cuboids.size());
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;
for(auto& [key, cmd] : model.Textures) {
PrecompiledTexturePipeline ptp = compileTexturePipeline(cmd, domain);
for(auto& [domain, key] : ptp.Assets) {
TextureDependencies[domain].push_back(key);
}
}
SubModels = model.SubModels;
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;
// }
}
PreparedModelCollision::PreparedModelCollision(const std::string& domain, const js::object& glTF) {
PreparedModel::PreparedModel(const std::string& domain, const js::object& glTF) {
// gltf
// Сцена по умолчанию
@@ -45,7 +55,7 @@ PreparedModelCollision::PreparedModelCollision(const std::string& domain, const
// Буферы
}
PreparedModelCollision::PreparedModelCollision(const std::string& domain, Resource glb) {
PreparedModel::PreparedModel(const std::string& domain, Resource glb) {
}
@@ -104,26 +114,26 @@ void AssetsManager::loadResourceFromFile_Model(ResourceChangeObj& out, const std
Resource res(path);
std::filesystem::file_time_type ftt = fs::last_write_time(path);
PreparedModel pmc;
if(path.extension() == "json") {
js::object obj = js::parse(std::string_view((const char*) res.data(), res.size())).as_object();
PreparedModel pm(domain, obj);
PreparedModelCollision pmc(pm);
LV::PreparedModel pm(domain, obj);
std::u8string data = pm.dump();
out.Models[domain].emplace_back(key, pmc);
pmc = PreparedModel(domain, pm);
out.NewOrChange[(int) EnumAssets::Model][domain].emplace_back(key, Resource((const uint8_t*) data.data(), data.size()), ftt);
} else if(path.extension() == "gltf") {
js::object obj = js::parse(std::string_view((const char*) res.data(), res.size())).as_object();
PreparedModelCollision pmc(domain, obj);
out.Models[domain].emplace_back(key, pmc);
pmc = PreparedModel(domain, obj);
out.NewOrChange[(int) EnumAssets::Model][domain].emplace_back(key, res, ftt);
} else if(path.extension() == "glb") {
PreparedModelCollision pmc(domain, res);
out.Models[domain].emplace_back(key, pmc);
pmc = PreparedModel(domain, res);
out.NewOrChange[(int) EnumAssets::Model][domain].emplace_back(key, res, ftt);
} else {
MAKE_ERROR("Не поддерживаемый формат файла");
}
out.Models[domain].emplace_back(key, pmc);
}
void AssetsManager::loadResourceFromFile_Texture(ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const {
@@ -436,14 +446,21 @@ AssetsManager::Out_applyResourceChange AssetsManager::applyResourceChange(const
assert(iter != keyToIdDomain.end());
ResourceId resId = iter->second;
// keyToIdDomain.erase(iter);
// lost[type].push_back(resId);
uint32_t localId = resId % TableEntry<DataEntry>::ChunkSize;
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->IsFull = false;
// chunk->Empty.set(localId);
chunk->Entries[localId].reset();
chunk->Entries[resId % TableEntry<DataEntry>::ChunkSize].reset();
}
}
}
@@ -484,6 +501,130 @@ AssetsManager::Out_applyResourceChange AssetsManager::applyResourceChange(const
std::set_difference(l.begin(), l.end(), noc.begin(), noc.end(), std::back_inserter(result.Lost[type]));
}
if(!orr.Nodestates.empty())
{
auto lock = LocalObj.lock();
for(auto& [domain, table] : orr.Nodestates) {
for(auto& [key, value] : table) {
ResourceId resId = lock->getId(EnumAssets::Nodestate, domain, key);
std::vector<AssetsModel> models;
for(auto& [domain, key] : value.ModelToLocalId) {
models.push_back(lock->getId(EnumAssets::Model, domain, key));
}
{
std::sort(models.begin(), models.end());
auto iterErase = std::unique(models.begin(), models.end());
models.erase(iterErase, models.end());
models.shrink_to_fit();
}
lock->Table_NodeState[resId / TableEntry<DataEntry>::ChunkSize]
->Entries[resId % TableEntry<DataEntry>::ChunkSize] = std::move(models);
}
}
}
if(!orr.Models.empty())
{
auto lock = LocalObj.lock();
for(auto& [domain, table] : orr.Models) {
for(auto& [key, value] : table) {
ResourceId resId = lock->getId(EnumAssets::Model, domain, key);
ModelDependency deps;
for(auto& [domain, list] : value.ModelDependencies) {
ResourceId subResId = lock->getId(EnumAssets::Model, domain, key);
deps.ModelDeps.push_back(subResId);
}
for(auto& [domain, list] : value.TextureDependencies) {
ResourceId subResId = lock->getId(EnumAssets::Texture, domain, key);
deps.TextureDeps.push_back(subResId);
}
lock->Table_Model[resId / TableEntry<DataEntry>::ChunkSize]
->Entries[resId % TableEntry<DataEntry>::ChunkSize] = std::move(deps);
}
}
}
// Вычислить зависимости моделей
{
// Затираем старые данные
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 = [&](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(!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);
}
}
{
std::sort(entry.FullSubTextureDeps.begin(), entry.FullSubTextureDeps.end());
auto eraseIter = std::unique(entry.FullSubTextureDeps.begin(), entry.FullSubTextureDeps.end());
entry.FullSubTextureDeps.erase(eraseIter, entry.FullSubTextureDeps.end());
entry.FullSubTextureDeps.shrink_to_fit();
}
{
std::sort(entry.FullSubModelDeps.begin(), entry.FullSubModelDeps.end());
auto eraseIter = std::unique(entry.FullSubModelDeps.begin(), entry.FullSubModelDeps.end());
entry.FullSubModelDeps.erase(eraseIter, entry.FullSubModelDeps.end());
entry.FullSubModelDeps.shrink_to_fit();
}
entry.Ready = true;
};
ssize_t iter = -1;
for(auto& entriesChunk : lock->Table_Model) {
for(auto& entry : entriesChunk->Entries) {
iter++;
if(!entry || entry->Ready)
continue;
// Собираем зависимости
calcDeps(iter, *entry);
}
}
}
return result;
}

View File

@@ -8,6 +8,7 @@
#include <filesystem>
#include <optional>
#include <unordered_map>
#include <unordered_set>
#include <variant>
@@ -17,31 +18,40 @@ namespace fs = std::filesystem;
/*
Используется для расчёта коллизии,
если это необходимо.
glTF конвертируется в кубы
если это необходимо, а также зависимостей к ассетам.
*/
struct PreparedModelCollision {
struct Cuboid {
glm::vec3 From, To;
uint8_t Faces;
std::vector<PreparedModel::Cuboid::Transformation> Transformations;
};
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;
std::vector<Cuboid> Cuboids;
std::vector<PreparedModel::SubModel> SubModels;
PreparedModel(const std::string& domain, const LV::PreparedModel& model);
PreparedModel(const std::string& domain, const js::object& glTF);
PreparedModel(const std::string& domain, Resource glb);
PreparedModelCollision(const PreparedModel& model);
PreparedModelCollision(const std::string& domain, const js::object& glTF);
PreparedModelCollision(const std::string& domain, Resource glb);
PreparedModel() = default;
PreparedModel(const PreparedModel&) = default;
PreparedModel(PreparedModel&&) = default;
PreparedModelCollision() = default;
PreparedModelCollision(const PreparedModelCollision&) = default;
PreparedModelCollision(PreparedModelCollision&&) = default;
PreparedModel& operator=(const PreparedModel&) = default;
PreparedModel& operator=(PreparedModel&&) = default;
};
PreparedModelCollision& operator=(const PreparedModelCollision&) = default;
PreparedModelCollision& operator=(PreparedModelCollision&&) = 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;
};
/*
@@ -59,7 +69,7 @@ public:
std::unordered_map<std::string, std::vector<std::pair<std::string, PreparedNodeState>>> Nodestates;
std::unordered_map<std::string, std::vector<std::pair<std::string, PreparedModelCollision>>> Models;
std::unordered_map<std::string, std::vector<std::pair<std::string, PreparedModel>>> Models;
};
private:
@@ -76,7 +86,7 @@ private:
static constexpr size_t ChunkSize = 4096;
bool IsFull = false;
std::bitset<ChunkSize> Empty;
std::array<std::optional<DataEntry>, ChunkSize> Entries;
std::array<std::optional<T>, ChunkSize> Entries;
TableEntry() {
Empty.set();
@@ -87,14 +97,64 @@ private:
// Связь ресурсов по идентификаторам
std::vector<std::unique_ptr<TableEntry<DataEntry>>> Table[(int) EnumAssets::MAX_ENUM];
// Распаршенные ресурсы, для использования сервером
std::vector<std::unique_ptr<TableEntry<PreparedNodeState>>> Table_NodeState;
std::vector<std::unique_ptr<TableEntry<PreparedModelCollision>>> Table_Model;
// Распаршенные ресурсы, для использования сервером (сбор зависимостей профиля нод и расчёт коллизии если нужно)
// Первичные зависимости 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::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;
// Расширяем таблицу с ресурсами, если необходимо
ssize_t tableChunks = (ssize_t) (id/TableEntry<DataEntry>::ChunkSize)-(ssize_t) Table[(int) type].size()+1;
for(; tableChunks > 0; tableChunks--) {
Table[(int) type].emplace_back(std::make_unique<TableEntry<DataEntry>>());
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;
}
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;
}
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;
@@ -167,28 +227,56 @@ public:
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::tuple<AssetsNodestate, std::vector<AssetsModel>, std::vector<AssetsTexture>>
getNodeDependency(const std::string& domain, const std::string& key)
{
auto lock = LocalObj.lock();
auto& keyToId = lock->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;
AssetsNodestate nodestateId = lock->getId(EnumAssets::Nodestate, domain, key);
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);
}
}
auto [id, entry] = lock->nextId(type);
keyToId[domain][key] = id;
return id;
{
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)};
}
std::optional<std::tuple<Resource, const std::string&, const std::string&>> getResource(EnumAssets type, ResourceId id) {
auto lock = LocalObj.lock();
assert(id < lock->Table[(int) type].size()*TableEntry<DataEntry>::ChunkSize);
auto& value = lock->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;
}
private:
TOS::Logger LOG = "Server>AssetsManager";
};
}

View File

@@ -1512,6 +1512,15 @@ void GameServer::prerun() {
}
void GameServer::run() {
{
IWorldSaveBackend::TickSyncInfo_In in;
for(int x = -1; x <= 1; x++)
for(int y = -1; y <= 1; y++)
for(int z = -1; z <= 1; z++)
in.Load[0].push_back(Pos::GlobalChunk(x, y, z));
stepGeneratorAndLuaAsync(SaveBackend.World->tickSync(std::move(in)));
}
while(true) {
((uint32_t&) Game.AfterStartTime) += (uint32_t) (CurrentTickDuration*256);