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