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