diff --git a/packages/core/src/any_props.rs b/packages/core/src/any_props.rs index 01645e6c8..aeecad2ff 100644 --- a/packages/core/src/any_props.rs +++ b/packages/core/src/any_props.rs @@ -1,7 +1,9 @@ +use std::marker::PhantomData; + use futures_util::Future; use crate::{ - component::{Component, ComponentFn, Dummy, IntoComponent}, + factory::ReturnType, scopes::{Scope, ScopeState}, Element, }; @@ -12,25 +14,30 @@ pub trait AnyProps<'a> { unsafe fn memoize(&self, other: &dyn AnyProps) -> bool; } -pub(crate) struct VComponentProps<'a, P, F: Future> = Dummy<'a>> { - pub render_fn: ComponentFn<'a, P, F>, +pub(crate) struct VComponentProps<'a, P, A, F = Element<'a>> +where + F: ReturnType<'a, A>, +{ + pub render_fn: fn(Scope<'a, P>) -> F, pub memo: unsafe fn(&P, &P) -> bool, pub props: *const P, + pub _marker: PhantomData, } -impl<'a> VComponentProps<'a, ()> { - pub fn new_empty(render_fn: Component<'a, ()>) -> Self { +impl<'a> VComponentProps<'a, (), ()> { + pub fn new_empty(render_fn: fn(Scope) -> Element) -> Self { Self { - render_fn: render_fn.into_component(), + render_fn, memo: <() as PartialEq>::eq, props: std::ptr::null_mut(), + _marker: PhantomData, } } } -impl<'a, P, F: Future>> VComponentProps<'a, P, F> { +impl<'a, P, A, F: ReturnType<'a, A>> VComponentProps<'a, P, A, F> { pub(crate) fn new( - render_fn: ComponentFn<'a, P, F>, + render_fn: fn(Scope<'a, P>) -> F, memo: unsafe fn(&P, &P) -> bool, props: *const P, ) -> Self { @@ -38,11 +45,12 @@ impl<'a, P, F: Future>> VComponentProps<'a, P, F> { render_fn, memo, props, + _marker: PhantomData, } } } -impl<'a, P, F: Future>> AnyProps<'a> for VComponentProps<'a, P, F> { +impl<'a, P, A, F: ReturnType<'a, A>> AnyProps<'a> for VComponentProps<'a, P, A, F> { fn as_ptr(&self) -> *const () { &self.props as *const _ as *const () } diff --git a/packages/core/src/component.rs b/packages/core/src/component.rs index be1837c41..824ae4916 100644 --- a/packages/core/src/component.rs +++ b/packages/core/src/component.rs @@ -9,73 +9,3 @@ use futures_util::Future; use crate::{scopes::Scope, Element}; pub type Component<'a, T = ()> = fn(Scope<'a, T>) -> Element<'a>; - -pub enum ComponentFn<'a, T, F: Future> = Dummy<'a>> { - Sync(fn(Scope<'a, T>) -> Element), - Async(fn(Scope<'a, T>) -> F), -} - -pub trait IntoComponent<'a, T, F: Future> = Dummy<'a>, A = ()> { - fn into_component(self) -> ComponentFn<'a, T, F>; -} - -impl<'a, T> IntoComponent<'a, T, Dummy<'a>> for fn(Scope<'a, T>) -> Element<'a> { - fn into_component(self) -> ComponentFn<'a, T> { - ComponentFn::Sync(self) - } -} - -enum ComponentFn2 { - Sync(fn(Scope) -> Element), -} - -trait AsComponent { - fn as_component(self) -> ComponentFn2; -} - -// impl AsComponent for fn(Scope) -> Element { -// fn as_component(self) -> ComponentFn2 { -// ComponentFn2::Sync(self) -// } -// } - -// impl AsComponent for for<'r> fn(Scope<'r>) -> -// where -// F: Future>, -// { -// fn as_component(self) -> ComponentFn2 { -// ComponentFn2::Sync(self) -// } -// } - -fn takes_f(f: impl AsComponent) {} - -#[test] -fn example() { - fn app(cx: Scope) -> Element { - todo!() - } - - // takes_f(app as fn(Scope) -> Element); -} - -// pub struct AsyncMarker; -// impl<'a, T, F: Future>> IntoComponent<'a, T, F, AsyncMarker> -// for fn(Scope<'a, T>) -> F -// { -// fn into_component(self) -> ComponentFn<'a, T, F> { -// ComponentFn::Async(self) -// } -// } - -pub struct Dummy<'a>(PhantomData<&'a ()>); -impl<'a> Future for Dummy<'a> { - type Output = Element<'a>; - - fn poll( - self: std::pin::Pin<&mut Self>, - _cx: &mut std::task::Context<'_>, - ) -> std::task::Poll { - unreachable!() - } -} diff --git a/packages/core/src/create.rs b/packages/core/src/create.rs index dbb736cc6..ff94aed1c 100644 --- a/packages/core/src/create.rs +++ b/packages/core/src/create.rs @@ -3,7 +3,7 @@ use crate::mutations::Mutation::*; use crate::nodes::VNode; use crate::nodes::{DynamicNode, TemplateNode}; use crate::virtualdom::VirtualDom; -use crate::{AttributeValue, TemplateAttribute}; +use crate::{AttributeValue, ElementId, TemplateAttribute}; impl VirtualDom { /// Create this template and write its mutations @@ -106,7 +106,7 @@ impl VirtualDom { on_stack } - fn create_static_node<'a>( + pub fn create_static_node<'a>( &mut self, mutations: &mut Vec>, template: &'a VNode<'a>, @@ -114,7 +114,7 @@ impl VirtualDom { ) { match *node { // Todo: create the children's template - TemplateNode::Dynamic(_) => mutations.push(CreatePlaceholder), + TemplateNode::Dynamic(_) => mutations.push(CreatePlaceholder { id: ElementId(0) }), TemplateNode::Text(value) => mutations.push(CreateText { value }), TemplateNode::DynamicText { .. } => mutations.push(CreateText { value: "placeholder", @@ -149,7 +149,7 @@ impl VirtualDom { } } - fn create_dynamic_node<'a>( + pub fn create_dynamic_node<'a>( &mut self, mutations: &mut Vec>, template: &'a VNode<'a>, @@ -170,7 +170,7 @@ impl VirtualDom { } DynamicNode::Component { props, .. } => { - let id = self.new_scope(unsafe { std::mem::transmute(*props) }); + let id = self.new_scope(unsafe { std::mem::transmute(props.get()) }); let template = self.run_scope(id); @@ -184,11 +184,14 @@ impl VirtualDom { created } - DynamicNode::Fragment { children } => { - // - children - .iter() - .fold(0, |acc, child| acc + self.create(mutations, child)) + DynamicNode::Fragment(children) => children + .iter() + .fold(0, |acc, child| acc + self.create(mutations, child)), + + DynamicNode::Placeholder(_) => { + let id = self.next_element(template); + mutations.push(CreatePlaceholder { id }); + 1 } } } diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 0fe810a7e..9f8b06dc5 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -1,4 +1,7 @@ +use std::any::Any; + use crate::virtualdom::VirtualDom; +use crate::{Attribute, AttributeValue, TemplateNode}; use crate::any_props::VComponentProps; @@ -13,6 +16,7 @@ use crate::{ nodes::VNode, scopes::{ScopeId, ScopeState}, }; +use fxhash::{FxHashMap, FxHashSet}; use slab::Slab; pub struct DirtyScope { @@ -20,16 +24,574 @@ pub struct DirtyScope { id: ScopeId, } -impl VirtualDom { - fn diff_scope<'a>(&'a mut self, mutations: &mut Vec>, scope: ScopeId) { +impl<'b> VirtualDom { + pub fn diff_scope(&mut self, mutations: &mut Vec>, scope: ScopeId) { let scope_state = &mut self.scopes[scope.0]; } - fn diff_template<'a>( - &'a mut self, - mutations: &mut Vec>, - left: &VNode, - right: &VNode, + pub fn diff_node( + &mut self, + muts: &mut Vec>, + left_template: &'b VNode<'b>, + right_template: &'b VNode<'b>, ) { + if left_template.template.id != right_template.template.id { + // do a light diff of the roots nodes. + return; + } + + for (_idx, (left_attr, right_attr)) in left_template + .dynamic_attrs + .iter() + .zip(right_template.dynamic_attrs.iter()) + .enumerate() + { + debug_assert!(left_attr.name == right_attr.name); + debug_assert!(left_attr.value == right_attr.value); + + // Move over the ID from the old to the new + right_attr + .mounted_element + .set(left_attr.mounted_element.get()); + + if left_attr.value != right_attr.value { + let value = "todo!()"; + muts.push(Mutation::SetAttribute { + id: left_attr.mounted_element.get(), + name: left_attr.name, + value, + }); + } + } + + for (idx, (left_node, right_node)) in left_template + .dynamic_nodes + .iter() + .zip(right_template.dynamic_nodes.iter()) + .enumerate() + { + #[rustfmt::skip] + match (left_node, right_node) { + (DynamicNode::Component { props: lprops, .. }, DynamicNode::Component { is_static , props: rprops, .. }) => { + let left_props = unsafe { &mut *lprops.get()}; + let right_props = unsafe { &mut *rprops.get()}; + + // Ensure these two props are of the same component type + match left_props.as_ptr() == right_props.as_ptr() { + true => { + // + + if *is_static { + let props_are_same = unsafe { left_props.memoize(right_props) }; + + if props_are_same{ + // + } else { + // + } + } else { + + } + + }, + false => todo!(), + } + // + }, + + // Make sure to drop the component properly + (DynamicNode::Component { .. }, right) => { + // remove all the component roots except for the first + // replace the first with the new node + let m = self.create_dynamic_node(muts, right_template, right, idx); + todo!() + }, + + (DynamicNode::Text { id: lid, value: lvalue }, DynamicNode::Text { id: rid, value: rvalue }) => { + rid.set(lid.get()); + if lvalue != rvalue { + muts.push(Mutation::SetText { + id: lid.get(), + value: rvalue, + }); + } + }, + + (DynamicNode::Text { id: lid, .. }, right) => { + let m = self.create_dynamic_node(muts, right_template, right, idx); + muts.push(Mutation::Replace { id: lid.get(), m }); + } + + (DynamicNode::Placeholder(_), DynamicNode::Placeholder(_)) => todo!(), + (DynamicNode::Placeholder(_), _) => todo!(), + + + (DynamicNode::Fragment (l), DynamicNode::Fragment (r)) => { + + + // match (old, new) { + // ([], []) => rp.set(lp.get()), + // ([], _) => { + // // + // todo!() + // }, + // (_, []) => { + // todo!() + // }, + // _ => { + // let new_is_keyed = new[0].key.is_some(); + // let old_is_keyed = old[0].key.is_some(); + + // debug_assert!( + // new.iter().all(|n| n.key.is_some() == new_is_keyed), + // "all siblings must be keyed or all siblings must be non-keyed" + // ); + // debug_assert!( + // old.iter().all(|o| o.key.is_some() == old_is_keyed), + // "all siblings must be keyed or all siblings must be non-keyed" + // ); + + // if new_is_keyed && old_is_keyed { + // self.diff_keyed_children(muts, old, new); + // } else { + // self.diff_non_keyed_children(muts, old, new); + // } + // } + // } + }, + + // Make sure to drop all the fragment children properly + (DynamicNode::Fragment { .. }, right) => todo!(), + }; + } + } + + // Diff children that are not keyed. + // + // The parent must be on the top of the change list stack when entering this + // function: + // + // [... parent] + // + // the change list stack is in the same state when this function returns. + fn diff_non_keyed_children( + &mut self, + muts: &mut Vec>, + old: &'b [VNode<'b>], + new: &'b [VNode<'b>], + ) { + use std::cmp::Ordering; + + // Handled these cases in `diff_children` before calling this function. + debug_assert!(!new.is_empty()); + debug_assert!(!old.is_empty()); + + match old.len().cmp(&new.len()) { + Ordering::Greater => self.remove_nodes(muts, &old[new.len()..]), + Ordering::Less => todo!(), + // Ordering::Less => self.create_and_insert_after(&new[old.len()..], old.last().unwrap()), + Ordering::Equal => {} + } + + for (new, old) in new.iter().zip(old.iter()) { + self.diff_node(muts, old, new); + } + } + + // Diffing "keyed" children. + // + // With keyed children, we care about whether we delete, move, or create nodes + // versus mutate existing nodes in place. Presumably there is some sort of CSS + // transition animation that makes the virtual DOM diffing algorithm + // observable. By specifying keys for nodes, we know which virtual DOM nodes + // must reuse (or not reuse) the same physical DOM nodes. + // + // This is loosely based on Inferno's keyed patching implementation. However, we + // have to modify the algorithm since we are compiling the diff down into change + // list instructions that will be executed later, rather than applying the + // changes to the DOM directly as we compare virtual DOMs. + // + // https://github.com/infernojs/inferno/blob/36fd96/packages/inferno/src/DOM/patching.ts#L530-L739 + // + // The stack is empty upon entry. + fn diff_keyed_children( + &mut self, + muts: &mut Vec>, + old: &'b [VNode<'b>], + new: &'b [VNode<'b>], + ) { + // if cfg!(debug_assertions) { + // let mut keys = fxhash::FxHashSet::default(); + // let mut assert_unique_keys = |children: &'b [VNode<'b>]| { + // keys.clear(); + // for child in children { + // let key = child.key; + // debug_assert!( + // key.is_some(), + // "if any sibling is keyed, all siblings must be keyed" + // ); + // keys.insert(key); + // } + // debug_assert_eq!( + // children.len(), + // keys.len(), + // "keyed siblings must each have a unique key" + // ); + // }; + // assert_unique_keys(old); + // assert_unique_keys(new); + // } + + // // First up, we diff all the nodes with the same key at the beginning of the + // // children. + // // + // // `shared_prefix_count` is the count of how many nodes at the start of + // // `new` and `old` share the same keys. + // let (left_offset, right_offset) = match self.diff_keyed_ends(muts, old, new) { + // Some(count) => count, + // None => return, + // }; + + // // Ok, we now hopefully have a smaller range of children in the middle + // // within which to re-order nodes with the same keys, remove old nodes with + // // now-unused keys, and create new nodes with fresh keys. + + // let old_middle = &old[left_offset..(old.len() - right_offset)]; + // let new_middle = &new[left_offset..(new.len() - right_offset)]; + + // debug_assert!( + // !((old_middle.len() == new_middle.len()) && old_middle.is_empty()), + // "keyed children must have the same number of children" + // ); + + // if new_middle.is_empty() { + // // remove the old elements + // self.remove_nodes(muts, old_middle); + // } else if old_middle.is_empty() { + // // there were no old elements, so just create the new elements + // // we need to find the right "foothold" though - we shouldn't use the "append" at all + // if left_offset == 0 { + // // insert at the beginning of the old list + // let foothold = &old[old.len() - right_offset]; + // self.create_and_insert_before(new_middle, foothold); + // } else if right_offset == 0 { + // // insert at the end the old list + // let foothold = old.last().unwrap(); + // self.create_and_insert_after(new_middle, foothold); + // } else { + // // inserting in the middle + // let foothold = &old[left_offset - 1]; + // self.create_and_insert_after(new_middle, foothold); + // } + // } else { + // self.diff_keyed_middle(muts, old_middle, new_middle); + // } + } + + // /// Diff both ends of the children that share keys. + // /// + // /// Returns a left offset and right offset of that indicates a smaller section to pass onto the middle diffing. + // /// + // /// If there is no offset, then this function returns None and the diffing is complete. + // fn diff_keyed_ends( + // &mut self, + // muts: &mut Vec>, + // old: &'b [VNode<'b>], + // new: &'b [VNode<'b>], + // ) -> Option<(usize, usize)> { + // let mut left_offset = 0; + + // for (old, new) in old.iter().zip(new.iter()) { + // // abort early if we finally run into nodes with different keys + // if old.key != new.key { + // break; + // } + // self.diff_node(muts, old, new); + // left_offset += 1; + // } + + // // If that was all of the old children, then create and append the remaining + // // new children and we're finished. + // if left_offset == old.len() { + // self.create_and_insert_after(&new[left_offset..], old.last().unwrap()); + // return None; + // } + + // // And if that was all of the new children, then remove all of the remaining + // // old children and we're finished. + // if left_offset == new.len() { + // self.remove_nodes(muts, &old[left_offset..]); + // return None; + // } + + // // if the shared prefix is less than either length, then we need to walk backwards + // let mut right_offset = 0; + // for (old, new) in old.iter().rev().zip(new.iter().rev()) { + // // abort early if we finally run into nodes with different keys + // if old.key != new.key { + // break; + // } + // self.diff_node(muts, old, new); + // right_offset += 1; + // } + + // Some((left_offset, right_offset)) + // } + + // // The most-general, expensive code path for keyed children diffing. + // // + // // We find the longest subsequence within `old` of children that are relatively + // // ordered the same way in `new` (via finding a longest-increasing-subsequence + // // of the old child's index within `new`). The children that are elements of + // // this subsequence will remain in place, minimizing the number of DOM moves we + // // will have to do. + // // + // // Upon entry to this function, the change list stack must be empty. + // // + // // This function will load the appropriate nodes onto the stack and do diffing in place. + // // + // // Upon exit from this function, it will be restored to that same self. + // #[allow(clippy::too_many_lines)] + // fn diff_keyed_middle( + // &mut self, + // muts: &mut Vec>, + // old: &'b [VNode<'b>], + // new: &'b [VNode<'b>], + // ) { + // /* + // 1. Map the old keys into a numerical ordering based on indices. + // 2. Create a map of old key to its index + // 3. Map each new key to the old key, carrying over the old index. + // - IE if we have ABCD becomes BACD, our sequence would be 1,0,2,3 + // - if we have ABCD to ABDE, our sequence would be 0,1,3,MAX because E doesn't exist + + // now, we should have a list of integers that indicates where in the old list the new items map to. + + // 4. Compute the LIS of this list + // - this indicates the longest list of new children that won't need to be moved. + + // 5. Identify which nodes need to be removed + // 6. Identify which nodes will need to be diffed + + // 7. Going along each item in the new list, create it and insert it before the next closest item in the LIS. + // - if the item already existed, just move it to the right place. + + // 8. Finally, generate instructions to remove any old children. + // 9. Generate instructions to finally diff children that are the same between both + // */ + // // 0. Debug sanity checks + // // Should have already diffed the shared-key prefixes and suffixes. + // debug_assert_ne!(new.first().map(|i| i.key), old.first().map(|i| i.key)); + // debug_assert_ne!(new.last().map(|i| i.key), old.last().map(|i| i.key)); + + // // 1. Map the old keys into a numerical ordering based on indices. + // // 2. Create a map of old key to its index + // // IE if the keys were A B C, then we would have (A, 1) (B, 2) (C, 3). + // let old_key_to_old_index = old + // .iter() + // .enumerate() + // .map(|(i, o)| (o.key.unwrap(), i)) + // .collect::>(); + + // let mut shared_keys = FxHashSet::default(); + + // // 3. Map each new key to the old key, carrying over the old index. + // let new_index_to_old_index = new + // .iter() + // .map(|node| { + // let key = node.key.unwrap(); + // if let Some(&index) = old_key_to_old_index.get(&key) { + // shared_keys.insert(key); + // index + // } else { + // u32::MAX as usize + // } + // }) + // .collect::>(); + + // // If none of the old keys are reused by the new children, then we remove all the remaining old children and + // // create the new children afresh. + // if shared_keys.is_empty() { + // if let Some(first_old) = old.get(0) { + // self.remove_nodes(muts, &old[1..]); + // let nodes_created = self.create_children(new); + // self.replace_inner(first_old, nodes_created); + // } else { + // // I think this is wrong - why are we appending? + // // only valid of the if there are no trailing elements + // self.create_and_append_children(new); + // } + // return; + // } + + // // remove any old children that are not shared + // // todo: make this an iterator + // for child in old { + // let key = child.key.unwrap(); + // if !shared_keys.contains(&key) { + // todo!("remove node"); + // // self.remove_nodes(muts, [child]); + // } + // } + + // // 4. Compute the LIS of this list + // let mut lis_sequence = Vec::default(); + // lis_sequence.reserve(new_index_to_old_index.len()); + + // let mut predecessors = vec![0; new_index_to_old_index.len()]; + // let mut starts = vec![0; new_index_to_old_index.len()]; + + // longest_increasing_subsequence::lis_with( + // &new_index_to_old_index, + // &mut lis_sequence, + // |a, b| a < b, + // &mut predecessors, + // &mut starts, + // ); + + // // the lis comes out backwards, I think. can't quite tell. + // lis_sequence.sort_unstable(); + + // // if a new node gets u32 max and is at the end, then it might be part of our LIS (because u32 max is a valid LIS) + // if lis_sequence.last().map(|f| new_index_to_old_index[*f]) == Some(u32::MAX as usize) { + // lis_sequence.pop(); + // } + + // for idx in &lis_sequence { + // self.diff_node(muts, &old[new_index_to_old_index[*idx]], &new[*idx]); + // } + + // let mut nodes_created = 0; + + // // add mount instruction for the first items not covered by the lis + // let last = *lis_sequence.last().unwrap(); + // if last < (new.len() - 1) { + // for (idx, new_node) in new[(last + 1)..].iter().enumerate() { + // let new_idx = idx + last + 1; + // let old_index = new_index_to_old_index[new_idx]; + // if old_index == u32::MAX as usize { + // nodes_created += self.create(muts, new_node); + // } else { + // self.diff_node(muts, &old[old_index], new_node); + // nodes_created += self.push_all_real_nodes(new_node); + // } + // } + + // self.mutations.insert_after( + // self.find_last_element(&new[last]).unwrap(), + // nodes_created as u32, + // ); + // nodes_created = 0; + // } + + // // for each spacing, generate a mount instruction + // let mut lis_iter = lis_sequence.iter().rev(); + // let mut last = *lis_iter.next().unwrap(); + // for next in lis_iter { + // if last - next > 1 { + // for (idx, new_node) in new[(next + 1)..last].iter().enumerate() { + // let new_idx = idx + next + 1; + // let old_index = new_index_to_old_index[new_idx]; + // if old_index == u32::MAX as usize { + // nodes_created += self.create(muts, new_node); + // } else { + // self.diff_node(muts, &old[old_index], new_node); + // nodes_created += self.push_all_real_nodes(new_node); + // } + // } + + // self.mutations.insert_before( + // self.find_first_element(&new[last]).unwrap(), + // nodes_created as u32, + // ); + + // nodes_created = 0; + // } + // last = *next; + // } + + // // add mount instruction for the last items not covered by the lis + // let first_lis = *lis_sequence.first().unwrap(); + // if first_lis > 0 { + // for (idx, new_node) in new[..first_lis].iter().enumerate() { + // let old_index = new_index_to_old_index[idx]; + // if old_index == u32::MAX as usize { + // nodes_created += self.create_node(new_node); + // } else { + // self.diff_node(muts, &old[old_index], new_node); + // nodes_created += self.push_all_real_nodes(new_node); + // } + // } + + // self.mutations.insert_before( + // self.find_first_element(&new[first_lis]).unwrap(), + // nodes_created as u32, + // ); + // } + // } + + /// Remove these nodes from the dom + /// Wont generate mutations for the inner nodes + fn remove_nodes(&mut self, muts: &mut Vec>, nodes: &'b [VNode<'b>]) { + // } } + +// /// Lightly diff the two templates and apply their edits to the dom +// fn light_diff_template_roots( +// &'a mut self, +// mutations: &mut Vec>, +// left: &VNode, +// right: &VNode, +// ) { +// match right.template.roots.len().cmp(&left.template.roots.len()) { +// std::cmp::Ordering::Less => { +// // remove the old nodes at the end +// } +// std::cmp::Ordering::Greater => { +// // add the extra nodes. +// } +// std::cmp::Ordering::Equal => {} +// } + +// for (left_node, right_node) in left.template.roots.iter().zip(right.template.roots.iter()) { +// if let (TemplateNode::Dynamic(lidx), TemplateNode::Dynamic(ridx)) = +// (left_node, right_node) +// { +// let left_node = &left.dynamic_nodes[*lidx]; +// let right_node = &right.dynamic_nodes[*ridx]; + +// // match (left_node, right_node) { +// // ( +// // DynamicNode::Component { +// // name, +// // can_memoize, +// // props, +// // }, +// // DynamicNode::Component { +// // name, +// // can_memoize, +// // props, +// // }, +// // ) => todo!(), +// // ( +// // DynamicNode::Component { +// // name, +// // can_memoize, +// // props, +// // }, +// // DynamicNode::Fragment { children }, +// // ) => todo!(), +// // ( +// // DynamicNode::Fragment { children }, +// // DynamicNode::Component { +// // name, +// // can_memoize, +// // props, +// // }, +// // ) => todo!(), +// // _ => {} +// // } +// } +// } +// } diff --git a/packages/core/src/factory.rs b/packages/core/src/factory.rs index badd7a220..8f53b9503 100644 --- a/packages/core/src/factory.rs +++ b/packages/core/src/factory.rs @@ -6,7 +6,6 @@ use futures_util::Future; use crate::{ any_props::{AnyProps, VComponentProps}, arena::ElementId, - component::IntoComponent, innerlude::DynamicNode, Attribute, AttributeValue, Element, LazyNodes, Properties, Scope, ScopeState, VNode, }; @@ -47,8 +46,9 @@ impl ScopeState { bump_vec.push(item.into_dynamic_node(self)); } - DynamicNode::Fragment { - children: bump_vec.into_bump_slice(), + match bump_vec.len() { + 0 => DynamicNode::Placeholder(Cell::new(ElementId(0))), + _ => DynamicNode::Fragment(bump_vec.into_bump_slice()), } } @@ -70,9 +70,9 @@ impl ScopeState { } /// Create a new [`VNode::Component`] - pub fn component<'a, P, F: Future>>( + pub fn component<'a, P, A, F: ReturnType<'a, A>>( &'a self, - component: impl IntoComponent<'a, P, F>, + component: fn(Scope<'a, P>) -> F, props: P, fn_name: &'static str, ) -> DynamicNode<'a> @@ -80,7 +80,7 @@ impl ScopeState { P: Properties + 'a, { let props = self.bump().alloc(props); - let as_component = component.into_component(); + let as_component = component; let vcomp = VComponentProps::new(as_component, P::memoize, props); let as_dyn = self.bump().alloc(vcomp) as &mut dyn AnyProps; let detached_dyn: *mut dyn AnyProps = unsafe { std::mem::transmute(as_dyn) }; @@ -94,12 +94,30 @@ impl ScopeState { DynamicNode::Component { name: fn_name, - can_memoize: P::IS_STATIC, - props: detached_dyn, + is_static: P::IS_STATIC, + props: Cell::new(detached_dyn), } } } +pub trait ReturnType<'a, A = ()> {} +impl<'a> ReturnType<'a> for Element<'a> {} + +pub struct AsyncMarker; +impl<'a, F> ReturnType<'a, AsyncMarker> for F where F: Future> + 'a {} + +#[test] +fn takes_it() { + fn demo(cx: Scope) -> Element { + todo!() + } +} + +enum RenderReturn<'a> { + Sync(Element<'a>), + Async(*mut dyn Future>), +} + pub trait IntoVnode<'a, A = ()> { fn into_dynamic_node(self, cx: &'a ScopeState) -> VNode<'a>; } diff --git a/packages/core/src/garbage.rs b/packages/core/src/garbage.rs index 30af43f31..8203fe1c2 100644 --- a/packages/core/src/garbage.rs +++ b/packages/core/src/garbage.rs @@ -19,7 +19,8 @@ impl VirtualDom { todo!() } - DynamicNode::Fragment { children } => {} + DynamicNode::Fragment(children) => {} + DynamicNode::Placeholder(_) => todo!(), } } } diff --git a/packages/core/src/mutations.rs b/packages/core/src/mutations.rs index 17c9e9395..9d991fcc5 100644 --- a/packages/core/src/mutations.rs +++ b/packages/core/src/mutations.rs @@ -46,6 +46,7 @@ pub enum Mutation<'a> { // Take the current element and replace it with the element with the given id. Replace { id: ElementId, + m: usize, }, CreateElement { @@ -58,7 +59,9 @@ pub enum Mutation<'a> { value: &'a str, }, - CreatePlaceholder, + CreatePlaceholder { + id: ElementId, + }, AppendChildren { m: usize, diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 9eca083cf..5fa185be9 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -1,6 +1,6 @@ use crate::{any_props::AnyProps, arena::ElementId}; use std::{ - any::Any, + any::{Any, TypeId}, cell::{Cell, RefCell}, hash::Hasher, }; @@ -77,8 +77,8 @@ pub enum DynamicNode<'a> { // IE in caps or with underscores Component { name: &'static str, - can_memoize: bool, - props: *mut dyn AnyProps<'a>, + is_static: bool, + props: Cell<*mut dyn AnyProps<'a>>, }, // Comes in with string interpolation or from format_args, include_str, etc @@ -88,9 +88,9 @@ pub enum DynamicNode<'a> { }, // Anything that's coming in as an iterator - Fragment { - children: &'a [VNode<'a>], - }, + Fragment(&'a [VNode<'a>]), + + Placeholder(Cell), } #[derive(Debug)] @@ -122,26 +122,71 @@ pub enum AttributeValue<'a> { None, } +impl<'a> std::fmt::Debug for AttributeValue<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Text(arg0) => f.debug_tuple("Text").field(arg0).finish(), + Self::Float(arg0) => f.debug_tuple("Float").field(arg0).finish(), + Self::Int(arg0) => f.debug_tuple("Int").field(arg0).finish(), + Self::Bool(arg0) => f.debug_tuple("Bool").field(arg0).finish(), + Self::Listener(arg0) => f.debug_tuple("Listener").finish(), + Self::Any(arg0) => f.debug_tuple("Any").finish(), + Self::None => write!(f, "None"), + } + } +} + +impl<'a> PartialEq for AttributeValue<'a> { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::Text(l0), Self::Text(r0)) => l0 == r0, + (Self::Float(l0), Self::Float(r0)) => l0 == r0, + (Self::Int(l0), Self::Int(r0)) => l0 == r0, + (Self::Bool(l0), Self::Bool(r0)) => l0 == r0, + (Self::Listener(l0), Self::Listener(r0)) => true, + (Self::Any(l0), Self::Any(r0)) => l0.any_cmp(*r0), + _ => core::mem::discriminant(self) == core::mem::discriminant(other), + } + } +} + impl<'a> AttributeValue<'a> { + pub fn matches_type(&self, other: &'a AttributeValue<'a>) -> bool { + match (self, other) { + (Self::Text(_), Self::Text(_)) => true, + (Self::Float(_), Self::Float(_)) => true, + (Self::Int(_), Self::Int(_)) => true, + (Self::Bool(_), Self::Bool(_)) => true, + (Self::Listener(_), Self::Listener(_)) => true, + (Self::Any(_), Self::Any(_)) => true, + _ => return false, + } + } + fn is_listener(&self) -> bool { matches!(self, AttributeValue::Listener(_)) } } pub trait AnyValue { - fn any_cmp(&self, other: &dyn Any) -> bool; + fn any_cmp(&self, other: &dyn AnyValue) -> bool; + fn our_typeid(&self) -> TypeId; } impl AnyValue for T where T: PartialEq + Any, { - fn any_cmp(&self, other: &dyn Any) -> bool { - if self.type_id() != other.type_id() { + fn any_cmp(&self, other: &dyn AnyValue) -> bool { + if self.type_id() != other.our_typeid() { return false; } self == unsafe { &*(other as *const _ as *const T) } } + + fn our_typeid(&self) -> TypeId { + self.type_id() + } } #[test] diff --git a/packages/core/src/properties.rs b/packages/core/src/properties.rs index 569648a6a..44e58c9d0 100644 --- a/packages/core/src/properties.rs +++ b/packages/core/src/properties.rs @@ -165,6 +165,6 @@ impl EmptyBuilder { /// This utility function launches the builder method so rsx! and html! macros can use the typed-builder pattern /// to initialize a component's props. -pub fn fc_to_builder<'a, T: Properties + 'a>(_: fn(Scope<'a, T>) -> Element) -> T::Builder { +pub fn fc_to_builder<'a, A, T: Properties + 'a>(_: fn(Scope<'a, T>) -> A) -> T::Builder { T::builder() } diff --git a/packages/core/src/virtualdom.rs b/packages/core/src/virtualdom.rs index 6b9f3fe4d..314115b24 100644 --- a/packages/core/src/virtualdom.rs +++ b/packages/core/src/virtualdom.rs @@ -10,6 +10,7 @@ use crate::{ arena::ElementId, scopes::{ScopeId, ScopeState}, }; +use crate::{Element, Scope}; use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender}; use slab::Slab; use std::collections::{BTreeSet, HashMap}; @@ -27,7 +28,7 @@ pub struct VirtualDom { } impl VirtualDom { - pub fn new<'a>(app: Component<'a, ()>) -> Self { + pub fn new(app: fn(Scope) -> Element) -> Self { let (sender, receiver) = futures_channel::mpsc::unbounded(); let mut res = Self { @@ -43,7 +44,7 @@ impl VirtualDom { }; let props = Box::into_raw(Box::new(VComponentProps::new_empty(app))); - let props: *mut VComponentProps<()> = unsafe { std::mem::transmute(props) }; + let props: *mut VComponentProps<(), ()> = unsafe { std::mem::transmute(props) }; let root = res.new_scope(props); diff --git a/packages/dioxus/tests/rsx_syntax.rs b/packages/dioxus/tests/rsx_syntax.rs index 8f0846df9..fd86a03c3 100644 --- a/packages/dioxus/tests/rsx_syntax.rs +++ b/packages/dioxus/tests/rsx_syntax.rs @@ -24,28 +24,20 @@ fn basic_syntax_is_a_template(cx: Scope) -> Element { } fn basic_template(cx: Scope) -> Element { - let val = 123; - - cx.component(basic_child, (), "fn_name"); - - todo!() - // cx.render(rsx! { - // div { class: "{val}", class: "{val}", class: "{val}", class: "{val}", - // (0..2).map(|i| rsx! { div { "asd {i}" } }) - // basic_child { } - // } - // }) + cx.render(rsx! { + div { + basic_child { } + async_child { } + } + }) } -/// A beautiful component fn basic_child(cx: Scope) -> Element { todo!() } -async fn async_component(cx: Scope<'_>) -> Element { - cx.render(rsx! { - div { class: "asd" } - }) +async fn async_child(cx: Scope<'_>) -> Element { + todo!() } #[test] @@ -63,4 +55,9 @@ fn basic_prints() { // let renderer = dioxus_edit_stream::Mutations::default(); // // dbg!(renderer.edits); + + // takes_it(basic_child); } + +// fn takes_it(f: fn(Scope) -> Element) {} +// fn takes_it(f: fn(Scope) -> Element) {} diff --git a/packages/edit-stream/Cargo.toml b/packages/edit-stream/Cargo.toml deleted file mode 100644 index bed3e6aef..000000000 --- a/packages/edit-stream/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "dioxus-edit-stream" -version = "0.0.0" -edition = "2018" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -dioxus-core = { path = "../core" } diff --git a/packages/edit-stream/src/lib.rs b/packages/edit-stream/src/lib.rs deleted file mode 100644 index 45c8ca3c5..000000000 --- a/packages/edit-stream/src/lib.rs +++ /dev/null @@ -1,358 +0,0 @@ -use dioxus_core::*; - -/// ## Mutations -/// -/// This method returns "mutations" - IE the necessary changes to get the RealDOM to match the VirtualDOM. It also -/// includes a list of NodeRefs that need to be applied and effects that need to be triggered after the RealDOM has -/// applied the edits. -/// -/// Mutations are the only link between the RealDOM and the VirtualDOM. -#[derive(Default)] -pub struct Mutations<'a> { - /// The list of edits that need to be applied for the RealDOM to match the VirtualDOM. - pub edits: Vec>, - - /// The list of Scopes that were diffed, created, and removed during the Diff process. - pub dirty_scopes: Vec, -} - -/// A `DomEdit` represents a serialized form of the VirtualDom's trait-based API. This allows streaming edits across the -/// network or through FFI boundaries. -#[derive(Debug, PartialEq)] -#[cfg_attr( - feature = "serialize", - derive(serde::Serialize, serde::Deserialize), - serde(tag = "type") -)] -pub enum DomEdit<'bump> { - /// Push the given root node onto our stack. - PushRoot { - /// The ID of the root node to push. - root: ElementId, - }, - - /// Pop the topmost node from our stack and append them to the node - /// at the top of the stack. - AppendChildren { - /// How many nodes should be popped from the stack. - /// The node remaining on the stack will be the target for the append. - many: u32, - }, - - /// Replace a given (single) node with a handful of nodes currently on the stack. - ReplaceWith { - /// The ID of the node to be replaced. - root: ElementId, - - /// How many nodes should be popped from the stack to replace the target node. - m: u32, - }, - - /// Insert a number of nodes after a given node. - InsertAfter { - /// The ID of the node to insert after. - root: ElementId, - - /// How many nodes should be popped from the stack to insert after the target node. - n: u32, - }, - - /// Insert a number of nodes before a given node. - InsertBefore { - /// The ID of the node to insert before. - root: ElementId, - - /// How many nodes should be popped from the stack to insert before the target node. - n: u32, - }, - - /// Remove a particular node from the DOM - Remove { - /// The ID of the node to remove. - root: ElementId, - }, - - /// Create a new purely-text node - CreateTextNode { - /// The ID the new node should have. - root: ElementId, - - /// The textcontent of the node - text: &'bump str, - }, - - /// Create a new purely-element node - CreateElement { - /// The ID the new node should have. - root: ElementId, - - /// The tagname of the node - tag: &'bump str, - }, - - /// Create a new purely-comment node with a given namespace - CreateElementNs { - /// The ID the new node should have. - root: ElementId, - - /// The namespace of the node - tag: &'bump str, - - /// The namespace of the node (like `SVG`) - ns: &'static str, - }, - - /// Create a new placeholder node. - /// In most implementations, this will either be a hidden div or a comment node. - CreatePlaceholder { - /// The ID the new node should have. - root: ElementId, - }, - - /// Create a new Event Listener. - NewEventListener { - /// The name of the event to listen for. - event_name: &'static str, - - /// The ID of the node to attach the listener to. - scope: ScopeId, - - /// The ID of the node to attach the listener to. - root: ElementId, - }, - - /// Remove an existing Event Listener. - RemoveEventListener { - /// The ID of the node to remove. - root: ElementId, - - /// The name of the event to remove. - event: &'static str, - }, - - /// Set the textcontent of a node. - SetText { - /// The ID of the node to set the textcontent of. - root: ElementId, - - /// The textcontent of the node - text: &'bump str, - }, - - /// Set the value of a node's attribute. - SetAttribute { - /// The ID of the node to set the attribute of. - root: ElementId, - - /// The name of the attribute to set. - field: &'static str, - - /// The value of the attribute. - value: AttributeValue<'bump>, - - // value: &'bump str, - /// The (optional) namespace of the attribute. - /// For instance, "style" is in the "style" namespace. - ns: Option<&'bump str>, - }, - - /// Remove an attribute from a node. - RemoveAttribute { - /// The ID of the node to remove. - root: ElementId, - - /// The name of the attribute to remove. - name: &'static str, - - /// The namespace of the attribute. - ns: Option<&'bump str>, - }, - - /// Manually pop a root node from the stack. - PopRoot { - /// The amount of nodes to pop - count: u32, - }, - - /// Remove all the children of an element - RemoveChildren { - /// The root - root: ElementId, - }, - - /* - - Template stuff - - - load into scratch space - - dump nodes into stack - - assign ids of nodes in template - - */ - /// Create a template using the nodes on the stack - Save { - /// The ID of the template - name: &'static str, - - /// The amount of nodes to pop from the stack into the template - num_children: u32, - }, - - /// Load the template into a scratch space on the stack - /// - /// The template body now lives on the stack, but needs to be finished before its nodes can be appended to the DOM. - Load { - /// The ID of the template - name: &'static str, - - id: u32, - }, - - AssignId { - index: &'static str, - id: ElementId, - }, - - ReplaceDescendant { - index: &'static str, - m: u32, - }, -} - -use DomEdit::*; - -impl<'a> dioxus_core::Renderer<'a> for Mutations<'a> { - // Navigation - fn push_root(&mut self, root: ElementId) { - self.edits.push(PushRoot { root }); - } - - // Navigation - fn pop_root(&mut self) { - self.edits.push(PopRoot { count: 1 }); - } - - fn replace_with(&mut self, root: ElementId, m: u32) { - self.edits.push(ReplaceWith { m, root }); - } - - fn replace_descendant(&mut self, descendent: &'static [u8], m: u32) { - self.edits.push(ReplaceDescendant { - // serializing is just hijacking ascii - index: unsafe { std::str::from_utf8_unchecked(descendent) }, - m, - }); - } - - fn insert_after(&mut self, root: ElementId, n: u32) { - self.edits.push(InsertAfter { n, root }); - } - - fn insert_before(&mut self, root: ElementId, n: u32) { - self.edits.push(InsertBefore { n, root }); - } - - fn append_children(&mut self, n: u32) { - self.edits.push(AppendChildren { many: n }); - } - - // Create - fn create_text_node(&mut self, text: &'a str, root: ElementId) { - self.edits.push(CreateTextNode { text, root }); - } - - fn create_element(&mut self, tag: &'static str, ns: Option<&'static str>, id: ElementId) { - match ns { - Some(ns) => self.edits.push(CreateElementNs { root: id, ns, tag }), - None => self.edits.push(CreateElement { root: id, tag }), - } - } - - // placeholders are nodes that don't get rendered but still exist as an "anchor" in the real dom - fn create_placeholder(&mut self, id: ElementId) { - self.edits.push(CreatePlaceholder { root: id }); - } - - fn assign_id(&mut self, descendent: &'static [u8], id: ElementId) { - self.edits.push(AssignId { - index: unsafe { std::str::from_utf8_unchecked(descendent) }, - id, - }); - } - - // Remove Nodes from the dom - fn remove(&mut self, root: ElementId) { - self.edits.push(Remove { root }); - } - - fn remove_attribute(&mut self, attribute: &Attribute, root: ElementId) { - self.edits.push(RemoveAttribute { - name: attribute.name, - ns: attribute.namespace, - root, - }); - } - - // events - fn new_event_listener(&mut self, listener: &Listener, scope: ScopeId) { - let Listener { - event, - mounted_node, - .. - } = listener; - - let element_id = mounted_node.get(); - - self.edits.push(NewEventListener { - scope, - event_name: event, - root: element_id, - }); - } - - fn remove_event_listener(&mut self, event: &'static str, root: ElementId) { - self.edits.push(RemoveEventListener { event, root }); - } - - // modify - fn set_text(&mut self, text: &'a str, root: ElementId) { - self.edits.push(SetText { text, root }); - } - - fn save(&mut self, id: &'static str, num: u32) { - self.edits.push(Save { - name: id, - num_children: num, - }); - } - - fn load(&mut self, id: &'static str, index: u32) { - self.edits.push(Load { - name: id, - id: index, - }); - } - - fn mark_dirty_scope(&mut self, scope: ScopeId) { - self.dirty_scopes.push(scope); - } - - fn set_attribute( - &mut self, - name: &'static str, - value: AttributeValue<'a>, - namespace: Option<&'a str>, - root: ElementId, - ) { - self.edits.push(SetAttribute { - field: name, - value: value.clone(), - ns: namespace, - root, - }); - } - - fn remove_children(&mut self, element: ElementId) { - todo!() - } -}