瀏覽代碼

wip: threadsafe

Jonathan Kelley 3 年之前
父節點
當前提交
82953f2

+ 0 - 1
.vscode/settings.json

@@ -1,4 +1,3 @@
 {
-  "rust-analyzer.inlayHints.enable": false,
   "rust-analyzer.cargo.allFeatures": true
 }

+ 4 - 2
Cargo.toml

@@ -41,15 +41,17 @@ futures-util = "0.3.16"
 log = "0.4.14"
 num-format = "0.4.0"
 separator = "0.4.1"
-serde = { version = "1.0.126", features = ["derive"] }
+serde = { version = "1.0.130", features = ["derive"] }
 im-rc = "15.0.0"
 fxhash = "0.2.1"
 anyhow = "1.0.42"
+reqwest = "0.11.4"
+serde_json = "1.0.68"
 
 [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
 argh = "0.1.5"
 env_logger = "0.9.0"
-async-std = { version = "1.9.0", features = ["attributes"] }
+tokio = { version = "1.12.0", features = ["full"] }
 rand = { version = "0.8.4", features = ["small_rng"] }
 gloo-timers = "0.2.1"
 # surf = { version = "2.3.1", default-features = false, features = [

+ 3 - 2
examples/async.rs

@@ -6,8 +6,9 @@ the `TaskHandle` object.
 use dioxus::prelude::*;
 use gloo_timers::future::TimeoutFuture;
 
-fn main() {
-    dioxus::desktop::launch(App, |c| c).unwrap();
+#[tokio::main]
+async fn main() {
+    dioxus::desktop::launch(App, |c| c).await;
 }
 
 pub static App: FC<()> = |cx, _| {

+ 0 - 208
examples/showcase.rs

@@ -1,208 +0,0 @@
-#![allow(
-    unused,
-    dead_code,
-    non_upper_case_globals,
-    non_camel_case_types,
-    non_snake_case
-)]
-
-fn main() {}
-
-use dioxus::prelude::*;
-use std::rc::Rc;
-
-static App: FC<()> = |cx, props| {
-    let (selection, set_selection) = use_state(cx, || None as Option<usize>).classic();
-
-    let body = match selection {
-        Some(id) => rsx!(cx, ReferenceItem { selected: *id }),
-        None => rsx!(cx, div { "Select an concept to explore" }),
-    };
-
-    cx.render(rsx! {
-        div {
-            ScrollSelector { onselect: move |id| set_selection(id)  }
-            {body}
-        }
-    })
-};
-
-// this is its own component to stay memoized
-#[derive(Props)]
-struct ScrollSelectorProps<'a> {
-    onselect: &'a dyn Fn(Option<usize>),
-}
-
-fn ScrollSelector<'a>(cx: Context<'a>, props: &'a ScrollSelectorProps) -> DomTree<'a> {
-    let selection_list = (&REFERENCES).iter().enumerate().map(|(id, _)| {
-        rsx! {
-            li {
-                h3 {}
-            }
-        }
-    });
-    cx.render(rsx! {
-        div {
-            h1 {""}
-            ul {
-                {selection_list}
-                button {
-                    onclick: move |_| (props.onselect)(Some(10))
-                }
-            }
-        }
-    })
-}
-
-#[derive(PartialEq, Props)]
-struct ReferenceItemProps {
-    selected: usize,
-}
-
-static ReferenceItem: FC<ReferenceItemProps> = |cx, props| {
-    let (caller, name, code) = REFERENCES[props.selected];
-
-    // Create the component using the factory API directly
-    let caller_node = LazyNodes::new(move |f| f.component(caller, (), None, &[]));
-
-    cx.render(rsx! {
-        div {
-            // Source of the left, rendered on the right
-            div {
-                code { "{code}" }
-            }
-            div {
-                {caller_node}
-            }
-        }
-    })
-};
-
-use reference::REFERENCES;
-mod reference {
-    mod antipatterns;
-    mod basics;
-    mod children;
-    mod conditional_rendering;
-    mod controlled_inputs;
-    mod custom_elements;
-    mod empty;
-    mod fragments;
-    mod global_css;
-    mod inline_styles;
-    mod iterators;
-    mod listener;
-    mod memo;
-    mod noderefs;
-    mod signals;
-    mod spreadpattern;
-    mod statemanagement;
-    mod suspense;
-    mod task;
-    mod testing;
-    mod tostring;
-    use super::*;
-    pub static REFERENCES: &[(FC<()>, &str, &str)] = &[
-        (
-            basics::Example,
-            "Basics",
-            include_str!("./reference/basics.rs"),
-        ),
-        (
-            children::Example,
-            "Children",
-            include_str!("./reference/children.rs"),
-        ),
-        (
-            conditional_rendering::Example,
-            "Conditional Rendering",
-            include_str!("./reference/conditional_rendering.rs"),
-        ),
-        // TODO
-        (
-            controlled_inputs::Example,
-            "Controlled Inputs",
-            include_str!("./reference/controlled_inputs.rs"),
-        ),
-        (
-            empty::Example,
-            "empty",
-            include_str!("./reference/empty.rs"),
-        ),
-        (
-            custom_elements::Example,
-            "Custom Elements & Web Components",
-            include_str!("./reference/custom_elements.rs"),
-        ),
-        (
-            fragments::Example,
-            "Fragments",
-            include_str!("./reference/fragments.rs"),
-        ),
-        (
-            iterators::Example,
-            "Iterators",
-            include_str!("./reference/iterators.rs"),
-        ),
-        (
-            global_css::Example,
-            "Global CSS",
-            include_str!("./reference/global_css.rs"),
-        ),
-        (
-            inline_styles::Example,
-            "Inline Styles",
-            include_str!("./reference/inline_styles.rs"),
-        ),
-        (
-            listener::Example,
-            "Listener",
-            include_str!("./reference/listener.rs"),
-        ),
-        (memo::Example, "Memo", include_str!("./reference/memo.rs")),
-        (
-            spreadpattern::Example,
-            "Spread Pattern",
-            include_str!("./reference/spreadpattern.rs"),
-        ),
-        (
-            suspense::Example,
-            "Suspense",
-            include_str!("./reference/suspense.rs"),
-        ),
-        (task::Example, "Task", include_str!("./reference/task.rs")),
-        (
-            tostring::Example,
-            "Tostring",
-            include_str!("./reference/tostring.rs"),
-        ),
-        (
-            antipatterns::Example,
-            "Anti-patterns",
-            include_str!("./reference/antipatterns.rs"),
-        ),
-        /*
-            TODO! these showcase features that aren't quite ready
-        */
-        (
-            signals::Example,
-            "Signals",
-            include_str!("./reference/signals.rs"),
-        ),
-        (
-            noderefs::Example,
-            "NodeRefs",
-            include_str!("./reference/noderefs.rs"),
-        ),
-        (
-            statemanagement::Example,
-            "State Management",
-            include_str!("./reference/statemanagement.rs"),
-        ),
-        (
-            testing::Example,
-            "Testing",
-            include_str!("./reference/testing.rs"),
-        ),
-    ];
-}

+ 9 - 2
examples/weather_app.rs

@@ -7,15 +7,22 @@
 use dioxus::prelude::*;
 
 fn main() {
-    // dioxus::desktop::launch(App, |c| c);
+    dioxus::desktop::launch(App, |c| c);
 }
 
+const ENDPOINT: &str = "https://api.openweathermap.org/data/2.5/weather";
+
 static App: FC<()> = |cx, props| {
     //
     let body = use_suspense(
         cx,
         || async {
-            //
+            let content = reqwest::get(ENDPOINT)
+                .await
+                .unwrap()
+                .json::<serde_json::Value>()
+                .await
+                .unwrap();
         },
         |cx, props| {
             //

+ 4 - 0
packages/core/src/context.rs

@@ -1,4 +1,8 @@
 //! Public APIs for managing component state, tasks, and lifecycles.
+//!
+//! This module is separate from `Scope` to narrow what exactly is exposed to user code.
+//!
+//! We unsafely implement `send` for the VirtualDom, but those guarantees can only be
 
 use crate::innerlude::*;
 use std::{any::TypeId, ops::Deref, rc::Rc};

+ 4 - 3
packages/core/src/events.rs

@@ -14,6 +14,7 @@ use std::{
     fmt::Debug,
     ops::Deref,
     rc::Rc,
+    sync::Arc,
 };
 
 #[derive(Debug)]
@@ -216,13 +217,13 @@ pub mod on {
         )* ) => {
             $(
                 $(#[$attr])*
-                pub struct $wrapper(pub Rc<dyn $eventdata>);
+                pub struct $wrapper(pub Arc<dyn $eventdata>);
 
                 // todo: derefing to the event is fine (and easy) but breaks some IDE stuff like (go to source)
                 // going to source in fact takes you to the source of Rc which is... less than useful
                 // Either we ask for this to be changed in Rust-analyzer or manually impkement the trait
                 impl Deref for $wrapper {
-                    type Target = Rc<dyn $eventdata>;
+                    type Target = Arc<dyn $eventdata>;
                     fn deref(&self) -> &Self::Target {
                         &self.0
                     }
@@ -598,7 +599,7 @@ pub mod on {
         ];
     }
 
-    pub struct GenericEvent(pub Rc<dyn GenericEventInner>);
+    pub struct GenericEvent(pub Arc<dyn GenericEventInner>);
 
     pub trait GenericEventInner {
         /// Return a reference to the raw event. User will need to downcast the event to the right platform-specific type.

+ 3 - 1
packages/core/src/lib.rs

@@ -29,6 +29,7 @@ pub mod scheduler;
 pub mod scope;
 pub mod tasks;
 pub mod test_dom;
+pub mod threadsafe;
 pub mod util;
 pub mod virtual_dom;
 
@@ -50,6 +51,7 @@ pub(crate) mod innerlude {
     pub use crate::scope::*;
     pub use crate::tasks::*;
     pub use crate::test_dom::*;
+    pub use crate::threadsafe::*;
     pub use crate::util::*;
     pub use crate::virtual_dom::*;
 
@@ -60,7 +62,7 @@ pub(crate) mod innerlude {
 pub use crate::innerlude::{
     Context, DioxusElement, DomEdit, DomTree, ElementId, EventPriority, LazyNodes, MountType,
     Mutations, NodeFactory, Properties, ScopeId, SuspendedContext, SyntheticEvent, TaskHandle,
-    TestDom, UserEvent, VNode, VirtualDom, FC,
+    TestDom, ThreadsafeVirtualDom, UserEvent, VNode, VirtualDom, FC,
 };
 
 pub mod prelude {

+ 1 - 0
packages/core/src/scope.rs

@@ -7,6 +7,7 @@ use std::{
     future::Future,
     pin::Pin,
     rc::Rc,
+    sync::Arc,
 };
 
 /// Every component in Dioxus is represented by a `Scope`.

+ 58 - 0
packages/core/src/threadsafe.rs

@@ -0,0 +1,58 @@
+//! A threadsafe wrapper for the VirtualDom
+
+use std::sync::{Arc, Mutex, MutexGuard};
+
+use crate::VirtualDom;
+
+/// A threadsafe wrapper for the Dioxus VirtualDom.
+///
+/// The Dioxus VirtualDom is not normally `Send` because user code can contain non-`Send` types. However, it is important
+/// to have a VirtualDom that is `Send` when used in server-side code since very few web frameworks support non-send
+/// handlers.
+///
+/// To address this, we have the `ThreadsafeVirtualDom` type which is a threadsafe wrapper for the VirtualDom. To access
+/// the VirtualDom, it must be first unlocked using the `lock` method. This locks the VirtualDom through a mutex and
+/// prevents any user code from leaking out. It is not possible to acquire any non-`Send` types from inside the VirtualDom.
+///
+/// The only way data may be accessed through the VirtualDom is from the "root props" method or by accessing a `Scope`
+/// directly. Even then, it's not possible to access any hook data. This means that non-Send types are only "in play"
+/// while the VirtualDom is locked with a non-Send marker.
+///
+/// Note that calling "wait for work" on the regular VirtualDom is inherently non-Send. If there are async tasks that
+/// need to be awaited, they must be done thread-local since we don't place any requirements on user tasks. This can be
+/// done with the function "spawn_local" in either tokio or async_std.
+pub struct ThreadsafeVirtualDom {
+    inner: Arc<Mutex<VirtualDom>>,
+}
+
+impl ThreadsafeVirtualDom {
+    pub fn new(inner: VirtualDom) -> Self {
+        let inner = Arc::new(Mutex::new(inner));
+        Self { inner }
+    }
+
+    pub fn lock(&self) -> Option<VirtualDomGuard> {
+        let locked = self.inner.lock().unwrap();
+        Some(VirtualDomGuard { guard: locked })
+    }
+}
+
+unsafe impl Send for ThreadsafeVirtualDom {}
+
+pub struct VirtualDomGuard<'a> {
+    guard: MutexGuard<'a, VirtualDom>,
+}
+
+impl<'a> std::ops::Deref for VirtualDomGuard<'a> {
+    type Target = MutexGuard<'a, VirtualDom>;
+
+    fn deref(&self) -> &Self::Target {
+        &self.guard
+    }
+}
+
+impl<'a> std::ops::DerefMut for VirtualDomGuard<'a> {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.guard
+    }
+}

+ 64 - 53
packages/core/src/virtual_dom.rs

@@ -20,8 +20,7 @@
 //! Additional functionality is defined in the respective files.
 
 use crate::innerlude::*;
-use futures_util::Future;
-use std::any::Any;
+use std::{any::Any, rc::Rc, sync::Arc};
 
 /// An integrated virtual node system that progresses events and diffs UI trees.
 ///
@@ -60,10 +59,10 @@ pub struct VirtualDom {
 
     root_fc: Box<dyn Any>,
 
-    root_props: Box<dyn Any>,
+    root_props: Rc<dyn Any + Send>,
 
     // we need to keep the allocation around, but we don't necessarily use it
-    _root_caller: Box<dyn for<'b> Fn(&'b Scope) -> DomTree<'b> + 'static>,
+    _root_caller: RootCaller,
 }
 
 impl VirtualDom {
@@ -121,16 +120,15 @@ impl VirtualDom {
     /// let mut dom = VirtualDom::new_with_props(Example, SomeProps { name: "jane" });
     /// let mutations = dom.rebuild();
     /// ```
-    pub fn new_with_props<P: Properties + 'static>(root: FC<P>, root_props: P) -> Self {
+    pub fn new_with_props<P: 'static + Send>(root: FC<P>, root_props: P) -> Self {
         let root_fc = Box::new(root);
 
-        let root_props: Box<dyn Any> = Box::new(root_props);
-
-        let props_ptr = root_props.downcast_ref::<P>().unwrap() as *const P;
+        let root_props: Rc<dyn Any + Send> = Rc::new(root_props);
 
+        let _p = root_props.clone();
         // Safety: this callback is only valid for the lifetime of the root props
-        let root_caller: Box<dyn Fn(&Scope) -> DomTree> = Box::new(move |scope: &Scope| unsafe {
-            let props: &'_ P = &*(props_ptr as *const P);
+        let root_caller: Rc<dyn Fn(&Scope) -> DomTree> = Rc::new(move |scope: &Scope| unsafe {
+            let props = _p.downcast_ref::<P>().unwrap();
             std::mem::transmute(root(Context { scope }, props))
         });
 
@@ -148,7 +146,7 @@ impl VirtualDom {
         });
 
         Self {
-            _root_caller: root_caller,
+            _root_caller: RootCaller(root_caller),
             root_fc,
             base_scope,
             scheduler,
@@ -189,14 +187,14 @@ impl VirtualDom {
     /// ```
     pub fn update_root_props<P>(&mut self, root_props: P) -> Option<Mutations>
     where
-        P: 'static,
+        P: 'static + Send,
     {
         let root_scope = self.scheduler.pool.get_scope_mut(self.base_scope).unwrap();
 
         // Pre-emptively drop any downstream references of the old props
         root_scope.ensure_drop_safety(&self.scheduler.pool);
 
-        let mut root_props: Box<dyn Any> = Box::new(root_props);
+        let mut root_props: Rc<dyn Any + Send> = Rc::new(root_props);
 
         if let Some(props_ptr) = root_props.downcast_ref::<P>().map(|p| p as *const P) {
             // Swap the old props and new props
@@ -345,46 +343,47 @@ impl VirtualDom {
     /// Waits for the scheduler to have work
     /// This lets us poll async tasks during idle periods without blocking the main thread.
     pub async fn wait_for_work(&mut self) {
-        if self.scheduler.has_any_work() {
-            log::debug!("No need to wait for work, we already have some");
-            return;
-        }
-
-        log::debug!("No active work.... waiting for some...");
-        use futures_util::StreamExt;
-
-        // right now this won't poll events if there is ongoing work
-        // in the future we want to prioritize some events over ongoing work
-        // this is coming in the "priorities" PR
-
-        // Wait for any new events if we have nothing to do
-        // todo: poll the events once even if there is work to do to prevent starvation
-        futures_util::select! {
-            _ = self.scheduler.async_tasks.next() => {}
-            msg = self.scheduler.receiver.next() => {
-                match msg.unwrap() {
-                    SchedulerMsg::Task(t) => todo!(),
-                    SchedulerMsg::Immediate(im) => {
-                        self.scheduler.dirty_scopes.insert(im);
-                    }
-                    SchedulerMsg::UiEvent(evt) => {
-                        self.scheduler.ui_events.push_back(evt);
-                    }
-                }
-            },
-        }
-
-        while let Ok(Some(msg)) = self.scheduler.receiver.try_next() {
-            match msg {
-                SchedulerMsg::Task(t) => todo!(),
-                SchedulerMsg::Immediate(im) => {
-                    self.scheduler.dirty_scopes.insert(im);
-                }
-                SchedulerMsg::UiEvent(evt) => {
-                    self.scheduler.ui_events.push_back(evt);
-                }
-            }
-        }
+        todo!("making vdom send right now");
+        // if self.scheduler.has_any_work() {
+        //     log::debug!("No need to wait for work, we already have some");
+        //     return;
+        // }
+
+        // log::debug!("No active work.... waiting for some...");
+        // use futures_util::StreamExt;
+
+        // // right now this won't poll events if there is ongoing work
+        // // in the future we want to prioritize some events over ongoing work
+        // // this is coming in the "priorities" PR
+
+        // // Wait for any new events if we have nothing to do
+        // // todo: poll the events once even if there is work to do to prevent starvation
+        // futures_util::select! {
+        //     _ = self.scheduler.async_tasks.next() => {}
+        //     msg = self.scheduler.receiver.next() => {
+        //         match msg.unwrap() {
+        //             SchedulerMsg::Task(t) => todo!(),
+        //             SchedulerMsg::Immediate(im) => {
+        //                 self.scheduler.dirty_scopes.insert(im);
+        //             }
+        //             SchedulerMsg::UiEvent(evt) => {
+        //                 self.scheduler.ui_events.push_back(evt);
+        //             }
+        //         }
+        //     },
+        // }
+
+        // while let Ok(Some(msg)) = self.scheduler.receiver.try_next() {
+        //     match msg {
+        //         SchedulerMsg::Task(t) => todo!(),
+        //         SchedulerMsg::Immediate(im) => {
+        //             self.scheduler.dirty_scopes.insert(im);
+        //         }
+        //         SchedulerMsg::UiEvent(evt) => {
+        //             self.scheduler.ui_events.push_back(evt);
+        //         }
+        //     }
+        // }
     }
 }
 
@@ -407,3 +406,15 @@ impl std::fmt::Display for VirtualDom {
         renderer.render(self, root, f, 0)
     }
 }
+
+/*
+Send safety...
+
+The VirtualDom can only be "send" if the internals exposed to user code are also "send".
+
+IE it's okay to move an Rc from one thread to another thread as long as it's only used internally
+
+*/
+
+// we never actually use the contents of this root caller
+struct RootCaller(Rc<dyn for<'b> Fn(&'b Scope) -> DomTree<'b> + 'static>);

+ 1 - 1
packages/core/tests/create_dom.rs

@@ -11,7 +11,7 @@ use dioxus_html as dioxus_elements;
 mod test_logging;
 use DomEdit::*;
 
-fn new_dom<P: Properties + 'static>(app: FC<P>, props: P) -> VirtualDom {
+fn new_dom<P: 'static + Send>(app: FC<P>, props: P) -> VirtualDom {
     const IS_LOGGING_ENABLED: bool = false;
     test_logging::set_up_logging(IS_LOGGING_ENABLED);
     VirtualDom::new_with_props(app, props)

+ 5 - 7
packages/core/tests/lifecycle.rs

@@ -1,33 +1,31 @@
 //! Tests for the lifecycle of components.
 
-use std::{cell::RefCell, rc::Rc};
-
 use anyhow::{Context, Result};
 use dioxus::prelude::*;
 use dioxus_core as dioxus;
 use dioxus_core_macro::*;
 use dioxus_html as dioxus_elements;
+use std::sync::{Arc, Mutex};
 
 mod test_logging;
 
 const IS_LOGGING_ENABLED: bool = true;
-type Shared<T> = Rc<RefCell<T>>;
+type Shared<T> = Arc<Mutex<T>>;
 
 #[test]
 fn manual_diffing() {
     test_logging::set_up_logging(IS_LOGGING_ENABLED);
 
-    #[derive(PartialEq, Props)]
     struct AppProps {
         value: Shared<&'static str>,
     }
 
     static App: FC<AppProps> = |cx, props| {
-        let val = props.value.borrow();
+        let val = props.value.lock().unwrap();
         cx.render(rsx! { div { "{val}" } })
     };
 
-    let value = Rc::new(RefCell::new("Hello"));
+    let value = Arc::new(Mutex::new("Hello"));
     let mut dom = VirtualDom::new_with_props(
         App,
         AppProps {
@@ -37,7 +35,7 @@ fn manual_diffing() {
 
     let _ = dom.rebuild();
 
-    *value.borrow_mut() = "goodbye";
+    *value.lock().unwrap() = "goodbye";
 
     let edits = dom.diff();
 

+ 2 - 2
packages/desktop/.vscode/settings.json

@@ -1,3 +1,3 @@
 {
-    "rust-analyzer.inlayHints.enable": false
-}
+  "rust-analyzer.inlayHints.enable": true
+}

+ 2 - 2
packages/desktop/Cargo.toml

@@ -20,14 +20,14 @@ log = "0.4.13"
 fern = { version = "0.6.0", features = ["colored"] }
 html-escape = "0.2.9"
 wry = "0.11.0"
-async-std = { version = "1.9.0", features = ["attributes"] }
+tokio = { version = "1.12.0", features = ["full"] }
 
 
 [dev-dependencies]
 dioxus-html = { path = "../html" }
 tide = "0.15.0"
 tide-websockets = "0.3.0"
-
+dioxus-core-macro = { path = "../core-macro" }
 # thiserror = "1.0.23"
 # log = "0.4.13"
 # fern = { version = "0.6.0", features = ["colored"] }

+ 24 - 0
packages/desktop/examples/demo.rs

@@ -0,0 +1,24 @@
+use dioxus_core as dioxus;
+use dioxus_core::prelude::*;
+use dioxus_core_macro::*;
+
+use dioxus_html as dioxus_elements;
+
+fn main() {
+    std::thread::spawn(|| {
+        let mut vdom = VirtualDom::new(App);
+        let f = async_std::task::block_on(vdom.wait_for_work());
+    });
+    let a = 10;
+    // async_std::task::spawn_blocking(|| async move {
+    // });
+}
+
+static App: FC<()> = |cx, props| {
+    //
+    cx.render(rsx!(
+        div {
+            "hello world!"
+        }
+    ))
+};

+ 0 - 16
packages/desktop/examples/test.rs

@@ -1,16 +0,0 @@
-use dioxus_core as dioxus;
-use dioxus_core::prelude::*;
-use dioxus_html as dioxus_elements;
-
-fn main() {
-    dioxus_desktop::launch(App, |f| f.with_window(|w| w.with_maximized(true))).expect("Failed");
-}
-
-static App: FC<()> = |cx, props|{
-    //
-    cx.render(rsx!(
-        div {
-            "hello world!"
-        }
-    ))
-};

+ 2 - 1
packages/desktop/src/events.rs

@@ -2,6 +2,7 @@
 //!
 
 use std::rc::Rc;
+use std::sync::Arc;
 
 use dioxus_core::{
     events::{
@@ -21,7 +22,7 @@ pub fn trigger_from_serialized(val: serde_json::Value) -> UserEvent {
     let mut data: Vec<ImEvent> = serde_json::from_value(val).unwrap();
     let data = data.drain(..).next().unwrap();
 
-    let event = SyntheticEvent::MouseEvent(MouseEvent(Rc::new(WebviewMouseEvent)));
+    let event = SyntheticEvent::MouseEvent(MouseEvent(Arc::new(WebviewMouseEvent)));
     let scope = ScopeId(data.scope as usize);
     let mounted_dom_id = Some(ElementId(data.mounted_dom_id as usize));
     UserEvent {

+ 167 - 186
packages/desktop/src/lib.rs

@@ -1,5 +1,11 @@
+//! Dioxus Desktop Renderer
+//!
+//! Render the Dioxus VirtualDom using the platform's native WebView implementation.
+//!
+
 use std::borrow::BorrowMut;
 use std::ops::{Deref, DerefMut};
+use std::sync::atomic::AtomicBool;
 use std::sync::mpsc::channel;
 use std::sync::{Arc, RwLock};
 
@@ -21,7 +27,6 @@ mod cfg;
 mod dom;
 mod escape;
 mod events;
-use events::*;
 
 static HTML_CONTENT: &'static str = include_str!("./index.html");
 
@@ -31,20 +36,15 @@ pub fn launch(
 ) -> anyhow::Result<()> {
     launch_with_props(root, (), config_builder)
 }
-pub fn launch_with_props<P: Properties + 'static>(
+
+pub fn launch_with_props<P: Properties + 'static + Send + Sync>(
     root: FC<P>,
     props: P,
     builder: impl for<'a, 'b> FnOnce(&'b mut DesktopConfig<'a>) -> &'b mut DesktopConfig<'a>,
 ) -> anyhow::Result<()> {
-    WebviewRenderer::run(root, props, builder)
+    run(root, props, builder)
 }
 
-/// The `WebviewRenderer` provides a way of rendering a Dioxus Virtual DOM through a bridge to a Webview instance.
-/// Components used in WebviewRenderer instances can directly use system libraries, access the filesystem, and multithread with ease.
-pub struct WebviewRenderer<T> {
-    /// The root component used to render the Webview
-    root: FC<T>,
-}
 enum RpcEvent<'a> {
     Initialize { edits: Vec<DomEdit<'a>> },
 }
@@ -55,184 +55,165 @@ struct Response<'a> {
     edits: Vec<DomEdit<'a>>,
 }
 
-impl<T: Properties + 'static> WebviewRenderer<T> {
-    pub fn run(
-        root: FC<T>,
-        props: T,
-        user_builder: impl for<'a, 'b> FnOnce(&'b mut DesktopConfig<'a>) -> &'b mut DesktopConfig<'a>,
-    ) -> anyhow::Result<()> {
-        Self::run_with_edits(root, props, user_builder, None)
-    }
-
-    pub fn run_with_edits<
-        F: for<'a, 'b> FnOnce(&'a mut DesktopConfig<'b>) -> &'a mut DesktopConfig<'b>,
-    >(
-        root: FC<T>,
-        props: T,
-        user_builder: F,
-        redits: Option<Vec<DomEdit<'static>>>,
-    ) -> anyhow::Result<()> {
-        log::info!("hello edits");
-        let event_loop = EventLoop::new();
-
-        let mut cfg = DesktopConfig::new();
-        user_builder(&mut cfg);
-
-        let DesktopConfig {
-            window,
-            manual_edits,
-            pre_rendered,
-        } = cfg;
-
-        let window = window.build(&event_loop)?;
-
-        let mut vir = VirtualDom::new_with_props(root, props);
-
-        let channel = vir.get_event_sender();
-        struct WebviewBridge {}
-        // impl RealDom for WebviewBridge {
-        //     fn raw_node_as_any(&self) -> &mut dyn std::any::Any {
-        //         todo!()
-        //     }
-
-        //     fn must_commit(&self) -> bool {
-        //         false
-        //     }
-
-        //     fn commit_edits<'a>(&mut self, edits: &mut Vec<DomEdit<'a>>) {}
-
-        //     fn wait_until_ready<'s>(
-        //         &'s mut self,
-        //     ) -> std::pin::Pin<Box<dyn std::future::Future<Output = ()> + 's>> {
-        //         //
-        //         Box::pin(async {
-        //             //
-        //         })
-        //     }
-        // }
-
-        let mut real_dom = WebviewBridge {};
-        // async_std::task::spawn_local(vir.run(&mut real_dom));
-
-        // todo: combine these or something
-        let vdom = Arc::new(RwLock::new(vir));
-        // let registry = Arc::new(RwLock::new(Some(WebviewRegistry::new())));
-
-        let webview = WebViewBuilder::new(window)?
-            .with_url(&format!("data:text/html,{}", HTML_CONTENT))?
-            // .with_rpc_handler(move |_window: &Window, mut req: RpcRequest| {
-            //     match req.method.as_str() {
-            //         "initiate" => {
-            //             let edits = if let Some(edits) = &redits {
-            //                 serde_json::to_value(edits).unwrap()
-            //             } else {
-            //                 let mut lock = vdom.write().unwrap();
-            //                 // let mut reg_lock = registry.write().unwrap();
-            //                 // Create the thin wrapper around the registry to collect the edits into
-            //                 let mut real = dom::WebviewDom::new();
-            //                 let pre = pre_rendered.clone();
-            //                 let response = match pre {
-            //                     Some(content) => {
-            //                         lock.rebuild_in_place().unwrap();
-            //                         Response {
-            //                             edits: Vec::new(),
-            //                             pre_rendered: Some(content),
-            //                         }
-            //                     }
-            //                     None => {
-            //                         //
-            //                         let edits = {
-            //                             // let mut edits = Vec::new();
-            //                             todo!()
-            //                             // lock.rebuild(&mut real, &mut edits).unwrap();
-            //                             // edits
-            //                         };
-            //                         Response {
-            //                             edits,
-            //                             pre_rendered: None,
-            //                         }
-            //                     }
-            //                 };
-            //                 serde_json::to_value(&response).unwrap()
-            //             };
-            //             // Return the edits into the webview runtime
-            //             Some(RpcResponse::new_result(req.id.take(), Some(edits)))
-            //         }
-            //         "user_event" => {
-            //             log::debug!("User event received");
-            //             // let registry = registry.clone();
-            //             let vdom = vdom.clone();
-            //             let response = async_std::task::block_on(async move {
-            //                 let mut lock = vdom.write().unwrap();
-            //                 // let mut reg_lock = registry.write().unwrap();
-            //                 // a deserialized event
-            //                 let data = req.params.unwrap();
-            //                 log::debug!("Data: {:#?}", data);
-            //                 let event = trigger_from_serialized(data);
-            //                 // lock.queue_event(event);
-            //                 // Create the thin wrapper around the registry to collect the edits into
-            //                 let mut real = dom::WebviewDom::new();
-            //                 // Serialize the edit stream
-            //                 //
-            //                 let mut edits = Vec::new();
-            //                 // lock.run(&mut real, &mut edits)
-            //                 //     .await
-            //                 //     .expect("failed to progress");
-            //                 let response = Response {
-            //                     edits,
-            //                     pre_rendered: None,
-            //                 };
-            //                 let response = serde_json::to_value(&response).unwrap();
-            //                 // Give back the registry into its slot
-            //                 // *reg_lock = Some(real.consume());
-            //                 // Return the edits into the webview runtime
-            //                 Some(RpcResponse::new_result(req.id.take(), Some(response)))
-            //             });
-            //             response
-            //             // spawn a task to clean up the garbage
-            //         }
-            //         _ => todo!("this message failed"),
-            //     }
-            // })
-            .build()?;
-
-        event_loop.run(move |event, _, control_flow| {
-            *control_flow = ControlFlow::Wait;
-
-            match event {
-                Event::WindowEvent {
-                    event, window_id, ..
-                } => match event {
-                    WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
-                    WindowEvent::Resized(_) | WindowEvent::Moved(_) => {
-                        let _ = webview.resize();
-                    }
-                    _ => {}
-                },
-
-                Event::MainEventsCleared => {
-                    webview.resize();
-                    // window.request_redraw();
-                }
+pub fn run<T: Properties + 'static + Send + Sync>(
+    root: FC<T>,
+    props: T,
+    user_builder: impl for<'a, 'b> FnOnce(&'b mut DesktopConfig<'a>) -> &'b mut DesktopConfig<'a>,
+) -> anyhow::Result<()> {
+    run_with_edits(root, props, user_builder, None)
+}
 
+pub fn run_with_edits<
+    F: for<'a, 'b> FnOnce(&'a mut DesktopConfig<'b>) -> &'a mut DesktopConfig<'b>,
+    T: Properties + 'static + Send + Sync,
+>(
+    root: FC<T>,
+    props: T,
+    user_builder: F,
+    redits: Option<Vec<DomEdit<'static>>>,
+) -> anyhow::Result<()> {
+    /*
+
+
+    */
+
+    let mut cfg = DesktopConfig::new();
+    user_builder(&mut cfg);
+    let DesktopConfig {
+        window,
+        manual_edits,
+        pre_rendered,
+    } = cfg;
+
+    let event_loop = EventLoop::new();
+    let window = window.build(&event_loop)?;
+
+    let (event_tx, mut event_rx) = tokio::sync::mpsc::unbounded_channel::<String>();
+
+    // Spawn the virtualdom onto its own thread
+    // if it wants to spawn multithreaded tasks, it can use the executor directly
+    std::thread::spawn(move || {
+        //
+        let runtime = tokio::runtime::Builder::new_current_thread()
+            .enable_all()
+            .build()
+            .unwrap();
+
+        runtime.block_on(async move {
+            let mut vir = VirtualDom::new_with_props(root, props);
+            let channel = vir.get_event_sender();
+            loop {
+                vir.wait_for_work().await;
+                let edits = vir.run_with_deadline(|| false);
+                let edit_string = serde_json::to_string(&edits[0].edits).unwrap();
+                event_tx.send(edit_string).unwrap();
+            }
+        })
+    });
+
+    let dioxus_requsted = Arc::new(AtomicBool::new(false));
+
+    let webview = WebViewBuilder::new(window)?
+        .with_url(&format!("data:text/html,{}", HTML_CONTENT))?
+        .with_rpc_handler(move |_window: &Window, mut req: RpcRequest| {
+            match req.method.as_str() {
+                "initiate" => {}
+                "user_event" => {}
+                _ => todo!("this message failed"),
+            }
+            todo!()
+        })
+        .build()?;
+
+    event_loop.run(move |event, _, control_flow| {
+        *control_flow = ControlFlow::Wait;
+
+        match event {
+            Event::WindowEvent {
+                event, window_id, ..
+            } => match event {
+                WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
+                WindowEvent::Resized(_) | WindowEvent::Moved(_) => {
+                    let _ = webview.resize();
+                }
                 _ => {}
+            },
+
+            Event::MainEventsCleared => {
+                webview.resize();
+                // window.request_redraw();
             }
-        });
-    }
-
-    /// Create a new text renderer from an existing Virtual DOM.
-    /// This will progress the existing VDom's events to completion.
-    pub fn from_vdom() -> Self {
-        todo!()
-    }
-
-    /// Pass new args to the root function
-    pub fn update(&mut self, _new_val: T) {
-        todo!()
-    }
-
-    /// Modify the root function in place, forcing a re-render regardless if the props changed
-    pub fn update_mut(&mut self, _modifier: impl Fn(&mut T)) {
-        todo!()
-    }
+
+            _ => {}
+        }
+    });
+
+    Ok(())
 }
+
+// let edits = if let Some(edits) = &redits {
+//     serde_json::to_value(edits).unwrap()
+// } else {
+//     let mut lock = vdom.write().unwrap();
+//     // let mut reg_lock = registry.write().unwrap();
+//     // Create the thin wrapper around the registry to collect the edits into
+//     let mut real = dom::WebviewDom::new();
+//     let pre = pre_rendered.clone();
+//     let response = match pre {
+//         Some(content) => {
+//             lock.rebuild_in_place().unwrap();
+//             Response {
+//                 edits: Vec::new(),
+//                 pre_rendered: Some(content),
+//             }
+//         }
+//         None => {
+//             //
+//             let edits = {
+//                 // let mut edits = Vec::new();
+//                 todo!()
+//                 // lock.rebuild(&mut real, &mut edits).unwrap();
+//                 // edits
+//             };
+//             Response {
+//                 edits,
+//                 pre_rendered: None,
+//             }
+//         }
+//     };
+//     serde_json::to_value(&response).unwrap()
+// };
+// // Return the edits into the webview runtime
+// Some(RpcResponse::new_result(req.id.take(), Some(edits)))
+
+// log::debug!("User event received");
+// // let registry = registry.clone();
+// let vdom = vdom.clone();
+// let response = async_std::task::block_on(async move {
+//     let mut lock = vdom.write().unwrap();
+//     // let mut reg_lock = registry.write().unwrap();
+//     // a deserialized event
+//     let data = req.params.unwrap();
+//     log::debug!("Data: {:#?}", data);
+//     let event = trigger_from_serialized(data);
+//     // lock.queue_event(event);
+//     // Create the thin wrapper around the registry to collect the edits into
+//     let mut real = dom::WebviewDom::new();
+//     // Serialize the edit stream
+//     //
+//     let mut edits = Vec::new();
+//     // lock.run(&mut real, &mut edits)
+//     //     .await
+//     //     .expect("failed to progress");
+//     let response = Response {
+//         edits,
+//         pre_rendered: None,
+//     };
+//     let response = serde_json::to_value(&response).unwrap();
+//     // Give back the registry into its slot
+//     // *reg_lock = Some(real.consume());
+//     // Return the edits into the webview runtime
+//     Some(RpcResponse::new_result(req.id.take(), Some(response)))
+// });
+// response
+// // spawn a task to clean up the garbage

+ 51 - 50
packages/ssr/examples/tide.rs

@@ -8,53 +8,54 @@ use dioxus_core_macro::*;
 use dioxus_hooks::use_state;
 use dioxus_html as dioxus_elements;
 
-use tide::{Request, Response};
-
-#[async_std::main]
-async fn main() -> Result<(), std::io::Error> {
-    let mut app = tide::new();
-
-    app.at("/:name").get(|req: Request<()>| async move {
-        let initial_name: String = req
-            .param("name")
-            .map(|f| f.parse().unwrap_or("...?".to_string()))
-            .unwrap_or("...?".to_string());
-
-        let mut dom = VirtualDom::new_with_props(Example, ExampleProps { initial_name });
-
-        dom.rebuild();
-
-        Ok(Response::builder(200)
-            .body(format!("{}", dioxus_ssr::render_vdom(&dom, |c| c)))
-            .content_type(tide::http::mime::HTML)
-            .build())
-    });
-
-    println!("Server available at [http://127.0.0.1:8080/bill]");
-    app.listen("127.0.0.1:8080").await?;
-
-    Ok(())
-}
-
-#[derive(PartialEq, Props)]
-struct ExampleProps {
-    initial_name: String,
-}
-
-static Example: FC<ExampleProps> = |cx, props| {
-    let dispaly_name = use_state(cx, move || props.initial_name.clone());
-
-    cx.render(rsx! {
-        div { class: "py-12 px-4 text-center w-full max-w-2xl mx-auto",
-            span { class: "text-sm font-semibold"
-                "Dioxus Example: Jack and Jill"
-            }
-            h2 { class: "text-5xl mt-2 mb-6 leading-tight font-semibold font-heading"
-                "Hello, {dispaly_name}"
-            }
-            ul {
-                {(0..10).map(|f| rsx!( li {"Element {f}"} ))}
-            }
-        }
-    })
-};
+fn main() {}
+// use tide::{Request, Response};
+
+// #[async_std::main]
+// async fn main() -> Result<(), std::io::Error> {
+//     let mut app = tide::new();
+
+//     app.at("/:name").get(|req: Request<()>| async move {
+//         let initial_name: String = req
+//             .param("name")
+//             .map(|f| f.parse().unwrap_or("...?".to_string()))
+//             .unwrap_or("...?".to_string());
+
+//         let mut dom = VirtualDom::new_with_props(Example, ExampleProps { initial_name });
+
+//         dom.rebuild();
+
+//         Ok(Response::builder(200)
+//             .body(format!("{}", dioxus_ssr::render_vdom(&dom, |c| c)))
+//             .content_type(tide::http::mime::HTML)
+//             .build())
+//     });
+
+//     println!("Server available at [http://127.0.0.1:8080/bill]");
+//     app.listen("127.0.0.1:8080").await?;
+
+//     Ok(())
+// }
+
+// #[derive(PartialEq, Props)]
+// struct ExampleProps {
+//     initial_name: String,
+// }
+
+// static Example: FC<ExampleProps> = |cx, props| {
+//     let dispaly_name = use_state(cx, move || props.initial_name.clone());
+
+//     cx.render(rsx! {
+//         div { class: "py-12 px-4 text-center w-full max-w-2xl mx-auto",
+//             span { class: "text-sm font-semibold"
+//                 "Dioxus Example: Jack and Jill"
+//             }
+//             h2 { class: "text-5xl mt-2 mb-6 leading-tight font-semibold font-heading"
+//                 "Hello, {dispaly_name}"
+//             }
+//             ul {
+//                 {(0..10).map(|f| rsx!( li {"Element {f}"} ))}
+//             }
+//         }
+//     })
+// };

+ 20 - 18
packages/web/src/dom.rs

@@ -14,7 +14,7 @@ use dioxus_core::{
     DomEdit, ElementId, ScopeId,
 };
 use fxhash::FxHashMap;
-use std::{fmt::Debug, rc::Rc};
+use std::{fmt::Debug, rc::Rc, sync::Arc};
 use wasm_bindgen::{closure::Closure, JsCast};
 use web_sys::{
     Attr, CssStyleDeclaration, Document, Element, Event, HtmlElement, HtmlInputElement,
@@ -471,77 +471,79 @@ fn virtual_event_from_websys_event(event: web_sys::Event) -> SyntheticEvent {
     use dioxus_core::events::on::*;
     match event.type_().as_str() {
         "copy" | "cut" | "paste" => {
-            SyntheticEvent::ClipboardEvent(ClipboardEvent(Rc::new(WebsysClipboardEvent(event))))
+            SyntheticEvent::ClipboardEvent(ClipboardEvent(Arc::new(WebsysClipboardEvent(event))))
         }
         "compositionend" | "compositionstart" | "compositionupdate" => {
             let evt: web_sys::CompositionEvent = event.dyn_into().unwrap();
-            SyntheticEvent::CompositionEvent(CompositionEvent(Rc::new(WebsysCompositionEvent(evt))))
+            SyntheticEvent::CompositionEvent(CompositionEvent(Arc::new(WebsysCompositionEvent(
+                evt,
+            ))))
         }
         "keydown" | "keypress" | "keyup" => {
             let evt: web_sys::KeyboardEvent = event.dyn_into().unwrap();
-            SyntheticEvent::KeyboardEvent(KeyboardEvent(Rc::new(WebsysKeyboardEvent(evt))))
+            SyntheticEvent::KeyboardEvent(KeyboardEvent(Arc::new(WebsysKeyboardEvent(evt))))
         }
         "focus" | "blur" => {
             let evt: web_sys::FocusEvent = event.dyn_into().unwrap();
-            SyntheticEvent::FocusEvent(FocusEvent(Rc::new(WebsysFocusEvent(evt))))
+            SyntheticEvent::FocusEvent(FocusEvent(Arc::new(WebsysFocusEvent(evt))))
         }
         "change" => {
             let evt = event.dyn_into().unwrap();
-            SyntheticEvent::GenericEvent(GenericEvent(Rc::new(WebsysGenericUiEvent(evt))))
+            SyntheticEvent::GenericEvent(GenericEvent(Arc::new(WebsysGenericUiEvent(evt))))
         }
         "input" | "invalid" | "reset" | "submit" => {
             let evt: web_sys::Event = event.dyn_into().unwrap();
-            SyntheticEvent::FormEvent(FormEvent(Rc::new(WebsysFormEvent(evt))))
+            SyntheticEvent::FormEvent(FormEvent(Arc::new(WebsysFormEvent(evt))))
         }
         "click" | "contextmenu" | "doubleclick" | "drag" | "dragend" | "dragenter" | "dragexit"
         | "dragleave" | "dragover" | "dragstart" | "drop" | "mousedown" | "mouseenter"
         | "mouseleave" | "mousemove" | "mouseout" | "mouseover" | "mouseup" => {
             let evt: web_sys::MouseEvent = event.dyn_into().unwrap();
-            SyntheticEvent::MouseEvent(MouseEvent(Rc::new(WebsysMouseEvent(evt))))
+            SyntheticEvent::MouseEvent(MouseEvent(Arc::new(WebsysMouseEvent(evt))))
         }
         "pointerdown" | "pointermove" | "pointerup" | "pointercancel" | "gotpointercapture"
         | "lostpointercapture" | "pointerenter" | "pointerleave" | "pointerover" | "pointerout" => {
             let evt: web_sys::PointerEvent = event.dyn_into().unwrap();
-            SyntheticEvent::PointerEvent(PointerEvent(Rc::new(WebsysPointerEvent(evt))))
+            SyntheticEvent::PointerEvent(PointerEvent(Arc::new(WebsysPointerEvent(evt))))
         }
         "select" => {
             let evt: web_sys::UiEvent = event.dyn_into().unwrap();
-            SyntheticEvent::SelectionEvent(SelectionEvent(Rc::new(WebsysGenericUiEvent(evt))))
+            SyntheticEvent::SelectionEvent(SelectionEvent(Arc::new(WebsysGenericUiEvent(evt))))
         }
         "touchcancel" | "touchend" | "touchmove" | "touchstart" => {
             let evt: web_sys::TouchEvent = event.dyn_into().unwrap();
-            SyntheticEvent::TouchEvent(TouchEvent(Rc::new(WebsysTouchEvent(evt))))
+            SyntheticEvent::TouchEvent(TouchEvent(Arc::new(WebsysTouchEvent(evt))))
         }
         "scroll" => {
             let evt: web_sys::UiEvent = event.dyn_into().unwrap();
-            SyntheticEvent::GenericEvent(GenericEvent(Rc::new(WebsysGenericUiEvent(evt))))
+            SyntheticEvent::GenericEvent(GenericEvent(Arc::new(WebsysGenericUiEvent(evt))))
         }
         "wheel" => {
             let evt: web_sys::WheelEvent = event.dyn_into().unwrap();
-            SyntheticEvent::WheelEvent(WheelEvent(Rc::new(WebsysWheelEvent(evt))))
+            SyntheticEvent::WheelEvent(WheelEvent(Arc::new(WebsysWheelEvent(evt))))
         }
         "animationstart" | "animationend" | "animationiteration" => {
             let evt: web_sys::AnimationEvent = event.dyn_into().unwrap();
-            SyntheticEvent::AnimationEvent(AnimationEvent(Rc::new(WebsysAnimationEvent(evt))))
+            SyntheticEvent::AnimationEvent(AnimationEvent(Arc::new(WebsysAnimationEvent(evt))))
         }
         "transitionend" => {
             let evt: web_sys::TransitionEvent = event.dyn_into().unwrap();
-            SyntheticEvent::TransitionEvent(TransitionEvent(Rc::new(WebsysTransitionEvent(evt))))
+            SyntheticEvent::TransitionEvent(TransitionEvent(Arc::new(WebsysTransitionEvent(evt))))
         }
         "abort" | "canplay" | "canplaythrough" | "durationchange" | "emptied" | "encrypted"
         | "ended" | "error" | "loadeddata" | "loadedmetadata" | "loadstart" | "pause" | "play"
         | "playing" | "progress" | "ratechange" | "seeked" | "seeking" | "stalled" | "suspend"
         | "timeupdate" | "volumechange" | "waiting" => {
             let evt: web_sys::UiEvent = event.dyn_into().unwrap();
-            SyntheticEvent::MediaEvent(MediaEvent(Rc::new(WebsysMediaEvent(evt))))
+            SyntheticEvent::MediaEvent(MediaEvent(Arc::new(WebsysMediaEvent(evt))))
         }
         "toggle" => {
             let evt: web_sys::UiEvent = event.dyn_into().unwrap();
-            SyntheticEvent::ToggleEvent(ToggleEvent(Rc::new(WebsysToggleEvent(evt))))
+            SyntheticEvent::ToggleEvent(ToggleEvent(Arc::new(WebsysToggleEvent(evt))))
         }
         _ => {
             let evt: web_sys::UiEvent = event.dyn_into().unwrap();
-            SyntheticEvent::GenericEvent(GenericEvent(Rc::new(WebsysGenericUiEvent(evt))))
+            SyntheticEvent::GenericEvent(GenericEvent(Arc::new(WebsysGenericUiEvent(evt))))
         }
     }
 }

+ 1 - 1
src/lib.rs

@@ -193,7 +193,7 @@ pub mod debug {}
 pub mod prelude {
     //! A glob import that includes helper types like FC, rsx!, html!, and required traits
     pub use dioxus_core::prelude::*;
-    use dioxus_core_macro::{format_args_f, html, rsx, Props};
+    pub use dioxus_core_macro::{format_args_f, html, rsx, Props};
     pub use dioxus_elements::{GlobalAttributes, SvgAttributes};
     pub use dioxus_hooks::*;
     pub use dioxus_html as dioxus_elements;