package_socket_plug_build.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. #include "package_socket_plug_build.h"
  2. #include <algorithm>
  3. #include <limits>
  4. #include "../../../../state/build_data/runtime.h"
  5. namespace sunrise::client::content::items::packages {
  6. namespace {
  7. /** Sundial/native category families whose socket seed expands to every plug in that family. */
  8. constexpr std::array<std::uint32_t, 3> kExpandableCategories{
  9. 0xB134761EU,
  10. 0x87727F34U,
  11. 0x6C863692U,
  12. };
  13. /** Tracker sockets synthesize these three safe plug choices by socket type. */
  14. constexpr std::array<std::uint32_t, 3> kTrackerPlugHashes{
  15. 2'285'418'970U,
  16. 2'302'094'943U,
  17. 38'912'240U,
  18. };
  19. /** Native ordinary socket type whose choices are the synthetic tracker set. */
  20. constexpr std::uint16_t kTrackerSocketType = 518;
  21. /** FNV-1a constants make pool fingerprints stable and cheap. */
  22. constexpr std::uint64_t kHashOffsetBasis = 14695981039346656037ULL;
  23. constexpr std::uint64_t kHashPrime = 1099511628211ULL;
  24. /** Visitor adapter that appends one list member to a bounded lane candidate. */
  25. struct VisitorContext {
  26. SocketPlugBuild* build{};
  27. std::size_t itemDefinitionCount{};
  28. };
  29. /** @return Whether the package-provided member was accepted into bounded scratch. */
  30. [[nodiscard]] bool visit_member(void* opaque, std::uint32_t itemDefinitionIndex) noexcept {
  31. auto& context = *static_cast<VisitorContext*>(opaque);
  32. return context.build != nullptr
  33. && context.build->add(itemDefinitionIndex, context.itemDefinitionCount);
  34. }
  35. } // namespace
  36. /** Returns the compact 1-based code of one native category-expansion family. */
  37. std::uint8_t special_plug_category(std::uint32_t categoryHash) noexcept {
  38. for (std::size_t index = 0; index < kExpandableCategories.size(); ++index) {
  39. if (kExpandableCategories[index] == categoryHash) {
  40. return static_cast<std::uint8_t>(index + 1);
  41. }
  42. }
  43. return 0;
  44. }
  45. /** Allocates the bounded build state and indexes expansion/tracker plug definitions. */
  46. bool SocketPlugBuild::prepare(
  47. std::span<const std::uint8_t> specialCategories,
  48. std::span<const state::build_data::items::Definition> itemDefinitions) noexcept {
  49. release();
  50. if (specialCategories.size() < itemDefinitions.size()
  51. || itemDefinitions.size() > state::build_data::items::kDefinitionCapacity) {
  52. return false;
  53. }
  54. rules_.assign(socket_plugs::kRuleCapacity, {});
  55. pools_.assign(socket_plugs::kPoolCapacity, {});
  56. members_.assign(socket_plugs::kMemberCapacity, {});
  57. candidates_.assign(state::build_data::items::kDefinitionCapacity, {});
  58. categoryMembers_.assign(kCategoryCount * state::build_data::items::kDefinitionCapacity, {});
  59. lookup_.assign(kLookupCapacity, {});
  60. pools_[socket_plugs::kEmptyPoolIndex] = {};
  61. poolCount_ = 1;
  62. for (std::size_t item = 0; item < itemDefinitions.size(); ++item) {
  63. const std::uint8_t category = specialCategories[item];
  64. if (category != 0 && category <= kCategoryCount) {
  65. const std::size_t family = category - 1;
  66. categoryMembers_[family * state::build_data::items::kDefinitionCapacity
  67. + categoryCounts_[family]++] = static_cast<std::uint16_t>(item);
  68. }
  69. for (const std::uint32_t trackerHash : kTrackerPlugHashes) {
  70. if (itemDefinitions[item].definitionHash != trackerHash) {
  71. continue;
  72. }
  73. if (trackerCount_ >= trackerMembers_.size()) {
  74. release();
  75. return false;
  76. }
  77. trackerMembers_[trackerCount_++] = static_cast<std::uint16_t>(item);
  78. }
  79. }
  80. return true;
  81. }
  82. /** Appends one package member after enforcing the installed item-table bound. */
  83. bool SocketPlugBuild::add(std::uint32_t itemDefinitionIndex,
  84. std::size_t itemDefinitionCount) noexcept {
  85. if (candidates_.empty() || itemDefinitionIndex >= itemDefinitionCount
  86. || itemDefinitionIndex >= state::build_data::items::kDefinitionCapacity
  87. || candidateCount_ >= state::build_data::items::kDefinitionCapacity) {
  88. return false;
  89. }
  90. candidates_[candidateCount_++] = static_cast<std::uint16_t>(itemDefinitionIndex);
  91. return true;
  92. }
  93. /** Expands special category seeds, sorts/deduplicates, then interns one exact pool. */
  94. bool SocketPlugBuild::intern(std::uint32_t& poolIndex) noexcept {
  95. poolIndex = socket_plugs::kEmptyPoolIndex;
  96. if (candidates_.empty() || categoryMembers_.empty() || lookup_.empty()) {
  97. return false;
  98. }
  99. std::sort(candidates_.data(), candidates_.data() + candidateCount_);
  100. candidateCount_ = static_cast<std::size_t>(
  101. std::unique(candidates_.data(), candidates_.data() + candidateCount_) - candidates_.data());
  102. std::array<bool, kCategoryCount> expand{};
  103. // Category codes were indexed by native item definition index during prepare().
  104. for (std::size_t family = 0; family < kCategoryCount; ++family) {
  105. const auto* familyMembers =
  106. categoryMembers_.data() + family * state::build_data::items::kDefinitionCapacity;
  107. for (std::size_t seed = 0; seed < candidateCount_ && !expand[family]; ++seed) {
  108. expand[family] = std::binary_search(
  109. familyMembers, familyMembers + categoryCounts_[family], candidates_[seed]);
  110. }
  111. }
  112. for (std::size_t family = 0; family < kCategoryCount; ++family) {
  113. if (!expand[family]) {
  114. continue;
  115. }
  116. if (categoryCounts_[family]
  117. > state::build_data::items::kDefinitionCapacity - candidateCount_) {
  118. return false;
  119. }
  120. const auto* first =
  121. categoryMembers_.data() + family * state::build_data::items::kDefinitionCapacity;
  122. std::copy_n(first, categoryCounts_[family], candidates_.data() + candidateCount_);
  123. candidateCount_ += categoryCounts_[family];
  124. }
  125. std::sort(candidates_.data(), candidates_.data() + candidateCount_);
  126. candidateCount_ = static_cast<std::size_t>(
  127. std::unique(candidates_.data(), candidates_.data() + candidateCount_) - candidates_.data());
  128. if (candidateCount_ == 0) {
  129. return true;
  130. }
  131. std::uint64_t fingerprint = kHashOffsetBasis;
  132. for (std::size_t member = 0; member < candidateCount_; ++member) {
  133. std::uint16_t value = candidates_[member];
  134. for (std::size_t byte = 0; byte < sizeof value; ++byte) {
  135. fingerprint ^= static_cast<std::uint8_t>(value);
  136. fingerprint *= kHashPrime;
  137. value >>= 8U;
  138. }
  139. }
  140. fingerprint ^= candidateCount_;
  141. fingerprint *= kHashPrime;
  142. static_assert((kLookupCapacity & (kLookupCapacity - 1)) == 0);
  143. const std::size_t start = static_cast<std::size_t>(fingerprint) & (kLookupCapacity - 1);
  144. for (std::size_t probe = 0; probe < kLookupCapacity; ++probe) {
  145. PoolLookup& slot = lookup_[(start + probe) & (kLookupCapacity - 1)];
  146. if (slot.poolIndex == UINT32_MAX) {
  147. if (poolCount_ >= socket_plugs::kPoolCapacity
  148. || candidateCount_ > socket_plugs::kMemberCapacity - memberCount_) {
  149. return false;
  150. }
  151. poolIndex = static_cast<std::uint32_t>(poolCount_);
  152. pools_[poolCount_++] = {static_cast<std::uint32_t>(memberCount_),
  153. static_cast<std::uint32_t>(candidateCount_)};
  154. std::copy_n(candidates_.data(), candidateCount_, members_.data() + memberCount_);
  155. memberCount_ += candidateCount_;
  156. slot = {fingerprint, poolIndex};
  157. return true;
  158. }
  159. if (slot.fingerprint != fingerprint || slot.poolIndex >= poolCount_) {
  160. continue;
  161. }
  162. const socket_plugs::Pool& pool = pools_[slot.poolIndex];
  163. if (pool.memberCount == candidateCount_
  164. && std::equal(candidates_.data(),
  165. candidates_.data() + candidateCount_,
  166. members_.data() + pool.memberOffset)) {
  167. poolIndex = slot.poolIndex;
  168. return true;
  169. }
  170. }
  171. return false;
  172. }
  173. /** Extracts every exact ordinary socket pool of one installed item definition. */
  174. bool SocketPlugBuild::append(const tables::items::Row& item,
  175. std::span<const std::byte> itemDefinition,
  176. std::span<const std::byte> plugSetTable,
  177. std::size_t itemDefinitionCount) noexcept {
  178. if (rules_.empty() || item.definitionIndex >= itemDefinitionCount
  179. || item.socketCount > socket_plugs::kLaneCapacity) {
  180. return false;
  181. }
  182. bool complete = true;
  183. for (std::uint8_t lane = 0; lane < item.socketCount; ++lane) {
  184. candidateCount_ = 0;
  185. VisitorContext visitor{this, itemDefinitionCount};
  186. bool laneValid = tables::items::visit_allowed_plugs(
  187. itemDefinition, plugSetTable, lane, visit_member, &visitor);
  188. if (laneValid && item.initialPlugs[lane] != tables::items::kUnavailablePlug) {
  189. laneValid = add(item.initialPlugs[lane], itemDefinitionCount);
  190. }
  191. if (laneValid && item.socketTypes[lane] == kTrackerSocketType) {
  192. for (std::size_t tracker = 0; tracker < trackerCount_ && laneValid; ++tracker) {
  193. laneValid = add(trackerMembers_[tracker], itemDefinitionCount);
  194. }
  195. }
  196. std::uint32_t poolIndex = socket_plugs::kEmptyPoolIndex;
  197. laneValid = laneValid && intern(poolIndex);
  198. if (!laneValid || ruleCount_ >= socket_plugs::kRuleCapacity) {
  199. ++skipped_;
  200. complete = false;
  201. continue;
  202. }
  203. rules_[ruleCount_++] = {item.definitionIndex, lane, 0, poolIndex};
  204. }
  205. return complete;
  206. }
  207. /** Publishes the bounded relation and releases all transient interning memory. */
  208. bool SocketPlugBuild::publish() noexcept {
  209. const bool published =
  210. !rules_.empty() && !pools_.empty() && !members_.empty()
  211. && state::build_data::publish_socket_plug_rules(std::span(rules_.data(), ruleCount_),
  212. std::span(pools_.data(), poolCount_),
  213. std::span(members_.data(), memberCount_));
  214. release();
  215. return published;
  216. }
  217. /** Reports how many lanes failed closed during extraction. */
  218. std::size_t SocketPlugBuild::skipped() const noexcept {
  219. return skipped_;
  220. }
  221. std::size_t SocketPlugBuild::rule_count() const noexcept {
  222. return ruleCount_;
  223. }
  224. std::size_t SocketPlugBuild::pool_count() const noexcept {
  225. return poolCount_;
  226. }
  227. std::size_t SocketPlugBuild::member_count() const noexcept {
  228. return memberCount_;
  229. }
  230. /** Drops all heap-backed extraction scratch and resets every count. */
  231. void SocketPlugBuild::release() noexcept {
  232. rules_.clear();
  233. rules_.shrink_to_fit();
  234. pools_.clear();
  235. pools_.shrink_to_fit();
  236. members_.clear();
  237. members_.shrink_to_fit();
  238. candidates_.clear();
  239. candidates_.shrink_to_fit();
  240. categoryMembers_.clear();
  241. categoryMembers_.shrink_to_fit();
  242. lookup_.clear();
  243. lookup_.shrink_to_fit();
  244. categoryCounts_ = {};
  245. trackerMembers_ = {};
  246. trackerCount_ = 0;
  247. ruleCount_ = 0;
  248. poolCount_ = 0;
  249. memberCount_ = 0;
  250. candidateCount_ = 0;
  251. skipped_ = 0;
  252. }
  253. } // namespace sunrise::client::content::items::packages