You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							111 lines
						
					
					
						
							4.3 KiB
						
					
					
				
			
		
		
	
	
							111 lines
						
					
					
						
							4.3 KiB
						
					
					
				| #include "tools/replay/timeline.h"
 | |
| 
 | |
| #include <algorithm>
 | |
| #include <array>
 | |
| 
 | |
| #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<void(std::shared_ptr<LogReader>)> callback) {
 | |
|   thread_ = std::thread(&Timeline::buildTimeline, this, route, route_start_ts, local_cache, callback);
 | |
| }
 | |
| 
 | |
| std::optional<uint64_t> 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::nextUserBookmark && entry.type == TimelineType::UserBookmark) ||
 | |
|           (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::Entry> 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<void(std::shared_ptr<LogReader>)> callback) {
 | |
|   std::optional<size_t> current_engaged_idx, current_alert_idx;
 | |
| 
 | |
|   for (const auto &segment : route.segments()) {
 | |
|     if (should_exit_) break;
 | |
| 
 | |
|     auto log = std::make_shared<LogReader>();
 | |
|     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<cereal::Event>().getSelfdriveState();
 | |
|         updateEngagementStatus(cs, current_engaged_idx, seconds);
 | |
|         updateAlertStatus(cs, current_alert_idx, seconds);
 | |
|       } else if (e.which == cereal::Event::Which::USER_BOOKMARK) {
 | |
|         staging_entries_.emplace_back(Entry{seconds, seconds, TimelineType::UserBookmark});
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     // Sort and finalize the timeline entries
 | |
|     auto entries = std::make_shared<std::vector<Entry>>(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<size_t> &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<size_t> &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();
 | |
|   }
 | |
| }
 | |
| 
 |