more janky caching

This commit is contained in:
Simon Gardling 2022-02-28 08:51:18 -05:00
parent 37a818d1c7
commit 3b0c2df9e0
4 changed files with 110 additions and 124 deletions

View File

@ -6,5 +6,4 @@
- Dynamically delete inputs - Dynamically delete inputs
- Different colors - Different colors
- Better Handling of area and integrals - Better Handling of area and integrals
2. Cache data when not zooming. 2. Non `y=` functions.
3. Non `y=` functions.

View File

@ -17,7 +17,7 @@ const INTEGRAL_NUM_RANGE: RangeInclusive<usize> = 10..=100000;
const MIN_X_TOTAL: f64 = -1000.0; const MIN_X_TOTAL: f64 = -1000.0;
const MAX_X_TOTAL: f64 = 1000.0; const MAX_X_TOTAL: f64 = 1000.0;
const X_RANGE: RangeInclusive<f64> = MIN_X_TOTAL..=MAX_X_TOTAL; const X_RANGE: RangeInclusive<f64> = 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 { pub struct MathApp {
functions: Vec<Function>, functions: Vec<Function>,
@ -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 { impl epi::App for MathApp {
// The name of the program (displayed when running natively as the window title) // The name of the program (displayed when running natively as the window title)
fn name(&self) -> &str { "Integral Demonstration" } 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. // Called each time the UI needs repainting, which may be many times per second.
#[inline(always)] #[inline(always)]
fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) { 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. // Note: This Instant implementation does not show microseconds when using wasm.
let start = instant::Instant::now(); let start = instant::Instant::now();
@ -98,7 +82,7 @@ impl epi::App for MathApp {
// TODO: add more detail // TODO: add more detail
egui::Window::new("Supported Functions") egui::Window::new("Supported Functions")
.default_pos([200.0, 200.0]) .default_pos([200.0, 200.0])
.open(help_open) .open(&mut self.help_open)
.show(ctx, |ui| { .show(ctx, |ui| {
ui.label("- sqrt, abs"); ui.label("- sqrt, abs");
ui.label("- exp, ln, log10 (log10 can also be called as log)"); ui.label("- exp, ln, log10 (log10 can also be called as log)");
@ -112,7 +96,7 @@ impl epi::App for MathApp {
ui.horizontal(|ui| { ui.horizontal(|ui| {
if ui.add(egui::Button::new("Add function")).clicked() { if ui.add(egui::Button::new("Add function")).clicked() {
// min_x and max_x will be updated later, doesn't matter here // 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), String::from(DEFAULT_FUNCION),
-1.0, -1.0,
1.0, 1.0,
@ -122,11 +106,11 @@ impl epi::App for MathApp {
None, None,
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() { 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| { .show(ctx, |ui| {
ui.heading("Side Panel"); ui.heading("Side Panel");
let min_x_old = *integral_min_x; let min_x_old = self.integral_min_x;
let min_x_response = 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_old = self.integral_max_x;
let max_x_response = ui.add(egui::Slider::new(integral_max_x, X_RANGE).text("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 // 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() { if max_x_response.changed() {
*integral_max_x = max_x_old; self.integral_max_x = max_x_old;
} else if min_x_response.changed() { } else if min_x_response.changed() {
*integral_min_x = min_x_old; self.integral_min_x = min_x_old;
} else { } else {
*integral_min_x = -10.0; self.integral_min_x = -10.0;
*integral_max_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; let mut integral_toggle: bool = false;
ui.horizontal(|ui| { ui.horizontal(|ui| {
ui.label("Function: "); ui.label("Function: ");
if ui.add(Button::new("Toggle Integrals")).clicked() { if ui.add(Button::new("Toggle Integrals")).clicked() {
integral_toggle = true; 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 { let integral: bool = if integral_toggle {
@ -174,13 +158,13 @@ impl epi::App for MathApp {
function.is_integral() function.is_integral()
}; };
if !func_strs[i].is_empty() { if !self.func_strs[i].is_empty() {
let proc_func_str = add_asterisks(func_strs[i].clone()); let proc_func_str = add_asterisks(self.func_strs[i].clone());
let func_test_output = test_func(proc_func_str.clone()); let func_test_output = test_func(proc_func_str.clone());
if !func_test_output.is_empty() { if !func_test_output.is_empty() {
parse_error += &func_test_output; parse_error += &func_test_output;
} else { } 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| { egui::CentralPanel::default().show(ctx, |ui| {
if !parse_error.is_empty() { if !parse_error.is_empty() {
ui.label(format!("Error: {}", parse_error)); 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 available_width: usize = ui.available_width() as usize;
let step = self.get_step();
let mut area_list: Vec<f64> = Vec::new(); let mut area_list: Vec<f64> = Vec::new();
Plot::new("plot") Plot::new("plot")
.set_margin_fraction(Vec2::ZERO) .set_margin_fraction(Vec2::ZERO)
@ -232,7 +217,10 @@ impl epi::App for MathApp {
let minx_bounds: f64 = bounds.min()[0]; let minx_bounds: f64 = bounds.min()[0];
let maxx_bounds: f64 = bounds.max()[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<Function> = Vec::new();
for function_1 in self.functions.iter_mut() {
let function = function_1;
function.update_bounds(minx_bounds, maxx_bounds, available_width); function.update_bounds(minx_bounds, maxx_bounds, available_width);
if self.func_strs[i].is_empty() { if self.func_strs[i].is_empty() {
@ -252,7 +240,10 @@ impl epi::App for MathApp {
} else { } else {
area_list.push(0.0); area_list.push(0.0);
} }
i += 1;
functions_2.push(function.clone());
} }
self.functions = functions_2;
}); });
let duration = start.elapsed(); let duration = start.elapsed();

View File

@ -1,4 +1,3 @@
use crate::misc::Cache;
use egui::plot::Value; use egui::plot::Value;
use egui::widgets::plot::Bar; use egui::widgets::plot::Bar;
use meval::Expr; use meval::Expr;
@ -38,8 +37,8 @@ pub struct Function {
max_x: f64, max_x: f64,
pixel_width: usize, pixel_width: usize,
back_cache: Cache<Vec<Value>>, back_cache: Option<Vec<Value>>,
front_cache: Cache<(Vec<Bar>, f64)>, front_cache: Option<(Vec<Bar>, f64)>,
integral: bool, integral: bool,
integral_min_x: f64, integral_min_x: f64,
@ -47,6 +46,26 @@ pub struct Function {
integral_num: usize, 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 { impl Function {
pub fn new( pub fn new(
func_str: String, min_x: f64, max_x: f64, pixel_width: usize, integral: bool, 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 expr: Expr = func_str.parse().unwrap();
let func = expr.bind("x").unwrap(); let func = expr.bind("x").unwrap();
let mut output = Self { Self {
function: Box::new(func), function: Box::new(func),
func_str, func_str,
min_x, min_x,
max_x, max_x,
pixel_width, pixel_width,
back_cache: Cache::new_empty(), back_cache: None,
front_cache: Cache::new_empty(), front_cache: None,
integral, integral,
integral_min_x: match integral_min_x { integral_min_x: match integral_min_x {
Some(x) => x, Some(x) => x,
@ -83,10 +102,7 @@ impl Function {
None => f64::NAN, None => f64::NAN,
}, },
integral_num: integral_num.unwrap_or(0), integral_num: integral_num.unwrap_or(0),
}; }
output.func_str = "".to_string();
output
} }
// Runs the internal function to get values // Runs the internal function to get values
@ -136,7 +152,7 @@ impl Function {
| (integral_max_x != Some(self.integral_max_x)) | (integral_max_x != Some(self.integral_max_x))
| (integral_num != Some(self.integral_num)) | (integral_num != Some(self.integral_num))
{ {
self.front_cache.invalidate(); self.front_cache = None;
self.integral_min_x = integral_min_x.expect(""); self.integral_min_x = integral_min_x.expect("");
self.integral_max_x = integral_max_x.expect(""); self.integral_max_x = integral_max_x.expect("");
self.integral_num = integral_num.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) { 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) { if pixel_width != self.pixel_width {
self.back_cache.invalidate(); 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<Value> = 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.min_x = min_x;
self.max_x = max_x; self.max_x = max_x;
self.pixel_width = pixel_width; self.pixel_width = pixel_width;
@ -163,40 +217,38 @@ impl Function {
#[inline(always)] #[inline(always)]
pub fn run(&mut self) -> FunctionOutput { pub fn run(&mut self) -> FunctionOutput {
let front_values: Vec<Value> = match self.back_cache.is_valid() { let front_values: Vec<Value> = match self.back_cache.is_some() {
true => self.back_cache.as_ref().expect("").clone(),
false => { false => {
let absrange = (self.max_x - self.min_x).abs(); let absrange = (self.max_x - self.min_x).abs();
let resolution: f64 = (self.pixel_width as f64 / absrange) as f64; 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<Value> = (1..=self.pixel_width)
.map(|x| (x as f64 / resolution as f64) + self.min_x) .map(|x| (x as f64 / resolution as f64) + self.min_x)
// .step_by()
.map(|x| (x, self.run_func(x))) .map(|x| (x, self.run_func(x)))
.map(|(x, y)| Value::new(x, y))
.collect(); .collect();
// println!("{} {}", front_data.len(), front_data.len() as f64/absrange); // println!("{} {}", front_data.len(), front_data.len() as f64/absrange);
let output: Vec<Value> = self.back_cache = Some(front_data.clone());
front_data.iter().map(|(x, y)| Value::new(*x, *y)).collect(); front_data
self.back_cache.set(output.clone());
output
} }
true => self.back_cache.get().clone(),
}; };
if self.integral { if self.integral {
let back_bars: (Vec<Bar>, f64) = match self.front_cache.is_valid() { let back_bars: (Vec<Bar>, f64) = match self.front_cache.is_some() {
true => {
let cache = self.front_cache.as_ref().expect("");
let vec_bars: Vec<Bar> = cache.0.to_vec();
(vec_bars, cache.1)
}
false => { false => {
let (data, area) = self.integral_rectangles(); let (data, area) = self.integral_rectangles();
let bars: Vec<Bar> = data.iter().map(|(x, y)| Bar::new(*x, *y)).collect(); let bars: Vec<Bar> = data.iter().map(|(x, y)| Bar::new(*x, *y)).collect();
let output = (bars, area); let output = (bars, area);
self.front_cache.set(output.clone()); self.front_cache = Some(output.clone());
output output
} }
true => {
let cache = self.front_cache.get();
let vec_bars: Vec<Bar> = cache.0.to_vec();
(vec_bars, cache.1)
}
}; };
FunctionOutput::new(front_values, Some(back_bars)) FunctionOutput::new(front_values, Some(back_bars))
} else { } else {

View File

@ -116,40 +116,6 @@ pub fn digits_precision(x: f64, digits: usize) -> f64 {
(x * large_number).round() / large_number (x * large_number).round() / large_number
} }
pub struct Cache<T> {
backing_data: Option<T>,
}
impl<T> Cache<T> {
#[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 // Tests to make sure my cursed function works as intended
#[test] #[test]
fn asterisk_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("emax(x)".to_string()), "e*max(x)");
// assert_eq!(&add_asterisks("pisin(x)".to_string()), "pi*sin(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());
}