diff --git a/tools/cabana/.gitignore b/tools/cabana/.gitignore index 0c21d5530d..88ffab2717 100644 --- a/tools/cabana/.gitignore +++ b/tools/cabana/.gitignore @@ -2,3 +2,4 @@ moc_* *.moc _cabana +settings diff --git a/tools/cabana/canmessages.cc b/tools/cabana/canmessages.cc index a55a045981..b717424ece 100644 --- a/tools/cabana/canmessages.cc +++ b/tools/cabana/canmessages.cc @@ -1,9 +1,11 @@ #include "tools/cabana/canmessages.h" #include +#include Q_DECLARE_METATYPE(std::vector); +Settings settings; CANMessages *can = nullptr; CANMessages::CANMessages(QObject *parent) : QObject(parent) { @@ -11,6 +13,7 @@ CANMessages::CANMessages(QObject *parent) : QObject(parent) { qRegisterMetaType>(); QObject::connect(this, &CANMessages::received, this, &CANMessages::process, Qt::QueuedConnection); + QObject::connect(&settings, &Settings::changed, this, &CANMessages::settingChanged); } CANMessages::~CANMessages() { @@ -24,6 +27,7 @@ static bool event_filter(const Event *e, void *opaque) { bool CANMessages::loadRoute(const QString &route, const QString &data_dir, bool use_qcam) { replay = new Replay(route, {"can", "roadEncodeIdx"}, {}, nullptr, use_qcam ? REPLAY_FLAG_QCAMERA : 0, data_dir, this); + replay->setSegmentCacheLimit(settings.cached_segment_limit); replay->installEventFilter(event_filter, this); QObject::connect(replay, &Replay::segmentsMerged, this, &CANMessages::segmentsMerged); if (replay->load()) { @@ -38,11 +42,11 @@ void CANMessages::process(QHash> *messages) { ++counters[it.key()]; auto &msgs = can_msgs[it.key()]; const auto &new_msgs = it.value(); - if (msgs.size() == CAN_MSG_LOG_SIZE || can_msgs[it.key()].size() == 0) { + if (new_msgs.size() == settings.can_msg_log_size || msgs.empty()) { msgs = std::move(new_msgs); } else { msgs.insert(msgs.begin(), std::make_move_iterator(new_msgs.begin()), std::make_move_iterator(new_msgs.end())); - while (msgs.size() >= CAN_MSG_LOG_SIZE) { + while (msgs.size() >= settings.can_msg_log_size) { msgs.pop_back(); } } @@ -71,7 +75,7 @@ bool CANMessages::eventFilter(const Event *event) { for (const auto &c : can_events) { QString id = QString("%1:%2").arg(c.getSrc()).arg(c.getAddress(), 1, 16); auto &list = (*received_msgs)[id]; - while (list.size() >= CAN_MSG_LOG_SIZE) { + while (list.size() >= settings.can_msg_log_size) { list.pop_back(); } CanData &data = list.emplace_front(); @@ -80,7 +84,7 @@ bool CANMessages::eventFilter(const Event *event) { data.dat.append((char *)c.getDat().begin(), c.getDat().size()); } - if (current_sec < prev_update_sec || (current_sec - prev_update_sec) > 1.0 / FPS) { + if (current_sec < prev_update_sec || (current_sec - prev_update_sec) > 1.0 / settings.fps) { prev_update_sec = current_sec; // use pointer to avoid data copy in queued connection. emit received(received_msgs.release()); @@ -121,3 +125,27 @@ void CANMessages::segmentsMerged() { void CANMessages::resetRange() { setRange(event_begin_sec, event_end_sec); } + +void CANMessages::settingChanged() { + replay->setSegmentCacheLimit(settings.cached_segment_limit); +} + +// Settings + +Settings::Settings() { + load(); +} + +void Settings::save() { + QSettings s("settings", QSettings::IniFormat); + s.setValue("fps", fps); + s.setValue("log_size", can_msg_log_size); + s.setValue("cached_segment", cached_segment_limit); +} + +void Settings::load() { + QSettings s("settings", QSettings::IniFormat); + fps = s.value("fps", 10).toInt(); + can_msg_log_size = s.value("log_size", 100).toInt(); + cached_segment_limit = s.value("cached_segment", 3.).toInt(); +} diff --git a/tools/cabana/canmessages.h b/tools/cabana/canmessages.h index a2af2a084c..3d33f801a7 100644 --- a/tools/cabana/canmessages.h +++ b/tools/cabana/canmessages.h @@ -8,8 +8,21 @@ #include "tools/replay/replay.h" -const int FPS = 10; -const int CAN_MSG_LOG_SIZE = 100; +class Settings : public QObject { + Q_OBJECT + +public: + Settings(); + void save(); + void load(); + + int fps = 10; + int can_msg_log_size = 100; + int cached_segment_limit = 3; + +signals: + void changed(); +}; struct CanData { double ts; @@ -57,6 +70,7 @@ public: protected: void process(QHash> *); void segmentsMerged(); + void settingChanged(); std::atomic current_sec = 0.; std::atomic seeking = false; @@ -82,3 +96,4 @@ inline const QString &getColor(int i) { // A global pointer referring to the unique CANMessages object extern CANMessages *can; +extern Settings settings; diff --git a/tools/cabana/mainwin.cc b/tools/cabana/mainwin.cc index 6fa24ea21d..c9d9c85141 100644 --- a/tools/cabana/mainwin.cc +++ b/tools/cabana/mainwin.cc @@ -1,7 +1,9 @@ #include "tools/cabana/mainwin.h" #include +#include #include +#include #include #include @@ -23,6 +25,9 @@ MainWindow::MainWindow() : QWidget() { right_container->setFixedWidth(640); r_layout = new QVBoxLayout(right_container); + QPushButton *settings_btn = new QPushButton("Settings"); + r_layout->addWidget(settings_btn, 0, Qt::AlignRight); + video_widget = new VideoWidget(this); r_layout->addWidget(video_widget, 0, Qt::AlignTop); @@ -34,6 +39,7 @@ MainWindow::MainWindow() : QWidget() { QObject::connect(messages_widget, &MessagesWidget::msgSelectionChanged, detail_widget, &DetailWidget::setMessage); QObject::connect(detail_widget, &DetailWidget::showChart, charts_widget, &ChartsWidget::addChart); QObject::connect(charts_widget, &ChartsWidget::dock, this, &MainWindow::dockCharts); + QObject::connect(settings_btn, &QPushButton::clicked, this, &MainWindow::setOption); } void MainWindow::dockCharts(bool dock) { @@ -59,3 +65,51 @@ void MainWindow::closeEvent(QCloseEvent *event) { floating_window->deleteLater(); QWidget::closeEvent(event); } + +void MainWindow::setOption() { + SettingsDlg dlg(this); + dlg.exec(); +} + +// SettingsDlg + +SettingsDlg::SettingsDlg(QWidget *parent) : QDialog(parent) { + setWindowTitle(tr("Settings")); + QVBoxLayout *main_layout = new QVBoxLayout(this); + QFormLayout *form_layout = new QFormLayout(); + + fps = new QSpinBox(this); + fps->setRange(10, 100); + fps->setSingleStep(10); + fps->setValue(settings.fps); + form_layout->addRow("FPS", fps); + + log_size = new QSpinBox(this); + log_size->setRange(50, 500); + log_size->setSingleStep(10); + log_size->setValue(settings.can_msg_log_size); + form_layout->addRow(tr("Log size"), log_size); + + cached_segment = new QSpinBox(this); + cached_segment->setRange(3, 60); + cached_segment->setSingleStep(1); + cached_segment->setValue(settings.cached_segment_limit); + form_layout->addRow(tr("Cached segments limit"), cached_segment); + + main_layout->addLayout(form_layout); + + auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + main_layout->addWidget(buttonBox); + + setFixedWidth(360); + connect(buttonBox, &QDialogButtonBox::accepted, this, &SettingsDlg::save); + connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); +} + +void SettingsDlg::save() { + settings.fps = fps->value(); + settings.can_msg_log_size = log_size->value(); + settings.cached_segment_limit = cached_segment->value(); + settings.save(); + accept(); +} diff --git a/tools/cabana/mainwin.h b/tools/cabana/mainwin.h index b0d7c273da..14c4b1dfa9 100644 --- a/tools/cabana/mainwin.h +++ b/tools/cabana/mainwin.h @@ -14,6 +14,7 @@ public: protected: void closeEvent(QCloseEvent *event) override; + void setOption(); VideoWidget *video_widget; MessagesWidget *messages_widget; @@ -22,3 +23,14 @@ protected: QWidget *floating_window = nullptr; QVBoxLayout *r_layout; }; + +class SettingsDlg : public QDialog { + Q_OBJECT + +public: + SettingsDlg(QWidget *parent); + void save(); + QSpinBox *fps; + QSpinBox *log_size ; + QSpinBox *cached_segment; +}; diff --git a/tools/replay/replay.cc b/tools/replay/replay.cc index 80e58b47a3..1337a4ef2c 100644 --- a/tools/replay/replay.cc +++ b/tools/replay/replay.cc @@ -211,7 +211,7 @@ void Replay::queueSegment() { SegmentMap::iterator cur, end; cur = end = segments_.lower_bound(std::min(current_segment_.load(), segments_.rbegin()->first)); - for (int i = 0; end != segments_.end() && i <= FORWARD_SEGS; ++i) { + for (int i = 0; end != segments_.end() && i <= segment_cache_limit + FORWARD_FETCH_SEGS; ++i) { ++end; } // load one segment at a time @@ -250,7 +250,7 @@ void Replay::mergeSegments(const SegmentMap::iterator &begin, const SegmentMap:: // merge 3 segments in sequence. std::vector segments_need_merge; size_t new_events_size = 0; - for (auto it = begin; it != end && it->second && it->second->isLoaded() && segments_need_merge.size() < 3; ++it) { + for (auto it = begin; it != end && it->second && it->second->isLoaded() && segments_need_merge.size() < segment_cache_limit; ++it) { segments_need_merge.push_back(it->first); new_events_size += it->second->log->events.size(); } diff --git a/tools/replay/replay.h b/tools/replay/replay.h index fbb36bd1ed..88c285125a 100644 --- a/tools/replay/replay.h +++ b/tools/replay/replay.h @@ -10,7 +10,7 @@ const QString DEMO_ROUTE = "4cf7a6ad03080c90|2021-09-29--13-46-36"; // one segment uses about 100M of memory -constexpr int FORWARD_SEGS = 5; +constexpr int FORWARD_FETCH_SEGS = 3; enum REPLAY_FLAGS { REPLAY_FLAG_NONE = 0x0000, @@ -57,6 +57,8 @@ public: filter_opaque = opaque; event_filter = filter; } + inline int segmentCacheLimit() const { return segment_cache_limit; } + inline void setSegmentCacheLimit(int n) { segment_cache_limit = std::max(3, n); } inline bool hasFlag(REPLAY_FLAGS flag) const { return flags_ & flag; } inline void addFlag(REPLAY_FLAGS flag) { flags_ |= flag; } inline void removeFlag(REPLAY_FLAGS flag) { flags_ &= ~flag; } @@ -131,4 +133,5 @@ protected: float speed_ = 1.0; replayEventFilter event_filter = nullptr; void *filter_opaque = nullptr; + int segment_cache_limit = 3; };