From f1f87e97be07f871930f16ce877dd14859c9bb61 Mon Sep 17 00:00:00 2001
From: Aaron Fenyes <aaron.fenyes@fareycircles.ooo>
Date: Wed, 26 Mar 2025 23:50:40 -0700
Subject: [PATCH] Store a product regulator's subjects in an array

This lets us iterate over subjects. Based on commit 257ce33, with a few
updates from 4a9e777.
---
 app-proto/src/add_remove.rs | 15 +++++++----
 app-proto/src/assembly.rs   | 52 ++++++++++++++++---------------------
 app-proto/src/outline.rs    |  6 ++---
 3 files changed, 35 insertions(+), 38 deletions(-)

diff --git a/app-proto/src/add_remove.rs b/app-proto/src/add_remove.rs
index 5fed411..cd8b4f9 100644
--- a/app-proto/src/add_remove.rs
+++ b/app-proto/src/add_remove.rs
@@ -188,11 +188,16 @@ pub fn AddRemove() -> View {
                 },
                 on:click=|_| {
                     let state = use_context::<AppState>();
-                    let subjects = state.selection.with(
-                        |sel| {
-                            let subject_vec: Vec<_> = sel.into_iter().collect();
-                            (subject_vec[0].clone(), subject_vec[1].clone())
-                        }
+                    let subjects: [_; 2] = state.selection.with(
+                        // the button is only enabled when two elements are
+                        // selected, so we know the cast to a two-element array
+                        // will succeed
+                        |sel| sel
+                            .clone()
+                            .into_iter()
+                            .collect::<Vec<_>>()
+                            .try_into()
+                            .unwrap()
                     );
                     state.assembly.insert_new_regulator(subjects);
                     state.selection.update(|sel| sel.clear());
diff --git a/app-proto/src/assembly.rs b/app-proto/src/assembly.rs
index 293bf73..d006b7b 100644
--- a/app-proto/src/assembly.rs
+++ b/app-proto/src/assembly.rs
@@ -134,7 +134,7 @@ impl Element {
 
 #[derive(Clone, Copy)]
 pub struct ProductRegulator {
-    pub subjects: (ElementKey, ElementKey),
+    pub subjects: [ElementKey; 2],
     pub measurement: ReadSignal<f64>,
     pub set_point: Signal<SpecifiedValue>
 }
@@ -143,12 +143,10 @@ impl ProductRegulator {
     fn write_to_problem(&self, problem: &mut ConstraintProblem, elts: &Slab<Element>) {
         self.set_point.with_untracked(|set_pt| {
             if let Some(val) = set_pt.value {
-                let subjects = self.subjects;
-                let subject_column_indices = (
-                    elts[subjects.0].column_index,
-                    elts[subjects.1].column_index
+                let subject_column_indices = self.subjects.map(
+                    |subj| elts[subj].column_index
                 );
-                if let (Some(row), Some(col)) = subject_column_indices {
+                if let [Some(row), Some(col)] = subject_column_indices {
                     problem.gram.push_sym(row, col, val);
                 } else {
                     panic!("Tried to write problem data from a regulator with an unindexed subject");
@@ -246,21 +244,23 @@ impl Assembly {
         let subjects = regulator.subjects;
         let key = self.regulators.update(|regs| regs.insert(regulator));
         let subject_regulators = self.elements.with(
-            |elts| (elts[subjects.0].regulators, elts[subjects.1].regulators)
+            |elts| subjects.map(|subj| elts[subj].regulators)
         );
-        subject_regulators.0.update(|regs| regs.insert(key));
-        subject_regulators.1.update(|regs| regs.insert(key));
+        for regulators in subject_regulators {
+            regulators.update(|regs| regs.insert(key));
+        }
     }
     
-    pub fn insert_new_regulator(self, subjects: (ElementKey, ElementKey)) {
+    pub fn insert_new_regulator(self, subjects: [ElementKey; 2]) {
         // create and insert a new regulator
         let measurement = self.elements.map(
             move |elts| {
-                let reps = (
-                    elts[subjects.0].representation.get_clone(),
-                    elts[subjects.1].representation.get_clone()
-                );
-                reps.0.dot(&(&*Q * reps.1))
+                let representations = subjects.map(|subj| elts[subj].representation);
+                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());
@@ -277,8 +277,8 @@ impl Assembly {
             for (_, reg) in regs.into_iter() {
                 console::log_5(
                     &JsValue::from(" "),
-                    &JsValue::from(reg.subjects.0),
-                    &JsValue::from(reg.subjects.1),
+                    &JsValue::from(reg.subjects[0]),
+                    &JsValue::from(reg.subjects[1]),
                     &JsValue::from(":"),
                     &reg.set_point.with_untracked(
                         |set_pt| JsValue::from(set_pt.spec.as_str())
@@ -502,25 +502,17 @@ mod tests {
     fn unindexed_subject_test() {
         let _ = create_root(|| {
             let mut elts = Slab::new();
-            let subjects = (
+            let subjects = [0, 1].map(|k| {
                 elts.insert(
                     Element::new(
-                        "sphere0".to_string(),
-                        "Sphere 0".to_string(),
-                        [1.0_f32, 1.0_f32, 1.0_f32],
-                        engine::sphere(0.0, 0.0, 0.0, 1.0)
-                    )
-                ),
-                elts.insert(
-                    Element::new(
-                        "sphere1".to_string(),
-                        "Sphere 1".to_string(),
+                        "sphere{k}".to_string(),
+                        "Sphere {k}".to_string(),
                         [1.0_f32, 1.0_f32, 1.0_f32],
                         engine::sphere(0.0, 0.0, 0.0, 1.0)
                     )
                 )
-            );
-            elts[subjects.0].column_index = Some(0);
+            });
+            elts[subjects[0]].column_index = Some(0);
             ProductRegulator {
                 subjects: subjects,
                 measurement: create_memo(|| 0.0),
diff --git a/app-proto/src/outline.rs b/app-proto/src/outline.rs
index deede23..2951e69 100644
--- a/app-proto/src/outline.rs
+++ b/app-proto/src/outline.rs
@@ -81,10 +81,10 @@ fn RegulatorOutlineItem(regulator_key: RegulatorKey, element_key: ElementKey) ->
     let state = use_context::<AppState>();
     let assembly = &state.assembly;
     let regulator = assembly.regulators.with(|regs| regs[regulator_key]);
-    let other_subject = if regulator.subjects.0 == element_key {
-        regulator.subjects.1
+    let other_subject = if regulator.subjects[0] == element_key {
+        regulator.subjects[1]
     } else {
-        regulator.subjects.0
+        regulator.subjects[0]
     };
     let other_subject_label = assembly.elements.with(|elts| elts[other_subject].label.clone());
     view! {