build_data_persistence.cpp 17 KB

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