Skip to content

Commit

Permalink
Refactor plots and map
Browse files Browse the repository at this point in the history
They now handle their own cache, and no longer need to be notified of
new states and resets.
  • Loading branch information
KoffeinFlummi committed Nov 4, 2023
1 parent 0c0cf17 commit 2581ed0
Show file tree
Hide file tree
Showing 9 changed files with 226 additions and 235 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@
!archive/*.kml
*.exe
*.bin
perf.data
flamegraph.svg
6 changes: 3 additions & 3 deletions src/data_source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>;
Expand Down
7 changes: 2 additions & 5 deletions src/data_source/log_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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)
Expand All @@ -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()
}

Expand Down
8 changes: 2 additions & 6 deletions src/data_source/serial.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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);

Expand All @@ -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()
}

Expand Down
7 changes: 2 additions & 5 deletions src/data_source/simulation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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()
}

Expand Down
21 changes: 3 additions & 18 deletions src/gui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down Expand Up @@ -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()));
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -450,7 +439,7 @@ impl Sam {
}

if ui.button("↻ Rerun").clicked() || (changed && released) {
self.reset(true);
self.data_source.reset();
}
});
});
Expand Down Expand Up @@ -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();
}
});

Expand Down Expand Up @@ -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() {
Expand Down
137 changes: 89 additions & 48 deletions src/gui/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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!(
Expand Down Expand Up @@ -139,13 +141,14 @@ pub struct MapCache {
max_alt: f64,
pub center: (f64, f64),
pub hdop_circle_points: Option<Vec<[f64; 2]>>,
cached_state: Option<(Instant, usize)>,
gradient_lookup: Vec<Color32>,
}

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();

Expand All @@ -155,72 +158,118 @@ 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
let points = (0..=64)
.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<Line> {
fn lines<'a>(&'a mut self, data_source: &dyn DataSource) -> Vec<Line> {
#[cfg(feature = "profiling")]
puffin::profile_function!();

self.update_cache_if_necessary(data_source);
self.plot_points
.clone()
.into_iter()
.map(|(pp, color)| Line::new(PlotPoints::from_iter(pp.into_iter())).width(2.0).color(color.clone()))
.collect()
}

pub fn hdop_circle_line(&self) -> Option<Line> {
pub fn hdop_circle_line(&mut self, data_source: &dyn DataSource) -> Option<Line> {
#[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))
Expand Down Expand Up @@ -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")
Expand All @@ -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");

Expand Down
Loading

0 comments on commit 2581ed0

Please sign in to comment.