Sfoglia il codice sorgente

Keep the Dialogue page to the cues it draws

The page scanned every dialogue text row of the pack for each cue of an expanded
sensor, and again from the start for each candidate it deduplicated. The text
section now holds every take of every mission, so an expanded sensor cost tens of
millions of row visits a frame and the game fell to 12 fps with the page open.

Rows are written in slot, cue, line then take order; the relation check now
requires it, and cue_dialogue_texts returns one cue's lines by binary search, as
slot_dialogue_cues does for the cue rows. The two takes of a line are still shown
once, compared inside that range only. The cue availability check reads the cue
only against the slot's cue count, so it is resolved once per sensor instead of
once per cue.
Fozkais 1 mese fa
parent
commit
d19ec1bae2

+ 1 - 0
Sunrise/src/client/content/activity/activity_sdk_lua_artifacts.h

@@ -79,6 +79,7 @@ struct Source final {
     std::span<const ScenarioWorldSource> scenarioWorldSources{};
     std::span<const format::CombatObjectiveGroup> combatObjectiveGroups{};
     std::span<const format::ActorAbility> actorAbilities{};
+    std::span<const format::AuthoredSceneEventKey> authoredSceneEventKeys{};
     std::span<const format::DialogueCue> dialogueCues{};
 };
 

+ 1 - 1
Sunrise/src/client/content/activity/activity_sdk_lua_catalog_contract.cpp

@@ -20,7 +20,7 @@ struct RowSpec final {
 };
 
 /** Every StringRef field declared by the row table below. */
-constexpr std::size_t kDecodedStringFieldCount = 55;
+constexpr std::size_t kDecodedStringFieldCount = 56;
 /** Section index of the activity-owned binding-tag rows, which have no direct collection. */
 constexpr std::uint32_t kBindingTagSectionIndex = 24;
 /** Section index of the binding-locator rows, projected both directly and per activity. */

+ 1 - 0
Sunrise/src/client/content/activity/activity_sdk_lua_contract.cpp

@@ -49,6 +49,7 @@ bool render_contract_files(const Source& source, Bundle& output) noexcept {
                  {"actor_sequence_tables", number(source.actorSequenceTables.size())},
                  {"actor_sequence_entries", number(source.actorSequenceEntries.size())},
                  {"actor_sequence_bindings", number(source.actorSequenceBindings.size())},
+                 {"authored_scene_event_keys", number(source.authoredSceneEventKeys.size())},
                  {"dialogue_cues", number(source.dialogueCues.size())},
              })},
         });

+ 1 - 0
Sunrise/src/client/content/activity/activity_sdk_lua_missions.cpp

@@ -14,6 +14,7 @@
 
 #include "../../../middleware/bap/activity_message/auth_schema_catalog.h"
 #include "../../../state/activity_sdk/runtime.h"
+#include "../../../state/activity_sdk/scene_triggers.h"
 #include "activity_sdk_lua_missions_internal.h"
 
 namespace sunrise::client::content::activity::sdk_generation::lua_artifacts::internal {

+ 1 - 0
Sunrise/src/client/content/activity/activity_sdk_lua_publish.cpp

@@ -406,6 +406,7 @@ bool is_current(const wchar_t* sdkDirectory, const state::activity_sdk::Catalog&
         source.actorSequenceTables = catalog.actor_sequence_tables();
         source.actorSequenceEntries = catalog.actor_sequence_entries();
         source.actorSequenceBindings = catalog.actor_sequence_bindings();
+        source.authoredSceneEventKeys = catalog.authored_scene_event_keys();
         source.dialogueCues = catalog.dialogue_cues();
         Bundle expected{};
         if (!internal::render_contract_files(source, expected)) {

+ 1 - 0
Sunrise/src/client/content/activity/activity_sdk_native_pack_pipeline.cpp

@@ -214,6 +214,7 @@ lua_source(const state::activity_sdk::identity::Expected& identity,
             worldSources,
             storage.combatObjectiveGroups,
             storage.actorAbilities,
+            storage.authoredSceneEventKeys,
             storage.dialogueCues};
 }
 

+ 9 - 11
Sunrise/src/server/ui/activity_host/activity_host_sdk_mission_view.cpp

@@ -462,12 +462,14 @@ void draw_dialogues(const sdk::BoundView& view, const mission::Snapshot& snapsho
                                     static_cast<unsigned>(occurrenceRow),
                                     static_cast<unsigned>(slot.slotIndex));
                 const auto cueRows = sdk::slot_dialogue_cues(catalog, slot);
+                // The check reads the cue only against the slot's cue count, which every cue
+                // drawn here satisfies, so one resolution per sensor answers for all of them.
+                const mission::SceneStatus available =
+                    mission::dialogue_cue_availability(view, occurrenceRow, slotRow, 0);
                 for (std::uint32_t cueRow = 0; cueRow < slot.reserved; ++cueRow) {
                     // The cue index is a 16-bit wire field, so the row is narrowed once here.
                     const auto cue = static_cast<std::uint16_t>(cueRow);
                     ImGui::PushID(static_cast<int>(cueRow));
-                    const mission::SceneStatus available =
-                        mission::dialogue_cue_availability(view, occurrenceRow, slotRow, cue);
                     ImGui::BeginDisabled(available != mission::SceneStatus::ready);
                     if (ImGui::Button("Play cue")) {
                         g_dialogueActionOccurrence = occurrenceRow;
@@ -491,21 +493,17 @@ void draw_dialogues(const sdk::BoundView& view, const mission::Snapshot& snapsho
                     }
                     ImGui::Indent();
                     bool hasCandidate = false;
-                    const auto dialogueRows = catalog.dialogue_cue_texts();
+                    const auto dialogueRows = sdk::cue_dialogue_texts(catalog, slot, cueRow);
                     for (std::size_t rowIndex = 0; rowIndex < dialogueRows.size(); ++rowIndex) {
-                        const sdk::format::DialogueCueText& row = dialogueRows[rowIndex];
-                        if (row.slotIndex != slotRow || row.cueIndex != cue) {
-                            continue;
-                        }
-                        const std::string_view candidate = catalog.string(row.text);
+                        const std::string_view candidate =
+                            catalog.string(dialogueRows[rowIndex].text);
                         if (candidate.empty()) {
                             continue;
                         }
+                        // The two takes of a line usually share their text; show it once.
                         bool duplicate = false;
                         for (std::size_t previous = 0; previous < rowIndex; ++previous) {
-                            const sdk::format::DialogueCueText& prior = dialogueRows[previous];
-                            if (prior.slotIndex == slotRow && prior.cueIndex == cue
-                                && catalog.string(prior.text) == candidate) {
+                            if (catalog.string(dialogueRows[previous].text) == candidate) {
                                 duplicate = true;
                                 break;
                             }

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

@@ -4,6 +4,7 @@
 #include <atomic>
 #include <cmath>
 #include <limits>
+#include <utility>
 
 #include "internal.h"
 #include "runtime.h"
@@ -795,6 +796,28 @@ std::span<const format::DialogueCue> slot_dialogue_cues(const Catalog& catalog,
                           static_cast<std::size_t>(last - first));
 }
 
+/** Relies on slot, cue, line then take ordering to return one contiguous zero-copy line range. */
+std::span<const format::DialogueCueText> cue_dialogue_texts(const Catalog& catalog,
+                                                            const format::Slot& slot,
+                                                            std::uint32_t cueIndex) noexcept {
+    const auto slots = catalog.slots();
+    if (!owns(slots, slot)) {
+        return {};
+    }
+    const auto values = catalog.dialogue_cue_texts();
+    const auto key = std::make_pair(static_cast<std::uint32_t>(&slot - slots.data()), cueIndex);
+    const auto first =
+        std::lower_bound(values.begin(), values.end(), key, [](const auto& row, const auto& cue) {
+            return std::make_pair(row.slotIndex, row.cueIndex) < cue;
+        });
+    const auto last =
+        std::upper_bound(first, values.end(), key, [](const auto& cue, const auto& row) {
+            return cue < std::make_pair(row.slotIndex, row.cueIndex);
+        });
+    return values.subspan(static_cast<std::size_t>(first - values.begin()),
+                          static_cast<std::size_t>(last - first));
+}
+
 std::uint32_t authored_milliseconds(float seconds) noexcept {
     const double value = std::round(static_cast<double>(seconds) * 1000.0);
     if (!(value > 0.0)) {

+ 21 - 9
Sunrise/src/state/activity_sdk/activity_sdk_relation_validation.cpp

@@ -198,11 +198,20 @@ constexpr std::uint32_t kAbilityTargetComponentClass = 0x80807D9BU;
         }
         priorGroup = &row;
     }
+    const format::DialogueCueText* priorText = nullptr;
     for (const format::DialogueCueText& row : catalog.dialogue_cue_texts()) {
+        // Slot, cue, line then take order, without repeats, backs the per-cue range lookup.
         if (row.slotIndex >= slots.size() || row.containerTag == 0 || row.stringHash == 0
-            || row.takeIndex >= format::kDialogueTakeCount) {
+            || row.takeIndex >= format::kDialogueTakeCount
+            || (priorText != nullptr
+                && std::tie(row.slotIndex, row.cueIndex, row.lineIndex, row.takeIndex)
+                       <= std::tie(priorText->slotIndex,
+                                   priorText->cueIndex,
+                                   priorText->lineIndex,
+                                   priorText->takeIndex))) {
             return false;
         }
+        priorText = &row;
         const auto cues = slot_dialogue_cues(catalog, slots[row.slotIndex]);
         if (row.cueIndex >= cues.size()) {
             return false;
@@ -681,14 +690,17 @@ bool relations(const Catalog& catalog) {
         bool (*run)(const Catalog&) noexcept;
     };
     // Every relation check a catalog must pass, named so a refusal reports which one failed.
-    static constexpr std::array<Check, 8> kChecks{{{"task_targets", &task_targets},
-                                                   {"dialogue_cues", &dialogue_cues},
-                                                   {"authored_text", &authored_text},
-                                                   {"behavior_edges", &behavior_edges},
-                                                   {"actor_semantics", &actor_semantics},
-                                                   {"runtime_semantics", &runtime_semantics},
-                                                   {"sobject_semantics", &sobject_semantics},
-                                                   {"entity_types", &entity_types}}};
+    static constexpr std::array<Check, 10> kChecks{
+        {{"authored_scene_event_keys", &authored_scene_event_keys},
+         {"squad_profiles", &squad_profiles},
+         {"task_targets", &task_targets},
+         {"dialogue_cues", &dialogue_cues},
+         {"authored_text", &authored_text},
+         {"behavior_edges", &behavior_edges},
+         {"actor_semantics", &actor_semantics},
+         {"runtime_semantics", &runtime_semantics},
+         {"sobject_semantics", &sobject_semantics},
+         {"entity_types", &entity_types}}};
     for (const Check& check : kChecks) {
         if (!check.run(catalog)) {
             remember(check.name);

+ 1 - 1
Sunrise/src/state/activity_sdk/format.h

@@ -490,7 +490,7 @@ inline constexpr std::size_t kAuthoredSceneSquadEdgeSize = 40;
 inline constexpr std::size_t kTaskTargetSize = 44;
 inline constexpr std::size_t kDialogueCueSize = 40;
 inline constexpr std::size_t kDialogueCueTextSize = 52;
-inline constexpr std::size_t kDirectiveElementSize = 56;
+inline constexpr std::size_t kDirectiveElementSize = 76;
 inline constexpr std::size_t kBehaviorProgramSize = 28;
 inline constexpr std::size_t kBehaviorInputSize = 36;
 inline constexpr std::size_t kBehaviorChannelWriteSize = 16;

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

@@ -512,6 +512,9 @@ squad_anchors(const Catalog& catalog, const format::Squad& squad) noexcept;
 /** Returns every cue of one type-53 slot, indexed by cue. */
 [[nodiscard]] std::span<const format::DialogueCue>
 slot_dialogue_cues(const Catalog& catalog, const format::Slot& slot) noexcept;
+/** Returns every take of every line of one cue of a type-53 slot, in play order. */
+[[nodiscard]] std::span<const format::DialogueCueText> cue_dialogue_texts(
+    const Catalog& catalog, const format::Slot& slot, std::uint32_t cueIndex) noexcept;
 /** Rounds authored seconds to the nearest millisecond, saturating at 32 bits. */
 [[nodiscard]] std::uint32_t authored_milliseconds(float seconds) noexcept;
 /** Returns all exact authored resources retained for one type-43 slot. */