Skip to main content

rue_core/node/
children.rs

1use std::collections::HashMap;
2
3use super::patch::patch_node;
4use super::mount::mount_to_dom;
5use super::VNode;
6
7// ---------------------------------------------------------------------------
8// Children reconciliation entry point
9// ---------------------------------------------------------------------------
10
11/// Patch the children list of a DOM node.
12///
13/// Decides whether to use keyed or un-keyed reconciliation based on whether
14/// the children have `key` attributes.
15pub fn patch_children(
16    old_children: &[VNode],
17    new_children: &[VNode],
18    parent: &web_sys::Node,
19) {
20    let old_has_keys = old_children.iter().any(|c| c.key().is_some());
21    let new_has_keys = new_children.iter().any(|c| c.key().is_some());
22
23    if old_has_keys && new_has_keys {
24        patch_keyed_children(old_children, new_children, parent);
25    } else {
26        patch_unkeyed_children(old_children, new_children, parent);
27    }
28}
29
30// ---------------------------------------------------------------------------
31// Un-keyed children (position-based)
32// ---------------------------------------------------------------------------
33
34/// Patch children by position — simpler but less efficient for reordering.
35fn patch_unkeyed_children(
36    old_children: &[VNode],
37    new_children: &[VNode],
38    parent: &web_sys::Node,
39) {
40    let old_len = old_children.len();
41    let new_len = new_children.len();
42    let common_len = old_len.min(new_len);
43
44    // Patch common prefix
45    for i in 0..common_len {
46        patch_node(&old_children[i], &new_children[i], parent, None);
47    }
48
49    if new_len > old_len {
50        // Add new children at the end
51        for i in old_len..new_len {
52            mount_to_dom(&new_children[i], parent, None);
53        }
54    } else if old_len > new_len {
55        // Remove extra old children
56        for i in new_len..old_len {
57            if let Some(node) = old_children[i].dom_node() {
58                let _ = parent.remove_child(&node);
59            }
60        }
61    }
62}
63
64// ---------------------------------------------------------------------------
65// Keyed children — uses Longest Increasing Subsequence
66// ---------------------------------------------------------------------------
67
68/// Patch children using keys for optimal reordering.
69///
70/// Algorithm (inspired by Vue 3):
71/// 1. Build key→index maps for old and new children
72/// 2. Find which old children can be reused (matched by key)
73/// 3. Compute longest increasing subsequence of reusable children
74///    → these are the nodes that stay in place
75/// 4. Remove children that no longer exist
76/// 5. Move and insert children efficiently
77/// 6. Recursively patch each matched pair
78fn patch_keyed_children(
79    old_children: &[VNode],
80    new_children: &[VNode],
81    parent: &web_sys::Node,
82) {
83    // Build key→index map for old children
84    let old_key_to_idx: HashMap<&str, usize> = old_children
85        .iter()
86        .enumerate()
87        .filter_map(|(i, c)| c.key().map(|k| (k, i)))
88        .collect();
89
90    // Build key→index map for new children
91    let new_key_to_idx: HashMap<&str, usize> = new_children
92        .iter()
93        .enumerate()
94        .filter_map(|(i, c)| c.key().map(|k| (k, i)))
95        .collect();
96
97    // Phase 1: Determine which old children are reused, and at what new index.
98    // `new_idx_for_old` maps old_index → new_index for reused children.
99    let mut new_idx_for_old: Vec<Option<usize>> = vec![None; old_children.len()];
100    let mut old_to_new_key: Vec<Option<&str>> = vec![None; old_children.len()];
101
102    for (old_idx, old_child) in old_children.iter().enumerate() {
103        if let Some(key) = old_child.key() {
104            if let Some(&new_idx) = new_key_to_idx.get(key) {
105                new_idx_for_old[old_idx] = Some(new_idx);
106                old_to_new_key[old_idx] = Some(key);
107            }
108        }
109    }
110
111    // Phase 2: Compute the longest increasing subsequence of `new_idx_for_old`
112    // (filtering out the None entries). These are the children that can stay
113    // in their current relative positions.
114    let lis_indices = longest_increasing_subsequence_filtered(&new_idx_for_old);
115
116    // Phase 3: Build a sequence of patch operations.
117    // We iterate through new children and determine what to do:
118    //   - If a child existed in old and is in the LIS → patch in-place, keep position
119    //   - If a child existed in old but is NOT in LIS → patch in-place, but it needs to move
120    //   - If a child is new → mount it
121
122    // First pass: remove children that no longer exist in new
123    for (old_idx, old_child) in old_children.iter().enumerate() {
124        if new_idx_for_old[old_idx].is_none() {
125            if let Some(node) = old_child.dom_node() {
126                let _ = parent.remove_child(&node);
127            }
128        }
129    }
130
131    // Second pass: process new children in order, patching or mounting as needed.
132    // We maintain a cursor position in the DOM to handle inserts correctly.
133    // This is a simplified version — for full correctness we'd use DOM anchors.
134
135    // We use a reference child approach: for each new child, we ensure it
136    // appears before the next old child that hasn't been processed yet.
137    // The LIS tells us which old children are already in the right order.
138
139    let mut lis_set: Vec<bool> = vec![false; old_children.len()];
140    for &idx in &lis_indices {
141        lis_set[idx] = true;
142    }
143
144    // We'll collect the operations and apply them.
145    // For simplicity, we do a two-pass: first patch/move, then insert new.
146
147    for (new_idx, new_child) in new_children.iter().enumerate() {
148        if let Some(key) = new_child.key() {
149            if let Some(&old_idx) = old_key_to_idx.get(key) {
150                // This child existed in old — patch it
151                let old_child = &old_children[old_idx];
152                let is_stable = lis_set[old_idx];
153
154                if is_stable {
155                    // Child is already in correct position — just patch
156                    patch_node(old_child, new_child, parent, None);
157                } else {
158                    // Child needs to be moved to its new position
159                    // We insert before the next reference node
160                    let ref_node = find_reference_node(new_children, new_idx, parent);
161                    patch_node(old_child, new_child, parent, ref_node.as_ref());
162                }
163            } else {
164                // Brand new child — mount it
165                let ref_node = find_reference_node(new_children, new_idx, parent);
166                mount_to_dom(new_child, parent, ref_node.as_ref());
167            }
168        } else {
169            // No key — treat as new and mount (simplified)
170            let ref_node = find_reference_node(new_children, new_idx, parent);
171            mount_to_dom(new_child, parent, ref_node.as_ref());
172        }
173    }
174}
175
176// ---------------------------------------------------------------------------
177// Helpers
178// ---------------------------------------------------------------------------
179
180/// Find a reference node to insert `new_child` before.
181/// This looks at the next new child that already exists in the DOM.
182fn find_reference_node(
183    new_children: &[VNode],
184    new_idx: usize,
185    _parent: &web_sys::Node,
186) -> Option<web_sys::Node> {
187    // Look for the next new child after `new_idx` that has a DOM node
188    for next in (new_idx + 1)..new_children.len() {
189        if let Some(node) = new_children[next].dom_node() {
190            return Some(node);
191        }
192    }
193    None
194}
195
196// ---------------------------------------------------------------------------
197// Longest Increasing Subsequence
198// ---------------------------------------------------------------------------
199
200/// Compute the longest increasing subsequence of an array, returning the
201/// **indices** (into `arr`) of elements that form the LIS.
202///
203/// Uses the patience sorting algorithm (O(n log n)) and then reconstructs
204/// the actual subsequence indices.
205pub fn longest_increasing_subsequence(arr: &[usize]) -> Vec<usize> {
206    if arr.is_empty() {
207        return vec![];
208    }
209
210    // `tails[i]` = last element of the best subsequence of length (i+1)
211    // We store *indices* into `arr`.
212    let mut tails: Vec<usize> = vec![];
213    // `prev[i]` = index of the previous element in the best subsequence ending at i
214    let mut prev: Vec<isize> = vec![-1; arr.len()];
215
216    for (i, &val) in arr.iter().enumerate() {
217        // Binary search for the first tail >= val
218        let pos = match tails.binary_search_by(|&tail_idx| arr[tail_idx].cmp(&val)) {
219            Ok(p) => p,  // exact match — replace
220            Err(p) => p, // insertion point
221        };
222
223        if pos == tails.len() {
224            tails.push(i);
225        } else {
226            tails[pos] = i;
227        }
228
229        // Set prev pointer (if pos > 0, link to tails[pos-1])
230        if pos > 0 {
231            prev[i] = tails[pos - 1] as isize;
232        }
233    }
234
235    // Reconstruct the subsequence from the tails
236    let mut result: Vec<usize> = vec![];
237    if let Some(&last) = tails.last() {
238        let mut idx = last;
239        loop {
240            result.push(idx);
241            if prev[idx] == -1 {
242                break;
243            }
244            idx = prev[idx] as usize;
245        }
246        result.reverse();
247    }
248
249    result
250}
251
252/// Compute LIS but only for elements where `arr[i]` is `Some(usize)`,
253/// ignoring `None` entries. Returns the indices (into `arr`) of the LIS.
254fn longest_increasing_subsequence_filtered(arr: &[Option<usize>]) -> Vec<usize> {
255    // Collect the values of Some entries with their original indices
256    let filtered: Vec<(usize, usize)> = arr
257        .iter()
258        .enumerate()
259        .filter_map(|(i, &opt)| opt.map(|v| (i, v)))
260        .collect();
261
262    if filtered.is_empty() {
263        return vec![];
264    }
265
266    let values: Vec<usize> = filtered.iter().map(|(_, v)| *v).collect();
267    let lis = longest_increasing_subsequence(&values);
268
269    // Map back to original indices
270    lis.iter().map(|&idx| filtered[idx].0).collect()
271}
272
273// ---------------------------------------------------------------------------
274// Tests
275// ---------------------------------------------------------------------------
276
277#[cfg(test)]
278mod tests {
279    use super::*;
280
281    #[test]
282    fn test_lis_empty() {
283        assert_eq!(longest_increasing_subsequence(&[]), Vec::<usize>::new());
284    }
285
286    #[test]
287    fn test_lis_single() {
288        assert_eq!(longest_increasing_subsequence(&[5]), vec![0]);
289    }
290
291    #[test]
292    fn test_lis_increasing() {
293        assert_eq!(longest_increasing_subsequence(&[1, 2, 3, 4]), vec![0, 1, 2, 3]);
294    }
295
296    #[test]
297    fn test_lis_decreasing() {
298        let result = longest_increasing_subsequence(&[4, 3, 2, 1]);
299        // Any single element is a valid LIS of length 1
300        assert_eq!(result.len(), 1);
301    }
302
303    #[test]
304    fn test_lis_mixed() {
305        // From Wikipedia: 0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15
306        // LIS: 0, 2, 6, 9, 11, 15 (values), or 0, 4, 6, 9, 13, 15 (indices)
307        let arr = [0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15];
308        let result = longest_increasing_subsequence(&arr);
309        // Verify it's increasing
310        for w in result.windows(2) {
311            assert!(arr[w[0]] < arr[w[1]], "LIS should be increasing");
312        }
313        // The known LIS length is 6
314        assert_eq!(result.len(), 6);
315    }
316
317    #[test]
318    fn test_lis_filtered() {
319        let arr = vec![Some(0), None, Some(1), None, Some(2)];
320        let result = longest_increasing_subsequence_filtered(&arr);
321        assert_eq!(result, vec![0, 2, 4]);
322    }
323
324    #[test]
325    fn test_lis_filtered_out_of_order() {
326        let arr = vec![Some(2), None, Some(0), Some(1)];
327        let result = longest_increasing_subsequence_filtered(&arr);
328        assert_eq!(result.len(), 2);
329        // Should be indices 2, 3 (values 0, 1)
330        assert!(result.contains(&2));
331        assert!(result.contains(&3));
332    }
333}