From d8da28f32d767b15e1c53af5c6f021e63acb27c3 Mon Sep 17 00:00:00 2001 From: Federico Lolli <federico.lolli@skywarder.eu> Date: Sun, 16 Mar 2025 21:11:42 +0100 Subject: [PATCH] CHECKPOINT --- src/mavlink/reflection.rs | 29 +++++- src/ui/panes/plot.rs | 156 ++++++++++------------------- src/ui/panes/plot/source_window.rs | 42 +++++--- 3 files changed, 108 insertions(+), 119 deletions(-) diff --git a/src/mavlink/reflection.rs b/src/mavlink/reflection.rs index c766536..e3ed70e 100644 --- a/src/mavlink/reflection.rs +++ b/src/mavlink/reflection.rs @@ -7,9 +7,10 @@ use std::collections::HashMap; use mavlink_bindgen::parser::{MavProfile, MavType}; +use serde::ser::SerializeStruct; use skyward_mavlink::mavlink::Message; -use crate::error::ErrInstrument; +use crate::{MAVLINK_PROFILE, error::ErrInstrument}; use super::MAVLINK_PROFILE_SERIALIZED; @@ -267,6 +268,32 @@ impl PartialEq for IndexedField { } } +impl serde::Serialize for IndexedField { + fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { + let mut state = serializer.serialize_struct("IndexedField", 3)?; + state.serialize_field("id", &self.id)?; + state.serialize_field("msg_id", &self.msg.id)?; + state.end() + } +} + +impl<'de> serde::Deserialize<'de> for IndexedField { + fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> { + #[derive(serde::Deserialize)] + struct IndexedFieldDe { + id: usize, + msg_id: u32, + } + + let de = IndexedFieldDe::deserialize(deserializer)?; + let field = de + .id + .to_mav_field(de.msg_id, &MAVLINK_PROFILE) + .map_err(|u| serde::de::Error::custom(format!("Invalid field: {}", u)))?; + Ok(field) + } +} + pub trait MessageLike { fn to_mav_message( &self, diff --git a/src/ui/panes/plot.rs b/src/ui/panes/plot.rs index 7159a19..c778a69 100644 --- a/src/ui/panes/plot.rs +++ b/src/ui/panes/plot.rs @@ -19,6 +19,7 @@ use source_window::sources_window; use std::{ hash::{DefaultHasher, Hash, Hasher}, iter::zip, + time::{Duration, Instant}, }; #[derive(Clone, Default, Debug, Serialize, Deserialize)] @@ -26,7 +27,7 @@ pub struct Plot2DPane { settings: PlotSettings, // UI settings #[serde(skip)] - line_data: Vec<Vec<PlotPoint>>, + line_data: Vec<TimeAwarePlotPoints>, #[serde(skip)] state_valid: bool, #[serde(skip)] @@ -149,7 +150,9 @@ impl PaneBehavior for Plot2DPane { response.set_drag_started(); } - for ((field, settings), points) in zip(self.settings.plot_lines(), &self.line_data) { + for ((field, settings), TimeAwarePlotPoints { points, .. }) in + zip(&self.settings.y_fields, &self.line_data) + { plot_ui.line( Line::new(&points[..]) .color(settings.color) @@ -188,9 +191,17 @@ impl PaneBehavior for Plot2DPane { } let PlotSettings { - x_field, y_fields, .. + x_field, + y_fields, + points_lifespan, + .. } = &self.settings; + // purge old data + for line in &mut self.line_data { + line.purge_old(*points_lifespan); + } + for msg in messages { let x: f64 = x_field.extract_as_f64(&msg.message).log_unwrap(); let ys: Vec<f64> = y_fields @@ -199,11 +210,11 @@ impl PaneBehavior for Plot2DPane { .collect(); if self.line_data.len() < ys.len() { - self.line_data.resize(ys.len(), Vec::new()); + self.line_data.resize(ys.len(), TimeAwarePlotPoints::new()); } - for (line, y) in zip(&mut self.line_data, ys) { - line.push(PlotPoint::new(x, y)); + for (points, y) in zip(&mut self.line_data, ys) { + points.push(msg.time, PlotPoint::new(x, y)); } } @@ -230,51 +241,21 @@ fn show_menu(ui: &mut egui::Ui, settings_visible: &mut bool, settings: &mut Plot ui.checkbox(&mut settings.axes_visible, "Show Axes"); } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] struct PlotSettings { - plot_message_id: u32, - x_field: IndexedField, - y_fields: Vec<(IndexedField, LineSettings)>, - axes_visible: bool, + /// The message id to plot + pub(super) plot_message_id: u32, + /// The field to plot on the x-axis + pub(super) x_field: IndexedField, + /// The fields to plot, with their respective line settings + pub(super) y_fields: Vec<(IndexedField, LineSettings)>, + /// Whether to show the axes of the plot + pub(super) axes_visible: bool, + /// Points will be shown for this duration before being removed + pub(super) points_lifespan: Duration, } impl PlotSettings { - fn plot_lines(&self) -> &[(IndexedField, LineSettings)] { - &self.y_fields - } - - fn fields_empty(&self) -> bool { - self.y_fields.is_empty() - } - - fn get_msg_id(&self) -> u32 { - self.plot_message_id - } - - fn get_x_field(&self) -> &IndexedField { - &self.x_field - } - - fn get_mut_x_field(&mut self) -> &mut IndexedField { - &mut self.x_field - } - - fn get_mut_y_fields(&mut self) -> &mut [(IndexedField, LineSettings)] { - &mut self.y_fields[..] - } - - fn set_x_field(&mut self, field: IndexedField) { - self.x_field = field; - } - - fn fields_len(&self) -> usize { - self.y_fields.len() - } - - fn contains_field(&self, field: &IndexedField) -> bool { - self.y_fields.iter().any(|(f, _)| f == field) - } - fn add_field(&mut self, field: IndexedField) { let line_settings = LineSettings::default(); self.y_fields.push((field, line_settings)); @@ -310,6 +291,7 @@ impl Default for PlotSettings { x_field, y_fields, axes_visible: true, + points_lifespan: Duration::from_secs(600), } } } @@ -336,69 +318,33 @@ impl Hash for LineSettings { } } -mod plot_serde { - use serde::{Deserialize, Serialize}; - - use super::*; - - #[derive(Serialize, Deserialize)] - struct FieldSettings { - field: usize, - settings: LineSettings, - } +#[derive(Clone, Debug)] +struct TimeAwarePlotPoints { + times: Vec<Instant>, + points: Vec<PlotPoint>, +} - #[derive(Serialize, Deserialize)] - struct PlotSettingsData { - msg_id: u32, - x_field: usize, - y_fields: Vec<FieldSettings>, - axes_visible: bool, +impl TimeAwarePlotPoints { + fn new() -> Self { + Self { + times: Vec::new(), + points: Vec::new(), + } } - impl Serialize for PlotSettings { - fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { - let data = PlotSettingsData { - msg_id: self.plot_message_id, - x_field: self.x_field.id(), - y_fields: self - .y_fields - .iter() - .map(|(field, settings)| FieldSettings { - field: field.id(), - settings: settings.clone(), - }) - .collect(), - axes_visible: self.axes_visible, - }; - data.serialize(serializer) - } + fn push(&mut self, time: Instant, point: PlotPoint) { + self.times.push(time); + self.points.push(point); } - impl<'de> Deserialize<'de> for PlotSettings { - fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> { - let data = PlotSettingsData::deserialize(deserializer)?; - let x_field = data - .x_field - .to_mav_field(data.msg_id, &MAVLINK_PROFILE) - .log_unwrap(); - let y_fields = data - .y_fields - .into_iter() - .map(|FieldSettings { field, settings }| { - ( - field - .to_mav_field(data.msg_id, &MAVLINK_PROFILE) - .log_unwrap(), - settings, - ) - }) - .collect(); - Ok(Self { - plot_message_id: data.msg_id, - x_field, - y_fields, - axes_visible: data.axes_visible, - }) + fn purge_old(&mut self, lifespan: Duration) { + while let Some(time) = self.times.first().copied() { + if time.elapsed() > lifespan { + self.times.remove(0); + self.points.remove(0); + } else { + break; + } } } } diff --git a/src/ui/panes/plot/source_window.rs b/src/ui/panes/plot/source_window.rs index 61ca3c7..a62d1f9 100644 --- a/src/ui/panes/plot/source_window.rs +++ b/src/ui/panes/plot/source_window.rs @@ -1,13 +1,30 @@ -use crate::MAVLINK_PROFILE; +use std::time::Duration; -use crate::error::ErrInstrument; +use crate::{MAVLINK_PROFILE, error::ErrInstrument}; use super::{LineSettings, PlotSettings}; #[profiling::function] pub fn sources_window(ui: &mut egui::Ui, plot_settings: &mut PlotSettings) { - let data_settings_digest = plot_settings.data_digest(); + // select how many points are shown on the plot + let mut points_lifespan_sec = plot_settings.points_lifespan.as_secs(); + ui.horizontal(|ui| { + let res1 = ui.add(egui::Label::new("Points Lifespan: ")); + let res2 = ui.add( + egui::DragValue::new(&mut points_lifespan_sec) + .range(5..=1800) + .speed(1) + .suffix(" seconds"), + ); + res1.union(res2) + }) + .inner + .on_hover_text("How long the data is shown on the plot"); + plot_settings.points_lifespan = Duration::from_secs(points_lifespan_sec); + + ui.add_sized([250., 10.], egui::Separator::default()); + let data_settings_digest = plot_settings.data_digest(); // extract the msg name from the id to show it in the combo box let msg_name = MAVLINK_PROFILE .get_msg(plot_settings.plot_message_id) @@ -30,10 +47,10 @@ pub fn sources_window(ui: &mut egui::Ui, plot_settings: &mut PlotSettings) { // check fields and assign a default field_x and field_y once the msg is changed let fields = MAVLINK_PROFILE - .get_plottable_fields(plot_settings.get_msg_id()) + .get_plottable_fields(plot_settings.plot_message_id) .log_expect("Invalid message id"); // get the first field that is in the list of fields or the previous if valid - let x_field = plot_settings.get_x_field(); + let x_field = &plot_settings.x_field; let new_field_x = fields .iter() .any(|f| f == x_field) @@ -46,10 +63,10 @@ pub fn sources_window(ui: &mut egui::Ui, plot_settings: &mut PlotSettings) { return; }; // update the field_x - plot_settings.set_x_field(new_field_x); - let x_field = plot_settings.get_mut_x_field(); + plot_settings.x_field = new_field_x; // if fields are valid, show the combo boxes for the x_axis + let x_field = &mut plot_settings.x_field; egui::ComboBox::from_label("X Axis") .selected_text(&x_field.field().name) .show_ui(ui, |ui| { @@ -59,19 +76,17 @@ pub fn sources_window(ui: &mut egui::Ui, plot_settings: &mut PlotSettings) { }); // populate the plot_lines with the first field if it is empty and there are more than 1 fields - if plot_settings.fields_empty() && fields.len() > 1 { + if plot_settings.y_fields.is_empty() && fields.len() > 1 { plot_settings.add_field(fields[1].to_owned()); } // check how many fields are left and how many are selected - let plot_lines_len = plot_settings.fields_len(); + let plot_lines_len = plot_settings.y_fields.len(); egui::Grid::new(ui.auto_id_with("y_axis")) .num_columns(3) .spacing([10.0, 2.5]) .show(ui, |ui| { - for (i, (field, line_settings)) in - plot_settings.get_mut_y_fields().iter_mut().enumerate() - { + for (i, (field, line_settings)) in plot_settings.y_fields[..].iter_mut().enumerate() { let LineSettings { width, color } = line_settings; let widget_label = if plot_lines_len > 1 { format!("Y Axis {}", i + 1) @@ -104,9 +119,10 @@ pub fn sources_window(ui: &mut egui::Ui, plot_settings: &mut PlotSettings) { .on_hover_text("Add another Y axis") .clicked() { + // get the first field that is not in the plot_lines let next_field = fields .iter() - .find(|f| !plot_settings.contains_field(f)) + .find(|field| !plot_settings.y_fields.iter().any(|(f, _)| f == *field)) .log_unwrap(); plot_settings.add_field(next_field.to_owned()); } -- GitLab