Доработка пайплайн машины (требуется пересмотр технологии)

This commit is contained in:
2026-01-03 00:41:09 +06:00
parent f56b46f669
commit 776e9bfaca
31 changed files with 2684 additions and 601 deletions

View File

@@ -1,4 +1,5 @@
#include "Abstract.hpp"
#include "Client/Vulkan/AtlasPipeline/TexturePipelineProgram.hpp"
#include "Common/Net.hpp"
#include "TOSLib.hpp"
#include <boost/interprocess/file_mapping.hpp>
@@ -6,6 +7,8 @@
#include "boost/json.hpp"
#include "sha2.hpp"
#include <algorithm>
#include <cctype>
#include <cstring>
#include <boost/iostreams/filtering_streambuf.hpp>
#include <boost/iostreams/copy.hpp>
#include <boost/iostreams/filter/zlib.hpp>
@@ -15,6 +18,7 @@
#include <sstream>
#include <string>
#include <string_view>
#include <unordered_set>
#include <utility>
@@ -22,6 +26,50 @@ namespace LV {
namespace fs = std::filesystem;
PrecompiledTexturePipeline compileTexturePipeline(const std::string &cmd, std::string_view defaultDomain) {
PrecompiledTexturePipeline result;
std::string_view view(cmd);
const size_t trimPos = view.find_first_not_of(" \t\r\n");
if(trimPos == std::string_view::npos)
MAKE_ERROR("Пустая текстурная команда");
view = view.substr(trimPos);
const bool isPipeline = view.size() >= 3
&& view.compare(0, 3, "tex") == 0
&& (view.size() == 3 || std::isspace(static_cast<unsigned char>(view[3])));
if(!isPipeline) {
auto [domain, key] = parseDomainKey(std::string(view), defaultDomain);
result.Assets.emplace_back(std::move(domain), std::move(key));
return result;
}
TexturePipelineProgram program;
std::string err;
if(!program.compile(std::string(view), &err)) {
MAKE_ERROR("Ошибка разбора pipeline: " << err);
}
result.IsSource = true;
result.Pipeline.assign(reinterpret_cast<const char8_t*>(view.data()), view.size());
std::unordered_set<std::string> seen;
for(const auto& patch : program.patches()) {
auto [domain, key] = parseDomainKey(patch.Name, defaultDomain);
std::string token;
token.reserve(domain.size() + key.size() + 1);
token.append(domain);
token.push_back(':');
token.append(key);
if(seen.insert(token).second)
result.Assets.emplace_back(std::move(domain), std::move(key));
}
return result;
}
CompressedVoxels compressVoxels_byte(const std::vector<VoxelCube>& voxels) {
std::u8string compressed;
@@ -1089,6 +1137,10 @@ uint16_t PreparedNodeState::parseCondition(const std::string_view expression) {
};
std::vector<std::variant<EnumTokenKind, std::string_view, int, uint16_t>> tokens;
if(expression.empty())
tokens.push_back(int(1));
ssize_t pos = 0;
auto skipWS = [&](){ while(pos<expression.size() && std::isspace((unsigned char) expression[pos])) ++pos; };

View File

@@ -516,6 +516,8 @@ struct PrecompiledTexturePipeline {
std::vector<std::pair<std::string, std::string>> Assets;
// Чистый код текстурных преобразований, локальные идентификаторы связаны с Assets
std::u8string Pipeline;
// Pipeline содержит исходный текст (tex ...), нужен для компиляции на сервере
bool IsSource = false;
};
struct TexturePipeline {
@@ -530,15 +532,7 @@ struct TexturePipeline {
};
// Компилятор текстурных потоков
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;
}
PrecompiledTexturePipeline compileTexturePipeline(const std::string &cmd, std::string_view defaultDomain = "core");
struct NodestateEntry {
std::string Name;

View File

@@ -0,0 +1,253 @@
#pragma once
#include <cstdint>
#include <filesystem>
#include <stdexcept>
#include <unordered_map>
#include "Common/Async.hpp"
#include "TOSAsync.hpp"
#include "boost/asio/executor.hpp"
#include "boost/asio/experimental/channel.hpp"
#include "boost/asio/this_coro.hpp"
#include "sha2.hpp"
/*
Класс отвечает за отслеживание изменений и подгрузки медиаресурсов в указанных директориях.
Медиаресурсы, собранные из папки assets или зарегистрированные модами.
Хранит все данные в оперативной памяти.
*/
enum class EnumAssets : int {
Nodestate, Particle, Animation, Model, Texture, Sound, Font, MAX_ENUM
};
using AssetsNodestate = uint32_t;
using AssetsParticle = uint32_t;
using AssetsAnimation = uint32_t;
using AssetsModel = uint32_t;
using AssetsTexture = uint32_t;
using AssetsSound = uint32_t;
using AssetsFont = uint32_t;
static constexpr const char* EnumAssetsToDirectory(EnumAssets value) {
switch(value) {
case EnumAssets::Nodestate: return "nodestate";
case EnumAssets::Particle: return "particles";
case EnumAssets::Animation: return "animations";
case EnumAssets::Model: return "models";
case EnumAssets::Texture: return "textures";
case EnumAssets::Sound: return "sounds";
case EnumAssets::Font: return "fonts";
default:
}
assert(!"Неизвестный тип медиаресурса");
}
namespace LV {
namespace fs = std::filesystem;
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());
}
};
class AssetsPreloader : public TOS::IAsyncDestructible {
public:
using Ptr = std::shared_ptr<AssetsPreloader>;
//
struct ReloadResult {
};
struct ReloadStatus {
/// TODO: callback'и для обновления статусов
/// TODO: многоуровневый статус std::vector<std::string>. Этапы/Шаги/Объекты
};
public:
static coro<Ptr> Create(asio::io_context& ioc);
~AssetsPreloader() = default;
AssetsPreloader(const AssetsPreloader&) = delete;
AssetsPreloader(AssetsPreloader&&) = delete;
AssetsPreloader& operator=(const AssetsPreloader&) = delete;
AssetsPreloader& operator=(AssetsPreloader&&) = delete;
// Пересматривает ресурсы и выдаёт изменения.
// Одновременно можно работать только один такой вызов.
// instances -> пути к директории с assets или архивы с assets внутри. От низшего приоритета к высшему.
// status -> обратный отклик о процессе обновления ресурсов.
// ReloadStatus <- новые и потерянные ресурсы.
coro<ReloadResult> reloadResources(const std::vector<fs::path>& instances, ReloadStatus* status = nullptr) {
bool expected = false;
assert(Reloading_.compare_exchange_strong(expected, true) && "Двойной вызов reloadResources");
try {
ReloadStatus secondStatus;
co_return _reloadResources(instances, status ? *status : secondStatus);
} catch(...) {
assert(!"reloadResources: здесь не должно быть ошибок");
}
Reloading_.exchange(false);
}
private:
struct ResourceFirstStageInfo {
// Путь к архиву (если есть), и путь до ресурса
fs::path ArchivePath, Path;
// Время изменения файла
fs::file_time_type Timestamp;
};
struct ResourceSecondStageInfo : public ResourceFirstStageInfo {
// Обезличенный ресурс
std::shared_ptr<std::vector<uint8_t>> Resource;
ResourceFile::Hash_t Hash;
// Сырой заголовок
std::vector<std::string> Dependencies;
};
/*
Ресурс имеет бинарную часть, из который вырезаны все зависимости.
Вторая часть это заголовок, которые всегда динамично передаётся с сервера.
В заголовке хранятся зависимости от ресурсов.
*/
struct MediaResource {
std::string Domain, Key;
fs::file_time_type Timestamp;
// Обезличенный ресурс
std::shared_ptr<std::vector<uint8_t>> Resource;
// Хэш ресурса
ResourceFile::Hash_t Hash;
// Скомпилированный заголовок
std::vector<uint8_t> Dependencies;
};
AssetsPreloader(asio::io_context& ioc)
: TOS::IAsyncDestructible(ioc)
{
}
// Текущее состояние reloadResources
std::atomic<bool> Reloading_ = false;
// Пересмотр ресурсов
coro<ReloadResult> _reloadResources(const std::vector<fs::path>& instances, ReloadStatus& status) const {
// 1) Поиск всех ресурсов и построение конечной карты ресурсов (timestamps, path, name, size)
// Карта найденных ресурсов
std::unordered_map<
EnumAssets, // Тип ресурса
std::unordered_map<
std::string, // Domain
std::unordered_map<
std::string, // Key
ResourceFirstStageInfo // ResourceInfo
>
>
> resourcesFirstStage;
for (const fs::path& instance : instances) {
try {
if (fs::is_regular_file(instance)) {
// Может архив
/// TODO: пока не поддерживается
} else if (fs::is_directory(instance)) {
// Директория
fs::path assets = instance / "assets";
if (fs::exists(assets) && fs::is_directory(assets)) {
// Директорию assets существует, перебираем домены в ней
for (auto begin = fs::directory_iterator(assets), end = fs::directory_iterator(); begin != end; begin++) {
if (!begin->is_directory())
continue;
/// TODO: выглядит всё не очень асинхронно
co_await asio::post(co_await asio::this_coro::executor);
fs::path domainPath = begin->path();
std::string domain = domainPath.filename();
// Перебираем по типу ресурса
for (EnumAssets assetType = EnumAssets(0); assetType < EnumAssets::MAX_ENUM; ((int&) assetType)++) {
fs::path assetPath = domainPath / EnumAssetsToDirectory(assetType);
std::unordered_map<
std::string, // Key
ResourceFirstStageInfo // ResourceInfo
>& firstStage = resourcesFirstStage[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();
std::string key = fs::relative(file, domainPath).string();
// Работаем с ресурсом
firstStage[key] = ResourceFirstStageInfo{
.Path = file,
.Timestamp = fs::last_write_time(file)
};
}
}
}
}
} else {
throw std::runtime_error("Неизвестный тип инстанса медиаресурсов");
}
} catch (const std::exception& exc) {
/// TODO: Логгировать в статусе
}
}
// 2) Обрабатываться будут только изменённые (новый timestamp) или новые ресурсы
// .meta
// Текстуры, шрифты, звуки хранить как есть
// У моделей, состояний нод, анимации, частиц обналичить зависимости
// Мета влияет только на хедер
/// TODO: реализовать реформатирование новых и изменённых ресурсов во внутренний обезличенный формат
co_await asio::post(co_await asio::this_coro::executor);
asio::experimental::channel<void()> ch(IOC, 8);
co_return ReloadResult{};
}
std::unordered_map<
EnumAssets, // Тип ресурса
std::unordered_map<
std::string, // Domain
std::unordered_map<
std::string, // Key
uint32_t // ResourceId
>
>
> DKToId;
std::unordered_map<
EnumAssets, // Тип ресурса
std::unordered_map<
uint32_t,
MediaResource // ResourceInfo
>
> MediaResources;
};
}