use crate::Widget;
use smol_str::SmolStr;
use std::collections::HashMap;
use web_time::Instant;
struct Frame {
new_siblings: Vec<Box<dyn Widget>>,
old_path: Vec<usize>,
keyed_old: HashMap<SmolStr, usize>,
consumed: Vec<bool>,
positional_cursor: usize,
next_index: usize,
}
impl Frame {
fn new(
new_siblings: Vec<Box<dyn Widget>>,
old_siblings: &[Box<dyn Widget>],
old_path: Vec<usize>
) -> Self {
let mut keyed_old = HashMap::new();
for (i, old) in old_siblings.iter().enumerate() {
if let Some(key) = old.get_key() {
keyed_old.entry(key.clone()).or_insert(i);
}
}
Self {
new_siblings,
old_path,
keyed_old,
consumed: vec![false; old_siblings.len()],
positional_cursor: 0,
next_index: 0,
}
}
}
pub enum WorkLoopStatus {
Yielded,
Complete(Vec<Box<dyn Widget>>),
}
const YIELD_CHECK_INTERVAL: u32 = 8;
pub struct WorkLoop {
stack: Vec<Frame>,
units_since_check: u32,
}
fn resolve_old_siblings<'a>(root: &'a [Box<dyn Widget>], path: &[usize]) -> &'a [Box<dyn Widget>] {
let mut current = root;
for &idx in path {
current = current[idx].children();
}
current
}
fn resolve_old_siblings_mut<'a>(
root: &'a mut [Box<dyn Widget>],
path: &[usize]
) -> &'a mut [Box<dyn Widget>] {
let mut current = root;
for &idx in path {
current = current[idx]
.children_mut()
.expect("old tree structure changed during reconciliation")
.as_mut_slice();
}
current
}
impl WorkLoop {
pub fn new(new_root: Vec<Box<dyn Widget>>, old_root: &[Box<dyn Widget>]) -> Self {
Self {
stack: vec![Frame::new(new_root, old_root, Vec::new())],
units_since_check: 0,
}
}
pub fn perform_work(
&mut self,
old_root: &mut [Box<dyn Widget>],
deadline: Instant
) -> WorkLoopStatus {
loop {
let frame_done = {
let frame = self.stack.last().expect("root frame always present");
frame.next_index >= frame.new_siblings.len()
};
if frame_done {
let finished = self.stack.pop().expect("frame exists");
let old_siblings = resolve_old_siblings_mut(old_root, &finished.old_path);
for (i, consumed) in finished.consumed.iter().enumerate() {
if !consumed {
unmount_subtree(old_siblings[i].as_mut());
}
}
match self.stack.last_mut() {
Some(parent) => {
let parent_idx = parent.next_index - 1;
if let Some(slot) = parent.new_siblings[parent_idx].children_mut() {
*slot = finished.new_siblings;
}
}
None => {
return WorkLoopStatus::Complete(finished.new_siblings);
}
}
continue;
}
self.process_one_node(old_root);
self.units_since_check += 1;
if self.units_since_check >= YIELD_CHECK_INTERVAL {
self.units_since_check = 0;
if Instant::now() >= deadline {
return WorkLoopStatus::Yielded;
}
}
}
}
fn process_one_node(&mut self, old_root: &mut [Box<dyn Widget>]) {
let frame = self.stack.last_mut().expect("root frame always present");
let idx = frame.next_index;
frame.next_index += 1;
let Some(old_idx) = Self::find_match(frame, old_root, idx) else {
mount_subtree(frame.new_siblings[idx].as_mut());
return;
};
if frame.consumed[old_idx] {
mount_subtree(frame.new_siblings[idx].as_mut());
return;
}
frame.consumed[old_idx] = true;
let old_path = frame.old_path.clone();
let old_siblings = resolve_old_siblings_mut(old_root, &old_path);
if frame.new_siblings[idx].as_any().type_id() != old_siblings[old_idx].as_any().type_id() {
unmount_subtree(old_siblings[old_idx].as_mut());
mount_subtree(frame.new_siblings[idx].as_mut());
return;
}
let new_node = &mut frame.new_siblings[idx];
let old_node = &mut old_siblings[old_idx];
new_node.transfer_interaction_state(old_node.as_ref());
let content_equal = new_node.content_eq(old_node.as_ref());
new_node.after_interaction_transfer();
if content_equal {
new_node.transfer_measured_state(old_node.as_ref());
new_node.layout(*old_node.layout_box());
new_node.set_dirty(false);
}
let has_children = new_node.children_mut().is_some_and(|c| !c.is_empty());
if has_children {
let mut child_path = old_path;
child_path.push(old_idx);
let taken_children = std::mem::take(new_node.children_mut().unwrap());
let old_children = resolve_old_siblings(old_root, &child_path);
self.stack.push(Frame::new(taken_children, old_children, child_path));
}
}
fn find_match(frame: &mut Frame, old_root: &[Box<dyn Widget>], idx: usize) -> Option<usize> {
let key = frame.new_siblings[idx].get_key().cloned();
if let Some(key) = key {
return frame.keyed_old.get(&key).copied();
}
let old_siblings = resolve_old_siblings(old_root, &frame.old_path);
while frame.positional_cursor < old_siblings.len() {
let candidate = frame.positional_cursor;
frame.positional_cursor += 1;
if frame.consumed[candidate] || old_siblings[candidate].get_key().is_some() {
continue;
}
return Some(candidate);
}
None
}
}
fn mount_subtree(widget: &mut dyn Widget) {
widget.on_mount();
if let Some(children) = widget.children_mut() {
for child in children.iter_mut() {
mount_subtree(child.as_mut());
}
}
}
fn unmount_subtree(widget: &mut dyn Widget) {
widget.on_unmount();
if let Some(children) = widget.children_mut() {
for child in children.iter_mut() {
unmount_subtree(child.as_mut());
}
}
}