Implement new mutations with native core (#630)

* work on seperating tree struture from realdom

* intial update to new mutations

* handle height

* update to taffy 0.2

* add as_varient functions to OwnedAttributeValue

* make get node parent mut optionally return a parent

* work on upward pass

* add more code for parrellel passes

* make resolve passes public

* more work on parallel passes

* fix deadlock and add more tests

* move height into the tree instead of the realdom

* make passes exicute in parallel instead of executing invidual passes in parellel

* fix some warnings

* add up dependant test

* clean up examples

* work on intigrating state with passes

* update to new mutations

* work on implementing macro

* make the macro compile

* more progress on macro

* mark cloned nodes as dirty

* update persistant_iterator utility

* fix mask generation

* update tui with new mutations

* more progress updating tui

* some basic tui examples working

* don't push template nodes onto the stack

* update hover example

* update benchmark

* update more examples

* fix root node layout

* ignore out of bounds renders

* update color picker example

* update all events example

* update remaining examples

* update tests

* tests passing

* move persistant iterator test

* update examples

* fix gaps in layout

* fix formatting

* fix memory leak
This commit is contained in:
Demonthos 2022-12-06 17:38:04 -06:00 committed by GitHub
parent b9aad5e8f4
commit 67a6fa9eb8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
38 changed files with 3405 additions and 2417 deletions

View file

@ -66,13 +66,6 @@ impl VirtualDom {
// safety: we're not going to modify the suspense context but we don't want to make a clone of it
let fiber = self.acquire_suspense_boundary(leaf.scope_id);
println!("ready pool");
println!(
"Existing mutations {:?}, scope {:?}",
fiber.mutations, fiber.id
);
let scope = &mut self.scopes[scope_id.0];
let arena = scope.current_frame();
@ -105,16 +98,9 @@ impl VirtualDom {
std::mem::swap(&mut self.mutations, mutations);
if fiber.waiting_on.borrow().is_empty() {
println!("fiber is finished!");
self.finished_fibers.push(fiber.id);
} else {
println!("fiber is not finished {:?}", fiber.waiting_on);
}
} else {
println!("nodes arent right");
}
} else {
println!("not ready");
}
}
}

2
packages/native-core-macro/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
/target
Cargo.lock

View file

@ -3,7 +3,7 @@ extern crate proc_macro;
mod sorted_slice;
use proc_macro::TokenStream;
use quote::{quote, ToTokens};
use quote::{quote, ToTokens, __private::Span};
use sorted_slice::StrSlice;
use syn::parenthesized;
use syn::parse::ParseBuffer;
@ -148,83 +148,29 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream {
let strct = Struct::new(type_name.clone(), &fields);
match StateStruct::parse(&fields, &strct) {
Ok(state_strct) => {
let members: Vec<_> = state_strct
.state_members
.iter()
.map(|m| &m.mem.ident)
.collect();
let passes = state_strct.state_members.iter().map(|m| {
let unit = &m.mem.unit_type;
match m.dep_kind {
DependencyKind::Node => quote! {dioxus_native_core::AnyPass::Node(&#unit)},
DependencyKind::Child => quote! {dioxus_native_core::AnyPass::Upward(&#unit)},
DependencyKind::Parent => {
quote! {dioxus_native_core::AnyPass::Downward(&#unit)}
}
}
});
let member_types = state_strct.state_members.iter().map(|m| &m.mem.ty);
let resolve_members = state_strct
let impl_members = state_strct
.state_members
.iter()
.map(|m| state_strct.resolve(m));
let child_types = state_strct.child_states.iter().map(|s| &s.ty);
let child_members = state_strct.child_states.iter().map(|s| &s.ident);
.map(|m| m.impl_pass(state_strct.ty));
let gen = quote! {
#(#impl_members)*
impl State for #type_name {
fn update<'a, T: dioxus_native_core::traversable::Traversable<Node = Self, Id = dioxus_native_core::RealNodeId>,T2: dioxus_native_core::traversable::Traversable<Node = dioxus_native_core::real_dom::NodeData, Id = dioxus_native_core::RealNodeId>>(
dirty: &[(dioxus_native_core::RealNodeId, dioxus_native_core::node_ref::NodeMask)],
state_tree: &'a mut T,
rdom: &'a T2,
ctx: &anymap::AnyMap,
) -> rustc_hash::FxHashSet<dioxus_native_core::RealNodeId>{
#[derive(Clone, Copy)]
struct MembersDirty {
#(#members: bool, )*
}
impl MembersDirty {
fn new() -> Self {
Self {#(#members: false),*}
}
fn any(&self) -> bool {
#(self.#members || )* false
}
fn union(self, other: Self) -> Self {
Self {#(#members: self.#members || other.#members),*}
}
}
let mut dirty_elements = rustc_hash::FxHashSet::default();
// the states of any elements that are dirty
let mut states: rustc_hash::FxHashMap<dioxus_native_core::RealNodeId, MembersDirty> = rustc_hash::FxHashMap::default();
for (id, mask) in dirty {
let members_dirty = MembersDirty {
#(#members: #member_types::NODE_MASK.overlaps(mask),)*
};
if members_dirty.any(){
if let Some(state) = states.get_mut(id){
*state = state.union(members_dirty);
}
else{
states.insert(*id, members_dirty);
}
}
dirty_elements.insert(*id);
}
#(
#resolve_members;
)*
#(
dirty_elements.extend(
<#child_types as dioxus_native_core::state::State>::update(
dirty,
&mut state_tree.map(|n| &n.#child_members, |n| &mut n.#child_members),
rdom,
ctx,
)
);
)*
dirty_elements
}
const PASSES: &'static [dioxus_native_core::AnyPass<dioxus_native_core::node::Node<Self>>] = &[
#(#passes),*
];
const MASKS: &'static [dioxus_native_core::NodeMask] = &[#(#member_types::NODE_MASK),*];
}
};
gen.into()
@ -233,12 +179,6 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream {
}
}
struct Depenadants<'a> {
node: Vec<&'a Member>,
child: Vec<&'a Member>,
parent: Vec<&'a Member>,
}
struct Struct {
name: Ident,
members: Vec<Member>,
@ -246,21 +186,27 @@ struct Struct {
impl Struct {
fn new(name: Ident, fields: &[&Field]) -> Self {
let members = fields.iter().filter_map(|f| Member::parse(f)).collect();
let members = fields
.iter()
.enumerate()
.filter_map(|(i, f)| Member::parse(&name, f, i as u64))
.collect();
Self { name, members }
}
}
struct StateStruct<'a> {
state_members: Vec<StateMember<'a>>,
#[allow(unused)]
child_states: Vec<&'a Member>,
ty: &'a Ident,
}
impl<'a> StateStruct<'a> {
/// Parse the state structure, and find a resolution order that will allow us to update the state for each node in after the state(s) it depends on have been resolved.
fn parse(fields: &[&'a Field], strct: &'a Struct) -> Result<Self> {
let mut parse_err = Ok(());
let mut unordered_state_members: Vec<_> = strct
let mut state_members: Vec<_> = strct
.members
.iter()
.zip(fields.iter())
@ -273,41 +219,10 @@ impl<'a> StateStruct<'a> {
})
.collect();
parse_err?;
let mut state_members = Vec::new();
// Keep adding members that have had all of there dependancies resolved until there are no more members left.
while !unordered_state_members.is_empty() {
let mut resolved = false;
for i in 0..unordered_state_members.len() {
let mem = &mut unordered_state_members[i];
// if this member has all of its dependancies resolved other than itself, resolve it next.
if mem.dep_mems.iter().all(|(dep, resolved)| {
*resolved || (*dep == mem.mem && mem.dep_kind != DependencyKind::Node)
}) {
let mem = unordered_state_members.remove(i);
// mark any dependency that depends on this member as resolved
for member in unordered_state_members.iter_mut() {
for (dep, resolved) in &mut member.dep_mems {
*resolved |= *dep == mem.mem;
}
}
state_members.push(mem);
resolved = true;
break;
}
}
if !resolved {
return Err(Error::new(
strct.name.span(),
format!(
"{} has circular dependacy in {:?}",
strct.name,
unordered_state_members
.iter()
.map(|m| format!("{}", &m.mem.ident))
.collect::<Vec<_>>()
),
));
for i in 0..state_members.len() {
let deps: Vec<_> = state_members[i].dep_mems.iter().map(|m| m.id).collect();
for dep in deps {
state_members[dep as usize].dependant_mems.push(i as u64);
}
}
@ -327,199 +242,11 @@ impl<'a> StateStruct<'a> {
// members need to be sorted so that members are updated after the members they depend on
Ok(Self {
ty: &strct.name,
state_members,
child_states: child_states.collect(),
})
}
fn get_depenadants(&self, mem: &Member) -> Depenadants {
let mut dependants = Depenadants {
node: Vec::new(),
child: Vec::new(),
parent: Vec::new(),
};
for member in &self.state_members {
for (dep, _) in &member.dep_mems {
if *dep == mem {
match member.dep_kind {
DependencyKind::Node => dependants.node.push(member.mem),
DependencyKind::Child => dependants.child.push(member.mem),
DependencyKind::Parent => dependants.parent.push(member.mem),
}
}
}
}
dependants
}
// Mark the states that depend on the current state as dirty
fn update_dependants(&self, mem: &Member) -> impl ToTokens {
let dep = self.get_depenadants(mem);
let update_child_dependants = if dep.child.is_empty() {
quote!()
} else {
let insert = dep.child.iter().map(|d|{
if *d == mem {
quote! {
let seeking = dioxus_native_core::HeightOrdering::new(state_tree.height(parent_id).unwrap(), parent_id);
if let Err(idx) = resolution_order
.binary_search_by(|ordering| ordering.cmp(&seeking).reverse()){
resolution_order.insert(
idx,
seeking,
);
}
}
} else {
quote! {}
}
});
let update: Vec<_> = dep
.child
.iter()
.map(|d| {
let ident = &d.ident;
quote! {
dirty.#ident = true;
}
})
.collect();
quote! {
if let Some(parent_id) = state_tree.parent(id) {
#(#insert)*
if let Some(dirty) = states.get_mut(&parent_id) {
#(#update)*
}
else {
let mut dirty = MembersDirty::new();
#(#update)*
states.insert(parent_id, dirty);
}
}
}
};
let node_dependants: Vec<_> = dep.node.iter().map(|d| &d.ident).collect();
let update_node_dependants = quote! {#(members_dirty.#node_dependants = true;)*};
let update_parent_dependants = if dep.parent.is_empty() {
quote!()
} else {
let insert = dep.parent.iter().map(|d| {
if *d == mem {
quote! {
let seeking = dioxus_native_core::HeightOrdering::new(state_tree.height(*child_id).unwrap(), *child_id);
if let Err(idx) = resolution_order
.binary_search(&seeking){
resolution_order.insert(
idx,
seeking,
);
}
}
} else {
quote! {}
}
});
let update: Vec<_> = dep
.parent
.iter()
.map(|d| {
let ident = &d.ident;
quote! {
dirty.#ident = true;
}
})
.collect();
quote! {
for child_id in state_tree.children(id) {
#(#insert)*
if let Some(dirty) = states.get_mut(&child_id) {
#(#update)*
}
else {
let mut dirty = MembersDirty::new();
#(#update)*
states.insert(*child_id, dirty);
}
}
}
};
quote! {
#update_node_dependants
#update_child_dependants
#update_parent_dependants
}
}
// Generate code to resolve this state
fn resolve(&self, mem: &StateMember) -> impl ToTokens {
let reduce_member = mem.reduce_self();
let update_dependant = self.update_dependants(mem.mem);
let member = &mem.mem.ident;
match mem.dep_kind {
DependencyKind::Parent => {
quote! {
// resolve parent dependant state
let mut resolution_order = states.keys().copied().map(|id| dioxus_native_core::HeightOrdering::new(state_tree.height(id).unwrap(), id)).collect::<Vec<_>>();
resolution_order.sort();
let mut i = 0;
while i < resolution_order.len(){
let id = resolution_order[i].id;
let node = rdom.get(id).unwrap();
let members_dirty = states.get_mut(&id).unwrap();
let (current_state, parent) = state_tree.get_node_parent_mut(id);
let current_state = current_state.unwrap();
if members_dirty.#member && #reduce_member {
dirty_elements.insert(id);
#update_dependant
}
i += 1;
}
}
}
DependencyKind::Child => {
quote! {
// resolve child dependant state
let mut resolution_order = states.keys().copied().map(|id| dioxus_native_core::HeightOrdering::new(state_tree.height(id).unwrap(), id)).collect::<Vec<_>>();
resolution_order.sort_by(|height_ordering1, height_ordering2| {
height_ordering1.cmp(&height_ordering2).reverse()
});
let mut i = 0;
while i < resolution_order.len(){
let id = resolution_order[i].id;
let node = rdom.get(id).unwrap();
let members_dirty = states.get_mut(&id).unwrap();
let (current_state, children) = state_tree.get_node_children_mut(id);
let current_state = current_state.unwrap();
if members_dirty.#member && #reduce_member {
dirty_elements.insert(id);
#update_dependant
}
i += 1;
}
}
}
DependencyKind::Node => {
quote! {
// resolve node dependant state
let mut resolution_order = states.keys().copied().collect::<Vec<_>>();
let mut i = 0;
while i < resolution_order.len(){
let id = resolution_order[i];
let node = rdom.get(id).unwrap();
let members_dirty = states.get_mut(&id).unwrap();
let current_state = state_tree.get_mut(id).unwrap();
if members_dirty.#member && #reduce_member {
dirty_elements.insert(id);
#update_dependant
}
i += 1;
}
}
}
}
}
}
fn try_parenthesized(input: ParseStream) -> Result<ParseBuffer> {
@ -562,14 +289,25 @@ impl Parse for Dependency {
/// The type of the member and the ident of the member
#[derive(PartialEq, Debug)]
struct Member {
id: u64,
ty: Type,
unit_type: Ident,
ident: Ident,
}
impl Member {
fn parse(field: &Field) -> Option<Self> {
fn parse(parent: &Ident, field: &Field, id: u64) -> Option<Self> {
Some(Self {
id,
ty: field.ty.clone(),
unit_type: Ident::new(
("_Unit".to_string()
+ parent.to_token_stream().to_string().as_str()
+ field.ty.to_token_stream().to_string().as_str())
.as_str(),
Span::call_site(),
)
.into(),
ident: field.ident.as_ref()?.clone(),
})
}
@ -581,7 +319,9 @@ struct StateMember<'a> {
// the kind of dependncies this state has
dep_kind: DependencyKind,
// the depenancy and if it is satified
dep_mems: Vec<(&'a Member, bool)>,
dep_mems: Vec<&'a Member>,
// any members that depend on this member
dependant_mems: Vec<u64>,
// the context this state requires
ctx_ty: Option<Type>,
}
@ -610,7 +350,7 @@ impl<'a> StateMember<'a> {
.iter()
.filter_map(|name| {
if let Some(found) = parent.members.iter().find(|m| &m.ident == name) {
Some((found, false))
Some(found)
} else {
err = Err(Error::new(
name.span(),
@ -624,6 +364,7 @@ impl<'a> StateMember<'a> {
mem,
dep_kind,
dep_mems,
dependant_mems: Vec::new(),
ctx_ty: dependency.ctx_ty,
})
}
@ -638,7 +379,7 @@ impl<'a> StateMember<'a> {
}
/// generate code to call the resolve function for the state. This does not handle checking if resolving the state is necessary, or marking the states that depend on this state as dirty.
fn reduce_self(&self) -> quote::__private::TokenStream {
fn impl_pass(&self, parent_type: &Ident) -> quote::__private::TokenStream {
let ident = &self.mem.ident;
let get_ctx = if let Some(ctx_ty) = &self.ctx_ty {
if ctx_ty == &parse_quote!(()) {
@ -652,23 +393,128 @@ impl<'a> StateMember<'a> {
};
let ty = &self.mem.ty;
let node_view = quote!(dioxus_native_core::node_ref::NodeView::new(node, #ty::NODE_MASK));
let dep_idents = self.dep_mems.iter().map(|m| &m.0.ident);
match self.dep_kind {
let unit_type = &self.mem.unit_type;
let node_view = quote!(dioxus_native_core::node_ref::NodeView::new(unsafe{&*{&node.node_data as *const _}}, #ty::NODE_MASK));
let dep_idents = self.dep_mems.iter().map(|m| &m.ident);
let impl_specific = match self.dep_kind {
DependencyKind::Node => {
quote!({
current_state.#ident.reduce(#node_view, (#(&current_state.#dep_idents,)*), #get_ctx)
})
quote! {
impl dioxus_native_core::NodePass<dioxus_native_core::node::Node<#parent_type>> for #unit_type {
fn pass(&self, node: &mut dioxus_native_core::node::Node<#parent_type>, ctx: &dioxus_native_core::SendAnyMap) -> bool {
node.state.#ident.reduce(#node_view, (#(&node.state.#dep_idents,)*), #get_ctx)
}
}
}
}
DependencyKind::Child => {
quote!({
current_state.#ident.reduce(#node_view, children.iter().map(|c| (#(&c.#dep_idents)*)), #get_ctx)
})
let update = if self.dep_mems.iter().any(|m| m.id == self.mem.id) {
quote! {
if update {
dioxus_native_core::PassReturn{
progress: true,
mark_dirty: true,
}
} else {
dioxus_native_core::PassReturn{
progress: false,
mark_dirty: false,
}
}
}
} else {
quote! {
if update {
dioxus_native_core::PassReturn{
progress: false,
mark_dirty: true,
}
} else {
dioxus_native_core::PassReturn{
progress: false,
mark_dirty: false,
}
}
}
};
quote!(
impl dioxus_native_core::UpwardPass<dioxus_native_core::node::Node<#parent_type>> for #unit_type{
fn pass<'a>(
&self,
node: &mut dioxus_native_core::node::Node<#parent_type>,
children: &mut dyn Iterator<Item = &'a mut dioxus_native_core::node::Node<#parent_type>>,
ctx: &dioxus_native_core::SendAnyMap,
) -> dioxus_native_core::PassReturn {
let update = node.state.#ident.reduce(#node_view, children.map(|c| (#(&c.state.#dep_idents,)*)), #get_ctx);
#update
}
}
)
}
DependencyKind::Parent => {
quote!({
current_state.#ident.reduce(#node_view, parent.as_ref().map(|p| (#(&p.#dep_idents)*)), #get_ctx)
})
let update = if self.dep_mems.iter().any(|m| m.id == self.mem.id) {
quote! {
if update {
dioxus_native_core::PassReturn{
progress: true,
mark_dirty: true,
}
} else {
dioxus_native_core::PassReturn{
progress: false,
mark_dirty: false,
}
}
}
} else {
quote! {
if update {
dioxus_native_core::PassReturn{
progress: false,
mark_dirty: true,
}
} else {
dioxus_native_core::PassReturn{
progress: false,
mark_dirty: false,
}
}
}
};
quote!(
impl dioxus_native_core::DownwardPass<dioxus_native_core::node::Node<#parent_type>> for #unit_type {
fn pass(&self, node: &mut dioxus_native_core::node::Node<#parent_type>, parent: Option<&mut dioxus_native_core::node::Node<#parent_type>>, ctx: &dioxus_native_core::SendAnyMap) -> dioxus_native_core::PassReturn{
let update = node.state.#ident.reduce(#node_view, parent.as_ref().map(|p| (#(&p.state.#dep_idents,)*)), #get_ctx);
#update
}
}
)
}
};
let pass_id = self.mem.id;
let depenancies = self.dep_mems.iter().map(|m| m.id);
let dependants = &self.dependant_mems;
let mask = self
.dep_mems
.iter()
.map(|m| 1u64 << m.id)
.fold(1 << self.mem.id, |a, b| a | b);
quote! {
#[derive(Clone, Copy)]
struct #unit_type;
#impl_specific
impl dioxus_native_core::Pass for #unit_type {
fn pass_id(&self) -> dioxus_native_core::PassId {
dioxus_native_core::PassId(#pass_id)
}
fn dependancies(&self) -> &'static [dioxus_native_core::PassId] {
&[#(dioxus_native_core::PassId(#depenancies)),*]
}
fn dependants(&self) -> &'static [dioxus_native_core::PassId] {
&[#(dioxus_native_core::PassId(#dependants)),*]
}
fn mask(&self) -> dioxus_native_core::MemberMask {
dioxus_native_core::MemberMask(#mask)
}
}
}
}

View file

@ -2,7 +2,11 @@ use anymap::AnyMap;
use dioxus::prelude::*;
use dioxus_native_core::node_ref::*;
use dioxus_native_core::real_dom::*;
use dioxus_native_core::state::{ChildDepState, NodeDepState, ParentDepState, State};
use dioxus_native_core::state::{
ChildDepState, ElementBorrowable, NodeDepState, ParentDepState, State,
};
use dioxus_native_core::tree::*;
use dioxus_native_core::SendAnyMap;
use dioxus_native_core_macro::State;
macro_rules! dep {
@ -14,7 +18,7 @@ macro_rules! dep {
fn reduce<'a>(
&mut self,
_: NodeView,
_: impl Iterator<Item = &'a Self::DepState>,
_: impl Iterator<Item = <Self::DepState as ElementBorrowable>::ElementBorrowed<'a>>,
_: &Self::Ctx,
) -> bool
where
@ -34,7 +38,7 @@ macro_rules! dep {
fn reduce(
&mut self,
_: NodeView,
_: Option<&Self::DepState>,
_: Option<<Self::DepState as ElementBorrowable>::ElementBorrowed<'_>>,
_: &Self::Ctx,
) -> bool {
self.0 += 1;
@ -43,14 +47,15 @@ macro_rules! dep {
}
};
( node( $name:ty, ($($l:lifetime),*), $dep:ty ) ) => {
impl<$($l),*> NodeDepState<$dep> for $name {
( node( $name:ty, $dep:ty ) ) => {
impl NodeDepState for $name {
type Ctx = ();
type DepState = $dep;
const NODE_MASK: NodeMask = NodeMask::ALL;
fn reduce(
&mut self,
_: NodeView,
_: $dep,
_: <Self::DepState as ElementBorrowable>::ElementBorrowed<'_>,
_: &Self::Ctx,
) -> bool {
self.0 += 1;
@ -66,27 +71,7 @@ macro_rules! test_state{
fn state_reduce_initally_called_minimally() {
#[allow(non_snake_case)]
fn Base(cx: Scope) -> Element {
render!(div {
div{
div{
p{}
}
p{
"hello"
}
div{
h1{}
}
p{
"world"
}
}
})
}
let vdom = VirtualDom::new(Base);
let mutations = vdom.create_vnodes(rsx! {
render!{
div {
div{
div{
@ -103,12 +88,17 @@ macro_rules! test_state{
}
}
}
});
}
}
let mut vdom = VirtualDom::new(Base);
let mutations = vdom.rebuild();
let mut dom: RealDom<$s> = RealDom::new();
let nodes_updated = dom.apply_mutations(vec![mutations]);
let _to_rerender = dom.update_state(nodes_updated, AnyMap::new());
let (nodes_updated, _) = dom.apply_mutations(mutations);
let _to_rerender = dom.update_state(nodes_updated, SendAnyMap::new());
dom.traverse_depth_first(|n| {
$(
@ -129,15 +119,15 @@ mod node_depends_on_child_and_parent {
use super::*;
#[derive(Debug, Clone, Default, PartialEq)]
struct Node(i32);
dep!(node(Node, ('a, 'b), (&'a Child, &'b Parent)));
dep!(node(Node, (Child, Parent)));
#[derive(Debug, Clone, Default, PartialEq)]
struct Child(i32);
dep!(child(Child, Child));
dep!(child(Child, (Child,)));
#[derive(Debug, Clone, Default, PartialEq)]
struct Parent(i32);
dep!(parent(Parent, Parent));
dep!(parent(Parent, (Parent,)));
#[derive(Debug, Clone, Default, State)]
struct StateTester {
@ -156,15 +146,15 @@ mod child_depends_on_node_that_depends_on_parent {
use super::*;
#[derive(Debug, Clone, Default, PartialEq)]
struct Node(i32);
dep!(node(Node, ('a), (&'a Parent,)));
dep!(node(Node, (Parent,)));
#[derive(Debug, Clone, Default, PartialEq)]
struct Child(i32);
dep!(child(Child, Node));
dep!(child(Child, (Node,)));
#[derive(Debug, Clone, Default, PartialEq)]
struct Parent(i32);
dep!(parent(Parent, Parent));
dep!(parent(Parent, (Parent,)));
#[derive(Debug, Clone, Default, State)]
struct StateTester {
@ -183,15 +173,15 @@ mod parent_depends_on_node_that_depends_on_child {
use super::*;
#[derive(Debug, Clone, Default, PartialEq)]
struct Node(i32);
dep!(node(Node, ('a), (&'a Child,)));
dep!(node(Node, (Child,)));
#[derive(Debug, Clone, Default, PartialEq)]
struct Child(i32);
dep!(child(Child, Child));
dep!(child(Child, (Child,)));
#[derive(Debug, Clone, Default, PartialEq)]
struct Parent(i32);
dep!(parent(Parent, Node));
dep!(parent(Parent, (Node,)));
#[derive(Debug, Clone, Default, State)]
struct StateTester {
@ -210,11 +200,11 @@ mod node_depends_on_other_node_state {
use super::*;
#[derive(Debug, Clone, Default, PartialEq)]
struct Node1(i32);
dep!(node(Node1, ('a), (&'a Node2,)));
dep!(node(Node1, (Node2,)));
#[derive(Debug, Clone, Default, PartialEq)]
struct Node2(i32);
dep!(node(Node2, (), ()));
dep!(node(Node2, ()));
#[derive(Debug, Clone, Default, State)]
struct StateTester {
@ -231,15 +221,15 @@ mod node_child_and_parent_state_depends_on_self {
use super::*;
#[derive(Debug, Clone, Default, PartialEq)]
struct Node(i32);
dep!(node(Node, (), ()));
dep!(node(Node, ()));
#[derive(Debug, Clone, Default, PartialEq)]
struct Child(i32);
dep!(child(Child, Child));
dep!(child(Child, (Child,)));
#[derive(Debug, Clone, Default, PartialEq)]
struct Parent(i32);
dep!(parent(Parent, Parent));
dep!(parent(Parent, (Parent,)));
#[derive(Debug, Clone, Default, State)]
struct StateTester {

View file

@ -1,78 +0,0 @@
use dioxus::core::ElementId;
use dioxus::prelude::*;
use dioxus_native_core::real_dom::RealDom;
use dioxus_native_core::state::State;
use dioxus_native_core::RealNodeId;
use dioxus_native_core_macro::State;
#[derive(State, Default, Clone)]
struct Empty {}
#[test]
fn remove_node() {
#[allow(non_snake_case)]
fn Base(cx: Scope) -> Element {
render!(div {})
}
let vdom = VirtualDom::new(Base);
let mut dom: RealDom<Empty> = RealDom::new();
let (create, edit) = vdom.diff_lazynodes(
rsx! {
div{
div{}
}
},
rsx! {
div{}
},
);
println!("create: {:#?}", create);
println!("edit: {:#?}", edit);
let _to_update = dom.apply_mutations(vec![create]);
assert_eq!(dom[RealNodeId::ElementId(ElementId(0))].node_data.height, 0);
assert_eq!(dom[RealNodeId::UnaccessableId(0)].node_data.height, 1);
dom.apply_mutations(vec![edit]);
assert_eq!(dom.size(), 3);
assert_eq!(dom[RealNodeId::ElementId(ElementId(0))].node_data.height, 0);
}
#[test]
fn add_node() {
#[allow(non_snake_case)]
fn Base(cx: Scope) -> Element {
render!(div {})
}
let vdom = VirtualDom::new(Base);
let (create, update) = vdom.diff_lazynodes(
rsx! {
div{}
},
rsx! {
div{
p{}
}
},
);
let mut dom: RealDom<Empty> = RealDom::new();
let _to_update = dom.apply_mutations(vec![create]);
assert_eq!(dom.size(), 2);
assert_eq!(dom[RealNodeId::ElementId(ElementId(2))].node_data.height, 1);
dom.apply_mutations(vec![update]);
assert_eq!(dom.size(), 3);
assert_eq!(dom[RealNodeId::ElementId(ElementId(3))].node_data.height, 0);
assert_eq!(dom[RealNodeId::UnaccessableId(0)].node_data.height, 1);
}

View file

@ -1,63 +0,0 @@
use dioxus::core::ElementId;
use dioxus::prelude::*;
use dioxus_native_core::real_dom::RealDom;
use dioxus_native_core::state::State;
use dioxus_native_core::RealNodeId;
use dioxus_native_core_macro::State;
#[derive(Default, Clone, State)]
struct Empty {}
#[test]
fn initial_build_simple() {
#[allow(non_snake_case)]
fn Base(cx: Scope) -> Element {
render!(div {})
}
let vdom = VirtualDom::new(Base);
let mutations = vdom.create_vnodes(rsx! {
div{}
});
let mut dom: RealDom<Empty> = RealDom::new();
let _to_update = dom.apply_mutations(vec![mutations]);
assert_eq!(dom.size(), 2);
assert_eq!(dom[RealNodeId::ElementId(ElementId(2))].node_data.height, 1);
}
#[test]
fn initial_build_with_children() {
#[allow(non_snake_case)]
fn Base(cx: Scope) -> Element {
render!(div {})
}
let vdom = VirtualDom::new(Base);
let mutations = vdom.create_vnodes(rsx! {
div{
div{
"hello"
p{
"world"
}
"hello world"
}
}
});
let mut dom: RealDom<Empty> = RealDom::new();
let _to_update = dom.apply_mutations(vec![mutations]);
assert_eq!(dom.size(), 2);
assert_eq!(dom[RealNodeId::ElementId(ElementId(2))].node_data.height, 1);
assert_eq!(dom[RealNodeId::UnaccessableId(6)].node_data.height, 2);
assert_eq!(dom[RealNodeId::UnaccessableId(5)].node_data.height, 3);
assert_eq!(dom[RealNodeId::UnaccessableId(8)].node_data.height, 3);
assert_eq!(dom[RealNodeId::UnaccessableId(10)].node_data.height, 3);
assert_eq!(dom[RealNodeId::UnaccessableId(9)].node_data.height, 4);
}

View file

@ -1,333 +0,0 @@
use dioxus::core_macro::rsx_without_templates;
use dioxus::prelude::*;
use dioxus_native_core::{
real_dom::{NodeType, RealDom},
state::State,
utils::PersistantElementIter,
};
use dioxus_native_core_macro::State;
#[derive(State, Default, Clone)]
struct Empty {}
#[test]
#[allow(unused_variables)]
fn traverse() {
#[allow(non_snake_case)]
fn Base(cx: Scope) -> Element {
render!(div {})
}
let vdom = VirtualDom::new(Base);
let mutations = vdom.create_vnodes(rsx! {
div{
div{
"hello"
p{
"world"
}
"hello world"
}
}
});
let mut rdom: RealDom<Empty> = RealDom::new();
let _to_update = rdom.apply_mutations(vec![mutations]);
let mut iter = PersistantElementIter::new();
let div_tag = "div".to_string();
assert!(matches!(
&rdom[iter.next(&rdom).id()].node_data.node_type,
NodeType::Element { tag: div_tag, .. }
));
assert!(matches!(
&rdom[iter.next(&rdom).id()].node_data.node_type,
NodeType::Element { tag: div_tag, .. }
));
let text1 = "hello".to_string();
assert!(matches!(
&rdom[iter.next(&rdom).id()].node_data.node_type,
NodeType::Text { text: text1, .. }
));
let p_tag = "p".to_string();
assert!(matches!(
&rdom[iter.next(&rdom).id()].node_data.node_type,
NodeType::Element { tag: p_tag, .. }
));
let text2 = "world".to_string();
assert!(matches!(
&rdom[iter.next(&rdom).id()].node_data.node_type,
NodeType::Text { text: text2, .. }
));
let text3 = "hello world".to_string();
assert!(matches!(
&rdom[iter.next(&rdom).id()].node_data.node_type,
NodeType::Text { text: text3, .. }
));
assert!(matches!(
&rdom[iter.next(&rdom).id()].node_data.node_type,
NodeType::Element { tag: div_tag, .. }
));
assert!(matches!(
&rdom[iter.prev(&rdom).id()].node_data.node_type,
NodeType::Text { text: text3, .. }
));
assert!(matches!(
&rdom[iter.prev(&rdom).id()].node_data.node_type,
NodeType::Text { text: text2, .. }
));
assert!(matches!(
&rdom[iter.prev(&rdom).id()].node_data.node_type,
NodeType::Element { tag: p_tag, .. }
));
assert!(matches!(
&rdom[iter.prev(&rdom).id()].node_data.node_type,
NodeType::Text { text: text1, .. }
));
assert!(matches!(
&rdom[iter.prev(&rdom).id()].node_data.node_type,
NodeType::Element { tag: div_tag, .. }
));
assert!(matches!(
&rdom[iter.prev(&rdom).id()].node_data.node_type,
NodeType::Element { tag: div_tag, .. }
));
assert!(matches!(
&rdom[iter.prev(&rdom).id()].node_data.node_type,
NodeType::Element { tag: div_tag, .. }
));
assert!(matches!(
&rdom[iter.prev(&rdom).id()].node_data.node_type,
NodeType::Text { text: text3, .. }
));
}
#[test]
#[allow(unused_variables)]
fn persist_removes() {
#[allow(non_snake_case)]
fn Base(cx: Scope) -> Element {
render!(div {})
}
let vdom = VirtualDom::new(Base);
let (build, update) = vdom.diff_lazynodes(
rsx_without_templates! {
div{
p{
key: "1",
"hello"
}
p{
key: "2",
"world"
}
p{
key: "3",
"hello world"
}
}
},
rsx_without_templates! {
div{
p{
key: "1",
"hello"
}
p{
key: "3",
"hello world"
}
}
},
);
let mut rdom: RealDom<Empty> = RealDom::new();
let _to_update = rdom.apply_mutations(vec![build]);
// this will end on the node that is removed
let mut iter1 = PersistantElementIter::new();
// this will end on the after node that is removed
let mut iter2 = PersistantElementIter::new();
// div
iter1.next(&rdom).id();
iter2.next(&rdom).id();
// p
iter1.next(&rdom).id();
iter2.next(&rdom).id();
// "hello"
iter1.next(&rdom).id();
iter2.next(&rdom).id();
// p
iter1.next(&rdom).id();
iter2.next(&rdom).id();
// "world"
iter1.next(&rdom).id();
iter2.next(&rdom).id();
// p
iter2.next(&rdom).id();
// "hello world"
iter2.next(&rdom).id();
iter1.prune(&update, &rdom);
iter2.prune(&update, &rdom);
let _to_update = rdom.apply_mutations(vec![update]);
let p_tag = "p".to_string();
let idx = iter1.next(&rdom).id();
assert!(matches!(
&rdom[idx].node_data.node_type,
NodeType::Element { tag: p_tag, .. }
));
let text = "hello world".to_string();
let idx = iter1.next(&rdom).id();
assert!(matches!(
&rdom[idx].node_data.node_type,
NodeType::Text { text, .. }
));
let div_tag = "div".to_string();
let idx = iter2.next(&rdom).id();
assert!(matches!(
&rdom[idx].node_data.node_type,
NodeType::Element { tag: div_tag, .. }
));
}
#[test]
#[allow(unused_variables)]
fn persist_instertions_before() {
#[allow(non_snake_case)]
fn Base(cx: Scope) -> Element {
render!(div {})
}
let vdom = VirtualDom::new(Base);
let (build, update) = vdom.diff_lazynodes(
rsx_without_templates! {
div{
p{
key: "1",
"hello"
}
p{
key: "3",
"hello world"
}
}
},
rsx_without_templates! {
div{
p{
key: "1",
"hello"
}
p{
key: "2",
"world"
}
p{
key: "3",
"hello world"
}
}
},
);
let mut rdom: RealDom<Empty> = RealDom::new();
let _to_update = rdom.apply_mutations(vec![build]);
let mut iter = PersistantElementIter::new();
// div
iter.next(&rdom).id();
// p
iter.next(&rdom).id();
// "hello"
iter.next(&rdom).id();
// p
iter.next(&rdom).id();
// "hello world"
iter.next(&rdom).id();
iter.prune(&update, &rdom);
let _to_update = rdom.apply_mutations(vec![update]);
let p_tag = "div".to_string();
let idx = iter.next(&rdom).id();
assert!(matches!(
&rdom[idx].node_data.node_type,
NodeType::Element { tag: p_tag, .. }
));
}
#[test]
#[allow(unused_variables)]
fn persist_instertions_after() {
#[allow(non_snake_case)]
fn Base(cx: Scope) -> Element {
render!(div {})
}
let vdom = VirtualDom::new(Base);
let (build, update) = vdom.diff_lazynodes(
rsx_without_templates! {
div{
p{
key: "1",
"hello"
}
p{
key: "2",
"world"
}
}
},
rsx_without_templates! {
div{
p{
key: "1",
"hello"
}
p{
key: "2",
"world"
}
p{
key: "3",
"hello world"
}
}
},
);
let mut rdom: RealDom<Empty> = RealDom::new();
let _to_update = rdom.apply_mutations(vec![build]);
let mut iter = PersistantElementIter::new();
// div
iter.next(&rdom).id();
// p
iter.next(&rdom).id();
// "hello"
iter.next(&rdom).id();
// p
iter.next(&rdom).id();
// "world"
iter.next(&rdom).id();
iter.prune(&update, &rdom);
let _to_update = rdom.apply_mutations(vec![update]);
let p_tag = "p".to_string();
let idx = iter.next(&rdom).id();
assert!(matches!(
&rdom[idx].node_data.node_type,
NodeType::Element { tag: p_tag, .. }
));
let text = "hello world".to_string();
let idx = iter.next(&rdom).id();
assert!(matches!(
&rdom[idx].node_data.node_type,
NodeType::Text { text, .. }
));
}

View file

@ -1,43 +1,18 @@
use anymap::AnyMap;
use dioxus::core::ElementId;
use dioxus::core::{AttributeValue, DomEdit, Mutations};
use dioxus::core_macro::rsx_without_templates;
use dioxus::core::{AttributeValue, Mutations};
use dioxus::prelude::*;
use dioxus_native_core::real_dom::*;
use dioxus_native_core::state::{ChildDepState, NodeDepState, ParentDepState, State};
use dioxus_native_core::{node_ref::*, RealNodeId};
use dioxus_native_core::tree::TreeView;
use dioxus_native_core::{node_ref::*, NodeId, RealNodeId, SendAnyMap};
use dioxus_native_core_macro::State;
#[derive(Debug, Clone, Default, State)]
struct CallCounterStatePart1 {
#[child_dep_state(child_counter)]
child_counter: ChildDepCallCounter,
}
#[derive(Debug, Clone, Default, State)]
struct CallCounterStatePart2 {
#[parent_dep_state(parent_counter)]
parent_counter: ParentDepCallCounter,
}
#[derive(Debug, Clone, Default, State)]
struct CallCounterStatePart3 {
#[node_dep_state()]
node_counter: NodeDepCallCounter,
}
#[derive(Debug, Clone, Default, State)]
struct CallCounterState {
#[child_dep_state(child_counter)]
child_counter: ChildDepCallCounter,
#[state]
part2: CallCounterStatePart2,
#[parent_dep_state(parent_counter)]
parent_counter: ParentDepCallCounter,
#[state]
part1: CallCounterStatePart1,
#[state]
part3: CallCounterStatePart3,
#[node_dep_state()]
node_counter: NodeDepCallCounter,
}
@ -46,12 +21,12 @@ struct CallCounterState {
struct ChildDepCallCounter(u32);
impl ChildDepState for ChildDepCallCounter {
type Ctx = ();
type DepState = Self;
type DepState = (Self,);
const NODE_MASK: NodeMask = NodeMask::ALL;
fn reduce<'a>(
&mut self,
_: NodeView,
_: impl Iterator<Item = &'a Self::DepState>,
_: impl Iterator<Item = (&'a Self,)>,
_: &Self::Ctx,
) -> bool
where
@ -66,23 +41,20 @@ impl ChildDepState for ChildDepCallCounter {
struct ParentDepCallCounter(u32);
impl ParentDepState for ParentDepCallCounter {
type Ctx = ();
type DepState = Self;
type DepState = (Self,);
const NODE_MASK: NodeMask = NodeMask::ALL;
fn reduce(
&mut self,
_node: NodeView,
_parent: Option<&Self::DepState>,
_ctx: &Self::Ctx,
) -> bool {
fn reduce(&mut self, _node: NodeView, _parent: Option<(&Self,)>, _ctx: &Self::Ctx) -> bool {
self.0 += 1;
println!("ParentDepCallCounter::reduce on {:?}\n{}", _node, self.0);
true
}
}
#[derive(Debug, Clone, Default)]
struct NodeDepCallCounter(u32);
impl NodeDepState<()> for NodeDepCallCounter {
impl NodeDepState for NodeDepCallCounter {
type Ctx = ();
type DepState = ();
const NODE_MASK: NodeMask = NodeMask::ALL;
fn reduce(&mut self, _node: NodeView, _sibling: (), _ctx: &Self::Ctx) -> bool {
self.0 += 1;
@ -95,12 +67,12 @@ impl NodeDepState<()> for NodeDepCallCounter {
struct BubbledUpStateTester(Option<String>, Vec<Box<BubbledUpStateTester>>);
impl ChildDepState for BubbledUpStateTester {
type Ctx = u32;
type DepState = Self;
type DepState = (Self,);
const NODE_MASK: NodeMask = NodeMask::new().with_tag();
fn reduce<'a>(
&mut self,
node: NodeView,
children: impl Iterator<Item = &'a Self::DepState>,
children: impl Iterator<Item = (&'a Self,)>,
ctx: &Self::Ctx,
) -> bool
where
@ -109,7 +81,10 @@ impl ChildDepState for BubbledUpStateTester {
assert_eq!(*ctx, 42);
*self = BubbledUpStateTester(
node.tag().map(|s| s.to_string()),
children.into_iter().map(|c| Box::new(c.clone())).collect(),
children
.into_iter()
.map(|(c,)| Box::new(c.clone()))
.collect(),
);
true
}
@ -119,13 +94,13 @@ impl ChildDepState for BubbledUpStateTester {
struct PushedDownStateTester(Option<String>, Option<Box<PushedDownStateTester>>);
impl ParentDepState for PushedDownStateTester {
type Ctx = u32;
type DepState = Self;
type DepState = (Self,);
const NODE_MASK: NodeMask = NodeMask::new().with_tag();
fn reduce(&mut self, node: NodeView, parent: Option<&Self::DepState>, ctx: &Self::Ctx) -> bool {
fn reduce(&mut self, node: NodeView, parent: Option<(&Self,)>, ctx: &Self::Ctx) -> bool {
assert_eq!(*ctx, 42);
*self = PushedDownStateTester(
node.tag().map(|s| s.to_string()),
parent.map(|c| Box::new(c.clone())),
parent.map(|(c,)| Box::new(c.clone())),
);
true
}
@ -133,8 +108,9 @@ impl ParentDepState for PushedDownStateTester {
#[derive(Debug, Clone, PartialEq, Default)]
struct NodeStateTester(Option<String>, Vec<(String, String)>);
impl NodeDepState<()> for NodeStateTester {
impl NodeDepState for NodeStateTester {
type Ctx = u32;
type DepState = ();
const NODE_MASK: NodeMask = NodeMask::new_with_attrs(AttributeMask::All).with_tag();
fn reduce(&mut self, node: NodeView, _sibling: (), ctx: &Self::Ctx) -> bool {
assert_eq!(*ctx, 42);
@ -170,31 +146,29 @@ struct StateTester {
fn state_initial() {
#[allow(non_snake_case)]
fn Base(cx: Scope) -> Element {
render!(div {
p{}
h1{}
})
}
let vdom = VirtualDom::new(Base);
let mutations = vdom.create_vnodes(rsx! {
render! {
div {
p{
color: "red"
}
h1{}
}
});
}
}
let mut vdom = VirtualDom::new(Base);
let mutations = vdom.rebuild();
let mut dom: RealDom<StateTester> = RealDom::new();
let nodes_updated = dom.apply_mutations(vec![mutations]);
let mut ctx = AnyMap::new();
let (nodes_updated, _) = dom.apply_mutations(mutations);
let mut ctx = SendAnyMap::new();
ctx.insert(42u32);
let _to_rerender = dom.update_state(nodes_updated, ctx);
let root_div = &dom[RealNodeId::ElementId(ElementId(2))];
let root_div_id = dom.children_ids(NodeId(0)).unwrap()[0];
let root_div = &dom.get(root_div_id).unwrap();
assert_eq!(root_div.state.bubbled.0, Some("div".to_string()));
assert_eq!(
root_div.state.bubbled.1,
@ -214,7 +188,8 @@ fn state_initial() {
assert_eq!(root_div.state.node.0, Some("div".to_string()));
assert_eq!(root_div.state.node.1, vec![]);
let child_p = &dom[RealNodeId::UnaccessableId(3)];
let child_p_id = dom.children_ids(root_div_id).unwrap()[0];
let child_p = &dom[child_p_id];
assert_eq!(child_p.state.bubbled.0, Some("p".to_string()));
assert_eq!(child_p.state.bubbled.1, Vec::new());
assert_eq!(child_p.state.pushed.0, Some("p".to_string()));
@ -234,7 +209,8 @@ fn state_initial() {
vec![("color".to_string(), "red".to_string())]
);
let child_h1 = &dom[RealNodeId::UnaccessableId(4)];
let child_h1_id = dom.children_ids(root_div_id).unwrap()[1];
let child_h1 = &dom[child_h1_id];
assert_eq!(child_h1.state.bubbled.0, Some("h1".to_string()));
assert_eq!(child_h1.state.bubbled.1, Vec::new());
assert_eq!(child_h1.state.pushed.0, Some("h1".to_string()));
@ -256,8 +232,10 @@ fn state_initial() {
fn state_reduce_parent_called_minimally_on_update() {
#[allow(non_snake_case)]
fn Base(cx: Scope) -> Element {
render!(div {
width: "100%",
let width = if cx.generation() == 0 { "100%" } else { "99%" };
cx.render(rsx! {
div {
width: "{width}",
div{
div{
p{}
@ -272,50 +250,28 @@ fn state_reduce_parent_called_minimally_on_update() {
"world"
}
}
}
})
}
let vdom = VirtualDom::new(Base);
let mutations = vdom.create_vnodes(rsx_without_templates! {
div {
width: "100%",
div{
div{
p{}
}
p{
"hello"
}
div{
h1{}
}
p{
"world"
}
}
}
});
let mut vdom = VirtualDom::new(Base);
let mut dom: RealDom<CallCounterState> = RealDom::new();
let nodes_updated = dom.apply_mutations(vec![mutations]);
let _to_rerender = dom.update_state(nodes_updated, AnyMap::new());
let nodes_updated = dom.apply_mutations(vec![Mutations {
edits: vec![DomEdit::SetAttribute {
root: Some(1),
field: "width",
value: AttributeValue::Text("99%"),
ns: Some("style"),
}],
dirty_scopes: rustc_hash::FxHashSet::default(),
refs: Vec::new(),
}]);
let _to_rerender = dom.update_state(nodes_updated, AnyMap::new());
let (nodes_updated, _) = dom.apply_mutations(vdom.rebuild());
let _to_rerender = dom.update_state(nodes_updated, SendAnyMap::new());
vdom.mark_dirty(ScopeId(0));
let (nodes_updated, _) = dom.apply_mutations(vdom.render_immediate());
let _to_rerender = dom.update_state(nodes_updated, SendAnyMap::new());
let mut is_root = true;
dom.traverse_depth_first(|n| {
assert_eq!(n.state.part2.parent_counter.0, 2);
if is_root {
is_root = false;
assert_eq!(n.state.parent_counter.0, 1);
} else {
assert_eq!(n.state.parent_counter.0, 2);
}
});
}
@ -323,90 +279,59 @@ fn state_reduce_parent_called_minimally_on_update() {
fn state_reduce_child_called_minimally_on_update() {
#[allow(non_snake_case)]
fn Base(cx: Scope) -> Element {
cx.render(rsx_without_templates!(div {
div{
div{
p{
width: "100%",
}
}
p{
"hello"
}
div{
h1{}
}
p{
"world"
}
}
}))
}
let vdom = VirtualDom::new(Base);
let mutations = vdom.create_vnodes(rsx_without_templates! {
let width = if cx.generation() == 0 { "100%" } else { "99%" };
cx.render(rsx! {
// updated: 2
div {
// updated: 2
div{
// updated: 2
div{
// updated: 2
p{
width: "100%",
width: "{width}",
}
}
// updated: 1
p{
// updated: 1
"hello"
}
// updated: 1
div{
// updated: 1
h1{}
}
// updated: 1
p{
// updated: 1
"world"
}
}
}
});
})
}
let mut vdom = VirtualDom::new(Base);
let mut dom: RealDom<CallCounterState> = RealDom::new();
let nodes_updated = dom.apply_mutations(vec![mutations]);
let _to_rerender = dom.update_state(nodes_updated, AnyMap::new());
let nodes_updated = dom.apply_mutations(vec![Mutations {
edits: vec![DomEdit::SetAttribute {
root: Some(4),
field: "width",
value: AttributeValue::Text("99%"),
ns: Some("style"),
}],
dirty_scopes: rustc_hash::FxHashSet::default(),
refs: Vec::new(),
}]);
let _to_rerender = dom.update_state(nodes_updated, AnyMap::new());
let (nodes_updated, _) = dom.apply_mutations(vdom.rebuild());
let _to_rerender = dom.update_state(nodes_updated, SendAnyMap::new());
vdom.mark_dirty(ScopeId(0));
let (nodes_updated, _) = dom.apply_mutations(vdom.render_immediate());
let _to_rerender = dom.update_state(nodes_updated, SendAnyMap::new());
let mut traverse_count = 0;
dom.traverse_depth_first(|n| {
assert_eq!(
n.state.part1.child_counter.0,
if let Some(RealNodeId::ElementId(ElementId(id))) = n.node_data.id {
if id > 4 {
assert_eq!(n.state.child_counter.0, {
if traverse_count > 4 {
1
} else {
2
}
} else {
panic!()
}
);
assert_eq!(
n.state.child_counter.0,
if let Some(RealNodeId::ElementId(ElementId(id))) = n.node_data.id {
if id > 4 {
1
} else {
2
}
} else {
panic!()
}
);
});
traverse_count += 1;
});
}
@ -422,13 +347,14 @@ struct UnorderedDependanciesState {
#[derive(Debug, Clone, Default, PartialEq)]
struct ADepCallCounter(usize, BDepCallCounter);
impl<'a> NodeDepState<(&'a BDepCallCounter,)> for ADepCallCounter {
impl NodeDepState for ADepCallCounter {
type Ctx = ();
type DepState = (BDepCallCounter,);
const NODE_MASK: NodeMask = NodeMask::NONE;
fn reduce(
&mut self,
_node: NodeView,
(sibling,): (&'a BDepCallCounter,),
(sibling,): (&BDepCallCounter,),
_ctx: &Self::Ctx,
) -> bool {
self.0 += 1;
@ -439,13 +365,14 @@ impl<'a> NodeDepState<(&'a BDepCallCounter,)> for ADepCallCounter {
#[derive(Debug, Clone, Default, PartialEq)]
struct BDepCallCounter(usize, CDepCallCounter);
impl<'a> NodeDepState<(&'a CDepCallCounter,)> for BDepCallCounter {
impl NodeDepState for BDepCallCounter {
type Ctx = ();
type DepState = (CDepCallCounter,);
const NODE_MASK: NodeMask = NodeMask::NONE;
fn reduce(
&mut self,
_node: NodeView,
(sibling,): (&'a CDepCallCounter,),
(sibling,): (&CDepCallCounter,),
_ctx: &Self::Ctx,
) -> bool {
self.0 += 1;
@ -456,8 +383,9 @@ impl<'a> NodeDepState<(&'a CDepCallCounter,)> for BDepCallCounter {
#[derive(Debug, Clone, Default, PartialEq)]
struct CDepCallCounter(usize);
impl NodeDepState<()> for CDepCallCounter {
impl NodeDepState for CDepCallCounter {
type Ctx = ();
type DepState = ();
const NODE_MASK: NodeMask = NodeMask::ALL;
fn reduce(&mut self, _node: NodeView, _sibling: (), _ctx: &Self::Ctx) -> bool {
self.0 += 1;
@ -477,21 +405,13 @@ fn dependancies_order_independant() {
})
}
let vdom = VirtualDom::new(Base);
let mutations = vdom.create_vnodes(rsx! {
div {
width: "100%",
p{
"hello"
}
}
});
let mut vdom = VirtualDom::new(Base);
let mut dom: RealDom<UnorderedDependanciesState> = RealDom::new();
let nodes_updated = dom.apply_mutations(vec![mutations]);
let _to_rerender = dom.update_state(nodes_updated, AnyMap::new());
let mutations = vdom.rebuild();
let (nodes_updated, _) = dom.apply_mutations(mutations);
let _to_rerender = dom.update_state(nodes_updated, SendAnyMap::new());
let c = CDepCallCounter(1);
let b = BDepCallCounter(1, c.clone());
@ -502,15 +422,3 @@ fn dependancies_order_independant() {
assert_eq!(&n.state.c, &c);
});
}
#[derive(Clone, Default, State)]
struct DependanciesStateTest {
#[node_dep_state(c)]
b: BDepCallCounter,
#[node_dep_state()]
c: CDepCallCounter,
#[node_dep_state(b)]
a: ADepCallCounter,
#[state]
child: UnorderedDependanciesState,
}

2
packages/native-core/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
/target
Cargo.lock

View file

@ -15,11 +15,15 @@ dioxus-core = { path = "../core", version = "^0.2.1" }
dioxus-html = { path = "../html", version = "^0.2.1" }
dioxus-core-macro = { path = "../core-macro", version = "^0.2.1" }
taffy = "0.1.0"
taffy = "0.2.1"
smallvec = "1.6"
rustc-hash = "1.1.0"
anymap = "0.12.1"
slab = "0.4"
parking_lot = "0.12.1"
crossbeam-deque = "0.8.2"
dashmap = "5.4.0"
[dev-dependencies]
rand = "0.8.5"
dioxus = { path = "../dioxus", version = "^0.2.1" }

View file

@ -343,14 +343,14 @@ fn apply_border(name: &str, value: &str, style: &mut Style) {
"border-left" => {}
"border-left-color" => {}
"border-left-style" => {
if style.border.start == Dimension::default() {
if style.border.left == Dimension::default() {
let v = Dimension::Points(1.0);
style.border.start = v;
style.border.left = v;
}
}
"border-left-width" => {
if let Some(v) = parse_value(value) {
style.border.start = v;
style.border.left = v;
}
}
"border-radius" => {}
@ -358,11 +358,11 @@ fn apply_border(name: &str, value: &str, style: &mut Style) {
"border-right-color" => {}
"border-right-style" => {
let v = Dimension::Points(1.0);
style.border.end = v;
style.border.right = v;
}
"border-right-width" => {
if let Some(v) = parse_value(value) {
style.border.end = v;
style.border.right = v;
}
}
"border-spacing" => {}
@ -375,13 +375,13 @@ fn apply_border(name: &str, value: &str, style: &mut Style) {
let v = Dimension::Points(1.0);
style.border.bottom = v;
}
if style.border.start == Dimension::default() {
if style.border.left == Dimension::default() {
let v = Dimension::Points(1.0);
style.border.start = v;
style.border.left = v;
}
if style.border.end == Dimension::default() {
if style.border.right == Dimension::default() {
let v = Dimension::Points(1.0);
style.border.end = v;
style.border.right = v;
}
}
"border-top" => {}
@ -404,8 +404,8 @@ fn apply_border(name: &str, value: &str, style: &mut Style) {
if values.len() == 1 {
if let Some(dim) = parse_value(values[0]) {
style.border = Rect {
start: dim,
end: dim,
right: dim,
left: dim,
top: dim,
bottom: dim,
};
@ -414,8 +414,8 @@ fn apply_border(name: &str, value: &str, style: &mut Style) {
let border_widths = [
&mut style.border.top,
&mut style.border.bottom,
&mut style.border.start,
&mut style.border.end,
&mut style.border.left,
&mut style.border.right,
];
for (v, width) in values.into_iter().zip(border_widths) {
if let Some(w) = parse_value(v) {
@ -512,12 +512,12 @@ fn apply_padding(name: &str, value: &str, style: &mut Style) {
"padding" => {
style.padding.top = v;
style.padding.bottom = v;
style.padding.start = v;
style.padding.end = v;
style.padding.left = v;
style.padding.right = v;
}
"padding-bottom" => style.padding.bottom = v,
"padding-left" => style.padding.start = v,
"padding-right" => style.padding.end = v,
"padding-left" => style.padding.left = v,
"padding-right" => style.padding.right = v,
"padding-top" => style.padding.top = v,
_ => {}
}
@ -578,13 +578,13 @@ fn apply_margin(name: &str, value: &str, style: &mut Style) {
"margin" => {
style.margin.top = dim;
style.margin.bottom = dim;
style.margin.start = dim;
style.margin.end = dim;
style.margin.left = dim;
style.margin.right = dim;
}
"margin-top" => style.margin.top = dim,
"margin-bottom" => style.margin.bottom = dim,
"margin-left" => style.margin.start = dim,
"margin-right" => style.margin.end = dim,
"margin-left" => style.margin.left = dim,
"margin-right" => style.margin.right = dim,
_ => {}
}
}

View file

@ -1,54 +1,26 @@
use std::cmp::Ordering;
use std::hash::BuildHasherDefault;
use dioxus_core::ElementId;
pub use node_ref::NodeMask;
pub use passes::{
AnyPass, DownwardPass, MemberMask, NodePass, Pass, PassId, PassReturn, UpwardPass,
};
use rustc_hash::FxHasher;
pub use tree::NodeId;
pub mod layout_attributes;
pub mod node;
pub mod node_ref;
pub mod passes;
pub mod real_dom;
pub mod state;
#[doc(hidden)]
pub mod traversable;
pub mod tree;
pub mod utils;
/// A id for a node that lives in the real dom.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum RealNodeId {
ElementId(ElementId),
UnaccessableId(usize),
}
impl RealNodeId {
pub fn as_element_id(&self) -> ElementId {
match self {
RealNodeId::ElementId(id) => *id,
RealNodeId::UnaccessableId(_) => panic!("Expected element id"),
}
}
pub fn as_unaccessable_id(&self) -> usize {
match self {
RealNodeId::ElementId(_) => panic!("Expected unaccessable id"),
RealNodeId::UnaccessableId(id) => *id,
}
}
}
impl Ord for RealNodeId {
fn cmp(&self, other: &Self) -> Ordering {
self.partial_cmp(other).unwrap()
}
}
impl PartialOrd for RealNodeId {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
match (self, other) {
(Self::ElementId(a), Self::ElementId(b)) => a.0.partial_cmp(&b.0),
(Self::UnaccessableId(a), Self::UnaccessableId(b)) => a.partial_cmp(b),
(Self::ElementId(_), Self::UnaccessableId(_)) => Some(Ordering::Greater),
(Self::UnaccessableId(_), Self::ElementId(_)) => Some(Ordering::Less),
}
}
}
pub type RealNodeId = NodeId;
pub type FxDashMap<K, V> = dashmap::DashMap<K, V, BuildHasherDefault<FxHasher>>;
pub type FxDashSet<K> = dashmap::DashSet<K, BuildHasherDefault<FxHasher>>;
pub type SendAnyMap = anymap::Map<dyn anymap::any::Any + Send + Sync + 'static>;
/// Used in derived state macros
#[derive(Eq, PartialEq)]

View file

@ -0,0 +1,119 @@
use crate::{state::State, tree::NodeId};
use dioxus_core::ElementId;
use rustc_hash::{FxHashMap, FxHashSet};
/// The node is stored client side and stores only basic data about the node.
#[derive(Debug, Clone)]
pub struct Node<S: State> {
/// The transformed state of the node.
pub state: S,
/// The raw data for the node
pub node_data: NodeData,
}
#[derive(Debug, Clone)]
pub struct NodeData {
/// The id of the node
pub node_id: NodeId,
/// The id of the node in the vdom.
pub element_id: Option<ElementId>,
/// Additional inforation specific to the node type
pub node_type: NodeType,
}
/// A type of node with data specific to the node type. The types are a subset of the [VNode] types.
#[derive(Debug, Clone)]
pub enum NodeType {
Text {
text: String,
},
Element {
tag: String,
namespace: Option<String>,
attributes: FxHashMap<OwnedAttributeDiscription, OwnedAttributeValue>,
listeners: FxHashSet<String>,
},
Placeholder,
}
impl<S: State> Node<S> {
pub(crate) fn new(node_type: NodeType) -> Self {
Node {
state: S::default(),
node_data: NodeData {
element_id: None,
node_type,
node_id: NodeId(0),
},
}
}
/// get the mounted id of the node
pub fn mounted_id(&self) -> Option<ElementId> {
self.node_data.element_id
}
}
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct OwnedAttributeDiscription {
pub name: String,
pub namespace: Option<String>,
pub volatile: bool,
}
/// An attribute on a DOM node, such as `id="my-thing"` or
/// `href="https://example.com"`.
#[derive(Clone, Copy, Debug)]
pub struct OwnedAttributeView<'a> {
/// The discription of the attribute.
pub attribute: &'a OwnedAttributeDiscription,
/// The value of the attribute.
pub value: &'a OwnedAttributeValue,
}
#[derive(Clone, Debug)]
pub enum OwnedAttributeValue {
Text(String),
Float(f32),
Int(i32),
Bool(bool),
None,
}
impl OwnedAttributeValue {
pub fn as_text(&self) -> Option<&str> {
match self {
OwnedAttributeValue::Text(text) => Some(text),
_ => None,
}
}
pub fn as_float(&self) -> Option<f32> {
match self {
OwnedAttributeValue::Float(float) => Some(*float),
_ => None,
}
}
pub fn as_int(&self) -> Option<i32> {
match self {
OwnedAttributeValue::Int(int) => Some(*int),
_ => None,
}
}
pub fn as_bool(&self) -> Option<bool> {
match self {
OwnedAttributeValue::Bool(bool) => Some(*bool),
_ => None,
}
}
pub fn as_none(&self) -> Option<()> {
match self {
OwnedAttributeValue::None => Some(()),
_ => None,
}
}
}

View file

@ -1,5 +1,7 @@
use dioxus_core::ElementId;
use crate::{
real_dom::{NodeData, NodeType, OwnedAttributeView},
node::{NodeData, NodeType, OwnedAttributeView},
state::union_ordered_iter,
RealNodeId,
};
@ -21,8 +23,13 @@ impl<'a> NodeView<'a> {
}
/// Get the id of the node
pub fn id(&self) -> RealNodeId {
self.inner.id.unwrap()
pub fn id(&self) -> Option<ElementId> {
self.inner.element_id
}
/// Get the node id of the node
pub fn node_id(&self) -> RealNodeId {
self.inner.node_id
}
/// Get the tag of the node if the tag is enabled in the mask
@ -41,7 +48,7 @@ impl<'a> NodeView<'a> {
self.mask
.namespace
.then_some(match &self.inner.node_type {
NodeType::Element { namespace, .. } => *namespace,
NodeType::Element { namespace, .. } => namespace.as_deref(),
_ => None,
})
.flatten()
@ -92,7 +99,7 @@ impl<'a> NodeView<'a> {
pub enum AttributeMask {
All,
/// A list of attribute names that are visible, this list must be sorted
Dynamic(Vec<&'static str>),
Dynamic(Vec<String>),
/// A list of attribute names that are visible, this list must be sorted
Static(&'static [&'static str]),
}
@ -104,14 +111,14 @@ impl AttributeMask {
fn contains_attribute(&self, attr: &str) -> bool {
match self {
AttributeMask::All => true,
AttributeMask::Dynamic(l) => l.binary_search(&attr).is_ok(),
AttributeMask::Dynamic(l) => l.binary_search_by_key(&attr, |s| s.as_str()).is_ok(),
AttributeMask::Static(l) => l.binary_search(&attr).is_ok(),
}
}
/// Create a new dynamic attribute mask with a single attribute
pub fn single(new: &'static str) -> Self {
Self::Dynamic(vec![new])
pub fn single(new: &str) -> Self {
Self::Dynamic(vec![new.to_string()])
}
/// Ensure the attribute list is sorted.
@ -132,15 +139,27 @@ impl AttributeMask {
/// Combine two attribute masks
pub fn union(&self, other: &Self) -> Self {
let new = match (self, other) {
(AttributeMask::Dynamic(s), AttributeMask::Dynamic(o)) => AttributeMask::Dynamic(
union_ordered_iter(s.iter().copied(), o.iter().copied(), s.len() + o.len()),
),
(AttributeMask::Static(s), AttributeMask::Dynamic(o)) => AttributeMask::Dynamic(
union_ordered_iter(s.iter().copied(), o.iter().copied(), s.len() + o.len()),
),
(AttributeMask::Dynamic(s), AttributeMask::Static(o)) => AttributeMask::Dynamic(
union_ordered_iter(s.iter().copied(), o.iter().copied(), s.len() + o.len()),
),
(AttributeMask::Dynamic(s), AttributeMask::Dynamic(o)) => {
AttributeMask::Dynamic(union_ordered_iter(
s.iter().map(|s| s.as_str()),
o.iter().map(|s| s.as_str()),
s.len() + o.len(),
))
}
(AttributeMask::Static(s), AttributeMask::Dynamic(o)) => {
AttributeMask::Dynamic(union_ordered_iter(
s.iter().copied(),
o.iter().map(|s| s.as_str()),
s.len() + o.len(),
))
}
(AttributeMask::Dynamic(s), AttributeMask::Static(o)) => {
AttributeMask::Dynamic(union_ordered_iter(
s.iter().map(|s| s.as_str()),
o.iter().copied(),
s.len() + o.len(),
))
}
(AttributeMask::Static(s), AttributeMask::Static(o)) => AttributeMask::Dynamic(
union_ordered_iter(s.iter().copied(), o.iter().copied(), s.len() + o.len()),
),
@ -152,9 +171,9 @@ impl AttributeMask {
/// Check if two attribute masks overlap
fn overlaps(&self, other: &Self) -> bool {
fn overlaps_iter(
self_iter: impl Iterator<Item = &'static str>,
mut other_iter: impl Iterator<Item = &'static str>,
fn overlaps_iter<'a>(
self_iter: impl Iterator<Item = &'a str>,
mut other_iter: impl Iterator<Item = &'a str>,
) -> bool {
if let Some(mut other_attr) = other_iter.next() {
for self_attr in self_iter {
@ -179,13 +198,13 @@ impl AttributeMask {
(AttributeMask::Dynamic(v), AttributeMask::All) => !v.is_empty(),
(AttributeMask::Static(s), AttributeMask::All) => !s.is_empty(),
(AttributeMask::Dynamic(v1), AttributeMask::Dynamic(v2)) => {
overlaps_iter(v1.iter().copied(), v2.iter().copied())
overlaps_iter(v1.iter().map(|s| s.as_str()), v2.iter().map(|s| s.as_str()))
}
(AttributeMask::Dynamic(v), AttributeMask::Static(s)) => {
overlaps_iter(v.iter().copied(), s.iter().copied())
overlaps_iter(v.iter().map(|s| s.as_str()), s.iter().copied())
}
(AttributeMask::Static(s), AttributeMask::Dynamic(v)) => {
overlaps_iter(v.iter().copied(), s.iter().copied())
overlaps_iter(v.iter().map(|s| s.as_str()), s.iter().copied())
}
(AttributeMask::Static(s1), AttributeMask::Static(s2)) => {
overlaps_iter(s1.iter().copied(), s2.iter().copied())

View file

@ -0,0 +1,906 @@
use crate::tree::{NodeId, TreeView};
use crate::{FxDashMap, FxDashSet, SendAnyMap};
use rustc_hash::{FxHashMap, FxHashSet};
use std::collections::BTreeMap;
use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct DirtyNodes {
map: BTreeMap<u16, FxHashSet<NodeId>>,
}
impl DirtyNodes {
pub fn insert(&mut self, depth: u16, node_id: NodeId) {
self.map
.entry(depth)
.or_insert_with(FxHashSet::default)
.insert(node_id);
}
fn pop_front(&mut self) -> Option<NodeId> {
let (&depth, values) = self.map.iter_mut().next()?;
let key = *values.iter().next()?;
let node_id = values.take(&key)?;
if values.is_empty() {
self.map.remove(&depth);
}
Some(node_id)
}
fn pop_back(&mut self) -> Option<NodeId> {
let (&depth, values) = self.map.iter_mut().rev().next()?;
let key = *values.iter().next()?;
let node_id = values.take(&key)?;
if values.is_empty() {
self.map.remove(&depth);
}
Some(node_id)
}
}
#[test]
fn dirty_nodes() {
let mut dirty_nodes = DirtyNodes::default();
dirty_nodes.insert(1, NodeId(1));
dirty_nodes.insert(0, NodeId(0));
dirty_nodes.insert(2, NodeId(3));
dirty_nodes.insert(1, NodeId(2));
assert_eq!(dirty_nodes.pop_front(), Some(NodeId(0)));
assert!(matches!(dirty_nodes.pop_front(), Some(NodeId(1 | 2))));
assert!(matches!(dirty_nodes.pop_front(), Some(NodeId(1 | 2))));
assert_eq!(dirty_nodes.pop_front(), Some(NodeId(3)));
}
#[derive(Default)]
pub struct DirtyNodeStates {
dirty: FxDashMap<NodeId, Vec<AtomicU64>>,
}
impl DirtyNodeStates {
pub fn new(starting_nodes: FxHashMap<NodeId, FxHashSet<PassId>>) -> Self {
let this = Self::default();
for (node, nodes) in starting_nodes {
for pass_id in nodes {
this.insert(pass_id, node);
}
}
this
}
pub fn insert(&self, pass_id: PassId, node_id: NodeId) {
let pass_id = pass_id.0;
let index = pass_id / 64;
let bit = pass_id % 64;
let encoded = 1 << bit;
if let Some(dirty) = self.dirty.get(&node_id) {
if let Some(atomic) = dirty.get(index as usize) {
atomic.fetch_or(encoded, Ordering::Relaxed);
} else {
drop(dirty);
let mut write = self.dirty.get_mut(&node_id).unwrap();
write.resize_with(index as usize + 1, || AtomicU64::new(0));
write[index as usize].fetch_or(encoded, Ordering::Relaxed);
}
} else {
let mut v = Vec::with_capacity(index as usize + 1);
v.resize_with(index as usize + 1, || AtomicU64::new(0));
v[index as usize].fetch_or(encoded, Ordering::Relaxed);
self.dirty.insert(node_id, v);
}
}
fn all_dirty<T>(&self, pass_id: PassId, dirty_nodes: &mut DirtyNodes, tree: &impl TreeView<T>) {
let pass_id = pass_id.0;
let index = pass_id / 64;
let bit = pass_id % 64;
let encoded = 1 << bit;
for entry in self.dirty.iter() {
let node_id = entry.key();
let dirty = entry.value();
if let Some(atomic) = dirty.get(index as usize) {
if atomic.load(Ordering::Relaxed) & encoded != 0 {
dirty_nodes.insert(tree.height(*node_id).unwrap(), *node_id);
}
}
}
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, PartialOrd, Ord)]
pub struct PassId(pub u64);
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Default)]
pub struct MemberMask(pub u64);
impl MemberMask {
pub fn overlaps(&self, other: Self) -> bool {
(*self & other).0 != 0
}
}
impl BitAndAssign for MemberMask {
fn bitand_assign(&mut self, rhs: Self) {
self.0 &= rhs.0;
}
}
impl BitAnd for MemberMask {
type Output = Self;
fn bitand(self, rhs: Self) -> Self::Output {
MemberMask(self.0 & rhs.0)
}
}
impl BitOrAssign for MemberMask {
fn bitor_assign(&mut self, rhs: Self) {
self.0 |= rhs.0;
}
}
impl BitOr for MemberMask {
type Output = Self;
fn bitor(self, rhs: Self) -> Self::Output {
Self(self.0 | rhs.0)
}
}
pub struct PassReturn {
pub progress: bool,
pub mark_dirty: bool,
}
pub trait Pass {
fn pass_id(&self) -> PassId;
fn dependancies(&self) -> &'static [PassId];
fn dependants(&self) -> &'static [PassId];
fn mask(&self) -> MemberMask;
}
pub trait UpwardPass<T>: Pass {
fn pass<'a>(
&self,
node: &mut T,
children: &mut dyn Iterator<Item = &'a mut T>,
ctx: &SendAnyMap,
) -> PassReturn;
}
fn resolve_upward_pass<T, P: UpwardPass<T> + ?Sized>(
tree: &mut impl TreeView<T>,
pass: &P,
mut dirty: DirtyNodes,
dirty_states: &DirtyNodeStates,
nodes_updated: &FxDashSet<NodeId>,
ctx: &SendAnyMap,
) {
while let Some(id) = dirty.pop_back() {
let (node, mut children) = tree.parent_child_mut(id).unwrap();
let result = pass.pass(node, &mut children, ctx);
drop(children);
if result.progress || result.mark_dirty {
nodes_updated.insert(id);
if let Some(id) = tree.parent_id(id) {
if result.mark_dirty {
for dependant in pass.dependants() {
dirty_states.insert(*dependant, id);
}
}
if result.progress {
let height = tree.height(id).unwrap();
dirty.insert(height, id);
}
}
}
}
}
pub trait DownwardPass<T>: Pass {
fn pass(&self, node: &mut T, parent: Option<&mut T>, ctx: &SendAnyMap) -> PassReturn;
}
fn resolve_downward_pass<T, P: DownwardPass<T> + ?Sized>(
tree: &mut impl TreeView<T>,
pass: &P,
mut dirty: DirtyNodes,
dirty_states: &DirtyNodeStates,
nodes_updated: &FxDashSet<NodeId>,
ctx: &SendAnyMap,
) {
while let Some(id) = dirty.pop_front() {
let (node, parent) = tree.node_parent_mut(id).unwrap();
let result = pass.pass(node, parent, ctx);
if result.mark_dirty {
nodes_updated.insert(id);
}
if result.mark_dirty || result.progress {
for id in tree.children_ids(id).unwrap() {
if result.mark_dirty {
for dependant in pass.dependants() {
dirty_states.insert(*dependant, *id);
}
}
if result.progress {
let height = tree.height(*id).unwrap();
dirty.insert(height, *id);
}
}
}
}
}
pub trait NodePass<T>: Pass {
fn pass(&self, node: &mut T, ctx: &SendAnyMap) -> bool;
}
fn resolve_node_pass<T, P: NodePass<T> + ?Sized>(
tree: &mut impl TreeView<T>,
pass: &P,
mut dirty: DirtyNodes,
dirty_states: &DirtyNodeStates,
nodes_updated: &FxDashSet<NodeId>,
ctx: &SendAnyMap,
) {
while let Some(id) = dirty.pop_back() {
let node = tree.get_mut(id).unwrap();
if pass.pass(node, ctx) {
nodes_updated.insert(id);
for dependant in pass.dependants() {
dirty_states.insert(*dependant, id);
}
}
}
}
pub enum AnyPass<T: 'static> {
Upward(&'static (dyn UpwardPass<T> + Send + Sync + 'static)),
Downward(&'static (dyn DownwardPass<T> + Send + Sync + 'static)),
Node(&'static (dyn NodePass<T> + Send + Sync + 'static)),
}
impl<T> AnyPass<T> {
pub fn pass_id(&self) -> PassId {
match self {
Self::Upward(pass) => pass.pass_id(),
Self::Downward(pass) => pass.pass_id(),
Self::Node(pass) => pass.pass_id(),
}
}
pub fn dependancies(&self) -> &'static [PassId] {
match self {
Self::Upward(pass) => pass.dependancies(),
Self::Downward(pass) => pass.dependancies(),
Self::Node(pass) => pass.dependancies(),
}
}
fn mask(&self) -> MemberMask {
match self {
Self::Upward(pass) => pass.mask(),
Self::Downward(pass) => pass.mask(),
Self::Node(pass) => pass.mask(),
}
}
fn resolve(
&self,
tree: &mut impl TreeView<T>,
dirty: DirtyNodes,
dirty_states: &DirtyNodeStates,
nodes_updated: &FxDashSet<NodeId>,
ctx: &SendAnyMap,
) {
match self {
Self::Downward(pass) => {
resolve_downward_pass(tree, *pass, dirty, dirty_states, nodes_updated, ctx)
}
Self::Upward(pass) => {
resolve_upward_pass(tree, *pass, dirty, dirty_states, nodes_updated, ctx)
}
Self::Node(pass) => {
resolve_node_pass(tree, *pass, dirty, dirty_states, nodes_updated, ctx)
}
}
}
}
struct RawPointer<T>(*mut T);
unsafe impl<T> Send for RawPointer<T> {}
unsafe impl<T> Sync for RawPointer<T> {}
pub fn resolve_passes<T, Tr: TreeView<T>>(
tree: &mut Tr,
dirty_nodes: DirtyNodeStates,
mut passes: Vec<&AnyPass<T>>,
ctx: SendAnyMap,
) -> FxDashSet<NodeId> {
let dirty_states = Arc::new(dirty_nodes);
let mut resolved_passes: FxHashSet<PassId> = FxHashSet::default();
let mut resolving = Vec::new();
let nodes_updated = Arc::new(FxDashSet::default());
let ctx = Arc::new(ctx);
while !passes.is_empty() {
let mut currently_borrowed = MemberMask::default();
std::thread::scope(|s| {
let mut i = 0;
while i < passes.len() {
let pass = &passes[i];
let pass_id = pass.pass_id();
let pass_mask = pass.mask();
if pass
.dependancies()
.iter()
.all(|d| resolved_passes.contains(d) || *d == pass_id)
&& !pass_mask.overlaps(currently_borrowed)
{
let pass = passes.remove(i);
resolving.push(pass_id);
currently_borrowed |= pass_mask;
let tree_mut = tree as *mut _;
let raw_ptr = RawPointer(tree_mut);
let dirty_states = dirty_states.clone();
let nodes_updated = nodes_updated.clone();
let ctx = ctx.clone();
s.spawn(move || unsafe {
// let tree_mut: &mut Tr = &mut *raw_ptr.0;
let raw = raw_ptr;
// this is safe because the member_mask acts as a per-member mutex and we have verified that the pass does not overlap with any other pass
let tree_mut: &mut Tr = &mut *raw.0;
let mut dirty = DirtyNodes::default();
dirty_states.all_dirty(pass_id, &mut dirty, tree_mut);
pass.resolve(tree_mut, dirty, &dirty_states, &nodes_updated, &ctx);
});
} else {
i += 1;
}
}
// all passes are resolved at the end of the scope
});
resolved_passes.extend(resolving.iter().copied());
resolving.clear()
}
std::sync::Arc::try_unwrap(nodes_updated).unwrap()
}
#[test]
fn node_pass() {
use crate::tree::{Tree, TreeLike};
let mut tree = Tree::new(0);
struct AddPass;
impl Pass for AddPass {
fn pass_id(&self) -> PassId {
PassId(0)
}
fn dependancies(&self) -> &'static [PassId] {
&[]
}
fn dependants(&self) -> &'static [PassId] {
&[]
}
fn mask(&self) -> MemberMask {
MemberMask(0)
}
}
impl NodePass<i32> for AddPass {
fn pass(&self, node: &mut i32, _: &SendAnyMap) -> bool {
*node += 1;
true
}
}
let add_pass = AnyPass::Node(&AddPass);
let passes = vec![&add_pass];
let dirty_nodes: DirtyNodeStates = DirtyNodeStates::default();
dirty_nodes.insert(PassId(0), tree.root());
resolve_passes(&mut tree, dirty_nodes, passes, SendAnyMap::new());
assert_eq!(tree.get(tree.root()).unwrap(), &1);
}
#[test]
fn dependant_node_pass() {
use crate::tree::{Tree, TreeLike};
let mut tree = Tree::new(0);
struct AddPass;
impl Pass for AddPass {
fn pass_id(&self) -> PassId {
PassId(0)
}
fn dependancies(&self) -> &'static [PassId] {
&[PassId(1)]
}
fn dependants(&self) -> &'static [PassId] {
&[]
}
fn mask(&self) -> MemberMask {
MemberMask(0)
}
}
impl NodePass<i32> for AddPass {
fn pass(&self, node: &mut i32, _: &SendAnyMap) -> bool {
*node += 1;
true
}
}
struct SubtractPass;
impl Pass for SubtractPass {
fn pass_id(&self) -> PassId {
PassId(1)
}
fn dependancies(&self) -> &'static [PassId] {
&[]
}
fn dependants(&self) -> &'static [PassId] {
&[PassId(0)]
}
fn mask(&self) -> MemberMask {
MemberMask(0)
}
}
impl NodePass<i32> for SubtractPass {
fn pass(&self, node: &mut i32, _: &SendAnyMap) -> bool {
*node -= 1;
true
}
}
let add_pass = AnyPass::Node(&AddPass);
let subtract_pass = AnyPass::Node(&SubtractPass);
let passes = vec![&add_pass, &subtract_pass];
let dirty_nodes: DirtyNodeStates = DirtyNodeStates::default();
dirty_nodes.insert(PassId(1), tree.root());
resolve_passes(&mut tree, dirty_nodes, passes, SendAnyMap::new());
assert_eq!(*tree.get(tree.root()).unwrap(), 0);
}
#[test]
fn independant_node_pass() {
use crate::tree::{Tree, TreeLike};
let mut tree = Tree::new((0, 0));
struct AddPass1;
impl Pass for AddPass1 {
fn pass_id(&self) -> PassId {
PassId(0)
}
fn dependancies(&self) -> &'static [PassId] {
&[]
}
fn dependants(&self) -> &'static [PassId] {
&[]
}
fn mask(&self) -> MemberMask {
MemberMask(0)
}
}
impl NodePass<(i32, i32)> for AddPass1 {
fn pass(&self, node: &mut (i32, i32), _: &SendAnyMap) -> bool {
node.0 += 1;
true
}
}
struct AddPass2;
impl Pass for AddPass2 {
fn pass_id(&self) -> PassId {
PassId(1)
}
fn dependancies(&self) -> &'static [PassId] {
&[]
}
fn dependants(&self) -> &'static [PassId] {
&[]
}
fn mask(&self) -> MemberMask {
MemberMask(1)
}
}
impl NodePass<(i32, i32)> for AddPass2 {
fn pass(&self, node: &mut (i32, i32), _: &SendAnyMap) -> bool {
node.1 += 1;
true
}
}
let add_pass1 = AnyPass::Node(&AddPass1);
let add_pass2 = AnyPass::Node(&AddPass2);
let passes = vec![&add_pass1, &add_pass2];
let dirty_nodes: DirtyNodeStates = DirtyNodeStates::default();
dirty_nodes.insert(PassId(0), tree.root());
dirty_nodes.insert(PassId(1), tree.root());
resolve_passes(&mut tree, dirty_nodes, passes, SendAnyMap::new());
assert_eq!(tree.get(tree.root()).unwrap(), &(1, 1));
}
#[test]
fn down_pass() {
use crate::tree::{Tree, TreeLike};
let mut tree = Tree::new(1);
let parent = tree.root();
let child1 = tree.create_node(1);
tree.add_child(parent, child1);
let grandchild1 = tree.create_node(1);
tree.add_child(child1, grandchild1);
let child2 = tree.create_node(1);
tree.add_child(parent, child2);
let grandchild2 = tree.create_node(1);
tree.add_child(child2, grandchild2);
struct AddPass;
impl Pass for AddPass {
fn pass_id(&self) -> PassId {
PassId(0)
}
fn dependancies(&self) -> &'static [PassId] {
&[]
}
fn dependants(&self) -> &'static [PassId] {
&[]
}
fn mask(&self) -> MemberMask {
MemberMask(0)
}
}
impl DownwardPass<i32> for AddPass {
fn pass(&self, node: &mut i32, parent: Option<&mut i32>, _: &SendAnyMap) -> PassReturn {
if let Some(parent) = parent {
*node += *parent;
}
PassReturn {
progress: true,
mark_dirty: true,
}
}
}
let add_pass = AnyPass::Downward(&AddPass);
let passes = vec![&add_pass];
let dirty_nodes: DirtyNodeStates = DirtyNodeStates::default();
dirty_nodes.insert(PassId(0), tree.root());
resolve_passes(&mut tree, dirty_nodes, passes, SendAnyMap::new());
assert_eq!(tree.get(tree.root()).unwrap(), &1);
assert_eq!(tree.get(child1).unwrap(), &2);
assert_eq!(tree.get(grandchild1).unwrap(), &3);
assert_eq!(tree.get(child2).unwrap(), &2);
assert_eq!(tree.get(grandchild2).unwrap(), &3);
}
#[test]
fn dependant_down_pass() {
use crate::tree::{Tree, TreeLike};
// 0
let mut tree = Tree::new(1);
let parent = tree.root();
// 1
let child1 = tree.create_node(1);
tree.add_child(parent, child1);
// 2
let grandchild1 = tree.create_node(1);
tree.add_child(child1, grandchild1);
// 3
let child2 = tree.create_node(1);
tree.add_child(parent, child2);
// 4
let grandchild2 = tree.create_node(1);
tree.add_child(child2, grandchild2);
struct AddPass;
impl Pass for AddPass {
fn pass_id(&self) -> PassId {
PassId(0)
}
fn dependancies(&self) -> &'static [PassId] {
&[PassId(1)]
}
fn dependants(&self) -> &'static [PassId] {
&[]
}
fn mask(&self) -> MemberMask {
MemberMask(0)
}
}
impl DownwardPass<i32> for AddPass {
fn pass(&self, node: &mut i32, parent: Option<&mut i32>, _: &SendAnyMap) -> PassReturn {
if let Some(parent) = parent {
*node += *parent;
} else {
}
PassReturn {
progress: true,
mark_dirty: true,
}
}
}
struct SubtractPass;
impl Pass for SubtractPass {
fn pass_id(&self) -> PassId {
PassId(1)
}
fn dependancies(&self) -> &'static [PassId] {
&[]
}
fn dependants(&self) -> &'static [PassId] {
&[PassId(0)]
}
fn mask(&self) -> MemberMask {
MemberMask(0)
}
}
impl DownwardPass<i32> for SubtractPass {
fn pass(&self, node: &mut i32, parent: Option<&mut i32>, _: &SendAnyMap) -> PassReturn {
if let Some(parent) = parent {
*node -= *parent;
} else {
}
PassReturn {
progress: true,
mark_dirty: true,
}
}
}
let add_pass = AnyPass::Downward(&AddPass);
let subtract_pass = AnyPass::Downward(&SubtractPass);
let passes = vec![&add_pass, &subtract_pass];
let dirty_nodes: DirtyNodeStates = DirtyNodeStates::default();
dirty_nodes.insert(PassId(1), tree.root());
resolve_passes(&mut tree, dirty_nodes, passes, SendAnyMap::new());
// Tree before:
// 1=\
// 1=\
// 1
// 1=\
// 1
// Tree after subtract:
// 1=\
// 0=\
// 1
// 0=\
// 1
// Tree after add:
// 1=\
// 1=\
// 2
// 1=\
// 2
assert_eq!(tree.get(tree.root()).unwrap(), &1);
assert_eq!(tree.get(child1).unwrap(), &1);
assert_eq!(tree.get(grandchild1).unwrap(), &2);
assert_eq!(tree.get(child2).unwrap(), &1);
assert_eq!(tree.get(grandchild2).unwrap(), &2);
}
#[test]
fn up_pass() {
use crate::tree::{Tree, TreeLike};
// Tree before:
// 0=\
// 0=\
// 1
// 0=\
// 1
// Tree after:
// 2=\
// 1=\
// 1
// 1=\
// 1
let mut tree = Tree::new(0);
let parent = tree.root();
let child1 = tree.create_node(0);
tree.add_child(parent, child1);
let grandchild1 = tree.create_node(1);
tree.add_child(child1, grandchild1);
let child2 = tree.create_node(0);
tree.add_child(parent, child2);
let grandchild2 = tree.create_node(1);
tree.add_child(child2, grandchild2);
struct AddPass;
impl Pass for AddPass {
fn pass_id(&self) -> PassId {
PassId(0)
}
fn dependancies(&self) -> &'static [PassId] {
&[]
}
fn dependants(&self) -> &'static [PassId] {
&[]
}
fn mask(&self) -> MemberMask {
MemberMask(0)
}
}
impl UpwardPass<i32> for AddPass {
fn pass<'a>(
&self,
node: &mut i32,
children: &mut dyn Iterator<Item = &'a mut i32>,
_: &SendAnyMap,
) -> PassReturn {
*node += children.map(|i| *i).sum::<i32>();
PassReturn {
progress: true,
mark_dirty: true,
}
}
}
let add_pass = AnyPass::Upward(&AddPass);
let passes = vec![&add_pass];
let dirty_nodes: DirtyNodeStates = DirtyNodeStates::default();
dirty_nodes.insert(PassId(0), grandchild1);
dirty_nodes.insert(PassId(0), grandchild2);
resolve_passes(&mut tree, dirty_nodes, passes, SendAnyMap::new());
assert_eq!(tree.get(tree.root()).unwrap(), &2);
assert_eq!(tree.get(child1).unwrap(), &1);
assert_eq!(tree.get(grandchild1).unwrap(), &1);
assert_eq!(tree.get(child2).unwrap(), &1);
assert_eq!(tree.get(grandchild2).unwrap(), &1);
}
#[test]
fn dependant_up_pass() {
use crate::tree::{Tree, TreeLike};
// 0
let mut tree = Tree::new(0);
let parent = tree.root();
// 1
let child1 = tree.create_node(0);
tree.add_child(parent, child1);
// 2
let grandchild1 = tree.create_node(1);
tree.add_child(child1, grandchild1);
// 3
let child2 = tree.create_node(0);
tree.add_child(parent, child2);
// 4
let grandchild2 = tree.create_node(1);
tree.add_child(child2, grandchild2);
struct AddPass;
impl Pass for AddPass {
fn pass_id(&self) -> PassId {
PassId(0)
}
fn dependancies(&self) -> &'static [PassId] {
&[PassId(1)]
}
fn dependants(&self) -> &'static [PassId] {
&[]
}
fn mask(&self) -> MemberMask {
MemberMask(0)
}
}
impl UpwardPass<i32> for AddPass {
fn pass<'a>(
&self,
node: &mut i32,
children: &mut dyn Iterator<Item = &'a mut i32>,
_: &SendAnyMap,
) -> PassReturn {
*node += children.map(|i| *i).sum::<i32>();
PassReturn {
progress: true,
mark_dirty: true,
}
}
}
struct SubtractPass;
impl Pass for SubtractPass {
fn pass_id(&self) -> PassId {
PassId(1)
}
fn dependancies(&self) -> &'static [PassId] {
&[]
}
fn dependants(&self) -> &'static [PassId] {
&[PassId(0)]
}
fn mask(&self) -> MemberMask {
MemberMask(0)
}
}
impl UpwardPass<i32> for SubtractPass {
fn pass<'a>(
&self,
node: &mut i32,
children: &mut dyn Iterator<Item = &'a mut i32>,
_: &SendAnyMap,
) -> PassReturn {
*node -= children.map(|i| *i).sum::<i32>();
PassReturn {
progress: true,
mark_dirty: true,
}
}
}
let add_pass = AnyPass::Upward(&AddPass);
let subtract_pass = AnyPass::Upward(&SubtractPass);
let passes = vec![&add_pass, &subtract_pass];
let dirty_nodes: DirtyNodeStates = DirtyNodeStates::default();
dirty_nodes.insert(PassId(1), grandchild1);
dirty_nodes.insert(PassId(1), grandchild2);
resolve_passes(&mut tree, dirty_nodes, passes, SendAnyMap::new());
// Tree before:
// 0=\
// 0=\
// 1
// 0=\
// 1
// Tree after subtract:
// 2=\
// -1=\
// 1
// -1=\
// 1
// Tree after add:
// 2=\
// 0=\
// 1
// 0=\
// 1
assert_eq!(tree.get(tree.root()).unwrap(), &2);
assert_eq!(tree.get(child1).unwrap(), &0);
assert_eq!(tree.get(grandchild1).unwrap(), &1);
assert_eq!(tree.get(child2).unwrap(), &0);
assert_eq!(tree.get(grandchild2).unwrap(), &1);
}

File diff suppressed because it is too large Load diff

View file

@ -1,18 +1,17 @@
use std::{cmp::Ordering, fmt::Debug};
use std::cmp::Ordering;
use crate::node::Node;
use crate::node_ref::{NodeMask, NodeView};
use crate::real_dom::NodeData;
use crate::traversable::Traversable;
use crate::RealNodeId;
use anymap::AnyMap;
use rustc_hash::FxHashSet;
use crate::passes::{resolve_passes, AnyPass, DirtyNodeStates};
use crate::tree::TreeView;
use crate::{FxDashSet, RealNodeId, SendAnyMap};
/// Join two sorted iterators
pub(crate) fn union_ordered_iter<T: Ord + Debug>(
s_iter: impl Iterator<Item = T>,
o_iter: impl Iterator<Item = T>,
pub(crate) fn union_ordered_iter<'a>(
s_iter: impl Iterator<Item = &'a str>,
o_iter: impl Iterator<Item = &'a str>,
new_len_guess: usize,
) -> Vec<T> {
) -> Vec<String> {
let mut s_peekable = s_iter.peekable();
let mut o_peekable = o_iter.peekable();
let mut v = Vec::with_capacity(new_len_guess);
@ -23,7 +22,7 @@ pub(crate) fn union_ordered_iter<T: Ord + Debug>(
break;
}
Ordering::Less => {
v.push(o_peekable.next().unwrap());
v.push(o_peekable.next().unwrap().to_string());
}
Ordering::Equal => {
o_peekable.next();
@ -31,10 +30,10 @@ pub(crate) fn union_ordered_iter<T: Ord + Debug>(
}
}
}
v.push(s_peekable.next().unwrap());
v.push(s_peekable.next().unwrap().to_string());
}
for o_i in o_peekable {
v.push(o_i);
v.push(o_i.to_string());
}
for w in v.windows(2) {
debug_assert!(w[1] > w[0]);
@ -57,17 +56,17 @@ pub(crate) fn union_ordered_iter<T: Ord + Debug>(
/// impl ChildDepState for Layout {
/// type Ctx = ();
/// // The layout depends on the layout of the children.
/// type DepState = Layout;
/// type DepState = (Layout,);
/// fn reduce<'a>(
/// &mut self,
/// _node: NodeView,
/// children: impl Iterator<Item = &'a Self::DepState>,
/// children: impl Iterator<Item = (&'a Self,)>,
/// _ctx: &Self::Ctx,
/// ) -> bool
/// where
/// Self::DepState: 'a{
/// /// Children are layed out form left to right. The width of the parent is the sum of the widths and the max of the heights.
/// let new = children.copied().reduce(|c1, c2| Layout{
/// let new = children.map(|(&c,)|c).reduce(|c1, c2| Layout{
/// width: c1.width + c2.width,
/// height: c1.height.max(c2.height)
/// }).unwrap_or_default();
@ -81,15 +80,15 @@ pub trait ChildDepState {
/// The context is passed to the [ChildDepState::reduce] when it is resolved.
type Ctx;
/// A state from each child node that this node depends on. Typically this is Self, but it could be any state that is within the state tree.
/// This must be either a [ChildDepState], or [NodeDepState].
type DepState;
/// Depstate must be a tuple containing any number of borrowed elements that are either [ChildDepState] or [NodeDepState].
type DepState: ElementBorrowable;
/// The part of a node that this state cares about. This is used to determine if the state should be updated when a node is updated.
const NODE_MASK: NodeMask = NodeMask::NONE;
/// Resolve the state current node's state from the state of the children, the state of the node, and some external context.
fn reduce<'a>(
&mut self,
node: NodeView,
children: impl Iterator<Item = &'a Self::DepState>,
children: impl Iterator<Item = <Self::DepState as ElementBorrowable>::ElementBorrowed<'a>>,
ctx: &Self::Ctx,
) -> bool
where
@ -109,7 +108,7 @@ pub trait ChildDepState {
/// impl ParentDepState for FontSize {
/// type Ctx = ();
/// // The font size depends on the font size of the parent element.
/// type DepState = Self;
/// type DepState = (Self,);
/// const NODE_MASK: NodeMask =
/// NodeMask::new_with_attrs(AttributeMask::Static(&[
/// "font-size"
@ -117,12 +116,12 @@ pub trait ChildDepState {
/// fn reduce<'a>(
/// &mut self,
/// node: NodeView,
/// parent: Option<&'a Self::DepState>,
/// parent: Option<(&'a Self,)>,
/// ctx: &Self::Ctx,
/// ) -> bool{
/// let old = *self;
/// // If the font size was set on the parent, it is passed down to the current element
/// if let Some(parent) = parent {
/// if let Some((parent,)) = parent {
/// *self = *parent;
/// }
/// // If the current node overrides the font size, use that size insead.
@ -143,15 +142,15 @@ pub trait ParentDepState {
/// The context is passed to the [ParentDepState::reduce] when it is resolved.
type Ctx;
/// A state from from the parent node that this node depends on. Typically this is Self, but it could be any state that is within the state tree.
/// This must be either a [ParentDepState] or [NodeDepState]
type DepState;
/// Depstate must be a tuple containing any number of borrowed elements that are either [ParentDepState] or [NodeDepState].
type DepState: ElementBorrowable;
/// The part of a node that this state cares about. This is used to determine if the state should be updated when a node is updated.
const NODE_MASK: NodeMask = NodeMask::NONE;
/// Resolve the state current node's state from the state of the parent node, the state of the node, and some external context.
fn reduce<'a>(
&mut self,
node: NodeView,
parent: Option<&'a Self::DepState>,
parent: Option<<Self::DepState as ElementBorrowable>::ElementBorrowed<'a>>,
ctx: &Self::Ctx,
) -> bool;
}
@ -169,6 +168,7 @@ pub trait ParentDepState {
///
/// impl NodeDepState for TabIndex {
/// type Ctx = ();
/// type DepState = ();
/// const NODE_MASK: NodeMask =
/// NodeMask::new_with_attrs(AttributeMask::Static(&[
/// "tabindex"
@ -193,42 +193,44 @@ pub trait ParentDepState {
/// }
/// }
/// ```
/// The generic argument (Depstate) must be a tuple containing any number of borrowed elements that are either a [ChildDepState], [ParentDepState] or [NodeDepState].
// Todo: once GATs land we can model multable dependencies better
pub trait NodeDepState<DepState = ()> {
pub trait NodeDepState {
/// Depstate must be a tuple containing any number of borrowed elements that are either [ChildDepState], [ParentDepState] or [NodeDepState].
type DepState: ElementBorrowable;
/// The state passed to [NodeDepState::reduce] when it is resolved.
type Ctx;
/// The part of a node that this state cares about. This is used to determine if the state should be updated when a node is updated.
const NODE_MASK: NodeMask = NodeMask::NONE;
/// Resolve the state current node's state from the state of the sibling states, the state of the node, and some external context.
fn reduce(&mut self, node: NodeView, siblings: DepState, ctx: &Self::Ctx) -> bool;
fn reduce<'a>(
&mut self,
node: NodeView,
node_state: <Self::DepState as ElementBorrowable>::ElementBorrowed<'a>,
ctx: &Self::Ctx,
) -> bool;
}
/// Do not implement this trait. It is only meant to be derived and used through [crate::real_dom::RealDom].
pub trait State: Default + Clone {
pub trait State: Default + Clone + 'static {
#[doc(hidden)]
fn update<
'a,
T: Traversable<Node = Self, Id = RealNodeId>,
T2: Traversable<Node = NodeData, Id = RealNodeId>,
>(
dirty: &[(RealNodeId, NodeMask)],
state_tree: &'a mut T,
rdom: &'a T2,
ctx: &AnyMap,
) -> FxHashSet<RealNodeId>;
const PASSES: &'static [AnyPass<Node<Self>>];
#[doc(hidden)]
const MASKS: &'static [NodeMask];
#[doc(hidden)]
fn update<T: TreeView<Node<Self>>>(
dirty: DirtyNodeStates,
tree: &mut T,
ctx: SendAnyMap,
) -> FxDashSet<RealNodeId> {
let passes = Self::PASSES.iter().collect();
resolve_passes(tree, dirty, passes, ctx)
}
}
impl ChildDepState for () {
type Ctx = ();
type DepState = ();
fn reduce<'a>(
&mut self,
_: NodeView,
_: impl Iterator<Item = &'a Self::DepState>,
_: &Self::Ctx,
) -> bool
fn reduce<'a>(&mut self, _: NodeView, _: impl Iterator<Item = ()>, _: &Self::Ctx) -> bool
where
Self::DepState: 'a,
{
@ -239,14 +241,55 @@ impl ChildDepState for () {
impl ParentDepState for () {
type Ctx = ();
type DepState = ();
fn reduce<'a>(&mut self, _: NodeView, _: Option<&'a Self::DepState>, _: &Self::Ctx) -> bool {
fn reduce<'a>(&mut self, _: NodeView, _: Option<()>, _: &Self::Ctx) -> bool {
false
}
}
impl NodeDepState<()> for () {
impl NodeDepState for () {
type DepState = ();
type Ctx = ();
fn reduce(&mut self, _: NodeView, _sibling: (), _: &Self::Ctx) -> bool {
false
}
}
pub trait ElementBorrowable {
type ElementBorrowed<'a>
where
Self: 'a;
fn borrow_elements(&self) -> Self::ElementBorrowed<'_>;
}
macro_rules! impl_element_borrowable {
($($t:ident),*) => {
impl< $($t),* > ElementBorrowable for ($($t,)*) {
type ElementBorrowed<'a> = ($(&'a $t,)*) where Self: 'a;
#[allow(clippy::unused_unit, non_snake_case)]
fn borrow_elements<'a>(&'a self) -> Self::ElementBorrowed<'a> {
let ($($t,)*) = self;
($(&$t,)*)
}
}
};
}
impl_element_borrowable!();
impl_element_borrowable!(A);
impl_element_borrowable!(A, B);
impl_element_borrowable!(A, B, C);
impl_element_borrowable!(A, B, C, D);
impl_element_borrowable!(A, B, C, D, E);
impl_element_borrowable!(A, B, C, D, E, F);
impl_element_borrowable!(A, B, C, D, E, F, G);
impl_element_borrowable!(A, B, C, D, E, F, G, H);
impl_element_borrowable!(A, B, C, D, E, F, G, H, I);
impl_element_borrowable!(A, B, C, D, E, F, G, H, I, J);
impl_element_borrowable!(A, B, C, D, E, F, G, H, I, J, K);
impl_element_borrowable!(A, B, C, D, E, F, G, H, I, J, K, L);
impl_element_borrowable!(A, B, C, D, E, F, G, H, I, J, K, L, M);
impl_element_borrowable!(A, B, C, D, E, F, G, H, I, J, K, L, M, N);
impl_element_borrowable!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O);
impl_element_borrowable!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P);

View file

@ -1,102 +0,0 @@
/// This is a interface for a tree with the ability to jump to a specific node
pub trait Traversable {
type Id: Copy;
type Node;
fn height(&self, id: Self::Id) -> Option<u16>;
fn get(&self, id: Self::Id) -> Option<&Self::Node>;
fn get_mut(&mut self, id: Self::Id) -> Option<&mut Self::Node>;
fn children(&self, node: Self::Id) -> &[Self::Id];
fn parent(&self, node: Self::Id) -> Option<Self::Id>;
fn map<N, F: Fn(&Self::Node) -> &N, FMut: Fn(&mut Self::Node) -> &mut N>(
&mut self,
f: F,
f_mut: FMut,
) -> Map<Self, N, F, FMut>
where
Self: Sized,
{
Map {
tree: self,
f,
f_mut,
}
}
// this is safe because no node will have itself as it's parent
fn get_node_parent_mut(
&mut self,
id: Self::Id,
) -> (Option<&mut Self::Node>, Option<&mut Self::Node>) {
let node = self.get_mut(id).map(|n| n as *mut _);
let parent = self
.parent(id)
.and_then(|n| self.get_mut(n))
.map(|n| n as *mut _);
unsafe { (node.map(|n| &mut *n), parent.map(|n| &mut *n)) }
}
// this is safe because no node will have itself as a child
fn get_node_children_mut(
&mut self,
id: Self::Id,
) -> (Option<&mut Self::Node>, Vec<&mut Self::Node>) {
let node = self.get_mut(id).map(|n| n as *mut _);
let mut children = Vec::new();
let children_indexes = self.children(id).to_vec();
for id in children_indexes {
if let Some(n) = self.get_mut(id) {
children.push(unsafe { &mut *(n as *mut _) });
}
}
unsafe { (node.map(|n| &mut *n), children) }
}
}
/// Maps one type of tree to another. Similar to [std::iter::Map].
pub struct Map<
'a,
T: Traversable,
N,
F: Fn(&<T as Traversable>::Node) -> &N,
FMut: Fn(&mut <T as Traversable>::Node) -> &mut N,
> {
f: F,
f_mut: FMut,
tree: &'a mut T,
}
impl<
'a,
T: Traversable,
N,
F: Fn(&<T as Traversable>::Node) -> &N,
FMut: Fn(&mut <T as Traversable>::Node) -> &mut N,
> Traversable for Map<'a, T, N, F, FMut>
{
type Id = <T as Traversable>::Id;
type Node = N;
fn height(&self, id: Self::Id) -> Option<u16> {
self.tree.height(id)
}
fn get(&self, id: Self::Id) -> Option<&Self::Node> {
self.tree.get(id).map(&self.f)
}
fn get_mut(&mut self, id: Self::Id) -> Option<&mut Self::Node> {
self.tree.get_mut(id).map(&self.f_mut)
}
fn children(&self, id: Self::Id) -> &[Self::Id] {
self.tree.children(id)
}
fn parent(&self, id: Self::Id) -> Option<Self::Id> {
self.tree.parent(id)
}
}

View file

@ -0,0 +1,859 @@
use core::panic;
use parking_lot::lock_api::RawMutex as _;
use parking_lot::{RawMutex, RwLock};
use slab::Slab;
use std::cell::UnsafeCell;
use std::collections::VecDeque;
use std::marker::PhantomData;
use std::sync::Arc;
#[derive(Hash, PartialEq, Eq, Clone, Copy, Debug, PartialOrd, Ord)]
pub struct NodeId(pub usize);
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct Node<T> {
value: T,
parent: Option<NodeId>,
children: Vec<NodeId>,
height: u16,
}
#[derive(Debug)]
pub struct Tree<T> {
nodes: Slab<Node<T>>,
root: NodeId,
}
impl<T> Tree<T> {
fn try_remove(&mut self, id: NodeId) -> Option<Node<T>> {
self.nodes.try_remove(id.0).map(|node| {
if let Some(parent) = node.parent {
self.nodes
.get_mut(parent.0)
.unwrap()
.children
.retain(|child| child != &id);
}
for child in &node.children {
self.remove_recursive(*child);
}
node
})
}
fn remove_recursive(&mut self, node: NodeId) {
let node = self.nodes.remove(node.0);
for child in node.children {
self.remove_recursive(child);
}
}
fn set_height(&mut self, node: NodeId, height: u16) {
let self_mut = self as *mut Self;
let node = self.nodes.get_mut(node.0).unwrap();
node.height = height;
unsafe {
// Safety: No node has itself as a child
for child in &node.children {
(*self_mut).set_height(*child, height + 1);
}
}
}
}
pub trait TreeView<T>: Sized {
type Iterator<'a>: Iterator<Item = &'a T>
where
T: 'a,
Self: 'a;
type IteratorMut<'a>: Iterator<Item = &'a mut T>
where
T: 'a,
Self: 'a;
fn root(&self) -> NodeId;
fn contains(&self, id: NodeId) -> bool {
self.get(id).is_some()
}
fn get(&self, id: NodeId) -> Option<&T>;
fn get_unchecked(&self, id: NodeId) -> &T {
unsafe { self.get(id).unwrap_unchecked() }
}
fn get_mut(&mut self, id: NodeId) -> Option<&mut T>;
fn get_mut_unchecked(&mut self, id: NodeId) -> &mut T {
unsafe { self.get_mut(id).unwrap_unchecked() }
}
fn children(&self, id: NodeId) -> Option<Self::Iterator<'_>>;
fn children_mut(&mut self, id: NodeId) -> Option<Self::IteratorMut<'_>>;
fn parent_child_mut(&mut self, id: NodeId) -> Option<(&mut T, Self::IteratorMut<'_>)> {
let mut_ptr: *mut Self = self;
unsafe {
// Safety: No node has itself as a child.
(*mut_ptr).get_mut(id).and_then(|parent| {
(*mut_ptr)
.children_mut(id)
.map(|children| (parent, children))
})
}
}
fn children_ids(&self, id: NodeId) -> Option<&[NodeId]>;
fn parent(&self, id: NodeId) -> Option<&T>;
fn parent_mut(&mut self, id: NodeId) -> Option<&mut T>;
fn node_parent_mut(&mut self, id: NodeId) -> Option<(&mut T, Option<&mut T>)> {
let mut_ptr: *mut Self = self;
unsafe {
// Safety: No node has itself as a parent.
(*mut_ptr)
.get_mut(id)
.map(|node| (node, (*mut_ptr).parent_mut(id)))
}
}
fn parent_id(&self, id: NodeId) -> Option<NodeId>;
fn height(&self, id: NodeId) -> Option<u16>;
fn map<T2, F: Fn(&T) -> &T2, FMut: Fn(&mut T) -> &mut T2>(
&mut self,
map: F,
map_mut: FMut,
) -> TreeMap<T, T2, Self, F, FMut> {
TreeMap::new(self, map, map_mut)
}
fn size(&self) -> usize;
fn traverse_depth_first(&self, mut f: impl FnMut(&T)) {
let mut stack = vec![self.root()];
while let Some(id) = stack.pop() {
if let Some(node) = self.get(id) {
f(node);
if let Some(children) = self.children_ids(id) {
stack.extend(children.iter().copied().rev());
}
}
}
}
fn traverse_depth_first_mut(&mut self, mut f: impl FnMut(&mut T)) {
let mut stack = vec![self.root()];
while let Some(id) = stack.pop() {
if let Some(node) = self.get_mut(id) {
f(node);
if let Some(children) = self.children_ids(id) {
stack.extend(children.iter().copied().rev());
}
}
}
}
fn traverse_breadth_first(&self, mut f: impl FnMut(&T)) {
let mut queue = VecDeque::new();
queue.push_back(self.root());
while let Some(id) = queue.pop_front() {
if let Some(node) = self.get(id) {
f(node);
if let Some(children) = self.children_ids(id) {
for id in children {
queue.push_back(*id);
}
}
}
}
}
fn traverse_breadth_first_mut(&mut self, mut f: impl FnMut(&mut T)) {
let mut queue = VecDeque::new();
queue.push_back(self.root());
while let Some(id) = queue.pop_front() {
if let Some(node) = self.get_mut(id) {
f(node);
if let Some(children) = self.children_ids(id) {
for id in children {
queue.push_back(*id);
}
}
}
}
}
}
pub trait TreeLike<T>: TreeView<T> {
fn new(root: T) -> Self;
fn create_node(&mut self, value: T) -> NodeId;
fn add_child(&mut self, parent: NodeId, child: NodeId);
fn remove(&mut self, id: NodeId) -> Option<T>;
fn remove_all_children(&mut self, id: NodeId) -> Vec<T>;
fn replace(&mut self, old: NodeId, new: NodeId);
fn insert_before(&mut self, id: NodeId, new: NodeId);
fn insert_after(&mut self, id: NodeId, new: NodeId);
}
pub struct ChildNodeIterator<'a, T, Tr: TreeView<T>> {
tree: &'a Tr,
children_ids: &'a [NodeId],
index: usize,
node_type: PhantomData<T>,
}
impl<'a, T: 'a, Tr: TreeView<T>> Iterator for ChildNodeIterator<'a, T, Tr> {
type Item = &'a T;
fn next(&mut self) -> Option<Self::Item> {
self.children_ids.get(self.index).map(|id| {
self.index += 1;
self.tree.get_unchecked(*id)
})
}
}
pub struct ChildNodeIteratorMut<'a, T, Tr: TreeView<T> + 'a> {
tree: *mut Tr,
children_ids: &'a [NodeId],
index: usize,
node_type: PhantomData<T>,
}
unsafe impl<'a, T, Tr: TreeView<T> + 'a> Sync for ChildNodeIteratorMut<'a, T, Tr> {}
impl<'a, T, Tr: TreeView<T>> ChildNodeIteratorMut<'a, T, Tr> {
fn tree_mut(&mut self) -> &'a mut Tr {
unsafe { &mut *self.tree }
}
}
impl<'a, T: 'a, Tr: TreeView<T>> Iterator for ChildNodeIteratorMut<'a, T, Tr> {
type Item = &'a mut T;
fn next(&mut self) -> Option<Self::Item> {
let owned = self.children_ids.get(self.index).copied();
match owned {
Some(id) => {
self.index += 1;
Some(self.tree_mut().get_mut_unchecked(id))
}
None => None,
}
}
}
impl<T> TreeView<T> for Tree<T> {
type Iterator<'a> = ChildNodeIterator<'a, T, Tree<T>> where T: 'a;
type IteratorMut<'a> = ChildNodeIteratorMut<'a, T, Tree<T>> where T: 'a;
fn root(&self) -> NodeId {
self.root
}
fn get(&self, id: NodeId) -> Option<&T> {
self.nodes.get(id.0).map(|node| &node.value)
}
fn get_mut(&mut self, id: NodeId) -> Option<&mut T> {
self.nodes.get_mut(id.0).map(|node| &mut node.value)
}
fn children(&self, id: NodeId) -> Option<Self::Iterator<'_>> {
self.children_ids(id).map(|children_ids| ChildNodeIterator {
tree: self,
children_ids,
index: 0,
node_type: PhantomData,
})
}
fn children_mut(&mut self, id: NodeId) -> Option<Self::IteratorMut<'_>> {
let raw_ptr = self as *mut Self;
unsafe {
// Safety: No node will appear as a child twice
self.children_ids(id)
.map(|children_ids| ChildNodeIteratorMut {
tree: &mut *raw_ptr,
children_ids,
index: 0,
node_type: PhantomData,
})
}
}
fn children_ids(&self, id: NodeId) -> Option<&[NodeId]> {
self.nodes.get(id.0).map(|node| node.children.as_slice())
}
fn parent(&self, id: NodeId) -> Option<&T> {
self.nodes
.get(id.0)
.and_then(|node| node.parent.map(|id| self.nodes.get(id.0).unwrap()))
.map(|node| &node.value)
}
fn parent_mut(&mut self, id: NodeId) -> Option<&mut T> {
let self_ptr = self as *mut Self;
unsafe {
// Safety: No node has itself as a parent.
self.nodes
.get_mut(id.0)
.and_then(move |node| {
node.parent
.map(move |id| (*self_ptr).nodes.get_mut(id.0).unwrap())
})
.map(|node| &mut node.value)
}
}
fn parent_id(&self, id: NodeId) -> Option<NodeId> {
self.nodes.get(id.0).and_then(|node| node.parent)
}
fn height(&self, id: NodeId) -> Option<u16> {
self.nodes.get(id.0).map(|n| n.height)
}
fn get_unchecked(&self, id: NodeId) -> &T {
unsafe { &self.nodes.get_unchecked(id.0).value }
}
fn get_mut_unchecked(&mut self, id: NodeId) -> &mut T {
unsafe { &mut self.nodes.get_unchecked_mut(id.0).value }
}
fn size(&self) -> usize {
self.nodes.len()
}
}
impl<T> TreeLike<T> for Tree<T> {
fn new(root: T) -> Self {
let mut nodes = Slab::default();
let root = NodeId(nodes.insert(Node {
value: root,
parent: None,
children: Vec::new(),
height: 0,
}));
Self { nodes, root }
}
fn create_node(&mut self, value: T) -> NodeId {
NodeId(self.nodes.insert(Node {
value,
parent: None,
children: Vec::new(),
height: 0,
}))
}
fn add_child(&mut self, parent: NodeId, new: NodeId) {
self.nodes.get_mut(new.0).unwrap().parent = Some(parent);
let parent = self.nodes.get_mut(parent.0).unwrap();
parent.children.push(new);
let height = parent.height + 1;
self.set_height(new, height);
}
fn remove(&mut self, id: NodeId) -> Option<T> {
self.try_remove(id).map(|node| node.value)
}
fn remove_all_children(&mut self, id: NodeId) -> Vec<T> {
let mut children = Vec::new();
let self_mut = self as *mut Self;
for child in self.children_ids(id).unwrap() {
unsafe {
// Safety: No node has itself as a child
children.push((*self_mut).remove(*child).unwrap());
}
}
children
}
fn replace(&mut self, old_id: NodeId, new_id: NodeId) {
// remove the old node
let old = self
.try_remove(old_id)
.expect("tried to replace a node that doesn't exist");
// update the parent's link to the child
if let Some(parent_id) = old.parent {
let parent = self.nodes.get_mut(parent_id.0).unwrap();
for id in &mut parent.children {
if *id == old_id {
*id = new_id;
}
}
let height = parent.height + 1;
self.set_height(new_id, height);
}
}
fn insert_before(&mut self, id: NodeId, new: NodeId) {
let node = self.nodes.get(id.0).unwrap();
let parent_id = node.parent.expect("tried to insert before root");
self.nodes.get_mut(new.0).unwrap().parent = Some(parent_id);
let parent = self.nodes.get_mut(parent_id.0).unwrap();
let index = parent
.children
.iter()
.position(|child| child == &id)
.unwrap();
parent.children.insert(index, new);
let height = parent.height + 1;
self.set_height(new, height);
}
fn insert_after(&mut self, id: NodeId, new: NodeId) {
let node = self.nodes.get(id.0).unwrap();
let parent_id = node.parent.expect("tried to insert before root");
self.nodes.get_mut(new.0).unwrap().parent = Some(parent_id);
let parent = self.nodes.get_mut(parent_id.0).unwrap();
let index = parent
.children
.iter()
.position(|child| child == &id)
.unwrap();
parent.children.insert(index + 1, new);
let height = parent.height + 1;
self.set_height(new, height);
}
}
pub struct TreeMap<'a, T1, T2, Tr, F, FMut>
where
Tr: TreeView<T1>,
F: Fn(&T1) -> &T2,
FMut: Fn(&mut T1) -> &mut T2,
{
tree: &'a mut Tr,
map: F,
map_mut: FMut,
in_node_type: PhantomData<T1>,
out_node_type: PhantomData<T2>,
}
impl<'a, T1, T2, Tr, F, FMut> TreeMap<'a, T1, T2, Tr, F, FMut>
where
Tr: TreeView<T1>,
F: Fn(&T1) -> &T2,
FMut: Fn(&mut T1) -> &mut T2,
{
pub fn new(tree: &'a mut Tr, map: F, map_mut: FMut) -> Self {
TreeMap {
tree,
map,
map_mut,
in_node_type: PhantomData,
out_node_type: PhantomData,
}
}
}
impl<'a, T1, T2, Tr, F, FMut> TreeView<T2> for TreeMap<'a, T1, T2, Tr, F, FMut>
where
Tr: TreeView<T1>,
F: Fn(&T1) -> &T2,
FMut: Fn(&mut T1) -> &mut T2,
{
type Iterator<'b> = ChildNodeIterator<'b, T2, TreeMap<'a, T1, T2, Tr, F, FMut>>
where
T2: 'b,
Self:'b;
type IteratorMut<'b> = ChildNodeIteratorMut<'b, T2, TreeMap<'a, T1, T2, Tr, F, FMut>>
where
T2: 'b,
Self:'b;
fn root(&self) -> NodeId {
self.tree.root()
}
fn get(&self, id: NodeId) -> Option<&T2> {
self.tree.get(id).map(|node| (self.map)(node))
}
fn get_mut(&mut self, id: NodeId) -> Option<&mut T2> {
self.tree.get_mut(id).map(|node| (self.map_mut)(node))
}
fn children(&self, id: NodeId) -> Option<Self::Iterator<'_>> {
self.children_ids(id).map(|children_ids| ChildNodeIterator {
tree: self,
children_ids,
index: 0,
node_type: PhantomData,
})
}
fn children_mut(&mut self, id: NodeId) -> Option<Self::IteratorMut<'_>> {
let raw_ptr = self as *mut Self;
unsafe {
// Safety: No node can be a child twice.
self.children_ids(id)
.map(|children_ids| ChildNodeIteratorMut {
tree: &mut *raw_ptr,
children_ids,
index: 0,
node_type: PhantomData,
})
}
}
fn children_ids(&self, id: NodeId) -> Option<&[NodeId]> {
self.tree.children_ids(id)
}
fn parent(&self, id: NodeId) -> Option<&T2> {
self.tree.parent(id).map(|node| (self.map)(node))
}
fn parent_mut(&mut self, id: NodeId) -> Option<&mut T2> {
self.tree.parent_mut(id).map(|node| (self.map_mut)(node))
}
fn parent_id(&self, id: NodeId) -> Option<NodeId> {
self.tree.parent_id(id)
}
fn height(&self, id: NodeId) -> Option<u16> {
self.tree.height(id)
}
fn get_unchecked(&self, id: NodeId) -> &T2 {
(self.map)(self.tree.get_unchecked(id))
}
fn get_mut_unchecked(&mut self, id: NodeId) -> &mut T2 {
(self.map_mut)(self.tree.get_mut_unchecked(id))
}
fn size(&self) -> usize {
self.tree.size()
}
}
/// A view into a tree that can be shared between multiple threads. Nodes are locked invividually.
pub struct SharedView<'a, T, Tr: TreeView<T>> {
tree: Arc<UnsafeCell<&'a mut Tr>>,
node_locks: Arc<RwLock<Vec<RawMutex>>>,
node_type: PhantomData<T>,
}
impl<'a, T, Tr: TreeView<T>> SharedView<'a, T, Tr> {
/// Checks if a node is currently locked. Returns None if the node does not exist.
pub fn check_lock(&self, id: NodeId) -> Option<bool> {
let locks = self.node_locks.read();
locks.get(id.0).map(|lock| lock.is_locked())
}
}
unsafe impl<'a, T, Tr: TreeView<T>> Send for SharedView<'a, T, Tr> {}
unsafe impl<'a, T, Tr: TreeView<T>> Sync for SharedView<'a, T, Tr> {}
impl<'a, T, Tr: TreeView<T>> Clone for SharedView<'a, T, Tr> {
fn clone(&self) -> Self {
Self {
tree: self.tree.clone(),
node_locks: self.node_locks.clone(),
node_type: PhantomData,
}
}
}
impl<'a, T, Tr: TreeView<T>> SharedView<'a, T, Tr> {
pub fn new(tree: &'a mut Tr) -> Self {
let tree = Arc::new(UnsafeCell::new(tree));
let mut node_locks = Vec::new();
for _ in 0..unsafe { (*tree.get()).size() } {
node_locks.push(RawMutex::INIT);
}
Self {
tree,
node_locks: Arc::new(RwLock::new(node_locks)),
node_type: PhantomData,
}
}
fn lock_node(&self, node: NodeId) {
let read = self.node_locks.read();
let lock = read.get(node.0);
match lock {
Some(lock) => lock.lock(),
None => {
drop(read);
let mut write = self.node_locks.write();
write.resize_with(node.0 + 1, || RawMutex::INIT);
unsafe { write.get_unchecked(node.0).lock() }
}
}
}
fn unlock_node(&self, node: NodeId) {
let read = self.node_locks.read();
let lock = read.get(node.0);
match lock {
Some(lock) => unsafe { lock.unlock() },
None => {
panic!("unlocking node that was not locked")
}
}
}
fn with_node<R>(&self, node_id: NodeId, f: impl FnOnce(&'a mut Tr) -> R) -> R {
self.lock_node(node_id);
let tree = unsafe { &mut *self.tree.get() };
let r = f(tree);
self.unlock_node(node_id);
r
}
}
impl<'a, T, Tr: TreeView<T>> TreeView<T> for SharedView<'a, T, Tr> {
type Iterator<'b> = Tr::Iterator<'b> where T: 'b, Self: 'b;
type IteratorMut<'b>=Tr::IteratorMut<'b>
where
T: 'b,
Self: 'b;
fn root(&self) -> NodeId {
unsafe { (*self.tree.get()).root() }
}
fn get(&self, id: NodeId) -> Option<&T> {
self.with_node(id, |t| t.get(id))
}
fn get_mut(&mut self, id: NodeId) -> Option<&mut T> {
self.with_node(id, |t| t.get_mut(id))
}
fn children(&self, id: NodeId) -> Option<Self::Iterator<'_>> {
self.with_node(id, |t| t.children(id))
}
fn children_mut(&mut self, id: NodeId) -> Option<Self::IteratorMut<'_>> {
self.with_node(id, |t| t.children_mut(id))
}
fn children_ids(&self, id: NodeId) -> Option<&[NodeId]> {
self.with_node(id, |t| t.children_ids(id))
}
fn parent(&self, id: NodeId) -> Option<&T> {
self.with_node(id, |t| t.get(id))
}
fn parent_mut(&mut self, id: NodeId) -> Option<&mut T> {
self.with_node(id, |t| t.parent_mut(id))
}
fn parent_id(&self, id: NodeId) -> Option<NodeId> {
self.with_node(id, |t| t.parent_id(id))
}
fn height(&self, id: NodeId) -> Option<u16> {
unsafe { (*self.tree.get()).height(id) }
}
fn size(&self) -> usize {
unsafe { (*self.tree.get()).size() }
}
}
#[test]
fn creation() {
let mut tree = Tree::new(1);
let parent = tree.root();
let child = tree.create_node(0);
tree.add_child(parent, child);
println!("Tree: {:#?}", tree);
assert_eq!(tree.size(), 2);
assert_eq!(tree.height(parent), Some(0));
assert_eq!(tree.height(child), Some(1));
assert_eq!(*tree.get(parent).unwrap(), 1);
assert_eq!(*tree.get(child).unwrap(), 0);
assert_eq!(tree.parent_id(parent), None);
assert_eq!(tree.parent_id(child).unwrap(), parent);
assert_eq!(tree.children_ids(parent).unwrap(), &[child]);
}
#[test]
fn insertion() {
let mut tree = Tree::new(0);
let parent = tree.root();
let child = tree.create_node(2);
tree.add_child(parent, child);
let before = tree.create_node(1);
tree.insert_before(child, before);
let after = tree.create_node(3);
tree.insert_after(child, after);
println!("Tree: {:#?}", tree);
assert_eq!(tree.size(), 4);
assert_eq!(tree.height(parent), Some(0));
assert_eq!(tree.height(child), Some(1));
assert_eq!(tree.height(before), Some(1));
assert_eq!(tree.height(after), Some(1));
assert_eq!(*tree.get(parent).unwrap(), 0);
assert_eq!(*tree.get(before).unwrap(), 1);
assert_eq!(*tree.get(child).unwrap(), 2);
assert_eq!(*tree.get(after).unwrap(), 3);
assert_eq!(tree.parent_id(before).unwrap(), parent);
assert_eq!(tree.parent_id(child).unwrap(), parent);
assert_eq!(tree.parent_id(after).unwrap(), parent);
assert_eq!(tree.children_ids(parent).unwrap(), &[before, child, after]);
}
#[test]
fn deletion() {
let mut tree = Tree::new(0);
let parent = tree.root();
let child = tree.create_node(2);
tree.add_child(parent, child);
let before = tree.create_node(1);
tree.insert_before(child, before);
let after = tree.create_node(3);
tree.insert_after(child, after);
println!("Tree: {:#?}", tree);
assert_eq!(tree.size(), 4);
assert_eq!(tree.height(parent), Some(0));
assert_eq!(tree.height(child), Some(1));
assert_eq!(tree.height(before), Some(1));
assert_eq!(tree.height(after), Some(1));
assert_eq!(*tree.get(parent).unwrap(), 0);
assert_eq!(*tree.get(before).unwrap(), 1);
assert_eq!(*tree.get(child).unwrap(), 2);
assert_eq!(*tree.get(after).unwrap(), 3);
assert_eq!(tree.parent_id(before).unwrap(), parent);
assert_eq!(tree.parent_id(child).unwrap(), parent);
assert_eq!(tree.parent_id(after).unwrap(), parent);
assert_eq!(tree.children_ids(parent).unwrap(), &[before, child, after]);
tree.remove(child);
println!("Tree: {:#?}", tree);
assert_eq!(tree.size(), 3);
assert_eq!(tree.height(parent), Some(0));
assert_eq!(tree.height(before), Some(1));
assert_eq!(tree.height(after), Some(1));
assert_eq!(*tree.get(parent).unwrap(), 0);
assert_eq!(*tree.get(before).unwrap(), 1);
assert_eq!(tree.get(child), None);
assert_eq!(*tree.get(after).unwrap(), 3);
assert_eq!(tree.parent_id(before).unwrap(), parent);
assert_eq!(tree.parent_id(after).unwrap(), parent);
assert_eq!(tree.children_ids(parent).unwrap(), &[before, after]);
tree.remove(before);
println!("Tree: {:#?}", tree);
assert_eq!(tree.size(), 2);
assert_eq!(tree.height(parent), Some(0));
assert_eq!(tree.height(after), Some(1));
assert_eq!(*tree.get(parent).unwrap(), 0);
assert_eq!(tree.get(before), None);
assert_eq!(*tree.get(after).unwrap(), 3);
assert_eq!(tree.parent_id(after).unwrap(), parent);
assert_eq!(tree.children_ids(parent).unwrap(), &[after]);
tree.remove(after);
println!("Tree: {:#?}", tree);
assert_eq!(tree.size(), 1);
assert_eq!(tree.height(parent), Some(0));
assert_eq!(*tree.get(parent).unwrap(), 0);
assert_eq!(tree.get(after), None);
assert_eq!(tree.children_ids(parent).unwrap(), &[]);
}
#[test]
fn shared_view() {
use std::thread;
let mut tree = Tree::new(1);
let parent = tree.root();
let child = tree.create_node(0);
tree.add_child(parent, child);
let shared = SharedView::new(&mut tree);
thread::scope(|s| {
let (mut shared1, mut shared2, mut shared3) =
(shared.clone(), shared.clone(), shared.clone());
s.spawn(move || {
assert_eq!(*shared1.get_mut(parent).unwrap(), 1);
assert_eq!(*shared1.get_mut(child).unwrap(), 0);
});
s.spawn(move || {
assert_eq!(*shared2.get_mut(child).unwrap(), 0);
assert_eq!(*shared2.get_mut(parent).unwrap(), 1);
});
s.spawn(move || {
assert_eq!(*shared3.get_mut(parent).unwrap(), 1);
assert_eq!(*shared3.get_mut(child).unwrap(), 0);
});
});
}
#[test]
fn map() {
#[derive(Debug, PartialEq)]
struct Value {
value: i32,
}
impl Value {
fn new(value: i32) -> Self {
Self { value }
}
}
let mut tree = Tree::new(Value::new(1));
let parent = tree.root();
let child = tree.create_node(Value::new(0));
tree.add_child(parent, child);
let mut mapped = tree.map(|x| &x.value, |x| &mut x.value);
*mapped.get_mut(child).unwrap() = 1;
*mapped.get_mut(parent).unwrap() = 2;
assert_eq!(*tree.get(parent).unwrap(), Value::new(2));
assert_eq!(*tree.get(child).unwrap(), Value::new(1));
}
#[test]
fn traverse_depth_first() {
let mut tree = Tree::new(0);
let parent = tree.root();
let child1 = tree.create_node(1);
tree.add_child(parent, child1);
let grandchild1 = tree.create_node(2);
tree.add_child(child1, grandchild1);
let child2 = tree.create_node(3);
tree.add_child(parent, child2);
let grandchild2 = tree.create_node(4);
tree.add_child(child2, grandchild2);
let mut node_count = 0;
tree.traverse_depth_first(move |node| {
assert_eq!(*node, node_count);
node_count += 1;
});
}

View file

@ -1,10 +1,8 @@
use crate::{
real_dom::{NodeType, RealDom},
state::State,
RealNodeId,
};
use dioxus_core::{DomEdit, ElementId, Mutations};
use crate::{node::NodeType, real_dom::RealDom, state::State, tree::TreeView, NodeId, RealNodeId};
use dioxus_core::{Mutation, Mutations};
use std::fmt::Debug;
#[derive(Debug)]
pub enum ElementProduced {
/// The iterator produced an element by progressing to the next node in a depth first order.
Progressed(RealNodeId),
@ -57,7 +55,7 @@ pub struct PersistantElementIter {
impl Default for PersistantElementIter {
fn default() -> Self {
PersistantElementIter {
stack: smallvec::smallvec![(RealNodeId::ElementId(ElementId(0)), NodePosition::AtNode)],
stack: smallvec::smallvec![(NodeId(0), NodePosition::AtNode)],
}
}
}
@ -74,11 +72,10 @@ impl PersistantElementIter {
let ids_removed: Vec<_> = mutations
.edits
.iter()
.filter_map(|e| {
// nodes within templates will never be removed
if let DomEdit::Remove { root } = e {
let id = rdom.resolve_maybe_id(*root);
Some(id)
.filter_map(|m| {
// nodes within templates will never be removedns
if let Mutation::Remove { id } = m {
Some(rdom.element_to_node_id(*id))
} else {
None
}
@ -96,27 +93,25 @@ impl PersistantElementIter {
// if a child is removed or inserted before or at the current element, update the child index
for (el_id, child_idx) in self.stack.iter_mut() {
if let NodePosition::InChild(child_idx) = child_idx {
if let NodeType::Element { children, .. } = &rdom[*el_id].node_data.node_type {
if let Some(children) = &rdom.tree.children_ids(*el_id) {
for m in &mutations.edits {
match m {
DomEdit::Remove { root } => {
let id = rdom.resolve_maybe_id(*root);
Mutation::Remove { id } => {
let id = rdom.element_to_node_id(*id);
if children.iter().take(*child_idx + 1).any(|c| *c == id) {
*child_idx -= 1;
}
}
DomEdit::InsertBefore { root, nodes } => {
let id = rdom.resolve_maybe_id(*root);
let n = nodes.len();
Mutation::InsertBefore { id, m } => {
let id = rdom.element_to_node_id(*id);
if children.iter().take(*child_idx + 1).any(|c| *c == id) {
*child_idx += n as usize;
*child_idx += *m;
}
}
DomEdit::InsertAfter { root, nodes } => {
let id = rdom.resolve_maybe_id(*root);
let n = nodes.len();
Mutation::InsertAfter { id, m } => {
let id = rdom.element_to_node_id(*id);
if children.iter().take(*child_idx).any(|c| *c == id) {
*child_idx += n as usize;
*child_idx += *m;
}
}
_ => (),
@ -131,18 +126,19 @@ impl PersistantElementIter {
/// get the next element
pub fn next<S: State>(&mut self, rdom: &RealDom<S>) -> ElementProduced {
if self.stack.is_empty() {
let id = RealNodeId::ElementId(ElementId(0));
let id = NodeId(0);
let new = (id, NodePosition::AtNode);
self.stack.push(new);
ElementProduced::Looped(id)
} else {
let (last, o_child_idx) = self.stack.last_mut().unwrap();
let (last, old_child_idx) = self.stack.last_mut().unwrap();
let node = &rdom[*last];
match &node.node_data.node_type {
NodeType::Element { children, .. } => {
*o_child_idx = o_child_idx.map(|i| i + 1);
NodeType::Element { .. } => {
let children = rdom.tree.children_ids(*last).unwrap();
*old_child_idx = old_child_idx.map(|i| i + 1);
// if we have children, go to the next child
let child_idx = o_child_idx.get_or_insert(0);
let child_idx = old_child_idx.get_or_insert(0);
if child_idx >= children.len() {
self.pop();
self.next(rdom)
@ -172,7 +168,8 @@ impl PersistantElementIter {
rdom: &RealDom<S>,
) -> RealNodeId {
match &rdom[new_node].node_data.node_type {
NodeType::Element { children, .. } => {
NodeType::Element { .. } => {
let children = rdom.tree.children_ids(new_node).unwrap();
if children.is_empty() {
new_node
} else {
@ -184,19 +181,20 @@ impl PersistantElementIter {
}
}
if self.stack.is_empty() {
let new_node = RealNodeId::ElementId(ElementId(0));
let new_node = NodeId(0);
ElementProduced::Looped(push_back(&mut self.stack, new_node, rdom))
} else {
let (last, o_child_idx) = self.stack.last_mut().unwrap();
let (last, old_child_idx) = self.stack.last_mut().unwrap();
let node = &rdom[*last];
match &node.node_data.node_type {
NodeType::Element { children, .. } => {
NodeType::Element { .. } => {
let children = rdom.tree.children_ids(*last).unwrap();
// if we have children, go to the next child
if let NodePosition::InChild(0) = o_child_idx {
if let NodePosition::InChild(0) = old_child_idx {
ElementProduced::Progressed(self.pop())
} else {
*o_child_idx = o_child_idx.map(|i| i - 1);
if let NodePosition::InChild(child_idx) = o_child_idx {
*old_child_idx = old_child_idx.map(|i| i - 1);
if let NodePosition::InChild(child_idx) = old_child_idx {
if *child_idx >= children.len() || children.is_empty() {
self.pop();
self.prev(rdom)
@ -227,3 +225,304 @@ impl PersistantElementIter {
self.stack.pop().unwrap().0
}
}
#[derive(Default, Clone, Debug)]
struct Empty {}
impl State for Empty {
const PASSES: &'static [crate::AnyPass<crate::node::Node<Self>>] = &[];
const MASKS: &'static [crate::NodeMask] = &[];
}
#[test]
#[allow(unused_variables)]
fn traverse() {
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<Empty> = RealDom::new();
let _to_update = rdom.apply_mutations(mutations);
let mut iter = PersistantElementIter::new();
let div_tag = "div".to_string();
assert!(matches!(
&rdom[iter.next(&rdom).id()].node_data.node_type,
NodeType::Element { tag: div_tag, .. }
));
assert!(matches!(
&rdom[iter.next(&rdom).id()].node_data.node_type,
NodeType::Element { tag: div_tag, .. }
));
let text1 = "hello".to_string();
assert!(matches!(
&rdom[iter.next(&rdom).id()].node_data.node_type,
NodeType::Text { text: text1, .. }
));
let p_tag = "p".to_string();
assert!(matches!(
&rdom[iter.next(&rdom).id()].node_data.node_type,
NodeType::Element { tag: p_tag, .. }
));
let text2 = "world".to_string();
assert!(matches!(
&rdom[iter.next(&rdom).id()].node_data.node_type,
NodeType::Text { text: text2, .. }
));
let text3 = "hello world".to_string();
assert!(matches!(
&rdom[iter.next(&rdom).id()].node_data.node_type,
NodeType::Text { text: text3, .. }
));
assert!(matches!(
&rdom[iter.next(&rdom).id()].node_data.node_type,
NodeType::Element { tag: div_tag, .. }
));
assert!(matches!(
&rdom[iter.prev(&rdom).id()].node_data.node_type,
NodeType::Text { text: text3, .. }
));
assert!(matches!(
&rdom[iter.prev(&rdom).id()].node_data.node_type,
NodeType::Text { text: text2, .. }
));
assert!(matches!(
&rdom[iter.prev(&rdom).id()].node_data.node_type,
NodeType::Element { tag: p_tag, .. }
));
assert!(matches!(
&rdom[iter.prev(&rdom).id()].node_data.node_type,
NodeType::Text { text: text1, .. }
));
assert!(matches!(
&rdom[iter.prev(&rdom).id()].node_data.node_type,
NodeType::Element { tag: div_tag, .. }
));
assert!(matches!(
&rdom[iter.prev(&rdom).id()].node_data.node_type,
NodeType::Element { tag: div_tag, .. }
));
assert!(matches!(
&rdom[iter.prev(&rdom).id()].node_data.node_type,
NodeType::Element { tag: div_tag, .. }
));
assert!(matches!(
&rdom[iter.prev(&rdom).id()].node_data.node_type,
NodeType::Text { text: text3, .. }
));
}
#[test]
#[allow(unused_variables)]
fn persist_removes() {
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<Empty> = RealDom::new();
let build = vdom.rebuild();
let _to_update = rdom.apply_mutations(build);
// this will end on the node that is removed
let mut iter1 = PersistantElementIter::new();
// this will end on the after node that is removed
let mut iter2 = PersistantElementIter::new();
// div
iter1.next(&rdom).id();
iter2.next(&rdom).id();
// p
iter1.next(&rdom).id();
iter2.next(&rdom).id();
// "1"
iter1.next(&rdom).id();
iter2.next(&rdom).id();
// p
iter1.next(&rdom).id();
iter2.next(&rdom).id();
// "2"
iter1.next(&rdom).id();
iter2.next(&rdom).id();
// p
iter2.next(&rdom).id();
// "3"
iter2.next(&rdom).id();
vdom.mark_dirty(ScopeId(0));
let update = vdom.render_immediate();
iter1.prune(&update, &rdom);
iter2.prune(&update, &rdom);
let _to_update = rdom.apply_mutations(update);
let p_tag = "1".to_string();
let idx = iter1.next(&rdom).id();
assert!(matches!(
&rdom[idx].node_data.node_type,
NodeType::Element { tag: p_tag, .. }
));
let text = "2".to_string();
let idx = iter1.next(&rdom).id();
assert!(matches!(
&rdom[idx].node_data.node_type,
NodeType::Text { text, .. }
));
let div_tag = "div".to_string();
let idx = iter2.next(&rdom).id();
assert!(matches!(
&rdom[idx].node_data.node_type,
NodeType::Element { tag: div_tag, .. }
));
}
#[test]
#[allow(unused_variables)]
fn persist_instertions_before() {
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<Empty> = RealDom::new();
let build = vdom.rebuild();
let _to_update = rdom.apply_mutations(build);
let mut iter = PersistantElementIter::new();
// div
iter.next(&rdom).id();
// p
iter.next(&rdom).id();
// "1"
iter.next(&rdom).id();
// p
iter.next(&rdom).id();
// "2"
iter.next(&rdom).id();
vdom.mark_dirty(ScopeId(0));
let update = vdom.render_immediate();
iter.prune(&update, &rdom);
let _to_update = rdom.apply_mutations(update);
let p_tag = "div".to_string();
let idx = iter.next(&rdom).id();
assert!(matches!(
&rdom[idx].node_data.node_type,
NodeType::Element { tag: p_tag, .. }
));
}
#[test]
#[allow(unused_variables)]
fn persist_instertions_after() {
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<Empty> = RealDom::new();
let build = vdom.rebuild();
let _to_update = rdom.apply_mutations(build);
let mut iter = PersistantElementIter::new();
// div
iter.next(&rdom).id();
// p
iter.next(&rdom).id();
// "hello"
iter.next(&rdom).id();
// p
iter.next(&rdom).id();
// "world"
iter.next(&rdom).id();
let update = vdom.rebuild();
iter.prune(&update, &rdom);
let _to_update = rdom.apply_mutations(update);
let p_tag = "p".to_string();
let idx = iter.next(&rdom).id();
assert!(matches!(
&rdom[idx].node_data.node_type,
NodeType::Element { tag: p_tag, .. }
));
let text = "hello world".to_string();
let idx = iter.next(&rdom).id();
assert!(matches!(
&rdom[idx].node_data.node_type,
NodeType::Text { text, .. }
));
}

View file

@ -23,10 +23,11 @@ crossterm = "0.23.0"
anyhow = "1.0.42"
tokio = { version = "1.15.0", features = ["full"] }
futures = "0.3.19"
taffy = "0.1.0"
taffy = "0.2.1"
smallvec = "1.6"
rustc-hash = "1.1.0"
anymap = "0.12.1"
futures-channel = "0.3.25"
[dev-dependencies]
dioxus = { path = "../dioxus" }

View file

@ -73,7 +73,7 @@ fn Grid(cx: Scope<GridProps>) -> Element {
let count = use_state(&cx, || 0);
let counts = use_ref(&cx, || vec![0; size * size]);
let ctx: TuiContext = cx.consume_context().unwrap();
let ctx: &TuiContext = cx.consume_context().unwrap();
if *count.get() + 1 >= (size * size) {
ctx.quit();
} else {
@ -88,14 +88,14 @@ fn Grid(cx: Scope<GridProps>) -> Element {
});
}
cx.render(rsx! {
render! {
div{
width: "100%",
height: "100%",
flex_direction: "column",
(0..size).map(|x|
{
cx.render(rsx! {
rsx! {
div{
width: "100%",
height: "100%",
@ -104,7 +104,7 @@ fn Grid(cx: Scope<GridProps>) -> Element {
{
let alpha = y as f32*100.0/size as f32 + counts.read()[x*size + y] as f32;
let key = format!("{}-{}", x, y);
cx.render(rsx! {
rsx! {
Box{
x: x,
y: y,
@ -112,15 +112,15 @@ fn Grid(cx: Scope<GridProps>) -> Element {
hue: alpha,
key: "{key}",
}
})
}
}
)
}
})
}
}
)
}
})
}
}
fn app3(cx: Scope) -> Element {

View file

@ -1,6 +1,6 @@
use dioxus::prelude::*;
use dioxus_html::on::{FocusData, KeyboardData, MouseData, WheelData};
use std::sync::Arc;
use dioxus_html::{FocusData, KeyboardData, MouseData, WheelData};
use std::rc::Rc;
fn main() {
dioxus_tui::launch(app);
@ -8,20 +8,20 @@ fn main() {
#[derive(Debug)]
enum Event {
MouseMove(Arc<MouseData>),
MouseClick(Arc<MouseData>),
MouseDoubleClick(Arc<MouseData>),
MouseDown(Arc<MouseData>),
MouseUp(Arc<MouseData>),
MouseMove(Rc<MouseData>),
MouseClick(Rc<MouseData>),
MouseDoubleClick(Rc<MouseData>),
MouseDown(Rc<MouseData>),
MouseUp(Rc<MouseData>),
Wheel(Arc<WheelData>),
Wheel(Rc<WheelData>),
KeyDown(Arc<KeyboardData>),
KeyUp(Arc<KeyboardData>),
KeyPress(Arc<KeyboardData>),
KeyDown(Rc<KeyboardData>),
KeyUp(Rc<KeyboardData>),
KeyPress(Rc<KeyboardData>),
FocusIn(Arc<FocusData>),
FocusOut(Arc<FocusData>),
FocusIn(Rc<FocusData>),
FocusOut(Rc<FocusData>),
}
const MAX_EVENTS: usize = 8;
@ -37,7 +37,7 @@ fn app(cx: Scope) -> Element {
// todo: remove
let mut trimmed = format!("{event:?}");
trimmed.truncate(200);
cx.render(rsx!(p { "{trimmed}" }))
rsx!(p { "{trimmed}" })
});
let log_event = move |event: Event| {
@ -57,20 +57,20 @@ fn app(cx: Scope) -> Element {
align_items: "center",
background_color: "hsl(248, 53%, 58%)",
onmousemove: move |event| log_event(Event::MouseMove(event.data)),
onclick: move |event| log_event(Event::MouseClick(event.data)),
ondblclick: move |event| log_event(Event::MouseDoubleClick(event.data)),
onmousedown: move |event| log_event(Event::MouseDown(event.data)),
onmouseup: move |event| log_event(Event::MouseUp(event.data)),
onmousemove: move |event| log_event(Event::MouseMove(event.inner().clone())),
onclick: move |event| log_event(Event::MouseClick(event.inner().clone())),
ondblclick: move |event| log_event(Event::MouseDoubleClick(event.inner().clone())),
onmousedown: move |event| log_event(Event::MouseDown(event.inner().clone())),
onmouseup: move |event| log_event(Event::MouseUp(event.inner().clone())),
onwheel: move |event| log_event(Event::Wheel(event.data)),
onwheel: move |event| log_event(Event::Wheel(event.inner().clone())),
onkeydown: move |event| log_event(Event::KeyDown(event.data)),
onkeyup: move |event| log_event(Event::KeyUp(event.data)),
onkeypress: move |event| log_event(Event::KeyPress(event.data)),
onkeydown: move |event| log_event(Event::KeyDown(event.inner().clone())),
onkeyup: move |event| log_event(Event::KeyUp(event.inner().clone())),
onkeypress: move |event| log_event(Event::KeyPress(event.inner().clone())),
onfocusin: move |event| log_event(Event::FocusIn(event.data)),
onfocusout: move |event| log_event(Event::FocusOut(event.data)),
onfocusin: move |event| log_event(Event::FocusIn(event.inner().clone())),
onfocusout: move |event| log_event(Event::FocusOut(event.inner().clone())),
"Hover, click, type or scroll to see the info down below"
},

View file

@ -28,7 +28,7 @@ fn Button(cx: Scope<ButtonProps>) -> Element {
background_color: "{color}",
tabindex: "{cx.props.layer}",
onkeydown: |e| {
if let Code::Space = e.data.code() {
if let Code::Space = e.inner().code() {
toggle.modify(|f| !f);
}
},
@ -60,7 +60,7 @@ fn app(cx: Scope) -> Element {
height: "100%",
(1..8).map(|y|
cx.render(rsx!{
rsx!{
div{
display: "flex",
flex_direction: "row",
@ -68,26 +68,26 @@ fn app(cx: Scope) -> Element {
height: "100%",
(1..8).map(|x|{
if (x + y) % 2 == 0{
cx.render(rsx!{
rsx!{
div{
width: "100%",
height: "100%",
background_color: "rgb(100, 100, 100)",
}
})
}
}
else{
let layer = (x + y) % 3;
cx.render(rsx!{
rsx!{
Button{
color_offset: x * y,
layer: layer as u16,
}
})
}
}
})
}
})
}
)
}
})

View file

@ -17,7 +17,7 @@ fn app(cx: Scope) -> Element {
(0..=steps).map(|x|
{
let hue = x as f32*360.0/steps as f32;
cx.render(rsx! {
rsx! {
div{
width: "100%",
height: "100%",
@ -25,7 +25,7 @@ fn app(cx: Scope) -> Element {
(0..=steps).map(|y|
{
let alpha = y as f32*100.0/steps as f32;
cx.render(rsx! {
rsx! {
div {
left: "{x}px",
top: "{y}px",
@ -33,11 +33,11 @@ fn app(cx: Scope) -> Element {
height: "100%",
background_color: "hsl({hue}, 100%, 50%, {alpha}%)",
}
})
}
}
)
}
})
}
}
)
}

View file

@ -1,4 +1,4 @@
use dioxus::core_macro::rsx_without_templates;
use dioxus::core::RenderReturn;
use dioxus::prelude::*;
use dioxus_tui::query::Query;
use dioxus_tui::Size;
@ -10,18 +10,20 @@ fn main() {
fn app(cx: Scope) -> Element {
let hue = use_state(&cx, || 0.0);
let brightness = use_state(&cx, || 0.0);
let tui_query: Query = cx.consume_context().unwrap();
let tui_query: &Query = cx.consume_context().unwrap();
// disable templates so that every node has an id and can be queried
cx.render(rsx_without_templates! {
cx.render(rsx! {
div{
width: "100%",
background_color: "hsl({hue}, 70%, {brightness}%)",
onmousemove: move |evt| {
let node = tui_query.get(cx.root_node().mounted_id());
if let RenderReturn::Sync(Ok(node))=cx.root_node(){
let node = tui_query.get(node.root_ids[0].get());
let Size{width, height} = node.size().unwrap();
let pos = evt.data.element_coordinates();
let pos = evt.inner().element_coordinates();
hue.set((pos.x as f32/width as f32)*255.0);
brightness.set((pos.y as f32/height as f32)*100.0);
}
},
"hsl({hue}, 70%, {brightness}%)",
}

View file

@ -1,7 +1,7 @@
use std::{convert::TryInto, sync::Arc};
use dioxus::{events::MouseData, prelude::*};
use dioxus_core::UiEvent;
use dioxus_core::Event;
use std::rc::Rc;
use std::{convert::TryInto, sync::Arc};
fn main() {
dioxus_tui::launch(app);
@ -12,7 +12,7 @@ fn app(cx: Scope) -> Element {
"#".to_string() + &c.iter().map(|c| format!("{c:02X?}")).collect::<String>()
}
fn get_brightness(m: Arc<MouseData>) -> i32 {
fn get_brightness(m: &Rc<MouseData>) -> i32 {
let b: i32 = m.held_buttons().len().try_into().unwrap();
127 * b
}
@ -32,8 +32,8 @@ fn app(cx: Scope) -> Element {
let buttons = use_state(&cx, || "".to_string());
let modifiers = use_state(&cx, || "".to_string());
let update_data = move |event: UiEvent<MouseData>| {
let mouse_data = event.data;
let update_data = move |event: Event<MouseData>| {
let mouse_data = event.inner();
page_coordinates.set(format!("{:?}", mouse_data.page_coordinates()));
element_coordinates.set(format!("{:?}", mouse_data.element_coordinates()));
@ -62,9 +62,9 @@ fn app(cx: Scope) -> Element {
justify_content: "center",
align_items: "center",
background_color: "{q1_color_str}",
onmouseenter: move |m| q1_color.set([get_brightness(m.data), 0, 0]),
onmousedown: move |m| q1_color.set([get_brightness(m.data), 0, 0]),
onmouseup: move |m| q1_color.set([get_brightness(m.data), 0, 0]),
onmouseenter: move |m| q1_color.set([get_brightness(m.inner()), 0, 0]),
onmousedown: move |m| q1_color.set([get_brightness(m.inner()), 0, 0]),
onmouseup: move |m| q1_color.set([get_brightness(m.inner()), 0, 0]),
onwheel: move |w| q1_color.set([q1_color[0] + (10.0*w.delta().strip_units().y) as i32, 0, 0]),
onmouseleave: move |_| q1_color.set([200; 3]),
onmousemove: update_data,
@ -76,9 +76,9 @@ fn app(cx: Scope) -> Element {
justify_content: "center",
align_items: "center",
background_color: "{q2_color_str}",
onmouseenter: move |m| q2_color.set([get_brightness(m.data); 3]),
onmousedown: move |m| q2_color.set([get_brightness(m.data); 3]),
onmouseup: move |m| q2_color.set([get_brightness(m.data); 3]),
onmouseenter: move |m| q2_color.set([get_brightness(m.inner()); 3]),
onmousedown: move |m| q2_color.set([get_brightness(m.inner()); 3]),
onmouseup: move |m| q2_color.set([get_brightness(m.inner()); 3]),
onwheel: move |w| q2_color.set([q2_color[0] + (10.0*w.delta().strip_units().y) as i32;3]),
onmouseleave: move |_| q2_color.set([200; 3]),
onmousemove: update_data,
@ -96,9 +96,9 @@ fn app(cx: Scope) -> Element {
justify_content: "center",
align_items: "center",
background_color: "{q3_color_str}",
onmouseenter: move |m| q3_color.set([0, get_brightness(m.data), 0]),
onmousedown: move |m| q3_color.set([0, get_brightness(m.data), 0]),
onmouseup: move |m| q3_color.set([0, get_brightness(m.data), 0]),
onmouseenter: move |m| q3_color.set([0, get_brightness(m.inner()), 0]),
onmousedown: move |m| q3_color.set([0, get_brightness(m.inner()), 0]),
onmouseup: move |m| q3_color.set([0, get_brightness(m.inner()), 0]),
onwheel: move |w| q3_color.set([0, q3_color[1] + (10.0*w.delta().strip_units().y) as i32, 0]),
onmouseleave: move |_| q3_color.set([200; 3]),
onmousemove: update_data,
@ -110,9 +110,9 @@ fn app(cx: Scope) -> Element {
justify_content: "center",
align_items: "center",
background_color: "{q4_color_str}",
onmouseenter: move |m| q4_color.set([0, 0, get_brightness(m.data)]),
onmousedown: move |m| q4_color.set([0, 0, get_brightness(m.data)]),
onmouseup: move |m| q4_color.set([0, 0, get_brightness(m.data)]),
onmouseenter: move |m| q4_color.set([0, 0, get_brightness(m.inner())]),
onmousedown: move |m| q4_color.set([0, 0, get_brightness(m.inner())]),
onmouseup: move |m| q4_color.set([0, 0, get_brightness(m.inner())]),
onwheel: move |w| q4_color.set([0, 0, q4_color[2] + (10.0*w.delta().strip_units().y) as i32]),
onmouseleave: move |_| q4_color.set([200; 3]),
onmousemove: update_data,

View file

@ -11,13 +11,13 @@ fn app(cx: Scope) -> Element {
height: "100%",
flex_direction: "column",
background_color: "black",
// margin_right: "10px",
margin_right: "10px",
div {
width: "70%",
height: "70%",
background_color: "green",
// margin_left: "4px",
margin_left: "4px",
div {
width: "100%",
@ -34,10 +34,10 @@ fn app(cx: Scope) -> Element {
align_items: "center",
flex_direction: "column",
// padding_top: "2px",
// padding_bottom: "2px",
// padding_left: "4px",
// padding_right: "4px",
padding_top: "2px",
padding_bottom: "2px",
padding_left: "4px",
padding_right: "4px",
"[A]"
"[A]"

View file

@ -12,7 +12,7 @@ fn app(cx: Scope) -> Element {
width: "100%",
height: "100%",
flex_direction: "column",
onwheel: move |evt| alpha.set((**alpha + evt.data.delta().strip_units().y as i64).min(100).max(0)),
onwheel: move |evt| alpha.set((**alpha + evt.inner().delta().strip_units().y as i64).min(100).max(0)),
p {
background_color: "black",

View file

@ -1,6 +1,7 @@
use crate::{node::PreventDefault, Dom};
use crate::{node::PreventDefault, TuiDom};
use dioxus_native_core::{
tree::TreeView,
utils::{ElementProduced, PersistantElementIter},
RealNodeId,
};
@ -10,7 +11,6 @@ use std::{cmp::Ordering, num::NonZeroU16};
use dioxus_native_core::{
node_ref::{AttributeMask, NodeMask, NodeView},
real_dom::NodeType,
state::NodeDepState,
};
@ -64,7 +64,8 @@ pub(crate) struct Focus {
pub level: FocusLevel,
}
impl NodeDepState<()> for Focus {
impl NodeDepState for Focus {
type DepState = ();
type Ctx = ();
const NODE_MASK: NodeMask =
NodeMask::new_with_attrs(AttributeMask::Static(FOCUS_ATTRIBUTES)).with_listeners();
@ -77,7 +78,7 @@ impl NodeDepState<()> for Focus {
{
if let Some(index) = a
.value
.as_int32()
.as_int()
.or_else(|| a.value.as_text().and_then(|v| v.parse::<i32>().ok()))
{
match index.cmp(&0) {
@ -126,7 +127,7 @@ pub(crate) struct FocusState {
impl FocusState {
/// Returns true if the focus has changed.
pub fn progress(&mut self, rdom: &mut Dom, forward: bool) -> bool {
pub fn progress(&mut self, rdom: &mut TuiDom, forward: bool) -> bool {
if let Some(last) = self.last_focused_id {
if rdom[last].state.prevent_default == PreventDefault::KeyDown {
return false;
@ -234,10 +235,10 @@ impl FocusState {
false
}
pub(crate) fn prune(&mut self, mutations: &dioxus_core::Mutations, rdom: &Dom) {
pub(crate) fn prune(&mut self, mutations: &dioxus_core::Mutations, rdom: &TuiDom) {
fn remove_children(
to_prune: &mut [&mut Option<RealNodeId>],
rdom: &Dom,
rdom: &TuiDom,
removed: RealNodeId,
) {
for opt in to_prune.iter_mut() {
@ -247,8 +248,8 @@ impl FocusState {
}
}
}
if let NodeType::Element { children, .. } = &rdom[removed].node_data.node_type {
for child in children {
if let Some(children) = &rdom.children_ids(removed) {
for child in *children {
remove_children(to_prune, rdom, *child);
}
}
@ -258,22 +259,22 @@ impl FocusState {
}
for m in &mutations.edits {
match m {
dioxus_core::DomEdit::ReplaceWith { root, .. } => remove_children(
dioxus_core::Mutation::ReplaceWith { id, .. } => remove_children(
&mut [&mut self.last_focused_id],
rdom,
rdom.resolve_maybe_id(*root),
rdom.element_to_node_id(*id),
),
dioxus_core::DomEdit::Remove { root } => remove_children(
dioxus_core::Mutation::Remove { id } => remove_children(
&mut [&mut self.last_focused_id],
rdom,
rdom.resolve_maybe_id(*root),
rdom.element_to_node_id(*id),
),
_ => (),
}
}
}
pub(crate) fn set_focus(&mut self, rdom: &mut Dom, id: RealNodeId) {
pub(crate) fn set_focus(&mut self, rdom: &mut TuiDom, id: RealNodeId) {
if let Some(old) = self.last_focused_id.replace(id) {
rdom[old].state.focused = false;
}

View file

@ -2,7 +2,8 @@ use crossterm::event::{
Event as TermEvent, KeyCode as TermKeyCode, KeyModifiers, MouseButton, MouseEventKind,
};
use dioxus_core::*;
use dioxus_native_core::RealNodeId;
use dioxus_native_core::tree::TreeView;
use dioxus_native_core::NodeId;
use rustc_hash::{FxHashMap, FxHashSet};
use dioxus_html::geometry::euclid::{Point2D, Rect, Size2D};
@ -12,19 +13,25 @@ use dioxus_html::geometry::{
use dioxus_html::input_data::keyboard_types::{Code, Key, Location, Modifiers};
use dioxus_html::input_data::MouseButtonSet as DioxusMouseButtons;
use dioxus_html::input_data::{MouseButton as DioxusMouseButton, MouseButtonSet};
use dioxus_html::{event_bubbles, on::*};
use dioxus_html::{event_bubbles, FocusData, KeyboardData, MouseData, WheelData};
use std::{
any::Any,
cell::{RefCell, RefMut},
rc::Rc,
sync::Arc,
time::{Duration, Instant},
};
use taffy::geometry::{Point, Size};
use taffy::{prelude::Layout, Taffy};
use crate::FocusState;
use crate::{Dom, Node};
use crate::{layout_to_screen_space, FocusState};
use crate::{TuiDom, TuiNode};
pub(crate) struct Event {
pub id: ElementId,
pub name: &'static str,
pub data: Rc<dyn Any>,
pub bubbles: bool,
}
// a wrapper around the input state for easier access
// todo: fix loop
@ -71,12 +78,12 @@ enum EventData {
Keyboard(KeyboardData),
}
impl EventData {
fn into_any(self) -> Arc<dyn Any + Send + Sync> {
fn into_any(self) -> Rc<dyn Any + Send + Sync> {
match self {
Self::Mouse(m) => Arc::new(m),
Self::Wheel(w) => Arc::new(w),
Self::Screen(s) => Arc::new(s),
Self::Keyboard(k) => Arc::new(k),
Self::Mouse(m) => Rc::new(m),
Self::Wheel(w) => Rc::new(w),
Self::Screen(s) => Rc::new(s),
Self::Keyboard(k) => Rc::new(k),
}
}
}
@ -164,9 +171,9 @@ impl InnerInputState {
fn update(
&mut self,
evts: &mut Vec<EventCore>,
resolved_events: &mut Vec<UserEvent>,
resolved_events: &mut Vec<Event>,
layout: &Taffy,
dom: &mut Dom,
dom: &mut TuiDom,
) {
let previous_mouse = self.mouse.clone();
@ -192,35 +199,35 @@ impl InnerInputState {
if old_focus != self.focus_state.last_focused_id {
// elements with listeners will always have a element id
if let Some(RealNodeId::ElementId(id)) = self.focus_state.last_focused_id {
resolved_events.push(UserEvent {
scope_id: None,
priority: EventPriority::Medium,
if let Some(id) = self.focus_state.last_focused_id {
let element = dom.tree.get(id).unwrap();
if let Some(id) = element.node_data.element_id {
resolved_events.push(Event {
name: "focus",
element: Some(id),
data: Arc::new(FocusData {}),
id,
data: Rc::new(FocusData {}),
bubbles: event_bubbles("focus"),
});
resolved_events.push(UserEvent {
scope_id: None,
priority: EventPriority::Medium,
resolved_events.push(Event {
name: "focusin",
element: Some(id),
data: Arc::new(FocusData {}),
id,
data: Rc::new(FocusData {}),
bubbles: event_bubbles("focusin"),
});
}
if let Some(RealNodeId::ElementId(id)) = old_focus {
resolved_events.push(UserEvent {
scope_id: None,
priority: EventPriority::Medium,
}
if let Some(id) = old_focus {
let element = dom.tree.get(id).unwrap();
if let Some(id) = element.node_data.element_id {
resolved_events.push(Event {
name: "focusout",
element: Some(id),
data: Arc::new(FocusData {}),
id,
data: Rc::new(FocusData {}),
bubbles: event_bubbles("focusout"),
});
}
}
}
// for s in &self.subscribers {
// s();
@ -230,13 +237,21 @@ impl InnerInputState {
fn resolve_mouse_events(
&mut self,
previous_mouse: Option<MouseData>,
resolved_events: &mut Vec<UserEvent>,
resolved_events: &mut Vec<Event>,
layout: &Taffy,
dom: &mut Dom,
dom: &mut TuiDom,
) {
fn layout_contains_point(layout: &Layout, point: ScreenPoint) -> bool {
let Point { x, y } = layout.location;
let (x, y) = (
layout_to_screen_space(x).round(),
layout_to_screen_space(y).round(),
);
let Size { width, height } = layout.size;
let (width, height) = (
layout_to_screen_space(width).round(),
layout_to_screen_space(height).round(),
);
let layout_rect = Rect::new(Point2D::new(x, y), Size2D::new(width, height));
layout_rect.contains(point.cast())
@ -244,30 +259,31 @@ impl InnerInputState {
fn try_create_event(
name: &'static str,
data: Arc<dyn Any + Send + Sync>,
will_bubble: &mut FxHashSet<RealNodeId>,
resolved_events: &mut Vec<UserEvent>,
node: &Node,
dom: &Dom,
data: Rc<dyn Any>,
will_bubble: &mut FxHashSet<NodeId>,
resolved_events: &mut Vec<Event>,
node: &TuiNode,
dom: &TuiDom,
) {
// only trigger event if the event was not triggered already by a child
let id = node.mounted_id();
let id = node.node_data.node_id;
if will_bubble.insert(id) {
let mut parent = node.node_data.parent;
while let Some(parent_id) = parent {
let mut parent = dom.parent(id);
while let Some(current_parent) = parent {
let parent_id = current_parent.node_data.node_id;
will_bubble.insert(parent_id);
parent = dom[parent_id].node_data.parent;
parent = dom.parent(parent_id);
}
resolved_events.push(UserEvent {
scope_id: None,
priority: EventPriority::Medium,
if let Some(id) = node.mounted_id() {
resolved_events.push(Event {
name,
element: Some(id.as_element_id()),
id,
data,
bubbles: event_bubbles(name),
})
}
}
}
fn prepare_mouse_data(mouse_data: &MouseData, layout: &Layout) -> MouseData {
let Point { x, y } = layout.location;
@ -326,7 +342,7 @@ impl InnerInputState {
if currently_contains && previously_contained {
try_create_event(
"mousemove",
Arc::new(prepare_mouse_data(mouse_data, &node_layout)),
Rc::new(prepare_mouse_data(mouse_data, &node_layout)),
&mut will_bubble,
resolved_events,
node,
@ -350,7 +366,7 @@ impl InnerInputState {
if currently_contains && !previously_contained {
try_create_event(
"mouseenter",
Arc::new(mouse_data.clone()),
Rc::new(mouse_data.clone()),
&mut will_bubble,
resolved_events,
node,
@ -373,7 +389,7 @@ impl InnerInputState {
if currently_contains && !previously_contained {
try_create_event(
"mouseover",
Arc::new(prepare_mouse_data(mouse_data, &node_layout)),
Rc::new(prepare_mouse_data(mouse_data, &node_layout)),
&mut will_bubble,
resolved_events,
node,
@ -393,7 +409,7 @@ impl InnerInputState {
if currently_contains {
try_create_event(
"mousedown",
Arc::new(prepare_mouse_data(mouse_data, &node_layout)),
Rc::new(prepare_mouse_data(mouse_data, &node_layout)),
&mut will_bubble,
resolved_events,
node,
@ -414,7 +430,7 @@ impl InnerInputState {
if currently_contains {
try_create_event(
"mouseup",
Arc::new(prepare_mouse_data(mouse_data, &node_layout)),
Rc::new(prepare_mouse_data(mouse_data, &node_layout)),
&mut will_bubble,
resolved_events,
node,
@ -436,7 +452,7 @@ impl InnerInputState {
if currently_contains {
try_create_event(
"click",
Arc::new(prepare_mouse_data(mouse_data, &node_layout)),
Rc::new(prepare_mouse_data(mouse_data, &node_layout)),
&mut will_bubble,
resolved_events,
node,
@ -459,7 +475,7 @@ impl InnerInputState {
if currently_contains {
try_create_event(
"contextmenu",
Arc::new(prepare_mouse_data(mouse_data, &node_layout)),
Rc::new(prepare_mouse_data(mouse_data, &node_layout)),
&mut will_bubble,
resolved_events,
node,
@ -483,7 +499,7 @@ impl InnerInputState {
if currently_contains {
try_create_event(
"wheel",
Arc::new(w.clone()),
Rc::new(w.clone()),
&mut will_bubble,
resolved_events,
node,
@ -508,7 +524,7 @@ impl InnerInputState {
if !currently_contains && previously_contained {
try_create_event(
"mouseleave",
Arc::new(prepare_mouse_data(mouse_data, &node_layout)),
Rc::new(prepare_mouse_data(mouse_data, &node_layout)),
&mut will_bubble,
resolved_events,
node,
@ -531,7 +547,7 @@ impl InnerInputState {
if !currently_contains && previously_contained {
try_create_event(
"mouseout",
Arc::new(prepare_mouse_data(mouse_data, &node_layout)),
Rc::new(prepare_mouse_data(mouse_data, &node_layout)),
&mut will_bubble,
resolved_events,
node,
@ -549,7 +565,7 @@ impl InnerInputState {
let currently_contains = layout_contains_point(node_layout, new_pos);
if currently_contains && node.state.focus.level.focusable() {
focus_id = Some(node.mounted_id());
focus_id = Some(node.node_data.node_id);
}
});
if let Some(id) = focus_id {
@ -564,12 +580,11 @@ impl InnerInputState {
// }
}
fn get_abs_layout(node: &Node, dom: &Dom, taffy: &Taffy) -> Layout {
fn get_abs_layout(node: &TuiNode, dom: &TuiDom, taffy: &Taffy) -> Layout {
let mut node_layout = *taffy.layout(node.state.layout.node.unwrap()).unwrap();
let mut current = node;
while let Some(parent_id) = current.node_data.parent {
let parent = &dom[parent_id];
while let Some(parent) = dom.parent(current.node_data.node_id) {
current = parent;
let parent_layout = taffy.layout(parent.state.layout.node.unwrap()).unwrap();
node_layout.location.x += parent_layout.location.x;
@ -614,11 +629,11 @@ impl RinkInputHandler {
)
}
pub(crate) fn prune(&self, mutations: &dioxus_core::Mutations, rdom: &Dom) {
pub(crate) fn prune(&self, mutations: &dioxus_core::Mutations, rdom: &TuiDom) {
self.state.borrow_mut().focus_state.prune(mutations, rdom);
}
pub(crate) fn get_events(&self, layout: &Taffy, dom: &mut Dom) -> Vec<UserEvent> {
pub(crate) fn get_events(&self, layout: &Taffy, dom: &mut TuiDom) -> Vec<Event> {
let mut resolved_events = Vec::new();
(*self.state).borrow_mut().update(
@ -651,7 +666,7 @@ impl RinkInputHandler {
})
.map(|evt| (evt.0, evt.1.into_any()));
let mut hm: FxHashMap<&'static str, Vec<Arc<dyn Any + Send + Sync>>> = FxHashMap::default();
let mut hm: FxHashMap<&'static str, Vec<Rc<dyn Any + Send + Sync>>> = FxHashMap::default();
for (event, data) in events {
if let Some(v) = hm.get_mut(event) {
v.push(data);
@ -663,11 +678,10 @@ impl RinkInputHandler {
for node in dom.get_listening_sorted(event) {
for data in &datas {
if node.state.focused {
resolved_events.push(UserEvent {
scope_id: None,
priority: EventPriority::Medium,
if let Some(id) = node.mounted_id() {
resolved_events.push(Event {
name: event,
element: Some(node.mounted_id().as_element_id()),
id,
data: data.clone(),
bubbles: event_bubbles(event),
});
@ -675,6 +689,7 @@ impl RinkInputHandler {
}
}
}
}
resolved_events
}

View file

@ -1,15 +1,14 @@
use std::cell::RefCell;
use std::rc::Rc;
use std::sync::{Arc, Mutex};
use dioxus_core::*;
use dioxus_native_core::layout_attributes::apply_layout_attributes;
use dioxus_native_core::node::OwnedAttributeView;
use dioxus_native_core::node_ref::{AttributeMask, NodeMask, NodeView};
use dioxus_native_core::real_dom::OwnedAttributeView;
use dioxus_native_core::state::ChildDepState;
use dioxus_native_core::RealNodeId;
use dioxus_native_core_macro::sorted_str_slice;
use taffy::prelude::*;
use crate::screen_to_layout_space;
#[derive(Debug, Clone, Copy, PartialEq)]
pub(crate) enum PossiblyUninitalized<T> {
Uninitalized,
@ -19,7 +18,7 @@ impl<T> PossiblyUninitalized<T> {
pub fn unwrap(self) -> T {
match self {
Self::Initialized(i) => i,
_ => panic!(),
_ => panic!("uninitalized"),
}
}
pub fn ok(self) -> Option<T> {
@ -42,8 +41,8 @@ pub(crate) struct TaffyLayout {
}
impl ChildDepState for TaffyLayout {
type Ctx = Rc<RefCell<Taffy>>;
type DepState = Self;
type Ctx = Arc<Mutex<Taffy>>;
type DepState = (Self,);
// use tag to force this to be called when a node is built
const NODE_MASK: NodeMask =
NodeMask::new_with_attrs(AttributeMask::Static(SORTED_LAYOUT_ATTRS))
@ -54,14 +53,14 @@ impl ChildDepState for TaffyLayout {
fn reduce<'a>(
&mut self,
node: NodeView,
children: impl Iterator<Item = &'a Self::DepState>,
children: impl Iterator<Item = (&'a Self,)>,
ctx: &Self::Ctx,
) -> bool
where
Self::DepState: 'a,
{
let mut changed = false;
let mut taffy = ctx.borrow_mut();
let mut taffy = ctx.lock().expect("poisoned taffy");
let mut style = Style::default();
if let Some(text) = node.text() {
let char_len = text.chars().count();
@ -69,10 +68,10 @@ impl ChildDepState for TaffyLayout {
style = Style {
size: Size {
// characters are 1 point tall
height: Dimension::Points(1.0),
height: Dimension::Points(screen_to_layout_space(1)),
// text is as long as it is declared
width: Dimension::Points(char_len as f32),
width: Dimension::Points(screen_to_layout_space(char_len as u16)),
},
..Default::default()
};
@ -81,7 +80,7 @@ impl ChildDepState for TaffyLayout {
taffy.set_style(n, style).unwrap();
}
} else {
self.node = PossiblyUninitalized::Initialized(taffy.new_node(style, &[]).unwrap());
self.node = PossiblyUninitalized::Initialized(taffy.new_leaf(style).unwrap());
changed = true;
}
} else {
@ -100,15 +99,9 @@ impl ChildDepState for TaffyLayout {
}
}
// the root node fills the entire area
if node.id() == RealNodeId::ElementId(ElementId(0)) {
apply_layout_attributes("width", "100%", &mut style);
apply_layout_attributes("height", "100%", &mut style);
}
// Set all direct nodes as our children
let mut child_layout = vec![];
for l in children {
for (l,) in children {
child_layout.push(l.node.unwrap());
}
@ -121,7 +114,7 @@ impl ChildDepState for TaffyLayout {
}
} else {
self.node = PossiblyUninitalized::Initialized(
taffy.new_node(style, &child_layout).unwrap(),
taffy.new_with_children(style, &child_layout).unwrap(),
);
changed = true;
}

View file

@ -1,24 +1,26 @@
use anyhow::Result;
use anymap::AnyMap;
use crossterm::{
event::{DisableMouseCapture, EnableMouseCapture, Event as TermEvent, KeyCode, KeyModifiers},
execute,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
use dioxus_core::exports::futures_channel::mpsc::unbounded;
use dioxus_core::*;
use dioxus_native_core::{real_dom::RealDom, RealNodeId};
use dioxus_native_core::{real_dom::RealDom, FxDashSet, NodeId, NodeMask, SendAnyMap};
use focus::FocusState;
use futures::{
channel::mpsc::{UnboundedReceiver, UnboundedSender},
pin_mut, StreamExt,
};
use futures_channel::mpsc::unbounded;
use query::Query;
use std::cell::RefCell;
use std::rc::Rc;
use std::{
cell::RefCell,
sync::{Arc, Mutex},
};
use std::{io, time::Duration};
use taffy::Taffy;
pub use taffy::{geometry::Point, prelude::Size};
pub use taffy::{geometry::Point, prelude::*};
use tui::{backend::CrosstermBackend, layout::Rect, Terminal};
mod config;
@ -36,6 +38,15 @@ pub use config::*;
pub use hooks::*;
pub(crate) use node::*;
// the layout space has a multiplier of 10 to minimize rounding errors
pub(crate) fn screen_to_layout_space(screen: u16) -> f32 {
screen as f32 * 10.0
}
pub(crate) fn layout_to_screen_space(layout: f32) -> f32 {
layout / 10.0
}
#[derive(Clone)]
pub struct TuiContext {
tx: UnboundedSender<InputEvent>,
@ -80,10 +91,10 @@ pub fn launch_cfg(app: Component<()>, cfg: Config) {
let cx = dom.base_scope();
let rdom = Rc::new(RefCell::new(RealDom::new()));
let taffy = Rc::new(RefCell::new(Taffy::new()));
cx.provide_root_context(state);
cx.provide_root_context(TuiContext { tx: event_tx_clone });
cx.provide_root_context(Query {
let taffy = Arc::new(Mutex::new(Taffy::new()));
cx.provide_context(state);
cx.provide_context(TuiContext { tx: event_tx_clone });
cx.provide_context(Query {
rdom: rdom.clone(),
stretch: taffy.clone(),
});
@ -91,8 +102,8 @@ pub fn launch_cfg(app: Component<()>, cfg: Config) {
{
let mut rdom = rdom.borrow_mut();
let mutations = dom.rebuild();
let to_update = rdom.apply_mutations(vec![mutations]);
let mut any_map = AnyMap::new();
let (to_update, _) = rdom.apply_mutations(mutations);
let mut any_map = SendAnyMap::new();
any_map.insert(taffy.clone());
let _to_rerender = rdom.update_state(to_update, any_map);
}
@ -114,8 +125,8 @@ fn render_vdom(
mut event_reciever: UnboundedReceiver<InputEvent>,
handler: RinkInputHandler,
cfg: Config,
rdom: Rc<RefCell<Dom>>,
taffy: Rc<RefCell<Taffy>>,
rdom: Rc<RefCell<TuiDom>>,
taffy: Arc<Mutex<Taffy>>,
mut register_event: impl FnMut(crossterm::event::Event),
) -> Result<()> {
tokio::runtime::Builder::new_current_thread()
@ -133,10 +144,8 @@ fn render_vdom(
terminal.clear().unwrap();
}
let mut to_rerender: rustc_hash::FxHashSet<RealNodeId> =
vec![RealNodeId::ElementId(ElementId(0))]
.into_iter()
.collect();
let mut to_rerender = FxDashSet::default();
to_rerender.insert(NodeId(0));
let mut updated = true;
loop {
@ -145,43 +154,40 @@ fn render_vdom(
-> wait for changes
-> resolve events
-> lazily update the layout and style based on nodes changed
use simd to compare lines for diffing?
todo: lazy re-rendering
*/
if !to_rerender.is_empty() || updated {
updated = false;
fn resize(dims: Rect, taffy: &mut Taffy, rdom: &Dom) {
let width = dims.width;
let height = dims.height;
let root_node = rdom[ElementId(0)].state.layout.node.unwrap();
fn resize(dims: Rect, taffy: &mut Taffy, rdom: &TuiDom) {
let width = screen_to_layout_space(dims.width);
let height = screen_to_layout_space(dims.height);
let root_node = rdom[NodeId(0)].state.layout.node.unwrap();
taffy
.compute_layout(
root_node,
Size {
width: taffy::prelude::Number::Defined((width - 1) as f32),
height: taffy::prelude::Number::Defined((height - 1) as f32),
},
)
.unwrap();
// the root node fills the entire area
let mut style = *taffy.style(root_node).unwrap();
style.size = Size {
width: Dimension::Points(width),
height: Dimension::Points(height),
};
taffy.set_style(root_node, style).unwrap();
let size = Size {
width: AvailableSpace::Definite(width),
height: AvailableSpace::Definite(height),
};
taffy.compute_layout(root_node, size).unwrap();
}
if let Some(terminal) = &mut terminal {
terminal.draw(|frame| {
let rdom = rdom.borrow();
let mut taffy = taffy.lock().expect("taffy lock poisoned");
// size is guaranteed to not change when rendering
resize(frame.size(), &mut taffy.borrow_mut(), &rdom);
let root = &rdom[ElementId(0)];
render::render_vnode(
frame,
&taffy.borrow(),
&rdom,
root,
cfg,
Point::zero(),
);
resize(frame.size(), &mut *taffy, &rdom);
let root = &rdom[NodeId(0)];
render::render_vnode(frame, &*taffy, &rdom, root, cfg, Point::ZERO);
})?;
} else {
let rdom = rdom.borrow();
@ -189,10 +195,10 @@ fn render_vdom(
Rect {
x: 0,
y: 0,
width: 100,
height: 100,
width: 1000,
height: 1000,
},
&mut taffy.borrow_mut(),
&mut taffy.lock().expect("taffy lock poisoned"),
&rdom,
);
}
@ -234,25 +240,28 @@ fn render_vdom(
{
let evts = {
let mut rdom = rdom.borrow_mut();
handler.get_events(&taffy.borrow(), &mut rdom)
handler.get_events(&taffy.lock().expect("taffy lock poisoned"), &mut rdom)
};
{
updated |= handler.state().focus_state.clean();
}
for e in evts {
vdom.handle_message(SchedulerMsg::Event(e));
vdom.handle_event(e.name, e.data, e.id, e.bubbles)
}
let mut rdom = rdom.borrow_mut();
let mutations = vdom.work_with_deadline(|| false);
for m in &mutations {
handler.prune(m, &rdom);
}
let mutations = vdom.render_immediate();
handler.prune(&mutations, &rdom);
// updates the dom's nodes
let to_update = rdom.apply_mutations(mutations);
let (to_update, dirty) = rdom.apply_mutations(mutations);
// update the style and layout
let mut any_map = AnyMap::new();
let mut any_map = SendAnyMap::new();
any_map.insert(taffy.clone());
to_rerender = rdom.update_state(to_update, any_map);
for (id, mask) in dirty {
if mask.overlaps(&NodeMask::new().with_text()) {
to_rerender.insert(id);
}
}
}
}

View file

@ -4,12 +4,12 @@ use crate::style_attributes::StyleModifier;
use dioxus_native_core::{real_dom::RealDom, state::*};
use dioxus_native_core_macro::{sorted_str_slice, State};
pub(crate) type Dom = RealDom<NodeState>;
pub(crate) type Node = dioxus_native_core::real_dom::Node<NodeState>;
pub(crate) type TuiDom = RealDom<NodeState>;
pub(crate) type TuiNode = dioxus_native_core::node::Node<NodeState>;
#[derive(Debug, Clone, State, Default)]
pub(crate) struct NodeState {
#[child_dep_state(layout, RefCell<Stretch>)]
#[child_dep_state(layout, Mutex<Stretch>)]
pub layout: TaffyLayout,
#[parent_dep_state(style)]
pub style: StyleModifier,
@ -45,7 +45,8 @@ impl Default for PreventDefault {
}
}
impl NodeDepState<()> for PreventDefault {
impl NodeDepState for PreventDefault {
type DepState = ();
type Ctx = ();
const NODE_MASK: dioxus_native_core::node_ref::NodeMask =
@ -53,7 +54,8 @@ impl NodeDepState<()> for PreventDefault {
dioxus_native_core::node_ref::AttributeMask::Static(&sorted_str_slice!([
"dioxus-prevent-default"
])),
);
)
.with_listeners();
fn reduce(
&mut self,

View file

@ -1,6 +1,7 @@
use std::{
cell::{Ref, RefCell},
rc::Rc,
sync::{Arc, Mutex, MutexGuard},
};
use dioxus_core::ElementId;
@ -10,7 +11,7 @@ use taffy::{
Taffy,
};
use crate::Dom;
use crate::{layout_to_screen_space, TuiDom};
/// Allows querying the layout of nodes after rendering. It will only provide a correct value after a node is rendered.
/// Provided as a root context for all tui applictions.
@ -45,41 +46,54 @@ use crate::Dom;
/// ```
#[derive(Clone)]
pub struct Query {
pub(crate) rdom: Rc<RefCell<Dom>>,
pub(crate) stretch: Rc<RefCell<Taffy>>,
pub(crate) rdom: Rc<RefCell<TuiDom>>,
pub(crate) stretch: Arc<Mutex<Taffy>>,
}
impl Query {
pub fn get(&self, id: ElementId) -> ElementRef {
ElementRef::new(self.rdom.borrow(), self.stretch.borrow(), id)
ElementRef::new(
self.rdom.borrow(),
self.stretch.lock().expect("taffy lock poisoned"),
id,
)
}
}
pub struct ElementRef<'a> {
inner: Ref<'a, Dom>,
stretch: Ref<'a, Taffy>,
inner: Ref<'a, TuiDom>,
stretch: MutexGuard<'a, Taffy>,
id: ElementId,
}
impl<'a> ElementRef<'a> {
fn new(inner: Ref<'a, Dom>, stretch: Ref<'a, Taffy>, id: ElementId) -> Self {
fn new(inner: Ref<'a, TuiDom>, stretch: MutexGuard<'a, Taffy>, id: ElementId) -> Self {
Self { inner, stretch, id }
}
pub fn size(&self) -> Option<Size<u32>> {
self.layout().map(|l| l.size.map(|v| v as u32))
self.layout().map(|l| l.size.map(|v| v.round() as u32))
}
pub fn pos(&self) -> Option<Point<u32>> {
self.layout().map(|l| Point {
x: l.location.x as u32,
y: l.location.y as u32,
x: l.location.x.round() as u32,
y: l.location.y.round() as u32,
})
}
pub fn layout(&self) -> Option<&Layout> {
self.stretch
pub fn layout(&self) -> Option<Layout> {
let layout = self
.stretch
.layout(self.inner[self.id].state.layout.node.ok()?)
.ok()
.ok();
layout.map(|layout| Layout {
order: layout.order,
size: layout.size.map(|v| layout_to_screen_space(v)),
location: Point {
x: layout_to_screen_space(layout.location.x),
y: layout_to_screen_space(layout.location.y),
},
})
}
}

View file

@ -1,3 +1,4 @@
use dioxus_native_core::tree::TreeView;
use std::io::Stdout;
use taffy::{
geometry::Point,
@ -7,10 +8,11 @@ use taffy::{
use tui::{backend::CrosstermBackend, layout::Rect, style::Color};
use crate::{
layout_to_screen_space,
style::{RinkColor, RinkStyle},
style_attributes::{BorderEdge, BorderStyle},
widget::{RinkBuffer, RinkCell, RinkWidget, WidgetWithContext},
Config, Dom, Node,
Config, TuiDom, TuiNode,
};
const RADIUS_MULTIPLIER: [f32; 2] = [1.0, 0.5];
@ -18,12 +20,12 @@ const RADIUS_MULTIPLIER: [f32; 2] = [1.0, 0.5];
pub(crate) fn render_vnode(
frame: &mut tui::Frame<CrosstermBackend<Stdout>>,
layout: &Taffy,
rdom: &Dom,
node: &Node,
rdom: &TuiDom,
node: &TuiNode,
cfg: Config,
parent_location: Point<f32>,
) {
use dioxus_native_core::real_dom::NodeType;
use dioxus_native_core::node::NodeType;
if let NodeType::Placeholder = &node.node_data.node_type {
return;
@ -35,8 +37,12 @@ pub(crate) fn render_vnode(
location.x += parent_location.x;
location.y += parent_location.y;
let Point { x, y } = location;
let Size { width, height } = size;
let Point { x: fx, y: fy } = location;
let x = layout_to_screen_space(fx).round() as u16;
let y = layout_to_screen_space(fy).round() as u16;
let Size { width, height } = *size;
let width = layout_to_screen_space(fx + width).round() as u16 + x;
let height = layout_to_screen_space(fy + height).round() as u16 - y;
match &node.node_data.node_type {
NodeType::Text { text } => {
@ -61,22 +67,22 @@ pub(crate) fn render_vnode(
text,
style: node.state.style.core,
};
let area = Rect::new(x as u16, y as u16, *width as u16, *height as u16);
let area = Rect::new(x, y, width, height);
// the renderer will panic if a node is rendered out of range even if the size is zero
if area.width > 0 && area.height > 0 {
frame.render_widget(WidgetWithContext::new(label, cfg), area);
}
}
NodeType::Element { children, .. } => {
let area = Rect::new(x as u16, y as u16, *width as u16, *height as u16);
NodeType::Element { .. } => {
let area = Rect::new(x, y, width, height);
// the renderer will panic if a node is rendered out of range even if the size is zero
if area.width > 0 && area.height > 0 {
frame.render_widget(WidgetWithContext::new(node, cfg), area);
}
for c in children {
for c in rdom.children_ids(node.node_data.node_id).unwrap() {
render_vnode(frame, layout, rdom, &rdom[*c], cfg, location);
}
}
@ -84,7 +90,7 @@ pub(crate) fn render_vnode(
}
}
impl RinkWidget for &Node {
impl RinkWidget for &TuiNode {
fn render(self, area: Rect, mut buf: RinkBuffer<'_>) {
use tui::symbols::line::*;

View file

@ -31,8 +31,8 @@
use dioxus_native_core::{
layout_attributes::parse_value,
node::OwnedAttributeView,
node_ref::{AttributeMask, NodeMask, NodeView},
real_dom::OwnedAttributeView,
state::ParentDepState,
};
use dioxus_native_core_macro::sorted_str_slice;
@ -48,12 +48,12 @@ pub struct StyleModifier {
impl ParentDepState for StyleModifier {
type Ctx = ();
type DepState = Self;
type DepState = (Self,);
// todo: seperate each attribute into it's own class
const NODE_MASK: NodeMask =
NodeMask::new_with_attrs(AttributeMask::Static(SORTED_STYLE_ATTRS)).with_element();
fn reduce(&mut self, node: NodeView, parent: Option<&Self::DepState>, _: &Self::Ctx) -> bool {
fn reduce(&mut self, node: NodeView, parent: Option<(&Self,)>, _: &Self::Ctx) -> bool {
let mut new = StyleModifier::default();
if parent.is_some() {
new.core.fg = None;
@ -91,7 +91,7 @@ impl ParentDepState for StyleModifier {
}
// keep the text styling from the parent element
if let Some(parent) = parent {
if let Some((parent,)) = parent {
let mut new_style = new.core.merge(parent.core);
new_style.bg = new.core.bg;
new.core = new_style;

View file

@ -22,8 +22,9 @@ impl<'a> RinkBuffer<'a> {
pub fn set(&mut self, x: u16, y: u16, new: RinkCell) {
let area = self.buf.area();
if x < area.x || x > area.width || y < area.y || y > area.height {
panic!("({x}, {y}) is not in {area:?}");
if x < area.x || x >= area.width + area.x || y < area.y || y >= area.height + area.y {
// panic!("({x}, {y}) is not in {area:?}");
return;
}
let mut cell = self.buf.get_mut(x, y);
cell.bg = convert(self.cfg.rendering_mode, new.bg.blend(cell.bg));