Browse Source

Resolve a Directives page slot once for all its elements

The page walked every directive element of the pack for each occurrence of the
state, and resolved the binding, the lease and the scriptable route again for
every element it kept; each of those resolutions then scanned the whole element
section once more to check the element's identity. With the counter elements
now in the section the page dropped the game to 12 fps.

Elements are written in slot, name hash then element order without repeats; the
relation check now requires it, and slot_directive_elements returns one slot's
elements by binary search. The page walks the type-68 slots of the state's
objects, resolves each slot once with directives_availability and draws its
elements from that range. prepare_directive checks the identity in the same
range. A description or progress label is drawn only when authored; the missing
marker was printed before.
Fozkais 1 week ago
parent
commit
74e2f5513c

+ 8 - 8
Sunrise/src/server/activity/activity_sdk_mission_prepare.cpp

@@ -624,14 +624,14 @@ namespace {
     if (status != SceneStatus::ready) {
     if (status != SceneStatus::ready) {
         return status;
         return status;
     }
     }
-    std::size_t matches = 0;
-    for (const sdk::format::DirectiveElement& row : view.catalog->directive_elements()) {
-        matches +=
-            row.slotIndex == slotRow && row.nameHash == nameHash && row.elementIndex == elementIndex
-                ? 1U
-                : 0U;
-    }
-    return matches == 1 ? SceneStatus::ready : SceneStatus::invalidSlot;
+    // Validation keeps one row per (slot, name hash, element), so the slot's range decides.
+    for (const sdk::format::DirectiveElement& row :
+         sdk::slot_directive_elements(*view.catalog, view.catalog->slots()[slotRow])) {
+        if (row.nameHash == nameHash && row.elementIndex == elementIndex) {
+            return SceneStatus::ready;
+        }
+    }
+    return SceneStatus::invalidSlot;
 }
 }
 
 
 /** Resolves one exact type-3 objective sensor without changing transport state. */
 /** Resolves one exact type-3 objective sensor without changing transport state. */

+ 15 - 0
Sunrise/src/server/activity/activity_sdk_mission_runtime.cpp

@@ -178,6 +178,21 @@ SceneStatus play_dialogue_cue(const sdk::BoundView& view,
     return SceneStatus::refused;
     return SceneStatus::refused;
 }
 }
 
 
+/** Checks one exact type-68 slot; the answer holds for every element the slot carries. */
+SceneStatus directives_availability(const sdk::BoundView& view,
+                                    std::uint32_t occurrenceRow,
+                                    std::uint32_t slotRow) noexcept {
+    PreparedScene prepared{};
+    return prepare_typed_behavior(view,
+                                  occurrenceRow,
+                                  slotRow,
+                                  sdk::format::kDirectiveSlotType,
+                                  sdk::format::kDirectiveComponentClass,
+                                  sdk::format::kDirectiveAuthSchema,
+                                  false,
+                                  prepared);
+}
+
 SceneStatus directive_availability(const sdk::BoundView& view,
 SceneStatus directive_availability(const sdk::BoundView& view,
                                    std::uint32_t occurrenceRow,
                                    std::uint32_t occurrenceRow,
                                    std::uint32_t slotRow,
                                    std::uint32_t slotRow,

+ 5 - 0
Sunrise/src/server/activity/activity_sdk_mission_runtime.h

@@ -103,6 +103,11 @@ select_state(const state::activity_sdk::BoundView& view,
                                             std::uint32_t slotRow,
                                             std::uint32_t slotRow,
                                             std::uint16_t cueIndex) noexcept;
                                             std::uint16_t cueIndex) noexcept;
 
 
+/** Checks one exact generated type-68 slot; the answer holds for every element it carries. */
+[[nodiscard]] SceneStatus directives_availability(const state::activity_sdk::BoundView& view,
+                                                  std::uint32_t occurrenceRow,
+                                                  std::uint32_t slotRow) noexcept;
+
 /** Checks one exact generated type-68 HUD directive element. */
 /** Checks one exact generated type-68 HUD directive element. */
 [[nodiscard]] SceneStatus directive_availability(const state::activity_sdk::BoundView& view,
 [[nodiscard]] SceneStatus directive_availability(const state::activity_sdk::BoundView& view,
                                                  std::uint32_t occurrenceRow,
                                                  std::uint32_t occurrenceRow,

+ 80 - 73
Sunrise/src/server/ui/activity_host/activity_host_sdk_mission_view.cpp

@@ -528,7 +528,6 @@ void draw_directives(const sdk::BoundView& view, const mission::Snapshot& snapsh
     const sdk::Catalog& catalog = *view.catalog;
     const sdk::Catalog& catalog = *view.catalog;
     const auto occurrences = catalog.occurrences();
     const auto occurrences = catalog.occurrences();
     const auto objects = catalog.objects();
     const auto objects = catalog.objects();
-    const auto slots = catalog.slots();
     const std::string_view query = search_text();
     const std::string_view query = search_text();
     std::size_t rows = 0;
     std::size_t rows = 0;
     for (std::uint32_t occurrenceRow = 0; occurrenceRow < occurrences.size(); ++occurrenceRow) {
     for (std::uint32_t occurrenceRow = 0; occurrenceRow < occurrences.size(); ++occurrenceRow) {
@@ -538,85 +537,93 @@ void draw_directives(const sdk::BoundView& view, const mission::Snapshot& snapsh
             || occurrence.objectIndex >= objects.size()) {
             || occurrence.objectIndex >= objects.size()) {
             continue;
             continue;
         }
         }
-        for (const sdk::format::DirectiveElement& directive : catalog.directive_elements()) {
-            if (directive.slotIndex >= slots.size()
-                || slots[directive.slotIndex].objectIndex != occurrence.objectIndex) {
+        for (const sdk::format::Slot& slot :
+             sdk::object_slots(catalog, objects[occurrence.objectIndex])) {
+            if (slot.slotType != sdk::format::kDirectiveSlotType) {
                 continue;
                 continue;
             }
             }
-            const std::string_view title = display_text(catalog, directive.title);
-            const std::string_view description = display_text(catalog, directive.description);
-            const std::string_view progress = display_text(catalog, directive.progress);
-            const std::string_view id = display_text(catalog, directive.id);
-            if (!query.empty() && !contains_folded(title, query)
-                && !contains_folded(description, query) && !contains_folded(progress, query)
-                && !contains_folded(id, query)) {
+            const auto elements = sdk::slot_directive_elements(catalog, slot);
+            if (elements.empty()) {
                 continue;
                 continue;
             }
             }
-            ++rows;
-            const mission::SceneStatus available =
-                mission::directive_availability(view,
-                                                occurrenceRow,
-                                                directive.slotIndex,
-                                                directive.nameHash,
-                                                directive.elementIndex);
-            ImGui::PushID(static_cast<int>(occurrenceRow));
-            ImGui::PushID(static_cast<int>(directive.slotIndex));
-            ImGui::PushID(static_cast<int>(directive.nameHash));
-            ImGui::PushID(directive.elementIndex);
-            ImGui::Text("%.*s", print_length(title), title.data());
-            if (!description.empty()) {
-                ImGui::TextWrapped("%.*s", print_length(description), description.data());
-            }
-            if (!progress.empty()) {
-                ImGui::TextWrapped("%.*s%s",
-                                   print_length(progress),
-                                   progress.data(),
-                                   (directive.flags & sdk::format::kDirectiveElementCounter) != 0
-                                       ? " (counter)"
-                                       : "");
+            const std::uint32_t slotRow = global_slot_row(catalog, slot);
+            if (slotRow == sdk::format::kAbsentIndex) {
+                continue;
             }
             }
-            ImGui::TextDisabled("hash %08X  element %d  slot %u",
-                                static_cast<unsigned>(directive.nameHash),
-                                directive.elementIndex,
-                                static_cast<unsigned>(directive.slotIndex));
-            ImGui::BeginDisabled(available != mission::SceneStatus::ready);
-            const auto apply = [&](const char* label, std::int8_t state, bool visible) {
-                if (ImGui::Button(label)) {
-                    g_directiveActionOccurrence = occurrenceRow;
-                    g_directiveActionSlot = directive.slotIndex;
-                    g_directiveActionHash = directive.nameHash;
-                    g_directiveActionElement = directive.elementIndex;
-                    g_directiveActionStatus = mission::set_directive(view,
-                                                                     occurrenceRow,
-                                                                     directive.slotIndex,
-                                                                     directive.nameHash,
-                                                                     directive.elementIndex,
-                                                                     state,
-                                                                     visible);
-                    g_hasDirectiveActionStatus = true;
+            // The slot resolves once; validation keeps every element row unique inside it.
+            const mission::SceneStatus available =
+                mission::directives_availability(view, occurrenceRow, slotRow);
+            for (const sdk::format::DirectiveElement& directive : elements) {
+                const std::string_view title = display_text(catalog, directive.title);
+                const std::string_view description = catalog.string(directive.description);
+                const std::string_view progress = catalog.string(directive.progress);
+                const std::string_view id = display_text(catalog, directive.id);
+                if (!query.empty() && !contains_folded(title, query)
+                    && !contains_folded(description, query) && !contains_folded(progress, query)
+                    && !contains_folded(id, query)) {
+                    continue;
                 }
                 }
-            };
-            apply("Show", 0, true);
-            ImGui::SameLine();
-            apply("Complete", 1, true);
-            ImGui::SameLine();
-            apply("Alternate exit", 2, true);
-            ImGui::SameLine();
-            apply("Hide", 0, false);
-            ImGui::EndDisabled();
-            ImGui::SameLine();
-            ImGui::TextDisabled("%s", mission::status_name(available));
-            if (g_hasDirectiveActionStatus && g_directiveActionOccurrence == occurrenceRow
-                && g_directiveActionSlot == directive.slotIndex
-                && g_directiveActionHash == directive.nameHash
-                && g_directiveActionElement == directive.elementIndex) {
-                ImGui::TextDisabled("last: %s", mission::status_name(g_directiveActionStatus));
+                ++rows;
+                ImGui::PushID(static_cast<int>(occurrenceRow));
+                ImGui::PushID(static_cast<int>(slotRow));
+                ImGui::PushID(static_cast<int>(directive.nameHash));
+                ImGui::PushID(directive.elementIndex);
+                ImGui::Text("%.*s", print_length(title), title.data());
+                if (!description.empty()) {
+                    ImGui::TextWrapped("%.*s", print_length(description), description.data());
+                }
+                if (!progress.empty()) {
+                    ImGui::TextWrapped("%.*s%s",
+                                       print_length(progress),
+                                       progress.data(),
+                                       (directive.flags & sdk::format::kDirectiveElementCounter)
+                                               != 0
+                                           ? " (counter)"
+                                           : "");
+                }
+                ImGui::TextDisabled("hash %08X  element %d  slot %u",
+                                    static_cast<unsigned>(directive.nameHash),
+                                    directive.elementIndex,
+                                    static_cast<unsigned>(slotRow));
+                ImGui::BeginDisabled(available != mission::SceneStatus::ready);
+                const auto apply = [&](const char* label, std::int8_t state, bool visible) {
+                    if (ImGui::Button(label)) {
+                        g_directiveActionOccurrence = occurrenceRow;
+                        g_directiveActionSlot = slotRow;
+                        g_directiveActionHash = directive.nameHash;
+                        g_directiveActionElement = directive.elementIndex;
+                        g_directiveActionStatus = mission::set_directive(view,
+                                                                         occurrenceRow,
+                                                                         slotRow,
+                                                                         directive.nameHash,
+                                                                         directive.elementIndex,
+                                                                         state,
+                                                                         visible);
+                        g_hasDirectiveActionStatus = true;
+                    }
+                };
+                apply("Show", 0, true);
+                ImGui::SameLine();
+                apply("Complete", 1, true);
+                ImGui::SameLine();
+                apply("Alternate exit", 2, true);
+                ImGui::SameLine();
+                apply("Hide", 0, false);
+                ImGui::EndDisabled();
+                ImGui::SameLine();
+                ImGui::TextDisabled("%s", mission::status_name(available));
+                if (g_hasDirectiveActionStatus && g_directiveActionOccurrence == occurrenceRow
+                    && g_directiveActionSlot == slotRow
+                    && g_directiveActionHash == directive.nameHash
+                    && g_directiveActionElement == directive.elementIndex) {
+                    ImGui::TextDisabled("last: %s", mission::status_name(g_directiveActionStatus));
+                }
+                ImGui::Separator();
+                ImGui::PopID();
+                ImGui::PopID();
+                ImGui::PopID();
+                ImGui::PopID();
             }
             }
-            ImGui::Separator();
-            ImGui::PopID();
-            ImGui::PopID();
-            ImGui::PopID();
-            ImGui::PopID();
         }
         }
     }
     }
     if (rows == 0) {
     if (rows == 0) {

+ 21 - 0
Sunrise/src/state/activity_sdk/activity_sdk_catalog.cpp

@@ -793,6 +793,27 @@ slot_authored_scene_resources(const Catalog& catalog, const format::Slot& slot)
                           static_cast<std::size_t>(last - first));
                           static_cast<std::size_t>(last - first));
 }
 }
 
 
+/** Relies on slot ordering to return one contiguous zero-copy element range. */
+std::span<const format::DirectiveElement>
+slot_directive_elements(const Catalog& catalog, const format::Slot& slot) noexcept {
+    const auto slots = catalog.slots();
+    if (!owns(slots, slot)) {
+        return {};
+    }
+    const auto values = catalog.directive_elements();
+    const std::uint32_t slotIndex = static_cast<std::uint32_t>(&slot - slots.data());
+    const auto first =
+        std::lower_bound(values.begin(), values.end(), slotIndex, [](const auto& row, auto index) {
+            return row.slotIndex < index;
+        });
+    const auto last =
+        std::upper_bound(first, values.end(), slotIndex, [](auto index, const auto& row) {
+            return index < row.slotIndex;
+        });
+    return values.subspan(static_cast<std::size_t>(first - values.begin()),
+                          static_cast<std::size_t>(last - first));
+}
+
 /** Relies on scene-slot ordering to return one contiguous zero-copy key range. */
 /** Relies on scene-slot ordering to return one contiguous zero-copy key range. */
 std::span<const format::AuthoredSceneEventKey>
 std::span<const format::AuthoredSceneEventKey>
 slot_authored_scene_event_keys(const Catalog& catalog, const format::Slot& slot) noexcept {
 slot_authored_scene_event_keys(const Catalog& catalog, const format::Slot& slot) noexcept {

+ 10 - 1
Sunrise/src/state/activity_sdk/activity_sdk_relation_validation.cpp

@@ -162,20 +162,29 @@ constexpr std::uint32_t kAbilityTargetComponentClass = 0x80807D9BU;
             return false;
             return false;
         }
         }
     }
     }
+    const format::DirectiveElement* priorElement = nullptr;
     for (const format::DirectiveElement& row : catalog.directive_elements()) {
     for (const format::DirectiveElement& row : catalog.directive_elements()) {
         // An authored field has both source tags; an absent one has neither.
         // An authored field has both source tags; an absent one has neither.
         const bool descriptionPaired =
         const bool descriptionPaired =
             (row.descriptionContainerTag == 0) == (row.descriptionStringHash == 0);
             (row.descriptionContainerTag == 0) == (row.descriptionStringHash == 0);
         const bool progressPaired =
         const bool progressPaired =
             (row.progressContainerTag == 0) == (row.progressStringHash == 0);
             (row.progressContainerTag == 0) == (row.progressStringHash == 0);
+        // Slot, name hash then element order, without repeats, backs the per-slot range lookup
+        // and makes every (slot, name hash, element) identity unique.
         if (row.slotIndex >= slots.size()
         if (row.slotIndex >= slots.size()
             || slots[row.slotIndex].slotType != format::kDirectiveSlotType || row.elementIndex < 0
             || slots[row.slotIndex].slotType != format::kDirectiveSlotType || row.elementIndex < 0
             || static_cast<std::uint32_t>(row.elementIndex) >= row.elementCount
             || static_cast<std::uint32_t>(row.elementIndex) >= row.elementCount
             || row.titleContainerTag == 0 || row.titleStringHash == 0 || !descriptionPaired
             || row.titleContainerTag == 0 || row.titleStringHash == 0 || !descriptionPaired
             || !progressPaired
             || !progressPaired
-            || (row.descriptionContainerTag == 0 && row.progressContainerTag == 0)) {
+            || (row.descriptionContainerTag == 0 && row.progressContainerTag == 0)
+            || (priorElement != nullptr
+                && std::tie(row.slotIndex, row.nameHash, row.elementIndex)
+                       <= std::tie(priorElement->slotIndex,
+                                   priorElement->nameHash,
+                                   priorElement->elementIndex))) {
             return false;
             return false;
         }
         }
+        priorElement = &row;
     }
     }
     return true;
     return true;
 }
 }

+ 3 - 0
Sunrise/src/state/activity_sdk/runtime.h

@@ -511,6 +511,9 @@ slot_authored_scene_resources(const Catalog& catalog, const format::Slot& slot)
 /** Returns all exact same-object squad edges owned by one type-43 slot. */
 /** Returns all exact same-object squad edges owned by one type-43 slot. */
 [[nodiscard]] std::span<const format::AuthoredSceneSquadEdge>
 [[nodiscard]] std::span<const format::AuthoredSceneSquadEdge>
 slot_authored_scene_squad_edges(const Catalog& catalog, const format::Slot& slot) noexcept;
 slot_authored_scene_squad_edges(const Catalog& catalog, const format::Slot& slot) noexcept;
+/** Returns every HUD element of one type-68 slot, in name hash then element order. */
+[[nodiscard]] std::span<const format::DirectiveElement>
+slot_directive_elements(const Catalog& catalog, const format::Slot& slot) noexcept;
 /** Returns the event keys of one type-43 slot's graph, in gate order. */
 /** Returns the event keys of one type-43 slot's graph, in gate order. */
 [[nodiscard]] std::span<const format::AuthoredSceneEventKey>
 [[nodiscard]] std::span<const format::AuthoredSceneEventKey>
 slot_authored_scene_event_keys(const Catalog& catalog, const format::Slot& slot) noexcept;
 slot_authored_scene_event_keys(const Catalog& catalog, const format::Slot& slot) noexcept;