瀏覽代碼

Merge pull request #31 from DioxusLabs/jk/stack_dst

stack dst, children as props arg, rename component to scope, rename domtree to element, add router package
Jonathan Kelley 3 年之前
父節點
當前提交
af891ac532
共有 100 個文件被更改,包括 2840 次插入3371 次删除
  1. 7 7
      README.md
  2. 1 1
      examples/async.rs
  3. 1 1
      examples/borrowed.rs
  4. 1 1
      examples/core/async.rs
  5. 1 1
      examples/core/contextapi.rs
  6. 3 3
      examples/core/syntax.rs
  7. 1 1
      examples/core/vdom_usage.rs
  8. 8 8
      examples/core_reference/antipatterns.rs
  9. 2 2
      examples/core_reference/basics.rs
  10. 2 2
      examples/core_reference/children.rs
  11. 4 4
      examples/core_reference/conditional_rendering.rs
  12. 3 3
      examples/core_reference/controlled_inputs.rs
  13. 1 1
      examples/core_reference/custom_elements.rs
  14. 1 1
      examples/core_reference/empty.rs
  15. 6 6
      examples/core_reference/errorhandling.rs
  16. 4 4
      examples/core_reference/fragments.rs
  17. 1 1
      examples/core_reference/global_css.rs
  18. 2 2
      examples/core_reference/inline_styles.rs
  19. 1 1
      examples/core_reference/iterators.rs
  20. 4 4
      examples/core_reference/listener.rs
  21. 3 3
      examples/core_reference/memo.rs
  22. 1 1
      examples/core_reference/noderefs.rs
  23. 1 1
      examples/core_reference/signals.rs
  24. 1 1
      examples/core_reference/spreadpattern.rs
  25. 1 1
      examples/core_reference/statemanagement.rs
  26. 1 1
      examples/core_reference/suspense.rs
  27. 1 1
      examples/core_reference/task.rs
  28. 1 1
      examples/core_reference/testing.rs
  29. 2 2
      examples/core_reference/tostring.rs
  30. 3 3
      examples/coroutine.rs
  31. 13 11
      examples/crm.rs
  32. 1 1
      examples/desktop/demo.rs
  33. 1 1
      examples/file_explorer.rs
  34. 1 1
      examples/hydration.rs
  35. 1 1
      examples/pattern_model.rs
  36. 1 1
      examples/readme.rs
  37. 1 1
      examples/router.rs
  38. 10 6
      examples/rsx_usage.rs
  39. 2 2
      examples/ssr.rs
  40. 1 1
      examples/ssr/basic.rs
  41. 1 1
      examples/ssr/tide.rs
  42. 6 6
      examples/ssr/tofile.rs
  43. 6 6
      examples/tailwind.rs
  44. 23 0
      examples/tasks.rs
  45. 2 2
      examples/todomvc.rs
  46. 8 6
      examples/weather_app.rs
  47. 1 1
      examples/web/blah.rs
  48. 2 2
      examples/web/btns.rs
  49. 6 6
      examples/web/demo.rs
  50. 1 1
      examples/web_tick.rs
  51. 1 1
      examples/webview_web.rs
  52. 1 1
      packages/core-macro/src/rsx/component.rs
  53. 2 2
      packages/core-macro/src/rsx/element.rs
  54. 2 3
      packages/core/Cargo.toml
  55. 16 0
      packages/core/README.md
  56. 102 0
      packages/core/architecture.md
  57. 2 2
      packages/core/benches/jsframework.rs
  58. 9 0
      packages/core/examples/props_expand.rs
  59. 44 0
      packages/core/examples/works.rs
  60. 0 124
      packages/core/src/bumpframe.rs
  61. 0 93
      packages/core/src/childiter.rs
  62. 59 67
      packages/core/src/component.rs
  63. 0 271
      packages/core/src/context.rs
  64. 0 128
      packages/core/src/debug_dom.rs
  65. 319 171
      packages/core/src/diff.rs
  66. 0 117
      packages/core/src/diff_stack.rs
  67. 0 50
      packages/core/src/heuristics.rs
  68. 0 66
      packages/core/src/hooklist.rs
  69. 0 208
      packages/core/src/hooks.rs
  70. 159 78
      packages/core/src/lazynodes.rs
  71. 18 48
      packages/core/src/lib.rs
  72. 16 5
      packages/core/src/mutations.rs
  73. 0 119
      packages/core/src/noderef.rs
  74. 257 141
      packages/core/src/nodes.rs
  75. 0 77
      packages/core/src/resources.rs
  76. 0 609
      packages/core/src/scheduler.rs
  77. 449 209
      packages/core/src/scope.rs
  78. 361 0
      packages/core/src/scopearena.rs
  79. 0 39
      packages/core/src/tasks.rs
  80. 0 82
      packages/core/src/test_dom.rs
  81. 0 73
      packages/core/src/threadsafe.rs
  82. 0 91
      packages/core/src/util.rs
  83. 494 197
      packages/core/src/virtual_dom.rs
  84. 4 4
      packages/core/tests/borrowedstate.rs
  85. 14 31
      packages/core/tests/create_dom.rs
  86. 28 47
      packages/core/tests/diffing.rs
  87. 0 40
      packages/core/tests/display_vdom.rs
  88. 274 2
      packages/core/tests/lifecycle.rs
  89. 3 3
      packages/core/tests/sharedstate.rs
  90. 1 0
      packages/core/tests/task.rs
  91. 15 14
      packages/core/tests/vdom_rebuild.rs
  92. 1 1
      packages/desktop/Cargo.toml
  93. 3 7
      packages/desktop/src/desktop_context.rs
  94. 1 0
      packages/desktop/src/err.rs
  95. 6 6
      packages/desktop/src/events.rs
  96. 3 5
      packages/desktop/src/lib.rs
  97. 11 8
      packages/hooks/src/use_shared_state.rs
  98. 0 1
      packages/hooks/src/useref.rs
  99. 0 1
      packages/hooks/src/usestate.rs
  100. 8 3
      packages/html/Cargo.toml

+ 7 - 7
README.md

@@ -49,7 +49,7 @@
 Dioxus is a portable, performant, and ergonomic framework for building cross-platform user experiences in Rust.
 
 ```rust
-fn App((cx, props): Scope<()>) -> Element {
+fn App(cx: Context, props: &()) -> Element {
     let mut count = use_state(cx, || 0);
 
     cx.render(rsx!(
@@ -141,16 +141,16 @@ Dioxus is heavily inspired by React, but we want your transition to feel like an
 | NodeRef                   | ✅      | ✅     | gain direct access to nodes                                          |
 | Re-hydration              | ✅      | ✅     | Pre-render to HTML to speed up first contentful paint                |
 | Jank-Free Rendering       | ✅      | ✅     | Large diffs are segmented across frames for silky-smooth transitions |
-| Cooperative Scheduling    | ✅      | ✅     | Prioritize important events over non-important events                |
+| Effects                   | ✅      | ✅     | Run effects after a component has been committed to render           |
+| Cooperative Scheduling    | 🛠      | ✅     | Prioritize important events over non-important events                |
+| Server Components         | 🛠      | ✅     | Hybrid components for SPA and Server                                 |
+| Bundle Splitting          | 👀      | ✅     | Efficiently and asynchronously load the app                          |
+| Lazy Components           | 👀      | ✅     | Dynamically load the new components as the page is loaded            |
+| 1st class global state    | ✅      | ✅     | redux/recoil/mobx on top of context                                  |
 | Runs natively             | ✅      | ❓     | runs as a portable binary w/o a runtime (Node)                       |
-| 1st class global state    | ✅      | ❓     | redux/recoil/mobx on top of context                                  |
 | Subtree Memoization       | ✅      | ❓     | skip diffing static element subtrees                                 |
 | Compile-time correct      | ✅      | ❓     | Throw errors on invalid template layouts                             |
 | Heuristic Engine          | ✅      | ❓     | track component memory usage to minimize future allocations          |
-| Effects                   | 🛠      | ✅     | Run effects after a component has been committed to render           |
-| Server Components         | 🛠      | ✅     | Hybrid components for SPA and Server                                 |
-| Bundle Splitting          | 👀      | ✅     | Hybrid components for SPA and Server                                 |
-| Lazy Components           | 👀      | ✅     | Dynamically load the new components as the page is loaded            |
 | Fine-grained reactivity   | 👀      | ❓     | Skip diffing for fine-grain updates                                  |
 
 - ✅ = implemented and working

+ 1 - 1
examples/async.rs

@@ -17,7 +17,7 @@ pub static App: FC<()> = |(cx, _)| {
 
     let (async_count, dir) = (count.for_async(), *direction);
 
-    let (task, _) = use_task(cx, move || async move {
+    let (task, _) = use_coroutine(cx, move || async move {
         loop {
             TimeoutFuture::new(250).await;
             *async_count.get_mut() += dir;

+ 1 - 1
examples/borrowed.rs

@@ -21,7 +21,7 @@ fn main() {
 }
 
 fn App((cx, props): Scope<()>) -> Element {
-    let text: &mut Vec<String> = cx.use_hook(|_| vec![String::from("abc=def")], |f| f, |_| {});
+    let text: &mut Vec<String> = cx.use_hook(|_| vec![String::from("abc=def")], |f| f);
 
     let first = text.get_mut(0).unwrap();
 

+ 1 - 1
examples/core/async.rs

@@ -5,7 +5,7 @@ fn main() {
     dom.rebuild();
 }
 
-const App: FC<()> = |(cx, props)| {
+const App: FC<()> = |cx, props| {
     let id = cx.scope_id();
     // cx.submit_task(Box::pin(async move { id }));
 

+ 1 - 1
examples/core/contextapi.rs

@@ -6,7 +6,7 @@ struct SomeContext {
 }
 
 #[allow(unused)]
-static Example: FC<()> = |(cx, props)| {
+static Example: FC<()> = |cx, props| {
     todo!()
 
     // let value = cx.use_context(|c: &SomeContext| c.items.last().unwrap());

+ 3 - 3
examples/core/syntax.rs

@@ -33,7 +33,7 @@ fn html_usage() {
 
 static App2: FC<()> = |(cx, _)| cx.render(rsx!("hello world!"));
 
-static App: FC<()> = |(cx, props)| {
+static App: FC<()> = |cx, props| {
     let name = cx.use_state(|| 0);
 
     cx.render(rsx!(div {
@@ -99,7 +99,7 @@ impl<'a> Children<'a> {
     }
 }
 
-static Bapp: FC<()> = |(cx, props)| {
+static Bapp: FC<()> = |cx, props| {
     let name = cx.use_state(|| 0);
 
     cx.render(rsx!(
@@ -114,7 +114,7 @@ static Bapp: FC<()> = |(cx, props)| {
     ))
 };
 
-static Match: FC<()> = |(cx, props)| {
+static Match: FC<()> = |cx, props| {
     //
     let b: Box<dyn Fn(NodeFactory) -> VNode> = Box::new(|f| todo!());
 

+ 1 - 1
examples/core/vdom_usage.rs

@@ -5,7 +5,7 @@ use dioxus_core::{lazynodes::LazyNodes, prelude::*};
 // #[async_std::main]
 fn main() {
     static App: FC<()> =
-        |(cx, props)| cx.render(Some(LazyNodes::new(move |f| f.text(format_args!("hello")))));
+        |cx, props| cx.render(Some(LazyNodes::new(move |f| f.text(format_args!("hello")))));
 
     let mut dom = VirtualDom::new(App);
 

+ 8 - 8
examples/core_reference/antipatterns.rs

@@ -32,7 +32,7 @@ use dioxus::prelude::*;
 struct NoKeysProps {
     data: std::collections::HashMap<u32, String>,
 }
-static AntipatternNoKeys: FC<NoKeysProps> = |(cx, props)| {
+static AntipatternNoKeys: FC<NoKeysProps> = |cx, props| {
     // WRONG: Make sure to add keys!
     rsx!(cx, ul {
         {props.data.iter().map(|(k, v)| rsx!(li { "List item: {v}" }))}
@@ -54,7 +54,7 @@ static AntipatternNoKeys: FC<NoKeysProps> = |(cx, props)| {
 ///
 /// Only Component and Fragment nodes are susceptible to this issue. Dioxus mitigates this with components by providing
 /// an API for registering shared state without the ContextProvider pattern.
-static AntipatternNestedFragments: FC<()> = |(cx, props)| {
+static AntipatternNestedFragments: FC<()> = |cx, props| {
     // Try to avoid heavily nesting fragments
     rsx!(cx,
         Fragment {
@@ -82,7 +82,7 @@ static AntipatternNestedFragments: FC<()> = |(cx, props)| {
 /// However, calling set_state will *not* update the current version of state in the component. This should be easy to
 /// recognize from the function signature, but Dioxus will not update the "live" version of state. Calling `set_state`
 /// merely places a new value in the queue and schedules the component for a future update.
-static AntipatternRelyingOnSetState: FC<()> = |(cx, props)| {
+static AntipatternRelyingOnSetState: FC<()> = |cx, props| {
     let (state, set_state) = use_state(cx, || "Hello world").classic();
     set_state("New state");
     // This will return false! `state` will *still* be "Hello world"
@@ -99,7 +99,7 @@ static AntipatternRelyingOnSetState: FC<()> = |(cx, props)| {
 /// - All components must start with an uppercase character
 ///
 /// i.e.: the following component will be rejected when attempted to be used in the rsx! macro
-static antipattern_component: FC<()> = |(cx, props)| todo!();
+static antipattern_component: FC<()> = |cx, props| todo!();
 
 /// Antipattern: Misusing hooks
 /// ---------------------------
@@ -120,7 +120,7 @@ static antipattern_component: FC<()> = |(cx, props)| todo!();
 struct MisuedHooksProps {
     should_render_state: bool,
 }
-static AntipatternMisusedHooks: FC<MisuedHooksProps> = |(cx, props)| {
+static AntipatternMisusedHooks: FC<MisuedHooksProps> = |cx, props| {
     if props.should_render_state {
         // do not place a hook in the conditional!
         // prefer to move it out of the conditional
@@ -153,7 +153,7 @@ static AntipatternMisusedHooks: FC<MisuedHooksProps> = |(cx, props)| {
 ///         }
 ///     }
 /// })
-static _example: FC<()> = |(cx, props)| todo!();
+static _example: FC<()> = |cx, props| todo!();
 
 /// Antipattern: publishing components and hooks with all features enabled
 /// ----------------------------------------------------------------------
@@ -171,9 +171,9 @@ static _example: FC<()> = |(cx, props)| todo!();
 ///
 /// This will only include the `core` dioxus crate which is relatively slim and fast to compile and avoids target-specific
 /// libraries.
-static __example: FC<()> = |(cx, props)| todo!();
+static __example: FC<()> = |cx, props| todo!();
 
-pub static Example: FC<()> = |(cx, props)| {
+pub static Example: FC<()> = |cx, props| {
     cx.render(rsx! {
         AntipatternNoKeys { data: std::collections::HashMap::new() }
         AntipatternNestedFragments {}

+ 2 - 2
examples/core_reference/basics.rs

@@ -9,7 +9,7 @@
 
 use dioxus::prelude::*;
 
-pub static Example: FC<()> = |(cx, props)| {
+pub static Example: FC<()> = |cx, props| {
     cx.render(rsx! {
         div {
             Greeting {
@@ -25,7 +25,7 @@ struct GreetingProps {
     name: &'static str,
 }
 
-static Greeting: FC<GreetingProps> = |(cx, props)| {
+static Greeting: FC<GreetingProps> = |cx, props| {
     cx.render(rsx! {
         div {
             h1 { "Hello, {props.name}!" }

+ 2 - 2
examples/core_reference/children.rs

@@ -18,7 +18,7 @@
 
 use dioxus::prelude::*;
 
-pub static Example: FC<()> = |(cx, props)| {
+pub static Example: FC<()> = |cx, props| {
     cx.render(rsx! {
         div {
             Banner {
@@ -31,7 +31,7 @@ pub static Example: FC<()> = |(cx, props)| {
     })
 };
 
-pub static Banner: FC<()> = |(cx, props)| {
+pub static Banner: FC<()> = |cx, props| {
     cx.render(rsx! {
         div {
             h1 { "This is a great banner!" }

+ 4 - 4
examples/core_reference/conditional_rendering.rs

@@ -16,7 +16,7 @@ use dioxus::prelude::*;
 pub struct MyProps {
     should_show: bool,
 }
-pub static Example0: FC<MyProps> = |(cx, props)| {
+pub static Example0: FC<MyProps> = |cx, props| {
     cx.render(rsx! {
         div {
             {props.should_show.then(|| rsx!{
@@ -39,7 +39,7 @@ pub static Example0: FC<MyProps> = |(cx, props)| {
 pub struct MyProps1 {
     should_show: bool,
 }
-pub static Example1: FC<MyProps1> = |(cx, props)| {
+pub static Example1: FC<MyProps1> = |cx, props| {
     cx.render(rsx! {
         div {
             // With matching
@@ -77,7 +77,7 @@ pub enum Color {
 pub struct MyProps2 {
     color: Color,
 }
-pub static Example2: FC<MyProps2> = |(cx, props)| {
+pub static Example2: FC<MyProps2> = |cx, props| {
     cx.render(rsx! {
         div {
             {match props.color {
@@ -89,7 +89,7 @@ pub static Example2: FC<MyProps2> = |(cx, props)| {
     })
 };
 
-pub static Example: FC<()> = |(cx, props)| {
+pub static Example: FC<()> = |cx, props| {
     let should_show = use_state(cx, || false);
     let mut color_index = use_state(cx, || 0);
     let color = match *color_index % 2 {

+ 3 - 3
examples/core_reference/controlled_inputs.rs

@@ -1,7 +1,7 @@
 use dioxus::prelude::*;
 fn main() {}
 
-pub static Example: FC<()> = |(cx, props)| {
+pub static Example: FC<()> = |cx, props| {
     cx.render(rsx! {
         div {
 
@@ -10,7 +10,7 @@ pub static Example: FC<()> = |(cx, props)| {
 };
 
 // A controlled component:
-static ControlledSelect: FC<()> = |(cx, props)| {
+static ControlledSelect: FC<()> = |cx, props| {
     let value = use_state(cx, || String::from("Grapefruit"));
     cx.render(rsx! {
         select { value: "{value}", onchange: move |evt| value.set(evt.value()),
@@ -23,7 +23,7 @@ static ControlledSelect: FC<()> = |(cx, props)| {
 };
 
 // TODO - how do uncontrolled things work?
-static UncontrolledSelect: FC<()> = |(cx, props)| {
+static UncontrolledSelect: FC<()> = |cx, props| {
     let value = use_state(cx, || String::new());
 
     cx.render(rsx! {

+ 1 - 1
examples/core_reference/custom_elements.rs

@@ -11,7 +11,7 @@
 
 use dioxus::prelude::*;
 
-pub static Example: FC<()> = |(cx, props)| {
+pub static Example: FC<()> = |cx, props| {
     cx.render(rsx! {
         div {
             custom_element {

+ 1 - 1
examples/core_reference/empty.rs

@@ -5,4 +5,4 @@
 
 use dioxus::prelude::*;
 
-pub static Example: FC<()> = |(cx, props)| cx.render(rsx! { Fragment {} });
+pub static Example: FC<()> = |cx, props| cx.render(rsx! { Fragment {} });

+ 6 - 6
examples/core_reference/errorhandling.rs

@@ -23,14 +23,14 @@ fn main() {}
 /// This is one way to go about error handling (just toss things away with unwrap).
 /// However, if you get it wrong, the whole app will crash.
 /// This is pretty flimsy.
-static App: FC<()> = |(cx, props)| {
+static App: FC<()> = |cx, props| {
     let data = get_data().unwrap();
     cx.render(rsx!( div { "{data}" } ))
 };
 
 /// This is a pretty verbose way of error handling
 /// However, it's still pretty good since we don't panic, just fail to render anything
-static App1: FC<()> = |(cx, props)| {
+static App1: FC<()> = |cx, props| {
     let data = match get_data() {
         Some(data) => data,
         None => return None,
@@ -46,7 +46,7 @@ static App1: FC<()> = |(cx, props)| {
 /// a user is logged in.
 ///
 /// Dioxus will throw an error in the console if the None-path is ever taken.
-static App2: FC<()> = |(cx, props)| {
+static App2: FC<()> = |cx, props| {
     let data = get_data()?;
     cx.render(rsx!( div { "{data}" } ))
 };
@@ -54,14 +54,14 @@ static App2: FC<()> = |(cx, props)| {
 /// This is top-tier error handling since it displays a failure state.
 ///
 /// However, the error is lacking in context.
-static App3: FC<()> = |(cx, props)| match get_data() {
+static App3: FC<()> = |cx, props| match get_data() {
     Some(data) => cx.render(rsx!( div { "{data}" } )),
     None => cx.render(rsx!( div { "Failed to load data :(" } )),
 };
 
 /// For errors that return results, it's possible to short-circuit the match-based error handling with `.ok()` which converts
 /// a Result<T, V> into an Option<T> and lets you abort rendering by early-returning `None`
-static App4: FC<()> = |(cx, props)| {
+static App4: FC<()> = |cx, props| {
     let data = get_data_err().ok()?;
     cx.render(rsx!( div { "{data}" } ))
 };
@@ -69,7 +69,7 @@ static App4: FC<()> = |(cx, props)| {
 /// This is great error handling since it displays a failure state... with context!
 ///
 /// Hopefully you'll never need to display a screen like this. It's rather bad taste
-static App5: FC<()> = |(cx, props)| match get_data_err() {
+static App5: FC<()> = |cx, props| match get_data_err() {
     Ok(data) => cx.render(rsx!( div { "{data}" } )),
     Err(c) => cx.render(rsx!( div { "Failed to load data: {c}" } )),
 };

+ 4 - 4
examples/core_reference/fragments.rs

@@ -11,7 +11,7 @@
 use dioxus::prelude::*;
 
 // Returning multiple elements with rsx! or html!
-static App1: FC<()> = |(cx, props)| {
+static App1: FC<()> = |cx, props| {
     cx.render(rsx! {
         h1 { }
         h2 { }
@@ -20,7 +20,7 @@ static App1: FC<()> = |(cx, props)| {
 };
 
 // Using the Fragment component
-static App2: FC<()> = |(cx, props)| {
+static App2: FC<()> = |cx, props| {
     cx.render(rsx! {
         Fragment {
             div {}
@@ -31,7 +31,7 @@ static App2: FC<()> = |(cx, props)| {
 };
 
 // Using the `fragment` method on the NodeFactory
-static App3: FC<()> = |(cx, props)| {
+static App3: FC<()> = |cx, props| {
     cx.render(LazyNodes::new(move |fac| {
         fac.fragment_from_iter([
             fac.text(format_args!("A")),
@@ -42,7 +42,7 @@ static App3: FC<()> = |(cx, props)| {
     }))
 };
 
-pub static Example: FC<()> = |(cx, props)| {
+pub static Example: FC<()> = |cx, props| {
     cx.render(rsx! {
         App1 {}
         App2 {}

+ 1 - 1
examples/core_reference/global_css.rs

@@ -19,7 +19,7 @@ h1   {color: blue;}
 p    {color: red;}
 "#;
 
-pub static Example: FC<()> = |(cx, props)| {
+pub static Example: FC<()> = |cx, props| {
     cx.render(rsx! {
         head { style { "{STYLE}" } }
         body {

+ 2 - 2
examples/core_reference/inline_styles.rs

@@ -10,7 +10,7 @@
 
 use dioxus::prelude::*;
 
-pub static Example: FC<()> = |(cx, props)| {
+pub static Example: FC<()> = |cx, props| {
     cx.render(rsx! {
         head {
             style: { background_color: "powderblue" }
@@ -29,7 +29,7 @@ pub static Example: FC<()> = |(cx, props)| {
 // .... technically the rsx! macro is slightly broken at the moment and allows styles not wrapped in style {}
 // I haven't noticed any name collisions yet, and am tentatively leaving this behavior in..
 // Don't rely on it.
-static Example2: FC<()> = |(cx, props)| {
+static Example2: FC<()> = |cx, props| {
     cx.render(rsx! {
         div { color: "red"
             "hello world!"

+ 1 - 1
examples/core_reference/iterators.rs

@@ -12,7 +12,7 @@
 
 use dioxus::prelude::*;
 
-pub static Example: FC<()> = |(cx, props)| {
+pub static Example: FC<()> = |cx, props| {
     let example_data = use_state(cx, || 0);
 
     let v = (0..10).map(|f| {

+ 4 - 4
examples/core_reference/listener.rs

@@ -7,7 +7,7 @@
 
 use dioxus::prelude::*;
 
-pub static Example: FC<()> = |(cx, props)| {
+pub static Example: FC<()> = |cx, props| {
     cx.render(rsx! {
         ButtonList {}
         NonUpdatingEvents {}
@@ -16,7 +16,7 @@ pub static Example: FC<()> = |(cx, props)| {
 };
 
 /// We can use `set_name` in multiple closures; the closures automatically *copy* the reference to set_name.
-static ButtonList: FC<()> = |(cx, props)| {
+static ButtonList: FC<()> = |cx, props| {
     let name = use_state(cx, || "...?");
 
     let names = ["jack", "jill", "john", "jane"]
@@ -33,7 +33,7 @@ static ButtonList: FC<()> = |(cx, props)| {
 
 /// This shows how listeners may be without a visible change in the display.
 /// Check the console.
-static NonUpdatingEvents: FC<()> = |(cx, props)| {
+static NonUpdatingEvents: FC<()> = |cx, props| {
     rsx!(cx, div {
         button {
             onclick: move |_| log::info!("Did not cause any updates!")
@@ -42,7 +42,7 @@ static NonUpdatingEvents: FC<()> = |(cx, props)| {
     })
 };
 
-static DisablePropagation: FC<()> = |(cx, props)| {
+static DisablePropagation: FC<()> = |cx, props| {
     rsx!(cx,
         div {
             onclick: move |_| log::info!("event propagated to the div!")

+ 3 - 3
examples/core_reference/memo.rs

@@ -21,7 +21,7 @@ use dioxus::prelude::*;
 
 // By default, components with no props are always memoized.
 // A props of () is considered empty.
-pub static Example: FC<()> = |(cx, props)| {
+pub static Example: FC<()> = |cx, props| {
     cx.render(rsx! {
         div { "100% memoized!" }
     })
@@ -35,7 +35,7 @@ pub struct MyProps1 {
     name: String,
 }
 
-pub static Example1: FC<MyProps1> = |(cx, props)| {
+pub static Example1: FC<MyProps1> = |cx, props| {
     cx.render(rsx! {
         div { "100% memoized! {props.name}" }
     })
@@ -49,7 +49,7 @@ pub struct MyProps2 {
     name: std::rc::Rc<str>,
 }
 
-pub static Example2: FC<MyProps2> = |(cx, props)| {
+pub static Example2: FC<MyProps2> = |cx, props| {
     cx.render(rsx! {
         div { "100% memoized! {props.name}" }
     })

+ 1 - 1
examples/core_reference/noderefs.rs

@@ -1,7 +1,7 @@
 use dioxus::prelude::*;
 fn main() {}
 
-pub static Example: FC<()> = |(cx, props)| {
+pub static Example: FC<()> = |cx, props| {
     let p = 10;
 
     cx.render(rsx! {

+ 1 - 1
examples/core_reference/signals.rs

@@ -1,7 +1,7 @@
 use dioxus::prelude::*;
 fn main() {}
 
-pub static Example: FC<()> = |(cx, props)| {
+pub static Example: FC<()> = |cx, props| {
     cx.render(rsx! {
         div {
 

+ 1 - 1
examples/core_reference/spreadpattern.rs

@@ -9,7 +9,7 @@
 
 use dioxus::prelude::*;
 
-pub static Example: FC<()> = |(cx, props)| {
+pub static Example: FC<()> = |cx, props| {
     let props = MyProps {
         count: 0,
         live: true,

+ 1 - 1
examples/core_reference/statemanagement.rs

@@ -1,7 +1,7 @@
 use dioxus::prelude::*;
 fn main() {}
 
-pub static Example: FC<()> = |(cx, props)| {
+pub static Example: FC<()> = |cx, props| {
     cx.render(rsx! {
         div {
 

+ 1 - 1
examples/core_reference/suspense.rs

@@ -14,7 +14,7 @@ struct DogApi {
 }
 const ENDPOINT: &str = "https://dog.ceo/api/breeds/image/random";
 
-pub static Example: FC<()> = |(cx, props)| {
+pub static Example: FC<()> = |cx, props| {
     let doggo = use_suspense(
         cx,
         || surf::get(ENDPOINT).recv_json::<DogApi>(),

+ 1 - 1
examples/core_reference/task.rs

@@ -24,7 +24,7 @@
 
 use dioxus::prelude::*;
 
-pub static Example: FC<()> = |(cx, props)| {
+pub static Example: FC<()> = |cx, props| {
     let count = use_state(cx, || 0);
     let mut direction = use_state(cx, || 1);
 

+ 1 - 1
examples/core_reference/testing.rs

@@ -1,6 +1,6 @@
 use dioxus::prelude::*;
 
-pub static Example: FC<()> = |(cx, props)| {
+pub static Example: FC<()> = |cx, props| {
     cx.render(rsx! {
         div {
 

+ 2 - 2
examples/core_reference/tostring.rs

@@ -1,7 +1,7 @@
 use dioxus::prelude::*;
 use dioxus::ssr;
 
-pub static Example: FC<()> = |(cx, props)| {
+pub static Example: FC<()> = |cx, props| {
     let as_string = use_state(cx, || {
         // Currently, SSR is only supported for whole VirtualDOMs
         // This is an easy/low hanging fruit to improve upon
@@ -15,7 +15,7 @@ pub static Example: FC<()> = |(cx, props)| {
     })
 };
 
-static SomeApp: FC<()> = |(cx, props)| {
+static SomeApp: FC<()> = |cx, props| {
     cx.render(rsx! {
         div { style: {background_color: "blue"}
             h1 {"Some amazing app or component"}

+ 3 - 3
examples/coroutine.rs

@@ -27,18 +27,18 @@ fn main() {
 
 use dioxus::prelude::*;
 
-static App: FC<()> = |(cx, props)| {
+static App: FC<()> = |cx, props| {
     let p1 = use_state(cx, || 0);
     let p2 = use_state(cx, || 0);
 
     let (mut p1_async, mut p2_async) = (p1.for_async(), p2.for_async());
-    let (p1_handle, _) = use_task(cx, || async move {
+    let (p1_handle, _) = use_coroutine(cx, || async move {
         loop {
             *p1_async.get_mut() += 1;
             async_std::task::sleep(std::time::Duration::from_millis(75)).await;
         }
     });
-    let (p2_handle, _) = use_task(cx, || async move {
+    let (p2_handle, _) = use_coroutine(cx, || async move {
         loop {
             *p2_async.get_mut() += 1;
             async_std::task::sleep(std::time::Duration::from_millis(100)).await;

+ 13 - 11
examples/crm.rs

@@ -90,15 +90,17 @@ static App: FC<()> = |(cx, _)| {
         }
     };
 
-    rsx!(cx, body {
-        link {
-            rel: "stylesheet"
-            href: "https://unpkg.com/purecss@2.0.6/build/pure-min.css"
-            integrity: "sha384-Uu6IeWbM+gzNVXJcM9XV3SohHtmWE+3VGi496jvgX1jyvDTXfdK+rfZc8C1Aehk5"
-            crossorigin: "anonymous"
-        }
-        margin_left: "35%"
-        h1 {"Dioxus CRM Example"}
-        {scene}
-    })
+    cx.render(rsx!(
+        body {
+           link {
+               rel: "stylesheet"
+               href: "https://unpkg.com/purecss@2.0.6/build/pure-min.css"
+               integrity: "sha384-Uu6IeWbM+gzNVXJcM9XV3SohHtmWE+3VGi496jvgX1jyvDTXfdK+rfZc8C1Aehk5"
+               crossorigin: "anonymous"
+           }
+           margin_left: "35%"
+           h1 {"Dioxus CRM Example"}
+           {scene}
+       }
+    ))
 };

+ 1 - 1
examples/desktop/demo.rs

@@ -8,7 +8,7 @@ fn main() {
     dioxus_desktop::launch(App, |c| c);
 }
 
-static App: FC<()> = |(cx, props)| {
+static App: FC<()> = |cx, props| {
     cx.render(rsx!(
         div {
             "hello world!"

+ 1 - 1
examples/file_explorer.rs

@@ -18,7 +18,7 @@ fn main() {
     });
 }
 
-static App: FC<()> = |(cx, props)| {
+static App: FC<()> = |cx, props| {
     let file_manager = use_ref(cx, || Files::new());
     let files = file_manager.read();
 

+ 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: FC<()> = |cx, props| {
     let mut val = use_state(cx, || 0);
 
     cx.render(rsx! {

+ 1 - 1
examples/pattern_model.rs

@@ -31,7 +31,7 @@ fn main() {
     });
 }
 
-static App: FC<()> = |(cx, props)| {
+static App: FC<()> = |cx, props| {
     let state = use_ref(cx, || Calculator::new());
 
     let clear_display = state.read().display_value.eq("0");

+ 1 - 1
examples/readme.rs

@@ -7,7 +7,7 @@ fn main() {
     dioxus::desktop::launch(App, |c| c);
 }
 
-static App: FC<()> = |(cx, props)| {
+static App: FC<()> = |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: FC<()> = |cx, props| {
     let route = use_router(cx, Route::parse)?;
 
     cx.render(rsx! {

+ 10 - 6
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: FC<()> = |cx, props| {
     let formatting = "formatting!";
     let formatting_tuple = ("a", "b");
     let lazy_fmt = format_args!("lazily formatted text");
@@ -102,12 +102,9 @@ pub static Example: FC<()> = |(cx, props)| {
             }}
 
             // Matching
-            // Matching will throw a Rust error about "no two closures are the same type"
-            // To fix this, call "render" method or use the "in" syntax to produce VNodes.
-            // There's nothing we can do about it, sorry :/ (unless you want *really* unhygienic macros)
             {match true {
-                true => rsx!(cx, h1 {"Top text"}),
-                false => cx.render(rsx!( h1 {"Bottom text"}))
+                true => rsx!( h1 {"Top text"}),
+                false => rsx!( h1 {"Bottom text"})
             }}
 
             // Conditional rendering
@@ -174,10 +171,17 @@ pub static Example: FC<()> = |(cx, props)| {
 
             // Can take children too!
             Taller { a: "asd", div {"hello world!"} }
+
+            // helper functions
+            {helper(cx, "hello world!")}
         }
     })
 };
 
+fn helper(cx: Context, text: &str) -> Element {
+    rsx!(cx, p { "{text}" })
+}
+
 mod baller {
     use super::*;
     #[derive(Props, PartialEq)]

+ 2 - 2
examples/ssr.rs

@@ -9,7 +9,7 @@ fn main() {
     println!("{}", ssr::render_vdom(&vdom, |c| c));
 }
 
-static App: FC<()> = |(cx, props)| {
+static App: FC<()> = |cx, props| {
     cx.render(rsx!(
         div {
             h1 { "Title" }
@@ -21,6 +21,6 @@ static App: FC<()> = |(cx, props)| {
 struct MyProps<'a> {
     text: &'a str,
 }
-fn App2<'a>(cx: Context<'a>, props: &'a MyProps) -> Element<'a> {
+fn App2(cx: Context, props: &MyProps) -> Element {
     None
 }

+ 1 - 1
examples/ssr/basic.rs

@@ -12,7 +12,7 @@ fn main() {
     )
 }
 
-pub static App: FC<()> = |(cx, props)| {
+pub static App: FC<()> = |cx, props| {
     cx.render(rsx!(
         div {
             class: "overflow-hidden"

+ 1 - 1
examples/ssr/tide.rs

@@ -42,7 +42,7 @@ fn main() {}
 //     initial_name: String,
 // }
 
-// static Example: FC<ExampleProps> = |(cx, props)| {
+// static Example: FC<ExampleProps> = |cx, props| {
 //     let dispaly_name = use_state(cx, move || props.initial_name.clone());
 
 //     cx.render(rsx! {

+ 6 - 6
examples/ssr/tofile.rs

@@ -24,7 +24,7 @@ fn main() {
     .unwrap();
 }
 
-pub static App: FC<()> = |(cx, props)| {
+pub static App: FC<()> = |cx, props| {
     cx.render(rsx!(
         div { class: "overflow-hidden"
             link { href:"https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel:"stylesheet" }
@@ -39,7 +39,7 @@ pub static App: FC<()> = |(cx, props)| {
     ))
 };
 
-pub static Header: FC<()> = |(cx, props)| {
+pub static Header: FC<()> = |cx, props| {
     cx.render(rsx! {
         div {
             header { class: "text-gray-400 bg-gray-900 body-font"
@@ -65,7 +65,7 @@ pub static Header: FC<()> = |(cx, props)| {
     })
 };
 
-pub static Hero: FC<()> = |(cx, props)| {
+pub static Hero: FC<()> = |cx, props| {
     //
     cx.render(rsx! {
         section{ class: "text-gray-400 bg-gray-900 body-font"
@@ -103,7 +103,7 @@ pub static Hero: FC<()> = |(cx, props)| {
         }
     })
 };
-pub static Entry: FC<()> = |(cx, props)| {
+pub static Entry: FC<()> = |cx, props| {
     //
     cx.render(rsx! {
         section{ class: "text-gray-400 bg-gray-900 body-font"
@@ -116,7 +116,7 @@ pub static Entry: FC<()> = |(cx, props)| {
     })
 };
 
-pub static StacksIcon: FC<()> = |(cx, props)| {
+pub static StacksIcon: FC<()> = |cx, props| {
     cx.render(rsx!(
         svg {
             xmlns: "http://www.w3.org/2000/svg"
@@ -131,7 +131,7 @@ pub static StacksIcon: FC<()> = |(cx, props)| {
         }
     ))
 };
-pub static RightArrowIcon: FC<()> = |(cx, props)| {
+pub static RightArrowIcon: FC<()> = |cx, props| {
     cx.render(rsx!(
         svg {
             fill: "none"

+ 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: FC<()> = |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: FC<()> = |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: FC<()> = |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: FC<()> = |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: FC<()> = |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: FC<()> = |cx, props| {
     cx.render(rsx!(
         svg {
             fill: "none"

+ 23 - 0
examples/tasks.rs

@@ -0,0 +1,23 @@
+//! Example: README.md showcase
+//!
+//! The example from the README.md.
+
+use dioxus::prelude::*;
+fn main() {
+    dioxus::desktop::launch(App, |c| c);
+}
+
+static App: FC<()> = |cx, props| {
+    let mut count = use_state(cx, || 0);
+
+    cx.push_task(async {
+        panic!("polled future");
+        //
+    });
+
+    cx.render(rsx! {
+        div {
+            h1 { "High-Five counter: {count}" }
+        }
+    })
+};

+ 2 - 2
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: FC<()> = |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);
@@ -85,7 +85,7 @@ pub struct TodoEntryProps {
     todo: Rc<TodoItem>,
 }
 
-pub fn TodoEntry((cx, props): Scope<TodoEntryProps>) -> Element {
+pub fn TodoEntry(cx: Context, props: &TodoEntryProps) -> Element {
     let mut is_editing = use_state(cx, || false);
     let mut contents = use_state(cx, || String::from(""));
     let todo = &props.todo;

+ 8 - 6
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: FC<()> = |cx, props| {
     //
     let body = use_suspense(
         cx,
@@ -24,21 +24,23 @@ static App: FC<()> = |(cx, props)| {
                 .await
                 .unwrap();
         },
-        |cx, props| {
+        |props| {
             //
-            rsx!(WeatherDisplay {})
+            cx.render(rsx!(WeatherDisplay {}))
         },
     );
 
-    rsx!(cx, div {
-        {body}
+    cx.render(rsx! {
+        div {
+            {body}
+        }
     })
 };
 
 #[derive(PartialEq, Props)]
 struct WeatherProps {}
 
-static WeatherDisplay: FC<WeatherProps> = |(cx, props)| {
+static WeatherDisplay: FC<WeatherProps> = |cx, props| {
     //
     cx.render(rsx!(
         div { class: "flex items-center justify-center flex-col"

+ 1 - 1
examples/web/blah.rs

@@ -24,7 +24,7 @@ fn main() {
     dioxus_web::launch(App, |c| c)
 }
 
-static App: FC<()> = |(cx, props)| {
+static App: FC<()> = |cx, props| {
     let mut state = use_state(cx, || 0);
     cx.render(rsx! {
         div {

+ 2 - 2
examples/web/btns.rs

@@ -23,7 +23,7 @@ fn main() {
     // dioxus::web::launch(App, |c| c);
 }
 
-static App: FC<()> = |(cx, props)| {
+static App: FC<()> = |cx, props| {
     dbg!("rednering parent");
     cx.render(rsx! {
         div {
@@ -40,7 +40,7 @@ static App: FC<()> = |(cx, props)| {
     })
 };
 
-static But: FC<()> = |(cx, props)| {
+static But: FC<()> = |cx, props| {
     let mut count = use_state(cx, || 0);
 
     // let d = Dropper { name: "asd" };

+ 6 - 6
examples/web/demo.rs

@@ -24,7 +24,7 @@ fn main() {
     dioxus_web::launch(App, |c| c)
 }
 
-pub static App: FC<()> = |(cx, props)| {
+pub static App: FC<()> = |cx, props| {
     cx.render(rsx!(
         div { class: "overflow-hidden"
             link { href:"https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel:"stylesheet" }
@@ -39,7 +39,7 @@ pub static App: FC<()> = |(cx, props)| {
     ))
 };
 
-pub static Header: FC<()> = |(cx, props)| {
+pub static Header: FC<()> = |cx, props| {
     cx.render(rsx! {
         div {
             header { class: "text-gray-400 bg-gray-900 body-font"
@@ -65,7 +65,7 @@ pub static Header: FC<()> = |(cx, props)| {
     })
 };
 
-pub static Hero: FC<()> = |(cx, props)| {
+pub static Hero: FC<()> = |cx, props| {
     //
     cx.render(rsx! {
         section{ class: "text-gray-400 bg-gray-900 body-font"
@@ -103,7 +103,7 @@ pub static Hero: FC<()> = |(cx, props)| {
         }
     })
 };
-pub static Entry: FC<()> = |(cx, props)| {
+pub static Entry: FC<()> = |cx, props| {
     //
     cx.render(rsx! {
         section{ class: "text-gray-400 bg-gray-900 body-font"
@@ -116,7 +116,7 @@ pub static Entry: FC<()> = |(cx, props)| {
     })
 };
 
-pub static StacksIcon: FC<()> = |(cx, props)| {
+pub static StacksIcon: FC<()> = |cx, props| {
     cx.render(rsx!(
         svg {
             // xmlns: "http://www.w3.org/2000/svg"
@@ -131,7 +131,7 @@ pub static StacksIcon: FC<()> = |(cx, props)| {
         }
     ))
 };
-pub static RightArrowIcon: FC<()> = |(cx, props)| {
+pub static RightArrowIcon: FC<()> = |cx, props| {
     cx.render(rsx!(
         svg {
             fill: "none"

+ 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: FC<()> = |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: FC<()> = |cx, props| {
     let mut count = use_state(cx, || 0);
 
     cx.render(rsx! {

+ 1 - 1
packages/core-macro/src/rsx/component.rs

@@ -173,7 +173,7 @@ impl ToTokens for Component {
                 if !self.children.is_empty() {
                     let childs = &self.children;
                     toks.append_all(quote! {
-                        .children(ScopeChildren::new(__cx.fragment_from_iter([ #( #childs ),* ])))
+                        .children(__cx.create_children([ #( #childs ),* ]))
                     });
                 }
 

+ 2 - 2
packages/core-macro/src/rsx/element.rs

@@ -210,12 +210,12 @@ impl ToTokens for ElementAttrNamed {
 
             ElementAttr::EventClosure { name, closure } => {
                 quote! {
-                    dioxus::events::on::#name(__cx, #closure)
+                    dioxus_elements::on::#name(__cx, #closure)
                 }
             }
             ElementAttr::EventTokens { name, tokens } => {
                 quote! {
-                    dioxus::events::on::#name(__cx, #tokens)
+                    dioxus_elements::on::#name(__cx, #tokens)
                 }
             }
         };

+ 2 - 3
packages/core/Cargo.toml

@@ -40,8 +40,6 @@ indexmap = "1.7.0"
 # Serialize the Edits for use in Webview/Liveview instances
 serde = { version = "1", features = ["derive"], optional = true }
 
-serde_repr = { version = "0.1.7", optional = true }
-
 [dev-dependencies]
 anyhow = "1.0.42"
 dioxus-html = { path = "../html" }
@@ -49,12 +47,13 @@ fern = { version = "0.6.0", features = ["colored"] }
 rand = { version = "0.8.4", features = ["small_rng"] }
 simple_logger = "1.13.0"
 dioxus-core-macro = { path = "../core-macro", version = "0.1.2" }
+dioxus-hooks = { path = "../hooks" }
 # async-std = { version = "1.9.0", features = ["attributes"] }
 # criterion = "0.3.5"
 
 [features]
 default = []
-serialize = ["serde", "serde_repr"]
+serialize = ["serde"]
 debug_vdom = []
 
 [[bench]]

+ 16 - 0
packages/core/README.md

@@ -40,3 +40,19 @@ We have big goals for Dioxus. The final implementation must:
 - Be "live". Components should be able to be both server rendered and client rendered without needing frontend APIs.
 - Be modular. Components and hooks should be work anywhere without worrying about target platform.
 
+
+
+## Safety
+
+Dioxus deals with arenas, lifetimes, asynchronous tasks, custom allocators, pinning, and a lot more foundational low-level work that is very difficult to implement with 0 unsafe.
+
+If you don't want to use a crate that uses unsafe, then this crate is not for you.
+
+however, we are always interested in decreasing the scope of the core VirtualDom to make it easier to review.
+
+We'd also be happy to welcome PRs that can eliminate unsafe code while still upholding the numerous variants required to execute certain features.
+
+There's a few invariants that are very important:
+
+- References to `ScopeInner` and `Props` passed into components are *always* valid for as long as the component exists. Even if the scope backing is resized to fit more scopes, the scope has to stay the same place in memory.
+

+ 102 - 0
packages/core/architecture.md

@@ -99,3 +99,105 @@ Internally, the flow of suspense works like this:
 9. diff that node with the new node with a low priority on its own fiber
 10. return the patches back to the event loop
 11. apply the patches to the real dom
+
+/*
+Welcome to Dioxus's cooperative, priority-based scheduler.
+
+I hope you enjoy your stay.
+
+Some essential reading:
+- https://github.com/facebook/react/blob/main/packages/scheduler/src/forks/Scheduler.js#L197-L200
+- https://github.com/facebook/react/blob/main/packages/scheduler/src/forks/Scheduler.js#L440
+- https://github.com/WICG/is-input-pending
+- https://web.dev/rail/
+- https://indepth.dev/posts/1008/inside-fiber-in-depth-overview-of-the-new-reconciliation-algorithm-in-react
+
+# What's going on?
+
+Dioxus is a framework for "user experience" - not just "user interfaces." Part of the "experience" is keeping the UI
+snappy and "jank free" even under heavy work loads. Dioxus already has the "speed" part figured out - but there's no
+point in being "fast" if you can't also be "responsive."
+
+As such, Dioxus can manually decide on what work is most important at any given moment in time. With a properly tuned
+priority system, Dioxus can ensure that user interaction is prioritized and committed as soon as possible (sub 100ms).
+The controller responsible for this priority management is called the "scheduler" and is responsible for juggling many
+different types of work simultaneously.
+
+# How does it work?
+
+Per the RAIL guide, we want to make sure that A) inputs are handled ASAP and B) animations are not blocked.
+React-three-fiber is a testament to how amazing this can be - a ThreeJS scene is threaded in between work periods of
+React, and the UI still stays snappy!
+
+While it's straightforward to run code ASAP and be as "fast as possible", what's not  _not_ straightforward is how to do
+this while not blocking the main thread. The current prevailing thought is to stop working periodically so the browser
+has time to paint and run animations. When the browser is finished, we can step in and continue our work.
+
+React-Fiber uses the "Fiber" concept to achieve a pause-resume functionality. This is worth reading up on, but not
+necessary to understand what we're doing here. In Dioxus, our DiffMachine is guided by DiffInstructions - essentially
+"commands" that guide the Diffing algorithm through the tree. Our "diff_scope" method is async - we can literally pause
+our DiffMachine "mid-sentence" (so to speak) by just stopping the poll on the future. The DiffMachine periodically yields
+so Rust's async machinery can take over, allowing us to customize when exactly to pause it.
+
+React's "should_yield" method is more complex than ours, and I assume we'll move in that direction as Dioxus matures. For
+now, Dioxus just assumes a TimeoutFuture, and selects! on both the Diff algorithm and timeout. If the DiffMachine finishes
+before the timeout, then Dioxus will work on any pending work in the interim. If there is no pending work, then the changes
+are committed, and coroutines are polled during the idle period. However, if the timeout expires, then the DiffMachine
+future is paused and saved (self-referentially).
+
+# Priority System
+
+So far, we've been able to thread our Dioxus work between animation frames - the main thread is not blocked! But that
+doesn't help us _under load_. How do we still stay snappy... even if we're doing a lot of work? Well, that's where
+priorities come into play. The goal with priorities is to schedule shorter work as a "high" priority and longer work as
+a "lower" priority. That way, we can interrupt long-running low-priority work with short-running high-priority work.
+
+React's priority system is quite complex.
+
+There are 5 levels of priority and 2 distinctions between UI events (discrete, continuous). I believe React really only
+uses 3 priority levels and "idle" priority isn't used... Regardless, there's some batching going on.
+
+For Dioxus, we're going with a 4 tier priority system:
+- Sync: Things that need to be done by the next frame, like TextInput on controlled elements
+- High: for events that block all others - clicks, keyboard, and hovers
+- Medium: for UI events caused by the user but not directly - scrolls/forms/focus (all other events)
+- Low: set_state called asynchronously, and anything generated by suspense
+
+In "Sync" state, we abort our "idle wait" future, and resolve the sync queue immediately and escape. Because we completed
+work before the next rAF, any edits can be immediately processed before the frame ends. Generally though, we want to leave
+as much time to rAF as possible. "Sync" is currently only used by onInput - we'll leave some docs telling people not to
+do anything too arduous from onInput.
+
+For the rest, we defer to the rIC period and work down each queue from high to low.
+*/
+
+
+
+Strategy:
+- When called, check for any UI events that might've been received since the last frame.
+- Dump all UI events into a "pending discrete" queue and a "pending continuous" queue.
+
+- If there are any pending discrete events, then elevate our priority level. If our priority level is already "high,"
+    then we need to finish the high priority work first. If the current work is "low" then analyze what scopes
+    will be invalidated by this new work. If this interferes with any in-flight medium or low work, then we need
+    to bump the other work out of the way, or choose to process it so we don't have any conflicts.
+    'static components have a leg up here since their work can be re-used among multiple scopes.
+    "High priority" is only for blocking! Should only be used on "clicks"
+
+- If there are no pending discrete events, then check for continuous events. These can be completely batched
+
+- we batch completely until we run into a discrete event
+- all continuous events are batched together
+- so D C C C C C would be two separate events - D and C. IE onclick and onscroll
+- D C C C C C C D C C C D would be D C D C D in 5 distinct phases.
+
+- !listener bubbling is not currently implemented properly and will need to be implemented somehow in the future
+    - we need to keep track of element parents to be able to traverse properly
+
+
+Open questions:
+- what if we get two clicks from the component during the same slice?
+    - should we batch?
+    - react says no - they are continuous
+    - but if we received both - then we don't need to diff, do we? run as many as we can and then finally diff?
+

+ 2 - 2
packages/core/benches/jsframework.rs

@@ -24,7 +24,7 @@ criterion_group!(mbenches, create_rows);
 criterion_main!(mbenches);
 
 fn create_rows(c: &mut Criterion) {
-    static App: FC<()> = |(cx, _)| {
+    static App: FC<()> = |cx, _| {
         let mut rng = SmallRng::from_entropy();
         let rows = (0..10_000_usize).map(|f| {
             let label = Label::new(&mut rng);
@@ -58,7 +58,7 @@ struct RowProps {
     row_id: usize,
     label: Label,
 }
-fn Row((cx, props): Scope<RowProps>) -> Element {
+fn Row(cx: Context, props: &RowProps) -> Element {
     let [adj, col, noun] = props.label.0;
     cx.render(rsx! {
         tr {

+ 9 - 0
packages/core/examples/props_expand.rs

@@ -0,0 +1,9 @@
+use dioxus_core as dioxus;
+use dioxus_core_macro::*;
+
+fn main() {}
+
+#[derive(Props)]
+struct ChildProps<'a> {
+    name: &'a str,
+}

+ 44 - 0
packages/core/examples/works.rs

@@ -0,0 +1,44 @@
+use dioxus::prelude::*;
+use dioxus_core as dioxus;
+use dioxus_core_macro::*;
+use dioxus_html as dioxus_elements;
+
+fn main() {
+    let _ = VirtualDom::new(Parent);
+}
+
+fn Parent(cx: Context, props: &()) -> Element {
+    let value = cx.use_hook(|_| String::new(), |f| &*f);
+
+    cx.render(rsx! {
+        div {
+            Child { name: value }
+            Fragment { "asd" }
+        }
+    })
+}
+
+#[derive(Props)]
+struct ChildProps<'a> {
+    name: &'a str,
+}
+
+fn Child(cx: Context, props: &ChildProps) -> Element {
+    cx.render(rsx! {
+        div {
+            h1 { "it's nested" }
+            Child2 { name: props.name }
+        }
+    })
+}
+
+#[derive(Props)]
+struct Grandchild<'a> {
+    name: &'a str,
+}
+
+fn Child2(cx: Context, props: &Grandchild) -> Element {
+    cx.render(rsx! {
+        div { "Hello {props.name}!" }
+    })
+}

+ 0 - 124
packages/core/src/bumpframe.rs

@@ -1,124 +0,0 @@
-use crate::innerlude::*;
-use bumpalo::Bump;
-use std::cell::Cell;
-
-pub(crate) struct ActiveFrame {
-    // We use a "generation" for users of contents in the bump frames to ensure their data isn't broken
-    pub generation: Cell<usize>,
-
-    // The double-buffering situation that we will use
-    pub frames: [BumpFrame; 2],
-}
-
-pub(crate) struct BumpFrame {
-    pub bump: Bump,
-    pub(crate) head_node: VNode<'static>,
-
-    #[cfg(test)]
-    // used internally for debugging
-    _name: &'static str,
-}
-
-impl ActiveFrame {
-    pub fn new() -> Self {
-        let b1 = Bump::new();
-        let b2 = Bump::new();
-
-        let frame_a = BumpFrame {
-            head_node: VNode::Fragment(VFragment {
-                key: None,
-                children: &[],
-                is_static: false,
-            }),
-            bump: b1,
-
-            #[cfg(test)]
-            _name: "wip",
-        };
-        let frame_b = BumpFrame {
-            head_node: VNode::Fragment(VFragment {
-                key: None,
-                children: &[],
-                is_static: false,
-            }),
-            bump: b2,
-
-            #[cfg(test)]
-            _name: "fin",
-        };
-        Self {
-            generation: 0.into(),
-            frames: [frame_a, frame_b],
-        }
-    }
-
-    pub unsafe fn reset_wip_frame(&mut self) {
-        self.wip_frame_mut().bump.reset()
-    }
-
-    /// The "work in progress frame" represents the frame that is currently being worked on.
-    pub fn wip_frame(&self) -> &BumpFrame {
-        match self.generation.get() & 1 == 0 {
-            true => &self.frames[0],
-            false => &self.frames[1],
-        }
-    }
-
-    pub fn wip_frame_mut(&mut self) -> &mut BumpFrame {
-        match self.generation.get() & 1 == 0 {
-            true => &mut self.frames[0],
-            false => &mut self.frames[1],
-        }
-    }
-
-    /// The finished frame represents the frame that has been "finished" and cannot be modified again
-    pub fn finished_frame(&self) -> &BumpFrame {
-        match self.generation.get() & 1 == 1 {
-            true => &self.frames[0],
-            false => &self.frames[1],
-        }
-    }
-
-    /// Give out our self-referential item with our own borrowed lifetime
-    pub fn fin_head<'b>(&'b self) -> &'b VNode<'b> {
-        let cur_head = &self.finished_frame().head_node;
-        unsafe { std::mem::transmute::<&VNode<'static>, &VNode<'b>>(cur_head) }
-    }
-
-    /// Give out our self-referential item with our own borrowed lifetime
-    pub fn wip_head<'b>(&'b self) -> &'b VNode<'b> {
-        let cur_head = &self.wip_frame().head_node;
-        unsafe { std::mem::transmute::<&VNode<'static>, &VNode<'b>>(cur_head) }
-    }
-
-    pub fn cycle_frame(&mut self) {
-        self.generation.set(self.generation.get() + 1);
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    //! These tests are bad. I don't have a good way of properly testing the ActiveFrame stuff
-    use super::*;
-
-    #[test]
-    fn test_bump_frame() {
-        let mut frames = ActiveFrame::new();
-
-        // just cycle a few times and make sure we get the right frames out
-        for _ in 0..5 {
-            let fin = frames.finished_frame();
-            let wip = frames.wip_frame();
-            assert_eq!(wip._name, "wip");
-            assert_eq!(fin._name, "fin");
-            frames.cycle_frame();
-
-            let fin = frames.finished_frame();
-            let wip = frames.wip_frame();
-            assert_eq!(wip._name, "fin");
-            assert_eq!(fin._name, "wip");
-            frames.cycle_frame();
-        }
-        assert_eq!(frames.generation.get(), 10);
-    }
-}

+ 0 - 93
packages/core/src/childiter.rs

@@ -1,93 +0,0 @@
-use crate::innerlude::*;
-
-/// This iterator iterates through a list of virtual children and only returns real children (Elements, Text, Anchors).
-///
-/// This iterator is useful when it's important to load the next real root onto the top of the stack for operations like
-/// "InsertBefore".
-pub(crate) struct RealChildIterator<'a> {
-    scopes: &'a ResourcePool,
-
-    // Heuristically we should never bleed into 4 completely nested fragments/components
-    // Smallvec lets us stack allocate our little stack machine so the vast majority of cases are sane
-    // TODO: use const generics instead of the 4 estimation
-    stack: smallvec::SmallVec<[(u16, &'a VNode<'a>); 4]>,
-}
-
-impl<'a> RealChildIterator<'a> {
-    pub fn new(starter: &'a VNode<'a>, scopes: &'a ResourcePool) -> Self {
-        Self {
-            scopes,
-            stack: smallvec::smallvec![(0, starter)],
-        }
-    }
-}
-
-impl<'a> Iterator for RealChildIterator<'a> {
-    type Item = &'a VNode<'a>;
-
-    fn next(&mut self) -> Option<&'a VNode<'a>> {
-        let mut should_pop = false;
-        let mut returned_node: Option<&'a VNode<'a>> = None;
-        let mut should_push = None;
-
-        while returned_node.is_none() {
-            if let Some((count, node)) = self.stack.last_mut() {
-                match &node {
-                    // We can only exit our looping when we get "real" nodes
-                    // This includes fragments and components when they're empty (have a single root)
-                    VNode::Element(_) | VNode::Text(_) | VNode::Suspended(_) | VNode::Anchor(_) => {
-                        // We've recursed INTO an element/text
-                        // We need to recurse *out* of it and move forward to the next
-                        should_pop = true;
-                        returned_node = Some(node);
-                    }
-
-                    // If we get a fragment we push the next child
-                    VNode::Fragment(frag) => {
-                        let subcount = *count as usize;
-
-                        if frag.children.is_empty() {
-                            should_pop = true;
-                            returned_node = Some(node);
-                        }
-
-                        if subcount >= frag.children.len() {
-                            should_pop = true;
-                        } else {
-                            should_push = Some(&frag.children[subcount]);
-                        }
-                    }
-
-                    // For components, we load their root and push them onto the stack
-                    VNode::Component(sc) => {
-                        let scope = self
-                            .scopes
-                            .get_scope(sc.associated_scope.get().unwrap())
-                            .unwrap();
-
-                        // Simply swap the current node on the stack with the root of the component
-                        *node = scope.frames.fin_head();
-                    }
-                }
-            } else {
-                // If there's no more items on the stack, we're done!
-                return None;
-            }
-
-            if should_pop {
-                self.stack.pop();
-                if let Some((id, _)) = self.stack.last_mut() {
-                    *id += 1;
-                }
-                should_pop = false;
-            }
-
-            if let Some(push) = should_push {
-                self.stack.push((0, push));
-                should_push = None;
-            }
-        }
-
-        returned_node
-    }
-}

+ 59 - 67
packages/core/src/component.rs

@@ -5,86 +5,82 @@
 //! if the type supports PartialEq. The Properties trait is used by the rsx! and html! macros to generate the type-safe builder
 //! that ensures compile-time required and optional fields on cx.
 
-use crate::{
-    innerlude::{Context, Element, VAnchor, VFragment, VNode},
-    LazyNodes, ScopeChildren,
-};
-/// A component is a wrapper around a Context and some Props that share a lifetime
+use crate::innerlude::{Context, Element, LazyNodes, NodeLink};
+
+pub struct FragmentProps(Element);
+pub struct FragmentBuilder<const BUILT: bool>(Element);
+impl FragmentBuilder<false> {
+    pub fn children(self, children: Option<NodeLink>) -> FragmentBuilder<true> {
+        FragmentBuilder(children)
+    }
+}
+impl<const A: bool> FragmentBuilder<A> {
+    pub fn build(self) -> FragmentProps {
+        FragmentProps(self.0)
+    }
+}
+
+/// Access the children elements passed into the component
 ///
+/// This enables patterns where a component is passed children from its parent.
 ///
-/// # Example
+/// ## Details
 ///
-/// With memoized state:
-/// ```rust
-/// struct State {}
+/// Unlike React, Dioxus allows *only* lists of children to be passed from parent to child - not arbitrary functions
+/// or classes. If you want to generate nodes instead of accepting them as a list, consider declaring a closure
+/// on the props that takes Context.
 ///
-/// fn Example((cx, props): Scope<State>) -> DomTree {
-///     // ...
-/// }
-/// ```
+/// If a parent passes children into a component, the child will always re-render when the parent re-renders. In other
+/// words, a component cannot be automatically memoized if it borrows nodes from its parent, even if the component's
+/// props are valid for the static lifetime.
+///
+/// ## Example
 ///
-/// With borrowed state:
 /// ```rust
-/// struct State<'a> {
-///     name: &'a str
+/// fn App(cx: Context, props: &()) -> Element {
+///     cx.render(rsx!{
+///         CustomCard {
+///             h1 {}
+///             p {}
+///         }
+///     })
 /// }
 ///
-/// fn Example<'a>((cx, props): Scope<'a, State>) -> DomTree<'a> {
-///     // ...
+/// #[derive(PartialEq, Props)]
+/// struct CardProps {
+///     children: Element
 /// }
-/// ```
 ///
-/// With owned state as a closure:
-/// ```rust
-/// static Example: FC<()> = |(cx, props)| {
-///     // ...
-/// };
+/// fn CustomCard(cx: Context, props: &CardProps) -> Element {
+///     cx.render(rsx!{
+///         div {
+///             h1 {"Title card"}
+///             {props.children}
+///         }
+///     })
+/// }
 /// ```
-///
-pub type Scope<'a, T> = (Context<'a>, &'a T);
-
-pub struct FragmentProps<'a> {
-    children: ScopeChildren<'a>,
-}
-
-pub struct FragmentBuilder<'a, const BUILT: bool> {
-    children: Option<ScopeChildren<'a>>,
-}
-impl<'a> FragmentBuilder<'a, false> {
-    pub fn children(mut self, children: ScopeChildren<'a>) -> FragmentBuilder<'a, true> {
-        FragmentBuilder {
-            children: Some(children),
-        }
-    }
-}
-
-impl<'a, const A: bool> FragmentBuilder<'a, A> {
-    pub fn build(self) -> FragmentProps<'a> {
-        FragmentProps {
-            children: self.children.unwrap_or_default(),
-        }
-    }
-}
-
-impl<'a> Properties for FragmentProps<'a> {
-    type Builder = FragmentBuilder<'a, false>;
-
+impl Properties for FragmentProps {
+    type Builder = FragmentBuilder<false>;
     const IS_STATIC: bool = false;
-
     fn builder() -> Self::Builder {
-        FragmentBuilder { children: None }
+        FragmentBuilder(None)
     }
-
-    unsafe fn memoize(&self, other: &Self) -> bool {
+    unsafe fn memoize(&self, _other: &Self) -> bool {
         false
     }
 }
 
 /// Create inline fragments using Component syntax.
 ///
+/// ## Details
+///
 /// Fragments capture a series of children without rendering extra nodes.
 ///
-/// # Example
+/// Creating fragments explicitly with the Fragment component is particularly useful when rendering lists or tables and
+/// a key is needed to identify each item.
+///
+/// ## Example
 ///
 /// ```rust
 /// rsx!{
@@ -92,20 +88,17 @@ impl<'a> Properties for FragmentProps<'a> {
 /// }
 /// ```
 ///
-/// # Details
+/// ## Usage
 ///
 /// Fragments are incredibly useful when necessary, but *do* add cost in the diffing phase.
-/// Try to avoid nesting fragments if you can. There is no protection against infinitely nested fragments.
+/// Try to avoid highly nested fragments if you can. Unlike React, there is no protection against infinitely nested fragments.
 ///
 /// This function defines a dedicated `Fragment` component that can be used to create inline fragments in the RSX macro.
 ///
 /// You want to use this free-function when your fragment needs a key and simply returning multiple nodes from rsx! won't cut it.
-///
 #[allow(non_upper_case_globals, non_snake_case)]
-pub fn Fragment<'a>((cx, props): Scope<'a, FragmentProps<'a>>) -> Element<'a> {
-    cx.render(Some(LazyNodes::new(|f| {
-        f.fragment_from_iter(&props.children)
-    })))
+pub fn Fragment(cx: Context, props: &FragmentProps) -> Element {
+    cx.render(Some(LazyNodes::new(|f| f.fragment_from_iter(&props.0))))
 }
 
 /// Every "Props" used for a component must implement the `Properties` trait. This trait gives some hints to Dioxus
@@ -167,12 +160,11 @@ impl Properties for () {
 // that the macros use to anonymously complete prop construction.
 pub struct EmptyBuilder;
 impl EmptyBuilder {
-    #[inline]
     pub fn build(self) {}
 }
 
 /// This utility function launches the builder method so rsx! and html! macros can use the typed-builder pattern
 /// to initialize a component's props.
-pub fn fc_to_builder<'a, T: Properties + 'a>(_: fn(Scope<'a, T>) -> Element<'a>) -> T::Builder {
+pub fn fc_to_builder<'a, T: Properties + 'a>(_: fn(Context<'a>, &'a T) -> Element) -> T::Builder {
     T::builder()
 }

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

@@ -1,271 +0,0 @@
-//! Public APIs for managing component state, tasks, and lifecycles.
-//!
-//! This module is separate from `Scope` to narrow what exactly is exposed to user code.
-//!
-//! We unsafely implement `send` for the VirtualDom, but those guarantees can only be
-
-use bumpalo::Bump;
-
-use crate::{innerlude::*, lazynodes::LazyNodes};
-use std::{any::TypeId, ops::Deref, rc::Rc};
-
-/// Components in Dioxus use the "Context" object to interact with their lifecycle.
-///
-/// This lets components access props, schedule updates, integrate hooks, and expose shared state.
-///
-/// Note: all of these methods are *imperative* - they do not act as hooks! They are meant to be used by hooks
-/// to provide complex behavior. For instance, calling "add_shared_state" on every render is considered a leak. This method
-/// exists for the `use_provide_state` hook to provide a shared state object.
-///
-/// For the most part, the only method you should be using regularly is `render`.
-///
-/// ## Example
-///
-/// ```ignore
-/// #[derive(Properties)]
-/// struct Props {
-///     name: String
-/// }
-///
-/// fn example(cx: Context<Props>) -> VNode {
-///     html! {
-///         <div> "Hello, {cx.name}" </div>
-///     }
-/// }
-/// ```
-pub struct Context<'src> {
-    pub scope: &'src ScopeInner,
-}
-
-impl<'src> Copy for Context<'src> {}
-impl<'src> Clone for Context<'src> {
-    fn clone(&self) -> Self {
-        Self { scope: self.scope }
-    }
-}
-
-// We currently deref to props, but it might make more sense to deref to Scope?
-// This allows for code that takes cx.xyz instead of cx.props.xyz
-impl<'a> Deref for Context<'a> {
-    type Target = &'a ScopeInner;
-    fn deref(&self) -> &Self::Target {
-        &self.scope
-    }
-}
-
-impl<'src> Context<'src> {
-    /// Create a subscription that schedules a future render for the reference component
-    ///
-    /// ## Notice: you should prefer using prepare_update and get_scope_id
-    pub fn schedule_update(&self) -> Rc<dyn Fn() + 'static> {
-        self.scope.memoized_updater.clone()
-    }
-
-    pub fn needs_update(&self) {
-        (self.scope.memoized_updater)()
-    }
-
-    pub fn needs_update_any(&self, id: ScopeId) {
-        (self.scope.shared.schedule_any_immediate)(id)
-    }
-
-    /// Schedule an update for any component given its ScopeId.
-    ///
-    /// A component's ScopeId can be obtained from `use_hook` or the [`Context::scope_id`] method.
-    ///
-    /// This method should be used when you want to schedule an update for a component
-    pub fn schedule_update_any(&self) -> Rc<dyn Fn(ScopeId)> {
-        self.scope.shared.schedule_any_immediate.clone()
-    }
-
-    /// 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 scope_id(&self) -> ScopeId {
-        self.scope.our_arena_idx
-    }
-
-    pub fn bump(&self) -> &'src Bump {
-        let bump = &self.scope.frames.wip_frame().bump;
-        bump
-    }
-
-    /// Take a lazy VNode structure and actually build it with the context of the VDom's efficient VNode allocator.
-    ///
-    /// This function consumes the context and absorb the lifetime, so these VNodes *must* be returned.
-    ///
-    /// ## Example
-    ///
-    /// ```ignore
-    /// fn Component(cx: Context<()>) -> VNode {
-    ///     // Lazy assemble the VNode tree
-    ///     let lazy_tree = html! {<div> "Hello World" </div>};
-    ///
-    ///     // Actually build the tree and allocate it
-    ///     cx.render(lazy_tree)
-    /// }
-    ///```
-    pub fn render(self, lazy_nodes: Option<LazyNodes<'src, '_>>) -> Option<VNode<'src>> {
-        let bump = &self.scope.frames.wip_frame().bump;
-        let factory = NodeFactory { bump };
-        lazy_nodes.map(|f| f.call(factory))
-    }
-
-    /// `submit_task` will submit the future to be polled.
-    ///
-    /// This is useful when you have some async task that needs to be progressed.
-    ///
-    /// This method takes ownership over the task you've provided, and must return (). This means any work that needs to
-    /// happen must occur within the future or scheduled for after the future completes (through schedule_update )
-    ///
-    /// ## Explanation
-    /// Dioxus will step its internal event loop if the future returns if the future completes while waiting.
-    ///
-    /// Tasks can't return anything, but they can be controlled with the returned handle
-    ///
-    /// Tasks will only run until the component renders again. Because `submit_task` is valid for the &'src lifetime, it
-    /// is considered "stable"
-    ///
-    ///
-    ///
-    pub fn submit_task(&self, task: FiberTask) -> TaskHandle {
-        (self.scope.shared.submit_task)(task)
-    }
-
-    /// This method enables the ability to expose state to children further down the VirtualDOM Tree.
-    ///
-    /// This is a "fundamental" operation and should only be called during initialization of a hook.
-    ///
-    /// For a hook that provides the same functionality, use `use_provide_state` and `use_consume_state` instead.
-    ///
-    /// When the component is dropped, so is the context. Be aware of this behavior when consuming
-    /// the context via Rc/Weak.
-    ///
-    /// # Example
-    ///
-    /// ```
-    /// struct SharedState(&'static str);
-    ///
-    /// static App: FC<()> = |(cx, props)|{
-    ///     cx.use_hook(|_| cx.provide_state(SharedState("world")), |_| {}, |_| {});
-    ///     rsx!(cx, Child {})
-    /// }
-    ///
-    /// static Child: FC<()> = |(cx, props)|{
-    ///     let state = cx.consume_state::<SharedState>();
-    ///     rsx!(cx, div { "hello {state.0}" })
-    /// }
-    /// ```
-    pub fn provide_state<T>(self, value: T)
-    where
-        T: 'static,
-    {
-        self.scope
-            .shared_contexts
-            .borrow_mut()
-            .insert(TypeId::of::<T>(), Rc::new(value))
-            .map(|f| f.downcast::<T>().ok())
-            .flatten();
-    }
-
-    /// Try to retrieve a SharedState with type T from the any parent Scope.
-    pub fn consume_state<T: 'static>(self) -> Option<Rc<T>> {
-        let getter = &self.scope.shared.get_shared_context;
-        let ty = TypeId::of::<T>();
-        let idx = self.scope.our_arena_idx;
-        getter(idx, ty).map(|f| f.downcast().unwrap())
-    }
-
-    /// Create a new subtree with this scope as the root of the subtree.
-    ///
-    /// Each component has its own subtree ID - the root subtree has an ID of 0. This ID is used by the renderer to route
-    /// the mutations to the correct window/portal/subtree.
-    ///
-    /// This method
-    ///
-    /// # Example
-    ///
-    /// ```rust
-    /// static App: FC<()> = |(cx, props)| {
-    ///     todo!();
-    ///     rsx!(cx, div { "Subtree {id}"})
-    /// };
-    /// ```
-    pub fn create_subtree(self) -> Option<u32> {
-        self.scope.new_subtree()
-    }
-
-    /// Get the subtree ID that this scope belongs to.
-    ///
-    /// Each component has its own subtree ID - the root subtree has an ID of 0. This ID is used by the renderer to route
-    /// the mutations to the correct window/portal/subtree.
-    ///
-    /// # Example
-    ///
-    /// ```rust
-    /// static App: FC<()> = |(cx, props)| {
-    ///     let id = cx.get_current_subtree();
-    ///     rsx!(cx, div { "Subtree {id}"})
-    /// };
-    /// ```
-    pub fn get_current_subtree(self) -> u32 {
-        self.scope.subtree()
-    }
-
-    /// Store a value between renders
-    ///
-    /// This is *the* foundational hook for all other hooks.
-    ///
-    /// - Initializer: closure used to create the initial hook state
-    /// - Runner: closure used to output a value every time the hook is used
-    /// - Cleanup: closure used to teardown the hook once the dom is cleaned up
-    ///
-    ///
-    /// # Example
-    ///
-    /// ```ignore
-    /// // use_ref is the simplest way of storing a value between renders
-    /// fn use_ref<T: 'static>(initial_value: impl FnOnce() -> T) -> &RefCell<T> {
-    ///     use_hook(
-    ///         || Rc::new(RefCell::new(initial_value())),
-    ///         |state| state,
-    ///         |_| {},
-    ///     )
-    /// }
-    /// ```
-    pub fn use_hook<State, Output, Init, Run, Cleanup>(
-        self,
-        initializer: Init,
-        runner: Run,
-        cleanup: Cleanup,
-    ) -> Output
-    where
-        State: 'static,
-        Output: 'src,
-        Init: FnOnce(usize) -> State,
-        Run: FnOnce(&'src mut State) -> Output,
-        Cleanup: FnOnce(Box<State>) + 'static,
-    {
-        // If the idx is the same as the hook length, then we need to add the current hook
-        if self.scope.hooks.at_end() {
-            self.scope.hooks.push_hook(
-                initializer(self.scope.hooks.len()),
-                Box::new(|raw| {
-                    //
-                    let s = raw.downcast::<State>().unwrap();
-                    cleanup(s);
-                }),
-            );
-        }
-
-        runner(self.scope.hooks.next::<State>().expect(HOOK_ERR_MSG))
-    }
-}
-
-const HOOK_ERR_MSG: &str = r###"
-Unable to retrieve the hook that was initialized at this index.
-Consult the `rules of hooks` to understand how to use hooks properly.
-
-You likely used the hook in a conditional. Hooks rely on consistent ordering between renders.
-Functions prefixed with "use" should never be called conditionally.
-"###;

+ 0 - 128
packages/core/src/debug_dom.rs

@@ -1,128 +0,0 @@
-use crate::{innerlude::ScopeInner, virtual_dom::VirtualDom, VNode};
-
-impl std::fmt::Display for VirtualDom {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        let base = self.base_scope();
-        let root = base.root_node();
-
-        let renderer = ScopeRenderer {
-            show_fragments: false,
-            skip_components: false,
-
-            _scope: base,
-            _pre_render: false,
-            _newline: true,
-            _indent: true,
-            _max_depth: usize::MAX,
-        };
-
-        renderer.render(self, root, f, 0)
-    }
-}
-
-/// render the scope to a string using the rsx! syntax
-pub(crate) struct ScopeRenderer<'a> {
-    pub skip_components: bool,
-    pub show_fragments: bool,
-    pub _scope: &'a ScopeInner,
-    pub _pre_render: bool,
-    pub _newline: bool,
-    pub _indent: bool,
-    pub _max_depth: usize,
-}
-
-// this is more or less a debug tool, but it'll render the entire tree to the terminal
-impl<'a> ScopeRenderer<'a> {
-    pub fn render(
-        &self,
-        vdom: &VirtualDom,
-        node: &VNode,
-        f: &mut std::fmt::Formatter,
-        il: u16,
-    ) -> std::fmt::Result {
-        const INDENT: &str = "    ";
-        let write_indent = |_f: &mut std::fmt::Formatter, le| {
-            for _ in 0..le {
-                write!(_f, "{}", INDENT).unwrap();
-            }
-        };
-
-        match &node {
-            VNode::Text(text) => {
-                write_indent(f, il);
-                writeln!(f, "\"{}\"", text.text)?
-            }
-            VNode::Anchor(_anchor) => {
-                write_indent(f, il);
-                writeln!(f, "Anchor {{}}")?;
-            }
-            VNode::Element(el) => {
-                write_indent(f, il);
-                writeln!(f, "{} {{", el.tag_name)?;
-                // write!(f, "element: {}", el.tag_name)?;
-                let mut attr_iter = el.attributes.iter().peekable();
-
-                while let Some(attr) = attr_iter.next() {
-                    match attr.namespace {
-                        None => {
-                            //
-                            write_indent(f, il + 1);
-                            writeln!(f, "{}: \"{}\"", attr.name, attr.value)?
-                        }
-
-                        Some(ns) => {
-                            // write the opening tag
-                            write_indent(f, il + 1);
-                            write!(f, " {}:\"", ns)?;
-                            let mut cur_ns_el = attr;
-                            'ns_parse: loop {
-                                write!(f, "{}:{};", cur_ns_el.name, cur_ns_el.value)?;
-                                match attr_iter.peek() {
-                                    Some(next_attr) if next_attr.namespace == Some(ns) => {
-                                        cur_ns_el = attr_iter.next().unwrap();
-                                    }
-                                    _ => break 'ns_parse,
-                                }
-                            }
-                            // write the closing tag
-                            write!(f, "\"")?;
-                        }
-                    }
-                }
-
-                for child in el.children {
-                    self.render(vdom, child, f, il + 1)?;
-                }
-                write_indent(f, il);
-
-                writeln!(f, "}}")?;
-            }
-            VNode::Fragment(frag) => {
-                if self.show_fragments {
-                    write_indent(f, il);
-                    writeln!(f, "Fragment {{")?;
-                    for child in frag.children {
-                        self.render(vdom, child, f, il + 1)?;
-                    }
-                    write_indent(f, il);
-                    writeln!(f, "}}")?;
-                } else {
-                    for child in frag.children {
-                        self.render(vdom, child, f, il)?;
-                    }
-                }
-            }
-            VNode::Component(vcomp) => {
-                let idx = vcomp.associated_scope.get().unwrap();
-                if !self.skip_components {
-                    let new_node = vdom.get_scope(idx).unwrap().root_node();
-                    self.render(vdom, new_node, f, il)?;
-                }
-            }
-            VNode::Suspended { .. } => {
-                // we can't do anything with suspended nodes
-            }
-        }
-        Ok(())
-    }
-}

+ 319 - 171
packages/core/src/diff.rs

@@ -90,6 +90,7 @@
 
 use crate::innerlude::*;
 use fxhash::{FxHashMap, FxHashSet};
+use smallvec::{smallvec, SmallVec};
 use DomEdit::*;
 
 /// Our DiffMachine is an iterative tree differ.
@@ -104,78 +105,146 @@ 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(crate) struct DiffMachine<'bump> {
-    pub vdom: &'bump ResourcePool,
+pub struct DiffState<'bump> {
+    scopes: &'bump ScopeArena,
     pub mutations: Mutations<'bump>,
-    pub stack: DiffStack<'bump>,
+    pub(crate) stack: DiffStack<'bump>,
     pub seen_scopes: FxHashSet<ScopeId>,
-    pub cfg: DiffCfg,
-}
-
-pub(crate) struct DiffCfg {
     pub force_diff: bool,
 }
-impl Default for DiffCfg {
-    fn default() -> Self {
+
+impl<'bump> DiffState<'bump> {
+    pub(crate) fn new(scopes: &'bump ScopeArena) -> Self {
         Self {
-            force_diff: Default::default(),
+            scopes,
+            mutations: Mutations::new(),
+            stack: DiffStack::new(),
+            seen_scopes: Default::default(),
+            force_diff: false,
         }
     }
 }
 
-/// a "saved" form of a diff machine
-/// in regular diff machine, the &'bump reference is a stack borrow, but the
-/// bump lifetimes are heap borrows.
-pub(crate) struct SavedDiffWork<'bump> {
-    pub mutations: Mutations<'bump>,
-    pub stack: DiffStack<'bump>,
-    pub seen_scopes: FxHashSet<ScopeId>,
+/// The stack instructions we use to diff and create new nodes.
+#[derive(Debug)]
+pub(crate) enum DiffInstruction<'a> {
+    Diff {
+        old: &'a VNode<'a>,
+        new: &'a VNode<'a>,
+    },
+
+    Create {
+        node: &'a VNode<'a>,
+    },
+
+    /// pushes the node elements onto the stack for use in mount
+    PrepareMove {
+        node: &'a VNode<'a>,
+    },
+
+    Mount {
+        and: MountType<'a>,
+    },
+
+    PopScope,
 }
 
-impl<'a> SavedDiffWork<'a> {
-    pub unsafe fn extend(self: SavedDiffWork<'a>) -> SavedDiffWork<'static> {
-        std::mem::transmute(self)
-    }
+#[derive(Debug, Clone, Copy)]
+pub enum MountType<'a> {
+    Absorb,
+    Append,
+    Replace { old: &'a VNode<'a> },
+    InsertAfter { other_node: &'a VNode<'a> },
+    InsertBefore { other_node: &'a VNode<'a> },
+}
 
-    pub unsafe fn promote<'b>(self, vdom: &'b ResourcePool) -> DiffMachine<'b> {
-        let extended: SavedDiffWork<'b> = std::mem::transmute(self);
-        DiffMachine {
-            vdom,
-            cfg: DiffCfg::default(),
-            mutations: extended.mutations,
-            stack: extended.stack,
-            seen_scopes: extended.seen_scopes,
-        }
-    }
+pub(crate) struct DiffStack<'bump> {
+    pub(crate) instructions: Vec<DiffInstruction<'bump>>,
+    nodes_created_stack: SmallVec<[usize; 10]>,
+    pub scope_stack: SmallVec<[ScopeId; 5]>,
 }
 
-impl<'bump> DiffMachine<'bump> {
-    pub(crate) fn new(mutations: Mutations<'bump>, shared: &'bump ResourcePool) -> Self {
+impl<'bump> DiffStack<'bump> {
+    pub fn new() -> Self {
         Self {
-            mutations,
-            cfg: DiffCfg::default(),
-            stack: DiffStack::new(),
-            vdom: shared,
-            seen_scopes: FxHashSet::default(),
+            instructions: Vec::with_capacity(1000),
+            nodes_created_stack: smallvec![],
+            scope_stack: smallvec![],
         }
     }
 
-    pub fn save(self) -> SavedDiffWork<'bump> {
-        SavedDiffWork {
-            mutations: self.mutations,
-            stack: self.stack,
-            seen_scopes: self.seen_scopes,
-        }
+    pub fn pop(&mut self) -> Option<DiffInstruction<'bump>> {
+        self.instructions.pop()
     }
 
-    pub fn diff_scope(&mut self, id: ScopeId) {
-        if let Some(component) = self.vdom.get_scope_mut(id) {
-            let (old, new) = (component.frames.wip_head(), component.frames.fin_head());
-            self.stack.push(DiffInstruction::Diff { new, old });
-            self.work(|| false);
+    pub fn pop_off_scope(&mut self) {
+        self.scope_stack.pop();
+    }
+
+    pub fn push(&mut self, instruction: DiffInstruction<'bump>) {
+        self.instructions.push(instruction)
+    }
+
+    pub fn create_children(&mut self, children: &'bump [VNode<'bump>], and: MountType<'bump>) {
+        self.nodes_created_stack.push(0);
+        self.instructions.push(DiffInstruction::Mount { and });
+
+        for child in children.iter().rev() {
+            self.instructions
+                .push(DiffInstruction::Create { node: child });
         }
     }
 
+    pub fn push_subtree(&mut self) {
+        self.nodes_created_stack.push(0);
+        self.instructions.push(DiffInstruction::Mount {
+            and: MountType::Append,
+        });
+    }
+
+    pub fn push_nodes_created(&mut self, count: usize) {
+        self.nodes_created_stack.push(count);
+    }
+
+    pub fn create_node(&mut self, node: &'bump VNode<'bump>, and: MountType<'bump>) {
+        self.nodes_created_stack.push(0);
+        self.instructions.push(DiffInstruction::Mount { and });
+        self.instructions.push(DiffInstruction::Create { node });
+    }
+
+    pub fn add_child_count(&mut self, count: usize) {
+        *self.nodes_created_stack.last_mut().unwrap() += count;
+    }
+
+    pub fn pop_nodes_created(&mut self) -> usize {
+        self.nodes_created_stack.pop().unwrap()
+    }
+
+    pub fn current_scope(&self) -> Option<ScopeId> {
+        self.scope_stack.last().copied()
+    }
+
+    pub fn create_component(&mut self, idx: ScopeId, node: &'bump VNode<'bump>) {
+        // Push the new scope onto the stack
+        self.scope_stack.push(idx);
+
+        self.instructions.push(DiffInstruction::PopScope);
+
+        // Run the creation algorithm with this scope on the stack
+        // ?? I think we treat components as fragments??
+        self.instructions.push(DiffInstruction::Create { node });
+    }
+}
+
+impl<'bump> DiffState<'bump> {
+    pub fn diff_scope(&mut self, id: &ScopeId) {
+        let (old, new) = (self.scopes.wip_head(id), self.scopes.fin_head(id));
+        self.stack.push(DiffInstruction::Diff { old, new });
+        self.stack.scope_stack.push(*id);
+        self.stack.push_nodes_created(0);
+        self.work(|| false);
+    }
+
     /// Progress the diffing for this "fiber"
     ///
     /// This method implements a depth-first iterative tree traversal.
@@ -189,7 +258,10 @@ impl<'bump> DiffMachine<'bump> {
                 DiffInstruction::Diff { old, new } => self.diff_node(old, new),
                 DiffInstruction::Create { node } => self.create_node(node),
                 DiffInstruction::Mount { and } => self.mount(and),
-                DiffInstruction::PrepareMove { node } => self.prepare_move_node(node),
+                DiffInstruction::PrepareMove { node } => {
+                    let num_on_stack = self.push_all_nodes(node);
+                    self.stack.add_child_count(num_on_stack);
+                }
                 DiffInstruction::PopScope => self.stack.pop_off_scope(),
             };
 
@@ -202,10 +274,38 @@ impl<'bump> DiffMachine<'bump> {
         true
     }
 
-    fn prepare_move_node(&mut self, node: &'bump VNode<'bump>) {
-        for el in RealChildIterator::new(node, self.vdom) {
-            self.mutations.push_root(el.mounted_id());
-            self.stack.add_child_count(1);
+    // recursively push all the nodes of a tree onto the stack and return how many are there
+    fn push_all_nodes(&mut self, node: &'bump VNode<'bump>) -> usize {
+        match node {
+            VNode::Text(_) | VNode::Anchor(_) | VNode::Suspended(_) => {
+                self.mutations.push_root(node.mounted_id());
+                1
+            }
+
+            VNode::Linked(linked) => {
+                let node = unsafe { &*linked.node };
+                let node: &VNode = unsafe { std::mem::transmute(node) };
+                self.push_all_nodes(node)
+            }
+
+            VNode::Fragment(_) | VNode::Component(_) => {
+                //
+                let mut added = 0;
+                for child in node.children() {
+                    added += self.push_all_nodes(child);
+                }
+                added
+            }
+
+            VNode::Element(el) => {
+                let mut num_on_stack = 0;
+                for child in el.children.iter() {
+                    num_on_stack += self.push_all_nodes(child);
+                }
+                self.mutations.push_root(el.dom_id.get().unwrap());
+
+                num_on_stack + 1
+            }
         }
     }
 
@@ -219,15 +319,7 @@ impl<'bump> DiffMachine<'bump> {
             }
 
             MountType::Replace { old } => {
-                if let Some(old_id) = old.try_mounted_id() {
-                    self.mutations.replace_with(old_id, nodes_created as u32);
-                    self.remove_nodes(Some(old), true);
-                } else {
-                    if let Some(id) = self.find_first_element_id(old) {
-                        self.mutations.replace_with(id, nodes_created as u32);
-                    }
-                    self.remove_nodes(Some(old), true);
-                }
+                self.replace_node(old, nodes_created);
             }
 
             MountType::Append => {
@@ -260,18 +352,19 @@ impl<'bump> DiffMachine<'bump> {
             VNode::Element(element) => self.create_element_node(element, node),
             VNode::Fragment(frag) => self.create_fragment_node(frag),
             VNode::Component(component) => self.create_component_node(component),
+            VNode::Linked(linked) => self.create_linked_node(linked),
         }
     }
 
     fn create_text_node(&mut self, vtext: &'bump VText<'bump>, node: &'bump VNode<'bump>) {
-        let real_id = self.vdom.reserve_node(node);
+        let real_id = self.scopes.reserve_node(node);
         self.mutations.create_text_node(vtext.text, real_id);
         vtext.dom_id.set(Some(real_id));
         self.stack.add_child_count(1);
     }
 
     fn create_suspended_node(&mut self, suspended: &'bump VSuspended, node: &'bump VNode<'bump>) {
-        let real_id = self.vdom.reserve_node(node);
+        let real_id = self.scopes.reserve_node(node);
         self.mutations.create_placeholder(real_id);
 
         suspended.dom_id.set(Some(real_id));
@@ -281,9 +374,11 @@ impl<'bump> DiffMachine<'bump> {
     }
 
     fn create_anchor_node(&mut self, anchor: &'bump VAnchor, node: &'bump VNode<'bump>) {
-        let real_id = self.vdom.reserve_node(node);
+        let real_id = self.scopes.reserve_node(node);
+
         self.mutations.create_placeholder(real_id);
         anchor.dom_id.set(Some(real_id));
+
         self.stack.add_child_count(1);
     }
 
@@ -298,7 +393,7 @@ impl<'bump> DiffMachine<'bump> {
             ..
         } = element;
 
-        let real_id = self.vdom.reserve_node(node);
+        let real_id = self.scopes.reserve_node(node);
 
         dom_id.set(Some(real_id));
 
@@ -307,13 +402,13 @@ impl<'bump> DiffMachine<'bump> {
         self.stack.add_child_count(1);
 
         if let Some(cur_scope_id) = self.stack.current_scope() {
-            let scope = self.vdom.get_scope(cur_scope_id).unwrap();
+            let scope = self.scopes.get_scope(&cur_scope_id).unwrap();
 
-            listeners.iter().for_each(|listener| {
+            for listener in *listeners {
                 self.attach_listener_to_scope(listener, scope);
                 listener.mounted_node.set(Some(real_id));
                 self.mutations.new_event_listener(listener, cur_scope_id);
-            });
+            }
         } else {
             log::warn!("create element called with no scope on the stack - this is an error for a live dom");
         }
@@ -332,61 +427,47 @@ impl<'bump> DiffMachine<'bump> {
     }
 
     fn create_component_node(&mut self, vcomponent: &'bump VComponent<'bump>) {
-        let caller = vcomponent.caller;
-
         let parent_idx = self.stack.current_scope().unwrap();
 
-        let shared = self.vdom.channel.clone();
-
         // Insert a new scope into our component list
-        let parent_scope = self.vdom.get_scope(parent_idx).unwrap();
-
-        let new_idx = self.vdom.insert_scope_with_key(|new_idx| {
-            ScopeInner::new(
-                caller,
-                new_idx,
-                Some(parent_idx),
-                parent_scope.height + 1,
-                parent_scope.subtree(),
-                shared,
-            )
-        });
+        let parent_scope = self.scopes.get_scope(&parent_idx).unwrap();
+        let height = parent_scope.height + 1;
+        let subtree = parent_scope.subtree.get();
+
+        let parent_scope = unsafe { self.scopes.get_scope_raw(&parent_idx) };
+        let caller = unsafe { std::mem::transmute(vcomponent.caller as *const _) };
+        let fc_ptr = vcomponent.user_fc;
+
+        let new_idx = self
+            .scopes
+            .new_with_key(fc_ptr, caller, parent_scope, height, subtree);
 
         // Actually initialize the caller's slot with the right address
         vcomponent.associated_scope.set(Some(new_idx));
 
         if !vcomponent.can_memoize {
-            let cur_scope = self.vdom.get_scope_mut(parent_idx).unwrap();
-            let extended = vcomponent as *const VComponent;
-            let extended: *const VComponent<'static> = unsafe { std::mem::transmute(extended) };
-            cur_scope.borrowed_props.borrow_mut().push(extended);
+            let cur_scope = self.scopes.get_scope(&parent_idx).unwrap();
+            let extended = unsafe { std::mem::transmute(vcomponent) };
+            cur_scope.items.borrow_mut().borrowed_props.push(extended);
         }
 
         // TODO:
         //  add noderefs to current noderef list Noderefs
         //  add effects to current effect list Effects
-
-        let new_component = self.vdom.get_scope_mut(new_idx).unwrap();
+        let new_component = self.scopes.get_scope(&new_idx).unwrap();
 
         log::debug!(
             "initializing component {:?} with height {:?}",
             new_idx,
-            parent_scope.height + 1
+            height + 1
         );
 
         // Run the scope for one iteration to initialize it
-        if new_component.run_scope(self.vdom) {
+        if self.scopes.run_scope(&new_idx) {
             // Take the node that was just generated from running the component
-            let nextnode = new_component.frames.fin_head();
+            let nextnode = self.scopes.fin_head(&new_idx);
             self.stack.create_component(new_idx, nextnode);
 
-            //
-            /*
-            tree_item {
-
-            }
-
-            */
             if new_component.is_subtree_root.get() {
                 self.stack.push_subtree();
             }
@@ -396,6 +477,16 @@ impl<'bump> DiffMachine<'bump> {
         self.seen_scopes.insert(new_idx);
     }
 
+    fn create_linked_node(&mut self, link: &'bump NodeLink) {
+        if link.scope_id.get().is_none() {
+            if let Some(cur_scope) = self.stack.current_scope() {
+                link.scope_id.set(Some(cur_scope));
+            }
+        }
+        let node: &'bump VNode<'static> = unsafe { &*link.node };
+        self.create_node(unsafe { std::mem::transmute(node) });
+    }
+
     // =================================
     //  Tools for diffing nodes
     // =================================
@@ -412,11 +503,14 @@ impl<'bump> DiffMachine<'bump> {
             (Anchor(old), Anchor(new)) => new.dom_id.set(old.dom_id.get()),
             (Suspended(old), Suspended(new)) => self.diff_suspended_nodes(old, new),
             (Element(old), Element(new)) => self.diff_element_nodes(old, new, old_node, new_node),
+            (Linked(old), Linked(new)) => self.diff_linked_nodes(old, new),
 
             // Anything else is just a basic replace and create
             (
-                Component(_) | Fragment(_) | Text(_) | Element(_) | Anchor(_) | Suspended(_),
-                Component(_) | Fragment(_) | Text(_) | Element(_) | Anchor(_) | Suspended(_),
+                Linked(_) | Component(_) | Fragment(_) | Text(_) | Element(_) | Anchor(_)
+                | Suspended(_),
+                Linked(_) | Component(_) | Fragment(_) | Text(_) | Element(_) | Anchor(_)
+                | Suspended(_),
             ) => self
                 .stack
                 .create_node(new_node, MountType::Replace { old: old_node }),
@@ -494,7 +588,7 @@ impl<'bump> DiffMachine<'bump> {
         //
         // TODO: take a more efficient path than this
         if let Some(cur_scope_id) = self.stack.current_scope() {
-            let scope = self.vdom.get_scope(cur_scope_id).unwrap();
+            let scope = self.scopes.get_scope(&cur_scope_id).unwrap();
 
             if old.listeners.len() == new.listeners.len() {
                 for (old_l, new_l) in old.listeners.iter().zip(new.listeners.iter()) {
@@ -533,33 +627,42 @@ impl<'bump> DiffMachine<'bump> {
         &mut self,
         old_node: &'bump VNode<'bump>,
         new_node: &'bump VNode<'bump>,
-
         old: &'bump VComponent<'bump>,
         new: &'bump VComponent<'bump>,
     ) {
         let scope_addr = old.associated_scope.get().unwrap();
 
+        log::debug!(
+            "Diffing components. old_scope: {:?}, old_addr: {:?}, new_addr: {:?}",
+            scope_addr,
+            old.user_fc,
+            new.user_fc
+        );
+
         // Make sure we're dealing with the same component (by function pointer)
         if old.user_fc == new.user_fc {
-            log::debug!("Diffing component {:?} - {:?}", new.user_fc, scope_addr);
-            //
             self.stack.scope_stack.push(scope_addr);
 
             // Make sure the new component vnode is referencing the right scope id
             new.associated_scope.set(Some(scope_addr));
 
             // make sure the component's caller function is up to date
-            let scope = self.vdom.get_scope_mut(scope_addr).unwrap();
-            scope.update_scope_dependencies(new.caller);
+            let scope = unsafe {
+                self.scopes
+                    .get_scope_mut(&scope_addr)
+                    .expect(&format!("could not find {:?}", scope_addr))
+            };
+            scope.caller = unsafe { std::mem::transmute(new.caller) };
 
             // React doesn't automatically memoize, but we do.
             let props_are_the_same = old.comparator.unwrap();
 
-            if self.cfg.force_diff || !props_are_the_same(new) {
-                let succeeded = scope.run_scope(self.vdom);
-
-                if succeeded {
-                    self.diff_node(scope.frames.wip_head(), scope.frames.fin_head());
+            if self.force_diff || !props_are_the_same(new) {
+                if self.scopes.run_scope(&scope_addr) {
+                    self.diff_node(
+                        self.scopes.wip_head(&scope_addr),
+                        self.scopes.fin_head(&scope_addr),
+                    );
                 }
             }
 
@@ -589,6 +692,19 @@ impl<'bump> DiffMachine<'bump> {
         self.attach_suspended_node_to_scope(new);
     }
 
+    fn diff_linked_nodes(&mut self, old: &'bump NodeLink, new: &'bump NodeLink) {
+        if !std::ptr::eq(old.node, new.node) {
+            // if the ptrs are the same then theyr're the same
+            let old: &VNode = unsafe { std::mem::transmute(&*old.node) };
+            let new: &VNode = unsafe { std::mem::transmute(&*new.node) };
+            self.diff_node(old, new);
+        }
+
+        if new.scope_id.get().is_none() {
+            todo!("attach the link to the scope - when children are not created");
+        }
+    }
+
     // =============================================
     //  Utilities for creating new diff instructions
     // =============================================
@@ -627,7 +743,14 @@ impl<'bump> DiffMachine<'bump> {
                     .create_children(new, MountType::Replace { old: &old[0] });
             }
             (_, [VNode::Anchor(_)]) => {
-                self.replace_and_create_many_with_one(old, &new[0]);
+                let new: &'bump VNode<'bump> = &new[0];
+                if let Some(first_old) = old.get(0) {
+                    self.remove_nodes(&old[1..], true);
+                    self.stack
+                        .create_node(new, MountType::Replace { old: first_old });
+                } else {
+                    self.stack.create_node(new, MountType::Append {});
+                }
             }
             _ => {
                 let new_is_keyed = new[0].key().is_some();
@@ -799,6 +922,7 @@ impl<'bump> DiffMachine<'bump> {
     /// If there is no offset, then this function returns None and the diffing is complete.
     fn diff_keyed_ends(
         &mut self,
+
         old: &'bump [VNode<'bump>],
         new: &'bump [VNode<'bump>],
     ) -> Option<(usize, usize)> {
@@ -858,7 +982,7 @@ impl<'bump> DiffMachine<'bump> {
     //
     // This function will load the appropriate nodes onto the stack and do diffing in place.
     //
-    // Upon exit from this function, it will be restored to that same state.
+    // Upon exit from this function, it will be restored to that same self.
     fn diff_keyed_middle(&mut self, old: &'bump [VNode<'bump>], new: &'bump [VNode<'bump>]) {
         /*
         1. Map the old keys into a numerical ordering based on indices.
@@ -923,7 +1047,14 @@ impl<'bump> DiffMachine<'bump> {
             log::debug!("old_key_to_old_index, {:#?}", old_key_to_old_index);
             log::debug!("new_index_to_old_index, {:#?}", new_index_to_old_index);
             log::debug!("shared_keys, {:#?}", shared_keys);
-            self.replace_and_create_many_with_many(old, new);
+
+            if let Some(first_old) = old.get(0) {
+                self.remove_nodes(&old[1..], true);
+                self.stack
+                    .create_children(new, MountType::Replace { old: first_old })
+            } else {
+                self.stack.create_children(new, MountType::Append {});
+            }
             return;
         }
 
@@ -1032,14 +1163,16 @@ impl<'bump> DiffMachine<'bump> {
                 VNode::Element(t) => break t.dom_id.get(),
                 VNode::Suspended(t) => break t.dom_id.get(),
                 VNode::Anchor(t) => break t.dom_id.get(),
-
+                VNode::Linked(l) => {
+                    let node: &VNode = unsafe { std::mem::transmute(&*l.node) };
+                    self.find_last_element(node);
+                }
                 VNode::Fragment(frag) => {
                     search_node = frag.children.last();
                 }
                 VNode::Component(el) => {
                     let scope_id = el.associated_scope.get().unwrap();
-                    let scope = self.vdom.get_scope(scope_id).unwrap();
-                    search_node = Some(scope.root_node());
+                    search_node = Some(self.scopes.root_node(&scope_id));
                 }
             }
         }
@@ -1056,8 +1189,11 @@ impl<'bump> DiffMachine<'bump> {
                 }
                 VNode::Component(el) => {
                     let scope_id = el.associated_scope.get().unwrap();
-                    let scope = self.vdom.get_scope(scope_id).unwrap();
-                    search_node = Some(scope.root_node());
+                    search_node = Some(self.scopes.root_node(&scope_id));
+                }
+                VNode::Linked(link) => {
+                    let node = unsafe { std::mem::transmute(&*link.node) };
+                    search_node = Some(node);
                 }
                 VNode::Text(t) => break t.dom_id.get(),
                 VNode::Element(t) => break t.dom_id.get(),
@@ -1067,17 +1203,38 @@ impl<'bump> DiffMachine<'bump> {
         }
     }
 
-    fn replace_and_create_many_with_one(
-        &mut self,
-        old: &'bump [VNode<'bump>],
-        new: &'bump VNode<'bump>,
-    ) {
-        if let Some(first_old) = old.get(0) {
-            self.remove_nodes(&old[1..], true);
-            self.stack
-                .create_node(new, MountType::Replace { old: first_old });
-        } else {
-            self.stack.create_node(new, MountType::Append {});
+    fn replace_node(&mut self, old: &'bump VNode<'bump>, nodes_created: usize) {
+        match old {
+            VNode::Element(el) => {
+                let id = old.try_mounted_id().expect(&format!("broke on {:?}", old));
+                self.mutations.replace_with(id, nodes_created as u32);
+                self.remove_nodes(el.children, false);
+            }
+
+            VNode::Text(_) | VNode::Anchor(_) | VNode::Suspended(_) => {
+                let id = old.try_mounted_id().expect(&format!("broke on {:?}", old));
+                self.mutations.replace_with(id, nodes_created as u32);
+            }
+
+            VNode::Fragment(f) => {
+                self.replace_node(&f.children[0], nodes_created);
+                self.remove_nodes(f.children.iter().skip(1), true);
+            }
+
+            VNode::Component(c) => {
+                let node = self.scopes.fin_head(&c.associated_scope.get().unwrap());
+                self.replace_node(node, nodes_created);
+
+                let scope_id = c.associated_scope.get().unwrap();
+                println!("replacing c {:?} ", scope_id);
+                log::debug!("Destroying scope {:?}", scope_id);
+                self.scopes.try_remove(&scope_id).unwrap();
+            }
+
+            VNode::Linked(l) => {
+                let node: &'bump VNode<'bump> = unsafe { std::mem::transmute(&*l.node) };
+                self.replace_node(node, nodes_created);
+            }
         }
     }
 
@@ -1092,16 +1249,18 @@ impl<'bump> DiffMachine<'bump> {
         for node in nodes {
             match node {
                 VNode::Text(t) => {
-                    let id = t.dom_id.get().unwrap();
-                    self.vdom.collect_garbage(id);
+                    // this check exists because our null node will be removed but does not have an ID
+                    if let Some(id) = t.dom_id.get() {
+                        self.scopes.collect_garbage(id);
 
-                    if gen_muts {
-                        self.mutations.remove(id.as_u64());
+                        if gen_muts {
+                            self.mutations.remove(id.as_u64());
+                        }
                     }
                 }
                 VNode::Suspended(s) => {
                     let id = s.dom_id.get().unwrap();
-                    self.vdom.collect_garbage(id);
+                    self.scopes.collect_garbage(id);
 
                     if gen_muts {
                         self.mutations.remove(id.as_u64());
@@ -1109,7 +1268,7 @@ impl<'bump> DiffMachine<'bump> {
                 }
                 VNode::Anchor(a) => {
                     let id = a.dom_id.get().unwrap();
-                    self.vdom.collect_garbage(id);
+                    self.scopes.collect_garbage(id);
 
                     if gen_muts {
                         self.mutations.remove(id.as_u64());
@@ -1129,56 +1288,45 @@ impl<'bump> DiffMachine<'bump> {
                     self.remove_nodes(f.children, gen_muts);
                 }
 
+                VNode::Linked(l) => {
+                    let node = unsafe { std::mem::transmute(&*l.node) };
+                    self.remove_nodes(Some(node), gen_muts);
+                }
+
                 VNode::Component(c) => {
-                    let scope_id = c.associated_scope.get().unwrap();
-                    let scope = self.vdom.get_scope_mut(scope_id).unwrap();
-                    let root = scope.root_node();
-                    self.remove_nodes(Some(root), gen_muts);
-
-                    log::debug!("Destroying scope {:?}", scope_id);
-                    let mut s = self.vdom.try_remove(scope_id).unwrap();
-                    s.hooks.clear_hooks();
+                    self.destroy_vomponent(c, gen_muts);
                 }
             }
         }
     }
 
-    /// Remove all the old nodes and replace them with newly created new nodes.
-    ///
-    /// The new nodes *will* be created - don't create them yourself!
-    fn replace_and_create_many_with_many(
-        &mut self,
-        old: &'bump [VNode<'bump>],
-        new: &'bump [VNode<'bump>],
-    ) {
-        if let Some(first_old) = old.get(0) {
-            self.remove_nodes(&old[1..], true);
-            self.stack
-                .create_children(new, MountType::Replace { old: first_old })
-        } else {
-            self.stack.create_children(new, MountType::Append {});
-        }
+    fn destroy_vomponent(&mut self, vc: &VComponent, gen_muts: bool) {
+        let scope_id = vc.associated_scope.get().unwrap();
+        let root = self.scopes.root_node(&scope_id);
+        self.remove_nodes(Some(root), gen_muts);
+        log::debug!("Destroying scope {:?}", scope_id);
+        self.scopes.try_remove(&scope_id).unwrap();
     }
 
     /// Adds a listener closure to a scope during diff.
-    fn attach_listener_to_scope<'a>(&mut self, listener: &'a Listener<'a>, scope: &ScopeInner) {
-        let mut queue = scope.listeners.borrow_mut();
-        let long_listener: &'a Listener<'static> = unsafe { std::mem::transmute(listener) };
-        queue.push(long_listener as *const _)
+    fn attach_listener_to_scope(&mut self, listener: &'bump Listener<'bump>, scope: &Scope) {
+        let long_listener = unsafe { std::mem::transmute(listener) };
+        scope.items.borrow_mut().listeners.push(long_listener)
     }
 
     fn attach_suspended_node_to_scope(&mut self, suspended: &'bump VSuspended) {
         if let Some(scope) = self
             .stack
             .current_scope()
-            .and_then(|id| self.vdom.get_scope_mut(id))
+            .and_then(|id| self.scopes.get_scope(&id))
         {
             // safety: this lifetime is managed by the logic on scope
-            let extended: &VSuspended<'static> = unsafe { std::mem::transmute(suspended) };
+            let extended = unsafe { std::mem::transmute(suspended) };
             scope
-                .suspended_nodes
+                .items
                 .borrow_mut()
-                .insert(suspended.task_id, extended as *const _);
+                .suspended_nodes
+                .insert(suspended.task_id, extended);
         }
     }
 }

+ 0 - 117
packages/core/src/diff_stack.rs

@@ -1,117 +0,0 @@
-use crate::innerlude::*;
-use smallvec::{smallvec, SmallVec};
-
-/// The stack instructions we use to diff and create new nodes.
-#[derive(Debug)]
-pub(crate) enum DiffInstruction<'a> {
-    Diff {
-        old: &'a VNode<'a>,
-        new: &'a VNode<'a>,
-    },
-
-    Create {
-        node: &'a VNode<'a>,
-    },
-
-    /// pushes the node elements onto the stack for use in mount
-    PrepareMove {
-        node: &'a VNode<'a>,
-    },
-
-    Mount {
-        and: MountType<'a>,
-    },
-
-    PopScope,
-}
-
-#[derive(Debug, Clone, Copy)]
-pub enum MountType<'a> {
-    Absorb,
-    Append,
-    Replace { old: &'a VNode<'a> },
-    InsertAfter { other_node: &'a VNode<'a> },
-    InsertBefore { other_node: &'a VNode<'a> },
-}
-
-pub(crate) struct DiffStack<'bump> {
-    pub(crate) instructions: Vec<DiffInstruction<'bump>>,
-    nodes_created_stack: SmallVec<[usize; 10]>,
-    pub scope_stack: SmallVec<[ScopeId; 5]>,
-}
-
-impl<'bump> DiffStack<'bump> {
-    pub fn new() -> Self {
-        Self {
-            instructions: Vec::with_capacity(1000),
-            nodes_created_stack: smallvec![],
-            scope_stack: smallvec![],
-        }
-    }
-
-    pub fn is_empty(&self) -> bool {
-        self.instructions.is_empty()
-    }
-
-    pub fn pop(&mut self) -> Option<DiffInstruction<'bump>> {
-        self.instructions.pop()
-    }
-
-    pub fn pop_off_scope(&mut self) {
-        self.scope_stack.pop();
-    }
-
-    pub fn push(&mut self, instruction: DiffInstruction<'bump>) {
-        self.instructions.push(instruction)
-    }
-
-    pub fn create_children(&mut self, children: &'bump [VNode<'bump>], and: MountType<'bump>) {
-        self.nodes_created_stack.push(0);
-        self.instructions.push(DiffInstruction::Mount { and });
-
-        for child in children.iter().rev() {
-            self.instructions
-                .push(DiffInstruction::Create { node: child });
-        }
-    }
-
-    pub fn push_subtree(&mut self) {
-        self.nodes_created_stack.push(0);
-        self.instructions.push(DiffInstruction::Mount {
-            and: MountType::Append,
-        });
-    }
-
-    pub fn push_nodes_created(&mut self, count: usize) {
-        self.nodes_created_stack.push(count);
-    }
-
-    pub fn create_node(&mut self, node: &'bump VNode<'bump>, and: MountType<'bump>) {
-        self.nodes_created_stack.push(0);
-        self.instructions.push(DiffInstruction::Mount { and });
-        self.instructions.push(DiffInstruction::Create { node });
-    }
-
-    pub fn add_child_count(&mut self, count: usize) {
-        *self.nodes_created_stack.last_mut().unwrap() += count;
-    }
-
-    pub fn pop_nodes_created(&mut self) -> usize {
-        self.nodes_created_stack.pop().unwrap()
-    }
-
-    pub fn current_scope(&self) -> Option<ScopeId> {
-        self.scope_stack.last().copied()
-    }
-
-    pub fn create_component(&mut self, idx: ScopeId, node: &'bump VNode<'bump>) {
-        // Push the new scope onto the stack
-        self.scope_stack.push(idx);
-
-        self.instructions.push(DiffInstruction::PopScope);
-
-        // Run the creation algorithm with this scope on the stack
-        // ?? I think we treat components as fragments??
-        self.instructions.push(DiffInstruction::Create { node });
-    }
-}

+ 0 - 50
packages/core/src/heuristics.rs

@@ -1,50 +0,0 @@
-use std::collections::HashMap;
-
-use fxhash::FxHashMap;
-
-use crate::FC;
-
-/// Provides heuristics to the "SharedResources" object for improving allocation performance.
-///
-/// This heuristics engine records the memory footprint of bump arenas and hook lists for each component. These records are
-/// then used later on to optimize the initial allocation for future components. This helps save large allocations later on
-/// that would slow down the diffing and initialization process.
-///
-///
-pub struct HeuristicsEngine {
-    heuristics: FxHashMap<FcSlot, Heuristic>,
-}
-
-pub type FcSlot = *const ();
-
-pub struct Heuristic {
-    hooks: u32,
-    bump_size: u64,
-}
-
-impl HeuristicsEngine {
-    pub(crate) fn new() -> Self {
-        Self {
-            heuristics: FxHashMap::default(),
-        }
-    }
-
-    fn recommend<T>(&mut self, fc: FC<T>, heuristic: Heuristic) {
-        let g = fc as FcSlot;
-        let e = self.heuristics.entry(g);
-    }
-
-    fn get_recommendation<T>(&mut self, fc: FC<T>) -> &Heuristic {
-        let id = fc as FcSlot;
-
-        self.heuristics.entry(id).or_insert(Heuristic {
-            bump_size: 100,
-            hooks: 10,
-        })
-    }
-}
-
-#[test]
-fn types_work() {
-    let engine = HeuristicsEngine::new();
-}

+ 0 - 66
packages/core/src/hooklist.rs

@@ -1,66 +0,0 @@
-use std::{
-    any::Any,
-    cell::{Cell, RefCell, UnsafeCell},
-};
-
-type UnsafeInnerHookState = UnsafeCell<Box<dyn Any>>;
-type HookCleanup = Box<dyn FnOnce(Box<dyn Any>)>;
-
-/// An abstraction over internally stored data using a hook-based memory layout.
-///
-/// Hooks are allocated using Boxes and then our stored references are given out.
-///
-/// It's unsafe to "reset" the hooklist, but it is safe to add hooks into it.
-///
-/// Todo: this could use its very own bump arena, but that might be a tad overkill
-#[derive(Default)]
-pub(crate) struct HookList {
-    vals: RefCell<Vec<(UnsafeInnerHookState, HookCleanup)>>,
-    idx: Cell<usize>,
-}
-
-impl HookList {
-    pub(crate) fn next<T: 'static>(&self) -> Option<&mut T> {
-        self.vals.borrow().get(self.idx.get()).and_then(|inn| {
-            self.idx.set(self.idx.get() + 1);
-            let raw_box = unsafe { &mut *inn.0.get() };
-            raw_box.downcast_mut::<T>()
-        })
-    }
-
-    /// This resets the internal iterator count
-    /// It's okay that we've given out each hook, but now we have the opportunity to give it out again
-    /// Therefore, resetting is considered unsafe
-    ///
-    /// This should only be ran by Dioxus itself before "running scope".
-    /// Dioxus knows how to descend through the tree to prevent mutable aliasing.
-    pub(crate) unsafe fn reset(&mut self) {
-        self.idx.set(0);
-    }
-
-    pub(crate) fn push_hook<T: 'static>(&self, new: T, cleanup: HookCleanup) {
-        self.vals
-            .borrow_mut()
-            .push((UnsafeCell::new(Box::new(new)), cleanup))
-    }
-
-    pub(crate) fn len(&self) -> usize {
-        self.vals.borrow().len()
-    }
-
-    pub(crate) fn cur_idx(&self) -> usize {
-        self.idx.get()
-    }
-
-    pub(crate) fn at_end(&self) -> bool {
-        self.cur_idx() >= self.len()
-    }
-
-    pub fn clear_hooks(&mut self) {
-        log::debug!("clearing hooks...");
-        self.vals
-            .borrow_mut()
-            .drain(..)
-            .for_each(|(state, cleanup)| cleanup(state.into_inner()));
-    }
-}

+ 0 - 208
packages/core/src/hooks.rs

@@ -1,208 +0,0 @@
-//! Built-in hooks
-//!
-//! This module contains all the low-level built-in hooks that require first party support to work.
-//!
-//! Hooks:
-//! - [`use_hook`]
-//! - [`use_state_provider`]
-//! - [`use_state_consumer`]
-//! - [`use_task`]
-//! - [`use_suspense`]
-
-use crate::innerlude::*;
-use futures_util::FutureExt;
-use std::{any::Any, cell::RefCell, future::Future, ops::Deref, rc::Rc};
-
-/// Awaits the given task, forcing the component to re-render when the value is ready.
-///
-/// Returns the handle to the task and the value (if it is ready, else None).
-///
-/// ```
-/// static Example: FC<()> = |(cx, props)| {
-///     let (task, value) = use_task(|| async {
-///         timer::sleep(Duration::from_secs(1)).await;
-///         "Hello World"
-///     });
-///
-///     match contents {
-///         Some(contents) => rsx!(cx, div { "{title}" }),
-///         None => rsx!(cx, div { "Loading..." }),
-///     }
-/// };
-/// ```
-pub fn use_task<'src, Out, Fut, Init>(
-    cx: Context<'src>,
-    task_initializer: Init,
-) -> (&'src TaskHandle, &'src Option<Out>)
-where
-    Out: 'static,
-    Fut: Future<Output = Out> + 'static,
-    Init: FnOnce() -> Fut + 'src,
-{
-    struct TaskHook<T> {
-        handle: TaskHandle,
-        task_dump: Rc<RefCell<Option<T>>>,
-        value: Option<T>,
-    }
-
-    // whenever the task is complete, save it into th
-    cx.use_hook(
-        move |_| {
-            let task_fut = task_initializer();
-
-            let task_dump = Rc::new(RefCell::new(None));
-
-            let slot = task_dump.clone();
-
-            let updater = cx.schedule_update_any();
-            let originator = cx.scope.our_arena_idx;
-
-            let handle = cx.submit_task(Box::pin(task_fut.then(move |output| async move {
-                *slot.as_ref().borrow_mut() = Some(output);
-                updater(originator);
-                originator
-            })));
-
-            TaskHook {
-                task_dump,
-                value: None,
-                handle,
-            }
-        },
-        |hook| {
-            if let Some(val) = hook.task_dump.as_ref().borrow_mut().take() {
-                hook.value = Some(val);
-            }
-            (&hook.handle, &hook.value)
-        },
-        |_| {},
-    )
-}
-
-/// Asynchronously render new nodes once the given future has completed.
-///
-/// # Easda
-///
-///
-///
-///
-/// # Example
-///
-///
-pub fn use_suspense<'src, Out, Fut, Cb>(
-    cx: Context<'src>,
-    task_initializer: impl FnOnce() -> Fut,
-    user_callback: Cb,
-) -> Element<'src>
-where
-    Fut: Future<Output = Out> + 'static,
-    Out: 'static,
-    Cb: for<'a> Fn(SuspendedContext<'a>, &Out) -> Element<'a> + 'static,
-{
-    /*
-    General strategy:
-    - Create a slot for the future to dump its output into
-    - Create a new future feeding off the user's future that feeds the output into that slot
-    - Submit that future as a task
-    - Take the task handle id and attach that to our suspended node
-    - when the hook runs, check if the value exists
-    - if it does, then we can render the node directly
-    - if it doesn't, then we render a suspended node along with with the callback and task id
-    */
-    cx.use_hook(
-        move |_| {
-            let value = Rc::new(RefCell::new(None));
-            let slot = value.clone();
-            let originator = cx.scope.our_arena_idx;
-
-            let handle = cx.submit_task(Box::pin(task_initializer().then(
-                move |output| async move {
-                    *slot.borrow_mut() = Some(Box::new(output) as Box<dyn Any>);
-                    originator
-                },
-            )));
-
-            SuspenseHook { handle, value }
-        },
-        move |hook| match hook.value.borrow().as_ref() {
-            Some(value) => {
-                let out = value.downcast_ref::<Out>().unwrap();
-                let sus = SuspendedContext {
-                    inner: Context { scope: cx.scope },
-                };
-                user_callback(sus, out)
-            }
-            None => {
-                let value = hook.value.clone();
-
-                let id = hook.handle.our_id;
-
-                todo!()
-                // Some(LazyNodes::new(move |f| {
-                //     let bump = f.bump();
-
-                //     use bumpalo::boxed::Box as BumpBox;
-
-                //     let f: &mut dyn FnMut(SuspendedContext<'src>) -> Element<'src> =
-                //         bump.alloc(move |sus| {
-                //             let val = value.borrow();
-
-                //             let out = val
-                //                 .as_ref()
-                //                 .unwrap()
-                //                 .as_ref()
-                //                 .downcast_ref::<Out>()
-                //                 .unwrap();
-
-                //             user_callback(sus, out)
-                //         });
-                //     let callback = unsafe { BumpBox::from_raw(f) };
-
-                //     VNode::Suspended(bump.alloc(VSuspended {
-                //         dom_id: empty_cell(),
-                //         task_id: id,
-                //         callback: RefCell::new(Some(callback)),
-                //     }))
-                // }))
-            }
-        },
-        |_| {},
-    )
-}
-
-pub(crate) struct SuspenseHook {
-    pub handle: TaskHandle,
-    pub value: Rc<RefCell<Option<Box<dyn Any>>>>,
-}
-
-pub struct SuspendedContext<'a> {
-    pub(crate) inner: Context<'a>,
-}
-
-impl<'src> SuspendedContext<'src> {
-    // // pub fn render(
-    // pub fn render(
-    //     // pub fn render<F: FnOnce(NodeFactory<'src>) -> VNode<'src>>(
-    //     self,
-    //     lazy_nodes: LazyNodes<'_>,
-    //     // lazy_nodes: LazyNodes<'src, '_>,
-    // ) -> Element<'src> {
-    //     let bump = &self.inner.scope.frames.wip_frame().bump;
-    //     todo!("suspense")
-    //     // Some(lazy_nodes.into_vnode(NodeFactory { bump }))
-    // }
-}
-
-#[derive(Clone, Copy)]
-pub struct NodeRef<'src, T: 'static>(&'src RefCell<Option<T>>);
-
-impl<'a, T> Deref for NodeRef<'a, T> {
-    type Target = RefCell<Option<T>>;
-    fn deref(&self) -> &Self::Target {
-        self.0
-    }
-}
-
-pub fn use_node_ref<T, P>(cx: Context) -> NodeRef<T> {
-    cx.use_hook(|_| RefCell::new(None), |f| NodeRef { 0: f }, |_| {})
-}

+ 159 - 78
packages/core/src/lazynodes.rs

@@ -27,89 +27,107 @@ pub struct LazyNodes<'a, 'b> {
     inner: StackNodeStorage<'a, 'b>,
 }
 
-type StackHeapSize = [usize; 0];
+type StackHeapSize = [usize; 16];
 
 enum StackNodeStorage<'a, 'b> {
     Stack(LazyStack),
-    Heap(Box<dyn FnOnce(NodeFactory<'a>) -> VNode<'a> + 'b>),
+    Heap(Box<dyn FnMut(Option<NodeFactory<'a>>) -> Option<VNode<'a>> + 'b>),
 }
 
 impl<'a, 'b> LazyNodes<'a, 'b> {
-    pub fn new<F>(val: F) -> Self
+    pub fn new<F>(_val: F) -> Self
     where
         F: FnOnce(NodeFactory<'a>) -> VNode<'a> + 'b,
     {
-        unsafe {
-            let mut ptr: *const _ = &val as &dyn FnOnce(NodeFactory<'a>) -> VNode<'a>;
-
-            assert_eq!(
-                ptr as *const u8, &val as *const _ as *const u8,
-                "MISUSE: Closure returned different pointer"
-            );
-            assert_eq!(
-                std::mem::size_of_val(&*ptr),
-                std::mem::size_of::<F>(),
-                "MISUSE: Closure returned a subset pointer"
-            );
-            let words = ptr_as_slice(&mut ptr);
-            assert!(
-                words[0] == &val as *const _ as usize,
-                "BUG: Pointer layout is not (data_ptr, info...)"
-            );
-
-            // - Ensure that Self is aligned same as data requires
-            assert!(
-                std::mem::align_of::<F>() <= std::mem::align_of::<Self>(),
-                "TODO: Enforce alignment >{} (requires {})",
-                std::mem::align_of::<Self>(),
-                std::mem::align_of::<F>()
-            );
-
-            let info = &words[1..];
-            let data = words[0] as *mut ();
-            let size = mem::size_of::<F>();
-
-            if info.len() * mem::size_of::<usize>() + size > mem::size_of::<StackHeapSize>() {
-                log::debug!("lazy nodes was too large to fit into stack. falling back to heap");
-
-                Self {
-                    inner: StackNodeStorage::Heap(Box::new(val)),
-                }
-            } else {
-                log::debug!("lazy nodes fits on stack!");
-                let mut buf: StackHeapSize = StackHeapSize::default();
-
-                assert!(info.len() + round_to_words(size) <= buf.as_ref().len());
-
-                // Place pointer information at the end of the region
-                // - Allows the data to be at the start for alignment purposes
-                {
-                    let info_ofs = buf.as_ref().len() - info.len();
-                    let info_dst = &mut buf.as_mut()[info_ofs..];
-                    for (d, v) in Iterator::zip(info_dst.iter_mut(), info.iter()) {
-                        *d = *v;
-                    }
-                }
+        // there's no way to call FnOnce without a box, so we need to store it in a slot and use static dispatch
+        let mut slot = Some(_val);
 
-                let src_ptr = data as *const u8;
-                let dataptr = buf.as_mut()[..].as_mut_ptr() as *mut u8;
-                for i in 0..size {
-                    *dataptr.add(i) = *src_ptr.add(i);
-                }
+        let val = move |fac: Option<NodeFactory<'a>>| {
+            let inner = slot.take().unwrap();
+            fac.map(inner)
+        };
 
-                std::mem::forget(val);
+        unsafe { LazyNodes::new_inner(val) }
+    }
 
-                Self {
-                    inner: StackNodeStorage::Stack(LazyStack { _align: [], buf }),
+    unsafe fn new_inner<F>(val: F) -> Self
+    where
+        F: FnMut(Option<NodeFactory<'a>>) -> Option<VNode<'a>> + 'b,
+    {
+        let mut ptr: *const _ = &val as &dyn FnMut(Option<NodeFactory<'a>>) -> Option<VNode<'a>>;
+
+        assert_eq!(
+            ptr as *const u8, &val as *const _ as *const u8,
+            "MISUSE: Closure returned different pointer"
+        );
+        assert_eq!(
+            std::mem::size_of_val(&*ptr),
+            std::mem::size_of::<F>(),
+            "MISUSE: Closure returned a subset pointer"
+        );
+
+        let words = ptr_as_slice(&mut ptr);
+        assert!(
+            words[0] == &val as *const _ as usize,
+            "BUG: Pointer layout is not (data_ptr, info...)"
+        );
+
+        // - Ensure that Self is aligned same as data requires
+        assert!(
+            std::mem::align_of::<F>() <= std::mem::align_of::<Self>(),
+            "TODO: Enforce alignment >{} (requires {})",
+            std::mem::align_of::<Self>(),
+            std::mem::align_of::<F>()
+        );
+
+        let info = &words[1..];
+        let data = words[0] as *mut ();
+        let size = mem::size_of::<F>();
+
+        let stored_size = info.len() * mem::size_of::<usize>() + size;
+        let max_size = mem::size_of::<StackHeapSize>();
+
+        if stored_size > max_size {
+            Self {
+                inner: StackNodeStorage::Heap(Box::new(val)),
+            }
+        } else {
+            let mut buf: StackHeapSize = StackHeapSize::default();
+
+            assert!(info.len() + round_to_words(size) <= buf.as_ref().len());
+
+            // Place pointer information at the end of the region
+            // - Allows the data to be at the start for alignment purposes
+            {
+                let info_ofs = buf.as_ref().len() - info.len();
+                let info_dst = &mut buf.as_mut()[info_ofs..];
+                for (d, v) in Iterator::zip(info_dst.iter_mut(), info.iter()) {
+                    *d = *v;
                 }
             }
+
+            let src_ptr = data as *const u8;
+            let dataptr = buf.as_mut()[..].as_mut_ptr() as *mut u8;
+            for i in 0..size {
+                *dataptr.add(i) = *src_ptr.add(i);
+            }
+
+            std::mem::forget(val);
+
+            Self {
+                inner: StackNodeStorage::Stack(LazyStack {
+                    _align: [],
+                    buf,
+                    dropped: false,
+                }),
+            }
         }
     }
 
     pub fn call(self, f: NodeFactory<'a>) -> VNode<'a> {
         match self.inner {
-            StackNodeStorage::Heap(lazy) => lazy(f),
-            StackNodeStorage::Stack(stack) => stack.call(f),
+            StackNodeStorage::Heap(mut lazy) => lazy(Some(f)).unwrap(),
+            StackNodeStorage::Stack(mut stack) => stack.call(f),
         }
     }
 }
@@ -117,34 +135,51 @@ impl<'a, 'b> LazyNodes<'a, 'b> {
 struct LazyStack {
     _align: [u64; 0],
     buf: StackHeapSize,
+    dropped: bool,
 }
 
 impl LazyStack {
-    unsafe fn create_boxed<'a>(&mut self) -> Box<dyn FnOnce(NodeFactory<'a>) -> VNode<'a>> {
+    fn call<'a>(&mut self, f: NodeFactory<'a>) -> VNode<'a> {
         let LazyStack { buf, .. } = self;
         let data = buf.as_ref();
 
-        let info_size = mem::size_of::<*mut dyn FnOnce(NodeFactory<'a>) -> VNode<'a>>()
-            / mem::size_of::<usize>()
-            - 1;
+        let info_size =
+            mem::size_of::<*mut dyn FnMut(Option<NodeFactory<'a>>) -> Option<VNode<'a>>>()
+                / mem::size_of::<usize>()
+                - 1;
 
         let info_ofs = data.len() - info_size;
 
-        let g: *mut dyn FnOnce(NodeFactory<'a>) -> VNode<'a> =
-            make_fat_ptr(data[..].as_ptr() as usize, &data[info_ofs..]);
+        let g: *mut dyn FnMut(Option<NodeFactory<'a>>) -> Option<VNode<'a>> =
+            unsafe { make_fat_ptr(data[..].as_ptr() as usize, &data[info_ofs..]) };
 
-        Box::from_raw(g)
-    }
+        self.dropped = true;
 
-    fn call(mut self, f: NodeFactory) -> VNode {
-        let boxed = unsafe { self.create_boxed() };
-        boxed(f)
+        let clos = unsafe { &mut *g };
+        clos(Some(f)).unwrap()
     }
 }
 impl Drop for LazyStack {
     fn drop(&mut self) {
-        let boxed = unsafe { self.create_boxed() };
-        mem::drop(boxed);
+        if !self.dropped {
+            let LazyStack { buf, .. } = self;
+            let data = buf.as_ref();
+
+            let info_size = mem::size_of::<
+                *mut dyn FnMut(Option<NodeFactory<'_>>) -> Option<VNode<'_>>,
+            >() / mem::size_of::<usize>()
+                - 1;
+
+            let info_ofs = data.len() - info_size;
+
+            let g: *mut dyn FnMut(Option<NodeFactory<'_>>) -> Option<VNode<'_>> =
+                unsafe { make_fat_ptr(data[..].as_ptr() as usize, &data[info_ofs..]) };
+
+            self.dropped = true;
+
+            let clos = unsafe { &mut *g };
+            clos(None);
+        }
     }
 }
 
@@ -177,7 +212,7 @@ fn round_to_words(len: usize) -> usize {
 fn it_works() {
     let bump = bumpalo::Bump::new();
 
-    simple_logger::init();
+    simple_logger::init().unwrap();
 
     let factory = NodeFactory { bump: &bump };
 
@@ -191,3 +226,49 @@ fn it_works() {
 
     dbg!(g);
 }
+
+#[test]
+fn it_drops() {
+    use std::rc::Rc;
+    let bump = bumpalo::Bump::new();
+
+    simple_logger::init().unwrap();
+
+    // let factory = NodeFactory { scope: &bump };
+
+    struct DropInner {
+        id: i32,
+    }
+    impl Drop for DropInner {
+        fn drop(&mut self) {
+            log::debug!("dropping inner");
+        }
+    }
+    let val = Rc::new(10);
+
+    {
+        let it = (0..10)
+            .map(|i| {
+                let val = val.clone();
+
+                NodeFactory::annotate_lazy(move |f| {
+                    log::debug!("hell closure");
+                    let inner = DropInner { id: i };
+                    f.text(format_args!("hello world {:?}, {:?}", inner.id, val))
+                })
+            })
+            .collect::<Vec<_>>();
+
+        let caller = NodeFactory::annotate_lazy(|f| {
+            log::debug!("main closure");
+            f.fragment_from_iter(it)
+        })
+        .unwrap();
+    }
+
+    // let nodes = caller.call(factory);
+
+    // dbg!(nodes);
+
+    dbg!(Rc::strong_count(&val));
+}

+ 18 - 48
packages/core/src/lib.rs

@@ -12,69 +12,39 @@ Navigating this crate:
 
 Some utilities
 */
-pub mod bumpframe;
-pub mod childiter;
-pub mod component;
-pub mod context;
-pub mod diff;
-pub mod diff_stack;
-pub mod events;
-pub mod heuristics;
-pub mod hooklist;
-pub mod hooks;
-pub mod lazynodes;
-pub mod mutations;
-pub mod nodes;
-pub mod resources;
-pub mod scheduler;
-pub mod scope;
-pub mod tasks;
-pub mod test_dom;
-pub mod threadsafe;
-pub mod util;
-pub mod virtual_dom;
-
-#[cfg(feature = "debug_vdom")]
-pub mod debug_dom;
+pub(crate) mod component;
+pub(crate) mod diff;
+pub(crate) mod lazynodes;
+pub(crate) mod mutations;
+pub(crate) mod nodes;
+pub(crate) mod scope;
+pub(crate) mod scopearena;
+pub(crate) mod virtual_dom;
 
 pub(crate) mod innerlude {
-    pub(crate) use crate::bumpframe::*;
-    pub(crate) use crate::childiter::*;
     pub use crate::component::*;
-    pub use crate::context::*;
-    pub(crate) use crate::diff::*;
-    pub use crate::diff_stack::*;
-    pub use crate::events::*;
-    pub use crate::heuristics::*;
-    pub(crate) use crate::hooklist::*;
-    pub use crate::hooks::*;
+    pub use crate::diff::*;
     pub use crate::lazynodes::*;
     pub use crate::mutations::*;
     pub use crate::nodes::*;
-    pub(crate) use crate::resources::*;
-    pub use crate::scheduler::*;
     pub use crate::scope::*;
-    pub use crate::tasks::*;
-    pub use crate::test_dom::*;
-    pub use crate::threadsafe::*;
-    pub use crate::util::*;
+    pub use crate::scopearena::*;
     pub use crate::virtual_dom::*;
 
-    pub type Element<'a> = Option<VNode<'a>>;
-    pub type FC<P> = for<'a> fn(Scope<'a, P>) -> Element<'a>;
+    pub type Element = Option<NodeLink>;
+    pub type FC<P> = for<'a> fn(Context<'a>, &'a P) -> Element;
 }
 
 pub use crate::innerlude::{
-    Context, DioxusElement, DomEdit, Element, ElementId, EventPriority, LazyNodes, MountType,
-    Mutations, NodeFactory, Properties, SchedulerMsg, ScopeChildren, ScopeId, SuspendedContext,
-    TaskHandle, TestDom, ThreadsafeVirtualDom, UserEvent, VNode, VirtualDom, FC,
+    Attribute, Context, DioxusElement, DomEdit, Element, ElementId, EventPriority, IntoVNode,
+    LazyNodes, Listener, MountType, Mutations, NodeFactory, Properties, SchedulerMsg, ScopeId,
+    UserEvent, VAnchor, VElement, VFragment, VNode, VSuspended, VirtualDom, FC,
 };
 
 pub mod prelude {
-    pub use crate::component::{fc_to_builder, Fragment, Properties, Scope};
-    pub use crate::context::Context;
-    pub use crate::hooks::*;
-    pub use crate::innerlude::{DioxusElement, Element, LazyNodes, NodeFactory, ScopeChildren, FC};
+    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::nodes::VNode;
     pub use crate::VirtualDom;
 }

+ 16 - 5
packages/core/src/mutations.rs

@@ -3,21 +3,32 @@
 //! This module contains an internal API to generate these instructions.
 
 use crate::innerlude::*;
-use std::any::Any;
+use std::{any::Any, fmt::Debug};
 
-#[derive(Debug)]
 pub struct Mutations<'a> {
     pub edits: Vec<DomEdit<'a>>,
     pub noderefs: Vec<NodeRefMutation<'a>>,
+    pub effects: Vec<&'a dyn FnMut()>,
+}
+impl Debug for Mutations<'_> {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.debug_struct("Mutations")
+            .field("edits", &self.edits)
+            .field("noderefs", &self.noderefs)
+            // .field("effects", &self.effects)
+            .finish()
+    }
 }
 
 use DomEdit::*;
 
 impl<'a> Mutations<'a> {
     pub(crate) fn new() -> Self {
-        let edits = Vec::new();
-        let noderefs = Vec::new();
-        Self { edits, noderefs }
+        Self {
+            edits: Vec::new(),
+            noderefs: Vec::new(),
+            effects: Vec::new(),
+        }
     }
 
     // Navigation

+ 0 - 119
packages/core/src/noderef.rs

@@ -1,119 +0,0 @@
-// let scope = diff_machine.get_scope_mut(&trigger.originator).unwrap();
-
-// let mut garbage_list = scope.consume_garbage();
-
-// let mut scopes_to_kill = Vec::new();
-// while let Some(node) = garbage_list.pop() {
-//     match &node.kind {
-//         VNodeKind::Text(_) => {
-//             self.shared.collect_garbage(node.direct_id());
-//         }
-//         VNodeKind::Anchor(_) => {
-//             self.shared.collect_garbage(node.direct_id());
-//         }
-//         VNodeKind::Suspended(_) => {
-//             self.shared.collect_garbage(node.direct_id());
-//         }
-
-//         VNodeKind::Element(el) => {
-//             self.shared.collect_garbage(node.direct_id());
-//             for child in el.children {
-//                 garbage_list.push(child);
-//             }
-//         }
-
-//         VNodeKind::Fragment(frag) => {
-//             for child in frag.children {
-//                 garbage_list.push(child);
-//             }
-//         }
-
-//         VNodeKind::Component(comp) => {
-//             // TODO: run the hook destructors and then even delete the scope
-
-//             let scope_id = comp.ass_scope.get().unwrap();
-//             let scope = self.get_scope(scope_id).unwrap();
-//             let root = scope.root();
-//             garbage_list.push(root);
-//             scopes_to_kill.push(scope_id);
-//         }
-//     }
-// }
-
-// for scope in scopes_to_kill {
-//     // oy kill em
-//     log::debug!("should be removing scope {:#?}", scope);
-// }
-
-// // On the primary event queue, there is no batching, we take them off one-by-one
-// let trigger = match receiver.try_next() {
-//     Ok(Some(trigger)) => trigger,
-//     _ => {
-//         // Continuously poll the future pool and the event receiver for work
-//         let mut tasks = self.shared.async_tasks.borrow_mut();
-//         let tasks_tasks = tasks.next();
-
-//         // if the new event generates work more important than our current fiber, we should consider switching
-//         // only switch if it impacts different scopes.
-//         let mut ui_receiver = self.shared.ui_event_receiver.borrow_mut();
-//         let ui_reciv_task = ui_receiver.next();
-
-//         // right now, this polling method will only catch batched set_states that don't get awaited.
-//         // However, in the future, we might be interested in batching set_states across await points
-//         let immediate_tasks = ();
-
-//         futures_util::pin_mut!(tasks_tasks);
-//         futures_util::pin_mut!(ui_reciv_task);
-
-//         // Poll the event receiver and the future pool for work
-//         // Abort early if our deadline has ran out
-//         let mut deadline = (&mut deadline_future).fuse();
-
-//         let trig = futures_util::select! {
-//             trigger = tasks_tasks => trigger,
-//             trigger = ui_reciv_task => trigger,
-
-//             // abort if we're out of time
-//             _ = deadline => { return Ok(diff_machine.mutations); }
-//         };
-
-//         trig.unwrap()
-//     }
-// };
-
-// async fn select_next_event(&mut self) -> Option<EventTrigger> {
-//     let mut receiver = self.shared.task_receiver.borrow_mut();
-
-//     // drain the in-flight events so that we can sort them out with the current events
-//     while let Ok(Some(trigger)) = receiver.try_next() {
-//         log::info!("retrieving event from receiver");
-//         let key = self.shared.make_trigger_key(&trigger);
-//         self.pending_events.insert(key, trigger);
-//     }
-
-//     if self.pending_events.is_empty() {
-//         // Continuously poll the future pool and the event receiver for work
-//         let mut tasks = self.shared.async_tasks.borrow_mut();
-//         let tasks_tasks = tasks.next();
-
-//         let mut receiver = self.shared.task_receiver.borrow_mut();
-//         let reciv_task = receiver.next();
-
-//         futures_util::pin_mut!(tasks_tasks);
-//         futures_util::pin_mut!(reciv_task);
-
-//         let trigger = match futures_util::future::select(tasks_tasks, reciv_task).await {
-//             futures_util::future::Either::Left((trigger, _)) => trigger,
-//             futures_util::future::Either::Right((trigger, _)) => trigger,
-//         }
-//         .unwrap();
-//         let key = self.shared.make_trigger_key(&trigger);
-//         self.pending_events.insert(key, trigger);
-//     }
-
-//     // pop the most important event off
-//     let key = self.pending_events.keys().next().unwrap().clone();
-//     let trigger = self.pending_events.remove(&key).unwrap();
-
-//     Some(trigger)
-// }

+ 257 - 141
packages/core/src/nodes.rs

@@ -4,14 +4,12 @@
 //! cheap and *very* fast to construct - building a full tree should be quick.
 
 use crate::{
-    innerlude::{
-        empty_cell, Context, Element, ElementId, Properties, Scope, ScopeId, ScopeInner,
-        SuspendedContext,
-    },
+    innerlude::{Context, Element, Properties, Scope, ScopeId},
     lazynodes::LazyNodes,
 };
 use bumpalo::{boxed::Box as BumpBox, Bump};
 use std::{
+    any::Any,
     cell::{Cell, RefCell},
     fmt::{Arguments, Debug, Formatter},
 };
@@ -19,8 +17,8 @@ use std::{
 /// A composable "VirtualNode" to declare a User Interface in the Dioxus VirtualDOM.
 ///
 /// VNodes are designed to be lightweight and used with with a bump allocator. To create a VNode, you can use either of:
+///
 /// - the [`rsx`] macro
-/// - the [`html`] macro
 /// - the [`NodeFactory`] API
 pub enum VNode<'src> {
     /// Text VNodes simply bump-allocated (or static) string slices
@@ -28,7 +26,8 @@ pub enum VNode<'src> {
     /// # Example
     ///
     /// ```
-    /// let node = cx.render(rsx!{ "hello" }).unwrap();
+    /// let mut vdom = VirtualDom::new();
+    /// let node = vdom.render_vnode(rsx!( "hello" ));
     ///
     /// if let VNode::Text(vtext) = node {
     ///     assert_eq!(vtext.text, "hello");
@@ -43,7 +42,9 @@ pub enum VNode<'src> {
     /// # Example
     ///
     /// ```rust
-    /// let node = cx.render(rsx!{
+    /// let mut vdom = VirtualDom::new();
+    ///
+    /// let node = vdom.render_vnode(rsx!{
     ///     div {
     ///         key: "a",
     ///         onclick: |e| log::info!("clicked"),
@@ -51,7 +52,8 @@ pub enum VNode<'src> {
     ///         style: { background_color: "red" }
     ///         "hello"
     ///     }
-    /// }).unwrap();
+    /// });
+    ///
     /// if let VNode::Element(velement) = node {
     ///     assert_eq!(velement.tag_name, "div");
     ///     assert_eq!(velement.namespace, None);
@@ -80,13 +82,13 @@ pub enum VNode<'src> {
     /// # Example
     ///
     /// ```rust
-    /// fn Example(cx: Context<()>) -> DomTree {
+    /// fn Example(cx: Context, props: &()) -> Element {
     ///     todo!()
     /// }
     ///
-    /// let node = cx.render(rsx!{
-    ///     Example {}
-    /// }).unwrap();
+    /// let mut vdom = VirtualDom::new();
+    ///
+    /// let node = vdom.render_vnode(rsx!( Example {} ));
     ///
     /// if let VNode::Component(vcomp) = node {
     ///     assert_eq!(vcomp.user_fc, Example as *const ());
@@ -96,13 +98,11 @@ pub enum VNode<'src> {
 
     /// Suspended VNodes represent chunks of the UI tree that are not yet ready to be displayed.
     ///
-    /// These nodes currently can only be constructed via the [`use_suspense`] hook.
-    ///
     /// # Example
     ///
     /// ```rust
-    /// rsx!{
-    /// }
+    ///
+    ///
     /// ```
     Suspended(&'src VSuspended<'src>),
 
@@ -113,13 +113,32 @@ pub enum VNode<'src> {
     /// # Example
     ///
     /// ```rust
-    /// let node = cx.render(rsx! ( Fragment {} )).unwrap();
+    /// let mut vdom = VirtualDom::new();
+    ///
+    /// let node = vdom.render_vnode(rsx!( Fragment {} ));
+    ///
     /// if let VNode::Fragment(frag) = node {
     ///     let root = &frag.children[0];
     ///     assert_eq!(root, VNode::Anchor);
     /// }
     /// ```
     Anchor(&'src VAnchor),
+
+    /// A VNode that is actually a pointer to some nodes rather than the nodes directly. Useful when rendering portals
+    /// or eliding lifetimes on VNodes through runtime checks.
+    ///
+    /// Linked VNodes can only be made through the [`Context::render`] method
+    ///
+    /// Typically, linked nodes are found *not* in a VNode. When NodeLinks are in a VNode, the NodeLink was passed into
+    /// an `rsx!` call.
+    ///
+    /// # Example
+    /// ```rust
+    /// let mut vdom = VirtualDom::new();
+    ///
+    /// let node: NodeLink = vdom.render_vnode(rsx!( "hello" ));
+    /// ```
+    Linked(NodeLink),
 }
 
 impl<'src> VNode<'src> {
@@ -129,9 +148,11 @@ impl<'src> VNode<'src> {
             VNode::Element(el) => el.key,
             VNode::Component(c) => c.key,
             VNode::Fragment(f) => f.key,
+
             VNode::Text(_t) => None,
             VNode::Suspended(_s) => None,
             VNode::Anchor(_f) => None,
+            VNode::Linked(_c) => None,
         }
     }
 
@@ -151,11 +172,21 @@ impl<'src> VNode<'src> {
             VNode::Element(el) => el.dom_id.get(),
             VNode::Anchor(el) => el.dom_id.get(),
             VNode::Suspended(el) => el.dom_id.get(),
+
+            VNode::Linked(_) => None,
             VNode::Fragment(_) => None,
             VNode::Component(_) => None,
         }
     }
 
+    pub(crate) fn children(&self) -> &[VNode<'src>] {
+        match &self {
+            VNode::Fragment(f) => f.children,
+            VNode::Component(_c) => todo!("children are not accessible through this"),
+            _ => &[],
+        }
+    }
+
     // Create an "owned" version of the vnode.
     pub fn decouple(&self) -> VNode<'src> {
         match self {
@@ -167,7 +198,11 @@ impl<'src> VNode<'src> {
             VNode::Fragment(f) => VNode::Fragment(VFragment {
                 children: f.children,
                 key: f.key,
-                is_static: f.is_static,
+            }),
+            VNode::Linked(c) => VNode::Linked(NodeLink {
+                scope_id: c.scope_id.clone(),
+                link_idx: c.link_idx.clone(),
+                node: c.node.clone(),
             }),
         }
     }
@@ -177,21 +212,46 @@ impl Debug for VNode<'_> {
     fn fmt(&self, s: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
         match &self {
             VNode::Element(el) => s
-                .debug_struct("VElement")
+                .debug_struct("VNode::VElement")
                 .field("name", &el.tag_name)
                 .field("key", &el.key)
                 .finish(),
 
-            VNode::Text(t) => write!(s, "VText {{ text: {} }}", t.text),
-            VNode::Anchor(_) => write!(s, "VAnchor"),
+            VNode::Text(t) => write!(s, "VNode::VText {{ text: {} }}", t.text),
+            VNode::Anchor(_) => write!(s, "VNode::VAnchor"),
 
-            VNode::Fragment(frag) => write!(s, "VFragment {{ children: {:?} }}", frag.children),
-            VNode::Suspended { .. } => write!(s, "VSuspended"),
-            VNode::Component(comp) => write!(s, "VComponent {{ fc: {:?}}}", comp.user_fc),
+            VNode::Fragment(frag) => {
+                write!(s, "VNode::VFragment {{ children: {:?} }}", frag.children)
+            }
+            VNode::Suspended { .. } => write!(s, "VNode::VSuspended"),
+            VNode::Component(comp) => write!(s, "VNode::VComponent {{ fc: {:?}}}", comp.user_fc),
+            VNode::Linked(c) => write!(s, "VNode::VCached {{ scope_id: {:?} }}", c.scope_id.get()),
         }
     }
 }
 
+/// An Element's unique identifier.
+///
+/// `ElementId` is a `usize` that is unique across the entire VirtualDOM - but not unique across time. If a component is
+/// unmounted, then the `ElementId` will be reused for a new component.
+#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
+pub struct ElementId(pub usize);
+impl std::fmt::Display for ElementId {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "{}", self.0)
+    }
+}
+
+impl ElementId {
+    pub fn as_u64(self) -> u64 {
+        self.0 as u64
+    }
+}
+
+fn empty_cell() -> Cell<Option<ElementId>> {
+    Cell::new(None)
+}
+
 /// A placeholder node only generated when Fragments don't have any children.
 pub struct VAnchor {
     pub dom_id: Cell<Option<ElementId>>,
@@ -209,10 +269,7 @@ pub struct VText<'src> {
 /// A list of VNodes with no single root.
 pub struct VFragment<'src> {
     pub key: Option<&'src str>,
-
     pub children: &'src [VNode<'src>],
-
-    pub is_static: bool,
 }
 
 /// An element like a "div" with children, listeners, and attributes.
@@ -307,12 +364,11 @@ pub struct Listener<'bump> {
     /// IE "click" - whatever the renderer needs to attach the listener by name.
     pub event: &'static str,
 
-    #[allow(clippy::type_complexity)]
     /// The actual callback that the user specified
-    pub(crate) callback:
-        RefCell<Option<BumpBox<'bump, dyn FnMut(Box<dyn std::any::Any + Send>) + 'bump>>>,
+    pub(crate) callback: RefCell<Option<BumpBox<'bump, dyn FnMut(Box<dyn Any + Send>) + 'bump>>>,
 }
 
+pub type VCompCaller<'src> = BumpBox<'src, dyn Fn(Context) -> Element + 'src>;
 /// Virtual Components for custom user-defined components
 /// Only supports the functional syntax
 pub struct VComponent<'src> {
@@ -320,21 +376,23 @@ pub struct VComponent<'src> {
 
     pub associated_scope: Cell<Option<ScopeId>>,
 
-    pub is_static: bool,
-
     // Function pointer to the FC that was used to generate this component
     pub user_fc: *const (),
 
-    pub(crate) caller: &'src dyn for<'b> Fn(&'b ScopeInner) -> Element<'b>,
+    pub(crate) can_memoize: bool,
 
-    pub(crate) comparator: Option<&'src dyn Fn(&VComponent) -> bool>,
+    pub(crate) hard_allocation: Cell<Option<*const ()>>,
 
-    pub(crate) drop_props: RefCell<Option<BumpBox<'src, dyn FnMut()>>>,
+    // Raw pointer into the bump arena for the props of the component
+    pub(crate) bump_props: *const (),
 
-    pub(crate) can_memoize: bool,
+    // during the "teardown" process we'll take the caller out so it can be dropped properly
+    // pub(crate) caller: Option<VCompCaller<'src>>,
+    pub(crate) caller: &'src dyn Fn(&'src Scope) -> Element,
 
-    // Raw pointer into the bump arena for the props of the component
-    pub(crate) raw_props: *const (),
+    pub(crate) comparator: Option<&'src dyn Fn(&VComponent) -> bool>,
+
+    pub(crate) drop_props: RefCell<Option<BumpBox<'src, dyn FnMut()>>>,
 }
 
 pub struct VSuspended<'a> {
@@ -342,7 +400,41 @@ pub struct VSuspended<'a> {
     pub dom_id: Cell<Option<ElementId>>,
 
     #[allow(clippy::type_complexity)]
-    pub callback: RefCell<Option<BumpBox<'a, dyn FnMut(SuspendedContext<'a>) -> Element<'a>>>>,
+    pub callback: RefCell<Option<BumpBox<'a, dyn FnMut() -> Element + 'a>>>,
+}
+
+/// A cached node is a "pointer" to a "rendered" node in a particular scope
+///
+/// It does not provide direct access to the node, so it doesn't carry any lifetime information with it
+///
+/// It is used during the diffing/rendering process as a runtime key into an existing set of nodes. The "render" key
+/// is essentially a unique key to guarantee safe usage of the Node.
+///
+/// Linked VNodes can only be made through the [`Context::render`] method
+///
+/// Typically, NodeLinks are found *not* in a VNode. When NodeLinks are in a VNode, the NodeLink was passed into
+/// an `rsx!` call.
+#[derive(Debug)]
+pub struct NodeLink {
+    pub(crate) link_idx: Cell<usize>,
+    pub(crate) scope_id: Cell<Option<ScopeId>>,
+    pub(crate) node: *const VNode<'static>,
+}
+
+impl PartialEq for NodeLink {
+    fn eq(&self, other: &Self) -> bool {
+        self.node == other.node
+    }
+}
+impl NodeLink {
+    // we don't want to let users clone NodeLinks
+    pub(crate) fn clone_inner(&self) -> Self {
+        Self {
+            link_idx: self.link_idx.clone(),
+            scope_id: self.scope_id.clone(),
+            node: self.node.clone(),
+        }
+    }
 }
 
 /// This struct provides an ergonomic API to quickly build VNodes.
@@ -361,7 +453,7 @@ impl<'a> NodeFactory<'a> {
 
     #[inline]
     pub fn bump(&self) -> &'a bumpalo::Bump {
-        self.bump
+        &self.bump
     }
 
     /// Directly pass in text blocks without the need to use the format_args macro.
@@ -381,7 +473,7 @@ impl<'a> NodeFactory<'a> {
             Some(static_str) => (static_str, true),
             None => {
                 use bumpalo::core_alloc::fmt::Write;
-                let mut str_buf = bumpalo::collections::String::new_in(self.bump());
+                let mut str_buf = bumpalo::collections::String::new_in(self.bump);
                 str_buf.write_fmt(args).unwrap();
                 (str_buf.into_bump_str(), false)
             }
@@ -437,18 +529,18 @@ impl<'a> NodeFactory<'a> {
         A: 'a + AsRef<[Attribute<'a>]>,
         V: 'a + AsRef<[VNode<'a>]>,
     {
-        let listeners: &'a L = self.bump().alloc(listeners);
+        let listeners: &'a L = self.bump.alloc(listeners);
         let listeners = listeners.as_ref();
 
-        let attributes: &'a A = self.bump().alloc(attributes);
+        let attributes: &'a A = self.bump.alloc(attributes);
         let attributes = attributes.as_ref();
 
-        let children: &'a V = self.bump().alloc(children);
+        let children: &'a V = self.bump.alloc(children);
         let children = children.as_ref();
 
         let key = key.map(|f| self.raw_text(f).0);
 
-        VNode::Element(self.bump().alloc(VElement {
+        VNode::Element(self.bump.alloc(VElement {
             tag_name,
             key,
             namespace,
@@ -479,19 +571,19 @@ impl<'a> NodeFactory<'a> {
 
     pub fn component<P>(
         &self,
-        component: fn(Scope<'a, P>) -> Element<'a>,
+        component: fn(Context<'a>, &'a P) -> Element,
         props: P,
         key: Option<Arguments>,
     ) -> VNode<'a>
     where
         P: Properties + 'a,
     {
-        let bump = self.bump();
+        let bump = self.bump;
         let props = bump.alloc(props);
-        let raw_props = props as *mut P as *mut ();
+        let bump_props = props as *mut P as *mut ();
         let user_fc = component as *const ();
 
-        let comparator: Option<&dyn Fn(&VComponent) -> bool> = Some(bump.alloc_with(|| {
+        let comparator: &mut dyn Fn(&VComponent) -> bool = bump.alloc_with(|| {
             move |other: &VComponent| {
                 if user_fc == other.user_fc {
                     // Safety
@@ -500,12 +592,10 @@ impl<'a> NodeFactory<'a> {
                     // - Non-static P are autoderived to memoize as false
                     // - This comparator is only called on a corresponding set of bumpframes
                     let props_memoized = unsafe {
-                        let real_other: &P = &*(other.raw_props as *const _ as *const P);
+                        let real_other: &P = &*(other.bump_props as *const _ as *const P);
                         props.memoize(real_other)
                     };
 
-                    log::debug!("comparing props...");
-
                     // It's only okay to memoize if there are no children and the props can be memoized
                     // Implementing memoize is unsafe and done automatically with the props trait
                     props_memoized
@@ -513,7 +603,7 @@ impl<'a> NodeFactory<'a> {
                     false
                 }
             }
-        }));
+        });
 
         let drop_props = {
             // create a closure to drop the props
@@ -521,9 +611,8 @@ impl<'a> NodeFactory<'a> {
 
             let drop_props: &mut dyn FnMut() = bump.alloc_with(|| {
                 move || unsafe {
-                    log::debug!("dropping props!");
                     if !has_dropped {
-                        let real_other = raw_props as *mut _ as *mut P;
+                        let real_other = bump_props as *mut _ as *mut P;
                         let b = BumpBox::from_raw(real_other);
                         std::mem::drop(b);
 
@@ -541,36 +630,45 @@ impl<'a> NodeFactory<'a> {
 
         let key = key.map(|f| self.raw_text(f).0);
 
-        let caller: &'a mut dyn for<'b> Fn(&'b ScopeInner) -> Element<'b> =
-            bump.alloc(move |scope: &ScopeInner| -> Element {
-                log::debug!("calling component renderr {:?}", scope.our_arena_idx);
-                let props: &'_ P = unsafe { &*(raw_props as *const P) };
-
-                let scp: &'a ScopeInner = unsafe { std::mem::transmute(scope) };
-                let s: Scope<'a, P> = (Context { scope: scp }, props);
-
-                let res: Element = component(s);
-                unsafe { std::mem::transmute(res) }
+        let caller: &'a mut dyn Fn(&'a Scope) -> Element =
+            bump.alloc(move |scope: &Scope| -> Element {
+                let props: &'_ P = unsafe { &*(bump_props as *const P) };
+                let res = component(scope, props);
+                res
             });
 
+        let can_memoize = P::IS_STATIC;
+
         VNode::Component(bump.alloc(VComponent {
             user_fc,
-            comparator,
-            raw_props,
+            comparator: Some(comparator),
+            bump_props,
             caller,
-            is_static: P::IS_STATIC,
             key,
-            can_memoize: P::IS_STATIC,
+            can_memoize,
             drop_props,
             associated_scope: Cell::new(None),
+            hard_allocation: Cell::new(None),
         }))
     }
 
+    pub fn listener(
+        self,
+        event: &'static str,
+        callback: BumpBox<'a, dyn FnMut(Box<dyn Any + Send>) + 'a>,
+    ) -> Listener<'a> {
+        Listener {
+            mounted_node: Cell::new(None),
+            event,
+            callback: RefCell::new(Some(callback)),
+        }
+    }
+
     pub fn fragment_from_iter(
         self,
         node_iter: impl IntoIterator<Item = impl IntoVNode<'a>>,
     ) -> VNode<'a> {
-        let bump = self.bump();
+        let bump = self.bump;
         let mut nodes = bumpalo::collections::Vec::new_in(bump);
 
         for node in node_iter {
@@ -607,14 +705,64 @@ impl<'a> NodeFactory<'a> {
         VNode::Fragment(VFragment {
             children,
             key: None,
-            is_static: false,
         })
     }
 
-    pub fn annotate_lazy<'z, 'b, F>(f: F) -> Option<LazyNodes<'z, 'b>>
-    where
-        F: FnOnce(NodeFactory<'z>) -> VNode<'z> + 'b,
-    {
+    // this isn't quite feasible yet
+    // I think we need some form of interior mutability or state on nodefactory that stores which subtree was created
+    pub fn create_children(
+        self,
+        node_iter: impl IntoIterator<Item = impl IntoVNode<'a>>,
+    ) -> Element {
+        let bump = self.bump;
+        let mut nodes = bumpalo::collections::Vec::new_in(bump);
+
+        for node in node_iter {
+            nodes.push(node.into_vnode(self));
+        }
+
+        if nodes.is_empty() {
+            nodes.push(VNode::Anchor(bump.alloc(VAnchor {
+                dom_id: empty_cell(),
+            })));
+        }
+
+        let children = nodes.into_bump_slice();
+
+        // TODO
+        // We need a dedicated path in the rsx! macro that will trigger the "you need keys" warning
+        //
+        // if cfg!(debug_assertions) {
+        //     if children.len() > 1 {
+        //         if children.last().unwrap().key().is_none() {
+        //             log::error!(
+        //                 r#"
+        // Warning: Each child in an array or iterator should have a unique "key" prop.
+        // Not providing a key will lead to poor performance with lists.
+        // See docs.rs/dioxus for more information.
+        // ---
+        // To help you identify where this error is coming from, we've generated a backtrace.
+        //                         "#,
+        //             );
+        //         }
+        //     }
+        // }
+
+        let frag = VNode::Fragment(VFragment {
+            children,
+            key: None,
+        });
+        let ptr = self.bump.alloc(frag) as *const _;
+        Some(NodeLink {
+            link_idx: Default::default(),
+            scope_id: Default::default(),
+            node: unsafe { std::mem::transmute(ptr) },
+        })
+    }
+
+    pub fn annotate_lazy<'z, 'b>(
+        f: impl FnOnce(NodeFactory<'z>) -> VNode<'z> + 'b,
+    ) -> Option<LazyNodes<'z, 'b>> {
         Some(LazyNodes::new(f))
     }
 }
@@ -687,7 +835,6 @@ impl<'a> IntoVNode<'a> for Option<LazyNodes<'a, '_>> {
             Some(lazy) => lazy.call(cx),
             None => VNode::Fragment(VFragment {
                 children: &[],
-                is_static: false,
                 key: None,
             }),
         }
@@ -712,79 +859,48 @@ impl IntoVNode<'_> for Arguments<'_> {
     }
 }
 
-/// Access the children elements passed into the component
-///
-/// This enables patterns where a component is passed children from its parent.
-///
-/// ## Details
-///
-/// Unlike React, Dioxus allows *only* lists of children to be passed from parent to child - not arbitrary functions
-/// or classes. If you want to generate nodes instead of accepting them as a list, consider declaring a closure
-/// on the props that takes Context.
-///
-/// If a parent passes children into a component, the child will always re-render when the parent re-renders. In other
-/// words, a component cannot be automatically memoized if it borrows nodes from its parent, even if the component's
-/// props are valid for the static lifetime.
-///
-/// ## Example
-///
-/// ```rust
-/// const App: FC<()> = |(cx, props)|{
-///     cx.render(rsx!{
-///         CustomCard {
-///             h1 {}
-///             p {}
-///         }
-///     })
-/// }
-///
-/// const CustomCard: FC<()> = |(cx, props)|{
-///     cx.render(rsx!{
-///         div {
-///             h1 {"Title card"}
-///             {props.children}
-///         }
-///     })
-/// }
-/// ```
-///
-/// ## Notes:
-///
-/// This method returns a "ScopeChildren" object. This object is copy-able and preserve the correct lifetime.
-pub struct ScopeChildren<'a> {
-    root: Option<VNode<'a>>,
-}
-
-impl Default for ScopeChildren<'_> {
-    fn default() -> Self {
-        Self { root: None }
+// called cx.render from a helper function
+impl IntoVNode<'_> for Option<NodeLink> {
+    fn into_vnode(self, _cx: NodeFactory) -> VNode {
+        match self {
+            Some(node) => VNode::Linked(node),
+            None => {
+                todo!()
+            }
+        }
     }
 }
 
-impl<'a> ScopeChildren<'a> {
-    pub fn new(root: VNode<'a>) -> Self {
-        Self { root: Some(root) }
-    }
-    pub fn new_option(root: Option<VNode<'a>>) -> Self {
-        Self { root }
+// essentially passing elements through props
+// just build a new element in place
+impl IntoVNode<'_> for &Option<NodeLink> {
+    fn into_vnode(self, _cx: NodeFactory) -> VNode {
+        match self {
+            Some(node) => VNode::Linked(NodeLink {
+                link_idx: node.link_idx.clone(),
+                scope_id: node.scope_id.clone(),
+                node: node.node,
+            }),
+            None => {
+                //
+                todo!()
+            }
+        }
     }
 }
 
-impl IntoIterator for &ScopeChildren<'_> {
-    type Item = Self;
-
-    type IntoIter = std::iter::Once<Self>;
-
-    fn into_iter(self) -> Self::IntoIter {
-        std::iter::once(self)
+impl IntoVNode<'_> for NodeLink {
+    fn into_vnode(self, _cx: NodeFactory) -> VNode {
+        VNode::Linked(self)
     }
 }
 
-impl<'a> IntoVNode<'a> for &ScopeChildren<'a> {
-    fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> {
-        match &self.root {
-            Some(n) => n.decouple(),
-            None => cx.fragment_from_iter(None as Option<VNode>),
-        }
+impl IntoVNode<'_> for &NodeLink {
+    fn into_vnode(self, _cx: NodeFactory) -> VNode {
+        VNode::Linked(NodeLink {
+            link_idx: self.link_idx.clone(),
+            scope_id: self.scope_id.clone(),
+            node: self.node,
+        })
     }
 }

+ 0 - 77
packages/core/src/resources.rs

@@ -1,77 +0,0 @@
-use crate::innerlude::*;
-use slab::Slab;
-
-use std::{cell::UnsafeCell, rc::Rc};
-#[derive(Clone)]
-pub(crate) struct ResourcePool {
-    /*
-    This *has* to be an UnsafeCell.
-
-    Each BumpFrame and Scope is located in this Slab - and we'll need mutable access to a scope while holding on to
-    its bumpframe contents immutably.
-
-    However, all of the interaction with this Slab is done in this module and the Diff module, so it should be fairly
-    simple to audit.
-
-    Wrapped in Rc so the "get_shared_context" closure can walk the tree (immutably!)
-    */
-    pub components: Rc<UnsafeCell<Slab<ScopeInner>>>,
-
-    /*
-    Yes, a slab of "nil". We use this for properly ordering ElementIDs - all we care about is the allocation strategy
-    that slab uses. The slab essentially just provides keys for ElementIDs that we can re-use in a Vec on the client.
-
-    This just happened to be the simplest and most efficient way to implement a deterministic keyed map with slot reuse.
-
-    In the future, we could actually store a pointer to the VNode instead of nil to provide O(1) lookup for VNodes...
-    */
-    pub raw_elements: Rc<UnsafeCell<Slab<*const VNode<'static>>>>,
-
-    pub channel: EventChannel,
-}
-
-impl ResourcePool {
-    /// this is unsafe because the caller needs to track which other scopes it's already using
-    pub fn get_scope(&self, idx: ScopeId) -> Option<&ScopeInner> {
-        let inner = unsafe { &*self.components.get() };
-        inner.get(idx.0)
-    }
-
-    /// this is unsafe because the caller needs to track which other scopes it's already using
-    pub fn get_scope_mut(&self, idx: ScopeId) -> Option<&mut ScopeInner> {
-        let inner = unsafe { &mut *self.components.get() };
-        inner.get_mut(idx.0)
-    }
-
-    pub fn try_remove(&self, id: ScopeId) -> Option<ScopeInner> {
-        let inner = unsafe { &mut *self.components.get() };
-        Some(inner.remove(id.0))
-        // .try_remove(id.0)
-        // .ok_or_else(|| Error::FatalInternal("Scope not found"))
-    }
-
-    pub fn reserve_node<'a>(&self, node: &'a VNode<'a>) -> ElementId {
-        let els = unsafe { &mut *self.raw_elements.get() };
-        let entry = els.vacant_entry();
-        let key = entry.key();
-        let id = ElementId(key);
-        let node = node as *const _;
-        let node = unsafe { std::mem::transmute(node) };
-        entry.insert(node);
-        id
-    }
-
-    /// return the id, freeing the space of the original node
-    pub fn collect_garbage(&self, id: ElementId) {
-        let els = unsafe { &mut *self.raw_elements.get() };
-        els.remove(id.0);
-    }
-
-    pub fn insert_scope_with_key(&self, f: impl FnOnce(ScopeId) -> ScopeInner) -> ScopeId {
-        let g = unsafe { &mut *self.components.get() };
-        let entry = g.vacant_entry();
-        let id = ScopeId(entry.key());
-        entry.insert(f(id));
-        id
-    }
-}

+ 0 - 609
packages/core/src/scheduler.rs

@@ -1,609 +0,0 @@
-/*
-Welcome to Dioxus's cooperative, priority-based scheduler.
-
-I hope you enjoy your stay.
-
-Some essential reading:
-- https://github.com/facebook/react/blob/main/packages/scheduler/src/forks/Scheduler.js#L197-L200
-- https://github.com/facebook/react/blob/main/packages/scheduler/src/forks/Scheduler.js#L440
-- https://github.com/WICG/is-input-pending
-- https://web.dev/rail/
-- https://indepth.dev/posts/1008/inside-fiber-in-depth-overview-of-the-new-reconciliation-algorithm-in-react
-
-# What's going on?
-
-Dioxus is a framework for "user experience" - not just "user interfaces." Part of the "experience" is keeping the UI
-snappy and "jank free" even under heavy work loads. Dioxus already has the "speed" part figured out - but there's no
-point in being "fast" if you can't also be "responsive."
-
-As such, Dioxus can manually decide on what work is most important at any given moment in time. With a properly tuned
-priority system, Dioxus can ensure that user interaction is prioritized and committed as soon as possible (sub 100ms).
-The controller responsible for this priority management is called the "scheduler" and is responsible for juggling many
-different types of work simultaneously.
-
-# How does it work?
-
-Per the RAIL guide, we want to make sure that A) inputs are handled ASAP and B) animations are not blocked.
-React-three-fiber is a testament to how amazing this can be - a ThreeJS scene is threaded in between work periods of
-React, and the UI still stays snappy!
-
-While it's straightforward to run code ASAP and be as "fast as possible", what's not  _not_ straightforward is how to do
-this while not blocking the main thread. The current prevailing thought is to stop working periodically so the browser
-has time to paint and run animations. When the browser is finished, we can step in and continue our work.
-
-React-Fiber uses the "Fiber" concept to achieve a pause-resume functionality. This is worth reading up on, but not
-necessary to understand what we're doing here. In Dioxus, our DiffMachine is guided by DiffInstructions - essentially
-"commands" that guide the Diffing algorithm through the tree. Our "diff_scope" method is async - we can literally pause
-our DiffMachine "mid-sentence" (so to speak) by just stopping the poll on the future. The DiffMachine periodically yields
-so Rust's async machinery can take over, allowing us to customize when exactly to pause it.
-
-React's "should_yield" method is more complex than ours, and I assume we'll move in that direction as Dioxus matures. For
-now, Dioxus just assumes a TimeoutFuture, and selects! on both the Diff algorithm and timeout. If the DiffMachine finishes
-before the timeout, then Dioxus will work on any pending work in the interim. If there is no pending work, then the changes
-are committed, and coroutines are polled during the idle period. However, if the timeout expires, then the DiffMachine
-future is paused and saved (self-referentially).
-
-# Priority System
-
-So far, we've been able to thread our Dioxus work between animation frames - the main thread is not blocked! But that
-doesn't help us _under load_. How do we still stay snappy... even if we're doing a lot of work? Well, that's where
-priorities come into play. The goal with priorities is to schedule shorter work as a "high" priority and longer work as
-a "lower" priority. That way, we can interrupt long-running low-priority work with short-running high-priority work.
-
-React's priority system is quite complex.
-
-There are 5 levels of priority and 2 distinctions between UI events (discrete, continuous). I believe React really only
-uses 3 priority levels and "idle" priority isn't used... Regardless, there's some batching going on.
-
-For Dioxus, we're going with a 4 tier priority system:
-- Sync: Things that need to be done by the next frame, like TextInput on controlled elements
-- High: for events that block all others - clicks, keyboard, and hovers
-- Medium: for UI events caused by the user but not directly - scrolls/forms/focus (all other events)
-- Low: set_state called asynchronously, and anything generated by suspense
-
-In "Sync" state, we abort our "idle wait" future, and resolve the sync queue immediately and escape. Because we completed
-work before the next rAF, any edits can be immediately processed before the frame ends. Generally though, we want to leave
-as much time to rAF as possible. "Sync" is currently only used by onInput - we'll leave some docs telling people not to
-do anything too arduous from onInput.
-
-For the rest, we defer to the rIC period and work down each queue from high to low.
-*/
-use crate::heuristics::*;
-use crate::innerlude::*;
-use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};
-use futures_util::{pin_mut, stream::FuturesUnordered, Future, FutureExt, StreamExt};
-use fxhash::FxHashMap;
-use fxhash::FxHashSet;
-use indexmap::IndexSet;
-use slab::Slab;
-use std::{
-    any::{Any, TypeId},
-    cell::{Cell, UnsafeCell},
-    collections::{HashSet, VecDeque},
-    rc::Rc,
-};
-
-#[derive(Clone)]
-pub(crate) struct EventChannel {
-    pub task_counter: Rc<Cell<u64>>,
-    pub cur_subtree: Rc<Cell<u32>>,
-    pub sender: UnboundedSender<SchedulerMsg>,
-    pub schedule_any_immediate: Rc<dyn Fn(ScopeId)>,
-    pub submit_task: Rc<dyn Fn(FiberTask) -> TaskHandle>,
-    pub get_shared_context: GetSharedContext,
-}
-
-pub type GetSharedContext = Rc<dyn Fn(ScopeId, TypeId) -> Option<Rc<dyn Any>>>;
-
-pub enum SchedulerMsg {
-    // events from the host
-    UiEvent(UserEvent),
-
-    // setstate
-    Immediate(ScopeId),
-
-    // tasks
-    Task(TaskMsg),
-}
-
-pub enum TaskMsg {
-    ToggleTask(u64),
-    PauseTask(u64),
-    ResumeTask(u64),
-    DropTask(u64),
-}
-
-/// The scheduler holds basically everything around "working"
-///
-/// Each scope has the ability to lightly interact with the scheduler (IE, schedule an update) but ultimately the scheduler calls the components.
-///
-/// In Dioxus, the scheduler provides 4 priority levels - each with their own "DiffMachine". The DiffMachine state can be saved if the deadline runs
-/// out.
-///
-/// Saved DiffMachine state can be self-referential, so we need to be careful about how we save it. All self-referential data is a link between
-/// pending DiffInstructions, Mutations, and their underlying Scope. It's okay for us to be self-referential with this data, provided we don't priority
-/// task shift to a higher priority task that needs mutable access to the same scopes.
-///
-/// We can prevent this safety issue from occurring if we track which scopes are invalidated when starting a new task.
-///
-///
-pub(crate) struct Scheduler {
-    /// All mounted components are arena allocated to make additions, removals, and references easy to work with
-    /// A generational arena is used to re-use slots of deleted scopes without having to resize the underlying arena.
-    ///
-    /// This is wrapped in an UnsafeCell because we will need to get mutable access to unique values in unique bump arenas
-    /// and rusts's guarantees cannot prove that this is safe. We will need to maintain the safety guarantees manually.
-    pub pool: ResourcePool,
-
-    pub heuristics: HeuristicsEngine,
-
-    pub receiver: UnboundedReceiver<SchedulerMsg>,
-
-    // Garbage stored
-    pub pending_garbage: FxHashSet<ScopeId>,
-
-    // In-flight futures
-    pub async_tasks: FuturesUnordered<FiberTask>,
-
-    // // scheduler stuff
-    // pub current_priority: EventPriority,
-    pub ui_events: VecDeque<UserEvent>,
-
-    pub pending_immediates: VecDeque<ScopeId>,
-
-    pub pending_tasks: VecDeque<UserEvent>,
-
-    pub batched_events: VecDeque<UserEvent>,
-
-    pub garbage_scopes: HashSet<ScopeId>,
-
-    pub dirty_scopes: IndexSet<ScopeId>,
-    pub saved_state: Option<SavedDiffWork<'static>>,
-    pub in_progress: bool,
-}
-
-impl Scheduler {
-    pub(crate) fn new(
-        sender: UnboundedSender<SchedulerMsg>,
-        receiver: UnboundedReceiver<SchedulerMsg>,
-    ) -> Self {
-        /*
-        Preallocate 2000 elements and 100 scopes to avoid dynamic allocation.
-        Perhaps this should be configurable from some external config?
-        */
-        let components = Rc::new(UnsafeCell::new(Slab::with_capacity(100)));
-        let raw_elements = Rc::new(UnsafeCell::new(Slab::with_capacity(2000)));
-
-        let heuristics = HeuristicsEngine::new();
-
-        let task_counter = Rc::new(Cell::new(0));
-        let cur_subtree = Rc::new(Cell::new(0));
-
-        let channel = EventChannel {
-            cur_subtree,
-            task_counter: task_counter.clone(),
-            sender: sender.clone(),
-            schedule_any_immediate: {
-                let sender = sender.clone();
-                Rc::new(move |id| {
-                    //
-                    log::debug!("scheduling immediate! {:?}", id);
-                    sender.unbounded_send(SchedulerMsg::Immediate(id)).unwrap()
-                })
-            },
-            // todo: we want to get the futures out of the scheduler message
-            // the scheduler message should be send/sync
-            submit_task: {
-                Rc::new(move |fiber_task| {
-                    let task_id = task_counter.get();
-                    task_counter.set(task_id + 1);
-
-                    todo!();
-                    // sender
-                    //     .unbounded_send(SchedulerMsg::Task(TaskMsg::SubmitTask(
-                    //         fiber_task, task_id,
-                    //     )))
-                    //     .unwrap();
-                    TaskHandle {
-                        our_id: task_id,
-                        sender: sender.clone(),
-                    }
-                })
-            },
-            get_shared_context: {
-                let components = components.clone();
-                Rc::new(move |id, ty| {
-                    let components = unsafe { &*components.get() };
-                    let mut search: Option<&ScopeInner> = components.get(id.0);
-                    while let Some(inner) = search.take() {
-                        if let Some(shared) = inner.shared_contexts.borrow().get(&ty) {
-                            return Some(shared.clone());
-                        } else {
-                            search = inner.parent_idx.map(|id| components.get(id.0)).flatten();
-                        }
-                    }
-                    None
-                })
-            },
-        };
-
-        let pool = ResourcePool {
-            components,
-            raw_elements,
-            channel,
-        };
-
-        let async_tasks = FuturesUnordered::new();
-
-        // push a task that would never resolve - prevents us from immediately aborting the scheduler
-        async_tasks.push(Box::pin(async {
-            std::future::pending::<()>().await;
-            ScopeId(0)
-        }) as FiberTask);
-
-        let saved_state = SavedDiffWork {
-            mutations: Mutations::new(),
-            stack: DiffStack::new(),
-            seen_scopes: Default::default(),
-        };
-
-        Self {
-            pool,
-
-            receiver,
-
-            async_tasks,
-
-            pending_garbage: FxHashSet::default(),
-
-            heuristics,
-
-            ui_events: VecDeque::new(),
-
-            pending_immediates: VecDeque::new(),
-
-            pending_tasks: VecDeque::new(),
-
-            batched_events: VecDeque::new(),
-
-            garbage_scopes: HashSet::new(),
-
-            dirty_scopes: Default::default(),
-            saved_state: Some(saved_state),
-            in_progress: false,
-        }
-    }
-
-    // returns true if the event is discrete
-    pub fn handle_ui_event(&mut self, event: UserEvent) -> bool {
-        let (discrete, priority) = event_meta(&event);
-
-        if let Some(scope) = self.pool.get_scope_mut(event.scope) {
-            if let Some(element) = event.mounted_dom_id {
-                // TODO: bubble properly here
-                scope.call_listener(event, element);
-
-                while let Ok(Some(dirty_scope)) = self.receiver.try_next() {
-                    //
-                    //     self.add_dirty_scope(dirty_scope, trigger.priority)
-                }
-            }
-        }
-
-        // use EventPriority::*;
-
-        // match priority {
-        //     Immediate => todo!(),
-        //     High => todo!(),
-        //     Medium => todo!(),
-        //     Low => todo!(),
-        // }
-
-        discrete
-    }
-
-    fn prepare_work(&mut self) {
-        // while let Some(trigger) = self.ui_events.pop_back() {
-        //     if let Some(scope) = self.pool.get_scope_mut(trigger.scope) {}
-        // }
-    }
-
-    // nothing to do, no events on channels, no work
-    pub fn has_any_work(&self) -> bool {
-        !(self.dirty_scopes.is_empty() && self.ui_events.is_empty())
-    }
-
-    /// re-balance the work lanes, ensuring high-priority work properly bumps away low priority work
-    fn balance_lanes(&mut self) {}
-
-    fn save_work(&mut self, lane: SavedDiffWork) {
-        let saved: SavedDiffWork<'static> = unsafe { std::mem::transmute(lane) };
-        self.saved_state = Some(saved);
-    }
-
-    unsafe fn load_work(&mut self) -> SavedDiffWork<'static> {
-        self.saved_state.take().unwrap().extend()
-    }
-    pub fn handle_task(&mut self, evt: TaskMsg) {
-        //
-    }
-
-    pub fn handle_channel_msg(&mut self, msg: SchedulerMsg) {
-        match msg {
-            //
-            SchedulerMsg::Task(msg) => todo!(),
-
-            SchedulerMsg::Immediate(_) => todo!(),
-
-            SchedulerMsg::UiEvent(event) => {
-                //
-
-                let (discrete, priority) = event_meta(&event);
-
-                if let Some(scope) = self.pool.get_scope_mut(event.scope) {
-                    if let Some(element) = event.mounted_dom_id {
-                        // TODO: bubble properly here
-                        scope.call_listener(event, element);
-
-                        while let Ok(Some(dirty_scope)) = self.receiver.try_next() {
-                            //
-                            //     self.add_dirty_scope(dirty_scope, trigger.priority)
-                        }
-                    }
-                }
-
-                discrete;
-            }
-        }
-    }
-
-    /// Load the current lane, and work on it, periodically checking in if the deadline has been reached.
-    ///
-    /// Returns true if the lane is finished before the deadline could be met.
-    pub fn work_on_current_lane(
-        &mut self,
-        deadline_reached: impl FnMut() -> bool,
-        mutations: &mut Vec<Mutations>,
-    ) -> bool {
-        // Work through the current subtree, and commit the results when it finishes
-        // When the deadline expires, give back the work
-        let saved_state = unsafe { self.load_work() };
-
-        // We have to split away some parts of ourself - current lane is borrowed mutably
-        let shared = self.pool.clone();
-        let mut machine = unsafe { saved_state.promote(&shared) };
-
-        let mut ran_scopes = FxHashSet::default();
-
-        if machine.stack.is_empty() {
-            let shared = self.pool.clone();
-
-            self.dirty_scopes
-                .retain(|id| shared.get_scope(*id).is_some());
-            self.dirty_scopes.sort_by(|a, b| {
-                let h1 = shared.get_scope(*a).unwrap().height;
-                let h2 = shared.get_scope(*b).unwrap().height;
-                h1.cmp(&h2).reverse()
-            });
-
-            if let Some(scopeid) = self.dirty_scopes.pop() {
-                log::info!("handling dirty scope {:?}", scopeid);
-                if !ran_scopes.contains(&scopeid) {
-                    ran_scopes.insert(scopeid);
-                    log::debug!("about to run scope {:?}", scopeid);
-
-                    if let Some(component) = self.pool.get_scope_mut(scopeid) {
-                        if component.run_scope(&self.pool) {
-                            let (old, new) =
-                                (component.frames.wip_head(), component.frames.fin_head());
-                            // let (old, new) = (component.frames.wip_head(), component.frames.fin_head());
-                            machine.stack.scope_stack.push(scopeid);
-                            machine.stack.push(DiffInstruction::Diff { new, old });
-                        }
-                    }
-                }
-            }
-        }
-
-        let work_completed = machine.work(deadline_reached);
-
-        // log::debug!("raw edits {:?}", machine.mutations.edits);
-
-        let mut machine: DiffMachine<'static> = unsafe { std::mem::transmute(machine) };
-        // let mut saved = machine.save();
-
-        if work_completed {
-            for node in machine.seen_scopes.drain() {
-                // self.dirty_scopes.clear();
-                // self.ui_events.clear();
-                self.dirty_scopes.remove(&node);
-                // self.dirty_scopes.remove(&node);
-            }
-
-            let mut new_mutations = Mutations::new();
-
-            for edit in machine.mutations.edits.drain(..) {
-                new_mutations.edits.push(edit);
-            }
-
-            // for edit in saved.edits.drain(..) {
-            //     new_mutations.edits.push(edit);
-            // }
-
-            // std::mem::swap(&mut new_mutations, &mut saved.mutations);
-
-            mutations.push(new_mutations);
-
-            // log::debug!("saved edits {:?}", mutations);
-
-            let mut saved = machine.save();
-            self.save_work(saved);
-            true
-
-            // self.save_work(saved);
-            // false
-        } else {
-            false
-        }
-    }
-
-    /// The primary workhorse of the VirtualDOM.
-    ///
-    /// Uses some fairly complex logic to schedule what work should be produced.
-    ///
-    /// Returns a list of successful mutations.
-    pub fn work_with_deadline<'a>(
-        &'a mut self,
-        mut deadline: impl FnMut() -> bool,
-    ) -> Vec<Mutations<'a>> {
-        /*
-        Strategy:
-        - When called, check for any UI events that might've been received since the last frame.
-        - Dump all UI events into a "pending discrete" queue and a "pending continuous" queue.
-
-        - If there are any pending discrete events, then elevate our priority level. If our priority level is already "high,"
-            then we need to finish the high priority work first. If the current work is "low" then analyze what scopes
-            will be invalidated by this new work. If this interferes with any in-flight medium or low work, then we need
-            to bump the other work out of the way, or choose to process it so we don't have any conflicts.
-            'static components have a leg up here since their work can be re-used among multiple scopes.
-            "High priority" is only for blocking! Should only be used on "clicks"
-
-        - If there are no pending discrete events, then check for continuous events. These can be completely batched
-
-        - we batch completely until we run into a discrete event
-        - all continuous events are batched together
-        - so D C C C C C would be two separate events - D and C. IE onclick and onscroll
-        - D C C C C C C D C C C D would be D C D C D in 5 distinct phases.
-
-        - !listener bubbling is not currently implemented properly and will need to be implemented somehow in the future
-            - we need to keep track of element parents to be able to traverse properly
-
-
-        Open questions:
-        - what if we get two clicks from the component during the same slice?
-            - should we batch?
-            - react says no - they are continuous
-            - but if we received both - then we don't need to diff, do we? run as many as we can and then finally diff?
-        */
-        let mut committed_mutations = Vec::<Mutations<'static>>::new();
-
-        while self.has_any_work() {
-            while let Ok(Some(msg)) = self.receiver.try_next() {
-                match msg {
-                    SchedulerMsg::Task(t) => todo!(),
-                    SchedulerMsg::Immediate(im) => {
-                        self.dirty_scopes.insert(im);
-                    }
-                    SchedulerMsg::UiEvent(evt) => {
-                        self.ui_events.push_back(evt);
-                    }
-                }
-            }
-
-            // switch our priority, pop off any work
-            while let Some(event) = self.ui_events.pop_front() {
-                if let Some(scope) = self.pool.get_scope_mut(event.scope) {
-                    if let Some(element) = event.mounted_dom_id {
-                        log::info!("Calling listener {:?}, {:?}", event.scope, element);
-
-                        // TODO: bubble properly here
-                        scope.call_listener(event, element);
-
-                        while let Ok(Some(dirty_scope)) = self.receiver.try_next() {
-                            match dirty_scope {
-                                SchedulerMsg::Immediate(im) => {
-                                    self.dirty_scopes.insert(im);
-                                }
-                                SchedulerMsg::UiEvent(e) => self.ui_events.push_back(e),
-                                SchedulerMsg::Task(_) => todo!(),
-                            }
-                        }
-                    }
-                }
-            }
-
-            let work_complete = self.work_on_current_lane(&mut deadline, &mut committed_mutations);
-
-            if !work_complete {
-                return committed_mutations;
-            }
-        }
-
-        committed_mutations
-    }
-
-    /// Work the scheduler down, not polling any ongoing tasks.
-    ///
-    /// Will use the standard priority-based scheduling, batching, etc, but just won't interact with the async reactor.
-    pub fn work_sync<'a>(&'a mut self) -> Vec<Mutations<'a>> {
-        let mut committed_mutations = Vec::new();
-
-        while let Ok(Some(msg)) = self.receiver.try_next() {
-            self.handle_channel_msg(msg);
-        }
-
-        if !self.has_any_work() {
-            return committed_mutations;
-        }
-
-        while self.has_any_work() {
-            self.prepare_work();
-            self.work_on_current_lane(|| false, &mut committed_mutations);
-        }
-
-        committed_mutations
-    }
-
-    /// Restart the entire VirtualDOM from scratch, wiping away any old state and components.
-    ///
-    /// Typically used to kickstart the VirtualDOM after initialization.
-    pub fn rebuild(&mut self, base_scope: ScopeId) -> Mutations {
-        let mut shared = self.pool.clone();
-        let mut diff_machine = DiffMachine::new(Mutations::new(), &mut shared);
-
-        // TODO: drain any in-flight work
-        let cur_component = self
-            .pool
-            .get_scope_mut(base_scope)
-            .expect("The base scope should never be moved");
-
-        log::debug!("rebuild {:?}", base_scope);
-
-        // We run the component. If it succeeds, then we can diff it and add the changes to the dom.
-        if cur_component.run_scope(&self.pool) {
-            diff_machine
-                .stack
-                .create_node(cur_component.frames.fin_head(), MountType::Append);
-
-            diff_machine.stack.scope_stack.push(base_scope);
-
-            diff_machine.work(|| false);
-        } else {
-            // todo: should this be a hard error?
-            log::warn!(
-                "Component failed to run successfully during rebuild.
-                This does not result in a failed rebuild, but indicates a logic failure within your app."
-            );
-        }
-
-        unsafe { std::mem::transmute(diff_machine.mutations) }
-    }
-
-    pub fn hard_diff(&mut self, base_scope: ScopeId) -> Mutations {
-        let cur_component = self
-            .pool
-            .get_scope_mut(base_scope)
-            .expect("The base scope should never be moved");
-
-        log::debug!("hard diff {:?}", base_scope);
-
-        if cur_component.run_scope(&self.pool) {
-            let mut diff_machine = DiffMachine::new(Mutations::new(), &mut self.pool);
-            diff_machine.cfg.force_diff = true;
-            diff_machine.diff_scope(base_scope);
-            diff_machine.mutations
-        } else {
-            Mutations::new()
-        }
-    }
-}

+ 449 - 209
packages/core/src/scope.rs

@@ -1,14 +1,38 @@
 use crate::innerlude::*;
+
+use futures_channel::mpsc::UnboundedSender;
 use fxhash::FxHashMap;
+use smallvec::SmallVec;
 use std::{
     any::{Any, TypeId},
     cell::{Cell, RefCell},
     collections::HashMap,
     future::Future,
-    pin::Pin,
     rc::Rc,
 };
 
+use bumpalo::{boxed::Box as BumpBox, Bump};
+
+/// Components in Dioxus use the "Context" object to interact with their lifecycle.
+///
+/// This lets components access props, schedule updates, integrate hooks, and expose shared state.
+///
+/// For the most part, the only method you should be using regularly is `render`.
+///
+/// ## Example
+///
+/// ```ignore
+/// #[derive(Props)]
+/// struct ExampleProps {
+///     name: String
+/// }
+///
+/// fn Example(cx: Context, props: &ExampleProps) -> Element {
+///     cx.render(rsx!{ div {"Hello, {props.name}"} })
+/// }
+/// ```
+pub type Context<'a> = &'a Scope;
+
 /// Every component in Dioxus is represented by a `Scope`.
 ///
 /// Scopes contain the state for hooks, the component's props, and other lifecycle information.
@@ -18,17 +42,28 @@ use std::{
 ///
 /// We expose the `Scope` type so downstream users can traverse the Dioxus VirtualDOM for whatever
 /// use case they might have.
-pub struct ScopeInner {
-    // Book-keeping about our spot in the arena
-    pub(crate) parent_idx: Option<ScopeId>,
+pub struct Scope {
+    // safety:
+    //
+    // pointers to scopes are *always* valid since they are bump allocated and never freed until this scope is also freed
+    // this is just a bit of a hack to not need an Rc to the ScopeArena.
+    // todo: replace this will ScopeId and provide a connection to scope arena directly
+    pub(crate) parent_scope: Option<*mut Scope>,
+
     pub(crate) our_arena_idx: ScopeId,
+
     pub(crate) height: u32,
+
     pub(crate) subtree: Cell<u32>,
+
     pub(crate) is_subtree_root: Cell<bool>,
 
-    // Nodes
-    pub(crate) frames: ActiveFrame,
-    pub(crate) caller: *const dyn for<'b> Fn(&'b ScopeInner) -> Element<'b>,
+    pub(crate) generation: Cell<u32>,
+
+    // The double-buffering situation that we will use
+    pub(crate) frames: [BumpFrame; 2],
+
+    pub(crate) caller: *const dyn Fn(&Scope) -> Element,
 
     /*
     we care about:
@@ -36,9 +71,7 @@ pub struct ScopeInner {
     - borrowed props (and how to drop them when the parent is dropped)
     - suspended nodes (and how to call their callback when their associated tasks are complete)
     */
-    pub(crate) listeners: RefCell<Vec<*const Listener<'static>>>,
-    pub(crate) borrowed_props: RefCell<Vec<*const VComponent<'static>>>,
-    pub(crate) suspended_nodes: RefCell<FxHashMap<u64, *const VSuspended<'static>>>,
+    pub(crate) items: RefCell<SelfReferentialItems<'static>>,
 
     // State
     pub(crate) hooks: HookList,
@@ -46,34 +79,27 @@ pub struct ScopeInner {
     // todo: move this into a centralized place - is more memory efficient
     pub(crate) shared_contexts: RefCell<HashMap<TypeId, Rc<dyn Any>>>,
 
-    // whenever set_state is called, we fire off a message to the scheduler
-    // this closure _is_ the method called by schedule_update that marks this component as dirty
-    pub(crate) memoized_updater: Rc<dyn Fn()>,
-
-    pub(crate) shared: EventChannel,
+    pub(crate) sender: UnboundedSender<SchedulerMsg>,
 }
 
-/// Public interface for Scopes.
-impl ScopeInner {
-    /// Get the root VNode for this Scope.
-    ///
-    /// This VNode is the "entrypoint" VNode. If the component renders multiple nodes, then this VNode will be a fragment.
-    ///
-    /// # Example
-    /// ```rust
-    /// let mut dom = VirtualDom::new(|(cx, props)|cx.render(rsx!{ div {} }));
-    /// dom.rebuild();
-    ///
-    /// let base = dom.base_scope();
-    ///
-    /// if let VNode::VElement(node) = base.root_node() {
-    ///     assert_eq!(node.tag_name, "div");
-    /// }
-    /// ```
-    pub fn root_node(&self) -> &VNode {
-        self.frames.fin_head()
-    }
+pub struct SelfReferentialItems<'a> {
+    pub(crate) listeners: Vec<&'a Listener<'a>>,
+    pub(crate) borrowed_props: Vec<&'a VComponent<'a>>,
+    pub(crate) suspended_nodes: FxHashMap<u64, &'a VSuspended<'a>>,
+    pub(crate) tasks: Vec<BumpBox<'a, dyn Future<Output = ()>>>,
+    pub(crate) pending_effects: Vec<BumpBox<'a, dyn FnMut()>>,
+}
 
+/// A component's unique identifier.
+///
+/// `ScopeId` is a `usize` that is unique across the entire VirtualDOM - but not unique across time. If a component is
+/// unmounted, then the `ScopeId` will be reused for a new component.
+#[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.
     ///
     /// Each component has its own subtree ID - the root subtree has an ID of 0. This ID is used by the renderer to route
@@ -83,7 +109,7 @@ impl ScopeInner {
     /// # Example
     ///
     /// ```rust
-    /// let mut dom = VirtualDom::new(|(cx, props)|cx.render(rsx!{ div {} }));
+    /// let mut dom = VirtualDom::new(|cx, props|cx.render(rsx!{ div {} }));
     /// dom.rebuild();
     ///
     /// let base = dom.base_scope();
@@ -94,16 +120,6 @@ impl ScopeInner {
         self.subtree.get()
     }
 
-    pub(crate) fn new_subtree(&self) -> Option<u32> {
-        if self.is_subtree_root.get() {
-            None
-        } else {
-            let cur = self.shared.cur_subtree.get();
-            self.shared.cur_subtree.set(cur + 1);
-            Some(cur)
-        }
-    }
-
     /// Get the height of this Scope - IE the number of scopes above it.
     ///
     /// A Scope with a height of `0` is the root scope - there are no other scopes above it.
@@ -111,7 +127,7 @@ impl ScopeInner {
     /// # Example
     ///
     /// ```rust
-    /// let mut dom = VirtualDom::new(|(cx, props)|cx.render(rsx!{ div {} }));
+    /// let mut dom = VirtualDom::new(|cx, props|cx.render(rsx!{ div {} }));
     /// dom.rebuild();
     ///
     /// let base = dom.base_scope();
@@ -131,7 +147,7 @@ impl ScopeInner {
     /// # Example
     ///
     /// ```rust
-    /// let mut dom = VirtualDom::new(|(cx, props)|cx.render(rsx!{ div {} }));
+    /// let mut dom = VirtualDom::new(|cx, props|cx.render(rsx!{ div {} }));
     /// dom.rebuild();
     ///
     /// let base = dom.base_scope();
@@ -139,7 +155,10 @@ impl ScopeInner {
     /// assert_eq!(base.parent(), None);
     /// ```
     pub fn parent(&self) -> Option<ScopeId> {
-        self.parent_idx
+        match self.parent_scope {
+            Some(p) => Some(unsafe { &*p }.our_arena_idx),
+            None => None,
+        }
     }
 
     /// Get the ID of this Scope within this Dioxus VirtualDOM.
@@ -149,7 +168,7 @@ impl ScopeInner {
     /// # Example
     ///
     /// ```rust
-    /// let mut dom = VirtualDom::new(|(cx, props)|cx.render(rsx!{ div {} }));
+    /// let mut dom = VirtualDom::new(|cx, props|cx.render(rsx!{ div {} }));
     /// dom.rebuild();
     /// let base = dom.base_scope();
     ///
@@ -158,116 +177,290 @@ impl ScopeInner {
     pub fn scope_id(&self) -> ScopeId {
         self.our_arena_idx
     }
-}
 
-// The type of closure that wraps calling components
-/// The type of task that gets sent to the task scheduler
-/// Submitting a fiber task returns a handle to that task, which can be used to wake up suspended nodes
-pub type FiberTask = Pin<Box<dyn Future<Output = ScopeId>>>;
-
-/// Private interface for Scopes.
-impl ScopeInner {
-    // we are being created in the scope of an existing component (where the creator_node lifetime comes into play)
-    // we are going to break this lifetime by force in order to save it on ourselves.
-    // To make sure that the lifetime isn't truly broken, we receive a Weak RC so we can't keep it around after the parent dies.
-    // This should never happen, but is a good check to keep around
-    //
-    // Scopes cannot be made anywhere else except for this file
-    // Therefore, their lifetimes are connected exclusively to the virtual dom
-    pub(crate) fn new(
-        caller: &dyn for<'b> Fn(&'b ScopeInner) -> Element<'b>,
-        our_arena_idx: ScopeId,
-        parent_idx: Option<ScopeId>,
-        height: u32,
-        subtree: u32,
-        shared: EventChannel,
-    ) -> Self {
-        let schedule_any_update = shared.schedule_any_immediate.clone();
+    /// Create a subscription that schedules a future render for the reference component
+    ///
+    /// ## Notice: you should prefer using prepare_update and get_scope_id
+    pub fn schedule_update(&self) -> Rc<dyn Fn() + 'static> {
+        // pub fn schedule_update(&self) -> Rc<dyn Fn() + 'static> {
+        let chan = self.sender.clone();
+        let id = self.scope_id();
+        Rc::new(move || {
+            let _ = chan.unbounded_send(SchedulerMsg::Immediate(id));
+        })
+    }
+
+    /// Schedule an update for any component given its ScopeId.
+    ///
+    /// A component's ScopeId can be obtained from `use_hook` or the [`Context::scope_id`] method.
+    ///
+    /// This method should be used when you want to schedule an update for a component
+    pub fn schedule_update_any(&self) -> Rc<dyn Fn(ScopeId)> {
+        let chan = self.sender.clone();
+        Rc::new(move |id| {
+            let _ = chan.unbounded_send(SchedulerMsg::Immediate(id));
+        })
+    }
 
-        let memoized_updater = Rc::new(move || schedule_any_update(our_arena_idx));
+    /// 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 needs_update(&self) {
+        self.needs_update_any(self.scope_id())
+    }
 
-        let caller = caller as *const _;
+    /// 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 needs_update_any(&self, id: ScopeId) {
+        let _ = self.sender.unbounded_send(SchedulerMsg::Immediate(id));
+    }
 
-        // wipe away the associated lifetime - we are going to manually manage the one-way lifetime graph
-        let caller = unsafe { std::mem::transmute(caller) };
+    /// 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
+    }
 
-        Self {
-            memoized_updater,
-            shared,
-            caller,
-            parent_idx,
-            our_arena_idx,
-            height,
-            subtree: Cell::new(subtree),
-            is_subtree_root: Cell::new(false),
-
-            frames: ActiveFrame::new(),
-            hooks: Default::default(),
-            suspended_nodes: Default::default(),
-            shared_contexts: Default::default(),
-            listeners: Default::default(),
-            borrowed_props: Default::default(),
-        }
+    /// Take a lazy VNode structure and actually build it with the context of the VDom's efficient VNode allocator.
+    ///
+    /// This function consumes the context and absorb the lifetime, so these VNodes *must* be returned.
+    ///
+    /// ## Example
+    ///
+    /// ```ignore
+    /// fn Component(cx: Scope, props: &Props) -> Element {
+    ///     // Lazy assemble the VNode tree
+    ///     let lazy_nodes = rsx!("hello world");
+    ///
+    ///     // Actually build the tree and allocate it
+    ///     cx.render(lazy_tree)
+    /// }
+    ///```
+    pub fn render<'src>(&'src self, lazy_nodes: Option<LazyNodes<'src, '_>>) -> Option<NodeLink> {
+        let frame = self.wip_frame();
+        let bump = &frame.bump;
+        let factory = NodeFactory { bump };
+        let node = lazy_nodes.map(|f| f.call(factory))?;
+        let node = bump.alloc(node);
+
+        let node_ptr = node as *mut _;
+        let node_ptr = unsafe { std::mem::transmute(node_ptr) };
+
+        let link = NodeLink {
+            scope_id: Cell::new(Some(self.our_arena_idx)),
+            link_idx: Cell::new(0),
+            node: node_ptr,
+        };
+
+        Some(link)
     }
 
-    pub(crate) fn update_scope_dependencies(
-        &mut self,
-        caller: &dyn for<'b> Fn(&'b ScopeInner) -> Element<'b>,
-    ) {
-        log::debug!("Updating scope dependencies {:?}", self.our_arena_idx);
-        let caller = caller as *const _;
-        self.caller = unsafe { std::mem::transmute(caller) };
-    }
-
-    /// This method cleans up any references to data held within our hook list. This prevents mutable aliasing from
-    /// causing UB in our tree.
-    ///
-    /// This works by cleaning up our references from the bottom of the tree to the top. The directed graph of components
-    /// essentially forms a dependency tree that we can traverse from the bottom to the top. As we traverse, we remove
-    /// any possible references to the data in the hook list.
-    ///
-    /// References to hook data can only be stored in listeners and component props. During diffing, we make sure to log
-    /// all listeners and borrowed props so we can clear them here.
-    ///
-    /// 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(&mut self, pool: &ResourcePool) {
-        // 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)
-        // right now, we don't drop
-        self.borrowed_props
-            .get_mut()
-            .drain(..)
-            .map(|li| unsafe { &*li })
-            .for_each(|comp| {
-                // First drop the component's undropped references
-                let scope_id = comp
-                    .associated_scope
-                    .get()
-                    .expect("VComponents should be associated with a valid Scope");
-
-                if let Some(scope) = pool.get_scope_mut(scope_id) {
-                    scope.ensure_drop_safety(pool);
-
-                    let mut drop_props = comp.drop_props.borrow_mut().take().unwrap();
-                    drop_props();
+    /// Push an effect to be ran after the component has been successfully mounted to the dom
+    /// Returns the effect's position in the stack
+    pub fn push_effect<'src>(&'src self, effect: impl FnOnce() + 'src) -> usize {
+        // this is some tricker to get around not being able to actually call fnonces
+        let mut slot = Some(effect);
+        let fut: &mut dyn FnMut() = self.bump().alloc(move || slot.take().unwrap()());
+
+        // wrap it in a type that will actually drop the contents
+        let boxed_fut = unsafe { BumpBox::from_raw(fut) };
+
+        // erase the 'src lifetime for self-referential storage
+        let self_ref_fut = unsafe { std::mem::transmute(boxed_fut) };
+
+        let mut items = self.items.borrow_mut();
+        items.pending_effects.push(self_ref_fut);
+        items.pending_effects.len() - 1
+    }
+
+    /// Pushes the future onto the poll queue to be polled
+    /// The future is forcibly dropped if the component is not ready by the next render
+    pub fn push_task<'src>(&'src self, fut: impl Future<Output = ()> + 'src) -> usize {
+        // allocate the future
+        let fut: &mut dyn Future<Output = ()> = self.bump().alloc(fut);
+
+        // wrap it in a type that will actually drop the contents
+        let boxed_fut: BumpBox<dyn Future<Output = ()>> = unsafe { BumpBox::from_raw(fut) };
+
+        // erase the 'src lifetime for self-referential storage
+        let self_ref_fut = unsafe { std::mem::transmute(boxed_fut) };
+
+        let mut items = self.items.borrow_mut();
+        items.tasks.push(self_ref_fut);
+        items.tasks.len() - 1
+    }
+
+    /// This method enables the ability to expose state to children further down the VirtualDOM Tree.
+    ///
+    /// This is a "fundamental" operation and should only be called during initialization of a hook.
+    ///
+    /// For a hook that provides the same functionality, use `use_provide_state` and `use_consume_state` instead.
+    ///
+    /// When the component is dropped, so is the context. Be aware of this behavior when consuming
+    /// the context via Rc/Weak.
+    ///
+    /// # Example
+    ///
+    /// ```
+    /// struct SharedState(&'static str);
+    ///
+    /// static App: FC<()> = |cx, props|{
+    ///     cx.use_hook(|_| cx.provide_state(SharedState("world")), |_| {}, |_| {});
+    ///     rsx!(cx, Child {})
+    /// }
+    ///
+    /// static Child: FC<()> = |cx, props|{
+    ///     let state = cx.consume_state::<SharedState>();
+    ///     rsx!(cx, div { "hello {state.0}" })
+    /// }
+    /// ```
+    pub fn provide_state<T: 'static>(&self, value: T) {
+        self.shared_contexts
+            .borrow_mut()
+            .insert(TypeId::of::<T>(), Rc::new(value))
+            .map(|f| f.downcast::<T>().ok())
+            .flatten();
+    }
+
+    /// Try to retrieve a SharedState with type T from the any parent Scope.
+    pub fn consume_state<T: 'static>(&self) -> Option<Rc<T>> {
+        if let Some(shared) = self.shared_contexts.borrow().get(&TypeId::of::<T>()) {
+            Some(shared.clone().downcast::<T>().unwrap())
+        } else {
+            let mut search_parent = self.parent_scope;
+
+            while let Some(parent_ptr) = search_parent {
+                let parent = unsafe { &*parent_ptr };
+                if let Some(shared) = parent.shared_contexts.borrow().get(&TypeId::of::<T>()) {
+                    return Some(shared.clone().downcast::<T>().unwrap());
                 }
-            });
+                search_parent = parent.parent_scope;
+            }
+            None
+        }
+    }
+
+    /// Create a new subtree with this scope as the root of the subtree.
+    ///
+    /// Each component has its own subtree ID - the root subtree has an ID of 0. This ID is used by the renderer to route
+    /// the mutations to the correct window/portal/subtree.
+    ///
+    /// This method
+    ///
+    /// # Example
+    ///
+    /// ```rust
+    /// fn App(cx: Context, props: &()) -> Element {
+    ///     todo!();
+    ///     rsx!(cx, div { "Subtree {id}"})
+    /// };
+    /// ```
+    pub fn create_subtree(&self) -> Option<u32> {
+        if self.is_subtree_root.get() {
+            None
+        } else {
+            todo!()
+            // let cur = self.subtree().get();
+            // self.shared.cur_subtree.set(cur + 1);
+            // Some(cur)
+        }
+    }
+
+    /// Get the subtree ID that this scope belongs to.
+    ///
+    /// Each component has its own subtree ID - the root subtree has an ID of 0. This ID is used by the renderer to route
+    /// the mutations to the correct window/portal/subtree.
+    ///
+    /// # Example
+    ///
+    /// ```rust
+    /// fn App(cx: Context, props: &()) -> Element {
+    ///     let id = cx.get_current_subtree();
+    ///     rsx!(cx, div { "Subtree {id}"})
+    /// };
+    /// ```
+    pub fn get_current_subtree(&self) -> u32 {
+        self.subtree()
+    }
+
+    /// Store a value between renders
+    ///
+    /// This is *the* foundational hook for all other hooks.
+    ///
+    /// - Initializer: closure used to create the initial hook state
+    /// - Runner: closure used to output a value every time the hook is used
+    ///
+    /// To "cleanup" the hook, implement `Drop` on the stored hook value. Whenever the component is dropped, the hook
+    /// will be dropped as well.
+    ///
+    /// # Example
+    ///
+    /// ```ignore
+    /// // use_ref is the simplest way of storing a value between renders
+    /// fn use_ref<T: 'static>(initial_value: impl FnOnce() -> T) -> &RefCell<T> {
+    ///     use_hook(
+    ///         || Rc::new(RefCell::new(initial_value())),
+    ///         |state| state,
+    ///     )
+    /// }
+    /// ```
+    pub fn use_hook<'src, State: 'static, Output: 'src>(
+        &'src self,
+        initializer: impl FnOnce(usize) -> State,
+        runner: impl FnOnce(&'src mut State) -> Output,
+    ) -> Output {
+        if self.hooks.at_end() {
+            self.hooks.push_hook(initializer(self.hooks.len()));
+        }
+
+        const HOOK_ERR_MSG: &str = r###"
+Unable to retrieve the hook that was initialized at this index.
+Consult the `rules of hooks` to understand how to use hooks properly.
+
+You likely used the hook in a conditional. Hooks rely on consistent ordering between renders.
+Functions prefixed with "use" should never be called conditionally.
+"###;
+
+        runner(self.hooks.next::<State>().expect(HOOK_ERR_MSG))
+    }
+}
+
+// Important internal methods
+impl Scope {
+    /// The "work in progress frame" represents the frame that is currently being worked on.
+    pub(crate) fn wip_frame(&self) -> &BumpFrame {
+        match self.generation.get() & 1 == 0 {
+            true => &self.frames[0],
+            false => &self.frames[1],
+        }
+    }
+
+    pub(crate) fn fin_frame(&self) -> &BumpFrame {
+        match self.generation.get() & 1 == 1 {
+            true => &self.frames[0],
+            false => &self.frames[1],
+        }
+    }
 
-        // Now that all the references are gone, we can safely drop our own references in our listeners.
-        self.listeners
-            .get_mut()
-            .drain(..)
-            .map(|li| unsafe { &*li })
-            .for_each(|listener| drop(listener.callback.borrow_mut().take()));
+    pub unsafe fn reset_wip_frame(&self) {
+        // todo: unsafecell or something
+        let bump = self.wip_frame() as *const _ as *mut Bump;
+        let g = &mut *bump;
+        g.reset();
+    }
+
+    pub fn cycle_frame(&self) {
+        self.generation.set(self.generation.get() + 1);
     }
 
     /// A safe wrapper around calling listeners
-    pub(crate) fn call_listener(&mut self, event: UserEvent, element: ElementId) {
-        let listners = self.listeners.borrow_mut();
+    pub(crate) fn call_listener(&self, event: UserEvent, element: ElementId) {
+        let listners = &mut self.items.borrow_mut().listeners;
 
-        let raw_listener = listners.iter().find(|lis| {
-            let search = unsafe { &***lis };
+        let listener = listners.iter().find(|lis| {
+            let search = lis;
             if search.event == event.name {
                 let search_id = search.mounted_node.get();
                 search_id.map(|f| f == element).unwrap_or(false)
@@ -276,8 +469,7 @@ impl ScopeInner {
             }
         });
 
-        if let Some(raw_listener) = raw_listener {
-            let listener = unsafe { &**raw_listener };
+        if let Some(listener) = listener {
             let mut cb = listener.callback.borrow_mut();
             if let Some(cb) = cb.as_mut() {
                 (cb)(event.event);
@@ -287,89 +479,137 @@ impl ScopeInner {
         }
     }
 
-    /*
-    General strategy here is to load up the appropriate suspended task and then run it.
-    Suspended nodes cannot be called repeatedly.
-    */
+    // General strategy here is to load up the appropriate suspended task and then run it.
+    // Suspended nodes cannot be called repeatedly.
     pub(crate) fn call_suspended_node<'a>(&'a mut self, task_id: u64) {
-        let mut nodes = self.suspended_nodes.borrow_mut();
+        let mut nodes = &mut self.items.get_mut().suspended_nodes;
 
         if let Some(suspended) = nodes.remove(&task_id) {
             let sus: &'a VSuspended<'static> = unsafe { &*suspended };
             let sus: &'a VSuspended<'a> = unsafe { std::mem::transmute(sus) };
+            let mut boxed = sus.callback.borrow_mut().take().unwrap();
+            let new_node: Element = boxed();
+        }
+    }
 
-            let cx: SuspendedContext<'a> = SuspendedContext {
-                inner: Context { scope: self },
-            };
+    // run the list of effects
+    pub(crate) fn run_effects(&mut self) {
+        for mut effect in self.items.get_mut().pending_effects.drain(..) {
+            effect();
+        }
+    }
 
-            let mut cb = sus.callback.borrow_mut().take().unwrap();
+    pub fn root_node<'a>(&'a self) -> &'a VNode<'a> {
+        let node = *self.wip_frame().nodes.borrow().get(0).unwrap();
+        unsafe { std::mem::transmute(&*node) }
+    }
+}
 
-            let new_node: Element<'a> = (cb)(cx);
-        }
+pub(crate) struct BumpFrame {
+    pub bump: Bump,
+    pub nodes: RefCell<Vec<*const VNode<'static>>>,
+}
+impl BumpFrame {
+    pub fn new(capacity: usize) -> Self {
+        let bump = Bump::with_capacity(capacity);
+
+        let node = &*bump.alloc(VText {
+            text: "asd",
+            dom_id: Default::default(),
+            is_static: false,
+        });
+        let node = bump.alloc(VNode::Text(unsafe { std::mem::transmute(node) }));
+        let nodes = RefCell::new(vec![node as *const _]);
+        Self { bump, nodes }
     }
 
-    // run the list of effects
-    pub(crate) fn run_effects(&mut self, pool: &ResourcePool) {
-        todo!()
-        // let mut effects = self.frames.effects.borrow_mut();
-        // let mut effects = effects.drain(..).collect::<Vec<_>>();
+    pub fn allocated_bytes(&self) -> usize {
+        self.bump.allocated_bytes()
+    }
 
-        // for effect in effects {
-        //     let effect = unsafe { &*effect };
-        //     let effect = effect.as_ref();
+    pub fn assign_nodelink(&self, node: &NodeLink) {
+        let mut nodes = self.nodes.borrow_mut();
 
-        //     let mut effect = effect.borrow_mut();
-        //     let mut effect = effect.as_mut();
+        let len = nodes.len();
+        nodes.push(node.node);
 
-        //     effect.run(pool);
-        // }
+        node.link_idx.set(len);
     }
+}
 
-    /// Render this component.
-    ///
-    /// Returns true if the scope completed successfully and false if running failed (IE a None error was propagated).
-    pub(crate) fn run_scope<'sel>(&'sel mut self, pool: &ResourcePool) -> bool {
-        // Cycle to the next frame and then reset it
-        // This breaks any latent references, invalidating every pointer referencing into it.
-        // Remove all the outdated listeners
-        self.ensure_drop_safety(pool);
+/// An abstraction over internally stored data using a hook-based memory layout.
+///
+/// Hooks are allocated using Boxes and then our stored references are given out.
+///
+/// It's unsafe to "reset" the hooklist, but it is safe to add hooks into it.
+///
+/// Todo: this could use its very own bump arena, but that might be a tad overkill
+#[derive(Default)]
+pub(crate) struct HookList {
+    arena: Bump,
+    vals: RefCell<SmallVec<[*mut dyn Any; 5]>>,
+    idx: Cell<usize>,
+}
 
-        // Safety:
-        // - We dropped the listeners, so no more &mut T can be used while these are held
-        // - All children nodes that rely on &mut T are replaced with a new reference
-        unsafe { self.hooks.reset() };
+impl HookList {
+    pub fn new(capacity: usize) -> Self {
+        Self {
+            arena: Bump::with_capacity(capacity),
+            ..Default::default()
+        }
+    }
 
-        // Safety:
-        // - We've dropped all references to the wip bump frame
-        unsafe { self.frames.reset_wip_frame() };
+    pub(crate) fn next<T: 'static>(&self) -> Option<&mut T> {
+        self.vals.borrow().get(self.idx.get()).and_then(|inn| {
+            self.idx.set(self.idx.get() + 1);
+            let raw_box = unsafe { &mut **inn };
+            raw_box.downcast_mut::<T>()
+        })
+    }
 
-        // just forget about our suspended nodes while we're at it
-        self.suspended_nodes.get_mut().clear();
+    /// This resets the internal iterator count
+    /// It's okay that we've given out each hook, but now we have the opportunity to give it out again
+    /// Therefore, resetting is considered unsafe
+    ///
+    /// This should only be ran by Dioxus itself before "running scope".
+    /// Dioxus knows how to descend through the tree to prevent mutable aliasing.
+    pub(crate) unsafe fn reset(&self) {
+        self.idx.set(0);
+    }
 
-        // guarantee that we haven't screwed up - there should be no latent references anywhere
-        debug_assert!(self.listeners.borrow().is_empty());
-        debug_assert!(self.suspended_nodes.borrow().is_empty());
-        debug_assert!(self.borrowed_props.borrow().is_empty());
+    pub(crate) fn push_hook<T: 'static>(&self, new: T) {
+        let val = self.arena.alloc(new);
+        self.vals.borrow_mut().push(val)
+    }
 
-        log::debug!("Borrowed stuff is successfully cleared");
+    pub(crate) fn len(&self) -> usize {
+        self.vals.borrow().len()
+    }
 
-        // Cast the caller ptr from static to one with our own reference
-        let render: &dyn for<'b> Fn(&'b ScopeInner) -> Element<'b> = unsafe { &*self.caller };
+    pub(crate) fn cur_idx(&self) -> usize {
+        self.idx.get()
+    }
 
-        // Todo: see if we can add stronger guarantees around internal bookkeeping and failed component renders.
-        if let Some(builder) = render(self) {
-            let new_head = builder.into_vnode(NodeFactory {
-                bump: &self.frames.wip_frame().bump,
-            });
-            log::debug!("Render is successful");
+    pub(crate) fn at_end(&self) -> bool {
+        self.cur_idx() >= self.len()
+    }
 
-            // the user's component succeeded. We can safely cycle to the next frame
-            self.frames.wip_frame_mut().head_node = unsafe { std::mem::transmute(new_head) };
-            self.frames.cycle_frame();
+    pub fn clear(&mut self) {
+        self.vals.borrow_mut().drain(..).for_each(|state| {
+            let as_mut = unsafe { &mut *state };
+            let boxed = unsafe { bumpalo::boxed::Box::from_raw(as_mut) };
+            drop(boxed);
+        });
+    }
 
-            true
-        } else {
-            false
-        }
+    /// Get the ammount of memory a hooklist uses
+    /// Used in heuristics
+    pub fn get_hook_arena_size(&self) -> usize {
+        self.arena.allocated_bytes()
     }
 }
+
+#[test]
+fn sizeof() {
+    dbg!(std::mem::size_of::<Scope>());
+}

+ 361 - 0
packages/core/src/scopearena.rs

@@ -0,0 +1,361 @@
+use bumpalo::Bump;
+use futures_channel::mpsc::UnboundedSender;
+use fxhash::FxHashMap;
+use slab::Slab;
+use std::cell::{Cell, RefCell};
+
+use crate::innerlude::*;
+
+pub type FcSlot = *const ();
+
+pub struct Heuristic {
+    hook_arena_size: usize,
+    node_arena_size: usize,
+}
+
+// a slab-like arena with stable references even when new scopes are allocated
+// uses a bump arena as a backing
+//
+// has an internal heuristics engine to pre-allocate arenas to the right size
+pub(crate) struct ScopeArena {
+    bump: Bump,
+    scope_counter: Cell<usize>,
+    scopes: RefCell<FxHashMap<ScopeId, *mut Scope>>,
+    pub heuristics: RefCell<FxHashMap<FcSlot, Heuristic>>,
+    free_scopes: RefCell<Vec<*mut Scope>>,
+    nodes: RefCell<Slab<*const VNode<'static>>>,
+    pub(crate) sender: UnboundedSender<SchedulerMsg>,
+}
+
+impl ScopeArena {
+    pub fn new(sender: UnboundedSender<SchedulerMsg>) -> Self {
+        Self {
+            scope_counter: Cell::new(0),
+            bump: Bump::new(),
+            scopes: RefCell::new(FxHashMap::default()),
+            heuristics: RefCell::new(FxHashMap::default()),
+            free_scopes: RefCell::new(Vec::new()),
+            nodes: RefCell::new(Slab::new()),
+            sender,
+        }
+    }
+
+    pub fn get_scope(&self, id: &ScopeId) -> Option<&Scope> {
+        unsafe { self.scopes.borrow().get(id).map(|f| &**f) }
+    }
+
+    // this is unsafe
+    pub unsafe fn get_scope_raw(&self, id: &ScopeId) -> Option<*mut Scope> {
+        self.scopes.borrow().get(id).map(|f| *f)
+    }
+    // this is unsafe
+
+    pub unsafe fn get_scope_mut(&self, id: &ScopeId) -> Option<&mut Scope> {
+        self.scopes.borrow().get(id).map(|s| &mut **s)
+    }
+
+    pub fn new_with_key(
+        &self,
+        fc_ptr: *const (),
+        caller: *const dyn Fn(&Scope) -> Element,
+        parent_scope: Option<*mut Scope>,
+        height: u32,
+        subtree: u32,
+    ) -> ScopeId {
+        let new_scope_id = ScopeId(self.scope_counter.get());
+        self.scope_counter.set(self.scope_counter.get() + 1);
+
+        //
+        //
+        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;
+            scope.height = height;
+            scope.subtree = Cell::new(subtree);
+            scope.our_arena_idx = new_scope_id;
+
+            scope.frames[0].nodes.get_mut().push({
+                let vnode = scope.frames[0]
+                    .bump
+                    .alloc(VNode::Text(scope.frames[0].bump.alloc(VText {
+                        dom_id: Default::default(),
+                        is_static: false,
+                        text: "",
+                    })));
+                unsafe { std::mem::transmute(vnode as *mut VNode) }
+            });
+
+            scope.frames[1].nodes.get_mut().push({
+                let vnode = scope.frames[1]
+                    .bump
+                    .alloc(VNode::Text(scope.frames[1].bump.alloc(VText {
+                        dom_id: Default::default(),
+                        is_static: false,
+                        text: "",
+                    })));
+                unsafe { std::mem::transmute(vnode as *mut VNode) }
+            });
+
+            let r = self.scopes.borrow_mut().insert(new_scope_id, scope);
+
+            assert!(r.is_none());
+
+            new_scope_id
+        } else {
+            let (node_capacity, hook_capacity) = {
+                let heuristics = self.heuristics.borrow();
+                if let Some(heuristic) = heuristics.get(&fc_ptr) {
+                    (heuristic.node_arena_size, heuristic.hook_arena_size)
+                } else {
+                    (0, 0)
+                }
+            };
+
+            let mut frames = [BumpFrame::new(node_capacity), BumpFrame::new(node_capacity)];
+
+            frames[0].nodes.get_mut().push({
+                let vnode = frames[0]
+                    .bump
+                    .alloc(VNode::Text(frames[0].bump.alloc(VText {
+                        dom_id: Default::default(),
+                        is_static: false,
+                        text: "",
+                    })));
+                unsafe { std::mem::transmute(vnode as *mut VNode) }
+            });
+
+            frames[1].nodes.get_mut().push({
+                let vnode = frames[1]
+                    .bump
+                    .alloc(VNode::Text(frames[1].bump.alloc(VText {
+                        dom_id: Default::default(),
+                        is_static: false,
+                        text: "",
+                    })));
+                unsafe { std::mem::transmute(vnode as *mut VNode) }
+            });
+
+            let scope = self.bump.alloc(Scope {
+                sender: self.sender.clone(),
+                our_arena_idx: new_scope_id,
+                parent_scope,
+                height,
+                frames,
+                subtree: Cell::new(subtree),
+                is_subtree_root: Cell::new(false),
+
+                caller,
+                generation: 0.into(),
+
+                hooks: HookList::new(hook_capacity),
+                shared_contexts: Default::default(),
+
+                items: RefCell::new(SelfReferentialItems {
+                    listeners: Default::default(),
+                    borrowed_props: Default::default(),
+                    suspended_nodes: Default::default(),
+                    tasks: Default::default(),
+                    pending_effects: Default::default(),
+                }),
+            });
+
+            dbg!(self.scopes.borrow());
+
+            let r = self.scopes.borrow_mut().insert(new_scope_id, scope);
+
+            assert!(r.is_none());
+            // .expect(&format!("scope shouldnt exist, {:?}", new_scope_id));
+
+            new_scope_id
+        }
+    }
+
+    pub fn try_remove(&self, id: &ScopeId) -> Option<()> {
+        self.ensure_drop_safety(id);
+
+        log::debug!("removing scope {:?}", id);
+        println!("removing scope {:?}", id);
+
+        let scope = unsafe { &mut *self.scopes.borrow_mut().remove(&id).unwrap() };
+
+        // we're just reusing scopes so we need to clear it out
+        scope.hooks.clear();
+        scope.shared_contexts.get_mut().clear();
+        scope.parent_scope = None;
+        scope.generation.set(0);
+        scope.is_subtree_root.set(false);
+        scope.subtree.set(0);
+
+        scope.frames[0].nodes.get_mut().clear();
+        scope.frames[1].nodes.get_mut().clear();
+
+        scope.frames[0].bump.reset();
+        scope.frames[1].bump.reset();
+
+        let SelfReferentialItems {
+            borrowed_props,
+            listeners,
+            pending_effects,
+            suspended_nodes,
+            tasks,
+        } = scope.items.get_mut();
+
+        borrowed_props.clear();
+        listeners.clear();
+        pending_effects.clear();
+        suspended_nodes.clear();
+        tasks.clear();
+
+        self.free_scopes.borrow_mut().push(scope);
+
+        Some(())
+    }
+
+    pub fn reserve_node(&self, node: &VNode) -> ElementId {
+        let mut els = self.nodes.borrow_mut();
+        let entry = els.vacant_entry();
+        let key = entry.key();
+        let id = ElementId(key);
+        let node: *const VNode = node as *const _;
+        let node = unsafe { std::mem::transmute::<*const VNode, *const VNode>(node) };
+        entry.insert(node);
+        id
+    }
+
+    pub fn collect_garbage(&self, id: ElementId) {
+        self.nodes.borrow_mut().remove(id.0);
+    }
+
+    // These methods would normally exist on `scope` but they need access to *all* of the scopes
+
+    /// This method cleans up any references to data held within our hook list. This prevents mutable aliasing from
+    /// causing UB in our tree.
+    ///
+    /// This works by cleaning up our references from the bottom of the tree to the top. The directed graph of components
+    /// essentially forms a dependency tree that we can traverse from the bottom to the top. As we traverse, we remove
+    /// any possible references to the data in the hook list.
+    ///
+    /// References to hook data can only be stored in listeners and component props. During diffing, we make sure to log
+    /// all listeners and borrowed props so we can clear them here.
+    ///
+    /// 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();
+
+        // 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);
+
+            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()));
+    }
+
+    pub(crate) fn run_scope(&self, id: &ScopeId) -> bool {
+        let scope = unsafe { &mut *self.get_scope_mut(id).expect("could not find scope") };
+
+        log::debug!("found scope, about to run: {:?}", id);
+
+        // Cycle to the next frame and then reset it
+        // This breaks any latent references, invalidating every pointer referencing into it.
+        // Remove all the outdated listeners
+        self.ensure_drop_safety(id);
+
+        // Safety:
+        // - We dropped the listeners, so no more &mut T can be used while these are held
+        // - All children nodes that rely on &mut T are replaced with a new reference
+        unsafe { scope.hooks.reset() };
+
+        // Safety:
+        // - We've dropped all references to the wip bump frame with "ensure_drop_safety"
+        unsafe { scope.reset_wip_frame() };
+
+        {
+            let mut items = scope.items.borrow_mut();
+
+            // just forget about our suspended nodes while we're at it
+            items.suspended_nodes.clear();
+            items.tasks.clear();
+            items.pending_effects.clear();
+
+            // guarantee that we haven't screwed up - there should be no latent references anywhere
+            debug_assert!(items.listeners.is_empty());
+            debug_assert!(items.borrowed_props.is_empty());
+            debug_assert!(items.suspended_nodes.is_empty());
+            debug_assert!(items.tasks.is_empty());
+            debug_assert!(items.pending_effects.is_empty());
+
+            // Todo: see if we can add stronger guarantees around internal bookkeeping and failed component renders.
+            scope.wip_frame().nodes.borrow_mut().clear();
+        }
+
+        let render: &dyn Fn(&Scope) -> Element = unsafe { &*scope.caller };
+
+        if let Some(link) = render(scope) {
+            // right now, it's a panic to render a nodelink from another scope
+            // todo: enable this. it should (reasonably) work even if it doesnt make much sense
+            assert_eq!(link.scope_id.get(), Some(*id));
+
+            // nodelinks are not assigned when called and must be done so through the create/diff phase
+            // however, we need to link this one up since it will never be used in diffing
+            scope.wip_frame().assign_nodelink(&link);
+            debug_assert_eq!(scope.wip_frame().nodes.borrow().len(), 1);
+
+            if !scope.items.borrow().tasks.is_empty() {
+                // self.
+            }
+
+            // make the "wip frame" contents the "finished frame"
+            // any future dipping into completed nodes after "render" will go through "fin head"
+            scope.cycle_frame();
+            true
+        } else {
+            false
+        }
+    }
+
+    // The head of the bumpframe is the first linked NodeLink
+    pub fn wip_head(&self, id: &ScopeId) -> &VNode {
+        let scope = self.get_scope(id).unwrap();
+        let frame = scope.wip_frame();
+        let nodes = frame.nodes.borrow();
+        let node: &VNode = unsafe { &**nodes.get(0).unwrap() };
+        unsafe { std::mem::transmute::<&VNode, &VNode>(node) }
+    }
+
+    // The head of the bumpframe is the first linked NodeLink
+    pub fn fin_head(&self, id: &ScopeId) -> &VNode {
+        let scope = self.get_scope(id).unwrap();
+        let frame = scope.fin_frame();
+        let nodes = frame.nodes.borrow();
+        let node: &VNode = unsafe { &**nodes.get(0).unwrap() };
+        unsafe { std::mem::transmute::<&VNode, &VNode>(node) }
+    }
+
+    pub fn root_node(&self, id: &ScopeId) -> &VNode {
+        self.fin_head(id)
+    }
+}

+ 0 - 39
packages/core/src/tasks.rs

@@ -1,39 +0,0 @@
-use crate::innerlude::*;
-use futures_channel::mpsc::UnboundedSender;
-
-pub struct TaskHandle {
-    pub(crate) sender: UnboundedSender<SchedulerMsg>,
-    pub(crate) our_id: u64,
-}
-
-impl TaskHandle {
-    /// Toggles this coroutine off/on.
-    ///
-    /// This method is not synchronous - your task will not stop immediately.
-    pub fn toggle(&self) {
-        self.sender
-            .unbounded_send(SchedulerMsg::Task(TaskMsg::ToggleTask(self.our_id)))
-            .unwrap()
-    }
-
-    /// This method is not synchronous - your task will not stop immediately.
-    pub fn resume(&self) {
-        self.sender
-            .unbounded_send(SchedulerMsg::Task(TaskMsg::ResumeTask(self.our_id)))
-            .unwrap()
-    }
-
-    /// This method is not synchronous - your task will not stop immediately.
-    pub fn stop(&self) {
-        self.sender
-            .unbounded_send(SchedulerMsg::Task(TaskMsg::ToggleTask(self.our_id)))
-            .unwrap()
-    }
-
-    /// This method is not synchronous - your task will not stop immediately.
-    pub fn restart(&self) {
-        self.sender
-            .unbounded_send(SchedulerMsg::Task(TaskMsg::ToggleTask(self.our_id)))
-            .unwrap()
-    }
-}

+ 0 - 82
packages/core/src/test_dom.rs

@@ -1,82 +0,0 @@
-//! A DOM for testing - both internal and external code.
-use bumpalo::Bump;
-
-use crate::innerlude::*;
-use crate::nodes::IntoVNode;
-
-pub struct TestDom {
-    bump: Bump,
-    scheduler: Scheduler,
-}
-
-impl TestDom {
-    pub fn new() -> TestDom {
-        let bump = Bump::new();
-        let (sender, receiver) = futures_channel::mpsc::unbounded::<SchedulerMsg>();
-        let scheduler = Scheduler::new(sender, receiver);
-        TestDom { bump, scheduler }
-    }
-
-    pub fn new_factory(&self) -> NodeFactory {
-        NodeFactory::new(&self.bump)
-    }
-
-    pub fn render_direct<'a>(&'a self, lazy_nodes: Option<LazyNodes<'a, '_>>) -> VNode<'a> {
-        lazy_nodes.into_vnode(NodeFactory::new(&self.bump))
-    }
-
-    pub fn render<'a>(&'a self, lazy_nodes: Option<LazyNodes<'a, '_>>) -> &'a VNode<'a> {
-        self.bump
-            .alloc(lazy_nodes.into_vnode(NodeFactory::new(&self.bump)))
-    }
-
-    pub fn diff<'a>(&'a self, old: &'a VNode<'a>, new: &'a VNode<'a>) -> Mutations<'a> {
-        let mutations = Mutations::new();
-        let mut machine = DiffMachine::new(mutations, &self.scheduler.pool);
-        machine.stack.push(DiffInstruction::Diff { new, old });
-        machine.mutations
-    }
-
-    pub fn create<'a>(&'a self, left: Option<LazyNodes<'a, '_>>) -> Mutations<'a> {
-        let old = self.bump.alloc(self.render_direct(left));
-
-        let mut machine = DiffMachine::new(Mutations::new(), &self.scheduler.pool);
-
-        machine.stack.create_node(old, MountType::Append);
-
-        machine.work(&mut || false);
-
-        machine.mutations
-    }
-
-    pub fn lazy_diff<'a>(
-        &'a self,
-        left: Option<LazyNodes<'a, '_>>,
-        right: Option<LazyNodes<'a, '_>>,
-    ) -> (Mutations<'a>, Mutations<'a>) {
-        let (old, new) = (self.render(left), self.render(right));
-
-        let mut machine = DiffMachine::new(Mutations::new(), &self.scheduler.pool);
-
-        machine.stack.create_node(old, MountType::Append);
-
-        machine.work(|| false);
-        let create_edits = machine.mutations;
-
-        let mut machine = DiffMachine::new(Mutations::new(), &self.scheduler.pool);
-
-        machine.stack.push(DiffInstruction::Diff { old, new });
-
-        machine.work(&mut || false);
-
-        let edits = machine.mutations;
-
-        (create_edits, edits)
-    }
-}
-
-impl Default for TestDom {
-    fn default() -> Self {
-        Self::new()
-    }
-}

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

@@ -1,73 +0,0 @@
-//! A threadsafe wrapper for the VirtualDom
-//!
-//! This is an experimental module, and must be explicitly opted-into.
-//!
-//! It's not guaranteed that this module produces safe results, so use at your own peril.
-//!
-//! The only real "right" answer to a Send VirtualDom is by ensuring all hook data is Send
-//!
-//!
-use std::sync::{Arc, Mutex, MutexGuard};
-
-use crate::VirtualDom;
-
-/// A threadsafe wrapper for the Dioxus VirtualDom.
-///
-/// The Dioxus VirtualDom is not normally `Send` because user code can contain non-`Send` types. However, it is important
-/// to have a VirtualDom that is `Send` when used in server-side code since very few web frameworks support non-send
-/// handlers.
-///
-/// To address this, we have the `ThreadsafeVirtualDom` type which is a threadsafe wrapper for the VirtualDom. To access
-/// the VirtualDom, it must be first unlocked using the `lock` method. This locks the VirtualDom through a mutex and
-/// prevents any user code from leaking out. It is not possible to acquire any non-`Send` types from inside the VirtualDom.
-///
-/// The only way data may be accessed through the VirtualDom is from the "root props" method or by accessing a `Scope`
-/// directly. Even then, it's not possible to access any hook data. This means that non-Send types are only "in play"
-/// while the VirtualDom is locked with a non-Send marker.
-///
-/// Calling "wait for work" on the ThreadsafeVirtualDom does indeed work, because this method only accesses `Send` types.
-/// Otherwise, the VirtualDom must be unlocked on the current thread to modify any data.
-///
-/// Dioxus does have the concept of local tasks and non-local tasks.
-///
-/// For the ThreadsafeVirtualDom, non-Send tasks are not ran - and will error out during a Debug build if one is submitted.
-///
-///
-///
-/// When Tasks are submitted to a thread-local executor,
-///
-pub struct ThreadsafeVirtualDom {
-    inner: Arc<Mutex<VirtualDom>>,
-}
-
-impl ThreadsafeVirtualDom {
-    pub fn new(inner: VirtualDom) -> Self {
-        let inner = Arc::new(Mutex::new(inner));
-        Self { inner }
-    }
-
-    pub fn lock(&self) -> Option<VirtualDomGuard> {
-        let locked = self.inner.lock().unwrap();
-        Some(VirtualDomGuard { guard: locked })
-    }
-}
-
-unsafe impl Send for ThreadsafeVirtualDom {}
-
-pub struct VirtualDomGuard<'a> {
-    guard: MutexGuard<'a, VirtualDom>,
-}
-
-impl<'a> std::ops::Deref for VirtualDomGuard<'a> {
-    type Target = MutexGuard<'a, VirtualDom>;
-
-    fn deref(&self) -> &Self::Target {
-        &self.guard
-    }
-}
-
-impl<'a> std::ops::DerefMut for VirtualDomGuard<'a> {
-    fn deref_mut(&mut self) -> &mut Self::Target {
-        &mut self.guard
-    }
-}

+ 0 - 91
packages/core/src/util.rs

@@ -1,91 +0,0 @@
-use std::cell::Cell;
-use std::fmt::Display;
-
-// create a cell with a "none" value
-#[inline]
-pub fn empty_cell() -> Cell<Option<ElementId>> {
-    Cell::new(None)
-}
-
-pub fn type_name_of<T>(_: T) -> &'static str {
-    std::any::type_name::<T>()
-}
-
-use std::future::Future;
-use std::pin::Pin;
-use std::task::{Context, Poll};
-
-// use crate::task::{Context, Poll};
-
-/// Cooperatively gives up a timeslice to the task scheduler.
-///
-/// Calling this function will move the currently executing future to the back
-/// of the execution queue, making room for other futures to execute. This is
-/// especially useful after running CPU-intensive operations inside a future.
-///
-/// See also [`task::spawn_blocking`].
-///
-/// [`task::spawn_blocking`]: fn.spawn_blocking.html
-///
-/// # Examples
-///
-/// Basic usage:
-///
-/// ```
-/// # async_std::task::block_on(async {
-/// #
-/// use async_std::task;
-///
-/// task::yield_now().await;
-/// #
-/// # })
-/// ```
-#[inline]
-pub async fn yield_now() {
-    YieldNow(false).await
-}
-
-struct YieldNow(bool);
-
-impl Future for YieldNow {
-    type Output = ();
-
-    // The futures executor is implemented as a FIFO queue, so all this future
-    // does is re-schedule the future back to the end of the queue, giving room
-    // for other futures to progress.
-    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
-        if !self.0 {
-            self.0 = true;
-            cx.waker().wake_by_ref();
-            Poll::Pending
-        } else {
-            Poll::Ready(())
-        }
-    }
-}
-
-/// A component's unique identifier.
-///
-/// `ScopeId` is a `usize` that is unique across the entire VirtualDOM - but not unique across time. If a component is
-/// unmounted, then the `ScopeId` will be reused for a new component.
-#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
-#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
-pub struct ScopeId(pub usize);
-
-/// An Element's unique identifier.
-///
-/// `ElementId` is a `usize` that is unique across the entire VirtualDOM - but not unique across time. If a component is
-/// unmounted, then the `ElementId` will be reused for a new component.
-#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
-pub struct ElementId(pub usize);
-impl Display for ElementId {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(f, "{}", self.0)
-    }
-}
-
-impl ElementId {
-    pub fn as_u64(self) -> u64 {
-        self.0 as u64
-    }
-}

+ 494 - 197
packages/core/src/virtual_dom.rs

@@ -1,72 +1,127 @@
 //! # VirtualDOM Implementation for Rust
 //!
 //! This module provides the primary mechanics to create a hook-based, concurrent VDOM for Rust.
-//!
-//! In this file, multiple items are defined. This file is big, but should be documented well to
-//! navigate the inner workings of the Dom. We try to keep these main mechanics in this file to limit
-//! the possible exposed API surface (keep fields private). This particular implementation of VDOM
-//! is extremely efficient, but relies on some unsafety under the hood to do things like manage
-//! micro-heaps for components. We are currently working on refactoring the safety out into safe(r)
-//! abstractions, but current tests (MIRI and otherwise) show no issues with the current implementation.
-//!
-//! Included is:
-//! - The [`VirtualDom`] itself
-//! - The [`Scope`] object for managing component lifecycle
-//! - The [`ActiveFrame`] object for managing the Scope`s microheap
-//! - The [`Context`] object for exposing VirtualDOM API to components
-//! - The [`NodeFactory`] object for lazily exposing the `Context` API to the nodebuilder API
-//!
-//! This module includes just the barebones for a complete VirtualDOM API.
-//! Additional functionality is defined in the respective files.
-
-use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};
 
 use crate::innerlude::*;
-use std::{any::Any, rc::Rc};
-
-/// An integrated virtual node system that progresses events and diffs UI trees.
+use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};
+use futures_util::{Future, StreamExt};
+use fxhash::FxHashSet;
+use indexmap::IndexSet;
+use std::pin::Pin;
+use std::task::Poll;
+use std::{any::Any, collections::VecDeque};
+
+/// A virtual node system that progresses user events and diffs UI trees.
+///
+///
+/// ## Guide
+///
+/// Components are defined as simple functions that take [`Context`] and a [`Properties`] type and return an [`Element`].  
+///
+/// ```rust
+/// #[derive(Props, PartialEq)]
+/// struct AppProps {
+///     title: String
+/// }
+///
+/// fn App(cx: Context, props: &AppProps) -> Element {
+///     cx.render(rsx!(
+///         div {"hello, {props.title}"}
+///     ))
+/// }
+/// ```
+///
+/// Components may be composed to make complex apps.
+///
+/// ```rust
+/// fn App(cx: Context, props: &AppProps) -> Element {
+///     cx.render(rsx!(
+///         NavBar { routes: ROUTES }
+///         Title { "{props.title}" }
+///         Footer {}
+///     ))
+/// }
+/// ```
+///
+/// To start an app, create a [`VirtualDom`] and call [`VirtualDom::rebuild`] to get the list of edits required to
+/// draw the UI.
+///
+/// ```rust
+/// let mut vdom = VirtualDom::new(App);
+/// let edits = vdom.rebuild();
+/// ```
+///
+/// To inject UserEvents into the VirtualDom, call [`VirtualDom::get_scheduler_channel`] to get access to the scheduler.
+///
+/// ```rust
+/// let channel = vdom.get_scheduler_channel();
+/// channel.send_unbounded(SchedulerMsg::UserEvent(UserEvent {
+///     // ...
+/// }))
+/// ```
 ///
-/// Differences are converted into patches which a renderer can use to draw the UI.
+/// While waiting for UserEvents to occur, call [`VirtualDom::wait_for_work`] to poll any futures inside the VirtualDom.
 ///
-/// If you are building an App with Dioxus, you probably won't want to reach for this directly, instead opting to defer
-/// to a particular crate's wrapper over the [`VirtualDom`] API.
+/// ```rust
+/// vdom.wait_for_work().await;
+/// ```
+///
+/// Once work is ready, call [`VirtualDom::work_with_deadline`] to compute the differences between the previous and
+/// current UI trees. This will return a [`Mutations`] object that contains Edits, Effects, and NodeRefs that need to be
+/// handled by the renderer.
 ///
-/// Example
 /// ```rust
-/// static App: FC<()> = |(cx, props)|{
+/// let mutations = vdom.work_with_deadline(|| false);
+/// for edit in mutations {
+///     apply(edit);
+/// }
+/// ```
+///
+/// ## Building an event loop around Dioxus:
+///
+/// Putting everything together, you can build an event loop around Dioxus by using the methods outlined above.
+///
+/// ```rust
+/// fn App(cx: Context, props: &()) -> Element {
 ///     cx.render(rsx!{
-///         div {
-///             "Hello World"
-///         }
+///         div { "Hello World" }
 ///     })
 /// }
 ///
 /// async fn main() {
 ///     let mut dom = VirtualDom::new(App);
+///
 ///     let mut inital_edits = dom.rebuild();
-///     initialize_screen(inital_edits);
+///     apply_edits(inital_edits);
 ///
 ///     loop {
-///         let next_frame = TimeoutFuture::new(Duration::from_millis(16));
-///         let edits = dom.run_with_deadline(next_frame).await;
+///         dom.wait_for_work().await;
+///         let frame_timeout = TimeoutFuture::new(Duration::from_millis(16));
+///         let deadline = || (&mut frame_timeout).now_or_never();
+///         let edits = dom.run_with_deadline(deadline).await;
 ///         apply_edits(edits);
-///         render_frame();
 ///     }
 /// }
 /// ```
 pub struct VirtualDom {
-    scheduler: Scheduler,
-
     base_scope: ScopeId,
 
-    root_fc: Box<dyn Any>,
+    _root_caller: *mut dyn Fn(&Scope) -> Element,
+
+    scopes: Box<ScopeArena>,
+
+    receiver: UnboundedReceiver<SchedulerMsg>,
+
+    sender: UnboundedSender<SchedulerMsg>,
+
+    pending_futures: FxHashSet<ScopeId>,
 
-    root_props: Rc<dyn Any>,
+    pending_messages: VecDeque<SchedulerMsg>,
 
-    // we need to keep the allocation around, but we don't necessarily use it
-    _root_caller: Rc<dyn Any>,
+    dirty_scopes: IndexSet<ScopeId>,
 }
 
+// Methods to create the VirtualDom
 impl VirtualDom {
     /// Create a new VirtualDOM with a component that does not have special props.
     ///
@@ -80,7 +135,7 @@ impl VirtualDom {
     ///
     /// # Example
     /// ```
-    /// fn Example(cx: Context<()>) -> DomTree  {
+    /// fn Example(cx: Context, props: &()) -> Element  {
     ///     cx.render(rsx!( div { "hello world" } ))
     /// }
     ///
@@ -109,7 +164,7 @@ impl VirtualDom {
     ///     name: &'static str
     /// }
     ///
-    /// fn Example(cx: Context<SomeProps>) -> DomTree  {
+    /// fn Example(cx: Context, props: &SomeProps) -> Element  {
     ///     cx.render(rsx!{ div{ "hello {cx.name}" } })
     /// }
     ///
@@ -122,12 +177,12 @@ impl VirtualDom {
     /// let mut dom = VirtualDom::new_with_props(Example, SomeProps { name: "jane" });
     /// let mutations = dom.rebuild();
     /// ```
-    pub fn new_with_props<P: 'static + Send>(root: FC<P>, root_props: P) -> Self {
+    pub fn new_with_props<P: 'static>(root: FC<P>, root_props: P) -> Self {
         let (sender, receiver) = futures_channel::mpsc::unbounded::<SchedulerMsg>();
         Self::new_with_props_and_scheduler(root, root_props, sender, receiver)
     }
 
-    /// Launch the VirtualDom, but provide your own channel for receiving and sending messages into the scheduler.
+    /// Launch the VirtualDom, but provide your own channel for receiving and sending messages into the scheduler
     ///
     /// 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.
@@ -137,38 +192,26 @@ impl VirtualDom {
         sender: UnboundedSender<SchedulerMsg>,
         receiver: UnboundedReceiver<SchedulerMsg>,
     ) -> Self {
-        let root_fc = Box::new(root);
+        let scopes = ScopeArena::new(sender.clone());
 
-        let root_props: Rc<dyn Any> = Rc::new(root_props);
+        let caller = Box::new(move |scp: &Scope| -> Element { root(scp, &root_props) });
+        let caller_ref: *mut dyn Fn(&Scope) -> Element = Box::into_raw(caller);
+        let base_scope = scopes.new_with_key(root as _, caller_ref, None, 0, 0);
 
-        let props = root_props.clone();
-
-        let root_caller: Rc<dyn Fn(&ScopeInner) -> Element> = Rc::new(move |scope: &ScopeInner| {
-            let props = props.downcast_ref::<P>().unwrap();
-            let node = root((Context { scope }, props));
-            // cast into the right lifetime
-            unsafe { std::mem::transmute(node) }
-        });
-
-        let scheduler = Scheduler::new(sender, receiver);
-
-        let base_scope = scheduler.pool.insert_scope_with_key(|myidx| {
-            ScopeInner::new(
-                root_caller.as_ref(),
-                myidx,
-                None,
-                0,
-                0,
-                scheduler.pool.channel.clone(),
-            )
-        });
+        let pending_messages = VecDeque::new();
+        let mut dirty_scopes = IndexSet::new();
+        dirty_scopes.insert(base_scope);
 
         Self {
-            _root_caller: Rc::new(root_caller),
-            root_fc,
+            scopes: Box::new(scopes),
             base_scope,
-            scheduler,
-            root_props,
+            receiver,
+            // todo: clean this up manually?
+            _root_caller: caller_ref,
+            pending_messages,
+            pending_futures: Default::default(),
+            dirty_scopes,
+            sender,
         }
     }
 
@@ -176,64 +219,265 @@ impl VirtualDom {
     ///
     /// This is useful for traversing the tree from the root for heuristics or alternsative renderers that use Dioxus
     /// directly.
-    pub fn base_scope(&self) -> &ScopeInner {
-        self.scheduler.pool.get_scope(self.base_scope).unwrap()
+    ///
+    /// # Example
+    pub fn base_scope(&self) -> &Scope {
+        self.get_scope(&self.base_scope).unwrap()
     }
 
     /// Get the [`Scope`] for a component given its [`ScopeId`]
-    pub fn get_scope(&self, id: ScopeId) -> Option<&ScopeInner> {
-        self.scheduler.pool.get_scope(id)
+    ///
+    /// # Example
+    ///
+    ///
+    ///
+    pub fn get_scope<'a>(&'a self, id: &ScopeId) -> Option<&'a Scope> {
+        self.scopes.get_scope(id)
     }
 
-    /// Update the root props of this VirtualDOM.
+    /// Get an [`UnboundedSender`] handle to the channel used by the scheduler.
+    ///
+    /// # Example
+    ///
+    /// ```rust
     ///
-    /// This method returns None if the old props could not be removed. The entire VirtualDOM will be rebuilt immediately,
-    /// so calling this method will block the main thread until computation is done.
     ///
-    /// ## Example
+    /// ```
+    pub fn get_scheduler_channel(&self) -> futures_channel::mpsc::UnboundedSender<SchedulerMsg> {
+        self.sender.clone()
+    }
+
+    /// Check if the [`VirtualDom`] has any pending updates or work to be done.
+    ///
+    /// # Example
     ///
     /// ```rust
-    /// #[derive(Props, PartialEq)]
-    /// struct AppProps {
-    ///     route: &'static str
+    ///
+    ///
+    /// ```
+    pub fn has_any_work(&self) -> bool {
+        !(self.dirty_scopes.is_empty() && self.pending_messages.is_empty())
+    }
+
+    /// Waits for the scheduler to have work
+    /// This lets us poll async tasks during idle periods without blocking the main thread.
+    pub async fn wait_for_work(&mut self) {
+        // todo: poll the events once even if there is work to do to prevent starvation
+
+        // if there's no futures in the virtualdom, just wait for a scheduler message and put it into the queue to be processed
+        if self.pending_futures.is_empty() {
+            self.pending_messages
+                .push_front(self.receiver.next().await.unwrap());
+        } else {
+            struct PollTasks<'a> {
+                pending_futures: &'a FxHashSet<ScopeId>,
+                scopes: &'a ScopeArena,
+            }
+
+            impl<'a> Future for PollTasks<'a> {
+                type Output = ();
+
+                fn poll(
+                    self: Pin<&mut Self>,
+                    cx: &mut std::task::Context<'_>,
+                ) -> Poll<Self::Output> {
+                    let mut all_pending = true;
+
+                    // Poll every scope manually
+                    for fut in self.pending_futures.iter() {
+                        let scope = self
+                            .scopes
+                            .get_scope(fut)
+                            .expect("Scope should never be moved");
+
+                        let mut items = scope.items.borrow_mut();
+                        for task in items.tasks.iter_mut() {
+                            let task = task.as_mut();
+
+                            // 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 unpinned = unsafe { Pin::new_unchecked(task) };
+
+                            if unpinned.poll(cx).is_ready() {
+                                all_pending = false
+                            }
+                        }
+                    }
+
+                    // Resolve the future if any singular task is ready
+                    match all_pending {
+                        true => Poll::Pending,
+                        false => Poll::Ready(()),
+                    }
+                }
+            }
+
+            // Poll both the futures and the scheduler message queue simulataneously
+            use futures_util::future::{select, Either};
+
+            let scheduler_fut = self.receiver.next();
+            let tasks_fut = PollTasks {
+                pending_futures: &self.pending_futures,
+                scopes: &self.scopes,
+            };
+
+            match select(tasks_fut, scheduler_fut).await {
+                // Futures don't generate work
+                Either::Left((_, _)) => {}
+
+                // Save these messages in FIFO to be processed later
+                Either::Right((msg, _)) => self.pending_messages.push_front(msg.unwrap()),
+            }
+        }
+    }
+
+    /// Run the virtualdom with a deadline.
+    ///
+    /// This method will progress async tasks until the deadline is reached. If tasks are completed before the deadline,
+    /// and no tasks are pending, this method will return immediately. If tasks are still pending, then this method will
+    /// exhaust the deadline working on them.
+    ///
+    /// This method is useful when needing to schedule the virtualdom around other tasks on the main thread to prevent
+    /// "jank". It will try to finish whatever work it has by the deadline to free up time for other work.
+    ///
+    /// Due to platform differences in how time is handled, this method accepts a future that resolves when the deadline
+    /// is exceeded. However, the deadline won't be met precisely, so you might want to build some wiggle room into the
+    /// deadline closure manually.
+    ///
+    /// The deadline is polled before starting to diff components. This strikes a balance between the overhead of checking
+    /// the deadline and just completing the work. However, if an individual component takes more than 16ms to render, then
+    /// the screen will "jank" up. In debug, this will trigger an alert.
+    ///
+    /// If there are no in-flight fibers when this method is called, it will await any possible tasks, aborting early if
+    /// the provided deadline future resolves.
+    ///
+    /// For use in the web, it is expected that this method will be called to be executed during "idle times" and the
+    /// mutations to be applied during the "paint times" IE "animation frames". With this strategy, it is possible to craft
+    /// entirely jank-free applications that perform a ton of work.
+    ///
+    /// # Example
+    ///
+    /// ```no_run
+    /// fn App(cx: Context, props: &()) -> Element {
+    ///     cx.render(rsx!( div {"hello"} ))
     /// }
-    /// static App: FC<AppProps> = |(cx, props)|cx.render(rsx!{ "route is {cx.route}" });
     ///
-    /// let mut dom = VirtualDom::new_with_props(App, AppProps { route: "start" });
+    /// let mut dom = VirtualDom::new(App);
     ///
-    /// let mutations = dom.update_root_props(AppProps { route: "end" }).unwrap();
+    /// loop {
+    ///     let mut timeout = TimeoutFuture::from_ms(16);
+    ///     let deadline = move || (&mut timeout).now_or_never();
+    ///
+    ///     let mutations = dom.run_with_deadline(deadline).await;
+    ///
+    ///     apply_mutations(mutations);
+    /// }
     /// ```
-    pub fn update_root_props<P>(&mut self, root_props: P) -> Option<Mutations>
-    where
-        P: 'static,
-    {
-        let root_scope = self.scheduler.pool.get_scope_mut(self.base_scope).unwrap();
+    ///
+    /// ## Mutations
+    ///
+    /// This method returns "mutations" - IE the necessary changes to get the RealDOM to match the VirtualDOM. It also
+    /// includes a list of NodeRefs that need to be applied and effects that need to be triggered after the RealDOM has
+    /// applied the edits.
+    ///
+    /// Mutations are the only link between the RealDOM and the VirtualDOM.
+    ///
+    pub fn work_with_deadline(&mut self, mut deadline: impl FnMut() -> bool) -> Vec<Mutations> {
+        let mut committed_mutations = vec![];
 
-        // Pre-emptively drop any downstream references of the old props
-        root_scope.ensure_drop_safety(&self.scheduler.pool);
+        while self.has_any_work() {
+            while let Ok(Some(msg)) = self.receiver.try_next() {
+                self.pending_messages.push_front(msg);
+            }
 
-        let mut root_props: Rc<dyn Any> = Rc::new(root_props);
+            while let Some(msg) = self.pending_messages.pop_back() {
+                match msg {
+                    SchedulerMsg::Immediate(id) => {
+                        self.dirty_scopes.insert(id);
+                    }
+                    SchedulerMsg::UiEvent(event) => {
+                        if let Some(element) = event.mounted_dom_id {
+                            log::info!("Calling listener {:?}, {:?}", event.scope_id, element);
+
+                            if let Some(scope) = self.scopes.get_scope(&event.scope_id) {
+                                // TODO: bubble properly here
+                                scope.call_listener(event, element);
+
+                                while let Ok(Some(dirty_scope)) = self.receiver.try_next() {
+                                    self.pending_messages.push_front(dirty_scope);
+                                }
+                            }
+                        } else {
+                            log::debug!("User event without a targetted ElementId. Not currently supported.\nUnsure how to proceed. {:?}", event);
+                        }
+                    }
+                }
+            }
 
-        if let Some(props_ptr) = root_props.downcast_ref::<P>().map(|p| p as *const P) {
-            // Swap the old props and new props
-            std::mem::swap(&mut self.root_props, &mut root_props);
+            let scopes = &self.scopes;
+            let mut diff_state = DiffState::new(scopes);
 
-            let root = *self.root_fc.downcast_ref::<FC<P>>().unwrap();
+            let mut ran_scopes = FxHashSet::default();
 
-            let root_caller: Box<dyn Fn(&ScopeInner) -> Element> =
-                Box::new(move |scope: &ScopeInner| unsafe {
-                    let props: &'_ P = &*(props_ptr as *const P);
-                    std::mem::transmute(root((Context { scope }, props)))
-                });
+            // todo: the 2021 version of rust will let us not have to force the borrow
+            // let scopes = &self.scopes;
+            // Sort the scopes by height. Theoretically, we'll de-duplicate scopes by height
+            self.dirty_scopes
+                .retain(|id| scopes.get_scope(id).is_some());
 
-            root_scope.update_scope_dependencies(&root_caller);
+            self.dirty_scopes.sort_by(|a, b| {
+                let h1 = scopes.get_scope(a).unwrap().height;
+                let h2 = scopes.get_scope(b).unwrap().height;
+                h1.cmp(&h2).reverse()
+            });
 
-            drop(root_props);
+            if let Some(scopeid) = self.dirty_scopes.pop() {
+                log::info!("handling dirty scope {:?}", scopeid);
 
-            Some(self.rebuild())
-        } else {
-            None
+                if !ran_scopes.contains(&scopeid) {
+                    ran_scopes.insert(scopeid);
+
+                    log::debug!("about to run scope {:?}", scopeid);
+
+                    if self.scopes.run_scope(&scopeid) {
+                        let (old, new) = (
+                            self.scopes.wip_head(&scopeid),
+                            self.scopes.fin_head(&scopeid),
+                        );
+                        diff_state.stack.scope_stack.push(scopeid);
+                        diff_state.stack.push(DiffInstruction::Diff { new, old });
+                    }
+                }
+            }
+
+            let work_completed = diff_state.work(&mut deadline);
+
+            if work_completed {
+                let DiffState {
+                    mutations,
+                    seen_scopes,
+                    stack,
+                    ..
+                } = diff_state;
+
+                for scope in seen_scopes {
+                    self.dirty_scopes.remove(&scope);
+                }
+
+                // // I think the stack should be empty at the end of diffing?
+                // debug_assert_eq!(stack.scope_stack.len(), 1);
+
+                committed_mutations.push(mutations);
+            } else {
+                // leave the work in an incomplete state
+                log::debug!("don't have a mechanism to pause work (yet)");
+                return committed_mutations;
+            }
         }
+
+        committed_mutations
     }
 
     /// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom from scratch
@@ -247,14 +491,27 @@ impl VirtualDom {
     ///
     /// # Example
     /// ```
-    /// static App: FC<()> = |(cx, props)|cx.render(rsx!{ "hello world" });
+    /// static App: FC<()> = |cx, props| cx.render(rsx!{ "hello world" });
     /// let mut dom = VirtualDom::new();
     /// let edits = dom.rebuild();
     ///
     /// apply_edits(edits);
     /// ```
     pub fn rebuild(&mut self) -> Mutations {
-        self.scheduler.rebuild(self.base_scope)
+        let mut diff_state = DiffState::new(&self.scopes);
+
+        let scope_id = self.base_scope;
+        if self.scopes.run_scope(&scope_id) {
+            diff_state
+                .stack
+                .create_node(self.scopes.fin_head(&scope_id), MountType::Append);
+
+            diff_state.stack.scope_stack.push(scope_id);
+
+            diff_state.work(|| false);
+        }
+
+        diff_state.mutations
     }
 
     /// Compute a manual diff of the VirtualDOM between states.
@@ -272,7 +529,7 @@ impl VirtualDom {
     ///     value: Shared<&'static str>,
     /// }
     ///
-    /// static App: FC<AppProps> = |(cx, props)|{
+    /// static App: FC<AppProps> = |cx, props|{
     ///     let val = cx.value.borrow();
     ///     cx.render(rsx! { div { "{val}" } })
     /// };
@@ -291,107 +548,147 @@ impl VirtualDom {
     ///
     /// let edits = dom.diff();
     /// ```
-    pub fn diff(&mut self) -> Mutations {
-        self.scheduler.hard_diff(self.base_scope)
+    pub fn hard_diff<'a>(&'a mut self, scope_id: &ScopeId) -> Option<Mutations<'a>> {
+        let mut diff_machine = DiffState::new(&self.scopes);
+        if self.scopes.run_scope(scope_id) {
+            diff_machine.force_diff = true;
+            diff_machine.diff_scope(scope_id);
+        }
+        Some(diff_machine.mutations)
     }
 
-    /// Runs the virtualdom immediately, not waiting for any suspended nodes to complete.
+    /// Renders an `rsx` call into the Base Scope's allocator.
     ///
-    /// This method will not wait for any suspended nodes to complete. If there is no pending work, then this method will
-    /// return "None"
-    pub fn run_immediate(&mut self) -> Option<Vec<Mutations>> {
-        if self.scheduler.has_any_work() {
-            Some(self.scheduler.work_sync())
-        } else {
-            None
-        }
+    /// Useful when needing to render nodes from outside the VirtualDom, such as in a test.
+    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();
+        let factory = NodeFactory { bump: &frame.bump };
+        let node = lazy_nodes.unwrap().call(factory);
+        frame.bump.alloc(node)
     }
 
-    /// Run the virtualdom with a deadline.
+    /// Renders an `rsx` call into the Base Scope's allocator.
     ///
-    /// This method will progress async tasks until the deadline is reached. If tasks are completed before the deadline,
-    /// and no tasks are pending, this method will return immediately. If tasks are still pending, then this method will
-    /// exhaust the deadline working on them.
-    ///
-    /// This method is useful when needing to schedule the virtualdom around other tasks on the main thread to prevent
-    /// "jank". It will try to finish whatever work it has by the deadline to free up time for other work.
-    ///
-    /// Due to platform differences in how time is handled, this method accepts a future that resolves when the deadline
-    /// is exceeded. However, the deadline won't be met precisely, so you might want to build some wiggle room into the
-    /// deadline closure manually.
-    ///
-    /// The deadline is polled before starting to diff components. This strikes a balance between the overhead of checking
-    /// the deadline and just completing the work. However, if an individual component takes more than 16ms to render, then
-    /// the screen will "jank" up. In debug, this will trigger an alert.
-    ///
-    /// If there are no in-flight fibers when this method is called, it will await any possible tasks, aborting early if
-    /// the provided deadline future resolves.
-    ///
-    /// For use in the web, it is expected that this method will be called to be executed during "idle times" and the
-    /// mutations to be applied during the "paint times" IE "animation frames". With this strategy, it is possible to craft
-    /// entirely jank-free applications that perform a ton of work.
-    ///
-    /// # Example
-    ///
-    /// ```no_run
-    /// static App: FC<()> = |(cx, props)|rsx!(cx, div {"hello"} );
-    /// let mut dom = VirtualDom::new(App);
-    /// loop {
-    ///     let deadline = TimeoutFuture::from_ms(16);
-    ///     let mutations = dom.run_with_deadline(deadline).await;
-    ///     apply_mutations(mutations);
-    /// }
-    /// ```
-    ///
-    /// ## Mutations
-    ///
-    /// This method returns "mutations" - IE the necessary changes to get the RealDOM to match the VirtualDOM. It also
-    /// includes a list of NodeRefs that need to be applied and effects that need to be triggered after the RealDOM has
-    /// applied the edits.
+    /// Useful when needing to render nodes from outside the VirtualDom, such as in a test.    
+    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 });
+        machine.stack.scope_stack.push(self.base_scope);
+        machine.work(|| false);
+        machine.mutations
+    }
+
+    /// Renders an `rsx` call into the Base Scope's allocator.
     ///
-    /// Mutations are the only link between the RealDOM and the VirtualDOM.
-    pub fn run_with_deadline(&mut self, deadline: impl FnMut() -> bool) -> Vec<Mutations<'_>> {
-        self.scheduler.work_with_deadline(deadline)
+    /// Useful when needing to render nodes from outside the VirtualDom, such as in a test.
+    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);
+        machine.stack.create_node(nodes, MountType::Append);
+        machine.work(|| false);
+        machine.mutations
     }
 
-    pub fn get_event_sender(&self) -> futures_channel::mpsc::UnboundedSender<SchedulerMsg> {
-        self.scheduler.pool.channel.sender.clone()
+    /// 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.
+    pub fn diff_lazynodes<'a>(
+        &'a self,
+        left: Option<LazyNodes<'a, '_>>,
+        right: Option<LazyNodes<'a, '_>>,
+    ) -> (Mutations<'a>, Mutations<'a>) {
+        let (old, new) = (self.render_vnodes(left), self.render_vnodes(right));
+
+        let mut create = DiffState::new(&self.scopes);
+        create.stack.scope_stack.push(self.base_scope);
+        create.stack.create_node(old, MountType::Append);
+        create.work(|| false);
+
+        let mut edit = DiffState::new(&self.scopes);
+        create.stack.scope_stack.push(self.base_scope);
+        edit.stack.push(DiffInstruction::Diff { old, new });
+        edit.work(&mut || false);
+
+        (create.mutations, edit.mutations)
     }
+}
 
-    /// Waits for the scheduler to have work
-    /// This lets us poll async tasks during idle periods without blocking the main thread.
-    pub async fn wait_for_work(&mut self) {
-        // todo: poll the events once even if there is work to do to prevent starvation
-        if self.scheduler.has_any_work() {
-            return;
-        }
+pub enum SchedulerMsg {
+    // events from the host
+    UiEvent(UserEvent),
 
-        use futures_util::StreamExt;
+    // setstate
+    Immediate(ScopeId),
+}
 
-        // Wait for any new events if we have nothing to do
+#[derive(Debug)]
+pub struct UserEvent {
+    /// The originator of the event trigger
+    pub scope_id: ScopeId,
 
-        let tasks_fut = self.scheduler.async_tasks.next();
-        let scheduler_fut = self.scheduler.receiver.next();
+    pub priority: EventPriority,
 
-        use futures_util::future::{select, Either};
-        match select(tasks_fut, scheduler_fut).await {
-            // poll the internal futures
-            Either::Left((_id, _)) => {
-                //
-            }
+    /// The optional real node associated with the trigger
+    pub mounted_dom_id: Option<ElementId>,
 
-            // wait for an external event
-            Either::Right((msg, _)) => match msg.unwrap() {
-                SchedulerMsg::Task(t) => {
-                    self.scheduler.handle_task(t);
-                }
-                SchedulerMsg::Immediate(im) => {
-                    self.scheduler.dirty_scopes.insert(im);
-                }
-                SchedulerMsg::UiEvent(evt) => {
-                    self.scheduler.ui_events.push_back(evt);
-                }
-            },
-        }
-    }
+    /// The event type IE "onclick" or "onmouseover"
+    ///
+    /// The name that the renderer will use to mount the listener.
+    pub name: &'static str,
+
+    /// Event Data
+    pub event: Box<dyn Any + Send>,
+}
+
+/// Priority of Event Triggers.
+///
+/// Internally, Dioxus will abort work that's taking too long if new, more important work arrives. Unlike React, Dioxus
+/// won't be afraid to pause work or flush changes to the RealDOM. This is called "cooperative scheduling". Some Renderers
+/// implement this form of scheduling internally, however Dioxus will perform its own scheduling as well.
+///
+/// The ultimate goal of the scheduler is to manage latency of changes, prioritizing "flashier" changes over "subtler" changes.
+///
+/// React has a 5-tier priority system. However, they break things into "Continuous" and "Discrete" priority. For now,
+/// we keep it simple, and just use a 3-tier priority system.
+///
+/// - NoPriority = 0
+/// - LowPriority = 1
+/// - NormalPriority = 2
+/// - UserBlocking = 3
+/// - HighPriority = 4
+/// - ImmediatePriority = 5
+///
+/// We still have a concept of discrete vs continuous though - discrete events won't be batched, but continuous events will.
+/// This means that multiple "scroll" events will be processed in a single frame, but multiple "click" events will be
+/// flushed before proceeding. Multiple discrete events is highly unlikely, though.
+#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord)]
+pub enum EventPriority {
+    /// Work that must be completed during the EventHandler phase.
+    ///
+    /// Currently this is reserved for controlled inputs.
+    Immediate = 3,
+
+    /// "High Priority" work will not interrupt other high priority work, but will interrupt medium and low priority work.
+    ///
+    /// This is typically reserved for things like user interaction.
+    ///
+    /// React calls these "discrete" events, but with an extra category of "user-blocking" (Immediate).
+    High = 2,
+
+    /// "Medium priority" work is generated by page events not triggered by the user. These types of events are less important
+    /// than "High Priority" events and will take precedence over low priority events.
+    ///
+    /// This is typically reserved for VirtualEvents that are not related to keyboard or mouse input.
+    ///
+    /// React calls these "continuous" events (e.g. mouse move, mouse wheel, touch move, etc).
+    Medium = 1,
+
+    /// "Low Priority" work will always be preempted unless the work is significantly delayed, in which case it will be
+    /// advanced to the front of the work queue until completed.
+    ///
+    /// The primary user of Low Priority work is the asynchronous work system (Suspense).
+    ///
+    /// This is considered "idle" work or "background" work.
+    Low = 0,
 }

+ 4 - 4
packages/core/tests/borrowedstate.rs

@@ -10,8 +10,8 @@ fn test_borrowed_state() {
     let _ = VirtualDom::new(Parent);
 }
 
-fn Parent((cx, _): Scope<()>) -> 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 {
@@ -28,7 +28,7 @@ struct ChildProps<'a> {
     name: &'a str,
 }
 
-fn Child<'a>((cx, props): Scope<'a, ChildProps>) -> Element<'a> {
+fn Child(cx: Context, props: &ChildProps) -> Element {
     cx.render(rsx! {
         div {
             h1 { "it's nested" }
@@ -42,7 +42,7 @@ struct Grandchild<'a> {
     name: &'a str,
 }
 
-fn Child2<'a>((cx, props): Scope<'a, Grandchild>) -> Element<'a> {
+fn Child2(cx: Context, props: &Grandchild) -> Element {
     cx.render(rsx! {
         div { "Hello {props.name}!" }
     })

+ 14 - 31
packages/core/tests/create_dom.rs

@@ -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: FC<()> = |cx, props| {
         cx.render(rsx! {
             div {
                 div {
@@ -57,17 +57,17 @@ fn test_original_diff() {
 
 #[test]
 fn create() {
-    static APP: FC<()> = |(cx, props)| {
+    static APP: FC<()> = |cx, props| {
         cx.render(rsx! {
             div {
                 div {
                     "Hello, world!"
                     div {
                         div {
-                            // Fragment {
-                            //     "hello"
-                            //     "world"
-                            // }
+                            Fragment {
+                                "hello"
+                                "world"
+                            }
                         }
                     }
                 }
@@ -120,7 +120,7 @@ fn create() {
 
 #[test]
 fn create_list() {
-    static APP: FC<()> = |(cx, props)| {
+    static APP: FC<()> = |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: FC<()> = |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: FC<()> = |cx, props| {
         cx.render(rsx! {
             Child { "abc1" }
             Child { "abc2" }
@@ -215,12 +215,12 @@ fn create_components() {
         })
     };
 
-    #[derive(Props)]
-    struct ChildProps<'a> {
-        children: ScopeChildren<'a>,
+    #[derive(Props, PartialEq)]
+    struct ChildProps {
+        children: Element,
     }
 
-    fn Child<'a>((cx, props): Scope<'a, ChildProps<'a>>) -> Element {
+    fn Child(cx: Context, props: &ChildProps) -> Element {
         cx.render(rsx! {
             h1 {}
             div { {&props.children} }
@@ -273,7 +273,7 @@ fn create_components() {
 }
 #[test]
 fn anchors() {
-    static App: FC<()> = |(cx, props)| {
+    static App: FC<()> = |cx, props| {
         cx.render(rsx! {
             {true.then(|| rsx!{ div { "hello" } })}
             {false.then(|| rsx!{ div { "goodbye" } })}
@@ -299,20 +299,3 @@ fn anchors() {
         ]
     );
 }
-
-#[test]
-fn suspended() {
-    static App: FC<()> = |(cx, props)| {
-        let val = use_suspense(cx, || async {}, |cx, p| todo!());
-
-        cx.render(rsx! { {val} })
-    };
-
-    let mut dom = new_dom(App, ());
-    let mutations = dom.rebuild();
-
-    assert_eq!(
-        mutations.edits,
-        [CreatePlaceholder { root: 0 }, AppendChildren { many: 1 },]
-    );
-}

+ 28 - 47
packages/core/tests/diffing.rs

@@ -5,17 +5,17 @@
 //!
 //! It does not validated that component lifecycles work properly. This is done in another test file.
 
-use dioxus::{nodes::VSuspended, prelude::*, DomEdit, TestDom};
+use dioxus::{prelude::*, DomEdit, VSuspended};
 use dioxus_core as dioxus;
 use dioxus_core_macro::*;
 use dioxus_html as dioxus_elements;
 
 mod test_logging;
 
-fn new_dom() -> TestDom {
+fn new_dom() -> VirtualDom {
     const IS_LOGGING_ENABLED: bool = false;
     test_logging::set_up_logging(IS_LOGGING_ENABLED);
-    TestDom::new()
+    VirtualDom::new(|cx, props| todo!())
 }
 
 use DomEdit::*;
@@ -24,7 +24,7 @@ use DomEdit::*;
 #[test]
 fn html_and_rsx_generate_the_same_output() {
     let dom = new_dom();
-    let (create, change) = dom.lazy_diff(
+    let (create, change) = dom.diff_lazynodes(
         rsx! ( div { "Hello world" } ),
         rsx! ( div { "Goodbye world" } ),
     );
@@ -58,7 +58,7 @@ fn html_and_rsx_generate_the_same_output() {
 fn fragments_create_properly() {
     let dom = new_dom();
 
-    let create = dom.create(rsx! {
+    let create = dom.create_vnodes(rsx! {
         div { "Hello a" }
         div { "Hello b" }
         div { "Hello c" }
@@ -107,7 +107,7 @@ fn empty_fragments_create_anchors() {
     let left = rsx!({ (0..0).map(|_f| rsx! { div {}}) });
     let right = rsx!({ (0..1).map(|_f| rsx! { div {}}) });
 
-    let (create, change) = dom.lazy_diff(left, right);
+    let (create, change) = dom.diff_lazynodes(left, right);
 
     assert_eq!(
         create.edits,
@@ -133,7 +133,7 @@ fn empty_fragments_create_many_anchors() {
     let left = rsx!({ (0..0).map(|_f| rsx! { div {}}) });
     let right = rsx!({ (0..5).map(|_f| rsx! { div {}}) });
 
-    let (create, change) = dom.lazy_diff(left, right);
+    let (create, change) = dom.diff_lazynodes(left, right);
     assert_eq!(
         create.edits,
         [CreatePlaceholder { root: 0 }, AppendChildren { many: 1 }]
@@ -179,7 +179,7 @@ fn empty_fragments_create_anchors_with_many_children() {
         })
     });
 
-    let (create, change) = dom.lazy_diff(left, right);
+    let (create, change) = dom.diff_lazynodes(left, right);
     assert_eq!(
         create.edits,
         [CreatePlaceholder { root: 0 }, AppendChildren { many: 1 }]
@@ -231,7 +231,7 @@ fn many_items_become_fragment() {
     });
     let right = rsx!({ (0..0).map(|_| rsx! { div {} }) });
 
-    let (create, change) = dom.lazy_diff(left, right);
+    let (create, change) = dom.diff_lazynodes(left, right);
     assert_eq!(
         create.edits,
         [
@@ -257,12 +257,12 @@ fn many_items_become_fragment() {
         ]
     );
 
-    // hmmmmmmmmm worried about reusing IDs that we shouldnt be
+    // note: the ID gets reused
     assert_eq!(
         change.edits,
         [
             Remove { root: 2 },
-            CreatePlaceholder { root: 4 },
+            CreatePlaceholder { root: 3 },
             ReplaceWith { root: 0, m: 1 },
         ]
     );
@@ -284,7 +284,7 @@ fn two_equal_fragments_are_equal() {
         })
     });
 
-    let (_create, change) = dom.lazy_diff(left, right);
+    let (_create, change) = dom.diff_lazynodes(left, right);
     assert!(change.edits.is_empty());
 }
 
@@ -302,7 +302,7 @@ fn two_fragments_with_differrent_elements_are_differet() {
         p {}
     );
 
-    let (_create, changes) = dom.lazy_diff(left, right);
+    let (_create, changes) = dom.diff_lazynodes(left, right);
     log::debug!("{:#?}", &changes);
     assert_eq!(
         changes.edits,
@@ -335,7 +335,7 @@ fn two_fragments_with_differrent_elements_are_differet_shorter() {
         p {}
     );
 
-    let (create, change) = dom.lazy_diff(left, right);
+    let (create, change) = dom.diff_lazynodes(left, right);
     assert_eq!(
         create.edits,
         [
@@ -391,7 +391,7 @@ fn two_fragments_with_same_elements_are_differet() {
         p {}
     );
 
-    let (create, change) = dom.lazy_diff(left, right);
+    let (create, change) = dom.diff_lazynodes(left, right);
     assert_eq!(
         create.edits,
         [
@@ -433,7 +433,7 @@ fn keyed_diffing_order() {
     let dom = new_dom();
 
     let left = rsx!(
-        // {(0..5).map(|f| {rsx! { div { key: "{f}"  }}})}
+        {(0..5).map(|f| {rsx! { div { key: "{f}"  }}})}
         p {"e"}
     );
     let right = rsx!(
@@ -441,7 +441,7 @@ fn keyed_diffing_order() {
         p {"e"}
     );
 
-    let (create, change) = dom.lazy_diff(left, right);
+    let (create, change) = dom.diff_lazynodes(left, right);
     assert_eq!(
         change.edits,
         [Remove { root: 2 }, Remove { root: 3 }, Remove { root: 4 },]
@@ -465,7 +465,7 @@ fn keyed_diffing_out_of_order() {
         })
     });
 
-    let (_, changes) = dom.lazy_diff(left, right);
+    let (_, changes) = dom.diff_lazynodes(left, right);
     log::debug!("{:?}", &changes);
     assert_eq!(
         changes.edits,
@@ -490,7 +490,7 @@ fn keyed_diffing_out_of_order_adds() {
         })
     });
 
-    let (_, change) = dom.lazy_diff(left, right);
+    let (_, change) = dom.diff_lazynodes(left, right);
     assert_eq!(
         change.edits,
         [
@@ -517,7 +517,7 @@ fn keyed_diffing_out_of_order_adds_2() {
         })
     });
 
-    let (_, change) = dom.lazy_diff(left, right);
+    let (_, change) = dom.diff_lazynodes(left, right);
     assert_eq!(
         change.edits,
         [
@@ -545,7 +545,7 @@ fn keyed_diffing_out_of_order_adds_3() {
         })
     });
 
-    let (_, change) = dom.lazy_diff(left, right);
+    let (_, change) = dom.diff_lazynodes(left, right);
     assert_eq!(
         change.edits,
         [
@@ -573,7 +573,7 @@ fn keyed_diffing_out_of_order_adds_4() {
         })
     });
 
-    let (_, change) = dom.lazy_diff(left, right);
+    let (_, change) = dom.diff_lazynodes(left, right);
     assert_eq!(
         change.edits,
         [
@@ -601,7 +601,7 @@ fn keyed_diffing_out_of_order_adds_5() {
         })
     });
 
-    let (_, change) = dom.lazy_diff(left, right);
+    let (_, change) = dom.diff_lazynodes(left, right);
     assert_eq!(
         change.edits,
         [PushRoot { root: 4 }, InsertBefore { n: 1, root: 3 }]
@@ -624,7 +624,7 @@ fn keyed_diffing_additions() {
         })
     });
 
-    let (_, change) = dom.lazy_diff(left, right);
+    let (_, change) = dom.diff_lazynodes(left, right);
     assert_eq!(
         change.edits,
         [
@@ -657,7 +657,7 @@ fn keyed_diffing_additions_and_moves_on_ends() {
         })
     });
 
-    let (_, change) = dom.lazy_diff(left, right);
+    let (_, change) = dom.diff_lazynodes(left, right);
     log::debug!("{:?}", change);
     assert_eq!(
         change.edits,
@@ -696,7 +696,7 @@ fn keyed_diffing_additions_and_moves_in_middle() {
     });
 
     // LIS: 4, 5, 6
-    let (_, change) = dom.lazy_diff(left, right);
+    let (_, change) = dom.diff_lazynodes(left, right);
     log::debug!("{:#?}", change);
     assert_eq!(
         change.edits,
@@ -745,7 +745,7 @@ fn controlled_keyed_diffing_out_of_order() {
     });
 
     // LIS: 5, 6
-    let (_, changes) = dom.lazy_diff(left, right);
+    let (_, changes) = dom.diff_lazynodes(left, right);
     log::debug!("{:#?}", &changes);
     assert_eq!(
         changes.edits,
@@ -787,7 +787,7 @@ fn controlled_keyed_diffing_out_of_order_max_test() {
         })
     });
 
-    let (_, changes) = dom.lazy_diff(left, right);
+    let (_, changes) = dom.diff_lazynodes(left, right);
     log::debug!("{:#?}", &changes);
     assert_eq!(
         changes.edits,
@@ -802,22 +802,3 @@ fn controlled_keyed_diffing_out_of_order_max_test() {
         ]
     );
 }
-
-#[test]
-fn suspense() {
-    let dom = new_dom();
-
-    todo!()
-    // let edits = dom.create(Some(LazyNodes::new(|f| {
-    //     use std::cell::{Cell, RefCell};
-    //     VNode::Suspended(f.bump().alloc(VSuspended {
-    //         task_id: 0,
-    //         callback: RefCell::new(None),
-    //         dom_id: Cell::new(None),
-    //     }))
-    // })));
-    // assert_eq!(
-    //     edits.edits,
-    //     [CreatePlaceholder { root: 0 }, AppendChildren { many: 1 }]
-    // );
-}

+ 0 - 40
packages/core/tests/display_vdom.rs

@@ -1,40 +0,0 @@
-#![allow(unused, non_upper_case_globals)]
-
-//! test that we can display the virtualdom properly
-//!
-//!
-//!
-
-use dioxus::prelude::*;
-use dioxus_core as dioxus;
-use dioxus_core_macro::*;
-use dioxus_html as dioxus_elements;
-mod test_logging;
-
-#[test]
-fn please_work() {
-    static App: FC<()> = |(cx, props)| {
-        cx.render(rsx! {
-            div {
-                hidden: "true"
-                "hello"
-                div { "hello" }
-                // Child {}
-                // Child {}
-                // Child {}
-            }
-            // div { "hello" }
-        })
-    };
-
-    static Child: FC<()> = |(cx, props)| {
-        cx.render(rsx! {
-            div { "child" }
-        })
-    };
-
-    let mut dom = VirtualDom::new(App);
-    dom.rebuild();
-
-    println!("{}", dom);
-}

+ 274 - 2
packages/core/tests/lifecycle.rs

@@ -3,7 +3,11 @@
 //! Tests for the lifecycle of components.
 use dioxus::prelude::*;
 use dioxus_core as dioxus;
+use dioxus_core::DomEdit::*;
+use dioxus_core::ScopeId;
+
 use dioxus_core_macro::*;
+use dioxus_hooks::*;
 use dioxus_html as dioxus_elements;
 use std::sync::{Arc, Mutex};
 
@@ -20,7 +24,7 @@ fn manual_diffing() {
         value: Shared<&'static str>,
     }
 
-    static App: FC<AppProps> = |(cx, props)| {
+    static App: FC<AppProps> = |cx, props| {
         let val = props.value.lock().unwrap();
         cx.render(rsx! { div { "{val}" } })
     };
@@ -37,7 +41,275 @@ fn manual_diffing() {
 
     *value.lock().unwrap() = "goodbye";
 
-    let edits = dom.diff();
+    let edits = dom.rebuild();
 
     log::debug!("edits: {:?}", edits);
 }
+
+#[test]
+fn events_generate() {
+    static App: FC<()> = |cx, _| {
+        let mut count = use_state(cx, || 0);
+
+        let inner = match *count {
+            0 => {
+                rsx! {
+                    div {
+                        onclick: move |_| count += 1,
+                        div {
+                            "nested"
+                        }
+                        "Click me!"
+                    }
+                }
+            }
+            _ => todo!(),
+        };
+
+        cx.render(inner)
+    };
+
+    let mut dom = VirtualDom::new(App);
+    let mut channel = dom.get_scheduler_channel();
+    assert!(dom.has_any_work());
+
+    let edits = dom.work_with_deadline(|| false);
+    assert_eq!(
+        edits[0].edits,
+        [
+            CreateElement {
+                tag: "div",
+                root: 0,
+            },
+            NewEventListener {
+                event_name: "click",
+                scope: ScopeId(0),
+                root: 0,
+            },
+            CreateElement {
+                tag: "div",
+                root: 1,
+            },
+            CreateTextNode {
+                text: "nested",
+                root: 2,
+            },
+            AppendChildren { many: 1 },
+            CreateTextNode {
+                text: "Click me!",
+                root: 3,
+            },
+            AppendChildren { many: 2 },
+        ]
+    )
+}
+
+#[test]
+fn components_generate() {
+    static App: FC<()> = |cx, _| {
+        let mut render_phase = use_state(cx, || 0);
+        render_phase += 1;
+
+        cx.render(match *render_phase {
+            0 => rsx!("Text0"),
+            1 => rsx!(div {}),
+            2 => rsx!("Text2"),
+            3 => rsx!(Child {}),
+            4 => rsx!({ None as Option<()> }),
+            5 => rsx!("text 3"),
+            6 => rsx!({ (0..2).map(|f| rsx!("text {f}")) }),
+            7 => rsx!(Child {}),
+            _ => todo!(),
+        })
+    };
+
+    static Child: FC<()> = |cx, _| {
+        println!("running child");
+        cx.render(rsx! {
+            h1 {}
+        })
+    };
+
+    let mut dom = VirtualDom::new(App);
+    let edits = dom.rebuild();
+    assert_eq!(
+        edits.edits,
+        [
+            CreateTextNode {
+                text: "Text0",
+                root: 0,
+            },
+            AppendChildren { many: 1 },
+        ]
+    );
+
+    let edits = dom.hard_diff(&ScopeId(0)).unwrap();
+    assert_eq!(
+        edits.edits,
+        [
+            CreateElement {
+                tag: "div",
+                root: 1,
+            },
+            ReplaceWith { root: 0, m: 1 },
+        ]
+    );
+
+    let edits = dom.hard_diff(&ScopeId(0)).unwrap();
+    assert_eq!(
+        edits.edits,
+        [
+            CreateTextNode {
+                text: "Text2",
+                root: 2,
+            },
+            ReplaceWith { root: 1, m: 1 },
+        ]
+    );
+
+    let edits = dom.hard_diff(&ScopeId(0)).unwrap();
+    assert_eq!(
+        edits.edits,
+        [
+            CreateElement { tag: "h1", root: 3 },
+            ReplaceWith { root: 2, m: 1 },
+        ]
+    );
+
+    let edits = dom.hard_diff(&ScopeId(0)).unwrap();
+    assert_eq!(
+        edits.edits,
+        [CreatePlaceholder { root: 4 }, ReplaceWith { root: 3, m: 1 },]
+    );
+
+    let edits = dom.hard_diff(&ScopeId(0)).unwrap();
+    assert_eq!(
+        edits.edits,
+        [
+            CreateTextNode {
+                text: "text 3",
+                root: 5,
+            },
+            ReplaceWith { root: 4, m: 1 },
+        ]
+    );
+
+    let edits = dom.hard_diff(&ScopeId(0)).unwrap();
+    assert_eq!(
+        edits.edits,
+        [
+            CreateTextNode {
+                text: "text 0",
+                root: 6,
+            },
+            CreateTextNode {
+                text: "text 1",
+                root: 7,
+            },
+            ReplaceWith { root: 5, m: 2 },
+        ]
+    );
+
+    let edits = dom.hard_diff(&ScopeId(0)).unwrap();
+    assert_eq!(
+        edits.edits,
+        [
+            CreateElement { tag: "h1", root: 8 },
+            ReplaceWith { root: 6, m: 1 },
+            Remove { root: 7 },
+        ]
+    );
+}
+
+#[test]
+fn component_swap() {
+    // simple_logger::init();
+    static App: FC<()> = |cx, _| {
+        let mut render_phase = use_state(cx, || 0);
+        render_phase += 1;
+
+        cx.render(match *render_phase {
+            0 => rsx!(
+                div {
+                    NavBar {}
+                    Dashboard {}
+                }
+            ),
+            1 => rsx!(
+                div {
+                    NavBar {}
+                    Results {}
+                }
+            ),
+            2 => rsx!(
+                div {
+                    NavBar {}
+                    Dashboard {}
+                }
+            ),
+            3 => rsx!(
+                div {
+                    NavBar {}
+                    Results {}
+                }
+            ),
+            4 => rsx!(
+                div {
+                    NavBar {}
+                    Dashboard {}
+                }
+            ),
+            _ => rsx!("blah"),
+        })
+    };
+
+    static NavBar: FC<()> = |cx, _| {
+        println!("running navbar");
+        cx.render(rsx! {
+            h1 {
+                "NavBar"
+                {(0..3).map(|f| rsx!(NavLink {}))}
+            }
+        })
+    };
+
+    static NavLink: FC<()> = |cx, _| {
+        println!("running navlink");
+        cx.render(rsx! {
+            h1 {
+                "NavLink"
+            }
+        })
+    };
+
+    static Dashboard: FC<()> = |cx, _| {
+        println!("running dashboard");
+        cx.render(rsx! {
+            div {
+                "dashboard"
+            }
+        })
+    };
+
+    static Results: FC<()> = |cx, _| {
+        println!("running results");
+        cx.render(rsx! {
+            div {
+                "results"
+            }
+        })
+    };
+
+    let mut dom = VirtualDom::new(App);
+    let edits = dom.rebuild();
+    dbg!(&edits);
+
+    let edits = dom.work_with_deadline(|| false);
+    dbg!(&edits);
+    let edits = dom.work_with_deadline(|| false);
+    dbg!(&edits);
+    let edits = dom.work_with_deadline(|| false);
+    dbg!(&edits);
+    let edits = dom.work_with_deadline(|| false);
+    dbg!(&edits);
+}

+ 3 - 3
packages/core/tests/sharedstate.rs

@@ -1,6 +1,6 @@
 #![allow(unused, non_upper_case_globals)]
 
-use dioxus::{prelude::*, DomEdit, Mutations, TestDom};
+use dioxus::{prelude::*, DomEdit, Mutations};
 use dioxus_core as dioxus;
 use dioxus_core_macro::*;
 use dioxus_html as dioxus_elements;
@@ -13,12 +13,12 @@ mod test_logging;
 fn shared_state_test() {
     struct MySharedState(&'static str);
 
-    static App: FC<()> = |(cx, props)| {
+    static App: FC<()> = |cx, props| {
         cx.provide_state(MySharedState("world!"));
         cx.render(rsx!(Child {}))
     };
 
-    static Child: FC<()> = |(cx, props)| {
+    static Child: FC<()> = |cx, props| {
         let shared = cx.consume_state::<MySharedState>()?;
         cx.render(rsx!("Hello, {shared.0}"))
     };

+ 1 - 0
packages/core/tests/task.rs

@@ -0,0 +1 @@
+fn main() {}

+ 15 - 14
packages/core/tests/vdom_rebuild.rs

@@ -17,7 +17,7 @@ use dioxus_html as dioxus_elements;
 
 #[test]
 fn app_runs() {
-    static App: FC<()> = |(cx, props)| {
+    static App: FC<()> = |cx, props| {
         //
         cx.render(rsx!( div{"hello"} ))
     };
@@ -28,7 +28,7 @@ fn app_runs() {
 
 #[test]
 fn fragments_work() {
-    static App: FC<()> = |(cx, props)| {
+    static App: FC<()> = |cx, props| {
         cx.render(rsx!(
             div{"hello"}
             div{"goodbye"}
@@ -42,7 +42,7 @@ fn fragments_work() {
 
 #[test]
 fn lists_work() {
-    static App: FC<()> = |(cx, props)| {
+    static App: FC<()> = |cx, props| {
         cx.render(rsx!(
             h1 {"hello"}
             {(0..6).map(|f| rsx!(span{ "{f}" }))}
@@ -55,7 +55,7 @@ fn lists_work() {
 
 #[test]
 fn conditional_rendering() {
-    static App: FC<()> = |(cx, props)| {
+    static App: FC<()> = |cx, props| {
         cx.render(rsx!(
             h1 {"hello"}
             {true.then(|| rsx!(span{ "a" }))}
@@ -72,13 +72,13 @@ fn conditional_rendering() {
 
 #[test]
 fn child_components() {
-    static App: FC<()> = |(cx, props)| {
+    static App: FC<()> = |cx, props| {
         cx.render(rsx!(
             {true.then(|| rsx!(Child { }))}
             {false.then(|| rsx!(Child { }))}
         ))
     };
-    static Child: FC<()> = |(cx, props)| {
+    static Child: FC<()> = |cx, props| {
         cx.render(rsx!(
             h1 {"hello"}
             h1 {"goodbye"}
@@ -91,13 +91,14 @@ fn child_components() {
 
 #[test]
 fn suspended_works() {
-    static App: FC<()> = |(cx, props)| {
-        let title = use_suspense(cx, || async { "bob" }, move |cx, f| todo!());
-        // let title = use_suspense(cx, || async { "bob" }, move |cx, f| rsx! { "{f}"});
-        cx.render(rsx!("hello" { title }))
-    };
+    todo!()
+    // static App: FC<()> = |cx, props| {
+    //     let title = use_suspense(cx, || async { "bob" }, move |cx, f| todo!());
+    //     // let title = use_suspense(cx, || async { "bob" }, move |cx, f| rsx! { "{f}"});
+    //     cx.render(rsx!("hello" { title }))
+    // };
 
-    let mut vdom = VirtualDom::new(App);
-    let edits = vdom.rebuild();
-    dbg!(edits);
+    // let mut vdom = VirtualDom::new(App);
+    // let edits = vdom.rebuild();
+    // dbg!(edits);
 }

+ 1 - 1
packages/desktop/Cargo.toml

@@ -24,6 +24,7 @@ tokio = { version = "1.12.0", features = [
     "rt",
 ], optional = true, default-features = false }
 dioxus-core-macro = { path = "../core-macro" }
+dioxus-html = { path = "../html", features = ["serialize"] }
 
 [features]
 default = ["tokio_runtime"]
@@ -31,6 +32,5 @@ tokio_runtime = ["tokio"]
 
 
 [dev-dependencies]
-dioxus-html = { path = "../html" }
 dioxus-hooks = { path = "../hooks" }
 simple_logger = "1.13.0"

+ 3 - 7
packages/desktop/src/desktop_context.rs

@@ -1,7 +1,6 @@
 use std::cell::RefCell;
 
 use dioxus::prelude::Scope;
-use dioxus::ScopeChildren;
 use dioxus_core as dioxus;
 use dioxus_core::{Context, Element, LazyNodes, NodeFactory, Properties};
 use dioxus_core_macro::Props;
@@ -44,7 +43,7 @@ pub struct WebviewWindowProps<'a> {
     /// focuse me
     onfocused: &'a dyn FnMut(()),
 
-    children: ScopeChildren<'a>,
+    children: Element,
 }
 
 /// A handle to a
@@ -57,7 +56,7 @@ pub struct WebviewWindowProps<'a> {
 ///
 ///
 ///
-pub fn WebviewWindow<'a>((cx, props): Scope<'a, WebviewWindowProps>) -> Element<'a> {
+pub fn WebviewWindow(cx: Context, props: &WebviewWindowProps) -> Element {
     let dtcx = cx.consume_state::<RefCell<DesktopContext>>()?;
 
     cx.use_hook(
@@ -67,9 +66,6 @@ pub fn WebviewWindow<'a>((cx, props): Scope<'a, WebviewWindowProps>) -> Element<
         |state| {
             //
         },
-        |hook| {
-            //
-        },
     );
 
     // render the children directly
@@ -94,7 +90,7 @@ fn syntax_works() {
     use dioxus_hooks::*;
     use dioxus_html as dioxus_elements;
 
-    static App: FC<()> = |(cx, props)| {
+    static App: FC<()> = |cx, props| {
         cx.render(rsx! {
             // left window
             WebviewWindow {

+ 1 - 0
packages/desktop/src/err.rs

@@ -0,0 +1 @@
+

+ 6 - 6
packages/desktop/src/events.rs

@@ -4,7 +4,8 @@
 use std::sync::Arc;
 use std::{any::Any, rc::Rc};
 
-use dioxus_core::{events::on::MouseEvent, ElementId, EventPriority, ScopeId, UserEvent};
+use dioxus_core::{ElementId, EventPriority, ScopeId, UserEvent};
+use dioxus_html::on::*;
 
 #[derive(serde::Serialize, serde::Deserialize)]
 struct ImEvent {
@@ -23,7 +24,7 @@ pub fn trigger_from_serialized(val: serde_json::Value) -> UserEvent {
         contents,
     } = ims.into_iter().next().unwrap();
 
-    let scope = ScopeId(scope as usize);
+    let scope_id = ScopeId(scope as usize);
     let mounted_dom_id = Some(ElementId(mounted_dom_id as usize));
 
     let name = event_name_from_typ(&event);
@@ -31,15 +32,14 @@ pub fn trigger_from_serialized(val: serde_json::Value) -> UserEvent {
 
     UserEvent {
         name,
-        event,
-        scope,
+        priority: EventPriority::Low,
+        scope_id,
         mounted_dom_id,
+        event,
     }
 }
 
 fn make_synthetic_event(name: &str, val: serde_json::Value) -> Box<dyn Any + Send> {
-    use dioxus_core::events::on::*;
-
     match name {
         "copy" | "cut" | "paste" => {
             //

+ 3 - 5
packages/desktop/src/lib.rs

@@ -13,7 +13,6 @@ use std::sync::mpsc::channel;
 use std::sync::{Arc, RwLock};
 
 use cfg::DesktopConfig;
-use dioxus_core::scheduler::SchedulerMsg;
 use dioxus_core::*;
 use serde::{Deserialize, Serialize};
 
@@ -167,7 +166,7 @@ pub fn run<T: 'static + Send + Sync>(
                                 sender.unbounded_send(SchedulerMsg::UiEvent(event)).unwrap();
 
                                 if let Some(BridgeEvent::Update(edits)) = rx.blocking_recv() {
-                                    log::info!("bridge received message {:?}", edits);
+                                    log::info!("bridge received message");
                                     Some(RpcResponse::new_result(req.id.take(), Some(edits)))
                                 } else {
                                     log::info!("none received message");
@@ -277,7 +276,7 @@ pub(crate) fn launch_vdom_with_tokio<P: Send + 'static>(
 
         runtime.block_on(async move {
             let mut vir = VirtualDom::new_with_props_and_scheduler(root, props, sender, receiver);
-            let _ = vir.get_event_sender();
+            let _ = vir.get_scheduler_channel();
 
             let edits = vir.rebuild();
 
@@ -299,14 +298,13 @@ pub(crate) fn launch_vdom_with_tokio<P: Send + 'static>(
                 // todo: maybe we want to schedule ourselves in
                 // on average though, the virtualdom running natively is stupid fast
 
-                let mut muts = vir.run_with_deadline(|| false);
+                let mut muts = vir.work_with_deadline(|| false);
 
                 log::debug!("finished running with deadline");
 
                 let mut edits = vec![];
 
                 while let Some(edit) = muts.pop() {
-                    log::debug!("sending message on channel with edit {:?}", edit);
                     let edit_string = serde_json::to_value(Evt { edits: edit.edits })
                         .expect("serializing edits should never fail");
                     edits.push(edit_string);

+ 11 - 8
packages/hooks/src/use_shared_state.rs

@@ -17,6 +17,8 @@ pub(crate) struct ProvidedStateInner<T> {
 impl<T> ProvidedStateInner<T> {
     pub(crate) fn notify_consumers(&mut self) {
         for consumer in self.consumers.iter() {
+            println!("notifiying {:?}", consumer);
+            // log::debug("notifiying {:?}", consumer);
             (self.notify_any)(*consumer);
         }
     }
@@ -82,13 +84,6 @@ pub fn use_shared_state<'a, T: 'static>(cx: Context<'a>) -> Option<UseSharedStat
                 _ => None,
             }
         },
-        |f| {
-            // we need to unsubscribe when our component is unounted
-            if let Some(root) = &f.root {
-                let mut root = root.borrow_mut();
-                root.consumers.remove(&f.scope_id);
-            }
-        },
     )
 }
 
@@ -98,6 +93,15 @@ struct SharedStateInner<T: 'static> {
     scope_id: ScopeId,
     needs_notification: Cell<bool>,
 }
+impl<T> Drop for SharedStateInner<T> {
+    fn drop(&mut self) {
+        // we need to unsubscribe when our component is unounted
+        if let Some(root) = &self.root {
+            let mut root = root.borrow_mut();
+            root.consumers.remove(&self.scope_id);
+        }
+    }
+}
 
 pub struct UseSharedState<'a, T: 'static> {
     pub(crate) cx: Context<'a>,
@@ -172,6 +176,5 @@ pub fn use_provide_state<'a, T: 'static>(cx: Context<'a>, f: impl FnOnce() -> T)
             cx.provide_state(state)
         },
         |inner| {},
-        |_| {},
     )
 }

+ 0 - 1
packages/hooks/src/useref.rs

@@ -16,7 +16,6 @@ pub fn use_ref<T: 'static>(cx: Context, f: impl FnOnce() -> T) -> UseRef<T> {
             inner.update_scheuled.set(false);
             UseRef { inner }
         },
-        |_| {},
     )
 }
 

+ 0 - 1
packages/hooks/src/usestate.rs

@@ -69,7 +69,6 @@ pub fn use_state<'a, T: 'static>(
 
             UseState { inner: &*hook }
         },
-        |_| {},
     )
 }
 struct UseStateInner<T: 'static> {

+ 8 - 3
packages/html/Cargo.toml

@@ -9,7 +9,12 @@ description = "HTML Element pack for Dioxus - a concurrent renderer-agnostic Vir
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
-dioxus-core = { path = "../core", version = "0.1.2" }
+dioxus-core = { path = "../core", version = "0.1.3" }
+# Serialize the Edits for use in Webview/Liveview instances
+serde = { version = "1", features = ["derive"], optional = true }
+serde_repr = { version = "0.1.7", optional = true }
 
-[dev-dependencies]
-scraper = "0.12.0"
+
+[features]
+default = []
+serialize = ["serde", "serde_repr"]

部分文件因文件數量過多而無法顯示