Use pointers, not keys, to refer to elements

Use reference-counted pointers, rather than collection keys, to refer to
elements in tasks like specifying the subjects of regulators, handling
selection, and creating interface components.
This commit is contained in:
Aaron Fenyes 2025-05-04 00:09:30 -07:00
parent 17f30d1312
commit ab01c26415
4 changed files with 108 additions and 99 deletions

View file

@ -5,6 +5,9 @@ use std::{
any::{Any, TypeId}, any::{Any, TypeId},
cell::Cell, cell::Cell,
collections::BTreeSet, collections::BTreeSet,
fmt,
fmt::{Debug, Formatter},
hash::{Hash, Hasher},
rc::Rc, rc::Rc,
sync::atomic::{AtomicU64, Ordering} 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 // the regulators that should be created when an element of this type is
// inserted into the given assembly with the given storage key // inserted into the given assembly with the given storage key
/* KLUDGE */ /* KLUDGE */
// right now, this organization makes sense because regulators identify // this organization made sense when regulators identified their subjects by
// their subjects by storage key, so the element has to be inserted before // storage key, so the element has to be inserted before its regulators
// its regulators can be created. if we change the way regulators identify // could be created. now that regulators identify their subjects by pointer,
// their subjects, we should consider refactoring // we should consider refactoring
fn default_regulators(_key: ElementKey, _assembly: &Assembly) -> Vec<Rc<dyn Regulator>> where Self: Sized { fn default_regulators(self: Rc<Self>, _assembly: &Assembly) -> Vec<Rc<dyn Regulator>> where Self: Sized {
Vec::new() Vec::new()
} }
@ -97,12 +100,26 @@ pub trait Element: ProblemPoser + DisplayItem {
fn set_column_index(&self, index: usize); 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<H: Hasher>(&self, state: &mut H) {
self.serial().hash(state)
}
}
impl PartialEq for dyn Element { impl PartialEq for dyn Element {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.serial() == other.serial() self.serial() == other.serial()
} }
} }
impl Eq for dyn Element {}
pub struct Sphere { pub struct Sphere {
pub id: String, pub id: String,
pub label: String, pub label: String,
@ -148,8 +165,8 @@ impl Element for Sphere {
) )
} }
fn default_regulators(key: ElementKey, assembly: &Assembly) -> Vec<Rc<dyn Regulator>> { fn default_regulators(self: Rc<Self>, assembly: &Assembly) -> Vec<Rc<dyn Regulator>> {
vec![Rc::new(HalfCurvatureRegulator::new(key, assembly))] vec![Rc::new(HalfCurvatureRegulator::new(self, assembly))]
} }
fn id(&self) -> &String { fn id(&self) -> &String {
@ -277,7 +294,7 @@ impl ProblemPoser for Point {
} }
pub trait Regulator: ProblemPoser + OutlineItem { pub trait Regulator: ProblemPoser + OutlineItem {
fn subjects(&self) -> Vec<ElementKey>; fn subjects(&self) -> Vec<Rc<dyn Element>>;
fn measurement(&self) -> ReadSignal<f64>; fn measurement(&self) -> ReadSignal<f64>;
fn set_point(&self) -> Signal<SpecifiedValue>; fn set_point(&self) -> Signal<SpecifiedValue>;
@ -293,23 +310,21 @@ pub trait Regulator: ProblemPoser + OutlineItem {
} }
pub struct InversiveDistanceRegulator { pub struct InversiveDistanceRegulator {
pub subjects: [ElementKey; 2], pub subjects: [Rc<dyn Element>; 2],
pub measurement: ReadSignal<f64>, pub measurement: ReadSignal<f64>,
pub set_point: Signal<SpecifiedValue> pub set_point: Signal<SpecifiedValue>
} }
impl InversiveDistanceRegulator { impl InversiveDistanceRegulator {
pub fn new(subjects: [ElementKey; 2], assembly: &Assembly) -> InversiveDistanceRegulator { pub fn new(subjects: [Rc<dyn Element>; 2], assembly: &Assembly) -> InversiveDistanceRegulator {
let measurement = assembly.elements.map( let representations = subjects.each_ref().map(|subj| subj.representation());
move |elts| { let measurement = create_memo(move || {
let representations = subjects.map(|subj| elts[subj].representation()); representations[0].with(|rep_0|
representations[0].with(|rep_0| representations[1].with(|rep_1|
representations[1].with(|rep_1| rep_0.dot(&(&*Q * rep_1))
rep_0.dot(&(&*Q * rep_1))
)
) )
} )
); });
let set_point = create_signal(SpecifiedValue::from_empty_spec()); let set_point = create_signal(SpecifiedValue::from_empty_spec());
@ -318,8 +333,8 @@ impl InversiveDistanceRegulator {
} }
impl Regulator for InversiveDistanceRegulator { impl Regulator for InversiveDistanceRegulator {
fn subjects(&self) -> Vec<ElementKey> { fn subjects(&self) -> Vec<Rc<dyn Element>> {
self.subjects.into() self.subjects.clone().into()
} }
fn measurement(&self) -> ReadSignal<f64> { fn measurement(&self) -> ReadSignal<f64> {
@ -335,8 +350,8 @@ impl ProblemPoser for InversiveDistanceRegulator {
fn pose(&self, problem: &mut ConstraintProblem, elts: &Slab<Rc<dyn Element>>) { fn pose(&self, problem: &mut ConstraintProblem, elts: &Slab<Rc<dyn Element>>) {
self.set_point.with_untracked(|set_pt| { self.set_point.with_untracked(|set_pt| {
if let Some(val) = set_pt.value { if let Some(val) = set_pt.value {
let [row, col] = self.subjects.map( let [row, col] = self.subjects.each_ref().map(
|subj| elts[subj].column_index().expect( |subj| subj.column_index().expect(
"Subjects should be indexed before inversive distance regulator writes problem data" "Subjects should be indexed before inversive distance regulator writes problem data"
) )
); );
@ -347,17 +362,15 @@ impl ProblemPoser for InversiveDistanceRegulator {
} }
pub struct HalfCurvatureRegulator { pub struct HalfCurvatureRegulator {
pub subject: ElementKey, pub subject: Rc<dyn Element>,
pub measurement: ReadSignal<f64>, pub measurement: ReadSignal<f64>,
pub set_point: Signal<SpecifiedValue> pub set_point: Signal<SpecifiedValue>
} }
impl HalfCurvatureRegulator { impl HalfCurvatureRegulator {
pub fn new(subject: ElementKey, assembly: &Assembly) -> HalfCurvatureRegulator { pub fn new(subject: Rc<dyn Element>, assembly: &Assembly) -> HalfCurvatureRegulator {
let measurement = assembly.elements.map( let measurement = subject.representation().map(
move |elts| elts[subject].representation().with( |rep| rep[Sphere::CURVATURE_COMPONENT]
|rep| rep[Sphere::CURVATURE_COMPONENT]
)
); );
let set_point = create_signal(SpecifiedValue::from_empty_spec()); let set_point = create_signal(SpecifiedValue::from_empty_spec());
@ -367,8 +380,8 @@ impl HalfCurvatureRegulator {
} }
impl Regulator for HalfCurvatureRegulator { impl Regulator for HalfCurvatureRegulator {
fn subjects(&self) -> Vec<ElementKey> { fn subjects(&self) -> Vec<Rc<dyn Element>> {
vec![self.subject] vec![self.subject.clone()]
} }
fn measurement(&self) -> ReadSignal<f64> { fn measurement(&self) -> ReadSignal<f64> {
@ -382,10 +395,7 @@ impl Regulator for HalfCurvatureRegulator {
fn try_activate(&self, assembly: &Assembly) -> bool { fn try_activate(&self, assembly: &Assembly) -> bool {
match self.set_point.with(|set_pt| set_pt.value) { match self.set_point.with(|set_pt| set_pt.value) {
Some(half_curv) => { Some(half_curv) => {
let representation = assembly.elements.with_untracked( self.subject.representation().update(
|elts| elts[self.subject].representation()
);
representation.update(
|rep| change_half_curvature(rep, half_curv) |rep| change_half_curvature(rep, half_curv)
); );
true true
@ -399,7 +409,7 @@ impl ProblemPoser for HalfCurvatureRegulator {
fn pose(&self, problem: &mut ConstraintProblem, elts: &Slab<Rc<dyn Element>>) { fn pose(&self, problem: &mut ConstraintProblem, elts: &Slab<Rc<dyn Element>>) {
self.set_point.with_untracked(|set_pt| { self.set_point.with_untracked(|set_pt| {
if let Some(val) = set_pt.value { 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" "Subject should be indexed before half-curvature regulator writes problem data"
); );
problem.frozen.push(Sphere::CURVATURE_COMPONENT, col, val); problem.frozen.push(Sphere::CURVATURE_COMPONENT, col, val);
@ -410,7 +420,7 @@ impl ProblemPoser for HalfCurvatureRegulator {
// the velocity is expressed in uniform coordinates // the velocity is expressed in uniform coordinates
pub struct ElementMotion<'a> { pub struct ElementMotion<'a> {
pub key: ElementKey, pub element: Rc<dyn Element>,
pub velocity: DVectorView<'a, f64> pub velocity: DVectorView<'a, f64>
} }
@ -454,14 +464,15 @@ impl Assembly {
// insert an element 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 // 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 // same identifier will get kicked out of the `elements_by_id` index
fn insert_element_unchecked<T: Element + 'static>(&self, elt: T) -> ElementKey { fn insert_element_unchecked(&self, elt: impl Element + 'static) -> ElementKey {
// insert the element // insert the element
let id = elt.id().clone(); 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)); self.elements_by_id.update(|elts_by_id| elts_by_id.insert(id, key));
// create and insert the element's default regulators // 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); self.insert_regulator(reg);
} }
@ -503,11 +514,9 @@ impl Assembly {
// add the regulator to each subject's regulator list // add the regulator to each subject's regulator list
let subjects = regulator.subjects(); let subjects = regulator.subjects();
let subject_regulators: Vec<_> = self.elements.with_untracked( let subject_regulators: Vec<_> = subjects.into_iter().map(
|elts| subjects.into_iter().map( |subj| subj.regulators()
|subj| elts[subj].regulators() ).collect();
).collect()
);
for regulators in subject_regulators { for regulators in subject_regulators {
regulators.update(|regs| regs.insert(key)); 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 // in the process, we find out how many matrix columns we'll need to
// hold the deformation // hold the deformation
let realized_dim = self.tangent.with(|tan| tan.assembly_dim()); 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; let mut next_column_index = realized_dim;
for elt_motion in motion.iter() { 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() { if moving_elt.column_index().is_none() {
moving_elt.set_column_index(next_column_index); moving_elt.set_column_index(next_column_index);
next_column_index += 1; next_column_index += 1;
} }
} }
next_column_index next_column_index
}); };
// project the element motions onto the tangent space of the solution // project the element motions onto the tangent space of the solution
// variety and sum them to get a deformation of the whole assembly. the // variety and sum them to get a deformation of the whole assembly. the
@ -667,9 +676,7 @@ impl Assembly {
for elt_motion in motion { for elt_motion in motion {
// we can unwrap the column index because we know that every moving // we can unwrap the column index because we know that every moving
// element has one at this point // element has one at this point
let column_index = self.elements.with_untracked( let column_index = elt_motion.element.column_index().unwrap();
|elts| elts[elt_motion.key].column_index().unwrap()
);
if column_index < realized_dim { if column_index < realized_dim {
// this element had a column index when we started, so by // 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 // this element didn't have a column index when we started, so
// by invariant (2), it's unconstrained // by invariant (2), it's unconstrained
let mut target_column = motion_proj.column_mut(column_index); let mut target_column = motion_proj.column_mut(column_index);
let unif_to_std = self.elements.with_untracked( let unif_to_std = elt_motion.element.representation().with_untracked(
|elts| { |rep| local_unif_to_std(rep.as_view())
elts[elt_motion.key].representation().with_untracked(
|rep| local_unif_to_std(rep.as_view())
)
}
); );
target_column += unif_to_std * elt_motion.velocity; target_column += unif_to_std * elt_motion.velocity;
} }

View file

@ -1,5 +1,6 @@
use core::array; use core::array;
use nalgebra::{DMatrix, DVector, Rotation3, Vector3}; use nalgebra::{DMatrix, DVector, Rotation3, Vector3};
use std::rc::Rc;
use sycamore::{prelude::*, motion::create_raf}; use sycamore::{prelude::*, motion::create_raf};
use web_sys::{ use web_sys::{
console, console,
@ -16,7 +17,7 @@ use web_sys::{
use crate::{ use crate::{
AppState, AppState,
assembly::{ElementKey, ElementColor, ElementMotion, Point, Sphere} assembly::{Element, ElementColor, ElementMotion, Point, Sphere}
}; };
// --- scene data --- // --- scene data ---
@ -548,7 +549,7 @@ pub fn Display() -> View {
// manipulate the assembly // manipulate the assembly
if state.selection.with(|sel| sel.len() == 1) { if state.selection.with(|sel| sel.len() == 1) {
let sel = state.selection.with( 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_x = translate_pos_x_val - translate_neg_x_val;
let translate_y = translate_pos_y_val - translate_neg_y_val; let translate_y = translate_pos_y_val - translate_neg_y_val;
@ -574,7 +575,7 @@ pub fn Display() -> View {
assembly_for_raf.deform( assembly_for_raf.deform(
vec![ vec![
ElementMotion { ElementMotion {
key: sel, element: sel,
velocity: elt_motion.as_view() velocity: elt_motion.as_view()
} }
] ]
@ -615,8 +616,8 @@ pub fn Display() -> View {
// set up the scene // set up the scene
state.assembly.elements.with_untracked( state.assembly.elements.with_untracked(
|elts| for (key, elt) in elts { |elts| for (_, elt) in elts {
let selected = state.selection.with(|sel| sel.contains(&key)); let selected = state.selection.with(|sel| sel.contains(elt));
elt.show(&mut scene, selected); elt.show(&mut scene, selected);
} }
); );
@ -849,16 +850,16 @@ pub fn Display() -> View {
// find the nearest element along the pointer direction // find the nearest element along the pointer direction
let (dir, pixel_size) = event_dir(&event); let (dir, pixel_size) = event_dir(&event);
console::log_1(&JsValue::from(dir.to_string())); console::log_1(&JsValue::from(dir.to_string()));
let mut clicked: Option<(ElementKey, f64)> = None; let mut clicked: Option<(Rc<dyn Element>, f64)> = None;
for (key, elt) in state.assembly.elements.get_clone_untracked() { 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)) { match assembly_to_world.with(|asm_to_world| elt.cast(dir, asm_to_world, pixel_size)) {
Some(depth) => match clicked { Some(depth) => match clicked {
Some((_, best_depth)) => { Some((_, best_depth)) => {
if depth < 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 => () None => ()
}; };
@ -866,7 +867,7 @@ pub fn Display() -> View {
// if we clicked something, select it // if we clicked something, select it
match clicked { 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()) None => state.selection.update(|sel| sel.clear())
}; };
} }

View file

@ -9,17 +9,18 @@ mod specified;
mod tests; mod tests;
use rustc_hash::FxHashSet; use rustc_hash::FxHashSet;
use std::rc::Rc;
use sycamore::prelude::*; use sycamore::prelude::*;
use add_remove::AddRemove; use add_remove::AddRemove;
use assembly::{Assembly, ElementKey}; use assembly::{Assembly, Element};
use display::Display; use display::Display;
use outline::Outline; use outline::Outline;
#[derive(Clone)] #[derive(Clone)]
struct AppState { struct AppState {
assembly: Assembly, assembly: Assembly,
selection: Signal<FxHashSet<ElementKey>> selection: Signal<FxHashSet<Rc<dyn Element>>>
} }
impl AppState { impl AppState {
@ -30,20 +31,19 @@ impl AppState {
} }
} }
// in single-selection mode, select the element with the given key. in // in single-selection mode, select the given element. in multiple-selection
// multiple-selection mode, toggle whether the element with the given key // mode, toggle whether the given element is selected
// is selected fn select(&self, element: &Rc<dyn Element>, multi: bool) {
fn select(&self, key: ElementKey, multi: bool) {
if multi { if multi {
self.selection.update(|sel| { self.selection.update(|sel| {
if !sel.remove(&key) { if !sel.remove(element) {
sel.insert(key); sel.insert(element.clone());
} }
}); });
} else { } else {
self.selection.update(|sel| { self.selection.update(|sel| {
sel.clear(); sel.clear();
sel.insert(key); sel.insert(element.clone());
}); });
} }
} }

View file

@ -91,20 +91,17 @@ fn RegulatorInput(regulator: Rc<dyn Regulator>) -> View {
} }
pub trait OutlineItem { pub trait OutlineItem {
fn outline_item(self: Rc<Self>, element_key: ElementKey) -> View; fn outline_item(self: Rc<Self>, element: Rc<dyn Element>) -> View;
} }
impl OutlineItem for InversiveDistanceRegulator { impl OutlineItem for InversiveDistanceRegulator {
fn outline_item(self: Rc<Self>, element_key: ElementKey) -> View { fn outline_item(self: Rc<Self>, element: Rc<dyn Element>) -> View {
let state = use_context::<AppState>(); let state = use_context::<AppState>();
let other_subject = if self.subjects[0] == element_key { let other_subject_label = if self.subjects[0] == element {
self.subjects[1] self.subjects[1].label()
} else { } else {
self.subjects[0] self.subjects[0].label()
}; }.clone();
let other_subject_label = state.assembly.elements.with(
|elts| elts[other_subject].label().clone()
);
view! { view! {
li(class="regulator") { li(class="regulator") {
div(class="regulator-label") { (other_subject_label) } div(class="regulator-label") { (other_subject_label) }
@ -117,7 +114,7 @@ impl OutlineItem for InversiveDistanceRegulator {
} }
impl OutlineItem for HalfCurvatureRegulator { impl OutlineItem for HalfCurvatureRegulator {
fn outline_item(self: Rc<Self>, _element_key: ElementKey) -> View { fn outline_item(self: Rc<Self>, _element: Rc<dyn Element>) -> View {
view! { view! {
li(class="regulator") { li(class="regulator") {
div(class="regulator-label") // for spacing 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 // a list item that shows a regulator in an outline view of an element
#[component(inline_props)] #[component(inline_props)]
fn RegulatorOutlineItem(regulator_key: RegulatorKey, element_key: ElementKey) -> View { fn RegulatorOutlineItem(regulator_key: RegulatorKey, element: Rc<dyn Element>) -> View {
let state = use_context::<AppState>(); let state = use_context::<AppState>();
let regulator = state.assembly.regulators.with( let regulator = state.assembly.regulators.with(
|regs| regs[regulator_key].clone() |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 // a list item that shows an element in an outline view of an assembly
#[component(inline_props)] #[component(inline_props)]
fn ElementOutlineItem(key: ElementKey, element: Rc<dyn Element>) -> View { fn ElementOutlineItem(key: ElementKey, element: Rc<dyn Element>) -> View {
let state = use_context::<AppState>(); let state = use_context::<AppState>();
let class = state.selection.map( let class = {
move |sel| if sel.contains(&key) { "selected" } else { "" } 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 label = element.label().clone();
let representation = element.representation().clone(); let representation = element.representation().clone();
let rep_components = move || { let rep_components = move || {
@ -177,10 +177,11 @@ fn ElementOutlineItem(key: ElementKey, element: Rc<dyn Element>) -> View {
summary( summary(
class=class.get(), class=class.get(),
on:keydown={ on:keydown={
let element_for_handler = element.clone();
move |event: KeyboardEvent| { move |event: KeyboardEvent| {
match event.key().as_str() { match event.key().as_str() {
"Enter" => { "Enter" => {
state.select(key, event.shift_key()); state.select(&element_for_handler, event.shift_key());
event.prevent_default(); event.prevent_default();
}, },
"ArrowRight" if regulated.get() => { "ArrowRight" if regulated.get() => {
@ -207,9 +208,10 @@ fn ElementOutlineItem(key: ElementKey, element: Rc<dyn Element>) -> View {
div( div(
class="element", class="element",
on:click={ on:click={
let state_clone = state.clone(); let state_for_handler = state.clone();
let element_for_handler = element.clone();
move |event: MouseEvent| { move |event: MouseEvent| {
state_clone.select(key, event.shift_key()); state_for_handler.select(&element_for_handler, event.shift_key());
event.stop_propagation(); event.stop_propagation();
event.prevent_default(); event.prevent_default();
} }
@ -223,11 +225,14 @@ fn ElementOutlineItem(key: ElementKey, element: Rc<dyn Element>) -> View {
ul(class="regulators") { ul(class="regulators") {
Keyed( Keyed(
list=regulator_list, list=regulator_list,
view=move |reg_key| view! { view=move |reg_key| {
RegulatorOutlineItem( let element_for_view = element.clone();
regulator_key=reg_key, view! {
element_key=key RegulatorOutlineItem(
) regulator_key=reg_key,
element=element_for_view
)
}
}, },
key=|reg_key| reg_key.clone() key=|reg_key| reg_key.clone()
) )