activity_destination_spawn_binding.cpp 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. #include "activity_destination_spawn_binding.h"
  2. #include <array>
  3. #include <atomic>
  4. #include <cstddef>
  5. #include <cstdio>
  6. #include <span>
  7. #include <string_view>
  8. #include "../../../core/logging/log.h"
  9. #include "../../../middleware/content/packages/tables/region_reader.h"
  10. #include "../../build_data/runtime.h"
  11. namespace sunrise::state::activity::destination {
  12. namespace {
  13. /** Spawn-set rows read for one stem. The widest installed stem declares 294. */
  14. constexpr std::size_t kSpawnRowCapacity = 512;
  15. /** Last reported hash, so a per-push decision is written once. */
  16. std::atomic_uint32_t g_reportedHash{};
  17. /** @return The destination's package name as a bounded view. */
  18. [[nodiscard]] std::string_view name_of(const DestinationSelection& selection) noexcept {
  19. return {reinterpret_cast<const char*>(selection.packageName.data()),
  20. selection.packageNameLength};
  21. }
  22. /**
  23. * Tests whether a destination loads the package that declares one set.
  24. * @param layout Destination row carrying the packages it loads.
  25. * @param row Spawn-set row carrying the packages that declare it.
  26. * @return True when the set is in the map package or in one the destination names.
  27. */
  28. [[nodiscard]] bool loads_package(const build_data::scenarios::Definition& layout,
  29. const build_data::spawn_sets::NameHash& row) noexcept {
  30. if (row.inMapPackage != 0) {
  31. return true;
  32. }
  33. const std::size_t declared =
  34. layout.packageCount < layout.packages.size() ? layout.packageCount : layout.packages.size();
  35. for (std::size_t index = 0; index < row.activityPackageCount; ++index) {
  36. for (std::size_t package = 0; package < declared; ++package) {
  37. if (layout.packages[package] == row.activityPackages[index]) {
  38. return true;
  39. }
  40. }
  41. }
  42. return false;
  43. }
  44. /** Writes the drop once per hash. @param name Destination the set was dropped for. */
  45. void report_dropped(std::string_view name, std::uint32_t hash) noexcept {
  46. if (g_reportedHash.exchange(hash, std::memory_order_acq_rel) == hash) {
  47. return;
  48. }
  49. std::array<char, core::log::kLineCapacity> line{};
  50. const int written = std::snprintf(line.data(),
  51. line.size(),
  52. "ev=activity stage=spawn_set result=dropped name=%.*s "
  53. "spawn=0x%08X reason=not_loaded",
  54. static_cast<int>(name.size()),
  55. name.data(),
  56. hash);
  57. if (written > 0) {
  58. core::log::write(core::log::Channel::state,
  59. core::log::Level::warn,
  60. {line.data(), static_cast<std::size_t>(written)});
  61. }
  62. }
  63. /**
  64. * Tests whether one bubble of a destination is named by a spawn row's bubble mask.
  65. * The mask is keyed by map-global bubble index, so the destination's own ordinal has to be
  66. * translated through its map-index table first.
  67. * @param layout Destination row carrying the map-index table.
  68. * @param row Spawn-set row carrying the mask.
  69. * @param bubble Destination bubble ordinal.
  70. * @return True when the row declares that bubble.
  71. */
  72. [[nodiscard]] bool bubble_declares_set(const build_data::scenarios::Definition& layout,
  73. const build_data::spawn_sets::NameHash& row,
  74. std::size_t bubble) noexcept {
  75. if (bubble >= layout.bubbleCount || bubble >= layout.bubbleMapIndices.size()) {
  76. return false;
  77. }
  78. const std::size_t mapIndex = layout.bubbleMapIndices[bubble];
  79. const std::size_t byteIndex = mapIndex / 8;
  80. if (byteIndex >= row.bubbleMask.size()) {
  81. return false;
  82. }
  83. return (row.bubbleMask[byteIndex] >> (mapIndex % 8) & 1U) != 0;
  84. }
  85. } // namespace
  86. /** Finds the slice set whose bubble actually declares one spawn set. */
  87. std::uint16_t spawn_set_slice_set(const DestinationSelection& selection,
  88. std::uint32_t spawnSetHash,
  89. std::uint16_t arrivalSliceSet) noexcept {
  90. namespace tables = middleware::content::packages::tables;
  91. if (spawnSetHash == 0 || spawnSetHash == kAbsentSpawnSetHash) {
  92. return arrivalSliceSet;
  93. }
  94. const std::string_view name = name_of(selection);
  95. build_data::scenarios::Definition layout{};
  96. if (name.empty() || !build_data::find_scenario_layout(name, layout)) {
  97. return arrivalSliceSet;
  98. }
  99. const std::string_view stem(layout.spawnStem.data(), layout.spawnStemLength);
  100. static std::array<build_data::spawn_sets::NameHash, kSpawnRowCapacity> rows{};
  101. std::size_t count = 0;
  102. if (stem.empty() || !build_data::find_spawn_sets(stem, rows, count)) {
  103. return arrivalSliceSet;
  104. }
  105. for (std::size_t index = 0; index < count; ++index) {
  106. if (rows[index].value != spawnSetHash) {
  107. continue;
  108. }
  109. // The arrival wins whenever it is valid, so every configuration that already places a
  110. // player keeps the exact slice set it publishes today.
  111. const std::size_t arrivalBubble = arrivalSliceSet / tables::kSliceSetIndexFactor;
  112. if (bubble_declares_set(layout, rows[index], arrivalBubble)) {
  113. return arrivalSliceSet;
  114. }
  115. const std::size_t declared = layout.bubbleCount < layout.bubbleMapIndices.size()
  116. ? layout.bubbleCount
  117. : layout.bubbleMapIndices.size();
  118. for (std::size_t bubble = 0; bubble < declared; ++bubble) {
  119. if (bubble_declares_set(layout, rows[index], bubble)) {
  120. return static_cast<std::uint16_t>(
  121. tables::region_index(static_cast<std::uint32_t>(bubble)));
  122. }
  123. }
  124. return arrivalSliceSet;
  125. }
  126. // A hash no row carries is not proof of a miss: the row set can be capped.
  127. return arrivalSliceSet;
  128. }
  129. /** Drops a spawn set the destination cannot load. Only a proved miss is dropped. */
  130. std::uint32_t attachable_spawn_set_hash(const DestinationSelection& selection,
  131. std::uint32_t fallback) noexcept {
  132. const std::uint32_t hash = resolve_spawn_set_hash(selection, fallback);
  133. if (hash == 0 || hash == kAbsentSpawnSetHash) {
  134. return hash;
  135. }
  136. const std::string_view name = name_of(selection);
  137. build_data::scenarios::Definition layout{};
  138. if (name.empty() || !build_data::find_scenario_layout(name, layout)) {
  139. return hash;
  140. }
  141. const std::string_view stem(layout.spawnStem.data(), layout.spawnStemLength);
  142. static std::array<build_data::spawn_sets::NameHash, kSpawnRowCapacity> rows{};
  143. std::size_t count = 0;
  144. if (stem.empty() || !build_data::find_spawn_sets(stem, rows, count)) {
  145. return hash;
  146. }
  147. for (std::size_t index = 0; index < count; ++index) {
  148. if (rows[index].value != hash) {
  149. continue;
  150. }
  151. if (loads_package(layout, rows[index])) {
  152. g_reportedHash.store(0, std::memory_order_release);
  153. return hash;
  154. }
  155. report_dropped(name, hash);
  156. return kAbsentSpawnSetHash;
  157. }
  158. // A hash no row carries is not proof of a miss: the row set can be capped. Send it as picked.
  159. return hash;
  160. }
  161. } // namespace sunrise::state::activity::destination