investment_refresh.cpp 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. #include <Windows.h>
  2. #include <array>
  3. #include <cstdio>
  4. #include <string_view>
  5. #include "../../../core/logging/log.h"
  6. #include "../../../core/ui/busy/busy.h"
  7. #include "../../../middleware/content/packages/reader/reader.h"
  8. #include "../../../state/build_data/runtime.h"
  9. #include "../../../state/runtime/runtime.h"
  10. #include "../../process/freeze/client_process_freeze.h"
  11. #include "../items/packages/build.h"
  12. #include "internal.h"
  13. #include "runtime.h"
  14. namespace sunrise::client::content::investment {
  15. namespace {
  16. SRWLOCK g_refreshLock{SRWLOCK_INIT};
  17. /** One line reports the freeze, so a run that could not hold the game is visible. */
  18. constexpr std::size_t kLineLimit = 96;
  19. /**
  20. * @return True when every persistent mapping domain is fully published.
  21. * The destination layouts and spawn sets belong here even though they are not equipment mappings.
  22. * This is the only caller of the package pass, so a domain left out of this test stops being
  23. * extracted once the others finish, and the cache can then never be written.
  24. */
  25. [[nodiscard]] bool ready() noexcept {
  26. return state::build_data::named_catalog_ready() && state::build_data::item_definitions_ready()
  27. && state::build_data::collectible_definitions_ready()
  28. && state::build_data::material_requirement_sets_ready()
  29. && state::build_data::configured_item_details_ready()
  30. && state::build_data::socket_plug_rules_ready()
  31. && state::build_data::inventory_bucket_descriptors_ready()
  32. && state::build_data::socket_entry_lists_ready()
  33. && state::build_data::ability_buckets_ready()
  34. && state::build_data::progression_definitions_ready()
  35. && state::build_data::scenario_layouts_ready() && state::build_data::spawn_sets_ready()
  36. && state::build_data::hash_names_ready()
  37. && state::build_data::investment_constants_ready();
  38. }
  39. /**
  40. * Reports the outcome of one freeze attempt.
  41. * @param frozen True when the game was held.
  42. * @param threadCount Threads that were suspended.
  43. */
  44. void report_freeze(bool frozen, std::size_t threadCount) noexcept {
  45. std::array<char, kLineLimit> line{};
  46. const int written = std::snprintf(line.data(),
  47. line.size(),
  48. "ev=extract stage=freeze result=%s threads=%zu",
  49. frozen ? "ok" : "fail",
  50. threadCount);
  51. if (written <= 0) {
  52. return;
  53. }
  54. const auto length = static_cast<std::size_t>(written) < line.size()
  55. ? static_cast<std::size_t>(written)
  56. : line.size() - 1;
  57. // The pass runs in slices, so a working freeze reports at debug and only a failure is loud.
  58. core::log::write(core::log::Channel::client,
  59. frozen ? core::log::Level::debug : core::log::Level::warn,
  60. std::string_view(line.data(), length));
  61. }
  62. } // namespace
  63. /** @return True when the next refresh slice must hold the process for a package sweep. */
  64. bool requires_process_freeze() noexcept {
  65. return !state::build_data::item_definitions_ready() && items::packages::readable();
  66. }
  67. /** Publishes every installed equipment mapping domain. */
  68. bool refresh() noexcept {
  69. if (ready()) {
  70. // The same lock as the extraction path. A cache write holds its own lock across file
  71. // calls, so a held thread stopped inside one would deadlock the freeze below.
  72. AcquireSRWLockExclusive(&g_refreshLock);
  73. const bool persisted =
  74. state::ensure_profile_item_identities() && state::build_data::persist();
  75. // Nothing reads a package again until the next boot, so the open files and the held
  76. // tables go back now rather than at process exit.
  77. middleware::content::packages::reader::release_caches();
  78. ReleaseSRWLockExclusive(&g_refreshLock);
  79. core::ui::busy::end(core::ui::busy::Task::contentExtraction);
  80. return persisted;
  81. }
  82. AcquireSRWLockExclusive(&g_refreshLock);
  83. // Only the item sweep holds the game, and only once the block keys exist. The destination and
  84. // spawn-set passes after it are tens of thousands of tag reads over many slices, so the
  85. // overlay covers the whole pass: without it the longest stall of the boot has nothing on
  86. // screen.
  87. const bool sweeping = requires_process_freeze();
  88. process::freeze::Held held{};
  89. bool frozen = false;
  90. std::size_t heldThreads = 0;
  91. if (sweeping) {
  92. // The overlay reaches the screen before the freeze stops the frame loop. A held game
  93. // cannot time its connection out, which a slow disk otherwise causes here.
  94. core::ui::busy::begin(core::ui::busy::Task::contentExtraction);
  95. frozen = process::freeze::hold(held);
  96. heldThreads = held.count;
  97. } else {
  98. core::ui::busy::raise(core::ui::busy::Task::contentExtraction);
  99. }
  100. // The package pass owns the item table and must not wait on runtime content lookups.
  101. (void)items::packages::build();
  102. const bool domainsReady = ready();
  103. const bool complete =
  104. domainsReady && state::ensure_profile_item_identities() && state::build_data::persist();
  105. process::freeze::release(held);
  106. // The overlay ends with the work, not with the slice, so it spans every retry the pass needs.
  107. if (complete) {
  108. core::ui::busy::end(core::ui::busy::Task::contentExtraction);
  109. }
  110. ReleaseSRWLockExclusive(&g_refreshLock);
  111. if (sweeping) {
  112. report_freeze(frozen, heldThreads);
  113. }
  114. return complete;
  115. }
  116. } // namespace sunrise::client::content::investment