diff --git a/examples/reducer.rs b/examples/reducer.rs index e72e76a90..2f93beeee 100644 --- a/examples/reducer.rs +++ b/examples/reducer.rs @@ -7,6 +7,7 @@ use dioxus::prelude::*; fn main() { + env_logger::init(); dioxus::desktop::launch(App, |c| c); } diff --git a/examples/webview.rs b/examples/webview.rs index f10117908..22a18cedc 100644 --- a/examples/webview.rs +++ b/examples/webview.rs @@ -9,7 +9,7 @@ //! //! Currently, NodeRefs won't work properly, but all other event functionality will. -use dioxus::prelude::*; +use dioxus::{events::on::MouseEvent, prelude::*}; fn main() { env_logger::init(); @@ -17,13 +17,31 @@ fn main() { } static App: FC<()> = |cx| { - let mut count = use_state(cx, || 0); + let state = use_state(cx, || String::from("hello")); + let clear_text = state == "hello"; + dbg!("rednering parent"); cx.render(rsx! { div { - h1 { "Hifive counter: {count}" } - button { onclick: move |_| count += 1, "Up high!" } - button { onclick: move |_| count -= 1, "Down low!" } + h1 {"{state}"} + CalculatorKey { name: "key-clear", onclick: move |_| state.get_mut().push_str("hello"), "{clear_text}" } + CalculatorKey { name: "key-sign", onclick: move |_| { state.get_mut().pop(); }, "±"} } }) }; + +#[derive(Props)] +struct CalculatorKeyProps<'a> { + name: &'static str, + onclick: &'a dyn Fn(MouseEvent), +} + +fn CalculatorKey<'a, 'r>(cx: Context<'a, CalculatorKeyProps<'r>>) -> DomTree<'a> { + cx.render(rsx! { + button { + class: "calculator-key {cx.name}" + onclick: {cx.onclick} + {cx.children()} + } + }) +} diff --git a/examples/webview_web.rs b/examples/webview_web.rs new file mode 100644 index 000000000..5d8a02802 --- /dev/null +++ b/examples/webview_web.rs @@ -0,0 +1,63 @@ +//! Example: Webview Renderer +//! ------------------------- +//! +//! This example shows how to use the dioxus_desktop crate to build a basic desktop application. +//! +//! Under the hood, the dioxus_desktop crate bridges a native Dioxus VirtualDom with a custom prebuit application running +//! in the webview runtime. Custom handlers are provided for the webview instance to consume patches and emit user events +//! into the native VDom instance. +//! +//! Currently, NodeRefs won't work properly, but all other event functionality will. + +use dioxus::prelude::*; + +// #[cfg] +fn main() { + // env_logger::init(); + dioxus::web::launch(App, |c| c); +} + +static App: FC<()> = |cx| { + dbg!("rednering parent"); + cx.render(rsx! { + div { + But { + h1 {"he"} + } + // But { + // h1 {"llo"} + // } + // But { + // h1 {"world"} + // } + } + }) +}; + +static But: FC<()> = |cx| { + let mut count = use_state(cx, || 0); + + // let d = Dropper { name: "asd" }; + // let handler = move |_| { + // dbg!(d.name); + // }; + + cx.render(rsx! { + div { + h1 { "Hifive counter: {count}" } + {cx.children()} + button { onclick: move |_| count += 1, "Up high!" } + button { onclick: move |_| count -= 1, "Down low!" } + // button { onclick: {handler}, "Down low!" } + } + }) +}; + +// struct Dropper { +// name: &'static str, +// } +// impl Drop for Dropper { +// fn drop(&mut self) { +// dbg!("dropped"); +// } +// } diff --git a/packages/core/src/bumpframe.rs b/packages/core/src/bumpframe.rs index 19c545679..e55e57fe0 100644 --- a/packages/core/src/bumpframe.rs +++ b/packages/core/src/bumpframe.rs @@ -14,7 +14,7 @@ pub struct BumpFrame { pub bump: Bump, pub head_node: VNode<'static>, - #[cfg(test)] + // used internally for debugging name: &'static str, } @@ -23,16 +23,12 @@ impl ActiveFrame { let frame_a = BumpFrame { bump: Bump::new(), head_node: NodeFactory::unstable_place_holder(), - - #[cfg(test)] - name: "old", + name: "wip", }; let frame_b = BumpFrame { bump: Bump::new(), head_node: NodeFactory::unstable_place_holder(), - - #[cfg(test)] - name: "new", + name: "fin", }; Self { generation: 0.into(), @@ -40,42 +36,44 @@ impl ActiveFrame { } } - pub fn prev_frame(&self) -> &BumpFrame { + /// The "work in progress frame" represents the frame that is currently being worked on. + pub fn wip_frame(&self) -> &BumpFrame { match self.generation.get() & 1 == 0 { true => &self.frames[0], false => &self.frames[1], } } - pub fn prev_frame_mut(&mut self) -> &mut BumpFrame { + pub fn wip_frame_mut(&mut self) -> &mut BumpFrame { match self.generation.get() & 1 == 0 { true => &mut self.frames[0], false => &mut self.frames[1], } } - pub fn cur_frame(&self) -> &BumpFrame { + /// The finished frame represents the frame that has been "finished" and cannot be modified again + pub fn finished_frame(&self) -> &BumpFrame { match self.generation.get() & 1 == 1 { true => &self.frames[0], false => &self.frames[1], } } - pub fn cur_frame_mut(&mut self) -> &mut BumpFrame { + pub fn finished_frame_mut(&mut self) -> &mut BumpFrame { match self.generation.get() & 1 == 1 { true => &mut self.frames[0], false => &mut self.frames[1], } } /// Give out our self-referential item with our own borrowed lifetime - pub fn current_head_node<'b>(&'b self) -> &'b VNode<'b> { - let cur_head = &self.cur_frame().head_node; + pub fn fin_head<'b>(&'b self) -> &'b VNode<'b> { + let cur_head = &self.finished_frame().head_node; unsafe { std::mem::transmute::<&VNode<'static>, &VNode<'b>>(cur_head) } } /// Give out our self-referential item with our own borrowed lifetime - pub fn prev_head_node<'b>(&'b self) -> &'b VNode<'b> { - let cur_head = &self.prev_frame().head_node; + pub fn wip_head<'b>(&'b self) -> &'b VNode<'b> { + let cur_head = &self.wip_frame().head_node; unsafe { std::mem::transmute::<&VNode<'static>, &VNode<'b>>(cur_head) } } @@ -95,16 +93,16 @@ mod tests { // just cycle a few times and make sure we get the right frames out for _ in 0..5 { - let old = frames.prev_frame(); - let new = frames.cur_frame(); - assert_eq!(old.name, "old"); - assert_eq!(new.name, "new"); + let fin = frames.finished_frame(); + let wip = frames.wip_frame(); + assert_eq!(wip.name, "wip"); + assert_eq!(fin.name, "fin"); frames.cycle_frame(); - let old = frames.prev_frame(); - let new = frames.cur_frame(); - assert_eq!(old.name, "new"); - assert_eq!(new.name, "old"); + let fin = frames.finished_frame(); + let wip = frames.wip_frame(); + assert_eq!(wip.name, "fin"); + assert_eq!(fin.name, "wip"); frames.cycle_frame(); } assert_eq!(frames.generation.get(), 10); diff --git a/packages/core/src/context.rs b/packages/core/src/context.rs index efc7a5613..2bb98d846 100644 --- a/packages/core/src/context.rs +++ b/packages/core/src/context.rs @@ -5,9 +5,8 @@ use std::{ any::{Any, TypeId}, cell::{Cell, RefCell}, future::Future, - marker::PhantomData, ops::Deref, - rc::{Rc, Weak}, + rc::Rc, }; /// Components in Dioxus use the "Context" object to interact with their lifecycle. @@ -98,7 +97,7 @@ impl<'src, P> Context<'src, P> { /// }) /// } /// ``` - pub fn children(&self) -> &'src [VNode<'src>] { + pub fn children(&self) -> ScopeChildren<'src> { self.scope.child_nodes() } diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 3a43d7a1c..49fa8c4c6 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -85,7 +85,8 @@ impl<'r, 'b> DiffMachine<'r, 'b> { pub fn get_scope_mut(&mut self, id: &ScopeId) -> Option<&'b mut Scope> { // ensure we haven't seen this scope before // if we have, then we're trying to alias it, which is not allowed - // debug_assert!(!self.seen_nodes.contains(id)); + debug_assert!(!self.seen_nodes.contains(id)); + unsafe { self.vdom.get_scope_mut(*id) } } pub fn get_scope(&mut self, id: &ScopeId) -> Option<&'b Scope> { @@ -120,17 +121,17 @@ impl<'real, 'bump> DiffMachine<'real, 'bump> { // // each function call assumes the stack is fresh (empty). pub fn diff_node(&mut self, old_node: &'bump VNode<'bump>, new_node: &'bump VNode<'bump>) { - // currently busted for components - need to fid - let root = old_node.dom_id.get().expect(&format!( - "Should not be diffing old nodes that were never assigned, {:#?}", - old_node - )); - match (&old_node.kind, &new_node.kind) { // Handle the "sane" cases first. // The rsx and html macros strongly discourage dynamic lists not encapsulated by a "Fragment". // So the sane (and fast!) cases are where the virtual structure stays the same and is easily diffable. (VNodeKind::Text(old), VNodeKind::Text(new)) => { + // currently busted for components - need to fid + let root = old_node.dom_id.get().expect(&format!( + "Should not be diffing old nodes that were never assigned, {:#?}", + old_node + )); + if old.text != new.text { self.edits.push_root(root); log::debug!("Text has changed {}, {}", old.text, new.text); @@ -142,6 +143,12 @@ impl<'real, 'bump> DiffMachine<'real, 'bump> { } (VNodeKind::Element(old), VNodeKind::Element(new)) => { + // currently busted for components - need to fid + let root = old_node.dom_id.get().expect(&format!( + "Should not be diffing old nodes that were never assigned, {:#?}", + old_node + )); + // If the element type is completely different, the element needs to be re-rendered completely // This is an optimization React makes due to how users structure their code // @@ -159,11 +166,25 @@ impl<'real, 'bump> DiffMachine<'real, 'bump> { // push it just in case // TODO: remove this - it clogs up things and is inefficient + // self.edits.push_root(root); + + let mut has_comitted = false; self.edits.push_root(root); - self.diff_listeners(old.listeners, new.listeners); - self.diff_attr(old.attributes, new.attributes, new.namespace); - self.diff_children(old.children, new.children); + // dbg!("diffing listeners"); + self.diff_listeners(&mut has_comitted, old.listeners, new.listeners); + // dbg!("diffing attrs"); + self.diff_attr( + &mut has_comitted, + old.attributes, + new.attributes, + new.namespace, + ); + // dbg!("diffing childrne"); + self.diff_children(&mut has_comitted, old.children, new.children); self.edits.pop(); + // if has_comitted { + // self.edits.pop(); + // } } (VNodeKind::Component(old), VNodeKind::Component(new)) => { @@ -182,17 +203,18 @@ impl<'real, 'bump> DiffMachine<'real, 'bump> { scope.caller = new.caller.clone(); // ack - this doesn't work on its own! - scope.update_children(new.children); + scope.update_children(ScopeChildren(new.children)); // React doesn't automatically memoize, but we do. - let should_render = match old.comparator { - Some(comparator) => comparator(new), - None => true, - }; + let should_render = true; + // let should_render = match old.comparator { + // Some(comparator) => comparator(new), + // None => true, + // }; if should_render { scope.run_scope().unwrap(); - self.diff_node(scope.old_frame(), scope.next_frame()); + self.diff_node(scope.frames.wip_head(), scope.frames.fin_head()); } else { // } @@ -239,7 +261,8 @@ impl<'real, 'bump> DiffMachine<'real, 'bump> { // Diff where we think the elements are the same if old.children.len() == new.children.len() {} - self.diff_children(old.children, new.children); + let mut has_comitted = false; + self.diff_children(&mut has_comitted, old.children, new.children); } // The strategy here is to pick the first possible node from the previous set and use that as our replace with root @@ -259,6 +282,16 @@ impl<'real, 'bump> DiffMachine<'real, 'bump> { | VNodeKind::Text(_) | VNodeKind::Element(_), ) => { + log::info!( + "taking the awkard diffing path {:#?}, {:#?}", + old_node, + new_node + ); + // currently busted for components - need to fid + let root = old_node.dom_id.get().expect(&format!( + "Should not be diffing old nodes that were never assigned, {:#?}", + old_node + )); // Choose the node to use as the placeholder for replacewith let back_node_id = match old_node.kind { // We special case these two types to avoid allocating the small-vecs @@ -429,7 +462,7 @@ impl<'real, 'bump> DiffMachine<'real, 'bump> { new_idx, Some(parent_idx), height, - vcomponent.children, + ScopeChildren(vcomponent.children), self.vdom.clone(), ) }); @@ -455,10 +488,11 @@ impl<'real, 'bump> DiffMachine<'real, 'bump> { new_component.run_scope().unwrap(); // TODO: we need to delete (IE relcaim this node, otherwise the arena will grow infinitely) - let nextnode = new_component.next_frame(); + let nextnode = new_component.frames.fin_head(); self.cur_idxs.push(new_idx); let meta = self.create(nextnode); self.cur_idxs.pop(); + node.dom_id.set(nextnode.dom_id.get()); // Finally, insert this node as a seen node. self.seen_nodes.insert(new_idx); @@ -535,38 +569,49 @@ impl<'a, 'bump> DiffMachine<'a, 'bump> { // [... node] // // The change list stack is left unchanged. - fn diff_listeners(&mut self, old: &[Listener<'_>], new: &[Listener<'_>]) { + fn diff_listeners(&mut self, committed: &mut bool, old: &[Listener<'_>], new: &[Listener<'_>]) { if !old.is_empty() || !new.is_empty() { // self.edits.commit_traversal(); } // TODO // what does "diffing listeners" even mean? - 'outer1: for (_l_idx, new_l) in new.iter().enumerate() { - // go through each new listener - // find its corresponding partner in the old list - // if any characteristics changed, remove and then re-add - - // if nothing changed, then just move on - let _event_type = new_l.event; - - for old_l in old { - if new_l.event == old_l.event { - new_l.mounted_node.set(old_l.mounted_node.get()); - // if new_l.id != old_l.id { - // self.edits.remove_event_listener(event_type); - // // TODO! we need to mess with events and assign them by ElementId - // // self.edits - // // .update_event_listener(event_type, new_l.scope, new_l.id) - // } - - continue 'outer1; - } - } - - // self.edits - // .new_event_listener(event_type, new_l.scope, new_l.id); + for (old_l, new_l) in old.iter().zip(new.iter()) { + log::info!( + "moving listener forward with event. old: {:#?}", + old_l.mounted_node.get() + ); + new_l.mounted_node.set(old_l.mounted_node.get()); } + // 'outer1: for (_l_idx, new_l) in new.iter().enumerate() { + // // go through each new listener + // // find its corresponding partner in the old list + // // if any characteristics changed, remove and then re-add + + // // if nothing changed, then just move on + // let _event_type = new_l.event; + + // for old_l in old { + // if new_l.event == old_l.event { + // log::info!( + // "moving listener forward with event. old: {:#?}", + // old_l.mounted_node.get() + // ); + // new_l.mounted_node.set(old_l.mounted_node.get()); + // // if new_l.id != old_l.id { + // // self.edits.remove_event_listener(event_type); + // // // TODO! we need to mess with events and assign them by ElementId + // // // self.edits + // // // .update_event_listener(event_type, new_l.scope, new_l.id) + // // } + + // continue 'outer1; + // } + // } + + // self.edits + // .new_event_listener(event_type, new_l.scope, new_l.id); + // } // 'outer2: for old_l in old { // for new_l in new { @@ -587,50 +632,61 @@ impl<'a, 'bump> DiffMachine<'a, 'bump> { // The change list stack is left unchanged. fn diff_attr( &mut self, + committed: &mut bool, old: &'bump [Attribute<'bump>], new: &'bump [Attribute<'bump>], namespace: Option<&'static str>, ) { + for (old_attr, new_attr) in old.iter().zip(new.iter()) { + if old_attr.value != new_attr.value { + if !*committed { + *committed = true; + // self.edits.push_root(); + } + } + // if old_attr.name == new_attr.name { + // } + } // Do O(n^2) passes to add/update and remove attributes, since // there are almost always very few attributes. // // The "fast" path is when the list of attributes name is identical and in the same order // With the Rsx and Html macros, this will almost always be the case - 'outer: for new_attr in new { - if new_attr.is_volatile { - // self.edits.commit_traversal(); - self.edits - .set_attribute(new_attr.name, new_attr.value, namespace); - } else { - for old_attr in old { - if old_attr.name == new_attr.name { - if old_attr.value != new_attr.value { - // self.edits.commit_traversal(); - self.edits - .set_attribute(new_attr.name, new_attr.value, namespace); - } - continue 'outer; - } else { - // names are different, a varying order of attributes has arrived - } - } + // 'outer: for new_attr in new { + // if new_attr.is_volatile { + // // self.edits.commit_traversal(); + // self.edits + // .set_attribute(new_attr.name, new_attr.value, namespace); + // } else { + // for old_attr in old { + // if old_attr.name == new_attr.name { + // if old_attr.value != new_attr.value { + // // self.edits.commit_traversal(); + // self.edits + // .set_attribute(new_attr.name, new_attr.value, namespace); + // } + // continue 'outer; + // } else { + // // names are different, a varying order of attributes has arrived + // } + // } - // self.edits.commit_traversal(); - self.edits - .set_attribute(new_attr.name, new_attr.value, namespace); - } - } + // // self.edits.commit_traversal(); + // self.edits + // .set_attribute(new_attr.name, new_attr.value, namespace); + // } + // } - 'outer2: for old_attr in old { - for new_attr in new { - if old_attr.name == new_attr.name { - continue 'outer2; - } - } + // 'outer2: for old_attr in old { + // for new_attr in new { + // if old_attr.name == new_attr.name { + // continue 'outer2; + // } + // } - // self.edits.commit_traversal(); - self.edits.remove_attribute(old_attr.name); - } + // // self.edits.commit_traversal(); + // self.edits.remove_attribute(old_attr.name); + // } } // Diff the given set of old and new children. @@ -641,66 +697,72 @@ impl<'a, 'bump> DiffMachine<'a, 'bump> { // [... parent] // // the change list stack is in the same state when this function returns. - fn diff_children(&mut self, old: &'bump [VNode<'bump>], new: &'bump [VNode<'bump>]) { - if new.is_empty() { - if !old.is_empty() { - // self.edits.commit_traversal(); - self.remove_all_children(old); - } - return; - } + fn diff_children( + &mut self, + committed: &mut bool, + old: &'bump [VNode<'bump>], + new: &'bump [VNode<'bump>], + ) { + // if new.is_empty() { + // if !old.is_empty() { + // // self.edits.commit_traversal(); + // self.remove_all_children(old); + // } + // return; + // } - if new.len() == 1 { - match (&old.first(), &new[0]) { - // (Some(VNodeKind::Text(old_vtext)), VNodeKind::Text(new_vtext)) - // if old_vtext.text == new_vtext.text => - // { - // // Don't take this fast path... - // } + // if new.len() == 1 { + // match (&old.first(), &new[0]) { + // (Some(VNodeKind::Text(old_vtext)), VNodeKind::Text(new_vtext)) + // if old_vtext.text == new_vtext.text => + // { + // // Don't take this fast path... + // } - // (_, VNodeKind::Text(text)) => { - // // self.edits.commit_traversal(); - // log::debug!("using optimized text set"); - // self.edits.set_text(text.text); - // return; - // } + // (_, VNodeKind::Text(text)) => { + // // self.edits.commit_traversal(); + // log::debug!("using optimized text set"); + // self.edits.set_text(text.text); + // return; + // } - // todo: any more optimizations - (_, _) => {} - } - } + // todo: any more optimizations + // (_, _) => {} + // } + // } - if old.is_empty() { - if !new.is_empty() { - // self.edits.commit_traversal(); - self.create_and_append_children(new); - } - return; - } + // if old.is_empty() { + // if !new.is_empty() { + // // self.edits.commit_traversal(); + // self.create_and_append_children(new); + // } + // return; + // } - let new_is_keyed = new[0].key.is_some(); - let old_is_keyed = old[0].key.is_some(); + // 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" - ); + // 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 { - log::warn!("using the wrong approach"); - self.diff_non_keyed_children(old, new); - // todo!("Not yet implemented a migration away from temporaries"); - // let t = self.edits.next_temporary(); - // self.diff_keyed_children(old, new); - // self.edits.set_next_temporary(t); - } else { - // log::debug!("diffing non keyed children"); - self.diff_non_keyed_children(old, new); - } + // if new_is_keyed && old_is_keyed { + // // log::warn!("using the wrong approach"); + // self.diff_non_keyed_children(old, new); + // // todo!("Not yet implemented a migration away from temporaries"); + // // let t = self.edits.next_temporary(); + // // self.diff_keyed_children(old, new); + // // self.edits.set_next_temporary(t); + // } else { + // // log::debug!("diffing non keyed children"); + // self.diff_non_keyed_children(old, new); + // } + self.diff_non_keyed_children(old, new); } // Diffing "keyed" children. @@ -1146,8 +1208,8 @@ impl<'a, 'bump> DiffMachine<'a, 'bump> { // the change list stack is in the same state when this function returns. fn diff_non_keyed_children(&mut self, old: &'bump [VNode<'bump>], new: &'bump [VNode<'bump>]) { // Handled these cases in `diff_children` before calling this function. - debug_assert!(!new.is_empty()); - debug_assert!(!old.is_empty()); + // debug_assert!(!new.is_empty()); + // debug_assert!(!old.is_empty()); // [... parent] // self.edits.go_down(); @@ -1165,6 +1227,7 @@ impl<'a, 'bump> DiffMachine<'a, 'bump> { // log::debug!("Root is bad: {:#?}", old_child); // } // self.edits.push_root(did); + // dbg!("dffing child", new_child, old_child); self.diff_node(old_child, new_child); // let old_id = old_child.get_mounted_id(self.components).unwrap(); @@ -1333,7 +1396,7 @@ impl<'a> Iterator for RealChildIterator<'a> { // let scope = self.scopes.get(sc.ass_scope.get().unwrap()).unwrap(); // Simply swap the current node on the stack with the root of the component - *node = scope.root(); + *node = scope.frames.fin_head(); } } } else { diff --git a/packages/core/src/events.rs b/packages/core/src/events.rs index 33874b219..66c59681f 100644 --- a/packages/core/src/events.rs +++ b/packages/core/src/events.rs @@ -199,7 +199,7 @@ pub mod on { event: shortname, mounted_node: Cell::new(None), scope: c.scope.our_arena_idx, - callback: RefCell::new(callback), + callback: RefCell::new(Some(callback)), } } )* diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index f439a080b..969472f16 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -113,7 +113,7 @@ pub struct Listener<'bump> { pub mounted_node: Cell>, - pub(crate) callback: RefCell>, + pub(crate) callback: RefCell>>, } /// Virtual Components for custom user-defined components @@ -133,6 +133,7 @@ pub struct VComponent<'src> { pub(crate) raw_props: *const (), // a pointer to the raw fn typ + // pub(crate) user_fc: BumpB\, pub(crate) user_fc: *const (), } @@ -149,7 +150,8 @@ pub struct NodeFactory<'a> { impl<'a> NodeFactory<'a> { #[inline] pub fn bump(&self) -> &'a bumpalo::Bump { - &self.scope.cur_frame().bump + &self.scope.frames.wip_frame().bump + // &self.scope.cur_frame().bump } pub fn unstable_place_holder() -> VNode<'static> { @@ -393,36 +395,14 @@ impl<'a> NodeFactory<'a> { unsafe { std::mem::transmute::<_, Captured<'static>>(caller) } } - pub fn fragment_from_iter( - self, - node_iter: impl IntoIterator>, - ) -> VNode<'a> { - let mut nodes = bumpalo::collections::Vec::new_in(self.bump()); + pub fn fragment_from_iter(self, node_iter: impl IntoVNodeList<'a> + 'a) -> VNode<'a> { + let children = node_iter.into_vnode_list(self); - for node in node_iter.into_iter() { - nodes.push(node.into_vnode(self)); - } - - if cfg!(debug_assertions) { - if nodes.len() > 1 { - if nodes.last().unwrap().key().is_none() { - log::error!( - r#" -Warning: Each child in an array or iterator should have a unique "key" prop. -Not providing a key will lead to poor performance with lists. -See docs.rs/dioxus for more information. ---- -To help you identify where this error is coming from, we've generated a backtrace. - "#, - ); - } - } - } VNode { dom_id: empty_cell(), key: None, kind: VNodeKind::Fragment(VFragment { - children: nodes.into_bump_slice(), + children, is_static: false, }), } @@ -448,6 +428,63 @@ pub trait IntoVNode<'a> { fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a>; } +pub trait IntoVNodeList<'a> { + fn into_vnode_list(self, cx: NodeFactory<'a>) -> &'a [VNode<'a>]; +} + +impl<'a, T, V> IntoVNodeList<'a> for T +where + T: IntoIterator, + V: IntoVNode<'a>, +{ + fn into_vnode_list(self, cx: NodeFactory<'a>) -> &'a [VNode<'a>] { + let mut nodes = bumpalo::collections::Vec::new_in(cx.bump()); + + for node in self.into_iter() { + nodes.push(node.into_vnode(cx)); + } + + if cfg!(debug_assertions) { + if nodes.len() > 1 { + if nodes.last().unwrap().key().is_none() { + log::error!( + r#" + Warning: Each child in an array or iterator should have a unique "key" prop. + Not providing a key will lead to poor performance with lists. + See docs.rs/dioxus for more information. + --- + To help you identify where this error is coming from, we've generated a backtrace. + "#, + ); + } + } + } + + nodes.into_bump_slice() + } +} + +pub struct ScopeChildren<'a>(pub &'a [VNode<'a>]); +impl Copy for ScopeChildren<'_> {} +impl<'a> Clone for ScopeChildren<'a> { + fn clone(&self) -> Self { + ScopeChildren(self.0) + } +} +impl ScopeChildren<'_> { + pub unsafe fn extend_lifetime(self) -> ScopeChildren<'static> { + std::mem::transmute(self) + } + pub unsafe fn unextend_lfetime<'a>(self) -> ScopeChildren<'a> { + std::mem::transmute(self) + } +} +impl<'a> IntoVNodeList<'a> for ScopeChildren<'a> { + fn into_vnode_list(self, _: NodeFactory<'a>) -> &'a [VNode<'a>] { + self.0 + } +} + // For the case where a rendered VNode is passed into the rsx! macro through curly braces impl<'a> IntoIterator for VNode<'a> { type Item = VNode<'a>; @@ -470,27 +507,28 @@ impl<'a> IntoVNode<'a> for VNode<'a> { // Designed to support indexing impl<'a> IntoVNode<'a> for &VNode<'a> { fn into_vnode(self, _: NodeFactory<'a>) -> VNode<'a> { - let kind = match &self.kind { - VNodeKind::Element(element) => VNodeKind::Element(element), - VNodeKind::Text(old) => VNodeKind::Text(VText { - text: old.text, - is_static: old.is_static, - }), - VNodeKind::Fragment(fragment) => VNodeKind::Fragment(VFragment { - children: fragment.children, - is_static: fragment.is_static, - }), - VNodeKind::Component(component) => VNodeKind::Component(component), + todo!() + // let kind = match &self.kind { + // VNodeKind::Element(element) => VNodeKind::Element(element), + // VNodeKind::Text(old) => VNodeKind::Text(VText { + // text: old.text, + // is_static: old.is_static, + // }), + // VNodeKind::Fragment(fragment) => VNodeKind::Fragment(VFragment { + // children: fragment.children, + // is_static: fragment.is_static, + // }), + // VNodeKind::Component(component) => VNodeKind::Component(component), - // todo: it doesn't make much sense to pass in suspended nodes - // I think this is right but I'm not too sure. - VNodeKind::Suspended { node } => VNodeKind::Suspended { node: node.clone() }, - }; - VNode { - kind, - dom_id: self.dom_id.clone(), - key: self.key.clone(), - } + // // todo: it doesn't make much sense to pass in suspended nodes + // // I think this is right but I'm not too sure. + // VNodeKind::Suspended { node } => VNodeKind::Suspended { node: node.clone() }, + // }; + // VNode { + // kind, + // dom_id: self.dom_id.clone(), + // key: self.key.clone(), + // } } } diff --git a/packages/core/src/scope.rs b/packages/core/src/scope.rs index 4f15f150d..89b24d7bb 100644 --- a/packages/core/src/scope.rs +++ b/packages/core/src/scope.rs @@ -2,6 +2,7 @@ use crate::innerlude::*; use bumpalo::boxed::Box as BumpBox; use std::{ any::{Any, TypeId}, + borrow::BorrowMut, cell::{Cell, RefCell}, collections::{HashMap, HashSet}, future::Future, @@ -29,7 +30,7 @@ pub struct Scope { // an internal, highly efficient storage of vnodes pub(crate) frames: ActiveFrame, pub(crate) caller: Rc, - pub(crate) child_nodes: &'static [VNode<'static>], + pub(crate) child_nodes: ScopeChildren<'static>, // Listeners pub(crate) listeners: RefCell>>, @@ -66,11 +67,14 @@ impl Scope { height: u32, - child_nodes: &'creator_node [VNode<'creator_node>], - + child_nodes: ScopeChildren, + // child_nodes: &'creator_node [VNode<'creator_node>], vdom: SharedResources, ) -> Self { - let child_nodes = unsafe { std::mem::transmute(child_nodes) }; + let child_nodes = unsafe { child_nodes.extend_lifetime() }; + // let child_nodes = unsafe { std::mem::transmute(child_nodes) }; + + // dbg!(child_nodes); Self { child_nodes, caller, @@ -93,9 +97,11 @@ impl Scope { pub(crate) fn update_children<'creator_node>( &mut self, - child_nodes: &'creator_node [VNode<'creator_node>], + child_nodes: ScopeChildren, + // child_nodes: &'creator_node [VNode<'creator_node>], ) { - let child_nodes = unsafe { std::mem::transmute(child_nodes) }; + // let child_nodes = unsafe { std::mem::transmute(child_nodes) }; + let child_nodes = unsafe { child_nodes.extend_lifetime() }; self.child_nodes = child_nodes; } @@ -104,17 +110,29 @@ impl Scope { // This breaks any latent references, invalidating every pointer referencing into it. // Remove all the outdated listeners - // This is a very dangerous operation - let next_frame = self.frames.prev_frame_mut(); - next_frame.bump.reset(); + log::debug!("reset okay"); // make sure we call the drop implementation on all the listeners // this is important to not leak memory - self.listeners.borrow_mut().clear(); + for li in self.listeners.borrow_mut().drain(..) { + log::debug!("dropping listener"); + let d = unsafe { &*li }; + let mut r = d.callback.borrow_mut(); + let p = r.take().unwrap(); + std::mem::drop(p); + // d.callback.borrow_mut(); + } + + // self.listeners.borrow_mut().clear(); unsafe { self.hooks.reset() }; + self.listener_idx.set(0); + // This is a very dangerous operation + let next_frame = self.frames.wip_frame_mut(); + next_frame.bump.reset(); + // Cast the caller ptr from static to one with our own reference let c3: &WrappedCaller = self.caller.as_ref(); @@ -126,8 +144,9 @@ impl Scope { } Some(new_head) => { // the user's component succeeded. We can safely cycle to the next frame - self.frames.prev_frame_mut().head_node = unsafe { std::mem::transmute(new_head) }; + self.frames.wip_frame_mut().head_node = unsafe { std::mem::transmute(new_head) }; self.frames.cycle_frame(); + log::debug!("Cycle okay"); Ok(()) } } @@ -159,7 +178,11 @@ impl Scope { let raw_listener = listners.iter().find(|lis| { let search = unsafe { &***lis }; let search_id = search.mounted_node.get(); - log::info!("searching listener {:#?}", search_id); + log::info!( + "searching listener {:#?} for real {:?}", + search_id, + real_node_id + ); match (real_node_id, search_id) { (Some(e), Some(search_id)) => search_id == e, @@ -169,8 +192,16 @@ impl Scope { if let Some(raw_listener) = raw_listener { let listener = unsafe { &**raw_listener }; + + log::info!( + "calling listener {:?}, {:?}", + listener.event, + listener.scope + ); let mut cb = listener.callback.borrow_mut(); - (cb)(event); + if let Some(cb) = cb.as_mut() { + (cb)(event); + } } else { log::warn!("An event was triggered but there was no listener to handle it"); } @@ -178,28 +209,14 @@ impl Scope { Ok(()) } - #[inline] - pub(crate) fn next_frame<'bump>(&'bump self) -> &'bump VNode<'bump> { - self.frames.current_head_node() + pub fn root(&self) -> &VNode { + self.frames.fin_head() } - #[inline] - pub(crate) fn old_frame<'bump>(&'bump self) -> &'bump VNode<'bump> { - self.frames.prev_head_node() - } - - #[inline] - pub(crate) fn cur_frame(&self) -> &BumpFrame { - self.frames.cur_frame() - } - - /// Get the root VNode of this component - #[inline] - pub fn root<'a>(&'a self) -> &'a VNode<'a> { - &self.frames.current_head_node() - } - #[inline] - pub fn child_nodes<'a>(&'a self) -> &'a [VNode<'a>] { - unsafe { std::mem::transmute(self.child_nodes) } + // #[inline] + pub fn child_nodes<'a>(&'a self) -> ScopeChildren { + // self.child_nodes + unsafe { self.child_nodes.unextend_lfetime() } + // unsafe { std::mem::transmute(self.child_nodes) } } } diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 243b4c5d4..3001e26ec 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -135,7 +135,7 @@ impl VirtualDom { let base_scope = components.insert_scope_with_key(move |myidx| { let caller = NodeFactory::create_component_caller(root, props_ptr as *const _); - Scope::new(caller, myidx, None, 0, &[], link) + Scope::new(caller, myidx, None, 0, ScopeChildren(&[]), link) }); Self { @@ -192,7 +192,7 @@ impl VirtualDom { // We run the component. If it succeeds, then we can diff it and add the changes to the dom. if cur_component.run_scope().is_ok() { - let meta = diff_machine.create(cur_component.next_frame()); + let meta = diff_machine.create(cur_component.frames.fin_head()); diff_machine.edits.append_children(meta.added_to_stack); } else { // todo: should this be a hard error? @@ -289,7 +289,8 @@ impl VirtualDom { log::warn!("Suspense event came through, but there was no mounted node to update >:("); } Some(nodes) => { - let nodes = scope.cur_frame().bump.alloc(nodes); + todo!("using the wrong frame"); + let nodes = scope.frames.finished_frame().bump.alloc(nodes); // push the old node's root onto the stack let real_id = domnode.get().ok_or(Error::NotMounted)?; @@ -329,20 +330,24 @@ impl VirtualDom { // Make sure this isn't a node we've already seen, we don't want to double-render anything // If we double-renderer something, this would cause memory safety issues if diff_machine.seen_nodes.contains(&update.idx) { + log::debug!("Skipping update for: {:#?}", update); continue; } - // Now, all the "seen nodes" are nodes that got notified by running this listener - diff_machine.seen_nodes.insert(update.idx.clone()); - // Start a new mutable borrow to components // We are guaranteeed that this scope is unique because we are tracking which nodes have modified in the diff machine let cur_component = diff_machine .get_scope_mut(&update.idx) .expect("Failed to find scope or borrow would be aliasing"); + // Now, all the "seen nodes" are nodes that got notified by running this listener + diff_machine.seen_nodes.insert(update.idx.clone()); + if cur_component.run_scope().is_ok() { - let (old, new) = (cur_component.old_frame(), cur_component.next_frame()); + let (old, new) = ( + cur_component.frames.wip_head(), + cur_component.frames.fin_head(), + ); diff_machine.diff_node(old, new); } } diff --git a/packages/desktop/src/dom.rs b/packages/desktop/src/dom.rs index 59debac46..4c08b0c82 100644 --- a/packages/desktop/src/dom.rs +++ b/packages/desktop/src/dom.rs @@ -2,32 +2,32 @@ use dioxus_core::{DomEdit, RealDom}; -pub struct WebviewRegistry {} +// pub struct WebviewRegistry {} -impl WebviewRegistry { - pub fn new() -> Self { - Self {} - } -} +// impl WebviewRegistry { +// pub fn new() -> Self { +// Self {} +// } +// } pub struct WebviewDom<'bump> { pub edits: Vec>, pub node_counter: u64, - pub registry: WebviewRegistry, + // pub registry: WebviewRegistry, } impl WebviewDom<'_> { - pub fn new(registry: WebviewRegistry) -> Self { + pub fn new() -> Self { Self { edits: Vec::new(), node_counter: 0, - registry, + // registry, } } - // Finish using the dom (for its edit list) and give back the node and event registry - pub fn consume(self) -> WebviewRegistry { - self.registry - } + // // Finish using the dom (for its edit list) and give back the node and event registry + // pub fn consume(self) -> WebviewRegistry { + // self.registry + // } } impl<'bump> RealDom<'bump> for WebviewDom<'bump> { fn raw_node_as_any(&self) -> &mut dyn std::any::Any { diff --git a/packages/desktop/src/index.html b/packages/desktop/src/index.html index c734af71c..bbe3d2d84 100644 --- a/packages/desktop/src/index.html +++ b/packages/desktop/src/index.html @@ -29,6 +29,10 @@ this.stack.push(node); } + PopRoot(edit) { + this.stack.pop(); + } + AppendChildren(edit) { let root = this.stack[this.stack.length - (edit.many + 1)]; for (let i = 0; i < edit.many; i++) { diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index 8e05ddd14..83283932f 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -70,7 +70,7 @@ impl WebviewRenderer { // todo: combine these or something let vdom = Arc::new(RwLock::new(vir)); - let registry = Arc::new(RwLock::new(Some(WebviewRegistry::new()))); + // let registry = Arc::new(RwLock::new(Some(WebviewRegistry::new()))); let webview = WebViewBuilder::new(window)? // .with_visible(false) @@ -83,10 +83,10 @@ impl WebviewRenderer { serde_json::to_value(edits).unwrap() } else { let mut lock = vdom.write().unwrap(); - let mut reg_lock = registry.write().unwrap(); + // let mut reg_lock = registry.write().unwrap(); // Create the thin wrapper around the registry to collect the edits into - let mut real = dom::WebviewDom::new(reg_lock.take().unwrap()); + let mut real = dom::WebviewDom::new(); // Serialize the edit stream let edits = { @@ -96,7 +96,7 @@ impl WebviewRenderer { }; // Give back the registry into its slot - *reg_lock = Some(real.consume()); + // *reg_lock = Some(real.consume()); edits }; @@ -106,11 +106,11 @@ impl WebviewRenderer { "user_event" => { log::debug!("User event received"); - let registry = registry.clone(); + // let registry = registry.clone(); let vdom = vdom.clone(); let response = async_std::task::block_on(async move { let mut lock = vdom.write().unwrap(); - let mut reg_lock = registry.write().unwrap(); + // let mut reg_lock = registry.write().unwrap(); // a deserialized event let data = req.params.unwrap(); @@ -120,7 +120,7 @@ impl WebviewRenderer { lock.queue_event(event); // Create the thin wrapper around the registry to collect the edits into - let mut real = dom::WebviewDom::new(reg_lock.take().unwrap()); + let mut real = dom::WebviewDom::new(); // Serialize the edit stream // @@ -131,7 +131,7 @@ impl WebviewRenderer { let edits = serde_json::to_value(edits).unwrap(); // Give back the registry into its slot - *reg_lock = Some(real.consume()); + // *reg_lock = Some(real.consume()); // Return the edits into the webview runtime Some(RpcResponse::new_result(req.id.take(), Some(edits))) @@ -206,7 +206,7 @@ impl WebviewRenderer { use serde::{Deserialize, Serialize}; use serde_json::Value; -use crate::dom::WebviewRegistry; +// use crate::dom::WebviewRegistry; #[derive(Debug, Serialize, Deserialize)] struct MessageParameters { diff --git a/packages/web/examples/btns.rs b/packages/web/examples/btns.rs new file mode 100644 index 000000000..1dfca2a4b --- /dev/null +++ b/packages/web/examples/btns.rs @@ -0,0 +1,68 @@ +//! Example: Webview Renderer +//! ------------------------- +//! +//! This example shows how to use the dioxus_desktop crate to build a basic desktop application. +//! +//! Under the hood, the dioxus_desktop crate bridges a native Dioxus VirtualDom with a custom prebuit application running +//! in the webview runtime. Custom handlers are provided for the webview instance to consume patches and emit user events +//! into the native VDom instance. +//! +//! Currently, NodeRefs won't work properly, but all other event functionality will. + +use dioxus::prelude::*; +use dioxus_core as dioxus; +use dioxus_hooks::*; +use dioxus_html as dioxus_elements; + +// #[cfg] +fn main() { + wasm_logger::init(wasm_logger::Config::new(log::Level::Debug)); + dioxus_web::launch(App, |c| c); + // env_logger::init(); + // dioxus::web::launch(App, |c| c); +} + +static App: FC<()> = |cx| { + dbg!("rednering parent"); + cx.render(rsx! { + div { + But { + h1 {"he"} + } + // But { + // h1 {"llo"} + // } + // But { + // h1 {"world"} + // } + } + }) +}; + +static But: FC<()> = |cx| { + let mut count = use_state(cx, || 0); + + // let d = Dropper { name: "asd" }; + // let handler = move |_| { + // dbg!(d.name); + // }; + + cx.render(rsx! { + div { + h1 { "Hifive counter: {count}" } + {cx.children()} + button { onclick: move |_| count += 1, "Up high!" } + button { onclick: move |_| count -= 1, "Down low!" } + // button { onclick: {handler}, "Down low!" } + } + }) +}; + +// struct Dropper { +// name: &'static str, +// } +// impl Drop for Dropper { +// fn drop(&mut self) { +// dbg!("dropped"); +// } +// }