1use std::collections::{HashMap, HashSet};
2use std::iter;
3use std::rc::Rc;
4use_RefCell!();
5
6use pax_language::Computable;
7use pax_manifest::ExpressionInfo;
8use pax_runtime_api::CoercionRules;
9use pax_runtime_api::{
10 borrow, borrow_mut, use_RefCell, ImplToFromPaxAny, LocalProperty, PaxValue, ToPaxValue,
11 Variable,
12};
13
14use crate::api::Layer;
15use crate::{
16 BaseInstance, ExpandedNode, InstanceFlags, InstanceNode, InstantiationArgs, RuntimeContext,
17 RuntimePropertiesStackFrame,
18};
19
20mod value_eq;
21
22fn update_repeat_bindings(
23 index: &LocalProperty<usize>,
24 item: &LocalProperty<PaxValue>,
25 i: usize,
26 value: PaxValue,
27) {
28 index.set_if_neq(i);
29 if !item.read(|previous| value_eq::same_value(previous, &value)) {
32 item.set(value);
33 }
34}
35
36const INTERNAL_REPEAT_INDEX_SYMBOL: &str = "$repeat_index";
37const INTERNAL_REPEAT_ELEMENT_SYMBOL: &str = "$repeat_element";
38const INTERNAL_REPEAT_KEY_SYMBOL: &str = "$repeat_key";
39
40pub struct RepeatInstance {
46 pub base: BaseInstance,
47}
48
49impl ImplToFromPaxAny for RepeatProperties {}
50#[derive(Default)]
53pub struct RepeatProperties {
54 pub source_expression: LocalProperty<PaxValue>,
55 pub iterator_i_symbol: LocalProperty<Option<String>>,
56 pub iterator_elem_symbol: LocalProperty<Option<String>>,
57 pub repeat_key_expression: Option<ExpressionInfo>,
58}
59
60impl ToPaxValue for RepeatProperties {
61 fn to_pax_value(self) -> PaxValue {
62 PaxValue::Object(
63 vec![
64 (
65 "source_expression".to_string(),
66 self.source_expression.to_pax_value(),
67 ),
68 (
69 "iterator_i_symbol".to_string(),
70 self.iterator_i_symbol.to_pax_value(),
71 ),
72 (
73 "iterator_elem_symbol".to_string(),
74 self.iterator_elem_symbol.to_pax_value(),
75 ),
76 ]
77 .into_iter()
78 .collect(),
79 )
80 }
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86 use crate::api::math::Transform2;
87 use crate::api::CommonProperties;
88 use crate::{ComponentInstance, ExpandedNode, Globals, RouteLocation, TransformAndBounds};
89 use pax_language::interpreter::property_resolution::IdentifierResolver;
90 use pax_language::parse_pax_expression;
91 use pax_manifest::cartridge_generation::{
92 ComponentTransitionConfig, TRANSITION_PHASE_ENTER, TRANSITION_PHASE_EXIT,
93 TRANSITION_PHASE_IDLE,
94 };
95 use pax_runtime_api::pax_value::{PaxAny, ToFromPaxAny};
96 use pax_runtime_api::{borrow, CoercionRules, Numeric, Platform, Size, TargetInfo, OS};
97 use std::cell::RefCell;
98
99 fn test_globals() -> Globals {
100 Globals {
101 elapsed_frames: LocalProperty::new(0),
102 elapsed_millis: LocalProperty::new(0),
103 viewport: LocalProperty::new(TransformAndBounds {
104 transform: Transform2::identity(),
105 bounds: (100.0, 100.0),
106 }),
107 gyro: LocalProperty::new(Default::default()),
108 accel: LocalProperty::new(Default::default()),
109 route_location: LocalProperty::new(RouteLocation::root()),
110 browser_allows_scroller_vector_layers: LocalProperty::new(true),
111 browser_allows_nested_scroller_vector_layers: LocalProperty::new(true),
112 platform: Platform::Unknown,
113 os: OS::Unknown,
114 target: TargetInfo::new(Platform::Unknown, OS::Unknown),
115 #[cfg(feature = "designtime")]
116 designtime: crate::test_support::designtime(),
117 get_elapsed_millis: Rc::new(|| 0),
118 }
119 }
120
121 fn default_properties_factory() -> Box<
122 dyn Fn(
123 Rc<RuntimePropertiesStackFrame>,
124 Option<Rc<ExpandedNode>>,
125 ) -> Option<Rc<RefCell<PaxAny>>>,
126 > {
127 Box::new(|_, expanded_node| {
128 expanded_node
129 .is_none()
130 .then(|| Rc::new(RefCell::new(PaxAny::Builtin(PaxValue::default()))))
131 })
132 }
133
134 fn default_common_properties_factory() -> Box<
135 dyn Fn(
136 Rc<RuntimePropertiesStackFrame>,
137 Option<Rc<ExpandedNode>>,
138 ) -> Option<Rc<RefCell<CommonProperties>>>,
139 > {
140 Box::new(|_, expanded_node| {
141 expanded_node
142 .is_none()
143 .then(|| Rc::new(RefCell::new(CommonProperties::default())))
144 })
145 }
146
147 fn component_args(template: Option<Vec<Rc<dyn InstanceNode>>>) -> InstantiationArgs {
148 InstantiationArgs {
149 prototypical_common_properties: crate::CommonPropertiesInit::Factory(
150 default_common_properties_factory(),
151 ),
152 prototypical_properties: crate::PropertiesInit::Factory(default_properties_factory()),
153 handler_registry: None,
154 children: None,
155 component_template: template.map(RefCell::new),
156 component_settings: None,
157 template_node_identifier: None,
158 template_node_type_id: None,
159 template_node_selector_info: None,
160 transition_config: Default::default(),
161 properties_scope: crate::PropertiesScopeInit::None,
162 }
163 }
164
165 fn leaf_args(transition_config: ComponentTransitionConfig) -> InstantiationArgs {
166 InstantiationArgs {
167 prototypical_common_properties: crate::CommonPropertiesInit::Factory(
168 default_common_properties_factory(),
169 ),
170 prototypical_properties: crate::PropertiesInit::Factory(default_properties_factory()),
171 handler_registry: None,
172 children: None,
173 component_template: Some(RefCell::new(Vec::new())),
174 component_settings: None,
175 template_node_identifier: None,
176 template_node_type_id: None,
177 template_node_selector_info: None,
178 transition_config,
179 properties_scope: crate::PropertiesScopeInit::None,
180 }
181 }
182
183 fn repeat_args(
184 source: LocalProperty<PaxValue>,
185 children: Vec<Rc<dyn InstanceNode>>,
186 ) -> InstantiationArgs {
187 repeat_args_with_key(source, children, true)
188 }
189
190 fn unkeyed_repeat_args(
191 source: LocalProperty<PaxValue>,
192 children: Vec<Rc<dyn InstanceNode>>,
193 ) -> InstantiationArgs {
194 repeat_args_with_key(source, children, false)
195 }
196
197 fn repeat_args_with_key(
198 source: LocalProperty<PaxValue>,
199 children: Vec<Rc<dyn InstanceNode>>,
200 use_key: bool,
201 ) -> InstantiationArgs {
202 let key_expression = ExpressionInfo::new(parse_pax_expression("item.id").unwrap());
203 let source_for_factory = source.clone();
204 InstantiationArgs {
205 prototypical_common_properties: crate::CommonPropertiesInit::Factory(
206 default_common_properties_factory(),
207 ),
208 prototypical_properties: crate::PropertiesInit::Factory(Box::new(
209 move |_, expanded_node| {
210 expanded_node.is_none().then(|| {
211 Rc::new(RefCell::new(
212 RepeatProperties {
213 source_expression: source_for_factory.clone(),
214 iterator_i_symbol: LocalProperty::new(Some("i".to_string())),
215 iterator_elem_symbol: LocalProperty::new(Some("item".to_string())),
216 repeat_key_expression: use_key.then(|| key_expression.clone()),
217 }
218 .to_pax_any(),
219 ))
220 })
221 },
222 )),
223 handler_registry: None,
224 children: Some(RefCell::new(children)),
225 component_template: None,
226 component_settings: None,
227 template_node_identifier: None,
228 template_node_type_id: None,
229 template_node_selector_info: None,
230 transition_config: Default::default(),
231 properties_scope: crate::PropertiesScopeInit::None,
232 }
233 }
234
235 fn positioned_leaf_args() -> InstantiationArgs {
236 InstantiationArgs {
237 prototypical_common_properties: crate::CommonPropertiesInit::Factory(Box::new(
238 |env, expanded_node| {
239 if expanded_node.is_some() {
240 return None;
241 }
242 let i_untyped = env.resolve_symbol_as_erased_property("i").unwrap();
243 let i = LocalProperty::<usize>::new_from_untyped(i_untyped.clone());
244 let deps = [i_untyped];
245
246 let mut cp = CommonProperties::default();
247 cp.x = LocalProperty::computed(
248 move || Some(Size::Pixels((i.get() as f64 * 10.0).into())),
249 &deps,
250 );
251 cp.width = LocalProperty::new(Some(Size::Pixels(10.into())));
252 cp.height = LocalProperty::new(Some(Size::Pixels(10.into())));
253 Some(Rc::new(RefCell::new(cp)))
254 },
255 )),
256 prototypical_properties: crate::PropertiesInit::Factory(default_properties_factory()),
257 handler_registry: None,
258 children: None,
259 component_template: Some(RefCell::new(Vec::new())),
260 component_settings: None,
261 template_node_identifier: None,
262 template_node_type_id: None,
263 template_node_selector_info: None,
264 transition_config: Default::default(),
265 properties_scope: crate::PropertiesScopeInit::None,
266 }
267 }
268
269 fn transitioned_positioned_leaf_args() -> InstantiationArgs {
270 let mut args = positioned_leaf_args();
271 args.transition_config = ComponentTransitionConfig {
272 has_enter: true,
273 enter_frame_count: 10,
274 has_exit: true,
275 exit_frame_count: 10,
276 timeout_ms: 5_000,
277 ..Default::default()
278 };
279 args
280 }
281
282 fn expression_positioned_leaf_args(expr: &str) -> InstantiationArgs {
283 let expression = ExpressionInfo::new(parse_pax_expression(expr).unwrap());
284 InstantiationArgs {
285 prototypical_common_properties: crate::CommonPropertiesInit::Factory(Box::new(
286 move |env, expanded_node| {
287 if expanded_node.is_some() {
288 return None;
289 }
290 let mut deps = Vec::new();
291 for dependency in &expression.dependencies {
292 let property = env
293 .resolve_symbol_as_erased_property(dependency)
294 .unwrap_or_else(|| panic!("missing dependency: {dependency}"));
295 deps.push(property);
296 }
297
298 let expression_for_eval = expression.clone();
299 let env_for_eval = Rc::clone(&env);
300 let mut cp = CommonProperties::default();
301 cp.x = LocalProperty::computed(
302 move || {
303 let env_for_eval: Rc<dyn IdentifierResolver> = env_for_eval.clone();
304 expression_for_eval
305 .expression
306 .compute(env_for_eval)
307 .ok()
308 .and_then(|value| Size::try_coerce(value).ok())
309 },
310 &deps,
311 );
312 cp.width = LocalProperty::new(Some(Size::Pixels(10.into())));
313 cp.height = LocalProperty::new(Some(Size::Pixels(10.into())));
314 Some(Rc::new(RefCell::new(cp)))
315 },
316 )),
317 prototypical_properties: crate::PropertiesInit::Factory(default_properties_factory()),
318 handler_registry: None,
319 children: None,
320 component_template: Some(RefCell::new(Vec::new())),
321 component_settings: None,
322 template_node_identifier: None,
323 template_node_type_id: None,
324 template_node_selector_info: None,
325 transition_config: Default::default(),
326 properties_scope: crate::PropertiesScopeInit::None,
327 }
328 }
329
330 fn transitioned_expression_positioned_leaf_args(expr: &str) -> InstantiationArgs {
331 let mut args = expression_positioned_leaf_args(expr);
332 args.transition_config = ComponentTransitionConfig {
333 has_enter: true,
334 enter_frame_count: 10,
335 has_exit: true,
336 exit_frame_count: 10,
337 timeout_ms: 5_000,
338 ..Default::default()
339 };
340 args
341 }
342
343 fn item(id: &str) -> PaxValue {
344 item_with_x(id, 0.0)
345 }
346
347 fn item_with_x(id: &str, x: f64) -> PaxValue {
348 PaxValue::Object(
349 vec![
350 ("id".to_string(), PaxValue::String(id.to_string())),
351 ("x".to_string(), x.to_pax_value()),
352 ]
353 .into_iter()
354 .collect(),
355 )
356 }
357
358 fn source(ids: &[&str]) -> PaxValue {
359 PaxValue::Vec(ids.iter().map(|id| item(id)).collect())
360 }
361
362 fn source_with_x(entries: &[(&str, f64)]) -> PaxValue {
363 PaxValue::Vec(entries.iter().map(|(id, x)| item_with_x(id, *x)).collect())
364 }
365
366 fn ids(nodes: &[Rc<ExpandedNode>]) -> Vec<u32> {
367 nodes.iter().map(|node| node.id.0).collect()
368 }
369
370 fn mounted_repeat(source: LocalProperty<PaxValue>) -> (Rc<ExpandedNode>, Rc<RuntimeContext>) {
371 let leaf: Rc<dyn InstanceNode> =
372 ComponentInstance::instantiate(leaf_args(Default::default()));
373 let repeat: Rc<dyn InstanceNode> =
374 RepeatInstance::instantiate(repeat_args(source, vec![leaf]));
375 let root = ComponentInstance::instantiate(component_args(Some(vec![repeat])));
376 let context = Rc::new(crate::test_support::runtime_context(test_globals()));
377 let root = ExpandedNode::initialize_root(root, &context);
378 root.recurse_update(&context);
379 context.drain_node_effects();
380 (root, context)
381 }
382
383 fn binding_probe(
384 child: &ExpandedNode,
385 symbol: &str,
386 ) -> (LocalProperty<()>, Rc<std::cell::Cell<usize>>) {
387 let property = child
388 .stack
389 .resolve_symbol_as_erased_property(symbol)
390 .unwrap();
391 let runs = Rc::new(std::cell::Cell::new(0));
392 let count = Rc::clone(&runs);
393 let probe = LocalProperty::computed(move || count.set(count.get() + 1), &[property]);
394 probe.get();
395 (probe, runs)
396 }
397
398 #[test]
399 fn keyed_repeat_only_notifies_changed_items_and_indices() {
400 let source_property = LocalProperty::new(source_with_x(&[("a", 1.0), ("b", 2.0)]));
401 let (root, context) = mounted_repeat(source_property.clone());
402 let repeat = root.children.get().remove(0);
403 let children = repeat.children.get();
404 let probes: Vec<_> = children
405 .iter()
406 .flat_map(|child| [binding_probe(child, "item"), binding_probe(child, "i")])
407 .collect();
408 let counts = || {
409 probes
410 .iter()
411 .map(|(probe, runs)| {
412 probe.get();
413 runs.get()
414 })
415 .collect::<Vec<_>>()
416 };
417
418 source_property.set(source_with_x(&[("a", 1.0), ("b", 2.0)]));
419 context.drain_node_effects();
420 assert_eq!(ids(&repeat.children.get()), ids(&children));
421 assert_eq!(
422 counts(),
423 [1, 1, 1, 1],
424 "identical source must not notify retained items"
425 );
426
427 source_property.set(source_with_x(&[("a", 1.0 + 1e-8), ("b", 2.0)]));
428 context.drain_node_effects();
429 assert_eq!(
430 counts(),
431 [2, 1, 1, 1],
432 "even sub-epsilon edits must propagate"
433 );
434
435 source_property.set(source_with_x(&[("b", 2.0), ("a", 1.0 + 1e-8)]));
436 context.drain_node_effects();
437 assert_eq!(
438 ids(&repeat.children.get()),
439 [children[1].id.0, children[0].id.0]
440 );
441 assert_eq!(counts(), [2, 2, 1, 2], "reordering only changes indices");
442 }
443
444 #[test]
445 fn keyed_repeat_reuses_reordered_children_and_exits_removed_keys() {
446 let source_property = LocalProperty::new(source(&["a", "b", "c"]));
447 let leaf_transition_config = ComponentTransitionConfig {
448 has_exit: true,
449 exit_frame_count: 30,
450 timeout_ms: 5_000,
451 ..Default::default()
452 };
453 let leaf: Rc<dyn InstanceNode> =
454 ComponentInstance::instantiate(leaf_args(leaf_transition_config));
455 let repeat: Rc<dyn InstanceNode> =
456 RepeatInstance::instantiate(repeat_args(source_property.clone(), vec![leaf]));
457 let root_component =
458 ComponentInstance::instantiate(component_args(Some(vec![Rc::clone(&repeat)])));
459 let context = Rc::new(crate::test_support::runtime_context(test_globals()));
460 let root = ExpandedNode::initialize_root(root_component, &context);
461
462 root.recurse_update(&context);
463 let repeat_node = root.children.get().remove(0);
464 let initial = repeat_node.children.get();
465 assert_eq!(initial.len(), 3);
466 let initial_ids = ids(&initial);
467
468 source_property.set(source(&["c", "a", "b"]));
469 root.recurse_update(&context);
470 let reordered_active = borrow!(repeat_node.active_children).clone();
471 assert_eq!(
472 ids(&reordered_active),
473 vec![initial_ids[2], initial_ids[0], initial_ids[1]]
474 );
475 assert!(borrow!(repeat_node.exiting_children).is_empty());
476
477 source_property.set(source(&["c", "d", "a"]));
478 root.recurse_update(&context);
479 let active = borrow!(repeat_node.active_children).clone();
480 let exiting = borrow!(repeat_node.exiting_children).clone();
481
482 assert_eq!(active.len(), 3);
483 assert_eq!(exiting.len(), 1);
484 assert_eq!(active[0].id.0, initial_ids[2]);
485 assert_ne!(active[1].id.0, initial_ids[0]);
486 assert_ne!(active[1].id.0, initial_ids[1]);
487 assert_ne!(active[1].id.0, initial_ids[2]);
488 assert_eq!(active[2].id.0, initial_ids[0]);
489 assert_eq!(exiting[0].id.0, initial_ids[1]);
490 assert_eq!(exiting[0].transition_phase.get(), TRANSITION_PHASE_EXIT);
491
492 source_property.set(source(&["c", "b", "a"]));
493 root.recurse_update(&context);
494 let active = borrow!(repeat_node.active_children).clone();
495 assert_eq!(active[1].id.0, initial_ids[1]);
496 assert_eq!(borrow!(repeat_node.mounted_children).len(), 4);
497 assert_eq!(active[1].transition_phase.get(), TRANSITION_PHASE_IDLE);
498 }
499
500 #[test]
501 fn retained_children_keep_parent_bindings_on_data_updates_and_reorder() {
502 let source_property = LocalProperty::new(source_with_x(&[("a", 1.0), ("b", 2.0)]));
503 let (root, context) = mounted_repeat(source_property.clone());
504 let repeat = root.children.get().remove(0);
505 let children = repeat.children.get();
506 let initial: Vec<_> = children
507 .iter()
508 .map(|c| c.parent_binding_rebuilds.get())
509 .collect();
510 for value in [
511 source_with_x(&[("a", 3.0), ("b", 2.0)]),
512 source_with_x(&[("b", 2.0), ("a", 3.0)]),
513 ] {
514 source_property.set(value);
515 context.drain_node_effects();
516 assert_eq!(
517 children
518 .iter()
519 .map(|c| c.parent_binding_rebuilds.get())
520 .collect::<Vec<_>>(),
521 initial
522 );
523 }
524 source_property.set(source_with_x(&[("b", 2.0), ("a", 3.0), ("c", 4.0)]));
525 context.drain_node_effects();
526 assert_eq!(
527 children
528 .iter()
529 .map(|c| c.parent_binding_rebuilds.get())
530 .collect::<Vec<_>>(),
531 initial
532 );
533 assert_eq!(repeat.children.get().len(), 3);
534 }
535
536 #[test]
537 fn equal_valued_parent_sources_are_not_interchangeable() {
538 let (root, context) = mounted_repeat(LocalProperty::new(source(&["a"])));
539 let repeat = root.children.get().remove(0);
540 let children = repeat.children.get();
541 let child = &children[0];
542 let first = LocalProperty::new(None);
543 let second = LocalProperty::new(None);
544 repeat.attach_children(children.clone(), &context, &first);
545 let n = child.parent_binding_rebuilds.get();
546 repeat.attach_children(children.clone(), &context, &first);
547 assert_eq!(child.parent_binding_rebuilds.get(), n);
548 repeat.attach_children(children.clone(), &context, &second);
549 assert_eq!(child.parent_binding_rebuilds.get(), n + 1);
550 first.set(Some(root.id));
551 assert_eq!(
552 child.parent_frame.get(),
553 None,
554 "old frame source must be disconnected"
555 );
556 second.set(Some(repeat.id));
557 assert_eq!(child.parent_frame.get(), Some(repeat.id));
558
559 let cp = repeat.get_common_properties();
561 borrow!(cp)
562 .opacity
563 .set(Some(pax_runtime_api::Opacity::from(0.25)));
564 assert_eq!(child.computed_opacity.get(), 0.25);
565 assert_eq!(child.parent_binding_rebuilds.get(), n + 1);
566 }
567
568 #[test]
569 fn data_only_reconciliation_skips_structure_and_churn_releases_properties() {
570 let source_property = LocalProperty::new(source_with_x(&[("a", 1.0), ("b", 2.0)]));
571 let (root, context) = mounted_repeat(source_property.clone());
572 let repeat = root.children.get().remove(0);
573 let counts = || {
574 (
575 repeat.layout_hull_rebuilds.get(),
576 repeat.structural_children_updates.get(),
577 )
578 };
579 let initial = counts();
580 source_property.set(source_with_x(&[("a", 8.0), ("b", 2.0)]));
581 context.drain_node_effects();
582 assert_eq!(counts(), initial);
583 let settled = pax_runtime_api::properties::property_table_total_properties_count();
584 for _ in 0..40 {
585 source_property.set(source_with_x(&[("a", 8.0), ("b", 2.0), ("c", 3.0)]));
586 context.drain_node_effects();
587 source_property.set(source_with_x(&[("a", 8.0), ("b", 2.0)]));
588 context.drain_node_effects();
589 assert_eq!(
590 pax_runtime_api::properties::property_table_total_properties_count(),
591 settled
592 );
593 }
594 }
595
596 #[test]
597 fn nested_repeats_do_not_reconcile_unchanged_sibling_payloads() {
598 let data = |x| {
599 PaxValue::Vec(vec![
600 PaxValue::Object(vec![
601 ("id".into(), PaxValue::String("a".into())),
602 ("parts".into(), source_with_x(&[("part", x)])),
603 ]),
604 PaxValue::Object(vec![
605 ("id".into(), PaxValue::String("b".into())),
606 ("parts".into(), source_with_x(&[("part", 2.0)])),
607 ]),
608 ])
609 };
610 let source_property = LocalProperty::new(data(1.0));
611 let source_evaluations = Rc::new(std::cell::Cell::new(0));
612 let evaluations = source_evaluations.clone();
613 let leaf: Rc<dyn InstanceNode> =
614 ComponentInstance::instantiate(leaf_args(Default::default()));
615 let mut inner_args = repeat_args(LocalProperty::new(PaxValue::default()), vec![leaf]);
616 inner_args.prototypical_properties =
617 crate::PropertiesInit::Factory(Box::new(move |env, node| {
618 if node.is_some() {
619 return None;
620 }
621 let outer_item = LocalProperty::<PaxValue>::new_from_untyped(
622 env.resolve_symbol_as_erased_property("item").unwrap(),
623 );
624 let dep = outer_item.untyped();
625 let evaluations = evaluations.clone();
626 let source_expression = LocalProperty::computed(
627 move || {
628 evaluations.set(evaluations.get() + 1);
629 let PaxValue::Object(fields) = outer_item.get() else {
630 panic!("expected item object")
631 };
632 fields
633 .into_iter()
634 .find(|(name, _)| name == "parts")
635 .unwrap()
636 .1
637 },
638 &[dep],
639 );
640 Some(Rc::new(RefCell::new(
641 RepeatProperties {
642 source_expression,
643 iterator_i_symbol: LocalProperty::new(Some("i".into())),
644 iterator_elem_symbol: LocalProperty::new(Some("item".into())),
645 repeat_key_expression: Some(ExpressionInfo::new(
646 parse_pax_expression("item.id").unwrap(),
647 )),
648 }
649 .to_pax_any(),
650 )))
651 }));
652 let inner: Rc<dyn InstanceNode> = RepeatInstance::instantiate(inner_args);
653 let outer: Rc<dyn InstanceNode> =
654 RepeatInstance::instantiate(repeat_args(source_property.clone(), vec![inner]));
655 let root = ComponentInstance::instantiate(component_args(Some(vec![outer])));
656 let context = Rc::new(crate::test_support::runtime_context(test_globals()));
657 let root = ExpandedNode::initialize_root(root, &context);
658 root.recurse_update(&context);
659 context.drain_node_effects();
660 let outer = root.children.get().remove(0);
661 let inners = outer.children.get();
662 let first = inners[0].children.get()[0].clone();
663 let other = inners[1].children.get()[0].clone();
664 let (first_probe, first_runs) = binding_probe(&first, "item");
665 let (other_probe, other_runs) = binding_probe(&other, "item");
666 let evaluations = source_evaluations.get();
667 source_property.set(data(1.0));
668 context.drain_node_effects();
669 assert_eq!(source_evaluations.get(), evaluations);
670 source_property.set(data(1.0 + 1e-8));
671 context.drain_node_effects();
672 first_probe.get();
673 other_probe.get();
674 assert_eq!(source_evaluations.get(), evaluations + 1);
675 assert_eq!((first_runs.get(), other_runs.get()), (2, 1));
676 assert_eq!(inners[0].children.get()[0].id, first.id);
677 assert_eq!(inners[1].children.get()[0].id, other.id);
678 }
679
680 #[test]
681 fn parent_values_resize_reactively_and_replaced_sources_rebind() {
682 let (root, context) = mounted_repeat(LocalProperty::new(source(&["a"])));
683 let repeat = root.children.get().remove(0);
684 let children = repeat.children.get();
685 let child = &children[0];
686 let parent_cp = repeat.get_common_properties();
687 let child_cp = child.get_common_properties();
688 borrow!(parent_cp).width.set(Some(Size::Pixels(100.into())));
689 borrow!(child_cp).width.set(Some(Size::Percent(50.into())));
690 assert_eq!(child.transform_and_bounds.get().bounds.0, 50.0);
691 let rebuilds = child.parent_binding_rebuilds.get();
692 borrow!(parent_cp).width.set(Some(Size::Pixels(200.into())));
693 assert_eq!(child.transform_and_bounds.get().bounds.0, 100.0);
694 repeat.attach_children(children.clone(), &context, &repeat.parent_frame);
695 assert_eq!(child.parent_binding_rebuilds.get(), rebuilds);
696
697 let old_opacity = borrow!(child_cp).opacity.clone();
698 let new_opacity = LocalProperty::new(old_opacity.get());
699 borrow_mut!(child_cp).opacity = new_opacity.clone();
700 repeat.attach_children(children.clone(), &context, &repeat.parent_frame);
701 assert_eq!(child.parent_binding_rebuilds.get(), rebuilds + 1);
702 old_opacity.set(Some(0.25.into()));
703 assert_eq!(child.computed_opacity.get(), 1.0);
704 new_opacity.set(Some(0.75.into()));
705 assert_eq!(child.computed_opacity.get(), 0.75);
706
707 let template = borrow!(child.instance_node).clone();
708 child.recreate_with_new_data(template, &context);
709 context.drain_node_effects();
710 let rebuilt = child.parent_binding_rebuilds.get();
711 repeat.attach_children(children.clone(), &context, &repeat.parent_frame);
712 assert_eq!(
713 child.parent_binding_rebuilds.get(),
714 rebuilt + 1,
715 "reload must invalidate the binding fast path"
716 );
717 }
718
719 #[test]
720 fn repeat_exit_partition_and_final_removal_are_structural_changes() {
721 let source_property = LocalProperty::new(source(&["a"]));
722 let config = ComponentTransitionConfig {
723 has_exit: true,
724 exit_frame_count: 5,
725 timeout_ms: 5_000,
726 ..Default::default()
727 };
728 let leaf: Rc<dyn InstanceNode> = ComponentInstance::instantiate(leaf_args(config));
729 let repeat: Rc<dyn InstanceNode> =
730 RepeatInstance::instantiate(repeat_args(source_property.clone(), vec![leaf]));
731 let root = ComponentInstance::instantiate(component_args(Some(vec![repeat])));
732 let context = Rc::new(crate::test_support::runtime_context(test_globals()));
733 let root = ExpandedNode::initialize_root(root, &context);
734 root.recurse_update(&context);
735 context.drain_node_effects();
736 let repeat = root.children.get().remove(0);
737 let id = repeat.children.get()[0].id;
738 let before_exit = repeat.structural_children_updates.get();
739 source_property.set(source(&[]));
740 context.drain_node_effects();
741 assert_eq!(repeat.children.get()[0].id, id);
742 assert!(
743 repeat.structural_children_updates.get() > before_exit,
744 "active-to-exiting affects projection even with identical rendered IDs"
745 );
746 let before_prune = repeat.structural_children_updates.get();
747 context.globals().elapsed_frames.set(10);
748 context.drain_node_effects();
749 assert!(repeat.children.get().is_empty());
750 assert!(repeat.structural_children_updates.get() > before_prune);
751
752 source_property.set(source(&["a"]));
754 context.drain_node_effects();
755 source_property.set(source(&[]));
756 context.drain_node_effects();
757 context.globals().elapsed_frames.set(20);
758 source_property.set(source(&["b"]));
759 context.drain_node_effects();
760 assert_eq!(repeat.children.get().len(), 1);
761 assert!(borrow!(repeat.exiting_children).is_empty());
762 }
763
764 #[test]
765 fn external_key_dependencies_and_invalid_key_fallback_remain_reactive() {
766 let source_property = LocalProperty::new(source(&["a"]));
767 let leaf: Rc<dyn InstanceNode> =
768 ComponentInstance::instantiate(leaf_args(Default::default()));
769 let mut args = repeat_args(source_property.clone(), vec![leaf]);
770 let source_copy = source_property.clone();
771 args.prototypical_properties = crate::PropertiesInit::Factory(Box::new(move |_, node| {
772 node.is_none().then(|| {
773 Rc::new(RefCell::new(
774 RepeatProperties {
775 source_expression: source_copy.clone(),
776 iterator_i_symbol: LocalProperty::new(Some("i".into())),
777 iterator_elem_symbol: LocalProperty::new(Some("item".into())),
778 repeat_key_expression: Some(ExpressionInfo::new(
779 parse_pax_expression("$frames").unwrap(),
780 )),
781 }
782 .to_pax_any(),
783 ))
784 })
785 }));
786 let repeat: Rc<dyn InstanceNode> = RepeatInstance::instantiate(args);
787 let root = ComponentInstance::instantiate(component_args(Some(vec![repeat])));
788 let context = Rc::new(crate::test_support::runtime_context(test_globals()));
789 let root = ExpandedNode::initialize_root(root, &context);
790 root.recurse_update(&context);
791 context.drain_node_effects();
792 let repeat = root.children.get().remove(0);
793 let first_id = repeat.children.get()[0].id;
794 context.globals().elapsed_frames.set(1);
795 context.drain_node_effects();
796 assert_ne!(repeat.children.get()[0].id, first_id);
797
798 let invalid = PaxValue::Object(vec![("id".into(), PaxValue::Bool(true))]);
799 let data = PaxValue::Vec(vec![item("same"), item("same"), invalid]);
800 let source_property = LocalProperty::new(data.clone());
801 let (root, context) = mounted_repeat(source_property.clone());
802 let repeat = root.children.get().remove(0);
803 let first_ids = ids(&repeat.children.get());
804 assert_eq!(first_ids.iter().collect::<HashSet<_>>().len(), 3);
805 source_property.set(data);
806 context.drain_node_effects();
807 assert_eq!(ids(&repeat.children.get()), first_ids);
808 }
809
810 #[test]
811 fn keyed_groups_keep_all_template_roots_together_on_reorder() {
812 let source_property = LocalProperty::new(source(&["a", "b"]));
813 let leaf: Rc<dyn InstanceNode> =
814 ComponentInstance::instantiate(leaf_args(Default::default()));
815 let repeat: Rc<dyn InstanceNode> = RepeatInstance::instantiate(repeat_args(
816 source_property.clone(),
817 vec![leaf.clone(), leaf],
818 ));
819 let context = Rc::new(crate::test_support::runtime_context(test_globals()));
820 let root = ExpandedNode::initialize_root(
821 ComponentInstance::instantiate(component_args(Some(vec![repeat]))),
822 &context,
823 );
824 root.recurse_update(&context);
825 context.drain_node_effects();
826 let repeat = root.children.get().remove(0);
827 let initial = ids(&repeat.children.get());
828 assert_eq!(initial.len(), 4);
829 source_property.set(source(&["b", "a"]));
830 context.drain_node_effects();
831 assert_eq!(
832 ids(&repeat.children.get()),
833 [initial[2], initial[3], initial[0], initial[1]]
834 );
835 }
836
837 #[test]
838 fn detached_mask_style_repeat_binds_inserted_children_and_releases_last_child() {
839 let source_property = LocalProperty::new(source(&[]));
840 let context = Rc::new(crate::test_support::runtime_context(test_globals()));
841 let root = ExpandedNode::initialize_root(
842 ComponentInstance::instantiate(component_args(None)),
843 &context,
844 );
845 let mut args = leaf_args(Default::default());
846 args.prototypical_common_properties =
847 crate::CommonPropertiesInit::Factory(Box::new(|_, node| {
848 node.is_none().then(|| {
849 let mut cp = CommonProperties::default();
850 cp.width = LocalProperty::new(Some(Size::Percent(50.into())));
851 cp.height = LocalProperty::new(Some(Size::Percent(50.into())));
852 Rc::new(RefCell::new(cp))
853 })
854 }));
855 let leaf: Rc<dyn InstanceNode> = ComponentInstance::instantiate(args);
856 let repeat: Rc<dyn InstanceNode> =
857 RepeatInstance::instantiate(repeat_args(source_property.clone(), vec![leaf]));
858 let sidecar = root.create_children_detached(
859 iter::once((repeat, root.stack.clone())),
860 &context,
861 &Rc::downgrade(&root),
862 );
863 let sidecar = root.attach_sidecar_children(sidecar, &context, &root.parent_frame);
864 let repeat = &sidecar[0];
865 repeat.recurse_control_flow_expansion(&context);
866 context.drain_node_effects();
867 assert!(repeat.children.get().is_empty());
868 source_property.set(source(&["ring"]));
869 let children = repeat.children.get();
870 repeat.attach_children(children.clone(), &context, &repeat.parent_frame);
871 context.drain_node_effects();
872 assert_eq!(
873 children[0].attached.get(),
874 0,
875 "mask geometry must not acquire visible mount lifecycle"
876 );
877 assert_eq!(children[0].transform_and_bounds.get().bounds, (50.0, 50.0));
878 context.globals().viewport.set(TransformAndBounds {
879 transform: Transform2::identity(),
880 bounds: (300.0, 200.0),
881 });
882 context.drain_node_effects();
883 assert_eq!(
884 children[0].transform_and_bounds.get().bounds,
885 (150.0, 100.0)
886 );
887 let rebuilds = children[0].parent_binding_rebuilds.get();
888 source_property.set(source(&["ring"]));
889 repeat.attach_children(repeat.children.get(), &context, &repeat.parent_frame);
890 assert_eq!(children[0].parent_binding_rebuilds.get(), rebuilds);
891 source_property.set(source(&[]));
892 repeat.attach_children(repeat.children.get(), &context, &repeat.parent_frame);
893 context.drain_node_effects();
894 assert!(repeat.children.get().is_empty());
895 assert!(borrow!(repeat.mounted_children).is_empty());
896 }
897
898 #[test]
899 fn unkeyed_repeat_rescues_a_position_readded_during_exit() {
900 let source_property = LocalProperty::new(source(&["a", "b"]));
901 let leaf_transition_config = ComponentTransitionConfig {
902 has_enter: true,
903 enter_frame_count: 10,
904 has_exit: true,
905 exit_frame_count: 30,
906 timeout_ms: 5_000,
907 ..Default::default()
908 };
909 let leaf: Rc<dyn InstanceNode> =
910 ComponentInstance::instantiate(leaf_args(leaf_transition_config));
911 let repeat: Rc<dyn InstanceNode> =
912 RepeatInstance::instantiate(unkeyed_repeat_args(source_property.clone(), vec![leaf]));
913 let root_component =
914 ComponentInstance::instantiate(component_args(Some(vec![Rc::clone(&repeat)])));
915 let context = Rc::new(crate::test_support::runtime_context(test_globals()));
916 let root = ExpandedNode::initialize_root(root_component, &context);
917
918 root.recurse_update(&context);
919 let repeat_node = root.children.get().remove(0);
920 let second_id = borrow!(repeat_node.active_children)[1].id;
921
922 source_property.set(source(&["a"]));
923 root.recurse_update(&context);
924 assert_eq!(borrow!(repeat_node.exiting_children)[0].id, second_id);
925
926 source_property.set(source(&["a", "b"]));
927 root.recurse_update(&context);
928 assert_eq!(borrow!(repeat_node.active_children)[1].id, second_id);
929 assert!(borrow!(repeat_node.exiting_children).is_empty());
930 assert_eq!(
931 borrow!(repeat_node.active_children)[1]
932 .transition_phase
933 .get(),
934 TRANSITION_PHASE_ENTER
935 );
936 }
937
938 #[test]
939 fn keyed_repeat_assigns_distinct_scope_bindings_per_child() {
940 let source_property = LocalProperty::new(source(&["a", "b", "c"]));
941 let leaf: Rc<dyn InstanceNode> =
942 ComponentInstance::instantiate(leaf_args(Default::default()));
943 let repeat: Rc<dyn InstanceNode> =
944 RepeatInstance::instantiate(repeat_args(source_property, vec![leaf]));
945 let root_component =
946 ComponentInstance::instantiate(component_args(Some(vec![Rc::clone(&repeat)])));
947 let context = Rc::new(crate::test_support::runtime_context(test_globals()));
948 let root = ExpandedNode::initialize_root(root_component, &context);
949
950 root.recurse_update(&context);
951 let repeat_node = root.children.get().remove(0);
952 let children = repeat_node.children.get();
953 assert_eq!(children.len(), 3);
954
955 let indices: Vec<_> = children
956 .iter()
957 .map(|child| {
958 child
959 .stack
960 .resolve_symbol("i")
961 .and_then(|variable| Numeric::try_coerce(variable.get_as_pax_value()).ok())
962 .map(|value| value.to_int())
963 })
964 .collect();
965 assert_eq!(indices, vec![Some(0), Some(1), Some(2)]);
966 }
967
968 #[test]
969 fn keyed_repeat_assigns_distinct_element_bindings_per_child() {
970 let source_property =
971 LocalProperty::new(source_with_x(&[("a", 0.0), ("b", 10.0), ("c", 20.0)]));
972 let leaf: Rc<dyn InstanceNode> =
973 ComponentInstance::instantiate(leaf_args(Default::default()));
974 let repeat: Rc<dyn InstanceNode> =
975 RepeatInstance::instantiate(repeat_args(source_property, vec![leaf]));
976 let root_component =
977 ComponentInstance::instantiate(component_args(Some(vec![Rc::clone(&repeat)])));
978 let context = Rc::new(crate::test_support::runtime_context(test_globals()));
979 let root = ExpandedNode::initialize_root(root_component, &context);
980
981 root.recurse_update(&context);
982 let repeat_node = root.children.get().remove(0);
983 let children = repeat_node.children.get();
984 assert_eq!(children.len(), 3);
985
986 let xs: Vec<_> = children
987 .iter()
988 .map(|child| {
989 child
990 .stack
991 .resolve_symbol("item")
992 .map(|variable| variable.get_as_pax_value())
993 .and_then(|value| match value {
994 PaxValue::Object(fields) => fields
995 .into_iter()
996 .find_map(|(name, value)| (name == "x").then_some(value)),
997 _ => None,
998 })
999 .and_then(|value| Numeric::try_coerce(value).ok())
1000 .map(|value| value.to_float())
1001 })
1002 .collect();
1003
1004 assert_eq!(xs, vec![Some(0.0), Some(10.0), Some(20.0)]);
1005 }
1006
1007 #[test]
1008 fn keyed_repeat_resolves_distinct_common_properties_per_child() {
1009 let source_property = LocalProperty::new(source(&["a", "b", "c"]));
1010 let leaf: Rc<dyn InstanceNode> = ComponentInstance::instantiate(positioned_leaf_args());
1011 let repeat: Rc<dyn InstanceNode> =
1012 RepeatInstance::instantiate(repeat_args(source_property, vec![leaf]));
1013 let root_component =
1014 ComponentInstance::instantiate(component_args(Some(vec![Rc::clone(&repeat)])));
1015 let context = Rc::new(crate::test_support::runtime_context(test_globals()));
1016 let root = ExpandedNode::initialize_root(root_component, &context);
1017
1018 root.recurse_update(&context);
1019 let repeat_node = root.children.get().remove(0);
1020 let children = repeat_node.children.get();
1021 assert_eq!(children.len(), 3);
1022
1023 let xs: Vec<_> = children
1024 .iter()
1025 .map(|child| {
1026 borrow!(child.get_common_properties())
1027 .x
1028 .get()
1029 .map(|size| size.expect_pixels().to_float())
1030 })
1031 .collect();
1032
1033 assert_eq!(xs, vec![Some(0.0), Some(10.0), Some(20.0)]);
1034 }
1035
1036 #[test]
1037 fn keyed_repeat_resolves_distinct_expression_common_properties_from_element_binding() {
1038 let source_property =
1039 LocalProperty::new(source_with_x(&[("a", 0.0), ("b", 10.0), ("c", 20.0)]));
1040 let leaf: Rc<dyn InstanceNode> =
1041 ComponentInstance::instantiate(expression_positioned_leaf_args("(item.x)px"));
1042 let repeat: Rc<dyn InstanceNode> =
1043 RepeatInstance::instantiate(repeat_args(source_property, vec![leaf]));
1044 let root_component =
1045 ComponentInstance::instantiate(component_args(Some(vec![Rc::clone(&repeat)])));
1046 let context = Rc::new(crate::test_support::runtime_context(test_globals()));
1047 let root = ExpandedNode::initialize_root(root_component, &context);
1048
1049 root.recurse_update(&context);
1050 let repeat_node = root.children.get().remove(0);
1051 let children = repeat_node.children.get();
1052 assert_eq!(children.len(), 3);
1053
1054 let xs: Vec<_> = children
1055 .iter()
1056 .map(|child| {
1057 borrow!(child.get_common_properties())
1058 .x
1059 .get()
1060 .map(|size| size.expect_pixels().to_float())
1061 })
1062 .collect();
1063
1064 assert_eq!(xs, vec![Some(0.0), Some(10.0), Some(20.0)]);
1065 }
1066
1067 #[test]
1068 fn keyed_repeat_resolves_distinct_common_properties_for_transition_components() {
1069 let source_property = LocalProperty::new(source(&["a", "b", "c"]));
1070 let leaf: Rc<dyn InstanceNode> =
1071 ComponentInstance::instantiate(transitioned_positioned_leaf_args());
1072 let repeat: Rc<dyn InstanceNode> =
1073 RepeatInstance::instantiate(repeat_args(source_property, vec![leaf]));
1074 let root_component =
1075 ComponentInstance::instantiate(component_args(Some(vec![Rc::clone(&repeat)])));
1076 let context = Rc::new(crate::test_support::runtime_context(test_globals()));
1077 let root = ExpandedNode::initialize_root(root_component, &context);
1078
1079 root.recurse_update(&context);
1080 let repeat_node = root.children.get().remove(0);
1081 let children = repeat_node.children.get();
1082 assert_eq!(children.len(), 3);
1083
1084 let xs: Vec<_> = children
1085 .iter()
1086 .map(|child| {
1087 borrow!(child.get_common_properties())
1088 .x
1089 .get()
1090 .map(|size| size.expect_pixels().to_float())
1091 })
1092 .collect();
1093
1094 assert_eq!(xs, vec![Some(0.0), Some(10.0), Some(20.0)]);
1095 }
1096
1097 #[test]
1098 fn keyed_repeat_resolves_expression_common_properties_for_transition_components() {
1099 let source_property =
1100 LocalProperty::new(source_with_x(&[("a", 0.0), ("b", 10.0), ("c", 20.0)]));
1101 let leaf: Rc<dyn InstanceNode> = ComponentInstance::instantiate(
1102 transitioned_expression_positioned_leaf_args("(item.x)px"),
1103 );
1104 let repeat: Rc<dyn InstanceNode> =
1105 RepeatInstance::instantiate(repeat_args(source_property, vec![leaf]));
1106 let root_component =
1107 ComponentInstance::instantiate(component_args(Some(vec![Rc::clone(&repeat)])));
1108 let context = Rc::new(crate::test_support::runtime_context(test_globals()));
1109 let root = ExpandedNode::initialize_root(root_component, &context);
1110
1111 root.recurse_update(&context);
1112 let repeat_node = root.children.get().remove(0);
1113 let children = repeat_node.children.get();
1114 assert_eq!(children.len(), 3);
1115
1116 let xs: Vec<_> = children
1117 .iter()
1118 .map(|child| {
1119 borrow!(child.get_common_properties())
1120 .x
1121 .get()
1122 .map(|size| size.expect_pixels().to_float())
1123 })
1124 .collect();
1125
1126 assert_eq!(xs, vec![Some(0.0), Some(10.0), Some(20.0)]);
1127 }
1128
1129 #[test]
1130 fn unkeyed_repeat_stale_element_binding_defaults_after_source_shrinks() {
1131 let source_property = LocalProperty::new(source_with_x(&[("a", 0.0), ("b", 10.0)]));
1132 let leaf: Rc<dyn InstanceNode> =
1133 ComponentInstance::instantiate(leaf_args(Default::default()));
1134 let repeat: Rc<dyn InstanceNode> =
1135 RepeatInstance::instantiate(unkeyed_repeat_args(source_property.clone(), vec![leaf]));
1136 let root_component =
1137 ComponentInstance::instantiate(component_args(Some(vec![Rc::clone(&repeat)])));
1138 let context = Rc::new(crate::test_support::runtime_context(test_globals()));
1139 let root = ExpandedNode::initialize_root(root_component, &context);
1140
1141 root.recurse_update(&context);
1142 let repeat_node = root.children.get().remove(0);
1143 let children = repeat_node.children.get();
1144 assert_eq!(children.len(), 2);
1145
1146 let stale_item = children[1].stack.resolve_symbol("item").unwrap();
1147 source_property.set(source_with_x(&[("a", 0.0)]));
1148
1149 assert_eq!(stale_item.get_as_pax_value(), PaxValue::default());
1150 }
1151}
1152
1153#[derive(Clone, Debug, Eq, Hash, PartialEq)]
1154enum RepeatKey {
1155 String(String),
1156 Int(i64),
1157 Synthetic(usize),
1158}
1159
1160#[derive(Clone)]
1161struct RepeatChildGroup {
1162 key: RepeatKey,
1163 elem: LocalProperty<PaxValue>,
1164 i: LocalProperty<usize>,
1165 children: Vec<Rc<ExpandedNode>>,
1166}
1167
1168pub struct RepeatItem {
1170 pub elem: LocalProperty<PaxValue>,
1171 pub i: LocalProperty<usize>,
1172}
1173
1174impl ToPaxValue for RepeatItem {
1175 fn to_pax_value(self) -> PaxValue {
1176 PaxValue::Object(
1177 vec![
1178 ("elem".to_string(), self.elem.get().to_pax_value()),
1179 ("i".to_string(), self.i.get().to_pax_value()),
1180 ]
1181 .into_iter()
1182 .collect(),
1183 )
1184 }
1185}
1186
1187impl InstanceNode for RepeatInstance {
1188 fn instantiate(args: InstantiationArgs) -> Rc<Self>
1189 where
1190 Self: Sized,
1191 {
1192 Rc::new(Self {
1193 base: BaseInstance::new(
1194 args,
1195 InstanceFlags {
1196 invisible_to_slot: true,
1197 invisible_to_raycasting: true,
1198 layer: Layer::DontCare,
1199 is_component: false,
1200 is_slot: false,
1201 },
1202 ),
1203 })
1204 }
1205
1206 fn resolve_debug(
1207 &self,
1208 f: &mut std::fmt::Formatter,
1209 _expanded_node: Option<&ExpandedNode>,
1210 ) -> std::fmt::Result {
1211 f.debug_struct("Repeat").finish()
1212 }
1213
1214 fn base(&self) -> &BaseInstance {
1215 &self.base
1216 }
1217
1218 fn handle_mount(
1219 self: Rc<Self>,
1220 expanded_node: &Rc<ExpandedNode>,
1221 context: &Rc<RuntimeContext>,
1222 ) {
1223 self.handle_setup(expanded_node, context, true);
1224 }
1225
1226 fn handle_control_flow_node_expansion(
1227 self: Rc<Self>,
1228 expanded_node: &Rc<ExpandedNode>,
1229 context: &Rc<RuntimeContext>,
1230 ) {
1231 self.handle_setup(expanded_node, context, false);
1232 }
1233}
1234
1235impl RepeatInstance {
1236 fn handle_setup(
1237 self: Rc<Self>,
1238 expanded_node: &Rc<ExpandedNode>,
1239 context: &Rc<RuntimeContext>,
1240 is_mount: bool,
1241 ) {
1242 let weak_ref_self = Rc::downgrade(expanded_node);
1245 let cloned_self = Rc::clone(&self);
1246 let cloned_context = Rc::clone(context);
1247 let source_expression =
1248 expanded_node.with_properties_unwrapped(|properties: &mut RepeatProperties| {
1249 properties.source_expression.clone()
1250 });
1251
1252 let i_symbol =
1253 expanded_node.with_properties_unwrapped(|properties: &mut RepeatProperties| {
1254 properties.iterator_i_symbol.clone()
1255 });
1256 let elem_symbol =
1257 expanded_node.with_properties_unwrapped(|properties: &mut RepeatProperties| {
1258 properties.iterator_elem_symbol.clone()
1259 });
1260 let repeat_key_expression =
1261 expanded_node.with_properties_unwrapped(|properties: &mut RepeatProperties| {
1262 properties.repeat_key_expression.clone()
1263 });
1264
1265 let mut deps = vec![
1266 source_expression.untyped(),
1267 i_symbol.untyped(),
1268 elem_symbol.untyped(),
1269 ];
1270 if let Some(repeat_key_expression) = &repeat_key_expression {
1271 for dependency in &repeat_key_expression.dependencies {
1272 if let Some(property) = expanded_node
1273 .stack
1274 .resolve_symbol_as_erased_property(dependency)
1275 {
1276 deps.push(property);
1277 }
1278 }
1279 }
1280
1281 let last_length = Rc::new(RefCell::new(0));
1282 let last_elem_sym = Rc::new(RefCell::new(None));
1283 let last_i_sym = Rc::new(RefCell::new(None));
1284 let cached_children: Rc<RefCell<Vec<Rc<ExpandedNode>>>> = Default::default();
1285 let keyed_groups: Rc<RefCell<Vec<RepeatChildGroup>>> = Default::default();
1286
1287 let children = LocalProperty::computed_with_name(
1288 move || {
1289 let Some(cloned_expanded_node) = weak_ref_self.upgrade() else {
1290 panic!("ran evaluator after expanded node dropped (repeat elem)")
1291 };
1292 let source_len = source_expression.read(Self::source_len);
1293 if let Some(repeat_key_expression) = &repeat_key_expression {
1294 let i_symbol_value = i_symbol.get();
1295 let elem_symbol_value = elem_symbol.get();
1296 let symbols_changed = i_symbol_value != *borrow!(last_i_sym)
1297 || elem_symbol_value != *borrow!(last_elem_sym);
1298 *borrow_mut!(last_i_sym) = i_symbol_value.clone();
1299 *borrow_mut!(last_elem_sym) = elem_symbol_value.clone();
1300 if symbols_changed {
1301 borrow_mut!(keyed_groups).clear();
1302 }
1303 return Self::compute_keyed_children(
1304 &cloned_expanded_node,
1305 &cloned_context,
1306 Rc::clone(&cloned_self),
1307 &source_expression,
1308 i_symbol_value,
1309 elem_symbol_value,
1310 repeat_key_expression,
1311 Rc::clone(&keyed_groups),
1312 source_len,
1313 is_mount,
1314 !symbols_changed,
1315 );
1316 }
1317
1318 if source_len == *borrow!(last_length)
1319 && i_symbol.read(|i| i == &*borrow!(last_i_sym))
1320 && elem_symbol.read(|e| e == &*borrow!(last_elem_sym))
1321 {
1322 return if cloned_expanded_node.attached.get() > 0 {
1323 cloned_expanded_node.current_attached_children()
1324 } else {
1325 borrow!(cached_children).clone()
1326 };
1327 }
1328 let i_symbol_value = i_symbol.get();
1329 let elem_symbol_value = elem_symbol.get();
1330 let symbols_unchanged = i_symbol_value == *borrow!(last_i_sym)
1331 && elem_symbol_value == *borrow!(last_elem_sym);
1332 *borrow_mut!(last_length) = source_len;
1333 *borrow_mut!(last_i_sym) = i_symbol_value.clone();
1334 *borrow_mut!(last_elem_sym) = elem_symbol_value.clone();
1335
1336 let template_children = borrow!(cloned_self.base().get_instance_children()).clone();
1337 let active_children = borrow!(cloned_expanded_node.active_children).clone();
1338 let mut selected_children = Vec::new();
1339 for i in 0..source_len {
1340 let property_i = LocalProperty::new(i);
1341 let cp_source_expression = source_expression.clone();
1342 let property_elem = LocalProperty::computed_with_name(
1343 move || cp_source_expression.read(|source| Self::source_elem(source, i)),
1344 &[source_expression.untyped()],
1345 "repeat elem",
1346 );
1347 let scope = Self::repeat_scope(
1348 i_symbol_value.clone(),
1349 elem_symbol_value.clone(),
1350 property_i,
1351 property_elem,
1352 );
1353 let new_env = cloned_expanded_node.stack.push(scope);
1354 for (template_offset, template) in template_children.iter().enumerate() {
1355 let flat_index = i * template_children.len() + template_offset;
1356 let active = symbols_unchanged
1357 .then(|| active_children.get(flat_index))
1358 .flatten()
1359 .filter(|child| {
1360 let child_template = borrow!(child.instance_node).clone();
1361 Rc::ptr_eq(&child_template, template)
1362 })
1363 .cloned();
1364 let rescued = active.or_else(|| {
1365 (symbols_unchanged
1366 && is_mount
1367 && cloned_expanded_node.attached.get() > 0)
1368 .then(|| {
1369 cloned_expanded_node.rescue_exiting_child_matching(|child| {
1370 let child_template = borrow!(child.instance_node).clone();
1371 let template_matches =
1372 Rc::ptr_eq(&child_template, template);
1373 let index_matches = child
1374 .stack
1375 .resolve_symbol_as_erased_property(
1376 INTERNAL_REPEAT_INDEX_SYMBOL,
1377 )
1378 .map(LocalProperty::<usize>::new_from_untyped)
1379 .map(|index| index.get() == i)
1380 .unwrap_or(true);
1381 template_matches && index_matches
1382 })
1383 })
1384 .flatten()
1385 });
1386 let child = rescued.unwrap_or_else(|| {
1387 cloned_expanded_node
1388 .create_children_detached(
1389 iter::once((Rc::clone(template), Rc::clone(&new_env))),
1390 &cloned_context,
1391 &Rc::downgrade(&cloned_expanded_node),
1392 )
1393 .pop()
1394 .expect("repeat child creation returned no child")
1395 });
1396 if !is_mount && child.attached.get() == 0 {
1397 child.recurse_control_flow_expansion(&cloned_context);
1398 }
1399 selected_children.push(child);
1400 }
1401 }
1402 let ret = if is_mount {
1403 cloned_expanded_node.attach_children(
1404 selected_children,
1405 &cloned_context,
1406 &cloned_expanded_node.parent_frame,
1407 )
1408 } else {
1409 selected_children
1410 };
1411 *borrow_mut!(cached_children) = ret.clone();
1412 ret
1413 },
1414 &deps,
1415 &format!("repeat_children (node id: {})", expanded_node.id.0),
1416 );
1417 expanded_node.children.replace_with(children);
1418 }
1419
1420 fn source_len(source: &PaxValue) -> usize {
1421 if let PaxValue::Range(start, end) = source {
1422 (isize::try_coerce(*end.clone()).unwrap() - isize::try_coerce(*start.clone()).unwrap())
1423 as usize
1424 } else if let PaxValue::Vec(v) = source {
1425 v.len()
1426 } else {
1427 log::warn!("source is not a vec");
1428 0
1429 }
1430 }
1431
1432 fn source_elem(source: &PaxValue, i: usize) -> PaxValue {
1433 if let PaxValue::Range(start, _) = source {
1434 let start = isize::try_coerce(*start.clone()).unwrap();
1435 (start + i as isize).to_pax_value()
1436 } else if let PaxValue::Vec(v) = source {
1437 v.get(i).cloned().unwrap_or_default()
1438 } else {
1439 log::warn!("source is not a vec");
1440 Default::default()
1441 }
1442 }
1443
1444 fn repeat_scope(
1445 i_symbol: Option<String>,
1446 elem_symbol: Option<String>,
1447 property_i: LocalProperty<usize>,
1448 property_elem: LocalProperty<PaxValue>,
1449 ) -> HashMap<String, Variable> {
1450 let mut scope = HashMap::new();
1451 if let Some(i_symbol) = i_symbol {
1452 scope.insert(
1453 i_symbol,
1454 Variable::new_from_typed_property(property_i.clone()),
1455 );
1456 }
1457 if let Some(elem_symbol) = elem_symbol {
1458 scope.insert(
1459 elem_symbol,
1460 Variable::new_from_typed_property(property_elem.clone()),
1461 );
1462 }
1463 scope.insert(
1464 INTERNAL_REPEAT_INDEX_SYMBOL.to_string(),
1465 Variable::new_from_typed_property(property_i),
1466 );
1467 scope.insert(
1468 INTERNAL_REPEAT_ELEMENT_SYMBOL.to_string(),
1469 Variable::new_from_typed_property(property_elem),
1470 );
1471 scope
1472 }
1473
1474 fn encoded_repeat_key(key: &RepeatKey) -> String {
1475 match key {
1476 RepeatKey::String(value) => format!("string:{value}"),
1477 RepeatKey::Int(value) => format!("int:{value}"),
1478 RepeatKey::Synthetic(value) => format!("synthetic:{value}"),
1479 }
1480 }
1481
1482 fn repeat_scope_with_key(
1483 i_symbol: Option<String>,
1484 elem_symbol: Option<String>,
1485 property_i: LocalProperty<usize>,
1486 property_elem: LocalProperty<PaxValue>,
1487 key: &RepeatKey,
1488 ) -> HashMap<String, Variable> {
1489 let mut scope = Self::repeat_scope(i_symbol, elem_symbol, property_i, property_elem);
1490 scope.insert(
1491 INTERNAL_REPEAT_KEY_SYMBOL.to_string(),
1492 Variable::new_from_typed_property(LocalProperty::new(Self::encoded_repeat_key(key))),
1493 );
1494 scope
1495 }
1496
1497 fn repeat_key_from_value(value: PaxValue, index: usize) -> Option<RepeatKey> {
1498 match value {
1499 PaxValue::String(value) => Some(RepeatKey::String(value)),
1500 PaxValue::Numeric(value) if !value.is_float() => Some(RepeatKey::Int(value.to_int())),
1501 _ => {
1502 log::warn!(
1503 "repeat key for index {} must evaluate to a string or integer; using positional fallback",
1504 index
1505 );
1506 None
1507 }
1508 }
1509 }
1510
1511 fn evaluate_key(
1512 key_expression: &ExpressionInfo,
1513 env: Rc<RuntimePropertiesStackFrame>,
1514 index: usize,
1515 ) -> Option<RepeatKey> {
1516 key_expression
1517 .expression
1518 .compute(env)
1519 .map(|value| Self::repeat_key_from_value(value, index))
1520 .unwrap_or_else(|err| {
1521 log::warn!(
1522 "failed to compute repeat key for index {}: {}; using positional fallback",
1523 index,
1524 err
1525 );
1526 None
1527 })
1528 }
1529
1530 fn compute_keyed_children(
1531 expanded_node: &Rc<ExpandedNode>,
1532 context: &Rc<RuntimeContext>,
1533 repeat: Rc<RepeatInstance>,
1534 source_expression: &LocalProperty<PaxValue>,
1535 i_symbol_value: Option<String>,
1536 elem_symbol_value: Option<String>,
1537 key_expression: &ExpressionInfo,
1538 keyed_groups: Rc<RefCell<Vec<RepeatChildGroup>>>,
1539 source_len: usize,
1540 is_mount: bool,
1541 allow_rescue: bool,
1542 ) -> Vec<Rc<ExpandedNode>> {
1543 let mut old_groups_by_key: HashMap<RepeatKey, RepeatChildGroup> = borrow_mut!(keyed_groups)
1544 .drain(..)
1545 .map(|group| (group.key.clone(), group))
1546 .collect();
1547 let mut new_groups = Vec::new();
1548 let mut new_children = Vec::new();
1549 let mut used_keys = HashSet::new();
1550 let template_children = repeat.base().get_instance_children();
1551
1552 for i in 0..source_len {
1553 let elem = source_expression.read(|source| Self::source_elem(source, i));
1554 let key_probe_i = LocalProperty::new(i);
1555 let key_probe_elem = LocalProperty::new(elem.clone());
1556 let key_probe_scope = Self::repeat_scope(
1557 i_symbol_value.clone(),
1558 elem_symbol_value.clone(),
1559 key_probe_i,
1560 key_probe_elem,
1561 );
1562 let key_probe_env = expanded_node.stack.push(key_probe_scope);
1563 let evaluated_key = Self::evaluate_key(key_expression, key_probe_env, i);
1564 let key = match evaluated_key {
1565 Some(key) if used_keys.insert(key.clone()) => key,
1566 Some(key) => {
1567 log::warn!(
1568 "duplicate repeat key {:?} at index {}; using positional fallback",
1569 key,
1570 i
1571 );
1572 RepeatKey::Synthetic(i)
1573 }
1574 None => RepeatKey::Synthetic(i),
1575 };
1576
1577 let group = if let Some(group) = old_groups_by_key.remove(&key) {
1578 update_repeat_bindings(&group.i, &group.elem, i, elem);
1579 group
1580 } else if allow_rescue && is_mount {
1581 Self::rescue_keyed_group(
1582 expanded_node,
1583 context,
1584 &template_children,
1585 i_symbol_value.clone(),
1586 elem_symbol_value.clone(),
1587 &key,
1588 i,
1589 elem.clone(),
1590 )
1591 .unwrap_or_else(|| {
1592 Self::create_keyed_group(
1593 expanded_node,
1594 context,
1595 &template_children,
1596 i_symbol_value.clone(),
1597 elem_symbol_value.clone(),
1598 key.clone(),
1599 i,
1600 elem,
1601 is_mount,
1602 )
1603 })
1604 } else {
1605 Self::create_keyed_group(
1606 expanded_node,
1607 context,
1608 &template_children,
1609 i_symbol_value.clone(),
1610 elem_symbol_value.clone(),
1611 key.clone(),
1612 i,
1613 elem,
1614 is_mount,
1615 )
1616 };
1617
1618 new_children.extend(group.children.iter().cloned());
1619 new_groups.push(group);
1620 }
1621
1622 *borrow_mut!(keyed_groups) = new_groups;
1623 if is_mount {
1624 expanded_node.attach_children(new_children, context, &expanded_node.parent_frame)
1625 } else {
1626 new_children
1627 }
1628 }
1629
1630 fn create_keyed_group(
1631 expanded_node: &Rc<ExpandedNode>,
1632 context: &Rc<RuntimeContext>,
1633 template_children: &RefCell<Vec<Rc<dyn InstanceNode>>>,
1634 i_symbol: Option<String>,
1635 elem_symbol: Option<String>,
1636 key: RepeatKey,
1637 i: usize,
1638 elem: PaxValue,
1639 is_mount: bool,
1640 ) -> RepeatChildGroup {
1641 let property_i = LocalProperty::new(i);
1642 let property_elem = LocalProperty::new(elem);
1643 let scope = Self::repeat_scope_with_key(
1644 i_symbol,
1645 elem_symbol,
1646 property_i.clone(),
1647 property_elem.clone(),
1648 &key,
1649 );
1650 let new_env = expanded_node.stack.push(scope);
1651 let children = expanded_node.create_children_detached(
1652 borrow!(template_children)
1653 .clone()
1654 .into_iter()
1655 .zip(iter::repeat(new_env)),
1656 context,
1657 &Rc::downgrade(expanded_node),
1658 );
1659 if !is_mount {
1660 for child in &children {
1661 child.recurse_control_flow_expansion(context);
1662 }
1663 }
1664 RepeatChildGroup {
1665 key,
1666 elem: property_elem,
1667 i: property_i,
1668 children,
1669 }
1670 }
1671
1672 fn rescue_keyed_group(
1673 expanded_node: &Rc<ExpandedNode>,
1674 context: &Rc<RuntimeContext>,
1675 template_children: &RefCell<Vec<Rc<dyn InstanceNode>>>,
1676 i_symbol: Option<String>,
1677 elem_symbol: Option<String>,
1678 key: &RepeatKey,
1679 i: usize,
1680 elem: PaxValue,
1681 ) -> Option<RepeatChildGroup> {
1682 let encoded_key = Self::encoded_repeat_key(key);
1683 let templates = borrow!(template_children).clone();
1684 let mut children = templates
1685 .iter()
1686 .map(|template| {
1687 expanded_node.rescue_exiting_child_matching(|child| {
1688 let child_template = borrow!(child.instance_node).clone();
1689 Rc::ptr_eq(&child_template, template)
1690 && child
1691 .stack
1692 .resolve_symbol_as_erased_property(INTERNAL_REPEAT_KEY_SYMBOL)
1693 .map(LocalProperty::<String>::new_from_untyped)
1694 .map(|key| key.get() == encoded_key)
1695 .unwrap_or(false)
1696 })
1697 })
1698 .collect::<Vec<_>>();
1699 let seed = children.iter().flatten().next()?.clone();
1700 let property_i = seed
1701 .stack
1702 .resolve_symbol_as_erased_property(INTERNAL_REPEAT_INDEX_SYMBOL)
1703 .map(LocalProperty::<usize>::new_from_untyped)?;
1704 let property_elem = seed
1705 .stack
1706 .resolve_symbol_as_erased_property(INTERNAL_REPEAT_ELEMENT_SYMBOL)
1707 .map(LocalProperty::<PaxValue>::new_from_untyped)?;
1708 update_repeat_bindings(&property_i, &property_elem, i, elem);
1709 let env = expanded_node.stack.push(Self::repeat_scope_with_key(
1710 i_symbol,
1711 elem_symbol,
1712 property_i.clone(),
1713 property_elem.clone(),
1714 key,
1715 ));
1716 let resolved_children = children
1717 .drain(..)
1718 .zip(templates)
1719 .map(|(child, template)| {
1720 child.unwrap_or_else(|| {
1721 expanded_node
1722 .create_children_detached(
1723 iter::once((template, Rc::clone(&env))),
1724 context,
1725 &Rc::downgrade(expanded_node),
1726 )
1727 .pop()
1728 .expect("repeat child creation returned no child")
1729 })
1730 })
1731 .collect();
1732 Some(RepeatChildGroup {
1733 key: key.clone(),
1734 elem: property_elem,
1735 i: property_i,
1736 children: resolved_children,
1737 })
1738 }
1739}