diff --git a/Cargo.toml b/Cargo.toml index 6d78e7182..3ed970f1b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ dioxus-desktop = { path = "./packages/desktop", optional = true } dioxus-hooks = { path = "./packages/hooks", optional = true } dioxus-ssr = { path = "./packages/ssr", optional = true } dioxus-mobile = { path = "./packages/mobile", optional = true } +dioxus-liveview = { path = "./packages/liveview", optional = true } [features] # core @@ -26,6 +27,7 @@ macro = ["dioxus-core-macro"] hooks = ["dioxus-hooks"] html = ["dioxus-html"] router = ["dioxus-router"] +liveview = ["dioxus-liveview"] # utilities atoms = [] diff --git a/examples/async.rs b/examples/async.rs index bb05669e4..7143a9af6 100644 --- a/examples/async.rs +++ b/examples/async.rs @@ -11,7 +11,7 @@ async fn main() { dioxus::desktop::launch(App, |c| c); } -pub static App: FC<()> = |cx, _| { +pub static App: Component<()> = |cx, _| { let count = use_state(cx, || 0); let mut direction = use_state(cx, || 1); diff --git a/examples/calculator.rs b/examples/calculator.rs index 1a918879c..d17d35b84 100644 --- a/examples/calculator.rs +++ b/examples/calculator.rs @@ -13,7 +13,7 @@ fn main() { dioxus::desktop::launch(APP, |cfg| cfg); } -const APP: FC<()> = |cx, _| { +const APP: Component<()> = |cx, _| { let cur_val = use_state(cx, || 0.0_f64); let operator = use_state(cx, || None as Option<&'static str>); let display_value = use_state(cx, || String::from("")); diff --git a/examples/coroutine.rs b/examples/coroutine.rs index c2e025e16..2fa8e25e6 100644 --- a/examples/coroutine.rs +++ b/examples/coroutine.rs @@ -27,7 +27,7 @@ fn main() { use dioxus::prelude::*; -static App: FC<()> = |cx, props| { +static App: Component<()> = |cx, props| { let p1 = use_state(cx, || 0); let p2 = use_state(cx, || 0); diff --git a/examples/crm.rs b/examples/crm.rs index 3de5b7e3a..f146e0856 100644 --- a/examples/crm.rs +++ b/examples/crm.rs @@ -19,7 +19,7 @@ pub struct Client { pub description: String, } -static App: FC<()> = |cx, _| { +static App: Component<()> = |cx, _| { let mut clients = use_ref(cx, || vec![] as Vec); let mut scene = use_state(cx, || Scene::ClientsList); diff --git a/examples/file_explorer.rs b/examples/file_explorer.rs index 113f52216..74ea6a7d9 100644 --- a/examples/file_explorer.rs +++ b/examples/file_explorer.rs @@ -18,7 +18,7 @@ fn main() { }); } -static App: FC<()> = |cx, props| { +static App: Component<()> = |cx, props| { let file_manager = use_ref(cx, || Files::new()); let files = file_manager.read(); diff --git a/examples/framework_benchmark.rs b/examples/framework_benchmark.rs index 782081c41..0330357a5 100644 --- a/examples/framework_benchmark.rs +++ b/examples/framework_benchmark.rs @@ -30,7 +30,7 @@ impl Label { } } -static App: FC<()> = |cx, _props| { +static App: Component<()> = |cx, _props| { let mut items = use_ref(cx, || vec![]); let mut selected = use_state(cx, || None); diff --git a/examples/hydration.rs b/examples/hydration.rs index f50f740ac..22a81ffe7 100644 --- a/examples/hydration.rs +++ b/examples/hydration.rs @@ -19,7 +19,7 @@ fn main() { dioxus::desktop::launch(App, |c| c.with_prerendered(content)); } -static App: FC<()> = |cx, props| { +static App: Component<()> = |cx, props| { let mut val = use_state(cx, || 0); cx.render(rsx! { diff --git a/examples/manual_edits.rs b/examples/manual_edits.rs index a24b606f9..7b4f9aaf2 100644 --- a/examples/manual_edits.rs +++ b/examples/manual_edits.rs @@ -36,7 +36,7 @@ fn main() { dioxus_desktop::run(APP, (), |c| c.with_edits(edits)); } -const APP: FC<()> = |(cx, _props)| { +const APP: Component<()> = |(cx, _props)| { rsx!(cx, div { "some app" }) diff --git a/examples/pattern_model.rs b/examples/pattern_model.rs index d20477e81..518f60101 100644 --- a/examples/pattern_model.rs +++ b/examples/pattern_model.rs @@ -31,7 +31,7 @@ fn main() { }); } -static App: FC<()> = |cx, props| { +static App: Component<()> = |cx, props| { let state = use_ref(cx, || Calculator::new()); let clear_display = state.read().display_value.eq("0"); diff --git a/examples/pattern_reducer.rs b/examples/pattern_reducer.rs index 221e8eb0d..42c1976f9 100644 --- a/examples/pattern_reducer.rs +++ b/examples/pattern_reducer.rs @@ -11,7 +11,7 @@ fn main() { dioxus::desktop::launch(App, |c| c); } -pub static App: FC<()> = |cx, _| { +pub static App: Component<()> = |cx, _| { let state = use_state(cx, PlayerState::new); let is_playing = state.is_playing(); diff --git a/examples/readme.rs b/examples/readme.rs index 8eb8b098f..afff68f87 100644 --- a/examples/readme.rs +++ b/examples/readme.rs @@ -7,7 +7,7 @@ fn main() { dioxus::desktop::launch(App, |c| c); } -static App: FC<()> = |cx, props| { +static App: Component<()> = |cx, props| { let mut count = use_state(cx, || 0); cx.render(rsx! { diff --git a/examples/router.rs b/examples/router.rs index 5b021a807..66b86f56d 100644 --- a/examples/router.rs +++ b/examples/router.rs @@ -23,7 +23,7 @@ pub enum Route { NotFound, } -static App: FC<()> = |cx, props| { +static App: Component<()> = |cx, props| { let route = use_router(cx, Route::parse)?; cx.render(rsx! { diff --git a/examples/rsx_usage.rs b/examples/rsx_usage.rs index c0d69535e..7a69c6de9 100644 --- a/examples/rsx_usage.rs +++ b/examples/rsx_usage.rs @@ -49,7 +49,7 @@ const NONE_ELEMENT: Option<()> = None; use baller::Baller; use dioxus::prelude::*; -pub static Example: FC<()> = |cx, props| { +pub static Example: Component<()> = |cx, props| { let formatting = "formatting!"; let formatting_tuple = ("a", "b"); let lazy_fmt = format_args!("lazily formatted text"); diff --git a/examples/ssr.rs b/examples/ssr.rs index 539db8653..87bb4328e 100644 --- a/examples/ssr.rs +++ b/examples/ssr.rs @@ -9,7 +9,7 @@ fn main() { println!("{}", ssr::render_vdom(&vdom, |c| c)); } -static App: FC<()> = |cx, props| { +static App: Component<()> = |cx, props| { cx.render(rsx!( div { h1 { "Title" } diff --git a/examples/tailwind.rs b/examples/tailwind.rs index fe7100762..555d13d5d 100644 --- a/examples/tailwind.rs +++ b/examples/tailwind.rs @@ -14,7 +14,7 @@ fn main() { const STYLE: &str = "body {overflow:hidden;}"; -pub static App: FC<()> = |cx, props| { +pub static App: Component<()> = |cx, props| { cx.render(rsx!( div { class: "overflow-hidden" style { "{STYLE}" } @@ -30,7 +30,7 @@ pub static App: FC<()> = |cx, props| { )) }; -pub static Header: FC<()> = |cx, props| { +pub static Header: Component<()> = |cx, props| { cx.render(rsx! { div { header { class: "text-gray-400 bg-gray-900 body-font" @@ -56,7 +56,7 @@ pub static Header: FC<()> = |cx, props| { }) }; -pub static Hero: FC<()> = |cx, props| { +pub static Hero: Component<()> = |cx, props| { // cx.render(rsx! { section{ class: "text-gray-400 bg-gray-900 body-font" @@ -94,7 +94,7 @@ pub static Hero: FC<()> = |cx, props| { } }) }; -pub static Entry: FC<()> = |cx, props| { +pub static Entry: Component<()> = |cx, props| { // cx.render(rsx! { section{ class: "text-gray-400 bg-gray-900 body-font" @@ -107,7 +107,7 @@ pub static Entry: FC<()> = |cx, props| { }) }; -pub static StacksIcon: FC<()> = |cx, props| { +pub static StacksIcon: Component<()> = |cx, props| { cx.render(rsx!( svg { // xmlns: "http://www.w3.org/2000/svg" @@ -122,7 +122,7 @@ pub static StacksIcon: FC<()> = |cx, props| { } )) }; -pub static RightArrowIcon: FC<()> = |cx, props| { +pub static RightArrowIcon: Component<()> = |cx, props| { cx.render(rsx!( svg { fill: "none" diff --git a/examples/tasks.rs b/examples/tasks.rs index 54aa7ee5f..699ae1274 100644 --- a/examples/tasks.rs +++ b/examples/tasks.rs @@ -9,7 +9,7 @@ fn main() { dioxus::desktop::launch(App, |c| c); } -static App: FC<()> = |cx, props| { +static App: Component<()> = |cx, props| { let mut count = use_state(cx, || 0); cx.push_task(async move { diff --git a/examples/todomvc.rs b/examples/todomvc.rs index cfc49f530..9d8969c33 100644 --- a/examples/todomvc.rs +++ b/examples/todomvc.rs @@ -22,7 +22,7 @@ pub struct TodoItem { } const STYLE: &str = include_str!("./assets/todomvc.css"); -const App: FC<()> = |cx, props| { +const App: Component<()> = |cx, props| { let mut draft = use_state(cx, || "".to_string()); let mut todos = use_state(cx, || HashMap::>::new()); let mut filter = use_state(cx, || FilterState::All); diff --git a/examples/weather_app.rs b/examples/weather_app.rs index fca6c8cf7..20fde8a28 100644 --- a/examples/weather_app.rs +++ b/examples/weather_app.rs @@ -12,7 +12,7 @@ fn main() { const ENDPOINT: &str = "https://api.openweathermap.org/data/2.5/weather"; -static App: FC<()> = |cx, props| { +static App: Component<()> = |cx, props| { // let body = use_suspense( cx, @@ -40,7 +40,7 @@ static App: FC<()> = |cx, props| { #[derive(PartialEq, Props)] struct WeatherProps {} -static WeatherDisplay: FC = |cx, props| { +static WeatherDisplay: Component = |cx, props| { // cx.render(rsx!( div { class: "flex items-center justify-center flex-col" diff --git a/examples/web_tick.rs b/examples/web_tick.rs index 1e60af992..a1913e766 100644 --- a/examples/web_tick.rs +++ b/examples/web_tick.rs @@ -19,7 +19,7 @@ fn main() { dioxus::web::launch(App, |c| c); } -static App: FC<()> = |cx, props| { +static App: Component<()> = |cx, props| { let mut rng = SmallRng::from_entropy(); let rows = (0..1_000).map(|f| { let label = Label::new(&mut rng); diff --git a/examples/webview_web.rs b/examples/webview_web.rs index debe4d8e0..55f9f4309 100644 --- a/examples/webview_web.rs +++ b/examples/webview_web.rs @@ -16,7 +16,7 @@ fn main() { dioxus::web::launch(App, |c| c); } -static App: FC<()> = |cx, props| { +static App: Component<()> = |cx, props| { let mut count = use_state(cx, || 0); cx.render(rsx! { diff --git a/packages/core/benches/jsframework.rs b/packages/core/benches/jsframework.rs index 4f09f0202..f8e53bc88 100644 --- a/packages/core/benches/jsframework.rs +++ b/packages/core/benches/jsframework.rs @@ -23,7 +23,7 @@ criterion_group!(mbenches, create_rows); criterion_main!(mbenches); fn create_rows(c: &mut Criterion) { - static App: FC<()> = |cx, _| { + static App: Component<()> = |cx, _| { let mut rng = SmallRng::from_entropy(); let rows = (0..10_000_usize).map(|f| { let label = Label::new(&mut rng); diff --git a/packages/core/examples/handler_thru_props.rs b/packages/core/examples/handler_thru_props.rs new file mode 100644 index 000000000..368a58894 --- /dev/null +++ b/packages/core/examples/handler_thru_props.rs @@ -0,0 +1,34 @@ +#![allow(non_snake_case)] + +use dioxus::prelude::*; +use dioxus_core as dioxus; +use dioxus_core_macro::*; +use dioxus_html as dioxus_elements; + +fn main() { + let _ = VirtualDom::new(App); +} + +fn App(cx: Context, _props: &()) -> Element { + // + cx.render(rsx!( + div { + Child {} + } + )) +} + +struct ChildProps<'a> { + click_handler: EventHandler<'a>, +} + +fn Child(cx: Context, _props: &()) -> Element { + // + cx.render(rsx!( + div { + h1 { + "Hello, World!" + } + } + )) +} diff --git a/packages/core/examples/rows.rs b/packages/core/examples/rows.rs index 7506c418f..40ab0af7a 100644 --- a/packages/core/examples/rows.rs +++ b/packages/core/examples/rows.rs @@ -20,7 +20,7 @@ use dioxus_html as dioxus_elements; use rand::prelude::*; fn main() { - static App: FC<()> = |cx, _| { + static App: Component<()> = |cx, _| { let mut rng = SmallRng::from_entropy(); let rows = (0..10_000_usize).map(|f| { let label = Label::new(&mut rng); diff --git a/packages/core/examples/works.rs b/packages/core/examples/works.rs index 45104daa0..7e49da1ce 100644 --- a/packages/core/examples/works.rs +++ b/packages/core/examples/works.rs @@ -1,3 +1,5 @@ +#![allow(non_snake_case)] + use dioxus::prelude::*; use dioxus_core as dioxus; use dioxus_core_macro::*; @@ -7,8 +9,8 @@ fn main() { let _ = VirtualDom::new(Parent); } -fn Parent(cx: Context, props: &()) -> Element { - let value = cx.use_hook(|_| String::new(), |f| &*f); +fn Parent(cx: Context, _props: &()) -> Element { + let value = cx.use_hook(|_| String::new(), |f| f); cx.render(rsx! { div { diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 8bf67b3d0..a738faeeb 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -105,11 +105,11 @@ use DomEdit::*; /// /// Funnily enough, this stack machine's entire job is to create instructions for another stack machine to execute. It's /// stack machines all the way down! -pub struct DiffState<'bump> { - scopes: &'bump ScopeArena, - pub mutations: Mutations<'bump>, +pub(crate) struct DiffState<'bump> { + pub(crate) scopes: &'bump ScopeArena, + pub(crate) mutations: Mutations<'bump>, pub(crate) stack: DiffStack<'bump>, - pub force_diff: bool, + pub(crate) force_diff: bool, } impl<'bump> DiffState<'bump> { @@ -149,7 +149,7 @@ pub(crate) enum DiffInstruction<'a> { } #[derive(Debug, Clone, Copy)] -pub enum MountType<'a> { +pub(crate) enum MountType<'a> { Absorb, Append, Replace { old: &'a VNode<'a> }, @@ -159,9 +159,9 @@ pub enum MountType<'a> { pub(crate) struct DiffStack<'bump> { pub(crate) instructions: Vec>, - nodes_created_stack: SmallVec<[usize; 10]>, - pub scope_stack: SmallVec<[ScopeId; 5]>, - pub element_stack: SmallVec<[ElementId; 10]>, + pub(crate) nodes_created_stack: SmallVec<[usize; 10]>, + pub(crate) scope_stack: SmallVec<[ScopeId; 5]>, + pub(crate) element_stack: SmallVec<[ElementId; 10]>, } impl<'bump> DiffStack<'bump> { diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index f656b5a18..ce9ad0b2b 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -1,17 +1,6 @@ #![allow(non_snake_case)] #![doc = include_str!("../README.md")] -/* -Navigating this crate: -- virtual_dom: the primary entrypoint for the crate -- scheduler: the core interior logic called by the [`VirtualDom`] -- nodes: the definition of VNodes, listeners, etc. -- diff: the stackmachine-based diffing algorithm -- hooks: foundational hooks that require crate-private APIs -- mutations: DomEdits/NodeRefs and internal API to create them - -Some utilities -*/ pub(crate) mod component; pub(crate) mod diff; pub(crate) mod lazynodes; @@ -23,7 +12,7 @@ pub(crate) mod virtual_dom; pub(crate) mod innerlude { pub use crate::component::*; - pub use crate::diff::*; + pub(crate) use crate::diff::*; pub use crate::lazynodes::*; pub use crate::mutations::*; pub use crate::nodes::*; @@ -32,19 +21,21 @@ pub(crate) mod innerlude { pub use crate::virtual_dom::*; pub type Element = Option; - pub type FC

= for<'a> fn(Context<'a>, &'a P) -> Element; + pub type Component

= for<'a> fn(Context<'a>, &'a P) -> Element; } pub use crate::innerlude::{ - Attribute, Context, DioxusElement, DomEdit, Element, ElementId, EventPriority, IntoVNode, - LazyNodes, Listener, MountType, Mutations, NodeFactory, Properties, SchedulerMsg, ScopeId, - UserEvent, VElement, VFragment, VNode, VirtualDom, FC, + Attribute, Component, Context, DioxusElement, DomEdit, Element, ElementId, EventHandler, + EventPriority, IntoVNode, LazyNodes, Listener, Mutations, NodeFactory, Properties, + SchedulerMsg, ScopeId, UserEvent, VElement, VFragment, VNode, VirtualDom, }; pub mod prelude { pub use crate::component::{fc_to_builder, Fragment, Properties}; pub use crate::innerlude::Context; - pub use crate::innerlude::{DioxusElement, Element, LazyNodes, NodeFactory, Scope, FC}; + pub use crate::innerlude::{ + Component, DioxusElement, Element, EventHandler, LazyNodes, NodeFactory, Scope, + }; pub use crate::nodes::VNode; pub use crate::VirtualDom; } diff --git a/packages/core/src/mutations.rs b/packages/core/src/mutations.rs index e7c54cdca..862a42df4 100644 --- a/packages/core/src/mutations.rs +++ b/packages/core/src/mutations.rs @@ -16,7 +16,6 @@ pub struct Mutations<'a> { pub edits: Vec>, pub dirty_scopes: FxHashSet, pub refs: Vec>, - pub effects: Vec<&'a dyn FnMut()>, } impl Debug for Mutations<'_> { @@ -113,7 +112,6 @@ impl<'a> Mutations<'a> { edits: Vec::new(), refs: Vec::new(), dirty_scopes: Default::default(), - effects: Vec::new(), } } diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index f02b553b5..aedd5b90c 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -338,9 +338,32 @@ pub struct Listener<'bump> { pub event: &'static str, /// The actual callback that the user specified - #[allow(clippy::type_complexity)] - pub(crate) callback: - RefCell) + 'bump>>>, + pub(crate) callback: EventHandler<'bump>, +} + +pub struct EventHandler<'bump> { + pub callback: &'bump RefCell>>, +} + +impl EventHandler<'_> { + pub fn call(&self, event: Arc) { + if let Some(callback) = self.callback.borrow_mut().as_mut() { + callback(event); + } + } + pub fn release(&self) { + self.callback.replace(None); + } +} +type ListenerCallback<'bump> = BumpBox<'bump, dyn FnMut(Arc) + 'bump>; + +impl Copy for EventHandler<'_> {} +impl Clone for EventHandler<'_> { + fn clone(&self) -> Self { + Self { + callback: self.callback, + } + } } /// A cached node is a "pointer" to a "rendered" node in a particular scope @@ -608,15 +631,11 @@ impl<'a> NodeFactory<'a> { })) } - pub fn listener( - self, - event: &'static str, - callback: BumpBox<'a, dyn FnMut(Arc) + 'a>, - ) -> Listener<'a> { + pub fn listener(self, event: &'static str, callback: EventHandler<'a>) -> Listener<'a> { Listener { event, mounted_node: Cell::new(None), - callback: RefCell::new(Some(callback)), + callback, } } diff --git a/packages/core/src/scope.rs b/packages/core/src/scope.rs index debea22de..9f30019d1 100644 --- a/packages/core/src/scope.rs +++ b/packages/core/src/scope.rs @@ -7,6 +7,7 @@ use std::{ cell::{Cell, RefCell}, collections::HashMap, future::Future, + pin::Pin, rc::Rc, }; @@ -32,6 +33,14 @@ use bumpalo::{boxed::Box as BumpBox, Bump}; /// ``` pub type Context<'a> = &'a Scope; +/// A component's unique identifier. +/// +/// `ScopeId` is a `usize` that is unique across the entire VirtualDOM and across time. ScopeIDs will never be reused +/// once a component has been unmounted. +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub struct ScopeId(pub usize); + /// Every component in Dioxus is represented by a `Scope`. /// /// Scopes contain the state for hooks, the component's props, and other lifecycle information. @@ -44,7 +53,6 @@ pub type Context<'a> = &'a Scope; pub struct Scope { pub(crate) parent_scope: Option<*mut Scope>, - // parent element I think? pub(crate) container: ElementId, pub(crate) our_arena_idx: ScopeId, @@ -75,17 +83,9 @@ pub struct Scope { pub struct SelfReferentialItems<'a> { pub(crate) listeners: Vec<&'a Listener<'a>>, pub(crate) borrowed_props: Vec<&'a VComponent<'a>>, - pub(crate) tasks: Vec>>, + pub(crate) tasks: Vec>>>, } -/// A component's unique identifier. -/// -/// `ScopeId` is a `usize` that is unique across the entire VirtualDOM and across time. ScopeIDs will never be reused -/// once a component has been unmounted. -#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] -#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] -pub struct ScopeId(pub usize); - // Public methods exposed to libraries and components impl Scope { /// Get the subtree ID that this scope belongs to. @@ -227,11 +227,11 @@ impl Scope { let _ = self.sender.unbounded_send(SchedulerMsg::Immediate(id)); } - /// Get the [`ScopeId`] of a mounted component. - /// - /// `ScopeId` is not unique for the lifetime of the VirtualDom - a ScopeId will be reused if a component is unmounted. - pub fn bump(&self) -> &Bump { - &self.wip_frame().bump + /// Get the Root Node of this scope + pub fn root_node(&self) -> &VNode { + todo!("Portals have changed how we address nodes. Still fixing this, sorry."); + // let node = *self.wip_frame().nodes.borrow().get(0).unwrap(); + // unsafe { std::mem::transmute(&*node) } } /// This method enables the ability to expose state to children further down the VirtualDOM Tree. @@ -274,6 +274,7 @@ impl Scope { let mut search_parent = self.parent_scope; while let Some(parent_ptr) = search_parent { + // safety: all parent pointers are valid thanks to the bump arena let parent = unsafe { &*parent_ptr }; if let Some(shared) = parent.shared_contexts.borrow().get(&TypeId::of::()) { return Some(shared.clone().downcast::().unwrap()); @@ -286,15 +287,10 @@ impl Scope { /// Pushes the future onto the poll queue to be polled after the component renders. /// - /// - /// - /// /// The future is forcibly dropped if the component is not ready by the next render - pub fn push_task<'src, F: Future>( - &'src self, - fut: impl FnOnce() -> F + 'src, - ) -> usize + pub fn push_task<'src, F>(&'src self, fut: impl FnOnce() -> F + 'src) -> usize where + F: Future, F::Output: 'src, F: 'src, { @@ -302,18 +298,20 @@ impl Scope { .unbounded_send(SchedulerMsg::NewTask(self.our_arena_idx)) .unwrap(); - // allocate the future - let fut = fut(); - let fut: &mut dyn Future = self.bump().alloc(fut); - // wrap it in a type that will actually drop the contents + // + // Safety: we just made the pointer above and will promise not to alias it! + // The main reason we do this through from_raw is because Bumpalo's box does + // not support unsized coercion + let fut: &mut dyn Future = self.bump().alloc(fut()); let boxed_fut: BumpBox> = unsafe { BumpBox::from_raw(fut) }; + let pinned_fut: Pin> = boxed_fut.into(); // erase the 'src lifetime for self-referential storage - let self_ref_fut = unsafe { std::mem::transmute(boxed_fut) }; + let self_ref_fut = unsafe { std::mem::transmute(pinned_fut) }; + // Push the future into the tasks let mut items = self.items.borrow_mut(); - items.tasks.push(self_ref_fut); items.tasks.len() - 1 } @@ -334,22 +332,19 @@ impl Scope { /// } ///``` pub fn render<'src>(&'src self, rsx: Option>) -> Option { - let frame = self.wip_frame(); - let bump = &frame.bump; - let factory = NodeFactory { bump }; - let node = rsx.map(|f| f.call(factory))?; - let node = bump.alloc(node); + let bump = &self.wip_frame().bump; - let node_ptr = node as *mut _; - let node_ptr = unsafe { std::mem::transmute(node_ptr) }; + let owned_node: VNode<'src> = rsx.map(|f| f.call(NodeFactory { bump }))?; + let alloced_vnode: &'src mut VNode<'src> = bump.alloc(owned_node); + let node_ptr: *mut VNode<'src> = alloced_vnode as *mut _; - let link = VPortal { + let node: *mut VNode<'static> = unsafe { std::mem::transmute(node_ptr) }; + + Some(VPortal { scope_id: Cell::new(Some(self.our_arena_idx)), link_idx: Cell::new(0), - node: node_ptr, - }; - - Some(link) + node, + }) } /// Store a value between renders @@ -379,12 +374,12 @@ impl Scope { runner: impl FnOnce(&'src mut State) -> Output, ) -> Output { let mut vals = self.hook_vals.borrow_mut(); + let hook_len = vals.len(); let cur_idx = self.hook_idx.get(); if cur_idx >= hook_len { - let val = self.hook_arena.alloc(initializer(hook_len)); - vals.push(val); + vals.push(self.hook_arena.alloc(initializer(hook_len))); } let state = vals @@ -415,6 +410,7 @@ impl Scope { } } + /// Mutable access to the "work in progress frame" - used to clear it pub(crate) fn wip_frame_mut(&mut self) -> &mut BumpFrame { match self.generation.get() & 1 == 0 { true => &mut self.frames[0], @@ -422,6 +418,7 @@ impl Scope { } } + /// Access to the frame where finalized nodes existed pub(crate) fn fin_frame(&self) -> &BumpFrame { match self.generation.get() & 1 == 1 { true => &self.frames[0], @@ -432,20 +429,23 @@ impl Scope { /// Reset this component's frame /// /// # Safety: + /// /// This method breaks every reference of VNodes in the current frame. + /// + /// Calling reset itself is not usually a big deal, but we consider it important + /// due to the complex safety guarantees we need to uphold. pub(crate) unsafe fn reset_wip_frame(&mut self) { - // todo: unsafecell or something - let bump = self.wip_frame_mut(); - bump.bump.reset(); + self.wip_frame_mut().bump.reset(); } + /// Cycle to the next generation pub(crate) fn cycle_frame(&self) { self.generation.set(self.generation.get() + 1); } - pub fn root_node(&self) -> &VNode { - let node = *self.wip_frame().nodes.borrow().get(0).unwrap(); - unsafe { std::mem::transmute(&*node) } + /// Get the [`Bump`] of the WIP frame. + pub(crate) fn bump(&self) -> &Bump { + &self.wip_frame().bump } } @@ -454,7 +454,7 @@ pub(crate) struct BumpFrame { pub nodes: RefCell>>, } impl BumpFrame { - pub fn new(capacity: usize) -> Self { + pub(crate) fn new(capacity: usize) -> Self { let bump = Bump::with_capacity(capacity); let node = &*bump.alloc(VText { @@ -467,7 +467,7 @@ impl BumpFrame { Self { bump, nodes } } - pub fn assign_nodelink(&self, node: &VPortal) { + pub(crate) fn assign_nodelink(&self, node: &VPortal) { let mut nodes = self.nodes.borrow_mut(); let len = nodes.len(); diff --git a/packages/core/src/scopearena.rs b/packages/core/src/scopearena.rs index 1b2643d20..4f871e401 100644 --- a/packages/core/src/scopearena.rs +++ b/packages/core/src/scopearena.rs @@ -9,9 +9,9 @@ use std::{ use crate::innerlude::*; -pub type FcSlot = *const (); +pub(crate) type FcSlot = *const (); -pub struct Heuristic { +pub(crate) struct Heuristic { hook_arena_size: usize, node_arena_size: usize, } @@ -92,15 +92,8 @@ impl ScopeArena { let new_scope_id = ScopeId(self.scope_counter.get()); self.scope_counter.set(self.scope_counter.get() + 1); - // log::debug!("new scope {:?} with parent {:?}", new_scope_id, container); - if let Some(old_scope) = self.free_scopes.borrow_mut().pop() { let scope = unsafe { &mut *old_scope }; - // log::debug!( - // "reusing scope {:?} as {:?}", - // scope.our_arena_idx, - // new_scope_id - // ); scope.caller = caller; scope.parent_scope = parent_scope; @@ -203,8 +196,6 @@ impl ScopeArena { pub fn try_remove(&self, id: &ScopeId) -> Option<()> { self.ensure_drop_safety(id); - // log::debug!("removing scope {:?}", id); - // Safety: // - ensure_drop_safety ensures that no references to this scope are in use // - this raw pointer is removed from the map @@ -281,30 +272,30 @@ impl ScopeArena { /// This also makes sure that drop order is consistent and predictable. All resources that rely on being dropped will /// be dropped. pub(crate) fn ensure_drop_safety(&self, scope_id: &ScopeId) { - let scope = self.get_scope(scope_id).unwrap(); + if let Some(scope) = self.get_scope(scope_id) { + let mut items = scope.items.borrow_mut(); - let mut items = scope.items.borrow_mut(); + // make sure we drop all borrowed props manually to guarantee that their drop implementation is called before we + // run the hooks (which hold an &mut Reference) + // recursively call ensure_drop_safety on all children + items.borrowed_props.drain(..).for_each(|comp| { + let scope_id = comp + .associated_scope + .get() + .expect("VComponents should be associated with a valid Scope"); - // make sure we drop all borrowed props manually to guarantee that their drop implementation is called before we - // run the hooks (which hold an &mut Reference) - // recursively call ensure_drop_safety on all children - items.borrowed_props.drain(..).for_each(|comp| { - let scope_id = comp - .associated_scope - .get() - .expect("VComponents should be associated with a valid Scope"); + self.ensure_drop_safety(&scope_id); - self.ensure_drop_safety(&scope_id); + let mut drop_props = comp.drop_props.borrow_mut().take().unwrap(); + drop_props(); + }); - let mut drop_props = comp.drop_props.borrow_mut().take().unwrap(); - drop_props(); - }); - - // Now that all the references are gone, we can safely drop our own references in our listeners. - items - .listeners - .drain(..) - .for_each(|listener| drop(listener.callback.borrow_mut().take())); + // Now that all the references are gone, we can safely drop our own references in our listeners. + items + .listeners + .drain(..) + .for_each(|listener| drop(listener.callback.callback.borrow_mut().take())); + } } pub(crate) fn run_scope(&self, id: &ScopeId) -> bool { @@ -375,7 +366,7 @@ impl ScopeArena { // for listener in real_el.listeners.borrow().iter() { if listener.event == event.name { - let mut cb = listener.callback.borrow_mut(); + let mut cb = listener.callback.callback.borrow_mut(); if let Some(cb) = cb.as_mut() { (cb)(event.data.clone()); } diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 1de0e83aa..4980f15c5 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -140,7 +140,7 @@ impl VirtualDom { /// ``` /// /// Note: the VirtualDOM is not progressed, you must either "run_with_deadline" or use "rebuild" to progress it. - pub fn new(root: FC<()>) -> Self { + pub fn new(root: Component<()>) -> Self { Self::new_with_props(root, ()) } @@ -174,7 +174,7 @@ impl VirtualDom { /// let mut dom = VirtualDom::new_with_props(Example, SomeProps { name: "jane" }); /// let mutations = dom.rebuild(); /// ``` - pub fn new_with_props(root: FC

, root_props: P) -> Self { + pub fn new_with_props(root: Component

, root_props: P) -> Self { let (sender, receiver) = futures_channel::mpsc::unbounded::(); Self::new_with_props_and_scheduler(root, root_props, sender, receiver) } @@ -183,8 +183,13 @@ impl VirtualDom { /// /// This is useful when the VirtualDom must be driven from outside a thread and it doesn't make sense to wait for the /// VirtualDom to be created just to retrieve its channel receiver. + /// + /// ```rust + /// let (sender, receiver) = futures_channel::mpsc::unbounded(); + /// let dom = VirtualDom::new_with_scheduler(Example, (), sender, receiver); + /// ``` pub fn new_with_props_and_scheduler( - root: FC

, + root: Component

, root_props: P, sender: UnboundedSender, receiver: UnboundedReceiver, @@ -235,27 +240,55 @@ impl VirtualDom { /// # Example /// /// ```rust, ignore - /// - /// + /// let dom = VirtualDom::new(App); + /// let sender = dom.get_scheduler_channel(); /// ``` pub fn get_scheduler_channel(&self) -> futures_channel::mpsc::UnboundedSender { self.sender.clone() } + /// Add a new message to the scheduler queue directly. + /// + /// + /// This method makes it possible to send messages to the scheduler from outside the VirtualDom without having to + /// call `get_schedule_channel` and then `send`. + /// + /// # Example + /// ```rust, ignore + /// let dom = VirtualDom::new(App); + /// dom.insert_scheduler_message(SchedulerMsg::Immediate(ScopeId(0))); + /// ``` + pub fn insert_scheduler_message(&self, msg: SchedulerMsg) { + self.sender.unbounded_send(msg).unwrap() + } + /// Check if the [`VirtualDom`] has any pending updates or work to be done. /// /// # Example /// /// ```rust, ignore + /// let dom = VirtualDom::new(App); /// - /// + /// // the dom is "dirty" when it is started and must be rebuilt to get the first render + /// assert!(dom.has_any_work()); /// ``` - pub fn has_any_work(&self) -> bool { + pub fn has_work(&self) -> bool { !(self.dirty_scopes.is_empty() && self.pending_messages.is_empty()) } - /// Waits for the scheduler to have work + /// Wait for the scheduler to have any work. + /// + /// This method polls the internal future queue *and* the scheduler channel. + /// To add work to the VirtualDom, insert a message via the scheduler channel. + /// /// This lets us poll async tasks during idle periods without blocking the main thread. + /// + /// # Example + /// + /// ```rust, ignore + /// let dom = VirtualDom::new(App); + /// let sender = dom.get_scheduler_channel(); + /// ``` pub async fn wait_for_work(&mut self) { loop { if !self.dirty_scopes.is_empty() && self.pending_messages.is_empty() { @@ -390,9 +423,10 @@ impl VirtualDom { committed_mutations.push(mutations); } else { - log::debug!("Could not finish work in time"); - // leave the work in an incomplete state + // + // todo: we should store the edits and re-apply them later + // for now, we just dump the work completely (threadsafe) return committed_mutations; } } @@ -400,7 +434,7 @@ impl VirtualDom { committed_mutations } - /// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom from scratch + /// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom from scratch. /// /// The diff machine expects the RealDom's stack to be the root of the application. /// @@ -475,6 +509,15 @@ impl VirtualDom { /// Renders an `rsx` call into the Base Scope's allocator. /// /// Useful when needing to render nodes from outside the VirtualDom, such as in a test. + /// + /// ```rust + /// fn Base(cx: Context, props: &()) -> Element { + /// rsx!(cx, div {}) + /// } + /// + /// let dom = VirtualDom::new(Base); + /// let nodes = dom.render_nodes(rsx!("div")); + /// ``` pub fn render_vnodes<'a>(&'a self, lazy_nodes: Option>) -> &'a VNode<'a> { let scope = self.scopes.get_scope(&self.base_scope).unwrap(); let frame = scope.wip_frame(); @@ -485,7 +528,16 @@ impl VirtualDom { /// Renders an `rsx` call into the Base Scope's allocator. /// - /// Useful when needing to render nodes from outside the VirtualDom, such as in a test. + /// Useful when needing to render nodes from outside the VirtualDom, such as in a test. + /// + /// ```rust + /// fn Base(cx: Context, props: &()) -> Element { + /// rsx!(cx, div {}) + /// } + /// + /// let dom = VirtualDom::new(Base); + /// let nodes = dom.render_nodes(rsx!("div")); + /// ``` pub fn diff_vnodes<'a>(&'a self, old: &'a VNode<'a>, new: &'a VNode<'a>) -> Mutations<'a> { let mut machine = DiffState::new(&self.scopes); machine.stack.push(DiffInstruction::Diff { new, old }); @@ -498,6 +550,16 @@ impl VirtualDom { /// Renders an `rsx` call into the Base Scope's allocator. /// /// Useful when needing to render nodes from outside the VirtualDom, such as in a test. + /// + /// + /// ```rust + /// fn Base(cx: Context, props: &()) -> Element { + /// rsx!(cx, div {}) + /// } + /// + /// let dom = VirtualDom::new(Base); + /// let nodes = dom.render_nodes(rsx!("div")); + /// ``` pub fn create_vnodes<'a>(&'a self, left: Option>) -> Mutations<'a> { let nodes = self.render_vnodes(left); let mut machine = DiffState::new(&self.scopes); @@ -507,9 +569,19 @@ impl VirtualDom { machine.mutations } - /// Renders an `rsx` call into the Base Scope's allocator. + /// Renders an `rsx` call into the Base Scopes's arena. /// - /// Useful when needing to render nodes from outside the VirtualDom, such as in a test. + /// Useful when needing to diff two rsx! calls from outside the VirtualDom, such as in a test. + /// + /// + /// ```rust + /// fn Base(cx: Context, props: &()) -> Element { + /// rsx!(cx, div {}) + /// } + /// + /// let dom = VirtualDom::new(Base); + /// let nodes = dom.render_nodes(rsx!("div")); + /// ``` pub fn diff_lazynodes<'a>( &'a self, left: Option>, @@ -666,14 +738,7 @@ impl<'a> Future for PollTasks<'a> { // really this should just be retain_mut but that doesn't exist yet while let Some(mut task) = items.tasks.pop() { - // todo: does this make sense? - // I don't usually write futures by hand - // I think the futures neeed to be pinned using bumpbox or something - // right now, they're bump allocated so this shouldn't matter anyway - they're not going to move - let task_mut = task.as_mut(); - let pinned = unsafe { Pin::new_unchecked(task_mut) }; - - if pinned.poll(cx).is_ready() { + if task.as_mut().poll(cx).is_ready() { all_pending = false } else { unfinished_tasks.push(task); diff --git a/packages/core/tests/borrowedstate.rs b/packages/core/tests/borrowedstate.rs index 692a8f8db..fbfcf5096 100644 --- a/packages/core/tests/borrowedstate.rs +++ b/packages/core/tests/borrowedstate.rs @@ -10,7 +10,7 @@ fn test_borrowed_state() { let _ = VirtualDom::new(Parent); } -fn Parent(cx: Context, props: &()) -> Element { +fn Parent(cx: Context, _props: &()) -> Element { let value = cx.use_hook(|_| String::new(), |f| &*f); cx.render(rsx! { diff --git a/packages/core/tests/create_dom.rs b/packages/core/tests/create_dom.rs index fce8d3f15..f6e1d57e6 100644 --- a/packages/core/tests/create_dom.rs +++ b/packages/core/tests/create_dom.rs @@ -1,4 +1,4 @@ -#![allow(unused, non_upper_case_globals)] +#![allow(unused, non_upper_case_globals, non_snake_case)] //! Prove that the dom works normally through virtualdom methods. //! @@ -13,7 +13,7 @@ use dioxus_html as dioxus_elements; mod test_logging; use DomEdit::*; -fn new_dom(app: FC

, props: P) -> VirtualDom { +fn new_dom(app: Component

, props: P) -> VirtualDom { const IS_LOGGING_ENABLED: bool = false; test_logging::set_up_logging(IS_LOGGING_ENABLED); VirtualDom::new_with_props(app, props) @@ -21,7 +21,7 @@ fn new_dom(app: FC

, props: P) -> VirtualDom { #[test] fn test_original_diff() { - static APP: FC<()> = |cx, props| { + static APP: Component<()> = |cx, props| { cx.render(rsx! { div { div { @@ -57,7 +57,7 @@ fn test_original_diff() { #[test] fn create() { - static APP: FC<()> = |cx, props| { + static APP: Component<()> = |cx, props| { cx.render(rsx! { div { div { @@ -120,7 +120,7 @@ fn create() { #[test] fn create_list() { - static APP: FC<()> = |cx, props| { + static APP: Component<()> = |cx, props| { cx.render(rsx! { {(0..3).map(|f| rsx!{ div { "hello" @@ -169,7 +169,7 @@ fn create_list() { #[test] fn create_simple() { - static APP: FC<()> = |cx, props| { + static APP: Component<()> = |cx, props| { cx.render(rsx! { div {} div {} @@ -207,7 +207,7 @@ fn create_simple() { } #[test] fn create_components() { - static App: FC<()> = |cx, props| { + static App: Component<()> = |cx, props| { cx.render(rsx! { Child { "abc1" } Child { "abc2" } @@ -273,7 +273,7 @@ fn create_components() { } #[test] fn anchors() { - static App: FC<()> = |cx, props| { + static App: Component<()> = |cx, props| { cx.render(rsx! { {true.then(|| rsx!{ div { "hello" } })} {false.then(|| rsx!{ div { "goodbye" } })} diff --git a/packages/core/tests/lifecycle.rs b/packages/core/tests/lifecycle.rs index 657d34f52..1a7c4075e 100644 --- a/packages/core/tests/lifecycle.rs +++ b/packages/core/tests/lifecycle.rs @@ -22,7 +22,7 @@ fn manual_diffing() { value: Shared<&'static str>, } - static App: FC = |cx, props| { + static App: Component = |cx, props| { let val = props.value.lock().unwrap(); cx.render(rsx! { div { "{val}" } }) }; @@ -46,7 +46,7 @@ fn manual_diffing() { #[test] fn events_generate() { - static App: FC<()> = |cx, _| { + static App: Component<()> = |cx, _| { let mut count = use_state(cx, || 0); let inner = match *count { @@ -69,7 +69,7 @@ fn events_generate() { let mut dom = VirtualDom::new(App); let mut channel = dom.get_scheduler_channel(); - assert!(dom.has_any_work()); + assert!(dom.has_work()); let edits = dom.rebuild(); assert_eq!( @@ -105,7 +105,7 @@ fn events_generate() { #[test] fn components_generate() { - static App: FC<()> = |cx, _| { + static App: Component<()> = |cx, _| { let mut render_phase = use_state(cx, || 0); render_phase += 1; @@ -122,7 +122,7 @@ fn components_generate() { }) }; - static Child: FC<()> = |cx, _| { + static Child: Component<()> = |cx, _| { cx.render(rsx! { h1 {} }) @@ -222,7 +222,7 @@ fn components_generate() { #[test] fn component_swap() { // simple_logger::init(); - static App: FC<()> = |cx, _| { + static App: Component<()> = |cx, _| { let mut render_phase = use_state(cx, || 0); render_phase += 1; @@ -261,7 +261,7 @@ fn component_swap() { }) }; - static NavBar: FC<()> = |cx, _| { + static NavBar: Component<()> = |cx, _| { println!("running navbar"); cx.render(rsx! { h1 { @@ -271,7 +271,7 @@ fn component_swap() { }) }; - static NavLink: FC<()> = |cx, _| { + static NavLink: Component<()> = |cx, _| { println!("running navlink"); cx.render(rsx! { h1 { @@ -280,7 +280,7 @@ fn component_swap() { }) }; - static Dashboard: FC<()> = |cx, _| { + static Dashboard: Component<()> = |cx, _| { println!("running dashboard"); cx.render(rsx! { div { @@ -289,7 +289,7 @@ fn component_swap() { }) }; - static Results: FC<()> = |cx, _| { + static Results: Component<()> = |cx, _| { println!("running results"); cx.render(rsx! { div { diff --git a/packages/core/tests/sharedstate.rs b/packages/core/tests/sharedstate.rs index 9ec4a6a8d..4e8b84484 100644 --- a/packages/core/tests/sharedstate.rs +++ b/packages/core/tests/sharedstate.rs @@ -13,12 +13,12 @@ mod test_logging; fn shared_state_test() { struct MySharedState(&'static str); - static App: FC<()> = |cx, props| { + static App: Component<()> = |cx, props| { cx.provide_state(MySharedState("world!")); cx.render(rsx!(Child {})) }; - static Child: FC<()> = |cx, props| { + static Child: Component<()> = |cx, props| { let shared = cx.consume_state::()?; cx.render(rsx!("Hello, {shared.0}")) }; diff --git a/packages/core/tests/vdom_rebuild.rs b/packages/core/tests/vdom_rebuild.rs index dc7b76755..fcbcfbe43 100644 --- a/packages/core/tests/vdom_rebuild.rs +++ b/packages/core/tests/vdom_rebuild.rs @@ -19,7 +19,7 @@ use DomEdit::*; #[test] fn app_runs() { - static App: FC<()> = |cx, props| rsx!(cx, div{"hello"} ); + static App: Component<()> = |cx, props| rsx!(cx, div{"hello"} ); let mut vdom = VirtualDom::new(App); let edits = vdom.rebuild(); @@ -27,7 +27,7 @@ fn app_runs() { #[test] fn fragments_work() { - static App: FC<()> = |cx, props| { + static App: Component<()> = |cx, props| { cx.render(rsx!( div{"hello"} div{"goodbye"} @@ -41,7 +41,7 @@ fn fragments_work() { #[test] fn lists_work() { - static App: FC<()> = |cx, props| { + static App: Component<()> = |cx, props| { cx.render(rsx!( h1 {"hello"} {(0..6).map(|f| rsx!(span{ "{f}" }))} @@ -54,7 +54,7 @@ fn lists_work() { #[test] fn conditional_rendering() { - static App: FC<()> = |cx, props| { + static App: Component<()> = |cx, props| { cx.render(rsx!( h1 {"hello"} {true.then(|| rsx!(span{ "a" }))} @@ -87,13 +87,13 @@ fn conditional_rendering() { #[test] fn child_components() { - static App: FC<()> = |cx, props| { + static App: Component<()> = |cx, props| { cx.render(rsx!( {true.then(|| rsx!(Child { }))} {false.then(|| rsx!(Child { }))} )) }; - static Child: FC<()> = |cx, props| { + static Child: Component<()> = |cx, props| { cx.render(rsx!( h1 {"hello"} h1 {"goodbye"} diff --git a/packages/desktop/examples/async.rs b/packages/desktop/examples/async.rs index be0b7847b..b86b66df4 100644 --- a/packages/desktop/examples/async.rs +++ b/packages/desktop/examples/async.rs @@ -15,7 +15,7 @@ fn main() { dioxus_desktop::launch(App, |c| c); } -static App: FC<()> = |cx, props| { +static App: Component<()> = |cx, props| { let mut count = use_state(cx, || 0); log::debug!("count is {:?}", count); diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index 9fb86b907..df97b49a8 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -15,7 +15,6 @@ use std::{ sync::atomic::AtomicBool, sync::{Arc, RwLock}, }; -use tokio::task::LocalSet; use wry::{ application::{ accelerator::{Accelerator, SysMods}, @@ -30,14 +29,14 @@ use wry::{ }; pub fn launch( - root: FC<()>, + root: Component<()>, config_builder: impl for<'a, 'b> FnOnce(&'b mut DesktopConfig<'a>) -> &'b mut DesktopConfig<'a>, ) { launch_with_props(root, (), config_builder) } pub fn launch_with_props( - root: FC

, + root: Component

, props: P, builder: impl for<'a, 'b> FnOnce(&'b mut DesktopConfig<'a>) -> &'b mut DesktopConfig<'a>, ) { @@ -51,7 +50,7 @@ struct Response<'a> { } pub fn run( - root: FC, + root: Component, props: T, user_builder: impl for<'a, 'b> FnOnce(&'b mut DesktopConfig<'a>) -> &'b mut DesktopConfig<'a>, ) { @@ -112,7 +111,7 @@ pub struct DesktopController { impl DesktopController { // Launch the virtualdom on its own thread managed by tokio // returns the desktop state - pub fn new_on_tokio(root: FC

, props: P) -> Self { + pub fn new_on_tokio(root: Component

, props: P) -> Self { let edit_queue = Arc::new(RwLock::new(VecDeque::new())); let pending_edits = edit_queue.clone(); @@ -142,7 +141,6 @@ impl DesktopController { dom.wait_for_work().await; let mut muts = dom.work_with_deadline(|| false); while let Some(edit) = muts.pop() { - log::debug!("found mutations {:?}", muts); edit_queue .write() .unwrap() diff --git a/packages/hooks/src/lib.rs b/packages/hooks/src/lib.rs index c09c287bb..f6a5227bc 100644 --- a/packages/hooks/src/lib.rs +++ b/packages/hooks/src/lib.rs @@ -1,6 +1,9 @@ mod usestate; pub use usestate::{use_state, AsyncUseState, UseState}; +mod usestate2; +// pub use usestate2::use_state2; + mod useref; pub use useref::*; @@ -9,3 +12,6 @@ pub use use_shared_state::*; mod usecoroutine; pub use usecoroutine::*; + +mod usemodel; +pub use usemodel::*; diff --git a/packages/hooks/src/use_shared_state.rs b/packages/hooks/src/use_shared_state.rs index 12a5702e0..1a52217de 100644 --- a/packages/hooks/src/use_shared_state.rs +++ b/packages/hooks/src/use_shared_state.rs @@ -8,7 +8,7 @@ use std::{ type ProvidedState = RefCell>; // Tracks all the subscribers to a shared State -pub(crate) struct ProvidedStateInner { +pub struct ProvidedStateInner { value: Rc>, notify_any: Rc, consumers: HashSet, @@ -20,6 +20,14 @@ impl ProvidedStateInner { (self.notify_any)(*consumer); } } + + pub fn write(&self) -> RefMut { + self.value.borrow_mut() + } + + pub fn read(&self) -> Ref { + self.value.borrow() + } } /// This hook provides some relatively light ergonomics around shared state. @@ -114,10 +122,10 @@ impl<'a, T: 'static> UseSharedState<'a, T> { } pub fn notify_consumers(self) { - // if !self.needs_notification.get() { - self.root.borrow_mut().notify_consumers(); - // self.needs_notification.set(true); - // } + if !self.needs_notification.get() { + self.root.borrow_mut().notify_consumers(); + self.needs_notification.set(true); + } } pub fn read_write(&self) -> (Ref<'_, T>, &Self) { @@ -138,6 +146,10 @@ impl<'a, T: 'static> UseSharedState<'a, T> { pub fn write_silent(&self) -> RefMut<'_, T> { self.value.borrow_mut() } + + pub fn inner(&self) -> Rc>> { + self.root.clone() + } } impl Copy for UseSharedState<'_, T> {} diff --git a/packages/hooks/src/usecoroutine.rs b/packages/hooks/src/usecoroutine.rs index a8a10ebc2..380d06284 100644 --- a/packages/hooks/src/usecoroutine.rs +++ b/packages/hooks/src/usecoroutine.rs @@ -6,9 +6,9 @@ use std::{ rc::Rc, }; -pub fn use_coroutine<'a, F: Future + 'a>( +pub fn use_coroutine<'a, F: Future + 'static>( cx: Context<'a>, - f: impl FnOnce() -> F + 'a, + mut f: impl FnMut() -> F + 'a, ) -> CoroutineHandle { // cx.use_hook( @@ -59,10 +59,19 @@ pub struct CoroutineHandle<'a> { cx: Context<'a>, inner: &'a State, } +impl Clone for CoroutineHandle<'_> { + fn clone(&self) -> Self { + CoroutineHandle { + cx: self.cx, + inner: self.inner, + } + } +} +impl Copy for CoroutineHandle<'_> {} impl<'a> CoroutineHandle<'a> { pub fn start(&self) { - if self.inner.running.get() { + if self.is_running() { return; } if let Some(submit) = self.inner.submit.borrow_mut().take() { @@ -71,5 +80,10 @@ impl<'a> CoroutineHandle<'a> { self.cx.push_task(|| fut.as_mut().unwrap().as_mut()); } } + + pub fn is_running(&self) -> bool { + self.inner.running.get() + } + pub fn resume(&self) {} } diff --git a/packages/hooks/src/usemodel.rs b/packages/hooks/src/usemodel.rs new file mode 100644 index 000000000..de991df68 --- /dev/null +++ b/packages/hooks/src/usemodel.rs @@ -0,0 +1,136 @@ +//! When building complex components, it's occasionally useful to dip into a pure MVC pattern instead of the +//! React hooks pattern. Hooks are useful to abstract over some reusable logic, but many models are not reusable +//! in the same way that hooks are. +//! +//! In these cases, we provide `use_model` - a convenient way of abstracting over some state and async functions. + +use dioxus_core::prelude::Context; +use futures::Future; +use std::{ + cell::{Cell, Ref, RefCell, RefMut}, + marker::PhantomData, + pin::Pin, + rc::Rc, +}; + +pub fn use_model(cx: Context, f: impl FnOnce() -> T) -> UseModel { + cx.use_hook( + |_| UseModelInner { + update_scheduled: Cell::new(false), + update_callback: cx.schedule_update(), + value: RefCell::new(f()), + // tasks: RefCell::new(Vec::new()), + }, + |inner| { + inner.update_scheduled.set(false); + UseModel { inner } + }, + ) +} + +pub struct UseModel<'a, T> { + inner: &'a UseModelInner, +} + +struct UseModelInner { + update_scheduled: Cell, + update_callback: Rc, + value: RefCell, + // tasks: RefCell>, +} + +type ModelTask = Pin + 'static>>; + +impl<'a, T: 'static> UseModel<'a, T> { + pub fn read(&self) -> Ref<'_, T> { + self.inner.value.borrow() + } + pub fn write(&self) -> RefMut<'_, T> { + self.needs_update(); + self.inner.value.borrow_mut() + } + /// Allows the ability to write the value without forcing a re-render + pub fn write_silent(&self) -> RefMut<'_, T> { + self.inner.value.borrow_mut() + } + + pub fn needs_update(&self) { + if !self.inner.update_scheduled.get() { + self.inner.update_scheduled.set(true); + (self.inner.update_callback)(); + } + } + + pub fn set(&self, new: T) { + *self.inner.value.borrow_mut() = new; + self.needs_update(); + } + + pub fn read_write(&self) -> (Ref<'_, T>, &Self) { + (self.read(), self) + } + + pub fn start(&self, f: impl FnOnce() -> ModelTask) { + todo!() + } +} + +// keep a coroutine going +pub fn use_model_coroutine + 'static>( + cx: Context, + model: UseModel, + f: impl FnOnce(AppModels) -> F, +) -> UseModelCoroutine { + cx.use_hook( + |_| { + // + UseModelTaskInner { + task: Default::default(), + } + }, + |inner| { + if let Some(task) = inner.task.get_mut() { + cx.push_task(|| task); + } + // + todo!() + }, + ) +} + +pub struct UseModelCoroutine {} + +struct UseModelTaskInner { + task: RefCell>, +} + +impl UseModelCoroutine { + pub fn start(&self) {} +} + +pub struct ModelAsync { + _p: PhantomData, +} +impl ModelAsync { + pub fn write(&self) -> RefMut<'_, T> { + todo!() + } + pub fn read(&self) -> Ref<'_, T> { + todo!() + } +} + +pub struct AppModels {} + +impl AppModels { + pub fn get(&self) -> ModelAsync { + unimplemented!() + } +} + +impl Copy for UseModel<'_, T> {} +impl<'a, T> Clone for UseModel<'a, T> { + fn clone(&self) -> Self { + Self { inner: self.inner } + } +} diff --git a/packages/hooks/src/useref.rs b/packages/hooks/src/useref.rs index 9ebfbfebf..c1362f4da 100644 --- a/packages/hooks/src/useref.rs +++ b/packages/hooks/src/useref.rs @@ -8,12 +8,12 @@ use dioxus_core::Context; pub fn use_ref(cx: Context, f: impl FnOnce() -> T) -> UseRef { cx.use_hook( |_| UseRefInner { - update_scheuled: Cell::new(false), + update_scheduled: Cell::new(false), update_callback: cx.schedule_update(), value: RefCell::new(f()), }, |inner| { - inner.update_scheuled.set(false); + inner.update_scheduled.set(false); UseRef { inner } }, ) @@ -23,7 +23,7 @@ pub struct UseRef<'a, T> { inner: &'a UseRefInner, } struct UseRefInner { - update_scheuled: Cell, + update_scheduled: Cell, update_callback: Rc, value: RefCell, } @@ -54,8 +54,8 @@ impl<'a, T> UseRef<'a, T> { } pub fn needs_update(&self) { - if !self.inner.update_scheuled.get() { - self.inner.update_scheuled.set(true); + if !self.inner.update_scheduled.get() { + self.inner.update_scheduled.set(true); (self.inner.update_callback)(); } } diff --git a/packages/hooks/src/usestate.rs b/packages/hooks/src/usestate.rs index d215c87a7..c1156f4a3 100644 --- a/packages/hooks/src/usestate.rs +++ b/packages/hooks/src/usestate.rs @@ -8,7 +8,7 @@ use std::{ /// Store state between component renders! /// -/// ## The "Pinnacle" of state hooks +/// ## Dioxus equivalent of useState, designed for Rust /// /// The Dioxus version of `useState` is the "king daddy" of state management. It allows you to ergonomically store and /// modify state between component renders. When the state is updated, the component will re-render. @@ -55,9 +55,9 @@ pub fn use_state<'a, T: 'static>( ) -> UseState<'a, T> { cx.use_hook( move |_| { - // + let first_val = initial_state_fn(); UseStateInner { - current_val: initial_state_fn(), + current_val: Rc::new(first_val), update_callback: cx.schedule_update(), wip: Rc::new(RefCell::new(None)), update_scheuled: Cell::new(false), @@ -65,9 +65,15 @@ pub fn use_state<'a, T: 'static>( }, move |hook| { hook.update_scheuled.set(false); + let mut new_val = hook.wip.borrow_mut(); if new_val.is_some() { - hook.current_val = new_val.take().unwrap(); + // if there's only one reference (weak or otherwise), we can just swap the values + if let Some(val) = Rc::get_mut(&mut hook.current_val) { + *val = new_val.take().unwrap(); + } else { + hook.current_val = Rc::new(new_val.take().unwrap()); + } } UseState { inner: &*hook } @@ -75,7 +81,7 @@ pub fn use_state<'a, T: 'static>( ) } struct UseStateInner { - current_val: T, + current_val: Rc, update_scheuled: Cell, update_callback: Rc, wip: Rc>>, @@ -117,6 +123,10 @@ impl<'a, T: 'static> UseState<'a, T> { &self.inner.current_val } + pub fn get_rc(&self) -> &'a Rc { + &self.inner.current_val + } + /// Get the current status of the work-in-progress data pub fn get_wip(&self) -> Ref> { self.inner.wip.borrow() @@ -134,14 +144,15 @@ impl<'a, T: 'static> UseState<'a, T> { pub fn setter(&self) -> Rc { let slot = self.inner.wip.clone(); Rc::new(move |new| { - // *slot.borrow_mut() = Some(new); }) } pub fn for_async(&self) -> AsyncUseState { AsyncUseState { + re_render: self.inner.update_callback.clone(), wip: self.inner.wip.clone(), + inner: self.inner.current_val.clone(), } } @@ -164,11 +175,15 @@ impl<'a, T: 'static + ToOwned> UseState<'a, T> { // "get_mut" is locked behind "ToOwned" to make it explicit that cloning occurs to use this RefMut::map(self.inner.wip.borrow_mut(), |slot| { if slot.is_none() { - *slot = Some(self.inner.current_val.to_owned()); + *slot = Some(self.inner.current_val.as_ref().to_owned()); } slot.as_mut().unwrap() }) } + + pub fn inner(self) -> T { + self.inner.current_val.as_ref().to_owned() + } } impl<'a, T> std::ops::Deref for UseState<'a, T> { @@ -254,6 +269,8 @@ impl<'a, T: 'static + Display> std::fmt::Display for UseState<'a, T> { /// A less ergonmic but still capable form of use_state that's valid for `static lifetime pub struct AsyncUseState { + inner: Rc, + re_render: Rc, wip: Rc>>, } @@ -269,7 +286,17 @@ impl AsyncUseState { slot.as_mut().unwrap() }) } +} +impl AsyncUseState { pub fn set(&mut self, val: T) { + (self.re_render)(); *self.wip.borrow_mut() = Some(val); } + pub fn get(&self) -> &T { + self.inner.as_ref() + } + + pub fn get_rc(&self) -> &Rc { + &self.inner + } } diff --git a/packages/hooks/src/usestate2.rs b/packages/hooks/src/usestate2.rs new file mode 100644 index 000000000..df778f0f1 --- /dev/null +++ b/packages/hooks/src/usestate2.rs @@ -0,0 +1,227 @@ +use dioxus_core::prelude::Context; +use std::{ + borrow::{Borrow, BorrowMut}, + cell::{Cell, Ref, RefCell, RefMut}, + fmt::{Debug, Display}, + ops::Not, + rc::Rc, +}; + +// /// Store state between component renders! +// /// +// /// ## Dioxus equivalent of UseStateInner2, designed for Rust +// /// +// /// The Dioxus version of `UseStateInner2` is the "king daddy" of state management. It allows you to ergonomically store and +// /// modify state between component renders. When the state is updated, the component will re-render. +// /// +// /// Dioxus' use_state basically wraps a RefCell with helper methods and integrates it with the VirtualDOM update system. +// /// +// /// [`use_state`] exposes a few helper methods to modify the underlying state: +// /// - `.set(new)` allows you to override the "work in progress" value with a new value +// /// - `.get_mut()` allows you to modify the WIP value +// /// - `.get_wip()` allows you to access the WIP value +// /// - `.deref()` provides the previous value (often done implicitly, though a manual dereference with `*` might be required) +// /// +// /// Additionally, a ton of std::ops traits are implemented for the `UseStateInner2` wrapper, meaning any mutative type operations +// /// will automatically be called on the WIP value. +// /// +// /// ## Combinators +// /// +// /// On top of the methods to set/get state, `use_state` also supports fancy combinators to extend its functionality: +// /// - `.classic()` and `.split()` convert the hook into the classic React-style hook +// /// ```rust +// /// let (state, set_state) = use_state(cx, || 10).split() +// /// ``` +// /// +// /// +// /// Usage: +// /// ```ignore +// /// const Example: FC<()> = |cx, props|{ +// /// let counter = use_state(cx, || 0); +// /// let increment = |_| counter += 1; +// /// let decrement = |_| counter += 1; +// /// +// /// html! { +// ///

+// ///

"Counter: {counter}"

+// /// +// /// +// ///
+// /// } +// /// } +// /// ``` +// pub fn use_state2<'a, T: 'static>( +// cx: Context<'a>, +// initial_state_fn: impl FnOnce() -> T, +// ) -> &'a UseState2 { +// cx.use_hook( +// move |_| { +// UseState2(Rc::new(UseStateInner2 { +// current_val: initial_state_fn(), +// update_callback: cx.schedule_update(), +// wip: None, +// update_scheuled: Cell::new(false), +// })) +// }, +// move |hook: &mut UseState2| { +// { +// let r = hook.0.as_ref(); +// let mut state = r.borrow_mut(); +// state.update_scheuled.set(false); +// if state.wip.is_some() { +// state.current_val = state.wip.take().unwrap(); +// } +// } +// &*hook +// }, +// ) +// } + +// pub struct UseState2(Rc>); + +// impl ToOwned for UseState2 { +// type Owned = UseState2; +// fn to_owned(&self) -> Self::Owned { +// UseState2(self.0.clone()) +// } +// } + +// pub struct UseStateInner2 { +// current_val: T, +// update_scheuled: Cell, +// update_callback: Rc, +// wip: Option, +// } + +// impl Debug for UseStateInner2 { +// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +// write!(f, "{:?}", self.current_val) +// } +// } + +// impl UseState2 { +// /// Tell the Dioxus Scheduler that we need to be processed +// pub fn needs_update(&self) { +// if !self.update_scheuled.get() { +// self.update_scheuled.set(true); +// (self.update_callback)(); +// } +// } + +// pub fn set(&mut self, new_val: T) -> Option { +// self.needs_update(); +// ip.wip.replace(new_val) +// } + +// pub fn get(&self) -> &T { +// &self.current_val +// } +// } + +// // impl> UseState2 { +// // /// Gain mutable access to the new value. This method is only available when the value is a `ToOwned` type. +// // /// +// // /// Mutable access is derived by calling "ToOwned" (IE cloning) on the current value. +// // /// +// // /// To get a reference to the current value, use `.get()` +// // pub fn modify(&self) -> RefMut { +// // // make sure we get processed +// // self.0.needs_update(); + +// // // Bring out the new value, cloning if it we need to +// // // "get_mut" is locked behind "ToOwned" to make it explicit that cloning occurs to use this +// // RefMut::map(self.wip.borrow_mut(), |slot| { +// // if slot.is_none() { +// // *slot = Some(self.current_val.to_owned()); +// // } +// // slot.as_mut().unwrap() +// // }) +// // } + +// // pub fn inner(self) -> T { +// // self.current_val.to_owned() +// // } +// // } + +// impl std::ops::Deref for UseStateInner2 { +// type Target = T; + +// fn deref(&self) -> &Self::Target { +// self.get() +// } +// } + +// use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign}; + +// use crate::UseState; + +// impl> Add for UseStateInner2 { +// type Output = T; + +// fn add(self, rhs: T) -> Self::Output { +// self.current_val.add(rhs) +// } +// } +// impl> AddAssign for UseStateInner2 { +// fn add_assign(&mut self, rhs: T) { +// self.set(self.current_val.add(rhs)); +// } +// } +// impl> Sub for UseStateInner2 { +// type Output = T; + +// fn sub(self, rhs: T) -> Self::Output { +// self.current_val.sub(rhs) +// } +// } +// impl> SubAssign for UseStateInner2 { +// fn sub_assign(&mut self, rhs: T) { +// self.set(self.current_val.sub(rhs)); +// } +// } + +// /// MUL +// impl> Mul for UseStateInner2 { +// type Output = T; + +// fn mul(self, rhs: T) -> Self::Output { +// self.current_val.mul(rhs) +// } +// } +// impl> MulAssign for UseStateInner2 { +// fn mul_assign(&mut self, rhs: T) { +// self.set(self.current_val.mul(rhs)); +// } +// } +// /// DIV +// impl> Div for UseStateInner2 { +// type Output = T; + +// fn div(self, rhs: T) -> Self::Output { +// self.current_val.div(rhs) +// } +// } +// impl> DivAssign for UseStateInner2 { +// fn div_assign(&mut self, rhs: T) { +// self.set(self.current_val.div(rhs)); +// } +// } +// impl> PartialEq for UseStateInner2 { +// fn eq(&self, other: &V) -> bool { +// self.get() == other +// } +// } +// impl + Copy> Not for UseStateInner2 { +// type Output = O; + +// fn not(self) -> Self::Output { +// !*self.get() +// } +// } + +// // enable displaty for the handle +// impl std::fmt::Display for UseStateInner2 { +// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +// write!(f, "{}", self.current_val) +// } +// } diff --git a/packages/html/src/events.rs b/packages/html/src/events.rs index 0ede805f7..9292f8e64 100644 --- a/packages/html/src/events.rs +++ b/packages/html/src/events.rs @@ -44,7 +44,11 @@ pub mod on { // ie copy let shortname: &'static str = &event_name[2..]; - c.listener(shortname, callback) + let handler = EventHandler { + callback: bump.alloc(std::cell::RefCell::new(Some(callback))), + }; + + c.listener(shortname, handler) } )* )* diff --git a/packages/liveview/Cargo.toml b/packages/liveview/Cargo.toml new file mode 100644 index 000000000..1fb2905fc --- /dev/null +++ b/packages/liveview/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "dioxus-liveview" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +axum = { version = "0.4.2", optional = true, features = ["ws", "headers"] } + +[features] +default = ["axum"] + +[dev-dependencies] +tokio = { version = "1.14.0", features = ["full"] } +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } +tower-http = { version = "0.2.0", features = ["fs", "trace"] } +headers = "0.3" diff --git a/packages/liveview/README.md b/packages/liveview/README.md new file mode 100644 index 000000000..1e29ac75a --- /dev/null +++ b/packages/liveview/README.md @@ -0,0 +1,49 @@ +# Dioxus LiveView + +Enabling server-rendered and hybrid applications with incredibly low latency (<1ms). + +```rust +#[async_std::main] +async fn main() -> tide::Result<()> { + let liveview_pool = dioxus::liveview::pool::default(); + let mut app = tide::new(); + + // serve the liveview client + app.at("/").get(dioxus::liveview::liveview_frontend); + + // and then connect the client to the backend + app.at("/app").get(|req| dioxus::liveview::launch(App, Props { req })) + + app.listen("127.0.0.1:8080").await?; + + Ok(()) +} +``` + +Dioxus LiveView runs your Dioxus apps on the server + + + +```rust +use soyuz::prelude::*; + +#[tokio::main] +async fn main() { + let mut app = soyuz::new(); + app.at("/app").get(websocket(handler)); + app.listen("127.0.0.1:8080").await.unwrap(); +} + +async fn order_shoes(mut req: WebsocketRequest) -> Response { + let stream = req.upgrade(); + dioxus::liveview::launch(App, stream).await; +} + +fn App(cx: Context, props: &()) -> Element { + let mut count = use_state(cx, || 0); + cx.render(rsx!( + button { onclick: move |_| count += 1, "Incr" } + button { onclick: move |_| count -= 1, "Decr" } + )) +} +``` diff --git a/packages/liveview/examples/axum.rs b/packages/liveview/examples/axum.rs new file mode 100644 index 000000000..f280a25c6 --- /dev/null +++ b/packages/liveview/examples/axum.rs @@ -0,0 +1,96 @@ +//! Example websocket server. +//! +//! Run with +//! +//! ```not_rust +//! cargo run -p example-websockets +//! ``` + +use axum::{ + extract::{ + ws::{Message, WebSocket, WebSocketUpgrade}, + TypedHeader, + }, + http::StatusCode, + response::IntoResponse, + routing::{get, get_service}, + Router, +}; +use std::net::SocketAddr; +use tower_http::{ + services::ServeDir, + trace::{DefaultMakeSpan, TraceLayer}, +}; + +#[tokio::main] +async fn main() { + // Set the RUST_LOG, if it hasn't been explicitly defined + if std::env::var_os("RUST_LOG").is_none() { + std::env::set_var("RUST_LOG", "example_websockets=debug,tower_http=debug") + } + tracing_subscriber::fmt::init(); + + // build our application with some routes + let app = Router::new() + .fallback( + get_service( + ServeDir::new("examples/axum_assets").append_index_html_on_directories(true), + ) + .handle_error(|error: std::io::Error| async move { + ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Unhandled internal error: {}", error), + ) + }), + ) + // routes are matched from bottom to top, so we have to put `nest` at the + // top since it matches all routes + .route("/ws", get(ws_handler)) + // logging so we can see whats going on + .layer( + TraceLayer::new_for_http() + .make_span_with(DefaultMakeSpan::default().include_headers(true)), + ); + + // run it with hyper + let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); + tracing::debug!("listening on {}", addr); + axum::Server::bind(&addr) + .serve(app.into_make_service()) + .await + .unwrap(); +} + +async fn ws_handler( + ws: WebSocketUpgrade, + user_agent: Option>, +) -> impl IntoResponse { + if let Some(TypedHeader(user_agent)) = user_agent { + println!("`{}` connected", user_agent.as_str()); + } + + ws.on_upgrade(handle_socket) +} + +async fn handle_socket(mut socket: WebSocket) { + if let Some(msg) = socket.recv().await { + if let Ok(msg) = msg { + println!("Client says: {:?}", msg); + } else { + println!("client disconnected"); + return; + } + } + + loop { + if socket + .send(Message::Text(String::from("Hi!"))) + .await + .is_err() + { + println!("client disconnected"); + return; + } + tokio::time::sleep(std::time::Duration::from_secs(1)).await; + } +} diff --git a/packages/liveview/examples/axum_assets/index.html b/packages/liveview/examples/axum_assets/index.html new file mode 100644 index 000000000..390bb86b7 --- /dev/null +++ b/packages/liveview/examples/axum_assets/index.html @@ -0,0 +1 @@ + diff --git a/packages/liveview/examples/axum_assets/script.js b/packages/liveview/examples/axum_assets/script.js new file mode 100644 index 000000000..264efe809 --- /dev/null +++ b/packages/liveview/examples/axum_assets/script.js @@ -0,0 +1,9 @@ +const socket = new WebSocket('ws://localhost:3000/ws'); + +socket.addEventListener('open', function (event) { + socket.send('Hello Server!'); +}); + +socket.addEventListener('message', function (event) { + console.log('Message from server ', event.data); +}); diff --git a/packages/liveview/src/lib.rs b/packages/liveview/src/lib.rs new file mode 100644 index 000000000..e69de29bb diff --git a/packages/liveview/src/supported/actix_handler.rs b/packages/liveview/src/supported/actix_handler.rs new file mode 100644 index 000000000..e69de29bb diff --git a/packages/liveview/src/supported/axum_handler.rs b/packages/liveview/src/supported/axum_handler.rs new file mode 100644 index 000000000..e69de29bb diff --git a/packages/liveview/src/supported/tide_handler.rs b/packages/liveview/src/supported/tide_handler.rs new file mode 100644 index 000000000..e69de29bb diff --git a/packages/mobile/src/lib.rs b/packages/mobile/src/lib.rs index a3452c8b4..6d7f27114 100644 --- a/packages/mobile/src/lib.rs +++ b/packages/mobile/src/lib.rs @@ -26,11 +26,14 @@ fn init_logging() { static HTML_CONTENT: &'static str = include_str!("../../desktop/src/index.html"); -pub fn launch(root: FC<()>, builder: fn(WindowBuilder) -> WindowBuilder) -> anyhow::Result<()> { +pub fn launch( + root: Component<()>, + builder: fn(WindowBuilder) -> WindowBuilder, +) -> anyhow::Result<()> { launch_with_props(root, (), builder) } pub fn launch_with_props( - root: FC

, + root: Component

, props: P, builder: fn(WindowBuilder) -> WindowBuilder, ) -> anyhow::Result<()> { @@ -41,7 +44,7 @@ pub fn launch_with_props( /// Components used in WebviewRenderer instances can directly use system libraries, access the filesystem, and multithread with ease. pub struct WebviewRenderer { /// The root component used to render the Webview - root: FC, + root: Component, } enum RpcEvent<'a> { Initialize { @@ -52,7 +55,7 @@ enum RpcEvent<'a> { impl WebviewRenderer { pub fn run( - root: FC, + root: Component, props: T, user_builder: fn(WindowBuilder) -> WindowBuilder, ) -> anyhow::Result<()> { @@ -60,7 +63,7 @@ impl WebviewRenderer { } pub fn run_with_edits( - root: FC, + root: Component, props: T, user_builder: fn(WindowBuilder) -> WindowBuilder, redits: Option>>, diff --git a/packages/router/examples/simple.rs b/packages/router/examples/simple.rs index 4a317dd5c..6c604b88a 100644 --- a/packages/router/examples/simple.rs +++ b/packages/router/examples/simple.rs @@ -9,7 +9,7 @@ fn main() { dioxus_web::launch(App, |c| c); } -static App: FC<()> = |cx, props| { +static App: Component<()> = |cx, props| { #[derive(Clone, Debug, PartialEq)] enum Route { Home, diff --git a/packages/ssr/src/lib.rs b/packages/ssr/src/lib.rs index 84215517c..e092daca7 100644 --- a/packages/ssr/src/lib.rs +++ b/packages/ssr/src/lib.rs @@ -301,13 +301,13 @@ mod tests { use dioxus_core_macro::*; use dioxus_html as dioxus_elements; - static SIMPLE_APP: FC<()> = |cx, _| { + static SIMPLE_APP: Component<()> = |cx, _| { cx.render(rsx!(div { "hello world!" })) }; - static SLIGHTLY_MORE_COMPLEX: FC<()> = |cx, _| { + static SLIGHTLY_MORE_COMPLEX: Component<()> = |cx, _| { cx.render(rsx! { div { title: "About W3Schools" @@ -326,14 +326,14 @@ mod tests { }) }; - static NESTED_APP: FC<()> = |cx, _| { + static NESTED_APP: Component<()> = |cx, _| { cx.render(rsx!( div { SIMPLE_APP {} } )) }; - static FRAGMENT_APP: FC<()> = |cx, _| { + static FRAGMENT_APP: Component<()> = |cx, _| { cx.render(rsx!( div { "f1" } div { "f2" } @@ -389,7 +389,7 @@ mod tests { #[test] fn styles() { - static STLYE_APP: FC<()> = |cx, _| { + static STLYE_APP: Component<()> = |cx, _| { cx.render(rsx! { div { color: "blue", font_size: "46px" } }) diff --git a/packages/web/examples/async.rs b/packages/web/examples/async.rs index 09301ba27..c6cedf9be 100644 --- a/packages/web/examples/async.rs +++ b/packages/web/examples/async.rs @@ -14,7 +14,7 @@ fn main() { dioxus_web::launch(App, |c| c); } -static App: FC<()> = |cx, props| { +static App: Component<()> = |cx, props| { let mut count = use_state(cx, || 0); cx.push_task(|| async move { diff --git a/packages/web/examples/js_bench.rs b/packages/web/examples/js_bench.rs index 9ce90d841..eeb7dc622 100644 --- a/packages/web/examples/js_bench.rs +++ b/packages/web/examples/js_bench.rs @@ -107,7 +107,7 @@ impl Label { } } -static App: FC<()> = |cx, _props| { +static App: Component<()> = |cx, _props| { let mut items = use_ref(cx, || vec![]); let mut selected = use_state(cx, || None); diff --git a/packages/web/examples/simple.rs b/packages/web/examples/simple.rs index f6a397e00..d93591952 100644 --- a/packages/web/examples/simple.rs +++ b/packages/web/examples/simple.rs @@ -15,7 +15,7 @@ fn main() { dioxus_web::launch(App, |c| c); } -static App: FC<()> = |cx, props| { +static App: Component<()> = |cx, props| { let show = use_state(cx, || true); let inner = match *show { diff --git a/packages/web/examples/suspense.rs b/packages/web/examples/suspense.rs index e0c7b690c..615351a81 100644 --- a/packages/web/examples/suspense.rs +++ b/packages/web/examples/suspense.rs @@ -14,7 +14,7 @@ fn main() { dioxus_web::launch(App, |c| c); } -static App: FC<()> = |cx, _| { +static App: Component<()> = |cx, _| { let doggo = cx.suspend(|| async move { #[derive(serde::Deserialize)] struct Doggo { diff --git a/packages/web/src/cache.rs b/packages/web/src/cache.rs index 61339f918..8beb58f18 100644 --- a/packages/web/src/cache.rs +++ b/packages/web/src/cache.rs @@ -1,311 +1,298 @@ -/// Wasm-bindgen has a performance option to intern commonly used phrases -/// This saves the decoding cost, making the interaction of Rust<->JS more performant. -/// We intern all the HTML tags and attributes, making most operations much faster. -/// -/// Interning takes < 1ms at the start of the app, but saves a *ton* of time later on. -/// -/// Eventually we might want to procedurally generate these strings for common words, phrases, and values. -pub(crate) fn intern_cached_strings() { - let cached_words = [ - // Important tags to dioxus - "dioxus-id", - "dioxus", - "dioxus-event-click", // todo: more events - "click", - // All the HTML Tags - "a", - "abbr", - "address", - "area", - "article", - "aside", - "audio", - "b", - "base", - "bdi", - "bdo", - "big", - "blockquote", - "body", - "br", - "button", - "canvas", - "caption", - "cite", - "code", - "col", - "colgroup", - "command", - "data", - "datalist", - "dd", - "del", - "details", - "dfn", - "dialog", - "div", - "dl", - "dt", - "em", - "embed", - "fieldset", - "figcaption", - "figure", - "footer", - "form", - "h1", - "h2", - "h3", - "h4", - "h5", - "h6", - "head", - "header", - "hr", - "html", - "i", - "iframe", - "img", - "input", - "ins", - "kbd", - "keygen", - "label", - "legend", - "li", - "link", - "main", - "map", - "mark", - "menu", - "menuitem", - "meta", - "meter", - "nav", - "noscript", - "object", - "ol", - "optgroup", - "option", - "output", - "p", - "param", - "picture", - "pre", - "progress", - "q", - "rp", - "rt", - "ruby", - "s", - "samp", - "script", - "section", - "select", - "small", - "source", - "span", - "strong", - "style", - "sub", - "summary", - "sup", - "table", - "tbody", - "td", - "textarea", - "tfoot", - "th", - "thead", - "time", - "title", - "tr", - "track", - "u", - "ul", - "var", - "video", - "wbr", - // All the event handlers - "Attribute", - "accept", - "accept-charset", - "accesskey", - "action", - "alt", - "async", - "autocomplete", - "autofocus", - "autoplay", - "charset", - "checked", - "cite", - "class", - "cols", - "colspan", - "content", - "contenteditable", - "controls", - "coords", - "data", - "data-*", - "datetime", - "default", - "defer", - "dir", - "dirname", - "disabled", - "download", - "draggable", - "enctype", - "for", - "form", - "formaction", - "headers", - "height", - "hidden", - "high", - "href", - "hreflang", - "http-equiv", - "id", - "ismap", - "kind", - "label", - "lang", - "list", - "loop", - "low", - "max", - "maxlength", - "media", - "method", - "min", - "multiple", - "muted", - "name", - "novalidate", - "onabort", - "onafterprint", - "onbeforeprint", - "onbeforeunload", - "onblur", - "oncanplay", - "oncanplaythrough", - "onchange", - "onclick", - "oncontextmenu", - "oncopy", - "oncuechange", - "oncut", - "ondblclick", - "ondrag", - "ondragend", - "ondragenter", - "ondragleave", - "ondragover", - "ondragstart", - "ondrop", - "ondurationchange", - "onemptied", - "onended", - "onerror", - "onfocus", - "onhashchange", - "oninput", - "oninvalid", - "onkeydown", - "onkeypress", - "onkeyup", - "onload", - "onloadeddata", - "onloadedmetadata", - "onloadstart", - "onmousedown", - "onmousemove", - "onmouseout", - "onmouseover", - "onmouseup", - "onmousewheel", - "onoffline", - "ononline", - "onpageshow", - "onpaste", - "onpause", - "onplay", - "onplaying", - "onprogress", - "onratechange", - "onreset", - "onresize", - "onscroll", - "onsearch", - "onseeked", - "onseeking", - "onselect", - "onstalled", - "onsubmit", - "onsuspend", - "ontimeupdate", - "ontoggle", - "onunload", - "onvolumechange", - "onwaiting", - "onwheel", - "open", - "optimum", - "pattern", - "placeholder", - "poster", - "preload", - "readonly", - "rel", - "required", - "reversed", - "rows", - "rowspan", - "sandbox", - "scope", - "selected", - "shape", - "size", - "sizes", - "span", - "spellcheck", - "src", - "srcdoc", - "srclang", - "srcset", - "start", - "step", - "style", - "tabindex", - "target", - "title", - "translate", - "type", - "usemap", - "value", - "width", - "wrap", - "0", - "1", - "2", - "3", - "4", - "5", - "6", - "7", - "8", - "9", - "10", - "11", - "12", - "13", - "14", - ]; - - for s in cached_words { - wasm_bindgen::intern(s); - } -} +pub static BUILTIN_INTERNED_STRINGS: &[&'static str] = &[ + // Important tags to dioxus + "dioxus-id", + "dioxus", + "dioxus-event-click", // todo: more events + "click", + // All the HTML Tags + "a", + "abbr", + "address", + "area", + "article", + "aside", + "audio", + "b", + "base", + "bdi", + "bdo", + "big", + "blockquote", + "body", + "br", + "button", + "canvas", + "caption", + "cite", + "code", + "col", + "colgroup", + "command", + "data", + "datalist", + "dd", + "del", + "details", + "dfn", + "dialog", + "div", + "dl", + "dt", + "em", + "embed", + "fieldset", + "figcaption", + "figure", + "footer", + "form", + "h1", + "h2", + "h3", + "h4", + "h5", + "h6", + "head", + "header", + "hr", + "html", + "i", + "iframe", + "img", + "input", + "ins", + "kbd", + "keygen", + "label", + "legend", + "li", + "link", + "main", + "map", + "mark", + "menu", + "menuitem", + "meta", + "meter", + "nav", + "noscript", + "object", + "ol", + "optgroup", + "option", + "output", + "p", + "param", + "picture", + "pre", + "progress", + "q", + "rp", + "rt", + "ruby", + "s", + "samp", + "script", + "section", + "select", + "small", + "source", + "span", + "strong", + "style", + "sub", + "summary", + "sup", + "table", + "tbody", + "td", + "textarea", + "tfoot", + "th", + "thead", + "time", + "title", + "tr", + "track", + "u", + "ul", + "var", + "video", + "wbr", + // All the event handlers + "Attribute", + "accept", + "accept-charset", + "accesskey", + "action", + "alt", + "async", + "autocomplete", + "autofocus", + "autoplay", + "charset", + "checked", + "cite", + "class", + "cols", + "colspan", + "content", + "contenteditable", + "controls", + "coords", + "data", + "data-*", + "datetime", + "default", + "defer", + "dir", + "dirname", + "disabled", + "download", + "draggable", + "enctype", + "for", + "form", + "formaction", + "headers", + "height", + "hidden", + "high", + "href", + "hreflang", + "http-equiv", + "id", + "ismap", + "kind", + "label", + "lang", + "list", + "loop", + "low", + "max", + "maxlength", + "media", + "method", + "min", + "multiple", + "muted", + "name", + "novalidate", + "onabort", + "onafterprint", + "onbeforeprint", + "onbeforeunload", + "onblur", + "oncanplay", + "oncanplaythrough", + "onchange", + "onclick", + "oncontextmenu", + "oncopy", + "oncuechange", + "oncut", + "ondblclick", + "ondrag", + "ondragend", + "ondragenter", + "ondragleave", + "ondragover", + "ondragstart", + "ondrop", + "ondurationchange", + "onemptied", + "onended", + "onerror", + "onfocus", + "onhashchange", + "oninput", + "oninvalid", + "onkeydown", + "onkeypress", + "onkeyup", + "onload", + "onloadeddata", + "onloadedmetadata", + "onloadstart", + "onmousedown", + "onmousemove", + "onmouseout", + "onmouseover", + "onmouseup", + "onmousewheel", + "onoffline", + "ononline", + "onpageshow", + "onpaste", + "onpause", + "onplay", + "onplaying", + "onprogress", + "onratechange", + "onreset", + "onresize", + "onscroll", + "onsearch", + "onseeked", + "onseeking", + "onselect", + "onstalled", + "onsubmit", + "onsuspend", + "ontimeupdate", + "ontoggle", + "onunload", + "onvolumechange", + "onwaiting", + "onwheel", + "open", + "optimum", + "pattern", + "placeholder", + "poster", + "preload", + "readonly", + "rel", + "required", + "reversed", + "rows", + "rowspan", + "sandbox", + "scope", + "selected", + "shape", + "size", + "sizes", + "span", + "spellcheck", + "src", + "srcdoc", + "srclang", + "srcset", + "start", + "step", + "style", + "tabindex", + "target", + "title", + "translate", + "type", + "usemap", + "value", + "width", + "wrap", + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10", + "11", + "12", + "13", + "14", +]; diff --git a/packages/web/src/cfg.rs b/packages/web/src/cfg.rs index 09c488022..0fa0dc48c 100644 --- a/packages/web/src/cfg.rs +++ b/packages/web/src/cfg.rs @@ -11,6 +11,7 @@ pub struct WebConfig { pub(crate) hydrate: bool, pub(crate) rootname: String, + pub(crate) cached_strings: Vec, } impl Default for WebConfig { @@ -18,6 +19,7 @@ impl Default for WebConfig { Self { hydrate: false, rootname: "main".to_string(), + cached_strings: Vec::new(), } } } @@ -41,4 +43,12 @@ impl WebConfig { self.rootname = name.into(); self } + + /// Set the name of the element that Dioxus will use as the root. + /// + /// This is akint to calling React.render() on the element with the specified name. + pub fn with_string_cache(mut self, cache: Vec) -> Self { + self.cached_strings = cache; + self + } } diff --git a/packages/web/src/lib.rs b/packages/web/src/lib.rs index d9e14b6c2..d792c3725 100644 --- a/packages/web/src/lib.rs +++ b/packages/web/src/lib.rs @@ -56,11 +56,10 @@ use std::rc::Rc; pub use crate::cfg::WebConfig; use crate::dom::load_document; -use cache::intern_cached_strings; use dioxus::SchedulerMsg; use dioxus::VirtualDom; pub use dioxus_core as dioxus; -use dioxus_core::prelude::FC; +use dioxus_core::prelude::Component; use futures_util::FutureExt; mod cache; @@ -82,15 +81,15 @@ mod ric_raf; /// /// ```rust /// fn main() { -/// dioxus_web::launch(App, |c| c); +/// dioxus_web::launch(App); /// } /// -/// static App: FC<()> = |cx, props| { +/// static App: Component<()> = |cx, props| { /// rsx!(cx, div {"hello world"}) /// } /// ``` -pub fn launch(root_component: FC<()>, configuration: impl FnOnce(WebConfig) -> WebConfig) { - launch_with_props(root_component, (), configuration) +pub fn launch(root_component: Component<()>) { + launch_with_props(root_component, (), |c| c); } /// Launches the VirtualDOM from the specified component function and props. @@ -109,12 +108,15 @@ pub fn launch(root_component: FC<()>, configuration: impl FnOnce(WebConfig) -> W /// name: String /// } /// -/// static App: FC = |cx, props| { +/// static App: Component = |cx, props| { /// rsx!(cx, div {"hello {props.name}"}) /// } /// ``` -pub fn launch_with_props(root_component: FC, root_properties: T, configuration_builder: F) -where +pub fn launch_with_props( + root_component: Component, + root_properties: T, + configuration_builder: F, +) where T: Send + 'static, F: FnOnce(WebConfig) -> WebConfig, { @@ -134,10 +136,15 @@ where /// wasm_bindgen_futures::spawn_local(app_fut); /// } /// ``` -pub async fn run_with_props(root: FC, root_props: T, cfg: WebConfig) { +pub async fn run_with_props(root: Component, root_props: T, cfg: WebConfig) { let mut dom = VirtualDom::new_with_props(root, root_props); - intern_cached_strings(); + for s in crate::cache::BUILTIN_INTERNED_STRINGS { + wasm_bindgen::intern(s); + } + for s in &cfg.cached_strings { + wasm_bindgen::intern(s); + } let should_hydrate = cfg.hydrate; @@ -155,7 +162,6 @@ pub async fn run_with_props(root: FC, root_props: T, cfg: // hydrating is simply running the dom for a single render. If the page is already written, then the corresponding // ElementIds should already line up because the web_sys dom has already loaded elements with the DioxusID into memory if !should_hydrate { - // log::info!("Applying rebuild edits..., {:?}", mutations); websys_dom.process_edits(&mut mutations.edits); } @@ -166,20 +172,17 @@ pub async fn run_with_props(root: FC, root_props: T, cfg: // if there is work then this future resolves immediately. dom.wait_for_work().await; - // // wait for the mainthread to schedule us in - // let mut deadline = work_loop.wait_for_idle_time().await; + // wait for the mainthread to schedule us in + let mut deadline = work_loop.wait_for_idle_time().await; // run the virtualdom work phase until the frame deadline is reached - let mutations = dom.work_with_deadline(|| false); - // // run the virtualdom work phase until the frame deadline is reached - // let mutations = dom.work_with_deadline(|| (&mut deadline).now_or_never().is_some()); + let mutations = dom.work_with_deadline(|| (&mut deadline).now_or_never().is_some()); // wait for the animation frame to fire so we can apply our changes work_loop.wait_for_raf().await; for mut edit in mutations { // actually apply our changes during the animation frame - // log::info!("Applying change edits..., {:?}", edit); websys_dom.process_edits(&mut edit.edits); } }