1use crate::constants::{PRE_RENDER_HANDLERS, TICK_HANDLERS};
2use crate::{
3 api::Property, ExpandedNodeIdentifier, RouteLocation, RuntimePropertiesStackFrame,
4 TransformAndBounds, INTERNAL_ROUTE_LOCATION_SYMBOL,
5};
6use_RefCell!();
7use std::collections::{HashMap, HashSet};
8use std::rc::Rc;
9use std::sync::atomic::{AtomicUsize, Ordering};
10
11use pax_message::NativeMessage;
12use pax_runtime_api::{
13 borrow, pax_value::PaxAny, use_RefCell, Accel, Event, Focus, Gyro, SelectStart, TargetInfo,
14 Variable, Viewport, Window, OS,
15};
16
17use crate::api::{KeyDown, KeyPress, KeyUp, NodeContext, RenderContext};
18
19use crate::{ComponentInstance, RuntimeContext};
20use pax_runtime_api::Platform;
21
22pub mod layer_surface;
23pub mod layer_tiling;
24pub mod node_interface;
25pub mod occlusion;
26pub mod pax_gpu_render_context;
28pub mod piet_render_context;
29
30mod expanded_node;
37pub use expanded_node::{
38 ExpandedNode, RuntimeResolvedPropertyColumns, RuntimeResolvedPropertyEntry,
39 RuntimeSettingsCondition, RuntimeSettingsLayer, RuntimeSettingsSignatureEntry,
40 RuntimeSettingsSource,
41};
42
43use self::node_interface::NodeLocal;
44
45fn saturating_u128_to_u64(value: u128) -> u64 {
46 value.min(u64::MAX as u128) as u64
47}
48
49struct FilteredRenderPlan {
50 dirty_nodes: HashSet<ExpandedNodeIdentifier>,
51 render_path_nodes: HashSet<ExpandedNodeIdentifier>,
52}
53
54fn viewport_info_property(
55 viewport_bounds: &Property<TransformAndBounds<NodeLocal, Window>>,
56) -> Property<Viewport> {
57 let cloned_viewport = viewport_bounds.clone();
58 let deps = [cloned_viewport.untyped()];
59 Property::computed(
60 move || {
61 let viewport = cloned_viewport.get();
62 Viewport::new(viewport.bounds.0, viewport.bounds.1)
63 },
64 &deps,
65 )
66}
67
68fn viewport_number_property(
69 viewport: &Property<Viewport>,
70 name: &str,
71 accessor: fn(Viewport) -> f64,
72) -> Property<f64> {
73 let cloned_viewport = viewport.clone();
74 let deps = [cloned_viewport.untyped()];
75 Property::computed_with_name(move || accessor(cloned_viewport.get()), &deps, name)
76}
77
78fn viewport_bool_property(
79 viewport: &Property<Viewport>,
80 name: &str,
81 accessor: fn(Viewport) -> bool,
82) -> Property<bool> {
83 let cloned_viewport = viewport.clone();
84 let deps = [cloned_viewport.untyped()];
85 Property::computed_with_name(move || accessor(cloned_viewport.get()), &deps, name)
86}
87
88#[cfg(feature = "designtime")]
89use {crate::InstanceNode, pax_designtime::DesigntimeManager, pax_runtime_api::borrow_mut};
90
91#[derive(Clone)]
92pub struct Globals {
94 pub elapsed_frames: Property<u64>,
95 pub elapsed_millis: Property<u64>,
96 pub viewport: Property<TransformAndBounds<NodeLocal, Window>>,
97 pub gyro: Property<Gyro>,
98 pub accel: Property<Accel>,
99 pub route_location: Property<RouteLocation>,
100 pub browser_allows_scroller_vector_layers: Property<bool>,
101 pub browser_allows_nested_scroller_vector_layers: Property<bool>,
102 pub platform: Platform,
103 pub os: OS,
104 pub target: TargetInfo,
105 #[cfg(feature = "designtime")]
106 pub designtime: Rc<RefCell<DesigntimeManager>>,
107 pub get_elapsed_millis: Rc<dyn Fn() -> u128>,
108}
109
110impl Globals {
111 pub fn stack_frame(&self) -> Rc<RuntimePropertiesStackFrame> {
113 let target = self.target;
114 let viewport = viewport_info_property(&self.viewport);
115
116 let target_var = Variable::new_from_typed_property(Property::new(target));
117 let viewport_var = Variable::new_from_typed_property(viewport.clone());
118 let gyro_var = Variable::new_from_typed_property(self.gyro.clone());
119 let accel_var = Variable::new_from_typed_property(self.accel.clone());
120 let elapsed_frames_var = Variable::new_from_typed_property(self.elapsed_frames.clone());
121 let elapsed_millis_var = Variable::new_from_typed_property(self.elapsed_millis.clone());
122 let route_location_var = Variable::new_from_typed_property(self.route_location.clone());
123
124 let global_scope = vec![
125 ("$target".to_string(), target_var),
126 ("$viewport".to_string(), viewport_var),
127 (
128 "$web".to_string(),
129 Variable::new_from_typed_property(Property::new(target.web)),
130 ),
131 (
132 "$native".to_string(),
133 Variable::new_from_typed_property(Property::new(target.native)),
134 ),
135 (
136 "$ios".to_string(),
137 Variable::new_from_typed_property(Property::new(target.ios)),
138 ),
139 (
140 "$iphone".to_string(),
141 Variable::new_from_typed_property(Property::new(target.iphone)),
142 ),
143 (
144 "$ipad".to_string(),
145 Variable::new_from_typed_property(Property::new(target.ipad)),
146 ),
147 (
148 "$macos".to_string(),
149 Variable::new_from_typed_property(Property::new(target.macos)),
150 ),
151 (
152 "$android".to_string(),
153 Variable::new_from_typed_property(Property::new(target.android)),
154 ),
155 (
156 "$windows".to_string(),
157 Variable::new_from_typed_property(Property::new(target.windows)),
158 ),
159 (
160 "$linux".to_string(),
161 Variable::new_from_typed_property(Property::new(target.linux)),
162 ),
163 (
164 "$mobile".to_string(),
165 Variable::new_from_typed_property(Property::new(target.mobile)),
166 ),
167 (
168 "$desktop".to_string(),
169 Variable::new_from_typed_property(Property::new(target.desktop)),
170 ),
171 (
172 "$major".to_string(),
173 Variable::new_from_typed_property(viewport_number_property(
174 &viewport,
175 "$major",
176 |viewport| viewport.major,
177 )),
178 ),
179 (
180 "$minor".to_string(),
181 Variable::new_from_typed_property(viewport_number_property(
182 &viewport,
183 "$minor",
184 |viewport| viewport.minor,
185 )),
186 ),
187 (
188 "$aspect".to_string(),
189 Variable::new_from_typed_property(viewport_number_property(
190 &viewport,
191 "$aspect",
192 |viewport| viewport.aspect,
193 )),
194 ),
195 (
196 "$landscape".to_string(),
197 Variable::new_from_typed_property(viewport_bool_property(
198 &viewport,
199 "$landscape",
200 |viewport| viewport.landscape,
201 )),
202 ),
203 (
204 "$portrait".to_string(),
205 Variable::new_from_typed_property(viewport_bool_property(
206 &viewport,
207 "$portrait",
208 |viewport| viewport.portrait,
209 )),
210 ),
211 (
212 "$square".to_string(),
213 Variable::new_from_typed_property(viewport_bool_property(
214 &viewport,
215 "$square",
216 |viewport| viewport.square,
217 )),
218 ),
219 ("$gyro".to_string(), gyro_var),
220 ("$accel".to_string(), accel_var),
221 ("$frames".to_string(), elapsed_frames_var),
222 ("$millis".to_string(), elapsed_millis_var),
223 (
224 INTERNAL_ROUTE_LOCATION_SYMBOL.to_string(),
225 route_location_var,
226 ),
227 ]
228 .into_iter()
229 .collect();
230
231 let root_env = RuntimePropertiesStackFrame::new(global_scope);
232 root_env
233 }
234}
235
236impl std::fmt::Debug for Globals {
237 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
238 f.debug_struct("Globals")
239 .field("elapsed_frames", &self.elapsed_frames)
240 .field("elapsed_millis", &self.elapsed_millis)
241 .field("viewport", &self.viewport)
242 .field("gyro", &self.gyro)
243 .field("accel", &self.accel)
244 .field("route_location", &self.route_location)
245 .field("target", &self.target)
246 .finish_non_exhaustive()
247 }
248}
249
250pub struct PaxEngine {
252 pub runtime_context: Rc<RuntimeContext>,
253 pub root_expanded_node: Option<Rc<ExpandedNode>>,
254 pub scroller_tiling_policy: layer_tiling::ScrollerTilingPolicy,
255}
256
257pub enum HandlerLocation {
259 Inline,
260 Component,
261}
262
263pub struct Handler {
265 pub function: fn(Rc<RefCell<PaxAny>>, &NodeContext, Option<PaxAny>),
266 pub location: HandlerLocation,
267}
268
269impl Handler {
270 pub fn new_inline_handler(
272 function: fn(Rc<RefCell<PaxAny>>, &NodeContext, Option<PaxAny>),
273 ) -> Self {
274 Handler {
275 function,
276 location: HandlerLocation::Inline,
277 }
278 }
279
280 pub fn new_component_handler(
282 function: fn(Rc<RefCell<PaxAny>>, &NodeContext, Option<PaxAny>),
283 ) -> Self {
284 Handler {
285 function,
286 location: HandlerLocation::Component,
287 }
288 }
289}
290
291pub struct HandlerRegistry {
293 pub handlers: HashMap<String, Vec<Handler>>,
294}
295
296impl Default for HandlerRegistry {
297 fn default() -> Self {
298 HandlerRegistry {
299 handlers: HashMap::new(),
300 }
301 }
302}
303
304impl PaxEngine {
308 #[cfg(not(feature = "designtime"))]
309 fn build_globals(
310 viewport_size: (f64, f64),
311 platform: Platform,
312 os: OS,
313 get_elapsed_millis: Box<dyn Fn() -> u128>,
314 ) -> Globals {
315 use crate::api::math::Transform2;
316 use pax_runtime_api::{properties, Functions};
317 Functions::register_all_functions();
318
319 let elapsed_frames = Property::new(0);
320 let elapsed_millis = Property::new(saturating_u128_to_u64(get_elapsed_millis()));
321 properties::register_time(&elapsed_frames);
322 properties::register_millis(&elapsed_millis);
323 Globals {
324 elapsed_frames,
325 elapsed_millis,
326 viewport: Property::new(TransformAndBounds {
327 transform: Transform2::identity(),
328 bounds: viewport_size,
329 }),
330 gyro: Property::new(Gyro::default()),
331 accel: Property::new(Accel::default()),
332 route_location: Property::new(RouteLocation::root()),
333 browser_allows_scroller_vector_layers: Property::new(true),
334 browser_allows_nested_scroller_vector_layers: Property::new(true),
335 platform,
336 os,
337 target: TargetInfo::new(platform, os),
338 get_elapsed_millis: Rc::from(get_elapsed_millis),
339 }
340 }
341
342 #[cfg(feature = "designtime")]
343 fn build_globals(
344 viewport_size: (f64, f64),
345 designtime: Rc<RefCell<DesigntimeManager>>,
346 platform: Platform,
347 os: OS,
348 get_elapsed_millis: Box<dyn Fn() -> u128>,
349 ) -> Globals {
350 use pax_runtime_api::{math::Transform2, properties, Functions};
351 Functions::register_all_functions();
352
353 let elapsed_frames = Property::new(0);
354 let elapsed_millis = Property::new(saturating_u128_to_u64(get_elapsed_millis()));
355 properties::register_time(&elapsed_frames);
356 properties::register_millis(&elapsed_millis);
357 Globals {
358 elapsed_frames,
359 elapsed_millis,
360 viewport: Property::new(TransformAndBounds {
361 transform: Transform2::identity(),
362 bounds: viewport_size,
363 }),
364 gyro: Property::new(Gyro::default()),
365 accel: Property::new(Accel::default()),
366 route_location: Property::new(RouteLocation::root()),
367 browser_allows_scroller_vector_layers: Property::new(true),
368 browser_allows_nested_scroller_vector_layers: Property::new(true),
369 platform,
370 os,
371 target: TargetInfo::new(platform, os),
372 designtime: designtime.clone(),
373 get_elapsed_millis: Rc::from(get_elapsed_millis),
374 }
375 }
376
377 #[cfg(feature = "designtime")]
378 pub fn new_empty_with_designtime(
379 viewport_size: (f64, f64),
380 designtime: Rc<RefCell<DesigntimeManager>>,
381 platform: Platform,
382 os: OS,
383 get_elapsed_millis: Box<dyn Fn() -> u128>,
384 scroller_tiling_policy: layer_tiling::ScrollerTilingPolicy,
385 ) -> Self {
386 let globals =
387 Self::build_globals(viewport_size, designtime, platform, os, get_elapsed_millis);
388 let runtime_context = Rc::new(RuntimeContext::new_empty(globals));
389 PaxEngine {
390 runtime_context,
391 root_expanded_node: None,
392 scroller_tiling_policy,
393 }
394 }
395
396 #[cfg(not(feature = "designtime"))]
397 pub fn new_empty(
398 viewport_size: (f64, f64),
399 platform: Platform,
400 os: OS,
401 get_elapsed_millis: Box<dyn Fn() -> u128>,
402 scroller_tiling_policy: layer_tiling::ScrollerTilingPolicy,
403 ) -> Self {
404 let globals = Self::build_globals(viewport_size, platform, os, get_elapsed_millis);
405 let runtime_context = Rc::new(RuntimeContext::new(globals));
406 PaxEngine {
407 runtime_context,
408 root_expanded_node: None,
409 scroller_tiling_policy,
410 }
411 }
412
413 pub fn mount_root_component(
415 &mut self,
416 main_component_instance: Rc<ComponentInstance>,
417 ) -> Rc<ExpandedNode> {
418 self.unmount();
419 let root_node = ExpandedNode::initialize_root(
420 Rc::clone(&main_component_instance),
421 &self.runtime_context,
422 );
423 self.runtime_context.register_root_expanded_node(&root_node);
424 self.root_expanded_node = Some(Rc::clone(&root_node));
425 root_node
426 }
427
428 pub fn unmount(&mut self) {
430 let Some(root_expanded_node) = self.root_expanded_node.take() else {
431 return;
432 };
433
434 #[cfg(feature = "designtime")]
435 let root_id = root_expanded_node.id;
436 root_expanded_node.recurse_unmount(&self.runtime_context);
437 self.runtime_context.clear_root_expanded_node();
438 self.runtime_context.layer_count.set(0);
439 self.runtime_context.clear_layer_scroller_owners();
440 self.runtime_context.set_root_scroller_id(None);
441 self.runtime_context.clear_visual_viewport_state();
442
443 #[cfg(feature = "designtime")]
444 if self
445 .runtime_context
446 .get_userland_root_expanded_node()
447 .as_ref()
448 .is_some_and(|node| node.id == root_id)
449 {
450 self.runtime_context.set_userland_root_expanded_node(None);
451 }
452 }
453
454 #[cfg(not(feature = "designtime"))]
455 pub fn new(
456 main_component_instance: Rc<ComponentInstance>,
457 viewport_size: (f64, f64),
458 platform: Platform,
459 os: OS,
460 get_elapsed_millis: Box<dyn Fn() -> u128>,
461 scroller_tiling_policy: layer_tiling::ScrollerTilingPolicy,
462 ) -> Self {
463 let mut engine = Self::new_empty(
464 viewport_size,
465 platform,
466 os,
467 get_elapsed_millis,
468 scroller_tiling_policy,
469 );
470 engine.mount_root_component(main_component_instance);
471 engine
472 }
473
474 #[cfg(feature = "designtime")]
475 pub fn new_with_designtime(
476 userland_main_component_instance: Rc<ComponentInstance>,
477 viewport_size: (f64, f64),
478 designtime: Rc<RefCell<DesigntimeManager>>,
479 platform: Platform,
480 os: OS,
481 get_elapsed_millis: Box<dyn Fn() -> u128>,
482 scroller_tiling_policy: layer_tiling::ScrollerTilingPolicy,
483 ) -> Self {
484 let mut engine = Self::new_empty_with_designtime(
485 viewport_size,
486 designtime,
487 platform,
488 os,
489 get_elapsed_millis,
490 scroller_tiling_policy,
491 );
492 engine
493 .runtime_context
494 .set_userland_root_instance_node(Some(userland_main_component_instance.clone()));
495 let root_expanded_node = engine.mount_root_component(userland_main_component_instance);
496 engine
497 .runtime_context
498 .set_userland_root_expanded_node(Some(root_expanded_node));
499 engine
500 }
501
502 #[cfg(feature = "designtime")]
503 pub fn partial_update_expanded_node(&mut self, new_instance: Rc<dyn InstanceNode>) {
504 let unique_id = new_instance
506 .base()
507 .template_node_identifier
508 .clone()
509 .expect("new instance node has unique identifier");
510
511 let nodes = self
512 .runtime_context
513 .get_expanded_nodes_by_global_ids(&unique_id);
514 for node in nodes {
515 node.recreate_with_new_data(new_instance.clone(), &self.runtime_context);
516 }
517 }
518
519 #[cfg(feature = "designtime")]
520 pub fn full_reload_userland(&mut self, new_userland_instance: Rc<dyn InstanceNode>) {
521 let node = borrow!(self.runtime_context.userland_root_expanded_node)
522 .as_ref()
523 .map(Rc::clone)
524 .unwrap();
525 *borrow_mut!(self.runtime_context.userland_frame_instance_node) =
526 Some(Rc::clone(&new_userland_instance));
527 node.fully_recreate_with_new_data(new_userland_instance.clone(), &self.runtime_context);
528 }
529
530 pub fn tick(&mut self) -> Vec<NativeMessage> {
543 if let Some(root_expanded_node) = &self.root_expanded_node {
548 root_expanded_node.recurse_update(&self.runtime_context);
549 if self.runtime_context.has_import_settings_nodes() {
550 root_expanded_node.recurse_sync_import_settings(&self.runtime_context);
551 }
552 }
553
554 let ctx = &self.runtime_context;
555 ctx.drain_node_effects();
556 self.run_lifecycle_handlers(TICK_HANDLERS, ctx.tick_handler_nodes());
557 ctx.drain_node_effects();
558 self.run_lifecycle_handlers(PRE_RENDER_HANDLERS, ctx.pre_render_handler_nodes());
559 ctx.drain_node_effects();
560
561 let globals = ctx.globals();
562 let time = &globals.elapsed_frames;
563 time.set(time.get() + 1);
564 globals
565 .elapsed_millis
566 .set(saturating_u128_to_u64((globals.get_elapsed_millis)()));
567
568 ctx.flush_custom_events().unwrap();
569
570 ctx.drain_node_effects();
574 if ctx.take_occlusion_dirty() {
575 if let Some(root_expanded_node) = &self.root_expanded_node {
576 occlusion::update_node_occlusion(root_expanded_node, ctx);
577 }
578 ctx.drain_node_effects();
579 }
580 let native_messages = ctx.take_native_messages();
581 native_messages
582 }
583
584 fn run_lifecycle_handlers(&self, handler_key: &str, nodes: Vec<ExpandedNodeIdentifier>) {
585 for node_id in nodes {
586 if let Some(node) = self.runtime_context.get_expanded_node_by_eid(node_id) {
587 node.run_lifecycle_handlers(handler_key, &self.runtime_context);
588 }
589 }
590 }
591
592 fn build_filtered_render_plan(
593 &self,
594 root: &Rc<ExpandedNode>,
595 dirty_node_ids: &[ExpandedNodeIdentifier],
596 ) -> Result<FilteredRenderPlan, &'static str> {
597 let dirty_nodes: HashSet<_> = dirty_node_ids.iter().copied().collect();
598 let mut render_path_nodes = HashSet::new();
599
600 for dirty_node_id in &dirty_nodes {
601 let mut node = self
602 .runtime_context
603 .get_expanded_node_by_eid(*dirty_node_id)
604 .ok_or("missing_dirty_node")?;
605 let mut local_path = HashSet::new();
606 loop {
607 if !local_path.insert(node.id) {
608 return Err("render_parent_cycle");
609 }
610 if node.is_unclippable() {
611 return Err("unclippable_path");
612 }
613 render_path_nodes.insert(node.id);
614 if node.id == root.id {
615 break;
616 }
617 node = node.render_parent_node().ok_or("missing_render_parent")?;
618 }
619 }
620
621 if !render_path_nodes.contains(&root.id) {
622 return Err("missing_root_path");
623 }
624
625 Ok(FilteredRenderPlan {
626 dirty_nodes,
627 render_path_nodes,
628 })
629 }
630
631 fn dirty_layer_can_use_targeted_replay_nodes(
632 &self,
633 layer: usize,
634 dirty_node_ids: &[ExpandedNodeIdentifier],
635 targeted_node_ids: &HashSet<u32>,
636 ) -> bool {
637 for dirty_node_id in dirty_node_ids {
638 let Some(node) = self
639 .runtime_context
640 .get_expanded_node_by_eid(*dirty_node_id)
641 else {
642 return false;
643 };
644 if node.occlusion.get().render_layer_id != layer
645 || borrow!(node.instance_node).base().flags().layer != crate::api::Layer::Canvas
646 {
647 continue;
648 }
649 if !targeted_node_ids.contains(&dirty_node_id.to_u32()) {
650 return false;
651 }
652 }
653 true
654 }
655
656 pub fn render(&mut self, rcs: &mut dyn RenderContext) {
657 self.update_layer_count(rcs);
658
659 if !self.runtime_context.has_canvas_render_work() {
660 return;
661 }
662
663 let removals = self.runtime_context.take_canvas_node_removals();
664 let mut dirty_layers = self.runtime_context.dirty_canvas_layers();
665 dirty_layers.extend(removals.iter().map(|(layer, _)| *layer));
666 dirty_layers.sort_unstable();
667 dirty_layers.dedup();
668 for layer in &dirty_layers {
669 let lighting = self
670 .runtime_context
671 .collect_scene_lighting_for_layer(*layer);
672 rcs.set_scene_lighting(*layer, &lighting);
673 }
674 let removal_layers: HashSet<_> = removals.iter().map(|(layer, _)| *layer).collect();
675 let targeted_replay_node_ids = self.runtime_context.take_targeted_canvas_replay_node_ids();
676 let dirty_node_ids_before_expansion = self.runtime_context.dirty_canvas_node_ids();
677 let retains_canvas_nodes = rcs.retains_canvas_nodes();
678 let mut retained_full_replay_layers = Vec::new();
679 if retains_canvas_nodes {
680 for layer in &dirty_layers {
681 let has_retained_removal = removal_layers.contains(layer);
682 let has_targeted_conflict =
683 targeted_replay_node_ids
684 .get(layer)
685 .is_some_and(|targeted_node_ids| {
686 !self.dirty_layer_can_use_targeted_replay_nodes(
687 *layer,
688 &dirty_node_ids_before_expansion,
689 targeted_node_ids,
690 )
691 });
692 if has_retained_removal || has_targeted_conflict {
693 rcs.clear_targeted_replay(*layer);
694 self.runtime_context
695 .mark_canvas_nodes_on_layer_dirty(*layer);
696 retained_full_replay_layers.push(*layer);
697 }
698 }
699 }
700 if !retains_canvas_nodes && (!dirty_layers.is_empty() || !removals.is_empty()) {
701 for layer in &dirty_layers {
702 let can_use_targeted_replay_nodes = targeted_replay_node_ids
703 .get(layer)
704 .is_some_and(|targeted_node_ids| {
705 !removal_layers.contains(layer)
706 && self.dirty_layer_can_use_targeted_replay_nodes(
707 *layer,
708 &dirty_node_ids_before_expansion,
709 targeted_node_ids,
710 )
711 });
712 if !can_use_targeted_replay_nodes {
713 self.runtime_context
714 .mark_canvas_nodes_on_layer_dirty(*layer);
715 }
716 }
717 }
718 let dirty_node_ids = self.runtime_context.dirty_canvas_node_ids();
719 let has_dirty_nodes = !dirty_node_ids.is_empty();
720 let has_node_removals = !removals.is_empty();
721 if !has_dirty_nodes && !has_node_removals {
722 for layer in &dirty_layers {
723 rcs.clear(*layer);
724 }
725 } else if !retains_canvas_nodes {
726 for layer in &dirty_layers {
727 rcs.clear(*layer);
728 }
729 } else {
730 for layer in retained_full_replay_layers {
731 rcs.clear(layer);
732 }
733 }
734
735 let mut failed_removal_layers = Vec::new();
736 for (layer, node_id) in removals {
737 if !rcs.remove_node(layer, node_id) {
738 self.runtime_context
739 .enqueue_canvas_node_removal(layer, node_id);
740 failed_removal_layers.push(layer);
741 }
742 }
743
744 if has_dirty_nodes {
747 if let Some(root_expanded_node) = &self.root_expanded_node {
748 match self.build_filtered_render_plan(root_expanded_node, &dirty_node_ids) {
749 Ok(plan) => {
750 let mut stats = expanded_node::FilteredRenderStats::default();
751 root_expanded_node.recurse_render_filtered(
752 &self.runtime_context,
753 rcs,
754 &plan.render_path_nodes,
755 &plan.dirty_nodes,
756 &mut stats,
757 );
758 #[cfg(debug_assertions)]
759 log::trace!(
760 "[pax-render-filter] dirty_nodes={} path_nodes={} visited_path_nodes={} dirty_nodes_rendered={} skipped_subtrees={}",
761 plan.dirty_nodes.len(),
762 plan.render_path_nodes.len(),
763 stats.path_nodes_visited,
764 stats.dirty_nodes_rendered,
765 stats.skipped_subtrees,
766 );
767 }
768 Err(_reason) => {
769 #[cfg(debug_assertions)]
770 log::trace!(
771 "[pax-render-filter] fallback={} dirty_nodes={}",
772 _reason,
773 dirty_node_ids.len(),
774 );
775 root_expanded_node.recurse_render_queue(&self.runtime_context, rcs);
776 }
777 }
778 }
779 }
780 self.runtime_context.recurse_flush_queued_renders(rcs);
781
782 self.runtime_context.clear_all_dirty_canvases();
783 for layer in failed_removal_layers {
784 self.runtime_context.set_canvas_dirty(layer);
785 }
786
787 for layer in dirty_layers {
788 rcs.flush(layer, Rc::clone(&self.runtime_context.dirty_canvases));
789 }
790 }
791
792 pub fn get_expanded_node(&self, id: ExpandedNodeIdentifier) -> Option<Rc<ExpandedNode>> {
793 let val = self.runtime_context.get_expanded_node_by_eid(id).clone();
794 val.map(|v| v.clone())
795 }
796
797 pub fn set_viewport_size(&mut self, new_viewport_size: (f64, f64)) {
799 const VIEWPORT_SIZE_EPSILON: f64 = 0.001;
800 let current_viewport_size = self.runtime_context.globals().viewport.get().bounds;
801 if (current_viewport_size.0 - new_viewport_size.0).abs() <= VIEWPORT_SIZE_EPSILON
802 && (current_viewport_size.1 - new_viewport_size.1).abs() <= VIEWPORT_SIZE_EPSILON
803 {
804 return;
805 }
806
807 self.runtime_context.edit_globals(|globals| {
808 globals
809 .viewport
810 .update(|t_and_b| t_and_b.bounds = new_viewport_size);
811 });
812 self.runtime_context.mark_layer_canvas_plans_dirty();
813 self.runtime_context.mark_occlusion_dirty();
814 }
815
816 pub fn update_layer_count(&self, rcs: &mut dyn RenderContext) {
817 static LAST_LAYER_COUNT: AtomicUsize = AtomicUsize::new(0); let curr_layer_count = self.runtime_context.layer_count.get();
819 let old_layer_count = LAST_LAYER_COUNT.load(Ordering::Relaxed);
820 if old_layer_count != curr_layer_count {
821 rcs.resize_layers_to(
822 curr_layer_count,
823 Rc::clone(&self.runtime_context.dirty_canvases),
824 );
825 self.runtime_context
826 .resize_canvas_layers_to(curr_layer_count);
827 LAST_LAYER_COUNT.store(curr_layer_count, Ordering::Relaxed)
828 }
829 }
830
831 pub fn global_dispatch_focus(&self, args: Focus) -> bool {
832 let Some(root_expanded_node) = &self.root_expanded_node else {
833 return false;
834 };
835 let mut prevent_default = false;
836 root_expanded_node.recurse_visit_postorder(&mut |expanded_node| {
837 prevent_default |= expanded_node.dispatch_focus(
838 Event::new(args.clone()),
839 &self.runtime_context.globals(),
840 &self.runtime_context,
841 );
842 });
843 prevent_default
844 }
845
846 pub fn global_dispatch_select_start(&self, args: SelectStart) -> bool {
847 let Some(root_expanded_node) = &self.root_expanded_node else {
848 return false;
849 };
850 let mut prevent_default = false;
851 root_expanded_node.recurse_visit_postorder(&mut |expanded_node| {
852 prevent_default |= expanded_node.dispatch_select_start(
853 Event::new(args.clone()),
854 &self.runtime_context.globals(),
855 &self.runtime_context,
856 );
857 });
858 prevent_default
859 }
860
861 pub fn global_dispatch_key_down(&self, args: KeyDown) -> bool {
862 let Some(root_expanded_node) = &self.root_expanded_node else {
863 return false;
864 };
865 let mut prevent_default = false;
866 root_expanded_node.recurse_visit_postorder(&mut |expanded_node| {
867 prevent_default |= expanded_node.dispatch_key_down(
868 Event::new(args.clone()),
869 &self.runtime_context.globals(),
870 &self.runtime_context,
871 );
872 });
873 prevent_default
874 }
875
876 pub fn global_dispatch_key_up(&self, args: KeyUp) -> bool {
877 let Some(root_expanded_node) = &self.root_expanded_node else {
878 return false;
879 };
880 let mut prevent_default = false;
881 root_expanded_node.recurse_visit_postorder(&mut |expanded_node| {
882 prevent_default |= expanded_node.dispatch_key_up(
883 Event::new(args.clone()),
884 &self.runtime_context.globals(),
885 &self.runtime_context,
886 );
887 });
888 prevent_default
889 }
890
891 pub fn global_dispatch_key_press(&self, args: KeyPress) -> bool {
892 let Some(root_expanded_node) = &self.root_expanded_node else {
893 return false;
894 };
895 let mut prevent_default = false;
896 root_expanded_node.recurse_visit_postorder(&mut |expanded_node| {
897 prevent_default |= expanded_node.dispatch_key_press(
898 Event::new(args.clone()),
899 &self.runtime_context.globals(),
900 &self.runtime_context,
901 );
902 });
903 prevent_default
904 }
905
906 pub fn global_dispatch_gyro(&self, args: Gyro) -> bool {
907 let mut prevent_default = false;
908 for node_id in self.runtime_context.gyro_handler_nodes() {
909 let Some(expanded_node) = self.runtime_context.get_expanded_node_by_eid(node_id) else {
910 continue;
911 };
912 prevent_default |= expanded_node.dispatch_gyro(
913 Event::new(args.clone()),
914 &self.runtime_context.globals(),
915 &self.runtime_context,
916 );
917 }
918 prevent_default
919 }
920
921 pub fn global_dispatch_accel(&self, args: Accel) -> bool {
922 let mut prevent_default = false;
923 for node_id in self.runtime_context.accel_handler_nodes() {
924 let Some(expanded_node) = self.runtime_context.get_expanded_node_by_eid(node_id) else {
925 continue;
926 };
927 prevent_default |= expanded_node.dispatch_accel(
928 Event::new(args.clone()),
929 &self.runtime_context.globals(),
930 &self.runtime_context,
931 );
932 }
933 prevent_default
934 }
935}
936
937#[cfg(test)]
938mod tests {
939 use super::*;
940 use crate::constants::{ACCEL_HANDLERS, GYRO_HANDLERS, PRE_RENDER_HANDLERS, TICK_HANDLERS};
941 use crate::{InstanceNode, InstantiationArgs};
942 use pax_runtime_api::pax_value::{PaxAny, PaxValue};
943 use std::cell::RefCell;
944 use std::sync::atomic::{AtomicUsize, Ordering};
945
946 static TICK_CALLS: AtomicUsize = AtomicUsize::new(0);
947 static PRE_RENDER_CALLS: AtomicUsize = AtomicUsize::new(0);
948 static GYRO_CALLS: AtomicUsize = AtomicUsize::new(0);
949 static ACCEL_CALLS: AtomicUsize = AtomicUsize::new(0);
950
951 fn count_tick_handler(
952 _properties: Rc<RefCell<PaxAny>>,
953 _ctx: &NodeContext,
954 _event: Option<PaxAny>,
955 ) {
956 TICK_CALLS.fetch_add(1, Ordering::SeqCst);
957 }
958
959 fn count_pre_render_handler(
960 _properties: Rc<RefCell<PaxAny>>,
961 _ctx: &NodeContext,
962 _event: Option<PaxAny>,
963 ) {
964 PRE_RENDER_CALLS.fetch_add(1, Ordering::SeqCst);
965 }
966
967 fn count_gyro_handler(
968 _properties: Rc<RefCell<PaxAny>>,
969 _ctx: &NodeContext,
970 _event: Option<PaxAny>,
971 ) {
972 GYRO_CALLS.fetch_add(1, Ordering::SeqCst);
973 }
974
975 fn count_accel_handler(
976 _properties: Rc<RefCell<PaxAny>>,
977 _ctx: &NodeContext,
978 _event: Option<PaxAny>,
979 ) {
980 ACCEL_CALLS.fetch_add(1, Ordering::SeqCst);
981 }
982
983 fn empty_component() -> Rc<ComponentInstance> {
984 <ComponentInstance as InstanceNode>::instantiate(InstantiationArgs {
985 prototypical_common_properties: crate::CommonPropertiesInit::Default,
986 prototypical_properties: crate::PropertiesInit::Factory(Box::new(|_, _| {
987 Some(Rc::new(RefCell::new(PaxAny::Builtin(PaxValue::default()))))
988 })),
989 handler_registry: None,
990 children: None,
991 component_template: None,
992 component_settings: None,
993 template_node_identifier: None,
994 template_node_type_id: None,
995 template_node_selector_info: None,
996 transition_config: Default::default(),
997 properties_scope: crate::PropertiesScopeInit::None,
998 })
999 }
1000
1001 fn component_with_lifecycle_handlers() -> Rc<ComponentInstance> {
1002 let mut registry = HandlerRegistry::default();
1003 registry.handlers.insert(
1004 TICK_HANDLERS.to_string(),
1005 vec![Handler::new_inline_handler(count_tick_handler)],
1006 );
1007 registry.handlers.insert(
1008 PRE_RENDER_HANDLERS.to_string(),
1009 vec![Handler::new_inline_handler(count_pre_render_handler)],
1010 );
1011
1012 <ComponentInstance as InstanceNode>::instantiate(InstantiationArgs {
1013 prototypical_common_properties: crate::CommonPropertiesInit::Default,
1014 prototypical_properties: crate::PropertiesInit::Factory(Box::new(|_, _| {
1015 Some(Rc::new(RefCell::new(PaxAny::Builtin(PaxValue::default()))))
1016 })),
1017 handler_registry: Some(Rc::new(RefCell::new(registry))),
1018 children: None,
1019 component_template: None,
1020 component_settings: None,
1021 template_node_identifier: None,
1022 template_node_type_id: None,
1023 template_node_selector_info: None,
1024 transition_config: Default::default(),
1025 properties_scope: crate::PropertiesScopeInit::None,
1026 })
1027 }
1028
1029 fn component_with_sensor_handlers() -> Rc<ComponentInstance> {
1030 let mut registry = HandlerRegistry::default();
1031 registry.handlers.insert(
1032 GYRO_HANDLERS.to_string(),
1033 vec![Handler::new_inline_handler(count_gyro_handler)],
1034 );
1035 registry.handlers.insert(
1036 ACCEL_HANDLERS.to_string(),
1037 vec![Handler::new_inline_handler(count_accel_handler)],
1038 );
1039
1040 <ComponentInstance as InstanceNode>::instantiate(InstantiationArgs {
1041 prototypical_common_properties: crate::CommonPropertiesInit::Default,
1042 prototypical_properties: crate::PropertiesInit::Factory(Box::new(|_, _| {
1043 Some(Rc::new(RefCell::new(PaxAny::Builtin(PaxValue::default()))))
1044 })),
1045 handler_registry: Some(Rc::new(RefCell::new(registry))),
1046 children: None,
1047 component_template: None,
1048 component_settings: None,
1049 template_node_identifier: None,
1050 template_node_type_id: None,
1051 template_node_selector_info: None,
1052 transition_config: Default::default(),
1053 properties_scope: crate::PropertiesScopeInit::None,
1054 })
1055 }
1056
1057 fn object_field(value: PaxValue, field: &str) -> PaxValue {
1058 let PaxValue::Object(fields) = value else {
1059 panic!("expected object value");
1060 };
1061 fields
1062 .into_iter()
1063 .find_map(|(name, value)| (name == field).then_some(value))
1064 .unwrap_or_else(|| panic!("expected object field {field}"))
1065 }
1066
1067 #[test]
1068 fn empty_engine_can_tick_and_mount_later() {
1069 let mut engine = PaxEngine::new_empty(
1070 (320.0, 240.0),
1071 Platform::Web,
1072 OS::Mac,
1073 Box::new(|| 0),
1074 layer_tiling::ScrollerTilingPolicy::default(),
1075 );
1076
1077 assert!(engine.root_expanded_node.is_none());
1078 assert!(engine.runtime_context.get_root_expanded_node().is_none());
1079 assert!(engine.tick().is_empty());
1080
1081 let root = engine.mount_root_component(empty_component());
1082 assert_eq!(
1083 engine.root_expanded_node.as_ref().map(|node| node.id),
1084 Some(root.id)
1085 );
1086 assert_eq!(
1087 engine
1088 .runtime_context
1089 .get_root_expanded_node()
1090 .map(|node| node.id),
1091 Some(root.id)
1092 );
1093 let _ = engine.tick();
1094 }
1095
1096 #[test]
1097 fn unmount_clears_registered_root() {
1098 let mut engine = PaxEngine::new_empty(
1099 (320.0, 240.0),
1100 Platform::Web,
1101 OS::Mac,
1102 Box::new(|| 0),
1103 layer_tiling::ScrollerTilingPolicy::default(),
1104 );
1105
1106 engine.mount_root_component(empty_component());
1107 engine.unmount();
1108
1109 assert!(engine.root_expanded_node.is_none());
1110 assert!(engine.runtime_context.get_root_expanded_node().is_none());
1111 }
1112
1113 #[test]
1114 fn globals_expose_sensor_values_to_stack_frame() {
1115 let engine = PaxEngine::new_empty(
1116 (320.0, 240.0),
1117 Platform::Web,
1118 OS::Mac,
1119 Box::new(|| 0),
1120 layer_tiling::ScrollerTilingPolicy::default(),
1121 );
1122 let globals = engine.runtime_context.globals();
1123 globals.gyro.set(Gyro {
1124 x: 1.0,
1125 y: 2.0,
1126 z: 3.0,
1127 });
1128 globals.accel.set(Accel {
1129 x: 4.0,
1130 y: 5.0,
1131 z: 6.0,
1132 });
1133
1134 assert_eq!(
1135 globals
1136 .stack_frame()
1137 .resolve_symbol("$gyro")
1138 .unwrap()
1139 .get_as_pax_value(),
1140 PaxValue::Object(
1141 vec![
1142 ("x".to_string(), PaxValue::Numeric(1.0.into())),
1143 ("y".to_string(), PaxValue::Numeric(2.0.into())),
1144 ("z".to_string(), PaxValue::Numeric(3.0.into())),
1145 ]
1146 .into_iter()
1147 .collect(),
1148 )
1149 );
1150 assert_eq!(
1151 globals
1152 .stack_frame()
1153 .resolve_symbol("$accel")
1154 .unwrap()
1155 .get_as_pax_value(),
1156 PaxValue::Object(
1157 vec![
1158 ("x".to_string(), PaxValue::Numeric(4.0.into())),
1159 ("y".to_string(), PaxValue::Numeric(5.0.into())),
1160 ("z".to_string(), PaxValue::Numeric(6.0.into())),
1161 ]
1162 .into_iter()
1163 .collect(),
1164 )
1165 );
1166 }
1167
1168 #[test]
1169 fn lifecycle_handlers_run_once_per_tick() {
1170 TICK_CALLS.store(0, Ordering::SeqCst);
1171 PRE_RENDER_CALLS.store(0, Ordering::SeqCst);
1172 let mut engine = PaxEngine::new_empty(
1173 (320.0, 240.0),
1174 Platform::Web,
1175 OS::Mac,
1176 Box::new(|| 0),
1177 layer_tiling::ScrollerTilingPolicy::default(),
1178 );
1179
1180 engine.mount_root_component(component_with_lifecycle_handlers());
1181 let _ = engine.tick();
1182
1183 assert_eq!(TICK_CALLS.load(Ordering::SeqCst), 1);
1184 assert_eq!(PRE_RENDER_CALLS.load(Ordering::SeqCst), 1);
1185 }
1186
1187 #[test]
1188 fn sensor_handlers_dispatch_through_registered_nodes() {
1189 GYRO_CALLS.store(0, Ordering::SeqCst);
1190 ACCEL_CALLS.store(0, Ordering::SeqCst);
1191 let mut engine = PaxEngine::new_empty(
1192 (320.0, 240.0),
1193 Platform::Web,
1194 OS::Mac,
1195 Box::new(|| 0),
1196 layer_tiling::ScrollerTilingPolicy::default(),
1197 );
1198
1199 assert!(!engine.global_dispatch_gyro(Gyro::default()));
1200 assert!(!engine.global_dispatch_accel(Accel::default()));
1201 engine.mount_root_component(component_with_sensor_handlers());
1202 assert!(!engine.global_dispatch_gyro(Gyro::default()));
1203 assert!(!engine.global_dispatch_accel(Accel::default()));
1204
1205 assert_eq!(GYRO_CALLS.load(Ordering::SeqCst), 1);
1206 assert_eq!(ACCEL_CALLS.load(Ordering::SeqCst), 1);
1207 }
1208
1209 #[test]
1210 fn globals_expose_clock_aliases_to_stack_frame() {
1211 let engine = PaxEngine::new_empty(
1212 (320.0, 240.0),
1213 Platform::Web,
1214 OS::Mac,
1215 Box::new(|| 123),
1216 layer_tiling::ScrollerTilingPolicy::default(),
1217 );
1218 let globals = engine.runtime_context.globals();
1219 let stack = globals.stack_frame();
1220
1221 assert_eq!(
1222 stack.resolve_symbol("$frames").unwrap().get_as_pax_value(),
1223 PaxValue::Numeric(0.into())
1224 );
1225 assert_eq!(
1226 stack.resolve_symbol("$millis").unwrap().get_as_pax_value(),
1227 PaxValue::Numeric(123.into())
1228 );
1229 let old_frames_symbol = format!("{}{}", "$frames", "_elapsed");
1230 let old_millis_symbol = format!("{}{}", "$elapsed", "_millis");
1231 assert!(stack.resolve_symbol(&old_frames_symbol).is_none());
1232 assert!(stack.resolve_symbol(&old_millis_symbol).is_none());
1233 }
1234
1235 #[test]
1236 fn globals_expose_target_aliases_to_stack_frame() {
1237 let engine = PaxEngine::new_empty(
1238 (320.0, 240.0),
1239 Platform::Native,
1240 OS::IPad,
1241 Box::new(|| 0),
1242 layer_tiling::ScrollerTilingPolicy::default(),
1243 );
1244 let globals = engine.runtime_context.globals();
1245 let stack = globals.stack_frame();
1246
1247 assert_eq!(
1248 stack.resolve_symbol("$ios").unwrap().get_as_pax_value(),
1249 PaxValue::Bool(true)
1250 );
1251 assert_eq!(
1252 stack.resolve_symbol("$ipad").unwrap().get_as_pax_value(),
1253 PaxValue::Bool(true)
1254 );
1255 assert_eq!(
1256 stack.resolve_symbol("$iphone").unwrap().get_as_pax_value(),
1257 PaxValue::Bool(false)
1258 );
1259 assert_eq!(
1260 stack.resolve_symbol("$native").unwrap().get_as_pax_value(),
1261 PaxValue::Bool(true)
1262 );
1263 assert_eq!(
1264 object_field(
1265 stack.resolve_symbol("$target").unwrap().get_as_pax_value(),
1266 "ipad"
1267 ),
1268 PaxValue::Bool(true)
1269 );
1270 }
1271
1272 #[test]
1273 fn globals_expose_viewport_orientation_aliases_to_stack_frame() {
1274 let engine = PaxEngine::new_empty(
1275 (320.0, 240.0),
1276 Platform::Web,
1277 OS::Mac,
1278 Box::new(|| 0),
1279 layer_tiling::ScrollerTilingPolicy::default(),
1280 );
1281 let globals = engine.runtime_context.globals();
1282 let stack = globals.stack_frame();
1283
1284 assert_eq!(
1285 stack
1286 .resolve_symbol("$landscape")
1287 .unwrap()
1288 .get_as_pax_value(),
1289 PaxValue::Bool(true)
1290 );
1291 assert_eq!(
1292 stack.resolve_symbol("$major").unwrap().get_as_pax_value(),
1293 PaxValue::Numeric(320.0.into())
1294 );
1295 assert_eq!(
1296 object_field(
1297 stack
1298 .resolve_symbol("$viewport")
1299 .unwrap()
1300 .get_as_pax_value(),
1301 "minor"
1302 ),
1303 PaxValue::Numeric(240.0.into())
1304 );
1305
1306 globals.viewport.update(|viewport| {
1307 viewport.bounds = (240.0, 320.0);
1308 });
1309
1310 assert_eq!(
1311 stack
1312 .resolve_symbol("$portrait")
1313 .unwrap()
1314 .get_as_pax_value(),
1315 PaxValue::Bool(true)
1316 );
1317 assert_eq!(
1318 stack.resolve_symbol("$minor").unwrap().get_as_pax_value(),
1319 PaxValue::Numeric(240.0.into())
1320 );
1321 }
1322}