diff --git a/app-proto/src/assembly.rs b/app-proto/src/assembly.rs index 8403406..2ccfc69 100644 --- a/app-proto/src/assembly.rs +++ b/app-proto/src/assembly.rs @@ -1,7 +1,11 @@ use nalgebra::{DMatrix, DVector, DVectorView, Vector3}; use rustc_hash::FxHashMap; use slab::Slab; -use std::{collections::BTreeSet, sync::atomic::{AtomicU64, Ordering}}; +use std::{ + collections::BTreeSet, + num::ParseFloatError, + sync::atomic::{AtomicU64, Ordering} +}; use sycamore::prelude::*; use web_sys::{console, wasm_bindgen::JsValue}; /* DEBUG */ @@ -111,41 +115,75 @@ impl Element { } } -// `set_point_spec` is always a valid specification of `set_point` -// ┌────────────┬─────────────────────────────────────────────────────┐ -// │`set_point` │ `set_point_spec` │ -// ┝━━━━━━━━━━━━┿━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥ -// │`Some(x)` │ a string that parses to the floating-point value `x`│ -// ├────────────┼─────────────────────────────────────────────────────┤ -// │`None` │ the empty string │ -// └────────────┴─────────────────────────────────────────────────────┘ +// to construct a `SpecifiedValue` that might be `Present`, use the associated +// function `try_from`. this ensures that `spec` is always a valid specification +// of `value` according to the format discussed above the implementation of +// `TryFrom` +pub enum SpecifiedValue { + Absent, + Present { + spec: String, + value: f64 + } +} + +use SpecifiedValue::*; + +impl SpecifiedValue { + // get the specification for this value. the associated function `try_from` + // is essentially a left inverse of this method: + // + // SpecifiedValue::try_from(x.spec()) == Ok(x) + // + pub fn spec(&self) -> String { + match self { + Absent => String::new(), + Present { spec, .. } => spec.clone() + } + } + + fn is_present(&self) -> bool { + match self { + Absent => false, + Present { .. } => true + } + } +} + +// we can try to turn a specification string into a `SpecifiedValue`. if the +// specification is empty, the `SpecifiedValue` is `Absent`. if the +// specification parses to a floating-point value `x`, the `SpecifiedValue` is +// `Present`, with a `value` of `x`, and the specification is stored in `spec`. +// these are the only valid specifications; any other produces an error +impl TryFrom for SpecifiedValue { + type Error = ParseFloatError; + + fn try_from(spec: String) -> Result { + if spec.is_empty() { + Ok(Absent) + } else { + spec.parse::().map( + |value| Present { spec: spec, value: value } + ) + } + } +} + #[derive(Clone, Copy)] pub struct Regulator { pub subjects: (ElementKey, ElementKey), pub measurement: ReadSignal, - pub set_point: ReadSignal>, - - set_point_writable: Signal>, - set_point_spec: Signal + pub set_point: Signal } impl Regulator { - pub fn get_set_point_spec_clone(&self) -> String { - self.set_point_spec.get_clone() - } - - pub fn get_set_point_spec_clone_untracked(&self) -> String { - self.set_point_spec.get_clone_untracked() - } - - pub fn try_specify_set_point(&self, spec: String) -> bool { - match spec.parse::() { - Err(_) if !spec.is_empty() => false, - set_pt => { - self.set_point_writable.set(set_pt.ok()); - self.set_point_spec.set(spec); + pub fn try_set(&self, set_pt_spec: String) -> bool { + match SpecifiedValue::try_from(set_pt_spec) { + Ok(set_pt) => { + self.set_point.set(set_pt); true } + Err(_) => false, } } } @@ -255,18 +293,15 @@ impl Assembly { reps.0.dot(&(&*Q * reps.1)) } ); - let set_point_writable = create_signal(None); - let set_point = *set_point_writable; + let set_point = create_signal(Absent); self.insert_regulator(Regulator { subjects: subjects, measurement: measurement, - set_point: set_point, - set_point_writable: set_point_writable, - set_point_spec: create_signal(String::new()) + set_point: set_point }); /* DEBUG */ - // print updated regulator list + // print an updated list of regulators console::log_1(&JsValue::from("Regulators:")); self.regulators.with(|regs| { for (_, reg) in regs.into_iter() { @@ -275,7 +310,9 @@ impl Assembly { &JsValue::from(reg.subjects.0), &JsValue::from(reg.subjects.1), &JsValue::from(":"), - &JsValue::from(reg.set_point.get_untracked()) + &JsValue::from(reg.set_point.with_untracked( + |set_pt| set_pt.spec() + )) ); } }); @@ -286,7 +323,7 @@ impl Assembly { console::log_1(&JsValue::from( format!("Updated constraint with subjects ({}, {})", subjects.0, subjects.1) )); - if set_point.with(|set_pt| set_pt.is_some()) { + if set_point.with(|set_pt| set_pt.is_present()) { self.realize(); } }); @@ -308,15 +345,17 @@ impl Assembly { let mut gram_to_be = PartialMatrix::new(); self.regulators.with_untracked(|regs| { for (_, reg) in regs { - match reg.set_point.get_untracked() { - Some(set_pt) => { - let subjects = reg.subjects; - let row = elts[subjects.0].column_index.unwrap(); - let col = elts[subjects.1].column_index.unwrap(); - gram_to_be.push_sym(row, col, set_pt); - }, - None => () - } + reg.set_point.with_untracked(|set_pt| { + match set_pt { + Absent => (), + Present { value, .. } => { + let subjects = reg.subjects; + let row = elts[subjects.0].column_index.unwrap(); + let col = elts[subjects.1].column_index.unwrap(); + gram_to_be.push_sym(row, col, *value); + } + }; + }); } }); diff --git a/app-proto/src/outline.rs b/app-proto/src/outline.rs index 4898aba..04bda9a 100644 --- a/app-proto/src/outline.rs +++ b/app-proto/src/outline.rs @@ -10,9 +10,10 @@ use crate::{ AppState, assembly, assembly::{ + ElementKey, Regulator, RegulatorKey, - ElementKey + SpecifiedValue::* } }; @@ -20,14 +21,16 @@ use crate::{ #[component(inline_props)] fn RegulatorInput(regulator: Regulator) -> View { let valid = create_signal(true); - let value = create_signal(regulator.get_set_point_spec_clone_untracked()); + let value = create_signal( + regulator.set_point.with_untracked(|set_pt| set_pt.spec()) + ); // this closure resets the input value to the regulator's set point - // specification, which is always a valid specification + // specification let reset_value = move || { batch(|| { valid.set(true); - value.set(regulator.get_set_point_spec_clone()); + value.set(regulator.set_point.with(|set_pt| set_pt.spec())); }) }; @@ -40,10 +43,12 @@ fn RegulatorInput(regulator: Regulator) -> View { r#type="text", class=move || { if valid.get() { - match regulator.set_point.get() { - Some(_) => "regulator-input constraint", - None => "regulator-input" - } + regulator.set_point.with(|set_pt| { + match set_pt { + Absent => "regulator-input", + Present { .. } => "regulator-input constraint" + } + }) } else { "regulator-input invalid" } @@ -51,7 +56,7 @@ fn RegulatorInput(regulator: Regulator) -> View { placeholder=regulator.measurement.with(|result| result.to_string()), bind:value=value, on:change=move |_| valid.set( - regulator.try_specify_set_point(value.get_clone_untracked()) + regulator.try_set(value.get_clone_untracked()) ), on:keydown={ move |event: KeyboardEvent| {