Skip to main content

windows_reactor/core/pump/
mod.rs

1use crate::reference::{HostRequest, ImperativeEndpoint, ImperativeRequest, NativeElementRef};
2use rustc_hash::{FxHashMap, FxHashSet};
3use std::collections::{HashMap, HashSet, VecDeque};
4use std::rc::Rc;
5
6use super::*;
7
8type IdMap<K, V> = FxHashMap<K, V>;
9type IdSet<T> = FxHashSet<T>;
10
11mod lifecycle;
12mod native_work;
13mod plan;
14mod planner;
15mod publish;
16#[cfg(test)]
17mod tests;
18mod turn;
19
20use plan::*;
21
22#[cfg(test)]
23use native_work::{EVENT_WORK_BUDGET, REALIZATION_WORK_BUDGET};
24
25#[derive(Clone, Debug, Eq, PartialEq)]
26pub enum PumpError {
27    AlreadyMounted,
28    NotMounted,
29    DuplicateEffectKey(EffectKey),
30    DuplicateElementRef,
31    DuplicateKey(Key),
32    DuplicateColorSchemeObservation,
33    DuplicateWindowSizeObservation,
34    DuplicateWindowTitle,
35    DuplicateWindowTitleBar,
36    DuplicateWindowVisuals,
37    ExitTransitionUnsupported,
38    EventReadFailed(RuntimeError),
39    NativeApplyFailed(NativeApplyError),
40    Poisoned,
41    StructureUnsupported,
42}
43
44impl PumpError {
45    pub(crate) fn is_declaration_rejection(&self) -> bool {
46        matches!(
47            self,
48            Self::DuplicateEffectKey(_)
49                | Self::DuplicateElementRef
50                | Self::DuplicateKey(_)
51                | Self::DuplicateColorSchemeObservation
52                | Self::DuplicateWindowSizeObservation
53                | Self::DuplicateWindowTitle
54                | Self::DuplicateWindowTitleBar
55                | Self::DuplicateWindowVisuals
56                | Self::ExitTransitionUnsupported
57                | Self::StructureUnsupported
58        )
59    }
60}
61
62impl From<ComponentDeclarationError> for PumpError {
63    fn from(value: ComponentDeclarationError) -> Self {
64        match value {
65            ComponentDeclarationError::EffectKey(key) => Self::DuplicateEffectKey(key),
66            ComponentDeclarationError::ColorSchemeObservation => {
67                Self::DuplicateColorSchemeObservation
68            }
69            ComponentDeclarationError::WindowSizeObservation => {
70                Self::DuplicateWindowSizeObservation
71            }
72            ComponentDeclarationError::WindowTitle => Self::DuplicateWindowTitle,
73            ComponentDeclarationError::WindowVisuals => Self::DuplicateWindowVisuals,
74        }
75    }
76}
77
78#[derive(Clone, Debug, Eq, PartialEq)]
79pub enum PumpDiagnostic {
80    WindowOpenRejected {
81        error: RuntimeError,
82    },
83    VirtualRowRootCount {
84        collection: NodeId,
85        key: Key,
86        actual: usize,
87    },
88}
89
90pub struct Pump<R: NativeRuntime> {
91    application: Option<NodeId>,
92    components: ComponentStore,
93    diagnostics: VecDeque<PumpDiagnostic>,
94    dirty_components: IdSet<ComponentToken>,
95    #[cfg(test)]
96    element: Option<Element>,
97    tree: Tree,
98    runtime: R,
99    root: Option<NodeId>,
100    events: VecDeque<NativeWork<QueuedEvent>>,
101    host_events: VecDeque<NativeWork<HostEvent>>,
102    imperative: ImperativeEndpoint,
103    identity: WindowToken,
104    native_observation_pending: bool,
105    last_native_observation: Option<(NodeId, EventId)>,
106    planning_dirty: IdSet<ComponentToken>,
107    poisoned: bool,
108    realizations: VecDeque<NativeWork<RealizationRequest>>,
109    reset_on_drop: bool,
110    trace_component_plans: bool,
111    version: u64,
112    window: Option<NodeId>,
113}
114
115impl<R: NativeRuntime> Pump<R> {
116    pub fn new(mut runtime: R) -> Self {
117        let identity = WindowToken::new(WindowId::allocate());
118        runtime.set_identity(identity);
119        let mut components = ComponentStore::new(identity);
120        if let Some(wake) = runtime.component_waker() {
121            components.set_waker(wake);
122        }
123        if let Some(wake) = runtime.component_background_waker() {
124            components.set_background_waker(wake);
125        }
126        let imperative = ImperativeEndpoint::new(runtime.component_waker());
127        Self {
128            application: None,
129            components,
130            diagnostics: VecDeque::new(),
131            dirty_components: IdSet::default(),
132            #[cfg(test)]
133            element: None,
134            tree: Tree::new(),
135            runtime,
136            root: None,
137            events: VecDeque::new(),
138            host_events: VecDeque::new(),
139            imperative,
140            identity,
141            native_observation_pending: false,
142            last_native_observation: None,
143            planning_dirty: IdSet::default(),
144            poisoned: false,
145            realizations: VecDeque::new(),
146            reset_on_drop: true,
147            trace_component_plans: cfg!(debug_assertions)
148                && std::env::var("WINDOWS_REACTOR_TRACE")
149                    .is_ok_and(|value| value == "1" || value.eq_ignore_ascii_case("true")),
150            version: 0,
151            window: None,
152        }
153    }
154
155    #[cfg(test)]
156    pub(crate) fn mount(&mut self, element: Element) -> Result<(), PumpError> {
157        if self.poisoned {
158            return Err(PumpError::Poisoned);
159        }
160
161        if self.root.is_some() {
162            return Err(PumpError::AlreadyMounted);
163        }
164        let next_version = self.next_version();
165        let desired = element.clone();
166        let mut candidate = Tree::new();
167        let mut plan = UpdatePlan::new(self.identity);
168        let application = candidate.insert(None, NodeKind::Application);
169        plan.push(Command::CreateApplication { node: application });
170        let window = candidate.insert(Some(application), NodeKind::Window);
171        plan.push(Command::CreateWindow { node: window });
172        let node =
173            Self::mount_planned_element(&mut candidate, Some(window), None, element, &mut plan)?;
174        plan.push(Command::InsertChild {
175            parent: window,
176            slot: None,
177            child: node,
178            index: 0,
179        });
180        Self::plan_window_title_bar(window, &self.tree, &candidate, &mut plan)?;
181        plan.push(Command::ActivateWindow { node: window });
182
183        self.publish_candidate(
184            CandidateState::Tree {
185                tree: candidate,
186                root: node,
187            },
188            plan,
189            FrontendChanges::Element(desired),
190            next_version,
191            PlanningFailure::Discard,
192        )?;
193        self.application = Some(application);
194        self.window = Some(window);
195        Ok(())
196    }
197
198    pub fn mount_view(&mut self, view: View) -> Result<(), PumpError> {
199        if self.poisoned {
200            return Err(PumpError::Poisoned);
201        }
202        if self.root.is_some() {
203            return Err(PumpError::AlreadyMounted);
204        }
205        let next_version = self.next_version();
206        let mut candidate = Tree::new();
207        let mut plan = UpdatePlan::new(self.identity);
208        let mut changes = ComponentChanges::default();
209        let application = candidate.insert(None, NodeKind::Application);
210        plan.push(Command::CreateApplication { node: application });
211        let window = candidate.insert(Some(application), NodeKind::Window);
212        plan.push(Command::CreateWindow { node: window });
213        let mounted = Self::mount_planned_view(
214            &mut candidate,
215            Some(window),
216            None,
217            view,
218            &mut self.components,
219            &mut changes,
220            &mut plan,
221        );
222        let (root, native_roots) = match mounted {
223            Ok(mounted) => mounted,
224            Err(error) => {
225                self.fail_component_candidate(&changes, PlanningFailure::Discard);
226                return Err(error);
227            }
228        };
229        match native_roots.as_slice() {
230            [] => {}
231            [native_root] => {
232                plan.push(Command::InsertChild {
233                    parent: window,
234                    slot: None,
235                    child: *native_root,
236                    index: 0,
237                });
238            }
239            _ => {
240                self.fail_component_candidate(&changes, PlanningFailure::Discard);
241                return Err(PumpError::StructureUnsupported);
242            }
243        }
244        self.finalize_component_candidate(ComponentCandidate {
245            activate_window: true,
246            changes,
247            next_version,
248            plan,
249            planning_failure: PlanningFailure::Discard,
250            root,
251            tree: candidate,
252            window,
253        })?;
254        self.application = Some(application);
255        self.window = Some(window);
256        Ok(())
257    }
258
259    #[cfg(any(test, feature = "test"))]
260    pub fn update_view(&mut self, view: View) -> Result<(), PumpError> {
261        if self.poisoned {
262            return Err(PumpError::Poisoned);
263        }
264        let next_version = self.next_version();
265        let root = self.root.ok_or(PumpError::NotMounted)?;
266        let mut candidate = self.tree.clone();
267        let mut plan = UpdatePlan {
268            reconcile_observations: self.native_observation_pending,
269            ..UpdatePlan::new(self.identity)
270        };
271        let mut changes = ComponentChanges {
272            recompose: self.planning_dirty.clone(),
273            ..ComponentChanges::default()
274        };
275        if let Err(error) = Self::reconcile_planned_view(
276            &mut candidate,
277            root,
278            view,
279            &mut self.components,
280            &mut changes,
281            &mut plan,
282        ) {
283            self.fail_component_candidate(&changes, PlanningFailure::Rearm);
284            return Err(error);
285        }
286        let window = self.window.ok_or(PumpError::NotMounted)?;
287        let candidate_root = if let [candidate_root] = candidate.children(window) {
288            *candidate_root
289        } else {
290            self.fail_component_candidate(&changes, PlanningFailure::Rearm);
291            return Err(PumpError::StructureUnsupported);
292        };
293        self.apply_component_candidate(candidate, candidate_root, plan, changes, next_version)
294    }
295
296    #[cfg(test)]
297    pub(crate) fn update(&mut self, element: Element) -> Result<(), PumpError> {
298        if matches!(element.structure(), ElementStructureRef::Virtual(_)) {
299            let desired_element = element.clone();
300            self.update_view(View::native(element))?;
301            self.element = Some(desired_element);
302            return Ok(());
303        }
304        if self.poisoned {
305            return Err(PumpError::Poisoned);
306        }
307        let next_version = self.next_version();
308        if !self.native_observation_pending
309            && self.element.as_ref() == Some(&element)
310            && Self::node_matches_element(
311                &self.tree,
312                self.root.ok_or(PumpError::NotMounted)?,
313                &element,
314            )?
315        {
316            self.version = next_version;
317            return Ok(());
318        }
319        let node = self.root.ok_or(PumpError::NotMounted)?;
320        let desired_element = element.clone();
321        let mut candidate = self.tree.clone();
322        let mut plan = UpdatePlan {
323            reconcile_observations: self.native_observation_pending,
324            ..UpdatePlan::new(self.identity)
325        };
326        let candidate_root = Self::reconcile_node(&mut candidate, node, element, &mut plan)?;
327        let window = self.window.ok_or(PumpError::NotMounted)?;
328        Self::plan_window_title_bar(window, &self.tree, &candidate, &mut plan)?;
329        self.publish_candidate(
330            CandidateState::Tree {
331                tree: candidate,
332                root: candidate_root,
333            },
334            plan,
335            FrontendChanges::Element(desired_element),
336            next_version,
337            PlanningFailure::Discard,
338        )
339    }
340
341    pub fn runtime(&self) -> &R {
342        &self.runtime
343    }
344
345    pub fn drain_diagnostics(&mut self) -> Vec<PumpDiagnostic> {
346        self.diagnostics.drain(..).collect()
347    }
348
349    #[cfg(any(test, feature = "test"))]
350    pub fn application(&self) -> Option<NodeId> {
351        self.application
352    }
353
354    #[cfg(any(test, feature = "test"))]
355    pub fn runtime_mut(&mut self) -> &mut R {
356        &mut self.runtime
357    }
358
359    pub fn shutdown(&mut self) {
360        let identity = self.identity.next();
361        self.cleanup_component_effects();
362        self.clear_published_references();
363        self.imperative.complete_unavailable();
364        self.runtime.reset();
365        self.application = None;
366        #[cfg(test)]
367        {
368            self.element = None;
369        }
370        self.dirty_components.clear();
371        self.diagnostics.clear();
372        self.events.clear();
373        self.host_events.clear();
374        self.realizations.clear();
375        self.native_observation_pending = false;
376        self.last_native_observation = None;
377        self.planning_dirty.clear();
378        self.root = None;
379        self.tree = Tree::new();
380        self.version = 0;
381        self.window = None;
382        self.identity = identity;
383        self.runtime.set_identity(identity);
384        self.imperative = ImperativeEndpoint::new(self.runtime.component_waker());
385        let mut components = self.components.restarted(identity);
386        if let Some(wake) = self.runtime.component_waker() {
387            components.set_waker(wake);
388        }
389        if let Some(wake) = self.runtime.component_background_waker() {
390            components.set_background_waker(wake);
391        }
392        self.components = components;
393        self.poisoned = false;
394    }
395
396    fn cleanup_component_effects(&mut self) {
397        let Some(root) = self.root else {
398            return;
399        };
400        for node in self.tree.subtree_postorder(root) {
401            if self.tree.kind(node) == NodeKind::Component {
402                let scope = self.tree.component_scope(node);
403                let token = self.components.token(scope);
404                self.components.cleanup_effects(token);
405            }
406        }
407    }
408
409    pub(crate) fn native_window_closed(&mut self) {
410        self.cleanup_component_effects();
411        self.clear_published_references();
412        self.imperative.complete_unavailable();
413        self.components.close();
414        self.runtime.native_window_closed();
415        self.reset_on_drop = false;
416    }
417
418    #[cfg(any(test, feature = "test"))]
419    pub fn root(&self) -> Option<NodeId> {
420        self.root
421    }
422
423    #[cfg(feature = "test")]
424    pub(crate) fn root_native(&self) -> Option<NodeId> {
425        Self::native_root(&self.tree, self.root?).ok()
426    }
427
428    #[cfg(feature = "test")]
429    pub(crate) fn live_native_children(&self, node: NodeId) -> &[NodeId] {
430        self.tree.children(node)
431    }
432
433    #[cfg(any(test, feature = "test"))]
434    pub fn version(&self) -> u64 {
435        self.version
436    }
437
438    #[cfg(any(test, feature = "test"))]
439    pub fn window(&self) -> Option<NodeId> {
440        self.window
441    }
442
443    #[cfg(any(test, feature = "test"))]
444    pub fn poisoned(&self) -> bool {
445        self.poisoned
446    }
447
448    #[cfg(test)]
449    pub(crate) fn components(&self) -> &ComponentStore {
450        &self.components
451    }
452
453    #[cfg(test)]
454    pub(crate) fn components_mut(&mut self) -> &mut ComponentStore {
455        &mut self.components
456    }
457
458    fn commit_tree_properties(tree: &mut Tree, commits: &[PropertyCommit]) {
459        for commit in commits {
460            tree.native_mut(commit.node)
461                .properties
462                .insert(commit.property, commit.value.clone());
463        }
464    }
465
466    fn commit_tree_references(tree: &mut Tree, commits: &[ReferenceCommit]) {
467        for commit in commits {
468            if let Some(native) = tree.try_native_mut(commit.node) {
469                native.reference.clone_from(&commit.new);
470            }
471        }
472    }
473
474    fn commit_candidate_properties(
475        &mut self,
476        candidate: &mut CandidateState,
477        commits: &[PropertyCommit],
478    ) {
479        match candidate {
480            CandidateState::Tree { tree, .. } => Self::commit_tree_properties(tree, commits),
481            CandidateState::Native { node, .. } => {
482                let native = self.tree.native_mut(*node);
483                for commit in commits {
484                    assert_eq!(commit.node, *node);
485                    native
486                        .properties
487                        .insert(commit.property, commit.value.clone());
488                }
489            }
490        }
491    }
492
493    fn commit_candidate_references(
494        &mut self,
495        candidate: &mut CandidateState,
496        commits: &[ReferenceCommit],
497    ) {
498        match candidate {
499            CandidateState::Tree { tree, .. } => Self::commit_tree_references(tree, commits),
500            CandidateState::Native {
501                node, reference, ..
502            } => {
503                for commit in commits {
504                    assert_eq!(commit.node, *node);
505                }
506                if let Some(commit) = commits.last() {
507                    reference.clone_from(&commit.new);
508                }
509            }
510        }
511    }
512
513    fn apply_reference_bindings(&self, commits: &[ReferenceCommit]) {
514        for commit in commits {
515            if let Some(old) = &commit.old {
516                old.unbind(self.identity, commit.node);
517            }
518            if let Some(new) = &commit.new {
519                new.bind(self.imperative.clone(), self.identity, commit.node);
520            }
521        }
522    }
523
524    fn validate_tree_references(
525        &self,
526        tree: &Tree,
527        root: NodeId,
528        commits: &[ReferenceCommit],
529    ) -> Result<(), PumpError> {
530        if !commits.iter().any(|commit| commit.new.is_some()) {
531            return Ok(());
532        }
533        let mut references = HashSet::new();
534        for node in tree.subtree_postorder(root) {
535            let Some(native) = tree.try_native(node) else {
536                continue;
537            };
538            let desired = commits
539                .iter()
540                .rev()
541                .find(|commit| commit.node == node)
542                .map_or(&native.reference, |commit| &commit.new);
543            let Some(reference) = desired else {
544                continue;
545            };
546            if !references.insert(reference.identity())
547                || reference
548                    .binding_target()
549                    .is_some_and(|(identity, _)| identity != self.identity)
550            {
551                return Err(PumpError::DuplicateElementRef);
552            }
553        }
554        Ok(())
555    }
556
557    fn validate_candidate_references(
558        &self,
559        candidate: &CandidateState,
560        commits: &[ReferenceCommit],
561    ) -> Result<(), PumpError> {
562        if !commits.iter().any(|commit| commit.new.is_some()) {
563            return Ok(());
564        }
565        match candidate {
566            CandidateState::Tree { tree, root } => {
567                self.validate_tree_references(tree, *root, commits)
568            }
569            CandidateState::Native {
570                node, reference, ..
571            } => {
572                let Some(reference) = reference else {
573                    return Ok(());
574                };
575                if reference
576                    .binding_target()
577                    .is_some_and(|(identity, _)| identity != self.identity)
578                {
579                    return Err(PumpError::DuplicateElementRef);
580                }
581                let Some(root) = self.root else {
582                    return Ok(());
583                };
584                for current in self.tree.subtree_postorder(root) {
585                    if current != *node
586                        && self
587                            .tree
588                            .try_native(current)
589                            .and_then(|native| native.reference.as_ref())
590                            == Some(reference)
591                    {
592                        return Err(PumpError::DuplicateElementRef);
593                    }
594                }
595                Ok(())
596            }
597        }
598    }
599
600    fn clear_published_references(&self) {
601        let Some(root) = self.root else {
602            return;
603        };
604        for node in self.tree.subtree_postorder(root) {
605            if let Some(native) = self.tree.try_native(node)
606                && let Some(reference) = &native.reference
607            {
608                reference.unbind(self.identity, node);
609            }
610        }
611    }
612
613    fn next_version(&self) -> u64 {
614        self.version.checked_add(1).unwrap()
615    }
616
617    fn apply_component_candidate(
618        &mut self,
619        candidate: Tree,
620        candidate_root: NodeId,
621        plan: UpdatePlan,
622        changes: ComponentChanges,
623        next_version: u64,
624    ) -> Result<(), PumpError> {
625        let window = self.window.ok_or(PumpError::NotMounted)?;
626        let resolved = changes
627            .composed
628            .iter()
629            .chain(changes.retired.iter())
630            .copied()
631            .collect::<HashSet<_>>();
632        self.finalize_component_candidate(ComponentCandidate {
633            activate_window: false,
634            changes,
635            next_version,
636            plan,
637            planning_failure: PlanningFailure::Rearm,
638            root: candidate_root,
639            tree: candidate,
640            window,
641        })?;
642        self.planning_dirty
643            .retain(|token| !resolved.contains(token));
644        Ok(())
645    }
646
647    fn finalize_component_candidate(
648        &mut self,
649        mut candidate: ComponentCandidate,
650    ) -> Result<(), PumpError> {
651        let planning = (|| {
652            Self::plan_window_title_bar(
653                candidate.window,
654                &self.tree,
655                &candidate.tree,
656                &mut candidate.plan,
657            )?;
658            Self::plan_window_title(
659                candidate.window,
660                &self.tree,
661                &candidate.tree,
662                &mut candidate.plan,
663            )?;
664            Self::plan_window_visuals(
665                candidate.window,
666                &self.tree,
667                &candidate.tree,
668                &mut candidate.plan,
669            )?;
670            Self::plan_window_observations(
671                candidate.window,
672                &self.tree,
673                &candidate.tree,
674                &mut candidate.plan,
675            )?;
676            if candidate.activate_window {
677                candidate.plan.push(Command::ActivateWindow {
678                    node: candidate.window,
679                });
680            }
681            Self::plan_host_requests(
682                candidate.window,
683                &mut candidate.changes.host_requests,
684                &mut candidate.plan,
685            );
686            Ok::<(), PumpError>(())
687        })();
688        if let Err(error) = planning {
689            self.fail_component_candidate(&candidate.changes, candidate.planning_failure);
690            return Err(error);
691        }
692        self.publish_candidate(
693            CandidateState::Tree {
694                tree: candidate.tree,
695                root: candidate.root,
696            },
697            candidate.plan,
698            FrontendChanges::Component(candidate.changes),
699            candidate.next_version,
700            candidate.planning_failure,
701        )
702    }
703
704    fn plan_host_requests(window: NodeId, requests: &mut Vec<HostRequest>, plan: &mut UpdatePlan) {
705        let mut close = false;
706        for request in requests.drain(..) {
707            match request {
708                HostRequest::CloseWindow { identity } if identity == plan.identity => close = true,
709                HostRequest::OpenWindow { identity, root } if identity == plan.identity => {
710                    plan.post_publish_windows.push(root);
711                }
712                HostRequest::CloseWindow { .. } | HostRequest::OpenWindow { .. } => {}
713            }
714        }
715        if close {
716            plan.post_publish_commands
717                .push(Command::CloseWindow { node: window });
718        }
719    }
720
721    fn plan_window_title(
722        window: NodeId,
723        current: &Tree,
724        candidate: &Tree,
725        plan: &mut UpdatePlan,
726    ) -> Result<(), PumpError> {
727        let current = current
728            .validate_window_title()
729            .map_err(|()| PumpError::DuplicateWindowTitle)?;
730        let candidate = candidate
731            .validate_window_title()
732            .map_err(|()| PumpError::DuplicateWindowTitle)?;
733        let current = current.as_ref().map(|title| title.title.as_ref());
734        let candidate = candidate.as_ref().map(|title| title.title.as_ref());
735        if current != candidate {
736            plan.push(Command::SetWindowTitle {
737                node: window,
738                title: candidate.unwrap_or_default().to_string(),
739            });
740        }
741        Ok(())
742    }
743
744    fn plan_window_visuals(
745        window: NodeId,
746        current: &Tree,
747        candidate: &Tree,
748        plan: &mut UpdatePlan,
749    ) -> Result<(), PumpError> {
750        let current = current
751            .validate_window_visuals()
752            .map_err(|()| PumpError::DuplicateWindowVisuals)?;
753        let candidate = candidate
754            .validate_window_visuals()
755            .map_err(|()| PumpError::DuplicateWindowVisuals)?;
756        let current = current.map(|state| state.visuals).unwrap_or_default();
757        let candidate = candidate.map(|state| state.visuals).unwrap_or_default();
758        if current != candidate {
759            plan.push(Command::SetWindowVisuals {
760                node: window,
761                visuals: candidate,
762            });
763        }
764        Ok(())
765    }
766
767    fn plan_window_observations(
768        window: NodeId,
769        current: &Tree,
770        candidate: &Tree,
771        plan: &mut UpdatePlan,
772    ) -> Result<(), PumpError> {
773        let current = WindowObservationFlags {
774            window_size: current
775                .validate_window_size_observation()
776                .map_err(|()| PumpError::DuplicateWindowSizeObservation)?
777                .map(|(observation, _)| observation),
778            color_scheme: current
779                .validate_color_scheme_observation()
780                .map_err(|()| PumpError::DuplicateColorSchemeObservation)?
781                .map(|(observation, _)| observation),
782        };
783        let candidate = WindowObservationFlags {
784            window_size: candidate
785                .validate_window_size_observation()
786                .map_err(|()| PumpError::DuplicateWindowSizeObservation)?
787                .map(|(observation, _)| observation),
788            color_scheme: candidate
789                .validate_color_scheme_observation()
790                .map_err(|()| PumpError::DuplicateColorSchemeObservation)?
791                .map(|(observation, _)| observation),
792        };
793        if current != candidate {
794            plan.push(Command::SetWindowObservations {
795                node: window,
796                observations: candidate,
797            });
798        }
799        Ok(())
800    }
801
802    fn plan_window_title_bar(
803        window: NodeId,
804        current: &Tree,
805        candidate: &Tree,
806        plan: &mut UpdatePlan,
807    ) -> Result<(), PumpError> {
808        let current_state = current
809            .validate_window_title_bar()
810            .map_err(|()| PumpError::DuplicateWindowTitleBar)?;
811        let candidate_state = candidate
812            .validate_window_title_bar()
813            .map_err(|()| PumpError::DuplicateWindowTitleBar)?;
814
815        if current_state == candidate_state {
816            return Ok(());
817        }
818
819        if current_state.is_some_and(|current| {
820            candidate_state.is_none_or(|candidate| candidate.title_bar != current.title_bar)
821        }) {
822            plan.prepend(Command::ClearWindowTitleBar { node: window });
823        }
824        if let Some(state) = candidate_state {
825            plan.push(Command::SetWindowTitleBar {
826                node: window,
827                title_bar: state.title_bar,
828                height: state.height,
829            });
830        }
831        Ok(())
832    }
833}
834
835impl<R: NativeRuntime> Drop for Pump<R> {
836    fn drop(&mut self) {
837        self.cleanup_component_effects();
838        self.clear_published_references();
839        self.imperative.clear();
840        if self.reset_on_drop {
841            self.runtime.reset();
842        }
843    }
844}