#![warn(clippy::pedantic)]
#![allow(clippy::cast_possible_truncation)]
use crate::innerlude::{
AnyProps, ElementId, Mutations, ScopeArena, ScopeId, VComponent, VElement, VFragment, VNode,
VPlaceholder, VText,
};
use fxhash::{FxHashMap, FxHashSet};
use smallvec::{smallvec, SmallVec};
pub(crate) struct DiffState<'bump> {
pub(crate) scopes: &'bump ScopeArena,
pub(crate) mutations: Mutations<'bump>,
pub(crate) force_diff: bool,
pub(crate) element_stack: SmallVec<[ElementId; 10]>,
pub(crate) scope_stack: SmallVec<[ScopeId; 5]>,
}
impl<'b> DiffState<'b> {
pub fn new(scopes: &'b ScopeArena) -> Self {
Self {
scopes,
mutations: Mutations::new(),
force_diff: false,
element_stack: smallvec![],
scope_stack: smallvec![],
}
}
pub fn diff_scope(&mut self, scopeid: ScopeId) {
let (old, new) = (self.scopes.wip_head(scopeid), self.scopes.fin_head(scopeid));
let scope = self.scopes.get_scope(scopeid).unwrap();
self.scope_stack.push(scopeid);
self.element_stack.push(scope.container);
{
self.diff_node(old, new);
}
self.element_stack.pop();
self.scope_stack.pop();
self.mutations.mark_dirty_scope(scopeid);
}
pub fn diff_node(&mut self, old_node: &'b VNode<'b>, new_node: &'b VNode<'b>) {
use VNode::{Component, Element, Fragment, Placeholder, Text};
match (old_node, new_node) {
(Text(old), Text(new)) => {
self.diff_text_nodes(old, new, old_node, new_node);
}
(Placeholder(old), Placeholder(new)) => {
self.diff_placeholder_nodes(old, new, old_node, new_node);
}
(Element(old), Element(new)) => {
self.diff_element_nodes(old, new, old_node, new_node);
}
(Component(old), Component(new)) => {
self.diff_component_nodes(old_node, new_node, *old, *new);
}
(Fragment(old), Fragment(new)) => {
self.diff_fragment_nodes(old, new);
}
(
Component(_) | Fragment(_) | Text(_) | Element(_) | Placeholder(_),
Component(_) | Fragment(_) | Text(_) | Element(_) | Placeholder(_),
) => self.replace_node(old_node, new_node),
}
}
pub fn create_node(&mut self, node: &'b VNode<'b>) -> usize {
match node {
VNode::Text(vtext) => self.create_text_node(vtext, node),
VNode::Placeholder(anchor) => self.create_anchor_node(anchor, node),
VNode::Element(element) => self.create_element_node(element, node),
VNode::Fragment(frag) => self.create_fragment_node(frag),
VNode::Component(component) => self.create_component_node(*component),
}
}
fn create_text_node(&mut self, text: &'b VText<'b>, node: &'b VNode<'b>) -> usize {
let real_id = self.scopes.reserve_node(node);
text.id.set(Some(real_id));
self.mutations.create_text_node(text.text, real_id);
1
}
fn create_anchor_node(&mut self, anchor: &'b VPlaceholder, node: &'b VNode<'b>) -> usize {
let real_id = self.scopes.reserve_node(node);
anchor.id.set(Some(real_id));
self.mutations.create_placeholder(real_id);
1
}
fn create_element_node(&mut self, element: &'b VElement<'b>, node: &'b VNode<'b>) -> usize {
let VElement {
tag: tag_name,
listeners,
attributes,
children,
namespace,
id: dom_id,
parent: parent_id,
..
} = &element;
parent_id.set(self.element_stack.last().copied());
let real_id = self.scopes.reserve_node(node);
dom_id.set(Some(real_id));
self.element_stack.push(real_id);
{
self.mutations.create_element(tag_name, *namespace, real_id);
let cur_scope_id = self.current_scope();
for listener in listeners.iter() {
listener.mounted_node.set(Some(real_id));
self.mutations.new_event_listener(listener, cur_scope_id);
}
for attr in attributes.iter() {
self.mutations.set_attribute(attr, real_id.as_u64());
}
if !children.is_empty() {
self.create_and_append_children(children);
}
}
self.element_stack.pop();
1
}
fn create_fragment_node(&mut self, frag: &'b VFragment<'b>) -> usize {
self.create_children(frag.children)
}
fn create_component_node(&mut self, vcomponent: &'b VComponent<'b>) -> usize {
let parent_idx = self.current_scope();
let new_idx = if let Some(idx) = vcomponent.scope.get() {
assert!(self.scopes.get_scope(idx).is_some());
idx
} else {
let props: Box<dyn AnyProps + 'b> = vcomponent.props.borrow_mut().take().unwrap();
let props: Box<dyn AnyProps + 'static> = unsafe { std::mem::transmute(props) };
self.scopes.new_with_key(
vcomponent.user_fc,
props,
Some(parent_idx),
self.element_stack.last().copied().unwrap(),
0,
)
};
vcomponent.scope.set(Some(new_idx));
log::trace!(
"created component \"{}\", id: {:?} parent {:?}",
vcomponent.fn_name,
new_idx,
parent_idx,
);
self.enter_scope(new_idx);
let created = {
self.scopes.run_scope(new_idx);
self.mutations.mark_dirty_scope(new_idx);
let nextnode = self.scopes.fin_head(new_idx);
self.create_node(nextnode)
};
self.leave_scope();
created
}
pub(crate) fn diff_text_nodes(
&mut self,
old: &'b VText<'b>,
new: &'b VText<'b>,
_old_node: &'b VNode<'b>,
new_node: &'b VNode<'b>,
) {
if std::ptr::eq(old, new) {
return;
}
let root = match old.id.get() {
Some(id) => id,
None => self.scopes.reserve_node(new_node),
};
if old.text != new.text {
self.mutations.set_text(new.text, root.as_u64());
}
self.scopes.update_node(new_node, root);
new.id.set(Some(root));
}
pub(crate) fn diff_placeholder_nodes(
&mut self,
old: &'b VPlaceholder,
new: &'b VPlaceholder,
_old_node: &'b VNode<'b>,
new_node: &'b VNode<'b>,
) {
if std::ptr::eq(old, new) {
return;
}
let root = match old.id.get() {
Some(id) => id,
None => self.scopes.reserve_node(new_node),
};
self.scopes.update_node(new_node, root);
new.id.set(Some(root));
}
fn diff_element_nodes(
&mut self,
old: &'b VElement<'b>,
new: &'b VElement<'b>,
old_node: &'b VNode<'b>,
new_node: &'b VNode<'b>,
) {
if std::ptr::eq(old, new) {
return;
}
let root = match old.id.get() {
Some(id) => id,
None => self.scopes.reserve_node(new_node),
};
if new.tag != old.tag || new.namespace != old.namespace {
self.replace_node(old_node, new_node);
return;
}
self.scopes.update_node(new_node, root);
new.id.set(Some(root));
new.parent.set(old.parent.get());
if old.attributes.len() == new.attributes.len() {
for (old_attr, new_attr) in old.attributes.iter().zip(new.attributes.iter()) {
if old_attr.value != new_attr.value || new_attr.is_volatile {
self.mutations.set_attribute(new_attr, root.as_u64());
}
}
} else {
for attribute in old.attributes {
self.mutations.remove_attribute(attribute, root.as_u64());
}
for attribute in new.attributes {
self.mutations.set_attribute(attribute, root.as_u64());
}
}
let cur_scope_id = self.current_scope();
if old.listeners.len() == new.listeners.len() {
for (old_l, new_l) in old.listeners.iter().zip(new.listeners.iter()) {
if old_l.event != new_l.event {
self.mutations
.remove_event_listener(old_l.event, root.as_u64());
self.mutations.new_event_listener(new_l, cur_scope_id);
}
new_l.mounted_node.set(old_l.mounted_node.get());
}
} else {
for listener in old.listeners {
self.mutations
.remove_event_listener(listener.event, root.as_u64());
}
for listener in new.listeners {
listener.mounted_node.set(Some(root));
self.mutations.new_event_listener(listener, cur_scope_id);
}
}
match (old.children.len(), new.children.len()) {
(0, 0) => {}
(0, _) => {
self.mutations.push_root(root);
let created = self.create_children(new.children);
self.mutations.append_children(created as u32);
self.mutations.pop_root();
}
(_, _) => self.diff_children(old.children, new.children),
};
}
fn diff_component_nodes(
&mut self,
old_node: &'b VNode<'b>,
new_node: &'b VNode<'b>,
old: &'b VComponent<'b>,
new: &'b VComponent<'b>,
) {
let scope_addr = old
.scope
.get()
.expect("existing component nodes should have a scope");
if std::ptr::eq(old, new) {
return;
}
if old.user_fc == new.user_fc {
self.enter_scope(scope_addr);
{
new.scope.set(Some(scope_addr));
let scope = self
.scopes
.get_scope(scope_addr)
.unwrap_or_else(|| panic!("could not find {:?}", scope_addr));
let new_props = new
.props
.borrow_mut()
.take()
.expect("new component props should exist");
let should_diff = {
if old.can_memoize {
let props_are_the_same = unsafe {
let new_ref = new_props.as_ref();
scope.props.borrow().as_ref().unwrap().memoize(new_ref)
};
!props_are_the_same || self.force_diff
} else {
true
}
};
if should_diff {
let _old_props = scope
.props
.replace(unsafe { std::mem::transmute(Some(new_props)) });
self.scopes.run_scope(scope_addr);
self.mutations.mark_dirty_scope(scope_addr);
self.diff_node(
self.scopes.wip_head(scope_addr),
self.scopes.fin_head(scope_addr),
);
} else {
drop(new_props);
};
}
self.leave_scope();
} else {
self.replace_node(old_node, new_node);
}
}
fn diff_fragment_nodes(&mut self, old: &'b VFragment<'b>, new: &'b VFragment<'b>) {
if std::ptr::eq(old, new) {
return;
}
if old.children.len() == 1 && new.children.len() == 1 {
if !std::ptr::eq(old, new) {
self.diff_node(&old.children[0], &new.children[0]);
}
return;
}
debug_assert!(!old.children.is_empty());
debug_assert!(!new.children.is_empty());
self.diff_children(old.children, new.children);
}
fn diff_children(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) {
if std::ptr::eq(old, new) {
return;
}
match (old, new) {
([], []) => {}
([], _) => self.create_and_append_children(new),
(_, []) => self.remove_nodes(old, true),
_ => {
let new_is_keyed = new[0].key().is_some();
let old_is_keyed = old[0].key().is_some();
debug_assert!(
new.iter().all(|n| n.key().is_some() == new_is_keyed),
"all siblings must be keyed or all siblings must be non-keyed"
);
debug_assert!(
old.iter().all(|o| o.key().is_some() == old_is_keyed),
"all siblings must be keyed or all siblings must be non-keyed"
);
if new_is_keyed && old_is_keyed {
self.diff_keyed_children(old, new);
} else {
self.diff_non_keyed_children(old, new);
}
}
}
}
fn diff_non_keyed_children(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) {
use std::cmp::Ordering;
debug_assert!(!new.is_empty());
debug_assert!(!old.is_empty());
match old.len().cmp(&new.len()) {
Ordering::Greater => self.remove_nodes(&old[new.len()..], true),
Ordering::Less => self.create_and_insert_after(&new[old.len()..], old.last().unwrap()),
Ordering::Equal => {}
}
for (new, old) in new.iter().zip(old.iter()) {
self.diff_node(old, new);
}
}
fn diff_keyed_children(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) {
if cfg!(debug_assertions) {
let mut keys = fxhash::FxHashSet::default();
let mut assert_unique_keys = |children: &'b [VNode<'b>]| {
keys.clear();
for child in children {
let key = child.key();
debug_assert!(
key.is_some(),
"if any sibling is keyed, all siblings must be keyed"
);
keys.insert(key);
}
debug_assert_eq!(
children.len(),
keys.len(),
"keyed siblings must each have a unique key"
);
};
assert_unique_keys(old);
assert_unique_keys(new);
}
let (left_offset, right_offset) = match self.diff_keyed_ends(old, new) {
Some(count) => count,
None => return,
};
let old_middle = &old[left_offset..(old.len() - right_offset)];
let new_middle = &new[left_offset..(new.len() - right_offset)];
debug_assert!(
!((old_middle.len() == new_middle.len()) && old_middle.is_empty()),
"keyed children must have the same number of children"
);
if new_middle.is_empty() {
self.remove_nodes(old_middle, true);
} else if old_middle.is_empty() {
if left_offset == 0 {
let foothold = &old[old.len() - right_offset];
self.create_and_insert_before(new_middle, foothold);
} else if right_offset == 0 {
let foothold = old.last().unwrap();
self.create_and_insert_after(new_middle, foothold);
} else {
let foothold = &old[left_offset - 1];
self.create_and_insert_after(new_middle, foothold);
}
} else {
self.diff_keyed_middle(old_middle, new_middle);
}
}
fn diff_keyed_ends(
&mut self,
old: &'b [VNode<'b>],
new: &'b [VNode<'b>],
) -> Option<(usize, usize)> {
let mut left_offset = 0;
for (old, new) in old.iter().zip(new.iter()) {
if old.key() != new.key() {
break;
}
self.diff_node(old, new);
left_offset += 1;
}
if left_offset == old.len() {
self.create_and_insert_after(&new[left_offset..], old.last().unwrap());
return None;
}
if left_offset == new.len() {
self.remove_nodes(&old[left_offset..], true);
return None;
}
let mut right_offset = 0;
for (old, new) in old.iter().rev().zip(new.iter().rev()) {
if old.key() != new.key() {
break;
}
self.diff_node(old, new);
right_offset += 1;
}
Some((left_offset, right_offset))
}
#[allow(clippy::too_many_lines)]
fn diff_keyed_middle(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) {
debug_assert_ne!(new.first().map(VNode::key), old.first().map(VNode::key));
debug_assert_ne!(new.last().map(VNode::key), old.last().map(VNode::key));
let old_key_to_old_index = old
.iter()
.enumerate()
.map(|(i, o)| (o.key().unwrap(), i))
.collect::<FxHashMap<_, _>>();
let mut shared_keys = FxHashSet::default();
let new_index_to_old_index = new
.iter()
.map(|node| {
let key = node.key().unwrap();
if let Some(&index) = old_key_to_old_index.get(&key) {
shared_keys.insert(key);
index
} else {
u32::MAX as usize
}
})
.collect::<Vec<_>>();
if shared_keys.is_empty() {
if let Some(first_old) = old.get(0) {
self.remove_nodes(&old[1..], true);
let nodes_created = self.create_children(new);
self.replace_inner(first_old, nodes_created);
} else {
self.create_and_append_children(new);
}
return;
}
for child in old {
let key = child.key().unwrap();
if !shared_keys.contains(&key) {
self.remove_nodes([child], true);
}
}
let mut lis_sequence = Vec::default();
lis_sequence.reserve(new_index_to_old_index.len());
let mut predecessors = vec![0; new_index_to_old_index.len()];
let mut starts = vec![0; new_index_to_old_index.len()];
longest_increasing_subsequence::lis_with(
&new_index_to_old_index,
&mut lis_sequence,
|a, b| a < b,
&mut predecessors,
&mut starts,
);
lis_sequence.sort_unstable();
if lis_sequence.last().map(|f| new_index_to_old_index[*f]) == Some(u32::MAX as usize) {
lis_sequence.pop();
}
for idx in &lis_sequence {
self.diff_node(&old[new_index_to_old_index[*idx]], &new[*idx]);
}
let mut nodes_created = 0;
let last = *lis_sequence.last().unwrap();
if last < (new.len() - 1) {
for (idx, new_node) in new[(last + 1)..].iter().enumerate() {
let new_idx = idx + last + 1;
let old_index = new_index_to_old_index[new_idx];
if old_index == u32::MAX as usize {
nodes_created += self.create_node(new_node);
} else {
self.diff_node(&old[old_index], new_node);
nodes_created += self.push_all_real_nodes(new_node);
}
}
self.mutations.insert_after(
self.find_last_element(&new[last]).unwrap(),
nodes_created as u32,
);
nodes_created = 0;
}
let mut lis_iter = lis_sequence.iter().rev();
let mut last = *lis_iter.next().unwrap();
for next in lis_iter {
if last - next > 1 {
for (idx, new_node) in new[(next + 1)..last].iter().enumerate() {
let new_idx = idx + next + 1;
let old_index = new_index_to_old_index[new_idx];
if old_index == u32::MAX as usize {
nodes_created += self.create_node(new_node);
} else {
self.diff_node(&old[old_index], new_node);
nodes_created += self.push_all_real_nodes(new_node);
}
}
self.mutations.insert_before(
self.find_first_element(&new[last]).unwrap(),
nodes_created as u32,
);
nodes_created = 0;
}
last = *next;
}
let first_lis = *lis_sequence.first().unwrap();
if first_lis > 0 {
for (idx, new_node) in new[..first_lis].iter().enumerate() {
let old_index = new_index_to_old_index[idx];
if old_index == u32::MAX as usize {
nodes_created += self.create_node(new_node);
} else {
self.diff_node(&old[old_index], new_node);
nodes_created += self.push_all_real_nodes(new_node);
}
}
self.mutations.insert_before(
self.find_first_element(&new[first_lis]).unwrap(),
nodes_created as u32,
);
}
}
fn replace_node(&mut self, old: &'b VNode<'b>, new: &'b VNode<'b>) {
let nodes_created = self.create_node(new);
self.replace_inner(old, nodes_created);
}
fn replace_inner(&mut self, old: &'b VNode<'b>, nodes_created: usize) {
match old {
VNode::Element(el) => {
let id = old
.try_mounted_id()
.unwrap_or_else(|| panic!("broke on {:?}", old));
self.mutations.replace_with(id, nodes_created as u32);
self.remove_nodes(el.children, false);
self.scopes.collect_garbage(id);
}
VNode::Text(_) | VNode::Placeholder(_) => {
let id = old
.try_mounted_id()
.unwrap_or_else(|| panic!("broke on {:?}", old));
self.mutations.replace_with(id, nodes_created as u32);
self.scopes.collect_garbage(id);
}
VNode::Fragment(f) => {
self.replace_inner(&f.children[0], nodes_created);
self.remove_nodes(f.children.iter().skip(1), true);
}
VNode::Component(c) => {
log::trace!("Replacing component {:?}", old);
let scope_id = c.scope.get().unwrap();
let node = self.scopes.fin_head(scope_id);
self.enter_scope(scope_id);
{
self.replace_inner(node, nodes_created);
log::trace!("Replacing component x2 {:?}", old);
let scope = self.scopes.get_scope(scope_id).unwrap();
c.scope.set(None);
let props = scope.props.take().unwrap();
c.props.borrow_mut().replace(props);
self.scopes.try_remove(scope_id).unwrap();
}
self.leave_scope();
}
}
}
pub fn remove_nodes(&mut self, nodes: impl IntoIterator<Item = &'b VNode<'b>>, gen_muts: bool) {
for node in nodes {
match node {
VNode::Text(t) => {
if let Some(id) = t.id.get() {
self.scopes.collect_garbage(id);
t.id.set(None);
if gen_muts {
self.mutations.remove(id.as_u64());
}
}
}
VNode::Placeholder(a) => {
let id = a.id.get().unwrap();
self.scopes.collect_garbage(id);
a.id.set(None);
if gen_muts {
self.mutations.remove(id.as_u64());
}
}
VNode::Element(e) => {
let id = e.id.get().unwrap();
if gen_muts {
self.mutations.remove(id.as_u64());
}
self.scopes.collect_garbage(id);
e.id.set(None);
self.remove_nodes(e.children, false);
}
VNode::Fragment(f) => {
self.remove_nodes(f.children, gen_muts);
}
VNode::Component(c) => {
self.enter_scope(c.scope.get().unwrap());
{
let scope_id = c.scope.get().unwrap();
let root = self.scopes.root_node(scope_id);
self.remove_nodes([root], gen_muts);
let scope = self.scopes.get_scope(scope_id).unwrap();
c.scope.set(None);
let props = scope.props.take().unwrap();
c.props.borrow_mut().replace(props);
self.scopes.try_remove(scope_id).unwrap();
}
self.leave_scope();
}
}
}
}
fn create_children(&mut self, nodes: &'b [VNode<'b>]) -> usize {
let mut created = 0;
for node in nodes {
created += self.create_node(node);
}
created
}
fn create_and_append_children(&mut self, nodes: &'b [VNode<'b>]) {
let created = self.create_children(nodes);
self.mutations.append_children(created as u32);
}
fn create_and_insert_after(&mut self, nodes: &'b [VNode<'b>], after: &'b VNode<'b>) {
let created = self.create_children(nodes);
let last = self.find_last_element(after).unwrap();
self.mutations.insert_after(last, created as u32);
}
fn create_and_insert_before(&mut self, nodes: &'b [VNode<'b>], before: &'b VNode<'b>) {
let created = self.create_children(nodes);
let first = self.find_first_element(before).unwrap();
self.mutations.insert_before(first, created as u32);
}
fn current_scope(&self) -> ScopeId {
self.scope_stack.last().copied().expect("no current scope")
}
fn enter_scope(&mut self, scope: ScopeId) {
self.scope_stack.push(scope);
}
fn leave_scope(&mut self) {
self.scope_stack.pop();
}
fn find_last_element(&self, vnode: &'b VNode<'b>) -> Option<ElementId> {
let mut search_node = Some(vnode);
loop {
match &search_node.take().unwrap() {
VNode::Text(t) => break t.id.get(),
VNode::Element(t) => break t.id.get(),
VNode::Placeholder(t) => break t.id.get(),
VNode::Fragment(frag) => search_node = frag.children.last(),
VNode::Component(el) => {
let scope_id = el.scope.get().unwrap();
search_node = Some(self.scopes.root_node(scope_id));
}
}
}
}
fn find_first_element(&self, vnode: &'b VNode<'b>) -> Option<ElementId> {
let mut search_node = Some(vnode);
loop {
match &search_node.take().expect("search node to have an ID") {
VNode::Text(t) => break t.id.get(),
VNode::Element(t) => break t.id.get(),
VNode::Placeholder(t) => break t.id.get(),
VNode::Fragment(frag) => search_node = Some(&frag.children[0]),
VNode::Component(el) => {
let scope = el.scope.get().expect("element to have a scope assigned");
search_node = Some(self.scopes.root_node(scope));
}
}
}
}
fn push_all_real_nodes(&mut self, node: &'b VNode<'b>) -> usize {
match node {
VNode::Text(_) | VNode::Placeholder(_) | VNode::Element(_) => {
self.mutations.push_root(node.mounted_id());
1
}
VNode::Fragment(frag) => {
let mut added = 0;
for child in frag.children {
added += self.push_all_real_nodes(child);
}
added
}
VNode::Component(c) => {
let scope_id = c.scope.get().unwrap();
let root = self.scopes.root_node(scope_id);
self.push_all_real_nodes(root)
}
}
}
}