diff --git a/app-proto/src/assembly.rs b/app-proto/src/assembly.rs index 7841922..4e48c65 100644 --- a/app-proto/src/assembly.rs +++ b/app-proto/src/assembly.rs @@ -5,6 +5,9 @@ use std::{ any::{Any, TypeId}, cell::Cell, collections::BTreeSet, + fmt, + fmt::{Debug, Formatter}, + hash::{Hash, Hasher}, rc::Rc, sync::atomic::{AtomicU64, Ordering} }; @@ -54,11 +57,11 @@ pub trait Element: ProblemPoser + DisplayItem { // 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 { + // this organization made sense when regulators identified their subjects by + // storage key, so the element has to be inserted before its regulators + // could be created. now that regulators identify their subjects by pointer, + // we should consider refactoring + fn default_regulators(self: Rc, _assembly: &Assembly) -> Vec> where Self: Sized { Vec::new() } @@ -97,12 +100,26 @@ pub trait Element: ProblemPoser + DisplayItem { fn set_column_index(&self, index: usize); } +impl Debug for dyn Element { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> { + self.id().fmt(f) + } +} + +impl Hash for dyn Element { + fn hash(&self, state: &mut H) { + self.serial().hash(state) + } +} + impl PartialEq for dyn Element { fn eq(&self, other: &Self) -> bool { self.serial() == other.serial() } } +impl Eq for dyn Element {} + pub struct Sphere { pub id: String, pub label: String, @@ -148,8 +165,8 @@ impl Element for Sphere { ) } - fn default_regulators(key: ElementKey, assembly: &Assembly) -> Vec> { - vec![Rc::new(HalfCurvatureRegulator::new(key, assembly))] + fn default_regulators(self: Rc, assembly: &Assembly) -> Vec> { + vec![Rc::new(HalfCurvatureRegulator::new(self, assembly))] } fn id(&self) -> &String { @@ -277,7 +294,7 @@ impl ProblemPoser for Point { } pub trait Regulator: ProblemPoser + OutlineItem { - fn subjects(&self) -> Vec; + fn subjects(&self) -> Vec>; fn measurement(&self) -> ReadSignal; fn set_point(&self) -> Signal; @@ -293,23 +310,21 @@ pub trait Regulator: ProblemPoser + OutlineItem { } pub struct InversiveDistanceRegulator { - pub subjects: [ElementKey; 2], + pub subjects: [Rc; 2], pub measurement: ReadSignal, pub set_point: Signal } impl InversiveDistanceRegulator { - pub fn new(subjects: [ElementKey; 2], assembly: &Assembly) -> InversiveDistanceRegulator { - let measurement = assembly.elements.map( - move |elts| { - let representations = subjects.map(|subj| elts[subj].representation()); - representations[0].with(|rep_0| - representations[1].with(|rep_1| - rep_0.dot(&(&*Q * rep_1)) - ) + pub fn new(subjects: [Rc; 2], assembly: &Assembly) -> InversiveDistanceRegulator { + let representations = subjects.each_ref().map(|subj| subj.representation()); + let measurement = create_memo(move || { + representations[0].with(|rep_0| + representations[1].with(|rep_1| + rep_0.dot(&(&*Q * rep_1)) ) - } - ); + ) + }); let set_point = create_signal(SpecifiedValue::from_empty_spec()); @@ -318,8 +333,8 @@ impl InversiveDistanceRegulator { } impl Regulator for InversiveDistanceRegulator { - fn subjects(&self) -> Vec { - self.subjects.into() + fn subjects(&self) -> Vec> { + self.subjects.clone().into() } fn measurement(&self) -> ReadSignal { @@ -335,8 +350,8 @@ impl ProblemPoser for InversiveDistanceRegulator { fn pose(&self, problem: &mut ConstraintProblem, elts: &Slab>) { self.set_point.with_untracked(|set_pt| { if let Some(val) = set_pt.value { - let [row, col] = self.subjects.map( - |subj| elts[subj].column_index().expect( + let [row, col] = self.subjects.each_ref().map( + |subj| subj.column_index().expect( "Subjects should be indexed before inversive distance regulator writes problem data" ) ); @@ -347,17 +362,15 @@ impl ProblemPoser for InversiveDistanceRegulator { } pub struct HalfCurvatureRegulator { - pub subject: ElementKey, + pub subject: Rc, pub measurement: ReadSignal, pub set_point: Signal } impl HalfCurvatureRegulator { - pub fn new(subject: ElementKey, assembly: &Assembly) -> HalfCurvatureRegulator { - let measurement = assembly.elements.map( - move |elts| elts[subject].representation().with( - |rep| rep[Sphere::CURVATURE_COMPONENT] - ) + pub fn new(subject: Rc, assembly: &Assembly) -> HalfCurvatureRegulator { + let measurement = subject.representation().map( + |rep| rep[Sphere::CURVATURE_COMPONENT] ); let set_point = create_signal(SpecifiedValue::from_empty_spec()); @@ -367,8 +380,8 @@ impl HalfCurvatureRegulator { } impl Regulator for HalfCurvatureRegulator { - fn subjects(&self) -> Vec { - vec![self.subject] + fn subjects(&self) -> Vec> { + vec![self.subject.clone()] } fn measurement(&self) -> ReadSignal { @@ -382,10 +395,7 @@ impl Regulator for HalfCurvatureRegulator { fn try_activate(&self, assembly: &Assembly) -> bool { match self.set_point.with(|set_pt| set_pt.value) { Some(half_curv) => { - let representation = assembly.elements.with_untracked( - |elts| elts[self.subject].representation() - ); - representation.update( + self.subject.representation().update( |rep| change_half_curvature(rep, half_curv) ); true @@ -399,7 +409,7 @@ impl ProblemPoser for HalfCurvatureRegulator { fn pose(&self, problem: &mut ConstraintProblem, elts: &Slab>) { self.set_point.with_untracked(|set_pt| { if let Some(val) = set_pt.value { - let col = elts[self.subject].column_index().expect( + let col = self.subject.column_index().expect( "Subject should be indexed before half-curvature regulator writes problem data" ); problem.frozen.push(Sphere::CURVATURE_COMPONENT, col, val); @@ -410,7 +420,7 @@ impl ProblemPoser for HalfCurvatureRegulator { // the velocity is expressed in uniform coordinates pub struct ElementMotion<'a> { - pub key: ElementKey, + pub element: Rc, pub velocity: DVectorView<'a, f64> } @@ -454,14 +464,15 @@ impl Assembly { // 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_element_unchecked(&self, elt: T) -> ElementKey { + fn insert_element_unchecked(&self, elt: impl Element + 'static) -> ElementKey { // insert the element let id = elt.id().clone(); - let key = self.elements.update(|elts| elts.insert(Rc::new(elt))); + let elt_rc = Rc::new(elt); + let key = self.elements.update(|elts| elts.insert(elt_rc.clone())); /* KLUDGE */ // reorganize to avoid cloning? self.elements_by_id.update(|elts_by_id| elts_by_id.insert(id, key)); // create and insert the element's default regulators - for reg in T::default_regulators(key, &self) { + for reg in elt_rc.default_regulators(&self) { self.insert_regulator(reg); } @@ -503,11 +514,9 @@ impl Assembly { // add the regulator to each subject's regulator list let subjects = regulator.subjects(); - let subject_regulators: Vec<_> = self.elements.with_untracked( - |elts| subjects.into_iter().map( - |subj| elts[subj].regulators() - ).collect() - ); + let subject_regulators: Vec<_> = subjects.into_iter().map( + |subj| subj.regulators() + ).collect(); for regulators in subject_regulators { regulators.update(|regs| regs.insert(key)); } @@ -646,17 +655,17 @@ impl Assembly { // in the process, we find out how many matrix columns we'll need to // hold the deformation let realized_dim = self.tangent.with(|tan| tan.assembly_dim()); - let motion_dim = self.elements.update_silent(|elts| { + let motion_dim = { let mut next_column_index = realized_dim; for elt_motion in motion.iter() { - let moving_elt = &mut elts[elt_motion.key]; + let moving_elt = &elt_motion.element; if moving_elt.column_index().is_none() { moving_elt.set_column_index(next_column_index); next_column_index += 1; } } next_column_index - }); + }; // project the element motions onto the tangent space of the solution // variety and sum them to get a deformation of the whole assembly. the @@ -667,9 +676,7 @@ impl Assembly { for elt_motion in motion { // we can unwrap the column index because we know that every moving // element has one at this point - let column_index = self.elements.with_untracked( - |elts| elts[elt_motion.key].column_index().unwrap() - ); + let column_index = elt_motion.element.column_index().unwrap(); if column_index < realized_dim { // this element had a column index when we started, so by @@ -682,12 +689,8 @@ impl Assembly { // this element didn't have a column index when we started, so // by invariant (2), it's unconstrained let mut target_column = motion_proj.column_mut(column_index); - let unif_to_std = self.elements.with_untracked( - |elts| { - elts[elt_motion.key].representation().with_untracked( - |rep| local_unif_to_std(rep.as_view()) - ) - } + let unif_to_std = elt_motion.element.representation().with_untracked( + |rep| local_unif_to_std(rep.as_view()) ); target_column += unif_to_std * elt_motion.velocity; } diff --git a/app-proto/src/display.rs b/app-proto/src/display.rs index 51b207d..c6a5441 100644 --- a/app-proto/src/display.rs +++ b/app-proto/src/display.rs @@ -1,5 +1,6 @@ use core::array; use nalgebra::{DMatrix, DVector, Rotation3, Vector3}; +use std::rc::Rc; use sycamore::{prelude::*, motion::create_raf}; use web_sys::{ console, @@ -16,7 +17,7 @@ use web_sys::{ use crate::{ AppState, - assembly::{ElementKey, ElementColor, ElementMotion, Point, Sphere} + assembly::{Element, ElementColor, ElementMotion, Point, Sphere} }; // --- scene data --- @@ -548,7 +549,7 @@ pub fn Display() -> View { // manipulate the assembly if state.selection.with(|sel| sel.len() == 1) { let sel = state.selection.with( - |sel| *sel.into_iter().next().unwrap() + |sel| sel.into_iter().next().unwrap().clone() ); let translate_x = translate_pos_x_val - translate_neg_x_val; let translate_y = translate_pos_y_val - translate_neg_y_val; @@ -574,7 +575,7 @@ pub fn Display() -> View { assembly_for_raf.deform( vec![ ElementMotion { - key: sel, + element: sel, velocity: elt_motion.as_view() } ] @@ -615,8 +616,8 @@ pub fn Display() -> View { // set up the scene state.assembly.elements.with_untracked( - |elts| for (key, elt) in elts { - let selected = state.selection.with(|sel| sel.contains(&key)); + |elts| for (_, elt) in elts { + let selected = state.selection.with(|sel| sel.contains(elt)); elt.show(&mut scene, selected); } ); @@ -849,16 +850,16 @@ pub fn Display() -> View { // find the nearest element along the pointer direction let (dir, pixel_size) = event_dir(&event); console::log_1(&JsValue::from(dir.to_string())); - let mut clicked: Option<(ElementKey, f64)> = None; - for (key, elt) in state.assembly.elements.get_clone_untracked() { + let mut clicked: Option<(Rc, f64)> = None; + for (_, elt) in state.assembly.elements.get_clone_untracked() { match assembly_to_world.with(|asm_to_world| elt.cast(dir, asm_to_world, pixel_size)) { Some(depth) => match clicked { Some((_, best_depth)) => { if depth < best_depth { - clicked = Some((key, depth)) + clicked = Some((elt, depth)) } }, - None => clicked = Some((key, depth)) + None => clicked = Some((elt, depth)) } None => () }; @@ -866,7 +867,7 @@ pub fn Display() -> View { // if we clicked something, select it match clicked { - Some((key, _)) => state.select(key, event.shift_key()), + Some((elt, _)) => state.select(&elt, event.shift_key()), None => state.selection.update(|sel| sel.clear()) }; } diff --git a/app-proto/src/main.rs b/app-proto/src/main.rs index e581997..586d6d0 100644 --- a/app-proto/src/main.rs +++ b/app-proto/src/main.rs @@ -9,17 +9,18 @@ mod specified; mod tests; use rustc_hash::FxHashSet; +use std::rc::Rc; use sycamore::prelude::*; use add_remove::AddRemove; -use assembly::{Assembly, ElementKey}; +use assembly::{Assembly, Element}; use display::Display; use outline::Outline; #[derive(Clone)] struct AppState { assembly: Assembly, - selection: Signal> + selection: Signal>> } impl AppState { @@ -30,20 +31,19 @@ impl AppState { } } - // in single-selection mode, select the element with the given key. in - // multiple-selection mode, toggle whether the element with the given key - // is selected - fn select(&self, key: ElementKey, multi: bool) { + // in single-selection mode, select the given element. in multiple-selection + // mode, toggle whether the given element is selected + fn select(&self, element: &Rc, multi: bool) { if multi { self.selection.update(|sel| { - if !sel.remove(&key) { - sel.insert(key); + if !sel.remove(element) { + sel.insert(element.clone()); } }); } else { self.selection.update(|sel| { sel.clear(); - sel.insert(key); + sel.insert(element.clone()); }); } } diff --git a/app-proto/src/outline.rs b/app-proto/src/outline.rs index 7e7048d..5aeb62b 100644 --- a/app-proto/src/outline.rs +++ b/app-proto/src/outline.rs @@ -91,20 +91,17 @@ fn RegulatorInput(regulator: Rc) -> View { } pub trait OutlineItem { - fn outline_item(self: Rc, element_key: ElementKey) -> View; + fn outline_item(self: Rc, element: Rc) -> View; } impl OutlineItem for InversiveDistanceRegulator { - fn outline_item(self: Rc, element_key: ElementKey) -> View { + fn outline_item(self: Rc, element: Rc) -> View { let state = use_context::(); - let other_subject = if self.subjects[0] == element_key { - self.subjects[1] + let other_subject_label = if self.subjects[0] == element { + self.subjects[1].label() } else { - self.subjects[0] - }; - let other_subject_label = state.assembly.elements.with( - |elts| elts[other_subject].label().clone() - ); + self.subjects[0].label() + }.clone(); view! { li(class="regulator") { div(class="regulator-label") { (other_subject_label) } @@ -117,7 +114,7 @@ impl OutlineItem for InversiveDistanceRegulator { } impl OutlineItem for HalfCurvatureRegulator { - fn outline_item(self: Rc, _element_key: ElementKey) -> View { + fn outline_item(self: Rc, _element: Rc) -> View { view! { li(class="regulator") { div(class="regulator-label") // for spacing @@ -131,21 +128,24 @@ impl OutlineItem for HalfCurvatureRegulator { // a list item that shows a regulator in an outline view of an element #[component(inline_props)] -fn RegulatorOutlineItem(regulator_key: RegulatorKey, element_key: ElementKey) -> View { +fn RegulatorOutlineItem(regulator_key: RegulatorKey, element: Rc) -> View { let state = use_context::(); let regulator = state.assembly.regulators.with( |regs| regs[regulator_key].clone() ); - regulator.outline_item(element_key) + regulator.outline_item(element) } // a list item that shows an element in an outline view of an assembly #[component(inline_props)] fn ElementOutlineItem(key: ElementKey, element: Rc) -> View { let state = use_context::(); - let class = state.selection.map( - move |sel| if sel.contains(&key) { "selected" } else { "" } - ); + let class = { + let element_for_class = element.clone(); + state.selection.map( + move |sel| if sel.contains(&element_for_class) { "selected" } else { "" } + ) + }; let label = element.label().clone(); let representation = element.representation().clone(); let rep_components = move || { @@ -177,10 +177,11 @@ fn ElementOutlineItem(key: ElementKey, element: Rc) -> View { summary( class=class.get(), on:keydown={ + let element_for_handler = element.clone(); move |event: KeyboardEvent| { match event.key().as_str() { "Enter" => { - state.select(key, event.shift_key()); + state.select(&element_for_handler, event.shift_key()); event.prevent_default(); }, "ArrowRight" if regulated.get() => { @@ -207,9 +208,10 @@ fn ElementOutlineItem(key: ElementKey, element: Rc) -> View { div( class="element", on:click={ - let state_clone = state.clone(); + let state_for_handler = state.clone(); + let element_for_handler = element.clone(); move |event: MouseEvent| { - state_clone.select(key, event.shift_key()); + state_for_handler.select(&element_for_handler, event.shift_key()); event.stop_propagation(); event.prevent_default(); } @@ -223,11 +225,14 @@ fn ElementOutlineItem(key: ElementKey, element: Rc) -> View { ul(class="regulators") { Keyed( list=regulator_list, - view=move |reg_key| view! { - RegulatorOutlineItem( - regulator_key=reg_key, - element_key=key - ) + view=move |reg_key| { + let element_for_view = element.clone(); + view! { + RegulatorOutlineItem( + regulator_key=reg_key, + element=element_for_view + ) + } }, key=|reg_key| reg_key.clone() )