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

View file

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

View file

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

View file

@ -363,7 +363,7 @@ pub fn Display() -> View {
let scene_changed = create_signal(true);
create_effect(move || {
state.assembly.elements.with(|elts| {
for (_, elt) in elts {
for elt in elts {
elt.representation().track();
}
});
@ -616,7 +616,7 @@ pub fn Display() -> View {
// set up the scene
state.assembly.elements.with_untracked(
|elts| for (_, elt) in elts {
|elts| for elt in elts {
let selected = state.selection.with(|sel| sel.contains(elt));
elt.show(&mut scene, selected);
}
@ -851,7 +851,7 @@ pub fn Display() -> View {
let (dir, pixel_size) = event_dir(&event);
console::log_1(&JsValue::from(dir.to_string()));
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)) {
Some(depth) => match clicked {
Some((_, best_depth)) => {

View file

@ -228,11 +228,15 @@ pub fn Outline() -> View {
let state = use_context::<AppState>();
// 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(
|elts| elts
.clone()
.into_iter()
.sorted_by_key(|(_, elt)| elt.id().clone())
.sorted_by_key(|elt| elt.id().clone())
.collect()
);
@ -246,10 +250,10 @@ pub fn Outline() -> View {
) {
Keyed(
list=element_list,
view=|(_, elt)| view! {
view=|elt| view! {
ElementOutlineItem(element=elt)
},
key=|(_, elt)| elt.serial()
key=|elt| elt.serial()
)
}
}