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

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

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

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

View file

@ -1,12 +1,14 @@
use nalgebra::{DMatrix, DVector, DVectorView};
use rustc_hash::FxHashMap;
use slab::Slab;
use std::{
any::{Any, TypeId},
cell::Cell,
collections::BTreeSet,
collections::{BTreeMap, 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 */
@ -27,49 +29,16 @@ use crate::{
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];
/* KLUDGE */
// 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,
// 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
static NEXT_ELEMENT_SERIAL: AtomicU64 = AtomicU64::new(0);
static NEXT_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
fn serial(&self) -> u64;
@ -80,11 +49,62 @@ pub trait Element: ProblemPoser + DisplayItem {
//
// https://marabos.nl/atomics/atomics.html#example-handle-overflow
//
NEXT_ELEMENT_SERIAL.fetch_update(
Ordering::SeqCst, Ordering::SeqCst,
NEXT_SERIAL.fetch_update(
atomic::Ordering::SeqCst, atomic::Ordering::SeqCst,
|serial| serial.checked_add(1)
).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
// 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);
}
// the `Element` trait needs to be dyn-compatible, so its method signatures can
// only use `Self` in the type of the receiver. that means `Element` can't
// implement `PartialEq`. if you need partial equivalence for `Element` trait
// objects, use this wrapper
#[derive(Clone)]
pub struct ElementRc(pub Rc<dyn Element>);
impl Debug for dyn Element {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
self.id().fmt(f)
}
}
impl PartialEq for ElementRc {
fn eq(&self, ElementRc(other): &Self) -> bool {
let ElementRc(rc) = self;
Rc::ptr_eq(rc, &other)
impl Hash for dyn Element {
fn hash<H: Hasher>(&self, state: &mut H) {
<dyn Serial>::hash(self, state)
}
}
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 color: ElementColor,
pub representation: Signal<DVector<f64>>,
pub regulators: Signal<BTreeSet<RegulatorKey>>,
pub serial: u64,
pub regulators: Signal<BTreeSet<Rc<dyn Regulator>>>,
serial: u64,
column_index: Cell<Option<usize>>
}
@ -135,7 +173,7 @@ impl Sphere {
label: label,
color: color,
representation: create_signal(representation),
regulators: create_signal(BTreeSet::default()),
regulators: create_signal(BTreeSet::new()),
serial: Self::next_serial(),
column_index: None.into()
}
@ -156,8 +194,8 @@ impl Element for Sphere {
)
}
fn default_regulators(key: ElementKey, assembly: &Assembly) -> Vec<Rc<dyn Regulator>> {
vec![Rc::new(HalfCurvatureRegulator::new(key, assembly))]
fn default_regulators(self: Rc<Self>) -> Vec<Rc<dyn Regulator>> {
vec![Rc::new(HalfCurvatureRegulator::new(self))]
}
fn id(&self) -> &String {
@ -172,14 +210,10 @@ impl Element for Sphere {
self.representation
}
fn regulators(&self) -> Signal<BTreeSet<RegulatorKey>> {
fn regulators(&self) -> Signal<BTreeSet<Rc<dyn Regulator>>> {
self.regulators
}
fn serial(&self) -> u64 {
self.serial
}
fn column_index(&self) -> Option<usize> {
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 {
fn pose(&self, problem: &mut ConstraintProblem, _elts: &Slab<Rc<dyn Element>>) {
fn pose(&self, problem: &mut ConstraintProblem) {
let index = self.column_index().expect(
format!("Sphere \"{}\" should be indexed before writing problem data", self.id).as_str()
);
@ -204,8 +244,8 @@ pub struct Point {
pub label: String,
pub color: ElementColor,
pub representation: Signal<DVector<f64>>,
pub regulators: Signal<BTreeSet<RegulatorKey>>,
pub serial: u64,
pub regulators: Signal<BTreeSet<Rc<dyn Regulator>>>,
serial: u64,
column_index: Cell<Option<usize>>
}
@ -223,7 +263,7 @@ impl Point {
label,
color,
representation: create_signal(representation),
regulators: create_signal(BTreeSet::default()),
regulators: create_signal(BTreeSet::new()),
serial: Self::next_serial(),
column_index: None.into()
}
@ -256,14 +296,10 @@ impl Element for Point {
self.representation
}
fn regulators(&self) -> Signal<BTreeSet<RegulatorKey>> {
fn regulators(&self) -> Signal<BTreeSet<Rc<dyn Regulator>>> {
self.regulators
}
fn serial(&self) -> u64 {
self.serial
}
fn column_index(&self) -> Option<usize> {
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 {
fn pose(&self, problem: &mut ConstraintProblem, _elts: &Slab<Rc<dyn Element>>) {
fn pose(&self, problem: &mut ConstraintProblem) {
let index = self.column_index().expect(
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 {
fn subjects(&self) -> Vec<ElementKey>;
pub trait Regulator: Serial + ProblemPoser + OutlineItem {
fn subjects(&self) -> Vec<Rc<dyn Element>>;
fn measurement(&self) -> ReadSignal<f64>;
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
// to report whether the set is present. the default implementation does no
// preconditioning
fn try_activate(&self, _assembly: &Assembly) -> bool {
fn try_activate(&self) -> bool {
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 subjects: [ElementKey; 2],
pub subjects: [Rc<dyn Element>; 2],
pub measurement: ReadSignal<f64>,
pub set_point: Signal<SpecifiedValue>
pub set_point: Signal<SpecifiedValue>,
serial: u64
}
impl InversiveDistanceRegulator {
pub fn new(subjects: [ElementKey; 2], assembly: &Assembly) -> InversiveDistanceRegulator {
let measurement = assembly.elements.map(
move |elts| {
let representations = subjects.map(|subj| elts[subj].representation());
representations[0].with(|rep_0|
representations[1].with(|rep_1|
rep_0.dot(&(&*Q * rep_1))
)
pub fn new(subjects: [Rc<dyn Element>; 2]) -> InversiveDistanceRegulator {
let representations = subjects.each_ref().map(|subj| subj.representation());
let measurement = create_memo(move || {
representations[0].with(|rep_0|
representations[1].with(|rep_1|
rep_0.dot(&(&*Q * rep_1))
)
}
);
)
});
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 {
fn subjects(&self) -> Vec<ElementKey> {
self.subjects.into()
fn subjects(&self) -> Vec<Rc<dyn Element>> {
self.subjects.clone().into()
}
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 {
fn pose(&self, problem: &mut ConstraintProblem, elts: &Slab<Rc<dyn Element>>) {
fn pose(&self, problem: &mut ConstraintProblem) {
self.set_point.with_untracked(|set_pt| {
if let Some(val) = set_pt.value {
let [row, col] = self.subjects.map(
|subj| elts[subj].column_index().expect(
let [row, col] = self.subjects.each_ref().map(
|subj| subj.column_index().expect(
"Subjects should be indexed before inversive distance regulator writes problem data"
)
);
@ -355,28 +429,28 @@ impl ProblemPoser for InversiveDistanceRegulator {
}
pub struct HalfCurvatureRegulator {
pub subject: ElementKey,
pub subject: Rc<dyn Element>,
pub measurement: ReadSignal<f64>,
pub set_point: Signal<SpecifiedValue>
pub set_point: Signal<SpecifiedValue>,
serial: u64
}
impl HalfCurvatureRegulator {
pub fn new(subject: ElementKey, assembly: &Assembly) -> HalfCurvatureRegulator {
let measurement = assembly.elements.map(
move |elts| elts[subject].representation().with(
|rep| rep[Sphere::CURVATURE_COMPONENT]
)
pub fn new(subject: Rc<dyn Element>) -> HalfCurvatureRegulator {
let measurement = subject.representation().map(
|rep| rep[Sphere::CURVATURE_COMPONENT]
);
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 {
fn subjects(&self) -> Vec<ElementKey> {
vec![self.subject]
fn subjects(&self) -> Vec<Rc<dyn Element>> {
vec![self.subject.clone()]
}
fn measurement(&self) -> ReadSignal<f64> {
@ -387,13 +461,10 @@ impl Regulator for HalfCurvatureRegulator {
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) {
Some(half_curv) => {
let representation = assembly.elements.with_untracked(
|elts| elts[self.subject].representation()
);
representation.update(
self.subject.representation().update(
|rep| change_half_curvature(rep, half_curv)
);
true
@ -403,11 +474,17 @@ impl Regulator for HalfCurvatureRegulator {
}
}
impl Serial for HalfCurvatureRegulator {
fn serial(&self) -> u64 {
self.serial
}
}
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| {
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"
);
problem.frozen.push(Sphere::CURVATURE_COMPONENT, col, val);
@ -418,7 +495,7 @@ impl ProblemPoser for HalfCurvatureRegulator {
// the velocity is expressed in uniform coordinates
pub struct ElementMotion<'a> {
pub key: ElementKey,
pub element: Rc<dyn Element>,
pub velocity: DVectorView<'a, f64>
}
@ -428,8 +505,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
@ -444,16 +521,16 @@ pub struct Assembly {
pub tangent: Signal<ConfigSubspace>,
// indexing
pub elements_by_id: Signal<FxHashMap<String, ElementKey>>
pub elements_by_id: Signal<BTreeMap<String, Rc<dyn Element>>>
}
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())
elements_by_id: create_signal(BTreeMap::default())
}
}
@ -462,29 +539,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<T: Element + 'static>(&self, elt: T) -> ElementKey {
fn insert_element_unchecked(&self, elt: impl Element + 'static) {
// insert the element
let id = elt.id().clone();
let key = self.elements.update(|elts| elts.insert(Rc::new(elt)));
self.elements_by_id.update(|elts_by_id| elts_by_id.insert(id, key));
let elt_rc = 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, elt_rc.clone()));
// 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);
}
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) {
@ -505,19 +580,16 @@ impl Assembly {
pub fn insert_regulator(&self, regulator: Rc<dyn Regulator>) {
// add the regulator to the assembly's regulator list
let key = self.regulators.update(
self.regulators.update(
|regs| regs.insert(regulator.clone())
);
// add the regulator to each subject's regulator list
let subjects = regulator.subjects();
let subject_regulators: Vec<_> = self.elements.with_untracked(
|elts| subjects.into_iter().map(
|subj| elts[subj].regulators()
).collect()
);
let subject_regulators: Vec<_> = regulator.subjects().into_iter().map(
|subj| subj.regulators()
).collect();
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
@ -530,7 +602,7 @@ impl Assembly {
format!("Updated regulator with subjects {:?}", regulator.subjects())
));
if regulator.try_activate(&self_for_effect) {
if regulator.try_activate() {
self_for_effect.realize();
}
});
@ -539,7 +611,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(),
@ -563,7 +635,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);
}
});
@ -571,12 +643,12 @@ 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 {
elt.pose(&mut problem, elts);
for elt in elts {
elt.pose(&mut problem);
}
self.regulators.with_untracked(|regs| {
for (_, reg) in regs {
reg.pose(&mut problem, elts);
for reg in regs {
reg.pose(&mut problem);
}
});
problem
@ -618,7 +690,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()))
);
@ -654,17 +726,17 @@ impl Assembly {
// in the process, we find out how many matrix columns we'll need to
// hold the deformation
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;
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() {
moving_elt.set_column_index(next_column_index);
next_column_index += 1;
}
}
next_column_index
});
};
// project the element motions onto the tangent space of the solution
// variety and sum them to get a deformation of the whole assembly. the
@ -675,9 +747,7 @@ impl Assembly {
for elt_motion in motion {
// we can unwrap the column index because we know that every moving
// element has one at this point
let column_index = self.elements.with_untracked(
|elts| elts[elt_motion.key].column_index().unwrap()
);
let column_index = elt_motion.element.column_index().unwrap();
if column_index < realized_dim {
// 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
// by invariant (2), it's unconstrained
let mut target_column = motion_proj.column_mut(column_index);
let unif_to_std = self.elements.with_untracked(
|elts| {
elts[elt_motion.key].representation().with_untracked(
|rep| local_unif_to_std(rep.as_view())
)
}
let unif_to_std = elt_motion.element.representation().with_untracked(
|rep| local_unif_to_std(rep.as_view())
);
target_column += unif_to_std * elt_motion.velocity;
}
@ -705,7 +771,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) => {
@ -747,7 +813,7 @@ mod tests {
fn unindexed_element_test() {
let _ = create_root(|| {
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")]
fn unindexed_subject_test_inversive_distance() {
let _ = create_root(|| {
let mut elts = Slab::<Rc<dyn Element>>::new();
let subjects = [0, 1].map(|k| {
elts.insert(
Rc::new(Sphere::default(format!("sphere{k}"), k))
)
});
elts[subjects[0]].set_column_index(0);
let subjects = [0, 1].map(
|k| Rc::new(Sphere::default(format!("sphere{k}"), k)) as Rc<dyn Element>
);
subjects[0].set_column_index(0);
InversiveDistanceRegulator {
subjects: subjects,
measurement: create_memo(|| 0.0),
set_point: create_signal(SpecifiedValue::try_from("0.0".to_string()).unwrap())
}.pose(&mut ConstraintProblem::new(2), &elts);
set_point: create_signal(SpecifiedValue::try_from("0.0".to_string()).unwrap()),
serial: InversiveDistanceRegulator::next_serial()
}.pose(&mut ConstraintProblem::new(2));
});
}
}

View file

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

View file

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

View file

@ -11,12 +11,9 @@ use crate::{
AppState,
assembly::{
Element,
ElementKey,
ElementRc,
HalfCurvatureRegulator,
InversiveDistanceRegulator,
Regulator,
RegulatorKey
Regulator
},
specified::SpecifiedValue
};
@ -92,20 +89,16 @@ fn RegulatorInput(regulator: Rc<dyn Regulator>) -> View {
}
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 {
fn outline_item(self: Rc<Self>, element_key: ElementKey) -> View {
let state = use_context::<AppState>();
let other_subject = if self.subjects[0] == element_key {
self.subjects[1]
fn outline_item(self: Rc<Self>, element: &Rc<dyn Element>) -> View {
let other_subject_label = if self.subjects[0] == element.clone() {
self.subjects[1].label()
} else {
self.subjects[0]
};
let other_subject_label = state.assembly.elements.with(
|elts| elts[other_subject].label().clone()
);
self.subjects[0].label()
}.clone();
view! {
li(class="regulator") {
div(class="regulator-label") { (other_subject_label) }
@ -118,7 +111,7 @@ impl OutlineItem for InversiveDistanceRegulator {
}
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! {
li(class="regulator") {
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
#[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 class = state.selection.map(
move |sel| if sel.contains(&key) { "selected" } else { "" }
);
let class = {
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 representation = element.representation().clone();
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 regulator_list = element.regulators().map(
move |elt_reg_keys| elt_reg_keys
|regs| regs
.clone()
.into_iter()
.sorted_by_key(
|&reg_key| state.assembly.regulators.with(
|regs| regs[reg_key].subjects().len()
)
)
.sorted_by_key(|reg| reg.subjects().len())
.collect()
);
let details_node = create_node_ref();
@ -178,10 +160,11 @@ fn ElementOutlineItem(key: ElementKey, element: Rc<dyn Element>) -> View {
summary(
class=class.get(),
on:keydown={
let element_for_handler = element.clone();
move |event: KeyboardEvent| {
match event.key().as_str() {
"Enter" => {
state.select(key, event.shift_key());
state.select(&element_for_handler, event.shift_key());
event.prevent_default();
},
"ArrowRight" if regulated.get() => {
@ -208,19 +191,10 @@ fn ElementOutlineItem(key: ElementKey, element: Rc<dyn Element>) -> View {
div(
class="element",
on:click={
let state_for_handler = state.clone();
let element_for_handler = element.clone();
move |event: MouseEvent| {
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);
});
}
state_for_handler.select(&element_for_handler, event.shift_key());
event.stop_propagation();
event.prevent_default();
}
@ -234,13 +208,8 @@ fn ElementOutlineItem(key: ElementKey, element: Rc<dyn Element>) -> View {
ul(class="regulators") {
Keyed(
list=regulator_list,
view=move |reg_key| view! {
RegulatorOutlineItem(
regulator_key=reg_key,
element_key=key
)
},
key=|reg_key| reg_key.clone()
view=move |reg| reg.outline_item(&element),
key=|reg| reg.serial()
)
}
}
@ -259,12 +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())
.map(|(key, elt)| (key, ElementRc(elt)))
.sorted_by_key(|elt| elt.id().clone())
.collect()
);
@ -278,10 +250,10 @@ pub fn Outline() -> View {
) {
Keyed(
list=element_list,
view=|(key, ElementRc(elt))| view! {
ElementOutlineItem(key=key, element=elt)
view=|elt| view! {
ElementOutlineItem(element=elt)
},
key=|(_, ElementRc(elt))| elt.serial()
key=|elt| elt.serial()
)
}
}