1use std::collections::HashMap;
4use std::rc::Rc;
5
6use ailloli_ui_core::ElementId;
7
8use std::collections::HashSet;
9
10use super::{ElementKind, ElementTree, Key};
11use crate::app::RuntimeHandle;
12use crate::component::reactive::{
13 MountGeneration, ReactiveReadScope, ReactiveReadSet, ReactiveStage,
14};
15use crate::component::view::{component_mount_identity, widget_mount_identity};
16use crate::component::{Context, View, ViewKind};
17
18#[derive(Debug, Clone)]
19pub struct ReconcileInputChild {
30 pub id: ElementId,
32 pub key: Option<Key>,
34}
35
36#[derive(Debug, Clone)]
37pub struct ReconcileOutputChild {
51 pub id: ElementId,
53 pub reused: bool,
55}
56
57pub fn reconcile_children(
83 old: &[ReconcileInputChild],
84 new_keys: &[Option<Key>],
85) -> Vec<Option<ReconcileOutputChild>> {
86 let mut by_key: HashMap<&Key, ElementId> = HashMap::new();
87 for c in old {
88 if let Some(k) = c.key.as_ref() {
89 by_key.insert(k, c.id);
90 }
91 }
92
93 let mut out = Vec::with_capacity(new_keys.len());
94 for (i, nk) in new_keys.iter().enumerate() {
95 if let Some(k) = nk.as_ref() {
96 if let Some(&id) = by_key.get(k) {
97 out.push(Some(ReconcileOutputChild { id, reused: true }));
98 continue;
99 }
100 out.push(None);
101 continue;
102 }
103
104 if let Some(old_child) = old.get(i) {
105 out.push(Some(ReconcileOutputChild {
106 id: old_child.id,
107 reused: true,
108 }));
109 } else {
110 out.push(None);
111 }
112 }
113
114 out
115}
116
117fn key_from_view<A>(view: &View<A>) -> Option<Key> {
119 view.key_ref().map(|k| Key::String(k.to_string()))
120}
121
122fn same_mount_payload_type<A: 'static>(old: &ElementKind<A>, new: &ViewKind<A>) -> bool {
128 match (old, new) {
129 (ElementKind::Empty, ViewKind::Empty) => true,
130 (ElementKind::Widget(old), ViewKind::Widget(new)) => {
131 match (widget_mount_identity(old), widget_mount_identity(new)) {
132 (Some(old), Some(new)) => old == new,
133 (None, None) => Rc::ptr_eq(old, new),
134 _ => false,
135 }
136 }
137 (ElementKind::Component(old), ViewKind::Component(new)) => {
138 match (component_mount_identity(old), component_mount_identity(new)) {
139 (Some(old), Some(new)) => old == new,
140 (None, None) => Rc::ptr_eq(old, new),
141 _ => false,
142 }
143 }
144 _ => false,
145 }
146}
147
148fn remove_subtree<A>(tree: &mut ElementTree<A>, id: ElementId, runtime: &RuntimeHandle<A>) {
150 let children = tree.get(id).map(|e| e.children.clone()).unwrap_or_default();
151 for c in children {
152 remove_subtree(tree, c, runtime);
153 }
154
155 runtime.unregister_element_mount(id);
156 runtime
157 .states()
158 .borrow_mut()
159 .remove_element_scoped(runtime.element_tree_id(), id);
160 let _ = tree.remove_element(id);
161}
162
163fn publish_build_dependencies_or_retry<A: 'static>(
170 runtime: &RuntimeHandle<A>,
171 element_id: ElementId,
172 mount_generation: MountGeneration,
173 reads: &ReactiveReadSet,
174) {
175 if reads.is_current() {
176 runtime.replace_reactive_dependencies(
177 element_id,
178 mount_generation,
179 ReactiveStage::Build,
180 reads,
181 );
182 } else {
183 runtime.request_build(element_id);
184 }
185}
186
187fn create_from_view<A: 'static>(
189 tree: &mut ElementTree<A>,
190 runtime: &RuntimeHandle<A>,
191 parent: Option<ElementId>,
192 view: View<A>,
193) -> ElementId {
194 let key = key_from_view(&view);
195 let flex_item = view.flex_item;
196 let size_hint = view.size_hint;
197
198 let kind = match &view.kind {
199 ViewKind::Empty => ElementKind::Empty,
200 ViewKind::Widget(w) => ElementKind::Widget(w.clone()),
201 ViewKind::Component(c) => ElementKind::Component(c.clone()),
202 };
203
204 let id = tree.create_element(kind, key, parent);
205 let mount_generation = tree
206 .get(id)
207 .expect("newly-created retained element must exist")
208 .mount_generation();
209 runtime.register_element_mount(id, mount_generation);
210 tree.set_view_metadata(id, flex_item, size_hint);
211
212 match view.kind {
213 ViewKind::Empty => {}
214 ViewKind::Widget(_) => {
215 let mut children = Vec::with_capacity(view.children.len());
216 for child_view in view.children {
217 let child_id = create_from_view(tree, runtime, Some(id), child_view);
218 children.push(child_id);
219 }
220 tree.set_children(id, children);
221 }
222 ViewKind::Component(component) => {
223 tree.record_build(id);
224 let mut ctx = Context::new(id, runtime.clone());
225 let scope = ReactiveReadScope::new();
226 let built = component.build(&mut ctx);
227 let reads = scope.finish();
228 publish_build_dependencies_or_retry(runtime, id, mount_generation, &reads);
229 let child_id = create_from_view(tree, runtime, Some(id), built);
230 tree.set_children(id, vec![child_id]);
231 }
232 }
233
234 id
235}
236
237pub fn reconcile_root<A: 'static>(
258 tree: &mut ElementTree<A>,
259 runtime: &RuntimeHandle<A>,
260 root_view: View<A>,
261) -> ElementId {
262 match tree.root() {
263 Some(root_id) => reconcile_element(tree, runtime, root_id, root_view),
264 None => create_from_view(tree, runtime, None, root_view),
265 }
266}
267
268pub fn reconcile_element<A: 'static>(
294 tree: &mut ElementTree<A>,
295 runtime: &RuntimeHandle<A>,
296 element_id: ElementId,
297 new_view: View<A>,
298) -> ElementId {
299 let new_key = key_from_view(&new_view);
300 let flex_item = new_view.flex_item;
301 let size_hint = new_view.size_hint;
302
303 let mount_replaced = tree
306 .get(element_id)
307 .is_some_and(|element| !same_mount_payload_type(&element.kind, &new_view.kind));
308 let mut mount_generation = tree
309 .get(element_id)
310 .map(|element| element.mount_generation());
311 if let Some(el) = tree.get_mut(element_id) {
312 if mount_replaced {
313 mount_generation = Some(el.advance_mount_generation());
314 el.layout = None;
315 el.layout_reactive_dependencies = Default::default();
316 el.layout_commit_reactive_dependencies = Default::default();
317 el.committed_layout_generation = None;
318 el.committed_layout_attempt = None;
319 el.layout_callback_executed = false;
320 el.committed_bounds = None;
321 }
322 el.key = new_key;
323 el.kind = match &new_view.kind {
324 ViewKind::Empty => ElementKind::Empty,
325 ViewKind::Widget(w) => ElementKind::Widget(w.clone()),
326 ViewKind::Component(c) => ElementKind::Component(c.clone()),
327 };
328 el.dirty = super::DirtyFlags::layout();
331 el.layout_revision = el.layout_revision.wrapping_add(1).max(1);
332 el.layout_cache_key = None;
333 el.measurement_layout = None;
334 el.measurement_layout_cache_key = None;
335 el.measurement_reactive_dependencies = Default::default();
336 el.commit_dirty = true;
337 }
338 if let Some(mount_generation) = mount_generation {
339 runtime.register_element_mount(element_id, mount_generation);
340 }
341 if mount_replaced {
342 runtime
343 .states()
344 .borrow_mut()
345 .remove_element_scoped(runtime.element_tree_id(), element_id);
346 }
347 tree.set_view_metadata(element_id, flex_item, size_hint);
348
349 let children_views: Vec<View<A>> = match new_view.kind {
353 ViewKind::Empty => Vec::new(),
354 ViewKind::Widget(_) => new_view.children,
355 ViewKind::Component(component) => {
356 tree.record_build(element_id);
357 let mut ctx = Context::new(element_id, runtime.clone());
358 let scope = ReactiveReadScope::new();
359 let built = component.build(&mut ctx);
360 let reads = scope.finish();
361 publish_build_dependencies_or_retry(
362 runtime,
363 element_id,
364 mount_generation.expect("reconciled retained element must have a generation"),
365 &reads,
366 );
367 vec![built]
368 }
369 };
370
371 let old_children = tree
372 .children_of(element_id)
373 .iter()
374 .filter_map(|&id| {
375 tree.get(id).map(|e| ReconcileInputChild {
376 id,
377 key: e.key.clone(),
378 })
379 })
380 .collect::<Vec<_>>();
381
382 let new_keys = children_views
383 .iter()
384 .map(|v| key_from_view(v))
385 .collect::<Vec<_>>();
386
387 let mapping = reconcile_children(&old_children, &new_keys);
388
389 let mut next_children = Vec::with_capacity(children_views.len());
390 let mut reused = HashSet::<ElementId>::new();
391
392 for (slot, view) in mapping.into_iter().zip(children_views) {
393 match slot {
394 Some(reuse) => {
395 reused.insert(reuse.id);
396 let cid = reconcile_element(tree, runtime, reuse.id, view);
397 next_children.push(cid);
398 }
399 None => {
400 let cid = create_from_view(tree, runtime, Some(element_id), view);
401 next_children.push(cid);
402 }
403 }
404 }
405
406 for c in old_children {
408 if !reused.contains(&c.id) {
409 remove_subtree(tree, c.id, runtime);
410 }
411 }
412
413 tree.set_children(element_id, next_children);
414 element_id
415}
416
417pub fn reconcile_existing_component<A: 'static>(
436 tree: &mut ElementTree<A>,
437 runtime: &RuntimeHandle<A>,
438 element_id: ElementId,
439) -> bool {
440 let Some(component) = tree.get(element_id).and_then(|el| match &el.kind {
441 ElementKind::Component(component) => Some(component.clone()),
442 _ => None,
443 }) else {
444 return false;
445 };
446
447 if let Some(el) = tree.get_mut(element_id) {
448 el.dirty = super::DirtyFlags::layout();
449 el.layout_revision = el.layout_revision.wrapping_add(1).max(1);
450 el.layout_cache_key = None;
451 el.measurement_layout = None;
452 el.measurement_layout_cache_key = None;
453 el.measurement_reactive_dependencies = Default::default();
454 el.commit_dirty = true;
455 }
456
457 tree.record_build(element_id);
458 let mut ctx = Context::new(element_id, runtime.clone());
459 let mount_generation = tree
460 .get(element_id)
461 .expect("existing component must remain mounted")
462 .mount_generation();
463 let scope = ReactiveReadScope::new();
464 let built = component.build(&mut ctx);
465 let reads = scope.finish();
466 publish_build_dependencies_or_retry(runtime, element_id, mount_generation, &reads);
467 reconcile_child_views(tree, runtime, element_id, vec![built]);
468 true
469}
470
471fn reconcile_child_views<A: 'static>(
473 tree: &mut ElementTree<A>,
474 runtime: &RuntimeHandle<A>,
475 element_id: ElementId,
476 children_views: Vec<View<A>>,
477) {
478 let old_children = tree
479 .children_of(element_id)
480 .iter()
481 .filter_map(|&id| {
482 tree.get(id).map(|e| ReconcileInputChild {
483 id,
484 key: e.key.clone(),
485 })
486 })
487 .collect::<Vec<_>>();
488
489 let new_keys = children_views
490 .iter()
491 .map(|v| key_from_view(v))
492 .collect::<Vec<_>>();
493
494 let mapping = reconcile_children(&old_children, &new_keys);
495
496 let mut next_children = Vec::with_capacity(children_views.len());
497 let mut reused = HashSet::<ElementId>::new();
498
499 for (slot, view) in mapping.into_iter().zip(children_views) {
500 match slot {
501 Some(reuse) => {
502 reused.insert(reuse.id);
503 let cid = reconcile_element(tree, runtime, reuse.id, view);
504 next_children.push(cid);
505 }
506 None => {
507 let cid = create_from_view(tree, runtime, Some(element_id), view);
508 next_children.push(cid);
509 }
510 }
511 }
512
513 for c in old_children {
514 if !reused.contains(&c.id) {
515 remove_subtree(tree, c.id, runtime);
516 }
517 }
518
519 tree.set_children(element_id, next_children);
520}
521
522#[cfg(test)]
523mod tests {
525 use super::*;
526 use crate::app::Runtime;
527 use crate::component::{Component, ComponentNode, Widget};
528 use crate::layout::{LayoutChild, LayoutCtx, LayoutEngine, LayoutResult};
529 use crate::scene::PaintCtx;
530 use ailloli_ui_core::{Constraints, Rect};
531 use std::cell::RefCell;
532 use std::rc::Rc;
533
534 struct WidgetA;
535 struct WidgetB;
536
537 macro_rules! impl_empty_widget {
538 ($widget:ty, $name:literal) => {
539 impl Widget<()> for $widget {
540 fn debug_name(&self) -> &'static str {
541 $name
542 }
543
544 fn layout(
545 &self,
546 _engine: &mut LayoutEngine<'_, ()>,
547 _ctx: &mut LayoutCtx<'_>,
548 _children: &mut [LayoutChild],
549 _constraints: Constraints,
550 ) -> LayoutResult {
551 LayoutResult::empty()
552 }
553
554 fn paint(&self, _ctx: &mut PaintCtx<'_>, _bounds: Rect, _layout: &LayoutResult) {}
555 }
556 };
557 }
558
559 impl_empty_widget!(WidgetA, "WidgetA");
560 impl_empty_widget!(WidgetB, "WidgetB");
561
562 struct ComponentA;
563 struct ComponentB;
564
565 impl ComponentNode<()> for ComponentA {
566 fn build(&self, _context: &mut Context<()>) -> View<()> {
567 View::empty()
568 }
569 }
570
571 impl ComponentNode<()> for ComponentB {
572 fn build(&self, _context: &mut Context<()>) -> View<()> {
573 View::empty()
574 }
575 }
576
577 fn render_u8_slot(context: &mut Context<()>, seen: Rc<RefCell<Vec<String>>>) -> View<()> {
578 let state = context.state(7_u8);
579 seen.borrow_mut().push(format!("u8:{}", state.read()));
580 View::empty()
581 }
582
583 fn render_string_slot(context: &mut Context<()>, seen: Rc<RefCell<Vec<String>>>) -> View<()> {
584 let state = context.state(String::from("fresh"));
585 seen.borrow_mut().push(format!("string:{}", state.read()));
586 View::empty()
587 }
588
589 fn render_initial_u8_slot(
590 context: &mut Context<()>,
591 (seen, initial): (Rc<RefCell<Vec<u8>>>, u8),
592 ) -> View<()> {
593 let state = context.state(initial);
594 seen.borrow_mut().push(state.read());
595 View::empty()
596 }
597
598 #[test]
599 fn reconcile_by_index_without_keys() {
601 let old = vec![
602 ReconcileInputChild {
603 id: ElementId(1),
604 key: None,
605 },
606 ReconcileInputChild {
607 id: ElementId(2),
608 key: None,
609 },
610 ];
611 let new_keys = vec![None, None, None];
612 let out = reconcile_children(&old, &new_keys);
613 assert_eq!(out.len(), 3);
614 assert_eq!(out[0].as_ref().unwrap().id, ElementId(1));
615 assert_eq!(out[1].as_ref().unwrap().id, ElementId(2));
616 assert!(out[2].is_none());
617 }
618
619 #[test]
620 fn reconcile_by_key() {
622 let old = vec![
623 ReconcileInputChild {
624 id: ElementId(10),
625 key: Some(Key::U64(7)),
626 },
627 ReconcileInputChild {
628 id: ElementId(11),
629 key: Some(Key::Static("a")),
630 },
631 ];
632 let new_keys = vec![Some(Key::Static("a")), Some(Key::U64(7))];
633 let out = reconcile_children(&old, &new_keys);
634 assert_eq!(out[0].as_ref().unwrap().id, ElementId(11));
635 assert_eq!(out[1].as_ref().unwrap().id, ElementId(10));
636 }
637
638 #[test]
639 fn replacing_a_widget_with_another_concrete_type_advances_the_mount_generation() {
640 let mut runtime = Runtime::new(RuntimeHandle::<()>::new());
641 let root = runtime.reconcile_view(View::leaf(WidgetA));
642 let first_generation = runtime.tree.get(root).unwrap().mount_generation();
643
644 let reconciled = runtime.reconcile_view(View::leaf(WidgetB));
645 let replacement_generation = runtime.tree.get(root).unwrap().mount_generation();
646
647 assert_eq!(reconciled, root);
648 assert!(replacement_generation.get() > first_generation.get());
649 }
650
651 #[test]
652 fn replacing_a_component_with_another_concrete_type_advances_the_mount_generation() {
653 let mut runtime = Runtime::new(RuntimeHandle::<()>::new());
654 let root = runtime.reconcile_view(View::component(ComponentA));
655 let first_generation = runtime.tree.get(root).unwrap().mount_generation();
656
657 let reconciled = runtime.reconcile_view(View::component(ComponentB));
658 let replacement_generation = runtime.tree.get(root).unwrap().mount_generation();
659
660 assert_eq!(reconciled, root);
661 assert!(replacement_generation.get() > first_generation.get());
662 }
663
664 #[test]
665 fn rebuilding_the_same_component_type_preserves_the_mount_generation() {
666 let mut runtime = Runtime::new(RuntimeHandle::<()>::new());
667 let root = runtime.reconcile_view(View::component(ComponentA));
668 let first_generation = runtime.tree.get(root).unwrap().mount_generation();
669
670 assert_eq!(
671 runtime.reconcile_view(View::component(ComponentA)),
672 root,
673 "a fresh declarative payload of the same component type must reconcile in place"
674 );
675 assert_eq!(
676 runtime.tree.get(root).unwrap().mount_generation(),
677 first_generation
678 );
679
680 runtime.runtime.request_build(root);
681 runtime.prepare_frame();
682
683 assert_eq!(
684 runtime.tree.get(root).unwrap().mount_generation(),
685 first_generation
686 );
687 }
688
689 #[test]
690 fn changing_a_function_component_render_resets_slots_and_advances_generation() {
691 let seen = Rc::new(RefCell::new(Vec::new()));
692 let mut runtime = Runtime::new(RuntimeHandle::<()>::new());
693 let root = runtime.reconcile_view(View::component(Component::new(
694 seen.clone(),
695 render_u8_slot,
696 )));
697 let first_generation = runtime.tree.get(root).unwrap().mount_generation();
698
699 let reconciled = runtime.reconcile_view(View::component(Component::new(
700 seen.clone(),
701 render_string_slot,
702 )));
703 let replacement_generation = runtime.tree.get(root).unwrap().mount_generation();
704
705 assert_eq!(reconciled, root);
706 assert!(replacement_generation.get() > first_generation.get());
707 assert_eq!(
708 seen.borrow().as_slice(),
709 ["u8:7", "string:fresh"],
710 "the second render must receive a fresh state slot of its own type"
711 );
712 }
713
714 #[test]
715 fn fresh_function_item_wrappers_preserve_the_mount_and_slots() {
716 let seen = Rc::new(RefCell::new(Vec::new()));
717 let mut runtime = Runtime::new(RuntimeHandle::<()>::new());
718 let root = runtime.reconcile_view(View::component(Component::new(
719 (seen.clone(), 7),
720 render_initial_u8_slot,
721 )));
722 let first_generation = runtime.tree.get(root).unwrap().mount_generation();
723
724 runtime.reconcile_view(View::component(Component::new(
725 (seen.clone(), 99),
726 render_initial_u8_slot,
727 )));
728
729 assert_eq!(
730 runtime.tree.get(root).unwrap().mount_generation(),
731 first_generation
732 );
733 assert_eq!(seen.borrow().as_slice(), [7, 7]);
734 }
735
736 #[test]
737 fn explicitly_erased_function_pointer_preserves_the_historical_type_mount() {
738 type Props = (Rc<RefCell<Vec<u8>>>, u8);
739 type ErasedComponent = Component<Props, ()>;
740
741 let seen = Rc::new(RefCell::new(Vec::new()));
742 let first: ErasedComponent =
743 Component::<Props, ()>::new((seen.clone(), 7), render_initial_u8_slot);
744 let mut runtime = Runtime::new(RuntimeHandle::<()>::new());
745 let root = runtime.reconcile_view(View::component(first));
746 let first_generation = runtime.tree.get(root).unwrap().mount_generation();
747
748 let second: ErasedComponent =
749 Component::<Props, ()>::new((seen.clone(), 99), render_initial_u8_slot);
750 runtime.reconcile_view(View::component(second));
751
752 assert_eq!(
753 runtime.tree.get(root).unwrap().mount_generation(),
754 first_generation
755 );
756 assert_eq!(seen.borrow().as_slice(), [7, 7]);
757 }
758}