build_data_persistence.cpp 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501
  1. #include "build_data_persistence.h"
  2. #include <Windows.h>
  3. #include <span>
  4. #include <vector>
  5. #include "../../../../core/ui/busy/busy.h"
  6. #include "../../abilities/ability_bucket_catalog.h"
  7. #include "../../bounties/bounty_catalog.h"
  8. #include "../../cache/internal.h"
  9. #include "../../cache/records/validation.h"
  10. #include "../../collectibles/collectible_catalog.h"
  11. #include "../../constants/investment_constant_catalog.h"
  12. #include "../../hash_names/hash_name_catalog.h"
  13. #include "../../inventory/buckets/inventory_bucket_catalog.h"
  14. #include "../../items/catalysts/exotic_catalyst_catalog.h"
  15. #include "../../items/details/item_detail_catalog.h"
  16. #include "../../items/socket_plugs/socket_plug_catalog.h"
  17. #include "../../material_requirements/material_requirement_catalog.h"
  18. #include "../../nodes/node_catalog.h"
  19. #include "../../progressions/progression_catalog.h"
  20. #include "../../records/record_catalog.h"
  21. #include "../../rewards/reward_catalog.h"
  22. #include "../../runtime.h"
  23. #include "../../scenarios/scenario_catalog.h"
  24. #include "../../season_pass/season_pass_catalog.h"
  25. #include "../../sobjects/sobject_catalog.h"
  26. #include "../../socket_entry_lists/socket_entry_list_catalog.h"
  27. #include "../../spawn_sets/spawn_set_catalog.h"
  28. #include "../../vendors/vendor_catalog.h"
  29. #include "../domain_markers.h"
  30. namespace sunrise::state::build_data::runtime::persistence {
  31. namespace {
  32. Context g_context;
  33. /**
  34. * Lazily sizes one bounded cache-snapshot bank and exposes all rows.
  35. * @param storage Bank held by the caller's context, resized on first use.
  36. * @return The whole bank.
  37. */
  38. template <typename Value, std::size_t Capacity>
  39. [[nodiscard]] std::span<Value> ensure_scratch(std::vector<Value>& storage) noexcept {
  40. if (storage.size() != Capacity) {
  41. storage.assign(Capacity, Value{});
  42. }
  43. return storage;
  44. }
  45. /** @param value Published runtime constants. @return The packed header form. */
  46. [[nodiscard]] cache::records::InvestmentConstants
  47. to_record(const constants::InvestmentConstants& value) noexcept {
  48. return {value.lightStatRow,
  49. value.weaponPowerStatRow,
  50. value.characterStatRows,
  51. value.extracted ? std::uint8_t{1} : std::uint8_t{0}};
  52. }
  53. /**
  54. * Copies all generated catalogs into fixed scratch storage.
  55. * @param state Its lock must already be held exclusively.
  56. * @param counts Receives every copied row count.
  57. * @return True when every snapshot fits and the canonical sort works.
  58. */
  59. [[nodiscard]] bool snapshot_domains_locked(Context& state,
  60. cache::records::DomainCounts& counts) noexcept {
  61. counts = {};
  62. const cache::records::MutableDomains scratch = scratch_domains(state);
  63. *scratch.constants = to_record(constants::snapshot());
  64. gameplay::entity_object_types::Fingerprint objectFingerprint{};
  65. return content::snapshot(scratch.named, counts.named)
  66. && items::snapshot(scratch.items, counts.items)
  67. && collectibles::snapshot(scratch.collectibles, counts.collectibles)
  68. && material_requirements::snapshot(scratch.materialRequirementSets,
  69. counts.materialRequirementSets)
  70. && items::details::snapshot(scratch.itemDetails, counts.itemDetails)
  71. && items::socket_plugs::snapshot(scratch.socketPlugRules,
  72. counts.socketPlugRules,
  73. scratch.socketPlugPools,
  74. counts.socketPlugPools,
  75. scratch.socketPlugMembers,
  76. counts.socketPlugMembers)
  77. && items::catalysts::snapshot(scratch.exoticCatalysts, counts.exoticCatalysts)
  78. && inventory::buckets::snapshot(scratch.inventoryBuckets, counts.inventoryBuckets)
  79. && socket_entry_lists::snapshot(scratch.socketEntryLists, counts.socketEntryLists)
  80. && socket_entry_lists::snapshot_entry_tables(scratch.socketEntryTables,
  81. counts.socketEntryTables)
  82. && abilities::snapshot(scratch.abilityBuckets, counts.abilityBuckets)
  83. && progressions::snapshot(scratch.progressions, counts.progressions)
  84. && progressions::snapshot_steps(scratch.progressionSteps, counts.progressionSteps)
  85. && season_pass::snapshot(scratch.seasonPassRewards, counts.seasonPassRewards)
  86. && bounties::snapshot(scratch.bounties, counts.bounties)
  87. && rewards::snapshot(scratch.rewardPools, counts.rewardPools)
  88. && rewards::snapshot(scratch.rewardEntries, counts.rewardEntries)
  89. && rewards::snapshot(scratch.rewardItems, counts.rewardItems)
  90. && rewards::snapshot(scratch.rewardInstructions, counts.rewardInstructions)
  91. && rewards::snapshot(scratch.rewardModifiers, counts.rewardModifiers)
  92. && rewards::snapshot(scratch.rewardSockets, counts.rewardSockets)
  93. && records::snapshot(scratch.records, counts.records)
  94. && records::snapshot_objectives(scratch.recordObjectives, counts.recordObjectives)
  95. && records::snapshot_intervals(scratch.recordIntervals, counts.recordIntervals)
  96. && records::snapshot_rewards(scratch.recordRewards, counts.recordRewards)
  97. && nodes::snapshot(scratch.nodes, counts.nodes)
  98. && sobjects::snapshot(scratch.sobjects, counts.sobjects)
  99. && scenarios::snapshot(scratch.scenarios, counts.scenarios)
  100. && scenarios::snapshot_groups(scratch.rosterGroups, counts.rosterGroups)
  101. && spawn_sets::snapshot(scratch.spawnStems, counts.spawnStems)
  102. && spawn_sets::snapshot_hashes(scratch.spawnNameHashes, counts.spawnNameHashes)
  103. && spawn_sets::snapshot_points(scratch.spawnPoints, counts.spawnPoints)
  104. && hash_names::snapshot(scratch.hashNames, counts.hashNames)
  105. && vendors::snapshot_index(scratch.vendorIndex, counts.vendorIndex)
  106. && vendors::snapshot_definitions(scratch.vendorDefinitions, counts.vendorDefinitions)
  107. && vendors::snapshot_sale_rows(scratch.vendorSaleRows, counts.vendorSaleRows)
  108. && vendors::snapshot_installed_rows(scratch.vendorInstalledRows,
  109. counts.vendorInstalledRows)
  110. && gameplay::entity_position_profiles::snapshot(
  111. scratch.positionProfiles, counts.positionProfiles, *scratch.positionFingerprint)
  112. && gameplay::entity_object_types::snapshot(
  113. scratch.objectTypes, counts.objectTypes, objectFingerprint)
  114. && objectFingerprint == *scratch.positionFingerprint
  115. && cache::records::canonicalize(scratch, counts);
  116. }
  117. /** @return True when every required extracted domain is complete in State. */
  118. [[nodiscard]] bool required_domains_ready() noexcept {
  119. constants::InvestmentConstants published{};
  120. return runtime::named::ready() && item_definitions_ready() && configured_item_details_ready()
  121. && collectible_definitions_ready() && socket_plug_rules_ready()
  122. && material_requirement_sets_ready() && inventory_bucket_descriptors_ready()
  123. && socket_entry_lists_ready() && ability_buckets_ready()
  124. && progression_definitions_ready() && season_pass_ready() && repeatable_bounties_ready()
  125. && rewards::ready() && record_definitions_ready() && node_definitions_ready()
  126. && sobject_definitions_ready() && scenario_layouts_ready() && spawn_sets_ready()
  127. && hash_names_ready() && vendor_catalog_ready()
  128. && gameplay::entity_position_profiles::available()
  129. && gameplay::entity_object_types::available() && constants::find(published);
  130. }
  131. } // namespace
  132. /** @return The process-wide persistence context, shared by lifecycle and writer code. */
  133. Context& context() noexcept {
  134. return g_context;
  135. }
  136. /** Gives mutable views over every fixed snapshot buffer. */
  137. cache::records::MutableDomains scratch_domains(Context& state) noexcept {
  138. const auto named = ensure_scratch<content::Definition, content::kDefinitionCatalogCapacity>(
  139. state.namedScratch);
  140. const auto items =
  141. ensure_scratch<build_data::items::Definition, build_data::items::kDefinitionCapacity>(
  142. state.itemScratch);
  143. const auto collectibles =
  144. ensure_scratch<build_data::collectibles::Definition,
  145. build_data::collectibles::kDefinitionCapacity>(state.collectibleScratch);
  146. const auto materialRequirementSets = ensure_scratch<material_requirements::Definition,
  147. material_requirements::kDefinitionCapacity>(
  148. state.materialRequirementSetScratch);
  149. const auto itemDetails =
  150. ensure_scratch<build_data::items::details::Definition,
  151. build_data::items::details::kDefinitionCapacity>(state.itemDetailScratch);
  152. const auto socketPlugRules =
  153. ensure_scratch<build_data::items::socket_plugs::Rule,
  154. build_data::items::socket_plugs::kRuleCapacity>(state.socketPlugRuleScratch);
  155. const auto socketPlugPools =
  156. ensure_scratch<build_data::items::socket_plugs::Pool,
  157. build_data::items::socket_plugs::kPoolCapacity>(state.socketPlugPoolScratch);
  158. const auto socketPlugMembers = ensure_scratch<build_data::items::socket_plugs::Member,
  159. build_data::items::socket_plugs::kMemberCapacity>(
  160. state.socketPlugMemberScratch);
  161. const auto exoticCatalysts = ensure_scratch<build_data::items::catalysts::Definition,
  162. build_data::items::catalysts::kDefinitionCapacity>(
  163. state.exoticCatalystScratch);
  164. const auto inventoryBuckets =
  165. ensure_scratch<inventory::buckets::Descriptor, inventory::buckets::kDescriptorCapacity>(
  166. state.inventoryBucketScratch);
  167. const auto socketEntryLists =
  168. ensure_scratch<socket_entry_lists::Definition, socket_entry_lists::kDefinitionCapacity>(
  169. state.socketEntryListScratch);
  170. const auto socketEntryTables =
  171. ensure_scratch<socket_entry_lists::EntryTable, socket_entry_lists::kEntryTableCapacity>(
  172. state.socketEntryTableScratch);
  173. const auto abilityBuckets =
  174. ensure_scratch<abilities::Definition, abilities::kDefinitionCapacity>(
  175. state.abilityBucketScratch);
  176. const auto progressions =
  177. ensure_scratch<progressions::Definition, progressions::kDefinitionCapacity>(
  178. state.progressionScratch);
  179. const auto recordRows =
  180. ensure_scratch<records::Definition, records::kDefinitionCapacity>(state.recordScratch);
  181. const auto recordObjectives = ensure_scratch<records::Objective, records::kObjectiveCapacity>(
  182. state.recordObjectiveScratch);
  183. const auto recordIntervals =
  184. ensure_scratch<records::Interval, records::kIntervalCapacity>(state.recordIntervalScratch);
  185. const auto recordRewards =
  186. ensure_scratch<records::Reward, records::kRewardCapacity>(state.recordRewardScratch);
  187. const auto nodeRows =
  188. ensure_scratch<nodes::Definition, nodes::kDefinitionCapacity>(state.nodeScratch);
  189. const auto sobjectRows =
  190. ensure_scratch<sobjects::Definition, sobjects::kDefinitionCapacity>(state.sobjectScratch);
  191. const auto scenarios = ensure_scratch<scenarios::Definition, scenarios::kDefinitionCapacity>(
  192. state.scenarioScratch);
  193. const auto rosterGroups =
  194. ensure_scratch<scenarios::RosterGroup, scenarios::kRosterGroupCapacity>(
  195. state.rosterGroupScratch);
  196. const auto spawnStems =
  197. ensure_scratch<spawn_sets::Stem, spawn_sets::kStemCapacity>(state.spawnStemScratch);
  198. const auto spawnNameHashes =
  199. ensure_scratch<spawn_sets::NameHash, spawn_sets::kNameHashCapacity>(
  200. state.spawnNameHashScratch);
  201. const auto spawnPoints =
  202. ensure_scratch<spawn_sets::Point, spawn_sets::kPointCapacity>(state.spawnPointScratch);
  203. const auto hashNames =
  204. ensure_scratch<hash_names::Name, hash_names::kNameCapacity>(state.hashNameScratch);
  205. const auto vendorIndex =
  206. ensure_scratch<vendors::IndexEntry, vendors::kIndexCapacity>(state.vendorIndexScratch);
  207. const auto vendorDefinitions =
  208. ensure_scratch<vendors::Definition, vendors::kDefinitionCapacity>(
  209. state.vendorDefinitionScratch);
  210. const auto vendorSaleRows =
  211. ensure_scratch<vendors::SaleRow, vendors::kSaleRowCapacity>(state.vendorSaleRowScratch);
  212. const auto vendorInstalledRows =
  213. ensure_scratch<vendors::InstalledRow, vendors::kInstalledRowCapacity>(
  214. state.vendorInstalledRowScratch);
  215. const auto progressionSteps = ensure_scratch<progressions::Step, progressions::kStepCapacity>(
  216. state.progressionStepScratch);
  217. const auto seasonPassRewards =
  218. ensure_scratch<season_pass::Reward, season_pass::kRewardCapacity>(
  219. state.seasonPassRewardScratch);
  220. const auto bountyRows =
  221. ensure_scratch<bounties::Definition, bounties::kDefinitionCapacity>(state.bountyScratch);
  222. const auto positionProfiles = ensure_scratch<gameplay::entity_position_profiles::Row,
  223. gameplay::entity_position_profiles::kMaximumRows>(
  224. state.positionProfileScratch);
  225. return {
  226. &state.constantsScratch,
  227. named,
  228. items,
  229. collectibles,
  230. materialRequirementSets,
  231. itemDetails,
  232. socketPlugRules,
  233. socketPlugPools,
  234. socketPlugMembers,
  235. exoticCatalysts,
  236. inventoryBuckets,
  237. socketEntryLists,
  238. socketEntryTables,
  239. abilityBuckets,
  240. progressions,
  241. recordRows,
  242. nodeRows,
  243. sobjectRows,
  244. scenarios,
  245. rosterGroups,
  246. spawnStems,
  247. spawnNameHashes,
  248. spawnPoints,
  249. hashNames,
  250. vendorIndex,
  251. vendorDefinitions,
  252. vendorSaleRows,
  253. vendorInstalledRows,
  254. positionProfiles,
  255. &state.positionFingerprint,
  256. ensure_scratch<gameplay::entity_object_types::Row,
  257. gameplay::entity_object_types::kMaximumRows>(state.objectTypeScratch),
  258. recordObjectives,
  259. recordIntervals,
  260. recordRewards,
  261. progressionSteps,
  262. seasonPassRewards,
  263. bountyRows,
  264. ensure_scratch<rewards::Pool, rewards::kPoolCapacity>(state.rewardPoolsScratch),
  265. ensure_scratch<rewards::Entry, rewards::kEntryCapacity>(state.rewardEntriesScratch),
  266. ensure_scratch<rewards::Item, rewards::kItemCapacity>(state.rewardItemsScratch),
  267. ensure_scratch<rewards::Instruction, rewards::kInstructionCapacity>(
  268. state.rewardInstructionsScratch),
  269. ensure_scratch<rewards::Modifier, rewards::kModifierCapacity>(state.rewardModifiersScratch),
  270. ensure_scratch<rewards::SocketOverride, rewards::kSocketOverrideCapacity>(
  271. state.rewardSocketsScratch),
  272. };
  273. }
  274. /**
  275. * Releases one transient cache snapshot bank without walking its capacity.
  276. * @param storage Bank emptied and handed back to the allocator.
  277. */
  278. template <typename Value> static void release_bank(std::vector<Value>& storage) noexcept {
  279. std::vector<Value>{}.swap(storage);
  280. }
  281. /** Releases transient cache snapshot banks without allocating or walking their capacities. */
  282. void release_scratch_locked(Context& state) noexcept {
  283. release_bank(state.namedScratch);
  284. release_bank(state.itemScratch);
  285. release_bank(state.collectibleScratch);
  286. release_bank(state.materialRequirementSetScratch);
  287. release_bank(state.itemDetailScratch);
  288. release_bank(state.socketPlugRuleScratch);
  289. release_bank(state.socketPlugPoolScratch);
  290. release_bank(state.socketPlugMemberScratch);
  291. release_bank(state.exoticCatalystScratch);
  292. release_bank(state.inventoryBucketScratch);
  293. release_bank(state.socketEntryListScratch);
  294. release_bank(state.socketEntryTableScratch);
  295. release_bank(state.abilityBucketScratch);
  296. release_bank(state.progressionScratch);
  297. release_bank(state.progressionStepScratch);
  298. release_bank(state.seasonPassRewardScratch);
  299. release_bank(state.bountyScratch);
  300. release_bank(state.rewardPoolsScratch);
  301. release_bank(state.rewardEntriesScratch);
  302. release_bank(state.rewardItemsScratch);
  303. release_bank(state.rewardInstructionsScratch);
  304. release_bank(state.rewardModifiersScratch);
  305. release_bank(state.rewardSocketsScratch);
  306. release_bank(state.recordScratch);
  307. release_bank(state.recordObjectiveScratch);
  308. release_bank(state.recordIntervalScratch);
  309. release_bank(state.recordRewardScratch);
  310. release_bank(state.nodeScratch);
  311. release_bank(state.sobjectScratch);
  312. release_bank(state.scenarioScratch);
  313. release_bank(state.rosterGroupScratch);
  314. release_bank(state.spawnStemScratch);
  315. release_bank(state.spawnNameHashScratch);
  316. release_bank(state.spawnPointScratch);
  317. release_bank(state.hashNameScratch);
  318. release_bank(state.vendorIndexScratch);
  319. release_bank(state.vendorDefinitionScratch);
  320. release_bank(state.vendorSaleRowScratch);
  321. release_bank(state.vendorInstalledRowScratch);
  322. release_bank(state.positionProfileScratch);
  323. release_bank(state.objectTypeScratch);
  324. state.positionFingerprint = {};
  325. state.constantsScratch = {};
  326. }
  327. /** Clears fixed cache paths, identity, flags, and snapshot storage. */
  328. void clear_locked(Context& state) noexcept {
  329. release_scratch_locked(state);
  330. state.cacheDirectory = {};
  331. state.cachePath = {};
  332. state.buildIdentity = {};
  333. state.catalystError = items::catalysts::Error::none;
  334. state.enabled = false;
  335. state.persisted = false;
  336. state.replaceStaleCache = false;
  337. }
  338. /** Gives read-only views over the used rows of one canonical snapshot. */
  339. cache::records::Domains occupied_domains(Context& state,
  340. const cache::records::DomainCounts& counts) noexcept {
  341. const std::span<const items::details::Definition> itemDetails{state.itemDetailScratch.data(),
  342. counts.itemDetails};
  343. const std::span<const items::socket_plugs::Rule> socketPlugRules{
  344. state.socketPlugRuleScratch.data(), counts.socketPlugRules};
  345. const std::span<const items::socket_plugs::Pool> socketPlugPools{
  346. state.socketPlugPoolScratch.data(), counts.socketPlugPools};
  347. const std::span<const items::socket_plugs::Member> socketPlugMembers{
  348. state.socketPlugMemberScratch.data(), counts.socketPlugMembers};
  349. const std::span<const items::catalysts::Definition> exoticCatalysts{
  350. state.exoticCatalystScratch.data(), counts.exoticCatalysts};
  351. return {
  352. state.constantsScratch,
  353. std::span<const content::Definition>{state.namedScratch.data(), counts.named},
  354. std::span<const build_data::items::Definition>{state.itemScratch.data(), counts.items},
  355. std::span<const build_data::collectibles::Definition>{state.collectibleScratch.data(),
  356. counts.collectibles},
  357. std::span<const material_requirements::Definition>{
  358. state.materialRequirementSetScratch.data(), counts.materialRequirementSets},
  359. itemDetails,
  360. socketPlugRules,
  361. socketPlugPools,
  362. socketPlugMembers,
  363. exoticCatalysts,
  364. std::span<const inventory::buckets::Descriptor>{state.inventoryBucketScratch.data(),
  365. counts.inventoryBuckets},
  366. std::span<const socket_entry_lists::Definition>{state.socketEntryListScratch.data(),
  367. counts.socketEntryLists},
  368. std::span<const socket_entry_lists::EntryTable>{state.socketEntryTableScratch.data(),
  369. counts.socketEntryTables},
  370. std::span<const abilities::Definition>{state.abilityBucketScratch.data(),
  371. counts.abilityBuckets},
  372. std::span<const progressions::Definition>{state.progressionScratch.data(),
  373. counts.progressions},
  374. std::span<const records::Definition>{state.recordScratch.data(), counts.records},
  375. std::span<const nodes::Definition>{state.nodeScratch.data(), counts.nodes},
  376. std::span<const sobjects::Definition>{state.sobjectScratch.data(), counts.sobjects},
  377. std::span<const scenarios::Definition>{state.scenarioScratch.data(), counts.scenarios},
  378. std::span<const scenarios::RosterGroup>{state.rosterGroupScratch.data(),
  379. counts.rosterGroups},
  380. std::span<const spawn_sets::Stem>{state.spawnStemScratch.data(), counts.spawnStems},
  381. std::span<const spawn_sets::NameHash>{state.spawnNameHashScratch.data(),
  382. counts.spawnNameHashes},
  383. std::span<const spawn_sets::Point>{state.spawnPointScratch.data(), counts.spawnPoints},
  384. std::span<const hash_names::Name>{state.hashNameScratch.data(), counts.hashNames},
  385. std::span<const vendors::IndexEntry>{state.vendorIndexScratch.data(), counts.vendorIndex},
  386. std::span<const vendors::Definition>{state.vendorDefinitionScratch.data(),
  387. counts.vendorDefinitions},
  388. std::span<const vendors::SaleRow>{state.vendorSaleRowScratch.data(), counts.vendorSaleRows},
  389. std::span<const vendors::InstalledRow>{state.vendorInstalledRowScratch.data(),
  390. counts.vendorInstalledRows},
  391. std::span<const gameplay::entity_position_profiles::Row>{
  392. state.positionProfileScratch.data(), counts.positionProfiles},
  393. state.positionFingerprint,
  394. std::span<const gameplay::entity_object_types::Row>{state.objectTypeScratch.data(),
  395. counts.objectTypes},
  396. std::span<const records::Objective>{state.recordObjectiveScratch.data(),
  397. counts.recordObjectives},
  398. std::span<const records::Interval>{state.recordIntervalScratch.data(),
  399. counts.recordIntervals},
  400. std::span<const records::Reward>{state.recordRewardScratch.data(), counts.recordRewards},
  401. std::span<const progressions::Step>{state.progressionStepScratch.data(),
  402. counts.progressionSteps},
  403. std::span<const season_pass::Reward>{state.seasonPassRewardScratch.data(),
  404. counts.seasonPassRewards},
  405. std::span<const bounties::Definition>{state.bountyScratch.data(), counts.bounties},
  406. std::span<const rewards::Pool>{state.rewardPoolsScratch.data(), counts.rewardPools},
  407. std::span<const rewards::Entry>{state.rewardEntriesScratch.data(), counts.rewardEntries},
  408. std::span<const rewards::Item>{state.rewardItemsScratch.data(), counts.rewardItems},
  409. std::span<const rewards::Instruction>{state.rewardInstructionsScratch.data(),
  410. counts.rewardInstructions},
  411. std::span<const rewards::Modifier>{state.rewardModifiersScratch.data(),
  412. counts.rewardModifiers},
  413. std::span<const rewards::SocketOverride>{state.rewardSocketsScratch.data(),
  414. counts.rewardSockets},
  415. };
  416. }
  417. /** Saves one canonical snapshot when all domains required by its build are ready. */
  418. bool persist_if_ready_locked(Context& state, bool catalystRequired) noexcept {
  419. if (!required_domains_ready() || (catalystRequired && !exotic_catalysts_ready())
  420. || state.persisted || !state.enabled) {
  421. return true;
  422. }
  423. cache::records::DomainCounts counts{};
  424. // One canonical snapshot keeps header counts and payload rows in the same commit.
  425. if (!snapshot_domains_locked(state, counts)) {
  426. return false;
  427. }
  428. // The write flushes every domain to disk once. It is the only stall here, so the overlay
  429. // covers just it.
  430. core::ui::busy::begin(core::ui::busy::Task::cacheWrite);
  431. const bool written =
  432. cache::write(state.cacheDirectory.chars.data(),
  433. state.cachePath.chars.data(),
  434. state.buildIdentity,
  435. occupied_domains(state, counts),
  436. state.replaceStaleCache ? cache::WriteDisposition::replaceStale
  437. : cache::WriteDisposition::createOnly);
  438. core::ui::busy::end(core::ui::busy::Task::cacheWrite);
  439. if (!written) {
  440. return false;
  441. }
  442. state.persisted = true;
  443. state.replaceStaleCache = false;
  444. release_scratch_locked(state);
  445. return true;
  446. }
  447. } // namespace sunrise::state::build_data::runtime::persistence
  448. namespace sunrise::state::build_data {
  449. /** Newly extracted package data replaces the shared cache only after all domains are ready. */
  450. void invalidate_cache() noexcept {
  451. auto& state = runtime::persistence::context();
  452. AcquireSRWLockExclusive(&state.lock);
  453. state.persisted = false;
  454. state.replaceStaleCache = true;
  455. ReleaseSRWLockExclusive(&state.lock);
  456. }
  457. /** @return True when required domains are ready and any safe cache write works. */
  458. bool persist() noexcept {
  459. runtime::persistence::Context& state = runtime::persistence::context();
  460. AcquireSRWLockExclusive(&state.lock);
  461. const bool requiredReady = runtime::persistence::required_domains_ready();
  462. bool result = false;
  463. switch (runtime::persistence::cache_action(
  464. requiredReady, exotic_catalysts_ready(), state.catalystError)) {
  465. case runtime::persistence::CacheAction::waitForDomains:
  466. break;
  467. case runtime::persistence::CacheAction::writeRequiredDomains:
  468. // Unsupported catalyst facts do not prevent the other build-bound domains from caching.
  469. result = runtime::persistence::persist_if_ready_locked(state, false);
  470. break;
  471. case runtime::persistence::CacheAction::writeCompleteCache:
  472. result = runtime::persistence::persist_if_ready_locked(state, true);
  473. break;
  474. }
  475. ReleaseSRWLockExclusive(&state.lock);
  476. return result;
  477. }
  478. } // namespace sunrise::state::build_data