package_item_rows.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. #include <array>
  2. #include <span>
  3. #include "../../../../middleware/content/packages/tables/quest_initialization_reader.h"
  4. #include "../../../../state/build_data/items/catalysts/exotic_catalyst_builder.h"
  5. #include "../../../../state/build_data/items/details/item_detail_catalog.h"
  6. #include "../../../../state/build_data/runtime.h"
  7. #include "internal.h"
  8. #include "package_socket_plug_build.h"
  9. namespace sunrise::client::content::items::packages {
  10. namespace {
  11. namespace build_details = state::build_data::items::details;
  12. namespace build_items = state::build_data::items;
  13. /**
  14. * Set when a derivation reports the installed executable is not the one the catalyst facts pin.
  15. * A build identity cannot change while the process runs, so the catalog will never derive here.
  16. * TODO: replace with a build_data predicate for catalyst build support; nothing exports one yet.
  17. */
  18. bool g_catalystsUnsupported = false;
  19. /** @return True when one extracted detail can join the currently published numeric domains. */
  20. [[nodiscard]] bool publishable_detail(const build_details::Definition& detail) noexcept {
  21. const std::size_t itemCount = state::build_data::item_definition_count();
  22. const std::size_t socketListCount = state::build_data::socket_entry_list_count();
  23. build_items::Definition item{};
  24. if (!build_details::valid(std::span<const build_details::Definition>{&detail, 1})
  25. || detail.definitionIndex >= itemCount || detail.socketEntryListIndex >= socketListCount
  26. || !build_items::find_index(detail.definitionIndex, item)
  27. || item.bucketId != detail.bucketId) {
  28. return false;
  29. }
  30. for (const std::uint16_t plugIndex : detail.initialPlugIndices) {
  31. if (plugIndex != build_details::kUnavailableItemIndex && plugIndex >= itemCount) {
  32. return false;
  33. }
  34. }
  35. return true;
  36. }
  37. } // namespace
  38. /** @return True when the catalyst catalog is published or cannot exist on this executable. */
  39. bool exotic_catalysts_settled() noexcept {
  40. return state::build_data::exotic_catalysts_ready() || g_catalystsUnsupported;
  41. }
  42. /**
  43. * Publishes missing item domains from the located index table and its definitions.
  44. * @param source Borrowed package source.
  45. * @param storage Pass storage holding the table in child, root data, maps, and output rows.
  46. * @param table Located item index array within storage.child.
  47. * @param rowCount Starts at zero; receives the rows retained even if publication fails.
  48. * @param reason Receives the last stage reached or its failure reason.
  49. * @return True when required non-reward item domains are ready. Failure may retain prior results.
  50. */
  51. bool build_item_rows(const reader::Source& source,
  52. Storage& storage,
  53. const tables::Array& table,
  54. std::size_t& rowCount,
  55. const char*& reason) noexcept {
  56. const bool needRewards = !reward_definitions_settled();
  57. const bool rewardStorageReady =
  58. needRewards && storage.rewardBuild.begin_items(static_cast<std::size_t>(table.count));
  59. const bool needDefinitions = !state::build_data::item_definitions_ready();
  60. const bool needDetails = !state::build_data::configured_item_details_ready();
  61. const bool needSocketPlugs = !state::build_data::socket_plug_rules_ready();
  62. const bool needCatalysts = !exotic_catalysts_settled();
  63. const bool needBuckets = !state::build_data::inventory_bucket_descriptors_ready();
  64. const bool retainDetails = needDetails || needCatalysts;
  65. const bool needSocketRows = needSocketPlugs || needCatalysts;
  66. const bool needDetailRows = needDetails || needSocketRows;
  67. // Bucket equipment slots are derived from this same complete item walk, so a partial retry
  68. // must still revisit the table even when definitions and detail domains already published.
  69. const bool needRows = needDefinitions || needDetailRows || needBuckets || rewardStorageReady;
  70. bool published = !needRows;
  71. if (retainDetails && storage.details.size() != kDetailCapacity) {
  72. storage.details.assign(kDetailCapacity, build_details::Definition{});
  73. }
  74. if (needCatalysts) {
  75. storage.catalystCompletionConditions.assign(
  76. static_cast<std::size_t>(table.count),
  77. state::build_data::items::catalysts::CompletionCondition{});
  78. for (std::size_t item = 0; item < storage.catalystCompletionConditions.size(); ++item) {
  79. storage.catalystCompletionConditions[item].itemDefinitionIndex =
  80. static_cast<std::uint16_t>(item);
  81. }
  82. }
  83. const bool detailStorageReady = !retainDetails || storage.details.size() == kDetailCapacity;
  84. const std::span<const std::byte> container{storage.child};
  85. reason = "rows";
  86. // The detail closure is gathered during this one walk. Collections can name any installed
  87. // item row, including profile-owned shaders and modifications, so retain every readable row.
  88. // The fixed request bitset bounds this to the installed 16-bit item-table domain.
  89. storage.detailRequests.reset();
  90. storage.specialPlugCategories.fill(0);
  91. std::size_t detailCount = 0;
  92. // One malformed entry is omitted on its own so the rest of the table still publishes. An
  93. // entry can fail either at its index row or at the definition the row points to, and neither
  94. // says anything about the entries that follow it.
  95. std::uint64_t index = 0;
  96. for (; needRows && index < table.count && rowCount < storage.rows.size(); ++index) {
  97. tables::IndexRow row{};
  98. if (!tables::index_row(container, table, index, row)) {
  99. continue;
  100. }
  101. tables::items::Row item{};
  102. item.definitionHash = row.definitionHash;
  103. item.definitionIndex = static_cast<std::uint16_t>(index);
  104. std::uint32_t itemClass = 0;
  105. if (!reader::read_tag(
  106. source, storage.scratch, row.targetTag, storage.definition, itemClass)) {
  107. // The published catalog read this row before, so its reward waits for a retry; a row
  108. // the catalog lacks stays pruned from both.
  109. state::build_data::items::Definition cataloged{};
  110. if (rewardStorageReady
  111. && state::build_data::find_item_definition_index(item.definitionIndex, cataloged)) {
  112. storage.rewardBuild.item_unread(item.definitionIndex);
  113. }
  114. continue;
  115. }
  116. if (!tables::items::read_definition(std::span<const std::byte>{storage.definition}, item)) {
  117. continue;
  118. }
  119. if (rewardStorageReady && itemClass == tables::kItemDefinitionClass) {
  120. storage.rewardBuild.item(item.definitionIndex, item.definitionHash, storage.definition);
  121. }
  122. const std::uint32_t plugCategoryHash =
  123. corrected_plug_category(item.definitionHash, item.plugCategoryHash);
  124. build_items::QuestInitialization quest{};
  125. const auto parentIndex = tables::items::quest_parent(storage.definition);
  126. if (needDefinitions && itemClass == tables::kItemDefinitionClass
  127. && parentIndex < table.count) {
  128. std::span<const std::byte> parent = storage.definition;
  129. tables::IndexRow parentRow{};
  130. std::uint32_t parentClass = itemClass;
  131. const bool parentReady = parentIndex == item.definitionIndex
  132. || (tables::index_row(container, table, parentIndex, parentRow)
  133. && reader::read_tag(source,
  134. storage.scratch,
  135. parentRow.targetTag,
  136. storage.questParentDefinition,
  137. parentClass));
  138. if (parentIndex != item.definitionIndex) {
  139. parent = storage.questParentDefinition;
  140. }
  141. if (parentReady && parentClass == tables::kItemDefinitionClass) {
  142. quest =
  143. tables::items::read_quest_initialization(storage.definition,
  144. item.definitionIndex,
  145. parent,
  146. static_cast<std::size_t>(table.count),
  147. storage.questValueMap);
  148. }
  149. }
  150. storage.rows[rowCount++] =
  151. state::build_data::items::Definition{item.definitionHash,
  152. item.definitionIndex,
  153. item.bucketId,
  154. item.insertionMaterialRequirementSetIndex,
  155. item.enabledMaterialRequirementSetIndex,
  156. item.tier,
  157. plugCategoryHash,
  158. item.rollSetIndex,
  159. item.linkedPlugIndex,
  160. quest};
  161. if (needSocketRows) {
  162. storage.specialPlugCategories[item.definitionIndex] =
  163. special_plug_category(plugCategoryHash);
  164. }
  165. if (needDetailRows) {
  166. request(item.definitionIndex, storage.detailRequests);
  167. append_initial_plugs(item, table.count, storage.detailRequests);
  168. }
  169. }
  170. bool requestsFit = true;
  171. if (needRows) {
  172. // Every walked entry either published a row or was skipped, so the count of one
  173. // follows from the other rather than being tracked alongside them.
  174. report_row_count(index, rowCount, index - rowCount, index < table.count);
  175. requestsFit = publish_buckets(storage)
  176. && (!needDetailRows
  177. || materialize_requests(
  178. storage.detailRequests, storage.requestedDetailIndices, detailCount));
  179. published = rowCount != 0 && requestsFit && detailStorageReady;
  180. }
  181. if (published && needDefinitions) {
  182. published =
  183. state::build_data::publish_item_definitions(std::span(storage.rows).first(rowCount));
  184. }
  185. // A reward refusal is reported by RewardBuild and does not block the other item domains.
  186. // Rewards publish only beside the item catalog and at its row count, so both omit the same
  187. // unread rows.
  188. const bool rewardsPublished =
  189. !needRewards
  190. || (rewardStorageReady && state::build_data::item_definitions_ready()
  191. && storage.rewardBuild.publish(state::build_data::item_definition_count()));
  192. if (!published) {
  193. reason = !detailStorageReady ? "detail_storage"
  194. : !requestsFit ? "detail_capacity"
  195. : "publish";
  196. }
  197. SocketPlugBuild socketPlugBuild;
  198. const bool socketStorageReady =
  199. !needSocketRows
  200. || socketPlugBuild.prepare(storage.specialPlugCategories,
  201. std::span(storage.rows).first(rowCount));
  202. if (published && !socketStorageReady) {
  203. published = false;
  204. reason = "socket_storage";
  205. }
  206. // Every readable installed row is found through the table this pass just published. One
  207. // malformed row is omitted independently so unrelated Collections categories stay usable.
  208. if (published && needDetailRows) {
  209. reason = "details";
  210. const DetailSource detailSource{
  211. &source, &storage.scratch, container, table, &storage.definition};
  212. std::size_t builtDetailCount = 0;
  213. for (std::size_t slot = 0; slot < detailCount; ++slot) {
  214. build_details::Definition detail{};
  215. tables::items::Row item{};
  216. if (!build_detail(detailSource, storage.requestedDetailIndices[slot], detail, item)
  217. || !publishable_detail(detail)) {
  218. report_detail_failure(slot, storage.requestedDetailIndices[slot]);
  219. continue;
  220. }
  221. if (retainDetails) {
  222. storage.details[builtDetailCount++] = detail;
  223. }
  224. if (needCatalysts
  225. && detail.definitionIndex < storage.catalystCompletionConditions.size()) {
  226. read_catalyst_completion_condition(
  227. std::span<const std::byte>{storage.definition},
  228. detail.definitionIndex,
  229. storage.catalystCompletionConditions[detail.definitionIndex]);
  230. }
  231. if (needSocketRows) {
  232. (void)socketPlugBuild.append(item,
  233. std::span<const std::byte>{storage.definition},
  234. std::span<const std::byte>{storage.plugSetTable},
  235. table.count);
  236. }
  237. }
  238. if (needDetails) {
  239. published = state::build_data::publish_configured_item_details(
  240. std::span<build_details::Definition>{storage.details}.first(builtDetailCount));
  241. report_detail_count(detailCount, builtDetailCount);
  242. }
  243. std::array<state::build_data::items::catalysts::Definition,
  244. state::build_data::items::catalysts::kDefinitionCapacity>
  245. catalystRows{};
  246. std::size_t catalystCount = 0;
  247. state::build_data::items::catalysts::Report catalystReport{};
  248. const state::build_data::items::catalysts::Source catalystSource{
  249. {},
  250. std::span(storage.rows).first(rowCount),
  251. std::span(storage.details).first(builtDetailCount),
  252. socketPlugBuild.rules(),
  253. socketPlugBuild.pools(),
  254. socketPlugBuild.members(),
  255. storage.catalystCompletionConditions,
  256. storage.catalystAcquisitionGates,
  257. storage.catalystObjectiveValues,
  258. storage.slotMaps.accountFlag,
  259. };
  260. bool catalystBuilt = false;
  261. if (published && needCatalysts) {
  262. reason = "exotic_catalysts";
  263. catalystBuilt = state::build_data::derive_exotic_catalysts(
  264. catalystSource, catalystRows, catalystCount, catalystReport);
  265. report_catalyst_catalog(catalystReport, catalystBuilt);
  266. // An unsupported executable settles the domain: retrying repeats the whole item walk
  267. // on every refresh slice and reaches the same verdict.
  268. g_catalystsUnsupported =
  269. catalystReport.error
  270. == state::build_data::items::catalysts::Error::unsupportedBuild;
  271. published = catalystBuilt || g_catalystsUnsupported;
  272. }
  273. if (published && needSocketPlugs) {
  274. const std::size_t rules = socketPlugBuild.rule_count();
  275. const std::size_t pools = socketPlugBuild.pool_count();
  276. const std::size_t members = socketPlugBuild.member_count();
  277. const std::size_t skipped = socketPlugBuild.skipped();
  278. reason = "socket_plugs";
  279. published = socketPlugBuild.publish();
  280. if (published) {
  281. report_socket_plug_count(rules, pools, members, skipped);
  282. }
  283. }
  284. if (published && needCatalysts && catalystBuilt) {
  285. reason = "exotic_catalysts";
  286. published = state::build_data::publish_exotic_catalysts(
  287. catalystSource, std::span(catalystRows).first(catalystCount));
  288. }
  289. }
  290. // Ability buckets read the socket entry list table again and depend on the detail domain, so
  291. // they run last. The entry-bucket table never joins the on-disk cache, so a warm boot still
  292. // has to run this once to fill it in for the session.
  293. if (published
  294. && (!state::build_data::ability_buckets_ready()
  295. || !state::build_data::socket_entry_buckets_ready())) {
  296. reason = "abilities";
  297. std::size_t abilityCount = 0;
  298. std::size_t entryBucketCount = 0;
  299. const bool built = build_character_abilities(source,
  300. storage.scratch,
  301. std::span<const std::byte>{storage.root},
  302. storage.abilityTable,
  303. storage.definition,
  304. storage.abilityPool,
  305. storage.abilityRows,
  306. abilityCount,
  307. storage.entryBucketRows,
  308. entryBucketCount);
  309. published = built
  310. && state::build_data::publish_ability_buckets(
  311. std::span(storage.abilityRows).first(abilityCount))
  312. && state::build_data::publish_socket_entry_buckets(
  313. std::span(storage.entryBucketRows).first(entryBucketCount));
  314. if (built) {
  315. report_ability_count(abilityCount);
  316. }
  317. }
  318. // Named last; later stages overwrite the reason as they start.
  319. if (published && !rewardsPublished) {
  320. reason = "rewards";
  321. }
  322. return published && state::build_data::item_definitions_ready()
  323. && state::build_data::configured_item_details_ready()
  324. && state::build_data::socket_plug_rules_ready() && exotic_catalysts_settled()
  325. && state::build_data::ability_buckets_ready();
  326. }
  327. } // namespace sunrise::client::content::items::packages