Bläddra i källkod

Merge pull request #42 from DioxusLabs/jk/stack_dst

Jk/stack dst
Jonathan Kelley 3 år sedan
förälder
incheckning
f53b824089
66 ändrade filer med 1272 tillägg och 569 borttagningar
  1. 2 0
      Cargo.toml
  2. 1 1
      examples/async.rs
  3. 1 1
      examples/calculator.rs
  4. 1 1
      examples/coroutine.rs
  5. 1 1
      examples/crm.rs
  6. 1 1
      examples/file_explorer.rs
  7. 1 1
      examples/framework_benchmark.rs
  8. 1 1
      examples/hydration.rs
  9. 1 1
      examples/manual_edits.rs
  10. 1 1
      examples/pattern_model.rs
  11. 1 1
      examples/pattern_reducer.rs
  12. 1 1
      examples/readme.rs
  13. 1 1
      examples/router.rs
  14. 1 1
      examples/rsx_usage.rs
  15. 1 1
      examples/ssr.rs
  16. 6 6
      examples/tailwind.rs
  17. 1 1
      examples/tasks.rs
  18. 1 1
      examples/todomvc.rs
  19. 2 2
      examples/weather_app.rs
  20. 1 1
      examples/web_tick.rs
  21. 1 1
      examples/webview_web.rs
  22. 1 1
      packages/core/benches/jsframework.rs
  23. 34 0
      packages/core/examples/handler_thru_props.rs
  24. 1 1
      packages/core/examples/rows.rs
  25. 4 2
      packages/core/examples/works.rs
  26. 8 8
      packages/core/src/diff.rs
  27. 8 17
      packages/core/src/lib.rs
  28. 0 2
      packages/core/src/mutations.rs
  29. 28 9
      packages/core/src/nodes.rs
  30. 50 50
      packages/core/src/scope.rs
  31. 23 32
      packages/core/src/scopearena.rs
  32. 87 22
      packages/core/src/virtual_dom.rs
  33. 1 1
      packages/core/tests/borrowedstate.rs
  34. 8 8
      packages/core/tests/create_dom.rs
  35. 10 10
      packages/core/tests/lifecycle.rs
  36. 2 2
      packages/core/tests/sharedstate.rs
  37. 6 6
      packages/core/tests/vdom_rebuild.rs
  38. 1 1
      packages/desktop/examples/async.rs
  39. 4 6
      packages/desktop/src/lib.rs
  40. 6 0
      packages/hooks/src/lib.rs
  41. 17 5
      packages/hooks/src/use_shared_state.rs
  42. 17 3
      packages/hooks/src/usecoroutine.rs
  43. 136 0
      packages/hooks/src/usemodel.rs
  44. 5 5
      packages/hooks/src/useref.rs
  45. 34 7
      packages/hooks/src/usestate.rs
  46. 227 0
      packages/hooks/src/usestate2.rs
  47. 5 1
      packages/html/src/events.rs
  48. 19 0
      packages/liveview/Cargo.toml
  49. 49 0
      packages/liveview/README.md
  50. 96 0
      packages/liveview/examples/axum.rs
  51. 1 0
      packages/liveview/examples/axum_assets/index.html
  52. 9 0
      packages/liveview/examples/axum_assets/script.js
  53. 0 0
      packages/liveview/src/lib.rs
  54. 0 0
      packages/liveview/src/supported/actix_handler.rs
  55. 0 0
      packages/liveview/src/supported/axum_handler.rs
  56. 0 0
      packages/liveview/src/supported/tide_handler.rs
  57. 8 5
      packages/mobile/src/lib.rs
  58. 1 1
      packages/router/examples/simple.rs
  59. 5 5
      packages/ssr/src/lib.rs
  60. 1 1
      packages/web/examples/async.rs
  61. 1 1
      packages/web/examples/js_bench.rs
  62. 1 1
      packages/web/examples/simple.rs
  63. 1 1
      packages/web/examples/suspense.rs
  64. 298 311
      packages/web/src/cache.rs
  65. 10 0
      packages/web/src/cfg.rs
  66. 21 18
      packages/web/src/lib.rs

+ 2 - 0
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 = []

+ 1 - 1
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);
 

+ 1 - 1
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(""));

+ 1 - 1
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);
 

+ 1 - 1
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<Client>);
     let mut scene = use_state(cx, || Scene::ClientsList);
 

+ 1 - 1
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();
 

+ 1 - 1
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);
 

+ 1 - 1
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! {

+ 1 - 1
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"
     })

+ 1 - 1
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");

+ 1 - 1
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();

+ 1 - 1
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! {

+ 1 - 1
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! {

+ 1 - 1
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");

+ 1 - 1
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" }

+ 6 - 6
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"

+ 1 - 1
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 {

+ 1 - 1
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::<u32, Rc<TodoItem>>::new());
     let mut filter = use_state(cx, || FilterState::All);

+ 2 - 2
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<WeatherProps> = |cx, props| {
+static WeatherDisplay: Component<WeatherProps> = |cx, props| {
     //
     cx.render(rsx!(
         div { class: "flex items-center justify-center flex-col"

+ 1 - 1
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);

+ 1 - 1
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! {

+ 1 - 1
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);

+ 34 - 0
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!"
+            }
+        }
+    ))
+}

+ 1 - 1
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);

+ 4 - 2
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 {

+ 8 - 8
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<DiffInstruction<'bump>>,
-    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> {

+ 8 - 17
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<VPortal>;
-    pub type FC<P> = for<'a> fn(Context<'a>, &'a P) -> Element;
+    pub type Component<P> = 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;
 }

+ 0 - 2
packages/core/src/mutations.rs

@@ -16,7 +16,6 @@ pub struct Mutations<'a> {
     pub edits: Vec<DomEdit<'a>>,
     pub dirty_scopes: FxHashSet<ScopeId>,
     pub refs: Vec<NodeRefMutation<'a>>,
-    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(),
         }
     }
 

+ 28 - 9
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<Option<BumpBox<'bump, dyn FnMut(std::sync::Arc<dyn Any + Send + Sync>) + 'bump>>>,
+    pub(crate) callback: EventHandler<'bump>,
+}
+
+pub struct EventHandler<'bump> {
+    pub callback: &'bump RefCell<Option<ListenerCallback<'bump>>>,
+}
+
+impl EventHandler<'_> {
+    pub fn call(&self, event: Arc<dyn Any + Send + Sync>) {
+        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<dyn Any + Send + Sync>) + '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<dyn Any + Send + Sync>) + '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,
         }
     }
 

+ 50 - 50
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<BumpBox<'a, dyn Future<Output = ()>>>,
+    pub(crate) tasks: Vec<Pin<BumpBox<'a, dyn Future<Output = ()>>>>,
 }
 
-/// 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::<T>()) {
                     return Some(shared.clone().downcast::<T>().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<Output = ()>>(
-        &'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<Output = ()>,
         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<Output = ()> = 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<Output = ()> = self.bump().alloc(fut());
         let boxed_fut: BumpBox<dyn Future<Output = ()>> = unsafe { BumpBox::from_raw(fut) };
+        let pinned_fut: Pin<BumpBox<_>> = 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<LazyNodes<'src, '_>>) -> Option<VPortal> {
-        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<Vec<*const VNode<'static>>>,
 }
 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();

+ 23 - 32
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();
-
-        let mut items = scope.items.borrow_mut();
+        if let Some(scope) = self.get_scope(scope_id) {
+            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());
                             }

+ 87 - 22
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<P: 'static>(root: FC<P>, root_props: P) -> Self {
+    pub fn new_with_props<P: 'static>(root: Component<P>, root_props: P) -> Self {
         let (sender, receiver) = futures_channel::mpsc::unbounded::<SchedulerMsg>();
         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<P: 'static>(
-        root: FC<P>,
+        root: Component<P>,
         root_props: P,
         sender: UnboundedSender<SchedulerMsg>,
         receiver: UnboundedReceiver<SchedulerMsg>,
@@ -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<SchedulerMsg> {
         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<LazyNodes<'a, '_>>) -> &'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<LazyNodes<'a, '_>>) -> 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<LazyNodes<'a, '_>>,
@@ -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);

+ 1 - 1
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! {

+ 8 - 8
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<P: 'static + Send>(app: FC<P>, props: P) -> VirtualDom {
+fn new_dom<P: 'static + Send>(app: Component<P>, 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<P: 'static + Send>(app: FC<P>, 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" } })}

+ 10 - 10
packages/core/tests/lifecycle.rs

@@ -22,7 +22,7 @@ fn manual_diffing() {
         value: Shared<&'static str>,
     }
 
-    static App: FC<AppProps> = |cx, props| {
+    static App: Component<AppProps> = |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 {

+ 2 - 2
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::<MySharedState>()?;
         cx.render(rsx!("Hello, {shared.0}"))
     };

+ 6 - 6
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"}

+ 1 - 1
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);
 

+ 4 - 6
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<P: Properties + 'static + Send + Sync>(
-    root: FC<P>,
+    root: Component<P>,
     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<T: 'static + Send + Sync>(
-    root: FC<T>,
+    root: Component<T>,
     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<P: Send + 'static>(root: FC<P>, props: P) -> Self {
+    pub fn new_on_tokio<P: Send + 'static>(root: Component<P>, 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()

+ 6 - 0
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::*;

+ 17 - 5
packages/hooks/src/use_shared_state.rs

@@ -8,7 +8,7 @@ use std::{
 type ProvidedState<T> = RefCell<ProvidedStateInner<T>>;
 
 // Tracks all the subscribers to a shared State
-pub(crate) struct ProvidedStateInner<T> {
+pub struct ProvidedStateInner<T> {
     value: Rc<RefCell<T>>,
     notify_any: Rc<dyn Fn(ScopeId)>,
     consumers: HashSet<ScopeId>,
@@ -20,6 +20,14 @@ impl<T> ProvidedStateInner<T> {
             (self.notify_any)(*consumer);
         }
     }
+
+    pub fn write(&self) -> RefMut<T> {
+        self.value.borrow_mut()
+    }
+
+    pub fn read(&self) -> Ref<T> {
+        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<RefCell<ProvidedStateInner<T>>> {
+        self.root.clone()
+    }
 }
 
 impl<T> Copy for UseSharedState<'_, T> {}

+ 17 - 3
packages/hooks/src/usecoroutine.rs

@@ -6,9 +6,9 @@ use std::{
     rc::Rc,
 };
 
-pub fn use_coroutine<'a, F: Future<Output = ()> + 'a>(
+pub fn use_coroutine<'a, F: Future<Output = ()> + '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) {}
 }

+ 136 - 0
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<T: 'static>(cx: Context, f: impl FnOnce() -> T) -> UseModel<T> {
+    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<T>,
+}
+
+struct UseModelInner<T> {
+    update_scheduled: Cell<bool>,
+    update_callback: Rc<dyn Fn()>,
+    value: RefCell<T>,
+    // tasks: RefCell<Vec<ModelTask>>,
+}
+
+type ModelTask = Pin<Box<dyn Future<Output = ()> + '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<T, F: Future<Output = ()> + 'static>(
+    cx: Context,
+    model: UseModel<T>,
+    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<Option<ModelTask>>,
+}
+
+impl UseModelCoroutine {
+    pub fn start(&self) {}
+}
+
+pub struct ModelAsync<T> {
+    _p: PhantomData<T>,
+}
+impl<T> ModelAsync<T> {
+    pub fn write(&self) -> RefMut<'_, T> {
+        todo!()
+    }
+    pub fn read(&self) -> Ref<'_, T> {
+        todo!()
+    }
+}
+
+pub struct AppModels {}
+
+impl AppModels {
+    pub fn get<T: 'static>(&self) -> ModelAsync<T> {
+        unimplemented!()
+    }
+}
+
+impl<T> Copy for UseModel<'_, T> {}
+impl<'a, T> Clone for UseModel<'a, T> {
+    fn clone(&self) -> Self {
+        Self { inner: self.inner }
+    }
+}

+ 5 - 5
packages/hooks/src/useref.rs

@@ -8,12 +8,12 @@ use dioxus_core::Context;
 pub fn use_ref<T: 'static>(cx: Context, f: impl FnOnce() -> T) -> UseRef<T> {
     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<T>,
 }
 struct UseRefInner<T> {
-    update_scheuled: Cell<bool>,
+    update_scheduled: Cell<bool>,
     update_callback: Rc<dyn Fn()>,
     value: RefCell<T>,
 }
@@ -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)();
         }
     }

+ 34 - 7
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<T: 'static> {
-    current_val: T,
+    current_val: Rc<T>,
     update_scheuled: Cell<bool>,
     update_callback: Rc<dyn Fn()>,
     wip: Rc<RefCell<Option<T>>>,
@@ -117,6 +123,10 @@ impl<'a, T: 'static> UseState<'a, T> {
         &self.inner.current_val
     }
 
+    pub fn get_rc(&self) -> &'a Rc<T> {
+        &self.inner.current_val
+    }
+
     /// Get the current status of the work-in-progress data
     pub fn get_wip(&self) -> Ref<Option<T>> {
         self.inner.wip.borrow()
@@ -134,14 +144,15 @@ impl<'a, T: 'static> UseState<'a, T> {
     pub fn setter(&self) -> Rc<dyn Fn(T)> {
         let slot = self.inner.wip.clone();
         Rc::new(move |new| {
-            //
             *slot.borrow_mut() = Some(new);
         })
     }
 
     pub fn for_async(&self) -> AsyncUseState<T> {
         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<Owned = T>> 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<T: 'static> {
+    inner: Rc<T>,
+    re_render: Rc<dyn Fn()>,
     wip: Rc<RefCell<Option<T>>>,
 }
 
@@ -269,7 +286,17 @@ impl<T: ToOwned> AsyncUseState<T> {
             slot.as_mut().unwrap()
         })
     }
+}
+impl<T> AsyncUseState<T> {
     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<T> {
+        &self.inner
+    }
 }

+ 227 - 0
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! {
+// ///         <div>
+// ///             <h1>"Counter: {counter}" </h1>
+// ///             <button onclick={increment}> "Increment" </button>
+// ///             <button onclick={decrement}> "Decrement" </button>
+// ///         </div>
+// ///     }
+// /// }
+// /// ```
+// pub fn use_state2<'a, T: 'static>(
+//     cx: Context<'a>,
+//     initial_state_fn: impl FnOnce() -> T,
+// ) -> &'a UseState2<T> {
+//     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<T>| {
+//             {
+//                 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<T: 'static>(Rc<UseStateInner2<T>>);
+
+// impl<T> ToOwned for UseState2<T> {
+//     type Owned = UseState2<T>;
+//     fn to_owned(&self) -> Self::Owned {
+//         UseState2(self.0.clone())
+//     }
+// }
+
+// pub struct UseStateInner2<T: 'static> {
+//     current_val: T,
+//     update_scheuled: Cell<bool>,
+//     update_callback: Rc<dyn Fn()>,
+//     wip: Option<T>,
+// }
+
+// impl<T: Debug> Debug for UseStateInner2<T> {
+//     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+//         write!(f, "{:?}", self.current_val)
+//     }
+// }
+
+// impl<T> UseState2<T> {
+//     /// 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<T> {
+//         self.needs_update();
+//         ip.wip.replace(new_val)
+//     }
+
+//     pub fn get(&self) -> &T {
+//         &self.current_val
+//     }
+// }
+
+// // impl<T: 'static + ToOwned<Owned = T>> UseState2<T> {
+// //     /// 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<T> {
+// //         // 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<T> std::ops::Deref for UseStateInner2<T> {
+//     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<T: Copy + Add<T, Output = T>> Add<T> for UseStateInner2<T> {
+//     type Output = T;
+
+//     fn add(self, rhs: T) -> Self::Output {
+//         self.current_val.add(rhs)
+//     }
+// }
+// impl<T: Copy + Add<T, Output = T>> AddAssign<T> for UseStateInner2<T> {
+//     fn add_assign(&mut self, rhs: T) {
+//         self.set(self.current_val.add(rhs));
+//     }
+// }
+// impl<T: Copy + Sub<T, Output = T>> Sub<T> for UseStateInner2<T> {
+//     type Output = T;
+
+//     fn sub(self, rhs: T) -> Self::Output {
+//         self.current_val.sub(rhs)
+//     }
+// }
+// impl<T: Copy + Sub<T, Output = T>> SubAssign<T> for UseStateInner2<T> {
+//     fn sub_assign(&mut self, rhs: T) {
+//         self.set(self.current_val.sub(rhs));
+//     }
+// }
+
+// /// MUL
+// impl<T: Copy + Mul<T, Output = T>> Mul<T> for UseStateInner2<T> {
+//     type Output = T;
+
+//     fn mul(self, rhs: T) -> Self::Output {
+//         self.current_val.mul(rhs)
+//     }
+// }
+// impl<T: Copy + Mul<T, Output = T>> MulAssign<T> for UseStateInner2<T> {
+//     fn mul_assign(&mut self, rhs: T) {
+//         self.set(self.current_val.mul(rhs));
+//     }
+// }
+// /// DIV
+// impl<T: Copy + Div<T, Output = T>> Div<T> for UseStateInner2<T> {
+//     type Output = T;
+
+//     fn div(self, rhs: T) -> Self::Output {
+//         self.current_val.div(rhs)
+//     }
+// }
+// impl<T: Copy + Div<T, Output = T>> DivAssign<T> for UseStateInner2<T> {
+//     fn div_assign(&mut self, rhs: T) {
+//         self.set(self.current_val.div(rhs));
+//     }
+// }
+// impl<V, T: PartialEq<V>> PartialEq<V> for UseStateInner2<T> {
+//     fn eq(&self, other: &V) -> bool {
+//         self.get() == other
+//     }
+// }
+// impl<O, T: Not<Output = O> + Copy> Not for UseStateInner2<T> {
+//     type Output = O;
+
+//     fn not(self) -> Self::Output {
+//         !*self.get()
+//     }
+// }
+
+// // enable displaty for the handle
+// impl<T: 'static + Display> std::fmt::Display for UseStateInner2<T> {
+//     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+//         write!(f, "{}", self.current_val)
+//     }
+// }

+ 5 - 1
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)
                     }
                 )*
             )*

+ 19 - 0
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"

+ 49 - 0
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" }
+    ))
+}
+```

+ 96 - 0
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<TypedHeader<headers::UserAgent>>,
+) -> 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;
+    }
+}

+ 1 - 0
packages/liveview/examples/axum_assets/index.html

@@ -0,0 +1 @@
+<script src='script.js'></script>

+ 9 - 0
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);
+});

+ 0 - 0
packages/liveview/src/lib.rs


+ 0 - 0
packages/liveview/src/supported/actix_handler.rs


+ 0 - 0
packages/liveview/src/supported/axum_handler.rs


+ 0 - 0
packages/liveview/src/supported/tide_handler.rs


+ 8 - 5
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<P: 'static + Send>(
-    root: FC<P>,
+    root: Component<P>,
     props: P,
     builder: fn(WindowBuilder) -> WindowBuilder,
 ) -> anyhow::Result<()> {
@@ -41,7 +44,7 @@ pub fn launch_with_props<P: 'static + Send>(
 /// 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>,
+    root: Component<T>,
 }
 enum RpcEvent<'a> {
     Initialize {
@@ -52,7 +55,7 @@ enum RpcEvent<'a> {
 
 impl<T: 'static + Send> WebviewRenderer<T> {
     pub fn run(
-        root: FC<T>,
+        root: Component<T>,
         props: T,
         user_builder: fn(WindowBuilder) -> WindowBuilder,
     ) -> anyhow::Result<()> {
@@ -60,7 +63,7 @@ impl<T: 'static + Send> WebviewRenderer<T> {
     }
 
     pub fn run_with_edits(
-        root: FC<T>,
+        root: Component<T>,
         props: T,
         user_builder: fn(WindowBuilder) -> WindowBuilder,
         redits: Option<Vec<DomEdit<'static>>>,

+ 1 - 1
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,

+ 5 - 5
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"  }
             })

+ 1 - 1
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 {

+ 1 - 1
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);
 

+ 1 - 1
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 {

+ 1 - 1
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 {

+ 298 - 311
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",
+];

+ 10 - 0
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<String>,
 }
 
 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<String>) -> Self {
+        self.cached_strings = cache;
+        self
+    }
 }

+ 21 - 18
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<RootProps> = |cx, props| {
+/// static App: Component<RootProps> = |cx, props| {
 ///     rsx!(cx, div {"hello {props.name}"})
 /// }
 /// ```
-pub fn launch_with_props<T, F>(root_component: FC<T>, root_properties: T, configuration_builder: F)
-where
+pub fn launch_with_props<T, F>(
+    root_component: Component<T>,
+    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<T: 'static + Send>(root: FC<T>, root_props: T, cfg: WebConfig) {
+pub async fn run_with_props<T: 'static + Send>(root: Component<T>, 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<T: 'static + Send>(root: FC<T>, 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<T: 'static + Send>(root: FC<T>, 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);
         }
     }