#include "tools/replay/timeline.h" #include #include #include "cereal/gen/cpp/log.capnp.h" Timeline::~Timeline() { should_exit_.store(true); if (thread_.joinable()) { thread_.join(); } } void Timeline::initialize(const Route &route, uint64_t route_start_ts, bool local_cache, std::function)> callback) { thread_ = std::thread(&Timeline::buildTimeline, this, route, route_start_ts, local_cache, callback); } std::optional Timeline::find(double cur_ts, FindFlag flag) const { for (const auto &entry : *getEntries()) { if (entry.type == TimelineType::Engaged) { if (flag == FindFlag::nextEngagement && entry.start_time > cur_ts) { return entry.start_time; } else if (flag == FindFlag::nextDisEngagement && entry.end_time > cur_ts) { return entry.end_time; } } else if (entry.start_time > cur_ts) { if ((flag == FindFlag::nextUserFlag && entry.type == TimelineType::UserFlag) || (flag == FindFlag::nextInfo && entry.type == TimelineType::AlertInfo) || (flag == FindFlag::nextWarning && entry.type == TimelineType::AlertWarning) || (flag == FindFlag::nextCritical && entry.type == TimelineType::AlertCritical)) { return entry.start_time; } } } return std::nullopt; } std::optional Timeline::findAlertAtTime(double target_time) const { for (const auto &entry : *getEntries()) { if (entry.start_time > target_time) break; if (entry.end_time >= target_time && entry.type >= TimelineType::AlertInfo) { return entry; } } return std::nullopt; } void Timeline::buildTimeline(const Route &route, uint64_t route_start_ts, bool local_cache, std::function)> callback) { std::optional current_engaged_idx, current_alert_idx; for (const auto &segment : route.segments()) { if (should_exit_) break; auto log = std::make_shared(); if (!log->load(segment.second.qlog, &should_exit_, local_cache, 0, 3) || log->events.empty()) { continue; // Skip if log loading fails or no events } for (const Event &e : log->events) { double seconds = (e.mono_time - route_start_ts) / 1e9; if (e.which == cereal::Event::Which::SELFDRIVE_STATE) { capnp::FlatArrayMessageReader reader(e.data); auto cs = reader.getRoot().getSelfdriveState(); updateEngagementStatus(cs, current_engaged_idx, seconds); updateAlertStatus(cs, current_alert_idx, seconds); } else if (e.which == cereal::Event::Which::USER_FLAG) { staging_entries_.emplace_back(Entry{seconds, seconds, TimelineType::UserFlag}); } } // Sort and finalize the timeline entries auto entries = std::make_shared>(staging_entries_); std::sort(entries->begin(), entries->end(), [](auto &a, auto &b) { return a.start_time < b.start_time; }); std::atomic_store(&timeline_entries_, std::move(entries)); callback(log); // Notify the callback once the log is processed } } void Timeline::updateEngagementStatus(const cereal::SelfdriveState::Reader &cs, std::optional &idx, double seconds) { if (idx) staging_entries_[*idx].end_time = seconds; if (cs.getEnabled()) { if (!idx) { idx = staging_entries_.size(); staging_entries_.emplace_back(Entry{seconds, seconds, TimelineType::Engaged}); } } else { idx.reset(); } } void Timeline::updateAlertStatus(const cereal::SelfdriveState::Reader &cs, std::optional &idx, double seconds) { static auto alert_types = std::array{TimelineType::AlertInfo, TimelineType::AlertWarning, TimelineType::AlertCritical}; Entry *entry = idx ? &staging_entries_[*idx] : nullptr; if (entry) entry->end_time = seconds; if (cs.getAlertSize() != cereal::SelfdriveState::AlertSize::NONE) { auto type = alert_types[(int)cs.getAlertStatus()]; std::string text1 = cs.getAlertText1().cStr(); std::string text2 = cs.getAlertText2().cStr(); if (!entry || entry->type != type || entry->text1 != text1 || entry->text2 != text2) { idx = staging_entries_.size(); staging_entries_.emplace_back(Entry{seconds, seconds, type, text1, text2}); // Start a new entry } } else { idx.reset(); } }