use std::rc::Rc;
use crate::{
MountedVNode, Runtime, VNode, VirtualDom, WriteMutations,
arena::ElementId,
innerlude::MountId,
mount::{MountedParent, MountedParentKind},
mutations::TargetedLazyScope,
nodes::DynamicNode,
};
use super::{
context::DiffContext,
node::EdgeScan,
template::{DynamicAnchor, DynamicNodeSlot},
};
#[derive(Clone, Copy)]
pub(crate) enum ElementEdge {
First,
Last,
}
impl ElementEdge {
pub(super) fn find_map<T>(
self,
len: usize,
mut find: impl FnMut(usize) -> Option<T>,
) -> Option<T> {
match self {
ElementEdge::First => (0..len).find_map(&mut find),
ElementEdge::Last => (0..len).rev().find_map(&mut find),
}
}
}
#[derive(Clone, Copy)]
pub(crate) enum InsertionSite {
Before(ElementId),
After(ElementId),
AppendTo(ElementId),
}
impl InsertionSite {
pub(crate) fn before(anchor: ElementId) -> Self {
Self::Before(anchor)
}
pub(super) fn after(anchor: ElementId) -> Self {
Self::After(anchor)
}
pub(super) fn append_to(anchor: ElementId) -> Self {
Self::AppendTo(anchor)
}
fn anchor(self) -> ElementId {
match self {
Self::Before(anchor) | Self::After(anchor) | Self::AppendTo(anchor) => anchor,
}
}
pub(crate) fn create_and_place_with_result<R>(
self,
to: &mut dyn WriteMutations,
runtime: Rc<Runtime>,
create: impl FnOnce(&mut dyn WriteMutations) -> (usize, R),
) -> R {
let anchor = self.anchor();
let mut to = TargetedLazyScope::new(to, runtime, move |to| to.push_id(anchor));
let (count, result) = create(&mut to);
self.splice_stack(&mut to, count);
result
}
fn splice_stack(self, to: &mut (impl WriteMutations + ?Sized), count: usize) {
if count == 0 {
return;
}
match self {
Self::Before(_) => to.insert_before(count),
Self::After(_) => to.insert_after(count),
Self::AppendTo(_) => to.append_children(count),
}
}
}
pub(crate) fn insertion_site_at(
vnode: MountedVNode<'_>,
dom: &VirtualDom,
context: Option<DiffContext<'_>>,
) -> InsertionSite {
PlacementResolver::new(dom, context).resolve_vnode_site(vnode)
}
pub(super) fn insertion_site_for_slot(
parent_mount: MountId,
slot: DynamicNodeSlot<'_>,
dom: &VirtualDom,
context: Option<DiffContext<'_>>,
) -> InsertionSite {
PlacementResolver::new(dom, context).resolve_slot(parent_mount, slot)
}
struct PlacementResolver<'dom, 'ctx> {
dom: &'dom VirtualDom,
context: Option<DiffContext<'ctx>>,
}
impl<'dom, 'ctx> PlacementResolver<'dom, 'ctx> {
fn new(dom: &'dom VirtualDom, context: Option<DiffContext<'ctx>>) -> Self {
Self { dom, context }
}
fn resolve_vnode_site(&self, vnode: MountedVNode<'_>) -> InsertionSite {
self.vnode_first_site(vnode)
.unwrap_or_else(|| self.resolve_mount_site(vnode.mount()))
}
fn resolve_slot(&self, parent_mount: MountId, slot: DynamicNodeSlot<'_>) -> InsertionSite {
if let Some(id) = self.with_parent_vnode(parent_mount, ParentVersion::Old, |parent_vnode| {
self.adjacent_dynamic_sibling_after_in_vnode(parent_vnode, parent_mount, slot)
}) {
return InsertionSite::before(id);
}
if let Some(id) = self.with_parent_vnode(parent_mount, ParentVersion::New, |parent_vnode| {
self.adjacent_dynamic_sibling_before_in_vnode(parent_vnode, parent_mount, slot)
}) {
return InsertionSite::after(id);
}
if let Some(id) = self
.dom
.mounted_anchor_node(parent_mount, slot.anchor().anchor_index())
{
return self.anchor_site(slot.anchor(), id.element_id());
}
self.resolve_mount_site(parent_mount)
}
fn resolve_mount_site(&self, mut mount: MountId) -> InsertionSite {
while let Some(render_parent) = self.dom.mounted_render_parent(mount) {
if let Some(site) = self.resolve_child_in_parent(mount, render_parent) {
return site;
}
mount = render_parent.parent();
}
InsertionSite::append_to(ElementId::ROOT)
}
fn resolve_child_in_parent(
&self,
mount: MountId,
render_parent: MountedParent,
) -> Option<InsertionSite> {
let parent_mount = render_parent.parent();
self.with_parent_vnode(parent_mount, ParentVersion::Old, |parent_vnode| {
let slot = render_parent
.slot(parent_vnode)
.expect("render parent slot should exist in the committed parent vnode");
match (
render_parent.kind(),
&parent_vnode.dynamic_node_values()[slot.index()],
) {
(
MountedParentKind::FragmentChild { child_index },
DynamicNode::Fragment(children),
) => {
let child_mounts = self.dom.mounted_fragment_children_exact(
parent_mount,
slot.index(),
children.len(),
);
self.resolve_fragment_child_site(&child_mounts, child_index, mount)
.or_else(|| Some(self.resolve_slot(parent_mount, slot)))
}
(MountedParentKind::ComponentRoot, DynamicNode::Component(_)) => {
Some(self.resolve_slot(parent_mount, slot))
}
_ => unreachable!("render parent kind should match its committed dynamic slot"),
}
})
}
fn resolve_fragment_child_site(
&self,
child_mounts: &[MountId],
position: usize,
mount: MountId,
) -> Option<InsertionSite> {
let following = || {
child_mounts
.iter()
.copied()
.skip(position + 1)
.filter(|&m| m != mount)
.find_map(|m| {
self.dom
.current_mounted_view(m)?
.find_first_element(m, self.dom)
.map(InsertionSite::before)
})
};
let previous = || {
child_mounts
.iter()
.copied()
.take(position)
.rev()
.filter(|&m| m != mount)
.find_map(|m| {
self.dom
.current_mounted_view(m)?
.find_last_element(m, self.dom)
.map(InsertionSite::after)
})
};
following().or_else(previous)
}
fn adjacent_dynamic_sibling_after_in_vnode(
&self,
parent_vnode: &VNode,
parent_mount: MountId,
slot: DynamicNodeSlot<'_>,
) -> Option<ElementId> {
parent_vnode
.dynamic_node_slots_after_sharing_insertion_position(slot)
.find_map(|sibling| {
parent_vnode.dynamic_node_edge_element(
parent_mount,
sibling.index(),
self.dom,
EdgeScan::placement(self.dom),
ElementEdge::First,
)
})
}
fn adjacent_dynamic_sibling_before_in_vnode(
&self,
parent_vnode: &VNode,
parent_mount: MountId,
slot: DynamicNodeSlot<'_>,
) -> Option<ElementId> {
let mut seen_slot = false;
for sibling in parent_vnode.dynamic_node_slots().rev() {
if !seen_slot {
seen_slot = sibling.index() == slot.index();
continue;
}
if !sibling.has_same_insertion_parent(slot) {
continue;
}
if !sibling.shares_insertion_position(slot) {
break;
}
if let Some(id) = parent_vnode.dynamic_node_edge_element(
parent_mount,
sibling.index(),
self.dom,
EdgeScan::placement(self.dom),
ElementEdge::Last,
) {
return Some(id);
}
}
None
}
fn with_parent_vnode<T>(
&self,
parent_mount: MountId,
version: ParentVersion,
f: impl FnOnce(&VNode) -> Option<T>,
) -> Option<T> {
if let Some(frame) = self
.context
.and_then(|context| context.for_mount(parent_mount))
{
let vnode = match version {
ParentVersion::Old => frame.old,
ParentVersion::New => frame.new,
};
f(vnode)
} else {
let vnode = self
.dom
.current_mounted_view(parent_mount)
.expect("parent mount");
f(&vnode)
}
}
fn vnode_first_site(&self, vnode: MountedVNode<'_>) -> Option<InsertionSite> {
vnode
.find_first_element(self.dom)
.map(InsertionSite::before)
}
fn anchor_site(&self, anchor: DynamicAnchor<'_>, anchor_id: ElementId) -> InsertionSite {
if anchor.is_parent_append_target() {
InsertionSite::append_to(anchor_id)
} else if anchor.is_last_static_node() {
InsertionSite::after(anchor_id)
} else {
InsertionSite::before(anchor_id)
}
}
}
pub(crate) fn splice_streamed_nodes<M: WriteMutations>(
to: &mut M,
site: InsertionSite,
push_nodes: impl FnOnce(&mut M) -> usize,
) -> usize {
to.push_id(site.anchor());
let count = push_nodes(to);
site.splice_stack(to, count);
to.pop();
count
}
#[derive(Clone, Copy)]
enum ParentVersion {
Old,
New,
}