diff --git a/.vscode/spellright.dict b/.vscode/spellright.dict index db3e9da9c..efbfd1aa3 100644 --- a/.vscode/spellright.dict +++ b/.vscode/spellright.dict @@ -45,3 +45,4 @@ namespacing impl destructured linting +lodash diff --git a/README.md b/README.md index 897558924..7210abde4 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,7 @@ TypeScript is a great addition to JavaScript, but comes with a lot of tweaking f - inline built-in unit/integration testing - best-in-class error handling - simple and fast build system +- powerful standard library (no need for lodash or underscore) - include_str! for integrating html/css/svg templates directly - various macros (`html!`, `rsx!`) for fast template iteration diff --git a/packages/core/.vscode/settings.json b/packages/core/.vscode/settings.json index 2f418701b..80f51cff8 100644 --- a/packages/core/.vscode/settings.json +++ b/packages/core/.vscode/settings.json @@ -1,3 +1,3 @@ { - "rust-analyzer.inlayHints.enable": true + "rust-analyzer.inlayHints.enable": false } \ No newline at end of file diff --git a/packages/core/examples/fragment.rs b/packages/core/examples/fragment.rs index 92d3cfd7f..479a2607d 100644 --- a/packages/core/examples/fragment.rs +++ b/packages/core/examples/fragment.rs @@ -1,17 +1,18 @@ use dioxus_core::prelude::*; fn main() { - let g = rsx! { - Fragment { - // div {} - // div {} - // div {} - // div {} - // div {} - // div {} - // div {} - // div {} - // div {} - } - }; + + // let g = rsx! { + // Fragment { + // // div {} + // // div {} + // // div {} + // // div {} + // // div {} + // // div {} + // // div {} + // // div {} + // // div {} + // } + // }; } diff --git a/packages/core/src/arena.rs b/packages/core/src/arena.rs index eeaec8b9d..bca37a866 100644 --- a/packages/core/src/arena.rs +++ b/packages/core/src/arena.rs @@ -1,14 +1,13 @@ use std::{ cell::{RefCell, UnsafeCell}, collections::HashMap, - sync::Arc, + rc::Rc, }; use generational_arena::Arena; use crate::innerlude::*; -type Rc = Arc; #[derive(Clone)] pub struct ScopeArena(Rc>); diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 75df8db26..d13793d6f 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -1007,138 +1007,3 @@ enum KeyedPrefixResult { // the beginning of `new` and `old` we already processed. MoreWorkToDo(usize), } - -mod support { - - // // Get or create the template. - // // - // // Upon entering this function the change list stack may be in any shape: - // // - // // [...] - // // - // // When this function returns, it leaves a freshly cloned copy of the template - // // on the top of the change list stack: - // // - // // [... template] - // #[inline] - // pub fn get_or_create_template<'a>(// cached_set: &'a CachedSet, - // // change_list: &mut ChangeListBuilder, - // // registry: &mut EventsRegistry, - // // cached_roots: &mut FxHashSet, - // // template_id: CacheId, - // ) -> (&'a Node<'a>, bool) { - // let (template, template_template) = cached_set.get(template_id); - // debug_assert!( - // template_template.is_none(), - // "templates should not be templated themselves" - // ); - - // // If we haven't already created and saved the physical DOM subtree for this - // // template, do that now. - // if change_list.has_template(template_id) { - // // Clone the template and push it onto the stack. - // // - // // [...] - // change_list.push_template(template_id); - // // [... template] - - // (template, true) - // } else { - // // [...] - // create(cached_set, change_list, registry, template, cached_roots); - // // [... template] - // change_list.save_template(template_id); - // // [... template] - - // (template, false) - // } - // } - - // pub fn create_and_replace( - // cached_set: &CachedSet, - // change_list: &mut ChangeListBuilder, - // registry: &mut EventsRegistry, - // new_template: Option, - // old: &Node, - // new: &Node, - // cached_roots: &mut FxHashSet, - // ) { - // debug_assert!(change_list.traversal_is_committed()); - - // if let Some(template_id) = new_template { - // let (template, needs_listeners) = get_or_create_template( - // cached_set, - // change_list, - // registry, - // cached_roots, - // template_id, - // ); - // change_list.replace_with(); - - // let mut old_forcing = None; - // if needs_listeners { - // old_forcing = Some(change_list.push_force_new_listeners()); - // } - - // diff( - // cached_set, - // change_list, - // registry, - // template, - // new, - // cached_roots, - // ); - - // if let Some(old) = old_forcing { - // change_list.pop_force_new_listeners(old); - // } - - // change_list.commit_traversal(); - // } else { - // create(cached_set, change_list, registry, new, cached_roots); - // change_list.replace_with(); - // } - // registry.remove_subtree(old); - // } - - // pub fn create_with_template( - // cached_set: &CachedSet, - // change_list: &mut ChangeListBuilder, - // registry: &mut EventsRegistry, - // template_id: CacheId, - // node: &Node, - // cached_roots: &mut FxHashSet, - // ) { - // debug_assert!(change_list.traversal_is_committed()); - - // // [...] - // let (template, needs_listeners) = - // get_or_create_template(cached_set, change_list, registry, cached_roots, template_id); - // // [... template] - - // // Now diff the node with its template. - // // - // // We must force adding new listeners instead of updating existing ones, - // // since listeners don't get cloned in `cloneNode`. - // let mut old_forcing = None; - // if needs_listeners { - // old_forcing = Some(change_list.push_force_new_listeners()); - // } - - // diff( - // cached_set, - // change_list, - // registry, - // template, - // node, - // cached_roots, - // ); - - // if let Some(old) = old_forcing { - // change_list.pop_force_new_listeners(old); - // } - - // // Make sure that we come back up to the level we were at originally. - // change_list.commit_traversal(); - // } -} diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 024d6ae93..388e8ae74 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -6,6 +6,7 @@ use crate::{ events::VirtualEvent, innerlude::{Context, Properties, Scope, ScopeIdx, FC}, + nodebuilder::text3, }; use bumpalo::Bump; use std::{ @@ -28,6 +29,8 @@ pub enum VNode<'src> { /// A text node (node type `TEXT_NODE`). Text(VText<'src>), + /// A fragment is a "virtual position" in the DOM + /// Fragments may have children and keys Fragment(&'src VFragment<'src>), /// A "suspended component" @@ -86,8 +89,8 @@ impl<'a> VNode<'a> { VNode::Text(VText { text }) } - pub fn text_args(b: &'a Bump, f: Arguments) -> VNode<'a> { - todo!() + pub fn text_args(bump: &'a Bump, args: Arguments) -> VNode<'a> { + text3(bump, args) } #[inline] @@ -95,11 +98,11 @@ impl<'a> VNode<'a> { match &self { VNode::Text(_) => NodeKey::NONE, VNode::Element(e) => e.key, - VNode::Suspended => { - todo!() - } VNode::Fragment(frag) => frag.key, VNode::Component(c) => c.key, + + // todo suspend should be allowed to have keys + VNode::Suspended => NodeKey::NONE, } } } @@ -254,9 +257,6 @@ pub struct VComponent<'src> { _p: PhantomData<&'src ()>, } -unsafe fn transmogrify<'a>(p: impl Properties + 'a) -> impl Properties + 'static { - todo!() -} impl<'a> VComponent<'a> { // use the type parameter on props creation and move it into a portable context // this lets us keep scope generic *and* downcast its props when we need to: diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 7475038d5..b84e79101 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -154,10 +154,11 @@ impl VirtualDom { // Make the first scope // We don't run the component though, so renderers will need to call "rebuild" when they initialize their DOM let link = components.clone(); + let event_channel = Rc::new(move || {}); let base_scope = components .with(|arena| { arena.insert_with(move |myidx| { - Scope::new(caller_ref, myidx, None, 0, _event_queue, link) + Scope::new(caller_ref, myidx, None, 0, event_channel, link, &[]) }) }) .unwrap(); @@ -172,6 +173,7 @@ impl VirtualDom { } /// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom rom scratch + /// Currently this doesn't do what we want it to do pub fn rebuild<'s>(&'s mut self) -> Result> { let mut diff_machine = DiffMachine::new(); @@ -180,8 +182,9 @@ impl VirtualDom { // Instead, this is done on top-level component let base = self.components.try_get(self.base_scope)?; - let immediate_update = self.event_queue.schedule_update(base); - immediate_update(); + + let update = &base.event_channel; + update(); self.progress_completely(&mut diff_machine)?; @@ -319,21 +322,23 @@ impl VirtualDom { // Insert a new scope into our component list let idx = self.components.with(|components| { - components.insert_with(|f| { + components.insert_with(|new_idx| { + let height = cur_height + 1; Scope::new( caller, - f, + new_idx, Some(cur_component.arena_idx), - cur_height + 1, - self.event_queue.clone(), + height, + self.event_queue.new_channel(height, new_idx), self.components.clone(), + &[], ) }) })?; { let cur_component = self.components.try_get_mut(update.idx).unwrap(); - let mut ch = cur_component.children.borrow_mut(); + let mut ch = cur_component.descendents.borrow_mut(); ch.insert(idx); std::mem::drop(ch); } @@ -444,7 +449,7 @@ impl VirtualDom { // Accumulate all the child components that need to be removed while let Some(child_id) = children_to_remove.pop_back() { let comp = self.components.try_get(child_id).unwrap(); - let children = comp.children.borrow(); + let children = comp.descendents.borrow(); for child in children.iter() { children_to_remove.push_front(*child); } @@ -471,7 +476,6 @@ impl VirtualDom { } // TODO! -// // These impls are actually wrong. The DOM needs to have a mutex implemented. unsafe impl Sync for VirtualDom {} unsafe impl Send for VirtualDom {} @@ -488,7 +492,9 @@ pub struct Scope { // IDs of children that this scope has created // This enables us to drop the children and their children when this scope is destroyed - children: RefCell>, + descendents: RefCell>, + + child_nodes: &'static [VNode<'static>], // A reference to the list of components. // This lets us traverse the component list whenever we need to access our parent or children. @@ -501,8 +507,9 @@ pub struct Scope { pub height: u32, - pub event_queue: EventQueue, + pub event_channel: Rc, + // pub event_queue: EventQueue, pub caller: Weak>, pub hookidx: RefCell, @@ -528,6 +535,7 @@ pub struct Scope { // We need to pin the hook so it doesn't move as we initialize the list of hooks type Hook = Pin>; +type EventChannel = Rc; impl Scope { // we are being created in the scope of an existing component (where the creator_node lifetime comes into play) @@ -542,8 +550,9 @@ impl Scope { arena_idx: ScopeIdx, parent: Option, height: u32, - event_queue: EventQueue, + event_channel: EventChannel, arena_link: ScopeArena, + child_nodes: &'creator_node [VNode<'creator_node>], ) -> Self { log::debug!( "New scope created, height is {}, idx is {:?}", @@ -569,18 +578,19 @@ impl Scope { }; Self { + child_nodes: &[], caller, parent, arena_idx, height, - event_queue, + event_channel, arena_link, frames: ActiveFrame::new(), hooks: Default::default(), shared_contexts: Default::default(), listeners: Default::default(), hookidx: Default::default(), - children: Default::default(), + descendents: Default::default(), } } @@ -732,14 +742,18 @@ pub trait Scoped<'src>: Sized { /// Access the children elements passed into the component fn children(&self) -> &'src [VNode<'src>] { - todo!("Children API not yet implemented for component Context") + // We're re-casting the nodes back out + // They don't really have a static lifetime + unsafe { + let scope = self.get_scope(); + let nodes = scope.child_nodes; + nodes + } } /// Create a subscription that schedules a future render for the reference component fn schedule_update(&self) -> Rc { - todo!() - // pub fn schedule_update(self) -> impl Fn() + 'static { - // self.scope.event_queue.schedule_update(&self.scope) + self.get_scope().event_channel.clone() } /// Take a lazy VNode structure and actually build it with the context of the VDom's efficient VNode allocator. @@ -759,22 +773,12 @@ pub trait Scoped<'src>: Sized { ///``` fn render<'a, F: for<'b> FnOnce(&'b NodeCtx<'src>) -> VNode<'src> + 'src + 'a>( self, - // &'a/ self, lazy_nodes: LazyNodes<'src, F>, - // lazy_nodes: LazyNodes<'src, F>, ) -> VNode<'src> { - let scope_ref = self.get_scope(); - let ctx = NodeCtx { - scope_ref, + lazy_nodes.into_vnode(&NodeCtx { + scope_ref: self.get_scope(), listener_id: 0.into(), - }; - - todo!() - // VNode { - // root: unsafe { - // std::mem::transmute::, VNode<'static>>(lazy_nodes.into_vnode(&ctx)) - // }, - // } + }) } // impl<'scope> Context<'scope> { @@ -957,17 +961,14 @@ Any function prefixed with "use" should not be called conditionally. // We then expose a handle to use those props for render in the form of "OpaqueComponent" pub(crate) type OpaqueComponent<'e> = dyn for<'b> Fn(&'b Scope) -> VNode<'b> + 'e; -#[derive(Debug, Default, Clone)] -pub struct EventQueue(pub(crate) Rc>>); +#[derive(PartialEq, Debug, Clone, Default)] +pub(crate) struct EventQueue(pub Rc>>); impl EventQueue { - pub fn schedule_update(&self, source: &Scope) -> impl Fn() { + pub fn new_channel(&self, height: u32, idx: ScopeIdx) -> Rc { let inner = self.clone(); - let marker = HeightMarker { - height: source.height, - idx: source.arena_idx, - }; - move || inner.0.as_ref().borrow_mut().push(marker) + let marker = HeightMarker { height, idx }; + Rc::new(move || inner.0.as_ref().borrow_mut().push(marker)) } } @@ -1107,35 +1108,3 @@ impl ActiveFrame { } } } - -mod tests { - use super::*; - - #[test] - fn simulate() { - let dom = VirtualDom::new(|ctx| { - // - ctx.render(rsx! { - div { - - } - }) - }); - // let root = dom.components.get(dom.base_scope).unwrap(); - } - - // ensure the virtualdom is send + sync - // needed for use in async/await contexts - #[test] - fn is_send_sync() { - fn check_send(_a: T) -> T { - todo!() - } - fn check_sync(_a: T) -> T { - todo!() - } - - let _ = check_send(VirtualDom::new(|ctx| ctx.render(rsx! { div {}}))); - let _ = check_sync(VirtualDom::new(|ctx| ctx.render(rsx! { div {}}))); - } -}