diff --git a/Cargo.lock b/Cargo.lock index 59b7831..3810736 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -887,6 +887,25 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.19" @@ -938,9 +957,9 @@ checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" [[package]] name = "dive-deco" -version = "2.0.0" +version = "4.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e8c64f545ca112878832dc208d5b2a36ac96facd529084c63beb9cb31e1a41a" +checksum = "bf5282e7801c1357fbf72a6cc4ba0772aad80b0cf057ac690f113017f198fe22" [[package]] name = "dive-reporter" @@ -951,6 +970,7 @@ dependencies = [ "eframe", "futures", "quick-xml", + "rayon", "rfd", "serde", ] @@ -1086,6 +1106,12 @@ dependencies = [ "winit", ] +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + [[package]] name = "emath" version = "0.27.2" @@ -2403,6 +2429,26 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8cc3bcbdb1ddfc11e700e62968e6b4cc9c75bb466464ad28fb61c5b2c964418b" +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "redox_syscall" version = "0.3.5" diff --git a/Cargo.toml b/Cargo.toml index 6f83fb1..9021a00 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,9 +10,10 @@ repository = "https://github.com/KG32/dive-reporter.git" [dependencies] colored = "2.1.0" -dive-deco = "2.0.0" +dive-deco = "4.3.4" eframe = "0.27.2" futures = "0.3.30" -quick-xml = {version = "0.31.0", features = ["serialize"] } +quick-xml = { version = "0.31.0", features = ["serialize"] } +rayon = "1.10.0" rfd = "0.14.1" serde = { version = "1.0", features = ["derive"] } diff --git a/src/app.rs b/src/app.rs index b655e83..abeceb3 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,10 +1,13 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release +use crate::{ + dive, + stats::{Stats, StatsData, StatsOutput}, +}; use eframe::egui::{self, InnerResponse, Ui}; -use std::{future::Future, path::PathBuf}; -use std::error::Error; use rfd::FileDialog; -use crate::{dive, stats::{Stats, StatsOutput}}; +use std::{error::Error, sync::Arc}; +use std::{future::Future, path::PathBuf}; #[derive(Clone)] pub struct App { @@ -17,7 +20,7 @@ pub struct App { #[derive(Clone)] struct AppState { - error: Option + error: Option, } #[derive(Clone)] @@ -39,11 +42,9 @@ impl Default for App { stats_output: vec![], config: AppConfig { path: None, - gradient_factors: (30, 70) + gradient_factors: (30, 70), }, - state: AppState { - error: None, - } + state: AppState { error: None }, } } } @@ -55,7 +56,11 @@ impl eframe::App for App { ui.separator(); // config - self.render_pair(ui, "Path:", &self.config.path.clone().unwrap_or("-".to_string())); + self.render_pair( + ui, + "Path:", + &self.config.path.clone().unwrap_or("-".to_string()), + ); ui.separator(); // open file btn @@ -66,12 +71,15 @@ impl eframe::App for App { // stats container match &self.state.error { None => { - let stats = self.stats.clone(); + // let stats = self.stats.clone(); + let stats_arc = Arc::clone(&self.stats.stats_data); + let stats_guard = stats_arc.lock().unwrap(); + let stats = stats_guard; if stats.dives_no > 0 { self.state.error = None; - self.render_stats(ui, &stats) + self.render_stats(ui, &*stats) } - }, + } Some(err) => { self.render_error(ui, &err); } @@ -91,9 +99,7 @@ impl App { eframe::run_native( "Dive reporter", options, - Box::new(|_cc| { - Box::::default() - }), + Box::new(|_cc| Box::::default()), ) } @@ -118,11 +124,10 @@ impl App { self.run_stats(&dir); } } - } } - fn render_stats(&mut self, ui: &mut Ui, stats: &Stats) { + fn render_stats(&mut self, ui: &mut Ui, stats: &StatsData) { let depth_max = stats.depth_max.to_string(); let gf_surf_max = stats.gf_surf_max.round().to_string(); let gf_99_max = stats.gf_99_max.round().to_string(); @@ -130,10 +135,18 @@ impl App { ui.vertical(|ui| { self.render_pair(ui, "Dives:", &stats.dives_no.to_string()); - self.render_pair(ui, "Total time:", &Stats::seconds_to_readable(stats.total_time)); + self.render_pair( + ui, + "Total time:", + &Stats::seconds_to_readable(stats.total_time), + ); self.render_pair(ui, "Max depth", &format!("{depth_max}m")); self.render_pair(ui, "Deco dives:", &stats.deco_dives_no.to_string()); - self.render_pair(ui, "Total time in deco:", &Stats::seconds_to_readable(stats.time_in_deco)); + self.render_pair( + ui, + "Total time in deco:", + &Stats::seconds_to_readable(stats.time_in_deco), + ); self.render_pair(ui, "Max surface GF:", &format!("{gf_surf_max}%")); self.render_pair(ui, "Max GF99:", &format!("{gf_99_max}%")); self.render_pair(ui, "Max end GF:", &format!("{gf_end_max}%")); @@ -141,7 +154,11 @@ impl App { for record in stats.time_below.iter() { let (depth, time) = record; ui.indent("", |ui| { - self.render_pair(ui, &format!("-{depth}:"), &Stats::seconds_to_readable(*time)); + self.render_pair( + ui, + &format!("-{depth}:"), + &Stats::seconds_to_readable(*time), + ); }); } }); @@ -177,10 +194,10 @@ impl App { self.state.error = None; } self.stats = stats - }, + } Err(err) => { let app_err = AppError { - text: err.to_string() + text: err.to_string(), }; self.state.error = Some(app_err); } diff --git a/src/dive.rs b/src/dive.rs index 1458482..2e411f7 100644 --- a/src/dive.rs +++ b/src/dive.rs @@ -3,7 +3,11 @@ use dive_deco::{BuehlmannConfig, BuehlmannModel, DecoModel, Gas, Pressure, Super use crate::common::{GradientFactorsSetting, GF}; use crate::parser::WaypointElem; use crate::stats::TimeBelowDepthData; -use crate::{common::{Depth, Seconds}, parser::DiveElem, stats::GasMixesData}; +use crate::{ + common::{Depth, Seconds}, + parser::DiveElem, + stats::GasMixesData, +}; #[derive(Debug)] pub struct DiveMeta { @@ -52,17 +56,16 @@ impl Dive { pub fn calc_dive_stats(&mut self, dive_data: &DiveElem, gas_mixes: &GasMixesData) { let (gf_lo, gf_hi) = self.meta.gradient_factors; - let mut model = BuehlmannModel::new(BuehlmannConfig::new().gradient_factors(gf_lo, gf_hi)); + let mut model = BuehlmannModel::new( + BuehlmannConfig::new() + .with_gradient_factors(gf_lo, gf_hi) + .with_ceiling_type(dive_deco::CeilingType::Adaptive), + ); // calc by data point let mut last_waypoint_time: Seconds = 0; let dive_data_points = &dive_data.samples.waypoints; for data_point in dive_data_points { - self.process_data_point( - &mut model, - &data_point, - last_waypoint_time, - &gas_mixes, - ); + self.process_data_point(&mut model, &data_point, last_waypoint_time, &gas_mixes); // update last waypoint time last_waypoint_time = data_point.dive_time; } @@ -91,13 +94,13 @@ impl Dive { match gas { Some(gas) => { self.meta.current_mix = gas; - }, + } None => { panic!("Gas not found"); } } - }, - None => () + } + None => (), } // deco model step @@ -160,7 +163,7 @@ impl Dive { gas = Some(Gas::new(mix_definition.o2, mix_definition.he.unwrap_or(0.))); } } - }, + } None => { panic!("No gas mixes, can't process switch"); } @@ -175,5 +178,4 @@ impl Dive { } time_below } - } diff --git a/src/stats.rs b/src/stats.rs index 7eaf0fe..eebce5a 100644 --- a/src/stats.rs +++ b/src/stats.rs @@ -1,11 +1,13 @@ -use std::{error::Error, fs, path::PathBuf, fmt::format}; use crate::common::{Depth, Seconds, GF}; -use crate::parser::{self, UDDFDoc, Mix, DiveElem, WaypointElem}; use crate::dive::{Dive, DiveConfig}; +use crate::parser::{self, DiveElem, Mix, UDDFDoc, WaypointElem}; use colored::*; +use rayon::prelude::*; +use std::sync::{Arc, Mutex}; +use std::{error::Error, fmt::format, fs, path::PathBuf}; -#[derive(Clone, Debug)] -pub struct Stats { +#[derive(Clone, Debug, Default)] +pub struct StatsData { pub dives_no: usize, pub total_time: Seconds, pub depth_max: Depth, @@ -17,6 +19,11 @@ pub struct Stats { pub time_below: TimeBelowDepthData, } +#[derive(Clone, Debug)] +pub struct Stats { + pub stats_data: Arc>, +} + pub type StatsOutput = Vec<(String, String)>; pub type TimeBelowDepthData = Vec<(Depth, Seconds)>; @@ -31,15 +38,7 @@ pub struct UDDFData { impl Stats { pub fn new() -> Self { Self { - dives_no: 0, - total_time: 0, - depth_max: 0.0, - deco_dives_no: 0, - time_in_deco: 0, - gf_surf_max: 0., - gf_99_max: 0., - gf_end_max: 0., - time_below: vec![], + stats_data: Arc::new(Mutex::new(StatsData::default())), } } @@ -53,31 +52,41 @@ impl Stats { println!("Directory: {}", path); stats.from_dir(path)?; } else { - return Err("Unable to resolve file or directory".into()) + return Err("Unable to resolve file or directory".into()); } stats.print_to_console(); Ok(stats) } fn from_file(&mut self, path: &str) -> Result<(), Box> { - let UDDFData { dives_data, gas_mixes } = self.extract_data_from_file(path)?; - for dive_data in dives_data { - let dive = self.calc_dive_stats(&dive_data, &gas_mixes)?; + let UDDFData { + dives_data, + gas_mixes, + } = self.extract_data_from_file(path)?; + dives_data.par_iter().for_each(|dd| { + let dive = self.calc_dive_stats(&dd, &gas_mixes).unwrap(); self.update_with_dive_data(dive); - } + }); Ok(()) } - fn from_dir(&mut self, path: &str) -> Result, Box> { + fn from_dir(&mut self, path: &str) -> Result<(), Box> { let paths = Self::traverse_for_uddf(path)?; - for path in &paths { - self.from_file(&path.to_str().unwrap()); - } - Ok(paths) + paths.par_iter().for_each(|path| { + let UDDFData { + dives_data, + gas_mixes, + } = self.extract_data_from_file(path.to_str().unwrap()).unwrap(); + dives_data.par_iter().for_each(|dd| { + let dive = self.calc_dive_stats(&dd, &gas_mixes).unwrap(); + self.update_with_dive_data(dive); + }); + }); + Ok(()) } fn traverse_for_uddf(path: &str) -> Result, Box> { - let mut uddf_file_paths:Vec = vec![]; + let mut uddf_file_paths: Vec = vec![]; let entries = fs::read_dir(path)?; for entry in entries { let entry = entry?; @@ -101,9 +110,7 @@ impl Stats { let gas_definitions = file.gas_definitions; let mut dives: Vec = vec![]; - let repetition_groups = file - .profile_data - .repetition_group; + let repetition_groups = file.profile_data.repetition_group; for mut group in repetition_groups { dives.append(&mut group.dives); } @@ -114,7 +121,11 @@ impl Stats { }) } - fn calc_dive_stats(&self, dive_data: &DiveElem, gas_mixes: &GasMixesData) -> Result> { + fn calc_dive_stats( + &self, + dive_data: &DiveElem, + gas_mixes: &GasMixesData, + ) -> Result> { // todo: set gradient factors from dive data with default fallback let tmp_init_gf = (30, 70); let tmp_treshold_depths: Vec = vec![10., 20., 30., 40.]; @@ -126,66 +137,103 @@ impl Stats { Ok(dive) } - fn update_with_dive_data(&mut self, dive: Dive) { + fn update_with_dive_data(&self, dive: Dive) { + let stats_data_arc = Arc::clone(&self.stats_data); + let mut stats_data = stats_data_arc.lock().unwrap(); + // dives no - self.dives_no += 1; + stats_data.dives_no += 1; // time - self.total_time += dive.total_time; + stats_data.total_time += dive.total_time; // depth - if dive.depth_max > self.depth_max { - self.depth_max = dive.depth_max; + if dive.depth_max > stats_data.depth_max { + stats_data.depth_max = dive.depth_max; } // time in deco if dive.time_in_deco > 0 { - self.time_in_deco += dive.time_in_deco; - self.deco_dives_no += 1; + stats_data.time_in_deco += dive.time_in_deco; + stats_data.deco_dives_no += 1; } // GFs - if dive.gf_surf_max > self.gf_surf_max { - self.gf_surf_max = dive.gf_surf_max; + if dive.gf_surf_max > stats_data.gf_surf_max { + stats_data.gf_surf_max = dive.gf_surf_max; } - if dive.gf_99_max > self.gf_99_max { - self.gf_99_max = dive.gf_99_max; + if dive.gf_99_max > stats_data.gf_99_max { + stats_data.gf_99_max = dive.gf_99_max; } - if dive.gf_end > self.gf_end_max { - self.gf_end_max = dive.gf_end; + if dive.gf_end > stats_data.gf_end_max { + stats_data.gf_end_max = dive.gf_end; } // time below 'outer: for dive_time_below in dive.time_below { let (dive_treshold_depth, dive_treshold_time) = dive_time_below; - for global_time_below in &mut self.time_below { + for global_time_below in &mut stats_data.time_below { let (global_treshold_depth, global_treshold_time) = global_time_below; if dive_treshold_depth == *global_treshold_depth { global_time_below.1 += dive_treshold_time; continue 'outer; } } - self.time_below.push((dive_treshold_depth, dive_treshold_time)); + stats_data + .time_below + .push((dive_treshold_depth, dive_treshold_time)); } } pub fn print_to_console(&self) { + let stats_data_arc = Arc::clone(&self.stats_data); + let stats = stats_data_arc.lock().unwrap(); + println!("{}", "\n STATS ".underline()); - println!("Dives: {}", Self::to_colored(self.dives_no)); - println!("Total time: {}", Self::to_colored(Self::seconds_to_readable(self.total_time))); - println!("Max depth: {}{}", Self::to_colored(self.depth_max), Self::to_colored("m")); - println!("Deco dives: {}", Self::to_colored(self.deco_dives_no)); - println!("Total time in deco: {}", Self::to_colored(Self::seconds_to_readable(self.time_in_deco))); - println!("Max surface GF: {}{}", Self::to_colored(self.gf_surf_max.round()), Self::to_colored("%")); - println!("Max GF99: {}{}", Self::to_colored(self.gf_99_max.round()), Self::to_colored("%")); - println!("Max end GF: {}{}", Self::to_colored(self.gf_end_max.round()), Self::to_colored("%")); - self.print_time_below(); + println!("Dives: {}", Self::to_colored(stats.dives_no)); + println!( + "Total time: {}", + Self::to_colored(Self::seconds_to_readable(stats.total_time)) + ); + println!( + "Max depth: {}{}", + Self::to_colored(stats.depth_max), + Self::to_colored("m") + ); + println!( + "Deco dives: {}", + Self::to_colored(stats.deco_dives_no) + ); + println!( + "Total time in deco: {}", + Self::to_colored(Self::seconds_to_readable(stats.time_in_deco)) + ); + println!( + "Max surface GF: {}{}", + Self::to_colored(stats.gf_surf_max.round()), + Self::to_colored("%") + ); + println!( + "Max GF99: {}{}", + Self::to_colored(stats.gf_99_max.round()), + Self::to_colored("%") + ); + println!( + "Max end GF: {}{}", + Self::to_colored(stats.gf_end_max.round()), + Self::to_colored("%") + ); + self.print_time_below(&stats.time_below); } fn to_colored(v: T) -> ColoredString { v.to_string().cyan().bold().dimmed() } - fn print_time_below(&self) { + fn print_time_below(&self, time_below: &TimeBelowDepthData) { println!("Time below:"); - for record in self.time_below.iter() { + for record in time_below.iter() { let (depth, time) = record; - println!(" - {}m: {}", depth, Self::to_colored(Self::seconds_to_readable(*time))); + println!( + " - {}m: {}", + depth, + Self::to_colored(Self::seconds_to_readable(*time)) + ); } }