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}