use crate::Widget;
use smol_str::SmolStr;
use std::collections::HashMap;
use web_time::Instant;
struct Frame {
new_siblings: Vec<Box<dyn Widget>>,
old_siblings: *const [Box<dyn Widget>],
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>]) -> 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_siblings: old_siblings as *const [Box<dyn Widget>],
keyed_old,
consumed: vec![false; old_siblings.len()],
positional_cursor: 0,
next_index: 0,
}
}
}
pub(crate) enum WorkLoopStatus {
Yielded,
Complete(Vec<Box<dyn Widget>>),
}
const YIELD_CHECK_INTERVAL: u32 = 8;
pub(crate) struct WorkLoop {
stack: Vec<Frame>,
units_since_check: u32,
}
impl WorkLoop {
pub(crate) fn new(new_root: Vec<Box<dyn Widget>>, old_root: &[Box<dyn Widget>]) -> Self {
Self {
stack: vec![Frame::new(new_root, old_root)],
units_since_check: 0,
}
}
pub(crate) fn perform_work(&mut self, 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");
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();
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) {
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, idx) else {
return;
};
if frame.consumed[old_idx] {
return;
}
let old_siblings: &[Box<dyn Widget>] = unsafe { &*frame.old_siblings };
let old_node = &old_siblings[old_idx];
if frame.new_siblings[idx].as_any().type_id() != old_node.as_any().type_id() {
frame.consumed[old_idx] = true;
return;
}
frame.consumed[old_idx] = true;
let new_node = &mut frame.new_siblings[idx];
let interaction_changed = new_node.transfer_interaction_state(old_node.as_ref());
new_node.after_interaction_transfer();
if !interaction_changed && new_node.content_eq(old_node.as_ref()) {
new_node.transfer_measured_state(old_node.as_ref());
new_node.set_dirty(false);
}
let old_children: *const [Box<dyn Widget>] = old_node.children() as *const _;
if let Some(child_slot) = frame.new_siblings[idx].children_mut() && !child_slot.is_empty() {
let taken_children = std::mem::take(child_slot);
let old_children: &[Box<dyn Widget>] = unsafe { &*old_children };
self.stack.push(Frame::new(taken_children, old_children));
}
}
fn find_match(frame: &mut Frame, 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: &[Box<dyn Widget>] = unsafe { &*frame.old_siblings };
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
}
}