diff --git a/src/mavlink/error.rs b/src/mavlink/error.rs index 23975eb59b7384774098dacb2e31f5c5b7f1c878..b0aca6e4114deca47fb0c777683cc36b826924e9 100644 --- a/src/mavlink/error.rs +++ b/src/mavlink/error.rs @@ -1,11 +1,7 @@ use thiserror::Error; -pub type Result<T> = std::result::Result<T, MavlinkError>; - #[derive(Debug, Error)] pub enum MavlinkError { - #[error("Error parsing field: {0}")] - UnknownField(String), #[error("Error parsing message: {0}")] ParseError(#[from] serde_json::Error), } diff --git a/src/mavlink/reflection.rs b/src/mavlink/reflection.rs index 8f81521e78a55057713a8488f0f94c68a5fcc157..68937c62c5b549a27abf81fa5bed5ae09f4540e8 100644 --- a/src/mavlink/reflection.rs +++ b/src/mavlink/reflection.rs @@ -105,7 +105,7 @@ pub struct IndexedField<'a> { field: &'a MavField, } -impl<'a> IndexedField<'a> { +impl IndexedField<'_> { pub fn msg(&self) -> &MavMessage { self.msg } @@ -234,7 +234,7 @@ impl<'b> FieldLike<'_, 'b> for &str { .iter() .find(|f| f.name == *self) .map(|f| IndexedField { - id: msg.fields.iter().position(|f2| f2 == f).unwrap(), + id: msg.fields.iter().position(|f2| f2 == f).log_unwrap(), msg, field: f, }) diff --git a/src/ui/cache.rs b/src/ui/cache.rs index 46b29cc2f5199005f2eb42e4ee4a5c827d457801..23d894d5b73859904f6767094245adcc0f44356a 100644 --- a/src/ui/cache.rs +++ b/src/ui/cache.rs @@ -1,7 +1,11 @@ //! Module for caching expensive UI calls using egui's temporary memory storage. //! It provides utilities for caching the results of functions to avoid frequent recalculations. -use std::time::{Duration, Instant}; +use std::{ + collections::hash_map::DefaultHasher, + hash::{Hash, Hasher}, + time::{Duration, Instant}, +}; use egui::Context; use serialport::SerialPortInfo; @@ -90,3 +94,69 @@ pub fn cached_first_stm32_port(ctx: &Context) -> Result<Option<SerialPortInfo>, SERIAL_PORT_REFRESH_INTERVAL, ) } + +/// ChangeTracker manages the tracking of state changes using an integrity digest. +/// +/// The `integrity_digest` field holds a 64-bit unsigned integer that represents +/// a summary (or hash) of the current state. This can be used to verify that the +/// cached UI state remains consistent, and to quickly detect any modifications. +pub struct ChangeTracker { + integrity_digest: u64, +} + +impl ChangeTracker { + /// Records the initial state of a hashable value by computing its hash digest. + /// + /// This method takes a reference to any value that implements the `Hash` trait, + /// computes its hash using the default hasher, and stores the resulting digest in a + /// newly created `ChangeTracker` instance. This digest serves as a reference point + /// for future state comparisons. + /// + /// # Parameters + /// + /// - `state`: A reference to the value whose state is to be recorded. + /// + /// # Returns + /// + /// A `ChangeTracker` initialized with the computed hash digest. + /// + /// # Examples + /// + /// ``` + /// let initial_tracker = ChangeTracker::record_initial_state(&state); + /// ``` + pub fn record_initial_state<T: Hash>(state: &T) -> Self { + let mut hasher = DefaultHasher::new(); + state.hash(&mut hasher); + let integrity_digest = hasher.finish(); + Self { integrity_digest } + } + + /// Checks whether the hash of the current state differs from the initially recorded state. + /// + /// This method computes the hash digest of the current state (which must implement the + /// `Hash` trait) and compares it with the digest stored in the `ChangeTracker`. If the digests + /// differ, it indicates that the state has changed since the initial recording. + /// + /// # Parameters + /// + /// - `state`: A reference to the current state to be checked for changes. + /// + /// # Returns + /// + /// `true` if the current state's hash digest does not match the initially recorded digest, + /// indicating a change; `false` otherwise. + /// + /// # Examples + /// + /// ``` + /// if tracker.has_changed(&state) { + /// println!("The state has changed."); + /// } + /// ``` + pub fn has_changed<T: Hash>(&self, state: &T) -> bool { + let mut hasher = DefaultHasher::new(); + state.hash(&mut hasher); + self.integrity_digest != hasher.finish() + } +} diff --git a/src/ui/panes/plot.rs b/src/ui/panes/plot.rs index a2a48b2a36c7beeb7bc915f459d0dcc89d4cc24d..7c2e27d0ce9ed07888f0d8a41d9982874d3a05e0 100644 --- a/src/ui/panes/plot.rs +++ b/src/ui/panes/plot.rs @@ -8,14 +8,14 @@ use crate::{ Message, MessageData, ROCKET_FLIGHT_TM_DATA, TimedMessage, reflection::{self, FieldLike}, }, - ui::app::PaneResponse, + ui::{app::PaneResponse, cache::ChangeTracker}, }; use egui::{Color32, Vec2b}; use egui_plot::{Legend, Line, PlotPoint, PlotPoints}; use egui_tiles::TileId; use mavlink_bindgen::parser::MavType; use serde::{Deserialize, Serialize}; -use source_window::{ChangeTracker, sources_window}; +use source_window::sources_window; use std::{hash::Hash, iter::zip}; #[derive(Clone, Default, Debug, Serialize, Deserialize)] @@ -170,14 +170,6 @@ impl PlotSettings { &self.x_field } - fn get_y_fields(&self) -> Vec<&FieldWithID> { - self.y_fields.iter().map(|(field, _)| field).collect() - } - - // fn get_mut_msg_id(&mut self) -> &mut u32 { - // &mut self.msg_sources.plot_message_id - // } - fn get_mut_x_field(&mut self) -> &mut FieldWithID { &mut self.x_field } @@ -194,10 +186,6 @@ impl PlotSettings { self.y_fields.len() } - // fn is_msg_id_changed(&self) -> bool { - // self.msg_sources.plot_message_id != self.old_msg_sources.plot_message_id - // } - fn contains_field(&self, field: &FieldWithID) -> bool { self.y_fields.iter().any(|(f, _)| f == field) } @@ -262,12 +250,6 @@ impl Hash for LineSettings { } } -impl LineSettings { - fn new(width: f32, color: Color32) -> Self { - Self { width, color } - } -} - /// A struct to hold a field and its ID in a message /// We use this and not `reflection::IndexedField` because we need to serialize it #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] diff --git a/src/ui/panes/plot/source_window.rs b/src/ui/panes/plot/source_window.rs index b4dca59810d895e687f6a98136e8ac58031cde26..e49df739c1d240ee93186a1c1fbd11ced4a389ec 100644 --- a/src/ui/panes/plot/source_window.rs +++ b/src/ui/panes/plot/source_window.rs @@ -1,10 +1,8 @@ -use std::{ - collections::hash_map::DefaultHasher, - hash::{Hash, Hasher}, +use crate::{ + MAVLINK_PROFILE, + ui::{cache::ChangeTracker, panes::plot::FieldWithID}, }; -use crate::{MAVLINK_PROFILE, ui::panes::plot::FieldWithID}; - use crate::error::ErrInstrument; use super::{LineSettings, PlotSettings}; @@ -78,7 +76,7 @@ pub fn sources_window(ui: &mut egui::Ui, plot_settings: &mut PlotSettings) { .spacing([10.0, 2.5]) .show(ui, |ui| { for (i, (field, line_settings)) in - plot_settings.get_mut_y_fields().into_iter().enumerate() + plot_settings.get_mut_y_fields().iter_mut().enumerate() { let LineSettings { width, color } = line_settings; let widget_label = if plot_lines_len > 1 { @@ -114,98 +112,3 @@ pub fn sources_window(ui: &mut egui::Ui, plot_settings: &mut PlotSettings) { plot_settings.add_field(next_field.to_owned()); } } - -pub struct ChangeTracker { - integrity_digest: u64, -} - -impl ChangeTracker { - pub fn record_initial_state<T: Hash>(state: &T) -> Self { - let mut hasher = DefaultHasher::new(); - state.hash(&mut hasher); - let integrity_digest = hasher.finish(); - Self { integrity_digest } - } - - pub fn has_changed<T: Hash>(&self, state: &T) -> bool { - let mut hasher = DefaultHasher::new(); - state.hash(&mut hasher); - self.integrity_digest != hasher.finish() - } -} - -// pub struct SourceSettings<'a> { -// msg_sources: &'a mut PlotSettings, -// } - -// impl<'a> SourceSettings<'a> { -// pub fn new( -// msg_sources: &'a mut PlotSettings, -// line_settings: &'a mut Vec<LineSettings>, -// ) -> Self { -// Self { -// old_msg_sources: msg_sources.clone(), -// msg_sources, -// line_settings, -// } -// } - -// pub fn are_sources_changed(&self) -> bool { -// self.msg_sources != &self.old_msg_sources -// } - -// pub fn fields_empty(&self) -> bool { -// self.msg_sources.y_field_ids.is_empty() -// } - -// fn get_msg_id(&self) -> u32 { -// self.msg_sources.plot_message_id -// } - -// fn get_x_field_id(&self) -> usize { -// self.msg_sources.x_field_id -// } - -// fn get_mut_msg_id(&mut self) -> &mut u32 { -// &mut self.msg_sources.plot_message_id -// } - -// fn get_mut_x_field_id(&mut self) -> &mut usize { -// &mut self.msg_sources.x_field_id -// } - -// fn set_x_field_id(&mut self, field_id: usize) { -// self.msg_sources.x_field_id = field_id; -// } - -// fn fields_len(&self) -> usize { -// self.msg_sources.y_field_ids.len() -// } - -// fn is_msg_id_changed(&self) -> bool { -// self.msg_sources.plot_message_id != self.old_msg_sources.plot_message_id -// } - -// fn contains_field(&self, field_id: usize) -> bool { -// self.msg_sources.y_field_ids.contains(&field_id) -// } - -// fn sync_fields_with_lines(&mut self) { -// self.msg_sources.y_field_ids = self -// .line_settings -// .iter() -// .map(|ls| ls.field_id.clone()) -// .collect(); -// } - -// fn add_field(&mut self, field_id: usize) { -// self.line_settings.push(LineSettings::new(field_id)); -// self.msg_sources.y_field_ids.push(field_id); -// } - -// fn clear_fields(&mut self) { -// self.msg_sources.y_field_ids.clear(); -// self.line_settings.clear(); -// self.msg_sources.x_field_id = 0; -// } -// }