From 873de78f2d7ebbaf4bc5da41f3e7d0f4ae95386e Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Thu, 24 Apr 2025 11:14:05 -0700 Subject: [PATCH] Generalize the element insertion methods --- app-proto/src/add_remove.rs | 33 +++++----- app-proto/src/assembly.rs | 128 +++++++++++++++++++++--------------- 2 files changed, 91 insertions(+), 70 deletions(-) diff --git a/app-proto/src/add_remove.rs b/app-proto/src/add_remove.rs index 62b3ebe..b7d6c40 100644 --- a/app-proto/src/add_remove.rs +++ b/app-proto/src/add_remove.rs @@ -1,3 +1,4 @@ +use std::rc::Rc; use sycamore::prelude::*; use web_sys::{console, wasm_bindgen::JsValue}; @@ -11,7 +12,7 @@ use crate::{ // load an example assembly for testing. this code will be removed once we've // built a more formal test assembly system fn load_gen_assemb(assembly: &Assembly) { - let _ = assembly.try_insert_sphere( + let _ = assembly.try_insert_element( Sphere::new( String::from("gemini_a"), String::from("Castor"), @@ -19,7 +20,7 @@ fn load_gen_assemb(assembly: &Assembly) { engine::sphere(0.5, 0.5, 0.0, 1.0) ) ); - let _ = assembly.try_insert_sphere( + let _ = assembly.try_insert_element( Sphere::new( String::from("gemini_b"), String::from("Pollux"), @@ -27,7 +28,7 @@ fn load_gen_assemb(assembly: &Assembly) { engine::sphere(-0.5, -0.5, 0.0, 1.0) ) ); - let _ = assembly.try_insert_sphere( + let _ = assembly.try_insert_element( Sphere::new( String::from("ursa_major"), String::from("Ursa major"), @@ -35,7 +36,7 @@ fn load_gen_assemb(assembly: &Assembly) { engine::sphere(-0.5, 0.5, 0.0, 0.75) ) ); - let _ = assembly.try_insert_sphere( + let _ = assembly.try_insert_element( Sphere::new( String::from("ursa_minor"), String::from("Ursa minor"), @@ -43,7 +44,7 @@ fn load_gen_assemb(assembly: &Assembly) { engine::sphere(0.5, -0.5, 0.0, 0.5) ) ); - let _ = assembly.try_insert_sphere( + let _ = assembly.try_insert_element( Sphere::new( String::from("moon_deimos"), String::from("Deimos"), @@ -51,7 +52,7 @@ fn load_gen_assemb(assembly: &Assembly) { engine::sphere(0.0, 0.15, 1.0, 0.25) ) ); - let _ = assembly.try_insert_sphere( + let _ = assembly.try_insert_element( Sphere::new( String::from("moon_phobos"), String::from("Phobos"), @@ -66,7 +67,7 @@ fn load_gen_assemb(assembly: &Assembly) { // built a more formal test assembly system fn load_low_curv_assemb(assembly: &Assembly) { let a = 0.75_f64.sqrt(); - let _ = assembly.try_insert_sphere( + let _ = assembly.try_insert_element( Sphere::new( "central".to_string(), "Central".to_string(), @@ -74,7 +75,7 @@ fn load_low_curv_assemb(assembly: &Assembly) { engine::sphere(0.0, 0.0, 0.0, 1.0) ) ); - let _ = assembly.try_insert_sphere( + let _ = assembly.try_insert_element( Sphere::new( "assemb_plane".to_string(), "Assembly plane".to_string(), @@ -82,7 +83,7 @@ fn load_low_curv_assemb(assembly: &Assembly) { engine::sphere_with_offset(0.0, 0.0, 1.0, 0.0, 0.0) ) ); - let _ = assembly.try_insert_sphere( + let _ = assembly.try_insert_element( Sphere::new( "side1".to_string(), "Side 1".to_string(), @@ -90,7 +91,7 @@ fn load_low_curv_assemb(assembly: &Assembly) { engine::sphere_with_offset(1.0, 0.0, 0.0, 1.0, 0.0) ) ); - let _ = assembly.try_insert_sphere( + let _ = assembly.try_insert_element( Sphere::new( "side2".to_string(), "Side 2".to_string(), @@ -98,7 +99,7 @@ fn load_low_curv_assemb(assembly: &Assembly) { engine::sphere_with_offset(-0.5, a, 0.0, 1.0, 0.0) ) ); - let _ = assembly.try_insert_sphere( + let _ = assembly.try_insert_element( Sphere::new( "side3".to_string(), "Side 3".to_string(), @@ -106,7 +107,7 @@ fn load_low_curv_assemb(assembly: &Assembly) { engine::sphere_with_offset(-0.5, -a, 0.0, 1.0, 0.0) ) ); - let _ = assembly.try_insert_sphere( + let _ = assembly.try_insert_element( Sphere::new( "corner1".to_string(), "Corner 1".to_string(), @@ -114,7 +115,7 @@ fn load_low_curv_assemb(assembly: &Assembly) { engine::sphere(-4.0/3.0, 0.0, 0.0, 1.0/3.0) ) ); - let _ = assembly.try_insert_sphere( + let _ = assembly.try_insert_element( Sphere::new( "corner2".to_string(), "Corner 2".to_string(), @@ -122,7 +123,7 @@ fn load_low_curv_assemb(assembly: &Assembly) { engine::sphere(2.0/3.0, -4.0/3.0 * a, 0.0, 1.0/3.0) ) ); - let _ = assembly.try_insert_sphere( + let _ = assembly.try_insert_element( Sphere::new( String::from("corner3"), String::from("Corner 3"), @@ -167,7 +168,7 @@ pub fn AddRemove() -> View { button( on:click=|_| { let state = use_context::(); - state.assembly.insert_new_sphere(); + state.assembly.insert_element_default::(); } ) { "+" } button( @@ -190,7 +191,7 @@ pub fn AddRemove() -> View { .unwrap() ); state.assembly.insert_regulator( - InversiveDistanceRegulator::new(subjects, &state.assembly) + Rc::new(InversiveDistanceRegulator::new(subjects, &state.assembly)) ); state.selection.update(|sel| sel.clear()); } diff --git a/app-proto/src/assembly.rs b/app-proto/src/assembly.rs index 84b21f7..000a619 100644 --- a/app-proto/src/assembly.rs +++ b/app-proto/src/assembly.rs @@ -43,6 +43,23 @@ pub trait ProblemPoser { } pub trait Element: ProblemPoser + DisplayItem { + // the default identifier for an element of this type + fn default_id() -> String where Self: Sized; + + // create the default example of an element of this type + fn default(id: String, id_num: u64) -> Self where Self: Sized; + + // the regulators that should be created when an element of this type is + // inserted into the given assembly with the given storage key + /* KLUDGE */ + // right now, this organization makes sense because regulators identify + // their subjects by storage key, so the element has to be inserted before + // its regulators can be created. if we change the way regulators identify + // their subjects, we should consider refactoring + fn default_regulators(_key: ElementKey, _assembly: &Assembly) -> Vec> where Self: Sized { + Vec::new() + } + fn id(&self) -> &String; fn label(&self) -> &String; fn representation(&self) -> Signal>; @@ -54,6 +71,19 @@ pub trait Element: ProblemPoser + DisplayItem { // a serial number that uniquely identifies this element fn serial(&self) -> u64; + // take the next serial number, panicking if that was the last one left + fn next_serial() -> u64 where Self: Sized { + // the technique we use to panic on overflow is taken from _Rust Atomics + // and Locks_, by Mara Bos + // + // https://marabos.nl/atomics/atomics.html#example-handle-overflow + // + NEXT_ELEMENT_SERIAL.fetch_update( + Ordering::SeqCst, Ordering::SeqCst, + |serial| serial.checked_add(1) + ).expect("Out of serial numbers for elements") + } + // the configuration matrix column index that was assigned to the element // last time the assembly was realized, or `None` if the element has never // been through a realization @@ -98,30 +128,36 @@ impl Sphere { color: ElementColor, representation: DVector ) -> Sphere { - // take the next serial number, panicking if that was the last number we - // had left. the technique we use to panic on overflow is taken from - // _Rust Atomics and Locks_, by Mara Bos - // - // https://marabos.nl/atomics/atomics.html#example-handle-overflow - // - let serial = NEXT_ELEMENT_SERIAL.fetch_update( - Ordering::SeqCst, Ordering::SeqCst, - |serial| serial.checked_add(1) - ).expect("Out of serial numbers for elements"); - Sphere { id: id, label: label, color: color, representation: create_signal(representation), regulators: create_signal(BTreeSet::default()), - serial: serial, + serial: Self::next_serial(), column_index: None.into() } } } impl Element for Sphere { + fn default_id() -> String { + "sphere".to_string() + } + + fn default(id: String, id_num: u64) -> Sphere { + Sphere::new( + id, + format!("Sphere {id_num}"), + [0.75_f32, 0.75_f32, 0.75_f32], + sphere(0.0, 0.0, 0.0, 1.0) + ) + } + + fn default_regulators(key: ElementKey, assembly: &Assembly) -> Vec> { + vec![Rc::new(HalfCurvatureRegulator::new(key, assembly))] + } + fn id(&self) -> &String { &self.id } @@ -336,63 +372,58 @@ impl Assembly { // --- inserting elements and regulators --- - // insert a sphere into the assembly without checking whether we already + // insert an element into the assembly without checking whether we already // have an element with the same identifier. any element that does have the // same identifier will get kicked out of the `elements_by_id` index - fn insert_sphere_unchecked(&self, sphere: Sphere) -> ElementKey { - // insert the sphere - let id = sphere.id.clone(); - let key = self.elements.update(|elts| elts.insert(Rc::new(sphere))); + fn insert_element_unchecked(&self, elt: T) -> ElementKey { + // insert the element + let id = elt.id().clone(); + let key = self.elements.update(|elts| elts.insert(Rc::new(elt))); self.elements_by_id.update(|elts_by_id| elts_by_id.insert(id, key)); - // regulate the sphere's curvature - self.insert_regulator(HalfCurvatureRegulator::new(key, &self)); + // create and insert the element's default regulators + for reg in T::default_regulators(key, &self) { + self.insert_regulator(reg); + } key } - pub fn try_insert_sphere(&self, sphere: Sphere) -> Option { + pub fn try_insert_element(&self, elt: impl Element + 'static) -> Option { let can_insert = self.elements_by_id.with_untracked( - |elts_by_id| !elts_by_id.contains_key(&sphere.id) + |elts_by_id| !elts_by_id.contains_key(elt.id()) ); if can_insert { - Some(self.insert_sphere_unchecked(sphere)) + Some(self.insert_element_unchecked(elt)) } else { None } } - pub fn insert_new_sphere(&self) { + pub fn insert_element_default(&self) { // find the next unused identifier in the default sequence + let default_id = T::default_id(); let mut id_num = 1; - let mut id = format!("sphere{}", id_num); + let mut id = format!("{default_id}{id_num}"); while self.elements_by_id.with_untracked( |elts_by_id| elts_by_id.contains_key(&id) ) { id_num += 1; - id = format!("sphere{}", id_num); + id = format!("{default_id}{id_num}"); } - // create and insert a sphere - let _ = self.insert_sphere_unchecked( - Sphere::new( - id, - format!("Sphere {}", id_num), - [0.75_f32, 0.75_f32, 0.75_f32], - sphere(0.0, 0.0, 0.0, 1.0) - ) - ); + // create and insert the default example of `T` + let _ = self.insert_element_unchecked(T::default(id, id_num)); } - pub fn insert_regulator(&self, regulator: T) { + pub fn insert_regulator(&self, regulator: Rc) { // add the regulator to the assembly's regulator list - let regulator_rc = Rc::new(regulator); let key = self.regulators.update( - |regs| regs.insert(regulator_rc.clone()) + |regs| regs.insert(regulator.clone()) ); // add the regulator to each subject's regulator list - let subjects = regulator_rc.subjects(); + let subjects = regulator.subjects(); let subject_regulators: Vec<_> = self.elements.with_untracked( |elts| subjects.into_iter().map( |subj| elts[subj].regulators() @@ -409,10 +440,10 @@ impl Assembly { /* DEBUG */ // log the regulator update console::log_1(&JsValue::from( - format!("Updated regulator with subjects {:?}", regulator_rc.subjects()) + format!("Updated regulator with subjects {:?}", regulator.subjects()) )); - if regulator_rc.try_activate(&self_for_effect) { + if regulator.try_activate(&self_for_effect) { self_for_effect.realize(); } }); @@ -621,20 +652,14 @@ impl Assembly { #[cfg(test)] mod tests { - use crate::engine; - use super::*; #[test] #[should_panic(expected = "Sphere \"sphere\" should be indexed before writing problem data")] fn unindexed_element_test() { let _ = create_root(|| { - Sphere::new( - "sphere".to_string(), - "Sphere".to_string(), - [1.0_f32, 1.0_f32, 1.0_f32], - engine::sphere(0.0, 0.0, 0.0, 1.0) - ).pose(&mut ConstraintProblem::new(1), &Slab::new()); + let elt = Sphere::default("sphere".to_string(), 0); + elt.pose(&mut ConstraintProblem::new(1), &Slab::new()); }); } @@ -645,12 +670,7 @@ mod tests { let mut elts = Slab::>::new(); let subjects = [0, 1].map(|k| { elts.insert( - Rc::new(Sphere::new( - format!("sphere{k}"), - format!("Sphere {k}"), - [1.0_f32, 1.0_f32, 1.0_f32], - engine::sphere(0.0, 0.0, 0.0, 1.0) - )) + Rc::new(Sphere::default(format!("sphere{k}"), k)) ) }); elts[subjects[0]].set_column_index(0);