package_socket_plug_build.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  1. #include "package_socket_plug_build.h"
  2. #include <algorithm>
  3. #include <cstring>
  4. #include <limits>
  5. #include "../../../../middleware/content/packages/tables/definition_index_table.h"
  6. #include "../../../../state/build_data/runtime.h"
  7. namespace sunrise::client::content::items::packages {
  8. namespace {
  9. /** Sundial/native category families whose socket seed expands to every plug in that family. */
  10. constexpr std::array<std::uint32_t, 3> kExpandableCategories{
  11. 0xB134761EU,
  12. 0x87727F34U,
  13. 0x6C863692U,
  14. };
  15. /** Tracker sockets synthesize these three safe plug choices by socket type. */
  16. constexpr std::array<std::uint32_t, 3> kTrackerPlugHashes{
  17. 2'285'418'970U,
  18. 2'302'094'943U,
  19. 38'912'240U,
  20. };
  21. /** Native ordinary socket type whose choices are the synthetic tracker set. */
  22. constexpr std::uint16_t kTrackerSocketType = 518;
  23. /** FNV-1a constants make pool fingerprints stable and cheap. */
  24. constexpr std::uint64_t kHashOffsetBasis = 14695981039346656037ULL;
  25. constexpr std::uint64_t kHashPrime = 1099511628211ULL;
  26. /** One acquired-state rule starts with kind 1 and then its item-definition index. */
  27. constexpr std::uint32_t kAcquisitionRuleKind = 1;
  28. constexpr std::size_t kAcquisitionRuleSize = 8;
  29. /** Reads one trivially copied scalar from a bounded package blob. */
  30. template <typename Value>
  31. [[nodiscard]] bool
  32. read(std::span<const std::byte> blob, std::size_t offset, Value& value) noexcept {
  33. if (offset > blob.size() || blob.size() - offset < sizeof value) {
  34. return false;
  35. }
  36. std::memcpy(&value, blob.data() + offset, sizeof value);
  37. return true;
  38. }
  39. /** Visitor adapter that appends one list member to a bounded lane candidate. */
  40. struct VisitorContext {
  41. SocketPlugBuild* build{};
  42. std::size_t itemDefinitionCount{};
  43. };
  44. /**
  45. * @param opaque Visitor context supplied by the socket build.
  46. * @param itemDefinitionIndex Package-provided plug index.
  47. * @return True when the plug was accepted into bounded scratch.
  48. */
  49. [[nodiscard]] bool visit_member(void* opaque, std::uint32_t itemDefinitionIndex) noexcept {
  50. auto& context = *static_cast<VisitorContext*>(opaque);
  51. return context.build != nullptr
  52. && context.build->add(itemDefinitionIndex, context.itemDefinitionCount);
  53. }
  54. } // namespace
  55. bool read_catalyst_acquisition_gates(const reader::Source& source,
  56. reader::Scratch& scratch,
  57. std::span<const std::byte> root,
  58. std::vector<std::byte>& blob,
  59. std::vector<catalysts::AcquisitionGate>& output) noexcept {
  60. output.clear();
  61. std::uint32_t tableTag = 0;
  62. tables::Array table{};
  63. if (!tables::slot_tag(root, tables::kSocketTypeTableSlot, tableTag) || tableTag == 0
  64. || !reader::read_tag(source, scratch, tableTag, blob)
  65. || !tables::find_array_at(blob, tables::kTableArrayDescriptor, table)
  66. || table.elementClass != tables::kSocketTypeTableClass || table.count == 0
  67. || table.count > (std::numeric_limits<std::uint16_t>::max)()) {
  68. return false;
  69. }
  70. const std::uint64_t tableSize = table.count * tables::kSocketTypeRowStride;
  71. if (tableSize > blob.size() || table.dataOffset > blob.size() - tableSize) {
  72. return false;
  73. }
  74. output.resize(static_cast<std::size_t>(table.count));
  75. const std::span<const std::byte> bytes{blob};
  76. for (std::size_t index = 0; index < output.size(); ++index) {
  77. catalysts::AcquisitionGate& gate = output[index];
  78. gate.socketType = static_cast<std::uint16_t>(index);
  79. const std::size_t row = table.dataOffset + index * tables::kSocketTypeRowStride;
  80. tables::Array rules{};
  81. if (!tables::find_array_at(bytes, row + tables::kSocketTypeAcquisitionDescriptor, rules)) {
  82. continue;
  83. }
  84. if (rules.count != 1 || rules.elementClass != tables::kInvestmentExpressionRowClass
  85. || rules.dataOffset > bytes.size()
  86. || bytes.size() - rules.dataOffset < kAcquisitionRuleSize) {
  87. gate.state = catalysts::AcquisitionState::ambiguous;
  88. continue;
  89. }
  90. std::uint32_t kind = 0;
  91. std::uint32_t definitionIndex = 0;
  92. if (!read(bytes, rules.dataOffset, kind)
  93. || !read(bytes, rules.dataOffset + sizeof kind, definitionIndex)
  94. || kind != kAcquisitionRuleKind
  95. || definitionIndex >= state::build_data::items::kDefinitionCapacity) {
  96. gate.state = catalysts::AcquisitionState::ambiguous;
  97. continue;
  98. }
  99. gate.definitionIndex = static_cast<std::uint16_t>(definitionIndex);
  100. gate.state = catalysts::AcquisitionState::present;
  101. }
  102. return true;
  103. }
  104. bool read_catalyst_objective_values(const reader::Source& source,
  105. reader::Scratch& scratch,
  106. std::span<const std::byte> root,
  107. std::vector<std::byte>& blob,
  108. std::vector<std::int32_t>& output) noexcept {
  109. output.clear();
  110. std::uint32_t tableTag = 0;
  111. std::uint32_t tableClass = 0;
  112. tables::Array table{};
  113. if (!tables::slot_tag(root, tables::kObjectiveTableSlot, tableTag) || tableTag == 0
  114. || !reader::read_tag(source, scratch, tableTag, blob, tableClass)
  115. || tableClass != tables::kObjectiveTableClass
  116. || !tables::find_array_at(blob, tables::kTableArrayDescriptor, table)
  117. || table.elementClass != tables::kObjectiveRowClass || table.count == 0
  118. || table.count > catalysts::kUnavailableObjectiveIndex) {
  119. return false;
  120. }
  121. const std::uint64_t tableSize = table.count * tables::kObjectiveRowStride;
  122. if (tableSize > blob.size() || table.dataOffset > blob.size() - tableSize) {
  123. return false;
  124. }
  125. output.resize(static_cast<std::size_t>(table.count));
  126. const std::span<const std::byte> bytes{blob};
  127. for (std::size_t index = 0; index < output.size(); ++index) {
  128. const std::size_t offset = table.dataOffset + index * tables::kObjectiveRowStride
  129. + tables::kObjectiveCompletionValueOffset;
  130. if (!read(bytes, offset, output[index])) {
  131. output.clear();
  132. return false;
  133. }
  134. }
  135. return true;
  136. }
  137. /** Returns the compact 1-based code of one native category-expansion family. */
  138. std::uint8_t special_plug_category(std::uint32_t categoryHash) noexcept {
  139. for (std::size_t index = 0; index < kExpandableCategories.size(); ++index) {
  140. if (kExpandableCategories[index] == categoryHash) {
  141. return static_cast<std::uint8_t>(index + 1);
  142. }
  143. }
  144. return 0;
  145. }
  146. /** Allocates the bounded build state and indexes expansion/tracker plug definitions. */
  147. bool SocketPlugBuild::prepare(
  148. std::span<const std::uint8_t> specialCategories,
  149. std::span<const state::build_data::items::Definition> itemDefinitions) noexcept {
  150. release();
  151. if (specialCategories.size() < itemDefinitions.size()
  152. || itemDefinitions.size() > state::build_data::items::kDefinitionCapacity) {
  153. return false;
  154. }
  155. rules_.assign(socket_plugs::kRuleCapacity, {});
  156. pools_.assign(socket_plugs::kPoolCapacity, {});
  157. members_.assign(socket_plugs::kMemberCapacity, {});
  158. candidates_.assign(state::build_data::items::kDefinitionCapacity, {});
  159. categoryMembers_.assign(kCategoryCount * state::build_data::items::kDefinitionCapacity, {});
  160. lookup_.assign(kLookupCapacity, {});
  161. pools_[socket_plugs::kEmptyPoolIndex] = {};
  162. poolCount_ = 1;
  163. for (std::size_t item = 0; item < itemDefinitions.size(); ++item) {
  164. const std::uint8_t category = specialCategories[item];
  165. if (category != 0 && category <= kCategoryCount) {
  166. const std::size_t family = category - 1;
  167. categoryMembers_[family * state::build_data::items::kDefinitionCapacity
  168. + categoryCounts_[family]++] = static_cast<std::uint16_t>(item);
  169. }
  170. for (const std::uint32_t trackerHash : kTrackerPlugHashes) {
  171. if (itemDefinitions[item].definitionHash != trackerHash) {
  172. continue;
  173. }
  174. if (trackerCount_ >= trackerMembers_.size()) {
  175. release();
  176. return false;
  177. }
  178. trackerMembers_[trackerCount_++] = static_cast<std::uint16_t>(item);
  179. }
  180. }
  181. return true;
  182. }
  183. /** Appends one package member after enforcing the installed item-table bound. */
  184. bool SocketPlugBuild::add(std::uint32_t itemDefinitionIndex,
  185. std::size_t itemDefinitionCount) noexcept {
  186. if (candidates_.empty() || itemDefinitionIndex >= itemDefinitionCount
  187. || itemDefinitionIndex >= state::build_data::items::kDefinitionCapacity
  188. || candidateCount_ >= state::build_data::items::kDefinitionCapacity) {
  189. return false;
  190. }
  191. candidates_[candidateCount_++] = static_cast<std::uint16_t>(itemDefinitionIndex);
  192. return true;
  193. }
  194. /** Expands special category seeds, sorts/deduplicates, then interns one exact pool. */
  195. bool SocketPlugBuild::intern(std::uint32_t& poolIndex) noexcept {
  196. poolIndex = socket_plugs::kEmptyPoolIndex;
  197. if (candidates_.empty() || categoryMembers_.empty() || lookup_.empty()) {
  198. return false;
  199. }
  200. std::sort(candidates_.data(), candidates_.data() + candidateCount_);
  201. candidateCount_ = static_cast<std::size_t>(
  202. std::unique(candidates_.data(), candidates_.data() + candidateCount_) - candidates_.data());
  203. std::array<bool, kCategoryCount> expand{};
  204. // Category codes were indexed by native item definition index during prepare().
  205. for (std::size_t family = 0; family < kCategoryCount; ++family) {
  206. const auto* familyMembers =
  207. categoryMembers_.data() + family * state::build_data::items::kDefinitionCapacity;
  208. for (std::size_t seed = 0; seed < candidateCount_ && !expand[family]; ++seed) {
  209. expand[family] = std::binary_search(
  210. familyMembers, familyMembers + categoryCounts_[family], candidates_[seed]);
  211. }
  212. }
  213. for (std::size_t family = 0; family < kCategoryCount; ++family) {
  214. if (!expand[family]) {
  215. continue;
  216. }
  217. if (categoryCounts_[family]
  218. > state::build_data::items::kDefinitionCapacity - candidateCount_) {
  219. return false;
  220. }
  221. const auto* first =
  222. categoryMembers_.data() + family * state::build_data::items::kDefinitionCapacity;
  223. std::copy_n(first, categoryCounts_[family], candidates_.data() + candidateCount_);
  224. candidateCount_ += categoryCounts_[family];
  225. }
  226. std::sort(candidates_.data(), candidates_.data() + candidateCount_);
  227. candidateCount_ = static_cast<std::size_t>(
  228. std::unique(candidates_.data(), candidates_.data() + candidateCount_) - candidates_.data());
  229. if (candidateCount_ == 0) {
  230. return true;
  231. }
  232. std::uint64_t fingerprint = kHashOffsetBasis;
  233. for (std::size_t member = 0; member < candidateCount_; ++member) {
  234. std::uint16_t value = candidates_[member];
  235. for (std::size_t byte = 0; byte < sizeof value; ++byte) {
  236. fingerprint ^= static_cast<std::uint8_t>(value);
  237. fingerprint *= kHashPrime;
  238. value >>= 8U;
  239. }
  240. }
  241. fingerprint ^= candidateCount_;
  242. fingerprint *= kHashPrime;
  243. static_assert((kLookupCapacity & (kLookupCapacity - 1)) == 0);
  244. const std::size_t start = static_cast<std::size_t>(fingerprint) & (kLookupCapacity - 1);
  245. for (std::size_t probe = 0; probe < kLookupCapacity; ++probe) {
  246. PoolLookup& slot = lookup_[(start + probe) & (kLookupCapacity - 1)];
  247. if (slot.poolIndex == UINT32_MAX) {
  248. if (poolCount_ >= socket_plugs::kPoolCapacity
  249. || candidateCount_ > socket_plugs::kMemberCapacity - memberCount_) {
  250. return false;
  251. }
  252. poolIndex = static_cast<std::uint32_t>(poolCount_);
  253. pools_[poolCount_++] = {static_cast<std::uint32_t>(memberCount_),
  254. static_cast<std::uint32_t>(candidateCount_)};
  255. std::copy_n(candidates_.data(), candidateCount_, members_.data() + memberCount_);
  256. memberCount_ += candidateCount_;
  257. slot = {fingerprint, poolIndex};
  258. return true;
  259. }
  260. if (slot.fingerprint != fingerprint || slot.poolIndex >= poolCount_) {
  261. continue;
  262. }
  263. const socket_plugs::Pool& pool = pools_[slot.poolIndex];
  264. if (pool.memberCount == candidateCount_
  265. && std::equal(candidates_.data(),
  266. candidates_.data() + candidateCount_,
  267. members_.data() + pool.memberOffset)) {
  268. poolIndex = slot.poolIndex;
  269. return true;
  270. }
  271. }
  272. return false;
  273. }
  274. /** Extracts every exact ordinary socket pool of one installed item definition. */
  275. bool SocketPlugBuild::append(const tables::items::Row& item,
  276. std::span<const std::byte> itemDefinition,
  277. std::span<const std::byte> plugSetTable,
  278. std::size_t itemDefinitionCount) noexcept {
  279. if (rules_.empty() || item.definitionIndex >= itemDefinitionCount
  280. || item.socketCount > socket_plugs::kLaneCapacity) {
  281. return false;
  282. }
  283. bool complete = true;
  284. for (std::uint8_t lane = 0; lane < item.socketCount; ++lane) {
  285. candidateCount_ = 0;
  286. VisitorContext visitor{this, itemDefinitionCount};
  287. bool laneValid = tables::items::visit_allowed_plugs(
  288. itemDefinition, plugSetTable, lane, visit_member, &visitor);
  289. if (laneValid && item.initialPlugs[lane] != tables::items::kUnavailablePlug) {
  290. laneValid = add(item.initialPlugs[lane], itemDefinitionCount);
  291. }
  292. if (laneValid && item.socketTypes[lane] == kTrackerSocketType) {
  293. for (std::size_t tracker = 0; tracker < trackerCount_ && laneValid; ++tracker) {
  294. laneValid = add(trackerMembers_[tracker], itemDefinitionCount);
  295. }
  296. }
  297. std::uint32_t poolIndex = socket_plugs::kEmptyPoolIndex;
  298. laneValid = laneValid && intern(poolIndex);
  299. if (!laneValid || ruleCount_ >= socket_plugs::kRuleCapacity) {
  300. ++skipped_;
  301. complete = false;
  302. continue;
  303. }
  304. rules_[ruleCount_++] = {item.definitionIndex, lane, 0, poolIndex};
  305. }
  306. return complete;
  307. }
  308. /** Publishes the bounded relation and retains its rows for dependent package builders. */
  309. bool SocketPlugBuild::publish() noexcept {
  310. return !rules_.empty() && !pools_.empty() && !members_.empty()
  311. && state::build_data::publish_socket_plug_rules(
  312. std::span(rules_.data(), ruleCount_),
  313. std::span(pools_.data(), poolCount_),
  314. std::span(members_.data(), memberCount_));
  315. }
  316. /** Reports how many lanes failed closed during extraction. */
  317. std::size_t SocketPlugBuild::skipped() const noexcept {
  318. return skipped_;
  319. }
  320. std::size_t SocketPlugBuild::rule_count() const noexcept {
  321. return ruleCount_;
  322. }
  323. std::size_t SocketPlugBuild::pool_count() const noexcept {
  324. return poolCount_;
  325. }
  326. std::size_t SocketPlugBuild::member_count() const noexcept {
  327. return memberCount_;
  328. }
  329. std::span<const socket_plugs::Rule> SocketPlugBuild::rules() const noexcept {
  330. return std::span(rules_).first(ruleCount_);
  331. }
  332. std::span<const socket_plugs::Pool> SocketPlugBuild::pools() const noexcept {
  333. return std::span(pools_).first(poolCount_);
  334. }
  335. std::span<const socket_plugs::Member> SocketPlugBuild::members() const noexcept {
  336. return std::span(members_).first(memberCount_);
  337. }
  338. /** Drops all heap-backed extraction scratch and resets every count. */
  339. void SocketPlugBuild::release() noexcept {
  340. rules_.clear();
  341. rules_.shrink_to_fit();
  342. pools_.clear();
  343. pools_.shrink_to_fit();
  344. members_.clear();
  345. members_.shrink_to_fit();
  346. candidates_.clear();
  347. candidates_.shrink_to_fit();
  348. categoryMembers_.clear();
  349. categoryMembers_.shrink_to_fit();
  350. lookup_.clear();
  351. lookup_.shrink_to_fit();
  352. categoryCounts_ = {};
  353. trackerMembers_ = {};
  354. trackerCount_ = 0;
  355. ruleCount_ = 0;
  356. poolCount_ = 0;
  357. memberCount_ = 0;
  358. candidateCount_ = 0;
  359. skipped_ = 0;
  360. }
  361. } // namespace sunrise::client::content::items::packages