Compare commits

..

No commits in common. "3dd6303e99d866f2d6d9a464abfdae5b87cd895e" and "a2478febc128b218d833bc3e904482f2055865cb" have entirely different histories.

7 changed files with 269 additions and 287 deletions

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 = 4 version = 3
[[package]] [[package]]
name = "ahash" name = "ahash"
@ -89,6 +89,8 @@ dependencies = [
"lazy_static", "lazy_static",
"nalgebra", "nalgebra",
"readonly", "readonly",
"rustc-hash",
"slab",
"sycamore", "sycamore",
"wasm-bindgen-test", "wasm-bindgen-test",
"web-sys", "web-sys",
@ -363,6 +365,12 @@ 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"
@ -406,6 +414,15 @@ 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,7 +3,6 @@ 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"]
@ -15,6 +14,8 @@ 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)) Rc::new(InversiveDistanceRegulator::new(subjects, &state.assembly))
); );
state.selection.update(|sel| sel.clear()); state.selection.update(|sel| sel.clear());
} }

View file

@ -1,14 +1,12 @@
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::{BTreeMap, BTreeSet}, collections::BTreeSet,
cmp::Ordering,
fmt,
fmt::{Debug, Formatter},
hash::{Hash, Hasher},
rc::Rc, rc::Rc,
sync::{atomic, atomic::AtomicU64} sync::atomic::{AtomicU64, Ordering}
}; };
use sycamore::prelude::*; use sycamore::prelude::*;
use web_sys::{console, wasm_bindgen::JsValue}; /* DEBUG */ use web_sys::{console, wasm_bindgen::JsValue}; /* DEBUG */
@ -29,16 +27,49 @@ 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 item has a key that identifies it within its assembly and // where each each element 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_SERIAL: AtomicU64 = AtomicU64::new(0); static NEXT_ELEMENT_SERIAL: AtomicU64 = AtomicU64::new(0);
pub trait ProblemPoser {
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>>;
pub trait Serial {
// a serial number that uniquely identifies this element // a serial number that uniquely identifies this element
fn serial(&self) -> u64; fn serial(&self) -> u64;
@ -49,62 +80,11 @@ 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_ELEMENT_SERIAL.fetch_update(
atomic::Ordering::SeqCst, atomic::Ordering::SeqCst, Ordering::SeqCst, 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
@ -117,35 +97,17 @@ pub trait Element: Serial + ProblemPoser + DisplayItem {
fn set_column_index(&self, index: usize); fn set_column_index(&self, index: usize);
} }
impl Debug for dyn Element { // the `Element` trait needs to be dyn-compatible, so its method signatures can
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> { // only use `Self` in the type of the receiver. that means `Element` can't
self.id().fmt(f) // implement `PartialEq`. if you need partial equivalence for `Element` trait
} // objects, use this wrapper
} #[derive(Clone)]
pub struct ElementRc(pub Rc<dyn Element>);
impl Hash for dyn Element { impl PartialEq for ElementRc {
fn hash<H: Hasher>(&self, state: &mut H) { fn eq(&self, ElementRc(other): &Self) -> bool {
<dyn Serial>::hash(self, state) let ElementRc(rc) = self;
} 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)
} }
} }
@ -154,8 +116,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<Rc<dyn Regulator>>>, pub regulators: Signal<BTreeSet<RegulatorKey>>,
serial: u64, pub serial: u64,
column_index: Cell<Option<usize>> column_index: Cell<Option<usize>>
} }
@ -173,7 +135,7 @@ impl Sphere {
label: label, label: label,
color: color, color: color,
representation: create_signal(representation), representation: create_signal(representation),
regulators: create_signal(BTreeSet::new()), regulators: create_signal(BTreeSet::default()),
serial: Self::next_serial(), serial: Self::next_serial(),
column_index: None.into() column_index: None.into()
} }
@ -194,8 +156,8 @@ impl Element for Sphere {
) )
} }
fn default_regulators(self: Rc<Self>) -> Vec<Rc<dyn Regulator>> { fn default_regulators(key: ElementKey, assembly: &Assembly) -> Vec<Rc<dyn Regulator>> {
vec![Rc::new(HalfCurvatureRegulator::new(self))] vec![Rc::new(HalfCurvatureRegulator::new(key, assembly))]
} }
fn id(&self) -> &String { fn id(&self) -> &String {
@ -210,10 +172,14 @@ impl Element for Sphere {
self.representation self.representation
} }
fn regulators(&self) -> Signal<BTreeSet<Rc<dyn Regulator>>> { fn regulators(&self) -> Signal<BTreeSet<RegulatorKey>> {
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()
} }
@ -223,14 +189,8 @@ 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) { fn pose(&self, problem: &mut ConstraintProblem, _elts: &Slab<Rc<dyn Element>>) {
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()
); );
@ -244,8 +204,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<Rc<dyn Regulator>>>, pub regulators: Signal<BTreeSet<RegulatorKey>>,
serial: u64, pub serial: u64,
column_index: Cell<Option<usize>> column_index: Cell<Option<usize>>
} }
@ -263,7 +223,7 @@ impl Point {
label, label,
color, color,
representation: create_signal(representation), representation: create_signal(representation),
regulators: create_signal(BTreeSet::new()), regulators: create_signal(BTreeSet::default()),
serial: Self::next_serial(), serial: Self::next_serial(),
column_index: None.into() column_index: None.into()
} }
@ -296,10 +256,14 @@ impl Element for Point {
self.representation self.representation
} }
fn regulators(&self) -> Signal<BTreeSet<Rc<dyn Regulator>>> { fn regulators(&self) -> Signal<BTreeSet<RegulatorKey>> {
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()
} }
@ -309,14 +273,8 @@ 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) { fn pose(&self, problem: &mut ConstraintProblem, _elts: &Slab<Rc<dyn Element>>) {
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()
); );
@ -326,8 +284,8 @@ impl ProblemPoser for Point {
} }
} }
pub trait Regulator: Serial + ProblemPoser + OutlineItem { pub trait Regulator: ProblemPoser + OutlineItem {
fn subjects(&self) -> Vec<Rc<dyn Element>>; fn subjects(&self) -> Vec<ElementKey>;
fn measurement(&self) -> ReadSignal<f64>; fn measurement(&self) -> ReadSignal<f64>;
fn set_point(&self) -> Signal<SpecifiedValue>; fn set_point(&self) -> Signal<SpecifiedValue>;
@ -337,65 +295,39 @@ pub trait Regulator: Serial + 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) -> bool { fn try_activate(&self, _assembly: &Assembly) -> 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: [Rc<dyn Element>; 2], pub subjects: [ElementKey; 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: [Rc<dyn Element>; 2]) -> InversiveDistanceRegulator { pub fn new(subjects: [ElementKey; 2], assembly: &Assembly) -> InversiveDistanceRegulator {
let representations = subjects.each_ref().map(|subj| subj.representation()); let measurement = assembly.elements.map(
let measurement = create_memo(move || { move |elts| {
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, serial } InversiveDistanceRegulator { subjects, measurement, set_point }
} }
} }
impl Regulator for InversiveDistanceRegulator { impl Regulator for InversiveDistanceRegulator {
fn subjects(&self) -> Vec<Rc<dyn Element>> { fn subjects(&self) -> Vec<ElementKey> {
self.subjects.clone().into() self.subjects.into()
} }
fn measurement(&self) -> ReadSignal<f64> { fn measurement(&self) -> ReadSignal<f64> {
@ -407,18 +339,12 @@ 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) { 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.each_ref().map( let [row, col] = self.subjects.map(
|subj| subj.column_index().expect( |subj| elts[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"
) )
); );
@ -429,28 +355,28 @@ impl ProblemPoser for InversiveDistanceRegulator {
} }
pub struct HalfCurvatureRegulator { pub struct HalfCurvatureRegulator {
pub subject: Rc<dyn Element>, pub subject: ElementKey,
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: Rc<dyn Element>) -> HalfCurvatureRegulator { pub fn new(subject: ElementKey, assembly: &Assembly) -> HalfCurvatureRegulator {
let measurement = subject.representation().map( let measurement = assembly.elements.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, serial } HalfCurvatureRegulator { subject, measurement, set_point }
} }
} }
impl Regulator for HalfCurvatureRegulator { impl Regulator for HalfCurvatureRegulator {
fn subjects(&self) -> Vec<Rc<dyn Element>> { fn subjects(&self) -> Vec<ElementKey> {
vec![self.subject.clone()] vec![self.subject]
} }
fn measurement(&self) -> ReadSignal<f64> { fn measurement(&self) -> ReadSignal<f64> {
@ -461,10 +387,13 @@ impl Regulator for HalfCurvatureRegulator {
self.set_point self.set_point
} }
fn try_activate(&self) -> 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) => {
self.subject.representation().update( let representation = assembly.elements.with_untracked(
|elts| elts[self.subject].representation()
);
representation.update(
|rep| change_half_curvature(rep, half_curv) |rep| change_half_curvature(rep, half_curv)
); );
true true
@ -474,17 +403,11 @@ 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) { 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 = self.subject.column_index().expect( let col = elts[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);
@ -495,7 +418,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 element: Rc<dyn Element>, pub key: ElementKey,
pub velocity: DVectorView<'a, f64> pub velocity: DVectorView<'a, f64>
} }
@ -505,8 +428,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<BTreeSet<Rc<dyn Element>>>, pub elements: Signal<Slab<Rc<dyn Element>>>,
pub regulators: Signal<BTreeSet<Rc<dyn Regulator>>>, pub regulators: Signal<Slab<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
@ -521,16 +444,16 @@ pub struct Assembly {
pub tangent: Signal<ConfigSubspace>, pub tangent: Signal<ConfigSubspace>,
// indexing // indexing
pub elements_by_id: Signal<BTreeMap<String, Rc<dyn Element>>> pub elements_by_id: Signal<FxHashMap<String, ElementKey>>
} }
impl Assembly { impl Assembly {
pub fn new() -> Assembly { pub fn new() -> Assembly {
Assembly { Assembly {
elements: create_signal(BTreeSet::new()), elements: create_signal(Slab::new()),
regulators: create_signal(BTreeSet::new()), regulators: create_signal(Slab::new()),
tangent: create_signal(ConfigSubspace::zero(0)), tangent: create_signal(ConfigSubspace::zero(0)),
elements_by_id: create_signal(BTreeMap::default()) elements_by_id: create_signal(FxHashMap::default())
} }
} }
@ -539,27 +462,29 @@ 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) { fn insert_element_unchecked<T: Element + 'static>(&self, elt: T) -> ElementKey {
// insert the element // insert the element
let id = elt.id().clone(); let id = elt.id().clone();
let elt_rc = Rc::new(elt); let key = self.elements.update(|elts| elts.insert(Rc::new(elt)));
self.elements.update(|elts| elts.insert(elt_rc.clone())); 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, 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 T::default_regulators(key, &self) {
self.insert_regulator(reg); self.insert_regulator(reg);
} }
key
} }
pub fn try_insert_element(&self, elt: impl Element + 'static) -> bool { pub fn try_insert_element(&self, elt: impl Element + 'static) -> Option<ElementKey> {
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 {
self.insert_element_unchecked(elt); Some(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) {
@ -580,16 +505,19 @@ 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
self.regulators.update( let key = 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 subject_regulators: Vec<_> = regulator.subjects().into_iter().map( let subjects = regulator.subjects();
|subj| subj.regulators() let subject_regulators: Vec<_> = self.elements.with_untracked(
).collect(); |elts| subjects.into_iter().map(
|subj| elts[subj].regulators()
).collect()
);
for regulators in subject_regulators { for regulators in subject_regulators {
regulators.update(|regs| regs.insert(regulator.clone())); regulators.update(|regs| regs.insert(key));
} }
// update the realization when the regulator becomes a constraint, or is // update the realization when the regulator becomes a constraint, or is
@ -602,7 +530,7 @@ impl Assembly {
format!("Updated regulator with subjects {:?}", regulator.subjects()) format!("Updated regulator with subjects {:?}", regulator.subjects())
)); ));
if regulator.try_activate() { if regulator.try_activate(&self_for_effect) {
self_for_effect.realize(); self_for_effect.realize();
} }
}); });
@ -611,7 +539,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(),
@ -635,7 +563,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.iter().enumerate() { for (index, (_, elt)) in elts.into_iter().enumerate() {
elt.set_column_index(index); elt.set_column_index(index);
} }
}); });
@ -643,12 +571,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); elt.pose(&mut problem, elts);
} }
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, elts);
} }
}); });
problem problem
@ -690,7 +618,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()))
); );
@ -726,17 +654,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 = { let motion_dim = self.elements.update_silent(|elts| {
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 = &elt_motion.element; let moving_elt = &mut elts[elt_motion.key];
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
@ -747,7 +675,9 @@ 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 = elt_motion.element.column_index().unwrap(); let column_index = self.elements.with_untracked(
|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
@ -760,8 +690,12 @@ 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 = elt_motion.element.representation().with_untracked( let unif_to_std = self.elements.with_untracked(
|elts| {
elts[elt_motion.key].representation().with_untracked(
|rep| local_unif_to_std(rep.as_view()) |rep| local_unif_to_std(rep.as_view())
)
}
); );
target_column += unif_to_std * elt_motion.velocity; target_column += unif_to_std * elt_motion.velocity;
} }
@ -771,7 +705,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) => {
@ -813,7 +747,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)); elt.pose(&mut ConstraintProblem::new(1), &Slab::new());
}); });
} }
@ -821,16 +755,18 @@ 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 subjects = [0, 1].map( let mut elts = Slab::<Rc<dyn Element>>::new();
|k| Rc::new(Sphere::default(format!("sphere{k}"), k)) as Rc<dyn Element> let subjects = [0, 1].map(|k| {
); elts.insert(
subjects[0].set_column_index(0); Rc::new(Sphere::default(format!("sphere{k}"), k))
)
});
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())
serial: InversiveDistanceRegulator::next_serial() }.pose(&mut ConstraintProblem::new(2), &elts);
}.pose(&mut ConstraintProblem::new(2));
}); });
} }
} }

View file

@ -1,6 +1,5 @@
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,
@ -17,7 +16,7 @@ use web_sys::{
use crate::{ use crate::{
AppState, AppState,
assembly::{Element, ElementColor, ElementMotion, Point, Sphere} assembly::{ElementKey, ElementColor, ElementMotion, Point, Sphere}
}; };
// --- scene data --- // --- scene data ---
@ -363,7 +362,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();
} }
}); });
@ -549,7 +548,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().clone() |sel| *sel.into_iter().next().unwrap()
); );
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;
@ -575,7 +574,7 @@ pub fn Display() -> View {
assembly_for_raf.deform( assembly_for_raf.deform(
vec![ vec![
ElementMotion { ElementMotion {
element: sel, key: sel,
velocity: elt_motion.as_view() velocity: elt_motion.as_view()
} }
] ]
@ -616,8 +615,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 elt in elts { |elts| for (key, elt) in elts {
let selected = state.selection.with(|sel| sel.contains(elt)); let selected = state.selection.with(|sel| sel.contains(&key));
elt.show(&mut scene, selected); elt.show(&mut scene, selected);
} }
); );
@ -850,16 +849,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<(Rc<dyn Element>, f64)> = None; let mut clicked: Option<(ElementKey, f64)> = None;
for elt in state.assembly.elements.get_clone_untracked() { for (key, 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((elt, depth)) clicked = Some((key, depth))
} }
}, },
None => clicked = Some((elt, depth)) None => clicked = Some((key, depth))
} }
None => () None => ()
}; };
@ -867,7 +866,7 @@ pub fn Display() -> View {
// if we clicked something, select it // if we clicked something, select it
match clicked { match clicked {
Some((elt, _)) => state.select(&elt, event.shift_key()), Some((key, _)) => state.select(key, event.shift_key()),
None => state.selection.update(|sel| sel.clear()) None => state.selection.update(|sel| sel.clear())
}; };
} }

View file

@ -8,41 +8,42 @@ mod specified;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
use std::{collections::BTreeSet, rc::Rc}; use rustc_hash::FxHashSet;
use sycamore::prelude::*; use sycamore::prelude::*;
use add_remove::AddRemove; use add_remove::AddRemove;
use assembly::{Assembly, Element}; use assembly::{Assembly, ElementKey};
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<BTreeSet<Rc<dyn Element>>> selection: Signal<FxHashSet<ElementKey>>
} }
impl AppState { impl AppState {
fn new() -> AppState { fn new() -> AppState {
AppState { AppState {
assembly: Assembly::new(), assembly: Assembly::new(),
selection: create_signal(BTreeSet::default()) selection: create_signal(FxHashSet::default())
} }
} }
// in single-selection mode, select the given element. in multiple-selection // in single-selection mode, select the element with the given key. in
// mode, toggle whether the given element is selected // multiple-selection mode, toggle whether the element with the given key
fn select(&self, element: &Rc<dyn Element>, multi: bool) { // is selected
fn select(&self, key: ElementKey, multi: bool) {
if multi { if multi {
self.selection.update(|sel| { self.selection.update(|sel| {
if !sel.remove(element) { if !sel.remove(&key) {
sel.insert(element.clone()); sel.insert(key);
} }
}); });
} else { } else {
self.selection.update(|sel| { self.selection.update(|sel| {
sel.clear(); sel.clear();
sel.insert(element.clone()); sel.insert(key);
}); });
} }
} }

View file

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