|
@@ -32,6 +32,25 @@ bool resource_id(const topology::Snapshot& topology,
|
|
|
static_cast<unsigned>(slot.slotType));
|
|
static_cast<unsigned>(slot.slotType));
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/** Formats one event-key ID from the resource, its graph, the scene slot and the gate. */
|
|
|
|
|
+bool event_key_id(const topology::Snapshot& topology,
|
|
|
|
|
+ const squad::DescriptorFact& descriptor,
|
|
|
|
|
+ std::uint32_t graphTag,
|
|
|
|
|
+ std::uint32_t gateOffset,
|
|
|
|
|
+ Text& output) noexcept {
|
|
|
|
|
+ if (descriptor.slotIndex >= topology.slots.size()) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ const topology::Slot& slot = topology.slots[descriptor.slotIndex];
|
|
|
|
|
+ return format_text(output,
|
|
|
|
|
+ "authored-scene-event-key/%08x/%08x/%04x/%04x/%08x",
|
|
|
|
|
+ static_cast<unsigned>(descriptor.configTag),
|
|
|
|
|
+ static_cast<unsigned>(graphTag),
|
|
|
|
|
+ static_cast<unsigned>(slot.slotIndex),
|
|
|
|
|
+ static_cast<unsigned>(slot.slotType),
|
|
|
|
|
+ static_cast<unsigned>(gateOffset));
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
/** Formats one scene-to-squad edge ID from its descriptor tuple and the squad slot it names. */
|
|
/** Formats one scene-to-squad edge ID from its descriptor tuple and the squad slot it names. */
|
|
|
bool edge_id(const topology::Snapshot& topology,
|
|
bool edge_id(const topology::Snapshot& topology,
|
|
|
const squad::DescriptorFact& descriptor,
|
|
const squad::DescriptorFact& descriptor,
|
|
@@ -219,6 +238,59 @@ void log_scene_resource(const squad::DescriptorFact& descriptor,
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/** Logs a scene graph that yielded no event keys, and why. */
|
|
|
|
|
+void log_scene_graph(const squad::DescriptorFact& descriptor,
|
|
|
|
|
+ std::uint32_t graphTag,
|
|
|
|
|
+ const char* result,
|
|
|
|
|
+ core::log::Level level) noexcept {
|
|
|
|
|
+ std::array<char, 176> line{};
|
|
|
|
|
+ const int written = std::snprintf(line.data(),
|
|
|
|
|
+ line.size(),
|
|
|
|
|
+ "ev=activity_sdk_scene_graph result=%s config=0x%08X "
|
|
|
|
|
+ "slot_row=%u graph=0x%08X",
|
|
|
|
|
+ result,
|
|
|
|
|
+ static_cast<unsigned>(descriptor.configTag),
|
|
|
|
|
+ static_cast<unsigned>(descriptor.slotIndex),
|
|
|
|
|
+ static_cast<unsigned>(graphTag));
|
|
|
|
|
+ if (written > 0) {
|
|
|
|
|
+ core::log::write(
|
|
|
|
|
+ core::log::Channel::client,
|
|
|
|
|
+ level,
|
|
|
|
|
+ {line.data(), (std::min)(static_cast<std::size_t>(written), line.size() - 1U)});
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Finds the one typed array of gate elements in a scene graph.
|
|
|
|
|
+ * @param blob Graph bytes.
|
|
|
|
|
+ * @param first Receives the offset of the first element.
|
|
|
|
|
+ * @param count Receives the element count.
|
|
|
|
|
+ * @return False when the graph holds no such array, or more than one.
|
|
|
|
|
+ */
|
|
|
|
|
+[[nodiscard]] bool find_gate_array(std::span<const std::byte> blob,
|
|
|
|
|
+ std::size_t& first,
|
|
|
|
|
+ std::uint64_t& count) noexcept {
|
|
|
|
|
+ bool found = false;
|
|
|
|
|
+ for (std::size_t offset = 0; offset + 16 <= blob.size(); offset += 4) {
|
|
|
|
|
+ std::uint32_t marker = 0;
|
|
|
|
|
+ std::uint32_t elementClass = 0;
|
|
|
|
|
+ std::uint64_t declared = 0;
|
|
|
|
|
+ if (!read_value(blob, offset, marker) || marker != format::kPackageArrayMarker
|
|
|
|
|
+ || !read_value(blob, offset + 4, declared)
|
|
|
|
|
+ || !read_value(blob, offset + 12, elementClass)
|
|
|
|
|
+ || elementClass != format::kAuthoredSceneGateArrayClass) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (found) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ found = true;
|
|
|
|
|
+ first = offset + 20;
|
|
|
|
|
+ count = declared;
|
|
|
|
|
+ }
|
|
|
|
|
+ return found;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
/** Reads one tag once and retains the physical class beside its bytes. */
|
|
/** Reads one tag once and retains the physical class beside its bytes. */
|
|
|
[[nodiscard]] bool package_row(squad::TagReader reader,
|
|
[[nodiscard]] bool package_row(squad::TagReader reader,
|
|
|
void* readerContext,
|
|
void* readerContext,
|
|
@@ -315,6 +387,75 @@ void log_scene_resource(const squad::DescriptorFact& descriptor,
|
|
|
} // namespace
|
|
} // namespace
|
|
|
|
|
|
|
|
/** Builds both authored-scene sections from complete topology and package facts. */
|
|
/** Builds both authored-scene sections from complete topology and package facts. */
|
|
|
|
|
+/**
|
|
|
|
|
+ * Follows a scene resource to its event graph and appends one row per gate.
|
|
|
|
|
+ * A graph that cannot be read or holds no gate array leaves the scene without keys and is
|
|
|
|
|
+ * logged; a gate that is not a gate element, or a count past the capacity, is a misread graph
|
|
|
|
|
+ * and is logged the same way. Only a topology inconsistency fails the build.
|
|
|
|
|
+ */
|
|
|
|
|
+[[nodiscard]] bool collect_event_keys(const topology::Snapshot& topology,
|
|
|
|
|
+ const squad::DescriptorFact& descriptor,
|
|
|
|
|
+ squad::TagReader reader,
|
|
|
|
|
+ void* readerContext,
|
|
|
|
|
+ PackageCache& cache,
|
|
|
|
|
+ std::uint32_t resourceTag,
|
|
|
|
|
+ std::span<const std::byte> resource,
|
|
|
|
|
+ std::vector<EventKey>& output) {
|
|
|
|
|
+ std::uint32_t graphTag = 0;
|
|
|
|
|
+ if (!read_value(resource, format::kAuthoredSceneGraphRelativeOffset, graphTag) || graphTag == 0
|
|
|
|
|
+ || graphTag == format::kAbsentIndex) {
|
|
|
|
|
+ log_scene_graph(descriptor, graphTag, "unreferenced", core::log::Level::warn);
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ const PackageRow* graph = nullptr;
|
|
|
|
|
+ if (!package_row(reader, readerContext, graphTag, cache, graph) || graph == nullptr) {
|
|
|
|
|
+ log_scene_graph(descriptor, graphTag, "unreadable", core::log::Level::warn);
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ const auto blob = std::span(graph->bytes);
|
|
|
|
|
+ std::size_t first = 0;
|
|
|
|
|
+ std::uint64_t count = 0;
|
|
|
|
|
+ if (!find_gate_array(blob, first, count)) {
|
|
|
|
|
+ log_scene_graph(descriptor, graphTag, "gate_array", core::log::Level::warn);
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (count > format::kAuthoredSceneGateCapacity) {
|
|
|
|
|
+ log_scene_graph(descriptor, graphTag, "capacity", core::log::Level::warn);
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ for (std::uint64_t gate = 0; gate < count; ++gate) {
|
|
|
|
|
+ const std::size_t element =
|
|
|
|
|
+ first + static_cast<std::size_t>(gate) * format::kAuthoredSceneGateSize;
|
|
|
|
|
+ std::uint32_t owner = 0;
|
|
|
|
|
+ std::uint32_t elementClass = 0;
|
|
|
|
|
+ std::uint32_t key = 0;
|
|
|
|
|
+ std::int32_t ordinal = 0;
|
|
|
|
|
+ if (!read_value(blob, element, owner)
|
|
|
|
|
+ || !read_value(blob, element + format::kAuthoredSceneGateClassOffset, elementClass)
|
|
|
|
|
+ || !read_value(blob, element + format::kAuthoredSceneGateKeyOffset, key)
|
|
|
|
|
+ || !read_value(blob, element + format::kAuthoredSceneGateOrdinalOffset, ordinal)
|
|
|
|
|
+ || owner != graphTag || elementClass != format::kAuthoredSceneGateClass || key == 0
|
|
|
|
|
+ || key == format::kAbsentIndex) {
|
|
|
|
|
+ log_scene_graph(descriptor, graphTag, "gate", core::log::Level::warn);
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ EventKey row{};
|
|
|
|
|
+ if (!event_key_id(
|
|
|
|
|
+ topology, descriptor, graphTag, static_cast<std::uint32_t>(element), row.id)) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ row.slotIndex = descriptor.slotIndex;
|
|
|
|
|
+ row.resourceTag = resourceTag;
|
|
|
|
|
+ row.graphTag = graphTag;
|
|
|
|
|
+ row.gateOffset = static_cast<std::uint32_t>(element);
|
|
|
|
|
+ row.ordinal = ordinal;
|
|
|
|
|
+ row.key = key;
|
|
|
|
|
+ row.flags = format::kAuthoredSceneEventKeyExact;
|
|
|
|
|
+ output.push_back(row);
|
|
|
|
|
+ }
|
|
|
|
|
+ return true;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
bool build(const topology::Snapshot& topology,
|
|
bool build(const topology::Snapshot& topology,
|
|
|
const Facts& facts,
|
|
const Facts& facts,
|
|
|
squad::TagReader reader,
|
|
squad::TagReader reader,
|
|
@@ -525,6 +666,16 @@ bool build(const topology::Snapshot& topology,
|
|
|
row.flags = format::kAuthoredSceneResourceExact;
|
|
row.flags = format::kAuthoredSceneResourceExact;
|
|
|
pending.resources.push_back(row);
|
|
pending.resources.push_back(row);
|
|
|
}
|
|
}
|
|
|
|
|
+ if (!collect_event_keys(topology,
|
|
|
|
|
+ descriptor,
|
|
|
|
|
+ reader,
|
|
|
|
|
+ readerContext,
|
|
|
|
|
+ cache,
|
|
|
|
|
+ resourceTag,
|
|
|
|
|
+ std::span(resourcePackage->bytes),
|
|
|
|
|
+ pending.eventKeys)) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -635,6 +786,11 @@ bool build(const topology::Snapshot& topology,
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
std::sort(pending.resources.begin(), pending.resources.end(), resource_less);
|
|
std::sort(pending.resources.begin(), pending.resources.end(), resource_less);
|
|
|
|
|
+ std::sort(pending.eventKeys.begin(),
|
|
|
|
|
+ pending.eventKeys.end(),
|
|
|
|
|
+ [](const EventKey& left, const EventKey& right) {
|
|
|
|
|
+ return event_key_natural(left) < event_key_natural(right);
|
|
|
|
|
+ });
|
|
|
std::sort(pending.squadEdges.begin(), pending.squadEdges.end(), edge_less);
|
|
std::sort(pending.squadEdges.begin(), pending.squadEdges.end(), edge_less);
|
|
|
std::sort(pending.taskTargets.begin(), pending.taskTargets.end(), task_less);
|
|
std::sort(pending.taskTargets.begin(), pending.taskTargets.end(), task_less);
|
|
|
pending.resources.erase(std::unique(pending.resources.begin(),
|
|
pending.resources.erase(std::unique(pending.resources.begin(),
|
|
@@ -644,6 +800,13 @@ bool build(const topology::Snapshot& topology,
|
|
|
== resource_natural(right);
|
|
== resource_natural(right);
|
|
|
}),
|
|
}),
|
|
|
pending.resources.end());
|
|
pending.resources.end());
|
|
|
|
|
+ pending.eventKeys.erase(std::unique(pending.eventKeys.begin(),
|
|
|
|
|
+ pending.eventKeys.end(),
|
|
|
|
|
+ [](const auto& left, const auto& right) {
|
|
|
|
|
+ return event_key_natural(left)
|
|
|
|
|
+ == event_key_natural(right);
|
|
|
|
|
+ }),
|
|
|
|
|
+ pending.eventKeys.end());
|
|
|
pending.squadEdges.erase(std::unique(pending.squadEdges.begin(),
|
|
pending.squadEdges.erase(std::unique(pending.squadEdges.begin(),
|
|
|
pending.squadEdges.end(),
|
|
pending.squadEdges.end(),
|
|
|
[](const auto& left, const auto& right) {
|
|
[](const auto& left, const auto& right) {
|