From bd3c5b2c2018941cc536cf89bd0e7c67c218a186 Mon Sep 17 00:00:00 2001 From: Felix Wiegand Date: Tue, 21 Nov 2023 00:51:20 +0100 Subject: [PATCH] Move all archive loading functionality into separate struct --- src/gui.rs | 134 +++---------------------- src/gui/panels/menu_bar.rs | 6 +- src/gui/windows/archive.rs | 194 +++++++++++++++++++++++++++++-------- src/gui/windows/mod.rs | 2 + 4 files changed, 172 insertions(+), 164 deletions(-) diff --git a/src/gui.rs b/src/gui.rs index f1a08d1..a789401 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -1,22 +1,12 @@ //! Main GUI code -use std::io::Write; use std::path::PathBuf; -use std::sync::mpsc::{Receiver, Sender}; - -#[cfg(not(target_arch = "wasm32"))] -use std::time::Instant; -#[cfg(target_arch = "wasm32")] -use web_time::Instant; use eframe::egui; use egui::FontFamily::Proportional; use egui::TextStyle::*; use egui::{Align, Color32, FontFamily, FontId, Key, Layout, Modifiers, Vec2}; -use futures::StreamExt; -use log::*; - use mithril::telemetry::*; mod panels; @@ -35,29 +25,17 @@ use crate::data_source::*; use crate::gui::panels::*; use crate::gui::tabs::*; use crate::gui::theme::*; -use crate::gui::windows::archive::open_archive_window; +use crate::gui::windows::*; use crate::settings::AppSettings; -#[derive(Debug)] -enum ArchiveLoadProgress { - Progress((u64, u64)), - Complete(Vec), - Error(reqwest::Error), -} - // The main state object of our GUI application pub struct Sam { settings: AppSettings, data_source: Box, tab: GuiTab, - plot_tab: PlotTab, configure_tab: ConfigureTab, - - archive_window_open: bool, - replay_logs: bool, - archive_progress_receiver: Option>, - archive_progress: Option<(u64, u64)>, + archive_window: ArchiveWindow, } impl Sam { @@ -87,72 +65,8 @@ impl Sam { plot_tab, configure_tab, - archive_window_open: cfg!(target_arch = "wasm32"), - replay_logs: false, - archive_progress_receiver: None, - archive_progress: None, - } - } - - /// Opens a log file data source - fn open_log_file(&mut self, ds: LogFileDataSource) { - self.data_source = Box::new(ds); - } - - async fn load_archive_log(url: &str, progress_sender: Sender) { - let start = Instant::now(); - let response = match reqwest::Client::new().get(url).send().await { - Ok(res) => res, - Err(e) => { - progress_sender.send(ArchiveLoadProgress::Error(e)).unwrap(); - return; - } - }; - - let total_size = response.content_length().unwrap_or(0); - progress_sender.send(ArchiveLoadProgress::Progress((0, total_size))).unwrap(); - - let mut cursor = std::io::Cursor::new(Vec::with_capacity(total_size as usize)); - let (mut progress, mut last_progress) = (0, 0); - let mut stream = response.bytes_stream(); - while let Some(result) = stream.next().await { - match result { - Ok(chunk) => { - cursor.write_all(&chunk).unwrap(); - progress = u64::min(progress + chunk.len() as u64, total_size); - if progress == total_size || progress > last_progress + 256 * 1024 { - let _ = progress_sender.send(ArchiveLoadProgress::Progress((progress, total_size))); - last_progress = progress; - } - } - Err(e) => { - progress_sender.send(ArchiveLoadProgress::Error(e)).unwrap(); - return; - } - } + archive_window: ArchiveWindow::default(), } - - progress_sender.send(ArchiveLoadProgress::Complete(cursor.into_inner())).unwrap(); - let duration = start.elapsed().as_secs_f32(); - let mib = (total_size as f32) / 1024.0 / 1024.0; - info!("Downloaded {}MiB in {:.1}ms ({}MiB/s)", mib, duration * 1000.0, mib / duration); - } - - #[cfg(not(target_arch = "wasm32"))] - fn open_archive_log(&mut self, url: &'static str) { - let (sender, receiver) = std::sync::mpsc::channel(); - self.archive_progress_receiver = Some(receiver); - std::thread::spawn(move || { - let rt = tokio::runtime::Builder::new_current_thread().enable_io().enable_time().build().unwrap(); - rt.block_on(Self::load_archive_log(url, sender)); - }); - } - - #[cfg(target_arch = "wasm32")] - fn open_archive_log(&mut self, url: &'static str) { - let (sender, receiver) = std::sync::mpsc::channel(); - self.archive_progress_receiver = Some(receiver); - wasm_bindgen_futures::spawn_local(Self::load_archive_log(url, sender)); } /// Closes the currently opened data source @@ -175,30 +89,6 @@ impl Sam { self.data_source.send(UplinkMessage::ReadSettings).unwrap(); } - if let Some(receiver) = self.archive_progress_receiver.as_ref() { - match receiver.try_recv() { - Ok(ArchiveLoadProgress::Progress(progress)) => { - self.archive_progress = Some(progress); - } - Ok(ArchiveLoadProgress::Complete(bytes)) => { - self.open_log_file(LogFileDataSource::from_bytes( - Some("".to_string()), // TODO: show title - bytes, - self.replay_logs, - )); - self.archive_window_open = false; - self.archive_progress_receiver = None; - self.archive_progress = None; - } - Ok(ArchiveLoadProgress::Error(e)) => { - error!("{:?}", e); // TODO: show this visually - self.archive_progress_receiver = None; - self.archive_progress = None; - } - _ => {} - } - } - // Check for keyboard inputs for tab and flight mode changes if ctx.input_mut(|i| i.consume_key(Modifiers::NONE, Key::F1)) { self.tab = GuiTab::Launch; @@ -245,25 +135,25 @@ impl Sam { } // A window to open archived logs directly in the application - let mut archive_open = self.archive_window_open; // necessary to avoid mutably borrowing self - open_archive_window(ctx, &mut archive_open, self); - self.archive_window_open = archive_open; + if let Some(log) = self.archive_window.show_if_open(ctx) { + self.data_source = Box::new(log); + } // Top menu bar // TODO: avoid passing in self here - MenuBarPanel::show(ctx, self, !self.archive_window_open); + MenuBarPanel::show(ctx, self, !self.archive_window.open); // If our current data source is a simulation, show a config panel to the left if let Some(sim) = self.data_source.as_any_mut().downcast_mut::() { - SimulationPanel::show(ctx, sim, !self.archive_window_open); + SimulationPanel::show(ctx, sim, !self.archive_window.open); } // Header containing text indicators and flight mode buttons - HeaderPanel::show(ctx, self.data_source.as_mut(), !self.archive_window_open); + HeaderPanel::show(ctx, self.data_source.as_mut(), !self.archive_window.open); // Bottom status bar egui::TopBottomPanel::bottom("bottombar").min_height(30.0).show(ctx, |ui| { - ui.set_enabled(!self.archive_window_open); + ui.set_enabled(!self.archive_window.open); ui.horizontal_centered(|ui| { // Give the data source some space on the left ... ui.horizontal_centered(|ui| { @@ -284,7 +174,7 @@ impl Sam { // Everything else. This has to be called after all the other panels are created. egui::CentralPanel::default().show(ctx, |ui| { - ui.set_enabled(!self.archive_window_open); + ui.set_enabled(!self.archive_window.open); match self.tab { GuiTab::Launch => {} GuiTab::Plot => self.plot_tab.main_ui(ui, self.data_source.as_mut()), @@ -300,7 +190,7 @@ impl Sam { // If we have live data coming in, we need to tell egui to repaint. // If we don't, we shouldn't. - if let Some(fps) = self.data_source.minimum_fps().or(self.archive_window_open.then(|| 60)) { + if let Some(fps) = self.data_source.minimum_fps().or(self.archive_window.open.then(|| 60)) { let t = std::time::Duration::from_millis(1000 / fps); ctx.request_repaint_after(t); } diff --git a/src/gui/panels/menu_bar.rs b/src/gui/panels/menu_bar.rs index 97191df..8e67717 100644 --- a/src/gui/panels/menu_bar.rs +++ b/src/gui/panels/menu_bar.rs @@ -1,5 +1,5 @@ use crate::data_source::{SimulationDataSource, LogFileDataSource}; -use crate::file::open_log_file; +use crate::file::*; use crate::gui::tabs::GuiTab; use crate::gui::Sam; @@ -38,12 +38,12 @@ impl MenuBarPanel { #[cfg(target_arch = "x86_64")] if ui.selectable_label(false, "🗁 Open Log File").clicked() { if let Some(data_source) = open_log_file() { - sam.open_log_file(data_source); + sam.data_source = Box::new(data_source); } } // Toggle archive panel - ui.toggle_value(&mut sam.archive_window_open, "🗄 Flight Archive"); + ui.toggle_value(&mut sam.archive_window.open, "🗄 Flight Archive"); // Toggle archive panel if ui.selectable_label(data_source_is_sim, "💻 Simulate").clicked() { diff --git a/src/gui/windows/archive.rs b/src/gui/windows/archive.rs index 9477b27..d44b3d2 100644 --- a/src/gui/windows/archive.rs +++ b/src/gui/windows/archive.rs @@ -1,7 +1,19 @@ -use crate::gui::Sam; +use std::io::Write; +use std::sync::mpsc::{Receiver, Sender}; + +#[cfg(not(target_arch = "wasm32"))] +use std::time::Instant; +#[cfg(target_arch = "wasm32")] +use web_time::Instant; + use eframe::egui; use egui::{Align, Align2, Button, Layout, ProgressBar}; +use futures::StreamExt; +use log::*; + +use crate::data_source::LogFileDataSource; + // Log files included with the application. // TODO: migrate old launches pub const ARCHIVE: [(&str, Option<&'static str>, Option<&'static str>); 5] = [ @@ -24,50 +36,154 @@ pub const ARCHIVE: [(&str, Option<&'static str>, Option<&'static str>); 5] = [ ), ]; -pub fn open_archive_window(ctx: &egui::Context, archive_open: &mut bool, sam: &mut Sam) { - egui::Window::new("Flight Archive") - .open(archive_open) - .min_width(300.0) - .anchor(Align2::CENTER_CENTER, [0.0, 0.0]) - .resizable(false) - .collapsible(false) - .show(ctx, |ui| { - ui.add_space(10.0); - - for (i, (title, telem, flash)) in ARCHIVE.iter().enumerate() { - if i != 0 { - ui.separator(); +#[derive(Debug)] +enum ArchiveLoadProgress { Progress((u64, u64)), + Complete(Vec), + Error(reqwest::Error), +} + +#[derive(Default)] +pub struct ArchiveWindow { + pub open: bool, + replay_logs: bool, + progress_receiver: Option>, + progress: Option<(u64, u64)>, +} + +impl ArchiveWindow { + async fn load_log(url: &str, progress_sender: Sender) { + let start = Instant::now(); + let response = match reqwest::Client::new().get(url).send().await { + Ok(res) => res, + Err(e) => { + progress_sender.send(ArchiveLoadProgress::Error(e)).unwrap(); + return; + } + }; + + let total_size = response.content_length().unwrap_or(0); + progress_sender.send(ArchiveLoadProgress::Progress((0, total_size))).unwrap(); + + let mut cursor = std::io::Cursor::new(Vec::with_capacity(total_size as usize)); + let (mut progress, mut last_progress) = (0, 0); + let mut stream = response.bytes_stream(); + while let Some(result) = stream.next().await { + match result { + Ok(chunk) => { + cursor.write_all(&chunk).unwrap(); + progress = u64::min(progress + chunk.len() as u64, total_size); + if progress == total_size || progress > last_progress + 256 * 1024 { + let _ = progress_sender.send(ArchiveLoadProgress::Progress((progress, total_size))); + last_progress = progress; + } } + Err(e) => { + progress_sender.send(ArchiveLoadProgress::Error(e)).unwrap(); + return; + } + } + } + + progress_sender.send(ArchiveLoadProgress::Complete(cursor.into_inner())).unwrap(); + let duration = start.elapsed().as_secs_f32(); + let mib = (total_size as f32) / 1024.0 / 1024.0; + info!("Downloaded {}MiB in {:.1}ms ({}MiB/s)", mib, duration * 1000.0, mib / duration); + } + + #[cfg(not(target_arch = "wasm32"))] + fn open_log(&mut self, url: &'static str) { + let (sender, receiver) = std::sync::mpsc::channel(); + self.progress_receiver = Some(receiver); + std::thread::spawn(move || { + let rt = tokio::runtime::Builder::new_current_thread().enable_io().enable_time().build().unwrap(); + rt.block_on(Self::load_log(url, sender)); + }); + } + + #[cfg(target_arch = "wasm32")] + fn open_log(&mut self, url: &'static str) { + let (sender, receiver) = std::sync::mpsc::channel(); + self.progress_receiver = Some(receiver); + wasm_bindgen_futures::spawn_local(Self::load_log(url, sender)); + } + pub fn show_if_open(&mut self, ctx: &egui::Context) -> Option { + if let Some(receiver) = self.progress_receiver.as_ref() { + match receiver.try_recv() { + Ok(ArchiveLoadProgress::Progress(progress)) => { + self.progress = Some(progress); + } + Ok(ArchiveLoadProgress::Complete(bytes)) => { + // TODO + self.open = false; + self.progress_receiver = None; + self.progress = None; + return Some(LogFileDataSource::from_bytes( + Some("".to_string()), // TODO: show title + bytes, + self.replay_logs, + )); + } + Ok(ArchiveLoadProgress::Error(e)) => { + error!("{:?}", e); // TODO: show this visually + self.progress_receiver = None; + self.progress = None; + } + _ => {} + } + } + + // avoids mutably borrowing self + let mut open = self.open; + + egui::Window::new("Flight Archive") + .open(&mut open) + .min_width(300.0) + .anchor(Align2::CENTER_CENTER, [0.0, 0.0]) + .resizable(false) + .collapsible(false) + .show(ctx, |ui| { + ui.add_space(10.0); + + for (i, (title, telem, flash)) in ARCHIVE.iter().enumerate() { + if i != 0 { + ui.separator(); + } + + ui.horizontal(|ui| { + ui.label(*title); + ui.with_layout(Layout::right_to_left(Align::Center), |ui| { + if ui.add_enabled(flash.is_some(), Button::new("🖴 Flash")).clicked() { + self.open_log(flash.unwrap()); + } + + if ui.add_enabled(telem.is_some(), Button::new("📡 Telemetry")).clicked() { + self.open_log(telem.unwrap()); + } + }); + }); + } + + ui.add_space(10.0); ui.horizontal(|ui| { - ui.label(*title); - ui.with_layout(Layout::right_to_left(Align::Center), |ui| { - if ui.add_enabled(flash.is_some(), Button::new("🖴 Flash")).clicked() { - sam.open_archive_log(flash.unwrap()); - } - - if ui.add_enabled(telem.is_some(), Button::new("📡 Telemetry")).clicked() { - sam.open_archive_log(telem.unwrap()); - } + ui.add_visible_ui(self.progress.is_some(), |ui| { + let (done, total) = self.progress.unwrap_or((0, 0)); + let f = (total > 0).then(|| done as f32 / total as f32).unwrap_or(0.0); + let text = format!( + "{:.2}MiB / {:.2}MiB", + done as f32 / (1024.0 * 1024.0), + total as f32 / (1024.0 * 1024.0) + ); + ui.add_sized([ui.available_width(), 20.0], ProgressBar::new(f).text(text)); }); }); - } + ui.add_space(10.0); - ui.add_space(10.0); - ui.horizontal(|ui| { - ui.add_visible_ui(sam.archive_progress.is_some(), |ui| { - let (done, total) = sam.archive_progress.unwrap_or((0, 0)); - let f = (total > 0).then(|| done as f32 / total as f32).unwrap_or(0.0); - let text = format!( - "{:.2}MiB / {:.2}MiB", - done as f32 / (1024.0 * 1024.0), - total as f32 / (1024.0 * 1024.0) - ); - ui.add_sized([ui.available_width(), 20.0], ProgressBar::new(f).text(text)); - }); + ui.checkbox(&mut self.replay_logs, "Replay logs"); }); - ui.add_space(10.0); - ui.checkbox(&mut sam.replay_logs, "Replay logs"); - }); + self.open = open; + + None + } } diff --git a/src/gui/windows/mod.rs b/src/gui/windows/mod.rs index 4193e3e..7953464 100644 --- a/src/gui/windows/mod.rs +++ b/src/gui/windows/mod.rs @@ -1 +1,3 @@ pub mod archive; + +pub use archive::*;