Refactor: Use pointers to refer to elements and regulators (#84)

Previously, dyna3 used storage keys to refer to elements, necessitating passing around element containers to various functions so that they could access the relevant elements. These storage keys have been replaced with reference-counted pointers, used for tasks like these:

- Specifying the subjects of regulators.
- Collecting the regulators each element is subject to
- Handling selection.
- Creating interface components.

Also, systematizes the handling of serial numbers for entities, through a Serial trait.
And updates to rust 1.86 and institutes explicit checking of the rust version.

Co-authored-by: Aaron Fenyes <aaron.fenyes@fareycircles.ooo>
Reviewed-on: StudioInfinity/dyna3#84
Co-authored-by: Vectornaut <vectornaut@nobody@nowhere.net>
Co-committed-by: Vectornaut <vectornaut@nobody@nowhere.net>
This commit is contained in:
Vectornaut 2025-05-06 19:17:30 +00:00 committed by Glen Whitney
parent a2478febc1
commit 2adf4669f4
8 changed files with 288 additions and 270 deletions

View file

@ -11,7 +11,7 @@ jobs:
test: test:
runs-on: docker runs-on: docker
container: container:
image: cimg/rust:1.85-node image: cimg/rust:1.86-node
defaults: defaults:
run: run:
# set the default working directory for each `run` step, relative to the # set the default working directory for each `run` step, relative to the

19
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"
@ -89,8 +89,6 @@ dependencies = [
"lazy_static", "lazy_static",
"nalgebra", "nalgebra",
"readonly", "readonly",
"rustc-hash",
"slab",
"sycamore", "sycamore",
"wasm-bindgen-test", "wasm-bindgen-test",
"web-sys", "web-sys",
@ -365,12 +363,6 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "rustc-hash"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152"
[[package]] [[package]]
name = "safe_arch" name = "safe_arch"
version = "0.7.2" version = "0.7.2"
@ -414,15 +406,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

@ -3,6 +3,7 @@ name = "dyna3"
version = "0.1.0" version = "0.1.0"
authors = ["Aaron Fenyes", "Glen Whitney"] authors = ["Aaron Fenyes", "Glen Whitney"]
edition = "2021" edition = "2021"
rust-version = "1.86"
[features] [features]
default = ["console_error_panic_hook"] default = ["console_error_panic_hook"]
@ -14,8 +15,6 @@ js-sys = "0.3.70"
lazy_static = "1.5.0" 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"
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

@ -241,7 +241,7 @@ pub fn AddRemove() -> View {
.unwrap() .unwrap()
); );
state.assembly.insert_regulator( state.assembly.insert_regulator(
Rc::new(InversiveDistanceRegulator::new(subjects, &state.assembly)) Rc::new(InversiveDistanceRegulator::new(subjects))
); );
state.selection.update(|sel| sel.clear()); state.selection.update(|sel| sel.clear());
} }

View file

@ -1,12 +1,14 @@
use nalgebra::{DMatrix, DVector, DVectorView}; use nalgebra::{DMatrix, DVector, DVectorView};
use rustc_hash::FxHashMap;
use slab::Slab;
use std::{ use std::{
any::{Any, TypeId}, any::{Any, TypeId},
cell::Cell, cell::Cell,
collections::BTreeSet, collections::{BTreeMap, BTreeSet},
cmp::Ordering,
fmt,
fmt::{Debug, Formatter},
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 */
@ -27,49 +29,16 @@ use crate::{
specified::SpecifiedValue specified::SpecifiedValue
}; };
// the types of the keys we use to access an assembly's elements and regulators
pub type ElementKey = usize;
pub type RegulatorKey = usize;
pub type ElementColor = [f32; 3]; pub type ElementColor = [f32; 3];
/* KLUDGE */ /* KLUDGE */
// we should reconsider this design when we build a system for switching between // we should reconsider this design when we build a system for switching between
// assemblies. at that point, we might want to switch to hierarchical keys, // assemblies. at that point, we might want to switch to hierarchical keys,
// where each each element has a key that identifies it within its assembly and // where each each item has a key that identifies it within its assembly and
// each assembly has a key that identifies it within the sesssion // each assembly has a key that identifies it within the sesssion
static NEXT_ELEMENT_SERIAL: AtomicU64 = AtomicU64::new(0); static NEXT_SERIAL: AtomicU64 = AtomicU64::new(0);
pub trait ProblemPoser { pub trait Serial {
fn pose(&self, problem: &mut ConstraintProblem, elts: &Slab<Rc<dyn Element>>);
}
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<Rc<dyn Regulator>> where Self: Sized {
Vec::new()
}
fn id(&self) -> &String;
fn label(&self) -> &String;
fn representation(&self) -> Signal<DVector<f64>>;
// 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<BTreeSet<RegulatorKey>>;
// a serial number that uniquely identifies this element // a serial number that uniquely identifies this element
fn serial(&self) -> u64; fn serial(&self) -> u64;
@ -80,11 +49,62 @@ pub trait Element: ProblemPoser + DisplayItem {
// //
// https://marabos.nl/atomics/atomics.html#example-handle-overflow // https://marabos.nl/atomics/atomics.html#example-handle-overflow
// //
NEXT_ELEMENT_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")
} }
}
impl Hash for dyn Serial {
fn hash<H: Hasher>(&self, state: &mut H) {
self.serial().hash(state)
}
}
impl PartialEq for dyn Serial {
fn eq(&self, other: &Self) -> bool {
self.serial() == other.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);
}
pub trait Element: Serial + ProblemPoser + DisplayItem {
// the default identifier for an element of this type
fn default_id() -> String where Self: Sized;
// the default example of an element of this type
fn default(id: String, id_num: u64) -> Self where Self: Sized;
// the default regulators that come with this element
fn default_regulators(self: Rc<Self>) -> Vec<Rc<dyn Regulator>> {
Vec::new()
}
fn id(&self) -> &String;
fn label(&self) -> &String;
fn representation(&self) -> Signal<DVector<f64>>;
// 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<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
@ -97,17 +117,35 @@ pub trait Element: ProblemPoser + DisplayItem {
fn set_column_index(&self, index: usize); fn set_column_index(&self, index: usize);
} }
// the `Element` trait needs to be dyn-compatible, so its method signatures can impl Debug for dyn Element {
// only use `Self` in the type of the receiver. that means `Element` can't fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
// implement `PartialEq`. if you need partial equivalence for `Element` trait self.id().fmt(f)
// objects, use this wrapper }
#[derive(Clone)] }
pub struct ElementRc(pub Rc<dyn Element>);
impl PartialEq for ElementRc { impl Hash for dyn Element {
fn eq(&self, ElementRc(other): &Self) -> bool { fn hash<H: Hasher>(&self, state: &mut H) {
let ElementRc(rc) = self; <dyn Serial>::hash(self, state)
Rc::ptr_eq(rc, &other) }
}
impl PartialEq for dyn Element {
fn eq(&self, other: &Self) -> bool {
<dyn Serial>::eq(self, other)
}
}
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)
} }
} }
@ -116,8 +154,8 @@ pub struct Sphere {
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<BTreeSet<RegulatorKey>>, pub regulators: Signal<BTreeSet<Rc<dyn Regulator>>>,
pub serial: u64, serial: u64,
column_index: Cell<Option<usize>> column_index: Cell<Option<usize>>
} }
@ -135,7 +173,7 @@ impl Sphere {
label: label, label: label,
color: color, color: color,
representation: create_signal(representation), representation: create_signal(representation),
regulators: create_signal(BTreeSet::default()), regulators: create_signal(BTreeSet::new()),
serial: Self::next_serial(), serial: Self::next_serial(),
column_index: None.into() column_index: None.into()
} }
@ -156,8 +194,8 @@ impl Element for Sphere {
) )
} }
fn default_regulators(key: ElementKey, assembly: &Assembly) -> Vec<Rc<dyn Regulator>> { fn default_regulators(self: Rc<Self>) -> Vec<Rc<dyn Regulator>> {
vec![Rc::new(HalfCurvatureRegulator::new(key, assembly))] vec![Rc::new(HalfCurvatureRegulator::new(self))]
} }
fn id(&self) -> &String { fn id(&self) -> &String {
@ -172,14 +210,10 @@ impl Element for Sphere {
self.representation self.representation
} }
fn regulators(&self) -> Signal<BTreeSet<RegulatorKey>> { fn regulators(&self) -> Signal<BTreeSet<Rc<dyn Regulator>>> {
self.regulators self.regulators
} }
fn serial(&self) -> u64 {
self.serial
}
fn column_index(&self) -> Option<usize> { fn column_index(&self) -> Option<usize> {
self.column_index.get() self.column_index.get()
} }
@ -189,8 +223,14 @@ impl Element for Sphere {
} }
} }
impl Serial for Sphere {
fn serial(&self) -> u64 {
self.serial
}
}
impl ProblemPoser for Sphere { impl ProblemPoser for Sphere {
fn pose(&self, problem: &mut ConstraintProblem, _elts: &Slab<Rc<dyn Element>>) { fn pose(&self, problem: &mut ConstraintProblem) {
let index = self.column_index().expect( let index = self.column_index().expect(
format!("Sphere \"{}\" should be indexed before writing problem data", self.id).as_str() format!("Sphere \"{}\" should be indexed before writing problem data", self.id).as_str()
); );
@ -204,8 +244,8 @@ 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<BTreeSet<RegulatorKey>>, pub regulators: Signal<BTreeSet<Rc<dyn Regulator>>>,
pub serial: u64, serial: u64,
column_index: Cell<Option<usize>> column_index: Cell<Option<usize>>
} }
@ -223,7 +263,7 @@ impl Point {
label, label,
color, color,
representation: create_signal(representation), representation: create_signal(representation),
regulators: create_signal(BTreeSet::default()), regulators: create_signal(BTreeSet::new()),
serial: Self::next_serial(), serial: Self::next_serial(),
column_index: None.into() column_index: None.into()
} }
@ -256,14 +296,10 @@ impl Element for Point {
self.representation self.representation
} }
fn regulators(&self) -> Signal<BTreeSet<RegulatorKey>> { fn regulators(&self) -> Signal<BTreeSet<Rc<dyn Regulator>>> {
self.regulators self.regulators
} }
fn serial(&self) -> u64 {
self.serial
}
fn column_index(&self) -> Option<usize> { fn column_index(&self) -> Option<usize> {
self.column_index.get() self.column_index.get()
} }
@ -273,8 +309,14 @@ impl Element for Point {
} }
} }
impl Serial for Point {
fn serial(&self) -> u64 {
self.serial
}
}
impl ProblemPoser for Point { impl ProblemPoser for Point {
fn pose(&self, problem: &mut ConstraintProblem, _elts: &Slab<Rc<dyn Element>>) { fn pose(&self, problem: &mut ConstraintProblem) {
let index = self.column_index().expect( let index = self.column_index().expect(
format!("Point \"{}\" should be indexed before writing problem data", self.id).as_str() format!("Point \"{}\" should be indexed before writing problem data", self.id).as_str()
); );
@ -284,8 +326,8 @@ impl ProblemPoser for Point {
} }
} }
pub trait Regulator: ProblemPoser + OutlineItem { pub trait Regulator: Serial + 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>;
@ -295,39 +337,65 @@ pub trait Regulator: ProblemPoser + OutlineItem {
// preconditioning when the set point is present, and use its return value // preconditioning when the set point is present, and use its return value
// to report whether the set is present. the default implementation does no // to report whether the set is present. the default implementation does no
// preconditioning // preconditioning
fn try_activate(&self, _assembly: &Assembly) -> bool { fn try_activate(&self) -> bool {
self.set_point().with(|set_pt| set_pt.is_present()) self.set_point().with(|set_pt| set_pt.is_present())
} }
} }
impl Hash for dyn Regulator {
fn hash<H: Hasher>(&self, state: &mut H) {
<dyn Serial>::hash(self, state)
}
}
impl PartialEq for dyn Regulator {
fn eq(&self, other: &Self) -> bool {
<dyn Serial>::eq(self, other)
}
}
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: [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>,
serial: u64
} }
impl InversiveDistanceRegulator { impl InversiveDistanceRegulator {
pub fn new(subjects: [ElementKey; 2], assembly: &Assembly) -> InversiveDistanceRegulator { pub fn new(subjects: [Rc<dyn Element>; 2]) -> 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());
let serial = Self::next_serial();
InversiveDistanceRegulator { subjects, measurement, set_point } InversiveDistanceRegulator { subjects, measurement, set_point, serial }
} }
} }
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> {
@ -339,12 +407,18 @@ impl Regulator for InversiveDistanceRegulator {
} }
} }
impl Serial for InversiveDistanceRegulator {
fn serial(&self) -> u64 {
self.serial
}
}
impl ProblemPoser for InversiveDistanceRegulator { impl ProblemPoser for InversiveDistanceRegulator {
fn pose(&self, problem: &mut ConstraintProblem, elts: &Slab<Rc<dyn Element>>) { fn pose(&self, problem: &mut ConstraintProblem) {
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"
) )
); );
@ -355,28 +429,28 @@ 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>,
serial: u64
} }
impl HalfCurvatureRegulator { impl HalfCurvatureRegulator {
pub fn new(subject: ElementKey, assembly: &Assembly) -> HalfCurvatureRegulator { pub fn new(subject: Rc<dyn Element>) -> 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());
let serial = Self::next_serial();
HalfCurvatureRegulator { subject, measurement, set_point } HalfCurvatureRegulator { subject, measurement, set_point, serial }
} }
} }
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> {
@ -387,13 +461,10 @@ impl Regulator for HalfCurvatureRegulator {
self.set_point self.set_point
} }
fn try_activate(&self, assembly: &Assembly) -> bool { fn try_activate(&self) -> 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
@ -403,11 +474,17 @@ impl Regulator for HalfCurvatureRegulator {
} }
} }
impl Serial for HalfCurvatureRegulator {
fn serial(&self) -> u64 {
self.serial
}
}
impl ProblemPoser for HalfCurvatureRegulator { impl ProblemPoser for HalfCurvatureRegulator {
fn pose(&self, problem: &mut ConstraintProblem, elts: &Slab<Rc<dyn Element>>) { fn pose(&self, problem: &mut ConstraintProblem) {
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);
@ -418,7 +495,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>
} }
@ -428,8 +505,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
@ -444,16 +521,16 @@ pub struct Assembly {
pub tangent: Signal<ConfigSubspace>, pub tangent: Signal<ConfigSubspace>,
// indexing // indexing
pub elements_by_id: Signal<FxHashMap<String, ElementKey>> pub elements_by_id: Signal<BTreeMap<String, Rc<dyn Element>>>
} }
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(BTreeMap::default())
} }
} }
@ -462,29 +539,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<T: Element + 'static>(&self, elt: T) -> 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 key = self.elements.update(|elts| elts.insert(Rc::new(elt))); let elt_rc = Rc::new(elt);
self.elements_by_id.update(|elts_by_id| elts_by_id.insert(id, key)); 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 // create and insert the element's default regulators
for reg in T::default_regulators(key, &self) { 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) {
@ -505,19 +580,16 @@ impl Assembly {
pub fn insert_regulator(&self, regulator: Rc<dyn Regulator>) { pub fn insert_regulator(&self, regulator: Rc<dyn Regulator>) {
// add the regulator to the assembly's regulator list // add the regulator to the assembly's regulator list
let key = self.regulators.update( self.regulators.update(
|regs| regs.insert(regulator.clone()) |regs| regs.insert(regulator.clone())
); );
// add the regulator to each subject's regulator list // add the regulator to each subject's regulator list
let subjects = regulator.subjects(); let subject_regulators: Vec<_> = regulator.subjects().into_iter().map(
let subject_regulators: Vec<_> = self.elements.with_untracked( |subj| subj.regulators()
|elts| subjects.into_iter().map( ).collect();
|subj| elts[subj].regulators()
).collect()
);
for regulators in subject_regulators { for regulators in subject_regulators {
regulators.update(|regs| regs.insert(key)); 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
@ -530,7 +602,7 @@ impl Assembly {
format!("Updated regulator with subjects {:?}", regulator.subjects()) format!("Updated regulator with subjects {:?}", regulator.subjects())
)); ));
if regulator.try_activate(&self_for_effect) { if regulator.try_activate() {
self_for_effect.realize(); self_for_effect.realize();
} }
}); });
@ -539,7 +611,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(),
@ -563,7 +635,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);
} }
}); });
@ -571,12 +643,12 @@ 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, elts); 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, elts); reg.pose(&mut problem);
} }
}); });
problem problem
@ -618,7 +690,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()))
); );
@ -654,17 +726,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
@ -675,9 +747,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
@ -690,12 +760,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;
} }
@ -705,7 +771,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) => {
@ -747,7 +813,7 @@ mod tests {
fn unindexed_element_test() { fn unindexed_element_test() {
let _ = create_root(|| { let _ = create_root(|| {
let elt = Sphere::default("sphere".to_string(), 0); let elt = Sphere::default("sphere".to_string(), 0);
elt.pose(&mut ConstraintProblem::new(1), &Slab::new()); elt.pose(&mut ConstraintProblem::new(1));
}); });
} }
@ -755,18 +821,16 @@ mod tests {
#[should_panic(expected = "Subjects should be indexed before inversive distance regulator writes problem data")] #[should_panic(expected = "Subjects should be indexed before inversive distance regulator writes problem data")]
fn unindexed_subject_test_inversive_distance() { fn unindexed_subject_test_inversive_distance() {
let _ = create_root(|| { let _ = create_root(|| {
let mut elts = Slab::<Rc<dyn Element>>::new(); let subjects = [0, 1].map(
let subjects = [0, 1].map(|k| { |k| Rc::new(Sphere::default(format!("sphere{k}"), k)) as Rc<dyn Element>
elts.insert( );
Rc::new(Sphere::default(format!("sphere{k}"), k)) subjects[0].set_column_index(0);
)
});
elts[subjects[0]].set_column_index(0);
InversiveDistanceRegulator { InversiveDistanceRegulator {
subjects: subjects, subjects: subjects,
measurement: create_memo(|| 0.0), measurement: create_memo(|| 0.0),
set_point: create_signal(SpecifiedValue::try_from("0.0".to_string()).unwrap()) set_point: create_signal(SpecifiedValue::try_from("0.0".to_string()).unwrap()),
}.pose(&mut ConstraintProblem::new(2), &elts); serial: InversiveDistanceRegulator::next_serial()
}.pose(&mut ConstraintProblem::new(2));
}); });
} }
} }

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 ---
@ -362,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();
} }
}); });
@ -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

@ -8,42 +8,41 @@ mod specified;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
use rustc_hash::FxHashSet; use std::{collections::BTreeSet, 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<BTreeSet<Rc<dyn Element>>>
} }
impl AppState { impl AppState {
fn new() -> AppState { fn new() -> AppState {
AppState { AppState {
assembly: Assembly::new(), assembly: Assembly::new(),
selection: create_signal(FxHashSet::default()) selection: create_signal(BTreeSet::default())
} }
} }
// 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

@ -11,12 +11,9 @@ use crate::{
AppState, AppState,
assembly::{ assembly::{
Element, Element,
ElementKey,
ElementRc,
HalfCurvatureRegulator, HalfCurvatureRegulator,
InversiveDistanceRegulator, InversiveDistanceRegulator,
Regulator, Regulator
RegulatorKey
}, },
specified::SpecifiedValue specified::SpecifiedValue
}; };
@ -92,20 +89,16 @@ 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 other_subject_label = if self.subjects[0] == element.clone() {
let other_subject = if self.subjects[0] == element_key { self.subjects[1].label()
self.subjects[1]
} 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) }
@ -118,7 +111,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
@ -130,23 +123,16 @@ 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 {
let state = use_context::<AppState>();
let regulator = state.assembly.regulators.with(
|regs| regs[regulator_key].clone()
);
regulator.outline_item(element_key)
}
// 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(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 || {
@ -161,14 +147,10 @@ fn ElementOutlineItem(key: ElementKey, element: Rc<dyn Element>) -> View {
}; };
let regulated = element.regulators().map(|regs| regs.len() > 0); let regulated = element.regulators().map(|regs| regs.len() > 0);
let regulator_list = element.regulators().map( let regulator_list = element.regulators().map(
move |elt_reg_keys| elt_reg_keys |regs| regs
.clone() .clone()
.into_iter() .into_iter()
.sorted_by_key( .sorted_by_key(|reg| reg.subjects().len())
|&reg_key| state.assembly.regulators.with(
|regs| regs[reg_key].subjects().len()
)
)
.collect() .collect()
); );
let details_node = create_node_ref(); let details_node = create_node_ref();
@ -178,10 +160,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() => {
@ -208,19 +191,10 @@ fn ElementOutlineItem(key: ElementKey, element: Rc<dyn Element>) -> View {
div( div(
class="element", class="element",
on:click={ on:click={
let state_for_handler = state.clone();
let element_for_handler = element.clone();
move |event: MouseEvent| { move |event: MouseEvent| {
if event.shift_key() { state_for_handler.select(&element_for_handler, event.shift_key());
state.selection.update(|sel| {
if !sel.remove(&key) {
sel.insert(key);
}
});
} else {
state.selection.update(|sel| {
sel.clear();
sel.insert(key);
});
}
event.stop_propagation(); event.stop_propagation();
event.prevent_default(); event.prevent_default();
} }
@ -234,13 +208,8 @@ 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| reg.outline_item(&element),
RegulatorOutlineItem( key=|reg| reg.serial()
regulator_key=reg_key,
element_key=key
)
},
key=|reg_key| reg_key.clone()
) )
} }
} }
@ -259,12 +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())
.map(|(key, elt)| (key, ElementRc(elt)))
.collect() .collect()
); );
@ -278,10 +250,10 @@ pub fn Outline() -> View {
) { ) {
Keyed( Keyed(
list=element_list, list=element_list,
view=|(key, ElementRc(elt))| view! { view=|elt| view! {
ElementOutlineItem(key=key, element=elt) ElementOutlineItem(element=elt)
}, },
key=|(_, ElementRc(elt))| elt.serial() key=|elt| elt.serial()
) )
} }
} }