build_data_persistence.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  1. #include "build_data_persistence.h"
  2. #include <Windows.h>
  3. #include <algorithm>
  4. #include <span>
  5. #include <vector>
  6. #include "../../../../core/ui/busy/busy.h"
  7. #include "../../abilities/ability_bucket_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 "../../progressions/progression_catalog.h"
  19. #include "../../runtime.h"
  20. #include "../../scenarios/scenario_catalog.h"
  21. #include "../../socket_entry_lists/socket_entry_list_catalog.h"
  22. #include "../../spawn_sets/spawn_set_catalog.h"
  23. #include "../../vendors/vendor_catalog.h"
  24. #include "../build_data_catalog_runtime.h"
  25. #include "../domain_markers.h"
  26. namespace sunrise::state::build_data::runtime::persistence {
  27. namespace {
  28. Context g_context;
  29. /**
  30. * Lazily sizes one bounded cache-snapshot bank and exposes all rows.
  31. * @param storage Bank held by the caller's context, resized on first use.
  32. * @return The whole bank.
  33. */
  34. template <typename Value, std::size_t Capacity>
  35. [[nodiscard]] std::span<Value> ensure_scratch(std::vector<Value>& storage) noexcept {
  36. if (storage.size() != Capacity) {
  37. storage.assign(Capacity, Value{});
  38. }
  39. return storage;
  40. }
  41. /** @param value Published runtime constants. @return The packed header form. */
  42. [[nodiscard]] cache::records::InvestmentConstants
  43. to_record(const constants::InvestmentConstants& value) noexcept {
  44. return {value.lightStatRow,
  45. value.weaponPowerStatRow,
  46. value.characterStatRows,
  47. value.extracted ? std::uint8_t{1} : std::uint8_t{0}};
  48. }
  49. /**
  50. * Copies all generated catalogs into fixed scratch storage.
  51. * @param state Its lock must already be held exclusively.
  52. * @param counts Receives every copied row count.
  53. * @return True when every snapshot fits and the canonical sort works.
  54. */
  55. [[nodiscard]] bool snapshot_domains_locked(Context& state,
  56. cache::records::DomainCounts& counts) noexcept {
  57. counts = {};
  58. const cache::records::MutableDomains scratch = scratch_domains(state);
  59. *scratch.constants = to_record(constants::snapshot());
  60. return content::snapshot(scratch.named, counts.named)
  61. && items::snapshot(scratch.items, counts.items)
  62. && collectibles::snapshot(scratch.collectibles, counts.collectibles)
  63. && material_requirements::snapshot(scratch.materialRequirementSets,
  64. counts.materialRequirementSets)
  65. && items::details::snapshot(scratch.itemDetails, counts.itemDetails)
  66. && items::socket_plugs::snapshot(scratch.socketPlugRules,
  67. counts.socketPlugRules,
  68. scratch.socketPlugPools,
  69. counts.socketPlugPools,
  70. scratch.socketPlugMembers,
  71. counts.socketPlugMembers)
  72. && items::catalysts::snapshot(scratch.exoticCatalysts, counts.exoticCatalysts)
  73. && inventory::buckets::snapshot(scratch.inventoryBuckets, counts.inventoryBuckets)
  74. && socket_entry_lists::snapshot(scratch.socketEntryLists, counts.socketEntryLists)
  75. && socket_entry_lists::snapshot_entry_tables(scratch.socketEntryTables,
  76. counts.socketEntryTables)
  77. && abilities::snapshot(scratch.abilityBuckets, counts.abilityBuckets)
  78. && progressions::snapshot(scratch.progressions, counts.progressions)
  79. && scenarios::snapshot(scratch.scenarios, counts.scenarios)
  80. && scenarios::snapshot_groups(scratch.rosterGroups, counts.rosterGroups)
  81. && spawn_sets::snapshot(scratch.spawnStems, counts.spawnStems)
  82. && spawn_sets::snapshot_hashes(scratch.spawnNameHashes, counts.spawnNameHashes)
  83. && spawn_sets::snapshot_points(scratch.spawnPoints, counts.spawnPoints)
  84. && hash_names::snapshot(scratch.hashNames, counts.hashNames)
  85. && vendors::snapshot_index(scratch.vendorIndex, counts.vendorIndex)
  86. && vendors::snapshot_definitions(scratch.vendorDefinitions, counts.vendorDefinitions)
  87. && vendors::snapshot_sale_rows(scratch.vendorSaleRows, counts.vendorSaleRows)
  88. && vendors::snapshot_installed_rows(scratch.vendorInstalledRows,
  89. counts.vendorInstalledRows)
  90. && cache::records::canonicalize(scratch, counts);
  91. }
  92. } // namespace
  93. /** @return The process-wide persistence context, shared by lifecycle and writer code. */
  94. Context& context() noexcept {
  95. return g_context;
  96. }
  97. /** @return True when every extracted domain is complete in State. */
  98. bool all_domains_ready() noexcept {
  99. constants::InvestmentConstants published{};
  100. return runtime::named::ready() && item_definitions_ready() && configured_item_details_ready()
  101. && collectible_definitions_ready() && socket_plug_rules_ready()
  102. && exotic_catalysts_ready() && material_requirement_sets_ready()
  103. && inventory_bucket_descriptors_ready() && socket_entry_lists_ready()
  104. && ability_buckets_ready() && progression_definitions_ready() && scenario_layouts_ready()
  105. && spawn_sets_ready() && hash_names_ready() && constants::find(published);
  106. }
  107. /** Gives mutable views over every fixed snapshot buffer. */
  108. cache::records::MutableDomains scratch_domains(Context& state) noexcept {
  109. const auto named = ensure_scratch<content::Definition, content::kDefinitionCatalogCapacity>(
  110. state.namedScratch);
  111. const auto items =
  112. ensure_scratch<build_data::items::Definition, build_data::items::kDefinitionCapacity>(
  113. state.itemScratch);
  114. const auto collectibles =
  115. ensure_scratch<build_data::collectibles::Definition,
  116. build_data::collectibles::kDefinitionCapacity>(state.collectibleScratch);
  117. const auto materialRequirementSets = ensure_scratch<material_requirements::Definition,
  118. material_requirements::kDefinitionCapacity>(
  119. state.materialRequirementSetScratch);
  120. const auto itemDetails =
  121. ensure_scratch<build_data::items::details::Definition,
  122. build_data::items::details::kDefinitionCapacity>(state.itemDetailScratch);
  123. const auto socketPlugRules =
  124. ensure_scratch<build_data::items::socket_plugs::Rule,
  125. build_data::items::socket_plugs::kRuleCapacity>(state.socketPlugRuleScratch);
  126. const auto socketPlugPools =
  127. ensure_scratch<build_data::items::socket_plugs::Pool,
  128. build_data::items::socket_plugs::kPoolCapacity>(state.socketPlugPoolScratch);
  129. const auto socketPlugMembers = ensure_scratch<build_data::items::socket_plugs::Member,
  130. build_data::items::socket_plugs::kMemberCapacity>(
  131. state.socketPlugMemberScratch);
  132. const auto exoticCatalysts = ensure_scratch<build_data::items::catalysts::Definition,
  133. build_data::items::catalysts::kDefinitionCapacity>(
  134. state.exoticCatalystScratch);
  135. const auto inventoryBuckets =
  136. ensure_scratch<inventory::buckets::Descriptor, inventory::buckets::kDescriptorCapacity>(
  137. state.inventoryBucketScratch);
  138. const auto socketEntryLists =
  139. ensure_scratch<socket_entry_lists::Definition, socket_entry_lists::kDefinitionCapacity>(
  140. state.socketEntryListScratch);
  141. const auto socketEntryTables =
  142. ensure_scratch<socket_entry_lists::EntryTable, socket_entry_lists::kEntryTableCapacity>(
  143. state.socketEntryTableScratch);
  144. const auto abilityBuckets =
  145. ensure_scratch<abilities::Definition, abilities::kDefinitionCapacity>(
  146. state.abilityBucketScratch);
  147. const auto progressions =
  148. ensure_scratch<progressions::Definition, progressions::kDefinitionCapacity>(
  149. state.progressionScratch);
  150. const auto scenarios = ensure_scratch<scenarios::Definition, scenarios::kDefinitionCapacity>(
  151. state.scenarioScratch);
  152. const auto rosterGroups =
  153. ensure_scratch<scenarios::RosterGroup, scenarios::kRosterGroupCapacity>(
  154. state.rosterGroupScratch);
  155. const auto spawnStems =
  156. ensure_scratch<spawn_sets::Stem, spawn_sets::kStemCapacity>(state.spawnStemScratch);
  157. const auto spawnNameHashes =
  158. ensure_scratch<spawn_sets::NameHash, spawn_sets::kNameHashCapacity>(
  159. state.spawnNameHashScratch);
  160. const auto spawnPoints =
  161. ensure_scratch<spawn_sets::Point, spawn_sets::kPointCapacity>(state.spawnPointScratch);
  162. const auto hashNames =
  163. ensure_scratch<hash_names::Name, hash_names::kNameCapacity>(state.hashNameScratch);
  164. const auto vendorIndex =
  165. ensure_scratch<vendors::IndexEntry, vendors::kIndexCapacity>(state.vendorIndexScratch);
  166. const auto vendorDefinitions =
  167. ensure_scratch<vendors::Definition, vendors::kDefinitionCapacity>(
  168. state.vendorDefinitionScratch);
  169. const auto vendorSaleRows =
  170. ensure_scratch<vendors::SaleRow, vendors::kSaleRowCapacity>(state.vendorSaleRowScratch);
  171. const auto vendorInstalledRows =
  172. ensure_scratch<vendors::InstalledRow, vendors::kInstalledRowCapacity>(
  173. state.vendorInstalledRowScratch);
  174. return {
  175. &state.constantsScratch,
  176. named,
  177. items,
  178. collectibles,
  179. materialRequirementSets,
  180. itemDetails,
  181. socketPlugRules,
  182. socketPlugPools,
  183. socketPlugMembers,
  184. exoticCatalysts,
  185. inventoryBuckets,
  186. socketEntryLists,
  187. socketEntryTables,
  188. abilityBuckets,
  189. progressions,
  190. scenarios,
  191. rosterGroups,
  192. spawnStems,
  193. spawnNameHashes,
  194. spawnPoints,
  195. hashNames,
  196. vendorIndex,
  197. vendorDefinitions,
  198. vendorSaleRows,
  199. vendorInstalledRows,
  200. };
  201. }
  202. /**
  203. * Releases one transient cache snapshot bank without walking its capacity.
  204. * @param storage Bank emptied and handed back to the allocator.
  205. */
  206. template <typename Value> void release_bank(std::vector<Value>& storage) noexcept {
  207. storage.clear();
  208. storage.shrink_to_fit();
  209. }
  210. /** Releases transient cache snapshot banks without allocating or walking their capacities. */
  211. void release_scratch_locked(Context& state) noexcept {
  212. release_bank(state.namedScratch);
  213. release_bank(state.itemScratch);
  214. release_bank(state.collectibleScratch);
  215. release_bank(state.materialRequirementSetScratch);
  216. release_bank(state.itemDetailScratch);
  217. release_bank(state.socketPlugRuleScratch);
  218. release_bank(state.socketPlugPoolScratch);
  219. release_bank(state.socketPlugMemberScratch);
  220. release_bank(state.exoticCatalystScratch);
  221. release_bank(state.inventoryBucketScratch);
  222. release_bank(state.socketEntryListScratch);
  223. release_bank(state.socketEntryTableScratch);
  224. release_bank(state.abilityBucketScratch);
  225. release_bank(state.progressionScratch);
  226. release_bank(state.scenarioScratch);
  227. release_bank(state.rosterGroupScratch);
  228. release_bank(state.spawnStemScratch);
  229. release_bank(state.spawnNameHashScratch);
  230. release_bank(state.spawnPointScratch);
  231. release_bank(state.hashNameScratch);
  232. release_bank(state.vendorIndexScratch);
  233. release_bank(state.vendorDefinitionScratch);
  234. release_bank(state.vendorSaleRowScratch);
  235. release_bank(state.vendorInstalledRowScratch);
  236. state.constantsScratch = {};
  237. }
  238. /** Clears fixed cache paths, identity, flags, and snapshot storage. */
  239. void clear_locked(Context& state) noexcept {
  240. release_scratch_locked(state);
  241. state.cacheDirectory = {};
  242. state.cachePath = {};
  243. state.buildIdentity = {};
  244. state.enabled = false;
  245. state.persisted = false;
  246. state.replaceStaleCache = false;
  247. }
  248. /** Gives read-only views over the used rows of one canonical snapshot. */
  249. cache::records::Domains occupied_domains(Context& state,
  250. const cache::records::DomainCounts& counts) noexcept {
  251. const std::span<const items::details::Definition> itemDetails{state.itemDetailScratch.data(),
  252. counts.itemDetails};
  253. const std::span<const items::socket_plugs::Rule> socketPlugRules{
  254. state.socketPlugRuleScratch.data(), counts.socketPlugRules};
  255. const std::span<const items::socket_plugs::Pool> socketPlugPools{
  256. state.socketPlugPoolScratch.data(), counts.socketPlugPools};
  257. const std::span<const items::socket_plugs::Member> socketPlugMembers{
  258. state.socketPlugMemberScratch.data(), counts.socketPlugMembers};
  259. const std::span<const items::catalysts::Definition> exoticCatalysts{
  260. state.exoticCatalystScratch.data(), counts.exoticCatalysts};
  261. return {
  262. state.constantsScratch,
  263. std::span<const content::Definition>{state.namedScratch.data(), counts.named},
  264. std::span<const build_data::items::Definition>{state.itemScratch.data(), counts.items},
  265. std::span<const build_data::collectibles::Definition>{state.collectibleScratch.data(),
  266. counts.collectibles},
  267. std::span<const material_requirements::Definition>{
  268. state.materialRequirementSetScratch.data(), counts.materialRequirementSets},
  269. itemDetails,
  270. socketPlugRules,
  271. socketPlugPools,
  272. socketPlugMembers,
  273. exoticCatalysts,
  274. std::span<const inventory::buckets::Descriptor>{state.inventoryBucketScratch.data(),
  275. counts.inventoryBuckets},
  276. std::span<const socket_entry_lists::Definition>{state.socketEntryListScratch.data(),
  277. counts.socketEntryLists},
  278. std::span<const socket_entry_lists::EntryTable>{state.socketEntryTableScratch.data(),
  279. counts.socketEntryTables},
  280. std::span<const abilities::Definition>{state.abilityBucketScratch.data(),
  281. counts.abilityBuckets},
  282. std::span<const progressions::Definition>{state.progressionScratch.data(),
  283. counts.progressions},
  284. std::span<const scenarios::Definition>{state.scenarioScratch.data(), counts.scenarios},
  285. std::span<const scenarios::RosterGroup>{state.rosterGroupScratch.data(),
  286. counts.rosterGroups},
  287. std::span<const spawn_sets::Stem>{state.spawnStemScratch.data(), counts.spawnStems},
  288. std::span<const spawn_sets::NameHash>{state.spawnNameHashScratch.data(),
  289. counts.spawnNameHashes},
  290. std::span<const spawn_sets::Point>{state.spawnPointScratch.data(), counts.spawnPoints},
  291. std::span<const hash_names::Name>{state.hashNameScratch.data(), counts.hashNames},
  292. std::span<const vendors::IndexEntry>{state.vendorIndexScratch.data(), counts.vendorIndex},
  293. std::span<const vendors::Definition>{state.vendorDefinitionScratch.data(),
  294. counts.vendorDefinitions},
  295. std::span<const vendors::SaleRow>{state.vendorSaleRowScratch.data(), counts.vendorSaleRows},
  296. std::span<const vendors::InstalledRow>{state.vendorInstalledRowScratch.data(),
  297. counts.vendorInstalledRows},
  298. };
  299. }
  300. /** Saves one complete canonical snapshot when every domain is ready. */
  301. bool persist_if_complete_locked(Context& state) noexcept {
  302. if (!all_domains_ready() || state.persisted || !state.enabled) {
  303. return true;
  304. }
  305. cache::records::DomainCounts counts{};
  306. // One canonical snapshot keeps header counts and payload rows in the same commit.
  307. if (!snapshot_domains_locked(state, counts)) {
  308. return false;
  309. }
  310. // The write flushes every domain to disk once. It is the only stall here, so the overlay
  311. // covers just it.
  312. core::ui::busy::begin(core::ui::busy::Task::cacheWrite);
  313. const bool written =
  314. cache::write(state.cacheDirectory.chars.data(),
  315. state.cachePath.chars.data(),
  316. state.buildIdentity,
  317. occupied_domains(state, counts),
  318. state.replaceStaleCache ? cache::WriteDisposition::replaceStale
  319. : cache::WriteDisposition::createOnly);
  320. core::ui::busy::end(core::ui::busy::Task::cacheWrite);
  321. if (!written) {
  322. return false;
  323. }
  324. state.persisted = true;
  325. state.replaceStaleCache = false;
  326. release_scratch_locked(state);
  327. return true;
  328. }
  329. } // namespace sunrise::state::build_data::runtime::persistence
  330. namespace sunrise::state::build_data {
  331. /** @return True only when every domain is ready and any needed cache write works. */
  332. bool persist() noexcept {
  333. runtime::persistence::Context& state = runtime::persistence::context();
  334. AcquireSRWLockExclusive(&state.lock);
  335. const bool result = runtime::persistence::all_domains_ready()
  336. && runtime::persistence::persist_if_complete_locked(state);
  337. ReleaseSRWLockExclusive(&state.lock);
  338. return result;
  339. }
  340. } // namespace sunrise::state::build_data