use core::{cmp::Ordering, iter::Peekable};
use crate::innerlude::MountId;
use crate::{
Attribute, AttributeValue, BorrowedAttributeValue, VNode, VirtualDom, WriteMutations,
arena::MountedElementId, diff::template::DynamicAnchor, mutations::TargetedLazyScope,
};
type AttributeKey = (&'static str, Option<&'static str>);
#[derive(Default)]
pub(crate) struct AttributeDiffScratch<'a> {
old_ranges: Vec<&'a [Attribute]>,
old_offsets: Vec<usize>,
new_ranges: Vec<&'a [Attribute]>,
new_offsets: Vec<usize>,
}
#[derive(Clone, Copy)]
pub(super) struct AttrDiffTarget {
id: MountedElementId,
mount: MountId,
}
impl AttrDiffTarget {
pub(super) fn new(id: MountedElementId, mount: MountId) -> Self {
Self { id, mount }
}
}
struct AttributeDelta<'a> {
key: AttributeKey,
old: Option<&'a Attribute>,
new: Option<&'a Attribute>,
}
impl VNode {
pub(super) fn diff_attribute_list<'a>(
&'a self,
old_anchor: DynamicAnchor<'a>,
new_anchor: DynamicAnchor<'a>,
target: AttrDiffTarget,
scratch: &mut AttributeDiffScratch<'a>,
dom: &mut VirtualDom,
to: &mut dyn WriteMutations,
) {
let AttributeDiffScratch {
old_ranges,
old_offsets,
new_ranges,
new_offsets,
} = scratch;
let mut from_iter = iter_sorted_last_wins(
old_anchor.attrs().map(|slot| slot.attrs()),
old_ranges,
old_offsets,
)
.peekable();
let mut to_iter = iter_sorted_last_wins(
new_anchor.attrs().map(|slot| slot.attrs()),
new_ranges,
new_offsets,
)
.peekable();
let element_id = target.id.element_id();
let mut to =
TargetedLazyScope::new(to, dom.runtime.clone(), move |to| to.push_id(element_id));
while let Some(delta) = Self::next_attribute_diff(&mut from_iter, &mut to_iter) {
self.diff_dynamic_attribute(old_anchor, delta, target, dom, &mut to);
}
}
fn next_attribute_diff<'a>(
from_iter: &mut Peekable<impl Iterator<Item = &'a Attribute>>,
to_iter: &mut Peekable<impl Iterator<Item = &'a Attribute>>,
) -> Option<AttributeDelta<'a>> {
match (from_iter.peek().copied(), to_iter.peek().copied()) {
(Some(from), Some(to_attr)) => {
let from_key = Self::attribute_key(from);
let to_key = Self::attribute_key(to_attr);
match from_key.cmp(&to_key) {
Ordering::Less => {
from_iter.next();
Some(AttributeDelta {
key: from_key,
old: Some(from),
new: None,
})
}
Ordering::Greater => {
to_iter.next();
Some(AttributeDelta {
key: to_key,
old: None,
new: Some(to_attr),
})
}
Ordering::Equal => {
from_iter.next();
to_iter.next();
Some(AttributeDelta {
key: to_key,
old: Some(from),
new: Some(to_attr),
})
}
}
}
(Some(from), None) => {
from_iter.next();
Some(AttributeDelta {
key: Self::attribute_key(from),
old: Some(from),
new: None,
})
}
(None, Some(to_attr)) => {
to_iter.next();
Some(AttributeDelta {
key: Self::attribute_key(to_attr),
old: None,
new: Some(to_attr),
})
}
(None, None) => None,
}
}
fn diff_dynamic_attribute(
&self,
anchor: DynamicAnchor<'_>,
delta: AttributeDelta<'_>,
target: AttrDiffTarget,
dom: &mut VirtualDom,
to: &mut dyn WriteMutations,
) {
let AttributeDelta { key, old, new } = delta;
let old_listener = matches!(old.map(|a| &a.value), Some(AttributeValue::Listener(_)));
let new_listener = matches!(new.map(|a| &a.value), Some(AttributeValue::Listener(_)));
if old_listener && new_listener {
return;
}
let value_changed = old.map(|a| &a.value) != new.map(|a| &a.value);
let volatile = old.is_some_and(|a| a.volatile) || new.is_some_and(|a| a.volatile);
if !value_changed && !volatile {
return;
}
match (old_listener, new_listener, old) {
(true, _, Some(old)) => {
to.remove_event_listener(&old.name[2..]);
}
(false, true, Some(_)) => {
let (name, namespace) = key;
to.set_attribute(name, namespace, BorrowedAttributeValue::None);
}
_ => {}
}
if let Some(new) = new {
Self::write_attribute_to_current(new, target.id, target.mount, dom, to);
} else if !old_listener {
Self::remove_attribute_or_restore_static(anchor, key, to)
}
}
fn attribute_key(attribute: &Attribute) -> AttributeKey {
(attribute.name, attribute.namespace)
}
fn remove_attribute_or_restore_static(
anchor: DynamicAnchor<'_>,
key: AttributeKey,
to: &mut dyn WriteMutations,
) {
let (name, namespace) = key;
let value = anchor
.static_attr_value_for_key(key)
.map(BorrowedAttributeValue::Text)
.unwrap_or(BorrowedAttributeValue::None);
to.set_attribute(name, namespace, value);
}
pub(super) fn write_attribute_to_current(
attribute: &Attribute,
id: MountedElementId,
mount: MountId,
dom: &mut VirtualDom,
to: &mut dyn WriteMutations,
) {
let value = match &attribute.value {
AttributeValue::Listener(_) => {
dom.set_element_ref_for_mount(mount, id);
to.add_event_listener(&attribute.name[2..]);
return;
}
AttributeValue::Text(value) => BorrowedAttributeValue::Text(value),
AttributeValue::Float(value) => BorrowedAttributeValue::Float(*value),
AttributeValue::Int(value) => BorrowedAttributeValue::Int(*value),
AttributeValue::Bool(value) => BorrowedAttributeValue::Bool(*value),
AttributeValue::Any(value) => BorrowedAttributeValue::Any(value),
AttributeValue::None => BorrowedAttributeValue::None,
};
to.set_attribute(attribute.name, attribute.namespace, value);
}
}
fn iter_sorted_last_wins<'items, 'scratch>(
slots: impl IntoIterator<Item = &'items [Attribute]>,
ranges: &'scratch mut Vec<&'items [Attribute]>,
offsets: &'scratch mut Vec<usize>,
) -> SortedRangeIter<'items, 'scratch> {
ranges.clear();
ranges.extend(slots);
offsets.clear();
offsets.resize(ranges.len(), 0);
SortedRangeIter { ranges, offsets }
}
struct SortedRangeIter<'items, 'scratch> {
ranges: &'scratch Vec<&'items [Attribute]>,
offsets: &'scratch mut Vec<usize>,
}
impl<'items> Iterator for SortedRangeIter<'items, '_> {
type Item = &'items Attribute;
fn next(&mut self) -> Option<Self::Item> {
let mut min_key = None;
for (range, offset) in self.ranges.iter().zip(self.offsets.iter()) {
if let Some(item) = range.get(*offset) {
let item_key = VNode::attribute_key(item);
match min_key {
None => min_key = Some(item_key),
Some(current_min_key) if item_key < current_min_key => {
min_key = Some(item_key);
}
Some(_) => {}
}
}
}
let min_key = min_key?;
let mut last = None;
for (range_idx, range) in self.ranges.iter().enumerate() {
while let Some(item) = range.get(self.offsets[range_idx]) {
if VNode::attribute_key(item) != min_key {
break;
}
last = Some(item);
self.offsets[range_idx] += 1;
}
}
last
}
}
#[test]
fn test_iter_sorted_last_wins() {
fn attr(name: &'static str, value: &'static str) -> Attribute {
Attribute {
name,
value: AttributeValue::Text(value.to_string()),
namespace: None,
volatile: false,
}
}
let slot_a = [attr("a", "0"), attr("b", "1"), attr("c", "2")];
let slot_b = [attr("a", "5"), attr("b", "3"), attr("d", "4")];
let mut ranges = Vec::new();
let mut offsets = Vec::new();
let mut iter = iter_sorted_last_wins(
[slot_a.as_slice(), slot_b.as_slice()],
&mut ranges,
&mut offsets,
);
assert_eq!(*iter.next().unwrap(), attr("a", "5"));
assert_eq!(*iter.next().unwrap(), attr("b", "3"));
assert_eq!(*iter.next().unwrap(), attr("c", "2"));
assert_eq!(*iter.next().unwrap(), attr("d", "4"));
assert!(iter.next().is_none());
}