From 2581ed0c0e8c06605feee074ec2b0d004a9be3e2 Mon Sep 17 00:00:00 2001 From: Felix Wiegand Date: Sat, 4 Nov 2023 20:15:15 +0100 Subject: [PATCH] Refactor plots and map They now handle their own cache, and no longer need to be notified of new states and resets. --- .gitignore | 2 + src/data_source.rs | 6 +- src/data_source/log_file.rs | 7 +- src/data_source/serial.rs | 8 +- src/data_source/simulation.rs | 7 +- src/gui.rs | 21 +--- src/gui/map.rs | 137 ++++++++++++++++--------- src/gui/plot.rs | 188 ++++++++++++++++++---------------- src/gui/tabs/plot.rs | 85 +++++---------- 9 files changed, 226 insertions(+), 235 deletions(-) diff --git a/.gitignore b/.gitignore index 09cee83..b754489 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ !archive/*.kml *.exe *.bin +perf.data +flamegraph.svg diff --git a/src/data_source.rs b/src/data_source.rs index ba83874..2a76cdd 100644 --- a/src/data_source.rs +++ b/src/data_source.rs @@ -25,10 +25,10 @@ pub use simulation::SimulationDataSource; /// Trait shared by all data sources. pub trait DataSource { - /// Return an iterator over only the states that have arrived since last time. - fn new_vehicle_states<'a>(&'a mut self) -> Iter<'_, (Instant, VehicleState)>; + /// Called every frame. + fn update(&mut self, ctx: &egui::Context); /// Return an iterator over all known states of the vehicle. - fn vehicle_states<'a>(&'a mut self) -> Iter<'_, (Instant, VehicleState)>; + fn vehicle_states<'a>(&'a self) -> Iter<'_, (Instant, VehicleState)>; /// Return the current flight computer settings, if known. fn fc_settings<'a>(&'a mut self) -> Option<&'a Settings>; diff --git a/src/data_source/log_file.rs b/src/data_source/log_file.rs index 4eaf187..b284616 100644 --- a/src/data_source/log_file.rs +++ b/src/data_source/log_file.rs @@ -75,7 +75,7 @@ impl LogFileDataSource { } impl DataSource for LogFileDataSource { - fn new_vehicle_states<'a>(&'a mut self) -> Iter<'_, (Instant, VehicleState)> { + fn update(&mut self, _ctx: &egui::Context) { if let Some(file) = self.file.as_mut() { if let Err(e) = file.read_to_end(&mut self.buffer) { error!("Failed to read log file: {:?}", e); @@ -119,7 +119,6 @@ impl DataSource for LogFileDataSource { self.messages.push((self.last_time.unwrap(), msg)); } - let start_i = self.vehicle_states.len(); let pointer = if self.replay { let now = Instant::now(); self.messages.partition_point(|(t, _)| t <= &now) @@ -130,11 +129,9 @@ impl DataSource for LogFileDataSource { for (t, msg) in self.messages.drain(..pointer) { self.vehicle_states.push((t, msg.into())); } - - self.vehicle_states[start_i..].iter() } - fn vehicle_states<'a>(&'a mut self) -> Iter<'_, (Instant, VehicleState)> { + fn vehicle_states<'a>(&'a self) -> Iter<'_, (Instant, VehicleState)> { self.vehicle_states.iter() } diff --git a/src/data_source/serial.rs b/src/data_source/serial.rs index c50e2f7..bde5d37 100644 --- a/src/data_source/serial.rs +++ b/src/data_source/serial.rs @@ -252,7 +252,7 @@ impl SerialDataSource { } impl DataSource for SerialDataSource { - fn new_vehicle_states<'a>(&'a mut self) -> Iter<'_, (Instant, VehicleState)> { + fn update(&mut self, _ctx: &egui::Context) { self.message_receipt_times.retain(|(i, _)| i.elapsed() < Duration::from_millis(1000)); for (status, port) in self.serial_status_rx.try_iter() { @@ -265,8 +265,6 @@ impl DataSource for SerialDataSource { } let msgs: Vec<_> = self.downlink_rx.try_iter().collect(); - - let start_i = self.vehicle_states.len(); for msg in msgs.into_iter() { self.write_to_telemetry_log(&msg); @@ -289,11 +287,9 @@ impl DataSource for SerialDataSource { } } } - - self.vehicle_states[start_i..].iter() } - fn vehicle_states<'a>(&'a mut self) -> Iter<'_, (Instant, VehicleState)> { + fn vehicle_states<'a>(&'a self) -> Iter<'_, (Instant, VehicleState)> { self.vehicle_states.iter() } diff --git a/src/data_source/simulation.rs b/src/data_source/simulation.rs index 731f937..a934847 100644 --- a/src/data_source/simulation.rs +++ b/src/data_source/simulation.rs @@ -26,12 +26,11 @@ pub struct SimulationDataSource { } impl DataSource for SimulationDataSource { - fn new_vehicle_states<'a>(&'a mut self) -> Iter<'_, (Instant, VehicleState)> { + fn update(&mut self, _ctx: &egui::Context) { if self.state.is_none() { self.state = Some(SimulationState::initialize(&self.settings)); } - let start_i = self.vehicle_states.len(); while !self.state.as_mut().unwrap().tick() { let sim_state = self.state.as_ref().unwrap(); if !sim_state.plottable() { @@ -46,11 +45,9 @@ impl DataSource for SimulationDataSource { .unwrap_or(Instant::now()); self.vehicle_states.push((time, vehicle_state)); } - - self.vehicle_states[start_i..].iter() } - fn vehicle_states<'a>(&'a mut self) -> Iter<'_, (Instant, VehicleState)> { + fn vehicle_states<'a>(&'a self) -> Iter<'_, (Instant, VehicleState)> { self.vehicle_states.iter() } diff --git a/src/gui.rs b/src/gui.rs index 420284d..5597a40 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -127,16 +127,8 @@ impl Sam { self.data_source.vehicle_states().rev().find_map(|(_t, msg)| callback(msg)) } - /// Resets the GUI - fn reset(&mut self, keep_position: bool) { - info!("Resetting."); - self.data_source.reset(); - self.plot_tab.reset(keep_position); - } - /// Opens a log file data source fn open_log_file(&mut self, ds: LogFileDataSource) { - self.reset(false); self.data_source = Box::new(ds); } @@ -198,7 +190,6 @@ impl Sam { /// Closes the currently opened data source fn close_data_source(&mut self) { - self.reset(false); self.data_source = Box::new(SerialDataSource::new(self.settings.lora.clone())); } @@ -367,8 +358,6 @@ impl Sam { // Toggle archive panel if ui.selectable_label(self.data_source.simulation_settings().is_some(), "💻 Simulate").clicked() { self.data_source = Box::new(SimulationDataSource::default()); - self.reset(false); - self.plot_tab.show_all(); } // Show a button to the right to close the current log/simulation and go back to @@ -450,7 +439,7 @@ impl Sam { } if ui.button("↻ Rerun").clicked() || (changed && released) { - self.reset(true); + self.data_source.reset(); } }); }); @@ -489,7 +478,7 @@ impl Sam { #[cfg(not(target_arch = "wasm32"))] ui.add_enabled_ui(!self.data_source.is_log_file(), |ui| { if ui.button("⏮ Reset").clicked() { - self.reset(false); + self.data_source.reset(); } }); @@ -544,11 +533,7 @@ impl eframe::App for Sam { #[cfg(feature = "profiling")] puffin_egui::profiler_window(ctx); - // Process new messages - let new: Vec<(Instant, VehicleState)> = self.data_source.new_vehicle_states().cloned().collect(); - for (time, vs) in &new { - self.plot_tab.push_vehicle_state(time, vs); - } + self.data_source.update(ctx); // TODO: only send this if we know it's not a ground station? if self.data_source.fc_settings().is_none() && self.data_source.vehicle_states().next().is_some() { diff --git a/src/gui/map.rs b/src/gui/map.rs index 5fffdad..8ea4917 100644 --- a/src/gui/map.rs +++ b/src/gui/map.rs @@ -19,7 +19,9 @@ use egui::mutex::Mutex; use egui_plot::{Line, PlotBounds, PlotImage, PlotPoint, PlotPoints}; use egui::{Color32, ColorImage, Context, TextureHandle, Vec2}; -use crate::state::*; +use crate::data_source::DataSource; + +const GRADIENT_MAX_ALT: f64 = 10000.0; fn tile_mapbox_url(tile: &Tile, access_token: &String) -> String { format!( @@ -139,13 +141,14 @@ pub struct MapCache { max_alt: f64, pub center: (f64, f64), pub hdop_circle_points: Option>, + cached_state: Option<(Instant, usize)>, gradient_lookup: Vec, } impl MapCache { pub fn new() -> Self { - let gradient_lookup = (0..=100) - .map(|i| colorgrad::yl_or_rd().at((i as f64) / 100.0).to_rgba8()) + let gradient_lookup = (0..=1000) + .map(|i| colorgrad::sinebow().at((i as f64) / 1000.0).to_rgba8()) .map(|color| Color32::from_rgb(color[0], color[1], color[2])) .collect(); @@ -155,34 +158,45 @@ impl MapCache { max_alt: 300.0, center: (49.861445, 8.68519), hdop_circle_points: None, + cached_state: None, gradient_lookup, } } - pub fn push(&mut self, _x: Instant, vs: &VehicleState) { - if let (Some(lat), Some(lng)) = (vs.latitude, vs.longitude) { - let (lat, lng) = (lat as f64, lng as f64); - let altitude_ground = vs.altitude_ground.unwrap_or(0.0); - let alt = vs.altitude_gps.map(|alt| alt - altitude_ground).unwrap_or(0.0) as f64; - let hdop = vs.hdop.unwrap_or(9999); + pub fn aspect(&self) -> f64 { + 1.0 / f64::cos(self.center.0.to_radians() as f64) + } - self.points.push((lat, lng, alt)); - self.max_alt = f64::max(self.max_alt, alt); - self.center = (lat, lng); + fn update_position_cache(&mut self, data_source: &dyn DataSource, keep_first: usize) { + let new_data = data_source.vehicle_states() + .skip(keep_first) + .filter(|(_t, vs)| vs.latitude.is_some() && vs.longitude.is_some()) + .map(|(_t, vs)| (vs.latitude.unwrap(), vs.longitude.unwrap(), vs.altitude_gps.unwrap_or(0.0))) + .map(|(lat, lng, alt)| (lat as f64, lng as f64, alt as f64)); + + if keep_first > 0 { + self.points.extend(new_data); + } else { + self.points = new_data.collect(); + } - self.plot_points = self - .points - .windows(2) - .map(|pair| { - #[cfg(feature = "profiling")] - puffin::profile_scope!("map_line_creation"); + self.plot_points = self + .points + .windows(2) + .map(|pair| { + let points = [[pair[0].1, pair[0].0], [pair[1].1, pair[1].0]]; + let index = usize::min(((pair[0].2 / GRADIENT_MAX_ALT) * self.gradient_lookup.len() as f64).floor() as usize, self.gradient_lookup.len() - 1); + let color = self.gradient_lookup[index]; + (points, color) + }) + .collect(); + } - let points = [[pair[0].1, pair[0].0], [pair[1].1, pair[1].0]]; - let index = usize::min(((1.0 - pair[0].2 / self.max_alt) * 100.0).floor() as usize, 100); - let color = self.gradient_lookup[index]; - (points, color) - }) - .collect(); + fn update_hdop_cache(&mut self, data_source: &dyn DataSource) { + let last = data_source.vehicle_states().rev().find(|(_, vs)| vs.latitude.is_some() && vs.longitude.is_some()); + if let Some((_, vs)) = last { + let (lat, lng) = (vs.latitude.unwrap() as f64, vs.longitude.unwrap() as f64); + let hdop = vs.hdop.unwrap_or(9999); let cep_m = 2.5 * (hdop as f64) / 100.0; let r = 360.0 * cep_m / 40_075_017.0; // meters to decimal degrees @@ -190,26 +204,60 @@ impl MapCache { .map(|i| (i as f64) * TAU / 64.0) .map(|i| [r * i.cos() * self.aspect() + lng, r * i.sin() + lat]) .collect(); + self.center = (lat, lng); self.hdop_circle_points = Some(points); + } else { + self.hdop_circle_points = None; } } - pub fn reset(&mut self) { - self.points.truncate(0); - self.plot_points.truncate(0); - self.max_alt = 300.0; - self.center = (49.861445, 8.68519); - self.hdop_circle_points = None; - } + fn update_cache_if_necessary(&mut self, data_source: &dyn DataSource) { + let new_len = data_source.vehicle_states().len(); + if new_len == 0 { + self.points.truncate(0); + self.plot_points.truncate(0); + self.hdop_circle_points = None; + self.cached_state = None; + self.max_alt = 300.0; + return; + } - pub fn aspect(&self) -> f64 { - 1.0 / f64::cos(self.center.0.to_radians() as f64) + let (last_t, _) = data_source.vehicle_states().rev().next().unwrap().clone(); + let cached_state = Some((last_t, new_len)); + + // We have already cached this exact set of vehicle states, do nothing. + if cached_state == self.cached_state { + return; + } + + // Try to determine if the new data is simply a few more points appended to the previously + // plotted data, which we have cached. If so, we keep the old and simply append the new + // points. If not, we recalculate the cache completely. + let old_len = self.cached_state.map(|(_, l)| l).unwrap_or(0); + let mut keep_first = (new_len > old_len).then_some(old_len).unwrap_or(0); + if keep_first > 0 { + // double-check that it is actually the same set of states by looking for our previous + // last state in the new data + let (previous_last, _) = data_source.vehicle_states() + .rev() + .skip(new_len - keep_first) + .next() + .unwrap(); + if self.cached_state.map(|(t, _)| t != *previous_last).unwrap_or(true) { + keep_first = 0; + } + } + + self.update_position_cache(data_source, keep_first); + self.update_hdop_cache(data_source); + self.cached_state = cached_state; } - fn lines<'a>(&'a self) -> Vec { + fn lines<'a>(&'a mut self, data_source: &dyn DataSource) -> Vec { #[cfg(feature = "profiling")] puffin::profile_function!(); + self.update_cache_if_necessary(data_source); self.plot_points .clone() .into_iter() @@ -217,10 +265,11 @@ impl MapCache { .collect() } - pub fn hdop_circle_line(&self) -> Option { + pub fn hdop_circle_line(&mut self, data_source: &dyn DataSource) -> Option { #[cfg(feature = "profiling")] puffin::profile_function!(); + self.update_cache_if_necessary(data_source); self.hdop_circle_points .as_ref() .map(|points| Line::new(points.clone()).width(1.5).color(Color32::RED)) @@ -301,26 +350,18 @@ impl MapState { }); Box::new(iter) } - - pub fn push(&self, x: Instant, vs: &VehicleState) { - self.cache.borrow_mut().push(x, vs); - } - - pub fn reset(&self) { - self.cache.borrow_mut().reset(); - } } pub trait MapUiExt { - fn map(&mut self, state: &MapState); + fn map(&mut self, state: &MapState, data_source: &dyn DataSource); } impl MapUiExt for egui::Ui { - fn map(&mut self, state: &MapState) { + fn map(&mut self, state: &MapState, data_source: &dyn DataSource) { #[cfg(feature = "profiling")] puffin::profile_function!(); - let cache = state.cache.borrow_mut(); + let mut cache = state.cache.borrow_mut(); self.vertical_centered(|ui| { let plot = egui_plot::Plot::new("map") @@ -343,14 +384,14 @@ impl MapUiExt for egui::Ui { plot_ui.image(pi); } - if let Some(line) = cache.hdop_circle_line() { + if let Some(line) = cache.hdop_circle_line(data_source) { #[cfg(feature = "profiling")] puffin::profile_scope!("hdop_circle"); plot_ui.line(line); } - for line in cache.lines() { + for line in cache.lines(data_source) { #[cfg(feature = "profiling")] puffin::profile_scope!("map_line"); diff --git a/src/gui/plot.rs b/src/gui/plot.rs index f8e78f7..fd3a2e1 100644 --- a/src/gui/plot.rs +++ b/src/gui/plot.rs @@ -16,15 +16,23 @@ use crate::gui::*; use crate::state::*; use crate::telemetry_ext::*; +fn plot_time(x: &Instant, data_source: &dyn DataSource) -> f64 { + if let Some((first_t, _first_vs)) = data_source.vehicle_states().next() { + x.duration_since(*first_t).as_secs_f64() + } else { + 0.0 + } +} + /// Cache for a single line. struct PlotCacheLine { name: String, color: Color32, pub callback: Box Option>, data: Vec<[f64; 2]>, + stats: Option<(f64, f64, f64, f64)>, last_bounds: Option, last_view: Vec<[f64; 2]>, - stats: Option<(f64, f64, f64, f64)>, } impl PlotCacheLine { @@ -34,33 +42,37 @@ impl PlotCacheLine { color, callback: Box::new(cb), data: Vec::new(), + stats: None, last_bounds: None, last_view: vec![], - stats: None, } } - pub fn push(&mut self, x: f64, vs: &VehicleState) { - if let Some(value) = (self.callback)(vs) { - self.data.push([x, value.into()]); - } + fn update_cache(&mut self, data_source: &dyn DataSource, keep_first: usize) { + let new_data = data_source.vehicle_states() + .skip(keep_first) + .map(|(t, vs)| (plot_time(t, data_source), vs)) + .filter_map(|(x, vs)| (self.callback)(vs).map(|y| [x, y as f64])); - self.last_bounds = None; // TODO - self.stats = None; + if keep_first > 0 { + self.data.extend(new_data) + } else { + self.data = new_data.collect(); + } } - pub fn reset(&mut self) { + fn clear_cache(&mut self) { self.data.truncate(0); - self.last_bounds = None; - self.last_view.truncate(0); - self.stats = None; } - pub fn data_for_bounds(&mut self, bounds: PlotBounds) -> Vec<[f64; 2]> { + pub fn data_for_bounds(&mut self, bounds: PlotBounds, data_source: &dyn DataSource) -> Vec<[f64; 2]> { #[cfg(feature = "profiling")] puffin::profile_function!(); - if self.data.is_empty() { + let len = data_source.vehicle_states().len(); + if len == 0 || self.data.is_empty() { + self.last_bounds = None; + self.stats = None; return vec![]; } @@ -99,20 +111,22 @@ impl PlotCacheLine { /// Larger data structures cached for each plot, to avoid being recalculated /// on each draw. struct PlotCache { - start: Instant, lines: Vec, mode_transitions: Vec<(f64, FlightMode)>, reset_on_next_draw: bool, + /// Identifies the origin of the current data using the last time cached and the number of + /// states included + cached_state: Option<(Instant, usize)> // TODO: maybe add some sort of flight identifier? } impl PlotCache { /// Create a new plot cache. - fn new(start: Instant) -> Self { + fn new() -> Self { Self { lines: Vec::new(), mode_transitions: Vec::new(), reset_on_next_draw: false, - start, + cached_state: None, } } @@ -120,47 +134,78 @@ impl PlotCache { self.lines.push(PlotCacheLine::new(name, color, cb)); } - /// Incorporate some new data into the cache. - pub fn push(&mut self, x: Instant, vs: &VehicleState) { - let x = x.duration_since(self.start).as_secs_f64(); + fn update_mode_transition_cache(&mut self, data_source: &dyn DataSource, keep_first: usize) { + let last_mode = (keep_first > 0).then_some(self.mode_transitions.last().map(|(_,m)| *m)).unwrap_or(None); + let new_data = data_source.vehicle_states() + .skip(keep_first) + .filter(|(_t, vs)| vs.mode.is_some()) + .scan(last_mode, |state, (t, vs)| { + if &vs.mode != state { + *state = vs.mode; + Some(Some((plot_time(t, data_source), vs.mode.unwrap()))) + } else { + Some(None) + } + }) + .filter_map(|x| x); - // Value to be plotted. - for l in self.lines.iter_mut() { - l.push(x, vs); + if keep_first > 0 { + self.mode_transitions.extend(new_data); + } else { + self.mode_transitions = new_data.collect(); } + } - // Vertical lines for mode transitions - if let Some(mode) = vs.mode { - if self.mode_transitions.last().map(|l| l.1 != mode).unwrap_or(true) { - self.mode_transitions.push((x, mode)); - } + fn update_caches_if_necessary(&mut self, data_source: &dyn DataSource) { + let new_len = data_source.vehicle_states().len(); + if new_len == 0 { + self.mode_transitions.truncate(0); + self.lines.iter_mut().for_each(|l| { l.clear_cache(); }); + self.cached_state = None; + return; } - } - /// Reset the cache. - pub fn reset(&mut self, start: Instant, keep_position: bool) { - for l in self.lines.iter_mut() { - l.reset(); + let (last_t, _) = data_source.vehicle_states().rev().next().unwrap().clone(); + let cached_state = Some((last_t, new_len)); + + // We have already cached this exact set of vehicle states, do nothing. + if cached_state == self.cached_state { + return; } - self.mode_transitions.truncate(0); - self.start = start; + // Try to determine if the new data is simply a few more points appended to the previously + // plotted data, which we have cached. If so, we keep the old and simply append the new + // points. If not, we recalculate the cache completely. + let old_len = self.cached_state.map(|(_, l)| l).unwrap_or(0); + let mut keep_first = (new_len > old_len).then_some(old_len).unwrap_or(0); + if keep_first > 0 { + // double-check that it is actually the same set of states by looking for our previous + // last state in the new data + let (previous_last, _) = data_source.vehicle_states() + .rev() + .skip(new_len - keep_first) + .next() + .unwrap(); + if self.cached_state.map(|(t, _)| t != *previous_last).unwrap_or(true) { + keep_first = 0; + } + } - // To reset some of the egui internals (state stored in the linked - // axes/cursor groups), we also need to call reset on the egui object - // during the next draw. Slightly hacky. - self.reset_on_next_draw = !keep_position; + self.lines.iter_mut().for_each(|l| { l.update_cache(data_source, keep_first); }); + self.update_mode_transition_cache(data_source, keep_first); + self.cached_state = cached_state; } /// Lines to be plotted - pub fn plot_lines(&mut self, bounds: PlotBounds, show_stats: bool) -> Vec { + pub fn plot_lines(&mut self, bounds: PlotBounds, show_stats: bool, data_source: &dyn DataSource) -> Vec { #[cfg(feature = "profiling")] puffin::profile_function!(); + self.update_caches_if_necessary(data_source); self.lines .iter_mut() .map(|pcl| { - let data = pcl.data_for_bounds(bounds); + let data = pcl.data_for_bounds(bounds, data_source); let stats = show_stats.then(|| pcl.stats()).flatten(); let legend = if let Some((mean, std_dev, min, max)) = stats { format!( @@ -176,7 +221,11 @@ impl PlotCache { } /// Vertical mode transition lines to be plotted - pub fn mode_lines(&self) -> Box + '_> { + pub fn mode_lines(&mut self, data_source: &dyn DataSource) -> Box + '_> { + #[cfg(feature = "profiling")] + puffin::profile_function!(); + + self.update_caches_if_necessary(data_source); let iter = self.mode_transitions.iter().map(|(x, mode)| VLine::new(*x).color(mode.color())); Box::new(iter) } @@ -214,19 +263,6 @@ impl SharedPlotState { self.end = end.unwrap_or(self.start); } - pub fn reset(&mut self, start: Instant) { - self.start = start; - self.end = start; - self.view_width = 10.0; - self.attached_to_edge = true; - self.box_dragging = false; - } - - pub fn show_all(&mut self) { - self.view_width = (self.end - self.start).as_secs_f64(); - self.reset_on_next_draw = true; - } - pub fn process_zoom(&mut self, zoom_delta: Vec2) { self.view_width /= zoom_delta[0] as f64; // Zooming detaches the egui plot from the edge usually, so reattach @@ -245,14 +281,6 @@ impl SharedPlotState { self.box_dragging = false; } } - - pub fn view_start(&self) -> f64 { - self.end.duration_since(self.start).as_secs_f64() - self.view_width - } - - pub fn view_end(&self) -> f64 { - self.end.duration_since(self.start).as_secs_f64() - } } /// State held by application for each plot, including the cached plot values. @@ -278,7 +306,7 @@ impl PlotState { ylimits: (Option, Option), shared: Rc>, ) -> Self { - let cache = PlotCache::new(shared.borrow().start); + let cache = PlotCache::new(); let (ymin, ymax) = ylimits; Self { @@ -294,29 +322,14 @@ impl PlotState { self.cache.borrow_mut().add_line(name, color, cb); self } - - // Add some newly downlinked data to the cache. - pub fn push(&mut self, x: Instant, vs: &VehicleState) { - self.cache.borrow_mut().push(x, vs) - } - - // Reset the plot, for instance when loading a different file. - pub fn reset(&mut self, start: Instant, keep_position: bool) { - self.cache.borrow_mut().reset(start, keep_position); - self.shared.borrow_mut().reset(start); - } - - pub fn show_all(&mut self) { - self.shared.borrow_mut().show_all(); - } } pub trait PlotUiExt { - fn plot_telemetry(&mut self, state: &PlotState); + fn plot_telemetry(&mut self, state: &PlotState, data_source: &dyn DataSource); } impl PlotUiExt for egui::Ui { - fn plot_telemetry(&mut self, state: &PlotState) { + fn plot_telemetry(&mut self, state: &PlotState, data_source: &dyn DataSource) { #[cfg(feature = "profiling")] puffin::profile_function!(); @@ -326,9 +339,10 @@ impl PlotUiExt for egui::Ui { let legend = Legend::default().text_style(egui::TextStyle::Small).background_alpha(0.5).position(Corner::LeftTop); + let view_end = plot_time(&data_source.end().unwrap_or(Instant::now()), data_source); let mut plot = egui_plot::Plot::new(&state.title) .link_axis("plot_axis_group", true, false) - .link_cursor("plot_cursor_gropu", true, false) + .link_cursor("plot_cursor_group", true, false) .set_margin_fraction(egui::Vec2::new(0.0, 0.15)) .allow_scroll(false) // TODO: x only .allow_drag(AxisBools::new(true, false)) @@ -339,8 +353,8 @@ impl PlotUiExt for egui::Ui { // TODO: maybe upstream an option to move ticks inside? .x_axis_position(egui_plot::VPlacement::Top) .y_axis_position(egui_plot::HPlacement::Right) - .include_x(shared.view_start()) - .include_x(shared.view_end()) + .include_x(view_end - shared.view_width) + .include_x(view_end) .auto_bounds_y() .sharp_grid_lines(true) .legend(legend.clone()); @@ -362,14 +376,12 @@ impl PlotUiExt for egui::Ui { let show_stats = shared.show_stats; let ir = plot.show(self, move |plot_ui| { - let lines = cache.plot_lines(plot_ui.plot_bounds(), show_stats); - let mode_lines = cache.mode_lines(); - + let lines = cache.plot_lines(plot_ui.plot_bounds(), show_stats, data_source); for l in lines.into_iter() { plot_ui.line(l.width(1.2)); } - for vl in mode_lines.into_iter() { + for vl in cache.mode_lines(data_source).into_iter() { plot_ui.vline(vl.style(LineStyle::Dashed { length: 4.0 })); } }); diff --git a/src/gui/tabs/plot.rs b/src/gui/tabs/plot.rs index dbc9060..97ec237 100644 --- a/src/gui/tabs/plot.rs +++ b/src/gui/tabs/plot.rs @@ -1,11 +1,6 @@ use std::cell::RefCell; use std::rc::Rc; -#[cfg(not(target_arch = "wasm32"))] -use std::time::Instant; -#[cfg(target_arch = "wasm32")] -use web_time::Instant; - use egui::Color32; use egui::Rect; use egui::Vec2; @@ -17,7 +12,6 @@ use nalgebra::Vector3; use crate::data_source::DataSource; use crate::settings::AppSettings; -use crate::state::VehicleState; use crate::gui::map::*; use crate::gui::maxi_grid::*; @@ -185,39 +179,6 @@ impl PlotTab { } } - fn all_plots(&mut self, f: impl FnOnce(&mut PlotState) + Copy) { - for plot in [ - &mut self.orientation_plot, - &mut self.vertical_speed_plot, - &mut self.altitude_plot, - &mut self.gyroscope_plot, - &mut self.accelerometer_plot, - &mut self.magnetometer_plot, - &mut self.barometer_plot, - &mut self.temperature_plot, - &mut self.power_plot, - &mut self.runtime_plot, - &mut self.signal_plot, - ] { - (f)(plot); - } - } - - pub fn reset(&mut self, keep_position: bool) { - let now = Instant::now(); - self.all_plots(|plot| plot.reset(now, keep_position)); - self.map.reset(); - } - - pub fn show_all(&mut self) { - self.all_plots(|plot| plot.show_all()); - } - - pub fn push_vehicle_state(&mut self, time: &Instant, vs: &VehicleState) { - self.all_plots(|plot| plot.push(*time, vs)); - self.map.push(*time, vs); - } - fn plot_gizmo( &mut self, ui: &mut egui::Ui, @@ -278,7 +239,7 @@ impl PlotTab { .rev() .find_map(|(_, vs)| vs.true_orientation); - ui.plot_telemetry(&self.orientation_plot); + ui.plot_telemetry(&self.orientation_plot, data_source); if let Some(orientation) = true_orientation { self.plot_gizmo(ui, viewport, orientation, (R1, G1, B1)); @@ -293,17 +254,17 @@ impl PlotTab { if ui.available_width() > 1000.0 { MaxiGrid::new((4, 3), ui, self.maxi_grid_state.clone()) .cell("Orientation", |ui| self.plot_orientation(ui, data_source)) - .cell("Vert. Speed & Accel", |ui| ui.plot_telemetry(&self.vertical_speed_plot)) - .cell("Altitude (ASL)", |ui| ui.plot_telemetry(&self.altitude_plot)) - .cell("Position", |ui| ui.map(&self.map)) - .cell("Gyroscope", |ui| ui.plot_telemetry(&self.gyroscope_plot)) - .cell("Accelerometers", |ui| ui.plot_telemetry(&self.accelerometer_plot)) - .cell("Magnetometer", |ui| ui.plot_telemetry(&self.magnetometer_plot)) - .cell("Pressures", |ui| ui.plot_telemetry(&self.barometer_plot)) - .cell("Temperature", |ui| ui.plot_telemetry(&self.temperature_plot)) - .cell("Power", |ui| ui.plot_telemetry(&self.power_plot)) - .cell("Runtime", |ui| ui.plot_telemetry(&self.runtime_plot)) - .cell("Signal", |ui| ui.plot_telemetry(&self.signal_plot)); + .cell("Vert. Speed & Accel", |ui| ui.plot_telemetry(&self.vertical_speed_plot, data_source)) + .cell("Altitude (ASL)", |ui| ui.plot_telemetry(&self.altitude_plot, data_source)) + .cell("Position", |ui| ui.map(&self.map, data_source)) + .cell("Gyroscope", |ui| ui.plot_telemetry(&self.gyroscope_plot, data_source)) + .cell("Accelerometers", |ui| ui.plot_telemetry(&self.accelerometer_plot, data_source)) + .cell("Magnetometer", |ui| ui.plot_telemetry(&self.magnetometer_plot, data_source)) + .cell("Pressures", |ui| ui.plot_telemetry(&self.barometer_plot, data_source)) + .cell("Temperature", |ui| ui.plot_telemetry(&self.temperature_plot, data_source)) + .cell("Power", |ui| ui.plot_telemetry(&self.power_plot, data_source)) + .cell("Runtime", |ui| ui.plot_telemetry(&self.runtime_plot, data_source)) + .cell("Signal", |ui| ui.plot_telemetry(&self.signal_plot, data_source)); } else { ui.vertical(|ui| { ui.horizontal(|ui| { @@ -333,17 +294,17 @@ impl PlotTab { match self.dropdown_selected_plot { SelectedPlot::Orientation => self.plot_orientation(ui, data_source), - SelectedPlot::VerticalSpeed => ui.plot_telemetry(&self.vertical_speed_plot), - SelectedPlot::Altitude => ui.plot_telemetry(&self.altitude_plot), - SelectedPlot::Gyroscope => ui.plot_telemetry(&self.gyroscope_plot), - SelectedPlot::Accelerometers => ui.plot_telemetry(&self.accelerometer_plot), - SelectedPlot::Magnetometer => ui.plot_telemetry(&self.magnetometer_plot), - SelectedPlot::Pressures => ui.plot_telemetry(&self.barometer_plot), - SelectedPlot::Temperatures => ui.plot_telemetry(&self.temperature_plot), - SelectedPlot::Power => ui.plot_telemetry(&self.power_plot), - SelectedPlot::Runtime => ui.plot_telemetry(&self.runtime_plot), - SelectedPlot::Signal => ui.plot_telemetry(&self.signal_plot), - SelectedPlot::Map => ui.map(&self.map), + SelectedPlot::VerticalSpeed => ui.plot_telemetry(&self.vertical_speed_plot, data_source), + SelectedPlot::Altitude => ui.plot_telemetry(&self.altitude_plot, data_source), + SelectedPlot::Gyroscope => ui.plot_telemetry(&self.gyroscope_plot, data_source), + SelectedPlot::Accelerometers => ui.plot_telemetry(&self.accelerometer_plot, data_source), + SelectedPlot::Magnetometer => ui.plot_telemetry(&self.magnetometer_plot, data_source), + SelectedPlot::Pressures => ui.plot_telemetry(&self.barometer_plot, data_source), + SelectedPlot::Temperatures => ui.plot_telemetry(&self.temperature_plot, data_source), + SelectedPlot::Power => ui.plot_telemetry(&self.power_plot, data_source), + SelectedPlot::Runtime => ui.plot_telemetry(&self.runtime_plot, data_source), + SelectedPlot::Signal => ui.plot_telemetry(&self.signal_plot, data_source), + SelectedPlot::Map => ui.map(&self.map, data_source), } }); }