pax_runtime/engine/
mod.rs

1use crate::{
2    api::Property, ExpandedNodeIdentifier, RuntimePropertiesStackFrame, TransformAndBounds,
3};
4use_RefCell!();
5use std::collections::HashMap;
6use std::rc::Rc;
7
8use kurbo::Affine;
9use pax_message::NativeMessage;
10use pax_runtime_api::{
11    pax_value::PaxAny, use_RefCell, Event, Focus, SelectStart, Variable, Window, OS,
12};
13
14use crate::api::{KeyDown, KeyPress, KeyUp, NodeContext, RenderContext};
15use piet::InterpolationMode;
16
17use crate::{ComponentInstance, RuntimeContext};
18use pax_runtime_api::Platform;
19use std::time::Instant;
20
21pub mod node_interface;
22pub mod occlusion;
23
24/// The atomic unit of rendering; also the container for each unique tuple of computed properties.
25/// Represents an expanded node, that is "expanded" in the context of computed properties and repeat expansion.
26/// For example, a Rectangle inside `for i in 0..3` and a `for j in 0..4` would have 12 expanded nodes representing the 12 virtual Rectangles in the
27/// rendered scene graph.
28/// `ExpandedNode`s are architecturally "type-blind" — while they store typed data e.g. inside `computed_properties` and `computed_common_properties`,
29/// they require coordinating with their "type-aware" [`InstanceNode`] to perform operations on those properties.
30mod expanded_node;
31pub use expanded_node::ExpandedNode;
32
33use self::node_interface::NodeLocal;
34
35#[cfg(feature = "designtime")]
36use {
37    crate::InstanceNode,
38    pax_designtime::DesigntimeManager,
39    pax_runtime_api::{borrow, borrow_mut},
40};
41
42#[derive(Clone)]
43pub struct Globals {
44    pub frames_elapsed: Property<u64>,
45    pub viewport: Property<TransformAndBounds<NodeLocal, Window>>,
46    pub platform: Platform,
47    pub os: OS,
48    #[cfg(feature = "designtime")]
49    pub designtime: Rc<RefCell<DesigntimeManager>>,
50    pub get_elapsed_millis: Rc<dyn Fn() -> u128>,
51}
52
53impl Globals {
54    pub fn stack_frame(&self) -> Rc<RuntimePropertiesStackFrame> {
55        let mobile = Property::new(self.os.is_mobile());
56        let desktop = Property::new(self.os.is_desktop());
57
58        let cloned_viewport = self.viewport.clone();
59        let deps = [cloned_viewport.untyped()];
60        let viewport = Property::computed(
61            move || {
62                let viewport = cloned_viewport.get();
63                pax_runtime_api::Viewport {
64                    width: viewport.bounds.0,
65                    height: viewport.bounds.1,
66                }
67            },
68            &deps,
69        );
70
71        let mobile_var = Variable::new_from_typed_property(mobile);
72        let desktop_var = Variable::new_from_typed_property(desktop);
73        let viewport_var = Variable::new_from_typed_property(viewport);
74        let frames_elapsed_var = Variable::new_from_typed_property(self.frames_elapsed.clone());
75
76        let global_scope = vec![
77            ("$mobile".to_string(), mobile_var),
78            ("$desktop".to_string(), desktop_var),
79            ("$viewport".to_string(), viewport_var),
80            ("$frames_elapsed".to_string(), frames_elapsed_var),
81        ]
82        .into_iter()
83        .collect();
84
85        let root_env = RuntimePropertiesStackFrame::new(global_scope);
86        root_env
87    }
88}
89
90impl std::fmt::Debug for Globals {
91    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
92        f.debug_struct("Globals")
93            .field("frames_elapsed", &self.frames_elapsed)
94            .field("viewport", &self.viewport)
95            .finish_non_exhaustive()
96    }
97}
98
99/// Singleton struct storing everything related to properties computation & rendering
100pub struct PaxEngine {
101    pub runtime_context: Rc<RuntimeContext>,
102    pub root_expanded_node: Rc<ExpandedNode>,
103}
104
105pub enum HandlerLocation {
106    Inline,
107    Component,
108}
109
110pub struct Handler {
111    pub function: fn(Rc<RefCell<PaxAny>>, &NodeContext, Option<PaxAny>),
112    pub location: HandlerLocation,
113}
114
115impl Handler {
116    pub fn new_inline_handler(
117        function: fn(Rc<RefCell<PaxAny>>, &NodeContext, Option<PaxAny>),
118    ) -> Self {
119        Handler {
120            function,
121            location: HandlerLocation::Inline,
122        }
123    }
124
125    pub fn new_component_handler(
126        function: fn(Rc<RefCell<PaxAny>>, &NodeContext, Option<PaxAny>),
127    ) -> Self {
128        Handler {
129            function,
130            location: HandlerLocation::Component,
131        }
132    }
133}
134
135pub struct HandlerRegistry {
136    pub handlers: HashMap<String, Vec<Handler>>,
137}
138
139impl Default for HandlerRegistry {
140    fn default() -> Self {
141        HandlerRegistry {
142            handlers: HashMap::new(),
143        }
144    }
145}
146
147struct ImgData<R: piet::RenderContext> {
148    img: R::Image,
149    size: (usize, usize),
150}
151
152pub struct Renderer<R: piet::RenderContext> {
153    backends: Vec<R>,
154    image_map: HashMap<String, ImgData<R>>,
155}
156
157impl<R: piet::RenderContext> Renderer<R> {
158    pub fn new() -> Self {
159        Self {
160            backends: Vec::new(),
161            image_map: HashMap::new(),
162        }
163    }
164
165    pub fn add_context(&mut self, id: usize, context: R) {
166        self.backends.insert(id.to_owned(), context);
167    }
168
169    pub fn remove_context(&mut self, id: usize) {
170        self.backends.remove(id);
171    }
172
173    pub fn image_loaded(&self, path: &str) -> bool {
174        self.image_map.contains_key(path)
175    }
176}
177
178impl<R: piet::RenderContext> crate::api::RenderContext for Renderer<R> {
179    fn fill(&mut self, layer: usize, path: kurbo::BezPath, brush: &piet_common::PaintBrush) {
180        if let Some(layer) = self.backends.get_mut(layer) {
181            layer.fill(path, brush);
182        }
183    }
184
185    fn stroke(
186        &mut self,
187        layer: usize,
188        path: kurbo::BezPath,
189        brush: &piet_common::PaintBrush,
190        width: f64,
191    ) {
192        if let Some(layer) = self.backends.get_mut(layer) {
193            layer.stroke(path, brush, width);
194        }
195    }
196
197    fn save(&mut self, layer: usize) {
198        if let Some(layer) = self.backends.get_mut(layer) {
199            let _ = layer.save();
200        }
201    }
202
203    fn transform(&mut self, layer: usize, affine: Affine) {
204        if let Some(layer) = self.backends.get_mut(layer) {
205            layer.transform(affine);
206        }
207    }
208
209    fn clip(&mut self, layer: usize, path: kurbo::BezPath) {
210        if let Some(layer) = self.backends.get_mut(layer) {
211            layer.clip(path);
212        }
213    }
214
215    fn restore(&mut self, layer: usize) {
216        if let Some(layer) = self.backends.get_mut(layer) {
217            let _ = layer.restore();
218        }
219    }
220
221    fn load_image(&mut self, path: &str, buf: &[u8], width: usize, height: usize) {
222        //is this okay!? we know it's the same kind of backend no matter what layer, but it might be storing data?
223        let render_context = self.backends.first_mut().unwrap();
224        let img = render_context
225            .make_image(width, height, buf, piet::ImageFormat::RgbaSeparate)
226            .expect("image creation successful");
227        self.image_map.insert(
228            path.to_owned(),
229            ImgData {
230                img,
231                size: (width, height),
232            },
233        );
234    }
235
236    fn get_image_size(&mut self, image_path: &str) -> Option<(usize, usize)> {
237        self.image_map.get(image_path).map(|img| (img.size))
238    }
239
240    fn draw_image(&mut self, layer: usize, image_path: &str, rect: kurbo::Rect) {
241        let Some(data) = self.image_map.get(image_path) else {
242            return;
243        };
244        if let Some(layer) = self.backends.get_mut(layer) {
245            layer.draw_image(&data.img, rect, InterpolationMode::Bilinear);
246        }
247    }
248
249    fn layers(&self) -> usize {
250        self.backends.len()
251    }
252}
253
254/// Central instance of the PaxEngine and runtime, intended to be created by a particular chassis.
255/// Contains all rendering and runtime logic.
256///
257impl PaxEngine {
258    #[cfg(not(feature = "designtime"))]
259    pub fn new(
260        main_component_instance: Rc<ComponentInstance>,
261        viewport_size: (f64, f64),
262        platform: Platform,
263        os: OS,
264        get_elapsed_millis: Box<dyn Fn() -> u128>,
265    ) -> Self {
266        use crate::api::math::Transform2;
267        use pax_runtime_api::{properties, Functions};
268        Functions::register_all_functions();
269
270        let frames_elapsed = Property::new(0);
271        properties::register_time(&frames_elapsed);
272        let globals = Globals {
273            frames_elapsed,
274            viewport: Property::new(TransformAndBounds {
275                transform: Transform2::identity(),
276                bounds: viewport_size,
277            }),
278            platform,
279            os,
280            get_elapsed_millis: Rc::from(get_elapsed_millis),
281        };
282        let runtime_context = Rc::new(RuntimeContext::new(globals));
283        let root_node =
284            ExpandedNode::initialize_root(Rc::clone(&main_component_instance), &runtime_context);
285        runtime_context.register_root_expanded_node(&root_node);
286
287        PaxEngine {
288            runtime_context,
289            root_expanded_node: root_node,
290        }
291    }
292
293    #[cfg(feature = "designtime")]
294    pub fn new_with_designtime(
295        designer_main_component_instance: Rc<ComponentInstance>,
296        userland_main_component_instance: Rc<ComponentInstance>,
297        viewport_size: (f64, f64),
298        designtime: Rc<RefCell<DesigntimeManager>>,
299        platform: Platform,
300        os: OS,
301        get_elapsed_millis: Box<dyn Fn() -> u128>,
302    ) -> Self {
303        use pax_runtime_api::{math::Transform2, properties, Functions};
304        Functions::register_all_functions();
305
306        let frames_elapsed = Property::new(0);
307        properties::register_time(&frames_elapsed);
308        let globals = Globals {
309            frames_elapsed,
310            viewport: Property::new(TransformAndBounds {
311                transform: Transform2::identity(),
312                bounds: viewport_size,
313            }),
314            platform,
315            os,
316            designtime: designtime.clone(),
317            get_elapsed_millis: Rc::from(get_elapsed_millis),
318        };
319
320        let mut runtime_context = Rc::new(RuntimeContext::new(
321            globals,
322            userland_main_component_instance,
323        ));
324
325        let root_expanded_node = ExpandedNode::initialize_root(
326            Rc::clone(&designer_main_component_instance),
327            &mut runtime_context,
328        );
329        runtime_context.register_root_expanded_node(&root_expanded_node);
330
331        PaxEngine {
332            runtime_context,
333            root_expanded_node,
334        }
335    }
336
337    #[cfg(feature = "designtime")]
338    pub fn partial_update_expanded_node(&mut self, new_instance: Rc<dyn InstanceNode>) {
339        // update the expanded nodes that just got a new instance node
340        let unique_id = new_instance
341            .base()
342            .template_node_identifier
343            .clone()
344            .expect("new instance node has unique identifier");
345
346        let nodes = self
347            .runtime_context
348            .get_expanded_nodes_by_global_ids(&unique_id);
349        for node in nodes {
350            node.recreate_with_new_data(new_instance.clone(), &self.runtime_context);
351        }
352    }
353
354    #[cfg(feature = "designtime")]
355    pub fn full_reload_userland(&mut self, new_userland_instance: Rc<dyn InstanceNode>) {
356        let node = borrow!(self.runtime_context.userland_root_expanded_node)
357            .as_ref()
358            .map(Rc::clone)
359            .unwrap();
360        *borrow_mut!(self.runtime_context.userland_frame_instance_node) =
361            Rc::clone(&new_userland_instance);
362        node.fully_recreate_with_new_data(new_userland_instance.clone(), &self.runtime_context);
363    }
364
365    // NOTES: this is the order of different things being computed in recurse-expand-nodes
366    // - expanded_node instantiated from instance_node.
367
368    /// Workhorse methods of every tick.  Will be executed up to 240 Hz.
369    /// Three phases:
370    /// 1. Expand nodes & compute properties; recurse entire instance tree and evaluate ExpandedNodes, stitching
371    ///    together parent/child relationships between ExpandedNodes along the way.
372    /// 2. Compute layout (z-index & TransformAndBounds) by visiting ExpandedNode tree
373    ///    in rendering order, writing computed rendering-specific values to ExpandedNodes
374    /// 3. Render:
375    ///     a. find lowest node (last child of last node)
376    ///     b. start rendering, from lowest node on-up, throughout tree
377    pub fn tick(&mut self) -> Vec<NativeMessage> {
378        //
379        // 1. UPDATE NODES (properties, etc.). This part we should be able to
380        // completely remove once reactive properties dirty-dag is a thing.
381        //
382        self.root_expanded_node
383            .recurse_update(&mut self.runtime_context);
384
385        let ctx = &self.runtime_context;
386        occlusion::update_node_occlusion(&self.root_expanded_node, ctx);
387        let time = &ctx.globals().frames_elapsed;
388        time.set(time.get() + 1);
389
390        ctx.flush_custom_events().unwrap();
391        let native_messages = ctx.take_native_messages();
392        native_messages
393    }
394
395    pub fn render(&mut self, rcs: &mut dyn RenderContext) {
396        // This is pretty useful during debugging - left it here since I use it often. /Sam
397        // crate::api::log(&format!("tree: {:#?}", self.root_node));
398        self.root_expanded_node
399            .recurse_render_queue(&mut self.runtime_context, rcs);
400        self.runtime_context.recurse_flush_queued_renders(rcs);
401    }
402
403    pub fn get_expanded_node(&self, id: ExpandedNodeIdentifier) -> Option<Rc<ExpandedNode>> {
404        let val = self.runtime_context.get_expanded_node_by_eid(id).clone();
405        val.map(|v| (v.clone()))
406    }
407
408    /// Called by chassis when viewport size changes, e.g. with native window resizes
409    pub fn set_viewport_size(&mut self, new_viewport_size: (f64, f64)) {
410        self.runtime_context.edit_globals(|globals| {
411            globals
412                .viewport
413                .update(|t_and_b| t_and_b.bounds = new_viewport_size);
414        });
415    }
416
417    pub fn global_dispatch_focus(&self, args: Focus) -> bool {
418        let mut prevent_default = false;
419        self.root_expanded_node
420            .recurse_visit_postorder(&mut |expanded_node| {
421                prevent_default |= expanded_node.dispatch_focus(
422                    Event::new(args.clone()),
423                    &self.runtime_context.globals(),
424                    &self.runtime_context,
425                );
426            });
427        prevent_default
428    }
429
430    pub fn global_dispatch_select_start(&self, args: SelectStart) -> bool {
431        let mut prevent_default = false;
432        self.root_expanded_node
433            .recurse_visit_postorder(&mut |expanded_node| {
434                prevent_default |= expanded_node.dispatch_select_start(
435                    Event::new(args.clone()),
436                    &self.runtime_context.globals(),
437                    &self.runtime_context,
438                );
439            });
440        prevent_default
441    }
442
443    pub fn global_dispatch_key_down(&self, args: KeyDown) -> bool {
444        let mut prevent_default = false;
445        self.root_expanded_node
446            .recurse_visit_postorder(&mut |expanded_node| {
447                prevent_default |= expanded_node.dispatch_key_down(
448                    Event::new(args.clone()),
449                    &self.runtime_context.globals(),
450                    &self.runtime_context,
451                );
452            });
453        prevent_default
454    }
455
456    pub fn global_dispatch_key_up(&self, args: KeyUp) -> bool {
457        let mut prevent_default = false;
458        self.root_expanded_node
459            .recurse_visit_postorder(&mut |expanded_node| {
460                prevent_default |= expanded_node.dispatch_key_up(
461                    Event::new(args.clone()),
462                    &self.runtime_context.globals(),
463                    &self.runtime_context,
464                );
465            });
466        prevent_default
467    }
468
469    pub fn global_dispatch_key_press(&self, args: KeyPress) -> bool {
470        let mut prevent_default = false;
471        self.root_expanded_node
472            .recurse_visit_postorder(&mut |expanded_node| {
473                prevent_default |= expanded_node.dispatch_key_press(
474                    Event::new(args.clone()),
475                    &self.runtime_context.globals(),
476                    &self.runtime_context,
477                );
478            });
479        prevent_default
480    }
481}