use super::{
add_attr::AddAnyAttr, MarkBranch, Mountable, Position, PositionState,
Render, RenderHtml,
};
use crate::{
html::attribute::{any_attribute::AnyAttribute, Attribute},
hydration::Cursor,
renderer::{CastFrom, Rndr},
ssr::StreamBuilder,
};
use drain_filter_polyfill::VecExt as VecDrainFilterExt;
use indexmap::IndexSet;
use rustc_hash::FxHasher;
use std::hash::{BuildHasherDefault, Hash};
type FxIndexSet<T> = IndexSet<T, BuildHasherDefault<FxHasher>>;
pub fn keyed<T, I, K, KF, VF, VFS, V>(
items: I,
key_fn: KF,
view_fn: VF,
) -> Keyed<T, I, K, KF, VF, VFS, V>
where
I: IntoIterator<Item = T>,
K: Eq + Hash + SerializableKey + 'static,
KF: Fn(&T) -> K,
V: Render,
VF: Fn(usize, T) -> (VFS, V),
VFS: Fn(usize),
{
Keyed {
#[cfg(not(feature = "ssr"))]
items: Some(items),
#[cfg(feature = "ssr")]
items: None,
#[cfg(feature = "ssr")]
ssr_items: items
.into_iter()
.enumerate()
.map(|(i, t)| {
let key = if cfg!(feature = "islands") {
let key = (key_fn)(&t);
key.ser_key()
} else {
String::new()
};
let (_, view) = (view_fn)(i, t);
(key, view)
})
.collect::<Vec<_>>(),
key_fn,
view_fn,
}
}
pub struct Keyed<T, I, K, KF, VF, VFS, V>
where
I: IntoIterator<Item = T>,
K: Eq + Hash + 'static,
KF: Fn(&T) -> K,
VF: Fn(usize, T) -> (VFS, V),
VFS: Fn(usize),
{
items: Option<I>,
#[cfg(feature = "ssr")]
ssr_items: Vec<(String, V)>,
key_fn: KF,
view_fn: VF,
}
pub trait SerializableKey {
fn ser_key(&self) -> String;
}
#[cfg(not(feature = "islands"))]
impl<T> SerializableKey for T {
fn ser_key(&self) -> String {
panic!(
"SerializableKey called without the `islands` feature enabled. \
Something has gone wrong."
);
}
}
#[cfg(feature = "islands")]
impl<T: serde::Serialize> SerializableKey for T {
fn ser_key(&self) -> String {
serde_json::to_string(self).expect("failed to serialize key")
}
}
pub struct KeyedState<K, VFS, V>
where
K: Eq + Hash + 'static,
VFS: Fn(usize),
V: Render,
{
parent: Option<crate::renderer::types::Element>,
marker: crate::renderer::types::Placeholder,
hashed_items: IndexSet<K, BuildHasherDefault<FxHasher>>,
rendered_items: Vec<Option<(VFS, V::State)>>,
}
impl<T, I, K, KF, VF, VFS, V> Render for Keyed<T, I, K, KF, VF, VFS, V>
where
I: IntoIterator<Item = T>,
K: Eq + Hash + SerializableKey + 'static,
KF: Fn(&T) -> K,
V: Render,
VF: Fn(usize, T) -> (VFS, V),
VFS: Fn(usize),
{
type State = KeyedState<K, VFS, V>;
fn build(self) -> Self::State {
let items = self.items.into_iter().flatten();
let (capacity, _) = items.size_hint();
let mut hashed_items =
FxIndexSet::with_capacity_and_hasher(capacity, Default::default());
let mut rendered_items = Vec::with_capacity(capacity);
for (index, item) in items.enumerate() {
hashed_items.insert((self.key_fn)(&item));
let (set_index, view) = (self.view_fn)(index, item);
rendered_items.push(Some((set_index, view.build())));
}
KeyedState {
parent: None,
marker: Rndr::create_placeholder(),
hashed_items,
rendered_items,
}
}
fn rebuild(self, state: &mut Self::State) {
let KeyedState {
parent,
marker,
hashed_items,
ref mut rendered_items,
} = state;
let new_items = self.items.into_iter().flatten();
let (capacity, _) = new_items.size_hint();
let mut new_hashed_items =
FxIndexSet::with_capacity_and_hasher(capacity, Default::default());
let mut items = Vec::new();
for item in new_items {
new_hashed_items.insert((self.key_fn)(&item));
items.push(Some(item));
}
let cmds = diff(hashed_items, &new_hashed_items);
apply_diff(
parent.as_ref(),
marker,
cmds,
rendered_items,
&self.view_fn,
items,
);
*hashed_items = new_hashed_items;
}
}
impl<T, I, K, KF, VF, VFS, V> AddAnyAttr for Keyed<T, I, K, KF, VF, VFS, V>
where
I: IntoIterator<Item = T> + Send + 'static,
K: Eq + Hash + SerializableKey + 'static,
KF: Fn(&T) -> K + Send + 'static,
V: RenderHtml,
V: 'static,
VF: Fn(usize, T) -> (VFS, V) + Send + 'static,
VFS: Fn(usize) + 'static,
T: 'static,
{
type Output<SomeNewAttr: Attribute> = Keyed<
T,
I,
K,
KF,
Box<
dyn Fn(
usize,
T,
) -> (
VFS,
<V as AddAnyAttr>::Output<SomeNewAttr::CloneableOwned>,
) + Send,
>,
VFS,
V::Output<SomeNewAttr::CloneableOwned>,
>;
fn add_any_attr<NewAttr: Attribute>(
self,
attr: NewAttr,
) -> Self::Output<NewAttr>
where
Self::Output<NewAttr>: RenderHtml,
{
let Keyed {
items,
#[cfg(feature = "ssr")]
ssr_items,
key_fn,
view_fn,
} = self;
let attr = attr.into_cloneable_owned();
Keyed {
items,
key_fn,
#[cfg(feature = "ssr")]
ssr_items: ssr_items
.into_iter()
.map(|(k, v)| (k, v.add_any_attr(attr.clone())))
.collect(),
view_fn: Box::new(move |index, item| {
let (index, view) = view_fn(index, item);
(index, view.add_any_attr(attr.clone()))
}),
}
}
}
impl<T, I, K, KF, VF, VFS, V> RenderHtml for Keyed<T, I, K, KF, VF, VFS, V>
where
I: IntoIterator<Item = T> + Send + 'static,
K: Eq + Hash + SerializableKey + 'static,
KF: Fn(&T) -> K + Send + 'static,
V: RenderHtml + 'static,
VF: Fn(usize, T) -> (VFS, V) + Send + 'static,
VFS: Fn(usize) + 'static,
T: 'static,
{
type AsyncOutput = Vec<V::AsyncOutput>; type Owned = Self;
const MIN_LENGTH: usize = 0;
fn dry_resolve(&mut self) {
#[cfg(feature = "ssr")]
for view in &mut self.ssr_items {
view.dry_resolve();
}
}
async fn resolve(self) -> Self::AsyncOutput {
#[cfg(feature = "ssr")]
{
futures::future::join_all(
self.ssr_items.into_iter().map(|(_, view)| view.resolve()),
)
.await
.into_iter()
.collect::<Vec<_>>()
}
#[cfg(not(feature = "ssr"))]
{
futures::future::join_all(
self.items.into_iter().flatten().enumerate().map(
|(index, item)| {
let (_, view) = (self.view_fn)(index, item);
view.resolve()
},
),
)
.await
.into_iter()
.collect::<Vec<_>>()
}
}
#[allow(unused)]
fn to_html_with_buf(
self,
buf: &mut String,
position: &mut Position,
escape: bool,
mark_branches: bool,
extra_attrs: Vec<AnyAttribute>,
) {
if mark_branches && escape {
buf.open_branch("for");
}
#[cfg(feature = "ssr")]
for item in self.ssr_items {
if mark_branches && escape {
buf.open_branch("item");
}
item.to_html_with_buf(
buf,
position,
escape,
mark_branches,
extra_attrs.clone(),
);
if mark_branches && escape {
buf.close_branch("item");
}
*position = Position::NextChild;
}
if mark_branches && escape {
buf.close_branch("for");
}
buf.push_str("<!>");
}
#[allow(unused)]
fn to_html_async_with_buf<const OUT_OF_ORDER: bool>(
self,
buf: &mut StreamBuilder,
position: &mut Position,
escape: bool,
mark_branches: bool,
extra_attrs: Vec<AnyAttribute>,
) {
if mark_branches && escape {
buf.open_branch("for");
}
#[cfg(feature = "ssr")]
for (key, item) in self.ssr_items {
let branch_name = mark_branches.then(|| format!("item-{key}"));
if mark_branches && escape {
buf.open_branch(branch_name.as_ref().unwrap());
}
item.to_html_async_with_buf::<OUT_OF_ORDER>(
buf,
position,
escape,
mark_branches,
extra_attrs.clone(),
);
if mark_branches && escape {
buf.close_branch(branch_name.as_ref().unwrap());
}
*position = Position::NextChild;
}
if mark_branches && escape {
buf.close_branch("for");
}
buf.push_sync("<!>");
}
fn hydrate<const FROM_SERVER: bool>(
self,
cursor: &Cursor,
position: &PositionState,
) -> Self::State {
let current = cursor.current();
let parent = if position.get() == Position::FirstChild {
current
} else {
Rndr::get_parent(¤t)
.expect("first child of keyed list has no parent")
};
let parent = crate::renderer::types::Element::cast_from(parent)
.expect("parent of keyed list should be an element");
let items = self.items.into_iter().flatten();
let (capacity, _) = items.size_hint();
let mut hashed_items =
FxIndexSet::with_capacity_and_hasher(capacity, Default::default());
let mut rendered_items = Vec::with_capacity(capacity);
for (index, item) in items.enumerate() {
hashed_items.insert((self.key_fn)(&item));
let (set_index, view) = (self.view_fn)(index, item);
let item = view.hydrate::<FROM_SERVER>(cursor, position);
rendered_items.push(Some((set_index, item)));
}
let marker = cursor.next_placeholder(position);
position.set(Position::NextChild);
KeyedState {
parent: Some(parent),
marker,
hashed_items,
rendered_items,
}
}
async fn hydrate_async(
self,
cursor: &Cursor,
position: &PositionState,
) -> Self::State {
let current = cursor.current();
let parent = if position.get() == Position::FirstChild {
current
} else {
Rndr::get_parent(¤t)
.expect("first child of keyed list has no parent")
};
let parent = crate::renderer::types::Element::cast_from(parent)
.expect("parent of keyed list should be an element");
let items = self.items.into_iter().flatten();
let (capacity, _) = items.size_hint();
let mut hashed_items =
FxIndexSet::with_capacity_and_hasher(capacity, Default::default());
let mut rendered_items = Vec::with_capacity(capacity);
for (index, item) in items.enumerate() {
hashed_items.insert((self.key_fn)(&item));
let (set_index, view) = (self.view_fn)(index, item);
let item = view.hydrate_async(cursor, position).await;
rendered_items.push(Some((set_index, item)));
}
let marker = cursor.next_placeholder(position);
position.set(Position::NextChild);
KeyedState {
parent: Some(parent),
marker,
hashed_items,
rendered_items,
}
}
fn into_owned(self) -> Self::Owned {
self
}
}
impl<K, VFS, V> Mountable for KeyedState<K, VFS, V>
where
K: Eq + Hash + 'static,
VFS: Fn(usize),
V: Render,
{
fn mount(
&mut self,
parent: &crate::renderer::types::Element,
marker: Option<&crate::renderer::types::Node>,
) {
self.parent = Some(parent.clone());
for (_, item) in self.rendered_items.iter_mut().flatten() {
item.mount(parent, marker);
}
self.marker.mount(parent, marker);
}
fn unmount(&mut self) {
for (_, item) in self.rendered_items.iter_mut().flatten() {
item.unmount();
}
self.marker.unmount();
}
fn insert_before_this(&self, child: &mut dyn Mountable) -> bool {
self.rendered_items
.first()
.map(|item| {
if let Some((_, item)) = item {
item.insert_before_this(child)
} else {
false
}
})
.unwrap_or_else(|| self.marker.insert_before_this(child))
}
fn elements(&self) -> Vec<crate::renderer::types::Element> {
self.rendered_items
.iter()
.flatten()
.flat_map(|item| item.1.elements())
.collect()
}
}
trait VecExt<T> {
fn get_next_closest_mounted_sibling(
&self,
start_at: usize,
) -> Option<&Option<T>>;
}
impl<T> VecExt<T> for Vec<Option<T>> {
fn get_next_closest_mounted_sibling(
&self,
start_at: usize,
) -> Option<&Option<T>> {
self[start_at..].iter().find(|s| s.is_some())
}
}
fn diff<K: Eq + Hash>(from: &FxIndexSet<K>, to: &FxIndexSet<K>) -> Diff {
if from.is_empty() && to.is_empty() {
return Diff::default();
} else if to.is_empty() {
return Diff {
clear: true,
..Default::default()
};
} else if from.is_empty() {
return Diff {
added: to
.iter()
.enumerate()
.map(|(at, _)| DiffOpAdd {
at,
mode: DiffOpAddMode::Append,
})
.collect(),
..Default::default()
};
}
let mut removed = vec![];
let mut moved = vec![];
let mut added = vec![];
let max_len = std::cmp::max(from.len(), to.len());
for index in 0..max_len {
let from_item = from.get_index(index);
let to_item = to.get_index(index);
if from_item != to_item {
if from_item.is_some() && !to.contains(from_item.unwrap()) {
let op = DiffOpRemove { at: index };
removed.push(op);
}
if to_item.is_some() && !from.contains(to_item.unwrap()) {
let op = DiffOpAdd {
at: index,
mode: DiffOpAddMode::Normal,
};
added.push(op);
}
if let Some(from_item) = from_item {
if let Some(to_item) = to.get_full(from_item) {
let moves_forward_by = (to_item.0 as i32) - (index as i32);
let move_in_dom = moves_forward_by
!= (added.len() as i32) - (removed.len() as i32);
let op = DiffOpMove {
from: index,
len: 1,
to: to_item.0,
move_in_dom,
};
moved.push(op);
}
}
}
}
moved = group_adjacent_moves(moved);
Diff {
removed,
items_to_move: moved.iter().map(|m| m.len).sum(),
moved,
added,
clear: false,
}
}
fn group_adjacent_moves(moved: Vec<DiffOpMove>) -> Vec<DiffOpMove> {
let mut prev: Option<DiffOpMove> = None;
let mut new_moved = Vec::with_capacity(moved.len());
for m in moved {
match prev {
Some(mut p) => {
if (m.from == p.from + p.len) && (m.to == p.to + p.len) {
p.len += 1;
prev = Some(p);
} else {
new_moved.push(prev.take().unwrap());
prev = Some(m);
}
}
None => prev = Some(m),
}
}
if let Some(prev) = prev {
new_moved.push(prev)
}
new_moved
}
#[derive(Debug, Default, PartialEq, Eq)]
struct Diff {
removed: Vec<DiffOpRemove>,
moved: Vec<DiffOpMove>,
items_to_move: usize,
added: Vec<DiffOpAdd>,
clear: bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct DiffOpMove {
from: usize,
len: usize,
to: usize,
move_in_dom: bool,
}
impl Default for DiffOpMove {
fn default() -> Self {
Self {
from: 0,
to: 0,
len: 1,
move_in_dom: true,
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
struct DiffOpAdd {
at: usize,
mode: DiffOpAddMode,
}
#[derive(Debug, PartialEq, Eq)]
struct DiffOpRemove {
at: usize,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
enum DiffOpAddMode {
#[default]
Normal,
Append,
}
fn apply_diff<T, VFS, V>(
parent: Option<&crate::renderer::types::Element>,
marker: &crate::renderer::types::Placeholder,
diff: Diff,
children: &mut Vec<Option<(VFS, V::State)>>,
view_fn: &dyn Fn(usize, T) -> (VFS, V),
mut items: Vec<Option<T>>,
) where
VFS: Fn(usize),
V: Render,
{
if diff.clear {
for (_, mut child) in children.drain(0..).flatten() {
child.unmount();
}
if diff.added.is_empty() {
return;
}
}
for DiffOpRemove { at } in &diff.removed {
let (_, mut item_to_remove) = children[*at].take().unwrap();
item_to_remove.unmount();
}
let (move_cmds, add_cmds) = unpack_moves(&diff);
let mut moved_children = move_cmds
.iter()
.map(|move_| children[move_.from].take())
.collect::<Vec<_>>();
children.resize_with(children.len() + diff.added.len(), || None);
for (i, DiffOpMove { to, .. }) in move_cmds
.iter()
.enumerate()
.filter(|(_, move_)| !move_.move_in_dom)
{
children[*to] = moved_children[i]
.take()
.inspect(|(set_index, _)| set_index(*to));
}
for (i, DiffOpMove { to, .. }) in move_cmds
.into_iter()
.enumerate()
.filter(|(_, move_)| move_.move_in_dom)
{
let (set_index, mut each_item) = moved_children[i].take().unwrap();
if let Some(parent) = parent {
if let Some(Some((_, state))) =
children.get_next_closest_mounted_sibling(to)
{
state.insert_before_this_or_marker(
parent,
&mut each_item,
Some(marker.as_ref()),
)
} else {
each_item.try_mount(parent, Some(marker.as_ref()));
}
}
set_index(to);
children[to] = Some((set_index, each_item));
}
for DiffOpAdd { at, mode } in add_cmds {
let item = items[at].take().unwrap();
let (set_index, item) = view_fn(at, item);
let mut item = item.build();
if let Some(parent) = parent {
match mode {
DiffOpAddMode::Normal => {
if let Some(Some((_, state))) =
children.get_next_closest_mounted_sibling(at)
{
state.insert_before_this_or_marker(
parent,
&mut item,
Some(marker.as_ref()),
)
} else {
item.try_mount(parent, Some(marker.as_ref()));
}
}
DiffOpAddMode::Append => {
item.try_mount(parent, Some(marker.as_ref()));
}
}
}
children[at] = Some((set_index, item));
}
#[allow(unstable_name_collisions)]
children.drain_filter(|c| c.is_none());
}
fn unpack_moves(diff: &Diff) -> (Vec<DiffOpMove>, Vec<DiffOpAdd>) {
let mut moves = Vec::with_capacity(diff.items_to_move);
let mut adds = Vec::with_capacity(diff.added.len());
let mut removes_iter = diff.removed.iter();
let mut adds_iter = diff.added.iter();
let mut moves_iter = diff.moved.iter();
let mut removes_next = removes_iter.next();
let mut adds_next = adds_iter.next();
let mut moves_next = moves_iter.next().copied();
for i in 0..diff.items_to_move + diff.added.len() + diff.removed.len() {
if let Some(DiffOpRemove { at, .. }) = removes_next {
if i == *at {
removes_next = removes_iter.next();
continue;
}
}
match (adds_next, &mut moves_next) {
(Some(add), Some(move_)) => {
if add.at == i {
adds.push(*add);
adds_next = adds_iter.next();
} else {
let mut single_move = *move_;
single_move.len = 1;
moves.push(single_move);
move_.len -= 1;
move_.from += 1;
move_.to += 1;
if move_.len == 0 {
moves_next = moves_iter.next().copied();
}
}
}
(Some(add), None) => {
adds.push(*add);
adds_next = adds_iter.next();
}
(None, Some(move_)) => {
let mut single_move = *move_;
single_move.len = 1;
moves.push(single_move);
move_.len -= 1;
move_.from += 1;
move_.to += 1;
if move_.len == 0 {
moves_next = moves_iter.next().copied();
}
}
(None, None) => break,
}
}
(moves, adds)
}