internal.h 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. #pragma once
  2. #include <array>
  3. #include <cstddef>
  4. #include <cstdint>
  5. #include <span>
  6. #include <vector>
  7. #include "../../../middleware/content/packages/reader/reader.h"
  8. #include "../../../middleware/content/packages/tables/roster_intersection.h"
  9. #include "../../../state/build_data/scenarios/definition.h"
  10. namespace sunrise::client::content::scenarios {
  11. namespace layouts = state::build_data::scenarios;
  12. namespace reader = middleware::content::packages::reader;
  13. /**
  14. * Placed-object tags the memo holds. The walk reaches 5,826 distinct objects over the installed
  15. * packages, and the table needs headroom to stay a cheap open-addressed probe.
  16. */
  17. inline constexpr std::size_t kObjectMemoCapacity = 16'384;
  18. /** Memo value for an object that declares no roster slot type. */
  19. inline constexpr std::uint16_t kNotARosterGroup = 0xFFFF;
  20. /** Slot types run from 1 through the widest the packages declare. */
  21. inline constexpr std::size_t kSlotTypeSpan = layouts::kMaximumSlotType + 1;
  22. /** One memo row: a placed-object tag and the roster group it produced. */
  23. struct ObjectMemo {
  24. std::uint32_t tag{};
  25. std::uint16_t group{kNotARosterGroup};
  26. };
  27. /** Fixed working storage for one roster pass, kept off the caller stack. */
  28. struct RosterStorage {
  29. std::vector<std::byte> scenario;
  30. std::vector<std::byte> entry;
  31. std::vector<std::byte> registry;
  32. std::vector<std::byte> object;
  33. std::vector<std::byte> chain;
  34. std::array<ObjectMemo, kObjectMemoCapacity> memo{};
  35. std::array<layouts::RosterGroup, layouts::kRosterGroupCapacity> groups{};
  36. std::size_t groupCount{};
  37. /** Slot flags per slot type, read from a group object's descriptor chain. */
  38. std::array<std::uint8_t, kSlotTypeSpan> slotFlags{};
  39. std::array<std::uint8_t, kSlotTypeSpan> slotFlagsKnown{};
  40. /** Group objects whose descriptor chain did not give every slot type they declare. */
  41. std::size_t unresolvedGroups{};
  42. /** Destinations walked so far. The walk resumes here on the next call. */
  43. std::size_t cursor{};
  44. /** Tag reads spent in the current call, which is what bounds how long it blocks. */
  45. std::size_t reads{};
  46. };
  47. /** Tag-read budget bounds one process-freeze interval and keeps worker shutdown responsive. */
  48. inline constexpr std::size_t kRosterReadBudget = 150;
  49. /** Live scenario tags found by the class sweep. The measured live count is 468. */
  50. inline constexpr std::size_t kLiveTagCapacity = 1'024;
  51. /**
  52. * How long the collection keeps retrying the destinations that have not read yet.
  53. * Packages register during the boot, so an early attempt reads fewer of them. One run latched at
  54. * 417 of 466 and the destination it dropped was the Tower.
  55. */
  56. inline constexpr std::uint64_t kResolveWindowMs = 15'000;
  57. /** Tag reads one collection call may spend, for the same reason the roster walk is bounded. */
  58. inline constexpr std::size_t kResolveReadBudget = 150;
  59. /** One live scenario tag and the map-package stem of the package that carries it. */
  60. struct LiveTag {
  61. std::uint32_t tag{};
  62. std::array<char, layouts::kSpawnStemCapacity> stem{};
  63. std::uint8_t stemLength{};
  64. };
  65. /** One pass of fixed storage, kept off the caller stack. */
  66. struct Storage {
  67. std::array<LiveTag, kLiveTagCapacity> liveTags{};
  68. std::size_t liveTagCount{};
  69. std::array<layouts::Definition, layouts::kDefinitionCapacity> rows{};
  70. std::size_t rowCount{};
  71. /** Patch index each row's tag came from, so a later patch replaces an earlier one. */
  72. std::array<std::uint32_t, layouts::kDefinitionCapacity> rowPatch{};
  73. /** One byte per row: set once its bubble layout has read. */
  74. std::array<std::uint8_t, layouts::kDefinitionCapacity> resolved{};
  75. std::size_t resolvedCount{};
  76. /** Rows whose tag the class sweep still carries. Only these can ever read. */
  77. std::size_t liveRowCount{};
  78. /** Where the next resolve pass starts, so every pending row is retried in turn. */
  79. std::size_t resolveCursor{};
  80. /** Tick after which the collection stops waiting for the rows that have not read. */
  81. std::uint64_t resolveDeadlineTick{};
  82. std::vector<std::byte> blob{};
  83. RosterStorage roster{};
  84. /** Resolved rows, moved to the front. The roster walk runs over exactly these. */
  85. std::size_t keptCount{};
  86. /** Retried rounds of the resolve window, so a boot that never reads reports it once. */
  87. std::uint32_t resolveRounds{};
  88. /** Set once the sweep and the name match are done, so they run once per boot. */
  89. bool collected{};
  90. /** Set once the resolved rows are compacted and the roster walk may start. */
  91. bool compacted{};
  92. };
  93. /**
  94. * Sweeps the installed packages for scenario tags and matches them to destination names.
  95. * @param source Package directory and borrowed block keys.
  96. * @param storage Pass storage receiving the live tags and the named rows.
  97. * @param reason Receives the step that refused, or stays null.
  98. * @return True when both steps finished.
  99. */
  100. [[nodiscard]] bool
  101. collect_rows(const reader::Source& source, Storage& storage, const char*& reason) noexcept;
  102. /**
  103. * Reads the bubble layout of rows that have not read yet, within this call's budget.
  104. * @param source Package directory and borrowed block keys.
  105. * @param scratch Lock-owned block storage.
  106. * @param storage Pass storage carrying the resolve cursor.
  107. * @return True when the collection has settled and may be compacted.
  108. */
  109. [[nodiscard]] bool
  110. resolve_pending(const reader::Source& source, reader::Scratch& scratch, Storage& storage) noexcept;
  111. /**
  112. * Moves every resolved row to the front of the row array.
  113. * @param storage Pass storage whose kept count is set here.
  114. */
  115. void compact_rows(Storage& storage) noexcept;
  116. /**
  117. * Re-arms the resolve window so a pass that read nothing tries the whole row set again.
  118. * @param storage Pass storage whose resolve state is cleared.
  119. */
  120. void rearm_resolve(Storage& storage) noexcept;
  121. /** One candidate group of one destination, with what its publish order is sorted on. */
  122. struct Candidate {
  123. std::uint16_t group{};
  124. std::uint32_t key{};
  125. bool bindsPlayer{};
  126. bool reportsLifetime{};
  127. bool primaryRegistry{};
  128. };
  129. /** @return True when both groups carry the same registry key and full wire slot layout. */
  130. [[nodiscard]] constexpr bool same_group_layout(const layouts::RosterGroup& left,
  131. const layouts::RosterGroup& right) noexcept {
  132. if (left.registryKey != right.registryKey || left.slotCount != right.slotCount) {
  133. return false;
  134. }
  135. for (std::size_t slot = 0; slot < left.slotCount; ++slot) {
  136. if (left.slotTypes[slot] != right.slotTypes[slot]
  137. || left.slotFlags[slot] != right.slotFlags[slot]) {
  138. return false;
  139. }
  140. }
  141. return true;
  142. }
  143. /** Everything one destination's walk builds up. */
  144. struct Walk {
  145. middleware::content::packages::tables::RosterIntersection intersection{};
  146. std::array<Candidate, middleware::content::packages::tables::kRosterKeyCapacity> candidates{};
  147. std::size_t candidateCount{};
  148. };
  149. /**
  150. * Keeps the candidates whose key is in every slice set and writes them into the destination row.
  151. * @param walk Accumulator for one destination.
  152. * @param row Destination row receiving its group indices.
  153. */
  154. void publish_safe(Walk& walk, layouts::Definition& row) noexcept;
  155. /**
  156. * Finds the roster group of one placed object, reading it only the first time it is seen.
  157. * @param source Package directory and borrowed block keys.
  158. * @param scratch Lock-owned block storage.
  159. * @param storage Working storage for this pass.
  160. * @param objectTag Tag from an object registry.
  161. * @param group Receives the roster group index, or the not-a-group sentinel.
  162. * @return True when the object was read or was already known.
  163. */
  164. [[nodiscard]] bool resolve_object(const reader::Source& source,
  165. reader::Scratch& scratch,
  166. RosterStorage& storage,
  167. std::uint32_t objectTag,
  168. std::uint16_t& group) noexcept;
  169. /**
  170. * Walks the next batch of destination rows for their roster groups.
  171. * One call spends at most the read budget and then returns, so the pass resumes across calls.
  172. * @param source Package directory and borrowed block keys.
  173. * @param scratch Lock-owned block storage.
  174. * @param storage Working storage carrying the cursor between calls.
  175. * @param rows Destination rows whose tag is already set, updated in place.
  176. * @return True when every row has been walked.
  177. */
  178. [[nodiscard]] bool build_rosters(const reader::Source& source,
  179. reader::Scratch& scratch,
  180. RosterStorage& storage,
  181. std::span<layouts::Definition> rows) noexcept;
  182. } // namespace sunrise::client::content::scenarios