From f782e142118fb7acf1b88a0f3fbb03e4a5e3e91e Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 27 Jul 2021 00:27:07 -0400 Subject: [PATCH] feat: omg what a dumb mistake --- Cargo.toml | 2 +- examples/coroutine.rs | 71 +++++++++++++++++++++ examples/file_explorer.rs | 113 +++++++++++++++++++++++++++++++++ examples/model.rs | 9 ++- packages/core/src/bumpframe.rs | 10 ++- packages/core/src/context.rs | 11 ++-- packages/core/src/diff.rs | 93 +++++++++------------------ packages/core/src/nodes.rs | 38 ++--------- packages/core/src/scope.rs | 35 +++++----- packages/desktop/Cargo.toml | 2 +- packages/hooks/src/usestate.rs | 2 +- packages/mobile/Cargo.toml | 2 +- packages/mobile/src/lib.rs | 1 + packages/ssr/Cargo.toml | 2 +- packages/web/Cargo.toml | 2 +- 15 files changed, 262 insertions(+), 131 deletions(-) create mode 100644 examples/coroutine.rs create mode 100644 examples/file_explorer.rs diff --git a/Cargo.toml b/Cargo.toml index e0bdd6ed3..6ccba2e5c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ dioxus-mobile = { path = "./packages/mobile", optional = true } [features] # core -default = ["core", "ssr"] +default = ["core", "ssr", "desktop"] core = ["macro", "hooks", "html"] macro = ["dioxus-core-macro"] hooks = ["dioxus-hooks"] diff --git a/examples/coroutine.rs b/examples/coroutine.rs new file mode 100644 index 000000000..7617cae7a --- /dev/null +++ b/examples/coroutine.rs @@ -0,0 +1,71 @@ +//! Example: Coroutines! +//! -------------------- +//! +//! Coroutines are an awesome way to write concurrent code. Dioxus heavily leverages coroutines to make sense of complex +//! ongoing asynchronous tasks. The async scheduler of Dioxus supports both single-threaded and multi-threaded coroutines, +//! so you can drop in code to run across multiple threads without blocking the main thread. +//! +//! Dioxus cannot simply abstract away the threading model for the web, unfortunately. If you want to use "web threads" +//! you either need to limit support for Chrome, or you need to use a Web Workers and message passing. This is easy enough +//! to do in your own code, and doesn't require 1st-party support from Dioxus itself. +//! +//! UseState and friends work fine with coroutines, but coroutines might be easier to use with the Dirac global state +//! management API. This lets you easily drive global state from a coroutine without having to subscribe to the state. +//! +//! For now, this example shows how to use coroutines used with use_state. +//! +//! +//! ## What is a Couroutine? +//! +//! A coroutine is a function that can be paused and resumed. It can be paused internally through "await" or externally +//! using the `TaskHandle` API. Within a coroutine, you may execute asynchonous code, that modifies values captured when +//! the coroutine was initiated. `use_state` always returns the same setter, so you don't need to worry about + +fn main() { + dioxus::desktop::launch(App, |c| c); +} + +use dioxus::prelude::*; + +static App: FC<()> = |cx| { + let p1 = use_state(cx, || 0); + let p2 = use_state(cx, || 0); + + let (mut p1_async, mut p2_async) = (p1.for_async(), p2.for_async()); + let (p1_handle, _) = cx.use_task(|| async move { + loop { + *p1_async.get_mut() += 1; + async_std::task::sleep(std::time::Duration::from_millis(75)).await; + } + }); + let (p2_handle, _) = cx.use_task(|| async move { + loop { + *p2_async.get_mut() += 1; + async_std::task::sleep(std::time::Duration::from_millis(100)).await; + } + }); + + cx.render(rsx! { + div { style: { width: "400px", height: "400px", position: "relative", background: "yellow" } + button { "reset", onclick: move |_| {} } + Horsey { pos: *p1, "horsey 1" } + Horsey { pos: *p2, "horsey 2" } + } + }) +}; + +#[derive(PartialEq, Props)] +struct HorseyProps { + pos: i32, +} + +static Horsey: FC = |cx| { + cx.render(rsx! { + div { + button { "pause" } + div { + {cx.children()} + } + } + }) +}; diff --git a/examples/file_explorer.rs b/examples/file_explorer.rs new file mode 100644 index 000000000..da566b42c --- /dev/null +++ b/examples/file_explorer.rs @@ -0,0 +1,113 @@ +//! Example: File Explorer +//! ------------------------- +//! +//! This is a fun little desktop application that lets you explore the file system. +//! +//! This example is interesting because it's mixing filesystem operations and GUI, which is typically hard for UI to do. + +use dioxus::desktop::wry::application::dpi::LogicalSize; +use dioxus::prelude::*; +use std::fs::{self, DirEntry}; + +fn main() { + env_logger::init(); + dioxus::desktop::launch(App, |c| { + c.with_resizable(false) + .with_inner_size(LogicalSize::new(800.0, 400.0)) + }) + .unwrap(); +} + +static App: FC<()> = |cx| { + let files = use_state(cx, || Files::new()); + + let file_list = files.path_names.iter().enumerate().map(|(dir_id, path)| { + rsx! ( + li { a {"{path}", onclick: move |_| files.get_mut().enter_dir(dir_id), href: "#"} } + ) + }); + + let err_disp = files.err.as_ref().map(|err| { + rsx! { + div { + code {"{err}"} + button {"x", onclick: move |_| files.get_mut().clear_err() } + } + } + }); + + let cur = files.current(); + cx.render(rsx! { + div { + h1 {"Files: "} + h3 {"Cur dir: {cur}"} + button { "go up", onclick: move |_| files.get_mut().go_up() } + ol { {file_list} } + {err_disp} + } + }) +}; + +// right now, this gets cloned every time. It might be a bit better to use im_rc's collections instead +#[derive(Clone)] +struct Files { + path_stack: Vec, + path_names: Vec, + err: Option, +} + +impl Files { + fn new() -> Self { + let mut files = Self { + path_stack: vec!["./".to_string()], + path_names: vec![], + err: None, + }; + + files.reload_path_list(); + + files + } + + fn reload_path_list(&mut self) { + let cur_path = self.path_stack.last().unwrap(); + let paths = match fs::read_dir(cur_path) { + Ok(e) => e, + Err(err) => { + let err = format!("An error occured: {:?}", err); + self.err = Some(err); + self.path_stack.pop(); + return; + } + }; + + // clear the current state + self.clear_err(); + self.path_names.clear(); + + for path in paths { + self.path_names + .push(path.unwrap().path().display().to_string()); + } + } + + fn go_up(&mut self) { + if self.path_stack.len() > 1 { + self.path_stack.pop(); + } + self.reload_path_list(); + } + + fn enter_dir(&mut self, dir_id: usize) { + let path = &self.path_names[dir_id]; + self.path_stack.push(path.clone()); + self.reload_path_list(); + } + + fn current(&self) -> &str { + self.path_stack.last().unwrap() + } + fn clear_err(&mut self) { + self.err = None; + } +} diff --git a/examples/model.rs b/examples/model.rs index bc5185b32..b3d87c612 100644 --- a/examples/model.rs +++ b/examples/model.rs @@ -17,12 +17,17 @@ use dioxus::events::on::*; use dioxus::prelude::*; +use dioxus_desktop::wry::application::dpi::LogicalSize; const STYLE: &str = include_str!("./assets/calculator.css"); fn main() { env_logger::init(); - dioxus::desktop::launch(App, |cfg| cfg.with_title("Calculator Demo")) - .expect("failed to launch dioxus app"); + dioxus::desktop::launch(App, |cfg| { + cfg.with_title("Calculator Demo") + .with_resizable(false) + .with_inner_size(LogicalSize::new(320.0, 530.0)) + }) + .expect("failed to launch dioxus app"); } static App: FC<()> = |cx| { diff --git a/packages/core/src/bumpframe.rs b/packages/core/src/bumpframe.rs index e55e57fe0..f1ff888d3 100644 --- a/packages/core/src/bumpframe.rs +++ b/packages/core/src/bumpframe.rs @@ -12,7 +12,7 @@ pub struct ActiveFrame { pub struct BumpFrame { pub bump: Bump, - pub head_node: VNode<'static>, + pub(crate) head_node: VNode<'static>, // used internally for debugging name: &'static str, @@ -36,6 +36,14 @@ impl ActiveFrame { } } + pub unsafe fn reset_wip_frame(&mut self) { + self.wip_frame_mut().bump.reset() + } + + pub fn update_head_node<'a>(&mut self, node: VNode<'a>) { + self.wip_frame_mut().head_node = unsafe { std::mem::transmute(node) }; + } + /// 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 { diff --git a/packages/core/src/context.rs b/packages/core/src/context.rs index 2bb98d846..753626d96 100644 --- a/packages/core/src/context.rs +++ b/packages/core/src/context.rs @@ -148,10 +148,10 @@ impl<'src, P> Context<'src, P> { lazy_nodes: LazyNodes<'src, F>, ) -> DomTree<'src> { let scope_ref = self.scope; - let listener_id = &scope_ref.listener_idx; + // let listener_id = &scope_ref.listener_idx; Some(lazy_nodes.into_vnode(NodeFactory { scope: scope_ref, - listener_id, + // listener_id, })) } @@ -505,10 +505,7 @@ impl<'src> SuspendedContext<'src> { lazy_nodes: LazyNodes<'src, F>, ) -> DomTree<'src> { let scope_ref = self.inner.scope; - let listener_id = &scope_ref.listener_idx; - Some(lazy_nodes.into_vnode(NodeFactory { - scope: scope_ref, - listener_id, - })) + + Some(lazy_nodes.into_vnode(NodeFactory { scope: scope_ref })) } } diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 49fa8c4c6..45a8ac630 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -55,7 +55,7 @@ use crate::{arena::SharedResources, innerlude::*}; use fxhash::{FxHashMap, FxHashSet}; use smallvec::{smallvec, SmallVec}; -use std::{any::Any, borrow::Borrow}; +use std::{any::Any, borrow::Borrow, cmp::Ordering}; /// Instead of having handles directly over nodes, Dioxus uses simple u32 as node IDs. /// The expectation is that the underlying renderer will mainain their Nodes in vec where the ids are the index. This allows @@ -206,13 +206,12 @@ impl<'real, 'bump> DiffMachine<'real, 'bump> { scope.update_children(ScopeChildren(new.children)); // React doesn't automatically memoize, but we do. - let should_render = true; - // let should_render = match old.comparator { - // Some(comparator) => comparator(new), - // None => true, - // }; + let are_the_same = match old.comparator { + Some(comparator) => comparator(new), + None => false, + }; - if should_render { + if !are_the_same { scope.run_scope().unwrap(); self.diff_node(scope.frames.wip_head(), scope.frames.fin_head()); } else { @@ -915,7 +914,7 @@ impl<'a, 'bump> DiffMachine<'a, 'bump> { // [... parent] // // When this function returns, the change list stack is in the same state. - pub fn remove_all_children(&mut self, old: &'bump [VNode<'bump>]) { + fn remove_all_children(&mut self, old: &'bump [VNode<'bump>]) { // debug_assert!(self.edits.traversal_is_committed()); log::debug!("REMOVING CHILDREN"); for _child in old { @@ -1208,67 +1207,35 @@ 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()); - // [... parent] - // self.edits.go_down(); - // self.edits.push_root() - // [... parent child] - - // todo!() for (_i, (new_child, old_child)) in new.iter().zip(old.iter()).enumerate() { - // [... parent prev_child] - // self.edits.go_to_sibling(i); - // [... parent this_child] - - // let did = old_child.get_mounted_id(self.components).unwrap(); - // if did.0 == 0 { - // 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(); - // let new_id = new_child.get_mounted_id(self.components).unwrap(); - - // log::debug!( - // "pushed root. {:?}, {:?}", - // old_child.get_mounted_id(self.components).unwrap(), - // new_child.get_mounted_id(self.components).unwrap() - // ); - // if old_id != new_id { - // log::debug!("Mismatch: {:?}", new_child); - // } } - // match old.len().cmp(&new.len()) { - // // old.len > new.len -> removing some nodes - // Ordering::Greater => { - // // [... parent prev_child] - // self.edits.go_to_sibling(new.len()); - // // [... parent first_child_to_remove] - // // self.edits.commit_traversal(); - // // support::remove_self_and_next_siblings(state, &old[new.len()..]); - // self.remove_self_and_next_siblings(&old[new.len()..]); - // // [... parent] - // } - // // old.len < new.len -> adding some nodes - // Ordering::Less => { - // // [... parent last_child] - // self.edits.go_up(); - // // [... parent] - // // self.edits.commit_traversal(); - // self.create_and_append_children(&new[old.len()..]); - // } - // // old.len == new.len -> no nodes added/removed, but πerhaps changed - // Ordering::Equal => { - // // [... parent child] - // self.edits.go_up(); - // // [... parent] - // } - // } + match old.len().cmp(&new.len()) { + // old.len > new.len -> removing some nodes + Ordering::Greater => { + for item in &old[new.len()..] { + for i in RealChildIterator::new(item, self.vdom) { + self.edits.push_root(i.element_id().unwrap()); + self.edits.remove(); + } + } + } + // old.len < new.len -> adding some nodes + Ordering::Less => { + // [... parent last_child] + // self.edits.go_up(); + // [... parent] + // self.edits.commit_traversal(); + self.create_and_append_children(&new[old.len()..]); + } + // old.len == new.len -> no nodes added/removed, but πerhaps changed + Ordering::Equal => {} + } } // ====================== @@ -1284,7 +1251,7 @@ impl<'a, 'bump> DiffMachine<'a, 'bump> { // After the function returns, the child is no longer on the change list stack: // // [... parent] - pub fn remove_self_and_next_siblings(&self, old: &[VNode<'bump>]) { + fn remove_self_and_next_siblings(&self, old: &[VNode<'bump>]) { // debug_assert!(self.edits.traversal_is_committed()); for child in old { if let VNodeKind::Component(_vcomp) = child.kind { diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 969472f16..3cbd6a35c 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -144,14 +144,12 @@ pub struct VComponent<'src> { #[derive(Copy, Clone)] pub struct NodeFactory<'a> { pub scope: &'a Scope, - pub listener_id: &'a Cell, } impl<'a> NodeFactory<'a> { #[inline] pub fn bump(&self) -> &'a bumpalo::Bump { &self.scope.frames.wip_frame().bump - // &self.scope.cur_frame().bump } pub fn unstable_place_holder() -> VNode<'static> { @@ -346,6 +344,9 @@ impl<'a> NodeFactory<'a> { if user_fc == other.user_fc { let real_other = unsafe { &*(other.raw_props as *const _ as *const P) }; let props_memoized = unsafe { props.memoize(&real_other) }; + + // It's only okay to memoize if there are no children and the props can be memoized + // Implementing memoize is unsafe and done automatically with the props trait match (props_memoized, children.len() == 0) { (true, true) => true, _ => false, @@ -395,7 +396,7 @@ impl<'a> NodeFactory<'a> { unsafe { std::mem::transmute::<_, Captured<'static>>(caller) } } - pub fn fragment_from_iter(self, node_iter: impl IntoVNodeList<'a> + 'a) -> VNode<'a> { + pub fn fragment_from_iter(self, node_iter: impl IntoVNodeList<'a>) -> VNode<'a> { let children = node_iter.into_vnode_list(self); VNode { @@ -501,37 +502,6 @@ impl<'a> IntoVNode<'a> for VNode<'a> { } } -// For the case where a rendered VNode is by reference passed into the rsx! macro through curly braces -// This behavior is designed for the cx.children method where child nodes are passed by reference. -// -// Designed to support indexing -impl<'a> IntoVNode<'a> for &VNode<'a> { - fn into_vnode(self, _: NodeFactory<'a>) -> VNode<'a> { - 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(), - // } - } -} - /// A concrete type provider for closures that build VNode structures. /// /// This struct wraps lazy structs that build VNode trees Normally, we cannot perform a blanket implementation over diff --git a/packages/core/src/scope.rs b/packages/core/src/scope.rs index 89b24d7bb..645f4f238 100644 --- a/packages/core/src/scope.rs +++ b/packages/core/src/scope.rs @@ -34,7 +34,6 @@ pub struct Scope { // Listeners pub(crate) listeners: RefCell>>, - pub(crate) listener_idx: Cell, // State pub(crate) hooks: HookList, @@ -72,9 +71,7 @@ impl Scope { vdom: SharedResources, ) -> Self { let child_nodes = unsafe { child_nodes.extend_lifetime() }; - // let child_nodes = unsafe { std::mem::transmute(child_nodes) }; - // dbg!(child_nodes); Self { child_nodes, caller, @@ -82,7 +79,6 @@ impl Scope { our_arena_idx: arena_idx, height, vdom, - listener_idx: Default::default(), frames: ActiveFrame::new(), hooks: Default::default(), shared_contexts: Default::default(), @@ -114,24 +110,27 @@ impl Scope { // make sure we call the drop implementation on all the listeners // this is important to not leak memory - 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(); + for listener in self + .listeners + .borrow_mut() + .drain(..) + .map(|li| unsafe { &*li }) + { + let mut cb = listener.callback.borrow_mut(); + match cb.take() { + Some(val) => std::mem::drop(val), + None => log::info!("no callback to drop. component must be broken"), + }; } - // self.listeners.borrow_mut().clear(); - + // Safety: + // - We dropped the listeners, so no more &mut T can be used while these are held + // - All children nodes that rely on &mut T are replaced with a new reference 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(); + // Safety: + // - We've dropped all references to the wip bump frame + unsafe { self.frames.reset_wip_frame() }; // Cast the caller ptr from static to one with our own reference let c3: &WrappedCaller = self.caller.as_ref(); diff --git a/packages/desktop/Cargo.toml b/packages/desktop/Cargo.toml index 2c12663ba..d3e474966 100644 --- a/packages/desktop/Cargo.toml +++ b/packages/desktop/Cargo.toml @@ -11,7 +11,7 @@ license = "MIT/Apache-2.0" [dependencies] # web-view = { git = "https://github.com/Boscop/web-view" } dioxus-core = { path = "../core", version = "0.1.2", features = ["serialize"] } -anyhow = "1.0.38" +anyhow = "1.0" argh = "0.1.4" serde = "1.0.120" serde_json = "1.0.61" diff --git a/packages/hooks/src/usestate.rs b/packages/hooks/src/usestate.rs index b9f3b6c20..f0d3abc43 100644 --- a/packages/hooks/src/usestate.rs +++ b/packages/hooks/src/usestate.rs @@ -52,7 +52,7 @@ use std::{ pub fn use_state<'a, 'c, T: 'static, F: FnOnce() -> T, P>( cx: Context<'a, P>, initial_state_fn: F, -) -> UseState { +) -> UseState<'a, T> { cx.use_hook( move |_| UseStateInner { current_val: initial_state_fn(), diff --git a/packages/mobile/Cargo.toml b/packages/mobile/Cargo.toml index 07e6fe7a3..c11d17ef4 100644 --- a/packages/mobile/Cargo.toml +++ b/packages/mobile/Cargo.toml @@ -7,7 +7,7 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -anyhow = "1.0.42" +anyhow = "1.0" # cacao = { git = "https://github.com/ryanmcgrath/cacao" } dioxus-core = { path = "../core", version = "0.1.2" } log = "0.4.14" diff --git a/packages/mobile/src/lib.rs b/packages/mobile/src/lib.rs index 69e0a73f7..9cd24460a 100644 --- a/packages/mobile/src/lib.rs +++ b/packages/mobile/src/lib.rs @@ -185,6 +185,7 @@ impl WebviewRenderer { }); } } +// brad johnson go chat fn main() { init_logging(); diff --git a/packages/ssr/Cargo.toml b/packages/ssr/Cargo.toml index e53dad3be..192346475 100644 --- a/packages/ssr/Cargo.toml +++ b/packages/ssr/Cargo.toml @@ -17,7 +17,7 @@ tide-websockets = "*" thiserror = "1.0.23" log = "0.4.13" fern = { version = "0.6.0", features = ["colored"] } -anyhow = "1.0.38" +anyhow = "1.0" argh = "0.1.4" serde = "1.0.120" serde_json = "1.0.61" diff --git a/packages/web/Cargo.toml b/packages/web/Cargo.toml index e23d4c76d..78000c4b7 100644 --- a/packages/web/Cargo.toml +++ b/packages/web/Cargo.toml @@ -22,7 +22,7 @@ generational-arena = "0.2.8" wasm-bindgen-test = "0.3.21" once_cell = "1.8" async-channel = "1.6.1" -anyhow = "1.0.41" +anyhow = "1.0" futures-util = "0.3.15"