1use crate::api::math::Point2;
2use crate::api::Window;
3use crate::constants::{ACCEL_HANDLERS, GYRO_HANDLERS, PRE_RENDER_HANDLERS, TICK_HANDLERS};
4use pax_language::interpreter::property_resolution::IdentifierResolver;
5use pax_manifest::cartridge_generation::{TRANSITION_PHASE_ENTER, TRANSITION_PHASE_EXIT};
6use pax_manifest::UniqueTemplateNodeIdentifier;
7use pax_message::{NativeMessage, SafeAreaInsets, ScreenshotData};
8use pax_runtime_api::properties::{
9 drain_effects, drain_effects_with_report, property_has_direct_outbound,
10 property_outbound_debug_names, register_effect_property, register_effect_property_with_name,
11 UntypedProperty,
12};
13use pax_runtime_api::{
14 borrow, borrow_mut, use_RefCell, Event, Interpolatable, LightShape, LocalProperty, MouseOut,
15 MouseOver, RenderContext, SceneLight, SceneLighting, Variable,
16};
17use_RefCell!();
18use kurbo::{Affine, Point};
19use std::cell::Cell;
20use std::collections::{HashMap, HashSet};
21use std::hash::Hash;
22use std::rc::{Rc, Weak};
23use std::sync::OnceLock;
24use std::time::Instant;
25
26use crate::{ExpandedNode, Globals};
27
28#[cfg(feature = "designtime")]
29use crate::{ComponentInstance, InstanceNode};
30
31fn effect_drain_instrumentation_enabled() -> bool {
32 static ENABLED: OnceLock<bool> = OnceLock::new();
33 *ENABLED.get_or_init(|| {
34 std::env::var("PAX_EFFECT_DRAIN_INSTRUMENTATION")
35 .map(|value| {
36 let value = value.to_ascii_lowercase();
37 matches!(value.as_str(), "1" | "true" | "yes" | "on")
38 })
39 .unwrap_or(false)
40 })
41}
42
43fn format_effect_top(top_effects: &[(String, usize)]) -> String {
44 top_effects
45 .iter()
46 .map(|(name, count)| format!("{count}x {name}"))
47 .collect::<Vec<_>>()
48 .join("; ")
49}
50
51fn summarize_debug_names(names: Vec<String>) -> Vec<(String, usize)> {
52 let mut counts = HashMap::new();
53 for name in names {
54 let category = name
55 .split_once(" (")
56 .map(|(category, _)| category)
57 .unwrap_or(&name)
58 .to_owned();
59 *counts.entry(category).or_insert(0) += 1;
60 }
61 let mut counts = counts.into_iter().collect::<Vec<_>>();
62 counts.sort_by(|(left_name, left_count), (right_name, right_count)| {
63 right_count
64 .cmp(left_count)
65 .then_with(|| left_name.cmp(right_name))
66 });
67 counts
68}
69
70impl Interpolatable for ExpandedNodeIdentifier {}
71
72fn transform_scene_light_for_layer(
73 mut light: SceneLight,
74 source_to_root: Affine,
75 root_to_target: Affine,
76) -> SceneLight {
77 if matches!(light.shape, LightShape::Point) {
78 let point = Point::new(light.position.x, light.position.y);
79 let point = root_to_target * (source_to_root * point);
80 light.position.x = point.x;
81 light.position.y = point.y;
82 }
83 light
84}
85
86#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
87pub struct ExpandedNodeIdentifier(pub u32);
89
90impl ExpandedNodeIdentifier {
91 pub fn to_u32(&self) -> u32 {
93 self.0
94 }
95}
96
97pub struct RuntimeContext {
99 pub application: pax_runtime_api::application::ApplicationInstance,
100 pub property_graph: pax_runtime_api::PropertyGraph,
102 next_uid: Cell<ExpandedNodeIdentifier>,
103 messages: RefCell<Vec<NativeMessage>>,
104 globals: RefCell<Globals>,
105 root_expanded_node: RefCell<Weak<ExpandedNode>>,
106 #[cfg(feature = "designtime")]
107 pub userland_frame_instance_node: RefCell<Option<Rc<dyn InstanceNode>>>,
108 #[cfg(feature = "designtime")]
109 pub userland_root_expanded_node: RefCell<Option<Rc<ExpandedNode>>>,
110 node_cache: RefCell<NodeCache>,
111 last_topmost_element: RefCell<Weak<ExpandedNode>>,
112 active_touch_targets: RefCell<HashMap<i64, ExpandedNodeIdentifier>>,
113 queued_custom_events: RefCell<Vec<(Rc<ExpandedNode>, &'static str)>>,
114 queued_renders: RefCell<Vec<Rc<ExpandedNode>>>,
115 pub layer_count: Cell<usize>,
116 pub dirty_canvases: Rc<RefCell<Vec<bool>>>,
117 dirty_canvas_nodes: RefCell<HashSet<ExpandedNodeIdentifier>>,
118 scene_geometry: RefCell<crate::scene_geometry::SceneGeometry>,
119 pub(crate) viewport_proximity: RefCell<crate::viewport_proximity::ViewportProximityState>,
120 canvas_node_light_masks: RefCell<HashMap<ExpandedNodeIdentifier, u32>>,
121 scene_lighting_nodes: RefCell<HashSet<ExpandedNodeIdentifier>>,
122 scene_lighting_dirty: Cell<bool>,
123 layer_light_scopes:
125 RefCell<HashMap<usize, Vec<(ExpandedNodeIdentifier, Option<ExpandedNodeIdentifier>)>>>,
126 #[cfg(test)]
127 lighting_mask_node_visits: Cell<usize>,
128 lighting_overflow_counts: RefCell<HashMap<usize, usize>>,
129 targeted_canvas_replay_node_ids: RefCell<HashMap<usize, HashSet<u32>>>,
132 removed_canvas_nodes: RefCell<Vec<(usize, u32)>>,
133 occlusion_dirty: Cell<bool>,
134 pub(crate) occlusion_state: RefCell<crate::engine::occlusion::OcclusionState>,
135 layer_canvas_plan_generation: Cell<u32>,
136 canvas_drawable_layers: RefCell<HashSet<usize>>,
137 import_settings_node_count: Cell<usize>,
138 tick_handler_nodes: RefCell<Vec<ExpandedNodeIdentifier>>,
139 pre_render_handler_nodes: RefCell<Vec<ExpandedNodeIdentifier>>,
140 gyro_handler_nodes: RefCell<Vec<ExpandedNodeIdentifier>>,
141 accel_handler_nodes: RefCell<Vec<ExpandedNodeIdentifier>>,
142 screenshot_map: Rc<RefCell<HashMap<u32, ScreenshotData>>>,
143 scroller_surface_states: RefCell<HashMap<u32, ScrollerSurfaceState>>,
144 layer_scroller_owners: RefCell<HashMap<usize, ExpandedNodeIdentifier>>,
145 root_scroller_id: Cell<Option<u32>>,
146 visual_viewport_state: Cell<Option<VisualViewportState>>,
147 safe_area_insets: LocalProperty<SafeAreaInsets>,
148}
149
150#[derive(Clone, Copy, Debug, Default)]
151pub struct ScrollerSurfaceState {
153 pub viewport_width: f64,
154 pub viewport_height: f64,
155 pub content_width: f64,
156 pub content_height: f64,
157 pub scroll_x: f64,
158 pub scroll_y: f64,
159 pub presentation_scroll_x: f64,
160 pub presentation_scroll_y: f64,
161 pub clip_content: bool,
162}
163
164#[derive(Clone, Copy, Debug, PartialEq, Eq)]
165pub enum ScrollerSurfaceStateChange {
167 Unchanged,
168 ScrollOnly,
169 Structural,
170}
171
172impl ScrollerSurfaceState {
173 fn close_enough(lhs: f64, rhs: f64) -> bool {
174 (lhs - rhs).abs() <= 1e-4
175 }
176
177 fn structural_eq(&self, other: &Self) -> bool {
178 Self::close_enough(self.viewport_width, other.viewport_width)
179 && Self::close_enough(self.viewport_height, other.viewport_height)
180 && Self::close_enough(self.content_width, other.content_width)
181 && Self::close_enough(self.content_height, other.content_height)
182 && self.clip_content == other.clip_content
183 }
184
185 fn scroll_eq(&self, other: &Self) -> bool {
186 Self::close_enough(self.scroll_x, other.scroll_x)
187 && Self::close_enough(self.scroll_y, other.scroll_y)
188 && Self::close_enough(self.presentation_scroll_x, other.presentation_scroll_x)
189 && Self::close_enough(self.presentation_scroll_y, other.presentation_scroll_y)
190 }
191}
192
193#[derive(Clone, Copy, Debug, Default)]
194pub struct VisualViewportState {
196 pub width: f64,
197 pub height: f64,
198 pub offset_x: f64,
199 pub offset_y: f64,
200 pub page_scroll_x: f64,
201 pub page_scroll_y: f64,
202}
203
204struct NodeCache {
205 eid_to_node: HashMap<ExpandedNodeIdentifier, Rc<ExpandedNode>>,
206 uni_to_eid: HashMap<UniqueTemplateNodeIdentifier, Vec<ExpandedNodeIdentifier>>,
207}
208
209impl NodeCache {
210 fn new() -> Self {
211 Self {
212 eid_to_node: Default::default(),
213 uni_to_eid: Default::default(),
214 }
215 }
216
217 fn add_to_cache(&mut self, node: &Rc<ExpandedNode>) {
219 self.eid_to_node.insert(node.id, Rc::clone(&node));
220 let uni = borrow!(node.instance_node)
221 .base()
222 .template_node_identifier
223 .clone();
224 if let Some(uni) = uni {
225 self.uni_to_eid.entry(uni).or_default().push(node.id);
226 }
227 }
228
229 fn remove_from_cache(&mut self, node: &Rc<ExpandedNode>) {
231 self.eid_to_node.remove(&node.id);
232 if let Some(uni) = &borrow!(node.instance_node).base().template_node_identifier {
233 self.uni_to_eid
234 .entry(uni.clone())
235 .or_default()
236 .retain(|&n| n != node.id);
237 }
238 }
239}
240
241impl RuntimeContext {
242 #[cfg(not(feature = "designtime"))]
243 pub fn new(globals: Globals) -> Self {
245 let application =
246 pax_runtime_api::application::ApplicationInstance::for_runtime(globals.target);
247 Self {
248 property_graph: application.property_graph(),
249 application,
250 next_uid: Cell::new(ExpandedNodeIdentifier(0)),
251 messages: RefCell::new(Vec::new()),
252 globals: RefCell::new(globals),
253 root_expanded_node: RefCell::new(Weak::new()),
254 node_cache: RefCell::new(NodeCache::new()),
255 active_touch_targets: Default::default(),
256 queued_custom_events: Default::default(),
257 queued_renders: Default::default(),
258 layer_count: Cell::default(),
259 last_topmost_element: Default::default(),
260 dirty_canvases: Default::default(),
261 dirty_canvas_nodes: Default::default(),
262 scene_geometry: Default::default(),
263 viewport_proximity: Default::default(),
264 canvas_node_light_masks: Default::default(),
265 scene_lighting_nodes: Default::default(),
266 scene_lighting_dirty: Default::default(),
267 layer_light_scopes: Default::default(),
268 #[cfg(test)]
269 lighting_mask_node_visits: Cell::new(0),
270 lighting_overflow_counts: Default::default(),
271 targeted_canvas_replay_node_ids: Default::default(),
272 removed_canvas_nodes: Default::default(),
273 occlusion_dirty: Cell::new(true),
274 occlusion_state: Default::default(),
275 layer_canvas_plan_generation: Cell::new(1),
276 canvas_drawable_layers: Default::default(),
277 import_settings_node_count: Cell::new(0),
278 tick_handler_nodes: Default::default(),
279 pre_render_handler_nodes: Default::default(),
280 gyro_handler_nodes: Default::default(),
281 accel_handler_nodes: Default::default(),
282 screenshot_map: Default::default(),
283 scroller_surface_states: Default::default(),
284 layer_scroller_owners: Default::default(),
285 root_scroller_id: Cell::new(None),
286 visual_viewport_state: Cell::new(None),
287 safe_area_insets: LocalProperty::default(),
288 }
289 }
290
291 #[cfg(feature = "designtime")]
292 pub fn new(globals: Globals, userland: Rc<ComponentInstance>) -> Self {
294 let application =
295 pax_runtime_api::application::ApplicationInstance::for_runtime(globals.target);
296 Self {
297 property_graph: application.property_graph(),
298 application,
299 next_uid: Cell::new(ExpandedNodeIdentifier(0)),
300 messages: RefCell::new(Vec::new()),
301 globals: RefCell::new(globals),
302 root_expanded_node: RefCell::new(Weak::new()),
303 userland_frame_instance_node: RefCell::new(Some(userland)),
304 userland_root_expanded_node: Default::default(),
305 node_cache: RefCell::new(NodeCache::new()),
306 active_touch_targets: Default::default(),
307 queued_custom_events: Default::default(),
308 queued_renders: Default::default(),
309 layer_count: Cell::default(),
310 last_topmost_element: Default::default(),
311 dirty_canvases: Default::default(),
312 dirty_canvas_nodes: Default::default(),
313 scene_geometry: Default::default(),
314 viewport_proximity: Default::default(),
315 canvas_node_light_masks: Default::default(),
316 scene_lighting_nodes: Default::default(),
317 scene_lighting_dirty: Default::default(),
318 layer_light_scopes: Default::default(),
319 #[cfg(test)]
320 lighting_mask_node_visits: Cell::new(0),
321 lighting_overflow_counts: Default::default(),
322 targeted_canvas_replay_node_ids: Default::default(),
323 removed_canvas_nodes: Default::default(),
324 occlusion_dirty: Cell::new(true),
325 occlusion_state: Default::default(),
326 layer_canvas_plan_generation: Cell::new(1),
327 canvas_drawable_layers: Default::default(),
328 import_settings_node_count: Cell::new(0),
329 tick_handler_nodes: Default::default(),
330 pre_render_handler_nodes: Default::default(),
331 gyro_handler_nodes: Default::default(),
332 accel_handler_nodes: Default::default(),
333 screenshot_map: Default::default(),
334 scroller_surface_states: Default::default(),
335 layer_scroller_owners: Default::default(),
336 root_scroller_id: Cell::new(None),
337 visual_viewport_state: Cell::new(None),
338 safe_area_insets: LocalProperty::default(),
339 }
340 }
341
342 #[cfg(feature = "designtime")]
343 pub fn new_empty(globals: Globals) -> Self {
345 let application =
346 pax_runtime_api::application::ApplicationInstance::for_runtime(globals.target);
347 Self {
348 property_graph: application.property_graph(),
349 application,
350 next_uid: Cell::new(ExpandedNodeIdentifier(0)),
351 messages: RefCell::new(Vec::new()),
352 globals: RefCell::new(globals),
353 root_expanded_node: RefCell::new(Weak::new()),
354 userland_frame_instance_node: RefCell::new(None),
355 userland_root_expanded_node: Default::default(),
356 node_cache: RefCell::new(NodeCache::new()),
357 active_touch_targets: Default::default(),
358 queued_custom_events: Default::default(),
359 queued_renders: Default::default(),
360 layer_count: Cell::default(),
361 last_topmost_element: Default::default(),
362 dirty_canvases: Default::default(),
363 dirty_canvas_nodes: Default::default(),
364 scene_geometry: Default::default(),
365 viewport_proximity: Default::default(),
366 canvas_node_light_masks: Default::default(),
367 scene_lighting_nodes: Default::default(),
368 scene_lighting_dirty: Default::default(),
369 layer_light_scopes: Default::default(),
370 #[cfg(test)]
371 lighting_mask_node_visits: Cell::new(0),
372 lighting_overflow_counts: Default::default(),
373 targeted_canvas_replay_node_ids: Default::default(),
374 removed_canvas_nodes: Default::default(),
375 occlusion_dirty: Cell::new(true),
376 occlusion_state: Default::default(),
377 layer_canvas_plan_generation: Cell::new(1),
378 canvas_drawable_layers: Default::default(),
379 import_settings_node_count: Cell::new(0),
380 tick_handler_nodes: Default::default(),
381 pre_render_handler_nodes: Default::default(),
382 gyro_handler_nodes: Default::default(),
383 accel_handler_nodes: Default::default(),
384 screenshot_map: Default::default(),
385 scroller_surface_states: Default::default(),
386 layer_scroller_owners: Default::default(),
387 root_scroller_id: Cell::new(None),
388 visual_viewport_state: Cell::new(None),
389 safe_area_insets: LocalProperty::default(),
390 }
391 }
392
393 pub fn register_root_expanded_node(&self, root: &Rc<ExpandedNode>) {
395 *borrow_mut!(self.root_expanded_node) = Rc::downgrade(root);
396 }
397
398 pub fn clear_root_expanded_node(&self) {
400 *borrow_mut!(self.root_expanded_node) = Weak::new();
401 let mut state = self.occlusion_state.borrow_mut();
402 let stats = state.stats;
403 *state = Default::default();
404 state.stats = stats;
405 }
406
407 pub fn add_to_cache(&self, node: &Rc<ExpandedNode>) {
409 borrow_mut!(self.node_cache).add_to_cache(node);
410 self.refresh_scene_lighting_node(node);
411 self.register_node_lifecycle_handlers(node);
412 if borrow!(node.instance_node).base().flags().layer == crate::api::Layer::Canvas {
413 self.mark_canvas_node_dirty(node.id);
416 if !node.is_render_source() {
417 self.set_canvas_dirty(node.occlusion.get().render_layer_id);
418 }
419 }
420 if node.is_import_settings_node() {
421 self.import_settings_node_count
422 .set(self.import_settings_node_count.get() + 1);
423 }
424 self.mark_occlusion_dirty();
425 }
426
427 pub fn remove_from_cache(&self, node: &Rc<ExpandedNode>) {
429 borrow_mut!(self.node_cache).remove_from_cache(node);
430 if borrow_mut!(self.scene_lighting_nodes).remove(&node.id) {
431 self.set_all_canvases_dirty();
433 }
434 self.unregister_viewport_proximity(node.id.to_u32());
435 borrow_mut!(self.scene_geometry).remove(node.id.to_u32());
436 self.occlusion_state.borrow_mut().remove(node.id.to_u32());
437 borrow_mut!(self.canvas_node_light_masks).remove(&node.id);
438 borrow_mut!(self.active_touch_targets).retain(|_, target| *target != node.id);
439 self.unregister_node_lifecycle_handlers(node.id);
440 if node.is_import_settings_node() {
441 self.import_settings_node_count
442 .set(self.import_settings_node_count.get().saturating_sub(1));
443 }
444 self.mark_occlusion_dirty();
445 }
446
447 pub fn has_import_settings_nodes(&self) -> bool {
448 self.import_settings_node_count.get() > 0
449 }
450
451 pub(crate) fn refresh_scene_lighting_node(&self, node: &ExpandedNode) {
452 let mut contributors = borrow_mut!(self.scene_lighting_nodes);
453 let changed = if borrow!(node.instance_node).has_scene_lighting() {
454 contributors.insert(node.id)
455 } else {
456 contributors.remove(&node.id)
457 };
458 if changed {
459 self.set_all_canvases_dirty();
460 }
461 }
462
463 pub fn register_node_effect_property(
464 &self,
465 node: ExpandedNodeIdentifier,
466 prop: &LocalProperty<()>,
467 ) {
468 self.register_node_effect_property_named(node, prop, "node effect");
469 }
470
471 pub fn register_node_effect_property_named(
472 &self,
473 node: ExpandedNodeIdentifier,
474 prop: &LocalProperty<()>,
475 effect_name: &str,
476 ) {
477 if effect_drain_instrumentation_enabled() {
478 let debug_name = self.node_effect_debug_name(node, effect_name);
479 register_effect_property_with_name(prop, &debug_name);
480 } else {
481 register_effect_property(prop);
482 }
483 }
484
485 pub fn register_expanded_node_effect_property_named(
486 &self,
487 node: &Rc<ExpandedNode>,
488 prop: &LocalProperty<()>,
489 effect_name: &str,
490 ) {
491 if effect_drain_instrumentation_enabled() {
492 let debug_name = Self::expanded_node_effect_debug_name(node, effect_name);
493 register_effect_property_with_name(prop, &debug_name);
494 } else {
495 register_effect_property(prop);
496 }
497 }
498
499 pub fn register_node_effect(
500 &self,
501 node: ExpandedNodeIdentifier,
502 dependencies: &[UntypedProperty],
503 effect: impl Fn() + 'static,
504 ) -> LocalProperty<()> {
505 let prop = LocalProperty::computed(effect, dependencies);
506 self.register_node_effect_property_named(node, &prop, "registered node effect");
507 prop
508 }
509
510 pub fn drain_node_effects(&self) {
511 let _graph = self.property_graph.enter();
512 self.property_graph.import();
513 const MAX_NODE_EFFECTS_PER_TICK: usize = 100_000;
514 if !effect_drain_instrumentation_enabled() {
515 let drained = drain_effects(MAX_NODE_EFFECTS_PER_TICK);
516 if drained == MAX_NODE_EFFECTS_PER_TICK {
517 log::warn!(
518 "reactive drain hit {} evaluations in one tick; deferring remaining work",
519 MAX_NODE_EFFECTS_PER_TICK
520 );
521 }
522 return;
523 }
524
525 let start = Instant::now();
526 let report = drain_effects_with_report(MAX_NODE_EFFECTS_PER_TICK);
527 let elapsed_ms = start.elapsed().as_secs_f64() * 1000.0;
528 if report.budget_exhausted {
529 log::warn!(
530 "reactive drain hit {} evaluations in one tick; deferring remaining work",
531 MAX_NODE_EFFECTS_PER_TICK
532 );
533 }
534 if report.popped > 0 || report.cutoffs_evaluated > 0 {
535 let globals = self.globals();
536 let frame_dependents = summarize_debug_names(property_outbound_debug_names(
537 &globals.elapsed_frames.untyped(),
538 ));
539 let millis_dependents = summarize_debug_names(property_outbound_debug_names(
540 &globals.elapsed_millis.untyped(),
541 ));
542 let frame_clock = globals.elapsed_frames.untyped();
543 let millis_clock = globals.elapsed_millis.untyped();
544 let (entering, exiting, enter_cleanup, exit_cleanup, stale_clock_nodes) = {
545 let nodes = borrow!(self.node_cache);
546 nodes.eid_to_node.values().fold(
547 (0, 0, 0, 0, Vec::new()),
548 |(entering, exiting, enter_cleanup, exit_cleanup, mut stale_clock_nodes),
549 node| {
550 let phase = node.transition_phase.get();
551 if phase != TRANSITION_PHASE_ENTER
552 && phase != TRANSITION_PHASE_EXIT
553 && (property_has_direct_outbound(
554 &frame_clock,
555 &node.transition_playhead.untyped(),
556 ) || property_has_direct_outbound(
557 &millis_clock,
558 &node.transition_playhead_millis.untyped(),
559 ))
560 {
561 stale_clock_nodes
562 .push(Self::expanded_node_effect_debug_name(node, "idle clock"));
563 }
564 (
565 entering + usize::from(phase == TRANSITION_PHASE_ENTER),
566 exiting + usize::from(phase == TRANSITION_PHASE_EXIT),
567 enter_cleanup + usize::from(node.enter_cleanup_active.get()),
568 exit_cleanup + usize::from(node.exit_cleanup_active.get()),
569 stale_clock_nodes,
570 )
571 },
572 )
573 };
574 println!(
575 "[PaxEffectDrain] ran={} popped={} clean={} unregistered={} missing={} remaining={} cutoffs={} suppressed={} propagated={} remaining_cutoffs={} elapsed_ms={:.3} transitions=enter:{}/exit:{}/enter_cleanup:{}/exit_cleanup:{} stale_clocks=[{}] frame_deps=[{}] millis_deps=[{}] effects=[{}] cutoff_nodes=[{}]",
576 report.ran,
577 report.popped,
578 report.skipped_clean,
579 report.skipped_unregistered,
580 report.skipped_missing,
581 report.remaining,
582 report.cutoffs_evaluated,
583 report.cutoffs_suppressed,
584 report.cutoffs_propagated,
585 report.remaining_cutoffs,
586 elapsed_ms,
587 entering,
588 exiting,
589 enter_cleanup,
590 exit_cleanup,
591 stale_clock_nodes.join("; "),
592 format_effect_top(&frame_dependents),
593 format_effect_top(&millis_dependents),
594 format_effect_top(&report.top_effects),
595 format_effect_top(&report.top_cutoffs),
596 );
597 }
598 }
599
600 fn node_effect_debug_name(&self, node: ExpandedNodeIdentifier, effect_name: &str) -> String {
601 let node = borrow!(self.node_cache).eid_to_node.get(&node).cloned();
602 if let Some(node) = node {
603 Self::expanded_node_effect_debug_name(&node, effect_name)
604 } else {
605 format!("{effect_name} node={node:?}")
606 }
607 }
608
609 fn expanded_node_effect_debug_name(node: &Rc<ExpandedNode>, effect_name: &str) -> String {
610 let instance_node = borrow!(node.instance_node);
611 let base = instance_node.base();
612 let type_name = base
613 .template_node_type_id
614 .as_ref()
615 .map(|type_id| {
616 type_id
617 .get_pascal_identifier()
618 .unwrap_or_else(|| type_id.to_string())
619 })
620 .unwrap_or_else(|| "<unknown>".to_owned());
621
622 format!("{effect_name} type={type_name}")
623 }
624
625 pub fn tick_handler_nodes(&self) -> Vec<ExpandedNodeIdentifier> {
626 borrow!(self.tick_handler_nodes).clone()
627 }
628
629 pub fn pre_render_handler_nodes(&self) -> Vec<ExpandedNodeIdentifier> {
630 borrow!(self.pre_render_handler_nodes).clone()
631 }
632
633 pub fn gyro_handler_nodes(&self) -> Vec<ExpandedNodeIdentifier> {
634 borrow!(self.gyro_handler_nodes).clone()
635 }
636
637 pub fn accel_handler_nodes(&self) -> Vec<ExpandedNodeIdentifier> {
638 borrow!(self.accel_handler_nodes).clone()
639 }
640
641 fn register_node_lifecycle_handlers(&self, node: &Rc<ExpandedNode>) {
642 let registry = borrow!(node.instance_node)
643 .base()
644 .handler_registry
645 .as_ref()
646 .map(Rc::clone);
647 let Some(registry) = registry else {
648 return;
649 };
650 let handlers = &borrow!(registry).handlers;
651 if handlers.get(TICK_HANDLERS).is_some_and(|h| !h.is_empty()) {
652 self.register_lifecycle_handler(&self.tick_handler_nodes, node.id);
653 }
654 if handlers
655 .get(PRE_RENDER_HANDLERS)
656 .is_some_and(|h| !h.is_empty())
657 {
658 self.register_lifecycle_handler(&self.pre_render_handler_nodes, node.id);
659 }
660 if handlers.get(GYRO_HANDLERS).is_some_and(|h| !h.is_empty()) {
661 self.register_lifecycle_handler(&self.gyro_handler_nodes, node.id);
662 }
663 if handlers.get(ACCEL_HANDLERS).is_some_and(|h| !h.is_empty()) {
664 self.register_lifecycle_handler(&self.accel_handler_nodes, node.id);
665 }
666 }
667
668 fn unregister_node_lifecycle_handlers(&self, id: ExpandedNodeIdentifier) {
669 borrow_mut!(self.tick_handler_nodes).retain(|node_id| *node_id != id);
670 borrow_mut!(self.pre_render_handler_nodes).retain(|node_id| *node_id != id);
671 borrow_mut!(self.gyro_handler_nodes).retain(|node_id| *node_id != id);
672 borrow_mut!(self.accel_handler_nodes).retain(|node_id| *node_id != id);
673 }
674
675 fn register_lifecycle_handler(
676 &self,
677 handler_nodes: &RefCell<Vec<ExpandedNodeIdentifier>>,
678 id: ExpandedNodeIdentifier,
679 ) {
680 let mut handler_nodes = borrow_mut!(handler_nodes);
681 if !handler_nodes.contains(&id) {
682 handler_nodes.push(id);
683 }
684 }
685
686 pub fn get_expanded_node_by_eid(&self, id: ExpandedNodeIdentifier) -> Option<Rc<ExpandedNode>> {
688 borrow!(self.node_cache).eid_to_node.get(&id).cloned()
689 }
690
691 pub fn capture_touch_target(&self, identifier: i64, target: ExpandedNodeIdentifier) {
693 borrow_mut!(self.active_touch_targets).insert(identifier, target);
694 }
695
696 pub fn captured_touch_target(&self, identifier: i64) -> Option<Rc<ExpandedNode>> {
698 let target = borrow!(self.active_touch_targets)
699 .get(&identifier)
700 .copied()?;
701 self.get_expanded_node_by_eid(target)
702 }
703
704 pub fn release_touch_target(&self, identifier: i64) -> Option<Rc<ExpandedNode>> {
706 let target = borrow_mut!(self.active_touch_targets).remove(&identifier)?;
707 self.get_expanded_node_by_eid(target)
708 }
709
710 pub fn load_screenshot(&self, id: u32, data: ScreenshotData) -> bool {
712 borrow_mut!(self.screenshot_map).insert(id, data);
713 true
714 }
715
716 pub fn get_screenshot_map(&self) -> Rc<RefCell<HashMap<u32, ScreenshotData>>> {
718 Rc::clone(&self.screenshot_map)
719 }
720
721 pub fn set_scroller_surface_state(
723 &self,
724 id: u32,
725 state: ScrollerSurfaceState,
726 ) -> ScrollerSurfaceStateChange {
727 let change = {
728 let mut states = borrow_mut!(self.scroller_surface_states);
729 let change = match states.get(&id) {
730 Some(previous) if previous.structural_eq(&state) && previous.scroll_eq(&state) => {
731 ScrollerSurfaceStateChange::Unchanged
732 }
733 Some(previous) if previous.structural_eq(&state) => {
734 ScrollerSurfaceStateChange::ScrollOnly
735 }
736 _ => ScrollerSurfaceStateChange::Structural,
737 };
738 if change != ScrollerSurfaceStateChange::Unchanged {
739 states.insert(id, state);
740 }
741 change
742 };
743
744 match change {
745 ScrollerSurfaceStateChange::Unchanged => {}
746 ScrollerSurfaceStateChange::ScrollOnly => {
747 self.mark_occlusion_presentation_dirty(id);
748 self.invalidate_viewport_presentation();
749 self.mark_layer_canvas_plans_dirty();
750 self.mark_scroller_content_layers_dirty(id);
751 }
752 ScrollerSurfaceStateChange::Structural => {
753 self.invalidate_viewport_presentation();
754 self.mark_layer_canvas_plans_dirty();
755 self.mark_occlusion_dirty();
756 }
757 }
758
759 change
760 }
761
762 pub fn update_scroller_surface_scroll(
765 &self,
766 id: u32,
767 scroll_x: f64,
768 scroll_y: f64,
769 presentation_scroll_x: f64,
770 presentation_scroll_y: f64,
771 ) -> ScrollerSurfaceStateChange {
772 let change = {
773 let mut states = borrow_mut!(self.scroller_surface_states);
774 let Some(state) = states.get_mut(&id) else {
775 return ScrollerSurfaceStateChange::Unchanged;
776 };
777 let next = ScrollerSurfaceState {
778 scroll_x,
779 scroll_y,
780 presentation_scroll_x,
781 presentation_scroll_y,
782 ..*state
783 };
784 let change = if state.scroll_eq(&next) {
785 ScrollerSurfaceStateChange::Unchanged
786 } else {
787 ScrollerSurfaceStateChange::ScrollOnly
788 };
789 if change != ScrollerSurfaceStateChange::Unchanged {
790 *state = next;
791 }
792 change
793 };
794
795 if change == ScrollerSurfaceStateChange::ScrollOnly {
796 self.mark_occlusion_presentation_dirty(id);
797 self.invalidate_viewport_presentation();
798 self.mark_layer_canvas_plans_dirty();
799 }
800
801 change
802 }
803
804 pub fn remove_scroller_surface_state(&self, id: u32) {
806 borrow_mut!(self.scroller_surface_states).remove(&id);
807 self.invalidate_viewport_presentation();
808 self.mark_layer_canvas_plans_dirty();
809 self.mark_occlusion_dirty();
810 }
811
812 pub fn get_scroller_surface_state(&self, id: u32) -> Option<ScrollerSurfaceState> {
814 borrow!(self.scroller_surface_states).get(&id).cloned()
815 }
816
817 pub fn get_scroller_surface_scroll(&self, id: u32) -> Option<(f64, f64)> {
820 borrow!(self.scroller_surface_states).get(&id).map(|state| {
821 let scroll_x = if state.presentation_scroll_x.is_finite() {
822 state.presentation_scroll_x
823 } else {
824 state.scroll_x
825 };
826 let scroll_y = if state.presentation_scroll_y.is_finite() {
827 state.presentation_scroll_y
828 } else {
829 state.scroll_y
830 };
831 (scroll_x, scroll_y)
832 })
833 }
834
835 pub(crate) fn scroller_content_presentation_transform(&self, node: &ExpandedNode) -> Affine {
836 fn clamp_offset(value: f64, content: f64, viewport: f64) -> f64 {
837 if content <= viewport || !value.is_finite() {
838 return 0.0;
839 }
840 value.max(0.0).min((content - viewport).max(0.0))
841 }
842
843 let root_delegates_to_page_scroll = self.get_root_scroller_id() == Some(node.id.to_u32())
844 && self.get_visual_viewport_state().is_some();
845 if root_delegates_to_page_scroll {
846 return Affine::IDENTITY;
847 }
848
849 let instance_node = borrow!(node.instance_node);
850 let (scroll_x, scroll_y) = if self.get_root_scroller_id() == Some(node.id.to_u32()) {
851 if let Some(visual) = self.get_visual_viewport_state() {
852 let visual_x = visual.page_scroll_x + visual.offset_x;
853 let visual_y = visual.page_scroll_y + visual.offset_y;
854 if visual_x.is_finite() && visual_y.is_finite() {
855 if let Some(state) = self.get_scroller_surface_state(node.id.to_u32()) {
856 let viewport_width = if visual.width.is_finite() {
857 visual.width
858 } else {
859 state.viewport_width
860 };
861 let viewport_height = if visual.height.is_finite() {
862 visual.height
863 } else {
864 state.viewport_height
865 };
866 (
867 clamp_offset(visual_x, state.content_width, viewport_width),
868 clamp_offset(visual_y, state.content_height, viewport_height),
869 )
870 } else {
871 (visual_x, visual_y)
872 }
873 } else {
874 self.get_scroller_surface_scroll(node.id.to_u32())
875 .or_else(|| instance_node.resolve_scroll_offset(node))
876 .unwrap_or((0.0, 0.0))
877 }
878 } else {
879 self.get_scroller_surface_scroll(node.id.to_u32())
880 .or_else(|| instance_node.resolve_scroll_offset(node))
881 .unwrap_or((0.0, 0.0))
882 }
883 } else {
884 self.get_scroller_surface_scroll(node.id.to_u32())
885 .or_else(|| instance_node.resolve_scroll_offset(node))
886 .unwrap_or((0.0, 0.0))
887 };
888
889 if scroll_x.abs() <= f64::EPSILON && scroll_y.abs() <= f64::EPSILON {
890 return Affine::IDENTITY;
891 }
892
893 let world_transform = Affine::from(node.transform_and_bounds.get().transform);
894 let inverse_world = Affine::from(node.transform_and_bounds.get().transform.inverse());
895 world_transform * Affine::translate((-scroll_x, -scroll_y)) * inverse_world
896 }
897
898 pub(crate) fn presentation_scroll_transform_for_node(&self, node: &ExpandedNode) -> Affine {
902 let mut ancestors = Vec::new();
903 let mut current = node.render_parent_node();
904 while let Some(parent) = current {
905 current = parent.render_parent_node();
906 ancestors.push(parent);
907 }
908 ancestors.reverse();
909
910 ancestors
911 .into_iter()
912 .filter(|ancestor| borrow!(ancestor.instance_node).scrolls_content(ancestor))
913 .fold(Affine::IDENTITY, |transform, scroller| {
914 transform * self.scroller_content_presentation_transform(&scroller)
915 })
916 }
917
918 pub fn clear_layer_scroller_owners(&self) {
920 borrow_mut!(self.layer_scroller_owners).clear();
921 self.mark_layer_canvas_plans_dirty();
922 }
923
924 pub fn register_layer_scroller_owner(
926 &self,
927 layer_id: usize,
928 scroller_id: ExpandedNodeIdentifier,
929 ) {
930 borrow_mut!(self.layer_scroller_owners).insert(layer_id, scroller_id);
931 self.mark_layer_canvas_plans_dirty();
932 }
933
934 pub fn get_layer_scroller_owner(&self, layer_id: usize) -> Option<ExpandedNodeIdentifier> {
936 borrow!(self.layer_scroller_owners).get(&layer_id).copied()
937 }
938
939 fn mark_scroller_content_layers_dirty(&self, scroller_id: u32) {
940 let layers = borrow!(self.layer_scroller_owners)
941 .iter()
942 .filter_map(|(layer, owner)| (owner.to_u32() == scroller_id).then_some(*layer))
943 .collect::<Vec<_>>();
944 for layer in layers {
945 self.set_canvas_dirty(layer);
946 }
947 }
948
949 pub fn set_root_scroller_id(&self, id: Option<u32>) {
951 self.root_scroller_id.set(id);
952 self.invalidate_viewport_presentation();
953 self.mark_layer_canvas_plans_dirty();
954 self.mark_occlusion_dirty();
955 }
956
957 pub fn get_root_scroller_id(&self) -> Option<u32> {
959 self.root_scroller_id.get()
960 }
961
962 pub fn safe_area_insets(&self) -> LocalProperty<SafeAreaInsets> {
964 self.safe_area_insets.clone()
965 }
966
967 pub fn set_safe_area_insets(&self, insets: SafeAreaInsets) {
969 let valid = |value: f64| {
970 if value.is_finite() {
971 value.max(0.0)
972 } else {
973 0.0
974 }
975 };
976 self.safe_area_insets.set_if_neq(SafeAreaInsets {
977 top: valid(insets.top),
978 right: valid(insets.right),
979 bottom: valid(insets.bottom),
980 left: valid(insets.left),
981 });
982 }
983
984 pub fn set_visual_viewport_state(&self, state: VisualViewportState) {
986 self.visual_viewport_state.set(Some(state));
987 self.invalidate_viewport_presentation();
988 self.mark_layer_canvas_plans_dirty();
989 self.mark_occlusion_dirty();
990 }
991
992 pub fn clear_visual_viewport_state(&self) {
994 self.visual_viewport_state.set(None);
995 self.invalidate_viewport_presentation();
996 self.mark_layer_canvas_plans_dirty();
997 self.mark_occlusion_dirty();
998 }
999
1000 pub fn get_visual_viewport_state(&self) -> Option<VisualViewportState> {
1002 self.visual_viewport_state.get()
1003 }
1004
1005 pub fn resize_canvas_layers_to(&self, id: usize) {
1007 let mut dirty_canvases = borrow_mut!(self.dirty_canvases);
1008 let old_len = dirty_canvases.len();
1009 dirty_canvases.resize(id, false);
1010 for dirty in dirty_canvases.iter_mut().skip(old_len) {
1011 *dirty = true;
1012 }
1013 if old_len != id {
1014 self.mark_layer_canvas_plans_dirty();
1015 }
1016 }
1017
1018 pub fn mark_layer_canvas_plans_dirty(&self) {
1019 let next = self.layer_canvas_plan_generation.get().wrapping_add(1);
1022 self.layer_canvas_plan_generation.set(next.max(1));
1023 }
1024
1025 pub fn layer_canvas_plan_generation(&self) -> u32 {
1026 self.layer_canvas_plan_generation.get()
1027 }
1028
1029 pub fn set_canvas_drawable_layers(&self, layers: HashSet<usize>) {
1031 let mut current = borrow_mut!(self.canvas_drawable_layers);
1032 if *current == layers {
1033 return;
1034 }
1035 *current = layers;
1036 self.mark_layer_canvas_plans_dirty();
1037 }
1038
1039 pub fn layer_has_canvas_drawables(&self, layer: usize) -> bool {
1041 borrow!(self.canvas_drawable_layers).contains(&layer)
1042 }
1043
1044 pub fn clear_all_dirty_canvases(&self) {
1046 let mut dirty_canvases = borrow_mut!(self.dirty_canvases);
1047 for v in dirty_canvases.iter_mut() {
1048 *v = false;
1049 }
1050 }
1051
1052 pub fn set_canvas_dirty(&self, id: usize) {
1054 let mut dirty_canvases = borrow_mut!(self.dirty_canvases);
1055 if let Some(v) = dirty_canvases.get_mut(id) {
1056 *v = true;
1057 }
1058 }
1059
1060 pub fn is_canvas_dirty(&self, id: &usize) -> bool {
1062 *borrow!(self.dirty_canvases).get(*id).unwrap_or(&true)
1063 }
1064
1065 pub fn dirty_canvas_layers(&self) -> Vec<usize> {
1066 borrow!(self.dirty_canvases)
1067 .iter()
1068 .enumerate()
1069 .filter_map(|(index, dirty)| dirty.then_some(index))
1070 .collect()
1071 }
1072
1073 pub fn has_dirty_canvas_layers(&self) -> bool {
1074 borrow!(self.dirty_canvases).iter().any(|dirty| *dirty)
1075 }
1076
1077 pub fn has_dirty_canvas_nodes(&self) -> bool {
1078 !borrow!(self.dirty_canvas_nodes).is_empty()
1079 }
1080
1081 pub fn has_canvas_node_removals(&self) -> bool {
1082 !borrow!(self.removed_canvas_nodes).is_empty()
1083 }
1084
1085 pub fn has_canvas_render_work(&self) -> bool {
1086 self.has_dirty_canvas_layers()
1087 || self.has_canvas_node_removals()
1088 || self.scene_lighting_dirty.get()
1089 }
1090
1091 pub fn mark_scene_lighting_dirty(&self) {
1094 self.scene_lighting_dirty.set(true);
1095 }
1096
1097 pub(crate) fn take_scene_lighting_dirty(&self) -> bool {
1098 self.scene_lighting_dirty.replace(false)
1099 }
1100
1101 pub fn mark_occlusion_dirty(&self) {
1102 self.occlusion_dirty.set(true);
1103 self.occlusion_state.borrow_mut().structural = true;
1104 borrow_mut!(self.layer_light_scopes).clear();
1107 if !borrow!(self.scene_lighting_nodes).is_empty()
1108 || !borrow!(self.canvas_node_light_masks).is_empty()
1109 {
1110 self.set_all_canvases_dirty();
1111 }
1112 }
1113
1114 pub fn mark_node_occlusion_dirty(&self, mut id: u32) {
1117 loop {
1118 borrow_mut!(self.scene_geometry).invalidate_existing(id);
1122 let owner = self
1123 .get_expanded_node_by_eid(ExpandedNodeIdentifier(id))
1124 .and_then(|node| node.render_source_owner());
1125 let Some(owner) = owner else {
1126 break;
1127 };
1128 id = owner.to_u32();
1129 }
1130 self.occlusion_dirty.set(true);
1131 self.occlusion_state.borrow_mut().dirty.insert(id);
1132 }
1133
1134 fn mark_occlusion_presentation_dirty(&self, id: u32) {
1135 self.occlusion_dirty.set(true);
1136 self.occlusion_state.borrow_mut().scrolled.insert(id);
1137 }
1138
1139 pub fn occlusion_stats(&self) -> crate::engine::occlusion::OcclusionStats {
1141 self.occlusion_state.borrow().stats
1142 }
1143
1144 pub fn take_occlusion_dirty(&self) -> bool {
1145 let dirty = self.occlusion_dirty.get();
1146 self.occlusion_dirty.set(false);
1147 dirty
1148 }
1149
1150 pub fn set_all_canvases_dirty(&self) {
1151 let mut dirty_canvases = borrow_mut!(self.dirty_canvases);
1152 for v in dirty_canvases.iter_mut() {
1153 *v = true;
1154 }
1155 }
1156
1157 pub fn mark_canvas_node_dirty(&self, id: ExpandedNodeIdentifier) {
1158 borrow_mut!(self.scene_geometry).invalidate(id.to_u32());
1159 let owner = self
1160 .get_expanded_node_by_eid(id)
1161 .and_then(|node| node.render_source_owner())
1162 .and_then(|id| self.get_expanded_node_by_eid(id));
1163 if let Some(owner) = owner {
1164 owner.changed_listener.invalidate();
1165 return;
1166 }
1167 borrow_mut!(self.dirty_canvas_nodes).insert(id);
1168 #[cfg(feature = "shaders")]
1169 {
1170 let mut parent = self
1173 .get_expanded_node_by_eid(id)
1174 .and_then(|n| n.render_parent_node());
1175 while let Some(node) = parent {
1176 if node.shader_effect_bound.get() {
1177 borrow_mut!(self.dirty_canvas_nodes).insert(node.id);
1178 for ids in borrow_mut!(self.targeted_canvas_replay_node_ids).values_mut() {
1179 ids.remove(&node.id.to_u32());
1180 }
1181 }
1182 parent = node.render_parent_node();
1183 }
1184 }
1185
1186 for ids in borrow_mut!(self.targeted_canvas_replay_node_ids).values_mut() {
1189 ids.remove(&id.to_u32());
1190 }
1191 }
1192
1193 pub fn clear_canvas_node_dirty(&self, id: &ExpandedNodeIdentifier) {
1194 borrow_mut!(self.dirty_canvas_nodes).remove(id);
1195 }
1196
1197 pub fn is_canvas_node_dirty(&self, id: &ExpandedNodeIdentifier) -> bool {
1198 borrow!(self.dirty_canvas_nodes).contains(id)
1199 }
1200
1201 pub fn dirty_canvas_node_ids(&self) -> Vec<ExpandedNodeIdentifier> {
1202 borrow!(self.dirty_canvas_nodes).iter().copied().collect()
1203 }
1204
1205 pub fn mark_canvas_nodes_on_layer_dirty(&self, layer: usize) {
1206 if let Some(ids) = borrow_mut!(self.targeted_canvas_replay_node_ids).get_mut(&layer) {
1207 ids.clear();
1208 }
1209 let node_cache = borrow!(self.node_cache);
1210 let dirty_nodes = &mut *borrow_mut!(self.dirty_canvas_nodes);
1211 for node in node_cache.eid_to_node.values() {
1212 if !node.is_render_source()
1213 && node.occlusion.get().render_layer_id == layer
1214 && borrow!(node.instance_node).base().flags().layer == crate::api::Layer::Canvas
1215 {
1216 dirty_nodes.insert(node.id);
1217 }
1218 }
1219 }
1220
1221 pub fn canvas_node_light_mask(&self, id: ExpandedNodeIdentifier) -> u32 {
1223 borrow!(self.canvas_node_light_masks)
1224 .get(&id)
1225 .copied()
1226 .unwrap_or(0)
1227 }
1228
1229 pub fn collect_scene_lighting_for_layer(&self, layer: usize) -> SceneLighting {
1235 if borrow!(self.scene_lighting_nodes).is_empty()
1236 && borrow!(self.canvas_node_light_masks).is_empty()
1237 {
1238 borrow_mut!(self.lighting_overflow_counts).remove(&layer);
1239 return SceneLighting::with_default_ambient(Vec::new());
1240 }
1241 let layer_to_root_transforms = self.layer_to_root_transforms(layer);
1242 let root_to_target = layer_to_root_transforms[&layer].inverse();
1243 let node_cache = borrow!(self.node_cache);
1244 let mut scoped_lights = Vec::new();
1245 let mut topmost_ambient = None;
1246
1247 for id in borrow!(self.scene_lighting_nodes).iter() {
1248 let Some(node) = node_cache.eid_to_node.get(id) else {
1249 continue;
1250 };
1251 if node.is_render_source() {
1252 continue;
1253 }
1254 let occlusion = node.occlusion.get();
1255 let source_layer = occlusion.render_layer_id;
1256 let Some(source_to_root) = layer_to_root_transforms.get(&source_layer) else {
1257 continue;
1258 };
1259
1260 let (light, ambient) = {
1261 let instance_node = borrow!(node.instance_node);
1262 (
1263 instance_node.resolve_scene_light(node, self),
1264 instance_node.resolve_scene_ambient_light(node, self),
1265 )
1266 };
1267 if let Some(light) = light {
1268 if light.enabled {
1269 scoped_lights.push((
1270 occlusion.z_index,
1271 node.id,
1272 Self::nearest_light_frame(node),
1273 transform_scene_light_for_layer(light, *source_to_root, root_to_target),
1274 ));
1275 }
1276 }
1277 if let Some(ambient) = ambient {
1278 let should_replace = topmost_ambient
1279 .as_ref()
1280 .map(|(z_index, id, _)| (occlusion.z_index, node.id) >= (*z_index, *id))
1281 .unwrap_or(true);
1282 if should_replace {
1283 topmost_ambient = Some((occlusion.z_index, node.id, ambient));
1284 }
1285 }
1286 }
1287
1288 scoped_lights
1289 .sort_by(|left, right| right.0.cmp(&left.0).then_with(|| left.1.cmp(&right.1)));
1290 let overflow = scoped_lights
1291 .len()
1292 .saturating_sub(SceneLighting::MAX_LIGHTS);
1293 scoped_lights.truncate(SceneLighting::MAX_LIGHTS);
1294
1295 {
1296 let mut overflow_counts = borrow_mut!(self.lighting_overflow_counts);
1297 if overflow == 0 {
1298 overflow_counts.remove(&layer);
1299 } else if overflow_counts.insert(layer, overflow) != Some(overflow) {
1300 log::warn!(
1301 "canvas layer {layer} has more than {} enabled lights; omitting {overflow}",
1302 SceneLighting::MAX_LIGHTS
1303 );
1304 }
1305 }
1306
1307 let scopes = scoped_lights
1308 .iter()
1309 .map(|(_, id, frame, _)| (*id, *frame))
1310 .collect::<Vec<_>>();
1311 let scopes_changed = borrow!(self.layer_light_scopes).get(&layer) != Some(&scopes);
1312 if scopes_changed {
1313 let mut masks = borrow_mut!(self.canvas_node_light_masks);
1314 if !scopes.is_empty() || !masks.is_empty() {
1317 let mut dirty_nodes = borrow_mut!(self.dirty_canvas_nodes);
1318 for node in node_cache.eid_to_node.values() {
1319 #[cfg(test)]
1320 self.lighting_mask_node_visits
1321 .set(self.lighting_mask_node_visits.get() + 1);
1322 if node.occlusion.get().render_layer_id != layer
1323 || borrow!(node.instance_node).base().flags().layer
1324 != crate::api::Layer::Canvas
1325 {
1326 continue;
1327 }
1328 let mask = if scopes.is_empty() {
1329 0
1330 } else {
1331 let ancestry = Self::light_frame_ancestry(node);
1332 scopes
1333 .iter()
1334 .enumerate()
1335 .fold(0u32, |mask, (slot, (_, owner))| {
1336 if light_reaches_frame_ancestry(owner.as_ref(), &ancestry) {
1337 mask | (1u32 << slot)
1338 } else {
1339 mask
1340 }
1341 })
1342 };
1343 let previous = if mask == 0 {
1344 masks.remove(&node.id)
1345 } else {
1346 masks.insert(node.id, mask)
1347 }
1348 .unwrap_or(0);
1349 if previous != mask {
1350 self.set_canvas_dirty(layer);
1351 dirty_nodes.insert(node.id);
1352 if let Some(ids) =
1353 borrow_mut!(self.targeted_canvas_replay_node_ids).get_mut(&layer)
1354 {
1355 ids.remove(&node.id.to_u32());
1356 }
1357 }
1358 }
1359 }
1360 borrow_mut!(self.layer_light_scopes).insert(layer, scopes);
1361 }
1362 drop(node_cache);
1363
1364 let lights = scoped_lights
1365 .into_iter()
1366 .map(|(_, _, _, light)| light)
1367 .collect::<Vec<_>>();
1368
1369 if let Some((_, _, ambient)) = topmost_ambient {
1370 SceneLighting {
1371 active: true,
1372 ambient_is_authored: true,
1373 ambient,
1374 lights,
1375 }
1376 } else {
1377 SceneLighting::with_default_ambient(lights)
1378 }
1379 }
1380
1381 fn nearest_light_frame(node: &ExpandedNode) -> Option<ExpandedNodeIdentifier> {
1382 let mut current = node.render_parent_node();
1383 while let Some(parent) = current {
1384 if borrow!(parent.instance_node).establishes_light_frame() {
1385 return Some(parent.id);
1386 }
1387 current = parent.render_parent_node();
1388 }
1389 None
1390 }
1391
1392 fn light_frame_ancestry(node: &ExpandedNode) -> HashSet<ExpandedNodeIdentifier> {
1393 let mut frames = HashSet::new();
1394 let mut current = node.render_parent_node();
1395 while let Some(parent) = current {
1396 if borrow!(parent.instance_node).establishes_light_frame() {
1397 frames.insert(parent.id);
1398 }
1399 current = parent.render_parent_node();
1400 }
1401 frames
1402 }
1403
1404 fn layer_to_root_transforms(&self, layer: usize) -> HashMap<usize, Affine> {
1405 let mut transforms = HashMap::new();
1406 transforms.insert(layer, self.layer_to_root_transform(layer));
1407
1408 let mut current_layer = layer;
1409 let mut visited = HashSet::new();
1410 while let Some(owner_id) = self.get_layer_scroller_owner(current_layer) {
1411 if !visited.insert(current_layer) {
1412 break;
1413 }
1414 let Some(owner_node) = self.get_expanded_node_by_eid(owner_id) else {
1415 break;
1416 };
1417 let owner_layer = owner_node.occlusion.get().render_layer_id;
1418 transforms
1419 .entry(owner_layer)
1420 .or_insert_with(|| self.layer_to_root_transform(owner_layer));
1421 if owner_layer == current_layer {
1422 break;
1423 }
1424 current_layer = owner_layer;
1425 }
1426
1427 transforms
1428 }
1429
1430 fn layer_to_root_transform(&self, layer: usize) -> Affine {
1431 let mut transform = Affine::IDENTITY;
1432 let mut current_layer = layer;
1433 let mut visited = HashSet::new();
1434
1435 while let Some(owner_id) = self.get_layer_scroller_owner(current_layer) {
1436 if !visited.insert(current_layer) {
1437 break;
1438 }
1439 let Some(owner_node) = self.get_expanded_node_by_eid(owner_id) else {
1440 break;
1441 };
1442 let owner_layer = owner_node.occlusion.get().render_layer_id;
1443 let owner_transform = self.canvas_surface_transform_for_node(&owner_node);
1444 let (scroll_x, scroll_y) = self
1445 .get_scroller_surface_scroll(owner_node.id.to_u32())
1446 .or_else(|| borrow!(owner_node.instance_node).resolve_scroll_offset(&owner_node))
1447 .unwrap_or((0.0, 0.0));
1448 transform = owner_transform * Affine::translate((-scroll_x, -scroll_y)) * transform;
1449
1450 if owner_layer == current_layer {
1451 break;
1452 }
1453 current_layer = owner_layer;
1454 }
1455
1456 transform
1457 }
1458
1459 pub fn canvas_surface_transform_for_node(&self, node: &ExpandedNode) -> Affine {
1464 let transform = Affine::from(node.transform_and_bounds.get().transform);
1465 if node.is_render_source() {
1466 return transform;
1467 }
1468 let own_layer = node.occlusion.get().render_layer_id;
1469 self.get_layer_scroller_owner(own_layer)
1473 .and_then(|owner| self.get_expanded_node_by_eid(owner))
1474 .map_or(transform, |owner| {
1475 Affine::from(owner.transform_and_bounds.get().transform.inverse()) * transform
1476 })
1477 }
1478
1479 pub(crate) fn invalidate_scene_geometry(&self, id: u32) {
1480 borrow_mut!(self.scene_geometry).invalidate(id);
1481 }
1482
1483 pub(crate) fn remove_observation_geometry(&self, id: u32) {
1484 if let Some(node) = self.get_expanded_node_by_eid(ExpandedNodeIdentifier(id)) {
1485 let instance = borrow!(node.instance_node);
1486 if instance.base().flags().layer != crate::api::Layer::DontCare
1487 || instance.materializes_native_surface(&node)
1488 {
1489 return;
1490 }
1491 }
1492 borrow_mut!(self.scene_geometry).remove(id);
1493 }
1494
1495 pub fn prepare_scene_geometry(&self) {
1497 let dirty = borrow_mut!(self.scene_geometry).take_dirty();
1498 for id in dirty {
1499 if borrow!(self.scene_geometry).get(id).is_some() {
1501 continue;
1502 }
1503 if let Some(node) = self.get_expanded_node_by_eid(ExpandedNodeIdentifier(id)) {
1504 if borrow!(node.instance_node).base().flags().layer != crate::api::Layer::DontCare
1505 || borrow!(node.instance_node).materializes_native_surface(&node)
1506 || self.viewport_dependency(id)
1507 {
1508 self.prepare_canvas_geometry(&node);
1509 } else {
1510 borrow_mut!(self.scene_geometry).remove(id);
1511 }
1512 }
1513 }
1514 }
1515
1516 fn prepare_canvas_geometry(
1517 &self,
1518 node: &ExpandedNode,
1519 ) -> Rc<crate::scene_geometry::PreparedCanvasGeometry> {
1520 let tab = node.transform_and_bounds.get();
1521 let local = borrow!(node.instance_node).prepare_canvas_geometry(node);
1522 let mut prepared = crate::scene_geometry::PreparedCanvasGeometry::new(
1523 node.occlusion.get().render_layer_id,
1524 tab.bounds,
1525 self.canvas_surface_transform_for_node(node),
1526 local,
1527 );
1528 prepared.world_transform = Affine::from(tab.transform);
1529 prepared.is_canvas = !node.is_render_source()
1532 && borrow!(node.instance_node).base().flags().layer == crate::api::Layer::Canvas;
1533 prepared.is_native = !node.is_render_source()
1534 && (matches!(
1535 borrow!(node.instance_node).base().flags().layer,
1536 crate::api::Layer::Native | crate::api::Layer::NativeNonOccluding
1537 ) || borrow!(node.instance_node).materializes_native_surface(node));
1538 borrow_mut!(self.scene_geometry).insert(node.id.to_u32(), prepared)
1539 }
1540
1541 pub fn canvas_geometry_for_node(
1543 &self,
1544 node: &ExpandedNode,
1545 ) -> Rc<crate::scene_geometry::PreparedCanvasGeometry> {
1546 let cached = borrow!(self.scene_geometry).get(node.id.to_u32());
1547 cached.unwrap_or_else(|| self.prepare_canvas_geometry(node))
1548 }
1549
1550 pub fn scene_geometry_stats(&self) -> crate::scene_geometry::SceneGeometryStats {
1552 borrow!(self.scene_geometry).stats
1553 }
1554
1555 pub fn canvas_nodes_intersecting(
1558 &self,
1559 layer: usize,
1560 regions: &[kurbo::Rect],
1561 ) -> Option<Vec<u32>> {
1562 self.prepare_scene_geometry();
1563 borrow_mut!(self.scene_geometry).query(layer, regions)
1564 }
1565
1566 pub(crate) fn compositing_nodes_intersecting(
1567 &self,
1568 layer: usize,
1569 regions: &[kurbo::Rect],
1570 native: bool,
1571 ) -> Vec<u32> {
1572 borrow_mut!(self.scene_geometry).query_exact(layer, regions, native)
1573 }
1574
1575 pub fn request_canvas_replay(&self, update: pax_runtime_api::ReplayCanvasLayerUpdate) {
1577 if let Some(ids) = update
1578 .regions
1579 .as_deref()
1580 .and_then(|regions| self.canvas_nodes_intersecting(update.layer, regions))
1581 {
1582 self.mark_targeted_canvas_replay_nodes(update.layer, &ids);
1583 self.mark_canvas_nodes_on_layer_dirty_by_id(update.layer, &ids);
1584 } else {
1585 self.mark_canvas_nodes_on_layer_dirty(update.layer);
1586 }
1587 self.set_canvas_dirty(update.layer);
1588 }
1589
1590 pub fn mark_canvas_nodes_on_layer_dirty_by_id(&self, layer: usize, node_ids: &[u32]) {
1591 let node_cache = borrow!(self.node_cache);
1592 let dirty_nodes = &mut *borrow_mut!(self.dirty_canvas_nodes);
1593 for node_id in node_ids {
1594 let id = ExpandedNodeIdentifier(*node_id);
1595 let Some(node) = node_cache.eid_to_node.get(&id) else {
1596 continue;
1597 };
1598 if node.occlusion.get().render_layer_id == layer
1599 && borrow!(node.instance_node).base().flags().layer == crate::api::Layer::Canvas
1600 {
1601 dirty_nodes.insert(id);
1602 }
1603 }
1604 }
1605
1606 pub fn mark_targeted_canvas_replay_nodes(&self, layer: usize, node_ids: &[u32]) {
1607 let mut targeted = borrow_mut!(self.targeted_canvas_replay_node_ids);
1608 let layer_nodes = targeted.entry(layer).or_default();
1609 let dirty = borrow!(self.dirty_canvas_nodes);
1610 for id in node_ids {
1614 if !dirty.contains(&ExpandedNodeIdentifier(*id)) {
1615 layer_nodes.insert(*id);
1616 }
1617 }
1618 }
1619
1620 pub fn take_targeted_canvas_replay_node_ids(&self) -> HashMap<usize, HashSet<u32>> {
1621 let targeted = &mut *borrow_mut!(self.targeted_canvas_replay_node_ids);
1622 std::mem::take(targeted)
1623 }
1624
1625 pub fn mark_all_canvas_nodes_dirty(&self) {
1626 for ids in borrow_mut!(self.targeted_canvas_replay_node_ids).values_mut() {
1627 ids.clear();
1628 }
1629 let node_cache = borrow!(self.node_cache);
1630 let dirty_nodes = &mut *borrow_mut!(self.dirty_canvas_nodes);
1631 for node in node_cache.eid_to_node.values() {
1632 if !node.is_render_source()
1633 && borrow!(node.instance_node).base().flags().layer == crate::api::Layer::Canvas
1634 {
1635 dirty_nodes.insert(node.id);
1636 }
1637 }
1638 }
1639
1640 pub fn enqueue_canvas_node_removal(&self, layer: usize, node_id: u32) {
1641 borrow_mut!(self.removed_canvas_nodes).push((layer, node_id));
1642 }
1643
1644 pub fn take_canvas_node_removals(&self) -> Vec<(usize, u32)> {
1645 let mut removals = borrow_mut!(self.removed_canvas_nodes);
1646 std::mem::take(&mut *removals)
1647 }
1648
1649 pub fn get_expanded_nodes_by_id(&self, id: &str) -> Vec<Rc<ExpandedNode>> {
1651 borrow!(self.node_cache)
1653 .eid_to_node
1654 .values()
1655 .filter(|val| {
1656 let common_props = val.get_common_properties();
1657 let common_props = borrow!(common_props);
1658 common_props.id.get().is_some_and(|i| i == id)
1659 })
1660 .cloned()
1661 .collect()
1662 }
1663
1664 pub fn get_expanded_nodes_by_global_ids(
1666 &self,
1667 uni: &UniqueTemplateNodeIdentifier,
1668 ) -> Vec<Rc<ExpandedNode>> {
1669 let node_cache = borrow!(self.node_cache);
1670 node_cache
1671 .uni_to_eid
1672 .get(uni)
1673 .map(|eids| {
1674 let mut nodes = vec![];
1675 for e in eids {
1676 if let Some(node) = node_cache.eid_to_node.get(e) {
1677 nodes.push(Rc::clone(node));
1678 } else {
1679 log::warn!(
1680 "failed to find node in engine for expanded node identifier: {:?}",
1681 e
1682 );
1683 }
1684 }
1685 nodes
1686 })
1687 .unwrap_or_default()
1688 }
1689
1690 pub fn get_elements_beneath_ray(
1695 &self,
1696 root: Option<Rc<ExpandedNode>>,
1697 ray: Point2<Window>,
1698 limit_one: bool,
1699 mut accum: Vec<Rc<ExpandedNode>>,
1700 hit_invisible: bool,
1701 ) -> Vec<Rc<ExpandedNode>> {
1702 let Some(root_node) = root.or_else(|| borrow!(self.root_expanded_node).upgrade()) else {
1708 return accum;
1709 };
1710 let mut to_process = vec![(root_node, false, Affine::IDENTITY, false)];
1711 while let Some((node, clipped, active_scroll_transform, retained_for_exit)) =
1712 to_process.pop()
1713 {
1714 if node.is_render_source() {
1715 continue;
1716 }
1717 node.compute_flattened_projected_children();
1719 if !hit_invisible
1720 && (retained_for_exit || node.transition_phase.get() == TRANSITION_PHASE_EXIT)
1721 {
1722 continue;
1723 }
1724 let (scroll_transform, clips_content) = {
1727 let instance_node = borrow!(node.instance_node);
1728 let scrolls_content = instance_node.scrolls_content(&node);
1729 let clips_content = instance_node.clips_content(&node);
1730 drop(instance_node);
1731 (
1732 if scrolls_content {
1733 self.scroller_content_presentation_transform(&node)
1734 } else {
1735 Affine::IDENTITY
1736 },
1737 clips_content,
1738 )
1739 };
1740 let descendant_scroll_transform = active_scroll_transform * scroll_transform;
1741 let hit = node.ray_cast_test(active_scroll_transform.inverse() * ray);
1742 if hit && !clipped {
1743 if hit_invisible
1744 || !borrow!(node.instance_node)
1745 .base()
1746 .flags()
1747 .invisible_to_raycasting
1748 {
1749 if limit_one {
1753 return vec![node];
1754 }
1755 accum.push(Rc::clone(&node));
1756 }
1757 }
1758 let clipped = clipped || (!hit && clips_content);
1759 let retained_children = borrow!(node.exiting_children).clone();
1760 to_process.extend(
1761 node.children
1762 .get()
1763 .iter()
1764 .cloned()
1765 .map(|v| {
1766 let retained_for_exit = retained_children
1767 .iter()
1768 .any(|retained| Rc::ptr_eq(retained, &v));
1769 let cp = v.get_common_properties();
1770 let unclippable = borrow!(cp).unclippable.get().unwrap_or(false);
1771 (
1772 v,
1773 clipped && !unclippable,
1774 descendant_scroll_transform,
1775 retained_for_exit,
1776 )
1777 })
1778 .rev(),
1779 )
1780 }
1781 accum
1782 }
1783 pub fn get_topmost_element_beneath_ray(
1785 self: &Rc<Self>,
1786 ray: Point2<Window>,
1787 ) -> Option<Rc<ExpandedNode>> {
1788 let res = self.get_elements_beneath_ray(None, ray, true, vec![], false);
1789 let Some(new_topmost) = res
1790 .into_iter()
1791 .next()
1792 .or_else(|| borrow!(self.root_expanded_node).upgrade())
1793 else {
1794 *borrow_mut!(self.last_topmost_element) = Weak::new();
1795 return None;
1796 };
1797
1798 let last_topmost = borrow!(self.last_topmost_element).upgrade();
1802 if Some(new_topmost.id) != last_topmost.as_ref().map(|n| n.id) {
1803 let (leaving, entering) = find_template_paths_to_common_ancestor(
1804 &last_topmost,
1805 &Some(Rc::clone(&new_topmost)),
1806 );
1807 for leave in leaving {
1808 leave.dispatch_mouse_out(Event::new(MouseOut {}), &self.globals(), self);
1809 }
1810 for enter in entering {
1811 enter.dispatch_mouse_over(Event::new(MouseOver {}), &self.globals(), self);
1812 }
1813 *borrow_mut!(self.last_topmost_element) = Rc::downgrade(&new_topmost);
1814 }
1815 Some(new_topmost)
1816 }
1817
1818 pub fn gen_uid(&self) -> ExpandedNodeIdentifier {
1819 let val = self.next_uid.get();
1820 let next_val = ExpandedNodeIdentifier(val.0 + 1);
1821 self.next_uid.set(next_val);
1822 val
1823 }
1824
1825 pub fn enqueue_native_message(&self, message: NativeMessage) {
1826 borrow_mut!(self.messages).push(message)
1827 }
1828
1829 pub fn take_native_messages(&self) -> Vec<NativeMessage> {
1830 let mut messages = borrow_mut!(self.messages);
1831 std::mem::take(&mut *messages)
1832 }
1833
1834 pub fn globals(&self) -> Globals {
1835 borrow!(self.globals).clone()
1836 }
1837
1838 pub fn edit_globals(&self, f: impl Fn(&mut Globals)) {
1839 let mut globals = borrow_mut!(self.globals);
1840 f(&mut globals);
1841 }
1842
1843 pub fn queue_custom_event(&self, source_expanded_node: Rc<ExpandedNode>, name: &'static str) {
1844 let mut queued_custom_events = borrow_mut!(self.queued_custom_events);
1845 queued_custom_events.push((source_expanded_node, name));
1846 }
1847
1848 pub fn flush_custom_events(self: &Rc<Self>) -> Result<(), String> {
1849 let mut queued_custom_event = borrow_mut!(self.queued_custom_events);
1850 let to_flush: Vec<_> = std::mem::take(queued_custom_event.as_mut());
1851 for (target, ident) in to_flush {
1852 target.dispatch_custom_event(ident, self)?;
1853 }
1854 Ok(())
1855 }
1856
1857 #[cfg(feature = "designtime")]
1858 pub fn get_userland_root_expanded_node(&self) -> Option<Rc<ExpandedNode>> {
1859 borrow!(self.userland_root_expanded_node).clone()
1860 }
1861
1862 #[cfg(feature = "designtime")]
1863 pub fn get_userland_root_instance_node(&self) -> Option<Rc<dyn InstanceNode>> {
1864 borrow!(self.userland_frame_instance_node).clone()
1865 }
1866
1867 #[cfg(feature = "designtime")]
1868 pub fn set_userland_root_expanded_node(&self, root: Option<Rc<ExpandedNode>>) {
1869 *borrow_mut!(self.userland_root_expanded_node) = root;
1870 }
1871
1872 #[cfg(feature = "designtime")]
1873 pub fn set_userland_root_instance_node(&self, node: Option<Rc<dyn InstanceNode>>) {
1874 *borrow_mut!(self.userland_frame_instance_node) = node;
1875 }
1876
1877 pub fn get_root_expanded_node(&self) -> Option<Rc<ExpandedNode>> {
1878 borrow!(self.root_expanded_node).upgrade()
1879 }
1880
1881 pub fn queue_render(&self, expanded_node: Rc<ExpandedNode>) {
1882 borrow_mut!(self.queued_renders).push(expanded_node);
1883 }
1884
1885 pub fn recurse_flush_queued_renders(self: &Rc<RuntimeContext>, rcs: &mut dyn RenderContext) {
1886 while !borrow!(self.queued_renders).is_empty() {
1887 for n in std::mem::take(&mut *borrow_mut!(self.queued_renders)) {
1888 n.recurse_render(self, rcs);
1889 }
1890 }
1891 }
1892}
1893
1894fn find_template_paths_to_common_ancestor(
1895 last_topmost: &Option<Rc<ExpandedNode>>,
1896 new_topmost: &Option<Rc<ExpandedNode>>,
1897) -> (Vec<Rc<ExpandedNode>>, Vec<Rc<ExpandedNode>>) {
1898 let mut last_path = template_path_from_root(last_topmost);
1899 let mut new_path = template_path_from_root(new_topmost);
1900 let common_prefix_len = last_path
1901 .iter()
1902 .zip(new_path.iter())
1903 .take_while(|(last, new)| last.id == new.id)
1904 .count();
1905
1906 last_path.drain(0..common_prefix_len);
1908 new_path.drain(0..common_prefix_len);
1909
1910 (last_path, new_path)
1911}
1912
1913fn template_path_from_root(node: &Option<Rc<ExpandedNode>>) -> Vec<Rc<ExpandedNode>> {
1914 let mut path = Vec::new();
1915 let mut current = node.clone();
1916 while let Some(node) = current {
1917 path.push(Rc::clone(&node));
1918 current = node.template_parent.upgrade();
1919 }
1920 path.reverse();
1921 path
1922}
1923
1924pub struct RuntimePropertiesStackFrame {
1933 symbols_within_frame: HashMap<String, Variable>,
1934 parent: Option<Rc<RuntimePropertiesStackFrame>>,
1935}
1936
1937impl RuntimePropertiesStackFrame {
1938 pub fn new(symbols_within_frame: HashMap<String, Variable>) -> Rc<Self> {
1939 Rc::new(Self {
1940 symbols_within_frame,
1941 parent: None,
1942 })
1943 }
1944
1945 pub fn push(self: &Rc<Self>, symbols_within_frame: HashMap<String, Variable>) -> Rc<Self> {
1946 Rc::new(RuntimePropertiesStackFrame {
1947 symbols_within_frame,
1948 parent: Some(Rc::clone(self)),
1949 })
1950 }
1951
1952 pub fn pop(self: &Rc<Self>) -> Option<Rc<Self>> {
1953 self.parent.clone()
1954 }
1955
1956 pub fn resolve_symbol_as_variable(&self, symbol: &str) -> Option<Variable> {
1957 if let Some(e) = self.symbols_within_frame.get(&clean_symbol(symbol)) {
1958 Some(e.clone())
1959 } else {
1960 self.parent.as_ref()?.resolve_symbol_as_variable(symbol)
1961 }
1962 }
1963
1964 pub fn resolve_symbol_as_erased_property(&self, symbol: &str) -> Option<UntypedProperty> {
1965 if let Some(e) = self.symbols_within_frame.get(&clean_symbol(symbol)) {
1966 Some(e.clone().get_untyped_property().clone())
1967 } else {
1968 self.parent
1969 .as_ref()?
1970 .resolve_symbol_as_erased_property(symbol)
1971 }
1972 }
1973
1974 pub fn resolve_symbol(&self, symbol: &str) -> Option<Variable> {
1975 self.symbols_within_frame
1976 .get(&clean_symbol(symbol))
1977 .cloned()
1978 .or_else(|| self.parent.as_ref()?.resolve_symbol(symbol))
1979 }
1980}
1981
1982fn clean_symbol(symbol: &str) -> String {
1983 symbol.replace("self.", "").replace("this.", "")
1984}
1985
1986impl IdentifierResolver for RuntimePropertiesStackFrame {
1987 fn resolve(&self, name: String) -> Result<Variable, String> {
1988 self.resolve_symbol(&name)
1989 .ok_or_else(|| format!("Could not resolve symbol {}", name))
1990 }
1991}
1992
1993fn light_reaches_frame_ancestry(
1994 owner_frame: Option<&ExpandedNodeIdentifier>,
1995 frame_ancestry: &HashSet<ExpandedNodeIdentifier>,
1996) -> bool {
1997 owner_frame.is_none_or(|frame| frame_ancestry.contains(frame))
1998}
1999
2000pub struct ExpressionContext {
2004 pub stack_frame: Rc<RuntimePropertiesStackFrame>,
2005}
2006
2007#[cfg(test)]
2008mod light_scope_tests {
2009 use super::*;
2010 use crate::api::math::Transform2;
2011 use crate::{
2012 BaseInstance, CommonPropertiesInit, ComponentInstance, InstanceFlags, InstanceNode,
2013 InstantiationArgs, PropertiesInit, PropertiesScopeInit, RouteLocation, TransformAndBounds,
2014 };
2015 use pax_runtime_api::pax_value::{PaxAny, PaxValue};
2016 use pax_runtime_api::{Layer, Platform, SceneAmbientLight, TargetInfo, OS};
2017 use std::fmt;
2018
2019 #[derive(Clone, Copy)]
2020 enum TestLightingRole {
2021 Frame,
2022 Light(f64),
2023 Ambient(f64),
2024 Surface,
2025 }
2026
2027 struct TestLightingNode {
2028 base: BaseInstance,
2029 role: TestLightingRole,
2030 enabled: Cell<bool>,
2031 intensity_multiplier: Cell<f64>,
2032 resolutions: Cell<usize>,
2033 }
2034
2035 impl TestLightingNode {
2036 fn new(
2037 role: TestLightingRole,
2038 children: Vec<Rc<dyn InstanceNode>>,
2039 ) -> Rc<TestLightingNode> {
2040 Rc::new(Self {
2041 base: BaseInstance::new(
2042 node_args(children),
2043 InstanceFlags {
2044 invisible_to_slot: false,
2045 invisible_to_raycasting: true,
2046 layer: match role {
2047 TestLightingRole::Frame => Layer::DontCare,
2048 TestLightingRole::Light(_)
2049 | TestLightingRole::Ambient(_)
2050 | TestLightingRole::Surface => Layer::Canvas,
2051 },
2052 is_component: false,
2053 is_slot: false,
2054 },
2055 ),
2056 role,
2057 enabled: Cell::new(true),
2058 intensity_multiplier: Cell::new(1.0),
2059 resolutions: Cell::new(0),
2060 })
2061 }
2062
2063 fn as_instance(self: &Rc<Self>) -> Rc<dyn InstanceNode> {
2064 self.clone()
2065 }
2066
2067 fn set_enabled(&self, enabled: bool) {
2068 self.enabled.set(enabled);
2069 }
2070 }
2071
2072 impl InstanceNode for TestLightingNode {
2073 fn has_scene_lighting(&self) -> bool {
2074 matches!(
2075 self.role,
2076 TestLightingRole::Light(_) | TestLightingRole::Ambient(_)
2077 )
2078 }
2079
2080 fn instantiate(args: InstantiationArgs) -> Rc<Self>
2081 where
2082 Self: Sized,
2083 {
2084 Rc::new(Self {
2085 base: BaseInstance::new(
2086 args,
2087 InstanceFlags {
2088 invisible_to_slot: false,
2089 invisible_to_raycasting: true,
2090 layer: Layer::Canvas,
2091 is_component: false,
2092 is_slot: false,
2093 },
2094 ),
2095 role: TestLightingRole::Surface,
2096 enabled: Cell::new(true),
2097 intensity_multiplier: Cell::new(1.0),
2098 resolutions: Cell::new(0),
2099 })
2100 }
2101
2102 fn resolve_debug(
2103 &self,
2104 f: &mut fmt::Formatter,
2105 _expanded_node: Option<&ExpandedNode>,
2106 ) -> fmt::Result {
2107 f.debug_struct("TestLightingNode").finish()
2108 }
2109
2110 fn resolve_scene_light(
2111 &self,
2112 _expanded_node: &ExpandedNode,
2113 _context: &RuntimeContext,
2114 ) -> Option<SceneLight> {
2115 assert!(self.has_scene_lighting());
2116 self.resolutions.set(self.resolutions.get() + 1);
2117 match self.role {
2118 TestLightingRole::Light(intensity) if self.enabled.get() => Some(SceneLight {
2119 intensity: intensity * self.intensity_multiplier.get(),
2120 ..Default::default()
2121 }),
2122 _ => None,
2123 }
2124 }
2125
2126 fn resolve_scene_ambient_light(
2127 &self,
2128 _expanded_node: &ExpandedNode,
2129 _context: &RuntimeContext,
2130 ) -> Option<SceneAmbientLight> {
2131 assert!(self.has_scene_lighting());
2132 self.resolutions.set(self.resolutions.get() + 1);
2133 match self.role {
2134 TestLightingRole::Ambient(intensity) if self.enabled.get() => {
2135 Some(SceneAmbientLight {
2136 intensity,
2137 ..Default::default()
2138 })
2139 }
2140 _ => None,
2141 }
2142 }
2143
2144 fn establishes_light_frame(&self) -> bool {
2145 matches!(self.role, TestLightingRole::Frame)
2146 }
2147
2148 fn base(&self) -> &BaseInstance {
2149 &self.base
2150 }
2151 }
2152
2153 struct ScopedLightingFixture {
2154 context: Rc<RuntimeContext>,
2155 _root: Rc<ExpandedNode>,
2156 root_light: Rc<TestLightingNode>,
2157 outer_light: Rc<TestLightingNode>,
2158 ambient: Rc<TestLightingNode>,
2159 root_surface: Rc<ExpandedNode>,
2160 outer_surface: Rc<ExpandedNode>,
2161 nested_surface: Rc<ExpandedNode>,
2162 sibling_surface: Rc<ExpandedNode>,
2163 sibling_frame: Rc<ExpandedNode>,
2164 }
2165
2166 fn test_globals() -> Globals {
2167 Globals {
2168 elapsed_frames: LocalProperty::new(0),
2169 elapsed_millis: LocalProperty::new(0),
2170 viewport: LocalProperty::new(TransformAndBounds {
2171 transform: Transform2::identity(),
2172 bounds: (100.0, 100.0),
2173 }),
2174 gyro: LocalProperty::new(Default::default()),
2175 accel: LocalProperty::new(Default::default()),
2176 route_location: LocalProperty::new(RouteLocation::root()),
2177 browser_allows_scroller_vector_layers: LocalProperty::new(true),
2178 browser_allows_nested_scroller_vector_layers: LocalProperty::new(true),
2179 platform: Platform::Unknown,
2180 os: OS::Unknown,
2181 target: TargetInfo::new(Platform::Unknown, OS::Unknown),
2182 #[cfg(feature = "designtime")]
2183 designtime: crate::test_support::designtime(),
2184 get_elapsed_millis: Rc::new(|| 0),
2185 }
2186 }
2187
2188 fn properties_factory() -> crate::PropertiesFactory {
2189 Box::new(|_, expanded_node| {
2190 expanded_node
2191 .is_none()
2192 .then(|| Rc::new(RefCell::new(PaxAny::Builtin(PaxValue::default()))))
2193 })
2194 }
2195
2196 fn node_args(children: Vec<Rc<dyn InstanceNode>>) -> InstantiationArgs {
2197 InstantiationArgs {
2198 prototypical_common_properties: CommonPropertiesInit::Default,
2199 prototypical_properties: PropertiesInit::Factory(properties_factory()),
2200 handler_registry: None,
2201 children: Some(RefCell::new(children)),
2202 component_template: None,
2203 component_settings: None,
2204 template_node_identifier: None,
2205 template_node_type_id: None,
2206 template_node_selector_info: None,
2207 transition_config: Default::default(),
2208 properties_scope: PropertiesScopeInit::None,
2209 }
2210 }
2211
2212 fn component_args(template: Vec<Rc<dyn InstanceNode>>) -> InstantiationArgs {
2213 InstantiationArgs {
2214 prototypical_common_properties: CommonPropertiesInit::Default,
2215 prototypical_properties: PropertiesInit::Factory(properties_factory()),
2216 handler_registry: None,
2217 children: None,
2218 component_template: Some(RefCell::new(template)),
2219 component_settings: None,
2220 template_node_identifier: None,
2221 template_node_type_id: None,
2222 template_node_selector_info: None,
2223 transition_config: Default::default(),
2224 properties_scope: PropertiesScopeInit::None,
2225 }
2226 }
2227
2228 fn mount_test_tree(
2229 children: Vec<Rc<dyn InstanceNode>>,
2230 ) -> (Rc<RuntimeContext>, Rc<ExpandedNode>) {
2231 let root_component = ComponentInstance::instantiate(component_args(children));
2232 let context = Rc::new(crate::test_support::runtime_context(test_globals()));
2233 let root = ExpandedNode::initialize_root(root_component, &context);
2234 root.recurse_update(&context);
2235 crate::engine::occlusion::update_node_occlusion(&root, &context);
2236 (context, root)
2237 }
2238
2239 fn clear_dirty_canvas_nodes(context: &RuntimeContext) {
2240 for id in context.dirty_canvas_node_ids() {
2241 context.clear_canvas_node_dirty(&id);
2242 }
2243 }
2244
2245 fn scoped_lighting_fixture() -> ScopedLightingFixture {
2246 let nested_light = TestLightingNode::new(TestLightingRole::Light(3.0), Vec::new());
2247 let nested_surface = TestLightingNode::new(TestLightingRole::Surface, Vec::new());
2248 let nested_frame = TestLightingNode::new(
2249 TestLightingRole::Frame,
2250 vec![nested_light.as_instance(), nested_surface.as_instance()],
2251 );
2252
2253 let sibling_surface = TestLightingNode::new(TestLightingRole::Surface, Vec::new());
2254 let sibling_frame =
2255 TestLightingNode::new(TestLightingRole::Frame, vec![sibling_surface.as_instance()]);
2256
2257 let outer_light = TestLightingNode::new(TestLightingRole::Light(2.0), Vec::new());
2258 let outer_surface = TestLightingNode::new(TestLightingRole::Surface, Vec::new());
2259 let outer_frame = TestLightingNode::new(
2260 TestLightingRole::Frame,
2261 vec![
2262 outer_light.as_instance(),
2263 outer_surface.as_instance(),
2264 nested_frame.as_instance(),
2265 sibling_frame.as_instance(),
2266 ],
2267 );
2268
2269 let root_light = TestLightingNode::new(TestLightingRole::Light(1.0), Vec::new());
2270 let root_surface = TestLightingNode::new(TestLightingRole::Surface, Vec::new());
2271 let ambient = TestLightingNode::new(TestLightingRole::Ambient(0.72), Vec::new());
2272 ambient.set_enabled(false);
2273
2274 let (context, root) = mount_test_tree(vec![
2275 root_light.as_instance(),
2276 root_surface.as_instance(),
2277 outer_frame.as_instance(),
2278 ambient.as_instance(),
2279 ]);
2280
2281 let root_children = root.children.get();
2282 let root_surface_node = root_children[1].clone();
2283 let outer_frame_node = root_children[2].clone();
2284 let outer_children = outer_frame_node.children.get();
2285 let outer_surface_node = outer_children[1].clone();
2286 let nested_frame_node = outer_children[2].clone();
2287 let sibling_frame_node = outer_children[3].clone();
2288 let nested_children = nested_frame_node.children.get();
2289 let nested_surface_node = nested_children[1].clone();
2290 let sibling_surface_node = sibling_frame_node.children.get()[0].clone();
2291
2292 ScopedLightingFixture {
2293 context,
2294 _root: root,
2295 root_light,
2296 outer_light,
2297 ambient,
2298 root_surface: root_surface_node,
2299 outer_surface: outer_surface_node,
2300 nested_surface: nested_surface_node,
2301 sibling_surface: sibling_surface_node,
2302 sibling_frame: sibling_frame_node,
2303 }
2304 }
2305
2306 #[test]
2307 fn root_lights_reach_every_frame_ancestry() {
2308 let ancestry = HashSet::from([ExpandedNodeIdentifier(10), ExpandedNodeIdentifier(20)]);
2309 assert!(light_reaches_frame_ancestry(None, &ancestry));
2310 }
2311
2312 #[test]
2313 fn framed_lights_reach_only_descendants_of_their_owner_frame() {
2314 let outer_frame = ExpandedNodeIdentifier(10);
2315 let nested_frame = ExpandedNodeIdentifier(20);
2316 let sibling_frame = ExpandedNodeIdentifier(30);
2317 let nested_ancestry = HashSet::from([outer_frame, nested_frame]);
2318
2319 assert!(light_reaches_frame_ancestry(
2320 Some(&outer_frame),
2321 &nested_ancestry
2322 ));
2323 assert!(light_reaches_frame_ancestry(
2324 Some(&nested_frame),
2325 &nested_ancestry
2326 ));
2327 assert!(!light_reaches_frame_ancestry(
2328 Some(&sibling_frame),
2329 &nested_ancestry
2330 ));
2331 }
2332
2333 #[test]
2334 fn mounted_tree_resolves_root_nested_and_sibling_light_masks() {
2335 let fixture = scoped_lighting_fixture();
2336
2337 let lighting = fixture.context.collect_scene_lighting_for_layer(0);
2338
2339 assert_eq!(
2340 lighting
2341 .lights
2342 .iter()
2343 .map(|light| light.intensity)
2344 .collect::<Vec<_>>(),
2345 vec![1.0, 2.0, 3.0]
2346 );
2347 assert!(!lighting.ambient_is_authored);
2348 assert_eq!(
2349 fixture
2350 .context
2351 .canvas_node_light_mask(fixture.root_surface.id),
2352 0b001
2353 );
2354 assert_eq!(
2355 fixture
2356 .context
2357 .canvas_node_light_mask(fixture.outer_surface.id),
2358 0b011
2359 );
2360 assert_eq!(
2361 fixture
2362 .context
2363 .canvas_node_light_mask(fixture.nested_surface.id),
2364 0b111
2365 );
2366 assert_eq!(
2367 fixture
2368 .context
2369 .canvas_node_light_mask(fixture.sibling_surface.id),
2370 0b011
2371 );
2372 }
2373
2374 #[test]
2375 fn unlit_scene_does_not_resolve_nonproviders_or_store_zero_masks() {
2376 let surfaces = (0..1000)
2377 .map(|_| TestLightingNode::new(TestLightingRole::Surface, Vec::new()))
2378 .collect::<Vec<_>>();
2379 let (context, _root) =
2380 mount_test_tree(surfaces.iter().map(TestLightingNode::as_instance).collect());
2381 clear_dirty_canvas_nodes(&context);
2382 for _ in 0..100 {
2383 let lighting = context.collect_scene_lighting_for_layer(0);
2384 assert!(lighting.lights.is_empty());
2385 assert!(!lighting.ambient_is_authored);
2386 assert_eq!(
2387 lighting.ambient.intensity,
2388 SceneLighting::DEFAULT_AMBIENT_INTENSITY
2389 );
2390 }
2391 assert!(surfaces
2392 .iter()
2393 .all(|surface| surface.resolutions.get() == 0));
2394 assert!(borrow!(context.canvas_node_light_masks).is_empty());
2395 assert!(context.dirty_canvas_node_ids().is_empty());
2396 }
2397
2398 #[test]
2399 fn disabled_provider_enable_remove_and_remount_refresh_memberships() {
2400 let light = TestLightingNode::new(TestLightingRole::Light(2.0), Vec::new());
2401 light.set_enabled(false);
2402 let surface = TestLightingNode::new(TestLightingRole::Surface, Vec::new());
2403 let (context, root) = mount_test_tree(vec![light.as_instance(), surface.as_instance()]);
2404 let children = root.children.get();
2405 let light_node = children[0].clone();
2406 let surface_node = &children[1];
2407 assert!(context
2408 .collect_scene_lighting_for_layer(0)
2409 .lights
2410 .is_empty());
2411 light.set_enabled(true);
2412 assert_eq!(context.collect_scene_lighting_for_layer(0).lights.len(), 1);
2413 assert_eq!(context.canvas_node_light_mask(surface_node.id), 1);
2414 clear_dirty_canvas_nodes(&context);
2415 light_node.clone().recurse_unmount(&context);
2416 assert!(context
2417 .collect_scene_lighting_for_layer(0)
2418 .lights
2419 .is_empty());
2420 assert_eq!(context.canvas_node_light_mask(surface_node.id), 0);
2421 assert!(context.is_canvas_node_dirty(&surface_node.id));
2422 assert!(borrow!(context.scene_lighting_nodes).is_empty());
2423 assert!(borrow!(context.canvas_node_light_masks).is_empty());
2424 light_node.recurse_mount(&context);
2425 assert_eq!(context.collect_scene_lighting_for_layer(0).lights.len(), 1);
2426 assert_eq!(context.canvas_node_light_mask(surface_node.id), 1);
2427 }
2428
2429 #[test]
2430 fn template_replacement_refreshes_provider_capability_without_remount() {
2431 let surface = TestLightingNode::new(TestLightingRole::Surface, Vec::new());
2432 let (context, root) = mount_test_tree(vec![surface.as_instance()]);
2433 let node = root.children.get()[0].clone();
2434 assert!(context
2435 .collect_scene_lighting_for_layer(0)
2436 .lights
2437 .is_empty());
2438 let light = TestLightingNode::new(TestLightingRole::Light(3.0), Vec::new());
2439 node.recreate_with_new_data(light.as_instance(), &context);
2440 assert_eq!(
2441 context.collect_scene_lighting_for_layer(0).lights[0].intensity,
2442 3.0
2443 );
2444 node.recreate_with_new_data(surface.as_instance(), &context);
2445 assert!(context
2446 .collect_scene_lighting_for_layer(0)
2447 .lights
2448 .is_empty());
2449 assert_eq!(context.canvas_node_light_mask(node.id), 0);
2450 assert!(borrow!(context.scene_lighting_nodes).is_empty());
2451 }
2452
2453 #[test]
2454 fn slot_reallocation_and_render_reparenting_refresh_retained_masks() {
2455 let fixture = scoped_lighting_fixture();
2456 fixture.context.collect_scene_lighting_for_layer(0);
2457 clear_dirty_canvas_nodes(&fixture.context);
2458
2459 fixture.outer_light.set_enabled(false);
2460 let lighting = fixture.context.collect_scene_lighting_for_layer(0);
2461
2462 assert_eq!(
2463 lighting
2464 .lights
2465 .iter()
2466 .map(|light| light.intensity)
2467 .collect::<Vec<_>>(),
2468 vec![1.0, 3.0]
2469 );
2470 assert_eq!(
2471 fixture
2472 .context
2473 .canvas_node_light_mask(fixture.root_surface.id),
2474 0b01
2475 );
2476 assert_eq!(
2477 fixture
2478 .context
2479 .canvas_node_light_mask(fixture.outer_surface.id),
2480 0b01
2481 );
2482 assert_eq!(
2483 fixture
2484 .context
2485 .canvas_node_light_mask(fixture.nested_surface.id),
2486 0b11
2487 );
2488 assert!(fixture
2489 .context
2490 .is_canvas_node_dirty(&fixture.outer_surface.id));
2491 assert!(fixture
2492 .context
2493 .is_canvas_node_dirty(&fixture.nested_surface.id));
2494 assert!(!fixture
2495 .context
2496 .is_canvas_node_dirty(&fixture.root_surface.id));
2497
2498 clear_dirty_canvas_nodes(&fixture.context);
2499 *borrow_mut!(fixture.nested_surface.render_parent) = Rc::downgrade(&fixture.sibling_frame);
2500 fixture.context.mark_occlusion_dirty();
2501 fixture.context.collect_scene_lighting_for_layer(0);
2502
2503 assert_eq!(
2504 fixture
2505 .context
2506 .canvas_node_light_mask(fixture.nested_surface.id),
2507 0b01
2508 );
2509 assert!(fixture
2510 .context
2511 .is_canvas_node_dirty(&fixture.nested_surface.id));
2512 }
2513
2514 #[test]
2515 fn default_and_authored_ambient_keep_empty_direct_masks_distinct() {
2516 let fixture = scoped_lighting_fixture();
2517 fixture.root_light.set_enabled(false);
2518
2519 let default_ambient = fixture.context.collect_scene_lighting_for_layer(0);
2520 assert!(default_ambient.active);
2521 assert!(!default_ambient.ambient_is_authored);
2522 assert_eq!(
2523 fixture
2524 .context
2525 .canvas_node_light_mask(fixture.root_surface.id),
2526 0
2527 );
2528 assert_eq!(
2529 default_ambient.ambient.intensity,
2530 SceneLighting::DEFAULT_AMBIENT_INTENSITY
2531 );
2532
2533 fixture.ambient.set_enabled(true);
2534 let authored_ambient = fixture.context.collect_scene_lighting_for_layer(0);
2535 assert!(authored_ambient.active);
2536 assert!(authored_ambient.ambient_is_authored);
2537 assert_eq!(authored_ambient.ambient.intensity, 0.72);
2538 assert_eq!(
2539 fixture
2540 .context
2541 .canvas_node_light_mask(fixture.root_surface.id),
2542 0
2543 );
2544 }
2545
2546 #[test]
2547 fn light_overflow_selection_is_deterministic_and_recovers() {
2548 let lights = (0..10)
2549 .map(|index| {
2550 TestLightingNode::new(TestLightingRole::Light((index + 1) as f64), Vec::new())
2551 })
2552 .collect::<Vec<_>>();
2553 let surface = TestLightingNode::new(TestLightingRole::Surface, Vec::new());
2554 let mut templates = lights
2555 .iter()
2556 .rev()
2557 .map(TestLightingNode::as_instance)
2558 .collect::<Vec<_>>();
2559 templates.push(surface.as_instance());
2560 let (context, root) = mount_test_tree(templates);
2561 let expanded = root.children.get();
2562 let surface_node = expanded[10].clone();
2563
2564 let first = context.collect_scene_lighting_for_layer(0);
2565 let second = context.collect_scene_lighting_for_layer(0);
2566 let expected = vec![10.0, 9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0];
2567 assert_eq!(
2568 first
2569 .lights
2570 .iter()
2571 .map(|light| light.intensity)
2572 .collect::<Vec<_>>(),
2573 expected
2574 );
2575 assert_eq!(first.lights, second.lights);
2576 assert_eq!(context.canvas_node_light_mask(surface_node.id), 0xff);
2577 assert_eq!(borrow!(context.lighting_overflow_counts).get(&0), Some(&2));
2578
2579 lights[0].set_enabled(false);
2580 lights[1].set_enabled(false);
2581 let recovered = context.collect_scene_lighting_for_layer(0);
2582 assert_eq!(
2583 recovered
2584 .lights
2585 .iter()
2586 .map(|light| light.intensity)
2587 .collect::<Vec<_>>(),
2588 expected
2589 );
2590 assert!(!borrow!(context.lighting_overflow_counts).contains_key(&0));
2591 }
2592
2593 #[test]
2594 fn unlit_scene_does_no_lighting_scan_regardless_of_scene_size() {
2595 let surfaces = (0..1024)
2596 .map(|_| TestLightingNode::new(TestLightingRole::Surface, Vec::new()).as_instance())
2597 .collect();
2598 let (context, _root) = mount_test_tree(surfaces);
2599 clear_dirty_canvas_nodes(&context);
2600 for _ in 0..20 {
2601 context.set_canvas_dirty(0);
2602 let lighting = context.collect_scene_lighting_for_layer(0);
2603 assert!(lighting.lights.is_empty());
2604 assert!(!lighting.ambient_is_authored);
2605 }
2606 assert_eq!(context.lighting_mask_node_visits.get(), 0);
2607 assert!(borrow!(context.canvas_node_light_masks).is_empty());
2608 assert!(context.dirty_canvas_node_ids().is_empty());
2609 }
2610
2611 #[test]
2612 fn lighting_masks_survive_geometry_and_value_changes_but_not_scope_changes() {
2613 let fixture = scoped_lighting_fixture();
2614 fixture.context.collect_scene_lighting_for_layer(0);
2615 clear_dirty_canvas_nodes(&fixture.context);
2616 let visits = fixture.context.lighting_mask_node_visits.get();
2617 fixture.root_light.intensity_multiplier.set(4.0);
2618 fixture
2619 .context
2620 .mark_node_occlusion_dirty(fixture.outer_surface.id.to_u32());
2621 let lighting = fixture.context.collect_scene_lighting_for_layer(0);
2622 assert_eq!(lighting.lights[0].intensity, 4.0);
2623 assert_eq!(fixture.context.lighting_mask_node_visits.get(), visits);
2624 assert!(fixture.context.dirty_canvas_node_ids().is_empty());
2625
2626 let root_light = fixture._root.children.get()[0].clone();
2629 *borrow_mut!(root_light.render_parent) = Rc::downgrade(&fixture.sibling_frame);
2630 fixture.context.mark_occlusion_dirty();
2631 fixture.context.collect_scene_lighting_for_layer(0);
2632 assert_eq!(
2633 fixture
2634 .context
2635 .canvas_node_light_mask(fixture.root_surface.id),
2636 0
2637 );
2638 assert_eq!(
2639 fixture
2640 .context
2641 .canvas_node_light_mask(fixture.sibling_surface.id),
2642 0b011
2643 );
2644 assert!(fixture
2645 .context
2646 .is_canvas_node_dirty(&fixture.root_surface.id));
2647 assert!(fixture.context.lighting_mask_node_visits.get() > visits);
2648 }
2649
2650 #[test]
2651 fn disabled_light_stays_registered_and_last_light_removal_clears_masks() {
2652 let light = TestLightingNode::new(TestLightingRole::Light(2.0), Vec::new());
2653 light.set_enabled(false);
2654 let surface = TestLightingNode::new(TestLightingRole::Surface, Vec::new());
2655 let (context, root) = mount_test_tree(vec![light.as_instance(), surface.as_instance()]);
2656 let nodes = root.children.get();
2657 assert!(context
2658 .collect_scene_lighting_for_layer(0)
2659 .lights
2660 .is_empty());
2661 assert_eq!(context.lighting_mask_node_visits.get(), 0);
2662 light.set_enabled(true);
2663 assert_eq!(context.collect_scene_lighting_for_layer(0).lights.len(), 1);
2664 assert_eq!(context.canvas_node_light_mask(nodes[1].id), 1);
2665 clear_dirty_canvas_nodes(&context);
2666 context.clear_all_dirty_canvases();
2667 nodes[0].clone().recurse_unmount(&context);
2668 assert!(context.is_canvas_dirty(&0));
2669 assert!(context
2670 .collect_scene_lighting_for_layer(0)
2671 .lights
2672 .is_empty());
2673 assert_eq!(context.canvas_node_light_mask(nodes[1].id), 0);
2674 assert!(context.is_canvas_node_dirty(&nodes[1].id));
2675 assert!(borrow!(context.scene_lighting_nodes).is_empty());
2676 let visits = context.lighting_mask_node_visits.get();
2677 context.collect_scene_lighting_for_layer(0);
2678 assert_eq!(context.lighting_mask_node_visits.get(), visits);
2679 }
2680
2681 #[test]
2682 fn ambient_only_scene_never_builds_direct_masks() {
2683 let ambient = TestLightingNode::new(TestLightingRole::Ambient(0.72), Vec::new());
2684 let (context, _root) = mount_test_tree(vec![
2685 ambient.as_instance(),
2686 TestLightingNode::new(TestLightingRole::Surface, Vec::new()).as_instance(),
2687 ]);
2688 assert!(
2689 context
2690 .collect_scene_lighting_for_layer(0)
2691 .ambient_is_authored
2692 );
2693 ambient.set_enabled(false);
2694 assert!(
2695 !context
2696 .collect_scene_lighting_for_layer(0)
2697 .ambient_is_authored
2698 );
2699 ambient.set_enabled(true);
2700 assert_eq!(
2701 context
2702 .collect_scene_lighting_for_layer(0)
2703 .ambient
2704 .intensity,
2705 0.72
2706 );
2707 assert_eq!(context.lighting_mask_node_visits.get(), 0);
2708 }
2709
2710 #[test]
2711 fn replacement_refreshes_lighting_capability_in_both_directions() {
2712 let (context, root) = mount_test_tree(vec![TestLightingNode::new(
2713 TestLightingRole::Surface,
2714 Vec::new(),
2715 )
2716 .as_instance()]);
2717 let node = root.children.get()[0].clone();
2718 context.collect_scene_lighting_for_layer(0);
2719 node.recreate_with_new_data(
2720 TestLightingNode::new(TestLightingRole::Light(5.0), Vec::new()).as_instance(),
2721 &context,
2722 );
2723 assert_eq!(
2724 context.collect_scene_lighting_for_layer(0).lights[0].intensity,
2725 5.0
2726 );
2727 node.recreate_with_new_data(
2728 TestLightingNode::new(TestLightingRole::Surface, Vec::new()).as_instance(),
2729 &context,
2730 );
2731 assert!(context
2732 .collect_scene_lighting_for_layer(0)
2733 .lights
2734 .is_empty());
2735 assert_eq!(context.canvas_node_light_mask(node.id), 0);
2736 }
2737
2738 #[test]
2739 fn ancestor_lights_follow_scroll_without_rescanning_and_clear_after_layer_move() {
2740 let light = TestLightingNode::new(TestLightingRole::Light(1.0), Vec::new());
2741 let (context, root) = mount_test_tree(vec![
2742 light.as_instance(),
2743 TestLightingNode::new(TestLightingRole::Surface, Vec::new()).as_instance(),
2744 TestLightingNode::new(TestLightingRole::Surface, Vec::new()).as_instance(),
2745 ]);
2746 let nodes = root.children.get();
2747 let surface = &nodes[2];
2748 let mut occlusion = surface.occlusion.get();
2749 occlusion.render_layer_id = 1;
2750 surface.occlusion.set(occlusion);
2751 context.register_layer_scroller_owner(1, nodes[1].id);
2752 context.set_scroller_surface_state(
2753 nodes[1].id.to_u32(),
2754 ScrollerSurfaceState {
2755 viewport_width: 100.0,
2756 viewport_height: 100.0,
2757 content_width: 100.0,
2758 content_height: 1000.0,
2759 clip_content: true,
2760 ..Default::default()
2761 },
2762 );
2763 let before = context.collect_scene_lighting_for_layer(1);
2764 let visits = context.lighting_mask_node_visits.get();
2765 context.set_scroller_surface_state(
2766 nodes[1].id.to_u32(),
2767 ScrollerSurfaceState {
2768 scroll_y: 300.0,
2769 presentation_scroll_y: 300.0,
2770 ..context
2771 .get_scroller_surface_state(nodes[1].id.to_u32())
2772 .unwrap()
2773 },
2774 );
2775 let after = context.collect_scene_lighting_for_layer(1);
2776 assert_eq!(
2777 after.lights[0].position.y - before.lights[0].position.y,
2778 300.0
2779 );
2780 assert_eq!(context.canvas_node_light_mask(surface.id), 1);
2781 assert_eq!(context.lighting_mask_node_visits.get(), visits);
2782
2783 occlusion.render_layer_id = 2;
2786 surface.occlusion.set(occlusion);
2787 context.mark_occlusion_dirty();
2788 context.collect_scene_lighting_for_layer(1);
2789 assert!(context
2790 .collect_scene_lighting_for_layer(2)
2791 .lights
2792 .is_empty());
2793 assert_eq!(context.canvas_node_light_mask(surface.id), 0);
2794 context.clear_all_dirty_canvases();
2795 nodes[0].clone().recurse_unmount(&context);
2796 assert!(context.is_canvas_dirty(&1));
2797 }
2798
2799 #[test]
2800 fn detached_source_changes_preserve_incremental_scene_reconciliation() {
2801 let (context, root) = mount_test_tree(vec![TestLightingNode::new(
2802 TestLightingRole::Surface,
2803 Vec::new(),
2804 )
2805 .as_instance()]);
2806 let owner = root.children.get()[0].clone();
2807 let sources = owner.create_children_detached(
2808 vec![(
2809 TestLightingNode::new(TestLightingRole::Surface, Vec::new()).as_instance(),
2810 owner.stack.clone(),
2811 )],
2812 &context,
2813 &Rc::downgrade(&owner),
2814 );
2815 let source = owner
2816 .attach_sidecar_children(sources, &context, &owner.parent_frame)
2817 .remove(0);
2818 source.mount_as_render_source(&owner, &context);
2819 root.recurse_update(&context);
2820 crate::engine::occlusion::update_node_occlusion(&root, &context);
2821 let before = context.occlusion_stats();
2822 source
2823 .get_common_properties()
2824 .borrow()
2825 .x
2826 .set(Some(pax_runtime_api::Size::Pixels(15.0.into())));
2827 root.recurse_update(&context);
2828 crate::engine::occlusion::update_node_occlusion(&root, &context);
2829 let after = context.occlusion_stats();
2830 assert_eq!(after.rebuilds, before.rebuilds);
2831 assert!(after.records_updated > before.records_updated);
2832 root.recurse_unmount(&context);
2833 }
2834
2835 #[test]
2836 fn canvas_surface_transform_uses_the_layer_owner_not_intermediate_frames() {
2837 let leaf = TestLightingNode::new(TestLightingRole::Surface, Vec::new());
2838 let mask = TestLightingNode::new(TestLightingRole::Frame, vec![leaf.as_instance()]);
2839 let inner = TestLightingNode::new(TestLightingRole::Frame, vec![mask.as_instance()]);
2840 let outer = TestLightingNode::new(TestLightingRole::Frame, vec![inner.as_instance()]);
2841 let (context, root) = mount_test_tree(vec![outer.as_instance()]);
2842 let outer = root.children.get()[0].clone();
2843 let inner = outer.children.get()[0].clone();
2844 let mask = inner.children.get()[0].clone();
2845 let leaf = mask.children.get()[0].clone();
2846
2847 let outer_transform = Affine::translate((40.0, 70.0)) * Affine::scale(1.5);
2848 let inner_local = Affine::translate((25.0, 35.0)) * Affine::rotate(0.3);
2849 let inner_transform = outer_transform * inner_local;
2850 let mask_transform = inner_transform * Affine::translate((60.0, 90.0));
2851 let leaf_local = Affine::translate((8.0, 12.0));
2852 let leaf_transform = mask_transform * leaf_local;
2853 for (node, transform, layer, parent_frame) in [
2854 (&outer, outer_transform, 0, None),
2855 (&inner, inner_transform, 1, Some(outer.id)),
2856 (&mask, mask_transform, 0, Some(inner.id)),
2858 (&leaf, leaf_transform, 2, Some(mask.id)),
2859 ] {
2860 node.transform_and_bounds
2861 .replace_with(LocalProperty::new(TransformAndBounds {
2862 transform: Transform2::new(transform.as_coeffs()),
2863 bounds: (100.0, 100.0),
2864 }));
2865 let mut occlusion = node.occlusion.get();
2866 occlusion.render_layer_id = layer;
2867 node.occlusion.set(occlusion);
2868 node.parent_frame
2869 .replace_with(LocalProperty::new(parent_frame));
2870 }
2871 context.register_layer_scroller_owner(1, outer.id);
2872 context.register_layer_scroller_owner(2, inner.id);
2873
2874 let assert_transform = |actual: Affine, expected: Affine| {
2875 for (actual, expected) in actual.as_coeffs().into_iter().zip(expected.as_coeffs()) {
2876 assert!((actual - expected).abs() < 1e-9, "{actual} != {expected}");
2877 }
2878 };
2879 assert_transform(
2880 context.canvas_surface_transform_for_node(&leaf),
2881 Affine::translate((60.0, 90.0)) * leaf_local,
2882 );
2883 assert_transform(context.layer_to_root_transform(2), inner_transform);
2884
2885 context.clear_layer_scroller_owners();
2888 assert_transform(
2889 context.canvas_surface_transform_for_node(&leaf),
2890 leaf_transform,
2891 );
2892 }
2893
2894 #[test]
2895 fn touch_capture_releases_and_clears_removed_targets() {
2896 let surface = TestLightingNode::new(TestLightingRole::Surface, Vec::new());
2897 let (context, root) = mount_test_tree(vec![surface.as_instance()]);
2898 let surface_node = root.children.get()[0].clone();
2899
2900 context.capture_touch_target(41, surface_node.id);
2901 assert!(Rc::ptr_eq(
2902 &context.captured_touch_target(41).unwrap(),
2903 &surface_node
2904 ));
2905 assert!(Rc::ptr_eq(
2906 &context.release_touch_target(41).unwrap(),
2907 &surface_node
2908 ));
2909 assert!(context.captured_touch_target(41).is_none());
2910
2911 context.capture_touch_target(42, surface_node.id);
2912 context.remove_from_cache(&surface_node);
2913 assert!(context.captured_touch_target(42).is_none());
2914 assert!(context.release_touch_target(42).is_none());
2915 }
2916}