dioxus_native_core/utils/
persistant_iterator.rsuse smallvec::SmallVec;
use crate::{
node::FromAnyValue,
node_watcher::NodeWatcher,
prelude::{NodeMut, NodeRef},
real_dom::{NodeImmutable, RealDom},
NodeId,
};
use std::{
fmt::Debug,
sync::{Arc, Mutex},
};
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct ElementProduced {
id: NodeId,
movement: IteratorMovement,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum IteratorMovement {
Progressed,
Looped,
}
impl ElementProduced {
pub fn id(&self) -> NodeId {
self.id
}
pub fn movement(&self) -> &IteratorMovement {
&self.movement
}
fn looped(id: NodeId) -> Self {
Self {
id,
movement: IteratorMovement::Looped,
}
}
fn progressed(id: NodeId) -> Self {
Self {
id,
movement: IteratorMovement::Progressed,
}
}
}
struct PersistantElementIterUpdater<V> {
stack: Arc<Mutex<smallvec::SmallVec<[NodeId; 5]>>>,
phantom: std::marker::PhantomData<V>,
}
impl<V: FromAnyValue + Sync + Send> NodeWatcher<V> for PersistantElementIterUpdater<V> {
fn on_node_moved(&mut self, node: NodeMut<V>) {
let mut stack = self.stack.lock().unwrap();
let moved = node.id();
let rdom = node.real_dom();
if let Some(r) = stack.iter().position(|el_id| *el_id == moved) {
let back = &stack[r..];
let mut new = SmallVec::new();
let mut parent = node.parent_id();
while let Some(p) = parent.and_then(|id| rdom.get(id)) {
new.push(p.id());
parent = p.parent_id();
}
new.extend(back.iter().copied());
*stack = new;
}
}
fn on_node_removed(&mut self, node: NodeMut<V>) {
let mut stack = self.stack.lock().unwrap();
let removed = node.id();
if let Some(r) = stack.iter().position(|el_id| *el_id == removed) {
stack.truncate(r);
}
}
}
pub struct PersistantElementIter {
stack: Arc<Mutex<smallvec::SmallVec<[NodeId; 5]>>>,
}
impl PersistantElementIter {
pub fn create<V: FromAnyValue + Send + Sync>(rdom: &mut RealDom<V>) -> Self {
let inner = Arc::new(Mutex::new(smallvec::smallvec![rdom.root_id()]));
rdom.add_node_watcher(PersistantElementIterUpdater {
stack: inner.clone(),
phantom: std::marker::PhantomData,
});
PersistantElementIter { stack: inner }
}
pub fn next<V: FromAnyValue + Send + Sync>(&mut self, rdom: &RealDom<V>) -> ElementProduced {
let mut stack = self.stack.lock().unwrap();
if stack.is_empty() {
let id = rdom.root_id();
let new = id;
stack.push(new);
ElementProduced::looped(id)
} else {
let mut look_in_children = true;
loop {
if let Some(current) = stack.last().and_then(|last| rdom.get(*last)) {
if look_in_children {
if let Some(first) = current.children().first() {
let new = first.id();
stack.push(new);
return ElementProduced::progressed(new);
}
}
stack.pop();
if let Some(new) = current.next() {
let new = new.id();
stack.push(new);
return ElementProduced::progressed(new);
}
} else {
let new = rdom.root_id();
stack.clear();
stack.push(new);
return ElementProduced::looped(new);
}
look_in_children = false;
}
}
}
pub fn prev<V: FromAnyValue + Send + Sync>(&mut self, rdom: &RealDom<V>) -> ElementProduced {
fn push_back<V: FromAnyValue + Send + Sync>(
stack: &mut smallvec::SmallVec<[NodeId; 5]>,
node: NodeRef<V>,
) -> NodeId {
stack.push(node.id());
if let Some(last) = node.children().last() {
push_back(stack, *last)
} else {
node.id()
}
}
let mut stack = self.stack.lock().unwrap();
if stack.is_empty() {
let id = rdom.root_id();
let last = push_back(&mut stack, rdom.get(id).unwrap());
ElementProduced::looped(last)
} else if let Some(current) = stack.pop().and_then(|last| rdom.get(last)) {
if let Some(new) = current.prev() {
let new = push_back(&mut stack, new);
ElementProduced::progressed(new)
}
else if let Some(parent) = stack.last() {
ElementProduced::progressed(*parent)
} else {
let id = rdom.root_id();
let last = push_back(&mut stack, rdom.get(id).unwrap());
ElementProduced::looped(last)
}
} else {
let id = rdom.root_id();
let last = push_back(&mut stack, rdom.get(id).unwrap());
ElementProduced::looped(last)
}
}
}
#[test]
#[allow(unused_variables)]
fn traverse() {
use crate::dioxus::DioxusState;
use crate::prelude::*;
use dioxus::prelude::*;
#[allow(non_snake_case)]
fn Base(cx: Scope) -> Element {
render!(
div{
div{
"hello"
p{
"world"
}
"hello world"
}
}
)
}
let mut vdom = VirtualDom::new(Base);
let mutations = vdom.rebuild();
let mut rdom: RealDom = RealDom::new([]);
let mut iter = PersistantElementIter::create(&mut rdom);
let mut dioxus_state = DioxusState::create(&mut rdom);
dioxus_state.apply_mutations(&mut rdom, mutations);
let div_tag = "div".to_string();
assert!(matches!(
&*rdom.get(iter.next(&rdom).id()).unwrap().node_type(),
NodeType::Element(ElementNode { tag: div_tag, .. })
));
assert!(matches!(
&*rdom.get(iter.next(&rdom).id()).unwrap().node_type(),
NodeType::Element(ElementNode { tag: div_tag, .. })
));
let text1 = "hello".to_string();
assert!(matches!(
&*rdom.get(iter.next(&rdom).id()).unwrap().node_type(),
NodeType::Text(text1)
));
let p_tag = "p".to_string();
assert!(matches!(
&*rdom.get(iter.next(&rdom).id()).unwrap().node_type(),
NodeType::Element(ElementNode { tag: p_tag, .. })
));
let text2 = "world".to_string();
assert!(matches!(
&*rdom.get(iter.next(&rdom).id()).unwrap().node_type(),
NodeType::Text(text2)
));
let text3 = "hello world".to_string();
assert!(matches!(
&*rdom.get(iter.next(&rdom).id()).unwrap().node_type(),
NodeType::Text(text3)
));
assert!(matches!(
&*rdom.get(iter.next(&rdom).id()).unwrap().node_type(),
NodeType::Element(ElementNode { tag: div_tag, .. })
));
assert!(matches!(
&*rdom.get(iter.prev(&rdom).id()).unwrap().node_type(),
NodeType::Text(text3)
));
assert!(matches!(
&*rdom.get(iter.prev(&rdom).id()).unwrap().node_type(),
NodeType::Text(text2)
));
assert!(matches!(
&*rdom.get(iter.prev(&rdom).id()).unwrap().node_type(),
NodeType::Element(ElementNode { tag: p_tag, .. })
));
assert!(matches!(
&*rdom.get(iter.prev(&rdom).id()).unwrap().node_type(),
NodeType::Text(text1)
));
assert!(matches!(
&*rdom.get(iter.prev(&rdom).id()).unwrap().node_type(),
NodeType::Element(ElementNode { tag: div_tag, .. })
));
assert!(matches!(
&*rdom.get(iter.prev(&rdom).id()).unwrap().node_type(),
NodeType::Element(ElementNode { tag: div_tag, .. })
));
assert!(matches!(
&*rdom.get(iter.prev(&rdom).id()).unwrap().node_type(),
NodeType::Element(ElementNode { tag: div_tag, .. })
));
assert!(matches!(
&*rdom.get(iter.prev(&rdom).id()).unwrap().node_type(),
NodeType::Text(text3)
));
}
#[test]
#[allow(unused_variables)]
fn persist_removes() {
use crate::dioxus::DioxusState;
use crate::prelude::*;
use dioxus::prelude::*;
#[allow(non_snake_case)]
fn Base(cx: Scope) -> Element {
let children = match cx.generation() % 2 {
0 => 3,
1 => 2,
_ => unreachable!(),
};
render!(
div{
(0..children).map(|i|{
rsx!{
p{
key: "{i}",
"{i}"
}
}
})
}
)
}
let mut vdom = VirtualDom::new(Base);
let mut rdom: RealDom = RealDom::new([]);
let build = vdom.rebuild();
println!("{build:#?}");
let mut iter1 = PersistantElementIter::create(&mut rdom);
let mut iter2 = PersistantElementIter::create(&mut rdom);
let mut dioxus_state = DioxusState::create(&mut rdom);
dioxus_state.apply_mutations(&mut rdom, build);
iter1.next(&rdom).id();
iter2.next(&rdom).id();
iter1.next(&rdom).id();
iter2.next(&rdom).id();
iter1.next(&rdom).id();
iter2.next(&rdom).id();
iter1.next(&rdom).id();
iter2.next(&rdom).id();
iter1.next(&rdom).id();
iter2.next(&rdom).id();
iter1.next(&rdom).id();
iter2.next(&rdom).id();
iter2.next(&rdom).id();
iter2.next(&rdom).id();
vdom.mark_dirty(ScopeId::ROOT);
let update = vdom.render_immediate();
println!("{update:#?}");
dioxus_state.apply_mutations(&mut rdom, update);
let root_tag = "Root".to_string();
let idx = iter1.next(&rdom).id();
assert!(matches!(
&*rdom.get(idx).unwrap().node_type(),
NodeType::Element(ElementNode { tag: root_tag, .. })
));
let idx = iter2.next(&rdom).id();
assert!(matches!(
&*rdom.get(idx).unwrap().node_type(),
NodeType::Element(ElementNode { tag: root_tag, .. })
));
}
#[test]
#[allow(unused_variables)]
fn persist_instertions_before() {
use crate::dioxus::DioxusState;
use crate::prelude::*;
use dioxus::prelude::*;
#[allow(non_snake_case)]
fn Base(cx: Scope) -> Element {
let children = match cx.generation() % 2 {
0 => 3,
1 => 2,
_ => unreachable!(),
};
render!(
div{
(0..children).map(|i|{
rsx!{
p{
key: "{i}",
"{i}"
}
}
})
}
)
}
let mut vdom = VirtualDom::new(Base);
let mut rdom: RealDom = RealDom::new([]);
let mut dioxus_state = DioxusState::create(&mut rdom);
let build = vdom.rebuild();
dioxus_state.apply_mutations(&mut rdom, build);
let mut iter = PersistantElementIter::create(&mut rdom);
iter.next(&rdom).id();
iter.next(&rdom).id();
iter.next(&rdom).id();
iter.next(&rdom).id();
iter.next(&rdom).id();
vdom.mark_dirty(ScopeId::ROOT);
let update = vdom.render_immediate();
dioxus_state.apply_mutations(&mut rdom, update);
let p_tag = "div".to_string();
let idx = iter.next(&rdom).id();
assert!(matches!(
&*rdom.get(idx).unwrap().node_type(),
NodeType::Element(ElementNode { tag: p_tag, .. })
));
}
#[test]
#[allow(unused_variables)]
fn persist_instertions_after() {
use crate::dioxus::DioxusState;
use crate::prelude::*;
use dioxus::prelude::*;
#[allow(non_snake_case)]
fn Base(cx: Scope) -> Element {
let children = match cx.generation() % 2 {
0 => 3,
1 => 2,
_ => unreachable!(),
};
render!(
div{
(0..children).map(|i|{
rsx!{
p{
key: "{i}",
"{i}"
}
}
})
}
)
}
let mut vdom = VirtualDom::new(Base);
let mut rdom: RealDom = RealDom::new([]);
let mut iter = PersistantElementIter::create(&mut rdom);
let mut dioxus_state = DioxusState::create(&mut rdom);
let build = vdom.rebuild();
dioxus_state.apply_mutations(&mut rdom, build);
iter.next(&rdom).id();
iter.next(&rdom).id();
iter.next(&rdom).id();
iter.next(&rdom).id();
iter.next(&rdom).id();
let update = vdom.rebuild();
dioxus_state.apply_mutations(&mut rdom, update);
let p_tag = "p".to_string();
let idx = iter.next(&rdom).id();
assert!(matches!(
&*rdom.get(idx).unwrap().node_type(),
NodeType::Element(ElementNode { tag: p_tag, .. })
));
let text = "hello world".to_string();
let idx = iter.next(&rdom).id();
assert!(matches!(
&*rdom.get(idx).unwrap().node_type(),
NodeType::Text(text)
));
}