Store elements and regulators without keys

Since we're no longer using storage keys to refer to elements and
regulators, we don't need to store these items in keyed collections
anymore.

To keep element and regulator pointers in `BTreeSet` collections, we
implement `Ord` for `Serial` trait objects. As a bonus, this lets us
turn the element-wise regulator collections back into `BTreeSet`.
This commit is contained in:
Aaron Fenyes 2025-05-04 12:24:17 -07:00
parent 8a86038de0
commit c6b628d424
5 changed files with 74 additions and 50 deletions

12
app-proto/Cargo.lock generated
View file

@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo. # This file is automatically @generated by Cargo.
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 4
[[package]] [[package]]
name = "ahash" name = "ahash"
@ -90,7 +90,6 @@ dependencies = [
"nalgebra", "nalgebra",
"readonly", "readonly",
"rustc-hash", "rustc-hash",
"slab",
"sycamore", "sycamore",
"wasm-bindgen-test", "wasm-bindgen-test",
"web-sys", "web-sys",
@ -414,15 +413,6 @@ dependencies = [
"wide", "wide",
] ]
[[package]]
name = "slab"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
dependencies = [
"autocfg",
]
[[package]] [[package]]
name = "slotmap" name = "slotmap"
version = "1.0.7" version = "1.0.7"

View file

@ -16,7 +16,6 @@ lazy_static = "1.5.0"
nalgebra = "0.33.0" nalgebra = "0.33.0"
readonly = "0.2.12" readonly = "0.2.12"
rustc-hash = "2.0.0" rustc-hash = "2.0.0"
slab = "0.4.9"
sycamore = "0.9.0-beta.3" sycamore = "0.9.0-beta.3"
# The `console_error_panic_hook` crate provides better debugging of panics by # The `console_error_panic_hook` crate provides better debugging of panics by

View file

@ -1,14 +1,15 @@
use nalgebra::{DMatrix, DVector, DVectorView}; use nalgebra::{DMatrix, DVector, DVectorView};
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use slab::Slab;
use std::{ use std::{
any::{Any, TypeId}, any::{Any, TypeId},
cell::Cell, cell::Cell,
collections::BTreeSet,
cmp::Ordering,
fmt, fmt,
fmt::{Debug, Formatter}, fmt::{Debug, Formatter},
hash::{Hash, Hasher}, hash::{Hash, Hasher},
rc::Rc, rc::Rc,
sync::atomic::{AtomicU64, Ordering} sync::{atomic, atomic::AtomicU64}
}; };
use sycamore::prelude::*; use sycamore::prelude::*;
use web_sys::{console, wasm_bindgen::JsValue}; /* DEBUG */ use web_sys::{console, wasm_bindgen::JsValue}; /* DEBUG */
@ -29,9 +30,6 @@ use crate::{
specified::SpecifiedValue specified::SpecifiedValue
}; };
// the types of the keys we use to access an assembly's elements
pub type ElementKey = usize;
pub type ElementColor = [f32; 3]; pub type ElementColor = [f32; 3];
/* KLUDGE */ /* KLUDGE */
@ -53,7 +51,7 @@ pub trait Serial {
// https://marabos.nl/atomics/atomics.html#example-handle-overflow // https://marabos.nl/atomics/atomics.html#example-handle-overflow
// //
NEXT_SERIAL.fetch_update( NEXT_SERIAL.fetch_update(
Ordering::SeqCst, Ordering::SeqCst, atomic::Ordering::SeqCst, atomic::Ordering::SeqCst,
|serial| serial.checked_add(1) |serial| serial.checked_add(1)
).expect("Out of serial numbers for elements") ).expect("Out of serial numbers for elements")
} }
@ -73,6 +71,18 @@ impl PartialEq for dyn Serial {
impl Eq for dyn Serial {} impl Eq for dyn Serial {}
impl PartialOrd for dyn Serial {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for dyn Serial {
fn cmp(&self, other: &Self) -> Ordering {
self.serial().cmp(&other.serial())
}
}
pub trait ProblemPoser { pub trait ProblemPoser {
fn pose(&self, problem: &mut ConstraintProblem); fn pose(&self, problem: &mut ConstraintProblem);
} }
@ -95,7 +105,7 @@ pub trait Element: Serial + ProblemPoser + DisplayItem {
// the regulators the element is subject to. the assembly that owns the // the regulators the element is subject to. the assembly that owns the
// element is responsible for keeping this set up to date // element is responsible for keeping this set up to date
fn regulators(&self) -> Signal<Vec<Rc<dyn Regulator>>>; fn regulators(&self) -> Signal<BTreeSet<Rc<dyn Regulator>>>;
// the configuration matrix column index that was assigned to the element // the configuration matrix column index that was assigned to the element
// last time the assembly was realized, or `None` if the element has never // last time the assembly was realized, or `None` if the element has never
@ -128,12 +138,24 @@ impl PartialEq for dyn Element {
impl Eq for dyn Element {} impl Eq for dyn Element {}
impl PartialOrd for dyn Element {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
<dyn Serial>::partial_cmp(self, other)
}
}
impl Ord for dyn Element {
fn cmp(&self, other: &Self) -> Ordering {
<dyn Serial>::cmp(self, other)
}
}
pub struct Sphere { pub struct Sphere {
pub id: String, pub id: String,
pub label: String, pub label: String,
pub color: ElementColor, pub color: ElementColor,
pub representation: Signal<DVector<f64>>, pub representation: Signal<DVector<f64>>,
pub regulators: Signal<Vec<Rc<dyn Regulator>>>, pub regulators: Signal<BTreeSet<Rc<dyn Regulator>>>,
serial: u64, serial: u64,
column_index: Cell<Option<usize>> column_index: Cell<Option<usize>>
} }
@ -152,7 +174,7 @@ impl Sphere {
label: label, label: label,
color: color, color: color,
representation: create_signal(representation), representation: create_signal(representation),
regulators: create_signal(Vec::new()), regulators: create_signal(BTreeSet::new()),
serial: Self::next_serial(), serial: Self::next_serial(),
column_index: None.into() column_index: None.into()
} }
@ -189,7 +211,7 @@ impl Element for Sphere {
self.representation self.representation
} }
fn regulators(&self) -> Signal<Vec<Rc<dyn Regulator>>> { fn regulators(&self) -> Signal<BTreeSet<Rc<dyn Regulator>>> {
self.regulators self.regulators
} }
@ -223,7 +245,7 @@ pub struct Point {
pub label: String, pub label: String,
pub color: ElementColor, pub color: ElementColor,
pub representation: Signal<DVector<f64>>, pub representation: Signal<DVector<f64>>,
pub regulators: Signal<Vec<Rc<dyn Regulator>>>, pub regulators: Signal<BTreeSet<Rc<dyn Regulator>>>,
serial: u64, serial: u64,
column_index: Cell<Option<usize>> column_index: Cell<Option<usize>>
} }
@ -242,7 +264,7 @@ impl Point {
label, label,
color, color,
representation: create_signal(representation), representation: create_signal(representation),
regulators: create_signal(Vec::new()), regulators: create_signal(BTreeSet::new()),
serial: Self::next_serial(), serial: Self::next_serial(),
column_index: None.into() column_index: None.into()
} }
@ -275,7 +297,7 @@ impl Element for Point {
self.representation self.representation
} }
fn regulators(&self) -> Signal<Vec<Rc<dyn Regulator>>> { fn regulators(&self) -> Signal<BTreeSet<Rc<dyn Regulator>>> {
self.regulators self.regulators
} }
@ -335,6 +357,18 @@ impl PartialEq for dyn Regulator {
impl Eq for dyn Regulator {} impl Eq for dyn Regulator {}
impl PartialOrd for dyn Regulator {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
<dyn Serial>::partial_cmp(self, other)
}
}
impl Ord for dyn Regulator {
fn cmp(&self, other: &Self) -> Ordering {
<dyn Serial>::cmp(self, other)
}
}
pub struct InversiveDistanceRegulator { pub struct InversiveDistanceRegulator {
pub subjects: [Rc<dyn Element>; 2], pub subjects: [Rc<dyn Element>; 2],
pub measurement: ReadSignal<f64>, pub measurement: ReadSignal<f64>,
@ -472,8 +506,8 @@ type AssemblyMotion<'a> = Vec<ElementMotion<'a>>;
#[derive(Clone)] #[derive(Clone)]
pub struct Assembly { pub struct Assembly {
// elements and regulators // elements and regulators
pub elements: Signal<Slab<Rc<dyn Element>>>, pub elements: Signal<BTreeSet<Rc<dyn Element>>>,
pub regulators: Signal<Slab<Rc<dyn Regulator>>>, pub regulators: Signal<BTreeSet<Rc<dyn Regulator>>>,
// solution variety tangent space. the basis vectors are stored in // solution variety tangent space. the basis vectors are stored in
// configuration matrix format, ordered according to the elements' column // configuration matrix format, ordered according to the elements' column
@ -494,8 +528,8 @@ pub struct Assembly {
impl Assembly { impl Assembly {
pub fn new() -> Assembly { pub fn new() -> Assembly {
Assembly { Assembly {
elements: create_signal(Slab::new()), elements: create_signal(BTreeSet::new()),
regulators: create_signal(Slab::new()), regulators: create_signal(BTreeSet::new()),
tangent: create_signal(ConfigSubspace::zero(0)), tangent: create_signal(ConfigSubspace::zero(0)),
elements_by_id: create_signal(FxHashMap::default()) elements_by_id: create_signal(FxHashMap::default())
} }
@ -506,30 +540,27 @@ 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(&self, elt: impl Element + 'static) -> ElementKey { fn insert_element_unchecked(&self, elt: impl Element + 'static) {
// insert the element // insert the element
let id = elt.id().clone(); let id = elt.id().clone();
let elt_rc = Rc::new(elt); let elt_rc = Rc::new(elt);
let key = self.elements.update(|elts| elts.insert(elt_rc.clone())); self.elements.update(|elts| elts.insert(elt_rc.clone()));
self.elements_by_id.update(|elts_by_id| elts_by_id.insert(id, elt_rc.clone())); self.elements_by_id.update(|elts_by_id| elts_by_id.insert(id, elt_rc.clone()));
// create and insert the element's default regulators // create and insert the element's default regulators
for reg in elt_rc.default_regulators() { for reg in elt_rc.default_regulators() {
self.insert_regulator(reg); self.insert_regulator(reg);
} }
key
} }
pub fn try_insert_element(&self, elt: impl Element + 'static) -> Option<ElementKey> { pub fn try_insert_element(&self, elt: impl Element + 'static) -> bool {
let can_insert = self.elements_by_id.with_untracked( let can_insert = self.elements_by_id.with_untracked(
|elts_by_id| !elts_by_id.contains_key(elt.id()) |elts_by_id| !elts_by_id.contains_key(elt.id())
); );
if can_insert { if can_insert {
Some(self.insert_element_unchecked(elt)) self.insert_element_unchecked(elt);
} else {
None
} }
can_insert
} }
pub fn insert_element_default<T: Element + 'static>(&self) { pub fn insert_element_default<T: Element + 'static>(&self) {
@ -559,7 +590,7 @@ impl Assembly {
|subj| subj.regulators() |subj| subj.regulators()
).collect(); ).collect();
for regulators in subject_regulators { for regulators in subject_regulators {
regulators.update(|regs| regs.push(regulator.clone())); regulators.update(|regs| regs.insert(regulator.clone()));
} }
// update the realization when the regulator becomes a constraint, or is // update the realization when the regulator becomes a constraint, or is
@ -581,7 +612,7 @@ impl Assembly {
// print an updated list of regulators // print an updated list of regulators
console::log_1(&JsValue::from("Regulators:")); console::log_1(&JsValue::from("Regulators:"));
self.regulators.with_untracked(|regs| { self.regulators.with_untracked(|regs| {
for (_, reg) in regs.into_iter() { for reg in regs.into_iter() {
console::log_1(&JsValue::from(format!( console::log_1(&JsValue::from(format!(
" {:?}: {}", " {:?}: {}",
reg.subjects(), reg.subjects(),
@ -605,7 +636,7 @@ impl Assembly {
pub fn realize(&self) { pub fn realize(&self) {
// index the elements // index the elements
self.elements.update_silent(|elts| { self.elements.update_silent(|elts| {
for (index, (_, elt)) in elts.into_iter().enumerate() { for (index, elt) in elts.iter().enumerate() {
elt.set_column_index(index); elt.set_column_index(index);
} }
}); });
@ -613,11 +644,11 @@ impl Assembly {
// set up the constraint problem // set up the constraint problem
let problem = self.elements.with_untracked(|elts| { let problem = self.elements.with_untracked(|elts| {
let mut problem = ConstraintProblem::new(elts.len()); let mut problem = ConstraintProblem::new(elts.len());
for (_, elt) in elts { for elt in elts {
elt.pose(&mut problem); elt.pose(&mut problem);
} }
self.regulators.with_untracked(|regs| { self.regulators.with_untracked(|regs| {
for (_, reg) in regs { for reg in regs {
reg.pose(&mut problem); reg.pose(&mut problem);
} }
}); });
@ -660,7 +691,7 @@ impl Assembly {
if success { if success {
// read out the solution // read out the solution
for (_, elt) in self.elements.get_clone_untracked() { for elt in self.elements.get_clone_untracked() {
elt.representation().update( elt.representation().update(
|rep| rep.set_column(0, &config.column(elt.column_index().unwrap())) |rep| rep.set_column(0, &config.column(elt.column_index().unwrap()))
); );
@ -741,7 +772,7 @@ impl Assembly {
// normalizations, so we restore those afterward // normalizations, so we restore those afterward
/* KLUDGE */ /* KLUDGE */
// for now, we only restore the normalizations of spheres // for now, we only restore the normalizations of spheres
for (_, elt) in self.elements.get_clone_untracked() { for elt in self.elements.get_clone_untracked() {
elt.representation().update_silent(|rep| { elt.representation().update_silent(|rep| {
match elt.column_index() { match elt.column_index() {
Some(column_index) => { Some(column_index) => {

View file

@ -363,7 +363,7 @@ pub fn Display() -> View {
let scene_changed = create_signal(true); let scene_changed = create_signal(true);
create_effect(move || { create_effect(move || {
state.assembly.elements.with(|elts| { state.assembly.elements.with(|elts| {
for (_, elt) in elts { for elt in elts {
elt.representation().track(); elt.representation().track();
} }
}); });
@ -616,7 +616,7 @@ pub fn Display() -> View {
// set up the scene // set up the scene
state.assembly.elements.with_untracked( state.assembly.elements.with_untracked(
|elts| for (_, elt) in elts { |elts| for elt in elts {
let selected = state.selection.with(|sel| sel.contains(elt)); let selected = state.selection.with(|sel| sel.contains(elt));
elt.show(&mut scene, selected); elt.show(&mut scene, selected);
} }
@ -851,7 +851,7 @@ pub fn Display() -> View {
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<(Rc<dyn Element>, f64)> = None; let mut clicked: Option<(Rc<dyn Element>, f64)> = None;
for (_, 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)) => {

View file

@ -228,11 +228,15 @@ pub fn Outline() -> View {
let state = use_context::<AppState>(); let state = use_context::<AppState>();
// list the elements alphabetically by ID // list the elements alphabetically by ID
/* TO DO */
// this code is designed to generalize easily to other sort keys. if we only
// ever wanted to sort by ID, we could do that more simply using the
// `elements_by_id` index
let element_list = state.assembly.elements.map( let element_list = state.assembly.elements.map(
|elts| elts |elts| elts
.clone() .clone()
.into_iter() .into_iter()
.sorted_by_key(|(_, elt)| elt.id().clone()) .sorted_by_key(|elt| elt.id().clone())
.collect() .collect()
); );
@ -246,10 +250,10 @@ pub fn Outline() -> View {
) { ) {
Keyed( Keyed(
list=element_list, list=element_list,
view=|(_, elt)| view! { view=|elt| view! {
ElementOutlineItem(element=elt) ElementOutlineItem(element=elt)
}, },
key=|(_, elt)| elt.serial() key=|elt| elt.serial()
) )
} }
} }