reward_catalog.cpp 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. #include "reward_catalog.h"
  2. #include <algorithm>
  3. #include <array>
  4. #include <cmath>
  5. #include <mutex>
  6. #include <shared_mutex>
  7. #include "../../unlocks/definition.h"
  8. #include "../table.h"
  9. #include "core/threading/srw_lock.h"
  10. namespace sunrise::state::build_data::rewards {
  11. namespace {
  12. core::threading::SrwLock g_lock;
  13. Table<Pool, kPoolCapacity> g_pools;
  14. Table<Entry, kEntryCapacity> g_entries;
  15. Table<Item, kItemCapacity> g_items;
  16. Table<Instruction, kInstructionCapacity> g_instructions;
  17. Table<Modifier, kModifierCapacity> g_modifiers;
  18. Table<SocketOverride, kSocketOverrideCapacity> g_sockets;
  19. /** Borrows every published bank; the caller holds the catalog lock. */
  20. View view() noexcept {
  21. return {g_pools.rows(),
  22. g_entries.rows(),
  23. g_items.rows(),
  24. g_instructions.rows(),
  25. g_modifiers.rows(),
  26. g_sockets.rows()};
  27. }
  28. /** Rejects cycles and bounds combined pool and automatic-wrapper paths, memoizing heights. */
  29. bool valid_depth(View data,
  30. std::size_t index,
  31. std::array<std::uint8_t, kPoolCapacity>& heights,
  32. std::size_t depth = 0) noexcept {
  33. constexpr std::uint8_t kVisiting = 0xFF;
  34. if (depth >= kTraversalDepth) {
  35. return false;
  36. }
  37. if (heights[index] != 0) {
  38. // Reused subtrees must also fit when reached through a deeper path.
  39. return heights[index] != kVisiting && heights[index] <= kTraversalDepth - depth;
  40. }
  41. heights[index] = kVisiting;
  42. std::uint8_t height = 1;
  43. const Range range = data.pools[index].entries;
  44. for (const Entry& entry : data.entries.subspan(range.first, range.count)) {
  45. auto child = entry.poolIndex;
  46. // Pool references take precedence over an item named by the same entry.
  47. if (child == kAbsent && entry.itemIndex != kAbsent) {
  48. const Item& item = data.items[entry.itemIndex];
  49. if ((item.flags & kOpenOnAcquisition) != 0) {
  50. child = item.poolIndex;
  51. }
  52. }
  53. if (child == kAbsent) {
  54. continue;
  55. }
  56. if (!valid_depth(data, child, heights, depth + 1)) {
  57. return false;
  58. }
  59. height = (std::max)(height, static_cast<std::uint8_t>(heights[child] + 1));
  60. }
  61. heights[index] = height;
  62. return true;
  63. }
  64. /** Checks one entry's references, dependent ranges and weight. */
  65. bool valid_entry(View data, const Entry& entry) noexcept {
  66. return (entry.itemIndex == kAbsent || entry.itemIndex < data.items.size())
  67. && (entry.poolIndex == kAbsent || entry.poolIndex < data.pools.size())
  68. && (!entry.supplementalMissing || entry.supplementalIndex != kAbsent)
  69. && fits(entry.condition, data.instructions) && fits(entry.modifiers, data.modifiers)
  70. && fits(entry.sockets, data.sockets) && std::isfinite(entry.weight) && entry.weight >= 0;
  71. }
  72. /** An unavailable item row carries nothing; an available one names valid banks. */
  73. bool valid_item(View data, const Item& item) noexcept {
  74. if (item.definitionHash == 0) {
  75. return item.poolIndex == kAbsent && item.acquiredFlag == kAbsent
  76. && item.selectionCount == 0;
  77. }
  78. return item.selectionCount <= item.selections.size()
  79. && (item.acquiredFlag == kAbsent || item.acquiredFlag < unlocks::kAccountFlagCapacity)
  80. && (item.poolIndex == kAbsent || item.poolIndex < data.pools.size());
  81. }
  82. } // namespace
  83. /** Discards the reward graph and every borrowed bank. */
  84. void clear() noexcept {
  85. const std::lock_guard guard(g_lock);
  86. g_pools.clear();
  87. g_entries.clear();
  88. g_items.clear();
  89. g_instructions.clear();
  90. g_modifiers.clear();
  91. g_sockets.clear();
  92. }
  93. /** @return True once pool and item banks have been published. */
  94. bool ready() noexcept {
  95. const std::shared_lock guard(g_lock);
  96. return g_pools.count() != 0 && g_items.count() != 0;
  97. }
  98. /** Checks bank ranges, item references and bounded acyclic pool and wrapper traversal. */
  99. bool valid(View data) noexcept {
  100. if (data.pools.empty() || data.pools.size() > kPoolCapacity || data.entries.empty()
  101. || data.entries.size() > kEntryCapacity || data.items.empty()
  102. || data.items.size() > kItemCapacity || data.instructions.size() > kInstructionCapacity
  103. || data.modifiers.size() > kModifierCapacity
  104. || data.sockets.size() > kSocketOverrideCapacity) {
  105. return false;
  106. }
  107. for (const Pool& pool : data.pools) {
  108. if ((pool.definitionHash == 0 && pool.entries.count != 0)
  109. || !fits(pool.entries, data.entries)) {
  110. return false;
  111. }
  112. }
  113. for (const Entry& entry : data.entries) {
  114. if (!valid_entry(data, entry)) {
  115. return false;
  116. }
  117. }
  118. for (const Item& item : data.items) {
  119. if (!valid_item(data, item)) {
  120. return false;
  121. }
  122. }
  123. if (!std::all_of(data.instructions.begin(), data.instructions.end(), unlocks::valid)) {
  124. return false;
  125. }
  126. for (const Modifier& modifier : data.modifiers) {
  127. if (!fits(modifier.condition, data.instructions) || !std::isfinite(modifier.value)) {
  128. return false;
  129. }
  130. }
  131. for (const SocketOverride& socket : data.sockets) {
  132. if (!valid_socket(socket, data.items.size())) {
  133. return false;
  134. }
  135. }
  136. std::array<std::uint8_t, kPoolCapacity> heights{};
  137. for (std::size_t index = 0; index < data.pools.size(); ++index) {
  138. if (!valid_depth(data, index, heights)) {
  139. return false;
  140. }
  141. }
  142. return true;
  143. }
  144. /** Publishes validated banks together under the catalog lock. */
  145. bool replace(View data) noexcept {
  146. if (!valid(data)) {
  147. return false;
  148. }
  149. const std::lock_guard guard(g_lock);
  150. return g_pools.replace(data.pools) && g_entries.replace(data.entries)
  151. && g_items.replace(data.items) && g_instructions.replace(data.instructions)
  152. && g_modifiers.replace(data.modifiers) && g_sockets.replace(data.sockets);
  153. }
  154. /** Lends every bank to one callback under the read lock. */
  155. bool read(void* context, bool (*consume)(void*, View) noexcept) noexcept {
  156. const std::shared_lock guard(g_lock);
  157. return consume != nullptr && g_pools.count() != 0 && consume(context, view());
  158. }
  159. /** Copies one extracted item row; unavailable rows are refused. */
  160. bool find_item(std::uint16_t itemIndex, Item& item) noexcept {
  161. item = {};
  162. const std::shared_lock guard(g_lock);
  163. if (itemIndex >= g_items.count() || g_items.rows()[itemIndex].definitionHash == 0) {
  164. return false;
  165. }
  166. item = g_items.rows()[itemIndex];
  167. return true;
  168. }
  169. /** Copies every pool; count is zero when the output is too small. */
  170. bool snapshot(std::span<Pool> output, std::size_t& count) noexcept {
  171. const std::shared_lock guard(g_lock);
  172. return g_pools.snapshot(output, count);
  173. }
  174. /** Copies every entry; count is zero when the output is too small. */
  175. bool snapshot(std::span<Entry> output, std::size_t& count) noexcept {
  176. const std::shared_lock guard(g_lock);
  177. return g_entries.snapshot(output, count);
  178. }
  179. /** Copies every item row; count is zero when the output is too small. */
  180. bool snapshot(std::span<Item> output, std::size_t& count) noexcept {
  181. const std::shared_lock guard(g_lock);
  182. return g_items.snapshot(output, count);
  183. }
  184. /** Copies every condition instruction; count is zero when the output is too small. */
  185. bool snapshot(std::span<Instruction> output, std::size_t& count) noexcept {
  186. const std::shared_lock guard(g_lock);
  187. return g_instructions.snapshot(output, count);
  188. }
  189. /** Copies every modifier; count is zero when the output is too small. */
  190. bool snapshot(std::span<Modifier> output, std::size_t& count) noexcept {
  191. const std::shared_lock guard(g_lock);
  192. return g_modifiers.snapshot(output, count);
  193. }
  194. /** Copies every socket override; count is zero when the output is too small. */
  195. bool snapshot(std::span<SocketOverride> output, std::size_t& count) noexcept {
  196. const std::shared_lock guard(g_lock);
  197. return g_sockets.snapshot(output, count);
  198. }
  199. } // namespace sunrise::state::build_data::rewards