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}