pax_runtime/
properties.rs

1use crate::api::math::Point2;
2use crate::api::Window;
3use pax_lang::interpreter::property_resolution::IdentifierResolver;
4use pax_manifest::UniqueTemplateNodeIdentifier;
5use pax_message::NativeMessage;
6use pax_runtime_api::pax_value::PaxAny;
7use pax_runtime_api::properties::UntypedProperty;
8use pax_runtime_api::{
9    borrow, borrow_mut, use_RefCell, Event, Interpolatable, MouseOut, MouseOver, PaxValue,
10    RenderContext, Store, Variable,
11};
12use_RefCell!();
13use std::any::{Any, TypeId};
14use std::cell::Cell;
15use std::collections::HashMap;
16use std::hash::Hash;
17use std::rc::{Rc, Weak};
18
19use crate::{ExpandedNode, Globals};
20
21#[cfg(feature = "designtime")]
22use crate::{ComponentInstance, InstanceNode};
23
24impl Interpolatable for ExpandedNodeIdentifier {}
25
26#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
27pub struct ExpandedNodeIdentifier(pub u32);
28
29impl ExpandedNodeIdentifier {
30    // used for sending identifiers to chassis
31    pub fn to_u32(&self) -> u32 {
32        self.0
33    }
34}
35
36/// Shared context for properties pass recursion
37pub struct RuntimeContext {
38    next_uid: Cell<ExpandedNodeIdentifier>,
39    messages: RefCell<Vec<NativeMessage>>,
40    globals: RefCell<Globals>,
41    root_expanded_node: RefCell<Weak<ExpandedNode>>,
42    #[cfg(feature = "designtime")]
43    pub userland_frame_instance_node: RefCell<Rc<dyn InstanceNode>>,
44    #[cfg(feature = "designtime")]
45    pub userland_root_expanded_node: RefCell<Option<Rc<ExpandedNode>>>,
46    node_cache: RefCell<NodeCache>,
47    last_topmost_element: RefCell<Weak<ExpandedNode>>,
48    queued_custom_events: RefCell<Vec<(Rc<ExpandedNode>, &'static str)>>,
49    queued_renders: RefCell<Vec<Rc<ExpandedNode>>>,
50    pub layer_count: Cell<usize>,
51    pub dirty_canvases: RefCell<Vec<bool>>,
52}
53
54struct NodeCache {
55    eid_to_node: HashMap<ExpandedNodeIdentifier, Rc<ExpandedNode>>,
56    uni_to_eid: HashMap<UniqueTemplateNodeIdentifier, Vec<ExpandedNodeIdentifier>>,
57}
58
59impl NodeCache {
60    fn new() -> Self {
61        Self {
62            eid_to_node: Default::default(),
63            uni_to_eid: Default::default(),
64        }
65    }
66
67    // Add this node to all relevant constant lookup cache structures
68    fn add_to_cache(&mut self, node: &Rc<ExpandedNode>) {
69        self.eid_to_node.insert(node.id, Rc::clone(&node));
70        let uni = borrow!(node.instance_node)
71            .base()
72            .template_node_identifier
73            .clone();
74        if let Some(uni) = uni {
75            self.uni_to_eid.entry(uni).or_default().push(node.id);
76        }
77    }
78
79    // Remove this node from all relevant constant lookup cache structures
80    fn remove_from_cache(&mut self, node: &Rc<ExpandedNode>) {
81        self.eid_to_node.remove(&node.id);
82        if let Some(uni) = &borrow!(node.instance_node).base().template_node_identifier {
83            self.uni_to_eid
84                .entry(uni.clone())
85                .or_default()
86                .retain(|&n| n != node.id);
87        }
88    }
89}
90
91impl RuntimeContext {
92    #[cfg(not(feature = "designtime"))]
93    pub fn new(globals: Globals) -> Self {
94        Self {
95            next_uid: Cell::new(ExpandedNodeIdentifier(0)),
96            messages: RefCell::new(Vec::new()),
97            globals: RefCell::new(globals),
98            root_expanded_node: RefCell::new(Weak::new()),
99            node_cache: RefCell::new(NodeCache::new()),
100            queued_custom_events: Default::default(),
101            queued_renders: Default::default(),
102            layer_count: Cell::default(),
103            last_topmost_element: Default::default(),
104            dirty_canvases: Default::default(),
105        }
106    }
107
108    #[cfg(feature = "designtime")]
109    pub fn new(globals: Globals, userland: Rc<ComponentInstance>) -> Self {
110        Self {
111            next_uid: Cell::new(ExpandedNodeIdentifier(0)),
112            messages: RefCell::new(Vec::new()),
113            globals: RefCell::new(globals),
114            root_expanded_node: RefCell::new(Weak::new()),
115            userland_frame_instance_node: RefCell::new(userland),
116            userland_root_expanded_node: Default::default(),
117            node_cache: RefCell::new(NodeCache::new()),
118            queued_custom_events: Default::default(),
119            queued_renders: Default::default(),
120            layer_count: Cell::default(),
121            last_topmost_element: Default::default(),
122            dirty_canvases: Default::default(),
123        }
124    }
125
126    pub fn register_root_expanded_node(&self, root: &Rc<ExpandedNode>) {
127        *borrow_mut!(self.root_expanded_node) = Rc::downgrade(root);
128    }
129
130    pub fn add_to_cache(&self, node: &Rc<ExpandedNode>) {
131        borrow_mut!(self.node_cache).add_to_cache(node);
132    }
133
134    pub fn remove_from_cache(&self, node: &Rc<ExpandedNode>) {
135        borrow_mut!(self.node_cache).remove_from_cache(node);
136    }
137
138    pub fn get_expanded_node_by_eid(&self, id: ExpandedNodeIdentifier) -> Option<Rc<ExpandedNode>> {
139        borrow!(self.node_cache).eid_to_node.get(&id).cloned()
140    }
141
142    pub fn add_canvas(&self, id: usize) {
143        let mut dirty_canvases = borrow_mut!(self.dirty_canvases);
144        while dirty_canvases.len() <= id + 1 {
145            dirty_canvases.push(true);
146        }
147    }
148
149    pub fn remove_canvas(&self, id: usize) {
150        let mut dirty_canvases = borrow_mut!(self.dirty_canvases);
151        while dirty_canvases.len() > id {
152            dirty_canvases.pop();
153        }
154    }
155
156    pub fn get_dirty_canvases(&self) -> Vec<usize> {
157        borrow!(self.dirty_canvases)
158            .iter()
159            .enumerate()
160            .filter_map(|(i, v)| if *v { Some(i) } else { None })
161            .collect()
162    }
163
164    pub fn clear_all_dirty_canvases(&self) {
165        let mut dirty_canvases = borrow_mut!(self.dirty_canvases);
166        for v in dirty_canvases.iter_mut() {
167            *v = false;
168        }
169    }
170
171    pub fn set_canvas_dirty(&self, id: usize) {
172        let mut dirty_canvases = borrow_mut!(self.dirty_canvases);
173        if let Some(v) = dirty_canvases.get_mut(id) {
174            *v = true;
175        }
176    }
177
178    pub fn is_canvas_dirty(&self, id: &usize) -> bool {
179        *borrow!(self.dirty_canvases).get(*id).unwrap_or(&false)
180    }
181
182    pub fn set_all_canvases_dirty(&self) {
183        let mut dirty_canvases = borrow_mut!(self.dirty_canvases);
184        for v in dirty_canvases.iter_mut() {
185            *v = true;
186        }
187    }
188
189    /// Finds all ExpandedNodes with the CommonProperty#id matching the provided string
190    pub fn get_expanded_nodes_by_id(&self, id: &str) -> Vec<Rc<ExpandedNode>> {
191        //v0 limitation: currently an O(n) lookup cost (could be made O(1) with an id->expandednode cache)
192        borrow!(self.node_cache)
193            .eid_to_node
194            .values()
195            .filter(|val| {
196                let common_props = val.get_common_properties();
197                let common_props = borrow!(common_props);
198                common_props.id.get().is_some_and(|i| i == id)
199            })
200            .cloned()
201            .collect()
202    }
203
204    /// Finds all ExpandedNodes with corresponding UniqueTemplateNodeIdentifier
205    pub fn get_expanded_nodes_by_global_ids(
206        &self,
207        uni: &UniqueTemplateNodeIdentifier,
208    ) -> Vec<Rc<ExpandedNode>> {
209        let node_cache = borrow!(self.node_cache);
210        node_cache
211            .uni_to_eid
212            .get(uni)
213            .map(|eids| {
214                let mut nodes = vec![];
215                for e in eids {
216                    nodes.extend(
217                        node_cache
218                            .eid_to_node
219                            .get(e)
220                            .map(|node| vec![Rc::clone(node)])
221                            .unwrap_or_default(),
222                    )
223                }
224                nodes
225            })
226            .unwrap_or_default()
227    }
228
229    /// Simple 2D raycasting: the coordinates of the ray represent a
230    /// ray running orthogonally to the view plane, intersecting at
231    /// the specified point `ray`.  Areas outside of clipping bounds will
232    /// not register a `hit`, nor will elements that suppress input events.
233    pub fn get_elements_beneath_ray(
234        &self,
235        root: Option<Rc<ExpandedNode>>,
236        ray: Point2<Window>,
237        limit_one: bool,
238        mut accum: Vec<Rc<ExpandedNode>>,
239        hit_invisible: bool,
240    ) -> Vec<Rc<ExpandedNode>> {
241        //Traverse all elements in render tree sorted by z-index (highest-to-lowest)
242        //First: check whether events are suppressed
243        //Next: check whether ancestral clipping bounds (hit_test) are satisfied
244        //Finally: check whether element itself satisfies hit_test(ray)
245
246        let root_node = root.unwrap_or_else(|| borrow!(self.root_expanded_node).upgrade().unwrap());
247        let mut to_process = vec![(root_node, false)];
248        while let Some((node, clipped)) = to_process.pop() {
249            // make sure slot sources are updated for this node
250            node.compute_flattened_slot_children();
251            let hit = node.ray_cast_test(ray);
252            if hit && !clipped {
253                if hit_invisible
254                    || !borrow!(node.instance_node)
255                        .base()
256                        .flags()
257                        .invisible_to_raycasting
258                {
259                    //We only care about the topmost node getting hit, and the element
260                    //pool is ordered by z-index so we can just resolve the whole
261                    //calculation when we find the first matching node
262                    if limit_one {
263                        return vec![node];
264                    }
265                    accum.push(Rc::clone(&node));
266                }
267            }
268            let clipped = clipped || (!hit && borrow!(node.instance_node).clips_content(&node));
269            to_process.extend(
270                node.children
271                    .get()
272                    .iter()
273                    .cloned()
274                    .map(|v| {
275                        let cp = v.get_common_properties();
276                        let unclippable = borrow!(cp).unclippable.get().unwrap_or(false);
277                        (v, clipped && !unclippable)
278                    })
279                    .rev(),
280            )
281        }
282        accum
283    }
284    /// Alias for `get_elements_beneath_ray` with `limit_one = true`
285    pub fn get_topmost_element_beneath_ray(
286        self: &Rc<Self>,
287        ray: Point2<Window>,
288    ) -> Rc<ExpandedNode> {
289        let res = self.get_elements_beneath_ray(None, ray, true, vec![], false);
290        let new_topmost = res
291            .into_iter()
292            .next()
293            .unwrap_or(borrow!(self.root_expanded_node).upgrade().unwrap());
294
295        //send mouse over/out events if the hit element is different than last
296        let last_topmost = borrow!(self.last_topmost_element).upgrade();
297        let new_topmost_comp = new_topmost.containing_component.upgrade();
298        if new_topmost_comp.as_ref().map(|n| n.id) != last_topmost.as_ref().map(|n| n.id) {
299            let (leaving, entering) =
300                find_paths_to_common_ancestor(&last_topmost, &new_topmost_comp);
301            for leave in leaving {
302                leave.dispatch_mouse_out(Event::new(MouseOut {}), &self.globals(), self);
303            }
304            for enter in entering {
305                enter.dispatch_mouse_over(Event::new(MouseOver {}), &self.globals(), self);
306            }
307            *borrow_mut!(self.last_topmost_element) = new_topmost_comp
308                .as_ref()
309                .map(Rc::downgrade)
310                .unwrap_or_default();
311        }
312        new_topmost
313    }
314
315    pub fn gen_uid(&self) -> ExpandedNodeIdentifier {
316        let val = self.next_uid.get();
317        let next_val = ExpandedNodeIdentifier(val.0 + 1);
318        self.next_uid.set(next_val);
319        val
320    }
321
322    pub fn enqueue_native_message(&self, message: NativeMessage) {
323        borrow_mut!(self.messages).push(message)
324    }
325
326    pub fn take_native_messages(&self) -> Vec<NativeMessage> {
327        let mut messages = borrow_mut!(self.messages);
328        std::mem::take(&mut *messages)
329    }
330
331    pub fn globals(&self) -> Globals {
332        borrow!(self.globals).clone()
333    }
334
335    pub fn edit_globals(&self, f: impl Fn(&mut Globals)) {
336        let mut globals = borrow_mut!(self.globals);
337        f(&mut globals);
338    }
339
340    pub fn queue_custom_event(&self, source_expanded_node: Rc<ExpandedNode>, name: &'static str) {
341        let mut queued_custom_events = borrow_mut!(self.queued_custom_events);
342        queued_custom_events.push((source_expanded_node, name));
343    }
344
345    pub fn flush_custom_events(self: &Rc<Self>) -> Result<(), String> {
346        let mut queued_custom_event = borrow_mut!(self.queued_custom_events);
347        let to_flush: Vec<_> = std::mem::take(queued_custom_event.as_mut());
348        for (target, ident) in to_flush {
349            target.dispatch_custom_event(ident, self)?;
350        }
351        Ok(())
352    }
353
354    #[cfg(feature = "designtime")]
355    pub fn get_userland_root_expanded_node(&self) -> Option<Rc<ExpandedNode>> {
356        borrow!(self.userland_root_expanded_node).clone()
357    }
358
359    #[cfg(feature = "designtime")]
360    pub fn get_userland_root_instance_node(&self) -> Option<Rc<dyn InstanceNode>> {
361        Some(borrow!(self.userland_frame_instance_node).clone())
362    }
363
364    pub fn get_root_expanded_node(&self) -> Option<Rc<ExpandedNode>> {
365        borrow!(self.root_expanded_node).upgrade()
366    }
367
368    pub fn queue_render(&self, expanded_node: Rc<ExpandedNode>) {
369        borrow_mut!(self.queued_renders).push(expanded_node);
370    }
371
372    pub fn recurse_flush_queued_renders(self: &Rc<RuntimeContext>, rcs: &mut dyn RenderContext) {
373        while !borrow!(self.queued_renders).is_empty() {
374            for n in std::mem::take(&mut *borrow_mut!(self.queued_renders)) {
375                n.recurse_render(self, rcs);
376            }
377        }
378    }
379}
380
381fn find_paths_to_common_ancestor(
382    last_topmost: &Option<Rc<ExpandedNode>>,
383    new_topmost_comp: &Option<Rc<ExpandedNode>>,
384) -> (Vec<Rc<ExpandedNode>>, Vec<Rc<ExpandedNode>>) {
385    let mut last_path = Vec::new();
386    let mut new_path = Vec::new();
387
388    // If either node is None, return empty paths
389    if last_topmost.is_none() || new_topmost_comp.is_none() {
390        return (last_path, new_path);
391    }
392
393    let mut last_node = last_topmost.clone();
394    let mut new_node = new_topmost_comp.clone();
395
396    // Build paths from nodes to root
397    while let Some(node) = last_node.clone() {
398        last_path.push(node.clone());
399        last_node = node.containing_component.upgrade();
400    }
401
402    while let Some(node) = new_node.clone() {
403        new_path.push(node.clone());
404        new_node = node.containing_component.upgrade();
405    }
406
407    // Reverse paths to start from root
408    last_path.reverse();
409    new_path.reverse();
410
411    // Find the last common node
412    let mut common_ancestor_index = 0;
413    for (i, (last, new)) in last_path.iter().zip(new_path.iter()).enumerate() {
414        if last.id == new.id {
415            common_ancestor_index = i;
416        } else {
417            break;
418        }
419    }
420
421    // Remove common ancestors from both paths
422    last_path.drain(0..=common_ancestor_index);
423    new_path.drain(0..=common_ancestor_index);
424
425    (last_path, new_path)
426}
427
428/// Data structure for a single frame of our runtime stack, including
429/// a reference to its parent frame and `properties` for
430/// runtime evaluation, e.g. of Expressions.  `RuntimePropertiesStackFrame`s also track
431/// timeline playhead position.
432///
433/// `Component`s push `RuntimePropertiesStackFrame`s before computing properties and pop them after computing, thus providing a
434/// hierarchical store of node-relevant data that can be bound to symbols in expressions.
435
436pub struct RuntimePropertiesStackFrame {
437    symbols_within_frame: HashMap<String, Variable>,
438    local_stores: Rc<RefCell<HashMap<TypeId, Box<dyn Any>>>>,
439    parent: Weak<RuntimePropertiesStackFrame>,
440}
441
442impl RuntimePropertiesStackFrame {
443    pub fn new(symbols_within_frame: HashMap<String, Variable>) -> Rc<Self> {
444        Rc::new(Self {
445            symbols_within_frame,
446            local_stores: Default::default(),
447            parent: Weak::new(),
448        })
449    }
450
451    pub fn push(self: &Rc<Self>, symbols_within_frame: HashMap<String, Variable>) -> Rc<Self> {
452        Rc::new(RuntimePropertiesStackFrame {
453            symbols_within_frame,
454            local_stores: Default::default(),
455            parent: Rc::downgrade(&self),
456        })
457    }
458
459    pub fn pop(self: &Rc<Self>) -> Option<Rc<Self>> {
460        self.parent.upgrade()
461    }
462
463    pub fn insert_stack_local_store<T: Store>(&self, store: T) {
464        let type_id = TypeId::of::<T>();
465        borrow_mut!(self.local_stores).insert(type_id, Box::new(store));
466    }
467
468    pub fn peek_stack_local_store<T: Store, V>(
469        self: &Rc<Self>,
470        f: impl FnOnce(&mut T) -> V,
471    ) -> Result<V, String> {
472        let mut current = Rc::clone(self);
473        let type_id = TypeId::of::<T>();
474
475        while !borrow!(current.local_stores).contains_key(&type_id) {
476            current = current
477                .parent
478                .upgrade()
479                .ok_or_else(|| format!("couldn't find store in local stack"))?;
480        }
481        let v = {
482            let mut stores = borrow_mut!(current.local_stores);
483            let store = stores.get_mut(&type_id).unwrap().downcast_mut().unwrap();
484            f(store)
485        };
486        Ok(v)
487    }
488
489    pub fn resolve_symbol_as_variable(&self, symbol: &str) -> Option<Variable> {
490        if let Some(e) = self.symbols_within_frame.get(&clean_symbol(symbol)) {
491            Some(e.clone())
492        } else {
493            self.parent.upgrade()?.resolve_symbol_as_variable(symbol)
494        }
495    }
496
497    pub fn resolve_symbol_as_erased_property(&self, symbol: &str) -> Option<UntypedProperty> {
498        if let Some(e) = self.symbols_within_frame.get(&clean_symbol(symbol)) {
499            Some(e.clone().get_untyped_property().clone())
500        } else {
501            self.parent
502                .upgrade()?
503                .resolve_symbol_as_erased_property(symbol)
504        }
505    }
506
507    pub fn resolve_symbol_as_pax_value(&self, symbol: &str) -> Option<PaxValue> {
508        if let Some(e) = self.symbols_within_frame.get(&clean_symbol(symbol)) {
509            Some(e.get_as_pax_value())
510        } else {
511            self.parent.upgrade()?.resolve_symbol_as_pax_value(symbol)
512        }
513    }
514}
515
516fn clean_symbol(symbol: &str) -> String {
517    symbol.replace("self.", "").replace("this.", "")
518}
519
520impl IdentifierResolver for RuntimePropertiesStackFrame {
521    fn resolve(&self, name: String) -> Result<PaxValue, String> {
522        self.resolve_symbol_as_pax_value(&name)
523            .ok_or_else(|| format!("Could not resolve symbol {}", name))
524    }
525}
526
527/// Data structure used for dynamic injection of values
528/// into Expressions, maintaining a pointer e.g. to the current
529/// stack frame to enable evaluation of properties & dependencies
530pub struct ExpressionContext {
531    pub stack_frame: Rc<RuntimePropertiesStackFrame>,
532}