1use std::{iter, ops::Range, rc::Rc};
2use_RefCell!();
3
4use pax_runtime_api::pax_value::ImplToFromPaxAny;
5use pax_runtime_api::{borrow, borrow_mut, use_RefCell, LocalProperty, PaxValue, ToPaxValue};
6
7use crate::api::Layer;
8use crate::{
9 BaseInstance, ExpandedNode, InstanceFlags, InstanceNode, InstantiationArgs, RuntimeContext,
10};
11
12pub struct ConditionalInstance {
18 base: BaseInstance,
19 branch_child_ranges: Vec<Range<usize>>,
20}
21
22impl ImplToFromPaxAny for ConditionalProperties {}
23
24#[derive(Default)]
26pub struct ConditionalProperties {
27 pub boolean_expression: LocalProperty<bool>,
28 pub conditional_branches: Vec<LocalProperty<bool>>,
29}
30
31impl ToPaxValue for ConditionalProperties {
32 fn to_pax_value(self) -> PaxValue {
33 PaxValue::Object(
34 vec![
35 (
36 "boolean_expression".to_string(),
37 self.boolean_expression.to_pax_value(),
38 ),
39 (
40 "conditional_branches".to_string(),
41 self.conditional_branches.to_pax_value(),
42 ),
43 ]
44 .into_iter()
45 .collect(),
46 )
47 }
48}
49
50impl InstanceNode for ConditionalInstance {
51 fn instantiate(args: InstantiationArgs) -> Rc<Self>
52 where
53 Self: Sized,
54 {
55 Rc::new(Self {
56 base: BaseInstance::new(
57 args,
58 InstanceFlags {
59 invisible_to_slot: true,
60 invisible_to_raycasting: true,
61 layer: Layer::DontCare,
62 is_component: false,
63 is_slot: false,
64 },
65 ),
66 branch_child_ranges: vec![],
67 })
68 }
69
70 fn handle_mount(
71 self: Rc<Self>,
72 expanded_node: &Rc<ExpandedNode>,
73 context: &Rc<RuntimeContext>,
74 ) {
75 self.handle_setup(expanded_node, context, true);
76 }
77
78 fn handle_control_flow_node_expansion(
79 self: Rc<Self>,
80 expanded_node: &Rc<ExpandedNode>,
81 context: &Rc<RuntimeContext>,
82 ) {
83 self.handle_setup(expanded_node, context, false);
84 }
85
86 fn resolve_debug(
87 &self,
88 f: &mut std::fmt::Formatter,
89 _expanded_node: Option<&ExpandedNode>,
90 ) -> std::fmt::Result {
91 f.debug_struct("Conditional").finish()
92 }
93
94 fn base(&self) -> &BaseInstance {
95 &self.base
96 }
97}
98
99impl ConditionalInstance {
100 pub fn instantiate_with_branch_child_ranges(
101 args: InstantiationArgs,
102 branch_child_ranges: Vec<Range<usize>>,
103 ) -> Rc<Self> {
104 Rc::new(Self {
105 base: BaseInstance::new(
106 args,
107 InstanceFlags {
108 invisible_to_slot: true,
109 invisible_to_raycasting: true,
110 layer: Layer::DontCare,
111 is_component: false,
112 is_slot: false,
113 },
114 ),
115 branch_child_ranges,
116 })
117 }
118
119 fn handle_setup(
120 self: Rc<Self>,
121 expanded_node: &Rc<ExpandedNode>,
122 context: &Rc<RuntimeContext>,
123 is_mount: bool,
124 ) {
125 let weak_ref_self = Rc::downgrade(expanded_node);
126 let cloned_self = Rc::clone(&self);
127 let cloned_context = Rc::clone(context);
128
129 let boolean_expression =
130 expanded_node.with_properties_unwrapped(|properties: &mut ConditionalProperties| {
131 properties.boolean_expression.clone()
132 });
133 let conditional_branches =
134 expanded_node.with_properties_unwrapped(|properties: &mut ConditionalProperties| {
135 properties.conditional_branches.clone()
136 });
137 let branch_conditions = if conditional_branches.is_empty() {
138 vec![boolean_expression]
139 } else {
140 conditional_branches
141 };
142
143 let deps = branch_conditions
144 .iter()
145 .map(|condition| condition.untyped())
146 .collect::<Vec<_>>();
147 let branch_child_ranges = self.branch_child_ranges.clone();
148
149 let old_active_branch = RefCell::new(None::<Option<usize>>);
150 let cached_children = RefCell::new(Vec::new());
151 expanded_node
152 .children
153 .replace_with(LocalProperty::computed_with_name(
154 move || {
155 let Some(cloned_expanded_node) = weak_ref_self.upgrade() else {
156 panic!("ran evaluator after expanded node dropped (conditional elem)")
157 };
158 let active_branch = branch_conditions
159 .iter()
160 .enumerate()
161 .find_map(|(index, condition)| condition.get().then_some(index));
162 if *borrow!(old_active_branch) == Some(active_branch) {
163 return if cloned_expanded_node.attached.get() > 0 {
164 cloned_expanded_node.current_attached_children()
165 } else {
166 borrow!(cached_children).clone()
167 };
168 }
169 *borrow_mut!(old_active_branch) = Some(active_branch);
170
171 let children = borrow!(cloned_self.base().get_instance_children());
172 let effective_branch_ranges = if branch_child_ranges.is_empty() {
173 vec![0..children.len()]
174 } else {
175 branch_child_ranges.clone()
176 };
177 let selected_range = active_branch
178 .and_then(|index| effective_branch_ranges.get(index).cloned())
179 .unwrap_or(0..0);
180 let start = selected_range.start.min(children.len());
181 let end = selected_range.end.min(children.len());
182 let env = Rc::clone(&cloned_expanded_node.stack);
183 let mut selected_children = Vec::new();
184 for template in children[start..end].iter() {
185 let rescued = (is_mount && cloned_expanded_node.attached.get() > 0)
186 .then(|| {
187 cloned_expanded_node.rescue_exiting_child_matching(|child| {
188 let child_template = borrow!(child.instance_node).clone();
189 Rc::ptr_eq(&child_template, template)
190 })
191 })
192 .flatten();
193 let child = rescued.unwrap_or_else(|| {
194 cloned_expanded_node
195 .create_children_detached(
196 iter::once((Rc::clone(template), Rc::clone(&env))),
197 &cloned_context,
198 &Rc::downgrade(&cloned_expanded_node),
199 )
200 .pop()
201 .expect("conditional child creation returned no child")
202 });
203 if !is_mount && child.attached.get() == 0 {
204 child.recurse_control_flow_expansion(&cloned_context);
205 }
206 selected_children.push(child);
207 }
208 let ret = if is_mount {
209 cloned_expanded_node.attach_children(
210 selected_children,
211 &cloned_context,
212 &cloned_expanded_node.parent_frame,
213 )
214 } else {
215 selected_children
216 };
217 *borrow_mut!(cached_children) = ret.clone();
218 ret
219 },
220 &deps,
221 &format!("conditional_children (node id: {})", expanded_node.id.0),
222 ));
223 }
224}
225
226#[cfg(test)]
227mod tests {
228 use super::*;
229 use crate::api::math::Transform2;
230 use crate::api::CommonProperties;
231 use crate::{
232 BaseInstance, ComponentInstance, Globals, InstanceFlags, RouteLocation,
233 RuntimePropertiesStackFrame, TransformAndBounds,
234 };
235 use pax_manifest::cartridge_generation::{
236 ComponentTransitionConfig, TRANSITION_PHASE_ENTER, TRANSITION_PHASE_EXIT,
237 };
238 use pax_runtime_api::pax_value::{PaxAny, ToFromPaxAny};
239 use pax_runtime_api::{LocalProperty, Platform, TargetInfo, OS};
240 use std::cell::RefCell;
241
242 fn test_globals() -> Globals {
243 Globals {
244 elapsed_frames: LocalProperty::new(0),
245 elapsed_millis: LocalProperty::new(0),
246 viewport: LocalProperty::new(TransformAndBounds {
247 transform: Transform2::identity(),
248 bounds: (100.0, 100.0),
249 }),
250 gyro: LocalProperty::new(Default::default()),
251 accel: LocalProperty::new(Default::default()),
252 route_location: LocalProperty::new(RouteLocation::root()),
253 browser_allows_scroller_vector_layers: LocalProperty::new(true),
254 browser_allows_nested_scroller_vector_layers: LocalProperty::new(true),
255 platform: Platform::Unknown,
256 os: OS::Unknown,
257 target: TargetInfo::new(Platform::Unknown, OS::Unknown),
258 #[cfg(feature = "designtime")]
259 designtime: crate::test_support::designtime(),
260 get_elapsed_millis: Rc::new(|| 0),
261 }
262 }
263
264 fn default_properties_factory() -> Box<
265 dyn Fn(
266 Rc<RuntimePropertiesStackFrame>,
267 Option<Rc<ExpandedNode>>,
268 ) -> Option<Rc<RefCell<PaxAny>>>,
269 > {
270 Box::new(|_, expanded_node| {
271 expanded_node
272 .is_none()
273 .then(|| Rc::new(RefCell::new(PaxAny::Builtin(Default::default()))))
274 })
275 }
276
277 fn default_common_properties_factory() -> Box<
278 dyn Fn(
279 Rc<RuntimePropertiesStackFrame>,
280 Option<Rc<ExpandedNode>>,
281 ) -> Option<Rc<RefCell<CommonProperties>>>,
282 > {
283 Box::new(|_, expanded_node| {
284 expanded_node
285 .is_none()
286 .then(|| Rc::new(RefCell::new(CommonProperties::default())))
287 })
288 }
289
290 fn component_args(template: Option<Vec<Rc<dyn InstanceNode>>>) -> InstantiationArgs {
291 InstantiationArgs {
292 prototypical_common_properties: crate::CommonPropertiesInit::Factory(
293 default_common_properties_factory(),
294 ),
295 prototypical_properties: crate::PropertiesInit::Factory(default_properties_factory()),
296 handler_registry: None,
297 children: None,
298 component_template: template.map(RefCell::new),
299 component_settings: None,
300 template_node_identifier: None,
301 template_node_type_id: None,
302 template_node_selector_info: None,
303 transition_config: Default::default(),
304 properties_scope: crate::PropertiesScopeInit::None,
305 }
306 }
307
308 fn transition_leaf() -> Rc<dyn InstanceNode> {
309 let mut args = component_args(Some(Vec::new()));
310 args.transition_config = ComponentTransitionConfig {
311 has_enter: true,
312 enter_frame_count: 10,
313 has_exit: true,
314 exit_frame_count: 10,
315 timeout_ms: 5_000,
316 ..Default::default()
317 };
318 ComponentInstance::instantiate(args)
319 }
320
321 fn mounted_conditional(
322 condition: LocalProperty<bool>,
323 ) -> (Rc<ExpandedNode>, Rc<ExpandedNode>, Rc<RuntimeContext>) {
324 let conditional: Rc<dyn InstanceNode> =
325 ConditionalInstance::instantiate(conditional_args(condition, vec![transition_leaf()]));
326 let root_component =
327 ComponentInstance::instantiate(component_args(Some(vec![conditional])));
328 let context = Rc::new(crate::test_support::runtime_context(test_globals()));
329 let root = ExpandedNode::initialize_root(root_component, &context);
330 root.recurse_update(&context);
331 let conditional_node = root.children.get().remove(0);
332 (root, conditional_node, context)
333 }
334
335 fn conditional_args(
336 condition: LocalProperty<bool>,
337 children: Vec<Rc<dyn InstanceNode>>,
338 ) -> InstantiationArgs {
339 InstantiationArgs {
340 prototypical_common_properties: crate::CommonPropertiesInit::Factory(
341 default_common_properties_factory(),
342 ),
343 prototypical_properties: crate::PropertiesInit::Factory(Box::new(
344 move |_, expanded_node| {
345 expanded_node.is_none().then(|| {
346 Rc::new(RefCell::new(
347 ConditionalProperties {
348 boolean_expression: condition.clone(),
349 conditional_branches: Vec::new(),
350 }
351 .to_pax_any(),
352 ))
353 })
354 },
355 )),
356 handler_registry: None,
357 children: Some(RefCell::new(children)),
358 component_template: None,
359 component_settings: None,
360 template_node_identifier: None,
361 template_node_type_id: None,
362 template_node_selector_info: None,
363 transition_config: Default::default(),
364 properties_scope: crate::PropertiesScopeInit::None,
365 }
366 }
367
368 struct TestLeaf {
369 base: BaseInstance,
370 }
371
372 impl InstanceNode for TestLeaf {
373 fn instantiate(args: InstantiationArgs) -> Rc<Self>
374 where
375 Self: Sized,
376 {
377 Rc::new(Self {
378 base: BaseInstance::new(
379 args,
380 InstanceFlags {
381 invisible_to_slot: false,
382 invisible_to_raycasting: true,
383 layer: Layer::DontCare,
384 is_component: false,
385 is_slot: false,
386 },
387 ),
388 })
389 }
390
391 fn resolve_debug(
392 &self,
393 f: &mut std::fmt::Formatter,
394 _expanded_node: Option<&ExpandedNode>,
395 ) -> std::fmt::Result {
396 f.debug_struct("TestLeaf").finish()
397 }
398
399 fn base(&self) -> &BaseInstance {
400 &self.base
401 }
402 }
403
404 #[test]
405 fn detached_conditional_keeps_cached_children_on_same_branch_recompute() {
406 let condition = LocalProperty::new(true);
407 let leaf: Rc<dyn InstanceNode> = TestLeaf::instantiate(component_args(Some(Vec::new())));
408 let conditional =
409 ConditionalInstance::instantiate(conditional_args(condition.clone(), vec![leaf]));
410 let root_component = ComponentInstance::instantiate(component_args(None));
411 let context = Rc::new(crate::test_support::runtime_context(test_globals()));
412 let root = ExpandedNode::initialize_root(root_component, &context);
413
414 let detached = root
415 .create_children_detached(
416 vec![(
417 conditional.clone() as Rc<dyn InstanceNode>,
418 Rc::clone(&root.stack),
419 )],
420 &context,
421 &Rc::downgrade(&root),
422 )
423 .remove(0);
424
425 conditional.clone().handle_setup(&detached, &context, false);
426
427 let initial = detached.children.get();
428 assert_eq!(initial.len(), 1);
429 let initial_id = initial[0].id;
430
431 condition.set(true);
432 let recomputed = detached.children.get();
433 assert_eq!(recomputed.len(), 1);
434 assert_eq!(recomputed[0].id, initial_id);
435 }
436
437 #[test]
438 fn conditional_prunes_completed_exit_and_can_reenter_during_exit() {
439 let condition = LocalProperty::new(true);
440 let (root, conditional_node, context) = mounted_conditional(condition.clone());
441 let initial_id = borrow!(conditional_node.active_children)[0].id;
442
443 condition.set(false);
444 root.recurse_update(&context);
445 assert!(borrow!(conditional_node.active_children).is_empty());
446 assert_eq!(borrow!(conditional_node.exiting_children).len(), 1);
447 assert_eq!(
448 borrow!(conditional_node.exiting_children)[0]
449 .transition_phase
450 .get(),
451 TRANSITION_PHASE_EXIT
452 );
453
454 context.globals().elapsed_frames.set(5);
455 condition.set(true);
456 root.recurse_update(&context);
457
458 let active = borrow!(conditional_node.active_children).clone();
459 assert_eq!(active.len(), 1);
460 assert_eq!(active[0].id, initial_id);
461 assert_eq!(active[0].transition_phase.get(), TRANSITION_PHASE_ENTER);
462 assert!(borrow!(conditional_node.exiting_children).is_empty());
463
464 context.globals().elapsed_frames.set(10);
465 context.drain_node_effects();
466 assert!(borrow!(conditional_node.exiting_children).is_empty());
467 assert_eq!(conditional_node.children.get().len(), 1);
468 }
469
470 #[test]
471 fn conditional_survives_repeated_rapid_toggles_during_transitions() {
472 let condition = LocalProperty::new(true);
473 let (root, conditional_node, context) = mounted_conditional(condition.clone());
474 let stable_id = borrow!(conditional_node.active_children)[0].id;
475
476 for frame in 1..=6 {
477 context.globals().elapsed_frames.set(frame);
478 condition.set(frame % 2 == 0);
479 root.recurse_update(&context);
480
481 let expected_active = usize::from(frame % 2 == 0);
482 assert_eq!(
483 borrow!(conditional_node.active_children).len(),
484 expected_active
485 );
486 assert_eq!(borrow!(conditional_node.mounted_children).len(), 1);
487 assert_eq!(borrow!(conditional_node.mounted_children)[0].id, stable_id);
488 }
489
490 context.globals().elapsed_frames.set(20);
491 context.drain_node_effects();
492 assert_eq!(borrow!(conditional_node.active_children).len(), 1);
493 assert!(borrow!(conditional_node.exiting_children).is_empty());
494 }
495
496 #[test]
497 fn conditional_refreshes_computed_children_after_exit_cleanup() {
498 let condition = LocalProperty::new(true);
499 let (_root, conditional_node, context) = mounted_conditional(condition.clone());
500
501 condition.set(false);
502 let _ = conditional_node.children.get();
503 context.globals().elapsed_frames.set(10);
504 context.drain_node_effects();
505
506 assert!(borrow!(conditional_node.active_children).is_empty());
507 assert!(borrow!(conditional_node.exiting_children).is_empty());
508 assert!(conditional_node.children.get().is_empty());
509 }
510}