From 3b0c2df9e0b751735f0ff914a6d8a4b4fb0e51b2 Mon Sep 17 00:00:00 2001 From: Simon Gardling Date: Mon, 28 Feb 2022 08:51:18 -0500 Subject: [PATCH] more janky caching --- TODO.md | 3 +- src/egui_app.rs | 67 +++++++++++++----------------- src/function.rs | 108 +++++++++++++++++++++++++++++++++++------------- src/misc.rs | 56 ------------------------- 4 files changed, 110 insertions(+), 124 deletions(-) diff --git a/TODO.md b/TODO.md index 2290574..0d7c8c4 100644 --- a/TODO.md +++ b/TODO.md @@ -6,5 +6,4 @@ - Dynamically delete inputs - Different colors - Better Handling of area and integrals -2. Cache data when not zooming. -3. Non `y=` functions. \ No newline at end of file +2. Non `y=` functions. \ No newline at end of file diff --git a/src/egui_app.rs b/src/egui_app.rs index b64dff7..1781b7a 100644 --- a/src/egui_app.rs +++ b/src/egui_app.rs @@ -17,7 +17,7 @@ const INTEGRAL_NUM_RANGE: RangeInclusive = 10..=100000; const MIN_X_TOTAL: f64 = -1000.0; const MAX_X_TOTAL: f64 = 1000.0; const X_RANGE: RangeInclusive = MIN_X_TOTAL..=MAX_X_TOTAL; -const DEFAULT_FUNCION: &str = "x^2"; +const DEFAULT_FUNCION: &str = "x^2"; // Default function that appears when adding a new function pub struct MathApp { functions: Vec, @@ -61,13 +61,6 @@ impl Default for MathApp { } } -impl MathApp { - #[inline] - pub fn get_step(&self) -> f64 { - (self.integral_min_x - self.integral_max_x).abs() / (self.integral_num as f64) - } -} - impl epi::App for MathApp { // The name of the program (displayed when running natively as the window title) fn name(&self) -> &str { "Integral Demonstration" } @@ -82,15 +75,6 @@ impl epi::App for MathApp { // Called each time the UI needs repainting, which may be many times per second. #[inline(always)] fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) { - let Self { - functions, - func_strs, - integral_min_x, - integral_max_x, - integral_num, - help_open, - } = self; - // Note: This Instant implementation does not show microseconds when using wasm. let start = instant::Instant::now(); @@ -98,7 +82,7 @@ impl epi::App for MathApp { // TODO: add more detail egui::Window::new("Supported Functions") .default_pos([200.0, 200.0]) - .open(help_open) + .open(&mut self.help_open) .show(ctx, |ui| { ui.label("- sqrt, abs"); ui.label("- exp, ln, log10 (log10 can also be called as log)"); @@ -112,7 +96,7 @@ impl epi::App for MathApp { ui.horizontal(|ui| { if ui.add(egui::Button::new("Add function")).clicked() { // min_x and max_x will be updated later, doesn't matter here - functions.push(Function::new( + self.functions.push(Function::new( String::from(DEFAULT_FUNCION), -1.0, 1.0, @@ -122,11 +106,11 @@ impl epi::App for MathApp { None, None, )); - func_strs.push(String::from(DEFAULT_FUNCION)); + self.func_strs.push(String::from(DEFAULT_FUNCION)); } if ui.add(egui::Button::new("Open Help")).clicked() { - *help_open = true; + self.help_open = true; } }); }); @@ -137,35 +121,35 @@ impl epi::App for MathApp { .show(ctx, |ui| { ui.heading("Side Panel"); - let min_x_old = *integral_min_x; + let min_x_old = self.integral_min_x; let min_x_response = - ui.add(egui::Slider::new(integral_min_x, X_RANGE.clone()).text("Min X")); + ui.add(egui::Slider::new(&mut self.integral_min_x, X_RANGE.clone()).text("Min X")); - let max_x_old = *integral_max_x; - let max_x_response = ui.add(egui::Slider::new(integral_max_x, X_RANGE).text("Max X")); + let max_x_old = self.integral_max_x; + let max_x_response = ui.add(egui::Slider::new(&mut self.integral_max_x, X_RANGE).text("Max X")); // Checks bounds, and if they are invalid, fix them - if integral_min_x >= integral_max_x { + if self.integral_min_x >= self.integral_max_x { if max_x_response.changed() { - *integral_max_x = max_x_old; + self.integral_max_x = max_x_old; } else if min_x_response.changed() { - *integral_min_x = min_x_old; + self.integral_min_x = min_x_old; } else { - *integral_min_x = -10.0; - *integral_max_x = 10.0; + self.integral_min_x = -10.0; + self.integral_max_x = 10.0; } } - ui.add(egui::Slider::new(integral_num, INTEGRAL_NUM_RANGE).text("Interval")); + ui.add(egui::Slider::new(&mut self.integral_num, INTEGRAL_NUM_RANGE).text("Interval")); - for (i, function) in functions.iter_mut().enumerate() { + for (i, function) in self.functions.iter_mut().enumerate() { let mut integral_toggle: bool = false; ui.horizontal(|ui| { ui.label("Function: "); if ui.add(Button::new("Toggle Integrals")).clicked() { integral_toggle = true; } - ui.text_edit_singleline(&mut func_strs[i]); + ui.text_edit_singleline(&mut self.func_strs[i]); }); let integral: bool = if integral_toggle { @@ -174,13 +158,13 @@ impl epi::App for MathApp { function.is_integral() }; - if !func_strs[i].is_empty() { - let proc_func_str = add_asterisks(func_strs[i].clone()); + if !self.func_strs[i].is_empty() { + let proc_func_str = add_asterisks(self.func_strs[i].clone()); let func_test_output = test_func(proc_func_str.clone()); if !func_test_output.is_empty() { parse_error += &func_test_output; } else { - function.update(proc_func_str, integral, Some(*integral_min_x), Some(*integral_max_x), Some(*integral_num)); + function.update(proc_func_str, integral, Some(self.integral_min_x), Some(self.integral_max_x), Some(self.integral_num)); } } } @@ -213,6 +197,8 @@ impl epi::App for MathApp { }); }); + let step = (self.integral_min_x - self.integral_max_x).abs() / (self.integral_num as f64); + egui::CentralPanel::default().show(ctx, |ui| { if !parse_error.is_empty() { ui.label(format!("Error: {}", parse_error)); @@ -220,7 +206,6 @@ impl epi::App for MathApp { } let available_width: usize = ui.available_width() as usize; - let step = self.get_step(); let mut area_list: Vec = Vec::new(); Plot::new("plot") .set_margin_fraction(Vec2::ZERO) @@ -232,7 +217,10 @@ impl epi::App for MathApp { let minx_bounds: f64 = bounds.min()[0]; let maxx_bounds: f64 = bounds.max()[0]; - for (i, function) in self.functions.iter_mut().enumerate() { + let mut i: usize = 0; + let mut functions_2: Vec = Vec::new(); + for function_1 in self.functions.iter_mut() { + let function = function_1; function.update_bounds(minx_bounds, maxx_bounds, available_width); if self.func_strs[i].is_empty() { @@ -252,7 +240,10 @@ impl epi::App for MathApp { } else { area_list.push(0.0); } + i += 1; + functions_2.push(function.clone()); } + self.functions = functions_2; }); let duration = start.elapsed(); diff --git a/src/function.rs b/src/function.rs index 2526ac7..7516b7b 100644 --- a/src/function.rs +++ b/src/function.rs @@ -1,4 +1,3 @@ -use crate::misc::Cache; use egui::plot::Value; use egui::widgets::plot::Bar; use meval::Expr; @@ -38,8 +37,8 @@ pub struct Function { max_x: f64, pixel_width: usize, - back_cache: Cache>, - front_cache: Cache<(Vec, f64)>, + back_cache: Option>, + front_cache: Option<(Vec, f64)>, integral: bool, integral_min_x: f64, @@ -47,6 +46,26 @@ pub struct Function { integral_num: usize, } +impl Clone for Function { + fn clone(&self) -> Self { + let expr: Expr = self.func_str.parse().unwrap(); + let func = expr.bind("x").unwrap(); + Self { + function: Box::new(func), + func_str: self.func_str.clone(), + min_x: self.min_x.clone(), + max_x: self.max_x.clone(), + pixel_width: self.pixel_width.clone(), + back_cache: self.back_cache.clone(), + front_cache: self.front_cache.clone(), + integral: self.integral.clone(), + integral_min_x: self.integral_min_x.clone(), + integral_max_x: self.integral_max_x.clone(), + integral_num: self.integral_num.clone(), + } + } +} + impl Function { pub fn new( func_str: String, min_x: f64, max_x: f64, pixel_width: usize, integral: bool, @@ -65,14 +84,14 @@ impl Function { let expr: Expr = func_str.parse().unwrap(); let func = expr.bind("x").unwrap(); - let mut output = Self { + Self { function: Box::new(func), func_str, min_x, max_x, pixel_width, - back_cache: Cache::new_empty(), - front_cache: Cache::new_empty(), + back_cache: None, + front_cache: None, integral, integral_min_x: match integral_min_x { Some(x) => x, @@ -83,10 +102,7 @@ impl Function { None => f64::NAN, }, integral_num: integral_num.unwrap_or(0), - }; - - output.func_str = "".to_string(); - output + } } // Runs the internal function to get values @@ -136,7 +152,7 @@ impl Function { | (integral_max_x != Some(self.integral_max_x)) | (integral_num != Some(self.integral_num)) { - self.front_cache.invalidate(); + self.front_cache = None; self.integral_min_x = integral_min_x.expect(""); self.integral_max_x = integral_max_x.expect(""); self.integral_num = integral_num.expect(""); @@ -145,8 +161,46 @@ impl Function { } pub fn update_bounds(&mut self, min_x: f64, max_x: f64, pixel_width: usize) { - if (min_x != self.min_x) | (max_x != self.max_x) | (pixel_width != self.pixel_width) { - self.back_cache.invalidate(); + if pixel_width != self.pixel_width { + self.back_cache = None; + self.min_x = min_x; + self.max_x = max_x; + self.pixel_width = pixel_width; + } else if ((min_x != self.min_x) | (max_x != self.max_x)) + && self.back_cache.is_some() + && false + { + println!("rebuilding cache"); + let range_new: f64 = max_x.abs() + min_x.abs(); + + let resolution: f64 = (self.pixel_width as f64 / range_new) as f64; + let movement_right = min_x > self.min_x; + let mut new_back: Vec = self + .back_cache + .as_ref() + .expect("") + .clone() + .iter() + .filter(|ele| (ele.x >= min_x) && (min_x >= ele.x)) + .map(|ele| *ele) + .collect(); + + let x_to_go = match movement_right { + true => ((self.max_x - max_x) * resolution) as usize, + false => ((self.min_x - min_x) * resolution) as usize, + }; + + new_back.append( + &mut (1..x_to_go) + .map(|x| (x as f64 / resolution as f64) + min_x) + .map(|x| (x, self.run_func(x))) + .map(|(x, y)| Value::new(x, y)) + .collect(), + ); + + self.back_cache = Some(new_back); + } else { + self.back_cache = None; self.min_x = min_x; self.max_x = max_x; self.pixel_width = pixel_width; @@ -163,40 +217,38 @@ impl Function { #[inline(always)] pub fn run(&mut self) -> FunctionOutput { - let front_values: Vec = match self.back_cache.is_valid() { + let front_values: Vec = match self.back_cache.is_some() { + true => self.back_cache.as_ref().expect("").clone(), false => { let absrange = (self.max_x - self.min_x).abs(); let resolution: f64 = (self.pixel_width as f64 / absrange) as f64; - let front_data: Vec<(f64, f64)> = (1..=self.pixel_width) + let front_data: Vec = (1..=self.pixel_width) .map(|x| (x as f64 / resolution as f64) + self.min_x) - // .step_by() .map(|x| (x, self.run_func(x))) + .map(|(x, y)| Value::new(x, y)) .collect(); // println!("{} {}", front_data.len(), front_data.len() as f64/absrange); - let output: Vec = - front_data.iter().map(|(x, y)| Value::new(*x, *y)).collect(); - self.back_cache.set(output.clone()); - output + self.back_cache = Some(front_data.clone()); + front_data } - true => self.back_cache.get().clone(), }; if self.integral { - let back_bars: (Vec, f64) = match self.front_cache.is_valid() { + let back_bars: (Vec, f64) = match self.front_cache.is_some() { + true => { + let cache = self.front_cache.as_ref().expect(""); + let vec_bars: Vec = cache.0.to_vec(); + (vec_bars, cache.1) + } false => { let (data, area) = self.integral_rectangles(); let bars: Vec = data.iter().map(|(x, y)| Bar::new(*x, *y)).collect(); let output = (bars, area); - self.front_cache.set(output.clone()); + self.front_cache = Some(output.clone()); output } - true => { - let cache = self.front_cache.get(); - let vec_bars: Vec = cache.0.to_vec(); - (vec_bars, cache.1) - } }; FunctionOutput::new(front_values, Some(back_bars)) } else { diff --git a/src/misc.rs b/src/misc.rs index 28a9a7b..e68b500 100644 --- a/src/misc.rs +++ b/src/misc.rs @@ -116,40 +116,6 @@ pub fn digits_precision(x: f64, digits: usize) -> f64 { (x * large_number).round() / large_number } -pub struct Cache { - backing_data: Option, -} - -impl Cache { - #[allow(dead_code)] - #[inline] - pub fn new(backing_data: T) -> Self { - Self { - backing_data: Some(backing_data), - } - } - - #[inline] - pub fn new_empty() -> Self { Self { backing_data: None } } - - #[inline] - pub fn get(&self) -> &T { - match &self.backing_data { - Some(x) => x, - None => panic!("self.backing_data is None"), - } - } - - #[inline] - pub fn set(&mut self, data: T) { self.backing_data = Some(data); } - - #[inline] - pub fn invalidate(&mut self) { self.backing_data = None; } - - #[inline] - pub fn is_valid(&self) -> bool { self.backing_data.is_some() } -} - // Tests to make sure my cursed function works as intended #[test] fn asterisk_test() { @@ -185,25 +151,3 @@ fn asterisk_test() { // assert_eq!(&add_asterisks("emax(x)".to_string()), "e*max(x)"); // assert_eq!(&add_asterisks("pisin(x)".to_string()), "pi*sin(x)"); } - -// Tests cache when initialized with value -#[test] -fn cache_test_full() { - let mut cache = Cache::new("data"); - assert!(cache.is_valid()); - cache.invalidate(); - assert!(!cache.is_valid()); - cache.set("data2"); - assert!(cache.is_valid()); -} - -// Tests cache when initialized without value -#[test] -fn cache_test_empty() { - let mut cache: Cache<&str> = Cache::new_empty(); - assert!(!cache.is_valid()); - cache.invalidate(); - assert!(!cache.is_valid()); - cache.set("data"); - assert!(cache.is_valid()); -}