#include "package_socket_plug_build.h" #include #include #include "../../../../state/build_data/runtime.h" namespace sunrise::client::content::items::packages { namespace { /** Sundial/native category families whose socket seed expands to every plug in that family. */ constexpr std::array kExpandableCategories{ 0xB134761EU, 0x87727F34U, 0x6C863692U, }; /** Tracker sockets synthesize these three safe plug choices by socket type. */ constexpr std::array kTrackerPlugHashes{ 2'285'418'970U, 2'302'094'943U, 38'912'240U, }; /** Native ordinary socket type whose choices are the synthetic tracker set. */ constexpr std::uint16_t kTrackerSocketType = 518; /** FNV-1a constants make pool fingerprints stable and cheap. */ constexpr std::uint64_t kHashOffsetBasis = 14695981039346656037ULL; constexpr std::uint64_t kHashPrime = 1099511628211ULL; /** Visitor adapter that appends one list member to a bounded lane candidate. */ struct VisitorContext { SocketPlugBuild* build{}; std::size_t itemDefinitionCount{}; }; /** @return Whether the package-provided member was accepted into bounded scratch. */ [[nodiscard]] bool visit_member(void* opaque, std::uint32_t itemDefinitionIndex) noexcept { auto& context = *static_cast(opaque); return context.build != nullptr && context.build->add(itemDefinitionIndex, context.itemDefinitionCount); } } // namespace /** Returns the compact 1-based code of one native category-expansion family. */ std::uint8_t special_plug_category(std::uint32_t categoryHash) noexcept { for (std::size_t index = 0; index < kExpandableCategories.size(); ++index) { if (kExpandableCategories[index] == categoryHash) { return static_cast(index + 1); } } return 0; } /** Allocates the bounded build state and indexes expansion/tracker plug definitions. */ bool SocketPlugBuild::prepare( std::span specialCategories, std::span itemDefinitions) noexcept { release(); if (specialCategories.size() < itemDefinitions.size() || itemDefinitions.size() > state::build_data::items::kDefinitionCapacity) { return false; } rules_.assign(socket_plugs::kRuleCapacity, {}); pools_.assign(socket_plugs::kPoolCapacity, {}); members_.assign(socket_plugs::kMemberCapacity, {}); candidates_.assign(state::build_data::items::kDefinitionCapacity, {}); categoryMembers_.assign(kCategoryCount * state::build_data::items::kDefinitionCapacity, {}); lookup_.assign(kLookupCapacity, {}); pools_[socket_plugs::kEmptyPoolIndex] = {}; poolCount_ = 1; for (std::size_t item = 0; item < itemDefinitions.size(); ++item) { const std::uint8_t category = specialCategories[item]; if (category != 0 && category <= kCategoryCount) { const std::size_t family = category - 1; categoryMembers_[family * state::build_data::items::kDefinitionCapacity + categoryCounts_[family]++] = static_cast(item); } for (const std::uint32_t trackerHash : kTrackerPlugHashes) { if (itemDefinitions[item].definitionHash != trackerHash) { continue; } if (trackerCount_ >= trackerMembers_.size()) { release(); return false; } trackerMembers_[trackerCount_++] = static_cast(item); } } return true; } /** Appends one package member after enforcing the installed item-table bound. */ bool SocketPlugBuild::add(std::uint32_t itemDefinitionIndex, std::size_t itemDefinitionCount) noexcept { if (candidates_.empty() || itemDefinitionIndex >= itemDefinitionCount || itemDefinitionIndex >= state::build_data::items::kDefinitionCapacity || candidateCount_ >= state::build_data::items::kDefinitionCapacity) { return false; } candidates_[candidateCount_++] = static_cast(itemDefinitionIndex); return true; } /** Expands special category seeds, sorts/deduplicates, then interns one exact pool. */ bool SocketPlugBuild::intern(std::uint32_t& poolIndex) noexcept { poolIndex = socket_plugs::kEmptyPoolIndex; if (candidates_.empty() || categoryMembers_.empty() || lookup_.empty()) { return false; } std::sort(candidates_.data(), candidates_.data() + candidateCount_); candidateCount_ = static_cast( std::unique(candidates_.data(), candidates_.data() + candidateCount_) - candidates_.data()); std::array expand{}; // Category codes were indexed by native item definition index during prepare(). for (std::size_t family = 0; family < kCategoryCount; ++family) { const auto* familyMembers = categoryMembers_.data() + family * state::build_data::items::kDefinitionCapacity; for (std::size_t seed = 0; seed < candidateCount_ && !expand[family]; ++seed) { expand[family] = std::binary_search( familyMembers, familyMembers + categoryCounts_[family], candidates_[seed]); } } for (std::size_t family = 0; family < kCategoryCount; ++family) { if (!expand[family]) { continue; } if (categoryCounts_[family] > state::build_data::items::kDefinitionCapacity - candidateCount_) { return false; } const auto* first = categoryMembers_.data() + family * state::build_data::items::kDefinitionCapacity; std::copy_n(first, categoryCounts_[family], candidates_.data() + candidateCount_); candidateCount_ += categoryCounts_[family]; } std::sort(candidates_.data(), candidates_.data() + candidateCount_); candidateCount_ = static_cast( std::unique(candidates_.data(), candidates_.data() + candidateCount_) - candidates_.data()); if (candidateCount_ == 0) { return true; } std::uint64_t fingerprint = kHashOffsetBasis; for (std::size_t member = 0; member < candidateCount_; ++member) { std::uint16_t value = candidates_[member]; for (std::size_t byte = 0; byte < sizeof value; ++byte) { fingerprint ^= static_cast(value); fingerprint *= kHashPrime; value >>= 8U; } } fingerprint ^= candidateCount_; fingerprint *= kHashPrime; static_assert((kLookupCapacity & (kLookupCapacity - 1)) == 0); const std::size_t start = static_cast(fingerprint) & (kLookupCapacity - 1); for (std::size_t probe = 0; probe < kLookupCapacity; ++probe) { PoolLookup& slot = lookup_[(start + probe) & (kLookupCapacity - 1)]; if (slot.poolIndex == UINT32_MAX) { if (poolCount_ >= socket_plugs::kPoolCapacity || candidateCount_ > socket_plugs::kMemberCapacity - memberCount_) { return false; } poolIndex = static_cast(poolCount_); pools_[poolCount_++] = {static_cast(memberCount_), static_cast(candidateCount_)}; std::copy_n(candidates_.data(), candidateCount_, members_.data() + memberCount_); memberCount_ += candidateCount_; slot = {fingerprint, poolIndex}; return true; } if (slot.fingerprint != fingerprint || slot.poolIndex >= poolCount_) { continue; } const socket_plugs::Pool& pool = pools_[slot.poolIndex]; if (pool.memberCount == candidateCount_ && std::equal(candidates_.data(), candidates_.data() + candidateCount_, members_.data() + pool.memberOffset)) { poolIndex = slot.poolIndex; return true; } } return false; } /** Extracts every exact ordinary socket pool of one installed item definition. */ bool SocketPlugBuild::append(const tables::items::Row& item, std::span itemDefinition, std::span plugSetTable, std::size_t itemDefinitionCount) noexcept { if (rules_.empty() || item.definitionIndex >= itemDefinitionCount || item.socketCount > socket_plugs::kLaneCapacity) { return false; } bool complete = true; for (std::uint8_t lane = 0; lane < item.socketCount; ++lane) { candidateCount_ = 0; VisitorContext visitor{this, itemDefinitionCount}; bool laneValid = tables::items::visit_allowed_plugs( itemDefinition, plugSetTable, lane, visit_member, &visitor); if (laneValid && item.initialPlugs[lane] != tables::items::kUnavailablePlug) { laneValid = add(item.initialPlugs[lane], itemDefinitionCount); } if (laneValid && item.socketTypes[lane] == kTrackerSocketType) { for (std::size_t tracker = 0; tracker < trackerCount_ && laneValid; ++tracker) { laneValid = add(trackerMembers_[tracker], itemDefinitionCount); } } std::uint32_t poolIndex = socket_plugs::kEmptyPoolIndex; laneValid = laneValid && intern(poolIndex); if (!laneValid || ruleCount_ >= socket_plugs::kRuleCapacity) { ++skipped_; complete = false; continue; } rules_[ruleCount_++] = {item.definitionIndex, lane, 0, poolIndex}; } return complete; } /** Publishes the bounded relation and releases all transient interning memory. */ bool SocketPlugBuild::publish() noexcept { const bool published = !rules_.empty() && !pools_.empty() && !members_.empty() && state::build_data::publish_socket_plug_rules(std::span(rules_.data(), ruleCount_), std::span(pools_.data(), poolCount_), std::span(members_.data(), memberCount_)); release(); return published; } /** Reports how many lanes failed closed during extraction. */ std::size_t SocketPlugBuild::skipped() const noexcept { return skipped_; } std::size_t SocketPlugBuild::rule_count() const noexcept { return ruleCount_; } std::size_t SocketPlugBuild::pool_count() const noexcept { return poolCount_; } std::size_t SocketPlugBuild::member_count() const noexcept { return memberCount_; } /** Drops all heap-backed extraction scratch and resets every count. */ void SocketPlugBuild::release() noexcept { rules_.clear(); rules_.shrink_to_fit(); pools_.clear(); pools_.shrink_to_fit(); members_.clear(); members_.shrink_to_fit(); candidates_.clear(); candidates_.shrink_to_fit(); categoryMembers_.clear(); categoryMembers_.shrink_to_fit(); lookup_.clear(); lookup_.shrink_to_fit(); categoryCounts_ = {}; trackerMembers_ = {}; trackerCount_ = 0; ruleCount_ = 0; poolCount_ = 0; memberCount_ = 0; candidateCount_ = 0; skipped_ = 0; } } // namespace sunrise::client::content::items::packages