Skip to main content

inset_rendering/
pipeline_owner.rs

1//! Flutter counterpart: `rendering/object.dart` (`PipelineOwner`).
2//!
3//! Semantics callbacks and the semantics half of [`PipelineManifold`] wait.
4
5use std::rc::Rc;
6
7use inset_foundation::{App, Handle, Listener, ValueChanged};
8
9use crate::layer::ErasedLayer;
10use crate::object::AnyRenderObject;
11use crate::painting_context::PaintingContext;
12
13/// Manages a tree of [`PipelineOwner`]s.
14///
15/// All [`PipelineOwner`]s within a tree are attached to the same
16/// [`PipelineManifold`], which gives them access to shared functionality such
17/// as requesting a visual update (by calling
18/// [`request_visual_update`](Self::request_visual_update)). As such, the
19/// [`PipelineManifold`] gives the [`PipelineOwner`]s access to functionality
20/// usually provided by the bindings without tying the [`PipelineOwner`]s to a
21/// particular binding implementation.
22///
23/// The root of the [`PipelineOwner`] tree is attached to a [`PipelineManifold`] by
24/// passing the manifold to [`PipelineOwner::attach`]. Children are attached to the
25/// same [`PipelineManifold`] as their parent when they are adopted via
26/// [`PipelineOwner::adopt_child`].
27///
28/// Dart's `semanticsEnabled` and the listeners that report its changes wait with
29/// semantics.
30pub trait PipelineManifold {
31    /// Called by a [`PipelineOwner`] connected to this [`PipelineManifold`] when a
32    /// `RenderObject` associated with that pipeline owner wishes to update its
33    /// visual appearance.
34    ///
35    /// Typical implementations of this function will schedule a task to flush the
36    /// various stages of the pipeline. This function might be called multiple
37    /// times in quick succession. Implementations should take care to discard
38    /// duplicate calls quickly.
39    ///
40    /// A [`PipelineOwner`] connected to this [`PipelineManifold`] will call its
41    /// `on_need_visual_update` callback instead of this method if it has been
42    /// configured with one ([`PipelineOwner::new`]).
43    ///
44    /// See also:
45    ///
46    ///  * `SchedulerBinding::ensure_visual_update`, which [`PipelineManifold`]
47    ///    implementations typically call to implement this method.
48    fn request_visual_update(&self, app: &mut App);
49}
50
51/// Signature for a function that is called when the pipeline owner and child owner
52/// finished the paint phase.
53///
54/// The `bool` is `is_dirty`: whether the pipeline owner had dirty render objects that needed to
55/// be painted.
56pub type FlushedPaintCallback = ValueChanged<bool>;
57
58/// The pipeline owner manages the rendering pipeline.
59///
60/// Flutter's counterpart is `PipelineOwner`.
61pub struct PipelineOwner {
62    on_need_visual_update: Option<Listener>,
63    /// Called when this pipeline owner and child owner have finished the paint phase.
64    ///
65    /// The `is_dirty` parameter is true if the pipeline owner has dirty render objects that
66    /// needed to be painted.
67    on_flushed_paint: Option<FlushedPaintCallback>,
68    manifold: Option<Rc<dyn PipelineManifold>>,
69    root_node: Option<AnyRenderObject>,
70    nodes_needing_layout: Vec<AnyRenderObject>,
71    nodes_needing_compositing_bits_update: Vec<AnyRenderObject>,
72    should_merge_dirty_nodes: bool,
73    /// Dart's `_debugAllowMutationsToDirtySubtrees`.
74    debug_allow_mutations_to_dirty_subtrees: bool,
75    debug_doing_layout: bool,
76    debug_doing_child_layout: bool,
77    nodes_needing_paint: Vec<AnyRenderObject>,
78    debug_doing_paint: bool,
79    children: Vec<Handle<PipelineOwner>>,
80    debug_parent: Option<Handle<PipelineOwner>>,
81}
82
83impl PipelineOwner {
84    /// Creates a pipeline owner.
85    ///
86    /// Typically created by the binding, but can be created separately to drive
87    /// off-screen render objects through the rendering pipeline.
88    pub fn new(
89        app: &mut App,
90        on_need_visual_update: Option<Listener>,
91        on_flushed_paint: Option<FlushedPaintCallback>,
92    ) -> Handle<PipelineOwner> {
93        app.create(PipelineOwner {
94            on_need_visual_update,
95            on_flushed_paint,
96            manifold: None,
97            root_node: None,
98            nodes_needing_layout: Vec::new(),
99            nodes_needing_compositing_bits_update: Vec::new(),
100            should_merge_dirty_nodes: false,
101            debug_allow_mutations_to_dirty_subtrees: false,
102            debug_doing_layout: false,
103            debug_doing_child_layout: false,
104            nodes_needing_paint: Vec::new(),
105            debug_doing_paint: false,
106            children: Vec::new(),
107            debug_parent: None,
108        })
109    }
110
111    /// Calls the `on_need_visual_update` callback given to [`new`](Self::new) if there is
112    /// one, otherwise asks the [`PipelineManifold`] this owner is attached to.
113    ///
114    /// Used to notify the pipeline owner that an associated render object wishes
115    /// to update its visual appearance.
116    pub fn request_visual_update(self: Handle<Self>, app: &mut App) {
117        if let Some(callback) = app.get(self).on_need_visual_update.clone() {
118            callback.call(app);
119        } else if let Some(manifold) = app.get(self).manifold.clone() {
120            manifold.request_visual_update(app);
121        }
122    }
123
124    /// The unique object managed by this pipeline that has no parent.
125    pub fn root_node(self: Handle<Self>, app: &App) -> Option<AnyRenderObject> {
126        app.get(self).root_node
127    }
128
129    /// Sets the unique object managed by this pipeline that has no parent.
130    pub fn set_root_node(self: Handle<Self>, app: &mut App, value: Option<AnyRenderObject>) {
131        let current = app.get(self).root_node;
132        if current == value {
133            return;
134        }
135        if let Some(old) = current {
136            old.detach(app);
137        }
138        app.get_mut(self).root_node = value;
139        if let Some(new) = value {
140            new.attach(app, self);
141        }
142    }
143
144    /// Relayout boundaries which need to be laid out in the next
145    /// [`flush_layout`](Self::flush_layout) pass.
146    pub fn nodes_needing_layout(self: Handle<Self>, app: &App) -> Vec<AnyRenderObject> {
147        app.get(self).nodes_needing_layout.clone()
148    }
149
150    pub(crate) fn add_node_needing_layout(
151        self: Handle<Self>,
152        app: &mut App,
153        node: AnyRenderObject,
154    ) {
155        app.get_mut(self).nodes_needing_layout.push(node);
156    }
157
158    pub(crate) fn add_node_needing_compositing_bits_update(
159        self: Handle<Self>,
160        app: &mut App,
161        node: AnyRenderObject,
162    ) {
163        app.get_mut(self)
164            .nodes_needing_compositing_bits_update
165            .push(node);
166    }
167
168    pub(crate) fn remove_node_needing_paint(
169        self: Handle<Self>,
170        app: &mut App,
171        node: AnyRenderObject,
172    ) {
173        app.get_mut(self)
174            .nodes_needing_paint
175            .retain(|candidate| *candidate != node);
176    }
177
178    /// Whether this pipeline is currently in the layout phase.
179    ///
180    /// Always `false` when debug assertions are disabled.
181    pub fn debug_doing_layout(self: Handle<Self>, app: &App) -> bool {
182        if !cfg!(debug_assertions) {
183            return false;
184        }
185        app.get(self).debug_doing_layout
186    }
187
188    /// See [`AnyRenderObject::invoke_layout_callback`]: runs `callback` with mutations to
189    /// dirty subtrees allowed, then asks [`flush_layout`](Self::flush_layout) to merge the
190    /// nodes it dirtied before continuing.
191    pub(crate) fn enable_mutations_to_dirty_subtrees(
192        self: Handle<Self>,
193        app: &mut App,
194        callback: impl FnOnce(&mut App),
195    ) {
196        debug_assert!(app.get(self).debug_doing_layout);
197        let old_state = app.get(self).debug_allow_mutations_to_dirty_subtrees;
198        if cfg!(debug_assertions) {
199            app.get_mut(self).debug_allow_mutations_to_dirty_subtrees = true;
200        }
201        callback(app);
202        app.get_mut(self).should_merge_dirty_nodes = true;
203        if cfg!(debug_assertions) {
204            app.get_mut(self).debug_allow_mutations_to_dirty_subtrees = old_state;
205        }
206    }
207
208    /// Update the layout information for all dirty render objects.
209    pub fn flush_layout(self: Handle<Self>, app: &mut App) {
210        if cfg!(debug_assertions) {
211            app.get_mut(self).debug_doing_layout = true;
212        }
213        while !app.get(self).nodes_needing_layout.is_empty() {
214            debug_assert!(!app.get(self).should_merge_dirty_nodes);
215            let mut dirty_nodes = std::mem::take(&mut app.get_mut(self).nodes_needing_layout);
216            dirty_nodes.sort_by_key(|node| node.depth(app));
217            let mut i = 0;
218            while i < dirty_nodes.len() {
219                if app.get(self).should_merge_dirty_nodes {
220                    app.get_mut(self).should_merge_dirty_nodes = false;
221                    if !app.get(self).nodes_needing_layout.is_empty() {
222                        app.get_mut(self)
223                            .nodes_needing_layout
224                            .extend(dirty_nodes.drain(i..));
225                        break;
226                    }
227                }
228                let node = dirty_nodes[i];
229                if node.needs_layout(app) && node.owner(app) == Some(self) {
230                    node.layout_without_resize(app);
231                }
232                i += 1;
233            }
234            app.get_mut(self).should_merge_dirty_nodes = false;
235        }
236        if cfg!(debug_assertions) {
237            app.get_mut(self).debug_doing_child_layout = true;
238        }
239        let children = app.get(self).children.clone();
240        for child in children {
241            child.flush_layout(app);
242        }
243        debug_assert!(
244            app.get(self).nodes_needing_layout.is_empty(),
245            "Child PipelineOwners must not dirty nodes in their parent."
246        );
247        app.get_mut(self).should_merge_dirty_nodes = false;
248        if cfg!(debug_assertions) {
249            app.get_mut(self).debug_doing_layout = false;
250            app.get_mut(self).debug_doing_child_layout = false;
251        }
252    }
253
254    /// Updates the [`AnyRenderObject::needs_compositing`] bits.
255    ///
256    /// Called as part of the rendering pipeline after [`flush_layout`](Self::flush_layout) and
257    /// before [`flush_paint`](Self::flush_paint).
258    pub fn flush_compositing_bits(self: Handle<Self>, app: &mut App) {
259        let mut dirty_nodes =
260            std::mem::take(&mut app.get_mut(self).nodes_needing_compositing_bits_update);
261        dirty_nodes.sort_by_key(|node| node.depth(app));
262        for node in dirty_nodes {
263            if node.needs_compositing_bits_update(app) && node.owner(app) == Some(self) {
264                node.update_compositing_bits(app);
265            }
266        }
267        let children = app.get(self).children.clone();
268        for child in children {
269            child.flush_compositing_bits(app);
270        }
271        debug_assert!(
272            app.get(self)
273                .nodes_needing_compositing_bits_update
274                .is_empty(),
275            "Child PipelineOwners must not dirty nodes in their parent."
276        );
277    }
278
279    /// Nodes with a dirty layer or paint state, to be updated in the next
280    /// [`flush_paint`](Self::flush_paint) pass.
281    pub fn nodes_needing_paint(self: Handle<Self>, app: &App) -> Vec<AnyRenderObject> {
282        app.get(self).nodes_needing_paint.clone()
283    }
284
285    pub(crate) fn add_node_needing_paint(self: Handle<Self>, app: &mut App, node: AnyRenderObject) {
286        app.get_mut(self).nodes_needing_paint.push(node);
287    }
288
289    /// Whether this pipeline is currently in the paint phase.
290    ///
291    /// Only meaningful when asserts are enabled.
292    pub fn debug_doing_paint(self: Handle<Self>, app: &App) -> bool {
293        app.get(self).debug_doing_paint
294    }
295
296    /// Update the display lists for all render objects.
297    ///
298    /// This function is one of the core stages of the rendering pipeline. Painting occurs after
299    /// layout and before the scene is recomposited so that scene is composited with up-to-date
300    /// display lists for every render object.
301    ///
302    /// A frame paints each of its views by
303    /// [`flush_paint_without_children`](Self::flush_paint_without_children) instead.
304    pub fn flush_paint(self: Handle<Self>, app: &mut App) {
305        if cfg!(debug_assertions) {
306            app.get_mut(self).debug_doing_paint = true;
307        }
308        let is_dirty = self.paint_dirty_nodes(app);
309        let children = app.get(self).children.clone();
310        for child in children {
311            child.flush_paint(app);
312        }
313        self.did_flush_paint(app, is_dirty);
314        debug_assert!(
315            app.get(self).nodes_needing_paint.is_empty(),
316            "Child PipelineOwners must not dirty nodes in their parent."
317        );
318        if cfg!(debug_assertions) {
319            app.get_mut(self).debug_doing_paint = false;
320        }
321    }
322
323    /// [`flush_paint`](Self::flush_paint) for this owner's render objects alone: the views
324    /// anchored in a view, whose owners are its owner's children, paint at frames of their own.
325    pub fn flush_paint_without_children(self: Handle<Self>, app: &mut App) {
326        if cfg!(debug_assertions) {
327            app.get_mut(self).debug_doing_paint = true;
328        }
329        let is_dirty = self.paint_dirty_nodes(app);
330        self.did_flush_paint(app, is_dirty);
331        if cfg!(debug_assertions) {
332            app.get_mut(self).debug_doing_paint = false;
333        }
334    }
335
336    /// Calls the `on_flushed_paint` callback given to [`new`](Self::new), if there is one.
337    fn did_flush_paint(self: Handle<Self>, app: &mut App, is_dirty: bool) {
338        if let Some(on_flushed_paint) = app.get(self).on_flushed_paint.clone() {
339            on_flushed_paint(app, is_dirty);
340        }
341    }
342
343    /// The paint pass over this owner's dirty render objects, deepest first; answers whether
344    /// there were any.
345    fn paint_dirty_nodes(self: Handle<Self>, app: &mut App) -> bool {
346        let mut dirty_nodes = std::mem::take(&mut app.get_mut(self).nodes_needing_paint);
347        let is_dirty = !dirty_nodes.is_empty();
348        dirty_nodes.sort_by_key(|node| std::cmp::Reverse(node.depth(app)));
349        for node in dirty_nodes {
350            debug_assert!(node.layer(app).is_some() || !cfg!(debug_assertions));
351            if (node.needs_paint(app) || node.needs_composited_layer_update(app))
352                && node.owner(app) == Some(self)
353            {
354                if node
355                    .layer(app)
356                    .is_some_and(|layer| layer.as_layer().attached(app))
357                {
358                    debug_assert!(node.is_repaint_boundary(app));
359                    if node.needs_paint(app) {
360                        PaintingContext::repaint_composited_child(app, node);
361                    } else {
362                        PaintingContext::update_layer_properties(app, node);
363                    }
364                } else {
365                    node.skipped_painting_on_layer(app);
366                }
367            }
368        }
369        is_dirty
370    }
371
372    /// Mark this [`PipelineOwner`] as attached to the given [`PipelineManifold`].
373    ///
374    /// Typically, this is only called directly on the root [`PipelineOwner`].
375    /// Children are automatically attached to their parent's [`PipelineManifold`]
376    /// when [`adopt_child`](Self::adopt_child) is called.
377    pub fn attach(self: Handle<Self>, app: &mut App, manifold: Rc<dyn PipelineManifold>) {
378        debug_assert!(app.get(self).manifold.is_none());
379        app.get_mut(self).manifold = Some(Rc::clone(&manifold));
380        let children = app.get(self).children.clone();
381        for child in children {
382            child.attach(app, Rc::clone(&manifold));
383        }
384    }
385
386    /// Mark this [`PipelineOwner`] as detached.
387    ///
388    /// Typically, this is only called directly on the root [`PipelineOwner`].
389    /// Children are automatically detached from their parent's [`PipelineManifold`]
390    /// when [`drop_child`](Self::drop_child) is called.
391    pub fn detach(self: Handle<Self>, app: &mut App) {
392        debug_assert!(app.get(self).manifold.is_some());
393        app.get_mut(self).manifold = None;
394        let children = app.get(self).children.clone();
395        for child in children {
396            child.detach(app);
397        }
398    }
399
400    /// Adds `child` to this [`PipelineOwner`].
401    pub fn adopt_child(self: Handle<Self>, app: &mut App, child: Handle<PipelineOwner>) {
402        debug_assert!(app.get(child).debug_parent.is_none());
403        debug_assert!(!app.get(self).children.contains(&child));
404        debug_assert!(
405            !app.get(self).debug_doing_child_layout,
406            "Cannot modify child list after layout."
407        );
408        app.get_mut(self).children.push(child);
409        if cfg!(debug_assertions) {
410            app.get_mut(child).debug_parent = Some(self);
411        }
412        if let Some(manifold) = app.get(self).manifold.clone() {
413            child.attach(app, manifold);
414        }
415    }
416
417    /// Removes a child [`PipelineOwner`] previously added via
418    /// [`adopt_child`](Self::adopt_child).
419    pub fn drop_child(self: Handle<Self>, app: &mut App, child: Handle<PipelineOwner>) {
420        debug_assert_eq!(app.get(child).debug_parent, Some(self));
421        debug_assert!(app.get(self).children.contains(&child));
422        debug_assert!(
423            !app.get(self).debug_doing_child_layout,
424            "Cannot modify child list after layout."
425        );
426        let children = &mut app.get_mut(self).children;
427        let index = children
428            .iter()
429            .position(|&candidate| candidate == child)
430            .expect("child was in the list");
431        children.remove(index);
432        if cfg!(debug_assertions) {
433            app.get_mut(child).debug_parent = None;
434        }
435        if app.get(self).manifold.is_some() {
436            child.detach(app);
437        }
438    }
439
440    /// Calls `visitor` for each immediate child of this [`PipelineOwner`].
441    pub fn visit_children(
442        self: Handle<Self>,
443        app: &App,
444        visitor: &mut dyn FnMut(Handle<PipelineOwner>),
445    ) {
446        for child in &app.get(self).children {
447            visitor(*child);
448        }
449    }
450}
451
452#[cfg(test)]
453mod tests {
454    use std::cell::{Cell, RefCell};
455    use std::rc::Rc;
456
457    use inset_embedder::Size;
458    use inset_foundation::{App, AppCell};
459
460    use super::*;
461    use crate::box_::{RenderBox, RenderBoxData};
462    use crate::object::{RenderHandle, RenderObject, RenderObjectData};
463
464    struct TestLeaf {
465        render_object: RenderObjectData,
466        render_box: RenderBoxData,
467        on_layout: Option<Rc<dyn Fn()>>,
468    }
469
470    impl RenderObject for TestLeaf {
471        crate::render_object_accessors!();
472
473        fn perform_layout(self: RenderHandle<Self>, app: &mut App) {
474            if let Some(on_layout) = self.get(app).on_layout.clone() {
475                on_layout();
476            }
477            let size = self
478                .render_box_data(app)
479                .constraints
480                .map(crate::box_::BoxConstraints::smallest)
481                .unwrap_or(Size::ZERO);
482            self.set_size(app, size);
483        }
484    }
485
486    impl RenderBox for TestLeaf {
487        crate::render_box_accessors!();
488    }
489
490    fn leaf(on_layout: Option<Rc<dyn Fn()>>) -> TestLeaf {
491        TestLeaf {
492            render_object: RenderObjectData::new(),
493            render_box: RenderBoxData::new(),
494            on_layout,
495        }
496    }
497
498    /// `pipeline_owner_tree_test.dart`: parent's render objects are laid out
499    /// before child's render objects.
500    #[test]
501    fn parent_pipeline_lays_out_before_child_pipeline() {
502        let cell = AppCell::new();
503        let mut app = cell.borrow_mut();
504        let log = Rc::new(RefCell::new(Vec::new()));
505
506        let root_log = Rc::clone(&log);
507        let root_node = RenderHandle::new_box(
508            &mut app,
509            leaf(Some(Rc::new(move || {
510                root_log.borrow_mut().push("layout parent")
511            }))),
512        );
513        let root = PipelineOwner::new(&mut app, None, None);
514        root.set_root_node(&mut app, Some(root_node.as_object()));
515        root_node.schedule_initial_layout(&mut app);
516
517        let child_log = Rc::clone(&log);
518        let child_node = RenderHandle::new_box(
519            &mut app,
520            leaf(Some(Rc::new(move || {
521                child_log.borrow_mut().push("layout child")
522            }))),
523        );
524        let child = PipelineOwner::new(&mut app, None, None);
525        child.set_root_node(&mut app, Some(child_node.as_object()));
526        child_node.schedule_initial_layout(&mut app);
527
528        root.adopt_child(&mut app, child);
529        root.flush_layout(&mut app);
530        assert_eq!(*log.borrow(), ["layout parent", "layout child"]);
531    }
532
533    #[test]
534    fn set_root_node_attaches_and_replacing_detaches() {
535        let cell = AppCell::new();
536        let mut app = cell.borrow_mut();
537        let owner = PipelineOwner::new(&mut app, None, None);
538        let first = RenderHandle::new_box(&mut app, leaf(None));
539        let second = RenderHandle::new_box(&mut app, leaf(None));
540        owner.set_root_node(&mut app, Some(first.as_object()));
541        assert!(first.attached(&app));
542        assert_eq!(first.owner(&app), Some(owner));
543
544        owner.set_root_node(&mut app, Some(second.as_object()));
545        assert!(!first.attached(&app));
546        assert!(second.attached(&app));
547    }
548
549    #[test]
550    fn schedule_initial_layout_then_flush() {
551        let cell = AppCell::new();
552        let mut app = cell.borrow_mut();
553        let owner = PipelineOwner::new(&mut app, None, None);
554        let node = RenderHandle::new_box(&mut app, leaf(None));
555        owner.set_root_node(&mut app, Some(node.as_object()));
556        node.schedule_initial_layout(&mut app);
557        assert!(node.debug_needs_layout(&app));
558        owner.flush_layout(&mut app);
559        assert!(!node.debug_needs_layout(&app));
560        assert_eq!(node.size(&app), Size::ZERO);
561    }
562
563    /// Counts the visual updates requested through it.
564    struct CountingManifold {
565        requests: Cell<u32>,
566    }
567
568    impl PipelineManifold for CountingManifold {
569        fn request_visual_update(&self, _app: &mut App) {
570            self.requests.set(self.requests.get() + 1);
571        }
572    }
573
574    #[test]
575    fn an_owner_without_a_callback_requests_visual_updates_through_its_manifold() {
576        let cell = AppCell::new();
577        let mut app = cell.borrow_mut();
578        let manifold = Rc::new(CountingManifold {
579            requests: Cell::new(0),
580        });
581        let root = PipelineOwner::new(&mut app, None, None);
582        let adopted_before = PipelineOwner::new(&mut app, None, None);
583        root.adopt_child(&mut app, adopted_before);
584
585        adopted_before.request_visual_update(&mut app);
586        assert_eq!(
587            manifold.requests.get(),
588            0,
589            "unattached owners request nothing"
590        );
591
592        root.attach(&mut app, manifold.clone());
593        root.request_visual_update(&mut app);
594        adopted_before.request_visual_update(&mut app);
595        assert_eq!(
596            manifold.requests.get(),
597            2,
598            "attach reaches existing children"
599        );
600
601        let adopted_after = PipelineOwner::new(&mut app, None, None);
602        root.adopt_child(&mut app, adopted_after);
603        adopted_after.request_visual_update(&mut app);
604        assert_eq!(manifold.requests.get(), 3, "adopt_child attaches the child");
605
606        root.drop_child(&mut app, adopted_after);
607        adopted_after.request_visual_update(&mut app);
608        assert_eq!(manifold.requests.get(), 3, "drop_child detaches the child");
609
610        root.detach(&mut app);
611        adopted_before.request_visual_update(&mut app);
612        assert_eq!(manifold.requests.get(), 3, "detach reaches the children");
613    }
614
615    #[test]
616    fn a_callback_takes_precedence_over_the_manifold() {
617        let cell = AppCell::new();
618        let mut app = cell.borrow_mut();
619        let manifold = Rc::new(CountingManifold {
620            requests: Cell::new(0),
621        });
622        let callbacks = Rc::new(Cell::new(0));
623        let owner = PipelineOwner::new(
624            &mut app,
625            Some(Listener::new({
626                let callbacks = callbacks.clone();
627                move |_app| callbacks.set(callbacks.get() + 1)
628            })),
629            None,
630        );
631        owner.attach(&mut app, manifold.clone());
632        owner.request_visual_update(&mut app);
633        assert_eq!((callbacks.get(), manifold.requests.get()), (1, 0));
634    }
635}