Przeglądaj źródła

Stop a book's count driving its neighbour's bar

A parent's bar sits one slot above its category on books where that slot is
free, confirmed by marker on two: The Pigeon and the Phoenix reads 2573 against
a category of 2572, The Singular Exegete 2663 against 2662.

Categories are allocated contiguously, so for many books the slot above is the
next book's category, and writing it there drove the wrong book: claiming in one
book moved another's parent bar. Those are now skipped by checking the slot
against every known category.

Books with a free slot above count correctly. Books inside a contiguous run
leave their parent bar at zero, which is wrong but is not someone else's count.
Millie 2 tygodni temu
rodzic
commit
5c2f950494
1 zmienionych plików z 28 dodań i 2 usunięć
  1. 28 2
      Sunrise/src/state/record_claims/record_claims.cpp

+ 28 - 2
Sunrise/src/state/record_claims/record_claims.cpp

@@ -245,6 +245,8 @@ namespace {
 struct NodeProgress {
     std::span<std::int32_t> values;
     std::size_t written;
+    /** One entry per value index, non-zero where a category's own bar reads. */
+    std::span<const char> categories;
 };
 
 /** True when this account flag bank row is held. The caller owns the claim lock. */
@@ -269,7 +271,25 @@ std::size_t apply_node_progress(std::span<std::int32_t> objectiveValues) noexcep
         && build_data::nodes::load_and_publish()) {
         nodesPublished.store(true, std::memory_order_relaxed);
     }
-    NodeProgress progress{objectiveValues, 0};
+    // Every category's own index, so the walk can tell a free slot above a category from the next
+    // category along. Without it, writing the slot above drives whichever book owns that slot.
+    std::vector<char> categoryFlags(objectiveValues.size(), 0);
+    {
+        namespace nodes = build_data::nodes;
+        std::vector<nodes::Definition> all(nodes::kDefinitionCapacity);
+        std::size_t total = 0;
+        if (nodes::snapshot(std::span<nodes::Definition>{all}, total)) {
+            for (std::size_t row = 0; row < total; ++row) {
+                const std::uint16_t index = all[row].valueIndex;
+                if (index != nodes::kUnavailableValueIndex
+                    && static_cast<std::size_t>(index) < categoryFlags.size()) {
+                    categoryFlags[index] = 1;
+                }
+            }
+        }
+    }
+
+    NodeProgress progress{objectiveValues, 0, std::span<const char>{categoryFlags}};
     // The claim lock is taken first and the node lock inside the walk. Nothing takes them the other
     // way round, so the order cannot close a cycle.
     const std::lock_guard<std::mutex> guard(g_lock);
@@ -297,7 +317,13 @@ std::size_t apply_node_progress(std::span<std::int32_t> objectiveValues) noexcep
                     ++claimedChapters;
                 }
             }
-            if (static_cast<std::size_t>(node.parentValueIndex) < state->values.size()) {
+            // The parent's bar sits one slot above its category on the books where that slot is
+            // free, confirmed by marker on two of them. Categories are allocated contiguously
+            // though, so for many books the slot above is the next book's category, and writing it
+            // drove the wrong book's bar. Those are skipped: a bar left at zero is wrong, a bar
+            // carrying another book's count is worse. Where those parents read is not yet known.
+            if (static_cast<std::size_t>(node.parentValueIndex) < state->values.size()
+                && state->categories[node.parentValueIndex] == 0) {
                 state->values[node.parentValueIndex] = claimedChapters;
                 ++state->written;
             }