build_data_persistence.cpp 17 KB

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