#pragma once #include "TOSLib.hpp" #include "boost/json/array.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include 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{}(sv); } size_t operator()(const std::string& s) const noexcept { return std::hash{}(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 Pos { template class BitVec3 { static_assert(std::is_integral_v, "T must be an integral type"); static_assert(BitsPerComponent > 0, "Bits per component must be at least 1"); static constexpr size_t N = 3; static constexpr auto getType() { constexpr size_t bits = N*BitsPerComponent; if constexpr(bits <= 8) return uint8_t(0); else if constexpr(bits <= 16) return uint16_t(0); else if constexpr(bits <= 32) return uint32_t(0); else if constexpr(bits <= 64) return uint64_t(0); else { static_assert("Нет подходящего хранилища"); return uint8_t(0); } } public: using Pack = decltype(getType()); using Type = T; using value_type = Type; T x : BitsPerComponent, y : BitsPerComponent, z : BitsPerComponent; public: BitVec3() = default; BitVec3(T value) : x(value), y(value), z(value) {} BitVec3(const T x, const T y, const T z) : x(x), y(y), z(z) {} template BitVec3(const glm::vec<3, vT, vQ> vec) : x(vec.x), y(vec.y), z(vec.z) {} BitVec3(const BitVec3&) = default; BitVec3(BitVec3&&) = default; BitVec3& operator=(const BitVec3&) = default; BitVec3& operator=(BitVec3&&) = default; void set(size_t index, const T value) { assert(index < N); if(index == 0) x = value; else if(index == 1) y = value; else if(index == 2) z = value; } const T get(size_t index) const { assert(index < N); if(index == 0) return x; else if(index == 1) return y; else if(index == 2) return z; return 0; } const T operator[](size_t index) const { return get(index); } Pack pack() const requires (N*BitsPerComponent <= 64) { Pack out = 0; using U = std::make_unsigned_t; for(size_t iter = 0; iter < N; iter++) { out |= Pack(U(get(iter)) & U((Pack(1) << BitsPerComponent)-1)) << BitsPerComponent*iter; } return out; } BitVec3 unpack(const Pack pack) requires (N*BitsPerComponent <= 64) { using U = std::make_unsigned_t; for(size_t iter = 0; iter < N; iter++) { set(iter, T(U((pack >> BitsPerComponent*iter) & U((Pack(1) << BitsPerComponent)-1)))); } return *this; } auto operator<=>(const BitVec3&) const = default; template operator BitVec3() const { BitVec3 out; for(size_t iter = 0; iter < N; iter++) { out.set(iter, T2(std::make_unsigned_t(std::make_unsigned_t(get(iter))))); } return out; } template operator glm::vec<3, vT, vQ>() const { return {x, y, z}; } BitVec3 operator+(const BitVec3 &other) const { BitVec3 out; for(size_t iter = 0; iter < N; iter++) out.set(iter, get(iter) + other[iter]); return out; } BitVec3& operator+=(const BitVec3 &other) { for(size_t iter = 0; iter < N; iter++) set(iter, get(iter) + other[iter]); return *this; } BitVec3 operator+(const T value) const { BitVec3 out; for(size_t iter = 0; iter < N; iter++) out.set(iter, get(iter) + value); return out; } BitVec3& operator+=(const T value) { for(size_t iter = 0; iter < N; iter++) set(iter, get(iter) + value); return *this; } BitVec3 operator-(const BitVec3 &other) const { BitVec3 out; for(size_t iter = 0; iter < N; iter++) out.set(iter, get(iter) - other[iter]); return out; } BitVec3& operator-=(const BitVec3 &other) { for(size_t iter = 0; iter < N; iter++) set(iter, get(iter) - other[iter]); return *this; } BitVec3 operator-(const T value) const { BitVec3 out; for(size_t iter = 0; iter < N; iter++) out.set(iter, get(iter) - value); return out; } BitVec3& operator-=(const T value) { for(size_t iter = 0; iter < N; iter++) set(iter, get(iter) - value); return *this; } BitVec3 operator*(const BitVec3 &other) const { BitVec3 out; for(size_t iter = 0; iter < N; iter++) out.set(iter, get(iter) * other[iter]); return out; } BitVec3& operator*=(const BitVec3 &other) { for(size_t iter = 0; iter < N; iter++) set(iter, get(iter) * other[iter]); return *this; } BitVec3 operator*(const T value) const { BitVec3 out; for(size_t iter = 0; iter < N; iter++) out.set(iter, get(iter) * value); return out; } BitVec3& operator*=(const T value) { for(size_t iter = 0; iter < N; iter++) set(iter, get(iter) * value); return *this; } BitVec3 operator/(const BitVec3 &other) const { BitVec3 out; for(size_t iter = 0; iter < N; iter++) out.set(iter, get(iter) / other[iter]); return out; } BitVec3& operator/=(const BitVec3 &other) { for(size_t iter = 0; iter < N; iter++) set(iter, get(iter) / other[iter]); return *this; } BitVec3 operator/(const T value) const { BitVec3 out; for(size_t iter = 0; iter < N; iter++) out.set(iter, get(iter) / value); return out; } BitVec3& operator/=(const T value) { for(size_t iter = 0; iter < N; iter++) set(iter, get(iter) / value); return *this; } BitVec3 operator>>(const auto offset) const { BitVec3 out; for(size_t iter = 0; iter < N; iter++) out.set(iter, get(iter) >> offset); return out; } BitVec3& operator>>=(const auto offset) { for(size_t iter = 0; iter < N; iter++) set(iter, get(iter) >> offset); return *this; } BitVec3 operator<<(const auto offset) const { BitVec3 out; for(size_t iter = 0; iter < N; iter++) out.set(iter, get(iter) << offset); return out; } BitVec3& operator<<=(const auto offset) { for(size_t iter = 0; iter < N; iter++) set(iter, get(iter) << offset); return *this; } BitVec3 operator|(const BitVec3 other) const { BitVec3 out; for(size_t iter = 0; iter < N; iter++) out.set(iter, get(iter) | other[iter]); return out; } BitVec3& operator|=(const BitVec3 other) { for(size_t iter = 0; iter < N; iter++) set(iter, get(iter) | other[iter]); return *this; } BitVec3 operator|(const T value) const { BitVec3 out; for(size_t iter = 0; iter < N; iter++) out.set(iter, get(iter) | value); return out; } BitVec3& operator|=(const T value) { for(size_t iter = 0; iter < N; iter++) set(iter, get(iter) | value); return *this; } BitVec3 operator&(const BitVec3 other) const { BitVec3 out; for(size_t iter = 0; iter < N; iter++) out.set(iter, get(iter) & other[iter]); return out; } BitVec3& operator&=(const BitVec3 other) { for(size_t iter = 0; iter < N; iter++) set(iter, get(iter) & other[iter]); return *this; } BitVec3 operator&(const T value) const { BitVec3 out; for(size_t iter = 0; iter < N; iter++) out.set(iter, get(iter) & value); return out; } BitVec3& operator&=(const T value) { for(size_t iter = 0; iter < N; iter++) set(iter, get(iter) & value); return *this; } static constexpr size_t length() { return N; } }; using bvec4i = BitVec3; using bvec4u = BitVec3; using bvec16i = BitVec3; using bvec16u = BitVec3; using bvec64i = BitVec3; using bvec64u = BitVec3; using bvec256i = BitVec3; using bvec256u = BitVec3; using bvec1024i = BitVec3; using bvec1024u = BitVec3; using bvec4096i = BitVec3; using bvec4096u = BitVec3; using GlobalVoxel = BitVec3; using GlobalNode = BitVec3; using GlobalChunk = BitVec3; using GlobalRegion = BitVec3; using Object = BitVec3; struct Object_t { // Позиции объектов целочисленные, BS единиц это один метр static constexpr int32_t BS = 4096, BS_Bit = 12; static glm::vec3 asFloatVec(const Object &obj) { return glm::vec3(float(obj[0])/float(BS), float(obj[1])/float(BS), float(obj[2])/float(BS)); } static GlobalNode asNodePos(const Object &obj) { return (GlobalNode) (obj >> BS_Bit); } static GlobalChunk asChunkPos(const Object &obj) { return (GlobalChunk) (obj >> BS_Bit >> 4); } static GlobalRegion asRegionsPos(const Object &obj) { return (GlobalRegion) (obj >> BS_Bit >> 6); } }; } using ResourceId = uint32_t; struct Resource; /* Объекты, собранные из папки assets или зарегистрированные модами. Клиент получает полную информацию о таких объектах и при надобности запрашивает получение файла. Id -> Key + SHA256 Если объекты удаляются, то сторона клиента об этом не уведомляется */ enum class EnumAssets { Nodestate, Particle, Animation, Model, Texture, Sound, Font, MAX_ENUM }; using AssetsNodestate = ResourceId; using AssetsParticle = ResourceId; using AssetsAnimation = ResourceId; using AssetsModel = ResourceId; using AssetsTexture = ResourceId; using AssetsSound = ResourceId; using AssetsFont = ResourceId; /* Определения контента, доставляются клиентам сразу */ enum class EnumDefContent { Voxel, Node, World, Portal, Entity, Item, MAX_ENUM }; using DefVoxelId = ResourceId; using DefNodeId = ResourceId; using DefWorldId = ResourceId; using DefPortalId = ResourceId; using DefEntityId = ResourceId; using DefItemId = ResourceId; /* Контент, основанный на определениях. Отдельные свойства могут менятся в самих объектах */ using WorldId_t = ResourceId; // struct LightPrism { // uint8_t R : 2, G : 2, B : 2; // }; struct VoxelCube { union { struct { DefVoxelId VoxelId : 24, Meta : 8; }; DefVoxelId Data = 0; }; Pos::bvec256u Pos, Size; // Размер+1, 0 это единичный размер auto operator<=>(const VoxelCube& other) const { if (auto cmp = Pos <=> other.Pos; cmp != 0) return cmp; if (auto cmp = Size <=> other.Size; cmp != 0) return cmp; return Data <=> other.Data; } bool operator==(const VoxelCube& other) const { return Pos == other.Pos && Size == other.Size && Data == other.Data; } }; std::u8string compressVoxels(const std::vector& voxels, bool fast = true); std::vector unCompressVoxels(const std::u8string& compressed); struct Node { union { struct { DefNodeId NodeId : 24, Meta : 8; }; DefNodeId Data; }; }; struct CompressedNodes { std::u8string Compressed; // Уникальный сортированный список идентификаторов нод std::vector Defines; }; std::u8string compressNodes(const Node* nodes, bool fast = true); void unCompressNodes(std::u8string_view compressed, Node* ptr); std::u8string compressLinear(std::u8string_view data); std::u8string unCompressLinear(std::u8string_view data); inline std::pair 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 parseDomainKey(const std::string& value, const std::string_view defaultDomain = "core") { auto regResult = TOS::Str::match(value, "(?:([\\w\\d_]+):)?([\\w\\d/_.]+)"); if(!regResult) MAKE_ERROR("Недействительный домен:ключ"); if(regResult->at(1)) { return std::pair{*regResult->at(1), *regResult->at(2)}; } else { return std::pair{defaultDomain, *regResult->at(2)}; } } struct ResourceFile { using Hash_t = std::array; Hash_t Hash; std::u8string Data; static Hash_t calcHash(const char8_t* data, size_t size); void calcHash() { Hash = calcHash(Data.data(), Data.size()); } }; struct NodestateEntry { std::string Name; int Variability = 0; // Количество возможный значений состояния std::vector ValueNames; // Имена состояний, если имеются }; struct Vertex { glm::vec3 Pos; glm::vec2 UV; uint32_t TexId; }; struct Transformation { enum EnumTransform { MoveX, MoveY, MoveZ, RotateX, RotateY, RotateZ, ScaleX, ScaleY, ScaleZ, MAX_ENUM } Op; float Value; }; struct Transformations { std::vector OPs; void apply(std::vector& vertices) const { if (vertices.empty() || OPs.empty()) return; glm::mat4 transform(1.0f); for (const auto& op : OPs) { switch (op.Op) { case Transformation::MoveX: transform = glm::translate(transform, glm::vec3(op.Value, 0.0f, 0.0f)); break; case Transformation::MoveY: transform = glm::translate(transform, glm::vec3(0.0f, op.Value, 0.0f)); break; case Transformation::MoveZ: transform = glm::translate(transform, glm::vec3(0.0f, 0.0f, op.Value)); break; case Transformation::ScaleX: transform = glm::scale(transform, glm::vec3(op.Value, 1.0f, 1.0f)); break; case Transformation::ScaleY: transform = glm::scale(transform, glm::vec3(1.0f, op.Value, 1.0f)); break; case Transformation::ScaleZ: transform = glm::scale(transform, glm::vec3(1.0f, 1.0f, op.Value)); break; case Transformation::RotateX: transform = glm::rotate(transform, op.Value, glm::vec3(1.0f, 0.0f, 0.0f)); break; case Transformation::RotateY: transform = glm::rotate(transform, op.Value, glm::vec3(0.0f, 1.0f, 0.0f)); break; case Transformation::RotateZ: transform = glm::rotate(transform, op.Value, glm::vec3(0.0f, 0.0f, 1.0f)); break; default: break; } } std::transform( std::execution::unseq, vertices.begin(), vertices.end(), vertices.begin(), [transform](Vertex v) -> Vertex { glm::vec4 pos_h(v.Pos, 1.0f); pos_h = transform * pos_h; v.Pos = glm::vec3(pos_h) / pos_h.w; return v; } ); } std::vector apply(const std::vector& vertices) const { std::vector result = vertices; apply(result); return result; } }; struct NodeStateInfo { std::string Name; std::vector Variable; int Variations = 0; }; using ResourceHeader = std::u8string; /* Хранит распаршенное определение состояний нод. Не привязано ни к какому окружению. */ struct HeadlessNodeState { enum class Op { Add, Sub, Mul, Div, Mod, LT, LE, GT, GE, EQ, NE, And, Or, Pos, Neg, Not }; struct Node { struct Num { int32_t v; }; struct Var { std::string name; }; struct Unary { Op op; uint16_t rhs; }; struct Binary { Op op; uint16_t lhs, rhs; }; std::variant v; }; struct Model { uint16_t Id; bool UVLock = false; std::vector Transforms; }; struct VectorModel { std::vector Models; bool UVLock = false; // Может добавить возможность использовать переменную рандома в трансформациях? std::vector Transforms; }; struct Header { std::vector Models; uint16_t addModel(AssetsModel id); void load(std::u8string_view data); ResourceHeader dump() const; }; // Ноды выражений std::vector Nodes; // Условия -> вариации модели + веса boost::container::small_vector< std::pair>, 1 > > , 1> Routes; HeadlessNodeState() = default; HeadlessNodeState(const HeadlessNodeState&) = default; HeadlessNodeState(HeadlessNodeState&&) = default; HeadlessNodeState& operator=(const HeadlessNodeState&) = default; HeadlessNodeState& operator=(HeadlessNodeState&&) = default; /* Парсит json формат с выделением все зависимостей в заголовок. Требуется ресолвер идентификаторов моделей. */ ResourceHeader parse(const js::object& profile, const std::function& modelResolver); /* Парсит lua формат с выделением зависимостей в заголовок. Требуется ресолвер идентификаторов моделей. */ ResourceHeader parse(const sol::table& profile, const std::function& modelResolver); /* Загружает ресурс из двоичного формата. */ void load(std::u8string_view data); /* Транслирует в двоичный формат. */ std::u8string dump() const; // Если зависит от случайного распределения по миру bool hasVariability() const { return HasVariability; } // Возвращает идентификаторы routes прошедшии по состояниям std::vector getModelsForState(const std::vector& statesInfo, const std::unordered_map& states) { std::unordered_map values; std::vector upUse; // Проверить какие переменные упоминаются для составления таблицы быстрых значений (без обозначения <имя состояния>:<вариант состояния>) { std::vector variables; std::move_only_function lambda; lambda = [&](uint16_t nodeId) { Node& node = Nodes.at(nodeId); if(std::find(upUse.begin(), upUse.end(), nodeId) != upUse.end()) { MAKE_ERROR("Циклическая зависимость нод"); } if(Node::Var* ptr = std::get_if(&node.v)) { variables.push_back(ptr->name); } else if(Node::Unary* ptr = std::get_if(&node.v)) { upUse.push_back(nodeId); lambda(ptr->rhs); upUse.pop_back(); } else if(Node::Binary* ptr = std::get_if(&node.v)) { upUse.push_back(nodeId); lambda(ptr->lhs); lambda(ptr->rhs); upUse.pop_back(); } }; for(const auto& route : Routes) lambda(route.first); std::sort(variables.begin(), variables.end()); auto eraseIter = std::unique(variables.begin(), variables.end()); variables.erase(eraseIter, variables.end()); for(const std::string_view key : variables) { bool ok = false; if(size_t pos = key.find(':'); pos != std::string::npos) { std::string_view state, value; state = key.substr(0, pos); value = key.substr(pos+1); for(const NodeStateInfo& info : statesInfo) { if(info.Name != state) continue; for(size_t iter = 0; iter < info.Variable.size(); iter++) { if(info.Variable[iter] == value) { ok = true; values[(const std::string) key] = iter; break; } } break; } } else { for(const NodeStateInfo& info : statesInfo) { if(info.Name == key) { ok = true; values[(const std::string) key] = states.at((std::string) key); break; } for(size_t iter = 0; iter < info.Variable.size(); iter++) { if(info.Variable[iter] == key) { ok = true; values[(const std::string) key] = iter; break; } } if(ok) break; } } if(!ok) values[(const std::string) key] = 0; } } std::move_only_function calcNode; calcNode = [&](uint16_t nodeId) -> int32_t { if(std::find(upUse.begin(), upUse.end(), nodeId) != upUse.end()) { MAKE_ERROR("Циклическая зависимость нод"); } int32_t result; Node& node = Nodes.at(nodeId); if(Node::Num* ptr = std::get_if(&node.v)) { result = ptr->v; } else if(Node::Var* ptr = std::get_if(&node.v)) { result = values.at(ptr->name); } else if(Node::Unary* ptr = std::get_if(&node.v)) { int32_t rhs; upUse.push_back(nodeId); rhs = calcNode(ptr->rhs); upUse.pop_back(); if(ptr->op == Op::Not) { result = !rhs; } else if(ptr->op == Op::Pos) { result = +rhs; } else if(ptr->op == Op::Neg) { result = -rhs; } else MAKE_ERROR("Ошибка в данных"); } else if(Node::Binary* ptr = std::get_if(&node.v)) { int32_t lhs, rhs; upUse.push_back(nodeId); lhs = calcNode(ptr->lhs); rhs = calcNode(ptr->rhs); upUse.pop_back(); if(ptr->op == Op::Add) { result = lhs+rhs; } else if(ptr->op == Op::Sub) { result = lhs-rhs; } else if(ptr->op == Op::Mul) { result = lhs*rhs; } else if(ptr->op == Op::Div) { result = lhs/rhs; } else if(ptr->op == Op::Mod) { result = lhs%rhs; } else if(ptr->op == Op::And) { result = lhs && rhs; } else if(ptr->op == Op::Or) { result = lhs || rhs; } else if(ptr->op == Op::LT) { result = lhs < rhs; } else if(ptr->op == Op::LE) { result = lhs <= rhs; } else if(ptr->op == Op::GT) { result = lhs > rhs; } else if(ptr->op == Op::GE) { result = lhs >= rhs; } else if(ptr->op == Op::EQ) { result = lhs == rhs; } else if(ptr->op == Op::NE) { result = lhs != rhs; } else { MAKE_ERROR("Ошибка в данных"); } } return result; }; std::vector out; for(size_t iter = 0; iter < Routes.size(); iter++) { if(calcNode(Routes[iter].first)) { out.push_back(iter); } } return out; } private: bool HasVariability = false; uint16_t parseCondition(const std::string_view condition); std::pair> parseModel(const js::object& obj, const std::function& modelResolver); std::vector parseTransormations(const js::array& arr); }; enum class EnumFace { Down, Up, North, South, West, East, None }; /* Парсит json модель */ struct HeadlessModel { enum class EnumGuiLight { Default }; std::optional GuiLight = EnumGuiLight::Default; std::optional AmbientOcclusion = false; struct Header { std::vector Models; std::vector> TexturePipelines; uint16_t addModel(AssetsModel id); uint16_t addTexturePipeline(std::vector pipeline); void load(std::u8string_view data); ResourceHeader dump() const; }; struct FullTransformation { glm::vec3 Rotation = glm::vec3(0), Translation = glm::vec3(0), Scale = glm::vec3(1); }; std::unordered_map Display; std::unordered_map Textures; struct Cuboid { bool Shade; glm::vec3 From, To; struct Face { glm::vec4 UV; std::string Texture; EnumFace Cullface = EnumFace::None; int TintIndex = -1; int16_t Rotation = 0; }; std::unordered_map Faces; Transformations Trs; }; std::vector Cuboids; struct SubModel { uint16_t Id; std::optional Scene; }; std::vector SubModels; HeadlessModel() = default; HeadlessModel(const HeadlessModel&) = default; HeadlessModel(HeadlessModel&&) = default; HeadlessModel& operator=(const HeadlessModel&) = default; HeadlessModel& operator=(HeadlessModel&&) = default; /* Парсит json формат с выделением все зависимостей в заголовок. Требуется ресолвер идентификаторов моделей. */ ResourceHeader parse( const js::object& profile, const std::function& modelResolver, const std::function(const std::string_view texturePipelineSrc)>& textureResolver ); /* Парсит lua формат с выделением зависимостей в заголовок. Требуется ресолвер идентификаторов моделей. */ ResourceHeader parse( const sol::table& profile, const std::function& modelResolver, const std::function(const std::string_view texturePipelineSrc)>& textureResolver ); /* Загружает ресурс из двоичного формата. */ void load(std::u8string_view data); /* Транслирует в двоичный формат. */ std::u8string dump() const; }; struct TexturePipeline { std::vector BinTextures; std::vector Pipeline; bool operator==(const TexturePipeline& other) const { return BinTextures == other.BinTextures && Pipeline == other.Pipeline; } }; struct PreparedNodeState : public HeadlessNodeState { using HeadlessNodeState::Model; using HeadlessNodeState::VectorModel; std::vector LocalToModel; PreparedNodeState() = default; PreparedNodeState(std::u8string_view data) { load(data); } PreparedNodeState(const std::u8string& data) { load(data); } }; enum struct TexturePipelineCMD : uint8_t { Texture, // Указание текстуры Combine, // Комбинирование }; using Hash_t = std::array; struct Resource { private: struct InlineMMap; struct InlinePtr; std::shared_ptr> In; public: Resource() = default; Resource(std::filesystem::path path); Resource(const uint8_t* data, size_t size); Resource(const std::u8string& data); Resource(std::u8string&& data); Resource(const Resource&) = default; Resource(Resource&&) = default; Resource& operator=(const Resource&) = default; Resource& operator=(Resource&&) = default; auto operator<=>(const Resource&) const; const std::byte* data() const; size_t size() const; Hash_t hash() const; Resource convertToMem() const; operator bool() const { return (bool) In; } }; } #include namespace std { template struct hash> { std::size_t operator()(const LV::Pos::BitVec3& obj) const { std::size_t result = 0; constexpr std::size_t seed = 0x9E3779B9; for (size_t i = 0; i < 3; ++i) { T value = obj[i]; std::hash hasher; std::size_t h = hasher(value); result ^= h + seed + (result << 6) + (result >> 2); } return result; } }; template <> struct hash { std::size_t operator()(const LV::Hash_t& hash) const noexcept { std::size_t v = 14695981039346656037ULL; for (const auto& byte : hash) { v ^= static_cast(byte); v *= 1099511628211ULL; } return v; } }; template <> struct hash { std::size_t operator()(const LV::TexturePipeline& pipe) const noexcept { std::size_t v = 14695981039346656037ULL; for (uint32_t id : pipe.BinTextures) { v ^= static_cast(id); v *= 1099511628211ULL; } for (uint8_t byte : pipe.Pipeline) { v ^= static_cast(byte); v *= 1099511628211ULL; } return v; } }; }